From 06e4141268ad366686fe8d8d7aaa8fb953cc9e69 Mon Sep 17 00:00:00 2001 From: evan Date: Sat, 13 Jul 2019 00:57:16 -0500 Subject: [PATCH] combine libev/pynput solutions, add --evdev arg --- remarkable_mouse/remarkable_mouse.py | 132 ++++++++++++++++++++++++--- 1 file changed, 120 insertions(+), 12 deletions(-) diff --git a/remarkable_mouse/remarkable_mouse.py b/remarkable_mouse/remarkable_mouse.py index c91267a..9be0f22 100755 --- a/remarkable_mouse/remarkable_mouse.py +++ b/remarkable_mouse/remarkable_mouse.py @@ -9,7 +9,6 @@ import sys import struct from getpass import getpass -import libevdev import paramiko logging.basicConfig(format='%(message)s') @@ -21,6 +20,48 @@ MAX_ABS_X = 20967 # Maximum value that can be reported by the Wacom driver for the Y axis MAX_ABS_Y = 15725 + +# evtype_sync = 0 +# evtype_key = 1 +e_type_abs = 3 + +# evcode_stylus_distance = 25 +# evcode_stylus_xtilt = 26 +# evcode_stylus_ytilt = 27 +e_code_stylus_xpos = 1 +e_code_stylus_ypos = 0 +e_code_stylus_pressure = 24 +# evcode_finger_xpos = 53 +# evcode_finger_ypos = 54 +# evcode_finger_pressure = 58 + +stylus_width = 15725 +stylus_height = 20951 +# finger_width = 767 +# finger_height = 1023 + + +# remap wacom coordinates in various orientations +def fit(x, y, stylus_width, stylus_height, monitor, orientation): + + if orientation == 'vertical': + y = stylus_height - y + elif orientation == 'right': + x, y = y, x + stylus_width, stylus_height = stylus_height, stylus_width + elif orientation == 'left': + x, y = stylus_height - y, stylus_width - x + stylus_width, stylus_height = stylus_height, stylus_width + + ratio_width, ratio_height = monitor.width / stylus_width, monitor.height / stylus_height + scaling = ratio_width if ratio_width > ratio_height else ratio_height + + return ( + scaling * (x - (stylus_width - monitor.width / scaling) / 2), + scaling * (y - (stylus_height - monitor.height / scaling) / 2) + ) + + def create_local_device(): """ Create a virtual input device on this host that has the same @@ -28,6 +69,7 @@ def create_local_device(): :returns: virtual input device """ + import libevdev device = libevdev.Device() # Set device properties to emulate those of Wacom tablets @@ -146,6 +188,7 @@ def pipe_device(args, remote_device, local_device): :param remote_device: stream of events to read from :param local_device: local virtual device to write events to """ + import libevdev # While debug mode is active, we log events grouped together between # SYN_REPORT events. Pending events for the next log are stored here pending_events = [] @@ -170,6 +213,65 @@ def pipe_device(args, remote_device, local_device): else: pending_events += [event] + +def read_tablet(args): + """Loop forever and map evdev events to mouse""" + + from screeninfo import get_monitors + from pynput.mouse import Button, Controller + + lifted = True + new_x = new_y = False + + mouse = Controller() + + monitor = get_monitors()[args.monitor] + log.debug('Chose monitor: {}'.format(monitor)) + + stdout = open_remote_device(args) + + while True: + _, _, e_type, e_code, e_value = struct.unpack('2IHHi', stdout.read(16)) + + if e_type == e_type_abs: + + # handle x direction + if e_code == e_code_stylus_xpos: + log.debug(e_value) + x = e_value + new_x = True + + # handle y direction + if e_code == e_code_stylus_ypos: + log.debug('\t{}'.format(e_value)) + y = e_value + new_y = True + + # handle draw + if e_code == e_code_stylus_pressure: + log.debug('\t\t{}'.format(e_value)) + if e_value > args.threshold: + if lifted: + log.debug('PRESS') + lifted = False + mouse.press(Button.left) + else: + if not lifted: + log.debug('RELEASE') + lifted = True + mouse.release(Button.left) + + + # only move when x and y are updated for smoother mouse + if new_x and new_y: + mapped_x, mapped_y = fit(x, y, stylus_width, stylus_height, monitor, args.orientation) + mouse.move( + monitor.x + mapped_x - mouse.position[0], + monitor.y + mapped_y - mouse.position[1] + ) + new_x = new_y = False + + def main(): try: parser = argparse.ArgumentParser(description="use reMarkable tablet as a mouse input") @@ -177,6 +279,10 @@ def main(): parser.add_argument('--key', type=str, metavar='PATH', help="ssh private key") parser.add_argument('--password', default=None, type=str, help="ssh password") parser.add_argument('--address', default='10.11.99.1', type=str, help="device address") + parser.add_argument('--orientation', default='left', choices=['vertical', 'left', 'right']) + parser.add_argument('--monitor', default=0, type=int, metavar='NUM', help="monitor to use") + parser.add_argument('--threshold', default=1000, type=int, help="stylus pressure threshold (default 1000)") + parser.add_argument('--evdev', action='store_true', default=False, help="use evdev to support pen tilt (requires root, libev)") args = parser.parse_args() @@ -185,19 +291,21 @@ def main(): log.setLevel(logging.DEBUG) log.info('Debugging enabled...') else: - logging.getLogger('').setLevel(logging.INFO) log.setLevel(logging.INFO) - try: - local_device = create_local_device() - log.info("Created virtual input device '{}'".format(local_device.devnode)) - except PermissionError: - log.error('Insufficient permissions for creating a virtual input device') - log.error('Make sure you run this program as root') - sys.exit(1) - - remote_device = open_remote_device(args) - pipe_device(args, remote_device, local_device) + if args.evdev: + try: + local_device = create_local_device() + log.info("Created virtual input device '{}'".format(local_device.devnode)) + except PermissionError: + log.error('Insufficient permissions for creating a virtual input device') + log.error('Make sure you run this program as root') + sys.exit(1) + + remote_device = open_remote_device(args) + pipe_device(args, remote_device, local_device) + else: + read_tablet(args) except KeyboardInterrupt: pass except EOFError: