evdev 1.9.3__tar.gz → 2.0.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.9.3/src/evdev.egg-info → evdev-2.0.0}/PKG-INFO +11 -9
  2. {evdev-1.9.3 → evdev-2.0.0}/README.md +10 -8
  3. {evdev-1.9.3 → evdev-2.0.0}/pyproject.toml +17 -14
  4. {evdev-1.9.3 → evdev-2.0.0}/src/evdev/device.py +42 -23
  5. {evdev-1.9.3 → evdev-2.0.0}/src/evdev/eventio.py +7 -7
  6. {evdev-1.9.3 → evdev-2.0.0}/src/evdev/eventio_async.py +25 -17
  7. {evdev-1.9.3 → evdev-2.0.0}/src/evdev/events.py +17 -17
  8. {evdev-1.9.3 → evdev-2.0.0}/src/evdev/genecodes_c.py +3 -0
  9. evdev-2.0.0/src/evdev/genecodes_py.py +54 -0
  10. {evdev-1.9.3 → evdev-2.0.0}/src/evdev/input.c +19 -2
  11. {evdev-1.9.3 → evdev-2.0.0}/src/evdev/uinput.c +25 -1
  12. {evdev-1.9.3 → evdev-2.0.0}/src/evdev/uinput.py +27 -21
  13. {evdev-1.9.3 → evdev-2.0.0}/src/evdev/util.py +16 -8
  14. {evdev-1.9.3 → evdev-2.0.0/src/evdev.egg-info}/PKG-INFO +11 -9
  15. {evdev-1.9.3 → evdev-2.0.0}/tests/test_uinput.py +10 -0
  16. evdev-1.9.3/src/evdev/genecodes_py.py +0 -54
  17. {evdev-1.9.3 → evdev-2.0.0}/LICENSE +0 -0
  18. {evdev-1.9.3 → evdev-2.0.0}/MANIFEST.in +0 -0
  19. {evdev-1.9.3 → evdev-2.0.0}/setup.cfg +0 -0
  20. {evdev-1.9.3 → evdev-2.0.0}/setup.py +0 -0
  21. {evdev-1.9.3 → evdev-2.0.0}/src/evdev/__init__.py +0 -0
  22. {evdev-1.9.3 → evdev-2.0.0}/src/evdev/ecodes.py +0 -0
  23. {evdev-1.9.3 → evdev-2.0.0}/src/evdev/ecodes_runtime.py +0 -0
  24. {evdev-1.9.3 → evdev-2.0.0}/src/evdev/evtest.py +0 -0
  25. {evdev-1.9.3 → evdev-2.0.0}/src/evdev/ff.py +0 -0
  26. {evdev-1.9.3 → evdev-2.0.0}/src/evdev/py.typed +0 -0
  27. {evdev-1.9.3 → evdev-2.0.0}/src/evdev.egg-info/SOURCES.txt +0 -0
  28. {evdev-1.9.3 → evdev-2.0.0}/src/evdev.egg-info/dependency_links.txt +0 -0
  29. {evdev-1.9.3 → evdev-2.0.0}/src/evdev.egg-info/top_level.txt +0 -0
  30. {evdev-1.9.3 → evdev-2.0.0}/tests/test_ecodes.py +0 -0
  31. {evdev-1.9.3 → evdev-2.0.0}/tests/test_events.py +0 -0
  32. {evdev-1.9.3 → evdev-2.0.0}/tests/test_util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: evdev
3
- Version: 1.9.3
3
+ Version: 2.0.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>
@@ -13,7 +13,7 @@ Classifier: Operating System :: POSIX :: Linux
13
13
  Classifier: Intended Audience :: Developers
14
14
  Classifier: Topic :: Software Development :: Libraries
15
15
  Classifier: Programming Language :: Python :: Implementation :: CPython
16
- Requires-Python: >=3.9
16
+ Requires-Python: >=3.11
17
17
  Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
  Dynamic: license-file
@@ -21,9 +21,11 @@ Dynamic: license-file
21
21
  # evdev
22
22
 
23
23
  <p>
24
- <a href="https://pypi.python.org/pypi/evdev"><img alt="pypi version" src="https://img.shields.io/pypi/v/evdev.svg"></a>
24
+ <a href="https://pypi.org/pypi/evdev"><img alt="pypi version" src="https://img.shields.io/pypi/v/evdev.svg"></a>
25
25
  <a href="https://github.com/gvalkov/python-evdev/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/pypi/l/evdev"></a>
26
- <a href="https://repology.org/project/python:evdev/versions"><img alt="Packaging status" src="https://repology.org/badge/tiny-repos/python:evdev.svg"></a>
26
+ <a href="https://repology.org/project/python%3Aevdev/versions">
27
+ <img src="https://repology.org/badge/tiny-repos/python%3Aevdev.svg" alt="Packaging status">
28
+ </a>
27
29
  </p>
28
30
 
29
31
  This package provides bindings to the generic input event interface in Linux.
@@ -35,14 +37,14 @@ This package also comes with bindings to *uinput*, the userspace input
35
37
  subsystem. *Uinput* allows userspace programs to create and handle input devices
36
38
  that can inject events directly into the input subsystem.
37
39
 
38
- ***Documentation:***
40
+ ***Documentation:***
39
41
  https://python-evdev.readthedocs.io/en/latest/
40
42
 
41
- ***Development:***
43
+ ***Development:***
42
44
  https://github.com/gvalkov/python-evdev
43
45
 
44
- ***Package:***
45
- https://pypi.python.org/pypi/evdev
46
+ ***Package:***
47
+ https://pypi.org/pypi/evdev
46
48
 
47
- ***Changelog:***
49
+ ***Changelog:***
48
50
  https://python-evdev.readthedocs.io/en/latest/changelog.html
@@ -1,9 +1,11 @@
1
1
  # evdev
2
2
 
3
3
  <p>
4
- <a href="https://pypi.python.org/pypi/evdev"><img alt="pypi version" src="https://img.shields.io/pypi/v/evdev.svg"></a>
4
+ <a href="https://pypi.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
+ <a href="https://repology.org/project/python%3Aevdev/versions">
7
+ <img src="https://repology.org/badge/tiny-repos/python%3Aevdev.svg" alt="Packaging status">
8
+ </a>
7
9
  </p>
8
10
 
9
11
  This package provides bindings to the generic input event interface in Linux.
@@ -15,14 +17,14 @@ This package also comes with bindings to *uinput*, the userspace input
15
17
  subsystem. *Uinput* allows userspace programs to create and handle input devices
16
18
  that can inject events directly into the input subsystem.
17
19
 
18
- ***Documentation:***
20
+ ***Documentation:***
19
21
  https://python-evdev.readthedocs.io/en/latest/
20
22
 
21
- ***Development:***
23
+ ***Development:***
22
24
  https://github.com/gvalkov/python-evdev
23
25
 
24
- ***Package:***
25
- https://pypi.python.org/pypi/evdev
26
+ ***Package:***
27
+ https://pypi.org/pypi/evdev
26
28
 
27
- ***Changelog:***
28
- https://python-evdev.readthedocs.io/en/latest/changelog.html
29
+ ***Changelog:***
30
+ https://python-evdev.readthedocs.io/en/latest/changelog.html
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "evdev"
7
- version = "1.9.3"
7
+ version = "2.0.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 = "BSD-3-Clause"
12
- requires-python = ">=3.9"
12
+ requires-python = ">=3.11"
13
13
  authors = [
14
14
  { name="Georgi Valkov", email="georgi.t.valkov@gmail.com" },
15
15
  ]
@@ -34,18 +34,6 @@ line-length = 120
34
34
  [tool.ruff.lint]
35
35
  ignore = ["E265", "E241", "F403", "F401", "E401", "E731"]
36
36
 
37
- [tool.bumpversion]
38
- current_version = "1.9.3"
39
- commit = true
40
- tag = true
41
- allow_dirty = true
42
-
43
- [[tool.bumpversion.files]]
44
- filename = "pyproject.toml"
45
-
46
- [[tool.bumpversion.files]]
47
- filename = "docs/conf.py"
48
-
49
37
  [tool.pylint.'MESSAGES CONTROL']
50
38
  disable = """
51
39
  no-member,
@@ -54,3 +42,18 @@ disable = """
54
42
  [tool.pylint.typecheck]
55
43
  generated-members = ["evdev.ecodes.*"]
56
44
  ignored-modules= ["evdev._*"]
45
+
46
+ [dependency-groups]
47
+ dev = [
48
+ "build>=1.5.0",
49
+ "cibuildwheel>=2.23.4",
50
+ "furo>=2025.12.19",
51
+ "pytest>=9.1.1",
52
+ "ruff>=0.16.4",
53
+ "setuptools>=82.0.1",
54
+ "sphinx>=9.0.4",
55
+ "sphinx-autobuild>=2025.8.25",
56
+ "sphinx-autodoc-typehints>=2.3.0",
57
+ "sphinx-copybutton>=0.5.2",
58
+ "twine>=7.0.0",
59
+ ]
@@ -1,15 +1,15 @@
1
1
  import contextlib
2
2
  import os
3
- from typing import Dict, Generic, Iterator, List, Literal, NamedTuple, Tuple, TypeVar, Union, overload
3
+ from typing import Generic, Iterator, Literal, NamedTuple, AnyStr, overload
4
4
 
5
- from . import _input, ecodes, util
5
+ from . import _input, ecodes, ff, util
6
6
 
7
7
  try:
8
8
  from .eventio_async import EvdevError, EventIO
9
9
  except ImportError:
10
10
  from .eventio import EvdevError, EventIO
11
11
 
12
- _AnyStr = TypeVar("_AnyStr", str, bytes)
12
+ PathLike = AnyStr | os.PathLike[AnyStr]
13
13
 
14
14
 
15
15
  class AbsInfo(NamedTuple):
@@ -58,7 +58,7 @@ class AbsInfo(NamedTuple):
58
58
  flat: int
59
59
  resolution: int
60
60
 
61
- def __str__(self):
61
+ def __str__(self) -> str:
62
62
  return "value {}, min {}, max {}, fuzz {}, flat {}, res {}".format(*self) # pylint: disable=not-an-iterable
63
63
 
64
64
 
@@ -78,7 +78,7 @@ class KbdInfo(NamedTuple):
78
78
  delay: int
79
79
  repeat: int
80
80
 
81
- def __str__(self):
81
+ def __str__(self) -> str:
82
82
  return "delay {}, repeat {}".format(self.delay, self.repeat)
83
83
 
84
84
 
@@ -102,26 +102,31 @@ class DeviceInfo(NamedTuple):
102
102
  return msg.format(*self) # pylint: disable=not-an-iterable
103
103
 
104
104
 
105
- class InputDevice(EventIO, Generic[_AnyStr]):
105
+ class InputDevice(EventIO, Generic[AnyStr]):
106
106
  """
107
107
  A linux input device from which input events can be read.
108
108
  """
109
109
 
110
110
  __slots__ = ("path", "fd", "info", "name", "phys", "uniq", "_rawcapabilities", "version", "ff_effects_count")
111
111
 
112
- def __init__(self, dev: Union[_AnyStr, "os.PathLike[_AnyStr]"]):
112
+ def __init__(self, dev: PathLike, readonly: bool = False):
113
113
  """
114
114
  Arguments
115
115
  ---------
116
- dev : str|bytes|PathLike
116
+ dev :
117
117
  Path to input device
118
+ readonly : bool
119
+ Open in read-only mode (``O_RDONLY``) without attempting ``O_RDWR`` first.
118
120
  """
119
121
 
120
122
  #: Path to input device.
121
- self.path: _AnyStr = dev if not hasattr(dev, "__fspath__") else dev.__fspath__()
123
+ self.path: str | bytes = dev if not hasattr(dev, "__fspath__") else dev.__fspath__()
122
124
 
123
- # Certain operations are possible only when the device is opened in read-write mode.
124
125
  try:
126
+ # Certain operations are possible only when the device is opened in read-write mode.
127
+ # This avoids triggering firmware side-effects (such as LED state re-assertion) on certain hardware.
128
+ if readonly:
129
+ raise OSError
125
130
  fd = os.open(dev, os.O_RDWR | os.O_NONBLOCK)
126
131
  except OSError:
127
132
  fd = os.open(dev, os.O_RDONLY | os.O_NONBLOCK)
@@ -133,7 +138,7 @@ class InputDevice(EventIO, Generic[_AnyStr]):
133
138
  info_res = _input.ioctl_devinfo(self.fd)
134
139
 
135
140
  #: A :class:`DeviceInfo <evdev.device.DeviceInfo>` instance.
136
- self.info = DeviceInfo(*info_res[:4])
141
+ self.info: DeviceInfo = DeviceInfo(*info_res[:4])
137
142
 
138
143
  #: The name of the event device.
139
144
  self.name: str = info_res[4]
@@ -179,21 +184,23 @@ class InputDevice(EventIO, Generic[_AnyStr]):
179
184
  return res
180
185
 
181
186
  @overload
182
- def capabilities(self, verbose: Literal[False] = ..., absinfo: bool = ...) -> Dict[int, List[int]]:
187
+ def capabilities(self, verbose: Literal[False] = ..., absinfo: bool = ...) -> dict[int, list[int]]:
183
188
  ...
184
189
  @overload
185
- def capabilities(self, verbose: Literal[True], absinfo: bool = ...) -> Dict[Tuple[str, int], List[Tuple[str, int]]]:
190
+ def capabilities(self, verbose: Literal[True], absinfo: bool = ...) -> dict[tuple[str, int], list[tuple[str, int]]]:
186
191
  ...
187
- def capabilities(self, verbose: bool = False, absinfo: bool = True) -> Union[Dict[int, List[int]], Dict[Tuple[str, int], List[Tuple[str, int]]]]:
192
+ def capabilities(
193
+ self, verbose: bool = False, absinfo: bool = True
194
+ ) -> dict[int, list[int]] | dict[tuple[str, int], list[tuple[str, int]]]:
188
195
  """
189
196
  Return the event types that this device supports as a mapping of
190
197
  supported event types to lists of handled event codes.
191
198
 
192
199
  Example
193
200
  --------
201
+
194
202
  >>> device.capabilities()
195
- { 1: [272, 273, 274],
196
- 2: [0, 1, 6, 8] }
203
+ { 1: [272, 273, 274], 2: [0, 1, 6, 8] }
197
204
 
198
205
  If ``verbose`` is ``True``, event codes and types will be resolved
199
206
  to their names.
@@ -281,7 +288,7 @@ class InputDevice(EventIO, Generic[_AnyStr]):
281
288
  """
282
289
  self.write(ecodes.EV_LED, led_num, value)
283
290
 
284
- def __eq__(self, other):
291
+ def __eq__(self, other) -> bool:
285
292
  """
286
293
  Two devices are equal if their :data:`info` attributes are equal.
287
294
  """
@@ -295,7 +302,7 @@ class InputDevice(EventIO, Generic[_AnyStr]):
295
302
  msg = (self.__class__.__name__, self.path)
296
303
  return "{}({!r})".format(*msg)
297
304
 
298
- def __fspath__(self):
305
+ def __fspath__(self) -> AnyStr:
299
306
  return self.path
300
307
 
301
308
  def close(self) -> None:
@@ -341,16 +348,19 @@ class InputDevice(EventIO, Generic[_AnyStr]):
341
348
  yield
342
349
  self.ungrab()
343
350
 
344
- def upload_effect(self, effect: "ff.Effect"):
351
+ def upload_effect(self, effect: "ff.Effect", writeback_id: bool = True) -> int:
345
352
  """
346
- Upload a force feedback effect to a force feedback device.
353
+ Upload a force feedback effect to a force feedback device and return its effect id.
354
+ If ``writeback_id`` is ``True``, the effect id is written back to the ``effect`` struct.
347
355
  """
348
356
 
349
357
  data = memoryview(effect).tobytes()
350
358
  ff_id = _input.upload_effect(self.fd, data)
359
+ if writeback_id:
360
+ effect.id = ff_id
351
361
  return ff_id
352
362
 
353
- def erase_effect(self, ff_id) -> None:
363
+ def erase_effect(self, ff_id: int) -> None:
354
364
  """
355
365
  Erase a force effect from a force feedback device. This also
356
366
  stops the effect.
@@ -368,7 +378,7 @@ class InputDevice(EventIO, Generic[_AnyStr]):
368
378
  return KbdInfo(*_input.ioctl_EVIOCGREP(self.fd))
369
379
 
370
380
  @repeat.setter
371
- def repeat(self, value: Tuple[int, int]):
381
+ def repeat(self, value: tuple[int, int]):
372
382
  return _input.ioctl_EVIOCSREP(self.fd, *value)
373
383
 
374
384
  def active_keys(self, verbose: bool = False):
@@ -410,7 +420,16 @@ class InputDevice(EventIO, Generic[_AnyStr]):
410
420
  """
411
421
  return AbsInfo(*_input.ioctl_EVIOCGABS(self.fd, axis_num))
412
422
 
413
- def set_absinfo(self, axis_num: int, value=None, min=None, max=None, fuzz=None, flat=None, resolution=None) -> None:
423
+ def set_absinfo(
424
+ self,
425
+ axis_num: int,
426
+ value: int | None = None,
427
+ min: int | None = None,
428
+ max: int | None = None,
429
+ fuzz: int | None = None,
430
+ flat: int | None = None,
431
+ resolution: int | None = None,
432
+ ) -> None:
414
433
  """
415
434
  Update :class:`AbsInfo` values. Only specified values will be overwritten.
416
435
 
@@ -2,7 +2,7 @@ import fcntl
2
2
  import functools
3
3
  import os
4
4
  import select
5
- from typing import Iterator, Union
5
+ from typing import Iterator, Callable
6
6
 
7
7
  from . import _input, _uinput, ecodes
8
8
  from .events import InputEvent
@@ -28,7 +28,7 @@ class EventIO:
28
28
  beeps).
29
29
  """
30
30
 
31
- def fileno(self):
31
+ def fileno(self) -> int:
32
32
  """
33
33
  Return the file descriptor to the open event device. This makes
34
34
  it possible to pass instances directly to :func:`select.select()` and
@@ -46,7 +46,7 @@ class EventIO:
46
46
  for event in self.read():
47
47
  yield event
48
48
 
49
- def read_one(self) -> Union[InputEvent, None]:
49
+ def read_one(self) -> InputEvent | None:
50
50
  """
51
51
  Read and return a single input event as an instance of
52
52
  :class:`InputEvent <evdev.events.InputEvent>`.
@@ -74,7 +74,7 @@ class EventIO:
74
74
  yield InputEvent(*event)
75
75
 
76
76
  # pylint: disable=no-self-argument
77
- def need_write(func):
77
+ def need_write(func: Callable) -> Callable:
78
78
  """
79
79
  Decorator that raises :class:`EvdevError` if there is no write access to the
80
80
  input device.
@@ -91,7 +91,7 @@ class EventIO:
91
91
 
92
92
  return wrapper
93
93
 
94
- def write_event(self, event):
94
+ def write_event(self, event: InputEvent):
95
95
  """
96
96
  Inject an input event into the input subsystem. Events are
97
97
  queued until a synchronization event is received.
@@ -139,7 +139,7 @@ class EventIO:
139
139
 
140
140
  _uinput.write(self.fd, etype, code, value)
141
141
 
142
- def syn(self):
142
+ def syn(self) -> None:
143
143
  """
144
144
  Inject a ``SYN_REPORT`` event into the input subsystem. Events
145
145
  queued by :func:`write()` will be fired. If possible, events
@@ -148,5 +148,5 @@ class EventIO:
148
148
 
149
149
  self.write(ecodes.EV_SYN, ecodes.SYN_REPORT, 0)
150
150
 
151
- def close(self):
151
+ def close(self) -> None:
152
152
  pass
@@ -15,7 +15,7 @@ else:
15
15
 
16
16
 
17
17
  class ReadIterator:
18
- def __init__(self, device):
18
+ def __init__(self, device: "EventIO"):
19
19
  self.current_batch = iter(())
20
20
  self.device = device
21
21
 
@@ -36,7 +36,7 @@ class ReadIterator:
36
36
  return self
37
37
 
38
38
  def __anext__(self) -> "asyncio.Future[InputEvent]":
39
- future = asyncio.Future()
39
+ future = asyncio.get_running_loop().create_future()
40
40
  try:
41
41
  # Read from the previous batch of events.
42
42
  future.set_result(next(self.current_batch))
@@ -54,8 +54,15 @@ class ReadIterator:
54
54
 
55
55
 
56
56
  class EventIO(eventio.EventIO):
57
- def _do_when_readable(self, callback):
58
- loop = asyncio.get_event_loop()
57
+ # The event loop a reader was last registered on, or None if no async read
58
+ # has been awaited yet. Set in _do_when_readable, used by close().
59
+ _loop: "asyncio.AbstractEventLoop | None" = None
60
+
61
+ def _do_when_readable(self, callback) -> None:
62
+ # Remember the loop the reader is registered on so that close() can
63
+ # remove it later, even when called without a running event loop.
64
+ loop = asyncio.get_running_loop()
65
+ self._loop = loop
59
66
 
60
67
  def ready():
61
68
  loop.remove_reader(self.fileno())
@@ -63,28 +70,28 @@ class EventIO(eventio.EventIO):
63
70
 
64
71
  loop.add_reader(self.fileno(), ready)
65
72
 
66
- def _set_result(self, future, cb):
73
+ def _set_result(self, future, cb) -> None:
67
74
  try:
68
75
  future.set_result(cb())
69
76
  except Exception as error:
70
77
  future.set_exception(error)
71
78
 
72
- def async_read_one(self):
79
+ def async_read_one(self) -> "asyncio.Future[InputEvent]":
73
80
  """
74
81
  Asyncio coroutine to read and return a single input event as
75
82
  an instance of :class:`InputEvent <evdev.events.InputEvent>`.
76
83
  """
77
- future = asyncio.Future()
84
+ future = asyncio.get_running_loop().create_future()
78
85
  self._do_when_readable(lambda: self._set_result(future, self.read_one))
79
86
  return future
80
87
 
81
- def async_read(self):
88
+ def async_read(self) -> "asyncio.Future[InputEvent]":
82
89
  """
83
90
  Asyncio coroutine to read multiple input events from device. Return
84
91
  a generator object that yields :class:`InputEvent <evdev.events.InputEvent>`
85
92
  instances.
86
93
  """
87
- future = asyncio.Future()
94
+ future = asyncio.get_running_loop().create_future()
88
95
  self._do_when_readable(lambda: self._set_result(future, self.read))
89
96
  return future
90
97
 
@@ -96,11 +103,12 @@ class EventIO(eventio.EventIO):
96
103
  """
97
104
  return ReadIterator(self)
98
105
 
99
- def close(self):
100
- try:
101
- loop = asyncio.get_event_loop()
102
- loop.remove_reader(self.fileno())
103
- except RuntimeError:
104
- # no event loop present, so there is nothing to
105
- # remove the reader from. Ignore
106
- pass
106
+ def close(self) -> None:
107
+ # A reader is only registered once an async read has been awaited, in
108
+ # which case _do_when_readable recorded the loop it was added to.
109
+ loop = self._loop
110
+ if loop is None or loop.is_closed():
111
+ # No reader was ever registered, or its loop is already gone, so
112
+ # there is nothing to remove the reader from.
113
+ return
114
+ loop.remove_reader(self.fileno())
@@ -47,7 +47,7 @@ class InputEvent:
47
47
 
48
48
  __slots__ = "sec", "usec", "type", "code", "value"
49
49
 
50
- def __init__(self, sec, usec, type, code, value):
50
+ def __init__(self, sec: int, usec: int, type: int, code: int, value: int):
51
51
  #: Time in seconds since epoch at which event occurred.
52
52
  self.sec: int = sec
53
53
 
@@ -67,11 +67,11 @@ class InputEvent:
67
67
  """Return event timestamp as a float."""
68
68
  return self.sec + (self.usec / 1000000.0)
69
69
 
70
- def __str__(self):
70
+ def __str__(self) -> str:
71
71
  msg = "event at {:f}, code {:02d}, type {:02d}, val {:02d}"
72
72
  return msg.format(self.timestamp(), self.code, self.type, self.value)
73
73
 
74
- def __repr__(self):
74
+ def __repr__(self) -> str:
75
75
  msg = "{}({!r}, {!r}, {!r}, {!r}, {!r})"
76
76
  return msg.format(self.__class__.__name__, self.sec, self.usec, self.type, self.code, self.value)
77
77
 
@@ -95,24 +95,24 @@ class KeyEvent:
95
95
  self.scancode: int = event.code
96
96
 
97
97
  if event.value == 0:
98
- self.keystate = KeyEvent.key_up
98
+ self.keystate: int = KeyEvent.key_up
99
99
  elif event.value == 2:
100
- self.keystate = KeyEvent.key_hold
100
+ self.keystate: int = KeyEvent.key_hold
101
101
  elif event.value == 1:
102
- self.keystate = KeyEvent.key_down
102
+ self.keystate: int = KeyEvent.key_down
103
103
 
104
104
  try:
105
- self.keycode = keys[event.code]
105
+ self.keycode: int = keys[event.code]
106
106
  except KeyError:
107
107
  if allow_unknown:
108
- self.keycode = "0x{:02X}".format(event.code)
108
+ self.keycode: int = "0x{:02X}".format(event.code)
109
109
  else:
110
110
  raise
111
111
 
112
112
  #: Reference to an :class:`InputEvent` instance.
113
113
  self.event: InputEvent = event
114
114
 
115
- def __str__(self):
115
+ def __str__(self) -> str:
116
116
  try:
117
117
  ks = ("up", "down", "hold")[self.keystate]
118
118
  except IndexError:
@@ -121,7 +121,7 @@ class KeyEvent:
121
121
  msg = "key event at {:f}, {} ({}), {}"
122
122
  return msg.format(self.event.timestamp(), self.scancode, self.keycode, ks)
123
123
 
124
- def __repr__(self):
124
+ def __repr__(self) -> str:
125
125
  return "{}({!r})".format(self.__class__.__name__, self.event)
126
126
 
127
127
 
@@ -134,11 +134,11 @@ class RelEvent:
134
134
  #: Reference to an :class:`InputEvent` instance.
135
135
  self.event: InputEvent = event
136
136
 
137
- def __str__(self):
137
+ def __str__(self) -> str:
138
138
  msg = "relative axis event at {:f}, {}"
139
139
  return msg.format(self.event.timestamp(), REL[self.event.code])
140
140
 
141
- def __repr__(self):
141
+ def __repr__(self) -> str:
142
142
  return "{}({!r})".format(self.__class__.__name__, self.event)
143
143
 
144
144
 
@@ -151,11 +151,11 @@ class AbsEvent:
151
151
  #: Reference to an :class:`InputEvent` instance.
152
152
  self.event: InputEvent = event
153
153
 
154
- def __str__(self):
154
+ def __str__(self) -> str:
155
155
  msg = "absolute axis event at {:f}, {}"
156
156
  return msg.format(self.event.timestamp(), ABS[self.event.code])
157
157
 
158
- def __repr__(self):
158
+ def __repr__(self) -> str:
159
159
  return "{}({!r})".format(self.__class__.__name__, self.event)
160
160
 
161
161
 
@@ -171,17 +171,17 @@ class SynEvent:
171
171
  #: Reference to an :class:`InputEvent` instance.
172
172
  self.event: InputEvent = event
173
173
 
174
- def __str__(self):
174
+ def __str__(self) -> str:
175
175
  msg = "synchronization event at {:f}, {}"
176
176
  return msg.format(self.event.timestamp(), SYN[self.event.code])
177
177
 
178
- def __repr__(self):
178
+ def __repr__(self) -> str:
179
179
  return "{}({!r})".format(self.__class__.__name__, self.event)
180
180
 
181
181
 
182
182
  #: A mapping of event types to :class:`InputEvent` sub-classes. Used
183
183
  #: by :func:`evdev.util.categorize()`
184
- event_factory = {
184
+ event_factory: dict[int, type[InputEvent]] = {
185
185
  EV_KEY: KeyEvent,
186
186
  EV_REL: RelEvent,
187
187
  EV_ABS: AbsEvent,
@@ -77,6 +77,9 @@ PyInit__ecodes(void)
77
77
  {
78
78
  PyObject* m = PyModule_Create(&moduledef);
79
79
  if (m == NULL) return NULL;
80
+ #ifdef Py_GIL_DISABLED
81
+ PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
82
+ #endif
80
83
 
81
84
  %s
82
85
 
@@ -0,0 +1,54 @@
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")
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, str | tuple[str]]]", "#: Mapping of event types to other value/name mappings."),
28
+ ("keys", "dict[int, str | tuple[str]]", "#: Keys are a combination of all BTN and KEY codes."),
29
+ ("KEY", "dict[int, str | tuple[str]]", None),
30
+ ("ABS", "dict[int, str | tuple[str]]", None),
31
+ ("REL", "dict[int, str | tuple[str]]", None),
32
+ ("SW", "dict[int, str | tuple[str]]", None),
33
+ ("MSC", "dict[int, str | tuple[str]]", None),
34
+ ("LED", "dict[int, str | tuple[str]]", None),
35
+ ("BTN", "dict[int, str | tuple[str]]", None),
36
+ ("REP", "dict[int, str | tuple[str]]", None),
37
+ ("SND", "dict[int, str | tuple[str]]", None),
38
+ ("ID", "dict[int, str | tuple[str]]", None),
39
+ ("EV", "dict[int, str | tuple[str]]", None),
40
+ ("BUS", "dict[int, str | tuple[str]]", None),
41
+ ("SYN", "dict[int, str | tuple[str]]", None),
42
+ ("FF", "dict[int, str | tuple[str]]", None),
43
+ ("UI_FF", "dict[int, str | tuple[str]]", None),
44
+ ("FF_STATUS", "dict[int, str | tuple[str]]", None),
45
+ ("INPUT_PROP", "dict[int, str | tuple[str]]", None)
46
+ ]
47
+
48
+ for key, annotation, doc in entries:
49
+ if doc:
50
+ print(doc)
51
+
52
+ print(f"{key}: {annotation} = ", end="")
53
+ pprint(getattr(ecodes, key))
54
+ print()
@@ -24,6 +24,14 @@
24
24
  #include <linux/input.h>
25
25
  #endif
26
26
 
27
+ #if defined(Py_GIL_DISABLED)
28
+ #define MAYBE_BEGIN_ALLOW_THREADS Py_BEGIN_ALLOW_THREADS
29
+ #define MAYBE_END_ALLOW_THREADS Py_END_ALLOW_THREADS
30
+ #else
31
+ #define MAYBE_BEGIN_ALLOW_THREADS
32
+ #define MAYBE_END_ALLOW_THREADS
33
+ #endif
34
+
27
35
  #ifndef input_event_sec
28
36
  #define input_event_sec time.tv_sec
29
37
  #define input_event_usec time.tv_usec
@@ -51,7 +59,10 @@ device_read(PyObject *self, PyObject *args)
51
59
  // get device file descriptor (O_RDONLY|O_NONBLOCK)
52
60
  int fd = (int)PyLong_AsLong(PyTuple_GET_ITEM(args, 0));
53
61
 
54
- int n = read(fd, &event, sizeof(event));
62
+ int n;
63
+ MAYBE_BEGIN_ALLOW_THREADS
64
+ n = read(fd, &event, sizeof(event));
65
+ MAYBE_END_ALLOW_THREADS
55
66
 
56
67
  if (n < 0) {
57
68
  if (errno == EAGAIN) {
@@ -84,7 +95,10 @@ device_read_many(PyObject *self, PyObject *args)
84
95
  struct input_event event[64];
85
96
 
86
97
  size_t event_size = sizeof(struct input_event);
87
- ssize_t nread = read(fd, event, event_size*64);
98
+ ssize_t nread;
99
+ MAYBE_BEGIN_ALLOW_THREADS
100
+ nread = read(fd, event, event_size*64);
101
+ MAYBE_END_ALLOW_THREADS
88
102
 
89
103
  if (nread < 0) {
90
104
  PyErr_SetFromErrno(PyExc_OSError);
@@ -570,6 +584,9 @@ moduleinit(void)
570
584
  {
571
585
  PyObject* m = PyModule_Create(&moduledef);
572
586
  if (m == NULL) return NULL;
587
+ #ifdef Py_GIL_DISABLED
588
+ PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
589
+ #endif
573
590
  return m;
574
591
  }
575
592
 
@@ -298,6 +298,25 @@ uinput_write(PyObject *self, PyObject *args)
298
298
  }
299
299
 
300
300
 
301
+ static PyObject *
302
+ uinput_enable_event_type(PyObject *self, PyObject *args)
303
+ {
304
+ int fd;
305
+ uint16_t type;
306
+
307
+ int ret = PyArg_ParseTuple(args, "ih", &fd, &type);
308
+ if (!ret) return NULL;
309
+
310
+ if (ioctl(fd, UI_SET_EVBIT, type) < 0) {
311
+ _uinput_close(fd);
312
+ PyErr_SetFromErrno(PyExc_OSError);
313
+ return NULL;
314
+ }
315
+
316
+ Py_RETURN_NONE;
317
+ }
318
+
319
+
301
320
  static PyObject *
302
321
  uinput_enable_event(PyObject *self, PyObject *args)
303
322
  {
@@ -376,6 +395,9 @@ static PyMethodDef MethodTable[] = {
376
395
  { "enable", uinput_enable_event, METH_VARARGS,
377
396
  "Enable a type of event."},
378
397
 
398
+ { "enable_type", uinput_enable_event_type, METH_VARARGS,
399
+ "Enable an event type without enabling an event code."},
400
+
379
401
  { "set_phys", uinput_set_phys, METH_VARARGS,
380
402
  "Set physical path"},
381
403
 
@@ -405,7 +427,9 @@ moduleinit(void)
405
427
  {
406
428
  PyObject* m = PyModule_Create(&moduledef);
407
429
  if (m == NULL) return NULL;
408
-
430
+ #ifdef Py_GIL_DISABLED
431
+ PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
432
+ #endif
409
433
  PyModule_AddIntConstant(m, "maxnamelen", UINPUT_MAX_NAME_SIZE);
410
434
  return m;
411
435
  }
@@ -5,7 +5,7 @@ import re
5
5
  import stat
6
6
  import time
7
7
  from collections import defaultdict
8
- from typing import Union, Tuple, Dict, Sequence, Optional
8
+ from typing import Sequence
9
9
 
10
10
  from . import _uinput, ecodes, ff, util
11
11
  from .device import InputDevice, AbsInfo
@@ -42,10 +42,10 @@ class UInput(EventIO):
42
42
  @classmethod
43
43
  def from_device(
44
44
  cls,
45
- *devices: Union[InputDevice, Union[str, bytes, os.PathLike]],
46
- filtered_types: Tuple[int] = (ecodes.EV_SYN, ecodes.EV_FF),
45
+ *devices: InputDevice | str | bytes | os.PathLike,
46
+ filtered_types: tuple[int] = (ecodes.EV_SYN, ecodes.EV_FF),
47
47
  **kwargs,
48
- ):
48
+ ) -> "UInput":
49
49
  """
50
50
  Create an UInput device with the capabilities of one or more input
51
51
  devices.
@@ -86,7 +86,7 @@ class UInput(EventIO):
86
86
 
87
87
  def __init__(
88
88
  self,
89
- events: Optional[Dict[int, Sequence[int]]] = None,
89
+ events: dict[int, Sequence[int]] | None = None,
90
90
  name: str = "py-evdev-uinput",
91
91
  vendor: int = 0x1,
92
92
  product: int = 0x1,
@@ -94,11 +94,11 @@ class UInput(EventIO):
94
94
  bustype: int = 0x3,
95
95
  devnode: str = "/dev/uinput",
96
96
  phys: str = "py-evdev-uinput",
97
- input_props=None,
97
+ input_props: Sequence[int] | None = None,
98
98
  # CentOS 7 has sufficiently old headers that FF_MAX_EFFECTS is not defined there,
99
99
  # which causes the whole module to fail loading. Fallback on a hardcoded value of
100
100
  # FF_MAX_EFFECTS if it is not defined in the ecodes.
101
- max_effects=ecodes.ecodes.get("FF_MAX_EFFECTS", 96),
101
+ max_effects: int = ecodes.ecodes.get("FF_MAX_EFFECTS", 96),
102
102
  ):
103
103
  """
104
104
  Arguments
@@ -106,7 +106,9 @@ class UInput(EventIO):
106
106
  events : dict
107
107
  Dictionary of event types mapping to lists of event codes. The
108
108
  event types and codes that the uinput device will be able to
109
- inject - defaults to all key codes.
109
+ inject - defaults to all key codes. An event type mapped to an
110
+ empty sequence enables the event type without enabling any event
111
+ codes, which is useful for types such as ``EV_REP``.
110
112
 
111
113
  name
112
114
  The name of the input device.
@@ -165,6 +167,10 @@ class UInput(EventIO):
165
167
  for prop in input_props:
166
168
  _uinput.set_prop(self.fd, prop)
167
169
 
170
+ for etype, codes in events.items():
171
+ if not codes:
172
+ _uinput.enable_type(self.fd, etype)
173
+
168
174
  for etype, code in prepared_events:
169
175
  _uinput.enable(self.fd, etype, code)
170
176
 
@@ -182,7 +188,7 @@ class UInput(EventIO):
182
188
  #: opened for reading and writing.
183
189
  self.device: InputDevice = self._find_device(self.fd)
184
190
 
185
- def _prepare_events(self, events):
191
+ def _prepare_events(self, events: dict[int, Sequence[int]]):
186
192
  """Prepare events for passing to _uinput.enable and _uinput.setup"""
187
193
  absinfo, prepared_events = [], []
188
194
  for etype, codes in events.items():
@@ -200,19 +206,19 @@ class UInput(EventIO):
200
206
  prepared_events.append((etype, code))
201
207
  return absinfo, prepared_events
202
208
 
203
- def __enter__(self):
209
+ def __enter__(self) -> "UInput":
204
210
  return self
205
211
 
206
- def __exit__(self, type, value, tb):
212
+ def __exit__(self, type, value, tb) -> None:
207
213
  if hasattr(self, "fd"):
208
214
  self.close()
209
215
 
210
- def __repr__(self):
216
+ def __repr__(self) -> str:
211
217
  # TODO:
212
218
  v = (repr(getattr(self, i)) for i in ("name", "bustype", "vendor", "product", "version", "phys"))
213
219
  return "{}({})".format(self.__class__.__name__, ", ".join(v))
214
220
 
215
- def __str__(self):
221
+ def __str__(self) -> str:
216
222
  msg = 'name "{}", bus "{}", vendor "{:04x}", product "{:04x}", version "{:04x}", phys "{}"\nevent types: {}'
217
223
 
218
224
  evtypes = [i[0] for i in self.capabilities(True).keys()]
@@ -222,7 +228,7 @@ class UInput(EventIO):
222
228
 
223
229
  return msg
224
230
 
225
- def close(self):
231
+ def close(self) -> None:
226
232
  # Close the associated InputDevice, if it was previously opened.
227
233
  if self.device is not None:
228
234
  self.device.close()
@@ -232,14 +238,14 @@ class UInput(EventIO):
232
238
  _uinput.close(self.fd)
233
239
  self.fd = -1
234
240
 
235
- def capabilities(self, verbose: bool = False, absinfo: bool = True):
241
+ def capabilities( self, verbose: bool = False, absinfo: bool = True):
236
242
  """See :func:`capabilities <evdev.device.InputDevice.capabilities>`."""
237
243
  if self.device is None:
238
244
  raise UInputError("input device not opened - cannot read capabilities")
239
245
 
240
246
  return self.device.capabilities(verbose, absinfo)
241
247
 
242
- def begin_upload(self, effect_id):
248
+ def begin_upload(self, effect_id: int) -> ff.UInputUpload:
243
249
  upload = ff.UInputUpload()
244
250
  upload.effect_id = effect_id
245
251
 
@@ -249,12 +255,12 @@ class UInput(EventIO):
249
255
 
250
256
  return upload
251
257
 
252
- def end_upload(self, upload):
258
+ def end_upload(self, upload: ff.UInputUpload) -> None:
253
259
  ret = self.dll._uinput_end_upload(self.fd, ctypes.byref(upload))
254
260
  if ret:
255
261
  raise UInputError("Failed to end uinput upload: " + os.strerror(ret))
256
262
 
257
- def begin_erase(self, effect_id):
263
+ def begin_erase(self, effect_id: int) -> ff.UInputErase:
258
264
  erase = ff.UInputErase()
259
265
  erase.effect_id = effect_id
260
266
 
@@ -263,12 +269,12 @@ class UInput(EventIO):
263
269
  raise UInputError("Failed to begin uinput erase: " + os.strerror(ret))
264
270
  return erase
265
271
 
266
- def end_erase(self, erase):
272
+ def end_erase(self, erase: ff.UInputErase) -> None:
267
273
  ret = self.dll._uinput_end_erase(self.fd, ctypes.byref(erase))
268
274
  if ret:
269
275
  raise UInputError("Failed to end uinput erase: " + os.strerror(ret))
270
276
 
271
- def _verify(self):
277
+ def _verify(self) -> None:
272
278
  """
273
279
  Verify that an uinput device exists and is readable and writable
274
280
  by the current process.
@@ -342,7 +348,7 @@ class UInput(EventIO):
342
348
  # shall be the exception that this function raises.
343
349
  return InputDevice(device_path)
344
350
 
345
- def _find_device_fallback(self) -> Union[InputDevice, None]:
351
+ def _find_device_fallback(self) -> InputDevice | None:
346
352
  """
347
353
  Tries to find the device node when UI_GET_SYSNAME is not available or
348
354
  we're running on a system sufficiently exotic that we do not know how
@@ -3,21 +3,28 @@ import glob
3
3
  import os
4
4
  import re
5
5
  import stat
6
- from typing import Union, List
7
6
 
8
7
  from . import ecodes
9
8
  from .events import InputEvent, event_factory, KeyEvent, RelEvent, AbsEvent, SynEvent
10
9
 
11
10
 
12
- def list_devices(input_device_dir: Union[str, bytes, os.PathLike] = "/dev/input") -> List[str]:
13
- """List readable character devices in ``input_device_dir``."""
11
+ def list_devices(input_device_dir: str | bytes | os.PathLike = "/dev/input", writable: bool = True) -> list[str]:
12
+ """List readable (and optionally writable) character devices in ``input_device_dir``.
13
+
14
+ Arguments
15
+ ---------
16
+ input_device_dir
17
+ Path to the input device directory. Default is ``/dev/input``.
18
+ writable
19
+ Only list devices that are both readable and writable.
20
+ """
14
21
 
15
22
  fns = glob.glob("{}/event*".format(input_device_dir))
16
- return list(filter(is_device, fns))
23
+ return [fn for fn in fns if is_device(fn, writable=writable)]
17
24
 
18
25
 
19
- def is_device(fn: Union[str, bytes, os.PathLike]) -> bool:
20
- """Check if ``fn`` is a readable and writable character device."""
26
+ def is_device(fn: str | bytes | os.PathLike, writable: bool = True) -> bool:
27
+ """Check if ``fn`` is a readable (and optionally writable) character device."""
21
28
 
22
29
  if not os.path.exists(fn):
23
30
  return False
@@ -26,13 +33,14 @@ def is_device(fn: Union[str, bytes, os.PathLike]) -> bool:
26
33
  if not stat.S_ISCHR(m):
27
34
  return False
28
35
 
29
- if not os.access(fn, os.R_OK | os.W_OK):
36
+ access = (os.R_OK | os.W_OK) if writable else os.R_OK
37
+ if not os.access(fn, access):
30
38
  return False
31
39
 
32
40
  return True
33
41
 
34
42
 
35
- def categorize(event: InputEvent) -> Union[InputEvent, KeyEvent, RelEvent, AbsEvent, SynEvent]:
43
+ def categorize(event: InputEvent) -> InputEvent | KeyEvent | RelEvent | AbsEvent | SynEvent:
36
44
  """
37
45
  Categorize an event according to its type.
38
46
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: evdev
3
- Version: 1.9.3
3
+ Version: 2.0.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>
@@ -13,7 +13,7 @@ Classifier: Operating System :: POSIX :: Linux
13
13
  Classifier: Intended Audience :: Developers
14
14
  Classifier: Topic :: Software Development :: Libraries
15
15
  Classifier: Programming Language :: Python :: Implementation :: CPython
16
- Requires-Python: >=3.9
16
+ Requires-Python: >=3.11
17
17
  Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
  Dynamic: license-file
@@ -21,9 +21,11 @@ Dynamic: license-file
21
21
  # evdev
22
22
 
23
23
  <p>
24
- <a href="https://pypi.python.org/pypi/evdev"><img alt="pypi version" src="https://img.shields.io/pypi/v/evdev.svg"></a>
24
+ <a href="https://pypi.org/pypi/evdev"><img alt="pypi version" src="https://img.shields.io/pypi/v/evdev.svg"></a>
25
25
  <a href="https://github.com/gvalkov/python-evdev/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/pypi/l/evdev"></a>
26
- <a href="https://repology.org/project/python:evdev/versions"><img alt="Packaging status" src="https://repology.org/badge/tiny-repos/python:evdev.svg"></a>
26
+ <a href="https://repology.org/project/python%3Aevdev/versions">
27
+ <img src="https://repology.org/badge/tiny-repos/python%3Aevdev.svg" alt="Packaging status">
28
+ </a>
27
29
  </p>
28
30
 
29
31
  This package provides bindings to the generic input event interface in Linux.
@@ -35,14 +37,14 @@ This package also comes with bindings to *uinput*, the userspace input
35
37
  subsystem. *Uinput* allows userspace programs to create and handle input devices
36
38
  that can inject events directly into the input subsystem.
37
39
 
38
- ***Documentation:***
40
+ ***Documentation:***
39
41
  https://python-evdev.readthedocs.io/en/latest/
40
42
 
41
- ***Development:***
43
+ ***Development:***
42
44
  https://github.com/gvalkov/python-evdev
43
45
 
44
- ***Package:***
45
- https://pypi.python.org/pypi/evdev
46
+ ***Package:***
47
+ https://pypi.org/pypi/evdev
46
48
 
47
- ***Changelog:***
49
+ ***Changelog:***
48
50
  https://python-evdev.readthedocs.io/en/latest/changelog.html
@@ -67,6 +67,16 @@ def test_enable_events(c):
67
67
  assert sorted(cap[e.EV_KEY]) == sorted(c["events"][e.EV_KEY])
68
68
 
69
69
 
70
+ def test_enable_event_type_without_codes(c):
71
+ e = ecodes
72
+ c["events"] = {e.EV_KEY: [e.KEY_A], e.EV_REP: []}
73
+
74
+ with uinput.UInput(**c) as ui:
75
+ repeat = ui.device.repeat
76
+ assert repeat.delay > 0
77
+ assert repeat.repeat > 0
78
+
79
+
70
80
  def test_abs_values(c):
71
81
  e = ecodes
72
82
  c = {
@@ -1,54 +0,0 @@
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
- ("UI_FF", "Dict[int, Union[str, Tuple[str]]]", None),
44
- ("FF_STATUS", "Dict[int, Union[str, Tuple[str]]]", None),
45
- ("INPUT_PROP", "Dict[int, Union[str, Tuple[str]]]", None)
46
- ]
47
-
48
- for key, annotation, doc in entries:
49
- if doc:
50
- print(doc)
51
-
52
- print(f"{key}: {annotation} = ", end="")
53
- pprint(getattr(ecodes, key))
54
- print()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes