Skip to content

fix(shell-gtk): grid drags page from the real pointer and a 10% hint (grid flakes) - #674

Merged
hanthor merged 2 commits into
mainfrom
fix/grid-drag-page-flakes
Oct 15, 2026
Merged

hanthor merged 2 commits into
mainfrom
fix/grid-drag-page-flakes

Conversation

@hanthor

@hanthor hanthor commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

Fixes the app-grid drag flakes behind G-GRID-SHORT-PAGES, G-GRID-PAGE-PEEK and G-GRID-DRAG-CANCEL's hint check (#655 covers the cancel gate; its layout-change mode is a separate root cause, see the companion PR).

Failure history (ci workflow, last 150 runs, 10-13 04:39 → 10-15 09:38 UTC)

gate job fail / reached runs
G-GRID-SHORT-PAGES journey 2 / 88 38709393542 (main 84c8721), 38687286512 (#510 at 4768fc4)
G-GRID-PAGE-PEEK journey 3 / 93 38382455637, 38389170995, 38444117537 (branches on c387326; grid code identical to main)
G-GRID-DRAG-CANCEL "no neighboring-page hint" gtk-shell 1 38389170995
G-WORKSPACE-SLIDE gtk-shell 2 / 48 38382455637 (218e0a4), 38453826185 (38a7242): both branches predate #553; 0 / 45 on code with #553

G-WORKSPACE-SLIDE needs no change here. Its race (the proof advanced the frozen animation clock before the frame that observes the key started the slide) was fixed by #553 (fe78a5d, slide_began in scripts/tuna-gtk-shell-proof:3772-3774). Both failures ran a proof without that wait: 218e0a4 checked only in_flight != null for the first press, and 38a7242 had no slide_began.

Root cause 1: phantom left-edge bump on drag enter (G-GRID-SHORT-PAGES)

GDK creates every GDK_DRAG_ENTER at (0, 0) (gdk_drop_emit_enter_event, gtk gdk/gdkdrop.c), so DropControllerMotion::enter never carries the pointer. paged_grid fed it to the pager (crates/shell-gtk/src/overview.rs:1414-1418 on main): every drag started with a bump on the left edge (DragPager::motion, overview.rs:1661-1665), which

  • turns page 2+ back to the previous page the moment a drag starts there, and
  • sets overshoot = 0, so the next real edge bump is ignored until a motion lands mid-grid.

Main run 38709393542's nested.log (TUNA_GRID_TRACE): pager motion x=0 … x=1279, nothing in between. GTK started the drag after the proof's six small steps, so the first DnD event was the jump to the right edge; it arrived as the (0, 0) enter, the page never turned, and the drop missed page 2 (a11y-grid-short-target.json still shows page 1 with "Next page preview"). Passing runs have one mid-grid motion (x=387) that clears the overshoot.

Fix: drop_enter_x reads the drop device's surface position, which wl_data_device.enter set (gdk_wayland_device_surface_at_position), and converts it to the widget. The pager and the tile hover use it on enter; when the position is unknown, enter only shows the hints.

Root cause 2: the "10%" page hint is up to ~1000 px wide (G-GRID-PAGE-PEEK, G-GRID-DRAG-CANCEL hint)

PageHint subclassed GtkFixed and overrode measure to its 10% size request (overview.rs:1241-1260). GtkFixed has a layout manager, which measures children's extents and bypasses the widget's measure vfunc. The hint grew to its slid-in icons, and every preview() call grew it further (width = hint.width()). The passing run's 11h-grid-page-peek.png shows the next-page hint spanning x=542-1280 over page-1 tiles. Once it reaches the pointer, over_next (overview.rs:1672-1673) starts the 1 s dwell (overview.rs:1694-1701) and the page turns under a drag that is only crossing tiles. All four failures show page 2 mid-slide with only "Previous page preview", pointer at x=262/274, no edge motion.

Fix: PageHint is now a plain gtk::Widget container. It measures at its size request only and allocates children at their stored positions, so the hint is 10% of the grid.

Proof: wait on observable state, not sleeps

scripts/tuna-gtk-shell-proof read state after fixed sleeps (0.25 s after the steps, 0.6 s after the bump, 1.5 s after the drop and the rebuild). New helpers poll the shell's AT-SPI tree:

  • a11y_until FILE JQ: dump until the filter holds (the drag's hint showing, the rebuilt catalog's new entry).
  • a11y_settled FILE EXPR: dump until the expression is non-empty and unchanged across two dumps (the target page at rest in view; the hint's icons done sliding).

SHORT-PAGES bumps the edge only once the drag has started, aims at the target only once the carousel rests, and polls the saved layout. PAGE-PEEK now also requires the hint to be ≤ 1/10 of the grid layer, which guards root cause 2.

Validation

Exact-head reruns of gtk-shell and journey are listed below as they finish.

Three app-grid gates flaked on main and every PR (G-GRID-SHORT-PAGES,
G-GRID-PAGE-PEEK, and G-GRID-DRAG-CANCEL's hint check). Two product
bugs in the drag pager, exposed whenever GTK starts the drag late:

- GDK emits every drag-enter event at (0, 0) (gdk_drop_emit_enter_event
  in gdk/gdkdrop.c), so DropControllerMotion::enter never carries the
  pointer. The pager fed that (0, 0) to its edge logic: each drag began
  with a phantom bump on the left edge, which turned page 2+ back at
  once and armed the overshoot so the next real edge bump was ignored.
  When the drag's first DnD event was the jump to the right edge, the
  page never turned and the cross-page drop missed (main run
  38709393542). Enter now reads the drop device's position, which
  wl_data_device.enter set; the tile hover does the same.
- PageHint overrode measure on a GtkFixed, but GtkFixed's layout manager
  measures its children's extents and bypasses the vfunc. Icons sliding
  in from outside the clip widened the "10%" hint to 700-1000+ px, so a
  pointer resting mid-grid sat "over" the next-page hint and its 1 s
  dwell turned the page under the drag (the peek and cancel failures
  show page 2 mid-slide with only the previous-page hint). PageHint is
  now a plain widget that measures at its size request and allocates
  children at their positions.

The proof also read state on fixed sleeps. It now waits on what the
shell shows: the drag's hint before acting, the page and hint icons
settling (same bounds in two dumps), the saved layout, and the
rebuilt catalog; PAGE-PEEK also checks the hint is at most a tenth of
the grid.

Refs #655

Signed-off-by: James Reilly <jreilly1821@gmail.com>
Installing an entry does not redisplay an open grid; the shell lists the
catalog as the overview opens (LiveApps rescans on the next read). The
old check passed on the post-drop rebuild without a catalog rebuild.
Reopen the grid and wait for the new entry before checking the saved
short page.

Signed-off-by: James Reilly <jreilly1821@gmail.com>
@hanthor
hanthor merged commit 668a129 into main Oct 15, 2026
16 checks passed
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