xstate: Ignore Window/Drawable errors

Sometimes, windows will have actually been destroyed before we even
processed the events for it, so if we try to do something like get
properties on windows (as we do whenever a window is mapped) we will get
an error from the X server, but this is fine - we can just ignore this.

Should fix #16, #20, #22, etc.
This commit is contained in:
Shawn Wallace 2024-05-23 23:53:47 -04:00
parent 016cef6f72
commit 2444c2f07b
4 changed files with 176 additions and 72 deletions

View file

@ -1150,7 +1150,11 @@ impl<C: XConnection> Dispatch<XwaylandSurfaceV1, ObjectKey> for ServerState<C> {
.zip((data.surface_serial.is_some_and(|s| s == serial)).then_some(data)) .zip((data.surface_serial.is_some_and(|s| s == serial)).then_some(data))
}) })
{ {
debug!("associate surface {} with {:?}", data.server.id().protocol_id(), win); debug!(
"associate surface {} with {:?}",
data.server.id().protocol_id(),
win
);
window_data.surface_key = Some(*key); window_data.surface_key = Some(*key);
state.associated_windows.insert(*key, win); state.associated_windows.insert(*key, win);
if window_data.mapped { if window_data.mapped {

View file

@ -511,6 +511,7 @@ impl<C: XConnection> ServerState<C> {
debug!("skipping setting window name to {name:?} because a _NET_WM_NAME title is already set"); debug!("skipping setting window name to {name:?} because a _NET_WM_NAME title is already set");
None None
} else { } else {
debug!("setting {window:?} title to {name:?}");
*w = name; *w = name;
Some(w) Some(w)
} }

View file

@ -13,6 +13,35 @@ pub struct XState {
pub atoms: Atoms, pub atoms: Atoms,
} }
// Sometimes we'll get events on windows that have already been destroyed
#[derive(Debug)]
enum MaybeBadWindow {
BadWindow,
Other(xcb::Error),
}
impl From<xcb::Error> for MaybeBadWindow {
fn from(value: xcb::Error) -> Self {
match value {
xcb::Error::Protocol(xcb::ProtocolError::X(
x::Error::Window(_) | x::Error::Drawable(_),
_,
)) => Self::BadWindow,
other => Self::Other(other),
}
}
}
impl From<xcb::ProtocolError> for MaybeBadWindow {
fn from(value: xcb::ProtocolError) -> Self {
match value {
xcb::ProtocolError::X(x::Error::Window(_) | x::Error::Drawable(_), _) => {
Self::BadWindow
}
other => Self::Other(xcb::Error::Protocol(other)),
}
}
}
type XResult<T> = Result<T, MaybeBadWindow>;
/// Essentially a trait alias. /// Essentially a trait alias.
trait PropertyResolver { trait PropertyResolver {
type Output; type Output;
@ -36,12 +65,12 @@ struct PropertyCookieWrapper<'a, F: PropertyResolver> {
impl<F: PropertyResolver> PropertyCookieWrapper<'_, F> { impl<F: PropertyResolver> PropertyCookieWrapper<'_, F> {
/// Get the result from our property cookie. /// Get the result from our property cookie.
fn resolve(self) -> Option<F::Output> { fn resolve(self) -> XResult<Option<F::Output>> {
let reply = self.connection.wait_for_reply(self.cookie).unwrap(); let reply = self.connection.wait_for_reply(self.cookie)?;
if reply.r#type() == x::ATOM_NONE { if reply.r#type() == x::ATOM_NONE {
None Ok(None)
} else { } else {
Some(self.resolver.resolve(reply)) Ok(Some(self.resolver.resolve(reply)))
} }
} }
} }
@ -165,6 +194,20 @@ impl XState {
} }
pub fn handle_events(&mut self, server_state: &mut super::RealServerState) { pub fn handle_events(&mut self, server_state: &mut super::RealServerState) {
macro_rules! unwrap_or_skip_bad_window {
($err:expr) => {
match $err {
Ok(v) => v,
Err(e) => {
let err = MaybeBadWindow::from(e);
match err {
MaybeBadWindow::BadWindow => continue,
MaybeBadWindow::Other(other) => panic!("X11 protocol error: {other:?}"),
}
}
}
};
}
while let Some(event) = self.connection.poll_for_event().unwrap() { while let Some(event) = self.connection.poll_for_event().unwrap() {
trace!("x11 event: {event:?}"); trace!("x11 event: {event:?}");
match event { match event {
@ -181,7 +224,8 @@ impl XState {
xcb::Event::X(x::Event::ReparentNotify(e)) => { xcb::Event::X(x::Event::ReparentNotify(e)) => {
debug!("reparent event: {e:?}"); debug!("reparent event: {e:?}");
if e.parent() == self.root { if e.parent() == self.root {
let attrs = self.get_window_attributes(e.window()); let attrs =
unwrap_or_skip_bad_window!(self.get_window_attributes(e.window()));
server_state.new_window( server_state.new_window(
e.window(), e.window(),
attrs.override_redirect, attrs.override_redirect,
@ -196,20 +240,20 @@ impl XState {
} }
xcb::Event::X(x::Event::MapRequest(e)) => { xcb::Event::X(x::Event::MapRequest(e)) => {
debug!("requested to map {:?}", e.window()); debug!("requested to map {:?}", e.window());
self.connection unwrap_or_skip_bad_window!(self
.send_and_check_request(&x::MapWindow { window: e.window() }) .connection
.unwrap(); .send_and_check_request(&x::MapWindow { window: e.window() }));
} }
xcb::Event::X(x::Event::MapNotify(e)) => { xcb::Event::X(x::Event::MapNotify(e)) => {
let attrs = self.get_window_attributes(e.window()); unwrap_or_skip_bad_window!(self.connection.send_and_check_request(
self.handle_window_attributes(server_state, e.window(), attrs); &x::ChangeWindowAttributes {
server_state.map_window(e.window());
self.connection
.send_and_check_request(&x::ChangeWindowAttributes {
window: e.window(), window: e.window(),
value_list: &[x::Cw::EventMask(x::EventMask::PROPERTY_CHANGE)], value_list: &[x::Cw::EventMask(x::EventMask::PROPERTY_CHANGE)],
}) }
.unwrap(); ));
let attrs = unwrap_or_skip_bad_window!(self.get_window_attributes(e.window()));
self.handle_window_attributes(server_state, e.window(), attrs);
server_state.map_window(e.window());
} }
xcb::Event::X(x::Event::ConfigureNotify(e)) => { xcb::Event::X(x::Event::ConfigureNotify(e)) => {
server_state.reconfigure_window(e); server_state.reconfigure_window(e);
@ -217,17 +261,12 @@ impl XState {
xcb::Event::X(x::Event::UnmapNotify(e)) => { xcb::Event::X(x::Event::UnmapNotify(e)) => {
trace!("unmap event: {:?}", e.event()); trace!("unmap event: {:?}", e.event());
server_state.unmap_window(e.window()); server_state.unmap_window(e.window());
match self unwrap_or_skip_bad_window!(self.connection.send_and_check_request(
.connection &x::ChangeWindowAttributes {
.send_and_check_request(&x::ChangeWindowAttributes {
window: e.window(), window: e.window(),
value_list: &[x::Cw::EventMask(x::EventMask::empty())], value_list: &[x::Cw::EventMask(x::EventMask::empty())],
}) { }
// Window error may occur if the window has been destroyed, ));
// which is fine
Ok(_) | Err(xcb::ProtocolError::X(x::Error::Window(_), _)) => {}
Err(other) => panic!("Error removing event mask from window: {other:?}"),
}
let active_win = self let active_win = self
.connection .connection
@ -277,12 +316,12 @@ impl XState {
list.push(x::ConfigWindow::Height(e.height().into())); list.push(x::ConfigWindow::Height(e.height().into()));
} }
self.connection unwrap_or_skip_bad_window!(self.connection.send_and_check_request(
.send_and_check_request(&x::ConfigureWindow { &x::ConfigureWindow {
window: e.window(), window: e.window(),
value_list: &list, value_list: &list,
}) }
.unwrap(); ));
} }
xcb::Event::X(x::Event::ClientMessage(e)) => match e.r#type() { xcb::Event::X(x::Event::ClientMessage(e)) => match e.r#type() {
x if x == self.atoms.wl_surface_id => { x if x == self.atoms.wl_surface_id => {
@ -328,7 +367,7 @@ impl XState {
} }
} }
fn get_window_attributes(&self, window: x::Window) -> WindowAttributes { fn get_window_attributes(&self, window: x::Window) -> XResult<WindowAttributes> {
let geometry = self.connection.send_request(&x::GetGeometry { let geometry = self.connection.send_request(&x::GetGeometry {
drawable: x::Drawable::Window(window), drawable: x::Drawable::Window(window),
}); });
@ -341,16 +380,19 @@ impl XState {
let wm_hints = self.get_wm_hints(window); let wm_hints = self.get_wm_hints(window);
let size_hints = self.get_wm_size_hints(window); let size_hints = self.get_wm_size_hints(window);
let geometry = self.connection.wait_for_reply(geometry).unwrap(); let geometry = self.connection.wait_for_reply(geometry)?;
let attrs = self.connection.wait_for_reply(attrs).unwrap(); let attrs = self.connection.wait_for_reply(attrs)?;
let title = name let mut title = name.resolve()?;
.resolve() if title.is_none() {
.or_else(|| self.get_wm_name(window).resolve()); title = self.get_wm_name(window).resolve()?;
let class = class.resolve(); }
let wm_hints = wm_hints.resolve();
let size_hints = size_hints.resolve();
WindowAttributes { debug!("got title: {title:?}");
let class = class.resolve()?;
let wm_hints = wm_hints.resolve()?;
let size_hints = size_hints.resolve()?;
Ok(WindowAttributes {
override_redirect: attrs.override_redirect(), override_redirect: attrs.override_redirect(),
popup_for: None, popup_for: None,
dims: WindowDims { dims: WindowDims {
@ -363,7 +405,7 @@ impl XState {
class, class,
group: wm_hints.and_then(|h| h.window_group), group: wm_hints.and_then(|h| h.window_group),
size_hints, size_hints,
} })
} }
fn handle_window_attributes( fn handle_window_attributes(
@ -373,15 +415,12 @@ impl XState {
attrs: WindowAttributes, attrs: WindowAttributes,
) { ) {
if let Some(name) = attrs.title { if let Some(name) = attrs.title {
debug!("setting {window:?} title to {name:?}");
server_state.set_win_title(window, name); server_state.set_win_title(window, name);
} }
if let Some(class) = attrs.class { if let Some(class) = attrs.class {
debug!("setting {window:?} class to {class}");
server_state.set_win_class(window, class); server_state.set_win_class(window, class);
} }
if let Some(hints) = attrs.size_hints { if let Some(hints) = attrs.size_hints {
debug!("{window:?} size hints: {hints:?}");
server_state.set_size_hints(window, hints); server_state.set_size_hints(window, hints);
} }
} }
@ -506,46 +545,44 @@ impl XState {
} }
let window = event.window(); let window = event.window();
let get_prop = |r#type, long_length| { macro_rules! unwrap_or_skip_bad_window {
self.connection ($err:expr) => {
.wait_for_reply(self.connection.send_request(&x::GetProperty { match $err {
window, Ok(v) => v,
property: event.atom(), Err(e) => {
r#type, let err = MaybeBadWindow::from(e);
long_offset: 0, match err {
long_length, MaybeBadWindow::BadWindow => return,
delete: false, MaybeBadWindow::Other(other) => panic!("X11 protocol error: {other:?}"),
})) }
}; }
}
};
}
match event.atom() { match event.atom() {
x if x == x::ATOM_WM_HINTS => { x if x == x::ATOM_WM_HINTS => {
let hints = self.get_wm_hints(window).resolve().unwrap(); let hints =
unwrap_or_skip_bad_window!(self.get_wm_hints(window).resolve()).unwrap();
server_state.set_win_hints(window, hints); server_state.set_win_hints(window, hints);
} }
x if x == x::ATOM_WM_NORMAL_HINTS => { x if x == x::ATOM_WM_NORMAL_HINTS => {
let hints = self.get_wm_size_hints(window).resolve().unwrap(); let hints =
unwrap_or_skip_bad_window!(self.get_wm_size_hints(window).resolve()).unwrap();
server_state.set_size_hints(window, hints); server_state.set_size_hints(window, hints);
} }
x if x == x::ATOM_WM_NAME || x == self.atoms.net_wm_name => { x if x == x::ATOM_WM_NAME => {
let ty = if x == x::ATOM_WM_NAME { let name = unwrap_or_skip_bad_window!(self.get_wm_name(window).resolve()).unwrap();
x::ATOM_STRING server_state.set_win_title(window, name);
} else { }
self.atoms.utf8_string x if x == self.atoms.net_wm_name => {
}; let name =
let prop = get_prop(ty, 256).unwrap(); unwrap_or_skip_bad_window!(self.get_net_wm_name(window).resolve()).unwrap();
let data: &[u8] = prop.value();
let name = String::from_utf8(data.to_vec()).unwrap();
debug!("{:?} named: {name}", window);
let name = if x == x::ATOM_WM_NAME {
WmName::WmName(name)
} else {
WmName::NetWmName(name)
};
server_state.set_win_title(window, name); server_state.set_win_title(window, name);
} }
x if x == x::ATOM_WM_CLASS => { x if x == x::ATOM_WM_CLASS => {
let class = self.get_wm_class(window).resolve().unwrap(); let class =
unwrap_or_skip_bad_window!(self.get_wm_class(window).resolve()).unwrap();
server_state.set_win_class(window, class); server_state.set_win_class(window, class);
} }
_ => { _ => {

View file

@ -295,11 +295,18 @@ impl Connection {
wid wid
} }
#[track_caller]
fn map_window(&self, window: x::Window) { fn map_window(&self, window: x::Window) {
self.send_and_check_request(&x::MapWindow { window }) self.send_and_check_request(&x::MapWindow { window })
.unwrap(); .unwrap();
} }
#[track_caller]
fn destroy_window(&self, window: x::Window) {
self.send_and_check_request(&x::DestroyWindow { window })
.unwrap();
}
fn set_property<P: x::PropEl>( fn set_property<P: x::PropEl>(
&self, &self,
window: x::Window, window: x::Window,
@ -357,6 +364,13 @@ fn toplevel_flow() {
x::ATOM_WM_NORMAL_HINTS, x::ATOM_WM_NORMAL_HINTS,
&[flags, 0, 0, 0, 0, 50, 100, 300, 400], &[flags, 0, 0, 0, 0, 50, 100, 300, 400],
); );
println!("set title: window");
connection.set_property(
window,
x::ATOM_STRING,
x::ATOM_WM_NAME,
c"window".to_bytes(),
);
connection.map_window(window); connection.map_window(window);
f.wait_and_dispatch(); f.wait_and_dispatch();
@ -514,3 +528,51 @@ fn input_focus() {
|win, focus| assert_eq!(win, focus), |win, focus| assert_eq!(win, focus),
); );
} }
#[test]
fn quick_delete() {
let mut f = Fixture::new();
let connection = Connection::new(&f.display);
let window = connection.new_window(connection.root, 0, 0, 20, 20, false);
connection.map_window(window);
connection.set_property(
window,
x::ATOM_WM_HINTS,
x::ATOM_WM_HINTS,
&[WmHintsFlags::Input.bits(), 0],
);
connection.set_property(
window,
x::ATOM_STRING,
x::ATOM_WM_NAME,
c"bindow".to_bytes(),
);
connection.set_property(
window,
x::ATOM_STRING,
x::ATOM_WM_CLASS,
&[c"f".to_bytes_with_nul(), c"ssalc".to_bytes_with_nul()].concat(),
);
let flags = (WmSizeHintsFlags::ProgramMaxSize | WmSizeHintsFlags::ProgramMinSize).bits();
connection.set_property(
window,
x::ATOM_WM_SIZE_HINTS,
x::ATOM_WM_NORMAL_HINTS,
&[flags, 1, 2, 3, 4, 25, 50, 150, 200],
);
connection
.send_and_check_request(&x::ConfigureWindow {
window,
value_list: &[x::ConfigWindow::X(10), x::ConfigWindow::Y(40)],
})
.unwrap();
connection.destroy_window(window);
f.wait_and_dispatch();
let last_surf = f
.testwl
.last_created_surface_id()
.expect("No surface created");
assert_eq!(f.testwl.get_surface_data(last_surf), None);
}