evdev 1.9.2__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 (34) hide show
  1. evdev-2.0.0/PKG-INFO +50 -0
  2. {evdev-1.9.2 → evdev-2.0.0}/README.md +10 -8
  3. {evdev-1.9.2 → evdev-2.0.0}/pyproject.toml +19 -17
  4. {evdev-1.9.2 → evdev-2.0.0}/src/evdev/device.py +42 -23
  5. {evdev-1.9.2 → evdev-2.0.0}/src/evdev/eventio.py +7 -7
  6. {evdev-1.9.2 → evdev-2.0.0}/src/evdev/eventio_async.py +25 -17
  7. {evdev-1.9.2 → evdev-2.0.0}/src/evdev/events.py +17 -17
  8. {evdev-1.9.2 → 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.2 → evdev-2.0.0}/src/evdev/input.c +44 -26
  11. {evdev-1.9.2 → evdev-2.0.0}/src/evdev/uinput.c +25 -1
  12. {evdev-1.9.2 → evdev-2.0.0}/src/evdev/uinput.py +27 -21
  13. {evdev-1.9.2 → evdev-2.0.0}/src/evdev/util.py +16 -8
  14. evdev-2.0.0/src/evdev.egg-info/PKG-INFO +50 -0
  15. {evdev-1.9.2 → evdev-2.0.0}/tests/test_uinput.py +10 -0
  16. evdev-1.9.2/PKG-INFO +0 -78
  17. evdev-1.9.2/src/evdev/genecodes_py.py +0 -54
  18. evdev-1.9.2/src/evdev.egg-info/PKG-INFO +0 -78
  19. {evdev-1.9.2 → evdev-2.0.0}/LICENSE +0 -0
  20. {evdev-1.9.2 → evdev-2.0.0}/MANIFEST.in +0 -0
  21. {evdev-1.9.2 → evdev-2.0.0}/setup.cfg +0 -0
  22. {evdev-1.9.2 → evdev-2.0.0}/setup.py +0 -0
  23. {evdev-1.9.2 → evdev-2.0.0}/src/evdev/__init__.py +0 -0
  24. {evdev-1.9.2 → evdev-2.0.0}/src/evdev/ecodes.py +0 -0
  25. {evdev-1.9.2 → evdev-2.0.0}/src/evdev/ecodes_runtime.py +0 -0
  26. {evdev-1.9.2 → evdev-2.0.0}/src/evdev/evtest.py +0 -0
  27. {evdev-1.9.2 → evdev-2.0.0}/src/evdev/ff.py +0 -0
  28. {evdev-1.9.2 → evdev-2.0.0}/src/evdev/py.typed +0 -0
  29. {evdev-1.9.2 → evdev-2.0.0}/src/evdev.egg-info/SOURCES.txt +0 -0
  30. {evdev-1.9.2 → evdev-2.0.0}/src/evdev.egg-info/dependency_links.txt +0 -0
  31. {evdev-1.9.2 → evdev-2.0.0}/src/evdev.egg-info/top_level.txt +0 -0
  32. {evdev-1.9.2 → evdev-2.0.0}/tests/test_ecodes.py +0 -0
  33. {evdev-1.9.2 → evdev-2.0.0}/tests/test_events.py +0 -0
  34. {evdev-1.9.2 → evdev-2.0.0}/tests/test_util.py +0 -0
evdev-2.0.0/PKG-INFO ADDED
@@ -0,0 +1,50 @@
1
+ Metadata-Version: 2.4
2
+ Name: evdev
3
+ Version: 2.0.0
4
+ Summary: Bindings to the Linux input handling subsystem
5
+ Author-email: Georgi Valkov <georgi.t.valkov@gmail.com>
6
+ Maintainer-email: Tobi <proxima@sezanzeb.de>
7
+ License-Expression: BSD-3-Clause
8
+ Project-URL: Homepage, https://github.com/gvalkov/python-evdev
9
+ Keywords: evdev,input,uinput
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Operating System :: POSIX :: Linux
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Topic :: Software Development :: Libraries
15
+ Classifier: Programming Language :: Python :: Implementation :: CPython
16
+ Requires-Python: >=3.11
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Dynamic: license-file
20
+
21
+ # evdev
22
+
23
+ <p>
24
+ <a href="https://pypi.org/pypi/evdev"><img alt="pypi version" src="https://img.shields.io/pypi/v/evdev.svg"></a>
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%3Aevdev/versions">
27
+ <img src="https://repology.org/badge/tiny-repos/python%3Aevdev.svg" alt="Packaging status">
28
+ </a>
29
+ </p>
30
+
31
+ This package provides bindings to the generic input event interface in Linux.
32
+ The *evdev* interface serves the purpose of passing events generated in the
33
+ kernel directly to userspace through character devices that are typically
34
+ located in `/dev/input/`.
35
+
36
+ This package also comes with bindings to *uinput*, the userspace input
37
+ subsystem. *Uinput* allows userspace programs to create and handle input devices
38
+ that can inject events directly into the input subsystem.
39
+
40
+ ***Documentation:***
41
+ https://python-evdev.readthedocs.io/en/latest/
42
+
43
+ ***Development:***
44
+ https://github.com/gvalkov/python-evdev
45
+
46
+ ***Package:***
47
+ https://pypi.org/pypi/evdev
48
+
49
+ ***Changelog:***
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
@@ -1,15 +1,15 @@
1
1
  [build-system]
2
- requires = ["setuptools>=61.0"]
2
+ requires = ["setuptools>=77.0"]
3
3
  build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "evdev"
7
- version = "1.9.2"
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
- license = {file = "LICENSE"}
12
- requires-python = ">=3.8"
11
+ license = "BSD-3-Clause"
12
+ requires-python = ">=3.11"
13
13
  authors = [
14
14
  { name="Georgi Valkov", email="georgi.t.valkov@gmail.com" },
15
15
  ]
@@ -22,7 +22,6 @@ classifiers = [
22
22
  "Operating System :: POSIX :: Linux",
23
23
  "Intended Audience :: Developers",
24
24
  "Topic :: Software Development :: Libraries",
25
- "License :: OSI Approved :: BSD License",
26
25
  "Programming Language :: Python :: Implementation :: CPython",
27
26
  ]
28
27
 
@@ -35,18 +34,6 @@ line-length = 120
35
34
  [tool.ruff.lint]
36
35
  ignore = ["E265", "E241", "F403", "F401", "E401", "E731"]
37
36
 
38
- [tool.bumpversion]
39
- current_version = "1.9.2"
40
- commit = true
41
- tag = true
42
- allow_dirty = true
43
-
44
- [[tool.bumpversion.files]]
45
- filename = "pyproject.toml"
46
-
47
- [[tool.bumpversion.files]]
48
- filename = "docs/conf.py"
49
-
50
37
  [tool.pylint.'MESSAGES CONTROL']
51
38
  disable = """
52
39
  no-member,
@@ -55,3 +42,18 @@ disable = """
55
42
  [tool.pylint.typecheck]
56
43
  generated-members = ["evdev.ecodes.*"]
57
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()