├── debian ├── source │ └── format ├── touch-keyboard.install ├── touch-keyboard-udeb.install ├── touch-keyboard-udeb.postinst ├── touch-keyboard.lintian-overrides ├── rules ├── touch-keyboard.postinst ├── control ├── copyright └── changelog ├── layout-touchpad.csv ├── touch-hw.csv ├── touch-keyboard-handler.service ├── layouts ├── README ├── YB1-X9x-pc104.csv └── YB1-X9x-pc105.csv ├── 61-evdev-yogabook.hwdb ├── statemachine ├── slot.cc ├── eventkey.cc ├── slot.h ├── statemachine.cc ├── eventkey.h └── statemachine.h ├── logging.cc ├── logging.h ├── README.md ├── 60-touch-keyboard.rules ├── base_macros.h ├── .github └── workflows │ └── cmake-single-platform.yml ├── uinput_definitions.h ├── haptic ├── ff_driver.h ├── ff_driver.cc ├── touch_ff_manager.cc └── touch_ff_manager.h ├── CMakeLists.txt ├── evdevsource.cc ├── LICENSE.md ├── evdevsource.h ├── syscallhandler.h ├── README.upstream.md ├── faketouchpad.h ├── uinputdevice.h ├── main.cc ├── uinputdevice.cc ├── faketouchpad.cc ├── fakekeyboard.h ├── fakekeyboard.cc └── csv.h /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /debian/touch-keyboard.install: -------------------------------------------------------------------------------- 1 | usr/ 2 | etc/ 3 | -------------------------------------------------------------------------------- /debian/touch-keyboard-udeb.install: -------------------------------------------------------------------------------- 1 | touch-keyboard.install -------------------------------------------------------------------------------- /debian/touch-keyboard-udeb.postinst: -------------------------------------------------------------------------------- 1 | touch-keyboard.postinst -------------------------------------------------------------------------------- /layout-touchpad.csv: -------------------------------------------------------------------------------- 1 | x1;y1;x2;y2 2 | 78;98,5;145,5;133 3 | -------------------------------------------------------------------------------- /debian/touch-keyboard.lintian-overrides: -------------------------------------------------------------------------------- 1 | custom-library-search-path 2 | 3 | -------------------------------------------------------------------------------- /touch-hw.csv: -------------------------------------------------------------------------------- 1 | resolution_x;resolution_y;width_mm;height_mm;left_margin_mm;top_margin_mm;rotation_cw 2 | 1200;1920;139;242;0;4;270 3 | -------------------------------------------------------------------------------- /touch-keyboard-handler.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Touch keyboard handler 3 | 4 | [Service] 5 | WorkingDirectory=/etc/touch_keyboard 6 | ExecStart=/usr/sbin/touch_keyboard_handler -m 1.0 -D 6 7 | Type=simple 8 | 9 | -------------------------------------------------------------------------------- /layouts/README: -------------------------------------------------------------------------------- 1 | This directory contains layouts files for virtual keyboard variants. 2 | 3 | Files: 4 | YB1-X9x-pc104.csv – Yoga Book YB1-X9* PC-104-like layout (Russian, US) 5 | YB1-X9x-pc105.csv - Yoga Book YB1-X9* PC-105-like layout (International) 6 | 7 | -------------------------------------------------------------------------------- /61-evdev-yogabook.hwdb: -------------------------------------------------------------------------------- 1 | evdev:name:Goodix Capacitive TouchScreen:dmi:bvnLENOVO*pnLenovoYB1-X9* 2 | TOUCH_KEYBOARD=1 3 | LIBINPUT_IGNORE_DEVICE=1 4 | 5 | evdev:name:Goodix Capacitive TouchScreen:dmi:*pnCHERRYVIEWD1PLATFORM*:pvrYETI-11*:* 6 | TOUCH_KEYBOARD=1 7 | LIBINPUT_IGNORE_DEVICE=1 8 | 9 | evdev:name:virtual-touchpad:dmi:bvnLENOVO*pnLenovoYB1-X9* 10 | ID_INPUT_TOUCHPAD_INTEGRATION=internal 11 | 12 | evdev:name:virtual-touchpad:dmi:*pnCHERRYVIEWD1PLATFORM*:pvrYETI-11*:* 13 | ID_INPUT_TOUCHPAD_INTEGRATION=internal 14 | -------------------------------------------------------------------------------- /statemachine/slot.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Chromium OS Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #include "statemachine/slot.h" 6 | 7 | namespace mtstatemachine { 8 | 9 | int Slot::FindValueByEvent(int ev_type, int ev_code) const { 10 | EventKey x_key(ev_type, ev_code); 11 | auto it = find(x_key); 12 | if (it != end()) { 13 | return it->second; 14 | } 15 | return kSlotMissingValue; 16 | } 17 | 18 | } // namespace mtstatemachine 19 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # See debhelper(7) (uncomment to enable) 3 | # output every command that modifies files on the build system. 4 | # export DH_VERBOSE = 1 5 | 6 | 7 | # see FEATURE AREAS in dpkg-buildflags(1) 8 | #export DEB_BUILD_MAINT_OPTIONS = hardening=+all 9 | 10 | # see ENVIRONMENT in dpkg-buildflags(1) 11 | # package maintainers to append CFLAGS 12 | #export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic 13 | # package maintainers to append LDFLAGS 14 | #export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed 15 | 16 | 17 | %: 18 | dh $@ 19 | 20 | -------------------------------------------------------------------------------- /logging.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "logging.h" 4 | 5 | class NullBuffer : public std::streambuf 6 | { 7 | public: 8 | int overflow(int c) { return c; } 9 | }; 10 | 11 | NullBuffer null_buffer; 12 | std::ostream null_stream(&null_buffer); 13 | 14 | enum LogSeverity min_severity = INFO; 15 | 16 | std::ostream& get_log_stream(enum LogSeverity severity) 17 | { 18 | if (severity >= min_severity) 19 | return std::cout; 20 | else 21 | return null_stream; 22 | } 23 | 24 | void SetMinimumLogSeverity(enum LogSeverity severity) 25 | { 26 | min_severity = severity; 27 | } 28 | -------------------------------------------------------------------------------- /logging.h: -------------------------------------------------------------------------------- 1 | #ifndef _LOGGING_H 2 | #define _LOGGING_H 3 | 4 | #include 5 | 6 | enum LogSeverity { 7 | VERBOSE, 8 | DEBUG, 9 | INFO, 10 | WARNING, 11 | ERROR, 12 | FATAL_WITHOUT_ABORT, 13 | FATAL, 14 | }; 15 | 16 | void SetMinimumLogSeverity(enum LogSeverity severity); 17 | std::ostream& get_log_stream(enum LogSeverity severity); 18 | 19 | //#define LOG(severity) (get_log_stream(severity) << __FILE__ << ":" << __LINE__ << ": ") 20 | #define LOG(severity) (get_log_stream(severity) << #severity[0] << " ") 21 | #define PLOG(severity) LOG(severity) 22 | 23 | 24 | #endif /* _LOGGING_H */ 25 | -------------------------------------------------------------------------------- /debian/touch-keyboard.postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | confdir="/etc/touch_keyboard" 6 | 7 | case "$1" in 8 | configure) 9 | if [ ! -e $confdir/layout.csv ]; then 10 | ln -sf $confdir/layouts/YB1-X9x-pc105.csv $confdir/layout.csv 11 | fi 12 | ;; 13 | 14 | abort-upgrade|abort-remove|abort-deconfigure) 15 | ;; 16 | 17 | *) 18 | echo "postinst called with unknown argument \`$1'" >&2 19 | exit 1 20 | ;; 21 | esac 22 | 23 | # dh_installdeb will replace this with shell code automatically 24 | # generated by other debhelper scripts. 25 | 26 | #DEBHELPER# 27 | 28 | exit 0 29 | 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Touch Keyboard 2 | 3 | This is fork of ChromiumOS touch keyboard driver found at 4 | https://chromium.googlesource.com/chromiumos/platform2/+/19effa94d56d31397a55292771c8ea196f419f1e/touch_keyboard/ 5 | 6 | To get some details about the project, check the [README.upstream.md](README.upstream.md) file. 7 | 8 | ## Build and install 9 | 10 | ``` 11 | apt install build-essential cmake 12 | mkdir build 13 | cd build 14 | cmake ../ 15 | make 16 | sudo make install 17 | ``` 18 | 19 | Or just run the `dpkg-buildpackage -b --no-sign` command to build .deb package. 20 | 21 | ## Configuration 22 | To create custom keyboard layout, edit the file layout.csv and place it as /etc/touch_keyboard/layout.csv. 23 | -------------------------------------------------------------------------------- /60-touch-keyboard.rules: -------------------------------------------------------------------------------- 1 | ACTION=="remove", GOTO="default_end" 2 | 3 | SUBSYSTEM=="input", KERNEL=="event*", ENV{TOUCH_KEYBOARD}=="1", SYMLINK+="touch_keyboard", TAG+="systemd", ENV{SYSTEMD_WANTS}+="touch-keyboard-handler.service" 4 | 5 | # Create /dev/{left,right}_vibrator symlinks for DRV2604 haptic devices 6 | KERNEL=="event*", SUBSYSTEM=="input", KERNELS=="i2c-DRV2604:00", SYMLINK+="right_vibrator" 7 | KERNEL=="event*", SUBSYSTEM=="input", KERNELS=="i2c-DRV2604:01", SYMLINK+="left_vibrator" 8 | KERNEL=="event*", SUBSYSTEM=="input", KERNELS=="i2c-drv2604l.0", SYMLINK+="right_vibrator" 9 | KERNEL=="event*", SUBSYSTEM=="input", KERNELS=="i2c-drv2604l.1", SYMLINK+="left_vibrator" 10 | 11 | LABEL="default_end" 12 | 13 | -------------------------------------------------------------------------------- /base_macros.h: -------------------------------------------------------------------------------- 1 | #ifndef _BASE_MACROS_H 2 | #define _BASE_MACROS_H 3 | 4 | // A macro to disallow the copy constructor and operator= functions 5 | // This must be placed in the private: declarations for a class. 6 | // 7 | // For disallowing only assign or copy, delete the relevant operator or 8 | // constructor, for example: 9 | // void operator=(const TypeName&) = delete; 10 | // Note, that most uses of DISALLOW_ASSIGN and DISALLOW_COPY are broken 11 | // semantically, one should either use disallow both or neither. Try to 12 | // avoid these in new code. 13 | #define DISALLOW_COPY_AND_ASSIGN(TypeName) \ 14 | TypeName(const TypeName&) = delete; \ 15 | void operator=(const TypeName&) = delete 16 | 17 | #endif /* _BASE_MACROS_H */ 18 | -------------------------------------------------------------------------------- /.github/workflows/cmake-single-platform.yml: -------------------------------------------------------------------------------- 1 | name: Packages for Ubuntu 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Build Ubuntu packages 17 | run: | 18 | sudo apt install build-essential cmake debhelper-compat 19 | dpkg-buildpackage --no-sign 20 | mkdir out-debs 21 | cp ../*.deb out-debs/ 22 | cp ../*.ddeb out-debs/ 23 | cp ../*.changes out-debs/ 24 | cp ../*.buildinfo out-debs/ 25 | cp ../*.tar.xz out-debs/ 26 | cp ../*.dsc out-debs/ 27 | 28 | - name: Upload artifacts 29 | uses: actions/upload-artifact@v5.0.0 30 | with: 31 | name: debs 32 | path: out-debs/* 33 | -------------------------------------------------------------------------------- /uinput_definitions.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Chromium OS Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #ifndef TOUCH_KEYBOARD_UINPUT_DEFINITIONS_H_ 6 | #define TOUCH_KEYBOARD_UINPUT_DEFINITIONS_H_ 7 | 8 | #include 9 | 10 | /* Define these structs and ioctls so this can compile against an old kernel 11 | * that doesn't support these features yet. The kernel this code runs on 12 | * must support it, but adding these here allows it to build elsewhere. 13 | * Without these, uinput can't correctly support setting the axis ranges for 14 | * absolute axes which is essential for simulated touch devices. 15 | */ 16 | 17 | #ifndef UI_ABS_SETUP 18 | #define UI_ABS_SETUP _IOW(UINPUT_IOCTL_BASE, 4, struct uinput_abs_setup) 19 | struct uinput_abs_setup { 20 | __u16 code; /* axis code */ 21 | /* __u16 filler; */ 22 | struct input_absinfo absinfo; 23 | }; 24 | #endif 25 | 26 | #ifndef UI_DEV_SETUP 27 | #define UI_DEV_SETUP _IOW(UINPUT_IOCTL_BASE, 3, struct uinput_setup) 28 | struct uinput_setup { 29 | struct input_id id; 30 | char name[UINPUT_MAX_NAME_SIZE]; 31 | __u32 ff_effects_max; 32 | }; 33 | #endif 34 | 35 | #endif // TOUCH_KEYBOARD_UINPUT_DEFINITIONS_H_ 36 | -------------------------------------------------------------------------------- /statemachine/eventkey.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Chromium OS Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #include "statemachine/eventkey.h" 6 | 7 | namespace mtstatemachine { 8 | 9 | bool EventKey::operator==(const EventKey& other) const { 10 | return (type_ == other.type_ && code_ == other.code_); 11 | } 12 | 13 | bool EventKey::IsSyn() const { 14 | return (type_ == EV_SYN && code_ == SYN_REPORT); 15 | } 16 | 17 | bool EventKey::IsSlot() const { 18 | return (type_ == EV_ABS && code_ == ABS_MT_SLOT); 19 | } 20 | 21 | bool EventKey::IsTrackingID() const { 22 | return (type_ == EV_ABS && code_ == ABS_MT_TRACKING_ID); 23 | } 24 | 25 | bool EventKey::IsX() const { 26 | return (type_ == EV_ABS && code_ == ABS_MT_POSITION_X); 27 | } 28 | 29 | bool EventKey::IsY() const { 30 | return (type_ == EV_ABS && code_ == ABS_MT_POSITION_Y); 31 | } 32 | 33 | bool EventKey::IsSupportedForTouchpads() const { 34 | if (type_ != EV_ABS) 35 | return false; 36 | return (code_ == ABS_MT_TRACKING_ID || 37 | code_ == ABS_MT_PRESSURE || 38 | code_ == ABS_MT_POSITION_X || 39 | code_ == ABS_MT_POSITION_Y || 40 | code_ == ABS_MT_TOUCH_MAJOR || 41 | code_ == ABS_MT_TOUCH_MINOR); 42 | } 43 | 44 | } // namespace mtstatemachine 45 | -------------------------------------------------------------------------------- /haptic/ff_driver.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Chromium OS Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #ifndef TOUCH_KEYBOARD_HAPTIC_FF_DRIVER_H_ 6 | #define TOUCH_KEYBOARD_HAPTIC_FF_DRIVER_H_ 7 | 8 | #include 9 | 10 | #include "base_macros.h" 11 | 12 | namespace touch_keyboard { 13 | 14 | class FFDriver { 15 | /* FFDriver is one haptic driver. 16 | * Once it is initialized, you can upload effect and later play 17 | * the effect. 18 | */ 19 | public: 20 | FFDriver(); 21 | 22 | ~FFDriver(); 23 | 24 | // Try to open the device at the path. Will return false if fails. 25 | bool Init(const std::string& device_path); 26 | 27 | // Upload an effect for the event. It will return the effect id. 28 | // When it fails to upload, id -1 is returned. 29 | int UploadEffect(float magnitude, int time_ms); 30 | 31 | // Play the effect loaded corresponding to the event. It will return 32 | // false if fails. 33 | bool PlayEffect(int id); 34 | 35 | private: 36 | // This is the file descriptor for the driver. 37 | int fd_; 38 | 39 | // This function close the fd 40 | void CloseFDIfValid(); 41 | 42 | DISALLOW_COPY_AND_ASSIGN(FFDriver); 43 | }; 44 | 45 | } // namespace touch_keyboard 46 | 47 | #endif // TOUCH_KEYBOARD_HAPTIC_FF_DRIVER_H_ 48 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | 3 | project(chromiumos_touch_keyboard) 4 | 5 | find_package(PkgConfig) 6 | 7 | include_directories("${PROJECT_BINARY_DIR}") 8 | include_directories("${PROJECT_SOURCE_DIR}") 9 | 10 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror -Wall -Wextra -pedantic -Wno-unknown-pragmas") 11 | 12 | add_executable(touch_keyboard_handler 13 | main.cc 14 | evdevsource.cc 15 | fakekeyboard.cc 16 | faketouchpad.cc 17 | uinputdevice.cc 18 | haptic/ff_driver.cc 19 | haptic/touch_ff_manager.cc 20 | statemachine/eventkey.cc 21 | statemachine/slot.cc 22 | statemachine/statemachine.cc 23 | logging.cc 24 | ) 25 | 26 | include(GNUInstallDirs) 27 | 28 | pkg_check_modules(SYSTEMD "systemd") 29 | pkg_get_variable(SYSTEMD_SYSTEM_UNIT_DIR systemd systemdsystemunitdir) 30 | 31 | pkg_check_modules(UDEV "udev") 32 | pkg_get_variable(UDEV_DIR udev udevdir) 33 | 34 | install(TARGETS touch_keyboard_handler 35 | DESTINATION "${CMAKE_INSTALL_SBINDIR}") 36 | 37 | install(DIRECTORY layouts 38 | DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/touch_keyboard) 39 | 40 | install(FILES layout-touchpad.csv touch-hw.csv 41 | DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/touch_keyboard) 42 | 43 | install(FILES 60-touch-keyboard.rules 44 | DESTINATION ${UDEV_DIR}/rules.d) 45 | 46 | install(FILES 61-evdev-yogabook.hwdb 47 | DESTINATION ${UDEV_DIR}/hwdb.d) 48 | 49 | install(FILES touch-keyboard-handler.service 50 | DESTINATION ${SYSTEMD_SYSTEM_UNIT_DIR}) 51 | 52 | -------------------------------------------------------------------------------- /statemachine/slot.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Chromium OS Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #ifndef TOUCH_KEYBOARD_STATEMACHINE_SLOT_H_ 6 | #define TOUCH_KEYBOARD_STATEMACHINE_SLOT_H_ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "statemachine/eventkey.h" 14 | 15 | namespace mtstatemachine { 16 | 17 | constexpr int kSlotMissingValue = -1; 18 | 19 | class Slot : public std::unordered_map { 20 | /* Storage for all the event data for a single multitouch slot 21 | * 22 | * Multitouch works by filling "slots" with key-value pairs. Each slot 23 | * corresponds with a single finger and is incrementally updated until a SYN 24 | * event is finally sent, indicating that the slot has been fully updated and 25 | * the current values are valid. This class implements a slot as a mapping 26 | * from event keys to their values. Slots are used by MtStateMachine to 27 | * store all the touch information it receives. 28 | */ 29 | public: 30 | // Scan through the slot's fields for one that matches this event type and 31 | // code. If a match is found, return it. Otherwise return kSlotMissingValue. 32 | int FindValueByEvent(int ev_type, int ev_code) const; 33 | }; 34 | 35 | } // namespace mtstatemachine 36 | 37 | #endif // TOUCH_KEYBOARD_STATEMACHINE_SLOT_H_ 38 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: touch-keyboard 2 | Section: misc 3 | Priority: optional 4 | Maintainer: Yauhen Kharuzhy 5 | Build-Depends: cmake, debhelper-compat (= 13) 6 | Standards-Version: 4.5.0 7 | #Vcs-Browser: https://salsa.debian.org/debian/touch-keyboard 8 | #Vcs-Git: https://salsa.debian.org/debian/touch-keyboard.git 9 | Rules-Requires-Root: no 10 | 11 | Package: touch-keyboard 12 | Architecture: any 13 | Depends: ${shlibs:Depends}, ${misc:Depends} 14 | Description: Userspace driver for touchpad-based keyboard 15 | The userspace driver to support touch keyboard based on touchpad. Such 16 | keyboard may be found in some tablets (Lenovo Yoga Book YB1-X9* for example). 17 | . 18 | This package is fork of touch keyboard driver from ChromiumOS. It contains 19 | keyboard geometry description for Lenovo Yoga Book but can be reconfigured for 20 | any touch keyboad. 21 | 22 | Package: touch-keyboard-udeb 23 | Architecture: any 24 | Package-Type: udeb 25 | Depends: ${shlibs:Depends}, ${misc:Depends} 26 | Section: debian-installer 27 | Description: Touchpad-based keyboard support for the debian-installer 28 | The userspace driver to support touch keyboard based on touchpad. Such 29 | keyboard may be found in some tablets (Lenovo Yoga Book YB1-X9* for example). 30 | . 31 | This package is fork of touch keyboard driver from ChromiumOS. It contains 32 | keyboard geometry description for Lenovo Yoga Book but can be reconfigured for 33 | any touch keyboad. 34 | . 35 | The udeb is used by the debian-installer. 36 | -------------------------------------------------------------------------------- /evdevsource.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Chromium OS Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #include "evdevsource.h" 6 | 7 | namespace touch_keyboard { 8 | 9 | bool EvdevSource::OpenSourceDevice(std::string const &source_device_path) { 10 | source_fd_ = syscall_handler_->open(source_device_path.c_str(), O_RDONLY); 11 | if (source_fd_ < 0) { 12 | PLOG(ERROR) << "Failed to open() source device " << source_device_path << ". (" << source_fd_ << ")\n"; 13 | return false; 14 | } 15 | return true; 16 | } 17 | 18 | bool EvdevSource::GetNextEvent(int timeout_ms, struct input_event *ev) const { 19 | if (timeout_ms > 0) { 20 | int num_ready; 21 | struct timeval timeout = {0, timeout_ms * 1000}; 22 | fd_set set; 23 | 24 | // Block until there's something to read or we hit a timeout. 25 | FD_ZERO(&set); 26 | FD_SET(source_fd_, &set); 27 | num_ready = syscall_handler_->select(source_fd_ + 1, &set, 28 | NULL, NULL, &timeout); 29 | 30 | // If the timeout triggered, return false instead of waiting forever. 31 | if (num_ready != 1) { 32 | return false; 33 | } 34 | } 35 | 36 | int num_bytes_read = syscall_handler_->read(source_fd_, ev, sizeof(*ev)); 37 | if (num_bytes_read != sizeof(*ev)) { 38 | PLOG(ERROR) << "ERROR: A read failed to read an entire event. Only read " << 39 | num_bytes_read << " of " << sizeof(*ev) << "expected bytes.\n"; 40 | if (num_bytes_read == -1) { 41 | throw "Evdev read() failed"; 42 | } 43 | return false; 44 | } 45 | return true; 46 | } 47 | 48 | } // namespace touch_keyboard 49 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2014 The ChromiumOS Authors 2 | 3 | Copyright 2019-2025 Yauhen Kharuzhy 4 | and other contributors 5 | (https://github.com/jekhor/chromiumos_touch_keyboard/graphs/contributors) 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are 9 | met: 10 | 11 | 1. Redistributions of source code must retain the above copyright 12 | notice, this list of conditions and the following disclaimer. 13 | 14 | 2. Redistributions in binary form must reproduce the above 15 | copyright notice, this list of conditions and the following disclaimer 16 | in the documentation and/or other materials provided with the 17 | distribution. 18 | 19 | 3. Neither the name of Google LLC nor the names of contributors may 20 | be used to endorse or promote products derived from 21 | this software without specific prior written permission. 22 | 23 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 26 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 27 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 28 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 29 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 30 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 31 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 33 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | -------------------------------------------------------------------------------- /statemachine/statemachine.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Chromium OS Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #include "statemachine/statemachine.h" 6 | 7 | namespace mtstatemachine { 8 | 9 | bool MtStateMachine::AddEvent( 10 | struct input_event const &ev, 11 | std::unordered_map *out_snapshot) { 12 | // Here we process an event. This function returns true at the end of a full 13 | // snapshot of the data (whenever there is a SYN event) and if you 14 | // pass it a pointer to an appropriate map, it will fill it with the 15 | // current state. If you pass NULL, it will skip that step. 16 | EventKey key(ev); 17 | if (key.IsSlot()) { 18 | slot_ = ev.value; 19 | } else if (key.IsSyn()) { 20 | if (out_snapshot) { 21 | FillSnapshot(out_snapshot); 22 | } 23 | return true; 24 | } else if (ev.type == EV_ABS) { 25 | slots_[slot_][key] = ev.value; 26 | } 27 | return false; 28 | } 29 | 30 | void MtStateMachine::FillSnapshot( 31 | std::unordered_map *out_snapshot) { 32 | out_snapshot->clear(); 33 | 34 | for (int slot = 0; slot < kNumSlots; slot++) { 35 | int tid = slots_[slot].FindValueByEvent(EV_ABS, ABS_MT_TRACKING_ID); 36 | if (tid == -1) { 37 | continue; 38 | } 39 | struct MtFinger finger; 40 | finger.x = slots_[slot].FindValueByEvent(EV_ABS, ABS_MT_POSITION_X); 41 | finger.y = slots_[slot].FindValueByEvent(EV_ABS, ABS_MT_POSITION_Y); 42 | finger.p = slots_[slot].FindValueByEvent(EV_ABS, ABS_MT_PRESSURE); 43 | finger.touch_major = slots_[slot].FindValueByEvent(EV_ABS, 44 | ABS_MT_TOUCH_MAJOR); 45 | (*out_snapshot)[tid] = finger; 46 | } 47 | } 48 | 49 | } // namespace mtstatemachine 50 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: touch_keyboard 3 | Upstream-Contact: Yauhen Kharuzhy 4 | Source: https://github.com/jekhor/chromiumos_touch_keyboard 5 | 6 | Files: * 7 | Copyright: 2020 Yauhen Kharuzhy 8 | 2016i-2017 The Chromium OS Authors 9 | License: BSD-3-Clause 10 | 11 | Files: csv.h 12 | Copyright: 2012-2015 Ben Strasser 13 | License: BSD-3-Clause 14 | 15 | Files: debian/* 16 | Copyright: 2020 Yauhen Kharuzhy 17 | License: BSD-3-Clause 18 | 19 | License: BSD-3-Clause 20 | Redistribution and use in source and binary forms, with or without 21 | modification, are permitted provided that the following conditions 22 | are met: 23 | 1. Redistributions of source code must retain the above copyright 24 | notice, this list of conditions and the following disclaimer. 25 | 2. Redistributions in binary form must reproduce the above copyright 26 | notice, this list of conditions and the following disclaimer in the 27 | documentation and/or other materials provided with the distribution. 28 | 3. Neither the name of the University nor the names of its contributors 29 | may be used to endorse or promote products derived from this software 30 | without specific prior written permission. 31 | . 32 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 33 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 34 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 35 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE HOLDERS OR 36 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 37 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 38 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 39 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 40 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 41 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 42 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 43 | -------------------------------------------------------------------------------- /statemachine/eventkey.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Chromium OS Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #ifndef TOUCH_KEYBOARD_STATEMACHINE_EVENTKEY_H_ 6 | #define TOUCH_KEYBOARD_STATEMACHINE_EVENTKEY_H_ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace mtstatemachine { 13 | 14 | class EventKey { 15 | /* An Event Key to correspond to the various values a multitouch device 16 | * can report. (x position, y position, pressure, etc...) 17 | * 18 | * In the kernel, each event is specified as a pair of integers: one 19 | * for the event type (key, absolute position, relative position, etc..) and 20 | * one for the specific event code (which key it is, which axis movement is 21 | * on, etc..). This class represents both of those as a cohesive groups and 22 | * offers several convenience functions for classifying these events. 23 | */ 24 | public: 25 | EventKey(int type, int code): type_(type), code_(code) {} 26 | explicit EventKey(struct input_event const& ev): 27 | type_(ev.type), code_(ev.code) {} 28 | 29 | // Define equality so we can use EventKeys as an std::unordered_map key. 30 | bool operator==(const EventKey& other) const; 31 | 32 | // These Is*() functions identify various types of Events. 33 | bool IsSyn() const; 34 | bool IsSlot() const; 35 | bool IsTrackingID() const; 36 | bool IsX() const; 37 | bool IsY() const; 38 | 39 | // Use this function to check to see if this Event is supported for touchpads. 40 | bool IsSupportedForTouchpads() const; 41 | 42 | int type_, code_; 43 | }; 44 | 45 | struct EventKeyHasher { 46 | // This defines a functor used to Hash EventKeys so they can be an 47 | // std::unordered_map key. Unordered Maps require a hash function for 48 | // whatever datatypes they use as keys. 49 | size_t operator()(const EventKey& key) const { 50 | return (std::hash()(key.type_) ^ 51 | (std::hash()(key.code_) << 1) >> 1); 52 | } 53 | }; 54 | 55 | } // namespace mtstatemachine 56 | 57 | #endif // TOUCH_KEYBOARD_STATEMACHINE_EVENTKEY_H_ 58 | -------------------------------------------------------------------------------- /statemachine/statemachine.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Chromium OS Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #ifndef TOUCH_KEYBOARD_STATEMACHINE_STATEMACHINE_H_ 6 | #define TOUCH_KEYBOARD_STATEMACHINE_STATEMACHINE_H_ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "statemachine/slot.h" 14 | 15 | namespace mtstatemachine { 16 | 17 | constexpr int kNumSlots = 10; 18 | 19 | // The information reported by a state machine for a single finger 20 | struct MtFinger { 21 | int x, y; 22 | int p; 23 | int touch_major; 24 | }; 25 | 26 | class MtStateMachine { 27 | /* Multi-touch State Machine 28 | * 29 | * This class implements a multitouch state machine and interprets the 30 | * individual touch kernel events in a manageable way. To use a state machine 31 | * instantiate a new MtStateMachine object at the beginning of your program 32 | * and then continually call AddEvent() to pass the new touch kernel events 33 | * for processing. AddEvent() will return 'true' at the end of an entire 34 | * frame (SYN event) and then the snapshot map you passed will be populated 35 | * and ready for use. The snapshot is a mapping from tracking IDs to finger 36 | * data at the time of the SYN event. 37 | */ 38 | public: 39 | MtStateMachine(): slot_(0) {} 40 | 41 | // Consume an input event and update the internal state. If this was a SYN 42 | // (which means it's the end of a full update) populate out_snapshot with 43 | // the current state and return true. Otherwise leave out_snapshot unchanged 44 | // and return false to indicate there is still more events on the way. 45 | bool AddEvent(struct input_event const &ev, 46 | std::unordered_map *out_snapshot); 47 | 48 | int slot_; 49 | Slot slots_[kNumSlots]; 50 | 51 | private: 52 | // Populate out_snapshot with the current state of the MtStateMachine. 53 | void FillSnapshot(std::unordered_map *out_snapshot); 54 | }; 55 | 56 | } // namespace mtstatemachine 57 | 58 | #endif // TOUCH_KEYBOARD_STATEMACHINE_STATEMACHINE_H_ 59 | -------------------------------------------------------------------------------- /evdevsource.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Chromium OS Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #ifndef TOUCH_KEYBOARD_EVDEVSOURCE_H_ 6 | #define TOUCH_KEYBOARD_EVDEVSOURCE_H_ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "syscallhandler.h" 19 | 20 | namespace touch_keyboard { 21 | 22 | // Timeout value to use when you want the EvdevSource to block indefinitely 23 | // when calling GetNextEvent(). 24 | constexpr int kNoTimeout = -1; 25 | 26 | class EvdevSource { 27 | /* A class that uses an Evdev device as an event source 28 | * 29 | * This class opens an Evdev device and allows you to easily process the 30 | * events it is producing. Generally speaking, you should probably derive 31 | * from this class instead of instantiating it directly. Essentially you 32 | * should call OpenSourceDevice() at the beginning, then repeatedly 33 | * call GetNextEvent() to collect up the individual events being produced 34 | * by the Evdev device you selected. 35 | */ 36 | public: 37 | EvdevSource() : syscall_handler_(&default_syscall_handler), 38 | source_fd_(-1) { } 39 | explicit EvdevSource(SyscallHandler *syscall_handler) : 40 | syscall_handler_(syscall_handler), source_fd_(-1) { 41 | // This constructor allows you to pass in a SyscallHandler when unit 42 | // testing this class. For real use, allow it to use the default value 43 | // by using the constructor with no arguments. 44 | if (syscall_handler_ == NULL) { 45 | LOG(INFO) << "NULL syscall_handler specified, using default.\n"; 46 | syscall_handler_ = &default_syscall_handler; 47 | } 48 | } 49 | 50 | protected: 51 | // Open the device file on disk and store the descriptor in this object. 52 | bool OpenSourceDevice(std::string const &source_device_path); 53 | // Wait for a new event to come from the source and populate *ev with it. 54 | bool GetNextEvent(int timeout_ms, struct input_event *ev) const; 55 | 56 | SyscallHandler *syscall_handler_; 57 | int source_fd_; 58 | }; 59 | 60 | } // namespace touch_keyboard 61 | 62 | #endif // TOUCH_KEYBOARD_EVDEVSOURCE_H_ 63 | -------------------------------------------------------------------------------- /layouts/YB1-X9x-pc104.csv: -------------------------------------------------------------------------------- 1 | y;height;x;width;name;code;name_fn;code_fn 2 | 0;8;0;18;ESC;1;; 3 | 0;8;21,5;12;F1;59;MUTE;113 4 | 0;8;36;12;F2;60;VOLUMEDOWN;114 5 | 0;8;50,5;12;F3;61;VOLUMEUP;115 6 | 0;8;65;12;F4;62;RFKILL;247 7 | 0;8;80,5;12;F5;63;BRIGHTNESSDOWN;224 8 | 0;8;95;12;F6;64;BRIGHTNESSUP;225 9 | 0;8;109,5;12;F7;65;FIND;136 10 | 0;8;124;12;F8;66;CONFIG;171 11 | 0;8;140;12;F9;67;PREVIOUSSONG;165 12 | 0;8;154;12;F10;68;PLAYPAUSE;164 13 | 0;8;168,5;12;F11;87;NEXTSONG;163 14 | 0;8;183;12;F12;88;SYSRQ;99 15 | 0;8;199;18;DELETE;111;; 16 | 0;25;220,5;20;BACKSPACE;14;; 17 | 11,5;13,7;0;10;GRAVE;41;; 18 | 11,5;13,7;13,5;14,5;1;2;; 19 | 11,5;13,7;31,6;14,5;2;3;; 20 | 11,5;13,7;49,7;14,5;3;4;; 21 | 11,5;13,7;67,8;14,5;4;5;; 22 | 11,5;13,7;85,9;14,5;5;6;; 23 | 11,5;13,7;104;14,5;6;7;; 24 | 11,5;13,7;122,1;14,5;7;8;; 25 | 11,5;13,7;140,2;14,5;8;9;; 26 | 11,5;13,7;158,3;14,5;9;10;; 27 | 11,5;13,7;176,4;14,5;0;11;; 28 | 11,5;13,7;194,5;9,5;MINUS;12;; 29 | 11,5;13,7;207,6;9,5;EQUAL;13;; 30 | 29;13,7;0;14,5;TAB;15;; 31 | 29;13,7;17,8;14,5;Q;16;; 32 | 29;13,7;35,92;14,5;W;17;; 33 | 29;13,7;54,04;14,5;E;18;; 34 | 29;13,7;72,16;14,5;R;19;; 35 | 29;13,7;90,28;14,5;T;20;; 36 | 29;13,7;108,4;14,5;Y;21;; 37 | 29;13,7;126,52;14,5;U;22;; 38 | 29;13,7;144,64;14,5;I;23;; 39 | 29;13,7;162,76;14,5;O;24;; 40 | 29;13,7;180,88;14,5;P;25;; 41 | 29;13,7;199;11,5;LEFTBRACE;26;; 42 | 29;13,7;214,7;11,5;RIGHTBRACE;27;; 43 | 29;13,7;230,4;11;BACKSLASH;43;; 44 | 46,5;13,7;0;19;CAPSLOCK;58;; 45 | 46,5;13,7;22,5;14,5;A;30;; 46 | 46,5;13,7;40,62;14,5;S;31;; 47 | 46,5;13,7;58,74;14,5;D;32;; 48 | 46,5;13,7;76,86;14,5;F;33;; 49 | 46,5;13,7;94,98;14,5;G;34;; 50 | 46,5;13,7;113,1;14,5;H;35;; 51 | 46,5;13,7;131,22;14,5;J;36;; 52 | 46,5;13,7;149,34;14,5;K;37;; 53 | 46,5;13,7;167,46;14,5;L;38;; 54 | 46,5;13,7;185,58;14,5;SEMICOLON;39;; 55 | 46,5;13,7;203,7;11,5;APOSTROPHE;40;; 56 | 46,5;13,7;219;22;ENTER;28;; 57 | 64;13,7;0;28;LEFTSHIFT;42;; 58 | 64;13,7;31,7;14,5;Z;44;; 59 | 64;13,7;49,8;14,5;X;45;; 60 | 64;13,7;67,9;14,5;C;46;; 61 | 64;13,7;86;14,5;V;47;; 62 | 64;13,7;104,1;14,5;B;48;; 63 | 64;13,7;122,2;14,5;N;49;; 64 | 64;13,7;140,3;14,5;M;50;; 65 | 64;13,7;158,4;14,5;COMMA;51;; 66 | 64;13,7;176,5;14,5;DOT;52;; 67 | 64;13,7;194,6;14,5;SLASH;53;; 68 | 64;13,7;212,7;28,5;RIGHTSHIFT;54;; 69 | 81,5;13,7;0;14,5;FN;464;; 70 | 81,5;13,7;18;14,5;LEFTCTRL;29;; 71 | 81,5;13,7;36;10;LEFTMETA;125;; 72 | 81,5;13,7;49,6;14,5;LEFTALT;56;; 73 | 81,5;13,7;67,7;87;SPACE;57;; 74 | 81,5;13,7;158,4;14,5;RIGHTALT;100;; 75 | 81,5;13,7;176,5;14,5;RIGHTCTRL;97;; 76 | 81,5;9,5;194,6;13;PAGEUP;104;; 77 | 81,5;9,5;211,3;13,5;UP;103;; 78 | 81,5;9,5;228,3;13;PAGEDOWN;109;; 79 | 92,7;9,5;194,6;13;LEFT;105;; 80 | 92,7;9,5;211,3;13,5;DOWN;108;; 81 | 92,7;9,5;228,3;13;RIGHT;106;; 82 | 98,5;34,5;145;10;BTN_RIGHT;273;; 83 | 98,5;34,5;68;10;BTN_LEFT;272;; 84 | -------------------------------------------------------------------------------- /syscallhandler.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Chromium OS Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #ifndef TOUCH_KEYBOARD_SYSCALLHANDLER_H_ 6 | #define TOUCH_KEYBOARD_SYSCALLHANDLER_H_ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "uinput_definitions.h" 16 | 17 | namespace touch_keyboard { 18 | 19 | class SyscallHandler { 20 | /* This class wraps raw syscall access when using them in this module. 21 | * 22 | * This class wraps the basic low-level system calls you would make to 23 | * raw file descriptors when interacting with devices on the OS. The 24 | * intention is to allow for us to mock these functions away for improved 25 | * testing, otherwise it's very difficult to write unit tests for 26 | * classes that use open(), read(), ioctl(), etc. 27 | * 28 | * All functions directly mirror the standard syscalls. 29 | */ 30 | public: 31 | virtual int open(const char* pathname, int flags) const { 32 | return ::open(pathname, flags); 33 | } 34 | 35 | virtual ssize_t write(int fd, const void *buf, size_t count) const { 36 | return ::write(fd, buf, count); 37 | } 38 | 39 | virtual ssize_t read(int fd, void *buf, size_t count) const { 40 | return ::read(fd, buf, count); 41 | } 42 | 43 | virtual int select(int nfds, fd_set *readfds, fd_set *writefds, 44 | fd_set *exceptfds, struct timeval *timeout) const { 45 | return ::select(nfds, readfds, writefds, exceptfds, timeout); 46 | } 47 | 48 | virtual int ioctl(int fd, long request_code) const { 49 | return ::ioctl(fd, request_code); 50 | } 51 | 52 | virtual int ioctl(int fd, long request_code, uint64_t arg1) const { 53 | return ::ioctl(fd, request_code, arg1); 54 | } 55 | 56 | virtual int ioctl(int fd, long request_code, int64_t *arg1) const { 57 | return ::ioctl(fd, request_code, arg1); 58 | } 59 | 60 | virtual int ioctl(int fd, long request_code, 61 | struct input_absinfo *arg1) const { 62 | return ::ioctl(fd, request_code, arg1); 63 | } 64 | 65 | virtual int ioctl(int fd, long request_code, 66 | struct uinput_abs_setup *arg1) const { 67 | return ::ioctl(fd, request_code, arg1); 68 | } 69 | 70 | virtual int ioctl(int fd, long request_code, 71 | struct uinput_setup *arg1) const { 72 | return ::ioctl(fd, request_code, arg1); 73 | } 74 | }; 75 | 76 | static SyscallHandler default_syscall_handler; 77 | 78 | } // namespace touch_keyboard 79 | 80 | #endif // TOUCH_KEYBOARD_SYSCALLHANDLER_H_ 81 | -------------------------------------------------------------------------------- /layouts/YB1-X9x-pc105.csv: -------------------------------------------------------------------------------- 1 | y;height;x;width;name;code;name_fn;code_fn 2 | 0;8;0;18;ESC;1;; 3 | 0;8;21,5;12;F1;59;MUTE;113 4 | 0;8;36;12;F2;60;VOLUMEDOWN;114 5 | 0;8;50,5;12;F3;61;VOLUMEUP;115 6 | 0;8;65;12;F4;62;RFKILL;247 7 | 0;8;80,5;12;F5;63;BRIGHTNESSDOWN;224 8 | 0;8;95;12;F6;64;BRIGHTNESSUP;225 9 | 0;8;109,5;12;F7;65;FIND;136 10 | 0;8;124;12;F8;66;CONFIG;171 11 | 0;8;140;12;F9;67;PREVIOUSSONG;165 12 | 0;8;154;12;F10;68;PLAYPAUSE;164 13 | 0;8;168,5;12;F11;87;NEXTSONG;163 14 | 0;8;183;12;F12;88;SYSRQ;99 15 | 0;8;199;18;DELETE;111;; 16 | 0;25;220,5;20;BACKSPACE;14;; 17 | 11,5;13,7;0;10;GRAVE;41;; 18 | 11,5;13,7;13,5;14,5;1;2;; 19 | 11,5;13,7;31,6;14,5;2;3;; 20 | 11,5;13,7;49,7;14,5;3;4;; 21 | 11,5;13,7;67,8;14,5;4;5;; 22 | 11,5;13,7;85,9;14,5;5;6;; 23 | 11,5;13,7;104;14,5;6;7;; 24 | 11,5;13,7;122,1;14,5;7;8;; 25 | 11,5;13,7;140,2;14,5;8;9;; 26 | 11,5;13,7;158,3;14,5;9;10;; 27 | 11,5;13,7;176,4;14,5;0;11;; 28 | 11,5;13,7;194,5;9,5;MINUS;12;; 29 | 11,5;13,7;207,6;9,5;EQUAL;13;; 30 | 29;13,7;0;14,5;TAB;15;; 31 | 29;13,7;17,8;14,5;Q;16;; 32 | 29;13,7;35,92;14,5;W;17;; 33 | 29;13,7;54,04;14,5;E;18;; 34 | 29;13,7;72,16;14,5;R;19;; 35 | 29;13,7;90,28;14,5;T;20;; 36 | 29;13,7;108,4;14,5;Y;21;; 37 | 29;13,7;126,52;14,5;U;22;; 38 | 29;13,7;144,64;14,5;I;23;; 39 | 29;13,7;162,76;14,5;O;24;; 40 | 29;13,7;180,88;14,5;P;25;; 41 | 29;13,7;199;11,5;LEFTBRACE;26;; 42 | 29;13,7;214,7;11,5;RIGHTBRACE;27;; 43 | 29;13,7;226,6;15,4;ENTER;28;; 44 | 46,5;13,7;0;19;CAPSLOCK;58;; 45 | 46,5;13,7;22,5;14,5;A;30;; 46 | 46,5;13,7;40,62;14,5;S;31;; 47 | 46,5;13,7;58,74;14,5;D;32;; 48 | 46,5;13,7;76,86;14,5;F;33;; 49 | 46,5;13,7;94,98;14,5;G;34;; 50 | 46,5;13,7;113,1;14,5;H;35;; 51 | 46,5;13,7;131,22;14,5;J;36;; 52 | 46,5;13,7;149,34;14,5;K;37;; 53 | 46,5;13,7;167,46;14,5;L;38;; 54 | 46,5;13,7;185,58;14,5;SEMICOLON;39;; 55 | 46,5;13,7;203,7;11;APOSTROPHE;40;; 56 | 46,5;13,7;217,5;11;BACKSLASH;43;; 57 | 42,7;13,7;231,5;10,5;ENTER;28;; 58 | 64;13,7;0;15;LEFTSHIFT;42;; 59 | 64;13,7;18;10,5;102ND;86;; 60 | 64;13,7;31,7;14,5;Z;44;; 61 | 64;13,7;49,8;14,5;X;45;; 62 | 64;13,7;67,9;14,5;C;46;; 63 | 64;13,7;86;14,5;V;47;; 64 | 64;13,7;104,1;14,5;B;48;; 65 | 64;13,7;122,2;14,5;N;49;; 66 | 64;13,7;140,3;14,5;M;50;; 67 | 64;13,7;158,4;14,5;COMMA;51;; 68 | 64;13,7;176,5;14,5;DOT;52;; 69 | 64;13,7;194,6;14,5;SLASH;53;; 70 | 64;13,7;212,7;28,5;RIGHTSHIFT;54;; 71 | 81,5;13,7;0;14,5;FN;464;; 72 | 81,5;13,7;18;14,5;LEFTCTRL;29;; 73 | 81,5;13,7;36;10;LEFTMETA;125;; 74 | 81,5;13,7;49,6;14,5;LEFTALT;56;; 75 | 81,5;13,7;67,7;87;SPACE;57;; 76 | 81,5;13,7;158,4;14,5;RIGHTALT;100;; 77 | 81,5;13,7;176,5;14,5;RIGHTCTRL;97;; 78 | 81,5;9,5;194,6;13;PAGEUP;104;; 79 | 81,5;9,5;211,3;13,5;UP;103;; 80 | 81,5;9,5;228,3;13;PAGEDOWN;109;; 81 | 92,7;9,5;194,6;13;LEFT;105;; 82 | 92,7;9,5;211,3;13,5;DOWN;108;; 83 | 92,7;9,5;228,3;13;RIGHT;106;; 84 | 98,5;34,5;145;10;BTN_RIGHT;273;; 85 | 98,5;34,5;68;10;BTN_LEFT;272;; 86 | -------------------------------------------------------------------------------- /haptic/ff_driver.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Chromium OS Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #include "logging.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "haptic/ff_driver.h" 18 | 19 | namespace { 20 | // This value drive the vibrator at max strength. 21 | constexpr int kMaxDriverInput = 0xffff; 22 | } 23 | 24 | namespace touch_keyboard { 25 | 26 | FFDriver::FFDriver() : fd_ {-1} {} 27 | 28 | FFDriver::~FFDriver() { 29 | CloseFDIfValid(); 30 | } 31 | 32 | // Try to open the device, will return false if failed. 33 | bool FFDriver::Init(const std::string& device_path) { 34 | struct input_event ie; 35 | // Close the fd if previously inited. 36 | CloseFDIfValid(); 37 | 38 | fd_ = open(device_path.c_str(), O_RDWR | O_CLOEXEC); 39 | if (fd_ == -1) { 40 | PLOG(ERROR) << "Fail to open haptic device\n"; 41 | return false; 42 | } 43 | 44 | ie.type = EV_FF; 45 | ie.code = FF_GAIN; 46 | ie.value = 0xFFFF; 47 | 48 | if (write(fd_, &ie, sizeof(ie)) == -1) 49 | PLOG(ERROR) << "Failed to set FF gain: " << errno << "\n"; 50 | 51 | return true; 52 | } 53 | 54 | int FFDriver::UploadEffect(float magnitude, int time_ms) { 55 | if (fd_ == -1) { 56 | PLOG(DEBUG) << "Cannot upload effect cause FFDriver is not initialized\n"; 57 | return -1; 58 | } 59 | struct ff_effect effect; 60 | // Set up the effect with parameters. 61 | effect.type = FF_RUMBLE; 62 | effect.id = -1; 63 | effect.u.rumble.strong_magnitude = 64 | static_cast(magnitude * kMaxDriverInput); 65 | effect.u.rumble.weak_magnitude = 0; 66 | effect.replay.length = time_ms; 67 | effect.replay.delay = 0; 68 | 69 | if (ioctl(fd_, EVIOCSFF, &effect) == -1) { 70 | PLOG(ERROR) << "Fail to upload effect\n"; 71 | return -1; 72 | } 73 | 74 | return effect.id; 75 | } 76 | 77 | bool FFDriver::PlayEffect(int id) { 78 | if (fd_ == -1) { 79 | PLOG(DEBUG) << "Cannot play effect cause FFDriver is not initialized\n"; 80 | return false; 81 | } 82 | 83 | if (id < 0) { 84 | PLOG(ERROR) << "Invalid effect id\n"; 85 | return false; 86 | } 87 | 88 | struct input_event play; 89 | memset(&play, 0, sizeof(play)); 90 | play.type = EV_FF; 91 | play.code = id; 92 | play.value = 1; 93 | 94 | if (write(fd_, (const void*) &play, sizeof(play)) != sizeof(play)) { 95 | PLOG(ERROR) << "Fail to play effect\n"; 96 | return false; 97 | } 98 | return true; 99 | } 100 | 101 | void FFDriver::CloseFDIfValid() { 102 | if (fd_ != -1) { 103 | close(fd_); 104 | } 105 | } 106 | 107 | } // namespace touch_keyboard 108 | -------------------------------------------------------------------------------- /README.upstream.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | This package includes the software required for enabling a low-level 3 | touch-based keyboard and touchpad. It includes utilities for reading 4 | touch events from a specified multitouch input device and selecting 5 | a certain region to act as a normal touchpad, then laying out where 6 | various "keys" are for a keyboard. 7 | 8 | To generate the actual events, this package uses the uinput kernel module 9 | to create fake input devices at the kernel level. This allows the 10 | touch keyboard and touchpad to look identical to real, physical devices 11 | and keeps the upper layers consistent. 12 | 13 | When you build this package it will generate an executable called 14 | /usr/bin/touch\_keyboard\_handler that gets started automatically on boot by 15 | an upstart script. 16 | 17 | This package also includes a command line tool to test the vibrator 18 | for the touch keyboard: /usr/bin/touchkb\_haptic\_test. It operates the 19 | symlink of left vibrator and right vibrator under /dev/left\_vibrator 20 | and /dev/right\_vibrator. The proper symlink should be created by udev rules. 21 | 22 | ## Class descriptions 23 | 24 | ### FakeTouchpad 25 | This class is a high-level abstraction that handles everything required to 26 | create a "fake" touchpad from a subregion of a larger touch device by using the 27 | other helper classes defined here. 28 | 29 | ### FakeKeyboard 30 | This class is a high-level abstraction that handles everything required to 31 | create a "fake" keyboard from a subregion of a larger touch device. It knows 32 | where each key is on the pad and generates input events accordingly for the 33 | system as if it were a real keyboard. 34 | 35 | ### EvdevSource 36 | This is a class that can be derived from (as FakeTouchpad does) to easily allow 37 | an object to collect and process Evdev events from an input device. 38 | 39 | ### UinputDevice 40 | This is a class that can be derived from (as FakeTouchpad does) to easily allow 41 | an object to create a brand new input device for the OS by using the uinput 42 | module, and then generate input event programmatically. 43 | 44 | ### MtStateMachine 45 | This class processes Evdev events into a more manageable format 46 | by mimicking the kernel's state machine, and allows us to make sense 47 | of the events captured from the source touch sensor. 48 | 49 | ## Haptic Test 50 | 51 | ### Usage 52 | | parameter | explain | 53 | |---------------|------------| 54 | help | Show help message. 55 | duramtion\_ms | Set the duration of vibration in ms. Default is 1000. 56 | magnitude | Set the strength of vibration, the value is from 0.0 to 1.0. Default is 1.0. 57 | vibrator | Select left or right motor. Default is "left". 58 | 59 | ### Example 60 | Drive the left vibrator at max strength for 500ms: 61 | 62 | $touchkb\_haptic\_test --vibrator=left --magnitude=1.0 --duration\_ms=500 63 | 64 | Drive the right vibrator at half strength for 300ms: 65 | 66 | $touchkb\_haptic\_test --vibrator=right --magnitude=0.5 --duration\_ms=300 67 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | touch-keyboard (1.4.7) unstable; urgency=medium 2 | 3 | * Fix systemd and udev configs paths in CMakeLists.txt to be compatible 4 | with various Linux distros. 5 | 6 | -- Yauhen Kharuzhy Sat, 08 Nov 2025 00:30:36 +0200 7 | 8 | touch-keyboard (1.4.6) unstable; urgency=medium 9 | 10 | * Set force-feedback magnitude and duration from command-line. 11 | * Fix minor building issue 12 | 13 | -- Yauhen Kharuzhy Sun, 02 Nov 2025 23:20:04 +0200 14 | 15 | touch-keyboard (1.4.5) unstable; urgency=medium 16 | 17 | * Vibration feedback support 18 | 19 | -- Yauhen Kharuzhy Sat, 01 Nov 2025 01:28:52 +0200 20 | 21 | touch-keyboard (1.4.4) unstable; urgency=medium 22 | 23 | * Fix some build errors, merged PRs #10, #14, #15 24 | 25 | -- Yauhen Kharuzhy Wed, 29 Oct 2025 01:54:45 +0200 26 | 27 | touch-keyboard (1.4.3) unstable; urgency=medium 28 | 29 | * Add support of Android Yoga Book version 30 | 31 | -- Yauhen Kharuzhy Sun, 26 Oct 2025 13:51:19 +0200 32 | 33 | touch-keyboard (1.4.2) unstable; urgency=medium 34 | 35 | * Update csv.h to latest version 36 | 37 | -- Yauhen Kharuzhy Sat, 30 Dec 2023 02:51:08 +0200 38 | 39 | touch-keyboard (1.4.1) unstable; urgency=medium 40 | 41 | * layouts: Fix invalid mouse button definitions 42 | 43 | -- Yauhen Kharuzhy Wed, 20 Jan 2021 23:35:29 +0300 44 | 45 | touch-keyboard (1.4) unstable; urgency=medium 46 | 47 | * Make touchpad geometry customizable 48 | * Add virtual touchpad buttons along of left and right sides 49 | * Move keyboard layouts to separated dir 50 | * Add PC-105-like international keyboard layout 51 | 52 | -- Yauhen Kharuzhy Tue, 19 Jan 2021 02:29:02 +0300 53 | 54 | touch-keyboard (1.3) unstable; urgency=medium 55 | 56 | * Remove all unit-tests 57 | * Remove android-libbase dependency 58 | * Generate udeb package for debian-installer also 59 | 60 | -- Yauhen Kharuzhy Sat, 26 Dec 2020 18:06:05 +0300 61 | 62 | touch-keyboard (1.2.1) unstable; urgency=medium 63 | 64 | * Fix build with debian testing 65 | 66 | -- Yauhen Kharuzhy Fri, 25 Dec 2020 02:28:14 +0300 67 | 68 | touch-keyboard (1.2) unstable; urgency=medium 69 | 70 | * Remove PWM settings (handled by kernel driver now) 71 | * Update README with build instructions 72 | * Remove obsoleted yogabook-specific touch-keyboard-sleep hook 73 | * Decrease default verbosity level 74 | * Handle exception and exit with failure 75 | 76 | -- Yauhen Kharuzhy Fri, 18 Dec 2020 01:43:47 +0300 77 | 78 | touch-keyboard (1.1) unstable; urgency=medium 79 | 80 | * Implement Fn modifier key emulation 81 | * haptic/ff_driver: Don't flood log if no haptic driver found 82 | * Add lintian overrides to ignore custom rpath in the binary 83 | * Add script to reload module at resume 84 | 85 | -- Yauhen Kharuzhy Wed, 02 Dec 2020 01:51:21 +0300 86 | 87 | touch-keyboard (1.0) unstable; urgency=medium 88 | 89 | * Initial Release. 90 | 91 | -- Yauhen Kharuzhy Mon, 23 Nov 2020 01:15:08 +0300 92 | -------------------------------------------------------------------------------- /haptic/touch_ff_manager.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Chromium OS Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #include "logging.h" 6 | #include 7 | 8 | #include "haptic/touch_ff_manager.h" 9 | 10 | namespace { 11 | // Path for left and right vibrators. 12 | const char kLeftVibratorPath[] = "/dev/left_vibrator"; 13 | const char kRightVibratorPath[] = "/dev/right_vibrator"; 14 | } 15 | 16 | namespace touch_keyboard { 17 | 18 | TouchFFManager::TouchFFManager(int max_x, int max_y, int rotation, 19 | double magnitude, int duration_ms) { 20 | switch (rotation) { 21 | case 0: 22 | case 180: 23 | touch_max_x_ = max_x; 24 | break; 25 | case 90: 26 | case 270: 27 | touch_max_x_ = max_y; 28 | break; 29 | default: 30 | LOG(ERROR) << "Invalid rotation angle: " << rotation << "\n"; 31 | } 32 | 33 | touch_rotation_ = rotation; 34 | 35 | if (!left_driver_.Init(kLeftVibratorPath)) { 36 | LOG(ERROR) << "Cannot find left motor\n"; 37 | } 38 | 39 | if (!right_driver_.Init(kRightVibratorPath)) { 40 | LOG(ERROR) << "Cannot find right motor\n"; 41 | } 42 | 43 | magnitude_ = magnitude; 44 | duration_ms_ = duration_ms; 45 | 46 | RegisterFF(TouchKeyboardEvent::FingerDown, magnitude_, duration_ms_); 47 | } 48 | 49 | void TouchFFManager::RegisterFF(TouchKeyboardEvent event, 50 | double magnitude, int length_ms) { 51 | int tmp_event_id; 52 | // The effect is uploaded to both drivers. 53 | tmp_event_id = left_driver_.UploadEffect(magnitude, length_ms); 54 | left_driver_fflib_[event] = tmp_event_id; 55 | 56 | tmp_event_id = right_driver_.UploadEffect(magnitude, length_ms); 57 | right_driver_fflib_[event] = tmp_event_id; 58 | } 59 | 60 | void TouchFFManager::EventTriggered(TouchKeyboardEvent event, int x, int y) { 61 | // Play ff effects based on the location of the event. Currently, we drive 62 | // left OR right motor depend on the event possition. When the event is on the 63 | // left half of touch surface, only the left vibrator will run. 64 | int val; 65 | 66 | switch (touch_rotation_) { 67 | case 0: 68 | val = x; 69 | break; 70 | case 90: 71 | val = touch_max_x_ - y; 72 | break; 73 | case 180: 74 | val = touch_max_x_ - x; 75 | break; 76 | case 270: 77 | val = y; 78 | break; 79 | default: 80 | val = x; 81 | } 82 | 83 | if (val < touch_max_x_ / 2) { 84 | PlayEffectOfEvent(event, &left_driver_, &left_driver_fflib_); 85 | } else { 86 | PlayEffectOfEvent(event, &right_driver_, &right_driver_fflib_); 87 | } 88 | } 89 | 90 | void TouchFFManager::PlayEffectOfEvent( 91 | TouchKeyboardEvent event, 92 | FFDriver* driver, 93 | std::unordered_map* lib) { 94 | auto iter = lib->find(event); 95 | if (iter == lib->end()) { 96 | return; 97 | } 98 | driver->PlayEffect(iter->second); 99 | } 100 | 101 | } // namespace touch_keyboard 102 | -------------------------------------------------------------------------------- /faketouchpad.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Chromium OS Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #ifndef TOUCH_KEYBOARD_FAKETOUCHPAD_H_ 6 | #define TOUCH_KEYBOARD_FAKETOUCHPAD_H_ 7 | 8 | #include 9 | #include 10 | 11 | #include "evdevsource.h" 12 | #include "statemachine/statemachine.h" 13 | #include "uinputdevice.h" 14 | #include "fakekeyboard.h" 15 | 16 | namespace touch_keyboard { 17 | 18 | class FakeTouchpad : public UinputDevice, public EvdevSource { 19 | /* Generate a "fake" touchpad device that pulls it's touch events from a sub- 20 | * region of a larger touch sensor. 21 | * 22 | * This class collects evdev events from one touch sensor and creates a 23 | * similar device with udev, then pipes events from a certain area through. 24 | * It also handles various bookkeeping with respect to which fingers are 25 | * within the "touchpad" region. In essence, you initialize one of these 26 | * objects with a source device and the area you want to treat as a touchpad. 27 | * Then, when you run Start() it sets up the devices and will block forever 28 | * passing though the appropriate events and modifying them to maintain the 29 | * illusion of a normal touchpad. 30 | */ 31 | public: 32 | FakeTouchpad(struct hw_config &hw_config); 33 | 34 | void Start(std::string const &source_device_path, 35 | std::string const &touchpad_device_name); 36 | 37 | private: 38 | // Load touchpad geometry from file 39 | bool LoadLayout(std::string const &layout_filename); 40 | 41 | // Loop forever consuming events from the source and emitting them from the 42 | // fake touchpad -- the main workhorse function that Start() calls. 43 | void Consume(); 44 | 45 | // Send button events indicating how many fingers are currently on the fake 46 | // touchpad. 47 | void SendTouchpadBtnEvents(int touch_count) const; 48 | 49 | // Test to see if the finger who's data is stored in the slot is currently 50 | // within the region defined as the touchpad. 51 | bool Contains(mtstatemachine::Slot const &slot) const; 52 | 53 | // Check the state of the input touch and sync the fake touchpad by passing 54 | // through any new updates. 55 | int SyncTouchEvents(); 56 | 57 | // Used by SyncTouchEvents, this function blindly duplicates the state stored 58 | // in the slot for the fake touchpad by replicating events for each value. 59 | bool PassEventsThrough(mtstatemachine::Slot const &slot) const; 60 | 61 | // These member variables store the ranges of x/y coordinates that make up 62 | // the "touchpad" area on the source input device. 63 | int xmin_, xmax_, ymin_, ymax_; 64 | 65 | // 'Real world' touchpad width and height in mm 66 | double width_mm_, height_mm_; 67 | 68 | struct hw_config hw_config_; 69 | 70 | // Every FakeTouchpad needs a state machine to interpret incoming events, it 71 | // is defined and created here as a member of each object. 72 | mtstatemachine::MtStateMachine sm_; 73 | 74 | // Here we store a mapping that determines which slots are in the touchpad 75 | // region or not currently. 76 | std::vector slot_memberships_; 77 | 78 | DISALLOW_COPY_AND_ASSIGN(FakeTouchpad); 79 | }; 80 | 81 | } // namespace touch_keyboard 82 | 83 | #endif // TOUCH_KEYBOARD_FAKETOUCHPAD_H_ 84 | -------------------------------------------------------------------------------- /haptic/touch_ff_manager.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Chromium OS Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #ifndef TOUCH_KEYBOARD_HAPTIC_TOUCH_FF_MANAGER_H_ 6 | #define TOUCH_KEYBOARD_HAPTIC_TOUCH_FF_MANAGER_H_ 7 | 8 | #include 9 | 10 | #include "base_macros.h" 11 | #include "haptic/ff_driver.h" 12 | 13 | namespace touch_keyboard { 14 | 15 | // This is a set of events that might be interested to TouchFFManager. 16 | // When the event happens, it should report the event to TouchFFManager, then 17 | // TouchFFManager can decide how to play/cancel ff effect. 18 | enum class TouchKeyboardEvent { 19 | FingerDown, 20 | SendKey, 21 | FingerUp, 22 | }; 23 | 24 | // Default magnitude for haptic feedback. 25 | constexpr double kDefaultHapticMagnitude = 1.0; 26 | // Default duration for haptic feedback in ms. 27 | constexpr int kDefaultHapticDurationMs = 6; 28 | 29 | class TouchFFManager { 30 | /* TouchFFManager (touch force feeback manager) class manages the haptic 31 | * feedback for the touch keyboard. It is initialized with the length of x 32 | * axis of the touch surface. When some TouchFFEvent is happening, 33 | * TouchFFManager should be notified with the x position, then it will decide 34 | * when and how to fire the vibration. 35 | */ 36 | public: 37 | // Init TouchFFManager with max_x, where max_x is the max possible x 38 | // of the touch surface. 39 | explicit TouchFFManager(int max_x, int max_y, int rotation, 40 | double magnitude = kDefaultHapticMagnitude, 41 | int duration_ms = kDefaultHapticDurationMs); 42 | 43 | // Inform the TouchFFManager that a particular kind of keyboard event as 44 | // happened at which x location. This may or may not trigger haptic feedback. 45 | // X value should be between 0 and max_x. X = 0 means the left side of the 46 | // touch surface. 47 | void EventTriggered(TouchKeyboardEvent event, int x, int y); 48 | 49 | // Register force feeback effect for the touch keyboard event. When the event 50 | // is triggered later, the effect will be played. 51 | void RegisterFF(TouchKeyboardEvent event, double magnitude, int length_ms); 52 | 53 | private: 54 | // This hash functor makes sure enum class works with unordered map. 55 | struct TouchKeyboardEventHash { 56 | std::size_t operator()(TouchKeyboardEvent event) const { 57 | return static_cast(event); 58 | } 59 | }; 60 | 61 | // This helper function get the effect id of the event from lib and then play 62 | // the effect on the driver. 63 | void PlayEffectOfEvent( 64 | TouchKeyboardEvent event, FFDriver* driver, 65 | std::unordered_map* lib); 66 | 67 | FFDriver left_driver_; 68 | FFDriver right_driver_; 69 | 70 | std::unordered_map 71 | left_driver_fflib_; 72 | std::unordered_map 73 | right_driver_fflib_; 74 | 75 | // This is the max x axis value of the touchpad. 76 | int touch_max_x_; 77 | 78 | // Touchscreen rotation angle, CW 79 | int touch_rotation_; 80 | 81 | double magnitude_; 82 | int duration_ms_; 83 | 84 | DISALLOW_COPY_AND_ASSIGN(TouchFFManager); 85 | }; 86 | 87 | } // namespace touch_keyboard 88 | 89 | #endif // TOUCH_KEYBOARD_HAPTIC_TOUCH_FF_MANAGER_H_ 90 | -------------------------------------------------------------------------------- /uinputdevice.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Chromium OS Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #ifndef TOUCH_KEYBOARD_UINPUTDEVICE_H_ 6 | #define TOUCH_KEYBOARD_UINPUTDEVICE_H_ 7 | 8 | #include "logging.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "syscallhandler.h" 18 | #include "uinput_definitions.h" 19 | 20 | 21 | namespace touch_keyboard { 22 | 23 | // This is the file handle on disk that you use to control the uinput module. 24 | constexpr char kUinputControlFilename[] = "/dev/uinput"; 25 | 26 | class UinputDevice { 27 | /* A class to allow you to easily create uinput devices and generate events. 28 | * 29 | * This class can be used to create uinput devices, setup which events they 30 | * are capable of generating, and actually sending them. The general flow is 31 | * to instantiate a UinputDevice object and call CreateUinputFD() to get the 32 | * process started. You can then use the various EnableXXXX() functions to 33 | * enable the correct event types that you plan to generate. Once all the 34 | * events are enabled, FinalizeUinputCreation() will tell the kernel create 35 | * the device and SendEvent() can now be used. 36 | */ 37 | public: 38 | UinputDevice() : syscall_handler_(&default_syscall_handler), 39 | uinput_fd_(-1) {} 40 | explicit UinputDevice(SyscallHandler *syscall_handler) : 41 | syscall_handler_(syscall_handler), uinput_fd_(-1) { 42 | // This constructor allows you to pass in a SyscallHandler when unit 43 | // testing this class. For real use, allow it to use the default value 44 | // by using the constructor with no arguments. 45 | if (syscall_handler_ == NULL) { 46 | syscall_handler_ = &default_syscall_handler; 47 | } 48 | } 49 | 50 | ~UinputDevice(); 51 | 52 | protected: 53 | // Generate a new uinput file descriptor to communicate with the uinput 54 | // module through. 55 | bool CreateUinputFD(); 56 | 57 | // Enable this uinput device to generate a certain event type. This are 58 | // overarching categories, not individual events. eg: EV_ABS, EV_KEY, etc 59 | bool EnableEventType(int ev_type) const; 60 | 61 | // Enable this uinput device to generate a specific event code under the 62 | // event type specified in the function name. eg: KEY_ENTER or ABS_MT_SLOT 63 | bool EnableKeyEvent(int ev_code) const; 64 | bool EnableAbsEvent(int ev_code) const; 65 | 66 | // Clone the EV_ABS event capability of a given evdev device. The width and 67 | // height are used to setup the ranges of X and Y coordinates. 68 | bool CopyABSOutputEvents(int source_evdev_fd, int width, int height, 69 | int xres, int yres) const; 70 | 71 | // Wrap up creation once all your events are enabled, and give it a name. 72 | // Once this is called the device is ready to start sending events out. 73 | bool FinalizeUinputCreation(std::string const &device_name) const; 74 | 75 | // Once the device is finalized, this function sends the actual events 76 | // to the input subsystem just like a normal input device. 77 | bool SendEvent(int ev_type, int ev_code, int value) const; 78 | 79 | private: 80 | SyscallHandler *syscall_handler_; 81 | int uinput_fd_; 82 | 83 | // A helper function that determines if an event is supported by a device 84 | // when trying to clone its capabilities. 85 | bool IsEventSupported(int event, int64_t *supported_event_types) const; 86 | }; 87 | 88 | } // namespace touch_keyboard 89 | 90 | #endif // TOUCH_KEYBOARD_UINPUTDEVICE_H_ 91 | -------------------------------------------------------------------------------- /main.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Chromium OS Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define CSV_IO_NO_THREAD 11 | #include "csv.h" 12 | 13 | #include "fakekeyboard.h" 14 | #include "faketouchpad.h" 15 | #include "haptic/touch_ff_manager.h" 16 | 17 | // This filepath is used as the input evdev device. Whichever touch sensor is 18 | // to be used for touch keyboard input should have a udev rule put in place to 19 | // set up this symlink. 20 | constexpr char kTouchSensorDevicePath[] = "/dev/touch_keyboard"; 21 | 22 | using touch_keyboard::FakeTouchpad; 23 | using touch_keyboard::FakeKeyboard; 24 | using touch_keyboard::TouchFFManager; 25 | 26 | bool LoadHWConfig(std::string const &hw_config_file, struct touch_keyboard::hw_config &hw_config) { 27 | io::CSVReader<7, 28 | io::trim_chars<' ', '\t'>, 29 | io::no_quote_escape<';'>> csv(hw_config_file); 30 | 31 | csv.read_header(io::ignore_no_column, 32 | "resolution_x", "resolution_y", 33 | "width_mm", "height_mm", 34 | "left_margin_mm", "top_margin_mm", 35 | "rotation_cw"); 36 | 37 | int res_x, res_y; 38 | double w_mm, h_mm; 39 | double left_margin_mm, top_margin_mm; 40 | int rotation; 41 | 42 | if (!csv.read_row(res_x, res_y, w_mm, h_mm, 43 | left_margin_mm, top_margin_mm, rotation)) 44 | return false; 45 | 46 | LOG(INFO) << "Touchpad HW config: " << res_x << "x" << res_y << " points, " << 47 | w_mm << "x" << h_mm << " mm, margins is " << 48 | left_margin_mm << "+" << top_margin_mm << 49 | ", rotated by " << rotation << " deg. clockwise.\n"; 50 | 51 | hw_config.res_x = res_x; 52 | hw_config.res_y = res_y; 53 | hw_config.width_mm = w_mm; 54 | hw_config.height_mm = h_mm; 55 | hw_config.rotation = rotation; 56 | hw_config.left_margin_mm = left_margin_mm; 57 | hw_config.top_margin_mm = top_margin_mm; 58 | 59 | return true; 60 | } 61 | 62 | int main(int argc, char *argv[]) { 63 | struct touch_keyboard::hw_config hw_config; 64 | int debug_level = 0; 65 | int opt; 66 | double ff_magnitude = 1.0; 67 | int ff_duration_ms = 4; 68 | 69 | while ((opt = getopt(argc, argv, "hdm:D:")) != -1) { 70 | switch (opt) { 71 | case 'h': 72 | std::cerr << "Usage: touch_keyboard_handler [-h] [-d] [-m ] [-D ]\n"; 73 | return 0; 74 | case 'd': 75 | debug_level++; 76 | break; 77 | case 'm': 78 | ff_magnitude = atof(optarg); 79 | break; 80 | case 'D': 81 | ff_duration_ms = atoi(optarg); 82 | break; 83 | default: 84 | std::cerr << "Unknown option " << (char)opt << "\n"; 85 | exit(EXIT_FAILURE); 86 | } 87 | } 88 | 89 | if (debug_level) 90 | SetMinimumLogSeverity(DEBUG); 91 | 92 | LOG(INFO) << "Starting touch_keyboard_handler\n"; 93 | 94 | LoadHWConfig("touch-hw.csv", hw_config); 95 | 96 | // Fork into two processes, one to handle the keyboard functionality 97 | // and one to handle the touchpad region. 98 | int pid = fork(); 99 | 100 | try { 101 | if (pid < 0) { 102 | LOG(FATAL) << "ERROR: Unable to fork! (" << pid << ")\n"; 103 | } else if (pid == 0) { 104 | // TODO(charliemooney): Get these coordinates from somewhere not hard-coded 105 | LOG(INFO) << "Creating Fake Touchpad.\n"; 106 | FakeTouchpad tp(hw_config); 107 | tp.Start(kTouchSensorDevicePath, "virtual-touchpad"); 108 | } else { 109 | TouchFFManager ffManager(hw_config.res_x, hw_config.res_y, 110 | hw_config.rotation, ff_magnitude, ff_duration_ms); 111 | 112 | FakeKeyboard kbd(hw_config, ffManager); 113 | kbd.Start(kTouchSensorDevicePath, "virtual-keyboard"); 114 | wait(NULL); 115 | } 116 | } catch (...) { 117 | LOG(ERROR) << "Exception occured"; 118 | exit(EXIT_FAILURE); 119 | } 120 | 121 | return 0; 122 | } 123 | -------------------------------------------------------------------------------- /uinputdevice.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Chromium OS Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #include "uinputdevice.h" 6 | 7 | namespace touch_keyboard { 8 | 9 | // When creating a new uinput device, you must specify these parameters like 10 | // with an actual, physical device. These are sane, safe values that we use 11 | // when creating a uinput device. 12 | constexpr int kGoogleVendorID = 0x18d1; 13 | constexpr int kDummyProductID = 0x00FF; 14 | constexpr int kVersionNumber = 1; 15 | 16 | // This is used when interpreting the results of ioctls that query the event 17 | // capabilities of a device. They are returned in an int64_t bitfield. 18 | constexpr int kNumBitsPerInt = sizeof(int64_t) * 8; 19 | 20 | UinputDevice::~UinputDevice() { 21 | // Tell the OS to destroy the uinput device as this object is destructed. 22 | if (uinput_fd_ >= 0) { 23 | int error = syscall_handler_->ioctl(uinput_fd_, UI_DEV_DESTROY); 24 | if (error) { 25 | PLOG(ERROR) << "Unable to destroy uinput device (" << error << ")\n"; 26 | } 27 | } 28 | } 29 | 30 | bool UinputDevice::CreateUinputFD() { 31 | // Open a control file descriptor for creating a new uinput device. 32 | // This file descriptor is used with ioctls to configure the device and 33 | // receive the outgoing event information. 34 | if (uinput_fd_ >= 0) { 35 | LOG(ERROR) << "Control FD already opened! (" << uinput_fd_ << ") Quitting.\n"; 36 | return false; 37 | } 38 | 39 | uinput_fd_ = syscall_handler_->open(kUinputControlFilename, 40 | O_WRONLY | O_NONBLOCK); 41 | if (uinput_fd_ < 0) { 42 | PLOG(ERROR) << "Unable to open " << kUinputControlFilename << 43 | " (" << uinput_fd_ << ")\n"; 44 | return false; 45 | } 46 | LOG(DEBUG) << "Uinput control file descriptor opened (" << uinput_fd_ << ")\n"; 47 | return true; 48 | } 49 | 50 | bool UinputDevice::EnableEventType(int ev_type) const { 51 | // Tell the kernel that this uinput device will report events of a 52 | // certain type (ABS, KEY, etc). Individual event codes must still be 53 | // enabled individually, but their overarching types need to be enabled 54 | // first, which is done here. 55 | int error = syscall_handler_->ioctl(uinput_fd_, UI_SET_EVBIT, ev_type); 56 | if (error) { 57 | LOG(ERROR) << "Unable to enable event type 0x" << std::hex << ev_type << 58 | "(" << std::dec << error << ")\n"; 59 | return false; 60 | } 61 | LOG(DEBUG) << "Enabled events of type 0x" << std::hex << ev_type << "\n"; 62 | return true; 63 | } 64 | 65 | bool UinputDevice::EnableKeyEvent(int ev_code) const { 66 | // Tell the kernel that this region's uinput device will report a specific 67 | // key event. (eg: KEY_BACKSPACE or BTN_TOUCH) 68 | int error = syscall_handler_->ioctl(uinput_fd_, UI_SET_KEYBIT, ev_code); 69 | if (error) { 70 | LOG(ERROR) << "Unable to enable EV_KEY 0x" << std::hex << ev_code << 71 | " events (" << std::dec << ")\n"; 72 | return false; 73 | } 74 | LOG(DEBUG) << "Enabled EV_KEY 0x" << std::hex << ev_code << " events" << "\n"; 75 | return true; 76 | } 77 | 78 | bool UinputDevice::EnableAbsEvent(int ev_code) const { 79 | // Tell the kernel that this region's uinput device will report a specific 80 | // kind of ABS event. (eg: ABS_MT_POSITION_X or ABS_PRESSURE) 81 | int error = syscall_handler_->ioctl(uinput_fd_, UI_SET_ABSBIT, ev_code); 82 | if (error) { 83 | LOG(ERROR) << "Unable to enable EV_ABS 0x" << std::hex << ev_code << 84 | " events (" << std::dec << error << ")\n"; 85 | return false; 86 | } 87 | LOG(DEBUG) << "Enabled EV_ABS 0x" << std::hex << ev_code << " events\n"; 88 | return true; 89 | } 90 | 91 | bool UinputDevice::CopyABSOutputEvents(int source_evdev_fd, 92 | int width, int height, 93 | int xres, int yres) const { 94 | // Configure this region's uinput device to report the correct kinds of 95 | // events by copying the events that are reported by the input device 96 | // who's file descriptor is passed as a reference. 97 | // Instead of copying the range of the absolute axes though, the user 98 | // specifies the width and height manually -- essentially creating a 99 | // cloned input device with a different size than the source device. 100 | int ev_code, error; 101 | struct uinput_abs_setup abs_setup; 102 | int64_t supported_abs_event_codes[((KEY_MAX - 1) / kNumBitsPerInt) + 1]; 103 | int64_t supported_event_types[EV_MAX]; 104 | 105 | // Query the source evdev file descriptor to see which event types it 106 | // supports to make sure it supports ABS. 107 | memset(supported_event_types, 0, sizeof(supported_event_types)); 108 | syscall_handler_->ioctl(source_evdev_fd, EVIOCGBIT(0, EV_MAX), 109 | supported_event_types); 110 | if (!IsEventSupported(EV_ABS, supported_event_types)) { 111 | LOG(ERROR) << "Touchscreen does not support EV_ABS events.\n"; 112 | return false; 113 | } 114 | 115 | // Enable the EV_ABS event type for this device. Fail if it can't. 116 | if (!EnableEventType(EV_ABS)) { 117 | return false; 118 | } 119 | 120 | // Query the device to find which ABS event codes are supported and then 121 | // enable them for this uinput device as well. 122 | memset(supported_abs_event_codes, 0, sizeof(supported_abs_event_codes)); 123 | syscall_handler_->ioctl(source_evdev_fd, EVIOCGBIT(EV_ABS, KEY_MAX), 124 | supported_abs_event_codes); 125 | for (ev_code = 0; ev_code < KEY_MAX; ev_code++) { 126 | // Skip over any event codes that are not supported. 127 | if (!IsEventSupported(ev_code, supported_abs_event_codes)) { 128 | continue; 129 | } 130 | 131 | // Enable this event code for the uinput device. 132 | if (!EnableAbsEvent(ev_code)) { 133 | return false; 134 | } 135 | 136 | // Fill in the ranges for each EV_ABS axis, modifying them for X and Y. 137 | memset(&abs_setup, 0, sizeof(abs_setup)); 138 | abs_setup.code = ev_code; 139 | syscall_handler_->ioctl(source_evdev_fd, EVIOCGABS(ev_code), 140 | &abs_setup.absinfo); 141 | if (ev_code == ABS_MT_POSITION_X || ev_code == ABS_X) { 142 | abs_setup.absinfo.minimum = 0; 143 | abs_setup.absinfo.maximum = width; 144 | abs_setup.absinfo.resolution = xres; 145 | } else if (ev_code == ABS_MT_POSITION_Y || ev_code == ABS_Y) { 146 | abs_setup.absinfo.minimum = 0; 147 | abs_setup.absinfo.maximum = height; 148 | abs_setup.absinfo.resolution = yres; 149 | } 150 | error = syscall_handler_->ioctl(uinput_fd_, UI_ABS_SETUP, &abs_setup); 151 | if (error) { 152 | LOG(ERROR) << "Unable to set up axis for event code 0x" << std::hex << 153 | ev_code << " (" << std::dec << error << ")\n"; 154 | return false; 155 | } 156 | } 157 | 158 | LOG(INFO) << "Successfully copied all EV_ABS events from source device\n"; 159 | return true; 160 | } 161 | 162 | bool UinputDevice::FinalizeUinputCreation( 163 | std::string const &device_name) const { 164 | int error; 165 | struct uinput_setup device_info; 166 | 167 | // Build a uinput device struct and write it to the ui_fd to specify the 168 | // various identification parameters required such as the device name. 169 | memset(&device_info, 0, sizeof(device_info)); 170 | snprintf(device_info.name, UINPUT_MAX_NAME_SIZE, "%s", device_name.c_str()); 171 | device_info.id.bustype = BUS_USB; 172 | device_info.id.vendor = kGoogleVendorID; 173 | device_info.id.product = kDummyProductID; 174 | device_info.id.version = kVersionNumber; 175 | error = syscall_handler_->ioctl(uinput_fd_, UI_DEV_SETUP, &device_info); 176 | if (error) { 177 | LOG(ERROR) << "uinput device setup ioctl failed. (" << error << ")\n"; 178 | return false; 179 | } 180 | 181 | // Finally request that a new uinput device is created to those specs. 182 | // After this step the device should be fully functional and ready to 183 | // send events. 184 | error = syscall_handler_->ioctl(uinput_fd_, UI_DEV_CREATE); 185 | if (error) { 186 | LOG(ERROR) << "uinput device creation ioctl failed. (" << error << ")\n"; 187 | return false; 188 | } 189 | 190 | LOG(INFO) << "Successfully finalized uinput device creation.\n"; 191 | return true; 192 | } 193 | 194 | bool UinputDevice::SendEvent(int ev_type, int ev_code, int value) const { 195 | // Send an input event to the kernel through this uinput device. 196 | struct input_event ev; 197 | ev.type = ev_type; 198 | ev.code = ev_code; 199 | ev.value = value; 200 | 201 | int bytes_written = 202 | syscall_handler_->write(uinput_fd_, &ev, sizeof(struct input_event)); 203 | if (bytes_written != sizeof(struct input_event)) { 204 | LOG(ERROR) << "Failed to write() when sending an event. (" << 205 | bytes_written << ")\n"; 206 | return false; 207 | } 208 | return true; 209 | } 210 | 211 | bool UinputDevice::IsEventSupported(int event, 212 | int64_t *supported_event_types) const { 213 | // The array is essentially a big bit field and we're testing to see if 214 | // the event-th bit is set. That tells us if the event type in question 215 | // is included in the support event types. 216 | return (supported_event_types[event / kNumBitsPerInt] >> 217 | (event % kNumBitsPerInt)) & 1; 218 | } 219 | 220 | } // namespace touch_keyboard 221 | -------------------------------------------------------------------------------- /faketouchpad.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Chromium OS Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #include 6 | 7 | #include "faketouchpad.h" 8 | 9 | #define CSV_IO_NO_THREAD 10 | #include "csv.h" 11 | 12 | namespace touch_keyboard { 13 | 14 | FakeTouchpad::FakeTouchpad(struct hw_config &hw_config) : 15 | hw_config_(hw_config) { 16 | 17 | if (!LoadLayout("layout-touchpad.csv")) 18 | throw "Failed to load touchpad geometry"; 19 | 20 | for (int i = 0; i < mtstatemachine::kNumSlots; i++) { 21 | slot_memberships_.push_back(false); 22 | } 23 | } 24 | 25 | bool FakeTouchpad::LoadLayout(std::string const &layout_filename) { 26 | double hw_pitch_x, hw_pitch_y; 27 | double left_margin, top_margin; 28 | double xmin_mm, ymin_mm, xmax_mm, ymax_mm; 29 | 30 | hw_pitch_x = hw_config_.res_x / hw_config_.width_mm; 31 | hw_pitch_y = hw_config_.res_y / hw_config_.height_mm; 32 | 33 | LOG(DEBUG) << "pitch: " << hw_pitch_x << "x" << hw_pitch_y << "\n"; 34 | 35 | io::CSVReader<4, 36 | io::trim_chars<' ', '\t'>, 37 | io::no_quote_escape<';'>> l_csv(layout_filename); 38 | 39 | l_csv.read_header(io::ignore_missing_column, "x1", "y1", "x2", "y2"); 40 | 41 | if (!l_csv.read_row(xmin_mm, ymin_mm, xmax_mm, ymax_mm)) { 42 | LOG(ERROR) << "CSV read failed"; 43 | return false; 44 | } 45 | 46 | LOG(DEBUG) << "x1: " << xmin_mm << ", y1: " << ymin_mm << ", x2: " << xmax_mm << 47 | ", y2:" << ymax_mm; 48 | 49 | left_margin = hw_config_.left_margin_mm; 50 | top_margin = hw_config_.top_margin_mm; 51 | 52 | width_mm_ = xmax_mm - xmin_mm; 53 | height_mm_ = ymax_mm - ymin_mm; 54 | 55 | switch (hw_config_.rotation) { 56 | case 0: 57 | xmin_ = xmin_mm + left_margin; 58 | xmax_ = xmax_mm + left_margin; 59 | ymin_ = ymin_mm + top_margin; 60 | ymax_ = ymax_mm + top_margin; 61 | break; 62 | case 90: 63 | xmin_ = top_margin + ymin_mm; 64 | xmax_ = top_margin + ymax_mm; 65 | ymin_ = hw_config_.height_mm - (left_margin + xmax_mm); 66 | ymax_ = hw_config_.height_mm - (left_margin + xmin_mm); 67 | break; 68 | case 180: 69 | xmin_ = hw_config_.width_mm - (xmax_mm + left_margin); 70 | xmax_ = hw_config_.width_mm - (xmin_mm + left_margin); 71 | ymin_ = hw_config_.height_mm - (ymax_mm + top_margin); 72 | ymax_ = hw_config_.height_mm - (ymin_mm + top_margin); 73 | break; 74 | case 270: 75 | xmin_ = hw_config_.width_mm - (ymax_mm + top_margin); 76 | xmax_ = hw_config_.width_mm - (ymin_mm + top_margin); 77 | ymin_ = left_margin + xmin_mm; 78 | ymax_ = left_margin + xmax_mm; 79 | break; 80 | default: 81 | LOG(ERROR) << "Invalid rotation value: " << hw_config_.rotation << "\n"; 82 | throw; 83 | } 84 | 85 | xmin_ *= hw_pitch_x; 86 | xmax_ *= hw_pitch_x; 87 | ymin_ *= hw_pitch_y; 88 | ymax_ *= hw_pitch_y; 89 | 90 | LOG(INFO) << "FakeTouchpad geometry: (" << xmin_ << ", " << xmax_ << 91 | "), (" << ymin_ << ", " << ymax_ << ")\n"; 92 | 93 | return true; 94 | } 95 | 96 | void FakeTouchpad::Start(std::string const &source_device_path, 97 | std::string const &touchpad_device_name) { 98 | if (!OpenSourceDevice(source_device_path)) 99 | return; 100 | CreateUinputFD(); 101 | 102 | // Enable the few button events that touchpads need. 103 | EnableEventType(EV_KEY); 104 | EnableKeyEvent(BTN_TOUCH); 105 | EnableKeyEvent(BTN_TOOL_FINGER); 106 | EnableKeyEvent(BTN_TOOL_DOUBLETAP); 107 | EnableKeyEvent(BTN_TOOL_TRIPLETAP); 108 | EnableKeyEvent(BTN_TOOL_QUADTAP); 109 | 110 | int w = 0, h = 0; 111 | int xres, yres; 112 | 113 | switch (hw_config_.rotation) { 114 | case 0: 115 | case 180: 116 | w = xmax_ - xmin_; 117 | h = ymax_ - ymin_; 118 | break; 119 | case 90: 120 | case 270: 121 | w = ymax_ - ymin_; 122 | h = xmax_ - xmin_; 123 | break; 124 | default: 125 | break; 126 | } 127 | 128 | xres = round(w / width_mm_); 129 | yres = round(h / height_mm_); 130 | 131 | // Duplicate the ABS events from the source device. 132 | CopyABSOutputEvents(source_fd_, w, h, xres, yres); 133 | 134 | // Finally, tell kernel to create the new fake touchpad's uinput device. 135 | FinalizeUinputCreation(touchpad_device_name); 136 | 137 | // Loop forever consuming the events coming in from the source device. 138 | Consume(); 139 | } 140 | 141 | void FakeTouchpad::Consume() { 142 | while (1) { 143 | struct input_event ev; 144 | bool event_ready = GetNextEvent(kNoTimeout, &ev); 145 | // If we timed out waiting, then there is no event yet. 146 | if (!event_ready) { 147 | continue; 148 | } 149 | 150 | if (sm_.AddEvent(ev, NULL)) { 151 | // Sync over all the touch events from the source state machine. 152 | int touch_count = SyncTouchEvents(); 153 | // Make sure the BTN events are correct since this is a fake touchpad. 154 | SendTouchpadBtnEvents(touch_count); 155 | // Finally send a SYN after all applicable events are sent. 156 | SendEvent(EV_SYN, SYN_REPORT, 0); 157 | } 158 | } 159 | } 160 | 161 | void FakeTouchpad::SendTouchpadBtnEvents(int touch_count) const { 162 | // Since this is a fake touchpad, we need to send BTN_TOUCH and BTN_TOOL_* 163 | // events whenever a finger arrives and leaves for the gesture library to 164 | // interpret the motions correctly. This function generates those events 165 | // based on the number of fingers currently being reported by the fake 166 | // touchpad. 167 | SendEvent(EV_KEY, BTN_TOUCH, (touch_count > 0) ? 1 : 0); 168 | SendEvent(EV_KEY, BTN_TOOL_FINGER, (touch_count == 1) ? 1 : 0); 169 | SendEvent(EV_KEY, BTN_TOOL_DOUBLETAP, (touch_count == 2) ? 1 : 0); 170 | SendEvent(EV_KEY, BTN_TOOL_TRIPLETAP, (touch_count == 3) ? 1 : 0); 171 | SendEvent(EV_KEY, BTN_TOOL_QUADTAP, (touch_count == 4) ? 1 : 0); 172 | } 173 | 174 | bool FakeTouchpad::Contains(mtstatemachine::Slot const &slot) const { 175 | // Check and see if the contact in the slot is currently contained within 176 | // the boundaries of this region. 177 | int x = slot.FindValueByEvent(EV_ABS, ABS_MT_POSITION_X); 178 | int y = slot.FindValueByEvent(EV_ABS, ABS_MT_POSITION_Y); 179 | if (x < xmin_ || x > xmax_) 180 | return false; 181 | if (y < ymin_ || y > ymax_) 182 | return false; 183 | return true; 184 | } 185 | 186 | bool FakeTouchpad::PassEventsThrough(mtstatemachine::Slot const &slot) const { 187 | // Go through the slot in question and send events setting each of the set 188 | // values into this region. Essentially this updates all of the values for 189 | // this slot in the kernel to match our internal version. 190 | // This function returns True if the updated slot represented a contact that 191 | // is current on the touchpad. 192 | mtstatemachine::Slot::const_iterator it; 193 | bool is_valid = false; 194 | 195 | // Iterate over each value in the slot and send the corresponding event. 196 | for (it = slot.begin(); it != slot.end(); it++) { 197 | mtstatemachine::EventKey slot_event_key = it->first; 198 | int value = it->second; 199 | int code = slot_event_key.code_; 200 | 201 | // If the value is for an unsupported event_key, skip it. 202 | if (!slot_event_key.IsSupportedForTouchpads()) 203 | continue; 204 | 205 | // Check the tracking ID. (-1 indicates this contact is not valid anymore) 206 | if (slot_event_key.IsTrackingID()) { 207 | is_valid |= (value != -1); 208 | } 209 | 210 | // Transform X and Y values to keep the corner of the region 0,0 and 211 | // invert any axes that were set to be inverted at creation. 212 | if (slot_event_key.IsX()) { 213 | value -= xmin_; 214 | 215 | switch (hw_config_.rotation) { 216 | case 0: 217 | break; // no transform 218 | case 90: 219 | code = ABS_MT_POSITION_Y; 220 | break; 221 | case 180: 222 | value = (xmax_ - xmin_) - value; 223 | break; 224 | case 270: 225 | code = ABS_MT_POSITION_Y; 226 | value = (xmax_ - xmin_) - value; 227 | break; 228 | } 229 | } else if (slot_event_key.IsY()) { 230 | value -= ymin_; 231 | switch (hw_config_.rotation) { 232 | case 0: 233 | break; // no transform 234 | case 90: 235 | code = ABS_MT_POSITION_X; 236 | value = (ymax_ - ymin_) - value; 237 | break; 238 | case 180: 239 | value = (ymax_ - ymin_) - value; 240 | break; 241 | case 270: 242 | code = ABS_MT_POSITION_X; 243 | break; 244 | } 245 | } 246 | 247 | // Push an event that sets this value into the region. 248 | SendEvent(slot_event_key.type_, code, value); 249 | } 250 | 251 | return is_valid; 252 | } 253 | 254 | int FakeTouchpad::SyncTouchEvents() { 255 | // Scan through all the slots of the state machine and sync the 256 | // uinput device with it by copying over the touch events for any contacts 257 | // that are currently contained within the region, returning the number 258 | // of such contacts. This only passes on events that are contained within 259 | // the region and performs transformations on the coordinates to maintain 260 | // the illusion of a different device (shifting x/y, adding fake finger 261 | // arriving events, etc) 262 | int touch_count = 0; 263 | 264 | for (int slot = 0; slot < mtstatemachine::kNumSlots; slot++) { 265 | // Send a SLOT message to make sure these events go to the right slot 266 | SendEvent(EV_ABS, ABS_MT_SLOT, slot); 267 | 268 | // Don't pass on events from contacts outside of the region. 269 | if (!Contains(sm_.slots_[slot])) { 270 | // If this slot just left the region, send a finger-leaving event. 271 | if (slot_memberships_[slot]) { 272 | SendEvent(EV_ABS, ABS_MT_TRACKING_ID, -1); 273 | } 274 | slot_memberships_[slot] = false; 275 | continue; 276 | } else { 277 | // If this slot just entered the region, send a finger-arrive event. 278 | if (!slot_memberships_[slot]) { 279 | int tid = sm_.slots_[slot].FindValueByEvent(EV_ABS, 280 | ABS_MT_TRACKING_ID); 281 | SendEvent(EV_ABS, ABS_MT_TRACKING_ID, tid); 282 | } 283 | slot_memberships_[slot] = true; 284 | } 285 | 286 | // Scan through the slot and update all the properties. 287 | bool valid_finger = PassEventsThrough(sm_.slots_[slot]); 288 | if (valid_finger && slot_memberships_[slot]) { 289 | touch_count++; 290 | } 291 | } 292 | 293 | return touch_count; 294 | } 295 | 296 | } // namespace touch_keyboard 297 | -------------------------------------------------------------------------------- /fakekeyboard.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Chromium OS Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #ifndef TOUCH_KEYBOARD_FAKEKEYBOARD_H_ 6 | #define TOUCH_KEYBOARD_FAKEKEYBOARD_H_ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "evdevsource.h" 18 | #include "haptic/touch_ff_manager.h" 19 | #include "statemachine/statemachine.h" 20 | #include "uinputdevice.h" 21 | 22 | namespace touch_keyboard { 23 | 24 | struct hw_config { 25 | int rotation; // Hardware rotation of the touchpad, 90, 180 or 270 deg. CW 26 | int res_x; // points 27 | int res_y; // points 28 | double width_mm; // mm 29 | double height_mm; //mm 30 | double left_margin_mm; 31 | double top_margin_mm; // margins between physical edge and edge of keys layout 32 | }; 33 | 34 | class Key { 35 | /* A class that represents a single key on the fake keyboard. 36 | * 37 | * This class is used to describe the location, size, and event code (which 38 | * letter is on the key) for a single key on a fake keyboard and keep track 39 | * of it's current state. A keyboard's layout is defined as a vector of 40 | * these Key objects. 41 | */ 42 | public: 43 | Key(int event_code, int event_code_fn, 44 | int xmin, int xmax, int ymin, int ymax) : 45 | event_code_(event_code), event_code_fn_(event_code_fn), 46 | xmin_(xmin), xmax_(xmax), ymin_(ymin), 47 | ymax_(ymax) {} 48 | 49 | // Check if the point (x, y) is contained within this key. 50 | bool Contains(int x, int y) const { 51 | return (x < xmax_ && x >= xmin_ && y < ymax_ && y >= ymin_); 52 | } 53 | 54 | // This defines which event code to emit when this key is pressed. 55 | // Essentially this specifies which key it is. (eg: KEY_A, KEY_BACKSPACE, etc) 56 | int event_code_; 57 | 58 | // The same for Fn modifier key pressed 59 | int event_code_fn_; 60 | 61 | // The range of x and y values that are contained within the key. 62 | int xmin_, xmax_; 63 | int ymin_, ymax_; 64 | }; 65 | 66 | struct Event { 67 | /* A class to represent a pending keyboard event that is scheduled to be 68 | * generated by the fake keyboard. 69 | * 70 | * As keys are pressed by the user, the FakeKeyboard class generates keyboard 71 | * events, which represent the keys being pressed and released. To allow some 72 | * measure of revoking, events are enqueued and only released after a brief 73 | * pause. This way, if something unexpected happens to invalidate a keypress, 74 | * the system can simply not send it. These events are represented as Event 75 | * objects stored in a queue. They store all the information you need about 76 | * a keyboard event such as which keycode it is for, which direction the key 77 | * is going, and when the deadline to release the event is. 78 | */ 79 | public: 80 | Event(int ev_code, bool is_down, struct timespec deadline, int tid) : 81 | is_guaranteed_(false), ev_code_(ev_code), is_down_(is_down), tid_(tid), 82 | deadline_(deadline) {} 83 | 84 | // Some events are guaranteed to fire before their deadline expires. For 85 | // example, if a finger leaves before the deadline the system already knows 86 | // everything about it and can make a decision right away. Since there may 87 | // be earlier, non-guaranteed events pending, we have to make such events 88 | // guaranteed so that when they make it to the front of the queue, we know 89 | // they are already checked and ready to go. 90 | bool is_guaranteed_; 91 | 92 | // This value stores which event code (which key) this event deals with. 93 | int ev_code_; 94 | 95 | // This value stores the "direction" of the event. A value of true indicates 96 | // that this is a key-down event whereas false indicates a key-up event. 97 | bool is_down_; 98 | 99 | // Here we store the tracking ID (tid) of the finger that triggered this 100 | // event. This is used to determine the validity of the event later, by 101 | // looking up the finger's behavior via this tid. 102 | int tid_; 103 | 104 | // This timespec represents the deadline for this event to be emitted by. 105 | // When an event is added to the queue a deadline is set briefly in the 106 | // future. When this deadline passes, the FakeKeyboard is forced to make a 107 | // decision on whether or not the event is valid. 108 | struct timespec deadline_; 109 | }; 110 | 111 | // These values represent the various rejection states of a finger that 112 | // we're tracking on the touch keyboard and should be stored in their 113 | // corresponding FingerData.rejection_status values. 114 | enum class RejectionStatus { 115 | kNotRejectedYet = 0, 116 | kRejectTouchdownOffKey, 117 | kRejectMovedOffKey, 118 | kRejectAlreadyComplete, 119 | }; 120 | 121 | struct FingerData { 122 | /* FingerData objects represent the information we have for a certain contact. 123 | * 124 | * As a finger arrives, moves, and leaves the touch sensor we need to track 125 | * various properties of the finger to allow this program to make intelligent 126 | * decisions about what it's doing. This information is stored in these 127 | * FingerData objects for each contact. 128 | */ 129 | public: 130 | // Here is the time the finger was first reported on the touchpad. 131 | struct timespec arrival_time_; 132 | 133 | // This value stores the maximum pressure reported for this contact since 134 | // its arrival. 135 | int max_pressure_; 136 | 137 | // This value stores the maximum touch diameter reported for this contact 138 | int max_touch_major_; 139 | 140 | // Here we track which key in the layout the finger first appeared on. 141 | int starting_key_number_; 142 | 143 | int event_code_; 144 | 145 | // This Boolean indicates if a "key down" event has already been sent because 146 | // of something this finger did, and as a result a "key up" event must be sent 147 | // eventually. 148 | bool down_sent_; 149 | 150 | // This value indicates the current rejection state of this finger. When a 151 | // finger misbehaves (acts in some way non-key-tapping-like) it can be marked 152 | // here for rejection. 153 | RejectionStatus rejection_status_; 154 | }; 155 | 156 | class FakeKeyboard : public UinputDevice, public EvdevSource { 157 | /* The FakeKeyboard class implements a kernel-level keyboard that 158 | * generates events by processing touch input and comparing them 159 | * to a predefined layout. 160 | * 161 | * A FakeKeyboard object consists of several parts: 162 | * 1. It is an EvdevSource which pulls touch events from a source touch 163 | * sensor. 164 | * 2. It is a UinputDevice which creates a fake input device in the 165 | * kernel using the uinput module that will emit keyboard events. 166 | * 3. Finally, it includes logic to compare touches to a layout of keys 167 | * printed on the touch sensor and determine which keys the user 168 | * is intending to press 169 | * 170 | * To use this class, you should first instantiate a FakeKeyboard object 171 | * then specify which device it is reading from. When you run Start() the 172 | * object will block forever, looping on the touch input and generating 173 | * keyboard events. 174 | */ 175 | public: 176 | FakeKeyboard(struct hw_config &hw_config, TouchFFManager &ffManager); 177 | 178 | // Use this function to actually start processing. Start will block forever 179 | // and should never return, but a new keyboard device should appear and 180 | // begin sending out key events once you type on the touch sensor. 181 | void Start(std::string const &source_device_path, 182 | std::string const &keyboard_device_name); 183 | 184 | private: 185 | // This is the workhorse function called by Start() that actually loops to 186 | // consume the touch events and generate keystrokes. 187 | void Consume(); 188 | 189 | // Use this function to enable the appropriate input events for the uinput 190 | // keyboard device when setting it up. (eg: EV_KEY, KEY_ENTER, etc) 191 | void EnableKeyboardEvents() const; 192 | 193 | // This function does all the necessary work on each full "snapshot" 194 | // describing the current state of the touchpad. This includes things like 195 | // updating the current FingerData objects and making inferences based on 196 | // finger position. 197 | void ProcessIncomingSnapshot( 198 | struct timespec now, 199 | std::unordered_map const &snapshot); 200 | 201 | // Load layout from CSV file 202 | // Calling this function populates the layout_ member of a FakeKeyboard, 203 | // filling it with the locations of each key printed on the touch sensor. 204 | bool LoadLayout(std::string const &layout_filename); 205 | 206 | // Place ev into the event queue, while maintaining chronological order of 207 | // the deadlines. 208 | void EnqueueEvent(Event ev); 209 | 210 | // Convenience function to build a guaranteed key-up event and enqueue it for 211 | // the given event code using the default deadline. 212 | void EnqueueKeyUpEvent(int ev_code, timespec now); 213 | 214 | // Mark a given contact as rejected for the stated reason. This scans for 215 | // all pending events associated with this tracking ID and rejects them all. 216 | void RejectFinger(int tid, RejectionStatus reason); 217 | 218 | // When a finger is leaving the pad, some special bookkeeping is required. 219 | void HandleLeavingFinger(int tid, FingerData finger, timespec now); 220 | 221 | // When a finger first arrives on the sensor some special setup is required. 222 | int GenerateEventForArrivingFinger( 223 | struct timespec now, 224 | struct mtstatemachine::MtFinger const &finger, int tid, 225 | int *event_code); 226 | 227 | // Confirm that a finger's correct position is still within the boundaries of 228 | // the key that it initially arrived on. 229 | bool StillOnFirstKey(struct mtstatemachine::MtFinger const & finger, 230 | FingerData const & data) const; 231 | 232 | // This is a convenience function that allows for easy manipulation of 233 | // timespec structs, which are used to track the deadlines for sending 234 | // events. To easily add a few ms to a timespec, you can just call this. 235 | static struct timespec AddMsToTimespec(struct timespec const& orig, 236 | int additional_ms); 237 | 238 | // This is a convenience function that compares two timespecs, returning 239 | // true if t1 comes *after* than t2. 240 | static bool TimespecIsLater(struct timespec const& t1, 241 | struct timespec const& t2); 242 | 243 | // The touch force feedback manager used to play ff effects. 244 | TouchFFManager *ff_manager_; 245 | 246 | // This group of Key objects stores the full layout of the keyboard. 247 | std::vector layout_; 248 | 249 | // This state machine is used to interpret the raw touch events coming from 250 | // the kernel -- separating them by finger/etc. 251 | mtstatemachine::MtStateMachine sm_; 252 | 253 | // This list of events stores all pending events in chronological order based 254 | // on their deadlines. 255 | std::list pending_events_; 256 | 257 | // This is a mapping front tracking id's (TIDs) to finger information that 258 | // persists over the life of a contact to track global stats and information. 259 | std::unordered_map finger_data_; 260 | 261 | bool fn_key_pressed_; 262 | 263 | struct hw_config hw_config_; 264 | 265 | DISALLOW_COPY_AND_ASSIGN(FakeKeyboard); 266 | }; 267 | 268 | } // namespace touch_keyboard 269 | 270 | #endif // TOUCH_KEYBOARD_FAKEKEYBOARD_H_ 271 | -------------------------------------------------------------------------------- /fakekeyboard.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Chromium OS Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #include "fakekeyboard.h" 6 | 7 | #define CSV_IO_NO_THREAD 8 | #include "csv.h" 9 | 10 | namespace touch_keyboard { 11 | 12 | // This sets how long (in ms) to delay a key down event before sending it. More 13 | // delay causes a visible lag when typing and a shorter delay means the system 14 | // has less time to confirm keypresses and may increase the error rate. 15 | constexpr int kEventDelayMS = 50; 16 | 17 | // These dummy values are used to track events. 18 | constexpr bool kKeyDownEvent = true; 19 | constexpr bool kKeyUpEvent = false; 20 | constexpr int kNoKey = -1; 21 | constexpr int kOldTID = -999; 22 | 23 | constexpr int kMinTapPressure = 50; 24 | constexpr int kMaxTapPressure = 110; 25 | 26 | constexpr int kMinTapTouchDiameter = 300; 27 | constexpr int kMaxTapTouchDiameter = 3000; 28 | 29 | FakeKeyboard::FakeKeyboard(struct hw_config &hw_config, 30 | TouchFFManager &ffManager) : 31 | hw_config_(hw_config) { 32 | 33 | fn_key_pressed_ = false; 34 | 35 | LoadLayout("layout.csv"); 36 | 37 | ff_manager_ = &ffManager; 38 | 39 | } 40 | 41 | bool FakeKeyboard::LoadLayout(std::string const &layout_filename) { 42 | double hw_pitch_x, hw_pitch_y; 43 | 44 | hw_pitch_x = hw_config_.res_x / hw_config_.width_mm; 45 | hw_pitch_y = hw_config_.res_y / hw_config_.height_mm; 46 | 47 | LOG(DEBUG) << "pitch: " << hw_pitch_x << "x" << hw_pitch_y << "\n"; 48 | 49 | io::CSVReader<8, 50 | io::trim_chars<' ', '\t'>, 51 | io::no_quote_escape<';'>> l_csv(layout_filename); 52 | 53 | l_csv.read_header(io::ignore_missing_column, "x", "y", "width", "height", 54 | "name", "code", "name_fn", "code_fn"); 55 | 56 | double x, y, w, h; 57 | double left_margin, top_margin; 58 | std::string keyname; 59 | std::string keyname_fn = ""; 60 | int keycode = 0; 61 | int keycode_fn = 0; 62 | 63 | left_margin = hw_config_.left_margin_mm; 64 | top_margin = hw_config_.top_margin_mm; 65 | 66 | while(l_csv.read_row(x, y, w, h, keyname, keycode, keyname_fn, keycode_fn)) { 67 | LOG(DEBUG) << "Key " << keyname << "(" << keycode << ") | " << 68 | keyname_fn << " (" << keycode_fn << "): " << 69 | w << "x" << h << "@(" << x << "," << y << ") mm\n"; 70 | 71 | int x1 = 0, x2 = 0, y1 = 0, y2 = 0; 72 | 73 | switch (hw_config_.rotation) { 74 | case 0: 75 | x1 = (left_margin + x) * hw_pitch_x; 76 | x2 = (left_margin + x + w) * hw_pitch_x; 77 | y1 = (top_margin + y) * hw_pitch_y; 78 | y2 = (top_margin + y + h) * hw_pitch_y; 79 | break; 80 | case 90: 81 | x1 = (top_margin + y) * hw_pitch_x; 82 | x2 = (top_margin + y + h) * hw_pitch_x; 83 | y1 = (hw_config_.height_mm - (left_margin + x + w)) * hw_pitch_y; 84 | y2 = (hw_config_.height_mm - (left_margin + x)) * hw_pitch_y; 85 | break; 86 | case 180: 87 | x1 = (hw_config_.width_mm - (left_margin + x + w)) * hw_pitch_x; 88 | x1 = (hw_config_.width_mm - (left_margin + x)) * hw_pitch_x; 89 | y1 = (hw_config_.height_mm - (top_margin + y + h)) * hw_pitch_y; 90 | y2 = (hw_config_.height_mm - (top_margin + y)) * hw_pitch_y; 91 | break; 92 | case 270: 93 | x1 = (hw_config_.width_mm - (y + h + top_margin)) * hw_pitch_x; 94 | x2 = (hw_config_.width_mm - (y + top_margin)) * hw_pitch_x; 95 | y1 = (left_margin + x) * hw_pitch_y; 96 | y2 = (left_margin + x + w) * hw_pitch_y; 97 | break; 98 | default: 99 | LOG(ERROR) << "Rotation by " << hw_config_.rotation << " degrees is not supported\n"; 100 | return false; 101 | } 102 | 103 | LOG(DEBUG) << "HW coords: (" << x1 << ", " << y1 << "), (" << 104 | x2 << ", " << y2 << ")\n"; 105 | 106 | layout_.push_back(Key(keycode, keycode_fn, x1, x2, y1, y2)); 107 | } 108 | 109 | return true; 110 | } 111 | 112 | void FakeKeyboard::EnableKeyboardEvents() const { 113 | // Enable key events in general for output. 114 | EnableEventType(EV_KEY); 115 | // Enable each specific key code found in the layout. 116 | for (unsigned int i = 0; i < layout_.size(); i++) { 117 | EnableKeyEvent(layout_[i].event_code_); 118 | if (layout_[i].event_code_fn_) 119 | EnableKeyEvent(layout_[i].event_code_fn_); 120 | } 121 | } 122 | 123 | struct timespec FakeKeyboard::AddMsToTimespec(struct timespec const& orig, 124 | int additional_ms) { 125 | struct timespec extended = orig; 126 | extended.tv_nsec += additional_ms * 1e6; 127 | while (extended.tv_nsec >= 1e9) { 128 | extended.tv_nsec -= 1e9; 129 | extended.tv_sec += 1; 130 | } 131 | return extended; 132 | } 133 | 134 | bool FakeKeyboard::TimespecIsLater(struct timespec const& t1, 135 | struct timespec const& t2) { 136 | return ((t1.tv_sec > t2.tv_sec) || 137 | (t1.tv_sec == t2.tv_sec && t1.tv_nsec > t2.tv_nsec)); 138 | } 139 | 140 | int FakeKeyboard::GenerateEventForArrivingFinger( 141 | struct timespec now, 142 | struct mtstatemachine::MtFinger const &finger, int tid, int *event_code) { 143 | 144 | for (unsigned int key_num = 0; key_num < layout_.size(); key_num++) { 145 | if (layout_[key_num].Contains(finger.x, finger.y)) { 146 | 147 | if (fn_key_pressed_ && layout_[key_num].event_code_fn_) 148 | *event_code = layout_[key_num].event_code_fn_; 149 | else 150 | *event_code = layout_[key_num].event_code_; 151 | 152 | LOG(DEBUG) << "fn_key_pressed_: " << fn_key_pressed_ << ", event_code: " << 153 | *event_code << "\n"; 154 | 155 | Event ev(*event_code, kKeyDownEvent, 156 | AddMsToTimespec(now, kEventDelayMS), tid); 157 | EnqueueEvent(ev); 158 | return key_num; 159 | } 160 | } 161 | return kNoKey; 162 | } 163 | 164 | void FakeKeyboard::HandleLeavingFinger(int tid, FingerData finger, 165 | timespec now) { 166 | bool up_event_guaranteed = false, down_event_guaranteed = false; 167 | 168 | // If the finger has already been marked dead for some reason, ignore it. 169 | if (finger.rejection_status_ != RejectionStatus::kNotRejectedYet) { 170 | return; 171 | } 172 | 173 | // If there is an outstanding down event for this finger and mark it 174 | // guaranteed. 175 | if (!finger.down_sent_) { 176 | std::list::iterator it = pending_events_.begin(); 177 | while (it != pending_events_.end()) { 178 | if (it->tid_ == tid) { 179 | it->is_guaranteed_ = true; 180 | down_event_guaranteed |= it->is_down_; 181 | up_event_guaranteed |= !it->is_down_; 182 | break; 183 | } 184 | it++; 185 | } 186 | } 187 | 188 | // If we're here we either have already sent the key_down event for this 189 | // finger, or have just marked in guaranteed. Either way we have to enqueue 190 | // a guaranteed up event now if there isn't one already. 191 | if (!up_event_guaranteed) { 192 | EnqueueKeyUpEvent(finger.event_code_, now); 193 | if (finger.event_code_ == KEY_FN) 194 | fn_key_pressed_ = false; 195 | } 196 | } 197 | 198 | void FakeKeyboard::EnqueueKeyUpEvent(int ev_code, timespec now) { 199 | Event up_event(ev_code, kKeyUpEvent, 200 | AddMsToTimespec(now, kEventDelayMS), kOldTID); 201 | up_event.is_guaranteed_ = true; 202 | EnqueueEvent(up_event); 203 | } 204 | 205 | bool FakeKeyboard::StillOnFirstKey( 206 | struct mtstatemachine::MtFinger const & finger, 207 | FingerData const & data) const { 208 | // If this finger didn't start on a key, then automatically return false. 209 | if (data.starting_key_number_ == kNoKey) { 210 | return false; 211 | } 212 | 213 | // Otherwise, see if it's still contained in that starting key. 214 | return layout_.at(data.starting_key_number_).Contains(finger.x, finger.y); 215 | } 216 | 217 | void FakeKeyboard::RejectFinger(int tid, RejectionStatus reason) { 218 | LOG(DEBUG) << "Reject finger, reason " << static_cast(reason) << "\n"; 219 | // First, mark the finger's FingerData as rejected. 220 | finger_data_[tid].rejection_status_ = reason; 221 | 222 | // Next, scan through the pending events and delete any for that finger. 223 | std::list::iterator it = pending_events_.begin(); 224 | while (it != pending_events_.end()) { 225 | auto current = it++; 226 | if (current->tid_ == tid) { 227 | pending_events_.erase(current); 228 | } 229 | } 230 | } 231 | 232 | void FakeKeyboard::ProcessIncomingSnapshot( 233 | struct timespec now, 234 | std::unordered_map const &snapshot) { 235 | // First we go through all the touches reported by the touchscreen in the most 236 | // recent snapshot. 237 | for (auto map_entry : snapshot) { 238 | int tid = map_entry.first; 239 | struct mtstatemachine::MtFinger finger = map_entry.second; 240 | 241 | std::unordered_map::iterator data_for_tid_it; 242 | data_for_tid_it = finger_data_.find(tid); 243 | if (data_for_tid_it == finger_data_.end()) { 244 | int event_code = 0; 245 | int key = GenerateEventForArrivingFinger(now, finger, tid, &event_code); 246 | 247 | // If this is a newly arriving finger, make a new entry for it and fill 248 | // out all the starting data we have. In some cases, this may invalidate 249 | // a finger immediately. 250 | FingerData data; 251 | data.arrival_time_ = now; 252 | data.max_pressure_ = finger.p; 253 | data.max_touch_major_ = finger.touch_major; 254 | data.starting_key_number_ = key; 255 | data.event_code_ = event_code; 256 | data.down_sent_ = false; 257 | data.rejection_status_ = RejectionStatus::kNotRejectedYet; 258 | 259 | if (event_code == KEY_FN) 260 | fn_key_pressed_ = true; 261 | 262 | if (key == kNoKey) { 263 | data.rejection_status_ = RejectionStatus::kRejectTouchdownOffKey; 264 | } else { 265 | ff_manager_->EventTriggered(TouchKeyboardEvent::FingerDown, finger.x, finger.y); 266 | } 267 | 268 | // TODO(charliemooney): Add more data here that can be used for 269 | // tracking fingers. 270 | finger_data_[tid] = data; 271 | } else if (data_for_tid_it->second.rejection_status_ == 272 | RejectionStatus::kNotRejectedYet) { 273 | // If we've seen this finger before, update the data on it. 274 | // First, Check if the maxium pressure has changed. 275 | data_for_tid_it->second.max_pressure_ = 276 | std::max(data_for_tid_it->second.max_pressure_, finger.p); 277 | 278 | // The same for touch contact diameter 279 | data_for_tid_it->second.max_touch_major_ = 280 | std::max(data_for_tid_it->second.max_touch_major_, finger.touch_major); 281 | 282 | // Check if the finger has left the key it started on 283 | if (!StillOnFirstKey(finger, data_for_tid_it->second)) { 284 | RejectFinger(data_for_tid_it->first, 285 | RejectionStatus::kRejectMovedOffKey); 286 | if (data_for_tid_it->second.down_sent_) { 287 | // Send a KeyUp event to cancel any held-down buttons. 288 | EnqueueKeyUpEvent( 289 | data_for_tid_it->second.event_code_, now); 290 | 291 | if (data_for_tid_it->second.event_code_ == KEY_FN) 292 | fn_key_pressed_ = false; 293 | } 294 | } 295 | 296 | // TODO(charliemooney): Update the additional data here, once it's added. 297 | } 298 | } 299 | 300 | // Next we need to check if there are any fingers missing that we saw before 301 | // which would indicate a finger leaving the touchscreen. 302 | std::unordered_map::iterator data_it = finger_data_.begin(); 303 | while (data_it != finger_data_.end()) { 304 | int tid = data_it->first; 305 | FingerData this_finger_data = data_it->second; 306 | auto input_it = snapshot.find(tid); 307 | if (tid != kOldTID && input_it == snapshot.end()) { 308 | HandleLeavingFinger(tid, this_finger_data, now); 309 | auto next = data_it; 310 | next++; 311 | finger_data_.erase(data_it); 312 | data_it = next; 313 | } else { 314 | data_it++; 315 | } 316 | } 317 | } 318 | 319 | void FakeKeyboard::EnqueueEvent(Event ev) { 320 | // Find the right place in the queue, maintaining the order. 321 | std::list::iterator it = pending_events_.begin(); 322 | while (it != pending_events_.end()) { 323 | if (TimespecIsLater(it->deadline_, ev.deadline_)) { 324 | pending_events_.insert(it, ev); 325 | return; 326 | } 327 | it++; 328 | } 329 | 330 | // If we didn't find a place for it, then it must belong at the end. 331 | pending_events_.push_back(ev); 332 | } 333 | 334 | void FakeKeyboard::Consume() { 335 | while (1) { 336 | bool needs_syn = false; 337 | 338 | // Compute how long to wait for a timeout. (At most until the next pending 339 | // event is set to fire) 340 | struct timespec now; 341 | clock_gettime(CLOCK_MONOTONIC, &now); 342 | int timeout_ms = -1; // No timeout if there aren't any pending events. 343 | if (!pending_events_.empty()) { 344 | timespec deadline = pending_events_.front().deadline_; 345 | timeout_ms = ((deadline.tv_sec - now.tv_sec) * 1000 + 346 | (deadline.tv_nsec - now.tv_nsec) / 1000000); 347 | timeout_ms++; // Always add 1 more ms so as to not undershoot. 348 | if (timeout_ms < 0) { 349 | LOG(WARNING) << "Negative timeout (" << timeout_ms << 350 | "). We missed an event somewhere!\n"; 351 | timeout_ms = 1; 352 | } 353 | } 354 | 355 | // Wait for a touchscreen event or a timeout. If there's an event from 356 | // the touchscreen add it to the statemachine. Otherwise, that means it's 357 | // time to fire the next pending event. 358 | struct input_event ev; 359 | bool event_ready = GetNextEvent(timeout_ms, &ev); 360 | clock_gettime(CLOCK_MONOTONIC, &now); 361 | if (event_ready) { 362 | std::unordered_map snapshot; 363 | if (sm_.AddEvent(ev, &snapshot)) { 364 | // Here we process the new snapshot, enqueing events as needed. 365 | ProcessIncomingSnapshot(now, snapshot); 366 | } else { 367 | // If it was a touchscreen event but *not* the end of a snapshot, then 368 | // just continue the loop. This allows us to treat touchscreen events 369 | // as atomic events where the entire snapshot arrives at one time. 370 | continue; 371 | } 372 | } 373 | 374 | // Loop over pending events and process any that are ready to fire. 375 | while (!pending_events_.empty()) { 376 | // If the next event's deadline is still in the future, stop looking. 377 | Event next_event = pending_events_.front(); 378 | if (TimespecIsLater(next_event.deadline_, now)) { 379 | break; 380 | } 381 | 382 | // Pop off the next pending event and process it now. 383 | pending_events_.pop_front(); 384 | 385 | // Look up the FingerData associated with this event and make sure the 386 | // event is still valid. 387 | std::unordered_map::iterator it; 388 | it = finger_data_.find(next_event.tid_); 389 | if (it != finger_data_.end()) { 390 | // Here we check to see if this event is still valid before firing it 391 | // off to the OS. Currently there is only a pressure check here, but 392 | // more could easily be added later. 393 | 394 | if (it->second.max_pressure_ != -1) { 395 | // This checks if the maximum pressure a finger reported is within 396 | // range. An exception is made for the spacebar since it is often 397 | // pressed by a user's thumb, which may have unusually high pressure. 398 | if (it->second.max_pressure_ < kMinTapPressure || 399 | (layout_[it->second.starting_key_number_].event_code_ != 400 | KEY_SPACE && it->second.max_pressure_ > kMaxTapPressure)) { 401 | LOG(INFO) << "Tap rejected! Pressure of " << 402 | it->second.max_pressure_ << " is out of range " << 403 | kMinTapPressure << "->" << kMaxTapPressure << "\n"; 404 | continue; 405 | } 406 | } else { 407 | if (it->second.max_touch_major_ < kMinTapTouchDiameter || 408 | (layout_[it->second.starting_key_number_].event_code_ != 409 | KEY_SPACE && it->second.max_touch_major_ > kMaxTapTouchDiameter)) { 410 | LOG(INFO) << "Tap rejected! Diameter of " << 411 | it->second.max_touch_major_ << " is out of range " << 412 | kMinTapTouchDiameter << "->" << kMaxTapTouchDiameter << "\n"; 413 | continue; 414 | } 415 | 416 | } 417 | } else { 418 | // The finger has already left -- that's OK as long as it is 419 | // "guaranteed" to fire. 420 | if (!next_event.is_guaranteed_) { 421 | LOG(ERROR) << "No finger data for event that should have some! " << 422 | "(guaranteed: " << next_event.is_guaranteed_ << ", " << 423 | "is_down: " << next_event.is_down_ << ", " << 424 | "tid: " << next_event.tid_ << ")\n"; 425 | } 426 | } 427 | 428 | LOG(DEBUG) << "Event: EV_KEY, code " << next_event.ev_code_ << " down: " << next_event.is_down_ << "\n"; 429 | // Actually send the event and update the fingerdata if applicable. 430 | SendEvent(EV_KEY, next_event.ev_code_, next_event.is_down_ ? 1 : 0); 431 | needs_syn = true; 432 | if (next_event.is_down_) { 433 | std::unordered_map::iterator it; 434 | it = finger_data_.find(next_event.tid_); 435 | if (it != finger_data_.end()) { 436 | finger_data_[next_event.tid_].down_sent_ = true; 437 | } 438 | } 439 | } 440 | if (needs_syn) { 441 | // Finally, send out a SYN after all applicable events are sent. 442 | SendEvent(EV_SYN, SYN_REPORT, 0); 443 | } 444 | } 445 | } 446 | 447 | void FakeKeyboard::Start(std::string const &source_device_path, 448 | std::string const &keyboard_device_name) { 449 | // Do all the set up steps. 450 | if (!OpenSourceDevice(source_device_path)) 451 | return; 452 | 453 | CreateUinputFD(); 454 | EnableKeyboardEvents(); 455 | FinalizeUinputCreation(keyboard_device_name); 456 | 457 | // Loop forever, comsuming the events coming in from the source device and 458 | // generating keystroke events when appropriate. 459 | Consume(); 460 | } 461 | 462 | } // namespace touch_keyboard 463 | -------------------------------------------------------------------------------- /csv.h: -------------------------------------------------------------------------------- 1 | // Copyright: (2012-2015) Ben Strasser 2 | // License: BSD-3 3 | // 4 | // All rights reserved. 5 | // 6 | // Redistribution and use in source and binary forms, with or without 7 | // modification, are permitted provided that the following conditions are met: 8 | // 9 | // 1. Redistributions of source code must retain the above copyright notice, 10 | // this list of conditions and the following disclaimer. 11 | // 12 | // 2. Redistributions in binary form must reproduce the above copyright notice, 13 | // this list of conditions and the following disclaimer in the documentation 14 | // and/or other materials provided with the distribution. 15 | // 16 | // 3. Neither the name of the copyright holder nor the names of its contributors 17 | // may be used to endorse or promote products derived from this software 18 | // without specific prior written permission. 19 | // 20 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | // POSSIBILITY OF SUCH DAMAGE. 31 | 32 | #ifndef CSV_H 33 | #define CSV_H 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #ifndef CSV_IO_NO_THREAD 43 | #include 44 | #include 45 | #include 46 | #endif 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | 53 | namespace io { 54 | //////////////////////////////////////////////////////////////////////////// 55 | // LineReader // 56 | //////////////////////////////////////////////////////////////////////////// 57 | 58 | namespace error { 59 | struct base : std::exception { 60 | virtual void format_error_message() const = 0; 61 | 62 | const char *what() const noexcept override { 63 | format_error_message(); 64 | return error_message_buffer; 65 | } 66 | 67 | mutable char error_message_buffer[2048]; 68 | }; 69 | 70 | // this only affects the file name in the error message 71 | const int max_file_name_length = 1024; 72 | 73 | struct with_file_name { 74 | with_file_name() { std::memset(file_name, 0, sizeof(file_name)); } 75 | 76 | void set_file_name(const char *file_name) { 77 | if (file_name != nullptr) { 78 | // This call to strncpy has parenthesis around it 79 | // to silence the GCC -Wstringop-truncation warning 80 | (strncpy(this->file_name, file_name, sizeof(this->file_name))); 81 | this->file_name[sizeof(this->file_name) - 1] = '\0'; 82 | } else { 83 | this->file_name[0] = '\0'; 84 | } 85 | } 86 | 87 | char file_name[max_file_name_length + 1]; 88 | }; 89 | 90 | struct with_file_line { 91 | with_file_line() { file_line = -1; } 92 | 93 | void set_file_line(int file_line) { this->file_line = file_line; } 94 | 95 | int file_line; 96 | }; 97 | 98 | struct with_errno { 99 | with_errno() { errno_value = 0; } 100 | 101 | void set_errno(int errno_value) { this->errno_value = errno_value; } 102 | 103 | int errno_value; 104 | }; 105 | 106 | struct can_not_open_file : base, with_file_name, with_errno { 107 | void format_error_message() const override { 108 | if (errno_value != 0) 109 | std::snprintf(error_message_buffer, sizeof(error_message_buffer), 110 | "Can not open file \"%s\" because \"%s\".", file_name, 111 | std::strerror(errno_value)); 112 | else 113 | std::snprintf(error_message_buffer, sizeof(error_message_buffer), 114 | "Can not open file \"%s\".", file_name); 115 | } 116 | }; 117 | 118 | struct line_length_limit_exceeded : base, with_file_name, with_file_line { 119 | void format_error_message() const override { 120 | std::snprintf( 121 | error_message_buffer, sizeof(error_message_buffer), 122 | "Line number %d in file \"%s\" exceeds the maximum length of 2^24-1.", 123 | file_line, file_name); 124 | } 125 | }; 126 | } // namespace error 127 | 128 | class ByteSourceBase { 129 | public: 130 | virtual int read(char *buffer, int size) = 0; 131 | virtual ~ByteSourceBase() {} 132 | }; 133 | 134 | namespace detail { 135 | 136 | class OwningStdIOByteSourceBase : public ByteSourceBase { 137 | public: 138 | explicit OwningStdIOByteSourceBase(FILE *file) : file(file) { 139 | // Tell the std library that we want to do the buffering ourself. 140 | std::setvbuf(file, 0, _IONBF, 0); 141 | } 142 | 143 | int read(char *buffer, int size) { return std::fread(buffer, 1, size, file); } 144 | 145 | ~OwningStdIOByteSourceBase() { std::fclose(file); } 146 | 147 | private: 148 | FILE *file; 149 | }; 150 | 151 | class NonOwningIStreamByteSource : public ByteSourceBase { 152 | public: 153 | explicit NonOwningIStreamByteSource(std::istream &in) : in(in) {} 154 | 155 | int read(char *buffer, int size) { 156 | in.read(buffer, size); 157 | return in.gcount(); 158 | } 159 | 160 | ~NonOwningIStreamByteSource() {} 161 | 162 | private: 163 | std::istream ∈ 164 | }; 165 | 166 | class NonOwningStringByteSource : public ByteSourceBase { 167 | public: 168 | NonOwningStringByteSource(const char *str, long long size) 169 | : str(str), remaining_byte_count(size) {} 170 | 171 | int read(char *buffer, int desired_byte_count) { 172 | int to_copy_byte_count = desired_byte_count; 173 | if (remaining_byte_count < to_copy_byte_count) 174 | to_copy_byte_count = remaining_byte_count; 175 | std::memcpy(buffer, str, to_copy_byte_count); 176 | remaining_byte_count -= to_copy_byte_count; 177 | str += to_copy_byte_count; 178 | return to_copy_byte_count; 179 | } 180 | 181 | ~NonOwningStringByteSource() {} 182 | 183 | private: 184 | const char *str; 185 | long long remaining_byte_count; 186 | }; 187 | 188 | #ifndef CSV_IO_NO_THREAD 189 | class AsynchronousReader { 190 | public: 191 | void init(std::unique_ptr arg_byte_source) { 192 | std::unique_lock guard(lock); 193 | byte_source = std::move(arg_byte_source); 194 | desired_byte_count = -1; 195 | termination_requested = false; 196 | worker = std::thread([&] { 197 | std::unique_lock guard(lock); 198 | try { 199 | for (;;) { 200 | read_requested_condition.wait(guard, [&] { 201 | return desired_byte_count != -1 || termination_requested; 202 | }); 203 | if (termination_requested) 204 | return; 205 | 206 | read_byte_count = byte_source->read(buffer, desired_byte_count); 207 | desired_byte_count = -1; 208 | if (read_byte_count == 0) 209 | break; 210 | read_finished_condition.notify_one(); 211 | } 212 | } catch (...) { 213 | read_error = std::current_exception(); 214 | } 215 | read_finished_condition.notify_one(); 216 | }); 217 | } 218 | 219 | bool is_valid() const { return byte_source != nullptr; } 220 | 221 | void start_read(char *arg_buffer, int arg_desired_byte_count) { 222 | std::unique_lock guard(lock); 223 | buffer = arg_buffer; 224 | desired_byte_count = arg_desired_byte_count; 225 | read_byte_count = -1; 226 | read_requested_condition.notify_one(); 227 | } 228 | 229 | int finish_read() { 230 | std::unique_lock guard(lock); 231 | read_finished_condition.wait( 232 | guard, [&] { return read_byte_count != -1 || read_error; }); 233 | if (read_error) 234 | std::rethrow_exception(read_error); 235 | else 236 | return read_byte_count; 237 | } 238 | 239 | ~AsynchronousReader() { 240 | if (byte_source != nullptr) { 241 | { 242 | std::unique_lock guard(lock); 243 | termination_requested = true; 244 | } 245 | read_requested_condition.notify_one(); 246 | worker.join(); 247 | } 248 | } 249 | 250 | private: 251 | std::unique_ptr byte_source; 252 | 253 | std::thread worker; 254 | 255 | bool termination_requested; 256 | std::exception_ptr read_error; 257 | char *buffer; 258 | int desired_byte_count; 259 | int read_byte_count; 260 | 261 | std::mutex lock; 262 | std::condition_variable read_finished_condition; 263 | std::condition_variable read_requested_condition; 264 | }; 265 | #endif 266 | 267 | class SynchronousReader { 268 | public: 269 | void init(std::unique_ptr arg_byte_source) { 270 | byte_source = std::move(arg_byte_source); 271 | } 272 | 273 | bool is_valid() const { return byte_source != nullptr; } 274 | 275 | void start_read(char *arg_buffer, int arg_desired_byte_count) { 276 | buffer = arg_buffer; 277 | desired_byte_count = arg_desired_byte_count; 278 | } 279 | 280 | int finish_read() { return byte_source->read(buffer, desired_byte_count); } 281 | 282 | private: 283 | std::unique_ptr byte_source; 284 | char *buffer; 285 | int desired_byte_count; 286 | }; 287 | } // namespace detail 288 | 289 | class LineReader { 290 | private: 291 | static const int block_len = 1 << 20; 292 | std::unique_ptr buffer; // must be constructed before (and thus 293 | // destructed after) the reader! 294 | #ifdef CSV_IO_NO_THREAD 295 | detail::SynchronousReader reader; 296 | #else 297 | detail::AsynchronousReader reader; 298 | #endif 299 | int data_begin; 300 | int data_end; 301 | 302 | char file_name[error::max_file_name_length + 1]; 303 | unsigned file_line; 304 | 305 | static std::unique_ptr open_file(const char *file_name) { 306 | // We open the file in binary mode as it makes no difference under *nix 307 | // and under Windows we handle \r\n newlines ourself. 308 | FILE *file = std::fopen(file_name, "rb"); 309 | if (file == 0) { 310 | int x = errno; // store errno as soon as possible, doing it after 311 | // constructor call can fail. 312 | error::can_not_open_file err; 313 | err.set_errno(x); 314 | err.set_file_name(file_name); 315 | throw err; 316 | } 317 | return std::unique_ptr( 318 | new detail::OwningStdIOByteSourceBase(file)); 319 | } 320 | 321 | void init(std::unique_ptr byte_source) { 322 | file_line = 0; 323 | 324 | buffer = std::unique_ptr(new char[3 * block_len]); 325 | data_begin = 0; 326 | data_end = byte_source->read(buffer.get(), 2 * block_len); 327 | 328 | // Ignore UTF-8 BOM 329 | if (data_end >= 3 && buffer[0] == '\xEF' && buffer[1] == '\xBB' && 330 | buffer[2] == '\xBF') 331 | data_begin = 3; 332 | 333 | if (data_end == 2 * block_len) { 334 | reader.init(std::move(byte_source)); 335 | reader.start_read(buffer.get() + 2 * block_len, block_len); 336 | } 337 | } 338 | 339 | public: 340 | LineReader() = delete; 341 | LineReader(const LineReader &) = delete; 342 | LineReader &operator=(const LineReader &) = delete; 343 | 344 | explicit LineReader(const char *file_name) { 345 | set_file_name(file_name); 346 | init(open_file(file_name)); 347 | } 348 | 349 | explicit LineReader(const std::string &file_name) { 350 | set_file_name(file_name.c_str()); 351 | init(open_file(file_name.c_str())); 352 | } 353 | 354 | LineReader(const char *file_name, 355 | std::unique_ptr byte_source) { 356 | set_file_name(file_name); 357 | init(std::move(byte_source)); 358 | } 359 | 360 | LineReader(const std::string &file_name, 361 | std::unique_ptr byte_source) { 362 | set_file_name(file_name.c_str()); 363 | init(std::move(byte_source)); 364 | } 365 | 366 | LineReader(const char *file_name, const char *data_begin, 367 | const char *data_end) { 368 | set_file_name(file_name); 369 | init(std::unique_ptr(new detail::NonOwningStringByteSource( 370 | data_begin, data_end - data_begin))); 371 | } 372 | 373 | LineReader(const std::string &file_name, const char *data_begin, 374 | const char *data_end) { 375 | set_file_name(file_name.c_str()); 376 | init(std::unique_ptr(new detail::NonOwningStringByteSource( 377 | data_begin, data_end - data_begin))); 378 | } 379 | 380 | LineReader(const char *file_name, FILE *file) { 381 | set_file_name(file_name); 382 | init(std::unique_ptr( 383 | new detail::OwningStdIOByteSourceBase(file))); 384 | } 385 | 386 | LineReader(const std::string &file_name, FILE *file) { 387 | set_file_name(file_name.c_str()); 388 | init(std::unique_ptr( 389 | new detail::OwningStdIOByteSourceBase(file))); 390 | } 391 | 392 | LineReader(const char *file_name, std::istream &in) { 393 | set_file_name(file_name); 394 | init(std::unique_ptr( 395 | new detail::NonOwningIStreamByteSource(in))); 396 | } 397 | 398 | LineReader(const std::string &file_name, std::istream &in) { 399 | set_file_name(file_name.c_str()); 400 | init(std::unique_ptr( 401 | new detail::NonOwningIStreamByteSource(in))); 402 | } 403 | 404 | void set_file_name(const std::string &file_name) { 405 | set_file_name(file_name.c_str()); 406 | } 407 | 408 | void set_file_name(const char *file_name) { 409 | if (file_name != nullptr) { 410 | strncpy(this->file_name, file_name, sizeof(this->file_name) - 1); 411 | this->file_name[sizeof(this->file_name) - 1] = '\0'; 412 | } else { 413 | this->file_name[0] = '\0'; 414 | } 415 | } 416 | 417 | const char *get_truncated_file_name() const { return file_name; } 418 | 419 | void set_file_line(unsigned file_line) { this->file_line = file_line; } 420 | 421 | unsigned get_file_line() const { return file_line; } 422 | 423 | char *next_line() { 424 | if (data_begin == data_end) 425 | return nullptr; 426 | 427 | ++file_line; 428 | 429 | assert(data_begin < data_end); 430 | assert(data_end <= block_len * 2); 431 | 432 | if (data_begin >= block_len) { 433 | std::memcpy(buffer.get(), buffer.get() + block_len, block_len); 434 | data_begin -= block_len; 435 | data_end -= block_len; 436 | if (reader.is_valid()) { 437 | data_end += reader.finish_read(); 438 | std::memcpy(buffer.get() + block_len, buffer.get() + 2 * block_len, 439 | block_len); 440 | reader.start_read(buffer.get() + 2 * block_len, block_len); 441 | } 442 | } 443 | 444 | int line_end = data_begin; 445 | while (line_end != data_end && buffer[line_end] != '\n') { 446 | ++line_end; 447 | } 448 | 449 | if (line_end - data_begin + 1 > block_len) { 450 | error::line_length_limit_exceeded err; 451 | err.set_file_name(file_name); 452 | err.set_file_line(file_line); 453 | throw err; 454 | } 455 | 456 | if (line_end != data_end && buffer[line_end] == '\n') { 457 | buffer[line_end] = '\0'; 458 | } else { 459 | // some files are missing the newline at the end of the 460 | // last line 461 | ++data_end; 462 | buffer[line_end] = '\0'; 463 | } 464 | 465 | // handle windows \r\n-line breaks 466 | if (line_end != data_begin && buffer[line_end - 1] == '\r') 467 | buffer[line_end - 1] = '\0'; 468 | 469 | char *ret = buffer.get() + data_begin; 470 | data_begin = line_end + 1; 471 | return ret; 472 | } 473 | }; 474 | 475 | //////////////////////////////////////////////////////////////////////////// 476 | // CSV // 477 | //////////////////////////////////////////////////////////////////////////// 478 | 479 | namespace error { 480 | const int max_column_name_length = 63; 481 | struct with_column_name { 482 | with_column_name() { 483 | std::memset(column_name, 0, max_column_name_length + 1); 484 | } 485 | 486 | void set_column_name(const char *column_name) { 487 | if (column_name != nullptr) { 488 | std::strncpy(this->column_name, column_name, max_column_name_length); 489 | this->column_name[max_column_name_length] = '\0'; 490 | } else { 491 | this->column_name[0] = '\0'; 492 | } 493 | } 494 | 495 | char column_name[max_column_name_length + 1]; 496 | }; 497 | 498 | const int max_column_content_length = 63; 499 | 500 | struct with_column_content { 501 | with_column_content() { 502 | std::memset(column_content, 0, max_column_content_length + 1); 503 | } 504 | 505 | void set_column_content(const char *column_content) { 506 | if (column_content != nullptr) { 507 | std::strncpy(this->column_content, column_content, 508 | max_column_content_length); 509 | this->column_content[max_column_content_length] = '\0'; 510 | } else { 511 | this->column_content[0] = '\0'; 512 | } 513 | } 514 | 515 | char column_content[max_column_content_length + 1]; 516 | }; 517 | 518 | struct extra_column_in_header : base, with_file_name, with_column_name { 519 | void format_error_message() const override { 520 | std::snprintf(error_message_buffer, sizeof(error_message_buffer), 521 | R"(Extra column "%s" in header of file "%s".)", column_name, 522 | file_name); 523 | } 524 | }; 525 | 526 | struct missing_column_in_header : base, with_file_name, with_column_name { 527 | void format_error_message() const override { 528 | std::snprintf(error_message_buffer, sizeof(error_message_buffer), 529 | R"(Missing column "%s" in header of file "%s".)", column_name, 530 | file_name); 531 | } 532 | }; 533 | 534 | struct duplicated_column_in_header : base, with_file_name, with_column_name { 535 | void format_error_message() const override { 536 | std::snprintf(error_message_buffer, sizeof(error_message_buffer), 537 | R"(Duplicated column "%s" in header of file "%s".)", 538 | column_name, file_name); 539 | } 540 | }; 541 | 542 | struct header_missing : base, with_file_name { 543 | void format_error_message() const override { 544 | std::snprintf(error_message_buffer, sizeof(error_message_buffer), 545 | "Header missing in file \"%s\".", file_name); 546 | } 547 | }; 548 | 549 | struct too_few_columns : base, with_file_name, with_file_line { 550 | void format_error_message() const override { 551 | std::snprintf(error_message_buffer, sizeof(error_message_buffer), 552 | "Too few columns in line %d in file \"%s\".", file_line, 553 | file_name); 554 | } 555 | }; 556 | 557 | struct too_many_columns : base, with_file_name, with_file_line { 558 | void format_error_message() const override { 559 | std::snprintf(error_message_buffer, sizeof(error_message_buffer), 560 | "Too many columns in line %d in file \"%s\".", file_line, 561 | file_name); 562 | } 563 | }; 564 | 565 | struct escaped_string_not_closed : base, with_file_name, with_file_line { 566 | void format_error_message() const override { 567 | std::snprintf(error_message_buffer, sizeof(error_message_buffer), 568 | "Escaped string was not closed in line %d in file \"%s\".", 569 | file_line, file_name); 570 | } 571 | }; 572 | 573 | struct integer_must_be_positive : base, 574 | with_file_name, 575 | with_file_line, 576 | with_column_name, 577 | with_column_content { 578 | void format_error_message() const override { 579 | std::snprintf( 580 | error_message_buffer, sizeof(error_message_buffer), 581 | R"(The integer "%s" must be positive or 0 in column "%s" in file "%s" in line "%d".)", 582 | column_content, column_name, file_name, file_line); 583 | } 584 | }; 585 | 586 | struct no_digit : base, 587 | with_file_name, 588 | with_file_line, 589 | with_column_name, 590 | with_column_content { 591 | void format_error_message() const override { 592 | std::snprintf( 593 | error_message_buffer, sizeof(error_message_buffer), 594 | R"(The integer "%s" contains an invalid digit in column "%s" in file "%s" in line "%d".)", 595 | column_content, column_name, file_name, file_line); 596 | } 597 | }; 598 | 599 | struct integer_overflow : base, 600 | with_file_name, 601 | with_file_line, 602 | with_column_name, 603 | with_column_content { 604 | void format_error_message() const override { 605 | std::snprintf( 606 | error_message_buffer, sizeof(error_message_buffer), 607 | R"(The integer "%s" overflows in column "%s" in file "%s" in line "%d".)", 608 | column_content, column_name, file_name, file_line); 609 | } 610 | }; 611 | 612 | struct integer_underflow : base, 613 | with_file_name, 614 | with_file_line, 615 | with_column_name, 616 | with_column_content { 617 | void format_error_message() const override { 618 | std::snprintf( 619 | error_message_buffer, sizeof(error_message_buffer), 620 | R"(The integer "%s" underflows in column "%s" in file "%s" in line "%d".)", 621 | column_content, column_name, file_name, file_line); 622 | } 623 | }; 624 | 625 | struct invalid_single_character : base, 626 | with_file_name, 627 | with_file_line, 628 | with_column_name, 629 | with_column_content { 630 | void format_error_message() const override { 631 | std::snprintf( 632 | error_message_buffer, sizeof(error_message_buffer), 633 | R"(The content "%s" of column "%s" in file "%s" in line "%d" is not a single character.)", 634 | column_content, column_name, file_name, file_line); 635 | } 636 | }; 637 | } // namespace error 638 | 639 | using ignore_column = unsigned int; 640 | static const ignore_column ignore_no_column = 0; 641 | static const ignore_column ignore_extra_column = 1; 642 | static const ignore_column ignore_missing_column = 2; 643 | 644 | template struct trim_chars { 645 | private: 646 | constexpr static bool is_trim_char(char) { return false; } 647 | 648 | template 649 | constexpr static bool is_trim_char(char c, char trim_char, 650 | OtherTrimChars... other_trim_chars) { 651 | return c == trim_char || is_trim_char(c, other_trim_chars...); 652 | } 653 | 654 | public: 655 | static void trim(char *&str_begin, char *&str_end) { 656 | while (str_begin != str_end && is_trim_char(*str_begin, trim_char_list...)) 657 | ++str_begin; 658 | while (str_begin != str_end && 659 | is_trim_char(*(str_end - 1), trim_char_list...)) 660 | --str_end; 661 | *str_end = '\0'; 662 | } 663 | }; 664 | 665 | struct no_comment { 666 | static bool is_comment(const char *) { return false; } 667 | }; 668 | 669 | template struct single_line_comment { 670 | private: 671 | constexpr static bool is_comment_start_char(char) { return false; } 672 | 673 | template 674 | constexpr static bool 675 | is_comment_start_char(char c, char comment_start_char, 676 | OtherCommentStartChars... other_comment_start_chars) { 677 | return c == comment_start_char || 678 | is_comment_start_char(c, other_comment_start_chars...); 679 | } 680 | 681 | public: 682 | static bool is_comment(const char *line) { 683 | return is_comment_start_char(*line, comment_start_char_list...); 684 | } 685 | }; 686 | 687 | struct empty_line_comment { 688 | static bool is_comment(const char *line) { 689 | if (*line == '\0') 690 | return true; 691 | while (*line == ' ' || *line == '\t') { 692 | ++line; 693 | if (*line == 0) 694 | return true; 695 | } 696 | return false; 697 | } 698 | }; 699 | 700 | template 701 | struct single_and_empty_line_comment { 702 | static bool is_comment(const char *line) { 703 | return single_line_comment::is_comment(line) || 704 | empty_line_comment::is_comment(line); 705 | } 706 | }; 707 | 708 | template struct no_quote_escape { 709 | static const char *find_next_column_end(const char *col_begin) { 710 | while (*col_begin != sep && *col_begin != '\0') 711 | ++col_begin; 712 | return col_begin; 713 | } 714 | 715 | static void unescape(char *&, char *&) {} 716 | }; 717 | 718 | template struct double_quote_escape { 719 | static const char *find_next_column_end(const char *col_begin) { 720 | while (*col_begin != sep && *col_begin != '\0') 721 | if (*col_begin != quote) 722 | ++col_begin; 723 | else { 724 | do { 725 | ++col_begin; 726 | while (*col_begin != quote) { 727 | if (*col_begin == '\0') 728 | throw error::escaped_string_not_closed(); 729 | ++col_begin; 730 | } 731 | ++col_begin; 732 | } while (*col_begin == quote); 733 | } 734 | return col_begin; 735 | } 736 | 737 | static void unescape(char *&col_begin, char *&col_end) { 738 | if (col_end - col_begin >= 2) { 739 | if (*col_begin == quote && *(col_end - 1) == quote) { 740 | ++col_begin; 741 | --col_end; 742 | char *out = col_begin; 743 | for (char *in = col_begin; in != col_end; ++in) { 744 | if (*in == quote && (in + 1) != col_end && *(in + 1) == quote) { 745 | ++in; 746 | } 747 | *out = *in; 748 | ++out; 749 | } 750 | col_end = out; 751 | *col_end = '\0'; 752 | } 753 | } 754 | } 755 | }; 756 | 757 | struct throw_on_overflow { 758 | template static void on_overflow(T &) { 759 | throw error::integer_overflow(); 760 | } 761 | 762 | template static void on_underflow(T &) { 763 | throw error::integer_underflow(); 764 | } 765 | }; 766 | 767 | struct ignore_overflow { 768 | template static void on_overflow(T &) {} 769 | 770 | template static void on_underflow(T &) {} 771 | }; 772 | 773 | struct set_to_max_on_overflow { 774 | template static void on_overflow(T &x) { 775 | // using (std::numeric_limits::max) instead of 776 | // std::numeric_limits::max to make code including windows.h with its max 777 | // macro happy 778 | x = (std::numeric_limits::max)(); 779 | } 780 | 781 | template static void on_underflow(T &x) { 782 | x = (std::numeric_limits::min)(); 783 | } 784 | }; 785 | 786 | namespace detail { 787 | template 788 | void chop_next_column(char *&line, char *&col_begin, char *&col_end) { 789 | assert(line != nullptr); 790 | 791 | col_begin = line; 792 | // the col_begin + (... - col_begin) removes the constness 793 | col_end = 794 | col_begin + (quote_policy::find_next_column_end(col_begin) - col_begin); 795 | 796 | if (*col_end == '\0') { 797 | line = nullptr; 798 | } else { 799 | *col_end = '\0'; 800 | line = col_end + 1; 801 | } 802 | } 803 | 804 | template 805 | void parse_line(char *line, char **sorted_col, 806 | const std::vector &col_order) { 807 | for (int i : col_order) { 808 | if (line == nullptr) 809 | throw ::io::error::too_few_columns(); 810 | char *col_begin, *col_end; 811 | chop_next_column(line, col_begin, col_end); 812 | 813 | if (i != -1) { 814 | trim_policy::trim(col_begin, col_end); 815 | quote_policy::unescape(col_begin, col_end); 816 | 817 | sorted_col[i] = col_begin; 818 | } 819 | } 820 | if (line != nullptr) 821 | throw ::io::error::too_many_columns(); 822 | } 823 | 824 | template 825 | void parse_header_line(char *line, std::vector &col_order, 826 | const std::string *col_name, 827 | ignore_column ignore_policy) { 828 | col_order.clear(); 829 | 830 | bool found[column_count]; 831 | std::fill(found, found + column_count, false); 832 | while (line) { 833 | char *col_begin, *col_end; 834 | chop_next_column(line, col_begin, col_end); 835 | 836 | trim_policy::trim(col_begin, col_end); 837 | quote_policy::unescape(col_begin, col_end); 838 | 839 | for (unsigned i = 0; i < column_count; ++i) 840 | if (col_begin == col_name[i]) { 841 | if (found[i]) { 842 | error::duplicated_column_in_header err; 843 | err.set_column_name(col_begin); 844 | throw err; 845 | } 846 | found[i] = true; 847 | col_order.push_back(i); 848 | col_begin = 0; 849 | break; 850 | } 851 | if (col_begin) { 852 | if (ignore_policy & ::io::ignore_extra_column) 853 | col_order.push_back(-1); 854 | else { 855 | error::extra_column_in_header err; 856 | err.set_column_name(col_begin); 857 | throw err; 858 | } 859 | } 860 | } 861 | if (!(ignore_policy & ::io::ignore_missing_column)) { 862 | for (unsigned i = 0; i < column_count; ++i) { 863 | if (!found[i]) { 864 | error::missing_column_in_header err; 865 | err.set_column_name(col_name[i].c_str()); 866 | throw err; 867 | } 868 | } 869 | } 870 | } 871 | 872 | template void parse(char *col, char &x) { 873 | if (!*col) 874 | throw error::invalid_single_character(); 875 | x = *col; 876 | ++col; 877 | if (*col) 878 | throw error::invalid_single_character(); 879 | } 880 | 881 | template void parse(char *col, std::string &x) { 882 | x = col; 883 | } 884 | 885 | template void parse(char *col, const char *&x) { 886 | x = col; 887 | } 888 | 889 | template void parse(char *col, char *&x) { x = col; } 890 | 891 | template 892 | void parse_unsigned_integer(const char *col, T &x) { 893 | x = 0; 894 | while (*col != '\0') { 895 | if ('0' <= *col && *col <= '9') { 896 | T y = *col - '0'; 897 | if (x > ((std::numeric_limits::max)() - y) / 10) { 898 | overflow_policy::on_overflow(x); 899 | return; 900 | } 901 | x = 10 * x + y; 902 | } else 903 | throw error::no_digit(); 904 | ++col; 905 | } 906 | } 907 | 908 | template void parse(char *col, unsigned char &x) { 909 | parse_unsigned_integer(col, x); 910 | } 911 | template void parse(char *col, unsigned short &x) { 912 | parse_unsigned_integer(col, x); 913 | } 914 | template void parse(char *col, unsigned int &x) { 915 | parse_unsigned_integer(col, x); 916 | } 917 | template void parse(char *col, unsigned long &x) { 918 | parse_unsigned_integer(col, x); 919 | } 920 | template void parse(char *col, unsigned long long &x) { 921 | parse_unsigned_integer(col, x); 922 | } 923 | 924 | template 925 | void parse_signed_integer(const char *col, T &x) { 926 | if (*col == '-') { 927 | ++col; 928 | 929 | x = 0; 930 | while (*col != '\0') { 931 | if ('0' <= *col && *col <= '9') { 932 | T y = *col - '0'; 933 | if (x < ((std::numeric_limits::min)() + y) / 10) { 934 | overflow_policy::on_underflow(x); 935 | return; 936 | } 937 | x = 10 * x - y; 938 | } else 939 | throw error::no_digit(); 940 | ++col; 941 | } 942 | return; 943 | } else if (*col == '+') 944 | ++col; 945 | parse_unsigned_integer(col, x); 946 | } 947 | 948 | template void parse(char *col, signed char &x) { 949 | parse_signed_integer(col, x); 950 | } 951 | template void parse(char *col, signed short &x) { 952 | parse_signed_integer(col, x); 953 | } 954 | template void parse(char *col, signed int &x) { 955 | parse_signed_integer(col, x); 956 | } 957 | template void parse(char *col, signed long &x) { 958 | parse_signed_integer(col, x); 959 | } 960 | template void parse(char *col, signed long long &x) { 961 | parse_signed_integer(col, x); 962 | } 963 | 964 | template void parse_float(const char *col, T &x) { 965 | bool is_neg = false; 966 | if (*col == '-') { 967 | is_neg = true; 968 | ++col; 969 | } else if (*col == '+') 970 | ++col; 971 | 972 | x = 0; 973 | while ('0' <= *col && *col <= '9') { 974 | int y = *col - '0'; 975 | x *= 10; 976 | x += y; 977 | ++col; 978 | } 979 | 980 | if (*col == '.' || *col == ',') { 981 | ++col; 982 | T pos = 1; 983 | while ('0' <= *col && *col <= '9') { 984 | pos /= 10; 985 | int y = *col - '0'; 986 | ++col; 987 | x += y * pos; 988 | } 989 | } 990 | 991 | if (*col == 'e' || *col == 'E') { 992 | ++col; 993 | int e; 994 | 995 | parse_signed_integer(col, e); 996 | 997 | if (e != 0) { 998 | T base; 999 | if (e < 0) { 1000 | base = T(0.1); 1001 | e = -e; 1002 | } else { 1003 | base = T(10); 1004 | } 1005 | 1006 | while (e != 1) { 1007 | if ((e & 1) == 0) { 1008 | base = base * base; 1009 | e >>= 1; 1010 | } else { 1011 | x *= base; 1012 | --e; 1013 | } 1014 | } 1015 | x *= base; 1016 | } 1017 | } else { 1018 | if (*col != '\0') 1019 | throw error::no_digit(); 1020 | } 1021 | 1022 | if (is_neg) 1023 | x = -x; 1024 | } 1025 | 1026 | template void parse(char *col, float &x) { 1027 | parse_float(col, x); 1028 | } 1029 | template void parse(char *col, double &x) { 1030 | parse_float(col, x); 1031 | } 1032 | template void parse(char *col, long double &x) { 1033 | parse_float(col, x); 1034 | } 1035 | 1036 | template void parse(char *col, T &x) { 1037 | // Mute unused variable compiler warning 1038 | (void)col; 1039 | (void)x; 1040 | // GCC evaluates "false" when reading the template and 1041 | // "sizeof(T)!=sizeof(T)" only when instantiating it. This is why 1042 | // this strange construct is used. 1043 | static_assert(sizeof(T) != sizeof(T), 1044 | "Can not parse this type. Only builtin integrals, floats, " 1045 | "char, char*, const char* and std::string are supported"); 1046 | } 1047 | 1048 | } // namespace detail 1049 | 1050 | template , 1051 | class quote_policy = no_quote_escape<','>, 1052 | class overflow_policy = throw_on_overflow, 1053 | class comment_policy = no_comment> 1054 | class CSVReader { 1055 | private: 1056 | LineReader in; 1057 | 1058 | char *row[column_count]; 1059 | std::string column_names[column_count]; 1060 | 1061 | std::vector col_order; 1062 | 1063 | template 1064 | void set_column_names(std::string s, ColNames... cols) { 1065 | column_names[column_count - sizeof...(ColNames) - 1] = std::move(s); 1066 | set_column_names(std::forward(cols)...); 1067 | } 1068 | 1069 | void set_column_names() {} 1070 | 1071 | public: 1072 | CSVReader() = delete; 1073 | CSVReader(const CSVReader &) = delete; 1074 | CSVReader &operator=(const CSVReader &); 1075 | 1076 | template 1077 | explicit CSVReader(Args &&... args) : in(std::forward(args)...) { 1078 | std::fill(row, row + column_count, nullptr); 1079 | col_order.resize(column_count); 1080 | for (unsigned i = 0; i < column_count; ++i) 1081 | col_order[i] = i; 1082 | for (unsigned i = 1; i <= column_count; ++i) 1083 | column_names[i - 1] = "col" + std::to_string(i); 1084 | } 1085 | 1086 | char *next_line() { return in.next_line(); } 1087 | 1088 | template 1089 | void read_header(ignore_column ignore_policy, ColNames... cols) { 1090 | static_assert(sizeof...(ColNames) >= column_count, 1091 | "not enough column names specified"); 1092 | static_assert(sizeof...(ColNames) <= column_count, 1093 | "too many column names specified"); 1094 | try { 1095 | set_column_names(std::forward(cols)...); 1096 | 1097 | char *line; 1098 | do { 1099 | line = in.next_line(); 1100 | if (!line) 1101 | throw error::header_missing(); 1102 | } while (comment_policy::is_comment(line)); 1103 | 1104 | detail::parse_header_line( 1105 | line, col_order, column_names, ignore_policy); 1106 | } catch (error::with_file_name &err) { 1107 | err.set_file_name(in.get_truncated_file_name()); 1108 | throw; 1109 | } 1110 | } 1111 | 1112 | template void set_header(ColNames... cols) { 1113 | static_assert(sizeof...(ColNames) >= column_count, 1114 | "not enough column names specified"); 1115 | static_assert(sizeof...(ColNames) <= column_count, 1116 | "too many column names specified"); 1117 | set_column_names(std::forward(cols)...); 1118 | std::fill(row, row + column_count, nullptr); 1119 | col_order.resize(column_count); 1120 | for (unsigned i = 0; i < column_count; ++i) 1121 | col_order[i] = i; 1122 | } 1123 | 1124 | bool has_column(const std::string &name) const { 1125 | return col_order.end() != 1126 | std::find(col_order.begin(), col_order.end(), 1127 | std::find(std::begin(column_names), std::end(column_names), 1128 | name) - 1129 | std::begin(column_names)); 1130 | } 1131 | 1132 | void set_file_name(const std::string &file_name) { 1133 | in.set_file_name(file_name); 1134 | } 1135 | 1136 | void set_file_name(const char *file_name) { in.set_file_name(file_name); } 1137 | 1138 | const char *get_truncated_file_name() const { 1139 | return in.get_truncated_file_name(); 1140 | } 1141 | 1142 | void set_file_line(unsigned file_line) { in.set_file_line(file_line); } 1143 | 1144 | unsigned get_file_line() const { return in.get_file_line(); } 1145 | 1146 | private: 1147 | void parse_helper(std::size_t) {} 1148 | 1149 | template 1150 | void parse_helper(std::size_t r, T &t, ColType &... cols) { 1151 | if (row[r]) { 1152 | try { 1153 | try { 1154 | ::io::detail::parse(row[r], t); 1155 | } catch (error::with_column_content &err) { 1156 | err.set_column_content(row[r]); 1157 | throw; 1158 | } 1159 | } catch (error::with_column_name &err) { 1160 | err.set_column_name(column_names[r].c_str()); 1161 | throw; 1162 | } 1163 | } 1164 | parse_helper(r + 1, cols...); 1165 | } 1166 | 1167 | public: 1168 | template bool read_row(ColType &... cols) { 1169 | static_assert(sizeof...(ColType) >= column_count, 1170 | "not enough columns specified"); 1171 | static_assert(sizeof...(ColType) <= column_count, 1172 | "too many columns specified"); 1173 | try { 1174 | try { 1175 | 1176 | char *line; 1177 | do { 1178 | line = in.next_line(); 1179 | if (!line) 1180 | return false; 1181 | } while (comment_policy::is_comment(line)); 1182 | 1183 | detail::parse_line(line, row, col_order); 1184 | 1185 | parse_helper(0, cols...); 1186 | } catch (error::with_file_name &err) { 1187 | err.set_file_name(in.get_truncated_file_name()); 1188 | throw; 1189 | } 1190 | } catch (error::with_file_line &err) { 1191 | err.set_file_line(in.get_file_line()); 1192 | throw; 1193 | } 1194 | 1195 | return true; 1196 | } 1197 | }; 1198 | } // namespace io 1199 | #endif 1200 | --------------------------------------------------------------------------------