ydotool 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ydotool-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 astalunavortex
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
ydotool-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,201 @@
1
+ Metadata-Version: 2.4
2
+ Name: ydotool
3
+ Version: 0.1.0
4
+ Summary: Python wrapper for ydotool
5
+ Keywords: ydotool,wayland,uinput,automation
6
+ Author: astalunavortex
7
+ Author-email: astalunavortex <astalunavortex@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Operating System :: POSIX :: Linux
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Programming Language :: Python :: 3.15
17
+ Requires-Dist: pytest>=8 ; extra == 'dev'
18
+ Requires-Python: >=3.12
19
+ Project-URL: Homepage, https://github.com/astalunavortex/ydotool-py
20
+ Project-URL: Repository, https://github.com/astalunavortex/ydotool-py
21
+ Project-URL: Issues, https://github.com/astalunavortex/ydotool-py/issues
22
+ Provides-Extra: dev
23
+ Description-Content-Type: text/markdown
24
+
25
+ # ydotool-py
26
+
27
+ [![PyPI version](https://img.shields.io/pypi/v/ydotool.svg)](https://pypi.org/project/ydotool/)
28
+ [![Python versions](https://img.shields.io/pypi/pyversions/ydotool.svg)](https://pypi.org/project/ydotool/)
29
+ [![License: MIT](https://img.shields.io/badge/License-MIT-gren.svg)](https://opensource.org/licenses/MIT)
30
+
31
+ Python wrapper for [ydotool](https://github.com/ReimuNotMoe/ydotool) - a generic command-line automation tool that works on **Wayland**, **X11**, and even **TTY**.
32
+
33
+ Unlike `pyautogui`, `ydotool` operates at the kernel level via `/dev/uinput`, making it compatible with any Linux desktop environment.
34
+
35
+ ## Features
36
+
37
+ - **Mouse**: move, click, scroll
38
+ - **Keyboard**: type text, press keys, hotkey combinations
39
+
40
+ ## Installation
41
+
42
+ ### 1. Install ydotool
43
+
44
+ ```bash
45
+ # Debian/Ubuntu
46
+ sudo apt install ydotool
47
+
48
+ # Arch Linux
49
+ sudo pacman -S ydotool
50
+
51
+ # Fedora
52
+ sudo dnf install ydotool
53
+ ```
54
+ If your distro doesn't have `ydotool`, you can build it from source [ydotool](https://github.com/ReimuNotMoe/ydotool)
55
+
56
+ ### 2. Start the daemon
57
+
58
+ ```bash
59
+ # Enable for current user
60
+ systemctl --user enable --now ydotool.service
61
+
62
+ # Or run manually
63
+ ydotoold
64
+ ```
65
+
66
+ ### 4. Install Python package
67
+
68
+ ```bash
69
+ # Add with uv
70
+ uv add ydotool
71
+
72
+ # Or install with pip
73
+ pip install ydotool
74
+ ```
75
+
76
+ ## Quick Start
77
+
78
+ ```python
79
+ from ydotool import do_click, do_mousemove, do_type, do_key, do_wheel
80
+
81
+ # Move mouse and click
82
+ do_mousemove(500, 300)
83
+ do_click('left', 'click')
84
+
85
+ # Type text
86
+ do_type("Hello, Wayland!")
87
+
88
+ # Press key combination (Ctrl+S)
89
+ do_key(['ctrl:1', 's:1', 's:0', 'ctrl:0'])
90
+
91
+ # Scroll down
92
+ do_wheel(vertical=-3)
93
+
94
+ # Double-click
95
+ do_click('left', 'click', repeat=2)
96
+ ```
97
+
98
+ ## API Reference
99
+
100
+ ### Mouse Control
101
+
102
+ #### `do_click(button, action, repeat=None, next_delay=None)`
103
+
104
+ Perform mouse click action.
105
+
106
+ - **button**: `'left'`, `'right'`, `'middle'`, `'side'`, `'extra'`, `'forward'`, `'back'`
107
+ - **action**: `'click'`, `'down'`, `'up'`
108
+ - **repeat**: Number of repetitions
109
+ - **next_delay**: Delay between events in milliseconds
110
+
111
+ ```python
112
+ do_click('left', 'click') # Single left click
113
+ do_click('right', 'click', repeat=2) # Double right click
114
+ do_click('left', 'down') # Press and hold
115
+ do_click('left', 'up') # Release
116
+ ```
117
+
118
+ #### `do_mousemove(x, y, absolute=True)`
119
+
120
+ Move mouse cursor.
121
+
122
+ ```python
123
+ do_mousemove(500, 300) # Absolute position
124
+ do_mousemove(100, -50, absolute=False) # Relative movement
125
+ ```
126
+
127
+ #### `do_wheel(vertical=0, horizontal=0)`
128
+
129
+ Scroll mouse wheel.
130
+
131
+ ```python
132
+ do_wheel(vertical=3) # Scroll up
133
+ do_wheel(vertical=-5) # Scroll down
134
+ do_wheel(horizontal=2) # Scroll right
135
+ ```
136
+
137
+ ### Keyboard Control
138
+
139
+ #### `do_type(text, key_delay=None, key_hold=None)`
140
+
141
+ Type text string. Handles special characters automatically.
142
+
143
+ ```python
144
+ do_type("Hello, World!")
145
+ do_type("Slow typing", key_delay=100)
146
+ ```
147
+
148
+ #### `do_key(keys)`
149
+
150
+ Press keyboard keys in sequence.
151
+
152
+ Keys are specified as `'keyname:state'` where state is `'1'` (down) or `'0'` (up).
153
+
154
+ ```python
155
+ # Press Enter
156
+ do_key(['enter:1', 'enter:0'])
157
+
158
+ # Ctrl+S
159
+ do_key(['ctrl:1', 's:1', 's:0', 'ctrl:0'])
160
+
161
+ # Alt+Tab
162
+ do_key(['alt:1', 'tab:1', 'tab:0', 'alt:0'])
163
+ ```
164
+
165
+ See [keycodes.py](src/ydotool/keycodes.py) for the complete list of key names.
166
+
167
+
168
+ ## Troubleshooting
169
+
170
+ ### "Failed to connect to ydotoold"
171
+
172
+ Make sure the daemon is running:
173
+
174
+ ```bash
175
+ systemctl --user status ydotool.service
176
+ # or
177
+ pgrep -a ydotoold
178
+ ```
179
+
180
+ ### "Permission denied" on `/dev/uinput`
181
+
182
+ Check udev rules and log out/in:
183
+
184
+ ```bash
185
+ ls -l /dev/uinput
186
+ # Should show your user has access
187
+ ```
188
+
189
+ ### Mouse coordinates are wrong
190
+
191
+ Disable mouse acceleration and set mouse sensetivity to default in your desktop environment settings.
192
+
193
+ ### Keys not working
194
+
195
+ Check that you're using the correct format: `'keyname:state'` (e.g., `'ctrl:1'`, not just `'ctrl'`).
196
+
197
+ ## License
198
+
199
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) for details.
200
+
201
+ [ydotool](https://github.com/ReimuNotMoe/ydotool) is licensed under [AGPL-3.0](https://github.com/ReimuNotMoe/ydotool/blob/master/LICENSE).
@@ -0,0 +1,177 @@
1
+ # ydotool-py
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/ydotool.svg)](https://pypi.org/project/ydotool/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/ydotool.svg)](https://pypi.org/project/ydotool/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-gren.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ Python wrapper for [ydotool](https://github.com/ReimuNotMoe/ydotool) - a generic command-line automation tool that works on **Wayland**, **X11**, and even **TTY**.
8
+
9
+ Unlike `pyautogui`, `ydotool` operates at the kernel level via `/dev/uinput`, making it compatible with any Linux desktop environment.
10
+
11
+ ## Features
12
+
13
+ - **Mouse**: move, click, scroll
14
+ - **Keyboard**: type text, press keys, hotkey combinations
15
+
16
+ ## Installation
17
+
18
+ ### 1. Install ydotool
19
+
20
+ ```bash
21
+ # Debian/Ubuntu
22
+ sudo apt install ydotool
23
+
24
+ # Arch Linux
25
+ sudo pacman -S ydotool
26
+
27
+ # Fedora
28
+ sudo dnf install ydotool
29
+ ```
30
+ If your distro doesn't have `ydotool`, you can build it from source [ydotool](https://github.com/ReimuNotMoe/ydotool)
31
+
32
+ ### 2. Start the daemon
33
+
34
+ ```bash
35
+ # Enable for current user
36
+ systemctl --user enable --now ydotool.service
37
+
38
+ # Or run manually
39
+ ydotoold
40
+ ```
41
+
42
+ ### 4. Install Python package
43
+
44
+ ```bash
45
+ # Add with uv
46
+ uv add ydotool
47
+
48
+ # Or install with pip
49
+ pip install ydotool
50
+ ```
51
+
52
+ ## Quick Start
53
+
54
+ ```python
55
+ from ydotool import do_click, do_mousemove, do_type, do_key, do_wheel
56
+
57
+ # Move mouse and click
58
+ do_mousemove(500, 300)
59
+ do_click('left', 'click')
60
+
61
+ # Type text
62
+ do_type("Hello, Wayland!")
63
+
64
+ # Press key combination (Ctrl+S)
65
+ do_key(['ctrl:1', 's:1', 's:0', 'ctrl:0'])
66
+
67
+ # Scroll down
68
+ do_wheel(vertical=-3)
69
+
70
+ # Double-click
71
+ do_click('left', 'click', repeat=2)
72
+ ```
73
+
74
+ ## API Reference
75
+
76
+ ### Mouse Control
77
+
78
+ #### `do_click(button, action, repeat=None, next_delay=None)`
79
+
80
+ Perform mouse click action.
81
+
82
+ - **button**: `'left'`, `'right'`, `'middle'`, `'side'`, `'extra'`, `'forward'`, `'back'`
83
+ - **action**: `'click'`, `'down'`, `'up'`
84
+ - **repeat**: Number of repetitions
85
+ - **next_delay**: Delay between events in milliseconds
86
+
87
+ ```python
88
+ do_click('left', 'click') # Single left click
89
+ do_click('right', 'click', repeat=2) # Double right click
90
+ do_click('left', 'down') # Press and hold
91
+ do_click('left', 'up') # Release
92
+ ```
93
+
94
+ #### `do_mousemove(x, y, absolute=True)`
95
+
96
+ Move mouse cursor.
97
+
98
+ ```python
99
+ do_mousemove(500, 300) # Absolute position
100
+ do_mousemove(100, -50, absolute=False) # Relative movement
101
+ ```
102
+
103
+ #### `do_wheel(vertical=0, horizontal=0)`
104
+
105
+ Scroll mouse wheel.
106
+
107
+ ```python
108
+ do_wheel(vertical=3) # Scroll up
109
+ do_wheel(vertical=-5) # Scroll down
110
+ do_wheel(horizontal=2) # Scroll right
111
+ ```
112
+
113
+ ### Keyboard Control
114
+
115
+ #### `do_type(text, key_delay=None, key_hold=None)`
116
+
117
+ Type text string. Handles special characters automatically.
118
+
119
+ ```python
120
+ do_type("Hello, World!")
121
+ do_type("Slow typing", key_delay=100)
122
+ ```
123
+
124
+ #### `do_key(keys)`
125
+
126
+ Press keyboard keys in sequence.
127
+
128
+ Keys are specified as `'keyname:state'` where state is `'1'` (down) or `'0'` (up).
129
+
130
+ ```python
131
+ # Press Enter
132
+ do_key(['enter:1', 'enter:0'])
133
+
134
+ # Ctrl+S
135
+ do_key(['ctrl:1', 's:1', 's:0', 'ctrl:0'])
136
+
137
+ # Alt+Tab
138
+ do_key(['alt:1', 'tab:1', 'tab:0', 'alt:0'])
139
+ ```
140
+
141
+ See [keycodes.py](src/ydotool/keycodes.py) for the complete list of key names.
142
+
143
+
144
+ ## Troubleshooting
145
+
146
+ ### "Failed to connect to ydotoold"
147
+
148
+ Make sure the daemon is running:
149
+
150
+ ```bash
151
+ systemctl --user status ydotool.service
152
+ # or
153
+ pgrep -a ydotoold
154
+ ```
155
+
156
+ ### "Permission denied" on `/dev/uinput`
157
+
158
+ Check udev rules and log out/in:
159
+
160
+ ```bash
161
+ ls -l /dev/uinput
162
+ # Should show your user has access
163
+ ```
164
+
165
+ ### Mouse coordinates are wrong
166
+
167
+ Disable mouse acceleration and set mouse sensetivity to default in your desktop environment settings.
168
+
169
+ ### Keys not working
170
+
171
+ Check that you're using the correct format: `'keyname:state'` (e.g., `'ctrl:1'`, not just `'ctrl'`).
172
+
173
+ ## License
174
+
175
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) for details.
176
+
177
+ [ydotool](https://github.com/ReimuNotMoe/ydotool) is licensed under [AGPL-3.0](https://github.com/ReimuNotMoe/ydotool/blob/master/LICENSE).
@@ -0,0 +1,44 @@
1
+ [build-system]
2
+ requires = ["uv_build>=0.12.0,<0.13.0"]
3
+ build-backend = "uv_build"
4
+
5
+ [project]
6
+ name = "ydotool"
7
+ version = "0.1.0"
8
+ description = "Python wrapper for ydotool"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ keywords = [
14
+ "ydotool",
15
+ "wayland",
16
+ "uinput",
17
+ "automation",
18
+ ]
19
+ classifiers = [
20
+ "Development Status :: 4 - Beta",
21
+ "Operating System :: POSIX :: Linux",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Programming Language :: Python :: 3.14",
26
+ "Programming Language :: Python :: 3.15",
27
+ ]
28
+ dependencies = []
29
+
30
+ [[project.authors]]
31
+ name = "astalunavortex"
32
+ email = "astalunavortex@gmail.com"
33
+
34
+ [project.optional-dependencies]
35
+ dev = ["pytest>=8"]
36
+
37
+ [project.urls]
38
+ Homepage = "https://github.com/astalunavortex/ydotool-py"
39
+ Repository = "https://github.com/astalunavortex/ydotool-py"
40
+ Issues = "https://github.com/astalunavortex/ydotool-py/issues"
41
+
42
+ [tool.pytest.ini_options]
43
+ testpaths = ["tests"]
44
+ addopts = ["-v"]
@@ -0,0 +1,38 @@
1
+ [build-system]
2
+ requires = ["uv_build>=0.12.0,<0.13.0"]
3
+ build-backend = "uv_build"
4
+
5
+ [project]
6
+ name = "ydotool"
7
+ version = "0.1.0"
8
+ description = "Python wrapper for ydotool"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [
14
+ { name = "astalunavortex", email = "astalunavortex@gmail.com" }
15
+ ]
16
+ keywords = ["ydotool", "wayland", "uinput", "automation"]
17
+ classifiers = [
18
+ "Development Status :: 4 - Beta",
19
+ "Operating System :: POSIX :: Linux",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Programming Language :: Python :: 3.14",
24
+ "Programming Language :: Python :: 3.15",
25
+ ]
26
+ dependencies = []
27
+
28
+ [project.optional-dependencies]
29
+ dev = ["pytest>=8"]
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/astalunavortex/ydotool-py"
33
+ Repository = "https://github.com/astalunavortex/ydotool-py"
34
+ Issues = "https://github.com/astalunavortex/ydotool-py/issues"
35
+
36
+ [tool.pytest.ini_options]
37
+ testpaths = ["tests"]
38
+ addopts = ["-v"]
@@ -0,0 +1,7 @@
1
+ from importlib.metadata import version
2
+
3
+ from .client import do_click, do_mousemove, do_type, do_key, do_wheel
4
+
5
+ __all__ = ["do_click", "do_mousemove", "do_type", "do_key", "do_wheel"]
6
+
7
+ __version__ = version("ydotool")
@@ -0,0 +1,123 @@
1
+ """
2
+ Python wrapper for ydotool CLI commands.
3
+
4
+ Requires ydotool 1.0.4 to be installed and ydotoold daemon running.
5
+
6
+ See:
7
+ - https://github.com/ReimuNotMoe/ydotool
8
+ - https://github.com/ReimuNotMoe/ydotool/releases/tag/v1.0.4
9
+ """
10
+
11
+ import subprocess
12
+
13
+ from .keycodes import get_keycodes
14
+ from .mousecodes import get_mousecode
15
+
16
+ def _run(*args: str) -> None:
17
+ subprocess.run(
18
+ ["ydotool", *args],
19
+ check=True
20
+ )
21
+
22
+ def do_click(button: str, action: str, repeat: int | None = None, next_delay: int | None = None) -> None:
23
+ """
24
+ Perform mouse click action.
25
+
26
+ Args:
27
+ button: Button name ('left', 'right', 'middle', 'side',
28
+ 'extra', 'forward', 'back', 'task').
29
+ action: Action name ('click', 'down', 'up').
30
+ repeat: Number of times to repeat the sequence (default: 1).
31
+ next_delay: Delay in milliseconds between events.
32
+ Full click takes 2x this value.
33
+
34
+ Raises:
35
+ ValueError: If button or action is invalid.
36
+ """
37
+
38
+ mousecode = get_mousecode(button, action)
39
+
40
+ if mousecode:
41
+ args = []
42
+
43
+ if repeat and repeat > 1:
44
+ args.append(f"--repeat={repeat}")
45
+ if next_delay and next_delay > 0:
46
+ args.append(f"--next-delay={next_delay}")
47
+
48
+ _run("click", *args, mousecode)
49
+ else:
50
+ raise ValueError("Invalid button or action")
51
+
52
+ def do_mousemove(x: int, y: int, absolute: bool = True) -> None:
53
+ """
54
+ Move mouse cursor to coordinates.
55
+
56
+ Args:
57
+ x: X coordinate in pixels.
58
+ y: Y coordinate in pixels.
59
+ absolute: If True, move to absolute position.
60
+ If False, move relative to current position.
61
+
62
+ Note:
63
+ For correct movement, disable mouse acceleration
64
+ and set mouse sensitivity to default
65
+ in your desktop environment settings.
66
+ """
67
+
68
+ args = []
69
+
70
+ if absolute:
71
+ args.append("--absolute")
72
+
73
+ _run("mousemove", *args, str(x), str(y))
74
+
75
+ def do_type(text: str, key_delay: int | None = None, key_hold: int | None = None) -> None:
76
+ """
77
+ Type text string.
78
+
79
+ Args:
80
+ text: Text to type. Handles special characters automatically.
81
+ key_delay: Delay in milliseconds between key events (default: 20).
82
+ key_hold: Hold time in milliseconds for each key (default: 20).
83
+ """
84
+
85
+ args = []
86
+
87
+ if key_delay and key_delay > 0:
88
+ args.append(f"--key-delay={key_delay}")
89
+ if key_hold and key_hold > 0:
90
+ args.append(f"--key-hold={key_hold}")
91
+
92
+ _run("type", *args, text)
93
+
94
+ def do_key(keys: list[str]) -> None:
95
+ """
96
+ Press keyboard keys in sequence.
97
+ Key names are defined in `keycodes.KEYCODES`
98
+
99
+ Args:
100
+ keys: List of 'key:state' strings where state is '1' (down)
101
+ or '0' (up). Order matters for combinations.
102
+
103
+ Raises:
104
+ ValueError: If any key is invalid or format is wrong.
105
+ """
106
+
107
+ keycodes = get_keycodes(keys)
108
+
109
+ if keycodes:
110
+ _run("key", *keycodes)
111
+ else:
112
+ raise ValueError("Invalid keys")
113
+
114
+ def do_wheel(vertical: int = 0, horizontal: int = 0) -> None:
115
+ """
116
+ Scroll mouse wheel.
117
+
118
+ Args:
119
+ vertical: Scroll ticks (positive=up, negative=down).
120
+ horizontal: Scroll ticks (positive=right, negative=left).
121
+ """
122
+
123
+ _run("mousemove", "--wheel", str(horizontal), str(vertical))
@@ -0,0 +1,219 @@
1
+ """
2
+ Keyboard key code mappings based on `/usr/include/linux/input-event-codes.h`
3
+
4
+ All codes are decimal integers corresponding to `KEY_*` constants
5
+ in the Linux kernel header.
6
+
7
+ Unusual keys have not been added here.
8
+ """
9
+
10
+ # exclamation = shift + 1 ( ! ) = ( shift ) + ( 1 )
11
+ # at = shift + 2 ( @ ) = ( shift ) + ( 2 )
12
+ # hash = shift + 3 ( # ) = ( shift ) + ( 3 )
13
+ # dollar = shift + 4 ( $ ) = ( shift ) + ( 4 )
14
+ # percent = shift + 5 ( % ) = ( shift ) + ( 5 )
15
+ # caret = shift + 6 ( ^ ) = ( shift ) + ( 6 )
16
+ # ampersand = shift + 7 ( & ) = ( shift ) + ( 7 )
17
+ # asterisk = shift + 8 ( * ) = ( shift ) + ( 8 )
18
+ # leftparenthesis = shift + 9 ( ( ) = ( shift ) + ( 9 )
19
+ # rightparenthesis = shift + 0 ( ) ) = ( shift ) + ( 0 )
20
+ # underscore = shift + minus ( _ ) = ( shift ) + ( - )
21
+ # plus = shift + equal ( + ) = ( shift ) + ( = )
22
+ # leftcurlybracket = shift + leftbracket ( { ) = ( shift ) + ( [ )
23
+ # rightcurlybracket = shift + rightbracket ( } ) = ( shift ) + ( ] )
24
+ # verticalbar = shift + backslash ( | ) = ( shift ) + ( \ )
25
+ # colon = shift + semicolon ( : ) = ( shift ) + ( ; )
26
+ # doublequote = shift + apostrophe ( " ) = ( shift ) + ( ' )
27
+ # less = shift + comma ( < ) = ( shift ) + ( , )
28
+ # greater = shift + dot ( > ) = ( shift ) + ( . )
29
+ # question = shift + slash ( ? ) = ( shift ) + ( / )
30
+ # tilde = shift + grave ( ~ ) = ( shift ) + ( ` )
31
+
32
+ KEYCODES = {
33
+ "0": "11",
34
+ "1": "2",
35
+ "2": "3",
36
+ "3": "4",
37
+ "4": "5",
38
+ "5": "6",
39
+ "6": "7",
40
+ "7": "8",
41
+ "8": "9",
42
+ "9": "10",
43
+
44
+ "a": "30",
45
+ "b": "48",
46
+ "c": "46",
47
+ "d": "32",
48
+ "e": "18",
49
+ "f": "33",
50
+ "g": "34",
51
+ "h": "35",
52
+ "i": "23",
53
+ "j": "36",
54
+ "k": "37",
55
+ "l": "38",
56
+ "m": "50",
57
+ "n": "49",
58
+ "o": "24",
59
+ "p": "25",
60
+ "q": "16",
61
+ "r": "19",
62
+ "s": "31",
63
+ "t": "20",
64
+ "u": "22",
65
+ "v": "47",
66
+ "w": "17",
67
+ "x": "45",
68
+ "y": "21",
69
+ "z": "44",
70
+
71
+ "esc": "1",
72
+ "backspace": "14",
73
+ "tab": "15",
74
+ "enter": "28",
75
+ "space": "57",
76
+ "capslock": "58",
77
+
78
+ "ctrl": "29",
79
+ "leftctrl": "29",
80
+ "rightctrl": "97",
81
+
82
+ "shift": "42",
83
+ "leftshift": "42",
84
+ "rightshift": "54",
85
+
86
+ "alt": "56",
87
+ "leftalt": "56",
88
+ "rightalt": "100",
89
+
90
+ "super": "125",
91
+ "leftsuper": "125",
92
+ "rightsuper": "126",
93
+
94
+ "menu": "127",
95
+ "compose": "127",
96
+
97
+ "f1": "59",
98
+ "f2": "60",
99
+ "f3": "61",
100
+ "f4": "62",
101
+ "f5": "63",
102
+ "f6": "64",
103
+ "f7": "65",
104
+ "f8": "66",
105
+ "f9": "67",
106
+ "f10": "68",
107
+ "f11": "87",
108
+ "f12": "88",
109
+
110
+ "up": "103",
111
+ "down": "108",
112
+ "left": "105",
113
+ "right": "106",
114
+
115
+ "home": "102",
116
+ "end": "107",
117
+ "pageup": "104",
118
+ "pagedown": "109",
119
+
120
+ "insert": "110",
121
+ "delete": "111",
122
+
123
+ "printscreen": "99",
124
+
125
+ "scrolllock": "70",
126
+
127
+ "pause": "119",
128
+
129
+ "numlock": "69",
130
+
131
+ "minus": "12",
132
+ "equal": "13",
133
+ "leftbracket": "26",
134
+ "rightbracket": "27",
135
+ "semicolon": "39",
136
+ "apostrophe": "40",
137
+ "grave": "41",
138
+ "backslash": "43",
139
+ "comma": "51",
140
+ "dot": "52",
141
+ "slash": "53",
142
+
143
+ "np0": "82",
144
+ "np1": "79",
145
+ "np2": "80",
146
+ "np3": "81",
147
+ "np4": "75",
148
+ "np5": "76",
149
+ "np6": "77",
150
+ "np7": "71",
151
+ "np8": "72",
152
+ "np9": "73",
153
+
154
+ "npasterisk": "55",
155
+ "npminus": "74",
156
+ "npplus": "78",
157
+ "npdot": "83",
158
+ "npenter": "96",
159
+ "npslash": "98",
160
+ "npequal": "117",
161
+
162
+ "mute": "113",
163
+ "volumedown": "114",
164
+ "volumeup": "115",
165
+ "micmute": "248",
166
+
167
+ "power": "116",
168
+ "sleep": "142",
169
+ "wakeup": "143",
170
+
171
+ "playpause": "164",
172
+ "nextsong": "163",
173
+ "previoussong": "165",
174
+
175
+ "back": "158",
176
+ "forward": "159",
177
+ "homepage": "172",
178
+ "search": "217",
179
+ "refresh": "173",
180
+
181
+ "stop": "166",
182
+
183
+ "brightnessup": "224",
184
+ "brightnessdown": "225",
185
+
186
+ "browser": "150",
187
+ "mail": "155",
188
+ "calc": "140",
189
+ }
190
+
191
+ def _split_key(key_str: str) -> tuple[str, str] | None:
192
+ if key_str.count(":") != 1:
193
+ return None
194
+ key, state = key_str.split(":")
195
+ if state in ("0", "1"):
196
+ return key, state
197
+ return None
198
+
199
+ def get_keycode(key: str) -> str | None:
200
+ return KEYCODES.get(key)
201
+
202
+ def get_keycodes(keys: list[str]) -> list[str] | None:
203
+ keycodes_list = []
204
+
205
+ for key_str in keys:
206
+ key_and_state = _split_key(key_str)
207
+
208
+ if not key_and_state:
209
+ return None
210
+
211
+ key, state = key_and_state
212
+ keycode = get_keycode(key)
213
+
214
+ if not keycode:
215
+ return None
216
+
217
+ keycodes_list.append(f"{keycode}:{state}")
218
+
219
+ return keycodes_list
@@ -0,0 +1,35 @@
1
+ """
2
+ Mouse button and action code mappings for ydotool.
3
+
4
+ ydotool 1.0.4 uses hexadecimal bit masks for mouse buttons:
5
+ - Lower bits (0x00-0x07): button identifier
6
+ - Upper bits (0x40=DOWN, 0x80=UP, 0xC0=CLICK): action
7
+
8
+ 0x can be omitted.
9
+ """
10
+
11
+ MOUSECODES = {
12
+ "left": "0",
13
+ "right": "1",
14
+ "middle": "2",
15
+ "side": "3",
16
+ "extra": "4",
17
+ "forward": "5",
18
+ "back": "6",
19
+ "task": "7",
20
+ }
21
+
22
+ ACTIONCODES = {
23
+ "click": "C",
24
+ "down": "4",
25
+ "up": "8",
26
+ }
27
+
28
+ def get_mousecode(button: str, action: str) -> str | None:
29
+ buttoncode = MOUSECODES.get(button)
30
+ actioncode = ACTIONCODES.get(action)
31
+
32
+ if buttoncode and actioncode:
33
+ return f"{actioncode}{buttoncode}"
34
+
35
+ return None