Skip to content

Fix slow pointer navigation with flat acceleration in Quattro - #6343

Merged
dhh merged 1 commit into
basecamp:quattrofrom
ssupt:fix/quattro-initial-pointer-hover
Jul 22, 2026
Merged

Fix slow pointer navigation with flat acceleration in Quattro#6343
dhh merged 1 commit into
basecamp:quattrofrom
ssupt:fix/quattro-initial-pointer-hover

Conversation

@ssupt

@ssupt ssupt commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Fix slow pointer navigation with flat acceleration in Quattro

Summary

With Hyprland's input.accel_profile set to flat, slow pointer movement in Quattro's native launcher and Omarchy menu could leave the highlight behind the cursor. On the tested hardware, Omarchy's default adaptive profile produced large enough event deltas to mask the problem.

The pointer gate compared each event with the immediately preceding event, so every sub-threshold event replaced the movement reference point. As a result, a stream of small movements could be ignored indefinitely. Testing the affected paths also exposed two related pointer-selection inconsistencies:

  • Entering a contiguous launcher row could wait for another position event before updating the highlight.
  • Clicking into a subordinate menu discarded the fact that navigation came from the pointer and always highlighted its first row.

This change makes pointer selection responsive while keeping keyboard behavior predictable:

  • Accumulate small movements from the last accepted pointer sample, so slow motion crosses the jitter threshold normally.
  • Sample pointer coordinates on row entry as well as position changes, while still using stable card coordinates to reject stationary delegate churn.
  • Carry pointer intent through mouse-opened subordinate menus, without changing keyboard-opened route behavior.
  • Keep newly opened launcher/menu surfaces on the first row until genuine pointer movement occurs, avoiding selection from a stale hidden cursor position.

Before

old.mp4

After

fix.mp4

Testing

  • qmllint shell/Ui/PointerMoveGate.qml shell/plugins/launcher/Launcher.qml shell/plugins/menu/Menu.qml test/shell.d/fixtures/pointer-move-gate/shell.qml
  • bash test/shell.d/pointer-move-gate-test.sh
  • bash test/shell.d/launcher-search-test.sh
  • bash test/shell.d/menu-test.sh
  • bash test/shell.d/clipboard-test.sh
  • bash test/shell.d/manifest-entrypoints-test.sh
  • Manually verified slow pointer movement and row-boundary transitions in SUPER+SPACE.
  • Manually verified mouse- and keyboard-opened subordinate menus in SUPER+ALT+SPACE.
  • Manually verified that a keyboard-opened launcher ignores the stale position of a hidden cursor.

Acceleration profile comparison

Version flat adaptive, sensitivity 0
Before this change Reproduced Not reproduced
With this change Not reproduced Not reproduced

Copilot AI review requested due to automatic review settings July 22, 2026 09:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR improves Quattro launcher + Omarchy menu pointer navigation by making hover/selection updates respond immediately and consistently, while preventing accidental selection from stale cursor state when surfaces become visible.

Changes:

  • Updates PointerMoveGate to accumulate sub-threshold pointer motion and optionally allow a single “initial sample” when a transition is known to originate from the mouse.
  • Ensures menu/launcher rows sample pointer position on onEntered (not only onPositionChanged) so highlighting updates as soon as a row is entered.
  • Propagates “from pointer” intent into subordinate menus so mouse-opened routes don’t revert to first-row selection semantics.

Tip

If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.

Show a summary per file
File Description
shell/Ui/PointerMoveGate.qml Adds initial-sample opt-in and jitter accumulation so slow pointer movement crosses the threshold naturally.
shell/plugins/menu/Menu.qml Carries pointer intent through menu navigation and samples pointer position on row entry for immediate highlight updates.
shell/plugins/launcher/Launcher.qml Prevents stale hidden-pointer selection on open and samples pointer position on row entry.
test/shell.d/pointer-move-gate-test.sh Extends coverage for new gate behavior and adds an optional quickshell runtime fixture check.
test/shell.d/fixtures/pointer-move-gate/shell.qml New runtime fixture validating the gate’s movement/initial-sample semantics end-to-end.
test/shell.d/menu-test.sh Updates assertions for pointer-intent propagation and row-entry sampling in the menu plugin.
test/shell.d/launcher-search-test.sh Updates assertions for launcher open semantics and row-entry sampling.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@ssupt ssupt changed the title Fix mouse navigation in Quattro launcher menus Fix slow pointer navigation with flat acceleration in Quattro Jul 22, 2026
Accumulate sub-threshold movement so slow motion with a flat acceleration profile updates selection. Sample pointer state on row entry and carry pointer intent through subordinate menus while preserving predictable keyboard selection.
@ssupt
ssupt force-pushed the fix/quattro-initial-pointer-hover branch from caea5f2 to de7e3fc Compare July 22, 2026 11:45
@dhh

dhh commented Jul 22, 2026

Copy link
Copy Markdown
Member

What hardware are you seeing the slowness on?

@ssupt

ssupt commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

What hardware are you seeing the slowness on?

Sorry for the long reply, but I had to dig a little deeper for science :)

I’m using a Logitech G PRO X Superlight at 1600 DPI / 1000 Hz. Under the same mouse and flat profile configuration, I did not encounter this with Walker in Omarchy 3.x or earlier. It appeared after Quattro replaced Walker and Elephant with the native Quickshell launcher and menu implementation.

This is not general rendering or hardware latency. Hyprland’s flat acceleration profile makes the problem visibly severe during slow pointer movement, while Omarchy’s default adaptive profile produces enough larger movements to mask it. The underlying issue in the native pointer gate exists with either profile.

The gate accepts movement only when either axis changes by strictly more than 1 logical pixel. However, the existing implementation also replaces its reference position after every rejected event. Consecutive small movements therefore never accumulate: every event is compared only with the immediately preceding one, allowing legitimate pointer movement to be discarded indefinitely.

To isolate this, I captured a real slow movement from the PRO X before libinput applied its acceleration profile, replayed that identical input through both profiles, and then applied both gate algorithms to the processed output. I repeated the test with a Logitech M90 at 1000 DPI / 125 Hz using a separate physical movement:

Mouse and settings Profile Existing per-event gate Proposed cumulative gate
PRO X, 1600 DPI / 1000 Hz flat 0 / 485 accepted 224 / 485 accepted
PRO X, 1600 DPI / 1000 Hz adaptive 229 / 485 accepted 341–342 / 485 accepted
M90, 1000 DPI / 125 Hz flat 127 / 313 accepted 189 / 313 accepted
M90, 1000 DPI / 125 Hz adaptive 129 / 313 accepted 191 / 313 accepted

Each comparison was repeated five times with the same raw input delivered to both profiles. The two mice used separate physical recordings, so their total event counts are not a performance comparison; the relevant comparison is between the existing and proposed gates within each row. The one-event range in the PRO X adaptive result comes from its timing-sensitive acceleration processing.

The before-fix video shows the severe PRO X + flat manifestation represented by the first row: the pointer continues moving slowly while the highlight remains behind. In the lower-polling-rate M90 recording, enough individual events exceed the threshold to avoid a complete stall, but the existing gate still rejects legitimate cumulative movement under both profiles.

The polling rates offer a likely explanation for the different severity. At 1000 Hz, slow movement can be divided across reports roughly 1 ms apart; at 125 Hz, roughly 8 ms of movement can be delivered together, making each M90 event more likely to exceed the gate’s per-event threshold. Because these were separate physical recordings, this is a technically consistent explanation rather than an isolated measurement of polling rate alone. It also illustrates why the gate should accumulate movement instead of making selection behavior depend on how each device divides motion into reports.

I also checked Walker v2.16.2’s mouse handling. Walker did not gate every selection update against a per-event threshold. After keyboard navigation disabled mouse targeting, it recorded an initial pointer sample and re-enabled GTK list targeting on any coordinate change from that sample; GTK then handled pointer interaction continuously. Quattro’s native implementation introduced the per-event gate that advances its reference position even when movement is rejected, which explains why this behavior appeared with the transition away from Walker.

The proposed change retains the last accepted pointer position, allowing consecutive sub-threshold movements to accumulate until they cross the threshold. It keeps the strict threshold and stable launcher-card coordinates used to filter stationary-pointer jitter. Therefore, flat does not cause the bug, it exposes an issue in the gate’s movement accounting that the fix corrects across both tested mice and acceleration profiles.

For completeness, the launcher was displayed on an HP OMEN X 25f at 1920×1080 / 240 Hz with scale 1. The measured profile difference is already present in the processed pointer input before rendering, so I did not identify refresh rate as the cause, it may only affect how noticeable the stalled highlight appears.

@dhh
dhh merged commit 3481b56 into basecamp:quattro Jul 22, 2026
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.

3 participants