Skip to main content

keybinds

Flexible global keyboard & mouse hotkeys for Windows.

Python Platform License Status PyPI

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_extensions on 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 expressions
  • bind_mouse(...) — mouse button binds
  • bind_logical(...) — experimental layout-aware logical key binds
  • bind_text(...) — experimental typed-text matching
  • add_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.binds always contains a list of all created bind objects
  • func.bind is kept as a compatibility alias:
    • single bind → func.bind is that bind object
    • multiple binds → func.bind is a list of bind objects

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 small runtime helpers:

if bind.is_pressed():
    print("full chord is currently held")

if bind.any_pressed():
    print("at least one key/button of this bind is held")

print(bind.pressed_keys())  # held VKs for this bind, or [button] for mouse

bind.wait()          # wait until the bind fires
bind.wait(0.5)       # wait up to 0.5s, returns True/False

is_pressed() is true only when the current chord step is fully held (same ChordPolicy / InjectedPolicy rules as matching). any_pressed() is true if any key or button belonging to that step is held. pressed_keys() returns those held keys: sorted VK codes for keyboard/logical binds, or [button] / [] for mouse. Text-sequence and abbreviation binds: any_pressed() follows buffer/pending match; pressed_keys() returns []. 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:

  • left
  • right
  • middle
  • x1
  • x2

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:

  • NEVER
  • WHEN_MATCHED
  • WHILE_ACTIVE
  • WHILE_EVALUATING
  • ALWAYS

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:

  • ALLOW
  • IGNORE
  • ONLY

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=True
  • hold=400
  • repeat=80 (optionally delay=200)
  • sequence=True (optionally timeout=600)
  • double_tap=True
  • suppress=True

Documentation


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.

Metadata

Release files for keybinds 1.3.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for keybinds 1.3.3
File Size Uploaded
keybinds-1.3.3.tar.gz 83.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for keybinds 1.3.3
File Interpreter ABI Platform
keybinds-1.3.3-py3-none-any.whl Python 3 none any Details

Total release size: 166.4 kB

Release files / keybinds-1.3.3.tar.gz

Download URL keybinds-1.3.3.tar.gz
Size 83.0 kB
Tags Source
SHA-256 checksum
How to use checksums
190941ca1a2604b65bfa0029d3ced81f124f2335f6f8913a8bd62600ab1b1d61
BLAKE2b-256 checksum
How to use checksums
36ad035ad0c73e559b1539340335cfb721742c0e2d75a81f91ad0baf644f62ac
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.10.0

Release files / keybinds-1.3.3-py3-none-any.whl

Download URL keybinds-1.3.3-py3-none-any.whl
Size 83.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7da50f854645fc331b84ba77cb8ecc4fbf79b51801f63de671fc8e9737b242de
BLAKE2b-256 checksum
How to use checksums
86e58b7b56aa1bb53512309c8a5f67b89c8609f54784cdd966e7fa05fd5d6b7d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.10.0

Release history Release notifications | RSS feed

1.3.6

2 release files

1.3.5

2 release files

1.3.4

2 release files

This release

1.3.3 This release

2 release files

1.3.2

2 release files

1.3.1

2 release files

1.3.0

2 release files

1.2.1

2 release files

1.2.0

2 release files

1.1.2

2 release files

1.1.1

2 release files

1.1.0

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

0.0.4

2 release files

0.0.3

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page