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