forked from https://github.com/Evidlo/remarkable_mouse | patches include cool mapping mode that actually does proper aspect ratio conversion and fixing it for smartcard ssh setups
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
176 lines
5.4 KiB
176 lines
5.4 KiB
#!/bin/env python |
|
# Evan Widloski - 2019-02-23 |
|
# Use reMarkable as mouse input |
|
|
|
import argparse |
|
import logging |
|
import os |
|
import sys |
|
import struct |
|
from getpass import getpass |
|
import paramiko |
|
from screeninfo import get_monitors |
|
from pynput.mouse import Button, Controller |
|
|
|
EV_SYNC = 0 |
|
EV_KEY = 1 |
|
EV_ABS = 3 |
|
|
|
WACOM_EVCODE_PRESSURE = 24 |
|
WACOM_EVCODE_DISTANCE = 25 |
|
WACOM_EVCODE_XTILT = 26 |
|
WACOM_EVCODE_YTILT = 27 |
|
WACOM_EVCODE_XPOS = 0 |
|
WACOM_EVCODE_YPOS = 1 |
|
|
|
wacom_width = 15725 |
|
wacom_height = 20951 |
|
wacom_click_pressure_min = 1000 |
|
screen_width = 675 |
|
screen_height = 900 |
|
|
|
mouse = Controller() |
|
logging.basicConfig(format='%(message)s') |
|
log = logging.getLogger(__name__) |
|
|
|
|
|
# remap wacom coordinates in various orientations |
|
def fit(x, y, wacom_width, wacom_height, monitor, orientation): |
|
|
|
if orientation == 'vertical': |
|
y = wacom_height - y |
|
elif orientation == 'right': |
|
x, y = y, x |
|
wacom_width, wacom_height = wacom_height, wacom_width |
|
elif orientation == 'left': |
|
x, y = wacom_height - y, wacom_width - x |
|
wacom_width, wacom_height = wacom_height, wacom_width |
|
|
|
ratio_width, ratio_height = monitor.width / wacom_width, monitor.height / wacom_height |
|
scaling = ratio_width if ratio_width > ratio_height else ratio_height |
|
|
|
return ( |
|
scaling * (x - (wacom_width - monitor.width / scaling) / 2), |
|
scaling * (y - (wacom_height - monitor.height / scaling) / 2) |
|
) |
|
|
|
|
|
def open_eventfile(args): |
|
"""ssh to reMarkable and open event0""" |
|
|
|
if args.key is not None: |
|
password = None |
|
try: |
|
pkey = paramiko.RSAKey.from_private_key_file(os.path.expanduser(args.key)) |
|
except paramiko.ssh_exception.PasswordRequiredException: |
|
passphrase = getpass( |
|
"Enter passphrase for key '{}': ".format(os.path.expanduser(args.key)) |
|
) |
|
pkey = paramiko.RSAKey.from_private_key_file( |
|
os.path.expanduser(args.key), |
|
password=passphrase |
|
) |
|
else: |
|
password = getpass( |
|
"Password for '{}': ".format(args.address) |
|
) |
|
pkey = None |
|
|
|
client = paramiko.SSHClient() |
|
client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) |
|
client.connect( |
|
args.address, |
|
username='root', |
|
password=password, |
|
pkey=pkey, |
|
look_for_keys=False |
|
) |
|
print("Connected to {}".format(args.address)) |
|
|
|
# Start reading events |
|
_, stdout, _ = client.exec_command('cat /dev/input/event0') |
|
|
|
return stdout |
|
|
|
|
|
def read_tablet(args): |
|
"""Loop forever and map evdev events to mouse""" |
|
|
|
updates_since_pressure = 0 |
|
new_x = new_y = False |
|
|
|
monitor = get_monitors()[args.monitor] |
|
log.debug('Chose monitor: {}'.format(monitor)) |
|
|
|
stdout = open_eventfile(args) |
|
|
|
while True: |
|
_, _, e_type, e_code, e_value = struct.unpack('2IHHi', stdout.read(16)) |
|
|
|
if e_type == EV_ABS: |
|
|
|
# handle x direction |
|
if e_code == WACOM_EVCODE_YPOS: |
|
log.debug(f'{e_value}') |
|
x = e_value |
|
new_x = True |
|
updates_since_pressure += 1 |
|
|
|
# handle y direction |
|
if e_code == WACOM_EVCODE_XPOS: |
|
log.debug(f'\t{e_value}') |
|
updates_since_pressure += 1 |
|
y = e_value |
|
new_y = True |
|
|
|
# handle draw |
|
if e_code == WACOM_EVCODE_PRESSURE: |
|
log.debug(f'\t\t{e_value}') |
|
updates_since_pressure = 0 |
|
if e_value > 1000: |
|
mouse.press(Button.left) |
|
else: |
|
mouse.release(Button.left) |
|
|
|
# FIXME: bug |
|
# sometimes the last detected pressure is above threshold |
|
# make sure pen doesnt get stuck down |
|
if updates_since_pressure > 8: |
|
updates_since_pressure = 0 |
|
mouse.release(Button.left) |
|
|
|
# only move when x and y are updated for smoother mouse |
|
if new_x and new_y: |
|
x, y = fit(x, y, wacom_width, wacom_height, monitor, args.orientation) |
|
mouse.move( |
|
monitor.x + x - mouse.position[0], |
|
monitor.y + y - mouse.position[1] |
|
) |
|
count = 0 |
|
new_x = new_y = False |
|
|
|
def main(): |
|
|
|
try: |
|
parser = argparse.ArgumentParser(description="use reMarkable tablet as a mouse input") |
|
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('--offset', default=(0, 0), type=int, metavar=('x', 'y'), nargs=2, help="offset mapped region on monitor") |
|
parser.add_argument('--debug', action='store_true', default=False, help="enable debug messages") |
|
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") |
|
args = parser.parse_args() |
|
|
|
if args.debug: |
|
print('Debugging enabled...') |
|
logging.getLogger('').setLevel(logging.DEBUG) |
|
log.setLevel(logging.DEBUG) |
|
|
|
read_tablet(args) |
|
except KeyboardInterrupt: |
|
pass |
|
|
|
|
|
if __name__ == '__main__': |
|
main()
|
|
|