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.1.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.1-py3-none-any.whl (39.6 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: keybinds-1.0.1.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.1.tar.gz
Algorithm Hash digest
SHA256 f7944eec5b6ea953892941cd71e381a8d6779ec52a5c17aa423090d68576a176
MD5 cbbbf8337f7de91938160f652b456025
BLAKE2b-256 fcd4fbb72009f59fb5436af8c0a1a18add730ee1b574bd86aae3fd2a1aa1ac55

See more details on using hashes here.

File details

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

File metadata

  • Download URL: keybinds-1.0.1-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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 54f5751c15f72b14a732f39e7a17177396da6c15405eb72166d6287b18660d6d
MD5 f543b3b739b97fdf78cacca08f265538
BLAKE2b-256 7ff2dff76e2df55e5ed145ae463eee84cb82528190b9ede152d4752e8edf291a

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