Desktop helper to visualize split/ergonomic keyboard layouts (Corne, Lily58, Sofle, Dactyl, QWERTZ, Magic) and highlight pressed keys in real time. Built with vanilla HTML/CSS/JS on top of Tauri.
git clone https://github.com/maxistar/keyboard_helper.git
cd keyboard_helper- Node.js (LTS)
- Rust toolchain (
rustuprecommended) - Platform dependencies for Tauri v2: tauri.app/start/prerequisites
The Tauri CLI is a dev dependency — npm install pulls it in automatically.
Install dependencies and launch the Tauri dev window:
npm install
npm run tauri devProduce a packaged release build (bundles Rust + frontend):
npm run tauri buildArtifacts will be written to src-tauri/target/release/ (per-platform bundles such as .app, .dmg, .exe, .msi, or distributable archives). Use the standard Tauri CLI flags for platform-specific targets if you need to cross-compile.
On macOS, the packaged application is named Keyboard Helper.app. When upgrading from an older Keyboard Layout.app release, quit the old application and remove that bundle before installing the new one. Existing ~/.keyri.json settings remain compatible. If key highlighting stops after the replacement, remove the stale application entry from System Settings → Privacy & Security → Input Monitoring, add Keyboard Helper.app, and launch it again.
GitHub releases provide separate macos-apple-silicon downloads for M-series Macs and macos-intel downloads for Intel Macs. Each DMG has a neighboring .sha256 file; place both in the same directory and verify the download before opening it:
shasum -a 256 -c Keyboard-Helper_*.dmg.sha256These macOS packages use an ad-hoc code signature so they can be built without Apple Developer credentials. They are unnotarized preview builds: Apple has not verified them, and Gatekeeper is expected to block the first launch.
- Open the DMG, drag
Keyboard Helper.appto Applications, and try to launch it once. - Open System Settings → Privacy & Security, scroll to Security, and choose Open Anyway for Keyboard Helper.
- Confirm Open in the warning dialog and enter your Mac login password if requested.
- Grant Input Monitoring when needed so global key highlighting can work.
This approves only Keyboard Helper; there is no need to disable Gatekeeper or remove download quarantine metadata. Because an ad-hoc identity is specific to a build, macOS may ask for privacy permissions again after an update. If highlighting stops, quit the app, remove its stale entry from System Settings → Privacy & Security → Input Monitoring, add /Applications/Keyboard Helper.app, and relaunch it. Bluetooth or other privacy prompts may also reappear.
The application bundle name is user-facing; the internal executable remains keyboard-app. External shortcut tools can toggle the running overlay with:
/Applications/Keyboard\ Helper.app/Contents/MacOS/keyboard-app --toggleClosing the window hides the app to the system tray instead of quitting. Use the tray menu to restore the window or quit the app.
Choose Mini Mode from the overlay menu to shrink the current keyboard into a compact, frameless window. On macOS, the same action is available as View → Enter Mini Mode. Mini Mode keeps the active layer, live key highlights, combo borders, and BLE updates running while hiding the regular menu and status indicators.
- Drag the compact overlay from the empty padding around the keyboard.
- Choose the arrow icon in the upper-right corner (labelled Restore full size) to return to the exact full-size window geometry.
- Layout changes and external layout reloads resize the compact window automatically.
- Mini Mode is temporary: every fresh application launch starts in full mode.
If entering or restoring Mini Mode fails, the overlay shows a temporary error and keeps a usable recovery control on screen.
The desktop application includes a word-typing arcade game. Open the application menu and choose Shift-Space Invaders to launch it in a separate window; choosing the action again focuses the existing game window.
- Select an alien by typing the first letter of its visible English word, then finish the word to destroy it.
- Each correct character fires a hit. A mistake keeps the completed prefix but resets the score multiplier.
- Press
Escto pause or resume. Moving focus away from the game pauses it automatically. - Later waves introduce longer words, faster targets, shorter spawn intervals, and more simultaneous targets.
- The game-over screen reports score, highest wave, destroyed targets, accuracy, and words per minute.
The first release uses curated English words and keeps results only for the current session. It does not save profiles, achievements, or high scores. The game window is available through the native Tauri application; the standalone browser frontend does not create desktop windows.
Open the application menu, expand Games, and choose Underwater Typing Fishing to start a calm Zen typing session in a separate desktop window. Choosing it again focuses the existing fishing window.
- Each visible fish carries a word and no two visible fish begin with the same character.
- Type a fish's first character to hook it, then complete the word to reel it in.
- A mistake resets the current streak but preserves the correctly typed prefix; fish never escape merely because time passes.
- Reach the catch quota to finish the dive, or choose End dive to see the current results early.
- Press
Escto pause or resume. Moving focus away from the game pauses it automatically. - Results include score, caught fish, correct characters, mistakes, accuracy, words per minute, best streak, and active time.
The initial Zen mode uses bundled English words and stores no profile or high score. The game accepts focused semantic keyboard input and does not require BLE telemetry or global key events. Like the other secondary game windows, it is available in the native desktop application rather than the standalone browser frontend.
Open the overlay menu, expand Keyboard, and choose Keyboard Self-test. The desktop app opens one separate test window (or focuses the existing one), initially selecting the overlay's current layout and its base layer.
- Choose a configured built-in or external layout and the layer you want to verify.
- Choose Start guided layer test. If the layout explicitly maps that layer and writable BLE layer control is ready, Keyboard Helper activates it and waits for authoritative confirmation. Otherwise activate the layer manually and continue in HID-only mode.
- Press and release the highlighted physical position. An unexpected output can be retried or recorded as a problem; use Skip this position when a key produces no event.
- Review passed, unexpected, skipped, and not-testable positions. You can retest only the problems or return to choose another layer.
Layout-wide firmware combos are not repeated in each layer test. When detailed BLE telemetry is ready and permitted, choose Test global combos to run one separate firmware-authoritative plan. Only combo definitions with a non-empty code are included, and this test does not activate or lease a firmware layer. If BLE telemetry is unavailable or disabled for privacy, the combo action is unavailable without affecting ordinary HID layer testing.
The self-test window is a compact controller; the existing overlay remains the only keyboard visualization. Guided outlines are added by physical position while normal live pressed-key highlighting continues, so the expected position and the received key can be seen together. A confirmed automatic layer selection is temporary: input-source reconciliation is deferred during the lease, an unexpected authoritative layer change pauses the test, and Stop, completion, Test another layer, or close conditionally restores the preceding layer without overwriting newer user intent.
The report exists only until the self-test window closes. System HID events are the sole authority for ordinary Passed and Unexpected results, so stock ZMK and non-ZMK keyboards remain testable without custom telemetry. Optional BLE key telemetry only corroborates a physical position or adds a warning; it may be unavailable or disabled for privacy and never blocks a correct HID verdict. Without corroboration, events can come from any attached keyboard. The separate global combo test remains BLE-authoritative, while raw matrix health and unsupported or multi-step behaviors are outside the test.
Releases are cut from master with semantic version tags.
The required source matrix, website production build, platform packages, and Windows secondary-window smoke all finish before CI creates a GitHub release. See Product quality gates for local commands, platform constraints, retained artifacts, and failure recovery.
- Version source: keep
package.json,src-tauri/tauri.conf.json, andsrc-tauri/Cargo.tomlon the same semver (e.g.,0.2.0). Update all three before tagging so the JS package, Tauri config, and Rust crate stay aligned. - Trigger: create an annotated tag
vMAJOR.MINOR.PATCHonmasterand push it; CI will build macOS/Windows/Linux bundles and publish a GitHub release with the assets attached. macOS release downloads are explicitly labelledmacos-apple-siliconormacos-intel, include SHA-256 files, and remain unnotarized previews. The job fails if the tag does not match all three version sources. - Steps:
- Bump the version in
package.json,src-tauri/tauri.conf.json, andsrc-tauri/Cargo.toml, commit, and merge tomaster. - Draft release notes (highlights, fixes, platform notes). Keep them short and paste them into the GitHub release description after CI creates it.
- Tag the merge commit (
git tag -a v0.2.0 -m "Release v0.2.0") and push the tag (git push origin v0.2.0). - Watch the release workflow in GitHub Actions. A failed prerequisite creates no normal release; use its retained artifacts to diagnose the failure and rerun the complete tagged workflow. After success, open the generated release for
v0.2.0and replace the automated notes with the prepared notes.
- Bump the version in
- Non-tag pushes to
masterstill run the build and upload architecture-specific artifacts to the workflow run but do not create a GitHub release entry or stable release downloads.
Open the application menu and choose Settings to select the startup layout, enable built-in layouts, record the show/hide shortcut, or add an external layout with a file picker. Changes are validated before they are saved and apply to the running overlay.
Advanced users can still edit the compatible JSON configuration directly. The app looks for ~/.keyri.json first and then the legacy ~/keyri.json; the Settings window writes new changes to ~/.keyri.json.
- Fields:
defaultLayout: key of the layout to select at startup (must exist inlayouts).layouts: object mapping layout keys to eithertrue(use built-in) or a filesystem path (load external JSON).
- Layout combos (layout-specific): add a
combosarray inside a layout file with entries like{ "key1": { "row": 1, "col": 4 }, "key2": { "row": 1, "col": 5 }, "code": "Enter" }. - Layout file format: JSON with
name, optionalbleLayerSource, optionalinputSourceSync,keySize(w,h,gapin px),keyPositions(array of{row,col}with optionalw/hoverrides), andkeyLayers.keyLayerscan be an object withdefault,shift, etc., or an array where index 0 is the base layer. Each layer entry is[label, code](or an object withtext/imagefor custom labels). - The normalized zero-based
keyLayersorder must match ZMK firmware layer numbering. Self-test uses that order for automatic layer activation and treats empty or unsupported output codes as not testable; layout JSON has no separate self-test mapping or exclusion metadata. bleLayerSourceis optional and enables BLE-authoritative layer updates for the selected keyboard only. Shape:deviceName: BLE keyboard name to matchserviceUuid: custom GATT service UUIDcharacteristicUuid: custom active-layer characteristic UUIDformat: currentlyint32-le
- References: see built-in layouts for structure (
src/layout_corne.json,src/layout_qwertz.json,src/layout_dactyl.json,src/layout_lily58.json,src/layout_sofle.json,src/layout_mac.json,src/layout_magic.json). Copy one, edit, and point your config at the new path. Keep a personal config in~/.keyri.json. - If no config file is found, the Settings window starts from all built-in layouts with QWERTY selected. A malformed config is never silently replaced: recovery requires confirmation and creates a timestamped backup. Unreadable external layouts produce a visible error while the overlay falls back to an available built-in layout.
If the selected layout defines bleLayerSource, the app attempts to connect to that keyboard over BLE and listen for active-layer notifications. When BLE sync is active, the on-screen layer indicator follows the keyboard's reported layer and key highlighting remains independent.
- If BLE sync starts successfully, the app bootstraps the current layer and then updates immediately from notifications.
- If BLE metadata is absent, BLE startup fails, or the BLE feed disconnects, the app falls back to the existing non-BLE behavior.
- BLE sync starts and stops automatically when you switch layouts.
A layout may add a platform-scoped input-source block to make the confirmed OS input source authoritative for its ZMK language family. This requires the same bleLayerSource characteristic to support encrypted Write with response and Notify. Unsupported platforms, layouts without current-platform metadata, and malformed metadata retain ordinary read-only BLE observation and local layer preview.
Use inputSourceSync.macos with exact installed Text Input Source Services identifiers.
Each source has a unique app ID and label, the exact installed macOS inputSourceId, one stable baseLayer, and all related language layers. neutralLayers lists utility layers that belong to neither language. Family and neutral layer indexes must exist and must not overlap. Non-base family layers and neutral layers defer correction; the app only corrects a stable foreign base layer after settleMs. This optional interval defaults to 1000 milliseconds and accepts integers from 0 through 60000.
{
"inputSourceSync": {
"macos": {
"settleMs": 1000,
"sources": [
{
"id": "de",
"label": "Deutsch",
"inputSourceId": "com.apple.keylayout.German",
"baseLayer": 4,
"layers": [4, 5, 6, 16]
},
{
"id": "ru",
"label": "Русский",
"inputSourceId": "com.apple.keylayout.Russian",
"baseLayer": 9,
"layers": [9, 10, 12, 17]
}
],
"neutralLayers": [13, 18]
}
}
}Use defaults read ~/Library/Preferences/com.apple.HIToolbox.plist AppleEnabledInputSources to inspect enabled sources. The configured value must be the exact Text Input Source Services kTISPropertyInputSourceID; plist fields vary by source type, so treat names and bundle fields as discovery hints rather than transforming them. A configured but uninstalled ID is shown as unavailable and is never replaced by a guessed or cycled source. See ../corney/layout_corney.json for the full Corney layout example.
Use inputSourceSync.windows with canonical Windows KLIDs such as windows:klid:00000409. Helper follows the foreground application's layout and respects Windows settings. See Windows configuration, selection policy, and CorneyMX verification.
Use inputSourceSync.linux.x11 in a real X11 session. Wayland and XWayland are intentionally unsupported. Stable identifiers use xkb:layout:<layout> or xkb:layout:<layout>:<variant>; zero-based xkb:group:<index> identifiers are available as a positional fallback. Prefer stable layout identifiers because group indexes change when the desktop keyboard-layout order changes.
{
"inputSourceSync": {
"linux": {
"x11": {
"settleMs": 1000,
"sources": [
{
"id": "de",
"label": "Deutsch",
"inputSourceId": "xkb:layout:de",
"baseLayer": 1,
"layers": [1, 2, 3, 15]
},
{
"id": "ru",
"label": "Русский",
"inputSourceId": "xkb:layout:ru",
"baseLayer": 8,
"layers": [8, 11]
},
{
"id": "en",
"label": "English",
"inputSourceId": "xkb:layout:us",
"baseLayer": 0,
"layers": [0]
}
],
"neutralLayers": [13, 14, 18]
}
}
}
}The Language flyout reports the detected zero-based group index, XKB group name, layout, variant, and accepted identifiers. Selection must be confirmed by an XKB state event or read-back within 1000 milliseconds before reconciliation may write a keyboard layer. The adapter follows the XKB group only; it does not observe IBus or Fcitx language state, so do not enable it when an input method is the actual language authority.
On Debian/Ubuntu, Linux development and packaging require libx11-dev in addition to the existing Tauri GTK/WebKit/AppIndicator packages. The complete test layout is ../corney/layout_corney.json; point a Keyboard Helper external layout entry at that file to exercise its de → ru → en cycle. The English XKB group uses the canonical xkb:layout:us identifier and maps to Corney's layer 0.