Skip to content

feat: add function key remapper - #344

Merged
davidbudnick merged 21 commits into
AprilNEA:masterfrom
MichaelDanCurtis:feature/function-key-remapper
Aug 10, 2026
Merged

feat: add function key remapper#344
davidbudnick merged 21 commits into
AprilNEA:masterfrom
MichaelDanCurtis:feature/function-key-remapper

Conversation

@MichaelDanCurtis

@MichaelDanCurtis MichaelDanCurtis commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

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

@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.

@davidbudnick davidbudnick added type: feature New feature request platform: all Cross-platform issue labels Aug 2, 2026
@davidbudnick
davidbudnick marked this pull request as ready for review August 9, 2026 23:53
@greptile-apps

greptile-apps Bot commented Aug 9, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds persisted global function-key bindings, a keyboard Keys editor, runtime action dispatch, IPC compatibility updates, and isolated macOS development bundles.

  • Adds Esc/F1–F19 binding configuration and action editors.
  • Routes captured key events through the agent action worker.
  • Separates development bundle identifiers, profile paths, sockets, locks, and generated packages.
  • Updates localized labels and platform hook abstractions.

Confidence Score: 3/5

The PR is not safe to merge until synthetic macOS keys can no longer re-enter remapping and non-macOS backends can capture configured function keys.

Synthetic keyboard events posted on macOS still feed back into the key-binding dispatcher, while Linux and Windows never emit keyboard hook events, leaving the feature broken on those targets.

Files Needing Attention: crates/openlogi-hook/src/macos.rs, crates/openlogi-hook/src/linux.rs, crates/openlogi-hook/src/windows.rs, crates/openlogi-inject/src/inject/macos.rs

Important Files Changed

Filename Overview
crates/openlogi-hook/src/macos.rs Adds macOS keyboard capture, but synthetic keyboard events remain able to re-enter the remapping path.
crates/openlogi-hook/src/linux.rs Widens the callback abstraction to HookEvent without implementing keyboard-device capture or key-event translation.
crates/openlogi-hook/src/windows.rs Completes the callback type migration, but the backend remains mouse-only and cannot deliver function-key events.
crates/openlogi-agent-core/src/hook_runtime.rs Adds global key-trigger lookup, suppression, and queued action dispatch; its behavior still depends on platform backends filtering and emitting key events correctly.
crates/openlogi-agent-core/src/orchestrator.rs Publishes global keyboard bindings into shared runtime state and refreshes them when configuration is rebuilt.
crates/openlogi-core/src/config.rs Adds persisted keyboard trigger-to-action configuration and supporting accessors.
crates/openlogi-gui/src/keyboard_model/function_row.rs Implements the keyboard function-row visualization and binding selection interface.
xtask/src/commands/macos/bundle.rs Updates local macOS bundling to isolate development identities and remove the stale pre-helper DMG artifact.

Sequence Diagram

sequenceDiagram
  participant User
  participant GUI
  participant Config
  participant Agent
  participant Hook
  participant Actions
  participant OS
  User->>GUI: Configure function-key binding
  GUI->>Config: Persist KeyTrigger → Action
  GUI->>Agent: Reload config over IPC
  Agent->>Hook: Publish keyboard binding map
  User->>Hook: Press configured key
  Hook->>Agent: HookEvent::Key
  Agent->>Actions: Queue bound action
  Actions->>OS: Inject action output
Loading

Reviews (4): Last reviewed commit: "fix: rebase function-key remapper onto m..." | Re-trigger Greptile

@davidbudnick
davidbudnick force-pushed the feature/function-key-remapper branch 2 times, most recently from b88e2d7 to 13df2f8 Compare August 10, 2026 00:45
@davidbudnick davidbudnick added this to the v0.6.24 milestone Aug 10, 2026
@davidbudnick davidbudnick self-assigned this Aug 10, 2026
MichaelDanCurtis and others added 21 commits August 9, 2026 20:28
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).
@davidbudnick
davidbudnick force-pushed the feature/function-key-remapper branch from 103f49d to 248b6e8 Compare August 10, 2026 01:35
@davidbudnick
davidbudnick merged commit 91fe5d8 into AprilNEA:master Aug 10, 2026
18 of 27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

platform: all Cross-platform issue type: feature New feature request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants