https://github.com/winstxnhdw/keywin
A fast Python wrapper for Win32's SendInput function using C extensions.
https://github.com/winstxnhdw/keywin
python-c-extension sendinput win32-api
Last synced: about 1 year ago
JSON representation
A fast Python wrapper for Win32's SendInput function using C extensions.
- Host: GitHub
- URL: https://github.com/winstxnhdw/keywin
- Owner: winstxnhdw
- License: mit
- Created: 2022-11-23T11:45:58.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2025-05-04T21:37:42.000Z (about 1 year ago)
- Last Synced: 2025-05-04T22:33:08.511Z (about 1 year ago)
- Topics: python-c-extension, sendinput, win32-api
- Language: Python
- Homepage:
- Size: 262 KB
- Stars: 11
- Watchers: 4
- Forks: 3
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- License: LICENSE.txt
Awesome Lists containing this project
README
# KeyWin
[](https://www.python.org/)
[](https://github.com/PyCQA/pylint)
[](https://github.com/winstxnhdw/KeyWin/actions/workflows/main.yml)
[](https://github.com/winstxnhdw/KeyWin/actions/workflows/formatter.yml)
`KeyWin` is a fast Python wrapper for Win32's SendInput function using C extensions. It is designed to be used in applications that require low-latency inputs. All functions drop the Global Interpreter Lock (GIL).
## Installation
```bash
pip install git+https://github.com/winstxnhdw/KeyWin
```
## Usage
### Keyboard
`KeyWin` provides a low-level API for keyboard inputs based on Microsoft's [Virtual-Key Codes](https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes).
#### Pre-mapped Key Codes
`KeyWin` provides a set of pre-mapped key codes for common keys. These key codes are defined [here](keywin/keyboard/codes/__init__.py).
```python
from keywin import KeyCode, keyboard
# Enter
keyboard.press(KeyCode.VK_RETURN)
# Win + D
keyboard.press(KeyCode.VK_LWIN, KeyCode.VK_D)
# Hold Shift + A
keyboard.hold(KeyCode.VK_SHIFT, KeyCode.VK_A)
# Release Shift + A
keyboard.release(KeyCode.VK_SHIFT, KeyCode.VK_A)
# Hold unicode character
keyboard.hold_unicode('!')
# Release unicode character
keyboard.release_unicode('!')
```
#### Manual Key Codes
If you are unable to find the key code you need, you can enter the hex key values manually.
```python
from keywin import keyboard
# Enter
keyboard.press(0x0D)
# Win + D
keyboard.press(0x5B, 0x44)
```
#### Unicode Inputs
You may also send long unicode inputs. Certain unicode, such as `\n`, cannot be converted into a keystroke and will be ignored by Windows. It is also more performant to use this function rather than `press()` for long unicode inputs.
```python
from keywin import keyboard
keyboard.write('Hello, world!')
```
### Mouse
Similar to the keyboard, `KeyWin` provides a low-level API for mouse inputs based on Microsoft's [MOUSEINPUT](https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-mouseinput) structure.
#### Helpers
`KeyWin` provides a set of helper functions for common mouse inputs.
```python
from keywin import mouse
# Move mouse 100 down and 100 right from current position
mouse.move_relative(100, 100)
# Move mouse to (100, 100)
mouse.move(100, 100)
# Left click
mouse.left_click()
# Right click
mouse.right_click()
# Middle click
mouse.middle_click()
# xbutton1 click
mouse.xbutton1_click()
# xbutton2 click
mouse.xbutton2_click()
# Press left button
mouse.left_press()
# Press right button
mouse.right_press()
# Press middle button
mouse.middle_press()
# Press xbutton1
mouse.xbutton1_press()
# Press xbutton2
mouse.xbutton2_press()
# Release left button
mouse.left_release()
# Release right button
mouse.right_release()
# Release middle button
mouse.middle_release()
# Release xbutton1
mouse.xbutton1_release()
# Release xbutton2
mouse.xbutton2_release()
# Scroll up
mouse.scroll(10)
# Scroll down
mouse.scroll(-10)
# Scroll left
mouse.scroll_horizontal(10)
# Scroll right
mouse.scroll_horizontal(-10)
```
#### Low-level Access
The `mouse` helpers have _some_ overhead due to indirection. You can avoid this by directly accessing the low-level API. `MouseEvent` is a `NamedTuple` which can be passed to the low-level wrapper function `send_events()`.
```python
from keywin.mouse import MouseCode, send_events
from keywin.mouse.helpers import MouseEvent
left_click_event = MouseEvent(MouseCode.MOUSE_LEFT_CLICK, 100, 100)
right_click_event = MouseEvent(MouseCode.MOUSE_RIGHT_CLICK, 100, 100)
send_events(left_click_event, right_click_event)
```
## Benchmarks
`KeyWin` is designed to be used in applications that require low-latency inputs. The following benchmarks were performed against boppreh's [keyboard](https://github.com/boppreh/keyboard) and [mouse](https://github.com/boppreh/mouse) libraries. In all cases, `KeyWin` is magnitudes faster than the other libraries.
### Keyboard Benchmark
KeyWin
```python
import cProfile as profile
from keywin import keyboard, KeyCode
def keywin():
keyboard.press(KeyCode.VK_SPACE)
if __name__ == '__main__':
profile.run('keywin()')
```
```txt
6 function calls in 0.000 seconds
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.000 0.000 :1()
1 0.000 0.000 0.000 0.000 keyboard.py:6(press)
1 0.000 0.000 0.000 0.000 test.py:6(keywin)
1 0.000 0.000 0.000 0.000 {built-in method builtins.exec}
1 0.000 0.000 0.000 0.000 {built-in method keywin.send_input.press_keyboard}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
```
```python
import cProfile as profile
from keyboard import press_and_release
def keyboard():
press_and_release("space")
if __name__ == '__main__':
profile.run('keyboard()')
```
```txt
172510 function calls (172499 primitive calls) in 0.177 seconds
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.177 0.177 :1()
1 0.000 0.000 0.000 0.000 __init__.py:102()
2 0.000 0.000 0.000 0.000 __init__.py:103()
2 0.000 0.000 0.000 0.000 __init__.py:106()
1 0.000 0.000 0.175 0.175 __init__.py:298(key_to_scan_codes)
121 0.000 0.000 0.175 0.001 __init__.py:317()
1 0.000 0.000 0.175 0.175 __init__.py:328(parse_hotkey)
2 0.000 0.000 0.175 0.088 __init__.py:358()
1 0.000 0.000 0.177 0.177 __init__.py:361(send)
2 0.000 0.000 0.000 0.000 __init__.py:384(__getattr__)
2 0.000 0.000 0.000 0.000 __init__.py:391(__getitem__)
21209 0.015 0.000 0.022 0.000 _canonical_names.py:1233(normalize_name)
18796 0.110 0.000 0.111 0.000 _winkeyboard.py:351(get_event_names)
1 0.028 0.028 0.175 0.175 _winkeyboard.py:383(_setup_name_tables)
1 0.000 0.000 0.000 0.000 _winkeyboard.py:393()
1 0.000 0.000 0.000 0.000 _winkeyboard.py:394()
4400 0.003 0.000 0.004 0.000 _winkeyboard.py:411()
5 0.000 0.000 0.000 0.000 _winkeyboard.py:429()
12488 0.002 0.000 0.002 0.000 _winkeyboard.py:431(order_key)
121 0.000 0.000 0.175 0.001 _winkeyboard.py:567(map_name)
2 0.001 0.001 0.002 0.001 _winkeyboard.py:577(_send_event)
1 0.000 0.000 0.000 0.000 _winkeyboard.py:590(press)
1 0.000 0.000 0.001 0.001 _winkeyboard.py:593(release)
4 0.000 0.000 0.000 0.000 enum.py:359(__call__)
4 0.000 0.000 0.000 0.000 enum.py:678(__new__)
2 0.000 0.000 0.000 0.000 enum.py:986(__and__)
2 0.000 0.000 0.000 0.000 re.py:222(split)
2 0.000 0.000 0.000 0.000 re.py:288(_compile)
3 0.000 0.000 0.000 0.000 sre_compile.py:265(_compile_charset)
3 0.000 0.000 0.000 0.000 sre_compile.py:292(_optimize_charset)
3 0.000 0.000 0.000 0.000 sre_compile.py:447(_simple)
1 0.000 0.000 0.000 0.000 sre_compile.py:456(_generate_overlap_table)
3 0.000 0.000 0.000 0.000 sre_compile.py:477(_get_iscased)
2 0.000 0.000 0.000 0.000 sre_compile.py:485(_get_literal_prefix)
1 0.000 0.000 0.000 0.000 sre_compile.py:516(_get_charset_prefix)
2 0.000 0.000 0.000 0.000 sre_compile.py:560(_compile_info)
4 0.000 0.000 0.000 0.000 sre_compile.py:619(isstring)
2 0.000 0.000 0.000 0.000 sre_compile.py:622(_code)
2 0.000 0.000 0.000 0.000 sre_compile.py:783(compile)
5/2 0.000 0.000 0.000 0.000 sre_compile.py:87(_compile)
5 0.000 0.000 0.000 0.000 sre_parse.py:112(__init__)
11 0.000 0.000 0.000 0.000 sre_parse.py:161(__len__)
26 0.000 0.000 0.000 0.000 sre_parse.py:165(__getitem__)
3 0.000 0.000 0.000 0.000 sre_parse.py:169(__setitem__)
5 0.000 0.000 0.000 0.000 sre_parse.py:173(append)
5/2 0.000 0.000 0.000 0.000 sre_parse.py:175(getwidth)
2 0.000 0.000 0.000 0.000 sre_parse.py:225(__init__)
10 0.000 0.000 0.000 0.000 sre_parse.py:234(__next)
5 0.000 0.000 0.000 0.000 sre_parse.py:250(match)
8 0.000 0.000 0.000 0.000 sre_parse.py:255(get)
5 0.000 0.000 0.000 0.000 sre_parse.py:287(tell)
4 0.000 0.000 0.000 0.000 sre_parse.py:356(_escape)
2 0.000 0.000 0.000 0.000 sre_parse.py:436(_parse_sub)
2 0.000 0.000 0.000 0.000 sre_parse.py:494(_parse)
2 0.000 0.000 0.000 0.000 sre_parse.py:76(__init__)
4 0.000 0.000 0.000 0.000 sre_parse.py:82(groups)
2 0.000 0.000 0.000 0.000 sre_parse.py:928(fix_flags)
2 0.000 0.000 0.000 0.000 sre_parse.py:944(parse)
1 0.000 0.000 0.177 0.177 test.py:6(keyboard)
2 0.000 0.000 0.000 0.000 {built-in method _sre.compile}
2016 0.000 0.000 0.000 0.000 {built-in method builtins.chr}
1 0.000 0.000 0.177 0.177 {built-in method builtins.exec}
21256 0.002 0.000 0.002 0.000 {built-in method builtins.isinstance}
30886 0.003 0.000 0.003 0.000 {built-in method builtins.len}
12 0.000 0.000 0.000 0.000 {built-in method builtins.min}
2 0.000 0.000 0.000 0.000 {built-in method builtins.ord}
2 0.000 0.000 0.000 0.000 {built-in method builtins.setattr}
205 0.004 0.000 0.006 0.000 {built-in method builtins.sorted}
1 0.000 0.000 0.000 0.000 {method '__exit__' of '_thread.lock' objects}
21284 0.002 0.000 0.002 0.000 {method 'append' of 'list' objects}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
2 0.000 0.000 0.000 0.000 {method 'extend' of 'list' objects}
3 0.000 0.000 0.000 0.000 {method 'find' of 'bytearray' objects}
21217 0.003 0.000 0.003 0.000 {method 'get' of 'dict' objects}
3 0.000 0.000 0.000 0.000 {method 'items' of 'dict' objects}
18301 0.002 0.000 0.002 0.000 {method 'lower' of 'str' objects}
2 0.000 0.000 0.000 0.000 {method 'split' of 're.Pattern' objects}
2 0.000 0.000 0.000 0.000 {method 'startswith' of 'str' objects}
1 0.000 0.000 0.000 0.000 {method 'update' of 'dict' objects}
```
### Mouse Benchmark
KeyWin
```python
import cProfile as profile
from keywin import mouse, MouseCode
def keywin():
desired_position = (100, 100)
# Left + Right click at (100, 100)
mouse.send_events(
[*desired_position, 0, MouseCode.MOUSE_MOVE_ABSOLUTE | MouseCode.MOUSE_LEFT_CLICK],
[*desired_position, 0, MouseCode.MOUSE_MOVE_ABSOLUTE | MouseCode.MOUSE_RIGHT_CLICK]
)
if __name__ == '__main__':
profile.run('keywin()')
```
```txt
6 function calls in 0.004 seconds
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.004 0.004 :1()
1 0.000 0.000 0.004 0.004 mouse.py:12(send_events)
1 0.000 0.000 0.004 0.004 test.py:9(keywin)
1 0.000 0.000 0.004 0.004 {built-in method builtins.exec}
1 0.004 0.004 0.004 0.004 {built-in method keywin.send_input.send_mouse_event}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
```
```python
import cProfile as profile
from mouse import click, move, right_click
def mouse():
move(100, 100)
click()
right_click()
if __name__ == '__main__':
profile.run('mouse()')
```
```txt
35 function calls in 0.008 seconds
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.008 0.008 :1()
1 0.000 0.000 0.001 0.001 __init__.py:101(right_click)
1 0.000 0.000 0.000 0.000 __init__.py:109(move)
1 0.000 0.000 0.000 0.000 __init__.py:199(get_position)
3 0.000 0.000 0.000 0.000 __init__.py:391(__getitem__)
2 0.000 0.000 0.008 0.004 __init__.py:91(click)
4 0.000 0.000 0.000 0.000 _winmouse.py:179(_translate_button)
2 0.004 0.002 0.004 0.002 _winmouse.py:185(press)
2 0.003 0.002 0.003 0.002 _winmouse.py:190(release)
1 0.000 0.000 0.000 0.000 _winmouse.py:199(move_to)
1 0.000 0.000 0.000 0.000 _winmouse.py:208(get_position)
1 0.000 0.000 0.008 0.008 test.py:6(mouse)
1 0.000 0.000 0.000 0.000 {built-in method _ctypes.byref}
1 0.000 0.000 0.008 0.008 {built-in method builtins.exec}
3 0.000 0.000 0.000 0.000 {built-in method builtins.isinstance}
3 0.000 0.000 0.000 0.000 {built-in method builtins.setattr}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
3 0.000 0.000 0.000 0.000 {method 'startswith' of 'str' objects}
```
## Development
You can build `KeyWin` with the following.
```bash
pdm install
```