Skip to content

fix(agent): apply per-app gesture button clicks#430

Open
devsaddle wants to merge 1 commit into
AprilNEA:masterfrom
devsaddle:codex/fix-per-app-gesture-button
Open

fix(agent): apply per-app gesture button clicks#430
devsaddle wants to merge 1 commit into
AprilNEA:masterfrom
devsaddle:codex/fix-per-app-gesture-button

Conversation

@devsaddle

Copy link
Copy Markdown

Summary

Apply per-application plain-click overrides to the dedicated HID++ gesture button while preserving its global directional gestures.

This completes the dedicated gesture-button counterpart to the OS-hook per-app behavior introduced in #188.

Root cause

The foreground-app update path rebuilt the OS-hook binding maps, but the dedicated HID++ gesture map was only rebuilt from global device bindings. As a result, a per-app GestureButton entry was parsed and persisted correctly but the capture watcher continued dispatching the global Click action.

Changes

  • derive the dedicated gesture button's Click action from the active app overlay
  • keep Up, Down, Left, and Right on the global gesture map
  • refresh the shared HID++ gesture map whenever the foreground application changes
  • add projection and orchestrator tests covering app entry and exit

Testing

  • cargo fmt --all -- --check
  • cargo test -p openlogi-agent-core --locked (42 unit tests, 8 wire-format tests)
  • cargo clippy -p openlogi-agent-core --all-targets --locked -- -D warnings
  • Hardware validated on macOS with an MX Master 2S:
    • global gesture-button click remained App Exposé
    • Chrome used a per-app MouseBack click and navigated back
    • Safari used a per-app BrowserBack click and navigated back
    • leaving the browser restored the global click action

@devsaddle
devsaddle marked this pull request as ready for review July 20, 2026 09:55
@greptile-apps

greptile-apps Bot commented Jul 20, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes a missing per-app overlay for the dedicated HID++ gesture button's plain-click action. Previously, set_current_app only rebuilt the OS-hook maps; the HID++ gesture map was never refreshed on foreground-app changes, so a per-app GestureButton entry was parsed and persisted correctly but the capture watcher kept dispatching the global Click action.

  • Introduces gesture_bindings_for_app in bindings.rs, which builds the full directional gesture map from global config then injects the active app's Binding::Single override into GestureDirection::Click when present, leaving Up/Down/Left/Right intact.
  • Updates Orchestrator::set_current_app to also write gesture_bindings (previously only hook_maps was refreshed), and threads current_app into rebuild so the map is correct from first construction.

Confidence Score: 5/5

Safe to merge — the change is narrowly scoped to wiring current_app into the HID++ gesture map and is fully covered by new unit and integration tests.

Both changed files make straightforward, symmetric additions: bindings.rs adds one new public function that delegates from the existing gesture_bindings_for, and orchestrator.rs adds a single write_value call to set_current_app mirroring the existing hook_maps write. The logic is covered by two new tests that exercise app-entry, app-exit, and the directional-gesture-preservation invariant. No existing code paths are altered in a way that could regress non-per-app users.

No files require special attention.

Important Files Changed

Filename Overview
crates/openlogi-agent-core/src/bindings.rs Refactors gesture_bindings_for to delegate to the new gesture_bindings_for_app, which injects a per-app Click override into the HID++ gesture map while leaving all directional gestures intact; well-tested with a new unit test.
crates/openlogi-agent-core/src/orchestrator.rs Adds gesture_bindings refresh to set_current_app and threads current_app into rebuild's gesture map write; new orchestrator integration test validates app-enter and app-exit round-trip for the dedicated gesture click.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant App as Foreground-App Watcher
    participant Orch as Orchestrator
    participant Bind as gesture_bindings_for_app
    participant Shared as SharedRuntime.gesture_bindings

    Note over Orch,Shared: Initial rebuild (no app)
    Orch->>Bind: (config, key, None)
    Bind-->>Orch: global defaults (e.g. Click→AppExposé)
    Orch->>Shared: write global map

    App->>Orch: set_current_app("com.apple.Safari")
    Orch->>Bind: (config, key, Some("com.apple.Safari"))
    Bind->>Bind: build global gesture map
    Bind->>Bind: has_app_override? → yes
    Bind->>Bind: effective_bindings → Binding::Single(BrowserBack)
    Bind->>Bind: insert Click→BrowserBack
    Bind-->>Orch: map with Click→BrowserBack, Up/Down/Left/Right unchanged
    Orch->>Shared: write hook_maps (app-scoped)
    Orch->>Shared: write gesture_bindings (app-scoped click)

    App->>Orch: set_current_app(None / other app)
    Orch->>Bind: (config, key, None or no override)
    Bind-->>Orch: global defaults (Click→AppExposé)
    Orch->>Shared: write hook_maps
    Orch->>Shared: write gesture_bindings (global restored)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant App as Foreground-App Watcher
    participant Orch as Orchestrator
    participant Bind as gesture_bindings_for_app
    participant Shared as SharedRuntime.gesture_bindings

    Note over Orch,Shared: Initial rebuild (no app)
    Orch->>Bind: (config, key, None)
    Bind-->>Orch: global defaults (e.g. Click→AppExposé)
    Orch->>Shared: write global map

    App->>Orch: set_current_app("com.apple.Safari")
    Orch->>Bind: (config, key, Some("com.apple.Safari"))
    Bind->>Bind: build global gesture map
    Bind->>Bind: has_app_override? → yes
    Bind->>Bind: effective_bindings → Binding::Single(BrowserBack)
    Bind->>Bind: insert Click→BrowserBack
    Bind-->>Orch: map with Click→BrowserBack, Up/Down/Left/Right unchanged
    Orch->>Shared: write hook_maps (app-scoped)
    Orch->>Shared: write gesture_bindings (app-scoped click)

    App->>Orch: set_current_app(None / other app)
    Orch->>Bind: (config, key, None or no override)
    Bind-->>Orch: global defaults (Click→AppExposé)
    Orch->>Shared: write hook_maps
    Orch->>Shared: write gesture_bindings (global restored)
Loading

Reviews (2): Last reviewed commit: "fix(agent): apply per-app gesture button..." | Re-trigger Greptile

Comment on lines +77 to +79
let Some(key) = config_key else {
return BTreeMap::new();
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Unreachable early-return guard

The let Some(key) = config_key else { return BTreeMap::new() } branch can never execute: the owner check immediately above already calls config_key.and_then(|key| config.gesture_owner(key)), so when config_key is None the owner is None, None != Some(ButtonId::GestureButton) fires, and the function returns an empty map before reaching this guard. The let Some(key) arm is dead code that could confuse future readers into thinking there is a reachable None-key path distinct from the owner-check path.

Fix in Codex Fix in Claude Code

@devsaddle
devsaddle force-pushed the codex/fix-per-app-gesture-button branch from 1058b38 to c16ed37 Compare July 20, 2026 10:00
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.

1 participant