From 6e21c18a94be99a4f8cd94387f5a07b218fd0244 Mon Sep 17 00:00:00 2001 From: Francesca Frangipane Date: Sun, 4 Mar 2018 02:22:00 -0500 Subject: [PATCH] x11: Destroy dropped windows; handle WM_DELETE_WINDOW MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- CHANGELOG.md | 1 + src/platform/linux/x11/mod.rs | 130 ++++++++++++++++++++++++---------- 2 files changed, 92 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c7390a6f39..1b185b1a287 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased - Impl `Hash`, `PartialEq`, and `Eq` for `events::ModifiersState`. +- 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. # Version 0.11.1 (2018-02-19) diff --git a/src/platform/linux/x11/mod.rs b/src/platform/linux/x11/mod.rs index fbac1e32af7..4d71534d5d4 100644 --- a/src/platform/linux/x11/mod.rs +++ b/src/platform/linux/x11/mod.rs @@ -217,7 +217,20 @@ impl EventsLoop { let window_id = mkwid(window); if client_msg.data.get_long(0) as ffi::Atom == self.wm_delete_window { - callback(Event::WindowEvent { window_id, event: WindowEvent::Closed }) + callback(Event::WindowEvent { window_id, event: WindowEvent::Closed }); + + let mut windows = self.windows.lock().unwrap(); + let window_data = windows.remove(&WindowId(window)).unwrap(); + let _lock = GLOBAL_XOPENIM_LOCK.lock().unwrap(); + unsafe { + (self.display.xlib.XDestroyIC)(window_data.ic); + (self.display.xlib.XCloseIM)(window_data.im); + self.display.check_errors() + .expect("Failed to close XIM"); + (self.display.xlib.XDestroyWindow)(self.display.display, window); + self.display.check_errors() + .expect("Failed to destroy window"); + } } else if client_msg.message_type == self.dnd.atoms.enter { let source_window = client_msg.data.get_long(0) as c_ulong; let flags = client_msg.data.get_long(1); @@ -678,51 +691,76 @@ impl EventsLoop { ffi::XI_Leave => { let xev: &ffi::XILeaveEvent = unsafe { &*(xev.data as *const _) }; - callback(Event::WindowEvent { - window_id: mkwid(xev.event), - event: CursorLeft { device_id: mkdid(xev.deviceid) }, - }); + // Leave, FocusIn, and FocusOut can be received by a window that's already + // been destroyed, which the user presumably doesn't want to deal with. + let window_closed = self.windows + .lock() + .unwrap() + .get(&WindowId(xev.event)) + .is_none(); + + if !window_closed { + callback(Event::WindowEvent { + window_id: mkwid(xev.event), + event: CursorLeft { device_id: mkdid(xev.deviceid) }, + }); + } } ffi::XI_FocusIn => { let xev: &ffi::XIFocusInEvent = unsafe { &*(xev.data as *const _) }; let window_id = mkwid(xev.event); - unsafe { + let window_closed = unsafe { let mut windows = self.windows.lock().unwrap(); - let window_data = windows.get_mut(&WindowId(xev.event)).unwrap(); - (self.display.xlib.XSetICFocus)(window_data.ic); - } - callback(Event::WindowEvent { window_id, event: Focused(true) }); + if let Some(window_data) = windows.get_mut(&WindowId(xev.event)) { + (self.display.xlib.XSetICFocus)(window_data.ic); + false + } else { + true + } + }; - // The deviceid for this event is for a keyboard instead of a pointer, so - // we have to do a little extra work. - let device_info = DeviceInfo::get(&self.display, xev.deviceid); - // For master devices, the attachment field contains the ID of the paired - // master device; for the master keyboard, the attachment is the master - // pointer, and vice versa. - let pointer_id = unsafe { (*device_info.info) }.attachment; + if !window_closed { + callback(Event::WindowEvent { window_id, event: Focused(true) }); - callback(Event::WindowEvent { - window_id, - event: CursorMoved { - device_id: mkdid(pointer_id), - position: (xev.event_x, xev.event_y), - modifiers: ModifiersState::from(xev.mods), - } - }); + // The deviceid for this event is for a keyboard instead of a pointer, + // so we have to do a little extra work. + let device_info = DeviceInfo::get(&self.display, xev.deviceid); + // For master devices, the attachment field contains the ID of the + // paired master device; for the master keyboard, the attachment is + // the master pointer, and vice versa. + let pointer_id = unsafe { (*device_info.info) }.attachment; + + callback(Event::WindowEvent { + window_id, + event: CursorMoved { + device_id: mkdid(pointer_id), + position: (xev.event_x, xev.event_y), + modifiers: ModifiersState::from(xev.mods), + } + }); + } } ffi::XI_FocusOut => { let xev: &ffi::XIFocusOutEvent = unsafe { &*(xev.data as *const _) }; - unsafe { + + let window_closed = unsafe { let mut windows = self.windows.lock().unwrap(); - let window_data = windows.get_mut(&WindowId(xev.event)).unwrap(); - (self.display.xlib.XUnsetICFocus)(window_data.ic); + if let Some(window_data) = windows.get_mut(&WindowId(xev.event)) { + (self.display.xlib.XUnsetICFocus)(window_data.ic); + false + } else { + true + } + }; + + if !window_closed { + callback(Event::WindowEvent { + window_id: mkwid(xev.event), + event: Focused(false), + }) } - callback(Event::WindowEvent { - window_id: mkwid(xev.event), - event: Focused(false), - }) } ffi::XI_TouchBegin | ffi::XI_TouchUpdate | ffi::XI_TouchEnd => { @@ -1020,13 +1058,27 @@ impl Window { impl Drop for Window { fn drop(&mut self) { if let (Some(windows), Some(display)) = (self.windows.upgrade(), self.display.upgrade()) { - let mut windows = windows.lock().unwrap(); - let w = windows.remove(&self.window.id()).unwrap(); - let _lock = GLOBAL_XOPENIM_LOCK.lock().unwrap(); - unsafe { - (display.xlib.XDestroyIC)(w.ic); - (display.xlib.XCloseIM)(w.im); - } + // It's possible for the Window object to outlive the actual window, so we need to + // check for that, lest the program explode with BadWindow errors soon after this. + let window_closed = windows + .lock() + .unwrap() + .get(&self.window.id()) + .is_none(); + if !window_closed { unsafe { + let wm_protocols_atom = util::get_atom(&display, b"WM_PROTOCOLS\0") + .expect("Failed to call XInternAtom (WM_PROTOCOLS)"); + let wm_delete_atom = util::get_atom(&display, b"WM_DELETE_WINDOW\0") + .expect("Failed to call XInternAtom (WM_DELETE_WINDOW)"); + util::send_client_msg( + &display, + self.window.id().0, + self.window.id().0, + wm_protocols_atom, + None, + (wm_delete_atom as _, ffi::CurrentTime as _, 0, 0, 0), + ).expect("Failed to send window deletion message"); + } } } } }