server: verify window still exists when processing surface serial

Should fix #191
This commit is contained in:
Shawn Wallace 2025-06-29 16:02:02 -04:00
parent 91d463851e
commit 0b2a139f98
3 changed files with 63 additions and 10 deletions

View file

@ -1,5 +1,5 @@
use super::*; use super::*;
use hecs::CommandBuffer; use hecs::{CommandBuffer, DynamicBundle};
use log::{debug, error, trace, warn}; use log::{debug, error, trace, warn};
use macros::simple_event_shunt; use macros::simple_event_shunt;
use std::sync::{Arc, OnceLock}; use std::sync::{Arc, OnceLock};
@ -1435,13 +1435,19 @@ impl<C: XConnection> Dispatch<XwaylandSurfaceV1, Entity> for ServerState<C> {
.map(|i| i.0); .map(|i| i.0);
if let Some(win_entity) = win_entity { if let Some(win_entity) = win_entity {
let win = *state.world.get::<&x::Window>(win_entity).unwrap(); let bundle = state.world.take(win_entity).unwrap();
debug!("associate {surface_id} with {win:?}"); if !bundle.has::<x::Window>() {
state.windows.insert(win, *entity); warn!("Window with same serial ({serial:?}) as {surface_id} has been destroyed?");
return;
}
let mut builder = hecs::EntityBuilder::new(); let mut builder = hecs::EntityBuilder::new();
builder.add_bundle(state.world.take(win_entity).unwrap()); builder.add_bundle(bundle);
state.world.insert(*entity, builder.build()).unwrap(); state.world.insert(*entity, builder.build()).unwrap();
state.world.remove_one::<SurfaceSerial>(*entity).unwrap();
let data = state.world.entity(*entity).unwrap(); let data = state.world.entity(*entity).unwrap();
let win = data.get::<&x::Window>().as_deref().copied().unwrap();
state.windows.insert(win, *entity);
debug!("associate {surface_id} with {win:?} (serial {serial:?})");
if data.get::<&WindowData>().unwrap().mapped { if data.get::<&WindowData>().unwrap().mapped {
state.create_role_window(win, *entity); state.create_role_window(win, *entity);
} }
@ -1449,9 +1455,7 @@ impl<C: XConnection> Dispatch<XwaylandSurfaceV1, Entity> for ServerState<C> {
state.world.insert(*entity, (serial,)).unwrap(); state.world.insert(*entity, (serial,)).unwrap();
} }
} }
Request::Destroy => { Request::Destroy => {}
state.world.remove_one::<SurfaceSerial>(*entity).unwrap();
}
_ => unreachable!(), _ => unreachable!(),
} }
} }

View file

@ -172,7 +172,7 @@ struct SurfaceAttach {
y: i32, y: i32,
} }
#[derive(PartialEq, Eq)] #[derive(PartialEq, Eq, Debug)]
struct SurfaceSerial([u32; 2]); struct SurfaceSerial([u32; 2]);
#[derive(Debug)] #[derive(Debug)]
@ -749,7 +749,7 @@ impl<C: XConnection> ServerState<C> {
if dims == win.attrs.dims { if dims == win.attrs.dims {
return; return;
} }
debug!("Reconfiguring {win:?} {:?}", dims); debug!("Reconfiguring {:?} {:?}", event.window(), dims);
if !win.mapped { if !win.mapped {
win.attrs.dims = dims; win.attrs.dims = dims;
return; return;

View file

@ -2444,6 +2444,55 @@ fn quick_empty_data_offer() {
let selection = f.satellite.new_selection(); let selection = f.satellite.new_selection();
assert!(selection.is_none()); assert!(selection.is_none());
} }
#[test]
fn quick_destroy_window_with_serial() {
let (mut f, comp) = TestFixture::new_with_compositor();
let window = unsafe { Window::new(1) };
let data = WindowData {
mapped: true,
dims: WindowDims {
x: 0,
y: 0,
width: 50,
height: 50,
},
fullscreen: false,
};
f.new_window(window, false, data);
f.satellite.map_window(window);
let (_, surface) = comp.create_surface();
let xwl = TestObject::<XwaylandSurfaceV1>::from_request(
&comp.shell.obj,
Req::<XwaylandShellV1>::GetXwaylandSurface {
surface: surface.clone(),
},
);
let serial = f.surface_serial;
let serial_lo = (serial & 0xFF) as u32;
let serial_hi = ((serial >> 8) & 0xFF) as u32;
xwl.send_request(Req::<XwaylandSurfaceV1>::SetSerial {
serial_lo,
serial_hi,
})
.unwrap();
f.satellite
.set_window_serial(window, [serial_lo, serial_hi]);
f.satellite.unmap_window(window);
f.satellite.destroy_window(window);
f.run();
let id = f.testwl.last_created_surface_id().unwrap();
let surface_data = f.testwl.get_surface_data(id).unwrap();
assert!(
surface_data.role.is_none(),
"Surface unexpectedly has role: {:?}",
surface_data.role
);
}
/// See Pointer::handle_event for an explanation. /// See Pointer::handle_event for an explanation.
#[test] #[test]
fn popup_pointer_motion_workaround() {} fn popup_pointer_motion_workaround() {}