diff --git a/examples/open_parented/src/main.rs b/examples/open_parented/src/main.rs index cca879de..4c9433fc 100644 --- a/examples/open_parented/src/main.rs +++ b/examples/open_parented/src/main.rs @@ -20,10 +20,8 @@ impl ParentWindowHandler { let size = window.size().physical; surface.resize(size.width.try_into()?, size.height.try_into()?)?; - let window_open_options = WindowSettings::new() - .with_size(LogicalSize::new(256, 256)) - .with_parent(&window) - .with_title("baseview child"); + let window_open_options = + WindowSettings::new().with_size(size).with_parent(&window).with_title("baseview child"); let child_window = Window::create(window_open_options, ChildWindowHandler::new)?; child_window.show()?; @@ -37,7 +35,7 @@ impl WindowHandler for ParentWindowHandler { let mut surface = self.surface.borrow_mut(); let mut buf = surface.buffer_mut()?; if self.damaged.get() { - buf.fill(0xFFAAAAAA); + buf.fill(0xFFAA0000); self.damaged.set(false); } buf.present()?; @@ -55,10 +53,7 @@ impl WindowHandler for ParentWindowHandler { self.damaged.set(true); } - let child_size = - LogicalSize::new(new_size.logical.width / 2., new_size.logical.height / 2.); self.child_window.suggest_fallback_scale_factor(new_size.scale_factor)?; - self.child_window.resize(child_size)?; Ok(()) } @@ -95,7 +90,7 @@ impl WindowHandler for ChildWindowHandler { let mut surface = self.surface.borrow_mut(); let mut buf = surface.buffer_mut()?; if self.damaged.get() { - buf.fill(0xFFAA0000); + buf.fill(0xFFAAAAAA); self.damaged.set(false); } buf.present()?; diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index 4a2e22dc..fa966477 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -51,7 +51,8 @@ pub(crate) struct EventLoop { handler: Box, window: Rc, - new_physical_size: Option>, + new_size: Option>, + new_parent_size: Option>, exposed: bool, loop_signal: LoopSignal, @@ -90,7 +91,8 @@ impl EventLoop { Ok(Self { loop_signal: inner.get_signal(), handler, - new_physical_size: None, + new_size: None, + new_parent_size: None, exposed: false, drag_n_drop: DragNDropState::NoCurrentSession, xkb_state: XkbcommonState::new(&window.connection), @@ -148,7 +150,7 @@ impl EventLoop { } self.exposed = false; - if !self.window.is_mapped.get() { + if !self.window.visibility_state.own_window_is_viewable() { return; } @@ -162,7 +164,20 @@ impl EventLoop { } fn handle_coalesced_resize_events(&mut self) -> Result<(), FatalError> { - let Some(new_size) = self.new_physical_size.take() else { return Ok(()) }; + if let Some(new_parent_size) = self.new_parent_size.take() { + if new_parent_size != self.window.get_size() { + // The parent was resized, which means we should resize ourselves too. + if let Err(e) = self.window.xcb_window.resize(new_parent_size.cast()) { + crate::warn!("Failed to resize window: {}", e); + } else { + // Makes the rest of this function run on the new parent size immediately (without waiting for a ConfigureNotify round-trip) + // Also overrides any new sizes we may have received this event loop iteration,it would probably be invalidated anyway + self.new_size = Some(new_parent_size); + } + } + } + + let Some(new_size) = self.new_size.take() else { return Ok(()) }; let previous = self.window.store_size(new_size); if previous == new_size { @@ -346,7 +361,7 @@ impl EventLoop { //// // window //// - XEvent::ClientMessage(event) => { + XEvent::ClientMessage(event) if event.window == self.window.raw_id() => { if event.format != 32 { return Ok(()); } @@ -386,15 +401,21 @@ impl EventLoop { XEvent::ConfigureNotify(event) => { // These are coalesced and then handled asynchronously at the end of the event loop - self.new_physical_size = Some(PhysicalSize::new(event.width, event.height)); + 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() { + // 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)); + } } - XEvent::Expose(_) => self.exposed = true, + XEvent::Expose(e) if e.window == self.window.raw_id() => self.exposed = true, //// // mouse //// - XEvent::MotionNotify(event) => { + XEvent::MotionNotify(event) if event.event == self.window.raw_id() => { let physical_pos = PhysicalPosition::new(event.event_x, event.event_y); self.handle_event(Event::Mouse(MouseEvent::CursorMoved { @@ -403,7 +424,7 @@ impl EventLoop { })); } - XEvent::EnterNotify(event) => { + XEvent::EnterNotify(event) if event.event == self.window.raw_id() => { self.handle_event(Event::Mouse(MouseEvent::CursorEntered)); // since no `MOTION_NOTIFY` event is generated when `ENTER_NOTIFY` is generated, // we generate a CursorMoved as well, so the mouse position from here isn't lost @@ -414,32 +435,36 @@ impl EventLoop { })); } - XEvent::LeaveNotify(_) => { + XEvent::LeaveNotify(event) if event.event == self.window.raw_id() => { self.handle_event(Event::Mouse(MouseEvent::CursorLeft)); } - XEvent::ButtonPress(event) => match event.detail { - 4..=7 => { - self.handle_event(Event::Mouse(MouseEvent::WheelScrolled { - delta: match event.detail { - 4 => ScrollDelta::Lines { x: 0.0, y: 1.0 }, - 5 => ScrollDelta::Lines { x: 0.0, y: -1.0 }, - 6 => ScrollDelta::Lines { x: -1.0, y: 0.0 }, - 7 => ScrollDelta::Lines { x: 1.0, y: 0.0 }, - _ => unreachable!(), - }, - modifiers: key_mods(event.state), - })); + XEvent::ButtonPress(event) if event.event == self.window.raw_id() => { + match event.detail { + 4..=7 => { + self.handle_event(Event::Mouse(MouseEvent::WheelScrolled { + delta: match event.detail { + 4 => ScrollDelta::Lines { x: 0.0, y: 1.0 }, + 5 => ScrollDelta::Lines { x: 0.0, y: -1.0 }, + 6 => ScrollDelta::Lines { x: -1.0, y: 0.0 }, + 7 => ScrollDelta::Lines { x: 1.0, y: 0.0 }, + _ => unreachable!(), + }, + modifiers: key_mods(event.state), + })); + } + detail => { + self.handle_event(Event::Mouse(MouseEvent::ButtonPressed { + button: mouse_id(detail), + modifiers: key_mods(event.state), + })); + } } - detail => { - self.handle_event(Event::Mouse(MouseEvent::ButtonPressed { - button: mouse_id(detail), - modifiers: key_mods(event.state), - })); - } - }, + } - XEvent::ButtonRelease(event) if !(4..=7).contains(&event.detail) => { + XEvent::ButtonRelease(event) + if event.event == self.window.raw_id() && !(4..=7).contains(&event.detail) => + { let button_id = mouse_id(event.detail); self.handle_event(Event::Mouse(MouseEvent::ButtonReleased { button: button_id, @@ -450,31 +475,56 @@ impl EventLoop { //// // keys //// - XEvent::KeyPress(event) => { + XEvent::KeyPress(event) if event.event == self.window.raw_id() => { let ev = Event::Keyboard(convert_key_press_event(&event, &mut self.xkb_state)); self.handle_event(ev); } - XEvent::KeyRelease(event) => { + XEvent::KeyRelease(event) if event.event == self.window.raw_id() => { let ev = Event::Keyboard(convert_key_release_event(&event, &mut self.xkb_state)); self.handle_event(ev); } - XEvent::FocusIn(_) => { + XEvent::FocusIn(event) if event.event == self.window.raw_id() => { self.window.is_focused.set(true); self.handle_event(Event::Window(WindowEvent::Focused)); } - XEvent::FocusOut(_) => { + XEvent::FocusOut(e) if e.event == self.window.raw_id() => { self.window.is_focused.set(false); self.handle_event(Event::Window(WindowEvent::Unfocused)); } - XEvent::MapNotify(_) => { - self.window.is_mapped.set(true); - self.exposed = true; + XEvent::MapNotify(e) => { + if e.window == self.window.raw_id() { + self.window.is_mapped.set(true); + } + + let became_viewable = self.window.visibility_state.window_mapped(e.window); + + if became_viewable { + self.exposed = true; + } + } + + XEvent::UnmapNotify(e) => { + if e.window == self.window.raw_id() { + self.window.is_mapped.set(false) + } + + self.window.visibility_state.window_unmapped(e.window); } - XEvent::UnmapNotify(_) => self.window.is_mapped.set(false), + + XEvent::ReparentNotify(e) => self.window.visibility_state.window_reparented( + e.window, + e.parent, + &self.window.connection.conn, + ), + + XEvent::DestroyNotify(e) => self + .window + .visibility_state + .window_destroyed(e.window, &self.window.connection.conn), _ => {} } diff --git a/src/platform/x11/mod.rs b/src/platform/x11/mod.rs index b45c1990..3ce949d0 100644 --- a/src/platform/x11/mod.rs +++ b/src/platform/x11/mod.rs @@ -20,6 +20,7 @@ mod keyboard; mod visual_info; mod xcb_window; +mod visibility_tree; mod window_shared; mod window_thread; diff --git a/src/platform/x11/visibility_tree.rs b/src/platform/x11/visibility_tree.rs new file mode 100644 index 00000000..64eb88df --- /dev/null +++ b/src/platform/x11/visibility_tree.rs @@ -0,0 +1,245 @@ +use std::cell::{Cell, RefCell}; +use x11rb::errors::ReplyError; +use x11rb::protocol::xproto::{ConnectionExt, MapState, QueryTreeReply, Window}; +use x11rb::protocol::ErrorKind; +use x11rb::x11_utils::X11Error; +use x11rb::xcb_ffi::XCBConnection; + +#[cfg_attr(debug_assertions, derive(Debug))] +pub struct AncestorVisibilityState { + ancestry: AncestryList, + own_window_viewable: Cell, +} + +#[cfg_attr(debug_assertions, derive(Debug))] +struct AncestryList { + inner: RefCell>, +} + +impl AncestryList { + pub fn new(own_window: Window) -> Self { + Self { inner: RefCell::new(vec![Ancestor { id: own_window, mapped: false.into() }]) } + } + + pub fn pop_id(&self) -> Option { + self.inner.borrow_mut().pop().map(|a| a.id) + } + + pub fn last_id(&self) -> Option { + self.inner.borrow().last().map(|a| a.id) + } + + pub fn push(&self, ancestor: Ancestor) { + self.inner.borrow_mut().push(ancestor); + } + + pub fn parent_id(&self) -> Option { + self.inner.borrow().get(1).map(|a| a.id) + } + + pub fn remove_window(&self, id: Window) -> bool { + let mut inner = self.inner.borrow_mut(); + let Some(index) = inner.iter().position(|a| a.id == id) else { + return false; + }; + + inner.truncate(index.saturating_add(1)); + + true + } + + pub fn remove_after_window(&self, id: Window) -> bool { + let mut inner = self.inner.borrow_mut(); + let Some(index) = inner.iter().position(|a| a.id == id) else { + return false; + }; + + inner.truncate(index.saturating_add(2)); + + true + } + + pub fn check_all_mapped(&self) -> bool { + self.inner.borrow().iter().all(|a| a.mapped.get()) + } + + pub fn set_mapped(&self, window: Window, mapped: bool) -> bool { + let inner = self.inner.borrow(); + let Some(ancestor) = inner.iter().find(|a| a.id == window) else { + return false; + }; + + ancestor.mapped.set(mapped); + true + } +} + +#[cfg_attr(debug_assertions, derive(Debug))] +struct Ancestor { + id: Window, + mapped: Cell, +} + +impl AncestorVisibilityState { + pub fn discover(connection: &XCBConnection, own_window_id: Window) -> Result { + let this = Self { + ancestry: AncestryList::new(own_window_id), + own_window_viewable: Cell::new(false), + }; + + this.try_regenerate_from_last_window(connection)?; + + Ok(this) + } + + pub fn own_window_is_viewable(&self) -> bool { + self.own_window_viewable.get() + } + + 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 { + if !self.ancestry.set_mapped(window_id, true) { + return false; + } + + if self.own_window_viewable.get() { + return false; + } + + let all_mapped = self.ancestry.check_all_mapped(); + if all_mapped { + self.own_window_viewable.set(true); + } + + all_mapped + } + + pub fn window_unmapped(&self, window_id: Window) { + if !self.ancestry.set_mapped(window_id, false) { + return; + } + + self.own_window_viewable.set(false); + } + + pub fn window_destroyed(&self, window_id: Window, connection: &XCBConnection) { + if !self.ancestry.remove_window(window_id) { + return; + } + + self.regenerate_from_last_window(connection); + } + + pub fn window_reparented( + &self, window_id: Window, new_parent: Window, connection: &XCBConnection, + ) { + if !self.ancestry.remove_after_window(window_id) { + return; + } + + self.ancestry.push(Ancestor { id: new_parent, mapped: Cell::new(false) }); + + self.regenerate_from_last_window(connection); + } + + pub fn regenerate_from_last_window(&self, connection: &XCBConnection) { + 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, + ) -> Result<(), ReplyError> { + let Some(mut current_window) = self.ancestry.pop_id() else { return Ok(()) }; + + 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. + // Try to go back a layer and fetch the window's state and parent again + + crate::warn!("Failed to get info for window {}: XBadWindow", current_window); + + let Some(previous_parent) = self.ancestry.pop_id() else { + // No previous parent, this was the first window. Stop everything and return an empty state + break; + }; + + current_window = previous_parent; + continue; + }; + + if tree.parent == current_window { + // 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) { + // 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 + ); + + let Some(_) = self.ancestry.pop_id() else { unreachable!() }; + current_window = child_id; + continue; + } + } + + // 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() }); + + if tree.parent == tree.root { + // No need to get info for the root, we assume it's always there. We can just stop here. + break; + } + + current_window = tree.parent; + } + + self.own_window_viewable.set(self.ancestry.check_all_mapped()); + + Ok(()) + } +} + +/// Returns Ok(None) on BadWindow +fn fetch_window_info( + connection: &XCBConnection, window: Window, +) -> Result, ReplyError> { + let attrs_cookie = connection.get_window_attributes(window)?; + let tree_cookie = connection.query_tree(window)?; + + let mapped = match attrs_cookie.reply() { + Ok(attr) => attr.map_state != MapState::UNMAPPED, + Err(ReplyError::X11Error(X11Error { error_kind: ErrorKind::Window, .. })) => { + tree_cookie.discard_reply_and_errors(); + return Ok(None); + } + Err(e) => { + tree_cookie.discard_reply_and_errors(); + return Err(e); + } + }; + + let tree = match tree_cookie.reply() { + Ok(tree) => tree, + Err(ReplyError::X11Error(X11Error { error_kind: ErrorKind::Window, .. })) => { + return Ok(None) + } + Err(e) => return Err(e), + }; + + Ok(Some((mapped, tree))) +} diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index bc74b7a4..32a9c340 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -1,4 +1,5 @@ use crate::platform::x11::event_loop::EventLoop; +use crate::platform::x11::visibility_tree::AncestorVisibilityState; use crate::platform::x11::visual_info::WindowVisualConfig; use crate::platform::x11::window_thread::WindowThreadShared; use crate::platform::x11::xcb_connection::get_size_hints; @@ -12,6 +13,7 @@ use raw_window_handle::{DisplayHandle, XlibWindowHandle}; use std::cell::Cell; use std::rc::Rc; use std::sync::Arc; +use x11rb::protocol::xproto; use x11rb::protocol::xproto::{ChangeWindowAttributesAux, ConnectionExt, InputFocus, Visualid}; use x11rb::CURRENT_TIME; @@ -59,6 +61,8 @@ pub(crate) struct WindowInner { pub(crate) is_mapped: Cell, pub(crate) loop_signal: LoopSignal, + pub(crate) visibility_state: AncestorVisibilityState, + pub(crate) main_thread_shared: Arc, } @@ -97,6 +101,12 @@ impl WindowInner { options.parent.map(|p| p.inner.window_id), )?; + connection.register_tree_structure_events()?.check()?; + + let visibility_state = + AncestorVisibilityState::discover(&connection.conn, xcb_window.id().get())?; + dbg!(&visibility_state); + let cookies = [ xcb_window.set_title(&options.title)?, xcb_window.enable_wm_protocols()?, @@ -138,6 +148,8 @@ impl WindowInner { is_mapped: false.into(), main_thread_shared: shared, + visibility_state, + #[cfg(feature = "opengl")] gl_context, })) @@ -271,4 +283,8 @@ impl WindowInner { pub fn size(&self) -> WindowSize { WindowSize::from_physical(self.window_size.get().cast(), self.scaling_factor.get()) } + + pub fn raw_id(&self) -> xproto::Window { + self.xcb_window.id().get() + } } diff --git a/src/platform/x11/xcb_connection.rs b/src/platform/x11/xcb_connection.rs index 4dd4d44c..bbe1854f 100644 --- a/src/platform/x11/xcb_connection.rs +++ b/src/platform/x11/xcb_connection.rs @@ -1,15 +1,19 @@ +use super::cursor; +use crate::platform::*; +use crate::wrappers::xlib::XlibXcbConnection; +use crate::MouseCursor; use std::cell::RefCell; use std::collections::hash_map::{Entry, HashMap}; use std::sync::Arc; use x11rb::connection::Connection; +use x11rb::cookie::VoidCookie; use x11rb::cursor::Handle as CursorHandle; -use x11rb::protocol::xproto::{self, Cursor, Screen}; +use x11rb::errors::ConnectionError; +use x11rb::protocol::xproto::{ + self, ChangeWindowAttributesAux, ConnectionExt, Cursor, EventMask, Screen, +}; use x11rb::resource_manager; - -use super::cursor; -use crate::platform::*; -use crate::wrappers::xlib::XlibXcbConnection; -use crate::MouseCursor; +use x11rb::xcb_ffi::XCBConnection; mod get_property; pub use get_property::GetPropertyError; @@ -109,4 +113,15 @@ impl X11Connection { ) -> core::result::Result, GetPropertyError> { get_property::get_property(window, property, property_type, &self.conn) } + + pub fn register_tree_structure_events( + &self, + ) -> core::result::Result, ConnectionError> { + let root = self.screen().root; + + self.conn.change_window_attributes( + root, + &ChangeWindowAttributesAux::new().event_mask(EventMask::SUBSTRUCTURE_NOTIFY), + ) + } }