Skip to main content

Flexible and high-performance global keyboard & mouse hotkeys for Windows

Project description

keybinds

Flexible and high-performance global keyboard & mouse hotkeys for Windows.

Python Platform License Status PyPI

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_extensions on Python 3.7

When to use keybinds

keybinds is for Windows applications that need more than basic hotkeys.

Use it when your app needs:

  • global keyboard and mouse binds
  • strict chords and key sequences
  • advanced trigger behavior such as hold, repeat, and double tap
  • suppression control and injected-input filtering
  • a binding model that scales from simple shortcuts to more complex setups
  • configuration-driven binds, presets, and async callbacks

If you only need a few simple shortcuts, a smaller hotkey library may be enough. If you need raw event streams instead of bind matching, a lower-level input library may be a better fit.


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:

  • 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

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

Suppression limitations

On Windows, suppression depends on low-level hook order. See Advanced Usage — 4.1) 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

keybinds-1.2.1.tar.gz (53.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

keybinds-1.2.1-py3-none-any.whl (59.0 kB view details)

Uploaded Python 3

File details

Details for the file keybinds-1.2.1.tar.gz.

File metadata

  • Download URL: keybinds-1.2.1.tar.gz
  • Upload date:
  • Size: 53.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.0

File hashes

Hashes for keybinds-1.2.1.tar.gz
Algorithm Hash digest
SHA256 ecb143bd1d8462610e33ab02a9eaa42de19a30e3952c818bbda63f579aa07365
MD5 1d2a45a55d124e5af39142dab0409942
BLAKE2b-256 e038f0f054b034626b55ddb2496ebfb7c222c70379a4b73cf9dad5fae094aec4

See more details on using hashes here.

File details

Details for the file keybinds-1.2.1-py3-none-any.whl.

File metadata

  • Download URL: keybinds-1.2.1-py3-none-any.whl
  • Upload date:
  • Size: 59.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.0

File hashes

Hashes for keybinds-1.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 d4c6f594891318c8fef4d7d1f4a77d3cefed8665a9aa06ff8975d84330076feb
MD5 3a6cdfdf6a54eb98de01bfa30559441e
BLAKE2b-256 352d616f15cbf9ef1d7bdf4909c48602e8ff985afeb21470a5c1d44855533734

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page