Why Keyhac 2 exists and the founding decisions behind it. This is the developer-facing design rationale; user documentation starts at ../../README.md.
Keyhac is a keyboard customization tool: it installs a system-wide keyboard hook and lets
the user script every behavior in Python — key-to-key remapping, app-specific keymaps,
multi-stroke keys, user-defined modifiers, one-shot modifiers, clipboard history,
launcher/candidate windows, and keyboard macros. The user writes a config.py that
defines configure(keymap).
Two independent implementations preceded Keyhac 2:
| keyhac-win (v1.83) | keyhac-mac (v1.68) | |
|---|---|---|
| Host / UI | Thin C++ launcher; UI in Python on ckit (author’s Win32 C++ toolkit) | Swift/SwiftUI menu-bar app; C++ PythonBridge embeds CPython |
| Input layer | pyauto (author’s C++ extension): WH_KEYBOARD_LL, SendInput, Win32 window API |
Swift: CGEventTap, CGEventPost, AXUIElement |
| Python | 3.14 embedded (PEP 587, isolated) | 3.14 embedded (PEP 587, isolated) |
| Config API | camelCase (defineWindowKeymap, InputKeyCommand, …) |
snake_case (define_keytable, ThreadedAction, …) — newer, cleaner |
| Feature set | Larger (mouse, macro, balloons, themes, migemo, tray menu…) | Smaller but modernized (UIElement/AX automation, focus paths, chooser) |
Keyhac2’s premise: everything that can be shared, is shared — one keymap engine, one config API, one UI codebase on PuiKit — and the per-OS surface shrinks to the smallest possible layer: the low-level hook, event injection, focus/window queries, and packaging.
config.py can run on both OSes.config.py files (camelCase API). We
provide a migration guide
(../migration-from-keyhac-win.md), not a shim.Short answers; each links to the full analysis.
Almost none at runtime. Python 3.14 everywhere; Windows OS access via ctypes
(as PuiKit’s Windows backend already proves viable, including COM), macOS via PyObjC
(Quartz/ApplicationServices provide CGEventTap and AXUIElement). The only non-Python
code is a tiny embedding launcher per OS (~150 lines of C/C++ using the PEP 587
PyConfig API, patterned on keyhac-win’s main.cpp and keyhac-mac’s PythonBridge.cpp)
whose job is app identity + starting the interpreter. No ckit, no pyauto, no Swift app
layer. → packaging.md
The list is short but deep: key hook & recovery, event injection & ordering, keycode tables/layouts, focus & window queries, window actions, clipboard monitoring, permissions, packaging. Notably, both hooks are synchronous consume-decisions (the “sync vs async” difference is real but lives in the surrounding machinery: macOS needs injected-vs-real event reordering and tap re-enable; Windows needs silent-unhook detection). → platform-layer.md
Yes for Keyhac2 going forward; no for keyhac-win compatibility. The two existing APIs
already diverged (camelCase vs snake_case, defineWindowKeymap(exe_name=...) vs
define_keytable(focus_path_pattern=...)). Keyhac2 adopts keyhac-mac’s API as the base —
existing keyhac-mac configs should run nearly unchanged — and adds portable focus
matching (app=, title=) plus the missing keyhac-win features. One config file can
serve both OSes, with keymap.platform branches for genuinely OS-specific parts.
→ ../configuration.md
Yes — and it happened. PuiKit was “one backend = one standard resizable window as a
regular app”. Keyhac2 needed: multiple simultaneous windows (console + chooser +
balloon), frameless / always-on-top / no-activate popup styles, an agent-app mode (no
Dock icon), a system tray icon / menu-bar extra, screen geometry queries, runtime window
control (show/hide/move), and a call_later timer. All were added additively under
PuiKit’s capability model and shipped in puikit ≥ 1.0.8.
→ puikit.md
keyhac. Repo: keyhac2.~/.keyhac/ on both OSes (keyhac-mac already did this; keyhac-win used
%APPDATA%\Keyhac — migration note in
../migration-from-keyhac-win.md).