Skip to content

feat(hook,agent,gui): vibrate mice when the cursor hovers a window's close button - #987

Open
cddu33 wants to merge 13 commits into
AprilNEA:masterfrom
cddu33:master
Open

feat(hook,agent,gui): vibrate mice when the cursor hovers a window's close button#987
cddu33 wants to merge 13 commits into
AprilNEA:masterfrom
cddu33:master

Conversation

@cddu33

@cddu33 cddu33 commented Aug 25, 2026

Copy link
Copy Markdown

Summary

  • Adds a Logi Options+-style haptic pulse: connected haptic-capable mice buzz once when the cursor hovers a window's close button (macOS traffic-light dot / Windows titlebar X), independent of the Actions Ring's own haptics.
  • Detection is OS-native (Accessibility hit-test on macOS, WM_NCHITTEST on Windows) and only polls while at least one connected device has the setting enabled — no idle cost otherwise. Linux is a clean no-op (Wayland can't answer this; X11 was intentionally left out of scope).
  • New per-device toggle in the Pointer tab, off by default.

Changes

  • openlogi-hook: new HookBackend::cursor_is_over_close_button capability — macOS via objc2-application-services (AXUIElementCopyElementAtPosition + AXSubrole), Windows via WindowFromPoint + SendMessageTimeoutW(WM_NCHITTEST) (bounded timeout so a hung target window can't stall the watcher thread).
  • openlogi-agent-core: new watchers::close_button_haptics poll watcher (dedupes hover-enter/exit via the existing Poll::on_change); Orchestrator::close_button_haptic_routes() resolves every online, opted-in, haptic-capable device.
  • openlogi-agent: dynamically starts/stops the watcher based on eligibility (checked on a cheap in-memory interval, no OS calls when nothing is enabled); generalized the existing single-flight RingHapticPlayer to play_many/arm_many so every eligible device buzzes together for one hover-enter (there's only one cursor).
  • openlogi-core: new per-device close_button_haptics: bool config field (DeviceConfig), off by default, omitted from config.toml when unset.
  • openlogi-desktop: new toggle card in the Pointer tab, gated on the same haptic-feedback capability check the Actions Ring already uses (extracted into a shared AppState::current_haptics_supported() instead of duplicating it).
  • openlogi-ui: new locale keys across all supported languages.
image

cddu33 and others added 7 commits August 25, 2026 10:32
…close button

Adds a Logi Options+ parity feature: a single haptic pulse
(HapticWaveform::SubtleCollision) fires when the cursor enters a window's
close control. macOS uses an Accessibility hit-test (objc2-application-services,
typed AXUIElement bindings); Windows uses the OS's own WM_NCHITTEST answer via
WindowFromPoint + SendMessageTimeoutW; Linux stays a clean no-op (Wayland can't
answer this, X11 support is out of scope).

- openlogi-hook: new HookBackend::cursor_is_over_close_button capability,
  default false, implemented per-platform.
- openlogi-agent-core: a new close_button_haptics watcher (Poll::on_change,
  dedupes on the false->true/true->false transition) and
  Orchestrator::close_button_haptic_routes, which returns every online,
  haptic-capable, opted-in device (there is one cursor, so every eligible
  device buzzes together).
- openlogi-agent: the watcher is started/stopped dynamically - only while at
  least one connected device has the new per-device setting enabled, rechecked
  on a cheap 500ms interval - rather than polling the OS unconditionally.
  RingHapticPlayer is generalized from a single DeviceRoute to Vec<DeviceRoute>
  (play_many/arm_many) so this feature and the Actions Ring share one
  single-flight coalescing worker instead of two near-duplicate ones.
- openlogi-core: new per-device DeviceConfig::close_button_haptics bool,
  following the existing invert_scroll field's exact serde shape.
- openlogi-desktop: a new settings card (Pointer tab) toggles the setting per
  device, gated on the same haptic-feedback capability check the Actions Ring
  already uses (now shared via AppState::current_haptics_supported).
- i18n: three new strings added to every locale catalog.
The close-button haptics feature shipped its three new keys as English
fill-ins in every non-English catalog, so the card read English in all 21
locales. Translate them, reusing each catalog's established terminology for
"haptic feedback" (from the Actions Ring card) and for the cursor/pointer,
and the existing "This device does not ..." phrasing for the unsupported
case. en.yml is unchanged.
# Conflicts:
#	crates/openlogi-agent/src/main.rs
#	crates/openlogi-desktop/src/state.rs
The upstream merge (2c3c893) made AppState.config a ConfigState wrapper
that no longer allows direct mutation; this call site still used the
pre-refactor pattern and didn't compile until fixed inline during the
merge — commit it separately since it landed after `git commit --no-edit`.
@cddu33
cddu33 marked this pull request as ready for review August 25, 2026 13:54
@greptile-apps

greptile-apps Bot commented Aug 25, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds configurable close-button hover haptics across native hit-testing, agent-side device routing and playback, desktop settings, and localization. The latest changes attempt to rearm devices across eligibility transitions and generalize the haptic worker to multiple routes, but both previously reported arming failures remain reachable.

  • Adds macOS Accessibility and Windows non-client close-button hit testing.
  • Adds dynamic hover polling and multi-device haptic routing in the agent.
  • Adds per-device configuration, Pointer-tab controls, and translated UI strings.

Confidence Score: 3/5

The PR is not yet safe to merge because stable routes can remain disarmed after power transitions and concurrent arming requests can discard required routes.

The current route-difference logic does not reassert firmware state when a wake preserves route identity, while the shared pending arming slot still lets the Actions Ring and close-button watcher overwrite each other; both paths can leave enabled haptic interactions physically silent.

Files Needing Attention: crates/openlogi-agent/src/main.rs, crates/openlogi-agent/src/server.rs

Important Files Changed

Filename Overview
crates/openlogi-agent/src/main.rs Adds the close-button watcher lifecycle, but its route-difference rearming still misses firmware resets that preserve route identity.
crates/openlogi-agent/src/server.rs Generalizes haptic playback to route batches, but concurrent arming producers still overwrite the shared pending request.
crates/openlogi-agent-core/src/orchestrator.rs Adds filtering for online, opted-in, haptic-capable device routes without an identified blocking defect.
crates/openlogi-hook/src/macos.rs Implements best-effort macOS Accessibility close-button hit testing without an identified blocking defect.
crates/openlogi-hook/src/windows.rs Implements bounded Windows non-client hit testing without an identified blocking defect.
crates/openlogi-core/src/config/device.rs Adds an off-by-default, serialization-compatible per-device setting without an identified blocking defect.
crates/openlogi-desktop/src/state/close_button_haptics.rs Adds capability-gated persistence and agent reload behavior without an identified blocking defect.

Sequence Diagram

sequenceDiagram
    participant Inventory
    participant Watcher as CloseButtonWatcher
    participant Ring as Actions Ring
    participant Player as RingHapticPlayer
    participant Firmware
    Inventory->>Watcher: Same eligible route after wake
    Note over Firmware: Power transition clears armed state
    Watcher->>Watcher: Route found in armed_routes
    Note over Watcher: Rearming skipped
    par Concurrent producers
        Ring->>Player: arm(route A)
        Watcher->>Player: arm_many(routes B)
    end
    Note over Player: Later pending_arm assignment replaces earlier routes
    Player->>Firmware: Arm only surviving vector
    Watcher->>Player: play_many(current routes)
    Player->>Firmware: Play waveform
    Note over Firmware: Unarmed route remains physically silent
Loading

Fix all with Greploop Fix All in Codex Fix All in Claude Code

Reviews (5): Last reviewed commit: "Merge branch 'master' into master" | Re-trigger Greptile

Comment thread crates/openlogi-agent/src/main.rs
claude and others added 4 commits August 25, 2026 14:23
…come eligible

The dynamic close-button watcher started polling and played haptic
pulses without ever calling RingHapticPlayer::arm_many, so a device
whose firmware haptic engine got disarmed by a power transition
(sleep/wake, reconnect) accepted play commands silently, with no
physical feedback. Re-assert arming for any route that wasn't already
eligible on the last check — covers first activation, a newly opted-in
device, and a reconnect that drops a route out of and back into the
eligible set, even while other devices keep the watcher running.

Reported by Greptile on PR AprilNEA#987.
Comment thread crates/openlogi-agent/src/main.rs
Comment thread crates/openlogi-agent/src/main.rs
Comment thread crates/openlogi-agent/src/server.rs
@davidbudnick davidbudnick added type: feature New feature request area: gui Graphical user interface platform: all Cross-platform issue labels Aug 25, 2026
Comment thread crates/openlogi-agent/src/main.rs
Comment thread crates/openlogi-agent/src/server.rs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: gui Graphical user interface 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