Skip to main content

Flexible global keyboard & mouse hotkeys for Windows

Project description

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

if bind.is_pressed():
    print("bind is currently active")

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

is_pressed() checks whether the bind is currently pressed. 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.

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.2.tar.gz (80.0 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.2-py3-none-any.whl (81.0 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: keybinds-1.3.2.tar.gz
  • Upload date:
  • Size: 80.0 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.2.tar.gz
Algorithm Hash digest
SHA256 203c89e60ae5b9506336360ab42b0250df5645e1cd224151a8cd4f906c631625
MD5 b7642676ac2ab4a7b2d6a3acd42b809e
BLAKE2b-256 bc586f1b3e5ee59430bd73e1412dfa00fcf29c72bd9e695d01a257e918b9da85

See more details on using hashes here.

File details

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

File metadata

  • Download URL: keybinds-1.3.2-py3-none-any.whl
  • Upload date:
  • Size: 81.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.3.2-py3-none-any.whl
Algorithm Hash digest
SHA256 40493dcb31632234af8d024e1b4118ce92828a5da27dabe0b1c0b51a3d1cc144
MD5 8dc70eea6c89e22c662424590b35a7d7
BLAKE2b-256 a84fc3c9008386bd9cd7f808a6330a2faf5937b967c1d90359d34994003edcdf

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