docs(mosaic): preserve nested XDG valid-grab probe
This commit is contained in:
parent
285239a58e
commit
52174ceb6f
10 changed files with 950 additions and 3 deletions
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
### STEP20C experimental evidence
|
||||
|
||||
- Added a source-only nested XDG grab fixture and a comparative headless probe. Two real button serials produced an interactive toplevel → popup A → popup B chain; sampled guarded Mosaic clipping, child-first dismissal, hidden-owner camera return and standard-layout return were feasible. STEP20C production support remains open.
|
||||
- Preserved the valid-serial XDG grab / hidden-owner probe's original C and Python sources, private patches and protocol XML with hashes and a disposable headless replay procedure. Its result remains **PARTIALLY_FEASIBLE**; no production source, package or UX policy changed.
|
||||
|
||||
### Patch export tooling
|
||||
|
|
|
|||
|
|
@ -65,6 +65,16 @@ The [valid-grab and hidden-owner probe](design/STEP20C_VALID_GRAB_HIDDEN_OWNER_P
|
|||
|
||||
The [preserved source fixture](../tooling/probes/step20c-valid-grab/README.md) reconstructs the private guard from pinned bases and patches. It supplies reproducible evidence, not a compositor-owned STEP20C implementation.
|
||||
|
||||
The subsequent [nested valid-grab probe](design/STEP20C_NESTED_VALID_GRAB_PROBE.md)
|
||||
used two real pointer-button serials and a true toplevel → popup A → popup B
|
||||
XDG parent chain. In a disposable guarded binary, both popup levels were
|
||||
interactive within the horizontal two-output Mosaic viewport, absent from
|
||||
sampled clipped visual/input positions, and restored after a fully hidden
|
||||
owner returned without popup re-creation. The unmodified SwayFX and private
|
||||
standard-layout controls preserved ordinary sampled popup behavior. This
|
||||
narrows the feasibility gap for nested grabs but does not define a production
|
||||
transaction, damage, output-geometry or SceneFX ABI contract.
|
||||
|
||||
## 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.
|
||||
|
|
|
|||
|
|
@ -20,10 +20,22 @@ The [STEP20C viewport proposal](design/STEP20C_VIEWPORT_DESIGN.md) identifies an
|
|||
|
||||
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 and fully hidden owners were untested in that probe; other output arrangements, complete damage and effects also remained unqualified. This does not change the current **NOT YET SUPPORTED** multi-output status.
|
||||
|
||||
The [valid-grab and hidden-owner follow-up](design/STEP20C_VALID_GRAB_HIDDEN_OWNER_PROBE.md) is also **PARTIALLY_FEASIBLE**. A genuine pointer-button serial produced an interactive grabbed XDG popup in unmodified SwayFX, private standard layout and private Mosaic. The previous owner-derived viewport clip leaked popup pixels and button hits when the owner was fully hidden. A further disposable owner-visibility guard removed that sampled leak while the popup stayed mapped and returned with the camera. Valid-grab nested popups, other output arrangements, transaction-generation consistency, complete damage/effect coverage and ABI/package maintenance remain unqualified. Multi-output Mosaic is still **NOT YET SUPPORTED**.
|
||||
The [valid-grab and hidden-owner follow-up](design/STEP20C_VALID_GRAB_HIDDEN_OWNER_PROBE.md) is also **PARTIALLY_FEASIBLE**. A genuine pointer-button serial produced an interactive grabbed XDG popup in unmodified SwayFX, private standard layout and private Mosaic. The previous owner-derived viewport clip leaked popup pixels and button hits when the owner was fully hidden. A further disposable owner-visibility guard removed that sampled leak while the popup stayed mapped and returned with the camera. This earlier probe did not qualify nested valid grabs; the separate nested-grab probe below covers only a sampled horizontal matrix. Other output arrangements, transaction-generation consistency, complete damage/effect coverage and ABI/package maintenance remain unqualified. Multi-output Mosaic is still **NOT YET SUPPORTED**.
|
||||
|
||||
The [retained source fixture](../tooling/probes/step20c-valid-grab/README.md) permits bounded replay; it does not close these qualification gaps.
|
||||
|
||||
The [nested valid-grab probe](design/STEP20C_NESTED_VALID_GRAB_PROBE.md) is
|
||||
**FEASIBLE for its sampled matrix only**. A separate client used real pointer
|
||||
button serials to grab A and then B as A's XDG child. In the disposable
|
||||
guarded compositor, both levels were visible and interactive inside the
|
||||
Mosaic viewport, absent at sampled pixels and hits beyond it, and suppressed
|
||||
while their tiled owner was fully hidden; the mapped surfaces returned with
|
||||
the camera. Closing B left A interactive, and outside clicks dismissed B
|
||||
then A. Control and private `splith` retained ordinary sampled behavior.
|
||||
Other output geometries, migration/resize, transaction generations, complete
|
||||
damage/effects and dependency/ABI maintenance remain **NOT TESTED**. Adjacent
|
||||
output Mosaic remains **NOT YET SUPPORTED** in production.
|
||||
|
||||
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**.
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@ The [root README](../README.md) is the public summary. For a specific topic, use
|
|||
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.
|
||||
12. [STEP20C valid-grab and hidden-owner probe](design/STEP20C_VALID_GRAB_HIDDEN_OWNER_PROBE.md): isolated valid-serial XDG grab comparison, fully hidden owner leak and private guard evidence; production gates remain open.
|
||||
[Preserved source fixtures](../tooling/probes/step20c-valid-grab/README.md) record exact inputs and a disposable replay procedure.
|
||||
13. [STEP20A](STEP20A.md) and [STEP20B](STEP20B.md): historical evidence.
|
||||
13. [STEP20C nested valid-grab probe](design/STEP20C_NESTED_VALID_GRAB_PROBE.md): two genuine nested XDG grabs and sampled clipping, dismissal, hidden-owner and standard-layout return evidence; remaining STEP20C gates stay open.
|
||||
[Nested-grab source fixture](../tooling/probes/step20c-nested-grab/README.md) reconstructs the same disposable experiment.
|
||||
14. [STEP20A](STEP20A.md) and [STEP20B](STEP20B.md): historical evidence.
|
||||
|
||||
## Project
|
||||
|
||||
|
|
@ -55,6 +57,7 @@ The [root README](../README.md) is the public summary. For a specific topic, use
|
|||
| [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 |
|
||||
| [STEP20C valid-grab and hidden-owner probe](design/STEP20C_VALID_GRAB_HIDDEN_OWNER_PROBE.md) | Experimental evidence | PARTIALLY_FEASIBLE; valid-serial popup grab measured, hidden-owner leak found and closed only in a private guard; broader STEP20C gates remain open |
|
||||
| [STEP20C nested valid-grab probe](design/STEP20C_NESTED_VALID_GRAB_PROBE.md) | Experimental evidence | FEASIBLE for sampled two-level grabbed XDG popup cases on horizontal headless outputs; no production qualification |
|
||||
|
||||
A proposal does not supersede accepted canonical contracts or assert implementation status. Its open feasibility items must be resolved before implementation.
|
||||
|
||||
|
|
|
|||
|
|
@ -22,10 +22,20 @@ 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. The [PARTIALLY_FEASIBLE popup ownership probe](design/STEP20C_POPUP_OWNERSHIP_PROBE.md) closes the sampled root/nested popup leak in an isolated build. The later [PARTIALLY_FEASIBLE valid-grab and hidden-owner probe](design/STEP20C_VALID_GRAB_HIDDEN_OWNER_PROBE.md) measures a real XDG grab and exposes a hidden-owner leak in that initial private clip; a further private guard closes the sampled leak. Valid-grab nested popups, other output geometries, transaction/output migration, complete damage/effects and packaging remain unresolved. None of these probes 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 [PARTIALLY_FEASIBLE popup ownership probe](design/STEP20C_POPUP_OWNERSHIP_PROBE.md) closes the sampled root/nested popup leak in an isolated build. The later [PARTIALLY_FEASIBLE valid-grab and hidden-owner probe](design/STEP20C_VALID_GRAB_HIDDEN_OWNER_PROBE.md) measures a real XDG grab and exposes a hidden-owner leak in that initial private clip; a further private guard closes the sampled leak. The later nested valid-grab probe closes only its sampled horizontal matrix. Other output geometries, transaction/output migration, complete damage/effects and packaging remain unresolved. None of these probes authorizes production STEP20C development or general multi-output support.
|
||||
|
||||
The [source fixture for the valid-grab probe](../tooling/probes/step20c-valid-grab/README.md) preserves a bounded replay path. Its existence does not change STEP20C's **NEXT** status or any exit criterion.
|
||||
|
||||
The [nested valid-grab probe](design/STEP20C_NESTED_VALID_GRAB_PROBE.md) is
|
||||
**FEASIBLE within its sampled horizontal headless matrix**: distinct valid
|
||||
grabs for parent and child popups, visual/input clipping, outside dismissal,
|
||||
hidden-owner camera return and standard-layout return were measured in a
|
||||
disposable guarded binary. Its [source fixture](../tooling/probes/step20c-nested-grab/README.md)
|
||||
is reproducible. The next bounded feasibility test should change output
|
||||
geometry with the same nested grabbed hierarchy and direct control comparison.
|
||||
Transaction, resize/migration, damage/effects and ABI/package gates remain
|
||||
open; this result does not change STEP20C's **NEXT** status.
|
||||
|
||||
## STEP20D — multi-row Spatial Canvas · PLANNED
|
||||
|
||||
**Goal:** connect rows to runtime tree, transactions, scene, input, and IPC. **Planned deliverables:** active-row navigation, row-local scroll/focus, deterministic insertion/reflow, in-row reorder, and between-row movement. **Exit criteria:** real tiled containers preserve identity and order across row changes; focus and camera restore correctly; headless multi-row tests pass. **Docs first:** settle exact interactions and row invariants in UX and Architecture. Unset key combinations remain TBD.
|
||||
|
|
|
|||
253
docs/design/STEP20C_NESTED_VALID_GRAB_PROBE.md
Normal file
253
docs/design/STEP20C_NESTED_VALID_GRAB_PROBE.md
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
# STEP20C nested valid-grab probe
|
||||
|
||||
**Status: EXPERIMENTAL / FEASIBLE for the sampled nested-grab matrix.** This document records a bounded headless
|
||||
feasibility experiment, not a production STEP20C implementation or UX decision.
|
||||
The [architecture](../ARCHITECTURE.md), [UX](../UX.md), [limitations](../KNOWN_LIMITATIONS.md)
|
||||
and [roadmap](../ROADMAP.md) retain authority.
|
||||
|
||||
## Hypothesis and protocol contract (before fixture implementation)
|
||||
|
||||
A real `xdg_toplevel → xdg_popup A → xdg_popup B` chain can keep both
|
||||
popups mapped under two valid explicit grabs. The disposable SceneFX/Mosaic
|
||||
clip should constrain **both** popup levels to the applied viewport, including
|
||||
when their tiled owner is fully hidden by camera translation. The unmodified
|
||||
SwayFX control establishes ordinary grab dismissal and pointer routing.
|
||||
|
||||
The installed `xdg-shell.xml` declares version 7; the prior fixture binds
|
||||
`xdg_wm_base` at version 3. Its `xdg_popup.grab` text requires a user action
|
||||
serial, a grabbing parent for nested grabbing popups, a mapped parent, grab
|
||||
before mapping, and reverse destruction order. It permits a compositor to
|
||||
dismiss the whole nested stack, topmost first. The initial buffer for each
|
||||
popup follows `xdg_popup.configure`, `xdg_surface.configure`, and
|
||||
`xdg_surface.ack_configure`. A denied grab immediately dismisses the popup.
|
||||
The client must log the originating `wl_pointer.button` target, serial and
|
||||
the matching `xdg_popup.grab` request for A and B separately. Nonzero serial
|
||||
alone is insufficient proof.
|
||||
|
||||
## Fixture architecture and geometry
|
||||
|
||||
The toplevel has its own `wl_surface`, `xdg_surface` and `xdg_toplevel`.
|
||||
Popup A has its own `wl_surface`, `xdg_surface` and `xdg_popup` with the
|
||||
toplevel `xdg_surface` as parent. Popup B has distinct objects and uses A's
|
||||
`xdg_surface` as its actual XDG parent. Toplevel, A and B use distinct solid
|
||||
colors and explicit `TOPLEVEL`, `POPUP_A`, `POPUP_B` log labels. Surface object
|
||||
identity and configure traces, rather than color alone, establish hierarchy.
|
||||
|
||||
A left button press on the mapped toplevel opens A with exactly that event's
|
||||
serial. After A is configured, acknowledged and mapped, a **new** left press
|
||||
on A opens B with that second event's serial. Each popup uses an independent
|
||||
positioner anchored inside its parent window geometry. Positioner offsets
|
||||
will provide a fully visible placement and one or two viewport-crossing
|
||||
placements. The runner records actual configure geometry, owner IPC rectangle,
|
||||
Mosaic `scroll_x`, scene clip log, output positions and injection coordinates;
|
||||
it must not infer rendered position from logical IPC `rect.x` alone.
|
||||
|
||||
Normal lifecycle: B role → B `xdg_surface` → B `wl_surface`, then A role → A
|
||||
`xdg_surface` → A `wl_surface`, then toplevel role/surface. On compositor
|
||||
`popup_done`, log event order separately from client destruction order and
|
||||
destroy topmost first. Explicit B closure must leave A mapped and interactive.
|
||||
Owner destruction is tested separately from normal popup closure; compositor
|
||||
`xdg_toplevel.close` and process teardown are distinct observations.
|
||||
|
||||
The probe uses two private 800×600 headless outputs at `(0,0)` and `(800,0)`.
|
||||
Configuration A is unmodified `/usr/bin/sway` in `splith`; B is private
|
||||
SceneFX/Mosaic in `splith`; C is the private guarded binary in `mosaic`.
|
||||
Every run has private `XDG_RUNTIME_DIR`, Wayland/IPC sockets, persistent
|
||||
virtual pointer, compositor log, `WAYLAND_DEBUG=1` client log, pointer log,
|
||||
IPC JSON and screenshots of both outputs. Private SceneFX is loaded only by
|
||||
the private compositor. No command addresses the desktop session.
|
||||
|
||||
## Scenario acceptance matrix
|
||||
|
||||
`PASS` requires matching protocol, pixels and pointer traces at named sample
|
||||
points. A popup closing before a hidden-owner sample makes that sample
|
||||
`INCONCLUSIVE`; a client merely surviving is never proof. `FAIL` records an
|
||||
observed violated expectation. `PARTIAL` means only part of the stated
|
||||
evidence exists. Unexecuted cells remain `NOT TESTED`.
|
||||
|
||||
| Scenario | A control | B private splith | C private mosaic | Required evidence |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 1. Both popups visible and interactive, two valid grabs | PASS | PASS | PASS | Distinct button serials; ordered XDG configure/ack/map; A/B pixels and B right-button events |
|
||||
| 2. B crosses viewport | PASS | PASS | PASS | B pixels on each output in standard layouts; Mosaic B pixels left only, no B hit right; normal right-output target receives click |
|
||||
| 3. A and B cross viewport | PASS | PASS | PASS | Both colors right in standard layouts, neither right in Mosaic; distinct A and B button targets left |
|
||||
| 4. Explicit B close, A remains | PASS | PASS | PASS | B disappears; A receives right press/release; no B re-creation or ghost at sampled point |
|
||||
| 5. Outside click | PASS | PASS | PASS | `popup_done` B then A; client destroys B then A; normal right-output client gets left press/release |
|
||||
| 6. Tiled owner fully hidden, then camera return | N/A | N/A | PASS | Same owner and popup IDs, both mapped; no A/B pixels or hits while hidden; both return without recreation |
|
||||
| 6b. Hidden owner with separate click | N/A | N/A | PASS | No hidden popup button; grab dismisses B then A; no return after dismissal |
|
||||
| 7. Toplevel destruction with A/B open | PASS | PASS | PASS | `xdg_toplevel.close`, then client destroys B, A and toplevel; no `popup_done` required on this path |
|
||||
| 8. Mosaic → splith with A/B open | N/A | N/A | PASS | Same B responds at global `(900,190)` after layout change; no prior `popup_done` |
|
||||
|
||||
The previously retained source fixtures and three patches must remain
|
||||
byte-identical. A new private correction, if needed, is a separately named
|
||||
delta with an explicit reason. Builds, sockets, generated protocol sources,
|
||||
captures and logs remain outside Git. Output resize/migration, vertical,
|
||||
negative-origin, scaled/rotated outputs, transaction generations, complete
|
||||
damage/effects and package ABI are outside this experiment.
|
||||
|
||||
## Reproduction and retained artifacts
|
||||
|
||||
The lab is `/tmp/swayfx-mosaic-nested-grab-If8ofeUu/` (about 54 MB after all
|
||||
runs). Its `artifacts-sha256.txt` lists 658 measured log, IPC and capture
|
||||
files; the manifest SHA-256 is
|
||||
`869f97f2ed8a7babc9db2bae49531a1c45e3aef84e6e799af87f2d8fcedb8a70`.
|
||||
It was reconstructed from Mosaic
|
||||
`cd550382b311efdbfe3ed892f4e1d281d852e754` and SceneFX
|
||||
`37ccd723bef49e6891156ffafce8f549f01446cc`, applying the unchanged
|
||||
private SceneFX patch, Mosaic patch, then hidden-owner guard in that order.
|
||||
All three patch SHA-256 values match the [previous fixture](../../tooling/probes/step20c-valid-grab/README.md).
|
||||
`prepare.sh` rebuilt both private libraries/compositor and generated XDG and
|
||||
virtual-pointer code; `nested-client.c` and the reused `pointer-daemon.c` were
|
||||
compiled with `-std=c17`. `/usr/bin/sway` provided the unmodified control.
|
||||
The [new fixture README](../../tooling/probes/step20c-nested-grab/README.md)
|
||||
gives clean-checkout commands. Each `run-*` directory holds client protocol,
|
||||
pointer, `wev`, compositor, IPC and two-output screenshot evidence. The runner
|
||||
uses short separate `/tmp/mng-*` socket directories recorded in `runtime-path`.
|
||||
|
||||
The client fixture SHA-256 at the measured build is
|
||||
`2e40c98af72a0df8b316810589a0be8f4425a763db1de448b49a7813956b6f7d`.
|
||||
The runner was corrected during the probe: its first, long runtime socket
|
||||
path caused one private `splith` startup to exit before any client appeared,
|
||||
with `Socket path won't fit into ipc_sockaddr->sun_path` at
|
||||
`sway/ipc-server.c:142` in the compositor log. `ipc_user_sockaddr()` checks
|
||||
the `snprintf` result against `sun_path`; the retained aborted directory has
|
||||
only `config`, `compositor.log` and an empty `wayland-1.lock`. A `SIGSEGV`
|
||||
process result was reported during the probe, but no exit-status record,
|
||||
core dump or stack trace is retained to corroborate the signal or locate its
|
||||
origin. Source inspection identifies a plausible error-path defect:
|
||||
`sway_abort` invokes the registered `sway_terminate(EXIT_FAILURE)` callback;
|
||||
with a Wayland display already present, `sway_terminate` calls
|
||||
`ipc_event_shutdown`, whose listener check dereferences `ipc_client_list`.
|
||||
The length check runs before `ipc_init` creates that list. This ordering could
|
||||
explain a null dereference, but no retained backtrace proves that this was the
|
||||
observed process exit. The overlong path is a confirmed fixture setup error;
|
||||
the reported `SIGSEGV` and its precise origin remain a separate **unverified
|
||||
startup incident**. A future isolated diagnostic run could preserve the exit
|
||||
status and a backtrace; this probe did not do so.
|
||||
Short `/tmp/mng-*` paths avoided the length failure in subsequent control,
|
||||
private standard and Mosaic runs. This is a fixture workaround, not a proven
|
||||
compositor bug fix. The first `close_b` runs used a left click that legitimately
|
||||
reopened B; they are retained with `-initial` suffix. The revised right click
|
||||
measures A's continued interaction without reopening B. No private source
|
||||
patch was changed to obtain these results.
|
||||
|
||||
## Independent post-audit replay
|
||||
|
||||
A fresh disposable laboratory was rebuilt from the pinned commits and the
|
||||
same three private patches. The reconstructed SceneFX and Mosaic builds,
|
||||
generated protocol code and both C17 clients succeeded; the Mosaic Meson
|
||||
model test passed. Twelve representative isolated runs plus one final `open`
|
||||
smoke run returned successfully. Direct log,
|
||||
pointer and screenshot checks confirmed both genuine grabs in all three
|
||||
`visible_click` configurations. With `b_cross`, unmodified SwayFX and private
|
||||
`splith` each showed **15,860** B-green pixels on HEADLESS-2; private Mosaic
|
||||
showed **0** there and **17,940** on HEADLESS-1. Mosaic's `(900,190)` click
|
||||
went to `wev`, not B, while standard controls delivered it to B. With
|
||||
`both_cross`, Mosaic showed **0** A-yellow and B-green pixels on HEADLESS-2
|
||||
and delivered separate left-output right clicks to A and B. In `hidden`, both
|
||||
colors fell to **0** and returned to their prior counts with one A/B map each;
|
||||
`hidden_click` dismissed B then A without a hidden popup button. `close_b`
|
||||
left A receiving right press/release, `outside` logged B then A `popup_done`
|
||||
and child-first destruction, and `toggle` restored B-green pixels and B's
|
||||
button delivery on HEADLESS-2 in `splith`. These are fresh confirmations of
|
||||
named samples, not a broader qualification. The original 658-artifact
|
||||
manifest was also rechecked with **658 OK, 0 failures**. Its historical
|
||||
measurements remain described here even if the temporary laboratory is later
|
||||
removed. The post-audit runner changes affect socket isolation and cleanup,
|
||||
not popup actions or the private compositor patches.
|
||||
|
||||
## Protocol and hierarchy evidence
|
||||
|
||||
In `run-control-open-visible/client.log`, A used button serial **61** from
|
||||
TOPLEVEL and B used **66** from POPUP_A. Both respective `GRAB_REQUEST`
|
||||
entries show `used=61` and `used=66`. `POPUP_A CREATE` records
|
||||
`parent_xdg=9` (TOPLEVEL); `POPUP_B CREATE` records `parent_xdg=12`, exactly
|
||||
A's XDG object. The two role configure events were `(50,80,250,170)` and
|
||||
`(20,60,170,130)`; their `xdg_surface.configure` serials 62 and 67 were
|
||||
acknowledged before each MAP. Private standard repeated 61→66 in the first
|
||||
visible run; private Mosaic used distinct real serials **55** and **60**.
|
||||
`WAYLAND_DEBUG=1` logs corroborate client request order. No protocol error,
|
||||
immediate grab denial or pre-configure dismissal was observed in the successful
|
||||
runs. A later right click on B was delivered in all three configurations
|
||||
(control/private standard button serial 71; Mosaic 65, with releases).
|
||||
|
||||
These are real nested XDG grabs, not merely two transient surfaces: B's parent
|
||||
is A's `xdg_surface`, A was mapped before B was created, both were mapped
|
||||
at the `open` capture, and each received pointer interaction. Source colors
|
||||
are blue TOPLEVEL `(35,68,204)`, yellow A `(224,192,32)` and green B
|
||||
`(32,208,160)` in the captured RGB images. Surface IDs and event targets are
|
||||
the primary identity evidence.
|
||||
|
||||
## Visual and pointer observations
|
||||
|
||||
All screenshots are 800×600 for HEADLESS-1 and HEADLESS-2. For `b_cross`,
|
||||
the compositor configured A at `(200,80,300,170)` relative to TOPLEVEL and B
|
||||
at `(220,60,300,130)` relative to A. At `open`, both standard configurations
|
||||
had **15,860** exact B-green pixels on HEADLESS-2; Mosaic had **0** while
|
||||
retaining **17,940** B-green pixels on HEADLESS-1. At global `(700,190)`, B
|
||||
received right press/release in all three cases. In the control, moving to
|
||||
global `(900,190)` yielded B-local `(278,25)` and a further right press/release.
|
||||
The private standard case matched it. In Mosaic, B received a leave, no
|
||||
right-output B enter/button, and the ordinary `wev` on HEADLESS-2 received
|
||||
the right press/release. The Mosaic grab then dismissed B and A. Thus a
|
||||
legitimate grab's dismissal is separated from a hit on an invisible B.
|
||||
|
||||
For `both_cross`, A was configured `(350,80,350,170)` and B
|
||||
`(160,60,300,130)`. At `open`, both standard configurations had **6,120**
|
||||
A-yellow and **27,560** B-green pixels on HEADLESS-2. Mosaic had **0** of
|
||||
both colors there, while retaining **30,080** A-yellow and **6,240** B-green
|
||||
pixels on HEADLESS-1. A right click at global `(700,190)` targeted A and
|
||||
one at `(780,190)` targeted B in Mosaic, confirmed by separate surface logs;
|
||||
the control and private standard also targeted A then B. At `(900,190)`,
|
||||
only the standard layouts delivered the button to B; Mosaic's ordinary
|
||||
right-output client received it and B/A were dismissed. These points test
|
||||
two hierarchy levels rather than treating the whole menu as one colored area.
|
||||
|
||||
On explicit `close_b`, the `POPUP_B DESTROY` entry preceded a right click on
|
||||
A in all three configurations. A received press and release and B did not
|
||||
reappear. The sampled pixel at B's former overlap changed from green to A's
|
||||
yellow. On the outside click `(900,400)`, every configuration logged
|
||||
`POPUP_B POPUP_DONE`, then `POPUP_A POPUP_DONE`, then client destruction B,
|
||||
then A. `wev` got the left press/release. On compositor IPC `kill` of the
|
||||
toplevel, the client logged `TOPLEVEL CLOSE_REQUEST`, destroyed B and A, then
|
||||
destroyed TOPLEVEL; no `popup_done` preceded this normal owner teardown.
|
||||
|
||||
## Hidden owner and standard-layout return
|
||||
|
||||
The Mosaic owner remained IPC container ID **8**, logical rectangle
|
||||
`(640,25,640,575)`, with `scroll_x` **400→1280→400**. Private compositor log
|
||||
`PROBE_POPUP_HIDDEN_OWNER` recorded applied owner `(-640,0,640,600)` against
|
||||
viewport `(0,0,800,600)`, an empty intersection. During `hidden` and
|
||||
`hidden-hover`, both A-yellow and B-green exact pixel counts on HEADLESS-1
|
||||
were **0**, down from **42,200** and **17,940** at `open`. No popup enter,
|
||||
button, `popup_done`, reconfigure, object re-creation or client exit occurred
|
||||
before the hidden measurement. After camera return, both counts returned
|
||||
to **42,200** and **17,940** with the same XDG object IDs. In the separate
|
||||
`hidden_click` case, right click at `(100,190)` produced no A/B button but
|
||||
did produce `popup_done` B then A; the returned frame correctly did not
|
||||
restore them after dismissal. This is sampled guard and grab behavior, not
|
||||
a universal offscreen-popup policy.
|
||||
|
||||
The `toggle` run left Mosaic with B still mapped. HEADLESS-2 local
|
||||
`(100,190)` changed from ordinary `(102,102,102)` to B-green
|
||||
`(32,208,160)` in `splith`, and B then received right press/release at global
|
||||
`(900,190)` without a prior `popup_done`. The private guard did not persist
|
||||
into the sampled standard-layout path.
|
||||
|
||||
## Classification and remaining gates
|
||||
|
||||
The specified horizontal, two-output nested-grab experiment is **FEASIBLE**
|
||||
at its measured placements and event points. This does **not** establish
|
||||
continuous clipping between sample points or a general popup dismissal policy.
|
||||
The startup incident above does not affect the later runs, which started their
|
||||
clients, mapped both popups and produced protocol, pointer and pixel evidence.
|
||||
No architectural guarantee is established for
|
||||
universal grab behavior, complete damage/stale-pixel coverage, decorations,
|
||||
shadows/blur/saved buffers, vertical/negative/scaled/rotated outputs,
|
||||
output resize/migration, transaction-generation agreement, SceneFX ABI or
|
||||
package maintenance. Each of those remains **NOT TESTED** in this probe.
|
||||
The private SceneFX symbols and owner guard remain disposable; no source
|
||||
or patch enters production. STEP20C remains NEXT and cross-output Mosaic
|
||||
remains NOT YET SUPPORTED. The smallest next bounded experiment should
|
||||
exercise a changed output geometry with these same nested grabbed surfaces
|
||||
and compare the control, private standard and private Mosaic paths before any
|
||||
production integration decision.
|
||||
97
tooling/probes/step20c-nested-grab/README.md
Normal file
97
tooling/probes/step20c-nested-grab/README.md
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
# STEP20C nested valid-grab fixture (EXPERIMENTAL)
|
||||
|
||||
This source-only fixture reproduces the [nested popup probe](../../../docs/design/STEP20C_NESTED_VALID_GRAB_PROBE.md).
|
||||
It does not enter the Mosaic build, production patch or package. It reuses
|
||||
the unchanged `pointer-daemon.c`, virtual-pointer XML and three private
|
||||
patches from [`../step20c-valid-grab/`](../step20c-valid-grab/README.md).
|
||||
|
||||
## Bases and dependencies
|
||||
|
||||
| Input | Pinned base / version | SHA-256 |
|
||||
| --- | --- | --- |
|
||||
| Mosaic private patch | Mosaic `cd550382b311efdbfe3ed892f4e1d281d852e754` | `d416f6413ea097a48a44d231e50ffba363914f46c8687f0ea2e4dcacd5f31fe9` |
|
||||
| SceneFX private patch | SceneFX `37ccd723bef49e6891156ffafce8f549f01446cc` (0.5) | `c74c515b98da8f5f637e260e6a3755c7a75bd78d1c39e54a67850f1c383e30e7` |
|
||||
| Hidden-owner guard | After Mosaic private patch | `35785a8f3e863b3c1fd87932d8ff0c2949b03cb04382cc34e1126170968f802b` |
|
||||
| Persistent virtual pointer | Previous fixture | `a00fcedc38ccb7ca4c25c982d9f38e8bde3168a3833f0c9cf2056e841d720938` |
|
||||
| Virtual-pointer XML | wlr-protocols `b010a03648b88d143236de193bddbfea0c08bc84` | `3ff6d540be0bc5228195bf072bde42117ea17945a5c2061add5d3cf97d6bb524` |
|
||||
| `nested-client.c` | This fixture | `2e40c98af72a0df8b316810589a0be8f4425a763db1de448b49a7813956b6f7d` |
|
||||
| `prepare.sh` | This fixture, post-audit failed-build cleanup | `55fe81b7f17fb1cf8af7089a0034864910c49feccbe4cfe22e857c98b9db0dde` |
|
||||
| `run.py` | This fixture, post-audit socket isolation and cleanup | `72ff17d8042edf3265b3a0ff91f0bc4805001b1438c4ecadfe29ead76278c9be` |
|
||||
|
||||
The measured machine had SwayFX 0.6, wlroots 0.20.2, SceneFX 0.5.0,
|
||||
Wayland client 1.26.0 and wayland-protocols 1.49. The installed XDG Shell
|
||||
XML declares version 7; the fixture binds `xdg_wm_base` version 3, like
|
||||
the previous client. Required local tools include Git, Meson, Ninja, C17
|
||||
compiler, `pkg-config`, `wayland-scanner`, `kitty`, `swaymsg`, `grim`, `wev`
|
||||
and Python. `prepare.sh` downloads the pinned SceneFX source into a disposable
|
||||
directory; it does not install anything.
|
||||
|
||||
## Differences from the earlier fixture
|
||||
|
||||
`nested-client.c` creates three distinct surfaces and XDG roles. Popup A's
|
||||
parent is the toplevel `xdg_surface`; popup B's parent is A's `xdg_surface`.
|
||||
Each popup uses a separate, logged, real pointer-button serial and maps only
|
||||
after its own configure/ack. The client logs all surface object IDs, events,
|
||||
local pointer coordinates, `popup_done` and child-first destruction. It accepts
|
||||
`close_b` and `close_owner` on stdin. The runner exercises three placements:
|
||||
`visible`, `b_cross` and `both_cross`. No prior source file is modified.
|
||||
|
||||
## Fresh replay
|
||||
|
||||
Run from a fresh repository checkout at or after the preserved fixture commit.
|
||||
No old `/tmp` laboratory is needed. The script prints `LAB=/tmp/...` after
|
||||
building a private SceneFX library, private guarded compositor, new client and
|
||||
unchanged virtual-pointer daemon. Save that path for subsequent commands.
|
||||
|
||||
```sh
|
||||
prepare_log=$(mktemp /tmp/mng-prepare-XXXXXXXX)
|
||||
sh tooling/probes/step20c-nested-grab/prepare.sh | tee "$prepare_log"
|
||||
LAB=$(sed -n 's/^LAB=//p' "$prepare_log")
|
||||
test -d "$LAB"
|
||||
python tooling/probes/step20c-nested-grab/run.py "$LAB" control visible_click visible
|
||||
python tooling/probes/step20c-nested-grab/run.py "$LAB" private_standard visible_click visible
|
||||
python tooling/probes/step20c-nested-grab/run.py "$LAB" private_mosaic visible_click visible
|
||||
python tooling/probes/step20c-nested-grab/run.py "$LAB" private_mosaic crossing b_cross
|
||||
python tooling/probes/step20c-nested-grab/run.py "$LAB" private_mosaic crossing both_cross
|
||||
python tooling/probes/step20c-nested-grab/run.py "$LAB" private_mosaic close_b visible
|
||||
python tooling/probes/step20c-nested-grab/run.py "$LAB" private_mosaic outside visible
|
||||
python tooling/probes/step20c-nested-grab/run.py "$LAB" private_mosaic hidden b_cross
|
||||
python tooling/probes/step20c-nested-grab/run.py "$LAB" private_mosaic hidden_click b_cross
|
||||
python tooling/probes/step20c-nested-grab/run.py "$LAB" private_mosaic owner visible
|
||||
python tooling/probes/step20c-nested-grab/run.py "$LAB" private_mosaic toggle b_cross
|
||||
```
|
||||
|
||||
For a full comparison, repeat `crossing`, `close_b`, `outside` and `owner`
|
||||
with `control` and `private_standard` as in the report. Each case name is
|
||||
single-use in a lab; use a new lab or rename an earlier case directory to
|
||||
retain both iterations. `run.py` uses a short, new `/tmp/mng-*` runtime path
|
||||
because Unix socket paths have a strict length limit; each case's
|
||||
`runtime-path` records the path for audit. The script removes inherited desktop
|
||||
Wayland/IPC variables, uses explicit `swaymsg -s`, terminates and reaps only
|
||||
its own process groups, then removes its runtime directory. It never reloads
|
||||
or replaces the daily compositor.
|
||||
|
||||
The retained historical runs used the earlier runner and preparation hashes;
|
||||
the current `prepare.sh` removes an incomplete laboratory on failure, while
|
||||
`run.py` adds an IPC path-length bound, clears inherited
|
||||
`I3SOCK`, reaps child processes and removes its runtime directory. It changes
|
||||
no popup protocol action, capture point or experimental compositor patch.
|
||||
|
||||
Each case contains `client.log` with `WAYLAND_DEBUG=1`, `pointer.log`,
|
||||
`compositor.log`, `wev.log`, `tree-*.json`, `outputs-*.json` and both output
|
||||
PNGs. Read `POPUP_A/B CREATE`, `GRAB_REQUEST`, `ROLE_CONFIGURE`,
|
||||
`XDG_CONFIGURE`, `MAP`, `POINTER_*`, `POPUP_DONE` and `DESTROY` in order.
|
||||
Compare screenshots and traces: a right-output pixel alone does not prove
|
||||
which surface received a button, and `popup_done` under a legitimate grab
|
||||
does not prove an invisible surface hit. The `open` scenario intentionally
|
||||
only captures mapping; `visible_click` also presses B. A case returning zero
|
||||
is not automatically a scientific PASS.
|
||||
|
||||
## Cleanup
|
||||
|
||||
After all per-case processes have exited, check `runtime-path` files for any
|
||||
directory left by an interrupted run and remove only those recorded `/tmp/mng-*`
|
||||
directories, then remove the named `LAB` directory and `prepare_log`. Preserve
|
||||
the laboratory while reviewing evidence. Generated
|
||||
protocol files, binaries, private libraries, sockets, logs and images remain
|
||||
outside Git. The source fixture and original patches stay in Git.
|
||||
263
tooling/probes/step20c-nested-grab/nested-client.c
Normal file
263
tooling/probes/step20c-nested-grab/nested-client.c
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
#define _POSIX_C_SOURCE 200809L
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#include <wayland-client.h>
|
||||
#include "xdg-shell-client.h"
|
||||
|
||||
/* EXPERIMENTAL CONTRACT: every popup owns distinct XDG objects. B's actual
|
||||
* parent is A's xdg_surface; logs name object IDs as well as colors. */
|
||||
struct surface {
|
||||
const char *name;
|
||||
struct wl_surface *wl;
|
||||
struct xdg_surface *xdg;
|
||||
struct xdg_popup *popup;
|
||||
bool configured, mapped, done;
|
||||
int width, height, anchor_x, anchor_y;
|
||||
uint32_t color;
|
||||
};
|
||||
static struct wl_display *display;
|
||||
static struct wl_compositor *compositor;
|
||||
static struct wl_shm *shm;
|
||||
static struct xdg_wm_base *wm;
|
||||
static struct wl_seat *seat;
|
||||
static struct wl_pointer *pointer;
|
||||
static struct xdg_toplevel *top_role;
|
||||
static struct surface top = {.name="TOPLEVEL", .width=420, .height=320, .color=0x002344cc};
|
||||
static struct surface a = {.name="POPUP_A", .width=300, .height=170, .color=0x00e0c020};
|
||||
static struct surface b = {.name="POPUP_B", .width=300, .height=130, .color=0x0020d0a0};
|
||||
static volatile sig_atomic_t running = 1;
|
||||
static struct surface *entered;
|
||||
|
||||
static void draw(struct surface *s) {
|
||||
size_t bytes = (size_t)s->width * (size_t)s->height * 4;
|
||||
char path[] = "/tmp/mosaic-nested-buffer-XXXXXX";
|
||||
int fd = mkstemp(path);
|
||||
if (fd < 0 || ftruncate(fd, (off_t)bytes) < 0) { perror("buffer"); exit(1); }
|
||||
unlink(path);
|
||||
uint32_t *pixels = mmap(NULL, bytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (pixels == MAP_FAILED) { perror("mmap"); exit(1); }
|
||||
for (size_t i=0; i<bytes/4; ++i) pixels[i] = s->color;
|
||||
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, (int)bytes);
|
||||
struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, s->width, s->height,
|
||||
s->width*4, WL_SHM_FORMAT_XRGB8888);
|
||||
wl_surface_attach(s->wl, buffer, 0, 0);
|
||||
wl_surface_damage_buffer(s->wl, 0, 0, s->width, s->height);
|
||||
wl_surface_commit(s->wl);
|
||||
wl_buffer_destroy(buffer);
|
||||
wl_shm_pool_destroy(pool);
|
||||
munmap(pixels, bytes);
|
||||
close(fd);
|
||||
s->mapped = true;
|
||||
fprintf(stderr, "%s MAP wl=%u xdg=%u\n", s->name,
|
||||
wl_proxy_get_id((struct wl_proxy *)s->wl), wl_proxy_get_id((struct wl_proxy *)s->xdg));
|
||||
}
|
||||
static void on_ping(void *data, struct xdg_wm_base *base, uint32_t serial) {
|
||||
xdg_wm_base_pong(base, serial);
|
||||
}
|
||||
static const struct xdg_wm_base_listener wm_listener = {.ping=on_ping};
|
||||
static void on_xdg_configure(void *data, struct xdg_surface *xdg, uint32_t serial) {
|
||||
struct surface *s=data;
|
||||
fprintf(stderr, "%s XDG_CONFIGURE serial=%u ack\n", s->name, serial);
|
||||
xdg_surface_ack_configure(xdg, serial);
|
||||
s->configured=true;
|
||||
if (!s->mapped && !s->done) draw(s);
|
||||
}
|
||||
static const struct xdg_surface_listener xdg_listener = {.configure=on_xdg_configure};
|
||||
static void on_top_configure(void *data, struct xdg_toplevel *role, int32_t w,
|
||||
int32_t h, struct wl_array *states) {
|
||||
fprintf(stderr, "TOPLEVEL ROLE_CONFIGURE w=%d h=%d\n", w,h);
|
||||
}
|
||||
static void on_top_close(void *data, struct xdg_toplevel *role) {
|
||||
fprintf(stderr, "TOPLEVEL CLOSE_REQUEST\n"); running=0;
|
||||
}
|
||||
static const struct xdg_toplevel_listener top_listener = {
|
||||
.configure=on_top_configure, .close=on_top_close,
|
||||
};
|
||||
static void on_popup_configure(void *data, struct xdg_popup *role, int32_t x,
|
||||
int32_t y, int32_t w, int32_t h) {
|
||||
struct surface *s=data;
|
||||
fprintf(stderr, "%s ROLE_CONFIGURE x=%d y=%d w=%d h=%d\n",s->name,x,y,w,h);
|
||||
}
|
||||
static void on_popup_done(void *data, struct xdg_popup *role) {
|
||||
struct surface *s=data;
|
||||
s->done=true;
|
||||
s->mapped=false;
|
||||
fprintf(stderr, "%s POPUP_DONE role=%u\n",s->name,wl_proxy_get_id((struct wl_proxy *)role));
|
||||
}
|
||||
static const struct xdg_popup_listener popup_listener = {
|
||||
.configure=on_popup_configure, .popup_done=on_popup_done,
|
||||
};
|
||||
/* WHY: XDG requires child-first destruction even if the compositor sends
|
||||
* dismissals for multiple levels in one dispatch batch. */
|
||||
static void destroy_popup(struct surface *s) {
|
||||
if (!s->popup) return;
|
||||
fprintf(stderr,"%s DESTROY role=%u xdg=%u wl=%u\n",s->name,
|
||||
wl_proxy_get_id((struct wl_proxy *)s->popup),
|
||||
wl_proxy_get_id((struct wl_proxy *)s->xdg),
|
||||
wl_proxy_get_id((struct wl_proxy *)s->wl));
|
||||
xdg_popup_destroy(s->popup);
|
||||
xdg_surface_destroy(s->xdg);
|
||||
wl_surface_destroy(s->wl);
|
||||
s->popup=NULL; s->xdg=NULL; s->wl=NULL; s->mapped=false;
|
||||
if (entered==s) entered=NULL;
|
||||
}
|
||||
static void collect_dismissals(void) {
|
||||
if (b.done) destroy_popup(&b);
|
||||
if (a.done) {
|
||||
destroy_popup(&b);
|
||||
destroy_popup(&a);
|
||||
}
|
||||
}
|
||||
static void open_popup(struct surface *s, struct surface *parent, uint32_t serial) {
|
||||
if (s->popup || !parent->mapped || !serial) return;
|
||||
s->wl=wl_compositor_create_surface(compositor);
|
||||
s->xdg=xdg_wm_base_get_xdg_surface(wm,s->wl);
|
||||
xdg_surface_add_listener(s->xdg,&xdg_listener,s);
|
||||
struct xdg_positioner *pos=xdg_wm_base_create_positioner(wm);
|
||||
xdg_positioner_set_size(pos,s->width,s->height);
|
||||
xdg_positioner_set_anchor_rect(pos,s->anchor_x,s->anchor_y,1,1);
|
||||
xdg_positioner_set_anchor(pos,XDG_POSITIONER_ANCHOR_TOP_LEFT);
|
||||
xdg_positioner_set_gravity(pos,XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
|
||||
s->popup=xdg_surface_get_popup(s->xdg,parent->xdg,pos);
|
||||
xdg_popup_add_listener(s->popup,&popup_listener,s);
|
||||
fprintf(stderr,"%s CREATE wl=%u xdg=%u role=%u parent=%s parent_xdg=%u\n",
|
||||
s->name,wl_proxy_get_id((struct wl_proxy *)s->wl),
|
||||
wl_proxy_get_id((struct wl_proxy *)s->xdg),
|
||||
wl_proxy_get_id((struct wl_proxy *)s->popup),parent->name,
|
||||
wl_proxy_get_id((struct wl_proxy *)parent->xdg));
|
||||
fprintf(stderr,"%s GRAB_REQUEST origin=wl_pointer.button target=%s serial=%u used=%u\n",
|
||||
s->name,parent->name,serial,serial);
|
||||
xdg_popup_grab(s->popup,seat,serial);
|
||||
wl_surface_commit(s->wl); /* Initial bufferless commit requests configure. */
|
||||
xdg_positioner_destroy(pos);
|
||||
}
|
||||
static struct surface *which(struct wl_surface *wl) {
|
||||
if (wl==top.wl) return ⊤
|
||||
if (wl==a.wl && wl) return &a;
|
||||
if (wl==b.wl && wl) return &b;
|
||||
return NULL;
|
||||
}
|
||||
static void on_enter(void *data,struct wl_pointer *ptr,uint32_t serial,
|
||||
struct wl_surface *wl,wl_fixed_t x,wl_fixed_t y) {
|
||||
entered=which(wl);
|
||||
fprintf(stderr,"POINTER_ENTER target=%s wl=%u serial=%u x=%.2f y=%.2f\n",
|
||||
entered?entered->name:"OTHER",wl_proxy_get_id((struct wl_proxy *)wl),serial,
|
||||
wl_fixed_to_double(x),wl_fixed_to_double(y));
|
||||
}
|
||||
static void on_leave(void *data,struct wl_pointer *ptr,uint32_t serial,
|
||||
struct wl_surface *wl) {
|
||||
struct surface *s=which(wl);
|
||||
fprintf(stderr,"POINTER_LEAVE target=%s serial=%u\n",s?s->name:"OTHER",serial);
|
||||
if (entered==s) entered=NULL;
|
||||
}
|
||||
static void on_motion(void *data,struct wl_pointer *ptr,uint32_t time,
|
||||
wl_fixed_t x,wl_fixed_t y) {
|
||||
fprintf(stderr,"POINTER_MOTION target=%s x=%.2f y=%.2f\n",
|
||||
entered?entered->name:"OTHER",wl_fixed_to_double(x),wl_fixed_to_double(y));
|
||||
}
|
||||
static void on_button(void *data,struct wl_pointer *ptr,uint32_t serial,
|
||||
uint32_t time,uint32_t button,uint32_t state) {
|
||||
fprintf(stderr,"POINTER_BUTTON target=%s serial=%u button=%u state=%u\n",
|
||||
entered?entered->name:"OTHER",serial,button,state);
|
||||
if (state!=WL_POINTER_BUTTON_STATE_PRESSED || button!=272) return;
|
||||
if (entered==&top && !a.popup) open_popup(&a,&top,serial);
|
||||
else if (entered==&a && a.mapped && !b.popup) open_popup(&b,&a,serial);
|
||||
}
|
||||
static void on_axis(void *data,struct wl_pointer *ptr,uint32_t t,uint32_t axis,wl_fixed_t v) {}
|
||||
static void on_frame(void *data,struct wl_pointer *ptr) {}
|
||||
static const struct wl_pointer_listener pointer_listener={
|
||||
.enter=on_enter,.leave=on_leave,.motion=on_motion,.button=on_button,
|
||||
.axis=on_axis,.frame=on_frame,
|
||||
};
|
||||
static void on_caps(void *data,struct wl_seat *s,uint32_t caps) {
|
||||
if ((caps & WL_SEAT_CAPABILITY_POINTER) && !pointer) {
|
||||
pointer=wl_seat_get_pointer(s);
|
||||
wl_pointer_add_listener(pointer,&pointer_listener,NULL);
|
||||
}
|
||||
}
|
||||
static void on_seat_name(void *data,struct wl_seat *s,const char *name) {}
|
||||
static const struct wl_seat_listener seat_listener={.capabilities=on_caps,.name=on_seat_name};
|
||||
static void on_global(void *data,struct wl_registry *reg,uint32_t name,
|
||||
const char *interface,uint32_t version) {
|
||||
if (!strcmp(interface,wl_compositor_interface.name))
|
||||
compositor=wl_registry_bind(reg,name,&wl_compositor_interface,version<4?version:4);
|
||||
else if (!strcmp(interface,wl_shm_interface.name))
|
||||
shm=wl_registry_bind(reg,name,&wl_shm_interface,1);
|
||||
else if (!strcmp(interface,xdg_wm_base_interface.name))
|
||||
wm=wl_registry_bind(reg,name,&xdg_wm_base_interface,version<3?version:3);
|
||||
else if (!strcmp(interface,wl_seat_interface.name)) {
|
||||
seat=wl_registry_bind(reg,name,&wl_seat_interface,version<5?version:5);
|
||||
wl_seat_add_listener(seat,&seat_listener,NULL);
|
||||
}
|
||||
}
|
||||
static void on_removed(void *data,struct wl_registry *reg,uint32_t name) {}
|
||||
static const struct wl_registry_listener registry_listener={.global=on_global,.global_remove=on_removed};
|
||||
static void stop(int sig) { running=0; }
|
||||
int main(int argc,char **argv) {
|
||||
signal(SIGTERM,stop);
|
||||
const char *placement=argc>1?argv[1]:"b_cross";
|
||||
if (!strcmp(placement,"visible")) {
|
||||
a.anchor_x=50; a.anchor_y=80; a.width=250;
|
||||
b.anchor_x=20; b.anchor_y=60; b.width=170;
|
||||
} else if (!strcmp(placement,"both_cross")) {
|
||||
a.anchor_x=350; a.anchor_y=80; a.width=350;
|
||||
b.anchor_x=160; b.anchor_y=60; b.width=300;
|
||||
} else if (!strcmp(placement,"b_cross")) {
|
||||
a.anchor_x=200; a.anchor_y=80; a.width=300;
|
||||
b.anchor_x=220; b.anchor_y=60; b.width=300;
|
||||
} else { fprintf(stderr,"invalid placement\n"); return 2; }
|
||||
display=wl_display_connect(NULL);
|
||||
if (!display) { fprintf(stderr,"cannot connect Wayland\n"); return 1; }
|
||||
struct wl_registry *registry=wl_display_get_registry(display);
|
||||
wl_registry_add_listener(registry,®istry_listener,NULL);
|
||||
wl_display_roundtrip(display); wl_display_roundtrip(display);
|
||||
if (!compositor || !shm || !wm || !seat || !pointer) {
|
||||
fprintf(stderr,"missing Wayland globals/pointer\n"); return 1;
|
||||
}
|
||||
xdg_wm_base_add_listener(wm,&wm_listener,NULL);
|
||||
top.wl=wl_compositor_create_surface(compositor);
|
||||
top.xdg=xdg_wm_base_get_xdg_surface(wm,top.wl);
|
||||
xdg_surface_add_listener(top.xdg,&xdg_listener,&top);
|
||||
top_role=xdg_surface_get_toplevel(top.xdg);
|
||||
xdg_toplevel_add_listener(top_role,&top_listener,NULL);
|
||||
xdg_toplevel_set_title(top_role,"Mosaic Nested Grab Probe");
|
||||
xdg_toplevel_set_app_id(top_role,"org.mosaic.NestedGrabProbe");
|
||||
fprintf(stderr,"TOPLEVEL CREATE wl=%u xdg=%u role=%u placement=%s\n",
|
||||
wl_proxy_get_id((struct wl_proxy *)top.wl),
|
||||
wl_proxy_get_id((struct wl_proxy *)top.xdg),
|
||||
wl_proxy_get_id((struct wl_proxy *)top_role),placement);
|
||||
wl_surface_commit(top.wl);
|
||||
while (running) {
|
||||
wl_display_flush(display);
|
||||
struct pollfd fds[2]={{.fd=wl_display_get_fd(display),.events=POLLIN},
|
||||
{.fd=STDIN_FILENO,.events=POLLIN}};
|
||||
int ready=poll(fds,2,500);
|
||||
if (ready<0) { if (errno==EINTR) continue; perror("poll"); break; }
|
||||
if (fds[0].revents & (POLLIN|POLLERR|POLLHUP)) {
|
||||
if (wl_display_dispatch(display)<0) break;
|
||||
collect_dismissals();
|
||||
}
|
||||
if (fds[1].revents & POLLIN) {
|
||||
char cmd[64];
|
||||
if (fgets(cmd,sizeof(cmd),stdin)) {
|
||||
if (!strncmp(cmd,"close_b",7)) { destroy_popup(&b); fprintf(stderr,"COMMAND close_b DONE\n"); }
|
||||
else if (!strncmp(cmd,"close_owner",11)) { fprintf(stderr,"COMMAND close_owner\n"); running=0; }
|
||||
}
|
||||
}
|
||||
}
|
||||
destroy_popup(&b); destroy_popup(&a);
|
||||
fprintf(stderr,"TOPLEVEL DESTROY\n");
|
||||
xdg_toplevel_destroy(top_role); xdg_surface_destroy(top.xdg); wl_surface_destroy(top.wl);
|
||||
wl_display_flush(display);
|
||||
wl_display_disconnect(display);
|
||||
return 0;
|
||||
}
|
||||
35
tooling/probes/step20c-nested-grab/prepare.sh
Normal file
35
tooling/probes/step20c-nested-grab/prepare.sh
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#!/bin/sh
|
||||
# EXPERIMENTAL: build all private inputs in a fresh temporary laboratory.
|
||||
set -eu
|
||||
repo=$(CDPATH= cd -- "$(dirname -- "$0")/../../.." && pwd)
|
||||
fixture="$repo/tooling/probes/step20c-valid-grab"
|
||||
lab=$(mktemp -d /tmp/swayfx-mosaic-nested-grab-XXXXXXXX)
|
||||
# CONTRACT: a failed preparation leaves no unreported private build or socket
|
||||
# tree; a successful one transfers LAB ownership to the caller.
|
||||
trap 'rm -rf -- "$lab"' 0
|
||||
mkdir "$lab/mosaic" "$lab/scenefx"
|
||||
git -C "$repo" archive cd550382b311efdbfe3ed892f4e1d281d852e754 | tar -xf - -C "$lab/mosaic"
|
||||
git clone --quiet https://github.com/wlrfx/scenefx.git "$lab/scenefx-git"
|
||||
git -C "$lab/scenefx-git" checkout --quiet --detach 37ccd723bef49e6891156ffafce8f549f01446cc
|
||||
git -C "$lab/scenefx-git" archive HEAD | tar -xf - -C "$lab/scenefx"
|
||||
(cd "$lab/scenefx" && git apply --check "$fixture/scenefx-popup-plus-base.patch" && git apply "$fixture/scenefx-popup-plus-base.patch")
|
||||
(cd "$lab/mosaic" && git apply --check "$fixture/mosaic-popup-plus-base.patch" && git apply "$fixture/mosaic-popup-plus-base.patch" && git apply --check "$fixture/hidden-guard.patch" && git apply "$fixture/hidden-guard.patch")
|
||||
meson setup "$lab/scenefx-build" "$lab/scenefx" -Dexamples=false -Dwerror=false
|
||||
meson compile -C "$lab/scenefx-build" -j 8
|
||||
PKG_CONFIG_PATH="$lab/scenefx-build/meson-uninstalled" meson setup "$lab/mosaic-build" "$lab/mosaic" \
|
||||
-Dmosaic-package=true -Ddefault-wallpaper=false -Dswaybar=false -Dswaynag=false \
|
||||
-Dman-pages=disabled -Dsd-bus-provider=libsystemd -Dwerror=false
|
||||
PKG_CONFIG_PATH="$lab/scenefx-build/meson-uninstalled" meson compile -C "$lab/mosaic-build" -j 8
|
||||
wayland-scanner client-header /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml "$lab/xdg-shell-client.h"
|
||||
wayland-scanner private-code /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml "$lab/xdg-shell-protocol.c"
|
||||
wayland-scanner client-header "$fixture/wlr-virtual-pointer-unstable-v1.xml" "$lab/virtual-pointer-client.h"
|
||||
wayland-scanner private-code "$fixture/wlr-virtual-pointer-unstable-v1.xml" "$lab/virtual-pointer-protocol.c"
|
||||
cc -std=c17 -Wall -Wextra -Wno-unused-parameter -I"$lab" \
|
||||
"$repo/tooling/probes/step20c-nested-grab/nested-client.c" "$lab/xdg-shell-protocol.c" \
|
||||
-o "$lab/nested-client" $(pkg-config --cflags --libs wayland-client)
|
||||
cc -std=c17 -Wall -Wextra -Wno-unused-parameter -I"$lab" \
|
||||
"$fixture/pointer-daemon.c" "$lab/virtual-pointer-protocol.c" \
|
||||
-o "$lab/pointer-daemon" $(pkg-config --cflags --libs wayland-client)
|
||||
sha256sum "$fixture"/*.patch "$repo/tooling/probes/step20c-nested-grab/nested-client.c" > "$lab/source-sha256.txt"
|
||||
trap - 0
|
||||
printf 'LAB=%s\n' "$lab"
|
||||
263
tooling/probes/step20c-nested-grab/run.py
Normal file
263
tooling/probes/step20c-nested-grab/run.py
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
"""EXPERIMENTAL isolated two-output nested XDG grab runner.
|
||||
|
||||
Usage: python run.py LAB {control,private_standard,private_mosaic} SCENARIO PLACEMENT
|
||||
SCENARIO: open, visible_click, crossing, close_b, outside, hidden, hidden_click, owner, toggle
|
||||
PLACEMENT: visible, b_cross, both_cross
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import pathlib
|
||||
import shutil
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
LAB = pathlib.Path(sys.argv[1]).resolve()
|
||||
case, scenario, placement = sys.argv[2:5]
|
||||
assert case in ('control', 'private_standard', 'private_mosaic')
|
||||
assert scenario in ('open', 'visible_click', 'crossing', 'close_b', 'outside', 'hidden',
|
||||
'hidden_click', 'owner', 'toggle')
|
||||
assert placement in ('visible', 'b_cross', 'both_cross')
|
||||
assert LAB.is_dir() and (LAB / 'nested-client').is_file()
|
||||
run = LAB / f'run-{case}-{scenario}-{placement}'
|
||||
run.mkdir(mode=0o700, exist_ok=False)
|
||||
runtime = pathlib.Path(tempfile.mkdtemp(prefix='mng-', dir='/tmp'))
|
||||
# WHY: keep both Unix socket names below sockaddr_un.sun_path even if LAB is
|
||||
# long; the compositor derives its IPC path from XDG_RUNTIME_DIR.
|
||||
if len(os.fsencode(str(runtime / 'sway-ipc.4294967295.2147483647.sock'))) >= 108:
|
||||
raise RuntimeError('private IPC socket path exceeds Linux sockaddr_un.sun_path')
|
||||
(run / 'runtime-path').write_text(str(runtime) + '\n')
|
||||
(run / 'config').write_text(
|
||||
'output HEADLESS-1 resolution 800x600 position 0 0\n'
|
||||
'output HEADLESS-2 resolution 800x600 position 800 0\n'
|
||||
'workspace 1 output HEADLESS-1\nworkspace 2 output HEADLESS-2\n'
|
||||
'focus_follows_mouse no\n')
|
||||
env = os.environ.copy()
|
||||
env.update(XDG_RUNTIME_DIR=str(runtime), WLR_BACKENDS='headless',
|
||||
WLR_HEADLESS_OUTPUTS='2', WLR_RENDERER='pixman')
|
||||
env.pop('WAYLAND_DISPLAY', None)
|
||||
env.pop('SWAYSOCK', None)
|
||||
env.pop('I3SOCK', None)
|
||||
env.pop('LD_LIBRARY_PATH', None)
|
||||
compositor_env = env.copy()
|
||||
if case == 'control':
|
||||
binary = pathlib.Path('/usr/bin/sway')
|
||||
else:
|
||||
binary = LAB / 'mosaic-build/sway/swayfx-mosaic'
|
||||
compositor_env['LD_LIBRARY_PATH'] = str(LAB / 'scenefx-build')
|
||||
files = []
|
||||
children = []
|
||||
|
||||
|
||||
def spawn(argv, name, *, debug=False, stdin=None):
|
||||
log = (run / name).open('w')
|
||||
files.append(log)
|
||||
child_env = env.copy()
|
||||
if debug:
|
||||
child_env['WAYLAND_DEBUG'] = '1'
|
||||
proc = subprocess.Popen(argv, env=child_env, stdin=stdin, stdout=log,
|
||||
stderr=subprocess.STDOUT, text=True,
|
||||
start_new_session=True)
|
||||
children.append(proc)
|
||||
return proc
|
||||
|
||||
|
||||
def wait(fn, seconds=12):
|
||||
end = time.monotonic() + seconds
|
||||
while time.monotonic() < end:
|
||||
result = fn()
|
||||
if result:
|
||||
return result
|
||||
if compositor.poll() is not None:
|
||||
raise RuntimeError(f'compositor exited {compositor.returncode}')
|
||||
time.sleep(.05)
|
||||
raise RuntimeError('timeout waiting for probe event')
|
||||
|
||||
|
||||
def ipc(*args):
|
||||
return json.loads(subprocess.run(['swaymsg', '-s', str(sock), *args],
|
||||
env=env, text=True, capture_output=True, check=True,
|
||||
timeout=5).stdout)
|
||||
|
||||
|
||||
def find(node, predicate):
|
||||
if isinstance(node, dict):
|
||||
if predicate(node):
|
||||
return node
|
||||
for child in node.get('nodes', []) + node.get('floating_nodes', []):
|
||||
result = find(child, predicate)
|
||||
if result:
|
||||
return result
|
||||
return None
|
||||
|
||||
|
||||
def tree():
|
||||
return ipc('-t', 'get_tree')
|
||||
|
||||
|
||||
def ws():
|
||||
return find(tree(), lambda n: n.get('type') == 'workspace' and n.get('name') == '1')
|
||||
|
||||
|
||||
def owner():
|
||||
return find(tree(), lambda n: n.get('app_id') == 'org.mosaic.NestedGrabProbe')
|
||||
|
||||
|
||||
def logged(s):
|
||||
path = run / 'client.log'
|
||||
return path.exists() and s in path.read_text()
|
||||
|
||||
|
||||
def capture(tag):
|
||||
(run / f'tree-{tag}.json').write_text(json.dumps(tree(), indent=2))
|
||||
(run / f'outputs-{tag}.json').write_text(json.dumps(ipc('-t', 'get_outputs'), indent=2))
|
||||
for output, label in (('HEADLESS-1', 'left'), ('HEADLESS-2', 'right')):
|
||||
subprocess.run(['grim', '-o', output, str(run / f'{label}-{tag}.png')],
|
||||
env=env, check=True, timeout=8, stdout=subprocess.DEVNULL)
|
||||
|
||||
|
||||
def inject(action, x, y):
|
||||
marker = f'INJECT_DONE {action} {x},{y}'
|
||||
pointer.stdin.write(f'{action} {x} {y}\n')
|
||||
pointer.stdin.flush()
|
||||
wait(lambda: marker in (run / 'pointer.log').read_text())
|
||||
|
||||
|
||||
def command(value):
|
||||
client.stdin.write(value + '\n')
|
||||
client.stdin.flush()
|
||||
wait(lambda: f'COMMAND {value}' in (run / 'client.log').read_text())
|
||||
|
||||
|
||||
compositor_log = (run / 'compositor.log').open('w')
|
||||
files.append(compositor_log)
|
||||
compositor = None
|
||||
try:
|
||||
compositor = subprocess.Popen([str(binary), '-c', str(run / 'config'), '-d'],
|
||||
env=compositor_env, stdout=compositor_log,
|
||||
stderr=subprocess.STDOUT, start_new_session=True)
|
||||
def sockets():
|
||||
s = next(iter(runtime.glob('sway-ipc.*.sock')), None)
|
||||
w = next((p for p in runtime.glob('wayland-*') if p.suffix != '.lock'), None)
|
||||
return (s, w) if s and w else None
|
||||
|
||||
sock, way = wait(sockets)
|
||||
env.update(SWAYSOCK=str(sock), WAYLAND_DISPLAY=way.name)
|
||||
pointer = spawn([str(LAB / 'pointer-daemon')], 'pointer.log',
|
||||
stdin=subprocess.PIPE)
|
||||
wait(lambda: 'POINTER_READY' in (run / 'pointer.log').read_text())
|
||||
ipc('workspace 1')
|
||||
ipc('layout mosaic' if case == 'private_mosaic' else 'layout splith')
|
||||
spawn(['kitty', '--class', 'A', '-o', 'background=#aa2222',
|
||||
'/usr/bin/sleep', '90'], 'A.log')
|
||||
wait(lambda: len(ws()['nodes']) >= 1)
|
||||
client = spawn([str(LAB / 'nested-client'), placement], 'client.log',
|
||||
debug=True, stdin=subprocess.PIPE)
|
||||
wait(lambda: owner() and logged('TOPLEVEL MAP'))
|
||||
for label in ('C', 'D'):
|
||||
spawn(['kitty', '--class', label, '/usr/bin/sleep', '90'], label + '.log')
|
||||
time.sleep(.35)
|
||||
ipc('workspace 2')
|
||||
spawn(['stdbuf', '-oL', 'wev'], 'wev.log')
|
||||
time.sleep(.5)
|
||||
ipc('workspace 1')
|
||||
ipc('[app_id="org.mosaic.NestedGrabProbe"] focus')
|
||||
if case == 'private_mosaic':
|
||||
current = ws()['mosaic']['rows'][0]['scroll_x']
|
||||
ipc(f'layout mosaic scroll {400-current:g}')
|
||||
time.sleep(.4)
|
||||
o = owner()
|
||||
scroll = ws()['mosaic']['rows'][0]['scroll_x'] if case == 'private_mosaic' else 0
|
||||
rendered_x = o['rect']['x'] - scroll
|
||||
top_x = int(max(40, min(760, rendered_x + 100)))
|
||||
inject('left', top_x, 150)
|
||||
wait(lambda: logged('POPUP_A MAP'))
|
||||
anchor_a = {'visible': 50, 'b_cross': 200, 'both_cross': 350}[placement]
|
||||
a_x = int(max(40, min(760, rendered_x + anchor_a + 40)))
|
||||
inject('left', a_x, 150)
|
||||
wait(lambda: logged('POPUP_B MAP'))
|
||||
time.sleep(.3)
|
||||
capture('open')
|
||||
print('CHAIN_OPEN', case, scenario, placement, 'owner_id', o['id'],
|
||||
'owner_rect', o['rect'], 'scroll', scroll,
|
||||
'top_click', top_x, 'a_click', a_x,
|
||||
'client_alive', client.poll() is None, flush=True)
|
||||
if scenario == 'visible_click':
|
||||
anchor_b = 20
|
||||
b_x = int(max(40, min(760, rendered_x + anchor_a + anchor_b + 40)))
|
||||
inject('right', b_x, 190)
|
||||
time.sleep(.2)
|
||||
capture('child-click')
|
||||
print('B_CLICK_POINT', b_x, 190, flush=True)
|
||||
elif scenario == 'crossing':
|
||||
actions = [('move', 700, 190), ('right', 700, 190)]
|
||||
if placement == 'both_cross':
|
||||
actions += [('move', 780, 190), ('right', 780, 190)]
|
||||
actions += [('move', 900, 190), ('right', 900, 190)]
|
||||
for action, x, y in actions:
|
||||
inject(action, x, y)
|
||||
time.sleep(.2)
|
||||
capture(f'{action}-{x}')
|
||||
elif scenario == 'close_b':
|
||||
command('close_b')
|
||||
time.sleep(.3)
|
||||
capture('closed-b')
|
||||
inject('right', a_x, 150)
|
||||
time.sleep(.25)
|
||||
capture('parent-click')
|
||||
elif scenario == 'outside':
|
||||
inject('left', 900, 400)
|
||||
time.sleep(.4)
|
||||
capture('outside')
|
||||
elif scenario in ('hidden', 'hidden_click'):
|
||||
assert case == 'private_mosaic'
|
||||
ipc('layout mosaic scroll 880')
|
||||
time.sleep(.5)
|
||||
capture('hidden')
|
||||
print('HIDDEN_STATE', ws()['mosaic'], 'owner_id', owner()['id'],
|
||||
'a_done', logged('POPUP_A POPUP_DONE'),
|
||||
'b_done', logged('POPUP_B POPUP_DONE'), flush=True)
|
||||
inject('move', 100, 190)
|
||||
time.sleep(.2)
|
||||
capture('hidden-hover')
|
||||
if scenario == 'hidden_click':
|
||||
inject('right', 100, 190)
|
||||
time.sleep(.2)
|
||||
capture('hidden-click')
|
||||
ipc('layout mosaic scroll -880')
|
||||
time.sleep(.4)
|
||||
capture('returned')
|
||||
elif scenario == 'owner':
|
||||
ipc(f'[con_id={o["id"]}] kill')
|
||||
time.sleep(.4)
|
||||
capture('owner-destroyed')
|
||||
elif scenario == 'toggle':
|
||||
assert case == 'private_mosaic'
|
||||
ipc('layout splith')
|
||||
time.sleep(.4)
|
||||
capture('standard')
|
||||
inject('right', 900, 190)
|
||||
time.sleep(.2)
|
||||
capture('standard-click')
|
||||
print('FINAL', 'a_done', logged('POPUP_A POPUP_DONE'),
|
||||
'b_done', logged('POPUP_B POPUP_DONE'),
|
||||
'client_alive', client.poll() is None, flush=True)
|
||||
finally:
|
||||
for child in children:
|
||||
if child.poll() is None:
|
||||
os.killpg(child.pid, signal.SIGTERM)
|
||||
if compositor is not None and compositor.poll() is None:
|
||||
os.killpg(compositor.pid, signal.SIGTERM)
|
||||
# CONTRACT: only process groups started by this runner are terminated.
|
||||
# Reap them before removing their private socket directory.
|
||||
for proc in [*children, *([compositor] if compositor is not None else [])]:
|
||||
try:
|
||||
proc.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
os.killpg(proc.pid, signal.SIGKILL)
|
||||
proc.wait(timeout=5)
|
||||
for file in files:
|
||||
file.close()
|
||||
shutil.rmtree(runtime)
|
||||
Loading…
Reference in a new issue