Skip to content

Commit

Permalink
x11: Destroy dropped windows; handle WM_DELETE_WINDOW
Browse files Browse the repository at this point in the history
Fixes rust-windowing#79 rust-windowing#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.
  • Loading branch information
francesca64 committed Mar 4, 2018
1 parent 4c62d71 commit 7965d0d
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 39 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)

Expand Down
130 changes: 91 additions & 39 deletions src/platform/linux/x11/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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, 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 => {
Expand Down Expand Up @@ -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");
} }
}
}
}
Expand Down

0 comments on commit 7965d0d

Please sign in to comment.