Skip to main content

j-input

Windows global input for Python, implemented in Rust.

One native thread owns the low-level mouse and keyboard hooks and a foreground-window event hook. Every hook callback runs a plain state machine and decides on the spot whether to swallow the event; it never calls into Python and never waits for the GIL. A busy Python main thread therefore cannot stall the system's mouse or keyboard. Only the few events the application cares about are queued for Python.

import inputhub

hub = inputhub.Hub()                                   # one per process
hub.configure_gestures([(["ctrl"], "left"), (["win"], "right")], True)
hub.configure_side_buttons(True, ["x1"])               # swallow Back, report both side buttons
hub.bind_hotkey("clipboard", ["win"], 0x56)            # take Win+V from the system
hub.watch_wheel("stitch", (0, 0, 1920, 1080))          # wheel events inside this rectangle only
hub.watch_keys("stitch", [0x10, 0xA0, 0xA1])           # Shift
hub.watch_foreground("clipboard")                      # every foreground change, including this process

event = hub.next_event()           # blocks with the GIL released; next_event(100) waits at most 100 ms
# ("gesture", "start", id, x, y) / ("moved", id) / ("side", "x1") / ("hotkey", "clipboard") /
# ("wheel", watcher, x, y, delta, horizontal) / ("key", watcher, vk, pressed) /
# ("foreground", hwnd) / ("failure", message)

hub.take_position(gesture_id)      # latest cursor position of a drag, consumed once per ("moved", id)
hub.close()                        # a pending next_event() returns None once queued events are drained

Gestures

A gesture binds one or two modifiers to a mouse button. It starts when the button is pressed while exactly those modifiers are held, and finishes when the button is released. The press and release are swallowed as a pair, so the window below never starts a drag. While a gesture is active, wheel events and other clicks are swallowed; Escape or releasing one of its modifiers cancels it. Releasing Win or Alt after a gesture sends a paired unassigned key first, so the Start menu or menu bar does not open. accepts(id) tells whether a queued start or finish is still current after a cancel or a reconfiguration.

A matching gesture takes precedence over a side-button hotkey on the same button.

Injected input is ignored by gestures. set_test_marker(value) makes injected input whose dwExtraInfo equals value count as real, so tests can drive the real hooks.

Hotkeys

A hotkey binds modifiers to a non-modifier key, and fires when that key is pressed while exactly those modifiers are held. Unlike RegisterHotKey, it can take combinations the system already owns, such as Win+V. The key's press, auto-repeats and release are swallowed together, and only the press is reported. A key already held before the modifiers is left alone. Releasing Win or Alt afterwards is masked the same way as after a gesture. Injected input is ignored unless it carries the test marker. unbind_hotkey(name) gives the combination back; a release still owed for a swallowed press is swallowed as well.

Hook lifetime

The low-level hooks are installed only while something needs them: enabled gestures or side buttons, a hotkey, a wheel or key watcher, or a swallowed press still waiting for its release. The out-of-context foreground-window hook is installed only while a foreground watcher exists. Foreground events are not filtered; the caller decides which windows count.

inputhub.Engine drives the same state machine without installing hooks, with held keys supplied by the caller; it exists for tests and may be called from any thread.

The distribution is j-input; the module imports as inputhub. Without the default python feature the crate is a plain Rust library.

Metadata

Release files for j-input 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distributions (wheels)

Table of built distributions (wheels) for j-input 0.1.0
File Interpreter ABI Platform
j_input-0.1.0-cp311-abi3-win_arm64.whl CPython 3.11 abi3 Windows ARM64 Details
j_input-0.1.0-cp311-abi3-win_amd64.whl CPython 3.11 abi3 Windows x86-64 Details

Total release size: 416.2 kB

Release files / j_input-0.1.0-cp311-abi3-win_arm64.whl

Download URL j_input-0.1.0-cp311-abi3-win_arm64.whl
Size 204.8 kB
Tags CPython 3.11 Windows ARM64 abi3
SHA-256 checksum
How to use checksums
e285a0ecdbf618e47a60e90d59093afd95c1bfeba7fbb3f345966fad6c96084b
BLAKE2b-256 checksum
How to use checksums
11ff36d3313912777e875b5a21c256222543ad877d584d74a1974e253fcc4207
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / j_input-0.1.0-cp311-abi3-win_amd64.whl

Download URL j_input-0.1.0-cp311-abi3-win_amd64.whl
Size 211.4 kB
Tags CPython 3.11 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
25dbf814ca58dad52465d7ff3872d70c1885372b7ddc05249c48f7042e2a06ac
BLAKE2b-256 checksum
How to use checksums
ae214dda4695753c2de360ad66a62e08fbe66bb13f8ce8b5c305c3cdac25e61c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release history Release notifications | RSS feed

0.1.1

2 release files

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page