#!/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 libevdev
import paramiko
logging . basicConfig ( format = ' %(message)s ' )
log = logging . getLogger ( __name__ )
# Maximum value that can be reported by the Wacom driver for the X axis
MAX_ABS_X = 20967
# Maximum value that can be reported by the Wacom driver for the Y axis
MAX_ABS_Y = 15725
def create_local_device ( ) :
"""
Create a virtual input device on this host that has the same
characteristics as a Wacom tablet .
: returns : virtual input device
"""
device = libevdev . Device ( )
# Set device properties to emulate those of Wacom tablets
device . name = ' reMarkable tablet '
device . id = {
' bustype ' : 24 ,
' vendor ' : 1386 ,
' product ' : 0 ,
' version ' : 54
}
# Enable buttons supported by the digitizer
device . enable ( libevdev . EV_KEY . BTN_TOOL_PEN )
device . enable ( libevdev . EV_KEY . BTN_TOOL_RUBBER )
device . enable ( libevdev . EV_KEY . BTN_TOUCH )
device . enable ( libevdev . EV_KEY . BTN_STYLUS )
device . enable ( libevdev . EV_KEY . BTN_STYLUS2 )
# Enable position, tilt, distance and pressure change events
device . enable (
libevdev . EV_ABS . ABS_X ,
libevdev . InputAbsInfo (
minimum = 0 ,
maximum = MAX_ABS_X
)
)
device . enable (
libevdev . EV_ABS . ABS_Y ,
libevdev . InputAbsInfo (
minimum = 0 ,
maximum = MAX_ABS_Y
)
)
device . enable (
libevdev . EV_ABS . ABS_PRESSURE ,
libevdev . InputAbsInfo (
minimum = 0 ,
maximum = 4095
)
)
device . enable (
libevdev . EV_ABS . ABS_DISTANCE ,
libevdev . InputAbsInfo (
minimum = 0 ,
maximum = 255
)
)
device . enable (
libevdev . EV_ABS . ABS_TILT_X ,
libevdev . InputAbsInfo (
minimum = - 9000 ,
maximum = 9000
)
)
device . enable (
libevdev . EV_ABS . ABS_TILT_Y ,
libevdev . InputAbsInfo (
minimum = - 9000 ,
maximum = 9000
)
)
return device . create_uinput_device ( )
def open_remote_device ( args , file = ' /dev/input/event0 ' ) :
"""
Open a remote input device via SSH .
: param args : command - line arguments
: param file : path to the input device on the device
: returns : read - only stream of input events
"""
log . info ( " Connecting to input ' {} ' on ' {} ' " . format ( file , args . address ) )
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
)
elif args . password :
password = args . password
pkey = None
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
)
# Start reading events
_ , stdout , _ = client . exec_command ( ' cat ' + file )
return stdout
def pipe_device ( args , remote_device , local_device ) :
"""
Pipe events from a remote device to a local device .
: param args : command - line arguments
: param remote_device : stream of events to read from
: param local_device : local virtual device to write events to
"""
# 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 = [ ]
while True :
e_time , e_millis , e_type , e_code , e_value = struct . unpack ( ' 2IHHi ' , remote_device . read ( 16 ) )
e_bit = libevdev . evbit ( e_type , e_code )
event = libevdev . InputEvent ( e_bit , value = e_value )
local_device . send_events ( [ event ] )
if args . debug :
if e_bit == libevdev . EV_SYN . SYN_REPORT :
event_repr = ' , ' . join (
' {} = {} ' . format (
event . code . name ,
event . value
) for event in pending_events
)
log . debug ( ' {} . {:0>6} - {} ' . format ( e_time , e_millis , event_repr ) )
pending_events = [ ]
else :
pending_events + = [ event ]
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 " )
args = parser . parse_args ( )
if args . debug :
logging . getLogger ( ' ' ) . setLevel ( logging . DEBUG )
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 )
except KeyboardInterrupt :
pass
except EOFError :
pass
if __name__ == ' __main__ ' :
main ( )