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


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.

Performance

Measured using examples/benchmark.py on one test system and Python build. These numbers are intended as a rough reference, not a universal baseline.

  • p50 ≈ 0.3 ms
  • p99 ≈ 0.5 ms
  • max < 1 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 — 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.3.0.tar.gz (71.1 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.3.0-py3-none-any.whl (79.7 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for keybinds-1.3.0.tar.gz
Algorithm Hash digest
SHA256 647ecfec42b246306d7659291a0f777f37332c8386fafe93a916aa73d5a8c128
MD5 4a43e2f789c76b0825b62d2f55d6d31b
BLAKE2b-256 5a50249756e757c00ef4656aa4be8a0bbc4a48363fd945ec2be85b2ba8c1372b

See more details on using hashes here.

File details

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

File metadata

  • Download URL: keybinds-1.3.0-py3-none-any.whl
  • Upload date:
  • Size: 79.7 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.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3e8b31c2b47b0108ed45b7cb5d06554791c2e8c61ee651a9635bb1aa7842913f
MD5 a1d3c3a36067b75c36a82cd8104fcc2b
BLAKE2b-256 9fd711d55e6b64156ddcf2e47dc9ffe58e0631e741961b4cc71d6449e33a2b56

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