1"""
2The evemu module provides the Python interface to the kernel-level input device
3raw events.
4"""
5
6# Copyright 2011-2012 Canonical Ltd.
7# Copyright 2014 Red Hat, Inc.
8#
9# This library is free software: you can redistribute it and/or modify it
10# under the terms of the GNU Lesser General Public License version 3
11# as published by the Free Software Foundation.
12#
13# This library is distributed in the hope that it will be useful, but
14# WITHOUT ANY WARRANTY; without even the implied warranties of
15# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
16# PURPOSE.  See the GNU General Public License for more details.
17#
18# You should have received a copy of the GNU Lesser General Public License
19# along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
21import ctypes
22import glob
23import os
24import re
25import stat
26import tempfile
27
28import evemu.base
29
30__all__ = ["Device",
31           "InputEvent",
32           "event_get_value",
33           "event_get_name",
34           "input_prop_get_value",
35           "input_prop_get_name"]
36
37_libevdev = evemu.base.LibEvdev()
38
39def event_get_value(event_type, event_code = None):
40    """
41    Return the integer-value for the given event type and/or code string
42    e.g. "EV_ABS" returns 0x03, ("EV_ABS", "ABS_Y") returns 0x01.
43    Unknown event types or type/code combinations return None.
44
45    If an event code is passed, the event type may be given as integer or
46    string.
47    """
48    t = -1
49    c = -1
50
51    if isinstance(event_type, int):
52        event_type = _libevdev.libevdev_event_type_get_name(event_type)
53        if event_type is None:
54            return None
55
56        event_type = event_type.decode("iso8859-1")
57
58    event_type = str(event_type).encode("iso8859-1")
59    t = _libevdev.libevdev_event_type_from_name(event_type)
60
61    if event_code is None:
62        return None if t < 0 else t
63
64    if isinstance(event_code, int):
65        event_code = _libevdev.libevdev_event_code_get_name(t, event_code)
66        if event_code is None:
67            return None
68
69        event_code = event_code.decode("iso8859-1")
70
71    event_code = str(event_code).encode("iso8859-1")
72    c = _libevdev.libevdev_event_code_from_name(t, event_code)
73
74    return None if c < 0 else c
75
76def event_get_name(event_type, event_code = None):
77    """
78    Return the string-value for the given event type and/or code value
79    e.g. 0x03 returns "EV_ABS", ("EV_ABS", 0x01) returns "ABS_Y"
80    Unknown event types or type/code combinations return None.
81
82    If an event code is passed, the event type may be given as integer or
83    string.
84    """
85    if not isinstance(event_type, int):
86        event_type = event_get_value(event_type)
87
88    if event_type is None:
89        return None
90
91    if event_code is None:
92        type_name = _libevdev.libevdev_event_type_get_name(event_type)
93
94        if type_name is None:
95            return None
96
97        return type_name.decode("iso8859-1")
98
99    if not isinstance(event_code, int):
100        event_code = event_get_value(event_type, event_code)
101
102    if event_code is None:
103        return None
104
105    code_name = _libevdev.libevdev_event_code_get_name(event_type, event_code)
106    if code_name is None:
107        return None
108
109    return code_name.decode("iso8859-1")
110
111def input_prop_get_name(prop):
112    """
113    Return the name of the input property, or None if undefined.
114    """
115    if not isinstance(prop, int):
116        prop = input_prop_get_value(prop)
117
118    if prop is None:
119        return None
120
121    prop = _libevdev.libevdev_property_get_name(prop)
122    if prop is None:
123        return None
124
125    return prop.decode("iso8859-1")
126
127def input_prop_get_value(prop):
128    """
129    Return the value of the input property, or None if undefined.
130    """
131    if isinstance(prop, int):
132        prop = input_prop_get_name(prop)
133
134    if prop is None:
135        return None
136
137    prop = str(prop).encode("iso8859-1")
138    prop = _libevdev.libevdev_property_from_name(prop)
139    return None if prop < 0 else prop
140
141class InputEvent(object):
142    __slots__ = 'sec', 'usec', 'type', 'code', 'value'
143
144    def __init__(self, sec, usec, type, code, value):
145        self.sec = sec
146        self.usec = usec
147        self.type = type
148        self.code = code
149        self.value = value
150
151    def matches(self, type, code = None):
152        """
153        If code is None, return True if the event matches the given event
154        type. If code is not None, return True if the event matches the
155        given type/code pair.
156
157        type and code may be ints or string-like ("EV_ABS", "ABS_X").
158        """
159        if event_get_value(type) != self.type:
160            return False
161
162        if code != None and event_get_value(self.type, code) != self.code:
163            return False
164
165        return True
166
167    def __str__(self):
168        f = tempfile.TemporaryFile()
169        libc = evemu.base.LibC()
170        fp = libc.fdopen(f.fileno(), b"w+")
171
172        event = evemu.base.InputEvent()
173        event.sec = self.sec
174        event.usec = self.usec
175        event.type = self.type
176        event.code = self.code
177        event.value = self.value
178
179        libevemu = evemu.base.LibEvemu()
180        libevemu.evemu_write_event(fp, ctypes.byref(event))
181        libc.fflush(fp)
182        f.seek(0)
183        return f.readline().rstrip()
184
185class Device(object):
186    """
187    Encapsulates a raw kernel input event device, either an existing one as
188    reported by the kernel or a pseudodevice as created through a .prop file.
189    """
190
191    def __init__(self, f, create=True):
192        """
193        Initialize an evemu Device.
194
195        args:
196        f -- a file object or filename string for either an existing input
197        device node (/dev/input/eventNN) or an evemu prop file that can be used
198        to create a pseudo-device node.
199        create -- If f points to an evemu prop file, 'create' specifies if a
200        uinput device should be created
201        """
202
203        if type(f) == str:
204            self._file = open(f)
205        elif hasattr(f, "read"):
206            self._file = f
207        else:
208            raise TypeError("expected file or file name")
209
210        self._is_propfile = self._check_is_propfile(self._file)
211        self._libc = evemu.base.LibC()
212        self._libevemu = evemu.base.LibEvemu()
213
214        self._evemu_device = self._libevemu.evemu_new(b"")
215
216        if self._is_propfile:
217            fs = self._libc.fdopen(self._file.fileno(), b"r")
218            self._libevemu.evemu_read(self._evemu_device, fs)
219            if create:
220                self._file = self._create_devnode()
221        else:
222            self._libevemu.evemu_extract(self._evemu_device,
223                                         self._file.fileno())
224
225    def __del__(self):
226        if hasattr(self, "_is_propfile") and self._is_propfile:
227            self._file.close()
228            self._libevemu.evemu_destroy(self._evemu_device)
229
230    def _create_devnode(self):
231        self._libevemu.evemu_create_managed(self._evemu_device)
232        return open(self._find_newest_devnode(self.name), 'r+b', buffering=0)
233
234    def _find_newest_devnode(self, target_name):
235        newest_node = (None, float(0))
236        for sysname in glob.glob("/sys/class/input/event*/device/name"):
237            with open(sysname) as f:
238                name = f.read().rstrip()
239                if name == target_name:
240                    ev = re.search("(event\d+)", sysname)
241                    if ev:
242                       devname = os.path.join("/dev/input", ev.group(1))
243                       ctime = os.stat(devname).st_ctime
244                       if ctime > newest_node[1]:
245                           newest_node = (devname, ctime)
246        return newest_node[0]
247
248    def _check_is_propfile(self, f):
249        if stat.S_ISCHR(os.fstat(f.fileno()).st_mode):
250            return False
251
252        result = False
253        for line in f.readlines():
254            if line.startswith("N:"):
255                result = True
256                break
257            elif line.startswith("# EVEMU"):
258                result = True
259                break
260            elif line[0] != "#":
261                raise TypeError("file must be a device special or prop file")
262
263        f.seek(0)
264        return result
265
266    def describe(self, prop_file):
267        """
268        Gathers information about the input device and prints it
269        to prop_file. This information can be parsed later when constructing
270        a Device to create a virtual input device with the same properties.
271
272        You need the required permissions to access the device file to
273        succeed (usually root).
274
275        prop_file must be a real file with fileno(), not file-like.
276        """
277        if not hasattr(prop_file, "fileno"):
278            raise TypeError("expected file")
279
280        fs = self._libc.fdopen(prop_file.fileno(), b"w")
281        self._libevemu.evemu_write(self._evemu_device, fs)
282        self._libc.fflush(fs)
283
284    def events(self, events_file=None):
285        """
286        Reads the events from the given file and returns them as a list of
287        dicts.
288
289        If not None, events_file must be a real file with fileno(), not
290        file-like. If None, the file used for creating this device is used.
291        """
292        if events_file:
293            if not hasattr(events_file, "fileno"):
294                raise TypeError("expected file")
295        else:
296            events_file = self._file
297
298        fs = self._libc.fdopen(events_file.fileno(), b"r")
299        event = evemu.base.InputEvent()
300        while self._libevemu.evemu_read_event(fs, ctypes.byref(event)) > 0:
301            yield InputEvent(event.sec, event.usec, event.type, event.code, event.value)
302
303        self._libc.rewind(fs)
304
305    def play(self, events_file):
306        """
307        Replays an event sequence, as provided by the events_file,
308        through the input device. The event sequence must be in
309        the form created by the record method.
310
311        You need the required permissions to access the device file to
312        succeed (usually root).
313
314        events_file must be a real file with fileno(), not file-like.
315        """
316        if not hasattr(events_file, "fileno"):
317            raise TypeError("expected file")
318
319        fs = self._libc.fdopen(events_file.fileno(), b"r")
320        self._libevemu.evemu_play(fs, self._file.fileno())
321
322    def record(self, events_file, timeout=10000):
323        """
324        Captures events from the input device and prints them to the
325        events_file. The events can be parsed by the play method,
326        allowing a virtual input device to emit the exact same event
327        sequence.
328
329        You need the required permissions to access the device file to
330        succeed (usually root).
331
332        events_file must be a real file with fileno(), not file-like.
333        """
334        if not hasattr(events_file, "fileno"):
335            raise TypeError("expected file")
336
337        fs = self._libc.fdopen(events_file.fileno(), b"w")
338        self._libevemu.evemu_record(fs, self._file.fileno(), timeout)
339        self._libc.fflush(fs)
340
341    @property
342    def version(self):
343        """
344        Gets the version of the evemu library used to create the Device.
345        """
346        return self._libevemu.evemu_get_version(self._evemu_device)
347
348    @property
349    def devnode(self):
350        """
351        Gets the name of the /dev node of the input device.
352        """
353        return self._file.name
354
355    @property
356    def name(self):
357        """
358        Gets the name of the input device (as reported by the device).
359        """
360        result = self._libevemu.evemu_get_name(self._evemu_device)
361        return result.decode("iso8859-1")
362
363    @property
364    def id_bustype(self):
365        """
366        Identifies the kernel device bustype.
367        """
368        return self._libevemu.evemu_get_id_bustype(self._evemu_device)
369
370    @property
371    def id_vendor(self):
372        """
373        Identifies the kernel device vendor.
374        """
375        return self._libevemu.evemu_get_id_vendor(self._evemu_device)
376
377    @property
378    def id_product(self):
379        """
380        Identifies the kernel device product.
381        """
382        return self._libevemu.evemu_get_id_product(self._evemu_device)
383
384    @property
385    def id_version(self):
386        """
387        Identifies the kernel device version.
388        """
389        return self._libevemu.evemu_get_id_version(self._evemu_device)
390
391    def get_abs_current_value(self, event_code):
392        """
393        Return the current value for the given EV_ABS value.
394
395        event_code may be an int or string-like ("ABS_X").
396        """
397        if not isinstance(event_code, int):
398            event_code = event_get_value("EV_ABS", event_code)
399        return self._libevemu.evemu_get_abs_current_value(self._evemu_device,
400                                                          event_code)
401
402    def get_abs_minimum(self, event_code):
403        """
404        Return the axis minimum for the given EV_ABS value.
405
406        event_code may be an int or string-like ("ABS_X").
407        """
408        if not isinstance(event_code, int):
409            event_code = evemu.event_get_value("EV_ABS", event_code)
410        return self._libevemu.evemu_get_abs_minimum(self._evemu_device,
411                                                    event_code)
412
413    def get_abs_maximum(self, event_code):
414        """
415        Return the axis maximum for the given EV_ABS value.
416
417        event_code may be an int or string-like ("ABS_X").
418        """
419        if not isinstance(event_code, int):
420            event_code = evemu.event_get_value("EV_ABS", event_code)
421        return self._libevemu.evemu_get_abs_maximum(self._evemu_device,
422                                                    event_code)
423
424    def get_abs_fuzz(self, event_code):
425        """
426        Return the abs fuzz for the given EV_ABS value.
427
428        event_code may be an int or string-like ("ABS_X").
429        """
430        if not isinstance(event_code, int):
431            event_code = evemu.event_get_value("EV_ABS", event_code)
432        return self._libevemu.evemu_get_abs_fuzz(self._evemu_device,
433                                                 event_code)
434
435    def get_abs_flat(self, event_code):
436        """
437        Return the abs flat for the given EV_ABS value.
438
439        event_code may be an int or string-like ("ABS_X").
440        """
441        if not isinstance(event_code, int):
442            event_code = evemu.event_get_value("EV_ABS", event_code)
443        return self._libevemu.evemu_get_abs_flat(self._evemu_device,
444                                                 event_code)
445
446    def get_abs_resolution(self, event_code):
447        """
448        Return the resolution for the given EV_ABS value.
449
450        event_code may be an int or string-like ("ABS_X").
451        """
452        if not isinstance(event_code, int):
453            event_code = evemu.event_get_value("EV_ABS", event_code)
454        return self._libevemu.evemu_get_abs_resolution(self._evemu_device,
455                                                       event_code)
456
457    # don't change 'event_code' to prop, it breaks API
458    def has_prop(self, event_code):
459        """
460        Return True if the device supports the given input property,
461        or False otherwise.
462
463        event_code may be an int or string-like ("INPUT_PROP_DIRECT").
464        """
465        if not isinstance(event_code, int):
466            event_code = evemu.input_prop_get_value(event_code)
467        result = self._libevemu.evemu_has_prop(self._evemu_device, event_code)
468        return bool(result)
469
470    def has_event(self, event_type, event_code):
471        """
472        Return True if the device supports the given event type/code
473        pair, or False otherwise.
474
475        event_type and event_code may be ints or string-like ("EV_REL",
476        "REL_X").
477        """
478        if not isinstance(event_type, int):
479            event_type = evemu.event_get_value(event_type)
480        if not isinstance(event_code, int):
481            event_code = evemu.event_get_value(event_type, event_code)
482        result = self._libevemu.evemu_has_event(self._evemu_device,
483                                                event_type,
484                                                event_code)
485        return bool(result)
486
487