Skip to main content

Flexible and high-performance 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.9+

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:

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


Simple API

For common cases, use the lightweight decorator wrapper:

from keybinds.simple import hotkey, 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")

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.21 ms
  • p99 ≈ 0.35 ms
  • max < 0.7 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


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.0.2.tar.gz (35.7 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.0.2-py3-none-any.whl (39.6 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for keybinds-1.0.2.tar.gz
Algorithm Hash digest
SHA256 7a45fd8a166c3751300d295c658fc2a93f54c315674dac8788b4af495a5c0295
MD5 13c6640ee01cdaaaa42c3bff198172b6
BLAKE2b-256 e10e9d670e6b5df8a2b427e582766e92d0ab72c383634c1c640e052539dfbb5c

See more details on using hashes here.

File details

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

File metadata

  • Download URL: keybinds-1.0.2-py3-none-any.whl
  • Upload date:
  • Size: 39.6 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.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 3c87ce3ef40d756731ce740f2c04c8def8b6d3613d6454a89f7bc4d843f6cd9d
MD5 a5b44d6d5e07b8786b991b3b7641c648
BLAKE2b-256 7cbe39a12e0f1aa5210ab75603deea0acf561b78ecbbdaea55afd5f09d4336d7

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