evan
5 years ago
4 changed files with 238 additions and 217 deletions
@ -0,0 +1,120 @@ |
|||||||
|
import logging |
||||||
|
import struct |
||||||
|
|
||||||
|
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 |
||||||
|
""" |
||||||
|
import libevdev |
||||||
|
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 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 |
||||||
|
""" |
||||||
|
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 = [] |
||||||
|
|
||||||
|
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] |
@ -0,0 +1,101 @@ |
|||||||
|
import logging |
||||||
|
import struct |
||||||
|
|
||||||
|
logging.basicConfig(format='%(message)s') |
||||||
|
log = logging.getLogger(__name__) |
||||||
|
|
||||||
|
# evtype_sync = 0 |
||||||
|
# evtype_key = 1 |
||||||
|
e_type_abs = 3 |
||||||
|
|
||||||
|
# evcode_stylus_distance = 25 |
||||||
|
# evcode_stylus_xtilt = 26 |
||||||
|
# evcode_stylus_ytilt = 27 |
||||||
|
e_code_stylus_xpos = 1 |
||||||
|
e_code_stylus_ypos = 0 |
||||||
|
e_code_stylus_pressure = 24 |
||||||
|
# evcode_finger_xpos = 53 |
||||||
|
# evcode_finger_ypos = 54 |
||||||
|
# evcode_finger_pressure = 58 |
||||||
|
|
||||||
|
stylus_width = 15725 |
||||||
|
stylus_height = 20951 |
||||||
|
# finger_width = 767 |
||||||
|
# finger_height = 1023 |
||||||
|
|
||||||
|
|
||||||
|
# remap wacom coordinates in various orientations |
||||||
|
def fit(x, y, stylus_width, stylus_height, monitor, orientation): |
||||||
|
|
||||||
|
if orientation == 'vertical': |
||||||
|
y = stylus_height - y |
||||||
|
elif orientation == 'right': |
||||||
|
x, y = y, x |
||||||
|
stylus_width, stylus_height = stylus_height, stylus_width |
||||||
|
elif orientation == 'left': |
||||||
|
x, y = stylus_height - y, stylus_width - x |
||||||
|
stylus_width, stylus_height = stylus_height, stylus_width |
||||||
|
|
||||||
|
ratio_width, ratio_height = monitor.width / stylus_width, monitor.height / stylus_height |
||||||
|
scaling = ratio_width if ratio_width > ratio_height else ratio_height |
||||||
|
|
||||||
|
return ( |
||||||
|
scaling * (x - (stylus_width - monitor.width / scaling) / 2), |
||||||
|
scaling * (y - (stylus_height - monitor.height / scaling) / 2) |
||||||
|
) |
||||||
|
|
||||||
|
|
||||||
|
def read_tablet(args, remote_device): |
||||||
|
"""Loop forever and map evdev events to mouse""" |
||||||
|
|
||||||
|
from screeninfo import get_monitors |
||||||
|
from pynput.mouse import Button, Controller |
||||||
|
|
||||||
|
lifted = True |
||||||
|
new_x = new_y = False |
||||||
|
|
||||||
|
mouse = Controller() |
||||||
|
|
||||||
|
monitor = get_monitors()[args.monitor] |
||||||
|
log.debug('Chose monitor: {}'.format(monitor)) |
||||||
|
|
||||||
|
while True: |
||||||
|
_, _, e_type, e_code, e_value = struct.unpack('2IHHi', remote_device.read(16)) |
||||||
|
|
||||||
|
if e_type == e_type_abs: |
||||||
|
|
||||||
|
# handle x direction |
||||||
|
if e_code == e_code_stylus_xpos: |
||||||
|
log.debug(e_value) |
||||||
|
x = e_value |
||||||
|
new_x = True |
||||||
|
|
||||||
|
# handle y direction |
||||||
|
if e_code == e_code_stylus_ypos: |
||||||
|
log.debug('\t{}'.format(e_value)) |
||||||
|
y = e_value |
||||||
|
new_y = True |
||||||
|
|
||||||
|
# handle draw |
||||||
|
if e_code == e_code_stylus_pressure: |
||||||
|
log.debug('\t\t{}'.format(e_value)) |
||||||
|
if e_value > args.threshold: |
||||||
|
if lifted: |
||||||
|
log.debug('PRESS') |
||||||
|
lifted = False |
||||||
|
mouse.press(Button.left) |
||||||
|
else: |
||||||
|
if not lifted: |
||||||
|
log.debug('RELEASE') |
||||||
|
lifted = True |
||||||
|
mouse.release(Button.left) |
||||||
|
|
||||||
|
|
||||||
|
# only move when x and y are updated for smoother mouse |
||||||
|
if new_x and new_y: |
||||||
|
mapped_x, mapped_y = fit(x, y, stylus_width, stylus_height, monitor, args.orientation) |
||||||
|
mouse.move( |
||||||
|
monitor.x + mapped_x - mouse.position[0], |
||||||
|
monitor.y + mapped_y - mouse.position[1] |
||||||
|
) |
||||||
|
new_x = new_y = False |
Loading…
Reference in new issue