Skip to content

Add test to make sure that layer shell surfaces are not remapped with the wrong position when hidden by fullscreen surfaces.#405

Closed
tarek-y-ismail wants to merge 3 commits intomainfrom
layer-shell-remains-attached-properly-when-occluded-by-fullscreen-surface
Closed

Add test to make sure that layer shell surfaces are not remapped with the wrong position when hidden by fullscreen surfaces.#405
tarek-y-ismail wants to merge 3 commits intomainfrom
layer-shell-remains-attached-properly-when-occluded-by-fullscreen-surface

Conversation

@tarek-y-ismail
Copy link
Contributor

Discovered while working on canonical/mir#4664

tl;dr of what's wrong:

When we enter fullscreen, we set the window's state to hidden, which works fine.

The bug occurs when the bar submits a frame. This piece of code here sees that the surface is not mapped, but it submitted a buffer, so it should be mapped. It then sets its state to restored: https://github.com/canonical/mir/blob/7e7e41f259a0cba81e870467d488d1dda37913a2/src/server/frontend_xwayland/xwayland_surface_role.cpp#L136-L141

Execution reaches this piece of code, where it sees the state as restored and sees that the restore rect does not overlap the application zone, because layer shell zones may have an exclusion zone, which shrinks the application zone: https://github.com/canonical/mir/blob/7e7e41f259a0cba81e870467d488d1dda37913a2/src/miral/basic_window_manager.cpp#L1445-L1452

Then clamp_top actually does not clamp, it centers the surface vertically: https://github.com/canonical/mir/blob/7e7e41f259a0cba81e870467d488d1dda37913a2/src/miral/basic_window_manager.cpp#L48-L51

Which leads to the bars (see next thread) appearing in the middle of the screen.

the wrong position when hidden by fullscreen surfaces.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 LayerSurfaceTest that 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.

Comment on lines +1281 to +1292
// 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
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
// 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.

Copilot uses AI. Check for mistakes.
Comment on lines +1302 to +1313
// 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.";
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
// 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.";

Copilot uses AI. Check for mistakes.
// Step 7: Unfullscreen the window
xdg_toplevel_unset_fullscreen(toplevel);
wl_surface_commit(window_surface);
client.roundtrip();
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
client.roundtrip();
client.dispatch_until([&] { return !window_state.fullscreen; });

Copilot uses AI. Check for mistakes.
@github-actions
Copy link

TICS Quality Gate

✔️ Passed

No changed files applicable for TICS analysis quality gating.

TICS / TICS / Run TICS analysis

@tarek-y-ismail
Copy link
Contributor Author

Too specific to Mir. Will try implementing it in Mir's test code instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants