From 75de1a96fe9dde15a01872b4d08cef710aa30f80 Mon Sep 17 00:00:00 2001 From: L Date: Fri, 1 Jan 2021 20:15:23 -0800 Subject: [PATCH] Initial commit --- .gitignore | 7 + LICENSE | 340 ++++++++++++++++ Makefile | 35 ++ README.md | 33 ++ default.nix | 1 + derivation.nix | 18 + input-devices-standalone.ld | 8 + private.h | 54 +++ rM-input-devices.c | 764 ++++++++++++++++++++++++++++++++++++ rM-input-devices.h | 61 +++ rM-mk-uinput.c | 8 + 11 files changed, 1329 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 default.nix create mode 100644 derivation.nix create mode 100644 input-devices-standalone.ld create mode 100644 private.h create mode 100644 rM-input-devices.c create mode 100644 rM-input-devices.h create mode 100644 rM-mk-uinput.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c3c1dfc --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*~ +\#*\# +.\#* +build +result +result-* + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e212642 --- /dev/null +++ b/LICENSE @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8bf0b5e --- /dev/null +++ b/Makefile @@ -0,0 +1,35 @@ +.PHONY: all clean + +CFLAGS += -DREMARKABLE_VERSION=$(REMARKABLE_VERSION) + +all: build/librM-input-devices.so build/librM-input-devices-standalone.a build/rM-mk-uinput build/rM-mk-uinput-standalone +clean: + rm -rf build + +build: + mkdir -p build +build/%.o: %.c | build + $(CC) $(CFLAGS) -c $< -o $@ $(LDFLAGS) +build/%.a: | build + $(AR) cr $@ $^ +build/%.so: | build + $(CC) -shared -z defs -o $@ $^ $(LDFLAGS) + +build/uinput.bin: | build + $(OBJCOPY) -I binary -O elf32-littlearm -B arm $(UINPUT_KO) $@ + +build/rM-input-devices.o: rM-input-devices.h +build/rM-input-devices-standalone.o: build/rM-input-devices.o input-devices-standalone.ld build/uinput.bin + $(LD) -Tinput-devices-standalone.ld -i -o $@ build/rM-input-devices.o + +build/rM-mk-uinput.o: rM-input-devices.h + +build/librM-input-devices-standalone.a: build/rM-input-devices-standalone.o + +build/librM-input-devices.so: build/rM-input-devices.o +build/librM-input-devices.so: private override LDFLAGS += -ludev -lpthread + +build/rM-mk-uinput: build/rM-mk-uinput.o build/librM-input-devices.so | build + $(CC) $(CFLAGS) -o $@ $< -Lbuild -lrM-input-devices +build/rM-mk-uinput-standalone: build/rM-mk-uinput.o build/librM-input-devices-standalone.a + $(CC) $(CFLAGS) -o $@ $< -Lbuild -lrM-input-devices-standalone -ludev -lpthread diff --git a/README.md b/README.md new file mode 100644 index 0000000..be01ee5 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# Introduction + +This library provides a simple API for receiving and sending +digitizer/touch/keyboard events on the [reMarkable +tablets](https://remarkable.com). Currently, the main consumer is +[rM-vnc-server](https://github.com/pl-semiotics/rM-vnc-server). It +also supports using uinput to emulate the presence of such devices, +which may be useful in virtualized environments. + +# Building + +The supported way to build this is via the +[Nix](https://nixos.org/nix) package manager, through the +[nix-remarkable](https://github.com/pl-semiotics/nix-remarkable) +expressions. To build just this project via `nix build` from this +repo, download it into the `pkgs/` directory of `nix-remarkable`. + +For any other system, the [Makefile](./Makefile) should provide some +guidance; please set `REMARKABLE_VERSION` to one or two (depending on +which version you are building for), and, if interested in the +standalone (statically linked, with a bundled uinput kernel module) +version of the library, provide the path to appropriate kernel module +in the `UINPUT_KO` environment variable. + +Prebuilt binaries are available in the [Releases +tab](https://github.com/pl-semiotics/rM-input-devices/releases). + +# Usage + +For the library, see [rM-input-devices.h](./rM-input-devices.h) for +the interface. A simple executable `rM-mk-uinput` (which takes no +arguments and does nothing but create uinput devices for any missing +input devices) is also provided. diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..00347bf --- /dev/null +++ b/default.nix @@ -0,0 +1 @@ +(import ../.. {}).rmPkgs.rM-input-devices diff --git a/derivation.nix b/derivation.nix new file mode 100644 index 0000000..b982825 --- /dev/null +++ b/derivation.nix @@ -0,0 +1,18 @@ +{ stdenv, lib, linuxHeaders, linuxPackages, targetPlatform }: + +stdenv.mkDerivation { + pname = "rM-input-devices"; + version = "0.0.1"; + src = lib.cleanSource ./.; + buildInputs = [ linuxHeaders ]; + REMARKABLE_VERSION = targetPlatform.platform.rmVersion; + UINPUT_KO = "${linuxPackages.kernel}/lib/modules/${linuxPackages.kernel.modDirVersion}/kernel/drivers/input/misc/uinput.ko"; + outputs = [ "out" "dev" ]; + installPhase = '' + mkdir -p $out/bin + cp build/rM-mk-uinput{,-standalone} $out/bin + mkdir -p $dev/include $dev/lib + cp rM-input-devices.h $dev/include + cp build/librM-input-devices{.so,-standalone.a} $dev/lib + ''; +} diff --git a/input-devices-standalone.ld b/input-devices-standalone.ld new file mode 100644 index 0000000..b4c81d9 --- /dev/null +++ b/input-devices-standalone.ld @@ -0,0 +1,8 @@ +SECTIONS +{ + .uinput_ko : { + uinput_ko_begin = . ; + build/uinput.bin(.data) + uinput_ko_end = . ; + } +} diff --git a/private.h b/private.h new file mode 100644 index 0000000..b55ac27 --- /dev/null +++ b/private.h @@ -0,0 +1,54 @@ +#include "rM-input-devices.h" + +#include + + +struct fd_list { + int fd; + struct fd_list *next; +}; + +#define N_SLOTS 32 +struct wacom_data { + pthread_mutex_t mutex; + int pen_down; int touch_down; + int abs_x; int abs_y; int abs_pressure; + int drop_until_syn; + void *userdata; + uint coord_kind; +}; +struct touch_data { + pthread_mutex_t mutex; + int slots[N_SLOTS]; /* keep track of the tracking id for each slot */ + int abs_x[N_SLOTS]; + int abs_y[N_SLOTS]; + int current_slot; + /* the kernel currently uses (mt->trkid++ & TRKID_MAX) to get a new + * tracking id, so we just stay a few thousand ids ahead of the + * kernel */ +#define TRKID_MAX 0xffff +#define KERN_TRKID_OFFSET 4096 + uint next_trkid; + int drop_until_syn; + void *userdata; + uint coord_kind; +}; +struct key_data { + pthread_mutex_t mutex; + void *userdata; +}; + +struct rM_input_devices_priv { + struct fd_list* extra_wacom_fds; + struct fd_list* extra_touch_fds; + struct fd_list* extra_key_fds; + handle_wacom_event_t hwe; + handle_touch_event_t hte; + handle_key_event_t hke; + pthread_mutex_t input_thread_mutex; + int input_thread_running; + pthread_t input_thread; + struct wacom_data wd; + struct touch_data td; + struct key_data kd; +}; diff --git a/rM-input-devices.c b/rM-input-devices.c new file mode 100644 index 0000000..486b413 --- /dev/null +++ b/rM-input-devices.c @@ -0,0 +1,764 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "private.h" + +struct input_device { + uint *propbits; + uint *evbits; + uint *keybits; + struct uinput_abs_setup *abs; + struct uinput_setup setup; + char *udev_prop_filter; + struct fd_list *fds; +}; + +uint digitizer_evbits[] = {EV_KEY,EV_ABS,0}; +uint digitizer_keybits[] = { + BTN_TOOL_PEN, + BTN_TOOL_RUBBER, + BTN_TOUCH, + BTN_STYLUS, + BTN_STYLUS2, + 0 +}; +#if REMARKABLE_VERSION < 2 +#define DIGITIZER_MAX_X 20967 +#define DIGITIZER_MAX_Y 15725 +#define TOUCH_MAX_X 767 +#define TOUCH_MAX_Y 1023 +#else +#define DIGITIZER_MAX_X 20966 +#define DIGITIZER_MAX_Y 15725 +#define TOUCH_MAX_X 1403 +#define TOUCH_MAX_Y 1871 +#endif +struct uinput_abs_setup digitizer_abs[] = { + { + .code = ABS_X, + .absinfo = { + .value = 0, + .minimum = 0, + .maximum = DIGITIZER_MAX_X, + } + }, + { + .code = ABS_Y, + .absinfo = { + .value = 0, + .minimum = 0, + .maximum = DIGITIZER_MAX_Y, + } + }, + { + .code = ABS_PRESSURE, + .absinfo = { + .value = 0, + .minimum = 0, + .maximum = 4095, + } + }, + { + .code = ABS_DISTANCE, + .absinfo = { + .value = 0, + .minimum = 0, + .maximum = 255, + } + }, + { + .code = ABS_TILT_X, + .absinfo = { + .value = 0, + .minimum = -9000, + .maximum = 9000, + } + }, + { + .code = ABS_TILT_Y, + .absinfo = { + .value = 0, + .minimum = -9000, + .maximum = -9000, + } + }, + { 0 }, +}; +struct input_device digitizer = { + .propbits = 0, + .evbits = digitizer_evbits, + .keybits = digitizer_keybits, + .abs = digitizer_abs, + .setup = { + .id = { .bustype = 0x18, .vendor = 0x56a, .product = 0x0, .version = 0x36 }, + .name = "Wacom I2C Digitizer", + 0 + }, + .udev_prop_filter = "ID_INPUT_TABLET", +}; + +uint touch_propbits[] = {INPUT_PROP_DIRECT, 0}; +uint touch_evbits[] = {EV_KEY,EV_REL,EV_ABS,0}; +struct uinput_abs_setup touch_abs[] = { + { + .code = ABS_MT_SLOT, + .absinfo = { + .value = 0, + .minimum = 0, + .maximum = 31, + } + }, + { + .code = ABS_MT_POSITION_X, + .absinfo = { + .value = 0, + .minimum = 0, + .maximum = TOUCH_MAX_X, + } + }, + { + .code = ABS_MT_POSITION_Y, + .absinfo = { + .value = 0, + .minimum = 0, + .maximum = TOUCH_MAX_Y, + } + }, + { + .code = ABS_MT_TRACKING_ID, + .absinfo = { + .value = 0, + .minimum = 0, + .maximum = 65536, + } + }, + { 0 }, +}; +struct input_device touch = { + .propbits = touch_propbits, + .evbits = touch_evbits, + .keybits = 0, + .abs = touch_abs, + .setup = { + .id = { .bustype = 0x0, .vendor = 0x0, .product = 0x0, .version = 0x0 }, + .name = "cyttsp5_mt", + 0 + }, + .udev_prop_filter = "ID_INPUT_TOUCHSCREEN", +}; + +uint kbd_evbits[] = {EV_KEY,0}; +uint kbd_keybits[] = { + KEY_HOME, + KEY_LEFT, + KEY_RIGHT, + KEY_POWER, + KEY_WAKEUP, + 0 +}; +struct input_device kbd = { + .propbits = 0, + .evbits = kbd_evbits, + .keybits = kbd_keybits, + .abs = 0, + .setup = { + .id = { .bustype = 0x19, .vendor = 0x1, .product = 0x1, .version = 0x100 }, + .name = "gpio-keys", + 0 + }, + .udev_prop_filter = "ID_INPUT_KEY", +}; + +#define mk_not_empty(n, t) \ + static int not_empty_ ## n(t *n) { \ + t test = {0}; \ + return !!memcmp(n, &test, sizeof(t)); \ + } +mk_not_empty(abs, struct uinput_abs_setup); +mk_not_empty(dev, struct input_device); + +#define UINPUT_KO_ENV "RM_INPUT_DEVICES_UINPUT_KO" +char __attribute__((weak)) uinput_ko_begin = 0; +char __attribute__((weak)) uinput_ko_end = 0; +static void ensure_have_uinput() { + if (faccessat(AT_FDCWD, "/dev/uinput", F_OK, AT_EACCESS) == 0) { return; } + + char *start = &uinput_ko_begin; + size_t size = &uinput_ko_end-&uinput_ko_begin; + + char *external = getenv(UINPUT_KO_ENV); + int fd; + if (external) { + fd = open(external, O_RDONLY); + if (fd < 0) { return; } + struct stat stat; + if (fstat(fd, &stat)) { return; } + start = mmap(NULL, stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (start == MAP_FAILED) { return; } + size = stat.st_size; + } + + syscall(__NR_init_module, start, size, ""); + + if (external) { + munmap(start, size); + close(fd); + } +} + +static int create_device(struct input_device *d) { + ensure_have_uinput(); + int fd = open("/dev/uinput", O_WRONLY|O_NONBLOCK); + + for (uint *bit = d->propbits; bit && *bit; bit++) { + ioctl(fd, UI_SET_PROPBIT, *bit); + } + + for (uint *bit = d->evbits; bit && *bit; bit++) { + ioctl(fd, UI_SET_EVBIT, *bit); + } + + for (uint *bit = d->keybits; bit && *bit; bit++) { + ioctl(fd, UI_SET_KEYBIT, *bit); + } + + for (struct uinput_abs_setup *abs = d->abs; abs && not_empty_abs(abs); abs++) { + int r = ioctl(fd, UI_ABS_SETUP, abs); + } + + ioctl(fd, UI_DEV_SETUP, &d->setup); + ioctl(fd, UI_DEV_CREATE); + + return fd; +} + +#define EVDEVICE_PREFIX "/dev/input/event" + +static void find_devices(struct input_device devices[], int create_if_missing) { + struct udev *u = udev_new(); + if (!u) { return; } + struct udev_enumerate *e = udev_enumerate_new(u); + udev_enumerate_add_match_subsystem(e, "input"); + for (struct input_device *d = devices; not_empty_dev(d); ++d) { + d->fds = NULL; + udev_enumerate_add_match_property(e, d->udev_prop_filter, "1"); + } + udev_enumerate_scan_devices(e); + struct udev_list_entry *ds, *de; + ds = udev_enumerate_get_list_entry(e); + udev_list_entry_foreach(de, ds) { + const char *p = udev_list_entry_get_name(de); + struct udev_device *dev = udev_device_new_from_syspath(u, p); + const char *devpath = udev_device_get_devnode(dev); + if (!devpath || + strncmp(EVDEVICE_PREFIX, devpath, strlen(EVDEVICE_PREFIX)) || + strcmp(EVDEVICE_PREFIX, devpath) >= 0) { continue; } + int fd = open(devpath, O_RDWR); + if (fd < 0) { continue; } +#define SIZE(x) ((x ## _MAX+7)/8) + char props[SIZE(INPUT_PROP)] = {0}; + ioctl(fd, EVIOCGPROP(SIZE(INPUT_PROP)), &props); + char evbits[SIZE(EV)] = {0}; + ioctl(fd, EVIOCGBIT(0, SIZE(EV)), &evbits); + char keybits[SIZE(KEY)] = {0}; + ioctl(fd, EVIOCGBIT(EV_KEY, SIZE(KEY)), &keybits); + char absbits[SIZE(ABS)] = {0}; + ioctl(fd, EVIOCGBIT(EV_ABS, SIZE(ABS)), &absbits); +#define CHECK_BIT(base, n) (base[n/8] & (1<<(n%8))) + for (struct input_device *d = devices; not_empty_dev(d); ++d) { + for (uint *bit = d->propbits; bit && *bit; bit++) { + if (!CHECK_BIT(props, *bit)) { goto next; } + } + for (uint *bit = d->evbits; bit && *bit; bit++) { + if (!CHECK_BIT(evbits, *bit)) { goto next; } + } + for (uint *bit = d->keybits; bit && *bit; bit++) { + if (!CHECK_BIT(keybits, *bit)) { goto next; } + } + for (struct uinput_abs_setup *abs = d->abs; abs && not_empty_abs(abs); abs++) { + if (!CHECK_BIT(absbits, abs->code)) { goto next; } + } + struct fd_list *fdl = malloc(sizeof(struct fd_list)); + fdl->fd = fd; + fdl->next = d->fds; + d->fds = fdl; + next: ; + } + } + udev_enumerate_unref(e); + + if (create_if_missing) { + for (struct input_device *d = devices; not_empty_dev(d); ++d) { + if (!d->fds) { + d->fds = malloc(sizeof(struct fd_list)); + d->fds->fd = create_device(d); + d->fds->next = NULL; + } + } + } +} + +struct rM_input_devices find_rm_input_devices(int create_if_missing) { + struct input_device devices[] = { digitizer, touch, kbd, 0 }; + find_devices(devices, create_if_missing); + struct rM_input_devices_priv *priv = malloc(sizeof(struct rM_input_devices_priv)); + *priv = (struct rM_input_devices_priv){ + .extra_wacom_fds = devices[0].fds ? devices[0].fds->next : NULL, + .extra_touch_fds = devices[1].fds ? devices[1].fds->next : NULL, + .extra_key_fds = devices[2].fds ? devices[2].fds->next : NULL, + .hwe = NULL, + .hte = NULL, + .hke = NULL, + .input_thread_mutex = PTHREAD_MUTEX_INITIALIZER, + .input_thread_running = 0, + .wd = { + .mutex = PTHREAD_MUTEX_INITIALIZER + }, + .td = { + .mutex = PTHREAD_MUTEX_INITIALIZER, + }, + .kd = { + .mutex = PTHREAD_MUTEX_INITIALIZER, + }, + }; + struct rM_input_devices ret = { + .digitizer = devices[0].fds ? devices[0].fds->fd : -1, + .touch = devices[1].fds ? devices[1].fds->fd : -1, + .kbd = devices[2].fds ? devices[2].fds->fd : -1, + .priv = priv, + }; + return ret; +} + +enum device_type { + DEV_WACOM, + DEV_TOUCH, + DEV_KEY, +}; + +static void wacom_coord_evd_to_disp(int *x, int *y) { + int yy = 1874-((1874*(*x))/DIGITIZER_MAX_X); + int xx = 1404*(*y)/DIGITIZER_MAX_Y; + *x = xx; *y = yy; +} +static void wacom_coord_disp_to_evd(int *x, int *y) { + int xx = DIGITIZER_MAX_X*(1874-(*y))/1874; + int yy = DIGITIZER_MAX_Y*(*x)/1404; + *x = xx; *y = yy; +} +/* On rm1, both axes are inverted; on rm2, only the y-axis */ +#if REMARKABLE_VERSION < 2 +static void touch_coord_evd_to_disp(int *x, int *y) { + int xx = 1404*(TOUCH_MAX_X-*x)/TOUCH_MAX_X; + int yy = 1874*(TOUCH_MAX_Y-*y)/TOUCH_MAX_Y; + *x = xx; *y = yy; +} +static void touch_coord_disp_to_evd(int *x, int *y) { + int xx = (1404-*x)*TOUCH_MAX_X/1404; + int yy = (1872-*y)*TOUCH_MAX_Y/1872; + *x = xx; *y = yy; +} +#else +static void touch_coord_evd_to_disp(int *x, int *y) { + int xx = 1404*(*x)/TOUCH_MAX_X; + int yy = 1874*(TOUCH_MAX_Y-*y)/TOUCH_MAX_Y; + *x = xx; *y = yy; +} +static void touch_coord_disp_to_evd(int *x, int *y) { + int xx = (*x)*TOUCH_MAX_X/1404; + int yy = (1872-*y)*TOUCH_MAX_Y/1872; + *x = xx; *y = yy; +} +#endif + +struct edata { + enum device_type dt; + int fd; +}; +static struct edata *mk_edata(enum device_type dt, int fd) { + struct edata *ret = malloc(sizeof(struct edata)); + ret->dt = dt; + ret->fd = fd; + return ret; +} +static void handle_wacom_syn_dropped(struct rM_input_devices *ds, int fd) { + char keybits[SIZE(KEY)] = {0}; + ioctl(fd, EVIOCGKEY(SIZE(KEY)), &keybits); + ds->priv->wd.pen_down = CHECK_BIT(keybits, BTN_TOOL_PEN); + ds->priv->wd.touch_down = CHECK_BIT(keybits, BTN_TOUCH); + struct input_absinfo abs = {0}; + ioctl(fd, EVIOCGABS(ABS_X), &abs); + ds->priv->wd.abs_x = abs.value; + ioctl(fd, EVIOCGABS(ABS_Y), &abs); + ds->priv->wd.abs_y = abs.value; + ioctl(fd, EVIOCGABS(ABS_PRESSURE), &abs); + ds->priv->wd.abs_pressure = abs.value; + ds->priv->wd.drop_until_syn = 1; +} +struct input_mt_request_layout { + __u32 code; + __s32 values[N_SLOTS]; +}; +static void update_trkid(struct touch_data *td, int kern_trkid) { + /* For now, we don't do anything to try to map existing contacts, + * and just hope that KERN_TRKID_OFFSET is big enough. */ + if (kern_trkid >= 0 && + /* TODO: fix to actually track what we're using instead of just + * bumping if we see something <1/2 the offset */ + kern_trkid < (td->next_trkid - KERN_TRKID_OFFSET/2)&TRKID_MAX && + (kern_trkid + KERN_TRKID_OFFSET)&TRKID_MAX > td->next_trkid) { + td->next_trkid = (kern_trkid + KERN_TRKID_OFFSET) & TRKID_MAX; + } +} +static void handle_touch_syn_dropped(struct rM_input_devices *ds, int fd) { + struct input_mt_request_layout imrl_id, imrl_x, imrl_y; + imrl_id.code = ABS_MT_TRACKING_ID; + ioctl(fd, EVIOCGMTSLOTS(sizeof(imrl_id)), &imrl_id); + imrl_x.code = ABS_MT_POSITION_X; + ioctl(fd, EVIOCGMTSLOTS(sizeof(imrl_x)), &imrl_x); + imrl_y.code = ABS_MT_POSITION_Y; + ioctl(fd, EVIOCGMTSLOTS(sizeof(imrl_y)), &imrl_y); + for (int i = 0; i < N_SLOTS; ++i) { + ds->priv->td.slots[i] = imrl_id.values[i]; + update_trkid(&ds->priv->td, imrl_id.values[i]); + ds->priv->td.abs_x[i] = imrl_x.values[i]; + ds->priv->td.abs_y[i] = imrl_y.values[i]; + if (ds->priv->hte && imrl_id.values[i] >= 0) { + int x = imrl_x.values[i], y = imrl_y.values[i]; + if (ds->priv->td.coord_kind & RM_COORD_DISPLAY) { + touch_coord_evd_to_disp(&x, &y); + } + ds->priv->hte(ds->priv->td.userdata, imrl_id.values[i], x, y); + } + } + struct input_absinfo abs; + ioctl(fd, EVIOCGABS(ABS_MT_SLOT), &abs); + ds->priv->td.current_slot = abs.value; + ds->priv->td.drop_until_syn = 1; +} +static void handle_wacom_event(struct rM_input_devices *ds, int fd) { + struct input_event ev; + struct wacom_data *wd = &ds->priv->wd; + pthread_mutex_lock(&wd->mutex); + while (read(fd, &ev, sizeof(struct input_event)) == sizeof(struct input_event)) { + if (ev.type == EV_SYN) { + if (ev.code == SYN_DROPPED) { handle_wacom_syn_dropped(ds, fd); } + if (ev.code == SYN_REPORT) { + if (wd->drop_until_syn) { wd->drop_until_syn = 0; continue; } + if (ds->priv->hwe) { + int x = wd->abs_x; int y = wd->abs_y; + if (wd->coord_kind & RM_COORD_DISPLAY) { + wacom_coord_evd_to_disp(&x, &y); + } + ds->priv->hwe(wd->userdata, wd->pen_down, wd->touch_down, + x, y, wd->abs_pressure); + } + } + } + if (wd->drop_until_syn) { continue; } + if (ev.type == EV_KEY) { + if (ev.code == BTN_TOOL_PEN) { wd->pen_down = ev.value; } + if (ev.code == BTN_TOUCH) { wd->touch_down = ev.value; } + } + if (ev.type == EV_ABS) { + if (ev.code == ABS_X) { wd->abs_x = ev.value; } + if (ev.code == ABS_Y) { wd->abs_y = ev.value; } + if (ev.code == ABS_PRESSURE) { wd->abs_pressure = ev.value; } + } + } + pthread_mutex_unlock(&wd->mutex); +} +static void handle_touch_event(struct rM_input_devices *ds, int fd) { + struct input_event ev; + struct touch_data *td = &ds->priv->td; + pthread_mutex_lock(&td->mutex); + while (read(fd, &ev, sizeof(struct input_event)) == sizeof(struct input_event)) { + if (ev.type == EV_SYN) { + if (ev.code == SYN_DROPPED) { handle_touch_syn_dropped(ds, fd); } + if (ev.code == SYN_REPORT) { + if (td->drop_until_syn) { td->drop_until_syn = 0; continue; } + if (ds->priv->hte) { + for (int i = 0; i < N_SLOTS; ++i) { + if (td->slots[i] >= 0) { + int x = td->abs_x[i]; int y = td->abs_y[i]; + if (td->coord_kind & RM_COORD_DISPLAY) { + touch_coord_evd_to_disp(&x, &y); + } + ds->priv->hte(td->userdata, td->slots[i], x, y); + } + } + } + } + } + if (td->drop_until_syn) { continue; } + if (ev.type == EV_ABS) { + if (ev.code == ABS_MT_SLOT) { + td->current_slot = ev.value; + } + if (ev.code == ABS_MT_TRACKING_ID) { + td->slots[td->current_slot] = ev.value; + update_trkid(td, ev.value); + } + if (ev.code == ABS_MT_POSITION_X) { + td->abs_x[td->current_slot] = ev.value; + } + if (ev.code == ABS_MT_POSITION_Y) { + td->abs_y[td->current_slot] = ev.value; + } + } + } + pthread_mutex_unlock(&td->mutex); +} +static void handle_key_event(struct rM_input_devices *ds, int fd) { + struct input_event ev; + pthread_mutex_lock(&ds->priv->kd.mutex); + while (read(fd, &ev, sizeof(struct input_event)) == sizeof(struct input_event)) { + /* TODO: we should wait for SYN_REPORT (and handle SYN_DROPPED) */ + if (ev.type == EV_KEY) { + if (ds->priv->hke) { + ds->priv->hke(ds->priv->kd.userdata, ev.code, ev.value); + } + } + } + pthread_mutex_unlock(&ds->priv->kd.mutex); +} +static int add_epoll_event(int epfd, int fd, enum device_type t) { + struct epoll_event ev; + ev.events = EPOLLIN; + int flags; + if ((flags = fcntl(fd, F_GETFL, 0)) < 0) { flags = 0; } + if (fcntl(fd, F_SETFL, flags|O_NONBLOCK) < 0) { return -1; } + ev.data.ptr = mk_edata(t, fd); + if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) == -1) { return -1; } + return 0; +} +static void *run_input_thread(void *ds_) { + struct rM_input_devices *ds = (struct rM_input_devices *)ds_; + pthread_mutex_lock(&ds->priv->input_thread_mutex); + if (ds->priv->input_thread_running) { goto err; } + + int epfd = epoll_create(3); + + if (add_epoll_event(epfd, ds->digitizer, DEV_WACOM) < 0) { goto err; } + for (struct fd_list *f = ds->priv->extra_wacom_fds; f; f = f->next) { + if (add_epoll_event(epfd, f->fd, DEV_WACOM) < 0) { goto err; } + } + if (add_epoll_event(epfd, ds->touch, DEV_TOUCH) < 0) { goto err; } + for (struct fd_list *f = ds->priv->extra_touch_fds; f; f = f->next) { + if (add_epoll_event(epfd, f->fd, DEV_TOUCH) < 0) { goto err; } + } + if (add_epoll_event(epfd, ds->kbd, DEV_KEY) < 0) { goto err; } + for (struct fd_list *f = ds->priv->extra_key_fds; f; f = f->next) { + if (add_epoll_event(epfd, f->fd, DEV_KEY) < 0) { goto err; } + } + + ds->priv->input_thread_running = 1; + pthread_mutex_unlock(&ds->priv->input_thread_mutex); + + handle_wacom_syn_dropped(ds, ds->digitizer); + ds->priv->wd.drop_until_syn = 0; + handle_touch_syn_dropped(ds, ds->touch); + ds->priv->td.drop_until_syn = 0; + + while (1) { +#define MAX_EVENTS 5 + struct epoll_event events[MAX_EVENTS]; + int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1); + if (nfds == -1) { + pthread_mutex_lock(&ds->priv->input_thread_mutex); + ds->priv->input_thread_running = 0; + pthread_mutex_unlock(&ds->priv->input_thread_mutex); + } + for (int n = 0; n < nfds; ++n) { + struct edata *ed = (struct edata *)events[n].data.ptr; + switch (ed->dt) { + case DEV_WACOM: + handle_wacom_event(ds, ed->fd); + break; + case DEV_TOUCH: + handle_touch_event(ds, ed->fd); + break; + case DEV_KEY: + handle_key_event(ds, ed->fd); + break; + } + } + } + + return NULL; + +err: + pthread_mutex_unlock(&ds->priv->input_thread_mutex); + return NULL; +} + +int enable_input_event_listening(struct rM_input_devices *ds) { + pthread_mutex_lock(&ds->priv->input_thread_mutex); + if (ds->priv->input_thread_running) { return 0; } + pthread_mutex_unlock(&ds->priv->input_thread_mutex); + for (int i = 0; i < 32; ++i) { + ds->priv->td.slots[i] = -1; + } + ds->priv->td.current_slot = -1; + ds->priv->td.next_trkid = 1; /* will be updated next time we see an event from the kernel */ + int ret = pthread_create(&ds->priv->input_thread, NULL, run_input_thread, ds); + if (!ret) { return ret; } +} + +int submit_wacom_event(struct rM_input_devices *ds, + int pen_down, int touch_down, + struct rM_coord coord, int abs_pressure, + uint which) { + int x = coord.x; int y = coord.y; + if (coord.coord_kind & RM_COORD_DISPLAY) { + wacom_coord_disp_to_evd(&x, &y); + } + struct input_event ies[6] = {0}; + int next = 0; + if (which & WHICH_WACOM_PEN) { + ies[next].type = EV_KEY; ies[next].code = BTN_TOOL_PEN; + ies[next].value = pen_down; + next++; + } + if (which & WHICH_WACOM_TOUCH) { + ies[next].type = EV_KEY; ies[next].code = BTN_TOUCH; + ies[next].value = touch_down; + next++; + } + if (which & WHICH_WACOM_X) { + ies[next].type = EV_ABS; ies[next].code = ABS_X; + ies[next].value = x; + next++; + } + if (which & WHICH_WACOM_Y) { + ies[next].type = EV_ABS; ies[next].code = ABS_Y; + ies[next].value = y; + next++; + } + if (which & WHICH_WACOM_PRESSURE) { + ies[next].type = EV_ABS; ies[next].code = ABS_PRESSURE; + ies[next].value = abs_pressure; + next++; + } + ies[next].type = EV_SYN; + ies[next].code = SYN_REPORT; + ies[next].value = 0; + next++; + return write(ds->digitizer, ies, sizeof(struct input_event)*next); +} +int on_wacom_event(struct rM_input_devices *ds, uint coord_kind, + handle_wacom_event_t handle, void *data) { + pthread_mutex_lock(&ds->priv->wd.mutex); + ds->priv->hwe = handle; + ds->priv->wd.userdata = data; + ds->priv->wd.coord_kind = coord_kind; + pthread_mutex_unlock(&ds->priv->wd.mutex); +} + +int touch_begin_contact(struct rM_input_devices *ds) { + struct touch_data *td = &ds->priv->td; + pthread_mutex_lock(&td->mutex); + int id = td->next_trkid; + td->next_trkid = (id+1)&TRKID_MAX; + + int slot = -1; + for (int i = N_SLOTS; i >= 0; --i) { + if (td->slots[i] < 0) { slot = i; break; } + } + if (slot < 0) { pthread_mutex_unlock(&td->mutex); return -1; /* out of slots */ } + td->slots[slot] = id; + + pthread_mutex_unlock(&td->mutex); + return id; +} +int submit_touch_contact(struct rM_input_devices *ds, int c, + struct rM_coord coord, int which) { + if (c < 0) { return -1; } + struct touch_data *td = &ds->priv->td; + pthread_mutex_lock(&td->mutex); + int x = coord.x; int y = coord.y; + if (coord.coord_kind & RM_COORD_DISPLAY) { + touch_coord_disp_to_evd(&x, &y); + } + int slot = -1; + for (int i = N_SLOTS; i >= 0; --i) { + if (td->slots[i] == c) { slot = i; break; } + } + if (slot < 0) { pthread_mutex_unlock(&td->mutex); return -1; } + /* Set slot, set tracking id, set x/y, syn report, set tracking id, set slot */ + struct input_event ies[6] = {0}; + int next = 0; + ies[next].type = EV_ABS; ies[next].code = ABS_MT_SLOT; ies[next].value = slot; + next++; + ies[next].type = EV_ABS; ies[next].code = ABS_MT_TRACKING_ID; + ies[next].value = c; next++; + if (which & WHICH_TOUCH_X) { + ies[next].type = EV_ABS; ies[next].code = ABS_MT_POSITION_X; + ies[next].value = x; next++; + } + if (which & WHICH_TOUCH_Y) { + ies[next].type = EV_ABS; ies[next].code = ABS_MT_POSITION_Y; + ies[next].value = y; next++; + } + ies[next].type = EV_ABS; ies[next].code = ABS_MT_SLOT; + ies[next].value = td->current_slot; next++; + ies[next].type = EV_SYN; ies[next].code = SYN_REPORT; ies[next].value = 0; + next++; + pthread_mutex_unlock(&td->mutex); + return write(ds->touch, ies, sizeof(struct input_event)*next); +} +int touch_end_contact(struct rM_input_devices *ds, int c) { +struct touch_data *td = &ds->priv->td; + pthread_mutex_lock(&td->mutex); + + int slot = -1; + for (int i = N_SLOTS; i >= 0; --i) { + if (td->slots[i] == c) { slot = i; break; } + } + if (slot < 0) { pthread_mutex_unlock(&td->mutex); return -1; } + + struct input_event ies[4] = { + { .type = EV_ABS, .code = ABS_MT_SLOT, .value = slot }, + { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = -1 }, + { .type = EV_ABS, .code = ABS_MT_SLOT, .value = td->current_slot }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + }; + td->slots[slot] = -1; + + pthread_mutex_unlock(&td->mutex); + return write(ds->touch, ies, sizeof(ies)); + +} +int on_touch_event(struct rM_input_devices *ds, uint coord_kind, + handle_touch_event_t handle, void *data) { + pthread_mutex_lock(&ds->priv->td.mutex); + ds->priv->hte = handle; + ds->priv->td.userdata = data; + ds->priv->td.coord_kind = coord_kind; + pthread_mutex_unlock(&ds->priv->td.mutex); +} + +int submit_key_event(struct rM_input_devices *ds, int key, int down) { + struct input_event ies[2] = { + { .type = EV_KEY, .code = key, .value = down }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + }; + return write(ds->kbd, ies, sizeof(ies)); +} +int on_key_event(struct rM_input_devices *ds, + handle_key_event_t handle, void *data) { + pthread_mutex_lock(&ds->priv->kd.mutex); + ds->priv->hke = handle; + ds->priv->kd.userdata = data; + pthread_mutex_unlock(&ds->priv->kd.mutex); +} diff --git a/rM-input-devices.h b/rM-input-devices.h new file mode 100644 index 0000000..f440e40 --- /dev/null +++ b/rM-input-devices.h @@ -0,0 +1,61 @@ +#ifndef RM_INPUT_DEVICES_H_ +#define RM_INPUT_DEVICES_H_ + +#include + +/* file descriptors */ +struct rM_input_devices { + int digitizer; + int touch; + int kbd; + struct rM_input_devices_priv *priv; +}; + +struct rM_input_devices find_rm_input_devices(int create_if_missing); + +#define RM_X 0x1 +#define RM_Y 0x2 +#define RM_PRESSURE 0x4 + +#define RM_COORD_EVDEVICE 0x1 +#define RM_COORD_DISPLAY 0x2 +struct rM_coord { + uint coord_kind; + uint x; + uint y; +}; + +/* needed for an on_*_event, and for submit_touch_* */ +int enable_input_event_listening(struct rM_input_devices *ds); + +/* the various handle_* fns should be idempotent */ + +#define WHICH_WACOM_PEN 0x1 +#define WHICH_WACOM_TOUCH 0x2 +#define WHICH_WACOM_X 0x4 +#define WHICH_WACOM_Y 0x8 +#define WHICH_WACOM_PRESSURE 0x10 +int submit_wacom_event(struct rM_input_devices *ds, + int pen_down, int touch_down, + struct rM_coord coord, int abs_pressure, + uint which); +typedef void (*handle_wacom_event_t)(void *, int pen_down, int touch_down, + int abs_x, int abs_y, int abs_pressure); +int on_wacom_event(struct rM_input_devices *ds, uint coord_kind, + handle_wacom_event_t handle, void *); + +#define WHICH_TOUCH_X 1 +#define WHICH_TOUCH_Y 2 +int touch_begin_contact(struct rM_input_devices *ds); +int submit_touch_contact(struct rM_input_devices *ds, int c, + struct rM_coord coord, int which); +int touch_end_contact(struct rM_input_devices *ds, int c); +typedef void (*handle_touch_event_t)(void *, int c, int abs_x, int abs_y); +int on_touch_event(struct rM_input_devices *ds, uint coord_kind, + handle_touch_event_t handle, void *); + +int submit_key_event(struct rM_input_devices *ds, int key, int down); +typedef void (*handle_key_event_t)(void *, int key, int down); +int on_key_event(struct rM_input_devices *ds, handle_key_event_t handle, void *); + +#endif /* RM_INPUT_DEVICES_H_ */ diff --git a/rM-mk-uinput.c b/rM-mk-uinput.c new file mode 100644 index 0000000..5bfc84f --- /dev/null +++ b/rM-mk-uinput.c @@ -0,0 +1,8 @@ +#include + +#include "rM-input-devices.h" + +int main(int argc, char *argv) { + find_rm_input_devices(1); + while (1) { pause(); } +}