Skip to content

fix(wayland): convert window geometry by the stage factor in mutter's PHYSICAL layout (extension v11) - #1785

Merged
dfattal merged 1 commit into
mainfrom
fix/wayland-physical-layout-mode
Oct 2, 2026
Merged

dfattal merged 1 commit into
mainfrom
fix/wayland-physical-layout-mode

Conversation

@dfattal

@dfattal dfattal commented Oct 2, 2026

Copy link
Copy Markdown
Collaborator

Problem

On Ubuntu 24.04 (GNOME 46, native Wayland) with a 3840x2160 3D panel at 200 %, no app weaves. wl_geom reports the window on a 7680x4320 output against the 3840x2160 panel. So the runtime decides the window is not on the panel, presents flat 2D and turns the lens off (NOT_1TO1, #1595/#1596). At 100 % it works.

Cause

mutter has two monitor layout modes, and the window-geometry extension publishes stage coordinates:

layout-mode when stage space 3840x2160 monitor at 200 %
1 LOGICAL fractional scaling; mutter 50's default logical px rect 1920x1080, scale 2, stage view scale 2
2 PHYSICAL Ubuntu 24.04 / GNOME 46 at an integer scale, out of the box device px rect 3840x2160, scale 2, stage view scale 1

comp_vk_native_wl_geom always multiplied by monitor.scale. That is right in LOGICAL, but in PHYSICAL it doubles values that are already device px.

Repro (private headless mutter 50.1, never the shared panel)

A private gnome-shell --headless runs with its own XDG_RUNTIME_DIR, D-Bus and dconf, plus a 3840x2160 virtual monitor. The mode is set with gdctl set --layout-mode physical --scale 2 (GetCurrentState layout-mode = 2). The repo's extension is enabled. cube_handle_vk_linux --platform=wayland --windowed runs on DXR_PLUGIN_EXCLUSIVE=sim-display (active plug-in: id=sim-display):

wl_geom: monitor scale 2.0000 — window content logical 0,180 3840x2160 on a 3840x2160 logical monitor converts to DEVICE 0,360 7680x4320 on a 7680x4320 px monitor (#1596)
NOT_1TO1: presenting 2D (no weave) — this surface is not on the 3D panel. buffer 3840x2160 px, surface 7680x4320 px on a 7680x4320 px output, 3D panel 3840x2160 px.
wl_geom: this surface is on a 7680x4320 px output, but the 3D panel is 3840x2160 — the window is not on the panel

The same binary in LOGICAL at 200 % converts to DEVICE 0,180 3840x2160 on a 3840x2160 px monitor and weaves.

Fix

Runtime (comp_vk_native_wl_geom.c, new pure u_wayland_layout.h): the conversion factor is now device px per stage px, resolved per window by u_wl_stage_to_device_scale(), in this order:

  1. The publisher's monitor.device_scale (extension v11).
  2. Its layout_mode (extension v11).
  3. mutter's own layout-mode from org.gnome.Mutter.DisplayConfig.GetCurrentState. This covers an extension older than v11, e.g. the one a session keeps running until logout after a package upgrade. It is queried once and refreshed on MonitorsChanged.
  4. The monitor scale, as before.

wr.scale carries the same factor, so the drop-time phase snap searches the right device positions. One WARN names PHYSICAL and where that came from. u_wayland_geom.h is untouched on purpose: displayxr-common vendors it byte-for-byte and the test-app build guards that, so this PR does not need a common release.

Extension v11 (lib.js): the snapshot gains a top-level layout_mode ("logical"/"physical") and monitor.device_scale. mutter does not introspect its layout mode, but the stage view scales give it away (StageScale, pure + unit-tested). No existing field changes meaning, so the schema stays version: 1. Bumping it would make every shipped runtime refuse a payload that is still correct for it on LOGICAL desktops. The drag-lattice choice and the stamp audit now weigh device px by the same factor.

Audit of the extension's other logic

  • MoveWindow, the pointer drag and the move-sync history all work in stage coordinates, so they follow the mode unchanged.
  • The move-sync tag decode needs no change. Subsurface actor positions are in surface units in both modes. Measured in PHYSICAL at 200 %: the tag actor sits at 96,208 for a buffer at stage 1120,720 (= mod 256), exactly what the runtime set.

The spec (§2, §3, new §4.1) and the extension README are updated.

Verification (headless, sim-display, windowed 1600x900)

layout scale extension device rect result
PHYSICAL 2 v11 1120,720 1600x900 on 3840x2160 present origin set, no NOT_1TO1
PHYSICAL 2 v10 (installed) same, via Mutter fallback same
PHYSICAL 1 v11 1120,675 1600x900 weaves
LOGICAL 2 / 1.5 / 1.25 / 1.6667 v11 1120,720 / 1121,698 / 1120,686 / 1120,705 on 3840x2160 weaves
PHYSICAL fullscreen 2 v11 0,0 3840x2160 NOT_1TO1 cleared, weaves 1:1

(gdctl snaps a requested 1.75 to 1.6667 on this mode.)

Tests

  • tests_aux_wayland_geom: PHYSICAL at 200 % lands 1:1; the reported failure (x2 → 7680x4320, "not the panel") is pinned; LOGICAL at 100/125/150/175/200 %; precedence and string parsing.
  • scripts/test_gnome_extension_stage_scale.js (new, added to lint.yml), on the measured view scales.
  • The existing lattice / move-sync / pointer-drag gjs tests and check_gnome_extension.py pass.

Follow-ups

  • displayxr-common has the same assumption: initial-rect sizing and landing check, drag-lattice map. That is in a separate PR. Once it is tagged, the test-app pin can move.
  • The XR_DXR_weave present-owner path takes its logical scale from the client (weave_set_window_geometry), so the browser's own use of the extension payload needs the same audit (separate repo).

🤖 Generated with Claude Code

…nitor scale, in mutter's PHYSICAL layout

mutter has two monitor layout modes. In LOGICAL (fractional scaling;
mutter 50's default) the stage is logical px and device px = stage x the
monitor scale. In PHYSICAL (Ubuntu 24.04 / GNOME 46 at an integer scale,
out of the box) the stage already is device px: a 3840x2160 monitor at
200 % is a 3840x2160 stage rect with scale 2. comp_vk_native_wl_geom
always multiplied the window-geometry extension's rects by the monitor
scale, so in PHYSICAL it read the panel as a 7680x4320 output, decided
the window was not on the panel, and presented flat 2D with the lens off
(NOT_1TO1) at any scale above 100 %.

Reproduced on a private headless mutter 50.1 (3840x2160 virtual monitor,
gdctl --layout-mode physical --scale 2, cube_handle_vk_linux windowed
on sim-display):

  wl_geom: monitor scale 2.0000 — window content logical 0,180
  3840x2160 on a 3840x2160 logical monitor converts to DEVICE 0,360
  7680x4320 on a 7680x4320 px monitor
  NOT_1TO1: presenting 2D (no weave) — this surface is not on the 3D
  panel. buffer 3840x2160 px, surface 7680x4320 px on a 7680x4320 px
  output, 3D panel 3840x2160 px.

The same run in LOGICAL at 200 % converts correctly.

Runtime: the factor is now resolved per window by
u_wl_stage_to_device_scale() (new u_wayland_layout.h, kept out of the
u_wayland_geom.h that displayxr-common vendors byte-for-byte): the
publisher's monitor.device_scale, else its layout_mode, else mutter's own
layout-mode from org.gnome.Mutter.DisplayConfig.GetCurrentState (for an
extension older than version 11, e.g. the one a session keeps running
until logout after a package upgrade; refreshed on MonitorsChanged), else
the monitor scale as before. wr.scale carries the same factor, so the
drop-time phase snap searches over the right device positions. One WARN
names PHYSICAL and its source.

Extension version 11: the snapshot gains a top-level layout_mode and
monitor.device_scale, read from the stage view scales (any view scale
other than 1 means LOGICAL; every view at 1 with a scaled monitor means
PHYSICAL). No existing field changes meaning, so the schema stays
version 1. The drag-lattice choice and the stamp audit weigh device px by
the same factor. The move-sync tag decode needs no change: subsurface
actor positions are in surface units in both modes (measured).

Verified headless (sim-display, windowed 1600x900): PHYSICAL 200 % ->
DEVICE 1120,720 1600x900 on 3840x2160, present origin set, no NOT_1TO1,
with both the v11 extension and the installed v10 one (Mutter fallback);
LOGICAL 100/125/150/166.67/200 % all land on the same device rect;
fullscreen at PHYSICAL 200 % weaves 1:1.

Tests: tests_aux_wayland_geom (both modes, every offered scale, the
reported failure pinned); scripts/test_gnome_extension_stage_scale.js
(new, in lint.yml).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@dfattal
dfattal requested a review from a team as a code owner October 2, 2026 05:59
@dfattal
dfattal merged commit e3af25f into main Oct 2, 2026
40 checks passed
@dfattal
dfattal deleted the fix/wayland-physical-layout-mode branch October 2, 2026 07: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