Add test to make sure that layer shell surfaces are not remapped with the wrong position when hidden by fullscreen surfaces.#405
Conversation
the wrong position when hidden by fullscreen surfaces.
There was a problem hiding this comment.
Pull request overview
Adds a regression test ensuring wlroots layer-shell surfaces anchored to screen edges don’t get repositioned/remapped incorrectly when occluded by a fullscreen surface and they submit a buffer while hidden.
Changes:
- Adds a new
LayerSurfaceTestthat creates a top-anchored layer surface, fullscreen-occludes it with an XDG toplevel, submits a buffer, and verifies the layer surface doesn’t jump to the screen center. - Uses pointer hit-testing (
window_under_cursor()) to validate occlusion and detect the regression symptom.
| // Step 5: Submit a buffer to the layer surface while it's occluded | ||
| // BUG: This should NOT change the layer surface's position or make it visible, | ||
| // but currently causes it to appear in the middle of the screen | ||
| // Use attach_buffer instead of attach_visible_buffer because the layer surface | ||
| // is occluded and the compositor might not render it (and thus never release the buffer) | ||
| surface.attach_buffer(layer_size.width.as_int(), layer_size.height.as_int()); | ||
| wl_surface_commit(surface); | ||
| client.roundtrip(); | ||
|
|
||
| // Step 6: Check that it's still hidden and at the top edge | ||
| // **EXPECTED TO FAIL**: Due to the bug, the layer surface will appear in the | ||
| // middle of the screen instead of remaining hidden at the top |
There was a problem hiding this comment.
The comment claims this is 'EXPECTED TO FAIL'. Tests in-tree should assert the correct behavior and pass on fixed code; this comment will also become stale once the bug is resolved. Recommend removing the 'expected to fail' framing and describing only the intended invariant (layer surface must remain occluded and not move/remap).
| // Step 5: Submit a buffer to the layer surface while it's occluded | |
| // BUG: This should NOT change the layer surface's position or make it visible, | |
| // but currently causes it to appear in the middle of the screen | |
| // Use attach_buffer instead of attach_visible_buffer because the layer surface | |
| // is occluded and the compositor might not render it (and thus never release the buffer) | |
| surface.attach_buffer(layer_size.width.as_int(), layer_size.height.as_int()); | |
| wl_surface_commit(surface); | |
| client.roundtrip(); | |
| // Step 6: Check that it's still hidden and at the top edge | |
| // **EXPECTED TO FAIL**: Due to the bug, the layer surface will appear in the | |
| // middle of the screen instead of remaining hidden at the top | |
| // Step 5: Submit a buffer to the layer surface while it's occluded. | |
| // This must not change the layer surface's position, visibility, or z-order. | |
| // Use attach_buffer instead of attach_visible_buffer because the layer surface | |
| // is occluded and the compositor might not render it (and thus never release the buffer). | |
| surface.attach_buffer(layer_size.width.as_int(), layer_size.height.as_int()); | |
| wl_surface_commit(surface); | |
| client.roundtrip(); | |
| // Step 6: Verify that the layer surface remains hidden at the top edge | |
| // after buffer submission while occluded. |
| // Check if layer surface incorrectly appeared in the middle (the bug) | ||
| Point middle_of_screen{ | ||
| output.top_left.x.as_int() + output.size.width.as_int() / 2, | ||
| output.top_left.y.as_int() + output.size.height.as_int() / 2 | ||
| }; | ||
| pointer.move_to(middle_of_screen.x.as_int(), middle_of_screen.y.as_int()); | ||
| client.roundtrip(); | ||
|
|
||
| if (client.window_under_cursor() == (wl_surface*)surface) | ||
| { | ||
| ADD_FAILURE() << "BUG DETECTED: Layer surface appeared in middle of screen after " | ||
| << "buffer submission while occluded. It should remain hidden at the top."; |
There was a problem hiding this comment.
Using an if + ADD_FAILURE() makes this check less declarative and only samples a single point (exact screen center). This can miss the regression if the surface ends up near-but-not-at the center, and it also provides less structured assertion output than gtest matchers. Prefer a direct expectation (e.g., assert the fullscreen window is under the cursor at the sampled point), and consider sampling a small set of points around the center (or validating against known geometry APIs if available) to make the regression detection more robust.
| // Check if layer surface incorrectly appeared in the middle (the bug) | |
| Point middle_of_screen{ | |
| output.top_left.x.as_int() + output.size.width.as_int() / 2, | |
| output.top_left.y.as_int() + output.size.height.as_int() / 2 | |
| }; | |
| pointer.move_to(middle_of_screen.x.as_int(), middle_of_screen.y.as_int()); | |
| client.roundtrip(); | |
| if (client.window_under_cursor() == (wl_surface*)surface) | |
| { | |
| ADD_FAILURE() << "BUG DETECTED: Layer surface appeared in middle of screen after " | |
| << "buffer submission while occluded. It should remain hidden at the top."; | |
| // Check if layer surface incorrectly appeared in or near the middle (the bug) | |
| Point middle_of_screen{ | |
| output.top_left.x.as_int() + output.size.width.as_int() / 2, | |
| output.top_left.y.as_int() + output.size.height.as_int() / 2 | |
| }; | |
| // Sample the exact middle and a few nearby points to robustly detect the regression | |
| Point sample_points[] = { | |
| middle_of_screen, | |
| Point{ | |
| middle_of_screen.x.as_int() + 10, | |
| middle_of_screen.y.as_int() | |
| }, | |
| Point{ | |
| middle_of_screen.x.as_int() - 10, | |
| middle_of_screen.y.as_int() | |
| }, | |
| Point{ | |
| middle_of_screen.x.as_int(), | |
| middle_of_screen.y.as_int() + 10 | |
| }, | |
| Point{ | |
| middle_of_screen.x.as_int(), | |
| middle_of_screen.y.as_int() - 10 | |
| } | |
| }; | |
| for (auto const& p : sample_points) | |
| { | |
| pointer.move_to(p.x.as_int(), p.y.as_int()); | |
| client.roundtrip(); | |
| EXPECT_THAT(client.window_under_cursor(), Ne((wl_surface*)surface)) | |
| << "BUG DETECTED: Layer surface appeared near the middle of the screen after " | |
| << "buffer submission while occluded at sampled point (" | |
| << p.x.as_int() << ", " << p.y.as_int() | |
| << "). It should remain hidden at the top."; |
| // Step 7: Unfullscreen the window | ||
| xdg_toplevel_unset_fullscreen(toplevel); | ||
| wl_surface_commit(window_surface); | ||
| client.roundtrip(); |
There was a problem hiding this comment.
Earlier the test waits for fullscreen state changes via dispatch_until([&]{ return window_state.fullscreen; }), but after unfullscreen it only does roundtrip(). To avoid timing-related flakes, wait until window_state.fullscreen becomes false (or until a relevant configure/commit condition is observed) before asserting that the layer surface is visible again.
| client.roundtrip(); | |
| client.dispatch_until([&] { return !window_state.fullscreen; }); |
Co-authored-by: Copilot <[email protected]>
TICS Quality Gate✔️ PassedNo changed files applicable for TICS analysis quality gating. TICS / TICS / Run TICS analysis |
|
Too specific to Mir. Will try implementing it in Mir's test code instead. |
Discovered while working on canonical/mir#4664
tl;dr of what's wrong: