779 lines
28 KiB
Python
779 lines
28 KiB
Python
|
# coding utf-8
|
||
|
'''
|
||
|
Native support for HID input from the linux kernel
|
||
|
==================================================
|
||
|
|
||
|
Support starts from 2.6.32-ubuntu, or 2.6.34.
|
||
|
|
||
|
To configure HIDInput, add this to your configuration::
|
||
|
|
||
|
[input]
|
||
|
# devicename = hidinput,/dev/input/eventXX
|
||
|
# example with Stantum MTP4.3" screen
|
||
|
stantum = hidinput,/dev/input/event2
|
||
|
|
||
|
.. note::
|
||
|
You must have read access to the input event.
|
||
|
|
||
|
You can use a custom range for the X, Y and pressure values.
|
||
|
For some drivers, the range reported is invalid.
|
||
|
To fix that, you can add these options to the argument line:
|
||
|
|
||
|
* invert_x : 1 to invert X axis
|
||
|
* invert_y : 1 to invert Y axis
|
||
|
* min_position_x : X relative minimum
|
||
|
* max_position_x : X relative maximum
|
||
|
* min_position_y : Y relative minimum
|
||
|
* max_position_y : Y relative maximum
|
||
|
* min_abs_x : X absolute minimum
|
||
|
* min_abs_y : Y absolute minimum
|
||
|
* max_abs_x : X absolute maximum
|
||
|
* max_abs_y : Y absolute maximum
|
||
|
* min_pressure : pressure minimum
|
||
|
* max_pressure : pressure maximum
|
||
|
* rotation : rotate the input coordinate (0, 90, 180, 270)
|
||
|
|
||
|
For example, on the Asus T101M, the touchscreen reports a range from 0-4095 for
|
||
|
the X and Y values, but the real values are in a range from 0-32768. To correct
|
||
|
this, you can add the following to the configuration::
|
||
|
|
||
|
[input]
|
||
|
t101m = hidinput,/dev/input/event7,max_position_x=32768,\
|
||
|
max_position_y=32768
|
||
|
|
||
|
.. versionadded:: 1.9.1
|
||
|
|
||
|
`rotation` configuration token added.
|
||
|
|
||
|
'''
|
||
|
import os
|
||
|
from kivy.input.motionevent import MotionEvent
|
||
|
from kivy.input.shape import ShapeRect
|
||
|
|
||
|
__all__ = ('HIDInputMotionEventProvider', 'HIDMotionEvent')
|
||
|
|
||
|
# late imports
|
||
|
Window = None
|
||
|
Keyboard = None
|
||
|
|
||
|
|
||
|
class HIDMotionEvent(MotionEvent):
|
||
|
|
||
|
def __init__(self, *args, **kwargs):
|
||
|
kwargs.setdefault('is_touch', True)
|
||
|
kwargs.setdefault('type_id', 'touch')
|
||
|
super().__init__(*args, **kwargs)
|
||
|
|
||
|
def depack(self, args):
|
||
|
self.sx = args['x']
|
||
|
self.sy = args['y']
|
||
|
self.profile = ['pos']
|
||
|
if 'size_w' in args and 'size_h' in args:
|
||
|
self.shape = ShapeRect()
|
||
|
self.shape.width = args['size_w']
|
||
|
self.shape.height = args['size_h']
|
||
|
self.profile.append('shape')
|
||
|
if 'pressure' in args:
|
||
|
self.pressure = args['pressure']
|
||
|
self.profile.append('pressure')
|
||
|
if 'button' in args:
|
||
|
self.button = args['button']
|
||
|
self.profile.append('button')
|
||
|
super().depack(args)
|
||
|
|
||
|
def __str__(self):
|
||
|
return '<HIDMotionEvent id=%d pos=(%f, %f) device=%s>' \
|
||
|
% (self.id, self.sx, self.sy, self.device)
|
||
|
|
||
|
|
||
|
if 'KIVY_DOC' in os.environ:
|
||
|
# documentation hack
|
||
|
HIDInputMotionEventProvider = None
|
||
|
|
||
|
else:
|
||
|
import threading
|
||
|
import collections
|
||
|
import struct
|
||
|
import fcntl
|
||
|
from kivy.input.provider import MotionEventProvider
|
||
|
from kivy.input.factory import MotionEventFactory
|
||
|
from kivy.logger import Logger
|
||
|
|
||
|
#
|
||
|
# This part is taken from linux-source-2.6.32/include/linux/input.h
|
||
|
#
|
||
|
|
||
|
# Event types
|
||
|
EV_SYN = 0x00
|
||
|
EV_KEY = 0x01
|
||
|
EV_REL = 0x02
|
||
|
EV_ABS = 0x03
|
||
|
EV_MSC = 0x04
|
||
|
EV_SW = 0x05
|
||
|
EV_LED = 0x11
|
||
|
EV_SND = 0x12
|
||
|
EV_REP = 0x14
|
||
|
EV_FF = 0x15
|
||
|
EV_PWR = 0x16
|
||
|
EV_FF_STATUS = 0x17
|
||
|
EV_MAX = 0x1f
|
||
|
EV_CNT = (EV_MAX + 1)
|
||
|
|
||
|
KEY_MAX = 0x2ff
|
||
|
|
||
|
# Synchronization events
|
||
|
SYN_REPORT = 0
|
||
|
SYN_CONFIG = 1
|
||
|
SYN_MT_REPORT = 2
|
||
|
|
||
|
# Misc events
|
||
|
MSC_SERIAL = 0x00
|
||
|
MSC_PULSELED = 0x01
|
||
|
MSC_GESTURE = 0x02
|
||
|
MSC_RAW = 0x03
|
||
|
MSC_SCAN = 0x04
|
||
|
MSC_MAX = 0x07
|
||
|
MSC_CNT = (MSC_MAX + 1)
|
||
|
|
||
|
ABS_X = 0x00
|
||
|
ABS_Y = 0x01
|
||
|
ABS_PRESSURE = 0x18
|
||
|
ABS_MT_TOUCH_MAJOR = 0x30 # Major axis of touching ellipse
|
||
|
ABS_MT_TOUCH_MINOR = 0x31 # Minor axis (omit if circular)
|
||
|
ABS_MT_WIDTH_MAJOR = 0x32 # Major axis of approaching ellipse
|
||
|
ABS_MT_WIDTH_MINOR = 0x33 # Minor axis (omit if circular)
|
||
|
ABS_MT_ORIENTATION = 0x34 # Ellipse orientation
|
||
|
ABS_MT_POSITION_X = 0x35 # Center X ellipse position
|
||
|
ABS_MT_POSITION_Y = 0x36 # Center Y ellipse position
|
||
|
ABS_MT_TOOL_TYPE = 0x37 # Type of touching device
|
||
|
ABS_MT_BLOB_ID = 0x38 # Group a set of packets as a blob
|
||
|
ABS_MT_TRACKING_ID = 0x39 # Unique ID of initiated contact
|
||
|
ABS_MT_PRESSURE = 0x3a # Pressure on contact area
|
||
|
|
||
|
# some ioctl base (with 0 value)
|
||
|
EVIOCGNAME = 2147501318
|
||
|
EVIOCGBIT = 2147501344
|
||
|
EVIOCGABS = 2149074240
|
||
|
|
||
|
keyboard_keys = {
|
||
|
0x29: ('`', '~'),
|
||
|
0x02: ('1', '!'),
|
||
|
0x03: ('2', '@'),
|
||
|
0x04: ('3', '#'),
|
||
|
0x05: ('4', '$'),
|
||
|
0x06: ('5', '%'),
|
||
|
0x07: ('6', '^'),
|
||
|
0x08: ('7', '&'),
|
||
|
0x09: ('8', '*'),
|
||
|
0x0a: ('9', '('),
|
||
|
0x0b: ('0', ')'),
|
||
|
0x0c: ('-', '_'),
|
||
|
0x0d: ('=', '+'),
|
||
|
0x0e: ('backspace', ),
|
||
|
0x0f: ('tab', ),
|
||
|
0x10: ('q', 'Q'),
|
||
|
0x11: ('w', 'W'),
|
||
|
0x12: ('e', 'E'),
|
||
|
0x13: ('r', 'R'),
|
||
|
0x14: ('t', 'T'),
|
||
|
0x15: ('y', 'Y'),
|
||
|
0x16: ('u', 'U'),
|
||
|
0x17: ('i', 'I'),
|
||
|
0x18: ('o', 'O'),
|
||
|
0x19: ('p', 'P'),
|
||
|
0x1a: ('[', '{'),
|
||
|
0x1b: (']', '}'),
|
||
|
0x2b: ('\\', '|'),
|
||
|
0x3a: ('capslock', ),
|
||
|
0x1e: ('a', 'A'),
|
||
|
0x1f: ('s', 'S'),
|
||
|
0x20: ('d', 'D'),
|
||
|
0x21: ('f', 'F'),
|
||
|
0x22: ('g', 'G'),
|
||
|
0x23: ('h', 'H'),
|
||
|
0x24: ('j', 'J'),
|
||
|
0x25: ('k', 'K'),
|
||
|
0x26: ('l', 'L'),
|
||
|
0x27: (';', ':'),
|
||
|
0x28: ("'", '"'),
|
||
|
0xff: ('non-US-1', ),
|
||
|
0x1c: ('enter', ),
|
||
|
0x2a: ('shift', ),
|
||
|
0x2c: ('z', 'Z'),
|
||
|
0x2d: ('x', 'X'),
|
||
|
0x2e: ('c', 'C'),
|
||
|
0x2f: ('v', 'V'),
|
||
|
0x30: ('b', 'B'),
|
||
|
0x31: ('n', 'N'),
|
||
|
0x32: ('m', 'M'),
|
||
|
0x33: (',', '<'),
|
||
|
0x34: ('.', '>'),
|
||
|
0x35: ('/', '?'),
|
||
|
0x36: ('shift', ),
|
||
|
0x56: ('pipe', ),
|
||
|
0x1d: ('lctrl', ),
|
||
|
0x7D: ('super', ),
|
||
|
0x38: ('alt', ),
|
||
|
0x39: ('spacebar', ),
|
||
|
0x64: ('alt-gr', ),
|
||
|
0x7e: ('super', ),
|
||
|
0x7f: ('compose', ),
|
||
|
0x61: ('rctrl', ),
|
||
|
0x45: ('numlock', ),
|
||
|
0x47: ('numpad7', 'home'),
|
||
|
0x4b: ('numpad4', 'left'),
|
||
|
0x4f: ('numpad1', 'end'),
|
||
|
0x48: ('numpad8', 'up'),
|
||
|
0x4c: ('numpad5', ),
|
||
|
0x50: ('numpad2', 'down'),
|
||
|
0x52: ('numpad0', 'insert'),
|
||
|
0x37: ('numpadmul', ),
|
||
|
0x62: ('numpaddivide', ),
|
||
|
0x49: ('numpad9', 'pageup'),
|
||
|
0x4d: ('numpad6', 'right'),
|
||
|
0x51: ('numpad3', 'pagedown'),
|
||
|
0x53: ('numpaddecimal', 'delete'),
|
||
|
0x4a: ('numpadsubstract', ),
|
||
|
0x4e: ('numpadadd', ),
|
||
|
0x60: ('numpadenter', ),
|
||
|
0x01: ('escape', ),
|
||
|
0x3b: ('f1', ),
|
||
|
0x3c: ('f2', ),
|
||
|
0x3d: ('f3', ),
|
||
|
0x3e: ('f4', ),
|
||
|
0x3f: ('f5', ),
|
||
|
0x40: ('f6', ),
|
||
|
0x41: ('f7', ),
|
||
|
0x42: ('f8', ),
|
||
|
0x43: ('f9', ),
|
||
|
0x44: ('f10', ),
|
||
|
0x57: ('f11', ),
|
||
|
0x58: ('f12', ),
|
||
|
0x54: ('Alt+SysRq', ),
|
||
|
0x46: ('Screenlock', ),
|
||
|
0x67: ('up', ),
|
||
|
0x6c: ('down', ),
|
||
|
0x69: ('left', ),
|
||
|
0x6a: ('right', ),
|
||
|
0x6e: ('insert', ),
|
||
|
0x6f: ('delete', ),
|
||
|
0x66: ('home', ),
|
||
|
0x6b: ('end', ),
|
||
|
0x68: ('pageup', ),
|
||
|
0x6d: ('pagedown', ),
|
||
|
0x63: ('print', ),
|
||
|
0x77: ('pause', ),
|
||
|
|
||
|
|
||
|
# TODO combinations
|
||
|
# e0-37 PrtScr
|
||
|
# e0-46 Ctrl+Break
|
||
|
# e0-5b LWin (USB: LGUI)
|
||
|
# e0-5c RWin (USB: RGUI)
|
||
|
# e0-5d Menu
|
||
|
# e0-5f Sleep
|
||
|
# e0-5e Power
|
||
|
# e0-63 Wake
|
||
|
# e0-38 RAlt
|
||
|
# e0-1d RCtrl
|
||
|
# e0-52 Insert
|
||
|
# e0-53 Delete
|
||
|
# e0-47 Home
|
||
|
# e0-4f End
|
||
|
# e0-49 PgUp
|
||
|
# e0-51 PgDn
|
||
|
# e0-4b Left
|
||
|
# e0-48 Up
|
||
|
# e0-50 Down
|
||
|
# e0-4d Right
|
||
|
# e0-35 KP-/
|
||
|
# e0-1c KP-Enter
|
||
|
# e1-1d-45 77 Pause
|
||
|
}
|
||
|
|
||
|
keys_str = {
|
||
|
'spacebar': ' ',
|
||
|
'tab': ' ',
|
||
|
'shift': '',
|
||
|
'alt': '',
|
||
|
'ctrl': '',
|
||
|
'escape': '',
|
||
|
'numpad1': '1',
|
||
|
'numpad2': '2',
|
||
|
'numpad3': '3',
|
||
|
'numpad4': '4',
|
||
|
'numpad5': '5',
|
||
|
'numpad6': '6',
|
||
|
'numpad7': '7',
|
||
|
'numpad8': '8',
|
||
|
'numpad9': '9',
|
||
|
'numpad0': '0',
|
||
|
'numpadmul': '*',
|
||
|
'numpaddivide': '/',
|
||
|
'numpadadd': '+',
|
||
|
'numpaddecimal': '.',
|
||
|
'numpadsubstract': '-',
|
||
|
}
|
||
|
|
||
|
# sizeof(struct input_event)
|
||
|
struct_input_event_sz = struct.calcsize('LLHHi')
|
||
|
struct_input_absinfo_sz = struct.calcsize('iiiiii')
|
||
|
sz_l = struct.calcsize('Q')
|
||
|
|
||
|
class HIDInputMotionEventProvider(MotionEventProvider):
|
||
|
|
||
|
options = ('min_position_x', 'max_position_x',
|
||
|
'min_position_y', 'max_position_y',
|
||
|
'min_pressure', 'max_pressure',
|
||
|
'min_abs_x', 'max_abs_x',
|
||
|
'min_abs_y', 'max_abs_y',
|
||
|
'invert_x', 'invert_y', 'rotation')
|
||
|
|
||
|
def __init__(self, device, args):
|
||
|
super(HIDInputMotionEventProvider, self).__init__(device, args)
|
||
|
global Window, Keyboard
|
||
|
|
||
|
if Window is None:
|
||
|
from kivy.core.window import Window
|
||
|
if Keyboard is None:
|
||
|
from kivy.core.window import Keyboard
|
||
|
|
||
|
self.input_fn = None
|
||
|
self.default_ranges = dict()
|
||
|
|
||
|
# split arguments
|
||
|
args = args.split(',')
|
||
|
if not args:
|
||
|
Logger.error('HIDInput: Filename missing in configuration')
|
||
|
Logger.error('HIDInput: Use /dev/input/event0 for example')
|
||
|
return None
|
||
|
|
||
|
# read filename
|
||
|
self.input_fn = args[0]
|
||
|
Logger.info('HIDInput: Read event from <%s>' % self.input_fn)
|
||
|
|
||
|
# read parameters
|
||
|
for arg in args[1:]:
|
||
|
if arg == '':
|
||
|
continue
|
||
|
arg = arg.split('=')
|
||
|
|
||
|
# ensure it's a key = value
|
||
|
if len(arg) != 2:
|
||
|
Logger.error('HIDInput: invalid parameter '
|
||
|
'%s, not in key=value format.' % arg)
|
||
|
continue
|
||
|
|
||
|
# ensure the key exist
|
||
|
key, value = arg
|
||
|
if key not in HIDInputMotionEventProvider.options:
|
||
|
Logger.error('HIDInput: unknown %s option' % key)
|
||
|
continue
|
||
|
|
||
|
# ensure the value
|
||
|
try:
|
||
|
self.default_ranges[key] = int(value)
|
||
|
except ValueError:
|
||
|
err = 'HIDInput: invalid value "%s" for "%s"' % (
|
||
|
key, value)
|
||
|
Logger.error(err)
|
||
|
continue
|
||
|
|
||
|
# all good!
|
||
|
Logger.info('HIDInput: Set custom %s to %d' % (
|
||
|
key, int(value)))
|
||
|
|
||
|
if 'rotation' not in self.default_ranges:
|
||
|
self.default_ranges['rotation'] = 0
|
||
|
elif self.default_ranges['rotation'] not in (0, 90, 180, 270):
|
||
|
Logger.error('HIDInput: invalid rotation value ({})'.format(
|
||
|
self.default_ranges['rotation']))
|
||
|
self.default_ranges['rotation'] = 0
|
||
|
|
||
|
def start(self):
|
||
|
if self.input_fn is None:
|
||
|
return
|
||
|
self.uid = 0
|
||
|
self.queue = collections.deque()
|
||
|
self.dispatch_queue = []
|
||
|
self.thread = threading.Thread(
|
||
|
name=self.__class__.__name__,
|
||
|
target=self._thread_run,
|
||
|
kwargs=dict(
|
||
|
queue=self.queue,
|
||
|
input_fn=self.input_fn,
|
||
|
device=self.device,
|
||
|
default_ranges=self.default_ranges))
|
||
|
self.thread.daemon = True
|
||
|
self.thread.start()
|
||
|
|
||
|
def _thread_run(self, **kwargs):
|
||
|
input_fn = kwargs.get('input_fn')
|
||
|
queue = self.queue
|
||
|
dispatch_queue = self.dispatch_queue
|
||
|
device = kwargs.get('device')
|
||
|
drs = kwargs.get('default_ranges').get
|
||
|
touches = {}
|
||
|
touches_sent = []
|
||
|
point = {}
|
||
|
l_points = []
|
||
|
|
||
|
# prepare some vars to get limit of some component
|
||
|
range_min_position_x = 0
|
||
|
range_max_position_x = 2048
|
||
|
range_min_position_y = 0
|
||
|
range_max_position_y = 2048
|
||
|
range_min_pressure = 0
|
||
|
range_max_pressure = 255
|
||
|
range_min_abs_x = 0
|
||
|
range_max_abs_x = 255
|
||
|
range_min_abs_y = 0
|
||
|
range_max_abs_y = 255
|
||
|
range_min_abs_pressure = 0
|
||
|
range_max_abs_pressure = 255
|
||
|
invert_x = int(bool(drs('invert_x', 0)))
|
||
|
invert_y = int(bool(drs('invert_y', 1)))
|
||
|
rotation = drs('rotation', 0)
|
||
|
|
||
|
def assign_coord(point, value, invert, coords):
|
||
|
cx, cy = coords
|
||
|
if invert:
|
||
|
value = 1. - value
|
||
|
if rotation == 0:
|
||
|
point[cx] = value
|
||
|
elif rotation == 90:
|
||
|
point[cy] = value
|
||
|
elif rotation == 180:
|
||
|
point[cx] = 1. - value
|
||
|
elif rotation == 270:
|
||
|
point[cy] = 1. - value
|
||
|
|
||
|
def assign_rel_coord(point, value, invert, coords):
|
||
|
cx, cy = coords
|
||
|
if invert:
|
||
|
value = -1 * value
|
||
|
if rotation == 0:
|
||
|
point[cx] += value
|
||
|
elif rotation == 90:
|
||
|
point[cy] += value
|
||
|
elif rotation == 180:
|
||
|
point[cx] += -value
|
||
|
elif rotation == 270:
|
||
|
point[cy] += -value
|
||
|
|
||
|
# limit it to the screen area 0-1
|
||
|
point['x'] = min(1., max(0., point['x']))
|
||
|
point['y'] = min(1., max(0., point['y']))
|
||
|
|
||
|
def process_as_multitouch(tv_sec, tv_usec, ev_type,
|
||
|
ev_code, ev_value):
|
||
|
# sync event
|
||
|
if ev_type == EV_SYN:
|
||
|
if ev_code == SYN_MT_REPORT:
|
||
|
if 'id' not in point:
|
||
|
return
|
||
|
l_points.append(point.copy())
|
||
|
elif ev_code == SYN_REPORT:
|
||
|
process(l_points)
|
||
|
del l_points[:]
|
||
|
|
||
|
elif ev_type == EV_MSC and ev_code in (MSC_RAW, MSC_SCAN):
|
||
|
pass
|
||
|
|
||
|
else:
|
||
|
# compute multitouch track
|
||
|
if ev_code == ABS_MT_TRACKING_ID:
|
||
|
point.clear()
|
||
|
point['id'] = ev_value
|
||
|
elif ev_code == ABS_MT_POSITION_X:
|
||
|
val = normalize(ev_value,
|
||
|
range_min_position_x,
|
||
|
range_max_position_x)
|
||
|
assign_coord(point, val, invert_x, 'xy')
|
||
|
elif ev_code == ABS_MT_POSITION_Y:
|
||
|
val = 1. - normalize(ev_value,
|
||
|
range_min_position_y,
|
||
|
range_max_position_y)
|
||
|
assign_coord(point, val, invert_y, 'yx')
|
||
|
elif ev_code == ABS_MT_ORIENTATION:
|
||
|
point['orientation'] = ev_value
|
||
|
elif ev_code == ABS_MT_BLOB_ID:
|
||
|
point['blobid'] = ev_value
|
||
|
elif ev_code == ABS_MT_PRESSURE:
|
||
|
point['pressure'] = normalize(ev_value,
|
||
|
range_min_pressure,
|
||
|
range_max_pressure)
|
||
|
elif ev_code == ABS_MT_TOUCH_MAJOR:
|
||
|
point['size_w'] = ev_value
|
||
|
elif ev_code == ABS_MT_TOUCH_MINOR:
|
||
|
point['size_h'] = ev_value
|
||
|
|
||
|
def process_as_mouse_or_keyboard(
|
||
|
tv_sec, tv_usec, ev_type, ev_code, ev_value):
|
||
|
if ev_type == EV_SYN:
|
||
|
if ev_code == SYN_REPORT:
|
||
|
process([point])
|
||
|
if ('button' in point and
|
||
|
point['button'].startswith('scroll')):
|
||
|
# for scrolls we need to remove it as there is
|
||
|
# no up key
|
||
|
del point['button']
|
||
|
point['id'] += 1
|
||
|
point['_avoid'] = True
|
||
|
process([point])
|
||
|
|
||
|
elif ev_type == EV_REL:
|
||
|
if ev_code == 0:
|
||
|
assign_rel_coord(point,
|
||
|
min(1., max(-1., ev_value / 1000.)),
|
||
|
invert_x, 'xy')
|
||
|
elif ev_code == 1:
|
||
|
assign_rel_coord(point,
|
||
|
min(1., max(-1., ev_value / 1000.)),
|
||
|
invert_y, 'yx')
|
||
|
elif ev_code == 8: # Wheel
|
||
|
# translates the wheel move to a button
|
||
|
b = "scrollup" if ev_value < 0 else "scrolldown"
|
||
|
if 'button' not in point:
|
||
|
point['button'] = b
|
||
|
point['id'] += 1
|
||
|
if '_avoid' in point:
|
||
|
del point['_avoid']
|
||
|
|
||
|
elif ev_type != EV_KEY:
|
||
|
if ev_code == ABS_X:
|
||
|
val = normalize(ev_value,
|
||
|
range_min_abs_x,
|
||
|
range_max_abs_x)
|
||
|
assign_coord(point, val, invert_x, 'xy')
|
||
|
elif ev_code == ABS_Y:
|
||
|
val = 1. - normalize(ev_value,
|
||
|
range_min_abs_y,
|
||
|
range_max_abs_y)
|
||
|
assign_coord(point, val, invert_y, 'yx')
|
||
|
elif ev_code == ABS_PRESSURE:
|
||
|
point['pressure'] = normalize(ev_value,
|
||
|
range_min_abs_pressure,
|
||
|
range_max_abs_pressure)
|
||
|
else:
|
||
|
buttons = {
|
||
|
272: 'left',
|
||
|
273: 'right',
|
||
|
274: 'middle',
|
||
|
275: 'side',
|
||
|
276: 'extra',
|
||
|
277: 'forward',
|
||
|
278: 'back',
|
||
|
279: 'task',
|
||
|
330: 'touch',
|
||
|
320: 'pen'}
|
||
|
|
||
|
if ev_code in buttons.keys():
|
||
|
if ev_value:
|
||
|
if 'button' not in point:
|
||
|
point['button'] = buttons[ev_code]
|
||
|
point['id'] += 1
|
||
|
if '_avoid' in point:
|
||
|
del point['_avoid']
|
||
|
elif 'button' in point:
|
||
|
if point['button'] == buttons[ev_code]:
|
||
|
del point['button']
|
||
|
point['id'] += 1
|
||
|
point['_avoid'] = True
|
||
|
else:
|
||
|
if not 0 <= ev_value <= 1:
|
||
|
return
|
||
|
|
||
|
if ev_code not in keyboard_keys:
|
||
|
Logger.warn('HIDInput: unhandled HID code: {}'.
|
||
|
format(ev_code))
|
||
|
return
|
||
|
|
||
|
z = keyboard_keys[ev_code][-1 if 'shift' in
|
||
|
Window._modifiers else 0]
|
||
|
if z.lower() not in Keyboard.keycodes:
|
||
|
# or if it is not in this LUT
|
||
|
Logger.warn('HIDInput: unhandled character: {}'.
|
||
|
format(z))
|
||
|
return
|
||
|
|
||
|
keycode = Keyboard.keycodes[z.lower()]
|
||
|
|
||
|
if ev_value == 1:
|
||
|
if z == 'shift' or z == 'alt':
|
||
|
Window._modifiers.append(z)
|
||
|
elif z.endswith('ctrl'):
|
||
|
Window._modifiers.append('ctrl')
|
||
|
|
||
|
dispatch_queue.append(('key_down', (
|
||
|
keycode, ev_code,
|
||
|
keys_str.get(z, z), Window._modifiers)))
|
||
|
elif ev_value == 0:
|
||
|
dispatch_queue.append(('key_up', (
|
||
|
keycode, ev_code,
|
||
|
keys_str.get(z, z), Window._modifiers)))
|
||
|
if ((z == 'shift' or z == 'alt') and
|
||
|
(z in Window._modifiers)):
|
||
|
Window._modifiers.remove(z)
|
||
|
elif (z.endswith('ctrl') and
|
||
|
'ctrl' in Window._modifiers):
|
||
|
Window._modifiers.remove('ctrl')
|
||
|
|
||
|
def process(points):
|
||
|
if not is_multitouch:
|
||
|
dispatch_queue.append(('mouse_pos', (
|
||
|
points[0]['x'] * Window.width,
|
||
|
points[0]['y'] * Window.height)))
|
||
|
|
||
|
actives = [args['id']
|
||
|
for args in points
|
||
|
if 'id' in args and '_avoid' not in args]
|
||
|
for args in points:
|
||
|
tid = args['id']
|
||
|
try:
|
||
|
touch = touches[tid]
|
||
|
if touch.sx == args['x'] and touch.sy == args['y']:
|
||
|
continue
|
||
|
touch.move(args)
|
||
|
if tid not in touches_sent:
|
||
|
queue.append(('begin', touch))
|
||
|
touches_sent.append(tid)
|
||
|
queue.append(('update', touch))
|
||
|
except KeyError:
|
||
|
if '_avoid' not in args:
|
||
|
touch = HIDMotionEvent(device, tid, args)
|
||
|
touches[touch.id] = touch
|
||
|
if tid not in touches_sent:
|
||
|
queue.append(('begin', touch))
|
||
|
touches_sent.append(tid)
|
||
|
|
||
|
for tid in list(touches.keys())[:]:
|
||
|
if tid not in actives:
|
||
|
touch = touches[tid]
|
||
|
if tid in touches_sent:
|
||
|
touch.update_time_end()
|
||
|
queue.append(('end', touch))
|
||
|
touches_sent.remove(tid)
|
||
|
del touches[tid]
|
||
|
|
||
|
def normalize(value, vmin, vmax):
|
||
|
return (value - vmin) / float(vmax - vmin)
|
||
|
|
||
|
# open the input
|
||
|
fd = open(input_fn, 'rb')
|
||
|
|
||
|
# get the controller name (EVIOCGNAME)
|
||
|
device_name = fcntl.ioctl(fd, EVIOCGNAME + (256 << 16),
|
||
|
" " * 256).decode().strip()
|
||
|
Logger.info('HIDMotionEvent: using <%s>' % device_name)
|
||
|
|
||
|
# get abs infos
|
||
|
bit = fcntl.ioctl(fd, EVIOCGBIT + (EV_MAX << 16), ' ' * sz_l)
|
||
|
bit, = struct.unpack('Q', bit)
|
||
|
is_multitouch = False
|
||
|
for x in range(EV_MAX):
|
||
|
# preserve this, we may want other things than EV_ABS
|
||
|
if x != EV_ABS:
|
||
|
continue
|
||
|
# EV_ABS available for this device ?
|
||
|
if (bit & (1 << x)) == 0:
|
||
|
continue
|
||
|
# ask abs info keys to the devices
|
||
|
sbit = fcntl.ioctl(fd, EVIOCGBIT + x + (KEY_MAX << 16),
|
||
|
' ' * sz_l)
|
||
|
sbit, = struct.unpack('Q', sbit)
|
||
|
for y in range(KEY_MAX):
|
||
|
if (sbit & (1 << y)) == 0:
|
||
|
continue
|
||
|
absinfo = fcntl.ioctl(fd, EVIOCGABS + y +
|
||
|
(struct_input_absinfo_sz << 16),
|
||
|
' ' * struct_input_absinfo_sz)
|
||
|
abs_value, abs_min, abs_max, abs_fuzz, \
|
||
|
abs_flat, abs_res = struct.unpack('iiiiii', absinfo)
|
||
|
if y == ABS_MT_POSITION_X:
|
||
|
is_multitouch = True
|
||
|
range_min_position_x = drs('min_position_x', abs_min)
|
||
|
range_max_position_x = drs('max_position_x', abs_max)
|
||
|
Logger.info('HIDMotionEvent: ' +
|
||
|
'<%s> range position X is %d - %d' % (
|
||
|
device_name, abs_min, abs_max))
|
||
|
elif y == ABS_MT_POSITION_Y:
|
||
|
is_multitouch = True
|
||
|
range_min_position_y = drs('min_position_y', abs_min)
|
||
|
range_max_position_y = drs('max_position_y', abs_max)
|
||
|
Logger.info('HIDMotionEvent: ' +
|
||
|
'<%s> range position Y is %d - %d' % (
|
||
|
device_name, abs_min, abs_max))
|
||
|
elif y == ABS_MT_PRESSURE:
|
||
|
range_min_pressure = drs('min_pressure', abs_min)
|
||
|
range_max_pressure = drs('max_pressure', abs_max)
|
||
|
Logger.info('HIDMotionEvent: ' +
|
||
|
'<%s> range pressure is %d - %d' % (
|
||
|
device_name, abs_min, abs_max))
|
||
|
elif y == ABS_X:
|
||
|
range_min_abs_x = drs('min_abs_x', abs_min)
|
||
|
range_max_abs_x = drs('max_abs_x', abs_max)
|
||
|
Logger.info('HIDMotionEvent: ' +
|
||
|
'<%s> range ABS X position is %d - %d' % (
|
||
|
device_name, abs_min, abs_max))
|
||
|
elif y == ABS_Y:
|
||
|
range_min_abs_y = drs('min_abs_y', abs_min)
|
||
|
range_max_abs_y = drs('max_abs_y', abs_max)
|
||
|
Logger.info('HIDMotionEvent: ' +
|
||
|
'<%s> range ABS Y position is %d - %d' % (
|
||
|
device_name, abs_min, abs_max))
|
||
|
elif y == ABS_PRESSURE:
|
||
|
range_min_abs_pressure = drs(
|
||
|
'min_abs_pressure', abs_min)
|
||
|
range_max_abs_pressure = drs(
|
||
|
'max_abs_pressure', abs_max)
|
||
|
Logger.info('HIDMotionEvent: ' +
|
||
|
'<%s> range ABS pressure is %d - %d' % (
|
||
|
device_name, abs_min, abs_max))
|
||
|
|
||
|
# init the point
|
||
|
if not is_multitouch:
|
||
|
point = {'x': .5, 'y': .5, 'id': 0, '_avoid': True}
|
||
|
|
||
|
# read until the end
|
||
|
while fd:
|
||
|
|
||
|
data = fd.read(struct_input_event_sz)
|
||
|
if len(data) < struct_input_event_sz:
|
||
|
break
|
||
|
|
||
|
# extract each event
|
||
|
for i in range(int(len(data) / struct_input_event_sz)):
|
||
|
ev = data[i * struct_input_event_sz:]
|
||
|
|
||
|
# extract timeval + event infos
|
||
|
infos = struct.unpack('LLHHi', ev[:struct_input_event_sz])
|
||
|
|
||
|
if is_multitouch:
|
||
|
process_as_multitouch(*infos)
|
||
|
else:
|
||
|
process_as_mouse_or_keyboard(*infos)
|
||
|
|
||
|
def update(self, dispatch_fn):
|
||
|
# dispatch all events from threads
|
||
|
dispatch_queue = self.dispatch_queue
|
||
|
n = len(dispatch_queue)
|
||
|
for name, args in dispatch_queue[:n]:
|
||
|
if name == 'mouse_pos':
|
||
|
Window.mouse_pos = args
|
||
|
elif name == 'key_down':
|
||
|
if not Window.dispatch('on_key_down', *args):
|
||
|
Window.dispatch('on_keyboard', *args)
|
||
|
elif name == 'key_up':
|
||
|
Window.dispatch('on_key_up', *args)
|
||
|
del dispatch_queue[:n]
|
||
|
|
||
|
try:
|
||
|
while True:
|
||
|
event_type, touch = self.queue.popleft()
|
||
|
dispatch_fn(event_type, touch)
|
||
|
except:
|
||
|
pass
|
||
|
|
||
|
MotionEventFactory.register('hidinput', HIDInputMotionEventProvider)
|