From 6f344e196b73ea0d6b57ac525d33c7df874a1b01 Mon Sep 17 00:00:00 2001 From: Sylvain Pasche Date: Sat, 5 Jul 2014 21:54:06 +0100 Subject: [PATCH 001/270] make the bits arrays large enough --- evdev/input.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evdev/input.c b/evdev/input.c index 6a62f17..e95758a 100644 --- a/evdev/input.c +++ b/evdev/input.c @@ -139,7 +139,7 @@ static PyObject * ioctl_capabilities(PyObject *self, PyObject *args) { int fd, ev_type, ev_code; - char ev_bits[EV_MAX/8], code_bits[KEY_MAX/8]; + char ev_bits[EV_MAX/8 + 1], code_bits[KEY_MAX/8 + 1]; struct input_absinfo absinfo; int ret = PyArg_ParseTuple(args, "i", &fd); From d1405c5789b3842cec789b0cf996533a99642662 Mon Sep 17 00:00:00 2001 From: Sylvain Pasche Date: Sat, 5 Jul 2014 21:01:28 +0000 Subject: [PATCH 002/270] expose API to retrieve active keys --- bin/evtest.py | 2 ++ evdev/device.py | 18 ++++++++++++++++++ evdev/input.c | 28 ++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/bin/evtest.py b/bin/evtest.py index ae37673..94fe6f4 100755 --- a/bin/evtest.py +++ b/bin/evtest.py @@ -76,6 +76,8 @@ def print_event(e): if ('EV_LED', ecodes.EV_LED) in capabs: print('Active LEDs: {}\n'.format(','.join(i[0] for i in device.leds(True)))) +print('Currently active keys: {}\n'.format(','.join(k[0] for k in device.active_keys(True)))) + print('Device capabilities:') for type, codes in capabs.items(): print(' Type {} {}:'.format(*type)) diff --git a/evdev/device.py b/evdev/device.py index a83b4ee..ce1b96b 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -310,3 +310,21 @@ def repeat(self): @repeat.setter def repeat(self, value): return _input.ioctl_EVIOCSREP(self.fd, *value) + + def active_keys(self, verbose=False): + ''' + Return currently active keys. Example:: + + [1, 42] + + If ``verbose`` is ``True``, keys codes are resolved to + their names. Unknown codes are resolved to ``'?'``. Example:: + + [('KEY_ESC', 1), ('KEY_LEFTSHIFT', 42)] + + ''' + active_keys = _input.ioctl_EVIOCGKEY(self.fd) + if verbose: + return [(ecodes.KEY[k] if k in ecodes.KEY else '?', k) for k in active_keys] + + return active_keys diff --git a/evdev/input.c b/evdev/input.c index e95758a..621af84 100644 --- a/evdev/input.c +++ b/evdev/input.c @@ -309,6 +309,33 @@ ioctl_EVIOCGRAB(PyObject *self, PyObject *args) } +static PyObject * +ioctl_EVIOCGKEY(PyObject *self, PyObject *args) +{ + int fd, ret, key; + char keys_bitmask[KEY_MAX/8 + 1]; + PyObject* res = PyList_New(0); + + ret = PyArg_ParseTuple(args, "i", &fd); + if (!ret) return NULL; + + memset(&keys_bitmask, 0, sizeof(keys_bitmask)); + ret = ioctl(fd, EVIOCGKEY(sizeof(keys_bitmask)), keys_bitmask); + if (ret < 0) { + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + + for (key = 0; key < KEY_MAX; key++) { + if (test_bit(keys_bitmask, key)) { + PyList_Append(res, Py_BuildValue("i", key)); + } + } + + return res; +} + + // todo: this function needs a better name static PyObject * get_sw_led_snd(PyObject *self, PyObject *args) @@ -437,6 +464,7 @@ static PyMethodDef MethodTable[] = { { "ioctl_EVIOCSREP", ioctl_EVIOCSREP, METH_VARARGS}, { "ioctl_EVIOCGVERSION", ioctl_EVIOCGVERSION, METH_VARARGS}, { "ioctl_EVIOCGRAB", ioctl_EVIOCGRAB, METH_VARARGS}, + { "ioctl_EVIOCGKEY", ioctl_EVIOCGKEY, METH_VARARGS, "get global key state" }, { "ioctl_EVIOCGEFFECTS", ioctl_EVIOCGEFFECTS, METH_VARARGS, "fetch the number of effects the device can keep in its memory." }, { "get_sw_led_snd", get_sw_led_snd, METH_VARARGS}, { "device_read", device_read, METH_VARARGS, "read an input event from a device" }, From bafe6cf8fb335cc2eff5a19fe5e2a738b436067a Mon Sep 17 00:00:00 2001 From: Sylvain Pasche Date: Sat, 5 Jul 2014 21:10:10 +0000 Subject: [PATCH 003/270] add documentation for the InputDevice.active_keys() method --- doc/tutorial.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/doc/tutorial.rst b/doc/tutorial.rst index 1c0349a..9248e9b 100644 --- a/doc/tutorial.rst +++ b/doc/tutorial.rst @@ -74,6 +74,18 @@ Getting and setting LED states >>> dev.set_led(ecodes.LED_NUML, 0) # disable numlock +Getting currently active keys +============================= + +:: + + >>> dev.active_keys(verbose=True) + ... [('KEY_3', 4), ('KEY_LEFTSHIFT', 42)] + + >>> dev.active_keys() + ... [4, 42] + + Accessing input subsystem constants =================================== From e0eb25f17f15f313041b5e173cc2cd5031aa2fe3 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 6 Jul 2014 11:50:39 +0200 Subject: [PATCH 004/270] fix typo (s/key/keys) --- evdev/device.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/evdev/device.py b/evdev/device.py index ce1b96b..ea6c837 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -317,8 +317,9 @@ def active_keys(self, verbose=False): [1, 42] - If ``verbose`` is ``True``, keys codes are resolved to - their names. Unknown codes are resolved to ``'?'``. Example:: + If ``verbose`` is ``True``, key codes are resolved to their + verbose names. Unknown codes are resolved to ``'?'``. For + example:: [('KEY_ESC', 1), ('KEY_LEFTSHIFT', 42)] From 318bf52453055ce00fc1d66006d25ef81f013dfa Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 6 Jul 2014 11:56:20 +0200 Subject: [PATCH 005/270] change output format a little --- bin/evtest.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/bin/evtest.py b/bin/evtest.py index 94fe6f4..c13f0ee 100755 --- a/bin/evtest.py +++ b/bin/evtest.py @@ -71,12 +71,14 @@ def print_event(e): print('Device name: {.name}'.format(device)) print('Device info: {.info}'.format(device)) -print('Repeat settings: {}'.format(device.repeat)) +print('Repeat settings: {}\n'.format(device.repeat)) if ('EV_LED', ecodes.EV_LED) in capabs: - print('Active LEDs: {}\n'.format(','.join(i[0] for i in device.leds(True)))) + leds = ','.join(i[0] for i in device.leds(True)) + print('Active LEDs: %s' % leds) -print('Currently active keys: {}\n'.format(','.join(k[0] for k in device.active_keys(True)))) +active_keys = ','.join(k[0] for k in device.active_keys(True)) +print('Active keys: %s\n' % active_keys) print('Device capabilities:') for type, codes in capabs.items(): From b4b113a050778d08a81352178422b75352ca61c1 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 6 Jul 2014 12:27:19 +0200 Subject: [PATCH 006/270] bump version to 0.4.5 --- LICENSE | 25 +++++++++++++------------ doc/changelog.rst | 13 ++++++++++++- setup.py | 2 +- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/LICENSE b/LICENSE index ec85c1d..f2cb880 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2012-2013 Georgi Valkov. All rights reserved. +Copyright (c) 2012-2014 Georgi Valkov. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -6,7 +6,7 @@ met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the @@ -16,13 +16,14 @@ met: be used to endorse or promote products derived from this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL GEORGI VALKOV BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/doc/changelog.rst b/doc/changelog.rst index 2b3b09f..d2a7a05 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -1,6 +1,16 @@ Changelog ========= +0.4.5 (Jul 06, 2014) +^^^^^^^^^^^^^^^^^^^^ + +Enhancements: + - Add method for returning a list of the currently active keys - + ``InputDevice.active_keys()`` (thanks `@spasche`_). + +Fixes: + - Fix a potential buffer overflow in ``ioctl_capabilities`` (thanks `@spasche`_). + 0.4.4 (Jun 04, 2014) ^^^^^^^^^^^^^^^^^^^^ @@ -25,7 +35,7 @@ Enhancements: - Rework documentation and docstrings. Fixes: - - Call ``InputDevice.close()`` in ``InputDevice.__del__()``. + - Call ``InputDevice.close()`` from ``InputDevice.__del__()``. 0.4.1 (Jul 24, 2013) ^^^^^^^^^^^^^^^^^^^^ @@ -171,3 +181,4 @@ Fixes: .. _`@bgilbert`: https://github.com/bgilbert .. _`@accek`: https://github.com/accek .. _`@kived`: https://github.com/kived +.. _`@spasche`: https://github.com/spasche diff --git a/setup.py b/setup.py index c153d78..9b201a2 100755 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ kw = { 'name': 'evdev', - 'version': '0.4.4', + 'version': '0.4.5', 'description': 'Bindings for the linux input handling subsystem', 'long_description': open(pjoin(here, 'README.rst')).read(), From 5bd72d0bf89552fb9d1e68c1504bbf20dd0fbdc2 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Fri, 15 Aug 2014 00:43:17 +0200 Subject: [PATCH 007/270] workaround for http://bugs.python.org/issue21121 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9b201a2..3c412e8 100755 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ 'Programming Language :: Python :: Implementation :: CPython', ) -cflags = ['-std=c99'] +cflags = ['-std=c99', '-Wno-error=declaration-after-statement'] input_c = Extension('evdev._input', sources=['evdev/input.c'], extra_compile_args=cflags) uinput_c = Extension('evdev._uinput', sources=['evdev/uinput.c'], extra_compile_args=cflags) ecodes_c = Extension('evdev._ecodes', sources=['evdev/ecodes.c'], extra_compile_args=cflags) From 64c07b8a168691451fa5cf146ce3b60b74b37d29 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Fri, 15 Aug 2014 00:57:25 +0200 Subject: [PATCH 008/270] update tutorial --- doc/tutorial.rst | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/doc/tutorial.rst b/doc/tutorial.rst index 9248e9b..5ec06ae 100644 --- a/doc/tutorial.rst +++ b/doc/tutorial.rst @@ -240,8 +240,9 @@ Specifying ``uinput`` device options >>> cap = { ... e.EV_KEY : [e.KEY_A, e.KEY_B], ... e.EV_ABS : [ - ... (e.ABS_X, AbsInfo(min=0, max=255, fuzz=0, flat=0)), - ... (e.ABS_Y, AbsInfo(0, 255, 0, 0)), + ... (e.ABS_X, AbsInfo(value=0, min=0, max=255, + ... fuzz=0, flat=0, resolution=0)), + ... (e.ABS_Y, AbsInfo(0, 0, 255, 0, 0, 0)), ... (e.ABS_MT_POSITION_X, (0, 255, 128, 0)) ] ... } @@ -251,10 +252,11 @@ Specifying ``uinput`` device options event types: EV_KEY EV_ABS EV_SYN >>> print(ui.capabilities()) - ... { 0: [0, 1, 3], 1: [30, 48], - ... 3: [(0, AbsInfo(min=0, max=255, fuzz=0, flat=0)), - ... (1, AbsInfo(min=0, max=255, fuzz=0, flat=0)), - ... (53, AbsInfo(min=0, max=255, fuzz=128, flat=0))] } + {0: [0, 1, 3], + 1: [30, 48], + 3: [(0, AbsInfo(value=0, min=0, max=0, fuzz=255, flat=0, resolution=0)), + (1, AbsInfo(value=0, min=0, max=0, fuzz=255, flat=0, resolution=0)), + (53, AbsInfo(value=0, min=0, max=255, fuzz=128, flat=0, resolution=0))]} >>> # move mouse cursor >>> ui.write(e.EV_ABS, e.ABS_X, 20) From 7f3ffee9735d3779290f85102631ac10500b5a7c Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sat, 6 Sep 2014 00:45:12 +0200 Subject: [PATCH 009/270] remove distinction between enhancements and fixes --- doc/changelog.rst | 156 ++++++++++++++++++++-------------------------- 1 file changed, 68 insertions(+), 88 deletions(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index d2a7a05..cc78e8a 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -4,172 +4,152 @@ Changelog 0.4.5 (Jul 06, 2014) ^^^^^^^^^^^^^^^^^^^^ -Enhancements: - - Add method for returning a list of the currently active keys - - ``InputDevice.active_keys()`` (thanks `@spasche`_). +- Add method for returning a list of the currently active keys - + ``InputDevice.active_keys()`` (thanks `@spasche`_). -Fixes: - - Fix a potential buffer overflow in ``ioctl_capabilities`` (thanks `@spasche`_). +- Fix a potential buffer overflow in ``ioctl_capabilities`` (thanks `@spasche`_). 0.4.4 (Jun 04, 2014) ^^^^^^^^^^^^^^^^^^^^ -Fixes: - - Calling ``InputDevice.read_one()`` should always return - ``None``, when there is nothing to be read, even in case of a - ``EAGAIN`` errno (thanks JPP). +- Calling ``InputDevice.read_one()`` should always return ``None``, + when there is nothing to be read, even in case of a ``EAGAIN`` errno + (thanks JPP). 0.4.3 (Dec 19, 2013) ^^^^^^^^^^^^^^^^^^^^ +- Silence ``OSError`` in destructor (thanks `@polyphemus`_). -Fixes: - - Silence ``OSError`` in destructor (thanks `@polyphemus`_). - - - Make ``InputDevice.close()`` work in cases in which stdin (fd 0) - has been closed (thanks `@polyphemus`_). +- Make ``InputDevice.close()`` work in cases in which stdin (fd 0) has + been closed (thanks `@polyphemus`_). 0.4.2 (Dec 13, 2013) ^^^^^^^^^^^^^^^^^^^^ -Enhancements: - - Rework documentation and docstrings. +- Rework documentation and docstrings. -Fixes: - - Call ``InputDevice.close()`` from ``InputDevice.__del__()``. +- Call ``InputDevice.close()`` from ``InputDevice.__del__()``. 0.4.1 (Jul 24, 2013) ^^^^^^^^^^^^^^^^^^^^ -Fixes: - - Fix reference counting in ``device_read``, ``device_read_many`` - and ``ioctl_capabilities``. +- Fix reference counting in ``device_read``, ``device_read_many`` and + ``ioctl_capabilities``. 0.4.0 (Jul 01, 2013) ^^^^^^^^^^^^^^^^^^^^ -Enhancements: - - Add ``FF_*`` and ``FF_STATUS`` codes to ``ecodes`` (thanks `@bgilbert`_). +- Add ``FF_*`` and ``FF_STATUS`` codes to ``ecodes`` (thanks `@bgilbert`_). - - Reverse event code mappings (``ecodes.{KEY,FF,REL,ABS}`` and - etc.) will now map to a list of codes, whenever a value - corresponds to multiple codes:: +- Reverse event code mappings (``ecodes.{KEY,FF,REL,ABS}`` and etc.) + will now map to a list of codes, whenever a value corresponds to + multiple codes:: - >>> ecodes.KEY[152] - ... ['KEY_COFFEE', 'KEY_SCREENLOCK'] - >>> ecodes.KEY[30] - ... 'KEY_A' + >>> ecodes.KEY[152] + ... ['KEY_COFFEE', 'KEY_SCREENLOCK'] + >>> ecodes.KEY[30] + ... 'KEY_A' - - Set the state of a LED through ``device.set_led()`` (thanks - `@accek`_). ``device.fd`` is opened in ``O_RDWR`` mode from now on. +- Set the state of a LED through ``device.set_led()`` (thanks + `@accek`_). ``device.fd`` is opened in ``O_RDWR`` mode from now on. -Fixes: - - Fix segfault in ``device_read_many()`` (thanks `@bgilbert`_). +- Fix segfault in ``device_read_many()`` (thanks `@bgilbert`_). 0.3.3 (May 29, 2013) ^^^^^^^^^^^^^^^^^^^^ -Fixes: - - Raise ``IOError`` from ``device_read()`` and ``device_read_many()`` when - ``read()`` fails. +- Raise ``IOError`` from ``device_read()`` and ``device_read_many()`` when + ``read()`` fails. - - Several stability and style changes (thank you debian code reviewers). +- Several stability and style changes (thank you debian code reviewers). 0.3.2 (Apr 05, 2013) ^^^^^^^^^^^^^^^^^^^^ -Fixes: - - Fix vendor id and product id order in ``DeviceInfo`` (thanks `@kived`_). +- Fix vendor id and product id order in ``DeviceInfo`` (thanks `@kived`_). 0.3.1 (Nov 23, 2012) ^^^^^^^^^^^^^^^^^^^^ -Fixes: - - ``device.read()`` will return an empty tuple if the device has - nothing to offer (instead of segfaulting). +- ``device.read()`` will return an empty tuple if the device has + nothing to offer (instead of segfaulting). - - Exclude unnecessary package data in sdist and bdist. +- Exclude unnecessary package data in sdist and bdist. 0.3.0 (Nov 06, 2012) ^^^^^^^^^^^^^^^^^^^^ -Enhancements: - - Add ability to set/get auto-repeat settings with ``EVIOC{SG}REP``. +- Add ability to set/get auto-repeat settings with ``EVIOC{SG}REP``. - - Add ``device.version`` - the value of ``EVIOCGVERSION``. +- Add ``device.version`` - the value of ``EVIOCGVERSION``. - - Add ``device.read_loop()``. +- Add ``device.read_loop()``. - - Add ``device.grab()`` and ``device.ungrab()`` - exposes ``EVIOCGRAB``. +- Add ``device.grab()`` and ``device.ungrab()`` - exposes ``EVIOCGRAB``. - - Add ``device.leds`` - exposes ``EVIOCGLED``. +- Add ``device.leds`` - exposes ``EVIOCGLED``. - - Replace ``DeviceInfo`` class with a namedtuple. +- Replace ``DeviceInfo`` class with a namedtuple. -Fixes: - - ``device.read_one()`` was dropping events. +- Prevent ``device.read_one()`` from skipping events. - - Rename ``AbsData`` to ``AbsInfo`` (as in ``struct input_absinfo``). +- Rename ``AbsData`` to ``AbsInfo`` (as in ``struct input_absinfo``). 0.2.0 (Aug 22, 2012) ^^^^^^^^^^^^^^^^^^^^ -Enhancements: - - Add the ability to set arbitrary device capabilities on uinput - devices (defaults to all ``EV_KEY`` ecodes). - - - Add ``UInput.device`` which is an open ``InputDevice`` to the - input device that uinput 'spawns'. +- Add the ability to set arbitrary device capabilities on uinput + devices (defaults to all ``EV_KEY`` ecodes). - - Add ``UInput.capabilities()`` which is just a shortcut to - ``UInput.device.capabilities()``. +- Add ``UInput.device`` which is an open ``InputDevice`` to the + input device that uinput 'spawns'. - - Rename ``UInput.write()`` to ``UInput.write_event()``. +- Add ``UInput.capabilities()`` which is just a shortcut to + ``UInput.device.capabilities()``. - - Add a simpler ``UInput.write(type, code, value)`` method. +- Rename ``UInput.write()`` to ``UInput.write_event()``. - - Make all ``UInput`` constructor arguments optional (default - device name is now ``py-evdev-uinput``). +- Add a simpler ``UInput.write(type, code, value)`` method. - - Add the ability to set ``absmin``, ``absmax``, ``absfuzz`` and - ``absflat`` when specifying the uinput device's capabilities. +- Make all ``UInput`` constructor arguments optional (default + device name is now ``py-evdev-uinput``). - - Remove the ``nophys`` argument - if a device fails the - ``EVIOCGPHYS`` ioctl, phys will equal the empty string. +- Add the ability to set ``absmin``, ``absmax``, ``absfuzz`` and + ``absflat`` when specifying the uinput device's capabilities. - - Make ``InputDevice.capabilities()`` perform a ``EVIOCGABS`` ioctl - for devices that support ``EV_ABS`` and return that info wrapped in - an ``AbsData`` namedtuple. +- Remove the ``nophys`` argument - if a device fails the + ``EVIOCGPHYS`` ioctl, phys will equal the empty string. - - Split ``ioctl_devinfo`` into ``ioctl_devinfo`` and - ``ioctl_capabilities``. +- Make ``InputDevice.capabilities()`` perform a ``EVIOCGABS`` ioctl + for devices that support ``EV_ABS`` and return that info wrapped in + an ``AbsData`` namedtuple. - - Split ``uinput_open()`` to ``uinput_open()`` and ``uinput_create()`` +- Split ``ioctl_devinfo`` into ``ioctl_devinfo`` and + ``ioctl_capabilities``. - - Add more uinput usage examples and documentation. +- Split ``uinput_open()`` to ``uinput_open()`` and ``uinput_create()`` - - Rewrite uinput tests. +- Add more uinput usage examples and documentation. - - Remove ``mouserel`` and ``mouseabs`` from ``UInput``. +- Rewrite uinput tests. - - Tie the sphinx version and release to the distutils version. +- Remove ``mouserel`` and ``mouseabs`` from ``UInput``. - - Set 'methods-before-attributes' sorting in the docs. +- Tie the sphinx version and release to the distutils version. +- Set 'methods-before-attributes' sorting in the docs. -Fixes: - - Remove ``KEY_CNT`` and ``KEY_MAX`` from ``ecodes.keys``. +- Remove ``KEY_CNT`` and ``KEY_MAX`` from ``ecodes.keys``. 0.1.1 (May 18, 2012) ^^^^^^^^^^^^^^^^^^^^ -Enhancements: - - Add ``events.keys``, which is a combination of all ``BTN_`` and - ``KEY_`` event codes. +- Add ``events.keys``, which is a combination of all ``BTN_`` and + ``KEY_`` event codes. -Fixes: - - ``ecodes.c`` was not generated when installing through ``pip``. +- ``ecodes.c`` was not generated when installing through ``pip``. 0.1.0 (May 17, 2012) From c222931128ae744eae62513d58b103f6f3087cb7 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 7 Sep 2014 20:05:49 +0200 Subject: [PATCH 010/270] clean up apidoc --- doc/apidoc.rst | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/doc/apidoc.rst b/doc/apidoc.rst index 48e4329..96a5c73 100644 --- a/doc/apidoc.rst +++ b/doc/apidoc.rst @@ -7,7 +7,8 @@ API documentation .. automodule:: evdev.events :members: InputEvent, KeyEvent, AbsEvent, RelEvent, SynEvent, event_factory :undoc-members: - :member-order: groupwise + :exclude-members: __dict__, __str__, __module__, __del__, __slots__, __repr__ + :member-order: bysource ``device`` ============ @@ -16,7 +17,8 @@ API documentation :members: InputDevice, DeviceInfo, AbsInfo, KbdInfo :undoc-members: :special-members: - :member-order: groupwise + :exclude-members: __dict__, __str__, __module__, __del__, __slots__, __repr__ + :member-order: bysource ``uinput`` ============ @@ -24,17 +26,29 @@ API documentation .. autoclass:: evdev.uinput.UInput :members: :special-members: - :member-order: groupwise + :exclude-members: __dict__, __str__, __module__, __del__, __slots__, __repr__ + :member-order: bysource ``util`` ========== .. automodule:: evdev.util :members: list_devices, is_device, categorize, resolve_ecodes - :member-order: groupwise + :member-order: bysource ``ecodes`` ============ .. automodule:: evdev.ecodes :members: + :exclude-members: __module__, keys, ecodes, bytype + :member-order: bysource + +.. autodata:: evdev.ecodes.keys + :annotation: {0: 'KEY_RESERVED', 1: 'KEY_ESC', 2: 'KEY_1', ...} + +.. autodata:: evdev.ecodes.ecodes + :annotation: {'KEY_END': 107, 'FF_RUMBLE': 80, 'KEY_KPDOT': 83, 'KEY_CNT': 768, ...}' + +.. autodata:: evdev.ecodes.bytype + :annotation: {0: {0: 'SYN_REPORT', 1: 'SYN_CONFIG', 2: 'SYN_MT_REPORT', 3: 'SYN_DROPPED'}, ...} From 7933a4d7cc7eeaea3fd650476a816157b593afb9 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Mon, 8 Sep 2014 00:15:36 +0200 Subject: [PATCH 011/270] redesign documentation and cleanup docstrings Several elements from the new documentation have been inspired or outright stolen from Plumbum's excellent landing page. --- .gitignore | 1 + doc/Makefile | 9 + doc/_static/evdev-logo-small.png | Bin 0 -> 5910 bytes doc/_static/evdev-logo.png | Bin 0 -> 4418 bytes doc/_static/github-logo.png | Bin 0 -> 13001 bytes .../distributor-logo-archlinux.png | Bin 0 -> 2777 bytes .../distributor-logo-debian.png | Bin 0 -> 2937 bytes .../distributor-logo-fedora.png | Bin 0 -> 3175 bytes .../distributor-logo-linux-mint.png | Bin 0 -> 3365 bytes .../distributor-logo-opensuse.png | Bin 0 -> 3193 bytes .../distributor-logo-raspbian.png | Bin 0 -> 1907 bytes .../distributor-logo-ubuntu.png | Bin 0 -> 3369 bytes doc/apidoc.rst | 8 +- doc/changelog.rst | 9 + doc/conf.py | 24 +- doc/index.rst | 214 +++++++++++++++--- doc/install.rst | 42 ---- doc/news.rst | 33 +++ doc/tutorial.rst | 52 ++--- evdev/__init__.py | 2 + evdev/device.py | 27 +-- evdev/events.py | 6 +- evdev/uinput.py | 2 +- evdev/util.py | 9 +- setup.py | 7 +- 25 files changed, 302 insertions(+), 143 deletions(-) create mode 100644 doc/_static/evdev-logo-small.png create mode 100644 doc/_static/evdev-logo.png create mode 100644 doc/_static/github-logo.png create mode 100644 doc/_static/pacifica-icon-set/distributor-logo-archlinux.png create mode 100644 doc/_static/pacifica-icon-set/distributor-logo-debian.png create mode 100644 doc/_static/pacifica-icon-set/distributor-logo-fedora.png create mode 100644 doc/_static/pacifica-icon-set/distributor-logo-linux-mint.png create mode 100644 doc/_static/pacifica-icon-set/distributor-logo-opensuse.png create mode 100644 doc/_static/pacifica-icon-set/distributor-logo-raspbian.png create mode 100644 doc/_static/pacifica-icon-set/distributor-logo-ubuntu.png delete mode 100644 doc/install.rst create mode 100644 doc/news.rst diff --git a/.gitignore b/.gitignore index 7623229..da51850 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ TAGS evdev/*.so evdev/ecodes.c doc/_build +.#* __pycache__ evdev/_ecodes.py diff --git a/doc/Makefile b/doc/Makefile index 3af3204..68b86e6 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -41,10 +41,12 @@ help: clean: -rm -rf $(BUILDDIR)/* + html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + @sed -i $(BUILDDIR)/html/index.html -e 's,Synopsis,Python Bindings,' dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @@ -152,6 +154,13 @@ doctest: @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." + +# .PHONY: news.rst +# news.rst: changelog.rst +# cat $< | grep -FB1 '^^^^^^^^^' \ +# | sed -e '/^\^/d;/--/d' -e 's,\(.*\)(\(.*\)),* **\2**: Version \1released.\n,' \ +# > $@ + $(BUILDDIR)/html/evdev-doc.zip: html cd $(BUILDDIR)/html/ && zip -x \*.zip -r evdev-doc.zip . @echo `readlink -f $@` diff --git a/doc/_static/evdev-logo-small.png b/doc/_static/evdev-logo-small.png new file mode 100644 index 0000000000000000000000000000000000000000..95c6491d2945abe777b1b95fda97a5154f1c58d3 GIT binary patch literal 5910 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 literal 0 HcmV?d00001 diff --git a/doc/_static/evdev-logo.png b/doc/_static/evdev-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..168a90aab13134a93cc573c810149e2a166b6250 GIT binary patch literal 4418 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 literal 0 HcmV?d00001 diff --git a/doc/_static/github-logo.png b/doc/_static/github-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..e03d8dd8bcf0d851fe1eb89f93a39de843e5c69c GIT binary patch literal 13001 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 Dq_KSN literal 0 HcmV?d00001 diff --git a/doc/_static/pacifica-icon-set/distributor-logo-archlinux.png b/doc/_static/pacifica-icon-set/distributor-logo-archlinux.png new file mode 100644 index 0000000000000000000000000000000000000000..e1e7b54fd2054838d0e33ef7ac22eefe18a758d3 GIT binary patch literal 2777 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 f%&7lggS-9H zK6dQbar5WT|6xu}&Toz%KOWtihCb0ZFaQfM0UIy^D=_!iGoa?EQKN>j4%o-2vFNP0 zxcJ?MhKBry4<9x?e*E~+lP6E8FYOV#9z1x^#4w#JDk@qIjKFHgJdDv8;t~1r9-z~&0KLZL_j zH*mBUPtF(SO(?Wz(V|JLu@*sL!B{wmj5xW=tyw1*9guRi~H}gWDzWv z%eS`vAymD6`*yz}FBmYl4iy-i%C~?cxVCeDWPVgstf06e#94nD_0GIO;KR5TGmc^4 z+BOV^poE+?Yt}^eg9rGLugKS*y-J2Zq>^s-$7I@^O?6+M;on&f6;l0~t7L6z_K65E z76$jHPoMq@xH?scnEO#WW@l&r+J2*V-M+<2bqCIpdddeRe{N2vrV5QE(??m$ty*+XNc_fe7P8Efhk&!Vs z6RSD6sZ=UbDwU*GFptT~CFRsM;qt;erz4FZrE1mds`|84Lz4 zmb9##Dt_vmM62V{#3?}C!Te`HW44jxW;u^@5XUHkIEb=^R;TSWH#c)JAQtz86X!|( zVl;18`O0!qzmdT6jHm=?R_IAF^er3t=w$A`7(uu+$Tv1NVpZ0?aAIO1R3M^k&L&&i zzuid2uM5TJ0j8vM8^EzKB)xWnlLvUXG{|@K8_VH~queG6xDkrTZvr@}P7RgQ! z0HFfw$ZK$K#e}zcKe~94%>u^F2W{@ZWILbBJDn-A{!Q9tseDENdBKEf5khn%06arJ z-X?%{p!U~Ih(3J&L-2Bxl|)j@%x0$5`1=dHQ8$dpnR2W%W|yN^b1qm-VZ{k z8X-iD)qlD4N-v}FTJ)`j1egIZ$(zic-wt1Ob;&#^uqbiKDKwMZL3EO7gaQj{nfad{L zq+mk?A{bZf?2_j|1Ys2@><_wH5Q$mLVyx*}rN_t$djY;8-Yc7; zaAoWOSIk^*ONMMp^CcP42>|!Sd@U&{CB@|TY|r*KWc=?40QW^Q!>*9LpqLcnmXhM7 zd1N{AAE%8@(F#ydP>|Tu4v-5lz2Om;=F)Xi{b&{UgsS)Vc)XMAO#?@l0-Qd5`cK}N zb})TmT0~ORN#vxNooow-E&uTNfrd8>+)kZ3wX&T6!utXH_wS#Nn|hjw@q5hWcqjrx zwP!eZmQ>$=hi{1Dt&rX(4@}^;XV0E_9ljueH$>L1UHiJLt!Ym>L{S6sLdk2^v&!#_ zT++_iNY(4osHv>7*G698wQAL>Y2fN401h66hK2?N1_q92Nlb@0_C_%xcmm2?hyX!^ zO0Q8{MV6DrWcjLyT689#j=aE!anl3^1-%Te_QMv>4nvF>F~Xl&cxh>ArtkxpH#`A3 zL8$E=0@$lRUlu>W-kQsmD_7D30s_LowcVi#<`ysF9pTi})P-z5e=3xvLaEwQkN_Y& z{>CSs0SE&4V$@rg)^__gB_(A6ICl8DEMfw$kFf+XQ7V<@0bpD6rV#u&pgrMw%L4@B z9bqpoFFy@#;Mg|)9btesDDfg;TwL57Hd6OmKe=gbf>*RonB6t}u5}oA+J?{$FuB6Z z7HaoauU}|02vZWWn^TmVR8Gdurbk(?MZri`f6|jH@9a7kPBFhFi#12 z^}>YrtMMOU*()XkCWh@Ai}$|(t3Bq0Ip5>+g9C;7fGr@t$1IBd`zJGJ z&U`aFJ9|T3Ufyn&Ck_=A6`kx0L*Hxxw;Nc13D|%USb^Cd^IkbcIyfRCVz|B0aF!$| zpd(gRPGU!()W1S5}EsYfwPGX6{?1!NOfX$Ow_w+(s4My$I jmu4U7K?DAOd3OC5;e5tQAL4jD00000NkvXXu0mjfor`<2 literal 0 HcmV?d00001 diff --git a/doc/_static/pacifica-icon-set/distributor-logo-fedora.png b/doc/_static/pacifica-icon-set/distributor-logo-fedora.png new file mode 100644 index 0000000000000000000000000000000000000000..74bc18ff508e5c0b5ea1b5bd2347f34524ad3fe9 GIT binary patch literal 3175 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 N002ovPDHLkV1fbo0Wtsp literal 0 HcmV?d00001 diff --git a/doc/_static/pacifica-icon-set/distributor-logo-linux-mint.png b/doc/_static/pacifica-icon-set/distributor-logo-linux-mint.png new file mode 100644 index 0000000000000000000000000000000000000000..2f822dd55e28df7cd320318a405e03b5e101d357 GIT binary patch literal 3365 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 literal 0 HcmV?d00001 diff --git a/doc/_static/pacifica-icon-set/distributor-logo-opensuse.png b/doc/_static/pacifica-icon-set/distributor-logo-opensuse.png new file mode 100644 index 0000000000000000000000000000000000000000..cc34ddd1407f52c520538c7a9103f3dce1d0617b GIT binary patch literal 3193 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 literal 0 HcmV?d00001 diff --git a/doc/_static/pacifica-icon-set/distributor-logo-raspbian.png b/doc/_static/pacifica-icon-set/distributor-logo-raspbian.png new file mode 100644 index 0000000000000000000000000000000000000000..b55ce5bfe8445c4a49b023b9a00b4c239b92e58e GIT binary patch literal 1907 zcmV-(2aNcMP)*@Kw z@WvTIW@-l}IpJ~Nedqss=iGblyVr$?T*fp0NfQ$jWk#b>;Y@>1__nM61SnTuUteWV zP>_!Q`UeIEhP1Y}F6r&bne`_C-%8`@gf~Oc#uw=I%U@%G(~-|fXM==(P(@Q9XhlLp&u`55L=%2 zGMS=7C_34jW@M^qN`jIiB7>u{hkd?idR#k`7#@$g=ur-`kd>r}+ zfZNRReq^2&Lhm)1E$wf7Zlu>gHPVKbC@QKqQA+A8yXgiCm}~&goH^5L??I$)(OdpD z;?vW71X%o^ut+1M9M@}im?-`^4dsGDAE?uH9v)(2`r9VW{+*~~+ zyr3bO+?6D*E&^!MVlAy}eAEvSjEGYUlWW2NVSD!Mp~xgH6@6v0h-18YsH7Otf=S&Z z!gv$!7k_7_xHKI#H8nX@dj1z6Z2$iK6cJ;f@BF!Zm=CmS}0g zYrYg7tD~;2E{A+O1+cQRl9J_q^ocT#)_6=ImBfweR7upMNf&H~_e`N}oo6Jy?P;b^ zH!mtHE1Mj^wzf8UQ64COwECowQR+o0GG7avXv^fmLi;b8H0n~Pl1ZwfhK7cT2P`To zqFp}o=o|0Z6fE_$j61*}VjI1pDVyP>yp+Ob$#m4NUO=0?rcqvA-oyjOL`TzdMJUN7 zt`y_uLk$f2wf8Jqp$N0U7I;KZn|i(wxh3)-^5?PnD&J=vx_0f_!~qr;7Yiu`M3PJ^ zq)$xgZ+wk=uwSj(VPZt1-Mp!wpnz`NxZ!-jj*bpm;OJcj@j1dd5vN zs{J$NjFIt}D%@{wZgxIkettgHsuBf3l#(y3X02$xu5yy`)&i-uZvWnAu4N*9=oL#@ zSy|2p%+1Z^$uXCH<|ttNWw`s>KookIEMpLn>{KtbiEJpR9@i*M85tSQ2dt{964JU= z6)!;Wlk~iM0KLf*VxCM-u{>cmct#5Vh!%h{`6Z^isV(4*-gAT!Wv?ba72v?Y0GWfs zNG)-v1fC$`iUEs|Do3UJS%wquV+NOTY6JNhy-r6LE?k(HSzKCLO0&7H0pKSdXGk3Y zO2(KlSXprIeXfhvsb0N$^~9AKeBvi^^hCjKwh%7XFUdnj0e;{WL-CKzXuMo|d;7!z zqJ9~Jf~kX722r$_R}Y0mN?-E&E4H=xBnxTBcof?Zaf4b*SFT)nI>5fZzTfRV2ty4+ zF~)$0@LH?cJC)blnH(ioF3R}-BOZ6?)6BEq%=c<)YV7)lfyD+8-~WdXAKq=R!i$D9 znSw%v4*-0%cyI()DvZKJ*vwIThreJ@%=KIsu@;9L0v-Mq1Or$sdlKxNuu<;m=_$uL z=_q7>e?OI%my=Sd6xQL#$6!7KD-;S^zkWUO`HRDFU;qo4Z1%;~)zwQeK6v9y*RNl9 z$RX_b@#B=ebg59^DtSq(;ySsQuhY}g=;+a-4*P*7w7~!tn|(2M@q7ek;_cp_moHx? z7&Qbh_xey1Eh` zTm9G~PKH4Xn$Q-<-V;w)`DoUYJB4RvW-i>fZ{O}CM~?i>4u(&jJW0+p_=In0Knt4C zcJz!|hC<25vS$7p#|Pdi6%`e0>g($_IK$uzKH+;r!(g3OyMR`pnBB_I&`=Eyym3V9 t41=!`pEx`pbuR5N;26LXXV`zy{1+KN!v;QKt#1GT002ovPDHLkV1lB*garTq literal 0 HcmV?d00001 diff --git a/doc/_static/pacifica-icon-set/distributor-logo-ubuntu.png b/doc/_static/pacifica-icon-set/distributor-logo-ubuntu.png new file mode 100644 index 0000000000000000000000000000000000000000..fc15ab689fb0d2c40556d855d2f6f2dd54591138 GIT binary patch literal 3369 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 literal 0 HcmV?d00001 diff --git a/doc/apidoc.rst b/doc/apidoc.rst index 96a5c73..ecfec5e 100644 --- a/doc/apidoc.rst +++ b/doc/apidoc.rst @@ -1,5 +1,5 @@ -API documentation ------------------ +API Reference +------------- ``events`` ============ @@ -23,8 +23,8 @@ API documentation ``uinput`` ============ -.. autoclass:: evdev.uinput.UInput - :members: +.. automodule:: evdev.uinput + :members: UInput :special-members: :exclude-members: __dict__, __str__, __module__, __del__, __slots__, __repr__ :member-order: bysource diff --git a/doc/changelog.rst b/doc/changelog.rst index cc78e8a..7143dc1 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -1,6 +1,13 @@ Changelog ========= +HEAD +^^^^^^^^^^^^^^^^^^^^ + +- Rework documentation and docstrings once more. + +- Fix install on Python 3.4 (works around issue21121_). + 0.4.5 (Jul 06, 2014) ^^^^^^^^^^^^^^^^^^^^ @@ -162,3 +169,5 @@ Changelog .. _`@accek`: https://github.com/accek .. _`@kived`: https://github.com/kived .. _`@spasche`: https://github.com/spasche + +.. _issue21121: http://bugs.python.org/issue21121 diff --git a/doc/conf.py b/doc/conf.py index ee8ec57..acdb9ba 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -33,6 +33,8 @@ # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.intersphinx'] +autodoc_member_order = 'bysource' + # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -47,7 +49,7 @@ # General information about the project. project = u'python-evdev' -copyright = u'2012-2013, Georgi Valkov' +copyright = u'2012-2014, Georgi Valkov' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -99,30 +101,30 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -if not on_rtd: - import sphinx_rtd_theme - html_theme = 'sphinx_rtd_theme' - html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] -else: - html_theme = 'default' +# if not on_rtd: +# import sphinx_rtd_theme +# html_theme = 'sphinx_rtd_theme' +# html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] +# else: +html_theme = 'haiku' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {"full_logo" : True} # Add any paths that contain custom themes here, relative to this directory. # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +html_title = 'Python-evdev' # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +html_short_title = 'evdev' # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +html_logo = '_static/evdev-logo-small.png' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 diff --git a/doc/index.rst b/doc/index.rst index 1c53e02..28cd5ab 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -1,5 +1,37 @@ -*evdev* documentation ---------------------- +.. raw:: html + + + +Synopsis +-------- This package provides bindings to the generic input event interface in Linux. The *evdev* interface serves the purpose of passing events @@ -11,61 +43,173 @@ subsystem. *Uinput* allows userspace programs to create and handle input devices that can inject events directly into the input subsystem. -Please refer to the :doc:`tutorial ` and the :doc:`apidoc -` for usage information. +In other words, *python-evdev* allows you to read and write input +events on Linux. An event can be a key or button press, a mouse +movement or a tap on a touchscreen. -Contents -======== +Quick Start +----------- -.. toctree:: - :maxdepth: 1 +**Installing:** - tutorial - install - apidoc - changelog +The following GNU/Linux distributions have *python-evdev* in their repositories: +.. raw:: html -Similar Projects -================ +
+ + + +
-* `python-uinput`_ -* `ruby-evdev`_ -* `evdev`_ (ctypes) +The latest stable version of *python-evdev* can be installed from +pypi_, provided that you have gcc/clang, pip_ and the Python and Linux +development headers installed on your system. Installing them is +distribution specific and usually falls in one of these categories: +On a Debian compatible OS: -License -======= +.. code-block:: bash + + $ apt-get install python-dev python-pip gcc + $ apt-get install linux-headers-$(uname -r) + +On a Redhat compatible OS: + +.. code-block:: bash + + $ yum install python-devel python-pip gcc + $ yum install kernel-headers-$(uname -r) + +On Arch Linux and derivatives: + +.. code-block:: bash + + $ pacman -S core/linux-headers python-pip gcc + +Installing *python-evdev* with pip_: + +.. code-block:: bash + + $ sudo pip install evdev + +**Listing accessible event devices:** + +:: + + >>> from evdev import InputDevice, list_devices + + >>> devices = [InputDevice(fn) for fn in list_devices()] + >>> for dev in devices: + ... print(dev.fn, dev.name, dev.phys) + /dev/input/event1 Dell Dell USB Keyboard usb-0000:00:12.1-2/input0 + /dev/input/event0 Dell USB Optical Mouse usb-0000:00:12.0-2/input0 + + +**Reading events from a device:** + +:: + + >>> from evdev import InputDevice, categorize, ecodes + + >>> dev = InputDevice('/dev/input/event1') + >>> print(dev) + device /dev/input/event1, name "Dell Dell USB Keyboard", phys "usb-0000:00:12.1-2/input0" + + >>> for event in dev.read_loop(): + ... if event.type == ecodes.EV_KEY: + ... print(categorize(event)) + ... # pressing 'a' and holding 'space' + key event at 1337016188.396030, 30 (KEY_A), down + key event at 1337016188.492033, 30 (KEY_A), up + key event at 1337016189.772129, 57 (KEY_SPACE), down + key event at 1337016190.275396, 57 (KEY_SPACE), hold + key event at 1337016190.284160, 57 (KEY_SPACE), up + +**Accessing evdev constants:** -Package :mod:`evdev` is released under the terms of the `Revised BSD License`_. +:: + >>> from evdev import ecodes -Todo -==== + >>> ecodes.KEY_A, ecodes.ecodes['KEY_A'] + ... (30, 30) + >>> ecodes.KEY[30] + ... 'KEY_A' + >>> ecodes.bytype[ecodes.EV_KEY][30] + ... 'KEY_A' + >>> ecodes.KEY[152] # a single value may correspond to multiple codes + ... ['KEY_COFFEE', 'KEY_SCREENLOCK'] -* Use libudev to find the uinput device node as well as the other input - devices. Their locations are currently assumed to be ``/dev/uinput`` and - ``/dev/input/*``. +**Further information:** -* More tests. +- Read the full :doc:`tutorial `. -* Better uinput support (setting device capabilities as in `python-uinput`_) +- See the example_ programs. -* Expose more input subsystem functionality (``EVIOCSKEYCODE``, ``EVIOCGREP`` etc) +- Refer to the API :doc:`documentation `: -* Figure out if using ``linux/input.h`` and other kernel headers in your - userspace program binds it to the GPL2. + * :mod:`device ` + - :class:`AbsInfo ` -Indices and Tables -================== + - :class:`KbdInfo ` -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` + - :class:`InputDevice ` + * :mod:`events ` + + - :class:`InputEvent ` + + - :class:`KeyEvent ` + + - :class:`RelEvent ` + + - :class:`AbsEvent ` + + - :class:`SynEvent ` + + * :mod:`util ` + + - :class:`list_devices() ` + + - :class:`is_device() ` + + - :class:`categorize() ` + + - :class:`categorize() ` + + * :mod:`uinput ` + + - :class:`UInput ` + + * :mod:`ecodes ` + + +News +---- +.. include:: news.rst + +See :doc:`changelog ` for a full list of changes. + + +License +------- + +The :mod:`evdev` package is released under the terms of the `Revised BSD License`_. .. _`Revised BSD License`: https://raw.github.com/gvalkov/python-evdev/master/LICENSE .. _python-uinput: https://github.com/tuomasjjrasanen/python-uinput .. _ruby-evdev: http://technofetish.net/repos/buffaloplay/ruby_evdev/doc/ .. _evdev: http://svn.navi.cx/misc/trunk/python/evdev/ + +.. _pypi: http://pypi.python.org/pypi/evdev +.. _github: https://github.com/gvalkov/python-evdev +.. _pip: http://pip.readthedocs.org/en/latest/installing.html +.. _example: https://github.com/gvalkov/python-evdev/tree/master/bin diff --git a/doc/install.rst b/doc/install.rst deleted file mode 100644 index 1b386ab..0000000 --- a/doc/install.rst +++ /dev/null @@ -1,42 +0,0 @@ -Installation -============ - -Before installing :mod:`evdev`, make sure that the Python and Linux -kernel headers are installed on your system. - -On a Debian compatible OS: - -.. code-block:: bash - - $ apt-get install python-dev - $ apt-get install linux-headers-$(uname -r) - -On a Redhat compatible OS: - -.. code-block:: bash - - $ yum install python-devel - $ yum install kernel-headers-$(uname -r) - -The latest stable version can be installed from pypi_, while the -development version can be installed from github_: - -.. code-block:: bash - - $ pip install evdev # latest stable version - $ pip install git+git://github.com/gvalkov/python-evdev.git # latest development version - -:mod:`evdev` can also be installed like any other :mod:`setuptools` -package. - -.. code-block:: bash - - $ git clone github.com/gvalkov/python-evdev.git - $ cd python-evdev - $ git checkout $versiontag - $ python setup.py install - -The :mod:`evdev` package works with CPython **>= 2.7**. - -.. _pypi: http://pypi.python.org/pypi/evdev -.. _github: https://github.com/gvalkov/python-evdev diff --git a/doc/news.rst b/doc/news.rst new file mode 100644 index 0000000..6c1f2c1 --- /dev/null +++ b/doc/news.rst @@ -0,0 +1,33 @@ +* ``Jul 06, 2014:`` Version 0.4.5 released. + +* ``Jun 04, 2014:`` Version 0.4.4 released. + +* ``Dec 19, 2013:`` Version 0.4.3 released. + +* ``Dec 13, 2013:`` Version 0.4.2 released. + +* ``Oct 17, 2013:`` *Python-evdev* is now available in `Ubuntu Saucy`_. + +* ``Oct 11, 2013:`` *Python-evdev* is now available in `Arch Linux`_. + +* ``Jul 24, 2013:`` Version 0.4.1 released. + +* ``Jul 01, 2013:`` Version 0.4.0 released. + +* ``May 29, 2013:`` Version 0.3.3 released. + +* ``Apr 05, 2013:`` Version 0.3.2 released. + +* ``Nov 23, 2012:`` Version 0.3.1 released. + +* ``Nov 06, 2012:`` Version 0.3.0 released. + +* ``Aug 22, 2012:`` Version 0.2.0 released. + +* ``May 18, 2012:`` Version 0.1.1 released. + +* ``May 17, 2012:`` Version 0.1.0 released. + +.. _`Arch Linux`: https://aur.archlinux.org/packages/python-evdev/ + +.. _`Ubuntu Saucy`: http://packages.ubuntu.com/saucy/python-evdev diff --git a/doc/tutorial.rst b/doc/tutorial.rst index 5ec06ae..826a536 100644 --- a/doc/tutorial.rst +++ b/doc/tutorial.rst @@ -8,10 +8,9 @@ Listing accessible event devices >>> from evdev import InputDevice, list_devices - >>> devices = map(InputDevice, list_devices()) - + >>> devices = [InputDevice(fn) for fn in list_devices()] >>> for dev in devices: - ... print( '%-20s %-32s %s' % (dev.fn, dev.name, dev.phys) ) + ... print(dev.fn, dev.name, dev.phys) /dev/input/event1 Dell Dell USB Keyboard usb-0000:00:12.1-2/input0 /dev/input/event0 Dell USB Optical Mouse usb-0000:00:12.0-2/input0 @@ -21,8 +20,9 @@ Listing device capabilities :: - >>> dev = InputDevice('/dev/input/event0') + >>> from evdev import InputDevice + >>> dev = InputDevice('/dev/input/event0') >>> print(dev) device /dev/input/event0, name "Dell USB Optical Mouse", phys "usb-0000:00:12.0-2/input0" @@ -39,8 +39,9 @@ Listing device capabilities for devices with absolute axes :: - >>> dev = InputDevice('/dev/input/event7') + >>> from evdev import InputDevice + >>> dev = InputDevice('/dev/input/event7') >>> print(dev) device /dev/input/event7, name "Wacom Bamboo 2FG 4x5 Finger", phys "" @@ -86,23 +87,6 @@ Getting currently active keys ... [4, 42] -Accessing input subsystem constants -=================================== - -:: - - >>> from evdev import ecodes - >>> ecodes.KEY_A, ecodes.ecodes['KEY_A'] - ... (30, 30) - >>> ecodes.KEY[30] - ... 'KEY_A' - >>> ecodes.bytype[ecodes.EV_KEY][30] - ... 'KEY_A' - # a single value in the reverse mappings may correspond to multiple codes - >>> ecodes.KEY[152] - ... ['KEY_COFFEE', 'KEY_SCREENLOCK'] - - Reading events ============== @@ -151,6 +135,23 @@ Reading events from multiple devices event at 1351116708.782237, code 02, type 01, val 01 +Accessing evdev constants +========================= + +:: + + >>> from evdev import ecodes + + >>> ecodes.KEY_A, ecodes.ecodes['KEY_A'] + ... (30, 30) + >>> ecodes.KEY[30] + ... 'KEY_A' + >>> ecodes.bytype[ecodes.EV_KEY][30] + ... 'KEY_A' + >>> ecodes.KEY[152] # a single value may correspond to multiple codes + ... ['KEY_COFFEE', 'KEY_SCREENLOCK'] + + Reading events with asyncore ============================ @@ -202,9 +203,8 @@ Associating classes with event types See :mod:`events ` for more information. - -Injecting events -================ +Injecting input events +====================== :: @@ -232,7 +232,7 @@ Injecting events (2) Specifying ``uinput`` device options -====================================== +==================================== :: >>> from evdev import UInput, AbsInfo, ecodes as e diff --git a/evdev/__init__.py b/evdev/__init__.py index bb9a241..6359778 100644 --- a/evdev/__init__.py +++ b/evdev/__init__.py @@ -1,6 +1,8 @@ # encoding: utf-8 +#-------------------------------------------------------------------------- # Gather everything into a convenient namespace +#-------------------------------------------------------------------------- from evdev.device import DeviceInfo, InputDevice, AbsInfo from evdev.events import InputEvent, KeyEvent, RelEvent, SynEvent, AbsEvent, event_factory diff --git a/evdev/device.py b/evdev/device.py index ea6c837..54ef49a 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -8,6 +8,7 @@ from evdev.events import InputEvent +#-------------------------------------------------------------------------- _AbsInfo = namedtuple('AbsInfo', ['value', 'min', 'max', 'fuzz', 'flat', 'resolution']) _KbdInfo = namedtuple('KbdInfo', ['repeat', 'delay']) _DeviceInfo = namedtuple('DeviceInfo', ['bustype', 'vendor', 'product', 'version']) @@ -18,24 +19,24 @@ class AbsInfo(_AbsInfo): A ``namedtuple`` for storing absolut axis information - corresponds to the ``input_absinfo`` struct: - - value + **value** Latest reported value for the axis. - - min + **min** Specifies minimum value for the axis. - - max + **max** Specifies maximum value for the axis. - - fuzz + **fuzz** Specifies fuzz value that is used to filter noise from the event stream. - - flat + **flat** Values that are within this value will be discarded by joydev interface and reported as 0 instead. - - resolution + **resolution** Specifies resolution for the values reported for the axis. Resolution for main axes (``ABS_X, ABS_Y, ABS_Z``) is reported in units per millimeter (units/mm), resolution for rotational @@ -54,10 +55,10 @@ class KbdInfo(_KbdInfo): ''' Keyboard repeat rate: - - repeat: + **repeat** Keyboard repeat rate in characters per second. - - delay: + **delay** Amount of time that a key must be depressed before it will start to repeat (in milliseconds). ''' @@ -178,12 +179,12 @@ def capabilities(self, verbose=False, absinfo=True): def leds(self, verbose=False): ''' - Return currently set LED keys. Example:: + Return currently set LED keys. For example:: [0, 1, 8, 9] - If ``verbose`` is ``True``, event codes are resolved to - their names. Unknown codes are resolved to ``'?'``. Example:: + If ``verbose`` is ``True``, event codes are resolved to their + names. Unknown codes are resolved to ``'?'``. For example:: [('LED_NUML', 0), ('LED_CAPSL', 1), ('LED_MISC', 8), ('LED_MAIL', 9)] @@ -196,7 +197,7 @@ def leds(self, verbose=False): def set_led(self, led_num, value): ''' - Set the state of the selected LED. Example:: + Set the state of the selected LED. For example:: device.set_led(ecodes.LED_NUML, 1) @@ -205,7 +206,7 @@ def set_led(self, led_num, value): _uinput.write(self.fd, ecodes.EV_LED, led_num, value) def __eq__(self, other): - '''Two devices are considered equal if their :data:`info` attributes are equal.''' + '''Two devices are equal if their :data:`info` attributes are equal.''' return self.info == other.info def __str__(self): diff --git a/evdev/events.py b/evdev/events.py index 8383d01..6c75588 100644 --- a/evdev/events.py +++ b/evdev/events.py @@ -13,8 +13,8 @@ __s32 value; }; -This module also defines :class:`InputEvent` sub-classes that know -more about the different types of events (key, abs, rel etc). The +This module also defines several :class:`InputEvent` sub-classes that +know more about the different types of events (key, abs, rel etc). The :data:`event_factory` dictionary maps event types to these classes. Assuming you use the :func:`evdev.util.categorize()` function to @@ -171,7 +171,7 @@ def __repr__(s): #: A mapping of event types to :class:`InputEvent` sub-classes. Used -#: by:func:`evdev.util.categorize()` +#: by :func:`evdev.util.categorize()` event_factory = { EV_KEY: KeyEvent, EV_REL: RelEvent, diff --git a/evdev/uinput.py b/evdev/uinput.py index 30c3a06..5a6fdbc 100644 --- a/evdev/uinput.py +++ b/evdev/uinput.py @@ -36,7 +36,7 @@ def __init__(self, :type events: dictionary of event types mapping to lists of event codes. - :param name: the name of the input device. + :param name: the name of the input device. :param vendor: vendor identifier. :param product: product identifier. :param version: version identifier. diff --git a/evdev/util.py b/evdev/util.py index ccc6b58..a407538 100644 --- a/evdev/util.py +++ b/evdev/util.py @@ -9,7 +9,7 @@ def list_devices(input_device_dir='/dev/input'): - '''List readable, character devices.''' + '''List readable character devices in ``input_device_dir``.''' fns = glob.glob('{}/event*'.format(input_device_dir)) fns = list(filter(is_device, fns)) @@ -39,9 +39,8 @@ def categorize(event): The :data:`event_factory ` dictionary maps event types to sub-classes of :class:`InputEvent - `. If there is no corresponding key, the - event is returned as it is. - ''' + `. If the event cannot be categorized, it + is returned unmodified.''' if event.type in event_factory: return event_factory[event.type](event) @@ -63,7 +62,7 @@ def resolve_ecodes(typecodemap, unknown='?'): ('BTN_RIGHT', 273), ('BTN_MIDDLE', 274)] } - If typecodemap contains absolute axis info (instances of + If ``typecodemap`` contains absolute axis info (instances of :class:`AbsInfo ` ) the result would look like:: diff --git a/setup.py b/setup.py index 3c412e8..9e05fc6 100755 --- a/setup.py +++ b/setup.py @@ -14,18 +14,19 @@ here = abspath(dirname(__file__)) -classifiers = ( +classifiers = [ 'Development Status :: 5 - Production/Stable', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', 'Operating System :: POSIX :: Linux', 'Intended Audience :: Developers', 'Topic :: Software Development :: Libraries', 'License :: OSI Approved :: BSD License', 'Programming Language :: Python :: Implementation :: CPython', -) +] cflags = ['-std=c99', '-Wno-error=declaration-after-statement'] input_c = Extension('evdev._input', sources=['evdev/input.c'], extra_compile_args=cflags) @@ -36,7 +37,7 @@ 'name': 'evdev', 'version': '0.4.5', - 'description': 'Bindings for the linux input handling subsystem', + 'description': 'Bindings to the Linux input handling subsystem', 'long_description': open(pjoin(here, 'README.rst')).read(), 'author': 'Georgi Valkov', From 5e060230658016a3b2aa2ccd9b8125f70f293863 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Tue, 7 Oct 2014 22:06:18 +0200 Subject: [PATCH 012/270] Pick up evdev headers from correct location on FreeBSD. This commit is part of Jakub Klama's work on bringing evdev support into the FreeBSD kernel. Author: Jakub Wojciech Klama --- evdev/genecodes.py | 4 ++++ evdev/input.c | 4 ++++ evdev/uinput.c | 6 +++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/evdev/genecodes.py b/evdev/genecodes.py index 8572920..5f89c98 100755 --- a/evdev/genecodes.py +++ b/evdev/genecodes.py @@ -11,7 +11,11 @@ template = r''' #include +#ifdef __FreeBSD__ +#include +#else #include +#endif /* Automatically generated by evdev.genecodes */ /* Generated on %s */ diff --git a/evdev/input.c b/evdev/input.c index 621af84..c3e6b46 100644 --- a/evdev/input.c +++ b/evdev/input.c @@ -18,7 +18,11 @@ #include #include +#ifdef __FreeBSD__ +#include +#else #include +#endif #define MAX_NAME_SIZE 256 diff --git a/evdev/uinput.c b/evdev/uinput.c index c885ed7..f3d1d93 100644 --- a/evdev/uinput.c +++ b/evdev/uinput.c @@ -8,9 +8,13 @@ #include #include +#ifdef __FreeBSD__ +#include +#include +#else #include #include - +#endif int _uinput_close(int fd) { From 6b88515ff352f995010ca18e8796bd9b69af3ce2 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Tue, 7 Oct 2014 22:12:09 +0200 Subject: [PATCH 013/270] Fix ioctl() requested buffer size - it should be specified in bytes not bits. This commit is part of Jakub Klama's work on bringing evdev support into the FreeBSD kernel. Author: Jakub Wojciech Klama --- evdev/input.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evdev/input.c b/evdev/input.c index c3e6b46..8318cb7 100644 --- a/evdev/input.c +++ b/evdev/input.c @@ -164,7 +164,7 @@ ioctl_capabilities(PyObject *self, PyObject *args) memset(&ev_bits, 0, sizeof(ev_bits)); - if (ioctl(_fd, EVIOCGBIT(0, EV_MAX), ev_bits) < 0) + if (ioctl(_fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) < 0) goto on_err; // Build a dictionary of the device's capabilities @@ -175,7 +175,7 @@ ioctl_capabilities(PyObject *self, PyObject *args) eventcodes = PyList_New(0); memset(&code_bits, 0, sizeof(code_bits)); - ioctl(_fd, EVIOCGBIT(ev_type, KEY_MAX), code_bits); + ioctl(_fd, EVIOCGBIT(ev_type, sizeof(code_bits)), code_bits); for (ev_code = 0; ev_code < KEY_MAX; ev_code++) { if (test_bit(code_bits, ev_code)) { From 3ec7111d6573fd8498666cbe1c8c66909d8cd6f3 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Tue, 7 Oct 2014 22:14:34 +0200 Subject: [PATCH 014/270] Remove usage of linux-ish data types. This commit is part of Jakub Klama's work on bringing evdev support into the FreeBSD kernel. Author: Jakub Wojciech Klama --- evdev/uinput.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evdev/uinput.c b/evdev/uinput.c index f3d1d93..9420fba 100644 --- a/evdev/uinput.c +++ b/evdev/uinput.c @@ -50,7 +50,7 @@ uinput_open(PyObject *self, PyObject *args) static PyObject * uinput_create(PyObject *self, PyObject *args) { int fd, len, i, abscode; - __u16 vendor, product, version, bustype; + uint16_t vendor, product, version, bustype; PyObject *absinfo = NULL, *item = NULL; @@ -150,7 +150,7 @@ static PyObject * uinput_enable_event(PyObject *self, PyObject *args) { int fd; - __u16 type, code; + uint16_t type, code; unsigned long req; int ret = PyArg_ParseTuple(args, "ihh", &fd, &type, &code); From 2120a4884086b30a18aa12460ce618fbd7219943 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Tue, 7 Oct 2014 22:23:56 +0200 Subject: [PATCH 015/270] bump version to 0.4.6 --- doc/changelog.rst | 4 +++- doc/news.rst | 2 ++ setup.py | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index 7143dc1..4fde143 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -1,13 +1,15 @@ Changelog ========= -HEAD +0.4.6 (Oct 07, 2014) ^^^^^^^^^^^^^^^^^^^^ - Rework documentation and docstrings once more. - Fix install on Python 3.4 (works around issue21121_). +- Fix ioctl() requested buffer size (thanks Jakub Wojciech Klama). + 0.4.5 (Jul 06, 2014) ^^^^^^^^^^^^^^^^^^^^ diff --git a/doc/news.rst b/doc/news.rst index 6c1f2c1..396a215 100644 --- a/doc/news.rst +++ b/doc/news.rst @@ -1,3 +1,5 @@ +* ``Oct 07, 2014:`` Version 0.4.6 released. + * ``Jul 06, 2014:`` Version 0.4.5 released. * ``Jun 04, 2014:`` Version 0.4.4 released. diff --git a/setup.py b/setup.py index 9e05fc6..123173a 100755 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ kw = { 'name': 'evdev', - 'version': '0.4.5', + 'version': '0.4.6', 'description': 'Bindings to the Linux input handling subsystem', 'long_description': open(pjoin(here, 'README.rst')).read(), From e1914d6f94a8dc881f1b01872ebeed355cd3ea84 Mon Sep 17 00:00:00 2001 From: Mike Kazantsev Date: Mon, 15 Dec 2014 17:32:45 +0500 Subject: [PATCH 016/270] Fix minor typo in missing linux-headers message --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 123173a..315c2d7 100755 --- a/setup.py +++ b/setup.py @@ -68,7 +68,7 @@ def create_ecodes(): install the headers for your kernel in order to continue: yum install kernel-headers-$(uname -r) - apt-get intall linux-headers-$(uname -r) + apt-get install linux-headers-$(uname -r) pacman -S kernel-headers\n\n''' sys.stderr.write(textwrap.dedent(msg)) From 45d5bda0d6a54e7549876e2227f1366c76c0456e Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sat, 10 Jan 2015 20:30:32 +0100 Subject: [PATCH 017/270] minor fixes + remove setup.py test command --- LICENSE | 2 +- requirements-dev.txt | 1 + setup.py | 27 ++++++--------------------- 3 files changed, 8 insertions(+), 22 deletions(-) create mode 100644 requirements-dev.txt diff --git a/LICENSE b/LICENSE index f2cb880..52683e8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2012-2014 Georgi Valkov. All rights reserved. +Copyright (c) 2012-2015 Georgi Valkov. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..e447160 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1 @@ +pytest>=2.6.4 diff --git a/setup.py b/setup.py index 315c2d7..2bc53bc 100755 --- a/setup.py +++ b/setup.py @@ -9,16 +9,17 @@ from distutils.command.build import build from setuptools.command.develop import develop from setuptools.command.bdist_egg import bdist_egg -from setuptools import setup, Extension, Command +from setuptools import setup, Extension +#----------------------------------------------------------------------------- here = abspath(dirname(__file__)) +#----------------------------------------------------------------------------- classifiers = [ 'Development Status :: 5 - Production/Stable', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Operating System :: POSIX :: Linux', @@ -28,11 +29,13 @@ 'Programming Language :: Python :: Implementation :: CPython', ] +#----------------------------------------------------------------------------- cflags = ['-std=c99', '-Wno-error=declaration-after-statement'] input_c = Extension('evdev._input', sources=['evdev/input.c'], extra_compile_args=cflags) uinput_c = Extension('evdev._uinput', sources=['evdev/uinput.c'], extra_compile_args=cflags) ecodes_c = Extension('evdev._ecodes', sources=['evdev/ecodes.c'], extra_compile_args=cflags) +#----------------------------------------------------------------------------- kw = { 'name': 'evdev', 'version': '0.4.6', @@ -50,7 +53,6 @@ 'packages': ['evdev'], 'ext_modules': [input_c, uinput_c, ecodes_c], - 'tests_require': ['pytest'], 'include_package_data': False, 'zip_safe': True, @@ -98,24 +100,7 @@ def run(self): create_ecodes() bdist_egg.run(self) - -class PyTest(Command): - '''setup.py test -> py.test tests''' - - user_options = [] - def initialize_options(self): - pass - - def finalize_options(self): - pass - - def run(self): - from subprocess import call - errno = call(('py.test', 'tests')) - raise SystemExit(errno) - - -kw['cmdclass']['test'] = PyTest +#----------------------------------------------------------------------------- kw['cmdclass']['build'] = BuildCommand kw['cmdclass']['develop'] = DevelopCommand kw['cmdclass']['bdist_egg'] = BdistEggCommand From 0dd4236783fc266528b480fa486ae7429a3969dc Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sat, 10 Jan 2015 20:31:11 +0100 Subject: [PATCH 018/270] use a fixture instead of a funcarg --- tests/test_ecodes.py | 1 + tests/test_uinput.py | 24 +++++++++++++----------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/tests/test_ecodes.py b/tests/test_ecodes.py index 622a06f..b2f10c4 100644 --- a/tests/test_ecodes.py +++ b/tests/test_ecodes.py @@ -2,6 +2,7 @@ from evdev import ecodes + prefixes = 'KEY ABS REL SW MSC LED BTN REP SND ID EV BUS SYN FF_STATUS FF' def to_tuples(l): diff --git a/tests/test_uinput.py b/tests/test_uinput.py index 69945ea..21d7b4e 100644 --- a/tests/test_uinput.py +++ b/tests/test_uinput.py @@ -1,11 +1,12 @@ # encoding: utf-8 from select import select -from pytest import raises +from pytest import raises, fixture from evdev import uinput, ecodes, events, device, util +#----------------------------------------------------------------------------- uinput_options = { 'name' : 'test-py-evdev-uinput', 'bustype' : ecodes.BUS_USB, @@ -14,21 +15,21 @@ 'version' : 0x3300, } - -def pytest_funcarg__c(request): +@fixture +def c(): return uinput_options.copy() - def device_exists(bustype, vendor, product, version): - match = 'I: Bus=%04hx Vendor=%04hx Product=%04hx Version=%04hx' % \ - (bustype, vendor, product, version) + match = 'I: Bus=%04hx Vendor=%04hx Product=%04hx Version=%04hx' + match = match % (bustype, vendor, product, version) for line in open('/proc/bus/input/devices'): - if line.strip() == match: return True + if line.strip() == match: + return True return False - +#----------------------------------------------------------------------------- def test_open(c): ui = uinput.UInput(**c) args = (c['bustype'], c['vendor'], c['product'], c['version']) @@ -59,15 +60,16 @@ def test_enable_events(c): def test_abs_values(c): e = ecodes c['events'] = { - e.EV_KEY : [e.KEY_A, e.KEY_B], - e.EV_ABS : [(e.ABS_X, (0, 255, 0, 0)), - (e.ABS_Y, device.AbsInfo(0, 255, 5, 10, 0, 0))], + e.EV_KEY: [e.KEY_A, e.KEY_B], + e.EV_ABS: [(e.ABS_X, (0, 255, 0, 0)), + (e.ABS_Y, device.AbsInfo(0, 255, 5, 10, 0, 0))], } with uinput.UInput(**c) as ui: c = ui.capabilities() abs = device.AbsInfo(value=0, min=0, max=255, fuzz=0, flat=0, resolution=0) assert c[e.EV_ABS][0] == (0, abs) + abs = device.AbsInfo(value=0, min=0, max=255, fuzz=5, flat=10, resolution=0) assert c[e.EV_ABS][1] == (1, abs) From 3844a3417f1e4f0a63c870347b883f42cf1fac2d Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 11 Jan 2015 00:27:15 +0100 Subject: [PATCH 019/270] Fallback to distutils if setuptools is not available. --- doc/changelog.rst | 5 +++++ setup.py | 48 ++++++++++++++++++++++------------------------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index 4fde143..fe5d843 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -1,6 +1,11 @@ Changelog ========= +Development +^^^^^^^^^^^^^^^^^^^^ + +- Fallback to distutils if setuptools is not available. + 0.4.6 (Oct 07, 2014) ^^^^^^^^^^^^^^^^^^^^ diff --git a/setup.py b/setup.py index 2bc53bc..99e0a15 100755 --- a/setup.py +++ b/setup.py @@ -6,11 +6,16 @@ import textwrap from os.path import abspath, dirname, join as pjoin -from distutils.command.build import build -from setuptools.command.develop import develop -from setuptools.command.bdist_egg import bdist_egg -from setuptools import setup, Extension +from distutils.command import build +#----------------------------------------------------------------------------- +try: + from setuptools import setup, Extension + from setuptools.command import bdist_egg, develop +except ImportError: + from distutils.core import setup, Extension + from distutils.command import build + develop, bdist_egg = None, None #----------------------------------------------------------------------------- here = abspath(dirname(__file__)) @@ -46,22 +51,20 @@ 'author': 'Georgi Valkov', 'author_email': 'georgi.t.valkov@gmail.com', 'license': 'Revised BSD License', - 'keywords': 'evdev input uinput', - 'classifiers': classifiers, 'url': 'https://github.com/gvalkov/python-evdev', + 'classifiers': classifiers, 'packages': ['evdev'], 'ext_modules': [input_c, uinput_c, ecodes_c], - 'include_package_data': False, 'zip_safe': True, 'cmdclass': {}, } +#----------------------------------------------------------------------------- def create_ecodes(): - # :todo: expose as a command option header = '/usr/include/linux/input.h' if not os.path.isfile(header): @@ -83,27 +86,20 @@ def create_ecodes(): check_call(cmd, cwd="%s/evdev" % here, shell=True) -# :todo: figure out a smarter way to do this -# :note: subclassing build_ext doesn't really cut it -class BuildCommand(build): - def run(self): - create_ecodes() - build.run(self) +def cmdfactory(cmd): + class cls(cmd): + def run(self): + create_ecodes() + cmd.run(self) + return cls -class DevelopCommand(develop): - def run(self): - create_ecodes() - develop.run(self) +#----------------------------------------------------------------------------- +kw['cmdclass']['build'] = cmdfactory(build.build) -class BdistEggCommand(bdist_egg): - def run(self): - create_ecodes() - bdist_egg.run(self) +if develop and bdist_egg: + kw['cmdclass']['develop'] = cmdfactory(develop.develop) + kw['cmdclass']['bdist_egg'] = cmdfactory(bdist_egg.bdist_egg) #----------------------------------------------------------------------------- -kw['cmdclass']['build'] = BuildCommand -kw['cmdclass']['develop'] = DevelopCommand -kw['cmdclass']['bdist_egg'] = BdistEggCommand - if __name__ == '__main__': setup(**kw) From 8d1cf6842c43f08ae3357019177884271c8d09ba Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 11 Jan 2015 00:34:04 +0100 Subject: [PATCH 020/270] bump version to 0.4.7 --- doc/changelog.rst | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index fe5d843..accfb16 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -1,7 +1,7 @@ Changelog ========= -Development +0.4.7 (Oct 07, 2015) ^^^^^^^^^^^^^^^^^^^^ - Fallback to distutils if setuptools is not available. diff --git a/setup.py b/setup.py index 99e0a15..76edd65 100755 --- a/setup.py +++ b/setup.py @@ -43,7 +43,7 @@ #----------------------------------------------------------------------------- kw = { 'name': 'evdev', - 'version': '0.4.6', + 'version': '0.4.7', 'description': 'Bindings to the Linux input handling subsystem', 'long_description': open(pjoin(here, 'README.rst')).read(), From 8261808f084eb008f5fd8e0117a00e9d62a76f94 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Mon, 11 May 2015 00:56:19 +0200 Subject: [PATCH 021/270] Mention that device.read() raises BlockingIOError --- evdev/device.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/evdev/device.py b/evdev/device.py index 54ef49a..c2d48e1 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -257,9 +257,9 @@ def read_loop(self): def read(self): ''' - Read multiple input events from device. Return a generator - object that yields :class:`InputEvent - ` instances. + Read multiple input events from device. Return a generator object that + yields :class:`InputEvent ` instances. Raises + `BlockingIOError` if there are no available events at the moment. ''' # events -> [(sec, usec, type, code, val), ...] From d4bf97d12acd0c5b9a32a4814635e278ec366026 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sat, 16 May 2015 17:15:21 +0200 Subject: [PATCH 022/270] Add example using the selectors module --- doc/tutorial.rst | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/doc/tutorial.rst b/doc/tutorial.rst index 826a536..b57bbb6 100644 --- a/doc/tutorial.rst +++ b/doc/tutorial.rst @@ -117,15 +117,16 @@ Reading events from multiple devices >>> from evdev import InputDevice >>> from select import select + # A mapping of file descriptors (integers) to InputDevice instances. >>> devices = map(InputDevice, ('/dev/input/event1', '/dev/input/event2')) - >>> devices = {dev.fd : dev for dev in devices} + >>> devices = {dev.fd: dev for dev in devices} >>> for dev in devices.values(): print(dev) device /dev/input/event1, name "Dell Dell USB Keyboard", phys "usb-0000:00:12.1-2/input0" device /dev/input/event2, name "Logitech USB Laser Mouse", phys "usb-0000:00:12.0-2/input0" >>> while True: - ... r,w,x = select(devices, [], []) + ... r, w, x = select(devices, [], []) ... for fd in r: ... for event in devices[fd].read(): ... print(event) @@ -135,6 +136,29 @@ Reading events from multiple devices event at 1351116708.782237, code 02, type 01, val 01 +This can also be achieved using the selectors_ module in Python 3.4: + +:: + + from evdev import InputDevice + from selectors import DefaultSelector, EVENT_READ + + selector = selectors.DefaultSelector() + + mouse = evdev.InputDevice('/dev/input/event1') + keybd = evdev.InputDevice('/dev/input/event2') + + # This works because InputDevice has a `fileno()` method. + selector.register(mouse, selectors.EVENT_READ) + selector.register(keybd, selectors.EVENT_READ) + + while True: + for key, mask in selector.select(): + device = key.fileobj + for event in device.read(): + print(event) + + Accessing evdev constants ========================= @@ -262,3 +286,6 @@ Specifying ``uinput`` device options >>> ui.write(e.EV_ABS, e.ABS_X, 20) >>> ui.write(e.EV_ABS, e.ABS_Y, 20) >>> ui.syn() + + +.. _selectors: https://docs.python.org/3/library/selectors.html From ba068fde3a51ff7551cdafb6dccd26cbddbeba4e Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sat, 16 May 2015 20:17:26 +0200 Subject: [PATCH 023/270] Add asyncio example --- doc/index.rst | 2 +- doc/tutorial.rst | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/doc/index.rst b/doc/index.rst index 28cd5ab..f00b2f0 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -91,7 +91,7 @@ On Arch Linux and derivatives: .. code-block:: bash - $ pacman -S core/linux-headers python-pip gcc + $ pacman -S core/linux-api-headers python-pip gcc Installing *python-evdev* with pip_: diff --git a/doc/tutorial.rst b/doc/tutorial.rst index b57bbb6..e1ecd08 100644 --- a/doc/tutorial.rst +++ b/doc/tutorial.rst @@ -203,6 +203,42 @@ Reading events with asyncore InputEvent(1337255905L, 358857L, 0, 0, 0L) +Reading events with asyncio +=========================== + +An example program that prints the number of events by type every 5 +seconds: + +:: + + import asyncio + + from collections import Counter + from functools import partial + from evdev import InputDevice, ecodes + + mouse = InputDevice('/dev/input/event1') + keybd = InputDevice('/dev/input/event2') + counter = Counter() + + def read_events(device): + events = device.read() + counter.update([ev.type for ev in events]) + + @asyncio.coroutine + def summarize(): + while True: + yield from asyncio.sleep(5) + print('Number of events by type in the last 5 seconds:') + print({ecodes.EV[k]: v for k,v in counter.items()}) + counter.clear() + + loop = asyncio.get_event_loop() + loop.add_reader(mouse, partial(read_events, mouse)) + loop.add_reader(keybd, partial(read_events, keybd)) + loop.run_until_complete(summarize()) + + Getting exclusive access to a device ==================================== From 4051ba9c7c731566b33251aed149f103aa8f7256 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Tue, 16 Jun 2015 19:50:23 +0200 Subject: [PATCH 024/270] Try to open device O_RDONLY open if O_RDWR fails Methods that require write access are wrapped with the 'needs_write' decorator. --- doc/changelog.rst | 12 ++++++++++-- evdev/device.py | 28 +++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index accfb16..2824f66 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -1,7 +1,15 @@ Changelog ========= -0.4.7 (Oct 07, 2015) +0.5.0 (Jun 16, 2015) +^^^^^^^^^^^^^^^^^^^^ + +- Write access to the input device is no longer mandatory. Evdev will + first try to open the device for reading and writing and fallback to + read-only. Methods that require write access (e.g. ``set_led()``) + will raise ``EvdevError`` if the device is open only for reading. + +0.4.7 (Oct 07, 2014) ^^^^^^^^^^^^^^^^^^^^ - Fallback to distutils if setuptools is not available. @@ -13,7 +21,7 @@ Changelog - Fix install on Python 3.4 (works around issue21121_). -- Fix ioctl() requested buffer size (thanks Jakub Wojciech Klama). +- Fix ``ioctl()`` requested buffer size (thanks Jakub Wojciech Klama). 0.4.5 (Jul 06, 2014) ^^^^^^^^^^^^^^^^^^^^ diff --git a/evdev/device.py b/evdev/device.py index c2d48e1..b223ce9 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -1,6 +1,7 @@ # encoding: utf-8 import os +import fcntl from select import select from collections import namedtuple @@ -8,6 +9,10 @@ from evdev.events import InputEvent +#-------------------------------------------------------------------------- +class EvdevError(Exception): + pass + #-------------------------------------------------------------------------- _AbsInfo = namedtuple('AbsInfo', ['value', 'min', 'max', 'fuzz', 'flat', 'resolution']) _KbdInfo = namedtuple('KbdInfo', ['repeat', 'delay']) @@ -89,8 +94,15 @@ def __init__(self, dev): #: Path to input device. self.fn = dev + # Certain operations are possible only when the device is opened in + # read-write mode. + try: + fd = os.open(dev, os.O_RDWR | os.O_NONBLOCK) + except OSError: + fd = os.open(dev, os.O_RDONLY | os.O_NONBLOCK) + #: A non-blocking file descriptor to the device file. - self.fd = os.open(dev, os.O_RDWR | os.O_NONBLOCK) + self.fd = fd # Returns (bustype, vendor, product, version, name, phys, capabilities). info_res = _input.ioctl_devinfo(self.fd) @@ -177,6 +189,19 @@ def capabilities(self, verbose=False, absinfo=True): else: return self._capabilities(absinfo) + def need_write(func): + ''' + Decorator that raises EvdevError() if there is no write access to the + input device. + ''' + def wrapper(*args): + fd = args[0].fd + if fcntl.fcntl(fd, fcntl.F_GETFL) & os.O_RDWR: + return func(*args) + msg = 'no write access to device "%s"' % args[0].fn + raise EvdevError(msg) + return wrapper + def leds(self, verbose=False): ''' Return currently set LED keys. For example:: @@ -195,6 +220,7 @@ def leds(self, verbose=False): return leds + @need_write def set_led(self, led_num, value): ''' Set the state of the selected LED. For example:: From 15007c4ac7a125af6c51fa2d7cfe01491b4d8110 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Tue, 16 Jun 2015 19:59:43 +0200 Subject: [PATCH 025/270] bump version to 0.5.0 --- doc/conf.py | 2 +- doc/news.rst | 2 ++ setup.py | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index acdb9ba..49425a0 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -49,7 +49,7 @@ # General information about the project. project = u'python-evdev' -copyright = u'2012-2014, Georgi Valkov' +copyright = u'2012-2015, Georgi Valkov' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/doc/news.rst b/doc/news.rst index 396a215..1428461 100644 --- a/doc/news.rst +++ b/doc/news.rst @@ -1,3 +1,5 @@ +* ``Jun 16, 2015:`` Version 0.5.0 released. + * ``Oct 07, 2014:`` Version 0.4.6 released. * ``Jul 06, 2014:`` Version 0.4.5 released. diff --git a/setup.py b/setup.py index 76edd65..b309fe9 100755 --- a/setup.py +++ b/setup.py @@ -43,7 +43,7 @@ #----------------------------------------------------------------------------- kw = { 'name': 'evdev', - 'version': '0.4.7', + 'version': '0.5.0', 'description': 'Bindings to the Linux input handling subsystem', 'long_description': open(pjoin(here, 'README.rst')).read(), From 2a1d51bc9c9a235e74f1ad1fd1144e7eb5b14729 Mon Sep 17 00:00:00 2001 From: Isataev Volodymir Date: Fri, 13 Nov 2015 23:04:20 +0200 Subject: [PATCH 026/270] Fixed comparing with other objects. Use case in select.select --- evdev/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evdev/device.py b/evdev/device.py index b223ce9..93694e4 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -233,7 +233,7 @@ def set_led(self, led_num, value): def __eq__(self, other): '''Two devices are equal if their :data:`info` attributes are equal.''' - return self.info == other.info + return isinstance(other, InputDevice) and self.info == other.info def __str__(self): msg = 'device {}, name "{}", phys "{}"' From 2fc19efae4b90fda5be44a046eaa4903eadca24c Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sat, 14 Nov 2015 12:47:00 +0100 Subject: [PATCH 027/270] Make __eq__ method more generic --- evdev/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evdev/device.py b/evdev/device.py index 93694e4..4735446 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -233,7 +233,7 @@ def set_led(self, led_num, value): def __eq__(self, other): '''Two devices are equal if their :data:`info` attributes are equal.''' - return isinstance(other, InputDevice) and self.info == other.info + return isinstance(other, self.__class__) and self.info == other.info def __str__(self): msg = 'device {}, name "{}", phys "{}"' From 62969d7bca43d87799237590a460d4361012225c Mon Sep 17 00:00:00 2001 From: John Titor Date: Mon, 25 Jan 2016 02:32:42 +0100 Subject: [PATCH 028/270] Fix absinfo item indexes in uinput_create (see #40) --- evdev/uinput.c | 11 ++++++----- evdev/uinput.py | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/evdev/uinput.c b/evdev/uinput.c index 9420fba..df6f5d2 100644 --- a/evdev/uinput.c +++ b/evdev/uinput.c @@ -70,14 +70,15 @@ uinput_create(PyObject *self, PyObject *args) { len = PyList_Size(absinfo); for (i=0; i (ABS_X, 0, 255, 0, 0) + // item -> (ABS_X, 0, 255, 0, 0, 0, 0) item = PyList_GetItem(absinfo, i); abscode = (int)PyLong_AsLong(PyList_GetItem(item, 0)); - uidev.absmin[abscode] = PyLong_AsLong(PyList_GetItem(item, 1)); - uidev.absmax[abscode] = PyLong_AsLong(PyList_GetItem(item, 2)); - uidev.absfuzz[abscode] = PyLong_AsLong(PyList_GetItem(item, 3)); - uidev.absflat[abscode] = PyLong_AsLong(PyList_GetItem(item, 4)); + /* min/max/fuzz/flat start from index 2 because index 1 is value */ + uidev.absmin[abscode] = PyLong_AsLong(PyList_GetItem(item, 2)); + uidev.absmax[abscode] = PyLong_AsLong(PyList_GetItem(item, 3)); + uidev.absfuzz[abscode] = PyLong_AsLong(PyList_GetItem(item, 4)); + uidev.absflat[abscode] = PyLong_AsLong(PyList_GetItem(item, 5)); } if (write(fd, &uidev, sizeof(uidev)) != sizeof(uidev)) diff --git a/evdev/uinput.py b/evdev/uinput.py index 5a6fdbc..04c6d91 100644 --- a/evdev/uinput.py +++ b/evdev/uinput.py @@ -71,7 +71,7 @@ def __init__(self, for code in codes: # handle max, min, fuzz, flat if isinstance(code, (tuple, list, device.AbsInfo)): - # flatten (ABS_Y, (0, 255, 0, 0)) to (ABS_Y, 0, 255, 0, 0) + # flatten (ABS_Y, (0, 255, 0, 0, 0, 0)) to (ABS_Y, 0, 255, 0, 0, 0, 0) f = [code[0]]; f += code[1] absinfo.append(f) code = code[0] From 631e2d32d7bdf38e3d7a5c850c9f5869d61e9183 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Mon, 1 Feb 2016 00:59:49 +0100 Subject: [PATCH 029/270] Improve ecode.c module generation - Support reading macros from multiple files. - Fail only if macros are not found in any files. - Account for the input.h/input-event-codes.h split in kernel tree. --- evdev/genecodes.py | 52 +++++++++++++++++++++++++++++++++------------- setup.py | 18 ++++++++++------ 2 files changed, 49 insertions(+), 21 deletions(-) diff --git a/evdev/genecodes.py b/evdev/genecodes.py index 5f89c98..65ddfc4 100755 --- a/evdev/genecodes.py +++ b/evdev/genecodes.py @@ -2,13 +2,33 @@ # -*- coding: utf-8; -*- ''' -Generate a Python extension module that exports macros from -/usr/include/linux/input.h +Generate a Python extension module with the constants defined in linux/input.h. ''' +from __future__ import print_function import os, sys, re +#----------------------------------------------------------------------------- +# The default header file locations to try. +headers = [ + '/usr/include/linux/input.h', + '/usr/include/linux/input-event-codes.h', +] + +if sys.argv[1:]: + headers = sys.argv[1:] + + +#----------------------------------------------------------------------------- +macro_regex = r'#define +((?:KEY|ABS|REL|SW|MSC|LED|BTN|REP|SND|ID|EV|BUS|SYN|FF)_\w+)' +macro_regex = re.compile(macro_regex) + +uname = list(os.uname()); del uname[1] +uname = ' '.join(uname) + + +#----------------------------------------------------------------------------- template = r''' #include #ifdef __FreeBSD__ @@ -73,22 +93,24 @@ #endif ''' -header = '/usr/include/linux/input.h' if len(sys.argv) == 1 else sys.argv[1] -regex = r'#define +((?:KEY|ABS|REL|SW|MSC|LED|BTN|REP|SND|ID|EV|BUS|SYN|FF)_\w+)' -regex = re.compile(regex) - -if not os.path.exists(header): - print('no such file: %s' % header) - sys.exit(1) - -def getmacros(): +def parse_header(header): for line in open(header): - macro = regex.search(line) + macro = macro_regex.search(line) if macro: yield ' PyModule_AddIntMacro(m, %s);' % macro.group(1) -uname = list(os.uname()); del uname[1] -uname = ' '.join(uname) +all_macros = [] +for header in headers: + try: + fh = open(header) + except (IOError, OSError): + continue + all_macros += parse_header(header) + +if not all_macros: + print('no input macros found in: %s' % ' '.join(headers), file=sys.stderr) + sys.exit(1) + -macros = os.linesep.join(getmacros()) +macros = os.linesep.join(all_macros) print(template % (uname, macros)) diff --git a/setup.py b/setup.py index b309fe9..30fea8c 100755 --- a/setup.py +++ b/setup.py @@ -65,15 +65,21 @@ #----------------------------------------------------------------------------- def create_ecodes(): - header = '/usr/include/linux/input.h' + headers = [ + '/usr/include/linux/input.h', + '/usr/include/linux/input-event-codes.h', + ] - if not os.path.isfile(header): + headers = [header for header in headers if os.path.isfile(header)] + if not headers: msg = '''\ - The linux/input.h header file is missing. You will have to - install the headers for your kernel in order to continue: + The linux/input.h and linux/input-event-codes.h include files are + missing missing. You will have to install the kernel header files in + order to continue: yum install kernel-headers-$(uname -r) apt-get install linux-headers-$(uname -r) + emerge sys-kernel/linux-headers pacman -S kernel-headers\n\n''' sys.stderr.write(textwrap.dedent(msg)) @@ -81,8 +87,8 @@ def create_ecodes(): from subprocess import check_call - print('writing ecodes.c (using %s)' % header) - cmd = '%s genecodes.py %s > ecodes.c' % (sys.executable, header) + print('writing ecodes.c (using %s)' % ' '.join(headers)) + cmd = '%s genecodes.py %s > ecodes.c' % (sys.executable, ' '.join(headers)) check_call(cmd, cwd="%s/evdev" % here, shell=True) From 324309e4887886c892f67e0b2949f9e347791fde Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Mon, 1 Feb 2016 01:14:10 +0100 Subject: [PATCH 030/270] Fix mixed tab/space indentation --- evdev/uinput.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/evdev/uinput.c b/evdev/uinput.c index df6f5d2..d766ba7 100644 --- a/evdev/uinput.c +++ b/evdev/uinput.c @@ -74,7 +74,7 @@ uinput_create(PyObject *self, PyObject *args) { item = PyList_GetItem(absinfo, i); abscode = (int)PyLong_AsLong(PyList_GetItem(item, 0)); - /* min/max/fuzz/flat start from index 2 because index 1 is value */ + /* min/max/fuzz/flat start from index 2 because index 1 is value */ uidev.absmin[abscode] = PyLong_AsLong(PyList_GetItem(item, 2)); uidev.absmax[abscode] = PyLong_AsLong(PyList_GetItem(item, 3)); uidev.absfuzz[abscode] = PyLong_AsLong(PyList_GetItem(item, 4)); @@ -84,15 +84,15 @@ uinput_create(PyObject *self, PyObject *args) { if (write(fd, &uidev, sizeof(uidev)) != sizeof(uidev)) goto on_err; - /* if (ioctl(fd, UI_SET_EVBIT, EV_KEY) < 0) */ + /* if (ioctl(fd, UI_SET_EVBIT, EV_KEY) < 0) */ /* goto on_err; */ /* int i; */ - /* for (i=0; i Date: Sun, 31 Jan 2016 22:37:10 -0200 Subject: [PATCH 031/270] Support for read() operation on UInput This is necessary to support devices with EV_LED and EV_SND --- evdev/uinput.c | 2 +- evdev/uinput.py | 48 ++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/evdev/uinput.c b/evdev/uinput.c index d766ba7..530e270 100644 --- a/evdev/uinput.c +++ b/evdev/uinput.c @@ -37,7 +37,7 @@ uinput_open(PyObject *self, PyObject *args) int ret = PyArg_ParseTuple(args, "s", &devnode); if (!ret) return NULL; - int fd = open(devnode, O_WRONLY | O_NONBLOCK); + int fd = open(devnode, O_RDWR | O_NONBLOCK); if (fd < 0) { PyErr_SetString(PyExc_IOError, "could not open uinput device in write mode"); return NULL; diff --git a/evdev/uinput.py b/evdev/uinput.py index 04c6d91..988dd48 100644 --- a/evdev/uinput.py +++ b/evdev/uinput.py @@ -4,9 +4,9 @@ import stat import time -from evdev import _uinput +from evdev import _input, _uinput from evdev import ecodes, util, device - +from evdev.events import InputEvent class UInputError(Exception): pass @@ -121,6 +121,50 @@ def close(self): _uinput.close(self.fd) self.fd = -1 + def fileno(self): + ''' + Return the file descriptor to the open event device. This + makes it possible to pass pass ``InputDevice`` instances + directly to :func:`select.select()` and + :class:`asyncore.file_dispatcher`.''' + + return self.fd + + def read_one(self): + ''' + Read and return a single input event as an instance of + :class:`InputEvent `. + + Return ``None`` if there are no pending input events. + ''' + + # event -> (sec, usec, type, code, val) + event = _input.device_read(self.fd) + + if event: + return InputEvent(*event) + + def read_loop(self): + '''Enter an endless ``select()`` loop that yields input events.''' + + while True: + r, w, x = select([self.fd], [], []) + for event in self.read(): + yield event + + def read(self): + ''' + Read multiple input events from device. Return a generator object that + yields :class:`InputEvent ` instances. Raises + `BlockingIOError` if there are no available events at the moment. + ''' + + # events -> [(sec, usec, type, code, val), ...] + events = _input.device_read_many(self.fd) + + for i in events: + yield InputEvent(*i) + def write_event(self, event): ''' Inject an input event into the input subsystem. Events are From 539d342d4b27daa1e4bfe68393eadf34d59178f0 Mon Sep 17 00:00:00 2001 From: Paulo Costa Date: Sun, 31 Jan 2016 22:51:23 -0200 Subject: [PATCH 032/270] Added generic `set()` method to `device` (Generalization of `set_led()`) --- evdev/device.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/evdev/device.py b/evdev/device.py index 4735446..fcb3cac 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -229,7 +229,18 @@ def set_led(self, led_num, value): .. ''' - _uinput.write(self.fd, ecodes.EV_LED, led_num, value) + self.set(ecodes.EV_LED, led_num, value) + + @need_write + def set(self, etype, code, value): + ''' + Set the state of the selected component. For example:: + + device.set(ecodes.EV_LED, ecodes.LED_NUML, 1) + + .. + ''' + _uinput.write(self.fd, etype, code, value) def __eq__(self, other): '''Two devices are equal if their :data:`info` attributes are equal.''' From d26950569d57b2e9aa8a7e563c25ad5da1db2971 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Tue, 2 Feb 2016 21:50:58 +0100 Subject: [PATCH 033/270] Merge ioctl_EVIOCGKEYS and get_sw_led_snd into one function --- evdev/device.py | 5 ++-- evdev/input.c | 73 +++++++++++++++++++------------------------------ 2 files changed, 31 insertions(+), 47 deletions(-) diff --git a/evdev/device.py b/evdev/device.py index fcb3cac..be3ef30 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -2,6 +2,7 @@ import os import fcntl + from select import select from collections import namedtuple @@ -214,7 +215,7 @@ def leds(self, verbose=False): [('LED_NUML', 0), ('LED_CAPSL', 1), ('LED_MISC', 8), ('LED_MAIL', 9)] ''' - leds = _input.get_sw_led_snd(self.fd, ecodes.EV_LED) + leds = _input.ioctl_EVIOCG_bits(self.fd, ecodes.EV_LED) if verbose: return [(ecodes.LED[l] if l in ecodes.LED else '?', l) for l in leds] @@ -362,7 +363,7 @@ def active_keys(self, verbose=False): [('KEY_ESC', 1), ('KEY_LEFTSHIFT', 42)] ''' - active_keys = _input.ioctl_EVIOCGKEY(self.fd) + active_keys = _input.ioctl_EVIOCG_bits(self.fd, ecodes.EV_KEY) if verbose: return [(ecodes.KEY[k] if k in ecodes.KEY else '?', k) for k in active_keys] diff --git a/evdev/input.c b/evdev/input.c index 8318cb7..5e2869b 100644 --- a/evdev/input.c +++ b/evdev/input.c @@ -314,62 +314,46 @@ ioctl_EVIOCGRAB(PyObject *self, PyObject *args) static PyObject * -ioctl_EVIOCGKEY(PyObject *self, PyObject *args) +ioctl_EVIOCG_bits(PyObject *self, PyObject *args) { - int fd, ret, key; - char keys_bitmask[KEY_MAX/8 + 1]; - PyObject* res = PyList_New(0); - - ret = PyArg_ParseTuple(args, "i", &fd); - if (!ret) return NULL; - - memset(&keys_bitmask, 0, sizeof(keys_bitmask)); - ret = ioctl(fd, EVIOCGKEY(sizeof(keys_bitmask)), keys_bitmask); - if (ret < 0) { - PyErr_SetFromErrno(PyExc_IOError); - return NULL; - } - - for (key = 0; key < KEY_MAX; key++) { - if (test_bit(keys_bitmask, key)) { - PyList_Append(res, Py_BuildValue("i", key)); - } - } - - return res; -} - - -// todo: this function needs a better name -static PyObject * -get_sw_led_snd(PyObject *self, PyObject *args) -{ - int i, max, fd, evtype, ret; - PyObject* res = PyList_New(0); + int max, fd, evtype, ret; ret = PyArg_ParseTuple(args, "ii", &fd, &evtype); if (!ret) return NULL; - if (evtype == EV_LED) - max = LED_MAX; - else if (evtype == EV_SW) - max = SW_MAX; - else if (evtype == EV_SND) - max = SND_MAX; - else + switch (evtype) { + case EV_LED: + max = LED_MAX; break; + case EV_SND: + max = SND_MAX; break; + case EV_KEY: + max = KEY_MAX; break; + case EV_SW: + max = SW_MAX; break; + default: return NULL; + } char bytes[(max+7)/8]; memset(bytes, 0, sizeof bytes); - if (evtype == EV_LED) + switch (evtype) { + case EV_LED: ret = ioctl(fd, EVIOCGLED(sizeof(bytes)), &bytes); - else if (evtype == EV_SW) - ret = ioctl(fd, EVIOCGSW(sizeof(bytes)), &bytes); - else if (evtype == EV_SND) + break; + case EV_SND: ret = ioctl(fd, EVIOCGSND(sizeof(bytes)), &bytes); + break; + case EV_KEY: + ret = ioctl(fd, EVIOCGKEY(sizeof(bytes)), &bytes); + break; + case EV_SW: + ret = ioctl(fd, EVIOCGSW(sizeof(bytes)), &bytes); + break; + } - for (i=0 ; i Date: Tue, 2 Feb 2016 22:17:10 +0100 Subject: [PATCH 034/270] Split util.resolve_ecodes() into two functions --- doc/apidoc.rst | 2 +- evdev/__init__.py | 2 +- evdev/device.py | 6 +++--- evdev/util.py | 49 +++++++++++++++++++++++++++++++---------------- 4 files changed, 38 insertions(+), 21 deletions(-) diff --git a/doc/apidoc.rst b/doc/apidoc.rst index ecfec5e..d846b14 100644 --- a/doc/apidoc.rst +++ b/doc/apidoc.rst @@ -33,7 +33,7 @@ API Reference ========== .. automodule:: evdev.util - :members: list_devices, is_device, categorize, resolve_ecodes + :members: list_devices, is_device, categorize, resolve_ecodes, resolve_ecodes_dict :member-order: bysource ``ecodes`` diff --git a/evdev/__init__.py b/evdev/__init__.py index 6359778..5f3645c 100644 --- a/evdev/__init__.py +++ b/evdev/__init__.py @@ -7,6 +7,6 @@ from evdev.device import DeviceInfo, InputDevice, AbsInfo from evdev.events import InputEvent, KeyEvent, RelEvent, SynEvent, AbsEvent, event_factory from evdev.uinput import UInput, UInputError -from evdev.util import list_devices, categorize, resolve_ecodes +from evdev.util import list_devices, categorize, resolve_ecodes, resolve_ecodes_dict from evdev import ecodes from evdev import ff diff --git a/evdev/device.py b/evdev/device.py index be3ef30..758f4d3 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -186,7 +186,7 @@ def capabilities(self, verbose=False, absinfo=True): ''' if verbose: - return dict(util.resolve_ecodes(self._capabilities(absinfo))) + return dict(util.resolve_ecodes_dict(self._capabilities(absinfo))) else: return self._capabilities(absinfo) @@ -217,7 +217,7 @@ def leds(self, verbose=False): ''' leds = _input.ioctl_EVIOCG_bits(self.fd, ecodes.EV_LED) if verbose: - return [(ecodes.LED[l] if l in ecodes.LED else '?', l) for l in leds] + return util.resolve_ecodes(ecodes.LED, leds) return leds @@ -365,6 +365,6 @@ def active_keys(self, verbose=False): ''' active_keys = _input.ioctl_EVIOCG_bits(self.fd, ecodes.EV_KEY) if verbose: - return [(ecodes.KEY[k] if k in ecodes.KEY else '?', k) for k in active_keys] + return util.resolve_ecodes(ecodes.KEY, active_keys) return active_keys diff --git a/evdev/util.py b/evdev/util.py index a407538..910da9f 100644 --- a/evdev/util.py +++ b/evdev/util.py @@ -48,7 +48,7 @@ def categorize(event): return event -def resolve_ecodes(typecodemap, unknown='?'): +def resolve_ecodes_dict(typecodemap, unknown='?'): ''' Resolve event codes and types to their verbose names. @@ -57,7 +57,7 @@ def resolve_ecodes(typecodemap, unknown='?'): Example:: - resolve_ecodes({ 1: [272, 273, 274] }) + resolve_ecodes_dict({ 1: [272, 273, 274] }) { ('EV_KEY', 1): [('BTN_MOUSE', 272), ('BTN_RIGHT', 273), ('BTN_MIDDLE', 274)] } @@ -66,7 +66,7 @@ def resolve_ecodes(typecodemap, unknown='?'): :class:`AbsInfo ` ) the result would look like:: - resolve_ecodes({ 3: [(0, AbsInfo(...))] }) + resolve_ecodes_dict({ 3: [(0, AbsInfo(...))] }) { ('EV_ABS', 3L): [(('ABS_X', 0L), AbsInfo(...))] } ''' @@ -75,24 +75,41 @@ def resolve_ecodes(typecodemap, unknown='?'): # ecodes.keys are a combination of KEY_ and BTN_ codes if etype == ecodes.EV_KEY: - code_names = ecodes.keys + ecode_dict = ecodes.keys else: - code_names = getattr(ecodes, type_name.split('_')[-1]) + ecode_dict = getattr(ecodes, type_name.split('_')[-1]) - res = [] - for i in codes: - # elements with AbsInfo(), eg { 3 : [(0, AbsInfo(...)), (1, AbsInfo(...))] } - if isinstance(i, tuple): - l = ((code_names[i[0]], i[0]), i[1]) if i[0] in code_names \ - else ((unknown, i[0]), i[1]) + resolved = resolve_ecodes(ecode_dict, codes, unknown) + yield (type_name, etype), resolved - # just ecodes { 0 : [0, 1, 3], 1 : [30, 48] } + +def resolve_ecodes(ecode_dict, ecode_list, unknown='?'): + ''' + Resolve event codes and types to their verbose names. + + Example:: + + resolve_ecodes([272, 273, 274]) + [('BTN_MOUSE', 272), ('BTN_RIGHT', 273), ('BTN_MIDDLE', 274)] + ''' + res = [] + for ecode in ecode_list: + # elements with AbsInfo(), eg { 3 : [(0, AbsInfo(...)), (1, AbsInfo(...))] } + if isinstance(ecode, tuple): + if ecode[0] in ecode_dict: + l = ((ecode_dict[ecode[0]], ecode[0]), ecode[1]) else: - l = (code_names[i], i) if i in code_names else (unknown, i) + l = ((unknown, ecode[0]), ecode[1]) - res.append(l) + # just ecodes, e.g: { 0 : [0, 1, 3], 1 : [30, 48] } + else: + if ecode in ecode_dict: + l = (ecode_dict[ecode], ecode) + else: + l = (unknown, ecode) + res.append(l) - yield (type_name, etype), res + return res -__all__ = ('list_devices', 'is_device', 'categorize', 'resolve_ecodes') +__all__ = ('list_devices', 'is_device', 'categorize', 'resolve_ecodes', 'resolve_ecodes_dict') From 96122543aa0f7cf6ba5cd21e47cabb2c4708e130 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Wed, 3 Feb 2016 01:19:37 +0100 Subject: [PATCH 035/270] Docstring improvements - Use numpy style docstrings with sphinx.ext.napoleon. - Consistency, grammar and spelling. --- doc/conf.py | 11 ++- evdev/device.py | 180 +++++++++++++++++++++++++++---------------- evdev/uinput.py | 107 ++++++++++++++----------- evdev/util.py | 26 +++---- requirements-dev.txt | 1 + 5 files changed, 199 insertions(+), 126 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 49425a0..0520a4f 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -31,7 +31,12 @@ # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.intersphinx'] +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.viewcode', + 'sphinx.ext.intersphinx', + 'sphinx.ext.napoleon', +] autodoc_member_order = 'bysource' @@ -49,7 +54,7 @@ # General information about the project. project = u'python-evdev' -copyright = u'2012-2015, Georgi Valkov' +copyright = u'2012-2016, Georgi Valkov' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -246,7 +251,7 @@ 'Miscellaneous'), ] -intersphinx_mapping = {'python': ('http://docs.python.org/3.3', None)} +intersphinx_mapping = {'python': ('http://docs.python.org/3.5', None)} # Documents to append as an appendix to all manuals. #texinfo_appendices = [] diff --git a/evdev/device.py b/evdev/device.py index 758f4d3..3914eb7 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -21,36 +21,42 @@ class EvdevError(Exception): class AbsInfo(_AbsInfo): - ''' - A ``namedtuple`` for storing absolut axis information - + '''Absolute axis information. + + A ``namedtuple`` used for storing absolute axis information - corresponds to the ``input_absinfo`` struct: - **value** - Latest reported value for the axis. + Attributes + --------- + value + Latest reported value for the axis. - **min** - Specifies minimum value for the axis. + min + Specifies minimum value for the axis. - **max** - Specifies maximum value for the axis. + max + Specifies maximum value for the axis. - **fuzz** - Specifies fuzz value that is used to filter noise from the - event stream. + fuzz + Specifies fuzz value that is used to filter noise from the + event stream. - **flat** - Values that are within this value will be discarded by joydev - interface and reported as 0 instead. + flat + Values that are within this value will be discarded by joydev + interface and reported as 0 instead. - **resolution** - Specifies resolution for the values reported for the axis. - Resolution for main axes (``ABS_X, ABS_Y, ABS_Z``) is reported - in units per millimeter (units/mm), resolution for rotational - axes (``ABS_RX, ABS_RY, ABS_RZ``) is reported in units per - radian. + resolution + Specifies resolution for the values reported for the axis. + Resolution for main axes (``ABS_X, ABS_Y, ABS_Z``) is reported + in units per millimeter (units/mm), resolution for rotational + axes (``ABS_RX, ABS_RY, ABS_RZ``) is reported in units per + radian. + + Note + ---- + The input core does not clamp reported values to the ``[minimum, + maximum]`` limits, such task is left to userspace. - .. note: The input core does not clamp reported values to the - ``[minimum, maximum]`` limits, such task is left to userspace. ''' def __str__(self): @@ -58,15 +64,16 @@ def __str__(self): class KbdInfo(_KbdInfo): - ''' - Keyboard repeat rate: + '''Keyboard repeat rate. - **repeat** - Keyboard repeat rate in characters per second. + Attributes + ---------- + repeat + Keyboard repeat rate in characters per second. - **delay** - Amount of time that a key must be depressed before it will start - to repeat (in milliseconds). + delay + Amount of time that a key must be depressed before it will start + to repeat (in milliseconds). ''' def __str__(self): @@ -74,6 +81,15 @@ def __str__(self): class DeviceInfo(_DeviceInfo): + ''' + Attributes + ---------- + bustype + vendor + product + version + ''' + def __str__(self): msg = 'bus: {:04x}, vendor {:04x}, product {:04x}, version {:04x}' return msg.format(*self) @@ -89,7 +105,10 @@ class InputDevice(object): def __init__(self, dev): ''' - :param dev: path to input device + Arguments + --------- + dev : str + Path to input device ''' #: Path to input device. @@ -153,13 +172,18 @@ def _capabilities(self, absinfo=True): def capabilities(self, verbose=False, absinfo=True): ''' Return the event types that this device supports as a mapping of - supported event types to lists of handled event codes. Example:: + supported event types to lists of handled event codes. - { 1: [272, 273, 274], - 2: [0, 1, 6, 8] } + Example + -------- + >>> device.capabilities() + { 1: [272, 273, 274], + 2: [0, 1, 6, 8] } If ``verbose`` is ``True``, event codes and types will be resolved - to their names. Example:: + to their names. + + :: { ('EV_KEY', 1): [('BTN_MOUSE', 272), ('BTN_RIGHT', 273), @@ -192,7 +216,7 @@ def capabilities(self, verbose=False, absinfo=True): def need_write(func): ''' - Decorator that raises EvdevError() if there is no write access to the + Decorator that raises :class:`EvdevError` if there is no write access to the input device. ''' def wrapper(*args): @@ -205,14 +229,17 @@ def wrapper(*args): def leds(self, verbose=False): ''' - Return currently set LED keys. For example:: + Return currently set LED keys. - [0, 1, 8, 9] + Example + ------- + >>> device.leds() + [0, 1, 8, 9] If ``verbose`` is ``True``, event codes are resolved to their - names. Unknown codes are resolved to ``'?'``. For example:: + names. Unknown codes are resolved to ``'?'``:: - [('LED_NUML', 0), ('LED_CAPSL', 1), ('LED_MISC', 8), ('LED_MAIL', 9)] + [('LED_NUML', 0), ('LED_CAPSL', 1), ('LED_MISC', 8), ('LED_MAIL', 9)] ''' leds = _input.ioctl_EVIOCG_bits(self.fd, ecodes.EV_LED) @@ -224,27 +251,29 @@ def leds(self, verbose=False): @need_write def set_led(self, led_num, value): ''' - Set the state of the selected LED. For example:: - - device.set_led(ecodes.LED_NUML, 1) + Set the state of the selected LED. - .. + Example + ------- + >>> device.set_led(ecodes.LED_NUML, 1) ''' self.set(ecodes.EV_LED, led_num, value) @need_write def set(self, etype, code, value): ''' - Set the state of the selected component. For example:: - - device.set(ecodes.EV_LED, ecodes.LED_NUML, 1) + Set the state of the selected component. - .. + Example + ------- + >>> device.set(ecodes.EV_LED, ecodes.LED_NUML, 1) ''' _uinput.write(self.fd, etype, code, value) def __eq__(self, other): - '''Two devices are equal if their :data:`info` attributes are equal.''' + ''' + Two devices are equal if their :data:`info` attributes are equal. + ''' return isinstance(other, self.__class__) and self.info == other.info def __str__(self): @@ -264,10 +293,10 @@ def close(self): def fileno(self): ''' - Return the file descriptor to the open event device. This - makes it possible to pass pass ``InputDevice`` instances - directly to :func:`select.select()` and - :class:`asyncore.file_dispatcher`.''' + Return the file descriptor to the open event device. This makes + it possible to pass :class:`InputDevice` instances directly to + :func:`select.select()` and :class:`asyncore.file_dispatcher`. + ''' return self.fd @@ -286,10 +315,12 @@ def read_one(self): return InputEvent(*event) def read_loop(self): - '''Enter an endless ``select()`` loop that yields input events.''' + ''' + Enter an endless :func:`select.select()` loop that yields input events. + ''' while True: - r, w, x = select([self.fd], [], []) + r, w, x = select.select([self.fd], [], []) for event in self.read(): yield event @@ -312,37 +343,48 @@ def grab(self): be unable to receive events until the device is released. Only one process can hold a ``EVIOCGRAB`` on a device. - .. warning:: Grabbing an already grabbed device will raise an - ``IOError``.''' + Warning + ------- + Grabbing an already grabbed device will raise an ``IOError``. + ''' _input.ioctl_EVIOCGRAB(self.fd, 1) def ungrab(self): - '''Release device if it has been already grabbed (uses - `EVIOCGRAB`). + ''' + Release device if it has been already grabbed (uses `EVIOCGRAB`). - .. warning:: Releasing an already released device will raise an - ``IOError('Invalid argument')``.''' + Warning + ------- + Releasing an already released device will raise an + ``IOError('Invalid argument')``. + ''' _input.ioctl_EVIOCGRAB(self.fd, 0) def upload_effect(self, effect): - '''Upload a force feedback effect to a force feedback device.''' + ''' + Upload a force feedback effect to a force feedback device. + ''' data = bytes(buffer(effect)[:]) ff_id = _input.upload_effect(self.fd, data) return ff_id def erase_effect(self, ff_id): - '''Erase a force effect from a force feedback device. This - also stops the effect.''' + ''' + Erase a force effect from a force feedback device. This also + stops the effect. + ''' _input.erase_effect(self.fd, ff_id) @property def repeat(self): - '''Get or set the keyboard repeat rate (in characters per - minute) and delay (in milliseconds).''' + ''' + Get or set the keyboard repeat rate (in characters per + minute) and delay (in milliseconds). + ''' return KbdInfo(*_input.ioctl_EVIOCGREP(self.fd)) @@ -352,9 +394,13 @@ def repeat(self, value): def active_keys(self, verbose=False): ''' - Return currently active keys. Example:: + Return currently active keys. + + Example + ------- - [1, 42] + >>> device.active_keys() + [1, 42] If ``verbose`` is ``True``, key codes are resolved to their verbose names. Unknown codes are resolved to ``'?'``. For diff --git a/evdev/uinput.py b/evdev/uinput.py index 988dd48..6b319dd 100644 --- a/evdev/uinput.py +++ b/evdev/uinput.py @@ -29,36 +29,46 @@ def __init__(self, vendor=0x1, product=0x1, version=0x1, bustype=0x3, devnode='/dev/uinput'): ''' - :param events: the event types and codes that the uinput - device will be able to inject - defaults to all - key codes. + Arguments + --------- + events : dict + Dictionary of event types mapping to lists of event codes. The + event types and codes that the uinput device will be able to + inject - defaults to all key codes. - :type events: dictionary of event types mapping to lists of - event codes. + name + The name of the input device. - :param name: the name of the input device. - :param vendor: vendor identifier. - :param product: product identifier. - :param version: version identifier. - :param bustype: bustype identifier. + vendor + Vendor identifier. - .. note:: If you do not specify any events, the uinput device - will be able to inject only ``KEY_*`` and ``BTN_*`` - event codes. + product + Product identifier. + + version + version identifier. + + bustype + bustype identifier. + + Note + ---- + If you do not specify any events, the uinput device will be able + to inject only ``KEY_*`` and ``BTN_*`` event codes. ''' self.name = name #: Uinput device name. self.vendor = vendor #: Device vendor identifier. self.product = product #: Device product identifier. self.version = version #: Device version identifier. - self.bustype = bustype #: Device bustype - eg. ``BUS_USB``. - self.devnode = devnode #: Uinput device node - eg. ``/dev/uinput/``. + self.bustype = bustype #: Device bustype - e.g. ``BUS_USB``. + self.devnode = devnode #: Uinput device node - e.g. ``/dev/uinput/``. if not events: events = {ecodes.EV_KEY: ecodes.keys.keys()} - # the min, max, fuzz and flat values for the absolute axis for - # a given code + # The min, max, fuzz and flat values for the absolute axis for + # a given code. absinfo = [] self._verify() @@ -66,20 +76,20 @@ def __init__(self, #: Write-only, non-blocking file descriptor to the uinput device node. self.fd = _uinput.open(devnode) - # set device capabilities + # Set device capabilities. for etype, codes in events.items(): for code in codes: - # handle max, min, fuzz, flat + # Handle max, min, fuzz, flat. if isinstance(code, (tuple, list, device.AbsInfo)): - # flatten (ABS_Y, (0, 255, 0, 0, 0, 0)) to (ABS_Y, 0, 255, 0, 0, 0, 0) + # Flatten (ABS_Y, (0, 255, 0, 0, 0, 0)) to (ABS_Y, 0, 255, 0, 0, 0, 0). f = [code[0]]; f += code[1] absinfo.append(f) code = code[0] - #:todo: a lot of unnecessary packing/unpacking + # TODO: a lot of unnecessary packing/unpacking _uinput.enable(self.fd, etype, code) - # create uinput device + # Create the uinput device. _uinput.create(self.fd, name, vendor, product, version, bustype, absinfo) #: An :class:`InputDevice ` instance @@ -95,7 +105,7 @@ def __exit__(self, type, value, tb): self.close() def __repr__(self): - # :todo: + # TODO: v = (repr(getattr(self, i)) for i in ('name', 'bustype', 'vendor', 'product', 'version')) return '{}({})'.format(self.__class__.__name__, ', '.join(v)) @@ -112,11 +122,11 @@ def __str__(self): return msg def close(self): - # close the associated InputDevice, if it was previously opened + # Close the associated InputDevice, if it was previously opened. if self.device is not None: self.device.close() - # destroy the uinput device + # Destroy the uinput device. if self.fd > -1: _uinput.close(self.fd) self.fd = -1 @@ -145,7 +155,9 @@ def read_one(self): return InputEvent(*event) def read_loop(self): - '''Enter an endless ``select()`` loop that yields input events.''' + ''' + Enter an endless :func:`select.select()` loop that yields input events. + ''' while True: r, w, x = select([self.fd], [], []) @@ -162,23 +174,25 @@ def read(self): # events -> [(sec, usec, type, code, val), ...] events = _input.device_read_many(self.fd) - for i in events: - yield InputEvent(*i) + for event in events: + yield InputEvent(*event) def write_event(self, event): ''' Inject an input event into the input subsystem. Events are queued until a synchronization event is received. - :param event: InputEvent instance or an object with an - ``event`` attribute (:class:`KeyEvent - `, :class:`RelEvent - ` etc). - - Example:: - - ev = InputEvent(1334414993, 274296, ecodes.EV_KEY, ecodes.KEY_A, 1) - ui.write_event(ev) + Arguments + --------- + event: InputEvent + InputEvent instance or an object with an ``event`` attribute + (:class:`KeyEvent `, :class:`RelEvent + ` etc). + + Example + ------- + >>> ev = InputEvent(1334414993, 274296, ecodes.EV_KEY, ecodes.KEY_A, 1) + >>> ui.write_event(ev) ''' if hasattr(event, 'event'): @@ -191,14 +205,21 @@ def write(self, etype, code, value): Inject an input event into the input subsystem. Events are queued until a synchronization event is received. - :param etype: event type (eg. ``EV_KEY``). - :param code: event code (eg. ``KEY_A``). - :param value: event value (eg. 0 1 2 - depends on event type). + Arguments + --------- + etype + event type (e.g. ``EV_KEY``). + + code + event code (e.g. ``KEY_A``). - Example:: + value + event value (e.g. 0 1 2 - depends on event type). - ui.write(e.EV_KEY, e.KEY_A, 1) # key A - down - ui.write(e.EV_KEY, e.KEY_A, 0) # key A - up + Example + --------- + >>> ui.write(e.EV_KEY, e.KEY_A, 1) # key A - down + >>> ui.write(e.EV_KEY, e.KEY_A, 0) # key A - up ''' _uinput.write(self.fd, etype, code, value) diff --git a/evdev/util.py b/evdev/util.py index 910da9f..b6652d7 100644 --- a/evdev/util.py +++ b/evdev/util.py @@ -55,19 +55,19 @@ def resolve_ecodes_dict(typecodemap, unknown='?'): :param typecodemap: mapping of event types to lists of event codes. :param unknown: symbol to which unknown types or codes will be resolved. - Example:: - - resolve_ecodes_dict({ 1: [272, 273, 274] }) - { ('EV_KEY', 1): [('BTN_MOUSE', 272), - ('BTN_RIGHT', 273), - ('BTN_MIDDLE', 274)] } + Example + ------- + >>> resolve_ecodes_dict({ 1: [272, 273, 274] }) + { ('EV_KEY', 1): [('BTN_MOUSE', 272), + ('BTN_RIGHT', 273), + ('BTN_MIDDLE', 274)] } If ``typecodemap`` contains absolute axis info (instances of :class:`AbsInfo ` ) the result would look - like:: + like: - resolve_ecodes_dict({ 3: [(0, AbsInfo(...))] }) - { ('EV_ABS', 3L): [(('ABS_X', 0L), AbsInfo(...))] } + >>> resolve_ecodes_dict({ 3: [(0, AbsInfo(...))] }) + { ('EV_ABS', 3L): [(('ABS_X', 0L), AbsInfo(...))] } ''' for etype, codes in typecodemap.items(): @@ -87,10 +87,10 @@ def resolve_ecodes(ecode_dict, ecode_list, unknown='?'): ''' Resolve event codes and types to their verbose names. - Example:: - - resolve_ecodes([272, 273, 274]) - [('BTN_MOUSE', 272), ('BTN_RIGHT', 273), ('BTN_MIDDLE', 274)] + Example + ------- + >>> resolve_ecodes([272, 273, 274]) + [('BTN_MOUSE', 272), ('BTN_RIGHT', 273), ('BTN_MIDDLE', 274)] ''' res = [] for ecode in ecode_list: diff --git a/requirements-dev.txt b/requirements-dev.txt index e447160..8bc0fb4 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1 +1,2 @@ pytest>=2.6.4 +Sphinx>=1.3.5 From a83226409f7559a995d8d0832b733add45d284a5 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Wed, 3 Feb 2016 01:22:42 +0100 Subject: [PATCH 036/270] Add missing import and fix documentation of wrapped methods --- evdev/device.py | 18 ++++++++++++------ evdev/uinput.py | 3 ++- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/evdev/device.py b/evdev/device.py index 3914eb7..9f30c62 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -2,9 +2,9 @@ import os import fcntl - -from select import select -from collections import namedtuple +import functools +import select +import collections from evdev import _input, _uinput, ecodes, util from evdev.events import InputEvent @@ -15,9 +15,14 @@ class EvdevError(Exception): pass #-------------------------------------------------------------------------- -_AbsInfo = namedtuple('AbsInfo', ['value', 'min', 'max', 'fuzz', 'flat', 'resolution']) -_KbdInfo = namedtuple('KbdInfo', ['repeat', 'delay']) -_DeviceInfo = namedtuple('DeviceInfo', ['bustype', 'vendor', 'product', 'version']) +_AbsInfo = collections.namedtuple( + 'AbsInfo', ['value', 'min', 'max', 'fuzz', 'flat', 'resolution']) + +_KbdInfo = collections.namedtuple( + 'KbdInfo', ['repeat', 'delay']) + +_DeviceInfo = collections.namedtuple( + 'DeviceInfo', ['bustype', 'vendor', 'product', 'version']) class AbsInfo(_AbsInfo): @@ -219,6 +224,7 @@ def need_write(func): Decorator that raises :class:`EvdevError` if there is no write access to the input device. ''' + @functools.wraps(func) def wrapper(*args): fd = args[0].fd if fcntl.fcntl(fd, fcntl.F_GETFL) & os.O_RDWR: diff --git a/evdev/uinput.py b/evdev/uinput.py index 6b319dd..b326f9e 100644 --- a/evdev/uinput.py +++ b/evdev/uinput.py @@ -3,6 +3,7 @@ import os import stat import time +import select from evdev import _input, _uinput from evdev import ecodes, util, device @@ -160,7 +161,7 @@ def read_loop(self): ''' while True: - r, w, x = select([self.fd], [], []) + r, w, x = select.select([self.fd], [], []) for event in self.read(): yield event From 27b9ec061b7d6f97ef91aecc3f802f2b01805195 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Wed, 3 Feb 2016 01:25:14 +0100 Subject: [PATCH 037/270] Move 'doc' to 'docs' --- .gitignore | 2 +- {doc => docs}/Makefile | 0 {doc => docs}/_static/.keep | 0 {doc => docs}/_static/evdev-logo-small.png | Bin {doc => docs}/_static/evdev-logo.png | Bin {doc => docs}/_static/github-logo.png | Bin .../distributor-logo-archlinux.png | Bin .../pacifica-icon-set/distributor-logo-debian.png | Bin .../pacifica-icon-set/distributor-logo-fedora.png | Bin .../distributor-logo-linux-mint.png | Bin .../pacifica-icon-set/distributor-logo-opensuse.png | Bin .../pacifica-icon-set/distributor-logo-raspbian.png | Bin .../pacifica-icon-set/distributor-logo-ubuntu.png | Bin {doc => docs}/_templates/.keep | 0 {doc => docs}/apidoc.rst | 0 {doc => docs}/changelog.rst | 0 {doc => docs}/conf.py | 0 {doc => docs}/index.rst | 0 {doc => docs}/news.rst | 0 {doc => docs}/tutorial.rst | 0 20 files changed, 1 insertion(+), 1 deletion(-) rename {doc => docs}/Makefile (100%) rename {doc => docs}/_static/.keep (100%) rename {doc => docs}/_static/evdev-logo-small.png (100%) rename {doc => docs}/_static/evdev-logo.png (100%) rename {doc => docs}/_static/github-logo.png (100%) rename {doc => docs}/_static/pacifica-icon-set/distributor-logo-archlinux.png (100%) rename {doc => docs}/_static/pacifica-icon-set/distributor-logo-debian.png (100%) rename {doc => docs}/_static/pacifica-icon-set/distributor-logo-fedora.png (100%) rename {doc => docs}/_static/pacifica-icon-set/distributor-logo-linux-mint.png (100%) rename {doc => docs}/_static/pacifica-icon-set/distributor-logo-opensuse.png (100%) rename {doc => docs}/_static/pacifica-icon-set/distributor-logo-raspbian.png (100%) rename {doc => docs}/_static/pacifica-icon-set/distributor-logo-ubuntu.png (100%) rename {doc => docs}/_templates/.keep (100%) rename {doc => docs}/apidoc.rst (100%) rename {doc => docs}/changelog.rst (100%) rename {doc => docs}/conf.py (100%) rename {doc => docs}/index.rst (100%) rename {doc => docs}/news.rst (100%) rename {doc => docs}/tutorial.rst (100%) diff --git a/.gitignore b/.gitignore index da51850..b351923 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,7 @@ tags TAGS evdev/*.so evdev/ecodes.c -doc/_build +docs/_build .#* __pycache__ diff --git a/doc/Makefile b/docs/Makefile similarity index 100% rename from doc/Makefile rename to docs/Makefile diff --git a/doc/_static/.keep b/docs/_static/.keep similarity index 100% rename from doc/_static/.keep rename to docs/_static/.keep diff --git a/doc/_static/evdev-logo-small.png b/docs/_static/evdev-logo-small.png similarity index 100% rename from doc/_static/evdev-logo-small.png rename to docs/_static/evdev-logo-small.png diff --git a/doc/_static/evdev-logo.png b/docs/_static/evdev-logo.png similarity index 100% rename from doc/_static/evdev-logo.png rename to docs/_static/evdev-logo.png diff --git a/doc/_static/github-logo.png b/docs/_static/github-logo.png similarity index 100% rename from doc/_static/github-logo.png rename to docs/_static/github-logo.png diff --git a/doc/_static/pacifica-icon-set/distributor-logo-archlinux.png b/docs/_static/pacifica-icon-set/distributor-logo-archlinux.png similarity index 100% rename from doc/_static/pacifica-icon-set/distributor-logo-archlinux.png rename to docs/_static/pacifica-icon-set/distributor-logo-archlinux.png diff --git a/doc/_static/pacifica-icon-set/distributor-logo-debian.png b/docs/_static/pacifica-icon-set/distributor-logo-debian.png similarity index 100% rename from doc/_static/pacifica-icon-set/distributor-logo-debian.png rename to docs/_static/pacifica-icon-set/distributor-logo-debian.png diff --git a/doc/_static/pacifica-icon-set/distributor-logo-fedora.png b/docs/_static/pacifica-icon-set/distributor-logo-fedora.png similarity index 100% rename from doc/_static/pacifica-icon-set/distributor-logo-fedora.png rename to docs/_static/pacifica-icon-set/distributor-logo-fedora.png diff --git a/doc/_static/pacifica-icon-set/distributor-logo-linux-mint.png b/docs/_static/pacifica-icon-set/distributor-logo-linux-mint.png similarity index 100% rename from doc/_static/pacifica-icon-set/distributor-logo-linux-mint.png rename to docs/_static/pacifica-icon-set/distributor-logo-linux-mint.png diff --git a/doc/_static/pacifica-icon-set/distributor-logo-opensuse.png b/docs/_static/pacifica-icon-set/distributor-logo-opensuse.png similarity index 100% rename from doc/_static/pacifica-icon-set/distributor-logo-opensuse.png rename to docs/_static/pacifica-icon-set/distributor-logo-opensuse.png diff --git a/doc/_static/pacifica-icon-set/distributor-logo-raspbian.png b/docs/_static/pacifica-icon-set/distributor-logo-raspbian.png similarity index 100% rename from doc/_static/pacifica-icon-set/distributor-logo-raspbian.png rename to docs/_static/pacifica-icon-set/distributor-logo-raspbian.png diff --git a/doc/_static/pacifica-icon-set/distributor-logo-ubuntu.png b/docs/_static/pacifica-icon-set/distributor-logo-ubuntu.png similarity index 100% rename from doc/_static/pacifica-icon-set/distributor-logo-ubuntu.png rename to docs/_static/pacifica-icon-set/distributor-logo-ubuntu.png diff --git a/doc/_templates/.keep b/docs/_templates/.keep similarity index 100% rename from doc/_templates/.keep rename to docs/_templates/.keep diff --git a/doc/apidoc.rst b/docs/apidoc.rst similarity index 100% rename from doc/apidoc.rst rename to docs/apidoc.rst diff --git a/doc/changelog.rst b/docs/changelog.rst similarity index 100% rename from doc/changelog.rst rename to docs/changelog.rst diff --git a/doc/conf.py b/docs/conf.py similarity index 100% rename from doc/conf.py rename to docs/conf.py diff --git a/doc/index.rst b/docs/index.rst similarity index 100% rename from doc/index.rst rename to docs/index.rst diff --git a/doc/news.rst b/docs/news.rst similarity index 100% rename from doc/news.rst rename to docs/news.rst diff --git a/doc/tutorial.rst b/docs/tutorial.rst similarity index 100% rename from doc/tutorial.rst rename to docs/tutorial.rst From b7f9afa18a0630897ea2e4a608fffa5c23728096 Mon Sep 17 00:00:00 2001 From: Paulo Costa Date: Thu, 4 Feb 2016 21:19:00 -0200 Subject: [PATCH 038/270] remove _input.event_unpack, which is used nowhere --- evdev/input.c | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/evdev/input.c b/evdev/input.c index 5e2869b..0ec2db3 100644 --- a/evdev/input.c +++ b/evdev/input.c @@ -119,25 +119,6 @@ device_read_many(PyObject *self, PyObject *args) } -// Unpack a single event (this is essentially a struct.unpack(), without having -// to worry about word size. -static PyObject * -event_unpack(PyObject *self, PyObject *args) -{ - struct input_event event; - - const char *data; - int len; - - int ret = PyArg_ParseTuple(args, "s#", &data, &len); - if (!ret) return NULL; - - memcpy(&event, data, sizeof(event)); - - Py_RETURN_NONE; -} - - // Get the event types and event codes that the input device supports static PyObject * ioctl_capabilities(PyObject *self, PyObject *args) @@ -445,7 +426,6 @@ erase_effect(PyObject *self, PyObject *args) static PyMethodDef MethodTable[] = { - { "unpack", event_unpack, METH_VARARGS, "unpack a single input event" }, { "ioctl_devinfo", ioctl_devinfo, METH_VARARGS, "fetch input device info" }, { "ioctl_capabilities", ioctl_capabilities, METH_VARARGS, "fetch input device capabilities" }, { "ioctl_EVIOCGREP", ioctl_EVIOCGREP, METH_VARARGS}, From 14fc7043da8d17491eaafc593741d8b9af5407f7 Mon Sep 17 00:00:00 2001 From: Paulo Costa Date: Thu, 4 Feb 2016 21:54:50 -0200 Subject: [PATCH 039/270] Create a baseclass for `device` and `uinput`, to hold the shared functions that read and write events --- evdev/device.py | 79 ++-------------------------- evdev/eventio.py | 130 +++++++++++++++++++++++++++++++++++++++++++++++ evdev/uinput.py | 99 ++---------------------------------- 3 files changed, 137 insertions(+), 171 deletions(-) create mode 100644 evdev/eventio.py diff --git a/evdev/device.py b/evdev/device.py index 9f30c62..23c4df7 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -6,8 +6,9 @@ import select import collections -from evdev import _input, _uinput, ecodes, util +from evdev import _input, ecodes, util from evdev.events import InputEvent +from evdev.eventio import EventIO #-------------------------------------------------------------------------- @@ -100,7 +101,7 @@ def __str__(self): return msg.format(*self) -class InputDevice(object): +class InputDevice(EventIO): ''' A linux input device from which input events can be read. ''' @@ -219,20 +220,6 @@ def capabilities(self, verbose=False, absinfo=True): else: return self._capabilities(absinfo) - def need_write(func): - ''' - Decorator that raises :class:`EvdevError` if there is no write access to the - input device. - ''' - @functools.wraps(func) - def wrapper(*args): - fd = args[0].fd - if fcntl.fcntl(fd, fcntl.F_GETFL) & os.O_RDWR: - return func(*args) - msg = 'no write access to device "%s"' % args[0].fn - raise EvdevError(msg) - return wrapper - def leds(self, verbose=False): ''' Return currently set LED keys. @@ -254,7 +241,6 @@ def leds(self, verbose=False): return leds - @need_write def set_led(self, led_num, value): ''' Set the state of the selected LED. @@ -263,18 +249,7 @@ def set_led(self, led_num, value): ------- >>> device.set_led(ecodes.LED_NUML, 1) ''' - self.set(ecodes.EV_LED, led_num, value) - - @need_write - def set(self, etype, code, value): - ''' - Set the state of the selected component. - - Example - ------- - >>> device.set(ecodes.EV_LED, ecodes.LED_NUML, 1) - ''' - _uinput.write(self.fd, etype, code, value) + self.write(ecodes.EV_LED, led_num, value) def __eq__(self, other): ''' @@ -297,52 +272,6 @@ def close(self): finally: self.fd = -1 - def fileno(self): - ''' - Return the file descriptor to the open event device. This makes - it possible to pass :class:`InputDevice` instances directly to - :func:`select.select()` and :class:`asyncore.file_dispatcher`. - ''' - - return self.fd - - def read_one(self): - ''' - Read and return a single input event as an instance of - :class:`InputEvent `. - - Return ``None`` if there are no pending input events. - ''' - - # event -> (sec, usec, type, code, val) - event = _input.device_read(self.fd) - - if event: - return InputEvent(*event) - - def read_loop(self): - ''' - Enter an endless :func:`select.select()` loop that yields input events. - ''' - - while True: - r, w, x = select.select([self.fd], [], []) - for event in self.read(): - yield event - - def read(self): - ''' - Read multiple input events from device. Return a generator object that - yields :class:`InputEvent ` instances. Raises - `BlockingIOError` if there are no available events at the moment. - ''' - - # events -> [(sec, usec, type, code, val), ...] - events = _input.device_read_many(self.fd) - - for i in events: - yield InputEvent(*i) - def grab(self): ''' Grab input device using ``EVIOCGRAB`` - other applications will diff --git a/evdev/eventio.py b/evdev/eventio.py new file mode 100644 index 0000000..62b9010 --- /dev/null +++ b/evdev/eventio.py @@ -0,0 +1,130 @@ +# encoding: utf-8 + +import os +import fcntl +import functools +import select +import collections + +from evdev import _input, _uinput, ecodes, util +from evdev.events import InputEvent + +class EventIO(object): + ''' + Class capable of read and write input events. + + This is used as as a base class for both device and uinput: + + - On ``device``, you read user-generated events (keys pressed, mouse moved, etc) and write feedback events (leds, beeps) + + - On ``uinput``, you write user-generated events (keys pressed, mouse moved, etc) and read feedback events (leds, beeps) + ''' + + def fileno(self): + ''' + Return the file descriptor to the open event device. This makes + it possible to pass instances directly to :func:`select.select()` and + :class:`asyncore.file_dispatcher`. + ''' + return self.fd + + def read_loop(self): + ''' + Enter an endless loop that yields input events. + ''' + while True: + r, w, x = select.select([self.fd], [], []) + for event in self.read(): + yield event + + + def read_one(self): + ''' + Read and return a single input event as an instance of + :class:`InputEvent `. + + Return ``None`` if there are no pending input events. + ''' + + # event -> (sec, usec, type, code, val) + event = _input.device_read(self.fd) + + if event: + return InputEvent(*event) + + def read(self): + ''' + Read multiple input events from device. Return a generator object that + yields :class:`InputEvent ` instances. Raises + `BlockingIOError` if there are no available events at the moment. + ''' + + # events -> [(sec, usec, type, code, val), ...] + events = _input.device_read_many(self.fd) + + for i in events: + yield InputEvent(*i) + + + + def _need_write(func): + ''' + Decorator that raises :class:`EvdevError` if there is no write access to the + input device. + ''' + @functools.wraps(func) + def wrapper(*args): + fd = args[0].fd + if fcntl.fcntl(fd, fcntl.F_GETFL) & os.O_RDWR: + return func(*args) + msg = 'no write access to device "%s"' % args[0].fn + raise EvdevError(msg) + return wrapper + + def write_event(self, event): + ''' + Inject an input event into the input subsystem. Events are + queued until a synchronization event is received. + + Arguments + --------- + event: InputEvent + InputEvent instance or an object with an ``event`` attribute + (:class:`KeyEvent `, :class:`RelEvent + ` etc). + + Example + ------- + >>> ev = InputEvent(1334414993, 274296, ecodes.EV_KEY, ecodes.KEY_A, 1) + >>> ui.write_event(ev) + ''' + + if hasattr(event, 'event'): + event = event.event + + self.write(event.type, event.code, event.value) + + @_need_write + def write(self, etype, code, value): + ''' + Inject an input event into the input subsystem. Events are + queued until a synchronization event is received. + + Arguments + --------- + etype + event type (e.g. ``EV_KEY``). + + code + event code (e.g. ``KEY_A``). + + value + event value (e.g. 0 1 2 - depends on event type). + + Example + --------- + >>> ui.write(e.EV_KEY, e.KEY_A, 1) # key A - down + >>> ui.write(e.EV_KEY, e.KEY_A, 0) # key A - up + ''' + + _uinput.write(self.fd, etype, code, value) diff --git a/evdev/uinput.py b/evdev/uinput.py index b326f9e..c06641a 100644 --- a/evdev/uinput.py +++ b/evdev/uinput.py @@ -3,17 +3,17 @@ import os import stat import time -import select -from evdev import _input, _uinput +from evdev import _uinput from evdev import ecodes, util, device from evdev.events import InputEvent +from evdev.eventio import EventIO class UInputError(Exception): pass -class UInput(object): +class UInput(EventIO): ''' A userland input device and that can inject input events into the linux input subsystem. @@ -132,99 +132,6 @@ def close(self): _uinput.close(self.fd) self.fd = -1 - def fileno(self): - ''' - Return the file descriptor to the open event device. This - makes it possible to pass pass ``InputDevice`` instances - directly to :func:`select.select()` and - :class:`asyncore.file_dispatcher`.''' - - return self.fd - - def read_one(self): - ''' - Read and return a single input event as an instance of - :class:`InputEvent `. - - Return ``None`` if there are no pending input events. - ''' - - # event -> (sec, usec, type, code, val) - event = _input.device_read(self.fd) - - if event: - return InputEvent(*event) - - def read_loop(self): - ''' - Enter an endless :func:`select.select()` loop that yields input events. - ''' - - while True: - r, w, x = select.select([self.fd], [], []) - for event in self.read(): - yield event - - def read(self): - ''' - Read multiple input events from device. Return a generator object that - yields :class:`InputEvent ` instances. Raises - `BlockingIOError` if there are no available events at the moment. - ''' - - # events -> [(sec, usec, type, code, val), ...] - events = _input.device_read_many(self.fd) - - for event in events: - yield InputEvent(*event) - - def write_event(self, event): - ''' - Inject an input event into the input subsystem. Events are - queued until a synchronization event is received. - - Arguments - --------- - event: InputEvent - InputEvent instance or an object with an ``event`` attribute - (:class:`KeyEvent `, :class:`RelEvent - ` etc). - - Example - ------- - >>> ev = InputEvent(1334414993, 274296, ecodes.EV_KEY, ecodes.KEY_A, 1) - >>> ui.write_event(ev) - ''' - - if hasattr(event, 'event'): - event = event.event - - self.write(event.type, event.code, event.value) - - def write(self, etype, code, value): - ''' - Inject an input event into the input subsystem. Events are - queued until a synchronization event is received. - - Arguments - --------- - etype - event type (e.g. ``EV_KEY``). - - code - event code (e.g. ``KEY_A``). - - value - event value (e.g. 0 1 2 - depends on event type). - - Example - --------- - >>> ui.write(e.EV_KEY, e.KEY_A, 1) # key A - down - >>> ui.write(e.EV_KEY, e.KEY_A, 0) # key A - up - ''' - - _uinput.write(self.fd, etype, code, value) - def syn(self): ''' Inject a ``SYN_REPORT`` event into the input subsystem. Events From 27dc2c347ace786b5de253a444a72f713fac7345 Mon Sep 17 00:00:00 2001 From: Paulo Costa Date: Thu, 4 Feb 2016 22:03:48 -0200 Subject: [PATCH 040/270] Support for new asyncio coroutines The new asyncio API is very powerful, and much easier than working with selectors directly. This patch adds the coroutines `async_read` and `async_read_one`, and makes `read_loop()` compatible with the new `async for` syntax. Unfortunately, the code turned out a bit ugly in order to make the syntax backward compatible with Python 2.7. --- evdev/eventio.py | 80 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 76 insertions(+), 4 deletions(-) diff --git a/evdev/eventio.py b/evdev/eventio.py index 62b9010..18e35cb 100644 --- a/evdev/eventio.py +++ b/evdev/eventio.py @@ -9,6 +9,11 @@ from evdev import _input, _uinput, ecodes, util from evdev.events import InputEvent +try: + import asyncio +except ImportError: + asyncio = None + class EventIO(object): ''' Class capable of read and write input events. @@ -31,11 +36,78 @@ def fileno(self): def read_loop(self): ''' Enter an endless loop that yields input events. + + The returned iterator is compatible with `async for` syntax ''' - while True: - r, w, x = select.select([self.fd], [], []) - for event in self.read(): - yield event + class ReadIterator: + def __init__(self, device): + self.current_batch = iter(()) + self.device = device + + # Standard iterator + def __iter__(self): + return self + + def next(self): # Python 2.x + return self.__next__() + + def __next__(self): # Python 3.x + try: + # Try to read from previous batch + return next(self.current_batch) + except StopIteration: + # Fetch next batch + r, w, x = select.select([self.device.fd], [], []) + self.current_batch = self.device.read() + return next(self.current_batch) + + # Async iteration - Python 3.5 + if asyncio is not None: + @asyncio.coroutine + def __aiter__(self): + return self + + @asyncio.coroutine + def __anext__(self): + future = asyncio.Future() + try: + # Try to read from previous batch + future.set_result(next(self.current_batch)) + except StopIteration: + # Fetch next batch + def next_batch_ready(batch): + self.current_batch = batch.result() + future.set_result(next(self.current_batch)) + self.device.async_read().add_done_callback(next_batch_ready) + return future + + return ReadIterator(self) + + if asyncio is not None: + def _do_when_readable(self, callback): + loop = asyncio.get_event_loop() + def ready(): + loop.remove_reader(self.fileno()) + callback() + loop.add_reader(self.fileno(), ready) + + def async_read_one(self): + ''' + asyncio coroutine to read and return a single input event as an instance of + :class:`InputEvent `. + ''' + future = asyncio.Future() + self._do_when_readable(lambda: future.set_result(self.read_one())) + return future + + def async_read(self): + ''' + asyncio coroutine to read multiple input events from device. Return a generator object that + yields :class:`InputEvent ` instances. + ''' + future = asyncio.Future() + self._do_when_readable(lambda: future.set_result(self.read())) + return future def read_one(self): From a0ee0685926fd55dcd676b1e645b7e531ca78729 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sat, 6 Feb 2016 20:07:31 +0100 Subject: [PATCH 041/270] Slightly refactor the last two changes - Split the asyncio and 'regular' io changes. - Reformat docstrings and remove trailing whitespace. - Rename _need_write() back to need_write(). --- evdev/device.py | 9 +++- evdev/eventio.py | 113 ++++++++--------------------------------- evdev/eventio_async.py | 81 +++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 93 deletions(-) create mode 100644 evdev/eventio_async.py diff --git a/evdev/device.py b/evdev/device.py index 23c4df7..ec3e0be 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -2,13 +2,18 @@ import os import fcntl -import functools import select +import functools import collections from evdev import _input, ecodes, util from evdev.events import InputEvent -from evdev.eventio import EventIO + +try: + import asyncio + from evdev.eventio_async import EventIO +except ImportError: + from evdev.eventio import EventIO #-------------------------------------------------------------------------- diff --git a/evdev/eventio.py b/evdev/eventio.py index 18e35cb..2860a1b 100644 --- a/evdev/eventio.py +++ b/evdev/eventio.py @@ -9,106 +9,39 @@ from evdev import _input, _uinput, ecodes, util from evdev.events import InputEvent -try: - import asyncio -except ImportError: - asyncio = None class EventIO(object): ''' - Class capable of read and write input events. - - This is used as as a base class for both device and uinput: - - - On ``device``, you read user-generated events (keys pressed, mouse moved, etc) and write feedback events (leds, beeps) - - - On ``uinput``, you write user-generated events (keys pressed, mouse moved, etc) and read feedback events (leds, beeps) + Base class for reading and writing input events. + + This class is used by :class:`InputDevice` and :class:`UInput`. + + - On, :class:`InputDevice` it used for reading user-generated events (e.g. + key presses, mouse movements) and writing feedback events (e.g. leds, + beeps). + + - On, :class:`UInput` it used for writing user-generated events (e.g. + key presses, mouse movements) and reading feedback events (e.g. leds, + beeps). ''' def fileno(self): ''' Return the file descriptor to the open event device. This makes - it possible to pass instances directly to :func:`select.select()` and + it possible to pass instances directly to :func:`select.select()` and :class:`asyncore.file_dispatcher`. ''' return self.fd def read_loop(self): ''' - Enter an endless loop that yields input events. - - The returned iterator is compatible with `async for` syntax + Enter an endless :func:`select.select()` loop that yields input events. ''' - class ReadIterator: - def __init__(self, device): - self.current_batch = iter(()) - self.device = device - - # Standard iterator - def __iter__(self): - return self - - def next(self): # Python 2.x - return self.__next__() - - def __next__(self): # Python 3.x - try: - # Try to read from previous batch - return next(self.current_batch) - except StopIteration: - # Fetch next batch - r, w, x = select.select([self.device.fd], [], []) - self.current_batch = self.device.read() - return next(self.current_batch) - - # Async iteration - Python 3.5 - if asyncio is not None: - @asyncio.coroutine - def __aiter__(self): - return self - - @asyncio.coroutine - def __anext__(self): - future = asyncio.Future() - try: - # Try to read from previous batch - future.set_result(next(self.current_batch)) - except StopIteration: - # Fetch next batch - def next_batch_ready(batch): - self.current_batch = batch.result() - future.set_result(next(self.current_batch)) - self.device.async_read().add_done_callback(next_batch_ready) - return future - - return ReadIterator(self) - - if asyncio is not None: - def _do_when_readable(self, callback): - loop = asyncio.get_event_loop() - def ready(): - loop.remove_reader(self.fileno()) - callback() - loop.add_reader(self.fileno(), ready) - - def async_read_one(self): - ''' - asyncio coroutine to read and return a single input event as an instance of - :class:`InputEvent `. - ''' - future = asyncio.Future() - self._do_when_readable(lambda: future.set_result(self.read_one())) - return future - - def async_read(self): - ''' - asyncio coroutine to read multiple input events from device. Return a generator object that - yields :class:`InputEvent ` instances. - ''' - future = asyncio.Future() - self._do_when_readable(lambda: future.set_result(self.read())) - return future + while True: + r, w, x = select([self.fd], [], []) + for event in self.read(): + yield event def read_one(self): ''' @@ -123,7 +56,7 @@ def read_one(self): if event: return InputEvent(*event) - + def read(self): ''' Read multiple input events from device. Return a generator object that @@ -134,12 +67,10 @@ def read(self): # events -> [(sec, usec, type, code, val), ...] events = _input.device_read_many(self.fd) - for i in events: - yield InputEvent(*i) - - + for event in events: + yield InputEvent(*event) - def _need_write(func): + def need_write(func): ''' Decorator that raises :class:`EvdevError` if there is no write access to the input device. @@ -176,7 +107,7 @@ def write_event(self, event): self.write(event.type, event.code, event.value) - @_need_write + @need_write def write(self, etype, code, value): ''' Inject an input event into the input subsystem. Events are diff --git a/evdev/eventio_async.py b/evdev/eventio_async.py new file mode 100644 index 0000000..47b0097 --- /dev/null +++ b/evdev/eventio_async.py @@ -0,0 +1,81 @@ +# encoding: utf-8 + +import asyncio + +from evdev import eventio + + +class EventIO(eventio.EventIO): + def _do_when_readable(self, callback): + loop = asyncio.get_event_loop() + def ready(): + loop.remove_reader(self.fileno()) + callback() + loop.add_reader(self.fileno(), ready) + + def async_read_one(self): + ''' + Asyncio coroutine to read and return a single input event as + an instance of :class:`InputEvent `. + ''' + future = asyncio.Future() + self._do_when_readable(lambda: future.set_result(self.read_one())) + return future + + def async_read(self): + ''' + Asyncio coroutine to read multiple input events from device. Return + a generator object that yields :class:`InputEvent ` + instances. + ''' + future = asyncio.Future() + self._do_when_readable(lambda: future.set_result(self.read())) + return future + + def read_loop(self): + ''' + Enter an endless loop that yields input events. + + The returned iterator is compatible with the ``async for`` syntax + ''' + return ReadItertor(self) + + +class ReadIterator(object): + def __init__(self, device): + self.current_batch = iter(()) + self.device = device + + # Standard iterator protocol. + def __iter__(self): + return self + + # Python 2.x compatibility. + def next(self): + return self.__next__() + + def __next__(self): + try: + # Read from the previous batch of events. + return next(self.current_batch) + except StopIteration: + r, w, x = select.select([self.device.fd], [], []) + self.current_batch = self.device.read() + return next(self.current_batch) + + @asyncio.coroutine + def __aiter__(self): + return self + + @asyncio.coroutine + def __anext__(self): + future = asyncio.Future() + try: + # Read from the previous batch of events. + future.set_result(next(self.current_batch)) + except StopIteration: + def next_batch_ready(batch): + self.current_batch = batch.result() + future.set_result(next(self.current_batch)) + self.device.async_read().add_done_callback(next_batch_ready) + return future From 7c57f5fa0f9061f445cf9fae4978c67dc4641d78 Mon Sep 17 00:00:00 2001 From: Paulo Costa Date: Sat, 6 Feb 2016 18:23:39 -0200 Subject: [PATCH 042/270] Fixing some imports --- evdev/device.py | 4 ---- evdev/eventio.py | 1 - evdev/eventio_async.py | 3 ++- evdev/uinput.py | 7 ++++++- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/evdev/device.py b/evdev/device.py index ec3e0be..d84ea5e 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -1,16 +1,12 @@ # encoding: utf-8 import os -import fcntl -import select -import functools import collections from evdev import _input, ecodes, util from evdev.events import InputEvent try: - import asyncio from evdev.eventio_async import EventIO except ImportError: from evdev.eventio import EventIO diff --git a/evdev/eventio.py b/evdev/eventio.py index 2860a1b..b8a1471 100644 --- a/evdev/eventio.py +++ b/evdev/eventio.py @@ -4,7 +4,6 @@ import fcntl import functools import select -import collections from evdev import _input, _uinput, ecodes, util from evdev.events import InputEvent diff --git a/evdev/eventio_async.py b/evdev/eventio_async.py index 47b0097..fe9a3a9 100644 --- a/evdev/eventio_async.py +++ b/evdev/eventio_async.py @@ -1,6 +1,7 @@ # encoding: utf-8 import asyncio +import select from evdev import eventio @@ -38,7 +39,7 @@ def read_loop(self): The returned iterator is compatible with the ``async for`` syntax ''' - return ReadItertor(self) + return ReadIterator(self) class ReadIterator(object): diff --git a/evdev/uinput.py b/evdev/uinput.py index c06641a..fd52930 100644 --- a/evdev/uinput.py +++ b/evdev/uinput.py @@ -7,7 +7,12 @@ from evdev import _uinput from evdev import ecodes, util, device from evdev.events import InputEvent -from evdev.eventio import EventIO + +try: + from evdev.eventio_async import EventIO +except ImportError: + from evdev.eventio import EventIO + class UInputError(Exception): pass From a246054c1a7ce1175ac06e770129d7789099dd09 Mon Sep 17 00:00:00 2001 From: Paulo Costa Date: Sat, 6 Feb 2016 19:30:30 -0200 Subject: [PATCH 043/270] Support setting physical path ("phys") on UInput --- evdev/uinput.c | 24 ++++++++++++++++++++++++ evdev/uinput.py | 16 ++++++++++++---- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/evdev/uinput.c b/evdev/uinput.c index 530e270..791d3a9 100644 --- a/evdev/uinput.c +++ b/evdev/uinput.c @@ -47,6 +47,27 @@ uinput_open(PyObject *self, PyObject *args) } +static PyObject * +uinput_set_phys(PyObject *self, PyObject *args) +{ + int fd; + const char* phys; + + int ret = PyArg_ParseTuple(args, "is", &fd, &phys); + if (!ret) return NULL; + + if (ioctl(fd, UI_SET_PHYS, phys) < 0) + goto on_err; + + Py_RETURN_NONE; + + on_err: + _uinput_close(fd); + PyErr_SetFromErrno(PyExc_IOError); + return NULL; +} + + static PyObject * uinput_create(PyObject *self, PyObject *args) { int fd, len, i, abscode; @@ -205,6 +226,9 @@ static PyMethodDef MethodTable[] = { { "enable", uinput_enable_event, METH_VARARGS, "Enable a type of event."}, + { "set_phys", uinput_set_phys, METH_VARARGS, + "Set physical path"}, + { NULL, NULL, 0, NULL} }; diff --git a/evdev/uinput.py b/evdev/uinput.py index c06641a..1a222b9 100644 --- a/evdev/uinput.py +++ b/evdev/uinput.py @@ -9,6 +9,7 @@ from evdev.events import InputEvent from evdev.eventio import EventIO + class UInputError(Exception): pass @@ -28,7 +29,7 @@ def __init__(self, events=None, name='py-evdev-uinput', vendor=0x1, product=0x1, version=0x1, bustype=0x3, - devnode='/dev/uinput'): + devnode='/dev/uinput', phys='py-evdev-uinput'): ''' Arguments --------- @@ -52,6 +53,9 @@ def __init__(self, bustype bustype identifier. + phys + physical path. + Note ---- If you do not specify any events, the uinput device will be able @@ -63,6 +67,7 @@ def __init__(self, self.product = product #: Device product identifier. self.version = version #: Device version identifier. self.bustype = bustype #: Device bustype - e.g. ``BUS_USB``. + self.phys = phys #: Uinput device physical path. self.devnode = devnode #: Uinput device node - e.g. ``/dev/uinput/``. if not events: @@ -76,6 +81,9 @@ def __init__(self, #: Write-only, non-blocking file descriptor to the uinput device node. self.fd = _uinput.open(devnode) + + # Set phys name + _uinput.set_phys(self.fd, phys) # Set device capabilities. for etype, codes in events.items(): @@ -108,17 +116,17 @@ def __exit__(self, type, value, tb): def __repr__(self): # TODO: v = (repr(getattr(self, i)) for i in - ('name', 'bustype', 'vendor', 'product', 'version')) + ('name', 'bustype', 'vendor', 'product', 'version', 'phys')) return '{}({})'.format(self.__class__.__name__, ', '.join(v)) def __str__(self): - msg = ('name "{}", bus "{}", vendor "{:04x}", product "{:04x}", version "{:04x}"\n' + msg = ('name "{}", bus "{}", vendor "{:04x}", product "{:04x}", version "{:04x}", phys "{}"\n' 'event types: {}') evtypes = [i[0] for i in self.capabilities(True).keys()] msg = msg.format(self.name, ecodes.BUS[self.bustype], self.vendor, self.product, - self.version, ' '.join(evtypes)) + self.version, self.phys, ' '.join(evtypes)) return msg From 7e35375feb369d098ef5414b85ce6d78c0493c8e Mon Sep 17 00:00:00 2001 From: Paulo Costa Date: Sat, 6 Feb 2016 20:59:44 -0200 Subject: [PATCH 044/270] async version of `read_loop()` must stop on errors. If I unplug a device, the loop should end with an error, instead of blocking forever. --- evdev/eventio_async.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/evdev/eventio_async.py b/evdev/eventio_async.py index fe9a3a9..57c310a 100644 --- a/evdev/eventio_async.py +++ b/evdev/eventio_async.py @@ -76,7 +76,10 @@ def __anext__(self): future.set_result(next(self.current_batch)) except StopIteration: def next_batch_ready(batch): - self.current_batch = batch.result() - future.set_result(next(self.current_batch)) + try: + self.current_batch = batch.result() + future.set_result(next(self.current_batch)) + except Exception as e: + future.set_exception(e) self.device.async_read().add_done_callback(next_batch_ready) return future From 17cafad1cf0b266f026ac1cc9d3d0b77b1e25b1e Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sat, 6 Feb 2016 23:13:14 +0100 Subject: [PATCH 045/270] Remove unused ecodes.c generation script --- evdev/ecodes.sh | 76 ------------------------------------------------- 1 file changed, 76 deletions(-) delete mode 100755 evdev/ecodes.sh diff --git a/evdev/ecodes.sh b/evdev/ecodes.sh deleted file mode 100755 index 8408e3f..0000000 --- a/evdev/ecodes.sh +++ /dev/null @@ -1,76 +0,0 @@ -#!/bin/bash - -# Generate a Python extension module that exports macros from -# /usr/include/linux/input.h - -# This script is obsolete. Please use `python -m evdev.genecodes`. - -header=${1:-/usr/include/linux/input.h} -[[ ! -e $header ]] && echo "no such file: $header" && exit 1 - - -function codes () { - awk ' - /#define +(KEY|ABS|REL|SW|MSC|LED|BTN|REP|SND|ID|EV|BUS|SYN|FF)_/ { - print " PyModule_AddIntMacro(m, "$2");" - }' ${header} -} - -cat << EOF -#include -#include - -/* Automatically generated by evdev/ecodes.sh */ - -#define MODULE_NAME "_ecodes" -#define MODULE_HELP "linux/input.h macros" - -static PyMethodDef MethodTable[] = { - { NULL, NULL, 0, NULL} -}; - -#if PY_MAJOR_VERSION >= 3 -static struct PyModuleDef moduledef = { - PyModuleDef_HEAD_INIT, - MODULE_NAME, - MODULE_HELP, - -1, /* m_size */ - MethodTable, /* m_methods */ - NULL, /* m_reload */ - NULL, /* m_traverse */ - NULL, /* m_clear */ - NULL, /* m_free */ -}; -#endif - -static PyObject * -moduleinit(void) -{ - -#if PY_MAJOR_VERSION >= 3 - PyObject* m = PyModule_Create(&moduledef); -#else - PyObject* m = Py_InitModule3(MODULE_NAME, MethodTable, MODULE_HELP); -#endif - - if (m == NULL) return NULL; - -$(codes) - - return m; -} - -#if PY_MAJOR_VERSION >= 3 -PyMODINIT_FUNC -PyInit__ecodes(void) -{ - return moduleinit(); -} -#else -PyMODINIT_FUNC -init_ecodes(void) -{ - moduleinit(); -} -#endif -EOF From e06c9cf4b21dac56c16530f766660d24ddeb88df Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 7 Feb 2016 00:14:03 +0100 Subject: [PATCH 046/270] Make the evtest program part of the evdev package - Make evtest work with multiple devices. - Add the -g/--grab option. - Remove the unused type and value arguments. --- bin/evtest.py | 103 ---------------------------------- evdev/evtest.py | 144 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+), 103 deletions(-) delete mode 100755 bin/evtest.py create mode 100755 evdev/evtest.py diff --git a/bin/evtest.py b/bin/evtest.py deleted file mode 100755 index c13f0ee..0000000 --- a/bin/evtest.py +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env python -# encoding: utf-8 - -''' -evdev example - input device event monitor -''' - - -from sys import argv, exit -from select import select -from evdev import ecodes, InputDevice, list_devices, AbsInfo - - -usage = 'usage: evtest [ ]' -evfmt = 'time {:<16} type {} ({}), code {:<4} ({}), value {}' -device_dir = '/dev/input/' -query_type = None -query_value = None - - -def select_device(): - '''Select a device from the list of accessible input devices.''' - - devices = [InputDevice(i) for i in reversed(list_devices(device_dir))] - if not devices: - print('error: no input devices found (do you have rw permission on /dev/input/*?)') - exit(1) - - dev_fmt = '{0:<3} {1.fn:<20} {1.name:<35} {1.phys}' - dev_lns = [dev_fmt.format(n, d) for n, d in enumerate(devices)] - - print('ID {:<20} {:<35} {}'.format('Device', 'Name', 'Phys')) - print('-' * len(max(dev_lns, key=len))) - print('\n'.join(dev_lns)) - print('') - - choice = input('Select device [0-{}]:'.format(len(dev_lns)-1)) - return devices[int(choice)] - - -def print_event(e): - if e.type == ecodes.EV_SYN: - if e.code == ecodes.SYN_MT_REPORT: - print('time {:<16} +++++++++ {} ++++++++'.format(e.timestamp(), ecodes.SYN[e.code])) - else: - print('time {:<16} --------- {} --------'.format(e.timestamp(), ecodes.SYN[e.code])) - else: - if e.type in ecodes.bytype: - codename = ecodes.bytype[e.type][e.code] - else: - codename = '?' - - print(evfmt.format(e.timestamp(), e.type, ecodes.EV[e.type], e.code, codename, e.value)) - - -if len(argv) == 1: - device = select_device() - -elif len(argv) == 2: - device = InputDevice(argv[1]) - -elif len(argv) == 4: - device = InputDevice(argv[1]) - query_type = argv[2] - query_value = argv[3] -else: - print(usage) - exit(1) - -capabs = device.capabilities(verbose=True) - -print('Device name: {.name}'.format(device)) -print('Device info: {.info}'.format(device)) -print('Repeat settings: {}\n'.format(device.repeat)) - -if ('EV_LED', ecodes.EV_LED) in capabs: - leds = ','.join(i[0] for i in device.leds(True)) - print('Active LEDs: %s' % leds) - -active_keys = ','.join(k[0] for k in device.active_keys(True)) -print('Active keys: %s\n' % active_keys) - -print('Device capabilities:') -for type, codes in capabs.items(): - print(' Type {} {}:'.format(*type)) - for i in codes: - # i <- ('BTN_RIGHT', 273) or (['BTN_LEFT', 'BTN_MOUSE'], 272) - if isinstance(i[1], AbsInfo): - print(' Code {:<4} {}:'.format(*i[0])) - print(' {}'.format(i[1])) - else: - # multiple names may resolve to one value - s = ', '.join(i[0]) if isinstance(i[0], list) else i[0] - print(' Code {:<4} {}'.format(s, i[1])) - print('') - - -print('Listening for events ...\n') -while True: - r, w, e = select([device], [], []) - - for ev in device.read(): - print_event(ev) diff --git a/evdev/evtest.py b/evdev/evtest.py new file mode 100755 index 0000000..262720c --- /dev/null +++ b/evdev/evtest.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python +# encoding: utf-8 + +''' +Usage: evtest [options] [, ...] + +Input device enumerator and event monitor. + +Running evtest without any arguments will let you select +from a list of all readable input devices. + +Options: + -h, --help Show this help message and exit. + -c, --capabilities List device capabilities and exit. + -g, --grab Other applications will not receive events from + the selected devices while evtest is running. + +Examples: + evtest /dev/input/event0 /dev/input/event1 +''' + + +from __future__ import print_function + +import sys +import select +import optparse + +from evdev import ecodes, list_devices, AbsInfo, InputDevice + + +def parseopt(): + parser = optparse.OptionParser(add_help_option=False) + parser.add_option('-h', '--help', action='store_true') + parser.add_option('-g', '--grab', action='store_true') + parser.add_option('-c', '--capabilities', action='store_true') + return parser.parse_args() + + +def main(): + opts, devices = parseopt() + if opts.help: + print(__doc__.strip()) + return 0 + + if not devices: + devices = select_devices() + else: + devices = [InputDevice(path) for path in devices] + + if opts.capabilities: + for device in devices: + print_capabilities(device) + return 0 + + if opts.grab: + for device in devices: + device.grab() + + print('Listening for events ...\n') + fd_to_device = {dev.fd: dev for dev in devices} + while True: + r, w, e = select.select(fd_to_device, [], []) + + for fd in r: + for event in fd_to_device[fd].read(): + print_event(event) + + +def select_devices(device_dir='/dev/input'): + ''' + Select one or more devices from a list of accessible input devices. + ''' + + devices = reversed(list_devices(device_dir)) + devices = [InputDevice(path) for path in devices] + if not devices: + msg = 'error: no input devices found (do you have rw permission on %s/*?)' + print(msg % device_dir, file=sys.stderr) + sys.exit(1) + + dev_format = '{0:<3} {1.fn:<20} {1.name:<35} {1.phys}' + dev_lines = [dev_format.format(n, d) for n, d in enumerate(devices)] + + print('ID {:<20} {:<35} {}'.format('Device', 'Name', 'Phys')) + print('-' * len(max(dev_lines, key=len))) + print('\n'.join(dev_lines)) + print() + + choice = input('Select devices [0-%s]: ' % (len(dev_lines)-1)) + choice = choice.split() + + print() + return [devices[int(num)] for num in choice] + + +def print_capabilities(device): + capabilities = device.capabilities(verbose=True) + + print('Device name: {.name}'.format(device)) + print('Device info: {.info}'.format(device)) + print('Repeat settings: {}\n'.format(device.repeat)) + + if ('EV_LED', ecodes.EV_LED) in capabilities: + leds = ','.join(i[0] for i in device.leds(True)) + print('Active LEDs: %s' % leds) + + active_keys = ','.join(k[0] for k in device.active_keys(True)) + print('Active keys: %s\n' % active_keys) + + print('Device capabilities:') + for type, codes in capabilities.items(): + print(' Type {} {}:'.format(*type)) + for code in codes: + # code <- ('BTN_RIGHT', 273) or (['BTN_LEFT', 'BTN_MOUSE'], 272) + if isinstance(code[1], AbsInfo): + print(' Code {:<4} {}:'.format(*code[0])) + print(' {}'.format(code[1])) + else: + # Multiple names may resolve to one value. + s = ', '.join(code[0]) if isinstance(code[0], list) else code[0] + print(' Code {:<4} {}'.format(s, code[1])) + print('') + + +def print_event(e): + if e.type == ecodes.EV_SYN: + if e.code == ecodes.SYN_MT_REPORT: + msg = 'time {:<16} +++++++++ {} ++++++++' + else: + msg = 'time {:<16} --------- {} --------' + print(msg.format(e.timestamp(), ecodes.SYN[e.code])) + else: + if e.type in ecodes.bytype: + codename = ecodes.bytype[e.type][e.code] + else: + codename = '?' + + evfmt = 'time {:<16} type {} ({}), code {:<4} ({}), value {}' + print(evfmt.format(e.timestamp(), e.type, ecodes.EV[e.type], e.code, codename, e.value)) + + +if __name__ == '__main__': + sys.exit(main()) From 2069c9adfee8ded8605453e72b88e5e15cda9221 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 7 Feb 2016 17:53:33 +0100 Subject: [PATCH 047/270] Rename bin/ to examples --- {bin => examples}/udev-example.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {bin => examples}/udev-example.py (100%) diff --git a/bin/udev-example.py b/examples/udev-example.py similarity index 100% rename from bin/udev-example.py rename to examples/udev-example.py From 0815b82980163b7c5c5e918314d8949b4d2f0405 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 7 Feb 2016 17:58:24 +0100 Subject: [PATCH 048/270] Use bumpversion to manage version bumping --- docs/conf.py | 4 +--- requirements-dev.txt | 1 + setup.cfg | 13 +++++++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 setup.cfg diff --git a/docs/conf.py b/docs/conf.py index 0520a4f..d9839c1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -8,8 +8,6 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.insert(0, os.path.abspath('..')) -from setup import kw # Trick autodoc into running without having built the extension modules. if on_rtd: @@ -61,7 +59,7 @@ # built documents. # # The full version, including alpha/beta/rc tags. -release = kw['version'] +release = '0.5.0' # The short X.Y version. version = release diff --git a/requirements-dev.txt b/requirements-dev.txt index 8bc0fb4..37f9590 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,2 +1,3 @@ pytest>=2.6.4 Sphinx>=1.3.5 +bumpversion>=0.5.3 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..0385b36 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,13 @@ +[bumpversion] +current_version = 0.5.0 +message = Bump version: {current_version} -> {new_version} +commit = True +tag = True + +[flake8] +ignore = W191,E302,E265,E241,F403,E401 +max-line-length = 110 + +[bumpversion:file:setup.py] + +[bumpversion:file:docs/conf.py] From 60b590a66d7e4e1a9b141d4b8c855bfffa7087da Mon Sep 17 00:00:00 2001 From: Paulo Costa Date: Sat, 6 Feb 2016 20:42:06 -0200 Subject: [PATCH 049/270] Added docs about `asyncio` support --- docs/apidoc.rst | 11 ++++++++ docs/tutorial.rst | 72 +++++++++++++++++++++++++++++------------------ 2 files changed, 56 insertions(+), 27 deletions(-) diff --git a/docs/apidoc.rst b/docs/apidoc.rst index d846b14..9a9f259 100644 --- a/docs/apidoc.rst +++ b/docs/apidoc.rst @@ -10,6 +10,17 @@ API Reference :exclude-members: __dict__, __str__, __module__, __del__, __slots__, __repr__ :member-order: bysource + +``eventio`` +============ + +.. automodule:: evdev.eventio_async + :members: EventIO + :undoc-members: + :special-members: + :exclude-members: __dict__, __str__, __module__, __del__, __slots__, __repr__ + :member-order: bysource + ``device`` ============ diff --git a/docs/tutorial.rst b/docs/tutorial.rst index e1ecd08..7d805ae 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -158,6 +158,34 @@ This can also be achieved using the selectors_ module in Python 3.4: for event in device.read(): print(event) + +Yet another possibility is asyncio in Python 3.4: + +:: + + import asyncio + from evdev import list_devices, InputDevice, categorize + + # Python 3.5 syntax, using "async for" + async def dump_events(device): + async for ev in device.read_loop(): + print(device.name, categorize(ev)) + + ## Python 3.4 syntax, using "yield from" in an infinite loop + #@asyncio.coroutine + #def dump_events(device): + # while True: + # evs = yield from device.async_read() + # for ev in evs: + # print(device.name, categorize(ev)) + + for file in list_devices(): + device = InputDevice(file) + asyncio.ensure_future( dump_events( device ) ) + + asyncio.get_event_loop().run_forever() + + Accessing evdev constants ========================= @@ -206,37 +234,27 @@ Reading events with asyncore Reading events with asyncio =========================== -An example program that prints the number of events by type every 5 -seconds: +To read a single event, you can use device.async_read_one() :: - import asyncio + event = await dev.async_read_one() + +To read a batch of events, you can use device.async_read(): + +:: + + ev_batch = await dev.async_read() + for ev in ev_batch: + print(categorize(ev)) - from collections import Counter - from functools import partial - from evdev import InputDevice, ecodes - - mouse = InputDevice('/dev/input/event1') - keybd = InputDevice('/dev/input/event2') - counter = Counter() - - def read_events(device): - events = device.read() - counter.update([ev.type for ev in events]) - - @asyncio.coroutine - def summarize(): - while True: - yield from asyncio.sleep(5) - print('Number of events by type in the last 5 seconds:') - print({ecodes.EV[k]: v for k,v in counter.items()}) - counter.clear() - - loop = asyncio.get_event_loop() - loop.add_reader(mouse, partial(read_events, mouse)) - loop.add_reader(keybd, partial(read_events, keybd)) - loop.run_until_complete(summarize()) +To read all events in an infinite loop, use dev.read_loop() and `async for`: + +:: + async for ev in dev.read_loop(): + print(categorize(ev)) + +See "Reading events from multiple devices" for a complete example using asyncio. Getting exclusive access to a device From 267147083a7c062371be5eb76977683f4fce4c97 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 7 Feb 2016 19:16:08 +0100 Subject: [PATCH 050/270] Do not overwrite read_loop when using asyncio --- evdev/eventio_async.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/evdev/eventio_async.py b/evdev/eventio_async.py index 57c310a..6e745d7 100644 --- a/evdev/eventio_async.py +++ b/evdev/eventio_async.py @@ -33,11 +33,11 @@ def async_read(self): self._do_when_readable(lambda: future.set_result(self.read())) return future - def read_loop(self): + def read_iter(self): ''' - Enter an endless loop that yields input events. + Return an iterator that yields input events. This iterator is + compatible with the ``async for`` syntax - The returned iterator is compatible with the ``async for`` syntax ''' return ReadIterator(self) From f1dc216df2e3bb4fd6e50ca2e6b21b8a6654ff87 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 7 Feb 2016 20:08:07 +0100 Subject: [PATCH 051/270] Fix import --- evdev/eventio.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evdev/eventio.py b/evdev/eventio.py index b8a1471..152bdf9 100644 --- a/evdev/eventio.py +++ b/evdev/eventio.py @@ -2,8 +2,8 @@ import os import fcntl -import functools import select +import functools from evdev import _input, _uinput, ecodes, util from evdev.events import InputEvent @@ -38,7 +38,7 @@ def read_loop(self): ''' while True: - r, w, x = select([self.fd], [], []) + r, w, x = select.select([self.fd], [], []) for event in self.read(): yield event From 936a1cafa7084157b11e3c4c523ce8e8c4bf3429 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 7 Feb 2016 20:18:03 +0100 Subject: [PATCH 052/270] Sort devices numerically --- evdev/evtest.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/evdev/evtest.py b/evdev/evtest.py index 262720c..7097950 100755 --- a/evdev/evtest.py +++ b/evdev/evtest.py @@ -22,6 +22,7 @@ from __future__ import print_function +import re import sys import select import optparse @@ -72,7 +73,11 @@ def select_devices(device_dir='/dev/input'): Select one or more devices from a list of accessible input devices. ''' - devices = reversed(list_devices(device_dir)) + def devicenum(device_path): + digits = re.findall('\d+$', device_path) + return [int(i) for i in digits] + + devices = sorted(list_devices(device_dir), key=devicenum) devices = [InputDevice(path) for path in devices] if not devices: msg = 'error: no input devices found (do you have rw permission on %s/*?)' From 96629bc232ff04bed2c1d2d1cbeef7941248fc3e Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 7 Feb 2016 20:41:02 +0100 Subject: [PATCH 053/270] Improve the asyncio examples --- docs/conf.py | 2 +- docs/tutorial.rst | 59 ++++++++++++++++++++++++++++++----------------- 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index d9839c1..62010bf 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -249,7 +249,7 @@ 'Miscellaneous'), ] -intersphinx_mapping = {'python': ('http://docs.python.org/3.5', None)} +intersphinx_mapping = {'python': ('http://docs.python.org/3', None)} # Documents to append as an appendix to all manuals. #texinfo_appendices = [] diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 7d805ae..d1ff176 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -1,6 +1,7 @@ Tutorial -------- + Listing accessible event devices ================================ @@ -158,33 +159,49 @@ This can also be achieved using the selectors_ module in Python 3.4: for event in device.read(): print(event) - -Yet another possibility is asyncio in Python 3.4: + +Yet another possibility is the :mod:`asyncio` module from Python 3.4: :: - import asyncio - from evdev import list_devices, InputDevice, categorize + import asyncio, evdev + + @asyncio.coroutine + def print_events(device): + while True: + events = yield from device.async_read() + for event in events: + print(device.fn, evdev.categorize(event), sep=': ') + + mouse = evdev.InputDevice('/dev/input/eventX') + keybd = evdev.InputDevice('/dev/input/eventY') + + for device in mouse, keybd: + asyncio.async(print_events(device)) - # Python 3.5 syntax, using "async for" - async def dump_events(device): - async for ev in device.read_loop(): - print(device.name, categorize(ev)) + loop = asyncio.get_event_loop() + loop.run_forever() - ## Python 3.4 syntax, using "yield from" in an infinite loop - #@asyncio.coroutine - #def dump_events(device): - # while True: - # evs = yield from device.async_read() - # for ev in evs: - # print(device.name, categorize(ev)) +Since Python 3.5, the `async/await +`_ syntax makes this +even simpler: - for file in list_devices(): - device = InputDevice(file) - asyncio.ensure_future( dump_events( device ) ) +:: + + import asyncio, evdev + + mouse = evdev.InputDevice('/dev/input/event4') + keybd = evdev.InputDevice('/dev/input/event5') - asyncio.get_event_loop().run_forever() + async def print_events(device): + async for event in device.read_iter(): + print(device.fn, evdev.categorize(event), sep=': ') + for device in mouse, keybd: + asyncio.ensure_future(print_events(device)) + + loop = asyncio.get_event_loop() + loop.run_forever() Accessing evdev constants @@ -238,7 +255,7 @@ To read a single event, you can use device.async_read_one() :: - event = await dev.async_read_one() + event = await dev.async_read_one() To read a batch of events, you can use device.async_read(): @@ -253,7 +270,7 @@ To read all events in an infinite loop, use dev.read_loop() and `async for`: :: async for ev in dev.read_loop(): print(categorize(ev)) - + See "Reading events from multiple devices" for a complete example using asyncio. From 7dc02f15f2a5e7a1612d218cc81807254a789193 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 7 Feb 2016 21:59:41 +0100 Subject: [PATCH 054/270] Documentation improvements --- LICENSE | 2 +- docs/apidoc.rst | 12 +++- docs/index.rst | 95 +++++++++++++++++++++-------- docs/tutorial.rst | 151 +++++++++++++++++++++------------------------- 4 files changed, 150 insertions(+), 110 deletions(-) diff --git a/LICENSE b/LICENSE index 52683e8..ce3a1f7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2012-2015 Georgi Valkov. All rights reserved. +Copyright (c) 2012-2016 Georgi Valkov. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are diff --git a/docs/apidoc.rst b/docs/apidoc.rst index 9a9f259..ba59e90 100644 --- a/docs/apidoc.rst +++ b/docs/apidoc.rst @@ -14,13 +14,23 @@ API Reference ``eventio`` ============ +.. automodule:: evdev.eventio + :members: EventIO + :undoc-members: + :special-members: + :exclude-members: __dict__, __str__, __module__, __del__, __slots__, __repr__ + :member-order: bysource + +``eventio_async`` +================= + .. automodule:: evdev.eventio_async :members: EventIO :undoc-members: :special-members: :exclude-members: __dict__, __str__, __module__, __del__, __slots__, __repr__ :member-order: bysource - + ``device`` ============ diff --git a/docs/index.rst b/docs/index.rst index f00b2f0..2de41d6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -11,9 +11,10 @@ Links
-The latest stable version of *python-evdev* can be installed from -pypi_, provided that you have gcc/clang, pip_ and the Python and Linux -development headers installed on your system. Installing them is -distribution specific and usually falls in one of these categories: +The latest stable version of *python-evdev* can be installed from pypi_, +provided that you have gcc/clang, pip_ and the Python and Linux development +headers installed on your system. Installing them is distribution specific and +typically falls in one of the following categories: On a Debian compatible OS: @@ -99,31 +102,33 @@ Installing *python-evdev* with pip_: $ sudo pip install evdev -**Listing accessible event devices:** +Listing accessible event devices: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: - >>> from evdev import InputDevice, list_devices + >>> import evdev - >>> devices = [InputDevice(fn) for fn in list_devices()] - >>> for dev in devices: - ... print(dev.fn, dev.name, dev.phys) + >>> devices = [evdev.InputDevice(fn) for fn in evdev.list_devices()] + >>> for device in devices: + ... print(device.fn, device.name, device.phys) /dev/input/event1 Dell Dell USB Keyboard usb-0000:00:12.1-2/input0 /dev/input/event0 Dell USB Optical Mouse usb-0000:00:12.0-2/input0 -**Reading events from a device:** +Reading events from a device: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: - >>> from evdev import InputDevice, categorize, ecodes + >>> import evdev - >>> dev = InputDevice('/dev/input/event1') - >>> print(dev) + >>> device = evdev.InputDevice('/dev/input/event1') + >>> print(device) device /dev/input/event1, name "Dell Dell USB Keyboard", phys "usb-0000:00:12.1-2/input0" - >>> for event in dev.read_loop(): - ... if event.type == ecodes.EV_KEY: + >>> for event in device.read_loop(): + ... if event.type == evdev.ecodes.EV_KEY: ... print(categorize(event)) ... # pressing 'a' and holding 'space' key event at 1337016188.396030, 30 (KEY_A), down @@ -132,7 +137,27 @@ Installing *python-evdev* with pip_: key event at 1337016190.275396, 57 (KEY_SPACE), hold key event at 1337016190.284160, 57 (KEY_SPACE), up -**Accessing evdev constants:** +Reading events using async/await: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +*Python-evdev* proudly supports the new `async/await`_ syntax in Python 3.5: + +:: + + import asyncio, evdev + + async def print_events(device): + async for event in device.read_iter(): + print(device.fn, evdev.categorize(event), sep=': ') + + device = evdev.InputDevice('/dev/input/event4') + asyncio.ensure_future(print_events(device)) + + loop = asyncio.get_event_loop() + loop.run_forever() + +Accessing evdev constants: +^^^^^^^^^^^^^^^^^^^^^^^^^^ :: @@ -147,7 +172,18 @@ Installing *python-evdev* with pip_: >>> ecodes.KEY[152] # a single value may correspond to multiple codes ... ['KEY_COFFEE', 'KEY_SCREENLOCK'] -**Further information:** +Listing and monitoring input devices: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The *python-evdev* package also comes with a small command-line program for +listing and monitoring input devices: + +.. code-block:: bash + + $ python -m evdev.evtest + +More information: +^^^^^^^^^^^^^^^^^ - Read the full :doc:`tutorial `. @@ -175,6 +211,14 @@ Installing *python-evdev* with pip_: - :class:`SynEvent ` + * :mod:`eventio ` + + - :class:`EventIO ` + + * :mod:`eventio_async ` + + - :class:`EventIO ` + * :mod:`util ` - :class:`list_devices() ` @@ -196,7 +240,7 @@ News ---- .. include:: news.rst -See :doc:`changelog ` for a full list of changes. +Please refer to the :doc:`changelog ` for a full list of changes. License @@ -212,4 +256,5 @@ The :mod:`evdev` package is released under the terms of the `Revised BSD License .. _pypi: http://pypi.python.org/pypi/evdev .. _github: https://github.com/gvalkov/python-evdev .. _pip: http://pip.readthedocs.org/en/latest/installing.html -.. _example: https://github.com/gvalkov/python-evdev/tree/master/bin +.. _example: https://github.com/gvalkov/python-evdev/tree/master/examples +.. _`async/await`: https://docs.python.org/3/library/asyncio-task.html diff --git a/docs/tutorial.rst b/docs/tutorial.rst index d1ff176..ffb7bdf 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -1,17 +1,17 @@ -Tutorial --------- +.. contents:: Examples + :depth: 2 Listing accessible event devices ================================ :: - >>> from evdev import InputDevice, list_devices + >>> import evdev - >>> devices = [InputDevice(fn) for fn in list_devices()] - >>> for dev in devices: - ... print(dev.fn, dev.name, dev.phys) + >>> devices = [evdev.InputDevice(fn) for fn in evdev.list_devices()] + >>> for device in devices: + >>> print(device.fn, device.name, device.phys) /dev/input/event1 Dell Dell USB Keyboard usb-0000:00:12.1-2/input0 /dev/input/event0 Dell USB Optical Mouse usb-0000:00:12.0-2/input0 @@ -21,42 +21,42 @@ Listing device capabilities :: - >>> from evdev import InputDevice + >>> import evdev - >>> dev = InputDevice('/dev/input/event0') - >>> print(dev) + >>> device = evdev.InputDevice('/dev/input/event0') + >>> print(device) device /dev/input/event0, name "Dell USB Optical Mouse", phys "usb-0000:00:12.0-2/input0" - >>> dev.capabilities() + >>> device.capabilities() ... { 0: [0, 1, 2], 1: [272, 273, 274, 275], 2: [0, 1, 6, 8], 4: [4] } - >>> dev.capabilities(verbose=True) + >>> device.capabilities(verbose=True) ... { ('EV_SYN', 0): [('SYN_REPORT', 0), ('SYN_CONFIG', 1), ('SYN_MT_REPORT', 2)], ... ('EV_KEY', 1): [('BTN_MOUSE', 272), ('BTN_RIGHT', 273), ('BTN_MIDDLE', 274), ('BTN_SIDE', 275)], ... -Listing device capabilities for devices with absolute axes -========================================================== +Listing device capabilities (devices with absolute axes) +======================================================== :: - >>> from evdev import InputDevice + >>> import evdev - >>> dev = InputDevice('/dev/input/event7') - >>> print(dev) + >>> device = evdev.InputDevice('/dev/input/event7') + >>> print(device) device /dev/input/event7, name "Wacom Bamboo 2FG 4x5 Finger", phys "" - >>> dev.capabilities() + >>> device.capabilities() ... { 1: [272, 273, 277, 278, 325, 330, 333] , ... 3: [(0, AbsInfo(min=0, max=15360, fuzz=128, flat=0)), ... (1, AbsInfo(min=0, max=10240, fuzz=128, flat=0))] } - >>> dev.capabilities(verbose=True) + >>> device.capabilities(verbose=True) ... { ('EV_KEY', 1): [('BTN_MOUSE', 272), ('BTN_RIGHT', 273), ...], ... ('EV_ABS', 3): [(('ABS_X', 0), AbsInfo(min=0, max=15360, fuzz=128, flat=0)), ... (('ABS_Y', 1), AbsInfo(min=0, max=10240, fuzz=128, flat=0)),] } - >>> dev.capabilities(absinfo=False) + >>> device.capabilities(absinfo=False) ... { 1: [272, 273, 277, 278, 325, 330, 333], ... 3: [0, 1, 47, 53, 54, 57] } @@ -91,6 +91,8 @@ Getting currently active keys Reading events ============== +Reading events from a single device in an endless loop. + :: >>> from evdev import InputDevice, categorize, ecodes @@ -110,8 +112,40 @@ Reading events key event at 1337016190.284160, 57 (KEY_SPACE), up -Reading events from multiple devices -==================================== +Reading events (using :mod:`asyncore`) +====================================== + +:: + + >>> from asyncore import file_dispatcher, loop + >>> from evdev import InputDevice, categorize, ecodes + >>> dev = InputDevice('/dev/input/event1') + + >>> class InputDeviceDispatcher(file_dispatcher): + ... def __init__(self, device): + ... self.device = device + ... file_dispatcher.__init__(self, device) + ... + ... def recv(self, ign=None): + ... return self.device.read() + ... + ... def handle_read(self): + ... for event in self.recv(): + ... print(repr(event)) + + >>> InputDeviceDispatcher(dev) + >>> loop() + InputEvent(1337255905L, 358854L, 1, 30, 0L) + InputEvent(1337255905L, 358857L, 0, 0, 0L) + +.. note:: + + The :mod:`asyncore` module is deprecated in recent versions of Python. + Please consider using :mod:`asyncio`. + + +Reading events from multiple devices (using :mod:`select`) +========================================================== :: @@ -137,7 +171,10 @@ Reading events from multiple devices event at 1351116708.782237, code 02, type 01, val 01 -This can also be achieved using the selectors_ module in Python 3.4: +Reading events from multiple devices (using :mod:`selectors`) +============================================================= + +This can also be achieved using the :mod:`selectors` module in Python 3.4: :: @@ -160,6 +197,9 @@ This can also be achieved using the selectors_ module in Python 3.4: print(event) +Reading events from multiple devices (using :mod:`asyncio`) +============================================================= + Yet another possibility is the :mod:`asyncio` module from Python 3.4: :: @@ -182,9 +222,7 @@ Yet another possibility is the :mod:`asyncio` module from Python 3.4: loop = asyncio.get_event_loop() loop.run_forever() -Since Python 3.5, the `async/await -`_ syntax makes this -even simpler: +Since Python 3.5, the `async/await`_ syntax makes this even simpler: :: @@ -221,59 +259,6 @@ Accessing evdev constants ... ['KEY_COFFEE', 'KEY_SCREENLOCK'] -Reading events with asyncore -============================ - -:: - - >>> from asyncore import file_dispatcher, loop - >>> from evdev import InputDevice, categorize, ecodes - >>> dev = InputDevice('/dev/input/event1') - - >>> class InputDeviceDispatcher(file_dispatcher): - ... def __init__(self, device): - ... self.device = device - ... file_dispatcher.__init__(self, device) - ... - ... def recv(self, ign=None): - ... return self.device.read() - ... - ... def handle_read(self): - ... for event in self.recv(): - ... print(repr(event)) - - >>> InputDeviceDispatcher(dev) - >>> loop() - InputEvent(1337255905L, 358854L, 1, 30, 0L) - InputEvent(1337255905L, 358857L, 0, 0, 0L) - - -Reading events with asyncio -=========================== - -To read a single event, you can use device.async_read_one() - -:: - - event = await dev.async_read_one() - -To read a batch of events, you can use device.async_read(): - -:: - - ev_batch = await dev.async_read() - for ev in ev_batch: - print(categorize(ev)) - -To read all events in an infinite loop, use dev.read_loop() and `async for`: - -:: - async for ev in dev.read_loop(): - print(categorize(ev)) - -See "Reading events from multiple devices" for a complete example using asyncio. - - Getting exclusive access to a device ==================================== @@ -298,8 +283,8 @@ Associating classes with event types See :mod:`events ` for more information. -Injecting input events -====================== +Injecting input +=============== :: @@ -315,8 +300,8 @@ Injecting input events >>> ui.close() -Injecting events (2) -==================== +Injecting events (using a context manager) +========================================== :: @@ -359,4 +344,4 @@ Specifying ``uinput`` device options >>> ui.syn() -.. _selectors: https://docs.python.org/3/library/selectors.html +.. _`async/await`: https://docs.python.org/3/library/asyncio-task.html From 0d2a4a37adb45a886dbfbad10f7157fc7d5894f9 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 7 Feb 2016 22:56:51 +0100 Subject: [PATCH 055/270] Update changelog --- docs/changelog.rst | 101 +++++++++++++++++++++++++++------------------ 1 file changed, 60 insertions(+), 41 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 2824f66..7feefe3 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,13 +1,24 @@ Changelog ========= +In developement +^^^^^^^^^^^^^^^ + +- Asyncio and async/await support (many thanks to `@paulo-raca`_). +- Add the ability to set the `phys` of uinput devices (thanks `@paulo-raca`_). +- Add a generic :func:`InputDevice.set` method (thanks `@paulo-raca`_). +- Distribute evtest along with evtest. +- Fix issue with generating :mod:`ecodes.c` in recent kernels (``>= 4.4.0``). +- Fix absinfo item indexes in :func:`UInput.uinput_create()` (thanks `@forsenonlhaimaisentito`_). +- More robust comparison of :class:`InputDevice` objects (thanks `@isia`_). + 0.5.0 (Jun 16, 2015) ^^^^^^^^^^^^^^^^^^^^ - Write access to the input device is no longer mandatory. Evdev will first try to open the device for reading and writing and fallback to - read-only. Methods that require write access (e.g. ``set_led()``) - will raise ``EvdevError`` if the device is open only for reading. + read-only. Methods that require write access (e.g. :func:`set_led()`) + will raise :class:`EvdevError` if the device is open only for reading. 0.4.7 (Oct 07, 2014) ^^^^^^^^^^^^^^^^^^^^ @@ -21,47 +32,47 @@ Changelog - Fix install on Python 3.4 (works around issue21121_). -- Fix ``ioctl()`` requested buffer size (thanks Jakub Wojciech Klama). +- Fix :func:`ioctl()` requested buffer size (thanks Jakub Wojciech Klama). 0.4.5 (Jul 06, 2014) ^^^^^^^^^^^^^^^^^^^^ - Add method for returning a list of the currently active keys - - ``InputDevice.active_keys()`` (thanks `@spasche`_). + :func:`InputDevice.active_keys()` (thanks `@spasche`_). -- Fix a potential buffer overflow in ``ioctl_capabilities`` (thanks `@spasche`_). +- Fix a potential buffer overflow in :func:`ioctl_capabilities()` (thanks `@spasche`_). 0.4.4 (Jun 04, 2014) ^^^^^^^^^^^^^^^^^^^^ -- Calling ``InputDevice.read_one()`` should always return ``None``, +- Calling :func:`InputDevice.read_one()` should always return ``None``, when there is nothing to be read, even in case of a ``EAGAIN`` errno (thanks JPP). 0.4.3 (Dec 19, 2013) ^^^^^^^^^^^^^^^^^^^^ -- Silence ``OSError`` in destructor (thanks `@polyphemus`_). +- Silence :class:`OSError` in destructor (thanks `@polyphemus`_). -- Make ``InputDevice.close()`` work in cases in which stdin (fd 0) has - been closed (thanks `@polyphemus`_). +- Make :func:`InputDevice.close()` work in cases in which stdin (fd 0) + has been closed (thanks `@polyphemus`_). 0.4.2 (Dec 13, 2013) ^^^^^^^^^^^^^^^^^^^^ - Rework documentation and docstrings. -- Call ``InputDevice.close()`` from ``InputDevice.__del__()``. +- Call :func:`InputDevice.close()` from :func:`InputDevice.__del__()`. 0.4.1 (Jul 24, 2013) ^^^^^^^^^^^^^^^^^^^^ -- Fix reference counting in ``device_read``, ``device_read_many`` and - ``ioctl_capabilities``. +- Fix reference counting in :func:`InputDevice.device_read()`, +:func:`InputDevice.device_read_many()` and :func:`ioctl_capabilities`. 0.4.0 (Jul 01, 2013) ^^^^^^^^^^^^^^^^^^^^ -- Add ``FF_*`` and ``FF_STATUS`` codes to ``ecodes`` (thanks `@bgilbert`_). +- Add ``FF_*`` and ``FF_STATUS`` codes to :func:`ecodes` (thanks `@bgilbert`_). - Reverse event code mappings (``ecodes.{KEY,FF,REL,ABS}`` and etc.) will now map to a list of codes, whenever a value corresponds to @@ -72,29 +83,32 @@ Changelog >>> ecodes.KEY[30] ... 'KEY_A' -- Set the state of a LED through ``device.set_led()`` (thanks - `@accek`_). ``device.fd`` is opened in ``O_RDWR`` mode from now on. +- Set the state of a LED through :func:`InputDevice.set_led()` (thanks + `@accek`_). + +- Open :attr:`InputDevice.fd` in ``O_RDWR`` mode from now on. -- Fix segfault in ``device_read_many()`` (thanks `@bgilbert`_). +- Fix segfault in :func:`InputDevice.device_read_many()` (thanks `@bgilbert`_). 0.3.3 (May 29, 2013) ^^^^^^^^^^^^^^^^^^^^ -- Raise ``IOError`` from ``device_read()`` and ``device_read_many()`` when - ``read()`` fails. +- Raise :class:`IOError` from :func:`InputDevice.device_read()` and +:func:`InputDevice.device_read_many()` when :func:`InputDevice.read()` +fails. - Several stability and style changes (thank you debian code reviewers). 0.3.2 (Apr 05, 2013) ^^^^^^^^^^^^^^^^^^^^ -- Fix vendor id and product id order in ``DeviceInfo`` (thanks `@kived`_). +- Fix vendor id and product id order in :func:`DeviceInfo` (thanks `@kived`_). 0.3.1 (Nov 23, 2012) ^^^^^^^^^^^^^^^^^^^^ -- ``device.read()`` will return an empty tuple if the device has - nothing to offer (instead of segfaulting). +- :func:`InputDevice.read()` will return an empty tuple if the device + has nothing to offer (instead of segfaulting). - Exclude unnecessary package data in sdist and bdist. @@ -103,19 +117,20 @@ Changelog - Add ability to set/get auto-repeat settings with ``EVIOC{SG}REP``. -- Add ``device.version`` - the value of ``EVIOCGVERSION``. +- Add :func:`InputDevice.version` - the value of ``EVIOCGVERSION``. -- Add ``device.read_loop()``. +- Add :func:`InputDevice.read_loop()`. -- Add ``device.grab()`` and ``device.ungrab()`` - exposes ``EVIOCGRAB``. +- Add :func:`InputDevice.grab()` and :func:`InputDevice.ungrab()` - + exposes ``EVIOCGRAB``. -- Add ``device.leds`` - exposes ``EVIOCGLED``. +- Add :func:`InputDevice.leds` - exposes ``EVIOCGLED``. -- Replace ``DeviceInfo`` class with a namedtuple. +- Replace :class:`DeviceInfo` class with a namedtuple. -- Prevent ``device.read_one()`` from skipping events. +- Prevent :func:`InputDevice.read_one()` from skipping events. -- Rename ``AbsData`` to ``AbsInfo`` (as in ``struct input_absinfo``). +- Rename :class:`AbsData` to :class:`AbsInfo` (as in ``struct input_absinfo``). 0.2.0 (Aug 22, 2012) @@ -124,17 +139,17 @@ Changelog - Add the ability to set arbitrary device capabilities on uinput devices (defaults to all ``EV_KEY`` ecodes). -- Add ``UInput.device`` which is an open ``InputDevice`` to the - input device that uinput 'spawns'. +- Add :attr:`UInput.device` which is an open :class:`InputDevice` to + the input device that uinput 'spawns'. -- Add ``UInput.capabilities()`` which is just a shortcut to - ``UInput.device.capabilities()``. +- Add :func:`UInput.capabilities()` which is just a shortcut to + :func:`UInput.device.capabilities()`. -- Rename ``UInput.write()`` to ``UInput.write_event()``. +- Rename :func:`UInput.write()` to :func:`UInput.write_event()`. -- Add a simpler ``UInput.write(type, code, value)`` method. +- Add a simpler :func:`UInput.write(type, code, value)` method. -- Make all ``UInput`` constructor arguments optional (default +- Make all :func:`UInput` constructor arguments optional (default device name is now ``py-evdev-uinput``). - Add the ability to set ``absmin``, ``absmax``, ``absfuzz`` and @@ -143,26 +158,27 @@ Changelog - Remove the ``nophys`` argument - if a device fails the ``EVIOCGPHYS`` ioctl, phys will equal the empty string. -- Make ``InputDevice.capabilities()`` perform a ``EVIOCGABS`` ioctl - for devices that support ``EV_ABS`` and return that info wrapped in - an ``AbsData`` namedtuple. +- Make :func:`InputDevice.capabilities()` perform a ``EVIOCGABS`` + ioctl for devices that support ``EV_ABS`` and return that info + wrapped in an ``AbsData`` namedtuple. - Split ``ioctl_devinfo`` into ``ioctl_devinfo`` and ``ioctl_capabilities``. -- Split ``uinput_open()`` to ``uinput_open()`` and ``uinput_create()`` +- Split :func:`UInput.uinput_open()` to :func:`UInput.uinput_open()` + and :func:`UInput.uinput_create()` - Add more uinput usage examples and documentation. - Rewrite uinput tests. -- Remove ``mouserel`` and ``mouseabs`` from ``UInput``. +- Remove ``mouserel`` and ``mouseabs`` from :class:`UInput`. - Tie the sphinx version and release to the distutils version. - Set 'methods-before-attributes' sorting in the docs. -- Remove ``KEY_CNT`` and ``KEY_MAX`` from ``ecodes.keys``. +- Remove ``KEY_CNT`` and ``KEY_MAX`` from :func:`ecodes.keys`. 0.1.1 (May 18, 2012) @@ -184,5 +200,8 @@ Changelog .. _`@accek`: https://github.com/accek .. _`@kived`: https://github.com/kived .. _`@spasche`: https://github.com/spasche +.. _`@isia`: https://github.com/isia +.. _`@forsenonlhaimaisentito`: https://github.com/forsenonlhaimaisentito +.. _`@paulo-raca`: https://github.com/paulo-raca .. _issue21121: http://bugs.python.org/issue21121 From 765a576252349770b01ec3d57a2c86a26afe428a Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Mon, 8 Feb 2016 18:49:26 +0100 Subject: [PATCH 056/270] Rename read_iter() to async_read_loop() --- docs/index.rst | 2 +- docs/tutorial.rst | 2 +- evdev/eventio_async.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 2de41d6..aded681 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -147,7 +147,7 @@ Reading events using async/await: import asyncio, evdev async def print_events(device): - async for event in device.read_iter(): + async for event in device.async_read_loop(): print(device.fn, evdev.categorize(event), sep=': ') device = evdev.InputDevice('/dev/input/event4') diff --git a/docs/tutorial.rst b/docs/tutorial.rst index ffb7bdf..f645c71 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -232,7 +232,7 @@ Since Python 3.5, the `async/await`_ syntax makes this even simpler: keybd = evdev.InputDevice('/dev/input/event5') async def print_events(device): - async for event in device.read_iter(): + async for event in device.async_read_loop(): print(device.fn, evdev.categorize(event), sep=': ') for device in mouse, keybd: diff --git a/evdev/eventio_async.py b/evdev/eventio_async.py index 6e745d7..c71e455 100644 --- a/evdev/eventio_async.py +++ b/evdev/eventio_async.py @@ -33,7 +33,7 @@ def async_read(self): self._do_when_readable(lambda: future.set_result(self.read())) return future - def read_iter(self): + def async_read_loop(self): ''' Return an iterator that yields input events. This iterator is compatible with the ``async for`` syntax From 7fcd680739c6c8dd276f88eaffbdfa484bc3e4c6 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 14 Feb 2016 17:46:09 +0100 Subject: [PATCH 057/270] Bump version: 0.5.0 -> 0.6.0 --- docs/changelog.rst | 10 +++++----- docs/conf.py | 2 +- evdev/eventio_async.py | 2 +- setup.cfg | 3 ++- setup.py | 2 +- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 7feefe3..90500ab 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,16 +1,16 @@ Changelog ========= -In developement -^^^^^^^^^^^^^^^ +0.6.0 (Feb 14, 2015) +^^^^^^^^^^^^^^^^^^^^ - Asyncio and async/await support (many thanks to `@paulo-raca`_). -- Add the ability to set the `phys` of uinput devices (thanks `@paulo-raca`_). +- Add the ability to set the `phys` property of uinput devices (thanks `@paulo-raca`_). - Add a generic :func:`InputDevice.set` method (thanks `@paulo-raca`_). -- Distribute evtest along with evtest. +- Distribute the evtest script along with evdev. - Fix issue with generating :mod:`ecodes.c` in recent kernels (``>= 4.4.0``). - Fix absinfo item indexes in :func:`UInput.uinput_create()` (thanks `@forsenonlhaimaisentito`_). -- More robust comparison of :class:`InputDevice` objects (thanks `@isia`_). +- More robust comparison of :class:`InputDevice` objects (thanks `@isia`_). 0.5.0 (Jun 16, 2015) ^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/conf.py b/docs/conf.py index 62010bf..cfef4e5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -59,7 +59,7 @@ # built documents. # # The full version, including alpha/beta/rc tags. -release = '0.5.0' +release = '0.6.0' # The short X.Y version. version = release diff --git a/evdev/eventio_async.py b/evdev/eventio_async.py index c71e455..ef2259a 100644 --- a/evdev/eventio_async.py +++ b/evdev/eventio_async.py @@ -36,7 +36,7 @@ def async_read(self): def async_read_loop(self): ''' Return an iterator that yields input events. This iterator is - compatible with the ``async for`` syntax + compatible with the ``async for`` syntax. ''' return ReadIterator(self) diff --git a/setup.cfg b/setup.cfg index 0385b36..3d63651 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.5.0 +current_version = 0.6.0 message = Bump version: {current_version} -> {new_version} commit = True tag = True @@ -11,3 +11,4 @@ max-line-length = 110 [bumpversion:file:setup.py] [bumpversion:file:docs/conf.py] + diff --git a/setup.py b/setup.py index 30fea8c..48cce37 100755 --- a/setup.py +++ b/setup.py @@ -43,7 +43,7 @@ #----------------------------------------------------------------------------- kw = { 'name': 'evdev', - 'version': '0.5.0', + 'version': '0.6.0', 'description': 'Bindings to the Linux input handling subsystem', 'long_description': open(pjoin(here, 'README.rst')).read(), From ab518cdba5b528cec48e60d7d0a3c66140f48f62 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Tue, 8 Mar 2016 21:35:47 +0100 Subject: [PATCH 058/270] Small user-friendliness improvements to evtest --- evdev/evtest.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/evdev/evtest.py b/evdev/evtest.py index 7097950..c4068b9 100755 --- a/evdev/evtest.py +++ b/evdev/evtest.py @@ -58,7 +58,7 @@ def main(): for device in devices: device.grab() - print('Listening for events ...\n') + print('Listening for events (press ctrl-c to exit) ...') fd_to_device = {dev.fd: dev for dev in devices} while True: r, w, e = select.select(fd_to_device, [], []) @@ -92,11 +92,17 @@ def devicenum(device_path): print('\n'.join(dev_lines)) print() - choice = input('Select devices [0-%s]: ' % (len(dev_lines)-1)) - choice = choice.split() + choices = input('Select devices [0-%s]: ' % (len(dev_lines)-1)) - print() - return [devices[int(num)] for num in choice] + try: + choices = choices.split() + choices = [devices[int(num)] for num in choices] + except ValueError: + msg = 'error: invalid input - please enter one or more numbers separated by spaces' + print(msg, file=sys.stderr) + sys.exit(1) + + return choices def print_capabilities(device): @@ -146,4 +152,8 @@ def print_event(e): if __name__ == '__main__': - sys.exit(main()) + try: + ret = main() + except KeyboardInterrupt: + ret = 0 + sys.exit(ret) From 165788a726d569b44b3f5d531a19df0726090508 Mon Sep 17 00:00:00 2001 From: abhenson Date: Wed, 13 Apr 2016 10:52:33 +0100 Subject: [PATCH 059/270] Update changelog.rst just a typo --- docs/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 90500ab..b4656c4 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,7 +1,7 @@ Changelog ========= -0.6.0 (Feb 14, 2015) +0.6.0 (Feb 14, 2016) ^^^^^^^^^^^^^^^^^^^^ - Asyncio and async/await support (many thanks to `@paulo-raca`_). From 84f6b03c11d5aaee4eff42b4fb439cd7993abf48 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Wed, 27 Apr 2016 00:26:21 +0200 Subject: [PATCH 060/270] Add paragraph on the current scope and status --- docs/index.rst | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/docs/index.rst b/docs/index.rst index aded681..a360839 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -236,6 +236,30 @@ More information: * :mod:`ecodes ` +Scope and Status +---------------- + +Python-evdev exposes most of the more common interfaces defined in the evdev +subsystem. Reading and injecting events is well supported and has been tested +with nearly all event types. + +The basic functionality for reading and uploading force-feedback events is +there, but it has not been exercised sufficiently. A major shortcoming of the +uinput wrapper is that it does not support force-feedback devices at all (see +issue `#23`_). + +Some characters, such as ``:`` (colon), cannot be easily injected (see issue +`#7`_), Translating them into UInput events would require knowing the kernel +keyboard translation table, which is beyond the scope of python-evdev. Please +look into the following projects if you need more complete or convenient input +injection support. + +- python-uinput_ +- uinput-mapper_ +- PyUserInput_ (cross-platform, works on the display server level) +- pygame_ (cross-platform) + + News ---- .. include:: news.rst @@ -258,3 +282,11 @@ The :mod:`evdev` package is released under the terms of the `Revised BSD License .. _pip: http://pip.readthedocs.org/en/latest/installing.html .. _example: https://github.com/gvalkov/python-evdev/tree/master/examples .. _`async/await`: https://docs.python.org/3/library/asyncio-task.html + +.. _python-uinput: https://github.com/tuomasjjrasanen/python-uinput +.. _uinput-mapper: https://github.com/MerlijnWajer/uinput-mapper +.. _PyUserInput: https://github.com/PyUserInput/PyUserInput +.. _pygame: http://www.pygame.org/ + +.. _`#7`: https://github.com/gvalkov/python-evdev/issues/7 +.. _`#23`: https://github.com/gvalkov/python-evdev/pull/23 From 8113343a99c1ae5a18096418d3230e10b97af857 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Wed, 27 Apr 2016 12:45:23 +0200 Subject: [PATCH 061/270] Disable echoing when evtest is listening for events --- evdev/evtest.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/evdev/evtest.py b/evdev/evtest.py index c4068b9..ef0a736 100755 --- a/evdev/evtest.py +++ b/evdev/evtest.py @@ -25,6 +25,8 @@ import re import sys import select +import atexit +import termios import optparse from evdev import ecodes, list_devices, AbsInfo, InputDevice @@ -58,6 +60,11 @@ def main(): for device in devices: device.grab() + # Disable tty echoing if stdin is a tty. + if sys.stdin.isatty(): + toggle_tty_echo(sys.stdin, enable=False) + atexit.register(toggle_tty_echo, sys.stdin, enable=False) + print('Listening for events (press ctrl-c to exit) ...') fd_to_device = {dev.fd: dev for dev in devices} while True: @@ -151,6 +158,15 @@ def print_event(e): print(evfmt.format(e.timestamp(), e.type, ecodes.EV[e.type], e.code, codename, e.value)) +def toggle_tty_echo(fh, enable=True): + flags = termios.tcgetattr(fh.fileno()) + if enable: + flags[3] |= termios.ECHO + else: + flags[3] &= ~termios.ECHO + termios.tcsetattr(fh.fileno(), termios.TCSANOW, flags) + + if __name__ == '__main__': try: ret = main() From ac96a907195245707b671287657a3729d0b9ab13 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Thu, 12 May 2016 00:25:37 +0200 Subject: [PATCH 062/270] Add the '--evdev-headers' option to 'build_ext' This makes it possible to easily override the location of the ``input.h`` and ``input-event-codes.h`` header files. --- docs/index.rst | 3 +++ docs/installation.rst | 19 +++++++++++++ setup.py | 63 ++++++++++++++++++++++++++++--------------- 3 files changed, 64 insertions(+), 21 deletions(-) create mode 100644 docs/installation.rst diff --git a/docs/index.rst b/docs/index.rst index a360839..66b115b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -102,6 +102,9 @@ Installing *python-evdev* with pip_: $ sudo pip install evdev +For more advanced installation options, please read the :doc:`full installation +` page. + Listing accessible event devices: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 0000000..16a3ba5 --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,19 @@ +Extended Installation +--------------------- + +Specifying header location +========================== + +You may use the ``--evdev-headers`` option to the ``build_ext`` setuptools +command to specify the location of ``input.h`` and ``input-event-codes.h`` +(optionally). It accepts one or more colon-separated paths. For example: + +.. code-block:: bash + + $ python setup.py build_ext \ + --evdev-headers buildroot/input.h:buildroot/input-event-codes.h \ + --include-dirs buildroot/ \ + install # or any other command (e.g. develop, bdist, bdist_wheel) + +If nothing is specified, ``build_ext`` will search in ``/usr/include/linux`` by +default. diff --git a/setup.py b/setup.py index 48cce37..8e68c19 100755 --- a/setup.py +++ b/setup.py @@ -11,11 +11,10 @@ #----------------------------------------------------------------------------- try: from setuptools import setup, Extension - from setuptools.command import bdist_egg, develop + from setuptools.command import bdist_egg, develop, build_ext except ImportError: from distutils.core import setup, Extension - from distutils.command import build - develop, bdist_egg = None, None + from distutils.command import build, build_ext #----------------------------------------------------------------------------- here = abspath(dirname(__file__)) @@ -27,6 +26,7 @@ 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', 'Operating System :: POSIX :: Linux', 'Intended Audience :: Developers', 'Topic :: Software Development :: Libraries', @@ -64,23 +64,34 @@ #----------------------------------------------------------------------------- -def create_ecodes(): - headers = [ - '/usr/include/linux/input.h', - '/usr/include/linux/input-event-codes.h', - ] +def create_ecodes(headers=None): + if not headers: + headers = [ + '/usr/include/linux/input.h', + '/usr/include/linux/input-event-codes.h', + ] headers = [header for header in headers if os.path.isfile(header)] if not headers: msg = '''\ - The linux/input.h and linux/input-event-codes.h include files are - missing missing. You will have to install the kernel header files in + The 'linux/input.h' and 'linux/input-event-codes.h' include files + are missing. You will have to install the kernel header files in order to continue: yum install kernel-headers-$(uname -r) apt-get install linux-headers-$(uname -r) emerge sys-kernel/linux-headers - pacman -S kernel-headers\n\n''' + pacman -S kernel-headers + + In case they are installed in a non-standard location, you may use + the '--evdev-headers' option to specify one or more colon-separated + paths. For example: + + python setup.py build_ext \\ + --evdev-headers path/input.h:path/input-event-codes.h \\ + --include-dirs path/ \\ + install + ''' sys.stderr.write(textwrap.dedent(msg)) sys.exit(1) @@ -92,19 +103,29 @@ def create_ecodes(): check_call(cmd, cwd="%s/evdev" % here, shell=True) -def cmdfactory(cmd): - class cls(cmd): - def run(self): - create_ecodes() - cmd.run(self) - return cls +#----------------------------------------------------------------------------- +class cmdbuild_ext(build_ext.build_ext): + user_options = build_ext.build_ext.user_options[:] + user_options.extend([ + ('evdev-headers=', None, 'colon-separated paths to input subsystem headers'), + ]) + + def initialize_options(self): + self.evdev_headers = None + super(cmdbuild_ext, self).initialize_options() + + def finalize_options(self): + if self.evdev_headers: + self.evdev_headers = self.evdev_headers.split(':') + super(cmdbuild_ext, self).finalize_options() + + def run(self): + create_ecodes(self.evdev_headers) + super(cmdbuild_ext, self).run() #----------------------------------------------------------------------------- -kw['cmdclass']['build'] = cmdfactory(build.build) +kw['cmdclass']['build_ext'] = cmdbuild_ext -if develop and bdist_egg: - kw['cmdclass']['develop'] = cmdfactory(develop.develop) - kw['cmdclass']['bdist_egg'] = cmdfactory(bdist_egg.bdist_egg) #----------------------------------------------------------------------------- if __name__ == '__main__': From 8bf397bd447d855e2cfce4f863729430775f221c Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sat, 4 Jun 2016 13:07:57 +0200 Subject: [PATCH 063/270] Python 2 related fixes --- evdev/evtest.py | 8 ++++++++ setup.py | 7 ++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/evdev/evtest.py b/evdev/evtest.py index ef0a736..ff658c4 100755 --- a/evdev/evtest.py +++ b/evdev/evtest.py @@ -29,6 +29,11 @@ import termios import optparse +try: + input = raw_input +except NameError: + pass + from evdev import ecodes, list_devices, AbsInfo, InputDevice @@ -105,6 +110,9 @@ def devicenum(device_path): choices = choices.split() choices = [devices[int(num)] for num in choices] except ValueError: + choices = None + + if not choices: msg = 'error: invalid input - please enter one or more numbers separated by spaces' print(msg, file=sys.stderr) sys.exit(1) diff --git a/setup.py b/setup.py index 8e68c19..d000e17 100755 --- a/setup.py +++ b/setup.py @@ -112,16 +112,17 @@ class cmdbuild_ext(build_ext.build_ext): def initialize_options(self): self.evdev_headers = None - super(cmdbuild_ext, self).initialize_options() + # We cannot use super(cmdbuild_ext, self) here for compatibility with Py2. + build_ext.build_ext.initialize_options(self) def finalize_options(self): if self.evdev_headers: self.evdev_headers = self.evdev_headers.split(':') - super(cmdbuild_ext, self).finalize_options() + build_ext.build_ext.finalize_options(self) def run(self): create_ecodes(self.evdev_headers) - super(cmdbuild_ext, self).run() + build_ext.build_ext.run(self) #----------------------------------------------------------------------------- kw['cmdclass']['build_ext'] = cmdbuild_ext From eb57fd76514ed6c216987d7d31447f5666d43b3f Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sat, 4 Jun 2016 13:13:52 +0200 Subject: [PATCH 064/270] Bump version: 0.6.0 -> 0.6.1 --- docs/changelog.rst | 15 +++++++++++++++ docs/conf.py | 2 +- setup.cfg | 2 +- setup.py | 2 +- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index b4656c4..2513d3c 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,21 @@ Changelog ========= +0.6.1 (Jun 04, 2016) +^^^^^^^^^^^^^^^^^^^^ + +- Dissable tty echoing while evtest is running. +- Allow evtest to listen to more than one devices. + +- The setup.py script now allows the location of the input header files to be + overwritten. For example:: + + python setup.py build_ext \ + --evdev-headers path/input.h:path/input-event-codes.h \ + --include-dirs path/ \ + install + + 0.6.0 (Feb 14, 2016) ^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/conf.py b/docs/conf.py index cfef4e5..e4442c0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -59,7 +59,7 @@ # built documents. # # The full version, including alpha/beta/rc tags. -release = '0.6.0' +release = '0.6.1' # The short X.Y version. version = release diff --git a/setup.cfg b/setup.cfg index 3d63651..ff5bc81 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.6.0 +current_version = 0.6.1 message = Bump version: {current_version} -> {new_version} commit = True tag = True diff --git a/setup.py b/setup.py index d000e17..94a244e 100755 --- a/setup.py +++ b/setup.py @@ -43,7 +43,7 @@ #----------------------------------------------------------------------------- kw = { 'name': 'evdev', - 'version': '0.6.0', + 'version': '0.6.1', 'description': 'Bindings to the Linux input handling subsystem', 'long_description': open(pjoin(here, 'README.rst')).read(), From 896007ba05487dbba5ab46502eaf20995de02d0a Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 5 Jun 2016 16:49:18 +0200 Subject: [PATCH 065/270] Add RPM spec file for building with COPR --- packaging/python-evdev.spec | 91 +++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 packaging/python-evdev.spec diff --git a/packaging/python-evdev.spec b/packaging/python-evdev.spec new file mode 100644 index 0000000..3a79fc5 --- /dev/null +++ b/packaging/python-evdev.spec @@ -0,0 +1,91 @@ +%global gittag0 v0.6.1 +%global _summary Python bindings for the Linux input handling subsystem + +Name: python-evdev +Version: 0.6.1 +Release: 1%{?dist} +Summary: %{_summary} + +License: BSD +URL: https://python-evdev.readthedocs.io +Source0: https://github.com/gvalkov/%{name}/archive/%{gittag0}.tar.gz#/%{name}-%{version}.tar.gz +Group: Development/Libraries + +BuildRequires: kernel-headers python3-devel python3-setuptools +BuildRequires: python2-devel python-setuptools + +%description +This package provides bindings to the Linux generic input event interface. + + +%package -n python2-evdev +Summary: %{_summary} +Group: Development/Libraries +%{?python_provide:%python_provide python2-evdev} + +%description -n python2-evdev + +This package provides bindings to the generic input event interface in Linux. +The evdev interface serves the purpose of passing events generated in the kernel +directly to userspace through character devices that are typically located in +/dev/input/. + +This package also comes with bindings to uinput, the userspace input subsystem. +Uinput allows userspace programs to create and handle input devices that can +inject events directly into the input subsystem. + +In other words, python-evdev allows you to read and write input events on Linux. +An event can be a key or button press, a mouse movement or a tap on a +touchscreen. + + +%package -n python3-evdev +Summary: %{_summary} +Group: Development/Libraries +%{?python_provide:%python_provide python3-evdev} + +%description -n python3-evdev +This package provides bindings to the generic input event interface in Linux. +The evdev interface serves the purpose of passing events generated in the kernel +directly to userspace through character devices that are typically located in +/dev/input/. + +This package also comes with bindings to uinput, the userspace input subsystem. +Uinput allows userspace programs to create and handle input devices that can +inject events directly into the input subsystem. + +In other words, python-evdev allows you to read and write input events on Linux. +An event can be a key or button press, a mouse movement or a tap on a +touchscreen. + + +#------------------------------------------------------------------------------ +%prep +%autosetup -n %{name}-%{version} + +#------------------------------------------------------------------------------ +%build +%py2_build +%py3_build + +#------------------------------------------------------------------------------ +%install +%py2_install +%py3_install + +#------------------------------------------------------------------------------ +%files -n python2-evdev +%license LICENSE +%doc README.rst +%{python2_sitearch}/* + +%files -n python3-evdev +%license LICENSE +%doc README.rst +%{python3_sitearch}/* + + +#------------------------------------------------------------------------------ +%changelog +* Sun Jun 05 2016 Georgi Valkov - 0.6.1-1 +- Initial RPM Release From efad81fb3f84a651812c9d08104332ad0f9ae087 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Thu, 16 Jun 2016 00:00:58 +0200 Subject: [PATCH 066/270] Restructure documentation --- docs/changelog.rst | 59 +++++---- docs/conf.py | 17 ++- docs/index.rst | 281 +++--------------------------------------- docs/install.rst | 89 +++++++++++++ docs/installation.rst | 19 --- docs/news.rst | 37 ------ docs/scope.rst | 31 +++++ docs/tutorial.rst | 7 +- docs/usage.rst | 73 +++++++++++ requirements-dev.txt | 1 + 10 files changed, 260 insertions(+), 354 deletions(-) create mode 100644 docs/install.rst delete mode 100644 docs/installation.rst delete mode 100644 docs/news.rst create mode 100644 docs/scope.rst create mode 100644 docs/usage.rst diff --git a/docs/changelog.rst b/docs/changelog.rst index 2513d3c..8ddd555 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,8 +1,9 @@ Changelog -========= +--------- + 0.6.1 (Jun 04, 2016) -^^^^^^^^^^^^^^^^^^^^ +==================== - Dissable tty echoing while evtest is running. - Allow evtest to listen to more than one devices. @@ -17,7 +18,7 @@ Changelog 0.6.0 (Feb 14, 2016) -^^^^^^^^^^^^^^^^^^^^ +==================== - Asyncio and async/await support (many thanks to `@paulo-raca`_). - Add the ability to set the `phys` property of uinput devices (thanks `@paulo-raca`_). @@ -27,21 +28,24 @@ Changelog - Fix absinfo item indexes in :func:`UInput.uinput_create()` (thanks `@forsenonlhaimaisentito`_). - More robust comparison of :class:`InputDevice` objects (thanks `@isia`_). + 0.5.0 (Jun 16, 2015) -^^^^^^^^^^^^^^^^^^^^ +==================== - Write access to the input device is no longer mandatory. Evdev will first try to open the device for reading and writing and fallback to read-only. Methods that require write access (e.g. :func:`set_led()`) will raise :class:`EvdevError` if the device is open only for reading. + 0.4.7 (Oct 07, 2014) -^^^^^^^^^^^^^^^^^^^^ +==================== - Fallback to distutils if setuptools is not available. + 0.4.6 (Oct 07, 2014) -^^^^^^^^^^^^^^^^^^^^ +==================== - Rework documentation and docstrings once more. @@ -49,43 +53,50 @@ Changelog - Fix :func:`ioctl()` requested buffer size (thanks Jakub Wojciech Klama). + 0.4.5 (Jul 06, 2014) -^^^^^^^^^^^^^^^^^^^^ +==================== - Add method for returning a list of the currently active keys - :func:`InputDevice.active_keys()` (thanks `@spasche`_). - Fix a potential buffer overflow in :func:`ioctl_capabilities()` (thanks `@spasche`_). + 0.4.4 (Jun 04, 2014) -^^^^^^^^^^^^^^^^^^^^ +==================== - Calling :func:`InputDevice.read_one()` should always return ``None``, when there is nothing to be read, even in case of a ``EAGAIN`` errno (thanks JPP). + 0.4.3 (Dec 19, 2013) -^^^^^^^^^^^^^^^^^^^^ +==================== + - Silence :class:`OSError` in destructor (thanks `@polyphemus`_). - Make :func:`InputDevice.close()` work in cases in which stdin (fd 0) has been closed (thanks `@polyphemus`_). + 0.4.2 (Dec 13, 2013) -^^^^^^^^^^^^^^^^^^^^ +==================== - Rework documentation and docstrings. - Call :func:`InputDevice.close()` from :func:`InputDevice.__del__()`. + 0.4.1 (Jul 24, 2013) -^^^^^^^^^^^^^^^^^^^^ +==================== - Fix reference counting in :func:`InputDevice.device_read()`, -:func:`InputDevice.device_read_many()` and :func:`ioctl_capabilities`. + :func:`InputDevice.device_read_many()` and :func:`ioctl_capabilities`. + 0.4.0 (Jul 01, 2013) -^^^^^^^^^^^^^^^^^^^^ +==================== - Add ``FF_*`` and ``FF_STATUS`` codes to :func:`ecodes` (thanks `@bgilbert`_). @@ -105,30 +116,34 @@ Changelog - Fix segfault in :func:`InputDevice.device_read_many()` (thanks `@bgilbert`_). + 0.3.3 (May 29, 2013) -^^^^^^^^^^^^^^^^^^^^ +==================== - Raise :class:`IOError` from :func:`InputDevice.device_read()` and -:func:`InputDevice.device_read_many()` when :func:`InputDevice.read()` -fails. + :func:`InputDevice.device_read_many()` when :func:`InputDevice.read()` + fails. - Several stability and style changes (thank you debian code reviewers). + 0.3.2 (Apr 05, 2013) -^^^^^^^^^^^^^^^^^^^^ +==================== - Fix vendor id and product id order in :func:`DeviceInfo` (thanks `@kived`_). + 0.3.1 (Nov 23, 2012) -^^^^^^^^^^^^^^^^^^^^ +==================== - :func:`InputDevice.read()` will return an empty tuple if the device has nothing to offer (instead of segfaulting). - Exclude unnecessary package data in sdist and bdist. + 0.3.0 (Nov 06, 2012) -^^^^^^^^^^^^^^^^^^^^ +==================== - Add ability to set/get auto-repeat settings with ``EVIOC{SG}REP``. @@ -149,7 +164,7 @@ fails. 0.2.0 (Aug 22, 2012) -^^^^^^^^^^^^^^^^^^^^ +==================== - Add the ability to set arbitrary device capabilities on uinput devices (defaults to all ``EV_KEY`` ecodes). @@ -197,7 +212,7 @@ fails. 0.1.1 (May 18, 2012) -^^^^^^^^^^^^^^^^^^^^ +==================== - Add ``events.keys``, which is a combination of all ``BTN_`` and ``KEY_`` event codes. @@ -206,7 +221,7 @@ fails. 0.1.0 (May 17, 2012) -^^^^^^^^^^^^^^^^^^^^ +==================== *Initial Release* diff --git a/docs/conf.py b/docs/conf.py index e4442c0..22699f9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import sys, os +import sphinx_rtd_theme # Check if readthedocs is building us on_rtd = os.environ.get('READTHEDOCS', None) == 'True' @@ -93,7 +94,7 @@ #show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +#pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] @@ -104,12 +105,10 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -# if not on_rtd: -# import sphinx_rtd_theme -# html_theme = 'sphinx_rtd_theme' -# html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] -# else: -html_theme = 'haiku' +if not on_rtd: + import sphinx_rtd_theme + html_theme = 'sphinx_rtd_theme' + html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -127,14 +126,14 @@ # The name of an image file (relative to this directory) to place at the top # of the sidebar. -html_logo = '_static/evdev-logo-small.png' +#html_logo = '_static/evdev-logo-small.png' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None -# Add any paths that contain custom static files (such as style sheets) here, +# Add any paths that contain custom static files (such as style sheets here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] diff --git a/docs/index.rst b/docs/index.rst index 66b115b..b30154d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,38 +1,5 @@ -.. raw:: html - -
- Links
- - -
- -Synopsis --------- +Introduction +------------ This package provides bindings to the generic input event interface in Linux. The *evdev* interface serves the purpose of passing events @@ -48,248 +15,34 @@ In other words, *python-evdev* allows you to read and write input events on Linux. An event can be a key or button press, a mouse movement or a tap on a touchscreen. -Quick Start ------------ - -Installing: -^^^^^^^^^^^ - -The following GNU/Linux distributions have *python-evdev* in their package -repositories: - -.. raw:: html - -
- - - -
- -The latest stable version of *python-evdev* can be installed from pypi_, -provided that you have gcc/clang, pip_ and the Python and Linux development -headers installed on your system. Installing them is distribution specific and -typically falls in one of the following categories: - -On a Debian compatible OS: - -.. code-block:: bash - - $ apt-get install python-dev python-pip gcc - $ apt-get install linux-headers-$(uname -r) - -On a Redhat compatible OS: - -.. code-block:: bash - - $ yum install python-devel python-pip gcc - $ yum install kernel-headers-$(uname -r) - -On Arch Linux and derivatives: - -.. code-block:: bash - - $ pacman -S core/linux-api-headers python-pip gcc - -Installing *python-evdev* with pip_: - -.. code-block:: bash - - $ sudo pip install evdev - -For more advanced installation options, please read the :doc:`full installation -` page. - -Listing accessible event devices: -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -:: - - >>> import evdev - - >>> devices = [evdev.InputDevice(fn) for fn in evdev.list_devices()] - >>> for device in devices: - ... print(device.fn, device.name, device.phys) - /dev/input/event1 Dell Dell USB Keyboard usb-0000:00:12.1-2/input0 - /dev/input/event0 Dell USB Optical Mouse usb-0000:00:12.0-2/input0 - - -Reading events from a device: -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -:: - - >>> import evdev - - >>> device = evdev.InputDevice('/dev/input/event1') - >>> print(device) - device /dev/input/event1, name "Dell Dell USB Keyboard", phys "usb-0000:00:12.1-2/input0" - - >>> for event in device.read_loop(): - ... if event.type == evdev.ecodes.EV_KEY: - ... print(categorize(event)) - ... # pressing 'a' and holding 'space' - key event at 1337016188.396030, 30 (KEY_A), down - key event at 1337016188.492033, 30 (KEY_A), up - key event at 1337016189.772129, 57 (KEY_SPACE), down - key event at 1337016190.275396, 57 (KEY_SPACE), hold - key event at 1337016190.284160, 57 (KEY_SPACE), up - -Reading events using async/await: -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -*Python-evdev* proudly supports the new `async/await`_ syntax in Python 3.5: - -:: - - import asyncio, evdev - - async def print_events(device): - async for event in device.async_read_loop(): - print(device.fn, evdev.categorize(event), sep=': ') - - device = evdev.InputDevice('/dev/input/event4') - asyncio.ensure_future(print_events(device)) - - loop = asyncio.get_event_loop() - loop.run_forever() - -Accessing evdev constants: -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -:: - >>> from evdev import ecodes +.. toctree:: + :caption: Installation + :maxdepth: 2 - >>> ecodes.KEY_A, ecodes.ecodes['KEY_A'] - ... (30, 30) - >>> ecodes.KEY[30] - ... 'KEY_A' - >>> ecodes.bytype[ecodes.EV_KEY][30] - ... 'KEY_A' - >>> ecodes.KEY[152] # a single value may correspond to multiple codes - ... ['KEY_COFFEE', 'KEY_SCREENLOCK'] + install -Listing and monitoring input devices: -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. toctree:: + :caption: Usage -The *python-evdev* package also comes with a small command-line program for -listing and monitoring input devices: + usage + tutorial + apidoc -.. code-block:: bash +.. toctree:: + :caption: Project + :maxdepth: 2 - $ python -m evdev.evtest - -More information: -^^^^^^^^^^^^^^^^^ - -- Read the full :doc:`tutorial `. - -- See the example_ programs. - -- Refer to the API :doc:`documentation `: - - * :mod:`device ` - - - :class:`AbsInfo ` - - - :class:`KbdInfo ` - - - :class:`InputDevice ` - - * :mod:`events ` - - - :class:`InputEvent ` - - - :class:`KeyEvent ` - - - :class:`RelEvent ` - - - :class:`AbsEvent ` - - - :class:`SynEvent ` - - * :mod:`eventio ` - - - :class:`EventIO ` - - * :mod:`eventio_async ` - - - :class:`EventIO ` - - * :mod:`util ` - - - :class:`list_devices() ` - - - :class:`is_device() ` - - - :class:`categorize() ` - - - :class:`categorize() ` - - * :mod:`uinput ` - - - :class:`UInput ` - - * :mod:`ecodes ` - - -Scope and Status ----------------- - -Python-evdev exposes most of the more common interfaces defined in the evdev -subsystem. Reading and injecting events is well supported and has been tested -with nearly all event types. - -The basic functionality for reading and uploading force-feedback events is -there, but it has not been exercised sufficiently. A major shortcoming of the -uinput wrapper is that it does not support force-feedback devices at all (see -issue `#23`_). - -Some characters, such as ``:`` (colon), cannot be easily injected (see issue -`#7`_), Translating them into UInput events would require knowing the kernel -keyboard translation table, which is beyond the scope of python-evdev. Please -look into the following projects if you need more complete or convenient input -injection support. - -- python-uinput_ -- uinput-mapper_ -- PyUserInput_ (cross-platform, works on the display server level) -- pygame_ (cross-platform) - - -News ----- -.. include:: news.rst - -Please refer to the :doc:`changelog ` for a full list of changes. + scope + changelog License ------- -The :mod:`evdev` package is released under the terms of the `Revised BSD License`_. +This package is released under the terms of the `Revised BSD License`_. .. _`Revised BSD License`: https://raw.github.com/gvalkov/python-evdev/master/LICENSE .. _python-uinput: https://github.com/tuomasjjrasanen/python-uinput .. _ruby-evdev: http://technofetish.net/repos/buffaloplay/ruby_evdev/doc/ .. _evdev: http://svn.navi.cx/misc/trunk/python/evdev/ - -.. _pypi: http://pypi.python.org/pypi/evdev -.. _github: https://github.com/gvalkov/python-evdev -.. _pip: http://pip.readthedocs.org/en/latest/installing.html -.. _example: https://github.com/gvalkov/python-evdev/tree/master/examples -.. _`async/await`: https://docs.python.org/3/library/asyncio-task.html - -.. _python-uinput: https://github.com/tuomasjjrasanen/python-uinput -.. _uinput-mapper: https://github.com/MerlijnWajer/uinput-mapper -.. _PyUserInput: https://github.com/PyUserInput/PyUserInput -.. _pygame: http://www.pygame.org/ - -.. _`#7`: https://github.com/gvalkov/python-evdev/issues/7 -.. _`#23`: https://github.com/gvalkov/python-evdev/pull/23 diff --git a/docs/install.rst b/docs/install.rst new file mode 100644 index 0000000..de18858 --- /dev/null +++ b/docs/install.rst @@ -0,0 +1,89 @@ +From a binary package +===================== + +Python-evdev has been packaged for the following GNU/Linux distributions: + + +.. raw:: html + + + +Consult the relevant documentation of your OS package manager for installation instructions. + + +From source +=========== + +The latest stable version of *python-evdev* can be installed from pypi_, +provided that you have gcc/clang, pip_ and the Python and Linux development +headers installed on your system. Installing them is distribution specific and +typically falls in one of the following categories: + +On a Debian compatible OS: + +.. code-block:: bash + + $ apt-get install python-dev python-pip gcc + $ apt-get install linux-headers-$(uname -r) + +On a Redhat compatible OS: + +.. code-block:: bash + + $ yum install python-devel python-pip gcc + $ yum install kernel-headers-$(uname -r) + +On Arch Linux and derivatives: + +.. code-block:: bash + + $ pacman -S core/linux-api-headers python-pip gcc + +Once all dependencies are available, you may install *python-evdev* using pip_: + +.. code-block:: bash + + $ sudo pip install evdev + + +Specifying header locations +=========================== + +By default, the setup script will look for the ``input.h`` and +``input-event-codes.h`` [#f1]_ header files ``/usr/include/linux``. + +You may use the ``--evdev-headers`` option to the ``build_ext`` setuptools +command to specify the location of these header files. It accepts one or more +colon-separated paths. For example: + +.. code-block:: bash + + $ python setup.py build_ext \ + --evdev-headers buildroot/input.h:buildroot/input-event-codes.h \ + --include-dirs buildroot/ \ + install # or any other command (e.g. develop, bdist, bdist_wheel) + +.. [#f1] ``input-event-codes.h`` is found only in more recent kernel versions. + + +.. _pypi: http://pypi.python.org/pypi/evdev +.. _github: https://github.com/gvalkov/python-evdev +.. _pip: http://pip.readthedocs.org/en/latest/installing.html +.. _example: https://github.com/gvalkov/python-evdev/tree/master/examples +.. _`async/await`: https://docs.python.org/3/library/asyncio-task.html diff --git a/docs/installation.rst b/docs/installation.rst deleted file mode 100644 index 16a3ba5..0000000 --- a/docs/installation.rst +++ /dev/null @@ -1,19 +0,0 @@ -Extended Installation ---------------------- - -Specifying header location -========================== - -You may use the ``--evdev-headers`` option to the ``build_ext`` setuptools -command to specify the location of ``input.h`` and ``input-event-codes.h`` -(optionally). It accepts one or more colon-separated paths. For example: - -.. code-block:: bash - - $ python setup.py build_ext \ - --evdev-headers buildroot/input.h:buildroot/input-event-codes.h \ - --include-dirs buildroot/ \ - install # or any other command (e.g. develop, bdist, bdist_wheel) - -If nothing is specified, ``build_ext`` will search in ``/usr/include/linux`` by -default. diff --git a/docs/news.rst b/docs/news.rst deleted file mode 100644 index 1428461..0000000 --- a/docs/news.rst +++ /dev/null @@ -1,37 +0,0 @@ -* ``Jun 16, 2015:`` Version 0.5.0 released. - -* ``Oct 07, 2014:`` Version 0.4.6 released. - -* ``Jul 06, 2014:`` Version 0.4.5 released. - -* ``Jun 04, 2014:`` Version 0.4.4 released. - -* ``Dec 19, 2013:`` Version 0.4.3 released. - -* ``Dec 13, 2013:`` Version 0.4.2 released. - -* ``Oct 17, 2013:`` *Python-evdev* is now available in `Ubuntu Saucy`_. - -* ``Oct 11, 2013:`` *Python-evdev* is now available in `Arch Linux`_. - -* ``Jul 24, 2013:`` Version 0.4.1 released. - -* ``Jul 01, 2013:`` Version 0.4.0 released. - -* ``May 29, 2013:`` Version 0.3.3 released. - -* ``Apr 05, 2013:`` Version 0.3.2 released. - -* ``Nov 23, 2012:`` Version 0.3.1 released. - -* ``Nov 06, 2012:`` Version 0.3.0 released. - -* ``Aug 22, 2012:`` Version 0.2.0 released. - -* ``May 18, 2012:`` Version 0.1.1 released. - -* ``May 17, 2012:`` Version 0.1.0 released. - -.. _`Arch Linux`: https://aur.archlinux.org/packages/python-evdev/ - -.. _`Ubuntu Saucy`: http://packages.ubuntu.com/saucy/python-evdev diff --git a/docs/scope.rst b/docs/scope.rst new file mode 100644 index 0000000..a67a096 --- /dev/null +++ b/docs/scope.rst @@ -0,0 +1,31 @@ +Scope and status +---------------- + +Python-evdev exposes most of the more common interfaces defined in the evdev +subsystem. Reading and injecting events is well supported and has been tested +with nearly all event types. + +The basic functionality for reading and uploading force-feedback events is +there, but it has not been exercised sufficiently. A major shortcoming of the +uinput wrapper is that it does not support force-feedback devices at all (see +issue `#23`_). + +Some characters, such as ``:`` (colon), cannot be easily injected (see issue +`#7`_), Translating them into UInput events would require knowing the kernel +keyboard translation table, which is beyond the scope of python-evdev. Please +look into the following projects if you need more complete or convenient input +injection support. + +- python-uinput_ +- uinput-mapper_ +- PyUserInput_ (cross-platform, works on the display server level) +- pygame_ (cross-platform) + + +.. _python-uinput: https://github.com/tuomasjjrasanen/python-uinput +.. _uinput-mapper: https://github.com/MerlijnWajer/uinput-mapper +.. _PyUserInput: https://github.com/PyUserInput/PyUserInput +.. _pygame: http://www.pygame.org/ + +.. _`#7`: https://github.com/gvalkov/python-evdev/issues/7 +.. _`#23`: https://github.com/gvalkov/python-evdev/pull/23 diff --git a/docs/tutorial.rst b/docs/tutorial.rst index f645c71..fe57c68 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -1,6 +1,6 @@ +Tutorial +-------- -.. contents:: Examples - :depth: 2 Listing accessible event devices ================================ @@ -198,7 +198,7 @@ This can also be achieved using the :mod:`selectors` module in Python 3.4: Reading events from multiple devices (using :mod:`asyncio`) -============================================================= +=========================================================== Yet another possibility is the :mod:`asyncio` module from Python 3.4: @@ -313,6 +313,7 @@ Injecting events (using a context manager) Specifying ``uinput`` device options ==================================== + :: >>> from evdev import UInput, AbsInfo, ecodes as e diff --git a/docs/usage.rst b/docs/usage.rst new file mode 100644 index 0000000..47f8a79 --- /dev/null +++ b/docs/usage.rst @@ -0,0 +1,73 @@ +Quick Start +----------- + + +Listing accessible event devices +================================ + +:: + + >>> import evdev + + >>> devices = [evdev.InputDevice(fn) for fn in evdev.list_devices()] + >>> for device in devices: + ... print(device.fn, device.name, device.phys) + /dev/input/event1 USB Keyboard usb-0000:00:12.1-2/input0 + /dev/input/event0 USB Optical Mouse usb-0000:00:12.0-2/input0 + + +Reading events from a device +============================ + +:: + + >>> import evdev + + >>> device = evdev.InputDevice('/dev/input/event1') + >>> print(device) + device /dev/input/event1, name "USB Keyboard", phys "usb-0000:00:12.1-2/input0" + + >>> for event in device.read_loop(): + ... if event.type == evdev.ecodes.EV_KEY: + ... print(categorize(event)) + ... # pressing 'a' and holding 'space' + key event at 1337016188.396030, 30 (KEY_A), down + key event at 1337016188.492033, 30 (KEY_A), up + key event at 1337016189.772129, 57 (KEY_SPACE), down + key event at 1337016190.275396, 57 (KEY_SPACE), hold + key event at 1337016190.284160, 57 (KEY_SPACE), up + + +Accessing event codes +===================== + +The ``evdev.ecodes`` module provides reverse and forward mappings between the +names and values of the event subsystem constants. + +:: + + >>> from evdev import ecodes + + >>> ecodes.KEY_A + ... 30 + >>> ecodes.ecodes['KEY_A'] + ... 30 + >>> ecodes.KEY[30] + ... 'KEY_A' + >>> ecodes.bytype[ecodes.EV_KEY][30] + ... 'KEY_A' + + # A single value may correspond to multiple event codes. + >>> ecodes.KEY[152] + ... ['KEY_COFFEE', 'KEY_SCREENLOCK'] + + +Listing and monitoring input devices +==================================== + +The *python-evdev* package also comes with a small command-line program for +listing and monitoring input devices: + +.. code-block:: bash + + $ python -m evdev.evtest diff --git a/requirements-dev.txt b/requirements-dev.txt index 37f9590..e7522a3 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,3 +1,4 @@ pytest>=2.6.4 Sphinx>=1.3.5 bumpversion>=0.5.3 +sphinx_rtd_theme From 436a967967a862481d7e34c5d243a67351d84500 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 21 Aug 2016 19:57:25 +0200 Subject: [PATCH 067/270] Spec file improvements - Use a macro for the %description section to avoid duplication. - Avoid duplicating the 'Summary' tag. - One BuildRequires per-line, use autosetup, remove 'Group' tags. - Explicitly include package and egg-info in %files. --- packaging/python-evdev.spec | 79 ++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 46 deletions(-) diff --git a/packaging/python-evdev.spec b/packaging/python-evdev.spec index 3a79fc5..1d110e2 100644 --- a/packaging/python-evdev.spec +++ b/packaging/python-evdev.spec @@ -1,67 +1,52 @@ -%global gittag0 v0.6.1 -%global _summary Python bindings for the Linux input handling subsystem - Name: python-evdev Version: 0.6.1 Release: 1%{?dist} -Summary: %{_summary} +Summary: Python bindings for the Linux input handling subsystem License: BSD URL: https://python-evdev.readthedocs.io -Source0: https://github.com/gvalkov/%{name}/archive/%{gittag0}.tar.gz#/%{name}-%{version}.tar.gz -Group: Development/Libraries +Source0: https://github.com/gvalkov/%{name}/archive/v%{version}.tar.gz#/%{name}-%{version}.tar.gz + +BuildRequires: kernel-headers +BuildRequires: python-setuptools +BuildRequires: python2-devel +BuildRequires: python3-devel +BuildRequires: python3-setuptools + + +%global _description \ +This package provides python bindings to the generic input event interface in \ +Linux. The evdev interface serves the purpose of passing events generated in \ +the kernel directly to userspace through character devices that are typically \ +located in /dev/input/. \ + \ +This package also comes with bindings to uinput, the userspace input subsystem. \ +Uinput allows userspace programs to create and handle input devices that can \ +inject events directly into the input subsystem. \ + \ +In other words, python-evdev allows you to read and write input events on Linux. \ +An event can be a key or button press, a mouse movement or a tap on a \ +touchscreen. -BuildRequires: kernel-headers python3-devel python3-setuptools -BuildRequires: python2-devel python-setuptools -%description -This package provides bindings to the Linux generic input event interface. +%description %{_description} %package -n python2-evdev -Summary: %{_summary} -Group: Development/Libraries +Summary: %{summary} %{?python_provide:%python_provide python2-evdev} - -%description -n python2-evdev - -This package provides bindings to the generic input event interface in Linux. -The evdev interface serves the purpose of passing events generated in the kernel -directly to userspace through character devices that are typically located in -/dev/input/. - -This package also comes with bindings to uinput, the userspace input subsystem. -Uinput allows userspace programs to create and handle input devices that can -inject events directly into the input subsystem. - -In other words, python-evdev allows you to read and write input events on Linux. -An event can be a key or button press, a mouse movement or a tap on a -touchscreen. +%description -n python2-evdev %{_description} %package -n python3-evdev -Summary: %{_summary} -Group: Development/Libraries +Summary: %{summary} %{?python_provide:%python_provide python3-evdev} - -%description -n python3-evdev -This package provides bindings to the generic input event interface in Linux. -The evdev interface serves the purpose of passing events generated in the kernel -directly to userspace through character devices that are typically located in -/dev/input/. - -This package also comes with bindings to uinput, the userspace input subsystem. -Uinput allows userspace programs to create and handle input devices that can -inject events directly into the input subsystem. - -In other words, python-evdev allows you to read and write input events on Linux. -An event can be a key or button press, a mouse movement or a tap on a -touchscreen. +%description -n python3-evdev %{_description} #------------------------------------------------------------------------------ %prep -%autosetup -n %{name}-%{version} +%autosetup #------------------------------------------------------------------------------ %build @@ -77,12 +62,14 @@ touchscreen. %files -n python2-evdev %license LICENSE %doc README.rst -%{python2_sitearch}/* +%{python2_sitearch}/evdev/ +%{python2_sitearch}/evdev-%{version}-py%{python2_version}.egg-info/ %files -n python3-evdev %license LICENSE %doc README.rst -%{python3_sitearch}/* +%{python3_sitearch}/evdev/ +%{python3_sitearch}/evdev-%{version}-py%{python3_version}.egg-info/ #------------------------------------------------------------------------------ From eff6213cf782ad6552a98716516cc0b9c082bedc Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Mon, 22 Aug 2016 00:30:29 +0200 Subject: [PATCH 068/270] s/python-setuptools/python2-setuptools --- packaging/python-evdev.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/python-evdev.spec b/packaging/python-evdev.spec index 1d110e2..a3cfb14 100644 --- a/packaging/python-evdev.spec +++ b/packaging/python-evdev.spec @@ -8,9 +8,9 @@ URL: https://python-evdev.readthedocs.io Source0: https://github.com/gvalkov/%{name}/archive/v%{version}.tar.gz#/%{name}-%{version}.tar.gz BuildRequires: kernel-headers -BuildRequires: python-setuptools BuildRequires: python2-devel BuildRequires: python3-devel +BuildRequires: python2-setuptools BuildRequires: python3-setuptools From 2ab24ff337316edc2668b7fd9dac0447aa03b1bd Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Mon, 22 Aug 2016 00:34:25 +0200 Subject: [PATCH 069/270] Remove shebang and executable bit --- evdev/evtest.py | 1 - evdev/genecodes.py | 1 - 2 files changed, 2 deletions(-) mode change 100755 => 100644 evdev/evtest.py mode change 100755 => 100644 evdev/genecodes.py diff --git a/evdev/evtest.py b/evdev/evtest.py old mode 100755 new mode 100644 index ff658c4..717e8a5 --- a/evdev/evtest.py +++ b/evdev/evtest.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # encoding: utf-8 ''' diff --git a/evdev/genecodes.py b/evdev/genecodes.py old mode 100755 new mode 100644 index 65ddfc4..981a66d --- a/evdev/genecodes.py +++ b/evdev/genecodes.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8; -*- ''' From f8d0053b771725dfccfa6d4c2498f380e0470039 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 4 Sep 2016 15:45:39 +0200 Subject: [PATCH 070/270] Create UInput device with the capabilities of existing devices --- docs/changelog.rst | 8 ++++++++ docs/tutorial.rst | 15 +++++++++++++++ evdev/uinput.py | 33 +++++++++++++++++++++++++++++++-- 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 8ddd555..84c7013 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,14 @@ Changelog --------- +Development +=========== + +- Add the ``UInput.from_device`` class method, which allows uinput device to be + created with the capabiltiies of one or more existing input devices:: + + ui = UInput.from_device('/dev/input1', '/dev/input2', **constructor_kwargs) + 0.6.1 (Jun 04, 2016) ==================== diff --git a/docs/tutorial.rst b/docs/tutorial.rst index fe57c68..83981e0 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -345,4 +345,19 @@ Specifying ``uinput`` device options >>> ui.syn() +Create ``uinput`` device with capabilities of another device +================================================================ + +:: + + >>> from evdev import UInput, InputDevice + + >>> mouse = InputDevice('/dev/input/event1')from evdev import UInput, InputDevice + >>> keybd = '/dev/input/event2' + + >>> ui = UInput.from_device(mouse, keybd, name='keyboard-mouse-device') + >>> ui.capabilities(verbose=True).keys() + dict_keys([('EV_LED', 17), ('EV_KEY', 1), ('EV_SYN', 0), ('EV_REL', 2), ('EV_MSC', 4)]) + + .. _`async/await`: https://docs.python.org/3/library/asyncio-task.html diff --git a/evdev/uinput.py b/evdev/uinput.py index 9c3dd7b..ca73fc8 100644 --- a/evdev/uinput.py +++ b/evdev/uinput.py @@ -30,6 +30,35 @@ class UInput(EventIO): 'events', 'devnode', 'fd', 'device', ) + @classmethod + def from_device(cls, *devices, **kwargs): + ''' + Create an UInput device with the capabilities of one or more input + devices. + + Arguments + --------- + devices : InputDevice|str + Varargs of InputDevice instances or paths to input devices. + + **kwargs + Keyword arguments to UInput constructor (i.e. name, vendor etc.). + ''' + + device_instances = [] + for dev in devices: + if not isinstance(dev, device.InputDevice): + dev = device.InputDevice(str(dev)) + device_instances.append(dev) + + all_capabilities = {} + for dev in device_instances: + all_capabilities.update(dev.capabilities()) + + del all_capabilities[ecodes.EV_SYN] + + return cls(events=all_capabilities, **kwargs) + def __init__(self, events=None, name='py-evdev-uinput', @@ -60,7 +89,7 @@ def __init__(self, phys physical path. - + Note ---- If you do not specify any events, the uinput device will be able @@ -86,7 +115,7 @@ def __init__(self, #: Write-only, non-blocking file descriptor to the uinput device node. self.fd = _uinput.open(devnode) - + # Set phys name _uinput.set_phys(self.fd, phys) From ad669c5493ec6849726e92341c03c0655ac959ed Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 4 Sep 2016 15:53:10 +0200 Subject: [PATCH 071/270] Update readthedocs links --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 46f8d67..207bc39 100644 --- a/README.rst +++ b/README.rst @@ -12,7 +12,7 @@ input devices that can inject events directly into the input subsystem. Documentation: - http://python-evdev.readthedocs.org/en/latest/ + http://python-evdev.readthedocs.io/en/latest/ Development: https://github.com/gvalkov/python-evdev @@ -21,4 +21,4 @@ Package: http://pypi.python.org/pypi/evdev Changelog: - http://python-evdev.readthedocs.org/en/latest/changelog.html + http://python-evdev.readthedocs.io/en/latest/changelog.html From 776bf930b16377a7e02ebb57439e33ba892eee69 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Thu, 6 Oct 2016 10:01:19 +0200 Subject: [PATCH 072/270] Fix -Wsign-compare warning --- evdev/input.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evdev/input.c b/evdev/input.c index 0ec2db3..4eae157 100644 --- a/evdev/input.c +++ b/evdev/input.c @@ -78,7 +78,7 @@ device_read(PyObject *self, PyObject *args) static PyObject * device_read_many(PyObject *self, PyObject *args) { - int fd, i; + int fd; // get device file descriptor (O_RDONLY|O_NONBLOCK) int ret = PyArg_ParseTuple(args, "i", &fd); @@ -101,7 +101,7 @@ device_read_many(PyObject *self, PyObject *args) } // Construct a list of event tuples, which we'll make sense of in Python - for (i = 0 ; i < nread/event_size ; i++) { + for (unsigned i = 0 ; i < nread/event_size ; i++) { sec = PyLong_FromLong(event[i].time.tv_sec); usec = PyLong_FromLong(event[i].time.tv_usec); val = PyLong_FromLong(event[i].value); From b20b83366fd74e4df66d331e980d195f2e029e0e Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Thu, 6 Oct 2016 17:44:50 +0200 Subject: [PATCH 073/270] More robust overwriting of evdev header locations Add the `build_ecodes` distutils command which generates the ecodes.c extension module source file. The `--evdev-headers` option was moved from `build_ext` to `build_ecodes`. The current setup allows the `install` command to reuse the results of the last `build` command. --- docs/changelog.rst | 17 +++++++++++++--- setup.py | 51 ++++++++++++++++++++++++++++++---------------- 2 files changed, 48 insertions(+), 20 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 84c7013..c387d72 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -9,6 +9,17 @@ Development ui = UInput.from_device('/dev/input1', '/dev/input2', **constructor_kwargs) +- Add the ``build_ecodes`` distutils command, which generates the ``ecodes.c`` + extension module. The new way of overwriting the evdev header locations is:: + + python setup.py build \ + build_ecodes --evdev-headers path/input.h:path/input-event-codes.h \ + build_ext --include-dirs path/ \ + install + + The ``build*`` and ``install`` commands no longer have to be part of the same + command-line (i.e. running ``install`` will reuse the outputs of the last + ``build``). 0.6.1 (Jun 04, 2016) ==================== @@ -20,9 +31,9 @@ Development overwritten. For example:: python setup.py build_ext \ - --evdev-headers path/input.h:path/input-event-codes.h \ - --include-dirs path/ \ - install + --evdev-headers path/input.h:path/input-event-codes.h \ + --include-dirs path/ \ + install 0.6.0 (Feb 14, 2016) diff --git a/setup.py b/setup.py index 94a244e..d24e8de 100755 --- a/setup.py +++ b/setup.py @@ -6,15 +6,16 @@ import textwrap from os.path import abspath, dirname, join as pjoin -from distutils.command import build #----------------------------------------------------------------------------- try: - from setuptools import setup, Extension - from setuptools.command import bdist_egg, develop, build_ext + import asd + from setuptools import setup, Extension, Command + from setuptools.command import build_ext as _build_ext except ImportError: - from distutils.core import setup, Extension - from distutils.command import build, build_ext + from distutils.core import setup, Extension, Command + from distutils.command import build_ext as _build_ext + #----------------------------------------------------------------------------- here = abspath(dirname(__file__)) @@ -87,9 +88,10 @@ def create_ecodes(headers=None): the '--evdev-headers' option to specify one or more colon-separated paths. For example: - python setup.py build_ext \\ - --evdev-headers path/input.h:path/input-event-codes.h \\ - --include-dirs path/ \\ + python setup.py \\ + build \\ + build_ecodes --evdev-headers path/input.h:path/input-event-codes.h \\ + build_ext --include-dirs path/ \\ install ''' @@ -104,28 +106,43 @@ def create_ecodes(headers=None): #----------------------------------------------------------------------------- -class cmdbuild_ext(build_ext.build_ext): - user_options = build_ext.build_ext.user_options[:] - user_options.extend([ +class build_ecodes(Command): + description = 'generate ecodes.c' + + user_options = [ ('evdev-headers=', None, 'colon-separated paths to input subsystem headers'), - ]) + ] def initialize_options(self): self.evdev_headers = None - # We cannot use super(cmdbuild_ext, self) here for compatibility with Py2. - build_ext.build_ext.initialize_options(self) def finalize_options(self): if self.evdev_headers: self.evdev_headers = self.evdev_headers.split(':') - build_ext.build_ext.finalize_options(self) def run(self): create_ecodes(self.evdev_headers) - build_ext.build_ext.run(self) + + +class build_ext(_build_ext.build_ext): + def has_ecodes(self): + ecodes_path = os.path.join(here, 'evdev/ecodes.c') + res = os.path.exists(ecodes_path) + if res: + print('ecodes.c already exists ... skipping build_ecodes') + return not res + + def run(self): + for cmd_name in self.get_sub_commands(): + self.run_command(cmd_name) + _build_ext.build_ext.run(self) + + sub_commands = [('build_ecodes', has_ecodes)] + _build_ext.build_ext.sub_commands + #----------------------------------------------------------------------------- -kw['cmdclass']['build_ext'] = cmdbuild_ext +kw['cmdclass']['build_ext'] = build_ext +kw['cmdclass']['build_ecodes'] = build_ecodes #----------------------------------------------------------------------------- From e04e9fc54c3eb5244e81019941883ec8a6a42d1e Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Thu, 6 Oct 2016 21:03:24 +0200 Subject: [PATCH 074/270] Bump version: 0.6.1 -> 0.6.2 --- MANIFEST.in | 2 -- docs/changelog.rst | 4 ++-- docs/conf.py | 2 +- setup.cfg | 2 +- setup.py | 2 +- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index fffc1f5..a5021c6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,2 @@ -# make github and sdist happy include README.rst -include evdev/genecodes.py include LICENSE diff --git a/docs/changelog.rst b/docs/changelog.rst index c387d72..d51d12f 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,8 +1,8 @@ Changelog --------- -Development -=========== +0.6.2 (Oct 06, 2016) +==================== - Add the ``UInput.from_device`` class method, which allows uinput device to be created with the capabiltiies of one or more existing input devices:: diff --git a/docs/conf.py b/docs/conf.py index 22699f9..fa5e194 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -60,7 +60,7 @@ # built documents. # # The full version, including alpha/beta/rc tags. -release = '0.6.1' +release = '0.6.2' # The short X.Y version. version = release diff --git a/setup.cfg b/setup.cfg index ff5bc81..8a60357 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.6.1 +current_version = 0.6.2 message = Bump version: {current_version} -> {new_version} commit = True tag = True diff --git a/setup.py b/setup.py index d24e8de..20dd808 100755 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ #----------------------------------------------------------------------------- kw = { 'name': 'evdev', - 'version': '0.6.1', + 'version': '0.6.2', 'description': 'Bindings to the Linux input handling subsystem', 'long_description': open(pjoin(here, 'README.rst')).read(), From 9621b7ac768c1fa1e6fcb7b93277ee6537244ecf Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Thu, 6 Oct 2016 21:09:16 +0200 Subject: [PATCH 075/270] Fix accidental include forcing the use of distutils Forgot to remove some testing code from setup.py. --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 20dd808..751d88d 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,6 @@ #----------------------------------------------------------------------------- try: - import asd from setuptools import setup, Extension, Command from setuptools.command import build_ext as _build_ext except ImportError: From 52cf4ddcc7315bc5dcad83eaee63263454770530 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Thu, 6 Oct 2016 21:14:56 +0200 Subject: [PATCH 076/270] Bump version: 0.6.2 -> 0.6.3 --- docs/changelog.rst | 4 +++- docs/conf.py | 2 +- setup.cfg | 2 +- setup.py | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index d51d12f..9d7e527 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,7 +1,8 @@ Changelog --------- -0.6.2 (Oct 06, 2016) + +0.6.3 (Oct 06, 2016) ==================== - Add the ``UInput.from_device`` class method, which allows uinput device to be @@ -21,6 +22,7 @@ Changelog command-line (i.e. running ``install`` will reuse the outputs of the last ``build``). + 0.6.1 (Jun 04, 2016) ==================== diff --git a/docs/conf.py b/docs/conf.py index fa5e194..0431faf 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -60,7 +60,7 @@ # built documents. # # The full version, including alpha/beta/rc tags. -release = '0.6.2' +release = '0.6.3' # The short X.Y version. version = release diff --git a/setup.cfg b/setup.cfg index 8a60357..7885df4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.6.2 +current_version = 0.6.3 message = Bump version: {current_version} -> {new_version} commit = True tag = True diff --git a/setup.py b/setup.py index 751d88d..676013b 100755 --- a/setup.py +++ b/setup.py @@ -43,7 +43,7 @@ #----------------------------------------------------------------------------- kw = { 'name': 'evdev', - 'version': '0.6.2', + 'version': '0.6.3', 'description': 'Bindings to the Linux input handling subsystem', 'long_description': open(pjoin(here, 'README.rst')).read(), From 9219ef10788d9136edf395bc77ed15662b7e484c Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Fri, 7 Oct 2016 20:44:54 +0200 Subject: [PATCH 077/270] Exclude 'ecodes.c' from source distribution --- MANIFEST.in | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MANIFEST.in b/MANIFEST.in index a5021c6..7066730 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,7 @@ include README.rst include LICENSE + +# The _ecodes extension module source file needs to be generated against the +# evdev headers of the running kernel. Refer to the 'build_ecodes' distutils +# command in setup.py. +exclude evdev/ecodes.c From 3d8923f71508236a795d9dd7b6491e6e2990bf2a Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Fri, 7 Oct 2016 21:27:10 +0200 Subject: [PATCH 078/270] Improve ecodes.py docstring --- evdev/ecodes.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/evdev/ecodes.py b/evdev/ecodes.py index c94b0c1..f0b5961 100644 --- a/evdev/ecodes.py +++ b/evdev/ecodes.py @@ -1,15 +1,16 @@ # encoding: utf-8 ''' -This modules exposes the integer constants defined in ``linux/input.h``. +This modules exposes the integer constants defined in ``linux/input.h`` and +``linux/input-event-codes.h``. Exposed constants:: KEY, ABS, REL, SW, MSC, LED, BTN, REP, SND, ID, EV, BUS, SYN, FF, FF_STATUS -This module also provides numerous reverse and forward mappings that are best -illustrated by a few examples:: +This module also provides reverse and forward mappings of the names and values +of the above mentioned constants:: >>> evdev.ecodes.KEY_A 30 @@ -29,7 +30,8 @@ >>> evdev.ecodes.bytype[evdev.ecodes.EV_REL][0] 'REL_X' -Values in reverse mappings may point to one or more ecodes. For example:: +Keep in mind that values in reverse mappings may point to one or more event +codes. For example:: >>> evdev.ecodes.FF[80] ['FF_EFFECT_MIN', 'FF_RUMBLE'] From 7f23464f93790fafdef20e9906b1a6c8d0e04e1b Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Fri, 7 Oct 2016 21:30:30 +0200 Subject: [PATCH 079/270] Bump version: 0.6.3 -> 0.6.4 --- docs/changelog.rst | 7 +++++++ docs/conf.py | 2 +- setup.cfg | 2 +- setup.py | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 9d7e527..f1c6a2e 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -2,6 +2,12 @@ Changelog --------- +0.6.4 (Oct 07, 2016) +==================== + +- Exclude ``ecodes.c`` from source distribution (Fixes `#63`_). + + 0.6.3 (Oct 06, 2016) ==================== @@ -256,3 +262,4 @@ Changelog .. _`@paulo-raca`: https://github.com/paulo-raca .. _issue21121: http://bugs.python.org/issue21121 +.. _`#63`: https://github.com/gvalkov/python-evdev/issues/63 diff --git a/docs/conf.py b/docs/conf.py index 0431faf..f0eec9a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -60,7 +60,7 @@ # built documents. # # The full version, including alpha/beta/rc tags. -release = '0.6.3' +release = '0.6.4' # The short X.Y version. version = release diff --git a/setup.cfg b/setup.cfg index 7885df4..425816b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.6.3 +current_version = 0.6.4 message = Bump version: {current_version} -> {new_version} commit = True tag = True diff --git a/setup.py b/setup.py index 676013b..8edd91b 100755 --- a/setup.py +++ b/setup.py @@ -43,7 +43,7 @@ #----------------------------------------------------------------------------- kw = { 'name': 'evdev', - 'version': '0.6.3', + 'version': '0.6.4', 'description': 'Bindings to the Linux input handling subsystem', 'long_description': open(pjoin(here, 'README.rst')).read(), From 02538759350bd0bbda0906e2ea99902fd1903b7e Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Fri, 16 Jun 2017 17:54:51 +0200 Subject: [PATCH 080/270] Support path protocol --- docs/changelog.rst | 6 ++++++ evdev/device.py | 3 +++ 2 files changed, 9 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index f1c6a2e..2385713 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -2,6 +2,12 @@ Changelog --------- +Unreleased +==================== + +- Support path protocol in ``InputDevice``. + + 0.6.4 (Oct 07, 2016) ==================== diff --git a/evdev/device.py b/evdev/device.py index d84ea5e..39cd355 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -266,6 +266,9 @@ def __repr__(self): msg = (self.__class__.__name__, self.fn) return '{}({!r})'.format(*msg) + def __fspath__(self): + return self.fn + def close(self): if self.fd > -1: try: From 388ce6fca4ed9391e503f3b4eaace4aa30b83398 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Fri, 16 Jun 2017 18:16:21 +0200 Subject: [PATCH 081/270] Add exception handling to functions returning futures --- docs/changelog.rst | 5 +++++ evdev/eventio_async.py | 10 ++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 2385713..5bcd6be 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -7,6 +7,10 @@ Unreleased - Support path protocol in ``InputDevice``. +- Exceptions raised during ``InputDevice.async_read()`` (and similar) are now + handled properly (i.e. an exception is set on the returned future instead of + leaking that exception into the event loop) (Fixes `#67`_). + 0.6.4 (Oct 07, 2016) ==================== @@ -269,3 +273,4 @@ Unreleased .. _issue21121: http://bugs.python.org/issue21121 .. _`#63`: https://github.com/gvalkov/python-evdev/issues/63 +.. _`#63`: https://github.com/gvalkov/python-evdev/issues/67 diff --git a/evdev/eventio_async.py b/evdev/eventio_async.py index ef2259a..dbb1a0d 100644 --- a/evdev/eventio_async.py +++ b/evdev/eventio_async.py @@ -14,13 +14,19 @@ def ready(): callback() loop.add_reader(self.fileno(), ready) + def _set_result(self, future, cb): + try: + future.set_result(cb()) + except Exception as error: + future.set_exception(error) + def async_read_one(self): ''' Asyncio coroutine to read and return a single input event as an instance of :class:`InputEvent `. ''' future = asyncio.Future() - self._do_when_readable(lambda: future.set_result(self.read_one())) + self._do_when_readable(lambda: self._set_result(future, self.read_one)) return future def async_read(self): @@ -30,7 +36,7 @@ def async_read(self): instances. ''' future = asyncio.Future() - self._do_when_readable(lambda: future.set_result(self.read())) + self._do_when_readable(lambda: self._set_result(future, self.read)) return future def async_read_loop(self): From e87617af7d927fdcfc6f06ba67f1870fc9ed459a Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Fri, 16 Jun 2017 18:50:00 +0200 Subject: [PATCH 082/270] Support path-like objects in InputDevice constructor --- docs/changelog.rst | 9 ++++++++- evdev/device.py | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 5bcd6be..933ab38 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,7 +5,14 @@ Changelog Unreleased ==================== -- Support path protocol in ``InputDevice``. +- ``InputDevice`` now accepts objects that support the path protocol. For + example:: + + pth = pathlib.Path('/dev/input/event0') + dev = evdev.InputDevice(pth) + +- Support path protocol in ``InputDevice``. This means that ``InputDevice`` + instances can be passed to callers that expect a ``os.PathLike`` object. - Exceptions raised during ``InputDevice.async_read()`` (and similar) are now handled properly (i.e. an exception is set on the returned future instead of diff --git a/evdev/device.py b/evdev/device.py index 39cd355..6a1db57 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -114,12 +114,12 @@ def __init__(self, dev): ''' Arguments --------- - dev : str + dev : str|bytes|PathLike Path to input device ''' #: Path to input device. - self.fn = dev + self.fn = dev if not hasattr(dev, '__fspath__') else dev.__fspath__() # Certain operations are possible only when the device is opened in # read-write mode. From 36dda463c1777cea8521317b19a9283431a77a32 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Fri, 16 Jun 2017 18:59:46 +0200 Subject: [PATCH 083/270] Bump version: 0.6.4 -> 0.7.0 --- docs/changelog.rst | 6 +++--- docs/conf.py | 2 +- setup.cfg | 2 +- setup.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 933ab38..1997e6e 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -2,11 +2,11 @@ Changelog --------- -Unreleased +0.7.0 (Jun 16, 2017) ==================== -- ``InputDevice`` now accepts objects that support the path protocol. For - example:: +- ``InputDevice`` now accepts objects that support the path protocol. + For example:: pth = pathlib.Path('/dev/input/event0') dev = evdev.InputDevice(pth) diff --git a/docs/conf.py b/docs/conf.py index f0eec9a..7a8ff20 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -60,7 +60,7 @@ # built documents. # # The full version, including alpha/beta/rc tags. -release = '0.6.4' +release = '0.7.0' # The short X.Y version. version = release diff --git a/setup.cfg b/setup.cfg index 425816b..770fb4e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.6.4 +current_version = 0.7.0 message = Bump version: {current_version} -> {new_version} commit = True tag = True diff --git a/setup.py b/setup.py index 8edd91b..59870c7 100755 --- a/setup.py +++ b/setup.py @@ -43,7 +43,7 @@ #----------------------------------------------------------------------------- kw = { 'name': 'evdev', - 'version': '0.6.4', + 'version': '0.7.0', 'description': 'Bindings to the Linux input handling subsystem', 'long_description': open(pjoin(here, 'README.rst')).read(), From f86e913eca102a3eef0d63f0bd5b72e965b8afa5 Mon Sep 17 00:00:00 2001 From: Wild Kat Date: Sun, 30 Jul 2017 15:58:46 +0200 Subject: [PATCH 084/270] add python 3.6 classifier to setuptools and pypi --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 59870c7..3433659 100755 --- a/setup.py +++ b/setup.py @@ -27,6 +27,7 @@ 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', 'Operating System :: POSIX :: Linux', 'Intended Audience :: Developers', 'Topic :: Software Development :: Libraries', From 49aac55731b2d7b0d19768c450b02841d21f2725 Mon Sep 17 00:00:00 2001 From: rkoe Date: Wed, 27 Sep 2017 15:13:41 +0200 Subject: [PATCH 085/270] fix example-code (categorize -> evdev.categorize) --- docs/usage.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage.rst b/docs/usage.rst index 47f8a79..35a7b7b 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -29,7 +29,7 @@ Reading events from a device >>> for event in device.read_loop(): ... if event.type == evdev.ecodes.EV_KEY: - ... print(categorize(event)) + ... print(evdev.categorize(event)) ... # pressing 'a' and holding 'space' key event at 1337016188.396030, 30 (KEY_A), down key event at 1337016188.492033, 30 (KEY_A), up From e02b051f35e6d471715074b333cc3eb01a01f4c4 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Thu, 28 Dec 2017 17:07:06 +0100 Subject: [PATCH 086/270] Add InputDevice.__ne__ to fix device != comparison on Python 2 --- evdev/device.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/evdev/device.py b/evdev/device.py index 6a1db57..d1dbf4c 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -258,6 +258,11 @@ def __eq__(self, other): ''' return isinstance(other, self.__class__) and self.info == other.info + def __ne__(self, other): + # Python 2 compatibility. Python 3 automatically negates the value of + # __eq__, in case __ne__ is not defined. + return not self == other + def __str__(self): msg = 'device {}, name "{}", phys "{}"' return msg.format(self.fn, self.name, self.phys) From 711606f4a7e41a967c763e03fada9f6046b38f09 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Thu, 28 Dec 2017 17:09:07 +0100 Subject: [PATCH 087/270] Include device path in equality comparison --- evdev/device.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/evdev/device.py b/evdev/device.py index d1dbf4c..e2e6863 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -256,7 +256,8 @@ def __eq__(self, other): ''' Two devices are equal if their :data:`info` attributes are equal. ''' - return isinstance(other, self.__class__) and self.info == other.info + return isinstance(other, self.__class__) and self.info == other.info \ + and self.path == other.path def __ne__(self, other): # Python 2 compatibility. Python 3 automatically negates the value of From a2335a3468f3dc42ca57607283603e65c71b0782 Mon Sep 17 00:00:00 2001 From: castis Date: Sun, 21 Jan 2018 20:47:08 -0500 Subject: [PATCH 088/270] Removing unnecessary code --- docs/tutorial.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 83981e0..da8b272 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -352,7 +352,7 @@ Create ``uinput`` device with capabilities of another device >>> from evdev import UInput, InputDevice - >>> mouse = InputDevice('/dev/input/event1')from evdev import UInput, InputDevice + >>> mouse = InputDevice('/dev/input/event1') >>> keybd = '/dev/input/event2' >>> ui = UInput.from_device(mouse, keybd, name='keyboard-mouse-device') From 8ab9f81200267622da55ad30147a7aa9360c544e Mon Sep 17 00:00:00 2001 From: ismailof Date: Thu, 15 Feb 2018 23:35:35 +0100 Subject: [PATCH 089/270] Fix merging capabilities of several devices (Issue #20) --- evdev/uinput.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/evdev/uinput.py b/evdev/uinput.py index ca73fc8..18e39d5 100644 --- a/evdev/uinput.py +++ b/evdev/uinput.py @@ -3,6 +3,7 @@ import os import stat import time +from collections import defaultdict from evdev import _uinput from evdev import ecodes, util, device @@ -51,9 +52,14 @@ def from_device(cls, *devices, **kwargs): dev = device.InputDevice(str(dev)) device_instances.append(dev) - all_capabilities = {} + # merge device capabilities + all_capabilities = defaultdict(list) for dev in device_instances: - all_capabilities.update(dev.capabilities()) + dev_caps = dev.capabilities() + for ev_type, ev_codes in dev_caps.iteritems(): + all_capabilities[ev_type].extend(ev_codes) + # remove duplicate ev_codes + all_capabilities[ev_type] = list(set(all_capabilities[ev_type])) del all_capabilities[ecodes.EV_SYN] From 6ec052b90b98e6e1901585d27cee0f6941d650b4 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Thu, 22 Feb 2018 23:55:30 +0100 Subject: [PATCH 090/270] Simplify and make Python 3.x compatible --- evdev/uinput.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/evdev/uinput.py b/evdev/uinput.py index 18e39d5..4cd0676 100644 --- a/evdev/uinput.py +++ b/evdev/uinput.py @@ -52,14 +52,12 @@ def from_device(cls, *devices, **kwargs): dev = device.InputDevice(str(dev)) device_instances.append(dev) - # merge device capabilities - all_capabilities = defaultdict(list) + all_capabilities = defaultdict(set) + + # Merge the capabilities of all devices into one dictionary. for dev in device_instances: - dev_caps = dev.capabilities() - for ev_type, ev_codes in dev_caps.iteritems(): - all_capabilities[ev_type].extend(ev_codes) - # remove duplicate ev_codes - all_capabilities[ev_type] = list(set(all_capabilities[ev_type])) + for ev_type, ev_codes in dev.capabilities().items(): + all_capabilities[ev_type].update(ev_codes) del all_capabilities[ecodes.EV_SYN] From fd0ecf21772f37b06be1c702cca79eb285136b3d Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 25 Feb 2018 13:01:40 +0100 Subject: [PATCH 091/270] Make excluded event types configurable and exclude EV_FF by default --- evdev/uinput.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/evdev/uinput.py b/evdev/uinput.py index 4cd0676..e5d4905 100644 --- a/evdev/uinput.py +++ b/evdev/uinput.py @@ -32,7 +32,7 @@ class UInput(EventIO): ) @classmethod - def from_device(cls, *devices, **kwargs): + def from_device(cls, *devices, filtered_types=(ecodes.EV_SYN, ecodes.EV_FF), **kwargs): ''' Create an UInput device with the capabilities of one or more input devices. @@ -42,6 +42,9 @@ def from_device(cls, *devices, **kwargs): devices : InputDevice|str Varargs of InputDevice instances or paths to input devices. + filtered_types : Tuple[event type codes] + Event types to exclude from the capabilities of the uinput device. + **kwargs Keyword arguments to UInput constructor (i.e. name, vendor etc.). ''' @@ -59,7 +62,8 @@ def from_device(cls, *devices, **kwargs): for ev_type, ev_codes in dev.capabilities().items(): all_capabilities[ev_type].update(ev_codes) - del all_capabilities[ecodes.EV_SYN] + for evtype in filtered_types: + del all_capabilities[evtype] return cls(events=all_capabilities, **kwargs) From f8fc8197220d164efa168578d0b0eb1ef6a59d15 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 11 Mar 2018 10:52:34 +0100 Subject: [PATCH 092/270] Update distribution links --- docs/install.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/install.rst b/docs/install.rst index de18858..d1abea7 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -7,13 +7,13 @@ Python-evdev has been packaged for the following GNU/Linux distributions: .. raw:: html -Consult the relevant documentation of your OS package manager for installation instructions. +Consult the documentation of your OS package manager for installation instructions. From source From 92670c1a12d8cc8df39afb0ede7be9d0db1bf68a Mon Sep 17 00:00:00 2001 From: brandonmoak Date: Sun, 5 Aug 2018 21:33:17 -0700 Subject: [PATCH 120/270] fixing typo in ff Condition, left_coeff This was preventing any force feedback effect that needs the left coefficient from working --- evdev/ff.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evdev/ff.py b/evdev/ff.py index e4598d7..dd68c3f 100644 --- a/evdev/ff.py +++ b/evdev/ff.py @@ -100,7 +100,7 @@ class Condition(ctypes.Structure): ('right_saturation', _u16), ('left_saturation', _u16), ('right_coeff', _s16), - ('left_foeff', _s16), + ('left_coeff', _s16), ('deadband', _u16), ('center', _s16), ] From 34fe42c0b5bf015b4b8315833b19d32d0b4de440 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Thu, 12 Jul 2018 21:32:34 -0700 Subject: [PATCH 121/270] Split "create" and "setup" parts for uinput devices creation In order to be able to enable EV_FF in uinput device, ff_max_effects fields needs to be set to a non-zero value. However the ordering of execution in uinput.py does not allow for that because indvidual events are enabled before _uinput.create() is invoked. In order to accomodate EV_FF, split _uinput.create() into two functions: - _uinput.setup(), which does the majority of device configuration. - _uinput.create(), which only creates the device without changing any of its configuration On top of that, change the code in uinput.py to make the call to setup() before individual events are enabled to enable adding ff_max_effects configuration in follow-up commits. --- evdev/uinput.c | 21 ++++++++++++++++++++- evdev/uinput.py | 4 +++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/evdev/uinput.c b/evdev/uinput.c index 791d3a9..0d2d282 100644 --- a/evdev/uinput.c +++ b/evdev/uinput.c @@ -69,7 +69,7 @@ uinput_set_phys(PyObject *self, PyObject *args) static PyObject * -uinput_create(PyObject *self, PyObject *args) { +uinput_setup(PyObject *self, PyObject *args) { int fd, len, i, abscode; uint16_t vendor, product, version, bustype; @@ -113,6 +113,22 @@ uinput_create(PyObject *self, PyObject *args) { /* goto on_err; */ /* } */ + Py_RETURN_NONE; + + on_err: + _uinput_close(fd); + PyErr_SetFromErrno(PyExc_IOError); + return NULL; +} + +static PyObject * +uinput_create(PyObject *self, PyObject *args) +{ + int fd; + + int ret = PyArg_ParseTuple(args, "i", &fd); + if (!ret) return NULL; + if (ioctl(fd, UI_DEV_CREATE) < 0) goto on_err; @@ -214,6 +230,9 @@ static PyMethodDef MethodTable[] = { { "open", uinput_open, METH_VARARGS, "Open uinput device node."}, + { "setup", uinput_setup, METH_VARARGS, + "Set an uinput device up."}, + { "create", uinput_create, METH_VARARGS, "Create an uinput device."}, diff --git a/evdev/uinput.py b/evdev/uinput.py index 4f910e2..b7c9fa5 100644 --- a/evdev/uinput.py +++ b/evdev/uinput.py @@ -131,6 +131,8 @@ def __init__(self, # Set phys name _uinput.set_phys(self.fd, phys) + _uinput.setup(self.fd, name, vendor, product, version, bustype, absinfo) + # Set device capabilities. for etype, codes in events.items(): for code in codes: @@ -149,7 +151,7 @@ def __init__(self, _uinput.enable(self.fd, etype, code) # Create the uinput device. - _uinput.create(self.fd, name, vendor, product, version, bustype, absinfo) + _uinput.create(self.fd) #: An :class:`InputDevice ` instance #: for the fake input device. ``None`` if the device cannot be From 71dcbe098ea0f5f8e5227cd35124fd6377af2817 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Thu, 12 Jul 2018 21:32:44 -0700 Subject: [PATCH 122/270] Set ff_effects_max to FF_EFFECTS_MAX in new uinput devices Set ff_effects_max to FF_EFFECTS_MAX in new uinput devices, so it would be possible to create devices that emit various EV_FF events. --- evdev/uinput.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/evdev/uinput.c b/evdev/uinput.c index 0d2d282..9ebff9d 100644 --- a/evdev/uinput.c +++ b/evdev/uinput.c @@ -89,6 +89,8 @@ uinput_setup(PyObject *self, PyObject *args) { uidev.id.version = version; uidev.id.bustype = bustype; + uidev.ff_effects_max = FF_MAX_EFFECTS; + len = PyList_Size(absinfo); for (i=0; i (ABS_X, 0, 255, 0, 0, 0, 0) From aa3b157eb71d4552c0f4294f0289b7b8e4b7be55 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Thu, 12 Jul 2018 21:32:56 -0700 Subject: [PATCH 123/270] Search for constants in as well A couple of important uinput constants are located in , so we need to scan that file in order to get them properly exposed. --- evdev/genecodes.py | 3 ++- setup.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/evdev/genecodes.py b/evdev/genecodes.py index 981a66d..eb6481a 100644 --- a/evdev/genecodes.py +++ b/evdev/genecodes.py @@ -20,7 +20,7 @@ #----------------------------------------------------------------------------- -macro_regex = r'#define +((?:KEY|ABS|REL|SW|MSC|LED|BTN|REP|SND|ID|EV|BUS|SYN|FF)_\w+)' +macro_regex = r'#define +((?:KEY|ABS|REL|SW|MSC|LED|BTN|REP|SND|ID|EV|BUS|SYN|FF|UI_FF)_\w+)' macro_regex = re.compile(macro_regex) uname = list(os.uname()); del uname[1] @@ -34,6 +34,7 @@ #include #else #include +#include #endif /* Automatically generated by evdev.genecodes */ diff --git a/setup.py b/setup.py index 3924880..cfa370a 100755 --- a/setup.py +++ b/setup.py @@ -70,6 +70,7 @@ def create_ecodes(headers=None): headers = [ '/usr/include/linux/input.h', '/usr/include/linux/input-event-codes.h', + '/usr/include/linux/uinput.h', ] headers = [header for header in headers if os.path.isfile(header)] From c9904b375a00d93b043104641141215cf1844070 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Fri, 13 Jul 2018 10:58:35 -0700 Subject: [PATCH 124/270] Add support for handling FF effects uploads Add code and type definitions needed to handle forced-feedback effect uploads done by the client of uinput device. The pluming added is a necessary minimum to implement algorithms listed here: https://elixir.bootlin.com/linux/v4.17/source/include/uapi/linux/uinput.h#L173 --- evdev/ff.py | 16 ++++++++++++++++ evdev/uinput.c | 19 +++++++++++++++++++ evdev/uinput.py | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+) diff --git a/evdev/ff.py b/evdev/ff.py index e4598d7..cb64af9 100644 --- a/evdev/ff.py +++ b/evdev/ff.py @@ -8,6 +8,7 @@ _u16 = ctypes.c_uint16 _u32 = ctypes.c_uint32 _s16 = ctypes.c_int16 +_s32 = ctypes.c_int32 class Replay(ctypes.Structure): ''' @@ -167,6 +168,21 @@ class Effect(ctypes.Structure): ('u', EffectType) ] +class UInputUpload(ctypes.Structure): + _fields_ = [ + ('request_id', _u32), + ('retval', _s32), + ('effect', Effect), + ('old', Effect), + ] + +class UInputErase(ctypes.Structure): + _fields_ = [ + ('request_id', _u32), + ('retval', _s32), + ('effect_id', _u32), + ] + # ff_types = { # ecodes.FF_CONSTANT, # ecodes.FF_PERIODIC, diff --git a/evdev/uinput.c b/evdev/uinput.c index 9ebff9d..2580543 100644 --- a/evdev/uinput.c +++ b/evdev/uinput.c @@ -224,6 +224,25 @@ uinput_enable_event(PyObject *self, PyObject *args) return NULL; } +int _uinput_begin_upload(int fd, struct uinput_ff_upload *upload) +{ + return ioctl(fd, UI_BEGIN_FF_UPLOAD, upload); +} + +int _uinput_end_upload(int fd, struct uinput_ff_upload *upload) +{ + return ioctl(fd, UI_END_FF_UPLOAD, upload); +} + +int _uinput_begin_erase(int fd, struct uinput_ff_erase *upload) +{ + return ioctl(fd, UI_BEGIN_FF_ERASE, upload); +} + +int _uinput_end_erase(int fd, struct uinput_ff_erase *upload) +{ + return ioctl(fd, UI_END_FF_ERASE, upload); +} #define MODULE_NAME "_uinput" #define MODULE_HELP "Python bindings for parts of linux/uinput.c" diff --git a/evdev/uinput.py b/evdev/uinput.py index b7c9fa5..ee28aa9 100644 --- a/evdev/uinput.py +++ b/evdev/uinput.py @@ -8,6 +8,8 @@ from evdev import _uinput from evdev import ecodes, util, device from evdev.events import InputEvent +import evdev.ff as ff +import ctypes try: from evdev.eventio_async import EventIO @@ -153,6 +155,10 @@ def __init__(self, # Create the uinput device. _uinput.create(self.fd) + self.dll = ctypes.CDLL(_uinput.__file__) + self.dll._uinput_begin_upload.restype = ctypes.c_int + self.dll._uinput_end_upload.restype = ctypes.c_int + #: An :class:`InputDevice ` instance #: for the fake input device. ``None`` if the device cannot be #: opened for reading and writing. @@ -208,6 +214,35 @@ def capabilities(self, verbose=False, absinfo=True): return self.device.capabilities(verbose, absinfo) + def begin_upload(self, effect_id): + upload = ff.UInputUpload() + upload.effect_id = effect_id + + if self.dll._uinput_begin_upload(self.fd, ctypes.byref(upload)): + raise UInputError('Failed to begin uinput upload: ' + + os.strerror()) + + return upload + + def end_upload(self, upload): + if self.dll._uinput_end_upload(self.fd, ctypes.byref(upload)): + raise UInputError('Failed to end uinput upload: ' + + os.strerror()) + + def begin_erase(self, effect_id): + erase = ff.UInputErase() + erase.effect_id = effect_id + + if self.dll._uinput_begin_erase(self.fd, ctypes.byref(erase)): + raise UInputError('Failed to begin uinput erase: ' + + os.strerror()) + return erase + + def end_erase(self, erase): + if self.dll._uinput_end_erase(self.fd, ctypes.byref(erase)): + raise UInputError('Failed to end uinput erase: ' + + os.strerror()) + def _verify(self): ''' Verify that an uinput device exists and is readable and writable From 1fb9e4cf75b689a52ee920ede5dc51af82d90521 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Thu, 9 Aug 2018 16:02:40 -0700 Subject: [PATCH 125/270] Add ruble effect tracing to print_ff_effects() --- evdev/input.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/evdev/input.c b/evdev/input.c index 86c54e9..b3e6e08 100644 --- a/evdev/input.c +++ b/evdev/input.c @@ -381,6 +381,11 @@ void print_ff_effect(struct ff_effect* effect) { effect->u.constant.envelope.attack_level, effect->u.constant.envelope.fade_length, effect->u.constant.envelope.fade_level); + + case FF_RUMBLE: + fprintf(stderr, " rumble: (%d, %d)\n", + effect->u.rumble.strong_magnitude, + effect->u.rumble.weak_magnitude); break; } } From 3c6a12eee054e20453a00368ddf50b37ef0b9946 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Thu, 9 Aug 2018 16:03:28 -0700 Subject: [PATCH 126/270] Add examples of working with FF-effects --- docs/tutorial.rst | 90 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 14337c5..9a5129e 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -362,3 +362,93 @@ Create ``uinput`` device with capabilities of another device .. _`async/await`: https://docs.python.org/3/library/asyncio-task.html + +Create ``uinput`` device capable of recieving FF-effects +======================================================== + +:: + import asyncio + from evdev import UInput, categorize, ecodes as e + + cap = { + e.EV_FF : [ e.FF_RUMBLE ], + e.EV_KEY : [e.KEY_A, e.KEY_B], + } + + try: + ui = UInput(cap, name='test-controller', version=0x3) + except UInputError: + print('Please make sure you have appropriate permissions to access /dev/uinput and the "uinput" kernel module is loaded') + raise + + async def print_events(device): + async for event in device.async_read_loop(): + print(categorize(event)) + # + # Wait for EV_UINPUT event that will signal us that effect + # upload/erase operation is in progress + # + if event.type != e.EV_UINPUT: + pass + + if event.code == e.UI_FF_UPLOAD: + upload = device.begin_upload(event.value) + upload.retval = 0 + + print('[upload] effect_id: {}, type: {}' + .format(upload.effect_id, upload.effect.type)) + device.end_upload(upload) + + elif event.code == e.UI_FF_ERASE: + erase = device.begin_erase(event.value) + print('[erase] effect_id {}'.format(erase.effect_id)) + + erase.retval = 0 + device.end_erase(erase) + + asyncio.ensure_future(print_events(ui)) + loop = asyncio.get_event_loop() + loop.run_forever() + + +Injecting FF-events into first FF-capable device found +====================================================== + +:: + from evdev import ecodes as e + from glob import glob + + dev = None + + # + # Find first EV_FF capable event device (that we have permissions to + # use) + # + for name in glob('/dev/input/event*'): + try: + dev = InputDevice(name) + + if e.EV_FF in dev.capabilities(): + break + except PermissionError as e: + print(e) + continue + + if dev: + duration_ms = 1000 + + rumble = ff.Rumble(strong_magnitude=0x0000, + weak_magnitude=0xffff) + effect_type = ff.EffectType(ff_rumble_effect=rumble) + effect = ff.Effect(e.FF_RUMBLE, -1, 0, + ff.Trigger(0, 0), + ff.Replay(duration_ms, 0), + effect_type) + + repeat_count = 1 + effect_id = dev.upload_effect(effect) + dev.write(e.EV_FF, effect_id, repeat_count) + dev.erase_effect(effect_id) + else: + print('No FF capable device was found!') + From df81554f4f166662da1ad53da4ea686383bb76a4 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Tue, 21 Aug 2018 20:22:41 +0200 Subject: [PATCH 127/270] Simplify effect upload examples --- docs/tutorial.rst | 94 +++++++++++++++++++---------------------------- 1 file changed, 38 insertions(+), 56 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 9a5129e..d0a4279 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -367,41 +367,36 @@ Create ``uinput`` device capable of recieving FF-effects ======================================================== :: + import asyncio - from evdev import UInput, categorize, ecodes as e + from evdev import UInput, categorize, ecodes cap = { - e.EV_FF : [ e.FF_RUMBLE ], - e.EV_KEY : [e.KEY_A, e.KEY_B], + ecodes.EV_FF: [ecodes.FF_RUMBLE ], + ecodes.EV_KEY: [ecodes.KEY_A, ecodes.KEY_B] } - try: - ui = UInput(cap, name='test-controller', version=0x3) - except UInputError: - print('Please make sure you have appropriate permissions to access /dev/uinput and the "uinput" kernel module is loaded') - raise + ui = UInput(cap, name='test-controller', version=0x3) async def print_events(device): async for event in device.async_read_loop(): print(categorize(event)) - # - # Wait for EV_UINPUT event that will signal us that effect - # upload/erase operation is in progress - # - if event.type != e.EV_UINPUT: + + # Wait for an EV_UINPUT event that will signal us that an + # effect upload/erase operation is in progress. + if event.type != ecodes.EV_UINPUT: pass - if event.code == e.UI_FF_UPLOAD: + if event.code == ecodes.UI_FF_UPLOAD: upload = device.begin_upload(event.value) upload.retval = 0 - print('[upload] effect_id: {}, type: {}' - .format(upload.effect_id, upload.effect.type)) + print(f'[upload] effect_id: {upload.effect_id}, type: {upload.effect.type}') device.end_upload(upload) - elif event.code == e.UI_FF_ERASE: + elif event.code == ecodes.UI_FF_ERASE: erase = device.begin_erase(event.value) - print('[erase] effect_id {}'.format(erase.effect_id)) + print(f'[erase] effect_id {erase.effect_id}') erase.retval = 0 device.end_erase(erase) @@ -411,44 +406,31 @@ Create ``uinput`` device capable of recieving FF-effects loop.run_forever() -Injecting FF-events into first FF-capable device found -====================================================== +Injecting an FF-event into first FF-capable device found +======================================================== :: - from evdev import ecodes as e - from glob import glob - - dev = None - - # - # Find first EV_FF capable event device (that we have permissions to - # use) - # - for name in glob('/dev/input/event*'): - try: - dev = InputDevice(name) - - if e.EV_FF in dev.capabilities(): - break - except PermissionError as e: - print(e) - continue - - if dev: - duration_ms = 1000 - - rumble = ff.Rumble(strong_magnitude=0x0000, - weak_magnitude=0xffff) - effect_type = ff.EffectType(ff_rumble_effect=rumble) - effect = ff.Effect(e.FF_RUMBLE, -1, 0, - ff.Trigger(0, 0), - ff.Replay(duration_ms, 0), - effect_type) - - repeat_count = 1 - effect_id = dev.upload_effect(effect) - dev.write(e.EV_FF, effect_id, repeat_count) - dev.erase_effect(effect_id) - else: - print('No FF capable device was found!') + from evdev import ecodes, InputDevice, ff + + # Find first EV_FF capable event device (that we have permissions to use). + for name in evdev.list_devices(): + dev = InputDevice(name) + if ecodes.EV_FF in dev.capabilities(): + break + + rumble = ff.Rumble(strong_magnitude=0x0000, weak_magnitude=0xffff) + effect_type = ff.EffectType(ff_rumble_effect=rumble) + duration_ms = 1000 + + effect = ff.Effect( + ecodes.FF_RUMBLE, -1, 0, + ff.Trigger(0, 0), + ff.Replay(duration_ms, 0), + ff.EffectType(ff_rumble_effect=rumble) + ) + + repeat_count = 1 + effect_id = dev.upload_effect(effect) + dev.write(e.EV_FF, effect_id, repeat_count) + dev.erase_effect(effect_id) From 78a4727772a82f147b2b4049e5ace0ecd775e889 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Mon, 27 Aug 2018 21:11:05 +0200 Subject: [PATCH 128/270] Update changelog --- docs/changelog.rst | 9 +++++++++ evdev/uinput.c | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 1afbc18..03c0a43 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,14 @@ Changelog --------- +1.1.0 (Aug 27, 2018) +==================== + +- Add support for handling force-feedback effect uploads (many thanks to `@ndreys`). + +- Fix typo preventing ff effects that need left coefficients from working. + + 1.0.0 (Jun 02, 2018) ==================== @@ -335,6 +343,7 @@ Changelog .. _`@isia`: https://github.com/isia .. _`@forsenonlhaimaisentito`: https://github.com/forsenonlhaimaisentito .. _`@paulo-raca`: https://github.com/paulo-raca +.. _`@ndreys`: https://github.com/ndreys .. _issue21121: http://bugs.python.org/issue21121 .. _`#63`: https://github.com/gvalkov/python-evdev/issues/63 diff --git a/evdev/uinput.c b/evdev/uinput.c index 2580543..011d868 100644 --- a/evdev/uinput.c +++ b/evdev/uinput.c @@ -268,7 +268,7 @@ static PyMethodDef MethodTable[] = { { "set_phys", uinput_set_phys, METH_VARARGS, "Set physical path"}, - + { NULL, NULL, 0, NULL} }; From ae19a3a9d0f9637f0cb0166426a009ca42514488 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Mon, 27 Aug 2018 21:11:25 +0200 Subject: [PATCH 129/270] Bump version: 1.0.0 -> 1.1.0 --- docs/conf.py | 2 +- setup.cfg | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 01de55f..35f7f4f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -60,7 +60,7 @@ # built documents. # # The full version, including alpha/beta/rc tags. -release = '1.0.0' +release = '1.1.0' # The short X.Y version. version = release diff --git a/setup.cfg b/setup.cfg index 5b380df..e0fe229 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.0.0 +current_version = 1.1.0 message = Bump version: {current_version} -> {new_version} commit = True tag = True diff --git a/setup.py b/setup.py index cfa370a..3f28181 100755 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ #----------------------------------------------------------------------------- kw = { 'name': 'evdev', - 'version': '1.0.0', + 'version': '1.1.0', 'description': 'Bindings to the Linux input handling subsystem', 'long_description': open(pjoin(here, 'README.rst')).read(), From 686cc5094100206c6d7fdbd7ef9de2c9a6906ad8 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sat, 1 Sep 2018 11:39:38 +0200 Subject: [PATCH 130/270] Check if FF_MAX_EFFECTS is defined --- evdev/uinput.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/evdev/uinput.c b/evdev/uinput.c index 011d868..528d75f 100644 --- a/evdev/uinput.c +++ b/evdev/uinput.c @@ -16,6 +16,12 @@ #include #endif + +// Workaround for installing on kernels newer than 4.4. +#ifndef FF_MAX_EFFECTS +#define FF_MAX_EFFECTS FF_GAIN; +#endif + int _uinput_close(int fd) { if (ioctl(fd, UI_DEV_DESTROY) < 0) { @@ -88,7 +94,6 @@ uinput_setup(PyObject *self, PyObject *args) { uidev.id.product = product; uidev.id.version = version; uidev.id.bustype = bustype; - uidev.ff_effects_max = FF_MAX_EFFECTS; len = PyList_Size(absinfo); From 481b697ba1df154454e186274faa05ad92ba321e Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sat, 1 Sep 2018 19:12:27 +0200 Subject: [PATCH 131/270] Fix invalid example --- docs/tutorial.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index d0a4279..e229244 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -325,7 +325,7 @@ Specifying ``uinput`` device options ... (e.ABS_X, AbsInfo(value=0, min=0, max=255, ... fuzz=0, flat=0, resolution=0)), ... (e.ABS_Y, AbsInfo(0, 0, 255, 0, 0, 0)), - ... (e.ABS_MT_POSITION_X, (0, 255, 128, 0)) ] + ... (e.ABS_MT_POSITION_X, (0, 128, 255, 0)) ] ... } >>> ui = UInput(cap, name='example-device', version=0x3) From a017827a5102d39d639d23affdc8e7b159e6e231 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sat, 1 Sep 2018 18:35:02 +0200 Subject: [PATCH 132/270] Fix handling of absinfo capabilities --- evdev/uinput.py | 55 ++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/evdev/uinput.py b/evdev/uinput.py index ee28aa9..eafdd4f 100644 --- a/evdev/uinput.py +++ b/evdev/uinput.py @@ -121,21 +121,37 @@ def __init__(self, if not events: events = {ecodes.EV_KEY: ecodes.keys.keys()} - # The min, max, fuzz and flat values for the absolute axis for - # a given code. - absinfo = [] - self._verify() #: Write-only, non-blocking file descriptor to the uinput device node. self.fd = _uinput.open(devnode) + # Prepare the list of events for passing to _uinput.enable and _uinput.setup. + absinfo, prepared_events = self._prepare_events(events) + # Set phys name _uinput.set_phys(self.fd, phys) + for etype, code in prepared_events: + _uinput.enable(self.fd, etype, code) + _uinput.setup(self.fd, name, vendor, product, version, bustype, absinfo) - # Set device capabilities. + # Create the uinput device. + _uinput.create(self.fd) + + self.dll = ctypes.CDLL(_uinput.__file__) + self.dll._uinput_begin_upload.restype = ctypes.c_int + self.dll._uinput_end_upload.restype = ctypes.c_int + + #: An :class:`InputDevice ` instance + #: for the fake input device. ``None`` if the device cannot be + #: opened for reading and writing. + self.device = self._find_device() + + def _prepare_events(self, events): + '''Prepare events for passing to _uinput.enable and _uinput.setup''' + absinfo, prepared_events = [], [] for etype, codes in events.items(): for code in codes: # Handle max, min, fuzz, flat. @@ -148,21 +164,8 @@ def __init__(self, f.extend([0] * (6 - len(code[1]))) absinfo.append(f) code = code[0] - - # TODO: remove a lot of unnecessary packing/unpacking - _uinput.enable(self.fd, etype, code) - - # Create the uinput device. - _uinput.create(self.fd) - - self.dll = ctypes.CDLL(_uinput.__file__) - self.dll._uinput_begin_upload.restype = ctypes.c_int - self.dll._uinput_end_upload.restype = ctypes.c_int - - #: An :class:`InputDevice ` instance - #: for the fake input device. ``None`` if the device cannot be - #: opened for reading and writing. - self.device = self._find_device() + prepared_events.append((etype, code)) + return absinfo, prepared_events def __enter__(self): return self @@ -219,29 +222,25 @@ def begin_upload(self, effect_id): upload.effect_id = effect_id if self.dll._uinput_begin_upload(self.fd, ctypes.byref(upload)): - raise UInputError('Failed to begin uinput upload: ' + - os.strerror()) + raise UInputError('Failed to begin uinput upload: ' + os.strerror()) return upload def end_upload(self, upload): if self.dll._uinput_end_upload(self.fd, ctypes.byref(upload)): - raise UInputError('Failed to end uinput upload: ' + - os.strerror()) + raise UInputError('Failed to end uinput upload: ' + os.strerror()) def begin_erase(self, effect_id): erase = ff.UInputErase() erase.effect_id = effect_id if self.dll._uinput_begin_erase(self.fd, ctypes.byref(erase)): - raise UInputError('Failed to begin uinput erase: ' + - os.strerror()) + raise UInputError('Failed to begin uinput erase: ' + os.strerror()) return erase def end_erase(self, erase): if self.dll._uinput_end_erase(self.fd, ctypes.byref(erase)): - raise UInputError('Failed to end uinput erase: ' + - os.strerror()) + raise UInputError('Failed to end uinput erase: ' + os.strerror()) def _verify(self): ''' From 34a7535e8e160c78bf923adc48a1f1f6bcdcaae8 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sat, 1 Sep 2018 19:37:44 +0200 Subject: [PATCH 133/270] Bump version: 1.1.0 -> 1.1.2 --- docs/changelog.rst | 8 ++++++++ docs/conf.py | 2 +- setup.cfg | 2 +- setup.py | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 03c0a43..74ffe0e 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,14 @@ Changelog --------- +1.1.2 (Sep 1, 2018) +==================== + +- Fix installation on kernels <= 4.4. + +- Fix uinput creation ignoring absinfo settings. + + 1.1.0 (Aug 27, 2018) ==================== diff --git a/docs/conf.py b/docs/conf.py index 35f7f4f..a0b9c03 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -60,7 +60,7 @@ # built documents. # # The full version, including alpha/beta/rc tags. -release = '1.1.0' +release = '1.1.2' # The short X.Y version. version = release diff --git a/setup.cfg b/setup.cfg index e0fe229..79e6c99 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.1.0 +current_version = 1.1.2 message = Bump version: {current_version} -> {new_version} commit = True tag = True diff --git a/setup.py b/setup.py index 3f28181..c3e9e60 100755 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ #----------------------------------------------------------------------------- kw = { 'name': 'evdev', - 'version': '1.1.0', + 'version': '1.1.2', 'description': 'Bindings to the Linux input handling subsystem', 'long_description': open(pjoin(here, 'README.rst')).read(), From 9c027ce3e5564375a20cfcb4a8d95df2565af793 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Fri, 5 Oct 2018 08:32:13 +1000 Subject: [PATCH 134/270] input: fix unintended fallthrough --- evdev/input.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evdev/input.c b/evdev/input.c index b3e6e08..b37d3b7 100644 --- a/evdev/input.c +++ b/evdev/input.c @@ -381,7 +381,7 @@ void print_ff_effect(struct ff_effect* effect) { effect->u.constant.envelope.attack_level, effect->u.constant.envelope.fade_length, effect->u.constant.envelope.fade_level); - + break; case FF_RUMBLE: fprintf(stderr, " rumble: (%d, %d)\n", effect->u.rumble.strong_magnitude, From 061417d37f48ca6d77d6a5aacc4faa94be31cf62 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Fri, 5 Oct 2018 08:41:07 +1000 Subject: [PATCH 135/270] input: fix ioctl return value handling() If any of those ioctls fail, the returned data is undefined. --- evdev/input.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/evdev/input.c b/evdev/input.c index b37d3b7..67b9348 100644 --- a/evdev/input.c +++ b/evdev/input.c @@ -250,7 +250,10 @@ ioctl_EVIOCGREP(PyObject *self, PyObject *args) ret = PyArg_ParseTuple(args, "i", &fd); if (!ret) return NULL; - ioctl(fd, EVIOCGREP, &rep); + ret = ioctl(fd, EVIOCGREP, &rep); + if (ret == -1) + return NULL; + return Py_BuildValue("(ii)", rep[0], rep[1]); } @@ -265,6 +268,9 @@ ioctl_EVIOCSREP(PyObject *self, PyObject *args) if (!ret) return NULL; ret = ioctl(fd, EVIOCSREP, &rep); + if (ret == -1) + return NULL; + return Py_BuildValue("i", ret); } @@ -277,6 +283,9 @@ ioctl_EVIOCGVERSION(PyObject *self, PyObject *args) if (!ret) return NULL; ret = ioctl(fd, EVIOCGVERSION, &res); + if (ret == -1) + return NULL; + return Py_BuildValue("i", res); } @@ -338,6 +347,9 @@ ioctl_EVIOCG_bits(PyObject *self, PyObject *args) break; } + if (ret == -1) + return NULL; + PyObject* res = PyList_New(0); for (int i=0; i Date: Fri, 5 Oct 2018 08:30:03 +1000 Subject: [PATCH 136/270] uinput: avoid possible unterminated string. This is a false positive because the kernel does the right thing and takes UINPUT_MAX_NAME_SIZE into account. But this shuts up coverity and other static analyzers so I think we can live with a 79 character restriction instead of 80. Also change to use sizeof(), not that it really makes a difference but it allows for the array size to be changed while leaving the constant as-is, impossible as that is. --- evdev/uinput.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evdev/uinput.c b/evdev/uinput.c index 528d75f..96a4fec 100644 --- a/evdev/uinput.c +++ b/evdev/uinput.c @@ -89,7 +89,7 @@ uinput_setup(PyObject *self, PyObject *args) { if (!ret) return NULL; memset(&uidev, 0, sizeof(uidev)); - strncpy(uidev.name, name, UINPUT_MAX_NAME_SIZE); + strncpy(uidev.name, name, sizeof(uidev.name) - 1); uidev.id.vendor = vendor; uidev.id.product = product; uidev.id.version = version; From 316ae76020e4e578620ec3ff0c0da9522c28d916 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20V=C3=A1radi?= Date: Mon, 7 Jan 2019 11:44:39 +0100 Subject: [PATCH 137/270] Use H to parse uinput_setup arguments --- evdev/uinput.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evdev/uinput.c b/evdev/uinput.c index 96a4fec..d7e3406 100644 --- a/evdev/uinput.c +++ b/evdev/uinput.c @@ -84,7 +84,7 @@ uinput_setup(PyObject *self, PyObject *args) { struct uinput_user_dev uidev; const char* name; - int ret = PyArg_ParseTuple(args, "ishhhhO", &fd, &name, &vendor, + int ret = PyArg_ParseTuple(args, "isHHHHO", &fd, &name, &vendor, &product, &version, &bustype, &absinfo); if (!ret) return NULL; From 52576087e647d958cf64cd46479a1854d297c3e7 Mon Sep 17 00:00:00 2001 From: Linus Date: Sun, 31 Mar 2019 04:39:00 +0200 Subject: [PATCH 138/270] Add UInput support for the resolution parameter in AbsInfo Creating a UInput device always ignored the resolution parameter since the c implementation was old for current linux kernels. --- evdev/uinput.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/evdev/uinput.c b/evdev/uinput.c index d7e3406..cc23d7f 100644 --- a/evdev/uinput.c +++ b/evdev/uinput.c @@ -74,6 +74,68 @@ uinput_set_phys(PyObject *self, PyObject *args) } +#if defined(UI_DEV_SETUP) && defined(UI_ABS_SETUP) +// New variant is not supported in old linux kernels and FreeBSD +static PyObject * +uinput_setup(PyObject *self, PyObject *args) { + int fd, len, i; + uint16_t vendor, product, version, bustype; + + PyObject *absinfo = NULL, *item = NULL; + + struct uinput_abs_setup abs_setup; + + const char* name; + int ret = PyArg_ParseTuple(args, "isHHHHO", &fd, &name, &vendor, + &product, &version, &bustype, &absinfo); + if (!ret) return NULL; + + // Setup absinfo: + len = PyList_Size(absinfo); + for (i=0; i (ABS_X, 0, 255, 0, 0, 0, 0) + item = PyList_GetItem(absinfo, i); + + memset(&abs_setup, 0, sizeof(abs_setup)); // Clear struct + abs_setup.code = PyLong_AsLong(PyList_GetItem(item, 0)); + abs_setup.absinfo.value = PyLong_AsLong(PyList_GetItem(item, 1)); + abs_setup.absinfo.minimum = PyLong_AsLong(PyList_GetItem(item, 2)); + abs_setup.absinfo.maximum = PyLong_AsLong(PyList_GetItem(item, 3)); + abs_setup.absinfo.fuzz = PyLong_AsLong(PyList_GetItem(item, 4)); + abs_setup.absinfo.flat = PyLong_AsLong(PyList_GetItem(item, 5)); + abs_setup.absinfo.resolution = PyLong_AsLong(PyList_GetItem(item, 6)); + + if(ioctl(fd, UI_ABS_SETUP, &abs_setup) < 0) + goto on_err; + } + + + // Setup evdev: + struct uinput_setup usetup; + + memset(&usetup, 0, sizeof(usetup)); + strncpy(usetup.name, name, sizeof(usetup.name) - 1); + usetup.id.vendor = vendor; + usetup.id.product = product; + usetup.id.version = version; + usetup.id.bustype = bustype; + usetup.ff_effects_max = FF_MAX_EFFECTS; + ioctl(fd, UI_DEV_SETUP, &usetup); + + if(ioctl(fd, UI_DEV_SETUP, &usetup) < 0) + goto on_err; + + + Py_RETURN_NONE; + + on_err: + _uinput_close(fd); + PyErr_SetFromErrno(PyExc_IOError); + return NULL; +} +#else +// Old variant (fallback): static PyObject * uinput_setup(PyObject *self, PyObject *args) { int fd, len, i, abscode; @@ -127,6 +189,8 @@ uinput_setup(PyObject *self, PyObject *args) { PyErr_SetFromErrno(PyExc_IOError); return NULL; } +#endif + static PyObject * uinput_create(PyObject *self, PyObject *args) From 2c5d62dccaf56b659a1cadb7f335c83173da1e7c Mon Sep 17 00:00:00 2001 From: Linus Date: Tue, 2 Apr 2019 19:01:24 +0200 Subject: [PATCH 139/270] Removed uncecessary duplicated ioctl-call --- evdev/uinput.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/evdev/uinput.c b/evdev/uinput.c index cc23d7f..5b5b74c 100644 --- a/evdev/uinput.c +++ b/evdev/uinput.c @@ -93,7 +93,7 @@ uinput_setup(PyObject *self, PyObject *args) { // Setup absinfo: len = PyList_Size(absinfo); for (i=0; i (ABS_X, 0, 255, 0, 0, 0, 0) item = PyList_GetItem(absinfo, i); @@ -113,7 +113,7 @@ uinput_setup(PyObject *self, PyObject *args) { // Setup evdev: struct uinput_setup usetup; - + memset(&usetup, 0, sizeof(usetup)); strncpy(usetup.name, name, sizeof(usetup.name) - 1); usetup.id.vendor = vendor; @@ -121,7 +121,6 @@ uinput_setup(PyObject *self, PyObject *args) { usetup.id.version = version; usetup.id.bustype = bustype; usetup.ff_effects_max = FF_MAX_EFFECTS; - ioctl(fd, UI_DEV_SETUP, &usetup); if(ioctl(fd, UI_DEV_SETUP, &usetup) < 0) goto on_err; From 6799dd8d101fe925d9472c6e3fa15c9eebf2e371 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 7 Apr 2019 17:28:03 +0200 Subject: [PATCH 140/270] Add comments and remove extra newlines --- evdev/uinput.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/evdev/uinput.c b/evdev/uinput.c index 5b5b74c..2f1bee1 100644 --- a/evdev/uinput.c +++ b/evdev/uinput.c @@ -74,8 +74,12 @@ uinput_set_phys(PyObject *self, PyObject *args) } +// Different kernel versions have different device setup methods. You can read +// more about it here: +// https://github.com/torvalds/linux/commit/052876f8e5aec887d22c4d06e54aa5531ffcec75 + +// Setup function for kernel >= v4.5 #if defined(UI_DEV_SETUP) && defined(UI_ABS_SETUP) -// New variant is not supported in old linux kernels and FreeBSD static PyObject * uinput_setup(PyObject *self, PyObject *args) { int fd, len, i; @@ -110,7 +114,6 @@ uinput_setup(PyObject *self, PyObject *args) { goto on_err; } - // Setup evdev: struct uinput_setup usetup; @@ -125,7 +128,6 @@ uinput_setup(PyObject *self, PyObject *args) { if(ioctl(fd, UI_DEV_SETUP, &usetup) < 0) goto on_err; - Py_RETURN_NONE; on_err: @@ -133,8 +135,9 @@ uinput_setup(PyObject *self, PyObject *args) { PyErr_SetFromErrno(PyExc_IOError); return NULL; } + +// Fallback setup function (Linux <= 4.5 and FreeBSD). #else -// Old variant (fallback): static PyObject * uinput_setup(PyObject *self, PyObject *args) { int fd, len, i, abscode; From 1c7773c750c57834ad96a2505666981594fd1c5c Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 7 Apr 2019 17:28:16 +0200 Subject: [PATCH 141/270] Remove dead code --- evdev/uinput.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/evdev/uinput.c b/evdev/uinput.c index 2f1bee1..192568d 100644 --- a/evdev/uinput.c +++ b/evdev/uinput.c @@ -176,14 +176,6 @@ uinput_setup(PyObject *self, PyObject *args) { if (write(fd, &uidev, sizeof(uidev)) != sizeof(uidev)) goto on_err; - /* if (ioctl(fd, UI_SET_EVBIT, EV_KEY) < 0) */ - /* goto on_err; */ - /* int i; */ - /* for (i=0; i Date: Sun, 7 Apr 2019 17:40:25 +0200 Subject: [PATCH 142/270] Bump version: 1.1.2 -> 1.2.0 --- docs/changelog.rst | 16 +++++++++++++++- docs/conf.py | 2 +- setup.cfg | 2 +- setup.py | 2 +- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 74ffe0e..0af74a5 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,17 @@ Changelog --------- +1.2.0 (Apr 7, 2019) +==================== + +- Add UInput support for the resolution parameter in AbsInfo. This brings + support for the new method of uinput device setup, which was `introduced in + Linux 4.5`_ (thanks to `@LinusCDE`_). + +- Vendor and product identifiers can be greater or equal to `0x8000` (thanks + `@ivaradi`_). + + 1.1.2 (Sep 1, 2018) ==================== @@ -12,7 +23,7 @@ Changelog 1.1.0 (Aug 27, 2018) ==================== -- Add support for handling force-feedback effect uploads (many thanks to `@ndreys`). +- Add support for handling force-feedback effect uploads (many thanks to `@ndreys`_). - Fix typo preventing ff effects that need left coefficients from working. @@ -352,7 +363,10 @@ Changelog .. _`@forsenonlhaimaisentito`: https://github.com/forsenonlhaimaisentito .. _`@paulo-raca`: https://github.com/paulo-raca .. _`@ndreys`: https://github.com/ndreys +.. _`@LinusCDE`: https://github.com/gvalkov/python-evdev/pulls/LinusCDE +.. _`ivaradi`: https://github.com/gvalkov/python-evdev/pull/104 +.. _`introduced in Linux 4.5`: https://github.com/torvalds/linux/commit/052876f8e5aec887d22c4d06e54aa5531ffcec75 .. _issue21121: http://bugs.python.org/issue21121 .. _`#63`: https://github.com/gvalkov/python-evdev/issues/63 .. _`#63`: https://github.com/gvalkov/python-evdev/issues/67 diff --git a/docs/conf.py b/docs/conf.py index a0b9c03..f7e4a82 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -60,7 +60,7 @@ # built documents. # # The full version, including alpha/beta/rc tags. -release = '1.1.2' +release = '1.2.0' # The short X.Y version. version = release diff --git a/setup.cfg b/setup.cfg index 79e6c99..d1ce534 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.1.2 +current_version = 1.2.0 message = Bump version: {current_version} -> {new_version} commit = True tag = True diff --git a/setup.py b/setup.py index c3e9e60..086bdc8 100755 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ #----------------------------------------------------------------------------- kw = { 'name': 'evdev', - 'version': '1.1.2', + 'version': '1.2.0', 'description': 'Bindings to the Linux input handling subsystem', 'long_description': open(pjoin(here, 'README.rst')).read(), From 2e1c7f952077a733066095b0f08fc51e279d0f10 Mon Sep 17 00:00:00 2001 From: Stephen Kitt Date: Thu, 11 Jul 2019 17:58:05 +0200 Subject: [PATCH 143/270] Fix a couple of typos These were flagged by Lintian, Debian's code-quality tool. Signed-off-by: Stephen Kitt --- docs/changelog.rst | 2 +- docs/tutorial.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 0af74a5..265de54 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -134,7 +134,7 @@ Changelog 0.6.1 (Jun 04, 2016) ==================== -- Dissable tty echoing while evtest is running. +- Disable tty echoing while evtest is running. - Allow evtest to listen to more than one devices. - The setup.py script now allows the location of the input header files to be diff --git a/docs/tutorial.rst b/docs/tutorial.rst index e229244..92a8780 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -363,7 +363,7 @@ Create ``uinput`` device with capabilities of another device .. _`async/await`: https://docs.python.org/3/library/asyncio-task.html -Create ``uinput`` device capable of recieving FF-effects +Create ``uinput`` device capable of receiving FF-effects ======================================================== :: From bcafe723e489e00441bbd0c86d252df63c422e2c Mon Sep 17 00:00:00 2001 From: Khem Raj Date: Sat, 30 Nov 2019 11:21:20 -0800 Subject: [PATCH 144/270] Fix build on 32bit arches with 64bit time_t time element is deprecated on new input_event structure in kernel's input.h [1] [1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit?id=152194fe9c3f --- evdev/input.c | 13 +++++++++---- evdev/uinput.c | 9 ++++++++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/evdev/input.c b/evdev/input.c index 67b9348..432db92 100644 --- a/evdev/input.c +++ b/evdev/input.c @@ -24,6 +24,11 @@ #include #endif +#ifndef input_event_sec +#define input_event_sec time.tv_sec +#define input_event_usec time.tv_usec +#endif + #define MAX_NAME_SIZE 256 extern char* EV_NAME[EV_CNT]; @@ -60,8 +65,8 @@ device_read(PyObject *self, PyObject *args) return NULL; } - PyObject* sec = PyLong_FromLong(event.time.tv_sec); - PyObject* usec = PyLong_FromLong(event.time.tv_usec); + PyObject* sec = PyLong_FromLong(event.input_event_sec); + PyObject* usec = PyLong_FromLong(event.input_event_usec); PyObject* val = PyLong_FromLong(event.value); PyObject* py_input_event = NULL; @@ -102,8 +107,8 @@ device_read_many(PyObject *self, PyObject *args) // Construct a list of event tuples, which we'll make sense of in Python for (unsigned i = 0 ; i < nread/event_size ; i++) { - sec = PyLong_FromLong(event[i].time.tv_sec); - usec = PyLong_FromLong(event[i].time.tv_usec); + sec = PyLong_FromLong(event[i].input_event_sec); + usec = PyLong_FromLong(event[i].input_event_usec); val = PyLong_FromLong(event[i].value); py_input_event = Py_BuildValue("OOhhO", sec, usec, event[i].type, event[i].code, val); diff --git a/evdev/uinput.c b/evdev/uinput.c index 192568d..56fe86c 100644 --- a/evdev/uinput.c +++ b/evdev/uinput.c @@ -16,6 +16,10 @@ #include #endif +#ifndef input_event_sec +#define input_event_sec time.tv_sec +#define input_event_usec time.tv_usec +#endif // Workaround for installing on kernels newer than 4.4. #ifndef FF_MAX_EFFECTS @@ -232,8 +236,11 @@ uinput_write(PyObject *self, PyObject *args) if (!ret) return NULL; struct input_event event; + struct timeval tval; memset(&event, 0, sizeof(event)); - gettimeofday(&event.time, 0); + gettimeofday(&tval, 0); + event.input_event_usec = tval.tv_usec; + event.input_event_sec = tval.tv_sec; event.type = type; event.code = code; event.value = value; From a4cf8b5b6223aef1851fb8d5cf49af614c1c8c2f Mon Sep 17 00:00:00 2001 From: Quoing Date: Sat, 21 Dec 2019 13:30:10 +0100 Subject: [PATCH 145/270] Functionality to set device properties eg. INPUT_PROP_DIRECT or INPUT_PROP_POINTER --- .gitignore | 1 + evdev/device.py | 5 ++ evdev/geniprops.py | 116 +++++++++++++++++++++++++++++++++++++++++++++ evdev/input.c | 28 +++++++++++ evdev/uinput.c | 23 +++++++++ evdev/uinput.py | 6 ++- setup.py | 73 +++++++++++++++++++++++++++- 7 files changed, 249 insertions(+), 3 deletions(-) create mode 100644 evdev/geniprops.py diff --git a/.gitignore b/.gitignore index 7180328..8fc000b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ tags TAGS evdev/*.so evdev/ecodes.c +evdev/iprops.c docs/_build .#* __pycache__ diff --git a/evdev/device.py b/evdev/device.py index de0da82..6c8feec 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -223,6 +223,11 @@ def capabilities(self, verbose=False, absinfo=True): else: return self._capabilities(absinfo) + def props(self): + props = _input.ioctl_EVIOCGPROP(self.fd) + + return props + def leds(self, verbose=False): ''' Return currently set LED keys. diff --git a/evdev/geniprops.py b/evdev/geniprops.py new file mode 100644 index 0000000..6a60469 --- /dev/null +++ b/evdev/geniprops.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8; -*- + +''' +Generate a Python extension module with the constants defined in linux/input.h. +''' + +from __future__ import print_function +import os, sys, re + + +#----------------------------------------------------------------------------- +# The default header file locations to try. +headers = [ + '/usr/include/linux/input.h', + '/usr/include/linux/input-event-codes.h', +] + +if sys.argv[1:]: + headers = sys.argv[1:] + + +#----------------------------------------------------------------------------- +macro_regex = r'#define +((?:INPUT_PROP)_\w+).*' +macro_regex = re.compile(macro_regex) + +uname = list(os.uname()); del uname[1] +uname = ' '.join(uname) + + +#----------------------------------------------------------------------------- +template = r''' +#include +#ifdef __FreeBSD__ +#include +#else +#include +#include +#endif + +/* Automatically generated by evdev.geniprops */ +/* Generated on %s */ + +#define MODULE_NAME "iprops" +#define MODULE_HELP "linux/input.h macros" + +static PyMethodDef MethodTable[] = { + { NULL, NULL, 0, NULL} +}; + +#if PY_MAJOR_VERSION >= 3 +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + MODULE_NAME, + MODULE_HELP, + -1, /* m_size */ + MethodTable, /* m_methods */ + NULL, /* m_reload */ + NULL, /* m_traverse */ + NULL, /* m_clear */ + NULL, /* m_free */ +}; +#endif + +static PyObject * +moduleinit(void) +{ + +#if PY_MAJOR_VERSION >= 3 + PyObject* m = PyModule_Create(&moduledef); +#else + PyObject* m = Py_InitModule3(MODULE_NAME, MethodTable, MODULE_HELP); +#endif + + if (m == NULL) return NULL; + +%s + + return m; +} + +#if PY_MAJOR_VERSION >= 3 +PyMODINIT_FUNC +PyInit__iprops(void) +{ + return moduleinit(); +} +#else +PyMODINIT_FUNC +init_iprops(void) +{ + moduleinit(); +} +#endif +''' + +def parse_header(header): + for line in open(header): + macro = macro_regex.search(line) + if macro: + yield ' PyModule_AddIntMacro(m, %s);' % macro.group(1) + +all_macros = [] +for header in headers: + try: + fh = open(header) + except (IOError, OSError): + continue + all_macros += parse_header(header) + +if not all_macros: + print('no input macros found in: %s' % ' '.join(headers), file=sys.stderr) + sys.exit(1) + + +macros = os.linesep.join(all_macros) +print(template % (uname, macros)) diff --git a/evdev/input.c b/evdev/input.c index 432db92..9fd7ec1 100644 --- a/evdev/input.c +++ b/evdev/input.c @@ -454,6 +454,33 @@ erase_effect(PyObject *self, PyObject *args) return Py_None; } +static PyObject * +ioctl_EVIOCGPROP(PyObject *self, PyObject *args) +{ + int fd, ret; + + ret = PyArg_ParseTuple(args, "i", &fd); + if (!ret) return NULL; + + char bytes[(INPUT_PROP_MAX+7)/8]; + memset(bytes, 0, sizeof bytes); + + ret = ioctl(fd, EVIOCGPROP(sizeof(bytes)), &bytes); + + if (ret == -1) + return NULL; + + PyObject* res = PyList_New(0); + for (int i=0; i Date: Sat, 11 Jan 2020 23:19:15 +0100 Subject: [PATCH 146/270] Rework input props functionality - Merged geniprops into genecodes and reverted the changes to setup.py. - Renamed InputDevice.props to InputDevice.input_props. - Renamed UInput(props=) to UInput(input_props=). --- docs/changelog.rst | 9 ++++ evdev/device.py | 18 ++++++- evdev/ecodes.py | 7 +-- evdev/genecodes.py | 2 +- evdev/geniprops.py | 116 --------------------------------------------- evdev/input.c | 2 +- evdev/uinput.c | 2 +- evdev/uinput.py | 14 ++++-- setup.py | 73 +--------------------------- 9 files changed, 44 insertions(+), 199 deletions(-) delete mode 100644 evdev/geniprops.py diff --git a/docs/changelog.rst b/docs/changelog.rst index 265de54..dc359ed 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,15 @@ Changelog --------- +Master +==================== + +- Fix build on 32bit arches with 64bit time_t + +- Add functionality to query device properties. See ``InputDevice.input_props`` + and the ``input_props`` argument to ``Uinput``. + + 1.2.0 (Apr 7, 2019) ==================== diff --git a/evdev/device.py b/evdev/device.py index 6c8feec..e0893c4 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -223,8 +223,24 @@ def capabilities(self, verbose=False, absinfo=True): else: return self._capabilities(absinfo) - def props(self): + def input_props(self, verbose=False): + ''' + Get device properties and quirks. + + Example + ------- + >>> device.input_props() + [0, 5] + + If ``verbose`` is ``True``, input properties are resolved to their + names. Unknown codes are resolved to ``'?'``:: + + [('INPUT_PROP_POINTER', 0), ('INPUT_PROP_POINTING_STICK', 5)] + + ''' props = _input.ioctl_EVIOCGPROP(self.fd) + if verbose: + return util.resolve_ecodes(ecodes.INPUT_PROP, props) return props diff --git a/evdev/ecodes.py b/evdev/ecodes.py index f0b5961..e3e4ffc 100644 --- a/evdev/ecodes.py +++ b/evdev/ecodes.py @@ -7,7 +7,7 @@ Exposed constants:: KEY, ABS, REL, SW, MSC, LED, BTN, REP, SND, ID, EV, - BUS, SYN, FF, FF_STATUS + BUS, SYN, FF, FF_STATUS, INPUT_PROP This module also provides reverse and forward mappings of the names and values of the above mentioned constants:: @@ -47,7 +47,7 @@ #: Mapping of names to values. ecodes = {} -prefixes = 'KEY ABS REL SW MSC LED BTN REP SND ID EV BUS SYN FF_STATUS FF' +prefixes = 'KEY ABS REL SW MSC LED BTN REP SND ID EV BUS SYN FF_STATUS FF INPUT_PROP' prev_prefix = '' g = globals() @@ -94,7 +94,8 @@ _ecodes.EV_SND: SND, _ecodes.EV_SYN: SYN, _ecodes.EV_FF: FF, - _ecodes.EV_FF_STATUS: FF_STATUS, } + _ecodes.EV_FF_STATUS: FF_STATUS, +} from evdev._ecodes import * diff --git a/evdev/genecodes.py b/evdev/genecodes.py index eb6481a..04c10e1 100644 --- a/evdev/genecodes.py +++ b/evdev/genecodes.py @@ -20,7 +20,7 @@ #----------------------------------------------------------------------------- -macro_regex = r'#define +((?:KEY|ABS|REL|SW|MSC|LED|BTN|REP|SND|ID|EV|BUS|SYN|FF|UI_FF)_\w+)' +macro_regex = r'#define +((?:KEY|ABS|REL|SW|MSC|LED|BTN|REP|SND|ID|EV|BUS|SYN|FF|UI_FF|INPUT_PROP)_\w+)' macro_regex = re.compile(macro_regex) uname = list(os.uname()); del uname[1] diff --git a/evdev/geniprops.py b/evdev/geniprops.py deleted file mode 100644 index 6a60469..0000000 --- a/evdev/geniprops.py +++ /dev/null @@ -1,116 +0,0 @@ -# -*- coding: utf-8; -*- - -''' -Generate a Python extension module with the constants defined in linux/input.h. -''' - -from __future__ import print_function -import os, sys, re - - -#----------------------------------------------------------------------------- -# The default header file locations to try. -headers = [ - '/usr/include/linux/input.h', - '/usr/include/linux/input-event-codes.h', -] - -if sys.argv[1:]: - headers = sys.argv[1:] - - -#----------------------------------------------------------------------------- -macro_regex = r'#define +((?:INPUT_PROP)_\w+).*' -macro_regex = re.compile(macro_regex) - -uname = list(os.uname()); del uname[1] -uname = ' '.join(uname) - - -#----------------------------------------------------------------------------- -template = r''' -#include -#ifdef __FreeBSD__ -#include -#else -#include -#include -#endif - -/* Automatically generated by evdev.geniprops */ -/* Generated on %s */ - -#define MODULE_NAME "iprops" -#define MODULE_HELP "linux/input.h macros" - -static PyMethodDef MethodTable[] = { - { NULL, NULL, 0, NULL} -}; - -#if PY_MAJOR_VERSION >= 3 -static struct PyModuleDef moduledef = { - PyModuleDef_HEAD_INIT, - MODULE_NAME, - MODULE_HELP, - -1, /* m_size */ - MethodTable, /* m_methods */ - NULL, /* m_reload */ - NULL, /* m_traverse */ - NULL, /* m_clear */ - NULL, /* m_free */ -}; -#endif - -static PyObject * -moduleinit(void) -{ - -#if PY_MAJOR_VERSION >= 3 - PyObject* m = PyModule_Create(&moduledef); -#else - PyObject* m = Py_InitModule3(MODULE_NAME, MethodTable, MODULE_HELP); -#endif - - if (m == NULL) return NULL; - -%s - - return m; -} - -#if PY_MAJOR_VERSION >= 3 -PyMODINIT_FUNC -PyInit__iprops(void) -{ - return moduleinit(); -} -#else -PyMODINIT_FUNC -init_iprops(void) -{ - moduleinit(); -} -#endif -''' - -def parse_header(header): - for line in open(header): - macro = macro_regex.search(line) - if macro: - yield ' PyModule_AddIntMacro(m, %s);' % macro.group(1) - -all_macros = [] -for header in headers: - try: - fh = open(header) - except (IOError, OSError): - continue - all_macros += parse_header(header) - -if not all_macros: - print('no input macros found in: %s' % ' '.join(headers), file=sys.stderr) - sys.exit(1) - - -macros = os.linesep.join(all_macros) -print(template % (uname, macros)) diff --git a/evdev/input.c b/evdev/input.c index 9fd7ec1..7a7c0c2 100644 --- a/evdev/input.c +++ b/evdev/input.c @@ -491,11 +491,11 @@ static PyMethodDef MethodTable[] = { { "ioctl_EVIOCGRAB", ioctl_EVIOCGRAB, METH_VARARGS}, { "ioctl_EVIOCGEFFECTS", ioctl_EVIOCGEFFECTS, METH_VARARGS, "fetch the number of effects the device can keep in its memory." }, { "ioctl_EVIOCG_bits", ioctl_EVIOCG_bits, METH_VARARGS, "get state of KEY|LED|SND|SW"}, + { "ioctl_EVIOCGPROP", ioctl_EVIOCGPROP, METH_VARARGS, "get device properties"}, { "device_read", device_read, METH_VARARGS, "read an input event from a device" }, { "device_read_many", device_read_many, METH_VARARGS, "read all available input events from a device" }, { "upload_effect", upload_effect, METH_VARARGS, "" }, { "erase_effect", erase_effect, METH_VARARGS, "" }, - { "ioctl_EVIOCGPROP", ioctl_EVIOCGPROP, METH_VARARGS, "get device properties"}, { NULL, NULL, 0, NULL} }; diff --git a/evdev/uinput.c b/evdev/uinput.c index 297ef2d..20318ce 100644 --- a/evdev/uinput.c +++ b/evdev/uinput.c @@ -360,7 +360,7 @@ static PyMethodDef MethodTable[] = { "Set physical path"}, { "set_prop", uinput_set_prop, METH_VARARGS, - "Set device property"}, + "Set device input property"}, { NULL, NULL, 0, NULL} }; diff --git a/evdev/uinput.py b/evdev/uinput.py index a3c9b27..8b9a597 100644 --- a/evdev/uinput.py +++ b/evdev/uinput.py @@ -77,7 +77,7 @@ def __init__(self, events=None, name='py-evdev-uinput', vendor=0x1, product=0x1, version=0x1, bustype=0x3, - devnode='/dev/uinput', phys='py-evdev-uinput', props=[]): + devnode='/dev/uinput', phys='py-evdev-uinput', input_props=None): ''' Arguments --------- @@ -96,13 +96,16 @@ def __init__(self, Product identifier. version - version identifier. + Version identifier. bustype - bustype identifier. + Bustype identifier. phys - physical path. + Physical path. + + input_props + Input properties and quirks. Note ---- @@ -133,7 +136,8 @@ def __init__(self, _uinput.set_phys(self.fd, phys) # Set properties - for prop in props: + input_props = input_props or [] + for prop in input_props: _uinput.set_prop(self.fd, prop) for etype, code in prepared_events: diff --git a/setup.py b/setup.py index b2a3f71..086bdc8 100755 --- a/setup.py +++ b/setup.py @@ -40,7 +40,6 @@ input_c = Extension('evdev._input', sources=['evdev/input.c'], extra_compile_args=cflags) uinput_c = Extension('evdev._uinput', sources=['evdev/uinput.c'], extra_compile_args=cflags) ecodes_c = Extension('evdev._ecodes', sources=['evdev/ecodes.c'], extra_compile_args=cflags) -iprops_c = Extension('evdev.iprops', sources=['evdev/iprops.c'], extra_compile_args=cflags) #----------------------------------------------------------------------------- kw = { @@ -58,7 +57,7 @@ 'classifiers': classifiers, 'packages': ['evdev'], - 'ext_modules': [input_c, uinput_c, ecodes_c, iprops_c], + 'ext_modules': [input_c, uinput_c, ecodes_c], 'include_package_data': False, 'zip_safe': True, 'cmdclass': {}, @@ -107,48 +106,6 @@ def create_ecodes(headers=None): check_call(cmd, cwd="%s/evdev" % here, shell=True) -#----------------------------------------------------------------------------- -def create_iprops(headers=None): - if not headers: - headers = [ - '/usr/include/linux/input.h', - '/usr/include/linux/input-event-codes.h', - '/usr/include/linux/uinput.h', - ] - - headers = [header for header in headers if os.path.isfile(header)] - if not headers: - msg = '''\ - The 'linux/input.h' and 'linux/input-event-codes.h' include files - are missing. You will have to install the kernel header files in - order to continue: - - yum install kernel-headers-$(uname -r) - apt-get install linux-headers-$(uname -r) - emerge sys-kernel/linux-headers - pacman -S kernel-headers - - In case they are installed in a non-standard location, you may use - the '--evdev-headers' option to specify one or more colon-separated - paths. For example: - - python setup.py \\ - build \\ - build_iprops --evdev-headers path/input.h:path/input-event-codes.h \\ - build_ext --include-dirs path/ \\ - install - ''' - - sys.stderr.write(textwrap.dedent(msg)) - sys.exit(1) - - from subprocess import check_call - - print('writing iprops.c (using %s)' % ' '.join(headers)) - cmd = '%s geniprops.py %s > iprops.c' % (sys.executable, ' '.join(headers)) - check_call(cmd, cwd="%s/evdev" % here, shell=True) - - #----------------------------------------------------------------------------- class build_ecodes(Command): description = 'generate ecodes.c' @@ -168,24 +125,6 @@ def run(self): create_ecodes(self.evdev_headers) -class build_iprops(Command): - description = 'generate iprops.c' - - user_options = [ - ('evdev-headers=', None, 'colon-separated paths to input subsystem headers'), - ] - - def initialize_options(self): - self.evdev_headers = None - - def finalize_options(self): - if self.evdev_headers: - self.evdev_headers = self.evdev_headers.split(':') - - def run(self): - create_iprops(self.evdev_headers) - - class build_ext(_build_ext.build_ext): def has_ecodes(self): ecodes_path = os.path.join(here, 'evdev/ecodes.c') @@ -194,25 +133,17 @@ def has_ecodes(self): print('ecodes.c already exists ... skipping build_ecodes') return not res - def has_iprops(self): - iprops_path = os.path.join(here, 'evdev/iprops.c') - res = os.path.exists(iprops_path) - if res: - print('iprops.c already exists ... skipping build_iprops') - return not res - def run(self): for cmd_name in self.get_sub_commands(): self.run_command(cmd_name) _build_ext.build_ext.run(self) - sub_commands = [('build_ecodes', has_ecodes), ('build_iprops', has_iprops)] + _build_ext.build_ext.sub_commands + sub_commands = [('build_ecodes', has_ecodes)] + _build_ext.build_ext.sub_commands #----------------------------------------------------------------------------- kw['cmdclass']['build_ext'] = build_ext kw['cmdclass']['build_ecodes'] = build_ecodes -kw['cmdclass']['build_iprops'] = build_iprops #----------------------------------------------------------------------------- From 05fd3fa3cae85e6f4ee5471437d692de2bc24d7a Mon Sep 17 00:00:00 2001 From: TheMadScientist1234 <40321768+TheMadScientist1234@users.noreply.github.com> Date: Mon, 24 Jun 2019 14:52:18 -0400 Subject: [PATCH 147/270] Handle unknown keys Add to the `keys` dictionary when an unknown key is pressed. --- evdev/events.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/evdev/events.py b/evdev/events.py index 6c75588..36eab8b 100644 --- a/evdev/events.py +++ b/evdev/events.py @@ -94,7 +94,9 @@ def __init__(self, event): elif event.value == 1: self.keystate = KeyEvent.key_down - self.keycode = keys[event.code] # :todo: + if not (event.code in keys): + keys[event.code] = ''.join('{:02X}'.format(event.code)) + self.keycode = keys[event.code] self.scancode = event.code #: Reference to an :class:`InputEvent` instance. From 8d549b97d4d818f9f58c023712a110d8f1a0440c Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sat, 11 Jan 2020 23:46:50 +0100 Subject: [PATCH 148/270] Add the allow_unknown argument to KeyEvent --- docs/changelog.rst | 5 +++++ evdev/events.py | 21 ++++++++++++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index dc359ed..8278d3e 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -9,6 +9,11 @@ Master - Add functionality to query device properties. See ``InputDevice.input_props`` and the ``input_props`` argument to ``Uinput``. +- ``KeyEvent`` received an ``allow_unknown`` constructor argument, which + determines what will happen when an event code cannot be mapped to a keycode. + The default and behavior so far has been to raise ``KeyError``. If set to + ``True``, the keycode will be set to the event code formatted as a hex number. + 1.2.0 (Apr 7, 2019) ==================== diff --git a/evdev/events.py b/evdev/events.py index 36eab8b..14bb0ce 100644 --- a/evdev/events.py +++ b/evdev/events.py @@ -86,7 +86,15 @@ class KeyEvent(object): __slots__ = 'scancode', 'keycode', 'keystate', 'event' - def __init__(self, event): + def __init__(self, event, allow_unknown=False): + ''' + The ``allow_unknown`` argument determines what to do in the event of a event code + for which a key code cannot be found. If ``False`` a ``KeyError`` will be raised. + If ``True`` the keycode will be set to the hex value of the event code. + ''' + + self.scancode = event.code + if event.value == 0: self.keystate = KeyEvent.key_up elif event.value == 2: @@ -94,10 +102,13 @@ def __init__(self, event): elif event.value == 1: self.keystate = KeyEvent.key_down - if not (event.code in keys): - keys[event.code] = ''.join('{:02X}'.format(event.code)) - self.keycode = keys[event.code] - self.scancode = event.code + try: + self.keycode = keys[event.code] + except KeyError: + if allow_unknown: + self.keycode = '0x{:02X}'.format(event.code) + else: + raise #: Reference to an :class:`InputEvent` instance. self.event = event From 05e36c2488e00e86dc1610a5edae22ce7fb6fb8d Mon Sep 17 00:00:00 2001 From: Arti Zirk Date: Tue, 9 Apr 2019 16:26:22 +0300 Subject: [PATCH 149/270] Expose get/set absinfo ioctl commands Changing AbsInfo data in kernel is useful for setting min/max calibration for other programs and flatness/fuzz settings are useful for kernel side filtering of input events --- evdev/device.py | 50 +++++++++++++++++++++++++++++++++++++++++ evdev/input.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) diff --git a/evdev/device.py b/evdev/device.py index e0893c4..14afaa6 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -398,3 +398,53 @@ def fn(self): msg = 'Please use {0}.path instead of {0}.fn'.format(self.__class__.__name__) warnings.warn(msg, DeprecationWarning, stacklevel=2) return self.path + + def get_absinfo(self, axis_num): + """ + Return current :class:`AbsInfo` for input device axis + + Arguments + --------- + axis_num : int + EV_ABS keycode (example :attr:`ecodes.ABS_X`) + + Example + ------- + + >>> device.get_absinfo(ecodes.ABS_X) + AbsInfo(value=1501, min=-32768, max=32767, fuzz=0, flat=128, resolution=0) + + """ + return AbsInfo(*_input.ioctl_EVIOCGABS(self.fd, axis_num)) + + def set_absinfo(self, axis_num, value=None, min=None, max=None, fuzz=None, flat=None, resolution=None): + """ + Set AbsInfo values for input device. + + Only values set will be overwritten. + + See :class:`AbsInfo` for more info about the arguments + + Arguments + --------- + axis_num : int + EV_ABS keycode (example :attr:`ecodes.ABS_X`) + + Example + ------- + >>> device.set_absinfo(ecodes.ABS_X, min=-2000, max=2000) + + You can also unpack AbsInfo tuple that will overwrite all values + + >>> device.set_absinfo(ecodes.ABS_Y, *AbsInfo(0, -2000, 2000, 0, 15, 0)) + + + """ + cur_absinfo = self.get_absinfo(axis_num) + new_absinfo = AbsInfo(value if value else cur_absinfo.value, + min if min else cur_absinfo.min, + max if max else cur_absinfo.max, + fuzz if fuzz else cur_absinfo.fuzz, + flat if flat else cur_absinfo.flat, + resolution if resolution else cur_absinfo.resolution) + _input.ioctl_EVIOCSABS(self.fd, axis_num, new_absinfo) diff --git a/evdev/input.c b/evdev/input.c index 7a7c0c2..b09102b 100644 --- a/evdev/input.c +++ b/evdev/input.c @@ -247,6 +247,63 @@ ioctl_devinfo(PyObject *self, PyObject *args) } +static PyObject * +ioctl_EVIOCGABS(PyObject *self, PyObject *args) +{ + int fd, ev_code; + struct input_absinfo absinfo; + PyObject* py_absinfo = NULL; + + int ret = PyArg_ParseTuple(args, "ii", &fd, &ev_code); + if (!ret) return NULL; + + memset(&absinfo, 0, sizeof(absinfo)); + ret = ioctl(fd, EVIOCGABS(ev_code), &absinfo); + if (ret == -1) { + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + + py_absinfo = Py_BuildValue("(iiiiii)", + absinfo.value, + absinfo.minimum, + absinfo.maximum, + absinfo.fuzz, + absinfo.flat, + absinfo.resolution); + return py_absinfo; +} + + +static PyObject * +ioctl_EVIOCSABS(PyObject *self, PyObject *args) +{ + int fd, ev_code; + struct input_absinfo absinfo; + + int ret = PyArg_ParseTuple(args, + "ii(iiiiii)", + &fd, + &ev_code, + &absinfo.value, + &absinfo.minimum, + &absinfo.maximum, + &absinfo.fuzz, + &absinfo.flat, + &absinfo.resolution); + if (!ret) return NULL; + + ret = ioctl(fd, EVIOCSABS(ev_code), &absinfo); + if (ret == -1) { + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + + static PyObject * ioctl_EVIOCGREP(PyObject *self, PyObject *args) { @@ -485,6 +542,8 @@ ioctl_EVIOCGPROP(PyObject *self, PyObject *args) static PyMethodDef MethodTable[] = { { "ioctl_devinfo", ioctl_devinfo, METH_VARARGS, "fetch input device info" }, { "ioctl_capabilities", ioctl_capabilities, METH_VARARGS, "fetch input device capabilities" }, + { "ioctl_EVIOCGABS", ioctl_EVIOCGABS, METH_VARARGS, "get input device absinfo"}, + { "ioctl_EVIOCSABS", ioctl_EVIOCSABS, METH_VARARGS, "set input device absinfo"}, { "ioctl_EVIOCGREP", ioctl_EVIOCGREP, METH_VARARGS}, { "ioctl_EVIOCSREP", ioctl_EVIOCSREP, METH_VARARGS}, { "ioctl_EVIOCGVERSION", ioctl_EVIOCGVERSION, METH_VARARGS}, From 2fa3d246e1116f3301ee935dbecdc81ffc8a709d Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sat, 11 Jan 2020 23:59:03 +0100 Subject: [PATCH 150/270] Minor changes to docstrings --- docs/changelog.rst | 2 ++ evdev/device.py | 17 +++++------------ 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 8278d3e..8df0b48 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -14,6 +14,8 @@ Master The default and behavior so far has been to raise ``KeyError``. If set to ``True``, the keycode will be set to the event code formatted as a hex number. +- Add ``InputDevice.set_absinfo()`` and ``InputDevice.get_absinfo()``. + 1.2.0 (Apr 7, 2019) ==================== diff --git a/evdev/device.py b/evdev/device.py index 14afaa6..1c17c6d 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -400,7 +400,7 @@ def fn(self): return self.path def get_absinfo(self, axis_num): - """ + ''' Return current :class:`AbsInfo` for input device axis Arguments @@ -410,20 +410,14 @@ def get_absinfo(self, axis_num): Example ------- - >>> device.get_absinfo(ecodes.ABS_X) AbsInfo(value=1501, min=-32768, max=32767, fuzz=0, flat=128, resolution=0) - - """ + ''' return AbsInfo(*_input.ioctl_EVIOCGABS(self.fd, axis_num)) def set_absinfo(self, axis_num, value=None, min=None, max=None, fuzz=None, flat=None, resolution=None): - """ - Set AbsInfo values for input device. - - Only values set will be overwritten. - - See :class:`AbsInfo` for more info about the arguments + ''' + Update :class:`AbsInfo` values. Only specified values will be overwritten. Arguments --------- @@ -437,9 +431,8 @@ def set_absinfo(self, axis_num, value=None, min=None, max=None, fuzz=None, flat= You can also unpack AbsInfo tuple that will overwrite all values >>> device.set_absinfo(ecodes.ABS_Y, *AbsInfo(0, -2000, 2000, 0, 15, 0)) + ''' - - """ cur_absinfo = self.get_absinfo(axis_num) new_absinfo = AbsInfo(value if value else cur_absinfo.value, min if min else cur_absinfo.min, From c115d5b7745000d706a6d434888258a1a1ac20be Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 12 Jan 2020 00:20:13 +0100 Subject: [PATCH 151/270] Call loop.remove_reader() when InputDevice is closed --- docs/changelog.rst | 3 +++ evdev/device.py | 1 + evdev/eventio.py | 3 +++ evdev/eventio_async.py | 4 ++++ 4 files changed, 11 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 8df0b48..6c10d3c 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -16,6 +16,9 @@ Master - Add ``InputDevice.set_absinfo()`` and ``InputDevice.get_absinfo()``. +- Instruct the asyncio event loop to stop monitoring the fd of the input device + when the device is closed. + 1.2.0 (Apr 7, 2019) ==================== diff --git a/evdev/device.py b/evdev/device.py index 1c17c6d..84bd73e 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -301,6 +301,7 @@ def __fspath__(self): def close(self): if self.fd > -1: try: + super().close() os.close(self.fd) finally: self.fd = -1 diff --git a/evdev/eventio.py b/evdev/eventio.py index fafdd93..22c37e2 100644 --- a/evdev/eventio.py +++ b/evdev/eventio.py @@ -134,3 +134,6 @@ def write(self, etype, code, value): ''' _uinput.write(self.fd, etype, code, value) + + def close(self): + pass diff --git a/evdev/eventio_async.py b/evdev/eventio_async.py index e899f58..d3b8ac3 100644 --- a/evdev/eventio_async.py +++ b/evdev/eventio_async.py @@ -50,6 +50,10 @@ def async_read_loop(self): ''' return ReadIterator(self) + def close(self): + loop = asyncio.get_event_loop() + loop.remove_reader(self.fileno()) + class ReadIterator(object): def __init__(self, device): From 1cc547b3cf583dfd704737a23678d072e57cf66c Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 12 Jan 2020 00:33:23 +0100 Subject: [PATCH 152/270] Rename get_absinfo() to absinfo() for consistency with the other poorly named methods --- docs/changelog.rst | 2 +- evdev/device.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 6c10d3c..c3254e7 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -14,7 +14,7 @@ Master The default and behavior so far has been to raise ``KeyError``. If set to ``True``, the keycode will be set to the event code formatted as a hex number. -- Add ``InputDevice.set_absinfo()`` and ``InputDevice.get_absinfo()``. +- Add ``InputDevice.set_absinfo()`` and ``InputDevice.absinfo()``. - Instruct the asyncio event loop to stop monitoring the fd of the input device when the device is closed. diff --git a/evdev/device.py b/evdev/device.py index 84bd73e..94fa037 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -400,7 +400,7 @@ def fn(self): warnings.warn(msg, DeprecationWarning, stacklevel=2) return self.path - def get_absinfo(self, axis_num): + def absinfo(self, axis_num): ''' Return current :class:`AbsInfo` for input device axis @@ -411,7 +411,7 @@ def get_absinfo(self, axis_num): Example ------- - >>> device.get_absinfo(ecodes.ABS_X) + >>> device.absinfo(ecodes.ABS_X) AbsInfo(value=1501, min=-32768, max=32767, fuzz=0, flat=128, resolution=0) ''' return AbsInfo(*_input.ioctl_EVIOCGABS(self.fd, axis_num)) @@ -434,7 +434,7 @@ def set_absinfo(self, axis_num, value=None, min=None, max=None, fuzz=None, flat= >>> device.set_absinfo(ecodes.ABS_Y, *AbsInfo(0, -2000, 2000, 0, 15, 0)) ''' - cur_absinfo = self.get_absinfo(axis_num) + cur_absinfo = self.absinfo(axis_num) new_absinfo = AbsInfo(value if value else cur_absinfo.value, min if min else cur_absinfo.min, max if max else cur_absinfo.max, From 646ac0c3cdab3d8f5be14c5b24ffdfae654d09a9 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 12 Jan 2020 00:37:28 +0100 Subject: [PATCH 153/270] Add input properties to output of evtest --- evdev/evtest.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/evdev/evtest.py b/evdev/evtest.py index 982744a..49e2c35 100644 --- a/evdev/evtest.py +++ b/evdev/evtest.py @@ -121,6 +121,7 @@ def devicenum(device_path): def print_capabilities(device): capabilities = device.capabilities(verbose=True) + input_props = device.input_props(verbose=True) print('Device name: {.name}'.format(device)) print('Device info: {.info}'.format(device)) @@ -133,6 +134,12 @@ def print_capabilities(device): active_keys = ','.join(k[0] for k in device.active_keys(True)) print('Active keys: %s\n' % active_keys) + if input_props: + print('Input properties:') + for type, code in input_props: + print(' %s %s' % (type, code)) + print() + print('Device capabilities:') for type, codes in capabilities.items(): print(' Type {} {}:'.format(*type)) From 43c8791d33ca1cc1ed787c7a7e29a2fcf9652a08 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 12 Jan 2020 00:41:29 +0100 Subject: [PATCH 154/270] Bump version: 1.2.0 -> 1.3.0 --- docs/changelog.rst | 2 +- docs/conf.py | 2 +- setup.cfg | 2 +- setup.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index c3254e7..e9cef1b 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,7 +1,7 @@ Changelog --------- -Master +1.3.0 (Jan 12, 2020) ==================== - Fix build on 32bit arches with 64bit time_t diff --git a/docs/conf.py b/docs/conf.py index f7e4a82..3b8bde3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -60,7 +60,7 @@ # built documents. # # The full version, including alpha/beta/rc tags. -release = '1.2.0' +release = '1.3.0' # The short X.Y version. version = release diff --git a/setup.cfg b/setup.cfg index d1ce534..fa36a17 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.2.0 +current_version = 1.3.0 message = Bump version: {current_version} -> {new_version} commit = True tag = True diff --git a/setup.py b/setup.py index 086bdc8..9f5d8ef 100755 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ #----------------------------------------------------------------------------- kw = { 'name': 'evdev', - 'version': '1.2.0', + 'version': '1.3.0', 'description': 'Bindings to the Linux input handling subsystem', 'long_description': open(pjoin(here, 'README.rst')).read(), From 0cdf86adf9576ba343c97a31bd0368c889e8d494 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 12 Jan 2020 00:45:01 +0100 Subject: [PATCH 155/270] Fix links in documentation --- docs/changelog.rst | 4 ++-- requirements-dev.txt | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index e9cef1b..8266989 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -383,9 +383,9 @@ Changelog .. _`@paulo-raca`: https://github.com/paulo-raca .. _`@ndreys`: https://github.com/ndreys .. _`@LinusCDE`: https://github.com/gvalkov/python-evdev/pulls/LinusCDE -.. _`ivaradi`: https://github.com/gvalkov/python-evdev/pull/104 +.. _`@ivaradi`: https://github.com/gvalkov/python-evdev/pull/104 .. _`introduced in Linux 4.5`: https://github.com/torvalds/linux/commit/052876f8e5aec887d22c4d06e54aa5531ffcec75 .. _issue21121: http://bugs.python.org/issue21121 .. _`#63`: https://github.com/gvalkov/python-evdev/issues/63 -.. _`#63`: https://github.com/gvalkov/python-evdev/issues/67 +.. _`#67`: https://github.com/gvalkov/python-evdev/issues/67 diff --git a/requirements-dev.txt b/requirements-dev.txt index 0561107..41728fd 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,3 +2,4 @@ pytest>=3.4.0 Sphinx>=1.7.0 bump2version sphinx_rtd_theme +twine From fb3f9e8559f3df2da84f317164ab18cdd26a0d34 Mon Sep 17 00:00:00 2001 From: Olivier Dormond Date: Sat, 30 May 2020 13:31:14 +0200 Subject: [PATCH 156/270] Fix set_absinfo to allow setting parameters to zero --- docs/changelog.rst | 6 ++++++ evdev/device.py | 12 ++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 8266989..07abba6 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,12 @@ Changelog --------- +Master +==================== + +- Fix ``InputDevice.set_absinfo`` to allow setting parameters to zero. + + 1.3.0 (Jan 12, 2020) ==================== diff --git a/evdev/device.py b/evdev/device.py index 94fa037..6949183 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -435,10 +435,10 @@ def set_absinfo(self, axis_num, value=None, min=None, max=None, fuzz=None, flat= ''' cur_absinfo = self.absinfo(axis_num) - new_absinfo = AbsInfo(value if value else cur_absinfo.value, - min if min else cur_absinfo.min, - max if max else cur_absinfo.max, - fuzz if fuzz else cur_absinfo.fuzz, - flat if flat else cur_absinfo.flat, - resolution if resolution else cur_absinfo.resolution) + new_absinfo = AbsInfo(value if value is not None else cur_absinfo.value, + min if min is not None else cur_absinfo.min, + max if max is not None else cur_absinfo.max, + fuzz if fuzz is not None else cur_absinfo.fuzz, + flat if flat is not None else cur_absinfo.flat, + resolution if resolution is not None else cur_absinfo.resolution) _input.ioctl_EVIOCSABS(self.fd, axis_num, new_absinfo) From 1db1c053d1cf6e4b4be8e31d9066a9b0c42f1c75 Mon Sep 17 00:00:00 2001 From: Frederik Aalund Date: Wed, 12 Aug 2020 16:55:39 +0200 Subject: [PATCH 157/270] Fix off-by-one in ioctl_EVIOCG_bits This bug caused values at the end of the list to not be reported back. Example: On my system, SW_PEN_INSERTED (0x0f) is the last switch constant. That is, SW_MAX=SW_PEN_INSERTED. Other tools (python-libevdev) correctly reported this switch as 'set' but python-evdev did not. This commit fixes this issue. --- evdev/input.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evdev/input.c b/evdev/input.c index b09102b..0a7b0d0 100644 --- a/evdev/input.c +++ b/evdev/input.c @@ -413,7 +413,7 @@ ioctl_EVIOCG_bits(PyObject *self, PyObject *args) return NULL; PyObject* res = PyList_New(0); - for (int i=0; i Date: Wed, 30 Sep 2020 01:10:27 -0700 Subject: [PATCH 158/270] Add missing return codes to os.strerror() calls. --- evdev/uinput.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/evdev/uinput.py b/evdev/uinput.py index 8b9a597..0dc5f89 100644 --- a/evdev/uinput.py +++ b/evdev/uinput.py @@ -229,26 +229,30 @@ def begin_upload(self, effect_id): upload = ff.UInputUpload() upload.effect_id = effect_id - if self.dll._uinput_begin_upload(self.fd, ctypes.byref(upload)): - raise UInputError('Failed to begin uinput upload: ' + os.strerror()) + ret = self.dll._uinput_begin_upload(self.fd, ctypes.byref(upload)) + if ret: + raise UInputError('Failed to begin uinput upload: ' + os.strerror(ret)) return upload def end_upload(self, upload): - if self.dll._uinput_end_upload(self.fd, ctypes.byref(upload)): - raise UInputError('Failed to end uinput upload: ' + os.strerror()) + ret = self.dll._uinput_end_upload(self.fd, ctypes.byref(upload)) + if ret: + raise UInputError('Failed to end uinput upload: ' + os.strerror(ret)) def begin_erase(self, effect_id): erase = ff.UInputErase() erase.effect_id = effect_id - if self.dll._uinput_begin_erase(self.fd, ctypes.byref(erase)): - raise UInputError('Failed to begin uinput erase: ' + os.strerror()) + ret = self.dll._uinput_begin_erase(self.fd, ctypes.byref(erase)) + if ret: + raise UInputError('Failed to begin uinput erase: ' + os.strerror(ret)) return erase def end_erase(self, erase): - if self.dll._uinput_end_erase(self.fd, ctypes.byref(erase)): - raise UInputError('Failed to end uinput erase: ' + os.strerror()) + ret = self.dll._uinput_end_erase(self.fd, ctypes.byref(erase)) + if ret: + raise UInputError('Failed to end uinput erase: ' + os.strerror(ret)) def _verify(self): ''' From 454f50f3118adbfd497a06c5c7657aed7e5a52d1 Mon Sep 17 00:00:00 2001 From: Taylor Alexander Date: Thu, 1 Oct 2020 01:25:03 -0700 Subject: [PATCH 159/270] Fix a typo in force feedback example. --- docs/tutorial.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 92a8780..332908b 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -391,7 +391,7 @@ Create ``uinput`` device capable of receiving FF-effects upload = device.begin_upload(event.value) upload.retval = 0 - print(f'[upload] effect_id: {upload.effect_id}, type: {upload.effect.type}') + print(f'[upload] effect_id: {upload.effect.id}, type: {upload.effect.type}') device.end_upload(upload) elif event.code == ecodes.UI_FF_ERASE: From 148e6e04dcbf4de6087923f5ae2706ec7ae61ae7 Mon Sep 17 00:00:00 2001 From: Taylor Alexander Date: Thu, 1 Oct 2020 02:44:33 -0700 Subject: [PATCH 160/270] One more bug fix in the example. --- docs/tutorial.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 332908b..fc0e3e0 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -385,7 +385,7 @@ Create ``uinput`` device capable of receiving FF-effects # Wait for an EV_UINPUT event that will signal us that an # effect upload/erase operation is in progress. if event.type != ecodes.EV_UINPUT: - pass + continue if event.code == ecodes.UI_FF_UPLOAD: upload = device.begin_upload(event.value) From 0622179729588c66b9346eecfa7af8cec30f6ba5 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Fri, 2 Oct 2020 00:29:48 +0200 Subject: [PATCH 161/270] Add the util.find_ecodes_by_regex helper function --- docs/tutorial.rst | 14 ++++++++++++++ evdev/util.py | 34 +++++++++++++++++++++++++++++++++- tests/test_util.py | 21 +++++++++++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 tests/test_util.py diff --git a/docs/tutorial.rst b/docs/tutorial.rst index fc0e3e0..604d289 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -253,6 +253,20 @@ Accessing evdev constants ... ['KEY_COFFEE', 'KEY_SCREENLOCK'] +Searching event codes by regex +============================== + +:: + + >>> from evdev import util + + >>> res = util.find_ecodes_by_regex(r'(ABS|KEY)_BR(AKE|EAK)') + >>> res + ... {1: [411], 3: [10]} + >>> util.resolve_ecodes_dict(res) + ... {('EV_KEY', 1): [('KEY_BREAK', 411)], ('EV_ABS', 3): [('ABS_BRAKE', 10)]} + + Getting exclusive access to a device ==================================== diff --git a/evdev/util.py b/evdev/util.py index b6652d7..185f475 100644 --- a/evdev/util.py +++ b/evdev/util.py @@ -1,8 +1,10 @@ # encoding: utf-8 +import re import os import stat import glob +import collections from evdev import ecodes from evdev.events import event_factory @@ -112,4 +114,34 @@ def resolve_ecodes(ecode_dict, ecode_list, unknown='?'): return res -__all__ = ('list_devices', 'is_device', 'categorize', 'resolve_ecodes', 'resolve_ecodes_dict') +def find_ecodes_by_regex(regex): + ''' + Find ecodes matching a regex and return a mapping of event type to event codes. + + Example + ------- + >>> find_ecodes_by_regex(r'(ABS|KEY)_BR(AKE|EAK)') + {1: [411], 3: [10]} + >>> res = find_ecodes_by_regex(r'(ABS|KEY)_BR(AKE|EAK)') + >>> resolve_ecodes_dict(res) + { + ('EV_KEY', 1): [('KEY_BREAK', 411)], + ('EV_ABS', 3): [('ABS_BRAKE', 10)] + } + ''' + + regex = regex if isinstance(regex, re.Pattern) else re.compile(regex) + result = collections.defaultdict(list) + + for type_code, codes in ecodes.bytype.items(): + for code, names in codes.items(): + names = (names,) if isinstance(names, str) else names + for name in names: + if regex.match(name): + result[type_code].append(code) + break + + return dict(result) + + +__all__ = ('list_devices', 'is_device', 'categorize', 'resolve_ecodes', 'resolve_ecodes_dict', 'find_ecodes_by_regex') diff --git a/tests/test_util.py b/tests/test_util.py new file mode 100644 index 0000000..11e338b --- /dev/null +++ b/tests/test_util.py @@ -0,0 +1,21 @@ +from evdev import util + + +def test_match_ecodes_a(): + res = util.find_ecodes_by_regex('KEY_ZOOM.*') + assert res == {1: [372, 418, 419, 420]} + assert dict(util.resolve_ecodes_dict(res)) == { + ('EV_KEY', 1): [ + (['KEY_FULL_SCREEN', 'KEY_ZOOM'], 372), + ('KEY_ZOOMIN', 418), + ('KEY_ZOOMOUT', 419), + ('KEY_ZOOMRESET', 420) + ] + } + + res = util.find_ecodes_by_regex(r'(ABS|KEY)_BR(AKE|EAK)') + assert res == {1: [411], 3: [10]} + assert dict(util.resolve_ecodes_dict(res)) == { + ('EV_KEY', 1): [('KEY_BREAK', 411)], + ('EV_ABS', 3): [('ABS_BRAKE', 10)] + } From e0492245a3fed73cc586564e91ecd12421241274 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Fri, 2 Oct 2020 00:55:14 +0200 Subject: [PATCH 162/270] Fix util.resolve_ecodes() example --- evdev/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evdev/util.py b/evdev/util.py index 185f475..818be69 100644 --- a/evdev/util.py +++ b/evdev/util.py @@ -91,8 +91,8 @@ def resolve_ecodes(ecode_dict, ecode_list, unknown='?'): Example ------- - >>> resolve_ecodes([272, 273, 274]) - [('BTN_MOUSE', 272), ('BTN_RIGHT', 273), ('BTN_MIDDLE', 274)] + >>> resolve_ecodes(ecodes.BTN, [272, 273, 274]) + [(['BTN_LEFT', 'BTN_MOUSE'], 272), ('BTN_RIGHT', 273), ('BTN_MIDDLE', 274)] ''' res = [] for ecode in ecode_list: From 39ba89ce5b40e05ab1304882c4cd14df6222b5da Mon Sep 17 00:00:00 2001 From: jbleon95 Date: Thu, 29 Oct 2020 10:15:47 -0400 Subject: [PATCH 163/270] deref event_list if returning IOError in device_ready_many --- evdev/input.c | 1 + 1 file changed, 1 insertion(+) diff --git a/evdev/input.c b/evdev/input.c index 0a7b0d0..b1497a4 100644 --- a/evdev/input.c +++ b/evdev/input.c @@ -102,6 +102,7 @@ device_read_many(PyObject *self, PyObject *args) if (nread < 0) { PyErr_SetFromErrno(PyExc_IOError); + Py_DECREF(event_list); return NULL; } From f95869ceaf442354bfdda0298907ca1e9f191ddc Mon Sep 17 00:00:00 2001 From: sezanzeb Date: Mon, 16 Nov 2020 12:42:26 +0100 Subject: [PATCH 164/270] threaded non-blocking close --- evdev/device.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/evdev/device.py b/evdev/device.py index 6949183..9c66e62 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -4,6 +4,7 @@ import warnings import contextlib import collections +import threading from evdev import _input, ecodes, util from evdev.events import InputEvent @@ -302,7 +303,7 @@ def close(self): if self.fd > -1: try: super().close() - os.close(self.fd) + threading.Thread(target=os.close, args=(self.fd,)).start() finally: self.fd = -1 From 3e06e5b61f979a644754b15beafa51b081685878 Mon Sep 17 00:00:00 2001 From: sezanzeb Date: Mon, 16 Nov 2020 13:18:36 +0100 Subject: [PATCH 165/270] add a comment explaining why --- evdev/device.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/evdev/device.py b/evdev/device.py index 9c66e62..ba5af0e 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -303,6 +303,8 @@ def close(self): if self.fd > -1: try: super().close() + # avoid blocking at the end of functions when the destructor + # is called. threading.Thread(target=os.close, args=(self.fd,)).start() finally: self.fd = -1 From deba5670496fba6555a5cfd155ab8dbf7a88ffbd Mon Sep 17 00:00:00 2001 From: sezanzeb Date: Sun, 6 Dec 2020 17:03:33 +0100 Subject: [PATCH 166/270] Update eventio_async.py fixes https://github.com/lutris/lutris/issues/3250 _do_when_readable is only used for e.g. async_read, but InputDevice may not use that and use read instead. So _do_when_readable is never called and no event loop is needed. --- evdev/eventio_async.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/evdev/eventio_async.py b/evdev/eventio_async.py index d3b8ac3..d7c6c86 100644 --- a/evdev/eventio_async.py +++ b/evdev/eventio_async.py @@ -51,8 +51,13 @@ def async_read_loop(self): return ReadIterator(self) def close(self): - loop = asyncio.get_event_loop() - loop.remove_reader(self.fileno()) + try: + loop = asyncio.get_event_loop() + loop.remove_reader(self.fileno()) + except RuntimeError: + # no event loop present, so there is nothing to + # remove the header from. ignore + pass class ReadIterator(object): From d62b36fd533702f242b781c729a4b05b34fd44e2 Mon Sep 17 00:00:00 2001 From: sezanzeb Date: Mon, 21 Dec 2020 18:10:32 +0100 Subject: [PATCH 167/270] typo --- evdev/eventio_async.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evdev/eventio_async.py b/evdev/eventio_async.py index d7c6c86..77542a4 100644 --- a/evdev/eventio_async.py +++ b/evdev/eventio_async.py @@ -56,7 +56,7 @@ def close(self): loop.remove_reader(self.fileno()) except RuntimeError: # no event loop present, so there is nothing to - # remove the header from. ignore + # remove the reader from. Ignore pass From 62884d675ac781dd09df18978028a32cf6591397 Mon Sep 17 00:00:00 2001 From: Paul Mundt Date: Fri, 8 Jan 2021 02:14:17 +0100 Subject: [PATCH 168/270] ff: Fix up attack_length key typo in Envelope struct The Envelope struct defined an 'attach_length' key instead of the expected 'attack_length'. While the documentation was correct, any passed in 'attack_length' value was being silently dropped. This was discovered when configuring a periodic sinusoidal effect and observing differences in the waveform in comparison to the C API. Signed-off-by: Paul Mundt --- evdev/ff.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evdev/ff.py b/evdev/ff.py index 182be4a..0008906 100644 --- a/evdev/ff.py +++ b/evdev/ff.py @@ -51,7 +51,7 @@ class Envelope(ctypes.Structure): ''' _fields_ = [ - ('attach_length', _u16), + ('attack_length', _u16), ('attack_level', _u16), ('fade_length', _u16), ('fade_level', _u16), From 642a3150ec50c027024cda02a6749f7a1a3b8a92 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sat, 16 Jan 2021 14:30:17 +0100 Subject: [PATCH 169/270] PyUserInput is deprecated in favor of pynput --- docs/scope.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/scope.rst b/docs/scope.rst index a67a096..66a887d 100644 --- a/docs/scope.rst +++ b/docs/scope.rst @@ -18,13 +18,13 @@ injection support. - python-uinput_ - uinput-mapper_ -- PyUserInput_ (cross-platform, works on the display server level) +- pynput_ - pygame_ (cross-platform) .. _python-uinput: https://github.com/tuomasjjrasanen/python-uinput .. _uinput-mapper: https://github.com/MerlijnWajer/uinput-mapper -.. _PyUserInput: https://github.com/PyUserInput/PyUserInput +.. _pynput: https://github.com/moses-palmer/pynput .. _pygame: http://www.pygame.org/ .. _`#7`: https://github.com/gvalkov/python-evdev/issues/7 From 5adc2abf1ec8a02c05c80cb1b3e34ba1d4237803 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sat, 16 Jan 2021 14:50:51 +0100 Subject: [PATCH 170/270] Bump version: 1.3.0 -> 1.4.0 --- docs/changelog.rst | 21 ++++++++++++++++++++- docs/conf.py | 2 +- setup.cfg | 3 +-- setup.py | 2 +- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 07abba6..042db50 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,11 +1,30 @@ Changelog --------- -Master + +1.4.0 (Jan 16, 2021) ==================== - Fix ``InputDevice.set_absinfo`` to allow setting parameters to zero. +- Fix off-by-one in ``ioctl_EVIOCG_bits``, which causes value at the end of the + list to not be reported back (`#131 `_). + +- Fix ``set_absinfo`` to allow setting parameters to zero (`#128 `_). + +- Fix leak when returning ``BlockingIOError`` from a read (`#143 `_). + +- Fix "There is no current event loop in thread" error for non asyncio code + (`#146 `_). + +- Prevent ``InputDevice`` destructor from blocking (`#145 `_). + +- Add missing return codes to ``os.strerror()`` calls and fix force feedback + example in docs (`#138 `_). + +- Add the ``util.find_ecodes_by_regex()`` helper function. + + 1.3.0 (Jan 12, 2020) ==================== diff --git a/docs/conf.py b/docs/conf.py index 3b8bde3..803993c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -60,7 +60,7 @@ # built documents. # # The full version, including alpha/beta/rc tags. -release = '1.3.0' +release = '1.4.0' # The short X.Y version. version = release diff --git a/setup.cfg b/setup.cfg index fa36a17..ff3bb7b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.3.0 +current_version = 1.4.0 message = Bump version: {current_version} -> {new_version} commit = True tag = True @@ -11,4 +11,3 @@ max-line-length = 110 [bumpversion:file:setup.py] [bumpversion:file:docs/conf.py] - diff --git a/setup.py b/setup.py index 9f5d8ef..e8ab8f1 100755 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ #----------------------------------------------------------------------------- kw = { 'name': 'evdev', - 'version': '1.3.0', + 'version': '1.4.0', 'description': 'Bindings to the Linux input handling subsystem', 'long_description': open(pjoin(here, 'README.rst')).read(), From a1382dcd9a50b1aae997c2ce62c7312746b1d806 Mon Sep 17 00:00:00 2001 From: Ben Greiner Date: Fri, 9 Apr 2021 22:14:41 +0200 Subject: [PATCH 171/270] re.Pattern is not a guaranteed class name --- evdev/util.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/evdev/util.py b/evdev/util.py index 818be69..e8009f7 100644 --- a/evdev/util.py +++ b/evdev/util.py @@ -118,6 +118,8 @@ def find_ecodes_by_regex(regex): ''' Find ecodes matching a regex and return a mapping of event type to event codes. + regex can be a pattern string or a compiled regular expression object. + Example ------- >>> find_ecodes_by_regex(r'(ABS|KEY)_BR(AKE|EAK)') @@ -130,7 +132,7 @@ def find_ecodes_by_regex(regex): } ''' - regex = regex if isinstance(regex, re.Pattern) else re.compile(regex) + regex = re.compile(regex) # re.compile is idempotent result = collections.defaultdict(list) for type_code, codes in ecodes.bytype.items(): From ddf3c78144db5659ef68c94286134196397f7379 Mon Sep 17 00:00:00 2001 From: Luiz Antonio Lazoti Date: Fri, 30 Apr 2021 15:23:15 -0300 Subject: [PATCH 172/270] fix --> ImportError: sys.meta_path is None Fix this error. Exception ignored in: Traceback (most recent call last): File "/usr/local/lib/python3.6/dist-packages/evdev/device.py", line 160, in __del__ File "/usr/local/lib/python3.6/dist-packages/evdev/device.py", line 305, in close File "/usr/local/lib/python3.6/dist-packages/evdev/eventio_async.py", line 55, in close File "/usr/lib/python3.6/asyncio/events.py", line 694, in get_event_loop File "/usr/lib/python3.6/asyncio/events.py", line 669, in get_event_loop_policy File "/usr/lib/python3.6/asyncio/events.py", line 662, in _init_event_loop_policy ImportError: sys.meta_path is None, Python is likely shutting down --- evdev/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evdev/device.py b/evdev/device.py index ba5af0e..d9f2b91 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -158,7 +158,7 @@ def __del__(self): if hasattr(self, 'fd') and self.fd is not None: try: self.close() - except OSError: + except (OSError, ImportError): pass def _capabilities(self, absinfo=True): From 426057d68f78bd9a22c2121ac4f9dd29b2360792 Mon Sep 17 00:00:00 2001 From: Luiz Antonio Lazoti Date: Fri, 30 Apr 2021 15:33:34 -0300 Subject: [PATCH 173/270] fix ImportError, AttributeError FIX Exception ignored in: Traceback (most recent call last): File "/usr/local/lib/python3.6/dist-packages/evdev/device.py", line 160, in __del__ File "/usr/local/lib/python3.6/dist-packages/evdev/device.py", line 305, in close File "/usr/local/lib/python3.6/dist-packages/evdev/eventio_async.py", line 55, in close File "/usr/lib/python3.6/asyncio/events.py", line 694, in get_event_loop File "/usr/lib/python3.6/asyncio/events.py", line 669, in get_event_loop_policy File "/usr/lib/python3.6/asyncio/events.py", line 662, in _init_event_loop_policy ImportError: sys.meta_path is None, Python is likely shutting down --- evdev/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evdev/device.py b/evdev/device.py index d9f2b91..02150be 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -158,7 +158,7 @@ def __del__(self): if hasattr(self, 'fd') and self.fd is not None: try: self.close() - except (OSError, ImportError): + except (OSError, ImportError, AttributeError): pass def _capabilities(self, absinfo=True): From 17c68cac99b811a3811d29f19dc8ac8c0746c983 Mon Sep 17 00:00:00 2001 From: Luc Ducazu <6098321+lducazu@users.noreply.github.com> Date: Tue, 18 May 2021 19:02:55 +0200 Subject: [PATCH 174/270] Re-enable TTY echo at exit. --- evdev/evtest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evdev/evtest.py b/evdev/evtest.py index 49e2c35..da6c683 100644 --- a/evdev/evtest.py +++ b/evdev/evtest.py @@ -67,7 +67,7 @@ def main(): # Disable tty echoing if stdin is a tty. if sys.stdin.isatty(): toggle_tty_echo(sys.stdin, enable=False) - atexit.register(toggle_tty_echo, sys.stdin, enable=False) + atexit.register(toggle_tty_echo, sys.stdin, enable=True) print('Listening for events (press ctrl-c to exit) ...') fd_to_device = {dev.fd: dev for dev in devices} From 3033ddfe6db429872a389067a21cd4524722147f Mon Sep 17 00:00:00 2001 From: Mario Mey Date: Wed, 16 Jun 2021 17:04:24 -0300 Subject: [PATCH 175/270] Importing modules was wrong in `selector` --- docs/tutorial.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 604d289..b594516 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -175,14 +175,14 @@ This can also be achieved using the :mod:`selectors` module in Python 3.4: from evdev import InputDevice from selectors import DefaultSelector, EVENT_READ - selector = selectors.DefaultSelector() + selector = DefaultSelector() - mouse = evdev.InputDevice('/dev/input/event1') - keybd = evdev.InputDevice('/dev/input/event2') + mouse = InputDevice('/dev/input/event1') + keybd = InputDevice('/dev/input/event2') # This works because InputDevice has a `fileno()` method. - selector.register(mouse, selectors.EVENT_READ) - selector.register(keybd, selectors.EVENT_READ) + selector.register(mouse, EVENT_READ) + selector.register(keybd, EVENT_READ) while True: for key, mask in selector.select(): From 44cc232db4686b63c2b96863e3ae3e1ef6c2feab Mon Sep 17 00:00:00 2001 From: Martin Dinov <46628515+martinthedinov@users.noreply.github.com> Date: Wed, 23 Jun 2021 01:09:14 +0100 Subject: [PATCH 176/270] Update tutorial.rst The "Injecting an FF-event into first FF-capable device found" example was slightly broken, with a missing import and a typo on what is now line 450 (previously 449) - should be ecodes.EV_FF and not e.EV_FF. Finally, added a duration_ms of time.sleep(), as otherwise the effect immediately gets erased and doesn't seem to do anything. --- docs/tutorial.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 604d289..4528730 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -425,10 +425,11 @@ Injecting an FF-event into first FF-capable device found :: - from evdev import ecodes, InputDevice, ff + from evdev import ecodes, InputDevice, ff, list_devices + import time # Find first EV_FF capable event device (that we have permissions to use). - for name in evdev.list_devices(): + for name in list_devices(): dev = InputDevice(name) if ecodes.EV_FF in dev.capabilities(): break @@ -446,5 +447,6 @@ Injecting an FF-event into first FF-capable device found repeat_count = 1 effect_id = dev.upload_effect(effect) - dev.write(e.EV_FF, effect_id, repeat_count) + dev.write(ecodes.EV_FF, effect_id, repeat_count) + time.sleep(duration_ms) dev.erase_effect(effect_id) From 893c8aed129bc5fb29e8b52dee838855913786c9 Mon Sep 17 00:00:00 2001 From: Daniel Quinn Date: Thu, 2 Sep 2021 21:03:32 +0000 Subject: [PATCH 177/270] Fix imports in sample The imports were for classes, while the example was referencing the package name --- docs/tutorial.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 604d289..b594516 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -175,14 +175,14 @@ This can also be achieved using the :mod:`selectors` module in Python 3.4: from evdev import InputDevice from selectors import DefaultSelector, EVENT_READ - selector = selectors.DefaultSelector() + selector = DefaultSelector() - mouse = evdev.InputDevice('/dev/input/event1') - keybd = evdev.InputDevice('/dev/input/event2') + mouse = InputDevice('/dev/input/event1') + keybd = InputDevice('/dev/input/event2') # This works because InputDevice has a `fileno()` method. - selector.register(mouse, selectors.EVENT_READ) - selector.register(keybd, selectors.EVENT_READ) + selector.register(mouse, EVENT_READ) + selector.register(keybd, EVENT_READ) while True: for key, mask in selector.select(): From 7ee9e37eb070b4bb3dbf3f11a2d306a5033c7cc0 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Thu, 24 Mar 2022 01:00:27 +0100 Subject: [PATCH 178/270] Add sphinx-copybutton --- docs/conf.py | 16 +++++++++++----- requirements-dev.txt | 5 +++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 803993c..5d8d0b1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -31,10 +31,11 @@ # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.viewcode', - 'sphinx.ext.intersphinx', - 'sphinx.ext.napoleon', + 'sphinx.ext.autodoc', + 'sphinx.ext.viewcode', + 'sphinx.ext.intersphinx', + 'sphinx.ext.napoleon', + 'sphinx_copybutton', ] autodoc_member_order = 'bysource' @@ -53,7 +54,7 @@ # General information about the project. project = u'python-evdev' -copyright = u'2012-2016, Georgi Valkov' +copyright = u'2012-2022, Georgi Valkov' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -258,3 +259,8 @@ # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' + +# Copybutton config +#copybutton_prompt_text = r">>> " +#copybutton_prompt_is_regexp = True +#copybutton_only_copy_prompt_lines = True diff --git a/requirements-dev.txt b/requirements-dev.txt index 41728fd..c7eb2f2 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,6 @@ -pytest>=3.4.0 -Sphinx>=1.7.0 +pytest ~= 7.1.0 +Sphinx ~= 4.4.0 +sphinx-copybutton ~= 0.5.0 bump2version sphinx_rtd_theme twine From d3b712285c50af9a758f6502a69c30b44c7166d7 Mon Sep 17 00:00:00 2001 From: Tobi Date: Thu, 24 Mar 2022 01:10:32 +0100 Subject: [PATCH 179/270] threaded_close (#165) * threaded_close * default to false * revert --- evdev/device.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/evdev/device.py b/evdev/device.py index 02150be..23254a3 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -4,7 +4,6 @@ import warnings import contextlib import collections -import threading from evdev import _input, ecodes, util from evdev.events import InputEvent @@ -303,9 +302,7 @@ def close(self): if self.fd > -1: try: super().close() - # avoid blocking at the end of functions when the destructor - # is called. - threading.Thread(target=os.close, args=(self.fd,)).start() + os.close(self.fd) finally: self.fd = -1 From 92c009a5001ebbfa94b535d8cff8c6ed9c51c35b Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Thu, 24 Mar 2022 01:29:19 +0100 Subject: [PATCH 180/270] Bump version: 1.4.0 -> 1.5.0 --- docs/changelog.rst | 16 ++++++++++++++++ docs/conf.py | 2 +- setup.cfg | 2 +- setup.py | 10 +++++----- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 042db50..ed85d06 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,22 @@ Changelog --------- +1.5.0 (Mar 24, 2022) +================== + +- Fix documentation (`#163 `_, `#160 `_). + +- Re-enable TTY echo at evtest exit (`#155 `_). + +- Fix ``ImportError: sys.meta_path is None, Python is likely shutting down`` (`#154 `_). + +- Closing the input device file descriptor in ``InputDevice.close()`` now + happens in the main thread, instead of in a new thread (reverts `#146 + `_). + +- Fix ``util.find_ecodes_by_regex`` not working across all supported Python versions (`#152 `_). + + 1.4.0 (Jan 16, 2021) ==================== diff --git a/docs/conf.py b/docs/conf.py index 5d8d0b1..2d16b6c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -61,7 +61,7 @@ # built documents. # # The full version, including alpha/beta/rc tags. -release = '1.4.0' +release = '1.5.0' # The short X.Y version. version = release diff --git a/setup.cfg b/setup.cfg index ff3bb7b..1f1ff70 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.4.0 +current_version = 1.5.0 message = Bump version: {current_version} -> {new_version} commit = True tag = True diff --git a/setup.py b/setup.py index e8ab8f1..e8c1174 100755 --- a/setup.py +++ b/setup.py @@ -22,12 +22,12 @@ #----------------------------------------------------------------------------- classifiers = [ 'Development Status :: 5 - Production/Stable', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Operating System :: POSIX :: Linux', 'Intended Audience :: Developers', 'Topic :: Software Development :: Libraries', @@ -44,7 +44,7 @@ #----------------------------------------------------------------------------- kw = { 'name': 'evdev', - 'version': '1.4.0', + 'version': '1.5.0', 'description': 'Bindings to the Linux input handling subsystem', 'long_description': open(pjoin(here, 'README.rst')).read(), From 1bf0999a6587aacc6877cfcda0dcd8f6fa4c6180 Mon Sep 17 00:00:00 2001 From: Dave Riedstra Date: Sat, 30 Apr 2022 23:38:34 +0100 Subject: [PATCH 181/270] Note that UInput capabilities can't include EV_SYN per https://github.com/gvalkov/python-evdev/issues/60#issuecomment-238791290 --- docs/tutorial.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 37d0182..d4f3c0b 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -380,6 +380,8 @@ Create ``uinput`` device with capabilities of another device Create ``uinput`` device capable of receiving FF-effects ======================================================== +Note that ``ecodes.EV_SYN`` cannot be in the ``cap`` dictionary or the device will not be created. + :: import asyncio From 6554c1bc576e8de14ff2a0a180880110b5c6183a Mon Sep 17 00:00:00 2001 From: Tobi Date: Mon, 2 May 2022 19:24:39 +0200 Subject: [PATCH 182/270] Update tutorial.rst --- docs/tutorial.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index d4f3c0b..ef60282 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -329,6 +329,8 @@ Injecting events (using a context manager) Specifying ``uinput`` device options ==================================== +Note that ``ecodes.EV_SYN`` cannot be in the ``cap`` dictionary or the device will not be created. + :: >>> from evdev import UInput, AbsInfo, ecodes as e @@ -380,8 +382,6 @@ Create ``uinput`` device with capabilities of another device Create ``uinput`` device capable of receiving FF-effects ======================================================== -Note that ``ecodes.EV_SYN`` cannot be in the ``cap`` dictionary or the device will not be created. - :: import asyncio From 4c6bee7c912a70cf7309e77d1ff6ccc6c2fc8211 Mon Sep 17 00:00:00 2001 From: Tobi Date: Mon, 2 May 2022 19:28:30 +0200 Subject: [PATCH 183/270] Update tutorial.rst --- docs/tutorial.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index ef60282..d35299c 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -329,7 +329,9 @@ Injecting events (using a context manager) Specifying ``uinput`` device options ==================================== -Note that ``ecodes.EV_SYN`` cannot be in the ``cap`` dictionary or the device will not be created. +.. note:: + + ``ecodes.EV_SYN`` cannot be in the ``cap`` dictionary or the device will not be created. :: From 1a51829e7a89525db35c24bae6ef064346462e1c Mon Sep 17 00:00:00 2001 From: Andrew Ammerlaan Date: Wed, 8 Jun 2022 21:44:20 +0200 Subject: [PATCH 184/270] evdev/eventio_async.py: python3.11 compatibility Signed-off-by: Andrew Ammerlaan --- evdev/eventio_async.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/evdev/eventio_async.py b/evdev/eventio_async.py index 77542a4..68225c3 100644 --- a/evdev/eventio_async.py +++ b/evdev/eventio_async.py @@ -85,8 +85,7 @@ def __next__(self): def __aiter__(self): return self - @asyncio.coroutine - def __anext__(self): + async def __anext__(self): future = asyncio.Future() try: # Read from the previous batch of events. From d4e96ac1b30f3fce82b2fac7eb7bf6a58362e57e Mon Sep 17 00:00:00 2001 From: Dick Marinus Date: Fri, 15 Jul 2022 20:41:35 +0200 Subject: [PATCH 185/270] add keycode for all events --- evdev/events.py | 46 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/evdev/events.py b/evdev/events.py index 14bb0ce..c63244d 100644 --- a/evdev/events.py +++ b/evdev/events.py @@ -130,15 +130,23 @@ def __repr__(s): class RelEvent(object): '''A relative axis event (e.g moving the mouse 5 units to the left).''' - __slots__ = 'event' + __slots__ = 'event', 'keycode' + + def __init__(self, event, allow_unknown=False): + try: + self.keycode = REL[event.code] + except KeyError: + if allow_unknown: + self.keycode = '0x{:02X}'.format(event.code) + else: + raise - def __init__(self, event): #: Reference to an :class:`InputEvent` instance. self.event = event def __str__(self): - msg = 'relative axis event at {:f}, {} ' - return msg.format(self.event.timestamp(), REL[self.event.code]) + msg = 'relative axis event at {:f}, {} {} ' + return msg.format(self.event.timestamp(), self.keycode, self.event.value) def __repr__(s): return '{}({!r})'.format(s.__class__.__name__, s.event) @@ -147,15 +155,23 @@ def __repr__(s): class AbsEvent(object): '''An absolute axis event (e.g the coordinates of a tap on a touchscreen).''' - __slots__ = 'event' + __slots__ = 'event', 'keycode' + + def __init__(self, event, allow_unknown=False): + try: + self.keycode = ABS[event.code] + except KeyError: + if allow_unknown: + self.keycode = '0x{:02X}'.format(event.code) + else: + raise - def __init__(self, event): #: Reference to an :class:`InputEvent` instance. self.event = event def __str__(self): - msg = 'absolute axis event at {:f}, {} ' - return msg.format(self.event.timestamp(), ABS[self.event.code]) + msg = 'absolute axis event at {:f}, {} {} ' + return msg.format(self.event.timestamp(), self.keycode, self.event.value) def __repr__(s): return '{}({!r})'.format(s.__class__.__name__, s.event) @@ -169,15 +185,23 @@ class SynEvent(object): the multitouch protocol. ''' - __slots__ = 'event' + __slots__ = 'event', 'keycode' + + def __init__(self, event, allow_known): + try: + self.keycode = SYN[event.code] + except KeyError: + if allow_unknown: + self.keycode = '0x{:02X}'.format(event.code) + else: + raise - def __init__(self, event): #: Reference to an :class:`InputEvent` instance. self.event = event def __str__(self): msg = 'synchronization event at {:f}, {} ' - return msg.format(self.event.timestamp(), SYN[self.event.code]) + return msg.format(self.event.timestamp(), self.keycode) def __repr__(s): return '{}({!r})'.format(s.__class__.__name__, s.event) From f24f7f48843876c149ebda3f18842ec5db21d5d2 Mon Sep 17 00:00:00 2001 From: Dick Marinus Date: Sun, 17 Jul 2022 21:27:35 +0200 Subject: [PATCH 186/270] fix typo, allow_known -> allow_unknown --- evdev/events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evdev/events.py b/evdev/events.py index c63244d..fa122f7 100644 --- a/evdev/events.py +++ b/evdev/events.py @@ -187,7 +187,7 @@ class SynEvent(object): __slots__ = 'event', 'keycode' - def __init__(self, event, allow_known): + def __init__(self, event, allow_unknown): try: self.keycode = SYN[event.code] except KeyError: From 227a01c78b4fd0ddc074110549fd381864301d41 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 17 Jul 2022 22:15:09 +0200 Subject: [PATCH 187/270] More asyncio compatibility fixes There is no need to mark __anext__ as async, since it already returns an awaitable object, in the form of asyncio.Future. --- evdev/eventio_async.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evdev/eventio_async.py b/evdev/eventio_async.py index 68225c3..5969423 100644 --- a/evdev/eventio_async.py +++ b/evdev/eventio_async.py @@ -85,7 +85,7 @@ def __next__(self): def __aiter__(self): return self - async def __anext__(self): + def __anext__(self): future = asyncio.Future() try: # Read from the previous batch of events. From b5f69fba79d9b828e4b769c1188a81edde6433bb Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 17 Jul 2022 22:26:27 +0200 Subject: [PATCH 188/270] Remove Python 2.x compatibility leftovers --- docs/tutorial.rst | 2 +- evdev/device.py | 5 ----- evdev/eventio.py | 2 +- evdev/eventio_async.py | 6 +----- evdev/events.py | 10 +++++----- evdev/uinput.py | 5 +---- 6 files changed, 9 insertions(+), 21 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index d35299c..200c637 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -290,7 +290,7 @@ Associating classes with event types >>> from evdev import categorize, event_factory, ecodes - >>> class SynEvent(object): + >>> class SynEvent: ... def __init__(self, event): ... ... diff --git a/evdev/device.py b/evdev/device.py index 23254a3..dbfad5d 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -282,11 +282,6 @@ def __eq__(self, other): return isinstance(other, self.__class__) and self.info == other.info \ and self.path == other.path - def __ne__(self, other): - # Python 2 compatibility. Python 3 automatically negates the value of - # __eq__, in case __ne__ is not defined. - return not self == other - def __str__(self): msg = 'device {}, name "{}", phys "{}"' return msg.format(self.path, self.name, self.phys) diff --git a/evdev/eventio.py b/evdev/eventio.py index 22c37e2..8d84f55 100644 --- a/evdev/eventio.py +++ b/evdev/eventio.py @@ -13,7 +13,7 @@ class EvdevError(Exception): pass -class EventIO(object): +class EventIO: ''' Base class for reading and writing input events. diff --git a/evdev/eventio_async.py b/evdev/eventio_async.py index 5969423..2d3468e 100644 --- a/evdev/eventio_async.py +++ b/evdev/eventio_async.py @@ -60,7 +60,7 @@ def close(self): pass -class ReadIterator(object): +class ReadIterator: def __init__(self, device): self.current_batch = iter(()) self.device = device @@ -69,10 +69,6 @@ def __init__(self, device): def __iter__(self): return self - # Python 2.x compatibility. - def next(self): - return self.__next__() - def __next__(self): try: # Read from the previous batch of events. diff --git a/evdev/events.py b/evdev/events.py index 14bb0ce..9d8758d 100644 --- a/evdev/events.py +++ b/evdev/events.py @@ -42,7 +42,7 @@ from evdev.ecodes import keys, KEY, SYN, REL, ABS, EV_KEY, EV_REL, EV_ABS, EV_SYN -class InputEvent(object): +class InputEvent: '''A generic input event.''' __slots__ = 'sec', 'usec', 'type', 'code', 'value' @@ -77,7 +77,7 @@ def __repr__(s): s.sec, s.usec, s.type, s.code, s.value) -class KeyEvent(object): +class KeyEvent: '''An event generated by a keyboard, button or other key-like devices.''' key_up = 0x0 @@ -127,7 +127,7 @@ def __repr__(s): return '{}({!r})'.format(s.__class__.__name__, s.event) -class RelEvent(object): +class RelEvent: '''A relative axis event (e.g moving the mouse 5 units to the left).''' __slots__ = 'event' @@ -144,7 +144,7 @@ def __repr__(s): return '{}({!r})'.format(s.__class__.__name__, s.event) -class AbsEvent(object): +class AbsEvent: '''An absolute axis event (e.g the coordinates of a tap on a touchscreen).''' __slots__ = 'event' @@ -161,7 +161,7 @@ def __repr__(s): return '{}({!r})'.format(s.__class__.__name__, s.event) -class SynEvent(object): +class SynEvent: ''' A synchronization event. Synchronization events are used as markers to separate event. Used as markers to separate diff --git a/evdev/uinput.py b/evdev/uinput.py index 0dc5f89..4f6148a 100644 --- a/evdev/uinput.py +++ b/evdev/uinput.py @@ -34,7 +34,7 @@ class UInput(EventIO): ) @classmethod - def from_device(cls, *devices, **kwargs): + def from_device(cls, *devices, filtered_types=(ecodes.EV_SYN, ecodes.EV_FF), **kwargs): ''' Create an UInput device with the capabilities of one or more input devices. @@ -51,9 +51,6 @@ def from_device(cls, *devices, **kwargs): Keyword arguments to UInput constructor (i.e. name, vendor etc.). ''' - # TODO: Move back to the argument list once Python 2 support is dropped. - filtered_types = kwargs.pop('filtered_types', (ecodes.EV_SYN, ecodes.EV_FF)) - device_instances = [] for dev in devices: if not isinstance(dev, device.InputDevice): From 03aa7f5877ad545a7a4281d52f4385a41e47cf7e Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 17 Jul 2022 22:27:33 +0200 Subject: [PATCH 189/270] Simplify asyncio example --- docs/tutorial.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 200c637..7416181 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -123,16 +123,15 @@ Reading events (using :mod:`asyncio`) :: >>> import asyncio - >>> from evdev import InputDevice, categorize, ecodes + >>> from evdev import InputDevice >>> dev = InputDevice('/dev/input/event1') - >>> async def helper(dev): + >>> async def main(dev): ... async for ev in dev.async_read_loop(): ... print(repr(ev)) - >>> loop = asyncio.get_event_loop() - >>> loop.run_until_complete(helper(dev)) + >>> asyncio.run(main(dev)) InputEvent(1527363738, 348740, 4, 4, 458792) InputEvent(1527363738, 348740, 1, 28, 0) InputEvent(1527363738, 348740, 0, 0, 0) From beeecf4000d2554f90470e5e1357b03eb48d1bd4 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 17 Jul 2022 22:50:09 +0200 Subject: [PATCH 190/270] Fix English --- evdev/events.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/evdev/events.py b/evdev/events.py index 1c50d47..4e359bc 100644 --- a/evdev/events.py +++ b/evdev/events.py @@ -88,7 +88,7 @@ class KeyEvent: def __init__(self, event, allow_unknown=False): ''' - The ``allow_unknown`` argument determines what to do in the event of a event code + The ``allow_unknown`` argument determines what to do in the event of an event code for which a key code cannot be found. If ``False`` a ``KeyError`` will be raised. If ``True`` the keycode will be set to the hex value of the event code. ''' @@ -179,10 +179,8 @@ def __repr__(s): class SynEvent: ''' - A synchronization event. Synchronization events are used as - markers to separate event. Used as markers to separate - events. Events may be separated in time or in space, such as with - the multitouch protocol. + A synchronization event. Used as markers to separate events. Events may be + separated in time or in space, such as with the multitouch protocol. ''' __slots__ = 'event', 'keycode' From 28b1739182fdf0d09ff952af3abbf73485ecb15c Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 17 Jul 2022 22:50:36 +0200 Subject: [PATCH 191/270] allow_unknown needs default value or categorize() breaks --- evdev/events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evdev/events.py b/evdev/events.py index 4e359bc..18b7f20 100644 --- a/evdev/events.py +++ b/evdev/events.py @@ -185,7 +185,7 @@ class SynEvent: __slots__ = 'event', 'keycode' - def __init__(self, event, allow_unknown): + def __init__(self, event, allow_unknown=True): try: self.keycode = SYN[event.code] except KeyError: From 0c39ecfe7210ef2a391d41663342f23e4f9f8971 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 17 Jul 2022 23:06:08 +0200 Subject: [PATCH 192/270] Revert "add keycode for all events" This reverts commit d4e96ac1b30f3fce82b2fac7eb7bf6a58362e57e. --- evdev/events.py | 46 +++++++++++----------------------------------- 1 file changed, 11 insertions(+), 35 deletions(-) diff --git a/evdev/events.py b/evdev/events.py index 18b7f20..37cf17e 100644 --- a/evdev/events.py +++ b/evdev/events.py @@ -130,23 +130,15 @@ def __repr__(s): class RelEvent: '''A relative axis event (e.g moving the mouse 5 units to the left).''' - __slots__ = 'event', 'keycode' - - def __init__(self, event, allow_unknown=False): - try: - self.keycode = REL[event.code] - except KeyError: - if allow_unknown: - self.keycode = '0x{:02X}'.format(event.code) - else: - raise + __slots__ = 'event' + def __init__(self, event): #: Reference to an :class:`InputEvent` instance. self.event = event def __str__(self): - msg = 'relative axis event at {:f}, {} {} ' - return msg.format(self.event.timestamp(), self.keycode, self.event.value) + msg = 'relative axis event at {:f}, {} ' + return msg.format(self.event.timestamp(), REL[self.event.code]) def __repr__(s): return '{}({!r})'.format(s.__class__.__name__, s.event) @@ -155,23 +147,15 @@ def __repr__(s): class AbsEvent: '''An absolute axis event (e.g the coordinates of a tap on a touchscreen).''' - __slots__ = 'event', 'keycode' - - def __init__(self, event, allow_unknown=False): - try: - self.keycode = ABS[event.code] - except KeyError: - if allow_unknown: - self.keycode = '0x{:02X}'.format(event.code) - else: - raise + __slots__ = 'event' + def __init__(self, event): #: Reference to an :class:`InputEvent` instance. self.event = event def __str__(self): - msg = 'absolute axis event at {:f}, {} {} ' - return msg.format(self.event.timestamp(), self.keycode, self.event.value) + msg = 'absolute axis event at {:f}, {} ' + return msg.format(self.event.timestamp(), ABS[self.event.code]) def __repr__(s): return '{}({!r})'.format(s.__class__.__name__, s.event) @@ -183,23 +167,15 @@ class SynEvent: separated in time or in space, such as with the multitouch protocol. ''' - __slots__ = 'event', 'keycode' - - def __init__(self, event, allow_unknown=True): - try: - self.keycode = SYN[event.code] - except KeyError: - if allow_unknown: - self.keycode = '0x{:02X}'.format(event.code) - else: - raise + __slots__ = 'event' + def __init__(self, event): #: Reference to an :class:`InputEvent` instance. self.event = event def __str__(self): msg = 'synchronization event at {:f}, {} ' - return msg.format(self.event.timestamp(), self.keycode) + return msg.format(self.event.timestamp(), SYN[self.event.code]) def __repr__(s): return '{}({!r})'.format(s.__class__.__name__, s.event) From 4050f506b016addd759085f2ae6f07b0065a2947 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 17 Jul 2022 23:16:02 +0200 Subject: [PATCH 193/270] Remove trailing whitespace --- evdev/events.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/evdev/events.py b/evdev/events.py index 37cf17e..af42386 100644 --- a/evdev/events.py +++ b/evdev/events.py @@ -137,7 +137,7 @@ def __init__(self, event): self.event = event def __str__(self): - msg = 'relative axis event at {:f}, {} ' + msg = 'relative axis event at {:f}, {}' return msg.format(self.event.timestamp(), REL[self.event.code]) def __repr__(s): @@ -154,7 +154,7 @@ def __init__(self, event): self.event = event def __str__(self): - msg = 'absolute axis event at {:f}, {} ' + msg = 'absolute axis event at {:f}, {}' return msg.format(self.event.timestamp(), ABS[self.event.code]) def __repr__(s): @@ -174,7 +174,7 @@ def __init__(self, event): self.event = event def __str__(self): - msg = 'synchronization event at {:f}, {} ' + msg = 'synchronization event at {:f}, {}' return msg.format(self.event.timestamp(), SYN[self.event.code]) def __repr__(s): From 3cd7fbf222dbf3b4ca277d5138eabdb1819faa66 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 17 Jul 2022 23:22:58 +0200 Subject: [PATCH 194/270] Bump version: 1.5.0 -> 1.6.0 --- docs/changelog.rst | 6 ++++++ docs/conf.py | 2 +- setup.cfg | 2 +- setup.py | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index ed85d06..e7b3af4 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,12 @@ Changelog --------- +1.6.0 (Jul 17, 2022) +================== + +- Fix Python 3.11 compatibility (`#174 `_) + + 1.5.0 (Mar 24, 2022) ================== diff --git a/docs/conf.py b/docs/conf.py index 2d16b6c..8dc3ae9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -61,7 +61,7 @@ # built documents. # # The full version, including alpha/beta/rc tags. -release = '1.5.0' +release = '1.6.0' # The short X.Y version. version = release diff --git a/setup.cfg b/setup.cfg index 1f1ff70..9e2dc85 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.5.0 +current_version = 1.6.0 message = Bump version: {current_version} -> {new_version} commit = True tag = True diff --git a/setup.py b/setup.py index e8c1174..847485e 100755 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ #----------------------------------------------------------------------------- kw = { 'name': 'evdev', - 'version': '1.5.0', + 'version': '1.6.0', 'description': 'Bindings to the Linux input handling subsystem', 'long_description': open(pjoin(here, 'README.rst')).read(), From 66aa0905b8ebe257e4518767b043d9f942449eba Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Fri, 20 Jan 2023 15:26:11 +0100 Subject: [PATCH 195/270] Fix escaping of genecodes command-line --- setup.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/setup.py b/setup.py index 847485e..bdda634 100755 --- a/setup.py +++ b/setup.py @@ -1,11 +1,10 @@ #!/usr/bin/env python -# encoding: utf-8 import os import sys import textwrap +from pathlib import Path -from os.path import abspath, dirname, join as pjoin #----------------------------------------------------------------------------- try: @@ -17,7 +16,8 @@ #----------------------------------------------------------------------------- -here = abspath(dirname(__file__)) +curdir = Path(__file__).resolve().parent +ecodes_path = curdir / 'evdev/ecodes.c' #----------------------------------------------------------------------------- classifiers = [ @@ -47,7 +47,7 @@ 'version': '1.6.0', 'description': 'Bindings to the Linux input handling subsystem', - 'long_description': open(pjoin(here, 'README.rst')).read(), + 'long_description': (curdir / 'README.rst').read_text(), 'author': 'Georgi Valkov', 'author_email': 'georgi.t.valkov@gmail.com', @@ -99,11 +99,12 @@ def create_ecodes(headers=None): sys.stderr.write(textwrap.dedent(msg)) sys.exit(1) - from subprocess import check_call + from subprocess import run - print('writing ecodes.c (using %s)' % ' '.join(headers)) - cmd = '%s genecodes.py %s > ecodes.c' % (sys.executable, ' '.join(headers)) - check_call(cmd, cwd="%s/evdev" % here, shell=True) + print('writing %s (using %s)' % (ecodes_path, ' '.join(headers))) + with ecodes_path.open('w') as fh: + cmd = [sys.executable, 'evdev/genecodes.py', *headers] + run(cmd, check=True, stdout=fh) #----------------------------------------------------------------------------- @@ -127,11 +128,9 @@ def run(self): class build_ext(_build_ext.build_ext): def has_ecodes(self): - ecodes_path = os.path.join(here, 'evdev/ecodes.c') - res = os.path.exists(ecodes_path) - if res: + if ecodes_path.exists(): print('ecodes.c already exists ... skipping build_ecodes') - return not res + return not ecodes_path.exists() def run(self): for cmd_name in self.get_sub_commands(): From 4c1e2b7f78603adf429ffb0a7130c9bf5aab3b2a Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Fri, 20 Jan 2023 15:27:34 +0100 Subject: [PATCH 196/270] Drop distutils compatibility imports --- setup.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index bdda634..9517fe8 100755 --- a/setup.py +++ b/setup.py @@ -7,12 +7,8 @@ #----------------------------------------------------------------------------- -try: - from setuptools import setup, Extension, Command - from setuptools.command import build_ext as _build_ext -except ImportError: - from distutils.core import setup, Extension, Command - from distutils.command import build_ext as _build_ext +from setuptools import setup, Extension, Command +from setuptools.command import build_ext as _build_ext #----------------------------------------------------------------------------- From a51eb87db756580e25b45b5cdcdf3c511d9222e1 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Fri, 20 Jan 2023 15:38:10 +0100 Subject: [PATCH 197/270] Drop Python 2 remnants from genecodes.py --- LICENSE | 2 +- evdev/genecodes.py | 26 ++------------------------ setup.py | 1 + 3 files changed, 4 insertions(+), 25 deletions(-) diff --git a/LICENSE b/LICENSE index ce3a1f7..5600871 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2012-2016 Georgi Valkov. All rights reserved. +Copyright (c) 2012-2023 Georgi Valkov. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are diff --git a/evdev/genecodes.py b/evdev/genecodes.py index 04c10e1..15e4ded 100644 --- a/evdev/genecodes.py +++ b/evdev/genecodes.py @@ -47,7 +47,6 @@ { NULL, NULL, 0, NULL} }; -#if PY_MAJOR_VERSION >= 3 static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, MODULE_NAME, @@ -59,38 +58,17 @@ NULL, /* m_clear */ NULL, /* m_free */ }; -#endif -static PyObject * -moduleinit(void) +PyMODINIT_FUNC +PyInit__ecodes(void) { - -#if PY_MAJOR_VERSION >= 3 PyObject* m = PyModule_Create(&moduledef); -#else - PyObject* m = Py_InitModule3(MODULE_NAME, MethodTable, MODULE_HELP); -#endif - if (m == NULL) return NULL; %s return m; } - -#if PY_MAJOR_VERSION >= 3 -PyMODINIT_FUNC -PyInit__ecodes(void) -{ - return moduleinit(); -} -#else -PyMODINIT_FUNC -init_ecodes(void) -{ - moduleinit(); -} -#endif ''' def parse_header(header): diff --git a/setup.py b/setup.py index 9517fe8..ff01e4d 100755 --- a/setup.py +++ b/setup.py @@ -24,6 +24,7 @@ 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', 'Operating System :: POSIX :: Linux', 'Intended Audience :: Developers', 'Topic :: Software Development :: Libraries', From 1eda7386a9efc13b8fdd50ea420f9e531e4999d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20=C5=A0u=C4=87ur?= Date: Fri, 20 Jan 2023 16:05:34 +0100 Subject: [PATCH 198/270] Put unused variable to use (#184) Variable effect_type was declared but never used, in the ff-event injection example. --- docs/tutorial.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 7416181..cdaa886 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -445,7 +445,7 @@ Injecting an FF-event into first FF-capable device found ecodes.FF_RUMBLE, -1, 0, ff.Trigger(0, 0), ff.Replay(duration_ms, 0), - ff.EffectType(ff_rumble_effect=rumble) + effect_type ) repeat_count = 1 From 2dd6ce6364bb67eedb209f6aa0bace0c18a3a40a Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Fri, 20 Jan 2023 16:15:19 +0100 Subject: [PATCH 199/270] Bump version: 1.6.0 -> 1.6.1 --- docs/changelog.rst | 6 ++++++ docs/conf.py | 2 +- setup.cfg | 2 +- setup.py | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index e7b3af4..f656eb1 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,12 @@ Changelog --------- +1.6.1 (Jan 20, 2023) +================== + +- Fix generation of ``ecodes.c`` when the path to ````sys.executable`` contains spaces. + + 1.6.0 (Jul 17, 2022) ================== diff --git a/docs/conf.py b/docs/conf.py index 8dc3ae9..96dc4d6 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -61,7 +61,7 @@ # built documents. # # The full version, including alpha/beta/rc tags. -release = '1.6.0' +release = '1.6.1' # The short X.Y version. version = release diff --git a/setup.cfg b/setup.cfg index 9e2dc85..47ca660 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.6.0 +current_version = 1.6.1 message = Bump version: {current_version} -> {new_version} commit = True tag = True diff --git a/setup.py b/setup.py index ff01e4d..73ba1f5 100755 --- a/setup.py +++ b/setup.py @@ -41,7 +41,7 @@ #----------------------------------------------------------------------------- kw = { 'name': 'evdev', - 'version': '1.6.0', + 'version': '1.6.1', 'description': 'Bindings to the Linux input handling subsystem', 'long_description': (curdir / 'README.rst').read_text(), From bedcfbf5d4c3efed91ee2c66511c52ad3612a147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Sun, 27 Aug 2023 15:27:29 +0200 Subject: [PATCH 200/270] respect CPATH/C_INCLUDE_PATH Flags are hard to pass when python-evdev is installed as part of another python library. Instead evdev should check standard environment variables that also a C compiler would use to locate a header file. See: https://gcc.gnu.org/onlinedocs/cpp/Environment-Variables.html Update setup.py Co-authored-by: Tobi --- setup.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 73ba1f5..a9f45ef 100755 --- a/setup.py +++ b/setup.py @@ -64,11 +64,14 @@ #----------------------------------------------------------------------------- def create_ecodes(headers=None): if not headers: - headers = [ - '/usr/include/linux/input.h', - '/usr/include/linux/input-event-codes.h', - '/usr/include/linux/uinput.h', - ] + include_paths = set() + if os.environ.get("CPATH", "").strip() != "": + include_paths.update(os.environ["CPATH"].split(":")) + if os.environ.get("C_INCLUDE_PATH", "").strip() != "": + include_paths.update(os.environ["C_INCLUDE_PATH"].split(":")) + include_paths.add("/usr/include") + files = ["linux/input.h", "linux/input-event-codes.h", "linux/uinput.h"] + headers = [os.path.join(path, file) for path in include_paths for file in files] headers = [header for header in headers if os.path.isfile(header)] if not headers: From ae51cb1dca1daff3d459ca1b0c8cb6eedc5129e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Sun, 3 Dec 2023 13:21:28 +0100 Subject: [PATCH 201/270] genecodes: also include uinput.h --- evdev/genecodes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/evdev/genecodes.py b/evdev/genecodes.py index 15e4ded..5b70154 100644 --- a/evdev/genecodes.py +++ b/evdev/genecodes.py @@ -13,6 +13,7 @@ headers = [ '/usr/include/linux/input.h', '/usr/include/linux/input-event-codes.h', + '/usr/include/linux/uinput.h', ] if sys.argv[1:]: From b4db32ebbcca4f7d6a7e9bac137e22b0edbd4fae Mon Sep 17 00:00:00 2001 From: quaxalber <64684396+quaxalber@users.noreply.github.com> Date: Fri, 8 Dec 2023 18:15:23 +0100 Subject: [PATCH 202/270] Print uniq instead of phys, if available --- evdev/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evdev/device.py b/evdev/device.py index dbfad5d..6ee6168 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -284,7 +284,7 @@ def __eq__(self, other): def __str__(self): msg = 'device {}, name "{}", phys "{}"' - return msg.format(self.path, self.name, self.phys) + return msg.format(self.path, self.name, self.uniq if self.uniq else self.phys) def __repr__(self): msg = (self.__class__.__name__, self.path) From e7b9dfc65579b2b889943823207c04d304b8477e Mon Sep 17 00:00:00 2001 From: Benjamin T Date: Sun, 10 Dec 2023 13:21:56 +0100 Subject: [PATCH 203/270] Print uniq additionally to phys, if available --- evdev/device.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/evdev/device.py b/evdev/device.py index 6ee6168..808b9fd 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -283,8 +283,9 @@ def __eq__(self, other): and self.path == other.path def __str__(self): - msg = 'device {}, name "{}", phys "{}"' - return msg.format(self.path, self.name, self.uniq if self.uniq else self.phys) + msg = 'device {}, name "{}", phys "{}"{}' + uniq = ', uniq "{}"'.format(self.uniq) if self.uniq else '' + return msg.format(self.path, self.name, self.phys, uniq) def __repr__(self): msg = (self.__class__.__name__, self.path) From c688c9e63c535f3a9e0fae4930315ef432d09384 Mon Sep 17 00:00:00 2001 From: John Salamon Date: Fri, 15 Dec 2023 01:04:06 +1030 Subject: [PATCH 204/270] Correct ff_effects_max of uinputs (#198) --- docs/tutorial.rst | 52 +++++++++++++++++++++++++++++++++++++++++++++++ evdev/uinput.c | 14 +++++++------ evdev/uinput.py | 11 ++++++++-- 3 files changed, 69 insertions(+), 8 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index cdaa886..286a493 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -453,3 +453,55 @@ Injecting an FF-event into first FF-capable device found dev.write(ecodes.EV_FF, effect_id, repeat_count) time.sleep(duration_ms) dev.erase_effect(effect_id) + +Forwarding force-feedback from uinput to a real device +====================================================== + +:: + + import evdev + from evdev import ecodes as e + + # Find first EV_FF capable event device (that we have permissions to use). + for name in evdev.list_devices(): + dev = evdev.InputDevice(name) + if e.EV_FF in dev.capabilities(): + break + # To ensure forwarding works correctly it is important that `max_effects` + # of the uinput device is <= `dev.ff_effects_count`. + # `from_device()` will do this automatically, but in some situations you may + # want to set the `max_effects` parameter manually, such as when using `Uinput()`. + # `filtered_types` is specified as by default EV_FF events are filtered + uinput = evdev.UInput.from_device(dev, filtered_types=[e.EV_SYN]) + + # Keeps track of which effects have been uploaded to the device + effects = set() + + for event in uinput.read_loop(): + + # Handle the special uinput events + if event.type == e.EV_UINPUT: + + if event.code == e.UI_FF_UPLOAD: + upload = uinput.begin_upload(event.value) + + # Checks if this is a new effect + if upload.effect.id not in effects: + effects.add(upload.effect.id) + # Setting id to 1 indicates that a new effect must be allocated + upload.effect.id = -1 + + dev.upload_effect(upload.effect) + upload.retval = 0 + uinput.end_upload(upload) + + elif event.code == e.UI_FF_ERASE: + erase = uinput.begin_erase(event.value) + erase.retval = 0 + dev.erase_effect(erase.effect_id) + effects.remove(erase.effect_id) + uinput.end_erase(erase) + + # Forward writes to actual rumble device. + elif event.type == e.EV_FF: + dev.write(event.type, event.code, event.value) diff --git a/evdev/uinput.c b/evdev/uinput.c index 20318ce..6848bc2 100644 --- a/evdev/uinput.c +++ b/evdev/uinput.c @@ -108,14 +108,15 @@ static PyObject * uinput_setup(PyObject *self, PyObject *args) { int fd, len, i; uint16_t vendor, product, version, bustype; + uint32_t max_effects; PyObject *absinfo = NULL, *item = NULL; struct uinput_abs_setup abs_setup; const char* name; - int ret = PyArg_ParseTuple(args, "isHHHHO", &fd, &name, &vendor, - &product, &version, &bustype, &absinfo); + int ret = PyArg_ParseTuple(args, "isHHHHOI", &fd, &name, &vendor, + &product, &version, &bustype, &absinfo, &max_effects); if (!ret) return NULL; // Setup absinfo: @@ -147,7 +148,7 @@ uinput_setup(PyObject *self, PyObject *args) { usetup.id.product = product; usetup.id.version = version; usetup.id.bustype = bustype; - usetup.ff_effects_max = FF_MAX_EFFECTS; + usetup.ff_effects_max = max_effects; if(ioctl(fd, UI_DEV_SETUP, &usetup) < 0) goto on_err; @@ -166,14 +167,15 @@ static PyObject * uinput_setup(PyObject *self, PyObject *args) { int fd, len, i, abscode; uint16_t vendor, product, version, bustype; + uint32_t max_effects; PyObject *absinfo = NULL, *item = NULL; struct uinput_user_dev uidev; const char* name; - int ret = PyArg_ParseTuple(args, "isHHHHO", &fd, &name, &vendor, - &product, &version, &bustype, &absinfo); + int ret = PyArg_ParseTuple(args, "isHHHHOI", &fd, &name, &vendor, + &product, &version, &bustype, &absinfo, &max_effects); if (!ret) return NULL; memset(&uidev, 0, sizeof(uidev)); @@ -182,7 +184,7 @@ uinput_setup(PyObject *self, PyObject *args) { uidev.id.product = product; uidev.id.version = version; uidev.id.bustype = bustype; - uidev.ff_effects_max = FF_MAX_EFFECTS; + uidev.ff_effects_max = max_effects; len = PyList_Size(absinfo); for (i=0; i Date: Tue, 9 Jan 2024 21:04:32 +0000 Subject: [PATCH 205/270] New method for finding the device node corresponding to a uinput device (#206) * Add binding for UI_GET_SYSNAME ioctl * New modern Linux-specific method to find device The old method of scanning the filesystem to find an event device with a matching name was prone to race conditions, required a time.sleep delay, unnecessarily opened many event devices, and had other fixable issues. The new UI_GET_SYSNAME-based method is immune to race conditions and does not require time.sleep(), while also not suffering from the aforementioned fixable issues. * Move get_sysname() to the same block as _find_device_linux() Co-authored-by: Tobi <28510156+sezanzeb@users.noreply.github.com> * Sort event devices in _find_device_fallback() * Wait 0.1s if /dev/input/event* does not exist Unlike what a previous commit claimed, the new method is only guaranteed to find the device name instantaneously, but if /dev is managed by udev, the corresponding /dev/input/input* file might not exist immediately. On modern Linux systems, those files are managed by devtmpfs so they do show up immediately and it would be a waste of time to sleep, but we do want a fallback time.sleep() call for those operating systems which do not use devtmpfs. It is assumed that on any linux system, /sys is managed by sysfs; it conceptually doesn't really make sense to let that directory be managed by any other filesystem because the files in /sys are basically syscalls. As such, we assume that /sys always updates instantaneously, and if for whatever reason it doesn't, _find_device_fallback shall be used instead. * Add a comment Co-authored-by: Tobi <28510156+sezanzeb@users.noreply.github.com> * Replace IOError with OSError Since Python 3.3 (PEP 3151), IOError and OSError are aliases of each other, and OSError is the prefered name. Rename all mentions of IOError in the C bindings with OSError. * Replace IOError with OSError in documentation * Explain why the sorting is necessary Co-authored-by: Tobi <28510156+sezanzeb@users.noreply.github.com> --------- Co-authored-by: Tobi <28510156+sezanzeb@users.noreply.github.com> --- evdev/device.py | 4 +-- evdev/input.c | 18 ++++++------ evdev/uinput.c | 41 ++++++++++++++++++++------- evdev/uinput.py | 74 +++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 114 insertions(+), 23 deletions(-) diff --git a/evdev/device.py b/evdev/device.py index dbfad5d..27c84a6 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -309,7 +309,7 @@ def grab(self): Warning ------- - Grabbing an already grabbed device will raise an ``IOError``. + Grabbing an already grabbed device will raise an ``OSError``. ''' _input.ioctl_EVIOCGRAB(self.fd, 1) @@ -321,7 +321,7 @@ def ungrab(self): Warning ------- Releasing an already released device will raise an - ``IOError('Invalid argument')``. + ``OSError('Invalid argument')``. ''' _input.ioctl_EVIOCGRAB(self.fd, 0) diff --git a/evdev/input.c b/evdev/input.c index b1497a4..0256745 100644 --- a/evdev/input.c +++ b/evdev/input.c @@ -61,7 +61,7 @@ device_read(PyObject *self, PyObject *args) return Py_None; } - PyErr_SetFromErrno(PyExc_IOError); + PyErr_SetFromErrno(PyExc_OSError); return NULL; } @@ -101,7 +101,7 @@ device_read_many(PyObject *self, PyObject *args) ssize_t nread = read(fd, event, event_size*64); if (nread < 0) { - PyErr_SetFromErrno(PyExc_IOError); + PyErr_SetFromErrno(PyExc_OSError); Py_DECREF(event_list); return NULL; } @@ -208,7 +208,7 @@ ioctl_capabilities(PyObject *self, PyObject *args) return capabilities; on_err: - PyErr_SetFromErrno(PyExc_IOError); + PyErr_SetFromErrno(PyExc_OSError); return NULL; } @@ -243,7 +243,7 @@ ioctl_devinfo(PyObject *self, PyObject *args) name, phys, uniq); on_err: - PyErr_SetFromErrno(PyExc_IOError); + PyErr_SetFromErrno(PyExc_OSError); return NULL; } @@ -261,7 +261,7 @@ ioctl_EVIOCGABS(PyObject *self, PyObject *args) memset(&absinfo, 0, sizeof(absinfo)); ret = ioctl(fd, EVIOCGABS(ev_code), &absinfo); if (ret == -1) { - PyErr_SetFromErrno(PyExc_IOError); + PyErr_SetFromErrno(PyExc_OSError); return NULL; } @@ -296,7 +296,7 @@ ioctl_EVIOCSABS(PyObject *self, PyObject *args) ret = ioctl(fd, EVIOCSABS(ev_code), &absinfo); if (ret == -1) { - PyErr_SetFromErrno(PyExc_IOError); + PyErr_SetFromErrno(PyExc_OSError); return NULL; } @@ -362,7 +362,7 @@ ioctl_EVIOCGRAB(PyObject *self, PyObject *args) ret = ioctl(fd, EVIOCGRAB, (intptr_t)flag); if (ret != 0) { - PyErr_SetFromErrno(PyExc_IOError); + PyErr_SetFromErrno(PyExc_OSError); return NULL; } @@ -485,7 +485,7 @@ upload_effect(PyObject *self, PyObject *args) ret = ioctl(fd, EVIOCSFF, &effect); if (ret != 0) { - PyErr_SetFromErrno(PyExc_IOError); + PyErr_SetFromErrno(PyExc_OSError); return NULL; } @@ -504,7 +504,7 @@ erase_effect(PyObject *self, PyObject *args) long ff_id = PyLong_AsLong(ff_id_obj); ret = ioctl(fd, EVIOCRMFF, ff_id); if (ret != 0) { - PyErr_SetFromErrno(PyExc_IOError); + PyErr_SetFromErrno(PyExc_OSError); return NULL; } diff --git a/evdev/uinput.c b/evdev/uinput.c index 6848bc2..2e2d8e5 100644 --- a/evdev/uinput.c +++ b/evdev/uinput.c @@ -49,7 +49,7 @@ uinput_open(PyObject *self, PyObject *args) int fd = open(devnode, O_RDWR | O_NONBLOCK); if (fd < 0) { - PyErr_SetString(PyExc_IOError, "could not open uinput device in write mode"); + PyErr_SetString(PyExc_OSError, "could not open uinput device in write mode"); return NULL; } @@ -73,7 +73,7 @@ uinput_set_phys(PyObject *self, PyObject *args) on_err: _uinput_close(fd); - PyErr_SetFromErrno(PyExc_IOError); + PyErr_SetFromErrno(PyExc_OSError); return NULL; } @@ -93,10 +93,28 @@ uinput_set_prop(PyObject *self, PyObject *args) on_err: _uinput_close(fd); - PyErr_SetFromErrno(PyExc_IOError); + PyErr_SetFromErrno(PyExc_OSError); return NULL; } +static PyObject * +uinput_get_sysname(PyObject *self, PyObject *args) +{ + int fd; + char sysname[64]; + + int ret = PyArg_ParseTuple(args, "i", &fd); + if (!ret) return NULL; + + if (ioctl(fd, UI_GET_SYSNAME(sizeof(sysname)), &sysname) < 0) + goto on_err; + + return Py_BuildValue("s", &sysname); + + on_err: + PyErr_SetFromErrno(PyExc_OSError); + return NULL; +} // Different kernel versions have different device setup methods. You can read // more about it here: @@ -157,7 +175,7 @@ uinput_setup(PyObject *self, PyObject *args) { on_err: _uinput_close(fd); - PyErr_SetFromErrno(PyExc_IOError); + PyErr_SetFromErrno(PyExc_OSError); return NULL; } @@ -206,7 +224,7 @@ uinput_setup(PyObject *self, PyObject *args) { on_err: _uinput_close(fd); - PyErr_SetFromErrno(PyExc_IOError); + PyErr_SetFromErrno(PyExc_OSError); return NULL; } #endif @@ -227,7 +245,7 @@ uinput_create(PyObject *self, PyObject *args) on_err: _uinput_close(fd); - PyErr_SetFromErrno(PyExc_IOError); + PyErr_SetFromErrno(PyExc_OSError); return NULL; } @@ -241,7 +259,7 @@ uinput_close(PyObject *self, PyObject *args) if (!ret) return NULL; if (_uinput_close(fd) < 0) { - PyErr_SetFromErrno(PyExc_IOError); + PyErr_SetFromErrno(PyExc_OSError); return NULL; } @@ -269,8 +287,8 @@ uinput_write(PyObject *self, PyObject *args) if (write(fd, &event, sizeof(event)) != sizeof(event)) { // @todo: elaborate - // PyErr_SetString(PyExc_IOError, "error writing event to uinput device"); - PyErr_SetFromErrno(PyExc_IOError); + // PyErr_SetString(PyExc_OSError, "error writing event to uinput device"); + PyErr_SetFromErrno(PyExc_OSError); return NULL; } @@ -312,7 +330,7 @@ uinput_enable_event(PyObject *self, PyObject *args) on_err: _uinput_close(fd); - PyErr_SetFromErrno(PyExc_IOError); + PyErr_SetFromErrno(PyExc_OSError); return NULL; } @@ -361,6 +379,9 @@ static PyMethodDef MethodTable[] = { { "set_phys", uinput_set_phys, METH_VARARGS, "Set physical path"}, + { "get_sysname", uinput_get_sysname, METH_VARARGS, + "Obtain the sysname of the uinput device."}, + { "set_prop", uinput_set_prop, METH_VARARGS, "Set device input property"}, diff --git a/evdev/uinput.py b/evdev/uinput.py index 5b11bd7..2c1424b 100644 --- a/evdev/uinput.py +++ b/evdev/uinput.py @@ -1,6 +1,8 @@ # encoding: utf-8 import os +import platform +import re import stat import time from collections import defaultdict @@ -159,7 +161,7 @@ def __init__(self, #: An :class:`InputDevice ` instance #: for the fake input device. ``None`` if the device cannot be #: opened for reading and writing. - self.device = self._find_device() + self.device = self._find_device(self.fd) def _prepare_events(self, events): '''Prepare events for passing to _uinput.enable and _uinput.setup''' @@ -281,11 +283,79 @@ def _verify(self): msg = 'uinput device name must not be longer than {} characters' raise UInputError(msg.format(_uinput.maxnamelen)) - def _find_device(self): + def _find_device(self, fd): + ''' + Tries to find the device node. Will delegate this task to one of + several platform-specific functions. + ''' + if platform.system() == 'Linux': + try: + sysname = _uinput.get_sysname(fd) + return self._find_device_linux(sysname) + except OSError: + # UI_GET_SYSNAME returned an error code. We're likely dealing with + # an old kernel. Guess the device based on the filesystem. + pass + + # If we're not running or Linux or the above method fails for any reason, + # use the generic fallback method. + return self._find_device_fallback() + + def _find_device_linux(self, sysname): + ''' + Tries to find the device node when running on Linux. + ''' + + syspath = f'/sys/devices/virtual/input/{sysname}' + + # The sysfs entry for event devices should contain exactly one folder + # whose name matches the format "event[0-9]+". It is then assumed that + # the device node in /dev/input uses the same name. + regex = re.compile('event[0-9]+') + for entry in os.listdir(syspath): + if regex.fullmatch(entry): + device_path = f'/dev/input/{entry}' + break + else: # no break + raise FileNotFoundError() + + # It is possible that there is some delay before /dev/input/event* shows + # up on old systems that do not use devtmpfs, so if the device cannot be + # found, wait for a short amount and then try again once. + try: + return device.InputDevice(device_path) + except FileNotFoundError: + time.sleep(0.1) + return device.InputDevice(device_path) + + def _find_device_fallback(self): + ''' + Tries to find the device node when UI_GET_SYSNAME is not available or + we're running on a system sufficiently exotic that we do not know how + to interpret its return value. + ''' #:bug: the device node might not be immediately available time.sleep(0.1) + # There could also be another device with the same name already present, + # make sure to select the newest one. + # Strictly speaking, we cannot be certain that everything returned by list_devices() + # ends at event[0-9]+: it might return something like "/dev/input/events_all". Find + # the devices that have the expected structure and extract their device number. + path_number_pairs = [] + regex = re.compile('/dev/input/event([0-9]+)') for path in util.list_devices('/dev/input/'): + regex_match = regex.fullmatch(path) + if not regex_match: + continue + device_number = int(regex_match[1]) + path_number_pairs.append((path, device_number)) + + # The modification date of the devnode is not reliable unfortunately, so we + # are sorting by the number in the name + path_number_pairs.sort(key=lambda pair: pair[1], reverse=True) + + for (path, _) in path_number_pairs: d = device.InputDevice(path) if d.name == self.name: return d From 693a2978af98793be634940cd53472c39bd6deaa Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 28 Jan 2024 23:48:20 +0100 Subject: [PATCH 206/270] Always include "uniq" address in InputDevice.__str__() --- evdev/device.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/evdev/device.py b/evdev/device.py index b92bfcc..83a2cc8 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -141,7 +141,7 @@ def __init__(self, dev): #: The physical topology of the device. self.phys = info_res[5] - #: The unique address of the device. + #: The unique identifier of the device. self.uniq = info_res[6] #: The evdev protocol version. @@ -283,9 +283,8 @@ def __eq__(self, other): and self.path == other.path def __str__(self): - msg = 'device {}, name "{}", phys "{}"{}' - uniq = ', uniq "{}"'.format(self.uniq) if self.uniq else '' - return msg.format(self.path, self.name, self.phys, uniq) + msg = 'device {}, name "{}", phys "{}", uniq "{}"' + return msg.format(self.path, self.name, self.phys, self.uniq or "") def __repr__(self): msg = (self.__class__.__name__, self.path) From e00c457dda090417c7258ced4d157447ca9ab771 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Mon, 29 Jan 2024 00:10:25 +0100 Subject: [PATCH 207/270] ruff format --- docs/conf.py | 163 ++++++++++++++++++++------------------- evdev/__init__.py | 4 +- evdev/device.py | 121 ++++++++++++++--------------- evdev/ecodes.py | 14 ++-- evdev/eventio.py | 41 +++++----- evdev/eventio_async.py | 18 +++-- evdev/events.py | 71 ++++++++--------- evdev/evtest.py | 81 ++++++++++--------- evdev/ff.py | 134 ++++++++++++++++---------------- evdev/genecodes.py | 35 +++++---- evdev/uinput.py | 132 ++++++++++++++++--------------- evdev/util.py | 34 ++++---- examples/udev-example.py | 18 ++--- pyproject.toml | 2 + setup.py | 113 +++++++++++++-------------- tests/test_ecodes.py | 12 ++- tests/test_events.py | 4 +- tests/test_uinput.py | 55 +++++++------ tests/test_util.py | 18 ++--- 19 files changed, 542 insertions(+), 528 deletions(-) create mode 100644 pyproject.toml diff --git a/docs/conf.py b/docs/conf.py index 96dc4d6..d1dc3fd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -4,7 +4,7 @@ import sphinx_rtd_theme # Check if readthedocs is building us -on_rtd = os.environ.get('READTHEDOCS', None) == 'True' +on_rtd = os.environ.get("READTHEDOCS", None) == "True" # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -12,93 +12,97 @@ # Trick autodoc into running without having built the extension modules. if on_rtd: - with open('../evdev/_ecodes.py', 'w') as fh: - fh.write(''' + with open("../evdev/_ecodes.py", "w") as fh: + fh.write( + """ KEY = ABS = REL = SW = MSC = LED = REP = SND = SYN = FF = FF_STATUS = BTN_A = KEY_A = 1 EV_KEY = EV_ABS = EV_REL = EV_SW = EV_MSC = EV_LED = EV_REP = 1 EV_SND = EV_SYN = EV_FF = EV_FF_STATUS = FF_STATUS = 1 -KEY_MAX, KEY_CNT = 1, 2''') +KEY_MAX, KEY_CNT = 1, 2""" + ) - with open('../evdev/_input.py', 'w'): pass - with open('../evdev/_uinput.py', 'w'): pass + with open("../evdev/_input.py", "w"): + pass + with open("../evdev/_uinput.py", "w"): + pass # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.viewcode', - 'sphinx.ext.intersphinx', - 'sphinx.ext.napoleon', - 'sphinx_copybutton', + "sphinx.ext.autodoc", + "sphinx.ext.viewcode", + "sphinx.ext.intersphinx", + "sphinx.ext.napoleon", + "sphinx_copybutton", ] -autodoc_member_order = 'bysource' +autodoc_member_order = "bysource" # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'python-evdev' -copyright = u'2012-2022, Georgi Valkov' +project = "python-evdev" +copyright = "2012-2024, Georgi Valkov and contributors" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The full version, including alpha/beta/rc tags. -release = '1.6.1' +release = "1.6.1" # The short X.Y version. version = release # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -#pygments_style = 'sphinx' +# pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- @@ -108,7 +112,8 @@ if not on_rtd: import sphinx_rtd_theme - html_theme = 'sphinx_rtd_theme' + + html_theme = "sphinx_rtd_theme" html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # Theme options are theme-specific and customize the look and feel of a theme @@ -120,122 +125,116 @@ # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -html_title = 'Python-evdev' +html_title = "Python-evdev" # A shorter title for the navigation bar. Default is the same as html_title. -html_short_title = 'evdev' +html_short_title = "evdev" # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = '_static/evdev-logo-small.png' +# html_logo = '_static/evdev-logo-small.png' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'python-evdev-doc' +htmlhelp_basename = "python-evdev-doc" # -- Options for LaTeX output -------------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'python-evdev.tex', u'evdev documentation', - u'Georgi Valkov', 'manual'), + ("index", "python-evdev.tex", "evdev documentation", "Georgi Valkov", "manual"), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'python-evdev', u'python-evdev Documentation', - [u'Georgi Valkov'], 1) -] +man_pages = [("index", "python-evdev", "python-evdev Documentation", ["Georgi Valkov"], 1)] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ @@ -244,23 +243,29 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'python-evdev', u'python-evdev Documentation', - u'Georgi Valkov', 'evdev', 'Bindings for the linux input handling subsystem.', - 'Miscellaneous'), + ( + "index", + "python-evdev", + "python-evdev Documentation", + "Georgi Valkov", + "evdev", + "Bindings for the linux input handling subsystem.", + "Miscellaneous", + ), ] -intersphinx_mapping = {'python': ('http://docs.python.org/3', None)} +intersphinx_mapping = {"python": ("http://docs.python.org/3", None)} # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' # Copybutton config -#copybutton_prompt_text = r">>> " -#copybutton_prompt_is_regexp = True -#copybutton_only_copy_prompt_lines = True +# copybutton_prompt_text = r">>> " +# copybutton_prompt_is_regexp = True +# copybutton_only_copy_prompt_lines = True diff --git a/evdev/__init__.py b/evdev/__init__.py index 797623b..36b330c 100644 --- a/evdev/__init__.py +++ b/evdev/__init__.py @@ -1,6 +1,6 @@ -#-------------------------------------------------------------------------- +# -------------------------------------------------------------------------- # Gather everything into a single, convenient namespace. -#-------------------------------------------------------------------------- +# -------------------------------------------------------------------------- from evdev.device import DeviceInfo, InputDevice, AbsInfo, EvdevError from evdev.events import InputEvent, KeyEvent, RelEvent, SynEvent, AbsEvent, event_factory diff --git a/evdev/device.py b/evdev/device.py index 83a2cc8..0d2d1d9 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -14,19 +14,16 @@ from evdev.eventio import EventIO, EvdevError -#-------------------------------------------------------------------------- -_AbsInfo = collections.namedtuple( - 'AbsInfo', ['value', 'min', 'max', 'fuzz', 'flat', 'resolution']) +# -------------------------------------------------------------------------- +_AbsInfo = collections.namedtuple("AbsInfo", ["value", "min", "max", "fuzz", "flat", "resolution"]) -_KbdInfo = collections.namedtuple( - 'KbdInfo', ['repeat', 'delay']) +_KbdInfo = collections.namedtuple("KbdInfo", ["repeat", "delay"]) -_DeviceInfo = collections.namedtuple( - 'DeviceInfo', ['bustype', 'vendor', 'product', 'version']) +_DeviceInfo = collections.namedtuple("DeviceInfo", ["bustype", "vendor", "product", "version"]) class AbsInfo(_AbsInfo): - '''Absolute axis information. + """Absolute axis information. A ``namedtuple`` used for storing absolute axis information - corresponds to the ``input_absinfo`` struct: @@ -62,14 +59,14 @@ class AbsInfo(_AbsInfo): The input core does not clamp reported values to the ``[minimum, maximum]`` limits, such task is left to userspace. - ''' + """ def __str__(self): - return 'val {}, min {}, max {}, fuzz {}, flat {}, res {}'.format(*self) + return "val {}, min {}, max {}, fuzz {}, flat {}, res {}".format(*self) class KbdInfo(_KbdInfo): - '''Keyboard repeat rate. + """Keyboard repeat rate. Attributes ---------- @@ -79,45 +76,44 @@ class KbdInfo(_KbdInfo): delay Amount of time that a key must be depressed before it will start to repeat (in milliseconds). - ''' + """ def __str__(self): - return 'repeat {}, delay {}'.format(*self) + return "repeat {}, delay {}".format(*self) class DeviceInfo(_DeviceInfo): - ''' + """ Attributes ---------- bustype vendor product version - ''' + """ def __str__(self): - msg = 'bus: {:04x}, vendor {:04x}, product {:04x}, version {:04x}' + msg = "bus: {:04x}, vendor {:04x}, product {:04x}, version {:04x}" return msg.format(*self) class InputDevice(EventIO): - ''' + """ A linux input device from which input events can be read. - ''' + """ - __slots__ = ('path', 'fd', 'info', 'name', 'phys', 'uniq', '_rawcapabilities', - 'version', 'ff_effects_count') + __slots__ = ("path", "fd", "info", "name", "phys", "uniq", "_rawcapabilities", "version", "ff_effects_count") def __init__(self, dev): - ''' + """ Arguments --------- dev : str|bytes|PathLike Path to input device - ''' + """ #: Path to input device. - self.path = dev if not hasattr(dev, '__fspath__') else dev.__fspath__() + self.path = dev if not hasattr(dev, "__fspath__") else dev.__fspath__() # Certain operations are possible only when the device is opened in # read-write mode. @@ -154,7 +150,7 @@ def __init__(self, dev): self.ff_effects_count = _input.ioctl_EVIOCGEFFECTS(self.fd) def __del__(self): - if hasattr(self, 'fd') and self.fd is not None: + if hasattr(self, "fd") and self.fd is not None: try: self.close() except (OSError, ImportError, AttributeError): @@ -179,7 +175,7 @@ def _capabilities(self, absinfo=True): return res def capabilities(self, verbose=False, absinfo=True): - ''' + """ Return the event types that this device supports as a mapping of supported event types to lists of handled event codes. @@ -216,7 +212,7 @@ def capabilities(self, verbose=False, absinfo=True): { ('EV_ABS', 3): [ (('ABS_X', 0), AbsInfo(min=0, max=255, fuzz=0, flat=0)), (('ABS_Y', 1), AbsInfo(min=0, max=255, fuzz=0, flat=0)) ]} - ''' + """ if verbose: return dict(util.resolve_ecodes_dict(self._capabilities(absinfo))) @@ -224,7 +220,7 @@ def capabilities(self, verbose=False, absinfo=True): return self._capabilities(absinfo) def input_props(self, verbose=False): - ''' + """ Get device properties and quirks. Example @@ -237,7 +233,7 @@ def input_props(self, verbose=False): [('INPUT_PROP_POINTER', 0), ('INPUT_PROP_POINTING_STICK', 5)] - ''' + """ props = _input.ioctl_EVIOCGPROP(self.fd) if verbose: return util.resolve_ecodes(ecodes.INPUT_PROP, props) @@ -245,7 +241,7 @@ def input_props(self, verbose=False): return props def leds(self, verbose=False): - ''' + """ Return currently set LED keys. Example @@ -258,7 +254,7 @@ def leds(self, verbose=False): [('LED_NUML', 0), ('LED_CAPSL', 1), ('LED_MISC', 8), ('LED_MAIL', 9)] - ''' + """ leds = _input.ioctl_EVIOCG_bits(self.fd, ecodes.EV_LED) if verbose: return util.resolve_ecodes(ecodes.LED, leds) @@ -266,21 +262,20 @@ def leds(self, verbose=False): return leds def set_led(self, led_num, value): - ''' + """ Set the state of the selected LED. Example ------- >>> device.set_led(ecodes.LED_NUML, 1) - ''' + """ self.write(ecodes.EV_LED, led_num, value) def __eq__(self, other): - ''' + """ Two devices are equal if their :data:`info` attributes are equal. - ''' - return isinstance(other, self.__class__) and self.info == other.info \ - and self.path == other.path + """ + return isinstance(other, self.__class__) and self.info == other.info and self.path == other.path def __str__(self): msg = 'device {}, name "{}", phys "{}", uniq "{}"' @@ -288,7 +283,7 @@ def __str__(self): def __repr__(self): msg = (self.__class__.__name__, self.path) - return '{}({!r})'.format(*msg) + return "{}({!r})".format(*msg) def __fspath__(self): return self.path @@ -302,7 +297,7 @@ def close(self): self.fd = -1 def grab(self): - ''' + """ Grab input device using ``EVIOCGRAB`` - other applications will be unable to receive events until the device is released. Only one process can hold a ``EVIOCGRAB`` on a device. @@ -310,55 +305,55 @@ def grab(self): Warning ------- Grabbing an already grabbed device will raise an ``OSError``. - ''' + """ _input.ioctl_EVIOCGRAB(self.fd, 1) def ungrab(self): - ''' + """ Release device if it has been already grabbed (uses `EVIOCGRAB`). Warning ------- Releasing an already released device will raise an ``OSError('Invalid argument')``. - ''' + """ _input.ioctl_EVIOCGRAB(self.fd, 0) @contextlib.contextmanager def grab_context(self): - ''' + """ A context manager for the duration of which only the current process will be able to receive events from the device. - ''' + """ self.grab() yield self.ungrab() def upload_effect(self, effect): - ''' + """ Upload a force feedback effect to a force feedback device. - ''' + """ data = memoryview(effect).tobytes() ff_id = _input.upload_effect(self.fd, data) return ff_id def erase_effect(self, ff_id): - ''' + """ Erase a force effect from a force feedback device. This also stops the effect. - ''' + """ _input.erase_effect(self.fd, ff_id) @property def repeat(self): - ''' + """ Get or set the keyboard repeat rate (in characters per minute) and delay (in milliseconds). - ''' + """ return KbdInfo(*_input.ioctl_EVIOCGREP(self.fd)) @@ -367,7 +362,7 @@ def repeat(self, value): return _input.ioctl_EVIOCSREP(self.fd, *value) def active_keys(self, verbose=False): - ''' + """ Return currently active keys. Example @@ -382,7 +377,7 @@ def active_keys(self, verbose=False): [('KEY_ESC', 1), ('KEY_LEFTSHIFT', 42)] - ''' + """ active_keys = _input.ioctl_EVIOCG_bits(self.fd, ecodes.EV_KEY) if verbose: return util.resolve_ecodes(ecodes.KEY, active_keys) @@ -391,12 +386,12 @@ def active_keys(self, verbose=False): @property def fn(self): - msg = 'Please use {0}.path instead of {0}.fn'.format(self.__class__.__name__) + msg = "Please use {0}.path instead of {0}.fn".format(self.__class__.__name__) warnings.warn(msg, DeprecationWarning, stacklevel=2) return self.path def absinfo(self, axis_num): - ''' + """ Return current :class:`AbsInfo` for input device axis Arguments @@ -408,11 +403,11 @@ def absinfo(self, axis_num): ------- >>> device.absinfo(ecodes.ABS_X) AbsInfo(value=1501, min=-32768, max=32767, fuzz=0, flat=128, resolution=0) - ''' + """ return AbsInfo(*_input.ioctl_EVIOCGABS(self.fd, axis_num)) def set_absinfo(self, axis_num, value=None, min=None, max=None, fuzz=None, flat=None, resolution=None): - ''' + """ Update :class:`AbsInfo` values. Only specified values will be overwritten. Arguments @@ -427,13 +422,15 @@ def set_absinfo(self, axis_num, value=None, min=None, max=None, fuzz=None, flat= You can also unpack AbsInfo tuple that will overwrite all values >>> device.set_absinfo(ecodes.ABS_Y, *AbsInfo(0, -2000, 2000, 0, 15, 0)) - ''' + """ cur_absinfo = self.absinfo(axis_num) - new_absinfo = AbsInfo(value if value is not None else cur_absinfo.value, - min if min is not None else cur_absinfo.min, - max if max is not None else cur_absinfo.max, - fuzz if fuzz is not None else cur_absinfo.fuzz, - flat if flat is not None else cur_absinfo.flat, - resolution if resolution is not None else cur_absinfo.resolution) + new_absinfo = AbsInfo( + value if value is not None else cur_absinfo.value, + min if min is not None else cur_absinfo.min, + max if max is not None else cur_absinfo.max, + fuzz if fuzz is not None else cur_absinfo.fuzz, + flat if flat is not None else cur_absinfo.flat, + resolution if resolution is not None else cur_absinfo.resolution, + ) _input.ioctl_EVIOCSABS(self.fd, axis_num, new_absinfo) diff --git a/evdev/ecodes.py b/evdev/ecodes.py index e3e4ffc..3562368 100644 --- a/evdev/ecodes.py +++ b/evdev/ecodes.py @@ -1,6 +1,4 @@ -# encoding: utf-8 - -''' +""" This modules exposes the integer constants defined in ``linux/input.h`` and ``linux/input-event-codes.h``. @@ -38,7 +36,7 @@ >>> evdev.ecodes.FF[81] 'FF_PERIODIC' -''' +""" from inspect import getmembers from evdev import _ecodes @@ -47,8 +45,8 @@ #: Mapping of names to values. ecodes = {} -prefixes = 'KEY ABS REL SW MSC LED BTN REP SND ID EV BUS SYN FF_STATUS FF INPUT_PROP' -prev_prefix = '' +prefixes = "KEY ABS REL SW MSC LED BTN REP SND ID EV BUS SYN FF_STATUS FF INPUT_PROP" +prev_prefix = "" g = globals() # eg. code: 'REL_Z', val: 2 @@ -87,13 +85,13 @@ _ecodes.EV_KEY: keys, _ecodes.EV_ABS: ABS, _ecodes.EV_REL: REL, - _ecodes.EV_SW: SW, + _ecodes.EV_SW: SW, _ecodes.EV_MSC: MSC, _ecodes.EV_LED: LED, _ecodes.EV_REP: REP, _ecodes.EV_SND: SND, _ecodes.EV_SYN: SYN, - _ecodes.EV_FF: FF, + _ecodes.EV_FF: FF, _ecodes.EV_FF_STATUS: FF_STATUS, } diff --git a/evdev/eventio.py b/evdev/eventio.py index 8d84f55..1b0e5cc 100644 --- a/evdev/eventio.py +++ b/evdev/eventio.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - import os import fcntl import select @@ -8,13 +6,14 @@ from evdev import _input, _uinput, ecodes, util from evdev.events import InputEvent -#-------------------------------------------------------------------------- + +# -------------------------------------------------------------------------- class EvdevError(Exception): pass class EventIO: - ''' + """ Base class for reading and writing input events. This class is used by :class:`InputDevice` and :class:`UInput`. @@ -26,20 +25,20 @@ class EventIO: - On, :class:`UInput` it used for writing user-generated events (e.g. key presses, mouse movements) and reading feedback events (e.g. leds, beeps). - ''' + """ def fileno(self): - ''' + """ Return the file descriptor to the open event device. This makes it possible to pass instances directly to :func:`select.select()` and :class:`asyncore.file_dispatcher`. - ''' + """ return self.fd def read_loop(self): - ''' + """ Enter an endless :func:`select.select()` loop that yields input events. - ''' + """ while True: r, w, x = select.select([self.fd], [], []) @@ -47,12 +46,12 @@ def read_loop(self): yield event def read_one(self): - ''' + """ Read and return a single input event as an instance of :class:`InputEvent `. Return ``None`` if there are no pending input events. - ''' + """ # event -> (sec, usec, type, code, val) event = _input.device_read(self.fd) @@ -61,11 +60,11 @@ def read_one(self): return InputEvent(*event) def read(self): - ''' + """ Read multiple input events from device. Return a generator object that yields :class:`InputEvent ` instances. Raises `BlockingIOError` if there are no available events at the moment. - ''' + """ # events -> [(sec, usec, type, code, val), ...] events = _input.device_read_many(self.fd) @@ -74,10 +73,11 @@ def read(self): yield InputEvent(*event) def need_write(func): - ''' + """ Decorator that raises :class:`EvdevError` if there is no write access to the input device. - ''' + """ + @functools.wraps(func) def wrapper(*args): fd = args[0].fd @@ -85,10 +85,11 @@ def wrapper(*args): return func(*args) msg = 'no write access to device "%s"' % args[0].path raise EvdevError(msg) + return wrapper def write_event(self, event): - ''' + """ Inject an input event into the input subsystem. Events are queued until a synchronization event is received. @@ -103,16 +104,16 @@ def write_event(self, event): ------- >>> ev = InputEvent(1334414993, 274296, ecodes.EV_KEY, ecodes.KEY_A, 1) >>> ui.write_event(ev) - ''' + """ - if hasattr(event, 'event'): + if hasattr(event, "event"): event = event.event self.write(event.type, event.code, event.value) @need_write def write(self, etype, code, value): - ''' + """ Inject an input event into the input subsystem. Events are queued until a synchronization event is received. @@ -131,7 +132,7 @@ def write(self, etype, code, value): --------- >>> ui.write(e.EV_KEY, e.KEY_A, 1) # key A - down >>> ui.write(e.EV_KEY, e.KEY_A, 0) # key A - up - ''' + """ _uinput.write(self.fd, etype, code, value) diff --git a/evdev/eventio_async.py b/evdev/eventio_async.py index 2d3468e..e89765e 100644 --- a/evdev/eventio_async.py +++ b/evdev/eventio_async.py @@ -1,9 +1,8 @@ -# encoding: utf-8 - import asyncio import select from evdev import eventio + # needed for compatibility from evdev.eventio import EvdevError @@ -15,6 +14,7 @@ def _do_when_readable(self, callback): def ready(): loop.remove_reader(self.fileno()) callback() + loop.add_reader(self.fileno(), ready) def _set_result(self, future, cb): @@ -24,30 +24,30 @@ def _set_result(self, future, cb): future.set_exception(error) def async_read_one(self): - ''' + """ Asyncio coroutine to read and return a single input event as an instance of :class:`InputEvent `. - ''' + """ future = asyncio.Future() self._do_when_readable(lambda: self._set_result(future, self.read_one)) return future def async_read(self): - ''' + """ Asyncio coroutine to read multiple input events from device. Return a generator object that yields :class:`InputEvent ` instances. - ''' + """ future = asyncio.Future() self._do_when_readable(lambda: self._set_result(future, self.read)) return future def async_read_loop(self): - ''' + """ Return an iterator that yields input events. This iterator is compatible with the ``async for`` syntax. - ''' + """ return ReadIterator(self) def close(self): @@ -87,11 +87,13 @@ def __anext__(self): # Read from the previous batch of events. future.set_result(next(self.current_batch)) except StopIteration: + def next_batch_ready(batch): try: self.current_batch = batch.result() future.set_result(next(self.current_batch)) except Exception as e: future.set_exception(e) + self.device.async_read().add_done_callback(next_batch_ready) return future diff --git a/evdev/events.py b/evdev/events.py index af42386..104b563 100644 --- a/evdev/events.py +++ b/evdev/events.py @@ -1,6 +1,4 @@ -# encoding: utf-8 - -''' +""" This module provides the :class:`InputEvent` class, which closely resembles the ``input_event`` struct defined in ``linux/input.h``: @@ -34,7 +32,7 @@ key event at 1337197425.477835, 28 (KEY_ENTER), up >>> print(repr(key_event)) KeyEvent(InputEvent(1337197425L, 477835L, 1, 28, 0L)) -''' +""" # event type descriptions have been taken mot-a-mot from: # http://www.kernel.org/doc/Documentation/input/event-codes.txt @@ -43,9 +41,9 @@ class InputEvent: - '''A generic input event.''' + """A generic input event.""" - __slots__ = 'sec', 'usec', 'type', 'code', 'value' + __slots__ = "sec", "usec", "type", "code", "value" def __init__(self, sec, usec, type, code, value): #: Time in seconds since epoch at which event occurred. @@ -64,34 +62,33 @@ def __init__(self, sec, usec, type, code, value): self.value = value def timestamp(self): - '''Return event timestamp as a float.''' + """Return event timestamp as a float.""" return self.sec + (self.usec / 1000000.0) def __str__(s): - msg = 'event at {:f}, code {:02d}, type {:02d}, val {:02d}' + msg = "event at {:f}, code {:02d}, type {:02d}, val {:02d}" return msg.format(s.timestamp(), s.code, s.type, s.value) def __repr__(s): - msg = '{}({!r}, {!r}, {!r}, {!r}, {!r})' - return msg.format(s.__class__.__name__, - s.sec, s.usec, s.type, s.code, s.value) + msg = "{}({!r}, {!r}, {!r}, {!r}, {!r})" + return msg.format(s.__class__.__name__, s.sec, s.usec, s.type, s.code, s.value) class KeyEvent: - '''An event generated by a keyboard, button or other key-like devices.''' + """An event generated by a keyboard, button or other key-like devices.""" - key_up = 0x0 + key_up = 0x0 key_down = 0x1 key_hold = 0x2 - __slots__ = 'scancode', 'keycode', 'keystate', 'event' + __slots__ = "scancode", "keycode", "keystate", "event" def __init__(self, event, allow_unknown=False): - ''' + """ The ``allow_unknown`` argument determines what to do in the event of an event code for which a key code cannot be found. If ``False`` a ``KeyError`` will be raised. If ``True`` the keycode will be set to the hex value of the event code. - ''' + """ self.scancode = event.code @@ -106,7 +103,7 @@ def __init__(self, event, allow_unknown=False): self.keycode = keys[event.code] except KeyError: if allow_unknown: - self.keycode = '0x{:02X}'.format(event.code) + self.keycode = "0x{:02X}".format(event.code) else: raise @@ -115,70 +112,69 @@ def __init__(self, event, allow_unknown=False): def __str__(self): try: - ks = ('up', 'down', 'hold')[self.keystate] + ks = ("up", "down", "hold")[self.keystate] except IndexError: - ks = 'unknown' + ks = "unknown" - msg = 'key event at {:f}, {} ({}), {}' - return msg.format(self.event.timestamp(), - self.scancode, self.keycode, ks) + msg = "key event at {:f}, {} ({}), {}" + return msg.format(self.event.timestamp(), self.scancode, self.keycode, ks) def __repr__(s): - return '{}({!r})'.format(s.__class__.__name__, s.event) + return "{}({!r})".format(s.__class__.__name__, s.event) class RelEvent: - '''A relative axis event (e.g moving the mouse 5 units to the left).''' + """A relative axis event (e.g moving the mouse 5 units to the left).""" - __slots__ = 'event' + __slots__ = "event" def __init__(self, event): #: Reference to an :class:`InputEvent` instance. self.event = event def __str__(self): - msg = 'relative axis event at {:f}, {}' + msg = "relative axis event at {:f}, {}" return msg.format(self.event.timestamp(), REL[self.event.code]) def __repr__(s): - return '{}({!r})'.format(s.__class__.__name__, s.event) + return "{}({!r})".format(s.__class__.__name__, s.event) class AbsEvent: - '''An absolute axis event (e.g the coordinates of a tap on a touchscreen).''' + """An absolute axis event (e.g the coordinates of a tap on a touchscreen).""" - __slots__ = 'event' + __slots__ = "event" def __init__(self, event): #: Reference to an :class:`InputEvent` instance. self.event = event def __str__(self): - msg = 'absolute axis event at {:f}, {}' + msg = "absolute axis event at {:f}, {}" return msg.format(self.event.timestamp(), ABS[self.event.code]) def __repr__(s): - return '{}({!r})'.format(s.__class__.__name__, s.event) + return "{}({!r})".format(s.__class__.__name__, s.event) class SynEvent: - ''' + """ A synchronization event. Used as markers to separate events. Events may be separated in time or in space, such as with the multitouch protocol. - ''' + """ - __slots__ = 'event' + __slots__ = "event" def __init__(self, event): #: Reference to an :class:`InputEvent` instance. self.event = event def __str__(self): - msg = 'synchronization event at {:f}, {}' + msg = "synchronization event at {:f}, {}" return msg.format(self.event.timestamp(), SYN[self.event.code]) def __repr__(s): - return '{}({!r})'.format(s.__class__.__name__, s.event) + return "{}({!r})".format(s.__class__.__name__, s.event) #: A mapping of event types to :class:`InputEvent` sub-classes. Used @@ -191,5 +187,4 @@ def __repr__(s): } -__all__ = ('InputEvent', 'KeyEvent', 'RelEvent', 'SynEvent', - 'AbsEvent', 'event_factory') +__all__ = ("InputEvent", "KeyEvent", "RelEvent", "SynEvent", "AbsEvent", "event_factory") diff --git a/evdev/evtest.py b/evdev/evtest.py index da6c683..063ca83 100644 --- a/evdev/evtest.py +++ b/evdev/evtest.py @@ -1,6 +1,4 @@ -# encoding: utf-8 - -''' +""" Usage: evtest [options] [, ...] Input device enumerator and event monitor. @@ -16,8 +14,7 @@ Examples: evtest /dev/input/event0 /dev/input/event1 -''' - +""" from __future__ import print_function @@ -38,9 +35,9 @@ def parseopt(): parser = optparse.OptionParser(add_help_option=False) - parser.add_option('-h', '--help', action='store_true') - parser.add_option('-g', '--grab', action='store_true') - parser.add_option('-c', '--capabilities', action='store_true') + parser.add_option("-h", "--help", action="store_true") + parser.add_option("-g", "--grab", action="store_true") + parser.add_option("-c", "--capabilities", action="store_true") return parser.parse_args() @@ -69,7 +66,7 @@ def main(): toggle_tty_echo(sys.stdin, enable=False) atexit.register(toggle_tty_echo, sys.stdin, enable=True) - print('Listening for events (press ctrl-c to exit) ...') + print("Listening for events (press ctrl-c to exit) ...") fd_to_device = {dev.fd: dev for dev in devices} while True: r, w, e = select.select(fd_to_device, [], []) @@ -79,31 +76,31 @@ def main(): print_event(event) -def select_devices(device_dir='/dev/input'): - ''' +def select_devices(device_dir="/dev/input"): + """ Select one or more devices from a list of accessible input devices. - ''' + """ def devicenum(device_path): - digits = re.findall(r'\d+$', device_path) + digits = re.findall(r"\d+$", device_path) return [int(i) for i in digits] devices = sorted(list_devices(device_dir), key=devicenum) devices = [InputDevice(path) for path in devices] if not devices: - msg = 'error: no input devices found (do you have rw permission on %s/*?)' + msg = "error: no input devices found (do you have rw permission on %s/*?)" print(msg % device_dir, file=sys.stderr) sys.exit(1) - dev_format = '{0:<3} {1.path:<20} {1.name:<35} {1.phys:<35} {1.uniq:<4}' + dev_format = "{0:<3} {1.path:<20} {1.name:<35} {1.phys:<35} {1.uniq:<4}" dev_lines = [dev_format.format(num, dev) for num, dev in enumerate(devices)] - print('ID {:<20} {:<35} {:<35} {}'.format('Device', 'Name', 'Phys', 'Uniq')) - print('-' * len(max(dev_lines, key=len))) - print('\n'.join(dev_lines)) + print("ID {:<20} {:<35} {:<35} {}".format("Device", "Name", "Phys", "Uniq")) + print("-" * len(max(dev_lines, key=len))) + print("\n".join(dev_lines)) print() - choices = input('Select devices [0-%s]: ' % (len(dev_lines) - 1)) + choices = input("Select devices [0-%s]: " % (len(dev_lines) - 1)) try: choices = choices.split() @@ -112,7 +109,7 @@ def devicenum(device_path): choices = None if not choices: - msg = 'error: invalid input - please enter one or more numbers separated by spaces' + msg = "error: invalid input - please enter one or more numbers separated by spaces" print(msg, file=sys.stderr) sys.exit(1) @@ -123,52 +120,52 @@ def print_capabilities(device): capabilities = device.capabilities(verbose=True) input_props = device.input_props(verbose=True) - print('Device name: {.name}'.format(device)) - print('Device info: {.info}'.format(device)) - print('Repeat settings: {}\n'.format(device.repeat)) + print("Device name: {.name}".format(device)) + print("Device info: {.info}".format(device)) + print("Repeat settings: {}\n".format(device.repeat)) - if ('EV_LED', ecodes.EV_LED) in capabilities: - leds = ','.join(i[0] for i in device.leds(True)) - print('Active LEDs: %s' % leds) + if ("EV_LED", ecodes.EV_LED) in capabilities: + leds = ",".join(i[0] for i in device.leds(True)) + print("Active LEDs: %s" % leds) - active_keys = ','.join(k[0] for k in device.active_keys(True)) - print('Active keys: %s\n' % active_keys) + active_keys = ",".join(k[0] for k in device.active_keys(True)) + print("Active keys: %s\n" % active_keys) if input_props: - print('Input properties:') + print("Input properties:") for type, code in input_props: - print(' %s %s' % (type, code)) + print(" %s %s" % (type, code)) print() - print('Device capabilities:') + print("Device capabilities:") for type, codes in capabilities.items(): - print(' Type {} {}:'.format(*type)) + print(" Type {} {}:".format(*type)) for code in codes: # code <- ('BTN_RIGHT', 273) or (['BTN_LEFT', 'BTN_MOUSE'], 272) if isinstance(code[1], AbsInfo): - print(' Code {:<4} {}:'.format(*code[0])) - print(' {}'.format(code[1])) + print(" Code {:<4} {}:".format(*code[0])) + print(" {}".format(code[1])) else: # Multiple names may resolve to one value. - s = ', '.join(code[0]) if isinstance(code[0], list) else code[0] - print(' Code {:<4} {}'.format(s, code[1])) - print('') + s = ", ".join(code[0]) if isinstance(code[0], list) else code[0] + print(" Code {:<4} {}".format(s, code[1])) + print("") def print_event(e): if e.type == ecodes.EV_SYN: if e.code == ecodes.SYN_MT_REPORT: - msg = 'time {:<16} +++++++++ {} ++++++++' + msg = "time {:<16} +++++++++ {} ++++++++" else: - msg = 'time {:<16} --------- {} --------' + msg = "time {:<16} --------- {} --------" print(msg.format(e.timestamp(), ecodes.SYN[e.code])) else: if e.type in ecodes.bytype: codename = ecodes.bytype[e.type][e.code] else: - codename = '?' + codename = "?" - evfmt = 'time {:<16} type {} ({}), code {:<4} ({}), value {}' + evfmt = "time {:<16} type {} ({}), code {:<4} ({}), value {}" print(evfmt.format(e.timestamp(), e.type, ecodes.EV[e.type], e.code, codename, e.value)) @@ -181,7 +178,7 @@ def toggle_tty_echo(fh, enable=True): termios.tcsetattr(fh.fileno(), termios.TCSANOW, flags) -if __name__ == '__main__': +if __name__ == "__main__": try: ret = main() except (KeyboardInterrupt, EOFError): diff --git a/evdev/ff.py b/evdev/ff.py index 0008906..edb5ff2 100644 --- a/evdev/ff.py +++ b/evdev/ff.py @@ -1,43 +1,42 @@ -# encoding: utf-8 - import ctypes from evdev import ecodes -_u8 = ctypes.c_uint8 +_u8 = ctypes.c_uint8 _u16 = ctypes.c_uint16 _u32 = ctypes.c_uint32 _s16 = ctypes.c_int16 _s32 = ctypes.c_int32 + class Replay(ctypes.Structure): - ''' + """ Defines scheduling of the force-feedback effect @length: duration of the effect @delay: delay before effect should start playing - ''' + """ _fields_ = [ - ('length', _u16), - ('delay', _u16), + ("length", _u16), + ("delay", _u16), ] class Trigger(ctypes.Structure): - ''' + """ Defines what triggers the force-feedback effect @button: number of the button triggering the effect @interval: controls how soon the effect can be re-triggered - ''' + """ _fields_ = [ - ('button', _u16), - ('interval', _u16), + ("button", _u16), + ("interval", _u16), ] class Envelope(ctypes.Structure): - ''' + """ Generic force-feedback effect envelope @attack_length: duration of the attack (ms) @attack_level: level at the beginning of the attack @@ -48,46 +47,46 @@ class Envelope(ctypes.Structure): envelope force-feedback core will convert to positive/negative value based on polarity of the default level of the effect. Valid range for the attack and fade levels is 0x0000 - 0x7fff - ''' + """ _fields_ = [ - ('attack_length', _u16), - ('attack_level', _u16), - ('fade_length', _u16), - ('fade_level', _u16), + ("attack_length", _u16), + ("attack_level", _u16), + ("fade_length", _u16), + ("fade_level", _u16), ] class Constant(ctypes.Structure): - ''' + """ Defines parameters of a constant force-feedback effect @level: strength of the effect; may be negative @envelope: envelope data - ''' + """ _fields_ = [ - ('level', _s16), - ('ff_envelope', Envelope), + ("level", _s16), + ("ff_envelope", Envelope), ] class Ramp(ctypes.Structure): - ''' + """ Defines parameters of a ramp force-feedback effect @start_level: beginning strength of the effect; may be negative @end_level: final strength of the effect; may be negative @envelope: envelope data - ''' + """ _fields_ = [ - ('start_level', _s16), - ('end_level', _s16), - ('ff_envelope', Envelope), + ("start_level", _s16), + ("end_level", _s16), + ("ff_envelope", Envelope), ] class Condition(ctypes.Structure): - ''' + """ Defines a spring or friction force-feedback effect @right_saturation: maximum level when joystick moved all way to the right @left_saturation: same for the left side @@ -95,20 +94,20 @@ class Condition(ctypes.Structure): @left_coeff: same for the left side @deadband: size of the dead zone, where no force is produced @center: position of the dead zone - ''' + """ _fields_ = [ - ('right_saturation', _u16), - ('left_saturation', _u16), - ('right_coeff', _s16), - ('left_coeff', _s16), - ('deadband', _u16), - ('center', _s16), + ("right_saturation", _u16), + ("left_saturation", _u16), + ("right_coeff", _s16), + ("left_coeff", _s16), + ("deadband", _u16), + ("center", _s16), ] class Periodic(ctypes.Structure): - ''' + """ Defines parameters of a periodic force-feedback effect @waveform: kind of the effect (wave) @period: period of the wave (ms) @@ -118,71 +117,74 @@ class Periodic(ctypes.Structure): @envelope: envelope data @custom_len: number of samples (FF_CUSTOM only) @custom_data: buffer of samples (FF_CUSTOM only) - ''' + """ _fields_ = [ - ('waveform', _u16), - ('period', _u16), - ('magnitude', _s16), - ('offset', _s16), - ('phase', _u16), - ('envelope', Envelope), - ('custom_len', _u32), - ('custom_data', ctypes.POINTER(_s16)), + ("waveform", _u16), + ("period", _u16), + ("magnitude", _s16), + ("offset", _s16), + ("phase", _u16), + ("envelope", Envelope), + ("custom_len", _u32), + ("custom_data", ctypes.POINTER(_s16)), ] class Rumble(ctypes.Structure): - ''' + """ Defines parameters of a periodic force-feedback effect @strong_magnitude: magnitude of the heavy motor @weak_magnitude: magnitude of the light one Some rumble pads have two motors of different weight. Strong_magnitude represents the magnitude of the vibration generated by the heavy one. - ''' + """ _fields_ = [ - ('strong_magnitude', _u16), - ('weak_magnitude', _u16), + ("strong_magnitude", _u16), + ("weak_magnitude", _u16), ] class EffectType(ctypes.Union): _fields_ = [ - ('ff_constant_effect', Constant), - ('ff_ramp_effect', Ramp), - ('ff_periodic_effect', Periodic), - ('ff_condition_effect', Condition * 2), # one for each axis - ('ff_rumble_effect', Rumble), + ("ff_constant_effect", Constant), + ("ff_ramp_effect", Ramp), + ("ff_periodic_effect", Periodic), + ("ff_condition_effect", Condition * 2), # one for each axis + ("ff_rumble_effect", Rumble), ] class Effect(ctypes.Structure): _fields_ = [ - ('type', _u16), - ('id', _s16), - ('direction', _u16), - ('ff_trigger', Trigger), - ('ff_replay', Replay), - ('u', EffectType) + ("type", _u16), + ("id", _s16), + ("direction", _u16), + ("ff_trigger", Trigger), + ("ff_replay", Replay), + ("u", EffectType), ] + class UInputUpload(ctypes.Structure): _fields_ = [ - ('request_id', _u32), - ('retval', _s32), - ('effect', Effect), - ('old', Effect), + ("request_id", _u32), + ("retval", _s32), + ("effect", Effect), + ("old", Effect), ] + class UInputErase(ctypes.Structure): _fields_ = [ - ('request_id', _u32), - ('retval', _s32), - ('effect_id', _u32), + ("request_id", _u32), + ("retval", _s32), + ("effect_id", _u32), ] + # ff_types = { # ecodes.FF_CONSTANT, # ecodes.FF_PERIODIC, diff --git a/evdev/genecodes.py b/evdev/genecodes.py index 5b70154..f27104e 100644 --- a/evdev/genecodes.py +++ b/evdev/genecodes.py @@ -1,35 +1,34 @@ -# -*- coding: utf-8; -*- - -''' +""" Generate a Python extension module with the constants defined in linux/input.h. -''' +""" from __future__ import print_function import os, sys, re -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # The default header file locations to try. headers = [ - '/usr/include/linux/input.h', - '/usr/include/linux/input-event-codes.h', - '/usr/include/linux/uinput.h', + "/usr/include/linux/input.h", + "/usr/include/linux/input-event-codes.h", + "/usr/include/linux/uinput.h", ] if sys.argv[1:]: headers = sys.argv[1:] -#----------------------------------------------------------------------------- -macro_regex = r'#define +((?:KEY|ABS|REL|SW|MSC|LED|BTN|REP|SND|ID|EV|BUS|SYN|FF|UI_FF|INPUT_PROP)_\w+)' +# ----------------------------------------------------------------------------- +macro_regex = r"#define +((?:KEY|ABS|REL|SW|MSC|LED|BTN|REP|SND|ID|EV|BUS|SYN|FF|UI_FF|INPUT_PROP)_\w+)" macro_regex = re.compile(macro_regex) -uname = list(os.uname()); del uname[1] -uname = ' '.join(uname) +uname = list(os.uname()) +del uname[1] +uname = " ".join(uname) -#----------------------------------------------------------------------------- -template = r''' +# ----------------------------------------------------------------------------- +template = r""" #include #ifdef __FreeBSD__ #include @@ -70,13 +69,15 @@ return m; } -''' +""" + def parse_header(header): for line in open(header): macro = macro_regex.search(line) if macro: - yield ' PyModule_AddIntMacro(m, %s);' % macro.group(1) + yield " PyModule_AddIntMacro(m, %s);" % macro.group(1) + all_macros = [] for header in headers: @@ -87,7 +88,7 @@ def parse_header(header): all_macros += parse_header(header) if not all_macros: - print('no input macros found in: %s' % ' '.join(headers), file=sys.stderr) + print("no input macros found in: %s" % " ".join(headers), file=sys.stderr) sys.exit(1) diff --git a/evdev/uinput.py b/evdev/uinput.py index 2c1424b..c09b4f1 100644 --- a/evdev/uinput.py +++ b/evdev/uinput.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - import os import platform import re @@ -19,25 +17,31 @@ from evdev.eventio import EventIO - class UInputError(Exception): pass class UInput(EventIO): - ''' + """ A userland input device and that can inject input events into the linux input subsystem. - ''' + """ __slots__ = ( - 'name', 'vendor', 'product', 'version', 'bustype', - 'events', 'devnode', 'fd', 'device', + "name", + "vendor", + "product", + "version", + "bustype", + "events", + "devnode", + "fd", + "device", ) @classmethod def from_device(cls, *devices, filtered_types=(ecodes.EV_SYN, ecodes.EV_FF), **kwargs): - ''' + """ Create an UInput device with the capabilities of one or more input devices. @@ -51,7 +55,7 @@ def from_device(cls, *devices, filtered_types=(ecodes.EV_SYN, ecodes.EV_FF), **k **kwargs Keyword arguments to UInput constructor (i.e. name, vendor etc.). - ''' + """ device_instances = [] for dev in devices: @@ -61,8 +65,8 @@ def from_device(cls, *devices, filtered_types=(ecodes.EV_SYN, ecodes.EV_FF), **k all_capabilities = defaultdict(set) - if 'max_effects' not in kwargs: - kwargs['max_effects'] = min([dev.ff_effects_count for dev in device_instances]) + if "max_effects" not in kwargs: + kwargs["max_effects"] = min([dev.ff_effects_count for dev in device_instances]) # Merge the capabilities of all devices into one dictionary. for dev in device_instances: @@ -75,13 +79,20 @@ def from_device(cls, *devices, filtered_types=(ecodes.EV_SYN, ecodes.EV_FF), **k return cls(events=all_capabilities, **kwargs) - def __init__(self, - events=None, - name='py-evdev-uinput', - vendor=0x1, product=0x1, version=0x1, bustype=0x3, - devnode='/dev/uinput', phys='py-evdev-uinput', input_props=None, - max_effects=ecodes.FF_MAX_EFFECTS): - ''' + def __init__( + self, + events=None, + name="py-evdev-uinput", + vendor=0x1, + product=0x1, + version=0x1, + bustype=0x3, + devnode="/dev/uinput", + phys="py-evdev-uinput", + input_props=None, + max_effects=ecodes.FF_MAX_EFFECTS, + ): + """ Arguments --------- events : dict @@ -117,15 +128,15 @@ def __init__(self, ---- If you do not specify any events, the uinput device will be able to inject only ``KEY_*`` and ``BTN_*`` event codes. - ''' + """ - self.name = name #: Uinput device name. - self.vendor = vendor #: Device vendor identifier. - self.product = product #: Device product identifier. - self.version = version #: Device version identifier. - self.bustype = bustype #: Device bustype - e.g. ``BUS_USB``. - self.phys = phys #: Uinput device physical path. - self.devnode = devnode #: Uinput device node - e.g. ``/dev/uinput/``. + self.name = name #: Uinput device name. + self.vendor = vendor #: Device vendor identifier. + self.product = product #: Device product identifier. + self.version = version #: Device version identifier. + self.bustype = bustype #: Device bustype - e.g. ``BUS_USB``. + self.phys = phys #: Uinput device physical path. + self.devnode = devnode #: Uinput device node - e.g. ``/dev/uinput/``. if not events: events = {ecodes.EV_KEY: ecodes.keys.keys()} @@ -164,7 +175,7 @@ def __init__(self, self.device = self._find_device(self.fd) def _prepare_events(self, events): - '''Prepare events for passing to _uinput.enable and _uinput.setup''' + """Prepare events for passing to _uinput.enable and _uinput.setup""" absinfo, prepared_events = [], [] for etype, codes in events.items(): for code in codes: @@ -185,23 +196,21 @@ def __enter__(self): return self def __exit__(self, type, value, tb): - if hasattr(self, 'fd'): + if hasattr(self, "fd"): self.close() def __repr__(self): # TODO: - v = (repr(getattr(self, i)) for i in - ('name', 'bustype', 'vendor', 'product', 'version', 'phys')) - return '{}({})'.format(self.__class__.__name__, ', '.join(v)) + v = (repr(getattr(self, i)) for i in ("name", "bustype", "vendor", "product", "version", "phys")) + return "{}({})".format(self.__class__.__name__, ", ".join(v)) def __str__(self): - msg = ('name "{}", bus "{}", vendor "{:04x}", product "{:04x}", version "{:04x}", phys "{}"\n' - 'event types: {}') + msg = 'name "{}", bus "{}", vendor "{:04x}", product "{:04x}", version "{:04x}", phys "{}"\n' "event types: {}" evtypes = [i[0] for i in self.capabilities(True).keys()] - msg = msg.format(self.name, ecodes.BUS[self.bustype], - self.vendor, self.product, - self.version, self.phys, ' '.join(evtypes)) + msg = msg.format( + self.name, ecodes.BUS[self.bustype], self.vendor, self.product, self.version, self.phys, " ".join(evtypes) + ) return msg @@ -216,18 +225,18 @@ def close(self): self.fd = -1 def syn(self): - ''' + """ Inject a ``SYN_REPORT`` event into the input subsystem. Events queued by :func:`write()` will be fired. If possible, events will be merged into an 'atomic' event. - ''' + """ _uinput.write(self.fd, ecodes.EV_SYN, ecodes.SYN_REPORT, 0) def capabilities(self, verbose=False, absinfo=True): - '''See :func:`capabilities `.''' + """See :func:`capabilities `.""" if self.device is None: - raise UInputError('input device not opened - cannot read capabilities') + raise UInputError("input device not opened - cannot read capabilities") return self.device.capabilities(verbose, absinfo) @@ -237,14 +246,14 @@ def begin_upload(self, effect_id): ret = self.dll._uinput_begin_upload(self.fd, ctypes.byref(upload)) if ret: - raise UInputError('Failed to begin uinput upload: ' + os.strerror(ret)) + raise UInputError("Failed to begin uinput upload: " + os.strerror(ret)) return upload def end_upload(self, upload): ret = self.dll._uinput_end_upload(self.fd, ctypes.byref(upload)) if ret: - raise UInputError('Failed to end uinput upload: ' + os.strerror(ret)) + raise UInputError("Failed to end uinput upload: " + os.strerror(ret)) def begin_erase(self, effect_id): erase = ff.UInputErase() @@ -252,27 +261,26 @@ def begin_erase(self, effect_id): ret = self.dll._uinput_begin_erase(self.fd, ctypes.byref(erase)) if ret: - raise UInputError('Failed to begin uinput erase: ' + os.strerror(ret)) + raise UInputError("Failed to begin uinput erase: " + os.strerror(ret)) return erase def end_erase(self, erase): ret = self.dll._uinput_end_erase(self.fd, ctypes.byref(erase)) if ret: - raise UInputError('Failed to end uinput erase: ' + os.strerror(ret)) + raise UInputError("Failed to end uinput erase: " + os.strerror(ret)) def _verify(self): - ''' + """ Verify that an uinput device exists and is readable and writable by the current process. - ''' + """ try: m = os.stat(self.devnode)[stat.ST_MODE] if not stat.S_ISCHR(m): raise except (IndexError, OSError): - msg = '"{}" does not exist or is not a character device file '\ - '- verify that the uinput module is loaded' + msg = '"{}" does not exist or is not a character device file ' "- verify that the uinput module is loaded" raise UInputError(msg.format(self.devnode)) if not os.access(self.devnode, os.W_OK): @@ -280,15 +288,15 @@ def _verify(self): raise UInputError(msg.format(self.devnode)) if len(self.name) > _uinput.maxnamelen: - msg = 'uinput device name must not be longer than {} characters' + msg = "uinput device name must not be longer than {} characters" raise UInputError(msg.format(_uinput.maxnamelen)) def _find_device(self, fd): - ''' + """ Tries to find the device node. Will delegate this task to one of several platform-specific functions. - ''' - if platform.system() == 'Linux': + """ + if platform.system() == "Linux": try: sysname = _uinput.get_sysname(fd) return self._find_device_linux(sysname) @@ -302,19 +310,19 @@ def _find_device(self, fd): return self._find_device_fallback() def _find_device_linux(self, sysname): - ''' + """ Tries to find the device node when running on Linux. - ''' + """ - syspath = f'/sys/devices/virtual/input/{sysname}' + syspath = f"/sys/devices/virtual/input/{sysname}" # The sysfs entry for event devices should contain exactly one folder # whose name matches the format "event[0-9]+". It is then assumed that # the device node in /dev/input uses the same name. - regex = re.compile('event[0-9]+') + regex = re.compile("event[0-9]+") for entry in os.listdir(syspath): if regex.fullmatch(entry): - device_path = f'/dev/input/{entry}' + device_path = f"/dev/input/{entry}" break else: # no break raise FileNotFoundError() @@ -329,11 +337,11 @@ def _find_device_linux(self, sysname): return device.InputDevice(device_path) def _find_device_fallback(self): - ''' + """ Tries to find the device node when UI_GET_SYSNAME is not available or we're running on a system sufficiently exotic that we do not know how to interpret its return value. - ''' + """ #:bug: the device node might not be immediately available time.sleep(0.1) @@ -343,8 +351,8 @@ def _find_device_fallback(self): # ends at event[0-9]+: it might return something like "/dev/input/events_all". Find # the devices that have the expected structure and extract their device number. path_number_pairs = [] - regex = re.compile('/dev/input/event([0-9]+)') - for path in util.list_devices('/dev/input/'): + regex = re.compile("/dev/input/event([0-9]+)") + for path in util.list_devices("/dev/input/"): regex_match = regex.fullmatch(path) if not regex_match: continue @@ -355,7 +363,7 @@ def _find_device_fallback(self): # are sorting by the number in the name path_number_pairs.sort(key=lambda pair: pair[1], reverse=True) - for (path, _) in path_number_pairs: + for path, _ in path_number_pairs: d = device.InputDevice(path) if d.name == self.name: return d diff --git a/evdev/util.py b/evdev/util.py index e8009f7..7209f4b 100644 --- a/evdev/util.py +++ b/evdev/util.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - import re import os import stat @@ -10,17 +8,17 @@ from evdev.events import event_factory -def list_devices(input_device_dir='/dev/input'): - '''List readable character devices in ``input_device_dir``.''' +def list_devices(input_device_dir="/dev/input"): + """List readable character devices in ``input_device_dir``.""" - fns = glob.glob('{}/event*'.format(input_device_dir)) + fns = glob.glob("{}/event*".format(input_device_dir)) fns = list(filter(is_device, fns)) return fns def is_device(fn): - '''Check if ``fn`` is a readable and writable character device.''' + """Check if ``fn`` is a readable and writable character device.""" if not os.path.exists(fn): return False @@ -36,13 +34,13 @@ def is_device(fn): def categorize(event): - ''' + """ Categorize an event according to its type. The :data:`event_factory ` dictionary maps event types to sub-classes of :class:`InputEvent `. If the event cannot be categorized, it - is returned unmodified.''' + is returned unmodified.""" if event.type in event_factory: return event_factory[event.type](event) @@ -50,8 +48,8 @@ def categorize(event): return event -def resolve_ecodes_dict(typecodemap, unknown='?'): - ''' +def resolve_ecodes_dict(typecodemap, unknown="?"): + """ Resolve event codes and types to their verbose names. :param typecodemap: mapping of event types to lists of event codes. @@ -70,7 +68,7 @@ def resolve_ecodes_dict(typecodemap, unknown='?'): >>> resolve_ecodes_dict({ 3: [(0, AbsInfo(...))] }) { ('EV_ABS', 3L): [(('ABS_X', 0L), AbsInfo(...))] } - ''' + """ for etype, codes in typecodemap.items(): type_name = ecodes.EV[etype] @@ -79,21 +77,21 @@ def resolve_ecodes_dict(typecodemap, unknown='?'): if etype == ecodes.EV_KEY: ecode_dict = ecodes.keys else: - ecode_dict = getattr(ecodes, type_name.split('_')[-1]) + ecode_dict = getattr(ecodes, type_name.split("_")[-1]) resolved = resolve_ecodes(ecode_dict, codes, unknown) yield (type_name, etype), resolved -def resolve_ecodes(ecode_dict, ecode_list, unknown='?'): - ''' +def resolve_ecodes(ecode_dict, ecode_list, unknown="?"): + """ Resolve event codes and types to their verbose names. Example ------- >>> resolve_ecodes(ecodes.BTN, [272, 273, 274]) [(['BTN_LEFT', 'BTN_MOUSE'], 272), ('BTN_RIGHT', 273), ('BTN_MIDDLE', 274)] - ''' + """ res = [] for ecode in ecode_list: # elements with AbsInfo(), eg { 3 : [(0, AbsInfo(...)), (1, AbsInfo(...))] } @@ -115,7 +113,7 @@ def resolve_ecodes(ecode_dict, ecode_list, unknown='?'): def find_ecodes_by_regex(regex): - ''' + """ Find ecodes matching a regex and return a mapping of event type to event codes. regex can be a pattern string or a compiled regular expression object. @@ -130,7 +128,7 @@ def find_ecodes_by_regex(regex): ('EV_KEY', 1): [('KEY_BREAK', 411)], ('EV_ABS', 3): [('ABS_BRAKE', 10)] } - ''' + """ regex = re.compile(regex) # re.compile is idempotent result = collections.defaultdict(list) @@ -146,4 +144,4 @@ def find_ecodes_by_regex(regex): return dict(result) -__all__ = ('list_devices', 'is_device', 'categorize', 'resolve_ecodes', 'resolve_ecodes_dict', 'find_ecodes_by_regex') +__all__ = ("list_devices", "is_device", "categorize", "resolve_ecodes", "resolve_ecodes_dict", "find_ecodes_by_regex") diff --git a/examples/udev-example.py b/examples/udev-example.py index 12a617c..8e827f6 100755 --- a/examples/udev-example.py +++ b/examples/udev-example.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 -''' +""" This is an example of using pyudev[1] alongside evdev. [1]: https://pyudev.readthedocs.org/ -''' +""" import functools import pyudev @@ -13,7 +13,7 @@ context = pyudev.Context() monitor = pyudev.Monitor.from_netlink(context) -monitor.filter_by(subsystem='input') +monitor.filter_by(subsystem="input") monitor.start() fds = {monitor.fileno(): monitor} @@ -32,16 +32,16 @@ break # find the device we're interested in and add it to fds - for name in (i['NAME'] for i in udev.ancestors if 'NAME' in i): + for name in (i["NAME"] for i in udev.ancestors if "NAME" in i): # I used a virtual input device for this test - you # should adapt this to your needs - if u'py-evdev-uinput' in name: - if udev.action == u'add': - print('Device added: %s' % udev) + if "py-evdev-uinput" in name: + if udev.action == "add": + print("Device added: %s" % udev) fds[dev.fd] = InputDevice(udev.device_node) break - if udev.action == u'remove': - print('Device removed: %s' % udev) + if udev.action == "remove": + print("Device removed: %s" % udev) def helper(): global fds diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..6dbd43f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,2 @@ +[tool.ruff] +line-length = 120 diff --git a/setup.py b/setup.py index a9f45ef..30130c6 100755 --- a/setup.py +++ b/setup.py @@ -6,62 +6,59 @@ from pathlib import Path -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- from setuptools import setup, Extension, Command from setuptools.command import build_ext as _build_ext -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- curdir = Path(__file__).resolve().parent -ecodes_path = curdir / 'evdev/ecodes.c' +ecodes_path = curdir / "evdev/ecodes.c" -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- classifiers = [ - 'Development Status :: 5 - Production/Stable', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Operating System :: POSIX :: Linux', - 'Intended Audience :: Developers', - 'Topic :: Software Development :: Libraries', - 'License :: OSI Approved :: BSD License', - 'Programming Language :: Python :: Implementation :: CPython', + "Development Status :: 5 - Production/Stable", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Operating System :: POSIX :: Linux", + "Intended Audience :: Developers", + "Topic :: Software Development :: Libraries", + "License :: OSI Approved :: BSD License", + "Programming Language :: Python :: Implementation :: CPython", ] -#----------------------------------------------------------------------------- -cflags = ['-std=c99', '-Wno-error=declaration-after-statement'] -input_c = Extension('evdev._input', sources=['evdev/input.c'], extra_compile_args=cflags) -uinput_c = Extension('evdev._uinput', sources=['evdev/uinput.c'], extra_compile_args=cflags) -ecodes_c = Extension('evdev._ecodes', sources=['evdev/ecodes.c'], extra_compile_args=cflags) +# ----------------------------------------------------------------------------- +cflags = ["-std=c99", "-Wno-error=declaration-after-statement"] +input_c = Extension("evdev._input", sources=["evdev/input.c"], extra_compile_args=cflags) +uinput_c = Extension("evdev._uinput", sources=["evdev/uinput.c"], extra_compile_args=cflags) +ecodes_c = Extension("evdev._ecodes", sources=["evdev/ecodes.c"], extra_compile_args=cflags) -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- kw = { - 'name': 'evdev', - 'version': '1.6.1', - - 'description': 'Bindings to the Linux input handling subsystem', - 'long_description': (curdir / 'README.rst').read_text(), - - 'author': 'Georgi Valkov', - 'author_email': 'georgi.t.valkov@gmail.com', - 'license': 'Revised BSD License', - 'keywords': 'evdev input uinput', - 'url': 'https://github.com/gvalkov/python-evdev', - 'classifiers': classifiers, - - 'packages': ['evdev'], - 'ext_modules': [input_c, uinput_c, ecodes_c], - 'include_package_data': False, - 'zip_safe': True, - 'cmdclass': {}, + "name": "evdev", + "version": "1.6.1", + "description": "Bindings to the Linux input handling subsystem", + "long_description": (curdir / "README.rst").read_text(), + "author": "Georgi Valkov", + "author_email": "georgi.t.valkov@gmail.com", + "license": "Revised BSD License", + "keywords": "evdev input uinput", + "url": "https://github.com/gvalkov/python-evdev", + "classifiers": classifiers, + "packages": ["evdev"], + "ext_modules": [input_c, uinput_c, ecodes_c], + "include_package_data": False, + "zip_safe": True, + "cmdclass": {}, } -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- def create_ecodes(headers=None): if not headers: include_paths = set() @@ -75,7 +72,7 @@ def create_ecodes(headers=None): headers = [header for header in headers if os.path.isfile(header)] if not headers: - msg = '''\ + msg = """\ The 'linux/input.h' and 'linux/input-event-codes.h' include files are missing. You will have to install the kernel header files in order to continue: @@ -94,25 +91,25 @@ def create_ecodes(headers=None): build_ecodes --evdev-headers path/input.h:path/input-event-codes.h \\ build_ext --include-dirs path/ \\ install - ''' + """ sys.stderr.write(textwrap.dedent(msg)) sys.exit(1) from subprocess import run - print('writing %s (using %s)' % (ecodes_path, ' '.join(headers))) - with ecodes_path.open('w') as fh: - cmd = [sys.executable, 'evdev/genecodes.py', *headers] + print("writing %s (using %s)" % (ecodes_path, " ".join(headers))) + with ecodes_path.open("w") as fh: + cmd = [sys.executable, "evdev/genecodes.py", *headers] run(cmd, check=True, stdout=fh) -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- class build_ecodes(Command): - description = 'generate ecodes.c' + description = "generate ecodes.c" user_options = [ - ('evdev-headers=', None, 'colon-separated paths to input subsystem headers'), + ("evdev-headers=", None, "colon-separated paths to input subsystem headers"), ] def initialize_options(self): @@ -120,7 +117,7 @@ def initialize_options(self): def finalize_options(self): if self.evdev_headers: - self.evdev_headers = self.evdev_headers.split(':') + self.evdev_headers = self.evdev_headers.split(":") def run(self): create_ecodes(self.evdev_headers) @@ -129,7 +126,7 @@ def run(self): class build_ext(_build_ext.build_ext): def has_ecodes(self): if ecodes_path.exists(): - print('ecodes.c already exists ... skipping build_ecodes') + print("ecodes.c already exists ... skipping build_ecodes") return not ecodes_path.exists() def run(self): @@ -137,14 +134,14 @@ def run(self): self.run_command(cmd_name) _build_ext.build_ext.run(self) - sub_commands = [('build_ecodes', has_ecodes)] + _build_ext.build_ext.sub_commands + sub_commands = [("build_ecodes", has_ecodes)] + _build_ext.build_ext.sub_commands -#----------------------------------------------------------------------------- -kw['cmdclass']['build_ext'] = build_ext -kw['cmdclass']['build_ecodes'] = build_ecodes +# ----------------------------------------------------------------------------- +kw["cmdclass"]["build_ext"] = build_ext +kw["cmdclass"]["build_ecodes"] = build_ecodes -#----------------------------------------------------------------------------- -if __name__ == '__main__': +# ----------------------------------------------------------------------------- +if __name__ == "__main__": setup(**kw) diff --git a/tests/test_ecodes.py b/tests/test_ecodes.py index b2f10c4..8f4cbbb 100644 --- a/tests/test_ecodes.py +++ b/tests/test_ecodes.py @@ -3,12 +3,14 @@ from evdev import ecodes -prefixes = 'KEY ABS REL SW MSC LED BTN REP SND ID EV BUS SYN FF_STATUS FF' +prefixes = "KEY ABS REL SW MSC LED BTN REP SND ID EV BUS SYN FF_STATUS FF" + def to_tuples(l): t = lambda x: tuple(x) if isinstance(x, list) else x return map(t, l) + def test_equality(): keys = [] for i in prefixes.split(): @@ -16,10 +18,12 @@ def test_equality(): assert set(keys) == set(ecodes.ecodes.values()) + def test_access(): - assert ecodes.KEY_A == ecodes.ecodes['KEY_A'] == ecodes.KEY_A - assert ecodes.KEY[ecodes.ecodes['KEY_A']] == 'KEY_A' - assert ecodes.REL[0] == 'REL_X' + assert ecodes.KEY_A == ecodes.ecodes["KEY_A"] == ecodes.KEY_A + assert ecodes.KEY[ecodes.ecodes["KEY_A"]] == "KEY_A" + assert ecodes.REL[0] == "REL_X" + def test_overlap(): vals_ff = set(to_tuples(ecodes.FF.values())) diff --git a/tests/test_events.py b/tests/test_events.py index d0717f2..f0f456c 100644 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -16,6 +16,7 @@ def test_categorize(): e = events.InputEvent(1036996631, 984417, ecodes.EV_MSC, 0, 0) assert e == util.categorize(e) + def test_keyevent(): e = events.InputEvent(1036996631, 984417, ecodes.EV_KEY, ecodes.KEY_A, 2) k = events.KeyEvent(e) @@ -23,5 +24,4 @@ def test_keyevent(): assert k.keystate == events.KeyEvent.key_hold assert k.event == e assert k.scancode == ecodes.KEY_A - assert k.keycode == 'KEY_A' # :todo: - + assert k.keycode == "KEY_A" # :todo: diff --git a/tests/test_uinput.py b/tests/test_uinput.py index 21d7b4e..2bf3dc1 100644 --- a/tests/test_uinput.py +++ b/tests/test_uinput.py @@ -6,63 +6,69 @@ from evdev import uinput, ecodes, events, device, util -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- uinput_options = { - 'name' : 'test-py-evdev-uinput', - 'bustype' : ecodes.BUS_USB, - 'vendor' : 0x1100, - 'product' : 0x2200, - 'version' : 0x3300, + "name": "test-py-evdev-uinput", + "bustype": ecodes.BUS_USB, + "vendor": 0x1100, + "product": 0x2200, + "version": 0x3300, } + @fixture def c(): return uinput_options.copy() + def device_exists(bustype, vendor, product, version): - match = 'I: Bus=%04hx Vendor=%04hx Product=%04hx Version=%04hx' + match = "I: Bus=%04hx Vendor=%04hx Product=%04hx Version=%04hx" match = match % (bustype, vendor, product, version) - for line in open('/proc/bus/input/devices'): + for line in open("/proc/bus/input/devices"): if line.strip() == match: return True return False -#----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- def test_open(c): ui = uinput.UInput(**c) - args = (c['bustype'], c['vendor'], c['product'], c['version']) + args = (c["bustype"], c["vendor"], c["product"], c["version"]) assert device_exists(*args) ui.close() assert not device_exists(*args) + def test_open_context(c): - args = (c['bustype'], c['vendor'], c['product'], c['version']) + args = (c["bustype"], c["vendor"], c["product"], c["version"]) with uinput.UInput(**c): assert device_exists(*args) assert not device_exists(*args) + def test_maxnamelen(c): with raises(uinput.UInputError): - c['name'] = 'a' * 150 + c["name"] = "a" * 150 uinput.UInput(**c) + def test_enable_events(c): e = ecodes - c['events'] = {e.EV_KEY : [e.KEY_A, e.KEY_B, e.KEY_C]} + c["events"] = {e.EV_KEY: [e.KEY_A, e.KEY_B, e.KEY_C]} with uinput.UInput(**c) as ui: cap = ui.capabilities() assert e.EV_KEY in cap - assert sorted(cap[e.EV_KEY]) == sorted(c['events'][e.EV_KEY]) + assert sorted(cap[e.EV_KEY]) == sorted(c["events"][e.EV_KEY]) + def test_abs_values(c): e = ecodes - c['events'] = { + c["events"] = { e.EV_KEY: [e.KEY_A, e.KEY_B], - e.EV_ABS: [(e.ABS_X, (0, 255, 0, 0)), - (e.ABS_Y, device.AbsInfo(0, 255, 5, 10, 0, 0))], + e.EV_ABS: [(e.ABS_X, (0, 255, 0, 0)), (e.ABS_Y, device.AbsInfo(0, 255, 5, 10, 0, 0))], } with uinput.UInput(**c) as ui: @@ -75,11 +81,12 @@ def test_abs_values(c): c = ui.capabilities(verbose=True) abs = device.AbsInfo(value=0, min=0, max=255, fuzz=0, flat=0, resolution=0) - assert c[('EV_ABS', 3)][0] == (('ABS_X', 0), abs) + assert c[("EV_ABS", 3)][0] == (("ABS_X", 0), abs) c = ui.capabilities(verbose=False, absinfo=False) assert c[e.EV_ABS] == list((0, 1)) + def test_write(c): with uinput.UInput(**c) as ui: d = ui.device @@ -89,12 +96,12 @@ def test_write(c): r, w, x = select([d], [d], []) if w and not wrote: - ui.write(ecodes.EV_KEY, ecodes.KEY_P, 1) # KEY_P down - ui.write(ecodes.EV_KEY, ecodes.KEY_P, 1) # KEY_P down - ui.write(ecodes.EV_KEY, ecodes.KEY_P, 0) # KEY_P up - ui.write(ecodes.EV_KEY, ecodes.KEY_A, 1) # KEY_A down - ui.write(ecodes.EV_KEY, ecodes.KEY_A, 2) # KEY_A hold - ui.write(ecodes.EV_KEY, ecodes.KEY_A, 0) # KEY_P up + ui.write(ecodes.EV_KEY, ecodes.KEY_P, 1) # KEY_P down + ui.write(ecodes.EV_KEY, ecodes.KEY_P, 1) # KEY_P down + ui.write(ecodes.EV_KEY, ecodes.KEY_P, 0) # KEY_P up + ui.write(ecodes.EV_KEY, ecodes.KEY_A, 1) # KEY_A down + ui.write(ecodes.EV_KEY, ecodes.KEY_A, 2) # KEY_A hold + ui.write(ecodes.EV_KEY, ecodes.KEY_A, 0) # KEY_P up ui.syn() wrote = True diff --git a/tests/test_util.py b/tests/test_util.py index 11e338b..5a979df 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -2,20 +2,20 @@ def test_match_ecodes_a(): - res = util.find_ecodes_by_regex('KEY_ZOOM.*') + res = util.find_ecodes_by_regex("KEY_ZOOM.*") assert res == {1: [372, 418, 419, 420]} assert dict(util.resolve_ecodes_dict(res)) == { - ('EV_KEY', 1): [ - (['KEY_FULL_SCREEN', 'KEY_ZOOM'], 372), - ('KEY_ZOOMIN', 418), - ('KEY_ZOOMOUT', 419), - ('KEY_ZOOMRESET', 420) + ("EV_KEY", 1): [ + (["KEY_FULL_SCREEN", "KEY_ZOOM"], 372), + ("KEY_ZOOMIN", 418), + ("KEY_ZOOMOUT", 419), + ("KEY_ZOOMRESET", 420), ] } - res = util.find_ecodes_by_regex(r'(ABS|KEY)_BR(AKE|EAK)') + res = util.find_ecodes_by_regex(r"(ABS|KEY)_BR(AKE|EAK)") assert res == {1: [411], 3: [10]} assert dict(util.resolve_ecodes_dict(res)) == { - ('EV_KEY', 1): [('KEY_BREAK', 411)], - ('EV_ABS', 3): [('ABS_BRAKE', 10)] + ("EV_KEY", 1): [("KEY_BREAK", 411)], + ("EV_ABS", 3): [("ABS_BRAKE", 10)], } From 8bcb935cdc9a191fa89a0d5136e14acd47b8feed Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Mon, 29 Jan 2024 23:01:43 +0100 Subject: [PATCH 208/270] Remove packaging/ --- packaging/python-evdev.spec | 78 ------------------------------------- 1 file changed, 78 deletions(-) delete mode 100644 packaging/python-evdev.spec diff --git a/packaging/python-evdev.spec b/packaging/python-evdev.spec deleted file mode 100644 index a3cfb14..0000000 --- a/packaging/python-evdev.spec +++ /dev/null @@ -1,78 +0,0 @@ -Name: python-evdev -Version: 0.6.1 -Release: 1%{?dist} -Summary: Python bindings for the Linux input handling subsystem - -License: BSD -URL: https://python-evdev.readthedocs.io -Source0: https://github.com/gvalkov/%{name}/archive/v%{version}.tar.gz#/%{name}-%{version}.tar.gz - -BuildRequires: kernel-headers -BuildRequires: python2-devel -BuildRequires: python3-devel -BuildRequires: python2-setuptools -BuildRequires: python3-setuptools - - -%global _description \ -This package provides python bindings to the generic input event interface in \ -Linux. The evdev interface serves the purpose of passing events generated in \ -the kernel directly to userspace through character devices that are typically \ -located in /dev/input/. \ - \ -This package also comes with bindings to uinput, the userspace input subsystem. \ -Uinput allows userspace programs to create and handle input devices that can \ -inject events directly into the input subsystem. \ - \ -In other words, python-evdev allows you to read and write input events on Linux. \ -An event can be a key or button press, a mouse movement or a tap on a \ -touchscreen. - - -%description %{_description} - - -%package -n python2-evdev -Summary: %{summary} -%{?python_provide:%python_provide python2-evdev} -%description -n python2-evdev %{_description} - - -%package -n python3-evdev -Summary: %{summary} -%{?python_provide:%python_provide python3-evdev} -%description -n python3-evdev %{_description} - - -#------------------------------------------------------------------------------ -%prep -%autosetup - -#------------------------------------------------------------------------------ -%build -%py2_build -%py3_build - -#------------------------------------------------------------------------------ -%install -%py2_install -%py3_install - -#------------------------------------------------------------------------------ -%files -n python2-evdev -%license LICENSE -%doc README.rst -%{python2_sitearch}/evdev/ -%{python2_sitearch}/evdev-%{version}-py%{python2_version}.egg-info/ - -%files -n python3-evdev -%license LICENSE -%doc README.rst -%{python3_sitearch}/evdev/ -%{python3_sitearch}/evdev-%{version}-py%{python3_version}.egg-info/ - - -#------------------------------------------------------------------------------ -%changelog -* Sun Jun 05 2016 Georgi Valkov - 0.6.1-1 -- Initial RPM Release From c1b8cac930e33517eba286dc33b27d7cf9a3ad1a Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Mon, 29 Jan 2024 23:49:33 +0100 Subject: [PATCH 209/270] Move project metatada to pyproject.toml and simplify setup.py --- pyproject.toml | 34 +++++++++++++++++++++++ setup.py | 75 ++++++++++---------------------------------------- 2 files changed, 49 insertions(+), 60 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6dbd43f..911b8e8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,2 +1,36 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "evdev" +version = "1.6.1" +description = "Bindings to the Linux input handling subsystem" +keywords = ["evdev", "input", "uinput"] +readme = "README.rst" +license = {file = "LICENSE"} +requires-python = ">=3.6" +authors = [ + { name="Georgi Valkov", email="georgi.t.valkov@gmail.com" }, +] +maintainers = [ + { name="Tobi", email="proxima@sezanzeb.de" }, +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Programming Language :: Python :: 3", + "Operating System :: POSIX :: Linux", + "Intended Audience :: Developers", + "Topic :: Software Development :: Libraries", + "License :: OSI Approved :: BSD License", + "Programming Language :: Python :: Implementation :: CPython", +] + +[project.urls] +"Homepage" = "https://github.com/gvalkov/python-evdev" + +[tool.setuptools] +packages = ["evdev"] + [tool.ruff] line-length = 120 diff --git a/setup.py b/setup.py index 30130c6..913cc6f 100755 --- a/setup.py +++ b/setup.py @@ -1,64 +1,16 @@ -#!/usr/bin/env python - import os import sys import textwrap from pathlib import Path - -# ----------------------------------------------------------------------------- from setuptools import setup, Extension, Command from setuptools.command import build_ext as _build_ext -# ----------------------------------------------------------------------------- curdir = Path(__file__).resolve().parent ecodes_path = curdir / "evdev/ecodes.c" -# ----------------------------------------------------------------------------- -classifiers = [ - "Development Status :: 5 - Production/Stable", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Operating System :: POSIX :: Linux", - "Intended Audience :: Developers", - "Topic :: Software Development :: Libraries", - "License :: OSI Approved :: BSD License", - "Programming Language :: Python :: Implementation :: CPython", -] - -# ----------------------------------------------------------------------------- -cflags = ["-std=c99", "-Wno-error=declaration-after-statement"] -input_c = Extension("evdev._input", sources=["evdev/input.c"], extra_compile_args=cflags) -uinput_c = Extension("evdev._uinput", sources=["evdev/uinput.c"], extra_compile_args=cflags) -ecodes_c = Extension("evdev._ecodes", sources=["evdev/ecodes.c"], extra_compile_args=cflags) - -# ----------------------------------------------------------------------------- -kw = { - "name": "evdev", - "version": "1.6.1", - "description": "Bindings to the Linux input handling subsystem", - "long_description": (curdir / "README.rst").read_text(), - "author": "Georgi Valkov", - "author_email": "georgi.t.valkov@gmail.com", - "license": "Revised BSD License", - "keywords": "evdev input uinput", - "url": "https://github.com/gvalkov/python-evdev", - "classifiers": classifiers, - "packages": ["evdev"], - "ext_modules": [input_c, uinput_c, ecodes_c], - "include_package_data": False, - "zip_safe": True, - "cmdclass": {}, -} - - -# ----------------------------------------------------------------------------- + def create_ecodes(headers=None): if not headers: include_paths = set() @@ -77,7 +29,7 @@ def create_ecodes(headers=None): are missing. You will have to install the kernel header files in order to continue: - yum install kernel-headers-$(uname -r) + dnf install kernel-headers-$(uname -r) apt-get install linux-headers-$(uname -r) emerge sys-kernel/linux-headers pacman -S kernel-headers @@ -89,7 +41,7 @@ def create_ecodes(headers=None): python setup.py \\ build \\ build_ecodes --evdev-headers path/input.h:path/input-event-codes.h \\ - build_ext --include-dirs path/ \\ + build_ext --include-dirs path/ \\ install """ @@ -104,7 +56,6 @@ def create_ecodes(headers=None): run(cmd, check=True, stdout=fh) -# ----------------------------------------------------------------------------- class build_ecodes(Command): description = "generate ecodes.c" @@ -137,11 +88,15 @@ def run(self): sub_commands = [("build_ecodes", has_ecodes)] + _build_ext.build_ext.sub_commands -# ----------------------------------------------------------------------------- -kw["cmdclass"]["build_ext"] = build_ext -kw["cmdclass"]["build_ecodes"] = build_ecodes - - -# ----------------------------------------------------------------------------- -if __name__ == "__main__": - setup(**kw) +cflags = ["-std=c99", "-Wno-error=declaration-after-statement"] +setup( + ext_modules=[ + Extension("evdev._input", sources=["evdev/input.c"], extra_compile_args=cflags), + Extension("evdev._uinput", sources=["evdev/uinput.c"], extra_compile_args=cflags), + Extension("evdev._ecodes", sources=["evdev/ecodes.c"], extra_compile_args=cflags), + ], + cmdclass={ + "build_ext": build_ext, + "build_ecodes": build_ecodes, + }, +) From 8c8014f78ceea2585a9092aedea5c4f528ec7ee8 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Mon, 29 Jan 2024 23:54:37 +0100 Subject: [PATCH 210/270] Cleaner CPATH and C_INC_PATH handling --- setup.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 913cc6f..a776130 100755 --- a/setup.py +++ b/setup.py @@ -14,10 +14,14 @@ def create_ecodes(headers=None): if not headers: include_paths = set() - if os.environ.get("CPATH", "").strip() != "": - include_paths.update(os.environ["CPATH"].split(":")) - if os.environ.get("C_INCLUDE_PATH", "").strip() != "": - include_paths.update(os.environ["C_INCLUDE_PATH"].split(":")) + cpath = os.environ.get("CPATH", "").strip() + c_inc_path = os.environ.get("C_INCLUDE_PATH", "").strip() + + if cpath: + include_paths.update(cpath.split(":")) + if c_inc_path: + include_paths.update(c_inc_path.split(":")) + include_paths.add("/usr/include") files = ["linux/input.h", "linux/input-event-codes.h", "linux/uinput.h"] headers = [os.path.join(path, file) for path in include_paths for file in files] From 67f4902dc5d1ace4365cb6ed5839989528929b77 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 18 Feb 2024 14:44:14 +0100 Subject: [PATCH 211/270] Test install --- .github/workflows/install.yaml | 29 +++++++++++++++++++++++++++++ requirements-dev.txt | 1 + 2 files changed, 30 insertions(+) create mode 100644 .github/workflows/install.yaml diff --git a/.github/workflows/install.yaml b/.github/workflows/install.yaml new file mode 100644 index 0000000..7d965b2 --- /dev/null +++ b/.github/workflows/install.yaml @@ -0,0 +1,29 @@ +name: Test install + +on: + - push + - pull_request + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] + include: + - os: ubuntu-latest + python-version: "3.7" + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install python-evdev + run: | + python -m pip install -v . + (cd /tmp && python -c "import evdev.ecodes; print(evdev.ecodes)") diff --git a/requirements-dev.txt b/requirements-dev.txt index c7eb2f2..d8aecc7 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,3 +4,4 @@ sphinx-copybutton ~= 0.5.0 bump2version sphinx_rtd_theme twine +ruff From 8ee96b0cbf369dee5772bd8a6b32c4c30eb20210 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 18 Feb 2024 17:01:42 +0100 Subject: [PATCH 212/270] Minor ruff lint fixes --- docs/conf.py | 3 ++- evdev/device.py | 4 ++-- pyproject.toml | 3 +++ requirements-dev.txt | 11 +++++------ tests/test_ecodes.py | 6 +++--- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index d1dc3fd..875ff1d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- -import sys, os +import os +import sys import sphinx_rtd_theme # Check if readthedocs is building us diff --git a/evdev/device.py b/evdev/device.py index 0d2d1d9..cde168e 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -159,8 +159,8 @@ def __del__(self): def _capabilities(self, absinfo=True): res = {} - for etype, ecodes in self._rawcapabilities.items(): - for code in ecodes: + for etype, _ecodes in self._rawcapabilities.items(): + for code in _ecodes: l = res.setdefault(etype, []) if isinstance(code, tuple): if absinfo: diff --git a/pyproject.toml b/pyproject.toml index 911b8e8..e1115d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,3 +34,6 @@ packages = ["evdev"] [tool.ruff] line-length = 120 + +[tool.ruff.lint] +ignore = ["E265", "E241", "F403", "F401", "E401", "E731"] \ No newline at end of file diff --git a/requirements-dev.txt b/requirements-dev.txt index d8aecc7..ef9693e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,7 +1,6 @@ -pytest ~= 7.1.0 -Sphinx ~= 4.4.0 -sphinx-copybutton ~= 0.5.0 -bump2version -sphinx_rtd_theme -twine +pytest +Sphinx +sphinx-copybutton ~= 0.5.2 +sphinx-rtd-theme ruff +bump-my-version ~= 0.17.4 \ No newline at end of file diff --git a/tests/test_ecodes.py b/tests/test_ecodes.py index 8f4cbbb..c810b4f 100644 --- a/tests/test_ecodes.py +++ b/tests/test_ecodes.py @@ -6,9 +6,9 @@ prefixes = "KEY ABS REL SW MSC LED BTN REP SND ID EV BUS SYN FF_STATUS FF" -def to_tuples(l): +def to_tuples(val): t = lambda x: tuple(x) if isinstance(x, list) else x - return map(t, l) + return map(t, val) def test_equality(): @@ -28,4 +28,4 @@ def test_access(): def test_overlap(): vals_ff = set(to_tuples(ecodes.FF.values())) vals_ff_status = set(to_tuples(ecodes.FF_STATUS.values())) - assert bool(vals_ff & vals_ff_status) == False + assert bool(vals_ff & vals_ff_status) is False From c6090df4ac715728bb910931c46590385687e662 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 18 Feb 2024 17:11:57 +0100 Subject: [PATCH 213/270] Move to bump-my-version from bump2version --- pyproject.toml | 14 +++++++++++++- setup.cfg | 13 ------------- 2 files changed, 13 insertions(+), 14 deletions(-) delete mode 100644 setup.cfg diff --git a/pyproject.toml b/pyproject.toml index e1115d4..f5f4a98 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,4 +36,16 @@ packages = ["evdev"] line-length = 120 [tool.ruff.lint] -ignore = ["E265", "E241", "F403", "F401", "E401", "E731"] \ No newline at end of file +ignore = ["E265", "E241", "F403", "F401", "E401", "E731"] + +[tool.bumpversion] +current_version = "1.6.1" +commit = true +tag = true +allow_dirty = true + +[[tool.bumpversion.files]] +filename = "pyproject.toml" + +[[tool.bumpversion.files]] +filename = "docs/conf.py" \ No newline at end of file diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 47ca660..0000000 --- a/setup.cfg +++ /dev/null @@ -1,13 +0,0 @@ -[bumpversion] -current_version = 1.6.1 -message = Bump version: {current_version} -> {new_version} -commit = True -tag = True - -[flake8] -ignore = W191,E302,E265,E241,F403,E401 -max-line-length = 110 - -[bumpversion:file:setup.py] - -[bumpversion:file:docs/conf.py] From 7249d93186f5db47e52ce103b51219db697fc639 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 18 Feb 2024 19:42:23 +0100 Subject: [PATCH 214/270] Add twine and build --- requirements-dev.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index ef9693e..cee79d4 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,4 +3,6 @@ Sphinx sphinx-copybutton ~= 0.5.2 sphinx-rtd-theme ruff -bump-my-version ~= 0.17.4 \ No newline at end of file +bump-my-version ~= 0.17.4 +build +twine From 452fc2dca407d1afdf96185318dfd093ecfa3140 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 18 Feb 2024 19:48:34 +0100 Subject: [PATCH 215/270] Simplify --- MANIFEST.in | 3 --- 1 file changed, 3 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 7066730..435d617 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,3 @@ -include README.rst -include LICENSE - # The _ecodes extension module source file needs to be generated against the # evdev headers of the running kernel. Refer to the 'build_ecodes' distutils # command in setup.py. From 0593dd422b2a62db9f390d49b618d12f069c9e1b Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 18 Feb 2024 19:57:59 +0100 Subject: [PATCH 216/270] Add .readthedocs.yaml --- .readthedocs.yaml | 15 +++++++++++++++ README.rst | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 .readthedocs.yaml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..fe05af4 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,15 @@ +# https://docs.readthedocs.io/en/stable/config-file/v2.html +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.12" + +sphinx: + configuration: docs/conf.py + +python: + install: + - requirements: requirements-dev.txt + - "." \ No newline at end of file diff --git a/README.rst b/README.rst index 207bc39..9337cf2 100644 --- a/README.rst +++ b/README.rst @@ -11,7 +11,7 @@ subsystem. *Uinput* allows userspace programs to create and handle input devices that can inject events directly into the input subsystem. -Documentation: +Documentation (stable): http://python-evdev.readthedocs.io/en/latest/ Development: From b613af4452d23d20bfd825595e552cada867767a Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 18 Feb 2024 21:44:59 +0100 Subject: [PATCH 217/270] Documentation fixes --- docs/changelog.rst | 22 +++++++++++++++++----- docs/conf.py | 7 ++----- docs/install.rst | 18 +++++++++--------- 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index f656eb1..3c625f6 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,20 +1,32 @@ Changelog --------- +1.7.0 (Feb 18, 2024) +==================== + +- Respect the ``CPATH/C_INCLUDE_PATH`` environment variables during install. + +- Add the uniq address to the string representation of ``InputDevice``. + +- Improved method for finding the device node corresponding to a uinput device (`#206 https://github.com/gvalkov/python-evdev/pull/206`_). + +- Repository TLC (reformatted with ruff, fixed linting warnings, moved packaging metadata to ``pyproject.toml`` etc.). + + 1.6.1 (Jan 20, 2023) -================== +==================== -- Fix generation of ``ecodes.c`` when the path to ````sys.executable`` contains spaces. +- Fix generation of ``ecodes.c`` when the path to ``sys.executable`` contains spaces. 1.6.0 (Jul 17, 2022) -================== +==================== -- Fix Python 3.11 compatibility (`#174 `_) +- Fix Python 3.11 compatibility (`#174 `_). 1.5.0 (Mar 24, 2022) -================== +==================== - Fix documentation (`#163 `_, `#160 `_). diff --git a/docs/conf.py b/docs/conf.py index 875ff1d..96c84f8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -40,6 +40,7 @@ "sphinx.ext.viewcode", "sphinx.ext.intersphinx", "sphinx.ext.napoleon", + "sphinx_rtd_theme", "sphinx_copybutton", ] @@ -111,11 +112,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -if not on_rtd: - import sphinx_rtd_theme - - html_theme = "sphinx_rtd_theme" - html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] +html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the diff --git a/docs/install.rst b/docs/install.rst index 6055f80..9906969 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -16,7 +16,7 @@ Python-evdev has been packaged for the following GNU/Linux distributions: - + - Consult the documentation of your OS package manager for installation instructions. From 81ac5bbd7dfc75117bbdaf79d15d1a3726f9702e Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Thu, 9 May 2024 00:03:01 +0200 Subject: [PATCH 227/270] Documentation fix --- docs/install.rst | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/docs/install.rst b/docs/install.rst index 304e617..da86e24 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -18,7 +18,7 @@ From source The latest stable version of *python-evdev* can be installed from pypi_, provided that you have a compiler, pip_ and the Python and Linux development headers installed on your system. Installing these is distribution specific and -typically falls in one of the following: +typically falls into one of the following: On a Debian compatible OS: @@ -40,12 +40,21 @@ On Arch Linux and derivatives: $ pacman -S core/linux-api-headers python-pip gcc -Once all dependencies are available, you may install *python-evdev* using pip_: +Once all OS dependencies are available, you may install *python-evdev* using +pip_, preferably in a [virtualenv]_: .. code-block:: bash - $ sudo pip install evdev # available globally - $ pip install --user evdev # available to the current user + # Install globally (not recommended). + $ sudo python3 -m pip install evdev + + # Install for the current user. + $ python3 -m pip install --user evdev + + # Install in a virtual environment. + $ python3 -m venv abc + $ source abc/bin/activate + $ python3 -m pip install evdev Specifying header locations @@ -73,3 +82,4 @@ colon-separated paths. For example: .. _pip: http://pip.readthedocs.org/en/latest/installing.html .. _example: https://github.com/gvalkov/python-evdev/tree/master/examples .. _`async/await`: https://docs.python.org/3/library/asyncio-task.html +.. _virtualenv: https://docs.python.org/3/library/venv.html From 396bf0c71024b6f280bb44b473895384fb7de874 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Thu, 9 May 2024 00:44:49 +0200 Subject: [PATCH 228/270] Binary wheels --- .gitignore | 9 +++++---- docs/changelog.rst | 7 +++++++ docs/install.rst | 23 +++++++++++++++++++---- requirements-dev.txt | 1 + scripts/build-binary.sh | 15 +++++++++++++++ scripts/cibw-before.sh | 6 ++++++ setup.py | 4 ++++ 7 files changed, 57 insertions(+), 8 deletions(-) create mode 100755 scripts/build-binary.sh create mode 100755 scripts/cibw-before.sh diff --git a/.gitignore b/.gitignore index 8fc000b..329a06d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,20 +5,21 @@ develop-eggs/ dist/ build/ +wheelhouse/ dropin.cache pip-log.txt .installed.cfg .coverage tags TAGS -evdev/*.so -evdev/ecodes.c -evdev/iprops.c -docs/_build .#* __pycache__ .pytest_cache +evdev/*.so +evdev/ecodes.c +evdev/iprops.c +docs/_build evdev/_ecodes.py evdev/_input.py evdev/_uinput.py diff --git a/docs/changelog.rst b/docs/changelog.rst index b5b2251..c14026a 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,13 @@ Changelog --------- +1.8.0 (Unreleased) +================== + +- Binary wheels are now provided by the `evdev-binary http://pypi.python.org/pypi/evdev-binary`_ package. + The package is compiled on manylinux_2_28 against kernel 4.18. + + 1.7.1 (May 8, 2024) ==================== diff --git a/docs/install.rst b/docs/install.rst index da86e24..f93e0b8 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -58,13 +58,13 @@ pip_, preferably in a [virtualenv]_: Specifying header locations -=========================== +--------------------------- By default, the setup script will look for the ``input.h`` and ``input-event-codes.h`` [#f1]_ header files ``/usr/include/linux``. You may use the ``--evdev-headers`` option to the ``build_ext`` setuptools -command to the location of these header files. It accepts one or more +command to the location of these header files. It accepts one or more colon-separated paths. For example: .. code-block:: bash @@ -74,12 +74,27 @@ colon-separated paths. For example: --include-dirs buildroot/ \ install # or any other command (e.g. develop, bdist, bdist_wheel) -.. [#f1] ``input-event-codes.h`` is found only in more recent kernel versions. +From a binary package +===================== +You may choose to install a precompiled version of *python-evdev* from pypi. The +`evdev-binary`_ package provides binary wheels that have been compiled on EL8 +against the 4.18.0 kernel headers. + +.. code-block:: bash + + $ python3 -m pip install evdev-binary + +While the evdev interface is stable, the precompiled version may not be fully +compatible or expose all the features of your running kernel. For best results, +it is recommended to use an OS package or to install from source. + + +.. [#f1] ``input-event-codes.h`` is found only in recent kernel versions. .. _pypi: http://pypi.python.org/pypi/evdev +.. _evdev-binary: http://pypi.python.org/pypi/evdev-binary .. _github: https://github.com/gvalkov/python-evdev .. _pip: http://pip.readthedocs.org/en/latest/installing.html .. _example: https://github.com/gvalkov/python-evdev/tree/master/examples -.. _`async/await`: https://docs.python.org/3/library/asyncio-task.html .. _virtualenv: https://docs.python.org/3/library/venv.html diff --git a/requirements-dev.txt b/requirements-dev.txt index cee79d4..96366e6 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,3 +6,4 @@ ruff bump-my-version ~= 0.17.4 build twine +cibuildwheel diff --git a/scripts/build-binary.sh b/scripts/build-binary.sh new file mode 100755 index 0000000..bbdae6c --- /dev/null +++ b/scripts/build-binary.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +set -o allexport +set -o nounset + +CIBW_MANYLINUX_X86_64_IMAGE="manylinux_2_28" +CIBW_MANYLINUX_I686_IMAGE="manylinux_2_28" +CIBW_CONTAINER_ENGINE="podman" +CIBW_SKIP="cp36-*" +CIBW_ARCHS_LINUX="auto64" +CIBW_BEFORE_ALL_LINUX=./scripts/cibw-before.sh +CIBW_TEST_COMMAND="python -c 'import evdev; print(evdev)'" +CIBW_ENVIRONMENT="PACKAGE_NAME=evdev-binary" + +exec cibuildwheel \ No newline at end of file diff --git a/scripts/cibw-before.sh b/scripts/cibw-before.sh new file mode 100755 index 0000000..25220d4 --- /dev/null +++ b/scripts/cibw-before.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + + +if [ -n "$PACKAGE_NAME" ]; then + sed -i -re 's,^(name = ")evdev("),\1'${PACKAGE_NAME}'\2,' pyproject.toml +fi \ No newline at end of file diff --git a/setup.py b/setup.py index a776130..6781527 100755 --- a/setup.py +++ b/setup.py @@ -47,6 +47,10 @@ def create_ecodes(headers=None): build_ecodes --evdev-headers path/input.h:path/input-event-codes.h \\ build_ext --include-dirs path/ \\ install + + If you prefer to avoid building this package from source, then please consider + installing the `evdev-binary` package instead. Keep in mind that it may not be + fully compatible with, or support all the features of your current kernel. """ sys.stderr.write(textwrap.dedent(msg)) From 596dc52ba6b1bf1584d74f82351343b28af16080 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Thu, 9 May 2024 00:46:56 +0200 Subject: [PATCH 229/270] Fix example --- docs/tutorial.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 286a493..04ae42f 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -451,9 +451,10 @@ Injecting an FF-event into first FF-capable device found repeat_count = 1 effect_id = dev.upload_effect(effect) dev.write(ecodes.EV_FF, effect_id, repeat_count) - time.sleep(duration_ms) + time.sleep(duration_ms / 1000) dev.erase_effect(effect_id) + Forwarding force-feedback from uinput to a real device ====================================================== From 0d496bf8a5bce2d5c60147609cb79df1386dbf23 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Thu, 9 May 2024 01:20:33 +0200 Subject: [PATCH 230/270] Drop from __future__ import print_function --- evdev/evtest.py | 1 - evdev/genecodes.py | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/evdev/evtest.py b/evdev/evtest.py index 063ca83..b61f093 100644 --- a/evdev/evtest.py +++ b/evdev/evtest.py @@ -16,7 +16,6 @@ evtest /dev/input/event0 /dev/input/event1 """ -from __future__ import print_function import re import sys diff --git a/evdev/genecodes.py b/evdev/genecodes.py index f27104e..ce9939e 100644 --- a/evdev/genecodes.py +++ b/evdev/genecodes.py @@ -2,7 +2,6 @@ Generate a Python extension module with the constants defined in linux/input.h. """ -from __future__ import print_function import os, sys, re @@ -22,9 +21,9 @@ macro_regex = r"#define +((?:KEY|ABS|REL|SW|MSC|LED|BTN|REP|SND|ID|EV|BUS|SYN|FF|UI_FF|INPUT_PROP)_\w+)" macro_regex = re.compile(macro_regex) +# Uname without hostname. uname = list(os.uname()) -del uname[1] -uname = " ".join(uname) +uname = " ".join((uname[0], *uname[2:])) # ----------------------------------------------------------------------------- From 21dc595f32c88f02cb47015546d4c3af0d725d38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Paku=C5=82a?= Date: Tue, 12 Nov 2024 16:33:29 +0100 Subject: [PATCH 231/270] Move `syn()` convenience method from `InputDevice` to `EventIO` (#224) Move `syn()` method from `UInput` to `EventIO`, makes it possible to use it with `InputDevice` as well --- evdev/eventio.py | 9 +++++++++ evdev/uinput.py | 9 --------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/evdev/eventio.py b/evdev/eventio.py index 1b0e5cc..415e2e8 100644 --- a/evdev/eventio.py +++ b/evdev/eventio.py @@ -136,5 +136,14 @@ def write(self, etype, code, value): _uinput.write(self.fd, etype, code, value) + def syn(self): + """ + Inject a ``SYN_REPORT`` event into the input subsystem. Events + queued by :func:`write()` will be fired. If possible, events + will be merged into an 'atomic' event. + """ + + self.write(ecodes.EV_SYN, ecodes.SYN_REPORT, 0) + def close(self): pass diff --git a/evdev/uinput.py b/evdev/uinput.py index 476a84a..c4225d8 100644 --- a/evdev/uinput.py +++ b/evdev/uinput.py @@ -227,15 +227,6 @@ def close(self): _uinput.close(self.fd) self.fd = -1 - def syn(self): - """ - Inject a ``SYN_REPORT`` event into the input subsystem. Events - queued by :func:`write()` will be fired. If possible, events - will be merged into an 'atomic' event. - """ - - _uinput.write(self.fd, ecodes.EV_SYN, ecodes.SYN_REPORT, 0) - def capabilities(self, verbose=False, absinfo=True): """See :func:`capabilities `.""" if self.device is None: From d182b7fbd145a245214a3a1949c0f85d38b18cf5 Mon Sep 17 00:00:00 2001 From: dani-hs Date: Sun, 19 Jan 2025 05:17:33 +0100 Subject: [PATCH 232/270] Fix swapped delay and repeat (#227) --- evdev/device.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/evdev/device.py b/evdev/device.py index cde168e..758f899 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -17,7 +17,7 @@ # -------------------------------------------------------------------------- _AbsInfo = collections.namedtuple("AbsInfo", ["value", "min", "max", "fuzz", "flat", "resolution"]) -_KbdInfo = collections.namedtuple("KbdInfo", ["repeat", "delay"]) +_KbdInfo = collections.namedtuple("KbdInfo", ["delay", "repeat"]) _DeviceInfo = collections.namedtuple("DeviceInfo", ["bustype", "vendor", "product", "version"]) @@ -70,16 +70,16 @@ class KbdInfo(_KbdInfo): Attributes ---------- - repeat - Keyboard repeat rate in characters per second. - delay Amount of time that a key must be depressed before it will start to repeat (in milliseconds). + + repeat + Keyboard repeat rate in characters per second. """ def __str__(self): - return "repeat {}, delay {}".format(*self) + return "delay {}, repeat {}".format(*self) class DeviceInfo(_DeviceInfo): From b1a5bd1cdf2dd8294c18ae97f7c1902b639c99b3 Mon Sep 17 00:00:00 2001 From: Tobi <28510156+sezanzeb@users.noreply.github.com> Date: Sun, 19 Jan 2025 10:37:30 +0100 Subject: [PATCH 233/270] Add pylint -E and pytest to ci (#228) * Remove EOL python 3.7 from the ci * Add pylint -E, fix some pylint errors * Add pytest step * Fix test_abs_values * Turned RuntimeError into the desired UInputError if the device is not a character device, caused by a re-raise outside an except block * Add test for S_ISCHR False --- .github/workflows/install.yaml | 4 ++-- .github/workflows/lint.yml | 27 +++++++++++++++++++++++++++ .github/workflows/test.yml | 29 +++++++++++++++++++++++++++++ evdev/ecodes.py | 1 + evdev/eventio.py | 2 ++ evdev/events.py | 24 ++++++++++++------------ evdev/uinput.py | 2 +- tests/test_uinput.py | 20 ++++++++++++++------ 8 files changed, 88 insertions(+), 21 deletions(-) create mode 100644 .github/workflows/lint.yml create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/install.yaml b/.github/workflows/install.yaml index 7d965b2..f959de2 100644 --- a/.github/workflows/install.yaml +++ b/.github/workflows/install.yaml @@ -11,10 +11,10 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] include: - os: ubuntu-latest - python-version: "3.7" + python-version: "3.8" steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..d499462 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,27 @@ +name: Lint + +on: + - push + - pull_request + +jobs: + pylint: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + python-version: ["3.12"] + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Check for pylint errors + run: | + python -m pip install pylint setuptools + python setup.py build + python -m pylint --disable=no-member --verbose -E build/lib*/evdev diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..3ee56d3 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,29 @@ +name: Test + +on: + - push + - pull_request + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + python-version: ["3.12"] + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Run pytest tests + # pip install -e . builds _ecodes and such into the evdev directory + # sudo required to write to uinputs + run: | + sudo python -m pip install pytest setuptools + sudo python -m pip install -e . + sudo python -m pytest tests diff --git a/evdev/ecodes.py b/evdev/ecodes.py index 3562368..759cfe7 100644 --- a/evdev/ecodes.py +++ b/evdev/ecodes.py @@ -1,3 +1,4 @@ +# pylint: disable=undefined-variable """ This modules exposes the integer constants defined in ``linux/input.h`` and ``linux/input-event-codes.h``. diff --git a/evdev/eventio.py b/evdev/eventio.py index 415e2e8..3335500 100644 --- a/evdev/eventio.py +++ b/evdev/eventio.py @@ -72,6 +72,7 @@ def read(self): for event in events: yield InputEvent(*event) + # pylint: disable=no-self-argument def need_write(func): """ Decorator that raises :class:`EvdevError` if there is no write access to the @@ -82,6 +83,7 @@ def need_write(func): def wrapper(*args): fd = args[0].fd if fcntl.fcntl(fd, fcntl.F_GETFL) & os.O_RDWR: + # pylint: disable=not-callable return func(*args) msg = 'no write access to device "%s"' % args[0].path raise EvdevError(msg) diff --git a/evdev/events.py b/evdev/events.py index 104b563..97f570d 100644 --- a/evdev/events.py +++ b/evdev/events.py @@ -65,13 +65,13 @@ def timestamp(self): """Return event timestamp as a float.""" return self.sec + (self.usec / 1000000.0) - def __str__(s): + def __str__(self): msg = "event at {:f}, code {:02d}, type {:02d}, val {:02d}" - return msg.format(s.timestamp(), s.code, s.type, s.value) + return msg.format(self.timestamp(), self.code, self.type, self.value) - def __repr__(s): + def __repr__(self): msg = "{}({!r}, {!r}, {!r}, {!r}, {!r})" - return msg.format(s.__class__.__name__, s.sec, s.usec, s.type, s.code, s.value) + return msg.format(self.__class__.__name__, self.sec, self.usec, self.type, self.code, self.value) class KeyEvent: @@ -119,8 +119,8 @@ def __str__(self): msg = "key event at {:f}, {} ({}), {}" return msg.format(self.event.timestamp(), self.scancode, self.keycode, ks) - def __repr__(s): - return "{}({!r})".format(s.__class__.__name__, s.event) + def __repr__(self): + return "{}({!r})".format(self.__class__.__name__, self.event) class RelEvent: @@ -136,8 +136,8 @@ def __str__(self): msg = "relative axis event at {:f}, {}" return msg.format(self.event.timestamp(), REL[self.event.code]) - def __repr__(s): - return "{}({!r})".format(s.__class__.__name__, s.event) + def __repr__(self): + return "{}({!r})".format(self.__class__.__name__, self.event) class AbsEvent: @@ -153,8 +153,8 @@ def __str__(self): msg = "absolute axis event at {:f}, {}" return msg.format(self.event.timestamp(), ABS[self.event.code]) - def __repr__(s): - return "{}({!r})".format(s.__class__.__name__, s.event) + def __repr__(self): + return "{}({!r})".format(self.__class__.__name__, self.event) class SynEvent: @@ -173,8 +173,8 @@ def __str__(self): msg = "synchronization event at {:f}, {}" return msg.format(self.event.timestamp(), SYN[self.event.code]) - def __repr__(s): - return "{}({!r})".format(s.__class__.__name__, s.event) + def __repr__(self): + return "{}({!r})".format(self.__class__.__name__, self.event) #: A mapping of event types to :class:`InputEvent` sub-classes. Used diff --git a/evdev/uinput.py b/evdev/uinput.py index c4225d8..756f83c 100644 --- a/evdev/uinput.py +++ b/evdev/uinput.py @@ -272,7 +272,7 @@ def _verify(self): try: m = os.stat(self.devnode)[stat.ST_MODE] if not stat.S_ISCHR(m): - raise + raise OSError except (IndexError, OSError): msg = '"{}" does not exist or is not a character device file ' "- verify that the uinput module is loaded" raise UInputError(msg.format(self.devnode)) diff --git a/tests/test_uinput.py b/tests/test_uinput.py index 2bf3dc1..dcd09e0 100644 --- a/tests/test_uinput.py +++ b/tests/test_uinput.py @@ -1,10 +1,12 @@ # encoding: utf-8 - +import stat from select import select -from pytest import raises, fixture +from unittest.mock import patch -from evdev import uinput, ecodes, events, device, util +import pytest +from pytest import raises, fixture +from evdev import uinput, ecodes, device, UInputError # ----------------------------------------------------------------------------- uinput_options = { @@ -66,12 +68,12 @@ def test_enable_events(c): def test_abs_values(c): e = ecodes - c["events"] = { + c = { e.EV_KEY: [e.KEY_A, e.KEY_B], - e.EV_ABS: [(e.ABS_X, (0, 255, 0, 0)), (e.ABS_Y, device.AbsInfo(0, 255, 5, 10, 0, 0))], + e.EV_ABS: [(e.ABS_X, (0, 0, 255, 0, 0)), (e.ABS_Y, device.AbsInfo(0, 0, 255, 5, 10, 0))], } - with uinput.UInput(**c) as ui: + with uinput.UInput(events=c) as ui: c = ui.capabilities() abs = device.AbsInfo(value=0, min=0, max=255, fuzz=0, flat=0, resolution=0) assert c[e.EV_ABS][0] == (0, abs) @@ -114,3 +116,9 @@ def test_write(c): assert evs[3].code == ecodes.KEY_A and evs[3].value == 2 assert evs[4].code == ecodes.KEY_A and evs[4].value == 0 break + + +@patch.object(stat, 'S_ISCHR', return_value=False) +def test_not_a_character_device(c): + with pytest.raises(UInputError): + uinput.UInput(**c) From 047bf13da1dc5acc3573f2d66c7d378011e73ec8 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Mon, 20 Jan 2025 21:32:02 +0100 Subject: [PATCH 234/270] Use relative imports and sort imports --- evdev/__init__.py | 11 +++++------ evdev/device.py | 11 +++++------ evdev/ecodes.py | 2 +- evdev/eventio.py | 8 ++++---- evdev/eventio_async.py | 4 ++-- evdev/events.py | 2 +- evdev/evtest.py | 13 ++++--------- evdev/ff.py | 2 +- evdev/uinput.py | 8 +++----- evdev/util.py | 10 +++++----- 10 files changed, 31 insertions(+), 40 deletions(-) diff --git a/evdev/__init__.py b/evdev/__init__.py index 36b330c..6aa6ef2 100644 --- a/evdev/__init__.py +++ b/evdev/__init__.py @@ -2,9 +2,8 @@ # Gather everything into a single, convenient namespace. # -------------------------------------------------------------------------- -from evdev.device import DeviceInfo, InputDevice, AbsInfo, EvdevError -from evdev.events import InputEvent, KeyEvent, RelEvent, SynEvent, AbsEvent, event_factory -from evdev.uinput import UInput, UInputError -from evdev.util import list_devices, categorize, resolve_ecodes, resolve_ecodes_dict -from evdev import ecodes -from evdev import ff +from . import ecodes, ff +from .device import AbsInfo, DeviceInfo, EvdevError, InputDevice +from .events import AbsEvent, InputEvent, KeyEvent, RelEvent, SynEvent, event_factory +from .uinput import UInput, UInputError +from .util import categorize, list_devices, resolve_ecodes, resolve_ecodes_dict diff --git a/evdev/device.py b/evdev/device.py index 758f899..7675a2d 100644 --- a/evdev/device.py +++ b/evdev/device.py @@ -1,17 +1,16 @@ # encoding: utf-8 +import collections +import contextlib import os import warnings -import contextlib -import collections -from evdev import _input, ecodes, util -from evdev.events import InputEvent +from . import _input, ecodes, util try: - from evdev.eventio_async import EventIO, EvdevError + from .eventio_async import EvdevError, EventIO except ImportError: - from evdev.eventio import EventIO, EvdevError + from .eventio import EvdevError, EventIO # -------------------------------------------------------------------------- diff --git a/evdev/ecodes.py b/evdev/ecodes.py index 759cfe7..3a6c3d0 100644 --- a/evdev/ecodes.py +++ b/evdev/ecodes.py @@ -40,8 +40,8 @@ """ from inspect import getmembers -from evdev import _ecodes +from . import _ecodes #: Mapping of names to values. ecodes = {} diff --git a/evdev/eventio.py b/evdev/eventio.py index 3335500..5478f02 100644 --- a/evdev/eventio.py +++ b/evdev/eventio.py @@ -1,10 +1,10 @@ -import os import fcntl -import select import functools +import os +import select -from evdev import _input, _uinput, ecodes, util -from evdev.events import InputEvent +from . import _input, _uinput, ecodes +from .events import InputEvent # -------------------------------------------------------------------------- diff --git a/evdev/eventio_async.py b/evdev/eventio_async.py index e89765e..fb8bcd2 100644 --- a/evdev/eventio_async.py +++ b/evdev/eventio_async.py @@ -1,10 +1,10 @@ import asyncio import select -from evdev import eventio +from . import eventio # needed for compatibility -from evdev.eventio import EvdevError +from .eventio import EvdevError class EventIO(eventio.EventIO): diff --git a/evdev/events.py b/evdev/events.py index 97f570d..9a85436 100644 --- a/evdev/events.py +++ b/evdev/events.py @@ -37,7 +37,7 @@ # event type descriptions have been taken mot-a-mot from: # http://www.kernel.org/doc/Documentation/input/event-codes.txt -from evdev.ecodes import keys, KEY, SYN, REL, ABS, EV_KEY, EV_REL, EV_ABS, EV_SYN +from .ecodes import ABS, EV_ABS, EV_KEY, EV_REL, EV_SYN, KEY, REL, SYN, keys class InputEvent: diff --git a/evdev/evtest.py b/evdev/evtest.py index b61f093..26e62ad 100644 --- a/evdev/evtest.py +++ b/evdev/evtest.py @@ -17,19 +17,14 @@ """ +import atexit +import optparse import re -import sys import select -import atexit +import sys import termios -import optparse - -try: - input = raw_input -except NameError: - pass -from evdev import ecodes, list_devices, AbsInfo, InputDevice +from . import AbsInfo, InputDevice, ecodes, list_devices def parseopt(): diff --git a/evdev/ff.py b/evdev/ff.py index edb5ff2..260c362 100644 --- a/evdev/ff.py +++ b/evdev/ff.py @@ -1,6 +1,6 @@ import ctypes -from evdev import ecodes +from . import ecodes _u8 = ctypes.c_uint8 _u16 = ctypes.c_uint16 diff --git a/evdev/uinput.py b/evdev/uinput.py index 756f83c..61de946 100644 --- a/evdev/uinput.py +++ b/evdev/uinput.py @@ -1,3 +1,4 @@ +import ctypes import os import platform import re @@ -5,11 +6,8 @@ import time from collections import defaultdict -from evdev import _uinput -from evdev import ecodes, util, device -from evdev.events import InputEvent -import evdev.ff as ff -import ctypes +from . import _uinput, device, ecodes, ff, util +from .events import InputEvent try: from evdev.eventio_async import EventIO diff --git a/evdev/util.py b/evdev/util.py index 7209f4b..59991f6 100644 --- a/evdev/util.py +++ b/evdev/util.py @@ -1,11 +1,11 @@ -import re +import collections +import glob import os +import re import stat -import glob -import collections -from evdev import ecodes -from evdev.events import event_factory +from . import ecodes +from .events import event_factory def list_devices(input_device_dir="/dev/input"): From 1818e9df54f84f6f1ed2bae87f8fe1e0c40d7682 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Mon, 20 Jan 2025 22:51:24 +0100 Subject: [PATCH 235/270] Bump required python version to 3.8 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9ba60ff..37260f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ description = "Bindings to the Linux input handling subsystem" keywords = ["evdev", "input", "uinput"] readme = "README.md" license = {file = "LICENSE"} -requires-python = ">=3.6" +requires-python = ">=3.8" authors = [ { name="Georgi Valkov", email="georgi.t.valkov@gmail.com" }, ] From 4a0efdd252cdd35a909ab46e89f7c76ca1cf4714 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Mon, 20 Jan 2025 22:48:47 +0100 Subject: [PATCH 236/270] Generate typing stubs for evdev.ecodes --- .gitignore | 2 +- MANIFEST.in | 1 + evdev/genecodes.py | 85 ++++++++++++++++++++++++++++++++++------------ pyproject.toml | 3 ++ setup.py | 21 ++++++++---- 5 files changed, 83 insertions(+), 29 deletions(-) diff --git a/.gitignore b/.gitignore index 329a06d..6548086 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,7 @@ __pycache__ evdev/*.so evdev/ecodes.c -evdev/iprops.c +evdev/ecodes.pyi docs/_build evdev/_ecodes.py evdev/_input.py diff --git a/MANIFEST.in b/MANIFEST.in index 435d617..1b5a7b6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,3 +2,4 @@ # evdev headers of the running kernel. Refer to the 'build_ecodes' distutils # command in setup.py. exclude evdev/ecodes.c +include evdev/ecodes.pyi diff --git a/evdev/genecodes.py b/evdev/genecodes.py index ce9939e..68b7dea 100644 --- a/evdev/genecodes.py +++ b/evdev/genecodes.py @@ -2,8 +2,10 @@ Generate a Python extension module with the constants defined in linux/input.h. """ -import os, sys, re - +import getopt +import os +import re +import sys # ----------------------------------------------------------------------------- # The default header file locations to try. @@ -13,8 +15,10 @@ "/usr/include/linux/uinput.h", ] -if sys.argv[1:]: - headers = sys.argv[1:] +opts, args = getopt.getopt(sys.argv[1:], "", ["ecodes", "stubs"]) +if not opts: + print("usage: genecodes.py [--ecodes|--stubs] ") + exit(2) # ----------------------------------------------------------------------------- @@ -27,7 +31,7 @@ # ----------------------------------------------------------------------------- -template = r""" +template_ecodes = r""" #include #ifdef __FreeBSD__ #include @@ -37,7 +41,8 @@ #endif /* Automatically generated by evdev.genecodes */ -/* Generated on %s */ +/* Generated on %s */ +/* Generated from %s */ #define MODULE_NAME "_ecodes" #define MODULE_HELP "linux/input.h macros" @@ -71,25 +76,63 @@ """ -def parse_header(header): - for line in open(header): - macro = macro_regex.search(line) - if macro: - yield " PyModule_AddIntMacro(m, %s);" % macro.group(1) +template_stubs = r""" +# Automatically generated by evdev.genecodes +# Generated on %s +# Generated from %s + +# pylint: skip-file + +ecodes: dict[str, int] +keys: dict[int, str|list[str]] +bytype: dict[int, dict[int, str|list[str]]] + +KEY: dict[int, str|list[str]] +ABS: dict[int, str|list[str]] +REL: dict[int, str|list[str]] +SW: dict[int, str|list[str]] +MSC: dict[int, str|list[str]] +LED: dict[int, str|list[str]] +BTN: dict[int, str|list[str]] +REP: dict[int, str|list[str]] +SND: dict[int, str|list[str]] +ID: dict[int, str|list[str]] +EV: dict[int, str|list[str]] +BUS: dict[int, str|list[str]] +SYN: dict[int, str|list[str]] +FF_STATUS: dict[int, str|list[str]] +FF_INPUT_PROP: dict[int, str|list[str]] + +%s +""" -all_macros = [] -for header in headers: - try: - fh = open(header) - except (IOError, OSError): - continue - all_macros += parse_header(header) +def parse_headers(headers=headers): + for header in headers: + try: + fh = open(header) + except (IOError, OSError): + continue + for line in fh: + macro = macro_regex.search(line) + if macro: + yield macro.group(1) + + +all_macros = list(parse_headers()) if not all_macros: print("no input macros found in: %s" % " ".join(headers), file=sys.stderr) sys.exit(1) - -macros = os.linesep.join(all_macros) -print(template % (uname, macros)) +# pylint: disable=possibly-used-before-assignment, used-before-assignment +if ("--ecodes", "") in opts: + body = (" PyModule_AddIntMacro(m, %s);" % macro for macro in all_macros) + template = template_ecodes +elif ("--stubs", "") in opts: + body = ("%s: int" % macro for macro in all_macros) + template = template_stubs + +body = os.linesep.join(body) +text = template % (uname, headers, body) +print(text.strip()) diff --git a/pyproject.toml b/pyproject.toml index 37260f1..3175a51 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,9 @@ classifiers = [ [tool.setuptools] packages = ["evdev"] +[tool.setuptools.data-files] +"data" = ["evdev/*.pyi"] + [tool.ruff] line-length = 120 diff --git a/setup.py b/setup.py index 6781527..0990554 100755 --- a/setup.py +++ b/setup.py @@ -8,7 +8,8 @@ curdir = Path(__file__).resolve().parent -ecodes_path = curdir / "evdev/ecodes.c" +ecodes_c_path = curdir / "evdev/ecodes.c" +ecodes_pyi_path = curdir / "evdev/ecodes.pyi" def create_ecodes(headers=None): @@ -58,9 +59,14 @@ def create_ecodes(headers=None): from subprocess import run - print("writing %s (using %s)" % (ecodes_path, " ".join(headers))) - with ecodes_path.open("w") as fh: - cmd = [sys.executable, "evdev/genecodes.py", *headers] + print("writing %s (using %s)" % (ecodes_c_path, " ".join(headers))) + with ecodes_c_path.open("w") as fh: + cmd = [sys.executable, "evdev/genecodes.py", "--ecodes", *headers] + run(cmd, check=True, stdout=fh) + + print("writing %s (using %s)" % (ecodes_pyi_path, " ".join(headers))) + with ecodes_pyi_path.open("w") as fh: + cmd = [sys.executable, "evdev/genecodes.py", "--stubs", *headers] run(cmd, check=True, stdout=fh) @@ -84,9 +90,10 @@ def run(self): class build_ext(_build_ext.build_ext): def has_ecodes(self): - if ecodes_path.exists(): - print("ecodes.c already exists ... skipping build_ecodes") - return not ecodes_path.exists() + if ecodes_c_path.exists() and ecodes_pyi_path.exists(): + print("ecodes.c and ecodes.pyi already exist ... skipping build_ecodes") + return False + return True def run(self): for cmd_name in self.get_sub_commands(): From abd286e3d8880d99556f37f6e349ce6cecaaf267 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Mon, 20 Jan 2025 23:45:41 +0100 Subject: [PATCH 237/270] Pylint fixes --- .github/workflows/install.yaml | 2 +- .github/workflows/lint.yml | 2 +- evdev/events.py | 1 + pyproject.toml | 9 +++++++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/install.yaml b/.github/workflows/install.yaml index f959de2..87502ad 100644 --- a/.github/workflows/install.yaml +++ b/.github/workflows/install.yaml @@ -11,7 +11,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] include: - os: ubuntu-latest python-version: "3.8" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d499462..e293976 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -24,4 +24,4 @@ jobs: run: | python -m pip install pylint setuptools python setup.py build - python -m pylint --disable=no-member --verbose -E build/lib*/evdev + python -m pylint --verbose -E build/lib*/evdev diff --git a/evdev/events.py b/evdev/events.py index 9a85436..a4f817d 100644 --- a/evdev/events.py +++ b/evdev/events.py @@ -37,6 +37,7 @@ # event type descriptions have been taken mot-a-mot from: # http://www.kernel.org/doc/Documentation/input/event-codes.txt +# pylint: disable=no-name-in-module from .ecodes import ABS, EV_ABS, EV_KEY, EV_REL, EV_SYN, KEY, REL, SYN, keys diff --git a/pyproject.toml b/pyproject.toml index 3175a51..5f56454 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,3 +52,12 @@ filename = "pyproject.toml" [[tool.bumpversion.files]] filename = "docs/conf.py" + +[tool.pylint.'MESSAGES CONTROL'] +disable = """ + no-member, +""" + +[tool.pylint.typecheck] +generated-members = ["evdev.ecodes.*"] +ignored-modules= ["evdev._*"] From 83f9360948534efd4bec82d5db213b49afd9cf15 Mon Sep 17 00:00:00 2001 From: Tobi <28510156+sezanzeb@users.noreply.github.com> Date: Tue, 21 Jan 2025 12:11:15 +0100 Subject: [PATCH 238/270] Small character device verification cleanup (#229) --- evdev/uinput.py | 8 +++----- tests/test_uinput.py | 17 +++++++++++++++-- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/evdev/uinput.py b/evdev/uinput.py index 61de946..9567374 100644 --- a/evdev/uinput.py +++ b/evdev/uinput.py @@ -266,13 +266,11 @@ def _verify(self): Verify that an uinput device exists and is readable and writable by the current process. """ - try: m = os.stat(self.devnode)[stat.ST_MODE] - if not stat.S_ISCHR(m): - raise OSError - except (IndexError, OSError): - msg = '"{}" does not exist or is not a character device file ' "- verify that the uinput module is loaded" + assert stat.S_ISCHR(m) + except (IndexError, OSError, AssertionError): + msg = '"{}" does not exist or is not a character device file - verify that the uinput module is loaded' raise UInputError(msg.format(self.devnode)) if not os.access(self.devnode, os.W_OK): diff --git a/tests/test_uinput.py b/tests/test_uinput.py index dcd09e0..666361f 100644 --- a/tests/test_uinput.py +++ b/tests/test_uinput.py @@ -1,4 +1,5 @@ # encoding: utf-8 +import os import stat from select import select from unittest.mock import patch @@ -119,6 +120,18 @@ def test_write(c): @patch.object(stat, 'S_ISCHR', return_value=False) -def test_not_a_character_device(c): - with pytest.raises(UInputError): +def test_not_a_character_device(ischr_mock, c): + with pytest.raises(UInputError, match='not a character device file'): + uinput.UInput(**c) + +@patch.object(stat, 'S_ISCHR', return_value=True) +@patch.object(os, 'stat', side_effect=OSError()) +def test_not_a_character_device_2(stat_mock, ischr_mock, c): + with pytest.raises(UInputError, match='not a character device file'): + uinput.UInput(**c) + +@patch.object(stat, 'S_ISCHR', return_value=True) +@patch.object(os, 'stat', return_value=[]) +def test_not_a_character_device_3(stat_mock, ischr_mock, c): + with pytest.raises(UInputError, match='not a character device file'): uinput.UInput(**c) From 4ca0a8b41915650e10fbdd4114519c9183406940 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Wed, 22 Jan 2025 00:43:21 +0100 Subject: [PATCH 239/270] Generate ecodes.py at build time - The existing ecodes.py is renamed to ecodes_runtime.py. - An ecodes.py is generated at build time (in build_ext) with the genecodes_py.py script, after the extension modules are built. The script essentially does a repr() on vars(ecodes_runtime) and adds type annotations. - If something goes wrong in the process of generating ecodes.py, ecodes_runtime.py is copied to ecodes.py. - Stop generating ecodes.pyi as the generated ecodes.py is fully annotated. --- .gitignore | 1 + MANIFEST.in | 2 +- docs/changelog.rst | 11 +++ evdev/ecodes.py | 105 +------------------------ evdev/ecodes_runtime.py | 102 ++++++++++++++++++++++++ evdev/{genecodes.py => genecodes_c.py} | 0 evdev/genecodes_py.py | 53 +++++++++++++ pyproject.toml | 3 - setup.py | 30 ++++--- 9 files changed, 190 insertions(+), 117 deletions(-) create mode 100644 evdev/ecodes_runtime.py rename evdev/{genecodes.py => genecodes_c.py} (100%) create mode 100644 evdev/genecodes_py.py diff --git a/.gitignore b/.gitignore index 6548086..3e244aa 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ TAGS .#* __pycache__ .pytest_cache +.ruff_cache evdev/*.so evdev/ecodes.c diff --git a/MANIFEST.in b/MANIFEST.in index 1b5a7b6..bcbbd6c 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,4 +2,4 @@ # evdev headers of the running kernel. Refer to the 'build_ecodes' distutils # command in setup.py. exclude evdev/ecodes.c -include evdev/ecodes.pyi +include evdev/ecodes.py diff --git a/docs/changelog.rst b/docs/changelog.rst index c14026a..81ff3d4 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -7,6 +7,17 @@ Changelog - Binary wheels are now provided by the `evdev-binary http://pypi.python.org/pypi/evdev-binary`_ package. The package is compiled on manylinux_2_28 against kernel 4.18. +- The ``evdev.ecodes`` module is now generated at install time and contains only constants. This allows type + checking and introspection of the ``evdev.ecodes`` module, without having to execute it first. The old + module is available as ``evdev.ecodes_runtime``. In case generation of the static ``ecodes.py`` fails, the + install process falls back to using ``ecodes_runtime.py`` as ``ecodes.py``. + +- Minimum Python version raised to Python 3.8. + +- Fix keyboard delay and repeat being swapped. + +- Move `syn()` convenience method from `InputDevice` to `EventIO`. + 1.7.1 (May 8, 2024) ==================== diff --git a/evdev/ecodes.py b/evdev/ecodes.py index 3a6c3d0..a19dcba 100644 --- a/evdev/ecodes.py +++ b/evdev/ecodes.py @@ -1,102 +1,5 @@ -# pylint: disable=undefined-variable -""" -This modules exposes the integer constants defined in ``linux/input.h`` and -``linux/input-event-codes.h``. +# When installed, this module is replaced by an ecodes.py generated at +# build time by genecodes_py.py (see build_ext in setup.py). -Exposed constants:: - - KEY, ABS, REL, SW, MSC, LED, BTN, REP, SND, ID, EV, - BUS, SYN, FF, FF_STATUS, INPUT_PROP - -This module also provides reverse and forward mappings of the names and values -of the above mentioned constants:: - - >>> evdev.ecodes.KEY_A - 30 - - >>> evdev.ecodes.ecodes['KEY_A'] - 30 - - >>> evdev.ecodes.KEY[30] - 'KEY_A' - - >>> evdev.ecodes.REL[0] - 'REL_X' - - >>> evdev.ecodes.EV[evdev.ecodes.EV_KEY] - 'EV_KEY' - - >>> evdev.ecodes.bytype[evdev.ecodes.EV_REL][0] - 'REL_X' - -Keep in mind that values in reverse mappings may point to one or more event -codes. For example:: - - >>> evdev.ecodes.FF[80] - ['FF_EFFECT_MIN', 'FF_RUMBLE'] - - >>> evdev.ecodes.FF[81] - 'FF_PERIODIC' -""" - -from inspect import getmembers - -from . import _ecodes - -#: Mapping of names to values. -ecodes = {} - -prefixes = "KEY ABS REL SW MSC LED BTN REP SND ID EV BUS SYN FF_STATUS FF INPUT_PROP" -prev_prefix = "" -g = globals() - -# eg. code: 'REL_Z', val: 2 -for code, val in getmembers(_ecodes): - for prefix in prefixes.split(): # eg. 'REL' - if code.startswith(prefix): - ecodes[code] = val - # FF_STATUS codes should not appear in the FF reverse mapping - if not code.startswith(prev_prefix): - d = g.setdefault(prefix, {}) - # codes that share the same value will be added to a list. eg: - # >>> ecodes.FF_STATUS - # {0: 'FF_STATUS_STOPPED', 1: ['FF_STATUS_MAX', 'FF_STATUS_PLAYING']} - if val in d: - if isinstance(d[val], list): - d[val].append(code) - else: - d[val] = [d[val], code] - else: - d[val] = code - - prev_prefix = prefix - -#: Keys are a combination of all BTN and KEY codes. -keys = {} -keys.update(BTN) -keys.update(KEY) - -# make keys safe to use for the default list of uinput device -# capabilities -del keys[_ecodes.KEY_MAX] -del keys[_ecodes.KEY_CNT] - -#: Mapping of event types to other value/name mappings. -bytype = { - _ecodes.EV_KEY: keys, - _ecodes.EV_ABS: ABS, - _ecodes.EV_REL: REL, - _ecodes.EV_SW: SW, - _ecodes.EV_MSC: MSC, - _ecodes.EV_LED: LED, - _ecodes.EV_REP: REP, - _ecodes.EV_SND: SND, - _ecodes.EV_SYN: SYN, - _ecodes.EV_FF: FF, - _ecodes.EV_FF_STATUS: FF_STATUS, -} - -from evdev._ecodes import * - -# cheaper than whitelisting in an __all__ -del code, val, prefix, getmembers, g, d, prefixes, prev_prefix +# This stub exists to make development of evdev itself more convenient. +from . ecodes_runtime import * diff --git a/evdev/ecodes_runtime.py b/evdev/ecodes_runtime.py new file mode 100644 index 0000000..3a6c3d0 --- /dev/null +++ b/evdev/ecodes_runtime.py @@ -0,0 +1,102 @@ +# pylint: disable=undefined-variable +""" +This modules exposes the integer constants defined in ``linux/input.h`` and +``linux/input-event-codes.h``. + +Exposed constants:: + + KEY, ABS, REL, SW, MSC, LED, BTN, REP, SND, ID, EV, + BUS, SYN, FF, FF_STATUS, INPUT_PROP + +This module also provides reverse and forward mappings of the names and values +of the above mentioned constants:: + + >>> evdev.ecodes.KEY_A + 30 + + >>> evdev.ecodes.ecodes['KEY_A'] + 30 + + >>> evdev.ecodes.KEY[30] + 'KEY_A' + + >>> evdev.ecodes.REL[0] + 'REL_X' + + >>> evdev.ecodes.EV[evdev.ecodes.EV_KEY] + 'EV_KEY' + + >>> evdev.ecodes.bytype[evdev.ecodes.EV_REL][0] + 'REL_X' + +Keep in mind that values in reverse mappings may point to one or more event +codes. For example:: + + >>> evdev.ecodes.FF[80] + ['FF_EFFECT_MIN', 'FF_RUMBLE'] + + >>> evdev.ecodes.FF[81] + 'FF_PERIODIC' +""" + +from inspect import getmembers + +from . import _ecodes + +#: Mapping of names to values. +ecodes = {} + +prefixes = "KEY ABS REL SW MSC LED BTN REP SND ID EV BUS SYN FF_STATUS FF INPUT_PROP" +prev_prefix = "" +g = globals() + +# eg. code: 'REL_Z', val: 2 +for code, val in getmembers(_ecodes): + for prefix in prefixes.split(): # eg. 'REL' + if code.startswith(prefix): + ecodes[code] = val + # FF_STATUS codes should not appear in the FF reverse mapping + if not code.startswith(prev_prefix): + d = g.setdefault(prefix, {}) + # codes that share the same value will be added to a list. eg: + # >>> ecodes.FF_STATUS + # {0: 'FF_STATUS_STOPPED', 1: ['FF_STATUS_MAX', 'FF_STATUS_PLAYING']} + if val in d: + if isinstance(d[val], list): + d[val].append(code) + else: + d[val] = [d[val], code] + else: + d[val] = code + + prev_prefix = prefix + +#: Keys are a combination of all BTN and KEY codes. +keys = {} +keys.update(BTN) +keys.update(KEY) + +# make keys safe to use for the default list of uinput device +# capabilities +del keys[_ecodes.KEY_MAX] +del keys[_ecodes.KEY_CNT] + +#: Mapping of event types to other value/name mappings. +bytype = { + _ecodes.EV_KEY: keys, + _ecodes.EV_ABS: ABS, + _ecodes.EV_REL: REL, + _ecodes.EV_SW: SW, + _ecodes.EV_MSC: MSC, + _ecodes.EV_LED: LED, + _ecodes.EV_REP: REP, + _ecodes.EV_SND: SND, + _ecodes.EV_SYN: SYN, + _ecodes.EV_FF: FF, + _ecodes.EV_FF_STATUS: FF_STATUS, +} + +from evdev._ecodes import * + +# cheaper than whitelisting in an __all__ +del code, val, prefix, getmembers, g, d, prefixes, prev_prefix diff --git a/evdev/genecodes.py b/evdev/genecodes_c.py similarity index 100% rename from evdev/genecodes.py rename to evdev/genecodes_c.py diff --git a/evdev/genecodes_py.py b/evdev/genecodes_py.py new file mode 100644 index 0000000..bd97553 --- /dev/null +++ b/evdev/genecodes_py.py @@ -0,0 +1,53 @@ +import sys +from unittest import mock +from pprint import PrettyPrinter + +sys.modules["evdev.ecodes"] = mock.Mock() +from evdev import ecodes_runtime as ecodes + +pprint = PrettyPrinter(indent=2, sort_dicts=True, width=120).pprint + + +print("# Automatically generated by evdev.genecodes_py") +print() +print('"""') +print(ecodes.__doc__.strip()) +print('"""') + +print() +print("from typing import Final, Dict, List, Union") +print() + +for name, value in ecodes.ecodes.items(): + print(f"{name}: Final[int] = {value}") +print() + +entries = [ + ("ecodes", "Dict[str, int]", "#: Mapping of names to values."), + ("bytype", "Dict[int, Dict[int, Union[str, List[str]]]]", "#: Mapping of event types to other value/name mappings."), + ("keys", "Dict[int, Union[str, List[str]]]", "#: Keys are a combination of all BTN and KEY codes."), + ("KEY", "Dict[int, Union[str, List[str]]]", None), + ("ABS", "Dict[int, Union[str, List[str]]]", None), + ("REL", "Dict[int, Union[str, List[str]]]", None), + ("SW", "Dict[int, Union[str, List[str]]]", None), + ("MSC", "Dict[int, Union[str, List[str]]]", None), + ("LED", "Dict[int, Union[str, List[str]]]", None), + ("BTN", "Dict[int, Union[str, List[str]]]", None), + ("REP", "Dict[int, Union[str, List[str]]]", None), + ("SND", "Dict[int, Union[str, List[str]]]", None), + ("ID", "Dict[int, Union[str, List[str]]]", None), + ("EV", "Dict[int, Union[str, List[str]]]", None), + ("BUS", "Dict[int, Union[str, List[str]]]", None), + ("SYN", "Dict[int, Union[str, List[str]]]", None), + ("FF", "Dict[int, Union[str, List[str]]]", None), + ("FF_STATUS", "Dict[int, Union[str, List[str]]]", None), + ("INPUT_PROP", "Dict[int, Union[str, List[str]]]", None) +] + +for key, annotation, doc in entries: + if doc: + print(doc) + + print(f"{key}: {annotation} = ", end="") + pprint(getattr(ecodes, key)) + print() diff --git a/pyproject.toml b/pyproject.toml index 5f56454..e7ea393 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,9 +32,6 @@ classifiers = [ [tool.setuptools] packages = ["evdev"] -[tool.setuptools.data-files] -"data" = ["evdev/*.pyi"] - [tool.ruff] line-length = 120 diff --git a/setup.py b/setup.py index 0990554..e6e0c1d 100755 --- a/setup.py +++ b/setup.py @@ -1,7 +1,9 @@ import os import sys +import shutil import textwrap from pathlib import Path +from subprocess import run from setuptools import setup, Extension, Command from setuptools.command import build_ext as _build_ext @@ -9,7 +11,6 @@ curdir = Path(__file__).resolve().parent ecodes_c_path = curdir / "evdev/ecodes.c" -ecodes_pyi_path = curdir / "evdev/ecodes.pyi" def create_ecodes(headers=None): @@ -49,7 +50,7 @@ def create_ecodes(headers=None): build_ext --include-dirs path/ \\ install - If you prefer to avoid building this package from source, then please consider + If you want to avoid building this package from source, then please consider installing the `evdev-binary` package instead. Keep in mind that it may not be fully compatible with, or support all the features of your current kernel. """ @@ -57,16 +58,9 @@ def create_ecodes(headers=None): sys.stderr.write(textwrap.dedent(msg)) sys.exit(1) - from subprocess import run - print("writing %s (using %s)" % (ecodes_c_path, " ".join(headers))) with ecodes_c_path.open("w") as fh: - cmd = [sys.executable, "evdev/genecodes.py", "--ecodes", *headers] - run(cmd, check=True, stdout=fh) - - print("writing %s (using %s)" % (ecodes_pyi_path, " ".join(headers))) - with ecodes_pyi_path.open("w") as fh: - cmd = [sys.executable, "evdev/genecodes.py", "--stubs", *headers] + cmd = [sys.executable, "evdev/genecodes_c.py", "--ecodes", *headers] run(cmd, check=True, stdout=fh) @@ -90,15 +84,27 @@ def run(self): class build_ext(_build_ext.build_ext): def has_ecodes(self): - if ecodes_c_path.exists() and ecodes_pyi_path.exists(): - print("ecodes.c and ecodes.pyi already exist ... skipping build_ecodes") + if ecodes_c_path.exists(): + print("ecodes.c already exists ... skipping build_ecodes") return False return True + def generate_ecodes_py(self): + ecodes_py = Path(self.build_lib) / "evdev/ecodes.py" + print(f"writing {ecodes_py}") + with ecodes_py.open("w") as fh: + cmd = [sys.executable, "-B", "evdev/genecodes_py.py"] + res = run(cmd, env={"PYTHONPATH": self.build_lib}, stdout=fh) + + if res.returncode != 0: + print(f"failed to generate static {ecodes_py} - will use ecodes_runtime.py") + shutil.copy("evdev/ecodes_runtime.py", ecodes_py) + def run(self): for cmd_name in self.get_sub_commands(): self.run_command(cmd_name) _build_ext.build_ext.run(self) + self.generate_ecodes_py() sub_commands = [("build_ecodes", has_ecodes)] + _build_ext.build_ext.sub_commands From dfd45df12abcb6b55f3f19ec1fb100821284a089 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sat, 25 Jan 2025 17:36:10 +0100 Subject: [PATCH 240/270] ecodes mappings that point to more than one value are now tuples --- docs/changelog.rst | 7 ++++++- evdev/ecodes_runtime.py | 17 +++++++++++++---- evdev/genecodes_py.py | 38 +++++++++++++++++++------------------- 3 files changed, 38 insertions(+), 24 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 81ff3d4..15ba749 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,7 +1,7 @@ Changelog --------- -1.8.0 (Unreleased) +1.8.0 (Jan 25, 2025) ================== - Binary wheels are now provided by the `evdev-binary http://pypi.python.org/pypi/evdev-binary`_ package. @@ -12,6 +12,11 @@ Changelog module is available as ``evdev.ecodes_runtime``. In case generation of the static ``ecodes.py`` fails, the install process falls back to using ``ecodes_runtime.py`` as ``ecodes.py``. +- Reverse mappings in ``evdev.ecodes`` that point to more than one value are now tuples and not lists. For example:: + + >>> ecodes.KEY[153] + 153: ('KEY_DIRECTION', 'KEY_ROTATE_DISPLAY'), + - Minimum Python version raised to Python 3.8. - Fix keyboard delay and repeat being swapped. diff --git a/evdev/ecodes_runtime.py b/evdev/ecodes_runtime.py index 3a6c3d0..d6c8b2a 100644 --- a/evdev/ecodes_runtime.py +++ b/evdev/ecodes_runtime.py @@ -33,7 +33,7 @@ codes. For example:: >>> evdev.ecodes.FF[80] - ['FF_EFFECT_MIN', 'FF_RUMBLE'] + ('FF_EFFECT_MIN', 'FF_RUMBLE') >>> evdev.ecodes.FF[81] 'FF_PERIODIC' @@ -46,13 +46,13 @@ #: Mapping of names to values. ecodes = {} -prefixes = "KEY ABS REL SW MSC LED BTN REP SND ID EV BUS SYN FF_STATUS FF INPUT_PROP" +prefixes = "KEY ABS REL SW MSC LED BTN REP SND ID EV BUS SYN FF_STATUS FF INPUT_PROP".split() prev_prefix = "" g = globals() # eg. code: 'REL_Z', val: 2 for code, val in getmembers(_ecodes): - for prefix in prefixes.split(): # eg. 'REL' + for prefix in prefixes: # eg. 'REL' if code.startswith(prefix): ecodes[code] = val # FF_STATUS codes should not appear in the FF reverse mapping @@ -71,6 +71,15 @@ prev_prefix = prefix + +# Convert lists to tuples. +k, v = None, None +for prefix in prefixes: + for k, v in g[prefix].items(): + if isinstance(v, list): + g[prefix][k] = tuple(v) + + #: Keys are a combination of all BTN and KEY codes. keys = {} keys.update(BTN) @@ -99,4 +108,4 @@ from evdev._ecodes import * # cheaper than whitelisting in an __all__ -del code, val, prefix, getmembers, g, d, prefixes, prev_prefix +del code, val, prefix, getmembers, g, d, k, v, prefixes, prev_prefix diff --git a/evdev/genecodes_py.py b/evdev/genecodes_py.py index bd97553..1afbc34 100644 --- a/evdev/genecodes_py.py +++ b/evdev/genecodes_py.py @@ -15,7 +15,7 @@ print('"""') print() -print("from typing import Final, Dict, List, Union") +print("from typing import Final, Dict, Tuple, Union") print() for name, value in ecodes.ecodes.items(): @@ -24,24 +24,24 @@ entries = [ ("ecodes", "Dict[str, int]", "#: Mapping of names to values."), - ("bytype", "Dict[int, Dict[int, Union[str, List[str]]]]", "#: Mapping of event types to other value/name mappings."), - ("keys", "Dict[int, Union[str, List[str]]]", "#: Keys are a combination of all BTN and KEY codes."), - ("KEY", "Dict[int, Union[str, List[str]]]", None), - ("ABS", "Dict[int, Union[str, List[str]]]", None), - ("REL", "Dict[int, Union[str, List[str]]]", None), - ("SW", "Dict[int, Union[str, List[str]]]", None), - ("MSC", "Dict[int, Union[str, List[str]]]", None), - ("LED", "Dict[int, Union[str, List[str]]]", None), - ("BTN", "Dict[int, Union[str, List[str]]]", None), - ("REP", "Dict[int, Union[str, List[str]]]", None), - ("SND", "Dict[int, Union[str, List[str]]]", None), - ("ID", "Dict[int, Union[str, List[str]]]", None), - ("EV", "Dict[int, Union[str, List[str]]]", None), - ("BUS", "Dict[int, Union[str, List[str]]]", None), - ("SYN", "Dict[int, Union[str, List[str]]]", None), - ("FF", "Dict[int, Union[str, List[str]]]", None), - ("FF_STATUS", "Dict[int, Union[str, List[str]]]", None), - ("INPUT_PROP", "Dict[int, Union[str, List[str]]]", None) + ("bytype", "Dict[int, Dict[int, Union[str, Tuple[str]]]]", "#: Mapping of event types to other value/name mappings."), + ("keys", "Dict[int, Union[str, Tuple[str]]]", "#: Keys are a combination of all BTN and KEY codes."), + ("KEY", "Dict[int, Union[str, Tuple[str]]]", None), + ("ABS", "Dict[int, Union[str, Tuple[str]]]", None), + ("REL", "Dict[int, Union[str, Tuple[str]]]", None), + ("SW", "Dict[int, Union[str, Tuple[str]]]", None), + ("MSC", "Dict[int, Union[str, Tuple[str]]]", None), + ("LED", "Dict[int, Union[str, Tuple[str]]]", None), + ("BTN", "Dict[int, Union[str, Tuple[str]]]", None), + ("REP", "Dict[int, Union[str, Tuple[str]]]", None), + ("SND", "Dict[int, Union[str, Tuple[str]]]", None), + ("ID", "Dict[int, Union[str, Tuple[str]]]", None), + ("EV", "Dict[int, Union[str, Tuple[str]]]", None), + ("BUS", "Dict[int, Union[str, Tuple[str]]]", None), + ("SYN", "Dict[int, Union[str, Tuple[str]]]", None), + ("FF", "Dict[int, Union[str, Tuple[str]]]", None), + ("FF_STATUS", "Dict[int, Union[str, Tuple[str]]]", None), + ("INPUT_PROP", "Dict[int, Union[str, Tuple[str]]]", None) ] for key, annotation, doc in entries: From 2e3b843f37f79a9404da4541bbf9cf96dc5a4d57 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sat, 25 Jan 2025 17:37:33 +0100 Subject: [PATCH 241/270] =?UTF-8?q?Bump=20version:=201.7.1=20=E2=86=92=201?= =?UTF-8?q?.8.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 2 +- docs/conf.py | 2 +- pyproject.toml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/LICENSE b/LICENSE index 5600871..8482b07 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2012-2023 Georgi Valkov. All rights reserved. +Copyright (c) 2012-2025 Georgi Valkov. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are diff --git a/docs/conf.py b/docs/conf.py index bf03b42..7af99b9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -67,7 +67,7 @@ # built documents. # # The full version, including alpha/beta/rc tags. -release = "1.7.1" +release = "1.8.0" # The short X.Y version. version = release diff --git a/pyproject.toml b/pyproject.toml index e7ea393..e5e5d00 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "evdev" -version = "1.7.1" +version = "1.8.0" description = "Bindings to the Linux input handling subsystem" keywords = ["evdev", "input", "uinput"] readme = "README.md" @@ -39,7 +39,7 @@ line-length = 120 ignore = ["E265", "E241", "F403", "F401", "E401", "E731"] [tool.bumpversion] -current_version = "1.7.1" +current_version = "1.8.0" commit = true tag = true allow_dirty = true From 27eb2ff11bb6b41fa0cfcff4f80d6c26d4b65742 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sat, 25 Jan 2025 18:04:39 +0100 Subject: [PATCH 242/270] Fix tests --- tests/test_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_util.py b/tests/test_util.py index 5a979df..7112927 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -6,7 +6,7 @@ def test_match_ecodes_a(): assert res == {1: [372, 418, 419, 420]} assert dict(util.resolve_ecodes_dict(res)) == { ("EV_KEY", 1): [ - (["KEY_FULL_SCREEN", "KEY_ZOOM"], 372), + (("KEY_FULL_SCREEN", "KEY_ZOOM"), 372), ("KEY_ZOOMIN", 418), ("KEY_ZOOMOUT", 419), ("KEY_ZOOMRESET", 420), From 4b8fa71d3c0123916138d19729e13caee03ab47f Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sat, 25 Jan 2025 21:23:11 +0100 Subject: [PATCH 243/270] Fix docs --- docs/changelog.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 15ba749..92f4e23 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,7 +4,7 @@ Changelog 1.8.0 (Jan 25, 2025) ================== -- Binary wheels are now provided by the `evdev-binary http://pypi.python.org/pypi/evdev-binary`_ package. +- Binary wheels are now provided by the `evdev-binary `_ package. The package is compiled on manylinux_2_28 against kernel 4.18. - The ``evdev.ecodes`` module is now generated at install time and contains only constants. This allows type @@ -12,16 +12,16 @@ Changelog module is available as ``evdev.ecodes_runtime``. In case generation of the static ``ecodes.py`` fails, the install process falls back to using ``ecodes_runtime.py`` as ``ecodes.py``. -- Reverse mappings in ``evdev.ecodes`` that point to more than one value are now tuples and not lists. For example:: +- Reverse mappings in ``evdev.ecodes`` that point to more than one value are now tuples instead of lists. For example:: >>> ecodes.KEY[153] - 153: ('KEY_DIRECTION', 'KEY_ROTATE_DISPLAY'), + ('KEY_DIRECTION', 'KEY_ROTATE_DISPLAY') -- Minimum Python version raised to Python 3.8. +- Raise the minimum supported Python version to 3.8. -- Fix keyboard delay and repeat being swapped. +- Fix keyboard delay and repeat being swapped (#227). -- Move `syn()` convenience method from `InputDevice` to `EventIO`. +- Move the ``syn()`` convenience method from ``InputDevice`` to ``EventIO`` (#224). 1.7.1 (May 8, 2024) @@ -41,7 +41,7 @@ Changelog - Add the uniq address to the string representation of ``InputDevice``. -- Improved method for finding the device node corresponding to a uinput device (`#206 https://github.com/gvalkov/python-evdev/pull/206`_). +- Improved method for finding the device node corresponding to a uinput device (`#206 `_). - Repository TLC (reformatted with ruff, fixed linting warnings, moved packaging metadata to ``pyproject.toml`` etc.). From 3ff9816e08be95b331ea9dadc18fc17f1e04e272 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 2 Feb 2025 14:19:12 +0100 Subject: [PATCH 244/270] Fix ecodes.c generation Header files passed to genecodes_c.py were ignored after commit 4a0efdd. --- evdev/genecodes_c.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/evdev/genecodes_c.py b/evdev/genecodes_c.py index 68b7dea..dd3ee91 100644 --- a/evdev/genecodes_c.py +++ b/evdev/genecodes_c.py @@ -20,6 +20,9 @@ print("usage: genecodes.py [--ecodes|--stubs] ") exit(2) +if args: + headers = args + # ----------------------------------------------------------------------------- macro_regex = r"#define +((?:KEY|ABS|REL|SW|MSC|LED|BTN|REP|SND|ID|EV|BUS|SYN|FF|UI_FF|INPUT_PROP)_\w+)" From 61beda72e7b101e270f914d5f1d633730e60d083 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 2 Feb 2025 17:25:16 +0100 Subject: [PATCH 245/270] Move from flat-layout to src-layout --- .gitignore | 12 ++++++------ MANIFEST.in | 4 ++-- docs/conf.py | 10 ++++------ pyproject.toml | 3 --- setup.py | 14 +++++++------- {evdev => src/evdev}/__init__.py | 0 {evdev => src/evdev}/device.py | 0 {evdev => src/evdev}/ecodes.py | 0 {evdev => src/evdev}/ecodes_runtime.py | 0 {evdev => src/evdev}/eventio.py | 0 {evdev => src/evdev}/eventio_async.py | 0 {evdev => src/evdev}/events.py | 0 {evdev => src/evdev}/evtest.py | 0 {evdev => src/evdev}/ff.py | 0 {evdev => src/evdev}/genecodes_c.py | 0 {evdev => src/evdev}/genecodes_py.py | 0 {evdev => src/evdev}/input.c | 0 {evdev => src/evdev}/uinput.c | 0 {evdev => src/evdev}/uinput.py | 0 {evdev => src/evdev}/util.py | 0 20 files changed, 19 insertions(+), 24 deletions(-) rename {evdev => src/evdev}/__init__.py (100%) rename {evdev => src/evdev}/device.py (100%) rename {evdev => src/evdev}/ecodes.py (100%) rename {evdev => src/evdev}/ecodes_runtime.py (100%) rename {evdev => src/evdev}/eventio.py (100%) rename {evdev => src/evdev}/eventio_async.py (100%) rename {evdev => src/evdev}/events.py (100%) rename {evdev => src/evdev}/evtest.py (100%) rename {evdev => src/evdev}/ff.py (100%) rename {evdev => src/evdev}/genecodes_c.py (100%) rename {evdev => src/evdev}/genecodes_py.py (100%) rename {evdev => src/evdev}/input.c (100%) rename {evdev => src/evdev}/uinput.c (100%) rename {evdev => src/evdev}/uinput.py (100%) rename {evdev => src/evdev}/util.py (100%) diff --git a/.gitignore b/.gitignore index 3e244aa..557f265 100644 --- a/.gitignore +++ b/.gitignore @@ -17,10 +17,10 @@ __pycache__ .pytest_cache .ruff_cache -evdev/*.so -evdev/ecodes.c -evdev/ecodes.pyi +src/evdev/*.so +src/evdev/ecodes.c +src/evdev/ecodes.pyi docs/_build -evdev/_ecodes.py -evdev/_input.py -evdev/_uinput.py +src/evdev/_ecodes.py +src/evdev/_input.py +src/evdev/_uinput.py diff --git a/MANIFEST.in b/MANIFEST.in index bcbbd6c..be2be3d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,5 @@ # The _ecodes extension module source file needs to be generated against the # evdev headers of the running kernel. Refer to the 'build_ecodes' distutils # command in setup.py. -exclude evdev/ecodes.c -include evdev/ecodes.py +exclude src/evdev/ecodes.c +include src/evdev/ecodes.py diff --git a/docs/conf.py b/docs/conf.py index 7af99b9..53b5206 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import os import sys import sphinx_rtd_theme @@ -13,7 +11,7 @@ # Trick autodoc into running without having built the extension modules. if on_rtd: - with open("../evdev/_ecodes.py", "w") as fh: + with open("../src/evdev/_ecodes.py", "w") as fh: fh.write( """ KEY = ABS = REL = SW = MSC = LED = REP = SND = SYN = FF = FF_STATUS = BTN_A = KEY_A = 1 @@ -22,9 +20,9 @@ KEY_MAX, KEY_CNT = 1, 2""" ) - with open("../evdev/_input.py", "w"): + with open("../src/evdev/_input.py", "w"): pass - with open("../evdev/_uinput.py", "w"): + with open("../src/evdev/_uinput.py", "w"): pass @@ -60,7 +58,7 @@ # General information about the project. project = "python-evdev" -copyright = "2012-2024, Georgi Valkov and contributors" +copyright = "2012-2025, Georgi Valkov and contributors" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/pyproject.toml b/pyproject.toml index e5e5d00..7854d91 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,9 +29,6 @@ classifiers = [ [project.urls] "Homepage" = "https://github.com/gvalkov/python-evdev" -[tool.setuptools] -packages = ["evdev"] - [tool.ruff] line-length = 120 diff --git a/setup.py b/setup.py index e6e0c1d..c5ab4a0 100755 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ curdir = Path(__file__).resolve().parent -ecodes_c_path = curdir / "evdev/ecodes.c" +ecodes_c_path = curdir / "src/evdev/ecodes.c" def create_ecodes(headers=None): @@ -60,7 +60,7 @@ def create_ecodes(headers=None): print("writing %s (using %s)" % (ecodes_c_path, " ".join(headers))) with ecodes_c_path.open("w") as fh: - cmd = [sys.executable, "evdev/genecodes_c.py", "--ecodes", *headers] + cmd = [sys.executable, "src/evdev/genecodes_c.py", "--ecodes", *headers] run(cmd, check=True, stdout=fh) @@ -93,12 +93,12 @@ def generate_ecodes_py(self): ecodes_py = Path(self.build_lib) / "evdev/ecodes.py" print(f"writing {ecodes_py}") with ecodes_py.open("w") as fh: - cmd = [sys.executable, "-B", "evdev/genecodes_py.py"] + cmd = [sys.executable, "-B", "src/evdev/genecodes_py.py"] res = run(cmd, env={"PYTHONPATH": self.build_lib}, stdout=fh) if res.returncode != 0: print(f"failed to generate static {ecodes_py} - will use ecodes_runtime.py") - shutil.copy("evdev/ecodes_runtime.py", ecodes_py) + shutil.copy("src/evdev/ecodes_runtime.py", ecodes_py) def run(self): for cmd_name in self.get_sub_commands(): @@ -112,9 +112,9 @@ def run(self): cflags = ["-std=c99", "-Wno-error=declaration-after-statement"] setup( ext_modules=[ - Extension("evdev._input", sources=["evdev/input.c"], extra_compile_args=cflags), - Extension("evdev._uinput", sources=["evdev/uinput.c"], extra_compile_args=cflags), - Extension("evdev._ecodes", sources=["evdev/ecodes.c"], extra_compile_args=cflags), + Extension("evdev._input", sources=["src/evdev/input.c"], extra_compile_args=cflags), + Extension("evdev._uinput", sources=["src/evdev/uinput.c"], extra_compile_args=cflags), + Extension("evdev._ecodes", sources=["src/evdev/ecodes.c"], extra_compile_args=cflags), ], cmdclass={ "build_ext": build_ext, diff --git a/evdev/__init__.py b/src/evdev/__init__.py similarity index 100% rename from evdev/__init__.py rename to src/evdev/__init__.py diff --git a/evdev/device.py b/src/evdev/device.py similarity index 100% rename from evdev/device.py rename to src/evdev/device.py diff --git a/evdev/ecodes.py b/src/evdev/ecodes.py similarity index 100% rename from evdev/ecodes.py rename to src/evdev/ecodes.py diff --git a/evdev/ecodes_runtime.py b/src/evdev/ecodes_runtime.py similarity index 100% rename from evdev/ecodes_runtime.py rename to src/evdev/ecodes_runtime.py diff --git a/evdev/eventio.py b/src/evdev/eventio.py similarity index 100% rename from evdev/eventio.py rename to src/evdev/eventio.py diff --git a/evdev/eventio_async.py b/src/evdev/eventio_async.py similarity index 100% rename from evdev/eventio_async.py rename to src/evdev/eventio_async.py diff --git a/evdev/events.py b/src/evdev/events.py similarity index 100% rename from evdev/events.py rename to src/evdev/events.py diff --git a/evdev/evtest.py b/src/evdev/evtest.py similarity index 100% rename from evdev/evtest.py rename to src/evdev/evtest.py diff --git a/evdev/ff.py b/src/evdev/ff.py similarity index 100% rename from evdev/ff.py rename to src/evdev/ff.py diff --git a/evdev/genecodes_c.py b/src/evdev/genecodes_c.py similarity index 100% rename from evdev/genecodes_c.py rename to src/evdev/genecodes_c.py diff --git a/evdev/genecodes_py.py b/src/evdev/genecodes_py.py similarity index 100% rename from evdev/genecodes_py.py rename to src/evdev/genecodes_py.py diff --git a/evdev/input.c b/src/evdev/input.c similarity index 100% rename from evdev/input.c rename to src/evdev/input.c diff --git a/evdev/uinput.c b/src/evdev/uinput.c similarity index 100% rename from evdev/uinput.c rename to src/evdev/uinput.c diff --git a/evdev/uinput.py b/src/evdev/uinput.py similarity index 100% rename from evdev/uinput.py rename to src/evdev/uinput.py diff --git a/evdev/util.py b/src/evdev/util.py similarity index 100% rename from evdev/util.py rename to src/evdev/util.py From 64c6555101b5172d4194c7f92728c1638c613200 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 2 Feb 2025 18:12:47 +0100 Subject: [PATCH 246/270] Optimize reading of events - Construct tuple directly instead of using Py_BuildValue. - Return a tuple of tuples instead of a list of tuples. - Read argument (fd) directly instead of using PyArg_ParseTuple. --- src/evdev/input.c | 43 +++++++++++++++++-------------------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/src/evdev/input.c b/src/evdev/input.c index 0256745..55e6808 100644 --- a/src/evdev/input.c +++ b/src/evdev/input.c @@ -46,12 +46,10 @@ int test_bit(const char* bitmask, int bit) { static PyObject * device_read(PyObject *self, PyObject *args) { - int fd; struct input_event event; // get device file descriptor (O_RDONLY|O_NONBLOCK) - if (PyArg_ParseTuple(args, "i", &fd) < 0) - return NULL; + int fd = (int)PyLong_AsLong(PyTuple_GET_ITEM(args, 0)); int n = read(fd, &event, sizeof(event)); @@ -68,12 +66,9 @@ device_read(PyObject *self, PyObject *args) PyObject* sec = PyLong_FromLong(event.input_event_sec); PyObject* usec = PyLong_FromLong(event.input_event_usec); PyObject* val = PyLong_FromLong(event.value); - PyObject* py_input_event = NULL; - - py_input_event = Py_BuildValue("OOhhO", sec, usec, event.type, event.code, val); - Py_DECREF(sec); - Py_DECREF(usec); - Py_DECREF(val); + PyObject* type = PyLong_FromLong(event.type); + PyObject* code = PyLong_FromLong(event.code); + PyObject* py_input_event = PyTuple_Pack(5, sec, usec, type, code, val); return py_input_event; } @@ -83,17 +78,16 @@ device_read(PyObject *self, PyObject *args) static PyObject * device_read_many(PyObject *self, PyObject *args) { - int fd; - // get device file descriptor (O_RDONLY|O_NONBLOCK) - int ret = PyArg_ParseTuple(args, "i", &fd); - if (!ret) return NULL; + int fd = (int)PyLong_AsLong(PyTuple_GET_ITEM(args, 0)); - PyObject* event_list = PyList_New(0); PyObject* py_input_event = NULL; + PyObject* events = NULL; PyObject* sec = NULL; PyObject* usec = NULL; PyObject* val = NULL; + PyObject* type = NULL; + PyObject* code = NULL; struct input_event event[64]; @@ -102,26 +96,24 @@ device_read_many(PyObject *self, PyObject *args) if (nread < 0) { PyErr_SetFromErrno(PyExc_OSError); - Py_DECREF(event_list); return NULL; } - // Construct a list of event tuples, which we'll make sense of in Python - for (unsigned i = 0 ; i < nread/event_size ; i++) { + // Construct a tuple of event tuples. Each tuple is the arguments to InputEvent. + size_t num_events = nread / event_size; + events = PyTuple_New(num_events); + for (size_t i = 0 ; i < num_events; i++) { sec = PyLong_FromLong(event[i].input_event_sec); usec = PyLong_FromLong(event[i].input_event_usec); val = PyLong_FromLong(event[i].value); + type = PyLong_FromLong(event[i].type); + code = PyLong_FromLong(event[i].code); - py_input_event = Py_BuildValue("OOhhO", sec, usec, event[i].type, event[i].code, val); - PyList_Append(event_list, py_input_event); - - Py_DECREF(py_input_event); - Py_DECREF(sec); - Py_DECREF(usec); - Py_DECREF(val); + py_input_event = PyTuple_Pack(5, sec, usec, type, code, val); + PyTuple_SET_ITEM(events, i, py_input_event); } - return event_list; + return events; } @@ -539,7 +531,6 @@ ioctl_EVIOCGPROP(PyObject *self, PyObject *args) } - static PyMethodDef MethodTable[] = { { "ioctl_devinfo", ioctl_devinfo, METH_VARARGS, "fetch input device info" }, { "ioctl_capabilities", ioctl_capabilities, METH_VARARGS, "fetch input device capabilities" }, From 0487652d1cb8f32c0f4a3830753b66cc12bab4c5 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 2 Feb 2025 18:25:22 +0100 Subject: [PATCH 247/270] Drop Python 2 support from input.c and uinput.c --- src/evdev/input.c | 24 ++---------------------- src/evdev/uinput.c | 25 ++----------------------- 2 files changed, 4 insertions(+), 45 deletions(-) diff --git a/src/evdev/input.c b/src/evdev/input.c index 55e6808..cfce67c 100644 --- a/src/evdev/input.c +++ b/src/evdev/input.c @@ -552,14 +552,10 @@ static PyMethodDef MethodTable[] = { }; -#define MODULE_NAME "_input" -#define MODULE_HELP "Python bindings to certain linux input subsystem functions" - -#if PY_MAJOR_VERSION >= 3 static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, - MODULE_NAME, - MODULE_HELP, + "_input", + "Python bindings to certain linux input subsystem functions", -1, /* m_size */ MethodTable, /* m_methods */ NULL, /* m_reload */ @@ -581,19 +577,3 @@ PyInit__input(void) { return moduleinit(); } - -#else -static PyObject * -moduleinit(void) -{ - PyObject* m = Py_InitModule3(MODULE_NAME, MethodTable, MODULE_HELP); - if (m == NULL) return NULL; - return m; -} - -PyMODINIT_FUNC -init_input(void) -{ - moduleinit(); -} -#endif diff --git a/src/evdev/uinput.c b/src/evdev/uinput.c index 3494705..8d2c096 100644 --- a/src/evdev/uinput.c +++ b/src/evdev/uinput.c @@ -356,8 +356,6 @@ int _uinput_end_erase(int fd, struct uinput_ff_erase *upload) return ioctl(fd, UI_END_FF_ERASE, upload); } -#define MODULE_NAME "_uinput" -#define MODULE_HELP "Python bindings for parts of linux/uinput.c" static PyMethodDef MethodTable[] = { { "open", uinput_open, METH_VARARGS, @@ -390,11 +388,10 @@ static PyMethodDef MethodTable[] = { { NULL, NULL, 0, NULL} }; -#if PY_MAJOR_VERSION >= 3 static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, - MODULE_NAME, - MODULE_HELP, + "_uinput", + "Python bindings for parts of linux/uinput.c", -1, /* m_size */ MethodTable, /* m_methods */ NULL, /* m_reload */ @@ -418,21 +415,3 @@ PyInit__uinput(void) { return moduleinit(); } - -#else -static PyObject * -moduleinit(void) -{ - PyObject* m = Py_InitModule3(MODULE_NAME, MethodTable, MODULE_HELP); - if (m == NULL) return NULL; - - PyModule_AddIntConstant(m, "maxnamelen", UINPUT_MAX_NAME_SIZE); - return m; -} - -PyMODINIT_FUNC -init_uinput(void) -{ - moduleinit(); -} -#endif From 3e6fd3e24218d868d8c8d1c08b475574b062d08f Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 2 Feb 2025 18:44:40 +0100 Subject: [PATCH 248/270] Update changelog and requirements --- .gitignore | 2 ++ docs/changelog.rst | 10 +++++++++- requirements-dev.txt | 1 + 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 557f265..70ac303 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,8 @@ TAGS __pycache__ .pytest_cache .ruff_cache +.venv +uv.lock src/evdev/*.so src/evdev/ecodes.c diff --git a/docs/changelog.rst b/docs/changelog.rst index 92f4e23..8320eae 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,9 +1,17 @@ Changelog --------- -1.8.0 (Jan 25, 2025) +1.9.0 (Unreleased) ================== +- Fix ``CPATH/C_INCLUDE_PATH`` being ignored during build. + +- Slightly faster reading of events. + + +1.8.0 (Jan 25, 2025) +==================== + - Binary wheels are now provided by the `evdev-binary `_ package. The package is compiled on manylinux_2_28 against kernel 4.18. diff --git a/requirements-dev.txt b/requirements-dev.txt index 96366e6..725ad7f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,3 +7,4 @@ bump-my-version ~= 0.17.4 build twine cibuildwheel +setuptools From 78650f8f50f6a51fe98af9af54d42a121f602e5e Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 2 Feb 2025 20:49:43 +0100 Subject: [PATCH 249/270] FreeBSD related fixes --- docs/changelog.rst | 2 ++ setup.py | 7 ++++++- src/evdev/genecodes_c.py | 3 ++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 8320eae..20e7293 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -8,6 +8,8 @@ Changelog - Slightly faster reading of events. +- Fix build on FreeBSD. + 1.8.0 (Jan 25, 2025) ==================== diff --git a/setup.py b/setup.py index c5ab4a0..6b721d7 100755 --- a/setup.py +++ b/setup.py @@ -2,6 +2,7 @@ import sys import shutil import textwrap +import platform from pathlib import Path from subprocess import run @@ -25,7 +26,11 @@ def create_ecodes(headers=None): include_paths.update(c_inc_path.split(":")) include_paths.add("/usr/include") - files = ["linux/input.h", "linux/input-event-codes.h", "linux/uinput.h"] + if platform.system().lower() == "freebsd": + files = ["dev/evdev/input.h", "dev/evdev/input-event-codes.h", "dev/evdev/uinput.h"] + else: + files = ["linux/input.h", "linux/input-event-codes.h", "linux/uinput.h"] + headers = [os.path.join(path, file) for path in include_paths for file in files] headers = [header for header in headers if os.path.isfile(header)] diff --git a/src/evdev/genecodes_c.py b/src/evdev/genecodes_c.py index dd3ee91..5c2d946 100644 --- a/src/evdev/genecodes_c.py +++ b/src/evdev/genecodes_c.py @@ -25,7 +25,7 @@ # ----------------------------------------------------------------------------- -macro_regex = r"#define +((?:KEY|ABS|REL|SW|MSC|LED|BTN|REP|SND|ID|EV|BUS|SYN|FF|UI_FF|INPUT_PROP)_\w+)" +macro_regex = r"#define\s+((?:KEY|ABS|REL|SW|MSC|LED|BTN|REP|SND|ID|EV|BUS|SYN|FF|UI_FF|INPUT_PROP)_\w+)" macro_regex = re.compile(macro_regex) # Uname without hostname. @@ -38,6 +38,7 @@ #include #ifdef __FreeBSD__ #include +#include #else #include #include From 5478d94359801adb73d642c412cecd2042677230 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 2 Feb 2025 22:31:20 +0100 Subject: [PATCH 250/270] Give SYN_DROPPED special treatment in evtest and fix alignment --- src/evdev/eventio.py | 2 +- src/evdev/evtest.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/evdev/eventio.py b/src/evdev/eventio.py index 5478f02..5735d18 100644 --- a/src/evdev/eventio.py +++ b/src/evdev/eventio.py @@ -66,7 +66,7 @@ def read(self): `BlockingIOError` if there are no available events at the moment. """ - # events -> [(sec, usec, type, code, val), ...] + # events -> ((sec, usec, type, code, val), ...) events = _input.device_read_many(self.fd) for event in events: diff --git a/src/evdev/evtest.py b/src/evdev/evtest.py index 26e62ad..b0244a9 100644 --- a/src/evdev/evtest.py +++ b/src/evdev/evtest.py @@ -149,9 +149,11 @@ def print_capabilities(device): def print_event(e): if e.type == ecodes.EV_SYN: if e.code == ecodes.SYN_MT_REPORT: - msg = "time {:<16} +++++++++ {} ++++++++" + msg = "time {:<17} +++++++++++++ {} +++++++++++++" + elif e.code == ecodes.SYN_DROPPED: + msg = "time {:<17} !!!!!!!!!!!!! {} !!!!!!!!!!!!!" else: - msg = "time {:<16} --------- {} --------" + msg = "time {:<17} ------------- {} -------------" print(msg.format(e.timestamp(), ecodes.SYN[e.code])) else: if e.type in ecodes.bytype: @@ -159,7 +161,7 @@ def print_event(e): else: codename = "?" - evfmt = "time {:<16} type {} ({}), code {:<4} ({}), value {}" + evfmt = "time {:<17} type {} ({}), code {:<4} ({}), value {}" print(evfmt.format(e.timestamp(), e.type, ecodes.EV[e.type], e.code, codename, e.value)) From 1f083add5e5c377351db976c28fd477507dd7286 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 2 Feb 2025 22:33:17 +0100 Subject: [PATCH 251/270] Drop deprecated InputDevice.fn --- docs/changelog.rst | 2 ++ src/evdev/device.py | 9 --------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 20e7293..6ad25ed 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -10,6 +10,8 @@ Changelog - Fix build on FreeBSD. +- Drop deprecated ``InputDevice.fn`` (use ``InputDevice.path`` instead). + 1.8.0 (Jan 25, 2025) ==================== diff --git a/src/evdev/device.py b/src/evdev/device.py index 7675a2d..fdd8363 100644 --- a/src/evdev/device.py +++ b/src/evdev/device.py @@ -1,9 +1,6 @@ -# encoding: utf-8 - import collections import contextlib import os -import warnings from . import _input, ecodes, util @@ -383,12 +380,6 @@ def active_keys(self, verbose=False): return active_keys - @property - def fn(self): - msg = "Please use {0}.path instead of {0}.fn".format(self.__class__.__name__) - warnings.warn(msg, DeprecationWarning, stacklevel=2) - return self.path - def absinfo(self, axis_num): """ Return current :class:`AbsInfo` for input device axis From e71716192675fba399cca083e87ac3db64327ca6 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 2 Feb 2025 23:49:30 +0100 Subject: [PATCH 252/270] Use REP_ constants in input.c --- src/evdev/input.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/evdev/input.c b/src/evdev/input.c index cfce67c..4ad0408 100644 --- a/src/evdev/input.c +++ b/src/evdev/input.c @@ -301,7 +301,7 @@ static PyObject * ioctl_EVIOCGREP(PyObject *self, PyObject *args) { int fd, ret; - unsigned int rep[2] = {0}; + unsigned int rep[REP_CNT] = {0}; ret = PyArg_ParseTuple(args, "i", &fd); if (!ret) return NULL; @@ -309,7 +309,7 @@ ioctl_EVIOCGREP(PyObject *self, PyObject *args) if (ret == -1) return NULL; - return Py_BuildValue("(ii)", rep[0], rep[1]); + return Py_BuildValue("(ii)", rep[REP_DELAY], rep[REP_PERIOD]); } @@ -317,7 +317,7 @@ static PyObject * ioctl_EVIOCSREP(PyObject *self, PyObject *args) { int fd, ret; - unsigned int rep[2] = {0}; + unsigned int rep[REP_CNT] = {0}; ret = PyArg_ParseTuple(args, "iii", &fd, &rep[0], &rep[1]); if (!ret) return NULL; From 59dc614a0b3a7046d611aa3ef5f5ee04b4050580 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sun, 2 Feb 2025 23:51:22 +0100 Subject: [PATCH 253/270] More type hints --- docs/changelog.rst | 2 ++ src/evdev/device.py | 74 ++++++++++++++++++++++++-------------------- src/evdev/eventio.py | 9 +++--- src/evdev/uinput.py | 65 +++++++++++++++++++++----------------- src/evdev/util.py | 9 +++--- 5 files changed, 87 insertions(+), 72 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 6ad25ed..5dfeaab 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -12,6 +12,8 @@ Changelog - Drop deprecated ``InputDevice.fn`` (use ``InputDevice.path`` instead). +- More type hints. + 1.8.0 (Jan 25, 2025) ==================== diff --git a/src/evdev/device.py b/src/evdev/device.py index fdd8363..73b0acb 100644 --- a/src/evdev/device.py +++ b/src/evdev/device.py @@ -1,6 +1,6 @@ -import collections import contextlib import os +from typing import NamedTuple, Tuple, Union from . import _input, ecodes, util @@ -10,18 +10,10 @@ from .eventio import EvdevError, EventIO -# -------------------------------------------------------------------------- -_AbsInfo = collections.namedtuple("AbsInfo", ["value", "min", "max", "fuzz", "flat", "resolution"]) - -_KbdInfo = collections.namedtuple("KbdInfo", ["delay", "repeat"]) - -_DeviceInfo = collections.namedtuple("DeviceInfo", ["bustype", "vendor", "product", "version"]) - - -class AbsInfo(_AbsInfo): +class AbsInfo(NamedTuple): """Absolute axis information. - A ``namedtuple`` used for storing absolute axis information - + A ``namedtuple`` with absolute axis information - corresponds to the ``input_absinfo`` struct: Attributes @@ -57,11 +49,18 @@ class AbsInfo(_AbsInfo): """ + value: int + min: int + max: int + fuzz: int + flat: int + resolution: int + def __str__(self): - return "val {}, min {}, max {}, fuzz {}, flat {}, res {}".format(*self) + return "value {}, min {}, max {}, fuzz {}, flat {}, res {}".format(*self) # pylint: disable=not-an-iterable -class KbdInfo(_KbdInfo): +class KbdInfo(NamedTuple): """Keyboard repeat rate. Attributes @@ -74,11 +73,14 @@ class KbdInfo(_KbdInfo): Keyboard repeat rate in characters per second. """ + delay: int + repeat: int + def __str__(self): - return "delay {}, repeat {}".format(*self) + return "delay {}, repeat {}".format(self.delay, self.repeat) -class DeviceInfo(_DeviceInfo): +class DeviceInfo(NamedTuple): """ Attributes ---------- @@ -88,9 +90,14 @@ class DeviceInfo(_DeviceInfo): version """ + bustype: int + vendor: int + product: int + version: int + def __str__(self): msg = "bus: {:04x}, vendor {:04x}, product {:04x}, version {:04x}" - return msg.format(*self) + return msg.format(*self) # pylint: disable=not-an-iterable class InputDevice(EventIO): @@ -100,7 +107,7 @@ class InputDevice(EventIO): __slots__ = ("path", "fd", "info", "name", "phys", "uniq", "_rawcapabilities", "version", "ff_effects_count") - def __init__(self, dev): + def __init__(self, dev: Union[str, bytes, os.PathLike]): """ Arguments --------- @@ -111,15 +118,14 @@ def __init__(self, dev): #: Path to input device. self.path = dev if not hasattr(dev, "__fspath__") else dev.__fspath__() - # Certain operations are possible only when the device is opened in - # read-write mode. + # Certain operations are possible only when the device is opened in read-write mode. try: fd = os.open(dev, os.O_RDWR | os.O_NONBLOCK) except OSError: fd = os.open(dev, os.O_RDONLY | os.O_NONBLOCK) #: A non-blocking file descriptor to the device file. - self.fd = fd + self.fd: int = fd # Returns (bustype, vendor, product, version, name, phys, capabilities). info_res = _input.ioctl_devinfo(self.fd) @@ -128,16 +134,16 @@ def __init__(self, dev): self.info = DeviceInfo(*info_res[:4]) #: The name of the event device. - self.name = info_res[4] + self.name: str = info_res[4] #: The physical topology of the device. - self.phys = info_res[5] + self.phys: str = info_res[5] #: The unique identifier of the device. - self.uniq = info_res[6] + self.uniq: str = info_res[6] #: The evdev protocol version. - self.version = _input.ioctl_EVIOCGVERSION(self.fd) + self.version: int = _input.ioctl_EVIOCGVERSION(self.fd) #: The raw dictionary of device capabilities - see `:func:capabilities()`. self._rawcapabilities = _input.ioctl_capabilities(self.fd) @@ -152,7 +158,7 @@ def __del__(self): except (OSError, ImportError, AttributeError): pass - def _capabilities(self, absinfo=True): + def _capabilities(self, absinfo: bool = True): res = {} for etype, _ecodes in self._rawcapabilities.items(): @@ -170,7 +176,7 @@ def _capabilities(self, absinfo=True): return res - def capabilities(self, verbose=False, absinfo=True): + def capabilities(self, verbose: bool = False, absinfo: bool = True): """ Return the event types that this device supports as a mapping of supported event types to lists of handled event codes. @@ -215,7 +221,7 @@ def capabilities(self, verbose=False, absinfo=True): else: return self._capabilities(absinfo) - def input_props(self, verbose=False): + def input_props(self, verbose: bool = False): """ Get device properties and quirks. @@ -236,7 +242,7 @@ def input_props(self, verbose=False): return props - def leds(self, verbose=False): + def leds(self, verbose: bool = False): """ Return currently set LED keys. @@ -257,7 +263,7 @@ def leds(self, verbose=False): return leds - def set_led(self, led_num, value): + def set_led(self, led_num: int, value: int): """ Set the state of the selected LED. @@ -327,7 +333,7 @@ def grab_context(self): yield self.ungrab() - def upload_effect(self, effect): + def upload_effect(self, effect: "ff.Effect"): """ Upload a force feedback effect to a force feedback device. """ @@ -354,10 +360,10 @@ def repeat(self): return KbdInfo(*_input.ioctl_EVIOCGREP(self.fd)) @repeat.setter - def repeat(self, value): + def repeat(self, value: Tuple[int, int]): return _input.ioctl_EVIOCSREP(self.fd, *value) - def active_keys(self, verbose=False): + def active_keys(self, verbose: bool = False): """ Return currently active keys. @@ -380,7 +386,7 @@ def active_keys(self, verbose=False): return active_keys - def absinfo(self, axis_num): + def absinfo(self, axis_num: int): """ Return current :class:`AbsInfo` for input device axis @@ -396,7 +402,7 @@ def absinfo(self, axis_num): """ return AbsInfo(*_input.ioctl_EVIOCGABS(self.fd, axis_num)) - def set_absinfo(self, axis_num, value=None, min=None, max=None, fuzz=None, flat=None, resolution=None): + def set_absinfo(self, axis_num: int, value=None, min=None, max=None, fuzz=None, flat=None, resolution=None): """ Update :class:`AbsInfo` values. Only specified values will be overwritten. diff --git a/src/evdev/eventio.py b/src/evdev/eventio.py index 5735d18..27bba9d 100644 --- a/src/evdev/eventio.py +++ b/src/evdev/eventio.py @@ -2,6 +2,7 @@ import functools import os import select +from typing import Iterator from . import _input, _uinput, ecodes from .events import InputEvent @@ -35,7 +36,7 @@ def fileno(self): """ return self.fd - def read_loop(self): + def read_loop(self) -> Iterator[InputEvent]: """ Enter an endless :func:`select.select()` loop that yields input events. """ @@ -45,7 +46,7 @@ def read_loop(self): for event in self.read(): yield event - def read_one(self): + def read_one(self) -> InputEvent: """ Read and return a single input event as an instance of :class:`InputEvent `. @@ -59,7 +60,7 @@ def read_one(self): if event: return InputEvent(*event) - def read(self): + def read(self) -> Iterator[InputEvent]: """ Read multiple input events from device. Return a generator object that yields :class:`InputEvent ` instances. Raises @@ -114,7 +115,7 @@ def write_event(self, event): self.write(event.type, event.code, event.value) @need_write - def write(self, etype, code, value): + def write(self, etype: int, code: int, value: int): """ Inject an input event into the input subsystem. Events are queued until a synchronization event is received. diff --git a/src/evdev/uinput.py b/src/evdev/uinput.py index 9567374..2c69c2b 100644 --- a/src/evdev/uinput.py +++ b/src/evdev/uinput.py @@ -5,8 +5,10 @@ import stat import time from collections import defaultdict +from typing import Union, Tuple, Dict, Sequence, Optional -from . import _uinput, device, ecodes, ff, util +from . import _uinput, ecodes, ff, util +from .device import InputDevice, AbsInfo from .events import InputEvent try: @@ -38,7 +40,12 @@ class UInput(EventIO): ) @classmethod - def from_device(cls, *devices, filtered_types=(ecodes.EV_SYN, ecodes.EV_FF), **kwargs): + def from_device( + cls, + *devices: Union[InputDevice, Union[str, bytes, os.PathLike]], + filtered_types: Tuple[int] = (ecodes.EV_SYN, ecodes.EV_FF), + **kwargs, + ): """ Create an UInput device with the capabilities of one or more input devices. @@ -57,8 +64,8 @@ def from_device(cls, *devices, filtered_types=(ecodes.EV_SYN, ecodes.EV_FF), **k device_instances = [] for dev in devices: - if not isinstance(dev, device.InputDevice): - dev = device.InputDevice(str(dev)) + if not isinstance(dev, InputDevice): + dev = InputDevice(str(dev)) device_instances.append(dev) all_capabilities = defaultdict(set) @@ -79,14 +86,14 @@ def from_device(cls, *devices, filtered_types=(ecodes.EV_SYN, ecodes.EV_FF), **k def __init__( self, - events=None, - name="py-evdev-uinput", - vendor=0x1, - product=0x1, - version=0x1, - bustype=0x3, - devnode="/dev/uinput", - phys="py-evdev-uinput", + events: Optional[Dict[int, Sequence[int]]] = None, + name: str = "py-evdev-uinput", + vendor: int = 0x1, + product: int = 0x1, + version: int = 0x1, + bustype: int = 0x3, + devnode: str = "/dev/uinput", + phys: str = "py-evdev-uinput", input_props=None, # CentOS 7 has sufficiently old headers that FF_MAX_EFFECTS is not defined there, # which causes the whole module to fail loading. Fallback on a hardcoded value of @@ -131,13 +138,13 @@ def __init__( to inject only ``KEY_*`` and ``BTN_*`` event codes. """ - self.name = name #: Uinput device name. - self.vendor = vendor #: Device vendor identifier. - self.product = product #: Device product identifier. - self.version = version #: Device version identifier. - self.bustype = bustype #: Device bustype - e.g. ``BUS_USB``. - self.phys = phys #: Uinput device physical path. - self.devnode = devnode #: Uinput device node - e.g. ``/dev/uinput/``. + self.name: str = name #: Uinput device name. + self.vendor: int = vendor #: Device vendor identifier. + self.product: int = product #: Device product identifier. + self.version: int = version #: Device version identifier. + self.bustype: int = bustype #: Device bustype - e.g. ``BUS_USB``. + self.phys: str = phys #: Uinput device physical path. + self.devnode: str = devnode #: Uinput device node - e.g. ``/dev/uinput/``. if not events: events = {ecodes.EV_KEY: ecodes.keys.keys()} @@ -173,7 +180,7 @@ def __init__( #: An :class:`InputDevice ` instance #: for the fake input device. ``None`` if the device cannot be #: opened for reading and writing. - self.device = self._find_device(self.fd) + self.device: InputDevice = self._find_device(self.fd) def _prepare_events(self, events): """Prepare events for passing to _uinput.enable and _uinput.setup""" @@ -181,7 +188,7 @@ def _prepare_events(self, events): for etype, codes in events.items(): for code in codes: # Handle max, min, fuzz, flat. - if isinstance(code, (tuple, list, device.AbsInfo)): + if isinstance(code, (tuple, list, AbsInfo)): # Flatten (ABS_Y, (0, 255, 0, 0, 0, 0)) to (ABS_Y, 0, 255, 0, 0, 0, 0). f = [code[0]] f.extend(code[1]) @@ -206,7 +213,7 @@ def __repr__(self): return "{}({})".format(self.__class__.__name__, ", ".join(v)) def __str__(self): - msg = 'name "{}", bus "{}", vendor "{:04x}", product "{:04x}", version "{:04x}", phys "{}"\n' "event types: {}" + msg = 'name "{}", bus "{}", vendor "{:04x}", product "{:04x}", version "{:04x}", phys "{}"\nevent types: {}' evtypes = [i[0] for i in self.capabilities(True).keys()] msg = msg.format( @@ -225,7 +232,7 @@ def close(self): _uinput.close(self.fd) self.fd = -1 - def capabilities(self, verbose=False, absinfo=True): + def capabilities(self, verbose: bool = False, absinfo: bool = True): """See :func:`capabilities `.""" if self.device is None: raise UInputError("input device not opened - cannot read capabilities") @@ -281,7 +288,7 @@ def _verify(self): msg = "uinput device name must not be longer than {} characters" raise UInputError(msg.format(_uinput.maxnamelen)) - def _find_device(self, fd): + def _find_device(self, fd: int) -> InputDevice: """ Tries to find the device node. Will delegate this task to one of several platform-specific functions. @@ -299,7 +306,7 @@ def _find_device(self, fd): # use the generic fallback method. return self._find_device_fallback() - def _find_device_linux(self, sysname): + def _find_device_linux(self, sysname: str) -> InputDevice: """ Tries to find the device node when running on Linux. """ @@ -327,15 +334,15 @@ def _find_device_linux(self, sysname): # device to show up or the permissions to be set. for attempt in range(19): try: - return device.InputDevice(device_path) + return InputDevice(device_path) except (FileNotFoundError, PermissionError): time.sleep(0.1) # Last attempt. If this fails, whatever exception the last attempt raises # shall be the exception that this function raises. - return device.InputDevice(device_path) + return InputDevice(device_path) - def _find_device_fallback(self): + def _find_device_fallback(self) -> Union[InputDevice, None]: """ Tries to find the device node when UI_GET_SYSNAME is not available or we're running on a system sufficiently exotic that we do not know how @@ -363,6 +370,6 @@ def _find_device_fallback(self): path_number_pairs.sort(key=lambda pair: pair[1], reverse=True) for path, _ in path_number_pairs: - d = device.InputDevice(path) + d = InputDevice(path) if d.name == self.name: return d diff --git a/src/evdev/util.py b/src/evdev/util.py index 59991f6..dd7cba6 100644 --- a/src/evdev/util.py +++ b/src/evdev/util.py @@ -3,21 +3,20 @@ import os import re import stat +from typing import Union, List from . import ecodes from .events import event_factory -def list_devices(input_device_dir="/dev/input"): +def list_devices(input_device_dir="/dev/input") -> List[str]: """List readable character devices in ``input_device_dir``.""" fns = glob.glob("{}/event*".format(input_device_dir)) - fns = list(filter(is_device, fns)) + return list(filter(is_device, fns)) - return fns - -def is_device(fn): +def is_device(fn: Union[str, bytes, os.PathLike]) -> bool: """Check if ``fn`` is a readable and writable character device.""" if not os.path.exists(fn): From bc91d17cd6103f37be3d705302132df328737698 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Tue, 4 Feb 2025 17:50:34 +0000 Subject: [PATCH 254/270] Expose type annotations (#233) * Expose type annotations --- src/evdev/py.typed | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/evdev/py.typed diff --git a/src/evdev/py.typed b/src/evdev/py.typed new file mode 100644 index 0000000..e69de29 From 2c623eb5b3b6442ae9cc6a7113d776f23e041cba Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sat, 8 Feb 2025 11:40:49 +0100 Subject: [PATCH 255/270] More type hints --- src/evdev/ecodes.py | 6 +++--- src/evdev/events.py | 37 +++++++++++++++++++------------------ src/evdev/evtest.py | 1 - src/evdev/util.py | 2 +- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/evdev/ecodes.py b/src/evdev/ecodes.py index a19dcba..fd4afc4 100644 --- a/src/evdev/ecodes.py +++ b/src/evdev/ecodes.py @@ -1,5 +1,5 @@ -# When installed, this module is replaced by an ecodes.py generated at +# When installed, this module is replaced by an ecodes.py generated at # build time by genecodes_py.py (see build_ext in setup.py). -# This stub exists to make development of evdev itself more convenient. -from . ecodes_runtime import * +# This stub exists to make development of evdev itself more convenient. +from .ecodes_runtime import * diff --git a/src/evdev/events.py b/src/evdev/events.py index a4f817d..922bfe6 100644 --- a/src/evdev/events.py +++ b/src/evdev/events.py @@ -38,6 +38,7 @@ # http://www.kernel.org/doc/Documentation/input/event-codes.txt # pylint: disable=no-name-in-module +from typing import Final from .ecodes import ABS, EV_ABS, EV_KEY, EV_REL, EV_SYN, KEY, REL, SYN, keys @@ -48,21 +49,21 @@ class InputEvent: def __init__(self, sec, usec, type, code, value): #: Time in seconds since epoch at which event occurred. - self.sec = sec + self.sec: int = sec #: Microsecond portion of the timestamp. - self.usec = usec + self.usec: int = usec #: Event type - one of ``ecodes.EV_*``. - self.type = type + self.type: int = type #: Event code related to the event type. - self.code = code + self.code: int = code #: Event value related to the event type. - self.value = value + self.value: int = value - def timestamp(self): + def timestamp(self) -> float: """Return event timestamp as a float.""" return self.sec + (self.usec / 1000000.0) @@ -78,20 +79,20 @@ def __repr__(self): class KeyEvent: """An event generated by a keyboard, button or other key-like devices.""" - key_up = 0x0 - key_down = 0x1 - key_hold = 0x2 + key_up: Final[int] = 0x0 + key_down: Final[int] = 0x1 + key_hold: Final[int] = 0x2 __slots__ = "scancode", "keycode", "keystate", "event" - def __init__(self, event, allow_unknown=False): + def __init__(self, event: InputEvent, allow_unknown: bool = False): """ The ``allow_unknown`` argument determines what to do in the event of an event code for which a key code cannot be found. If ``False`` a ``KeyError`` will be raised. If ``True`` the keycode will be set to the hex value of the event code. """ - self.scancode = event.code + self.scancode: int = event.code if event.value == 0: self.keystate = KeyEvent.key_up @@ -109,7 +110,7 @@ def __init__(self, event, allow_unknown=False): raise #: Reference to an :class:`InputEvent` instance. - self.event = event + self.event: InputEvent = event def __str__(self): try: @@ -129,9 +130,9 @@ class RelEvent: __slots__ = "event" - def __init__(self, event): + def __init__(self, event: InputEvent): #: Reference to an :class:`InputEvent` instance. - self.event = event + self.event: InputEvent = event def __str__(self): msg = "relative axis event at {:f}, {}" @@ -146,9 +147,9 @@ class AbsEvent: __slots__ = "event" - def __init__(self, event): + def __init__(self, event: InputEvent): #: Reference to an :class:`InputEvent` instance. - self.event = event + self.event: InputEvent = event def __str__(self): msg = "absolute axis event at {:f}, {}" @@ -166,9 +167,9 @@ class SynEvent: __slots__ = "event" - def __init__(self, event): + def __init__(self, event: InputEvent): #: Reference to an :class:`InputEvent` instance. - self.event = event + self.event: InputEvent = event def __str__(self): msg = "synchronization event at {:f}, {}" diff --git a/src/evdev/evtest.py b/src/evdev/evtest.py index b0244a9..6ea3bb5 100644 --- a/src/evdev/evtest.py +++ b/src/evdev/evtest.py @@ -16,7 +16,6 @@ evtest /dev/input/event0 /dev/input/event1 """ - import atexit import optparse import re diff --git a/src/evdev/util.py b/src/evdev/util.py index dd7cba6..b84ef09 100644 --- a/src/evdev/util.py +++ b/src/evdev/util.py @@ -9,7 +9,7 @@ from .events import event_factory -def list_devices(input_device_dir="/dev/input") -> List[str]: +def list_devices(input_device_dir: Union[str, bytes, os.PathLike] = "/dev/input") -> List[str]: """List readable character devices in ``input_device_dir``.""" fns = glob.glob("{}/event*".format(input_device_dir)) From 7cb02b9c644fbcce69b09375492e39382a1f450c Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sat, 8 Feb 2025 13:36:06 +0100 Subject: [PATCH 256/270] =?UTF-8?q?Bump=20version:=201.8.0=20=E2=86=92=201?= =?UTF-8?q?.9.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/changelog.rst | 10 +++++----- docs/conf.py | 2 +- pyproject.toml | 4 ++-- src/evdev/eventio.py | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 5dfeaab..f66cfff 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,18 +1,18 @@ Changelog --------- -1.9.0 (Unreleased) +1.9.0 (Feb 08, 2025) ================== -- Fix ``CPATH/C_INCLUDE_PATH`` being ignored during build. +- Fix for ``CPATH/C_INCLUDE_PATH`` being ignored during build. -- Slightly faster reading of events. +- Slightly faster reading of events in ``device.read()`` and ``device.read_one()``. -- Fix build on FreeBSD. +- Fix FreeBSD support. - Drop deprecated ``InputDevice.fn`` (use ``InputDevice.path`` instead). -- More type hints. +- Improve type hint coverage and add a ``py.typed`` file to the sdist. 1.8.0 (Jan 25, 2025) diff --git a/docs/conf.py b/docs/conf.py index 53b5206..b938fa0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -65,7 +65,7 @@ # built documents. # # The full version, including alpha/beta/rc tags. -release = "1.8.0" +release = "1.9.0" # The short X.Y version. version = release diff --git a/pyproject.toml b/pyproject.toml index 7854d91..346dedd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "evdev" -version = "1.8.0" +version = "1.9.0" description = "Bindings to the Linux input handling subsystem" keywords = ["evdev", "input", "uinput"] readme = "README.md" @@ -36,7 +36,7 @@ line-length = 120 ignore = ["E265", "E241", "F403", "F401", "E401", "E731"] [tool.bumpversion] -current_version = "1.8.0" +current_version = "1.9.0" commit = true tag = true allow_dirty = true diff --git a/src/evdev/eventio.py b/src/evdev/eventio.py index 27bba9d..bdb91a4 100644 --- a/src/evdev/eventio.py +++ b/src/evdev/eventio.py @@ -2,7 +2,7 @@ import functools import os import select -from typing import Iterator +from typing import Iterator, Union from . import _input, _uinput, ecodes from .events import InputEvent @@ -46,7 +46,7 @@ def read_loop(self) -> Iterator[InputEvent]: for event in self.read(): yield event - def read_one(self) -> InputEvent: + def read_one(self) -> Union[InputEvent, None]: """ Read and return a single input event as an instance of :class:`InputEvent `. From 6523e3f7d77ff5fd15bc2a02033527449b117e72 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Thu, 20 Feb 2025 07:48:34 +0000 Subject: [PATCH 257/270] Explicit export (#236) --- src/evdev/__init__.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/evdev/__init__.py b/src/evdev/__init__.py index 6aa6ef2..5d056f0 100644 --- a/src/evdev/__init__.py +++ b/src/evdev/__init__.py @@ -2,8 +2,25 @@ # Gather everything into a single, convenient namespace. # -------------------------------------------------------------------------- -from . import ecodes, ff -from .device import AbsInfo, DeviceInfo, EvdevError, InputDevice -from .events import AbsEvent, InputEvent, KeyEvent, RelEvent, SynEvent, event_factory -from .uinput import UInput, UInputError -from .util import categorize, list_devices, resolve_ecodes, resolve_ecodes_dict +from . import ecodes as ecodes, ff as ff +from .device import ( + AbsInfo as AbsInfo, + DeviceInfo as DeviceInfo, + EvdevError as EvdevError, + InputDevice as InputDevice, +) +from .events import ( + AbsEvent as AbsEvent, + InputEvent as InputEvent, + KeyEvent as KeyEvent, + RelEvent as RelEvent, + SynEvent as SynEvent, + event_factory as event_factory, +) +from .uinput import UInput as UInput, UInputError as UInputError +from .util import ( + categorize as categorize, + list_devices as list_devices, + resolve_ecodes as resolve_ecodes, + resolve_ecodes_dict as resolve_ecodes_dict, +) From 7916a7beb16f13cb0827e712aa3e889d38ea67e2 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Thu, 20 Feb 2025 20:56:45 +0000 Subject: [PATCH 258/270] Fill in some type annotations (#237) --- src/evdev/device.py | 32 ++++++++------ src/evdev/eventio_async.py | 87 ++++++++++++++++++++------------------ src/evdev/util.py | 4 +- 3 files changed, 68 insertions(+), 55 deletions(-) diff --git a/src/evdev/device.py b/src/evdev/device.py index 73b0acb..878a937 100644 --- a/src/evdev/device.py +++ b/src/evdev/device.py @@ -1,6 +1,6 @@ import contextlib import os -from typing import NamedTuple, Tuple, Union +from typing import Dict, Iterator, List, Literal, NamedTuple, Tuple, Union, overload from . import _input, ecodes, util @@ -95,7 +95,7 @@ class DeviceInfo(NamedTuple): product: int version: int - def __str__(self): + def __str__(self) -> str: msg = "bus: {:04x}, vendor {:04x}, product {:04x}, version {:04x}" return msg.format(*self) # pylint: disable=not-an-iterable @@ -151,7 +151,7 @@ def __init__(self, dev: Union[str, bytes, os.PathLike]): #: The number of force feedback effects the device can keep in its memory. self.ff_effects_count = _input.ioctl_EVIOCGEFFECTS(self.fd) - def __del__(self): + def __del__(self) -> None: if hasattr(self, "fd") and self.fd is not None: try: self.close() @@ -176,7 +176,13 @@ def _capabilities(self, absinfo: bool = True): return res - def capabilities(self, verbose: bool = False, absinfo: bool = True): + @overload + def capabilities(self, verbose: Literal[False] = ..., absinfo: bool = ...) -> Dict[int, List[int]]: + ... + @overload + def capabilities(self, verbose: Literal[True], absinfo: bool = ...) -> Dict[Tuple[str, int], List[Tuple[str, int]]]: + ... + def capabilities(self, verbose: bool = False, absinfo: bool = True) -> Union[Dict[int, List[int]], Dict[Tuple[str, int], List[Tuple[str, int]]]]: """ Return the event types that this device supports as a mapping of supported event types to lists of handled event codes. @@ -263,7 +269,7 @@ def leds(self, verbose: bool = False): return leds - def set_led(self, led_num: int, value: int): + def set_led(self, led_num: int, value: int) -> None: """ Set the state of the selected LED. @@ -279,18 +285,18 @@ def __eq__(self, other): """ return isinstance(other, self.__class__) and self.info == other.info and self.path == other.path - def __str__(self): + def __str__(self) -> str: msg = 'device {}, name "{}", phys "{}", uniq "{}"' return msg.format(self.path, self.name, self.phys, self.uniq or "") - def __repr__(self): + def __repr__(self) -> str: msg = (self.__class__.__name__, self.path) return "{}({!r})".format(*msg) def __fspath__(self): return self.path - def close(self): + def close(self) -> None: if self.fd > -1: try: super().close() @@ -298,7 +304,7 @@ def close(self): finally: self.fd = -1 - def grab(self): + def grab(self) -> None: """ Grab input device using ``EVIOCGRAB`` - other applications will be unable to receive events until the device is released. Only @@ -311,7 +317,7 @@ def grab(self): _input.ioctl_EVIOCGRAB(self.fd, 1) - def ungrab(self): + def ungrab(self) -> None: """ Release device if it has been already grabbed (uses `EVIOCGRAB`). @@ -324,7 +330,7 @@ def ungrab(self): _input.ioctl_EVIOCGRAB(self.fd, 0) @contextlib.contextmanager - def grab_context(self): + def grab_context(self) -> Iterator[None]: """ A context manager for the duration of which only the current process will be able to receive events from the device. @@ -342,7 +348,7 @@ def upload_effect(self, effect: "ff.Effect"): ff_id = _input.upload_effect(self.fd, data) return ff_id - def erase_effect(self, ff_id): + def erase_effect(self, ff_id) -> None: """ Erase a force effect from a force feedback device. This also stops the effect. @@ -402,7 +408,7 @@ def absinfo(self, axis_num: int): """ return AbsInfo(*_input.ioctl_EVIOCGABS(self.fd, axis_num)) - def set_absinfo(self, axis_num: int, value=None, min=None, max=None, fuzz=None, flat=None, resolution=None): + def set_absinfo(self, axis_num: int, value=None, min=None, max=None, fuzz=None, flat=None, resolution=None) -> None: """ Update :class:`AbsInfo` values. Only specified values will be overwritten. diff --git a/src/evdev/eventio_async.py b/src/evdev/eventio_async.py index fb8bcd2..4af1aab 100644 --- a/src/evdev/eventio_async.py +++ b/src/evdev/eventio_async.py @@ -1,11 +1,57 @@ import asyncio import select +import sys from . import eventio +from .events import InputEvent # needed for compatibility from .eventio import EvdevError +if sys.version_info >= (3, 11): + from typing import Self +else: + from typing import Any as Self + + +class ReadIterator: + def __init__(self, device): + self.current_batch = iter(()) + self.device = device + + # Standard iterator protocol. + def __iter__(self) -> Self: + return self + + def __next__(self) -> InputEvent: + try: + # Read from the previous batch of events. + return next(self.current_batch) + except StopIteration: + r, w, x = select.select([self.device.fd], [], []) + self.current_batch = self.device.read() + return next(self.current_batch) + + def __aiter__(self) -> Self: + return self + + def __anext__(self) -> "asyncio.Future[InputEvent]": + future = asyncio.Future() + try: + # Read from the previous batch of events. + future.set_result(next(self.current_batch)) + except StopIteration: + + def next_batch_ready(batch): + try: + self.current_batch = batch.result() + future.set_result(next(self.current_batch)) + except Exception as e: + future.set_exception(e) + + self.device.async_read().add_done_callback(next_batch_ready) + return future + class EventIO(eventio.EventIO): def _do_when_readable(self, callback): @@ -42,7 +88,7 @@ def async_read(self): self._do_when_readable(lambda: self._set_result(future, self.read)) return future - def async_read_loop(self): + def async_read_loop(self) -> ReadIterator: """ Return an iterator that yields input events. This iterator is compatible with the ``async for`` syntax. @@ -58,42 +104,3 @@ def close(self): # no event loop present, so there is nothing to # remove the reader from. Ignore pass - - -class ReadIterator: - def __init__(self, device): - self.current_batch = iter(()) - self.device = device - - # Standard iterator protocol. - def __iter__(self): - return self - - def __next__(self): - try: - # Read from the previous batch of events. - return next(self.current_batch) - except StopIteration: - r, w, x = select.select([self.device.fd], [], []) - self.current_batch = self.device.read() - return next(self.current_batch) - - def __aiter__(self): - return self - - def __anext__(self): - future = asyncio.Future() - try: - # Read from the previous batch of events. - future.set_result(next(self.current_batch)) - except StopIteration: - - def next_batch_ready(batch): - try: - self.current_batch = batch.result() - future.set_result(next(self.current_batch)) - except Exception as e: - future.set_exception(e) - - self.device.async_read().add_done_callback(next_batch_ready) - return future diff --git a/src/evdev/util.py b/src/evdev/util.py index b84ef09..f873655 100644 --- a/src/evdev/util.py +++ b/src/evdev/util.py @@ -6,7 +6,7 @@ from typing import Union, List from . import ecodes -from .events import event_factory +from .events import InputEvent, event_factory def list_devices(input_device_dir: Union[str, bytes, os.PathLike] = "/dev/input") -> List[str]: @@ -32,7 +32,7 @@ def is_device(fn: Union[str, bytes, os.PathLike]) -> bool: return True -def categorize(event): +def categorize(event: InputEvent) -> InputEvent: """ Categorize an event according to its type. From a98b68f9ac7ac32dbd175f6090e2458ec612f75c Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Fri, 21 Feb 2025 18:24:49 +0100 Subject: [PATCH 259/270] Fix for UI_FF constants missing from generated ecodes.py --- .github/workflows/test.yml | 1 - src/evdev/__init__.py | 17 +++++++++++++++-- src/evdev/ecodes_runtime.py | 2 +- src/evdev/genecodes_py.py | 3 ++- tests/test_ecodes.py | 16 +++++++++++++--- 5 files changed, 31 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3ee56d3..b9cd26d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,7 +21,6 @@ jobs: python-version: ${{ matrix.python-version }} - name: Run pytest tests - # pip install -e . builds _ecodes and such into the evdev directory # sudo required to write to uinputs run: | sudo python -m pip install pytest setuptools diff --git a/src/evdev/__init__.py b/src/evdev/__init__.py index 5d056f0..bae0fec 100644 --- a/src/evdev/__init__.py +++ b/src/evdev/__init__.py @@ -2,13 +2,21 @@ # Gather everything into a single, convenient namespace. # -------------------------------------------------------------------------- -from . import ecodes as ecodes, ff as ff +# The superfluous "import name as name" syntax is here to satisfy mypy's attrs-defined rule. +# Alternatively all exported objects can be listed in __all__. + +from . import ( + ecodes as ecodes, + ff as ff, +) + from .device import ( AbsInfo as AbsInfo, DeviceInfo as DeviceInfo, EvdevError as EvdevError, InputDevice as InputDevice, ) + from .events import ( AbsEvent as AbsEvent, InputEvent as InputEvent, @@ -17,7 +25,12 @@ SynEvent as SynEvent, event_factory as event_factory, ) -from .uinput import UInput as UInput, UInputError as UInputError + +from .uinput import ( + UInput as UInput, + UInputError as UInputError, +) + from .util import ( categorize as categorize, list_devices as list_devices, diff --git a/src/evdev/ecodes_runtime.py b/src/evdev/ecodes_runtime.py index d6c8b2a..47f3b23 100644 --- a/src/evdev/ecodes_runtime.py +++ b/src/evdev/ecodes_runtime.py @@ -46,7 +46,7 @@ #: Mapping of names to values. ecodes = {} -prefixes = "KEY ABS REL SW MSC LED BTN REP SND ID EV BUS SYN FF_STATUS FF INPUT_PROP".split() +prefixes = "KEY ABS REL SW MSC LED BTN REP SND ID EV BUS SYN FF_STATUS FF INPUT_PROP UI_FF".split() prev_prefix = "" g = globals() diff --git a/src/evdev/genecodes_py.py b/src/evdev/genecodes_py.py index 1afbc34..f00020c 100644 --- a/src/evdev/genecodes_py.py +++ b/src/evdev/genecodes_py.py @@ -40,6 +40,7 @@ ("BUS", "Dict[int, Union[str, Tuple[str]]]", None), ("SYN", "Dict[int, Union[str, Tuple[str]]]", None), ("FF", "Dict[int, Union[str, Tuple[str]]]", None), + ("UI_FF", "Dict[int, Union[str, Tuple[str]]]", None), ("FF_STATUS", "Dict[int, Union[str, Tuple[str]]]", None), ("INPUT_PROP", "Dict[int, Union[str, Tuple[str]]]", None) ] @@ -50,4 +51,4 @@ print(f"{key}: {annotation} = ", end="") pprint(getattr(ecodes, key)) - print() + print() \ No newline at end of file diff --git a/tests/test_ecodes.py b/tests/test_ecodes.py index c810b4f..5c3e38d 100644 --- a/tests/test_ecodes.py +++ b/tests/test_ecodes.py @@ -1,9 +1,8 @@ -# encoding: utf-8 - from evdev import ecodes +from evdev import ecodes_runtime -prefixes = "KEY ABS REL SW MSC LED BTN REP SND ID EV BUS SYN FF_STATUS FF" +prefixes = "KEY ABS REL SW MSC LED BTN REP SND ID EV BUS SYN FF_STATUS FF UI_FF" def to_tuples(val): @@ -29,3 +28,14 @@ def test_overlap(): vals_ff = set(to_tuples(ecodes.FF.values())) vals_ff_status = set(to_tuples(ecodes.FF_STATUS.values())) assert bool(vals_ff & vals_ff_status) is False + + +def test_generated(): + e_run = vars(ecodes_runtime) + e_gen = vars(ecodes) + + def keys(v): + res = {k for k in v.keys() if not k.startswith("_") and not k[1].islower()} + return res + + assert keys(e_run) == keys(e_gen) \ No newline at end of file From 82d09f631a16329c6d3ca2a2fd3789fac3a01c4b Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Sat, 22 Feb 2025 12:05:08 +0100 Subject: [PATCH 260/270] =?UTF-8?q?Bump=20version:=201.9.0=20=E2=86=92=201?= =?UTF-8?q?.9.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/changelog.rst | 11 ++++++++++- docs/conf.py | 2 +- pyproject.toml | 4 ++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index f66cfff..4dcf62f 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,8 +1,17 @@ Changelog --------- + +1.9.1 (Feb 22, 2025) +==================== + +- Fix fox missing ``UI_FF`` constants in generated ``ecodes.py``. + +- More type annotations. + + 1.9.0 (Feb 08, 2025) -================== +==================== - Fix for ``CPATH/C_INCLUDE_PATH`` being ignored during build. diff --git a/docs/conf.py b/docs/conf.py index b938fa0..86b3d06 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -65,7 +65,7 @@ # built documents. # # The full version, including alpha/beta/rc tags. -release = "1.9.0" +release = "1.9.1" # The short X.Y version. version = release diff --git a/pyproject.toml b/pyproject.toml index 346dedd..d248ab2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "evdev" -version = "1.9.0" +version = "1.9.1" description = "Bindings to the Linux input handling subsystem" keywords = ["evdev", "input", "uinput"] readme = "README.md" @@ -36,7 +36,7 @@ line-length = 120 ignore = ["E265", "E241", "F403", "F401", "E401", "E731"] [tool.bumpversion] -current_version = "1.9.0" +current_version = "1.9.1" commit = true tag = true allow_dirty = true From 5f9fd2cd11daa9a54452dadbf00aaf284d3d6063 Mon Sep 17 00:00:00 2001 From: bastian-wattro <106541220+bastian-wattro@users.noreply.github.com> Date: Fri, 28 Feb 2025 08:41:31 +0100 Subject: [PATCH 261/270] fix utils.categorize return type (#240) --- src/evdev/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/evdev/util.py b/src/evdev/util.py index f873655..db89a22 100644 --- a/src/evdev/util.py +++ b/src/evdev/util.py @@ -6,7 +6,7 @@ from typing import Union, List from . import ecodes -from .events import InputEvent, event_factory +from .events import InputEvent, event_factory, KeyEvent, RelEvent, AbsEvent, SynEvent def list_devices(input_device_dir: Union[str, bytes, os.PathLike] = "/dev/input") -> List[str]: @@ -32,7 +32,7 @@ def is_device(fn: Union[str, bytes, os.PathLike]) -> bool: return True -def categorize(event: InputEvent) -> InputEvent: +def categorize(event: InputEvent) -> Union[InputEvent, KeyEvent, RelEvent, AbsEvent, SynEvent]: """ Categorize an event according to its type. From 6b4e8ef0ee505d9c3d46b1787eac339d8bd0b934 Mon Sep 17 00:00:00 2001 From: Yoann Congal Date: Thu, 1 May 2025 19:42:17 +0200 Subject: [PATCH 262/270] Add a reproducibility option for building ecodes.c (#242) ecodes.c currently contains the kernel info of the build machine and the full path of the input*.h headers: This is not reproducible as output can change even is headers content do not. Downstream distributions might package ecodes.c and get non-reproducible output. To fix this: introduce a --reproducible option in the build: - in setup.py build_ecodes command - in underlying genecodes_c.py Note: These options are disabled by default so no change is expected in current builds. Signed-off-by: Yoann Congal --- setup.py | 13 ++++++++++--- src/evdev/genecodes_c.py | 17 +++++++++++------ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/setup.py b/setup.py index 6b721d7..3371199 100755 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ ecodes_c_path = curdir / "src/evdev/ecodes.c" -def create_ecodes(headers=None): +def create_ecodes(headers=None, reproducibility=False): if not headers: include_paths = set() cpath = os.environ.get("CPATH", "").strip() @@ -65,7 +65,10 @@ def create_ecodes(headers=None): print("writing %s (using %s)" % (ecodes_c_path, " ".join(headers))) with ecodes_c_path.open("w") as fh: - cmd = [sys.executable, "src/evdev/genecodes_c.py", "--ecodes", *headers] + cmd = [sys.executable, "src/evdev/genecodes_c.py"] + if reproducibility: + cmd.append("--reproducibility") + cmd.extend(["--ecodes", *headers]) run(cmd, check=True, stdout=fh) @@ -74,17 +77,21 @@ class build_ecodes(Command): user_options = [ ("evdev-headers=", None, "colon-separated paths to input subsystem headers"), + ("reproducibility", None, "hide host details (host/paths) to create a reproducible output"), ] def initialize_options(self): self.evdev_headers = None + self.reproducibility = False def finalize_options(self): if self.evdev_headers: self.evdev_headers = self.evdev_headers.split(":") + if self.reproducibility is None: + self.reproducibility = False def run(self): - create_ecodes(self.evdev_headers) + create_ecodes(self.evdev_headers, reproducibility=self.reproducibility) class build_ext(_build_ext.build_ext): diff --git a/src/evdev/genecodes_c.py b/src/evdev/genecodes_c.py index 5c2d946..24cad27 100644 --- a/src/evdev/genecodes_c.py +++ b/src/evdev/genecodes_c.py @@ -15,22 +15,27 @@ "/usr/include/linux/uinput.h", ] -opts, args = getopt.getopt(sys.argv[1:], "", ["ecodes", "stubs"]) +opts, args = getopt.getopt(sys.argv[1:], "", ["ecodes", "stubs", "reproducibility"]) if not opts: - print("usage: genecodes.py [--ecodes|--stubs] ") + print("usage: genecodes.py [--ecodes|--stubs] [--reproducibility] ") exit(2) if args: headers = args +reproducibility = ("--reproducibility", "") in opts + # ----------------------------------------------------------------------------- macro_regex = r"#define\s+((?:KEY|ABS|REL|SW|MSC|LED|BTN|REP|SND|ID|EV|BUS|SYN|FF|UI_FF|INPUT_PROP)_\w+)" macro_regex = re.compile(macro_regex) -# Uname without hostname. -uname = list(os.uname()) -uname = " ".join((uname[0], *uname[2:])) +if reproducibility: + uname = "hidden for reproducibility" +else: + # Uname without hostname. + uname = list(os.uname()) + uname = " ".join((uname[0], *uname[2:])) # ----------------------------------------------------------------------------- @@ -138,5 +143,5 @@ def parse_headers(headers=headers): template = template_stubs body = os.linesep.join(body) -text = template % (uname, headers, body) +text = template % (uname, headers if not reproducibility else ["hidden for reproducibility"], body) print(text.strip()) From 3bc969bf59e842c9e2f9569f39434f77b911224f Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Thu, 1 May 2025 22:14:44 +0300 Subject: [PATCH 263/270] s/reproducibility/reproducible --- setup.py | 16 ++++++++-------- src/evdev/genecodes_c.py | 10 +++++----- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/setup.py b/setup.py index 3371199..1f6eaac 100755 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ ecodes_c_path = curdir / "src/evdev/ecodes.c" -def create_ecodes(headers=None, reproducibility=False): +def create_ecodes(headers=None, reproducible=False): if not headers: include_paths = set() cpath = os.environ.get("CPATH", "").strip() @@ -66,8 +66,8 @@ def create_ecodes(headers=None, reproducibility=False): print("writing %s (using %s)" % (ecodes_c_path, " ".join(headers))) with ecodes_c_path.open("w") as fh: cmd = [sys.executable, "src/evdev/genecodes_c.py"] - if reproducibility: - cmd.append("--reproducibility") + if reproducible: + cmd.append("--reproducible") cmd.extend(["--ecodes", *headers]) run(cmd, check=True, stdout=fh) @@ -77,21 +77,21 @@ class build_ecodes(Command): user_options = [ ("evdev-headers=", None, "colon-separated paths to input subsystem headers"), - ("reproducibility", None, "hide host details (host/paths) to create a reproducible output"), + ("reproducible", None, "hide host details (host/paths) to create a reproducible output"), ] def initialize_options(self): self.evdev_headers = None - self.reproducibility = False + self.reproducible = False def finalize_options(self): if self.evdev_headers: self.evdev_headers = self.evdev_headers.split(":") - if self.reproducibility is None: - self.reproducibility = False + if self.reproducible is None: + self.reproducible = False def run(self): - create_ecodes(self.evdev_headers, reproducibility=self.reproducibility) + create_ecodes(self.evdev_headers, reproducible=self.reproducible) class build_ext(_build_ext.build_ext): diff --git a/src/evdev/genecodes_c.py b/src/evdev/genecodes_c.py index 24cad27..15a6693 100644 --- a/src/evdev/genecodes_c.py +++ b/src/evdev/genecodes_c.py @@ -15,22 +15,22 @@ "/usr/include/linux/uinput.h", ] -opts, args = getopt.getopt(sys.argv[1:], "", ["ecodes", "stubs", "reproducibility"]) +opts, args = getopt.getopt(sys.argv[1:], "", ["ecodes", "stubs", "reproducible"]) if not opts: - print("usage: genecodes.py [--ecodes|--stubs] [--reproducibility] ") + print("usage: genecodes.py [--ecodes|--stubs] [--reproducible] ") exit(2) if args: headers = args -reproducibility = ("--reproducibility", "") in opts +reproducible = ("--reproducible", "") in opts # ----------------------------------------------------------------------------- macro_regex = r"#define\s+((?:KEY|ABS|REL|SW|MSC|LED|BTN|REP|SND|ID|EV|BUS|SYN|FF|UI_FF|INPUT_PROP)_\w+)" macro_regex = re.compile(macro_regex) -if reproducibility: +if reproducible: uname = "hidden for reproducibility" else: # Uname without hostname. @@ -143,5 +143,5 @@ def parse_headers(headers=headers): template = template_stubs body = os.linesep.join(body) -text = template % (uname, headers if not reproducibility else ["hidden for reproducibility"], body) +text = template % (uname, headers if not reproducible else ["hidden for reproducibility"], body) print(text.strip()) From 8f45223a11d0b48b8485e059d63896e65657dea8 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Thu, 1 May 2025 19:31:49 +0100 Subject: [PATCH 264/270] Use Generic to set precise type for InputDevice.path (#241) * Use Generic to set precise type for InputDevice.path * Update src/evdev/device.py --- src/evdev/device.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/evdev/device.py b/src/evdev/device.py index 878a937..a7f9b92 100644 --- a/src/evdev/device.py +++ b/src/evdev/device.py @@ -1,6 +1,6 @@ import contextlib import os -from typing import Dict, Iterator, List, Literal, NamedTuple, Tuple, Union, overload +from typing import Dict, Generic, Iterator, List, Literal, NamedTuple, Tuple, TypeVar, Union, overload from . import _input, ecodes, util @@ -9,6 +9,8 @@ except ImportError: from .eventio import EvdevError, EventIO +_AnyStr = TypeVar("_AnyStr", str, bytes) + class AbsInfo(NamedTuple): """Absolute axis information. @@ -100,14 +102,14 @@ def __str__(self) -> str: return msg.format(*self) # pylint: disable=not-an-iterable -class InputDevice(EventIO): +class InputDevice(EventIO, Generic[_AnyStr]): """ A linux input device from which input events can be read. """ __slots__ = ("path", "fd", "info", "name", "phys", "uniq", "_rawcapabilities", "version", "ff_effects_count") - def __init__(self, dev: Union[str, bytes, os.PathLike]): + def __init__(self, dev: Union[_AnyStr, "os.PathLike[_AnyStr]"]): """ Arguments --------- @@ -116,7 +118,7 @@ def __init__(self, dev: Union[str, bytes, os.PathLike]): """ #: Path to input device. - self.path = dev if not hasattr(dev, "__fspath__") else dev.__fspath__() + self.path: _AnyStr = dev if not hasattr(dev, "__fspath__") else dev.__fspath__() # Certain operations are possible only when the device is opened in read-write mode. try: From a5d8cf0749f15d44feb76bbed27b30a75b3c7c1f Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Thu, 1 May 2025 22:15:19 +0300 Subject: [PATCH 265/270] =?UTF-8?q?Bump=20version:=201.9.1=20=E2=86=92=201?= =?UTF-8?q?.9.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/changelog.rst | 11 +++++++++++ docs/conf.py | 2 +- pyproject.toml | 4 ++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 4dcf62f..49f5911 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -2,6 +2,17 @@ Changelog --------- +1.9.2 (May 01, 2025) +==================== + +- Add the "--reproducible" build option which removes the build date and used headers from the + generated ``ecodes.c``. Example usage:: + + python -m build --config-setting=--build-option='build_ecodes --reproducible' -n + +- Use ``Generic`` to set precise type for ``InputDevice.path``. + + 1.9.1 (Feb 22, 2025) ==================== diff --git a/docs/conf.py b/docs/conf.py index 86b3d06..758f878 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -65,7 +65,7 @@ # built documents. # # The full version, including alpha/beta/rc tags. -release = "1.9.1" +release = "1.9.2" # The short X.Y version. version = release diff --git a/pyproject.toml b/pyproject.toml index d248ab2..e6a6ac7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "evdev" -version = "1.9.1" +version = "1.9.2" description = "Bindings to the Linux input handling subsystem" keywords = ["evdev", "input", "uinput"] readme = "README.md" @@ -36,7 +36,7 @@ line-length = 120 ignore = ["E265", "E241", "F403", "F401", "E401", "E731"] [tool.bumpversion] -current_version = "1.9.1" +current_version = "1.9.2" commit = true tag = true allow_dirty = true From 5227b1672cbf074287088860c855c24bb96fe6b1 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Thu, 5 Feb 2026 00:09:01 +0100 Subject: [PATCH 266/270] Fix memory leaks --- src/evdev/input.c | 49 ++++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/src/evdev/input.c b/src/evdev/input.c index 4ad0408..894db22 100644 --- a/src/evdev/input.c +++ b/src/evdev/input.c @@ -63,12 +63,12 @@ device_read(PyObject *self, PyObject *args) return NULL; } - PyObject* sec = PyLong_FromLong(event.input_event_sec); - PyObject* usec = PyLong_FromLong(event.input_event_usec); - PyObject* val = PyLong_FromLong(event.value); - PyObject* type = PyLong_FromLong(event.type); - PyObject* code = PyLong_FromLong(event.code); - PyObject* py_input_event = PyTuple_Pack(5, sec, usec, type, code, val); + PyObject *py_input_event = PyTuple_New(5); + PyTuple_SET_ITEM(py_input_event, 0, PyLong_FromLong(event.input_event_sec)); + PyTuple_SET_ITEM(py_input_event, 1, PyLong_FromLong(event.input_event_usec)); + PyTuple_SET_ITEM(py_input_event, 2, PyLong_FromLong(event.type)); + PyTuple_SET_ITEM(py_input_event, 3, PyLong_FromLong(event.code)); + PyTuple_SET_ITEM(py_input_event, 4, PyLong_FromLong(event.value)); return py_input_event; } @@ -81,14 +81,6 @@ device_read_many(PyObject *self, PyObject *args) // get device file descriptor (O_RDONLY|O_NONBLOCK) int fd = (int)PyLong_AsLong(PyTuple_GET_ITEM(args, 0)); - PyObject* py_input_event = NULL; - PyObject* events = NULL; - PyObject* sec = NULL; - PyObject* usec = NULL; - PyObject* val = NULL; - PyObject* type = NULL; - PyObject* code = NULL; - struct input_event event[64]; size_t event_size = sizeof(struct input_event); @@ -101,15 +93,15 @@ device_read_many(PyObject *self, PyObject *args) // Construct a tuple of event tuples. Each tuple is the arguments to InputEvent. size_t num_events = nread / event_size; - events = PyTuple_New(num_events); - for (size_t i = 0 ; i < num_events; i++) { - sec = PyLong_FromLong(event[i].input_event_sec); - usec = PyLong_FromLong(event[i].input_event_usec); - val = PyLong_FromLong(event[i].value); - type = PyLong_FromLong(event[i].type); - code = PyLong_FromLong(event[i].code); - py_input_event = PyTuple_Pack(5, sec, usec, type, code, val); + PyObject* events = PyTuple_New(num_events); + for (size_t i = 0 ; i < num_events; i++) { + PyObject *py_input_event = PyTuple_New(5); + PyTuple_SET_ITEM(py_input_event, 0, PyLong_FromLong(event[i].input_event_sec)); + PyTuple_SET_ITEM(py_input_event, 1, PyLong_FromLong(event[i].input_event_usec)); + PyTuple_SET_ITEM(py_input_event, 2, PyLong_FromLong(event[i].type)); + PyTuple_SET_ITEM(py_input_event, 3, PyLong_FromLong(event[i].code)); + PyTuple_SET_ITEM(py_input_event, 4, PyLong_FromLong(event[i].value)); PyTuple_SET_ITEM(events, i, py_input_event); } @@ -200,6 +192,11 @@ ioctl_capabilities(PyObject *self, PyObject *args) return capabilities; on_err: + Py_XDECREF(capabilities); + Py_XDECREF(eventcodes); + Py_XDECREF(capability); + Py_XDECREF(py_absinfo); + Py_XDECREF(absitem); PyErr_SetFromErrno(PyExc_OSError); return NULL; } @@ -408,7 +405,9 @@ ioctl_EVIOCG_bits(PyObject *self, PyObject *args) PyObject* res = PyList_New(0); for (int i=0; i<=max; i++) { if (test_bit(bytes, i)) { - PyList_Append(res, Py_BuildValue("i", i)); + PyObject *val = PyLong_FromLong(i); + PyList_Append(res, val); + Py_DECREF(val); } } @@ -523,7 +522,9 @@ ioctl_EVIOCGPROP(PyObject *self, PyObject *args) PyObject* res = PyList_New(0); for (int i=0; i Date: Thu, 5 Feb 2026 22:16:48 +0100 Subject: [PATCH 267/270] CI fixes --- .github/workflows/install.yaml | 6 +++--- .github/workflows/lint.yml | 6 +++--- .github/workflows/test.yml | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/install.yaml b/.github/workflows/install.yaml index 87502ad..f07c035 100644 --- a/.github/workflows/install.yaml +++ b/.github/workflows/install.yaml @@ -11,15 +11,15 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] include: - os: ubuntu-latest python-version: "3.8" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e293976..20d254b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -11,12 +11,12 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - python-version: ["3.12"] + python-version: ["3.14"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b9cd26d..073d524 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,12 +11,12 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - python-version: ["3.12"] + python-version: ["3.14"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} From fae2cf9d1d4a3e2700e148399f312b44b6208a56 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Thu, 5 Feb 2026 22:24:12 +0100 Subject: [PATCH 268/270] Use an SPDX license --- pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e6a6ac7..665a9b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ version = "1.9.2" description = "Bindings to the Linux input handling subsystem" keywords = ["evdev", "input", "uinput"] readme = "README.md" -license = {file = "LICENSE"} +license = "BSD-3-Clause" requires-python = ">=3.8" authors = [ { name="Georgi Valkov", email="georgi.t.valkov@gmail.com" }, @@ -22,7 +22,6 @@ classifiers = [ "Operating System :: POSIX :: Linux", "Intended Audience :: Developers", "Topic :: Software Development :: Libraries", - "License :: OSI Approved :: BSD License", "Programming Language :: Python :: Implementation :: CPython", ] From faf7bc93c6a97edc317c4cc6a8d81ab94e5ba77f Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Thu, 5 Feb 2026 22:36:13 +0100 Subject: [PATCH 269/270] Drop support for Python 3.8 and raise setuptools version to 77.0 --- .github/workflows/install.yaml | 4 ++-- docs/changelog.rst | 7 +++++++ pyproject.toml | 4 ++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/workflows/install.yaml b/.github/workflows/install.yaml index f07c035..e879179 100644 --- a/.github/workflows/install.yaml +++ b/.github/workflows/install.yaml @@ -11,10 +11,10 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] include: - os: ubuntu-latest - python-version: "3.8" + python-version: "3.9" steps: - uses: actions/checkout@v6 diff --git a/docs/changelog.rst b/docs/changelog.rst index 49f5911..bcf1636 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,13 @@ Changelog --------- +1.9.3 (Feb 05, 2025) +==================== + +- Fix several memory leaks in ``input.c``. + +- Raise the minimum supported Python version to 3.9 and the setuptools version to 77.0. + 1.9.2 (May 01, 2025) ==================== diff --git a/pyproject.toml b/pyproject.toml index 665a9b7..159460c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=61.0"] +requires = ["setuptools>=77.0"] build-backend = "setuptools.build_meta" [project] @@ -9,7 +9,7 @@ description = "Bindings to the Linux input handling subsystem" keywords = ["evdev", "input", "uinput"] readme = "README.md" license = "BSD-3-Clause" -requires-python = ">=3.8" +requires-python = ">=3.9" authors = [ { name="Georgi Valkov", email="georgi.t.valkov@gmail.com" }, ] From a47b5b5a6f79bde6823095d1105501856338aed7 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Thu, 5 Feb 2026 22:46:48 +0100 Subject: [PATCH 270/270] =?UTF-8?q?Bump=20version:=201.9.2=20=E2=86=92=201?= =?UTF-8?q?.9.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/conf.py | 2 +- pyproject.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 758f878..0be06b3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -65,7 +65,7 @@ # built documents. # # The full version, including alpha/beta/rc tags. -release = "1.9.2" +release = "1.9.3" # The short X.Y version. version = release diff --git a/pyproject.toml b/pyproject.toml index 159460c..d0b4f7c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "evdev" -version = "1.9.2" +version = "1.9.3" description = "Bindings to the Linux input handling subsystem" keywords = ["evdev", "input", "uinput"] readme = "README.md" @@ -35,7 +35,7 @@ line-length = 120 ignore = ["E265", "E241", "F403", "F401", "E401", "E731"] [tool.bumpversion] -current_version = "1.9.2" +current_version = "1.9.3" commit = true tag = true allow_dirty = true