Skip to content

feat: add function key remapper#344

Draft
MichaelDanCurtis wants to merge 16 commits into
AprilNEA:masterfrom
MichaelDanCurtis:feature/function-key-remapper
Draft

feat: add function key remapper#344
MichaelDanCurtis wants to merge 16 commits into
AprilNEA:masterfrom
MichaelDanCurtis:feature/function-key-remapper

Conversation

@MichaelDanCurtis

@MichaelDanCurtis MichaelDanCurtis commented Jul 1, 2026

Copy link
Copy Markdown

Summary

  • add a keyboard Keys tab with staggered Esc/F1-F19 callout bubbles, centered key markers, hover/selection highlighting, and a scrollable action picker panel
  • hide the legacy Buttons tab for keyboards; function-key remapping now lives only under Keys while mouse/trackball devices keep Buttons
  • persist global keyboard function-key bindings in config and wire them through the agent hook runtime so bound keys dispatch actions and suppress the original key event
  • add power-user picker/editor support for Type Text, Run AppleScript, Run Shell Command, and Workflow actions, reusing the mouse action picker style
  • bump the GUI/agent IPC protocol so the new GUI cannot silently drive an older installed agent without function-key dispatch
  • isolate local macOS bundle builds under org.openlogi.openlogi.dev / org.openlogi.agent.dev before signing, and route those builds through the openlogi-dev XDG profile so dev agents cannot occupy the production socket, lock, config, or asset cache
  • remove the stale cargo-bundle DMG generated before helper embedding/signing; macos package remains the production-ID DMG path

Verification

  • cargo fmt --check
  • git diff --check
  • cargo check -p xtask
  • cargo test -p openlogi-agent-core --test wire_format
  • cargo test -p openlogi-gui
  • cargo test --workspace
  • cargo run -p xtask -- macos bundle
  • verified keyboard tab regression: asset-backed keyboards with remappable keys show Keys, Lighting, and Device, but not Buttons
  • verified local bundle Info.plists are OpenLogi Dev / OpenLogi Agent Dev with .dev bundle identifiers
  • verified rebuilt local app/helper are signed with Apple Development: michaeldancurtis@gmail.com (P7R2A675CN) and pass codesign --verify --strict
  • verified target/release/bundle/dmg/OpenLogi.dmg is removed after macos bundle, avoiding the pre-helper cargo-bundle artifact
  • killed the mixed /Applications GUI + local helper state, launched the exact rebuilt dev app/helper path, and verified both running processes come from target/release/bundle/osx/OpenLogi.app
  • verified the running dev helper listens on ~/.config/openlogi-dev/agent.sock, the GUI process reports org.openlogi.openlogi.dev, TCC has an allowed org.openlogi.agent.dev row, and the visible app shows Accessibility granted instead of the background-service failure

Add an approved design for emitting mouse buttons 6–9 as pickable
actions. Approach A — four unit variants mirroring MouseBack/
MouseForward — surfaced through the existing data-driven action
stack (catalog/category/picker/inject). macOS and Linux get full
support; Windows is a documented log-and-skip gap (SendInput caps
at button 5).
Turn every capturable function-row key into a fully programmable trigger.
Execution half (all buildable): reassign media keys, type macro strings,
run AppleScript, run shell commands, run timed multi-step workflows.
Capture half splits by risk — F1-F12 extend the existing CGEventTap (low,
F1 proven); media keys need an NX_SYSDEFINED system-event tap (high,
gated milestone M3, feasibility-untested).

Three milestones: M1 F-key capture + action palette, M2 native Workflow
sequencer, M3 media-key capture (gated on its own feasibility test).
Includes Appendix A documenting the empirical finding that the Fn
modifier is not usable as a trigger (flag attaches only to function-row
keys).
Seven tasks: KeyEvent vocab + widened callback, macOS keyboard capture,
three power-user Action variants, [keyboard] config + KeyTrigger parser,
post_unicode primitive + execution arms, hook→binding dispatch, and
hardware verification. Press-to-bind UI deferred to a later M1.x (flagged
honestly). M2 Workflow and M3 media-key capture are separate plans.
Adds KeyEvent + KeyModifiers + HookEvent vocabulary alongside MouseEvent.
Hook::start's callback (and all three platform start fns) now receive
HookEvent; every callback invocation wraps MouseEvent in HookEvent::Mouse.
hook_runtime wraps its existing MouseEvent body and passes keys through
inertly. No behavior change — keyboard capture lands in the next task.

All hook + agent-core tests pass (33 + agent-core).
Adds KeyDown/KeyUp/FlagsChanged to the tap mask, plus translate_key()
which maps a key CGEvent to our KeyEvent (keycode from
KEYBOARD_EVENT_KEYCODE, press state from the event type, detectable
modifiers from the flags — SecondaryFn intentionally ignored as a
firmware-internal, unreliable trigger). The callback now builds a
HookEvent from whichever path (mouse or key) translates the event.

F1-F12/Esc are now observed by the tap; nothing acts on them yet
(hook_runtime passes them through inertly until Task 6 wires bindings).
Three power-user escape-hatch actions (excluded from the default catalog,
classed as Editing). RunAppleScript spawns osascript, RunShellCommand
spawns /bin/sh — both off the tap thread so a slow script can't wedge
input. TypeText is a temporary no-op pending the post_unicode primitive
(next task). Adds label/category/catalog wiring + roundtrip tests.

86 core tests + inject tests pass.
KeyTrigger parses '[mod+]+key' strings (f1, shift+cmd+f5, esc) into a
keycode + modifier mask using the macOS F-key virtual keycodes. It
serializes as its string form (Display) so it can be a TOML map key —
[keyboard.bindings] keys are 'f1', 'shift+f2', etc.

KeyModifiers is duplicated in core (leaf-level, no hook dep) and converted
at the agent boundary in the wiring task. Modifier aliases: ctrl/alt/cmd.

35 config tests pass (incl. parse, reject-unknown, and full TOML
roundtrip through Config); 89 core tests total.
post_unicode types a string one char at a time via a keyboard event
whose unicode payload is set with CGEventKeyboardSetUnicodeString
(layout-independent — the typed char comes from the string, not the
keycode). TypeText now calls it instead of the temporary no-op. Run*
actions were already wired in the previous task.

inject builds + tests pass.
A key-down whose keycode+modifiers match a [keyboard.bindings] entry
fires its action and suppresses the original key (so it doesn't also
type / trigger its native function); unmatched keys pass through
untouched. Key-up is ignored to avoid double-firing.

SharedKeyboardBindings is seeded from config.keyboard.bindings in
Orchestrator::new (single shared map — not per-app in M1). The hook-
layer KeyModifiers is converted to the config-layer type at the agent
boundary (core stays leaf-level).

33 agent-core tests pass. Full agent builds.
Adds Action::Workflow(Vec<WorkflowStep>) + the WorkflowStep enum
(TypeText / PressKey / Delay / RunAppleScript / RunShellCommand). The
sequencer (run_workflow) runs steps in order on a dedicated worker thread,
awaiting Delay(millis) sleeps between them — the 'type, wait, Enter, wait,
type, Escape' no-code macro flows from the spec.

PressKey reuses KeyCombo via a new macos::post_keycombo helper (which
mirrors the CustomShortcut flag construction, so the two never drift).
Workflow spawns off the tap thread like the Run actions, so blocking
delays never stall the event tap.

Excluded from the default catalog (power-user escape hatch). label/category
+ roundtrip tests pass (91 core tests).
@recchia

recchia commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Heads up @MichaelDanCurtis#191 also touches crates/openlogi-gui/src/main.rs (adds Wayland app_id handling + a couple of new mod declarations near the top of the file). The overlap looks minor (your change just adds mod keyboard_model; to the same block), so it should merge cleanly either way, but flagging it in case ordering matters when one of us rebases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants