Skip to content

A fast Python wrapper for Win32's SendInput function using C extensions.

License

Notifications You must be signed in to change notification settings

winstxnhdw/KeyWin

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

KeyWin

python linting: pylint main.yml 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

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.

Pre-mapped 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('!')

Manual Key Codes

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)

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.

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 structure.

Helpers

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)

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().

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 and mouse libraries. In all cases, KeyWin is magnitudes faster than the other libraries.

Keyboard Benchmark

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}

Mouse Benchmark

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}

Development

You can build KeyWin with the following.

pdm install

About

A fast Python wrapper for Win32's SendInput function using C extensions.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Contributors 4

  •  
  •  
  •  
  •