X11: Improve hint support (#529)

Fixes #257
This commit is contained in:
Francesca Frangipane 2018-05-20 10:47:22 -04:00 committed by GitHub
parent f51f7c0ca8
commit cebd15bfd1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 235 additions and 13 deletions

View file

@ -21,6 +21,8 @@
- On Windows, using `CursorState::Hide` when the cursor is grabbed now ungrabs the cursor first.
- Implemented `MouseCursor::NoneCursor` on Windows.
- Added `WindowBuilder::with_always_on_top` and `Window::set_always_on_top`. Implemented on Windows, macOS, and X11.
- On X11, `WindowBuilderExt` now has `with_class`, `with_override_redirect`, and `with_x11_window_type` to allow for more control over window creation. `WindowExt` additionally has `set_urgent`.
- More hints are set by default on X11, including `_NET_WM_PID` and `WM_CLIENT_MACHINE`. Note that prior to this, the `WM_CLASS` hint was automatically set to whatever value was passed to `with_title`. It's now set to the executable name to better conform to expectations and the specification; if this is undesirable, you must explicitly use `WindowBuilderExt::with_class`.
# Version 0.14.0 (2018-05-09)

View file

@ -15,7 +15,7 @@
//! - Calling `Window::new(&events_loop)`.
//! - Calling `let builder = WindowBuilder::new()` then `builder.build(&events_loop)`.
//!
//! The first way is the simpliest way and will give you default values for everything.
//! The first way is the simplest way and will give you default values for everything.
//!
//! The second way allows you to customize the way your window will look and behave by modifying
//! the fields of the `WindowBuilder` object before you create the window.

View file

@ -17,6 +17,7 @@ use platform::x11::ffi::XVisualInfo;
pub use platform::x11;
pub use platform::XNotSupported;
pub use platform::x11::util::WindowType as XWindowType;
/// Additional methods on `EventsLoop` that are specific to Linux.
pub trait EventsLoopExt {
@ -94,6 +95,9 @@ pub trait WindowExt {
fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>>;
/// Set window urgency hint (`XUrgencyHint`). Only relevant on X.
fn set_urgent(&self, is_urgent: bool);
/// This function returns the underlying `xcb_connection_t` of an xlib `Display`.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
@ -142,6 +146,7 @@ impl WindowExt for Window {
}
}
#[inline]
fn get_xlib_screen_id(&self) -> Option<raw::c_int> {
match self.window {
LinuxWindow::X(ref w) => Some(w.get_xlib_screen_id()),
@ -149,6 +154,7 @@ impl WindowExt for Window {
}
}
#[inline]
fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>> {
match self.window {
LinuxWindow::X(ref w) => Some(w.get_xlib_xconnection()),
@ -156,6 +162,7 @@ impl WindowExt for Window {
}
}
#[inline]
fn get_xcb_connection(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::X(ref w) => Some(w.get_xcb_connection()),
@ -163,6 +170,13 @@ impl WindowExt for Window {
}
}
#[inline]
fn set_urgent(&self, is_urgent: bool) {
if let LinuxWindow::X(ref w) = self.window {
w.set_urgent(is_urgent);
}
}
#[inline]
fn get_wayland_surface(&self) -> Option<*mut raw::c_void> {
match self.window {
@ -190,6 +204,12 @@ pub trait WindowBuilderExt {
fn with_x11_visual<T>(self, visual_infos: *const T) -> WindowBuilder;
fn with_x11_screen(self, screen_id: i32) -> WindowBuilder;
/// Build window with `WM_CLASS` hint; defaults to the name of the binary. Only relevant on X11.
fn with_class(self, class: String, instance: String) -> WindowBuilder;
/// Build window with override-redirect flag; defaults to false. Only relevant on X11.
fn with_override_redirect(self, override_redirect: bool) -> WindowBuilder;
/// Build window with `_NET_WM_WINDOW_TYPE` hint; defaults to `Normal`. Only relevant on X11.
fn with_x11_window_type(self, x11_window_type: XWindowType) -> WindowBuilder;
/// Build window with resize increment hint. Only implemented on X11.
fn with_resize_increments(self, width_inc: u32, height_inc: u32) -> WindowBuilder;
/// Build window with base size hint. Only implemented on X11.
@ -211,6 +231,24 @@ impl WindowBuilderExt for WindowBuilder {
self
}
#[inline]
fn with_class(mut self, instance: String, class: String) -> WindowBuilder {
self.platform_specific.class = Some((instance, class));
self
}
#[inline]
fn with_override_redirect(mut self, override_redirect: bool) -> WindowBuilder {
self.platform_specific.override_redirect = override_redirect;
self
}
#[inline]
fn with_x11_window_type(mut self, x11_window_type: XWindowType) -> WindowBuilder {
self.platform_specific.x11_window_type = x11_window_type;
self
}
#[inline]
fn with_resize_increments(mut self, width_inc: u32, height_inc: u32) -> WindowBuilder {
self.platform_specific.resize_increments = Some((width_inc, height_inc));

View file

@ -44,6 +44,9 @@ pub struct PlatformSpecificWindowBuilderAttributes {
pub screen_id: Option<i32>,
pub resize_increments: Option<(u32, u32)>,
pub base_size: Option<(u32, u32)>,
pub class: Option<(String, String)>,
pub override_redirect: bool,
pub x11_window_type: x11::util::WindowType,
}
lazy_static!(

View file

@ -7,7 +7,7 @@ mod window;
mod xdisplay;
mod dnd;
mod ime;
mod util;
pub mod util;
pub use self::monitor::{
MonitorId,

View file

@ -18,3 +18,51 @@ impl From<bool> for StateOperation {
}
}
}
/// X window type. Maps directly to
/// [`_NET_WM_WINDOW_TYPE`](https://specifications.freedesktop.org/wm-spec/1.3/ar01s05.html).
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum WindowType {
/// A desktop feature. This can include a single window containing desktop icons with the same dimensions as the
/// screen, allowing the desktop environment to have full control of the desktop, without the need for proxying
/// root window clicks.
Desktop,
/// A dock or panel feature. Typically a Window Manager would keep such windows on top of all other windows.
Dock,
/// Toolbar windows. "Torn off" from the main application.
Toolbar,
/// Pinnable menu windows. "Torn off" from the main application.
Menu,
/// A small persistent utility window, such as a palette or toolbox.
Utility,
/// The window is a splash screen displayed as an application is starting up.
Splash,
/// This is a dialog window.
Dialog,
/// This is a normal, top-level window.
Normal,
}
impl Default for WindowType {
fn default() -> Self {
WindowType::Normal
}
}
impl WindowType {
pub(crate) fn as_atom(&self, xconn: &Arc<XConnection>) -> ffi::Atom {
use self::WindowType::*;
let atom_name: &[u8] = match self {
Desktop => b"_NET_WM_WINDOW_TYPE_DESKTOP\0",
Dock => b"_NET_WM_WINDOW_TYPE_DOCK\0",
Toolbar => b"_NET_WM_WINDOW_TYPE_TOOLBAR\0",
Menu => b"_NET_WM_WINDOW_TYPE_MENU\0",
Utility => b"_NET_WM_WINDOW_TYPE_UTILITY\0",
Splash => b"_NET_WM_WINDOW_TYPE_SPLASH\0",
Dialog => b"_NET_WM_WINDOW_TYPE_DIALOG\0",
Normal => b"_NET_WM_WINDOW_TYPE_NORMAL\0",
};
unsafe { get_atom(xconn, atom_name) }
.expect("Failed to get atom for `WindowType`")
}
}

View file

@ -1,7 +1,8 @@
use std::{cmp, mem};
use std::{cmp, env, mem};
use std::borrow::Borrow;
use std::ffi::CString;
use std::os::raw::*;
use std::path::Path;
use std::sync::Arc;
use libc;
@ -115,6 +116,10 @@ impl Window2 {
window_attributes |= ffi::CWBackPixel;
}
if pl_attribs.override_redirect {
window_attributes |= ffi::CWOverrideRedirect;
}
// finally creating the window
let window = unsafe {
(xconn.xlib.XCreateWindow)(
@ -127,12 +132,12 @@ impl Window2 {
0,
match pl_attribs.visual_infos {
Some(vi) => vi.depth,
None => ffi::CopyFromParent
None => ffi::CopyFromParent,
},
ffi::InputOutput as c_uint,
match pl_attribs.visual_infos {
Some(vi) => vi.visual,
None => ffi::CopyFromParent as *mut _
None => ffi::CopyFromParent as *mut ffi::Visual,
},
window_attributes,
&mut set_win_attr,
@ -178,17 +183,42 @@ impl Window2 {
)
}.queue();
// Set ICCCM WM_CLASS property based on initial window title
// Must be done *before* mapping the window by ICCCM 4.1.2.5
// WM_CLASS must be set *before* mapping the window, as per ICCCM!
{
let name = CString::new(window_attrs.title.as_str())
.expect("Window title contained null byte");
let mut class_hints = {
let class_hints = unsafe { (xconn.xlib.XAllocClassHint)() };
util::XSmartPointer::new(xconn, class_hints)
}.expect("XAllocClassHint returned null; out of memory");
(*class_hints).res_name = name.as_ptr() as *mut c_char;
(*class_hints).res_class = name.as_ptr() as *mut c_char;
}.expect("`XAllocClassHint` returned null; out of memory");
let (class, instance) = if let Some((instance, class)) = pl_attribs.class {
let instance = CString::new(instance.as_str())
.expect("`WM_CLASS` instance contained null byte");
let class = CString::new(class.as_str())
.expect("`WM_CLASS` class contained null byte");
(instance, class)
} else {
let class = env::args()
.next()
.as_ref()
// Default to the name of the binary (via argv[0])
.and_then(|path| Path::new(path).file_name())
.and_then(|bin_name| bin_name.to_str())
.map(|bin_name| bin_name.to_owned())
.or_else(|| Some(window_attrs.title.clone()))
.and_then(|string| CString::new(string.as_str()).ok())
.expect("Default `WM_CLASS` class contained null byte");
// This environment variable is extraordinarily unlikely to actually be used...
let instance = env::var("RESOURCE_NAME")
.ok()
.and_then(|instance| CString::new(instance.as_str()).ok())
.or_else(|| Some(class.clone()))
.expect("Default `WM_CLASS` instance contained null byte");
(instance, class)
};
(*class_hints).res_name = class.as_ptr() as *mut c_char;
(*class_hints).res_class = instance.as_ptr() as *mut c_char;
unsafe {
(xconn.xlib.XSetClassHint)(
xconn.display,
@ -198,6 +228,17 @@ impl Window2 {
}//.queue();
}
Window2::set_pid(xconn, x_window.window)
.map(|flusher| flusher.queue());
if pl_attribs.x11_window_type != Default::default() {
Window2::set_window_type(
xconn,
x_window.window,
pl_attribs.x11_window_type,
).queue();
}
// set size hints
{
let mut size_hints = {
@ -338,6 +379,96 @@ impl Window2 {
))
}
fn set_pid(xconn: &Arc<XConnection>, window: ffi::Window) -> Option<util::Flusher> {
let pid_atom = unsafe { util::get_atom(xconn, b"_NET_WM_PID\0") }
.expect("Failed to call XInternAtom (_NET_WM_PID)");
let client_machine_atom = unsafe { util::get_atom(xconn, b"WM_CLIENT_MACHINE\0") }
.expect("Failed to call XInternAtom (WM_CLIENT_MACHINE)");
unsafe {
let (hostname, hostname_length) = {
// 64 would suffice for Linux, but 256 will be enough everywhere (as per SUSv2). For instance, this is
// the limit defined by OpenBSD.
const MAXHOSTNAMELEN: usize = 256;
let mut hostname: [c_char; MAXHOSTNAMELEN] = mem::uninitialized();
let status = libc::gethostname(hostname.as_mut_ptr(), hostname.len());
if status != 0 { return None; }
hostname[MAXHOSTNAMELEN - 1] = '\0' as c_char; // a little extra safety
let hostname_length = libc::strlen(hostname.as_ptr());
(hostname, hostname_length as usize)
};
util::change_property(
xconn,
window,
pid_atom,
ffi::XA_CARDINAL,
util::Format::Long,
util::PropMode::Replace,
&[libc::getpid() as util::Cardinal],
).queue();
let flusher = util::change_property(
xconn,
window,
client_machine_atom,
ffi::XA_STRING,
util::Format::Char,
util::PropMode::Replace,
&hostname[0..hostname_length],
);
Some(flusher)
}
}
fn set_window_type(
xconn: &Arc<XConnection>,
window: ffi::Window,
window_type: util::WindowType,
) -> util::Flusher {
let hint_atom = unsafe { util::get_atom(xconn, b"_NET_WM_WINDOW_TYPE\0") }
.expect("Failed to call XInternAtom (_NET_WM_WINDOW_TYPE)");
let window_type_atom = window_type.as_atom(xconn);
unsafe {
util::change_property(
xconn,
window,
hint_atom,
ffi::XA_ATOM,
util::Format::Long,
util::PropMode::Replace,
&[window_type_atom],
)
}
}
pub fn set_urgent(&self, is_urgent: bool) {
let xconn = &self.x.display;
let mut wm_hints = {
let mut wm_hints = unsafe {
(xconn.xlib.XGetWMHints)(xconn.display, self.x.window)
};
xconn.check_errors().expect("`XGetWMHints` failed");
if wm_hints.is_null() {
wm_hints = unsafe { (xconn.xlib.XAllocWMHints)() };
}
util::XSmartPointer::new(xconn, wm_hints)
}.expect("`XAllocWMHints` returned null; out of memory");
if is_urgent {
(*wm_hints).flags |= ffi::XUrgencyHint;
} else {
(*wm_hints).flags &= !ffi::XUrgencyHint;
}
unsafe {
(xconn.xlib.XSetWMHints)(
xconn.display,
self.x.window,
wm_hints.ptr,
);
util::flush_requests(xconn).expect("Failed to set urgency hint");
}
}
fn set_netwm(
xconn: &Arc<XConnection>,
window: ffi::Window,

View file

@ -389,7 +389,7 @@ impl Window {
self.window.set_window_icon(window_icon)
}
//// Sets location of IME candidate box in client area coordinates relative to the top left.
/// Sets location of IME candidate box in client area coordinates relative to the top left.
#[inline]
pub fn set_ime_spot(&self, x: i32, y: i32) {
self.window.set_ime_spot(x, y)