mirror of
https://github.com/aidygus/LinVAM.git
synced 2024-11-27 11:08:06 +11:00
175 lines
5.9 KiB
Python
175 lines
5.9 KiB
Python
|
# -*- coding: utf-8 -*-
|
||
|
import struct
|
||
|
import os
|
||
|
import atexit
|
||
|
from time import time as now
|
||
|
from threading import Thread
|
||
|
from glob import glob
|
||
|
try:
|
||
|
from queue import Queue
|
||
|
except ImportError:
|
||
|
from Queue import Queue
|
||
|
|
||
|
event_bin_format = 'llHHI'
|
||
|
|
||
|
# Taken from include/linux/input.h
|
||
|
# https://www.kernel.org/doc/Documentation/input/event-codes.txt
|
||
|
EV_SYN = 0x00
|
||
|
EV_KEY = 0x01
|
||
|
EV_REL = 0x02
|
||
|
EV_ABS = 0x03
|
||
|
EV_MSC = 0x04
|
||
|
|
||
|
def make_uinput():
|
||
|
if not os.path.exists('/dev/uinput'):
|
||
|
raise IOError('No uinput module found.')
|
||
|
|
||
|
import fcntl, struct
|
||
|
|
||
|
# Requires uinput driver, but it's usually available.
|
||
|
uinput = open("/dev/uinput", 'wb')
|
||
|
UI_SET_EVBIT = 0x40045564
|
||
|
fcntl.ioctl(uinput, UI_SET_EVBIT, EV_KEY)
|
||
|
|
||
|
UI_SET_KEYBIT = 0x40045565
|
||
|
for i in range(256):
|
||
|
fcntl.ioctl(uinput, UI_SET_KEYBIT, i)
|
||
|
|
||
|
BUS_USB = 0x03
|
||
|
uinput_user_dev = "80sHHHHi64i64i64i64i"
|
||
|
axis = [0] * 64 * 4
|
||
|
uinput.write(struct.pack(uinput_user_dev, b"Virtual Keyboard", BUS_USB, 1, 1, 1, 0, *axis))
|
||
|
uinput.flush() # Without this you may get Errno 22: Invalid argument.
|
||
|
|
||
|
UI_DEV_CREATE = 0x5501
|
||
|
fcntl.ioctl(uinput, UI_DEV_CREATE)
|
||
|
UI_DEV_DESTROY = 0x5502
|
||
|
#fcntl.ioctl(uinput, UI_DEV_DESTROY)
|
||
|
|
||
|
return uinput
|
||
|
|
||
|
class EventDevice(object):
|
||
|
def __init__(self, path):
|
||
|
self.path = path
|
||
|
self._input_file = None
|
||
|
self._output_file = None
|
||
|
|
||
|
@property
|
||
|
def input_file(self):
|
||
|
if self._input_file is None:
|
||
|
try:
|
||
|
self._input_file = open(self.path, 'rb')
|
||
|
except IOError as e:
|
||
|
if e.strerror == 'Permission denied':
|
||
|
print('Permission denied ({}). You must be sudo to access global events.'.format(self.path))
|
||
|
exit()
|
||
|
|
||
|
def try_close():
|
||
|
try:
|
||
|
self._input_file.close
|
||
|
except:
|
||
|
pass
|
||
|
atexit.register(try_close)
|
||
|
return self._input_file
|
||
|
|
||
|
@property
|
||
|
def output_file(self):
|
||
|
if self._output_file is None:
|
||
|
self._output_file = open(self.path, 'wb')
|
||
|
atexit.register(self._output_file.close)
|
||
|
return self._output_file
|
||
|
|
||
|
def read_event(self):
|
||
|
data = self.input_file.read(struct.calcsize(event_bin_format))
|
||
|
seconds, microseconds, type, code, value = struct.unpack(event_bin_format, data)
|
||
|
return seconds + microseconds / 1e6, type, code, value, self.path
|
||
|
|
||
|
def write_event(self, type, code, value):
|
||
|
integer, fraction = divmod(now(), 1)
|
||
|
seconds = int(integer)
|
||
|
microseconds = int(fraction * 1e6)
|
||
|
data_event = struct.pack(event_bin_format, seconds, microseconds, type, code, value)
|
||
|
|
||
|
# Send a sync event to ensure other programs update.
|
||
|
sync_event = struct.pack(event_bin_format, seconds, microseconds, EV_SYN, 0, 0)
|
||
|
|
||
|
self.output_file.write(data_event + sync_event)
|
||
|
self.output_file.flush()
|
||
|
|
||
|
class AggregatedEventDevice(object):
|
||
|
def __init__(self, devices, output=None):
|
||
|
self.event_queue = Queue()
|
||
|
self.devices = devices
|
||
|
self.output = output or self.devices[0]
|
||
|
def start_reading(device):
|
||
|
while True:
|
||
|
self.event_queue.put(device.read_event())
|
||
|
for device in self.devices:
|
||
|
thread = Thread(target=start_reading, args=[device])
|
||
|
thread.setDaemon(True)
|
||
|
thread.start()
|
||
|
|
||
|
def read_event(self):
|
||
|
return self.event_queue.get(block=True)
|
||
|
|
||
|
def write_event(self, type, code, value):
|
||
|
self.output.write_event(type, code, value)
|
||
|
|
||
|
import re
|
||
|
from collections import namedtuple
|
||
|
DeviceDescription = namedtuple('DeviceDescription', 'event_file is_mouse is_keyboard')
|
||
|
device_pattern = r"""N: Name="([^"]+?)".+?H: Handlers=([^\n]+)"""
|
||
|
def list_devices_from_proc(type_name):
|
||
|
try:
|
||
|
with open('/proc/bus/input/devices') as f:
|
||
|
description = f.read()
|
||
|
except FileNotFoundError:
|
||
|
return
|
||
|
|
||
|
devices = {}
|
||
|
for name, handlers in re.findall(device_pattern, description, re.DOTALL):
|
||
|
path = '/dev/input/event' + re.search(r'event(\d+)', handlers).group(1)
|
||
|
if type_name in handlers:
|
||
|
yield EventDevice(path)
|
||
|
|
||
|
def list_devices_from_by_id(name_suffix, by_id=True):
|
||
|
for path in glob('/dev/input/{}/*-event-{}'.format('by-id' if by_id else 'by-path', name_suffix)):
|
||
|
yield EventDevice(path)
|
||
|
|
||
|
def aggregate_devices(type_name):
|
||
|
# Some systems have multiple keyboards with different range of allowed keys
|
||
|
# on each one, like a notebook with a "keyboard" device exclusive for the
|
||
|
# power button. Instead of figuring out which keyboard allows which key to
|
||
|
# send events, we create a fake device and send all events through there.
|
||
|
try:
|
||
|
uinput = make_uinput()
|
||
|
fake_device = EventDevice('uinput Fake Device')
|
||
|
fake_device._input_file = uinput
|
||
|
fake_device._output_file = uinput
|
||
|
except IOError as e:
|
||
|
import warnings
|
||
|
warnings.warn('Failed to create a device file using `uinput` module. Sending of events may be limited or unavailable depending on plugged-in devices.', stacklevel=2)
|
||
|
fake_device = None
|
||
|
|
||
|
# We don't aggregate devices from different sources to avoid
|
||
|
# duplicates.
|
||
|
|
||
|
devices_from_proc = list(list_devices_from_proc(type_name))
|
||
|
if devices_from_proc:
|
||
|
return AggregatedEventDevice(devices_from_proc, output=fake_device)
|
||
|
|
||
|
# breaks on mouse for virtualbox
|
||
|
# was getting /dev/input/by-id/usb-VirtualBox_USB_Tablet-event-mouse
|
||
|
devices_from_by_id = list(list_devices_from_by_id(type_name)) or list(list_devices_from_by_id(type_name, by_id=False))
|
||
|
if devices_from_by_id:
|
||
|
return AggregatedEventDevice(devices_from_by_id, output=fake_device)
|
||
|
|
||
|
# If no keyboards were found we can only use the fake device to send keys.
|
||
|
assert fake_device
|
||
|
return fake_device
|
||
|
|
||
|
|
||
|
def ensure_root():
|
||
|
if os.geteuid() != 0:
|
||
|
raise ImportError('You must be root to use this library on linux.')
|