x11: Set window title prior to mapping window (#362)

Fixes #282

Some tiling window managers (i3, dwm, etc.) determine how a window should behave based on
its name. If the name is set after mapping, then window managers will check the name before
we set it, followed by them detecting it as a change when the name is actually set. That
results in the window briefly behaving in an unexpected way, followed by a rapid switch to
the expected behavior.

In accordance to section 4.1.2 of ICCCM, the name, decorations, size hints, and window
deletion redirection have all been moved up to be set before mapping.
This commit is contained in:
Francesca Sunshine 2017-12-13 16:41:49 -05:00 committed by GitHub
parent d18db208ff
commit 9698d0a8d8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -125,6 +125,24 @@ impl Window2 {
win win
}; };
let window = Window2 {
x: Arc::new(XWindow {
display: display.clone(),
window,
root,
screen_id,
}),
cursor_state: Mutex::new(CursorState::Normal),
};
// Title must be set before mapping, lest some tiling window managers briefly pick up on
// the initial un-titled window state
window.set_title(&window_attrs.title);
window.set_decorations(window_attrs.decorations);
{
let ref x_window: &XWindow = window.x.borrow();
// Enable drag and drop // Enable drag and drop
unsafe { unsafe {
let atom_name: *const libc::c_char = b"XdndAware\0".as_ptr() as _; let atom_name: *const libc::c_char = b"XdndAware\0".as_ptr() as _;
@ -132,7 +150,7 @@ impl Window2 {
let version = &5; // Latest version; hasn't changed since 2002 let version = &5; // Latest version; hasn't changed since 2002
(display.xlib.XChangeProperty)( (display.xlib.XChangeProperty)(
display.display, display.display,
window, x_window.window,
atom, atom,
ffi::XA_ATOM, ffi::XA_ATOM,
32, 32,
@ -150,39 +168,12 @@ impl Window2 {
let hint = (display.xlib.XAllocClassHint)(); let hint = (display.xlib.XAllocClassHint)();
(*hint).res_name = c_name as *mut libc::c_char; (*hint).res_name = c_name as *mut libc::c_char;
(*hint).res_class = c_name as *mut libc::c_char; (*hint).res_class = c_name as *mut libc::c_char;
(display.xlib.XSetClassHint)(display.display, window, hint); (display.xlib.XSetClassHint)(display.display, x_window.window, hint);
display.check_errors().expect("Failed to call XSetClassHint"); display.check_errors().expect("Failed to call XSetClassHint");
(display.xlib.XFree)(hint as *mut _); (display.xlib.XFree)(hint as *mut _);
}); });
} }
// set visibility
if window_attrs.visible {
unsafe {
(display.xlib.XMapRaised)(display.display, window);
(display.xlib.XFlush)(display.display);
}
display.check_errors().expect("Failed to set window visibility");
}
// Opt into handling window close
unsafe {
(display.xlib.XSetWMProtocols)(display.display, window, &ctx.wm_delete_window as *const _ as *mut _, 1);
display.check_errors().expect("Failed to call XSetWMProtocols");
(display.xlib.XFlush)(display.display);
display.check_errors().expect("Failed to call XFlush");
}
// Attempt to make keyboard input repeat detectable
unsafe {
let mut supported_ptr = ffi::False;
(display.xlib.XkbSetDetectableAutoRepeat)(display.display, ffi::True, &mut supported_ptr);
if supported_ptr == ffi::False {
return Err(OsError(format!("XkbSetDetectableAutoRepeat failed")));
}
}
// set size hints // set size hints
let mut size_hints: ffi::XSizeHints = unsafe { mem::zeroed() }; let mut size_hints: ffi::XSizeHints = unsafe { mem::zeroed() };
size_hints.flags = ffi::PSize; size_hints.flags = ffi::PSize;
@ -199,10 +190,37 @@ impl Window2 {
size_hints.max_height = dimensions.1 as i32; size_hints.max_height = dimensions.1 as i32;
} }
unsafe { unsafe {
(display.xlib.XSetNormalHints)(display.display, window, &mut size_hints); (display.xlib.XSetNormalHints)(display.display, x_window.window, &mut size_hints);
display.check_errors().expect("Failed to call XSetNormalHints"); display.check_errors().expect("Failed to call XSetNormalHints");
} }
// Opt into handling window close
unsafe {
(display.xlib.XSetWMProtocols)(display.display, x_window.window, &ctx.wm_delete_window as *const _ as *mut _, 1);
display.check_errors().expect("Failed to call XSetWMProtocols");
(display.xlib.XFlush)(display.display);
display.check_errors().expect("Failed to call XFlush");
}
// Set visibility (map window)
if window_attrs.visible {
unsafe {
(display.xlib.XMapRaised)(display.display, x_window.window);
(display.xlib.XFlush)(display.display);
}
display.check_errors().expect("Failed to set window visibility");
}
// Attempt to make keyboard input repeat detectable
unsafe {
let mut supported_ptr = ffi::False;
(display.xlib.XkbSetDetectableAutoRepeat)(display.display, ffi::True, &mut supported_ptr);
if supported_ptr == ffi::False {
return Err(OsError(format!("XkbSetDetectableAutoRepeat failed")));
}
}
// Select XInput2 events // Select XInput2 events
{ {
let mask = ffi::XI_MotionMask let mask = ffi::XI_MotionMask
@ -217,30 +235,17 @@ impl Window2 {
mask: mem::transmute::<*const i32, *mut c_uchar>(&mask as *const i32), mask: mem::transmute::<*const i32, *mut c_uchar>(&mask as *const i32),
mask_len: mem::size_of_val(&mask) as c_int, mask_len: mem::size_of_val(&mask) as c_int,
}; };
(display.xinput2.XISelectEvents)(display.display, window, (display.xinput2.XISelectEvents)(display.display, x_window.window,
&mut event_mask as *mut ffi::XIEventMask, 1); &mut event_mask as *mut ffi::XIEventMask, 1);
}; };
} }
let window = Window2 { // These properties must be set after mapping
x: Arc::new(XWindow {
display: display.clone(),
window,
root,
screen_id,
}),
cursor_state: Mutex::new(CursorState::Normal),
};
window.set_title(&window_attrs.title);
window.set_decorations(window_attrs.decorations);
window.set_maximized(window_attrs.maximized); window.set_maximized(window_attrs.maximized);
window.set_fullscreen(window_attrs.fullscreen.clone()); window.set_fullscreen(window_attrs.fullscreen.clone());
if window_attrs.visible { if window_attrs.visible {
unsafe { unsafe {
let ref x_window: &XWindow = window.x.borrow();
// XSetInputFocus generates an error if the window is not visible, // XSetInputFocus generates an error if the window is not visible,
// therefore we wait until it's the case. // therefore we wait until it's the case.
loop { loop {
@ -264,6 +269,7 @@ impl Window2 {
} }
} }
} }
}
// returning // returning
Ok(window) Ok(window)