← Keyhac crftwr/keyhac on GitHub · craftware

CLAUDE.md — Keyhac2

What this project is

Keyhac2 is the unification of Keyhac for Windows (../keyhac-win, v1.83) and Keyhac for macOS (../keyhac-mac, v1.68): a keyboard customization / macro tool whose behavior is scripted by the user in Python (~/.keyhac/config.py). It has one shared Python codebase for both OSes, with thin per-OS platform modules. All UI is built on PuiKit (../puikit), the author’s portable Python UI toolkit.

Documentation:

Sibling repositories (read-only references)

Repo What it is What to learn from it
../keyhac-win Python 3.13 + thin C++ launcher. UI via ckit, input via pyauto (external C++ extension repos, not present in this checkout). Synchronous WH_KEYBOARD_LL hook semantics, modifier-state engine, one-shot/multi-stroke logic, hook-recovery, clipboard history, list window UX, full feature set.
../keyhac-mac Swift/SwiftUI menu-bar app embedding CPython 3.13 via a C++ bridge. CGEventTap semantics (re-enable, event-source filtering, deferred-event reordering), AX focus paths, the modern snake_case config API that Keyhac2 adopted as its baseline, ThreadedAction model.
../puikit Pure-Python UI toolkit, PyPI puikit. Backends: curses / macOS (PyObjC+AppKit) / Windows (ctypes+Direct2D) / web / memory. The UI layer for Keyhac2. Its CLAUDE.md documents a strict additive API-compatibility policy — all Keyhac2-driven extensions must follow it.

Feature reference is frozen at win 1.83 / mac 1.68; changes upstream after that are reviewed one-off.

Key decisions (rationale in doc/dev/)

  1. Languages: Python 3.14 for everything at runtime. Platform bindings via ctypes (Windows) and PyObjC (macOS) — no custom compiled extension modules. The only non-Python code is a tiny PEP 587 embedding launcher per OS, needed for packaging and a stable app identity (macOS Accessibility permission is granted per bundle). See doc/dev/packaging.md.
  2. Key hook: both OS hooks are synchronous consume-decisions running on the main thread; the differences (tap re-enable + injected/real event reordering on macOS; silent-unhook recovery on Windows) are encapsulated behind one InputHook interface. See doc/dev/platform-layer.md.
  3. Config API: keyhac-mac’s snake_case API is the base, extended with portable focus conditions (app=, title=) and the keyhac-win features it lacked. One config.py runs on both OSes; keyhac-win configs require migration.
  4. PuiKit is extended additively per its compatibility policy; everything Keyhac2 needs shipped in puikit ≥ 1.0.8 (PyPI). See doc/dev/puikit.md.
  5. Single process, main-thread rule: the native event loop on the main thread services the hook and all PuiKit windows. Slow work goes to ThreadedAction; results come back via call_on_main_thread.

Source layout

keyhac/
  core/        # OS-independent: keymap engine, key expressions, input context,
               # actions, clipboard history, replay, config loader, settings, logging
  actions.py   # action objects needing platform/UI wiring (MoveWindow, choosers, ...)
  platform/    # base.py interface definitions + fake.py test doubles
    win/       # ctypes: WH_KEYBOARD_LL/WH_MOUSE_LL, SendInput, UIA, Win32 windows
    mac/       # PyObjC: CGEventTap, CGEventPost, AXUIElement, NSWorkspace, NSPasteboard
  ui/          # PuiKit-based: console, chooser, balloon, tray, runtime (backend holder)
  main.py      # bootstrap: loop setup, hook install, config load
  _config.py   # the config.py template copied on first run
windows_app/   # Keyhac.exe launcher + bundle build (build.ps1)
macos_app/     # Keyhac.app launcher + bundle build (build.sh, create_dmg.sh)
art/           # hand-maintained SVG icon sources (rendered by tools/make_icons.py)
tools/         # icon pipeline, release scripts, hook_echo diagnostic

Conventions

Status

Released as 2.0.0 stable (version in keyhac/__init__.py). The engine, both platform layers, all UI (console / chooser / balloon / tray), the config API surface, clipboard history, macros, window actions and mouse output are implemented and live-verified on both macOS and Windows; both native launchers build, and the macOS bundle is signed/notarized and verified end-to-end. The live verification record — including which passes caught which real bugs — is in doc/dev/testing.md.

AI integration is officially supported — the MCP endpoint (keyhac/mcp/), the action API (keymap.ui, UINode) and the authoring skill. It is off unless the user ticks AI Integration > MCP Server in the console or tray menu (persisted in settings.json; there is deliberately no config API), and it carries the same additive-only expectation as the rest of the public surface.

Two properties of UINode are what that expectation is really about — they are the ceiling on action expressiveness, and changing either breaks every action already written:

Both are settled. They change only in a major release.

What remains is tracked in the GitHub issues: the deferred features (themes/fonts, i18n, migemo, rich clipboard formats, macOS ISO layout, balloon help UI). The genuinely-interactive verification passes are through (issue #10, closed), but they are a standing pre-release routine, not a finished backlog — what to repeat, and what each check is looking for, is in doc/dev/testing.md.