evdev 1.7.1__tar.gz → 1.8.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. {evdev-1.7.1 → evdev-1.8.0}/LICENSE +1 -1
  2. {evdev-1.7.1 → evdev-1.8.0}/MANIFEST.in +1 -0
  3. {evdev-1.7.1/evdev.egg-info → evdev-1.8.0}/PKG-INFO +5 -4
  4. {evdev-1.7.1 → evdev-1.8.0}/README.md +1 -0
  5. evdev-1.8.0/evdev/__init__.py +9 -0
  6. {evdev-1.7.1 → evdev-1.8.0}/evdev/device.py +10 -11
  7. evdev-1.8.0/evdev/ecodes.py +5 -0
  8. evdev-1.7.1/evdev/ecodes.py → evdev-1.8.0/evdev/ecodes_runtime.py +15 -5
  9. {evdev-1.7.1 → evdev-1.8.0}/evdev/eventio.py +15 -4
  10. {evdev-1.7.1 → evdev-1.8.0}/evdev/eventio_async.py +2 -2
  11. {evdev-1.7.1 → evdev-1.8.0}/evdev/events.py +14 -13
  12. {evdev-1.7.1 → evdev-1.8.0}/evdev/evtest.py +4 -10
  13. {evdev-1.7.1 → evdev-1.8.0}/evdev/ff.py +1 -1
  14. evdev-1.8.0/evdev/genecodes_c.py +138 -0
  15. evdev-1.8.0/evdev/genecodes_py.py +53 -0
  16. {evdev-1.7.1 → evdev-1.8.0}/evdev/uinput.py +6 -19
  17. {evdev-1.7.1 → evdev-1.8.0}/evdev/util.py +5 -5
  18. {evdev-1.7.1 → evdev-1.8.0/evdev.egg-info}/PKG-INFO +5 -4
  19. {evdev-1.7.1 → evdev-1.8.0}/evdev.egg-info/SOURCES.txt +3 -1
  20. {evdev-1.7.1 → evdev-1.8.0}/pyproject.toml +12 -3
  21. {evdev-1.7.1 → evdev-1.8.0}/setup.py +25 -8
  22. {evdev-1.7.1 → evdev-1.8.0}/tests/test_uinput.py +27 -6
  23. evdev-1.7.1/evdev/__init__.py +0 -10
  24. evdev-1.7.1/evdev/genecodes.py +0 -96
  25. {evdev-1.7.1 → evdev-1.8.0}/evdev/input.c +0 -0
  26. {evdev-1.7.1 → evdev-1.8.0}/evdev/uinput.c +0 -0
  27. {evdev-1.7.1 → evdev-1.8.0}/evdev.egg-info/dependency_links.txt +0 -0
  28. {evdev-1.7.1 → evdev-1.8.0}/evdev.egg-info/top_level.txt +0 -0
  29. {evdev-1.7.1 → evdev-1.8.0}/setup.cfg +0 -0
  30. {evdev-1.7.1 → evdev-1.8.0}/tests/test_ecodes.py +0 -0
  31. {evdev-1.7.1 → evdev-1.8.0}/tests/test_events.py +0 -0
  32. {evdev-1.7.1 → evdev-1.8.0}/tests/test_util.py +0 -0
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012-2023 Georgi Valkov. All rights reserved.
1
+ Copyright (c) 2012-2025 Georgi Valkov. All rights reserved.
2
2
 
3
3
  Redistribution and use in source and binary forms, with or without
4
4
  modification, are permitted provided that the following conditions are
@@ -2,3 +2,4 @@
2
2
  # evdev headers of the running kernel. Refer to the 'build_ecodes' distutils
3
3
  # command in setup.py.
4
4
  exclude evdev/ecodes.c
5
+ include evdev/ecodes.py
@@ -1,10 +1,10 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: evdev
3
- Version: 1.7.1
3
+ Version: 1.8.0
4
4
  Summary: Bindings to the Linux input handling subsystem
5
5
  Author-email: Georgi Valkov <georgi.t.valkov@gmail.com>
6
6
  Maintainer-email: Tobi <proxima@sezanzeb.de>
7
- License: Copyright (c) 2012-2023 Georgi Valkov. All rights reserved.
7
+ License: Copyright (c) 2012-2025 Georgi Valkov. All rights reserved.
8
8
 
9
9
  Redistribution and use in source and binary forms, with or without
10
10
  modification, are permitted provided that the following conditions are
@@ -43,7 +43,7 @@ Classifier: Intended Audience :: Developers
43
43
  Classifier: Topic :: Software Development :: Libraries
44
44
  Classifier: License :: OSI Approved :: BSD License
45
45
  Classifier: Programming Language :: Python :: Implementation :: CPython
46
- Requires-Python: >=3.6
46
+ Requires-Python: >=3.8
47
47
  Description-Content-Type: text/markdown
48
48
  License-File: LICENSE
49
49
 
@@ -52,6 +52,7 @@ License-File: LICENSE
52
52
  <p>
53
53
  <a href="https://pypi.python.org/pypi/evdev"><img alt="pypi version" src="https://img.shields.io/pypi/v/evdev.svg"></a>
54
54
  <a href="https://github.com/gvalkov/python-evdev/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/pypi/l/evdev"></a>
55
+ <a href="https://repology.org/project/python:evdev/versions"><img alt="Packaging status" src="https://repology.org/badge/tiny-repos/python:evdev.svg"></a>
55
56
  </p>
56
57
 
57
58
  This package provides bindings to the generic input event interface in Linux.
@@ -3,6 +3,7 @@
3
3
  <p>
4
4
  <a href="https://pypi.python.org/pypi/evdev"><img alt="pypi version" src="https://img.shields.io/pypi/v/evdev.svg"></a>
5
5
  <a href="https://github.com/gvalkov/python-evdev/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/pypi/l/evdev"></a>
6
+ <a href="https://repology.org/project/python:evdev/versions"><img alt="Packaging status" src="https://repology.org/badge/tiny-repos/python:evdev.svg"></a>
6
7
  </p>
7
8
 
8
9
  This package provides bindings to the generic input event interface in Linux.
@@ -0,0 +1,9 @@
1
+ # --------------------------------------------------------------------------
2
+ # Gather everything into a single, convenient namespace.
3
+ # --------------------------------------------------------------------------
4
+
5
+ from . import ecodes, ff
6
+ from .device import AbsInfo, DeviceInfo, EvdevError, InputDevice
7
+ from .events import AbsEvent, InputEvent, KeyEvent, RelEvent, SynEvent, event_factory
8
+ from .uinput import UInput, UInputError
9
+ from .util import categorize, list_devices, resolve_ecodes, resolve_ecodes_dict
@@ -1,23 +1,22 @@
1
1
  # encoding: utf-8
2
2
 
3
+ import collections
4
+ import contextlib
3
5
  import os
4
6
  import warnings
5
- import contextlib
6
- import collections
7
7
 
8
- from evdev import _input, ecodes, util
9
- from evdev.events import InputEvent
8
+ from . import _input, ecodes, util
10
9
 
11
10
  try:
12
- from evdev.eventio_async import EventIO, EvdevError
11
+ from .eventio_async import EvdevError, EventIO
13
12
  except ImportError:
14
- from evdev.eventio import EventIO, EvdevError
13
+ from .eventio import EvdevError, EventIO
15
14
 
16
15
 
17
16
  # --------------------------------------------------------------------------
18
17
  _AbsInfo = collections.namedtuple("AbsInfo", ["value", "min", "max", "fuzz", "flat", "resolution"])
19
18
 
20
- _KbdInfo = collections.namedtuple("KbdInfo", ["repeat", "delay"])
19
+ _KbdInfo = collections.namedtuple("KbdInfo", ["delay", "repeat"])
21
20
 
22
21
  _DeviceInfo = collections.namedtuple("DeviceInfo", ["bustype", "vendor", "product", "version"])
23
22
 
@@ -70,16 +69,16 @@ class KbdInfo(_KbdInfo):
70
69
 
71
70
  Attributes
72
71
  ----------
73
- repeat
74
- Keyboard repeat rate in characters per second.
75
-
76
72
  delay
77
73
  Amount of time that a key must be depressed before it will start
78
74
  to repeat (in milliseconds).
75
+
76
+ repeat
77
+ Keyboard repeat rate in characters per second.
79
78
  """
80
79
 
81
80
  def __str__(self):
82
- return "repeat {}, delay {}".format(*self)
81
+ return "delay {}, repeat {}".format(*self)
83
82
 
84
83
 
85
84
  class DeviceInfo(_DeviceInfo):
@@ -0,0 +1,5 @@
1
+ # When installed, this module is replaced by an ecodes.py generated at
2
+ # build time by genecodes_py.py (see build_ext in setup.py).
3
+
4
+ # This stub exists to make development of evdev itself more convenient.
5
+ from . ecodes_runtime import *
@@ -1,3 +1,4 @@
1
+ # pylint: disable=undefined-variable
1
2
  """
2
3
  This modules exposes the integer constants defined in ``linux/input.h`` and
3
4
  ``linux/input-event-codes.h``.
@@ -32,26 +33,26 @@ Keep in mind that values in reverse mappings may point to one or more event
32
33
  codes. For example::
33
34
 
34
35
  >>> evdev.ecodes.FF[80]
35
- ['FF_EFFECT_MIN', 'FF_RUMBLE']
36
+ ('FF_EFFECT_MIN', 'FF_RUMBLE')
36
37
 
37
38
  >>> evdev.ecodes.FF[81]
38
39
  'FF_PERIODIC'
39
40
  """
40
41
 
41
42
  from inspect import getmembers
42
- from evdev import _ecodes
43
43
 
44
+ from . import _ecodes
44
45
 
45
46
  #: Mapping of names to values.
46
47
  ecodes = {}
47
48
 
48
- prefixes = "KEY ABS REL SW MSC LED BTN REP SND ID EV BUS SYN FF_STATUS FF INPUT_PROP"
49
+ prefixes = "KEY ABS REL SW MSC LED BTN REP SND ID EV BUS SYN FF_STATUS FF INPUT_PROP".split()
49
50
  prev_prefix = ""
50
51
  g = globals()
51
52
 
52
53
  # eg. code: 'REL_Z', val: 2
53
54
  for code, val in getmembers(_ecodes):
54
- for prefix in prefixes.split(): # eg. 'REL'
55
+ for prefix in prefixes: # eg. 'REL'
55
56
  if code.startswith(prefix):
56
57
  ecodes[code] = val
57
58
  # FF_STATUS codes should not appear in the FF reverse mapping
@@ -70,6 +71,15 @@ for code, val in getmembers(_ecodes):
70
71
 
71
72
  prev_prefix = prefix
72
73
 
74
+
75
+ # Convert lists to tuples.
76
+ k, v = None, None
77
+ for prefix in prefixes:
78
+ for k, v in g[prefix].items():
79
+ if isinstance(v, list):
80
+ g[prefix][k] = tuple(v)
81
+
82
+
73
83
  #: Keys are a combination of all BTN and KEY codes.
74
84
  keys = {}
75
85
  keys.update(BTN)
@@ -98,4 +108,4 @@ bytype = {
98
108
  from evdev._ecodes import *
99
109
 
100
110
  # cheaper than whitelisting in an __all__
101
- del code, val, prefix, getmembers, g, d, prefixes, prev_prefix
111
+ del code, val, prefix, getmembers, g, d, k, v, prefixes, prev_prefix
@@ -1,10 +1,10 @@
1
- import os
2
1
  import fcntl
3
- import select
4
2
  import functools
3
+ import os
4
+ import select
5
5
 
6
- from evdev import _input, _uinput, ecodes, util
7
- from evdev.events import InputEvent
6
+ from . import _input, _uinput, ecodes
7
+ from .events import InputEvent
8
8
 
9
9
 
10
10
  # --------------------------------------------------------------------------
@@ -72,6 +72,7 @@ class EventIO:
72
72
  for event in events:
73
73
  yield InputEvent(*event)
74
74
 
75
+ # pylint: disable=no-self-argument
75
76
  def need_write(func):
76
77
  """
77
78
  Decorator that raises :class:`EvdevError` if there is no write access to the
@@ -82,6 +83,7 @@ class EventIO:
82
83
  def wrapper(*args):
83
84
  fd = args[0].fd
84
85
  if fcntl.fcntl(fd, fcntl.F_GETFL) & os.O_RDWR:
86
+ # pylint: disable=not-callable
85
87
  return func(*args)
86
88
  msg = 'no write access to device "%s"' % args[0].path
87
89
  raise EvdevError(msg)
@@ -136,5 +138,14 @@ class EventIO:
136
138
 
137
139
  _uinput.write(self.fd, etype, code, value)
138
140
 
141
+ def syn(self):
142
+ """
143
+ Inject a ``SYN_REPORT`` event into the input subsystem. Events
144
+ queued by :func:`write()` will be fired. If possible, events
145
+ will be merged into an 'atomic' event.
146
+ """
147
+
148
+ self.write(ecodes.EV_SYN, ecodes.SYN_REPORT, 0)
149
+
139
150
  def close(self):
140
151
  pass
@@ -1,10 +1,10 @@
1
1
  import asyncio
2
2
  import select
3
3
 
4
- from evdev import eventio
4
+ from . import eventio
5
5
 
6
6
  # needed for compatibility
7
- from evdev.eventio import EvdevError
7
+ from .eventio import EvdevError
8
8
 
9
9
 
10
10
  class EventIO(eventio.EventIO):
@@ -37,7 +37,8 @@ methods::
37
37
  # event type descriptions have been taken mot-a-mot from:
38
38
  # http://www.kernel.org/doc/Documentation/input/event-codes.txt
39
39
 
40
- from evdev.ecodes import keys, KEY, SYN, REL, ABS, EV_KEY, EV_REL, EV_ABS, EV_SYN
40
+ # pylint: disable=no-name-in-module
41
+ from .ecodes import ABS, EV_ABS, EV_KEY, EV_REL, EV_SYN, KEY, REL, SYN, keys
41
42
 
42
43
 
43
44
  class InputEvent:
@@ -65,13 +66,13 @@ class InputEvent:
65
66
  """Return event timestamp as a float."""
66
67
  return self.sec + (self.usec / 1000000.0)
67
68
 
68
- def __str__(s):
69
+ def __str__(self):
69
70
  msg = "event at {:f}, code {:02d}, type {:02d}, val {:02d}"
70
- return msg.format(s.timestamp(), s.code, s.type, s.value)
71
+ return msg.format(self.timestamp(), self.code, self.type, self.value)
71
72
 
72
- def __repr__(s):
73
+ def __repr__(self):
73
74
  msg = "{}({!r}, {!r}, {!r}, {!r}, {!r})"
74
- return msg.format(s.__class__.__name__, s.sec, s.usec, s.type, s.code, s.value)
75
+ return msg.format(self.__class__.__name__, self.sec, self.usec, self.type, self.code, self.value)
75
76
 
76
77
 
77
78
  class KeyEvent:
@@ -119,8 +120,8 @@ class KeyEvent:
119
120
  msg = "key event at {:f}, {} ({}), {}"
120
121
  return msg.format(self.event.timestamp(), self.scancode, self.keycode, ks)
121
122
 
122
- def __repr__(s):
123
- return "{}({!r})".format(s.__class__.__name__, s.event)
123
+ def __repr__(self):
124
+ return "{}({!r})".format(self.__class__.__name__, self.event)
124
125
 
125
126
 
126
127
  class RelEvent:
@@ -136,8 +137,8 @@ class RelEvent:
136
137
  msg = "relative axis event at {:f}, {}"
137
138
  return msg.format(self.event.timestamp(), REL[self.event.code])
138
139
 
139
- def __repr__(s):
140
- return "{}({!r})".format(s.__class__.__name__, s.event)
140
+ def __repr__(self):
141
+ return "{}({!r})".format(self.__class__.__name__, self.event)
141
142
 
142
143
 
143
144
  class AbsEvent:
@@ -153,8 +154,8 @@ class AbsEvent:
153
154
  msg = "absolute axis event at {:f}, {}"
154
155
  return msg.format(self.event.timestamp(), ABS[self.event.code])
155
156
 
156
- def __repr__(s):
157
- return "{}({!r})".format(s.__class__.__name__, s.event)
157
+ def __repr__(self):
158
+ return "{}({!r})".format(self.__class__.__name__, self.event)
158
159
 
159
160
 
160
161
  class SynEvent:
@@ -173,8 +174,8 @@ class SynEvent:
173
174
  msg = "synchronization event at {:f}, {}"
174
175
  return msg.format(self.event.timestamp(), SYN[self.event.code])
175
176
 
176
- def __repr__(s):
177
- return "{}({!r})".format(s.__class__.__name__, s.event)
177
+ def __repr__(self):
178
+ return "{}({!r})".format(self.__class__.__name__, self.event)
178
179
 
179
180
 
180
181
  #: A mapping of event types to :class:`InputEvent` sub-classes. Used
@@ -16,21 +16,15 @@ Examples:
16
16
  evtest /dev/input/event0 /dev/input/event1
17
17
  """
18
18
 
19
- from __future__ import print_function
20
19
 
20
+ import atexit
21
+ import optparse
21
22
  import re
22
- import sys
23
23
  import select
24
- import atexit
24
+ import sys
25
25
  import termios
26
- import optparse
27
-
28
- try:
29
- input = raw_input
30
- except NameError:
31
- pass
32
26
 
33
- from evdev import ecodes, list_devices, AbsInfo, InputDevice
27
+ from . import AbsInfo, InputDevice, ecodes, list_devices
34
28
 
35
29
 
36
30
  def parseopt():
@@ -1,6 +1,6 @@
1
1
  import ctypes
2
- from evdev import ecodes
3
2
 
3
+ from . import ecodes
4
4
 
5
5
  _u8 = ctypes.c_uint8
6
6
  _u16 = ctypes.c_uint16
@@ -0,0 +1,138 @@
1
+ """
2
+ Generate a Python extension module with the constants defined in linux/input.h.
3
+ """
4
+
5
+ import getopt
6
+ import os
7
+ import re
8
+ import sys
9
+
10
+ # -----------------------------------------------------------------------------
11
+ # The default header file locations to try.
12
+ headers = [
13
+ "/usr/include/linux/input.h",
14
+ "/usr/include/linux/input-event-codes.h",
15
+ "/usr/include/linux/uinput.h",
16
+ ]
17
+
18
+ opts, args = getopt.getopt(sys.argv[1:], "", ["ecodes", "stubs"])
19
+ if not opts:
20
+ print("usage: genecodes.py [--ecodes|--stubs] <headers>")
21
+ exit(2)
22
+
23
+
24
+ # -----------------------------------------------------------------------------
25
+ macro_regex = r"#define +((?:KEY|ABS|REL|SW|MSC|LED|BTN|REP|SND|ID|EV|BUS|SYN|FF|UI_FF|INPUT_PROP)_\w+)"
26
+ macro_regex = re.compile(macro_regex)
27
+
28
+ # Uname without hostname.
29
+ uname = list(os.uname())
30
+ uname = " ".join((uname[0], *uname[2:]))
31
+
32
+
33
+ # -----------------------------------------------------------------------------
34
+ template_ecodes = r"""
35
+ #include <Python.h>
36
+ #ifdef __FreeBSD__
37
+ #include <dev/evdev/input.h>
38
+ #else
39
+ #include <linux/input.h>
40
+ #include <linux/uinput.h>
41
+ #endif
42
+
43
+ /* Automatically generated by evdev.genecodes */
44
+ /* Generated on %s */
45
+ /* Generated from %s */
46
+
47
+ #define MODULE_NAME "_ecodes"
48
+ #define MODULE_HELP "linux/input.h macros"
49
+
50
+ static PyMethodDef MethodTable[] = {
51
+ { NULL, NULL, 0, NULL}
52
+ };
53
+
54
+ static struct PyModuleDef moduledef = {
55
+ PyModuleDef_HEAD_INIT,
56
+ MODULE_NAME,
57
+ MODULE_HELP,
58
+ -1, /* m_size */
59
+ MethodTable, /* m_methods */
60
+ NULL, /* m_reload */
61
+ NULL, /* m_traverse */
62
+ NULL, /* m_clear */
63
+ NULL, /* m_free */
64
+ };
65
+
66
+ PyMODINIT_FUNC
67
+ PyInit__ecodes(void)
68
+ {
69
+ PyObject* m = PyModule_Create(&moduledef);
70
+ if (m == NULL) return NULL;
71
+
72
+ %s
73
+
74
+ return m;
75
+ }
76
+ """
77
+
78
+
79
+ template_stubs = r"""
80
+ # Automatically generated by evdev.genecodes
81
+ # Generated on %s
82
+ # Generated from %s
83
+
84
+ # pylint: skip-file
85
+
86
+ ecodes: dict[str, int]
87
+ keys: dict[int, str|list[str]]
88
+ bytype: dict[int, dict[int, str|list[str]]]
89
+
90
+ KEY: dict[int, str|list[str]]
91
+ ABS: dict[int, str|list[str]]
92
+ REL: dict[int, str|list[str]]
93
+ SW: dict[int, str|list[str]]
94
+ MSC: dict[int, str|list[str]]
95
+ LED: dict[int, str|list[str]]
96
+ BTN: dict[int, str|list[str]]
97
+ REP: dict[int, str|list[str]]
98
+ SND: dict[int, str|list[str]]
99
+ ID: dict[int, str|list[str]]
100
+ EV: dict[int, str|list[str]]
101
+ BUS: dict[int, str|list[str]]
102
+ SYN: dict[int, str|list[str]]
103
+ FF_STATUS: dict[int, str|list[str]]
104
+ FF_INPUT_PROP: dict[int, str|list[str]]
105
+
106
+ %s
107
+ """
108
+
109
+
110
+ def parse_headers(headers=headers):
111
+ for header in headers:
112
+ try:
113
+ fh = open(header)
114
+ except (IOError, OSError):
115
+ continue
116
+
117
+ for line in fh:
118
+ macro = macro_regex.search(line)
119
+ if macro:
120
+ yield macro.group(1)
121
+
122
+
123
+ all_macros = list(parse_headers())
124
+ if not all_macros:
125
+ print("no input macros found in: %s" % " ".join(headers), file=sys.stderr)
126
+ sys.exit(1)
127
+
128
+ # pylint: disable=possibly-used-before-assignment, used-before-assignment
129
+ if ("--ecodes", "") in opts:
130
+ body = (" PyModule_AddIntMacro(m, %s);" % macro for macro in all_macros)
131
+ template = template_ecodes
132
+ elif ("--stubs", "") in opts:
133
+ body = ("%s: int" % macro for macro in all_macros)
134
+ template = template_stubs
135
+
136
+ body = os.linesep.join(body)
137
+ text = template % (uname, headers, body)
138
+ print(text.strip())
@@ -0,0 +1,53 @@
1
+ import sys
2
+ from unittest import mock
3
+ from pprint import PrettyPrinter
4
+
5
+ sys.modules["evdev.ecodes"] = mock.Mock()
6
+ from evdev import ecodes_runtime as ecodes
7
+
8
+ pprint = PrettyPrinter(indent=2, sort_dicts=True, width=120).pprint
9
+
10
+
11
+ print("# Automatically generated by evdev.genecodes_py")
12
+ print()
13
+ print('"""')
14
+ print(ecodes.__doc__.strip())
15
+ print('"""')
16
+
17
+ print()
18
+ print("from typing import Final, Dict, Tuple, Union")
19
+ print()
20
+
21
+ for name, value in ecodes.ecodes.items():
22
+ print(f"{name}: Final[int] = {value}")
23
+ print()
24
+
25
+ entries = [
26
+ ("ecodes", "Dict[str, int]", "#: Mapping of names to values."),
27
+ ("bytype", "Dict[int, Dict[int, Union[str, Tuple[str]]]]", "#: Mapping of event types to other value/name mappings."),
28
+ ("keys", "Dict[int, Union[str, Tuple[str]]]", "#: Keys are a combination of all BTN and KEY codes."),
29
+ ("KEY", "Dict[int, Union[str, Tuple[str]]]", None),
30
+ ("ABS", "Dict[int, Union[str, Tuple[str]]]", None),
31
+ ("REL", "Dict[int, Union[str, Tuple[str]]]", None),
32
+ ("SW", "Dict[int, Union[str, Tuple[str]]]", None),
33
+ ("MSC", "Dict[int, Union[str, Tuple[str]]]", None),
34
+ ("LED", "Dict[int, Union[str, Tuple[str]]]", None),
35
+ ("BTN", "Dict[int, Union[str, Tuple[str]]]", None),
36
+ ("REP", "Dict[int, Union[str, Tuple[str]]]", None),
37
+ ("SND", "Dict[int, Union[str, Tuple[str]]]", None),
38
+ ("ID", "Dict[int, Union[str, Tuple[str]]]", None),
39
+ ("EV", "Dict[int, Union[str, Tuple[str]]]", None),
40
+ ("BUS", "Dict[int, Union[str, Tuple[str]]]", None),
41
+ ("SYN", "Dict[int, Union[str, Tuple[str]]]", None),
42
+ ("FF", "Dict[int, Union[str, Tuple[str]]]", None),
43
+ ("FF_STATUS", "Dict[int, Union[str, Tuple[str]]]", None),
44
+ ("INPUT_PROP", "Dict[int, Union[str, Tuple[str]]]", None)
45
+ ]
46
+
47
+ for key, annotation, doc in entries:
48
+ if doc:
49
+ print(doc)
50
+
51
+ print(f"{key}: {annotation} = ", end="")
52
+ pprint(getattr(ecodes, key))
53
+ print()
@@ -1,3 +1,4 @@
1
+ import ctypes
1
2
  import os
2
3
  import platform
3
4
  import re
@@ -5,11 +6,8 @@ import stat
5
6
  import time
6
7
  from collections import defaultdict
7
8
 
8
- from evdev import _uinput
9
- from evdev import ecodes, util, device
10
- from evdev.events import InputEvent
11
- import evdev.ff as ff
12
- import ctypes
9
+ from . import _uinput, device, ecodes, ff, util
10
+ from .events import InputEvent
13
11
 
14
12
  try:
15
13
  from evdev.eventio_async import EventIO
@@ -227,15 +225,6 @@ class UInput(EventIO):
227
225
  _uinput.close(self.fd)
228
226
  self.fd = -1
229
227
 
230
- def syn(self):
231
- """
232
- Inject a ``SYN_REPORT`` event into the input subsystem. Events
233
- queued by :func:`write()` will be fired. If possible, events
234
- will be merged into an 'atomic' event.
235
- """
236
-
237
- _uinput.write(self.fd, ecodes.EV_SYN, ecodes.SYN_REPORT, 0)
238
-
239
228
  def capabilities(self, verbose=False, absinfo=True):
240
229
  """See :func:`capabilities <evdev.device.InputDevice.capabilities>`."""
241
230
  if self.device is None:
@@ -277,13 +266,11 @@ class UInput(EventIO):
277
266
  Verify that an uinput device exists and is readable and writable
278
267
  by the current process.
279
268
  """
280
-
281
269
  try:
282
270
  m = os.stat(self.devnode)[stat.ST_MODE]
283
- if not stat.S_ISCHR(m):
284
- raise
285
- except (IndexError, OSError):
286
- msg = '"{}" does not exist or is not a character device file ' "- verify that the uinput module is loaded"
271
+ assert stat.S_ISCHR(m)
272
+ except (IndexError, OSError, AssertionError):
273
+ msg = '"{}" does not exist or is not a character device file - verify that the uinput module is loaded'
287
274
  raise UInputError(msg.format(self.devnode))
288
275
 
289
276
  if not os.access(self.devnode, os.W_OK):
@@ -1,11 +1,11 @@
1
- import re
1
+ import collections
2
+ import glob
2
3
  import os
4
+ import re
3
5
  import stat
4
- import glob
5
- import collections
6
6
 
7
- from evdev import ecodes
8
- from evdev.events import event_factory
7
+ from . import ecodes
8
+ from .events import event_factory
9
9
 
10
10
 
11
11
  def list_devices(input_device_dir="/dev/input"):
@@ -1,10 +1,10 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: evdev
3
- Version: 1.7.1
3
+ Version: 1.8.0
4
4
  Summary: Bindings to the Linux input handling subsystem
5
5
  Author-email: Georgi Valkov <georgi.t.valkov@gmail.com>
6
6
  Maintainer-email: Tobi <proxima@sezanzeb.de>
7
- License: Copyright (c) 2012-2023 Georgi Valkov. All rights reserved.
7
+ License: Copyright (c) 2012-2025 Georgi Valkov. All rights reserved.
8
8
 
9
9
  Redistribution and use in source and binary forms, with or without
10
10
  modification, are permitted provided that the following conditions are
@@ -43,7 +43,7 @@ Classifier: Intended Audience :: Developers
43
43
  Classifier: Topic :: Software Development :: Libraries
44
44
  Classifier: License :: OSI Approved :: BSD License
45
45
  Classifier: Programming Language :: Python :: Implementation :: CPython
46
- Requires-Python: >=3.6
46
+ Requires-Python: >=3.8
47
47
  Description-Content-Type: text/markdown
48
48
  License-File: LICENSE
49
49
 
@@ -52,6 +52,7 @@ License-File: LICENSE
52
52
  <p>
53
53
  <a href="https://pypi.python.org/pypi/evdev"><img alt="pypi version" src="https://img.shields.io/pypi/v/evdev.svg"></a>
54
54
  <a href="https://github.com/gvalkov/python-evdev/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/pypi/l/evdev"></a>
55
+ <a href="https://repology.org/project/python:evdev/versions"><img alt="Packaging status" src="https://repology.org/badge/tiny-repos/python:evdev.svg"></a>
55
56
  </p>
56
57
 
57
58
  This package provides bindings to the generic input event interface in Linux.
@@ -6,12 +6,14 @@ setup.py
6
6
  evdev/__init__.py
7
7
  evdev/device.py
8
8
  evdev/ecodes.py
9
+ evdev/ecodes_runtime.py
9
10
  evdev/eventio.py
10
11
  evdev/eventio_async.py
11
12
  evdev/events.py
12
13
  evdev/evtest.py
13
14
  evdev/ff.py
14
- evdev/genecodes.py
15
+ evdev/genecodes_c.py
16
+ evdev/genecodes_py.py
15
17
  evdev/input.c
16
18
  evdev/uinput.c
17
19
  evdev/uinput.py
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "evdev"
7
- version = "1.7.1"
7
+ version = "1.8.0"
8
8
  description = "Bindings to the Linux input handling subsystem"
9
9
  keywords = ["evdev", "input", "uinput"]
10
10
  readme = "README.md"
11
11
  license = {file = "LICENSE"}
12
- requires-python = ">=3.6"
12
+ requires-python = ">=3.8"
13
13
  authors = [
14
14
  { name="Georgi Valkov", email="georgi.t.valkov@gmail.com" },
15
15
  ]
@@ -39,7 +39,7 @@ line-length = 120
39
39
  ignore = ["E265", "E241", "F403", "F401", "E401", "E731"]
40
40
 
41
41
  [tool.bumpversion]
42
- current_version = "1.7.1"
42
+ current_version = "1.8.0"
43
43
  commit = true
44
44
  tag = true
45
45
  allow_dirty = true
@@ -49,3 +49,12 @@ filename = "pyproject.toml"
49
49
 
50
50
  [[tool.bumpversion.files]]
51
51
  filename = "docs/conf.py"
52
+
53
+ [tool.pylint.'MESSAGES CONTROL']
54
+ disable = """
55
+ no-member,
56
+ """
57
+
58
+ [tool.pylint.typecheck]
59
+ generated-members = ["evdev.ecodes.*"]
60
+ ignored-modules= ["evdev._*"]
@@ -1,14 +1,16 @@
1
1
  import os
2
2
  import sys
3
+ import shutil
3
4
  import textwrap
4
5
  from pathlib import Path
6
+ from subprocess import run
5
7
 
6
8
  from setuptools import setup, Extension, Command
7
9
  from setuptools.command import build_ext as _build_ext
8
10
 
9
11
 
10
12
  curdir = Path(__file__).resolve().parent
11
- ecodes_path = curdir / "evdev/ecodes.c"
13
+ ecodes_c_path = curdir / "evdev/ecodes.c"
12
14
 
13
15
 
14
16
  def create_ecodes(headers=None):
@@ -47,16 +49,18 @@ def create_ecodes(headers=None):
47
49
  build_ecodes --evdev-headers path/input.h:path/input-event-codes.h \\
48
50
  build_ext --include-dirs path/ \\
49
51
  install
52
+
53
+ If you want to avoid building this package from source, then please consider
54
+ installing the `evdev-binary` package instead. Keep in mind that it may not be
55
+ fully compatible with, or support all the features of your current kernel.
50
56
  """
51
57
 
52
58
  sys.stderr.write(textwrap.dedent(msg))
53
59
  sys.exit(1)
54
60
 
55
- from subprocess import run
56
-
57
- print("writing %s (using %s)" % (ecodes_path, " ".join(headers)))
58
- with ecodes_path.open("w") as fh:
59
- cmd = [sys.executable, "evdev/genecodes.py", *headers]
61
+ print("writing %s (using %s)" % (ecodes_c_path, " ".join(headers)))
62
+ with ecodes_c_path.open("w") as fh:
63
+ cmd = [sys.executable, "evdev/genecodes_c.py", "--ecodes", *headers]
60
64
  run(cmd, check=True, stdout=fh)
61
65
 
62
66
 
@@ -80,14 +84,27 @@ class build_ecodes(Command):
80
84
 
81
85
  class build_ext(_build_ext.build_ext):
82
86
  def has_ecodes(self):
83
- if ecodes_path.exists():
87
+ if ecodes_c_path.exists():
84
88
  print("ecodes.c already exists ... skipping build_ecodes")
85
- return not ecodes_path.exists()
89
+ return False
90
+ return True
91
+
92
+ def generate_ecodes_py(self):
93
+ ecodes_py = Path(self.build_lib) / "evdev/ecodes.py"
94
+ print(f"writing {ecodes_py}")
95
+ with ecodes_py.open("w") as fh:
96
+ cmd = [sys.executable, "-B", "evdev/genecodes_py.py"]
97
+ res = run(cmd, env={"PYTHONPATH": self.build_lib}, stdout=fh)
98
+
99
+ if res.returncode != 0:
100
+ print(f"failed to generate static {ecodes_py} - will use ecodes_runtime.py")
101
+ shutil.copy("evdev/ecodes_runtime.py", ecodes_py)
86
102
 
87
103
  def run(self):
88
104
  for cmd_name in self.get_sub_commands():
89
105
  self.run_command(cmd_name)
90
106
  _build_ext.build_ext.run(self)
107
+ self.generate_ecodes_py()
91
108
 
92
109
  sub_commands = [("build_ecodes", has_ecodes)] + _build_ext.build_ext.sub_commands
93
110
 
@@ -1,10 +1,13 @@
1
1
  # encoding: utf-8
2
-
2
+ import os
3
+ import stat
3
4
  from select import select
4
- from pytest import raises, fixture
5
+ from unittest.mock import patch
5
6
 
6
- from evdev import uinput, ecodes, events, device, util
7
+ import pytest
8
+ from pytest import raises, fixture
7
9
 
10
+ from evdev import uinput, ecodes, device, UInputError
8
11
 
9
12
  # -----------------------------------------------------------------------------
10
13
  uinput_options = {
@@ -66,12 +69,12 @@ def test_enable_events(c):
66
69
 
67
70
  def test_abs_values(c):
68
71
  e = ecodes
69
- c["events"] = {
72
+ c = {
70
73
  e.EV_KEY: [e.KEY_A, e.KEY_B],
71
- e.EV_ABS: [(e.ABS_X, (0, 255, 0, 0)), (e.ABS_Y, device.AbsInfo(0, 255, 5, 10, 0, 0))],
74
+ e.EV_ABS: [(e.ABS_X, (0, 0, 255, 0, 0)), (e.ABS_Y, device.AbsInfo(0, 0, 255, 5, 10, 0))],
72
75
  }
73
76
 
74
- with uinput.UInput(**c) as ui:
77
+ with uinput.UInput(events=c) as ui:
75
78
  c = ui.capabilities()
76
79
  abs = device.AbsInfo(value=0, min=0, max=255, fuzz=0, flat=0, resolution=0)
77
80
  assert c[e.EV_ABS][0] == (0, abs)
@@ -114,3 +117,21 @@ def test_write(c):
114
117
  assert evs[3].code == ecodes.KEY_A and evs[3].value == 2
115
118
  assert evs[4].code == ecodes.KEY_A and evs[4].value == 0
116
119
  break
120
+
121
+
122
+ @patch.object(stat, 'S_ISCHR', return_value=False)
123
+ def test_not_a_character_device(ischr_mock, c):
124
+ with pytest.raises(UInputError, match='not a character device file'):
125
+ uinput.UInput(**c)
126
+
127
+ @patch.object(stat, 'S_ISCHR', return_value=True)
128
+ @patch.object(os, 'stat', side_effect=OSError())
129
+ def test_not_a_character_device_2(stat_mock, ischr_mock, c):
130
+ with pytest.raises(UInputError, match='not a character device file'):
131
+ uinput.UInput(**c)
132
+
133
+ @patch.object(stat, 'S_ISCHR', return_value=True)
134
+ @patch.object(os, 'stat', return_value=[])
135
+ def test_not_a_character_device_3(stat_mock, ischr_mock, c):
136
+ with pytest.raises(UInputError, match='not a character device file'):
137
+ uinput.UInput(**c)
@@ -1,10 +0,0 @@
1
- # --------------------------------------------------------------------------
2
- # Gather everything into a single, convenient namespace.
3
- # --------------------------------------------------------------------------
4
-
5
- from evdev.device import DeviceInfo, InputDevice, AbsInfo, EvdevError
6
- from evdev.events import InputEvent, KeyEvent, RelEvent, SynEvent, AbsEvent, event_factory
7
- from evdev.uinput import UInput, UInputError
8
- from evdev.util import list_devices, categorize, resolve_ecodes, resolve_ecodes_dict
9
- from evdev import ecodes
10
- from evdev import ff
@@ -1,96 +0,0 @@
1
- """
2
- Generate a Python extension module with the constants defined in linux/input.h.
3
- """
4
-
5
- from __future__ import print_function
6
- import os, sys, re
7
-
8
-
9
- # -----------------------------------------------------------------------------
10
- # The default header file locations to try.
11
- headers = [
12
- "/usr/include/linux/input.h",
13
- "/usr/include/linux/input-event-codes.h",
14
- "/usr/include/linux/uinput.h",
15
- ]
16
-
17
- if sys.argv[1:]:
18
- headers = sys.argv[1:]
19
-
20
-
21
- # -----------------------------------------------------------------------------
22
- macro_regex = r"#define +((?:KEY|ABS|REL|SW|MSC|LED|BTN|REP|SND|ID|EV|BUS|SYN|FF|UI_FF|INPUT_PROP)_\w+)"
23
- macro_regex = re.compile(macro_regex)
24
-
25
- uname = list(os.uname())
26
- del uname[1]
27
- uname = " ".join(uname)
28
-
29
-
30
- # -----------------------------------------------------------------------------
31
- template = r"""
32
- #include <Python.h>
33
- #ifdef __FreeBSD__
34
- #include <dev/evdev/input.h>
35
- #else
36
- #include <linux/input.h>
37
- #include <linux/uinput.h>
38
- #endif
39
-
40
- /* Automatically generated by evdev.genecodes */
41
- /* Generated on %s */
42
-
43
- #define MODULE_NAME "_ecodes"
44
- #define MODULE_HELP "linux/input.h macros"
45
-
46
- static PyMethodDef MethodTable[] = {
47
- { NULL, NULL, 0, NULL}
48
- };
49
-
50
- static struct PyModuleDef moduledef = {
51
- PyModuleDef_HEAD_INIT,
52
- MODULE_NAME,
53
- MODULE_HELP,
54
- -1, /* m_size */
55
- MethodTable, /* m_methods */
56
- NULL, /* m_reload */
57
- NULL, /* m_traverse */
58
- NULL, /* m_clear */
59
- NULL, /* m_free */
60
- };
61
-
62
- PyMODINIT_FUNC
63
- PyInit__ecodes(void)
64
- {
65
- PyObject* m = PyModule_Create(&moduledef);
66
- if (m == NULL) return NULL;
67
-
68
- %s
69
-
70
- return m;
71
- }
72
- """
73
-
74
-
75
- def parse_header(header):
76
- for line in open(header):
77
- macro = macro_regex.search(line)
78
- if macro:
79
- yield " PyModule_AddIntMacro(m, %s);" % macro.group(1)
80
-
81
-
82
- all_macros = []
83
- for header in headers:
84
- try:
85
- fh = open(header)
86
- except (IOError, OSError):
87
- continue
88
- all_macros += parse_header(header)
89
-
90
- if not all_macros:
91
- print("no input macros found in: %s" % " ".join(headers), file=sys.stderr)
92
- sys.exit(1)
93
-
94
-
95
- macros = os.linesep.join(all_macros)
96
- print(template % (uname, macros))
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes