1#!/usr/bin/env python2
2# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6# Description:
7#
8# Class for handling linux 'evdev' input devices.
9#
10# Provides evtest-like functionality if run from the command line:
11# $ input_device.py -d /dev/input/event6
12
13""" Read properties and events of a linux input device. """
14
15from __future__ import division
16from __future__ import print_function
17
18import array
19import copy
20import fcntl
21import os.path
22import re
23import select
24import struct
25import time
26
27from collections import OrderedDict
28
29from linux_input import *
30from six.moves import range
31
32
33# The regular expression of possible keyboard types.
34KEYBOARD_TYPES = '(keyboard|chromeos-ec-i2c|cros-ec-spi|cros-ec-i2c|cros_ec)'
35
36_DEVICE_INFO_FILE = '/proc/bus/input/devices'
37
38
39class Valuator:
40    """ A Valuator just stores a value """
41    def __init__(self):
42        self.value = 0
43
44class SwValuator(Valuator):
45    """ A Valuator used for EV_SW (switch) events """
46    def __init__(self, value):
47        self.value = value
48
49class AbsValuator(Valuator):
50    """
51    An AbsValuator, used for EV_ABS events stores a value as well as other
52    properties of the corresponding absolute axis.
53    """
54    def __init__(self, value, min_value, max_value, fuzz, flat, resolution):
55        self.value = value
56        self.min = min_value
57        self.max = max_value
58        self.fuzz = fuzz
59        self.flat = flat
60        self.resolution = resolution
61
62
63class InputEvent:
64    """
65    Linux evdev input event
66
67    An input event has the following fields which can be accessed as public
68    properties of this class:
69        tv_sec
70        tv_usec
71        type
72        code
73        value
74    """
75    def __init__(self, tv_sec=0, tv_usec=0, type=0, code=0, value=0):
76        self.format = input_event_t
77        self.format_size = struct.calcsize(self.format)
78        (self.tv_sec, self.tv_usec, self.type, self.code,
79         self.value) = (tv_sec, tv_usec, type, code, value)
80
81    def read(self, stream):
82        """ Read an input event from the provided stream and unpack it. """
83        packed = stream.read(self.format_size)
84        (self.tv_sec, self.tv_usec, self.type, self.code,
85         self.value) = struct.unpack(self.format, packed)
86
87    def write(self, stream):
88        """ Pack an input event and write it to the provided stream. """
89        packed = struct.pack(self.format, self.tv_sec, self.tv_usec, self.type,
90                             self.code, self.value)
91        stream.write(packed)
92        stream.flush()
93
94    def __str__(self):
95        t = EV_TYPES.get(self.type, self.type)
96        if self.type in EV_STRINGS:
97            c = EV_STRINGS[self.type].get(self.code, self.code)
98        else:
99            c = self.code
100        return ('%d.%06d: %s[%s] = %d' %
101                (self.tv_sec, self.tv_usec, t, c, self.value))
102
103
104class InputDevice:
105    """
106    Linux evdev input device
107
108    A linux kernel "evdev" device sends a stream of "input events".
109    These events are grouped together into "input reports", which is a set of
110    input events ending in a single EV_SYN/SYN_REPORT event.
111
112    Each input event is tagged with a type and a code.
113    A given input device supports a subset of the possible types, and for
114    each type it supports a subset of the possible codes for that type.
115
116    The device maintains a "valuator" for each supported type/code pairs.
117    There are two types of "valuators":
118       Normal valuators represent just a value.
119       Absolute valuators are only for EV_ABS events. They have more fields:
120           value, minimum, maximum, resolution, fuzz, flatness
121    Note: Relative and Absolute "Valuators" are also often called relative
122          and absolute axis, respectively.
123
124    The evdev protocol is stateful.  Input events are only sent when the values
125    of a valuator actually changes.  This dramatically reduces the stream of
126    events emenating from the kernel.
127
128    Multitouch devices are a special case.  There are two types of multitouch
129    devices defined in the kernel:
130        Multitouch type "A" (MT-A) devices:
131            In each input report, the device sends an unordered list of all
132            active contacts.  The data for each active contact is separated
133            in the input report by an EV_SYN/SYN_MT_REPORT event.
134            Thus, the MT-A contact event stream is not stateful.
135            Note: MT-A is not currently supported by this class.
136
137        Multitouch type "B" (MT-B) devices:
138            The device maintains a list of slots, where each slot contains a
139            single contact.  In each input report, the device only sends
140            information about the slots that have changed.
141            Thus, the MT-B contact event stream is stateful.
142            When reporting multiple slots, the EV_ABS/MT_SLOT valuator is used
143            to indicate the 'current' slot for which subsequent EV_ABS/ABS_MT_*
144            valuator events apply.
145            An inactive slot has EV_ABS/ABS_MT_TRACKING_ID == -1
146            Active slots have EV_ABS/ABS_MT_TRACKING_ID >= 0
147
148    Besides maintaining the set of supported ABS_MT valuators in the supported
149    valuator list, a array of slots is also maintained.  Each slot has its own
150    unique copy of just the supported ABS_MT valuators.  This represents the
151    current state of that slot.
152    """
153
154    def __init__(self, path, ev_syn_cb=None):
155        """
156        Constructor opens the device file and probes its properties.
157
158        Note: The device file is left open when the constructor exits.
159        """
160        self.path = path
161        self.ev_syn_cb = ev_syn_cb
162        self.events = {}     # dict { ev_type : dict { ev_code : Valuator } }
163        self.mt_slots = []   # [ dict { mt_code : AbsValuator } ] * |MT-B slots|
164
165        # Open the device node, and use ioctls to probe its properties
166        self.f = None
167        self.f = open(path, 'rb+', buffering=0)
168        self._ioctl_version()
169        self._ioctl_id()
170        self._ioctl_name()
171        for t in self._ioctl_types():
172            self._ioctl_codes(t)
173        self._setup_mt_slots()
174
175    def __del__(self):
176        """
177        Deconstructor closes the device file, if it is open.
178        """
179        if self.f and not self.f.closed:
180            self.f.close()
181
182    def process_event(self, ev):
183        """
184        Processes an incoming input event.
185
186        Returns True for EV_SYN/SYN_REPORT events to indicate that a complete
187        input report has been received.
188
189        Returns False for other events.
190
191        Events not supported by this device are silently ignored.
192
193        For MT events, updates the slot valuator value for the current slot.
194        If current slot is the 'primary' slot, also updates the events entry.
195
196        For all other events, updates the corresponding valuator value.
197        """
198        if ev.type == EV_SYN and ev.code == SYN_REPORT:
199            return True
200        elif ev.type not in self.events or ev.code not in self.events[ev.type]:
201            return False
202        elif self.is_mt_b() and ev.type == EV_ABS and ev.code in ABS_MT_RANGE:
203            # TODO: Handle MT-A
204            slot = self._get_current_slot()
205            slot[ev.code].value = ev.value
206            # if the current slot is the "primary" slot,
207            # update the events[] entry, too.
208            if slot == self._get_mt_primary_slot():
209                self.events[ev.type][ev.code].value = ev.value
210        else:
211            self.events[ev.type][ev.code].value = ev.value
212        return False
213
214    def _ioctl_version(self):
215        """ Queries device file for version information. """
216        # Version is a 32-bit integer, which encodes 8-bit major version,
217        # 8-bit minor version and 16-bit revision.
218        version = array.array('I', [0])
219        fcntl.ioctl(self.f, EVIOCGVERSION, version, 1)
220        self.version = (version[0] >> 16, (version[0] >> 8) & 0xff,
221                        version[0] & 0xff)
222
223    def _ioctl_id(self):
224        """ Queries device file for input device identification. """
225        # struct input_id is 4 __u16
226        gid = array.array('H', [0] * 4)
227        fcntl.ioctl(self.f, EVIOCGID, gid, 1)
228        self.id_bus = gid[ID_BUS]
229        self.id_vendor = gid[ID_VENDOR]
230        self.id_product = gid[ID_PRODUCT]
231        self.id_version = gid[ID_VERSION]
232
233    def _ioctl_name(self):
234        """ Queries device file for the device name. """
235        # Device name is a C-string up to 255 bytes in length.
236        name_len = 255
237        name = array.array('B', [0] * name_len)
238        name_len = fcntl.ioctl(self.f, EVIOCGNAME(name_len), name, 1)
239        self.name = name[0:name_len-1].tostring()
240
241    def _ioctl_get_switch(self, sw):
242        """
243        Queries device file for current value of all switches and returns
244        a boolean indicating whether the switch sw is set.
245        """
246        size = SW_CNT // 8    # Buffer size of one __u16
247        buf = array.array('H', [0])
248        fcntl.ioctl(self.f, EVIOCGSW(size), buf)
249        return SwValuator(((buf[0] >> sw) & 0x01) == 1)
250
251    def _ioctl_absinfo(self, axis):
252        """
253        Queries device file for absinfo structure for given absolute axis.
254        """
255        # struct input_absinfo is 6 __s32
256        a = array.array('i', [0] * 6)
257        fcntl.ioctl(self.f, EVIOCGABS(axis), a, 1)
258        return AbsValuator(a[0], a[1], a[2], a[3], a[4], a[5])
259
260    def _ioctl_codes(self, ev_type):
261        """
262        Queries device file for supported event codes for given event type.
263        """
264        self.events[ev_type] = {}
265        if ev_type not in EV_SIZES:
266            return
267
268        size = EV_SIZES[ev_type] // 8    # Convert bits to bytes
269        ev_code = array.array('B', [0] * size)
270        try:
271            count = fcntl.ioctl(self.f, EVIOCGBIT(ev_type, size), ev_code, 1)
272            for c in range(count * 8):
273                if test_bit(c, ev_code):
274                    if ev_type == EV_ABS:
275                        self.events[ev_type][c] = self._ioctl_absinfo(c)
276                    elif ev_type == EV_SW:
277                        self.events[ev_type][c] = self._ioctl_get_switch(c)
278                    else:
279                        self.events[ev_type][c] = Valuator()
280        except IOError as errs:
281            # Errno 22 signifies that this event type has no event codes.
282            (errno, strerror) = errs.args
283            if errno != 22:
284                raise
285
286    def _ioctl_types(self):
287        """ Queries device file for supported event types. """
288        ev_types = array.array('B', [0] * (EV_CNT // 8))
289        fcntl.ioctl(self.f, EVIOCGBIT(EV_SYN, EV_CNT // 8), ev_types, 1)
290        types  = []
291        for t in range(EV_CNT):
292            if test_bit(t, ev_types):
293                types.append(t)
294        return types
295
296    def _convert_slot_index_to_slot_id(self, index):
297        """ Convert a slot index in self.mt_slots to its slot id. """
298        return self.abs_mt_slot.min + index
299
300    def _ioctl_mt_slots(self):
301        """Query mt slots values using ioctl.
302
303        The ioctl buffer argument should be binary equivalent to
304        struct input_mt_request_layout {
305            __u32 code;
306            __s32 values[num_slots];
307
308        Note that the slots information returned by EVIOCGMTSLOTS
309        corresponds to the slot ids ranging from abs_mt_slot.min to
310        abs_mt_slot.max which is not necessarily the same as the
311        slot indexes ranging from 0 to num_slots - 1 in self.mt_slots.
312        We need to map between the slot index and the slot id correctly.
313        };
314        """
315        # Iterate through the absolute mt events that are supported.
316        for c in range(ABS_MT_FIRST, ABS_MT_LAST):
317            if c not in self.events[EV_ABS]:
318                continue
319            # Sync with evdev kernel driver for the specified code c.
320            mt_slot_info = array.array('i', [c] + [0] * self.num_slots)
321            mt_slot_info_len = (self.num_slots + 1) * mt_slot_info.itemsize
322            fcntl.ioctl(self.f, EVIOCGMTSLOTS(mt_slot_info_len), mt_slot_info)
323            values = mt_slot_info[1:]
324            for slot_index in range(self.num_slots):
325                slot_id = self._convert_slot_index_to_slot_id(slot_index)
326                self.mt_slots[slot_index][c].value = values[slot_id]
327
328    def _setup_mt_slots(self):
329        """
330        Sets up the device's mt_slots array.
331
332        Each element of the mt_slots array is initialized as a deepcopy of a
333        dict containing all of the MT valuators from the events dict.
334        """
335        # TODO(djkurtz): MT-A
336        if not self.is_mt_b():
337            return
338        ev_abs = self.events[EV_ABS]
339        # Create dict containing just the MT valuators
340        mt_abs_info = dict((axis, ev_abs[axis])
341                           for axis in ev_abs
342                           if axis in ABS_MT_RANGE)
343
344        # Initialize TRACKING_ID to -1
345        mt_abs_info[ABS_MT_TRACKING_ID].value = -1
346
347        # Make a copy of mt_abs_info for each MT slot
348        self.abs_mt_slot = ev_abs[ABS_MT_SLOT]
349        self.num_slots = self.abs_mt_slot.max - self.abs_mt_slot.min + 1
350        for s in range(self.num_slots):
351            self.mt_slots.append(copy.deepcopy(mt_abs_info))
352
353        self._ioctl_mt_slots()
354
355    def get_current_slot_id(self):
356        """
357        Return the current slot id.
358        """
359        if not self.is_mt_b():
360            return None
361        return self.events[EV_ABS][ABS_MT_SLOT].value
362
363    def _get_current_slot(self):
364        """
365        Returns the current slot, as indicated by the last ABS_MT_SLOT event.
366        """
367        current_slot_id = self.get_current_slot_id()
368        if current_slot_id is None:
369            return None
370        return self.mt_slots[current_slot_id]
371
372    def _get_tid(self, slot):
373        """ Returns the tracking_id for the given MT slot. """
374        return slot[ABS_MT_TRACKING_ID].value
375
376    def _get_mt_valid_slots(self):
377        """
378        Returns a list of valid slots.
379
380        A valid slot is a slot whose tracking_id != -1.
381        """
382        return [s for s in self.mt_slots if self._get_tid(s) != -1]
383
384    def _get_mt_primary_slot(self):
385        """
386        Returns the "primary" MT-B slot.
387
388        The "primary" MT-B slot is arbitrarily chosen as the slot with lowest
389        tracking_id (> -1).  It is used to make an MT-B device look like
390        single-touch (ST) device.
391        """
392        slot = None
393        for s in self.mt_slots:
394            tid = self._get_tid(s)
395            if tid < 0:
396                continue
397            if not slot or tid < self._get_tid(slot):
398                slot = s
399        return slot
400
401    def _code_if_mt(self, type, code):
402        """
403        Returns MT-equivalent event code for certain specific event codes
404        """
405        if type != EV_ABS:
406            return code
407        elif code == ABS_X:
408            return  ABS_MT_POSITION_X
409        elif code == ABS_Y:
410            return ABS_MT_POSITION_Y
411        elif code == ABS_PRESSURE:
412            return ABS_MT_PRESSURE
413        elif code == ABS_TOOL_WIDTH:
414            return ABS_TOUCH_MAJOR
415        else:
416            return code
417
418    def _get_valuator(self, type, code):
419        """ Returns Valuator for given event type and code """
420        if (not type in self.events) or (not code in self.events[type]):
421            return None
422        if type == EV_ABS:
423            code = self._code_if_mt(type, code)
424        return self.events[type][code]
425
426    def _get_value(self, type, code):
427        """
428        Returns the value of the valuator with the give event (type, code).
429        """
430        axis = self._get_valuator(type, code)
431        if not axis:
432            return None
433        return axis.value
434
435    def _get_min(self, type, code):
436        """
437        Returns the min value of the valuator with the give event (type, code).
438
439        Note: Only AbsValuators (EV_ABS) have max values.
440        """
441        axis = self._get_valuator(type, code)
442        if not axis:
443            return None
444        return axis.min
445
446    def _get_max(self, type, code):
447        """
448        Returns the min value of the valuator with the give event (type, code).
449
450        Note: Only AbsValuators (EV_ABS) have max values.
451        """
452        axis = self._get_valuator(type, code)
453        if not axis:
454            return None
455        return axis.max
456
457    """ Public accessors """
458
459    def get_num_fingers(self):
460        if self.is_mt_b():
461            return len(self._get_mt_valid_slots())
462        elif self.is_mt_a():
463            return 0  # TODO(djkurtz): MT-A
464        else:  # Single-Touch case
465            if not self._get_value(EV_KEY, BTN_TOUCH) == 1:
466                return 0
467            elif self._get_value(EV_KEY, BTN_TOOL_TRIPLETAP) == 1:
468                return 3
469            elif self._get_value(EV_KEY, BTN_TOOL_DOUBLETAP) == 1:
470                return 2
471            elif self._get_value(EV_KEY, BTN_TOOL_FINGER) == 1:
472                return 1
473            else:
474                return 0
475
476    def get_x(self):
477        return self._get_value(EV_ABS, ABS_X)
478
479    def get_x_min(self):
480        return self._get_min(EV_ABS, ABS_X)
481
482    def get_x_max(self):
483        return self._get_max(EV_ABS, ABS_X)
484
485    def get_y(self):
486        return self._get_value(EV_ABS, ABS_Y)
487
488    def get_y_min(self):
489        return self._get_min(EV_ABS, ABS_Y)
490
491    def get_y_max(self):
492        return self._get_max(EV_ABS, ABS_Y)
493
494    def get_pressure(self):
495        return self._get_value(EV_ABS, ABS_PRESSURE)
496
497    def get_pressure_min(self):
498        return self._get_min(EV_ABS, ABS_PRESSURE)
499
500    def get_pressure_max(self):
501        return self._get_max(EV_ABS, ABS_PRESSURE)
502
503    def get_left(self):
504        return int(self._get_value(EV_KEY, BTN_LEFT) == 1)
505
506    def get_right(self):
507        return int(self._get_value(EV_KEY, BTN_RIGHT) == 1)
508
509    def get_middle(self):
510        return int(self._get_value(EV_KEY, BTN_MIDDLE) == 1)
511
512    def get_microphone_insert(self):
513        return self._get_value(EV_SW, SW_MICROPHONE_INSERT)
514
515    def get_headphone_insert(self):
516        return self._get_value(EV_SW, SW_HEADPHONE_INSERT)
517
518    def get_lineout_insert(self):
519        return self._get_value(EV_SW, SW_LINEOUT_INSERT)
520
521    def is_touchpad(self):
522        return ((EV_KEY in self.events) and
523                (BTN_TOOL_FINGER in self.events[EV_KEY]) and
524                (EV_ABS in self.events))
525
526    def is_keyboard(self):
527        if EV_KEY not in self.events:
528            return False
529        # Check first 31 keys. This is the same method udev and the
530        # Chromium browser use.
531        for key in range(KEY_ESC, KEY_D + 1):
532            if key not in self.events[EV_KEY]:
533                return False
534        return True
535
536    def is_touchscreen(self):
537        return ((EV_KEY in self.events) and
538                (BTN_TOUCH in self.events[EV_KEY]) and
539                (not BTN_TOOL_FINGER in self.events[EV_KEY]) and
540                (EV_ABS in self.events))
541
542    def is_lid(self):
543        return ((EV_SW in self.events) and
544                (SW_LID in self.events[EV_SW]))
545
546    def is_mt_b(self):
547        return self.is_mt() and ABS_MT_SLOT in self.events[EV_ABS]
548
549    def is_mt_a(self):
550        return self.is_mt() and ABS_MT_SLOT not in self.events[EV_ABS]
551
552    def is_mt(self):
553        return (EV_ABS in self.events and
554                (set(self.events[EV_ABS]) & set(ABS_MT_RANGE)))
555
556    def is_hp_jack(self):
557        return (EV_SW in self.events and
558                SW_HEADPHONE_INSERT in self.events[EV_SW])
559
560    def is_mic_jack(self):
561        return (EV_SW in self.events and
562                SW_MICROPHONE_INSERT in self.events[EV_SW])
563
564    def is_audio_jack(self):
565        return (EV_SW in self.events and
566                ((SW_HEADPHONE_INSERT in self.events[EV_SW]) or
567                 (SW_MICROPHONE_INSERT in self.events[EV_SW] or
568                 (SW_LINEOUT_INSERT in self.events[EV_SW]))))
569
570    """ Debug helper print functions """
571
572    def print_abs_info(self, axis):
573        if EV_ABS in self.events and axis in self.events[EV_ABS]:
574            a = self.events[EV_ABS][axis]
575            print('      Value       %6d' % a.value)
576            print('      Min         %6d' % a.min)
577            print('      Max         %6d' % a.max)
578            if a.fuzz != 0:
579                print('      Fuzz        %6d' % a.fuzz)
580            if a.flat != 0:
581                print('      Flat        %6d' % a.flat)
582            if a.resolution != 0:
583                print('      Resolution  %6d' % a.resolution)
584
585    def print_props(self):
586        print(('Input driver Version: %d.%d.%d' %
587               (self.version[0], self.version[1], self.version[2])))
588        print(('Input device ID: bus %x vendor %x product %x version %x' %
589               (self.id_bus, self.id_vendor, self.id_product, self.id_version)))
590        print('Input device name: "%s"' % (self.name))
591        for t in self.events:
592            print('  Event type %d (%s)' % (t, EV_TYPES.get(t, '?')))
593            for c in self.events[t]:
594                if (t in EV_STRINGS):
595                    code = EV_STRINGS[t].get(c, '?')
596                    print('    Event code %s (%d)' % (code, c))
597                else:
598                    print('    Event code (%d)' % (c))
599                self.print_abs_info(c)
600
601    def get_slots(self):
602        """ Get those slots with positive tracking IDs. """
603        slot_dict = OrderedDict()
604        for slot_index in range(self.num_slots):
605            slot = self.mt_slots[slot_index]
606            if self._get_tid(slot) == -1:
607                continue
608            slot_id = self._convert_slot_index_to_slot_id(slot_index)
609            slot_dict[slot_id] = slot
610        return slot_dict
611
612    def print_slots(self):
613        slot_dict = self.get_slots()
614        for slot_id, slot in slot_dict.items():
615            print('slot #%d' % slot_id)
616            for a in slot:
617                abs = EV_STRINGS[EV_ABS].get(a, '?')
618                print('  %s = %6d' % (abs, slot[a].value))
619
620
621def print_report(device):
622    print('----- EV_SYN -----')
623    if device.is_touchpad():
624        f = device.get_num_fingers()
625        if f == 0:
626            return
627        x = device.get_x()
628        y = device.get_y()
629        z = device.get_pressure()
630        l = device.get_left()
631        print('Left=%d Fingers=%d X=%d Y=%d Pressure=%d' % (l, f, x, y, z))
632        if device.is_mt():
633            device.print_slots()
634
635
636def get_device_node(device_type):
637    """Get the keyboard device node through device info file.
638
639    Example of the keyboard device information looks like
640
641    I: Bus=0011 Vendor=0001 Product=0001 Version=ab41
642    N: Name="AT Translated Set 2 keyboard"
643    P: Phys=isa0060/serio0/input0
644    S: Sysfs=/devices/platform/i8042/serio0/input/input5
645    U: Uniq=
646    H: Handlers=sysrq kbd event5
647    """
648    device_node = None
649    device_found = None
650    device_pattern = re.compile('N: Name=.*%s' % device_type, re.I)
651    event_number_pattern = re.compile(r'H: Handlers=.*event(\d?)', re.I)
652    with open(_DEVICE_INFO_FILE) as info:
653        for line in info:
654            if device_found:
655                result = event_number_pattern.search(line)
656                if result:
657                    event_number = int(result.group(1))
658                    device_node = '/dev/input/event%d' % event_number
659                    break
660            else:
661                device_found = device_pattern.search(line)
662    return device_node
663
664
665if __name__ == "__main__":
666    from optparse import OptionParser
667    import glob
668    parser = OptionParser()
669
670    parser.add_option("-a", "--audio_jack", action="store_true",
671                      dest="audio_jack", default=False,
672                      help="Find and use all audio jacks")
673    parser.add_option("-d", "--devpath", dest="devpath",
674                      default="/dev/input/event0",
675                      help="device path (/dev/input/event0)")
676    parser.add_option("-q", "--quiet", action="store_false", dest="verbose",
677                      default=True, help="print less messages to stdout")
678    parser.add_option("-t", "--touchpad", action="store_true", dest="touchpad",
679                      default=False, help="Find and use first touchpad device")
680    (options, args) = parser.parse_args()
681
682    # TODO: Use gudev to detect touchpad
683    devices = []
684    if options.touchpad:
685        for path in glob.glob('/dev/input/event*'):
686            device = InputDevice(path)
687            if device.is_touchpad():
688                print('Using touchpad %s.' % path)
689                options.devpath = path
690                devices.append(device)
691                break
692        else:
693            print('No touchpad found!')
694            exit()
695    elif options.audio_jack:
696        for path in glob.glob('/dev/input/event*'):
697            device = InputDevice(path)
698            if device.is_audio_jack():
699                devices.append(device)
700        device = None
701    elif os.path.exists(options.devpath):
702        print('Using %s.' % options.devpath)
703        devices.append(InputDevice(options.devpath))
704    else:
705        print('%s does not exist.' % options.devpath)
706        exit()
707
708    for device in devices:
709        device.print_props()
710        if device.is_touchpad():
711            print(('x: (%d,%d), y: (%d,%d), z: (%d, %d)' %
712                   (device.get_x_min(), device.get_x_max(),
713                    device.get_y_min(), device.get_y_max(),
714                    device.get_pressure_min(), device.get_pressure_max())))
715            device.print_slots()
716            print('Number of fingers: %d' % device.get_num_fingers())
717            print('Current slot id: %d' % device.get_current_slot_id())
718    print('------------------')
719    print()
720
721    ev = InputEvent()
722    while True:
723        _rl, _, _ = select.select([d.f for d in devices], [], [])
724        for fd in _rl:
725            # Lookup for the device which owns fd.
726            device = [d for d in devices if d.f == fd][0]
727            try:
728                ev.read(fd)
729            except KeyboardInterrupt:
730                exit()
731            is_syn = device.process_event(ev)
732            print(ev)
733            if is_syn:
734                print_report(device)
735