Skip to content

Commit d667a39

Browse files
francesca64tomaka
authored andcommitted
x11: Destroy dropped windows; handle WM_DELETE_WINDOW (#416)
Fixes #79 #414 This changes the implementation of Drop for Window to send a WM_DELETE_WINDOW ClientMessage, offloading all the cleanup and window destruction to the event loop. Unsurprisingly, this entails that the event loop now handles WM_DELETE_WINDOW using the behavior that was previously contained in Window's Drop implementation, along with destroying the Window. Not only does this mean that dropped windows are closed, but also that clicking the × button on the window actually closes it now. The previous implemention of Drop was also broken, as the event loop would be (seemingly permenanently) frozen after its invocation. That was caused specifically by the mutex locking, and is no longer an issue now that the locking is done in the event loop. While I don't have full confidence that it makes sense for the Drop implementation to behave this way, this is nonetheless a significant improvement. The previous behavior led to inconsistent state, panics, and event loop breakage, along with not actually destroying the window. This additionally makes the assumption that users don't need Focused or CursorLeft events for the destroyed window, as Closed is adequate to indicate unfocus, and users may not expect to receive events for closed/dropped windows. In my testing, those specific events were sent immediately after the window was destroyed, though this sort of behavior could be WM-specific. I've opted to explicitly suppress those events in the case of the window no longer existing.
1 parent 76118af commit d667a39

File tree

2 files changed

+114
-43
lines changed

2 files changed

+114
-43
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Unreleased
22

3+
- On X11, dropping a `Window` actually closes it now, and clicking the window's × button (or otherwise having the WM signal to close it) will result in the window closing.
34
- Added `WindowBuilderExt` methods for macos: `with_titlebar_transparent`,
45
`with_title_hidden`, `with_titlebar_buttons_hidden`,
56
`with_fullsize_content_view`.

src/platform/linux/x11/mod.rs

+113-43
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,22 @@ impl EventsLoop {
217217
let window_id = mkwid(window);
218218

219219
if client_msg.data.get_long(0) as ffi::Atom == self.wm_delete_window {
220-
callback(Event::WindowEvent { window_id, event: WindowEvent::Closed })
220+
callback(Event::WindowEvent { window_id, event: WindowEvent::Closed });
221+
222+
let mut windows = self.windows.lock().unwrap();
223+
let window_data = windows.remove(&WindowId(window));
224+
let _lock = GLOBAL_XOPENIM_LOCK.lock().unwrap();
225+
unsafe {
226+
if let Some(window_data) = window_data {
227+
(self.display.xlib.XDestroyIC)(window_data.ic);
228+
(self.display.xlib.XCloseIM)(window_data.im);
229+
self.display.check_errors()
230+
.expect("Failed to close XIM");
231+
}
232+
(self.display.xlib.XDestroyWindow)(self.display.display, window);
233+
self.display.check_errors()
234+
.expect("Failed to destroy window");
235+
}
221236
} else if client_msg.message_type == self.dnd.atoms.enter {
222237
let source_window = client_msg.data.get_long(0) as c_ulong;
223238
let flags = client_msg.data.get_long(1);
@@ -357,20 +372,23 @@ impl EventsLoop {
357372
// Gymnastics to ensure self.windows isn't locked when we invoke callback
358373
let (resized, moved) = {
359374
let mut windows = self.windows.lock().unwrap();
360-
let window_data = windows.get_mut(&WindowId(window)).unwrap();
361-
if window_data.config.is_none() {
362-
window_data.config = Some(WindowConfig::new(xev));
363-
(true, true)
375+
if let Some(window_data) = windows.get_mut(&WindowId(window)) {
376+
if window_data.config.is_none() {
377+
window_data.config = Some(WindowConfig::new(xev));
378+
(true, true)
379+
} else {
380+
let window_state = window_data.config.as_mut().unwrap();
381+
(if window_state.size != new_size {
382+
window_state.size = new_size;
383+
true
384+
} else { false },
385+
if window_state.position != new_position {
386+
window_state.position = new_position;
387+
true
388+
} else { false })
389+
}
364390
} else {
365-
let window_state = window_data.config.as_mut().unwrap();
366-
(if window_state.size != new_size {
367-
window_state.size = new_size;
368-
true
369-
} else { false },
370-
if window_state.position != new_position {
371-
window_state.position = new_position;
372-
true
373-
} else { false })
391+
return;
374392
}
375393
};
376394
if resized {
@@ -444,7 +462,13 @@ impl EventsLoop {
444462

445463
const INIT_BUFF_SIZE: usize = 16;
446464
let mut windows = self.windows.lock().unwrap();
447-
let window_data = windows.get_mut(&WindowId(window)).unwrap();
465+
let window_data = {
466+
if let Some(window_data) = windows.get_mut(&WindowId(window)) {
467+
window_data
468+
} else {
469+
return;
470+
}
471+
};
448472
/* buffer allocated on heap instead of stack, due to the possible
449473
* reallocation */
450474
let mut buffer: Vec<u8> = vec![mem::uninitialized(); INIT_BUFF_SIZE];
@@ -494,9 +518,16 @@ impl EventsLoop {
494518
let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) };
495519
let window_id = mkwid(xev.event);
496520
let device_id = mkdid(xev.deviceid);
497-
if (xev.flags & ffi::XIPointerEmulated) != 0 && self.windows.lock().unwrap().get(&WindowId(xev.event)).unwrap().multitouch {
498-
// Deliver multi-touch events instead of emulated mouse events.
499-
return;
521+
if (xev.flags & ffi::XIPointerEmulated) != 0 {
522+
let windows = self.windows.lock().unwrap();
523+
if let Some(window_data) = windows.get(&WindowId(xev.event)) {
524+
if window_data.multitouch {
525+
// Deliver multi-touch events instead of emulated mouse events.
526+
return;
527+
}
528+
} else {
529+
return;
530+
}
500531
}
501532

502533
let modifiers = ModifiersState::from(xev.mods);
@@ -578,7 +609,13 @@ impl EventsLoop {
578609
// Gymnastics to ensure self.windows isn't locked when we invoke callback
579610
if {
580611
let mut windows = self.windows.lock().unwrap();
581-
let window_data = windows.get_mut(&WindowId(xev.event)).unwrap();
612+
let window_data = {
613+
if let Some(window_data) = windows.get_mut(&WindowId(xev.event)) {
614+
window_data
615+
} else {
616+
return;
617+
}
618+
};
582619
if Some(new_cursor_pos) != window_data.cursor_pos {
583620
window_data.cursor_pos = Some(new_cursor_pos);
584621
true
@@ -678,29 +715,43 @@ impl EventsLoop {
678715
ffi::XI_Leave => {
679716
let xev: &ffi::XILeaveEvent = unsafe { &*(xev.data as *const _) };
680717

681-
callback(Event::WindowEvent {
682-
window_id: mkwid(xev.event),
683-
event: CursorLeft { device_id: mkdid(xev.deviceid) },
684-
});
718+
// Leave, FocusIn, and FocusOut can be received by a window that's already
719+
// been destroyed, which the user presumably doesn't want to deal with.
720+
let window_closed = self.windows
721+
.lock()
722+
.unwrap()
723+
.get(&WindowId(xev.event))
724+
.is_none();
725+
726+
if !window_closed {
727+
callback(Event::WindowEvent {
728+
window_id: mkwid(xev.event),
729+
event: CursorLeft { device_id: mkdid(xev.deviceid) },
730+
});
731+
}
685732
}
686733
ffi::XI_FocusIn => {
687734
let xev: &ffi::XIFocusInEvent = unsafe { &*(xev.data as *const _) };
688735

689736
let window_id = mkwid(xev.event);
690737

691-
unsafe {
692-
let mut windows = self.windows.lock().unwrap();
693-
let window_data = windows.get_mut(&WindowId(xev.event)).unwrap();
694-
(self.display.xlib.XSetICFocus)(window_data.ic);
738+
let mut windows = self.windows.lock().unwrap();
739+
if let Some(window_data) = windows.get_mut(&WindowId(xev.event)) {
740+
unsafe {
741+
(self.display.xlib.XSetICFocus)(window_data.ic);
742+
}
743+
} else {
744+
return;
695745
}
746+
696747
callback(Event::WindowEvent { window_id, event: Focused(true) });
697748

698-
// The deviceid for this event is for a keyboard instead of a pointer, so
699-
// we have to do a little extra work.
749+
// The deviceid for this event is for a keyboard instead of a pointer,
750+
// so we have to do a little extra work.
700751
let device_info = DeviceInfo::get(&self.display, xev.deviceid);
701-
// For master devices, the attachment field contains the ID of the paired
702-
// master device; for the master keyboard, the attachment is the master
703-
// pointer, and vice versa.
752+
// For master devices, the attachment field contains the ID of the
753+
// paired master device; for the master keyboard, the attachment is
754+
// the master pointer, and vice versa.
704755
let pointer_id = unsafe { (*device_info.info) }.attachment;
705756

706757
callback(Event::WindowEvent {
@@ -714,11 +765,16 @@ impl EventsLoop {
714765
}
715766
ffi::XI_FocusOut => {
716767
let xev: &ffi::XIFocusOutEvent = unsafe { &*(xev.data as *const _) };
717-
unsafe {
718-
let mut windows = self.windows.lock().unwrap();
719-
let window_data = windows.get_mut(&WindowId(xev.event)).unwrap();
720-
(self.display.xlib.XUnsetICFocus)(window_data.ic);
768+
769+
let mut windows = self.windows.lock().unwrap();
770+
if let Some(window_data) = windows.get_mut(&WindowId(xev.event)) {
771+
unsafe {
772+
(self.display.xlib.XUnsetICFocus)(window_data.ic);
773+
}
774+
} else {
775+
return;
721776
}
777+
722778
callback(Event::WindowEvent {
723779
window_id: mkwid(xev.event),
724780
event: Focused(false),
@@ -1020,13 +1076,27 @@ impl Window {
10201076
impl Drop for Window {
10211077
fn drop(&mut self) {
10221078
if let (Some(windows), Some(display)) = (self.windows.upgrade(), self.display.upgrade()) {
1023-
let mut windows = windows.lock().unwrap();
1024-
let w = windows.remove(&self.window.id()).unwrap();
1025-
let _lock = GLOBAL_XOPENIM_LOCK.lock().unwrap();
1026-
unsafe {
1027-
(display.xlib.XDestroyIC)(w.ic);
1028-
(display.xlib.XCloseIM)(w.im);
1029-
}
1079+
// It's possible for the Window object to outlive the actual window, so we need to
1080+
// check for that, lest the program explode with BadWindow errors soon after this.
1081+
let window_closed = windows
1082+
.lock()
1083+
.unwrap()
1084+
.get(&self.window.id())
1085+
.is_none();
1086+
if !window_closed { unsafe {
1087+
let wm_protocols_atom = util::get_atom(&display, b"WM_PROTOCOLS\0")
1088+
.expect("Failed to call XInternAtom (WM_PROTOCOLS)");
1089+
let wm_delete_atom = util::get_atom(&display, b"WM_DELETE_WINDOW\0")
1090+
.expect("Failed to call XInternAtom (WM_DELETE_WINDOW)");
1091+
util::send_client_msg(
1092+
&display,
1093+
self.window.id().0,
1094+
self.window.id().0,
1095+
wm_protocols_atom,
1096+
None,
1097+
(wm_delete_atom as _, ffi::CurrentTime as _, 0, 0, 0),
1098+
).expect("Failed to send window deletion message");
1099+
} }
10301100
}
10311101
}
10321102
}

0 commit comments

Comments
 (0)