Flexible global keyboard & mouse hotkeys for Windows
Project description
keybinds
Flexible global keyboard & mouse hotkeys for Windows.
keybinds is a Python library for global keyboard and mouse binds on Windows, with support for hotkeys, logical key binds, text matching, and text abbreviations.
It supports:
- global keyboard binds: single keys, chords (
ctrl+e), and sequences (g,k,i) - global mouse binds:
left,right,middle,x1,x2 - rich triggers: press, release, click, hold, repeat, double tap, and sequence
- suppression and injected-input policies
- sync and async callbacks
- decorator helpers and a simple API
- experimental logical binds, typed-text matching, and text abbreviations
- unified unbind helpers for bind handles, decorated functions, and bind collections
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
Quick Start
from keybinds import Hook
hook = Hook()
hook.bind("ctrl+e", lambda: print("Inventory"))
hook.bind_mouse("left", lambda: print("Fire"))
hook.join()
Decorator Style
Stable decorator helpers:
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 the default hook automatically.
Experimental logical/text decorators
import keybinds
from keybinds.decorators import bind_logical, bind_text, bind_abbreviation
# Experimental API: may change in future releases.
@bind_logical("ctrl+A")
def logical_inventory():
print("Logical inventory")
@bind_text("hello")
def saw_hello():
print("Hello typed")
@bind_abbreviation("brb", "be right back")
def expanded_brb():
print("Expanded brb")
keybinds.join()
For non-decorator usage, keybinds.add_abbreviation(...) is also available.
When to use what
bind(...)— regular keyboard shortcuts based on key expressionsbind_mouse(...)— mouse button bindsbind_logical(...)— experimental layout-aware logical key bindsbind_text(...)— experimental typed-text matchingadd_abbreviation(...)/bind_abbreviation(...)— experimental text expansion
For normal shortcuts, prefer bind(...). For typed text, prefer bind_text(...) instead of text-like expressions passed to bind_logical(...).
Unbinding
Bind methods return bind handles directly:
b = hook.bind("ctrl+e", callback)
hook.unbind(b)
Decorators attach created bind handles to the function:
func.bindsalways contains a list of all created bind objectsfunc.bindis kept as a compatibility alias:- single bind →
func.bindis that bind object - multiple binds →
func.bindis a list of bind objects
- single bind →
You can unbind by handle, by function, or by a bind collection:
hook.unbind(my_callback)
hook.unbind(my_callback.binds)
hook.unbind(my_callback.bind)
keybinds.unbind(my_callback)
Top-level helpers use the default hook. If you use multiple hooks, prefer hook.unbind(...) on the specific hook.
Bind state and waiting
All bind objects expose two small runtime helpers:
if bind.is_pressed():
print("bind is currently active")
bind.wait() # wait until the bind fires
bind.wait(0.5) # wait up to 0.5s, returns True/False
is_pressed() checks whether the bind is currently pressed.
wait(timeout=None) blocks until the bind fires and returns True, or returns False on timeout.
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
def not_injected(event, state):
return not event.injected
hook.bind("f1", lambda: print("Checked"), 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))
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
Documentation
- Advanced Usage.md — advanced triggers, constraints, suppression, callbacks, and API details
- Diagnostics.md — troubleshooting and runtime introspection
- Logical Binds and Abbreviations.md — experimental logical binds, typed text, and abbreviations
- Developer Notes - Logical.md — developer-oriented notes on logical internals
Notes
Logical binds, typed-text matching, and abbreviations are currently experimental. The API and behavior may still change in future releases.
Suppression limitations
On Windows, suppression depends on low-level hook order. See Advanced Usage — Hook chain limitations and reinstall_hooks().
License
MIT License
Third-party Components
This project bundles a modified copy of winput (originally by Zuzu_Typ, zlib/libpng license), extended so keybinds can detect injected keyboard and mouse hook events.
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.3.2.tar.gz.
File metadata
- Download URL: keybinds-1.3.2.tar.gz
- Upload date:
- Size: 80.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
203c89e60ae5b9506336360ab42b0250df5645e1cd224151a8cd4f906c631625
|
|
| MD5 |
b7642676ac2ab4a7b2d6a3acd42b809e
|
|
| BLAKE2b-256 |
bc586f1b3e5ee59430bd73e1412dfa00fcf29c72bd9e695d01a257e918b9da85
|
File details
Details for the file keybinds-1.3.2-py3-none-any.whl.
File metadata
- Download URL: keybinds-1.3.2-py3-none-any.whl
- Upload date:
- Size: 81.0 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 |
40493dcb31632234af8d024e1b4118ce92828a5da27dabe0b1c0b51a3d1cc144
|
|
| MD5 |
8dc70eea6c89e22c662424590b35a7d7
|
|
| BLAKE2b-256 |
a84fc3c9008386bd9cd7f808a6330a2faf5937b967c1d90359d34994003edcdf
|