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.
- {evdev-1.9.3/src/evdev.egg-info → evdev-2.0.0}/PKG-INFO +11 -9
- {evdev-1.9.3 → evdev-2.0.0}/README.md +10 -8
- {evdev-1.9.3 → evdev-2.0.0}/pyproject.toml +17 -14
- {evdev-1.9.3 → evdev-2.0.0}/src/evdev/device.py +42 -23
- {evdev-1.9.3 → evdev-2.0.0}/src/evdev/eventio.py +7 -7
- {evdev-1.9.3 → evdev-2.0.0}/src/evdev/eventio_async.py +25 -17
- {evdev-1.9.3 → evdev-2.0.0}/src/evdev/events.py +17 -17
- {evdev-1.9.3 → evdev-2.0.0}/src/evdev/genecodes_c.py +3 -0
- evdev-2.0.0/src/evdev/genecodes_py.py +54 -0
- {evdev-1.9.3 → evdev-2.0.0}/src/evdev/input.c +19 -2
- {evdev-1.9.3 → evdev-2.0.0}/src/evdev/uinput.c +25 -1
- {evdev-1.9.3 → evdev-2.0.0}/src/evdev/uinput.py +27 -21
- {evdev-1.9.3 → evdev-2.0.0}/src/evdev/util.py +16 -8
- {evdev-1.9.3 → evdev-2.0.0/src/evdev.egg-info}/PKG-INFO +11 -9
- {evdev-1.9.3 → evdev-2.0.0}/tests/test_uinput.py +10 -0
- evdev-1.9.3/src/evdev/genecodes_py.py +0 -54
- {evdev-1.9.3 → evdev-2.0.0}/LICENSE +0 -0
- {evdev-1.9.3 → evdev-2.0.0}/MANIFEST.in +0 -0
- {evdev-1.9.3 → evdev-2.0.0}/setup.cfg +0 -0
- {evdev-1.9.3 → evdev-2.0.0}/setup.py +0 -0
- {evdev-1.9.3 → evdev-2.0.0}/src/evdev/__init__.py +0 -0
- {evdev-1.9.3 → evdev-2.0.0}/src/evdev/ecodes.py +0 -0
- {evdev-1.9.3 → evdev-2.0.0}/src/evdev/ecodes_runtime.py +0 -0
- {evdev-1.9.3 → evdev-2.0.0}/src/evdev/evtest.py +0 -0
- {evdev-1.9.3 → evdev-2.0.0}/src/evdev/ff.py +0 -0
- {evdev-1.9.3 → evdev-2.0.0}/src/evdev/py.typed +0 -0
- {evdev-1.9.3 → evdev-2.0.0}/src/evdev.egg-info/SOURCES.txt +0 -0
- {evdev-1.9.3 → evdev-2.0.0}/src/evdev.egg-info/dependency_links.txt +0 -0
- {evdev-1.9.3 → evdev-2.0.0}/src/evdev.egg-info/top_level.txt +0 -0
- {evdev-1.9.3 → evdev-2.0.0}/tests/test_ecodes.py +0 -0
- {evdev-1.9.3 → evdev-2.0.0}/tests/test_events.py +0 -0
- {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:
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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 = "
|
|
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.
|
|
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
|
|
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
|
-
|
|
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[
|
|
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:
|
|
112
|
+
def __init__(self, dev: PathLike, readonly: bool = False):
|
|
113
113
|
"""
|
|
114
114
|
Arguments
|
|
115
115
|
---------
|
|
116
|
-
dev :
|
|
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:
|
|
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 = ...) ->
|
|
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 = ...) ->
|
|
190
|
+
def capabilities(self, verbose: Literal[True], absinfo: bool = ...) -> dict[tuple[str, int], list[tuple[str, int]]]:
|
|
186
191
|
...
|
|
187
|
-
def capabilities(
|
|
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:
|
|
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(
|
|
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,
|
|
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) ->
|
|
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.
|
|
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
|
-
|
|
58
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
#
|
|
105
|
-
# remove the reader from.
|
|
106
|
-
|
|
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,
|
|
@@ -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
|
|
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
|
|
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
|
|
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:
|
|
46
|
-
filtered_types:
|
|
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:
|
|
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) ->
|
|
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:
|
|
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
|
|
23
|
+
return [fn for fn in fns if is_device(fn, writable=writable)]
|
|
17
24
|
|
|
18
25
|
|
|
19
|
-
def is_device(fn:
|
|
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
|
-
|
|
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) ->
|
|
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:
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|