Skip to content

Commit ebd09fc

Browse files
committed
Harden everything, make child window automatically track parent window's
1 parent 5d6c910 commit ebd09fc

3 files changed

Lines changed: 172 additions & 78 deletions

File tree

examples/open_parented/src/main.rs

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,8 @@ impl ParentWindowHandler {
2020
let size = window.size().physical;
2121
surface.resize(size.width.try_into()?, size.height.try_into()?)?;
2222

23-
let window_open_options = WindowSettings::new()
24-
.with_size(LogicalSize::new(256, 256))
25-
.with_parent(&window)
26-
.with_title("baseview child");
23+
let window_open_options =
24+
WindowSettings::new().with_size(size).with_parent(&window).with_title("baseview child");
2725

2826
let child_window = Window::create(window_open_options, ChildWindowHandler::new)?;
2927
child_window.show()?;
@@ -37,7 +35,7 @@ impl WindowHandler for ParentWindowHandler {
3735
let mut surface = self.surface.borrow_mut();
3836
let mut buf = surface.buffer_mut()?;
3937
if self.damaged.get() {
40-
buf.fill(0xFFAAAAAA);
38+
buf.fill(0xFFAA0000);
4139
self.damaged.set(false);
4240
}
4341
buf.present()?;
@@ -55,10 +53,7 @@ impl WindowHandler for ParentWindowHandler {
5553
self.damaged.set(true);
5654
}
5755

58-
let child_size =
59-
LogicalSize::new(new_size.logical.width / 2., new_size.logical.height / 2.);
6056
self.child_window.suggest_fallback_scale_factor(new_size.scale_factor)?;
61-
self.child_window.resize(child_size)?;
6257
Ok(())
6358
}
6459

@@ -95,7 +90,7 @@ impl WindowHandler for ChildWindowHandler {
9590
let mut surface = self.surface.borrow_mut();
9691
let mut buf = surface.buffer_mut()?;
9792
if self.damaged.get() {
98-
buf.fill(0xFFAA0000);
93+
buf.fill(0xFFAAAAAA);
9994
self.damaged.set(false);
10095
}
10196
buf.present()?;

src/platform/x11/event_loop.rs

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ pub(crate) struct EventLoop {
5151
handler: Box<dyn WindowHandler>,
5252
window: Rc<WindowInner>,
5353

54-
new_physical_size: Option<PhysicalSize<u16>>,
54+
new_size: Option<PhysicalSize<u16>>,
55+
new_parent_size: Option<PhysicalSize<u16>>,
5556
exposed: bool,
5657

5758
loop_signal: LoopSignal,
@@ -90,7 +91,8 @@ impl EventLoop {
9091
Ok(Self {
9192
loop_signal: inner.get_signal(),
9293
handler,
93-
new_physical_size: None,
94+
new_size: None,
95+
new_parent_size: None,
9496
exposed: false,
9597
drag_n_drop: DragNDropState::NoCurrentSession,
9698
xkb_state: XkbcommonState::new(&window.connection),
@@ -162,7 +164,20 @@ impl EventLoop {
162164
}
163165

164166
fn handle_coalesced_resize_events(&mut self) -> Result<(), FatalError> {
165-
let Some(new_size) = self.new_physical_size.take() else { return Ok(()) };
167+
if let Some(new_parent_size) = self.new_parent_size.take() {
168+
if new_parent_size != self.window.get_size() {
169+
// The parent was resized, which means we should resize ourselves too.
170+
if let Err(e) = self.window.xcb_window.resize(new_parent_size.cast()) {
171+
crate::warn!("Failed to resize window: {}", e);
172+
} else {
173+
// Makes the rest of this function run on the new parent size immediately (without waiting for a ConfigureNotify round-trip)
174+
// Also overrides any new sizes we may have received this event loop iteration,it would probably be invalidated anyway
175+
self.new_size = Some(new_parent_size);
176+
}
177+
}
178+
}
179+
180+
let Some(new_size) = self.new_size.take() else { return Ok(()) };
166181
let previous = self.window.store_size(new_size);
167182

168183
if previous == new_size {
@@ -384,9 +399,15 @@ impl EventLoop {
384399
warn!("Received leftover X11 error: {:?}", e);
385400
}
386401

387-
XEvent::ConfigureNotify(event) if event.window == self.window.raw_id() => {
402+
XEvent::ConfigureNotify(event) => {
388403
// These are coalesced and then handled asynchronously at the end of the event loop
389-
self.new_physical_size = Some(PhysicalSize::new(event.width, event.height));
404+
if event.window == self.window.raw_id() {
405+
self.new_size = Some(PhysicalSize::new(event.width, event.height));
406+
} else if Some(event.window) == self.window.visibility_state.parent_id() {
407+
// Also resize the window if the parent is resized
408+
// This works around some hosts that might not call set_size() right away (or at all...)
409+
self.new_parent_size = Some(PhysicalSize::new(event.width, event.height));
410+
}
390411
}
391412

392413
XEvent::Expose(e) if e.window == self.window.raw_id() => self.exposed = true,
@@ -494,13 +515,16 @@ impl EventLoop {
494515
self.window.visibility_state.window_unmapped(e.window);
495516
}
496517

497-
XEvent::ReparentNotify(e) => {
498-
dbg!(e, e.window, e.parent); // TODO
499-
}
518+
XEvent::ReparentNotify(e) => self.window.visibility_state.window_reparented(
519+
e.window,
520+
e.parent,
521+
&self.window.connection.conn,
522+
),
500523

501-
XEvent::DestroyNotify(e) => {
502-
dbg!(e, e.window); // TODO
503-
}
524+
XEvent::DestroyNotify(e) => self
525+
.window
526+
.visibility_state
527+
.window_destroyed(e.window, &self.window.connection.conn),
504528

505529
_ => {}
506530
}

src/platform/x11/visibility_tree.rs

Lines changed: 133 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ struct AncestryList {
1717
}
1818

1919
impl AncestryList {
20-
pub fn new() -> Self {
21-
Self { inner: RefCell::new(Vec::new()) }
20+
pub fn new(own_window: Window) -> Self {
21+
Self { inner: RefCell::new(vec![Ancestor { id: own_window, mapped: false.into() }]) }
2222
}
2323

2424
pub fn pop_id(&self) -> Option<Window> {
@@ -33,6 +33,32 @@ impl AncestryList {
3333
self.inner.borrow_mut().push(ancestor);
3434
}
3535

36+
pub fn parent_id(&self) -> Option<Window> {
37+
self.inner.borrow().get(1).map(|a| a.id)
38+
}
39+
40+
pub fn remove_window(&self, id: Window) -> bool {
41+
let mut inner = self.inner.borrow_mut();
42+
let Some(index) = inner.iter().position(|a| a.id == id) else {
43+
return false;
44+
};
45+
46+
inner.truncate(index.saturating_add(1));
47+
48+
true
49+
}
50+
51+
pub fn remove_after_window(&self, id: Window) -> bool {
52+
let mut inner = self.inner.borrow_mut();
53+
let Some(index) = inner.iter().position(|a| a.id == id) else {
54+
return false;
55+
};
56+
57+
inner.truncate(index.saturating_add(2));
58+
59+
true
60+
}
61+
3662
pub fn check_all_mapped(&self) -> bool {
3763
self.inner.borrow().iter().all(|a| a.mapped.get())
3864
}
@@ -56,20 +82,89 @@ struct Ancestor {
5682

5783
impl AncestorVisibilityState {
5884
pub fn discover(connection: &XCBConnection, own_window_id: Window) -> Result<Self, ReplyError> {
59-
let mut current_window = own_window_id;
60-
let ancestry = AncestryList::new();
85+
let this = Self {
86+
ancestry: AncestryList::new(own_window_id),
87+
own_window_viewable: Cell::new(false),
88+
};
89+
90+
this.try_regenerate_from_last_window(connection)?;
91+
92+
Ok(this)
93+
}
94+
95+
pub fn own_window_is_viewable(&self) -> bool {
96+
self.own_window_viewable.get()
97+
}
98+
99+
pub fn parent_id(&self) -> Option<Window> {
100+
self.ancestry.parent_id()
101+
}
102+
103+
/// Returns `true` if this operation made our own window visible
104+
pub fn window_mapped(&self, window_id: Window) -> bool {
105+
if !self.ancestry.set_mapped(window_id, true) {
106+
return false;
107+
}
108+
109+
if self.own_window_viewable.get() {
110+
return false;
111+
}
112+
113+
let all_mapped = self.ancestry.check_all_mapped();
114+
if all_mapped {
115+
self.own_window_viewable.set(true);
116+
}
117+
118+
all_mapped
119+
}
120+
121+
pub fn window_unmapped(&self, window_id: Window) {
122+
if !self.ancestry.set_mapped(window_id, false) {
123+
return;
124+
}
125+
126+
self.own_window_viewable.set(false);
127+
}
128+
129+
pub fn window_destroyed(&self, window_id: Window, connection: &XCBConnection) {
130+
if !self.ancestry.remove_window(window_id) {
131+
return;
132+
}
133+
134+
self.regenerate_from_last_window(connection);
135+
}
136+
137+
pub fn window_reparented(
138+
&self, window_id: Window, new_parent: Window, connection: &XCBConnection,
139+
) {
140+
if !self.ancestry.remove_after_window(window_id) {
141+
return;
142+
}
143+
144+
self.ancestry.push(Ancestor { id: new_parent, mapped: Cell::new(false) });
145+
146+
self.regenerate_from_last_window(connection);
147+
}
148+
149+
pub fn regenerate_from_last_window(&self, connection: &XCBConnection) {
150+
if let Err(e) = self.try_regenerate_from_last_window(connection) {
151+
crate::warn!("Failed to generate window ancestry list: {}", e)
152+
}
153+
}
154+
155+
fn try_regenerate_from_last_window(
156+
&self, connection: &XCBConnection,
157+
) -> Result<(), ReplyError> {
158+
let Some(mut current_window) = self.ancestry.pop_id() else { return Ok(()) };
61159

62160
loop {
63-
let (Some(mapped), Some(tree)) = (
64-
fetch_is_window_mapped(connection, current_window)?,
65-
fetch_window_tree(connection, current_window)?,
66-
) else {
161+
let Some((mapped, tree)) = fetch_window_info(connection, current_window)? else {
67162
// We got a BadWindow while trying to get a window's info, it must have been destroyed.
68163
// Try to go back a layer and fetch the window's state and parent again
69164

70165
crate::warn!("Failed to get info for window {}: XBadWindow", current_window);
71166

72-
let Some(previous_parent) = ancestry.pop_id() else {
167+
let Some(previous_parent) = self.ancestry.pop_id() else {
73168
// No previous parent, this was the first window. Stop everything and return an empty state
74169
break;
75170
};
@@ -84,7 +179,7 @@ impl AncestorVisibilityState {
84179
}
85180

86181
// Sanity check if the current parent is actually registered to have the child in its children list
87-
if let Some(child_id) = ancestry.last_id() {
182+
if let Some(child_id) = self.ancestry.last_id() {
88183
if !tree.children.contains(&child_id) {
89184
// The child has been orphaned, it must have been reparented between our server queries.
90185
// Go back a step and check again.
@@ -96,14 +191,14 @@ impl AncestorVisibilityState {
96191
&tree.children
97192
);
98193

99-
let Some(_) = ancestry.pop_id() else { unreachable!() };
194+
let Some(_) = self.ancestry.pop_id() else { unreachable!() };
100195
current_window = child_id;
101196
continue;
102197
}
103198
}
104199

105200
// All checks succeeded, now register the current window info and fetch info from the parent
106-
ancestry.push(Ancestor { id: current_window, mapped: mapped.into() });
201+
self.ancestry.push(Ancestor { id: current_window, mapped: mapped.into() });
107202

108203
if tree.parent == tree.root {
109204
// No need to get info for the root, we assume it's always there. We can just stop here.
@@ -113,58 +208,38 @@ impl AncestorVisibilityState {
113208
current_window = tree.parent;
114209
}
115210

116-
Ok(Self { own_window_viewable: ancestry.check_all_mapped().into(), ancestry })
117-
}
211+
self.own_window_viewable.set(self.ancestry.check_all_mapped());
118212

119-
pub fn own_window_is_viewable(&self) -> bool {
120-
self.own_window_viewable.get()
213+
Ok(())
121214
}
215+
}
122216

123-
/// Returns `true` if this operation made our own window visible
124-
pub fn window_mapped(&self, mapped_window_id: Window) -> bool {
125-
if !self.ancestry.set_mapped(mapped_window_id, true) {
126-
return false;
127-
}
128-
129-
if self.own_window_viewable.get() {
130-
return false;
217+
/// Returns Ok(None) on BadWindow
218+
fn fetch_window_info(
219+
connection: &XCBConnection, window: Window,
220+
) -> Result<Option<(bool, QueryTreeReply)>, ReplyError> {
221+
let attrs_cookie = connection.get_window_attributes(window)?;
222+
let tree_cookie = connection.query_tree(window)?;
223+
224+
let mapped = match attrs_cookie.reply() {
225+
Ok(attr) => attr.map_state != MapState::UNMAPPED,
226+
Err(ReplyError::X11Error(X11Error { error_kind: ErrorKind::Window, .. })) => {
227+
tree_cookie.discard_reply_and_errors();
228+
return Ok(None);
131229
}
132-
133-
let all_mapped = self.ancestry.check_all_mapped();
134-
if all_mapped {
135-
self.own_window_viewable.set(true);
230+
Err(e) => {
231+
tree_cookie.discard_reply_and_errors();
232+
return Err(e);
136233
}
234+
};
137235

138-
all_mapped
139-
}
140-
141-
pub fn window_unmapped(&self, mapped_window_id: Window) {
142-
if !self.ancestry.set_mapped(mapped_window_id, false) {
143-
return;
236+
let tree = match tree_cookie.reply() {
237+
Ok(tree) => tree,
238+
Err(ReplyError::X11Error(X11Error { error_kind: ErrorKind::Window, .. })) => {
239+
return Ok(None)
144240
}
241+
Err(e) => return Err(e),
242+
};
145243

146-
self.own_window_viewable.set(false);
147-
}
148-
}
149-
150-
/// Returns Ok(None) on BadWindow
151-
fn fetch_is_window_mapped(
152-
connection: &XCBConnection, window: Window,
153-
) -> Result<Option<bool>, ReplyError> {
154-
match connection.get_window_attributes(window)?.reply() {
155-
Ok(attr) => Ok(Some(attr.map_state != MapState::UNMAPPED)),
156-
Err(ReplyError::X11Error(X11Error { error_kind: ErrorKind::Window, .. })) => Ok(None),
157-
Err(e) => Err(e),
158-
}
159-
}
160-
161-
/// Returns Ok(None) on BadWindow
162-
fn fetch_window_tree(
163-
connection: &XCBConnection, window: Window,
164-
) -> Result<Option<QueryTreeReply>, ReplyError> {
165-
match connection.query_tree(window)?.reply() {
166-
Ok(tree) => Ok(Some(tree)),
167-
Err(ReplyError::X11Error(X11Error { error_kind: ErrorKind::Window, .. })) => Ok(None),
168-
Err(e) => Err(e),
169-
}
244+
Ok(Some((mapped, tree)))
170245
}

0 commit comments

Comments
 (0)