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,103 +125,6 @@ impl Window2 {
win
};
// Enable drag and drop
unsafe {
let atom_name: *const libc::c_char = b"XdndAware\0".as_ptr() as _;
let atom = (display.xlib.XInternAtom)(display.display, atom_name, ffi::False);
let version = &5; // Latest version; hasn't changed since 2002
(display.xlib.XChangeProperty)(
display.display,
window,
atom,
ffi::XA_ATOM,
32,
ffi::PropModeReplace,
version,
1
);
display.check_errors().expect("Failed to set drag and drop properties");
}
// Set ICCCM WM_CLASS property based on initial window title
// Must be done *before* mapping the window by ICCCM 4.1.2.5
unsafe {
with_c_str(&*window_attrs.title, |c_name| {
let hint = (display.xlib.XAllocClassHint)();
(*hint).res_name = 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.check_errors().expect("Failed to call XSetClassHint");
(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
let mut size_hints: ffi::XSizeHints = unsafe { mem::zeroed() };
size_hints.flags = ffi::PSize;
size_hints.width = dimensions.0 as i32;
size_hints.height = dimensions.1 as i32;
if let Some(dimensions) = window_attrs.min_dimensions {
size_hints.flags |= ffi::PMinSize;
size_hints.min_width = dimensions.0 as i32;
size_hints.min_height = dimensions.1 as i32;
}
if let Some(dimensions) = window_attrs.max_dimensions {
size_hints.flags |= ffi::PMaxSize;
size_hints.max_width = dimensions.0 as i32;
size_hints.max_height = dimensions.1 as i32;
}
unsafe {
(display.xlib.XSetNormalHints)(display.display, window, &mut size_hints);
display.check_errors().expect("Failed to call XSetNormalHints");
}
// Select XInput2 events
{
let mask = ffi::XI_MotionMask
| ffi::XI_ButtonPressMask | ffi::XI_ButtonReleaseMask
// | ffi::XI_KeyPressMask | ffi::XI_KeyReleaseMask
| ffi::XI_EnterMask | ffi::XI_LeaveMask
| ffi::XI_FocusInMask | ffi::XI_FocusOutMask
| if window_attrs.multitouch { ffi::XI_TouchBeginMask | ffi::XI_TouchUpdateMask | ffi::XI_TouchEndMask } else { 0 };
unsafe {
let mut event_mask = ffi::XIEventMask{
deviceid: ffi::XIAllMasterDevices,
mask: mem::transmute::<*const i32, *mut c_uchar>(&mask as *const i32),
mask_len: mem::size_of_val(&mask) as c_int,
};
(display.xinput2.XISelectEvents)(display.display, window,
&mut event_mask as *mut ffi::XIEventMask, 1);
};
}
let window = Window2 {
x: Arc::new(XWindow {
display: display.clone(),
@ -232,35 +135,138 @@ impl Window2 {
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);
window.set_maximized(window_attrs.maximized);
window.set_fullscreen(window_attrs.fullscreen.clone());
if window_attrs.visible {
{
let ref x_window: &XWindow = window.x.borrow();
// Enable drag and drop
unsafe {
let ref x_window: &XWindow = window.x.borrow();
let atom_name: *const libc::c_char = b"XdndAware\0".as_ptr() as _;
let atom = (display.xlib.XInternAtom)(display.display, atom_name, ffi::False);
let version = &5; // Latest version; hasn't changed since 2002
(display.xlib.XChangeProperty)(
display.display,
x_window.window,
atom,
ffi::XA_ATOM,
32,
ffi::PropModeReplace,
version,
1
);
display.check_errors().expect("Failed to set drag and drop properties");
}
// XSetInputFocus generates an error if the window is not visible,
// therefore we wait until it's the case.
loop {
let mut window_attributes = mem::uninitialized();
(display.xlib.XGetWindowAttributes)(display.display, x_window.window, &mut window_attributes);
display.check_errors().expect("Failed to call XGetWindowAttributes");
// Set ICCCM WM_CLASS property based on initial window title
// Must be done *before* mapping the window by ICCCM 4.1.2.5
unsafe {
with_c_str(&*window_attrs.title, |c_name| {
let hint = (display.xlib.XAllocClassHint)();
(*hint).res_name = c_name as *mut libc::c_char;
(*hint).res_class = c_name as *mut libc::c_char;
(display.xlib.XSetClassHint)(display.display, x_window.window, hint);
display.check_errors().expect("Failed to call XSetClassHint");
(display.xlib.XFree)(hint as *mut _);
});
}
if window_attributes.map_state == ffi::IsViewable {
(display.xlib.XSetInputFocus)(
display.display,
x_window.window,
ffi::RevertToParent,
ffi::CurrentTime
);
display.check_errors().expect("Failed to call XSetInputFocus");
break;
// set size hints
let mut size_hints: ffi::XSizeHints = unsafe { mem::zeroed() };
size_hints.flags = ffi::PSize;
size_hints.width = dimensions.0 as i32;
size_hints.height = dimensions.1 as i32;
if let Some(dimensions) = window_attrs.min_dimensions {
size_hints.flags |= ffi::PMinSize;
size_hints.min_width = dimensions.0 as i32;
size_hints.min_height = dimensions.1 as i32;
}
if let Some(dimensions) = window_attrs.max_dimensions {
size_hints.flags |= ffi::PMaxSize;
size_hints.max_width = dimensions.0 as i32;
size_hints.max_height = dimensions.1 as i32;
}
unsafe {
(display.xlib.XSetNormalHints)(display.display, x_window.window, &mut size_hints);
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
{
let mask = ffi::XI_MotionMask
| ffi::XI_ButtonPressMask | ffi::XI_ButtonReleaseMask
// | ffi::XI_KeyPressMask | ffi::XI_KeyReleaseMask
| ffi::XI_EnterMask | ffi::XI_LeaveMask
| ffi::XI_FocusInMask | ffi::XI_FocusOutMask
| if window_attrs.multitouch { ffi::XI_TouchBeginMask | ffi::XI_TouchUpdateMask | ffi::XI_TouchEndMask } else { 0 };
unsafe {
let mut event_mask = ffi::XIEventMask{
deviceid: ffi::XIAllMasterDevices,
mask: mem::transmute::<*const i32, *mut c_uchar>(&mask as *const i32),
mask_len: mem::size_of_val(&mask) as c_int,
};
(display.xinput2.XISelectEvents)(display.display, x_window.window,
&mut event_mask as *mut ffi::XIEventMask, 1);
};
}
// These properties must be set after mapping
window.set_maximized(window_attrs.maximized);
window.set_fullscreen(window_attrs.fullscreen.clone());
if window_attrs.visible {
unsafe {
// XSetInputFocus generates an error if the window is not visible,
// therefore we wait until it's the case.
loop {
let mut window_attributes = mem::uninitialized();
(display.xlib.XGetWindowAttributes)(display.display, x_window.window, &mut window_attributes);
display.check_errors().expect("Failed to call XGetWindowAttributes");
if window_attributes.map_state == ffi::IsViewable {
(display.xlib.XSetInputFocus)(
display.display,
x_window.window,
ffi::RevertToParent,
ffi::CurrentTime
);
display.check_errors().expect("Failed to call XSetInputFocus");
break;
}
// Wait about a frame to avoid too-busy waiting
thread::sleep(Duration::from_millis(16));
}
// Wait about a frame to avoid too-busy waiting
thread::sleep(Duration::from_millis(16));
}
}
}