docs(mosaic): investigate popup viewport ownership
This commit is contained in:
parent
cd550382b3
commit
8e8d1a819b
5 changed files with 125 additions and 1 deletions
|
|
@ -59,6 +59,8 @@ The isolated [STEP20C feasibility probe](design/STEP20C_FEASIBILITY_PROBE.md) co
|
|||
|
||||
The follow-up SceneFX subtree spike is also **PARTIALLY FEASIBLE**. It confirms that a tree-local render/hit clip can cover ordinary tiled descendants in an isolated binary, including pointer pass-through to another output. The globally parented popup path violates that boundary. Its temporary static link demonstrates a possible package shape, not a decided SceneFX dependency or ABI contract. The next design must bind popups to the owner's viewport without changing ordinary floating, fullscreen or standard-layout popup behavior.
|
||||
|
||||
The later [popup ownership probe](design/STEP20C_POPUP_OWNERSHIP_PROBE.md) demonstrated, in an isolated private build, a root-popup scene clip derived from the tiled owner’s applied Mosaic viewport. A tested nested popup inherited the clip; sampled visible popup input worked and the clipped-away popup did not intercept the ordinary client on the adjacent output. Standard `splith` behavior was restored after layout exit. Valid-serial XDG grabs, fully hidden popup owners, additional output geometries, complete damage/effect coverage and package maintenance remain unresolved. This evidence does not authorize production STEP20C or multi-output Mosaic support.
|
||||
|
||||
## IPC
|
||||
|
||||
On a Mosaic workspace, `get_tree` reports `layout: "mosaic"` and adds a `mosaic` object with `active_row` and `rows`. Each row entry contains ordered child node IDs, `scroll_x`, and focused node ID or null. Existing Sway fields retain their shapes; normal workspace/window events and `get_workspaces` paths remain in use. This is an extension to the Sway layout vocabulary: strict IPC consumers with a closed layout enum must recognize `mosaic`. Additional rows in the model are not a promise of usable multi-row IPC behavior.
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ This document describes the development tree at the documentation foundation pas
|
|||
|
||||
The [STEP20C viewport proposal](design/STEP20C_VIEWPORT_DESIGN.md) identifies an additional **design feasibility blocker**: installed SceneFX 0.5 exposes no generic scene-tree clip. Its proposed Mosaic-only projection requires a disposable headless proof that all relevant nodes, popups, damage and pointer hits respect one viewport boundary. The proposal does not change any current support status.
|
||||
|
||||
The later [popup ownership probe](design/STEP20C_POPUP_OWNERSHIP_PROBE.md) is **PARTIALLY_FEASIBLE**. A private owner-derived root-popup clip kept tested root and nested GTK popup pixels and pointer hits inside the Mosaic viewport on two horizontal headless outputs. Valid-serial grabs, fully hidden owners, other output arrangements, complete damage and effects remain unqualified. This does not change the current **NOT YET SUPPORTED** multi-output status.
|
||||
|
||||
The [isolated feasibility probe](design/STEP20C_FEASIBILITY_PROBE.md) is **PARTIALLY FEASIBLE**, not a release qualification. On two horizontal headless outputs, real Mosaic content leaked visually into an empty adjacent output; the disposable candidate also delivered pointer enter from a partly visible client outside its viewport. Negative-origin, scaled and rotated arrangements showed visual leakage too. Popups, effects, saved buffers, vertical Y-crossing behavior, pointer behavior in those additional arrangements and complete damage correctness remain unqualified. Multi-output Mosaic remains **NOT YET SUPPORTED**.
|
||||
|
||||
The later [SceneFX subtree clipping spike](design/STEP20C_SCENEFX_CLIPPING_SPIKE.md) is also **PARTIALLY FEASIBLE**. Its private inherited tree clip stopped tested tiled pixels and pointer hits from crossing the output boundary, including a fully hidden cell, but a real GTK XDG popup rendered and received input on the neighboring output because its scene tree lives in the global popup layer. Dedicated subsurface, saved-buffer, nested-popup and complete effect/damage validation remain open. The spike changed no production library or compositor source; multi-output Mosaic remains **NOT YET SUPPORTED**.
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ The [root README](../README.md) is the public summary. For a specific topic, use
|
|||
8. [STEP20C viewport proposal](design/STEP20C_VIEWPORT_DESIGN.md): proposed design, subordinate to accepted canonical contracts.
|
||||
9. [STEP20C feasibility probe](design/STEP20C_FEASIBILITY_PROBE.md): isolated technical evidence and an unresolved multi-output gate.
|
||||
10. [STEP20C SceneFX subtree spike](design/STEP20C_SCENEFX_CLIPPING_SPIKE.md): isolated clipping, popup and packaging evidence; production gate remains open.
|
||||
11. [STEP20C popup ownership probe](design/STEP20C_POPUP_OWNERSHIP_PROBE.md): isolated owner-derived root/nested popup evidence and remaining grab, damage and packaging gates.
|
||||
11. [STEP20A](STEP20A.md) and [STEP20B](STEP20B.md): historical evidence.
|
||||
|
||||
## Project
|
||||
|
|
@ -50,6 +51,7 @@ The [root README](../README.md) is the public summary. For a specific topic, use
|
|||
| [STEP20C viewport design](design/STEP20C_VIEWPORT_DESIGN.md) | Proposed, not yet accepted | Source audit, clipping alternatives, test plan, feasibility gate |
|
||||
| [STEP20C feasibility probe](design/STEP20C_FEASIBILITY_PROBE.md) | Experimental evidence | PARTIALLY FEASIBLE; visual and input boundary failed in a two-output headless candidate |
|
||||
| [STEP20C SceneFX subtree spike](design/STEP20C_SCENEFX_CLIPPING_SPIKE.md) | Experimental evidence | PARTIALLY FEASIBLE; tiled clip worked privately, global popup leaked pixels and input |
|
||||
| [STEP20C popup ownership probe](design/STEP20C_POPUP_OWNERSHIP_PROBE.md) | Experimental evidence | PARTIALLY_FEASIBLE; owner-derived root/nested popup clipping worked in a horizontal private headless case, with grab/damage/output gates open |
|
||||
|
||||
A proposal does not supersede accepted canonical contracts or assert implementation status. Its open feasibility items must be resolved before implementation.
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ This is the canonical order of work, not proof that a feature exists. **A step i
|
|||
|
||||
## STEP20C — viewport correctness · NEXT
|
||||
|
||||
**Goal:** make the single-row viewport correct at workspace and output boundaries. **Planned deliverables:** clipping or an equivalent rendering strategy, matching hit testing, adjacent-output tests, resize/migration tests, and transaction-safe camera animation. **Exit criteria:** no cross-output visual/input leakage in the tested scenarios; camera and IPC remain consistent across transactions; standard layouts still pass. **Docs first:** review the [STEP20C viewport proposal](design/STEP20C_VIEWPORT_DESIGN.md), resolve the visual/input gaps found by the [PARTIALLY FEASIBLE isolated probe](design/STEP20C_FEASIBILITY_PROBE.md) and the [PARTIALLY FEASIBLE SceneFX subtree spike](design/STEP20C_SCENEFX_CLIPPING_SPIKE.md), then settle the resulting contract in Architecture and UX and update Known Limitations before implementation. The subtree spike proves a narrow tiled path but exposes a popup visual/input leak and unresolved damage, effect and package-maintenance gates. Neither probe authorizes production STEP20C development or general multi-output support.
|
||||
**Goal:** make the single-row viewport correct at workspace and output boundaries. **Planned deliverables:** clipping or an equivalent rendering strategy, matching hit testing, adjacent-output tests, resize/migration tests, and transaction-safe camera animation. **Exit criteria:** no cross-output visual/input leakage in the tested scenarios; camera and IPC remain consistent across transactions; standard layouts still pass. **Docs first:** review the [STEP20C viewport proposal](design/STEP20C_VIEWPORT_DESIGN.md), resolve the visual/input gaps found by the [PARTIALLY FEASIBLE isolated probe](design/STEP20C_FEASIBILITY_PROBE.md) and the [PARTIALLY FEASIBLE SceneFX subtree spike](design/STEP20C_SCENEFX_CLIPPING_SPIKE.md), then settle the resulting contract in Architecture and UX and update Known Limitations before implementation. The subtree spike proves a narrow tiled path but exposes a popup visual/input leak and unresolved damage, effect and package-maintenance gates. The follow-up [PARTIALLY_FEASIBLE popup ownership probe](design/STEP20C_POPUP_OWNERSHIP_PROBE.md) closes the sampled root/nested popup leak in an isolated build but leaves valid-serial grabs, hidden-owner behavior, other output geometries, complete damage/effects and packaging unresolved. None of these probes authorizes production STEP20C development or general multi-output support.
|
||||
|
||||
## STEP20D — multi-row Spatial Canvas · PLANNED
|
||||
|
||||
|
|
|
|||
118
docs/design/STEP20C_POPUP_OWNERSHIP_PROBE.md
Normal file
118
docs/design/STEP20C_POPUP_OWNERSHIP_PROBE.md
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
# STEP20C popup viewport ownership probe
|
||||
|
||||
**Decision: PARTIALLY_FEASIBLE (isolated experiment).** Starting Mosaic commit `cd550382b311efdbfe3ed892f4e1d281d852e754`. This plan was written before changing disposable experimental C code. It does not authorize production STEP20C, a SceneFX fork, or a package change.
|
||||
|
||||
## Observed failure and actual ownership
|
||||
|
||||
The [SceneFX clipping spike](STEP20C_SCENEFX_CLIPPING_SPIKE.md) demonstrated a private inherited clip for the tiled subtree. A GTK4 `Gtk.Popover` from a partly visible Mosaic tiled window still rendered on neighboring HEADLESS-2. At global `(900,190)`, the popup client logged pointer enter and right button while the ordinary `wev` window beneath logged no pointer event.
|
||||
|
||||
In `sway/desktop/xdg_shell.c`, `handle_new_popup` passes the owning `sway_view` to `popup_create` but parents the root popup scene tree under `root->layers.popup`. The popup descriptor stores both that view and the relative content-tree node. `popup_handle_new_popup` creates a nested popup beneath the parent popup's XDG scene tree, retaining the same owning view. `arrange_popups` in `sway/desktop/transaction.c` positions root popup scene nodes from the current global coordinates of their relative view node. Popup destroy removes listeners, destroys the scene tree, then frees the popup. Initial surface commit and reposition call `popup_unconstrain`, which currently builds an output box using the owner's **pending** content coordinates. This pending/current mismatch must be investigated, not silently reused for a committed visual clip.
|
||||
|
||||
`node_at_coords` in `sway/input/cursor.c` asks each root layer for its topmost scene hit and can resolve a popup descriptor back to the owning container. A popup pointer or keyboard grab may independently consume input; visual hit filtering alone does not promise click-through. The unmodified headless control will establish the existing grab behavior before any new input conclusion.
|
||||
|
||||
## PROPOSED owner-derived boundary
|
||||
|
||||
The logical chain is `popup -> owning XDG view -> real tiled container -> current workspace -> current Mosaic row viewport`. The root popup may remain in the global popup scene layer for normal stacking and grab behavior. A private SceneFX inherited clip would attach to that root popup scene tree; nested descendants inherit it. SceneFX owns only the clip mechanism. Mosaic owns the owner/viewport association and never duplicates popup buffers or container authority.
|
||||
|
||||
**PROPOSED coordinates:** obtain the owner's committed workspace and output; intersect its tiled usable-area rectangle after gaps with the output's current global logical box; translate that global rectangle into the root popup tree's local coordinates using its current scene-node coordinates. Do not use `container->pending.content_x/y` as the visible clip while a transaction is in flight. Recompute after scene positioning changes, output/workspace changes and camera moves. When the Mosaic owner is not visibly tiled on its active workspace, make the popup non-visible/non-hittable without leaving stale pixels; restore ordinary popup behavior after layout exit or conversion to floating/fullscreen. The exact off-screen popup UX policy remains open.
|
||||
|
||||
The clip and popup tree have different owners: the popup owns its scene node; its view pointer is valid only through the ordinary Sway popup/view lifetime. A callback must not retain a workspace/output pointer past destruction or output migration. A future multi-row design resolves the row from the actual owner and applies that row's viewport; `rows[0]` is permitted only in disposable Row-0 wiring.
|
||||
|
||||
## Alternatives to test
|
||||
|
||||
| Option | Expected advantage | Risk to test |
|
||||
| --- | --- | --- |
|
||||
| **A. Clip the root popup tree** | One inherited boundary for root and nested popup descendants, retaining normal global stacking. **Primary proposed experiment.** | Local/global conversion, grab behavior, popup damage and lifecycle updates; may need special handling if nested surfaces are elsewhere. |
|
||||
| **B. XDG unconstrain/reposition plus clip** | Could keep more of a popup visible near its owner. | `wlr_xdg_popup_unconstrain_from_box` changes XDG geometry; it does not promise clipping or removal of hidden hits. Client reposition protocol and pending/current geometry may diverge. |
|
||||
| **C. Reparent Mosaic popup scenes below tiling** | Would inherit the existing tiling clip automatically. | Changes global stacking, overlay order and popup scene coordinates; may interact with grabs and nested popups. Requires evidence before use. |
|
||||
|
||||
Select A initially because the observed failure is a missing inherited boundary on one existing root popup tree. Reject it if ordinary popup behavior regresses or it cannot cover nested rendering/input/damage. Do not choose C solely for clipping convenience.
|
||||
|
||||
## Input, grabs and acceptance rules
|
||||
|
||||
Test both a non-grab popup and a client-requested XDG grab if a reproducible client is available. For a clipped-away region, no popup surface may receive pointer focus or buttons. A legitimate grab may consume an outside click or dismiss the popup; the probe must compare against an unmodified SwayFX headless control and distinguish this from a scene hit on invisible popup pixels. Do not force click-through contrary to normal popup semantics. Test left/right buttons, enter/leave, motion, client-local coordinates, popup close and focus changes.
|
||||
|
||||
The primary headless matrix uses two horizontal 800×600 outputs, Mosaic GTK owner on HEADLESS-1, ordinary `wev` or Kitty on HEADLESS-2, and the previous failure point `(900,190)`. Record screenshots **and** actual client events. Vary owner visibility (full/partial/hidden), camera scroll, popup open/reposition/close, workspace switch, owner destruction, layout exit, floating/fullscreen conversion, and nested popup if a client can produce one. Extend to vertical, negative-origin, scale-1.25 and rotated outputs; unexecuted cases are **NOT TESTED**. Instrument clip changes and output-specific damage for exposed/vacated pixels. Decorations, shadow, blur and saved transaction buffers require explicit coverage or a clear untested label.
|
||||
|
||||
The experiment passes only if root and nested popup rendering and pointer behavior, relevant grabs, lifecycle, damage, ordinary popup controls and tested output arrangements agree. Partial root-only success is **PARTIALLY_FEASIBLE**. A persistent visible/hittable leak or an unavoidable ordinary-popup regression is **BLOCKED**. No popup-specific result alone authorizes production STEP20C.
|
||||
|
||||
## Isolation and ABI boundary
|
||||
|
||||
Reuse only verified `/tmp` SceneFX/Mosaic source and build artifacts; make a new disposable checkout and build directory for this probe. The previous first patch changed `struct wlr_scene_tree` layout and crashed; retain the ABI-preserving sidecar approach for any private test. Load a private SceneFX library only in a private headless process, with private Wayland and IPC sockets. Do not install or replace system libraries or compositors. The previous static-link success is a laboratory result; SceneFX/wlroots overlapping symbols, ABI updates, upstream rebases, security maintenance and package ownership remain separate approval gates.
|
||||
|
||||
|
||||
## Source and API audit after the experiment
|
||||
|
||||
Installed packages were `swayfx 0.6-1`, `scenefx 0.5-1`, and `wlroots0.20 0.20.2-1`. The private checkouts were Mosaic `cd550382b311efdbfe3ed892f4e1d281d852e754` and SceneFX 0.5 `37ccd723bef49e6891156ffafce8f549f01446cc`. The main checkout was clean at the stated Mosaic baseline before this document was created.
|
||||
|
||||
- `sway/desktop/xdg_shell.c:80` `popup_create` stores `popup->view` and a scene descriptor pointing to the view content tree. `handle_new_popup` at line 369 supplies `root->layers.popup` as the root scene parent. `popup_handle_new_popup` at line 26 uses the parent popup's `xdg_surface_tree` for nested children while retaining the same `view`. `popup_handle_destroy` at line 33 removes listeners, destroys the scene tree, then frees the descriptor owner. The image-capture scene has separate ownership and was not validated here.
|
||||
- `popup_unconstrain` at line 44 uses `container->pending.workspace`, pending content coordinates and the output geometry. It runs on initial surface commit and reposition. wlroots 0.20 declares `void wlr_xdg_popup_unconstrain_from_box(struct wlr_xdg_popup *popup, const struct wlr_box *toplevel_space_box);` in `wlr/types/wlr_xdg_shell.h`; its documented contract is to adjust popup geometry under XDG positioner rules in **root toplevel surface coordinates**. It does not clip rendered pixels or pointer hits. This pending geometry path was left unchanged in the experiment.
|
||||
- `sway/desktop/transaction.c:1086` `arrange_popups` positions root popup nodes using the view's current global content-tree coordinates; `arrange_root` invokes it after arranging outputs. `sway/input/cursor.c:39` `node_at_coords` visits the global scene layers top-down, calls `wlr_scene_node_at`, and resolves a hit popup descriptor back to its view container. This is distinct from popup grab state. `sway/tree/view.c` owns the view/container relationship; it does not make the popup scene a child of the tiled scene.
|
||||
- The prior private SceneFX patch modifies `types/scene/wlr_scene.c` visibility (`scene_node_update_iterator`, `scene_node_bounds`), hit testing (`scene_node_at_iterator`), direct scanout eligibility (`scene_entry_try_direct_scanout`), and damage through `scene_node_update`/`scene_output_damage`. Its `void wlr_scene_tree_set_clip(struct wlr_scene_tree *, const struct wlr_box *)` takes a copied **tree-local logical-coordinate** rectangle; `NULL` clears it. The new private `bool wlr_scene_tree_get_clip(struct wlr_scene_tree *, struct wlr_box *)` copies the applied rectangle or returns false. Both symbols belong to the private SceneFX shared object, not installed wlroots. These are experimental APIs, absent from installed SceneFX 0.5.
|
||||
|
||||
The private Mosaic change in `transaction.c` reads the already-applied tiled-tree clip for the popup owner's **current** Mosaic workspace, converts it to global logical coordinates using the tiled scene node's current position, intersects it with the owner's output box, then subtracts the root popup scene node's global position to obtain a root-popup-local clip. This preserves the global popup layer and nests descendants below the clipped root. It clears the clip for standard layout, floating or fullscreen owner; an inactive Mosaic workspace receives an empty clip. The root creation hook in `xdg_shell.c` arranges immediately before the first map. No second window tree or buffer copy was made. The Row-0 tiled viewport comes from the previous spike; the popup-specific lookup is through the owner, workspace and applied tree clip. A future row would supply that row's viewport through the same tree API without `rows[0]` in popup ownership.
|
||||
|
||||
The prototype has not proved that `current` state and applied scene state remain synchronized during every transaction, output migration or resize. `popup_unconstrain` still uses pending state. A production design must give the viewport/scene generation an explicit ownership and lifetime contract.
|
||||
|
||||
## Alternatives evaluated
|
||||
|
||||
| Strategy | Finding |
|
||||
| --- | --- |
|
||||
| A. Owner-derived root popup tree clip | **Selected for the private test.** One local clip covers the root and its nested scene descendants while retaining global popup stacking and Sway's existing grab path. It requires the private SceneFX inherited clip and careful committed-state conversion. |
|
||||
| B. XDG unconstrain/reposition plus clip | Reposition was observed and may improve popup placement, but wlroots only adjusts geometry according to XDG positioner rules. It cannot enforce a viewport boundary alone. Retain as a later UX choice, coupled with A if desired. |
|
||||
| C. Reparent beneath the Mosaic tiled tree | Would inherit a clip but alters normal global popup stacking and coordinate parenting. No need arose in this test. Overlay, nested and grab consequences were not proven safe, so this is not the preferred path. |
|
||||
|
||||
## Disposable source and reproduction
|
||||
|
||||
All experiment material is under `/tmp/swayfx-mosaic-popup-yEamPX/`; no system installation occurred. The previous evidence directory `/tmp/swayfx-mosaic-step20c-scenefx-aiMRk6/` was verified before reuse. The new directory contains `mosaic/`, `scenefx/`, `mosaic-build/`, `scenefx-build/`, GTK clients, a virtual-pointer injector, `run_popup.py`, per-run logs and PNGs. `mosaic-popup-plus-base.patch` and `scenefx-popup-plus-base.patch` retain the *complete* private differences from the pinned bases, including the earlier tiled clip. Their SHA-256 values are respectively `d416f6413ea097a48a44d231e50ffba363914f46c8687f0ea2e4dcacd5f31fe9` and `c74c515b98da8f5f637e260e6a3755c7a75bd78d1c39e54a67850f1c383e30e7`. The popup-specific delta is `probe_clip_popup`/`arrange_popups` and the root creation call in Mosaic, plus the SceneFX getter. Source files and scripts in `/tmp` are disposable; the preceding audit and observations preserve their essential logic if that directory is removed.
|
||||
|
||||
A representative run is:
|
||||
|
||||
```sh
|
||||
python3 /tmp/swayfx-mosaic-popup-yEamPX/run_popup.py exp 900,190 right
|
||||
python3 /tmp/swayfx-mosaic-popup-yEamPX/run_popup.py control 900,190 left
|
||||
python3 /tmp/swayfx-mosaic-popup-yEamPX/run_popup.py exp_nested 350,250 left
|
||||
```
|
||||
|
||||
The script launches either the private `mosaic-build/sway/swayfx-mosaic` with `LD_LIBRARY_PATH=/tmp/swayfx-mosaic-popup-yEamPX/scenefx-build`, or the unmodified `/usr/bin/sway`, with `WLR_BACKENDS=headless`, two 800×600 outputs, `WLR_RENDERER=pixman`, and run-local Wayland/IPC sockets. It starts a Kitty cell, a GTK4 popover owner on HEADLESS-1 and a `wev` client on HEADLESS-2; it injects a virtual-pointer move/button pair, captures both outputs with `grim`, and writes `gtk.log`, `X.log`, `compositor.log`, `tree.json` and screenshots to the named `run-*` directory. The GTK clients log widget events and `WAYLAND_DEBUG=1` protocol events. The test process and private library path do not affect the daily session. To rebuild from retained source, apply each patch to the pinned checkout, configure SceneFX 0.5 and Mosaic with Meson in separate `/tmp` build directories using the private SceneFX pkg-config/library path, then run the commands above. The prior [SceneFX spike](STEP20C_SCENEFX_CLIPPING_SPIKE.md) records the compatible base build flags. The recorded Meson setup is `meson setup /tmp/swayfx-mosaic-popup-yEamPX/scenefx-build /tmp/swayfx-mosaic-popup-yEamPX/scenefx -Dexamples=false -Dwerror=false`, followed by `PKG_CONFIG_PATH=/tmp/swayfx-mosaic-popup-yEamPX/scenefx-build/meson-uninstalled meson setup /tmp/swayfx-mosaic-popup-yEamPX/mosaic-build /tmp/swayfx-mosaic-popup-yEamPX/mosaic -Dmosaic-package=true -Ddefault-wallpaper=false -Dswaybar=false -Dswaynag=false -Dman-pages=disabled -Dsd-bus-provider=libsystemd -Dwerror=false`; `meson compile -C` each build directory in that order. The private SceneFX `.pc` file resolves the private headers and library. These commands require the retained patch application first.
|
||||
|
||||
## Rendering and real pointer observations
|
||||
|
||||
At global `(900,190)` (HEADLESS-2 local `(100,190)`), the unmodified SwayFX control screenshot pixel was `(49,50,68)`, the escaped GTK popup color. Its GTK client recorded `POPUP_POINTER_ENTER` and right/left `POPUP_BUTTON`; `wev` received no corresponding button. With the private owner clip, that pixel was the ordinary second-output client `(102,102,102)`. The popup client recorded no pointer enter or button there. A right click reached `wev`: enter at client-local `(98,165)` followed by button `273` press/release. The private log applied root clip `local=-162,-25 800x600`. This establishes visual and hit-test exclusion for that tested point while leaving HEADLESS-2 content visible.
|
||||
|
||||
At `(750,190)` inside the clipped popup on HEADLESS-1, the pixel stayed `(49,50,68)` and GTK recorded real enter and both right and left button events. The Wayland trace for the left run recorded `wl_pointer.enter(... wl_surface#84, 1244, 42)` and `wl_pointer.button(... 272, pressed/released)`; the widget coordinates differ from raw surface coordinates because GTK reports a child-widget origin. The client-local raw protocol coordinate remained stable across the tested event. The output screenshot and actual Wayland/client logs are complementary evidence.
|
||||
|
||||
The nested GTK client created a root XDG popup and a second `get_popup` whose parent was the first popup's `xdg_surface`. At `(900,190)`, the second-output pixel stayed `(102,102,102)`, no nested popup button was recorded, and `wev` received the right click. At visible nested content `(350,250)`, the pixel was `(83,84,99)` and GTK logged `NESTED_ENTER` plus `NESTED_BUTTON 1`; the raw protocol trace showed `wl_pointer.enter(... wl_surface#105, 844, 42)` and button `272` press/release. This demonstrates a real nested child inherits the root clip in the tested geometry. It does not cover every nested positioner or submenu arrangement.
|
||||
|
||||
### Grab and outside-click distinction
|
||||
|
||||
The GTK client emitted `xdg_popup.grab(wl_seat, 0)` in the protocol trace. Because serial zero is not a valid user-input serial for a general XDG grab, this does **not** qualify valid-serial grab behavior. In the clipped-away `(900,190)` region, the private run's **left** click caused `xdg_popup.popup_done` and did not deliver a popup button or a `wev` button; `wev` did receive pointer enter first. The private **right** click also dismissed the popup but reached `wev` as button 273. In the unmodified control, the same `(900,190)` left/right clicks went to the visible popup. At `(900,400)`, outside the popup surface in both control and private runs, `wev` got a right click and GTK got `popup_done`. These observations distinguish popup dismissal/consumption from a hidden scene hit. They do not establish a universal click-through contract or keyboard-grab semantics. The safety invariant held in the tested case: no clipped-away popup surface received pointer enter or button. Valid-serial grabs require a dedicated reproducer and policy review.
|
||||
|
||||
## Camera, lifecycle, damage and ordinary-layout checks
|
||||
|
||||
A `layout mosaic scroll -400` changed the tiled clip from local `x=480` to `x=80` and the popup clip from `x=-162` to `x=-562`; the second-output pixel stayed ordinary gray and `wev` received a right click. A +400 attempt had been clamped and is not counted as camera evidence. A real GTK `xdg_popup.reposition` request received `repositioned(1)` and a new configure while the second-output pixel remained gray and `wev` remained interactive. Switching to workspace 2 and destroying the owner separately produced no popup pixel at the sampled second-output point and no compositor crash; the owner-destroy run necessarily terminated GTK before another client event could be recorded. The workspace switch sent `popup_done`. Moving the owner to floating or fullscreen cleared the extra clip in the private log; sampled second-output interaction remained available, but those cases were not comprehensively compared. Switching the Mosaic workspace to standard `splith` cleared the clip and restored the popup's ordinary cross-output pixel and right-button behavior in the sampled control configuration.
|
||||
|
||||
The private SceneFX log recorded per-output `SPIKE_DAMAGE` extents during open, camera movement, popup change and scene destruction. The screenshots before and after pointer action showed no stale popup pixel at the sampled leak point. Some recorded damage extents were near whole-output size, and the log includes ordinary client changes and frame scheduling; it does **not** isolate popup-only damage cost or prove minimal invalidation, all exposed/vacated regions, or absence of every stale pixel. No full effect/buffer matrix was run. GTK's client-drawn popover and its nested surface were rendered; compositor SceneFX shadow and blur for a popup, title decorations, subsurfaces beyond GTK's surfaces, and saved transaction buffers were **NOT TESTED**. No effect was deliberately disabled by the popup-specific change.
|
||||
|
||||
An attempt to make the GTK owner fully hidden by adding more cells and scrolling was inconclusive: launching the new clients changed focus and GTK closed its popup before the hidden-owner measurement. Do not count this as a hidden-popup PASS. The prior tiled-cell spike's fully hidden result does not substitute for this popup case.
|
||||
|
||||
## Test matrix
|
||||
|
||||
| Configuration or case | Result | Boundary of evidence |
|
||||
| --- | --- | --- |
|
||||
| Two horizontal 800×600 outputs, root popup at `(900,190)` | PASS for sampled pixels and pointer events | Control leaked; private clip kept popup off HEADLESS-2 and right click reached ordinary client. |
|
||||
| Partly visible root popup | PASS for tested visible and clipped-away points | Left/right visible buttons reached GTK; clipped-away surface received neither. |
|
||||
| Nested root/child popup | PASS for tested visible and clipped-away points | Real second XDG popup, nested visible left click and outside right click. |
|
||||
| Camera scroll, popup reposition, workspace switch, owner destroy | PARTIAL | Sampled screenshots and event/lifecycle logs; transaction races and complete damage not exhausted. |
|
||||
| Standard `splith` layout restoration | PASS at sampled point | Ordinary popup visibility/input restored after layout exit. |
|
||||
| Floating/fullscreen conversion | PARTIAL | Clip clear observed; broader visual and grab parity unqualified. |
|
||||
| Fully hidden popup owner | NOT TESTED | Focus change dismissed popup in attempted fixture. |
|
||||
| Valid-serial XDG pointer/keyboard grab; non-grab popup | NOT TESTED | GTK request used serial zero; outside left/right behavior observed only for that control. |
|
||||
| Vertical outputs, negative origin, scale 1.25, rotation | NOT TESTED | No reliable popup fixture executed in these coordinate configurations. |
|
||||
| Output migration/resize, effect boundaries, saved transaction buffers | NOT TESTED | Requires more controlled fixture and damage instrumentation. |
|
||||
|
||||
## ABI, packaging and decision
|
||||
|
||||
The initial inherited-clip experiment's change to public `struct wlr_scene_tree` layout crashed; this test reused the ABI-preserving sidecar, which copies its box and removes it on tree destruction. `nm -D` found the private `wlr_scene_tree_set_clip`, `wlr_scene_tree_get_clip` and `wlr_scene_node_at` symbols in the private `libscenefx-0.5.so`. `ldd` on the private compositor resolved SceneFX from `/tmp/swayfx-mosaic-popup-yEamPX/scenefx-build` and wlroots from `/usr/lib/libwlroots-0.20.so`. This demonstrates process-local dynamic library selection, not stable ABI across wlroots or SceneFX updates. The private getter adds API surface; symbols that SceneFX and wlroots both export need version-aware review before packaging. The normal `/usr/bin/sway` control and installed libraries were untouched. A permanent SceneFX fork, system library replacement, production patch or package dependency is **not authorized**. Upstreaming a generic inherited clip remains the preferred maintenance discussion; privately bundling a versioned compatible implementation would require a separate legal, security, symbol and update plan. A second system SceneFX package would violate the preferred package architecture.
|
||||
|
||||
**Decision: PARTIALLY_FEASIBLE.** Owner-derived clipping on the existing root popup scene hierarchy closes the specific horizontal visual and pointer-hit escape and covers a tested nested popup while preserving standard `splith` behavior at the tested point. The result is insufficient for `POPUP_FEASIBLE`: valid-serial grabs, fully hidden owner, output geometry variants, transaction/output migration, complete damage and compositor effect/buffer coverage remain open. The next bounded probe should create a deterministic valid-serial grabbed popup with a controllable fully hidden owner and exercise output move/resize/transform plus damage. Decide the off-screen popup UX separately: hiding, dismissing or repositioning are all possible policies, and this probe approves none. STEP20C production implementation and multi-output support remain unauthorized.
|
||||
Loading…
Reference in a new issue