''' Python mtdev ============ The mtdev module provides Python bindings to the `Kernel multi-touch transformation library `_, also known as mtdev (MIT license). The mtdev library transforms all variants of kernel MT events to the slotted type B protocol. The events put into mtdev may be from any MT device, specifically type A without contact tracking, type A with contact tracking, or type B with contact tracking. See the kernel documentation for further details. .. warning:: This is an external library and Kivy does not provide any support for it. It might change in the future and we advise you don't rely on it in your code. ''' # flake8: noqa import os import time from ctypes import cdll, Structure, c_ulong, c_int, c_ushort, \ c_void_p, pointer, POINTER, byref # load library if 'KIVY_DOC' not in os.environ: libmtdev = cdll.LoadLibrary('libmtdev.so.1') # from linux/input.h MTDEV_CODE_SLOT = 0x2f # MT slot being modified MTDEV_CODE_TOUCH_MAJOR = 0x30 # Major axis of touching ellipse MTDEV_CODE_TOUCH_MINOR = 0x31 # Minor axis (omit if circular) MTDEV_CODE_WIDTH_MAJOR = 0x32 # Major axis of approaching ellipse MTDEV_CODE_WIDTH_MINOR = 0x33 # Minor axis (omit if circular) MTDEV_CODE_ORIENTATION = 0x34 # Ellipse orientation MTDEV_CODE_POSITION_X = 0x35 # Center X ellipse position MTDEV_CODE_POSITION_Y = 0x36 # Center Y ellipse position MTDEV_CODE_TOOL_TYPE = 0x37 # Type of touching device MTDEV_CODE_BLOB_ID = 0x38 # Group a set of packets as a blob MTDEV_CODE_TRACKING_ID = 0x39 # Unique ID of initiated contact MTDEV_CODE_PRESSURE = 0x3a # Pressure on contact area MTDEV_CODE_ABS_X = 0x00 MTDEV_CODE_ABS_Y = 0x01 MTDEV_CODE_ABS_Z = 0x02 MTDEV_CODE_BTN_DIGI = 0x140 MTDEV_CODE_BTN_TOOL_PEN = 0x140 MTDEV_CODE_BTN_TOOL_RUBBER = 0x141 MTDEV_CODE_BTN_TOOL_BRUSH = 0x142 MTDEV_CODE_BTN_TOOL_PENCIL = 0x143 MTDEV_CODE_BTN_TOOL_AIRBRUSH = 0x144 MTDEV_CODE_BTN_TOOL_FINGER = 0x145 MTDEV_CODE_BTN_TOOL_MOUSE = 0x146 MTDEV_CODE_BTN_TOOL_LENS = 0x147 MTDEV_CODE_BTN_TOUCH = 0x14a MTDEV_CODE_BTN_STYLUS = 0x14b MTDEV_CODE_BTN_STYLUS2 = 0x14c MTDEV_CODE_BTN_TOOL_DOUBLETAP = 0x14d MTDEV_CODE_BTN_TOOL_TRIPLETAP = 0x14e MTDEV_CODE_BTN_TOOL_QUADTAP = 0x14f # Four fingers on trackpad MTDEV_TYPE_EV_ABS = 0x03 MTDEV_TYPE_EV_SYN = 0x00 MTDEV_TYPE_EV_KEY = 0x01 MTDEV_TYPE_EV_REL = 0x02 MTDEV_TYPE_EV_ABS = 0x03 MTDEV_TYPE_EV_MSC = 0x04 MTDEV_TYPE_EV_SW = 0x05 MTDEV_TYPE_EV_LED = 0x11 MTDEV_TYPE_EV_SND = 0x12 MTDEV_TYPE_EV_REP = 0x14 MTDEV_TYPE_EV_FF = 0x15 MTDEV_TYPE_EV_PWR = 0x16 MTDEV_TYPE_EV_FF_STATUS = 0x17 MTDEV_ABS_TRACKING_ID = 9 MTDEV_ABS_POSITION_X = 5 MTDEV_ABS_POSITION_Y = 6 MTDEV_ABS_TOUCH_MAJOR = 0 MTDEV_ABS_TOUCH_MINOR = 1 MTDEV_ABS_WIDTH_MAJOR = 2 MTDEV_ABS_WIDTH_MINOR = 3 MTDEV_ABS_ORIENTATION = 4 MTDEV_ABS_SIZE = 11 class timeval(Structure): _fields_ = [ ('tv_sec', c_ulong), ('tv_usec', c_ulong) ] class input_event(Structure): _fields_ = [ ('time', timeval), ('type', c_ushort), ('code', c_ushort), ('value', c_int) ] class input_absinfo(Structure): _fields_ = [ ('value', c_int), ('minimum', c_int), ('maximum', c_int), ('fuzz', c_int), ('flat', c_int), ('resolution', c_int) ] class mtdev_caps(Structure): _fields_ = [ ('has_mtdata', c_int), ('has_slot', c_int), ('has_abs', c_int * MTDEV_ABS_SIZE), ('slot', input_absinfo), ('abs', input_absinfo * MTDEV_ABS_SIZE) ] class mtdev(Structure): _fields_ = [ ('caps', mtdev_caps), ('state', c_void_p) ] # binding if 'KIVY_DOC' not in os.environ: mtdev_open = libmtdev.mtdev_open mtdev_open.argtypes = [POINTER(mtdev), c_int] mtdev_get = libmtdev.mtdev_get mtdev_get.argtypes = [POINTER(mtdev), c_int, POINTER(input_event), c_int] mtdev_idle = libmtdev.mtdev_idle mtdev_idle.argtypes = [POINTER(mtdev), c_int, c_int] mtdev_close = libmtdev.mtdev_close mtdev_close.argtypes = [POINTER(mtdev)] class Device: def __init__(self, filename): self._filename = filename self._fd = -1 self._device = mtdev() # Linux kernel creates input devices then hands permission changes # off to udev. This results in a period of time when the device is # readable only by root. Device reconnects can be processed by # MTDMotionEventProvider faster than udev can get a chance to run, # so we spin for a period of time to allow udev to fix permissions. # We limit the loop time in case the system is misconfigured and # the user really does not (and will not) have permission to access # the device. # Note: udev takes about 0.6 s on a Raspberry Pi 4 permission_wait_until = time.time() + 3.0 while self._fd == -1: try: self._fd = os.open(filename, os.O_NONBLOCK | os.O_RDONLY) except PermissionError: if time.time() > permission_wait_until: raise ret = mtdev_open(pointer(self._device), self._fd) if ret != 0: os.close(self._fd) self._fd = -1 raise Exception('Unable to open device') def close(self): '''Close the mtdev converter ''' if self._fd == -1: return mtdev_close(pointer(self._device)) os.close(self._fd) self._fd = -1 def idle(self, ms): '''Check state of kernel device :Parameters: `ms`: int Number of milliseconds to wait for activity :Return: Return True if the device is idle, i.e, there are no fetched events in the pipe and there is nothing to fetch from the device. ''' if self._fd == -1: raise Exception('Device closed') return bool(mtdev_idle(pointer(self._device), self._fd, ms)) def get(self): if self._fd == -1: raise Exception('Device closed') ev = input_event() if mtdev_get(pointer(self._device), self._fd, byref(ev), 1) <= 0: return None return ev def has_mtdata(self): '''Return True if the device has multitouch data. ''' if self._fd == -1: raise Exception('Device closed') return bool(self._device.caps.has_mtdata) def has_slot(self): '''Return True if the device has slot information. ''' if self._fd == -1: raise Exception('Device closed') return bool(self._device.caps.has_slot) def has_abs(self, index): '''Return True if the device has abs data. :Parameters: `index`: int One of const starting with a name ABS_MT_ ''' if self._fd == -1: raise Exception('Device closed') if index < 0 or index >= MTDEV_ABS_SIZE: raise IndexError('Invalid index') return bool(self._device.caps.has_abs[index]) def get_max_abs(self): '''Return the maximum number of abs information available. ''' return MTDEV_ABS_SIZE def get_slot(self): '''Return the slot data. ''' if self._fd == -1: raise Exception('Device closed') if self._device.caps.has_slot == 0: return return self._device.caps.slot def get_abs(self, index): '''Return the abs data. :Parameters: `index`: int One of const starting with a name ABS_MT_ ''' if self._fd == -1: raise Exception('Device closed') if index < 0 or index >= MTDEV_ABS_SIZE: raise IndexError('Invalid index') return self._device.caps.abs[index]