1# Lint as: python2, python3 2# Copyright 2017 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"""This module provides functions to record input events.""" 7 8from __future__ import division 9from __future__ import print_function 10 11import logging 12import re 13import select 14import subprocess 15import threading 16import time 17 18from linux_input import EV_MSC, EV_SYN, MSC_SCAN, SYN_REPORT 19 20 21# Define extra misc events below as they are not defined in linux_input. 22MSC_SCAN_BTN_LEFT = 90001 23MSC_SCAN_BTN_RIGHT = 90002 24MSC_SCAN_BTN_MIDDLE = 90003 25 26 27class InputEventRecorderError(Exception): 28 """An exception class for input_event_recorder module.""" 29 pass 30 31 32class Event(object): 33 """An event class based on evtest constructed from an evtest event. 34 35 An ordinary event looks like: 36 Event: time 133082.748019, type 3 (EV_ABS), code 0 (ABS_X), value 316 37 38 A SYN_REPORT event looks like: 39 Event: time 10788.289613, -------------- SYN_REPORT ------------ 40 41 """ 42 43 def __init__(self, type=0, code=0, value=0): 44 """Construction of an input event. 45 46 @param type: the event type 47 @param code: the event code 48 @param value: the event value 49 50 """ 51 self.type = type 52 self.code = code 53 self.value= value 54 55 56 @staticmethod 57 def from_string(ev_string): 58 """Convert an event string to an event object. 59 60 @param ev_string: an event string. 61 62 @returns: an event object if the event string conforms to 63 event pattern format. None otherwise. 64 65 """ 66 # Get the pattern of an ordinary event 67 ev_pattern_time = r'Event:\s*time\s*(\d+\.\d+)' 68 ev_pattern_type = r'type\s*(\d+)\s*\(\w+\)' 69 ev_pattern_code = r'code\s*(\d+)\s*\(\w+\)' 70 ev_pattern_value = r'value\s*(-?\d+)' 71 ev_sep = r',\s*' 72 ev_pattern_str = ev_sep.join([ev_pattern_time, 73 ev_pattern_type, 74 ev_pattern_code, 75 ev_pattern_value]) 76 ev_pattern = re.compile(ev_pattern_str, re.I) 77 78 # Get the pattern of the SYN_REPORT event 79 ev_pattern_type_SYN_REPORT = r'-+\s*SYN_REPORT\s-+' 80 ev_pattern_SYN_REPORT_str = ev_sep.join([ev_pattern_time, 81 ev_pattern_type_SYN_REPORT]) 82 ev_pattern_SYN_REPORT = re.compile(ev_pattern_SYN_REPORT_str, re.I) 83 84 # Check if it is a SYN event. 85 result = ev_pattern_SYN_REPORT.search(ev_string) 86 if result: 87 return Event(EV_SYN, SYN_REPORT, 0) 88 else: 89 # Check if it is a regular event. 90 result = ev_pattern.search(ev_string) 91 if result: 92 ev_type = int(result.group(2)) 93 ev_code = int(result.group(3)) 94 ev_value = int(result.group(4)) 95 return Event(ev_type, ev_code, ev_value) 96 else: 97 return None 98 99 100 def is_syn(self): 101 """Determine if the event is a SYN report event. 102 103 @returns: True if it is a SYN report event. False otherwise. 104 105 """ 106 return self.type == EV_SYN and self.code == SYN_REPORT 107 108 109 def value_tuple(self): 110 """A tuple of the event type, code, and value. 111 112 @returns: the tuple of the event type, code, and value. 113 114 """ 115 return (self.type, self.code, self.value) 116 117 118 def __eq__(self, other): 119 """determine if two events are equal. 120 121 @param line: an event string line. 122 123 @returns: True if two events are equal. False otherwise. 124 125 """ 126 return (self.type == other.type and 127 self.code == other.code and 128 self.value == other.value) 129 130 131 def __str__(self): 132 """A string representation of the event. 133 134 @returns: a string representation of the event. 135 136 """ 137 return '%d %d %d' % (self.type, self.code, self.value) 138 139 140class InputEventRecorder(object): 141 """An input event recorder. 142 143 Refer to recording_example() below about how to record input events. 144 145 """ 146 147 INPUT_DEVICE_INFO_FILE = '/proc/bus/input/devices' 148 SELECT_TIMEOUT_SECS = 1 149 150 def __init__(self, device_name, uniq): 151 """Construction of input event recorder. 152 153 @param device_name: the device name of the input device node to record. 154 @param uniq: Unique address of input device (None if not used) 155 156 """ 157 self.device_name = device_name 158 self.uniq = uniq 159 self.device_node = self.get_device_node_by_name(device_name, uniq) 160 if self.device_node is None: 161 err_msg = 'Failed to find the device node of %s' % device_name 162 raise InputEventRecorderError(err_msg) 163 self._recording_thread = None 164 self._stop_recording_thread_event = threading.Event() 165 self.tmp_file = '/tmp/evtest.dat' 166 self.events = [] 167 168 169 def get_device_node_by_name(self, device_name, uniq): 170 """Get the input device node by name. 171 172 Example of a RN-42 emulated mouse device information looks like 173 174 I: Bus=0005 Vendor=0000 Product=0000 Version=0000 175 N: Name="RNBT-A96F" 176 P: Phys=6c:29:95:1a:b8:18 177 S: Sysfs=/devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8:1.0/bluetooth/hci0/hci0:512:29/0005:0000:0000.0004/input/input15 178 U: Uniq=00:06:66:75:a9:6f 179 H: Handlers=event12 180 B: PROP=0 181 B: EV=17 182 B: KEY=70000 0 0 0 0 183 B: REL=103 184 B: MSC=10 185 186 Each group of input devices is separated by an empty line. 187 188 @param device_name: the device name of the target input device node. 189 @param uniq: Unique address of the device. None if unused. 190 191 @returns: the corresponding device node of the device. 192 193 """ 194 device_node = None 195 device_found = None 196 event_number = None 197 uniq_found = None 198 uniq = uniq.lower() if uniq else None 199 200 entry_pattern = re.compile('^[A-Z]: ') 201 device_pattern = re.compile('N: Name=.*%s' % device_name, re.I) 202 event_number_pattern = re.compile('H: Handlers=.*event(\d*)', re.I) 203 uniq_pattern = re.compile('U: Uniq=([a-zA-Z0-9:]+)') 204 205 with open(self.INPUT_DEVICE_INFO_FILE) as info: 206 for line in info: 207 line = line.rstrip('\n') 208 209 if not entry_pattern.search(line): 210 device_found = None 211 event_number = None 212 uniq_found = None 213 elif device_found: 214 # Check if this is an event line 215 find_event = event_number_pattern.search(line) 216 if find_event: 217 event_number = int(find_event.group(1)) 218 219 # Check if this a uniq line 220 find_uniq = uniq_pattern.search(line) 221 if find_uniq: 222 uniq_found = find_uniq.group(1).lower() 223 224 # If uniq matches expectations, we found the device node 225 if event_number and (not uniq or uniq_found == uniq): 226 device_node = '/dev/input/event%d' % event_number 227 break 228 229 else: 230 device_found = device_pattern.search(line) 231 232 return device_node 233 234 235 def record(self): 236 """Record input events.""" 237 logging.info('Recording input events of %s.', self.device_node) 238 cmd = 'evtest %s' % self.device_node 239 recorder = subprocess.Popen(cmd, stdout=subprocess.PIPE, 240 shell=True) 241 with open(self.tmp_file, 'w') as output_f: 242 while True: 243 read_list, _, _ = select.select( 244 [recorder.stdout], [], [], 1) 245 if read_list: 246 line = recorder.stdout.readline() 247 output_f.write(line) 248 ev = Event.from_string(line) 249 if ev: 250 self.events.append(ev.value_tuple()) 251 elif self._stop_recording_thread_event.is_set(): 252 self._stop_recording_thread_event.clear() 253 break 254 255 recorder.terminate() 256 257 258 def start(self): 259 """Start the recording thread.""" 260 logging.info('Start recording thread.') 261 self._recording_thread = threading.Thread(target=self.record) 262 self._recording_thread.start() 263 264 265 def stop(self): 266 """Stop the recording thread.""" 267 logging.info('Stop recording thread.') 268 self._stop_recording_thread_event.set() 269 self._recording_thread.join() 270 271 272 def clear_events(self): 273 """Clear the event list.""" 274 self.events = [] 275 276 277 def get_events(self): 278 """Get the event list. 279 280 @returns: the event list. 281 """ 282 return self.events 283 284 285SYN_EVENT = Event(EV_SYN, SYN_REPORT, 0) 286MSC_SCAN_BTN_EVENT = {'LEFT': Event(EV_MSC, MSC_SCAN, MSC_SCAN_BTN_LEFT), 287 'RIGHT': Event(EV_MSC, MSC_SCAN, MSC_SCAN_BTN_RIGHT), 288 'MIDDLE': Event(EV_MSC, MSC_SCAN, MSC_SCAN_BTN_MIDDLE)} 289 290 291def recording_example(): 292 """Example code for capturing input events on a Samus. 293 294 For a quick swipe, it outputs events in numeric format: 295 296 (3, 57, 9) 297 (3, 53, 641) 298 (3, 54, 268) 299 (3, 58, 60) 300 (3, 48, 88) 301 (1, 330, 1) 302 (1, 325, 1) 303 (3, 0, 641) 304 (3, 1, 268) 305 (3, 24, 60) 306 (0, 0, 0) 307 (3, 53, 595) 308 (3, 54, 288) 309 (3, 0, 595) 310 (3, 1, 288) 311 (0, 0, 0) 312 (3, 57, -1) 313 (1, 330, 0) 314 (1, 325, 0) 315 (3, 24, 0) 316 (0, 0, 0) 317 318 The above events in corresponding evtest text format are: 319 320 Event: time .782950, type 3 (EV_ABS), code 57 (ABS_MT_TRACKING_ID), value 9 321 Event: time .782950, type 3 (EV_ABS), code 53 (ABS_MT_POSITION_X), value 641 322 Event: time .782950, type 3 (EV_ABS), code 54 (ABS_MT_POSITION_Y), value 268 323 Event: time .782950, type 3 (EV_ABS), code 58 (ABS_MT_PRESSURE), value 60 324 Event: time .782950, type 3 (EV_ABS), code 59 (?), value 0 325 Event: time .782950, type 3 (EV_ABS), code 48 (ABS_MT_TOUCH_MAJOR), value 88 326 Event: time .782950, type 1 (EV_KEY), code 330 (BTN_TOUCH), value 1 327 Event: time .782950, type 1 (EV_KEY), code 325 (BTN_TOOL_FINGER), value 1 328 Event: time .782950, type 3 (EV_ABS), code 0 (ABS_X), value 641 329 Event: time .782950, type 3 (EV_ABS), code 1 (ABS_Y), value 268 330 Event: time .782950, type 3 (EV_ABS), code 24 (ABS_PRESSURE), value 60 331 Event: time .782950, -------------- SYN_REPORT ------------ 332 Event: time .798273, type 3 (EV_ABS), code 53 (ABS_MT_POSITION_X), value 595 333 Event: time .798273, type 3 (EV_ABS), code 54 (ABS_MT_POSITION_Y), value 288 334 Event: time .798273, type 3 (EV_ABS), code 0 (ABS_X), value 595 335 Event: time .798273, type 3 (EV_ABS), code 1 (ABS_Y), value 288 336 Event: time .798273, -------------- SYN_REPORT ------------ 337 Event: time .821437, type 3 (EV_ABS), code 57 (ABS_MT_TRACKING_ID), value -1 338 Event: time .821437, type 1 (EV_KEY), code 330 (BTN_TOUCH), value 0 339 Event: time .821437, type 1 (EV_KEY), code 325 (BTN_TOOL_FINGER), value 0 340 Event: time .821437, type 3 (EV_ABS), code 24 (ABS_PRESSURE), value 0 341 Event: time .821437, -------------- SYN_REPORT ------------ 342 """ 343 device_name = 'Atmel maXTouch Touchpad' 344 recorder = InputEventRecorder(device_name) 345 print('Samus touchpad device name:', recorder.device_name) 346 print('Samus touchpad device node:', recorder.device_node) 347 print('Please make gestures on the touchpad for up to 5 seconds.') 348 recorder.clear_events() 349 recorder.start() 350 time.sleep(5) 351 recorder.stop() 352 for e in recorder.get_events(): 353 print(e) 354 355 356if __name__ == '__main__': 357 recording_example() 358