input_device.py revision 13e353729c400d029e9167ded3cdc3032cbf8411
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_mt_b(self):
532        return self.is_mt() and ABS_MT_SLOT in self.events[EV_ABS]
533
534    def is_mt_a(self):
535        return self.is_mt() and ABS_MT_SLOT not in self.events[EV_ABS]
536
537    def is_mt(self):
538        return (EV_ABS in self.events and
539                (set(self.events[EV_ABS]) & set(ABS_MT_RANGE)))
540
541    def is_hp_jack(self):
542        return (EV_SW in self.events and
543                SW_HEADPHONE_INSERT in self.events[EV_SW])
544
545    def is_mic_jack(self):
546        return (EV_SW in self.events and
547                SW_MICROPHONE_INSERT in self.events[EV_SW])
548
549    def is_audio_jack(self):
550        return (EV_SW in self.events and
551                ((SW_HEADPHONE_INSERT in self.events[EV_SW]) or
552                 (SW_MICROPHONE_INSERT in self.events[EV_SW] or
553                 (SW_LINEOUT_INSERT in self.events[EV_SW]))))
554
555    """ Debug helper print functions """
556
557    def print_abs_info(self, axis):
558        if EV_ABS in self.events and axis in self.events[EV_ABS]:
559            a = self.events[EV_ABS][axis]
560            print '      Value       %6d' % a.value
561            print '      Min         %6d' % a.min
562            print '      Max         %6d' % a.max
563            if a.fuzz != 0:
564                print '      Fuzz        %6d' % a.fuzz
565            if a.flat != 0:
566                print '      Flat        %6d' % a.flat
567            if a.resolution != 0:
568                print '      Resolution  %6d' % a.resolution
569
570    def print_props(self):
571        print ('Input driver Version: %d.%d.%d' %
572               (self.version[0], self.version[1], self.version[2]))
573        print ('Input device ID: bus %x vendor %x product %x version %x' %
574               (self.id_bus, self.id_vendor, self.id_product, self.id_version))
575        print 'Input device name: "%s"' % (self.name)
576        for t in self.events:
577            print '  Event type %d (%s)' % (t, EV_TYPES.get(t, '?'))
578            for c in self.events[t]:
579                if (t in EV_STRINGS):
580                    code = EV_STRINGS[t].get(c, '?')
581                    print '    Event code %s (%d)' % (code, c)
582                else:
583                    print '    Event code (%d)' % (c)
584                self.print_abs_info(c)
585
586    def get_slots(self):
587        """ Get those slots with positive tracking IDs. """
588        slot_dict = OrderedDict()
589        for slot_index in range(self.num_slots):
590            slot = self.mt_slots[slot_index]
591            if self._get_tid(slot) == -1:
592                continue
593            slot_id = self._convert_slot_index_to_slot_id(slot_index)
594            slot_dict[slot_id] = slot
595        return slot_dict
596
597    def print_slots(self):
598        slot_dict = self.get_slots()
599        for slot_id, slot in slot_dict.items():
600            print 'slot #%d' % slot_id
601            for a in slot:
602                abs = EV_STRINGS[EV_ABS].get(a, '?')
603                print '  %s = %6d' % (abs, slot[a].value)
604
605
606def print_report(device):
607    print '----- EV_SYN -----'
608    if device.is_touchpad():
609        f = device.get_num_fingers()
610        if f == 0:
611            return
612        x = device.get_x()
613        y = device.get_y()
614        z = device.get_pressure()
615        l = device.get_left()
616        print 'Left=%d Fingers=%d X=%d Y=%d Pressure=%d' % (l, f, x, y, z)
617        if device.is_mt():
618            device.print_slots()
619
620
621def get_device_node(device_type):
622    """Get the keyboard device node through device info file.
623
624    Example of the keyboard device information looks like
625
626    I: Bus=0011 Vendor=0001 Product=0001 Version=ab41
627    N: Name="AT Translated Set 2 keyboard"
628    P: Phys=isa0060/serio0/input0
629    S: Sysfs=/devices/platform/i8042/serio0/input/input5
630    U: Uniq=
631    H: Handlers=sysrq kbd event5
632    """
633    device_node = None
634    device_found = None
635    device_pattern = re.compile('N: Name=.*%s' % device_type, re.I)
636    event_number_pattern = re.compile(r'H: Handlers=.*event(\d?)', re.I)
637    with open(_DEVICE_INFO_FILE) as info:
638        for line in info:
639            if device_found:
640                result = event_number_pattern.search(line)
641                if result:
642                    event_number = int(result.group(1))
643                    device_node = '/dev/input/event%d' % event_number
644                    break
645            else:
646                device_found = device_pattern.search(line)
647    return device_node
648
649
650if __name__ == "__main__":
651    from optparse import OptionParser
652    import glob
653    parser = OptionParser()
654
655    parser.add_option("-a", "--audio_jack", action="store_true",
656                      dest="audio_jack", default=False,
657                      help="Find and use all audio jacks")
658    parser.add_option("-d", "--devpath", dest="devpath",
659                      default="/dev/input/event0",
660                      help="device path (/dev/input/event0)")
661    parser.add_option("-q", "--quiet", action="store_false", dest="verbose",
662                      default=True, help="print less messages to stdout")
663    parser.add_option("-t", "--touchpad", action="store_true", dest="touchpad",
664                      default=False, help="Find and use first touchpad device")
665    (options, args) = parser.parse_args()
666
667    # TODO: Use gudev to detect touchpad
668    devices = []
669    if options.touchpad:
670        for path in glob.glob('/dev/input/event*'):
671            device = InputDevice(path)
672            if device.is_touchpad():
673                print 'Using touchpad %s.' % path
674                options.devpath = path
675                devices.append(device)
676                break
677        else:
678            print 'No touchpad found!'
679            exit()
680    elif options.audio_jack:
681        for path in glob.glob('/dev/input/event*'):
682            device = InputDevice(path)
683            if device.is_audio_jack():
684                devices.append(device)
685        device = None
686    elif os.path.exists(options.devpath):
687        print 'Using %s.' % options.devpath
688        devices.append(InputDevice(options.devpath))
689    else:
690        print '%s does not exist.' % options.devpath
691        exit()
692
693    for device in devices:
694        device.print_props()
695        if device.is_touchpad():
696            print ('x: (%d,%d), y: (%d,%d), z: (%d, %d)' %
697                   (device.get_x_min(), device.get_x_max(),
698                    device.get_y_min(), device.get_y_max(),
699                    device.get_pressure_min(), device.get_pressure_max()))
700            device.print_slots()
701            print 'Number of fingers: %d' % device.get_num_fingers()
702            print 'Current slot id: %d' % device.get_current_slot_id()
703    print '------------------'
704    print
705
706    ev = InputEvent()
707    while True:
708        _rl, _, _ = select.select([d.f for d in devices], [], [])
709        for fd in _rl:
710            # Lookup for the device which owns fd.
711            device = [d for d in devices if d.f == fd][0]
712            try:
713                ev.read(fd)
714            except KeyboardInterrupt:
715                exit()
716            is_syn = device.process_event(ev)
717            print ev
718            if is_syn:
719                print_report(device)
720