Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
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: 13 days 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 (almost 2 years ago)
- Default Branch: main
- Last Pushed: 2024-10-17T20:23:13.000Z (18 days ago)
- Last Synced: 2024-10-21T00:12:38.389Z (15 days ago)
- Topics: python-c-extension, sendinput, win32-api
- Language: Python
- Homepage:
- Size: 129 KB
- Stars: 8
- Watchers: 3
- Forks: 2
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE.txt
Awesome Lists containing this project
README
# KeyWin
[![python](https://img.shields.io/badge/python-3.9%20|%203.10%20|%203.11%20|%203.12%20|%203.13-blue)](https://www.python.org/)
[![linting: pylint](https://img.shields.io/badge/linting-pylint-yellowgreen)](https://github.com/PyCQA/pylint)
[![main.yml](https://github.com/winstxnhdw/KeyWin/actions/workflows/main.yml/badge.svg)](https://github.com/winstxnhdw/KeyWin/actions/workflows/main.yml)
[![formatter.yml](https://github.com/winstxnhdw/KeyWin/actions/workflows/formatter.yml/badge.svg)](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 keyboardkeyboard.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 MouseEventleft_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 profilefrom keywin import keyboard, KeyCode
def keywin():
keyboard.press(KeyCode.VK_SPACE)
if __name__ == '__main__':
profile.run('keywin()')
``````txt
6 function calls in 0.000 secondsncalls 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 profilefrom 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 secondsncalls 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 profilefrom 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 secondsncalls 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 profilefrom 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 secondsncalls 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
```