[
  {
    "path": ".github/workflows/install.yaml",
    "content": "name: Test install\n\non:\n  - push\n  - pull_request\n\njobs:\n  build:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-latest]\n        python-version: [\"3.9\", \"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\"]\n        include:\n          - os: ubuntu-latest\n            python-version: \"3.9\"\n\n    steps:\n      - uses: actions/checkout@v6\n      - name: Set up Python ${{ matrix.python-version }}\n        uses: actions/setup-python@v6\n        with:\n          python-version: ${{ matrix.python-version }}\n\n      - name: Install python-evdev\n        run: |\n          python -m pip install -v .\n          (cd /tmp && python -c \"import evdev.ecodes; print(evdev.ecodes)\")\n"
  },
  {
    "path": ".github/workflows/lint.yml",
    "content": "name: Lint\n\non:\n  - push\n  - pull_request\n\njobs:\n  pylint:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-latest]\n        python-version: [\"3.14\"]\n\n    steps:\n      - uses: actions/checkout@v6\n      - name: Set up Python ${{ matrix.python-version }}\n        uses: actions/setup-python@v6\n        with:\n          python-version: ${{ matrix.python-version }}\n\n      - name: Check for pylint errors\n        run: |\n          python -m pip install pylint setuptools\n          python setup.py build\n          python -m pylint --verbose -E build/lib*/evdev\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Test\n\non:\n  - push\n  - pull_request\n\njobs:\n  test:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-latest]\n        python-version: [\"3.14\"]\n\n    steps:\n      - uses: actions/checkout@v6\n      - name: Set up Python ${{ matrix.python-version }}\n        uses: actions/setup-python@v6\n        with:\n          python-version: ${{ matrix.python-version }}\n\n      - name: Run pytest tests\n        # sudo required to write to uinputs\n        run: |\n          sudo python -m pip install pytest setuptools\n          sudo python -m pip install -e .\n          sudo python -m pytest tests\n"
  },
  {
    "path": ".gitignore",
    "content": "# --- Python ---\n*.py[co]\n*.egg\n*.egg-info/\ndevelop-eggs/\ndist/\nbuild/\nwheelhouse/\ndropin.cache\npip-log.txt\n.installed.cfg\n.coverage\ntags\nTAGS\n.#*\n__pycache__\n.pytest_cache\n.ruff_cache\n.venv\nuv.lock\n\nsrc/evdev/*.so\nsrc/evdev/ecodes.c\nsrc/evdev/ecodes.pyi\ndocs/_build\nsrc/evdev/_ecodes.py\nsrc/evdev/_input.py\nsrc/evdev/_uinput.py\n"
  },
  {
    "path": ".readthedocs.yaml",
    "content": "# https://docs.readthedocs.io/en/stable/config-file/v2.html\nversion: 2\n\nbuild:\n  os: ubuntu-22.04\n  tools:\n    python: \"3.12\"\n\nsphinx:\n  configuration: docs/conf.py\n\npython:\n  install:\n    - requirements: requirements-dev.txt\n    - path: ."
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2012-2025 Georgi Valkov. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n  1. Redistributions of source code must retain the above copyright\n     notice, this list of conditions and the following disclaimer.\n\n  2. Redistributions in binary form must reproduce the above copyright\n     notice, this list of conditions and the following disclaimer in\n     the documentation and/or other materials provided with the\n     distribution.\n\n  3. Neither the name of author nor the names of its contributors may\n     be used to endorse or promote products derived from this software\n     without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "# The _ecodes extension module source file needs to be generated against the\n# evdev headers of the running kernel. Refer to the 'build_ecodes' distutils\n# command in setup.py.\nexclude src/evdev/ecodes.c\ninclude src/evdev/ecodes.py\n"
  },
  {
    "path": "README.md",
    "content": "# evdev\n\n<p>\n    <a href=\"https://pypi.python.org/pypi/evdev\"><img alt=\"pypi version\" src=\"https://img.shields.io/pypi/v/evdev.svg\"></a>\n    <a href=\"https://github.com/gvalkov/python-evdev/blob/main/LICENSE\"><img alt=\"License\" src=\"https://img.shields.io/pypi/l/evdev\"></a>\n    <a href=\"https://repology.org/project/python:evdev/versions\"><img alt=\"Packaging status\" src=\"https://repology.org/badge/tiny-repos/python:evdev.svg\"></a>\n</p>\n\nThis package provides bindings to the generic input event interface in Linux.\nThe *evdev* interface serves the purpose of passing events generated in the\nkernel directly to userspace through character devices that are typically\nlocated in `/dev/input/`.\n\nThis package also comes with bindings to *uinput*, the userspace input\nsubsystem. *Uinput* allows userspace programs to create and handle input devices\nthat can inject events directly into the input subsystem.\n\n***Documentation:***  \nhttps://python-evdev.readthedocs.io/en/latest/\n\n***Development:***  \nhttps://github.com/gvalkov/python-evdev\n\n***Package:***  \nhttps://pypi.python.org/pypi/evdev\n\n***Changelog:***  \nhttps://python-evdev.readthedocs.io/en/latest/changelog.html"
  },
  {
    "path": "docs/Makefile",
    "content": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nPAPER         =\nBUILDDIR      = _build\n\n# Internal variables.\nPAPEROPT_a4     = -D latex_paper_size=a4\nPAPEROPT_letter = -D latex_paper_size=letter\nALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n# the i18n builder cannot share the environment and doctrees with the others\nI18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n\n.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext ziphtml\n\nhelp:\n\t@echo \"Please use \\`make <target>' where <target> is one of\"\n\t@echo \"  html       to make standalone HTML files\"\n\t@echo \"  dirhtml    to make HTML files named index.html in directories\"\n\t@echo \"  singlehtml to make a single large HTML file\"\n\t@echo \"  pickle     to make pickle files\"\n\t@echo \"  json       to make JSON files\"\n\t@echo \"  htmlhelp   to make HTML files and a HTML help project\"\n\t@echo \"  qthelp     to make HTML files and a qthelp project\"\n\t@echo \"  devhelp    to make HTML files and a Devhelp project\"\n\t@echo \"  epub       to make an epub\"\n\t@echo \"  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\"\n\t@echo \"  latexpdf   to make LaTeX files and run them through pdflatex\"\n\t@echo \"  text       to make text files\"\n\t@echo \"  man        to make manual pages\"\n\t@echo \"  texinfo    to make Texinfo files\"\n\t@echo \"  info       to make Texinfo files and run them through makeinfo\"\n\t@echo \"  gettext    to make PO message catalogs\"\n\t@echo \"  changes    to make an overview of all changed/added/deprecated items\"\n\t@echo \"  linkcheck  to check all external links for integrity\"\n\t@echo \"  doctest    to run all doctests embedded in the documentation (if enabled)\"\n\nclean:\n\t-rm -rf $(BUILDDIR)/*\n\n\nhtml:\n\t$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/html.\"\n\t@sed -i $(BUILDDIR)/html/index.html -e 's,<span>Synopsis</span>,<span>Python Bindings</span>,'\n\ndirhtml:\n\t$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/dirhtml.\"\n\nsinglehtml:\n\t$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml\n\t@echo\n\t@echo \"Build finished. The HTML page is in $(BUILDDIR)/singlehtml.\"\n\npickle:\n\t$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle\n\t@echo\n\t@echo \"Build finished; now you can process the pickle files.\"\n\njson:\n\t$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json\n\t@echo\n\t@echo \"Build finished; now you can process the JSON files.\"\n\nhtmlhelp:\n\t$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp\n\t@echo\n\t@echo \"Build finished; now you can run HTML Help Workshop with the\" \\\n\t      \".hhp project file in $(BUILDDIR)/htmlhelp.\"\n\nqthelp:\n\t$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp\n\t@echo\n\t@echo \"Build finished; now you can run \"qcollectiongenerator\" with the\" \\\n\t      \".qhcp project file in $(BUILDDIR)/qthelp, like this:\"\n\t@echo \"# qcollectiongenerator $(BUILDDIR)/qthelp/python-evdev.qhcp\"\n\t@echo \"To view the help file:\"\n\t@echo \"# assistant -collectionFile $(BUILDDIR)/qthelp/python-evdev.qhc\"\n\ndevhelp:\n\t$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp\n\t@echo\n\t@echo \"Build finished.\"\n\t@echo \"To view the help file:\"\n\t@echo \"# mkdir -p $$HOME/.local/share/devhelp/python-evdev\"\n\t@echo \"# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/python-evdev\"\n\t@echo \"# devhelp\"\n\nepub:\n\t$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub\n\t@echo\n\t@echo \"Build finished. The epub file is in $(BUILDDIR)/epub.\"\n\nlatex:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo\n\t@echo \"Build finished; the LaTeX files are in $(BUILDDIR)/latex.\"\n\t@echo \"Run \\`make' in that directory to run these through (pdf)latex\" \\\n\t      \"(use \\`make latexpdf' here to do that automatically).\"\n\nlatexpdf:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through pdflatex...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\ntext:\n\t$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text\n\t@echo\n\t@echo \"Build finished. The text files are in $(BUILDDIR)/text.\"\n\nman:\n\t$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man\n\t@echo\n\t@echo \"Build finished. The manual pages are in $(BUILDDIR)/man.\"\n\ntexinfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo\n\t@echo \"Build finished. The Texinfo files are in $(BUILDDIR)/texinfo.\"\n\t@echo \"Run \\`make' in that directory to run these through makeinfo\" \\\n\t      \"(use \\`make info' here to do that automatically).\"\n\ninfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo \"Running Texinfo files through makeinfo...\"\n\tmake -C $(BUILDDIR)/texinfo info\n\t@echo \"makeinfo finished; the Info files are in $(BUILDDIR)/texinfo.\"\n\ngettext:\n\t$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale\n\t@echo\n\t@echo \"Build finished. The message catalogs are in $(BUILDDIR)/locale.\"\n\nchanges:\n\t$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes\n\t@echo\n\t@echo \"The overview file is in $(BUILDDIR)/changes.\"\n\nlinkcheck:\n\t$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck\n\t@echo\n\t@echo \"Link check complete; look for any errors in the above output \" \\\n\t      \"or in $(BUILDDIR)/linkcheck/output.txt.\"\n\ndoctest:\n\t$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest\n\t@echo \"Testing of doctests in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/doctest/output.txt.\"\n\n\n# .PHONY: news.rst\n# news.rst: changelog.rst\n# \tcat $< | grep -FB1 '^^^^^^^^^' \\\n# \t| sed -e '/^\\^/d;/--/d' -e 's,\\(.*\\)(\\(.*\\)),* **\\2**: Version \\1released.\\n,' \\\n# \t> $@\n\n$(BUILDDIR)/html/evdev-doc.zip: html\n\tcd $(BUILDDIR)/html/ &&  zip -x \\*.zip -r evdev-doc.zip .\n\t@echo `readlink -f $@`\n\nhtmlzip: $(BUILDDIR)/html/evdev-doc.zip\n"
  },
  {
    "path": "docs/_static/.keep",
    "content": ""
  },
  {
    "path": "docs/_templates/.keep",
    "content": ""
  },
  {
    "path": "docs/apidoc.rst",
    "content": "API Reference\n-------------\n\n``events``\n============\n\n.. automodule:: evdev.events\n   :members: InputEvent, KeyEvent, AbsEvent, RelEvent, SynEvent, event_factory\n   :undoc-members:\n   :exclude-members: __dict__, __str__, __module__, __del__, __slots__, __repr__\n   :member-order: bysource\n\n\n``eventio``\n============\n\n.. automodule:: evdev.eventio\n   :members: EventIO\n   :undoc-members:\n   :special-members:\n   :exclude-members: __dict__, __str__, __module__, __del__, __slots__, __repr__\n   :member-order: bysource\n\n``eventio_async``\n=================\n\n.. automodule:: evdev.eventio_async\n   :members: EventIO\n   :undoc-members:\n   :special-members:\n   :exclude-members: __dict__, __str__, __module__, __del__, __slots__, __repr__\n   :member-order: bysource\n\n``device``\n============\n\n.. automodule:: evdev.device\n   :members: InputDevice, DeviceInfo, AbsInfo, KbdInfo\n   :undoc-members:\n   :special-members:\n   :exclude-members: __dict__, __str__, __module__, __del__, __slots__, __repr__\n   :member-order: bysource\n\n``uinput``\n============\n\n.. automodule:: evdev.uinput\n   :members: UInput\n   :special-members:\n   :exclude-members: __dict__, __str__, __module__, __del__, __slots__, __repr__\n   :member-order: bysource\n\n``util``\n==========\n\n.. automodule:: evdev.util\n   :members: list_devices, is_device, categorize, resolve_ecodes, resolve_ecodes_dict\n   :member-order: bysource\n\n``ecodes``\n============\n\n.. automodule:: evdev.ecodes\n   :members:\n   :exclude-members: __module__, keys, ecodes, bytype\n   :member-order: bysource\n\n.. autodata:: evdev.ecodes.keys\n   :annotation: {0: 'KEY_RESERVED', 1: 'KEY_ESC', 2: 'KEY_1', ...}\n\n.. autodata:: evdev.ecodes.ecodes\n   :annotation: {'KEY_END': 107, 'FF_RUMBLE': 80, 'KEY_KPDOT': 83, 'KEY_CNT': 768, ...}'\n\n.. autodata:: evdev.ecodes.bytype\n   :annotation: {0: {0: 'SYN_REPORT', 1: 'SYN_CONFIG', 2: 'SYN_MT_REPORT', 3: 'SYN_DROPPED'}, ...}\n"
  },
  {
    "path": "docs/changelog.rst",
    "content": "Changelog\n---------\n\n1.9.3 (Feb 05, 2025)\n====================\n\n- Fix several memory leaks in ``input.c``.\n\n- Raise the minimum supported Python version to 3.9 and the setuptools version to 77.0.\n\n\n1.9.2 (May 01, 2025)\n====================\n\n- Add the \"--reproducible\" build option which removes the build date and used headers from the\n  generated ``ecodes.c``. Example usage::\n\n    python -m build --config-setting=--build-option='build_ecodes --reproducible' -n \n\n- Use ``Generic`` to set precise type for ``InputDevice.path``.\n\n\n1.9.1 (Feb 22, 2025)\n====================\n\n- Fix fox missing ``UI_FF`` constants in generated ``ecodes.py``.\n\n- More type annotations.\n\n\n1.9.0 (Feb 08, 2025)\n====================\n\n- Fix for ``CPATH/C_INCLUDE_PATH`` being ignored during build.\n\n- Slightly faster reading of events in ``device.read()`` and ``device.read_one()``.\n\n- Fix FreeBSD support.\n\n- Drop deprecated ``InputDevice.fn`` (use ``InputDevice.path`` instead).\n\n- Improve type hint coverage and add a ``py.typed`` file to the sdist.\n\n\n1.8.0 (Jan 25, 2025)\n====================\n\n- Binary wheels are now provided by the `evdev-binary <http://pypi.python.org/pypi/evdev-binary>`_ package.\n  The package is compiled on manylinux_2_28 against kernel 4.18.\n\n- The ``evdev.ecodes`` module is now generated at install time and contains only constants. This allows type\n  checking and introspection of the ``evdev.ecodes`` module, without having to execute it first. The old\n  module is available as ``evdev.ecodes_runtime``. In case generation of the static ``ecodes.py`` fails, the\n  install process falls back to using ``ecodes_runtime.py`` as ``ecodes.py``.\n\n- Reverse mappings in ``evdev.ecodes`` that point to more than one value are now tuples instead of lists. For example::\n\n    >>> ecodes.KEY[153]\n    ('KEY_DIRECTION', 'KEY_ROTATE_DISPLAY')\n\n- Raise the minimum supported Python version to 3.8.\n\n- Fix keyboard delay and repeat being swapped (#227).\n\n- Move the ``syn()`` convenience method from ``InputDevice`` to ``EventIO`` (#224).\n\n\n1.7.1 (May 8, 2024)\n====================\n\n- Provide fallback value for ``FF_MAX_EFFECTS``, which fixes the build on EL 7 (#219).\n\n- Add ``#ifdef`` guards around ``UI_GET_SYSNAME`` to improve kernel compatibility (#218) .\n\n- Wait up to two seconds for uinput devices to appear. (#215)\n\n\n1.7.0 (Feb 18, 2024)\n====================\n\n- Respect the ``CPATH/C_INCLUDE_PATH`` environment variables during install.\n\n- Add the uniq address to the string representation of ``InputDevice``.\n\n- Improved method for finding the device node corresponding to a uinput device (`#206 <https://github.com/gvalkov/python-evdev/pull/206>`_).\n\n- Repository TLC (reformatted with ruff, fixed linting warnings, moved packaging metadata to ``pyproject.toml`` etc.).\n\n\n1.6.1 (Jan 20, 2023)\n====================\n\n- Fix generation of ``ecodes.c`` when the path to ``sys.executable`` contains spaces.\n\n\n1.6.0 (Jul 17, 2022)\n====================\n\n- Fix Python 3.11 compatibility (`#174 <https://github.com/gvalkov/python-evdev/pull/174>`_).\n\n\n1.5.0 (Mar 24, 2022)\n====================\n\n- Fix documentation (`#163 <https://github.com/gvalkov/python-evdev/pull/163>`_, `#160 <https://github.com/gvalkov/python-evdev/pull/160>`_).\n\n- Re-enable TTY echo at evtest exit (`#155 <https://github.com/gvalkov/python-evdev/pull/155>`_).\n\n- Fix ``ImportError: sys.meta_path is None, Python is likely shutting down`` (`#154 <https://github.com/gvalkov/python-evdev/pull/154>`_).\n\n- Closing the input device file descriptor in ``InputDevice.close()`` now\n  happens in the main thread, instead of in a new thread (reverts `#146\n  <https://github.com/gvalkov/python-evdev/pull/146>`_).\n\n- Fix ``util.find_ecodes_by_regex`` not working across all supported Python versions (`#152 <https://github.com/gvalkov/python-evdev/pull/152>`_).\n\n\n\n1.4.0 (Jan 16, 2021)\n====================\n\n- Fix ``InputDevice.set_absinfo`` to allow setting parameters to zero.\n\n- Fix off-by-one in ``ioctl_EVIOCG_bits``, which causes value at the end of the\n  list to not be reported back (`#131 <https://github.com/gvalkov/python-evdev/pull/131>`_).\n\n- Fix ``set_absinfo`` to allow setting parameters to zero (`#128 <https://github.com/gvalkov/python-evdev/pull/128>`_).\n\n- Fix leak when returning ``BlockingIOError`` from a read (`#143 <https://github.com/gvalkov/python-evdev/pull/143>`_).\n\n- Fix \"There is no current event loop in thread\" error for non asyncio code\n  (`#146 <https://github.com/gvalkov/python-evdev/pull/146>`_).\n\n- Prevent ``InputDevice`` destructor from blocking (`#145 <https://github.com/gvalkov/python-evdev/pull/145>`_).\n\n- Add missing return codes to ``os.strerror()`` calls and fix force feedback\n  example in docs (`#138 <https://github.com/gvalkov/python-evdev/pull/137>`_).\n\n- Add the ``util.find_ecodes_by_regex()`` helper function.\n\n\n\n1.3.0 (Jan 12, 2020)\n====================\n\n- Fix build on 32bit arches with 64bit time_t\n\n- Add functionality to query device properties. See ``InputDevice.input_props``\n  and the ``input_props`` argument to ``Uinput``.\n\n- ``KeyEvent`` received an ``allow_unknown`` constructor argument, which\n  determines what will happen when an event code cannot be mapped to a keycode.\n  The default and behavior so far has been to raise ``KeyError``. If set to\n  ``True``, the keycode will be set to the event code formatted as a hex number.\n\n- Add ``InputDevice.set_absinfo()`` and ``InputDevice.absinfo()``.\n\n- Instruct the asyncio event loop to stop monitoring the fd of the input device\n  when the device is closed.\n\n\n1.2.0 (Apr 7, 2019)\n====================\n\n- Add UInput support for the resolution parameter in AbsInfo. This brings\n  support for the new method of uinput device setup, which was `introduced in\n  Linux 4.5`_ (thanks to `@LinusCDE`_).\n\n- Vendor and product identifiers can be greater or equal to `0x8000` (thanks\n  `@ivaradi`_).\n\n\n1.1.2 (Sep 1, 2018)\n====================\n\n- Fix installation on kernels <= 4.4.\n\n- Fix uinput creation ignoring absinfo settings.\n\n\n1.1.0 (Aug 27, 2018)\n====================\n\n- Add support for handling force-feedback effect uploads (many thanks to `@ndreys`_).\n\n- Fix typo preventing ff effects that need left coefficients from working.\n\n\n1.0.0 (Jun 02, 2018)\n====================\n\n- Prevent ``Uinput`` device creation raising ``Objects/longobject.c:415: bad\n  argument to internal function`` when a non-complete ``AbsInfo`` structure is\n  passed. All missing ``AbsInfo`` fields are set to 0.\n\n- Fix ``Uinput`` device creation raising ``KeyError`` when a capability filtered\n  by default is not present.\n\n- The ``InputDevice.fn`` attribute was deprecated in favor of ``InputDevice.path``.\n  Using the former will show a ``DeprecationWarning``, but would otherwise continue\n  working as before.\n\n- Fix ``InputDevice`` comparison raising ``AttributeError`` due to a non-existant\n  ``path`` attribute.\n\n- Fix asyncio support in Python 3.5+.\n\n- Uploading FF effect now works both on Python 2.7 and Python 3+.\n\n- Remove the ``asyncore`` example from the tutorial.\n\n\n0.8.1 (Mar 24, 2018)\n====================\n\n- Fix Python 2 compatibility issue in with ``Uinput.from_device``.\n\n- Fix minor `evdev.evtest` formatting issue.\n\n\n0.8.0 (Mar 22, 2018)\n====================\n\n- Fix ``InputDevice`` comparison on Python 2.\n\n- The device path is now considered when comparing two devices.\n\n- Fix ``UInput.from_device`` not correctly merging the capabilities of\n  selected devices.\n\n- The list of excluded event types in ``UInput.from_device`` is now\n  configurable. For example::\n\n    UInput.from_device(dev, filtered_types=(EV_SYN, EV_FF))\n\n  In addition, ``ecodes.EV_FF`` is now excluded by default.\n\n- Add a context manager for grabbing access to a device -\n  ``InputDevice.grab_context``. For example::\n\n    with dev.grab_context():\n        pass\n\n- Add the ``InputDevice.uniq`` attribute, which contains the unique identifier\n  of the device. As with ``phys``, this attribute may be empty (i.e. `''`).\n\n\n0.7.0 (Jun 16, 2017)\n====================\n\n- ``InputDevice`` now accepts objects that support the path protocol.\n  For example::\n\n    pth = pathlib.Path('/dev/input/event0')\n    dev = evdev.InputDevice(pth)\n\n- Support path protocol in ``InputDevice``. This means that ``InputDevice``\n  instances can be passed to callers that expect a ``os.PathLike`` object.\n\n- Exceptions raised during ``InputDevice.async_read()`` (and similar) are now\n  handled properly (i.e. an exception is set on the returned future instead of\n  leaking that exception into the event loop) (Fixes `#67`_).\n\n\n0.6.4 (Oct 07, 2016)\n====================\n\n- Exclude ``ecodes.c`` from source distribution (Fixes `#63`_).\n\n\n0.6.3 (Oct 06, 2016)\n====================\n\n- Add the ``UInput.from_device`` class method, which allows uinput device to be\n  created with the capabiltiies of one or more existing input devices::\n\n    ui = UInput.from_device('/dev/input1', '/dev/input2', **constructor_kwargs)\n\n- Add the ``build_ecodes`` distutils command, which generates the ``ecodes.c``\n  extension module. The new way of overwriting the evdev header locations is::\n\n    python setup.py build \\\n      build_ecodes --evdev-headers path/input.h:path/input-event-codes.h \\\n      build_ext --include-dirs  path/ \\\n      install\n\n  The ``build*`` and ``install`` commands no longer have to be part of the same\n  command-line (i.e. running ``install`` will reuse the outputs of the last\n  ``build``).\n\n\n0.6.1 (Jun 04, 2016)\n====================\n\n- Disable tty echoing while evtest is running.\n- Allow evtest to listen to more than one devices.\n\n- The setup.py script now allows the location of the input header files to be\n  overwritten. For example::\n\n    python setup.py build_ext \\\n      --evdev-headers path/input.h:path/input-event-codes.h \\\n      --include-dirs  path/ \\\n      install\n\n\n0.6.0 (Feb 14, 2016)\n====================\n\n- Asyncio and async/await support (many thanks to `@paulo-raca`_).\n- Add the ability to set the `phys` property of uinput devices (thanks `@paulo-raca`_).\n- Add a generic :func:`InputDevice.set` method (thanks `@paulo-raca`_).\n- Distribute the evtest script along with evdev.\n- Fix issue with generating :mod:`ecodes.c` in recent kernels (``>= 4.4.0``).\n- Fix absinfo item indexes in :func:`UInput.uinput_create()` (thanks `@forsenonlhaimaisentito`_).\n- More robust comparison of :class:`InputDevice` objects (thanks `@isia`_).\n\n\n0.5.0 (Jun 16, 2015)\n====================\n\n- Write access to the input device is no longer mandatory. Evdev will\n  first try to open the device for reading and writing and fallback to\n  read-only. Methods that require write access (e.g. :func:`set_led()`)\n  will raise :class:`EvdevError` if the device is open only for reading.\n\n\n0.4.7 (Oct 07, 2014)\n====================\n\n- Fallback to distutils if setuptools is not available.\n\n\n0.4.6 (Oct 07, 2014)\n====================\n\n- Rework documentation and docstrings once more.\n\n- Fix install on Python 3.4 (works around issue21121_).\n\n- Fix :func:`ioctl()` requested buffer size (thanks Jakub Wojciech Klama).\n\n\n0.4.5 (Jul 06, 2014)\n====================\n\n- Add method for returning a list of the currently active keys -\n  :func:`InputDevice.active_keys()` (thanks `@spasche`_).\n\n- Fix a potential buffer overflow in :func:`ioctl_capabilities()` (thanks `@spasche`_).\n\n\n0.4.4 (Jun 04, 2014)\n====================\n\n- Calling :func:`InputDevice.read_one()` should always return ``None``,\n  when there is nothing to be read, even in case of a ``EAGAIN`` errno\n  (thanks JPP).\n\n\n0.4.3 (Dec 19, 2013)\n====================\n\n- Silence :class:`OSError` in destructor (thanks `@polyphemus`_).\n\n- Make :func:`InputDevice.close()` work in cases in which stdin (fd 0)\n  has been closed (thanks `@polyphemus`_).\n\n\n0.4.2 (Dec 13, 2013)\n====================\n\n- Rework documentation and docstrings.\n\n- Call :func:`InputDevice.close()` from :func:`InputDevice.__del__()`.\n\n\n0.4.1 (Jul 24, 2013)\n====================\n\n- Fix reference counting in :func:`InputDevice.device_read()`,\n  :func:`InputDevice.device_read_many()` and :func:`ioctl_capabilities`.\n\n\n0.4.0 (Jul 01, 2013)\n====================\n\n- Add ``FF_*`` and ``FF_STATUS`` codes to :func:`ecodes` (thanks `@bgilbert`_).\n\n- Reverse event code mappings (``ecodes.{KEY,FF,REL,ABS}`` and etc.)\n  will now map to a list of codes, whenever a value corresponds to\n  multiple codes::\n\n    >>> ecodes.KEY[152]\n    ... ['KEY_COFFEE', 'KEY_SCREENLOCK']\n    >>> ecodes.KEY[30]\n    ... 'KEY_A'\n\n- Set the state of a LED through :func:`InputDevice.set_led()` (thanks\n  `@accek`_).\n\n- Open :attr:`InputDevice.fd` in ``O_RDWR`` mode from now on.\n\n- Fix segfault in :func:`InputDevice.device_read_many()` (thanks `@bgilbert`_).\n\n\n0.3.3 (May 29, 2013)\n====================\n\n- Raise :class:`IOError` from :func:`InputDevice.device_read()` and\n  :func:`InputDevice.device_read_many()` when :func:`InputDevice.read()`\n  fails.\n\n- Several stability and style changes (thank you debian code reviewers).\n\n\n0.3.2 (Apr 05, 2013)\n====================\n\n- Fix vendor id and product id order in :func:`DeviceInfo` (thanks `@kived`_).\n\n\n0.3.1 (Nov 23, 2012)\n====================\n\n- :func:`InputDevice.read()` will return an empty tuple if the device\n  has nothing to offer (instead of segfaulting).\n\n- Exclude unnecessary package data in sdist and bdist.\n\n\n0.3.0 (Nov 06, 2012)\n====================\n\n- Add ability to set/get auto-repeat settings with ``EVIOC{SG}REP``.\n\n- Add :func:`InputDevice.version` - the value of ``EVIOCGVERSION``.\n\n- Add :func:`InputDevice.read_loop()`.\n\n- Add :func:`InputDevice.grab()` and :func:`InputDevice.ungrab()` -\n  exposes ``EVIOCGRAB``.\n\n- Add :func:`InputDevice.leds` - exposes ``EVIOCGLED``.\n\n- Replace :class:`DeviceInfo` class with a namedtuple.\n\n- Prevent :func:`InputDevice.read_one()` from skipping events.\n\n- Rename :class:`AbsData` to :class:`AbsInfo` (as in ``struct input_absinfo``).\n\n\n0.2.0 (Aug 22, 2012)\n====================\n\n- Add the ability to set arbitrary device capabilities on uinput\n  devices (defaults to all ``EV_KEY`` ecodes).\n\n- Add :attr:`UInput.device` which is an open :class:`InputDevice` to\n  the input device that uinput 'spawns'.\n\n- Add :func:`UInput.capabilities()` which is just a shortcut to\n  :func:`UInput.device.capabilities()`.\n\n- Rename :func:`UInput.write()` to :func:`UInput.write_event()`.\n\n- Add a simpler :func:`UInput.write(type, code, value)` method.\n\n- Make all :func:`UInput` constructor arguments optional (default\n  device name is now ``py-evdev-uinput``).\n\n- Add the ability to set ``absmin``, ``absmax``, ``absfuzz`` and\n  ``absflat`` when specifying the uinput device's capabilities.\n\n- Remove the ``nophys`` argument - if a device fails the\n  ``EVIOCGPHYS`` ioctl, phys will equal the empty string.\n\n- Make :func:`InputDevice.capabilities()` perform a ``EVIOCGABS``\n  ioctl for devices that support ``EV_ABS`` and return that info\n  wrapped in an ``AbsData`` namedtuple.\n\n- Split ``ioctl_devinfo`` into ``ioctl_devinfo`` and\n  ``ioctl_capabilities``.\n\n- Split :func:`UInput.uinput_open()` to :func:`UInput.uinput_open()`\n  and :func:`UInput.uinput_create()`\n\n- Add more uinput usage examples and documentation.\n\n- Rewrite uinput tests.\n\n- Remove ``mouserel`` and ``mouseabs`` from :class:`UInput`.\n\n- Tie the sphinx version and release to the distutils version.\n\n- Set 'methods-before-attributes' sorting in the docs.\n\n- Remove ``KEY_CNT`` and ``KEY_MAX`` from :func:`ecodes.keys`.\n\n\n0.1.1 (May 18, 2012)\n====================\n\n- Add ``events.keys``, which is a combination of all ``BTN_`` and\n  ``KEY_`` event codes.\n\n- ``ecodes.c`` was not generated when installing through ``pip``.\n\n\n0.1.0 (May 17, 2012)\n====================\n\n*Initial Release*\n\n.. _`@polyphemus`: https://github.com/polyphemus\n.. _`@bgilbert`: https://github.com/bgilbert\n.. _`@accek`: https://github.com/accek\n.. _`@kived`: https://github.com/kived\n.. _`@spasche`: https://github.com/spasche\n.. _`@isia`:    https://github.com/isia\n.. _`@forsenonlhaimaisentito`: https://github.com/forsenonlhaimaisentito\n.. _`@paulo-raca`: https://github.com/paulo-raca\n.. _`@ndreys`: https://github.com/ndreys\n.. _`@LinusCDE`: https://github.com/gvalkov/python-evdev/pulls/LinusCDE\n.. _`@ivaradi`: https://github.com/gvalkov/python-evdev/pull/104\n\n.. _`introduced in Linux 4.5`: https://github.com/torvalds/linux/commit/052876f8e5aec887d22c4d06e54aa5531ffcec75\n.. _issue21121: http://bugs.python.org/issue21121\n.. _`#63`:      https://github.com/gvalkov/python-evdev/issues/63\n.. _`#67`:      https://github.com/gvalkov/python-evdev/issues/67\n"
  },
  {
    "path": "docs/conf.py",
    "content": "import os\nimport sys\nimport sphinx_rtd_theme\n\n# Check if readthedocs is building us\non_rtd = os.environ.get(\"READTHEDOCS\", None) == \"True\"\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\n\n# Trick autodoc into running without having built the extension modules.\nif on_rtd:\n    with open(\"../src/evdev/_ecodes.py\", \"w\") as fh:\n        fh.write(\n            \"\"\"\nKEY = ABS = REL = SW = MSC = LED = REP = SND = SYN = FF = FF_STATUS = BTN_A = KEY_A = 1\nEV_KEY = EV_ABS = EV_REL = EV_SW = EV_MSC = EV_LED = EV_REP = 1\nEV_SND = EV_SYN = EV_FF  = EV_FF_STATUS = FF_STATUS = 1\nKEY_MAX, KEY_CNT = 1, 2\"\"\"\n        )\n\n    with open(\"../src/evdev/_input.py\", \"w\"):\n        pass\n    with open(\"../src/evdev/_uinput.py\", \"w\"):\n        pass\n\n\n# -- General configuration -----------------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n# needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be extensions\n# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.\nextensions = [\n    \"sphinx.ext.autodoc\",\n    \"sphinx.ext.viewcode\",\n    \"sphinx.ext.intersphinx\",\n    \"sphinx.ext.napoleon\",\n    \"sphinx_rtd_theme\",\n    \"sphinx_copybutton\",\n]\n\nautodoc_member_order = \"bysource\"\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = [\"_templates\"]\n\n# The suffix of source filenames.\nsource_suffix = \".rst\"\n\n# The encoding of source files.\n# source_encoding = 'utf-8-sig'\n\n# The master toctree document.\nmaster_doc = \"index\"\n\n# General information about the project.\nproject = \"python-evdev\"\ncopyright = \"2012-2025, Georgi Valkov and contributors\"\n\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n#\n# The full version, including alpha/beta/rc tags.\nrelease = \"1.9.3\"\n\n# The short X.Y version.\nversion = release\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n# language = None\n\n# There are two options for replacing |today|: either, you set today to some\n# non-false value, then it is used:\n# today = ''\n# Else, today_fmt is used as the format for a strftime call.\n# today_fmt = '%B %d, %Y'\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\nexclude_patterns = [\"_build\"]\n\n# The reST default role (used for this markup: `text`) to use for all documents.\n# default_role = None\n\n# If true, '()' will be appended to :func: etc. cross-reference text.\n# add_function_parentheses = True\n\n# If true, the current module name will be prepended to all description\n# unit titles (such as .. function::).\n# add_module_names = True\n\n# If true, sectionauthor and moduleauthor directives will be shown in the\n# output. They are ignored by default.\n# show_authors = False\n\n# The name of the Pygments (syntax highlighting) style to use.\n# pygments_style = 'sphinx'\n\n# A list of ignored prefixes for module index sorting.\n# modindex_common_prefix = []\n\n\n# -- Options for HTML output ---------------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\n\nhtml_theme = \"sphinx_rtd_theme\"\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\n# html_theme_options = {\"full_logo\" : True}\n\n# Add any paths that contain custom themes here, relative to this directory.\n\n# The name for this set of Sphinx documents.  If None, it defaults to\n# \"<project> v<release> documentation\".\nhtml_title = \"Python-evdev\"\n\n# A shorter title for the navigation bar.  Default is the same as html_title.\nhtml_short_title = \"evdev\"\n\n# The name of an image file (relative to this directory) to place at the top\n# of the sidebar.\n# html_logo = ''\n\n# The name of an image file (within the static path) to use as favicon of the\n# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32\n# pixels large.\n# html_favicon = None\n\n# Add any paths that contain custom static files (such as style sheets here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = [\"_static\"]\n\n# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,\n# using the given strftime format.\n# html_last_updated_fmt = '%b %d, %Y'\n\n# If true, SmartyPants will be used to convert quotes and dashes to\n# typographically correct entities.\n# html_use_smartypants = True\n\n# Custom sidebar templates, maps document names to template names.\n# html_sidebars = {}\n\n# Additional templates that should be rendered to pages, maps page names to\n# template names.\n# html_additional_pages = {}\n\n# If false, no module index is generated.\n# html_domain_indices = True\n\n# If false, no index is generated.\n# html_use_index = True\n\n# If true, the index is split into individual pages for each letter.\n# html_split_index = False\n\n# If true, links to the reST sources are added to the pages.\nhtml_show_sourcelink = True\n\n# If true, \"Created using Sphinx\" is shown in the HTML footer. Default is True.\n# html_show_sphinx = True\n\n# If true, \"(C) Copyright ...\" is shown in the HTML footer. Default is True.\n# html_show_copyright = True\n\n# If true, an OpenSearch description file will be output, and all pages will\n# contain a <link> tag referring to it.  The value of this option must be the\n# base URL from which the finished HTML is served.\n# html_use_opensearch = ''\n\n# This is the file name suffix for HTML files (e.g. \".xhtml\").\n# html_file_suffix = None\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = \"python-evdev-doc\"\n\n\n# -- Options for LaTeX output --------------------------------------------------\n\nlatex_elements = {\n    # The paper size ('letterpaper' or 'a4paper').\n    #'papersize': 'letterpaper',\n    # The font size ('10pt', '11pt' or '12pt').\n    #'pointsize': '10pt',\n    # Additional stuff for the LaTeX preamble.\n    #'preamble': '',\n}\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title, author, documentclass [howto/manual]).\nlatex_documents = [\n    (\"index\", \"python-evdev.tex\", \"evdev documentation\", \"Georgi Valkov\", \"manual\"),\n]\n\n# The name of an image file (relative to this directory) to place at the top of\n# the title page.\n# latex_logo = None\n\n# For \"manual\" documents, if this is true, then toplevel headings are parts,\n# not chapters.\n# latex_use_parts = False\n\n# If true, show page references after internal links.\n# latex_show_pagerefs = False\n\n# If true, show URL addresses after external links.\n# latex_show_urls = False\n\n# Documents to append as an appendix to all manuals.\n# latex_appendices = []\n\n# If false, no module index is generated.\n# latex_domain_indices = True\n\n\n# -- Options for manual page output --------------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [(\"index\", \"python-evdev\", \"python-evdev Documentation\", [\"Georgi Valkov\"], 1)]\n\n# If true, show URL addresses after external links.\n# man_show_urls = False\n\n\n# -- Options for Texinfo output ------------------------------------------------\n\n# Grouping the document tree into Texinfo files. List of tuples\n# (source start file, target name, title, author,\n#  dir menu entry, description, category)\ntexinfo_documents = [\n    (\n        \"index\",\n        \"python-evdev\",\n        \"python-evdev Documentation\",\n        \"Georgi Valkov\",\n        \"evdev\",\n        \"Bindings for the linux input handling subsystem.\",\n        \"Miscellaneous\",\n    ),\n]\n\nintersphinx_mapping = {\"python\": (\"http://docs.python.org/3\", None)}\n\n# Documents to append as an appendix to all manuals.\n# texinfo_appendices = []\n\n# If false, no module index is generated.\n# texinfo_domain_indices = True\n\n# How to display URL addresses: 'footnote', 'no', or 'inline'.\n# texinfo_show_urls = 'footnote'\n\n# Copybutton config\n# copybutton_prompt_text = r\">>> \"\n# copybutton_prompt_is_regexp = True\n# copybutton_only_copy_prompt_lines = True\n"
  },
  {
    "path": "docs/index.rst",
    "content": "Introduction\n------------\n\nThis package provides bindings to the generic input event interface in\nLinux. The *evdev* interface serves the purpose of passing events\ngenerated in the kernel directly to userspace through character\ndevices that are typically located in ``/dev/input/``.\n\nThis package also comes with bindings to *uinput*, the userspace input\nsubsystem. *Uinput* allows userspace programs to create and handle\ninput devices that can inject events directly into the input\nsubsystem.\n\nIn other words, *python-evdev* allows you to read and write input\nevents on Linux. An event can be a key or button press, a mouse\nmovement or a tap on a touchscreen.\n\n\n.. toctree::\n   :caption: Installation\n   :maxdepth: 2\n\n   install\n\n.. toctree::\n   :caption: Usage\n\n   usage\n   tutorial\n   apidoc\n\n.. toctree::\n   :caption: Project\n   :maxdepth: 2\n\n   scope\n   changelog\n\n\nLicense\n-------\n\nThis package is released under the terms of the `Revised BSD License`_.\n\n.. _`Revised BSD License`: https://raw.github.com/gvalkov/python-evdev/master/LICENSE\n.. _evdev:             http://svn.navi.cx/misc/trunk/python/evdev/\n"
  },
  {
    "path": "docs/install.rst",
    "content": "From an OS package\n==================\n\nPython-evdev has been packaged for the following distributions:\n\n.. raw:: html\n\n    <a href=\"https://repology.org/project/python:evdev/versions\">\n      <img src=\"https://repology.org/badge/vertical-allrepos/python:evdev.svg?exclude_sources=modules,site&exclude_unsupported=1\" alt=\"Packaging status\">\n    </a>\n\nConsult the documentation of your OS package manager for installation instructions.\n\n\nFrom source\n===========\n\nThe latest stable version of *python-evdev* can be installed from pypi_,\nprovided that you have a compiler, pip_ and the Python and Linux development\nheaders installed on your system. Installing these is distribution specific and\ntypically falls into one of the following:\n\nOn a Debian compatible OS:\n\n.. code-block:: bash\n\n    $ apt install python-dev python-pip gcc\n    $ apt install linux-headers-$(uname -r)\n\nOn a Redhat compatible OS:\n\n.. code-block:: bash\n\n    $ dnf install python-devel python-pip gcc\n    $ dnf install kernel-headers-$(uname -r)\n\nOn Arch Linux and derivatives:\n\n.. code-block:: bash\n\n    $ pacman -S core/linux-api-headers python-pip gcc\n\nOnce all OS dependencies are available, you may install *python-evdev* using\npip_, preferably in a [virtualenv]_:\n\n.. code-block:: bash\n\n    # Install globally (not recommended).\n    $ sudo python3 -m pip install evdev\n\n    # Install for the current user.\n    $ python3 -m pip install --user evdev\n\n    # Install in a virtual environment.\n    $ python3 -m venv abc\n    $ source abc/bin/activate\n    $ python3 -m pip install evdev\n\n\nSpecifying header locations\n---------------------------\n\nBy default, the setup script will look for the ``input.h`` and\n``input-event-codes.h`` [#f1]_ header files ``/usr/include/linux``.\n\nYou may use the ``--evdev-headers`` option to the ``build_ext`` setuptools\ncommand to the location of these header files. It accepts one or more\ncolon-separated paths. For example:\n\n.. code-block:: bash\n\n    $ python setup.py build_ext \\\n        --evdev-headers buildroot/input.h:buildroot/input-event-codes.h \\\n        --include-dirs  buildroot/ \\\n        install  # or any other command (e.g. develop, bdist, bdist_wheel)\n\n\nFrom a binary package\n=====================\n\nYou may choose to install a precompiled version of *python-evdev* from pypi. The\n`evdev-binary`_ package provides binary wheels that have been compiled on EL8\nagainst the 4.18.0 kernel headers.\n\n.. code-block:: bash\n\n    $ python3 -m pip install evdev-binary\n\nWhile the evdev interface is stable, the precompiled version may not be fully\ncompatible or expose all the features of your running kernel. For best results,\nit is recommended to use an OS package or to install from source.\n\n\n.. [#f1] ``input-event-codes.h`` is found only in recent kernel versions.\n.. _pypi:              http://pypi.python.org/pypi/evdev\n.. _evdev-binary:      http://pypi.python.org/pypi/evdev-binary\n.. _github:            https://github.com/gvalkov/python-evdev\n.. _pip:               http://pip.readthedocs.org/en/latest/installing.html\n.. _example:           https://github.com/gvalkov/python-evdev/tree/master/examples\n.. _virtualenv:        https://docs.python.org/3/library/venv.html\n"
  },
  {
    "path": "docs/scope.rst",
    "content": "Scope and status\n----------------\n\nPython-evdev exposes most of the more common interfaces defined in the evdev\nsubsystem. Reading and injecting events is well supported and has been tested\nwith nearly all event types.\n\nThe basic functionality for reading and uploading force-feedback events is\nthere, but it has not been exercised sufficiently. A major shortcoming of the\nuinput wrapper is that it does not support force-feedback devices at all (see\nissue `#23`_).\n\nSome characters, such as ``:`` (colon), cannot be easily injected (see issue\n`#7`_), Translating them into UInput events would require knowing the kernel\nkeyboard translation table, which is beyond the scope of python-evdev. Please\nlook into the following projects if you need more complete or convenient input\ninjection support.\n\n- python-uinput_\n- uinput-mapper_\n- pynput_\n- pygame_ (cross-platform)\n\n\n.. _python-uinput:     https://github.com/tuomasjjrasanen/python-uinput\n.. _uinput-mapper:     https://github.com/MerlijnWajer/uinput-mapper\n.. _pynput:            https://github.com/moses-palmer/pynput\n.. _pygame:            http://www.pygame.org/\n\n.. _`#7`:  https://github.com/gvalkov/python-evdev/issues/7\n.. _`#23`: https://github.com/gvalkov/python-evdev/pull/23\n"
  },
  {
    "path": "docs/tutorial.rst",
    "content": "Tutorial\n--------\n\n\nListing accessible event devices\n================================\n\n::\n\n    >>> import evdev\n\n    >>> devices = [evdev.InputDevice(path) for path in evdev.list_devices()]\n    >>> for device in devices:\n    >>>     print(device.path, device.name, device.phys)\n    /dev/input/event1    Dell Dell USB Keyboard   usb-0000:00:12.1-2/input0\n    /dev/input/event0    Dell USB Optical Mouse   usb-0000:00:12.0-2/input0\n\n\nListing device capabilities\n===========================\n\n::\n\n    >>> import evdev\n\n    >>> device = evdev.InputDevice('/dev/input/event0')\n    >>> print(device)\n    device /dev/input/event0, name \"Dell USB Optical Mouse\", phys \"usb-0000:00:12.0-2/input0\"\n\n    >>> device.capabilities()\n    ... { 0: [0, 1, 2], 1: [272, 273, 274, 275], 2: [0, 1, 6, 8], 4: [4] }\n\n    >>> device.capabilities(verbose=True)\n    ... { ('EV_SYN', 0): [('SYN_REPORT', 0), ('SYN_CONFIG', 1), ('SYN_MT_REPORT', 2)],\n    ...   ('EV_KEY', 1): [('BTN_MOUSE', 272), ('BTN_RIGHT', 273), ('BTN_MIDDLE', 274), ('BTN_SIDE', 275)], ...\n\n\nListing device capabilities (devices with absolute axes)\n========================================================\n\n::\n\n    >>> import evdev\n\n    >>> device = evdev.InputDevice('/dev/input/event7')\n    >>> print(device)\n    device /dev/input/event7, name \"Wacom Bamboo 2FG 4x5 Finger\", phys \"\"\n\n    >>> device.capabilities()\n    ... { 1: [272, 273, 277, 278, 325, 330, 333] ,\n    ...   3: [(0, AbsInfo(min=0, max=15360, fuzz=128, flat=0)),\n    ...       (1, AbsInfo(min=0, max=10240, fuzz=128, flat=0))] }\n\n    >>> device.capabilities(verbose=True)\n    ... { ('EV_KEY', 1): [('BTN_MOUSE', 272), ('BTN_RIGHT', 273), ...],\n    ...   ('EV_ABS', 3): [(('ABS_X', 0), AbsInfo(min=0, max=15360, fuzz=128, flat=0)),\n    ...                   (('ABS_Y', 1), AbsInfo(min=0, max=10240, fuzz=128, flat=0)),] }\n\n    >>> device.capabilities(absinfo=False)\n    ... { 1: [272, 273, 277, 278, 325, 330, 333],\n    ...   3: [0, 1, 47, 53, 54, 57] }\n\n\nGetting and setting LED states\n==============================\n\n::\n\n    >>> dev.leds(verbose=True)\n    ... [('LED_NUML', 0), ('LED_CAPSL', 1)]\n\n    >>> dev.leds()\n    ... [0, 1]\n\n    >>> dev.set_led(ecodes.LED_NUML, 1)  # enable numlock\n    >>> dev.set_led(ecodes.LED_NUML, 0)  # disable numlock\n\n\nGetting currently active keys\n=============================\n\n::\n\n    >>> dev.active_keys(verbose=True)\n    ... [('KEY_3', 4), ('KEY_LEFTSHIFT', 42)]\n\n    >>> dev.active_keys()\n    ... [4, 42]\n\n\nReading events\n==============\n\nReading events from a single device in an endless loop.\n\n::\n\n    >>> from evdev import InputDevice, categorize, ecodes\n    >>> dev = InputDevice('/dev/input/event1')\n\n    >>> print(dev)\n    device /dev/input/event1, name \"Dell Dell USB Keyboard\", phys \"usb-0000:00:12.1-2/input0\"\n\n    >>> for event in dev.read_loop():\n    ...     if event.type == ecodes.EV_KEY:\n    ...         print(categorize(event))\n    ... # pressing 'a' and holding 'space'\n    key event at 1337016188.396030, 30 (KEY_A), down\n    key event at 1337016188.492033, 30 (KEY_A), up\n    key event at 1337016189.772129, 57 (KEY_SPACE), down\n    key event at 1337016190.275396, 57 (KEY_SPACE), hold\n    key event at 1337016190.284160, 57 (KEY_SPACE), up\n\n\nReading events (using :mod:`asyncio`)\n======================================\n\n.. note::\n\n   This requires Python 3.5+ for the async/await keywords.\n\n\n::\n\n    >>> import asyncio\n    >>> from evdev import InputDevice\n\n    >>> dev = InputDevice('/dev/input/event1')\n\n    >>> async def main(dev):\n    ...     async for ev in dev.async_read_loop():\n    ...         print(repr(ev))\n\n    >>> asyncio.run(main(dev))\n    InputEvent(1527363738, 348740, 4, 4, 458792)\n    InputEvent(1527363738, 348740, 1, 28, 0)\n    InputEvent(1527363738, 348740, 0, 0, 0)\n\n\nReading events from multiple devices (using :mod:`select`)\n==========================================================\n\n::\n\n    >>> from evdev import InputDevice\n    >>> from select import select\n\n    # A mapping of file descriptors (integers) to InputDevice instances.\n    >>> devices = map(InputDevice, ('/dev/input/event1', '/dev/input/event2'))\n    >>> devices = {dev.fd: dev for dev in devices}\n\n    >>> for dev in devices.values(): print(dev)\n    device /dev/input/event1, name \"Dell Dell USB Keyboard\", phys \"usb-0000:00:12.1-2/input0\"\n    device /dev/input/event2, name \"Logitech USB Laser Mouse\", phys \"usb-0000:00:12.0-2/input0\"\n\n    >>> while True:\n    ...    r, w, x = select(devices, [], [])\n    ...    for fd in r:\n    ...        for event in devices[fd].read():\n    ...            print(event)\n    event at 1351116708.002230, code 01, type 02, val 01\n    event at 1351116708.002234, code 00, type 00, val 00\n    event at 1351116708.782231, code 04, type 04, val 458782\n    event at 1351116708.782237, code 02, type 01, val 01\n\n\nReading events from multiple devices (using :mod:`selectors`)\n=============================================================\n\nThis can also be achieved using the :mod:`selectors` module in Python 3.4:\n\n::\n\n   from evdev import InputDevice\n   from selectors import DefaultSelector, EVENT_READ\n\n   selector = DefaultSelector()\n\n   mouse = InputDevice('/dev/input/event1')\n   keybd = InputDevice('/dev/input/event2')\n\n   # This works because InputDevice has a `fileno()` method.\n   selector.register(mouse, EVENT_READ)\n   selector.register(keybd, EVENT_READ)\n\n   while True:\n       for key, mask in selector.select():\n           device = key.fileobj\n           for event in device.read():\n               print(event)\n\n\nReading events from multiple devices (using :mod:`asyncio`)\n===========================================================\n\nYet another possibility is the :mod:`asyncio` module from Python 3.4:\n\n::\n\n    import asyncio, evdev\n\n    @asyncio.coroutine\n    def print_events(device):\n        while True:\n            events = yield from device.async_read()\n            for event in events:\n                print(device.path, evdev.categorize(event), sep=': ')\n\n    mouse = evdev.InputDevice('/dev/input/eventX')\n    keybd = evdev.InputDevice('/dev/input/eventY')\n\n    for device in mouse, keybd:\n        asyncio.async(print_events(device))\n\n    loop = asyncio.get_event_loop()\n    loop.run_forever()\n\nSince Python 3.5, the `async/await`_ syntax makes this even simpler:\n\n::\n\n    import asyncio, evdev\n\n    mouse = evdev.InputDevice('/dev/input/event4')\n    keybd = evdev.InputDevice('/dev/input/event5')\n\n    async def print_events(device):\n        async for event in device.async_read_loop():\n            print(device.path, evdev.categorize(event), sep=': ')\n\n    for device in mouse, keybd:\n        asyncio.ensure_future(print_events(device))\n\n    loop = asyncio.get_event_loop()\n    loop.run_forever()\n\n\nAccessing evdev constants\n=========================\n\n::\n\n    >>> from evdev import ecodes\n\n    >>> ecodes.KEY_A, ecodes.ecodes['KEY_A']\n    ... (30, 30)\n    >>> ecodes.KEY[30]\n    ... 'KEY_A'\n    >>> ecodes.bytype[ecodes.EV_KEY][30]\n    ... 'KEY_A'\n    >>> ecodes.KEY[152]  # a single value may correspond to multiple codes\n    ... ['KEY_COFFEE', 'KEY_SCREENLOCK']\n\n\nSearching event codes by regex\n==============================\n\n::\n\n    >>> from evdev import util\n\n    >>> res = util.find_ecodes_by_regex(r'(ABS|KEY)_BR(AKE|EAK)')\n    >>> res\n    ... {1: [411], 3: [10]}\n    >>> util.resolve_ecodes_dict(res)\n    ... {('EV_KEY', 1): [('KEY_BREAK', 411)], ('EV_ABS', 3): [('ABS_BRAKE', 10)]}\n\n\nGetting exclusive access to a device\n====================================\n\n::\n\n    >>> dev.grab()  # become the sole recipient of all incoming input events\n    >>> dev.ungrab()\n\nThis functionality is also available as a context manager.\n\n::\n\n    >>> with dev.grab_context():\n    ...     pass\n\n\nAssociating classes with event types\n====================================\n\n::\n\n    >>> from evdev import categorize, event_factory, ecodes\n\n    >>> class SynEvent:\n    ...     def __init__(self, event):\n    ...         ...\n\n    >>> event_factory[ecodes.EV_SYN] = SynEvent\n\nSee :mod:`events <evdev.events.event_factory>` for more information.\n\nInjecting input\n===============\n\n::\n\n    >>> from evdev import UInput, ecodes as e\n\n    >>> ui = UInput()\n\n    >>> # accepts only KEY_* events by default\n    >>> ui.write(e.EV_KEY, e.KEY_A, 1)  # KEY_A down\n    >>> ui.write(e.EV_KEY, e.KEY_A, 0)  # KEY_A up\n    >>> ui.syn()\n\n    >>> ui.close()\n\n\nInjecting events (using a context manager)\n==========================================\n\n::\n\n    >>> ev = InputEvent(1334414993, 274296, ecodes.EV_KEY, ecodes.KEY_A, 1)\n    >>> with UInput() as ui:\n    ...    ui.write_event(ev)\n    ...    ui.syn()\n\n\nSpecifying ``uinput`` device options\n====================================\n\n.. note::\n\n   ``ecodes.EV_SYN`` cannot be in the ``cap`` dictionary or the device will not be created.\n\n::\n\n    >>> from evdev import UInput, AbsInfo, ecodes as e\n\n    >>> cap = {\n    ...     e.EV_KEY : [e.KEY_A, e.KEY_B],\n    ...     e.EV_ABS : [\n    ...         (e.ABS_X, AbsInfo(value=0, min=0, max=255,\n    ...                           fuzz=0, flat=0, resolution=0)),\n    ...         (e.ABS_Y, AbsInfo(0, 0, 255, 0, 0, 0)),\n    ...         (e.ABS_MT_POSITION_X, (0, 128, 255, 0)) ]\n    ... }\n\n    >>> ui = UInput(cap, name='example-device', version=0x3)\n    >>> print(ui)\n    name \"example-device\", bus \"BUS_USB\", vendor \"0001\", product \"0001\", version \"0003\"\n    event types: EV_KEY EV_ABS EV_SYN\n\n    >>> print(ui.capabilities())\n    {0: [0, 1, 3],\n     1: [30, 48],\n     3: [(0,  AbsInfo(value=0, min=0, max=0,   fuzz=255, flat=0, resolution=0)),\n         (1,  AbsInfo(value=0, min=0, max=0,   fuzz=255, flat=0, resolution=0)),\n         (53, AbsInfo(value=0, min=0, max=255, fuzz=128, flat=0, resolution=0))]}\n\n    >>> # move mouse cursor\n    >>> ui.write(e.EV_ABS, e.ABS_X, 20)\n    >>> ui.write(e.EV_ABS, e.ABS_Y, 20)\n    >>> ui.syn()\n\n\nCreate ``uinput`` device with capabilities of another device\n================================================================\n\n::\n\n    >>> from evdev import UInput, InputDevice\n\n    >>> mouse = InputDevice('/dev/input/event1')\n    >>> keybd = '/dev/input/event2'\n\n    >>> ui = UInput.from_device(mouse, keybd, name='keyboard-mouse-device')\n    >>> ui.capabilities(verbose=True).keys()\n    dict_keys([('EV_LED', 17), ('EV_KEY', 1), ('EV_SYN', 0), ('EV_REL', 2), ('EV_MSC', 4)])\n\n\n.. _`async/await`:  https://docs.python.org/3/library/asyncio-task.html\n\nCreate ``uinput`` device capable of receiving FF-effects\n========================================================\n\n::\n\n    import asyncio\n    from evdev import UInput, categorize, ecodes\n\n    cap = {\n       ecodes.EV_FF:  [ecodes.FF_RUMBLE ],\n       ecodes.EV_KEY: [ecodes.KEY_A, ecodes.KEY_B]\n    }\n\n    ui = UInput(cap, name='test-controller', version=0x3)\n\n    async def print_events(device):\n        async for event in device.async_read_loop():\n            print(categorize(event))\n\n            # Wait for an EV_UINPUT event that will signal us that an\n            # effect upload/erase operation is in progress.\n            if event.type != ecodes.EV_UINPUT:\n                continue\n\n            if event.code == ecodes.UI_FF_UPLOAD:\n                upload = device.begin_upload(event.value)\n                upload.retval = 0\n\n                print(f'[upload] effect_id: {upload.effect.id}, type: {upload.effect.type}')\n                device.end_upload(upload)\n\n            elif event.code == ecodes.UI_FF_ERASE:\n                erase = device.begin_erase(event.value)\n                print(f'[erase] effect_id {erase.effect_id}')\n\n                erase.retval = 0\n                device.end_erase(erase)\n\n    asyncio.ensure_future(print_events(ui))\n    loop = asyncio.get_event_loop()\n    loop.run_forever()\n\n\nInjecting an FF-event into first FF-capable device found\n========================================================\n\n::\n\n    from evdev import ecodes, InputDevice, ff, list_devices\n    import time\n\n    # Find first EV_FF capable event device (that we have permissions to use).\n    for name in list_devices():\n        dev = InputDevice(name)\n        if ecodes.EV_FF in dev.capabilities():\n            break\n\n    rumble = ff.Rumble(strong_magnitude=0x0000, weak_magnitude=0xffff)\n    effect_type = ff.EffectType(ff_rumble_effect=rumble)\n    duration_ms = 1000\n\n    effect = ff.Effect(\n        ecodes.FF_RUMBLE, -1, 0,\n        ff.Trigger(0, 0),\n        ff.Replay(duration_ms, 0),\n        effect_type\n    )\n\n    repeat_count = 1\n    effect_id = dev.upload_effect(effect)\n    dev.write(ecodes.EV_FF, effect_id, repeat_count)\n    time.sleep(duration_ms / 1000) \n    dev.erase_effect(effect_id)\n\n\nForwarding force-feedback from uinput to a real device\n======================================================\n\n::\n\n    import evdev\n    from evdev import ecodes as e\n\n    # Find first EV_FF capable event device (that we have permissions to use).\n    for name in evdev.list_devices():\n        dev = evdev.InputDevice(name)\n        if e.EV_FF in dev.capabilities():\n            break\n    # To ensure forwarding works correctly it is important that `max_effects` \n    # of the uinput device is <= `dev.ff_effects_count`.\n    # `from_device()` will do this automatically, but in some situations you may \n    # want to set the `max_effects` parameter manually, such as when using `Uinput()`.\n    # `filtered_types` is specified as by default EV_FF events are filtered\n    uinput = evdev.UInput.from_device(dev, filtered_types=[e.EV_SYN])\n\n    # Keeps track of which effects have been uploaded to the device\n    effects = set()\n\n    for event in uinput.read_loop():\n        \n        # Handle the special uinput events\n        if event.type == e.EV_UINPUT:\n\n            if event.code == e.UI_FF_UPLOAD:\n                upload = uinput.begin_upload(event.value)\n\n                # Checks if this is a new effect\n                if upload.effect.id not in effects:\n                    effects.add(upload.effect.id)\n                    # Setting id to 1 indicates that a new effect must be allocated\n                    upload.effect.id = -1\n\n                dev.upload_effect(upload.effect)\n                upload.retval = 0\n                uinput.end_upload(upload)\n                \n            elif event.code == e.UI_FF_ERASE:\n                erase = uinput.begin_erase(event.value)\n                erase.retval = 0\n                dev.erase_effect(erase.effect_id)\n                effects.remove(erase.effect_id)\n                uinput.end_erase(erase)\n        \n        # Forward writes to actual rumble device.\n        elif event.type == e.EV_FF:\n            dev.write(event.type, event.code, event.value)\n"
  },
  {
    "path": "docs/usage.rst",
    "content": "Quick Start\n-----------\n\n\nListing accessible event devices\n================================\n\n::\n\n    >>> import evdev\n\n    >>> devices = [evdev.InputDevice(path) for path in evdev.list_devices()]\n    >>> for device in devices:\n    ...    print(device.path, device.name, device.phys)\n    /dev/input/event1    USB Keyboard        usb-0000:00:12.1-2/input0\n    /dev/input/event0    USB Optical Mouse   usb-0000:00:12.0-2/input0\n\n.. note::\n\n   If you do not see any devices, ensure that your user is in the\n   correct group (typically ``input``) to have read/write access.\n\n\nReading events from a device\n============================\n\n::\n\n    >>> import evdev\n\n    >>> device = evdev.InputDevice('/dev/input/event1')\n    >>> print(device)\n    device /dev/input/event1, name \"USB Keyboard\", phys \"usb-0000:00:12.1-2/input0\"\n\n    >>> for event in device.read_loop():\n    ...     if event.type == evdev.ecodes.EV_KEY:\n    ...         print(evdev.categorize(event))\n    ... # pressing 'a' and holding 'space'\n    key event at 1337016188.396030, 30 (KEY_A), down\n    key event at 1337016188.492033, 30 (KEY_A), up\n    key event at 1337016189.772129, 57 (KEY_SPACE), down\n    key event at 1337016190.275396, 57 (KEY_SPACE), hold\n    key event at 1337016190.284160, 57 (KEY_SPACE), up\n\n\nAccessing event codes\n=====================\n\nThe ``evdev.ecodes`` module provides reverse and forward mappings between the\nnames and values of the event subsystem constants.\n\n::\n\n    >>> from evdev import ecodes\n\n    >>> ecodes.KEY_A\n    ... 30\n    >>> ecodes.ecodes['KEY_A']\n    ... 30\n    >>> ecodes.KEY[30]\n    ... 'KEY_A'\n    >>> ecodes.bytype[ecodes.EV_KEY][30]\n    ... 'KEY_A'\n\n    # A single value may correspond to multiple event codes.\n    >>> ecodes.KEY[152]\n    ... ['KEY_COFFEE', 'KEY_SCREENLOCK']\n\n\nListing and monitoring input devices\n====================================\n\nThe *python-evdev* package also comes with a small command-line program for\nlisting and monitoring input devices:\n\n.. code-block:: bash\n\n   $ python -m evdev.evtest\n"
  },
  {
    "path": "examples/udev-example.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"\nThis is an example of using pyudev[1] alongside evdev.\n[1]: https://pyudev.readthedocs.org/\n\"\"\"\n\nimport functools\nimport pyudev\n\nfrom select import select\nfrom evdev import InputDevice\n\ncontext = pyudev.Context()\nmonitor = pyudev.Monitor.from_netlink(context)\nmonitor.filter_by(subsystem=\"input\")\nmonitor.start()\n\nfds = {monitor.fileno(): monitor}\nfinalizers = []\n\nwhile True:\n    r, w, x = select(fds, [], [])\n\n    if monitor.fileno() in r:\n        r.remove(monitor.fileno())\n\n        for udev in iter(functools.partial(monitor.poll, 0), None):\n            # we're only interested in devices that have a device node\n            # (e.g. /dev/input/eventX)\n            if not udev.device_node:\n                break\n\n            # find the device we're interested in and add it to fds\n            for name in (i[\"NAME\"] for i in udev.ancestors if \"NAME\" in i):\n                # I used a virtual input device for this test - you\n                # should adapt this to your needs\n                if \"py-evdev-uinput\" in name:\n                    if udev.action == \"add\":\n                        print(\"Device added: %s\" % udev)\n                        fds[dev.fd] = InputDevice(udev.device_node)\n                        break\n                    if udev.action == \"remove\":\n                        print(\"Device removed: %s\" % udev)\n\n                        def helper():\n                            global fds\n                            fds = {monitor.fileno(): monitor}\n\n                        finalizers.append(helper)\n                        break\n\n    for fd in r:\n        dev = fds[fd]\n        for event in dev.read():\n            print(event)\n\n    for i in range(len(finalizers)):\n        finalizers.pop()()\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\"setuptools>=77.0\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[project]\nname = \"evdev\"\nversion = \"1.9.3\"\ndescription = \"Bindings to the Linux input handling subsystem\"\nkeywords = [\"evdev\", \"input\", \"uinput\"]\nreadme = \"README.md\"\nlicense = \"BSD-3-Clause\"\nrequires-python = \">=3.9\"\nauthors = [\n  { name=\"Georgi Valkov\", email=\"georgi.t.valkov@gmail.com\" },\n]\nmaintainers = [\n  { name=\"Tobi\", email=\"proxima@sezanzeb.de\" },\n]\nclassifiers = [\n    \"Development Status :: 5 - Production/Stable\",\n    \"Programming Language :: Python :: 3\",\n    \"Operating System :: POSIX :: Linux\",\n    \"Intended Audience :: Developers\",\n    \"Topic :: Software Development :: Libraries\",\n    \"Programming Language :: Python :: Implementation :: CPython\",\n]\n\n[project.urls]\n\"Homepage\" = \"https://github.com/gvalkov/python-evdev\"\n\n[tool.ruff]\nline-length = 120\n\n[tool.ruff.lint]\nignore = [\"E265\", \"E241\", \"F403\", \"F401\", \"E401\", \"E731\"]\n\n[tool.bumpversion]\ncurrent_version = \"1.9.3\"\ncommit = true\ntag = true\nallow_dirty = true\n\n[[tool.bumpversion.files]]\nfilename = \"pyproject.toml\"\n\n[[tool.bumpversion.files]]\nfilename = \"docs/conf.py\"\n\n[tool.pylint.'MESSAGES CONTROL']\ndisable = \"\"\"\n    no-member,\n\"\"\"\n\n[tool.pylint.typecheck]\ngenerated-members = [\"evdev.ecodes.*\"]\nignored-modules= [\"evdev._*\"]\n"
  },
  {
    "path": "requirements-dev.txt",
    "content": "pytest\nSphinx\nsphinx-copybutton ~= 0.5.2\nsphinx-rtd-theme\nruff\nbump-my-version ~= 0.17.4\nbuild\ntwine\ncibuildwheel\nsetuptools\n"
  },
  {
    "path": "scripts/build-binary.sh",
    "content": "#!/usr/bin/env bash\n\nset -o allexport\nset -o nounset\n\nCIBW_MANYLINUX_X86_64_IMAGE=\"manylinux_2_28\"\nCIBW_MANYLINUX_I686_IMAGE=\"manylinux_2_28\"\nCIBW_CONTAINER_ENGINE=\"podman\"\nCIBW_SKIP=\"cp36-*\"\nCIBW_ARCHS_LINUX=\"auto64\"\nCIBW_BEFORE_ALL_LINUX=./scripts/cibw-before.sh\nCIBW_TEST_COMMAND=\"python -c 'import evdev; print(evdev)'\"\nCIBW_ENVIRONMENT=\"PACKAGE_NAME=evdev-binary\"\n\nexec cibuildwheel "
  },
  {
    "path": "scripts/cibw-before.sh",
    "content": "#!/usr/bin/env bash\n\n\nif [ -n \"$PACKAGE_NAME\" ]; then\n    sed -i -re 's,^(name = \")evdev(\"),\\1'${PACKAGE_NAME}'\\2,' pyproject.toml\nfi"
  },
  {
    "path": "setup.py",
    "content": "import os\nimport sys\nimport shutil\nimport textwrap\nimport platform\nfrom pathlib import Path\nfrom subprocess import run\n\nfrom setuptools import setup, Extension, Command\nfrom setuptools.command import build_ext as _build_ext\n\n\ncurdir = Path(__file__).resolve().parent\necodes_c_path = curdir / \"src/evdev/ecodes.c\"\n\n\ndef create_ecodes(headers=None, reproducible=False):\n    if not headers:\n        include_paths = set()\n        cpath = os.environ.get(\"CPATH\", \"\").strip()\n        c_inc_path = os.environ.get(\"C_INCLUDE_PATH\", \"\").strip()\n\n        if cpath:\n            include_paths.update(cpath.split(\":\"))\n        if c_inc_path:\n            include_paths.update(c_inc_path.split(\":\"))\n\n        include_paths.add(\"/usr/include\")\n        if platform.system().lower() == \"freebsd\":\n            files = [\"dev/evdev/input.h\", \"dev/evdev/input-event-codes.h\", \"dev/evdev/uinput.h\"]\n        else:\n            files = [\"linux/input.h\", \"linux/input-event-codes.h\", \"linux/uinput.h\"]\n\n        headers = [os.path.join(path, file) for path in include_paths for file in files]\n\n    headers = [header for header in headers if os.path.isfile(header)]\n    if not headers:\n        msg = \"\"\"\\\n        The 'linux/input.h' and 'linux/input-event-codes.h' include files\n        are missing. You will have to install the kernel header files in\n        order to continue:\n\n            dnf install kernel-headers-$(uname -r)\n            apt-get install linux-headers-$(uname -r)\n            emerge sys-kernel/linux-headers\n            pacman -S kernel-headers\n\n        In case they are installed in a non-standard location, you may use\n        the '--evdev-headers' option to specify one or more colon-separated\n        paths. For example:\n\n            python setup.py \\\\\n              build \\\\\n              build_ecodes --evdev-headers path/input.h:path/input-event-codes.h \\\\\n              build_ext --include-dirs path/ \\\\\n              install\n\n        If you want to avoid building this package from source, then please consider\n        installing the `evdev-binary` package instead. Keep in mind that it may not be\n        fully compatible with, or support all the features of your current kernel.\n        \"\"\"\n\n        sys.stderr.write(textwrap.dedent(msg))\n        sys.exit(1)\n\n    print(\"writing %s (using %s)\" % (ecodes_c_path, \" \".join(headers)))\n    with ecodes_c_path.open(\"w\") as fh:\n        cmd = [sys.executable, \"src/evdev/genecodes_c.py\"]\n        if reproducible:\n            cmd.append(\"--reproducible\")\n        cmd.extend([\"--ecodes\", *headers])\n        run(cmd, check=True, stdout=fh)\n\n\nclass build_ecodes(Command):\n    description = \"generate ecodes.c\"\n\n    user_options = [\n        (\"evdev-headers=\", None, \"colon-separated paths to input subsystem headers\"),\n        (\"reproducible\", None, \"hide host details (host/paths) to create a reproducible output\"),\n    ]\n\n    def initialize_options(self):\n        self.evdev_headers = None\n        self.reproducible = False\n\n    def finalize_options(self):\n        if self.evdev_headers:\n            self.evdev_headers = self.evdev_headers.split(\":\")\n        if self.reproducible is None:\n            self.reproducible = False\n\n    def run(self):\n        create_ecodes(self.evdev_headers, reproducible=self.reproducible)\n\n\nclass build_ext(_build_ext.build_ext):\n    def has_ecodes(self):\n        if ecodes_c_path.exists():\n            print(\"ecodes.c already exists ... skipping build_ecodes\")\n            return False\n        return True\n\n    def generate_ecodes_py(self):\n        ecodes_py = Path(self.build_lib) / \"evdev/ecodes.py\"\n        print(f\"writing {ecodes_py}\")\n        with ecodes_py.open(\"w\") as fh:\n            cmd = [sys.executable, \"-B\", \"src/evdev/genecodes_py.py\"]\n            res = run(cmd, env={\"PYTHONPATH\": self.build_lib}, stdout=fh)\n\n        if res.returncode != 0:\n            print(f\"failed to generate static {ecodes_py} - will use ecodes_runtime.py\")\n            shutil.copy(\"src/evdev/ecodes_runtime.py\", ecodes_py)\n\n    def run(self):\n        for cmd_name in self.get_sub_commands():\n            self.run_command(cmd_name)\n        _build_ext.build_ext.run(self)\n        self.generate_ecodes_py()\n\n    sub_commands = [(\"build_ecodes\", has_ecodes)] + _build_ext.build_ext.sub_commands\n\n\ncflags = [\"-std=c99\", \"-Wno-error=declaration-after-statement\"]\nsetup(\n    ext_modules=[\n        Extension(\"evdev._input\", sources=[\"src/evdev/input.c\"], extra_compile_args=cflags),\n        Extension(\"evdev._uinput\", sources=[\"src/evdev/uinput.c\"], extra_compile_args=cflags),\n        Extension(\"evdev._ecodes\", sources=[\"src/evdev/ecodes.c\"], extra_compile_args=cflags),\n    ],\n    cmdclass={\n        \"build_ext\": build_ext,\n        \"build_ecodes\": build_ecodes,\n    },\n)\n"
  },
  {
    "path": "src/evdev/__init__.py",
    "content": "# --------------------------------------------------------------------------\n# Gather everything into a single, convenient namespace.\n# --------------------------------------------------------------------------\n\n# The superfluous \"import name as name\" syntax is here to satisfy mypy's attrs-defined rule.\n# Alternatively all exported objects can be listed in __all__.\n\nfrom . import (\n    ecodes as ecodes,\n    ff as ff,\n)\n\nfrom .device import (\n    AbsInfo as AbsInfo,\n    DeviceInfo as DeviceInfo,\n    EvdevError as EvdevError,\n    InputDevice as InputDevice,\n)\n\nfrom .events import (\n    AbsEvent as AbsEvent,\n    InputEvent as InputEvent,\n    KeyEvent as KeyEvent,\n    RelEvent as RelEvent,\n    SynEvent as SynEvent,\n    event_factory as event_factory,\n)\n\nfrom .uinput import (\n    UInput as UInput,\n    UInputError as UInputError,\n)\n\nfrom .util import (\n    categorize as categorize,\n    list_devices as list_devices,\n    resolve_ecodes as resolve_ecodes,\n    resolve_ecodes_dict as resolve_ecodes_dict,\n)\n"
  },
  {
    "path": "src/evdev/device.py",
    "content": "import contextlib\nimport os\nfrom typing import Dict, Generic, Iterator, List, Literal, NamedTuple, Tuple, TypeVar, Union, overload\n\nfrom . import _input, ecodes, util\n\ntry:\n    from .eventio_async import EvdevError, EventIO\nexcept ImportError:\n    from .eventio import EvdevError, EventIO\n\n_AnyStr = TypeVar(\"_AnyStr\", str, bytes)\n\n\nclass AbsInfo(NamedTuple):\n    \"\"\"Absolute axis information.\n\n    A ``namedtuple`` with absolute axis information -\n    corresponds to the ``input_absinfo`` struct:\n\n    Attributes\n    ---------\n    value\n      Latest reported value for the axis.\n\n    min\n      Specifies minimum value for the axis.\n\n    max\n      Specifies maximum value for the axis.\n\n    fuzz\n      Specifies fuzz value that is used to filter noise from the\n      event stream.\n\n    flat\n      Values that are within this value will be discarded by joydev\n      interface and reported as 0 instead.\n\n    resolution\n      Specifies resolution for the values reported for the axis.\n      Resolution for main axes (``ABS_X, ABS_Y, ABS_Z``) is reported\n      in units per millimeter (units/mm), resolution for rotational\n      axes (``ABS_RX, ABS_RY, ABS_RZ``) is reported in units per\n      radian.\n\n    Note\n    ----\n    The input core does not clamp reported values to the ``[minimum,\n    maximum]`` limits, such task is left to userspace.\n\n    \"\"\"\n\n    value: int\n    min: int\n    max: int\n    fuzz: int\n    flat: int\n    resolution: int\n\n    def __str__(self):\n        return \"value {}, min {}, max {}, fuzz {}, flat {}, res {}\".format(*self)  # pylint: disable=not-an-iterable\n\n\nclass KbdInfo(NamedTuple):\n    \"\"\"Keyboard repeat rate.\n\n    Attributes\n    ----------\n    delay\n      Amount of time that a key must be depressed before it will start\n      to repeat (in milliseconds).\n\n    repeat\n      Keyboard repeat rate in characters per second.\n    \"\"\"\n\n    delay: int\n    repeat: int\n\n    def __str__(self):\n        return \"delay {}, repeat {}\".format(self.delay, self.repeat)\n\n\nclass DeviceInfo(NamedTuple):\n    \"\"\"\n    Attributes\n    ----------\n    bustype\n    vendor\n    product\n    version\n    \"\"\"\n\n    bustype: int\n    vendor: int\n    product: int\n    version: int\n\n    def __str__(self) -> str:\n        msg = \"bus: {:04x}, vendor {:04x}, product {:04x}, version {:04x}\"\n        return msg.format(*self)  # pylint: disable=not-an-iterable\n\n\nclass InputDevice(EventIO, Generic[_AnyStr]):\n    \"\"\"\n    A linux input device from which input events can be read.\n    \"\"\"\n\n    __slots__ = (\"path\", \"fd\", \"info\", \"name\", \"phys\", \"uniq\", \"_rawcapabilities\", \"version\", \"ff_effects_count\")\n\n    def __init__(self, dev: Union[_AnyStr, \"os.PathLike[_AnyStr]\"]):\n        \"\"\"\n        Arguments\n        ---------\n        dev : str|bytes|PathLike\n          Path to input device\n        \"\"\"\n\n        #: Path to input device.\n        self.path: _AnyStr = dev if not hasattr(dev, \"__fspath__\") else dev.__fspath__()\n\n        # Certain operations are possible only when the device is opened in read-write mode.\n        try:\n            fd = os.open(dev, os.O_RDWR | os.O_NONBLOCK)\n        except OSError:\n            fd = os.open(dev, os.O_RDONLY | os.O_NONBLOCK)\n\n        #: A non-blocking file descriptor to the device file.\n        self.fd: int = fd\n\n        # Returns (bustype, vendor, product, version, name, phys, capabilities).\n        info_res = _input.ioctl_devinfo(self.fd)\n\n        #: A :class:`DeviceInfo <evdev.device.DeviceInfo>` instance.\n        self.info = DeviceInfo(*info_res[:4])\n\n        #: The name of the event device.\n        self.name: str = info_res[4]\n\n        #: The physical topology of the device.\n        self.phys: str = info_res[5]\n\n        #: The unique identifier of the device.\n        self.uniq: str = info_res[6]\n\n        #: The evdev protocol version.\n        self.version: int = _input.ioctl_EVIOCGVERSION(self.fd)\n\n        #: The raw dictionary of device capabilities - see `:func:capabilities()`.\n        self._rawcapabilities = _input.ioctl_capabilities(self.fd)\n\n        #: The number of force feedback effects the device can keep in its memory.\n        self.ff_effects_count = _input.ioctl_EVIOCGEFFECTS(self.fd)\n\n    def __del__(self) -> None:\n        if hasattr(self, \"fd\") and self.fd is not None:\n            try:\n                self.close()\n            except (OSError, ImportError, AttributeError):\n                pass\n\n    def _capabilities(self, absinfo: bool = True):\n        res = {}\n\n        for etype, _ecodes in self._rawcapabilities.items():\n            for code in _ecodes:\n                l = res.setdefault(etype, [])\n                if isinstance(code, tuple):\n                    if absinfo:\n                        a = code[1]  # (0, 0, 0, 255, 0, 0)\n                        i = AbsInfo(*a)\n                        l.append((code[0], i))\n                    else:\n                        l.append(code[0])\n                else:\n                    l.append(code)\n\n        return res\n\n    @overload\n    def capabilities(self, verbose: Literal[False] = ..., absinfo: bool = ...) -> Dict[int, List[int]]:\n        ...\n    @overload\n    def capabilities(self, verbose: Literal[True], absinfo: bool = ...) -> Dict[Tuple[str, int], List[Tuple[str, int]]]:\n        ...\n    def capabilities(self, verbose: bool = False, absinfo: bool = True) -> Union[Dict[int, List[int]], Dict[Tuple[str, int], List[Tuple[str, int]]]]:\n        \"\"\"\n        Return the event types that this device supports as a mapping of\n        supported event types to lists of handled event codes.\n\n        Example\n        --------\n        >>> device.capabilities()\n        { 1: [272, 273, 274],\n          2: [0, 1, 6, 8] }\n\n        If ``verbose`` is ``True``, event codes and types will be resolved\n        to their names.\n\n        ::\n\n          { ('EV_KEY', 1): [('BTN_MOUSE', 272),\n                            ('BTN_RIGHT', 273),\n                            ('BTN_MIDDLE', 273)],\n            ('EV_REL', 2): [('REL_X', 0),\n                            ('REL_Y', 1),\n                            ('REL_HWHEEL', 6),\n                            ('REL_WHEEL', 8)] }\n\n        Unknown codes or types will be resolved to ``'?'``.\n\n        If ``absinfo`` is ``True``, the list of capabilities will also\n        include absolute axis information in the form of\n        :class:`AbsInfo` instances::\n\n          { 3: [ (0, AbsInfo(min=0, max=255, fuzz=0, flat=0)),\n                 (1, AbsInfo(min=0, max=255, fuzz=0, flat=0)) ]}\n\n        Combined with ``verbose`` the above becomes::\n\n          { ('EV_ABS', 3): [ (('ABS_X', 0), AbsInfo(min=0, max=255, fuzz=0, flat=0)),\n                             (('ABS_Y', 1), AbsInfo(min=0, max=255, fuzz=0, flat=0)) ]}\n\n        \"\"\"\n\n        if verbose:\n            return dict(util.resolve_ecodes_dict(self._capabilities(absinfo)))\n        else:\n            return self._capabilities(absinfo)\n\n    def input_props(self, verbose: bool = False):\n        \"\"\"\n        Get device properties and quirks.\n\n        Example\n        -------\n        >>> device.input_props()\n        [0, 5]\n\n        If ``verbose`` is ``True``, input properties are resolved to their\n        names. Unknown codes are resolved to ``'?'``::\n\n        [('INPUT_PROP_POINTER', 0), ('INPUT_PROP_POINTING_STICK', 5)]\n\n        \"\"\"\n        props = _input.ioctl_EVIOCGPROP(self.fd)\n        if verbose:\n            return util.resolve_ecodes(ecodes.INPUT_PROP, props)\n\n        return props\n\n    def leds(self, verbose: bool = False):\n        \"\"\"\n        Return currently set LED keys.\n\n        Example\n        -------\n        >>> device.leds()\n        [0, 1, 8, 9]\n\n        If ``verbose`` is ``True``, event codes are resolved to their\n        names. Unknown codes are resolved to ``'?'``::\n\n        [('LED_NUML', 0), ('LED_CAPSL', 1), ('LED_MISC', 8), ('LED_MAIL', 9)]\n\n        \"\"\"\n        leds = _input.ioctl_EVIOCG_bits(self.fd, ecodes.EV_LED)\n        if verbose:\n            return util.resolve_ecodes(ecodes.LED, leds)\n\n        return leds\n\n    def set_led(self, led_num: int, value: int) -> None:\n        \"\"\"\n        Set the state of the selected LED.\n\n        Example\n        -------\n        >>> device.set_led(ecodes.LED_NUML, 1)\n        \"\"\"\n        self.write(ecodes.EV_LED, led_num, value)\n\n    def __eq__(self, other):\n        \"\"\"\n        Two devices are equal if their :data:`info` attributes are equal.\n        \"\"\"\n        return isinstance(other, self.__class__) and self.info == other.info and self.path == other.path\n\n    def __str__(self) -> str:\n        msg = 'device {}, name \"{}\", phys \"{}\", uniq \"{}\"'\n        return msg.format(self.path, self.name, self.phys, self.uniq or \"\")\n\n    def __repr__(self) -> str:\n        msg = (self.__class__.__name__, self.path)\n        return \"{}({!r})\".format(*msg)\n\n    def __fspath__(self):\n        return self.path\n\n    def close(self) -> None:\n        if self.fd > -1:\n            try:\n                super().close()\n                os.close(self.fd)\n            finally:\n                self.fd = -1\n\n    def grab(self) -> None:\n        \"\"\"\n        Grab input device using ``EVIOCGRAB`` - other applications will\n        be unable to receive events until the device is released. Only\n        one process can hold a ``EVIOCGRAB`` on a device.\n\n        Warning\n        -------\n        Grabbing an already grabbed device will raise an ``OSError``.\n        \"\"\"\n\n        _input.ioctl_EVIOCGRAB(self.fd, 1)\n\n    def ungrab(self) -> None:\n        \"\"\"\n        Release device if it has been already grabbed (uses `EVIOCGRAB`).\n\n        Warning\n        -------\n        Releasing an already released device will raise an\n        ``OSError('Invalid argument')``.\n        \"\"\"\n\n        _input.ioctl_EVIOCGRAB(self.fd, 0)\n\n    @contextlib.contextmanager\n    def grab_context(self) -> Iterator[None]:\n        \"\"\"\n        A context manager for the duration of which only the current\n        process will be able to receive events from the device.\n        \"\"\"\n        self.grab()\n        yield\n        self.ungrab()\n\n    def upload_effect(self, effect: \"ff.Effect\"):\n        \"\"\"\n        Upload a force feedback effect to a force feedback device.\n        \"\"\"\n\n        data = memoryview(effect).tobytes()\n        ff_id = _input.upload_effect(self.fd, data)\n        return ff_id\n\n    def erase_effect(self, ff_id) -> None:\n        \"\"\"\n        Erase a force effect from a force feedback device. This also\n        stops the effect.\n        \"\"\"\n\n        _input.erase_effect(self.fd, ff_id)\n\n    @property\n    def repeat(self):\n        \"\"\"\n        Get or set the keyboard repeat rate (in characters per\n        minute) and delay (in milliseconds).\n        \"\"\"\n\n        return KbdInfo(*_input.ioctl_EVIOCGREP(self.fd))\n\n    @repeat.setter\n    def repeat(self, value: Tuple[int, int]):\n        return _input.ioctl_EVIOCSREP(self.fd, *value)\n\n    def active_keys(self, verbose: bool = False):\n        \"\"\"\n        Return currently active keys.\n\n        Example\n        -------\n\n        >>> device.active_keys()\n        [1, 42]\n\n        If ``verbose`` is ``True``, key codes are resolved to their\n        verbose names. Unknown codes are resolved to ``'?'``. For\n        example::\n\n          [('KEY_ESC', 1), ('KEY_LEFTSHIFT', 42)]\n\n        \"\"\"\n        active_keys = _input.ioctl_EVIOCG_bits(self.fd, ecodes.EV_KEY)\n        if verbose:\n            return util.resolve_ecodes(ecodes.KEY, active_keys)\n\n        return active_keys\n\n    def absinfo(self, axis_num: int):\n        \"\"\"\n        Return current :class:`AbsInfo` for input device axis\n\n        Arguments\n        ---------\n        axis_num : int\n          EV_ABS keycode (example :attr:`ecodes.ABS_X`)\n\n        Example\n        -------\n        >>> device.absinfo(ecodes.ABS_X)\n        AbsInfo(value=1501, min=-32768, max=32767, fuzz=0, flat=128, resolution=0)\n        \"\"\"\n        return AbsInfo(*_input.ioctl_EVIOCGABS(self.fd, axis_num))\n\n    def set_absinfo(self, axis_num: int, value=None, min=None, max=None, fuzz=None, flat=None, resolution=None) -> None:\n        \"\"\"\n        Update :class:`AbsInfo` values. Only specified values will be overwritten.\n\n        Arguments\n        ---------\n        axis_num : int\n          EV_ABS keycode (example :attr:`ecodes.ABS_X`)\n\n        Example\n        -------\n        >>> device.set_absinfo(ecodes.ABS_X, min=-2000, max=2000)\n\n        You can also unpack AbsInfo tuple that will overwrite all values\n\n        >>> device.set_absinfo(ecodes.ABS_Y, *AbsInfo(0, -2000, 2000, 0, 15, 0))\n        \"\"\"\n\n        cur_absinfo = self.absinfo(axis_num)\n        new_absinfo = AbsInfo(\n            value if value is not None else cur_absinfo.value,\n            min if min is not None else cur_absinfo.min,\n            max if max is not None else cur_absinfo.max,\n            fuzz if fuzz is not None else cur_absinfo.fuzz,\n            flat if flat is not None else cur_absinfo.flat,\n            resolution if resolution is not None else cur_absinfo.resolution,\n        )\n        _input.ioctl_EVIOCSABS(self.fd, axis_num, new_absinfo)\n"
  },
  {
    "path": "src/evdev/ecodes.py",
    "content": "# When installed, this module is replaced by an ecodes.py generated at\n# build time by genecodes_py.py (see build_ext in setup.py).\n\n# This stub exists to make development of evdev itself more convenient.\nfrom .ecodes_runtime import *\n"
  },
  {
    "path": "src/evdev/ecodes_runtime.py",
    "content": "# pylint: disable=undefined-variable\n\"\"\"\nThis modules exposes the integer constants defined in ``linux/input.h`` and\n``linux/input-event-codes.h``.\n\nExposed constants::\n\n    KEY, ABS, REL, SW, MSC, LED, BTN, REP, SND, ID, EV,\n    BUS, SYN, FF, FF_STATUS, INPUT_PROP\n\nThis module also provides reverse and forward mappings of the names and values\nof the above mentioned constants::\n\n    >>> evdev.ecodes.KEY_A\n    30\n\n    >>> evdev.ecodes.ecodes['KEY_A']\n    30\n\n    >>> evdev.ecodes.KEY[30]\n    'KEY_A'\n\n    >>> evdev.ecodes.REL[0]\n    'REL_X'\n\n    >>> evdev.ecodes.EV[evdev.ecodes.EV_KEY]\n    'EV_KEY'\n\n    >>> evdev.ecodes.bytype[evdev.ecodes.EV_REL][0]\n    'REL_X'\n\nKeep in mind that values in reverse mappings may point to one or more event\ncodes. For example::\n\n    >>> evdev.ecodes.FF[80]\n    ('FF_EFFECT_MIN', 'FF_RUMBLE')\n\n    >>> evdev.ecodes.FF[81]\n    'FF_PERIODIC'\n\"\"\"\n\nfrom inspect import getmembers\n\nfrom . import _ecodes\n\n#: Mapping of names to values.\necodes = {}\n\nprefixes = \"KEY ABS REL SW MSC LED BTN REP SND ID EV BUS SYN FF_STATUS FF INPUT_PROP UI_FF\".split()\nprev_prefix = \"\"\ng = globals()\n\n# eg. code: 'REL_Z', val: 2\nfor code, val in getmembers(_ecodes):\n    for prefix in prefixes:  # eg. 'REL'\n        if code.startswith(prefix):\n            ecodes[code] = val\n            # FF_STATUS codes should not appear in the FF reverse mapping\n            if not code.startswith(prev_prefix):\n                d = g.setdefault(prefix, {})\n                # codes that share the same value will be added to a list. eg:\n                # >>> ecodes.FF_STATUS\n                # {0: 'FF_STATUS_STOPPED', 1: ['FF_STATUS_MAX', 'FF_STATUS_PLAYING']}\n                if val in d:\n                    if isinstance(d[val], list):\n                        d[val].append(code)\n                    else:\n                        d[val] = [d[val], code]\n                else:\n                    d[val] = code\n\n        prev_prefix = prefix\n\n\n# Convert lists to tuples.\nk, v = None, None\nfor prefix in prefixes:\n    for k, v in g[prefix].items():\n        if isinstance(v, list):\n            g[prefix][k] = tuple(v)\n\n\n#: Keys are a combination of all BTN and KEY codes.\nkeys = {}\nkeys.update(BTN)\nkeys.update(KEY)\n\n# make keys safe to use for the default list of uinput device\n# capabilities\ndel keys[_ecodes.KEY_MAX]\ndel keys[_ecodes.KEY_CNT]\n\n#: Mapping of event types to other value/name mappings.\nbytype = {\n    _ecodes.EV_KEY: keys,\n    _ecodes.EV_ABS: ABS,\n    _ecodes.EV_REL: REL,\n    _ecodes.EV_SW: SW,\n    _ecodes.EV_MSC: MSC,\n    _ecodes.EV_LED: LED,\n    _ecodes.EV_REP: REP,\n    _ecodes.EV_SND: SND,\n    _ecodes.EV_SYN: SYN,\n    _ecodes.EV_FF: FF,\n    _ecodes.EV_FF_STATUS: FF_STATUS,\n}\n\nfrom evdev._ecodes import *\n\n# cheaper than whitelisting in an __all__\ndel code, val, prefix, getmembers, g, d, k, v, prefixes, prev_prefix\n"
  },
  {
    "path": "src/evdev/eventio.py",
    "content": "import fcntl\nimport functools\nimport os\nimport select\nfrom typing import Iterator, Union\n\nfrom . import _input, _uinput, ecodes\nfrom .events import InputEvent\n\n\n# --------------------------------------------------------------------------\nclass EvdevError(Exception):\n    pass\n\n\nclass EventIO:\n    \"\"\"\n    Base class for reading and writing input events.\n\n    This class is used by :class:`InputDevice` and :class:`UInput`.\n\n    - On, :class:`InputDevice` it used for reading user-generated events (e.g.\n      key presses, mouse movements) and writing feedback events (e.g. leds,\n      beeps).\n\n    - On, :class:`UInput` it used for writing user-generated events (e.g.\n      key presses, mouse movements) and reading feedback events (e.g. leds,\n      beeps).\n    \"\"\"\n\n    def fileno(self):\n        \"\"\"\n        Return the file descriptor to the open event device. This makes\n        it possible to pass instances directly to :func:`select.select()` and\n        :class:`asyncore.file_dispatcher`.\n        \"\"\"\n        return self.fd\n\n    def read_loop(self) -> Iterator[InputEvent]:\n        \"\"\"\n        Enter an endless :func:`select.select()` loop that yields input events.\n        \"\"\"\n\n        while True:\n            r, w, x = select.select([self.fd], [], [])\n            for event in self.read():\n                yield event\n\n    def read_one(self) -> Union[InputEvent, None]:\n        \"\"\"\n        Read and return a single input event as an instance of\n        :class:`InputEvent <evdev.events.InputEvent>`.\n\n        Return ``None`` if there are no pending input events.\n        \"\"\"\n\n        # event -> (sec, usec, type, code, val)\n        event = _input.device_read(self.fd)\n\n        if event:\n            return InputEvent(*event)\n\n    def read(self) -> Iterator[InputEvent]:\n        \"\"\"\n        Read multiple input events from device. Return a generator object that\n        yields :class:`InputEvent <evdev.events.InputEvent>` instances. Raises\n        `BlockingIOError` if there are no available events at the moment.\n        \"\"\"\n\n        # events -> ((sec, usec, type, code, val), ...)\n        events = _input.device_read_many(self.fd)\n\n        for event in events:\n            yield InputEvent(*event)\n\n    # pylint: disable=no-self-argument\n    def need_write(func):\n        \"\"\"\n        Decorator that raises :class:`EvdevError` if there is no write access to the\n        input device.\n        \"\"\"\n\n        @functools.wraps(func)\n        def wrapper(*args):\n            fd = args[0].fd\n            if fcntl.fcntl(fd, fcntl.F_GETFL) & os.O_RDWR:\n                # pylint: disable=not-callable\n                return func(*args)\n            msg = 'no write access to device \"%s\"' % args[0].path\n            raise EvdevError(msg)\n\n        return wrapper\n\n    def write_event(self, event):\n        \"\"\"\n        Inject an input event into the input subsystem. Events are\n        queued until a synchronization event is received.\n\n        Arguments\n        ---------\n        event: InputEvent\n          InputEvent instance or an object with an ``event`` attribute\n          (:class:`KeyEvent <evdev.events.KeyEvent>`, :class:`RelEvent\n          <evdev.events.RelEvent>` etc).\n\n        Example\n        -------\n        >>> ev = InputEvent(1334414993, 274296, ecodes.EV_KEY, ecodes.KEY_A, 1)\n        >>> ui.write_event(ev)\n        \"\"\"\n\n        if hasattr(event, \"event\"):\n            event = event.event\n\n        self.write(event.type, event.code, event.value)\n\n    @need_write\n    def write(self, etype: int, code: int, value: int):\n        \"\"\"\n        Inject an input event into the input subsystem. Events are\n        queued until a synchronization event is received.\n\n        Arguments\n        ---------\n        etype\n          event type (e.g. ``EV_KEY``).\n\n        code\n          event code (e.g. ``KEY_A``).\n\n        value\n          event value (e.g. 0 1 2 - depends on event type).\n\n        Example\n        ---------\n        >>> ui.write(e.EV_KEY, e.KEY_A, 1) # key A - down\n        >>> ui.write(e.EV_KEY, e.KEY_A, 0) # key A - up\n        \"\"\"\n\n        _uinput.write(self.fd, etype, code, value)\n\n    def syn(self):\n        \"\"\"\n        Inject a ``SYN_REPORT`` event into the input subsystem. Events\n        queued by :func:`write()` will be fired. If possible, events\n        will be merged into an 'atomic' event.\n        \"\"\"\n\n        self.write(ecodes.EV_SYN, ecodes.SYN_REPORT, 0)\n\n    def close(self):\n        pass\n"
  },
  {
    "path": "src/evdev/eventio_async.py",
    "content": "import asyncio\nimport select\nimport sys\n\nfrom . import eventio\nfrom .events import InputEvent\n\n# needed for compatibility\nfrom .eventio import EvdevError\n\nif sys.version_info >= (3, 11):\n    from typing import Self\nelse:\n    from typing import Any as Self\n\n\nclass ReadIterator:\n    def __init__(self, device):\n        self.current_batch = iter(())\n        self.device = device\n\n    # Standard iterator protocol.\n    def __iter__(self) -> Self:\n        return self\n\n    def __next__(self) -> InputEvent:\n        try:\n            # Read from the previous batch of events.\n            return next(self.current_batch)\n        except StopIteration:\n            r, w, x = select.select([self.device.fd], [], [])\n            self.current_batch = self.device.read()\n            return next(self.current_batch)\n\n    def __aiter__(self) -> Self:\n        return self\n\n    def __anext__(self) -> \"asyncio.Future[InputEvent]\":\n        future = asyncio.Future()\n        try:\n            # Read from the previous batch of events.\n            future.set_result(next(self.current_batch))\n        except StopIteration:\n\n            def next_batch_ready(batch):\n                try:\n                    self.current_batch = batch.result()\n                    future.set_result(next(self.current_batch))\n                except Exception as e:\n                    future.set_exception(e)\n\n            self.device.async_read().add_done_callback(next_batch_ready)\n        return future\n\n\nclass EventIO(eventio.EventIO):\n    def _do_when_readable(self, callback):\n        loop = asyncio.get_event_loop()\n\n        def ready():\n            loop.remove_reader(self.fileno())\n            callback()\n\n        loop.add_reader(self.fileno(), ready)\n\n    def _set_result(self, future, cb):\n        try:\n            future.set_result(cb())\n        except Exception as error:\n            future.set_exception(error)\n\n    def async_read_one(self):\n        \"\"\"\n        Asyncio coroutine to read and return a single input event as\n        an instance of :class:`InputEvent <evdev.events.InputEvent>`.\n        \"\"\"\n        future = asyncio.Future()\n        self._do_when_readable(lambda: self._set_result(future, self.read_one))\n        return future\n\n    def async_read(self):\n        \"\"\"\n        Asyncio coroutine to read multiple input events from device. Return\n        a generator object that yields :class:`InputEvent <evdev.events.InputEvent>`\n        instances.\n        \"\"\"\n        future = asyncio.Future()\n        self._do_when_readable(lambda: self._set_result(future, self.read))\n        return future\n\n    def async_read_loop(self) -> ReadIterator:\n        \"\"\"\n        Return an iterator that yields input events. This iterator is\n        compatible with the ``async for`` syntax.\n\n        \"\"\"\n        return ReadIterator(self)\n\n    def close(self):\n        try:\n            loop = asyncio.get_event_loop()\n            loop.remove_reader(self.fileno())\n        except RuntimeError:\n            # no event loop present, so there is nothing to\n            # remove the reader from. Ignore\n            pass\n"
  },
  {
    "path": "src/evdev/events.py",
    "content": "\"\"\"\nThis module provides the :class:`InputEvent` class, which closely\nresembles the ``input_event`` struct defined in ``linux/input.h``:\n\n.. code-block:: c\n\n    struct input_event {\n        struct timeval time;\n        __u16 type;\n        __u16 code;\n        __s32 value;\n    };\n\nThis module also defines several :class:`InputEvent` sub-classes that\nknow more about the different types of events (key, abs, rel etc). The\n:data:`event_factory` dictionary maps event types to these classes.\n\nAssuming you use the :func:`evdev.util.categorize()` function to\ncategorize events according to their type, adding or replacing a class\nfor a specific event type becomes a matter of modifying\n:data:`event_factory`.\n\nAll classes in this module have reasonable ``str()`` and ``repr()``\nmethods::\n\n    >>> print(event)\n    event at 1337197425.477827, code 04, type 04, val 458792\n    >>> print(repr(event))\n    InputEvent(1337197425L, 477827L, 4, 4, 458792L)\n\n    >>> print(key_event)\n    key event at 1337197425.477835, 28 (KEY_ENTER), up\n    >>> print(repr(key_event))\n    KeyEvent(InputEvent(1337197425L, 477835L, 1, 28, 0L))\n\"\"\"\n\n# event type descriptions have been taken mot-a-mot from:\n# http://www.kernel.org/doc/Documentation/input/event-codes.txt\n\n# pylint: disable=no-name-in-module\nfrom typing import Final\nfrom .ecodes import ABS, EV_ABS, EV_KEY, EV_REL, EV_SYN, KEY, REL, SYN, keys\n\n\nclass InputEvent:\n    \"\"\"A generic input event.\"\"\"\n\n    __slots__ = \"sec\", \"usec\", \"type\", \"code\", \"value\"\n\n    def __init__(self, sec, usec, type, code, value):\n        #: Time in seconds since epoch at which event occurred.\n        self.sec: int = sec\n\n        #: Microsecond portion of the timestamp.\n        self.usec: int = usec\n\n        #: Event type - one of ``ecodes.EV_*``.\n        self.type: int = type\n\n        #: Event code related to the event type.\n        self.code: int = code\n\n        #: Event value related to the event type.\n        self.value: int = value\n\n    def timestamp(self) -> float:\n        \"\"\"Return event timestamp as a float.\"\"\"\n        return self.sec + (self.usec / 1000000.0)\n\n    def __str__(self):\n        msg = \"event at {:f}, code {:02d}, type {:02d}, val {:02d}\"\n        return msg.format(self.timestamp(), self.code, self.type, self.value)\n\n    def __repr__(self):\n        msg = \"{}({!r}, {!r}, {!r}, {!r}, {!r})\"\n        return msg.format(self.__class__.__name__, self.sec, self.usec, self.type, self.code, self.value)\n\n\nclass KeyEvent:\n    \"\"\"An event generated by a keyboard, button or other key-like devices.\"\"\"\n\n    key_up: Final[int] = 0x0\n    key_down: Final[int] = 0x1\n    key_hold: Final[int] = 0x2\n\n    __slots__ = \"scancode\", \"keycode\", \"keystate\", \"event\"\n\n    def __init__(self, event: InputEvent, allow_unknown: bool = False):\n        \"\"\"\n        The ``allow_unknown`` argument determines what to do in the event of an event code\n        for which a key code cannot be found. If ``False`` a ``KeyError`` will be raised.\n        If ``True`` the keycode will be set to the hex value of the event code.\n        \"\"\"\n\n        self.scancode: int = event.code\n\n        if event.value == 0:\n            self.keystate = KeyEvent.key_up\n        elif event.value == 2:\n            self.keystate = KeyEvent.key_hold\n        elif event.value == 1:\n            self.keystate = KeyEvent.key_down\n\n        try:\n            self.keycode = keys[event.code]\n        except KeyError:\n            if allow_unknown:\n                self.keycode = \"0x{:02X}\".format(event.code)\n            else:\n                raise\n\n        #: Reference to an :class:`InputEvent` instance.\n        self.event: InputEvent = event\n\n    def __str__(self):\n        try:\n            ks = (\"up\", \"down\", \"hold\")[self.keystate]\n        except IndexError:\n            ks = \"unknown\"\n\n        msg = \"key event at {:f}, {} ({}), {}\"\n        return msg.format(self.event.timestamp(), self.scancode, self.keycode, ks)\n\n    def __repr__(self):\n        return \"{}({!r})\".format(self.__class__.__name__, self.event)\n\n\nclass RelEvent:\n    \"\"\"A relative axis event (e.g moving the mouse 5 units to the left).\"\"\"\n\n    __slots__ = \"event\"\n\n    def __init__(self, event: InputEvent):\n        #: Reference to an :class:`InputEvent` instance.\n        self.event: InputEvent = event\n\n    def __str__(self):\n        msg = \"relative axis event at {:f}, {}\"\n        return msg.format(self.event.timestamp(), REL[self.event.code])\n\n    def __repr__(self):\n        return \"{}({!r})\".format(self.__class__.__name__, self.event)\n\n\nclass AbsEvent:\n    \"\"\"An absolute axis event (e.g the coordinates of a tap on a touchscreen).\"\"\"\n\n    __slots__ = \"event\"\n\n    def __init__(self, event: InputEvent):\n        #: Reference to an :class:`InputEvent` instance.\n        self.event: InputEvent = event\n\n    def __str__(self):\n        msg = \"absolute axis event at {:f}, {}\"\n        return msg.format(self.event.timestamp(), ABS[self.event.code])\n\n    def __repr__(self):\n        return \"{}({!r})\".format(self.__class__.__name__, self.event)\n\n\nclass SynEvent:\n    \"\"\"\n    A synchronization event. Used as markers to separate events. Events may be\n    separated in time or in space, such as with the multitouch protocol.\n    \"\"\"\n\n    __slots__ = \"event\"\n\n    def __init__(self, event: InputEvent):\n        #: Reference to an :class:`InputEvent` instance.\n        self.event: InputEvent = event\n\n    def __str__(self):\n        msg = \"synchronization event at {:f}, {}\"\n        return msg.format(self.event.timestamp(), SYN[self.event.code])\n\n    def __repr__(self):\n        return \"{}({!r})\".format(self.__class__.__name__, self.event)\n\n\n#: A mapping of event types to :class:`InputEvent` sub-classes. Used\n#: by :func:`evdev.util.categorize()`\nevent_factory = {\n    EV_KEY: KeyEvent,\n    EV_REL: RelEvent,\n    EV_ABS: AbsEvent,\n    EV_SYN: SynEvent,\n}\n\n\n__all__ = (\"InputEvent\", \"KeyEvent\", \"RelEvent\", \"SynEvent\", \"AbsEvent\", \"event_factory\")\n"
  },
  {
    "path": "src/evdev/evtest.py",
    "content": "\"\"\"\nUsage: evtest [options] [<device>, ...]\n\nInput device enumerator and event monitor.\n\nRunning evtest without any arguments will let you select\nfrom a list of all readable input devices.\n\nOptions:\n  -h, --help          Show this help message and exit.\n  -c, --capabilities  List device capabilities and exit.\n  -g, --grab          Other applications will not receive events from\n                      the selected devices while evtest is running.\n\nExamples:\n  evtest /dev/input/event0 /dev/input/event1\n\"\"\"\n\nimport atexit\nimport optparse\nimport re\nimport select\nimport sys\nimport termios\n\nfrom . import AbsInfo, InputDevice, ecodes, list_devices\n\n\ndef parseopt():\n    parser = optparse.OptionParser(add_help_option=False)\n    parser.add_option(\"-h\", \"--help\", action=\"store_true\")\n    parser.add_option(\"-g\", \"--grab\", action=\"store_true\")\n    parser.add_option(\"-c\", \"--capabilities\", action=\"store_true\")\n    return parser.parse_args()\n\n\ndef main():\n    opts, devices = parseopt()\n    if opts.help:\n        print(__doc__.strip())\n        return 0\n\n    if not devices:\n        devices = select_devices()\n    else:\n        devices = [InputDevice(path) for path in devices]\n\n    if opts.capabilities:\n        for device in devices:\n            print_capabilities(device)\n        return 0\n\n    if opts.grab:\n        for device in devices:\n            device.grab()\n\n    # Disable tty echoing if stdin is a tty.\n    if sys.stdin.isatty():\n        toggle_tty_echo(sys.stdin, enable=False)\n        atexit.register(toggle_tty_echo, sys.stdin, enable=True)\n\n    print(\"Listening for events (press ctrl-c to exit) ...\")\n    fd_to_device = {dev.fd: dev for dev in devices}\n    while True:\n        r, w, e = select.select(fd_to_device, [], [])\n\n        for fd in r:\n            for event in fd_to_device[fd].read():\n                print_event(event)\n\n\ndef select_devices(device_dir=\"/dev/input\"):\n    \"\"\"\n    Select one or more devices from a list of accessible input devices.\n    \"\"\"\n\n    def devicenum(device_path):\n        digits = re.findall(r\"\\d+$\", device_path)\n        return [int(i) for i in digits]\n\n    devices = sorted(list_devices(device_dir), key=devicenum)\n    devices = [InputDevice(path) for path in devices]\n    if not devices:\n        msg = \"error: no input devices found (do you have rw permission on %s/*?)\"\n        print(msg % device_dir, file=sys.stderr)\n        sys.exit(1)\n\n    dev_format = \"{0:<3} {1.path:<20} {1.name:<35} {1.phys:<35} {1.uniq:<4}\"\n    dev_lines = [dev_format.format(num, dev) for num, dev in enumerate(devices)]\n\n    print(\"ID  {:<20} {:<35} {:<35} {}\".format(\"Device\", \"Name\", \"Phys\", \"Uniq\"))\n    print(\"-\" * len(max(dev_lines, key=len)))\n    print(\"\\n\".join(dev_lines))\n    print()\n\n    choices = input(\"Select devices [0-%s]: \" % (len(dev_lines) - 1))\n\n    try:\n        choices = choices.split()\n        choices = [devices[int(num)] for num in choices]\n    except ValueError:\n        choices = None\n\n    if not choices:\n        msg = \"error: invalid input - please enter one or more numbers separated by spaces\"\n        print(msg, file=sys.stderr)\n        sys.exit(1)\n\n    return choices\n\n\ndef print_capabilities(device):\n    capabilities = device.capabilities(verbose=True)\n    input_props = device.input_props(verbose=True)\n\n    print(\"Device name: {.name}\".format(device))\n    print(\"Device info: {.info}\".format(device))\n    print(\"Repeat settings: {}\\n\".format(device.repeat))\n\n    if (\"EV_LED\", ecodes.EV_LED) in capabilities:\n        leds = \",\".join(i[0] for i in device.leds(True))\n        print(\"Active LEDs: %s\" % leds)\n\n    active_keys = \",\".join(k[0] for k in device.active_keys(True))\n    print(\"Active keys: %s\\n\" % active_keys)\n\n    if input_props:\n        print(\"Input properties:\")\n        for type, code in input_props:\n            print(\"  %s %s\" % (type, code))\n        print()\n\n    print(\"Device capabilities:\")\n    for type, codes in capabilities.items():\n        print(\"  Type {} {}:\".format(*type))\n        for code in codes:\n            # code <- ('BTN_RIGHT', 273) or (['BTN_LEFT', 'BTN_MOUSE'], 272)\n            if isinstance(code[1], AbsInfo):\n                print(\"    Code {:<4} {}:\".format(*code[0]))\n                print(\"      {}\".format(code[1]))\n            else:\n                # Multiple names may resolve to one value.\n                s = \", \".join(code[0]) if isinstance(code[0], list) else code[0]\n                print(\"    Code {:<4} {}\".format(s, code[1]))\n        print(\"\")\n\n\ndef print_event(e):\n    if e.type == ecodes.EV_SYN:\n        if e.code == ecodes.SYN_MT_REPORT:\n            msg = \"time {:<17} +++++++++++++ {} +++++++++++++\"\n        elif e.code == ecodes.SYN_DROPPED:\n            msg = \"time {:<17} !!!!!!!!!!!!! {} !!!!!!!!!!!!!\"\n        else:\n            msg = \"time {:<17} ------------- {} -------------\"\n        print(msg.format(e.timestamp(), ecodes.SYN[e.code]))\n    else:\n        if e.type in ecodes.bytype:\n            codename = ecodes.bytype[e.type][e.code]\n        else:\n            codename = \"?\"\n\n        evfmt = \"time {:<17} type {} ({}), code {:<4} ({}), value {}\"\n        print(evfmt.format(e.timestamp(), e.type, ecodes.EV[e.type], e.code, codename, e.value))\n\n\ndef toggle_tty_echo(fh, enable=True):\n    flags = termios.tcgetattr(fh.fileno())\n    if enable:\n        flags[3] |= termios.ECHO\n    else:\n        flags[3] &= ~termios.ECHO\n    termios.tcsetattr(fh.fileno(), termios.TCSANOW, flags)\n\n\nif __name__ == \"__main__\":\n    try:\n        ret = main()\n    except (KeyboardInterrupt, EOFError):\n        ret = 0\n    sys.exit(ret)\n"
  },
  {
    "path": "src/evdev/ff.py",
    "content": "import ctypes\n\nfrom . import ecodes\n\n_u8 = ctypes.c_uint8\n_u16 = ctypes.c_uint16\n_u32 = ctypes.c_uint32\n_s16 = ctypes.c_int16\n_s32 = ctypes.c_int32\n\n\nclass Replay(ctypes.Structure):\n    \"\"\"\n    Defines scheduling of the force-feedback effect\n    @length: duration of the effect\n    @delay: delay before effect should start playing\n    \"\"\"\n\n    _fields_ = [\n        (\"length\", _u16),\n        (\"delay\", _u16),\n    ]\n\n\nclass Trigger(ctypes.Structure):\n    \"\"\"\n    Defines what triggers the force-feedback effect\n    @button: number of the button triggering the effect\n    @interval: controls how soon the effect can be re-triggered\n    \"\"\"\n\n    _fields_ = [\n        (\"button\", _u16),\n        (\"interval\", _u16),\n    ]\n\n\nclass Envelope(ctypes.Structure):\n    \"\"\"\n    Generic force-feedback effect envelope\n    @attack_length: duration of the attack (ms)\n    @attack_level: level at the beginning of the attack\n    @fade_length: duration of fade (ms)\n    @fade_level: level at the end of fade\n\n    The @attack_level and @fade_level are absolute values; when applying\n    envelope force-feedback core will convert to positive/negative\n    value based on polarity of the default level of the effect.\n    Valid range for the attack and fade levels is 0x0000 - 0x7fff\n    \"\"\"\n\n    _fields_ = [\n        (\"attack_length\", _u16),\n        (\"attack_level\", _u16),\n        (\"fade_length\", _u16),\n        (\"fade_level\", _u16),\n    ]\n\n\nclass Constant(ctypes.Structure):\n    \"\"\"\n    Defines parameters of a constant force-feedback effect\n    @level: strength of the effect; may be negative\n    @envelope: envelope data\n    \"\"\"\n\n    _fields_ = [\n        (\"level\", _s16),\n        (\"ff_envelope\", Envelope),\n    ]\n\n\nclass Ramp(ctypes.Structure):\n    \"\"\"\n    Defines parameters of a ramp force-feedback effect\n    @start_level: beginning strength of the effect; may be negative\n    @end_level: final strength of the effect; may be negative\n    @envelope: envelope data\n    \"\"\"\n\n    _fields_ = [\n        (\"start_level\", _s16),\n        (\"end_level\", _s16),\n        (\"ff_envelope\", Envelope),\n    ]\n\n\nclass Condition(ctypes.Structure):\n    \"\"\"\n    Defines a spring or friction force-feedback effect\n    @right_saturation: maximum level when joystick moved all way to the right\n    @left_saturation: same for the left side\n    @right_coeff: controls how fast the force grows when the joystick moves to the right\n    @left_coeff: same for the left side\n    @deadband: size of the dead zone, where no force is produced\n    @center: position of the dead zone\n    \"\"\"\n\n    _fields_ = [\n        (\"right_saturation\", _u16),\n        (\"left_saturation\", _u16),\n        (\"right_coeff\", _s16),\n        (\"left_coeff\", _s16),\n        (\"deadband\", _u16),\n        (\"center\", _s16),\n    ]\n\n\nclass Periodic(ctypes.Structure):\n    \"\"\"\n    Defines parameters of a periodic force-feedback effect\n    @waveform: kind of the effect (wave)\n    @period: period of the wave (ms)\n    @magnitude: peak value\n    @offset: mean value of the wave (roughly)\n    @phase: 'horizontal' shift\n    @envelope: envelope data\n    @custom_len: number of samples (FF_CUSTOM only)\n    @custom_data: buffer of samples (FF_CUSTOM only)\n    \"\"\"\n\n    _fields_ = [\n        (\"waveform\", _u16),\n        (\"period\", _u16),\n        (\"magnitude\", _s16),\n        (\"offset\", _s16),\n        (\"phase\", _u16),\n        (\"envelope\", Envelope),\n        (\"custom_len\", _u32),\n        (\"custom_data\", ctypes.POINTER(_s16)),\n    ]\n\n\nclass Rumble(ctypes.Structure):\n    \"\"\"\n    Defines parameters of a periodic force-feedback effect\n    @strong_magnitude: magnitude of the heavy motor\n    @weak_magnitude: magnitude of the light one\n\n    Some rumble pads have two motors of different weight. Strong_magnitude\n    represents the magnitude of the vibration generated by the heavy one.\n    \"\"\"\n\n    _fields_ = [\n        (\"strong_magnitude\", _u16),\n        (\"weak_magnitude\", _u16),\n    ]\n\n\nclass EffectType(ctypes.Union):\n    _fields_ = [\n        (\"ff_constant_effect\", Constant),\n        (\"ff_ramp_effect\", Ramp),\n        (\"ff_periodic_effect\", Periodic),\n        (\"ff_condition_effect\", Condition * 2),  # one for each axis\n        (\"ff_rumble_effect\", Rumble),\n    ]\n\n\nclass Effect(ctypes.Structure):\n    _fields_ = [\n        (\"type\", _u16),\n        (\"id\", _s16),\n        (\"direction\", _u16),\n        (\"ff_trigger\", Trigger),\n        (\"ff_replay\", Replay),\n        (\"u\", EffectType),\n    ]\n\n\nclass UInputUpload(ctypes.Structure):\n    _fields_ = [\n        (\"request_id\", _u32),\n        (\"retval\", _s32),\n        (\"effect\", Effect),\n        (\"old\", Effect),\n    ]\n\n\nclass UInputErase(ctypes.Structure):\n    _fields_ = [\n        (\"request_id\", _u32),\n        (\"retval\", _s32),\n        (\"effect_id\", _u32),\n    ]\n\n\n# ff_types = {\n#     ecodes.FF_CONSTANT,\n#     ecodes.FF_PERIODIC,\n#     ecodes.FF_RAMP,\n#     ecodes.FF_SPRING,\n#     ecodes.FF_FRICTION,\n#     ecodes.FF_DAMPER,\n#     ecodes.FF_RUMBLE,\n#     ecodes.FF_INERTIA,\n#     ecodes.FF_CUSTOM,\n# }\n"
  },
  {
    "path": "src/evdev/genecodes_c.py",
    "content": "\"\"\"\nGenerate a Python extension module with the constants defined in linux/input.h.\n\"\"\"\n\nimport getopt\nimport os\nimport re\nimport sys\n\n# -----------------------------------------------------------------------------\n# The default header file locations to try.\nheaders = [\n    \"/usr/include/linux/input.h\",\n    \"/usr/include/linux/input-event-codes.h\",\n    \"/usr/include/linux/uinput.h\",\n]\n\nopts, args = getopt.getopt(sys.argv[1:], \"\", [\"ecodes\", \"stubs\", \"reproducible\"])\nif not opts:\n    print(\"usage: genecodes.py [--ecodes|--stubs] [--reproducible] <headers>\")\n    exit(2)\n\nif args:\n    headers = args\n\nreproducible = (\"--reproducible\", \"\") in opts\n\n\n# -----------------------------------------------------------------------------\nmacro_regex = r\"#define\\s+((?:KEY|ABS|REL|SW|MSC|LED|BTN|REP|SND|ID|EV|BUS|SYN|FF|UI_FF|INPUT_PROP)_\\w+)\"\nmacro_regex = re.compile(macro_regex)\n\nif reproducible:\n    uname = \"hidden for reproducibility\"\nelse:\n    # Uname without hostname.\n    uname = list(os.uname())\n    uname = \" \".join((uname[0], *uname[2:]))\n\n\n# -----------------------------------------------------------------------------\ntemplate_ecodes = r\"\"\"\n#include <Python.h>\n#ifdef __FreeBSD__\n#include <dev/evdev/input.h>\n#include <dev/evdev/uinput.h>\n#else\n#include <linux/input.h>\n#include <linux/uinput.h>\n#endif\n\n/* Automatically generated by evdev.genecodes */\n/* Generated on   %s */\n/* Generated from %s */\n\n#define MODULE_NAME \"_ecodes\"\n#define MODULE_HELP \"linux/input.h macros\"\n\nstatic PyMethodDef MethodTable[] = {\n    { NULL, NULL, 0, NULL}\n};\n\nstatic struct PyModuleDef moduledef = {\n    PyModuleDef_HEAD_INIT,\n    MODULE_NAME,\n    MODULE_HELP,\n    -1,          /* m_size */\n    MethodTable, /* m_methods */\n    NULL,        /* m_reload */\n    NULL,        /* m_traverse */\n    NULL,        /* m_clear */\n    NULL,        /* m_free */\n};\n\nPyMODINIT_FUNC\nPyInit__ecodes(void)\n{\n    PyObject* m = PyModule_Create(&moduledef);\n    if (m == NULL) return NULL;\n\n%s\n\n    return m;\n}\n\"\"\"\n\n\ntemplate_stubs = r\"\"\"\n# Automatically generated by evdev.genecodes\n# Generated on %s\n# Generated from %s\n\n# pylint: skip-file\n\necodes: dict[str, int]\nkeys: dict[int, str|list[str]]\nbytype: dict[int, dict[int, str|list[str]]]\n\nKEY: dict[int, str|list[str]]\nABS: dict[int, str|list[str]]\nREL: dict[int, str|list[str]]\nSW:  dict[int, str|list[str]]\nMSC: dict[int, str|list[str]]\nLED: dict[int, str|list[str]]\nBTN: dict[int, str|list[str]]\nREP: dict[int, str|list[str]]\nSND: dict[int, str|list[str]]\nID:  dict[int, str|list[str]]\nEV:  dict[int, str|list[str]]\nBUS: dict[int, str|list[str]]\nSYN: dict[int, str|list[str]]\nFF_STATUS:     dict[int, str|list[str]]\nFF_INPUT_PROP: dict[int, str|list[str]]\n\n%s\n\"\"\"\n\n\ndef parse_headers(headers=headers):\n    for header in headers:\n        try:\n            fh = open(header)\n        except (IOError, OSError):\n            continue\n\n        for line in fh:\n            macro = macro_regex.search(line)\n            if macro:\n                yield macro.group(1)\n\n\nall_macros = list(parse_headers())\nif not all_macros:\n    print(\"no input macros found in: %s\" % \" \".join(headers), file=sys.stderr)\n    sys.exit(1)\n\n# pylint: disable=possibly-used-before-assignment, used-before-assignment\nif (\"--ecodes\", \"\") in opts:\n    body = (\"    PyModule_AddIntMacro(m, %s);\" % macro for macro in all_macros)\n    template = template_ecodes\nelif (\"--stubs\", \"\") in opts:\n    body = (\"%s: int\" % macro for macro in all_macros)\n    template = template_stubs\n\nbody = os.linesep.join(body)\ntext = template % (uname, headers if not reproducible else [\"hidden for reproducibility\"], body)\nprint(text.strip())\n"
  },
  {
    "path": "src/evdev/genecodes_py.py",
    "content": "import sys\nfrom unittest import mock\nfrom pprint import PrettyPrinter\n\nsys.modules[\"evdev.ecodes\"] = mock.Mock()\nfrom evdev import ecodes_runtime as ecodes\n\npprint = PrettyPrinter(indent=2, sort_dicts=True, width=120).pprint\n\n\nprint(\"# Automatically generated by evdev.genecodes_py\")\nprint()\nprint('\"\"\"')\nprint(ecodes.__doc__.strip())\nprint('\"\"\"')\n\nprint()\nprint(\"from typing import Final, Dict, Tuple, Union\")\nprint()\n\nfor name, value in ecodes.ecodes.items():\n    print(f\"{name}: Final[int] = {value}\")\nprint()\n\nentries = [\n    (\"ecodes\", \"Dict[str, int]\", \"#: Mapping of names to values.\"),\n    (\"bytype\", \"Dict[int, Dict[int, Union[str, Tuple[str]]]]\", \"#: Mapping of event types to other value/name mappings.\"),\n    (\"keys\",   \"Dict[int, Union[str, Tuple[str]]]\", \"#: Keys are a combination of all BTN and KEY codes.\"),\n    (\"KEY\",    \"Dict[int, Union[str, Tuple[str]]]\", None),\n    (\"ABS\",    \"Dict[int, Union[str, Tuple[str]]]\", None),\n    (\"REL\",    \"Dict[int, Union[str, Tuple[str]]]\", None),\n    (\"SW\",     \"Dict[int, Union[str, Tuple[str]]]\", None),\n    (\"MSC\",    \"Dict[int, Union[str, Tuple[str]]]\", None),\n    (\"LED\",    \"Dict[int, Union[str, Tuple[str]]]\", None),\n    (\"BTN\",    \"Dict[int, Union[str, Tuple[str]]]\", None),\n    (\"REP\",    \"Dict[int, Union[str, Tuple[str]]]\", None),\n    (\"SND\",    \"Dict[int, Union[str, Tuple[str]]]\", None),\n    (\"ID\",     \"Dict[int, Union[str, Tuple[str]]]\", None),\n    (\"EV\",     \"Dict[int, Union[str, Tuple[str]]]\", None),\n    (\"BUS\",    \"Dict[int, Union[str, Tuple[str]]]\", None),\n    (\"SYN\",    \"Dict[int, Union[str, Tuple[str]]]\", None),\n    (\"FF\",     \"Dict[int, Union[str, Tuple[str]]]\", None),\n    (\"UI_FF\",  \"Dict[int, Union[str, Tuple[str]]]\", None),\n    (\"FF_STATUS\",  \"Dict[int, Union[str, Tuple[str]]]\", None),\n    (\"INPUT_PROP\", \"Dict[int, Union[str, Tuple[str]]]\", None)\n]\n\nfor key, annotation, doc in entries:\n    if doc:\n        print(doc)\n\n    print(f\"{key}: {annotation} = \", end=\"\")\n    pprint(getattr(ecodes, key))\n    print()"
  },
  {
    "path": "src/evdev/input.c",
    "content": "\n/*\n * Python bindings to certain linux input subsystem functions.\n *\n * While everything here can be implemented in pure Python with struct and\n * fcntl.ioctl, imho, it is much more straightforward to do so in C.\n *\n */\n\n#include <Python.h>\n\n#include <stdio.h>\n#include <stdint.h>\n#include <string.h>\n#include <errno.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n#include <unistd.h>\n\n#ifdef __FreeBSD__\n#include <dev/evdev/input.h>\n#else\n#include <linux/input.h>\n#endif\n\n#ifndef input_event_sec\n#define input_event_sec time.tv_sec\n#define input_event_usec time.tv_usec\n#endif\n\n#define MAX_NAME_SIZE 256\n\nextern char*  EV_NAME[EV_CNT];\nextern int    EV_TYPE_MAX[EV_CNT];\nextern char** EV_TYPE_NAME[EV_CNT];\nextern char*  BUS_NAME[];\n\n\nint test_bit(const char* bitmask, int bit) {\n    return bitmask[bit/8] & (1 << (bit % 8));\n}\n\n\n// Read input event from a device and return a tuple that mimics input_event\nstatic PyObject *\ndevice_read(PyObject *self, PyObject *args)\n{\n    struct input_event event;\n\n    // get device file descriptor (O_RDONLY|O_NONBLOCK)\n    int fd = (int)PyLong_AsLong(PyTuple_GET_ITEM(args, 0));\n\n    int n = read(fd, &event, sizeof(event));\n\n    if (n < 0) {\n        if (errno == EAGAIN) {\n            Py_INCREF(Py_None);\n            return Py_None;\n        }\n\n        PyErr_SetFromErrno(PyExc_OSError);\n        return NULL;\n    }\n\n    PyObject *py_input_event = PyTuple_New(5);\n    PyTuple_SET_ITEM(py_input_event, 0, PyLong_FromLong(event.input_event_sec));\n    PyTuple_SET_ITEM(py_input_event, 1, PyLong_FromLong(event.input_event_usec));\n    PyTuple_SET_ITEM(py_input_event, 2, PyLong_FromLong(event.type));\n    PyTuple_SET_ITEM(py_input_event, 3, PyLong_FromLong(event.code));\n    PyTuple_SET_ITEM(py_input_event, 4, PyLong_FromLong(event.value));\n\n    return py_input_event;\n}\n\n\n// Read multiple input events from a device and return a list of tuples\nstatic PyObject *\ndevice_read_many(PyObject *self, PyObject *args)\n{\n    // get device file descriptor (O_RDONLY|O_NONBLOCK)\n    int fd = (int)PyLong_AsLong(PyTuple_GET_ITEM(args, 0));\n\n    struct input_event event[64];\n\n    size_t event_size = sizeof(struct input_event);\n    ssize_t nread = read(fd, event, event_size*64);\n\n    if (nread < 0) {\n        PyErr_SetFromErrno(PyExc_OSError);\n        return NULL;\n    }\n\n    // Construct a tuple of event tuples. Each tuple is the arguments to InputEvent.\n    size_t num_events = nread / event_size;\n\n    PyObject* events = PyTuple_New(num_events);\n    for (size_t i = 0 ; i < num_events; i++) {\n        PyObject *py_input_event = PyTuple_New(5);\n        PyTuple_SET_ITEM(py_input_event, 0, PyLong_FromLong(event[i].input_event_sec));\n        PyTuple_SET_ITEM(py_input_event, 1, PyLong_FromLong(event[i].input_event_usec));\n        PyTuple_SET_ITEM(py_input_event, 2, PyLong_FromLong(event[i].type));\n        PyTuple_SET_ITEM(py_input_event, 3, PyLong_FromLong(event[i].code));\n        PyTuple_SET_ITEM(py_input_event, 4, PyLong_FromLong(event[i].value));\n        PyTuple_SET_ITEM(events, i, py_input_event);\n    }\n\n    return events;\n}\n\n\n// Get the event types and event codes that the input device supports\nstatic PyObject *\nioctl_capabilities(PyObject *self, PyObject *args)\n{\n    int fd, ev_type, ev_code;\n    char ev_bits[EV_MAX/8 + 1], code_bits[KEY_MAX/8 + 1];\n    struct input_absinfo absinfo;\n\n    int ret = PyArg_ParseTuple(args, \"i\", &fd);\n    if (!ret) return NULL;\n\n    // @todo: figure out why fd gets zeroed on an ioctl after the\n    // refactoring and get rid of this workaround\n    const int _fd = fd;\n\n    // Capabilities is a mapping of supported event types to lists of handled\n    // events e.g: {1: [272, 273, 274, 275], 2: [0, 1, 6, 8]}\n    PyObject* capabilities = PyDict_New();\n    PyObject* eventcodes = NULL;\n    PyObject* evlong = NULL;\n    PyObject* capability = NULL;\n    PyObject* py_absinfo = NULL;\n    PyObject* absitem = NULL;\n\n    memset(&ev_bits, 0, sizeof(ev_bits));\n\n    if (ioctl(_fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) < 0)\n        goto on_err;\n\n    // Build a dictionary of the device's capabilities\n    for (ev_type=0 ; ev_type<EV_MAX ; ev_type++) {\n        if (test_bit(ev_bits, ev_type)) {\n\n            capability = PyLong_FromLong(ev_type);\n            eventcodes = PyList_New(0);\n\n            memset(&code_bits, 0, sizeof(code_bits));\n            ioctl(_fd, EVIOCGBIT(ev_type, sizeof(code_bits)), code_bits);\n\n            for (ev_code = 0; ev_code < KEY_MAX; ev_code++) {\n                if (test_bit(code_bits, ev_code)) {\n                    // Get abs{min,max,fuzz,flat} values for ABS_* event codes\n                    if (ev_type == EV_ABS) {\n                        memset(&absinfo, 0, sizeof(absinfo));\n                        ioctl(_fd, EVIOCGABS(ev_code), &absinfo);\n\n                        py_absinfo = Py_BuildValue(\"(iiiiii)\",\n                                                   absinfo.value,\n                                                   absinfo.minimum,\n                                                   absinfo.maximum,\n                                                   absinfo.fuzz,\n                                                   absinfo.flat,\n                                                   absinfo.resolution);\n\n                        evlong = PyLong_FromLong(ev_code);\n                        absitem = Py_BuildValue(\"(OO)\", evlong, py_absinfo);\n\n                        // absitem -> tuple(ABS_X, (0, 255, 0, 0))\n                        PyList_Append(eventcodes, absitem);\n\n                        Py_DECREF(absitem);\n                        Py_DECREF(py_absinfo);\n                    }\n                    else {\n                        evlong = PyLong_FromLong(ev_code);\n                        PyList_Append(eventcodes, evlong);\n                    }\n\n                    Py_DECREF(evlong);\n                }\n            }\n            // capabilities[EV_KEY] = [KEY_A, KEY_B, KEY_C, ...]\n            // capabilities[EV_ABS] = [(ABS_X, (0, 255, 0, 0)), ...]\n            PyDict_SetItem(capabilities, capability, eventcodes);\n\n            Py_DECREF(capability);\n            Py_DECREF(eventcodes);\n        }\n    }\n\n    return capabilities;\n\n    on_err:\n        Py_XDECREF(capabilities);\n        Py_XDECREF(eventcodes);\n        Py_XDECREF(capability);\n        Py_XDECREF(py_absinfo);\n        Py_XDECREF(absitem);\n        PyErr_SetFromErrno(PyExc_OSError);\n        return NULL;\n}\n\n\n// An all-in-one function for describing an input device\nstatic PyObject *\nioctl_devinfo(PyObject *self, PyObject *args)\n{\n    int fd;\n\n    struct input_id iid;\n    char name[MAX_NAME_SIZE];\n    char phys[MAX_NAME_SIZE] = {0};\n    char uniq[MAX_NAME_SIZE] = {0};\n\n    int ret = PyArg_ParseTuple(args, \"i\", &fd);\n    if (!ret) return NULL;\n\n    memset(&iid,  0, sizeof(iid));\n\n    if (ioctl(fd, EVIOCGID, &iid) < 0)                 goto on_err;\n    if (ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0) goto on_err;\n\n    // Some devices do not have a physical topology associated with them\n    ioctl(fd, EVIOCGPHYS(sizeof(phys)), phys);\n\n    // Some kernels have started reporting bluetooth controller MACs as phys.\n    // This lets us get the real physical address. As with phys, it may be blank.\n    ioctl(fd, EVIOCGUNIQ(sizeof(uniq)), uniq);\n\n    return Py_BuildValue(\"hhhhsss\", iid.bustype, iid.vendor, iid.product, iid.version,\n                         name, phys, uniq);\n\n    on_err:\n        PyErr_SetFromErrno(PyExc_OSError);\n        return NULL;\n}\n\n\nstatic PyObject *\nioctl_EVIOCGABS(PyObject *self, PyObject *args)\n{\n    int fd, ev_code;\n    struct input_absinfo absinfo;\n    PyObject* py_absinfo = NULL;\n\n    int ret = PyArg_ParseTuple(args, \"ii\", &fd, &ev_code);\n    if (!ret) return NULL;\n\n    memset(&absinfo, 0, sizeof(absinfo));\n    ret = ioctl(fd, EVIOCGABS(ev_code), &absinfo);\n    if (ret == -1) {\n        PyErr_SetFromErrno(PyExc_OSError);\n        return NULL;\n    }\n\n    py_absinfo = Py_BuildValue(\"(iiiiii)\",\n                               absinfo.value,\n                               absinfo.minimum,\n                               absinfo.maximum,\n                               absinfo.fuzz,\n                               absinfo.flat,\n                               absinfo.resolution);\n    return py_absinfo;\n}\n\n\nstatic PyObject *\nioctl_EVIOCSABS(PyObject *self, PyObject *args)\n{\n    int fd, ev_code;\n    struct input_absinfo absinfo;\n\n    int ret = PyArg_ParseTuple(args,\n                               \"ii(iiiiii)\",\n                               &fd,\n                               &ev_code,\n                               &absinfo.value,\n                               &absinfo.minimum,\n                               &absinfo.maximum,\n                               &absinfo.fuzz,\n                               &absinfo.flat,\n                               &absinfo.resolution);\n    if (!ret) return NULL;\n\n    ret = ioctl(fd, EVIOCSABS(ev_code), &absinfo);\n    if (ret == -1) {\n        PyErr_SetFromErrno(PyExc_OSError);\n        return NULL;\n    }\n\n    Py_INCREF(Py_None);\n    return Py_None;\n}\n\n\nstatic PyObject *\nioctl_EVIOCGREP(PyObject *self, PyObject *args)\n{\n    int fd, ret;\n    unsigned int rep[REP_CNT] = {0};\n    ret = PyArg_ParseTuple(args, \"i\", &fd);\n    if (!ret) return NULL;\n\n    ret = ioctl(fd, EVIOCGREP, &rep);\n    if (ret == -1)\n        return NULL;\n\n    return Py_BuildValue(\"(ii)\", rep[REP_DELAY], rep[REP_PERIOD]);\n}\n\n\nstatic PyObject *\nioctl_EVIOCSREP(PyObject *self, PyObject *args)\n{\n    int fd, ret;\n    unsigned int rep[REP_CNT] = {0};\n\n    ret = PyArg_ParseTuple(args, \"iii\", &fd, &rep[0], &rep[1]);\n    if (!ret) return NULL;\n\n    ret = ioctl(fd, EVIOCSREP, &rep);\n    if (ret == -1)\n        return NULL;\n\n    return Py_BuildValue(\"i\", ret);\n}\n\n\nstatic PyObject *\nioctl_EVIOCGVERSION(PyObject *self, PyObject *args)\n{\n    int fd, ret, res;\n    ret = PyArg_ParseTuple(args, \"i\", &fd);\n    if (!ret) return NULL;\n\n    ret = ioctl(fd, EVIOCGVERSION, &res);\n    if (ret == -1)\n        return NULL;\n\n    return Py_BuildValue(\"i\", res);\n}\n\n\nstatic PyObject *\nioctl_EVIOCGRAB(PyObject *self, PyObject *args)\n{\n    int fd, ret, flag;\n    ret = PyArg_ParseTuple(args, \"ii\", &fd, &flag);\n    if (!ret) return NULL;\n\n    ret = ioctl(fd, EVIOCGRAB, (intptr_t)flag);\n    if (ret != 0) {\n        PyErr_SetFromErrno(PyExc_OSError);\n        return NULL;\n    }\n\n    Py_INCREF(Py_None);\n    return Py_None;\n}\n\n\nstatic PyObject *\nioctl_EVIOCG_bits(PyObject *self, PyObject *args)\n{\n    int max, fd, evtype, ret;\n\n    ret = PyArg_ParseTuple(args, \"ii\", &fd, &evtype);\n    if (!ret) return NULL;\n\n    switch (evtype) {\n    case EV_LED:\n        max = LED_MAX; break;\n    case EV_SND:\n        max = SND_MAX; break;\n    case EV_KEY:\n        max = KEY_MAX; break;\n    case EV_SW:\n        max = SW_MAX; break;\n    default:\n        return NULL;\n    }\n\n    char bytes[(max+7)/8];\n    memset(bytes, 0, sizeof bytes);\n\n    switch (evtype) {\n    case EV_LED:\n        ret = ioctl(fd, EVIOCGLED(sizeof(bytes)), &bytes);\n        break;\n    case EV_SND:\n        ret = ioctl(fd, EVIOCGSND(sizeof(bytes)), &bytes);\n        break;\n    case EV_KEY:\n        ret = ioctl(fd, EVIOCGKEY(sizeof(bytes)), &bytes);\n        break;\n    case EV_SW:\n        ret = ioctl(fd, EVIOCGSW(sizeof(bytes)), &bytes);\n        break;\n    }\n\n    if (ret == -1)\n        return NULL;\n\n    PyObject* res = PyList_New(0);\n    for (int i=0; i<=max; i++) {\n        if (test_bit(bytes, i)) {\n            PyObject *val = PyLong_FromLong(i);\n            PyList_Append(res, val);\n            Py_DECREF(val);\n        }\n    }\n\n    return res;\n}\n\n\nstatic PyObject *\nioctl_EVIOCGEFFECTS(PyObject *self, PyObject *args)\n{\n    int fd, ret, res;\n    ret = PyArg_ParseTuple(args, \"i\", &fd);\n    if (!ret) return NULL;\n\n    ret = ioctl(fd, EVIOCGEFFECTS, &res);\n    if (ret == -1)\n        return NULL;\n\n    return Py_BuildValue(\"i\", res);\n}\n\nvoid print_ff_effect(struct ff_effect* effect) {\n    fprintf(stderr,\n            \"ff_effect:\\n\"\n            \"  type: %d     \\n\"\n            \"  id:   %d     \\n\"\n            \"  direction: %d\\n\"\n            \"  trigger: (%d, %d)\\n\"\n            \"  replay:  (%d, %d)\\n\",\n            effect->type, effect->id, effect->direction,\n            effect->trigger.button, effect->trigger.interval,\n            effect->replay.length, effect->replay.delay\n        );\n\n\n    switch (effect->type) {\n    case FF_CONSTANT:\n        fprintf(stderr, \"  constant: (%d, (%d, %d, %d, %d))\\n\", effect->u.constant.level,\n                effect->u.constant.envelope.attack_length,\n                effect->u.constant.envelope.attack_level,\n                effect->u.constant.envelope.fade_length,\n                effect->u.constant.envelope.fade_level);\n        break;\n    case FF_RUMBLE:\n        fprintf(stderr, \"  rumble: (%d, %d)\\n\",\n\t\teffect->u.rumble.strong_magnitude,\n\t\teffect->u.rumble.weak_magnitude);\n        break;\n    }\n}\n\n\nstatic PyObject *\nupload_effect(PyObject *self, PyObject *args)\n{\n    int fd, ret;\n    PyObject* effect_data;\n    ret = PyArg_ParseTuple(args, \"iO\", &fd, &effect_data);\n    if (!ret) return NULL;\n\n    void* data = PyBytes_AsString(effect_data);\n    struct ff_effect effect = {};\n    memmove(&effect, data, sizeof(struct ff_effect));\n\n    // print_ff_effect(&effect);\n\n    ret = ioctl(fd, EVIOCSFF, &effect);\n    if (ret != 0) {\n        PyErr_SetFromErrno(PyExc_OSError);\n        return NULL;\n    }\n\n    return Py_BuildValue(\"i\", effect.id);\n}\n\n\nstatic PyObject *\nerase_effect(PyObject *self, PyObject *args)\n{\n    int fd, ret;\n    PyObject* ff_id_obj;\n    ret = PyArg_ParseTuple(args, \"iO\", &fd, &ff_id_obj);\n    if (!ret) return NULL;\n\n    long ff_id = PyLong_AsLong(ff_id_obj);\n    ret = ioctl(fd, EVIOCRMFF, ff_id);\n    if (ret != 0) {\n        PyErr_SetFromErrno(PyExc_OSError);\n        return NULL;\n    }\n\n    Py_INCREF(Py_None);\n    return Py_None;\n}\n\nstatic PyObject *\nioctl_EVIOCGPROP(PyObject *self, PyObject *args)\n{\n    int fd, ret;\n\n    ret = PyArg_ParseTuple(args, \"i\", &fd);\n    if (!ret) return NULL;\n\n    char bytes[(INPUT_PROP_MAX+7)/8];\n    memset(bytes, 0, sizeof bytes);\n\n    ret = ioctl(fd, EVIOCGPROP(sizeof(bytes)), &bytes);\n\n    if (ret == -1)\n        return NULL;\n\n    PyObject* res = PyList_New(0);\n    for (int i=0; i<INPUT_PROP_MAX; i++) {\n        if (test_bit(bytes, i)) {\n            PyObject *val = PyLong_FromLong(i);\n            PyList_Append(res, val);\n            Py_DECREF(val);\n        }\n    }\n\n    return res;\n}\n\n\nstatic PyMethodDef MethodTable[] = {\n    { \"ioctl_devinfo\",        ioctl_devinfo,        METH_VARARGS, \"fetch input device info\" },\n    { \"ioctl_capabilities\",   ioctl_capabilities,   METH_VARARGS, \"fetch input device capabilities\" },\n    { \"ioctl_EVIOCGABS\",      ioctl_EVIOCGABS,      METH_VARARGS, \"get input device absinfo\"},\n    { \"ioctl_EVIOCSABS\",      ioctl_EVIOCSABS,      METH_VARARGS, \"set input device absinfo\"},\n    { \"ioctl_EVIOCGREP\",      ioctl_EVIOCGREP,      METH_VARARGS},\n    { \"ioctl_EVIOCSREP\",      ioctl_EVIOCSREP,      METH_VARARGS},\n    { \"ioctl_EVIOCGVERSION\",  ioctl_EVIOCGVERSION,  METH_VARARGS},\n    { \"ioctl_EVIOCGRAB\",      ioctl_EVIOCGRAB,      METH_VARARGS},\n    { \"ioctl_EVIOCGEFFECTS\",  ioctl_EVIOCGEFFECTS,  METH_VARARGS, \"fetch the number of effects the device can keep in its memory.\" },\n    { \"ioctl_EVIOCG_bits\",    ioctl_EVIOCG_bits,    METH_VARARGS, \"get state of KEY|LED|SND|SW\"},\n    { \"ioctl_EVIOCGPROP\",     ioctl_EVIOCGPROP,     METH_VARARGS, \"get device properties\"},\n    { \"device_read\",          device_read,          METH_VARARGS, \"read an input event from a device\" },\n    { \"device_read_many\",     device_read_many,     METH_VARARGS, \"read all available input events from a device\" },\n    { \"upload_effect\",        upload_effect,        METH_VARARGS, \"\" },\n    { \"erase_effect\",         erase_effect,         METH_VARARGS, \"\" },\n\n    { NULL, NULL, 0, NULL}\n};\n\n\nstatic struct PyModuleDef moduledef = {\n    PyModuleDef_HEAD_INIT,\n    \"_input\",\n    \"Python bindings to certain linux input subsystem functions\",\n    -1,          /* m_size */\n    MethodTable, /* m_methods */\n    NULL,        /* m_reload */\n    NULL,        /* m_traverse */\n    NULL,        /* m_clear */\n    NULL,        /* m_free */\n};\n\nstatic PyObject *\nmoduleinit(void)\n{\n    PyObject* m = PyModule_Create(&moduledef);\n    if (m == NULL) return NULL;\n    return m;\n}\n\nPyMODINIT_FUNC\nPyInit__input(void)\n{\n    return moduleinit();\n}\n"
  },
  {
    "path": "src/evdev/py.typed",
    "content": ""
  },
  {
    "path": "src/evdev/uinput.c",
    "content": "#include <Python.h>\n\n#include <stdio.h>\n#include <string.h>\n#include <errno.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n#include <unistd.h>\n\n#ifdef __FreeBSD__\n#include <dev/evdev/input.h>\n#include <dev/evdev/uinput.h>\n#else\n#include <linux/input.h>\n#include <linux/uinput.h>\n#endif\n\n#ifndef input_event_sec\n#define input_event_sec time.tv_sec\n#define input_event_usec time.tv_usec\n#endif\n\n// Workaround for installing on kernels newer than 4.4.\n#ifndef FF_MAX_EFFECTS\n#define FF_MAX_EFFECTS FF_GAIN;\n#endif\n\nint _uinput_close(int fd)\n{\n    if (ioctl(fd, UI_DEV_DESTROY) < 0) {\n        int oerrno = errno;\n        close(fd);\n        errno = oerrno;\n        return -1;\n    }\n\n    return close(fd);\n}\n\n\nstatic PyObject *\nuinput_open(PyObject *self, PyObject *args)\n{\n    const char* devnode;\n\n    int ret = PyArg_ParseTuple(args, \"s\", &devnode);\n    if (!ret) return NULL;\n\n    int fd = open(devnode, O_RDWR | O_NONBLOCK);\n    if (fd < 0) {\n        PyErr_SetString(PyExc_OSError, \"could not open uinput device in write mode\");\n        return NULL;\n    }\n\n    return Py_BuildValue(\"i\", fd);\n}\n\n\nstatic PyObject *\nuinput_set_phys(PyObject *self, PyObject *args)\n{\n    int fd;\n    const char* phys;\n\n    int ret = PyArg_ParseTuple(args, \"is\", &fd, &phys);\n    if (!ret) return NULL;\n\n    if (ioctl(fd, UI_SET_PHYS, phys) < 0)\n        goto on_err;\n\n    Py_RETURN_NONE;\n\n    on_err:\n        _uinput_close(fd);\n        PyErr_SetFromErrno(PyExc_OSError);\n        return NULL;\n}\n\nstatic PyObject *\nuinput_set_prop(PyObject *self, PyObject *args)\n{\n    int fd;\n    uint16_t prop;\n\n    int ret = PyArg_ParseTuple(args, \"ih\", &fd, &prop);\n    if (!ret) return NULL;\n\n    if (ioctl(fd, UI_SET_PROPBIT, prop) < 0)\n        goto on_err;\n\n    Py_RETURN_NONE;\n\n    on_err:\n        _uinput_close(fd);\n        PyErr_SetFromErrno(PyExc_OSError);\n        return NULL;\n}\n\nstatic PyObject *\nuinput_get_sysname(PyObject *self, PyObject *args)\n{\n    int fd;\n    char sysname[64];\n\n    int ret = PyArg_ParseTuple(args, \"i\", &fd);\n    if (!ret) return NULL;\n\n    #ifdef UI_GET_SYSNAME\n    if (ioctl(fd, UI_GET_SYSNAME(sizeof(sysname)), &sysname) < 0)\n        goto on_err;\n\n    return Py_BuildValue(\"s\", &sysname);\n    #endif\n\n    on_err:\n        PyErr_SetFromErrno(PyExc_OSError);\n        return NULL;\n}\n\n// Different kernel versions have different device setup methods. You can read\n// more about it here:\n// https://github.com/torvalds/linux/commit/052876f8e5aec887d22c4d06e54aa5531ffcec75\n\n// Setup function for kernel >= v4.5\n#if defined(UI_DEV_SETUP) && defined(UI_ABS_SETUP)\nstatic PyObject *\nuinput_setup(PyObject *self, PyObject *args) {\n    int fd, len, i;\n    uint16_t vendor, product, version, bustype;\n    uint32_t max_effects;\n\n    PyObject *absinfo = NULL, *item = NULL;\n\n    struct uinput_abs_setup abs_setup;\n\n    const char* name;\n    int ret = PyArg_ParseTuple(args, \"isHHHHOI\", &fd, &name, &vendor,\n                               &product, &version, &bustype, &absinfo, &max_effects);\n    if (!ret) return NULL;\n\n    // Setup absinfo:\n    len = PyList_Size(absinfo);\n    for (i=0; i<len; i++) {\n\n        // item -> (ABS_X, 0, 255, 0, 0, 0, 0)\n        item = PyList_GetItem(absinfo, i);\n\n        memset(&abs_setup, 0, sizeof(abs_setup)); // Clear struct\n        abs_setup.code = PyLong_AsLong(PyList_GetItem(item, 0));\n        abs_setup.absinfo.value = PyLong_AsLong(PyList_GetItem(item, 1));\n        abs_setup.absinfo.minimum = PyLong_AsLong(PyList_GetItem(item, 2));\n        abs_setup.absinfo.maximum = PyLong_AsLong(PyList_GetItem(item, 3));\n        abs_setup.absinfo.fuzz = PyLong_AsLong(PyList_GetItem(item, 4));\n        abs_setup.absinfo.flat = PyLong_AsLong(PyList_GetItem(item, 5));\n        abs_setup.absinfo.resolution = PyLong_AsLong(PyList_GetItem(item, 6));\n\n        if(ioctl(fd, UI_ABS_SETUP, &abs_setup) < 0)\n            goto on_err;\n    }\n\n    // Setup evdev:\n    struct uinput_setup usetup;\n\n    memset(&usetup, 0, sizeof(usetup));\n    strncpy(usetup.name, name, sizeof(usetup.name) - 1);\n    usetup.id.vendor  = vendor;\n    usetup.id.product = product;\n    usetup.id.version = version;\n    usetup.id.bustype = bustype;\n    usetup.ff_effects_max = max_effects;\n\n    if(ioctl(fd, UI_DEV_SETUP, &usetup) < 0)\n        goto on_err;\n\n    Py_RETURN_NONE;\n\n    on_err:\n        _uinput_close(fd);\n        PyErr_SetFromErrno(PyExc_OSError);\n        return NULL;\n}\n\n// Fallback setup function (Linux <= 4.5 and FreeBSD).\n#else\nstatic PyObject *\nuinput_setup(PyObject *self, PyObject *args) {\n    int fd, len, i, abscode;\n    uint16_t vendor, product, version, bustype;\n    uint32_t max_effects;\n\n    PyObject *absinfo = NULL, *item = NULL;\n\n    struct uinput_user_dev uidev;\n    const char* name;\n\n    int ret = PyArg_ParseTuple(args, \"isHHHHOI\", &fd, &name, &vendor,\n                               &product, &version, &bustype, &absinfo, &max_effects);\n    if (!ret) return NULL;\n\n    memset(&uidev, 0, sizeof(uidev));\n    strncpy(uidev.name, name, sizeof(uidev.name) - 1);\n    uidev.id.vendor  = vendor;\n    uidev.id.product = product;\n    uidev.id.version = version;\n    uidev.id.bustype = bustype;\n    uidev.ff_effects_max = max_effects;\n\n    len = PyList_Size(absinfo);\n    for (i=0; i<len; i++) {\n        // item -> (ABS_X, 0, 255, 0, 0, 0, 0)\n        item = PyList_GetItem(absinfo, i);\n        abscode = (int)PyLong_AsLong(PyList_GetItem(item, 0));\n\n        /* min/max/fuzz/flat start from index 2 because index 1 is value */\n        uidev.absmin[abscode]  = PyLong_AsLong(PyList_GetItem(item, 2));\n        uidev.absmax[abscode]  = PyLong_AsLong(PyList_GetItem(item, 3));\n        uidev.absfuzz[abscode] = PyLong_AsLong(PyList_GetItem(item, 4));\n        uidev.absflat[abscode] = PyLong_AsLong(PyList_GetItem(item, 5));\n    }\n\n    if (write(fd, &uidev, sizeof(uidev)) != sizeof(uidev))\n        goto on_err;\n\n    Py_RETURN_NONE;\n\n    on_err:\n        _uinput_close(fd);\n        PyErr_SetFromErrno(PyExc_OSError);\n        return NULL;\n}\n#endif\n\n\nstatic PyObject *\nuinput_create(PyObject *self, PyObject *args)\n{\n    int fd;\n\n    int ret = PyArg_ParseTuple(args, \"i\", &fd);\n    if (!ret) return NULL;\n\n    if (ioctl(fd, UI_DEV_CREATE) < 0)\n        goto on_err;\n\n    Py_RETURN_NONE;\n\n    on_err:\n        _uinput_close(fd);\n        PyErr_SetFromErrno(PyExc_OSError);\n        return NULL;\n}\n\n\nstatic PyObject *\nuinput_close(PyObject *self, PyObject *args)\n{\n    int fd;\n\n    int ret = PyArg_ParseTuple(args, \"i\", &fd);\n    if (!ret) return NULL;\n\n    if (_uinput_close(fd) < 0) {\n        PyErr_SetFromErrno(PyExc_OSError);\n        return NULL;\n    }\n\n    Py_RETURN_NONE;\n}\n\n\nstatic PyObject *\nuinput_write(PyObject *self, PyObject *args)\n{\n    int fd, type, code, value;\n\n    int ret = PyArg_ParseTuple(args, \"iiii\", &fd, &type, &code, &value);\n    if (!ret) return NULL;\n\n    struct input_event event;\n    struct timeval tval;\n    memset(&event, 0, sizeof(event));\n    gettimeofday(&tval, 0);\n    event.input_event_usec = tval.tv_usec;\n    event.input_event_sec = tval.tv_sec;\n    event.type = type;\n    event.code = code;\n    event.value = value;\n\n    if (write(fd, &event, sizeof(event)) != sizeof(event)) {\n        // @todo: elaborate\n        // PyErr_SetString(PyExc_OSError, \"error writing event to uinput device\");\n        PyErr_SetFromErrno(PyExc_OSError);\n        return NULL;\n    }\n\n    Py_RETURN_NONE;\n}\n\n\nstatic PyObject *\nuinput_enable_event(PyObject *self, PyObject *args)\n{\n    int fd;\n    uint16_t type, code;\n    unsigned long req;\n\n    int ret = PyArg_ParseTuple(args, \"ihh\", &fd, &type, &code);\n    if (!ret) return NULL;\n\n    switch (type) {\n        case EV_KEY: req = UI_SET_KEYBIT; break;\n        case EV_ABS: req = UI_SET_ABSBIT; break;\n        case EV_REL: req = UI_SET_RELBIT; break;\n        case EV_MSC: req = UI_SET_MSCBIT; break;\n        case EV_SW:  req = UI_SET_SWBIT;  break;\n        case EV_LED: req = UI_SET_LEDBIT; break;\n        case EV_FF:  req = UI_SET_FFBIT;  break;\n        case EV_SND: req = UI_SET_SNDBIT; break;\n        default:\n            errno = EINVAL;\n            goto on_err;\n    }\n\n    if (ioctl(fd, UI_SET_EVBIT, type) < 0)\n        goto on_err;\n\n    if (ioctl(fd, req, code) < 0)\n        goto on_err;\n\n    Py_RETURN_NONE;\n\n    on_err:\n        _uinput_close(fd);\n        PyErr_SetFromErrno(PyExc_OSError);\n        return NULL;\n}\n\nint _uinput_begin_upload(int fd, struct uinput_ff_upload *upload)\n{\n    return ioctl(fd, UI_BEGIN_FF_UPLOAD, upload);\n}\n\nint _uinput_end_upload(int fd, struct uinput_ff_upload *upload)\n{\n    return ioctl(fd, UI_END_FF_UPLOAD, upload);\n}\n\nint _uinput_begin_erase(int fd, struct uinput_ff_erase *upload)\n{\n    return ioctl(fd, UI_BEGIN_FF_ERASE, upload);\n}\n\nint _uinput_end_erase(int fd, struct uinput_ff_erase *upload)\n{\n    return ioctl(fd, UI_END_FF_ERASE, upload);\n}\n\n\nstatic PyMethodDef MethodTable[] = {\n    { \"open\",  uinput_open, METH_VARARGS,\n      \"Open uinput device node.\"},\n\n    { \"setup\",  uinput_setup, METH_VARARGS,\n      \"Set an uinput device up.\"},\n\n    { \"create\",  uinput_create, METH_VARARGS,\n      \"Create an uinput device.\"},\n\n    { \"close\",  uinput_close, METH_VARARGS,\n      \"Destroy uinput device.\"},\n\n    { \"write\",  uinput_write, METH_VARARGS,\n      \"Write event to uinput device.\"},\n\n    { \"enable\", uinput_enable_event, METH_VARARGS,\n      \"Enable a type of event.\"},\n\n    { \"set_phys\", uinput_set_phys, METH_VARARGS,\n      \"Set physical path\"},\n\n    { \"get_sysname\", uinput_get_sysname, METH_VARARGS,\n      \"Obtain the sysname of the uinput device.\"},\n\n    { \"set_prop\", uinput_set_prop, METH_VARARGS,\n      \"Set device input property\"},\n\n    { NULL, NULL, 0, NULL}\n};\n\nstatic struct PyModuleDef moduledef = {\n    PyModuleDef_HEAD_INIT,\n    \"_uinput\",\n    \"Python bindings for parts of linux/uinput.c\",\n    -1,          /* m_size */\n    MethodTable, /* m_methods */\n    NULL,        /* m_reload */\n    NULL,        /* m_traverse */\n    NULL,        /* m_clear */\n    NULL,        /* m_free */\n};\n\nstatic PyObject *\nmoduleinit(void)\n{\n    PyObject* m = PyModule_Create(&moduledef);\n    if (m == NULL) return NULL;\n\n    PyModule_AddIntConstant(m, \"maxnamelen\", UINPUT_MAX_NAME_SIZE);\n    return m;\n}\n\nPyMODINIT_FUNC\nPyInit__uinput(void)\n{\n    return moduleinit();\n}\n"
  },
  {
    "path": "src/evdev/uinput.py",
    "content": "import ctypes\nimport os\nimport platform\nimport re\nimport stat\nimport time\nfrom collections import defaultdict\nfrom typing import Union, Tuple, Dict, Sequence, Optional\n\nfrom . import _uinput, ecodes, ff, util\nfrom .device import InputDevice, AbsInfo\nfrom .events import InputEvent\n\ntry:\n    from evdev.eventio_async import EventIO\nexcept ImportError:\n    from evdev.eventio import EventIO\n\n\nclass UInputError(Exception):\n    pass\n\n\nclass UInput(EventIO):\n    \"\"\"\n    A userland input device and that can inject input events into the\n    linux input subsystem.\n    \"\"\"\n\n    __slots__ = (\n        \"name\",\n        \"vendor\",\n        \"product\",\n        \"version\",\n        \"bustype\",\n        \"events\",\n        \"devnode\",\n        \"fd\",\n        \"device\",\n    )\n\n    @classmethod\n    def from_device(\n        cls,\n        *devices: Union[InputDevice, Union[str, bytes, os.PathLike]],\n        filtered_types: Tuple[int] = (ecodes.EV_SYN, ecodes.EV_FF),\n        **kwargs,\n    ):\n        \"\"\"\n        Create an UInput device with the capabilities of one or more input\n        devices.\n\n        Arguments\n        ---------\n        devices : InputDevice|str\n          Varargs of InputDevice instances or paths to input devices.\n\n        filtered_types : Tuple[event type codes]\n          Event types to exclude from the capabilities of the uinput device.\n\n        **kwargs\n          Keyword arguments to UInput constructor (i.e. name, vendor etc.).\n        \"\"\"\n\n        device_instances = []\n        for dev in devices:\n            if not isinstance(dev, InputDevice):\n                dev = InputDevice(str(dev))\n            device_instances.append(dev)\n\n        all_capabilities = defaultdict(set)\n\n        if \"max_effects\" not in kwargs:\n            kwargs[\"max_effects\"] = min([dev.ff_effects_count for dev in device_instances])\n\n        # Merge the capabilities of all devices into one dictionary.\n        for dev in device_instances:\n            for ev_type, ev_codes in dev.capabilities().items():\n                all_capabilities[ev_type].update(ev_codes)\n\n        for evtype in filtered_types:\n            if evtype in all_capabilities:\n                del all_capabilities[evtype]\n\n        return cls(events=all_capabilities, **kwargs)\n\n    def __init__(\n        self,\n        events: Optional[Dict[int, Sequence[int]]] = None,\n        name: str = \"py-evdev-uinput\",\n        vendor: int = 0x1,\n        product: int = 0x1,\n        version: int = 0x1,\n        bustype: int = 0x3,\n        devnode: str = \"/dev/uinput\",\n        phys: str = \"py-evdev-uinput\",\n        input_props=None,\n        # CentOS 7 has sufficiently old headers that FF_MAX_EFFECTS is not defined there,\n        # which causes the whole module to fail loading. Fallback on a hardcoded value of\n        # FF_MAX_EFFECTS if it is not defined in the ecodes.\n        max_effects=ecodes.ecodes.get(\"FF_MAX_EFFECTS\", 96),\n    ):\n        \"\"\"\n        Arguments\n        ---------\n        events : dict\n          Dictionary of event types mapping to lists of event codes. The\n          event types and codes that the uinput device will be able to\n          inject - defaults to all key codes.\n\n        name\n          The name of the input device.\n\n        vendor\n          Vendor identifier.\n\n        product\n          Product identifier.\n\n        version\n          Version identifier.\n\n        bustype\n          Bustype identifier.\n\n        phys\n          Physical path.\n\n        input_props\n          Input properties and quirks.\n\n        max_effects\n          Maximum simultaneous force-feedback effects.\n\n        Note\n        ----\n        If you do not specify any events, the uinput device will be able\n        to inject only ``KEY_*`` and ``BTN_*`` event codes.\n        \"\"\"\n\n        self.name: str = name  #: Uinput device name.\n        self.vendor: int = vendor  #: Device vendor identifier.\n        self.product: int = product  #: Device product identifier.\n        self.version: int = version  #: Device version identifier.\n        self.bustype: int = bustype  #: Device bustype - e.g. ``BUS_USB``.\n        self.phys: str = phys  #: Uinput device physical path.\n        self.devnode: str = devnode  #: Uinput device node - e.g. ``/dev/uinput/``.\n\n        if not events:\n            events = {ecodes.EV_KEY: ecodes.keys.keys()}\n\n        self._verify()\n\n        #: Write-only, non-blocking file descriptor to the uinput device node.\n        self.fd = _uinput.open(devnode)\n\n        # Prepare the list of events for passing to _uinput.enable and _uinput.setup.\n        absinfo, prepared_events = self._prepare_events(events)\n\n        # Set phys name\n        _uinput.set_phys(self.fd, phys)\n\n        # Set properties\n        input_props = input_props or []\n        for prop in input_props:\n            _uinput.set_prop(self.fd, prop)\n\n        for etype, code in prepared_events:\n            _uinput.enable(self.fd, etype, code)\n\n        _uinput.setup(self.fd, name, vendor, product, version, bustype, absinfo, max_effects)\n\n        # Create the uinput device.\n        _uinput.create(self.fd)\n\n        self.dll = ctypes.CDLL(_uinput.__file__)\n        self.dll._uinput_begin_upload.restype = ctypes.c_int\n        self.dll._uinput_end_upload.restype = ctypes.c_int\n\n        #: An :class:`InputDevice <evdev.device.InputDevice>` instance\n        #: for the fake input device. ``None`` if the device cannot be\n        #: opened for reading and writing.\n        self.device: InputDevice = self._find_device(self.fd)\n\n    def _prepare_events(self, events):\n        \"\"\"Prepare events for passing to _uinput.enable and _uinput.setup\"\"\"\n        absinfo, prepared_events = [], []\n        for etype, codes in events.items():\n            for code in codes:\n                # Handle max, min, fuzz, flat.\n                if isinstance(code, (tuple, list, AbsInfo)):\n                    # Flatten (ABS_Y, (0, 255, 0, 0, 0, 0)) to (ABS_Y, 0, 255, 0, 0, 0, 0).\n                    f = [code[0]]\n                    f.extend(code[1])\n                    # Ensure the tuple is always 6 ints long, since uinput.c:uinput_create\n                    # does little in the way of checking the length.\n                    f.extend([0] * (6 - len(code[1])))\n                    absinfo.append(f)\n                    code = code[0]\n                prepared_events.append((etype, code))\n        return absinfo, prepared_events\n\n    def __enter__(self):\n        return self\n\n    def __exit__(self, type, value, tb):\n        if hasattr(self, \"fd\"):\n            self.close()\n\n    def __repr__(self):\n        # TODO:\n        v = (repr(getattr(self, i)) for i in (\"name\", \"bustype\", \"vendor\", \"product\", \"version\", \"phys\"))\n        return \"{}({})\".format(self.__class__.__name__, \", \".join(v))\n\n    def __str__(self):\n        msg = 'name \"{}\", bus \"{}\", vendor \"{:04x}\", product \"{:04x}\", version \"{:04x}\", phys \"{}\"\\nevent types: {}'\n\n        evtypes = [i[0] for i in self.capabilities(True).keys()]\n        msg = msg.format(\n            self.name, ecodes.BUS[self.bustype], self.vendor, self.product, self.version, self.phys, \" \".join(evtypes)\n        )\n\n        return msg\n\n    def close(self):\n        # Close the associated InputDevice, if it was previously opened.\n        if self.device is not None:\n            self.device.close()\n\n        # Destroy the uinput device.\n        if self.fd > -1:\n            _uinput.close(self.fd)\n            self.fd = -1\n\n    def capabilities(self, verbose: bool = False, absinfo: bool = True):\n        \"\"\"See :func:`capabilities <evdev.device.InputDevice.capabilities>`.\"\"\"\n        if self.device is None:\n            raise UInputError(\"input device not opened - cannot read capabilities\")\n\n        return self.device.capabilities(verbose, absinfo)\n\n    def begin_upload(self, effect_id):\n        upload = ff.UInputUpload()\n        upload.effect_id = effect_id\n\n        ret = self.dll._uinput_begin_upload(self.fd, ctypes.byref(upload))\n        if ret:\n            raise UInputError(\"Failed to begin uinput upload: \" + os.strerror(ret))\n\n        return upload\n\n    def end_upload(self, upload):\n        ret = self.dll._uinput_end_upload(self.fd, ctypes.byref(upload))\n        if ret:\n            raise UInputError(\"Failed to end uinput upload: \" + os.strerror(ret))\n\n    def begin_erase(self, effect_id):\n        erase = ff.UInputErase()\n        erase.effect_id = effect_id\n\n        ret = self.dll._uinput_begin_erase(self.fd, ctypes.byref(erase))\n        if ret:\n            raise UInputError(\"Failed to begin uinput erase: \" + os.strerror(ret))\n        return erase\n\n    def end_erase(self, erase):\n        ret = self.dll._uinput_end_erase(self.fd, ctypes.byref(erase))\n        if ret:\n            raise UInputError(\"Failed to end uinput erase: \" + os.strerror(ret))\n\n    def _verify(self):\n        \"\"\"\n        Verify that an uinput device exists and is readable and writable\n        by the current process.\n        \"\"\"\n        try:\n            m = os.stat(self.devnode)[stat.ST_MODE]\n            assert stat.S_ISCHR(m)\n        except (IndexError, OSError, AssertionError):\n            msg = '\"{}\" does not exist or is not a character device file - verify that the uinput module is loaded'\n            raise UInputError(msg.format(self.devnode))\n\n        if not os.access(self.devnode, os.W_OK):\n            msg = '\"{}\" cannot be opened for writing'\n            raise UInputError(msg.format(self.devnode))\n\n        if len(self.name) > _uinput.maxnamelen:\n            msg = \"uinput device name must not be longer than {} characters\"\n            raise UInputError(msg.format(_uinput.maxnamelen))\n\n    def _find_device(self, fd: int) -> InputDevice:\n        \"\"\"\n        Tries to find the device node. Will delegate this task to one of\n        several platform-specific functions.\n        \"\"\"\n        if platform.system() == \"Linux\":\n            try:\n                sysname = _uinput.get_sysname(fd)\n                return self._find_device_linux(sysname)\n            except OSError:\n                # UI_GET_SYSNAME returned an error code. We're likely dealing with\n                # an old kernel. Guess the device based on the filesystem.\n                pass\n\n        # If we're not running or Linux or the above method fails for any reason,\n        # use the generic fallback method.\n        return self._find_device_fallback()\n\n    def _find_device_linux(self, sysname: str) -> InputDevice:\n        \"\"\"\n        Tries to find the device node when running on Linux.\n        \"\"\"\n\n        syspath = f\"/sys/devices/virtual/input/{sysname}\"\n\n        # The sysfs entry for event devices should contain exactly one folder\n        # whose name matches the format \"event[0-9]+\". It is then assumed that\n        # the device node in /dev/input uses the same name.\n        regex = re.compile(\"event[0-9]+\")\n        for entry in os.listdir(syspath):\n            if regex.fullmatch(entry):\n                device_path = f\"/dev/input/{entry}\"\n                break\n        else:  # no break\n            raise FileNotFoundError()\n\n        # It is possible that there is some delay before /dev/input/event* shows\n        # up on old systems that do not use devtmpfs, so if the device cannot be\n        # found, wait for a short amount and then try again once.\n        #\n        # Furthermore, even if devtmpfs is in use, it is possible that the device\n        # does show up immediately, but without the correct permissions that\n        # still need to be set by udev. Wait for up to two seconds for either the\n        # device to show up or the permissions to be set.\n        for attempt in range(19):\n            try:\n                return InputDevice(device_path)\n            except (FileNotFoundError, PermissionError):\n                time.sleep(0.1)\n\n        # Last attempt. If this fails, whatever exception the last attempt raises\n        # shall be the exception that this function raises.\n        return InputDevice(device_path)\n\n    def _find_device_fallback(self) -> Union[InputDevice, None]:\n        \"\"\"\n        Tries to find the device node when UI_GET_SYSNAME is not available or\n        we're running on a system sufficiently exotic that we do not know how\n        to interpret its return value.\n        \"\"\"\n        #:bug: the device node might not be immediately available\n        time.sleep(0.1)\n\n        # There could also be another device with the same name already present,\n        # make sure to select the newest one.\n        # Strictly speaking, we cannot be certain that everything returned by list_devices()\n        # ends at event[0-9]+: it might return something like \"/dev/input/events_all\". Find\n        # the devices that have the expected structure and extract their device number.\n        path_number_pairs = []\n        regex = re.compile(\"/dev/input/event([0-9]+)\")\n        for path in util.list_devices(\"/dev/input/\"):\n            regex_match = regex.fullmatch(path)\n            if not regex_match:\n                continue\n            device_number = int(regex_match[1])\n            path_number_pairs.append((path, device_number))\n\n        # The modification date of the devnode is not reliable unfortunately, so we\n        # are sorting by the number in the name\n        path_number_pairs.sort(key=lambda pair: pair[1], reverse=True)\n\n        for path, _ in path_number_pairs:\n            d = InputDevice(path)\n            if d.name == self.name:\n                return d\n"
  },
  {
    "path": "src/evdev/util.py",
    "content": "import collections\nimport glob\nimport os\nimport re\nimport stat\nfrom typing import Union, List\n\nfrom . import ecodes\nfrom .events import InputEvent, event_factory, KeyEvent, RelEvent, AbsEvent, SynEvent\n\n\ndef list_devices(input_device_dir: Union[str, bytes, os.PathLike] = \"/dev/input\") -> List[str]:\n    \"\"\"List readable character devices in ``input_device_dir``.\"\"\"\n\n    fns = glob.glob(\"{}/event*\".format(input_device_dir))\n    return list(filter(is_device, fns))\n\n\ndef is_device(fn: Union[str, bytes, os.PathLike]) -> bool:\n    \"\"\"Check if ``fn`` is a readable and writable character device.\"\"\"\n\n    if not os.path.exists(fn):\n        return False\n\n    m = os.stat(fn)[stat.ST_MODE]\n    if not stat.S_ISCHR(m):\n        return False\n\n    if not os.access(fn, os.R_OK | os.W_OK):\n        return False\n\n    return True\n\n\ndef categorize(event: InputEvent) -> Union[InputEvent, KeyEvent, RelEvent, AbsEvent, SynEvent]:\n    \"\"\"\n    Categorize an event according to its type.\n\n    The :data:`event_factory <evdev.events.event_factory>` dictionary\n    maps event types to sub-classes of :class:`InputEvent\n    <evdev.events.InputEvent>`. If the event cannot be categorized, it\n    is returned unmodified.\"\"\"\n\n    if event.type in event_factory:\n        return event_factory[event.type](event)\n    else:\n        return event\n\n\ndef resolve_ecodes_dict(typecodemap, unknown=\"?\"):\n    \"\"\"\n    Resolve event codes and types to their verbose names.\n\n    :param typecodemap: mapping of event types to lists of event codes.\n    :param unknown: symbol to which unknown types or codes will be resolved.\n\n    Example\n    -------\n    >>> resolve_ecodes_dict({ 1: [272, 273, 274] })\n    { ('EV_KEY', 1): [('BTN_MOUSE',  272),\n                      ('BTN_RIGHT',  273),\n                      ('BTN_MIDDLE', 274)] }\n\n    If ``typecodemap`` contains absolute axis info (instances of\n    :class:`AbsInfo <evdev.device.AbsInfo>` ) the result would look\n    like:\n\n    >>> resolve_ecodes_dict({ 3: [(0, AbsInfo(...))] })\n    { ('EV_ABS', 3L): [(('ABS_X', 0L), AbsInfo(...))] }\n    \"\"\"\n\n    for etype, codes in typecodemap.items():\n        type_name = ecodes.EV[etype]\n\n        # ecodes.keys are a combination of KEY_ and BTN_ codes\n        if etype == ecodes.EV_KEY:\n            ecode_dict = ecodes.keys\n        else:\n            ecode_dict = getattr(ecodes, type_name.split(\"_\")[-1])\n\n        resolved = resolve_ecodes(ecode_dict, codes, unknown)\n        yield (type_name, etype), resolved\n\n\ndef resolve_ecodes(ecode_dict, ecode_list, unknown=\"?\"):\n    \"\"\"\n    Resolve event codes and types to their verbose names.\n\n    Example\n    -------\n    >>> resolve_ecodes(ecodes.BTN, [272, 273, 274])\n    [(['BTN_LEFT', 'BTN_MOUSE'], 272), ('BTN_RIGHT', 273), ('BTN_MIDDLE', 274)]\n    \"\"\"\n    res = []\n    for ecode in ecode_list:\n        # elements with AbsInfo(), eg { 3 : [(0, AbsInfo(...)), (1, AbsInfo(...))] }\n        if isinstance(ecode, tuple):\n            if ecode[0] in ecode_dict:\n                l = ((ecode_dict[ecode[0]], ecode[0]), ecode[1])\n            else:\n                l = ((unknown, ecode[0]), ecode[1])\n\n        # just ecodes, e.g: { 0 : [0, 1, 3], 1 : [30, 48] }\n        else:\n            if ecode in ecode_dict:\n                l = (ecode_dict[ecode], ecode)\n            else:\n                l = (unknown, ecode)\n        res.append(l)\n\n    return res\n\n\ndef find_ecodes_by_regex(regex):\n    \"\"\"\n    Find ecodes matching a regex and return a mapping of event type to event codes.\n\n    regex can be a pattern string or a compiled regular expression object.\n\n    Example\n    -------\n    >>> find_ecodes_by_regex(r'(ABS|KEY)_BR(AKE|EAK)')\n    {1: [411], 3: [10]}\n    >>> res = find_ecodes_by_regex(r'(ABS|KEY)_BR(AKE|EAK)')\n    >>> resolve_ecodes_dict(res)\n    {\n        ('EV_KEY', 1): [('KEY_BREAK', 411)],\n        ('EV_ABS', 3): [('ABS_BRAKE', 10)]\n    }\n    \"\"\"\n\n    regex = re.compile(regex)  # re.compile is idempotent\n    result = collections.defaultdict(list)\n\n    for type_code, codes in ecodes.bytype.items():\n        for code, names in codes.items():\n            names = (names,) if isinstance(names, str) else names\n            for name in names:\n                if regex.match(name):\n                    result[type_code].append(code)\n                    break\n\n    return dict(result)\n\n\n__all__ = (\"list_devices\", \"is_device\", \"categorize\", \"resolve_ecodes\", \"resolve_ecodes_dict\", \"find_ecodes_by_regex\")\n"
  },
  {
    "path": "tests/test_ecodes.py",
    "content": "from evdev import ecodes\nfrom evdev import ecodes_runtime\n\n\nprefixes = \"KEY ABS REL SW MSC LED BTN REP SND ID EV BUS SYN FF_STATUS FF UI_FF\"\n\n\ndef to_tuples(val):\n    t = lambda x: tuple(x) if isinstance(x, list) else x\n    return map(t, val)\n\n\ndef test_equality():\n    keys = []\n    for i in prefixes.split():\n        keys.extend(getattr(ecodes, i, {}).keys())\n\n    assert set(keys) == set(ecodes.ecodes.values())\n\n\ndef test_access():\n    assert ecodes.KEY_A == ecodes.ecodes[\"KEY_A\"] == ecodes.KEY_A\n    assert ecodes.KEY[ecodes.ecodes[\"KEY_A\"]] == \"KEY_A\"\n    assert ecodes.REL[0] == \"REL_X\"\n\n\ndef test_overlap():\n    vals_ff = set(to_tuples(ecodes.FF.values()))\n    vals_ff_status = set(to_tuples(ecodes.FF_STATUS.values()))\n    assert bool(vals_ff & vals_ff_status) is False\n\n\ndef test_generated():\n    e_run = vars(ecodes_runtime)\n    e_gen = vars(ecodes)\n\n    def keys(v):\n        res = {k for k in v.keys() if not k.startswith(\"_\") and not k[1].islower()}\n        return res\n\n    assert keys(e_run) == keys(e_gen)"
  },
  {
    "path": "tests/test_events.py",
    "content": "# encoding: utf-8\n\nfrom evdev import events, ecodes, util\n\n\ndef test_categorize():\n    e = events.InputEvent(1036996631, 984417, ecodes.EV_KEY, ecodes.KEY_A, 0)\n    assert isinstance(util.categorize(e), events.KeyEvent)\n\n    e = events.InputEvent(1036996631, 984417, ecodes.EV_ABS, 0, 0)\n    assert isinstance(util.categorize(e), events.AbsEvent)\n\n    e = events.InputEvent(1036996631, 984417, ecodes.EV_REL, 0, 0)\n    assert isinstance(util.categorize(e), events.RelEvent)\n\n    e = events.InputEvent(1036996631, 984417, ecodes.EV_MSC, 0, 0)\n    assert e == util.categorize(e)\n\n\ndef test_keyevent():\n    e = events.InputEvent(1036996631, 984417, ecodes.EV_KEY, ecodes.KEY_A, 2)\n    k = events.KeyEvent(e)\n\n    assert k.keystate == events.KeyEvent.key_hold\n    assert k.event == e\n    assert k.scancode == ecodes.KEY_A\n    assert k.keycode == \"KEY_A\"  # :todo:\n"
  },
  {
    "path": "tests/test_uinput.py",
    "content": "# encoding: utf-8\nimport os\nimport stat\nfrom select import select\nfrom unittest.mock import patch\n\nimport pytest\nfrom pytest import raises, fixture\n\nfrom evdev import uinput, ecodes, device, UInputError\n\n# -----------------------------------------------------------------------------\nuinput_options = {\n    \"name\": \"test-py-evdev-uinput\",\n    \"bustype\": ecodes.BUS_USB,\n    \"vendor\": 0x1100,\n    \"product\": 0x2200,\n    \"version\": 0x3300,\n}\n\n\n@fixture\ndef c():\n    return uinput_options.copy()\n\n\ndef device_exists(bustype, vendor, product, version):\n    match = \"I: Bus=%04hx Vendor=%04hx Product=%04hx Version=%04hx\"\n    match = match % (bustype, vendor, product, version)\n\n    for line in open(\"/proc/bus/input/devices\"):\n        if line.strip() == match:\n            return True\n\n    return False\n\n\n# -----------------------------------------------------------------------------\ndef test_open(c):\n    ui = uinput.UInput(**c)\n    args = (c[\"bustype\"], c[\"vendor\"], c[\"product\"], c[\"version\"])\n    assert device_exists(*args)\n    ui.close()\n    assert not device_exists(*args)\n\n\ndef test_open_context(c):\n    args = (c[\"bustype\"], c[\"vendor\"], c[\"product\"], c[\"version\"])\n    with uinput.UInput(**c):\n        assert device_exists(*args)\n    assert not device_exists(*args)\n\n\ndef test_maxnamelen(c):\n    with raises(uinput.UInputError):\n        c[\"name\"] = \"a\" * 150\n        uinput.UInput(**c)\n\n\ndef test_enable_events(c):\n    e = ecodes\n    c[\"events\"] = {e.EV_KEY: [e.KEY_A, e.KEY_B, e.KEY_C]}\n\n    with uinput.UInput(**c) as ui:\n        cap = ui.capabilities()\n        assert e.EV_KEY in cap\n        assert sorted(cap[e.EV_KEY]) == sorted(c[\"events\"][e.EV_KEY])\n\n\ndef test_abs_values(c):\n    e = ecodes\n    c = {\n        e.EV_KEY: [e.KEY_A, e.KEY_B],\n        e.EV_ABS: [(e.ABS_X, (0, 0, 255, 0, 0)), (e.ABS_Y, device.AbsInfo(0, 0, 255, 5, 10, 0))],\n    }\n\n    with uinput.UInput(events=c) as ui:\n        c = ui.capabilities()\n        abs = device.AbsInfo(value=0, min=0, max=255, fuzz=0, flat=0, resolution=0)\n        assert c[e.EV_ABS][0] == (0, abs)\n\n        abs = device.AbsInfo(value=0, min=0, max=255, fuzz=5, flat=10, resolution=0)\n        assert c[e.EV_ABS][1] == (1, abs)\n\n        c = ui.capabilities(verbose=True)\n        abs = device.AbsInfo(value=0, min=0, max=255, fuzz=0, flat=0, resolution=0)\n        assert c[(\"EV_ABS\", 3)][0] == ((\"ABS_X\", 0), abs)\n\n        c = ui.capabilities(verbose=False, absinfo=False)\n        assert c[e.EV_ABS] == list((0, 1))\n\n\ndef test_write(c):\n    with uinput.UInput(**c) as ui:\n        d = ui.device\n        wrote = False\n\n        while True:\n            r, w, x = select([d], [d], [])\n\n            if w and not wrote:\n                ui.write(ecodes.EV_KEY, ecodes.KEY_P, 1)  # KEY_P down\n                ui.write(ecodes.EV_KEY, ecodes.KEY_P, 1)  # KEY_P down\n                ui.write(ecodes.EV_KEY, ecodes.KEY_P, 0)  # KEY_P up\n                ui.write(ecodes.EV_KEY, ecodes.KEY_A, 1)  # KEY_A down\n                ui.write(ecodes.EV_KEY, ecodes.KEY_A, 2)  # KEY_A hold\n                ui.write(ecodes.EV_KEY, ecodes.KEY_A, 0)  # KEY_P up\n                ui.syn()\n                wrote = True\n\n            if r:\n                evs = list(d.read())\n\n                assert evs[0].code == ecodes.KEY_P and evs[0].value == 1\n                assert evs[1].code == ecodes.KEY_P and evs[1].value == 0\n                assert evs[2].code == ecodes.KEY_A and evs[2].value == 1\n                assert evs[3].code == ecodes.KEY_A and evs[3].value == 2\n                assert evs[4].code == ecodes.KEY_A and evs[4].value == 0\n                break\n\n\n@patch.object(stat, 'S_ISCHR', return_value=False)\ndef test_not_a_character_device(ischr_mock, c):\n    with pytest.raises(UInputError, match='not a character device file'):\n        uinput.UInput(**c)\n\n@patch.object(stat, 'S_ISCHR', return_value=True)\n@patch.object(os, 'stat', side_effect=OSError())\ndef test_not_a_character_device_2(stat_mock, ischr_mock, c):\n    with pytest.raises(UInputError, match='not a character device file'):\n        uinput.UInput(**c)\n\n@patch.object(stat, 'S_ISCHR', return_value=True)\n@patch.object(os, 'stat', return_value=[])\ndef test_not_a_character_device_3(stat_mock, ischr_mock, c):\n    with pytest.raises(UInputError, match='not a character device file'):\n        uinput.UInput(**c)\n"
  },
  {
    "path": "tests/test_util.py",
    "content": "from evdev import util\n\n\ndef test_match_ecodes_a():\n    res = util.find_ecodes_by_regex(\"KEY_ZOOM.*\")\n    assert res == {1: [372, 418, 419, 420]}\n    assert dict(util.resolve_ecodes_dict(res)) == {\n        (\"EV_KEY\", 1): [\n            ((\"KEY_FULL_SCREEN\", \"KEY_ZOOM\"), 372),\n            (\"KEY_ZOOMIN\", 418),\n            (\"KEY_ZOOMOUT\", 419),\n            (\"KEY_ZOOMRESET\", 420),\n        ]\n    }\n\n    res = util.find_ecodes_by_regex(r\"(ABS|KEY)_BR(AKE|EAK)\")\n    assert res == {1: [411], 3: [10]}\n    assert dict(util.resolve_ecodes_dict(res)) == {\n        (\"EV_KEY\", 1): [(\"KEY_BREAK\", 411)],\n        (\"EV_ABS\", 3): [(\"ABS_BRAKE\", 10)],\n    }\n"
  }
]