fix(shell): distinguish between unmapping and destroying surfaces

Previously, `unmap_surface` automatically pushed all unmapped windows
into the `pending_windows` list. This behavior is correct for X11
windows (which may be remapped) but incorrect for Wayland `toplevel_destroyed`
events, where the role is permanently gone.

This caused issues with clients like Telegram that reuse `wl_surface`s.
Because the destroyed toplevel remained in `pending_windows`, a
subsequent cleanup commit (e.g., null buffer) triggered a configure
event. This prematurely marked the surface as `configured` in the
shell state.

Consequently, when the client attached a new `xdg_toplevel` role,
the compositor skipped the mandatory initial configure event (assuming
it was already done), causing the window to never appear.

This refactors `unmap_surface` to return `Option<PendingWindow>`
instead of mutating global state.
- XWayland: Explicitly saves the pending window (behavior preserved).
- XDG Shell: Drops the pending window, preventing ghost state interactions.

Fixes #1816
This commit is contained in:
mikairyuu 2026-01-09 17:23:58 +09:00 • committed by Jacob Kauffmann
parent e6a3a3a9c9
commit 15b6b678c1
3 changed files with 11 additions and 5 deletions

View file

@ -2832,7 +2832,8 @@ impl Shell {
surface: &S, surface: &S,
seat: &Seat<State>, seat: &Seat<State>,
toplevel_info: &mut ToplevelInfoState<State, CosmicSurface>, toplevel_info: &mut ToplevelInfoState<State, CosmicSurface>,
) where ) -> Option<PendingWindow>
where
CosmicSurface: PartialEq<S>, CosmicSurface: PartialEq<S>,
{ {
for set in self.workspaces.sets.values_mut() { for set in self.workspaces.sets.values_mut() {
@ -2890,15 +2891,16 @@ impl Shell {
if let Some(surface) = surface { if let Some(surface) = surface {
toplevel_info.remove_toplevel(&surface); toplevel_info.remove_toplevel(&surface);
self.pending_windows.push(PendingWindow { return Some(PendingWindow {
surface, surface,
seat: seat.clone(), seat: seat.clone(),
fullscreen: None, fullscreen: None,
maximized: false, maximized: false,
}); });
return;
} }
} }
None
} }
pub fn move_current( pub fn move_current(

View file

@ -321,7 +321,7 @@ impl XdgShellHandler for State {
let output = shell let output = shell
.visible_output_for_surface(surface.wl_surface()) .visible_output_for_surface(surface.wl_surface())
.cloned(); .cloned();
shell.unmap_surface( let _ = shell.unmap_surface(
surface.wl_surface(), surface.wl_surface(),
&seat, &seat,
&mut self.common.toplevel_info_state, &mut self.common.toplevel_info_state,

View file

@ -873,7 +873,11 @@ impl XwmHandler for State {
shell.override_redirect_windows.retain(|or| or != &window); shell.override_redirect_windows.retain(|or| or != &window);
} else { } else {
let seat = shell.seats.last_active().clone(); let seat = shell.seats.last_active().clone();
shell.unmap_surface(&window, &seat, &mut self.common.toplevel_info_state); if let Some(pending) =
shell.unmap_surface(&window, &seat, &mut self.common.toplevel_info_state)
{
shell.pending_windows.push(pending);
}
} }
let outputs = if let Some(wl_surface) = window.wl_surface() { let outputs = if let Some(wl_surface) = window.wl_surface() {