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).
pip install git+https://github.com/winstxnhdw/KeyWin
KeyWin
provides a low-level API for keyboard inputs based on Microsoft's Virtual-Key Codes.
KeyWin
provides a set of pre-mapped key codes for common keys. These key codes are defined here.
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('!')
If you are unable to find the key code you need, you can enter the hex key values manually.
from keywin import keyboard
# Enter
keyboard.press(0x0D)
# Win + D
keyboard.press(0x5B, 0x44)
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.
from keywin import keyboard
keyboard.write('Hello, world!')
Similar to the keyboard, KeyWin
provides a low-level API for mouse inputs based on Microsoft's MOUSEINPUT structure.
KeyWin
provides a set of helper functions for common mouse inputs.
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)
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()
.
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)
KeyWin
is designed to be used in applications that require low-latency inputs. The following benchmarks were performed against boppreh's keyboard and mouse libraries. In all cases, KeyWin
is magnitudes faster than the other libraries.
KeyWin
import cProfile as profile
from keywin import keyboard, KeyCode
def keywin():
keyboard.press(KeyCode.VK_SPACE)
if __name__ == '__main__':
profile.run('keywin()')
6 function calls in 0.000 seconds
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.000 0.000 <string>:1(<module>)
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}
boppreh/keyboard
import cProfile as profile
from keyboard import press_and_release
def keyboard():
press_and_release("space")
if __name__ == '__main__':
profile.run('keyboard()')
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 <string>:1(<module>)
1 0.000 0.000 0.000 0.000 __init__.py:102(<lambda>)
2 0.000 0.000 0.000 0.000 __init__.py:103(<lambda>)
2 0.000 0.000 0.000 0.000 __init__.py:106(<lambda>)
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(<genexpr>)
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(<genexpr>)
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(<listcomp>)
1 0.000 0.000 0.000 0.000 _winkeyboard.py:394(<listcomp>)
4400 0.003 0.000 0.004 0.000 _winkeyboard.py:411(<listcomp>)
5 0.000 0.000 0.000 0.000 _winkeyboard.py:429(<lambda>)
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}
KeyWin
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()')
6 function calls in 0.004 seconds
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.004 0.004 <string>:1(<module>)
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}
boppreh/mouse
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()')
35 function calls in 0.008 seconds
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.008 0.008 <string>:1(<module>)
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}
You can build KeyWin
with the following.
pdm install