diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..842ec23 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,123 @@ +# vim: set expandtab shiftwidth=2 tabstop=8 textwidth=0: + +.templates_sha: &templates_sha aec7a6ce7bb38902c70641526f6611e27141784a + +include: + - project: 'freedesktop/ci-templates' + ref: *templates_sha + file: '/templates/fedora.yml' + - project: 'freedesktop/ci-templates' + ref: *templates_sha + file: '/templates/ci-fairy.yml' + + +workflow: + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS + when: never + - if: $CI_COMMIT_BRANCH + +variables: + FDO_UPSTREAM_REPO: 'libevdev/python-libevdev' + + +stages: + - prep + - test + - deploy + +.policy: + retry: + max: 2 + when: + - runner_system_failure + - stuck_or_timeout_failure + # cancel run when a newer version is pushed to the branch + interruptible: true + dependencies: [] + +.fedora: + extends: + - .policy + variables: + FDO_DISTRIBUTION_VERSION: 42 + FDO_DISTRIBUTION_TAG: '2025-11-14.0' + FDO_DISTRIBUTION_PACKAGES: 'git python3 python3.9 python3.10 python3.11 python3-pytest python3-pytest-timeout libevdev virtme-ng qemu-img qemu-system-x86-core binutils file systemd-udev uv python3-hatchling' + +# +# Verify that commit messages are as expected, etc. +# + +check-commit: + extends: + - .fdo.ci-fairy + stage: prep + script: + - ci-fairy check-commits --junit-xml=results.xml + except: + - master@libevdev/python-libevdev + variables: + GIT_DEPTH: 100 + artifacts: + reports: + junit: results.xml + +container-prep: + extends: + - .fedora + - .fdo.container-build@fedora + stage: prep + variables: + GIT_STRATEGY: none + +pre-commit-hooks: + extends: + - .fdo.ci-fairy + stage: prep + script: + - python3 -m venv venv + - source venv/bin/activate + - pip3 install pre-commit + - pre-commit run --all-files + - git diff --exit-code || (echo "ERROR - Code style errors found, please fix" && false) + +pytest: + extends: + - .fedora + - .fdo.distribution-image@fedora + stage: test + tags: + - kvm + parallel: + matrix: + # We tests the ones most likely to break and the current default + - PYTHON_VERSION: ["3", "3.9", "3.10", "3.11"] + variables: + VNG_KERNEL: https://gitlab.freedesktop.org/api/v4/projects/libevdev%2Fhid-tools/packages/generic/kernel-x86_64/v6.14/bzImage + script: + - curl -LO $VNG_KERNEL + - export -p > .vngenv + - uv sync --python $PYTHON_VERSION + - uv add --dev pytest-timeout + - | + vng --exec "source $PWD/.vngenv; rm $PWD/.vngenv; uv run --python $PYTHON_VERSION pytest --verbose --junit-xml=results.xml --session-timeout=600" \ + --run ./bzImage \ + --user root \ + --overlay-rwdir=$HOME \ + --append HOME=$HOME \ + --overlay-rwdir=$(pwd) + artifacts: + when: always + reports: + junit: results.xml + +install: + extends: + - .fedora + - .fdo.distribution-image@fedora + stage: test + needs: + - container-prep + script: + - uv pip install --system . diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..bbf8475 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,11 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: end-of-file-fixer + - id: trailing-whitespace +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.14.5 + hooks: + - id: ruff-check + - id: ruff-format diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..377c153 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,21 @@ +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the version of Python and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: doc/source/conf.py + +# We recommend specifying your dependencies to enable reproducible builds: +# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +python: + install: + - requirements: doc/requirements.txt diff --git a/README.md b/README.md index 730373e..a473b44 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Source code ----------- The source code of python-libevdev can be found at: -http://github.com/whot/python-libevdev +https://gitlab.freedesktop.org/libevdev/python-libevdev License ------- @@ -32,5 +32,5 @@ python-libevdev is licensed under the MIT license. > and/or sell copies of the Software, and to permit persons to whom the > Software is furnished to do so, subject to the following conditions: [...] -See the [COPYING](http://github.com/whot/python-libevdev/blob/master/COPYING) +See the [COPYING](https://gitlab.freedesktop.org/libevdev/python-libevdev/blob/master/COPYING) file for the full license information. diff --git a/debian/changelog b/debian/changelog index 0943b20..4d50a74 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,36 @@ +python-libevdev (0.13.1-1) unstable; urgency=medium + + * New upstream release, switching to pyproject (with hatchling) and + pytest. + * Drop “Rules-Requires-Root: no” since it’s now the default. + + -- Stephen Kitt Mon, 17 Nov 2025 19:14:13 +0100 + +python-libevdev (0.12-1) unstable; urgency=medium + + * New upstream release, merging intersphinx-mapping.patch. + * Add “Built-Using” information to track the Sphinx version used to + build the documentation package. + * Standards-Version 4.7.2, no change required. + + -- Stephen Kitt Tue, 02 Sep 2025 18:58:35 +0200 + +python-libevdev (0.11-2) unstable; urgency=medium + + * Fix the intersphinx mapping declaration. Closes: #1090147. + * Update debian/watch to handle recent GitLab changes. + * Standards-Version 4.7.0, no change required. + + -- Stephen Kitt Tue, 17 Dec 2024 13:42:01 +0100 + +python-libevdev (0.11-1) unstable; urgency=medium + + * New upstream release. + * Track new upstream location. + * Standards-Version 4.6.2, no change required. + + -- Stephen Kitt Thu, 07 Mar 2024 13:07:44 +0100 + python-libevdev (0.5-3) unstable; urgency=low [ Debian Janitor ] diff --git a/debian/control b/debian/control index 0384739..1cc52b0 100644 --- a/debian/control +++ b/debian/control @@ -5,16 +5,17 @@ Priority: optional Build-Depends: debhelper-compat (= 13), dh-python, libevdev-dev, + pybuild-plugin-pyproject, python3-all, - python3-setuptools, + python3-hatchling, + python3-pytest, python3-sphinx, python3-sphinx-rtd-theme -Standards-Version: 4.5.1 +Standards-Version: 4.7.2 Vcs-Browser: https://salsa.debian.org/debian/python-libevdev Vcs-Git: https://salsa.debian.org/debian/python-libevdev.git -Homepage: https://github.com/whot/python-libevdev +Homepage: https://gitlab.freedesktop.org/libevdev/python-libevdev Testsuite: autopkgtest-pkg-python -Rules-Requires-Root: no Package: python3-libevdev Architecture: all @@ -42,6 +43,7 @@ Architecture: all Section: doc Depends: ${sphinxdoc:Depends}, ${misc:Depends} +Built-Using: ${sphinxdoc:Built-Using} Description: Python wrapper for libevdev (common documentation) This is a Python wrapper around libevdev, a wrapper library for evdev devices. It provides a simpler API around evdev and takes advantage diff --git a/debian/copyright b/debian/copyright index 2356220..cb4b9c1 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,13 +1,13 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: python-libevdev -Source: https://github.com/whot/python-libevdev +Source: https://gitlab.freedesktop.org/libevdev/python-libevdev Files: * -Copyright: 2016-2018 Red Hat, Inc. +Copyright: 2016-2022 Red Hat, Inc. License: MIT Files: debian/* -Copyright: 2018 Stephen Kitt +Copyright: 2018, 2020-2021, 2024-2025 Stephen Kitt License: MIT License: MIT diff --git a/debian/gbp.conf b/debian/gbp.conf new file mode 100644 index 0000000..68ebe86 --- /dev/null +++ b/debian/gbp.conf @@ -0,0 +1,5 @@ +# Configuration file for git-buildpackage and friends + +[DEFAULT] +pristine-tar = True +upstream-vcs-tag = %(version)s diff --git a/debian/upstream/metadata b/debian/upstream/metadata index 512f194..cea75a9 100644 --- a/debian/upstream/metadata +++ b/debian/upstream/metadata @@ -1,5 +1,5 @@ --- -Bug-Database: https://github.com/whot/python-libevdev/issues -Bug-Submit: https://github.com/whot/python-libevdev/issues/new -Repository: https://github.com/whot/python-libevdev.git -Repository-Browse: https://github.com/whot/python-libevdev +Bug-Database: https://gitlab.freedesktop.org/libevdev/python-libevdev/-/issues +Bug-Submit: https://gitlab.freedesktop.org/libevdev/python-libevdev/-/issues/new +Repository: https://gitlab.freedesktop.org/libevdev/python-libevdev.git +Repository-Browse: https://gitlab.freedesktop.org/libevdev/python-libevdev diff --git a/debian/watch b/debian/watch index ac95761..4f77167 100644 --- a/debian/watch +++ b/debian/watch @@ -1,5 +1,5 @@ version=4 -opts="filenamemangle=s%(?:.*?)?v?(\d[\d.]*)\.tar\.gz%python-libevdev-$1.tar.gz%" \ - https://github.com/whot/python-libevdev/tags \ - (?:.*?/)?v?(\d[\d.]*)\.tar\.gz debian uupdate +opts="searchmode=plain" \ + https://gitlab.freedesktop.org/libevdev/@PACKAGE@/tags?sort=updated_desc \ + -/archive/[^/]+/@PACKAGE@@ANY_VERSION@@ARCHIVE_EXT@ diff --git a/doc/requirements.txt b/doc/requirements.txt new file mode 100644 index 0000000..43eeb8e --- /dev/null +++ b/doc/requirements.txt @@ -0,0 +1 @@ +sphinx-rtd-theme==1.3.0 diff --git a/doc/source/conf.py b/doc/source/conf.py index cd47a0c..a3902f7 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # libevdev Python wrapper documentation build configuration file, created by # sphinx-quickstart on Fri Feb 26 08:28:42 2016. @@ -18,57 +17,57 @@ # 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('.')) +# sys.path.insert(0, os.path.abspath('.')) sys.path.insert(0, os.path.abspath("../../")) # -- 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.intersphinx', - 'sphinx.ext.ifconfig', + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinx.ext.ifconfig", ] # 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'libevdev Python wrapper' -copyright = u'2016-2017, Red Hat Inc. ' +project = "libevdev Python wrapper" +copyright = "2016-2022, Red Hat Inc. " # 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 short X.Y version. -version = '0.4' +version = "0.13.1" # The full version, including alpha/beta/rc tags. release = version # 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. @@ -76,10 +75,10 @@ # 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::). @@ -87,101 +86,101 @@ # 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 = [] # If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False +# keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'sphinx_rtd_theme' +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 # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # 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 = [] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. -#html_extra_path = [] +# html_extra_path = [] # 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 +# 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 = 'libevdevPythonwrapperdoc' +htmlhelp_basename = "libevdevPythonwrapperdoc" # -- Options for LaTeX output --------------------------------------------- @@ -189,10 +188,8 @@ 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': '', } @@ -201,29 +198,34 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - ('index', 'libevdevPythonwrapper.tex', u'libevdev Python wrapper Documentation', - u'Peter Hutterer', 'manual'), + ( + "index", + "libevdevPythonwrapper.tex", + "libevdev Python wrapper Documentation", + "Peter Hutterer", + "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 --------------------------------------- @@ -231,12 +233,17 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'libevdevpythonwrapper', u'libevdev Python wrapper Documentation', - [u'Peter Hutterer'], 1) + ( + "index", + "libevdevpythonwrapper", + "libevdev Python wrapper Documentation", + ["Peter Hutterer"], + 1, + ) ] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------- @@ -245,23 +252,29 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'libevdevPythonwrapper', u'libevdev Python wrapper Documentation', - u'Peter Hutterer', 'libevdevPythonwrapper', 'One line description of project.', - 'Miscellaneous'), + ( + "index", + "libevdevPythonwrapper", + "libevdev Python wrapper Documentation", + "Peter Hutterer", + "libevdevPythonwrapper", + "One line description of project.", + "Miscellaneous", + ), ] # 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' # If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False +# texinfo_no_detailmenu = False # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'http://docs.python.org/': None} +intersphinx_mapping = {"python": ("http://docs.python.org/3", None)} diff --git a/doc/source/index.rst b/doc/source/index.rst index d6aa3ca..742fbf2 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -15,8 +15,7 @@ Source code ----------- The source code for this project is available at -http://github.com/whot/python-libevdev - +https://gitlab.freedesktop.org/libevdev/python-libevdev Installation ------------ @@ -35,7 +34,7 @@ For more details on using pip and the PyPI, please see https://pypi.python.org/p Otherwise, you can install it from the git repository:: - git clone http://github.com/whot/python-libevdev + git clone https://gitlab.freedesktop.org/libevdev/python-libevdev.git cd python-libevdev sudo ./setup.py install @@ -86,10 +85,11 @@ page. To read events from an existing device:: import libevdev + import sys fd = open('/dev/input/event0', 'rb') d = libevdev.Device(fd) - if not d.has(libevdev.EV_KEY.BTN_LEFT): + if not d.has(libevdev.BTN_LEFT): print('This does not look like a mouse device') sys.exit(0) @@ -100,9 +100,9 @@ To read events from an existing device:: if not e.matches(libevdev.EV_KEY): continue - if e.matches(libevdev.EV_KEY.BTN_LEFT): + if e.matches(libevdev.BTN_LEFT): print('Left button event') - elif e.matches(libevdev.EV_KEY.BTN_RIGHT): + elif e.matches(libevdev.BTN_RIGHT): print('Right button event') .. note:: @@ -118,15 +118,15 @@ To create a new uinput device with a specific set of events:: d.name = 'some test device' d.enable(libevdev.EV_REL.REL_X) d.enable(libevdev.EV_REL.REL_Y) - d.enable(libevdev.EV_KEY.BTN_LEFT) - d.enable(libevdev.EV_KEY.BTN_MIDDLE) - d.enable(libevdev.EV_KEY.BTN_RIGHT) + d.enable(libevdev.BTN_LEFT) + d.enable(libevdev.BTN_MIDDLE) + d.enable(libevdev.BTN_RIGHT) uinput = d.create_uinput_device() print('new uinput test device at {}'.format(uinput.devnode)) - events = [InputEvent(libevdev.EV_REL.REL_X, 1), - InputEvent(libevdev.EV_REL.REL_Y, 1), - InputEvent(libevdev.EV_SYN.SYN_REPORT, 0)] + events = [InputEvent(libevdev.REL_X, 1), + InputEvent(libevdev.REL_Y, 1), + InputEvent(libevdev.SYN_REPORT, 0)] uinput.send_events(events) .. note:: diff --git a/doc/source/libevdev.rst b/doc/source/libevdev.rst index 2105648..d8ef144 100644 --- a/doc/source/libevdev.rst +++ b/doc/source/libevdev.rst @@ -4,35 +4,34 @@ libevdev package Submodules ---------- -libevdev\.const module ----------------------- +libevdev.const module +--------------------- .. automodule:: libevdev.const - :members: - :undoc-members: - :show-inheritance: + :members: + :show-inheritance: + :undoc-members: -libevdev\.device module ------------------------ +libevdev.device module +---------------------- .. automodule:: libevdev.device - :members: - :undoc-members: - :show-inheritance: + :members: + :show-inheritance: + :undoc-members: -libevdev\.event module ----------------------- +libevdev.event module +--------------------- .. automodule:: libevdev.event - :members: - :undoc-members: - :show-inheritance: - + :members: + :show-inheritance: + :undoc-members: Module contents --------------- .. automodule:: libevdev - :members: - :undoc-members: - :show-inheritance: + :members: + :show-inheritance: + :undoc-members: diff --git a/doc/source/tutorial.rst b/doc/source/tutorial.rst index 86293e3..8750024 100644 --- a/doc/source/tutorial.rst +++ b/doc/source/tutorial.rst @@ -4,7 +4,9 @@ Tutorial This is a tutorial on using libevdev's Python wrapper. It is not a tutorial on the evdev protocol, basic knowledge of how evdev works is assumed. Specifically, you're expected to know what an event type and an event code -is. If you don't, read https://who-t.blogspot.com.au/2016/09/understanding-evdev.html first. +is. If you don't, read +`Understanding evdev +`_ first. The basic building blocks ------------------------- @@ -19,7 +21,7 @@ relies on the caller using wrapped event code objects rather than just strings and numbers, :func:`evbit() ` converts from one to the other. -Event types and codes +Event types and codes --------------------- In raw evdev, each event has an event type and an event code. These are just @@ -46,6 +48,17 @@ All event codes and types are exposed in the libevdev module namespace:: >>> print(c.name) ABS_X + # This same event code is also available directly: + >>> c = libevdev.ABS_X + >>> c == libevdev.EV_ABS.ABS_X + True + >>> print(c) + ABS_X:0 + >>> print(c.value) + 0 + >>> print(c.name) + ABS_X + As you can see, each type or code has a ``value`` and ``name`` attribute. Each type and code also references each other, so you can go from one to the other easily:: @@ -79,7 +92,7 @@ that comes out:: >>> print(libevdev.evbit(3, 0)) ABS_X:0 - >>> libevdev.evbit(3, 0) == libevdev.EV_ABS.ABS_X + >>> libevdev.evbit(3, 0) == libevdev.ABS_X True For the cases where the data is strings, it works much in the same way:: @@ -91,12 +104,12 @@ For the cases where the data is strings, it works much in the same way:: >>> print(libevdev.evbit('EV_ABS', 'ABS_X')) ABS_X:0 - >>> libevdev.evbit('EV_ABS', 'ABS_X') == libevdev.EV_ABS.ABS_X + >>> libevdev.evbit('EV_ABS', 'ABS_X') == libevdev.ABS_X True >>> print(libevdev.evbit('ABS_X')) ABS_X:0 - >>> libevdev.evbit('ABS_X') == libevdev.EV_ABS.ABS_X + >>> libevdev.evbit('ABS_X') == libevdev.ABS_X True Most of the event code strings are unique, so as you can see in the third @@ -105,6 +118,8 @@ example above, the event type isn't needed when converting from string. Ok, now that we know how to deal with event codes and types, we can move on to actually using those. +.. _opening_a_device: + Opening a device ---------------- @@ -116,9 +131,11 @@ devices either - you can easily figure that out yourself by looping through the file system or using libudev. The simplest case (and good enough for most applications) is a mere call to -``open``:: +``open``, optionally followed by a call to ``fcntl`` to switch the file +descriptor into non-blocking mode:: >>> fd = open("/dev/input/event0", "rb") + >>> fcntl.fcntl(fd, fcntl.F_SETFL, os.O_NONBLOCK) >>> device = libevdev.Device(fd) >>> print(device.name) Lid Switch @@ -128,7 +145,7 @@ events from it later or even modify the kernel device. That's it. libevdev doesn't really care how you opened the device, as long as ``fileno()`` works on it it'll take it. Now we can move on to actually -handling the device +handling the device. Querying and modifying device capabilities ------------------------------------------ @@ -165,7 +182,7 @@ behaving correctly. .. note:: - Enabling absolute axes requires extra data. See + Enabling absolute axes requires extra data. See :func:`disable ` for details. Reading events @@ -197,7 +214,7 @@ types, codes and/or values:: if e.matches(libevdev.EV_MSC): continue # don't care about those - if e.matches(libevdev.EV_REL.REL_X: + if e.matches(libevdev.EV_REL.REL_X): move_by(e.value, 0) elif e.matches(libevdev.EV_REL.REL_Y): move_by(0, e.value) @@ -212,6 +229,16 @@ comparisons:: if btn in device.events(): print('There is a button event in there') +The above examples all depened on whether ``os.O_NONBLOCK`` was set on the +file descriptor after the initial ``open`` call: + +- ``os.O_NONBLOCK`` is set: :func:`events ` + returns immediately when no events are available. +- ``os.O_NONBLOCK`` is **not** set: :func:`events + ` blocks until events become available + +See :ref:`opening_a_device` for an example on setting ``os.O_NONBLOCK``. + Creating uinput devices ----------------------- diff --git a/examples/evtest.py b/examples/evtest.py index 0c16980..8f41107 100755 --- a/examples/evtest.py +++ b/examples/evtest.py @@ -4,78 +4,87 @@ import libevdev -def print_capabilities(l): - v = l.driver_version - print("Input driver version is {}.{}.{}".format(v >> 16, (v >> 8) & 0xff, v & 0xff)) - id = l.id - print("Input device ID: bus {:#x} vendor {:#x} product {:#x} version {:#x}".format( - id["bustype"], - id["vendor"], - id["product"], - id["version"], - )) - print("Input device name: {}".format(l.name)) +def print_capabilities(dev): + v = dev.driver_version + print("Input driver version is {}.{}.{}".format(v >> 16, (v >> 8) & 0xFF, v & 0xFF)) + id = dev.id + print( + "Input device ID: bus {:#x} vendor {:#x} product {:#x} version {:#x}".format( + id["bustype"], + id["vendor"], + id["product"], + id["version"], + ) + ) + print("Input device name: {}".format(dev.name)) print("Supported events:") - for t, cs in l.evbits.items(): + for t, cs in dev.evbits.items(): print(" Event type {} ({})".format(t.value, t.name)) for c in cs: if t in [libevdev.EV_LED, libevdev.EV_SND, libevdev.EV_SW]: - v = l.event_value(t, c) + v = dev.value[c] print(" Event code {} ({}) state {}".format(c.value, c.name, v)) else: print(" Event code {} ({})".format(c.value, c.name)) if t == libevdev.EV_ABS: - a = l.absinfo[c] - print(" {:10s} {:6d}".format('Value', a.value)) - print(" {:10s} {:6d}".format('Minimum', a.minimum)) - print(" {:10s} {:6d}".format('Maximum', a.maximum)) - print(" {:10s} {:6d}".format('Fuzz', a.fuzz)) - print(" {:10s} {:6d}".format('Flat', a.flat)) - print(" {:10s} {:6d}".format('Resolution', a.resolution)) + a = dev.absinfo[c] + print(" {:10s} {:6d}".format("Value", a.value)) + print(" {:10s} {:6d}".format("Minimum", a.minimum)) + print(" {:10s} {:6d}".format("Maximum", a.maximum)) + print(" {:10s} {:6d}".format("Fuzz", a.fuzz)) + print(" {:10s} {:6d}".format("Flat", a.flat)) + print(" {:10s} {:6d}".format("Resolution", a.resolution)) print("Properties:") - for p in l.properties: + for p in dev.properties: print(" Property type {} ({})".format(p.value, p.name)) def print_event(e): - print("Event: time {}.{:06d}, ".format(e.sec, e.usec), end='') - if e.matches(libevdev.EV_SYN): - if e.matches(libevdev.EV_SYN.SYN_MT_REPORT): - print("++++++++++++++ {} ++++++++++++".format(e.code.name)) - elif e.matches(libevdev.EV_SYN.SYN_DROPPED): - print(">>>>>>>>>>>>>> {} >>>>>>>>>>>>".format(e.code.name)) - else: - print("-------------- {} ------------".format(e.code.name)) + print("Event: time {}.{:06d}, ".format(e.sec, e.usec), end="") + if e.matches(libevdev.EV_SYN): + if e.matches(libevdev.SYN_MT_REPORT): + print("++++++++++++++ {} ++++++++++++".format(e.code.name)) + elif e.matches(libevdev.SYN_DROPPED): + print(">>>>>>>>>>>>>> {} >>>>>>>>>>>>".format(e.code.name)) else: - print("type {:02x} {} code {:03x} {:20s} value {:4d}".format(e.type.value, e.type.name, e.code.value, e.code.name, e.value)) + print("-------------- {} ------------".format(e.code.name)) + else: + print( + "type {:02x} {} code {:03x} {:20s} value {:4d}".format( + e.type.value, e.type.name, e.code.value, e.code.name, e.value + ) + ) def main(args): path = args[1] try: with open(path, "rb") as fd: - l = libevdev.Device(fd) - print_capabilities(l) - print("################################\n" - "# Waiting for events #\n" - "################################") + dev = libevdev.Device(fd) + print_capabilities(dev) + print( + "################################\n" + "# Waiting for events #\n" + "################################" + ) while True: try: - for e in l.events(): + for e in dev.events(): print_event(e) except libevdev.EventsDroppedException: - for e in l.sync(): + for e in dev.sync(): print_event(e) except KeyboardInterrupt: pass except IOError as e: import errno + if e.errno == errno.EACCES: print("Insufficient permissions to access {}".format(path)) elif e.errno == errno.ENOENT: diff --git a/examples/fake-tablet.py b/examples/fake-tablet.py new file mode 100755 index 0000000..36d0091 --- /dev/null +++ b/examples/fake-tablet.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +# +# Fake tablet emulator + +import sys +import libevdev +from libevdev import InputEvent, InputAbsInfo +import time + + +def prox_in(x, y, z=0, tilt=(0, 0), pressure=100, distance=0): + return [ + InputEvent(libevdev.ABS_X, x), + InputEvent(libevdev.ABS_Y, y), + InputEvent(libevdev.ABS_Z, z), + # Note: wheel for pen/eraser must be 0 + InputEvent(libevdev.ABS_WHEEL, 0), + InputEvent(libevdev.ABS_PRESSURE, pressure), + InputEvent(libevdev.ABS_DISTANCE, distance), + InputEvent(libevdev.ABS_TILT_X, tilt[0]), + InputEvent(libevdev.ABS_TILT_Y, tilt[1]), + InputEvent(libevdev.ABS_MISC, 2083), + InputEvent(libevdev.MSC_SERIAL, 297797542), + # Change to BTN_TOOL_RUBBER for the eraser end + InputEvent(libevdev.BTN_TOOL_PEN, 1), + InputEvent(libevdev.SYN_REPORT, 0), + ] + + +def prox_out(): + return [ + InputEvent(libevdev.ABS_X, 0), + InputEvent(libevdev.ABS_Y, 0), + InputEvent(libevdev.ABS_Z, 0), + InputEvent(libevdev.ABS_WHEEL, 0), + InputEvent(libevdev.ABS_PRESSURE, 0), + InputEvent(libevdev.ABS_DISTANCE, 0), + InputEvent(libevdev.ABS_TILT_X, 0), + InputEvent(libevdev.ABS_TILT_Y, 0), + InputEvent(libevdev.ABS_MISC, 0), + InputEvent(libevdev.MSC_SERIAL, 297797542), + InputEvent(libevdev.BTN_TOOL_PEN, 0), + InputEvent(libevdev.SYN_REPORT, 0), + ] + + +def motion(x, y, z=0, tilt=(0, 0), pressure=100, distance=0): + return [ + InputEvent(libevdev.ABS_X, x), + InputEvent(libevdev.ABS_Y, y), + InputEvent(libevdev.ABS_Z, z), + InputEvent(libevdev.ABS_WHEEL, 0), + InputEvent(libevdev.ABS_PRESSURE, pressure), + InputEvent(libevdev.ABS_DISTANCE, distance), + InputEvent(libevdev.ABS_TILT_X, tilt[0]), + InputEvent(libevdev.ABS_TILT_Y, tilt[1]), + InputEvent(libevdev.MSC_SERIAL, 297797542), + InputEvent(libevdev.SYN_REPORT, 0), + ] + + +def main(args): + dev = libevdev.Device() + dev.name = "Wacom Cintiq Pro 16 Pen" + dev.id = {"bustype": 0x3, "vendor": 0x56A, "product": 0x350, "version": 0xB} + dev.enable(libevdev.BTN_TOOL_PEN) + dev.enable(libevdev.BTN_TOOL_RUBBER) + dev.enable(libevdev.BTN_TOOL_BRUSH) + dev.enable(libevdev.BTN_TOOL_PENCIL) + dev.enable(libevdev.BTN_TOOL_AIRBRUSH) + dev.enable(libevdev.BTN_TOUCH) + dev.enable(libevdev.BTN_STYLUS) + dev.enable(libevdev.BTN_STYLUS2) + dev.enable(libevdev.BTN_STYLUS3) + dev.enable(libevdev.MSC_SERIAL) + dev.enable(libevdev.INPUT_PROP_DIRECT) + + a = InputAbsInfo(minimum=0, maximum=69920, resolution=200) + dev.enable(libevdev.EV_ABS.ABS_X, data=a) + a = InputAbsInfo(minimum=0, maximum=39980, resolution=200) + dev.enable(libevdev.EV_ABS.ABS_Y, data=a) + a = InputAbsInfo(minimum=-900, maximum=899, resolution=287) + dev.enable(libevdev.EV_ABS.ABS_Z, data=a) + a = InputAbsInfo(minimum=0, maximum=2047) + dev.enable(libevdev.EV_ABS.ABS_WHEEL, data=a) + a = InputAbsInfo(minimum=0, maximum=8196) + dev.enable(libevdev.EV_ABS.ABS_PRESSURE, data=a) + a = InputAbsInfo(minimum=0, maximum=63) + dev.enable(libevdev.EV_ABS.ABS_DISTANCE, data=a) + a = InputAbsInfo(minimum=-64, maximum=63, resolution=57) + dev.enable(libevdev.EV_ABS.ABS_TILT_X, data=a) + dev.enable(libevdev.EV_ABS.ABS_TILT_Y, data=a) + a = InputAbsInfo(minimum=0, maximum=0) + dev.enable(libevdev.EV_ABS.ABS_MISC, data=a) + + try: + uinput = dev.create_uinput_device() + print("New device at {} ({})".format(uinput.devnode, uinput.syspath)) + + # Sleep for a bit so udev, libinput, Xorg, Wayland, ... all have had + # a chance to see the device and initialize it. Otherwise the event + # will be sent by the kernel but nothing is ready to listen to the + # device yet. + time.sleep(1) + + x, y = 3000, 5000 + + events = prox_in(x, y) + uinput.send_events(events) + time.sleep(0.012) + + for _ in range(5): + x += 1000 + y += 1000 + events = motion(x, y) + uinput.send_events(events) + time.sleep(0.012) + + events = prox_out() + uinput.send_events(events) + time.sleep(0.012) + except OSError as e: + print(e) + + +if __name__ == "__main__": + main(sys.argv) diff --git a/examples/filter-device-events.py b/examples/filter-device-events.py index 05aaa22..6d40d05 100755 --- a/examples/filter-device-events.py +++ b/examples/filter-device-events.py @@ -14,16 +14,16 @@ def main(args): code_from = libevdev.evbit(args[2]) code_to = libevdev.evbit(args[3]) - print('Remapping {} to {}'.format(code_from, code_to)) + print("Remapping {} to {}".format(code_from, code_to)) - fd = open(path, 'rb') + fd = open(path, "rb") d = libevdev.Device(fd) d.grab() # create a duplicate of our input device d.enable(code_to) # make sure the code we map to is available uidev = d.create_uinput_device() - print('Device is at {}'.format(uidev.devnode)) + print("Device is at {}".format(uidev.devnode)) while True: for e in d.events(): diff --git a/examples/kernel-device-update.py b/examples/kernel-device-update.py index 3f10c4e..2336eab 100755 --- a/examples/kernel-device-update.py +++ b/examples/kernel-device-update.py @@ -8,20 +8,21 @@ import sys import libevdev + def main(args): path = args[1] axis = args[2] field = args[3] value = int(args[4]) - assert field in ['minimum', 'maximum', 'resolution'] + assert field in ["minimum", "maximum", "resolution"] axis = libevdev.evbit(axis) assert axis is not None - fd = open(path, 'rb') + fd = open(path, "rb") d = libevdev.Device(fd) if not d.has(axis): - print('Device does not have axis {}'.format(axis)) + print("Device does not have axis {}".format(axis)) sys.exit(1) a = d.absinfo[axis] @@ -38,4 +39,3 @@ def main(args): print(" .. the numeric value to set the axis field to") sys.exit(1) main(sys.argv) - diff --git a/examples/led-toggle.py b/examples/led-toggle.py new file mode 100755 index 0000000..28eacae --- /dev/null +++ b/examples/led-toggle.py @@ -0,0 +1,74 @@ +#!/usr/bin/python3 + +import libevdev +import sys + + +def toggle(path, ledstr): + ledmap = { + "numlock": (libevdev.LED_NUML, libevdev.KEY_NUMLOCK), + "capslock": (libevdev.LED_CAPSL, libevdev.KEY_CAPSLOCK), + "scrolllock": (libevdev.LED_SCROLLL, libevdev.KEY_SCROLLLOCK), + } + + if ledstr not in ledmap: + print( + 'Unknown LED: "{}". Use one of "{}".'.format( + ledstr, '", "'.join(ledmap.keys()) + ) + ) + sys.exit(1) + + led, key = ledmap[ledstr] + + with open(path, "r+b", buffering=0) as fd: + d = libevdev.Device(fd) + if not d.has(led): + print("Device does not have a {} LED".format(ledstr)) + sys.exit(0) + + if not d.has(key): + print("Device does not have a {} key".format(ledstr)) + sys.exit(0) + + state = d.value[key] + print("{} {}".format(ledstr, "on" if state else "off")) + + while True: + for e in d.events(): + if not e.matches(key): + continue + + if not e.value: + continue + + state = not state + d.set_leds([(led, state)]) + print("{} {}".format(ledstr, "on" if state else "off")) + + +if __name__ == "__main__": + if len(sys.argv) < 3: + print( + "Usage: {} /dev/input/eventX {{numlock|capslock|scrolllock}}".format( + sys.argv[0] + ) + ) + sys.exit(1) + + path = sys.argv[1] + ledstr = sys.argv[2] + + try: + toggle(path, ledstr) + except KeyboardInterrupt: + pass + except IOError as e: + import errno + + if e.errno == errno.EACCES: + print("Insufficient permissions to access {}".format(path)) + elif e.errno == errno.ENOENT: + print("Device {} does not exist".format(path)) + else: + raise e diff --git a/examples/uinput.py b/examples/uinput.py index 9fee4f7..1e84f40 100755 --- a/examples/uinput.py +++ b/examples/uinput.py @@ -3,21 +3,34 @@ import sys import libevdev from libevdev import InputEvent +import time def main(args): dev = libevdev.Device() dev.name = "test device" - dev.enable(libevdev.EV_REL.REL_X) - dev.enable(libevdev.EV_REL.REL_Y) + dev.enable(libevdev.REL_X) + dev.enable(libevdev.REL_Y) + dev.enable(libevdev.BTN_LEFT) + dev.enable(libevdev.BTN_RIGHT) try: uinput = dev.create_uinput_device() print("New device at {} ({})".format(uinput.devnode, uinput.syspath)) - events = [InputEvent(libevdev.EV_REL.REL_X, -1), - InputEvent(libevdev.EV_REL.REL_Y, 1), - InputEvent(libevdev.EV_SYN.SYN_REPORT, 0)] - uinput.send_events(events) + # Sleep for a bit so udev, libinput, Xorg, Wayland, ... all have had + # a chance to see the device and initialize it. Otherwise the event + # will be sent by the kernel but nothing is ready to listen to the + # device yet. + time.sleep(1) + + for _ in range(5): + events = [ + InputEvent(libevdev.REL_X, -1), + InputEvent(libevdev.REL_Y, 1), + InputEvent(libevdev.SYN_REPORT, 0), + ] + time.sleep(0.012) + uinput.send_events(events) except OSError as e: print(e) diff --git a/libevdev/__init__.py b/libevdev/__init__.py index 5cd4466..6c29a9f 100644 --- a/libevdev/__init__.py +++ b/libevdev/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: latin-1 -*- # Copyright © 2016 Red Hat, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a @@ -24,3 +23,17 @@ from .device import InvalidFileError, EventsDroppedException, InvalidArgumentException from .event import InputEvent from .const import evbit, propbit, EventType, EventCode, InputProperty + +__all__ = [ + "Device", + "InputAbsInfo", + "InvalidFileError", + "EventsDroppedException", + "InvalidArgumentException", + "InputEvent", + "evbit", + "propbit", + "EventType", + "EventCode", + "InputProperty", +] diff --git a/libevdev/__init__.pyi b/libevdev/__init__.pyi new file mode 100644 index 0000000..65fc4cf --- /dev/null +++ b/libevdev/__init__.pyi @@ -0,0 +1,2309 @@ +# This file is generated, do not edit +from typing import Type + +from libevdev import device +from libevdev import event +from libevdev import const + +Device: Type[device.Device] +InputAbsInfo: Type[device.InputAbsInfo] +InvalidFileError: Type[device.InvalidFileError] +EventsDroppedException: Type[device.EventsDroppedException] +InvalidArgumentException: Type[device.InvalidArgumentException] +InputEvent: Type[event.InputEvent] +EventType: Type[const.EventType] +EventCode: Type[const.EventCode] +InputProperty: Type[const.InputProperty] + +def evbit( + evtype: int | str, evcode: int | str | None = None +) -> const.EventCode | const.EventType | None: ... +def propbit(prop: int | str) -> const.InputProperty | None: ... + +class _EV_SYN(const.EventType): + @property + def SYN_REPORT(self) -> const.EventCode: ... + @property + def SYN_CONFIG(self) -> const.EventCode: ... + @property + def SYN_MT_REPORT(self) -> const.EventCode: ... + @property + def SYN_DROPPED(self) -> const.EventCode: ... + @property + def SYN_MAX(self) -> const.EventCode: ... + +class _EV_KEY(const.EventType): + @property + def KEY_RESERVED(self) -> const.EventCode: ... + @property + def KEY_ESC(self) -> const.EventCode: ... + @property + def KEY_1(self) -> const.EventCode: ... + @property + def KEY_2(self) -> const.EventCode: ... + @property + def KEY_3(self) -> const.EventCode: ... + @property + def KEY_4(self) -> const.EventCode: ... + @property + def KEY_5(self) -> const.EventCode: ... + @property + def KEY_6(self) -> const.EventCode: ... + @property + def KEY_7(self) -> const.EventCode: ... + @property + def KEY_8(self) -> const.EventCode: ... + @property + def KEY_9(self) -> const.EventCode: ... + @property + def KEY_0(self) -> const.EventCode: ... + @property + def KEY_MINUS(self) -> const.EventCode: ... + @property + def KEY_EQUAL(self) -> const.EventCode: ... + @property + def KEY_BACKSPACE(self) -> const.EventCode: ... + @property + def KEY_TAB(self) -> const.EventCode: ... + @property + def KEY_Q(self) -> const.EventCode: ... + @property + def KEY_W(self) -> const.EventCode: ... + @property + def KEY_E(self) -> const.EventCode: ... + @property + def KEY_R(self) -> const.EventCode: ... + @property + def KEY_T(self) -> const.EventCode: ... + @property + def KEY_Y(self) -> const.EventCode: ... + @property + def KEY_U(self) -> const.EventCode: ... + @property + def KEY_I(self) -> const.EventCode: ... + @property + def KEY_O(self) -> const.EventCode: ... + @property + def KEY_P(self) -> const.EventCode: ... + @property + def KEY_LEFTBRACE(self) -> const.EventCode: ... + @property + def KEY_RIGHTBRACE(self) -> const.EventCode: ... + @property + def KEY_ENTER(self) -> const.EventCode: ... + @property + def KEY_LEFTCTRL(self) -> const.EventCode: ... + @property + def KEY_A(self) -> const.EventCode: ... + @property + def KEY_S(self) -> const.EventCode: ... + @property + def KEY_D(self) -> const.EventCode: ... + @property + def KEY_F(self) -> const.EventCode: ... + @property + def KEY_G(self) -> const.EventCode: ... + @property + def KEY_H(self) -> const.EventCode: ... + @property + def KEY_J(self) -> const.EventCode: ... + @property + def KEY_K(self) -> const.EventCode: ... + @property + def KEY_L(self) -> const.EventCode: ... + @property + def KEY_SEMICOLON(self) -> const.EventCode: ... + @property + def KEY_APOSTROPHE(self) -> const.EventCode: ... + @property + def KEY_GRAVE(self) -> const.EventCode: ... + @property + def KEY_LEFTSHIFT(self) -> const.EventCode: ... + @property + def KEY_BACKSLASH(self) -> const.EventCode: ... + @property + def KEY_Z(self) -> const.EventCode: ... + @property + def KEY_X(self) -> const.EventCode: ... + @property + def KEY_C(self) -> const.EventCode: ... + @property + def KEY_V(self) -> const.EventCode: ... + @property + def KEY_B(self) -> const.EventCode: ... + @property + def KEY_N(self) -> const.EventCode: ... + @property + def KEY_M(self) -> const.EventCode: ... + @property + def KEY_COMMA(self) -> const.EventCode: ... + @property + def KEY_DOT(self) -> const.EventCode: ... + @property + def KEY_SLASH(self) -> const.EventCode: ... + @property + def KEY_RIGHTSHIFT(self) -> const.EventCode: ... + @property + def KEY_KPASTERISK(self) -> const.EventCode: ... + @property + def KEY_LEFTALT(self) -> const.EventCode: ... + @property + def KEY_SPACE(self) -> const.EventCode: ... + @property + def KEY_CAPSLOCK(self) -> const.EventCode: ... + @property + def KEY_F1(self) -> const.EventCode: ... + @property + def KEY_F2(self) -> const.EventCode: ... + @property + def KEY_F3(self) -> const.EventCode: ... + @property + def KEY_F4(self) -> const.EventCode: ... + @property + def KEY_F5(self) -> const.EventCode: ... + @property + def KEY_F6(self) -> const.EventCode: ... + @property + def KEY_F7(self) -> const.EventCode: ... + @property + def KEY_F8(self) -> const.EventCode: ... + @property + def KEY_F9(self) -> const.EventCode: ... + @property + def KEY_F10(self) -> const.EventCode: ... + @property + def KEY_NUMLOCK(self) -> const.EventCode: ... + @property + def KEY_SCROLLLOCK(self) -> const.EventCode: ... + @property + def KEY_KP7(self) -> const.EventCode: ... + @property + def KEY_KP8(self) -> const.EventCode: ... + @property + def KEY_KP9(self) -> const.EventCode: ... + @property + def KEY_KPMINUS(self) -> const.EventCode: ... + @property + def KEY_KP4(self) -> const.EventCode: ... + @property + def KEY_KP5(self) -> const.EventCode: ... + @property + def KEY_KP6(self) -> const.EventCode: ... + @property + def KEY_KPPLUS(self) -> const.EventCode: ... + @property + def KEY_KP1(self) -> const.EventCode: ... + @property + def KEY_KP2(self) -> const.EventCode: ... + @property + def KEY_KP3(self) -> const.EventCode: ... + @property + def KEY_KP0(self) -> const.EventCode: ... + @property + def KEY_KPDOT(self) -> const.EventCode: ... + @property + def KEY_ZENKAKUHANKAKU(self) -> const.EventCode: ... + @property + def KEY_102ND(self) -> const.EventCode: ... + @property + def KEY_F11(self) -> const.EventCode: ... + @property + def KEY_F12(self) -> const.EventCode: ... + @property + def KEY_RO(self) -> const.EventCode: ... + @property + def KEY_KATAKANA(self) -> const.EventCode: ... + @property + def KEY_HIRAGANA(self) -> const.EventCode: ... + @property + def KEY_HENKAN(self) -> const.EventCode: ... + @property + def KEY_KATAKANAHIRAGANA(self) -> const.EventCode: ... + @property + def KEY_MUHENKAN(self) -> const.EventCode: ... + @property + def KEY_KPJPCOMMA(self) -> const.EventCode: ... + @property + def KEY_KPENTER(self) -> const.EventCode: ... + @property + def KEY_RIGHTCTRL(self) -> const.EventCode: ... + @property + def KEY_KPSLASH(self) -> const.EventCode: ... + @property + def KEY_SYSRQ(self) -> const.EventCode: ... + @property + def KEY_RIGHTALT(self) -> const.EventCode: ... + @property + def KEY_LINEFEED(self) -> const.EventCode: ... + @property + def KEY_HOME(self) -> const.EventCode: ... + @property + def KEY_UP(self) -> const.EventCode: ... + @property + def KEY_PAGEUP(self) -> const.EventCode: ... + @property + def KEY_LEFT(self) -> const.EventCode: ... + @property + def KEY_RIGHT(self) -> const.EventCode: ... + @property + def KEY_END(self) -> const.EventCode: ... + @property + def KEY_DOWN(self) -> const.EventCode: ... + @property + def KEY_PAGEDOWN(self) -> const.EventCode: ... + @property + def KEY_INSERT(self) -> const.EventCode: ... + @property + def KEY_DELETE(self) -> const.EventCode: ... + @property + def KEY_MACRO(self) -> const.EventCode: ... + @property + def KEY_MUTE(self) -> const.EventCode: ... + @property + def KEY_VOLUMEDOWN(self) -> const.EventCode: ... + @property + def KEY_VOLUMEUP(self) -> const.EventCode: ... + @property + def KEY_POWER(self) -> const.EventCode: ... + @property + def KEY_KPEQUAL(self) -> const.EventCode: ... + @property + def KEY_KPPLUSMINUS(self) -> const.EventCode: ... + @property + def KEY_PAUSE(self) -> const.EventCode: ... + @property + def KEY_SCALE(self) -> const.EventCode: ... + @property + def KEY_KPCOMMA(self) -> const.EventCode: ... + @property + def KEY_HANGEUL(self) -> const.EventCode: ... + @property + def KEY_HANJA(self) -> const.EventCode: ... + @property + def KEY_YEN(self) -> const.EventCode: ... + @property + def KEY_LEFTMETA(self) -> const.EventCode: ... + @property + def KEY_RIGHTMETA(self) -> const.EventCode: ... + @property + def KEY_COMPOSE(self) -> const.EventCode: ... + @property + def KEY_STOP(self) -> const.EventCode: ... + @property + def KEY_AGAIN(self) -> const.EventCode: ... + @property + def KEY_PROPS(self) -> const.EventCode: ... + @property + def KEY_UNDO(self) -> const.EventCode: ... + @property + def KEY_FRONT(self) -> const.EventCode: ... + @property + def KEY_COPY(self) -> const.EventCode: ... + @property + def KEY_OPEN(self) -> const.EventCode: ... + @property + def KEY_PASTE(self) -> const.EventCode: ... + @property + def KEY_FIND(self) -> const.EventCode: ... + @property + def KEY_CUT(self) -> const.EventCode: ... + @property + def KEY_HELP(self) -> const.EventCode: ... + @property + def KEY_MENU(self) -> const.EventCode: ... + @property + def KEY_CALC(self) -> const.EventCode: ... + @property + def KEY_SETUP(self) -> const.EventCode: ... + @property + def KEY_SLEEP(self) -> const.EventCode: ... + @property + def KEY_WAKEUP(self) -> const.EventCode: ... + @property + def KEY_FILE(self) -> const.EventCode: ... + @property + def KEY_SENDFILE(self) -> const.EventCode: ... + @property + def KEY_DELETEFILE(self) -> const.EventCode: ... + @property + def KEY_XFER(self) -> const.EventCode: ... + @property + def KEY_PROG1(self) -> const.EventCode: ... + @property + def KEY_PROG2(self) -> const.EventCode: ... + @property + def KEY_WWW(self) -> const.EventCode: ... + @property + def KEY_MSDOS(self) -> const.EventCode: ... + @property + def KEY_COFFEE(self) -> const.EventCode: ... + @property + def KEY_ROTATE_DISPLAY(self) -> const.EventCode: ... + @property + def KEY_CYCLEWINDOWS(self) -> const.EventCode: ... + @property + def KEY_MAIL(self) -> const.EventCode: ... + @property + def KEY_BOOKMARKS(self) -> const.EventCode: ... + @property + def KEY_COMPUTER(self) -> const.EventCode: ... + @property + def KEY_BACK(self) -> const.EventCode: ... + @property + def KEY_FORWARD(self) -> const.EventCode: ... + @property + def KEY_CLOSECD(self) -> const.EventCode: ... + @property + def KEY_EJECTCD(self) -> const.EventCode: ... + @property + def KEY_EJECTCLOSECD(self) -> const.EventCode: ... + @property + def KEY_NEXTSONG(self) -> const.EventCode: ... + @property + def KEY_PLAYPAUSE(self) -> const.EventCode: ... + @property + def KEY_PREVIOUSSONG(self) -> const.EventCode: ... + @property + def KEY_STOPCD(self) -> const.EventCode: ... + @property + def KEY_RECORD(self) -> const.EventCode: ... + @property + def KEY_REWIND(self) -> const.EventCode: ... + @property + def KEY_PHONE(self) -> const.EventCode: ... + @property + def KEY_ISO(self) -> const.EventCode: ... + @property + def KEY_CONFIG(self) -> const.EventCode: ... + @property + def KEY_HOMEPAGE(self) -> const.EventCode: ... + @property + def KEY_REFRESH(self) -> const.EventCode: ... + @property + def KEY_EXIT(self) -> const.EventCode: ... + @property + def KEY_MOVE(self) -> const.EventCode: ... + @property + def KEY_EDIT(self) -> const.EventCode: ... + @property + def KEY_SCROLLUP(self) -> const.EventCode: ... + @property + def KEY_SCROLLDOWN(self) -> const.EventCode: ... + @property + def KEY_KPLEFTPAREN(self) -> const.EventCode: ... + @property + def KEY_KPRIGHTPAREN(self) -> const.EventCode: ... + @property + def KEY_NEW(self) -> const.EventCode: ... + @property + def KEY_REDO(self) -> const.EventCode: ... + @property + def KEY_F13(self) -> const.EventCode: ... + @property + def KEY_F14(self) -> const.EventCode: ... + @property + def KEY_F15(self) -> const.EventCode: ... + @property + def KEY_F16(self) -> const.EventCode: ... + @property + def KEY_F17(self) -> const.EventCode: ... + @property + def KEY_F18(self) -> const.EventCode: ... + @property + def KEY_F19(self) -> const.EventCode: ... + @property + def KEY_F20(self) -> const.EventCode: ... + @property + def KEY_F21(self) -> const.EventCode: ... + @property + def KEY_F22(self) -> const.EventCode: ... + @property + def KEY_F23(self) -> const.EventCode: ... + @property + def KEY_F24(self) -> const.EventCode: ... + @property + def KEY_PLAYCD(self) -> const.EventCode: ... + @property + def KEY_PAUSECD(self) -> const.EventCode: ... + @property + def KEY_PROG3(self) -> const.EventCode: ... + @property + def KEY_PROG4(self) -> const.EventCode: ... + @property + def KEY_ALL_APPLICATIONS(self) -> const.EventCode: ... + @property + def KEY_SUSPEND(self) -> const.EventCode: ... + @property + def KEY_CLOSE(self) -> const.EventCode: ... + @property + def KEY_PLAY(self) -> const.EventCode: ... + @property + def KEY_FASTFORWARD(self) -> const.EventCode: ... + @property + def KEY_BASSBOOST(self) -> const.EventCode: ... + @property + def KEY_PRINT(self) -> const.EventCode: ... + @property + def KEY_HP(self) -> const.EventCode: ... + @property + def KEY_CAMERA(self) -> const.EventCode: ... + @property + def KEY_SOUND(self) -> const.EventCode: ... + @property + def KEY_QUESTION(self) -> const.EventCode: ... + @property + def KEY_EMAIL(self) -> const.EventCode: ... + @property + def KEY_CHAT(self) -> const.EventCode: ... + @property + def KEY_SEARCH(self) -> const.EventCode: ... + @property + def KEY_CONNECT(self) -> const.EventCode: ... + @property + def KEY_FINANCE(self) -> const.EventCode: ... + @property + def KEY_SPORT(self) -> const.EventCode: ... + @property + def KEY_SHOP(self) -> const.EventCode: ... + @property + def KEY_ALTERASE(self) -> const.EventCode: ... + @property + def KEY_CANCEL(self) -> const.EventCode: ... + @property + def KEY_BRIGHTNESSDOWN(self) -> const.EventCode: ... + @property + def KEY_BRIGHTNESSUP(self) -> const.EventCode: ... + @property + def KEY_MEDIA(self) -> const.EventCode: ... + @property + def KEY_SWITCHVIDEOMODE(self) -> const.EventCode: ... + @property + def KEY_KBDILLUMTOGGLE(self) -> const.EventCode: ... + @property + def KEY_KBDILLUMDOWN(self) -> const.EventCode: ... + @property + def KEY_KBDILLUMUP(self) -> const.EventCode: ... + @property + def KEY_SEND(self) -> const.EventCode: ... + @property + def KEY_REPLY(self) -> const.EventCode: ... + @property + def KEY_FORWARDMAIL(self) -> const.EventCode: ... + @property + def KEY_SAVE(self) -> const.EventCode: ... + @property + def KEY_DOCUMENTS(self) -> const.EventCode: ... + @property + def KEY_BATTERY(self) -> const.EventCode: ... + @property + def KEY_BLUETOOTH(self) -> const.EventCode: ... + @property + def KEY_WLAN(self) -> const.EventCode: ... + @property + def KEY_UWB(self) -> const.EventCode: ... + @property + def KEY_UNKNOWN(self) -> const.EventCode: ... + @property + def KEY_VIDEO_NEXT(self) -> const.EventCode: ... + @property + def KEY_VIDEO_PREV(self) -> const.EventCode: ... + @property + def KEY_BRIGHTNESS_CYCLE(self) -> const.EventCode: ... + @property + def KEY_BRIGHTNESS_AUTO(self) -> const.EventCode: ... + @property + def KEY_DISPLAY_OFF(self) -> const.EventCode: ... + @property + def KEY_WWAN(self) -> const.EventCode: ... + @property + def KEY_RFKILL(self) -> const.EventCode: ... + @property + def KEY_MICMUTE(self) -> const.EventCode: ... + @property + def BTN_0(self) -> const.EventCode: ... + @property + def BTN_1(self) -> const.EventCode: ... + @property + def BTN_2(self) -> const.EventCode: ... + @property + def BTN_3(self) -> const.EventCode: ... + @property + def BTN_4(self) -> const.EventCode: ... + @property + def BTN_5(self) -> const.EventCode: ... + @property + def BTN_6(self) -> const.EventCode: ... + @property + def BTN_7(self) -> const.EventCode: ... + @property + def BTN_8(self) -> const.EventCode: ... + @property + def BTN_9(self) -> const.EventCode: ... + @property + def BTN_LEFT(self) -> const.EventCode: ... + @property + def BTN_RIGHT(self) -> const.EventCode: ... + @property + def BTN_MIDDLE(self) -> const.EventCode: ... + @property + def BTN_SIDE(self) -> const.EventCode: ... + @property + def BTN_EXTRA(self) -> const.EventCode: ... + @property + def BTN_FORWARD(self) -> const.EventCode: ... + @property + def BTN_BACK(self) -> const.EventCode: ... + @property + def BTN_TASK(self) -> const.EventCode: ... + @property + def BTN_TRIGGER(self) -> const.EventCode: ... + @property + def BTN_THUMB(self) -> const.EventCode: ... + @property + def BTN_THUMB2(self) -> const.EventCode: ... + @property + def BTN_TOP(self) -> const.EventCode: ... + @property + def BTN_TOP2(self) -> const.EventCode: ... + @property + def BTN_PINKIE(self) -> const.EventCode: ... + @property + def BTN_BASE(self) -> const.EventCode: ... + @property + def BTN_BASE2(self) -> const.EventCode: ... + @property + def BTN_BASE3(self) -> const.EventCode: ... + @property + def BTN_BASE4(self) -> const.EventCode: ... + @property + def BTN_BASE5(self) -> const.EventCode: ... + @property + def BTN_BASE6(self) -> const.EventCode: ... + @property + def BTN_DEAD(self) -> const.EventCode: ... + @property + def BTN_SOUTH(self) -> const.EventCode: ... + @property + def BTN_EAST(self) -> const.EventCode: ... + @property + def BTN_C(self) -> const.EventCode: ... + @property + def BTN_NORTH(self) -> const.EventCode: ... + @property + def BTN_WEST(self) -> const.EventCode: ... + @property + def BTN_Z(self) -> const.EventCode: ... + @property + def BTN_TL(self) -> const.EventCode: ... + @property + def BTN_TR(self) -> const.EventCode: ... + @property + def BTN_TL2(self) -> const.EventCode: ... + @property + def BTN_TR2(self) -> const.EventCode: ... + @property + def BTN_SELECT(self) -> const.EventCode: ... + @property + def BTN_START(self) -> const.EventCode: ... + @property + def BTN_MODE(self) -> const.EventCode: ... + @property + def BTN_THUMBL(self) -> const.EventCode: ... + @property + def BTN_THUMBR(self) -> const.EventCode: ... + @property + def BTN_TOOL_PEN(self) -> const.EventCode: ... + @property + def BTN_TOOL_RUBBER(self) -> const.EventCode: ... + @property + def BTN_TOOL_BRUSH(self) -> const.EventCode: ... + @property + def BTN_TOOL_PENCIL(self) -> const.EventCode: ... + @property + def BTN_TOOL_AIRBRUSH(self) -> const.EventCode: ... + @property + def BTN_TOOL_FINGER(self) -> const.EventCode: ... + @property + def BTN_TOOL_MOUSE(self) -> const.EventCode: ... + @property + def BTN_TOOL_LENS(self) -> const.EventCode: ... + @property + def BTN_TOOL_QUINTTAP(self) -> const.EventCode: ... + @property + def BTN_STYLUS3(self) -> const.EventCode: ... + @property + def BTN_TOUCH(self) -> const.EventCode: ... + @property + def BTN_STYLUS(self) -> const.EventCode: ... + @property + def BTN_STYLUS2(self) -> const.EventCode: ... + @property + def BTN_TOOL_DOUBLETAP(self) -> const.EventCode: ... + @property + def BTN_TOOL_TRIPLETAP(self) -> const.EventCode: ... + @property + def BTN_TOOL_QUADTAP(self) -> const.EventCode: ... + @property + def BTN_GEAR_DOWN(self) -> const.EventCode: ... + @property + def BTN_GEAR_UP(self) -> const.EventCode: ... + @property + def KEY_OK(self) -> const.EventCode: ... + @property + def KEY_SELECT(self) -> const.EventCode: ... + @property + def KEY_GOTO(self) -> const.EventCode: ... + @property + def KEY_CLEAR(self) -> const.EventCode: ... + @property + def KEY_POWER2(self) -> const.EventCode: ... + @property + def KEY_OPTION(self) -> const.EventCode: ... + @property + def KEY_INFO(self) -> const.EventCode: ... + @property + def KEY_TIME(self) -> const.EventCode: ... + @property + def KEY_VENDOR(self) -> const.EventCode: ... + @property + def KEY_ARCHIVE(self) -> const.EventCode: ... + @property + def KEY_PROGRAM(self) -> const.EventCode: ... + @property + def KEY_CHANNEL(self) -> const.EventCode: ... + @property + def KEY_FAVORITES(self) -> const.EventCode: ... + @property + def KEY_EPG(self) -> const.EventCode: ... + @property + def KEY_PVR(self) -> const.EventCode: ... + @property + def KEY_MHP(self) -> const.EventCode: ... + @property + def KEY_LANGUAGE(self) -> const.EventCode: ... + @property + def KEY_TITLE(self) -> const.EventCode: ... + @property + def KEY_SUBTITLE(self) -> const.EventCode: ... + @property + def KEY_ANGLE(self) -> const.EventCode: ... + @property + def KEY_FULL_SCREEN(self) -> const.EventCode: ... + @property + def KEY_MODE(self) -> const.EventCode: ... + @property + def KEY_KEYBOARD(self) -> const.EventCode: ... + @property + def KEY_ASPECT_RATIO(self) -> const.EventCode: ... + @property + def KEY_PC(self) -> const.EventCode: ... + @property + def KEY_TV(self) -> const.EventCode: ... + @property + def KEY_TV2(self) -> const.EventCode: ... + @property + def KEY_VCR(self) -> const.EventCode: ... + @property + def KEY_VCR2(self) -> const.EventCode: ... + @property + def KEY_SAT(self) -> const.EventCode: ... + @property + def KEY_SAT2(self) -> const.EventCode: ... + @property + def KEY_CD(self) -> const.EventCode: ... + @property + def KEY_TAPE(self) -> const.EventCode: ... + @property + def KEY_RADIO(self) -> const.EventCode: ... + @property + def KEY_TUNER(self) -> const.EventCode: ... + @property + def KEY_PLAYER(self) -> const.EventCode: ... + @property + def KEY_TEXT(self) -> const.EventCode: ... + @property + def KEY_DVD(self) -> const.EventCode: ... + @property + def KEY_AUX(self) -> const.EventCode: ... + @property + def KEY_MP3(self) -> const.EventCode: ... + @property + def KEY_AUDIO(self) -> const.EventCode: ... + @property + def KEY_VIDEO(self) -> const.EventCode: ... + @property + def KEY_DIRECTORY(self) -> const.EventCode: ... + @property + def KEY_LIST(self) -> const.EventCode: ... + @property + def KEY_MEMO(self) -> const.EventCode: ... + @property + def KEY_CALENDAR(self) -> const.EventCode: ... + @property + def KEY_RED(self) -> const.EventCode: ... + @property + def KEY_GREEN(self) -> const.EventCode: ... + @property + def KEY_YELLOW(self) -> const.EventCode: ... + @property + def KEY_BLUE(self) -> const.EventCode: ... + @property + def KEY_CHANNELUP(self) -> const.EventCode: ... + @property + def KEY_CHANNELDOWN(self) -> const.EventCode: ... + @property + def KEY_FIRST(self) -> const.EventCode: ... + @property + def KEY_LAST(self) -> const.EventCode: ... + @property + def KEY_AB(self) -> const.EventCode: ... + @property + def KEY_NEXT(self) -> const.EventCode: ... + @property + def KEY_RESTART(self) -> const.EventCode: ... + @property + def KEY_SLOW(self) -> const.EventCode: ... + @property + def KEY_SHUFFLE(self) -> const.EventCode: ... + @property + def KEY_BREAK(self) -> const.EventCode: ... + @property + def KEY_PREVIOUS(self) -> const.EventCode: ... + @property + def KEY_DIGITS(self) -> const.EventCode: ... + @property + def KEY_TEEN(self) -> const.EventCode: ... + @property + def KEY_TWEN(self) -> const.EventCode: ... + @property + def KEY_VIDEOPHONE(self) -> const.EventCode: ... + @property + def KEY_GAMES(self) -> const.EventCode: ... + @property + def KEY_ZOOMIN(self) -> const.EventCode: ... + @property + def KEY_ZOOMOUT(self) -> const.EventCode: ... + @property + def KEY_ZOOMRESET(self) -> const.EventCode: ... + @property + def KEY_WORDPROCESSOR(self) -> const.EventCode: ... + @property + def KEY_EDITOR(self) -> const.EventCode: ... + @property + def KEY_SPREADSHEET(self) -> const.EventCode: ... + @property + def KEY_GRAPHICSEDITOR(self) -> const.EventCode: ... + @property + def KEY_PRESENTATION(self) -> const.EventCode: ... + @property + def KEY_DATABASE(self) -> const.EventCode: ... + @property + def KEY_NEWS(self) -> const.EventCode: ... + @property + def KEY_VOICEMAIL(self) -> const.EventCode: ... + @property + def KEY_ADDRESSBOOK(self) -> const.EventCode: ... + @property + def KEY_MESSENGER(self) -> const.EventCode: ... + @property + def KEY_DISPLAYTOGGLE(self) -> const.EventCode: ... + @property + def KEY_SPELLCHECK(self) -> const.EventCode: ... + @property + def KEY_LOGOFF(self) -> const.EventCode: ... + @property + def KEY_DOLLAR(self) -> const.EventCode: ... + @property + def KEY_EURO(self) -> const.EventCode: ... + @property + def KEY_FRAMEBACK(self) -> const.EventCode: ... + @property + def KEY_FRAMEFORWARD(self) -> const.EventCode: ... + @property + def KEY_CONTEXT_MENU(self) -> const.EventCode: ... + @property + def KEY_MEDIA_REPEAT(self) -> const.EventCode: ... + @property + def KEY_10CHANNELSUP(self) -> const.EventCode: ... + @property + def KEY_10CHANNELSDOWN(self) -> const.EventCode: ... + @property + def KEY_IMAGES(self) -> const.EventCode: ... + @property + def KEY_NOTIFICATION_CENTER(self) -> const.EventCode: ... + @property + def KEY_PICKUP_PHONE(self) -> const.EventCode: ... + @property + def KEY_HANGUP_PHONE(self) -> const.EventCode: ... + @property + def KEY_LINK_PHONE(self) -> const.EventCode: ... + @property + def KEY_DEL_EOL(self) -> const.EventCode: ... + @property + def KEY_DEL_EOS(self) -> const.EventCode: ... + @property + def KEY_INS_LINE(self) -> const.EventCode: ... + @property + def KEY_DEL_LINE(self) -> const.EventCode: ... + @property + def KEY_FN(self) -> const.EventCode: ... + @property + def KEY_FN_ESC(self) -> const.EventCode: ... + @property + def KEY_FN_F1(self) -> const.EventCode: ... + @property + def KEY_FN_F2(self) -> const.EventCode: ... + @property + def KEY_FN_F3(self) -> const.EventCode: ... + @property + def KEY_FN_F4(self) -> const.EventCode: ... + @property + def KEY_FN_F5(self) -> const.EventCode: ... + @property + def KEY_FN_F6(self) -> const.EventCode: ... + @property + def KEY_FN_F7(self) -> const.EventCode: ... + @property + def KEY_FN_F8(self) -> const.EventCode: ... + @property + def KEY_FN_F9(self) -> const.EventCode: ... + @property + def KEY_FN_F10(self) -> const.EventCode: ... + @property + def KEY_FN_F11(self) -> const.EventCode: ... + @property + def KEY_FN_F12(self) -> const.EventCode: ... + @property + def KEY_FN_1(self) -> const.EventCode: ... + @property + def KEY_FN_2(self) -> const.EventCode: ... + @property + def KEY_FN_D(self) -> const.EventCode: ... + @property + def KEY_FN_E(self) -> const.EventCode: ... + @property + def KEY_FN_F(self) -> const.EventCode: ... + @property + def KEY_FN_S(self) -> const.EventCode: ... + @property + def KEY_FN_B(self) -> const.EventCode: ... + @property + def KEY_FN_RIGHT_SHIFT(self) -> const.EventCode: ... + @property + def KEY_BRL_DOT1(self) -> const.EventCode: ... + @property + def KEY_BRL_DOT2(self) -> const.EventCode: ... + @property + def KEY_BRL_DOT3(self) -> const.EventCode: ... + @property + def KEY_BRL_DOT4(self) -> const.EventCode: ... + @property + def KEY_BRL_DOT5(self) -> const.EventCode: ... + @property + def KEY_BRL_DOT6(self) -> const.EventCode: ... + @property + def KEY_BRL_DOT7(self) -> const.EventCode: ... + @property + def KEY_BRL_DOT8(self) -> const.EventCode: ... + @property + def KEY_BRL_DOT9(self) -> const.EventCode: ... + @property + def KEY_BRL_DOT10(self) -> const.EventCode: ... + @property + def KEY_NUMERIC_0(self) -> const.EventCode: ... + @property + def KEY_NUMERIC_1(self) -> const.EventCode: ... + @property + def KEY_NUMERIC_2(self) -> const.EventCode: ... + @property + def KEY_NUMERIC_3(self) -> const.EventCode: ... + @property + def KEY_NUMERIC_4(self) -> const.EventCode: ... + @property + def KEY_NUMERIC_5(self) -> const.EventCode: ... + @property + def KEY_NUMERIC_6(self) -> const.EventCode: ... + @property + def KEY_NUMERIC_7(self) -> const.EventCode: ... + @property + def KEY_NUMERIC_8(self) -> const.EventCode: ... + @property + def KEY_NUMERIC_9(self) -> const.EventCode: ... + @property + def KEY_NUMERIC_STAR(self) -> const.EventCode: ... + @property + def KEY_NUMERIC_POUND(self) -> const.EventCode: ... + @property + def KEY_NUMERIC_A(self) -> const.EventCode: ... + @property + def KEY_NUMERIC_B(self) -> const.EventCode: ... + @property + def KEY_NUMERIC_C(self) -> const.EventCode: ... + @property + def KEY_NUMERIC_D(self) -> const.EventCode: ... + @property + def KEY_CAMERA_FOCUS(self) -> const.EventCode: ... + @property + def KEY_WPS_BUTTON(self) -> const.EventCode: ... + @property + def KEY_TOUCHPAD_TOGGLE(self) -> const.EventCode: ... + @property + def KEY_TOUCHPAD_ON(self) -> const.EventCode: ... + @property + def KEY_TOUCHPAD_OFF(self) -> const.EventCode: ... + @property + def KEY_CAMERA_ZOOMIN(self) -> const.EventCode: ... + @property + def KEY_CAMERA_ZOOMOUT(self) -> const.EventCode: ... + @property + def KEY_CAMERA_UP(self) -> const.EventCode: ... + @property + def KEY_CAMERA_DOWN(self) -> const.EventCode: ... + @property + def KEY_CAMERA_LEFT(self) -> const.EventCode: ... + @property + def KEY_CAMERA_RIGHT(self) -> const.EventCode: ... + @property + def KEY_ATTENDANT_ON(self) -> const.EventCode: ... + @property + def KEY_ATTENDANT_OFF(self) -> const.EventCode: ... + @property + def KEY_ATTENDANT_TOGGLE(self) -> const.EventCode: ... + @property + def KEY_LIGHTS_TOGGLE(self) -> const.EventCode: ... + @property + def BTN_DPAD_UP(self) -> const.EventCode: ... + @property + def BTN_DPAD_DOWN(self) -> const.EventCode: ... + @property + def BTN_DPAD_LEFT(self) -> const.EventCode: ... + @property + def BTN_DPAD_RIGHT(self) -> const.EventCode: ... + @property + def BTN_GRIPL(self) -> const.EventCode: ... + @property + def BTN_GRIPR(self) -> const.EventCode: ... + @property + def BTN_GRIPL2(self) -> const.EventCode: ... + @property + def BTN_GRIPR2(self) -> const.EventCode: ... + @property + def KEY_ALS_TOGGLE(self) -> const.EventCode: ... + @property + def KEY_ROTATE_LOCK_TOGGLE(self) -> const.EventCode: ... + @property + def KEY_REFRESH_RATE_TOGGLE(self) -> const.EventCode: ... + @property + def KEY_BUTTONCONFIG(self) -> const.EventCode: ... + @property + def KEY_TASKMANAGER(self) -> const.EventCode: ... + @property + def KEY_JOURNAL(self) -> const.EventCode: ... + @property + def KEY_CONTROLPANEL(self) -> const.EventCode: ... + @property + def KEY_APPSELECT(self) -> const.EventCode: ... + @property + def KEY_SCREENSAVER(self) -> const.EventCode: ... + @property + def KEY_VOICECOMMAND(self) -> const.EventCode: ... + @property + def KEY_ASSISTANT(self) -> const.EventCode: ... + @property + def KEY_KBD_LAYOUT_NEXT(self) -> const.EventCode: ... + @property + def KEY_EMOJI_PICKER(self) -> const.EventCode: ... + @property + def KEY_DICTATE(self) -> const.EventCode: ... + @property + def KEY_CAMERA_ACCESS_ENABLE(self) -> const.EventCode: ... + @property + def KEY_CAMERA_ACCESS_DISABLE(self) -> const.EventCode: ... + @property + def KEY_CAMERA_ACCESS_TOGGLE(self) -> const.EventCode: ... + @property + def KEY_ACCESSIBILITY(self) -> const.EventCode: ... + @property + def KEY_DO_NOT_DISTURB(self) -> const.EventCode: ... + @property + def KEY_BRIGHTNESS_MIN(self) -> const.EventCode: ... + @property + def KEY_BRIGHTNESS_MAX(self) -> const.EventCode: ... + @property + def KEY_KBDINPUTASSIST_PREV(self) -> const.EventCode: ... + @property + def KEY_KBDINPUTASSIST_NEXT(self) -> const.EventCode: ... + @property + def KEY_KBDINPUTASSIST_PREVGROUP(self) -> const.EventCode: ... + @property + def KEY_KBDINPUTASSIST_NEXTGROUP(self) -> const.EventCode: ... + @property + def KEY_KBDINPUTASSIST_ACCEPT(self) -> const.EventCode: ... + @property + def KEY_KBDINPUTASSIST_CANCEL(self) -> const.EventCode: ... + @property + def KEY_RIGHT_UP(self) -> const.EventCode: ... + @property + def KEY_RIGHT_DOWN(self) -> const.EventCode: ... + @property + def KEY_LEFT_UP(self) -> const.EventCode: ... + @property + def KEY_LEFT_DOWN(self) -> const.EventCode: ... + @property + def KEY_ROOT_MENU(self) -> const.EventCode: ... + @property + def KEY_MEDIA_TOP_MENU(self) -> const.EventCode: ... + @property + def KEY_NUMERIC_11(self) -> const.EventCode: ... + @property + def KEY_NUMERIC_12(self) -> const.EventCode: ... + @property + def KEY_AUDIO_DESC(self) -> const.EventCode: ... + @property + def KEY_3D_MODE(self) -> const.EventCode: ... + @property + def KEY_NEXT_FAVORITE(self) -> const.EventCode: ... + @property + def KEY_STOP_RECORD(self) -> const.EventCode: ... + @property + def KEY_PAUSE_RECORD(self) -> const.EventCode: ... + @property + def KEY_VOD(self) -> const.EventCode: ... + @property + def KEY_UNMUTE(self) -> const.EventCode: ... + @property + def KEY_FASTREVERSE(self) -> const.EventCode: ... + @property + def KEY_SLOWREVERSE(self) -> const.EventCode: ... + @property + def KEY_DATA(self) -> const.EventCode: ... + @property + def KEY_ONSCREEN_KEYBOARD(self) -> const.EventCode: ... + @property + def KEY_PRIVACY_SCREEN_TOGGLE(self) -> const.EventCode: ... + @property + def KEY_SELECTIVE_SCREENSHOT(self) -> const.EventCode: ... + @property + def KEY_NEXT_ELEMENT(self) -> const.EventCode: ... + @property + def KEY_PREVIOUS_ELEMENT(self) -> const.EventCode: ... + @property + def KEY_AUTOPILOT_ENGAGE_TOGGLE(self) -> const.EventCode: ... + @property + def KEY_MARK_WAYPOINT(self) -> const.EventCode: ... + @property + def KEY_SOS(self) -> const.EventCode: ... + @property + def KEY_NAV_CHART(self) -> const.EventCode: ... + @property + def KEY_FISHING_CHART(self) -> const.EventCode: ... + @property + def KEY_SINGLE_RANGE_RADAR(self) -> const.EventCode: ... + @property + def KEY_DUAL_RANGE_RADAR(self) -> const.EventCode: ... + @property + def KEY_RADAR_OVERLAY(self) -> const.EventCode: ... + @property + def KEY_TRADITIONAL_SONAR(self) -> const.EventCode: ... + @property + def KEY_CLEARVU_SONAR(self) -> const.EventCode: ... + @property + def KEY_SIDEVU_SONAR(self) -> const.EventCode: ... + @property + def KEY_NAV_INFO(self) -> const.EventCode: ... + @property + def KEY_BRIGHTNESS_MENU(self) -> const.EventCode: ... + @property + def KEY_MACRO1(self) -> const.EventCode: ... + @property + def KEY_MACRO2(self) -> const.EventCode: ... + @property + def KEY_MACRO3(self) -> const.EventCode: ... + @property + def KEY_MACRO4(self) -> const.EventCode: ... + @property + def KEY_MACRO5(self) -> const.EventCode: ... + @property + def KEY_MACRO6(self) -> const.EventCode: ... + @property + def KEY_MACRO7(self) -> const.EventCode: ... + @property + def KEY_MACRO8(self) -> const.EventCode: ... + @property + def KEY_MACRO9(self) -> const.EventCode: ... + @property + def KEY_MACRO10(self) -> const.EventCode: ... + @property + def KEY_MACRO11(self) -> const.EventCode: ... + @property + def KEY_MACRO12(self) -> const.EventCode: ... + @property + def KEY_MACRO13(self) -> const.EventCode: ... + @property + def KEY_MACRO14(self) -> const.EventCode: ... + @property + def KEY_MACRO15(self) -> const.EventCode: ... + @property + def KEY_MACRO16(self) -> const.EventCode: ... + @property + def KEY_MACRO17(self) -> const.EventCode: ... + @property + def KEY_MACRO18(self) -> const.EventCode: ... + @property + def KEY_MACRO19(self) -> const.EventCode: ... + @property + def KEY_MACRO20(self) -> const.EventCode: ... + @property + def KEY_MACRO21(self) -> const.EventCode: ... + @property + def KEY_MACRO22(self) -> const.EventCode: ... + @property + def KEY_MACRO23(self) -> const.EventCode: ... + @property + def KEY_MACRO24(self) -> const.EventCode: ... + @property + def KEY_MACRO25(self) -> const.EventCode: ... + @property + def KEY_MACRO26(self) -> const.EventCode: ... + @property + def KEY_MACRO27(self) -> const.EventCode: ... + @property + def KEY_MACRO28(self) -> const.EventCode: ... + @property + def KEY_MACRO29(self) -> const.EventCode: ... + @property + def KEY_MACRO30(self) -> const.EventCode: ... + @property + def KEY_MACRO_RECORD_START(self) -> const.EventCode: ... + @property + def KEY_MACRO_RECORD_STOP(self) -> const.EventCode: ... + @property + def KEY_MACRO_PRESET_CYCLE(self) -> const.EventCode: ... + @property + def KEY_MACRO_PRESET1(self) -> const.EventCode: ... + @property + def KEY_MACRO_PRESET2(self) -> const.EventCode: ... + @property + def KEY_MACRO_PRESET3(self) -> const.EventCode: ... + @property + def KEY_KBD_LCD_MENU1(self) -> const.EventCode: ... + @property + def KEY_KBD_LCD_MENU2(self) -> const.EventCode: ... + @property + def KEY_KBD_LCD_MENU3(self) -> const.EventCode: ... + @property + def KEY_KBD_LCD_MENU4(self) -> const.EventCode: ... + @property + def KEY_KBD_LCD_MENU5(self) -> const.EventCode: ... + @property + def KEY_PERFORMANCE(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY1(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY2(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY3(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY4(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY5(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY6(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY7(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY8(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY9(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY10(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY11(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY12(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY13(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY14(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY15(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY16(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY17(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY18(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY19(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY20(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY21(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY22(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY23(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY24(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY25(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY26(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY27(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY28(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY29(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY30(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY31(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY32(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY33(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY34(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY35(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY36(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY37(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY38(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY39(self) -> const.EventCode: ... + @property + def BTN_TRIGGER_HAPPY40(self) -> const.EventCode: ... + @property + def KEY_AI_ASSISTANT(self) -> const.EventCode: ... + @property + def KEY_MAX(self) -> const.EventCode: ... + +class _EV_REL(const.EventType): + @property + def REL_X(self) -> const.EventCode: ... + @property + def REL_Y(self) -> const.EventCode: ... + @property + def REL_Z(self) -> const.EventCode: ... + @property + def REL_RX(self) -> const.EventCode: ... + @property + def REL_RY(self) -> const.EventCode: ... + @property + def REL_RZ(self) -> const.EventCode: ... + @property + def REL_HWHEEL(self) -> const.EventCode: ... + @property + def REL_DIAL(self) -> const.EventCode: ... + @property + def REL_WHEEL(self) -> const.EventCode: ... + @property + def REL_MISC(self) -> const.EventCode: ... + @property + def REL_RESERVED(self) -> const.EventCode: ... + @property + def REL_WHEEL_HI_RES(self) -> const.EventCode: ... + @property + def REL_HWHEEL_HI_RES(self) -> const.EventCode: ... + @property + def REL_MAX(self) -> const.EventCode: ... + +class _EV_ABS(const.EventType): + @property + def ABS_X(self) -> const.EventCode: ... + @property + def ABS_Y(self) -> const.EventCode: ... + @property + def ABS_Z(self) -> const.EventCode: ... + @property + def ABS_RX(self) -> const.EventCode: ... + @property + def ABS_RY(self) -> const.EventCode: ... + @property + def ABS_RZ(self) -> const.EventCode: ... + @property + def ABS_THROTTLE(self) -> const.EventCode: ... + @property + def ABS_RUDDER(self) -> const.EventCode: ... + @property + def ABS_WHEEL(self) -> const.EventCode: ... + @property + def ABS_GAS(self) -> const.EventCode: ... + @property + def ABS_BRAKE(self) -> const.EventCode: ... + @property + def ABS_HAT0X(self) -> const.EventCode: ... + @property + def ABS_HAT0Y(self) -> const.EventCode: ... + @property + def ABS_HAT1X(self) -> const.EventCode: ... + @property + def ABS_HAT1Y(self) -> const.EventCode: ... + @property + def ABS_HAT2X(self) -> const.EventCode: ... + @property + def ABS_HAT2Y(self) -> const.EventCode: ... + @property + def ABS_HAT3X(self) -> const.EventCode: ... + @property + def ABS_HAT3Y(self) -> const.EventCode: ... + @property + def ABS_PRESSURE(self) -> const.EventCode: ... + @property + def ABS_DISTANCE(self) -> const.EventCode: ... + @property + def ABS_TILT_X(self) -> const.EventCode: ... + @property + def ABS_TILT_Y(self) -> const.EventCode: ... + @property + def ABS_TOOL_WIDTH(self) -> const.EventCode: ... + @property + def ABS_VOLUME(self) -> const.EventCode: ... + @property + def ABS_PROFILE(self) -> const.EventCode: ... + @property + def ABS_MISC(self) -> const.EventCode: ... + @property + def ABS_RESERVED(self) -> const.EventCode: ... + @property + def ABS_MT_SLOT(self) -> const.EventCode: ... + @property + def ABS_MT_TOUCH_MAJOR(self) -> const.EventCode: ... + @property + def ABS_MT_TOUCH_MINOR(self) -> const.EventCode: ... + @property + def ABS_MT_WIDTH_MAJOR(self) -> const.EventCode: ... + @property + def ABS_MT_WIDTH_MINOR(self) -> const.EventCode: ... + @property + def ABS_MT_ORIENTATION(self) -> const.EventCode: ... + @property + def ABS_MT_POSITION_X(self) -> const.EventCode: ... + @property + def ABS_MT_POSITION_Y(self) -> const.EventCode: ... + @property + def ABS_MT_TOOL_TYPE(self) -> const.EventCode: ... + @property + def ABS_MT_BLOB_ID(self) -> const.EventCode: ... + @property + def ABS_MT_TRACKING_ID(self) -> const.EventCode: ... + @property + def ABS_MT_PRESSURE(self) -> const.EventCode: ... + @property + def ABS_MT_DISTANCE(self) -> const.EventCode: ... + @property + def ABS_MT_TOOL_X(self) -> const.EventCode: ... + @property + def ABS_MT_TOOL_Y(self) -> const.EventCode: ... + @property + def ABS_MAX(self) -> const.EventCode: ... + +class _EV_MSC(const.EventType): + @property + def MSC_SERIAL(self) -> const.EventCode: ... + @property + def MSC_PULSELED(self) -> const.EventCode: ... + @property + def MSC_GESTURE(self) -> const.EventCode: ... + @property + def MSC_RAW(self) -> const.EventCode: ... + @property + def MSC_SCAN(self) -> const.EventCode: ... + @property + def MSC_TIMESTAMP(self) -> const.EventCode: ... + @property + def MSC_MAX(self) -> const.EventCode: ... + +class _EV_SW(const.EventType): + @property + def SW_LID(self) -> const.EventCode: ... + @property + def SW_TABLET_MODE(self) -> const.EventCode: ... + @property + def SW_HEADPHONE_INSERT(self) -> const.EventCode: ... + @property + def SW_RFKILL_ALL(self) -> const.EventCode: ... + @property + def SW_MICROPHONE_INSERT(self) -> const.EventCode: ... + @property + def SW_DOCK(self) -> const.EventCode: ... + @property + def SW_LINEOUT_INSERT(self) -> const.EventCode: ... + @property + def SW_JACK_PHYSICAL_INSERT(self) -> const.EventCode: ... + @property + def SW_VIDEOOUT_INSERT(self) -> const.EventCode: ... + @property + def SW_CAMERA_LENS_COVER(self) -> const.EventCode: ... + @property + def SW_KEYPAD_SLIDE(self) -> const.EventCode: ... + @property + def SW_FRONT_PROXIMITY(self) -> const.EventCode: ... + @property + def SW_ROTATE_LOCK(self) -> const.EventCode: ... + @property + def SW_LINEIN_INSERT(self) -> const.EventCode: ... + @property + def SW_MUTE_DEVICE(self) -> const.EventCode: ... + @property + def SW_PEN_INSERTED(self) -> const.EventCode: ... + @property + def SW_MACHINE_COVER(self) -> const.EventCode: ... + @property + def SW_USB_INSERT(self) -> const.EventCode: ... + +class _EV_LED(const.EventType): + @property + def LED_NUML(self) -> const.EventCode: ... + @property + def LED_CAPSL(self) -> const.EventCode: ... + @property + def LED_SCROLLL(self) -> const.EventCode: ... + @property + def LED_COMPOSE(self) -> const.EventCode: ... + @property + def LED_KANA(self) -> const.EventCode: ... + @property + def LED_SLEEP(self) -> const.EventCode: ... + @property + def LED_SUSPEND(self) -> const.EventCode: ... + @property + def LED_MUTE(self) -> const.EventCode: ... + @property + def LED_MISC(self) -> const.EventCode: ... + @property + def LED_MAIL(self) -> const.EventCode: ... + @property + def LED_CHARGING(self) -> const.EventCode: ... + @property + def LED_MAX(self) -> const.EventCode: ... + +class _EV_SND(const.EventType): + @property + def SND_CLICK(self) -> const.EventCode: ... + @property + def SND_BELL(self) -> const.EventCode: ... + @property + def SND_TONE(self) -> const.EventCode: ... + @property + def SND_MAX(self) -> const.EventCode: ... + +class _EV_REP(const.EventType): + @property + def REP_DELAY(self) -> const.EventCode: ... + @property + def REP_PERIOD(self) -> const.EventCode: ... + +class _EV_FF(const.EventType): + @property + def FF_STATUS_STOPPED(self) -> const.EventCode: ... + @property + def FF_STATUS_MAX(self) -> const.EventCode: ... + @property + def FF_RUMBLE(self) -> const.EventCode: ... + @property + def FF_PERIODIC(self) -> const.EventCode: ... + @property + def FF_CONSTANT(self) -> const.EventCode: ... + @property + def FF_SPRING(self) -> const.EventCode: ... + @property + def FF_FRICTION(self) -> const.EventCode: ... + @property + def FF_DAMPER(self) -> const.EventCode: ... + @property + def FF_INERTIA(self) -> const.EventCode: ... + @property + def FF_RAMP(self) -> const.EventCode: ... + @property + def FF_SQUARE(self) -> const.EventCode: ... + @property + def FF_TRIANGLE(self) -> const.EventCode: ... + @property + def FF_SINE(self) -> const.EventCode: ... + @property + def FF_SAW_UP(self) -> const.EventCode: ... + @property + def FF_SAW_DOWN(self) -> const.EventCode: ... + @property + def FF_CUSTOM(self) -> const.EventCode: ... + @property + def FF_GAIN(self) -> const.EventCode: ... + @property + def FF_AUTOCENTER(self) -> const.EventCode: ... + @property + def FF_MAX(self) -> const.EventCode: ... + +EV_SYN: _EV_SYN +EV_KEY: _EV_KEY +EV_REL: _EV_REL +EV_ABS: _EV_ABS +EV_MSC: _EV_MSC +EV_SW: _EV_SW +EV_LED: _EV_LED +EV_SND: _EV_SND +EV_REP: _EV_REP +EV_FF: _EV_FF +SYN_REPORT: const.EventCode +SYN_CONFIG: const.EventCode +SYN_MT_REPORT: const.EventCode +SYN_DROPPED: const.EventCode +SYN_MAX: const.EventCode +KEY_RESERVED: const.EventCode +KEY_ESC: const.EventCode +KEY_1: const.EventCode +KEY_2: const.EventCode +KEY_3: const.EventCode +KEY_4: const.EventCode +KEY_5: const.EventCode +KEY_6: const.EventCode +KEY_7: const.EventCode +KEY_8: const.EventCode +KEY_9: const.EventCode +KEY_0: const.EventCode +KEY_MINUS: const.EventCode +KEY_EQUAL: const.EventCode +KEY_BACKSPACE: const.EventCode +KEY_TAB: const.EventCode +KEY_Q: const.EventCode +KEY_W: const.EventCode +KEY_E: const.EventCode +KEY_R: const.EventCode +KEY_T: const.EventCode +KEY_Y: const.EventCode +KEY_U: const.EventCode +KEY_I: const.EventCode +KEY_O: const.EventCode +KEY_P: const.EventCode +KEY_LEFTBRACE: const.EventCode +KEY_RIGHTBRACE: const.EventCode +KEY_ENTER: const.EventCode +KEY_LEFTCTRL: const.EventCode +KEY_A: const.EventCode +KEY_S: const.EventCode +KEY_D: const.EventCode +KEY_F: const.EventCode +KEY_G: const.EventCode +KEY_H: const.EventCode +KEY_J: const.EventCode +KEY_K: const.EventCode +KEY_L: const.EventCode +KEY_SEMICOLON: const.EventCode +KEY_APOSTROPHE: const.EventCode +KEY_GRAVE: const.EventCode +KEY_LEFTSHIFT: const.EventCode +KEY_BACKSLASH: const.EventCode +KEY_Z: const.EventCode +KEY_X: const.EventCode +KEY_C: const.EventCode +KEY_V: const.EventCode +KEY_B: const.EventCode +KEY_N: const.EventCode +KEY_M: const.EventCode +KEY_COMMA: const.EventCode +KEY_DOT: const.EventCode +KEY_SLASH: const.EventCode +KEY_RIGHTSHIFT: const.EventCode +KEY_KPASTERISK: const.EventCode +KEY_LEFTALT: const.EventCode +KEY_SPACE: const.EventCode +KEY_CAPSLOCK: const.EventCode +KEY_F1: const.EventCode +KEY_F2: const.EventCode +KEY_F3: const.EventCode +KEY_F4: const.EventCode +KEY_F5: const.EventCode +KEY_F6: const.EventCode +KEY_F7: const.EventCode +KEY_F8: const.EventCode +KEY_F9: const.EventCode +KEY_F10: const.EventCode +KEY_NUMLOCK: const.EventCode +KEY_SCROLLLOCK: const.EventCode +KEY_KP7: const.EventCode +KEY_KP8: const.EventCode +KEY_KP9: const.EventCode +KEY_KPMINUS: const.EventCode +KEY_KP4: const.EventCode +KEY_KP5: const.EventCode +KEY_KP6: const.EventCode +KEY_KPPLUS: const.EventCode +KEY_KP1: const.EventCode +KEY_KP2: const.EventCode +KEY_KP3: const.EventCode +KEY_KP0: const.EventCode +KEY_KPDOT: const.EventCode +KEY_ZENKAKUHANKAKU: const.EventCode +KEY_102ND: const.EventCode +KEY_F11: const.EventCode +KEY_F12: const.EventCode +KEY_RO: const.EventCode +KEY_KATAKANA: const.EventCode +KEY_HIRAGANA: const.EventCode +KEY_HENKAN: const.EventCode +KEY_KATAKANAHIRAGANA: const.EventCode +KEY_MUHENKAN: const.EventCode +KEY_KPJPCOMMA: const.EventCode +KEY_KPENTER: const.EventCode +KEY_RIGHTCTRL: const.EventCode +KEY_KPSLASH: const.EventCode +KEY_SYSRQ: const.EventCode +KEY_RIGHTALT: const.EventCode +KEY_LINEFEED: const.EventCode +KEY_HOME: const.EventCode +KEY_UP: const.EventCode +KEY_PAGEUP: const.EventCode +KEY_LEFT: const.EventCode +KEY_RIGHT: const.EventCode +KEY_END: const.EventCode +KEY_DOWN: const.EventCode +KEY_PAGEDOWN: const.EventCode +KEY_INSERT: const.EventCode +KEY_DELETE: const.EventCode +KEY_MACRO: const.EventCode +KEY_MUTE: const.EventCode +KEY_VOLUMEDOWN: const.EventCode +KEY_VOLUMEUP: const.EventCode +KEY_POWER: const.EventCode +KEY_KPEQUAL: const.EventCode +KEY_KPPLUSMINUS: const.EventCode +KEY_PAUSE: const.EventCode +KEY_SCALE: const.EventCode +KEY_KPCOMMA: const.EventCode +KEY_HANGEUL: const.EventCode +KEY_HANJA: const.EventCode +KEY_YEN: const.EventCode +KEY_LEFTMETA: const.EventCode +KEY_RIGHTMETA: const.EventCode +KEY_COMPOSE: const.EventCode +KEY_STOP: const.EventCode +KEY_AGAIN: const.EventCode +KEY_PROPS: const.EventCode +KEY_UNDO: const.EventCode +KEY_FRONT: const.EventCode +KEY_COPY: const.EventCode +KEY_OPEN: const.EventCode +KEY_PASTE: const.EventCode +KEY_FIND: const.EventCode +KEY_CUT: const.EventCode +KEY_HELP: const.EventCode +KEY_MENU: const.EventCode +KEY_CALC: const.EventCode +KEY_SETUP: const.EventCode +KEY_SLEEP: const.EventCode +KEY_WAKEUP: const.EventCode +KEY_FILE: const.EventCode +KEY_SENDFILE: const.EventCode +KEY_DELETEFILE: const.EventCode +KEY_XFER: const.EventCode +KEY_PROG1: const.EventCode +KEY_PROG2: const.EventCode +KEY_WWW: const.EventCode +KEY_MSDOS: const.EventCode +KEY_COFFEE: const.EventCode +KEY_ROTATE_DISPLAY: const.EventCode +KEY_CYCLEWINDOWS: const.EventCode +KEY_MAIL: const.EventCode +KEY_BOOKMARKS: const.EventCode +KEY_COMPUTER: const.EventCode +KEY_BACK: const.EventCode +KEY_FORWARD: const.EventCode +KEY_CLOSECD: const.EventCode +KEY_EJECTCD: const.EventCode +KEY_EJECTCLOSECD: const.EventCode +KEY_NEXTSONG: const.EventCode +KEY_PLAYPAUSE: const.EventCode +KEY_PREVIOUSSONG: const.EventCode +KEY_STOPCD: const.EventCode +KEY_RECORD: const.EventCode +KEY_REWIND: const.EventCode +KEY_PHONE: const.EventCode +KEY_ISO: const.EventCode +KEY_CONFIG: const.EventCode +KEY_HOMEPAGE: const.EventCode +KEY_REFRESH: const.EventCode +KEY_EXIT: const.EventCode +KEY_MOVE: const.EventCode +KEY_EDIT: const.EventCode +KEY_SCROLLUP: const.EventCode +KEY_SCROLLDOWN: const.EventCode +KEY_KPLEFTPAREN: const.EventCode +KEY_KPRIGHTPAREN: const.EventCode +KEY_NEW: const.EventCode +KEY_REDO: const.EventCode +KEY_F13: const.EventCode +KEY_F14: const.EventCode +KEY_F15: const.EventCode +KEY_F16: const.EventCode +KEY_F17: const.EventCode +KEY_F18: const.EventCode +KEY_F19: const.EventCode +KEY_F20: const.EventCode +KEY_F21: const.EventCode +KEY_F22: const.EventCode +KEY_F23: const.EventCode +KEY_F24: const.EventCode +KEY_PLAYCD: const.EventCode +KEY_PAUSECD: const.EventCode +KEY_PROG3: const.EventCode +KEY_PROG4: const.EventCode +KEY_ALL_APPLICATIONS: const.EventCode +KEY_SUSPEND: const.EventCode +KEY_CLOSE: const.EventCode +KEY_PLAY: const.EventCode +KEY_FASTFORWARD: const.EventCode +KEY_BASSBOOST: const.EventCode +KEY_PRINT: const.EventCode +KEY_HP: const.EventCode +KEY_CAMERA: const.EventCode +KEY_SOUND: const.EventCode +KEY_QUESTION: const.EventCode +KEY_EMAIL: const.EventCode +KEY_CHAT: const.EventCode +KEY_SEARCH: const.EventCode +KEY_CONNECT: const.EventCode +KEY_FINANCE: const.EventCode +KEY_SPORT: const.EventCode +KEY_SHOP: const.EventCode +KEY_ALTERASE: const.EventCode +KEY_CANCEL: const.EventCode +KEY_BRIGHTNESSDOWN: const.EventCode +KEY_BRIGHTNESSUP: const.EventCode +KEY_MEDIA: const.EventCode +KEY_SWITCHVIDEOMODE: const.EventCode +KEY_KBDILLUMTOGGLE: const.EventCode +KEY_KBDILLUMDOWN: const.EventCode +KEY_KBDILLUMUP: const.EventCode +KEY_SEND: const.EventCode +KEY_REPLY: const.EventCode +KEY_FORWARDMAIL: const.EventCode +KEY_SAVE: const.EventCode +KEY_DOCUMENTS: const.EventCode +KEY_BATTERY: const.EventCode +KEY_BLUETOOTH: const.EventCode +KEY_WLAN: const.EventCode +KEY_UWB: const.EventCode +KEY_UNKNOWN: const.EventCode +KEY_VIDEO_NEXT: const.EventCode +KEY_VIDEO_PREV: const.EventCode +KEY_BRIGHTNESS_CYCLE: const.EventCode +KEY_BRIGHTNESS_AUTO: const.EventCode +KEY_DISPLAY_OFF: const.EventCode +KEY_WWAN: const.EventCode +KEY_RFKILL: const.EventCode +KEY_MICMUTE: const.EventCode +BTN_0: const.EventCode +BTN_1: const.EventCode +BTN_2: const.EventCode +BTN_3: const.EventCode +BTN_4: const.EventCode +BTN_5: const.EventCode +BTN_6: const.EventCode +BTN_7: const.EventCode +BTN_8: const.EventCode +BTN_9: const.EventCode +BTN_LEFT: const.EventCode +BTN_RIGHT: const.EventCode +BTN_MIDDLE: const.EventCode +BTN_SIDE: const.EventCode +BTN_EXTRA: const.EventCode +BTN_FORWARD: const.EventCode +BTN_BACK: const.EventCode +BTN_TASK: const.EventCode +BTN_TRIGGER: const.EventCode +BTN_THUMB: const.EventCode +BTN_THUMB2: const.EventCode +BTN_TOP: const.EventCode +BTN_TOP2: const.EventCode +BTN_PINKIE: const.EventCode +BTN_BASE: const.EventCode +BTN_BASE2: const.EventCode +BTN_BASE3: const.EventCode +BTN_BASE4: const.EventCode +BTN_BASE5: const.EventCode +BTN_BASE6: const.EventCode +BTN_DEAD: const.EventCode +BTN_SOUTH: const.EventCode +BTN_EAST: const.EventCode +BTN_C: const.EventCode +BTN_NORTH: const.EventCode +BTN_WEST: const.EventCode +BTN_Z: const.EventCode +BTN_TL: const.EventCode +BTN_TR: const.EventCode +BTN_TL2: const.EventCode +BTN_TR2: const.EventCode +BTN_SELECT: const.EventCode +BTN_START: const.EventCode +BTN_MODE: const.EventCode +BTN_THUMBL: const.EventCode +BTN_THUMBR: const.EventCode +BTN_TOOL_PEN: const.EventCode +BTN_TOOL_RUBBER: const.EventCode +BTN_TOOL_BRUSH: const.EventCode +BTN_TOOL_PENCIL: const.EventCode +BTN_TOOL_AIRBRUSH: const.EventCode +BTN_TOOL_FINGER: const.EventCode +BTN_TOOL_MOUSE: const.EventCode +BTN_TOOL_LENS: const.EventCode +BTN_TOOL_QUINTTAP: const.EventCode +BTN_STYLUS3: const.EventCode +BTN_TOUCH: const.EventCode +BTN_STYLUS: const.EventCode +BTN_STYLUS2: const.EventCode +BTN_TOOL_DOUBLETAP: const.EventCode +BTN_TOOL_TRIPLETAP: const.EventCode +BTN_TOOL_QUADTAP: const.EventCode +BTN_GEAR_DOWN: const.EventCode +BTN_GEAR_UP: const.EventCode +KEY_OK: const.EventCode +KEY_SELECT: const.EventCode +KEY_GOTO: const.EventCode +KEY_CLEAR: const.EventCode +KEY_POWER2: const.EventCode +KEY_OPTION: const.EventCode +KEY_INFO: const.EventCode +KEY_TIME: const.EventCode +KEY_VENDOR: const.EventCode +KEY_ARCHIVE: const.EventCode +KEY_PROGRAM: const.EventCode +KEY_CHANNEL: const.EventCode +KEY_FAVORITES: const.EventCode +KEY_EPG: const.EventCode +KEY_PVR: const.EventCode +KEY_MHP: const.EventCode +KEY_LANGUAGE: const.EventCode +KEY_TITLE: const.EventCode +KEY_SUBTITLE: const.EventCode +KEY_ANGLE: const.EventCode +KEY_FULL_SCREEN: const.EventCode +KEY_MODE: const.EventCode +KEY_KEYBOARD: const.EventCode +KEY_ASPECT_RATIO: const.EventCode +KEY_PC: const.EventCode +KEY_TV: const.EventCode +KEY_TV2: const.EventCode +KEY_VCR: const.EventCode +KEY_VCR2: const.EventCode +KEY_SAT: const.EventCode +KEY_SAT2: const.EventCode +KEY_CD: const.EventCode +KEY_TAPE: const.EventCode +KEY_RADIO: const.EventCode +KEY_TUNER: const.EventCode +KEY_PLAYER: const.EventCode +KEY_TEXT: const.EventCode +KEY_DVD: const.EventCode +KEY_AUX: const.EventCode +KEY_MP3: const.EventCode +KEY_AUDIO: const.EventCode +KEY_VIDEO: const.EventCode +KEY_DIRECTORY: const.EventCode +KEY_LIST: const.EventCode +KEY_MEMO: const.EventCode +KEY_CALENDAR: const.EventCode +KEY_RED: const.EventCode +KEY_GREEN: const.EventCode +KEY_YELLOW: const.EventCode +KEY_BLUE: const.EventCode +KEY_CHANNELUP: const.EventCode +KEY_CHANNELDOWN: const.EventCode +KEY_FIRST: const.EventCode +KEY_LAST: const.EventCode +KEY_AB: const.EventCode +KEY_NEXT: const.EventCode +KEY_RESTART: const.EventCode +KEY_SLOW: const.EventCode +KEY_SHUFFLE: const.EventCode +KEY_BREAK: const.EventCode +KEY_PREVIOUS: const.EventCode +KEY_DIGITS: const.EventCode +KEY_TEEN: const.EventCode +KEY_TWEN: const.EventCode +KEY_VIDEOPHONE: const.EventCode +KEY_GAMES: const.EventCode +KEY_ZOOMIN: const.EventCode +KEY_ZOOMOUT: const.EventCode +KEY_ZOOMRESET: const.EventCode +KEY_WORDPROCESSOR: const.EventCode +KEY_EDITOR: const.EventCode +KEY_SPREADSHEET: const.EventCode +KEY_GRAPHICSEDITOR: const.EventCode +KEY_PRESENTATION: const.EventCode +KEY_DATABASE: const.EventCode +KEY_NEWS: const.EventCode +KEY_VOICEMAIL: const.EventCode +KEY_ADDRESSBOOK: const.EventCode +KEY_MESSENGER: const.EventCode +KEY_DISPLAYTOGGLE: const.EventCode +KEY_SPELLCHECK: const.EventCode +KEY_LOGOFF: const.EventCode +KEY_DOLLAR: const.EventCode +KEY_EURO: const.EventCode +KEY_FRAMEBACK: const.EventCode +KEY_FRAMEFORWARD: const.EventCode +KEY_CONTEXT_MENU: const.EventCode +KEY_MEDIA_REPEAT: const.EventCode +KEY_10CHANNELSUP: const.EventCode +KEY_10CHANNELSDOWN: const.EventCode +KEY_IMAGES: const.EventCode +KEY_NOTIFICATION_CENTER: const.EventCode +KEY_PICKUP_PHONE: const.EventCode +KEY_HANGUP_PHONE: const.EventCode +KEY_LINK_PHONE: const.EventCode +KEY_DEL_EOL: const.EventCode +KEY_DEL_EOS: const.EventCode +KEY_INS_LINE: const.EventCode +KEY_DEL_LINE: const.EventCode +KEY_FN: const.EventCode +KEY_FN_ESC: const.EventCode +KEY_FN_F1: const.EventCode +KEY_FN_F2: const.EventCode +KEY_FN_F3: const.EventCode +KEY_FN_F4: const.EventCode +KEY_FN_F5: const.EventCode +KEY_FN_F6: const.EventCode +KEY_FN_F7: const.EventCode +KEY_FN_F8: const.EventCode +KEY_FN_F9: const.EventCode +KEY_FN_F10: const.EventCode +KEY_FN_F11: const.EventCode +KEY_FN_F12: const.EventCode +KEY_FN_1: const.EventCode +KEY_FN_2: const.EventCode +KEY_FN_D: const.EventCode +KEY_FN_E: const.EventCode +KEY_FN_F: const.EventCode +KEY_FN_S: const.EventCode +KEY_FN_B: const.EventCode +KEY_FN_RIGHT_SHIFT: const.EventCode +KEY_BRL_DOT1: const.EventCode +KEY_BRL_DOT2: const.EventCode +KEY_BRL_DOT3: const.EventCode +KEY_BRL_DOT4: const.EventCode +KEY_BRL_DOT5: const.EventCode +KEY_BRL_DOT6: const.EventCode +KEY_BRL_DOT7: const.EventCode +KEY_BRL_DOT8: const.EventCode +KEY_BRL_DOT9: const.EventCode +KEY_BRL_DOT10: const.EventCode +KEY_NUMERIC_0: const.EventCode +KEY_NUMERIC_1: const.EventCode +KEY_NUMERIC_2: const.EventCode +KEY_NUMERIC_3: const.EventCode +KEY_NUMERIC_4: const.EventCode +KEY_NUMERIC_5: const.EventCode +KEY_NUMERIC_6: const.EventCode +KEY_NUMERIC_7: const.EventCode +KEY_NUMERIC_8: const.EventCode +KEY_NUMERIC_9: const.EventCode +KEY_NUMERIC_STAR: const.EventCode +KEY_NUMERIC_POUND: const.EventCode +KEY_NUMERIC_A: const.EventCode +KEY_NUMERIC_B: const.EventCode +KEY_NUMERIC_C: const.EventCode +KEY_NUMERIC_D: const.EventCode +KEY_CAMERA_FOCUS: const.EventCode +KEY_WPS_BUTTON: const.EventCode +KEY_TOUCHPAD_TOGGLE: const.EventCode +KEY_TOUCHPAD_ON: const.EventCode +KEY_TOUCHPAD_OFF: const.EventCode +KEY_CAMERA_ZOOMIN: const.EventCode +KEY_CAMERA_ZOOMOUT: const.EventCode +KEY_CAMERA_UP: const.EventCode +KEY_CAMERA_DOWN: const.EventCode +KEY_CAMERA_LEFT: const.EventCode +KEY_CAMERA_RIGHT: const.EventCode +KEY_ATTENDANT_ON: const.EventCode +KEY_ATTENDANT_OFF: const.EventCode +KEY_ATTENDANT_TOGGLE: const.EventCode +KEY_LIGHTS_TOGGLE: const.EventCode +BTN_DPAD_UP: const.EventCode +BTN_DPAD_DOWN: const.EventCode +BTN_DPAD_LEFT: const.EventCode +BTN_DPAD_RIGHT: const.EventCode +BTN_GRIPL: const.EventCode +BTN_GRIPR: const.EventCode +BTN_GRIPL2: const.EventCode +BTN_GRIPR2: const.EventCode +KEY_ALS_TOGGLE: const.EventCode +KEY_ROTATE_LOCK_TOGGLE: const.EventCode +KEY_REFRESH_RATE_TOGGLE: const.EventCode +KEY_BUTTONCONFIG: const.EventCode +KEY_TASKMANAGER: const.EventCode +KEY_JOURNAL: const.EventCode +KEY_CONTROLPANEL: const.EventCode +KEY_APPSELECT: const.EventCode +KEY_SCREENSAVER: const.EventCode +KEY_VOICECOMMAND: const.EventCode +KEY_ASSISTANT: const.EventCode +KEY_KBD_LAYOUT_NEXT: const.EventCode +KEY_EMOJI_PICKER: const.EventCode +KEY_DICTATE: const.EventCode +KEY_CAMERA_ACCESS_ENABLE: const.EventCode +KEY_CAMERA_ACCESS_DISABLE: const.EventCode +KEY_CAMERA_ACCESS_TOGGLE: const.EventCode +KEY_ACCESSIBILITY: const.EventCode +KEY_DO_NOT_DISTURB: const.EventCode +KEY_BRIGHTNESS_MIN: const.EventCode +KEY_BRIGHTNESS_MAX: const.EventCode +KEY_KBDINPUTASSIST_PREV: const.EventCode +KEY_KBDINPUTASSIST_NEXT: const.EventCode +KEY_KBDINPUTASSIST_PREVGROUP: const.EventCode +KEY_KBDINPUTASSIST_NEXTGROUP: const.EventCode +KEY_KBDINPUTASSIST_ACCEPT: const.EventCode +KEY_KBDINPUTASSIST_CANCEL: const.EventCode +KEY_RIGHT_UP: const.EventCode +KEY_RIGHT_DOWN: const.EventCode +KEY_LEFT_UP: const.EventCode +KEY_LEFT_DOWN: const.EventCode +KEY_ROOT_MENU: const.EventCode +KEY_MEDIA_TOP_MENU: const.EventCode +KEY_NUMERIC_11: const.EventCode +KEY_NUMERIC_12: const.EventCode +KEY_AUDIO_DESC: const.EventCode +KEY_3D_MODE: const.EventCode +KEY_NEXT_FAVORITE: const.EventCode +KEY_STOP_RECORD: const.EventCode +KEY_PAUSE_RECORD: const.EventCode +KEY_VOD: const.EventCode +KEY_UNMUTE: const.EventCode +KEY_FASTREVERSE: const.EventCode +KEY_SLOWREVERSE: const.EventCode +KEY_DATA: const.EventCode +KEY_ONSCREEN_KEYBOARD: const.EventCode +KEY_PRIVACY_SCREEN_TOGGLE: const.EventCode +KEY_SELECTIVE_SCREENSHOT: const.EventCode +KEY_NEXT_ELEMENT: const.EventCode +KEY_PREVIOUS_ELEMENT: const.EventCode +KEY_AUTOPILOT_ENGAGE_TOGGLE: const.EventCode +KEY_MARK_WAYPOINT: const.EventCode +KEY_SOS: const.EventCode +KEY_NAV_CHART: const.EventCode +KEY_FISHING_CHART: const.EventCode +KEY_SINGLE_RANGE_RADAR: const.EventCode +KEY_DUAL_RANGE_RADAR: const.EventCode +KEY_RADAR_OVERLAY: const.EventCode +KEY_TRADITIONAL_SONAR: const.EventCode +KEY_CLEARVU_SONAR: const.EventCode +KEY_SIDEVU_SONAR: const.EventCode +KEY_NAV_INFO: const.EventCode +KEY_BRIGHTNESS_MENU: const.EventCode +KEY_MACRO1: const.EventCode +KEY_MACRO2: const.EventCode +KEY_MACRO3: const.EventCode +KEY_MACRO4: const.EventCode +KEY_MACRO5: const.EventCode +KEY_MACRO6: const.EventCode +KEY_MACRO7: const.EventCode +KEY_MACRO8: const.EventCode +KEY_MACRO9: const.EventCode +KEY_MACRO10: const.EventCode +KEY_MACRO11: const.EventCode +KEY_MACRO12: const.EventCode +KEY_MACRO13: const.EventCode +KEY_MACRO14: const.EventCode +KEY_MACRO15: const.EventCode +KEY_MACRO16: const.EventCode +KEY_MACRO17: const.EventCode +KEY_MACRO18: const.EventCode +KEY_MACRO19: const.EventCode +KEY_MACRO20: const.EventCode +KEY_MACRO21: const.EventCode +KEY_MACRO22: const.EventCode +KEY_MACRO23: const.EventCode +KEY_MACRO24: const.EventCode +KEY_MACRO25: const.EventCode +KEY_MACRO26: const.EventCode +KEY_MACRO27: const.EventCode +KEY_MACRO28: const.EventCode +KEY_MACRO29: const.EventCode +KEY_MACRO30: const.EventCode +KEY_MACRO_RECORD_START: const.EventCode +KEY_MACRO_RECORD_STOP: const.EventCode +KEY_MACRO_PRESET_CYCLE: const.EventCode +KEY_MACRO_PRESET1: const.EventCode +KEY_MACRO_PRESET2: const.EventCode +KEY_MACRO_PRESET3: const.EventCode +KEY_KBD_LCD_MENU1: const.EventCode +KEY_KBD_LCD_MENU2: const.EventCode +KEY_KBD_LCD_MENU3: const.EventCode +KEY_KBD_LCD_MENU4: const.EventCode +KEY_KBD_LCD_MENU5: const.EventCode +KEY_PERFORMANCE: const.EventCode +BTN_TRIGGER_HAPPY1: const.EventCode +BTN_TRIGGER_HAPPY2: const.EventCode +BTN_TRIGGER_HAPPY3: const.EventCode +BTN_TRIGGER_HAPPY4: const.EventCode +BTN_TRIGGER_HAPPY5: const.EventCode +BTN_TRIGGER_HAPPY6: const.EventCode +BTN_TRIGGER_HAPPY7: const.EventCode +BTN_TRIGGER_HAPPY8: const.EventCode +BTN_TRIGGER_HAPPY9: const.EventCode +BTN_TRIGGER_HAPPY10: const.EventCode +BTN_TRIGGER_HAPPY11: const.EventCode +BTN_TRIGGER_HAPPY12: const.EventCode +BTN_TRIGGER_HAPPY13: const.EventCode +BTN_TRIGGER_HAPPY14: const.EventCode +BTN_TRIGGER_HAPPY15: const.EventCode +BTN_TRIGGER_HAPPY16: const.EventCode +BTN_TRIGGER_HAPPY17: const.EventCode +BTN_TRIGGER_HAPPY18: const.EventCode +BTN_TRIGGER_HAPPY19: const.EventCode +BTN_TRIGGER_HAPPY20: const.EventCode +BTN_TRIGGER_HAPPY21: const.EventCode +BTN_TRIGGER_HAPPY22: const.EventCode +BTN_TRIGGER_HAPPY23: const.EventCode +BTN_TRIGGER_HAPPY24: const.EventCode +BTN_TRIGGER_HAPPY25: const.EventCode +BTN_TRIGGER_HAPPY26: const.EventCode +BTN_TRIGGER_HAPPY27: const.EventCode +BTN_TRIGGER_HAPPY28: const.EventCode +BTN_TRIGGER_HAPPY29: const.EventCode +BTN_TRIGGER_HAPPY30: const.EventCode +BTN_TRIGGER_HAPPY31: const.EventCode +BTN_TRIGGER_HAPPY32: const.EventCode +BTN_TRIGGER_HAPPY33: const.EventCode +BTN_TRIGGER_HAPPY34: const.EventCode +BTN_TRIGGER_HAPPY35: const.EventCode +BTN_TRIGGER_HAPPY36: const.EventCode +BTN_TRIGGER_HAPPY37: const.EventCode +BTN_TRIGGER_HAPPY38: const.EventCode +BTN_TRIGGER_HAPPY39: const.EventCode +BTN_TRIGGER_HAPPY40: const.EventCode +KEY_AI_ASSISTANT: const.EventCode +KEY_MAX: const.EventCode +REL_X: const.EventCode +REL_Y: const.EventCode +REL_Z: const.EventCode +REL_RX: const.EventCode +REL_RY: const.EventCode +REL_RZ: const.EventCode +REL_HWHEEL: const.EventCode +REL_DIAL: const.EventCode +REL_WHEEL: const.EventCode +REL_MISC: const.EventCode +REL_RESERVED: const.EventCode +REL_WHEEL_HI_RES: const.EventCode +REL_HWHEEL_HI_RES: const.EventCode +REL_MAX: const.EventCode +ABS_X: const.EventCode +ABS_Y: const.EventCode +ABS_Z: const.EventCode +ABS_RX: const.EventCode +ABS_RY: const.EventCode +ABS_RZ: const.EventCode +ABS_THROTTLE: const.EventCode +ABS_RUDDER: const.EventCode +ABS_WHEEL: const.EventCode +ABS_GAS: const.EventCode +ABS_BRAKE: const.EventCode +ABS_HAT0X: const.EventCode +ABS_HAT0Y: const.EventCode +ABS_HAT1X: const.EventCode +ABS_HAT1Y: const.EventCode +ABS_HAT2X: const.EventCode +ABS_HAT2Y: const.EventCode +ABS_HAT3X: const.EventCode +ABS_HAT3Y: const.EventCode +ABS_PRESSURE: const.EventCode +ABS_DISTANCE: const.EventCode +ABS_TILT_X: const.EventCode +ABS_TILT_Y: const.EventCode +ABS_TOOL_WIDTH: const.EventCode +ABS_VOLUME: const.EventCode +ABS_PROFILE: const.EventCode +ABS_MISC: const.EventCode +ABS_RESERVED: const.EventCode +ABS_MT_SLOT: const.EventCode +ABS_MT_TOUCH_MAJOR: const.EventCode +ABS_MT_TOUCH_MINOR: const.EventCode +ABS_MT_WIDTH_MAJOR: const.EventCode +ABS_MT_WIDTH_MINOR: const.EventCode +ABS_MT_ORIENTATION: const.EventCode +ABS_MT_POSITION_X: const.EventCode +ABS_MT_POSITION_Y: const.EventCode +ABS_MT_TOOL_TYPE: const.EventCode +ABS_MT_BLOB_ID: const.EventCode +ABS_MT_TRACKING_ID: const.EventCode +ABS_MT_PRESSURE: const.EventCode +ABS_MT_DISTANCE: const.EventCode +ABS_MT_TOOL_X: const.EventCode +ABS_MT_TOOL_Y: const.EventCode +ABS_MAX: const.EventCode +MSC_SERIAL: const.EventCode +MSC_PULSELED: const.EventCode +MSC_GESTURE: const.EventCode +MSC_RAW: const.EventCode +MSC_SCAN: const.EventCode +MSC_TIMESTAMP: const.EventCode +MSC_MAX: const.EventCode +SW_LID: const.EventCode +SW_TABLET_MODE: const.EventCode +SW_HEADPHONE_INSERT: const.EventCode +SW_RFKILL_ALL: const.EventCode +SW_MICROPHONE_INSERT: const.EventCode +SW_DOCK: const.EventCode +SW_LINEOUT_INSERT: const.EventCode +SW_JACK_PHYSICAL_INSERT: const.EventCode +SW_VIDEOOUT_INSERT: const.EventCode +SW_CAMERA_LENS_COVER: const.EventCode +SW_KEYPAD_SLIDE: const.EventCode +SW_FRONT_PROXIMITY: const.EventCode +SW_ROTATE_LOCK: const.EventCode +SW_LINEIN_INSERT: const.EventCode +SW_MUTE_DEVICE: const.EventCode +SW_PEN_INSERTED: const.EventCode +SW_MACHINE_COVER: const.EventCode +SW_USB_INSERT: const.EventCode +LED_NUML: const.EventCode +LED_CAPSL: const.EventCode +LED_SCROLLL: const.EventCode +LED_COMPOSE: const.EventCode +LED_KANA: const.EventCode +LED_SLEEP: const.EventCode +LED_SUSPEND: const.EventCode +LED_MUTE: const.EventCode +LED_MISC: const.EventCode +LED_MAIL: const.EventCode +LED_CHARGING: const.EventCode +LED_MAX: const.EventCode +SND_CLICK: const.EventCode +SND_BELL: const.EventCode +SND_TONE: const.EventCode +SND_MAX: const.EventCode +REP_DELAY: const.EventCode +REP_PERIOD: const.EventCode +FF_STATUS_STOPPED: const.EventCode +FF_STATUS_MAX: const.EventCode +FF_RUMBLE: const.EventCode +FF_PERIODIC: const.EventCode +FF_CONSTANT: const.EventCode +FF_SPRING: const.EventCode +FF_FRICTION: const.EventCode +FF_DAMPER: const.EventCode +FF_INERTIA: const.EventCode +FF_RAMP: const.EventCode +FF_SQUARE: const.EventCode +FF_TRIANGLE: const.EventCode +FF_SINE: const.EventCode +FF_SAW_UP: const.EventCode +FF_SAW_DOWN: const.EventCode +FF_CUSTOM: const.EventCode +FF_GAIN: const.EventCode +FF_AUTOCENTER: const.EventCode +FF_MAX: const.EventCode +INPUT_PROP_POINTER: const.InputProperty +INPUT_PROP_DIRECT: const.InputProperty +INPUT_PROP_BUTTONPAD: const.InputProperty +INPUT_PROP_SEMI_MT: const.InputProperty +INPUT_PROP_TOPBUTTONPAD: const.InputProperty +INPUT_PROP_POINTING_STICK: const.InputProperty +INPUT_PROP_ACCELEROMETER: const.InputProperty +INPUT_PROP_MAX: const.InputProperty +types: list[const.EventType] +props: list[const.InputProperty] diff --git a/libevdev/_clib.py b/libevdev/_clib.py index 11daccf..c99fe4a 100644 --- a/libevdev/_clib.py +++ b/libevdev/_clib.py @@ -1,4 +1,3 @@ -# -*- coding: latin-1 -*- # Copyright © 2016 Red Hat, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a @@ -20,6 +19,8 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. +from __future__ import annotations + import libevdev import os @@ -32,6 +33,13 @@ from ctypes import c_long from ctypes import c_int32 from ctypes import c_uint16 +from typing import BinaryIO, Tuple + +try: + from typing import Self +except ImportError: + from typing_extensions import Self + READ_FLAG_SYNC = 0x1 READ_FLAG_NORMAL = 0x2 @@ -40,26 +48,31 @@ class _InputAbsinfo(ctypes.Structure): - _fields_ = [("value", c_int32), - ("minimum", c_int32), - ("maximum", c_int32), - ("fuzz", c_int32), - ("flat", c_int32), - ("resolution", c_int32)] + _fields_ = [ + ("value", c_int32), + ("minimum", c_int32), + ("maximum", c_int32), + ("fuzz", c_int32), + ("flat", c_int32), + ("resolution", c_int32), + ] class _InputEvent(ctypes.Structure): - _fields_ = [("sec", c_long), - ("usec", c_long), - ("type", c_uint16), - ("code", c_uint16), - ("value", c_int32)] + _fields_ = [ + ("sec", c_long), + ("usec", c_long), + ("type", c_uint16), + ("code", c_uint16), + ("value", c_int32), + ] -class _LibraryWrapper(object): +class _LibraryWrapper: """ Base class for wrapping a shared library. Do not use directly. """ + _lib = None # The shared library, shared between instances of the class # List of API prototypes, must be set by the subclass @@ -72,16 +85,16 @@ class _LibraryWrapper(object): } def __init__(self): - super(_LibraryWrapper, self).__init__() + super().__init__() self._load() @classmethod - def _load(cls): + def _load(cls) -> ctypes.CDLL: if cls._lib is not None: return cls._lib cls._lib = cls._cdll() - for (name, attrs) in cls._api_prototypes.items(): + for name, attrs in cls._api_prototypes.items(): func = getattr(cls._lib, name) func.argtypes = attrs["argtypes"] func.restype = attrs["restype"] @@ -96,7 +109,7 @@ def _load(cls): return cls._lib @staticmethod - def _cdll(): + def _cdll() -> ctypes.CDLL: """Override in subclass""" raise NotImplementedError @@ -106,7 +119,7 @@ class Libevdev(_LibraryWrapper): This class provides a wrapper around the libevdev C library. The API is modelled closely after the C API. API documentation in this - document only lists the specific behavior of the Phython API. For + document only lists the specific behavior of the Python API. For information on the behavior of libevdev itself, see https://www.freedesktop.org/software/libevdev/doc/latest/ @@ -119,29 +132,47 @@ class Libevdev(_LibraryWrapper): """ @staticmethod - def _cdll(): + def _cdll() -> ctypes.CDLL: return ctypes.CDLL("libevdev.so.2", use_errno=True) _api_prototypes = { # const char *libevdev_event_type_get_name(unsigned int type); - "libevdev_event_type_get_name": { - "argtypes": (c_uint,), - "restype": c_char_p - }, + "libevdev_event_type_get_name": {"argtypes": (c_uint,), "restype": c_char_p}, # int libevdev_event_type_from_name(const char *name); - "libevdev_event_type_from_name": { - "argtypes": (c_char_p,), - "restype": c_int - }, + "libevdev_event_type_from_name": {"argtypes": (c_char_p,), "restype": c_int}, # const char *libevdev_event_code_get_name(unsigned int type, unsigned int code); "libevdev_event_code_get_name": { - "argtypes": (c_uint, c_uint,), - "restype": c_char_p + "argtypes": ( + c_uint, + c_uint, + ), + "restype": c_char_p, }, # int libevdev_event_code_from_name(unsigned int type, const char *name); "libevdev_event_code_from_name": { - "argtypes": (c_uint, c_char_p,), - "restype": c_int + "argtypes": ( + c_uint, + c_char_p, + ), + "restype": c_int, + }, + # const char *libevdev_event_value_get_name(unsigned int type, unsigned int code, int value); + "libevdev_event_value_get_name": { + "argtypes": ( + c_uint, + c_uint, + c_int, + ), + "restype": c_char_p, + }, + # int libevdev_event_value_from_name(unsigned int type, unsigned int code, const char *name); + "libevdev_event_value_from_name": { + "argtypes": ( + c_uint, + c_uint, + c_char_p, + ), + "restype": c_int, }, # const char *libevdev_property_get_name(unsigned int prop); "libevdev_property_get_name": { @@ -149,13 +180,10 @@ def _cdll(): "restype": c_char_p, }, # int libevdev_property_from_name(const char *name); - "libevdev_property_from_name": { - "argtypes": (c_char_p,), - "restype": c_int - }, + "libevdev_property_from_name": {"argtypes": (c_char_p,), "restype": c_int}, # void libevdev_event_type_get_max(int) "libevdev_event_type_get_max": { - "argtypes": (c_int, ), + "argtypes": (c_int,), "restype": c_int, }, # struct libevdev *libevdev_new(void); @@ -203,7 +231,7 @@ def _cdll(): }, # int libevdev_get_driver_version(struct libevdev *); "libevdev_get_driver_version": { - "argtypes": (c_void_p, ), + "argtypes": (c_void_p,), "restype": c_int, }, # void libevdev_set_clock_id(struct libevdev *, int) @@ -266,7 +294,7 @@ def _cdll(): }, # int libevdev_get_fd(struct libevdev *) "libevdev_get_fd": { - "argtypes": (c_void_p, ), + "argtypes": (c_void_p,), "restype": c_int, }, # int libevdev_grab(struct libevdev *) @@ -285,29 +313,28 @@ def _cdll(): # const struct input_absinfo *libevdev_get_abs_info(struct libevdev*, int code) "libevdev_kernel_set_abs_info": { "argtypes": (c_void_p, c_int, ctypes.POINTER(_InputAbsinfo)), - "restype": (c_int) + "restype": (c_int), + }, + # int libevdev_kernel_set_led_value(struct libevdev *dev, unsigned int, enum libevdev_led_value); + "libevdev_kernel_set_led_value": { + "argtypes": (c_void_p, c_int, c_int), + "restype": (c_int), }, ########################## # Various has_ functions # ########################## - "libevdev_has_property": { - "argtypes": (c_void_p, c_int), - "restype": (c_int) - }, - "libevdev_has_event_type": { - "argtypes": (c_void_p, c_int), - "restype": (c_int) - }, + "libevdev_has_property": {"argtypes": (c_void_p, c_int), "restype": (c_int)}, + "libevdev_has_event_type": {"argtypes": (c_void_p, c_int), "restype": (c_int)}, "libevdev_has_event_code": { "argtypes": (c_void_p, c_int, c_int), - "restype": (c_int) + "restype": (c_int), }, ########################## # Other functions # ########################## "libevdev_set_event_value": { "argtypes": (c_void_p, c_int, c_int, c_int), - "restype": (c_int) + "restype": (c_int), }, "libevdev_get_event_value": { "argtypes": (c_void_p, c_int, c_int), @@ -359,7 +386,7 @@ def _cdll(): }, } - def __init__(self, fd=None): + def __init__(self, fd: BinaryIO | None = None): """ :param fd: A file-like object @@ -377,7 +404,7 @@ def __init__(self, fd=None): # l2 is an unbound device with the REL_X bit set """ - super(Libevdev, self).__init__() + super().__init__() self._ctx = self._new() self._file = None if fd is not None: @@ -388,20 +415,20 @@ def __del__(self): self._free(self._ctx) @property - def name(self): + def name(self) -> str: """ :return: A string with the device's kernel name. """ return self._get_name(self._ctx).decode("iso8859-1") @name.setter - def name(self, name): + def name(self, name: str | None): if name is None: - name = '' + name = "" return self._set_name(self._ctx, name.encode("iso8859-1")) @property - def phys(self): + def phys(self) -> str | None: """ :return: A string with the device's kernel phys or None. """ @@ -411,14 +438,14 @@ def phys(self): return phys.decode("iso8859-1") @phys.setter - def phys(self, phys): + def phys(self, phys: str | None): # libevdev issue: phys may be NULL, but can't be set to NULL if phys is None: - phys = '' + phys = "" return self._set_phys(self._ctx, phys.encode("iso8859-1")) @property - def uniq(self): + def uniq(self) -> str | None: """ :return: A string with the device's kernel uniq or None. """ @@ -431,18 +458,18 @@ def uniq(self): def uniq(self, uniq): # libevdev issue: uniq may be NULL, but can't be set to NULL if uniq is None: - uniq = '' + uniq = "" return self._set_uniq(self._ctx, uniq.encode("iso8859-1")) @property - def driver_version(self): + def driver_version(self) -> int: """ :note: Read-only """ return self._get_driver_version(self._ctx) @property - def id(self): + def id(self) -> dict[str, int]: """ :return: A dict with the keys 'bustype', 'vendor', 'product', 'version'. @@ -463,13 +490,10 @@ def id(self): vdr = self._get_id_vendor(self._ctx) pro = self._get_id_product(self._ctx) ver = self._get_id_version(self._ctx) - return {"bustype": bus, - "vendor": vdr, - "product": pro, - "version": ver} + return {"bustype": bus, "vendor": vdr, "product": pro, "version": ver} @id.setter - def id(self, vals): + def id(self, vals: dict[str, int]): if "bustype" in vals: self._set_id_bustype(self._ctx, vals["bustype"]) if "vendor" in vals: @@ -479,7 +503,7 @@ def id(self, vals): if "version" in vals: self._set_id_version(self._ctx, vals["version"]) - def set_clock_id(self, clock): + def set_clock_id(self, clock: int) -> int: """ :param clock: time.CLOCK_MONOTONIC :return: a negative errno on failure or 0 on success. @@ -487,7 +511,7 @@ def set_clock_id(self, clock): return self._set_clock_id(self._ctx, clock) @property - def fd(self): + def fd(self) -> BinaryIO | None: """ :return: The file-like object used during constructor or in the most recent assignment to self.fd. @@ -516,7 +540,7 @@ def fd(self): return self._file @fd.setter - def fd(self, fileobj): + def fd(self, fileobj: BinaryIO): try: fd = fileobj.fileno() except AttributeError: @@ -550,7 +574,12 @@ def grab(self, enable_grab=True): if r != 0: raise OSError(-r, os.strerror(-r)) - def absinfo(self, code, new_values=None, kernel=False): + def absinfo( + self, + code: int | str, + new_values: dict[str, int] | None = None, + kernel: bool = False, + ) -> dict[str, int] | None: """ :param code: the ABS_<*> code as integer or as string :param new_values: a dict with the same keys as the return values. @@ -593,15 +622,17 @@ def absinfo(self, code, new_values=None, kernel=False): if rc != 0: raise OSError(-rc, os.strerror(-rc)) - return {"value": absinfo.contents.value, - "minimum": absinfo.contents.minimum, - "maximum": absinfo.contents.maximum, - "fuzz": absinfo.contents.fuzz, - "flat": absinfo.contents.flat, - "resolution": absinfo.contents.resolution} + return { + "value": absinfo.contents.value, + "minimum": absinfo.contents.minimum, + "maximum": absinfo.contents.maximum, + "fuzz": absinfo.contents.fuzz, + "flat": absinfo.contents.flat, + "resolution": absinfo.contents.resolution, + } @classmethod - def property_to_name(cls, prop): + def property_to_name(cls, prop) -> str | None: """ :param prop: the numerical property value :return: A string with the property name or ``None`` @@ -614,7 +645,7 @@ def property_to_name(cls, prop): return name.decode("iso8859-1") @classmethod - def property_to_value(cls, prop): + def property_to_value(cls, prop) -> int | None: """ :param prop: the property name as string :return: The numerical property value or ``None`` @@ -627,7 +658,7 @@ def property_to_value(cls, prop): return v @classmethod - def type_max(cls, type): + def type_max(cls, type) -> int | None: """ :param type: the EV_<*> event type :return: the maximum code for this type or ``None`` if the type is @@ -639,17 +670,20 @@ def type_max(cls, type): return m if m > -1 else None @classmethod - def event_to_name(cls, event_type, event_code=None): + def event_to_name(cls, event_type, event_code=None, event_value=None) -> str | None: """ :param event_type: the numerical event type value :param event_code: optional, the numerical event code value + :param event_value: optional, the numerical event value :return: the event code name if a code is given otherwise the event type name. - This function is the equivalent to ``libevdev_event_code_get_name()`` - and ``libevdev_event_type_get_name()`` + This function is the equivalent to ``libevdev_event_value_get_name()``, + ``libevdev_event_code_get_name()``, and ``libevdev_event_type_get_name()`` """ - if event_code is not None: + if event_code is not None and event_value is not None: + name = cls._event_value_get_name(event_type, event_code, event_value) + elif event_code is not None: name = cls._event_code_get_name(event_type, event_code) else: name = cls._event_type_get_name(event_type) @@ -658,17 +692,28 @@ def event_to_name(cls, event_type, event_code=None): return name.decode("iso8859-1") @classmethod - def event_to_value(cls, event_type, event_code=None): + def event_to_value( + cls, event_type, event_code=None, event_value=None + ) -> int | None: """ :param event_type: the event type as string :param event_code: optional, the event code as string + :param event_value: optional, the numerical event value :return: the event code value if a code is given otherwise the event type value. - This function is the equivalent to ``libevdev_event_code_from_name()`` - and ``libevdev_event_type_from_name()`` + This function is the equivalent to ``libevdev_event_value_from_name()``, + ``libevdev_event_code_from_name()`` and ``libevdev_event_type_from_name()`` """ - if event_code is not None: + if event_code is not None and event_value is not None: + if not isinstance(event_type, int): + event_type = cls.event_to_value(event_type) + if not isinstance(event_code, int): + event_code = cls.event_to_value(event_type, event_code) + v = cls._event_value_from_name( + event_type, event_code, event_value.encode("iso8859-1") + ) + elif event_code is not None: if not isinstance(event_type, int): event_type = cls.event_to_value(event_type) v = cls._event_code_from_name(event_type, event_code.encode("iso8859-1")) @@ -678,7 +723,7 @@ def event_to_value(cls, event_type, event_code=None): return None return v - def has_property(self, prop): + def has_property(self, prop: int | str) -> bool: """ :param prop: a property, either as integer or string :return: True if the device has the property, False otherwise @@ -688,7 +733,9 @@ def has_property(self, prop): r = self._has_property(self._ctx, prop) return bool(r) - def has_event(self, event_type, event_code=None): + def has_event( + self, event_type: int | str, event_code: int | str | None = None + ) -> bool: """ :param event_type: the event type, either as integer or as string :param event_code: optional, the event code, either as integer or as @@ -706,18 +753,24 @@ def has_event(self, event_type, event_code=None): r = self._has_event_code(self._ctx, event_type, event_code) return bool(r) - def _code(self, t, c): + def _code(self, t: int | str, c: int | str | None) -> Tuple[int, int]: """ Resolves a type+code tuple, either of which could be integer or string. Returns a (t, c) tuple in integers """ if not isinstance(t, int): - t = self.event_to_value(t) + tv = self.event_to_value(t) + else: + tv = t if c is not None and not isinstance(c, int): - c = self.event_to_value(t, c) - return (t, c) + cv = self.event_to_value(t, c) + else: + cv = c + return (tv, cv) - def event_value(self, event_type, event_code, new_value=None): + def event_value( + self, event_type: int | str, event_code: int | str, new_value: int | None = None + ) -> int | None: """ :param event_type: the event type, either as integer or as string :param event_code: the event code, either as integer or as string @@ -737,7 +790,7 @@ def event_value(self, event_type, event_code, new_value=None): return v @property - def num_slots(self): + def num_slots(self) -> int | None: """ :return: the number of slots on this device or ``None`` if this device does not support slots @@ -748,7 +801,7 @@ def num_slots(self): return s if s >= 0 else None @property - def current_slot(self): + def current_slot(self) -> int | None: """ :return: the current of slots on this device or ``None`` if this device does not support slots @@ -758,7 +811,9 @@ def current_slot(self): s = self._get_current_slot(self._ctx) return s if s >= 0 else None - def slot_value(self, slot, event_code, new_value=None): + def slot_value( + self, slot: int, event_code: int | str, new_value: int | None = None + ) -> int | None: """ :param slot: the numeric slot number :param event_code: the ABS_<*> event code, either as integer or string @@ -774,7 +829,12 @@ def slot_value(self, slot, event_code, new_value=None): v = self._get_slot_value(self._ctx, slot, c) return v - def enable(self, event_type, event_code=None, data=None): + def enable( + self, + event_type: int | str, + event_code: int | str | None = None, + data: dict[str, int] | int | None = None, + ): """ :param event_type: the event type, either as integer or as string :param event_code: optional, the event code, either as integer or as string @@ -798,18 +858,24 @@ def enable(self, event_type, event_code=None, data=None): self._enable_event_type(self._ctx, t) else: if t == 0x03: # EV_ABS - data = _InputAbsinfo(data.get("value", 0), - data.get("minimum", 0), - data.get("maximum", 0), - data.get("fuzz", 0), - data.get("flat", 0), - data.get("resolution", 0)) - data = ctypes.pointer(data) + assert isinstance(data, dict) + absinfo = _InputAbsinfo( + data.get("value", 0), + data.get("minimum", 0), + data.get("maximum", 0), + data.get("fuzz", 0), + data.get("flat", 0), + data.get("resolution", 0), + ) + data_arg = ctypes.pointer(absinfo) elif t == 0x14: # EV_REP - data = ctypes.pointer(c_int(data)) - self._enable_event_code(self._ctx, t, c, data) + assert isinstance(data, int) + data_arg = ctypes.pointer(c_int(data)) + else: + data_arg = None + self._enable_event_code(self._ctx, t, c, data_arg) - def disable(self, event_type, event_code=None): + def disable(self, event_type: int | str, event_code: int | str | None = None): """ :param event_type: the event type, either as integer or as string :param event_code: optional, the event code, either as integer or as string @@ -820,7 +886,7 @@ def disable(self, event_type, event_code=None): else: self._disable_event_code(self._ctx, t, c) - def enable_property(self, prop): + def enable_property(self, prop: int | str): """ :param prop: the property as integer or string """ @@ -829,16 +895,16 @@ def enable_property(self, prop): self._enable_property(self._ctx, prop) - def set_led(self, led, on): + def set_led(self, led: int | str, on: bool): """ :param led: the LED_<*> name :on: True to turn the LED on, False to turn it off """ t, c = self._code("EV_LED", led) which = 3 if on else 4 - self._set_led_value(self._ctx, c, which) + self._kernel_set_led_value(self._ctx, c, which) - def next_event(self, flags=READ_FLAG_NORMAL): + def next_event(self, flags: int = READ_FLAG_NORMAL) -> _InputEvent | None: """ :param flags: a set of libevdev read flags. May be omitted to use the normal mode. @@ -863,6 +929,8 @@ def next_event(self, flags=READ_FLAG_NORMAL): rc = self._next_event(self._ctx, flags, ctypes.byref(ev)) if rc == -errno.EAGAIN: return None + if rc < 0: + raise OSError(-rc, os.strerror(-rc)) return ev @@ -881,14 +949,18 @@ class UinputDevice(_LibraryWrapper): """ @staticmethod - def _cdll(): + def _cdll() -> ctypes.CDLL: return ctypes.CDLL("libevdev.so.2", use_errno=True) _api_prototypes = { # int libevdev_uinput_create_from_device(const struct libevdev *, int, struct libevdev_uinput **) "libevdev_uinput_create_from_device": { - "argtypes": (c_void_p, c_int, ctypes.POINTER(ctypes.POINTER(_UinputDevice))), - "restype": c_int + "argtypes": ( + c_void_p, + c_int, + ctypes.POINTER(ctypes.POINTER(_UinputDevice)), + ), + "restype": c_int, }, # int libevdev_uinput_destroy(const struct libevdev *) "libevdev_uinput_destroy": { @@ -908,11 +980,11 @@ def _cdll(): # int libevdev_uinput_write_event(const struct libevdev *, uint, uint, int) "libevdev_uinput_write_event": { "argtypes": (c_void_p, c_uint, c_uint, c_int), - "restype": c_int + "restype": c_int, }, } - def __init__(self, source, fileobj=None): + def __init__(self, source: Libevdev, fileobj: BinaryIO | None = None): """ Create a new uinput device based on the source libevdev device. The uinput device will mirror all capabilities from the source device. @@ -922,7 +994,7 @@ def __init__(self, source, fileobj=None): libevdev will open the device in managed mode. See the libevdev documentation for details. """ - super(UinputDevice, self).__init__() + super().__init__() self._fileobj = fileobj if fileobj is None: @@ -931,26 +1003,37 @@ def __init__(self, source, fileobj=None): fd = fileobj.fileno() self._uinput_device = ctypes.POINTER(_UinputDevice)() - rc = self._uinput_create_from_device(source._ctx, fd, ctypes.byref(self._uinput_device)) + rc = self._uinput_create_from_device( + source._ctx, fd, ctypes.byref(self._uinput_device) + ) if rc != 0: raise OSError(-rc, os.strerror(-rc)) + def __enter__(self) -> Self: + return self + + def __exit__(self, *_): + if self._uinput_device is not None: + self._uinput_destroy(self._uinput_device) + self._uinput_device = None + def __del__(self): if self._uinput_device is not None: self._uinput_destroy(self._uinput_device) + self._uinput_device = None @property - def fd(self): + def fd(self) -> BinaryIO | None: """ :return: the file-like object used in the constructor. """ - return self.fileobj + return self._fileobj - def write_event(self, type, code, value): + def write_event(self, type: int, code: int, value: int) -> None: self._uinput_write_event(self._uinput_device, type, code, value) @property - def devnode(self): + def devnode(self) -> str: """ Return a string with the /dev/input/eventX device node """ @@ -958,7 +1041,7 @@ def devnode(self): return devnode.decode("iso8859-1") @property - def syspath(self): + def syspath(self) -> str: """ Return a string with the /dev/input/eventX device node """ diff --git a/libevdev/const.py b/libevdev/const.py index ab7a6d0..2868817 100644 --- a/libevdev/const.py +++ b/libevdev/const.py @@ -1,4 +1,3 @@ -# -*- coding: latin-1 -*- # Copyright © 2017 Red Hat, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a @@ -20,53 +19,24 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. +from __future__ import annotations + import os +from dataclasses import dataclass, field from functools import total_ordering +try: + from typing import Self +except ImportError: + from typing_extensions import Self + from ._clib import Libevdev import libevdev @total_ordering -class EvdevBit: - """ - Base class representing an evdev bit, comprised of a name and a value. - These two properties are guaranteed to exist on anything describing an - event code, event type or input property that comes out of libevdev:: - - >>> print(libevdev.EV_ABS.name) - EV_ABS - >>> print(libevdev.EV_ABS.value) - 3 - >>> print(libevdev.EV_SYN.SYN_REPORT.name) - SYN_REPORT - >>> print(libevdev.EV_SYN.SYN_REPORT.value) - 0 - >>> print(libevdev.INPUT_PROP_DIRECT.name) - INPUT_PROP_DIRECT - >>> print(libevdev.INPUT_PROP_DIRECT.value) - 1 - - .. attribute:: value - - The numeric value of the event code - - .. attribute:: name - - The string name of this event code - """ - - def __repr__(self): - return '{}:{}'.format(self.name, self.value) - - def __eq__(self, other): - return self.value == other.value - - def __lt__(self, other): - return self.value < other.value - - -class EventCode(EvdevBit): +@dataclass(unsafe_hash=True) +class EventCode: """ .. warning :: @@ -74,39 +44,123 @@ class EventCode(EvdevBit): are already present in the libevdev namespace. Use :func:`evbit()` to get an :class:`EventCode` from numerical or string values. - A class representing an evdev event code, e.g. libevdev.EV_ABS.ABS_X. + A class representing an evdev event code, e.g. libevdev.EV_ABS.ABS_X or, + shorter, simply libevdev.ABS_X. To use a :class:`EventCode`, use the namespaced name directly:: >>> print(libevdev.EV_ABS.ABS_X) ABS_X:0 - >>> print(libevdev.EV_ABS.ABS_Y) - ABS_X:1 - >>> code = libevdev.EV_REL.REL_X + >>> print(libevdev.ABS_Y) + ABS_Y:1 + >>> code = libevdev.REL_Y >>> print(code.type) EV_REL:2 + >>> int(code) + 1 .. attribute:: value - The numeric value of the event code + The numeric value of the event code. This value is also returned when + the object is converted to ``int``. .. attribute:: name - The string name of this event code + The string name of this event code. Where the name is not defined, a + fake name is generated by this module, consisting of the prefix and + the uppercase hexadecimal value, e.g. ``REL_0B``. These generated + names (see :func:`EventCode.is_defined()`) should never be used as + input. + + Note that even defined names are not a stable API. From time to time + the kernel introduces a new name and aliases the old one. The same + numeric event code may then return a different name than before. + This does not affect the :func:`evbit()` function which continues to + return the correct event code. .. attribute:: type The :class:`EventType` for this event code + + .. attribute:: is_defined + + ``True`` if this event bit has a ``#define`` in the kernel header. + :class:`EventCode` objects not defined in the header have a name + composed of the type name plus the hex code, e.g. ``KEY_2E8``. + + Usually you will not need to check this property unless you need to + filter for known codes only. """ - __hash__ = super.__hash__ - def __eq__(self, other): + value: int = field(compare=False) + name: str = field(compare=False) + type: "EventType" = field(compare=True) + is_defined: bool = field(compare=False) + + def __lt__(self, other: object) -> bool: if not isinstance(other, EventCode): - return False + return NotImplemented + return self.value < other.value + + def __int__(self) -> int: + return self.value + + def __eq__(self, other: object) -> bool: + if not isinstance(other, EventCode): + return NotImplemented return self.value == other.value and self.type == other.type + def __repr__(self) -> str: + return f"{self.name}:{self.value}" + + def __str__(self) -> str: + return repr(self) + + @classmethod + def from_type_and_code_value( + cls, evtype: "EventType | int", code: int + ) -> Self | None: + """ + Return the **known** EventCode with the given value. The type + must be known to libevdev at libevdev compilation time: + + >>> EventCode.from_type_and_code_value(libevdev.EV_ABS, 0) + ABS_X:0 + >>> EventCode.from_type_and_code_value(3, 0) + ABS_X:0 + >>> EventCode.from_type_and_code_value(99, 999) + None + """ + if isinstance(evtype, int): + t = EventType.from_value(evtype) + if t is None: + return None + else: + t = evtype + + return next((c for c in t.codes if c.value == code), None) # type: ignore + + @classmethod + def from_name(cls, name: str) -> Self | None: + """ + Return the **known** EventCode with the given name. The type + must be known to libevdev at libevdev compilation time:: + + >>> EventCode.from_name("ABS_X") + ABS_X:0 + >>> EventCode.from_name("KEY_ESC") + KEY_ESC:1 + >>> EventCode.from_name("DOES_NOT_EXIST") + None + """ + if name.startswith("INPUT_PROP_") or name.startswith("EV_"): + return None + return getattr(libevdev, name, None) -class EventType(EvdevBit): + +@total_ordering +@dataclass(unsafe_hash=True) +class EventType: """ .. warning :: @@ -119,8 +173,10 @@ class EventType(EvdevBit): >>> print(libevdev.EV_ABS) EV_ABS:3 - >>> print(libevdev.EV_ABS.ABS_X) + >>> print(libevdev.ABS_X) ABS_X:0 + >>> print(int(libevdev.EV_ABS.ABS_Y)) + 1 >>> print(libevdev.EV_ABS.max) 63 >>> print(libevdev.EV_ABS.ABS_MAX) @@ -134,7 +190,8 @@ class EventType(EvdevBit): .. attribute:: value - The numeric value of the event type + The numeric value of the event type. This value is also returned when + the object is converted to ``int``. .. attribute:: name @@ -148,14 +205,51 @@ class EventType(EvdevBit): The maximum event code permitted in this type as integer """ - __hash__ = super.__hash__ - def __eq__(self, other): - assert isinstance(other, EventType) + value: int = field(compare=False) + name: str = field(compare=False) + codes: list[EventCode] = field(compare=False, hash=False) + max: int | None = field(compare=False, hash=False) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, EventType): + return NotImplemented return self.value == other.value + def __lt__(self, other: object) -> bool: + if not isinstance(other, EventType): + return NotImplemented + return self.value < other.value + + def __int__(self) -> int: + return self.value + + def __repr__(self) -> str: + return f"{self.name}:{self.value}" + + def __str__(self) -> str: + return repr(self) + + @classmethod + def from_value(cls, value: int) -> Self | None: + """ + Return the **known** EventType with the given value. The type + must be known to libevdev at libevdev compilation time. + """ + return next((t for t in libevdev.types if t.value == value), None) # type: ignore + + @classmethod + def from_name(cls, name: str) -> Self | None: + """ + Return the **known** EventType with the given name. The type + must be known to libevdev at libevdev compilation time. + """ + return next((t for t in libevdev.types if t.name == name), None) # type: ignore + -class InputProperty(EvdevBit): +@total_ordering +@dataclass(frozen=True) +class InputProperty: """ .. warning :: @@ -167,24 +261,96 @@ class InputProperty(EvdevBit): >>> print(libevdev.INPUT_PROP_DIRECT) INPUT_PROP_DIRECT:1 - + >>> int(libevdev.INPUT_PROP_DIRECT) + 1 .. attribute:: value - The numeric value of the property + The numeric value of the property. This value is also returned when + the object is converted to ``int``. .. attribute:: name The string name of this property + + .. attribute:: is_defined + + ``True`` if this event bit has a ``#define`` in the kernel header. + :class:`InputProperty` objects not defined in the header have a name + composed of the type name plus the hex code, e.g. ``INPUT_PROP_2E8``. + + Usually you will not need to check this property unless you need to + filter for known codes only. """ - __hash__ = super.__hash__ - def __eq__(self, other): - assert isinstance(other, InputProperty) + value: int = field(compare=False) + name: str = field(compare=False) + is_defined: bool = field(compare=False, default=True) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, InputProperty): + return NotImplemented return self.value == other.value + def __lt__(self, other: object) -> bool: + if not isinstance(other, InputProperty): + return NotImplemented + return self.value < other.value + + def __int__(self) -> int: + return self.value + + def __repr__(self) -> str: + return f"{self.name}:{self.value}" + + def __str__(self) -> str: + return repr(self) + + @classmethod + def from_value(cls, value: int) -> Self | None: + """ + Return the **known** InputProperty with the given value. The property + must be known to libevdev at libevdev compilation time. Example:: + + >>> InputProperty.from_value(0) + INPUT_PROP_POINTER:0 + >>> InputProperty.from_value(9999) + None + """ + return next((p for p in libevdev.props if p.value == value), None) # type: ignore + + @classmethod + def from_name(cls, name: str) -> Self | None: + """ + Return the **known** InputProperty with the given name. The property + must be known to libevdev at libevdev compilation time. Example:: + + The name must contain the ``INPUT_PROP_`` prefix. + + >>> InputProperty.from_name("INPUT_PROP_POINTER") + INPUT_PROP_POINTER:0 + >>> InputProperty.from_name("INPUT_PROP_FOOBAR") + None + """ + return next((p for p in libevdev.props if p.name == name), None) # type: ignore -def evbit(evtype, evcode=None): + @classmethod + def create(cls, value: int, name: str) -> Self: + """ + Instantiate a new InputProperty with the given value and name. + + This function should only be used for the creation of new properties + not known to libevdev. No checks are performed. Example:: + + >>> InputProperty.create(0, "INPUT_PROP_FOOBAR") + INPUT_PROP_FOOBAR:0 + """ + return cls(value=value, name=name, is_defined=False) + + +def evbit( + evtype: int | str, evcode: int | str | None = None +) -> EventCode | EventType | None: """ Takes an event type and an (optional) event code and returns the Enum representing that type or code, whichever applies. For example:: @@ -252,7 +418,7 @@ def evbit(evtype, evcode=None): etype = t break - if evcode is None and isinstance(evtype, str) and not evtype.startswith('EV_'): + if evcode is None and isinstance(evtype, str) and not evtype.startswith("EV_"): for t in libevdev.types: for c in t.codes: if c.name == evtype: @@ -269,7 +435,7 @@ def evbit(evtype, evcode=None): return ecode -def propbit(prop): +def propbit(prop: int | str) -> InputProperty | None: """ Takes a property value and returns the :class:`InputProperty` representing that property:: @@ -287,13 +453,10 @@ def propbit(prop): :return: the converted :class:`InputProperty` or None if it does not exist :rtype: InputProperty """ - try: - return [p for p in libevdev.props if p.value == prop or p.name == prop][0] - except IndexError: - return None + return next((p for p in libevdev.props if p.value == prop or p.name == prop), None) -def _load_consts(): +def _load_consts() -> None: """ Loads all event type, code and property names and makes them available as enums in the module. Use as e.g. libevdev.EV_SYN.SYN_REPORT. @@ -330,40 +493,46 @@ def _load_consts(): cmax = Libevdev.type_max(t) - new_class = type(tname, (EventType, ), - {'value': t, - 'name': tname, - 'max': cmax}) + et = EventType(name=tname, value=t, max=cmax, codes=[]) + types.append(et) - type_object = new_class() # libevdev.EV_REL, libevdev.EV_ABS, etc. - setattr(libevdev, tname, type_object) - types.append(type_object) + setattr(libevdev, tname, et) if cmax is None: - setattr(type_object, 'codes', []) continue codes = [] for c in range(cmax + 1): cname = Libevdev.event_to_name(t, c) - # For those without names, we just use the type name plus - # hexcode + name = cname + has_name = cname is not None + # For those without names, we use the type name plus + # hexcode for the actual name, but a prefixing underscore for + # the class name (it's not stable API). + # i.e. libedev.EV_REL._REL_0B.name == 'REL_0B' + if name is None: + name = f"{tname[3:]}_{c:02X}" + if cname is None: - cname = "{}_{:02X}".format(tname[3:], c) + cname = f"_{name}" + + ec = EventCode(type=et, name=name, value=c, is_defined=has_name) + + # libdevdev.EV_REL.FOO + setattr(et, cname, ec) + + # defined event codes are set on the libevdev module directly + if has_name: + # libevdev.FOO + setattr(libevdev, cname, ec) - new_class = type(cname, (EventCode, ), - {'type': type_object, - 'name': cname, - 'value': c}) - code_object = new_class() - setattr(type_object, cname, code_object) - codes.append(code_object) + codes.append(ec) - setattr(type_object, 'codes', codes) + et.codes = codes # list of all types - setattr(libevdev, 'types', types) + setattr(libevdev, "types", types) pmax = Libevdev.property_to_value("INPUT_PROP_MAX") assert pmax is not None @@ -373,16 +542,13 @@ def _load_consts(): if pname is None: continue - new_class = type(pname, (InputProperty, ), - {'value': p, - 'name': pname}) - prop_object = new_class() + ip = InputProperty(name=pname, value=p) - setattr(libevdev, pname, prop_object) - props.append(prop_object) + setattr(libevdev, pname, ip) + props.append(ip) - setattr(libevdev, 'props', props) + setattr(libevdev, "props", props) -if not os.environ.get('READTHEDOCS'): +if not os.environ.get("READTHEDOCS"): _load_consts() diff --git a/libevdev/device.py b/libevdev/device.py index 2ff63ce..f89d4da 100644 --- a/libevdev/device.py +++ b/libevdev/device.py @@ -1,4 +1,3 @@ -# -*- coding: latin-1 -*- # Copyright © 2017 Red Hat, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a @@ -20,14 +19,23 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. +from __future__ import annotations + import time import os +from typing import BinaryIO, Iterator +from dataclasses import dataclass import libevdev from ._clib import Libevdev, UinputDevice -from ._clib import READ_FLAG_SYNC, READ_FLAG_NORMAL, READ_FLAG_FORCE_SYNC, READ_FLAG_BLOCKING +from ._clib import ( + READ_FLAG_SYNC, + READ_FLAG_NORMAL, + READ_FLAG_FORCE_SYNC, + READ_FLAG_BLOCKING, +) from .event import InputEvent -from .const import InputProperty +from .const import InputProperty, EventCode, EventType class InvalidFileError(Exception): @@ -35,7 +43,8 @@ class InvalidFileError(Exception): A file provided is not a valid file descriptor for libevdev or this device must not have a file descriptor """ - pass + + ... class InvalidArgumentException(Exception): @@ -47,7 +56,8 @@ class InvalidArgumentException(Exception): A human-readable error message """ - def __init__(self, msg=None): + + def __init__(self, msg: str | None = None): self.message = msg def __repr__(self): @@ -87,20 +97,18 @@ class EventsDroppedException(Exception): for e in ctx.sync(): print(e) """ - pass + + ... -class InputAbsInfo(object): +@dataclass +class InputAbsInfo: """ A class representing the struct input_absinfo for a given EV_ABS code. Any of the attributes may be set to None, those that are None are simply ignored by libevdev. - .. attribute:: minimum - - the minimum value of this axis - :property minimum: the minimum value for this axis :property maximum: the maximum value for this axis :property fuzz: the fuzz value for this axis @@ -108,30 +116,31 @@ class InputAbsInfo(object): :property resolution: the resolution for this axis :property value: the current value of this axis """ - def __init__(self, minimum=None, maximum=None, fuzz=None, flat=None, - resolution=None, value=None): - self.minimum = minimum - self.maximum = maximum - self.fuzz = fuzz - self.flat = flat - self.resolution = resolution - self.value = value - - def __repr__(self): - return 'min:{} max:{} fuzz:{} flat:{} resolution:{} value:{}'.format( - self.minimum, self.maximum, self.fuzz, self.flat, - self.resolution, self.value) - def __eq__(self, other): - return (self.minimum == other.minimum and - self.maximum == other.maximum and - self.value == other.value and - self.resolution == other.resolution and - self.fuzz == other.fuzz and - self.flat == other.flat) - - -class Device(object): + minimum: int | None = None + maximum: int | None = None + fuzz: int | None = None + flat: int | None = None + resolution: int | None = None + value: int | None = None + + def __repr__(self) -> str: + return f"min:{self.minimum} max:{self.maximum} fuzz:{self.fuzz} flat:{self.flat} resolution:{self.resolution} value:{self.value}" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, InputAbsInfo): + return NotImplemented + return ( + self.minimum == other.minimum + and self.maximum == other.maximum + and self.value == other.value + and self.resolution == other.resolution + and self.fuzz == other.fuzz + and self.flat == other.flat + ) + + +class Device: """ This class represents an evdev device backed by libevdev. The device may represent a real device in the system or a constructed device where the @@ -158,83 +167,93 @@ class Device(object): :type fd: A file-like object """ + class _EventValueSet: - def __init__(self, parent_device): + def __init__(self, parent_device: "Device") -> None: self._device = parent_device - def __getitem__(self, code): + def __getitem__(self, code: EventCode) -> int | None: # calling device.value[slot axis] is a bug on MT devices - if (code.type == libevdev.EV_ABS and - code >= libevdev.EV_ABS.ABS_MT_SLOT and - self._device.num_slots is not None): - raise InvalidArgumentException('Cannot fetch value for MT axes') + if ( + code.type == libevdev.EV_ABS + and code >= libevdev.ABS_MT_SLOT + and self._device.num_slots is not None + ): + raise InvalidArgumentException("Cannot fetch value for MT axes") return self._device._libevdev.event_value(code.type.value, code.value) class _InputAbsInfoSet: - def __init__(self, parent_device): + def __init__(self, parent_device: "Device") -> None: self._device = parent_device - def __getitem__(self, code): + def __getitem__(self, code: EventCode) -> InputAbsInfo | None: assert code.type == libevdev.EV_ABS r = self._device._libevdev.absinfo(code.value) if r is None: return r - return InputAbsInfo(r['minimum'], r['maximum'], - r['fuzz'], r['flat'], - r['resolution'], r['value']) + return InputAbsInfo( + r["minimum"], + r["maximum"], + r["fuzz"], + r["flat"], + r["resolution"], + r["value"], + ) - def __setitem__(self, code, absinfo): + def __setitem__(self, code: EventCode, absinfo: InputAbsInfo) -> None: assert code.type == libevdev.EV_ABS if not self._device.has(code): - raise InvalidArgumentException('Device does not have event code') + raise InvalidArgumentException("Device does not have event code") data = {} if absinfo.minimum is not None: - data['minimum'] = absinfo.minimum + data["minimum"] = absinfo.minimum if absinfo.maximum is not None: - data['maximum'] = absinfo.maximum + data["maximum"] = absinfo.maximum if absinfo.fuzz is not None: - data['fuzz'] = absinfo.fuzz + data["fuzz"] = absinfo.fuzz if absinfo.flat is not None: - data['flat'] = absinfo.flat + data["flat"] = absinfo.flat if absinfo.resolution is not None: - data['resolution'] = absinfo.resolution + data["resolution"] = absinfo.resolution if absinfo.value is not None: - data['value'] = absinfo.value + data["value"] = absinfo.value self._device._libevdev.absinfo(code.value, data) class _SlotValue: - def __init__(self, device, slot): + def __init__(self, device: "Device", slot: int) -> None: self._device = device self._slot = slot - def __getitem__(self, code): - if (code.type is not libevdev.EV_ABS or - code <= libevdev.EV_ABS.ABS_MT_SLOT): - raise InvalidArgumentException('Event code must be one of EV_ABS.ABS_MT_*') + def __getitem__(self, code: EventCode) -> int | None: + if code.type != libevdev.EV_ABS or code <= libevdev.ABS_MT_SLOT: + raise InvalidArgumentException( + "Event code must be one of EV_ABS.ABS_MT_*" + ) if not self._device.has(code): return None return self._device._libevdev.slot_value(self._slot, code.value) - def __setitem__(self, code, value): - if (code.type is not libevdev.EV_ABS or - code <= libevdev.EV_ABS.ABS_MT_SLOT): - raise InvalidArgumentException('Event code must be one of EV_ABS.ABS_MT_*') + def __setitem__(self, code: EventCode, value: int) -> None: + if code.type != libevdev.EV_ABS or code <= libevdev.ABS_MT_SLOT: + raise InvalidArgumentException( + "Event code must be one of EV_ABS.ABS_MT_*" + ) if not self._device.has(code): - raise InvalidArgumentException('Event code does not exist') + raise InvalidArgumentException("Event code does not exist") self._device._libevdev.slot_value(self._slot, code.value, new_value=value) - def __init__(self, fd=None): + def __init__(self, fd: BinaryIO | None = None) -> None: self._libevdev = Libevdev(fd) - self._uinput = None + self._uinput: UinputDevice | None = None self._is_grabbed = False self._values = Device._EventValueSet(self) self._absinfos = Device._InputAbsInfoSet(self) @@ -246,66 +265,79 @@ def __init__(self, fd=None): self._libevdev.set_clock_id(1) @property - def name(self): + def name(self) -> str: """ :returns: the device name """ return self._libevdev.name @name.setter - def name(self, name): + def name(self, name: str): self._libevdev.name = name @property - def phys(self): + def phys(self) -> str | None: """ :returns: the device's kernel phys or None. """ return self._libevdev.phys @phys.setter - def phys(self, phys): + def phys(self, phys: str | None): self._libevdev.phys = phys @property - def uniq(self): + def uniq(self) -> str | None: """ :returns: the device's uniq string or None """ return self._libevdev.uniq @uniq.setter - def uniq(self, uniq): + def uniq(self, uniq: str | None): self._libevdev.uniq = uniq @property - def driver_version(self): + def driver_version(self) -> int: """ :returns: the device's driver version """ return self._libevdev.driver_version @property - def id(self): + def id(self) -> dict[str, int]: """ - :returns: A dict with the keys 'bustype', 'vendor', 'product', 'version'. + :returns: A dict with the keys ``'bustype'``, ``'vendor'``, + ``'product'``, ``'version'``. When used as a setter, only existing keys are applied to the device. For example, to update the product ID only:: ctx = Device() - id["property"] = 1234 - ctx.id = id + ids = {'product' : 1234} + ctx.id = ids + + You must assign a new dictionary to ``id``. Technical limitations + prohibit accessing the ``id`` dictionary itself for write access. + See this example: :: + + $ ctx = Device() + $ ctx.id['vendor'] = 1234 + $ print(ctx.id['vendor']) + 0 + $ ctx.id = {'vendor': 1234} + $ print(ctx.id['vendor']) + 1234 """ return self._libevdev.id @id.setter - def id(self, vals): + def id(self, vals: dict[str, int]): self._libevdev.id = vals @property - def fd(self): + def fd(self) -> BinaryIO | None: """ This fd represents the file descriptor to this device, if any. If no fd was provided in the constructor, None is returned. If the device @@ -334,7 +366,7 @@ def fd(self): return self._libevdev.fd @fd.setter - def fd(self, fileobj): + def fd(self, fileobj: BinaryIO): if self._libevdev.fd is None: raise InvalidFileError() self._libevdev.fd = fileobj @@ -346,7 +378,7 @@ def fd(self, fileobj): self.grab() @property - def evbits(self): + def evbits(self) -> dict[EventType, list[EventCode]]: """ Returns a dict with all supported event types and event codes, in the form of:: @@ -371,32 +403,32 @@ def evbits(self): return types @property - def properties(self): + def properties(self) -> list[InputProperty]: """ Returns a list of all supported input properties """ return [p for p in libevdev.props if self.has_property(p)] - def has_property(self, prop): + def has_property(self, prop: InputProperty) -> bool: """ :param prop: a property :returns: True if the device has the property, False otherwise """ return self._libevdev.has_property(prop.value) - def has(self, evcode): + def has(self, evcode: EventType | EventCode) -> bool: """ :param evcode: the event type or event code :type evcode: EventType or EventCode :returns: True if the device has the type and/or code, False otherwise """ - try: + if isinstance(evcode, EventCode): return self._libevdev.has_event(evcode.type.value, evcode.value) - except AttributeError: + else: return self._libevdev.has_event(evcode.value) @property - def num_slots(self): + def num_slots(self) -> int | None: """ :returns: the number of slots on this device or ``None`` if this device does not support slots @@ -406,7 +438,7 @@ def num_slots(self): return self._libevdev.num_slots @property - def current_slot(self): + def current_slot(self) -> int | None: """ :returns: the current slot on this device or ``None`` if this device does not support slots @@ -416,7 +448,7 @@ def current_slot(self): return self._libevdev.current_slot @property - def absinfo(self): + def absinfo(self) -> "Device._InputAbsInfoSet": """ Query the device's absinfo for the given event code. This attribute can both query and modify the :class:`InputAbsInfo` values of this @@ -466,7 +498,7 @@ def absinfo(self): """ return self._absinfos - def sync_absinfo_to_kernel(self, code): + def sync_absinfo_to_kernel(self, code: EventCode) -> None: """ Synchronizes our view of an absinfo axis to the kernel, thus updating the the device for every other client. This function should @@ -485,26 +517,37 @@ def sync_absinfo_to_kernel(self, code): >>> d.sync_absinfo_to_kernel(libevdev.EV_ABS.ABS_X) """ a = self.absinfo[code] - data = {"minimum": a.minimum, - "maximum": a.maximum, - "fuzz": a.fuzz, - "flat": a.flat, - "resolution": a.resolution} + assert a is not None + assert a.minimum is not None + assert a.maximum is not None + assert a.resolution is not None + assert a.fuzz is not None + assert a.flat is not None + data = { + "minimum": a.minimum, + "maximum": a.maximum, + "fuzz": a.fuzz, + "flat": a.flat, + "resolution": a.resolution, + } self._libevdev.absinfo(code.value, new_values=data, kernel=True) - def events(self): + def events(self) -> Iterator[InputEvent]: """ Returns an iterable with currently pending events. Event processing should look like this:: fd = open("/dev/input/event0", "rb") + fcntl.fcntl(fd, fcntl.F_SETFL, os.O_NONBLOCK) # optional ctx = libevdev.Device(fd) while True: for e in ctx.events(): print(e): + ... other mainloop code ... + This function detects if the file descriptor is in blocking or non-blocking mode and adjusts its behavior accordingly. If the file descriptor is in nonblocking mode and no events are available, this @@ -514,7 +557,7 @@ def events(self): :returns: an iterable with the currently pending events """ if self._libevdev.fd is None: - return [] + return iter(()) if os.get_blocking(self._libevdev.fd.fileno()): flags = READ_FLAG_BLOCKING @@ -524,12 +567,13 @@ def events(self): ev = self._libevdev.next_event(flags) while ev is not None: code = libevdev.evbit(ev.type, ev.code) + assert code is not None yield InputEvent(code, ev.value, ev.sec, ev.usec) - if code == libevdev.EV_SYN.SYN_DROPPED: + if code == libevdev.SYN_DROPPED: raise EventsDroppedException() ev = self._libevdev.next_event(flags) - def sync(self, force=False): + def sync(self, force: bool = False) -> Iterator[InputEvent]: """ Returns an iterator with events pending to re-sync the caller's view of the device with the one from libevdev. @@ -539,7 +583,7 @@ def sync(self, force=False): may have changed while libevdev was not processing events. """ if self._libevdev.fd is None: - return [] + return iter(()) if force: flags = READ_FLAG_FORCE_SYNC @@ -549,11 +593,12 @@ def sync(self, force=False): ev = self._libevdev.next_event(flags) while ev is not None: code = libevdev.evbit(ev.type, ev.code) + assert code is not None yield InputEvent(code, ev.value, ev.sec, ev.usec) ev = self._libevdev.next_event(flags) @property - def value(self): + def value(self) -> "Device._EventValueSet": """ Returns the current value for a given event code or None where the event code does not exist on the device:: @@ -576,7 +621,7 @@ def value(self): return self._values @property - def slots(self): + def slots(self) -> tuple["Device._SlotValue", ...]: """ Returns a tuple with the available slots, each of which contains a wrapper object to access a slot value:: @@ -605,11 +650,15 @@ def slots(self): """ if self.num_slots is None: - raise InvalidArgumentException('Device has no slots') + raise InvalidArgumentException("Device has no slots") return tuple(Device._SlotValue(self, slot) for slot in range(self.num_slots)) - def enable(self, event_code, data=None): + def enable( + self, + event_code: EventCode | EventType | InputProperty, + data: InputAbsInfo | int | None = None, + ) -> None: """ Enable an event type or event code on this device, even if not supported by this device. @@ -642,25 +691,33 @@ def enable(self, event_code, data=None): self._libevdev.enable_property(event_code.value) return - try: + if isinstance(event_code, EventCode): + raw_data = None if event_code.type == libevdev.EV_ABS: if data is None or not isinstance(data, InputAbsInfo): - raise InvalidArgumentException('enabling EV_ABS codes requires an InputAbsInfo') - - data = {"minimum": data.minimum or 0, - "maximum": data.maximum or 0, - "fuzz": data.fuzz or 0, - "flat": data.flat or 0, - "resolution": data.resolution or 0} + raise InvalidArgumentException( + "enabling EV_ABS codes requires an InputAbsInfo" + ) + + raw_data = { + "minimum": data.minimum or 0, + "maximum": data.maximum or 0, + "fuzz": data.fuzz or 0, + "flat": data.flat or 0, + "resolution": data.resolution or 0, + } elif event_code.type == libevdev.EV_REP: - if data is None: - raise InvalidArgumentException('enabling EV_REP codes requires an integer') - - self._libevdev.enable(event_code.type.value, event_code.value, data) - except AttributeError: + if data is None or not isinstance(data, int): + raise InvalidArgumentException( + "enabling EV_REP codes requires an integer" + ) + raw_data = data + + self._libevdev.enable(event_code.type.value, event_code.value, raw_data) + elif isinstance(event_code, EventType): self._libevdev.enable(event_code.value) - def disable(self, event_code): + def disable(self, event_code: EventCode | EventType) -> None: """ Disable the given event type or event code on this device. If the device does not support this type or code, this function does @@ -679,14 +736,13 @@ def disable(self, event_code): """ if isinstance(event_code, InputProperty): raise NotImplementedError() - - try: + elif isinstance(event_code, EventCode): self._libevdev.disable(event_code.type.value, event_code.value) - except AttributeError: + elif isinstance(event_code, EventType): self._libevdev.disable(event_code.value) @property - def devnode(self): + def devnode(self) -> str | None: """ Returns the device node for this device. The device node is None if this device has not been created as uinput device. @@ -696,7 +752,7 @@ def devnode(self): return self._uinput.devnode @property - def syspath(self): + def syspath(self) -> str | None: """ Returns the syspath for this device. The syspath is None if this device has not been created as uinput device. @@ -705,7 +761,7 @@ def syspath(self): return None return self._uinput.syspath - def create_uinput_device(self, uinput_fd=None): + def create_uinput_device(self, uinput_fd: BinaryIO | None = None) -> "Device": """ Creates and returns a new :class:`Device` based on this libevdev device. The new device is equivalent to one created with @@ -732,12 +788,13 @@ def create_uinput_device(self, uinput_fd=None): :param uinput_fd: A file descriptor to the /dev/input/uinput device. If None, the device is opened and closed automatically. :raises: OSError """ - d = libevdev.Device() + d = Device() d.name = self.name d.id = self.id for t, cs in self.evbits.items(): for c in cs: + data: InputAbsInfo | int | None if t == libevdev.EV_ABS: data = self.absinfo[c] elif t == libevdev.EV_REP: @@ -747,12 +804,12 @@ def create_uinput_device(self, uinput_fd=None): d.enable(c, data) for p in self.properties: - self.enable(p) + d.enable(p) d._uinput = UinputDevice(self._libevdev, uinput_fd) return d - def send_events(self, events): + def send_events(self, events: list[InputEvent]) -> None: """ Send the list of :class:`InputEvent` events through this device. All events must have a valid :class:`EventCode` and value, the timestamp @@ -773,16 +830,21 @@ def send_events(self, events): if not self._uinput: raise InvalidFileError() - if None in [e.code for e in events]: - raise InvalidArgumentException('All events must have an event code') + if any(e.type is None or e.code is None for e in events): + raise InvalidArgumentException( + "All events must have an event type and code" + ) - if None in [e.value for e in events]: - raise InvalidArgumentException('All events must have a value') + if any(e.code.value is None for e in events): # type: ignore + raise InvalidArgumentException("All events must have a value") for e in events: + assert ( + e.code is not None and e.code.value is not None and e.value is not None + ) self._uinput.write_event(e.type.value, e.code.value, e.value) - def grab(self): + def grab(self) -> None: """ Exclusively grabs the device, preventing events from being seen by anyone else. This includes in-kernel consumers of the events (e.g. @@ -799,7 +861,7 @@ def grab(self): d.grab() # device is now exclusively grabbed - fd.close() + fd1.close() fd2 = open("/dev/input/event0", "rb") d.fd = fd2 # device is now exclusively grabbed @@ -816,9 +878,27 @@ def grab(self): self._is_grabbed = True - def ungrab(self): + def ungrab(self) -> None: """ Removes an exclusive grabs on the device, see :func:`grab`. """ self._libevdev.grab(False) self._is_grabbed = False + + def set_leds(self, leds: list[tuple[EventCode, int]]) -> None: + """ + Write the LEDs to the device:: + + >>> fd = open(path, 'r+b', buffering=0) + >>> d = libevdev.Device(fd) + >>> d.set_leds([(libevdev.EV_LED.LED_NUML, 0), + (libevdev.EV_LED.LED_SCROLLL, 1)]) + + Updating LED states require the fd to be in write-mode. + """ + for led in leds: + if led[0].type is not libevdev.EV_LED: + raise InvalidArgumentException("Event code must be one of EV_LED.*") + + for led in leds: + self._libevdev.set_led(led[0].value, led[1] != 0) diff --git a/libevdev/event.py b/libevdev/event.py index 0e86145..d505499 100644 --- a/libevdev/event.py +++ b/libevdev/event.py @@ -1,4 +1,3 @@ -# -*- coding: latin-1 -*- # Copyright © 2017 Red Hat, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a @@ -20,10 +19,12 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. +from __future__ import annotations + from .const import EventType, EventCode -class InputEvent(object): +class InputEvent: """ Represents one input event of type struct input_event as defined in ``linux/input.h`` and returned by ``libevdev_next_event()``. @@ -46,10 +47,6 @@ class InputEvent(object): >>> e == InputEvent(libevdev.EV_REL.REL_X, value=2) False - .. attribute:: code - - The :class:`EventCode` or :class:`EventType` for this input event - .. attribute:: value The (optional) value for the event's axis @@ -63,9 +60,16 @@ class InputEvent(object): The timestamp, microseconds """ - def __init__(self, code, value=None, sec=0, usec=0): + def __init__( + self, + code: EventCode | EventType, + value: int | None = None, + sec: int = 0, + usec: int = 0, + ) -> None: assert isinstance(code, EventCode) or isinstance(code, EventType) + self._code: EventCode | None if isinstance(code, EventCode): self._type = code.type self._code = code @@ -77,22 +81,22 @@ def __init__(self, code, value=None, sec=0, usec=0): self.value = value @property - def code(self): + def code(self) -> EventCode | None: """ - :return: the EventCode for this event or None + :return: The :class:`EventCode` or :class:`EventType` for this input event :rtype: EventCode """ return self._code @property - def type(self): + def type(self) -> EventType: """ :return: the event type for this event :rtype: EventType """ return self._type - def matches(self, code, value=None): + def matches(self, code: EventType | EventCode, value: int | None = None) -> bool: """ :param code: the event type or code :type code: EventType or EventCode @@ -124,7 +128,7 @@ def matches(self, code, value=None): else: return self._code == code - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, InputEvent): return False @@ -133,9 +137,9 @@ def __eq__(self, other): return self.matches(other.code, other.value) - def __repr__(self): + def __repr__(self) -> str: tname = self.type.name cname = None if self.code is not None: cname = self.code.name - return 'InputEvent({}, {}, {})'.format(tname, cname, self.value) + return f"InputEvent({tname}, {cname}, {self.value})" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..6609831 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,46 @@ +[project] +name = "libevdev" +version = "0.13.1" +description = "Python wrapper for libevdev" +readme = "README.md" +license = {text = "MIT"} +authors = [ + {name = "Peter Hutterer", email = "peter.hutterer@who-t.net"}, +] +keywords = ["evdev", "input", "uinput", "libevdev"] +classifiers = [ + "Development Status :: 4 - Beta", + "Topic :: Software Development", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", +] +requires-python = ">=3.9" +dependencies = [ + "typing_extensions>=4.0; python_version<'3.11'", +] + +[project.urls] +Homepage = "https://gitlab.freedesktop.org/libevdev/python-libevdev" +Documentation = "https://python-libevdev.readthedocs.io/en/latest/" +Repository = "https://gitlab.freedesktop.org/libevdev/python-libevdev" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["libevdev"] + +[tool.ruff] +exclude = [".git", "__pycache__"] + +[tool.pytest.ini_options] +testpaths = ["test"] +python_files = ["test_*.py"] +addopts = "-v --strict-markers" + +[dependency-groups] +dev = [ + "pytest>=8.4.2", +] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index b88034e..0000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[metadata] -description-file = README.md diff --git a/setup.py b/setup.py deleted file mode 100755 index 5ae5dfe..0000000 --- a/setup.py +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env python3 - -from distutils.core import setup - -long_description = ''' -python-libevdev is a Python wrapper arround the libevdev C library. It -provides a Pythonic API to read events from the Linux kernel's input device -nodes and to read and/or modify the device's state and capabilities. - -The documentation is available here: -https://python-evdev.readthedocs.io/en/latest/ -''' - -setup(name='libevdev', - version='0.5', - description='Python wrapper for libevdev', - long_description=long_description, - author='Peter Hutterer', - author_email='peter.hutterer@who-t.net', - url='https://www.github.com/whot/python-libevdev/', - packages=['libevdev'], - classifiers=[ - 'Development Status :: 3 - Alpha', - 'Topic :: Software Development', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 3', - ], - python_requires='>=3', -) diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/test-clib.py b/test/test-clib.py deleted file mode 100644 index 0ba00cb..0000000 --- a/test/test-clib.py +++ /dev/null @@ -1,581 +0,0 @@ -import os -import unittest -import ctypes -from libevdev._clib import Libevdev, UinputDevice - -# Note: these tests test for the correct functioning of the python bindings, -# not of libevdev underneath. Some ranges are hardcoded for simplicity, e.g. -# if properties 1-4 work the others will work too if libevdev works -# correctly - -def is_root(): - return os.getuid() == 0 - -class TestNameConversion(unittest.TestCase): - - def test_type_max(self): - for t in ["REL", "ABS"]: - c = Libevdev.event_to_value("EV_{}".format(t), "{}_MAX".format(t)) - max = Libevdev.type_max("EV_{}".format(t)) - self.assertEqual(c, max) - - def test_prop_name(self): - name = Libevdev.property_to_name(0) - self.assertEqual(name, "INPUT_PROP_POINTER") - - prevname = None - for i in range(5): - name = Libevdev.property_to_name(i) - self.assertIsNotNone(name) - self.assertTrue(name.startswith("INPUT_PROP_")) - self.assertNotEqual(prevname, name) - prevname = name - - def test_prop_to_name_invalid(self): - name = Libevdev.property_to_name(-1) - self.assertIsNone(name) - name = Libevdev.property_to_name(100) - self.assertIsNone(name) - with self.assertRaises(ctypes.ArgumentError): - name = Libevdev.property_to_name("foo") - - def test_type_to_name(self): - name = Libevdev.event_to_name(1) - self.assertEqual(name, "EV_KEY") - - prevname = None - for i in range(5): - name = Libevdev.event_to_name(i) - self.assertIsNotNone(name) - self.assertTrue(name.startswith("EV_")) - self.assertNotEqual(prevname, name) - prevname = name - - def test_type_to_name_invalid(self): - name = Libevdev.event_to_name(-1) - self.assertIsNone(name) - name = Libevdev.event_to_name(100) - self.assertIsNone(name) - with self.assertRaises(ctypes.ArgumentError): - name = Libevdev.event_to_name("foo") - - def test_code_to_name(self): - name = Libevdev.event_to_name(0, 0) - self.assertEqual(name, "SYN_REPORT") - - name = Libevdev.event_to_name(1, 1) - self.assertEqual(name, "KEY_ESC") - - def test_code_to_name_invalid(self): - name = Libevdev.event_to_name(0, 1000) - self.assertIsNone(name) - name = Libevdev.event_to_name(0, -1) - self.assertIsNone(name) - with self.assertRaises(ctypes.ArgumentError): - name = Libevdev.event_to_name(0, "foo") - - def test_type_to_value(self): - v = Libevdev.event_to_value("EV_REL") - self.assertEqual(v, 2) - - def test_type_value_invalid(self): - v = Libevdev.event_to_value("foo") - self.assertIsNone(v) - with self.assertRaises(AttributeError): - v = Libevdev.event_to_value(0) - - def test_code_value(self): - v = Libevdev.event_to_value("EV_REL", "REL_Y") - self.assertEqual(v, 1) - - v = Libevdev.event_to_value(0, "SYN_DROPPED") - self.assertEqual(v, 3) - - def test_code_value_invalid(self): - v = Libevdev.event_to_value("EV_REL", "KEY_ESC") - self.assertIsNone(v) - - -class TestLibevdevDevice(unittest.TestCase): - - def test_ctx_init(self): - l = Libevdev() - del l - - def test_set_get_name(self): - l = Libevdev() - name = l.name - self.assertEqual(name, '') - - l.name = "foo" - name = l.name - self.assertEqual(name, "foo") - - l.name = None - name = l.name - self.assertEqual(name, "") - - def test_set_get_uniq(self): - l = Libevdev() - uniq = l.uniq - self.assertIsNone(uniq) - - l.uniq = "foo" - uniq = l.uniq - self.assertEqual(uniq, "foo") - - # libevdev issue: phys may be NULL (unlike the name) but we can't - # set it to NULL. But the conversion code returns None for the empty - # string, so let's test for that - l.uniq = None - uniq = l.uniq - self.assertEqual(uniq, None) - - def test_set_get_phys(self): - l = Libevdev() - phys = l.phys - self.assertIsNone(phys) - - l.phys = "foo" - phys = l.phys - self.assertEqual(phys, "foo") - - # libevdev issue: phys may be NULL (unlike the name) but we can't - # set it to NULL. But the conversion code returns None for the empty - # string, so let's test for that - l.phys = None - phys = l.phys - self.assertEqual(phys, None) - - def test_get_driver_version(self): - l = Libevdev() - v = l.driver_version - self.assertEqual(v, 0) - # we can't set this, nothing else we can test - - def test_set_get_id(self): - l = Libevdev() - id = l.id - self.assertEqual(id["bustype"], 0) - self.assertEqual(id["vendor"], 0) - self.assertEqual(id["product"], 0) - self.assertEqual(id["version"], 0) - - id["bustype"] = 1 - id["vendor"] = 2 - id["product"] = 3 - id["version"] = 4 - - l.id = id - id = l.id - self.assertEqual(id["bustype"], 1) - self.assertEqual(id["vendor"], 2) - self.assertEqual(id["product"], 3) - self.assertEqual(id["version"], 4) - - del id["bustype"] - del id["vendor"] - del id["product"] - id["version"] = 5 - - l.id = id - id = l.id - self.assertEqual(id["bustype"], 1) - self.assertEqual(id["vendor"], 2) - self.assertEqual(id["product"], 3) - self.assertEqual(id["version"], 5) - - -class TestRealDevice(unittest.TestCase): - """ - Tests various things against /dev/input/event3 which is usually the - keyboard. Requires root rights though. - """ - - def setUp(self): - self.fd = open("/dev/input/event3", "rb") - - def tearDown(self): - self.fd.close() - - @unittest.skipUnless(is_root(), 'Test requires root') - def test_set_fd(self): - l = Libevdev() - l.fd = self.fd - fd = l.fd - self.assertEqual(self.fd, fd) - - fd2 = open("/dev/input/event3", "rb") - l.fd = fd2 - fd = l.fd - self.assertEqual(fd, fd2) - fd2.close() - - @unittest.skipUnless(is_root(), 'Test requires root') - def test_init_fd(self): - l = Libevdev(self.fd) - fd = l.fd - self.assertEqual(self.fd, fd) - - fd2 = open("/dev/input/event3", "rb") - l.fd = fd2 - fd = l.fd - self.assertEqual(fd, fd2) - fd2.close() - - @unittest.skipUnless(is_root(), 'Test requires root') - def test_ids(self): - l = Libevdev(self.fd) - id = l.id - self.assertNotEqual(id["bustype"], 0) - self.assertNotEqual(id["vendor"], 0) - self.assertNotEqual(id["product"], 0) - self.assertNotEqual(id["version"], 0) - - @unittest.skipUnless(is_root(), 'Test requires root') - def test_name(self): - l = Libevdev(self.fd) - name = l.name - self.assertNotEqual(name, "") - - @unittest.skipUnless(is_root(), 'Test requires root') - def test_driver_version(self): - l = Libevdev(self.fd) - v = l.driver_version - self.assertEqual(v, 0x010001) - - @unittest.skipUnless(is_root(), 'Test requires root') - def test_set_clock_id(self): - l = Libevdev(self.fd) - try: - import time - clock = time.CLOCK_MONOTONIC - except AttributeError: - clock = 1 - rc = l.set_clock_id(clock) - self.assertEqual(rc, 0) - - @unittest.skipUnless(is_root(), 'Test requires root') - def test_grab(self): - l = Libevdev(self.fd) - # no exception == success - l.grab() - l.grab(False) - l.grab(True) - - @unittest.skipUnless(is_root(), 'Test requires root') - def test_has_event(self): - l = Libevdev(self.fd) - self.assertTrue(l.has_event("EV_SYN", "SYN_REPORT")) - - type_supported = -1 - for i in range(1, 5): - if l.has_event(i): - type_supported = i - break - - self.assertGreater(type_supported, 0) - - codes_supported = 0 - for i in range(150): - if l.has_event(type_supported, i): - codes_supported += 1 - - self.assertGreater(codes_supported, 0) - - @unittest.skipUnless(is_root(), 'Test requires root') - def test_has_property(self): - """ - Let's assume at least one between event0 and event10 is a device - with at least one property set. May cause false negatives. - """ - - props_supported = 0 - for i in range(10): - try: - with open("/dev/input/event{}".format(i), "rb") as fd: - l = Libevdev(fd) - - for p in range(6): - if l.has_property(p): - props_supported += 1 - except IOError: - # Not all eventX nodes are guaranteed to exist - pass - self.assertGreater(props_supported, 0) - - @unittest.skipUnless(is_root(), 'Test requires root') - def test_num_slots(self): - """ - Let's assume that our device doesn't have slots - """ - l = Libevdev(self.fd) - self.assertIsNone(l.num_slots) - - -class TestAbsDevice(unittest.TestCase): - """ - Tests various things against the first device with EV_ABS. - We're relying on that this device has ABS_Y, this tests against a code - that's nonzero and is the most common ABS anyway. - Requires root rights. - """ - - def setUp(self): - want_fd = None - for i in range(20): - try: - fd = open("/dev/input/event{}".format(i), "rb") - l = Libevdev(fd) - if l.has_event("EV_ABS", "ABS_Y"): - want_fd = fd - break - fd.close() - except IOError: - # Not all eventX nodes are guaranteed to exist - pass - - self.assertIsNotNone(want_fd) - self.fd = want_fd - - def tearDown(self): - self.fd.close() - - @unittest.skipUnless(is_root(), 'Test requires root') - def test_absinfo(self): - l = Libevdev(self.fd) - a = l.absinfo("ABS_Y") - self.assertTrue("minimum" in a) - self.assertTrue("maximum" in a) - self.assertTrue("resolution" in a) - self.assertTrue("fuzz" in a) - self.assertTrue("flat" in a) - self.assertTrue("value" in a) - - @unittest.skipUnless(is_root(), 'Test requires root') - def test_set_absinfo(self): - l = Libevdev(self.fd) - real_a = l.absinfo("ABS_Y") - a = l.absinfo("ABS_Y") - a["minimum"] = 100 - a["maximum"] = 200 - a["fuzz"] = 300 - a["flat"] = 400 - a["resolution"] = 500 - a["value"] = 600 - - a = l.absinfo("ABS_Y", new_values=a) - self.assertEqual(a["minimum"], 100) - self.assertEqual(a["maximum"], 200) - self.assertEqual(a["fuzz"], 300) - self.assertEqual(a["flat"], 400) - self.assertEqual(a["resolution"], 500) - self.assertEqual(a["value"], 600) - - l2 = Libevdev(self.fd) - a2 = l2.absinfo("ABS_Y") - self.assertNotEqual(a["minimum"], real_a["minimum"]) - self.assertNotEqual(a["maximum"], real_a["maximum"]) - self.assertNotEqual(a["fuzz"], real_a["fuzz"]) - self.assertNotEqual(a["flat"], real_a["flat"]) - self.assertNotEqual(a["resolution"], real_a["resolution"]) - self.assertEqual(a2["value"], real_a["value"]) - self.assertEqual(a2["minimum"], real_a["minimum"]) - self.assertEqual(a2["maximum"], real_a["maximum"]) - self.assertEqual(a2["fuzz"], real_a["fuzz"]) - self.assertEqual(a2["flat"], real_a["flat"]) - self.assertEqual(a2["resolution"], real_a["resolution"]) - self.assertEqual(a2["value"], real_a["value"]) - - @unittest.skipUnless(is_root(), 'Test requires root') - def test_set_absinfo_invalid(self): - l = Libevdev(self.fd) - with self.assertRaises(ValueError): - l.absinfo("REL_X") - - @unittest.skipUnless(is_root(), 'Test requires root') - def test_set_absinfo_kernel(self): - # FIXME: yeah, nah, not testing that on a random device... - pass - - @unittest.skipUnless(is_root(), 'Test requires root') - def test_get_set_event_value(self): - l = Libevdev(self.fd) - v = l.event_value("EV_ABS", "ABS_Y") - self.assertIsNotNone(v) - v = l.event_value(0x03, 0x01, new_value=300) - self.assertEqual(v, 300) - v = l.event_value(0x03, 0x01) - self.assertEqual(v, 300) - - @unittest.skipUnless(is_root(), 'Test requires root') - def test_get_set_event_value_invalid(self): - l = Libevdev(self.fd) - v = l.event_value("EV_ABS", 0x600) - self.assertIsNone(v) - v = l.event_value(0x03, 0x600, new_value=300) - self.assertIsNone(v) - - @unittest.skipUnless(is_root(), 'Test requires root') - def test_enable_event_code(self): - l = Libevdev(self.fd) - - self.assertFalse(l.has_event("EV_REL", "REL_RY")) - l.enable("EV_REL", "REL_RY") - self.assertTrue(l.has_event("EV_REL", "REL_RY")) - l.disable("EV_REL", "REL_RY") - self.assertFalse(l.has_event("EV_REL", "REL_RY")) - - data = {"minimum": 100, - "maximum": 200, - "value": 300, - "fuzz": 400, - "flat": 500, - "resolution": 600} - self.assertFalse(l.has_event("EV_ABS", "ABS_RY")) - l.enable("EV_ABS", "ABS_RY", data) - self.assertTrue(l.has_event("EV_ABS", "ABS_RY")) - l.disable("EV_ABS", "ABS_RY") - self.assertFalse(l.has_event("EV_ABS", "ABS_RY")) - - @unittest.skipUnless(is_root(), 'Test requires root') - def test_enable_property(self): - l = Libevdev(self.fd) - self.assertFalse(l.has_property("INPUT_PROP_ACCELEROMETER")) - l.enable_property("INPUT_PROP_ACCELEROMETER") - self.assertTrue(l.has_property("INPUT_PROP_ACCELEROMETER")) - -class TestMTDevice(unittest.TestCase): - """ - Tests various things against the first MT device found. - Requires root rights. - """ - - def setUp(self): - want_fd = None - for i in range(20): - try: - fd = open("/dev/input/event{}".format(i), "rb") - l = Libevdev(fd) - if l.num_slots is not None: - want_fd = fd - break - fd.close() - except IOError: - # Not all eventX nodes are guaranteed to exist - pass - - self.assertIsNotNone(want_fd) - self.fd = want_fd - - def tearDown(self): - self.fd.close() - - @unittest.skipUnless(is_root(), 'Test requires root') - def test_num_slots(self): - l = Libevdev(self.fd) - self.assertGreater(l.num_slots, 0) - - @unittest.skipUnless(is_root(), 'Test requires root') - def test_current_slot(self): - l = Libevdev(self.fd) - self.assertGreaterEqual(l.current_slot, 0) - - @unittest.skipUnless(is_root(), 'Test requires root') - def test_slot_value(self): - l = Libevdev(self.fd) - a = l.absinfo("ABS_MT_POSITION_X") - v = l.slot_value(l.current_slot, "ABS_MT_POSITION_X") - self.assertLessEqual(a["minimum"], v) - self.assertGreaterEqual(a["maximum"], v) - - @unittest.skipUnless(is_root(), 'Test requires root') - def test_set_slot_value(self): - l = Libevdev(self.fd) - v = l.slot_value(l.current_slot, "ABS_MT_POSITION_X") - v += 10 - v2 = l.slot_value(l.current_slot, "ABS_MT_POSITION_X", v) - self.assertEqual(v, v2) - v2 = l.slot_value(l.current_slot, "ABS_MT_POSITION_X") - self.assertEqual(v, v2) - - -class TestUinput(unittest.TestCase): - """ - Tests uinput device creation. - Requires root rights. - """ - - def is_identical(self, d1, d2): - for t in range(Libevdev.event_to_value("EV_MAX")): - max = Libevdev.type_max(t) - if max is None: - continue - for c in range(max): - if d1.has_event(t, c) != d1.has_event(t, c): - return False - return True - - @unittest.skipUnless(is_root(), 'Test requires root') - def testRelative(self): - dev = Libevdev() - dev.name = "test device" - dev.enable("EV_REL", "REL_X") - dev.enable("EV_REL", "REL_Y") - uinput = UinputDevice(dev) - self.assertIsNotNone(uinput.devnode) - - with open(uinput.devnode) as f: - newdev = Libevdev(f) - self.assertTrue(self.is_identical(dev, newdev)) - - @unittest.skipUnless(is_root(), 'Test requires root') - def testButton(self): - dev = Libevdev() - dev.name = "test device" - dev.enable("EV_KEY", "BTN_LEFT") - dev.enable("EV_KEY", "KEY_A") - uinput = UinputDevice(dev) - self.assertIsNotNone(uinput.devnode) - - with open(uinput.devnode) as f: - newdev = Libevdev(f) - self.assertTrue(self.is_identical(dev, newdev)) - - @unittest.skipUnless(is_root(), 'Test requires root') - def testAbsolute(self): - absinfo = {"minimum": 0, - "maximum": 1} - - dev = Libevdev() - dev.name = "test device" - dev.enable("EV_ABS", "ABS_X", absinfo) - dev.enable("EV_ABS", "ABS_Y", absinfo) - uinput = UinputDevice(dev) - self.assertIsNotNone(uinput.devnode) - - with open(uinput.devnode) as f: - newdev = Libevdev(f) - self.assertTrue(self.is_identical(dev, newdev)) - - @unittest.skipUnless(is_root(), 'Test requires root') - def testDeviceNode(self): - dev = Libevdev() - dev.name = "test device" - dev.enable("EV_REL", "REL_X") - dev.enable("EV_REL", "REL_Y") - uinput = UinputDevice(dev) - self.assertTrue(uinput.devnode.startswith("/dev/input/event")) - - @unittest.skipUnless(is_root(), 'Test requires root') - def testSyspath(self): - dev = Libevdev() - dev.name = "test device" - dev.enable("EV_REL", "REL_X") - dev.enable("EV_REL", "REL_Y") - uinput = UinputDevice(dev) - self.assertTrue(uinput.syspath.startswith("/sys/devices/virtual/input/input")) - - -if __name__ == '__main__': - unittest.main() diff --git a/test/test-const.py b/test/test-const.py deleted file mode 100644 index 1e54e7c..0000000 --- a/test/test-const.py +++ /dev/null @@ -1,96 +0,0 @@ -# -*- coding: latin-1 -*- -# Copyright © 2017 Red Hat, Inc. -# -# Permission is hereby granted, free of charge, to any person obtaining a -# copy of this software and associated documentation files (the "Software"), -# to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, -# and/or sell copies of the Software, and to permit persons to whom the -# Software is furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice (including the next -# paragraph) shall be included in all copies or substantial portions of the -# Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -# DEALINGS IN THE SOFTWARE. - -import unittest - -import libevdev -from libevdev import evbit, propbit - -class TestEventBits(unittest.TestCase): - def test_ev_types(self): - self.assertIn(libevdev.EV_SYN, libevdev.types) - self.assertIn(libevdev.EV_REL, libevdev.types) - self.assertIn(libevdev.EV_ABS, libevdev.types) - self.assertEqual(len(libevdev.types), 13) - - def test_EV_REL(self): - self.assertIn(libevdev.EV_REL.REL_X, libevdev.EV_REL.codes) - self.assertIn(libevdev.EV_REL.REL_Y, libevdev.EV_REL.codes) - self.assertNotIn(libevdev.EV_ABS.ABS_X, libevdev.EV_REL.codes) - - def test_type_max(self): - self.assertEqual(libevdev.EV_REL.max, libevdev.EV_REL.REL_MAX.value) - self.assertEqual(libevdev.EV_ABS.max, libevdev.EV_ABS.ABS_MAX.value) - self.assertEqual(libevdev.EV_KEY.max, libevdev.EV_KEY.KEY_MAX.value) - - self.assertEqual(libevdev.EV_REL.max, 0x0f) - self.assertEqual(libevdev.EV_ABS.max, 0x3f) - self.assertEqual(libevdev.EV_KEY.max, 0x2ff) - - def test_evcode_compare(self): - self.assertNotEqual(libevdev.EV_REL.REL_X, libevdev.EV_REL.REL_Y) - self.assertNotEqual(libevdev.EV_REL.REL_X, libevdev.EV_ABS.ABS_X) - self.assertNotEqual(libevdev.EV_REL, libevdev.EV_ABS) - - self.assertNotEqual(libevdev.EV_REL.REL_X, libevdev.EV_REL) - self.assertNotEqual(libevdev.EV_ABS.ABS_X, libevdev.EV_ABS) - - def test_evbit(self): - self.assertEqual(evbit(0, 0), libevdev.EV_SYN.SYN_REPORT) - self.assertEqual(evbit(1, 30), libevdev.EV_KEY.KEY_A) - self.assertEqual(evbit(2, 1), libevdev.EV_REL.REL_Y) - - self.assertEqual(evbit(0), libevdev.EV_SYN) - self.assertEqual(evbit(1), libevdev.EV_KEY) - self.assertEqual(evbit(2), libevdev.EV_REL) - - for t in libevdev.types: - self.assertEqual(evbit(t.value), t) - - def test_propbit(self): - self.assertEqual(propbit(0), libevdev.INPUT_PROP_POINTER) - self.assertEqual(propbit(1), libevdev.INPUT_PROP_DIRECT) - - for p in libevdev.props: - self.assertEqual(propbit(p.value), p) - - def test_evbit_string(self): - self.assertEqual(evbit('EV_SYN'), libevdev.EV_SYN) - self.assertEqual(evbit('EV_KEY'), libevdev.EV_KEY) - self.assertEqual(evbit('EV_REL'), libevdev.EV_REL) - self.assertEqual(evbit('EV_REP'), libevdev.EV_REP) - - for t in libevdev.types: - self.assertEqual(evbit(t.name), t) - - def test_evcode_string(self): - self.assertEqual(evbit('ABS_X'), libevdev.EV_ABS.ABS_X) - self.assertEqual(evbit('REL_X'), libevdev.EV_REL.REL_X) - self.assertEqual(evbit('SYN_REPORT'), libevdev.EV_SYN.SYN_REPORT) - self.assertEqual(evbit('REP_PERIOD'), libevdev.EV_REP.REP_PERIOD) - - def test_propbit_string(self): - self.assertEqual(propbit('INPUT_PROP_POINTER'), libevdev.INPUT_PROP_POINTER) - self.assertEqual(propbit('INPUT_PROP_DIRECT'), libevdev.INPUT_PROP_DIRECT) - - for p in libevdev.props: - self.assertEqual(propbit(p.value), p) diff --git a/test/test-device.py b/test/test-device.py deleted file mode 100644 index 0d61432..0000000 --- a/test/test-device.py +++ /dev/null @@ -1,452 +0,0 @@ -# -*- coding: latin-1 -*- -# Copyright © 2017 Red Hat, Inc. -# -# Permission is hereby granted, free of charge, to any person obtaining a -# copy of this software and associated documentation files (the "Software"), -# to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, -# and/or sell copies of the Software, and to permit persons to whom the -# Software is furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice (including the next -# paragraph) shall be included in all copies or substantial portions of the -# Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -# DEALINGS IN THE SOFTWARE. - -import os -import unittest - -import libevdev -from libevdev import evbit, propbit, InputEvent, Device, InvalidFileError, InvalidArgumentException - -def is_root(): - return os.getuid() == 0 - -class TestDevice(unittest.TestCase): - def test_device_empty(self): - d = libevdev.Device() - id = {'bustype': 0, 'vendor': 0, 'product': 0, 'version': 0} - syns = { libevdev.EV_SYN : libevdev.EV_SYN.codes } - - self.assertEqual(d.name, '') - self.assertEqual(d.id, id) - self.assertIsNone(d.fd) - self.assertIsNone(d.phys) - self.assertIsNone(d.uniq) - self.assertEqual(d.driver_version, 0) - self.assertIsNone(d.syspath) - self.assertIsNone(d.devnode) - self.assertEqual(d.evbits, syns) - self.assertEqual(d.properties, []) - - for t in libevdev.types: - if t == libevdev.EV_SYN: - continue - - self.assertFalse(d.has(t)) - - for c in t.codes: - self.assertFalse(d.has(c)) - self.assertIsNone(d.value[c]) - - d.disable(c) # noop - - d.disable(t) # noop - - for p in libevdev.props: - self.assertFalse(d.has_property(p)) - - self.assertIsNone(d.num_slots) - self.assertIsNone(d.current_slot) - - for c in libevdev.EV_ABS.codes: - self.assertIsNone(d.absinfo[c]) - - self.assertEqual([e for e in d.events()], []) - self.assertEqual([e for e in d.sync()], []) - - with self.assertRaises(libevdev.InvalidArgumentException): - d.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] - - def test_device_name(self): - d = libevdev.Device() - d.name = 'test device' - self.assertEqual(d.name, 'test device') - - def test_device_id(self): - d = libevdev.Device() - id = {'bustype': 1, 'vendor': 2, 'product': 3, 'version': 4} - d.id = id - self.assertEqual(d.id, id) - - d.id = {'vendor': 3, 'product': 4, 'version': 5} - id = {'bustype': 1, 'vendor': 3, 'product': 4, 'version': 5} - self.assertEqual(d.id, id) - - d.id = {'bustype': 8, 'product': 5, 'version': 6} - id = {'bustype': 8, 'vendor': 3, 'product': 5, 'version': 6} - self.assertEqual(d.id, id) - - d.id = {'bustype': 8, 'vendor': 9, 'version': 10} - id = {'bustype': 8, 'vendor': 9, 'product': 5, 'version': 10} - self.assertEqual(d.id, id) - - d.id = {'bustype': 8, 'vendor': 9, 'product': 12} - id = {'bustype': 8, 'vendor': 9, 'product': 12, 'version': 10} - self.assertEqual(d.id, id) - - def test_device_phys(self): - d = libevdev.Device() - d.phys = 'foo' - self.assertEqual(d.phys, 'foo') - - d.phys = None - self.assertIsNone(d.phys) - - def test_device_uniq(self): - d = libevdev.Device() - d.uniq = 'bar' - self.assertEqual(d.uniq, 'bar') - - d.uniq = None - self.assertIsNone(d.uniq) - - def test_driver_version(self): - d = libevdev.Device() - # read-only - with self.assertRaises(AttributeError): - d.driver_version = 1 - - def test_garbage_fd(self): - with self.assertRaises(InvalidFileError): - libevdev.Device(fd=1) - - with self.assertRaises(InvalidFileError): - d = libevdev.Device() - d.fd = 2 - - @unittest.skipUnless(is_root(), 'Test requires root') - def test_fd_on_init(self): - fd = open('/dev/input/event0', 'rb') - d = libevdev.Device(fd) - self.assertEqual(d.fd, fd) - - @unittest.skipUnless(is_root(), 'Test requires root') - def test_fd_too_late(self): - fd = open('/dev/input/event0', 'rb') - d = libevdev.Device() - with self.assertRaises(InvalidFileError): - d.fd = fd - - @unittest.skipUnless(is_root(), 'Test requires root') - def test_fd_change(self): - fd1 = open('/dev/input/event0', 'rb') - fd2 = open('/dev/input/event1', 'rb') - d = libevdev.Device(fd1) - self.assertEqual(d.fd, fd1) - d.fd = fd2 - self.assertEqual(d.fd, fd2) - - @unittest.skipUnless(is_root(), 'Test requires root') - def test_has_bits(self): - fd = open('/dev/input/event0', 'rb') - d = libevdev.Device(fd) - bits = d.evbits - - # assume at least 2 event types - self.assertGreater(len(bits.keys()), 1) - - for t, cs in bits.items(): - if t == libevdev.EV_SYN: - continue - - # assume at least one code - self.assertGreater(len(cs), 0) - - def test_set_bits(self): - d = libevdev.Device() - # read-only - with self.assertRaises(AttributeError): - d.evbits = {} - - def test_bits_change_after_enable(self): - d = libevdev.Device() - bits = d.evbits - self.assertIn(libevdev.EV_SYN, bits) - self.assertNotIn(libevdev.EV_REL, bits) - - d.enable(libevdev.EV_REL.REL_X) - d.enable(libevdev.EV_REL.REL_Y) - - bits = d.evbits - self.assertIn(libevdev.EV_SYN, bits) - self.assertIn(libevdev.EV_REL, bits) - self.assertNotIn(libevdev.EV_ABS, bits) - self.assertNotIn(libevdev.EV_KEY, bits) - - self.assertIn(libevdev.EV_REL.REL_X, bits[libevdev.EV_REL]) - self.assertIn(libevdev.EV_REL.REL_Y, bits[libevdev.EV_REL]) - - def test_bits_change_after_disable(self): - d = libevdev.Device() - d.enable(libevdev.EV_REL.REL_X) - d.enable(libevdev.EV_REL.REL_Y) - d.enable(libevdev.EV_KEY.KEY_A) - d.enable(libevdev.EV_KEY.KEY_B) - - bits = d.evbits - self.assertIn(libevdev.EV_SYN, bits) - self.assertIn(libevdev.EV_REL, bits) - self.assertIn(libevdev.EV_KEY, bits) - self.assertNotIn(libevdev.EV_ABS, bits) - self.assertIn(libevdev.EV_REL.REL_X, bits[libevdev.EV_REL]) - self.assertIn(libevdev.EV_REL.REL_Y, bits[libevdev.EV_REL]) - self.assertIn(libevdev.EV_KEY.KEY_A, bits[libevdev.EV_KEY]) - self.assertIn(libevdev.EV_KEY.KEY_B, bits[libevdev.EV_KEY]) - - d.disable(libevdev.EV_REL.REL_Y) - d.disable(libevdev.EV_KEY) - bits = d.evbits - self.assertNotIn(libevdev.EV_KEY, bits) - self.assertIn(libevdev.EV_REL, bits) - self.assertIn(libevdev.EV_REL.REL_X, bits[libevdev.EV_REL]) - self.assertNotIn(libevdev.EV_REL.REL_Y, bits[libevdev.EV_REL]) - - def test_properties(self): - d = libevdev.Device() - self.assertEqual(d.properties, []) - for p in libevdev.props: - self.assertFalse(d.has_property(p)) - - props = sorted([libevdev.INPUT_PROP_BUTTONPAD, libevdev.INPUT_PROP_DIRECT]) - - for p in props: - d.enable(p) - - self.assertEqual(d.properties, props) - for p in libevdev.props: - if not p in props: - self.assertFalse(d.has_property(p)) - else: - self.assertTrue(d.has_property(p)) - - with self.assertRaises(NotImplementedError): - d.disable(libevdev.INPUT_PROP_BUTTONPAD) - - def test_has(self): - d = libevdev.Device() - - d.enable(libevdev.EV_REL.REL_X) - d.enable(libevdev.EV_REL.REL_Y) - d.enable(libevdev.EV_KEY.KEY_A) - d.enable(libevdev.EV_KEY.KEY_B) - - self.assertTrue(d.has(libevdev.EV_REL)) - self.assertTrue(d.has(libevdev.EV_REL.REL_X)) - self.assertTrue(d.has(libevdev.EV_REL.REL_Y)) - self.assertFalse(d.has(libevdev.EV_REL.REL_Z)) - - self.assertTrue(d.has(libevdev.EV_KEY)) - self.assertTrue(d.has(libevdev.EV_KEY.KEY_A)) - self.assertTrue(d.has(libevdev.EV_KEY.KEY_B)) - self.assertFalse(d.has(libevdev.EV_KEY.KEY_C)) - - self.assertFalse(d.has(libevdev.EV_ABS)) - - def test_enable_abs(self): - d = libevdev.Device() - with self.assertRaises(InvalidArgumentException): - d.enable(libevdev.EV_ABS.ABS_X) - - def test_value(self): - d = libevdev.Device() - d.enable(libevdev.EV_REL.REL_X) - self.assertEqual(d.value[libevdev.EV_REL.REL_X], 0) - self.assertIsNone(d.value[libevdev.EV_REL.REL_Y]) - - @unittest.skipUnless(is_root(), 'Test requires root') - def test_mt_value(self): - # Unable to set ABS_MT_SLOT on a libevdev device, see - # https://bugs.freedesktop.org/show_bug.cgi?id=104270 - d = libevdev.Device() - a = libevdev.InputAbsInfo(minimum=0, maximum=100) - d.name = 'test device' - d.enable(libevdev.EV_ABS.ABS_X, a) - d.enable(libevdev.EV_ABS.ABS_Y, a) - d.enable(libevdev.EV_ABS.ABS_MT_SLOT, a) - d.enable(libevdev.EV_ABS.ABS_MT_POSITION_X, a) - d.enable(libevdev.EV_ABS.ABS_MT_POSITION_Y, a) - d.enable(libevdev.EV_ABS.ABS_MT_TRACKING_ID, a) - - uinput = d.create_uinput_device() - - fd = open(uinput.devnode, 'rb') - d = libevdev.Device(fd) - - self.assertEqual(d.num_slots, 101) - self.assertEqual(d.value[libevdev.EV_ABS.ABS_X], 0) - self.assertEqual(d.value[libevdev.EV_ABS.ABS_Y], 0) - - with self.assertRaises(libevdev.InvalidArgumentException): - d.value[libevdev.EV_ABS.ABS_MT_POSITION_X] - with self.assertRaises(libevdev.InvalidArgumentException): - d.value[libevdev.EV_ABS.ABS_MT_POSITION_Y] - with self.assertRaises(libevdev.InvalidArgumentException): - d.value[libevdev.EV_ABS.ABS_MT_SLOT] - with self.assertRaises(libevdev.InvalidArgumentException): - d.value[libevdev.EV_ABS.ABS_MT_TRACKING_ID] - # also raises for non-existing axes - with self.assertRaises(libevdev.InvalidArgumentException): - d.value[libevdev.EV_ABS.ABS_MT_ORIENTATION] - - @unittest.skipUnless(is_root(), 'Test requires root') - def test_slot_value(self): - # Unable to set ABS_MT_SLOT on a libevdev device, see - # https://bugs.freedesktop.org/show_bug.cgi?id=104270 - d = libevdev.Device() - a = libevdev.InputAbsInfo(minimum=0, maximum=100) - d.name = 'test device' - d.enable(libevdev.EV_ABS.ABS_X, a) - d.enable(libevdev.EV_ABS.ABS_Y, a) - d.enable(libevdev.EV_ABS.ABS_MT_SLOT, a) - d.enable(libevdev.EV_ABS.ABS_MT_POSITION_X, a) - d.enable(libevdev.EV_ABS.ABS_MT_POSITION_Y, a) - d.enable(libevdev.EV_ABS.ABS_MT_TRACKING_ID, a) - - uinput = d.create_uinput_device() - events = [libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_SLOT, 0), - libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_TRACKING_ID, 1), - libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_X, 100), - libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_Y, 110), - libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_SLOT, 1), - libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_TRACKING_ID, 2), - libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_X, 200), - libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_Y, 210), - libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT, 0)] - uinput.send_events(events) - - fd = open(uinput.devnode, 'rb') - d = libevdev.Device(fd) - - self.assertEqual(d.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X], 100) - self.assertEqual(d.slots[1][libevdev.EV_ABS.ABS_MT_POSITION_X], 200) - self.assertEqual(d.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y], 110) - self.assertEqual(d.slots[1][libevdev.EV_ABS.ABS_MT_POSITION_Y], 210) - - self.assertIsNone(d.slots[0][libevdev.EV_ABS.ABS_MT_ORIENTATION]) - - for idx, s in enumerate(d.slots[:2]): - idx += 1 - self.assertEqual(s[libevdev.EV_ABS.ABS_MT_POSITION_X], idx * 100) - self.assertEqual(s[libevdev.EV_ABS.ABS_MT_POSITION_Y], idx * 100 + 10) - - for s in d.slots[2:]: - self.assertEqual(s[libevdev.EV_ABS.ABS_MT_POSITION_X], 0) - self.assertEqual(s[libevdev.EV_ABS.ABS_MT_POSITION_Y], 0) - - with self.assertRaises(IndexError): - d.slots[200] - - with self.assertRaises(libevdev.InvalidArgumentException): - d.slots[0][libevdev.EV_ABS.ABS_X] - with self.assertRaises(libevdev.InvalidArgumentException): - d.slots[0][libevdev.EV_ABS.ABS_MT_SLOT] - with self.assertRaises(libevdev.InvalidArgumentException): - d.slots[0][libevdev.EV_REL.REL_X] - with self.assertRaises(AttributeError): - d.slots[0][libevdev.EV_ABS] - - d.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] = 10 - self.assertEqual(d.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X], 10) - d.slots[1][libevdev.EV_ABS.ABS_MT_POSITION_Y] = 50 - self.assertEqual(d.slots[1][libevdev.EV_ABS.ABS_MT_POSITION_Y], 50) - - with self.assertRaises(libevdev.InvalidArgumentException): - d.slots[0][libevdev.EV_ABS.ABS_MT_ORIENTATION] = 50 - with self.assertRaises(libevdev.InvalidArgumentException): - d.slots[0][libevdev.EV_ABS.ABS_X] = 10 - with self.assertRaises(libevdev.InvalidArgumentException): - d.slots[0][libevdev.EV_ABS.ABS_MT_SLOT] = 10 - with self.assertRaises(libevdev.InvalidArgumentException): - d.slots[0][libevdev.EV_REL.REL_X] = 10 - with self.assertRaises(AttributeError): - d.slots[0][libevdev.EV_ABS] = 10 - - def test_absinfo(self): - d = libevdev.Device() - a = libevdev.InputAbsInfo(minimum=100, maximum=1000, resolution=50) - d.enable(libevdev.EV_ABS.ABS_X, a) - # we expect these to be filled in - a.fuzz = 0 - a.flat = 0 - a.value = 0 - - a2 = d.absinfo[libevdev.EV_ABS.ABS_X] - self.assertEqual(a, a2) - - self.assertIsNone(d.absinfo[libevdev.EV_ABS.ABS_Y]) - a.fuzz = 10 - d.absinfo[libevdev.EV_ABS.ABS_X] = a - a2 = d.absinfo[libevdev.EV_ABS.ABS_X] - self.assertEqual(a, a2) - - a = libevdev.InputAbsInfo(minimum=500) - d.absinfo[libevdev.EV_ABS.ABS_X] = a - a2 = d.absinfo[libevdev.EV_ABS.ABS_X] - self.assertEqual(a.minimum, a2.minimum) - self.assertIsNotNone(a2.minimum) - self.assertIsNotNone(a2.maximum) - self.assertIsNotNone(a2.fuzz) - self.assertIsNotNone(a2.flat) - self.assertIsNotNone(a2.resolution) - self.assertIsNotNone(a2.value) - - def test_absinfo_set_invalid(self): - a = libevdev.InputAbsInfo(minimum=500) - d = libevdev.Device() - with self.assertRaises(InvalidArgumentException): - d.absinfo[libevdev.EV_ABS.ABS_Y] = a - with self.assertRaises(AssertionError): - d.absinfo[libevdev.EV_REL.REL_X] - with self.assertRaises(AssertionError): - d.absinfo[libevdev.EV_REL.REL_X] = a - - @unittest.skipUnless(is_root(), 'Test requires root') - def test_absinfo_sync_kernel(self): - d = libevdev.Device() - d.name = 'sync test device' - a = libevdev.InputAbsInfo(minimum=0, maximum=1000, resolution=50, - fuzz=0, flat=0, value=0) - d.enable(libevdev.EV_ABS.ABS_X, a) - d.enable(libevdev.EV_ABS.ABS_Y, a) - uinput = d.create_uinput_device() - - fd = open(uinput.devnode, 'rb') - d = libevdev.Device(fd) - a2 = d.absinfo[libevdev.EV_ABS.ABS_X] - self.assertEquals(a, a2) - a2.resolution = 100 - d.absinfo[libevdev.EV_ABS.ABS_X] = a2 - d.sync_absinfo_to_kernel(libevdev.EV_ABS.ABS_X) - fd.close() - - fd = open(uinput.devnode, 'rb') - d = libevdev.Device(fd) - a3 = d.absinfo[libevdev.EV_ABS.ABS_X] - print(a3) - self.assertEquals(a2, a3) - a3 = d.absinfo[libevdev.EV_ABS.ABS_Y] - self.assertEquals(a, a3) - - @unittest.skipUnless(is_root(), 'Test requires root') - def test_uinput_empty(self): - d = libevdev.Device() - with self.assertRaises(OSError): - d.create_uinput_device() diff --git a/test/test_clib.py b/test/test_clib.py new file mode 100644 index 0000000..765feda --- /dev/null +++ b/test/test_clib.py @@ -0,0 +1,616 @@ +import os +import ctypes +import pytest +from libevdev._clib import Libevdev, UinputDevice +from pathlib import Path +from typing import BinaryIO, Iterable + +# Note: these tests test for the correct functioning of the python bindings, +# not of libevdev underneath. Some ranges are hardcoded for simplicity, e.g. +# if properties 1-4 work the others will work too if libevdev works +# correctly + + +def is_root(): + return os.getuid() == 0 + + +def has_event_devices(): + return list(Path("/dev/input/").glob("event*")) + + +@pytest.fixture +def local_device() -> Iterable[BinaryIO]: + try: + with open("/dev/input/event3", "rb") as fd: + yield fd + except FileNotFoundError: + pytest.skip("/dev/input/event3 not available") + + +@pytest.fixture +def local_abs_device() -> Iterable[BinaryIO]: + for device in Path("/dev/input/").glob("event*"): + with open(device, "rb") as fd: + dev = Libevdev(fd) + if dev.has_event("EV_ABS", "ABS_Y"): + yield fd + break + else: + pytest.skip("No abs device available") + + +@pytest.fixture +def local_mt_device() -> Iterable[BinaryIO]: + for device in Path("/dev/input/").glob("event*"): + with open(device, "rb") as fd: + dev = Libevdev(fd) + if dev.num_slots is not None: + yield fd + break + else: + pytest.skip("No MT device available") + + +class TestNameConversion: + def test_type_max(self): + for t in ["REL", "ABS"]: + c = Libevdev.event_to_value("EV_{}".format(t), "{}_MAX".format(t)) + max = Libevdev.type_max("EV_{}".format(t)) + assert c == max + + def test_prop_name(self): + name = Libevdev.property_to_name(0) + assert name == "INPUT_PROP_POINTER" + + prevname = None + for i in range(5): + name = Libevdev.property_to_name(i) + assert name is not None + assert name.startswith("INPUT_PROP_") + assert prevname != name + prevname = name + + def test_prop_to_name_invalid(self): + name = Libevdev.property_to_name(-1) + assert name is None + name = Libevdev.property_to_name(100) + assert name is None + with pytest.raises(ctypes.ArgumentError): + name = Libevdev.property_to_name("foo") + + def test_prop_to_value(self): + value = Libevdev.property_to_value("INPUT_PROP_POINTER") + assert value == 0 + + value = Libevdev.property_to_value("INPUT_PROP_DIRECT") + assert value == 1 + + def test_prop_to_value_invalid(self): + name = Libevdev.property_to_value("foo") + assert name is None + + def test_type_to_name(self): + name = Libevdev.event_to_name(1) + assert name == "EV_KEY" + + prevname = None + for i in range(5): + name = Libevdev.event_to_name(i) + assert name is not None + assert name.startswith("EV_") + assert prevname != name + prevname = name + + def test_type_to_name_invalid(self): + name = Libevdev.event_to_name(-1) + assert name is None + name = Libevdev.event_to_name(100) + assert name is None + with pytest.raises(ctypes.ArgumentError): + name = Libevdev.event_to_name("foo") + + def test_code_to_name(self): + name = Libevdev.event_to_name(0, 0) + assert name == "SYN_REPORT" + + name = Libevdev.event_to_name(1, 1) + assert name == "KEY_ESC" + + def test_code_to_name_invalid(self): + name = Libevdev.event_to_name(0, 1000) + assert name is None + name = Libevdev.event_to_name(0, -1) + assert name is None + with pytest.raises(ctypes.ArgumentError): + name = Libevdev.event_to_name(0, "foo") + + def test_value_to_name(self): + name = Libevdev.event_to_name(3, 0x37, 0) + assert name == "MT_TOOL_FINGER" + + name = Libevdev.event_to_name(3, 0x37, 1) + assert name == "MT_TOOL_PEN" + + def test_value_to_name_invalid(self): + name = Libevdev.event_to_name(3, 0x37, 1000) + assert name is None + name = Libevdev.event_to_name(3, 0x37, -1) + assert name is None + with pytest.raises(ctypes.ArgumentError): + name = Libevdev.event_to_name(0, "foo") + + def test_event_type_to_value(self): + v = Libevdev.event_to_value("EV_REL") + assert v == 2 + + def test_event_type_to_value_invalid(self): + v = Libevdev.event_to_value("foo") + assert v is None + with pytest.raises(AttributeError): + v = Libevdev.event_to_value(0) + + def test_event_code_to_value(self): + v = Libevdev.event_to_value("EV_REL", "REL_Y") + assert v == 1 + + v = Libevdev.event_to_value(0, "SYN_DROPPED") + assert v == 3 + + def test_event_code_to_value_invalid(self): + v = Libevdev.event_to_value("EV_REL", "KEY_ESC") + assert v is None + + def test_event_value_to_value(self): + v = Libevdev.event_to_value("EV_ABS", "ABS_MT_TOOL_TYPE", "MT_TOOL_FINGER") + assert v == 0 + + v = Libevdev.event_to_value("EV_ABS", "ABS_MT_TOOL_TYPE", "MT_TOOL_PEN") + assert v == 1 + + def test_event_value_to_value_invalid(self): + v = Libevdev.event_to_value("EV_ABS", "ABS_X", "foo") + assert v is None + + +class TestLibevdevDevice: + def test_ctx_init(self): + dev = Libevdev() + del dev + + def test_set_get_name(self): + dev = Libevdev() + name = dev.name + assert name == "" + + dev.name = "foo" + name = dev.name + assert name == "foo" + + dev.name = None + name = dev.name + assert name == "" + + def test_set_get_uniq(self): + dev = Libevdev() + uniq = dev.uniq + assert uniq is None + + dev.uniq = "foo" + uniq = dev.uniq + assert uniq == "foo" + + # libevdev issue: phys may be NULL (unlike the name) but we can't + # set it to NULL. But the conversion code returns None for the empty + # string, so let's test for that + dev.uniq = None + uniq = dev.uniq + assert uniq is None + + def test_set_get_phys(self): + dev = Libevdev() + phys = dev.phys + assert phys is None + + dev.phys = "foo" + phys = dev.phys + assert phys == "foo" + + # libevdev issue: phys may be NULL (unlike the name) but we can't + # set it to NULL. But the conversion code returns None for the empty + # string, so let's test for that + dev.phys = None + phys = dev.phys + assert phys is None + + def test_get_driver_version(self): + dev = Libevdev() + v = dev.driver_version + assert v == 0 + # we can't set this, nothing else we can test + + def test_set_get_id(self): + dev = Libevdev() + id = dev.id + assert id["bustype"] == 0 + assert id["vendor"] == 0 + assert id["product"] == 0 + assert id["version"] == 0 + + id["bustype"] = 1 + id["vendor"] = 2 + id["product"] = 3 + id["version"] = 4 + + dev.id = id + id = dev.id + assert id["bustype"] == 1 + assert id["vendor"] == 2 + assert id["product"] == 3 + assert id["version"] == 4 + + del id["bustype"] + del id["vendor"] + del id["product"] + id["version"] = 5 + + dev.id = id + id = dev.id + assert id["bustype"] == 1 + assert id["vendor"] == 2 + assert id["product"] == 3 + assert id["version"] == 5 + + +@pytest.mark.skipif(not is_root(), reason="Test requires root") +class TestRealDevice: + """ + Tests various things against /dev/input/event3 which is usually the + keyboard. Requires root rights though. + """ + + def test_set_fd(self, local_device): + dev = Libevdev() + dev.fd = local_device + fd = dev.fd + assert local_device == fd + + fd2 = open("/dev/input/event3", "rb") + dev.fd = fd2 + fd = dev.fd + assert fd == fd2 + fd2.close() + + def test_init_fd(self, local_device): + dev = Libevdev(local_device) + fd = dev.fd + assert local_device == fd + + fd2 = open("/dev/input/event3", "rb") + dev.fd = fd2 + fd = dev.fd + assert fd == fd2 + fd2.close() + + def test_ids(self, local_device): + dev = Libevdev(local_device) + id = dev.id + assert id["bustype"] != 0 + assert id["vendor"] != 0 + assert id["product"] != 0 + assert id["version"] != 0 + + def test_name(self, local_device): + dev = Libevdev(local_device) + name = dev.name + assert name != "" + + def test_driver_version(self, local_device): + dev = Libevdev(local_device) + v = dev.driver_version + assert v == 0x010001 + + def test_set_clock_id(self, local_device): + dev = Libevdev(local_device) + try: + import time + + clock = time.CLOCK_MONOTONIC + except AttributeError: + clock = 1 + rc = dev.set_clock_id(clock) + assert rc == 0 + + def test_grab(self, local_device): + dev = Libevdev(local_device) + # no exception == success + dev.grab() + dev.grab(False) + dev.grab(True) + + def test_has_event(self, local_device): + dev = Libevdev(local_device) + assert dev.has_event("EV_SYN", "SYN_REPORT") + + type_supported = -1 + max_code = -1 + for t in range(1, 5): + if dev.has_event(t): + type_supported = t + max_code = Libevdev.type_max(t) + if max is None: + continue + break + + assert type_supported > 0 + + codes_supported = 0 + for c in range(max_code + 1): + if dev.has_event(type_supported, c): + codes_supported += 1 + + assert codes_supported > 0 + + @pytest.mark.skipif(not not os.getenv("CI"), reason="Skip in CI") + def test_has_property(self): + """ + Let's assume at least one event device with at least one property set. + May cause false negatives. + """ + + props_supported = 0 + for device in Path("/dev/input/").glob("event*"): + with open(device, "rb") as fd: + dev = Libevdev(fd) + for p in range(6): + if dev.has_property(p): + props_supported += 1 + assert props_supported > 0 + + def test_num_slots(self, local_device): + """ + Let's assume that our device doesn't have slots + """ + dev = Libevdev(local_device) + assert dev.num_slots is None + + +class TestAbsDevice: + """ + Tests various things against the first device with EV_ABS. + We're relying on that this device has ABS_Y, this tests against a code + that's nonzero and is the most common ABS anyway. + Requires root rights. + """ + + @pytest.mark.skipif(not is_root(), reason="Test requires root") + def test_absinfo(self, local_abs_device): + dev = Libevdev(local_abs_device) + a = dev.absinfo("ABS_Y") + assert a is not None + assert "minimum" in a + assert "maximum" in a + assert "resolution" in a + assert "fuzz" in a + assert "flat" in a + assert "value" in a + + @pytest.mark.skipif(not is_root(), reason="Test requires root") + def test_set_absinfo(self, local_abs_device): + dev = Libevdev(local_abs_device) + real_a = dev.absinfo("ABS_Y") + assert real_a is not None + a = dev.absinfo("ABS_Y") + assert a is not None + a["minimum"] = 100 + a["maximum"] = 200 + a["fuzz"] = 300 + a["flat"] = 400 + a["resolution"] = 500 + a["value"] = 600 + + a = dev.absinfo("ABS_Y", new_values=a) + assert a is not None + assert a["minimum"] == 100 + assert a["maximum"] == 200 + assert a["fuzz"] == 300 + assert a["flat"] == 400 + assert a["resolution"] == 500 + assert a["value"] == 600 + + l2 = Libevdev(local_abs_device) + a2 = l2.absinfo("ABS_Y") + assert a2 is not None + assert a["minimum"] != real_a["minimum"] + assert a["maximum"] != real_a["maximum"] + assert a["fuzz"] != real_a["fuzz"] + assert a["flat"] != real_a["flat"] + assert a["resolution"] != real_a["resolution"] + assert a2["value"] == real_a["value"] + assert a2["minimum"] == real_a["minimum"] + assert a2["maximum"] == real_a["maximum"] + assert a2["fuzz"] == real_a["fuzz"] + assert a2["flat"] == real_a["flat"] + assert a2["resolution"] == real_a["resolution"] + assert a2["value"] == real_a["value"] + + @pytest.mark.skipif(not is_root(), reason="Test requires root") + def test_set_absinfo_invalid(self, local_abs_device): + dev = Libevdev(local_abs_device) + with pytest.raises(ValueError): + dev.absinfo("REL_X") + + @pytest.mark.skipif(not is_root(), reason="Test requires root") + def test_set_absinfo_kernel(self, local_abs_device): + # FIXME: yeah, nah, not testing that on a random device... + pass + + @pytest.mark.skipif(not is_root(), reason="Test requires root") + def test_get_set_event_value(self, local_abs_device): + dev = Libevdev(local_abs_device) + v = dev.event_value("EV_ABS", "ABS_Y") + assert v is not None + v = dev.event_value(0x03, 0x01, new_value=300) + assert v == 300 + v = dev.event_value(0x03, 0x01) + assert v == 300 + + @pytest.mark.skipif(not is_root(), reason="Test requires root") + def test_get_set_event_value_invalid(self, local_abs_device): + dev = Libevdev(local_abs_device) + v = dev.event_value("EV_ABS", 0x600) + assert v is None + v = dev.event_value(0x03, 0x600, new_value=300) + assert v is None + + @pytest.mark.skipif(not is_root(), reason="Test requires root") + def test_enable_event_code(self, local_abs_device): + dev = Libevdev(local_abs_device) + + assert not dev.has_event("EV_REL", "REL_RY") + dev.enable("EV_REL", "REL_RY") + assert dev.has_event("EV_REL", "REL_RY") + dev.disable("EV_REL", "REL_RY") + assert not dev.has_event("EV_REL", "REL_RY") + + data = { + "minimum": 100, + "maximum": 200, + "value": 300, + "fuzz": 400, + "flat": 500, + "resolution": 600, + } + assert not dev.has_event("EV_ABS", "ABS_RY") + dev.enable("EV_ABS", "ABS_RY", data) + assert dev.has_event("EV_ABS", "ABS_RY") + dev.disable("EV_ABS", "ABS_RY") + assert not dev.has_event("EV_ABS", "ABS_RY") + + data = 1 + assert not dev.has_event("EV_REP", "REP_DELAY") + dev.enable("EV_REP", "REP_DELAY", data) + assert dev.has_event("EV_REP", "REP_DELAY") + dev.disable("EV_REP", "REP_DELAY") + assert not dev.has_event("EV_REP", "REP_DELAY") + + @pytest.mark.skipif(not is_root(), reason="Test requires root") + def test_enable_property(self, local_abs_device): + dev = Libevdev(local_abs_device) + assert not dev.has_property("INPUT_PROP_ACCELEROMETER") + dev.enable_property("INPUT_PROP_ACCELEROMETER") + assert dev.has_property("INPUT_PROP_ACCELEROMETER") + + +@pytest.mark.skipif(not has_event_devices(), reason="Local event devices required") +class TestMTDevice: + """ + Tests various things against the first MT device found. + Requires root rights. + """ + + @pytest.mark.skipif(not is_root(), reason="Test requires root") + def test_current_slot(self, local_mt_device): + dev = Libevdev(local_mt_device) + assert dev.current_slot is not None and dev.current_slot >= 0 + + @pytest.mark.skipif(not is_root(), reason="Test requires root") + def test_slot_value(self, local_mt_device): + dev = Libevdev(local_mt_device) + assert dev.current_slot is not None + a = dev.absinfo("ABS_MT_POSITION_X") + v = dev.slot_value(dev.current_slot, "ABS_MT_POSITION_X") + assert a is not None + assert v is not None + assert a["minimum"] <= v + assert a["maximum"] >= v + + @pytest.mark.skipif(not is_root(), reason="Test requires root") + def test_set_slot_value(self, local_mt_device): + dev = Libevdev(local_mt_device) + assert dev.current_slot is not None + v = dev.slot_value(dev.current_slot, "ABS_MT_POSITION_X") + assert v is not None + v += 10 + v2 = dev.slot_value(dev.current_slot, "ABS_MT_POSITION_X", v) + assert v == v2 + v2 = dev.slot_value(dev.current_slot, "ABS_MT_POSITION_X") + assert v == v2 + + +class TestUinput: + """ + Tests uinput device creation. + Requires root rights. + """ + + def is_identical(self, d1: Libevdev, d2: Libevdev): + ev_max = Libevdev.event_to_value("EV_MAX") + assert ev_max is not None + for t in range(ev_max): + type_max = Libevdev.type_max(t) + if type_max is None: + continue + for c in range(type_max): + if d1.has_event(t, c) != d2.has_event(t, c): + return False + return True + + @pytest.mark.skipif(not is_root(), reason="Test requires root") + def test_relative(self): + dev = Libevdev() + dev.name = "test device" + dev.enable("EV_REL", "REL_X") + dev.enable("EV_REL", "REL_Y") + with UinputDevice(dev) as uinput: + assert uinput.devnode is not None + + with open(uinput.devnode, "rb") as f: + newdev = Libevdev(f) + assert self.is_identical(dev, newdev) + + @pytest.mark.skipif(not is_root(), reason="Test requires root") + def test_button(self): + dev = Libevdev() + dev.name = "test device" + dev.enable("EV_KEY", "BTN_LEFT") + dev.enable("EV_KEY", "KEY_A") + with UinputDevice(dev) as uinput: + assert uinput.devnode is not None + + with open(uinput.devnode, "rb") as f: + newdev = Libevdev(f) + assert self.is_identical(dev, newdev) + + @pytest.mark.skipif(not is_root(), reason="Test requires root") + def test_absolute(self): + absinfo = {"minimum": 0, "maximum": 1} + + dev = Libevdev() + dev.name = "test device" + dev.enable("EV_ABS", "ABS_X", absinfo) + dev.enable("EV_ABS", "ABS_Y", absinfo) + with UinputDevice(dev) as uinput: + assert uinput.devnode is not None + + with open(uinput.devnode, "rb") as f: + newdev = Libevdev(f) + assert self.is_identical(dev, newdev) + + @pytest.mark.skipif(not is_root(), reason="Test requires root") + def test_device_node(self): + dev = Libevdev() + dev.name = "test device" + dev.enable("EV_REL", "REL_X") + dev.enable("EV_REL", "REL_Y") + with UinputDevice(dev) as uinput: + assert uinput.devnode.startswith("/dev/input/event") + + @pytest.mark.skipif(not is_root(), reason="Test requires root") + def test_syspath(self): + dev = Libevdev() + dev.name = "test device" + dev.enable("EV_REL", "REL_X") + dev.enable("EV_REL", "REL_Y") + uinput = UinputDevice(dev) + assert uinput.syspath.startswith("/sys/devices/virtual/input/input") diff --git a/test/test_const.py b/test/test_const.py new file mode 100644 index 0000000..d814a4c --- /dev/null +++ b/test/test_const.py @@ -0,0 +1,384 @@ +# Copyright © 2017 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice (including the next +# paragraph) shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +import pytest + +import libevdev +from libevdev import evbit, propbit +from libevdev.const import EventType, EventCode, InputProperty + + +class TestEventBits: + def test_ev_types(self): + assert libevdev.EV_SYN in libevdev.types + assert libevdev.EV_REL in libevdev.types + assert libevdev.EV_ABS in libevdev.types + assert len(libevdev.types) == 13 + + def test_EV_REL(self): + assert libevdev.EV_REL.REL_X in libevdev.EV_REL.codes + assert libevdev.EV_REL.REL_Y in libevdev.EV_REL.codes + assert libevdev.EV_ABS.ABS_X not in libevdev.EV_REL.codes + + def test_type_max(self): + assert libevdev.EV_REL.max == libevdev.EV_REL.REL_MAX.value + assert libevdev.EV_ABS.max == libevdev.EV_ABS.ABS_MAX.value + assert libevdev.EV_KEY.max == libevdev.EV_KEY.KEY_MAX.value + + assert libevdev.EV_REL.max == 0x0F + assert libevdev.EV_ABS.max == 0x3F + assert libevdev.EV_KEY.max == 0x2FF + + def test_evcode_compare(self): + assert libevdev.EV_REL.REL_X != libevdev.EV_REL.REL_Y + assert libevdev.EV_REL.REL_X != libevdev.EV_ABS.ABS_X + assert libevdev.EV_REL != libevdev.EV_ABS + + assert libevdev.EV_REL.REL_X != libevdev.EV_REL + assert libevdev.EV_ABS.ABS_X != libevdev.EV_ABS + + def test_int_conversion(self): + assert int(libevdev.EV_REL.REL_X) == 0 + assert int(libevdev.EV_KEY.KEY_ESC) == 1 + assert int(libevdev.EV_KEY) == 0x1 + assert int(libevdev.INPUT_PROP_SEMI_MT) == 3 + + def test_evbit(self): + assert evbit(0, 0) == libevdev.EV_SYN.SYN_REPORT + assert evbit(1, 30) == libevdev.EV_KEY.KEY_A + assert evbit(2, 1) == libevdev.EV_REL.REL_Y + + assert evbit(0) == libevdev.EV_SYN + assert evbit(1) == libevdev.EV_KEY + assert evbit(2) == libevdev.EV_REL + + for t in libevdev.types: + assert evbit(t.value) == t + + def test_direct_name(self): + """ + libevdev.EV_FOO.FOO_BAR == libevdev.FOO_BAR + """ + for ty in libevdev.types: + for code in filter(lambda c: c.is_defined, ty.codes): + assert getattr(libevdev, code.name) == code + + def test_propbit(self): + assert propbit(0) == libevdev.INPUT_PROP_POINTER + assert propbit(1) == libevdev.INPUT_PROP_DIRECT + + for p in libevdev.props: + assert propbit(p.value) == p + + def test_evbit_string(self): + assert evbit("EV_SYN") == libevdev.EV_SYN + assert evbit("EV_KEY") == libevdev.EV_KEY + assert evbit("EV_REL") == libevdev.EV_REL + assert evbit("EV_REP") == libevdev.EV_REP + + for t in libevdev.types: + assert evbit(t.name) == t + + def test_evcode_string(self): + assert evbit("ABS_X") == libevdev.ABS_X + assert evbit("REL_X") == libevdev.REL_X + assert evbit("SYN_REPORT") == libevdev.SYN_REPORT + assert evbit("REP_PERIOD") == libevdev.REP_PERIOD + + def test_evcode_is_defined(self): + for t in libevdev.types: + for c in t.codes: + fake_name = "{}_{:02X}".format(t.name[3:], c.value) + if c.is_defined: + assert c.name != fake_name + else: + assert c.name == fake_name + + def test_evcode_undefined(self): + assert evbit("SYN_04") == libevdev.EV_SYN._SYN_04 + assert libevdev.EV_SYN._SYN_04.name == "SYN_04" + + def test_propbit_string(self): + assert propbit("INPUT_PROP_POINTER") == libevdev.INPUT_PROP_POINTER + assert propbit("INPUT_PROP_DIRECT") == libevdev.INPUT_PROP_DIRECT + + for p in libevdev.props: + assert propbit(p.value) == p + + def test_repr(self): + assert str(libevdev.EV_REL) == "EV_REL:2" + + def test_less_than(self): + assert libevdev.EV_REL < libevdev.EV_ABS + + def test_hashables(self): + d = {} + d[libevdev.ABS_Z] = True # same numeric value as EV_REL + d[libevdev.EV_ABS] = True + d[libevdev.EV_REL] = True + d[libevdev.INPUT_PROP_SEMI_MT] = True # same numeric value as EV_ABS + assert len(d) == 4 + + +class TestEventType: + def test_from_value_valid_types(self): + """Test from_value() with all valid event type values""" + assert EventType.from_value(0) == libevdev.EV_SYN + assert EventType.from_value(1) == libevdev.EV_KEY + assert EventType.from_value(2) == libevdev.EV_REL + assert EventType.from_value(3) == libevdev.EV_ABS + + @pytest.mark.parametrize("evtype", [t for t in libevdev.types]) + def test_from_value_all_types(self, evtype): + """Test from_value() returns correct type for all known types""" + result = EventType.from_value(evtype.value) + assert result is not None + assert result == evtype + assert result.value == evtype.value + assert result.name == evtype.name + + @pytest.mark.parametrize("value", [8, 13, 14, 50, 255, 1000, -5, -100]) + def test_from_value_invalid(self, value): + """Test from_value() with various invalid values""" + assert EventType.from_value(value) is None + + def test_from_name_valid_names(self): + """Test from_name() with valid event type names""" + assert EventType.from_name("EV_SYN") == libevdev.EV_SYN + assert EventType.from_name("EV_KEY") == libevdev.EV_KEY + assert EventType.from_name("EV_REL") == libevdev.EV_REL + assert EventType.from_name("EV_ABS") == libevdev.EV_ABS + assert EventType.from_name("EV_MSC") == libevdev.EV_MSC + + @pytest.mark.parametrize("evtype", [t for t in libevdev.types]) + def test_from_name_all_types(self, evtype): + """Test from_name() returns correct type for all known types""" + result = EventType.from_name(evtype.name) + assert result is not None + assert result == evtype + assert result.value == evtype.value + assert result.name == evtype.name + + @pytest.mark.parametrize( + "name", + [ + "INVALID", + "EV_INVALID", + "", + "ev_syn", # lowercase + "EV_SYN ", # trailing space + " EV_SYN", # leading space + "REL_X", # event code name, not type name + "INPUT_PROP_POINTER", # property name + "ABS", # missing prefix + "REL", # missing prefix + ], + ) + def test_from_name_invalid_parametrized(self, name): + """Test from_name() with various invalid names""" + assert EventType.from_name(name) is None + + def test_from_value_and_from_name_consistency(self): + """Test that from_value() and from_name() return the same objects""" + for evtype in libevdev.types: + from_value = EventType.from_value(evtype.value) + from_name = EventType.from_name(evtype.name) + assert from_value == from_name + assert from_value is from_name # should be same object reference + + +class TestEventCode: + def test_from_name_valid_codes(self): + """Test from_name() with valid event code names""" + assert EventCode.from_name("ABS_X") == libevdev.ABS_X + assert EventCode.from_name("REL_X") == libevdev.REL_X + assert EventCode.from_name("REL_Y") == libevdev.REL_Y + assert EventCode.from_name("KEY_A") == libevdev.KEY_A + assert EventCode.from_name("KEY_ESC") == libevdev.KEY_ESC + assert EventCode.from_name("SYN_REPORT") == libevdev.SYN_REPORT + + def test_from_name_all_defined_codes(self): + """Test from_name() for all defined event codes""" + for evtype in libevdev.types: + for code in filter(lambda c: c.is_defined, evtype.codes): + result = EventCode.from_name(code.name) + assert result is not None + assert result == code + assert result.name == code.name + assert result.value == code.value + + @pytest.mark.parametrize( + "name", + [ + "INVALID", + "", + "abs_x", # lowercase + "ABS_X ", # trailing space + " ABS_X", # leading space + "ABS_999", # doesn't exist + "X", # missing prefix + "Y", # missing prefix + "EV_ABS", # event type + "INPUT_PROP_POINTER", # input property + ], + ) + def test_from_name_invalid_parametrized(self, name): + """Test from_name() with various invalid names""" + assert EventCode.from_name(name) is None + + def test_from_name_undefined_codes(self): + """Test from_name() with undefined code names returns None""" + # Undefined codes like SYN_04 are not accessible via from_name + # because they are not set on the libevdev module + assert EventCode.from_name("SYN_04") is None + + @pytest.mark.parametrize( + "evtype,code,expected", + [ + (libevdev.EV_ABS, 0, libevdev.ABS_X), + (3, 0, libevdev.ABS_X), + (libevdev.EV_ABS, 1, libevdev.ABS_Y), + (3, 1, libevdev.ABS_Y), + (libevdev.EV_REL, 0, libevdev.REL_X), + (2, 0, libevdev.REL_X), + (libevdev.EV_REL, 1, libevdev.REL_Y), + (libevdev.EV_KEY, 1, libevdev.KEY_ESC), + (1, 1, libevdev.KEY_ESC), + (libevdev.EV_KEY, 30, libevdev.KEY_A), + (libevdev.EV_SYN, 0, libevdev.SYN_REPORT), + (0, 0, libevdev.SYN_REPORT), + ], + ) + def test_from_type_and_code_value(self, evtype, code, expected): + """Test from_type_and_code_value() with various EventType and code combinations""" + result = EventCode.from_type_and_code_value(evtype, code) + assert result == expected + + def test_from_type_and_code_value_all_codes(self): + """Test from_type_and_code_value() for all codes in all types""" + for evtype in libevdev.types: + for code in evtype.codes: + # Test with EventType object + result = EventCode.from_type_and_code_value(evtype, code.value) + assert result == code + + # Test with int type value + result = EventCode.from_type_and_code_value(evtype.value, code.value) + assert result == code + + @pytest.mark.parametrize( + "evtype,code", + [ + (999, 9999), + (libevdev.EV_ABS, 9999), + (3, 9999), + (libevdev.EV_ABS, -1), + (libevdev.EV_REL, 1000), + (libevdev.EV_KEY, 10000), + ], + ) + def test_from_type_and_code_value_invalid(self, evtype, code): + """Test from_type_and_code_value() with various invalid codes""" + result = EventCode.from_type_and_code_value(evtype, code) + assert result is None + + def test_from_type_and_code_value_undefined_codes(self): + """Test from_type_and_code_value() with undefined but valid code values""" + # These are codes that exist in the range but don't have names + # They should still be returned + result = EventCode.from_type_and_code_value(libevdev.EV_SYN, 4) + assert result is not None + # The code should be the undefined one with generated name + assert result.name == "SYN_04" + assert result.value == 4 + assert not result.is_defined + + +class TestInputProperty: + def test_from_value_valid_properties(self): + """Test from_value() with valid property values""" + assert InputProperty.from_value(0) == libevdev.INPUT_PROP_POINTER + assert InputProperty.from_value(1) == libevdev.INPUT_PROP_DIRECT + assert InputProperty.from_value(2) == libevdev.INPUT_PROP_BUTTONPAD + + @pytest.mark.parametrize("prop", [p for p in libevdev.props]) + def test_from_value_all_properties(self, prop): + """Test from_value() returns correct property for all known properties""" + result = InputProperty.from_value(prop.value) + assert result is not None + assert result == prop + assert result.value == prop.value + assert result.name == prop.name + + @pytest.mark.parametrize("value", [10, 20, 50, 255, 1000, -5, -100]) + def test_from_value_invalid(self, value): + """Test from_value() with various invalid values""" + assert InputProperty.from_value(value) is None + + @pytest.mark.parametrize( + "name,expected", + ( + ("INPUT_PROP_POINTER", libevdev.INPUT_PROP_POINTER), + ("INPUT_PROP_DIRECT", libevdev.INPUT_PROP_DIRECT), + ("INPUT_PROP_BUTTONPAD", libevdev.INPUT_PROP_BUTTONPAD), + ("INPUT_PROP_SEMI_MT", libevdev.INPUT_PROP_SEMI_MT), + ), + ) + def test_from_name_valid_names(self, name, expected): + """Test from_name() with valid property names""" + assert InputProperty.from_name(name) == expected + + @pytest.mark.parametrize("prop", [p for p in libevdev.props]) + def test_from_name_all_properties(self, prop): + """Test from_name() returns correct property for all known properties""" + result = InputProperty.from_name(prop.name) + assert result is not None + assert result == prop + assert result.value == prop.value + assert result.name == prop.name + + @pytest.mark.parametrize( + "name", + [ + "INVALID", + "INPUT_PROP_INVALID", + "", + "input_prop_pointer", # lowercase + "INPUT_PROP_POINTER ", # trailing space + " INPUT_PROP_POINTER", # leading space + "POINTER", # missing prefix + "EV_ABS", # event type name + "ABS_X", # event code name + "POINTER", # missing prefix + "DIRECT", # missing prefix + ], + ) + def test_from_name_invalid_parametrized(self, name): + """Test from_name() with various invalid names""" + assert InputProperty.from_name(name) is None + + def test_from_value_and_from_name_consistency(self): + """Test that from_value() and from_name() return the same objects""" + for prop in libevdev.props: + from_value = InputProperty.from_value(prop.value) + from_name = InputProperty.from_name(prop.name) + assert from_value == from_name + assert from_value is from_name # should be same object reference diff --git a/test/test_device.py b/test/test_device.py new file mode 100644 index 0000000..6ea8762 --- /dev/null +++ b/test/test_device.py @@ -0,0 +1,458 @@ +# Copyright © 2017 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice (including the next +# paragraph) shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +import os +import pytest + +import libevdev +from libevdev import InvalidFileError, InvalidArgumentException + + +def is_root() -> bool: + return os.getuid() == 0 + + +class TestDevice: + def test_device_empty(self): + d = libevdev.Device() + id = {"bustype": 0, "vendor": 0, "product": 0, "version": 0} + syns = {libevdev.EV_SYN: libevdev.EV_SYN.codes} + + assert d.name == "" + assert d.id == id + assert d.fd is None + assert d.phys is None + assert d.uniq is None + assert d.driver_version == 0 + assert d.syspath is None + assert d.devnode is None + assert d.evbits == syns + assert d.properties == [] + + for t in libevdev.types: + if t == libevdev.EV_SYN: + continue + + assert not d.has(t) + + for c in t.codes: + assert not d.has(c) + assert d.value[c] is None + d.disable(c) # noop + d.disable(t) # noop + + for p in libevdev.props: + assert not d.has_property(p) + + assert d.num_slots is None + assert d.current_slot is None + + for c in libevdev.EV_ABS.codes: + assert d.absinfo[c] is None + + assert [e for e in d.events()] == [] + assert [e for e in d.sync()] == [] + + with pytest.raises(libevdev.InvalidArgumentException): + d.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] + + def test_device_name(self): + d = libevdev.Device() + d.name = "test device" + assert d.name == "test device" + + def test_device_id(self): + d = libevdev.Device() + id = {"bustype": 1, "vendor": 2, "product": 3, "version": 4} + d.id = id + assert d.id == id + + d.id = {"vendor": 3, "product": 4, "version": 5} + id = {"bustype": 1, "vendor": 3, "product": 4, "version": 5} + assert d.id == id + + d.id = {"bustype": 8, "product": 5, "version": 6} + id = {"bustype": 8, "vendor": 3, "product": 5, "version": 6} + assert d.id == id + + d.id = {"bustype": 8, "vendor": 9, "version": 10} + id = {"bustype": 8, "vendor": 9, "product": 5, "version": 10} + assert d.id == id + + d.id = {"bustype": 8, "vendor": 9, "product": 12} + id = {"bustype": 8, "vendor": 9, "product": 12, "version": 10} + assert d.id == id + + def test_device_phys(self): + d = libevdev.Device() + d.phys = "foo" + assert d.phys == "foo" + + d.phys = None + assert d.phys is None + + def test_device_uniq(self): + d = libevdev.Device() + d.uniq = "bar" + assert d.uniq == "bar" + + d.uniq = None + assert d.uniq is None + + def test_driver_version(self): + d = libevdev.Device() + # read-only + with pytest.raises(AttributeError): + d.driver_version = 1 + + def test_garbage_fd(self): + with pytest.raises(InvalidFileError): + libevdev.Device(fd=1) + + with pytest.raises(InvalidFileError): + d = libevdev.Device() + d.fd = 2 + + @pytest.mark.skipif(not is_root(), reason="Test requires root") + def test_fd_on_init(self): + fd = open("/dev/input/event0", "rb") + d = libevdev.Device(fd) + assert d.fd == fd + + @pytest.mark.skipif(not is_root(), reason="Test requires root") + def test_fd_too_late(self): + fd = open("/dev/input/event0", "rb") + d = libevdev.Device() + with pytest.raises(InvalidFileError): + d.fd = fd + + @pytest.mark.skipif(not is_root(), reason="Test requires root") + def test_fd_change(self): + fd1 = open("/dev/input/event0", "rb") + fd2 = open("/dev/input/event1", "rb") + d = libevdev.Device(fd1) + assert d.fd == fd1 + d.fd = fd2 + assert d.fd == fd2 + + @pytest.mark.skipif(not is_root(), reason="Test requires root") + def test_has_bits(self): + fd = open("/dev/input/event0", "rb") + d = libevdev.Device(fd) + bits = d.evbits + + # assume at least 2 event types + assert len(bits.keys()) > 1 + + for t, cs in bits.items(): + if t == libevdev.EV_SYN: + continue + + # assume at least one code + assert len(cs) > 0 + + def test_set_bits(self): + d = libevdev.Device() + # read-only + with pytest.raises(AttributeError): + d.evbits = {} + + def test_bits_change_after_enable(self): + d = libevdev.Device() + bits = d.evbits + assert libevdev.EV_SYN in bits + assert libevdev.EV_REL not in bits + + d.enable(libevdev.EV_REL.REL_X) + d.enable(libevdev.EV_REL.REL_Y) + + bits = d.evbits + assert libevdev.EV_SYN in bits + assert libevdev.EV_REL in bits + assert libevdev.EV_ABS not in bits + assert libevdev.EV_KEY not in bits + + assert libevdev.EV_REL.REL_X in bits[libevdev.EV_REL] + assert libevdev.EV_REL.REL_Y in bits[libevdev.EV_REL] + + def test_bits_change_after_disable(self): + d = libevdev.Device() + d.enable(libevdev.EV_REL.REL_X) + d.enable(libevdev.EV_REL.REL_Y) + d.enable(libevdev.EV_KEY.KEY_A) + d.enable(libevdev.EV_KEY.KEY_B) + + bits = d.evbits + assert libevdev.EV_SYN in bits + assert libevdev.EV_REL in bits + assert libevdev.EV_KEY in bits + assert libevdev.EV_ABS not in bits + assert libevdev.EV_REL.REL_X in bits[libevdev.EV_REL] + assert libevdev.EV_REL.REL_Y in bits[libevdev.EV_REL] + assert libevdev.EV_KEY.KEY_A in bits[libevdev.EV_KEY] + assert libevdev.EV_KEY.KEY_B in bits[libevdev.EV_KEY] + + d.disable(libevdev.EV_REL.REL_Y) + d.disable(libevdev.EV_KEY) + bits = d.evbits + assert libevdev.EV_KEY not in bits + assert libevdev.EV_REL in bits + assert libevdev.EV_REL.REL_X in bits[libevdev.EV_REL] + assert libevdev.EV_REL.REL_Y not in bits[libevdev.EV_REL] + + def test_properties(self): + d = libevdev.Device() + assert d.properties == [] + for p in libevdev.props: + assert not d.has_property(p) + + props = sorted([libevdev.INPUT_PROP_BUTTONPAD, libevdev.INPUT_PROP_DIRECT]) + + for p in props: + d.enable(p) + + assert d.properties == props + for p in libevdev.props: + if p not in props: + assert not d.has_property(p) + else: + assert d.has_property(p) + + with pytest.raises(NotImplementedError): + d.disable(libevdev.INPUT_PROP_BUTTONPAD) + + def test_has(self): + d = libevdev.Device() + + d.enable(libevdev.EV_REL.REL_X) + d.enable(libevdev.EV_REL.REL_Y) + d.enable(libevdev.EV_KEY.KEY_A) + d.enable(libevdev.EV_KEY.KEY_B) + + assert d.has(libevdev.EV_REL) + assert d.has(libevdev.EV_REL.REL_X) + assert d.has(libevdev.EV_REL.REL_Y) + assert not d.has(libevdev.EV_REL.REL_Z) + + assert d.has(libevdev.EV_KEY) + assert d.has(libevdev.EV_KEY.KEY_A) + assert d.has(libevdev.EV_KEY.KEY_B) + assert not d.has(libevdev.EV_KEY.KEY_C) + + assert not d.has(libevdev.EV_ABS) + + def test_enable_abs(self): + d = libevdev.Device() + with pytest.raises(InvalidArgumentException): + d.enable(libevdev.EV_ABS.ABS_X) + + def test_value(self): + d = libevdev.Device() + d.enable(libevdev.EV_REL.REL_X) + assert d.value[libevdev.EV_REL.REL_X] == 0 + assert d.value[libevdev.EV_REL.REL_Y] is None + + @pytest.mark.skipif(not is_root(), reason="Test requires root") + def test_mt_value(self): + # Unable to set ABS_MT_SLOT on a libevdev device, see + # https://bugs.freedesktop.org/show_bug.cgi?id=104270 + d = libevdev.Device() + a = libevdev.InputAbsInfo(minimum=0, maximum=100) + d.name = "test device" + d.enable(libevdev.EV_ABS.ABS_X, a) + d.enable(libevdev.EV_ABS.ABS_Y, a) + d.enable( + libevdev.EV_ABS.ABS_MT_SLOT, libevdev.InputAbsInfo(minimum=0, maximum=30) + ) + d.enable(libevdev.EV_ABS.ABS_MT_POSITION_X, a) + d.enable(libevdev.EV_ABS.ABS_MT_POSITION_Y, a) + d.enable(libevdev.EV_ABS.ABS_MT_TRACKING_ID, a) + + uinput = d.create_uinput_device() + + fd = open(uinput.devnode, "rb") + d = libevdev.Device(fd) + + assert d.num_slots == 31 + assert d.value[libevdev.EV_ABS.ABS_X] == 0 + assert d.value[libevdev.EV_ABS.ABS_Y] == 0 + + with pytest.raises(libevdev.InvalidArgumentException): + d.value[libevdev.EV_ABS.ABS_MT_POSITION_X] + with pytest.raises(libevdev.InvalidArgumentException): + d.value[libevdev.EV_ABS.ABS_MT_POSITION_Y] + with pytest.raises(libevdev.InvalidArgumentException): + d.value[libevdev.EV_ABS.ABS_MT_SLOT] + with pytest.raises(libevdev.InvalidArgumentException): + d.value[libevdev.EV_ABS.ABS_MT_TRACKING_ID] + # also raises for non-existing axes + with pytest.raises(libevdev.InvalidArgumentException): + d.value[libevdev.EV_ABS.ABS_MT_ORIENTATION] + + @pytest.mark.skipif(not is_root(), reason="Test requires root") + def test_slot_value(self): + # Unable to set ABS_MT_SLOT on a libevdev device, see + # https://bugs.freedesktop.org/show_bug.cgi?id=104270 + d = libevdev.Device() + a = libevdev.InputAbsInfo(minimum=0, maximum=100) + d.name = "test device" + d.enable(libevdev.EV_ABS.ABS_X, a) + d.enable(libevdev.EV_ABS.ABS_Y, a) + d.enable( + libevdev.EV_ABS.ABS_MT_SLOT, libevdev.InputAbsInfo(minimum=0, maximum=30) + ) + d.enable(libevdev.EV_ABS.ABS_MT_POSITION_X, a) + d.enable(libevdev.EV_ABS.ABS_MT_POSITION_Y, a) + d.enable(libevdev.EV_ABS.ABS_MT_TRACKING_ID, a) + + uinput = d.create_uinput_device() + events = [ + libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_SLOT, 0), + libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_TRACKING_ID, 1), + libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_X, 100), + libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_Y, 110), + libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_SLOT, 1), + libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_TRACKING_ID, 2), + libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_X, 200), + libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_Y, 210), + libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT, 0), + ] + uinput.send_events(events) + + fd = open(uinput.devnode, "rb") + d = libevdev.Device(fd) + + assert d.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 100 + assert d.slots[1][libevdev.EV_ABS.ABS_MT_POSITION_X] == 200 + assert d.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 110 + assert d.slots[1][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 210 + + assert d.slots[0][libevdev.EV_ABS.ABS_MT_ORIENTATION] is None + + for idx, s in enumerate(d.slots[:2]): + idx += 1 + assert s[libevdev.EV_ABS.ABS_MT_POSITION_X] == idx * 100 + assert s[libevdev.EV_ABS.ABS_MT_POSITION_Y] == idx * 100 + 10 + + for s in d.slots[2:]: + assert s[libevdev.EV_ABS.ABS_MT_POSITION_X] == 0 + assert s[libevdev.EV_ABS.ABS_MT_POSITION_Y] == 0 + + with pytest.raises(IndexError): + d.slots[200] + + with pytest.raises(libevdev.InvalidArgumentException): + d.slots[0][libevdev.EV_ABS.ABS_X] + with pytest.raises(libevdev.InvalidArgumentException): + d.slots[0][libevdev.EV_ABS.ABS_MT_SLOT] + with pytest.raises(libevdev.InvalidArgumentException): + d.slots[0][libevdev.EV_REL.REL_X] + with pytest.raises(AttributeError): + d.slots[0][libevdev.EV_ABS] + + d.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] = 10 + assert d.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 10 + d.slots[1][libevdev.EV_ABS.ABS_MT_POSITION_Y] = 50 + assert d.slots[1][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 50 + + with pytest.raises(libevdev.InvalidArgumentException): + d.slots[0][libevdev.EV_ABS.ABS_MT_ORIENTATION] = 50 + with pytest.raises(libevdev.InvalidArgumentException): + d.slots[0][libevdev.EV_ABS.ABS_X] = 10 + with pytest.raises(libevdev.InvalidArgumentException): + d.slots[0][libevdev.EV_ABS.ABS_MT_SLOT] = 10 + with pytest.raises(libevdev.InvalidArgumentException): + d.slots[0][libevdev.EV_REL.REL_X] = 10 + with pytest.raises(AttributeError): + d.slots[0][libevdev.EV_ABS] = 10 + + def test_absinfo(self): + d = libevdev.Device() + a = libevdev.InputAbsInfo(minimum=100, maximum=1000, resolution=50) + d.enable(libevdev.EV_ABS.ABS_X, a) + # we expect these to be filled in + a.fuzz = 0 + a.flat = 0 + a.value = 0 + + a2 = d.absinfo[libevdev.EV_ABS.ABS_X] + assert a == a2 + + assert d.absinfo[libevdev.EV_ABS.ABS_Y] is None + a.fuzz = 10 + d.absinfo[libevdev.EV_ABS.ABS_X] = a + a2 = d.absinfo[libevdev.EV_ABS.ABS_X] + assert a == a2 + + a = libevdev.InputAbsInfo(minimum=500) + d.absinfo[libevdev.EV_ABS.ABS_X] = a + a2 = d.absinfo[libevdev.EV_ABS.ABS_X] + assert a.minimum == a2.minimum + assert a2.minimum is not None + assert a2.maximum is not None + assert a2.fuzz is not None + assert a2.flat is not None + assert a2.resolution is not None + assert a2.value is not None + + def test_absinfo_set_invalid(self): + a = libevdev.InputAbsInfo(minimum=500) + d = libevdev.Device() + with pytest.raises(InvalidArgumentException): + d.absinfo[libevdev.EV_ABS.ABS_Y] = a + with pytest.raises(AssertionError): + d.absinfo[libevdev.EV_REL.REL_X] + with pytest.raises(AssertionError): + d.absinfo[libevdev.EV_REL.REL_X] = a + + @pytest.mark.skipif(not is_root(), reason="Test requires root") + def test_absinfo_sync_kernel(self): + d = libevdev.Device() + d.name = "sync test device" + a = libevdev.InputAbsInfo( + minimum=0, maximum=1000, resolution=50, fuzz=0, flat=0, value=0 + ) + d.enable(libevdev.EV_ABS.ABS_X, a) + d.enable(libevdev.EV_ABS.ABS_Y, a) + uinput = d.create_uinput_device() + + fd = open(uinput.devnode, "rb") + d = libevdev.Device(fd) + a2 = d.absinfo[libevdev.EV_ABS.ABS_X] + assert a == a2 + a2.resolution = 100 + d.absinfo[libevdev.EV_ABS.ABS_X] = a2 + d.sync_absinfo_to_kernel(libevdev.EV_ABS.ABS_X) + fd.close() + + fd = open(uinput.devnode, "rb") + d = libevdev.Device(fd) + a3 = d.absinfo[libevdev.EV_ABS.ABS_X] + print(a3) + assert a2 == a3 + a3 = d.absinfo[libevdev.EV_ABS.ABS_Y] + assert a == a3 + + @pytest.mark.skipif(not is_root(), reason="Test requires root") + def test_uinput_empty(self): + d = libevdev.Device() + with pytest.raises(OSError): + d.create_uinput_device() diff --git a/test/test-events.py b/test/test_events.py similarity index 65% rename from test/test-events.py rename to test/test_events.py index 361b9ab..60a1261 100644 --- a/test/test-events.py +++ b/test/test_events.py @@ -1,4 +1,3 @@ -# -*- coding: latin-1 -*- # Copyright © 2017 Red Hat, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a @@ -20,35 +19,41 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -import unittest import libevdev -from libevdev import evbit, propbit, InputEvent +from libevdev import InputEvent -class TestEvents(unittest.TestCase): + +class TestEvents: def test_event_matches_type(self): ev = InputEvent(libevdev.EV_REL) - self.assertTrue(ev.matches(libevdev.EV_REL)) - self.assertFalse(ev.matches(libevdev.EV_REL.REL_X)) - self.assertFalse(ev.matches(libevdev.EV_ABS)) - self.assertFalse(ev.matches(libevdev.EV_ABS.ABS_X)) + assert ev.matches(libevdev.EV_REL) + assert not ev.matches(libevdev.EV_REL.REL_X) + assert not ev.matches(libevdev.EV_ABS) + assert not ev.matches(libevdev.EV_ABS.ABS_X) def test_event_matches_code(self): ev = InputEvent(libevdev.EV_REL.REL_X) - self.assertTrue(ev.matches(libevdev.EV_REL.REL_X)) - self.assertTrue(ev.matches(libevdev.EV_REL)) - self.assertFalse(ev.matches(libevdev.EV_ABS)) - self.assertFalse(ev.matches(libevdev.EV_ABS.ABS_X)) + assert ev.matches(libevdev.EV_REL.REL_X) + assert ev.matches(libevdev.EV_REL) + assert not ev.matches(libevdev.EV_ABS) + assert not ev.matches(libevdev.EV_ABS.ABS_X) def test_event_matches_self(self): e1 = InputEvent(libevdev.EV_REL.REL_X) e2 = InputEvent(libevdev.EV_REL) - self.assertEqual(e1, e1) - self.assertEqual(e2, e2) + assert e1 == e1 + assert e2 == e2 - self.assertEqual(e2, e1) - self.assertEqual(e1, e2) + assert e2 == e1 + assert e1 == e2 e2 = InputEvent(libevdev.EV_REL.REL_Y) - self.assertNotEqual(e2, e1) - self.assertNotEqual(e1, e2) + assert e2 != e1 + assert e1 != e2 + + def test_event_matches_invalid(self): + e1 = InputEvent(libevdev.EV_REL.REL_X) + assert e1 != 0 + assert e1 is not None + assert e1 != "foo" diff --git a/tools/generate-types.py b/tools/generate-types.py new file mode 100644 index 0000000..d4bf470 --- /dev/null +++ b/tools/generate-types.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +# Copyright © 2025 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice (including the next +# paragraph) shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# +# This script generates the __init__.pyi type definitions. +# Usage: +# $ python3 ./tools/generate-types.py > libevdev/__init__.pyi +# +# This only needs updates when new codes are being added to the kernel and even +# then it only needs updates for those codes to resolve for typing checkers. The +# actual lookup of the value is at runtime. + +from libevdev._clib import Libevdev + + +prefix = """# This file is generated, do not edit +from typing import Type + +from libevdev import device +from libevdev import event +from libevdev import const + +Device: Type[device.Device] +InputAbsInfo: Type[device.InputAbsInfo] +InvalidFileError: Type[device.InvalidFileError] +EventsDroppedException: Type[device.EventsDroppedException] +InvalidArgumentException: Type[device.InvalidArgumentException] +InputEvent: Type[event.InputEvent] +EventType: Type[const.EventType] +EventCode: Type[const.EventCode] +InputProperty: Type[const.InputProperty] + +def evbit( + evtype: int | str, evcode: int | str | None = None +) -> const.EventCode | const.EventType | None: ... +def propbit(prop: int | str) -> const.InputProperty | None: ... +""" + +print(prefix) + +Libevdev() # libevdev's methods are set as classmethods once we instantiate it + +tmax: int | None = Libevdev.event_to_value("EV_MAX") +assert tmax is not None + +type_names: list[str] = [] +code_names: list[str] = [] + +for t in range(tmax + 1): + tname = Libevdev.event_to_name(t) + if tname is None: + continue + + cmax = Libevdev.type_max(t) + if cmax is None: + continue + + print(f"class _{tname}(const.EventType):") + type_names.append(tname) + + for c in range(cmax + 1): + cname = Libevdev.event_to_name(t, c) + if cname is None: + continue + + print(" @property") + print(f" def {cname}(self) -> const.EventCode: ...") + code_names.append(f"{cname}") + + print() + +pmax: int | None = Libevdev.property_to_value("INPUT_PROP_MAX") +assert pmax is not None +prop_names: list[str] = [] +for p in range(pmax + 1): + pname = Libevdev.property_to_name(p) + if pname is None: + continue + prop_names.append(pname) + +for name in type_names: + print(f"{name}: _{name}") +for name in code_names: + print(f"{name}: const.EventCode") +for name in prop_names: + print(f"{name}: const.InputProperty") + +print("types: list[const.EventType]") +print("props: list[const.InputProperty]")