# 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: {} \n Touch: {} \n Button: {} ' . 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 ( )