21 KiB
STEP20C negative-origin nested-grab probe
Status: EXPERIMENTAL / FEASIBLE for the measured negative-origin matrix. This is a disposable feasibility test, not a production clipping change or UX decision. The canonical architecture, UX, limitations and roadmap retain authority.
Problem and hypotheses (recorded before fixture adaptation)
An output at global x=-800 makes global scene and pointer coordinates signed.
The virtual-pointer protocol instead transmits unsigned absolute coordinates
with unsigned extents. Passing a negative global X directly to that protocol
would wrap it. Hypothesis H1: with two adjacent 800×600 outputs, wlroots
maps normalized pointer X through the layout bounding box; therefore
injected_x=global_x-min_x=global_x+800 with extent 1600 reaches the
requested global point. This must be confirmed from the pinned wlroots path
and from client pointer events on both outputs before any clipping result is
valid. A protocol-level injection acknowledgement alone is insufficient.
Hypothesis H2: the disposable SceneFX subtree clip, owner-derived popup
clip and hidden-owner guard use signed applied scene positions correctly when
the viewport begins at -800. The control layouts must first demonstrate
real pixels and button delivery beyond x=0; configured rectangles alone do
not prove a crossing. Hypothesis H3: changing Mosaic back to splith
removes the private clip while both XDG popups remain mapped.
Coordinate contract and requested geometry
| Quantity | HEADLESS-1 | HEADLESS-2 |
|---|---|---|
| Requested global origin | (-800,0) |
(0,0) |
| Requested size, scale, transform | 800×600, 1, normal |
800×600, 1, normal |
| Global X interval | [-800,0) |
[0,800) |
| Local X | global_x+800 |
global_x |
The requested layout bounds are [-800,800) × [0,600), with border x=0.
get_outputs is authoritative. If the compositor normalizes these origins,
the negative-origin test is INCONCLUSIVE and the actual rectangles must
still be retained. Workspace 1 and the tiled popup owner belong to
HEADLESS-1; workspace 2 holds wev on HEADLESS-2.
For a requested global point (-100,190), HEADLESS-1 local X is 700 and
candidate virtual-pointer coordinates are (700,190) with extent 1600×600.
For (100,190), HEADLESS-2 local X is 100 and the candidate transmitted X
is 900. The runner must log requested signed global point, layout minimum,
transmitted normalized point, extents, reached output, client-local motion and
button events. It must reject out-of-bounds and nonintegral injection values.
Mosaic row order and logical owner IPC rectangle remain distinct from camera
and applied scene placement. For each sample, record logical rect,
window_rect, scroll_x, applied owner/popup scene positions, SceneFX clip,
XDG role configure rectangles and actual captures. The candidate applied
content X is owner.rect.x - scroll_x + owner.window_rect.x in Mosaic;
the compositor scene log and captured pixels must verify it. Popup A's XDG
configured rectangle is relative to the toplevel surface; B's is relative
to A's surface. Their resulting global rectangles are estimates until scene
position and pixels corroborate them. XDG anchor rectangles must lie within
their effective parent window geometry.
Experimental method and acceptance
Reuse the historical nested client, the vertical fixture's parameterized
pointer and the same three pinned private SceneFX/Mosaic patches, including
the hidden-owner guard. Adapt only the new fixture directory. Run identical
clients in (A) unmodified SwayFX splith, (B) private compositor splith,
and (C) private compositor mosaic, all with verified negative output origins.
Use isolated runtimes and sockets, swaymsg -s, two-output captures, IPC,
WAYLAND_DEBUG=1, compositor logs and structured measurements. A separate
positive-origin horizontal subset checks regression. No installed library,
daily session or production source is touched.
- Validate signed pointer targeting on both outputs, including round trip and left/right buttons. Failed targeting invalidates later graphics tests.
- Open two visible popups: TOPLEVEL button serial grabs A, then A button serial grabs B; verify distinct actual serials, parent XDG identities, configure/ack/map order and interaction with each surface.
- Test B crossing
x=0, then A and B crossing. Both standard controls must show actual popup pixels and B/A button targets on HEADLESS-2. Mosaic must preserve visible left portions and suppress right popup pixels/hits, with ordinary-client events interpreted under normal XDG grab semantics. - In separate runs, test outside-click dismissal, fully hidden applied owner
with no click and with a click, and
layout splithwhile popups remain open. - Replay a horizontal positive-origin subset with the same adapted clients and injector. Derive samples from actual configured/scene geometry and capture data, never from assumed historic positions.
Each scenario retains output JSON, signed coordinate conversions, pointer
events, owner IPC/Mosaic state, XDG configuration and identities, scene clips,
two captures per stage, lifecycle events, process exit codes and a
measurements.json. Rendering, hit testing, XDG grab, coordinate transform
and lifecycle verdicts are separate. A screenshot alone or a protocol send
alone cannot establish a pass. Keep failures and inconclusive runs visible.
The probe is FEASIBLE only if negative origins and control crossing are real, Mosaic excludes measured clipped pixels/hits while preserving visible interaction, and private standard layout has no blocking regression. It is PARTIALLY_FEASIBLE for demonstrated core behavior with matrix gaps, BLOCKED for reproducible rendering/input leak, XDG violation or blocking standard regression, and INCONCLUSIVE if geometry, crossing or pointer events cannot be established. These labels apply only to this probe.
Initial observations and correction history
The first unmodified SwayFX pointer run recorded get_outputs with active
HEADLESS-1 (-800,0,800,600) and HEADLESS-2 (0,0,800,600), both scale 1
and transform normal. Thus SwayFX did apply the requested negative origin.
The fixture transmitted 302 for global -498 and 830 for global 30.
The owner received wl_pointer.enter with surface-local X 100, then 105
on the return trip; wev received enters with local X 28, then 33
(its surface starts inside the container). This establishes preliminary
two-output targeting, subject to full button validation. Evidence is retained
in run-negative-control-pointer-initial/ under the new laboratory.
The first calibration checker falsely required POINTER_MOTION lines. An
inter-output move produces wl_pointer.enter at the destination instead;
both clients logged those enters. The checker now accepts enter or motion.
That first run exited with a fixture RuntimeError after its captures; it is
not classified as a compositor or clipping failure.
The actual wlroots 0.20.2 path confirms H1 at the source level:
types/wlr_virtual_pointer_v1.c
forms normalized x / x_extent, while
types/wlr_cursor.c
converts that fraction to mapping.width * x + mapping.x using the output
layout box when no device mapping overrides it. Sway's absolute-motion path
uses the wlroots cursor. With box X -800, width 1600, and transmitted X
global_x+800, the nominal reconstructed global point is global_x.
Client events remain the empirical check for device mapping and targeting.
The completed pointer runs in unmodified SwayFX, private splith, and
private Mosaic each reached both outputs on two round trips. TOPLEVEL received
right and left press/release (button=273/272); wev received left and
right press/release. In the control, requested global (-498,150) reached
TOPLEVEL local (100,125) and requested (30,80) reached wev local
(28,55); the approximately 2 px X and 25 px Y differences are the client
surface inset, not an output-origin shift. Private splith repeated those
coordinates, and private Mosaic used global (-458,150) for TOPLEVEL local
(100,125) after its camera translation. All protocol X values were
nonnegative and inside the 1600×600 extent.
All three negative-origin visible runs mapped A and B and kept them open
through separate right clicks. A's role configure was (50,80,250,170) and
B's (20,60,170,130) relative to its parent. Popup pixels were confined to
HEADLESS-1 in this reference placement; HEADLESS-2 contained zero exact A
yellow or B green pixels.
The negative-origin b_cross controls both configured B at
(-178,165,300,130) after applied-owner translation and measured 15,860
exact B-green pixels on HEADLESS-2 at the open capture. Private Mosaic
configured B at (-138,165,300,130) and measured 0 B-green pixels on
HEADLESS-2 while retaining 17,940 on HEADLESS-1. These are preliminary
render measurements; button traces and the remaining matrix are under review.
At the B crossing point, both standard controls delivered two B right-button
events and retained 15,860 B-green pixels beyond x=0; private Mosaic
delivered the left visible B button, then delivered the right-output button
to wev, with no right-output B button. Its legitimate outside click then
caused popup_done B before A. In the both_cross placement, both standard
controls had 6,120 A-yellow and 27,560 B-green pixels on HEADLESS-2;
private Mosaic had 0 of each there, while retaining 30,080 A-yellow
and 6,240 B-green pixels on HEADLESS-1. A separate a_cross run is used
for A's right-output button, since a first outside click can dismiss the
grabbed hierarchy.
Comparative results
All three compositors used HEADLESS-1 (-800,0,800,600) and HEADLESS-2
(0,0,800,600) at scale 1, transform normal. The same nested client used
TOPLEVEL XDG object 9, A object 12 and B object 15 in each run; A named
TOPLEVEL as parent, B named A. In b_cross, both standard controls took real
TOPLEVEL left-button serial 69 to grab A and A left-button serial 74
to grab B. Private Mosaic used 63 and 68. Each GRAB_REQUEST used
the exact target event's serial; configure, xdg_surface ack and MAP followed
in order. No protocol error or immediate grab denial occurred. The anchored
rectangles stay within their effective parent geometry: the horizontal
b_cross A anchor X 200 is inside the standard top buffer width 420,
and B anchor X 220 is inside A's width 300; both_cross A anchor X 350
is likewise inside 420, B anchor X 160 inside A's width 350.
| Scenario | Unmodified SwayFX splith |
Private splith |
Private Mosaic | Measured distinction |
|---|---|---|---|---|
| Pointer reference | PASS | PASS | PASS | Signed global points reached both clients twice; left/right press and release on both outputs. |
| A/B fully visible | PASS | PASS | PASS | Two grabs, mapped distinct surfaces, A/B right clicks, no unexpected dismissal. |
B crosses x=0 |
PASS | PASS | PASS | Standard B right pixels 15,860 and B button; Mosaic right pixels 0, B left button, wev right button and normal outside dismissal. |
A and B cross x=0 |
PASS | PASS | PASS | Standard right A/B pixels 6,120/27,560; Mosaic 0/0 with separate visible A/B clicks. |
| A crossing hit in separate run | PASS | PASS | PASS | Standard right A button; Mosaic no right A button, wev button and normal dismissal. |
| Outside click and lifecycle | PASS | PASS | PASS | popup_done B then A; client destroys B then A; ordinary client receives left button. |
| Owner fully hidden, no click | N/A | N/A | PASS | Applied scene owner (-1440,0,640,600) does not intersect viewport (-800,0,800,600); A/B pixels 0 then return with same XDG IDs. |
| Owner hidden with click | N/A | N/A | PASS | No hidden A/B button; B then A dismissed; pixels stay absent after camera return. |
Mosaic → splith with A/B open |
N/A | N/A | PASS | Right B pixels return to 15,860; B receives right button at (20,195) before any popup_done. |
| Positive horizontal regression | PASS | PASS | PASS | Standard B right pixels 15,860; Mosaic 0 with the same button-routing distinction as the historical horizontal probe. |
The B-crossing point was global (20,195) on HEADLESS-2. In standard
controls its open-capture RGB was B green (32,208,160) and its right click
went to B. In private Mosaic that point was ordinary background
(238,238,238), and its right click went to wev; B received no button
there. The corresponding Mosaic visible B point (-98,195) was green and
delivered a B right button. For A, the separate standard crossing point
(87,125) was A yellow (224,192,32) and delivered an A button; Mosaic's
scene-adjusted point (127,125) was ordinary background and delivered no A
button, while visible A/B points remained interactive. Ordinary-client
delivery and popup dismissal are distinct observations; dismissal alone is
never counted as a clipped-surface hit.
The private PROBE_POPUP_CLIP log recorded clip local (-242,-25,800,600)
in the sampled Mosaic runs. Relative to viewport origin (-800,0), this
gives applied root popup-tree scene origin (-558,25); combining the logged
XDG configure offsets gives A (-358,105,300,170) and B
(-138,165,300,130) in b_cross. These match the independently calculated
owner/window/camera positions and the captured crossing colors. In
both_cross, A is (-208,105,350,170) and B
(-48,165,300,130). A negative global origin is therefore present in both
the applied clip and popup surface geometry, not only in an IPC label.
For hidden owner, the compositor logged PROBE_POPUP_HIDDEN_OWNER with
applied (-1440,0,640,600) versus viewport (-800,0,800,600), proving
empty intersection from applied scene state. The no-click run's left A/B
exact pixel counts were 42,200/17,940 → 0/0 → 42,200/17,940. There
was no popup_done before return and no new XDG CREATE or MAP; XDG IDs
remained A 12 and B 15.
The IPC owner stayed container ID 8 and focused=true in open, hidden,
hidden-hover and returned snapshots; its logical rectangle stayed
(-160,25,640,575) while the applied scene position changed with camera.
In the separate hidden-click run, neither popup
received the right button; B then A received popup_done, were destroyed
child first and did not return. With both still open, switching to splith
restored the standard right B-green count 15,860 and a B right-button
press/release with no earlier dismissal.
The positive-origin regression used HEADLESS-1 (0,0) and HEADLESS-2
(800,0) with the same compiled clients and pointer. Both standard controls
again produced 15,860 B-green pixels on HEADLESS-2 and delivered the
right-output button to B. Private Mosaic produced 0 and delivered the
button to wev, followed by normal B/A dismissal. Counts happen to match
the historical horizontal probe after checking the actual applied placement;
the test did not assume their equality.
Scope of conclusion
This negative-origin, two-output, nested-valid-grab experiment is FEASIBLE for the measured placements and event points. It establishes correct signed-to-absolute pointer conversion in the observed wlroots path, real control crossing, sampled private clipping/hit behavior, hidden-owner guard behavior and standard-layout restoration. It does not prove continuous pixel coverage between samples or define universal offscreen-popup dismissal UX. Scaling, rotation, output migration/resize, complete damage/effects, transaction generations and ABI/package maintenance remain NOT TESTED. STEP20C remains NEXT and production adjacent-output clipping remains NOT YET SUPPORTED.
Independent freeze replay (2026-10-11)
A fresh laboratory, /tmp/swayfx-mosaic-nested-grab-1DzZQbcH/, was built by
the preserved prepare.sh from full Git checkouts, rather than an archive.
Its build log is /tmp/mno-prepare-ltr6sSlW. Fourteen new run directories
cover pointer targeting on both outputs in all three configurations,
negative-origin B crossing and A+B crossing in all three, private Mosaic
hidden-owner return and splith return, and positive-origin B crossing in
all three. analyze.py decoded the new PNGs. The fresh output JSON again
reported (-800,0,800,600) and (0,0,800,600) for negative-origin runs.
In each pointer run, the owner received destination wl_pointer.enter at
local (100,125) and (105,135), and wev received enters at (28,55)
and (33,60); both clients also logged press/release events. These client
events, not injector acknowledgements, validate signed-global targeting.
New control and private-standard B-crossing captures each contain 15,860
B-green pixels on HEADLESS-2 and two B button presses. Private Mosaic again
contains zero B-green pixels there, preserves 17,940 on HEADLESS-1, delivers
the visible B press, and sends the clipped-side press to wev, followed by
normal B/A popup_done. For A+B crossing, both standard controls again
contain 6,120 A-yellow and 27,560 B-green right-output pixels; private
Mosaic contains zero of either there while retaining 30,080/6,240 on the
left. The fresh logs show distinct A/B XDG objects and actual A/B grab
serials (69/74 in the control B run; 63/68 in private Mosaic), with matching
button serial, configure/ack/map order and no recorded protocol error.
Serials varied in another control run, as expected; their numeric values are
run evidence, not a protocol constant.
In the new no-click hidden-owner run, scroll_x moved 400 → 1280 → 400,
both popup colors changed 42,200/17,940 → 0/0 → 42,200/17,940 on
HEADLESS-1, the same XDG objects remained mapped, and no popup_done
preceded return. In the new layout-return run, splith restored 15,860
B-green pixels on HEADLESS-2 and a B press without prior dismissal. The
positive-origin B-crossing subset reproduced the same control/private
distinction. These are sampled stabilized observations: the replay did not
capture every presented frame or establish complete damage coverage.
Reproduction, provenance and validation
The retained laboratory is
/tmp/swayfx-mosaic-nested-grab-NWy4A23x/; its preparation log is
/tmp/mno-prepare-4FC2ZHqF. The source-only
fixture documents a
fresh replay from a full Git checkout. The three unchanged private patches
applied with git apply --check to Mosaic
cd550382b311efdbfe3ed892f4e1d281d852e754 and SceneFX
37ccd723bef49e6891156ffafce8f549f01446cc. The historical pointer
extents patch alone adapted a disposable copy of pointer-daemon.c; no new
private patch was needed. The historical nested client was byte-identical.
The environment used /usr/bin/sway 0.6, wlroots 0.20.2, SceneFX 0.5.0
and Wayland client 1.26.0. ldd resolved the private Mosaic executable to
the laboratory libscenefx-0.5.so and system libwlroots-0.20.so.
artifacts-sha256.txt lists 895 run files from the final and retained
intermediate cases; its SHA-256 is
0f3d74cb3477aa24b5e0f6bcf84f736ffcd31280de04b08b4622a761046eb793.
sha256sum -c verified 895/895. negative-origin-source-sha256.txt
lists six source inputs; its SHA-256 is
8cbf6228c0bd317442262265692f4027e41fd82c2e7c7cd22bbb0e03ddaf729d,
and sha256sum -c verified 6/6. Each run directory retains verified
output JSON, signed injections, observed client-local pointer reference,
XDG and WAYLAND_DEBUG=1 traces, IPC trees, scene clips, two-output PNGs,
RGB/count analysis, popup_done order and process exit status. The
intermediate failed pointer-calibration check and its evidence remain under
run-negative-control-pointer-initial; earlier both_cross and toggle
attempts remain under *-initial. The final both_cross runs were replayed
with the settled runner. The first toggle sampled B on the left after
return; the final run explicitly sampled its right-output portion.
Private SceneFX and Mosaic builds passed; the Mosaic Meson model test passed
1/1. C17 syntax checks passed for both client sources against generated
protocol headers. Shell syntax, Python AST parsing without bytecode cache,
document links, git diff --check, source hashes and artifact hashes passed.
An independent structured matrix check passed pointer targeting, both grabs,
crossing colors/buttons, outside dismissal, hidden-owner, layout return and
positive regression. All completed case directories include actual exit
codes; normal runner teardown produced compositor/client 0 and expected
SIGTERM -15 for the persistent pointer and wev. Their private runtime
directories were removed after reaping; no probe process remained. These
validation passes support only the measured scientific conclusion above.
No production Mosaic source, production patch, PKGBUILD, installed library,
historical fixture, UX policy or active session was changed; at the time of
the original experiment nothing had been committed or pushed. The smallest next bounded experiment is repeated
open/close and camera movement at this same negative-origin geometry, with
frame-by-frame stale-pixel and damage observations. It would test a currently
open gate without changing output transform or popup protocol.