DOKK / manpages / debian 11 / python-evdev-doc / python-evdev.7.en
PYTHON-EVDEV(7) python-evdev PYTHON-EVDEV(7)

python-evdev - python-evdev Documentation

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.

Python-evdev has been packaged for the following GNU/Linux distributions:

Consult the documentation of your OS package manager for installation instructions.

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:

$ apt-get install python-dev python-pip gcc
$ apt-get install linux-headers-$(uname -r)


On a Redhat compatible OS:

$ yum install python-devel python-pip gcc
$ yum install kernel-headers-$(uname -r)


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:

$ sudo pip install evdev    # available globally
$ pip install --user evdev  # available to the current user


By default, the setup script will look for the input.h and input-event-codes.h [1] 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:

$ 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)


[1]
input-event-codes.h is found only in more recent kernel versions.

Listing accessible event devices

>>> import evdev
>>> devices = [evdev.InputDevice(path) for path in evdev.list_devices()]
>>> for device in devices:
...    print(device.path, 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


NOTE:

If you do not see any devices, ensure that your user is in the correct group (typically input) to have read/write access.


>>> 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(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
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


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']


The python-evdev package also comes with a small command-line program for listing and monitoring input devices:

$ python -m evdev.evtest


Listing accessible event devices

>>> import evdev
>>> devices = [evdev.InputDevice(path) for path in evdev.list_devices()]
>>> for device in devices:
>>>     print(device.path, 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


>>> import evdev
>>> 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"
>>> device.capabilities()
... { 0: [0, 1, 2], 1: [272, 273, 274, 275], 2: [0, 1, 6, 8], 4: [4] }
>>> 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)], ...


>>> import evdev
>>> device = evdev.InputDevice('/dev/input/event7')
>>> print(device)
device /dev/input/event7, name "Wacom Bamboo 2FG 4x5 Finger", phys ""
>>> 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))] }
>>> 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)),] }
>>> device.capabilities(absinfo=False)
... { 1: [272, 273, 277, 278, 325, 330, 333],
...   3: [0, 1, 47, 53, 54, 57] }


>>> dev.leds(verbose=True)
... [('LED_NUML', 0), ('LED_CAPSL', 1)]
>>> dev.leds()
... [0, 1]
>>> dev.set_led(ecodes.LED_NUML, 1)  # enable numlock
>>> dev.set_led(ecodes.LED_NUML, 0)  # disable numlock


>>> dev.active_keys(verbose=True)
... [('KEY_3', 4), ('KEY_LEFTSHIFT', 42)]
>>> dev.active_keys()
... [4, 42]


Reading events from a single device in an endless loop.

>>> 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


NOTE:

This requires Python 3.5+ for the async/await keywords.


>>> import asyncio
>>> from evdev import InputDevice, categorize, ecodes
>>> dev = InputDevice('/dev/input/event1')
>>> async def helper(dev):
...     async for ev in dev.async_read_loop():
...         print(repr(ev))
>>> loop = asyncio.get_event_loop()
>>> loop.run_until_complete(helper(dev))
InputEvent(1527363738, 348740, 4, 4, 458792)
InputEvent(1527363738, 348740, 1, 28, 0)
InputEvent(1527363738, 348740, 0, 0, 0)


Reading events from multiple devices (using select)

>>> 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}
>>> 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, [], [])
...    for fd in r:
...        for event in devices[fd].read():
...            print(event)
event at 1351116708.002230, code 01, type 02, val 01
event at 1351116708.002234, code 00, type 00, val 00
event at 1351116708.782231, code 04, type 04, val 458782
event at 1351116708.782237, code 02, type 01, val 01


Reading events from multiple devices (using selectors)

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)


Reading events from multiple devices (using asyncio)

Yet another possibility is the asyncio module from Python 3.4:

import asyncio, evdev
@asyncio.coroutine
def print_events(device):

while True:
events = yield from device.async_read()
for event in events:
print(device.path, 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)) loop = asyncio.get_event_loop() loop.run_forever()


Since Python 3.5, the async/await syntax makes this even simpler:

import asyncio, evdev
mouse = evdev.InputDevice('/dev/input/event4')
keybd = evdev.InputDevice('/dev/input/event5')
async def print_events(device):

async for event in device.async_read_loop():
print(device.path, evdev.categorize(event), sep=': ') for device in mouse, keybd:
asyncio.ensure_future(print_events(device)) loop = asyncio.get_event_loop() loop.run_forever()


>>> 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']


>>> 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)]}


>>> dev.grab()  # become the sole recipient of all incoming input events
>>> dev.ungrab()


This functionality is also available as a context manager.

>>> with dev.grab_context():
...     pass


>>> from evdev import categorize, event_factory, ecodes
>>> class SynEvent(object):
...     def __init__(self, event):
...         ...
>>> event_factory[ecodes.EV_SYN] = SynEvent


See events for more information.

>>> from evdev import UInput, ecodes as e
>>> ui = UInput()
>>> # accepts only KEY_* events by default
>>> ui.write(e.EV_KEY, e.KEY_A, 1)  # KEY_A down
>>> ui.write(e.EV_KEY, e.KEY_A, 0)  # KEY_A up
>>> ui.syn()
>>> ui.close()


>>> ev = InputEvent(1334414993, 274296, ecodes.EV_KEY, ecodes.KEY_A, 1)
>>> with UInput() as ui:
...    ui.write_event(ev)
...    ui.syn()


>>> from evdev import UInput, AbsInfo, ecodes as e
>>> cap = {
...     e.EV_KEY : [e.KEY_A, e.KEY_B],
...     e.EV_ABS : [
...         (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, 128, 255, 0)) ]
... }
>>> ui = UInput(cap, name='example-device', version=0x3)
>>> print(ui)
name "example-device", bus "BUS_USB", vendor "0001", product "0001", version "0003"
event types: EV_KEY EV_ABS EV_SYN
>>> print(ui.capabilities())
{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) >>> ui.write(e.EV_ABS, e.ABS_Y, 20) >>> ui.syn()


Create uinput device with capabilities of another device

>>> from evdev import UInput, InputDevice
>>> mouse = InputDevice('/dev/input/event1')
>>> 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)])


Create uinput device capable of receiving FF-effects

import asyncio
from evdev import UInput, categorize, ecodes
cap = {

ecodes.EV_FF: [ecodes.FF_RUMBLE ],
ecodes.EV_KEY: [ecodes.KEY_A, ecodes.KEY_B] } 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 an EV_UINPUT event that will signal us that an
# effect upload/erase operation is in progress.
if event.type != ecodes.EV_UINPUT:
continue
if event.code == ecodes.UI_FF_UPLOAD:
upload = device.begin_upload(event.value)
upload.retval = 0
print(f'[upload] effect_id: {upload.effect.id}, type: {upload.effect.type}')
device.end_upload(upload)
elif event.code == ecodes.UI_FF_ERASE:
erase = device.begin_erase(event.value)
print(f'[erase] effect_id {erase.effect_id}')
erase.retval = 0
device.end_erase(erase) asyncio.ensure_future(print_events(ui)) loop = asyncio.get_event_loop() loop.run_forever()


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)


events

eventio

eventio_async

device

uinput

util

ecodes

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
  • pynput
  • pygame (cross-platform)

  • 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.

  • 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.
  • 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.
  • 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.

  • 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).

  • Fix installation on kernels <= 4.4.
  • Fix uinput creation ignoring absinfo settings.

  • Add support for handling force-feedback effect uploads (many thanks to @ndreys).
  • Fix typo preventing ff effects that need left coefficients from working.

  • Prevent Uinput device creation raising Objects/longobject.c:415: bad argument to internal function when a non-complete AbsInfo structure is passed. All missing AbsInfo fields are set to 0.
  • Fix Uinput device creation raising KeyError when a capability filtered by default is not present.
  • The InputDevice.fn attribute was deprecated in favor of InputDevice.path. Using the former will show a DeprecationWarning, but would otherwise continue working as before.
  • Fix InputDevice comparison raising AttributeError due to a non-existant path attribute.
  • Fix asyncio support in Python 3.5+.
  • Uploading FF effect now works both on Python 2.7 and Python 3+.
  • Remove the asyncore example from the tutorial.

  • Fix Python 2 compatibility issue in with Uinput.from_device.
  • Fix minor evdev.evtest formatting issue.

  • Fix InputDevice comparison on Python 2.
  • The device path is now considered when comparing two devices.
  • Fix UInput.from_device not correctly merging the capabilities of selected devices.
  • The list of excluded event types in UInput.from_device is now configurable. For example:

UInput.from_device(dev, filtered_types=(EV_SYN, EV_FF))


In addition, ecodes.EV_FF is now excluded by default.

Add a context manager for grabbing access to a device - InputDevice.grab_context. For example:

with dev.grab_context():

pass


Add the InputDevice.uniq attribute, which contains the unique identifier of the device. As with phys, this attribute may be empty (i.e. '').

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 leaking that exception into the event loop) (Fixes #67).

Exclude ecodes.c from source distribution (Fixes #63).

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)


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).


  • 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 overwritten. For example:

python setup.py build_ext \

--evdev-headers path/input.h:path/input-event-codes.h \
--include-dirs path/ \
install



  • Asyncio and async/await support (many thanks to @paulo-raca).
  • Add the ability to set the phys property of uinput devices (thanks @paulo-raca).
  • Add a generic InputDevice.set() method (thanks @paulo-raca).
  • Distribute the evtest script along with evdev.
  • Fix issue with generating ecodes.c in recent kernels (>= 4.4.0).
  • Fix absinfo item indexes in UInput.uinput_create() (thanks @forsenonlhaimaisentito).
  • More robust comparison of InputDevice objects (thanks @isia).

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.

Fallback to distutils if setuptools is not available.

  • Rework documentation and docstrings once more.
  • Fix install on Python 3.4 (works around issue21121).
  • Fix ioctl() requested buffer size (thanks Jakub Wojciech Klama).

  • Add method for returning a list of the currently active keys - InputDevice.active_keys() (thanks @spasche).
  • Fix a potential buffer overflow in ioctl_capabilities() (thanks @spasche).

Calling InputDevice.read_one() should always return None, when there is nothing to be read, even in case of a EAGAIN errno (thanks JPP).

  • Silence OSError in destructor (thanks @polyphemus).
  • Make InputDevice.close() work in cases in which stdin (fd 0) has been closed (thanks @polyphemus).

  • Rework documentation and docstrings.
  • Call InputDevice.close() from InputDevice.__del__().

Fix reference counting in InputDevice.device_read(), InputDevice.device_read_many() and ioctl_capabilities().

  • 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:

>>> ecodes.KEY[152]
... ['KEY_COFFEE', 'KEY_SCREENLOCK']
>>> ecodes.KEY[30]
... 'KEY_A'


  • Set the state of a LED through InputDevice.set_led() (thanks @accek).
  • Open InputDevice.fd in O_RDWR mode from now on.
  • Fix segfault in InputDevice.device_read_many() (thanks @bgilbert).

  • Raise IOError from InputDevice.device_read() and InputDevice.device_read_many() when InputDevice.read() fails.
  • Several stability and style changes (thank you debian code reviewers).

Fix vendor id and product id order in DeviceInfo() (thanks @kived).

  • 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.

  • Add ability to set/get auto-repeat settings with EVIOC{SG}REP.
  • Add InputDevice.version() - the value of EVIOCGVERSION.
  • Add InputDevice.read_loop().
  • Add InputDevice.grab() and InputDevice.ungrab() - exposes EVIOCGRAB.
  • Add InputDevice.leds() - exposes EVIOCGLED.
  • Replace DeviceInfo class with a namedtuple.
  • Prevent InputDevice.read_one() from skipping events.
  • Rename AbsData to AbsInfo (as in struct input_absinfo).

  • 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 UInput.capabilities() which is just a shortcut to UInput.device.capabilities().
  • Rename UInput.write() to UInput.write_event().
  • Add a simpler UInput.write(type, code, value)() method.
  • Make all UInput() constructor arguments optional (default device name is now py-evdev-uinput).
  • Add the ability to set absmin, absmax, absfuzz and absflat when specifying the uinput device's capabilities.
  • 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.
  • Split ioctl_devinfo into ioctl_devinfo and ioctl_capabilities.
  • Split UInput.uinput_open() to UInput.uinput_open() and UInput.uinput_create()
  • Add more uinput usage examples and documentation.
  • Rewrite uinput tests.
  • Remove mouserel and mouseabs from 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().

  • Add events.keys, which is a combination of all BTN_ and KEY_ event codes.
  • ecodes.c was not generated when installing through pip.

Initial Release

This package is released under the terms of the Revised BSD License.

Georgi Valkov

2012-2021, Georgi Valkov

June 2, 2021 1.4.0