swayfx-mosaic/docs/design/STEP20C_VERTICAL_OUTPUT_NESTED_GRAB_PROBE.md

22 KiB
Raw Permalink Blame History

STEP20C vertical-output nested valid-grab probe

Status: EXPERIMENTAL / FEASIBLE for the sampled vertical matrix. This protocol was written before adapting the preserved fixtures; measurements were appended as the experiments ran. It does not authorize production clipping, an offscreen-popup UX policy, or multi-output support. The canonical architecture, UX, limitations and roadmap retain authority. The horizontal nested-grab probe is historical evidence and remains byte-identical.

Hypotheses and compared systems

Unmodified SwayFX in splith (A) and private SceneFX/Mosaic in splith (B) should render and route a truly overflowing nested popup into the lower output. The guarded private binary in mosaic (C) should retain visible popup interaction above the boundary and omit popup pixels and hits below it. The same two real pointer-button serials must create A and B in every case. An outside click may dismiss both under a valid grab without delivering a button to a clipped popup. A compositor with no measured overflow cannot qualify the clip hypothesis.

Geometry and coordinate contract

Request HEADLESS-1 (0,0) 800×600 and HEADLESS-2 (0,600) 800×600. Workspace 1 and its tiled Mosaic owner belong to HEADLESS-1; workspace 2 hosts ordinary wev. Record get_outputs before using any coordinate. The global logical rectangle is [0,800) × [0,1200) only if IPC confirms the requested positions, resolution, scale and transform. The Mosaic viewport is the applied workspace/output intersection on HEADLESS-1; row scroll_x translates the tiled scene horizontally and does not change Y.

The virtual pointer's absolute request uses global-layout extents (800,1200) vertically and (1600,600) for the horizontal regression. The historical injector is hard-coded to (1600,600) and must remain unchanged. The new injector validates every requested point against its configured extents, logs request and button completion, and stays alive through each case. An injected coordinate is a request, not proof of the cursor's actual target: correlate it with the client's wl_pointer.enter, motion and button local coordinates, the lower-output wev events, and screenshots. Record a mismatch as an injection/targeting issue before making a compositor claim.

XDG positioner anchor rectangles are relative to their parent surface: A to the toplevel, B to A. xdg_popup.configure reports a popup rectangle relative to that parent. A candidate global popup rectangle is the applied toplevel content origin plus A's configured offset, and for B additionally plus B's configured offset. Verify that conversion against captured pixels and pointer-local coordinates; the IPC owner rect is logical and its X coordinate alone is insufficient after Mosaic camera translation. The root popup scene is separately positioned in the global popup layer, with its owner-derived clip translated into local scene coordinates.

SwayFX calls wlr_xdg_popup_unconstrain_from_box on initial popup commit and reposition using the owner's output box expressed in toplevel coordinates. The preserved client's positioner sets anchor and gravity but no constraint adjustment; XDG Shell specifies default none. Do not infer the final placement from the requested anchor: retain both ROLE_CONFIGURE rectangles and pixels. If an attempted crossing is flipped, slid, resized or otherwise entirely within HEADLESS-1, classify that case INCONCLUSIVE and try a bounded alternative placement or size in the private client fixture. Never force a compositor production position.

XDG Shell also requires the positioner's anchor rectangle to remain inside the parent's effective window geometry. A review after the first 21-run campaign found that the copied historical toplevel buffer was only 320 px high while the new A anchors were at y=350 and y=480. No protocol error was logged, but this violates that specification. Those initial overflow, hidden-owner and toggle measurements are INCONCLUSIVE as XDG compliance evidence. The private client was corrected to use a 575 px toplevel buffer; the observed effective get_tree window geometry became 420×575, enclosing both A anchors at y=350/480. B's y=155/80 anchors remain within A's 170 px height. The full 21-run matrix below was repeated from that corrected source. The historical source and compositor patches remain unchanged.

Planned matrix and acceptance

Scenario Candidate placement Required observations Status
Two valid grabs, both fully visible A and B high in HEADLESS-1 Distinct parents, button serials, configure/ack/map, A/B clicks, no lower-output pixels PASS at sampled points
B crosses y=600 A above boundary, B near its bottom Control A and B show B below; Mosaic shows none there and B still works above PASS at sampled points after corrected replay
A and B cross y=600 A near toplevel bottom, B within A Separate A/B lower pixels in controls; separate upper hits, no lower popup hits in Mosaic PASS at sampled points after corrected replay
Below-boundary input Point chosen from the actual control B/A pixels Separate move, left and right; popup local events versus wev and popup_done PASS at sampled points after corrected replay
Owner hidden and returned scroll_x 400→1280→400 Same owner and popup IDs, applied scene/clip evidence, no hidden pixels/hits, same mapped objects return PASS at sampled points after corrected replay
Hidden owner clicked separately Same, with an outside click while hidden No hidden popup button; record dismissal and absence of return after it PASS at sampled points after corrected replay
Return to splith with popups open Candidate crossing still mapped Lower pixels and popup target return without prior dismissal PASS at sampled points after corrected replay
Horizontal regression (0,0) and (800,0) with historical B crossing Valid grabs, original left/right pixel and pointer observations PASS at sampled points

For each A/B/C case, save effective output and workspace IPC JSON, owner identity/rectangle and camera, WAYLAND_DEBUG=1 client protocol, configured popup geometry, compositor/scene logs, injection log, wev events, both output PNGs and the chosen pixel/hit points. Treat visual visibility, pointer delivery, grab dismissal and coordinate agreement as four separate observations. A zero runner exit alone is insufficient. The first boundary point must come from the actual control popup pixels below y=600, never from the old horizontal (900,190) point. A candidate with no lower-output popup pixels in the control is INCONCLUSIVE for overflow.

FEASIBLE requires real vertical overflow in both standard controls, visual and interactive exclusion at sampled lower points in Mosaic, valid grabs, and no blocking standard-layout regression. PARTIALLY_FEASIBLE records a narrower demonstrated subset. A reproducible private compositor pixel/hit leak, protocol violation or blocking standard regression is BLOCKED. INCONCLUSIVE means the fixture cannot establish overflow or cannot distinguish targeting from compositor behavior. Individual unrun cells remain NOT TESTED.

Laboratory and remaining boundaries

Build from the prior pinned Mosaic and SceneFX commits, applying the same private patches in the same order. Keep all build trees, protocol output, short mode-0700 socket directories, captures and logs outside Git. Clear inherited desktop Wayland/IPC variables and pass every IPC command its discovered private socket. Load private SceneFX only in the private compositor process. Start and terminate only identified child process groups; preserve the log and actual exit status on startup failure. No test may use the live desktop socket or replace installed libraries.

This vertical popup experiment does not qualify tiled-window Y overflow, other output geometries, scaling, rotation, migration, transaction-generation agreement, complete damage/stale-pixel coverage, effects or SceneFX ABI and package maintenance. The previous horizontal probe is a control, not a production precedent. STEP20C remains NEXT and cross-output Mosaic remains NOT YET SUPPORTED regardless of a narrow positive result.

Observations and retained evidence

The first fresh unmodified SwayFX visible control confirmed IPC output rectangles (0,0,800,600) and (0,600,800,600), scale 1.0, transform normal. A's grab used received TOPLEVEL button serial 61; B's used received POPUP_A button serial 66. Both role configure/ack/map sequences completed, and separate right clicks reached A and B without dismissal.

The first v_b_cross control configured A at (50,300,250,170) relative to TOPLEVEL and B at (20,155,170,130) relative to A. With the applied owner content origin estimated at (202,25), B's candidate global rectangle was (272,480,170,130). The HEADLESS-2 open capture contained 1,700 exact B-green pixels, and the requested (312,605) yielded a B-local pointer motion (40,125) and a right press/release on B. The corresponding HEADLESS-1 frame had 20,400 B-green pixels. Thus the standard control really crossed y=600; this is not yet a Mosaic clipping result. Artifacts: /tmp/swayfx-mosaic-nested-grab-YNqcOBkl/run-vertical-control-{visible,b_cross}/. The original horizontal reports and fixtures remain unchanged.

The first A/B/C v_b_cross comparison found the same 1,700 lower-output B-green pixels in unmodified and private splith, versus 0 in guarded Mosaic. Above the boundary, B received a right press/release in all three. At (312,605) in the unmodified control and (352,605) in Mosaic, the control B received B-local (40,125) and right press/release; Mosaic B received no lower button and the grab dismissed B then A. However, wev's actual IPC rectangle begins at y=625, so this first lower point was over the destination workspace decoration. Ordinary-client delivery is PARTIAL at this point. The initial Mosaic visible run also failed solely because the runner wrote a floating-point text coordinate (534.0) to the integer injector; its INVALID_INJECTION log is retained with suffix -initial-invalid. The runner now validates and converts integral values, and a fresh visible run delivered separate right clicks to A and B. The next bounded placement increases B's measured overflow to place a lower sample inside the actual wev rectangle. These fixture findings are not classified as compositor defects.

In a rebuilt laboratory using the revised v_b_cross placement, both standard configurations configured A (50,350,250,170) and B (20,155,170,130); B's estimated global rectangle was (272,530,170,130). Each standard lower-output capture contained 10,200 B-green pixels, and each delivered the (312,640) right press/release to B at local (40,110). Guarded Mosaic configured the same relative rectangles; B's estimated global rectangle was (312,530,170,130). Its lower capture contained 0 B-green pixels, while the left capture retained 11,900. B received the upper right press/release but no lower button. The ordinary wev at (352,640) received the lower right press/release; the legitimate grab subsequently emitted popup_done B then A. This is a measured B-only vertical visual/input clip at these points. Artifacts: /tmp/swayfx-mosaic-nested-grab-gbU3A8Mk/run-vertical-{control,private_standard,private_mosaic}-b_cross/.

The three v_both_cross runs configured A (50,480,250,170) and B (20,80,170,130). Both standard lower frames contained 6,000 A-yellow and 19,550 B-green pixels; private Mosaic contained 0 of either color there while retaining 21,200 A-yellow and 2,550 B-green above. Separate upper right clicks reached A and B in all three. At the lower B point, standard layouts delivered right press/release to B, while Mosaic delivered it to wev and dismissed B then A without a lower popup button. This tests both levels visually and their separate upper hits. A separate lower A hit is measured in the below_a runs described next. Artifacts: /tmp/swayfx-mosaic-nested-grab-gbU3A8Mk/run-vertical-{control,private_standard,private_mosaic}-both_cross/.

Separate lower-input runs at y=640 resolve the hit target: in standard layouts, B received the left press/release at x=312, and A received the right press/release at x=260 outside B. In Mosaic, neither lower click reached a popup; wev received left at x=352 and right at x=300, and the grab dismissed B then A. Upper B and A clicks remained delivered before those lower samples. These are separate runs because an outside click can legitimately dismiss the stack. Artifacts: the same laboratory's run-vertical-{control,private_standard,private_mosaic}-{below_left,below_a}/ directories. The lower right B comparison is the b_cross run above.

With B crossing in guarded Mosaic, hidden kept IPC owner ID 8 and scroll_x 400→1280→400. The private scene log reported applied owner (-640,0,640,600) and viewport (0,0,800,600). A/B exact left-output pixel counts were 39,950/11,900 at open, 0/0 while hidden and on hidden hover, then 39,950/11,900 after return. Each popup mapped once; neither received popup_done before return. In the separate hidden_click run, no hidden popup button was delivered, popup_done occurred B then A, and neither popup returned after camera restoration. The toggle run kept both mapped through layout splith; B-green pixels on HEADLESS-2 changed from 0 to 10,200, and B received a right press/release at (312,640) without prior dismissal. Artifacts: /tmp/swayfx-mosaic-nested-grab-gbU3A8Mk/run-vertical-private_mosaic-{hidden,hidden_click,toggle}/.

The adapted pointer also replayed the historical horizontal b_cross geometry in all three configurations using (1600,600) extents. The two standard lower-output captures had 15,860 B-green pixels and B received the right-output right press/release. Guarded Mosaic had 0 B-green pixels there, 17,940 on HEADLESS-1, no lower B button, and a right press/release on wev; B then A were dismissed. All three used two distinct real grab serials and mapped both popups. This matches the preserved horizontal result at the historical points. Artifacts: /tmp/swayfx-mosaic-nested-grab-gbU3A8Mk/run-horizontal-{control,private_standard,private_mosaic}-b_cross/.

The main fresh laboratory contains 18 validated vertical and 3 horizontal run directories. Its artifacts-sha256.txt lists 669 files; the manifest SHA-256 is 91830d91906039a5c9205f5b05e14f347969d516e2a5f13313969c9a78bf14a9 and sha256sum -c returned 669 OK, 0 failures. These are temporary artifacts outside Git; the exact measured observations remain in this report if that laboratory is later removed. The retained first shallow crossing and rejected floating-coordinate injection are in the earlier temporary lab /tmp/swayfx-mosaic-nested-grab-YNqcOBkl/, not included in this manifest. The first full 21-run laboratory /tmp/swayfx-mosaic-nested-grab-yCtCIiUB/ is also excluded from the corrected verdict and manifest: its 320 px toplevel left the vertical A anchors outside the parent's effective window geometry. The corrected fixture's SHA-256 values and rebuild commands are in its README.

The sampled result is FEASIBLE for the vertical nested-grab matrix above. The standard controls establish actual Y overflow; the private Mosaic build clips measured A/B pixels and input below y=600 while preserving upper interaction, hidden-owner camera return and standard-layout behavior. This is not complete damage correctness, continuous clip coverage between samples, all tiled-content Y overflow, a transaction-generation proof, or a SceneFX ABI/package decision. Output scaling, rotation, negative origins, resize/migration, decoration/shadow/blur/saved-buffer coverage and live deployment remain NOT TESTED here. The next smallest distinct gate is a separate negative-origin output geometry comparison with the same hierarchy and direct unmodified/standard controls; it was not started in this probe. STEP20C remains NEXT and multi-output Mosaic remains NOT YET SUPPORTED.

Validation and provenance

Independent freeze replay

A fresh local Git clone at /tmp/mvg-freeze-clone-37QJ9Bq3/ received only the five reviewed vertical fixture source files from this worktree. Its prepare.sh reconstructed the pinned SceneFX and Mosaic sources, applied the three unchanged private compositor/library patches in their documented order and the two client-only patches to temporary copies, regenerated both Wayland protocols, and rebuilt the private libraries, compositor and C17 clients. The new laboratory is /tmp/swayfx-mosaic-nested-grab-5MXNLRED/; its build log is /tmp/mvg-prepare-DCyxEBUY. vertical-source-sha256.txt verifies all six new patch/script and patched-client entries against those sources.

The replay executed 17 scenarios: b_cross, both_cross, below_left and below_a in each of unmodified SwayFX splith, private splith and private mosaic; private Mosaic hidden and toggle; and b_cross in all three configurations with the original horizontal output arrangement. All 17 runners exited zero, but the verdict also checks their client protocol, pointer and wev logs, effective IPC geometry, scene log and both captures. Every vertical run reported outputs (0,0,800,600) and (0,600,800,600) with scale 1 and normal transform; the pointer extent was 800×1200. The effective toplevel get_tree geometry was 420×575. For B-only crossing, A configured (50,350,250,170) and B (20,155,170,130); for both-level crossing, A configured (50,480,250,170) and B (20,80,170,130). Each A anchor lies within the 575-pixel toplevel and each B anchor within A's 170-pixel height. Two distinct received wl_pointer.button press serials matched the A and B GRAB_REQUEST serials, and both configure/ack/map chains completed without logged XDG protocol error.

At open, both new standard B-crossing runs again contained 10,200 B-green pixels on HEADLESS-2, versus 0 in private Mosaic. Both-level crossing again contained 6,000 A-yellow and 19,550 B-green pixels on HEADLESS-2 in each standard control, versus 0/0 in Mosaic. The controls delivered the measured lower B and A presses to the corresponding popup; Mosaic delivered those presses to wev, with no lower popup press, after upper popup interaction. popup_done followed B then A in the outside-click cases. This dismissal is distinct from a hit on a clipped popup. In the new hidden run, owner ID 8 and its logical rectangle stayed fixed while scroll_x changed 400→1280→400; one mapped A and B each remained the same XDG objects, with no popup_done. Their left-output color counts were 39,950/11,900 → 0/0 → 39,950/11,900. The scene log recorded applied owner (-640,0,640,600) against viewport (0,0,800,600). With both still open, toggle restored 10,200 B-green pixels below the boundary and a button on B after layout splith. The three horizontal runs repeated the historical standard 15,860 B-green pixels on HEADLESS-2 versus 0 in private Mosaic, with the same popup-versus-wev button distinction. No measured divergence was found between this replay and the corrected historical campaign.

The new lab's freeze-artifacts-sha256.txt contains 574 run/command evidence files. Its SHA-256 is c5666bc236dbfff52996295fee15c378af0baa2d657ec76ed4c7df172889337d; sha256sum -c verified 574/574 entries. The files remain temporary and outside Git. An initial attempt using a source-only git archive failed before any client or scientific scenario because the reused preparation needs the pinned commit in a Git object database; its failed build log is /tmp/mvg-prepare-tZOrcirM, and no completed LAB= was reported. The full local clone resolved that fixture setup error. The README now states this prerequisite. No scientific fixture or private compositor patch was changed during freeze.

Original corrected campaign

The final 21-case replay used one immutable set of private compositor/client sources and the fixture run.py SHA-256 b7dc778c01d9e8e1f058c1aa2bb0e3e0e8dfa6ad1ba8e5af122a0503cd7eef96. All 21 runs exited normally; independent checks of client.log, wev.log, pointer.log, IPC, exact-color masks and ordered XDG events passed. Their measurements.json records configured rectangles and sampling points; exit-status.json records the actual compositor/child results. Each output geometry was verified by IPC before injecting input. All 21 short private runtime directories were removed and no probe process remained.

The private build used SceneFX 37ccd723bef49e6891156ffafce8f549f01446cc (0.5.0), Mosaic cd550382b311efdbfe3ed892f4e1d281d852e754, wlroots 0.20.2, Wayland client 1.26.0 and system control SwayFX 0.6. ldd resolved the private Mosaic binary to the laboratory libscenefx-0.5.so and system libwlroots-0.20.so. The three unchanged private compositor/library patches and two new client-only patches applied with git apply --check; vertical-source-sha256.txt verified 6/6 source entries. Private SceneFX/Mosaic builds and generated protocol files succeeded. Shell and Python syntax checks, C17 syntax checks for both clients, the Mosaic Meson model test (1/1), documentation links, git diff --check and the 669-artifact manifest passed. An initial version query used the wrong pkg-config name (scenefx); the corrected scenefx-0.5-uninstalled query returned 0.5.0. This command error did not affect a build or run.

No production Mosaic source, production patch, PKGBUILD, installed library, UX contract or historical fixture was changed. No commit or push belongs to this experiment.