Browse Source

provisional rm2 support

master
root 4 years ago
parent
commit
f187f125e3
  1. 244
      remarkable_mouse/evdev.py
  2. 2
      remarkable_mouse/pynput.py
  3. 58
      remarkable_mouse/remarkable_mouse.py

244
remarkable_mouse/evdev.py

@ -3,10 +3,11 @@ import struct
import subprocess import subprocess
from screeninfo import get_monitors from screeninfo import get_monitors
import time import time
from socket import timeout from socket import timeout as TimeoutError
from itertools import cycle
logging.basicConfig(format='%(message)s') logging.basicConfig(format='%(message)s')
log = logging.getLogger(__name__) log = logging.getLogger('remouse')
# Maximum value that can be reported by the Wacom driver for the X axis # Maximum value that can be reported by the Wacom driver for the X axis
MAX_ABS_X = 20967 MAX_ABS_X = 20967
@ -41,6 +42,8 @@ def create_local_device():
'version': 54 'version': 54
} }
# ----- Buttons -----
# Enable buttons supported by the digitizer # Enable buttons supported by the digitizer
device.enable(libevdev.EV_KEY.BTN_TOOL_PEN) device.enable(libevdev.EV_KEY.BTN_TOOL_PEN)
device.enable(libevdev.EV_KEY.BTN_TOOL_RUBBER) device.enable(libevdev.EV_KEY.BTN_TOOL_RUBBER)
@ -51,124 +54,79 @@ def create_local_device():
device.enable(libevdev.EV_KEY.BTN_1) device.enable(libevdev.EV_KEY.BTN_1)
device.enable(libevdev.EV_KEY.BTN_2) device.enable(libevdev.EV_KEY.BTN_2)
# ----- Touch -----
# Enable Touch input # Enable Touch input
device.enable( device.enable(
libevdev.EV_ABS.ABS_MT_POSITION_X, libevdev.EV_ABS.ABS_MT_POSITION_X,
libevdev.InputAbsInfo( libevdev.InputAbsInfo(minimum=0, maximum=MT_MAX_ABS_X, resolution=2531) # resolution correct?
minimum=0,
maximum=MT_MAX_ABS_X,
resolution=2531 #?
)
) )
device.enable( device.enable(
libevdev.EV_ABS.ABS_MT_POSITION_Y, libevdev.EV_ABS.ABS_MT_POSITION_Y,
libevdev.InputAbsInfo( libevdev.InputAbsInfo(minimum=0, maximum=MT_MAX_ABS_Y, resolution=2531) # resolution correct?
minimum=0,
maximum=MT_MAX_ABS_Y,
resolution=2531 #?
)
) )
device.enable( device.enable(
libevdev.EV_ABS.ABS_MT_PRESSURE, libevdev.EV_ABS.ABS_MT_PRESSURE,
libevdev.InputAbsInfo( libevdev.InputAbsInfo(minimum=0, maximum=255)
minimum=0,
maximum=255
)
) )
device.enable( device.enable(
libevdev.EV_ABS.ABS_MT_TOUCH_MAJOR, libevdev.EV_ABS.ABS_MT_TOUCH_MAJOR,
libevdev.InputAbsInfo( libevdev.InputAbsInfo(minimum=0, maximum=255)
minimum=0,
maximum=255
)
) )
device.enable( device.enable(
libevdev.EV_ABS.ABS_MT_TOUCH_MINOR, libevdev.EV_ABS.ABS_MT_TOUCH_MINOR,
libevdev.InputAbsInfo( libevdev.InputAbsInfo(minimum=0, maximum=255)
minimum=0,
maximum=255
)
) )
device.enable( device.enable(
libevdev.EV_ABS.ABS_MT_ORIENTATION, libevdev.EV_ABS.ABS_MT_ORIENTATION,
libevdev.InputAbsInfo( libevdev.InputAbsInfo(minimum=-127, maximum=127)
minimum=-127,
maximum=127
)
) )
device.enable( device.enable(
libevdev.EV_ABS.ABS_MT_SLOT, libevdev.EV_ABS.ABS_MT_SLOT,
libevdev.InputAbsInfo( libevdev.InputAbsInfo(minimum=0, maximum=31)
minimum=0,
maximum=31
)
) )
device.enable( device.enable(
libevdev.EV_ABS.ABS_MT_TOOL_TYPE, libevdev.EV_ABS.ABS_MT_TOOL_TYPE,
libevdev.InputAbsInfo( libevdev.InputAbsInfo(minimum=0, maximum=1)
minimum=0,
maximum=1
)
) )
device.enable( device.enable(
libevdev.EV_ABS.ABS_MT_TRACKING_ID, libevdev.EV_ABS.ABS_MT_TRACKING_ID,
libevdev.InputAbsInfo( libevdev.InputAbsInfo(minimum=0, maximum=65535)
minimum=0,
maximum=65535
)
) )
# ----- Pen -----
# Enable pen input, tilt and pressure # Enable pen input, tilt and pressure
device.enable( device.enable(
libevdev.EV_ABS.ABS_X, libevdev.EV_ABS.ABS_X,
libevdev.InputAbsInfo( libevdev.InputAbsInfo(minimum=0, maximum=MAX_ABS_X, resolution=2531)
minimum=0,
maximum=MAX_ABS_X,
resolution=2531
)
) )
device.enable( device.enable(
libevdev.EV_ABS.ABS_Y, libevdev.EV_ABS.ABS_Y,
libevdev.InputAbsInfo( libevdev.InputAbsInfo(minimum=0, maximum=MAX_ABS_Y, resolution=2531)
minimum=0,
maximum=MAX_ABS_Y,
resolution=2531
)
) )
device.enable( device.enable(
libevdev.EV_ABS.ABS_PRESSURE, libevdev.EV_ABS.ABS_PRESSURE,
libevdev.InputAbsInfo( libevdev.InputAbsInfo(minimum=0, maximum=4095)
minimum=0,
maximum=4095
)
) )
device.enable( device.enable(
libevdev.EV_ABS.ABS_DISTANCE, libevdev.EV_ABS.ABS_DISTANCE,
libevdev.InputAbsInfo( libevdev.InputAbsInfo(minimum=0, maximum=255)
minimum=0,
maximum=255
)
) )
device.enable( device.enable(
libevdev.EV_ABS.ABS_TILT_X, libevdev.EV_ABS.ABS_TILT_X,
libevdev.InputAbsInfo( libevdev.InputAbsInfo(minimum=-9000, maximum=9000)
minimum=-9000,
maximum=9000
)
) )
device.enable( device.enable(
libevdev.EV_ABS.ABS_TILT_Y, libevdev.EV_ABS.ABS_TILT_Y,
libevdev.InputAbsInfo( libevdev.InputAbsInfo(minimum=-9000, maximum=9000)
minimum=-9000,
maximum=9000
)
) )
return device.create_uinput_device() return device.create_uinput_device()
# remap screen coordinates to wacom coordinates # map computer screen coordinates to rM pen coordinates
def remap(x, y, wacom_width, wacom_height, monitor_width, def map_comp2pen(x, y, wacom_width, wacom_height, monitor_width,
monitor_height, mode, orientation=None): monitor_height, mode, orientation=None):
if orientation in ('bottom', 'top'): if orientation in ('bottom', 'top'):
@ -189,8 +147,8 @@ def remap(x, y, wacom_width, wacom_height, monitor_width,
scaling * (y - (monitor_height - wacom_height / scaling) / 2) scaling * (y - (monitor_height - wacom_height / scaling) / 2)
) )
# remap screen coordinates to touch coordinates # map computer screen coordinates to rM touch coordinates
def remapTouch(x, y, touch_width, touch_height, monitor_width, def map_comp2touch(x, y, touch_width, touch_height, monitor_width,
monitor_height, mode, orientation=None): monitor_height, mode, orientation=None):
if orientation in ('left', 'right'): if orientation in ('left', 'right'):
@ -211,19 +169,19 @@ def remapTouch(x, y, touch_width, touch_height, monitor_width,
scaling * (y - (monitor_height - touch_height / scaling) / 2) scaling * (y - (monitor_height - touch_height / scaling) / 2)
) )
def pipe_device(args, remote_device, local_device): def configure_xinput(args):
""" """
Pipe events from a remote device to a local device. Configure screen mapping settings from rM to local machine
Args: Args:
args: argparse arguments args: argparse arguments
remote_device (paramiko.ChannelFile): read-only stream of input events
local_device: local virtual input device to write events to
""" """
# give time for virtual device creation before running xinput commands # give time for virtual device creation before running xinput commands
time.sleep(1) time.sleep(1)
# ----- Pen -----
# set orientation with xinput # set orientation with xinput
orientation = {'left': 0, 'bottom': 1, 'top': 2, 'right': 3}[args.orientation] orientation = {'left': 0, 'bottom': 1, 'top': 2, 'right': 3}[args.orientation]
result = subprocess.run( result = subprocess.run(
@ -232,7 +190,7 @@ def pipe_device(args, remote_device, local_device):
shell=True shell=True
) )
if result.returncode != 0: if result.returncode != 0:
log.warning("Error setting orientation: %s", result.stderr) log.warning("Error setting orientation: %s", result.stderr.decode('utf8'))
# set monitor to use # set monitor to use
monitor = get_monitors()[args.monitor] monitor = get_monitors()[args.monitor]
@ -243,7 +201,7 @@ def pipe_device(args, remote_device, local_device):
shell=True shell=True
) )
if result.returncode != 0: if result.returncode != 0:
log.warning("Error setting monitor: %s", result.stderr) log.warning("Error setting monitor: %s", result.stderr.decode('utf8'))
# set stylus pressure # set stylus pressure
result = subprocess.run( result = subprocess.run(
@ -252,16 +210,16 @@ def pipe_device(args, remote_device, local_device):
shell=True shell=True
) )
if result.returncode != 0: if result.returncode != 0:
log.warning("Error setting pressure threshold: %s", result.stderr) log.warning("Error setting pressure threshold: %s", result.stderr.decode('utf8'))
# set fitting mode # set fitting mode
min_x, min_y = remap( min_x, min_y = map_comp2pen(
0, 0, 0, 0,
MAX_ABS_X, MAX_ABS_Y, monitor.width, monitor.height, MAX_ABS_X, MAX_ABS_Y, monitor.width, monitor.height,
args.mode, args.mode,
args.orientation args.orientation
) )
max_x, max_y = remap( max_x, max_y = map_comp2pen(
monitor.width, monitor.height, monitor.width, monitor.height,
MAX_ABS_X, MAX_ABS_Y, monitor.width, monitor.height, MAX_ABS_X, MAX_ABS_Y, monitor.width, monitor.height,
args.mode, args.mode,
@ -275,16 +233,18 @@ def pipe_device(args, remote_device, local_device):
shell=True shell=True
) )
if result.returncode != 0: if result.returncode != 0:
log.warning("Error setting fit: %s", result.stderr) log.warning("Error setting fit: %s", result.stderr.decode('utf8'))
# ----- Touch -----
# Set touch fitting mode # Set touch fitting mode
mt_min_x, mt_min_y = remapTouch( mt_min_x, mt_min_y = map_comp2touch(
0, 0, 0, 0,
MT_MAX_ABS_X, MT_MAX_ABS_Y, monitor.width, monitor.height, MT_MAX_ABS_X, MT_MAX_ABS_Y, monitor.width, monitor.height,
args.mode, args.mode,
args.orientation args.orientation
) )
mt_max_x, mt_max_y = remapTouch( mt_max_x, mt_max_y = map_comp2touch(
monitor.width, monitor.height, monitor.width, monitor.height,
MT_MAX_ABS_X, MT_MAX_ABS_Y, monitor.width, monitor.height, MT_MAX_ABS_X, MT_MAX_ABS_Y, monitor.width, monitor.height,
args.mode, args.mode,
@ -298,60 +258,104 @@ def pipe_device(args, remote_device, local_device):
shell=True shell=True
) )
if result.returncode != 0: if result.returncode != 0:
log.warning("Error setting fit: %s", result.stderr) log.warning("Error setting fit: %s", result.stderr.decode('utf8'))
result = subprocess.run( # Just need to rotate the touchscreen -90 so that it matches the wacom sensor. result = subprocess.run( # Just need to rotate the touchscreen -90 so that it matches the wacom sensor.
'xinput --set-prop "reMarkable pen touch" "Coordinate Transformation Matrix" 0 1 0 -1 0 1 0 0 1', 'xinput --set-prop "reMarkable pen touch" "Coordinate Transformation Matrix" 0 1 0 -1 0 1 0 0 1',
capture_output=True, capture_output=True,
shell=True shell=True
) )
if result.returncode != 0: if result.returncode != 0:
log.warning("Error setting orientation: %s", result.stderr) log.warning("Error setting orientation: %s", result.stderr.decode('utf8'))
def read_tablet(args, rm_inputs, local_device):
"""
Pipe rM evdev events to local device
Args:
rm_inputs (tuple of paramiko.ChannelFile): tuple of pen, button
and touch input streams
local_device: local virtual input device to write events to
"""
import libevdev import libevdev
# While debug mode is active, we log events grouped together between # While debug mode is active, we log events grouped together between
# SYN_REPORT events. Pending events for the next log are stored here # SYN_REPORT events. Pending events for the next log are stored here
pending_events = [] pending_events = []
pen_down = 0
while True: # loop inputs forever
for device in remote_device: for rm_input in cycle(rm_inputs[1:2]):
ev = 0 try:
try: data = rm_input.read(16)
ev = device.read(16) except TimeoutError:
except timeout: continue
continue
e_time, e_millis, e_type, e_code, e_value = struct.unpack('2IHHi', data)
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.append(event)
e_time, e_millis, e_type, e_code, e_value = struct.unpack('2IHHi', ev)
e_bit = libevdev.evbit(e_type, e_code)
if e_bit == libevdev.EV_KEY.KEY_LEFT:
e_bit = libevdev.EV_KEY.BTN_0
if e_bit == libevdev.EV_KEY.KEY_HOME:
e_bit = libevdev.EV_KEY.BTN_1
if e_bit == libevdev.EV_KEY.KEY_RIGHT:
e_bit = libevdev.EV_KEY.BTN_2
event = libevdev.InputEvent(e_bit, value=e_value)
if e_bit == libevdev.EV_KEY.BTN_TOOL_PEN:
pen_down = e_value
if pen_down and 'ABS_MT' in event.code.name: # Palm rejection
pass
else:
local_device.send_events([event])
if args.debug:
if e_bit == libevdev.EV_SYN.SYN_REPORT: # pen_down = 0
event_repr = ', '.join(
'{} = {}'.format( # while True:
event.code.name, # for device in rm_inputs:
event.value # try:
) for event in pending_events # e_time, e_millis, e_type, e_code, e_value = struct.unpack('2IHHi', ev.read(16))
) # e_bit = libevdev.evbit(e_type, e_code)
log.debug('{}.{:0>6} - {}'.format(e_time, e_millis, event_repr)) # except timeout:
pending_events = [] # continue
else:
pending_events += [event] # if e_bit == libevdev.EV_KEY.KEY_LEFT:
# e_bit = libevdev.EV_KEY.BTN_0
# if e_bit == libevdev.EV_KEY.KEY_HOME:
# e_bit = libevdev.EV_KEY.BTN_1
# if e_bit == libevdev.EV_KEY.KEY_RIGHT:
# e_bit = libevdev.EV_KEY.BTN_2
# event = libevdev.InputEvent(e_bit, value=e_value)
# if e_bit == libevdev.EV_KEY.BTN_TOOL_PEN:
# pen_down = e_value
# if pen_down and 'ABS_MT' in event.code.name: # Palm rejection
# pass
# else:
# 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]

2
remarkable_mouse/pynput.py

@ -3,7 +3,7 @@ import struct
from screeninfo import get_monitors from screeninfo import get_monitors
logging.basicConfig(format='%(message)s') logging.basicConfig(format='%(message)s')
log = logging.getLogger(__name__) log = logging.getLogger('remouse')
# evtype_sync = 0 # evtype_sync = 0
# evtype_key = 1 # evtype_key = 1

58
remarkable_mouse/remarkable_mouse.py

@ -12,10 +12,10 @@ import paramiko
import paramiko.agent import paramiko.agent
logging.basicConfig(format='%(message)s') logging.basicConfig(format='%(message)s')
log = logging.getLogger(__name__) log = logging.getLogger('remouse')
def open_remote_device(args, file='/dev/input/event0'): def open_rm_inputs(args):
""" """
Open a remote input device via SSH. Open a remote input device via SSH.
@ -23,9 +23,11 @@ def open_remote_device(args, file='/dev/input/event0'):
args: argparse arguments args: argparse arguments
file (str): path to the input device on the device file (str): path to the input device on the device
Returns: Returns:
(paramiko.ChannelFile): read-only stream of input events (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.info("Connecting to input '{}' on '{}'".format(file, args.address)) log.debug("Connecting to input '{}'".format(args.address))
client = paramiko.SSHClient() client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
@ -68,17 +70,35 @@ def open_remote_device(args, file='/dev/input/event0'):
paramiko.agent.AgentRequestHandler(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 # Start reading events
_, pen, _ = client.exec_command('cat ' + file) pen = client.exec_command('cat ' + pen_file)[1]
_, mt, _ = client.exec_command('cat /dev/input/event1') touch = client.exec_command('cat ' + touch_file)[1]
_, btn, _ = client.exec_command('cat /dev/input/event2') button = client.exec_command('cat ' + button_file)[1]
pen.channel.setblocking(0) # Skip to next input if no data available
mt.channel.setblocking(0) # pen.channel.setblocking(0)
btn.channel.setblocking(0) # touch.channel.setblocking(0)
# button.channel.setblocking(0)
print("connected to", args.address) print("Connected to", args.address)
return pen, mt, btn return pen, touch, button
def main(): def main():
@ -96,17 +116,16 @@ def main():
args = parser.parse_args() args = parser.parse_args()
remote_device = open_remote_device(args)
if args.debug: if args.debug:
logging.getLogger('').setLevel(logging.DEBUG)
log.setLevel(logging.DEBUG) log.setLevel(logging.DEBUG)
log.info('Debugging enabled...') print('Debugging enabled...')
else: else:
log.setLevel(logging.INFO) log.setLevel(logging.INFO)
rm_inputs = open_rm_inputs(args)
if args.evdev: if args.evdev:
from remarkable_mouse.evdev import create_local_device, pipe_device from remarkable_mouse.evdev import create_local_device, configure_xinput, read_tablet
try: try:
local_device = create_local_device() local_device = create_local_device()
@ -116,11 +135,12 @@ def main():
log.error('Make sure you run this program as root') log.error('Make sure you run this program as root')
sys.exit(1) sys.exit(1)
pipe_device(args, remote_device, local_device) configure_xinput(args)
read_tablet(args, rm_inputs, local_device)
else: else:
from remarkable_mouse.pynput import read_tablet from remarkable_mouse.pynput import read_tablet
read_tablet(args, remote_device) read_tablet(args, rm_inputs)
except KeyboardInterrupt: except KeyboardInterrupt:
pass pass

Loading…
Cancel
Save