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