docs(mosaic): preserve vertical nested-grab probe evidence
This commit is contained in:
parent
52174ceb6f
commit
4c1c395423
11 changed files with 991 additions and 2 deletions
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
### STEP20C experimental evidence
|
||||
|
||||
- Added a source-only stacked-output nested valid-grab probe. Both standard controls showed real popup overflow and pointer delivery below `y=600`; the private guarded Mosaic build clipped A/B at sampled lower points, preserved hidden-owner and standard-layout return, and reproduced the horizontal control. STEP20C production support remains open.
|
||||
- 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.
|
||||
|
||||
|
|
|
|||
|
|
@ -75,6 +75,16 @@ 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.
|
||||
|
||||
The [vertical-output nested-grab probe](design/STEP20C_VERTICAL_OUTPUT_NESTED_GRAB_PROBE.md)
|
||||
then placed HEADLESS-2 below HEADLESS-1 and used a parameterized private
|
||||
virtual pointer. Unmodified SwayFX and private `splith` both rendered and
|
||||
received input on A/B popup portions actually crossing `y=600`; the guarded
|
||||
private Mosaic build omitted those sampled lower pixels and popup hits while
|
||||
preserving upper interaction. A hidden owner returned with the same mapped
|
||||
popups, and `splith` restored the sampled ordinary lower-output behavior.
|
||||
This is vertical popup evidence only. The production compositor still has no
|
||||
qualified cross-output viewport clip or committed scene/transaction 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.
|
||||
|
|
|
|||
|
|
@ -36,6 +36,17 @@ 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 [vertical-output nested-grab probe](design/STEP20C_VERTICAL_OUTPUT_NESTED_GRAB_PROBE.md)
|
||||
is **FEASIBLE for its measured matrix only**. On two stacked 800×600 headless
|
||||
outputs, the standard controls showed real A/B XDG popup pixels and buttons
|
||||
below `y=600`, while the disposable guarded Mosaic build clipped sampled
|
||||
lower pixels and hits and left upper popup interaction intact. Hidden-owner
|
||||
and standard-layout return cases also passed at their measured points. This
|
||||
does not qualify tiled-window Y overflow, continuous or complete damage
|
||||
coverage, other output geometries, migrations, transaction generations,
|
||||
effects, or SceneFX ABI/package maintenance. Production adjacent-output
|
||||
Mosaic remains **NOT YET SUPPORTED**.
|
||||
|
||||
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**.
|
||||
|
|
|
|||
|
|
@ -21,7 +21,9 @@ The [root README](../README.md) is the public summary. For a specific topic, use
|
|||
[Preserved source fixtures](../tooling/probes/step20c-valid-grab/README.md) record exact inputs and a disposable replay procedure.
|
||||
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.
|
||||
14. [STEP20C vertical-output nested-grab probe](design/STEP20C_VERTICAL_OUTPUT_NESTED_GRAB_PROBE.md): sampled vertical popup clipping and pointer evidence with two standard controls and private Mosaic; production gates remain open.
|
||||
[Vertical source fixture](../tooling/probes/step20c-vertical-grab/README.md) replays it in a disposable laboratory.
|
||||
15. [STEP20A](STEP20A.md) and [STEP20B](STEP20B.md): historical evidence.
|
||||
|
||||
## Project
|
||||
|
||||
|
|
@ -58,6 +60,7 @@ The [root README](../README.md) is the public summary. For a specific topic, use
|
|||
| [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 |
|
||||
| [STEP20C vertical-output nested-grab probe](design/STEP20C_VERTICAL_OUTPUT_NESTED_GRAB_PROBE.md) | Experimental evidence | FEASIBLE for sampled two-level grabbed XDG popup cases across the horizontal output boundary; 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,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. 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.
|
||||
**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; the separate vertical probe covers sampled Y-crossing popups. Further 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.
|
||||
|
||||
|
|
@ -36,6 +36,17 @@ 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.
|
||||
|
||||
The [vertical-output nested-grab probe](design/STEP20C_VERTICAL_OUTPUT_NESTED_GRAB_PROBE.md)
|
||||
is **FEASIBLE within its sampled stacked-headless matrix**: both standard
|
||||
controls produced real Y-crossing A/B popup pixels and button targets, while
|
||||
the disposable guarded Mosaic build excluded sampled lower popup pixels/hits
|
||||
and preserved upper interaction, hidden-owner return and standard-layout
|
||||
return. Its [source fixture](../tooling/probes/step20c-vertical-grab/README.md)
|
||||
also reproduced the horizontal B-crossing control. The next separate geometry
|
||||
gate is a bounded negative-origin comparison. Scaling, rotation,
|
||||
resize/migration, transaction generations, complete damage/effects and
|
||||
dependency maintenance remain open. STEP20C stays **NEXT**.
|
||||
|
||||
## 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.
|
||||
|
|
|
|||
343
docs/design/STEP20C_VERTICAL_OUTPUT_NESTED_GRAB_PROBE.md
Normal file
343
docs/design/STEP20C_VERTICAL_OUTPUT_NESTED_GRAB_PROBE.md
Normal file
|
|
@ -0,0 +1,343 @@
|
|||
# STEP20C vertical-output nested valid-grab probe
|
||||
|
||||
**Status: EXPERIMENTAL / FEASIBLE for the sampled vertical matrix.** This
|
||||
protocol was written before adapting the preserved fixtures; measurements
|
||||
were appended as the experiments ran. It does not authorize production clipping,
|
||||
an offscreen-popup UX policy, or multi-output support. The canonical
|
||||
[architecture](../ARCHITECTURE.md), [UX](../UX.md), [limitations](../KNOWN_LIMITATIONS.md)
|
||||
and [roadmap](../ROADMAP.md) retain authority. The horizontal
|
||||
[nested-grab probe](STEP20C_NESTED_VALID_GRAB_PROBE.md) is historical evidence
|
||||
and remains byte-identical.
|
||||
|
||||
## Hypotheses and compared systems
|
||||
|
||||
Unmodified SwayFX in `splith` (A) and private SceneFX/Mosaic in `splith` (B)
|
||||
should render and route a truly overflowing nested popup into the lower
|
||||
output. The guarded private binary in `mosaic` (C) should retain visible
|
||||
popup interaction above the boundary and omit popup pixels and hits below it.
|
||||
The same two real pointer-button serials must create A and B in every case.
|
||||
An outside click may dismiss both under a valid grab without delivering a
|
||||
button to a clipped popup. A compositor with no measured overflow cannot
|
||||
qualify the clip hypothesis.
|
||||
|
||||
## Geometry and coordinate contract
|
||||
|
||||
Request HEADLESS-1 `(0,0) 800×600` and HEADLESS-2 `(0,600) 800×600`.
|
||||
Workspace 1 and its tiled Mosaic owner belong to HEADLESS-1; workspace 2
|
||||
hosts ordinary `wev`. Record `get_outputs` before using any coordinate.
|
||||
The global logical rectangle is `[0,800) × [0,1200)` only if IPC confirms
|
||||
the requested positions, resolution, scale and transform. The Mosaic
|
||||
viewport is the applied workspace/output intersection on HEADLESS-1; row
|
||||
`scroll_x` translates the tiled scene horizontally and does not change Y.
|
||||
|
||||
The virtual pointer's absolute request uses global-layout extents
|
||||
`(800,1200)` vertically and `(1600,600)` for the horizontal regression.
|
||||
The historical injector is hard-coded to `(1600,600)` and must remain
|
||||
unchanged. The new injector validates every requested point against its
|
||||
configured extents, logs request and button completion, and stays alive
|
||||
through each case. An injected coordinate is a request, not proof of the
|
||||
cursor's actual target: correlate it with the client's `wl_pointer.enter`,
|
||||
motion and button local coordinates, the lower-output `wev` events, and
|
||||
screenshots. Record a mismatch as an injection/targeting issue before making
|
||||
a compositor claim.
|
||||
|
||||
XDG positioner anchor rectangles are relative to their parent surface:
|
||||
A to the toplevel, B to A. `xdg_popup.configure` reports a popup rectangle
|
||||
relative to that parent. A candidate global popup rectangle is the applied
|
||||
toplevel content origin plus A's configured offset, and for B additionally
|
||||
plus B's configured offset. Verify that conversion against captured pixels
|
||||
and pointer-local coordinates; the IPC owner `rect` is logical and its X
|
||||
coordinate alone is insufficient after Mosaic camera translation. The
|
||||
root popup scene is separately positioned in the global popup layer, with
|
||||
its owner-derived clip translated into local scene coordinates.
|
||||
|
||||
SwayFX calls `wlr_xdg_popup_unconstrain_from_box` on initial popup commit and
|
||||
reposition using the owner's output box expressed in toplevel coordinates.
|
||||
The preserved client's positioner sets anchor and gravity but no constraint
|
||||
adjustment; XDG Shell specifies default `none`. Do not infer the final
|
||||
placement from the requested anchor: retain both `ROLE_CONFIGURE` rectangles
|
||||
and pixels. If an attempted crossing is flipped, slid, resized or otherwise
|
||||
entirely within HEADLESS-1, classify that case **INCONCLUSIVE** and try a
|
||||
bounded alternative placement or size in the private client fixture. Never
|
||||
force a compositor production position.
|
||||
|
||||
XDG Shell also requires the positioner's anchor rectangle to remain inside
|
||||
the parent's effective window geometry. A review after the first 21-run
|
||||
campaign found that the copied historical toplevel buffer was only 320 px
|
||||
high while the new A anchors were at `y=350` and `y=480`. No protocol error
|
||||
was logged, but this violates that specification. Those initial overflow,
|
||||
hidden-owner and toggle measurements are **INCONCLUSIVE as XDG compliance
|
||||
evidence**. The private client was corrected to use a 575 px toplevel buffer;
|
||||
the observed effective `get_tree` window geometry became `420×575`, enclosing
|
||||
both A anchors at `y=350/480`. B's `y=155/80` anchors remain within A's
|
||||
170 px height. The full 21-run matrix below was repeated from that corrected
|
||||
source. The historical source and compositor patches remain unchanged.
|
||||
|
||||
## Planned matrix and acceptance
|
||||
|
||||
| Scenario | Candidate placement | Required observations | Status |
|
||||
| --- | --- | --- | --- |
|
||||
| Two valid grabs, both fully visible | A and B high in HEADLESS-1 | Distinct parents, button serials, configure/ack/map, A/B clicks, no lower-output pixels | PASS at sampled points |
|
||||
| B crosses `y=600` | A above boundary, B near its bottom | Control A and B show B below; Mosaic shows none there and B still works above | PASS at sampled points after corrected replay |
|
||||
| A and B cross `y=600` | A near toplevel bottom, B within A | Separate A/B lower pixels in controls; separate upper hits, no lower popup hits in Mosaic | PASS at sampled points after corrected replay |
|
||||
| Below-boundary input | Point chosen from the actual control B/A pixels | Separate move, left and right; popup local events versus `wev` and `popup_done` | PASS at sampled points after corrected replay |
|
||||
| Owner hidden and returned | `scroll_x` 400→1280→400 | Same owner and popup IDs, applied scene/clip evidence, no hidden pixels/hits, same mapped objects return | PASS at sampled points after corrected replay |
|
||||
| Hidden owner clicked separately | Same, with an outside click while hidden | No hidden popup button; record dismissal and absence of return after it | PASS at sampled points after corrected replay |
|
||||
| Return to `splith` with popups open | Candidate crossing still mapped | Lower pixels and popup target return without prior dismissal | PASS at sampled points after corrected replay |
|
||||
| Horizontal regression | `(0,0)` and `(800,0)` with historical B crossing | Valid grabs, original left/right pixel and pointer observations | PASS at sampled points |
|
||||
|
||||
For each A/B/C case, save effective output and workspace IPC JSON, owner
|
||||
identity/rectangle and camera, `WAYLAND_DEBUG=1` client protocol, configured
|
||||
popup geometry, compositor/scene logs, injection log, `wev` events, both
|
||||
output PNGs and the chosen pixel/hit points. Treat visual visibility,
|
||||
pointer delivery, grab dismissal and coordinate agreement as four separate
|
||||
observations. A zero runner exit alone is insufficient. The first boundary
|
||||
point must come from the *actual* control popup pixels below `y=600`, never
|
||||
from the old horizontal `(900,190)` point. A candidate with no lower-output
|
||||
popup pixels in the control is **INCONCLUSIVE** for overflow.
|
||||
|
||||
**FEASIBLE** requires real vertical overflow in both standard controls,
|
||||
visual and interactive exclusion at sampled lower points in Mosaic, valid
|
||||
grabs, and no blocking standard-layout regression. **PARTIALLY_FEASIBLE**
|
||||
records a narrower demonstrated subset. A reproducible private compositor
|
||||
pixel/hit leak, protocol violation or blocking standard regression is
|
||||
**BLOCKED**. **INCONCLUSIVE** means the fixture cannot establish overflow or
|
||||
cannot distinguish targeting from compositor behavior. Individual unrun
|
||||
cells remain **NOT TESTED**.
|
||||
|
||||
## Laboratory and remaining boundaries
|
||||
|
||||
Build from the prior pinned Mosaic and SceneFX commits, applying the same
|
||||
private patches in the same order. Keep all build trees, protocol output,
|
||||
short mode-0700 socket directories, captures and logs outside Git. Clear
|
||||
inherited desktop Wayland/IPC variables and pass every IPC command its
|
||||
discovered private socket. Load private SceneFX only in the private compositor
|
||||
process. Start and terminate only identified child process groups; preserve
|
||||
the log and actual exit status on startup failure. No test may use the live
|
||||
desktop socket or replace installed libraries.
|
||||
|
||||
This vertical popup experiment does not qualify tiled-window Y overflow,
|
||||
other output geometries, scaling, rotation, migration, transaction-generation
|
||||
agreement, complete damage/stale-pixel coverage, effects or SceneFX ABI and
|
||||
package maintenance. The previous horizontal probe is a control, not a
|
||||
production precedent. STEP20C remains NEXT and cross-output Mosaic remains
|
||||
NOT YET SUPPORTED regardless of a narrow positive result.
|
||||
|
||||
## Observations and retained evidence
|
||||
|
||||
The first fresh unmodified SwayFX `visible` control confirmed IPC output
|
||||
rectangles `(0,0,800,600)` and `(0,600,800,600)`, scale `1.0`, transform
|
||||
`normal`. A's grab used received TOPLEVEL button serial 61; B's used received
|
||||
POPUP_A button serial 66. Both role configure/ack/map sequences completed,
|
||||
and separate right clicks reached A and B without dismissal.
|
||||
|
||||
The first `v_b_cross` control configured A at `(50,300,250,170)` relative to
|
||||
TOPLEVEL and B at `(20,155,170,130)` relative to A. With the applied owner
|
||||
content origin estimated at `(202,25)`, B's candidate global rectangle was
|
||||
`(272,480,170,130)`. The HEADLESS-2 `open` capture contained **1,700** exact
|
||||
B-green pixels, and the requested `(312,605)` yielded a B-local pointer
|
||||
motion `(40,125)` and a right press/release on B. The corresponding
|
||||
HEADLESS-1 frame had **20,400** B-green pixels. Thus the standard control
|
||||
really crossed `y=600`; this is not yet a Mosaic clipping result. Artifacts:
|
||||
`/tmp/swayfx-mosaic-nested-grab-YNqcOBkl/run-vertical-control-{visible,b_cross}/`.
|
||||
The original horizontal reports and fixtures remain unchanged.
|
||||
|
||||
The first A/B/C `v_b_cross` comparison found the same **1,700** lower-output
|
||||
B-green pixels in unmodified and private `splith`, versus **0** in guarded
|
||||
Mosaic. Above the boundary, B received a right press/release in all three.
|
||||
At `(312,605)` in the unmodified control and `(352,605)` in Mosaic, the
|
||||
control B received B-local `(40,125)` and right press/release; Mosaic B
|
||||
received no lower button and the grab dismissed B then A. However, `wev`'s
|
||||
actual IPC rectangle begins at `y=625`, so this first lower point was over
|
||||
the destination workspace decoration. **Ordinary-client delivery is PARTIAL
|
||||
at this point.** The initial Mosaic `visible` run also failed solely because
|
||||
the runner wrote a floating-point text coordinate (`534.0`) to the integer
|
||||
injector; its `INVALID_INJECTION` log is retained with suffix
|
||||
`-initial-invalid`. The runner now validates and converts integral values,
|
||||
and a fresh visible run delivered separate right clicks to A and B. The
|
||||
next bounded placement increases B's measured overflow to place a lower
|
||||
sample inside the actual `wev` rectangle. These fixture findings are not
|
||||
classified as compositor defects.
|
||||
|
||||
In a rebuilt laboratory using the revised `v_b_cross` placement, both
|
||||
standard configurations configured A `(50,350,250,170)` and B
|
||||
`(20,155,170,130)`; B's estimated global rectangle was
|
||||
`(272,530,170,130)`. Each standard lower-output capture contained
|
||||
**10,200** B-green pixels, and each delivered the `(312,640)` right
|
||||
press/release to B at local `(40,110)`. Guarded Mosaic configured the same
|
||||
relative rectangles; B's estimated global rectangle was
|
||||
`(312,530,170,130)`. Its lower capture contained **0** B-green pixels,
|
||||
while the left capture retained **11,900**. B received the upper right
|
||||
press/release but no lower button. The ordinary `wev` at `(352,640)` received
|
||||
the lower right press/release; the legitimate grab subsequently emitted
|
||||
`popup_done` B then A. This is a measured B-only vertical visual/input clip
|
||||
at these points. Artifacts:
|
||||
`/tmp/swayfx-mosaic-nested-grab-gbU3A8Mk/run-vertical-{control,private_standard,private_mosaic}-b_cross/`.
|
||||
|
||||
The three `v_both_cross` runs configured A `(50,480,250,170)` and B
|
||||
`(20,80,170,130)`. Both standard lower frames contained **6,000** A-yellow
|
||||
and **19,550** B-green pixels; private Mosaic contained **0** of either
|
||||
color there while retaining **21,200** A-yellow and **2,550** B-green above.
|
||||
Separate upper right clicks reached A and B in all three. At the lower B
|
||||
point, standard layouts delivered right press/release to B, while Mosaic
|
||||
delivered it to `wev` and dismissed B then A without a lower popup button.
|
||||
This tests both levels visually and their separate *upper* hits. A separate
|
||||
lower A hit is measured in the `below_a` runs described next. Artifacts:
|
||||
`/tmp/swayfx-mosaic-nested-grab-gbU3A8Mk/run-vertical-{control,private_standard,private_mosaic}-both_cross/`.
|
||||
|
||||
Separate lower-input runs at `y=640` resolve the hit target: in standard
|
||||
layouts, B received the left press/release at `x=312`, and A received the
|
||||
right press/release at `x=260` outside B. In Mosaic, neither lower click
|
||||
reached a popup; `wev` received left at `x=352` and right at `x=300`, and
|
||||
the grab dismissed B then A. Upper B and A clicks remained delivered before
|
||||
those lower samples. These are separate runs because an outside click can
|
||||
legitimately dismiss the stack. Artifacts: the same laboratory's
|
||||
`run-vertical-{control,private_standard,private_mosaic}-{below_left,below_a}/`
|
||||
directories. The lower right B comparison is the `b_cross` run above.
|
||||
|
||||
With B crossing in guarded Mosaic, `hidden` kept IPC owner ID **8** and
|
||||
`scroll_x` **400→1280→400**. The private scene log reported applied owner
|
||||
`(-640,0,640,600)` and viewport `(0,0,800,600)`. A/B exact left-output
|
||||
pixel counts were **39,950/11,900** at open, **0/0** while hidden and on
|
||||
hidden hover, then **39,950/11,900** after return. Each popup mapped once;
|
||||
neither received `popup_done` before return. In the separate `hidden_click`
|
||||
run, no hidden popup button was delivered, `popup_done` occurred B then A,
|
||||
and neither popup returned after camera restoration. The `toggle` run kept
|
||||
both mapped through `layout splith`; B-green pixels on HEADLESS-2 changed
|
||||
from **0** to **10,200**, and B received a right press/release at
|
||||
`(312,640)` without prior dismissal. Artifacts:
|
||||
`/tmp/swayfx-mosaic-nested-grab-gbU3A8Mk/run-vertical-private_mosaic-{hidden,hidden_click,toggle}/`.
|
||||
|
||||
The adapted pointer also replayed the historical horizontal `b_cross`
|
||||
geometry in all three configurations using `(1600,600)` extents. The two
|
||||
standard lower-output captures had **15,860** B-green pixels and B received
|
||||
the right-output right press/release. Guarded Mosaic had **0** B-green pixels
|
||||
there, **17,940** on HEADLESS-1, no lower B button, and a right press/release
|
||||
on `wev`; B then A were dismissed. All three used two distinct real grab
|
||||
serials and mapped both popups. This matches the preserved horizontal result
|
||||
at the historical points. Artifacts:
|
||||
`/tmp/swayfx-mosaic-nested-grab-gbU3A8Mk/run-horizontal-{control,private_standard,private_mosaic}-b_cross/`.
|
||||
|
||||
The main fresh laboratory contains **18 validated vertical** and **3
|
||||
horizontal** run directories. Its `artifacts-sha256.txt` lists **669** files;
|
||||
the manifest SHA-256 is
|
||||
`91830d91906039a5c9205f5b05e14f347969d516e2a5f13313969c9a78bf14a9`
|
||||
and `sha256sum -c` returned **669 OK, 0 failures**. These are temporary
|
||||
artifacts outside Git; the exact measured observations remain in this report
|
||||
if that laboratory is later removed. The retained first shallow crossing
|
||||
and rejected floating-coordinate injection are in the earlier temporary lab
|
||||
`/tmp/swayfx-mosaic-nested-grab-YNqcOBkl/`, not included in this manifest.
|
||||
The first full 21-run laboratory
|
||||
`/tmp/swayfx-mosaic-nested-grab-yCtCIiUB/` is also excluded from the
|
||||
corrected verdict and manifest: its 320 px toplevel left the vertical A
|
||||
anchors outside the parent's effective window geometry.
|
||||
The corrected fixture's SHA-256 values and rebuild commands are in its
|
||||
[README](../../tooling/probes/step20c-vertical-grab/README.md).
|
||||
|
||||
The sampled result is **FEASIBLE** for the vertical nested-grab matrix above.
|
||||
The standard controls establish actual Y overflow; the private Mosaic build
|
||||
clips measured A/B pixels and input below `y=600` while preserving upper
|
||||
interaction, hidden-owner camera return and standard-layout behavior. This
|
||||
is not complete damage correctness, continuous clip coverage between samples,
|
||||
all tiled-content Y overflow, a transaction-generation proof, or a SceneFX
|
||||
ABI/package decision. Output scaling, rotation, negative origins,
|
||||
resize/migration, decoration/shadow/blur/saved-buffer coverage and live
|
||||
deployment remain **NOT TESTED** here. The next smallest distinct gate is a
|
||||
separate negative-origin output geometry comparison with the same hierarchy
|
||||
and direct unmodified/standard controls; it was not started in this probe.
|
||||
STEP20C remains NEXT and multi-output Mosaic remains NOT YET SUPPORTED.
|
||||
|
||||
## Validation and provenance
|
||||
|
||||
### Independent freeze replay
|
||||
|
||||
A fresh local Git clone at `/tmp/mvg-freeze-clone-37QJ9Bq3/` received only
|
||||
the five reviewed vertical fixture source files from this worktree. Its
|
||||
`prepare.sh` reconstructed the pinned SceneFX and Mosaic sources, applied
|
||||
the three unchanged private compositor/library patches in their documented
|
||||
order and the two client-only patches to temporary copies, regenerated both
|
||||
Wayland protocols, and rebuilt the private libraries, compositor and C17
|
||||
clients. The new laboratory is
|
||||
`/tmp/swayfx-mosaic-nested-grab-5MXNLRED/`; its build log is
|
||||
`/tmp/mvg-prepare-DCyxEBUY`. `vertical-source-sha256.txt` verifies all six
|
||||
new patch/script and patched-client entries against those sources.
|
||||
|
||||
The replay executed **17** scenarios: `b_cross`, `both_cross`, `below_left`
|
||||
and `below_a` in each of unmodified SwayFX `splith`, private `splith` and
|
||||
private `mosaic`; private Mosaic `hidden` and `toggle`; and `b_cross` in all
|
||||
three configurations with the original horizontal output arrangement. All
|
||||
17 runners exited zero, but the verdict also checks their client protocol,
|
||||
pointer and `wev` logs, effective IPC geometry, scene log and both captures.
|
||||
Every vertical run reported outputs `(0,0,800,600)` and
|
||||
`(0,600,800,600)` with scale 1 and normal transform; the pointer extent was
|
||||
`800×1200`. The effective toplevel `get_tree` geometry was `420×575`.
|
||||
For B-only crossing, A configured `(50,350,250,170)` and B
|
||||
`(20,155,170,130)`; for both-level crossing, A configured
|
||||
`(50,480,250,170)` and B `(20,80,170,130)`. Each A anchor lies within the
|
||||
575-pixel toplevel and each B anchor within A's 170-pixel height. Two
|
||||
distinct received `wl_pointer.button` press serials matched the A and B
|
||||
`GRAB_REQUEST` serials, and both configure/ack/map chains completed without
|
||||
logged XDG protocol error.
|
||||
|
||||
At `open`, both new standard B-crossing runs again contained **10,200**
|
||||
B-green pixels on HEADLESS-2, versus **0** in private Mosaic. Both-level
|
||||
crossing again contained **6,000** A-yellow and **19,550** B-green pixels
|
||||
on HEADLESS-2 in each standard control, versus **0/0** in Mosaic. The
|
||||
controls delivered the measured lower B and A presses to the corresponding
|
||||
popup; Mosaic delivered those presses to `wev`, with no lower popup press,
|
||||
after upper popup interaction. `popup_done` followed B then A in the
|
||||
outside-click cases. This dismissal is distinct from a hit on a clipped
|
||||
popup. In the new `hidden` run, owner ID 8 and its logical rectangle stayed
|
||||
fixed while `scroll_x` changed **400→1280→400**; one mapped A and B each
|
||||
remained the same XDG objects, with no `popup_done`. Their left-output
|
||||
color counts were **39,950/11,900 → 0/0 → 39,950/11,900**. The scene log
|
||||
recorded applied owner `(-640,0,640,600)` against viewport
|
||||
`(0,0,800,600)`. With both still open, `toggle` restored **10,200** B-green
|
||||
pixels below the boundary and a button on B after `layout splith`. The
|
||||
three horizontal runs repeated the historical standard **15,860**
|
||||
B-green pixels on HEADLESS-2 versus **0** in private Mosaic, with the
|
||||
same popup-versus-`wev` button distinction. No measured divergence was
|
||||
found between this replay and the corrected historical campaign.
|
||||
|
||||
The new lab's `freeze-artifacts-sha256.txt` contains **574** run/command
|
||||
evidence files. Its SHA-256 is
|
||||
`c5666bc236dbfff52996295fee15c378af0baa2d657ec76ed4c7df172889337d`;
|
||||
`sha256sum -c` verified **574/574** entries. The files remain temporary
|
||||
and outside Git. An initial attempt using a source-only `git archive`
|
||||
failed before any client or scientific scenario because the reused
|
||||
preparation needs the pinned commit in a Git object database; its failed
|
||||
build log is `/tmp/mvg-prepare-tZOrcirM`, and no completed `LAB=` was
|
||||
reported. The full local clone resolved that fixture setup error. The
|
||||
README now states this prerequisite. No scientific fixture or private
|
||||
compositor patch was changed during freeze.
|
||||
|
||||
### Original corrected campaign
|
||||
|
||||
The final 21-case replay used one immutable set of private compositor/client
|
||||
sources and the fixture `run.py` SHA-256
|
||||
`b7dc778c01d9e8e1f058c1aa2bb0e3e0e8dfa6ad1ba8e5af122a0503cd7eef96`.
|
||||
All 21 runs exited normally; independent checks of `client.log`, `wev.log`,
|
||||
`pointer.log`, IPC, exact-color masks and ordered XDG events passed. Their
|
||||
`measurements.json` records configured rectangles and sampling points;
|
||||
`exit-status.json` records the actual compositor/child results. Each output
|
||||
geometry was verified by IPC before injecting input. All 21 short private
|
||||
runtime directories were removed and no probe process remained.
|
||||
|
||||
The private build used SceneFX `37ccd723bef49e6891156ffafce8f549f01446cc`
|
||||
(`0.5.0`), Mosaic `cd550382b311efdbfe3ed892f4e1d281d852e754`,
|
||||
wlroots `0.20.2`, Wayland client `1.26.0` and system control SwayFX `0.6`.
|
||||
`ldd` resolved the private Mosaic binary to the laboratory
|
||||
`libscenefx-0.5.so` and system `libwlroots-0.20.so`. The three unchanged
|
||||
private compositor/library patches and two new client-only patches applied
|
||||
with `git apply --check`; `vertical-source-sha256.txt` verified **6/6**
|
||||
source entries. Private SceneFX/Mosaic builds and generated protocol files
|
||||
succeeded. Shell and Python syntax checks, C17 syntax checks for both
|
||||
clients, the Mosaic Meson model test (**1/1**), documentation links,
|
||||
`git diff --check` and the 669-artifact manifest passed. An initial version
|
||||
query used the wrong `pkg-config` name (`scenefx`); the corrected
|
||||
`scenefx-0.5-uninstalled` query returned `0.5.0`. This command error did not
|
||||
affect a build or run.
|
||||
|
||||
No production Mosaic source, production patch, `PKGBUILD`, installed library,
|
||||
UX contract or historical fixture was changed. No commit or push belongs to
|
||||
this experiment.
|
||||
115
tooling/probes/step20c-vertical-grab/README.md
Normal file
115
tooling/probes/step20c-vertical-grab/README.md
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
# STEP20C vertical-output nested grab fixture (EXPERIMENTAL)
|
||||
|
||||
This source-only fixture reuses the pinned private
|
||||
[nested-grab build](../step20c-nested-grab/README.md) and the unchanged
|
||||
[valid-grab patches](../step20c-valid-grab/README.md). It does not enter the
|
||||
ordinary Mosaic build, production patch or package. The comparative protocol,
|
||||
observations and limits are in the
|
||||
[vertical probe report](../../../docs/design/STEP20C_VERTICAL_OUTPUT_NESTED_GRAB_PROBE.md).
|
||||
|
||||
## Provenance and SHA-256
|
||||
|
||||
| Input | Role / origin | SHA-256 |
|
||||
| --- | --- | --- |
|
||||
| SceneFX private patch | Unchanged, SceneFX `37ccd723bef49e6891156ffafce8f549f01446cc` | `c74c515b98da8f5f637e260e6a3755c7a75bd78d1c39e54a67850f1c383e30e7` |
|
||||
| Mosaic private patch | Unchanged, Mosaic `cd550382b311efdbfe3ed892f4e1d281d852e754` | `d416f6413ea097a48a44d231e50ffba363914f46c8687f0ea2e4dcacd5f31fe9` |
|
||||
| Hidden-owner guard | Unchanged, after Mosaic private patch | `35785a8f3e863b3c1fd87932d8ff0c2949b03cb04382cc34e1126170968f802b` |
|
||||
| `client-vertical.patch` | Adds three vertical placements and a 575 px toplevel buffer to a temporary copy of the preserved `nested-client.c` | `58e1c41cde875ebf2edf25adb0ae98c03cb33aaf26ab4088a8782880a366e837` |
|
||||
| `pointer-extents.patch` | Adds validated WIDTH/HEIGHT arguments to a temporary copy of the preserved `pointer-daemon.c` | `4eeb9f808d0941edca82dc3c5d24a9c936cc3d9d8bb80af3db6aed6af0d47c50` |
|
||||
| `prepare.sh` | Reuses the earlier pinned private build, applies the two fixture patches and recompiles only the clients | `6003004eaa3cbd005ca29d3e2319e1e8fe15377d4a883d352b27e289d70fbb52` |
|
||||
| `run.py` | Isolated comparative runner, derived from preserved nested-grab runner | `b7dc778c01d9e8e1f058c1aa2bb0e3e0e8dfa6ad1ba8e5af122a0503cd7eef96` |
|
||||
|
||||
The patched laboratory clients have SHA-256
|
||||
`b2381f088994202a0577318b2da3bcf799362c6d5308be1e66a6bdd55a1fc76b`
|
||||
(`nested-client.c`) and
|
||||
`ede9a39adbe1744c771a784f6ad0c296259e36b942c78d75be620b86b9f2a66d`
|
||||
(`pointer-daemon.c`). The originals and all three compositor/library patches
|
||||
remain byte-identical. `client-vertical.patch` adds `v_visible`,
|
||||
`v_b_cross` and `v_both_cross` and sets the vertical toplevel buffer height
|
||||
to 575 px. A's `y=350/480` anchors then remain inside its effective window
|
||||
geometry; horizontal client behavior is unchanged. `pointer-extents.patch` keeps left/right
|
||||
button behavior and rejects invalid coordinates; the runner passes
|
||||
`800 1200` vertically or `1600 600` horizontally. The virtual pointer stays
|
||||
alive for the case. The copied runner changes output geometry, verifies IPC
|
||||
output rectangles/scale/transform, derives sample points from configured
|
||||
popup geometry and the actual `wev` rectangle, saves `measurements.json`,
|
||||
and records process exit statuses. It does not change production C.
|
||||
|
||||
The measured environment used SwayFX 0.6 (`/usr/bin/sway`), SceneFX 0.5,
|
||||
wlroots 0.20.2, Wayland client 1.26.0 and wayland-protocols 1.49. Required
|
||||
tools: Git, Meson, Ninja, a C17 compiler, `pkg-config`, `wayland-scanner`,
|
||||
Python, `kitty`, `swaymsg`, `grim` and `wev`. The prior `prepare.sh` fetches
|
||||
the pinned SceneFX commit into a disposable directory, applies its private
|
||||
patch first, then the Mosaic patch and hidden-owner guard, builds private
|
||||
SceneFX and Mosaic, generates the XDG and virtual-pointer protocols and
|
||||
compiles both clients. This wrapper applies the two new patches only to
|
||||
temporary client copies and recompiles them. No system installation occurs.
|
||||
|
||||
## Fresh replay
|
||||
|
||||
Run from the root of a full Git checkout with the pinned Mosaic commit in its
|
||||
object database. A source-only `git archive` is insufficient because the
|
||||
reused nested-grab preparation runs `git archive` on that pinned commit.
|
||||
The build prints a real `LAB=` path and the
|
||||
private build log path. Capture that output rather than substituting a
|
||||
fictitious temporary directory. An unsuccessful preparation prints its log
|
||||
path and cleans its incomplete laboratory.
|
||||
|
||||
```sh
|
||||
prep=$(sh tooling/probes/step20c-vertical-grab/prepare.sh)
|
||||
printf '%s\n' "$prep"
|
||||
LAB=$(printf '%s\n' "$prep" | sed -n 's/^LAB=//p')
|
||||
test -d "$LAB"
|
||||
python tooling/probes/step20c-vertical-grab/run.py "$LAB" control visible vertical
|
||||
python tooling/probes/step20c-vertical-grab/run.py "$LAB" private_standard visible vertical
|
||||
python tooling/probes/step20c-vertical-grab/run.py "$LAB" private_mosaic visible vertical
|
||||
python tooling/probes/step20c-vertical-grab/run.py "$LAB" control b_cross vertical
|
||||
python tooling/probes/step20c-vertical-grab/run.py "$LAB" private_standard b_cross vertical
|
||||
python tooling/probes/step20c-vertical-grab/run.py "$LAB" private_mosaic b_cross vertical
|
||||
python tooling/probes/step20c-vertical-grab/run.py "$LAB" control both_cross vertical
|
||||
python tooling/probes/step20c-vertical-grab/run.py "$LAB" private_standard both_cross vertical
|
||||
python tooling/probes/step20c-vertical-grab/run.py "$LAB" private_mosaic both_cross vertical
|
||||
python tooling/probes/step20c-vertical-grab/run.py "$LAB" control below_left vertical
|
||||
python tooling/probes/step20c-vertical-grab/run.py "$LAB" private_standard below_left vertical
|
||||
python tooling/probes/step20c-vertical-grab/run.py "$LAB" private_mosaic below_left vertical
|
||||
python tooling/probes/step20c-vertical-grab/run.py "$LAB" control below_a vertical
|
||||
python tooling/probes/step20c-vertical-grab/run.py "$LAB" private_standard below_a vertical
|
||||
python tooling/probes/step20c-vertical-grab/run.py "$LAB" private_mosaic below_a vertical
|
||||
python tooling/probes/step20c-vertical-grab/run.py "$LAB" private_mosaic hidden vertical
|
||||
python tooling/probes/step20c-vertical-grab/run.py "$LAB" private_mosaic hidden_click vertical
|
||||
python tooling/probes/step20c-vertical-grab/run.py "$LAB" private_mosaic toggle vertical
|
||||
python tooling/probes/step20c-vertical-grab/run.py "$LAB" control b_cross horizontal
|
||||
python tooling/probes/step20c-vertical-grab/run.py "$LAB" private_standard b_cross horizontal
|
||||
python tooling/probes/step20c-vertical-grab/run.py "$LAB" private_mosaic b_cross horizontal
|
||||
```
|
||||
|
||||
Each run creates a single-use `run-GEOMETRY-CASE-SCENARIO` directory in `LAB`.
|
||||
Its `outputs-verified.json`, IPC tree/output snapshots, `measurements.json`,
|
||||
`client.log` (`WAYLAND_DEBUG=1`), `pointer.log`, `wev.log`, `compositor.log`,
|
||||
two-output PNGs and `exit-status.json` are the evidence. `runtime-path`
|
||||
records a new short mode-0700 `/tmp/mvg-*` directory; the runner removes it
|
||||
after reaping only processes it started. It clears inherited
|
||||
`WAYLAND_DISPLAY`, `SWAYSOCK`, `I3SOCK` and client `LD_LIBRARY_PATH`;
|
||||
private SceneFX is loaded only by the private compositor. Every `swaymsg`
|
||||
uses the discovered socket with `-s`. The daily desktop is never addressed.
|
||||
|
||||
Verify the configured A/B XDG rectangles and matching real button serials
|
||||
before using any pixel comparison. A crossing estimate is insufficient:
|
||||
standard controls must show popup pixels on HEADLESS-2 and receive the
|
||||
sampled lower button. Compare Mosaic upper popup buttons, lower `wev`
|
||||
buttons, `popup_done` order and both PNGs. The runner can finish normally
|
||||
even when scientific evidence is **INCONCLUSIVE**; use the report criteria.
|
||||
One outside click may dismiss the grabbed hierarchy, so left, right and A
|
||||
lower tests are separate runs. The `toggle` case changes layout with both
|
||||
popups still open. Horizontal regression uses the same adapted pointer with
|
||||
its historical `(1600,600)` extents.
|
||||
|
||||
## Cleanup
|
||||
|
||||
Preserve the lab until measurements and hashes have been reviewed. A normal
|
||||
run removes its own runtime directory. For an interrupted run, inspect its
|
||||
`runtime-path` and remove only that recorded `/tmp/mvg-*` directory after
|
||||
confirming its private processes have stopped. Then remove only the named
|
||||
`LAB` and the printed `BUILD_LOG`. Never remove the old historical labs or
|
||||
any system library. Generated sources, binaries, sockets, logs and captures
|
||||
stay outside Git.
|
||||
28
tooling/probes/step20c-vertical-grab/client-vertical.patch
Normal file
28
tooling/probes/step20c-vertical-grab/client-vertical.patch
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
diff --git a/nested-client.c b/nested-client.c
|
||||
--- a/nested-client.c
|
||||
+++ b/nested-client.c
|
||||
@@ -205,9 +205,24 @@
|
||||
int main(int argc,char **argv) {
|
||||
signal(SIGTERM,stop);
|
||||
const char *placement=argc>1?argv[1]:"b_cross";
|
||||
+ /* CONTRACT: vertical positioner anchors remain inside the toplevel's
|
||||
+ * effective window geometry, which defaults to its buffer bounds. */
|
||||
+ if (!strncmp(placement,"v_",2)) top.height=575;
|
||||
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,"v_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,"v_b_cross")) {
|
||||
+ /* WHY: only B's requested rectangle reaches below y=600 when the
|
||||
+ * tiled toplevel content begins near y=25. Configure and pixels
|
||||
+ * remain the authority for the actual crossing. */
|
||||
+ a.anchor_x=50; a.anchor_y=350; a.width=250;
|
||||
+ b.anchor_x=20; b.anchor_y=155; b.width=170;
|
||||
+ } else if (!strcmp(placement,"v_both_cross")) {
|
||||
+ a.anchor_x=50; a.anchor_y=480; a.width=250;
|
||||
+ b.anchor_x=20; b.anchor_y=80; 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;
|
||||
58
tooling/probes/step20c-vertical-grab/pointer-extents.patch
Normal file
58
tooling/probes/step20c-vertical-grab/pointer-extents.patch
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
diff --git a/pointer-daemon.c b/pointer-daemon.c
|
||||
--- a/pointer-daemon.c
|
||||
+++ b/pointer-daemon.c
|
||||
@@ -1,4 +1,5 @@
|
||||
#define _DEFAULT_SOURCE
|
||||
+#include <errno.h>
|
||||
#include <linux/input-event-codes.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
@@ -15,7 +16,22 @@
|
||||
version < 2 ? version : 2);
|
||||
}
|
||||
static void removed(void *data, struct wl_registry *registry, uint32_t name) {}
|
||||
-int main(void) {
|
||||
+static uint32_t extent(const char *arg) {
|
||||
+ char *end;
|
||||
+ errno = 0;
|
||||
+ unsigned long value = strtoul(arg, &end, 10);
|
||||
+ if (errno || end == arg || *end || value == 0 || value > UINT32_MAX) {
|
||||
+ fprintf(stderr, "INVALID_EXTENT %s\n", arg);
|
||||
+ exit(2);
|
||||
+ }
|
||||
+ return (uint32_t)value;
|
||||
+}
|
||||
+int main(int argc, char **argv) {
|
||||
+ if (argc != 3) {
|
||||
+ fprintf(stderr, "usage: pointer-daemon WIDTH HEIGHT\n");
|
||||
+ return 2;
|
||||
+ }
|
||||
+ uint32_t width = extent(argv[1]), height = extent(argv[2]);
|
||||
struct wl_display *display=wl_display_connect(NULL);
|
||||
if (!display) return 1;
|
||||
struct wl_registry *registry=wl_display_get_registry(display);
|
||||
@@ -26,14 +42,20 @@
|
||||
struct zwlr_virtual_pointer_v1 *pointer=
|
||||
zwlr_virtual_pointer_manager_v1_create_virtual_pointer(manager,NULL);
|
||||
wl_display_roundtrip(display);
|
||||
- fprintf(stderr,"POINTER_READY\n");fflush(stderr);
|
||||
+ fprintf(stderr,"POINTER_READY extent=%u,%u\n",width,height);fflush(stderr);
|
||||
char line[128];
|
||||
uint32_t stamp=1000;
|
||||
while (fgets(line,sizeof(line),stdin)) {
|
||||
char action[16];int x,y;
|
||||
- if (sscanf(line,"%15s %d %d",action,&x,&y)!=3) continue;
|
||||
- fprintf(stderr,"INJECT %s %d,%d\n",action,x,y);fflush(stderr);
|
||||
- zwlr_virtual_pointer_v1_motion_absolute(pointer,stamp++,x,y,1600,600);
|
||||
+ if (sscanf(line,"%15s %d %d",action,&x,&y)!=3 ||
|
||||
+ (strcmp(action,"move") && strcmp(action,"left") && strcmp(action,"right")) ||
|
||||
+ x < 0 || y < 0 || (uint32_t)x >= width || (uint32_t)y >= height) {
|
||||
+ fprintf(stderr,"INVALID_INJECTION %s",line);fflush(stderr);
|
||||
+ return 3;
|
||||
+ }
|
||||
+ fprintf(stderr,"INJECT %s %d,%d extent=%u,%u\n",action,x,y,width,height);
|
||||
+ fflush(stderr);
|
||||
+ zwlr_virtual_pointer_v1_motion_absolute(pointer,stamp++,x,y,width,height);
|
||||
zwlr_virtual_pointer_v1_frame(pointer);
|
||||
wl_display_flush(display);usleep(250000);
|
||||
if (!strcmp(action,"left") || !strcmp(action,"right")) {
|
||||
33
tooling/probes/step20c-vertical-grab/prepare.sh
Normal file
33
tooling/probes/step20c-vertical-grab/prepare.sh
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
#!/bin/sh
|
||||
# EXPERIMENTAL: reuse the pinned private build, then adapt only lab clients.
|
||||
set -eu
|
||||
repo=$(CDPATH= cd -- "$(dirname -- "$0")/../../.." && pwd)
|
||||
fixture="$repo/tooling/probes/step20c-vertical-grab"
|
||||
prior="$repo/tooling/probes/step20c-nested-grab"
|
||||
build_log=$(mktemp /tmp/mvg-prepare-XXXXXXXX)
|
||||
if ! sh "$prior/prepare.sh" > "$build_log" 2>&1; then
|
||||
printf 'PRIVATE_BUILD_FAILED log=%s\n' "$build_log" >&2
|
||||
tail -40 "$build_log" >&2
|
||||
exit 1
|
||||
fi
|
||||
lab=$(sed -n 's/^LAB=//p' "$build_log")
|
||||
test -n "$lab" && test -d "$lab"
|
||||
trap 'rm -rf -- "$lab"' 0
|
||||
# CONTRACT: the original sources and private compositor patches stay intact;
|
||||
# these deltas apply only to copies in the disposable laboratory.
|
||||
cp "$prior/nested-client.c" "$lab/nested-client.c"
|
||||
cp "$repo/tooling/probes/step20c-valid-grab/pointer-daemon.c" "$lab/pointer-daemon.c"
|
||||
(cd "$lab" && git apply --check "$fixture/client-vertical.patch" &&
|
||||
git apply "$fixture/client-vertical.patch" &&
|
||||
git apply --check "$fixture/pointer-extents.patch" &&
|
||||
git apply "$fixture/pointer-extents.patch")
|
||||
cc -std=c17 -Wall -Wextra -Wno-unused-parameter -I"$lab" \
|
||||
"$lab/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" \
|
||||
"$lab/pointer-daemon.c" "$lab/virtual-pointer-protocol.c" -o "$lab/pointer-daemon" \
|
||||
$(pkg-config --cflags --libs wayland-client)
|
||||
sha256sum "$fixture"/*.patch "$fixture"/prepare.sh "$fixture"/run.py \
|
||||
"$lab/nested-client.c" "$lab/pointer-daemon.c" > "$lab/vertical-source-sha256.txt"
|
||||
trap - 0
|
||||
printf 'LAB=%s\nBUILD_LOG=%s\n' "$lab" "$build_log"
|
||||
376
tooling/probes/step20c-vertical-grab/run.py
Normal file
376
tooling/probes/step20c-vertical-grab/run.py
Normal file
|
|
@ -0,0 +1,376 @@
|
|||
"""EXPERIMENTAL vertical/horizontal two-output nested XDG grab runner.
|
||||
|
||||
Usage: python run.py LAB {control,private_standard,private_mosaic} SCENARIO GEOMETRY
|
||||
SCENARIO: visible, b_cross, both_cross, below_left, below_right, hidden,
|
||||
below_a, hidden_click, toggle
|
||||
GEOMETRY: vertical, horizontal (horizontal b_cross regression only)
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import shutil
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
if len(sys.argv) != 5:
|
||||
raise SystemExit(__doc__)
|
||||
LAB = pathlib.Path(sys.argv[1]).resolve()
|
||||
case, scenario, geometry = sys.argv[2:5]
|
||||
assert case in ('control', 'private_standard', 'private_mosaic')
|
||||
assert scenario in ('visible', 'b_cross', 'both_cross', 'below_left', 'below_right', 'below_a',
|
||||
'hidden', 'hidden_click', 'toggle')
|
||||
assert geometry in ('vertical', 'horizontal')
|
||||
if geometry == 'horizontal' and scenario != 'b_cross':
|
||||
raise SystemExit('horizontal regression uses b_cross')
|
||||
if scenario in ('hidden', 'hidden_click', 'toggle') and case != 'private_mosaic':
|
||||
raise SystemExit('this scenario requires private_mosaic')
|
||||
assert LAB.is_dir() and (LAB / 'nested-client').is_file()
|
||||
placement = ('b_cross' if geometry == 'horizontal' else
|
||||
'v_visible' if scenario == 'visible' else
|
||||
'v_both_cross' if scenario in ('both_cross', 'below_a') else 'v_b_cross')
|
||||
run = LAB / f'run-{geometry}-{case}-{scenario}'
|
||||
run.mkdir(mode=0o700, exist_ok=False)
|
||||
runtime = pathlib.Path(tempfile.mkdtemp(prefix='mvg-', 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')
|
||||
output2 = ('output HEADLESS-2 resolution 800x600 position 0 600\n'
|
||||
if geometry == 'vertical' else
|
||||
'output HEADLESS-2 resolution 800x600 position 800 0\n')
|
||||
(run / 'config').write_text(
|
||||
'output HEADLESS-1 resolution 800x600 position 0 0\n' + output2 +
|
||||
'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}')
|
||||
if 'pointer' in globals() and pointer.poll() is not None:
|
||||
raise RuntimeError(f'virtual pointer exited {pointer.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 configured(name):
|
||||
"""The server's last role configure, not the positioner request."""
|
||||
matches = re.findall(rf'{name} ROLE_CONFIGURE x=(-?\d+) y=(-?\d+) w=(\d+) h=(\d+)',
|
||||
(run / 'client.log').read_text())
|
||||
if not matches:
|
||||
raise RuntimeError(f'missing {name} ROLE_CONFIGURE')
|
||||
return tuple(map(int, matches[-1]))
|
||||
|
||||
|
||||
def verify_outputs():
|
||||
expected = {'HEADLESS-1': (0, 0, 800, 600),
|
||||
'HEADLESS-2': (0, 600, 800, 600) if geometry == 'vertical'
|
||||
else (800, 0, 800, 600)}
|
||||
actual = {}
|
||||
data = ipc('-t', 'get_outputs')
|
||||
(run / 'outputs-verified.json').write_text(json.dumps(data, indent=2))
|
||||
for output in data:
|
||||
if output.get('name') in expected and output.get('active'):
|
||||
rect = output['rect']
|
||||
if output.get('scale') != 1.0 or output.get('transform') != 'normal':
|
||||
raise RuntimeError(f'unexpected output scale/transform: {output}')
|
||||
actual[output['name']] = (rect['x'], rect['y'], rect['width'], rect['height'])
|
||||
if actual != expected:
|
||||
raise RuntimeError(f'output geometry mismatch: {actual} != {expected}')
|
||||
return expected
|
||||
|
||||
|
||||
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):
|
||||
if int(x) != x or int(y) != y:
|
||||
raise ValueError(f'nonintegral pointer request: {x},{y}')
|
||||
x, y = int(x), int(y)
|
||||
if not (0 <= x < extent[0] and 0 <= y < extent[1]):
|
||||
raise ValueError(f'pointer request outside {extent}: {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)
|
||||
verify_outputs()
|
||||
extent = (800, 1200) if geometry == 'vertical' else (1600, 600)
|
||||
pointer = spawn([str(LAB / 'pointer-daemon'), *map(str, extent)], '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')
|
||||
ordinary = wait(lambda: find(tree(), lambda n: n.get('app_id') == 'wev'))
|
||||
ordinary_rect = ordinary['rect']
|
||||
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
|
||||
# CONTRACT: IPC rect is logical in Mosaic; the applied content origin
|
||||
# includes camera translation and the container's window rectangle.
|
||||
rendered_x = o['rect']['x'] - scroll + o['window_rect']['x']
|
||||
rendered_y = o['rect']['y'] + o['window_rect']['y']
|
||||
top_x = int(max(40, min(760, rendered_x + 100)))
|
||||
inject('left', top_x, 150)
|
||||
wait(lambda: logged('POPUP_A MAP'))
|
||||
a_cfg = configured('POPUP_A')
|
||||
a_x = int(max(40, min(760, rendered_x + a_cfg[0] + 40)))
|
||||
a_y = int(min(585, rendered_y + a_cfg[1] + 35))
|
||||
inject('left', a_x, a_y)
|
||||
wait(lambda: logged('POPUP_B MAP'))
|
||||
b_cfg = configured('POPUP_B')
|
||||
time.sleep(.3)
|
||||
capture('open')
|
||||
a_global = (rendered_x + a_cfg[0], rendered_y + a_cfg[1], a_cfg[2], a_cfg[3])
|
||||
b_global = (a_global[0] + b_cfg[0], a_global[1] + b_cfg[1], b_cfg[2], b_cfg[3])
|
||||
boundary = 600 if geometry == 'vertical' else 800
|
||||
crossing = (b_global[1] < boundary < b_global[1] + b_global[3]
|
||||
if geometry == 'vertical' else
|
||||
b_global[0] < boundary < b_global[0] + b_global[2])
|
||||
measurements = {'geometry': geometry, 'case': case, 'scenario': scenario,
|
||||
'requested_extent': extent, 'owner_id': o['id'],
|
||||
'owner_rect': o['rect'], 'owner_window_rect': o['window_rect'],
|
||||
'scroll_x': scroll, 'ordinary_rect': ordinary_rect,
|
||||
'popup_a_configure': a_cfg, 'popup_b_configure': b_cfg,
|
||||
'popup_a_global_estimate': a_global,
|
||||
'popup_b_global_estimate': b_global,
|
||||
'crossing_estimate': crossing, 'sample_points': []}
|
||||
(run / 'measurements.json').write_text(json.dumps(measurements, indent=2))
|
||||
print('CHAIN_OPEN', case, scenario, placement, 'owner_id', o['id'],
|
||||
'owner_rect', o['rect'], 'scroll', scroll,
|
||||
'top_click', (top_x, 150), 'a_click', (a_x, a_y),
|
||||
'a_configure', a_cfg, 'b_configure', b_cfg,
|
||||
'a_global_estimate', a_global, 'b_global_estimate', b_global,
|
||||
'crossing_estimate', crossing,
|
||||
'client_alive', client.poll() is None, flush=True)
|
||||
if scenario == 'visible':
|
||||
# A and B receive separate right clicks without dismissing the grab.
|
||||
inject('right', min(760, a_global[0] + a_global[2] - 8), a_global[1] + 20)
|
||||
inject('right', min(760, b_global[0] + 40), b_global[1] + 30)
|
||||
time.sleep(.2)
|
||||
capture('visible-clicks')
|
||||
elif scenario in ('b_cross', 'both_cross', 'below_left', 'below_right', 'below_a'):
|
||||
if geometry == 'vertical':
|
||||
if not crossing:
|
||||
print('INCONCLUSIVE no configured B crossing', flush=True)
|
||||
actions = []
|
||||
else:
|
||||
upper_b_y = min(590, b_global[1] + 20)
|
||||
# WHY: the destination client starts below its workspace
|
||||
# decoration; a point just below y=600 can miss wev entirely.
|
||||
below_y = int(max(boundary + 4, ordinary_rect['y'] + 15))
|
||||
below_x = int(min(790, a_global[0] + 8 if scenario == 'below_a'
|
||||
else b_global[0] + 40))
|
||||
target_bottom = (a_global[1] + a_global[3] if scenario == 'below_a'
|
||||
else b_global[1] + b_global[3])
|
||||
if below_y >= target_bottom or not (
|
||||
ordinary_rect['x'] <= below_x < ordinary_rect['x'] + ordinary_rect['width']):
|
||||
print('INCONCLUSIVE no popup-covered point inside wev', ordinary_rect,
|
||||
a_global, b_global, flush=True)
|
||||
actions = []
|
||||
else:
|
||||
upper_x = int(min(790, b_global[0] + 40))
|
||||
actions = [('move', upper_x, upper_b_y),
|
||||
('right', upper_x, upper_b_y),
|
||||
('move', below_x, below_y)]
|
||||
if scenario in ('both_cross', 'below_a'):
|
||||
actions.insert(0, ('right', int(a_global[0] + a_global[2] - 8),
|
||||
min(585, int(a_global[1] + 20))))
|
||||
actions.append(('left' if scenario == 'below_left' else 'right', below_x, below_y))
|
||||
print('BOUNDARY_POINT', below_x, below_y, 'upper_B', upper_b_y,
|
||||
'ordinary_rect', ordinary_rect, flush=True)
|
||||
measurements['sample_points'].extend(
|
||||
[{'purpose': 'visible_popup_b', 'global': [upper_x, upper_b_y]},
|
||||
{'purpose': 'lower_popup_a' if scenario == 'below_a'
|
||||
else 'lower_popup_b', 'global': [below_x, below_y]}])
|
||||
(run / 'measurements.json').write_text(json.dumps(measurements, indent=2))
|
||||
else:
|
||||
actions = [('move', 700, 190), ('right', 700, 190),
|
||||
('move', 900, 190), ('right', 900, 190)]
|
||||
measurements['sample_points'].extend(
|
||||
[{'purpose': 'historical_visible_popup_b', 'global': [700, 190]},
|
||||
{'purpose': 'historical_clipped_popup_b', 'global': [900, 190]}])
|
||||
(run / 'measurements.json').write_text(json.dumps(measurements, indent=2))
|
||||
for action, x, y in actions:
|
||||
inject(action, x, y)
|
||||
time.sleep(.2)
|
||||
capture(f'{action}-{x}-{y}')
|
||||
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')
|
||||
standard_owner = owner()
|
||||
standard_top_x = standard_owner['rect']['x'] + standard_owner['window_rect']['x']
|
||||
standard_top_y = standard_owner['rect']['y'] + standard_owner['window_rect']['y']
|
||||
toggle_x = int(min(790, standard_top_x + a_cfg[0] + b_cfg[0] + 40))
|
||||
toggle_y = int(min(1150, max(640, standard_top_y + a_cfg[1] + b_cfg[1] + 40)))
|
||||
print('STANDARD_RETURN_POINT', toggle_x, toggle_y,
|
||||
'owner_rect', standard_owner['rect'], flush=True)
|
||||
measurements['standard_owner_rect'] = standard_owner['rect']
|
||||
measurements['sample_points'].append(
|
||||
{'purpose': 'standard_popup_b', 'global': [toggle_x, toggle_y]})
|
||||
(run / 'measurements.json').write_text(json.dumps(measurements, indent=2))
|
||||
inject('right', toggle_x, toggle_y)
|
||||
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)
|
||||
(run / 'exit-status.json').write_text(json.dumps({
|
||||
'compositor': None if compositor is None else compositor.returncode,
|
||||
'children': [{'argv': proc.args, 'returncode': proc.returncode}
|
||||
for proc in children]}, indent=2))
|
||||
for file in files:
|
||||
file.close()
|
||||
shutil.rmtree(runtime)
|
||||
Loading…
Reference in a new issue