diff --git a/CHANGELOG.md b/CHANGELOG.md index 34b12764..ddf06f31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/src/lib.rs b/src/lib.rs index 7f458e14..5b489e0c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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. diff --git a/src/os/unix.rs b/src/os/unix.rs index 774c5ca8..95ef8b46 100644 --- a/src/os/unix.rs +++ b/src/os/unix.rs @@ -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>; + /// 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 { 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> { 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(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)); diff --git a/src/platform/linux/mod.rs b/src/platform/linux/mod.rs index 7af04880..69b3045b 100644 --- a/src/platform/linux/mod.rs +++ b/src/platform/linux/mod.rs @@ -44,6 +44,9 @@ pub struct PlatformSpecificWindowBuilderAttributes { pub screen_id: Option, 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!( diff --git a/src/platform/linux/x11/mod.rs b/src/platform/linux/x11/mod.rs index e27c1e79..1c362a8d 100644 --- a/src/platform/linux/x11/mod.rs +++ b/src/platform/linux/x11/mod.rs @@ -7,7 +7,7 @@ mod window; mod xdisplay; mod dnd; mod ime; -mod util; +pub mod util; pub use self::monitor::{ MonitorId, diff --git a/src/platform/linux/x11/util/hint.rs b/src/platform/linux/x11/util/hint.rs index f377a1ab..25a49e0f 100644 --- a/src/platform/linux/x11/util/hint.rs +++ b/src/platform/linux/x11/util/hint.rs @@ -18,3 +18,51 @@ impl From 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) -> 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`") + } +} diff --git a/src/platform/linux/x11/window.rs b/src/platform/linux/x11/window.rs index 5c95c5bb..a7cd6170 100644 --- a/src/platform/linux/x11/window.rs +++ b/src/platform/linux/x11/window.rs @@ -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, window: ffi::Window) -> Option { + 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, + 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, window: ffi::Window, diff --git a/src/window.rs b/src/window.rs index 6dcddd0f..cae1f239 100644 --- a/src/window.rs +++ b/src/window.rs @@ -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)