Skip to content

Commit

Permalink
x11: Set window title prior to mapping window
Browse files Browse the repository at this point in the history
Fixes rust-windowing#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.
  • Loading branch information
francesca64 committed Dec 7, 2017
1 parent d2dd82c commit d414288
Showing 1 changed file with 109 additions and 103 deletions.
212 changes: 109 additions & 103 deletions src/platform/linux/x11/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,85 +125,6 @@ impl Window2 {
win
};

// 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(),
Expand All @@ -214,35 +135,120 @@ 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();

// Set ICCCM WM_CLASS property based on initial window title
// Must be done *before* mapping the window by ICCCM 4.1.2.5
unsafe {
let ref x_window: &XWindow = window.x.borrow();

// 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;
}
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 _);
});
}

// 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);
}

// Wait about a frame to avoid too-busy waiting
thread::sleep(Duration::from_millis(16));
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));
}
}
}
}
Expand Down

0 comments on commit d414288

Please sign in to comment.