| #!/usr/bin/env python2 |
| # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| # Description: |
| # |
| # Class for handling linux 'evdev' input devices. |
| # |
| # Provides evtest-like functionality if run from the command line: |
| # $ input_device.py -d /dev/input/event6 |
| |
| """ Read properties and events of a linux input device. """ |
| |
| from __future__ import division |
| from __future__ import print_function |
| |
| import array |
| import copy |
| import fcntl |
| import os.path |
| import re |
| import select |
| import struct |
| import time |
| |
| from collections import OrderedDict |
| |
| from linux_input import * |
| from six.moves import range |
| |
| |
| # The regular expression of possible keyboard types. |
| KEYBOARD_TYPES = '(keyboard|chromeos-ec-i2c|cros-ec-spi|cros-ec-i2c|cros_ec)' |
| |
| _DEVICE_INFO_FILE = '/proc/bus/input/devices' |
| |
| |
| class Valuator: |
| """ A Valuator just stores a value """ |
| def __init__(self): |
| self.value = 0 |
| |
| class SwValuator(Valuator): |
| """ A Valuator used for EV_SW (switch) events """ |
| def __init__(self, value): |
| self.value = value |
| |
| class AbsValuator(Valuator): |
| """ |
| An AbsValuator, used for EV_ABS events stores a value as well as other |
| properties of the corresponding absolute axis. |
| """ |
| def __init__(self, value, min_value, max_value, fuzz, flat, resolution): |
| self.value = value |
| self.min = min_value |
| self.max = max_value |
| self.fuzz = fuzz |
| self.flat = flat |
| self.resolution = resolution |
| |
| |
| class InputEvent: |
| """ |
| Linux evdev input event |
| |
| An input event has the following fields which can be accessed as public |
| properties of this class: |
| tv_sec |
| tv_usec |
| type |
| code |
| value |
| """ |
| def __init__(self, tv_sec=0, tv_usec=0, type=0, code=0, value=0): |
| self.format = input_event_t |
| self.format_size = struct.calcsize(self.format) |
| (self.tv_sec, self.tv_usec, self.type, self.code, |
| self.value) = (tv_sec, tv_usec, type, code, value) |
| |
| def read(self, stream): |
| """ Read an input event from the provided stream and unpack it. """ |
| packed = stream.read(self.format_size) |
| (self.tv_sec, self.tv_usec, self.type, self.code, |
| self.value) = struct.unpack(self.format, packed) |
| |
| def write(self, stream): |
| """ Pack an input event and write it to the provided stream. """ |
| packed = struct.pack(self.format, self.tv_sec, self.tv_usec, self.type, |
| self.code, self.value) |
| stream.write(packed) |
| stream.flush() |
| |
| def __str__(self): |
| t = EV_TYPES.get(self.type, self.type) |
| if self.type in EV_STRINGS: |
| c = EV_STRINGS[self.type].get(self.code, self.code) |
| else: |
| c = self.code |
| return ('%d.%06d: %s[%s] = %d' % |
| (self.tv_sec, self.tv_usec, t, c, self.value)) |
| |
| |
| class InputDevice: |
| """ |
| Linux evdev input device |
| |
| A linux kernel "evdev" device sends a stream of "input events". |
| These events are grouped together into "input reports", which is a set of |
| input events ending in a single EV_SYN/SYN_REPORT event. |
| |
| Each input event is tagged with a type and a code. |
| A given input device supports a subset of the possible types, and for |
| each type it supports a subset of the possible codes for that type. |
| |
| The device maintains a "valuator" for each supported type/code pairs. |
| There are two types of "valuators": |
| Normal valuators represent just a value. |
| Absolute valuators are only for EV_ABS events. They have more fields: |
| value, minimum, maximum, resolution, fuzz, flatness |
| Note: Relative and Absolute "Valuators" are also often called relative |
| and absolute axis, respectively. |
| |
| The evdev protocol is stateful. Input events are only sent when the values |
| of a valuator actually changes. This dramatically reduces the stream of |
| events emenating from the kernel. |
| |
| Multitouch devices are a special case. There are two types of multitouch |
| devices defined in the kernel: |
| Multitouch type "A" (MT-A) devices: |
| In each input report, the device sends an unordered list of all |
| active contacts. The data for each active contact is separated |
| in the input report by an EV_SYN/SYN_MT_REPORT event. |
| Thus, the MT-A contact event stream is not stateful. |
| Note: MT-A is not currently supported by this class. |
| |
| Multitouch type "B" (MT-B) devices: |
| The device maintains a list of slots, where each slot contains a |
| single contact. In each input report, the device only sends |
| information about the slots that have changed. |
| Thus, the MT-B contact event stream is stateful. |
| When reporting multiple slots, the EV_ABS/MT_SLOT valuator is used |
| to indicate the 'current' slot for which subsequent EV_ABS/ABS_MT_* |
| valuator events apply. |
| An inactive slot has EV_ABS/ABS_MT_TRACKING_ID == -1 |
| Active slots have EV_ABS/ABS_MT_TRACKING_ID >= 0 |
| |
| Besides maintaining the set of supported ABS_MT valuators in the supported |
| valuator list, a array of slots is also maintained. Each slot has its own |
| unique copy of just the supported ABS_MT valuators. This represents the |
| current state of that slot. |
| """ |
| |
| def __init__(self, path, ev_syn_cb=None): |
| """ |
| Constructor opens the device file and probes its properties. |
| |
| Note: The device file is left open when the constructor exits. |
| """ |
| self.path = path |
| self.ev_syn_cb = ev_syn_cb |
| self.events = {} # dict { ev_type : dict { ev_code : Valuator } } |
| self.mt_slots = [] # [ dict { mt_code : AbsValuator } ] * |MT-B slots| |
| |
| # Open the device node, and use ioctls to probe its properties |
| self.f = None |
| self.f = open(path, 'rb+', buffering=0) |
| self._ioctl_version() |
| self._ioctl_id() |
| self._ioctl_name() |
| for t in self._ioctl_types(): |
| self._ioctl_codes(t) |
| self._setup_mt_slots() |
| |
| def __del__(self): |
| """ |
| Deconstructor closes the device file, if it is open. |
| """ |
| if self.f and not self.f.closed: |
| self.f.close() |
| |
| def process_event(self, ev): |
| """ |
| Processes an incoming input event. |
| |
| Returns True for EV_SYN/SYN_REPORT events to indicate that a complete |
| input report has been received. |
| |
| Returns False for other events. |
| |
| Events not supported by this device are silently ignored. |
| |
| For MT events, updates the slot valuator value for the current slot. |
| If current slot is the 'primary' slot, also updates the events entry. |
| |
| For all other events, updates the corresponding valuator value. |
| """ |
| if ev.type == EV_SYN and ev.code == SYN_REPORT: |
| return True |
| elif ev.type not in self.events or ev.code not in self.events[ev.type]: |
| return False |
| elif self.is_mt_b() and ev.type == EV_ABS and ev.code in ABS_MT_RANGE: |
| # TODO: Handle MT-A |
| slot = self._get_current_slot() |
| slot[ev.code].value = ev.value |
| # if the current slot is the "primary" slot, |
| # update the events[] entry, too. |
| if slot == self._get_mt_primary_slot(): |
| self.events[ev.type][ev.code].value = ev.value |
| else: |
| self.events[ev.type][ev.code].value = ev.value |
| return False |
| |
| def _ioctl_version(self): |
| """ Queries device file for version information. """ |
| # Version is a 32-bit integer, which encodes 8-bit major version, |
| # 8-bit minor version and 16-bit revision. |
| version = array.array('I', [0]) |
| fcntl.ioctl(self.f, EVIOCGVERSION, version, 1) |
| self.version = (version[0] >> 16, (version[0] >> 8) & 0xff, |
| version[0] & 0xff) |
| |
| def _ioctl_id(self): |
| """ Queries device file for input device identification. """ |
| # struct input_id is 4 __u16 |
| gid = array.array('H', [0] * 4) |
| fcntl.ioctl(self.f, EVIOCGID, gid, 1) |
| self.id_bus = gid[ID_BUS] |
| self.id_vendor = gid[ID_VENDOR] |
| self.id_product = gid[ID_PRODUCT] |
| self.id_version = gid[ID_VERSION] |
| |
| def _ioctl_name(self): |
| """ Queries device file for the device name. """ |
| # Device name is a C-string up to 255 bytes in length. |
| name_len = 255 |
| name = array.array('B', [0] * name_len) |
| name_len = fcntl.ioctl(self.f, EVIOCGNAME(name_len), name, 1) |
| self.name = name[0:name_len-1].tostring() |
| |
| def _ioctl_get_switch(self, sw): |
| """ |
| Queries device file for current value of all switches and returns |
| a boolean indicating whether the switch sw is set. |
| """ |
| size = SW_CNT // 8 # Buffer size of one __u16 |
| buf = array.array('H', [0]) |
| fcntl.ioctl(self.f, EVIOCGSW(size), buf) |
| return SwValuator(((buf[0] >> sw) & 0x01) == 1) |
| |
| def _ioctl_absinfo(self, axis): |
| """ |
| Queries device file for absinfo structure for given absolute axis. |
| """ |
| # struct input_absinfo is 6 __s32 |
| a = array.array('i', [0] * 6) |
| fcntl.ioctl(self.f, EVIOCGABS(axis), a, 1) |
| return AbsValuator(a[0], a[1], a[2], a[3], a[4], a[5]) |
| |
| def _ioctl_codes(self, ev_type): |
| """ |
| Queries device file for supported event codes for given event type. |
| """ |
| self.events[ev_type] = {} |
| if ev_type not in EV_SIZES: |
| return |
| |
| size = EV_SIZES[ev_type] // 8 # Convert bits to bytes |
| ev_code = array.array('B', [0] * size) |
| try: |
| count = fcntl.ioctl(self.f, EVIOCGBIT(ev_type, size), ev_code, 1) |
| for c in range(count * 8): |
| if test_bit(c, ev_code): |
| if ev_type == EV_ABS: |
| self.events[ev_type][c] = self._ioctl_absinfo(c) |
| elif ev_type == EV_SW: |
| self.events[ev_type][c] = self._ioctl_get_switch(c) |
| else: |
| self.events[ev_type][c] = Valuator() |
| except IOError as errs: |
| # Errno 22 signifies that this event type has no event codes. |
| (errno, strerror) = errs.args |
| if errno != 22: |
| raise |
| |
| def _ioctl_types(self): |
| """ Queries device file for supported event types. """ |
| ev_types = array.array('B', [0] * (EV_CNT // 8)) |
| fcntl.ioctl(self.f, EVIOCGBIT(EV_SYN, EV_CNT // 8), ev_types, 1) |
| types = [] |
| for t in range(EV_CNT): |
| if test_bit(t, ev_types): |
| types.append(t) |
| return types |
| |
| def _convert_slot_index_to_slot_id(self, index): |
| """ Convert a slot index in self.mt_slots to its slot id. """ |
| return self.abs_mt_slot.min + index |
| |
| def _ioctl_mt_slots(self): |
| """Query mt slots values using ioctl. |
| |
| The ioctl buffer argument should be binary equivalent to |
| struct input_mt_request_layout { |
| __u32 code; |
| __s32 values[num_slots]; |
| |
| Note that the slots information returned by EVIOCGMTSLOTS |
| corresponds to the slot ids ranging from abs_mt_slot.min to |
| abs_mt_slot.max which is not necessarily the same as the |
| slot indexes ranging from 0 to num_slots - 1 in self.mt_slots. |
| We need to map between the slot index and the slot id correctly. |
| }; |
| """ |
| # Iterate through the absolute mt events that are supported. |
| for c in range(ABS_MT_FIRST, ABS_MT_LAST): |
| if c not in self.events[EV_ABS]: |
| continue |
| # Sync with evdev kernel driver for the specified code c. |
| mt_slot_info = array.array('i', [c] + [0] * self.num_slots) |
| mt_slot_info_len = (self.num_slots + 1) * mt_slot_info.itemsize |
| fcntl.ioctl(self.f, EVIOCGMTSLOTS(mt_slot_info_len), mt_slot_info) |
| values = mt_slot_info[1:] |
| for slot_index in range(self.num_slots): |
| slot_id = self._convert_slot_index_to_slot_id(slot_index) |
| self.mt_slots[slot_index][c].value = values[slot_id] |
| |
| def _setup_mt_slots(self): |
| """ |
| Sets up the device's mt_slots array. |
| |
| Each element of the mt_slots array is initialized as a deepcopy of a |
| dict containing all of the MT valuators from the events dict. |
| """ |
| # TODO(djkurtz): MT-A |
| if not self.is_mt_b(): |
| return |
| ev_abs = self.events[EV_ABS] |
| # Create dict containing just the MT valuators |
| mt_abs_info = dict((axis, ev_abs[axis]) |
| for axis in ev_abs |
| if axis in ABS_MT_RANGE) |
| |
| # Initialize TRACKING_ID to -1 |
| mt_abs_info[ABS_MT_TRACKING_ID].value = -1 |
| |
| # Make a copy of mt_abs_info for each MT slot |
| self.abs_mt_slot = ev_abs[ABS_MT_SLOT] |
| self.num_slots = self.abs_mt_slot.max - self.abs_mt_slot.min + 1 |
| for s in range(self.num_slots): |
| self.mt_slots.append(copy.deepcopy(mt_abs_info)) |
| |
| self._ioctl_mt_slots() |
| |
| def get_current_slot_id(self): |
| """ |
| Return the current slot id. |
| """ |
| if not self.is_mt_b(): |
| return None |
| return self.events[EV_ABS][ABS_MT_SLOT].value |
| |
| def _get_current_slot(self): |
| """ |
| Returns the current slot, as indicated by the last ABS_MT_SLOT event. |
| """ |
| current_slot_id = self.get_current_slot_id() |
| if current_slot_id is None: |
| return None |
| return self.mt_slots[current_slot_id] |
| |
| def _get_tid(self, slot): |
| """ Returns the tracking_id for the given MT slot. """ |
| return slot[ABS_MT_TRACKING_ID].value |
| |
| def _get_mt_valid_slots(self): |
| """ |
| Returns a list of valid slots. |
| |
| A valid slot is a slot whose tracking_id != -1. |
| """ |
| return [s for s in self.mt_slots if self._get_tid(s) != -1] |
| |
| def _get_mt_primary_slot(self): |
| """ |
| Returns the "primary" MT-B slot. |
| |
| The "primary" MT-B slot is arbitrarily chosen as the slot with lowest |
| tracking_id (> -1). It is used to make an MT-B device look like |
| single-touch (ST) device. |
| """ |
| slot = None |
| for s in self.mt_slots: |
| tid = self._get_tid(s) |
| if tid < 0: |
| continue |
| if not slot or tid < self._get_tid(slot): |
| slot = s |
| return slot |
| |
| def _code_if_mt(self, type, code): |
| """ |
| Returns MT-equivalent event code for certain specific event codes |
| """ |
| if type != EV_ABS: |
| return code |
| elif code == ABS_X: |
| return ABS_MT_POSITION_X |
| elif code == ABS_Y: |
| return ABS_MT_POSITION_Y |
| elif code == ABS_PRESSURE: |
| return ABS_MT_PRESSURE |
| elif code == ABS_TOOL_WIDTH: |
| return ABS_TOUCH_MAJOR |
| else: |
| return code |
| |
| def _get_valuator(self, type, code): |
| """ Returns Valuator for given event type and code """ |
| if (not type in self.events) or (not code in self.events[type]): |
| return None |
| if type == EV_ABS: |
| code = self._code_if_mt(type, code) |
| return self.events[type][code] |
| |
| def _get_value(self, type, code): |
| """ |
| Returns the value of the valuator with the give event (type, code). |
| """ |
| axis = self._get_valuator(type, code) |
| if not axis: |
| return None |
| return axis.value |
| |
| def _get_min(self, type, code): |
| """ |
| Returns the min value of the valuator with the give event (type, code). |
| |
| Note: Only AbsValuators (EV_ABS) have max values. |
| """ |
| axis = self._get_valuator(type, code) |
| if not axis: |
| return None |
| return axis.min |
| |
| def _get_max(self, type, code): |
| """ |
| Returns the min value of the valuator with the give event (type, code). |
| |
| Note: Only AbsValuators (EV_ABS) have max values. |
| """ |
| axis = self._get_valuator(type, code) |
| if not axis: |
| return None |
| return axis.max |
| |
| """ Public accessors """ |
| |
| def get_num_fingers(self): |
| if self.is_mt_b(): |
| return len(self._get_mt_valid_slots()) |
| elif self.is_mt_a(): |
| return 0 # TODO(djkurtz): MT-A |
| else: # Single-Touch case |
| if not self._get_value(EV_KEY, BTN_TOUCH) == 1: |
| return 0 |
| elif self._get_value(EV_KEY, BTN_TOOL_TRIPLETAP) == 1: |
| return 3 |
| elif self._get_value(EV_KEY, BTN_TOOL_DOUBLETAP) == 1: |
| return 2 |
| elif self._get_value(EV_KEY, BTN_TOOL_FINGER) == 1: |
| return 1 |
| else: |
| return 0 |
| |
| def get_x(self): |
| return self._get_value(EV_ABS, ABS_X) |
| |
| def get_x_min(self): |
| return self._get_min(EV_ABS, ABS_X) |
| |
| def get_x_max(self): |
| return self._get_max(EV_ABS, ABS_X) |
| |
| def get_y(self): |
| return self._get_value(EV_ABS, ABS_Y) |
| |
| def get_y_min(self): |
| return self._get_min(EV_ABS, ABS_Y) |
| |
| def get_y_max(self): |
| return self._get_max(EV_ABS, ABS_Y) |
| |
| def get_pressure(self): |
| return self._get_value(EV_ABS, ABS_PRESSURE) |
| |
| def get_pressure_min(self): |
| return self._get_min(EV_ABS, ABS_PRESSURE) |
| |
| def get_pressure_max(self): |
| return self._get_max(EV_ABS, ABS_PRESSURE) |
| |
| def get_left(self): |
| return int(self._get_value(EV_KEY, BTN_LEFT) == 1) |
| |
| def get_right(self): |
| return int(self._get_value(EV_KEY, BTN_RIGHT) == 1) |
| |
| def get_middle(self): |
| return int(self._get_value(EV_KEY, BTN_MIDDLE) == 1) |
| |
| def get_microphone_insert(self): |
| return self._get_value(EV_SW, SW_MICROPHONE_INSERT) |
| |
| def get_headphone_insert(self): |
| return self._get_value(EV_SW, SW_HEADPHONE_INSERT) |
| |
| def get_lineout_insert(self): |
| return self._get_value(EV_SW, SW_LINEOUT_INSERT) |
| |
| def is_touchpad(self): |
| return ((EV_KEY in self.events) and |
| (BTN_TOOL_FINGER in self.events[EV_KEY]) and |
| (EV_ABS in self.events)) |
| |
| def is_keyboard(self): |
| if EV_KEY not in self.events: |
| return False |
| # Check first 31 keys. This is the same method udev and the |
| # Chromium browser use. |
| for key in range(KEY_ESC, KEY_D + 1): |
| if key not in self.events[EV_KEY]: |
| return False |
| return True |
| |
| def is_touchscreen(self): |
| return ((EV_KEY in self.events) and |
| (BTN_TOUCH in self.events[EV_KEY]) and |
| (not BTN_TOOL_FINGER in self.events[EV_KEY]) and |
| (EV_ABS in self.events)) |
| |
| def is_lid(self): |
| return ((EV_SW in self.events) and |
| (SW_LID in self.events[EV_SW])) |
| |
| def is_mt_b(self): |
| return self.is_mt() and ABS_MT_SLOT in self.events[EV_ABS] |
| |
| def is_mt_a(self): |
| return self.is_mt() and ABS_MT_SLOT not in self.events[EV_ABS] |
| |
| def is_mt(self): |
| return (EV_ABS in self.events and |
| (set(self.events[EV_ABS]) & set(ABS_MT_RANGE))) |
| |
| def is_hp_jack(self): |
| return (EV_SW in self.events and |
| SW_HEADPHONE_INSERT in self.events[EV_SW]) |
| |
| def is_mic_jack(self): |
| return (EV_SW in self.events and |
| SW_MICROPHONE_INSERT in self.events[EV_SW]) |
| |
| def is_audio_jack(self): |
| return (EV_SW in self.events and |
| ((SW_HEADPHONE_INSERT in self.events[EV_SW]) or |
| (SW_MICROPHONE_INSERT in self.events[EV_SW] or |
| (SW_LINEOUT_INSERT in self.events[EV_SW])))) |
| |
| """ Debug helper print functions """ |
| |
| def print_abs_info(self, axis): |
| if EV_ABS in self.events and axis in self.events[EV_ABS]: |
| a = self.events[EV_ABS][axis] |
| print(' Value %6d' % a.value) |
| print(' Min %6d' % a.min) |
| print(' Max %6d' % a.max) |
| if a.fuzz != 0: |
| print(' Fuzz %6d' % a.fuzz) |
| if a.flat != 0: |
| print(' Flat %6d' % a.flat) |
| if a.resolution != 0: |
| print(' Resolution %6d' % a.resolution) |
| |
| def print_props(self): |
| print(('Input driver Version: %d.%d.%d' % |
| (self.version[0], self.version[1], self.version[2]))) |
| print(('Input device ID: bus %x vendor %x product %x version %x' % |
| (self.id_bus, self.id_vendor, self.id_product, self.id_version))) |
| print('Input device name: "%s"' % (self.name)) |
| for t in self.events: |
| print(' Event type %d (%s)' % (t, EV_TYPES.get(t, '?'))) |
| for c in self.events[t]: |
| if (t in EV_STRINGS): |
| code = EV_STRINGS[t].get(c, '?') |
| print(' Event code %s (%d)' % (code, c)) |
| else: |
| print(' Event code (%d)' % (c)) |
| self.print_abs_info(c) |
| |
| def get_slots(self): |
| """ Get those slots with positive tracking IDs. """ |
| slot_dict = OrderedDict() |
| for slot_index in range(self.num_slots): |
| slot = self.mt_slots[slot_index] |
| if self._get_tid(slot) == -1: |
| continue |
| slot_id = self._convert_slot_index_to_slot_id(slot_index) |
| slot_dict[slot_id] = slot |
| return slot_dict |
| |
| def print_slots(self): |
| slot_dict = self.get_slots() |
| for slot_id, slot in slot_dict.items(): |
| print('slot #%d' % slot_id) |
| for a in slot: |
| abs = EV_STRINGS[EV_ABS].get(a, '?') |
| print(' %s = %6d' % (abs, slot[a].value)) |
| |
| |
| def print_report(device): |
| print('----- EV_SYN -----') |
| if device.is_touchpad(): |
| f = device.get_num_fingers() |
| if f == 0: |
| return |
| x = device.get_x() |
| y = device.get_y() |
| z = device.get_pressure() |
| l = device.get_left() |
| print('Left=%d Fingers=%d X=%d Y=%d Pressure=%d' % (l, f, x, y, z)) |
| if device.is_mt(): |
| device.print_slots() |
| |
| |
| def get_device_node(device_type): |
| """Get the keyboard device node through device info file. |
| |
| Example of the keyboard device information looks like |
| |
| I: Bus=0011 Vendor=0001 Product=0001 Version=ab41 |
| N: Name="AT Translated Set 2 keyboard" |
| P: Phys=isa0060/serio0/input0 |
| S: Sysfs=/devices/platform/i8042/serio0/input/input5 |
| U: Uniq= |
| H: Handlers=sysrq kbd event5 |
| """ |
| device_node = None |
| device_found = None |
| device_pattern = re.compile('N: Name=.*%s' % device_type, re.I) |
| event_number_pattern = re.compile(r'H: Handlers=.*event(\d?)', re.I) |
| with open(_DEVICE_INFO_FILE) as info: |
| for line in info: |
| if device_found: |
| result = event_number_pattern.search(line) |
| if result: |
| event_number = int(result.group(1)) |
| device_node = '/dev/input/event%d' % event_number |
| break |
| else: |
| device_found = device_pattern.search(line) |
| return device_node |
| |
| |
| if __name__ == "__main__": |
| from optparse import OptionParser |
| import glob |
| parser = OptionParser() |
| |
| parser.add_option("-a", "--audio_jack", action="store_true", |
| dest="audio_jack", default=False, |
| help="Find and use all audio jacks") |
| parser.add_option("-d", "--devpath", dest="devpath", |
| default="/dev/input/event0", |
| help="device path (/dev/input/event0)") |
| parser.add_option("-q", "--quiet", action="store_false", dest="verbose", |
| default=True, help="print less messages to stdout") |
| parser.add_option("-t", "--touchpad", action="store_true", dest="touchpad", |
| default=False, help="Find and use first touchpad device") |
| (options, args) = parser.parse_args() |
| |
| # TODO: Use gudev to detect touchpad |
| devices = [] |
| if options.touchpad: |
| for path in glob.glob('/dev/input/event*'): |
| device = InputDevice(path) |
| if device.is_touchpad(): |
| print('Using touchpad %s.' % path) |
| options.devpath = path |
| devices.append(device) |
| break |
| else: |
| print('No touchpad found!') |
| exit() |
| elif options.audio_jack: |
| for path in glob.glob('/dev/input/event*'): |
| device = InputDevice(path) |
| if device.is_audio_jack(): |
| devices.append(device) |
| device = None |
| elif os.path.exists(options.devpath): |
| print('Using %s.' % options.devpath) |
| devices.append(InputDevice(options.devpath)) |
| else: |
| print('%s does not exist.' % options.devpath) |
| exit() |
| |
| for device in devices: |
| device.print_props() |
| if device.is_touchpad(): |
| print(('x: (%d,%d), y: (%d,%d), z: (%d, %d)' % |
| (device.get_x_min(), device.get_x_max(), |
| device.get_y_min(), device.get_y_max(), |
| device.get_pressure_min(), device.get_pressure_max()))) |
| device.print_slots() |
| print('Number of fingers: %d' % device.get_num_fingers()) |
| print('Current slot id: %d' % device.get_current_slot_id()) |
| print('------------------') |
| print() |
| |
| ev = InputEvent() |
| while True: |
| _rl, _, _ = select.select([d.f for d in devices], [], []) |
| for fd in _rl: |
| # Lookup for the device which owns fd. |
| device = [d for d in devices if d.f == fd][0] |
| try: |
| ev.read(fd) |
| except KeyboardInterrupt: |
| exit() |
| is_syn = device.process_event(ev) |
| print(ev) |
| if is_syn: |
| print_report(device) |