Flexible and high-performance global keyboard & mouse hotkeys for Windows
Project description
keybinds
Flexible and high-performance global keyboard & mouse hotkeys for Windows.
keybinds is a Python library for building fully customizable global keybinds and mouse binds using low-level Windows hooks.
It supports:
- keyboard single keys, chords (
ctrl+e), sequences (g,k,i) - mouse button binds (
left,right,middle,x1,x2) - rich triggers (press / release / click / hold / repeat / double tap / sequence)
- suppression and injected-input policies
- sync + async callbacks
- decorators and config-driven API
Powered by a bundled/modified winput for reliable input suppression and precise control.
Installation
pip install keybinds
Requirements
- Windows
- Python 3.7+
typing_extensionson Python 3.7
Comparison (Windows hotkeys)
| Feature | keybinds | keyboard | pynput | AutoHotkey |
|---|---|---|---|---|
| Cross-platform | ❌ (Windows only) | ⚠️ Windows/Linux (+ experimental macOS) | ✅ | ❌ (Windows only) |
| Python-native library | ✅ | ✅ | ✅ | ❌ (separate DSL/tool) |
| Global keyboard hooks | ✅ | ✅ | ✅ | ✅ |
| Chords / combos | ✅ | ✅ | ✅* | ✅ |
| Sequences | ✅ | ✅ | ❌ | ✅ |
| Window-scoped/context hotkeys | ✅ (hwnd) |
❌ | ❌ | ✅ |
Async callbacks (asyncio) |
✅ | ❌ | ❌ | ❌ |
| Built-in trigger model (hold/repeat/double-tap/sequence/chord lifecycle) | ✅ | ⚠️ partial | ❌ | ⚠️ script-level patterns |
| Fine-grained constraints (strict chords, order policy, injected policy) | ✅ | ❌ | ❌ | ⚠️ possible, but not as a Python API model |
* pynput provides HotKey / GlobalHotKeys for combinations, but not built-in sequence-style hotkeys.
Quick Start
from keybinds.bind import Hook
hook = Hook()
hook.bind("ctrl+e", lambda: print("Inventory"))
hook.bind_mouse("left", lambda: print("Fire"))
hook.join()
Decorator Style (no manual Hook required)
import keybinds
from keybinds.decorators import bind_key, bind_mouse
@bind_key("ctrl+e")
def inventory():
print("Inventory")
@bind_mouse("left")
def fire():
print("Bang")
keybinds.join()
Decorators use a default hook automatically.
Keyboard Basics
Press (default)
hook.bind("ctrl+e", lambda: print("Pressed"))
Release
from keybinds.types import BindConfig, Trigger
hook.bind(
"ctrl+t",
lambda: print("Released"),
config=BindConfig(trigger=Trigger.ON_RELEASE),
)
Hold
from keybinds.types import BindConfig, Trigger, Timing
hook.bind(
"h",
lambda: print("Held"),
config=BindConfig(
trigger=Trigger.ON_HOLD,
timing=Timing(hold_ms=400),
),
)
Repeat (auto-fire while held)
from keybinds.types import BindConfig, Trigger, Timing
hook.bind(
"space",
lambda: print("Tick"),
config=BindConfig(
trigger=Trigger.ON_REPEAT,
timing=Timing(hold_ms=200, repeat_interval_ms=80),
),
)
Double tap
from keybinds.types import BindConfig, Trigger
hook.bind(
"g",
lambda: print("Dash"),
config=BindConfig(trigger=Trigger.ON_DOUBLE_TAP),
)
Sequence
from keybinds.types import BindConfig, Trigger
hook.bind(
"g,k,i",
lambda: print("Secret combo"),
config=BindConfig(trigger=Trigger.ON_SEQUENCE),
)
Mouse Basics
from keybinds.types import MouseBindConfig, Trigger
hook.bind_mouse(
"middle",
lambda: print("Middle pressed"),
config=MouseBindConfig(trigger=Trigger.ON_PRESS),
)
Mouse buttons:
leftrightmiddlex1x2
Suppression (block input from apps)
from keybinds.types import BindConfig, SuppressPolicy
hook.bind(
"ctrl+r",
lambda: print("Reload"),
config=BindConfig(suppress=SuppressPolicy.WHEN_MATCHED),
)
Policies:
NEVERWHEN_MATCHEDWHILE_ACTIVEWHILE_EVALUATINGALWAYS
Injected (synthetic) input policy
Control how injected input (macros, SendInput, automation tools) is handled:
from keybinds.types import BindConfig, InjectedPolicy
hook.bind(
"f1",
lambda: print("Only physical"),
config=BindConfig(injected=InjectedPolicy.IGNORE),
)
Policies:
ALLOWIGNOREONLY
Strict Chords
from keybinds.types import BindConfig, Constraints, ChordPolicy
hook.bind(
"ctrl+shift+u",
lambda: print("Strict"),
config=BindConfig(
constraints=Constraints(chord_policy=ChordPolicy.STRICT)
),
)
Checks / Predicates
from keybinds.types import BindConfig, Checks
def not_injected(event, state):
return not event.injected
hook.bind(
"f1",
lambda: print("Checked"),
config=BindConfig(checks=Checks([not_injected])),
)
You can also pass:
- a single callable
- a list/tuple of callables
Checks(...)
Async Callbacks
Callbacks may be async def. If a callback returns an awaitable, keybinds schedules it on an asyncio loop.
import asyncio
from keybinds import Hook
from keybinds.decorators import bind_key
hook = Hook()
@bind_key("f1", hook=hook)
async def ping():
await asyncio.sleep(0.1)
print("async ok")
hook.join()
If your app already runs its own event loop, pass it via Hook(asyncio_loop=...) and avoid calling blocking join() on that thread.
Presets (shortcut configs)
from keybinds.presets import press, release, click, hold, repeat, double_tap, sequence
hook.bind("ctrl+e", lambda: print("press"), config=press())
hook.bind("ctrl+e", lambda: print("release"), config=release())
hook.bind("k", lambda: print("tap"), config=click(220))
hook.bind("k", lambda: print("hold"), config=hold(450))
hook.bind("space", lambda: print("tick"), config=repeat(delay_ms=200, interval_ms=80))
hook.bind("d", lambda: print("dash"), config=double_tap(window_ms=250))
hook.bind("g,k,i", lambda: print("combo"), config=sequence(timeout_ms=600))
More presets, profiles and composition patterns are in Advanced Usage.md.
If you are troubleshooting why a bind fired or did not fire, see Diagnostics.md.
Simple API
For common cases, use the lightweight decorator wrapper:
from keybinds.simple import hotkey, mouse, run
@hotkey("ctrl+e")
def inventory():
print("Inventory")
@hotkey("space", repeat=80, delay=200)
def autofire():
print("Bang")
@hotkey("f", hold=400)
def charge():
print("Charged")
@mouse("left")
def on_left():
print("Mouse pressed")
run()
Supports common patterns with simple flags:
release=Truehold=400repeat=80(optionallydelay=200)sequence=True(optionallytimeout=600)double_tap=Truesuppress=True
Performance
Measured using examples/benchmark.py:
- p50 ≈ 0.29 ms
- p99 ≈ 0.48 ms
- max < 0.8 ms (rare spikes up to 3–5 ms)
Latency includes hook dispatch and callback scheduling (no heavy user code).
License
MIT License
Third-party Components
This project bundles a modified copy of winput (originally by Zuzu_Typ, zlib/libpng license).
The original license text is included in keybinds/winput/LICENSE.
Contributing
PRs and issues are welcome:
- bug fixes
- performance improvements
- new triggers
- documentation
- examples
⭐ If you like it
Star the repo — it helps a lot.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file keybinds-1.2.0.tar.gz.
File metadata
- Download URL: keybinds-1.2.0.tar.gz
- Upload date:
- Size: 52.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eef8632a5120ad37e1a3cbcefd838bc84bfa9ffa31558074674e3aeea13ef46d
|
|
| MD5 |
bcf065f8ae1a885987817350485f91df
|
|
| BLAKE2b-256 |
0a512cf64de283323246ad72e0b434479ff017fa3ff8132bd11ee96f30cc2754
|
File details
Details for the file keybinds-1.2.0-py3-none-any.whl.
File metadata
- Download URL: keybinds-1.2.0-py3-none-any.whl
- Upload date:
- Size: 58.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
95aae0e0f6271c64f9ba00b02b759c3a6e002d9529709966a4231968eaaff1c8
|
|
| MD5 |
bbbe9afedd645344e5be15dfe9ab2b0c
|
|
| BLAKE2b-256 |
346d29215031cfe5093d8d420e3a628aed8d63ea96abbf1d97bbd5a9bd14adfa
|