Browse Source

simplify main and make pynput/evdev api consistent

master
evan 3 years ago
parent
commit
392c502c56
  1. 2
      README.md
  2. 322
      remarkable_mouse/evdev.py
  3. 23
      remarkable_mouse/pynput.py
  4. 154
      remarkable_mouse/remarkable_mouse.py

2
README.md

@ -15,7 +15,7 @@ pip install remarkable-mouse @@ -15,7 +15,7 @@ pip install remarkable-mouse
remouse
```
By default, `10.11.99.1` is used as the address. Seems to work pretty well wirelessly, too. By default ssh-agent is used to authenticate if it is available, otherwise you are asked for your password.
By default, `10.11.99.1` is used as the address. Seems to work pretty well wirelessly, too. By default ssh-agent is used to authenticate if it is available, otherwise you are asked for your password. Find your password in the reMarkable's [settings menu](https://remarkablewiki.com/tech/ssh).
# Examples

322
remarkable_mouse/evdev.py

@ -3,9 +3,11 @@ import struct @@ -3,9 +3,11 @@ import struct
import subprocess
from screeninfo import get_monitors
import time
from itertools import cycle
from socket import timeout as TimeoutError
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
@ -13,6 +15,11 @@ MAX_ABS_X = 20967 @@ -13,6 +15,11 @@ 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():
"""
@ -26,23 +33,70 @@ def create_local_device(): @@ -26,23 +33,70 @@ def create_local_device():
device = libevdev.Device()
# Set device properties to emulate those of Wacom tablets
device.name = 'reMarkable tablet'
device.name = 'reMarkable pen'
device.id = {
'bustype': 0x18, # i2c
'bustype': 0x03, # usb
'vendor': 0x056a, # wacom
'product': 0,
'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)
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 position, tilt, distance and pressure change events
# ----- Touch -----
# Enable Touch input
device.enable(
libevdev.EV_ABS.ABS_MT_POSITION_X,
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) # resolution correct?
)
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)
)
# ----- Pen -----
# Enable pen input, tilt and pressure
device.enable(
libevdev.EV_ABS.ABS_X,
libevdev.InputAbsInfo(
@ -61,38 +115,26 @@ def create_local_device(): @@ -61,38 +115,26 @@ def create_local_device():
)
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'):
@ -113,86 +155,175 @@ def remap(x, y, wacom_width, wacom_height, monitor_width, @@ -113,86 +155,175 @@ def remap(x, y, wacom_width, wacom_height, monitor_width,
scaling * (y - (monitor_height - wacom_height / scaling) / 2)
)
def pipe_device(args, remote_device, local_device):
# 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'):
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 configure_xinput(*, orientation, monitor, threshold, mode):
"""
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
orientation (str): location of charging port relative to screen
monitor (int): monitor number to use
threshold (int): pressure threshold for detecting pen
mode (str): scaling mode when mapping reMarkable pen coordinates to screen
"""
# 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]
orientation = {'left': 0, 'bottom': 1, 'top': 2, 'right': 3}[orientation]
result = subprocess.run(
'xinput --set-prop "reMarkable tablet stylus" "Wacom Rotation" {}'.format(orientation),
'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)
log.warning("Error setting orientation: %s", result.stderr.decode('utf8'))
# set monitor to use
monitor = get_monitors()[args.monitor]
monitor = get_monitors()[monitor]
log.debug('Chose monitor: {}'.format(monitor))
result = subprocess.run(
'xinput --map-to-output "reMarkable tablet stylus" {}'.format(monitor.name),
'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)
log.warning("Error setting monitor: %s", result.stderr.decode('utf8'))
# set stylus pressure
result = subprocess.run(
'xinput --set-prop "reMarkable tablet stylus" "Wacom Pressure Threshold" {}'.format(args.threshold),
'xinput --set-prop "reMarkable pen stylus" "Wacom Pressure Threshold" {}'.format(threshold),
capture_output=True,
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
mode,
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,
args.orientation
mode,
orientation
)
log.debug("Wacom tablet area: {} {} {} {}".format(min_x, min_y, max_x, max_y))
result = subprocess.run(
'xinput --set-prop "reMarkable tablet stylus" "Wacom Tablet Area" \
'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)
log.warning("Error setting fit: %s", result.stderr.decode('utf8'))
# ----- Touch -----
# Set touch fitting mode
mt_min_x, mt_min_y = map_comp2touch(
0, 0,
MT_MAX_ABS_X, MT_MAX_ABS_Y, monitor.width, monitor.height,
mode,
orientation
)
mt_max_x, mt_max_y = map_comp2touch(
monitor.width, monitor.height,
MT_MAX_ABS_X, MT_MAX_ABS_Y, monitor.width, monitor.height,
mode,
orientation
)
log.debug("Multi-touch area: {} {} {} {}".format(mt_min_x, mt_min_y, mt_max_x * 2, mt_max_y * 2))
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.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.decode('utf8'))
def read_tablet(rm_inputs, *, orientation, monitor, threshold, mode):
"""Pipe rM evdev events to local device
Args:
rm_inputs (dictionary of paramiko.ChannelFile): dict of pen, button
and touch input streams
orientation (str): tablet orientation
monitor (int): monitor number to map to
threshold (int): pressure threshold
mode (str): mapping mode
"""
local_device = create_local_device()
log.debug("Created virtual input device '{}'".format(local_device.devnode))
configure_xinput(
orientation=orientation,
monitor=monitor,
threshold=threshold,
mode=mode,
)
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 = []
# loop inputs forever
# for input_name, stream in cycle(rm_inputs.items()):
stream = rm_inputs['pen']
while True:
e_time, e_millis, e_type, e_code, e_value = struct.unpack('2IHHi', remote_device.read(16))
try:
data = stream.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:
# While debug mode is active, we log events grouped together between
# SYN_REPORT events. Pending events for the next log are stored here
if log.level == logging.DEBUG:
if e_bit == libevdev.EV_SYN.SYN_REPORT:
event_repr = ', '.join(
'{} = {}'.format(
@ -203,4 +334,99 @@ def pipe_device(args, remote_device, local_device): @@ -203,4 +334,99 @@ def pipe_device(args, remote_device, local_device):
log.debug('{}.{:0>6} - {}'.format(e_time, e_millis, event_repr))
pending_events = []
else:
pending_events += [event]
pending_events.append(event)
def create_handler(*, orientation, monitor, threshold, mode):
import libevdev
try:
local_device = create_local_device()
log.info("Created virtual input device '{}'".format(local_device.devnode))
except PermissionError:
log.error('Insufficient permissions for creating a virtual input device')
log.error('Make sure you run this program as root')
sys.exit(1)
configure_xinput(
orientation=orientation,
monitor=monitor,
threshold=threshold,
mode=mode,
)
pending_events = []
def handler(e_time, e_millis, e_type, e_code, e_value):
e_bit = libevdev.evbit(e_type, e_code)
event = libevdev.InputEvent(e_bit, value=e_value)
local_device.send_events([event])
# While debug mode is active, we log events grouped together between
# SYN_REPORT events. Pending events for the next log are stored here
# if log.level == logging.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)
return handler
# 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]

23
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
@ -57,8 +57,17 @@ def remap(x, y, wacom_width, wacom_height, monitor_width, @@ -57,8 +57,17 @@ def remap(x, y, wacom_width, wacom_height, monitor_width,
)
def read_tablet(args, remote_device):
"""Loop forever and map evdev events to mouse"""
def read_tablet(rm_inputs, *, orientation, monitor, threshold, mode):
"""Loop forever and map evdev events to mouse
Args:
rm_inputs (dictionary of paramiko.ChannelFile): dict of pen, button
and touch input streams
orientation (str): tablet orientation
monitor (int): monitor number to map to
threshold (int): pressure threshold
mode (str): mapping mode
"""
from pynput.mouse import Button, Controller
@ -67,11 +76,11 @@ def read_tablet(args, remote_device): @@ -67,11 +76,11 @@ def read_tablet(args, remote_device):
mouse = Controller()
monitor = get_monitors()[args.monitor]
monitor = get_monitors()[monitor]
log.debug('Chose monitor: {}'.format(monitor))
while True:
_, _, e_type, e_code, e_value = struct.unpack('2IHHi', remote_device.read(16))
_, _, e_type, e_code, e_value = struct.unpack('2IHHi', rm_inputs['pen'].read(16))
if e_type == e_type_abs:
@ -90,7 +99,7 @@ def read_tablet(args, remote_device): @@ -90,7 +99,7 @@ def read_tablet(args, remote_device):
# handle draw
if e_code == e_code_stylus_pressure:
log.debug('\t\t{}'.format(e_value))
if e_value > args.threshold:
if e_value > threshold:
if lifted:
log.debug('PRESS')
lifted = False
@ -108,7 +117,7 @@ def read_tablet(args, remote_device): @@ -108,7 +117,7 @@ def read_tablet(args, remote_device):
x, y,
wacom_width, wacom_height,
monitor.width, monitor.height,
args.mode, args.orientation
mode, orientation
)
mouse.move(
monitor.x + mapped_x - mouse.position[0],

154
remarkable_mouse/remarkable_mouse.py

@ -7,25 +7,31 @@ import os @@ -7,25 +7,31 @@ import os
import sys
import struct
from getpass import getpass
from itertools import cycle
import paramiko
import paramiko.agent
logging.basicConfig(format='%(message)s')
log = logging.getLogger(__name__)
log = logging.getLogger('remouse')
default_key = os.path.expanduser('~/.ssh/remarkable')
def open_remote_device(args, file='/dev/input/event0'):
def open_rm_inputs(*, address, key, password):
"""
Open a remote input device via SSH.
Args:
args: argparse arguments
file (str): path to the input device on the device
address (str): address to reMarkable
key (str, optional): path to reMarkable ssh key
password (str, optional): reMarkable ssh password
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(address))
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
@ -35,29 +41,35 @@ def open_remote_device(args, file='/dev/input/event0'): @@ -35,29 +41,35 @@ def open_remote_device(args, file='/dev/input/event0'):
agent = paramiko.agent.Agent()
if args.key is not None:
password = None
def use_key(key):
try:
pkey = paramiko.RSAKey.from_private_key_file(os.path.expanduser(args.key))
pkey = paramiko.RSAKey.from_private_key_file(os.path.expanduser(key))
except paramiko.ssh_exception.PasswordRequiredException:
passphrase = getpass(
"Enter passphrase for key '{}': ".format(os.path.expanduser(args.key))
"Enter passphrase for key '{}': ".format(os.path.expanduser(key))
)
pkey = paramiko.RSAKey.from_private_key_file(
os.path.expanduser(args.key),
os.path.expanduser(key),
password=passphrase
)
elif args.password:
password = args.password
return pkey
if key is not None:
password = None
pkey = use_key(key)
elif os.path.exists(default_key):
password = None
pkey = use_key(default_key)
elif password:
pkey = None
elif not agent.get_keys():
password = getpass(
"Password for '{}': ".format(args.address)
"Password for '{}': ".format(address)
)
pkey = None
client.connect(
args.address,
address,
username='root',
password=password,
pkey=pkey,
@ -68,12 +80,33 @@ def open_remote_device(args, file='/dev/input/event0'): @@ -68,12 +80,33 @@ def open_remote_device(args, file='/dev/input/event0'):
paramiko.agent.AgentRequestHandler(session)
# Start reading events
_, stdout, _ = client.exec_command('cat ' + file)
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'
print("connected to", args.address)
log.debug('Pen:{}\nTouch:{}\nButton:{}'.format(pen_file, touch_file, button_file))
return stdout
# Start reading events
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)
return {'pen': pen, 'touch': touch, 'button': button}
def main():
@ -84,39 +117,90 @@ def main(): @@ -84,39 +117,90 @@ def main():
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")
parser.add_argument('--mode', default='fill', choices=['fit', 'fill'], help="scale setting")
parser.add_argument('--orientation', default='right', choices=['top', 'left', 'right', 'bottom'], help="position of tablet buttons")
parser.add_argument('--orientation', default='right', choices=['top', 'left', 'right', 'bottom'], help="position of charging port")
parser.add_argument('--monitor', default=0, type=int, metavar='NUM', help="monitor to output to")
parser.add_argument('--threshold', metavar='THRESH', default=600, type=int, help="stylus pressure threshold (default 600)")
parser.add_argument('--evdev', action='store_true', default=False, help="use evdev to support pen pressure (requires root, Linux only)")
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)
if args.evdev:
from remarkable_mouse.evdev import create_local_device, pipe_device
# ----- Connect to device -----
try:
local_device = create_local_device()
log.info("Created virtual input device '{}'".format(local_device.devnode))
except PermissionError:
log.error('Insufficient permissions for creating a virtual input device')
log.error('Make sure you run this program as root')
sys.exit(1)
rm_inputs = open_rm_inputs(
address=args.address,
key=args.key,
password=args.password,
)
print("Connected to", args.address)
pipe_device(args, remote_device, local_device)
# ----- Setup event handlers -----
if args.evdev:
# from remarkable_mouse.evdev import create_local_device, configure_xinput, read_tablet
# try:
# local_device = create_local_device()
# log.info("Created virtual input device '{}'".format(local_device.devnode))
# except PermissionError:
# log.error('Insufficient permissions for creating a virtual input device')
# log.error('Make sure you run this program as root')
# sys.exit(1)
# configure_xinput(
# orientation=args.orientation,
# monitor=args.monitor,
# threshold=args.threshold,
# mode=args.mode,
# )
# read_tablet(rm_inputs, local_device)
#
# from remarkable_mouse.evdev import create_handler
from remarkable_mouse.evdev import read_tablet
else:
from remarkable_mouse.pynput import read_tablet
read_tablet(args, remote_device)
read_tablet(
rm_inputs,
orientation=args.orientation,
monitor=args.monitor,
threshold=args.threshold,
mode=args.mode,
)
# from remarkable_mouse.pynput import create_handler
# event_handler = create_handler(
# orientation=args.orientation,
# monitor=args.monitor,
# threshold=args.threshold,
# mode=args.mode,
# )
# ----- Handle events -----
# loop inputs forever
# import socket
# for name, stream in cycle(rm_inputs.items()):
# try:
# data = stream.read(16)
# except socket.timeout:
# continue
# e_time, e_millis, e_type, e_code, e_value = struct.unpack('2IHHi', data)
# event_handler(e_time, e_millis, e_type, e_code, e_value)
except PermissionError:
log.error('Insufficient permissions for creating a virtual input device')
log.error('Make sure you run this program as root')
sys.exit(1)
except KeyboardInterrupt:
pass
except EOFError:

Loading…
Cancel
Save