Browse Source

provisional rm2 support

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

258
remarkable_mouse/evdev.py

@ -3,10 +3,11 @@ import struct @@ -3,10 +3,11 @@ import struct
import subprocess
from screeninfo import get_monitors
import time
from socket import timeout
from socket import timeout as TimeoutError
from itertools import cycle
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
MAX_ABS_X = 20967
@ -41,6 +42,8 @@ def create_local_device(): @@ -41,6 +42,8 @@ def create_local_device():
'version': 54
}
# ----- Buttons -----
# Enable buttons supported by the digitizer
device.enable(libevdev.EV_KEY.BTN_TOOL_PEN)
device.enable(libevdev.EV_KEY.BTN_TOOL_RUBBER)
@ -50,125 +53,80 @@ def create_local_device(): @@ -50,125 +53,80 @@ def create_local_device():
device.enable(libevdev.EV_KEY.BTN_0)
device.enable(libevdev.EV_KEY.BTN_1)
device.enable(libevdev.EV_KEY.BTN_2)
# ----- Touch -----
# Enable Touch input
device.enable(
libevdev.EV_ABS.ABS_MT_POSITION_X,
libevdev.InputAbsInfo(
minimum=0,
maximum=MT_MAX_ABS_X,
resolution=2531 #?
)
libevdev.InputAbsInfo(minimum=0, maximum=MT_MAX_ABS_X, resolution=2531) # resolution correct?
)
device.enable(
libevdev.EV_ABS.ABS_MT_POSITION_Y,
libevdev.InputAbsInfo(
minimum=0,
maximum=MT_MAX_ABS_Y,
resolution=2531 #?
)
libevdev.InputAbsInfo(minimum=0, maximum=MT_MAX_ABS_Y, resolution=2531) # resolution correct?
)
device.enable(
libevdev.EV_ABS.ABS_MT_PRESSURE,
libevdev.InputAbsInfo(
minimum=0,
maximum=255
)
libevdev.InputAbsInfo(minimum=0, maximum=255)
)
device.enable(
libevdev.EV_ABS.ABS_MT_TOUCH_MAJOR,
libevdev.InputAbsInfo(
minimum=0,
maximum=255
)
libevdev.InputAbsInfo(minimum=0, maximum=255)
)
device.enable(
libevdev.EV_ABS.ABS_MT_TOUCH_MINOR,
libevdev.InputAbsInfo(
minimum=0,
maximum=255
)
libevdev.InputAbsInfo(minimum=0, maximum=255)
)
device.enable(
libevdev.EV_ABS.ABS_MT_ORIENTATION,
libevdev.InputAbsInfo(
minimum=-127,
maximum=127
)
libevdev.InputAbsInfo(minimum=-127, maximum=127)
)
device.enable(
libevdev.EV_ABS.ABS_MT_SLOT,
libevdev.InputAbsInfo(
minimum=0,
maximum=31
)
libevdev.InputAbsInfo(minimum=0, maximum=31)
)
device.enable(
libevdev.EV_ABS.ABS_MT_TOOL_TYPE,
libevdev.InputAbsInfo(
minimum=0,
maximum=1
)
libevdev.InputAbsInfo(minimum=0, maximum=1)
)
device.enable(
libevdev.EV_ABS.ABS_MT_TRACKING_ID,
libevdev.InputAbsInfo(
minimum=0,
maximum=65535
)
libevdev.InputAbsInfo(minimum=0, maximum=65535)
)
# ----- Pen -----
# Enable pen input, tilt and pressure
device.enable(
libevdev.EV_ABS.ABS_X,
libevdev.InputAbsInfo(
minimum=0,
maximum=MAX_ABS_X,
resolution=2531
)
libevdev.InputAbsInfo(minimum=0, maximum=MAX_ABS_X, resolution=2531)
)
device.enable(
libevdev.EV_ABS.ABS_Y,
libevdev.InputAbsInfo(
minimum=0,
maximum=MAX_ABS_Y,
resolution=2531
)
libevdev.InputAbsInfo(minimum=0, maximum=MAX_ABS_Y, resolution=2531)
)
device.enable(
libevdev.EV_ABS.ABS_PRESSURE,
libevdev.InputAbsInfo(
minimum=0,
maximum=4095
)
libevdev.InputAbsInfo(minimum=0, maximum=4095)
)
device.enable(
libevdev.EV_ABS.ABS_DISTANCE,
libevdev.InputAbsInfo(
minimum=0,
maximum=255
)
libevdev.InputAbsInfo(minimum=0, maximum=255)
)
device.enable(
libevdev.EV_ABS.ABS_TILT_X,
libevdev.InputAbsInfo(
minimum=-9000,
maximum=9000
)
libevdev.InputAbsInfo(minimum=-9000, maximum=9000)
)
device.enable(
libevdev.EV_ABS.ABS_TILT_Y,
libevdev.InputAbsInfo(
minimum=-9000,
maximum=9000
)
libevdev.InputAbsInfo(minimum=-9000, maximum=9000)
)
return device.create_uinput_device()
# remap screen coordinates to wacom coordinates
def remap(x, y, wacom_width, wacom_height, monitor_width,
# map computer screen coordinates to rM pen coordinates
def map_comp2pen(x, y, wacom_width, wacom_height, monitor_width,
monitor_height, mode, orientation=None):
if orientation in ('bottom', 'top'):
@ -189,8 +147,8 @@ def remap(x, y, wacom_width, wacom_height, monitor_width, @@ -189,8 +147,8 @@ def remap(x, y, wacom_width, wacom_height, monitor_width,
scaling * (y - (monitor_height - wacom_height / scaling) / 2)
)
# remap screen coordinates to touch coordinates
def remapTouch(x, y, touch_width, touch_height, monitor_width,
# map computer screen coordinates to rM touch coordinates
def map_comp2touch(x, y, touch_width, touch_height, monitor_width,
monitor_height, mode, orientation=None):
if orientation in ('left', 'right'):
@ -211,19 +169,19 @@ def remapTouch(x, y, touch_width, touch_height, monitor_width, @@ -211,19 +169,19 @@ def remapTouch(x, y, touch_width, touch_height, monitor_width,
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: 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
time.sleep(1)
# ----- Pen -----
# set orientation with xinput
orientation = {'left': 0, 'bottom': 1, 'top': 2, 'right': 3}[args.orientation]
result = subprocess.run(
@ -232,7 +190,7 @@ def pipe_device(args, remote_device, local_device): @@ -232,7 +190,7 @@ def pipe_device(args, remote_device, local_device):
shell=True
)
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
monitor = get_monitors()[args.monitor]
@ -243,7 +201,7 @@ def pipe_device(args, remote_device, local_device): @@ -243,7 +201,7 @@ def pipe_device(args, remote_device, local_device):
shell=True
)
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
result = subprocess.run(
@ -252,16 +210,16 @@ def pipe_device(args, remote_device, local_device): @@ -252,16 +210,16 @@ def pipe_device(args, remote_device, local_device):
shell=True
)
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
min_x, min_y = remap(
min_x, min_y = map_comp2pen(
0, 0,
MAX_ABS_X, MAX_ABS_Y, monitor.width, monitor.height,
args.mode,
args.orientation
)
max_x, max_y = remap(
max_x, max_y = map_comp2pen(
monitor.width, monitor.height,
MAX_ABS_X, MAX_ABS_Y, monitor.width, monitor.height,
args.mode,
@ -275,16 +233,18 @@ def pipe_device(args, remote_device, local_device): @@ -275,16 +233,18 @@ def pipe_device(args, remote_device, local_device):
shell=True
)
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
mt_min_x, mt_min_y = remapTouch(
mt_min_x, mt_min_y = map_comp2touch(
0, 0,
MT_MAX_ABS_X, MT_MAX_ABS_Y, monitor.width, monitor.height,
args.mode,
args.orientation
)
mt_max_x, mt_max_y = remapTouch(
mt_max_x, mt_max_y = map_comp2touch(
monitor.width, monitor.height,
MT_MAX_ABS_X, MT_MAX_ABS_Y, monitor.width, monitor.height,
args.mode,
@ -298,60 +258,104 @@ def pipe_device(args, remote_device, local_device): @@ -298,60 +258,104 @@ def pipe_device(args, remote_device, local_device):
shell=True
)
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.
'xinput --set-prop "reMarkable pen touch" "Coordinate Transformation Matrix" 0 1 0 -1 0 1 0 0 1',
capture_output=True,
shell=True
)
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
# 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 = []
pen_down = 0
while True:
for device in remote_device:
ev = 0
try:
ev = device.read(16)
except timeout:
continue
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
# loop inputs forever
for rm_input in cycle(rm_inputs[1:2]):
try:
data = rm_input.read(16)
except TimeoutError:
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:
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]
pending_events.append(event)
# pen_down = 0
# while True:
# for device in rm_inputs:
# try:
# e_time, e_millis, e_type, e_code, e_value = struct.unpack('2IHHi', ev.read(16))
# e_bit = libevdev.evbit(e_type, e_code)
# except timeout:
# continue
# 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 @@ -3,7 +3,7 @@ import struct
from screeninfo import get_monitors
logging.basicConfig(format='%(message)s')
log = logging.getLogger(__name__)
log = logging.getLogger('remouse')
# evtype_sync = 0
# evtype_key = 1

58
remarkable_mouse/remarkable_mouse.py

@ -12,10 +12,10 @@ import paramiko @@ -12,10 +12,10 @@ import paramiko
import paramiko.agent
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.
@ -23,9 +23,11 @@ def open_remote_device(args, file='/dev/input/event0'): @@ -23,9 +23,11 @@ def open_remote_device(args, file='/dev/input/event0'):
args: argparse arguments
file (str): path to the input device on the device
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.set_missing_host_key_policy(paramiko.AutoAddPolicy())
@ -68,17 +70,35 @@ def open_remote_device(args, file='/dev/input/event0'): @@ -68,17 +70,35 @@ def open_remote_device(args, file='/dev/input/event0'):
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 ' + file)
_, mt, _ = client.exec_command('cat /dev/input/event1')
_, btn, _ = client.exec_command('cat /dev/input/event2')
pen.channel.setblocking(0)
mt.channel.setblocking(0)
btn.channel.setblocking(0)
pen = client.exec_command('cat ' + pen_file)[1]
touch = client.exec_command('cat ' + touch_file)[1]
button = client.exec_command('cat ' + button_file)[1]
# Skip to next input if no data available
# pen.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():
@ -96,17 +116,16 @@ def main(): @@ -96,17 +116,16 @@ def main():
args = parser.parse_args()
remote_device = open_remote_device(args)
if args.debug:
logging.getLogger('').setLevel(logging.DEBUG)
log.setLevel(logging.DEBUG)
log.info('Debugging enabled...')
print('Debugging enabled...')
else:
log.setLevel(logging.INFO)
rm_inputs = open_rm_inputs(args)
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:
local_device = create_local_device()
@ -116,11 +135,12 @@ def main(): @@ -116,11 +135,12 @@ def main():
log.error('Make sure you run this program as root')
sys.exit(1)
pipe_device(args, remote_device, local_device)
configure_xinput(args)
read_tablet(args, rm_inputs, local_device)
else:
from remarkable_mouse.pynput import read_tablet
read_tablet(args, remote_device)
read_tablet(args, rm_inputs)
except KeyboardInterrupt:
pass

Loading…
Cancel
Save