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.
173 lines
5.9 KiB
173 lines
5.9 KiB
# Evan Widloski - 2019-02-23 |
|
# Use reMarkable as mouse input |
|
|
|
import argparse |
|
import logging |
|
import os |
|
import sys |
|
import struct |
|
from getpass import getpass |
|
from itertools import cycle |
|
|
|
import paramiko |
|
import paramiko.agent |
|
|
|
logging.basicConfig(format='%(message)s') |
|
log = logging.getLogger('remouse') |
|
|
|
default_key = os.path.expanduser('~/.ssh/remarkable') |
|
|
|
|
|
def open_rm_inputs(*, address, key, password): |
|
""" |
|
Open a remote input device via SSH. |
|
|
|
Args: |
|
address (str): address to reMarkable |
|
key (str, optional): path to reMarkable ssh key |
|
password (str, optional): reMarkable ssh password |
|
Returns: |
|
(paramiko.ChannelFile): read-only stream of pen events |
|
(paramiko.ChannelFile): read-only stream of touch events |
|
(paramiko.ChannelFile): read-only stream of button events |
|
""" |
|
log.debug("Connecting to input '{}'".format(address)) |
|
|
|
client = paramiko.SSHClient() |
|
client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) |
|
|
|
pkey = None |
|
|
|
agent = paramiko.agent.Agent() |
|
|
|
def use_key(key): |
|
for key_type in [paramiko.RSAKey, paramiko.Ed25519Key, paramiko.ECDSAKey]: |
|
try: |
|
pkey = key_type.from_private_key_file(os.path.expanduser(key)) |
|
except paramiko.ssh_exception.SSHException: |
|
continue |
|
except paramiko.ssh_exception.PasswordRequiredException: |
|
passphrase = getpass( |
|
"Enter passphrase for key '{}': ".format(os.path.expanduser(key)) |
|
) |
|
pkey = paramiko.RSAKey.from_private_key_file( |
|
os.path.expanduser(key), password=passphrase |
|
) |
|
break |
|
return pkey |
|
|
|
if key is not None: |
|
password = None |
|
pkey = use_key(key) |
|
elif os.path.exists(default_key): |
|
password = None |
|
pkey = use_key(default_key) |
|
elif password is not None: |
|
pkey = None |
|
elif not agent.get_keys(): |
|
password = getpass( |
|
"Password for '{}': ".format(address) |
|
) |
|
pkey = None |
|
|
|
client.connect( |
|
address, |
|
username='root', |
|
password=password, |
|
pkey=pkey, |
|
look_for_keys=True, |
|
disabled_algorithms=dict(pubkeys=["rsa-sha2-512", "rsa-sha2-256"]) |
|
) |
|
|
|
session = client.get_transport().open_session() |
|
|
|
paramiko.agent.AgentRequestHandler(session) |
|
|
|
pen_file = client.exec_command( |
|
'readlink -f /dev/input/touchscreen0' |
|
)[1].read().decode('utf8').rstrip('\n') |
|
|
|
# handle both reMarkable versions |
|
# https://github.com/Eeems/oxide/issues/48#issuecomment-690830572 |
|
if pen_file == '/dev/input/event0': |
|
# rM 1 |
|
touch_file = '/dev/input/event1' |
|
button_file = '/dev/input/event2' |
|
else: |
|
# rM 2 |
|
touch_file = '/dev/input/event2' |
|
button_file = '/dev/input/event0' |
|
|
|
log.debug('Pen:{}\nTouch:{}\nButton:{}'.format(pen_file, touch_file, button_file)) |
|
|
|
# Start reading events |
|
pen = client.exec_command('cat ' + pen_file)[1] |
|
touch = client.exec_command('cat ' + touch_file)[1] |
|
button = client.exec_command('cat ' + button_file)[1] |
|
|
|
return {'pen': pen, 'touch': touch, 'button': button} |
|
|
|
|
|
def main(): |
|
try: |
|
parser = argparse.ArgumentParser(description="use reMarkable tablet as a mouse input") |
|
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") |
|
parser.add_argument('--mode', default='fill', choices=['fit', 'fill', 'stretch', 'cool'], help="""Scale setting. |
|
Fit (default): take up the entire tablet, but not necessarily the entire monitor. |
|
Fill: take up the entire monitor, but not necessarily the entire tablet. |
|
Stretch: take up both the entire tablet and monitor, but don't maintain aspect ratio. |
|
Cool: fit the entire monitor on the tablet, ensure 1:1 aspect ratio on both.""") |
|
parser.add_argument('--orientation', default='right', choices=['top', 'left', 'right', 'bottom'], help="position of tablet buttons") |
|
parser.add_argument('--monitor', default=0, type=int, metavar='NUM', help="monitor to output to") |
|
parser.add_argument('--region', action='store_true', default=False, help="Use a GUI to position the output area. Overrides --monitor") |
|
parser.add_argument('--threshold', metavar='THRESH', default=600, type=int, help="stylus pressure threshold (default 600)") |
|
parser.add_argument('--evdev', action='store_true', default=False, help="use evdev to support pen pressure (requires root, Linux only)") |
|
|
|
args = parser.parse_args() |
|
|
|
if args.debug: |
|
log.setLevel(logging.DEBUG) |
|
print('Debugging enabled...') |
|
else: |
|
log.setLevel(logging.INFO) |
|
|
|
# ----- Connect to device ----- |
|
|
|
rm_inputs = open_rm_inputs( |
|
address=args.address, |
|
key=args.key, |
|
password=args.password, |
|
) |
|
print("Connected to", args.address) |
|
|
|
# ----- Handle events ----- |
|
|
|
if args.evdev: |
|
from remarkable_mouse.evdev import read_tablet |
|
|
|
else: |
|
from remarkable_mouse.pynput import read_tablet |
|
|
|
read_tablet( |
|
rm_inputs, |
|
orientation=args.orientation, |
|
monitor_num=args.monitor, |
|
region=args.region, |
|
threshold=args.threshold, |
|
mode=args.mode, |
|
) |
|
|
|
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) |
|
except KeyboardInterrupt: |
|
pass |
|
except EOFError: |
|
pass |
|
|
|
if __name__ == '__main__': |
|
main()
|
|
|