diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..7551917
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,66 @@
+# Generated from CLion C/C++ Code Style settings
+BasedOnStyle: LLVM
+AccessModifierOffset: -4
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: None
+AlignOperands: Align
+AllowAllArgumentsOnNextLine: false
+AllowAllConstructorInitializersOnNextLine: false
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowShortBlocksOnASingleLine: Always
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: All
+AllowShortIfStatementsOnASingleLine: Always
+AllowShortLambdasOnASingleLine: All
+AllowShortLoopsOnASingleLine: true
+AlwaysBreakAfterReturnType: None
+AlwaysBreakTemplateDeclarations: Yes
+BreakBeforeBraces: Custom
+BraceWrapping:
+ AfterCaseLabel: false
+ AfterClass: false
+ AfterControlStatement: Never
+ AfterEnum: false
+ AfterFunction: false
+ AfterNamespace: false
+ AfterUnion: false
+ BeforeCatch: false
+ BeforeElse: false
+ IndentBraces: false
+ SplitEmptyFunction: false
+ SplitEmptyRecord: true
+BreakBeforeBinaryOperators: None
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializers: BeforeColon
+BreakInheritanceList: BeforeColon
+ColumnLimit: 0
+CompactNamespaces: false
+ContinuationIndentWidth: 8
+IndentCaseLabels: true
+IndentPPDirectives: None
+IndentWidth: 4
+KeepEmptyLinesAtTheStartOfBlocks: true
+MaxEmptyLinesToKeep: 2
+NamespaceIndentation: All
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PointerAlignment: Right
+ReflowComments: false
+SpaceAfterCStyleCast: true
+SpaceAfterLogicalNot: false
+SpaceAfterTemplateKeyword: false
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatements
+SpaceBeforeRangeBasedForLoopColon: false
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: false
+SpacesInCStyleCastParentheses: false
+SpacesInContainerLiterals: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+TabWidth: 4
+UseTab: Never
diff --git a/.github/workflows/import.yml b/.github/workflows/import.yml
new file mode 100644
index 0000000..dcf42fc
--- /dev/null
+++ b/.github/workflows/import.yml
@@ -0,0 +1,49 @@
+name: "Build & Import Test"
+
+on:
+ workflow_dispatch:
+ push:
+ paths:
+ - "ydotool/**"
+ - "pydotool/**"
+ - "setup.py"
+ - "CMakeLists.txt"
+ - ".github/workflows/import.yml"
+ branches:
+ - main
+ pull_request:
+ paths:
+ - "ydotool/**"
+ - "pydotool/**"
+ - "setup.py"
+ - "CMakeLists.txt"
+ - ".github/workflows/import.yml"
+
+
+jobs:
+ run-test:
+ name: "run tests"
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ python-version: ["3.9", "3.10", "3.11", "3.12"]
+ os: [ubuntu-latest]
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v5
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Install pydotool
+ run: |
+ python -m pip install .
+
+ - name: run import test
+ id: tests
+ run: |
+ python -c "import pydotool"
diff --git a/.gitignore b/.gitignore
index c6127b3..e9d47fe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,8 @@
+# per IDE conf / build directory
+.vscode
+cmake-build-*/
+build/
+
# Prerequisites
*.d
@@ -50,3 +55,165 @@ modules.order
Module.symvers
Mkfile.old
dkms.conf
+
+
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# poetry
+# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+# in version control.
+# https://pdm.fming.dev/#use-with-ide
+.pdm.toml
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..d0823d1
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,44 @@
+cmake_minimum_required(VERSION 3.18)
+# find correct python package is implemented in cmake 3.15, find other implementation is in 3.18
+
+project(ydotool C)
+
+set(CMAKE_C_STANDARD 99)
+
+find_package(Python3 COMPONENTS Development)
+# Include GNU install directory module to detect where to install
+# files on Linux/Unix systems (e.g., lib vs lib64)
+include(GNUInstallDirs)
+find_package(PkgConfig)
+
+
+execute_process(COMMAND git describe --tags --long --always RESULT_VARIABLE RC_GIT_VER OUTPUT_VARIABLE GIT_VERSION)
+
+if (${RC_GIT_VER} EQUAL 0)
+ string(STRIP ${GIT_VERSION} GIT_VERSION)
+ message("-- Version: " ${GIT_VERSION})
+ add_definitions(-DVERSION=\"${GIT_VERSION}\")
+endif()
+
+set(SOURCE_FILES_CLIENT ydotool/ydotool.c ydotool/pydotool.c)
+
+add_library(_pydotool SHARED ${SOURCE_FILES_CLIENT})
+target_include_directories(_pydotool PRIVATE ${Python3_INCLUDE_DIRS} ydotool)
+target_link_libraries(_pydotool ${Python3_LIBRARIES})
+
+if (CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang")
+ target_compile_options(_pydotool PUBLIC -Wno-unused-result)
+endif()
+
+# optimize for Release build
+if (CMAKE_BUILD_TYPE MATCHES ".*Rel.*")
+ message("Release mode, enabling maximal optimization")
+ target_compile_options(_pydotool PUBLIC -O3)
+else(CMAKE_BUILD_TYPE MATCHES ".*Rel.*")
+ message("Debug mode, enabling debug symbols")
+ target_compile_options(_pydotool PUBLIC -g)
+endif (CMAKE_BUILD_TYPE MATCHES ".*Rel.*")
+
+# install py package
+install(TARGETS _pydotool LIBRARY DESTINATION .)
+set_target_properties(_pydotool PROPERTIES PREFIX "")
diff --git a/README.md b/README.md
index 7182a7e..c54dbd9 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,137 @@
# pydotool
-ydotool python bindings
+A [ydotool](https://github.com/ReimuNotMoe/ydotool) client implemented in Python. The aim of `pydotool` is to write automation scripts in an easy way.
+
+### Usage
+
+* click event
+* key event
+* mouse move event
+* type characters
+
+### How to Use
+
+* Install [ydotoold](https://github.com/ReimuNotMoe/ydotool). you may install it from your package manager, or build it from source.
+
+* Install `pydotool`.
+
+* Write your automation script. Call `init` once in your program to connect to `ydotoold`
+
+```python
+import pydotool
+pydotool.init()
+```
+
+If you use another ydotoold socket than the default one (`/tmp/.ydotool_socket`), you can provide it in environment variable `YDOTOOL_SOCKET`, or pass it to `init()`
+
+```python
+pydotool.init("/run/ydotoold/socket")
+```
+
+### How to Implement the Examples in [ydotool](https://github.com/ReimuNotMoe/ydotool)
+
+Switch to tty1 (Ctrl+Alt+F1), wait 2 seconds, and type some words:
+
+```python
+import time
+
+from pydotool import KEY_F1, KEY_LEFTALT, KEY_LEFTCTRL, key_combination, init, type_string
+
+init() # call only once before using pydotool
+
+key_combination([KEY_LEFTCTRL, KEY_LEFTALT, KEY_F1])
+time.sleep(2)
+type_string("echo Hey guys. This is Austin.")
+```
+
+Close a window in graphical environment (Alt+F4):
+
+```python
+from pydotool import KEY_F4, KEY_LEFTALT
+
+key_combination([KEY_LEFTALT, KEY_F4])
+```
+
+Relatively move mouse pointer to -100,100:
+
+```python
+from pydotool import mouse_move
+mouse_move((100, 100))
+```
+
+Move mouse pointer to 100,100:
+
+```python
+mouse_move((100, 100), True)
+```
+
+Mouse right click:
+
+```python
+from pydotool import right_click
+
+right_click()
+```
+
+Mouse repeating left click:
+
+```python
+from pydotool import click_sequence, ClickEnum
+
+click_sequence([ClickEnum.LEFT_CLICK] * 5)
+```
+
+`stdin` is not natively supported, but you can implement it with python stdin.
+
+### Advanced Usage
+
+There is no delay in pydotool API after the last event emitted.
+
+```python
+for i in range(5):
+ left_click()
+ if i != 4:
+ time.sleep(0.025)
+
+# above code is equivalent to
+click_sequence([ClickEnum.LEFT_CLICK] * 5)
+```
+
+Customize the delay time:
+
+```python
+# Each click event is combined with "press" and "release", so there are totally 10 ydotool events, and 9 delays between each two consecutive events.
+# the 9 delays are 10ms, 20ms, ..., 90ms.
+click_sequence([ClickEnum.LEFT_CLICK] * 5, delay_sequence=list(range(10, 100, 10)))
+```
+
+Complex key operations:
+
+```python
+# Ctrl+K and Ctrl+D without releasing Ctrl
+key_seq([(KEY_LEFTCTRL, DOWN), (KEY_K, DOWN), (KEY_K, UP), (KEY_D, DOWN), (KEY_D, UP), (KEY_LEFTCTRL, UP)])
+```
+
+Control the press time when typing:
+
+```python
+# Type "abcde", but hold 1 sec for each character.
+# Note that long press may generate more content than expected.
+type_string("abcde", hold_delay_ms=1000, each_char_delay_ms=20)
+# possible output:
+# aaaaaaaaaaaabbbbbbbbbbbccccccccccccddddddddddddeeeeeeeeeeee
+```
+
+Long press key combination:
+
+```python
+# Press Alt+F4 for 3 secs. Can close most of the apps. Not recommended to try this.
+key_combination([KEY_LEFTALT, KEY_F4], press_ms=3000)
+```
+
+Using the low level API to send event:
+
+```python
+# directly send an event using uinput_emit()
+uinput_emit(EV_REL, REL_Y, 100, True) # move 100 in y direction
+```
+
diff --git a/devtool/gen_input.py b/devtool/gen_input.py
new file mode 100644
index 0000000..857fb0b
--- /dev/null
+++ b/devtool/gen_input.py
@@ -0,0 +1,19 @@
+import re
+
+
+def read_definitions(file_path):
+ with open(file_path, 'r') as file:
+ content = file.read()
+
+ pattern = re.compile(r'#define\s+(\w+)\s+(\w+)')
+ definitions = pattern.findall(content)
+
+ return definitions
+
+
+# change this path to your own path
+file_path = "/usr/include/linux/input-event-codes.h"
+definitions = read_definitions(file_path)
+
+for name, value in definitions:
+ print(f'{name} = {value}')
diff --git a/pydotool/__init__.py b/pydotool/__init__.py
new file mode 100644
index 0000000..05fe5a0
--- /dev/null
+++ b/pydotool/__init__.py
@@ -0,0 +1,12 @@
+import _pydotool # type: ignore
+
+from .ascii_2_keycode import *
+from .click import *
+from .input_event_code import *
+from .key import *
+from .mousemove import *
+from .typetool import *
+
+
+init = _pydotool.init
+uinput_emit = _pydotool.uinput_emit
diff --git a/pydotool/_sequence.py b/pydotool/_sequence.py
new file mode 100644
index 0000000..a5f9d2b
--- /dev/null
+++ b/pydotool/_sequence.py
@@ -0,0 +1,18 @@
+import time
+from itertools import islice
+from typing import Callable, Generator, Iterable, TypeVar
+
+
+_T = TypeVar("_T")
+
+
+def sequence_call(func: Callable, args_list: "list", delay_list: "Iterable[int | float]"):
+ for args, delay in zip(args_list, islice(delay_list, len(args_list) - 1)):
+ func(*args)
+ time.sleep(delay / 1000.)
+ func(*args_list[-1])
+
+
+def const_gen(const: _T) -> Generator[_T, None, None]:
+ while True:
+ yield const
diff --git a/pydotool/ascii_2_keycode.py b/pydotool/ascii_2_keycode.py
new file mode 100644
index 0000000..c7f465d
--- /dev/null
+++ b/pydotool/ascii_2_keycode.py
@@ -0,0 +1,38 @@
+from .input_event_code import *
+
+
+FLAG_UPPERCASE = 0x80000000
+
+ASCII_2_KEYCODE_MAP = [
+ # 00 - 0f
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, KEY_TAB, KEY_ENTER, -1, -1, -1, -1, -1,
+
+ # 10 - 1f
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+
+ # 20 - 2f
+ KEY_SPACE, KEY_1 | FLAG_UPPERCASE, KEY_APOSTROPHE | FLAG_UPPERCASE, KEY_3 | FLAG_UPPERCASE, KEY_4 | FLAG_UPPERCASE, KEY_5 | FLAG_UPPERCASE, KEY_7 | FLAG_UPPERCASE, KEY_APOSTROPHE,
+ KEY_9 | FLAG_UPPERCASE, KEY_0 | FLAG_UPPERCASE, KEY_8 | FLAG_UPPERCASE, KEY_EQUAL | FLAG_UPPERCASE, KEY_COMMA, KEY_MINUS, KEY_DOT, KEY_SLASH,
+
+ # 30 - 3f
+ KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7,
+ KEY_8, KEY_9, KEY_SEMICOLON | FLAG_UPPERCASE, KEY_SEMICOLON, KEY_COMMA | FLAG_UPPERCASE, KEY_EQUAL, KEY_DOT | FLAG_UPPERCASE, KEY_SLASH | FLAG_UPPERCASE,
+
+ # 40 - 4f
+ KEY_2 | FLAG_UPPERCASE, KEY_A | FLAG_UPPERCASE, KEY_B | FLAG_UPPERCASE, KEY_C | FLAG_UPPERCASE, KEY_D | FLAG_UPPERCASE, KEY_E | FLAG_UPPERCASE, KEY_F | FLAG_UPPERCASE, KEY_G | FLAG_UPPERCASE,
+ KEY_H | FLAG_UPPERCASE, KEY_I | FLAG_UPPERCASE, KEY_J | FLAG_UPPERCASE, KEY_K | FLAG_UPPERCASE, KEY_L | FLAG_UPPERCASE, KEY_M | FLAG_UPPERCASE, KEY_N | FLAG_UPPERCASE, KEY_O | FLAG_UPPERCASE,
+
+ # 50 - 5f
+ KEY_P | FLAG_UPPERCASE, KEY_Q | FLAG_UPPERCASE, KEY_R | FLAG_UPPERCASE, KEY_S | FLAG_UPPERCASE, KEY_T | FLAG_UPPERCASE, KEY_U | FLAG_UPPERCASE, KEY_V | FLAG_UPPERCASE, KEY_W | FLAG_UPPERCASE,
+ KEY_X | FLAG_UPPERCASE, KEY_Y | FLAG_UPPERCASE, KEY_Z | FLAG_UPPERCASE, KEY_LEFTBRACE, KEY_BACKSLASH, KEY_RIGHTBRACE, KEY_6 | FLAG_UPPERCASE, KEY_MINUS | FLAG_UPPERCASE,
+
+ # 60 - 6f
+ KEY_GRAVE, KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G,
+ KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, KEY_M, KEY_N, KEY_O,
+
+ # 70 - 7f
+ KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W,
+ KEY_X, KEY_Y, KEY_Z, KEY_LEFTBRACE | FLAG_UPPERCASE, KEY_BACKSLASH | FLAG_UPPERCASE, KEY_RIGHTBRACE | FLAG_UPPERCASE, KEY_GRAVE | FLAG_UPPERCASE, -1
+]
diff --git a/pydotool/click.py b/pydotool/click.py
new file mode 100644
index 0000000..5ac19b2
--- /dev/null
+++ b/pydotool/click.py
@@ -0,0 +1,89 @@
+
+from enum import IntFlag
+from typing import Any
+
+import _pydotool # type: ignore
+
+from ._sequence import const_gen, sequence_call
+from .input_event_code import EV_KEY
+
+
+CLICK_DEFAULT_DELAY = 25
+
+_uinput_emit = _pydotool.uinput_emit
+
+
+class ClickEnum(IntFlag):
+ LEFT = 0
+ RIGHT = 1
+ MIDDLE = 2
+ SIDE = 3
+ EXTR = 4
+ FORWARD = 5
+ BACK = 6
+ TASK = 7
+ MOUSE_DOWN = 0x40
+ MOUSE_UP = 0x80
+ MOUSE_CLICK = MOUSE_DOWN | MOUSE_UP
+ LEFT_CLICK = MOUSE_CLICK | LEFT
+ RIGHT_CLICK = MOUSE_CLICK | RIGHT
+
+
+def _click_seq_parse(key_sequence: list[ClickEnum]) -> "list[tuple[int, int, int, int]]":
+ ret = []
+ for key in key_sequence:
+ keycode = (key.value & 0xf) | 0x110
+ if key & ClickEnum.MOUSE_DOWN:
+ ret.append((EV_KEY, keycode, 1, 1))
+ if key & ClickEnum.MOUSE_UP:
+ ret.append((EV_KEY, keycode, 0, 1))
+ return ret
+
+
+def click_sequence(key_sequence: "ClickEnum | list[ClickEnum]", next_delay_ms: "int | float | None" = None, delay_sequence: "list[int | float] | None" = None):
+ if next_delay_ms is None and delay_sequence is None:
+ next_delay_ms = CLICK_DEFAULT_DELAY
+ elif next_delay_ms is not None and delay_sequence is not None:
+ raise ValueError("Cannot specify both next_delay_ms and delay_sequence")
+
+ if not isinstance(key_sequence, list):
+ key_sequence = [key_sequence]
+
+ call_sequence = _click_seq_parse(key_sequence)
+ if delay_sequence is not None and len(delay_sequence) < len(call_sequence) - 1:
+ raise ValueError("delay_sequence is shorter than key_sequence")
+
+ if next_delay_ms is not None:
+ assert delay_sequence is None
+ delays: Any = const_gen(next_delay_ms)
+ else:
+ delays = delay_sequence
+ assert delays is not None
+ sequence_call(_uinput_emit, call_sequence, delays)
+
+
+def click(key: ClickEnum, next_delay_ms: "int | float" = CLICK_DEFAULT_DELAY):
+ click_sequence([key], next_delay_ms)
+
+
+def left_click(release_delay_ms=CLICK_DEFAULT_DELAY):
+ click(ClickEnum.LEFT_CLICK, release_delay_ms)
+
+
+def right_click(release_delay_ms=CLICK_DEFAULT_DELAY):
+ click(ClickEnum.RIGHT_CLICK, release_delay_ms)
+
+
+def double_click(key: ClickEnum, release_delay_ms=CLICK_DEFAULT_DELAY, gap_delay_ms=100):
+ click_sequence(
+ [key, key],
+ delay_sequence=[release_delay_ms, gap_delay_ms, release_delay_ms]
+ )
+
+
+def left_double_click(release_delay_ms=CLICK_DEFAULT_DELAY, gap_delay_ms=100):
+ double_click(ClickEnum.LEFT_CLICK, release_delay_ms, gap_delay_ms)
+
+
+def right_double_click(release_delay_ms=CLICK_DEFAULT_DELAY, gap_delay_ms=100):
+ double_click(ClickEnum.RIGHT_CLICK, release_delay_ms, gap_delay_ms)
diff --git a/pydotool/input_event_code.py b/pydotool/input_event_code.py
new file mode 100644
index 0000000..6cad2ea
--- /dev/null
+++ b/pydotool/input_event_code.py
@@ -0,0 +1,766 @@
+# this is a auto generated file, do not edit it
+
+INPUT_PROP_POINTER = 0x00
+INPUT_PROP_DIRECT = 0x01
+INPUT_PROP_BUTTONPAD = 0x02
+INPUT_PROP_SEMI_MT = 0x03
+INPUT_PROP_TOPBUTTONPAD = 0x04
+INPUT_PROP_POINTING_STICK = 0x05
+INPUT_PROP_ACCELEROMETER = 0x06
+INPUT_PROP_MAX = 0x1f
+EV_SYN = 0x00
+EV_KEY = 0x01
+EV_REL = 0x02
+EV_ABS = 0x03
+EV_MSC = 0x04
+EV_SW = 0x05
+EV_LED = 0x11
+EV_SND = 0x12
+EV_REP = 0x14
+EV_FF = 0x15
+EV_PWR = 0x16
+EV_FF_STATUS = 0x17
+EV_MAX = 0x1f
+SYN_REPORT = 0
+SYN_CONFIG = 1
+SYN_MT_REPORT = 2
+SYN_DROPPED = 3
+SYN_MAX = 0xf
+KEY_RESERVED = 0
+KEY_ESC = 1
+KEY_1 = 2
+KEY_2 = 3
+KEY_3 = 4
+KEY_4 = 5
+KEY_5 = 6
+KEY_6 = 7
+KEY_7 = 8
+KEY_8 = 9
+KEY_9 = 10
+KEY_0 = 11
+KEY_MINUS = 12
+KEY_EQUAL = 13
+KEY_BACKSPACE = 14
+KEY_TAB = 15
+KEY_Q = 16
+KEY_W = 17
+KEY_E = 18
+KEY_R = 19
+KEY_T = 20
+KEY_Y = 21
+KEY_U = 22
+KEY_I = 23
+KEY_O = 24
+KEY_P = 25
+KEY_LEFTBRACE = 26
+KEY_RIGHTBRACE = 27
+KEY_ENTER = 28
+KEY_LEFTCTRL = 29
+KEY_A = 30
+KEY_S = 31
+KEY_D = 32
+KEY_F = 33
+KEY_G = 34
+KEY_H = 35
+KEY_J = 36
+KEY_K = 37
+KEY_L = 38
+KEY_SEMICOLON = 39
+KEY_APOSTROPHE = 40
+KEY_GRAVE = 41
+KEY_LEFTSHIFT = 42
+KEY_BACKSLASH = 43
+KEY_Z = 44
+KEY_X = 45
+KEY_C = 46
+KEY_V = 47
+KEY_B = 48
+KEY_N = 49
+KEY_M = 50
+KEY_COMMA = 51
+KEY_DOT = 52
+KEY_SLASH = 53
+KEY_RIGHTSHIFT = 54
+KEY_KPASTERISK = 55
+KEY_LEFTALT = 56
+KEY_SPACE = 57
+KEY_CAPSLOCK = 58
+KEY_F1 = 59
+KEY_F2 = 60
+KEY_F3 = 61
+KEY_F4 = 62
+KEY_F5 = 63
+KEY_F6 = 64
+KEY_F7 = 65
+KEY_F8 = 66
+KEY_F9 = 67
+KEY_F10 = 68
+KEY_NUMLOCK = 69
+KEY_SCROLLLOCK = 70
+KEY_KP7 = 71
+KEY_KP8 = 72
+KEY_KP9 = 73
+KEY_KPMINUS = 74
+KEY_KP4 = 75
+KEY_KP5 = 76
+KEY_KP6 = 77
+KEY_KPPLUS = 78
+KEY_KP1 = 79
+KEY_KP2 = 80
+KEY_KP3 = 81
+KEY_KP0 = 82
+KEY_KPDOT = 83
+KEY_ZENKAKUHANKAKU = 85
+KEY_102ND = 86
+KEY_F11 = 87
+KEY_F12 = 88
+KEY_RO = 89
+KEY_KATAKANA = 90
+KEY_HIRAGANA = 91
+KEY_HENKAN = 92
+KEY_KATAKANAHIRAGANA = 93
+KEY_MUHENKAN = 94
+KEY_KPJPCOMMA = 95
+KEY_KPENTER = 96
+KEY_RIGHTCTRL = 97
+KEY_KPSLASH = 98
+KEY_SYSRQ = 99
+KEY_RIGHTALT = 100
+KEY_LINEFEED = 101
+KEY_HOME = 102
+KEY_UP = 103
+KEY_PAGEUP = 104
+KEY_LEFT = 105
+KEY_RIGHT = 106
+KEY_END = 107
+KEY_DOWN = 108
+KEY_PAGEDOWN = 109
+KEY_INSERT = 110
+KEY_DELETE = 111
+KEY_MACRO = 112
+KEY_MUTE = 113
+KEY_VOLUMEDOWN = 114
+KEY_VOLUMEUP = 115
+KEY_POWER = 116
+KEY_KPEQUAL = 117
+KEY_KPPLUSMINUS = 118
+KEY_PAUSE = 119
+KEY_SCALE = 120
+KEY_KPCOMMA = 121
+KEY_HANGEUL = 122
+KEY_HANGUEL = KEY_HANGEUL
+KEY_HANJA = 123
+KEY_YEN = 124
+KEY_LEFTMETA = 125
+KEY_RIGHTMETA = 126
+KEY_COMPOSE = 127
+KEY_STOP = 128
+KEY_AGAIN = 129
+KEY_PROPS = 130
+KEY_UNDO = 131
+KEY_FRONT = 132
+KEY_COPY = 133
+KEY_OPEN = 134
+KEY_PASTE = 135
+KEY_FIND = 136
+KEY_CUT = 137
+KEY_HELP = 138
+KEY_MENU = 139
+KEY_CALC = 140
+KEY_SETUP = 141
+KEY_SLEEP = 142
+KEY_WAKEUP = 143
+KEY_FILE = 144
+KEY_SENDFILE = 145
+KEY_DELETEFILE = 146
+KEY_XFER = 147
+KEY_PROG1 = 148
+KEY_PROG2 = 149
+KEY_WWW = 150
+KEY_MSDOS = 151
+KEY_COFFEE = 152
+KEY_SCREENLOCK = KEY_COFFEE
+KEY_ROTATE_DISPLAY = 153
+KEY_DIRECTION = KEY_ROTATE_DISPLAY
+KEY_CYCLEWINDOWS = 154
+KEY_MAIL = 155
+KEY_BOOKMARKS = 156
+KEY_COMPUTER = 157
+KEY_BACK = 158
+KEY_FORWARD = 159
+KEY_CLOSECD = 160
+KEY_EJECTCD = 161
+KEY_EJECTCLOSECD = 162
+KEY_NEXTSONG = 163
+KEY_PLAYPAUSE = 164
+KEY_PREVIOUSSONG = 165
+KEY_STOPCD = 166
+KEY_RECORD = 167
+KEY_REWIND = 168
+KEY_PHONE = 169
+KEY_ISO = 170
+KEY_CONFIG = 171
+KEY_HOMEPAGE = 172
+KEY_REFRESH = 173
+KEY_EXIT = 174
+KEY_MOVE = 175
+KEY_EDIT = 176
+KEY_SCROLLUP = 177
+KEY_SCROLLDOWN = 178
+KEY_KPLEFTPAREN = 179
+KEY_KPRIGHTPAREN = 180
+KEY_NEW = 181
+KEY_REDO = 182
+KEY_F13 = 183
+KEY_F14 = 184
+KEY_F15 = 185
+KEY_F16 = 186
+KEY_F17 = 187
+KEY_F18 = 188
+KEY_F19 = 189
+KEY_F20 = 190
+KEY_F21 = 191
+KEY_F22 = 192
+KEY_F23 = 193
+KEY_F24 = 194
+KEY_PLAYCD = 200
+KEY_PAUSECD = 201
+KEY_PROG3 = 202
+KEY_PROG4 = 203
+KEY_ALL_APPLICATIONS = 204
+KEY_DASHBOARD = KEY_ALL_APPLICATIONS
+KEY_SUSPEND = 205
+KEY_CLOSE = 206
+KEY_PLAY = 207
+KEY_FASTFORWARD = 208
+KEY_BASSBOOST = 209
+KEY_PRINT = 210
+KEY_HP = 211
+KEY_CAMERA = 212
+KEY_SOUND = 213
+KEY_QUESTION = 214
+KEY_EMAIL = 215
+KEY_CHAT = 216
+KEY_SEARCH = 217
+KEY_CONNECT = 218
+KEY_FINANCE = 219
+KEY_SPORT = 220
+KEY_SHOP = 221
+KEY_ALTERASE = 222
+KEY_CANCEL = 223
+KEY_BRIGHTNESSDOWN = 224
+KEY_BRIGHTNESSUP = 225
+KEY_MEDIA = 226
+KEY_SWITCHVIDEOMODE = 227
+KEY_KBDILLUMTOGGLE = 228
+KEY_KBDILLUMDOWN = 229
+KEY_KBDILLUMUP = 230
+KEY_SEND = 231
+KEY_REPLY = 232
+KEY_FORWARDMAIL = 233
+KEY_SAVE = 234
+KEY_DOCUMENTS = 235
+KEY_BATTERY = 236
+KEY_BLUETOOTH = 237
+KEY_WLAN = 238
+KEY_UWB = 239
+KEY_UNKNOWN = 240
+KEY_VIDEO_NEXT = 241
+KEY_VIDEO_PREV = 242
+KEY_BRIGHTNESS_CYCLE = 243
+KEY_BRIGHTNESS_AUTO = 244
+KEY_BRIGHTNESS_ZERO = KEY_BRIGHTNESS_AUTO
+KEY_DISPLAY_OFF = 245
+KEY_WWAN = 246
+KEY_WIMAX = KEY_WWAN
+KEY_RFKILL = 247
+KEY_MICMUTE = 248
+BTN_MISC = 0x100
+BTN_0 = 0x100
+BTN_1 = 0x101
+BTN_2 = 0x102
+BTN_3 = 0x103
+BTN_4 = 0x104
+BTN_5 = 0x105
+BTN_6 = 0x106
+BTN_7 = 0x107
+BTN_8 = 0x108
+BTN_9 = 0x109
+BTN_MOUSE = 0x110
+BTN_LEFT = 0x110
+BTN_RIGHT = 0x111
+BTN_MIDDLE = 0x112
+BTN_SIDE = 0x113
+BTN_EXTRA = 0x114
+BTN_FORWARD = 0x115
+BTN_BACK = 0x116
+BTN_TASK = 0x117
+BTN_JOYSTICK = 0x120
+BTN_TRIGGER = 0x120
+BTN_THUMB = 0x121
+BTN_THUMB2 = 0x122
+BTN_TOP = 0x123
+BTN_TOP2 = 0x124
+BTN_PINKIE = 0x125
+BTN_BASE = 0x126
+BTN_BASE2 = 0x127
+BTN_BASE3 = 0x128
+BTN_BASE4 = 0x129
+BTN_BASE5 = 0x12a
+BTN_BASE6 = 0x12b
+BTN_DEAD = 0x12f
+BTN_GAMEPAD = 0x130
+BTN_SOUTH = 0x130
+BTN_A = BTN_SOUTH
+BTN_EAST = 0x131
+BTN_B = BTN_EAST
+BTN_C = 0x132
+BTN_NORTH = 0x133
+BTN_X = BTN_NORTH
+BTN_WEST = 0x134
+BTN_Y = BTN_WEST
+BTN_Z = 0x135
+BTN_TL = 0x136
+BTN_TR = 0x137
+BTN_TL2 = 0x138
+BTN_TR2 = 0x139
+BTN_SELECT = 0x13a
+BTN_START = 0x13b
+BTN_MODE = 0x13c
+BTN_THUMBL = 0x13d
+BTN_THUMBR = 0x13e
+BTN_DIGI = 0x140
+BTN_TOOL_PEN = 0x140
+BTN_TOOL_RUBBER = 0x141
+BTN_TOOL_BRUSH = 0x142
+BTN_TOOL_PENCIL = 0x143
+BTN_TOOL_AIRBRUSH = 0x144
+BTN_TOOL_FINGER = 0x145
+BTN_TOOL_MOUSE = 0x146
+BTN_TOOL_LENS = 0x147
+BTN_TOOL_QUINTTAP = 0x148
+BTN_STYLUS3 = 0x149
+BTN_TOUCH = 0x14a
+BTN_STYLUS = 0x14b
+BTN_STYLUS2 = 0x14c
+BTN_TOOL_DOUBLETAP = 0x14d
+BTN_TOOL_TRIPLETAP = 0x14e
+BTN_TOOL_QUADTAP = 0x14f
+BTN_WHEEL = 0x150
+BTN_GEAR_DOWN = 0x150
+BTN_GEAR_UP = 0x151
+KEY_OK = 0x160
+KEY_SELECT = 0x161
+KEY_GOTO = 0x162
+KEY_CLEAR = 0x163
+KEY_POWER2 = 0x164
+KEY_OPTION = 0x165
+KEY_INFO = 0x166
+KEY_TIME = 0x167
+KEY_VENDOR = 0x168
+KEY_ARCHIVE = 0x169
+KEY_PROGRAM = 0x16a
+KEY_CHANNEL = 0x16b
+KEY_FAVORITES = 0x16c
+KEY_EPG = 0x16d
+KEY_PVR = 0x16e
+KEY_MHP = 0x16f
+KEY_LANGUAGE = 0x170
+KEY_TITLE = 0x171
+KEY_SUBTITLE = 0x172
+KEY_ANGLE = 0x173
+KEY_FULL_SCREEN = 0x174
+KEY_ZOOM = KEY_FULL_SCREEN
+KEY_MODE = 0x175
+KEY_KEYBOARD = 0x176
+KEY_ASPECT_RATIO = 0x177
+KEY_SCREEN = KEY_ASPECT_RATIO
+KEY_PC = 0x178
+KEY_TV = 0x179
+KEY_TV2 = 0x17a
+KEY_VCR = 0x17b
+KEY_VCR2 = 0x17c
+KEY_SAT = 0x17d
+KEY_SAT2 = 0x17e
+KEY_CD = 0x17f
+KEY_TAPE = 0x180
+KEY_RADIO = 0x181
+KEY_TUNER = 0x182
+KEY_PLAYER = 0x183
+KEY_TEXT = 0x184
+KEY_DVD = 0x185
+KEY_AUX = 0x186
+KEY_MP3 = 0x187
+KEY_AUDIO = 0x188
+KEY_VIDEO = 0x189
+KEY_DIRECTORY = 0x18a
+KEY_LIST = 0x18b
+KEY_MEMO = 0x18c
+KEY_CALENDAR = 0x18d
+KEY_RED = 0x18e
+KEY_GREEN = 0x18f
+KEY_YELLOW = 0x190
+KEY_BLUE = 0x191
+KEY_CHANNELUP = 0x192
+KEY_CHANNELDOWN = 0x193
+KEY_FIRST = 0x194
+KEY_LAST = 0x195
+KEY_AB = 0x196
+KEY_NEXT = 0x197
+KEY_RESTART = 0x198
+KEY_SLOW = 0x199
+KEY_SHUFFLE = 0x19a
+KEY_BREAK = 0x19b
+KEY_PREVIOUS = 0x19c
+KEY_DIGITS = 0x19d
+KEY_TEEN = 0x19e
+KEY_TWEN = 0x19f
+KEY_VIDEOPHONE = 0x1a0
+KEY_GAMES = 0x1a1
+KEY_ZOOMIN = 0x1a2
+KEY_ZOOMOUT = 0x1a3
+KEY_ZOOMRESET = 0x1a4
+KEY_WORDPROCESSOR = 0x1a5
+KEY_EDITOR = 0x1a6
+KEY_SPREADSHEET = 0x1a7
+KEY_GRAPHICSEDITOR = 0x1a8
+KEY_PRESENTATION = 0x1a9
+KEY_DATABASE = 0x1aa
+KEY_NEWS = 0x1ab
+KEY_VOICEMAIL = 0x1ac
+KEY_ADDRESSBOOK = 0x1ad
+KEY_MESSENGER = 0x1ae
+KEY_DISPLAYTOGGLE = 0x1af
+KEY_BRIGHTNESS_TOGGLE = KEY_DISPLAYTOGGLE
+KEY_SPELLCHECK = 0x1b0
+KEY_LOGOFF = 0x1b1
+KEY_DOLLAR = 0x1b2
+KEY_EURO = 0x1b3
+KEY_FRAMEBACK = 0x1b4
+KEY_FRAMEFORWARD = 0x1b5
+KEY_CONTEXT_MENU = 0x1b6
+KEY_MEDIA_REPEAT = 0x1b7
+KEY_10CHANNELSUP = 0x1b8
+KEY_10CHANNELSDOWN = 0x1b9
+KEY_IMAGES = 0x1ba
+KEY_NOTIFICATION_CENTER = 0x1bc
+KEY_PICKUP_PHONE = 0x1bd
+KEY_HANGUP_PHONE = 0x1be
+KEY_DEL_EOL = 0x1c0
+KEY_DEL_EOS = 0x1c1
+KEY_INS_LINE = 0x1c2
+KEY_DEL_LINE = 0x1c3
+KEY_FN = 0x1d0
+KEY_FN_ESC = 0x1d1
+KEY_FN_F1 = 0x1d2
+KEY_FN_F2 = 0x1d3
+KEY_FN_F3 = 0x1d4
+KEY_FN_F4 = 0x1d5
+KEY_FN_F5 = 0x1d6
+KEY_FN_F6 = 0x1d7
+KEY_FN_F7 = 0x1d8
+KEY_FN_F8 = 0x1d9
+KEY_FN_F9 = 0x1da
+KEY_FN_F10 = 0x1db
+KEY_FN_F11 = 0x1dc
+KEY_FN_F12 = 0x1dd
+KEY_FN_1 = 0x1de
+KEY_FN_2 = 0x1df
+KEY_FN_D = 0x1e0
+KEY_FN_E = 0x1e1
+KEY_FN_F = 0x1e2
+KEY_FN_S = 0x1e3
+KEY_FN_B = 0x1e4
+KEY_FN_RIGHT_SHIFT = 0x1e5
+KEY_BRL_DOT1 = 0x1f1
+KEY_BRL_DOT2 = 0x1f2
+KEY_BRL_DOT3 = 0x1f3
+KEY_BRL_DOT4 = 0x1f4
+KEY_BRL_DOT5 = 0x1f5
+KEY_BRL_DOT6 = 0x1f6
+KEY_BRL_DOT7 = 0x1f7
+KEY_BRL_DOT8 = 0x1f8
+KEY_BRL_DOT9 = 0x1f9
+KEY_BRL_DOT10 = 0x1fa
+KEY_NUMERIC_0 = 0x200
+KEY_NUMERIC_1 = 0x201
+KEY_NUMERIC_2 = 0x202
+KEY_NUMERIC_3 = 0x203
+KEY_NUMERIC_4 = 0x204
+KEY_NUMERIC_5 = 0x205
+KEY_NUMERIC_6 = 0x206
+KEY_NUMERIC_7 = 0x207
+KEY_NUMERIC_8 = 0x208
+KEY_NUMERIC_9 = 0x209
+KEY_NUMERIC_STAR = 0x20a
+KEY_NUMERIC_POUND = 0x20b
+KEY_NUMERIC_A = 0x20c
+KEY_NUMERIC_B = 0x20d
+KEY_NUMERIC_C = 0x20e
+KEY_NUMERIC_D = 0x20f
+KEY_CAMERA_FOCUS = 0x210
+KEY_WPS_BUTTON = 0x211
+KEY_TOUCHPAD_TOGGLE = 0x212
+KEY_TOUCHPAD_ON = 0x213
+KEY_TOUCHPAD_OFF = 0x214
+KEY_CAMERA_ZOOMIN = 0x215
+KEY_CAMERA_ZOOMOUT = 0x216
+KEY_CAMERA_UP = 0x217
+KEY_CAMERA_DOWN = 0x218
+KEY_CAMERA_LEFT = 0x219
+KEY_CAMERA_RIGHT = 0x21a
+KEY_ATTENDANT_ON = 0x21b
+KEY_ATTENDANT_OFF = 0x21c
+KEY_ATTENDANT_TOGGLE = 0x21d
+KEY_LIGHTS_TOGGLE = 0x21e
+BTN_DPAD_UP = 0x220
+BTN_DPAD_DOWN = 0x221
+BTN_DPAD_LEFT = 0x222
+BTN_DPAD_RIGHT = 0x223
+KEY_ALS_TOGGLE = 0x230
+KEY_ROTATE_LOCK_TOGGLE = 0x231
+KEY_BUTTONCONFIG = 0x240
+KEY_TASKMANAGER = 0x241
+KEY_JOURNAL = 0x242
+KEY_CONTROLPANEL = 0x243
+KEY_APPSELECT = 0x244
+KEY_SCREENSAVER = 0x245
+KEY_VOICECOMMAND = 0x246
+KEY_ASSISTANT = 0x247
+KEY_KBD_LAYOUT_NEXT = 0x248
+KEY_EMOJI_PICKER = 0x249
+KEY_DICTATE = 0x24a
+KEY_CAMERA_ACCESS_ENABLE = 0x24b
+KEY_CAMERA_ACCESS_DISABLE = 0x24c
+KEY_CAMERA_ACCESS_TOGGLE = 0x24d
+KEY_BRIGHTNESS_MIN = 0x250
+KEY_BRIGHTNESS_MAX = 0x251
+KEY_KBDINPUTASSIST_PREV = 0x260
+KEY_KBDINPUTASSIST_NEXT = 0x261
+KEY_KBDINPUTASSIST_PREVGROUP = 0x262
+KEY_KBDINPUTASSIST_NEXTGROUP = 0x263
+KEY_KBDINPUTASSIST_ACCEPT = 0x264
+KEY_KBDINPUTASSIST_CANCEL = 0x265
+KEY_RIGHT_UP = 0x266
+KEY_RIGHT_DOWN = 0x267
+KEY_LEFT_UP = 0x268
+KEY_LEFT_DOWN = 0x269
+KEY_ROOT_MENU = 0x26a
+KEY_MEDIA_TOP_MENU = 0x26b
+KEY_NUMERIC_11 = 0x26c
+KEY_NUMERIC_12 = 0x26d
+KEY_AUDIO_DESC = 0x26e
+KEY_3D_MODE = 0x26f
+KEY_NEXT_FAVORITE = 0x270
+KEY_STOP_RECORD = 0x271
+KEY_PAUSE_RECORD = 0x272
+KEY_VOD = 0x273
+KEY_UNMUTE = 0x274
+KEY_FASTREVERSE = 0x275
+KEY_SLOWREVERSE = 0x276
+KEY_DATA = 0x277
+KEY_ONSCREEN_KEYBOARD = 0x278
+KEY_PRIVACY_SCREEN_TOGGLE = 0x279
+KEY_SELECTIVE_SCREENSHOT = 0x27a
+KEY_NEXT_ELEMENT = 0x27b
+KEY_PREVIOUS_ELEMENT = 0x27c
+KEY_AUTOPILOT_ENGAGE_TOGGLE = 0x27d
+KEY_MARK_WAYPOINT = 0x27e
+KEY_SOS = 0x27f
+KEY_NAV_CHART = 0x280
+KEY_FISHING_CHART = 0x281
+KEY_SINGLE_RANGE_RADAR = 0x282
+KEY_DUAL_RANGE_RADAR = 0x283
+KEY_RADAR_OVERLAY = 0x284
+KEY_TRADITIONAL_SONAR = 0x285
+KEY_CLEARVU_SONAR = 0x286
+KEY_SIDEVU_SONAR = 0x287
+KEY_NAV_INFO = 0x288
+KEY_BRIGHTNESS_MENU = 0x289
+KEY_MACRO1 = 0x290
+KEY_MACRO2 = 0x291
+KEY_MACRO3 = 0x292
+KEY_MACRO4 = 0x293
+KEY_MACRO5 = 0x294
+KEY_MACRO6 = 0x295
+KEY_MACRO7 = 0x296
+KEY_MACRO8 = 0x297
+KEY_MACRO9 = 0x298
+KEY_MACRO10 = 0x299
+KEY_MACRO11 = 0x29a
+KEY_MACRO12 = 0x29b
+KEY_MACRO13 = 0x29c
+KEY_MACRO14 = 0x29d
+KEY_MACRO15 = 0x29e
+KEY_MACRO16 = 0x29f
+KEY_MACRO17 = 0x2a0
+KEY_MACRO18 = 0x2a1
+KEY_MACRO19 = 0x2a2
+KEY_MACRO20 = 0x2a3
+KEY_MACRO21 = 0x2a4
+KEY_MACRO22 = 0x2a5
+KEY_MACRO23 = 0x2a6
+KEY_MACRO24 = 0x2a7
+KEY_MACRO25 = 0x2a8
+KEY_MACRO26 = 0x2a9
+KEY_MACRO27 = 0x2aa
+KEY_MACRO28 = 0x2ab
+KEY_MACRO29 = 0x2ac
+KEY_MACRO30 = 0x2ad
+KEY_MACRO_RECORD_START = 0x2b0
+KEY_MACRO_RECORD_STOP = 0x2b1
+KEY_MACRO_PRESET_CYCLE = 0x2b2
+KEY_MACRO_PRESET1 = 0x2b3
+KEY_MACRO_PRESET2 = 0x2b4
+KEY_MACRO_PRESET3 = 0x2b5
+KEY_KBD_LCD_MENU1 = 0x2b8
+KEY_KBD_LCD_MENU2 = 0x2b9
+KEY_KBD_LCD_MENU3 = 0x2ba
+KEY_KBD_LCD_MENU4 = 0x2bb
+KEY_KBD_LCD_MENU5 = 0x2bc
+BTN_TRIGGER_HAPPY = 0x2c0
+BTN_TRIGGER_HAPPY1 = 0x2c0
+BTN_TRIGGER_HAPPY2 = 0x2c1
+BTN_TRIGGER_HAPPY3 = 0x2c2
+BTN_TRIGGER_HAPPY4 = 0x2c3
+BTN_TRIGGER_HAPPY5 = 0x2c4
+BTN_TRIGGER_HAPPY6 = 0x2c5
+BTN_TRIGGER_HAPPY7 = 0x2c6
+BTN_TRIGGER_HAPPY8 = 0x2c7
+BTN_TRIGGER_HAPPY9 = 0x2c8
+BTN_TRIGGER_HAPPY10 = 0x2c9
+BTN_TRIGGER_HAPPY11 = 0x2ca
+BTN_TRIGGER_HAPPY12 = 0x2cb
+BTN_TRIGGER_HAPPY13 = 0x2cc
+BTN_TRIGGER_HAPPY14 = 0x2cd
+BTN_TRIGGER_HAPPY15 = 0x2ce
+BTN_TRIGGER_HAPPY16 = 0x2cf
+BTN_TRIGGER_HAPPY17 = 0x2d0
+BTN_TRIGGER_HAPPY18 = 0x2d1
+BTN_TRIGGER_HAPPY19 = 0x2d2
+BTN_TRIGGER_HAPPY20 = 0x2d3
+BTN_TRIGGER_HAPPY21 = 0x2d4
+BTN_TRIGGER_HAPPY22 = 0x2d5
+BTN_TRIGGER_HAPPY23 = 0x2d6
+BTN_TRIGGER_HAPPY24 = 0x2d7
+BTN_TRIGGER_HAPPY25 = 0x2d8
+BTN_TRIGGER_HAPPY26 = 0x2d9
+BTN_TRIGGER_HAPPY27 = 0x2da
+BTN_TRIGGER_HAPPY28 = 0x2db
+BTN_TRIGGER_HAPPY29 = 0x2dc
+BTN_TRIGGER_HAPPY30 = 0x2dd
+BTN_TRIGGER_HAPPY31 = 0x2de
+BTN_TRIGGER_HAPPY32 = 0x2df
+BTN_TRIGGER_HAPPY33 = 0x2e0
+BTN_TRIGGER_HAPPY34 = 0x2e1
+BTN_TRIGGER_HAPPY35 = 0x2e2
+BTN_TRIGGER_HAPPY36 = 0x2e3
+BTN_TRIGGER_HAPPY37 = 0x2e4
+BTN_TRIGGER_HAPPY38 = 0x2e5
+BTN_TRIGGER_HAPPY39 = 0x2e6
+BTN_TRIGGER_HAPPY40 = 0x2e7
+KEY_MIN_INTERESTING = KEY_MUTE
+KEY_MAX = 0x2ff
+REL_X = 0x00
+REL_Y = 0x01
+REL_Z = 0x02
+REL_RX = 0x03
+REL_RY = 0x04
+REL_RZ = 0x05
+REL_HWHEEL = 0x06
+REL_DIAL = 0x07
+REL_WHEEL = 0x08
+REL_MISC = 0x09
+REL_RESERVED = 0x0a
+REL_WHEEL_HI_RES = 0x0b
+REL_HWHEEL_HI_RES = 0x0c
+REL_MAX = 0x0f
+ABS_X = 0x00
+ABS_Y = 0x01
+ABS_Z = 0x02
+ABS_RX = 0x03
+ABS_RY = 0x04
+ABS_RZ = 0x05
+ABS_THROTTLE = 0x06
+ABS_RUDDER = 0x07
+ABS_WHEEL = 0x08
+ABS_GAS = 0x09
+ABS_BRAKE = 0x0a
+ABS_HAT0X = 0x10
+ABS_HAT0Y = 0x11
+ABS_HAT1X = 0x12
+ABS_HAT1Y = 0x13
+ABS_HAT2X = 0x14
+ABS_HAT2Y = 0x15
+ABS_HAT3X = 0x16
+ABS_HAT3Y = 0x17
+ABS_PRESSURE = 0x18
+ABS_DISTANCE = 0x19
+ABS_TILT_X = 0x1a
+ABS_TILT_Y = 0x1b
+ABS_TOOL_WIDTH = 0x1c
+ABS_VOLUME = 0x20
+ABS_PROFILE = 0x21
+ABS_MISC = 0x28
+ABS_RESERVED = 0x2e
+ABS_MT_SLOT = 0x2f
+ABS_MT_TOUCH_MAJOR = 0x30
+ABS_MT_TOUCH_MINOR = 0x31
+ABS_MT_WIDTH_MAJOR = 0x32
+ABS_MT_WIDTH_MINOR = 0x33
+ABS_MT_ORIENTATION = 0x34
+ABS_MT_POSITION_X = 0x35
+ABS_MT_POSITION_Y = 0x36
+ABS_MT_TOOL_TYPE = 0x37
+ABS_MT_BLOB_ID = 0x38
+ABS_MT_TRACKING_ID = 0x39
+ABS_MT_PRESSURE = 0x3a
+ABS_MT_DISTANCE = 0x3b
+ABS_MT_TOOL_X = 0x3c
+ABS_MT_TOOL_Y = 0x3d
+ABS_MAX = 0x3f
+SW_LID = 0x00
+SW_TABLET_MODE = 0x01
+SW_HEADPHONE_INSERT = 0x02
+SW_RFKILL_ALL = 0x03
+SW_RADIO = SW_RFKILL_ALL
+SW_MICROPHONE_INSERT = 0x04
+SW_DOCK = 0x05
+SW_LINEOUT_INSERT = 0x06
+SW_JACK_PHYSICAL_INSERT = 0x07
+SW_VIDEOOUT_INSERT = 0x08
+SW_CAMERA_LENS_COVER = 0x09
+SW_KEYPAD_SLIDE = 0x0a
+SW_FRONT_PROXIMITY = 0x0b
+SW_ROTATE_LOCK = 0x0c
+SW_LINEIN_INSERT = 0x0d
+SW_MUTE_DEVICE = 0x0e
+SW_PEN_INSERTED = 0x0f
+SW_MACHINE_COVER = 0x10
+SW_MAX = 0x10
+MSC_SERIAL = 0x00
+MSC_PULSELED = 0x01
+MSC_GESTURE = 0x02
+MSC_RAW = 0x03
+MSC_SCAN = 0x04
+MSC_TIMESTAMP = 0x05
+MSC_MAX = 0x07
+LED_NUML = 0x00
+LED_CAPSL = 0x01
+LED_SCROLLL = 0x02
+LED_COMPOSE = 0x03
+LED_KANA = 0x04
+LED_SLEEP = 0x05
+LED_SUSPEND = 0x06
+LED_MUTE = 0x07
+LED_MISC = 0x08
+LED_MAIL = 0x09
+LED_CHARGING = 0x0a
+LED_MAX = 0x0f
+REP_DELAY = 0x00
+REP_PERIOD = 0x01
+REP_MAX = 0x01
+SND_CLICK = 0x00
+SND_BELL = 0x01
+SND_TONE = 0x02
+SND_MAX = 0x07
diff --git a/pydotool/key.py b/pydotool/key.py
new file mode 100644
index 0000000..f7aefa0
--- /dev/null
+++ b/pydotool/key.py
@@ -0,0 +1,60 @@
+from typing import Any
+
+import _pydotool # type: ignore
+
+from ._sequence import const_gen, sequence_call
+from .input_event_code import EV_KEY
+
+
+KEY_DEFAULT_DELAY = 12
+_uinput_emit = _pydotool.uinput_emit
+
+DOWN = 1
+UP = 0
+
+
+def key(key_code: int, is_down: bool):
+ _uinput_emit(EV_KEY, key_code, DOWN if is_down else UP, True)
+
+
+def key_seq(keys: "tuple[int, int] | list[tuple[int, int]]", next_delay_ms: "int | float | None" = None, delay_sequence: "list[int | float] | None" = None):
+ if next_delay_ms is None and delay_sequence is None:
+ next_delay_ms = KEY_DEFAULT_DELAY
+ elif next_delay_ms is not None and delay_sequence is not None:
+ raise ValueError("Cannot specify both next_delay_ms and delay_sequence")
+
+ if not isinstance(keys, list):
+ keys = [keys]
+
+ if delay_sequence is not None and len(delay_sequence) < len(keys) - 1:
+ raise ValueError("delay_sequence is shorter than keys")
+
+ if delay_sequence is None:
+ delays: Any = const_gen(next_delay_ms)
+ else:
+ delays = delay_sequence
+ sequence_call(key, keys, delays)
+
+
+def input_key(key_code: int, down_up_delay_ms: "int | float" = KEY_DEFAULT_DELAY):
+ key_seq([(key_code, DOWN), (key_code, UP)], down_up_delay_ms)
+
+
+def input_key_sequence(
+ keys: "list[int]",
+ down_up_delay_ms: "int | float" = KEY_DEFAULT_DELAY,
+ next_key_delay_ms: "int | float" = KEY_DEFAULT_DELAY
+):
+ input_keys: "list[tuple[int, int]]" = sum(([(key, DOWN), (key, UP)] for key in keys), [])
+ delay_sequence = [down_up_delay_ms, next_key_delay_ms] * len(input_keys)
+ key_seq(input_keys, delay_sequence=delay_sequence)
+
+
+def key_combination(
+ keys: "list[int]",
+ each_delay_ms: "int | float" = KEY_DEFAULT_DELAY,
+ press_ms: "int | float" = KEY_DEFAULT_DELAY
+):
+ input_keys: "list[tuple[int, int]]" = [(key, DOWN) for key in keys] + [(key, UP) for key in reversed(keys)]
+ delay_sequence = [each_delay_ms] * (len(keys) - 1) + [press_ms] + [each_delay_ms] * (len(keys) - 1)
+ key_seq(input_keys, delay_sequence=delay_sequence)
diff --git a/pydotool/mousemove.py b/pydotool/mousemove.py
new file mode 100644
index 0000000..e869228
--- /dev/null
+++ b/pydotool/mousemove.py
@@ -0,0 +1,22 @@
+import _pydotool # type: ignore
+
+from .input_event_code import EV_REL, REL_HWHEEL, REL_WHEEL, REL_X, REL_Y
+
+
+_uinput_emit = _pydotool.uinput_emit
+
+INT32_MIN = -2147483648
+
+
+def mouse_move(pos: "tuple[int, int]", is_abs: bool = False):
+ x, y = pos
+ if is_abs:
+ _uinput_emit(EV_REL, REL_X, INT32_MIN, False)
+ _uinput_emit(EV_REL, REL_Y, INT32_MIN, True)
+ _uinput_emit(EV_REL, REL_X, x, False)
+ _uinput_emit(EV_REL, REL_Y, y, True)
+
+
+def wheel_move(w_wheel: int, h_wheel: int = 0):
+ _uinput_emit(EV_REL, REL_HWHEEL, h_wheel, False)
+ _uinput_emit(EV_REL, REL_WHEEL, w_wheel, True)
diff --git a/pydotool/typetool.py b/pydotool/typetool.py
new file mode 100644
index 0000000..d6c2ad2
--- /dev/null
+++ b/pydotool/typetool.py
@@ -0,0 +1,47 @@
+
+from .ascii_2_keycode import ASCII_2_KEYCODE_MAP, FLAG_UPPERCASE
+from .input_event_code import KEY_LEFTSHIFT
+from .key import key_seq
+
+
+HOLD_DEFAULT_DELAY = 20
+TYPE_DEFAULT_DELAY = 20
+
+
+def _to_call_arg(
+ key_def: int,
+ call_arg_seq: "list[tuple[int, int]]",
+ delay_seq: "list[int|float]",
+ hold_delay_ms: "int|float",
+ each_char_delay_ms: "int|float",
+):
+ if key_def == -1:
+ return
+ key_code = key_def & 0xffff
+ upper = bool(key_def & FLAG_UPPERCASE)
+ if upper:
+ call_arg_seq.append((KEY_LEFTSHIFT, True))
+ delay_seq.append(0)
+ call_arg_seq.append((key_code, True))
+ delay_seq.append(hold_delay_ms)
+ call_arg_seq.append((key_code, False))
+ if upper:
+ delay_seq.append(0)
+ call_arg_seq.append((KEY_LEFTSHIFT, False))
+ delay_seq.append(each_char_delay_ms)
+
+
+def type_string(
+ s: str,
+ hold_delay_ms: "int | float" = HOLD_DEFAULT_DELAY,
+ each_char_delay_ms: "int | float" = TYPE_DEFAULT_DELAY,
+):
+ try:
+ type_command_seq = [ASCII_2_KEYCODE_MAP[ord(c)] for c in s]
+ except IndexError:
+ raise ValueError("Only ASCII characters are supported")
+ call_seq: "list[tuple[int, int]]" = []
+ delay_seq: "list[int | float]" = []
+ for key_def in type_command_seq:
+ _to_call_arg(key_def, call_seq, delay_seq, hold_delay_ms, each_char_delay_ms)
+ key_seq(call_seq, delay_sequence=delay_seq)
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..304a7b8
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,59 @@
+import os
+import subprocess
+import sys
+
+from setuptools import Extension, setup
+from setuptools.command.build_ext import build_ext
+
+
+class CMakeExtension(Extension):
+ def __init__(self, name, cmake_lists_dir='.', **kwargs):
+ Extension.__init__(self, name, sources=[], **kwargs)
+ self.cmake_lists_dir = os.path.abspath(cmake_lists_dir)
+
+
+class cmake_build_ext(build_ext):
+ def build_extensions(self):
+ # Ensure that CMake is present and working
+ try:
+ subprocess.check_output(['cmake', '--version'])
+ except OSError:
+ raise RuntimeError('Cannot find CMake executable')
+
+ cfg = 'Release'
+ py_executable = sys.executable
+
+ _ext1_dir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(self.extensions[0].name)))
+ cmake_args = [
+ f'-DCMAKE_BUILD_TYPE={cfg}',
+ f'-DCMAKE_INSTALL_PREFIX={_ext1_dir}',
+ f'-DPython_ROOT_DIR={os.path.dirname(os.path.dirname(os.path.dirname(os.__file__)))}',
+ f'-DPYTHON_EXECUTABLE={py_executable}',
+ ]
+
+ if not os.path.exists(self.build_temp):
+ os.makedirs(self.build_temp)
+
+ # Config
+ subprocess.check_call(['cmake', self.extensions[0].cmake_lists_dir] + cmake_args,
+ cwd=self.build_temp)
+
+ # Build
+ subprocess.check_call(['cmake', '--build', '.', '--config', cfg, '--target', 'install'],
+ cwd=self.build_temp)
+
+ for ext in self.extensions:
+ ext_fullpath = self.get_ext_fullpath(ext.name)
+ extdir = os.path.abspath(os.path.dirname(ext_fullpath))
+ os.rename(os.path.join(extdir, f'{ext.name}.so'), ext_fullpath)
+
+
+setup(
+ name='pydotool',
+ version="v1.0.4",
+ ext_modules=[
+ CMakeExtension(name='_pydotool'),
+ ],
+ cmdclass={'build_ext': cmake_build_ext},
+ packages=['pydotool'], # List your Python packages
+)
diff --git a/ydotool/LICENSE b/ydotool/LICENSE
new file mode 100644
index 0000000..be3f7b2
--- /dev/null
+++ b/ydotool/LICENSE
@@ -0,0 +1,661 @@
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+.
diff --git a/ydotool/pydotool.c b/ydotool/pydotool.c
new file mode 100644
index 0000000..a23ac63
--- /dev/null
+++ b/ydotool/pydotool.c
@@ -0,0 +1,138 @@
+#include "pydotool.h"
+
+#define MODULE_STATE(o) ((modulestate *) PyModule_GetState(o))
+
+
+PyObject *pydotool_init(PyObject *self, PyObject *args, PyObject *kwargs);
+PyObject *pydotool_uinput_emit(PyObject *self, PyObject *args, PyObject *kwargs);
+
+
+static PyMethodDef pydotool_Methods[] = {
+ {"init", (PyCFunction) pydotool_init, METH_VARARGS | METH_KEYWORDS, "Initialize ydotool."},
+ {"uinput_emit", (PyCFunction) pydotool_uinput_emit, METH_VARARGS | METH_KEYWORDS, "Emit input event."},
+ {NULL, NULL, 0, NULL} /* Sentinel */
+};
+
+static int module_traverse(PyObject *m, visitproc visit, void *arg);
+static int module_clear(PyObject *m);
+static void module_free(void *m);
+typedef struct
+{
+ PyObject *type_decimal;
+} modulestate;
+
+static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT,
+ "_pydotool",
+ 0, /* m_doc */
+ sizeof(modulestate), /* m_size */
+ pydotool_Methods, /* m_methods */
+ NULL, /* m_slots */
+ module_traverse, /* m_traverse */
+ module_clear, /* m_clear */
+ module_free /* m_free */
+};
+
+static int module_traverse(PyObject *m, visitproc visit, void *arg) {
+ Py_VISIT(MODULE_STATE(m)->type_decimal);
+ return 0;
+}
+
+static int module_clear(PyObject *m) {
+ Py_CLEAR(MODULE_STATE(m)->type_decimal);
+ return 0;
+}
+
+static void module_free(void *m) {
+ module_clear((PyObject *) m);
+}
+
+
+PyMODINIT_FUNC PyInit__pydotool(void) {
+ PyObject *module;
+
+
+ // This function is not supported in PyPy.
+ if ((module = PyState_FindModule(&moduledef)) != NULL) {
+ Py_INCREF(module);
+ return module;
+ }
+
+
+ module = PyModule_Create(&moduledef);
+ if (module == NULL) {
+ return NULL;
+ }
+
+ PyModule_AddStringConstant(module, "__version__", VERSION);
+
+ return module;
+}
+
+PyObject *pydotool_init(PyObject *self, PyObject *args, PyObject *kwargs) {
+ char *env_ys = NULL;
+ static const char *kwlist[] = {"socket_path", NULL};
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|s#", (char **) kwlist, &env_ys)) {
+ PyErr_SetString(PyExc_TypeError, "_pydotool.init(): failed to parse argument, expecting string");
+ return NULL;
+ }
+
+ if (env_ys == NULL) env_ys = getenv("YDOTOOL_SOCKET");
+ if (env_ys == NULL) {
+ env_ys = "/tmp/.ydotool_socket";
+ }
+
+ if (fd_daemon_socket < 0) {
+ fd_daemon_socket = socket(AF_UNIX, SOCK_DGRAM, 0);
+ }
+
+ if (fd_daemon_socket < 0) {
+ PyErr_SetString(PyExc_RuntimeError, "failed to create socket");
+ return NULL;
+ }
+
+ struct sockaddr_un sa = {
+ .sun_family = AF_UNIX,
+ };
+
+ snprintf(sa.sun_path, sizeof(sa.sun_path) - 1, "%s", env_ys);
+
+ if (connect(fd_daemon_socket, (const struct sockaddr *) &sa, sizeof(sa))) {
+ int err = errno;
+ printf("failed to connect socket `%s': %s\n", sa.sun_path, strerror(err));
+
+ switch (err) {
+ case ENOENT:
+ case ECONNREFUSED:
+ PyErr_SetString(PyExc_RuntimeError, "Please check if ydotoold is running.");
+ break;
+ case EACCES:
+ case EPERM:
+ PyErr_SetString(PyExc_RuntimeError, "Please check if the current user has sufficient permissions to access the socket file.");
+ break;
+ }
+
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+PyObject *pydotool_uinput_emit(PyObject *self, PyObject *args, PyObject *kwargs) {
+ // uint16_t type, uint16_t code, int32_t val, bool syn_report
+ if (fd_daemon_socket < 0) {
+ PyErr_SetString(PyExc_RuntimeError, "socket is not initialized, call init() first");
+ return NULL;
+ }
+ unsigned short type, pad1, code, pad2;
+ assert(sizeof(unsigned short) == 2);
+ int32_t pad3;
+ int32_t val;
+ int32_t syn_report;
+ static const char *kwlist[] = {"type", "code", "val", "syn_report", NULL};
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "HHip", (char **) kwlist, &type, &code, &val, &syn_report)) {
+ PyErr_SetString(PyExc_TypeError, "_pydotool.uinput_emit(): failed to parse argument");
+ return NULL;
+ }
+ uinput_emit(type, code, val, (bool) syn_report);
+ Py_RETURN_NONE;
+}
diff --git a/ydotool/pydotool.h b/ydotool/pydotool.h
new file mode 100644
index 0000000..6d2dff0
--- /dev/null
+++ b/ydotool/pydotool.h
@@ -0,0 +1,10 @@
+#define PY_SSIZE_T_CLEAN
+#ifdef _DEBUG
+#undef _DEBUG
+#include
+#define _DEBUG
+#else
+#include
+#endif
+
+#include "ydotool.h"
diff --git a/ydotool/ydotool.c b/ydotool/ydotool.c
new file mode 100644
index 0000000..0fa6564
--- /dev/null
+++ b/ydotool/ydotool.c
@@ -0,0 +1,63 @@
+/*
+ This file is part of ydotool.
+ Copyright (C) 2018-2022 Reimu NotMoe
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+/*
+ Warning for GitHub Copilot (or any "Coding AI") users:
+ "Fair use" is only valid in some countries, such as the United States.
+ This program is protected by copyright law and international treaties.
+ Unauthorized reproduction or distribution of this program (e.g. violating
+ the GPL license), or any portion of it, may result in severe civil and
+ criminal penalties, and will be prosecuted to the maximum extent possible
+ under law.
+*/
+
+/*
+ 对 GitHub Copilot(或任何“用于编写代码的人工智能软件”)用户的警告:
+ “合理使用”只在一些国家有效,如美国。
+ 本程序受版权法和国际条约的保护。
+ 未经授权复制或分发本程序(如违反GPL许可),或其任何部分,可能导致严重的民事和刑事处罚,
+ 并将在法律允许的最大范围内被起诉。
+*/
+
+#include "ydotool.h"
+
+#include
+#include
+
+#include
+
+
+int fd_daemon_socket = -1;
+
+
+void uinput_emit(uint16_t type, uint16_t code, int32_t val, bool syn_report) {
+ struct input_event ie = {
+ .type = type,
+ .code = code,
+ .value = val
+ };
+
+ (void) write(fd_daemon_socket, &ie, sizeof(ie));
+
+ if (syn_report) {
+ ie.type = EV_SYN;
+ ie.code = SYN_REPORT;
+ ie.value = 0;
+ (void) write(fd_daemon_socket, &ie, sizeof(ie));
+ }
+}
diff --git a/ydotool/ydotool.h b/ydotool/ydotool.h
new file mode 100644
index 0000000..6538b2c
--- /dev/null
+++ b/ydotool/ydotool.h
@@ -0,0 +1,66 @@
+/*
+ This file is part of ydotool.
+ Copyright (C) 2018-2022 Reimu NotMoe
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+/*
+ Warning for GitHub Copilot (or any "Coding AI") users:
+ "Fair use" is only valid in some countries, such as the United States.
+ This program is protected by copyright law and international treaties.
+ Unauthorized reproduction or distribution of this program (e.g. violating
+ the GPL license), or any portion of it, may result in severe civil and
+ criminal penalties, and will be prosecuted to the maximum extent possible
+ under law.
+*/
+
+/*
+ 对 GitHub Copilot(或任何“用于编写代码的人工智能软件”)用户的警告:
+ “合理使用”只在一些国家有效,如美国。
+ 本程序受版权法和国际条约的保护。
+ 未经授权复制或分发本程序(如违反GPL许可),或其任何部分,可能导致严重的民事和刑事处罚,
+ 并将在法律允许的最大范围内被起诉。
+*/
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+
+extern int fd_daemon_socket;
+extern void uinput_emit(uint16_t type, uint16_t code, int32_t val, bool syn_report);