forked from https://github.com/Evidlo/remarkable_mouse | patches include cool mapping mode that actually does proper aspect ratio conversion and fixing it for smartcard ssh setups
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

357 lines
10 KiB

import logging
import struct
import subprocess
from screeninfo import get_monitors
import time
from socket import timeout
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
# Maximum value that can be reported by the cyttsp5_mt driver for the X axis
MT_MAX_ABS_X = 767
# Maximum value that can be reported by the cyttsp5_mt driver for the Y axis
MT_MAX_ABS_Y = 1023
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
"""
import libevdev
device = libevdev.Device()
# Set device properties to emulate those of Wacom tablets
device.name = 'reMarkable pen'
device.id = {
'bustype': 0x03, # usb
'vendor': 0x056a, # wacom
'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)
device.enable(libevdev.EV_KEY.BTN_0)
device.enable(libevdev.EV_KEY.BTN_1)
device.enable(libevdev.EV_KEY.BTN_2)
# Enable Touch input
device.enable(
libevdev.EV_ABS.ABS_MT_POSITION_X,
libevdev.InputAbsInfo(
minimum=0,
maximum=MT_MAX_ABS_X,
resolution=2531 #?
)
)
device.enable(
libevdev.EV_ABS.ABS_MT_POSITION_Y,
libevdev.InputAbsInfo(
minimum=0,
maximum=MT_MAX_ABS_Y,
resolution=2531 #?
)
)
device.enable(
libevdev.EV_ABS.ABS_MT_PRESSURE,
libevdev.InputAbsInfo(
minimum=0,
maximum=255
)
)
device.enable(
libevdev.EV_ABS.ABS_MT_TOUCH_MAJOR,
libevdev.InputAbsInfo(
minimum=0,
maximum=255
)
)
device.enable(
libevdev.EV_ABS.ABS_MT_TOUCH_MINOR,
libevdev.InputAbsInfo(
minimum=0,
maximum=255
)
)
device.enable(
libevdev.EV_ABS.ABS_MT_ORIENTATION,
libevdev.InputAbsInfo(
minimum=-127,
maximum=127
)
)
device.enable(
libevdev.EV_ABS.ABS_MT_SLOT,
libevdev.InputAbsInfo(
minimum=0,
maximum=31
)
)
device.enable(
libevdev.EV_ABS.ABS_MT_TOOL_TYPE,
libevdev.InputAbsInfo(
minimum=0,
maximum=1
)
)
device.enable(
libevdev.EV_ABS.ABS_MT_TRACKING_ID,
libevdev.InputAbsInfo(
minimum=0,
maximum=65535
)
)
# Enable pen input, tilt and pressure
device.enable(
libevdev.EV_ABS.ABS_X,
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
)
)
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()
# remap screen coordinates to wacom coordinates
def remap(x, y, wacom_width, wacom_height, monitor_width,
monitor_height, mode, orientation=None):
if orientation in ('bottom', 'top'):
x, y = y, x
monitor_width, monitor_height = monitor_height, monitor_width
ratio_width, ratio_height = wacom_width / monitor_width, wacom_height / monitor_height
if mode == 'fit':
scaling = max(ratio_width, ratio_height)
elif mode == 'fill':
scaling = min(ratio_width, ratio_height)
else:
raise NotImplementedError
return (
scaling * (x - (monitor_width - wacom_width / scaling) / 2),
scaling * (y - (monitor_height - wacom_height / scaling) / 2)
)
# remap screen coordinates to touch coordinates
def remapTouch(x, y, touch_width, touch_height, monitor_width,
monitor_height, mode, orientation=None):
if orientation in ('left', 'right'):
x, y = y, x
monitor_width, monitor_height = monitor_height, monitor_width
ratio_width, ratio_height = touch_width / monitor_width, touch_height / monitor_height
if mode == 'fit':
scaling = max(ratio_width, ratio_height)
elif mode == 'fill':
scaling = min(ratio_width, ratio_height)
else:
raise NotImplementedError
return (
scaling * (x - (monitor_width - touch_width / scaling) / 2),
scaling * (y - (monitor_height - touch_height / scaling) / 2)
)
def pipe_device(args, remote_device, local_device):
"""
Pipe events from a remote device to a local device.
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)
# set orientation with xinput
orientation = {'left': 0, 'bottom': 1, 'top': 2, 'right': 3}[args.orientation]
result = subprocess.run(
'xinput --set-prop "reMarkable pen stylus" "Wacom Rotation" {}'.format(orientation),
capture_output=True,
shell=True
)
if result.returncode != 0:
log.warning("Error setting orientation: %s", result.stderr)
# set monitor to use
monitor = get_monitors()[args.monitor]
log.debug('Chose monitor: {}'.format(monitor))
result = subprocess.run(
'xinput --map-to-output "reMarkable pen stylus" {}'.format(monitor.name),
capture_output=True,
shell=True
)
if result.returncode != 0:
log.warning("Error setting monitor: %s", result.stderr)
# set stylus pressure
result = subprocess.run(
'xinput --set-prop "reMarkable pen stylus" "Wacom Pressure Threshold" {}'.format(args.threshold),
capture_output=True,
shell=True
)
if result.returncode != 0:
log.warning("Error setting pressure threshold: %s", result.stderr)
# set fitting mode
min_x, min_y = remap(
0, 0,
MAX_ABS_X, MAX_ABS_Y, monitor.width, monitor.height,
args.mode,
args.orientation
)
max_x, max_y = remap(
monitor.width, monitor.height,
MAX_ABS_X, MAX_ABS_Y, monitor.width, monitor.height,
args.mode,
args.orientation
)
log.debug("Wacom tablet area: {} {} {} {}".format(min_x, min_y, max_x, max_y))
result = subprocess.run(
'xinput --set-prop "reMarkable pen stylus" "Wacom Tablet Area" \
{} {} {} {}'.format(min_x, min_y, max_x, max_y),
capture_output=True,
shell=True
)
if result.returncode != 0:
log.warning("Error setting fit: %s", result.stderr)
# Set touch fitting mode
mt_min_x, mt_min_y = remapTouch(
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(
monitor.width, monitor.height,
MT_MAX_ABS_X, MT_MAX_ABS_Y, monitor.width, monitor.height,
args.mode,
args.orientation
)
log.debug("Multi-touch area: {} {} {} {}".format(mt_min_x, mt_min_y, mt_max_x, mt_max_y))
result = subprocess.run(
'xinput --set-prop "reMarkable pen touch" "Wacom Tablet Area" \
{} {} {} {}'.format(mt_min_x, mt_min_y, mt_max_x, mt_max_y),
capture_output=True,
shell=True
)
if result.returncode != 0:
log.warning("Error setting fit: %s", result.stderr)
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)
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
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]