255 lines
7.9 KiB
Python
255 lines
7.9 KiB
Python
'''
|
|
Python mtdev
|
|
============
|
|
|
|
The mtdev module provides Python bindings to the `Kernel multi-touch
|
|
transformation library <https://launchpad.net/mtdev>`_, 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]
|
|
|
|
|