735 lines
26 KiB
Python
Executable File
735 lines
26 KiB
Python
Executable File
#!/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)
|