├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── images └── before_after.png ├── install.sh ├── precompiled ├── librecept_rs10.so ├── librecept_rs11.so ├── librecept_rs12.so ├── librecept_rs13.so ├── librecept_rs14.so ├── librecept_rs15.so ├── librecept_rs16.so ├── librecept_rs17.so ├── librecept_rs18.so ├── librecept_rs19.so ├── librecept_rs2.so ├── librecept_rs20.so ├── librecept_rs21.so ├── librecept_rs22.so ├── librecept_rs23.so ├── librecept_rs24.so ├── librecept_rs25.so ├── librecept_rs26.so ├── librecept_rs27.so ├── librecept_rs28.so ├── librecept_rs29.so ├── librecept_rs3.so ├── librecept_rs30.so ├── librecept_rs31.so ├── librecept_rs32.so ├── librecept_rs4.so ├── librecept_rs5.so ├── librecept_rs6.so ├── librecept_rs7.so ├── librecept_rs8.so └── librecept_rs9.so └── recept.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw[pmn] 2 | *.so 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jan Funke 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: compile 2 | 3 | compile: 4 | ! mkdir precompiled 5 | . /usr/local/oecore-x86_64/environment-setup-cortexa9hf-neon-oe-linux-gnueabi; \ 6 | for rs in `seq 2 32`; \ 7 | do \ 8 | arm-oe-linux-gnueabi-g++ \ 9 | -DRING_SIZE=$$rs \ 10 | -march=armv7-a \ 11 | -mfpu=neon \ 12 | -mfloat-abi=hard \ 13 | -mcpu=cortex-a9 \ 14 | --sysroot=/usr/local/oecore-x86_64/sysroots/cortexa9hf-neon-oe-linux-gnueabi \ 15 | -Wall \ 16 | -shared \ 17 | -ldl \ 18 | -fPIC \ 19 | -O3 \ 20 | recept.cpp \ 21 | -o precompiled/librecept_rs$$rs.so; \ 22 | done 23 | 24 | test: 25 | scp precompiled/librecept_rs16.so root@remarkable:librecept.so 26 | ssh remarkable "LD_PRELOAD=/home/root/librecept.so ls" 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ReCept: Remarkable Intercept 2 | ============================ 3 | 4 | A small library to intercept and smooth pen events on the [reMarkable 5 | 2](https://remarkable.com/) tablet. This fixes the infamous "jagged lines" 6 | issue some users of the reMarkable 2 experience. 7 | 8 | ![before vs after](images/before_after.png) 9 | 10 | Disclaimer 11 | ---------- 12 | 13 | This is not an official reMarkable product, and I am in no way affiliated with 14 | reMarkable. I release this library in the hope that it is helpful to others. I 15 | can make no guarantee that it works as intended. There might be bugs leading to 16 | device crashes. 17 | 18 | Installation 19 | ------------ 20 | 21 | You need access to a Linux machine to install the fix. On that machine: 22 | 23 | 1. Connect your reMarkable 2 with the USB-C cable. 24 | 2. Clone this repository, i.e., run `git clone https://github.com/funkey/recept` in a terminal. 25 | 3. Change into the repository with `cd recept`. 26 | 4. Run `./install.sh`. 27 | 28 | The install script will make several `ssh` connections to your device. For 29 | that, it needs to know the IP address or hostname of your device. If you 30 | haven't set up public key authentication before, it will also ask for a 31 | password. You can find both the IP address and password in "Settings" -> "Help" 32 | -> "Copyrights and licenses", at the bottom. 33 | 34 | If you want to uninstall the fix, simply enter `0` when asked for the smoothing 35 | value in the install script. 36 | 37 | Will it increase the latency? 38 | ----------------------------- 39 | 40 | In short, yes. The pen events themselves are not delayed (the filter itself 41 | consists only of a handful of integer operations, which are likely negligible). 42 | However, since we forward the average position of the past `N` events instead 43 | of the actual event, the reported position is somewhere close to the `N/2`-last 44 | event received. How much that actually increases latency depends on how fast 45 | events come in. As an example, if the pen sends 1000 events per second and the 46 | filter size `N` is 8, the mean will trail the actual pen position by around 4 47 | milliseconds. This calculation assumes isochronous events, which might not be 48 | the case. 49 | 50 | How does it work? 51 | ----------------- 52 | 53 | `ReCept` uses the `LD_PRELOAD` trick, which in this case intercepts calls to 54 | `open` and `read`. Whenever `/dev/input/event1` is opened by `xochitl` (the GUI 55 | running on the tablet), `librecept` remembers the file handle. Subsequent 56 | `read`s from this handle are transparently filtered with a moving average of 57 | size 16 by default. 58 | -------------------------------------------------------------------------------- /images/before_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/images/before_after.png -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "This script will install a library that will intercept pen events and smooth them." 4 | echo 5 | echo "If you haven't set up public key authentication on your remarkable yet, now would" 6 | echo "be a good time to do so (otherwise you'll have to type your password multiple" 7 | echo "times)." 8 | echo 9 | echo "Either way, make sure you have your remarkable password written down somewhere, you" 10 | echo "might otherwise risk to lock yourself out if the GUI does not start up anymore." 11 | 12 | echo 13 | read -p "Enter the hostname or IP address of your remarkable device [remarkable]:" remarkable 14 | remarkable=${remarkable:-remarkable} 15 | 16 | echo 17 | echo "Chose the amount of smoothing you want to apply to pen events. Larger values will" 18 | echo "smooth more, but will also lead to larger perceived latencies." 19 | echo 20 | echo "Entering 0 will uninstall the library." 21 | echo 22 | read -p "Amount of smoothing (value between 2 and 32, or 0) [8]:" ring_size 23 | ring_size=${ring_size:-8} 24 | 25 | if [ "${ring_size}" -eq "0" ] 26 | then \ 27 | echo "Uninstalling ReCept..." 28 | ssh root@$remarkable "grep -qxF 'Environment=LD_PRELOAD=/usr/lib/librecept.so' /lib/systemd/system/xochitl.service && sed -i '/Environment=LD_PRELOAD=\/usr\/lib\/librecept.so/d' /lib/systemd/system/xochitl.service" 29 | else \ 30 | echo "Installing ReCept with ring size ${ring_size}..." 31 | scp ./precompiled/librecept_rs${ring_size}.so root@$remarkable:/usr/lib/librecept.so 32 | ssh root@$remarkable "grep -qxF 'Environment=LD_PRELOAD=/usr/lib/librecept.so' /lib/systemd/system/xochitl.service || sed -i 's#\[Service\]#[Service]\nEnvironment=LD_PRELOAD=/usr/lib/librecept.so#' /lib/systemd/system/xochitl.service" 33 | fi 34 | 35 | echo "...done." 36 | echo "Restarting xochitl..." 37 | ssh root@$remarkable "systemctl daemon-reload; systemctl restart xochitl" 38 | echo "...done." 39 | -------------------------------------------------------------------------------- /precompiled/librecept_rs10.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/precompiled/librecept_rs10.so -------------------------------------------------------------------------------- /precompiled/librecept_rs11.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/precompiled/librecept_rs11.so -------------------------------------------------------------------------------- /precompiled/librecept_rs12.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/precompiled/librecept_rs12.so -------------------------------------------------------------------------------- /precompiled/librecept_rs13.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/precompiled/librecept_rs13.so -------------------------------------------------------------------------------- /precompiled/librecept_rs14.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/precompiled/librecept_rs14.so -------------------------------------------------------------------------------- /precompiled/librecept_rs15.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/precompiled/librecept_rs15.so -------------------------------------------------------------------------------- /precompiled/librecept_rs16.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/precompiled/librecept_rs16.so -------------------------------------------------------------------------------- /precompiled/librecept_rs17.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/precompiled/librecept_rs17.so -------------------------------------------------------------------------------- /precompiled/librecept_rs18.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/precompiled/librecept_rs18.so -------------------------------------------------------------------------------- /precompiled/librecept_rs19.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/precompiled/librecept_rs19.so -------------------------------------------------------------------------------- /precompiled/librecept_rs2.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/precompiled/librecept_rs2.so -------------------------------------------------------------------------------- /precompiled/librecept_rs20.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/precompiled/librecept_rs20.so -------------------------------------------------------------------------------- /precompiled/librecept_rs21.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/precompiled/librecept_rs21.so -------------------------------------------------------------------------------- /precompiled/librecept_rs22.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/precompiled/librecept_rs22.so -------------------------------------------------------------------------------- /precompiled/librecept_rs23.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/precompiled/librecept_rs23.so -------------------------------------------------------------------------------- /precompiled/librecept_rs24.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/precompiled/librecept_rs24.so -------------------------------------------------------------------------------- /precompiled/librecept_rs25.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/precompiled/librecept_rs25.so -------------------------------------------------------------------------------- /precompiled/librecept_rs26.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/precompiled/librecept_rs26.so -------------------------------------------------------------------------------- /precompiled/librecept_rs27.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/precompiled/librecept_rs27.so -------------------------------------------------------------------------------- /precompiled/librecept_rs28.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/precompiled/librecept_rs28.so -------------------------------------------------------------------------------- /precompiled/librecept_rs29.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/precompiled/librecept_rs29.so -------------------------------------------------------------------------------- /precompiled/librecept_rs3.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/precompiled/librecept_rs3.so -------------------------------------------------------------------------------- /precompiled/librecept_rs30.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/precompiled/librecept_rs30.so -------------------------------------------------------------------------------- /precompiled/librecept_rs31.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/precompiled/librecept_rs31.so -------------------------------------------------------------------------------- /precompiled/librecept_rs32.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/precompiled/librecept_rs32.so -------------------------------------------------------------------------------- /precompiled/librecept_rs4.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/precompiled/librecept_rs4.so -------------------------------------------------------------------------------- /precompiled/librecept_rs5.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/precompiled/librecept_rs5.so -------------------------------------------------------------------------------- /precompiled/librecept_rs6.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/precompiled/librecept_rs6.so -------------------------------------------------------------------------------- /precompiled/librecept_rs7.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/precompiled/librecept_rs7.so -------------------------------------------------------------------------------- /precompiled/librecept_rs8.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/precompiled/librecept_rs8.so -------------------------------------------------------------------------------- /precompiled/librecept_rs9.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkey/recept/cffcc8e94078824b0998dfbdfadcc4698ed215fa/precompiled/librecept_rs9.so -------------------------------------------------------------------------------- /recept.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #ifndef RING_SIZE 6 | #define RING_SIZE 16 7 | #endif 8 | 9 | template 10 | class ring { 11 | 12 | public: 13 | 14 | inline void add(const T& value) { 15 | 16 | if (empty()) { 17 | 18 | fill(value); 19 | return; 20 | } 21 | 22 | _index = (_index + 1) % Size; 23 | const T& oldest = _values[_index]; 24 | _sum = _sum + value - oldest; 25 | _values[_index] = value; 26 | } 27 | 28 | inline void clear() { 29 | 30 | _sum = std::numeric_limits::max(); 31 | } 32 | 33 | inline bool empty() const { 34 | 35 | return _sum == std::numeric_limits::max(); 36 | } 37 | 38 | /** 39 | * Return the last (i.e., newest) item added to the ring. 40 | */ 41 | inline const T& front() const { 42 | 43 | return _values[_index]; 44 | } 45 | 46 | inline void fill(const T& value) { 47 | 48 | _values.fill(value); 49 | _sum = value*Size; 50 | } 51 | 52 | inline T sum() const { 53 | 54 | return _sum; 55 | } 56 | 57 | inline T average() const { 58 | 59 | return _sum/Size; 60 | } 61 | 62 | private: 63 | 64 | std::array _values; 65 | uint8_t _index; 66 | T _sum; 67 | }; 68 | 69 | static ring ring_x; 70 | static ring ring_y; 71 | 72 | template 73 | inline uint32_t update_pos(Ring& ring, uint32_t value) { 74 | 75 | ring.add(value); 76 | return ring.average(); 77 | } 78 | 79 | void filter(char* buf) { 80 | 81 | const uint8_t type = (uint8_t)buf[8]; 82 | const uint16_t code = ( 83 | (uint16_t)buf[10] | 84 | (uint16_t)buf[11] << 8); 85 | uint32_t value = ( 86 | (uint32_t)buf[12] | 87 | (uint32_t)buf[13] << 8 | 88 | (uint32_t)buf[14] << 16 | 89 | (uint32_t)buf[15] << 24); 90 | 91 | // type == 1 && code == 320 && value == 1 -> pen in 92 | // type == 1 && code == 320 && value == 0 -> pen out 93 | // type == 1 && code == 321 && value == 1 -> eraser in 94 | // type == 1 && code == 321 && value == 0 -> eraser out 95 | // 96 | // type == 1 && code == 330 && value == 1 -> pen/eraser down 97 | // type == 1 && code == 330 && value == 0 -> pen/eraser up 98 | // 99 | // type == 3 && code == 0 -> value == x 100 | // type == 3 && code == 1 -> value == y 101 | // type == 3 && code == 24 -> value == pressure 102 | // type == 3 && code == 25 -> value == distance 103 | // type == 3 && code == 26 -> value == tilt x 104 | // type == 3 && code == 27 -> value == tilt y 105 | 106 | // pen/eraser in 107 | if (type == 1 && ((code == 320 && value == 1) || (code == 321 && value == 1))) { 108 | 109 | ring_x.clear(); 110 | ring_y.clear(); 111 | } 112 | 113 | if (type == 3) { 114 | 115 | if (code == 0) { 116 | 117 | value = update_pos(ring_x, value); 118 | 119 | } else if (code == 1) { 120 | 121 | value = update_pos(ring_y, value); 122 | } 123 | 124 | // copy value back to buffer 125 | buf[12] = (uint8_t)value; 126 | buf[13] = (uint8_t)(value >> 8); 127 | buf[14] = (uint8_t)(value >> 16); 128 | buf[15] = (uint8_t)(value >> 24); 129 | } 130 | } 131 | 132 | extern "C" { 133 | 134 | #ifndef _GNU_SOURCE 135 | #define _GNU_SOURCE 136 | #endif 137 | 138 | #include 139 | #include 140 | #include 141 | 142 | typedef int (*t_open)(const char*, int); 143 | typedef ssize_t (*t_read)(int, void*, size_t); 144 | 145 | static int event_fd = 0; 146 | 147 | int open(const char* filename, int flags) { 148 | 149 | static t_open real_open = NULL; 150 | if (!real_open) 151 | real_open = (t_open)dlsym(RTLD_NEXT, "open"); 152 | 153 | int fd = real_open(filename, flags); 154 | 155 | if (strcmp(filename, "/dev/input/event1") == 0) { 156 | 157 | printf(">| someone is trying to open event1, let's remember the fd!\n"); 158 | printf(">| (psssst: it is %d)\n", fd); 159 | event_fd = fd; 160 | } 161 | 162 | return fd; 163 | } 164 | 165 | ssize_t read(int fd, void* buf, size_t count) { 166 | 167 | static t_read real_read = NULL; 168 | if (!real_read) 169 | real_read = (t_read)dlsym(RTLD_NEXT, "read"); 170 | 171 | ssize_t ret = real_read(fd, buf, count); 172 | 173 | if (fd != 0 && fd == event_fd && ret == 16) 174 | filter((char*)buf); 175 | 176 | return ret; 177 | } 178 | 179 | } 180 | --------------------------------------------------------------------------------