Skip to content

jamiepine/keytap

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

keytap

Cross-platform, observe-only global keyboard taps for Rust.
macOS, Windows, and Linux (X11 + Wayland). Left/right modifier fidelity, clean shutdown, zero silent-failure modes.

crates.io docs.rs CI downloads license MSRV 1.85 Ask DeepWiki

Quick StartChord MatchingKeysFeaturesComparisonDesignDocs


Why

Every other Rust crate for global keyboard events forces a tradeoff:

  • rdev — full raw event stream, but collapses modifiers in some paths, crashes on macOS 14+ under threaded callers (TSMGetInputSourceProperty on a background thread), and has no clean shutdown.
  • global-hotkey — well-maintained, but registers named shortcuts with the OS and doesn't expose a raw event stream. No left/right modifier distinction.
  • hotkey-listener — nice evdev backend for Linux Wayland, but no Windows support and collapses ShiftLeft/ShiftRight into one Shift.

keytap is a focused, observe-only keyboard tap that keeps left/right modifier identity, shuts down cleanly when you drop it, and fails fast with a typed error if the OS denies permission — instead of silently producing no events.


Quick Start

[dependencies]
keytap = "0.3"
use keytap::{Tap, EventKind, Key};

let tap = Tap::new()?;

for event in tap.iter() {
    match event.kind {
        EventKind::KeyDown(Key::MetaRight) => println!("Right-⌘ down"),
        EventKind::KeyUp(Key::MetaRight)   => println!("Right-⌘ up"),
        _ => {}
    }
}
// Dropping `tap` stops the OS listener — no process-lifetime threads.

Tap::new() spawns a platform listener thread, installs the OS-level tap, and returns a handle. Events arrive on an internal channel; recv, try_recv, recv_timeout, and iter are all available. Tap is Send + Sync — share it via Arc<Tap> to fan events out across threads.

On macOS the first call may return Error::PermissionDenied if the process doesn't have Input Monitoring. This is a proactive check via IOHIDCheckAccess, not a silent failure.


Chord Matching

The default chord feature adds a state machine on top of the raw stream for the common "fire when this combination is held" pattern. Two modes:

use keytap::{Key, chord::{ChordMatcher, Chord, ChordEvent}};

let matcher = ChordMatcher::builder()
    // Momentary (default): Start on activation, End the moment any
    // chord key is released. Standard push-to-talk.
    .add("ptt", Chord::of([Key::MetaRight, Key::AltRight]))

    // Toggle: Start on first complete press, End on the *next* complete
    // press. Releases between presses are ignored — stays active until
    // re-pressed. While active, other registered chords are suppressed.
    .add_toggle("hands-free",
                Chord::of([Key::MetaRight, Key::AltRight, Key::Space]))
    .build()?;

while let Ok(event) = matcher.recv() {
    match event {
        ChordEvent::Start { id, .. } => start(id),
        ChordEvent::End   { id, .. } => stop(id),
    }
}

Semantics:

  • A chord is a set of keys — order doesn't matter for activation.
  • Longest match wins. If A and A+B are both registered, pressing A then B transitions from A to A+B. Ties broken by registration order (earlier wins).
  • Non-overlapping Start events. Transitioning directly from chord X to chord Y emits End(X) then Start(Y) — never two simultaneous actives.
  • Auto-repeat is ignored. Chord activation is edge-triggered; holding a chord doesn't spam events.
  • Toggle suppresses others. While a toggle chord is active, other registered chords won't fire — the session can't be hijacked by an overlapping chord.

Keys and Modifiers

Left and right modifiers are always distinct — no generic Shift / Control / Alt / Meta variant. Meta is ⌘ on macOS, the Windows key on Windows, and Super on Linux.

The Key enum covers the standard 104-key layout plus:

  • F1–F24 on all three platforms
  • Full numpad — digits, operators, decimal, NumpadEnter, NumLock
  • IntlBackslash — ISO layout key between Left Shift and Z (absent on ANSI)
  • Function — macOS Fn key
  • Unknown(RawCode) — any scancode keytap doesn't name yet is still emitted, never dropped

Letter, digit, and punctuation variants are keyed to their physical US-QWERTY location, not the glyph the user sees on a non-US layout. No character interpretation, no layout translation — that's the path that crashes rdev on macOS 14+.


Feature Flags

Flag Default Effect
chord keytap::chord::{ChordMatcher, Chord, ChordEvent, ChordMode}
serde Serialize / Deserialize on Key, RawCode, Chord, ChordMode — for storing hotkey configs on disk
tracing debug! at tap start/stop, trace! on channel-full backpressure, debug! on Linux hotplug adoption

Compared to Alternatives

keytap rdev global-hotkey hotkey-listener
macOS / Windows / Linux macOS + Linux only
Linux Wayland ✅ (evdev) ❌ (X11) ❌ (X11) ✅ (evdev)
Raw observe-only event stream ❌ (register-only) ❌ (register-only)
Left/right modifier fidelity
Clean Drop-based shutdown ❌ (listen() blocks forever) partial
macOS permission detected at startup ❌ (silent no-events) N/A (uses Carbon)
Multiple taps per process ❌ (global callback)
No Sonoma main-thread crash ✅ (API path doesn't exist) ❌ (inherits rdev)

What it doesn't do

  • Key simulation — use enigo or call the OS directly.
  • Grab / intercept — requires root on Linux; distinct concern.
  • Mouse events — keyboard-only in v1. A sibling mousetap crate may come later.
  • Character interpretation — no event.name: Option<String>. Keytap emits physical keycodes; consumers that want characters layer their own keymap.

Status

v0.3. macOS backend is live-tested end-to-end. Linux (evdev) and Windows (WH_KEYBOARD_LL) backends are implemented and compile-verified across targets; first-run bug reports on real hardware are welcome. Architecture and platform internals are documented in DESIGN.md.


License

MIT OR Apache-2.0.

About

Cross-platform, observe-only global keyboard taps with left/right modifier fidelity and clean shutdown. macOS, Windows, and Linux (evdev, works on Wayland).

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages