• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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