From 6c1d8e49170b9557415257a8f6eddeadae0f37b4 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Wed, 19 Aug 2026 06:00:05 +0200 Subject: [PATCH 1/6] X11: Harden against possibly-zero window IDs --- src/platform/x11/event_loop.rs | 53 +++++++++++++++++----------- src/platform/x11/visibility_tree.rs | 54 +++++++++++++++++------------ src/platform/x11/window_shared.rs | 2 +- 3 files changed, 66 insertions(+), 43 deletions(-) diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index c7c03f83..4bac90c0 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -410,7 +410,9 @@ impl EventLoop { // These are coalesced and then handled asynchronously at the end of the event loop if event.window == self.window.raw_id() { self.new_size = Some(PhysicalSize::new(event.width, event.height)); - } else if Some(event.window) == self.window.visibility_state.parent_id() { + } else if Some(event.window) + == self.window.visibility_state.parent_id().map(|i| i.get()) + { // Also resize the window if the parent is resized // This works around some hosts that might not call set_size() right away (or at all...) self.new_parent_size = Some(PhysicalSize::new(event.width, event.height)); @@ -503,35 +505,46 @@ impl EventLoop { } XEvent::MapNotify(e) => { - if e.window == self.window.raw_id() { - self.window.is_mapped.set(true); - } + if let Some(window_id) = NonZero::new(e.window) { + if window_id == self.window.xcb_window.id() { + self.window.is_mapped.set(true); + } - let became_viewable = self.window.visibility_state.window_mapped(e.window); + let became_viewable = self.window.visibility_state.window_mapped(window_id); - if became_viewable { - self.exposed = true; + if became_viewable { + self.exposed = true; + } } } XEvent::UnmapNotify(e) => { - if e.window == self.window.raw_id() { - self.window.is_mapped.set(false) - } + if let Some(window_id) = NonZero::new(e.window) { + if window_id == self.window.xcb_window.id() { + self.window.is_mapped.set(false) + } - self.window.visibility_state.window_unmapped(e.window); + self.window.visibility_state.window_unmapped(window_id); + } } - XEvent::ReparentNotify(e) => self.window.visibility_state.window_reparented( - e.window, - e.parent, - &self.window.connection.conn, - ), + XEvent::ReparentNotify(e) => { + if let Some(window_id) = NonZero::new(e.window) { + self.window.visibility_state.window_reparented( + window_id, + NonZero::new(e.parent), + &self.window.connection.conn, + ) + } + } - XEvent::DestroyNotify(e) => self - .window - .visibility_state - .window_destroyed(e.window, &self.window.connection.conn), + XEvent::DestroyNotify(e) => { + if let Some(window_id) = NonZero::new(e.window) { + self.window + .visibility_state + .window_destroyed(window_id, &self.window.connection.conn) + } + } _ => {} } diff --git a/src/platform/x11/visibility_tree.rs b/src/platform/x11/visibility_tree.rs index d44545b4..d1f513d4 100644 --- a/src/platform/x11/visibility_tree.rs +++ b/src/platform/x11/visibility_tree.rs @@ -1,4 +1,5 @@ use std::cell::{Cell, RefCell}; +use std::num::{NonZero, NonZeroU32}; use x11rb::errors::ReplyError; use x11rb::protocol::xproto::{ConnectionExt, MapState, QueryTreeReply, Window}; use x11rb::protocol::ErrorKind; @@ -15,15 +16,15 @@ struct AncestryList { } impl AncestryList { - pub fn new(own_window: Window) -> Self { + pub fn new(own_window: NonZeroU32) -> Self { Self { inner: RefCell::new(vec![Ancestor { id: own_window, mapped: false.into() }]) } } - pub fn pop_id(&self) -> Option { + pub fn pop_id(&self) -> Option { self.inner.borrow_mut().pop().map(|a| a.id) } - pub fn last_id(&self) -> Option { + pub fn last_id(&self) -> Option { self.inner.borrow().last().map(|a| a.id) } @@ -31,11 +32,11 @@ impl AncestryList { self.inner.borrow_mut().push(ancestor); } - pub fn parent_id(&self) -> Option { + pub fn parent_id(&self) -> Option { self.inner.borrow().get(1).map(|a| a.id) } - pub fn remove_window(&self, id: Window) -> bool { + pub fn remove_window(&self, id: NonZeroU32) -> bool { let mut inner = self.inner.borrow_mut(); let Some(index) = inner.iter().position(|a| a.id == id) else { return false; @@ -46,7 +47,7 @@ impl AncestryList { true } - pub fn remove_after_window(&self, id: Window) -> bool { + pub fn remove_after_window(&self, id: NonZeroU32) -> bool { let mut inner = self.inner.borrow_mut(); let Some(index) = inner.iter().position(|a| a.id == id) else { return false; @@ -61,7 +62,7 @@ impl AncestryList { self.inner.borrow().iter().all(|a| a.mapped.get()) } - pub fn set_mapped(&self, window: Window, mapped: bool) -> bool { + pub fn set_mapped(&self, window: NonZeroU32, mapped: bool) -> bool { let inner = self.inner.borrow(); let Some(ancestor) = inner.iter().find(|a| a.id == window) else { return false; @@ -74,12 +75,14 @@ impl AncestryList { #[cfg_attr(debug_assertions, derive(Debug))] struct Ancestor { - id: Window, + id: NonZeroU32, mapped: Cell, } impl AncestorVisibilityState { - pub fn discover(connection: &XCBConnection, own_window_id: Window) -> Result { + pub fn discover( + connection: &XCBConnection, own_window_id: NonZeroU32, + ) -> Result { let this = Self { ancestry: AncestryList::new(own_window_id), own_window_viewable: Cell::new(false), @@ -94,12 +97,12 @@ impl AncestorVisibilityState { self.own_window_viewable.get() } - pub fn parent_id(&self) -> Option { + pub fn parent_id(&self) -> Option { self.ancestry.parent_id() } /// Returns `true` if this operation made our own window visible. - pub fn window_mapped(&self, window_id: Window) -> bool { + pub fn window_mapped(&self, window_id: NonZeroU32) -> bool { if !self.ancestry.set_mapped(window_id, true) { return false; } @@ -116,7 +119,7 @@ impl AncestorVisibilityState { all_mapped } - pub fn window_unmapped(&self, window_id: Window) { + pub fn window_unmapped(&self, window_id: NonZeroU32) { if !self.ancestry.set_mapped(window_id, false) { return; } @@ -124,7 +127,7 @@ impl AncestorVisibilityState { self.own_window_viewable.set(false); } - pub fn window_destroyed(&self, window_id: Window, connection: &XCBConnection) { + pub fn window_destroyed(&self, window_id: NonZeroU32, connection: &XCBConnection) { if !self.ancestry.remove_window(window_id) { return; } @@ -133,15 +136,17 @@ impl AncestorVisibilityState { } pub fn window_reparented( - &self, window_id: Window, new_parent: Window, connection: &XCBConnection, + &self, window_id: NonZeroU32, new_parent: Option, connection: &XCBConnection, ) { if !self.ancestry.remove_after_window(window_id) { return; } - self.ancestry.push(Ancestor { id: new_parent, mapped: Cell::new(false) }); + if let Some(new_parent) = new_parent { + self.ancestry.push(Ancestor { id: new_parent, mapped: Cell::new(false) }); - self.regenerate_from_last_window(connection); + self.regenerate_from_last_window(connection); + } } pub fn regenerate_from_last_window(&self, connection: &XCBConnection) { @@ -171,14 +176,14 @@ impl AncestorVisibilityState { continue; }; - if tree.parent == current_window { + if tree.parent == current_window.get() { // Weird, but that might also mean we're at the end of the tree (or the window has no parent yet) break; } // Sanity check if the current parent is actually registered to have the child in its children list if let Some(child_id) = self.ancestry.last_id() { - if !tree.children.contains(&child_id) { + if !tree.children.contains(&child_id.get()) { // The child has been orphaned, it must have been reparented between our server queries. // Go back a step and check again. @@ -203,7 +208,12 @@ impl AncestorVisibilityState { break; } - current_window = tree.parent; + // If parent == 0, assume there's no parent and just break + if let Some(parent) = NonZeroU32::new(tree.parent) { + current_window = parent; + } else { + break; + } } self.own_window_viewable.set(self.ancestry.check_all_mapped()); @@ -214,10 +224,10 @@ impl AncestorVisibilityState { /// Returns Ok(None) on BadWindow. fn fetch_window_info( - connection: &XCBConnection, window: Window, + connection: &XCBConnection, window: NonZeroU32, ) -> Result, ReplyError> { - let attrs_cookie = connection.get_window_attributes(window)?; - let tree_cookie = connection.query_tree(window)?; + let attrs_cookie = connection.get_window_attributes(window.get())?; + let tree_cookie = connection.query_tree(window.get())?; let mapped = match attrs_cookie.reply() { Ok(attr) => attr.map_state != MapState::UNMAPPED, diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index 4a45354b..16f2b34c 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -104,7 +104,7 @@ impl WindowInner { connection.register_tree_structure_events()?.check()?; let visibility_state = - AncestorVisibilityState::discover(&connection.conn, xcb_window.id().get())?; + AncestorVisibilityState::discover(&connection.conn, xcb_window.id())?; let cookies = [ xcb_window.set_title(&options.title)?, From 283437297ff69e603a8bbbf213572d9ca5133c63 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Wed, 19 Aug 2026 06:01:01 +0200 Subject: [PATCH 2/6] Clippy fix --- src/platform/x11/visibility_tree.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/x11/visibility_tree.rs b/src/platform/x11/visibility_tree.rs index d1f513d4..8c322f21 100644 --- a/src/platform/x11/visibility_tree.rs +++ b/src/platform/x11/visibility_tree.rs @@ -1,7 +1,7 @@ use std::cell::{Cell, RefCell}; -use std::num::{NonZero, NonZeroU32}; +use std::num::NonZeroU32; use x11rb::errors::ReplyError; -use x11rb::protocol::xproto::{ConnectionExt, MapState, QueryTreeReply, Window}; +use x11rb::protocol::xproto::{ConnectionExt, MapState, QueryTreeReply}; use x11rb::protocol::ErrorKind; use x11rb::x11_utils::X11Error; use x11rb::xcb_ffi::XCBConnection; From f1a95448676148fd56a1eb45b49c88b7efeb7c08 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:54:49 +0200 Subject: [PATCH 3/6] More hardening --- src/platform/x11/visibility_tree.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/platform/x11/visibility_tree.rs b/src/platform/x11/visibility_tree.rs index 8c322f21..214f5f12 100644 --- a/src/platform/x11/visibility_tree.rs +++ b/src/platform/x11/visibility_tree.rs @@ -160,6 +160,8 @@ impl AncestorVisibilityState { ) -> Result<(), ReplyError> { let Some(mut current_window) = self.ancestry.pop_id() else { return Ok(()) }; + let mut shitlist = Vec::new(); + loop { let Some((mapped, tree)) = fetch_window_info(connection, current_window)? else { // We got a BadWindow while trying to get a window's info, it must have been destroyed. @@ -172,6 +174,23 @@ impl AncestorVisibilityState { break; }; + if shitlist.contains(&previous_parent) { + crate::warn!( + "Failed to get info for window {} in the past already. Stopping.", + previous_parent + ); + break; + } + + if shitlist.len() > 10 { + crate::warn!( + "Too many failures while trying to build X ancestry tree. Stopping." + ); + break; + } + + shitlist.push(previous_parent); + current_window = previous_parent; continue; }; From b3f3584c9fc584c9992d1c19319182e3abaeee8c Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Thu, 20 Aug 2026 01:50:34 +0200 Subject: [PATCH 4/6] why --- src/platform/x11/event_loop.rs | 14 +++++- src/platform/x11/visibility_tree.rs | 68 ++++++++++++++++++++++------- src/platform/x11/window_shared.rs | 5 ++- src/platform/x11/xcb_connection.rs | 18 ++++++++ 4 files changed, 85 insertions(+), 20 deletions(-) diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index 4bac90c0..8f11e542 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -283,10 +283,14 @@ impl EventLoop { } WindowThreadRequest::Show => { self.window.xcb_window.map_window()?.check()?; + dbg!("Show request!"); + self.window.visibility_state.window_mapped(self.window.xcb_window.id()); Ok(()) } WindowThreadRequest::Hide => { self.window.xcb_window.unmap_window()?.check()?; + dbg!("Hide request!"); + self.window.visibility_state.window_unmapped(self.window.xcb_window.id()); Ok(()) } } @@ -323,6 +327,7 @@ impl EventLoop { } pub fn run(mut self, mut inner: calloop::EventLoop) -> Result<(), PlatformError> { + self.drain_xcb_events()?; inner.run(None, &mut self, Self::handle_idle)?; self.handle_event(Event::Window(WindowEvent::WillClose)); @@ -505,7 +510,9 @@ impl EventLoop { } XEvent::MapNotify(e) => { + self.window.connection.dbg_event_mask(); if let Some(window_id) = NonZero::new(e.window) { + eprintln!("MapNotify {window_id}"); if window_id == self.window.xcb_window.id() { self.window.is_mapped.set(true); } @@ -520,6 +527,7 @@ impl EventLoop { XEvent::UnmapNotify(e) => { if let Some(window_id) = NonZero::new(e.window) { + eprintln!("UnmapNotify {window_id}"); if window_id == self.window.xcb_window.id() { self.window.is_mapped.set(false) } @@ -530,19 +538,21 @@ impl EventLoop { XEvent::ReparentNotify(e) => { if let Some(window_id) = NonZero::new(e.window) { + eprintln!("ReparentNotify {window_id}"); self.window.visibility_state.window_reparented( window_id, NonZero::new(e.parent), - &self.window.connection.conn, + &self.window.connection, ) } } XEvent::DestroyNotify(e) => { if let Some(window_id) = NonZero::new(e.window) { + eprintln!("DestroyNotify {window_id}"); self.window .visibility_state - .window_destroyed(window_id, &self.window.connection.conn) + .window_destroyed(window_id, &self.window.connection) } } diff --git a/src/platform/x11/visibility_tree.rs b/src/platform/x11/visibility_tree.rs index 214f5f12..6f742453 100644 --- a/src/platform/x11/visibility_tree.rs +++ b/src/platform/x11/visibility_tree.rs @@ -1,3 +1,5 @@ +use crate::platform::x11::error::CookieExt; +use crate::platform::X11Connection; use std::cell::{Cell, RefCell}; use std::num::NonZeroU32; use x11rb::errors::ReplyError; @@ -7,12 +9,13 @@ use x11rb::x11_utils::X11Error; use x11rb::xcb_ffi::XCBConnection; pub struct AncestorVisibilityState { - ancestry: AncestryList, + pub ancestry: AncestryList, own_window_viewable: Cell, + root_id: Cell>, } struct AncestryList { - inner: RefCell>, + pub inner: RefCell>, } impl AncestryList { @@ -81,11 +84,12 @@ struct Ancestor { impl AncestorVisibilityState { pub fn discover( - connection: &XCBConnection, own_window_id: NonZeroU32, + connection: &X11Connection, own_window_id: NonZeroU32, ) -> Result { let this = Self { ancestry: AncestryList::new(own_window_id), own_window_viewable: Cell::new(false), + root_id: Cell::new(NonZeroU32::new(connection.default_screen().root)), }; this.try_regenerate_from_last_window(connection)?; @@ -107,6 +111,8 @@ impl AncestorVisibilityState { return false; } + eprintln!("Mapped {window_id}"); + if self.own_window_viewable.get() { return false; } @@ -124,43 +130,59 @@ impl AncestorVisibilityState { return; } + eprintln!("Unmapped {window_id}"); + self.own_window_viewable.set(false); } - pub fn window_destroyed(&self, window_id: NonZeroU32, connection: &XCBConnection) { + pub fn window_destroyed(&self, window_id: NonZeroU32, connection: &X11Connection) { if !self.ancestry.remove_window(window_id) { return; } + eprintln!("Destroyed {window_id}"); + self.regenerate_from_last_window(connection); } pub fn window_reparented( - &self, window_id: NonZeroU32, new_parent: Option, connection: &XCBConnection, + &self, window_id: NonZeroU32, new_parent: Option, connection: &X11Connection, ) { + eprintln!("Reparent event {window_id} to {new_parent:?}"); + if !self.ancestry.remove_after_window(window_id) { return; } if let Some(new_parent) = new_parent { + if Some(new_parent) == self.root_id.get() { + return; + } + self.ancestry.push(Ancestor { id: new_parent, mapped: Cell::new(false) }); + eprintln!("Reparented {window_id} to new parent: {new_parent}"); self.regenerate_from_last_window(connection); + } else { + eprintln!("Unparented {window_id}"); } + + dbg!(self.ancestry.inner.borrow()); } - pub fn regenerate_from_last_window(&self, connection: &XCBConnection) { + pub fn regenerate_from_last_window(&self, connection: &X11Connection) { if let Err(e) = self.try_regenerate_from_last_window(connection) { crate::warn!("Failed to generate window ancestry list: {}", e) } } fn try_regenerate_from_last_window( - &self, connection: &XCBConnection, + &self, connection: &X11Connection, ) -> Result<(), ReplyError> { let Some(mut current_window) = self.ancestry.pop_id() else { return Ok(()) }; let mut shitlist = Vec::new(); + let mut rechecked_children = Vec::new(); loop { let Some((mapped, tree)) = fetch_window_info(connection, current_window)? else { @@ -206,12 +228,16 @@ impl AncestorVisibilityState { // The child has been orphaned, it must have been reparented between our server queries. // Go back a step and check again. - crate::warn!( - "Children of parent {} does not contain {}: {:?}", - current_window, - child_id, - &tree.children - ); + if rechecked_children.contains(&child_id) { + crate::warn!( + "Children of parent {} does not contain {}: {:?}", + current_window, + child_id, + &tree.children + ); + } else { + rechecked_children.push(child_id); + } let Some(_) = self.ancestry.pop_id() else { unreachable!() }; current_window = child_id; @@ -233,20 +259,30 @@ impl AncestorVisibilityState { } else { break; } + + if let Some(root) = NonZeroU32::new(tree.root) { + if Some(root) != self.root_id.get() { + self.root_id.set(Some(root)) + } + } } self.own_window_viewable.set(self.ancestry.check_all_mapped()); + dbg!(&self.ancestry.inner.borrow()); + Ok(()) } } /// Returns Ok(None) on BadWindow. fn fetch_window_info( - connection: &XCBConnection, window: NonZeroU32, + connection: &X11Connection, window: NonZeroU32, ) -> Result, ReplyError> { - let attrs_cookie = connection.get_window_attributes(window.get())?; - let tree_cookie = connection.query_tree(window.get())?; + let attrs_cookie = connection.conn.get_window_attributes(window.get())?; + let tree_cookie = connection.conn.query_tree(window.get())?; + + connection.register_tree_structure_events_for_window(window)?.check_warn(); let mapped = match attrs_cookie.reply() { Ok(attr) => attr.map_state != MapState::UNMAPPED, diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index 16f2b34c..63f6e999 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -103,8 +103,9 @@ impl WindowInner { connection.register_tree_structure_events()?.check()?; - let visibility_state = - AncestorVisibilityState::discover(&connection.conn, xcb_window.id())?; + let visibility_state = AncestorVisibilityState::discover(&connection, xcb_window.id())?; + + connection.dbg_event_mask(); let cookies = [ xcb_window.set_title(&options.title)?, diff --git a/src/platform/x11/xcb_connection.rs b/src/platform/x11/xcb_connection.rs index eab70899..4bb196ec 100644 --- a/src/platform/x11/xcb_connection.rs +++ b/src/platform/x11/xcb_connection.rs @@ -4,6 +4,7 @@ use crate::wrappers::xlib::XlibXcbConnection; use crate::MouseCursor; use std::cell::RefCell; use std::collections::hash_map::{Entry, HashMap}; +use std::num::NonZeroU32; use std::sync::Arc; use x11rb::cookie::VoidCookie; use x11rb::cursor::Handle as CursorHandle; @@ -118,4 +119,21 @@ impl X11Connection { &ChangeWindowAttributesAux::new().event_mask(EventMask::SUBSTRUCTURE_NOTIFY), ) } + + pub fn register_tree_structure_events_for_window( + &self, window_id: NonZeroU32, + ) -> core::result::Result, ConnectionError> { + self.conn.change_window_attributes( + window_id.get(), + &ChangeWindowAttributesAux::new().event_mask(EventMask::SUBSTRUCTURE_NOTIFY), + ) + } + + pub fn dbg_event_mask(&self) { + let root = self.default_screen().root; + + let events = + self.conn.get_window_attributes(root).unwrap().reply().unwrap().all_event_masks; + dbg!(events); + } } From 51e929a36206045730fc6bacc090b98432b167e4 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Thu, 20 Aug 2026 03:16:03 +0200 Subject: [PATCH 5/6] fix --- src/platform/x11/event_loop.rs | 7 ------ src/platform/x11/visibility_tree.rs | 36 +++++++++++------------------ src/platform/x11/window_shared.rs | 2 -- src/platform/x11/xcb_connection.rs | 8 ------- 4 files changed, 14 insertions(+), 39 deletions(-) diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index 8f11e542..2813d7b4 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -283,13 +283,11 @@ impl EventLoop { } WindowThreadRequest::Show => { self.window.xcb_window.map_window()?.check()?; - dbg!("Show request!"); self.window.visibility_state.window_mapped(self.window.xcb_window.id()); Ok(()) } WindowThreadRequest::Hide => { self.window.xcb_window.unmap_window()?.check()?; - dbg!("Hide request!"); self.window.visibility_state.window_unmapped(self.window.xcb_window.id()); Ok(()) } @@ -510,9 +508,7 @@ impl EventLoop { } XEvent::MapNotify(e) => { - self.window.connection.dbg_event_mask(); if let Some(window_id) = NonZero::new(e.window) { - eprintln!("MapNotify {window_id}"); if window_id == self.window.xcb_window.id() { self.window.is_mapped.set(true); } @@ -527,7 +523,6 @@ impl EventLoop { XEvent::UnmapNotify(e) => { if let Some(window_id) = NonZero::new(e.window) { - eprintln!("UnmapNotify {window_id}"); if window_id == self.window.xcb_window.id() { self.window.is_mapped.set(false) } @@ -538,7 +533,6 @@ impl EventLoop { XEvent::ReparentNotify(e) => { if let Some(window_id) = NonZero::new(e.window) { - eprintln!("ReparentNotify {window_id}"); self.window.visibility_state.window_reparented( window_id, NonZero::new(e.parent), @@ -549,7 +543,6 @@ impl EventLoop { XEvent::DestroyNotify(e) => { if let Some(window_id) = NonZero::new(e.window) { - eprintln!("DestroyNotify {window_id}"); self.window .visibility_state .window_destroyed(window_id, &self.window.connection) diff --git a/src/platform/x11/visibility_tree.rs b/src/platform/x11/visibility_tree.rs index 6f742453..268e7eca 100644 --- a/src/platform/x11/visibility_tree.rs +++ b/src/platform/x11/visibility_tree.rs @@ -1,4 +1,3 @@ -use crate::platform::x11::error::CookieExt; use crate::platform::X11Connection; use std::cell::{Cell, RefCell}; use std::num::NonZeroU32; @@ -6,16 +5,15 @@ use x11rb::errors::ReplyError; use x11rb::protocol::xproto::{ConnectionExt, MapState, QueryTreeReply}; use x11rb::protocol::ErrorKind; use x11rb::x11_utils::X11Error; -use x11rb::xcb_ffi::XCBConnection; pub struct AncestorVisibilityState { - pub ancestry: AncestryList, + ancestry: AncestryList, own_window_viewable: Cell, root_id: Cell>, } struct AncestryList { - pub inner: RefCell>, + inner: RefCell>, } impl AncestryList { @@ -111,8 +109,6 @@ impl AncestorVisibilityState { return false; } - eprintln!("Mapped {window_id}"); - if self.own_window_viewable.get() { return false; } @@ -130,8 +126,6 @@ impl AncestorVisibilityState { return; } - eprintln!("Unmapped {window_id}"); - self.own_window_viewable.set(false); } @@ -140,16 +134,12 @@ impl AncestorVisibilityState { return; } - eprintln!("Destroyed {window_id}"); - self.regenerate_from_last_window(connection); } pub fn window_reparented( &self, window_id: NonZeroU32, new_parent: Option, connection: &X11Connection, ) { - eprintln!("Reparent event {window_id} to {new_parent:?}"); - if !self.ancestry.remove_after_window(window_id) { return; } @@ -161,13 +151,8 @@ impl AncestorVisibilityState { self.ancestry.push(Ancestor { id: new_parent, mapped: Cell::new(false) }); - eprintln!("Reparented {window_id} to new parent: {new_parent}"); self.regenerate_from_last_window(connection); - } else { - eprintln!("Unparented {window_id}"); } - - dbg!(self.ancestry.inner.borrow()); } pub fn regenerate_from_last_window(&self, connection: &X11Connection) { @@ -185,7 +170,7 @@ impl AncestorVisibilityState { let mut rechecked_children = Vec::new(); loop { - let Some((mapped, tree)) = fetch_window_info(connection, current_window)? else { + let Some((mut mapped, tree)) = fetch_window_info(connection, current_window)? else { // We got a BadWindow while trying to get a window's info, it must have been destroyed. // Try to go back a layer and fetch the window's state and parent again @@ -245,6 +230,17 @@ impl AncestorVisibilityState { } } + // Despite what's documented, all windows down the parent tree must have the event mask + // bit set, otherwise events are not propagated through to us. + if let Err(e) = + connection.register_tree_structure_events_for_window(current_window)?.check() + { + crate::warn!( + "Could not register SubstructureNotify event for window {current_window}: {e}" + ); + mapped = true; // Assume it is mapped, since we'll possibly not get any events from this window + } + // All checks succeeded, now register the current window info and fetch info from the parent self.ancestry.push(Ancestor { id: current_window, mapped: mapped.into() }); @@ -269,8 +265,6 @@ impl AncestorVisibilityState { self.own_window_viewable.set(self.ancestry.check_all_mapped()); - dbg!(&self.ancestry.inner.borrow()); - Ok(()) } } @@ -282,8 +276,6 @@ fn fetch_window_info( let attrs_cookie = connection.conn.get_window_attributes(window.get())?; let tree_cookie = connection.conn.query_tree(window.get())?; - connection.register_tree_structure_events_for_window(window)?.check_warn(); - let mapped = match attrs_cookie.reply() { Ok(attr) => attr.map_state != MapState::UNMAPPED, Err(ReplyError::X11Error(X11Error { error_kind: ErrorKind::Window, .. })) => { diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index 63f6e999..f885124d 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -105,8 +105,6 @@ impl WindowInner { let visibility_state = AncestorVisibilityState::discover(&connection, xcb_window.id())?; - connection.dbg_event_mask(); - let cookies = [ xcb_window.set_title(&options.title)?, xcb_window.enable_wm_protocols()?, diff --git a/src/platform/x11/xcb_connection.rs b/src/platform/x11/xcb_connection.rs index 4bb196ec..229238e3 100644 --- a/src/platform/x11/xcb_connection.rs +++ b/src/platform/x11/xcb_connection.rs @@ -128,12 +128,4 @@ impl X11Connection { &ChangeWindowAttributesAux::new().event_mask(EventMask::SUBSTRUCTURE_NOTIFY), ) } - - pub fn dbg_event_mask(&self) { - let root = self.default_screen().root; - - let events = - self.conn.get_window_attributes(root).unwrap().reply().unwrap().all_event_masks; - dbg!(events); - } } From 3869a973c2b459b83b68887e006c2b2abc1f61cb Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Thu, 20 Aug 2026 03:19:11 +0200 Subject: [PATCH 6/6] fix --- src/platform/x11/visibility_tree.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/platform/x11/visibility_tree.rs b/src/platform/x11/visibility_tree.rs index 268e7eca..4f1abc75 100644 --- a/src/platform/x11/visibility_tree.rs +++ b/src/platform/x11/visibility_tree.rs @@ -236,7 +236,9 @@ impl AncestorVisibilityState { connection.register_tree_structure_events_for_window(current_window)?.check() { crate::warn!( - "Could not register SubstructureNotify event for window {current_window}: {e}" + "Could not register SubstructureNotify event for window {}: {}", + current_window, + e ); mapped = true; // Assume it is mapped, since we'll possibly not get any events from this window }