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