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

# 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()