# STEP20C valid-grab / hidden-owner fixture (EXPERIMENTAL) This fixture preserves the bounded headless comparison reported in [the STEP20C probe](../../../docs/design/STEP20C_VALID_GRAB_HIDDEN_OWNER_PROBE.md). It is not part of the Mosaic build or package. Its private SceneFX symbols and Mosaic edits are feasibility evidence, not production interfaces. The result is **PARTIALLY_FEASIBLE** at the measured pixels and pointer locations only. ## Provenance and exact inputs | Input | Base / origin | SHA-256 | | --- | --- | --- | | `mosaic-popup-plus-base.patch` | Complete private delta against Mosaic `cd550382b311efdbfe3ed892f4e1d281d852e754` | `d416f6413ea097a48a44d231e50ffba363914f46c8687f0ea2e4dcacd5f31fe9` | | `scenefx-popup-plus-base.patch` | Complete private delta against [SceneFX](https://github.com/wlrfx/scenefx) tag 0.5, `37ccd723bef49e6891156ffafce8f549f01446cc` | `c74c515b98da8f5f637e260e6a3755c7a75bd78d1c39e54a67850f1c383e30e7` | | `hidden-guard.patch` | Incremental private delta after the Mosaic patch | `35785a8f3e863b3c1fd87932d8ff0c2949b03cb04382cc34e1126170968f802b` | | `grab-client.c` | Original valid-serial XDG client from the probe lab | `202afdc5dae0ee4aba570eef4a563f8b298474eb84e33af9510c4c81c05d16e4` | | `pointer-daemon.c` | Original persistent virtual pointer from the probe lab | `a00fcedc38ccb7ca4c25c982d9f38e8bde3168a3833f0c9cf2056e841d720938` | | `run.py` | Original headless runner from the probe lab | `b2aaa90188ea387cb8a69aa69935b56e63f5ce70eb98e74783b0fb3c9236a41f` | | `wlr-virtual-pointer-unstable-v1.xml` | [wlr-protocols](https://github.com/swaywm/wlr-protocols), `b010a03648b88d143236de193bddbfea0c08bc84`, `unstable/` | `3ff6d540be0bc5228195bf072bde42117ea17945a5c2061add5d3cf97d6bb524` | The six lab files above are byte-identical to the originals in `/tmp/swayfx-mosaic-valid-grab-7SA2yO/`; none was adapted. The protocol XML regenerates that lab's `virtual-pointer-client.h` and `virtual-pointer-protocol.c` byte-for-byte. `xdg-shell.xml` comes from the installed `wayland-protocols` package. The pinned pristine SwayFX base is `fd71a6bdc061bd633b488ae7b83e8a6981d22f86`; **do not apply the Mosaic private patch to that base**. The Mosaic commit above already includes the project's prior Mosaic changes. The original environment used SwayFX 0.6 (`swayfx` 0.6-1), SceneFX 0.5 (`scenefx` 0.5-1), wlroots 0.20.2 (`wlroots0.20` 0.20.2-1), `wayland-client` 1.26.0, and the installed `wayland-protocols` XML. Meson, Ninja, `wayland-scanner`, a C17 compiler, `pkg-config`, Python, `kitty`, `swaymsg`, `grim`, `wev`, and `/usr/bin/sway` are required. Package releases and scanner versions may change; record them when reproducing. ## Rebuild in a new temporary laboratory Run from this repository root. The commands copy only source fixtures into a new `mktemp` directory; all compiled files, sockets, logs and captures stay there. Keep `R` for the commands below, then remove it during cleanup. ```sh set -eu R=$(mktemp -d /tmp/swayfx-mosaic-valid-grab-replay-XXXXXXXX) F="$PWD/tooling/probes/step20c-valid-grab" cp "$F/grab-client.c" "$F/pointer-daemon.c" "$F/run.py" "$F"/*.patch \ "$F/wlr-virtual-pointer-unstable-v1.xml" "$R/" mkdir "$R/mosaic" "$R/scenefx" git archive cd550382b311efdbfe3ed892f4e1d281d852e754 | tar -xf - -C "$R/mosaic" git clone --quiet https://github.com/wlrfx/scenefx.git "$R/scenefx-git" git -C "$R/scenefx-git" checkout --quiet --detach 37ccd723bef49e6891156ffafce8f549f01446cc git -C "$R/scenefx-git" archive HEAD | tar -xf - -C "$R/scenefx" (cd "$R/scenefx" && git apply --check "$R/scenefx-popup-plus-base.patch" && git apply "$R/scenefx-popup-plus-base.patch") (cd "$R/mosaic" && git apply --check "$R/mosaic-popup-plus-base.patch" && git apply "$R/mosaic-popup-plus-base.patch") # This is the unguarded baseline. Save it only in the temporary lab. cp "$R/mosaic/sway/desktop/transaction.c" "$R/transaction-before-hidden.c" (cd "$R/mosaic" && git apply --check "$R/hidden-guard.patch" && git apply "$R/hidden-guard.patch") meson setup "$R/scenefx-build" "$R/scenefx" -Dexamples=false -Dwerror=false meson compile -C "$R/scenefx-build" -j 8 PKG_CONFIG_PATH="$R/scenefx-build/meson-uninstalled" meson setup "$R/mosaic-build" "$R/mosaic" \ -Dmosaic-package=true -Ddefault-wallpaper=false -Dswaybar=false -Dswaynag=false \ -Dman-pages=disabled -Dsd-bus-provider=libsystemd -Dwerror=false PKG_CONFIG_PATH="$R/scenefx-build/meson-uninstalled" meson compile -C "$R/mosaic-build" -j 8 wayland-scanner client-header /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml "$R/xdg-shell-client.h" wayland-scanner private-code /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml "$R/xdg-shell-protocol.c" wayland-scanner client-header "$R/wlr-virtual-pointer-unstable-v1.xml" "$R/virtual-pointer-client.h" wayland-scanner private-code "$R/wlr-virtual-pointer-unstable-v1.xml" "$R/virtual-pointer-protocol.c" cc -std=c17 -Wall -Wextra -Wno-unused-parameter -I"$R" "$R/grab-client.c" "$R/xdg-shell-protocol.c" -o "$R/grab-client" $(pkg-config --cflags --libs wayland-client) cc -std=c17 -Wall -Wextra -Wno-unused-parameter -I"$R" "$R/pointer-daemon.c" "$R/virtual-pointer-protocol.c" -o "$R/pointer-daemon" $(pkg-config --cflags --libs wayland-client) ``` For the **unguarded** comparison, reconstruct a second disposable Mosaic source/build directory from the same Mosaic commit and apply only the first Mosaic patch. Keep the guarded binary intact for restoration: ```sh mkdir "$R/mosaic-unguarded" git archive cd550382b311efdbfe3ed892f4e1d281d852e754 | tar -xf - -C "$R/mosaic-unguarded" (cd "$R/mosaic-unguarded" && git apply --check "$R/mosaic-popup-plus-base.patch" && git apply "$R/mosaic-popup-plus-base.patch") cmp "$R/mosaic-unguarded/sway/desktop/transaction.c" "$R/transaction-before-hidden.c" PKG_CONFIG_PATH="$R/scenefx-build/meson-uninstalled" meson setup "$R/mosaic-unguarded-build" "$R/mosaic-unguarded" \ -Dmosaic-package=true -Ddefault-wallpaper=false -Dswaybar=false -Dswaynag=false \ -Dman-pages=disabled -Dsd-bus-provider=libsystemd -Dwerror=false PKG_CONFIG_PATH="$R/scenefx-build/meson-uninstalled" meson compile -C "$R/mosaic-unguarded-build" -j 8 ``` The original runner always selects `R/mosaic-build/sway/swayfx-mosaic`. To run the unguarded case, first save that *temporary* guarded binary and replace it with the unguarded build; restore it afterward. Never modify a system binary or library. ## Run the original reference cases `run.py` resolves its laboratory from its own copied path. It removes inherited `WAYLAND_DISPLAY` and `SWAYSOCK`, creates a mode-0700 run directory for each case, starts a two-output headless compositor, discovers its new sockets, and passes that socket explicitly to every `swaymsg -s` call. The private SceneFX library path is process-local to the private compositor. It never targets the active desktop socket. Run only these previously reported cases: ```sh python "$R/run.py" control boundary_left python "$R/run.py" private_standard boundary_right python "$R/run.py" private_mosaic boundary_left python "$R/run.py" private_mosaic # guarded, no hidden click; tests mapped return python "$R/run.py" private_mosaic_hit # guarded hidden-click case cp "$R/mosaic-build/sway/swayfx-mosaic" "$R/guarded-swayfx-mosaic" cp "$R/mosaic-unguarded-build/sway/swayfx-mosaic" "$R/mosaic-build/sway/swayfx-mosaic" mv "$R/run-private_mosaic_hit" "$R/run-private_mosaic_hit-guarded" python "$R/run.py" private_mosaic_hit # unguarded baseline cp "$R/guarded-swayfx-mosaic" "$R/mosaic-build/sway/swayfx-mosaic" ``` The runner reuses `run-private_mosaic_hit/` for both variants; rename that directory within `R` after the unguarded run to retain both result sets. Check `client.log` for the same received/used nonzero serial and XDG configure; inspect `pointer.log`, `wev.log`, IPC JSON and the left/right PNGs. Expected sampled observations: standard control and private standard deliver popup input at `(900,150)`; Mosaic clips that point; unguarded hidden owner leaks a yellow pixel and popup enter/button at `(100,150)`; guarded hidden owner shows ordinary background and no popup enter/button there; the no-click guarded case shows the same mapped popup after camera return. A hidden outside click may trigger `popup_done` under the valid grab without delivering a popup button, as it did in the original report. Treat any disagreement as a new observation, not as a reason to rewrite the recorded result. The probe does not establish universal leak freedom, full transaction/damage correctness, effects/saved-buffer behavior, ABI durability, general multi-output safety, or an offscreen-popup UX policy. Valid-grab nested popups and other output geometries remain **NOT TESTED**. ## Cleanup After all processes started by `run.py` have exited, remove only the new lab: ```sh rm -rf -- "$R" ``` Do not clean the original probe laboratory as part of this procedure.