Skip to content

fix(gnome-shell): move-sync tag decodes against the main surface by its texture, not the largest actor (extension v12) - #1856

Merged
dfattal merged 1 commit into
mainfrom
fix/gnome-ext-move-sync-main-surface
Oct 8, 2026
Merged

dfattal merged 1 commit into
mainfrom
fix/gnome-ext-move-sync-main-surface

Conversation

@dfattal

@dfattal dfattal commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator

Bug

_readTag in contrib/gnome-shell/window-geometry@displayxr.org/lib.js took the largest surface actor of a window as the main surface and decoded the 1x1 move-sync tag as its offset from it. Under libdecor-gtk (GLFW 3.4's and SDL2/SDL3's stock decorations on GNOME) the shadow subsurface is larger than the content, so every tag decoded against the shadow's origin, nothing resolved against the window's position history, and the move-sync hold timed out on every title-bar drag: stale frames and visible 3D stutter.

Found by the LeiaSR session's PR 447 hardware pass on this laptop (OpenGL example, windowed native Wayland): with libdecor disabled in the app the same drag was smooth (274 weaves at 19 ms mean, 164 position advances). Apps that draw their own chrome in a smaller subsurface, the test apps and the browser among them, were never affected.

Fix (extension version 12, no interface change)

  • The main surface is found structurally: MetaWindowActor.get_texture() is the toplevel surface's MetaShapedTexture, and mutter sets that same object as the surface actor's content, so the actor whose content is that texture is the toplevel's own surface — the one the tag is a subsurface of for the runtime and for the browser's present-owner tag alike (browser-pvt patch 0317 parents the tag on the root surface).
  • The pick is a pure function, MoveSyncChoice.pick(surfaces, texture), exported and unit-tested with the libdecor layout (new section 8 of scripts/test_gnome_extension_move_sync.js). The size rule stays as the fallback without a texture (the version 9-11 behaviour).
  • Positions are summed down the actor tree, so a nested tree decodes the same as mutter 45-50's flat one. With DISPLAYXR_DEBUG=1 a one-line log names which rule applied.
  • Spec §9.2 / §9.8 (the "not possible from GJS on GNOME 50" paragraph now says what IS reachable) and the README version log updated.

Verification

  • gjs scripts/test_gnome_extension_move_sync.js (8 sections), lattice / pointer-drag / stage-scale tests, scripts/check_gnome_extension.py --require-gjs: all pass.
  • §9.7 headless GNOME Shell 50.1 harness (scripts/linux/move_sync/run.sh default 2 … slow_h fast_h reverse, this extension via EXT_SRC): shell log move sync: main surface found by its texture (4 surface actors); analyze.py 0.0 % mismatch on all three drags, move-sync timeouts: 0, every hold settled.
  • Not yet done: the libdecor case on hardware. Needs a GLFW/SDL app with stock decorations dragged after the extension is reloaded (GNOME 45+ needs a login cycle for a changed ESM extension). The LeiaSR session has the example and the trace hook (SR_WEAVER_MOTION_TRACE=1).

🤖 Generated with Claude Code

…ts texture, not the largest actor (extension v12)

_readTag took the LARGEST surface actor of the window as the main surface
and decoded the 1x1 tag as its offset from it. Under libdecor-gtk — GLFW
3.4's and SDL2/SDL3's stock decorations on GNOME — the shadow subsurface
is larger than the content, so every tag decoded against the shadow's
origin, no frame ever resolved against the window's position history,
and the hold timed out on every title-bar drag: stale frames and visible
3D stutter. Found by the LeiaSR session on its OpenGL example (SR PR 447
hardware pass); the same drag was smooth with libdecor disabled. Apps
that draw their own chrome (the test apps, the browser) never hit it.

The main surface is now found structurally: MetaWindowActor.get_texture()
is the toplevel surface's MetaShapedTexture, and mutter sets that same
object as the surface actor's content, so the actor whose content is the
texture is the toplevel's own surface — the one the tag is a subsurface
of for the runtime and for the browser's present-owner tag alike. The
pick is a pure function (MoveSyncChoice.pick), unit-tested with the
libdecor layout; the size rule remains the fallback without a texture
(the 9-11 behaviour). Positions are summed down the actor tree so a
nested tree decodes the same as mutter 45-50's flat one. A debug log
names which rule applied.

Verified on the §9.7 headless GNOME Shell 50.1 harness (scale 2, slow_h /
fast_h / reverse): "main surface found by its texture", 0 mismatch,
0 timeouts, every drag settled. The libdecor case itself needs a
hardware pass with a GLFW/SDL app after the extension is reloaded
(login cycle).

Spec §9.2 / §9.8 and the README version log updated; lint comment too.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@dfattal
dfattal requested a review from a team as a code owner October 8, 2026 06:12
@dfattal
dfattal merged commit 8e702eb into main Oct 8, 2026
34 checks passed
@dfattal
dfattal deleted the fix/gnome-ext-move-sync-main-surface branch October 8, 2026 09:10
@dfattal

dfattal commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Hardware confirmation (2026-10-09, ds1-linux, GNOME Shell 50 Wayland, Acer DS1 on HDMI-1 at 200 %) — the libdecor case this PR fixes now passes on the real panel.

Extension v12 live (gnome-extensions info: Version 12, ACTIVE). LeiaSR OpenGL Wayland example with stock libdecor-gtk decorations (SR_EXAMPLE_LIBDECOR=1, libdecor-0.so.0.200.5), David dragging and resizing across both monitors; verdict "works well". Motion trace (~/.cache/leiasr-move-sync-traces/2026-10-09-libdecor-v12/gl-wayland-movesync-libdecor-on.log, LeiaSR session):

metric value
weaves while moving 804
synced=1 / moveSync=1 804 / 804
tag mismatches (tag ≠ content mod 256) 0
DS1 weaves with origin == (content − monitor) × 2 750 / 750
move-sync timeouts 0
inter-weave gap median / p95 / max 17 ms / 18 ms / 59 ms

Before this fix (2026-10-07, v11) every title-bar drag of the same app timed out because the tag was decoded against the libdecor shadow actor. The same evening's v11 run with libdecor off also passed (366/366), so the delta is the texture-based main-surface pick. Not verified: the one-time displayxr: move sync: main surface found by its texture journal line (gnome-shell's log is not readable from the user journal on this box); the trace is the evidence.

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