From f51f7c0ca844322cddfea53122c3d31023ccfcd8 Mon Sep 17 00:00:00 2001 From: Francesca Frangipane Date: Sun, 20 May 2018 10:24:05 -0400 Subject: [PATCH] Add option to make window "always on top" (#528) * macOS: always_on_top * Windows: always_on_top * X11: always_on_top * Stub set_always_on_top on other platforms --- CHANGELOG.md | 1 + src/lib.rs | 6 +++++ src/platform/android/mod.rs | 5 +++++ src/platform/emscripten/mod.rs | 5 +++++ src/platform/ios/mod.rs | 5 +++++ src/platform/linux/mod.rs | 8 +++++++ src/platform/linux/x11/window.rs | 32 +++++++++++++++++++++++++-- src/platform/macos/ffi.rs | 35 ++++++++++++++++++++++++++++- src/platform/macos/window.rs | 17 ++++++++++++++ src/platform/windows/window.rs | 38 +++++++++++++++++++++++++++++++- src/window.rs | 13 +++++++++++ 11 files changed, 161 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f85d97c..34b12764 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - On Windows, alt-tabbing while the cursor is grabbed no longer makes it impossible to re-grab the window. - 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. # Version 0.14.0 (2018-05-09) diff --git a/src/lib.rs b/src/lib.rs index 16b3fc7d..7f458e14 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -441,6 +441,11 @@ pub struct WindowAttributes { /// The default is `true`. pub decorations: bool, + /// Whether the window should always be on top of other windows. + /// + /// The default is `false`. + pub always_on_top: bool, + /// The window icon. /// /// The default is `None`. @@ -464,6 +469,7 @@ impl Default for WindowAttributes { visible: true, transparent: false, decorations: true, + always_on_top: false, window_icon: None, multitouch: false, } diff --git a/src/platform/android/mod.rs b/src/platform/android/mod.rs index 2ff43420..89ab7402 100644 --- a/src/platform/android/mod.rs +++ b/src/platform/android/mod.rs @@ -323,6 +323,11 @@ impl Window { // N/A } + #[inline] + pub fn set_always_on_top(&self, _always_on_top: bool) { + // N/A + } + #[inline] pub fn set_window_icon(&self, _icon: Option<::Icon>) { // N/A diff --git a/src/platform/emscripten/mod.rs b/src/platform/emscripten/mod.rs index 61db9f26..b54ae1a2 100644 --- a/src/platform/emscripten/mod.rs +++ b/src/platform/emscripten/mod.rs @@ -543,6 +543,11 @@ impl Window { // N/A } + #[inline] + pub fn set_always_on_top(&self, _always_on_top: bool) { + // N/A + } + #[inline] pub fn set_window_icon(&self, _icon: Option<::Icon>) { // N/A diff --git a/src/platform/ios/mod.rs b/src/platform/ios/mod.rs index 5c82bf38..5a3645c0 100644 --- a/src/platform/ios/mod.rs +++ b/src/platform/ios/mod.rs @@ -370,6 +370,11 @@ impl Window { // N/A } + #[inline] + pub fn set_always_on_top(&self, _always_on_top: bool) { + // N/A + } + #[inline] pub fn set_window_icon(&self, _icon: Option<::Icon>) { // N/A diff --git a/src/platform/linux/mod.rs b/src/platform/linux/mod.rs index 46ff806e..7af04880 100644 --- a/src/platform/linux/mod.rs +++ b/src/platform/linux/mod.rs @@ -302,6 +302,14 @@ impl Window { } } + #[inline] + pub fn set_always_on_top(&self, always_on_top: bool) { + match self { + &Window::X(ref w) => w.set_always_on_top(always_on_top), + &Window::Wayland(_) => (), + } + } + #[inline] pub fn set_window_icon(&self, window_icon: Option) { match self { diff --git a/src/platform/linux/x11/window.rs b/src/platform/linux/x11/window.rs index 1e08afb2..5c95c5bb 100644 --- a/src/platform/linux/x11/window.rs +++ b/src/platform/linux/x11/window.rs @@ -299,8 +299,15 @@ impl Window2 { }.queue(); // These properties must be set after mapping - window.set_maximized_inner(window_attrs.maximized).queue(); - window.set_fullscreen_inner(window_attrs.fullscreen.clone()).queue(); + if window_attrs.maximized { + window.set_maximized_inner(window_attrs.maximized).queue(); + } + if window_attrs.fullscreen.is_some() { + window.set_fullscreen_inner(window_attrs.fullscreen.clone()).queue(); + } + if window_attrs.always_on_top { + window.set_always_on_top_inner(window_attrs.always_on_top).queue(); + } if window_attrs.visible { unsafe { @@ -498,6 +505,27 @@ impl Window2 { self.invalidate_cached_frame_extents(); } + fn set_always_on_top_inner(&self, always_on_top: bool) -> util::Flusher { + let xconn = &self.x.display; + + let above_atom = unsafe { util::get_atom(xconn, b"_NET_WM_STATE_ABOVE\0") } + .expect("Failed to call XInternAtom (_NET_WM_STATE_ABOVE)"); + + Window2::set_netwm( + xconn, + self.x.window, + self.x.root, + (above_atom as c_long, 0, 0, 0), + always_on_top.into(), + ) + } + + pub fn set_always_on_top(&self, always_on_top: bool) { + self.set_always_on_top_inner(always_on_top) + .flush() + .expect("Failed to set always-on-top state"); + } + fn set_icon_inner(&self, icon: Icon) -> util::Flusher { let xconn = &self.x.display; diff --git a/src/platform/macos/ffi.rs b/src/platform/macos/ffi.rs index 988d1fd3..c3f14ca7 100644 --- a/src/platform/macos/ffi.rs +++ b/src/platform/macos/ffi.rs @@ -1,6 +1,6 @@ // TODO: Upstream these -#![allow(non_snake_case, non_upper_case_globals)] +#![allow(dead_code, non_snake_case, non_upper_case_globals)] use cocoa::base::{class, id}; use cocoa::foundation::{NSInteger, NSUInteger}; @@ -72,3 +72,36 @@ impl NSMutableAttributedString for id { msg_send![self, length] } } + +pub const kCGBaseWindowLevelKey: NSInteger = 0; +pub const kCGMinimumWindowLevelKey: NSInteger = 1; +pub const kCGDesktopWindowLevelKey: NSInteger = 2; +pub const kCGBackstopMenuLevelKey: NSInteger = 3; +pub const kCGNormalWindowLevelKey: NSInteger = 4; +pub const kCGFloatingWindowLevelKey: NSInteger = 5; +pub const kCGTornOffMenuWindowLevelKey: NSInteger = 6; +pub const kCGDockWindowLevelKey: NSInteger = 7; +pub const kCGMainMenuWindowLevelKey: NSInteger = 8; +pub const kCGStatusWindowLevelKey: NSInteger = 9; +pub const kCGModalPanelWindowLevelKey: NSInteger = 10; +pub const kCGPopUpMenuWindowLevelKey: NSInteger = 11; +pub const kCGDraggingWindowLevelKey: NSInteger = 12; +pub const kCGScreenSaverWindowLevelKey: NSInteger = 13; +pub const kCGMaximumWindowLevelKey: NSInteger = 14; +pub const kCGOverlayWindowLevelKey: NSInteger = 15; +pub const kCGHelpWindowLevelKey: NSInteger = 16; +pub const kCGUtilityWindowLevelKey: NSInteger = 17; +pub const kCGDesktopIconWindowLevelKey: NSInteger = 18; +pub const kCGCursorWindowLevelKey: NSInteger = 19; +pub const kCGNumberOfWindowLevelKeys: NSInteger = 20; + +pub enum NSWindowLevel { + NSNormalWindowLevel = kCGBaseWindowLevelKey as _, + NSFloatingWindowLevel = kCGFloatingWindowLevelKey as _, + NSTornOffMenuWindowLevel = kCGTornOffMenuWindowLevelKey as _, + NSModalPanelWindowLevel = kCGModalPanelWindowLevelKey as _, + NSMainMenuWindowLevel = kCGMainMenuWindowLevelKey as _, + NSStatusWindowLevel = kCGStatusWindowLevelKey as _, + NSPopUpMenuWindowLevel = kCGPopUpMenuWindowLevelKey as _, + NSScreenSaverWindowLevel = kCGScreenSaverWindowLevelKey as _, +} diff --git a/src/platform/macos/window.rs b/src/platform/macos/window.rs index e0c134d1..0828364e 100644 --- a/src/platform/macos/window.rs +++ b/src/platform/macos/window.rs @@ -25,6 +25,7 @@ use std::sync::Weak; use std::cell::{Cell, RefCell}; use super::events_loop::{EventsLoop, Shared}; +use platform::platform::ffi; use platform::platform::util; use platform::platform::view::{new_view, set_ime_spot}; @@ -774,6 +775,10 @@ impl Window2 { if pl_attrs.movable_by_window_background { window.setMovableByWindowBackground_(YES); } + + if attrs.always_on_top { + let _: () = msg_send![*window, setLevel:ffi::NSWindowLevel::NSFloatingWindowLevel]; + } if let Some((x, y)) = pl_attrs.resize_increments { if x >= 1 && y >= 1 { @@ -1066,6 +1071,18 @@ impl Window2 { } } + #[inline] + pub fn set_always_on_top(&self, always_on_top: bool) { + unsafe { + let level = if always_on_top { + ffi::NSWindowLevel::NSFloatingWindowLevel + } else { + ffi::NSWindowLevel::NSNormalWindowLevel + }; + let _: () = msg_send![*self.window, setLevel:level]; + } + } + #[inline] pub fn set_window_icon(&self, _icon: Option<::Icon>) { // macOS doesn't have window icons. Though, there is `setRepresentedFilename`, but that's diff --git a/src/platform/windows/window.rs b/src/platform/windows/window.rs index 3bc73e27..93fce9fe 100644 --- a/src/platform/windows/window.rs +++ b/src/platform/windows/window.rs @@ -650,6 +650,38 @@ impl Window { } } + #[inline] + pub fn set_always_on_top(&self, always_on_top: bool) { + if let Ok(mut window_state) = self.window_state.lock() { + if window_state.attributes.always_on_top == always_on_top { + return; + } + + let window = self.window.clone(); + self.events_loop_proxy.execute_in_thread(move |_| { + let insert_after = if always_on_top { + winuser::HWND_TOPMOST + } else { + winuser::HWND_NOTOPMOST + }; + unsafe { + winuser::SetWindowPos( + window.0, + insert_after, + 0, + 0, + 0, + 0, + winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOMOVE | winuser::SWP_NOSIZE, + ); + winuser::UpdateWindow(window.0); + } + }); + + window_state.attributes.always_on_top = always_on_top; + } + } + #[inline] pub fn get_current_monitor(&self) -> RootMonitorId { RootMonitorId { @@ -765,7 +797,7 @@ unsafe fn init( }; // computing the style and extended style of the window - let (ex_style, style) = if !window.decorations { + let (mut ex_style, style) = if !window.decorations { (winuser::WS_EX_APPWINDOW, //winapi::WS_POPUP is incompatible with winapi::WS_CHILD if pl_attribs.parent.is_some() { @@ -780,6 +812,10 @@ unsafe fn init( winuser::WS_OVERLAPPEDWINDOW | winuser::WS_CLIPSIBLINGS | winuser::WS_CLIPCHILDREN) }; + if window.always_on_top { + ex_style |= winuser::WS_EX_TOPMOST; + } + // adjusting the window coordinates using the style winuser::AdjustWindowRectEx(&mut rect, style, 0, ex_style); diff --git a/src/window.rs b/src/window.rs index 7491b478..6dcddd0f 100644 --- a/src/window.rs +++ b/src/window.rs @@ -92,6 +92,13 @@ impl WindowBuilder { self } + /// Sets whether or not the window will always be on top of other windows. + #[inline] + pub fn with_always_on_top(mut self, always_on_top: bool) -> WindowBuilder { + self.window.always_on_top = always_on_top; + self + } + /// Sets the window icon. On Windows and X11, this is typically the small icon in the top-left /// corner of the titlebar. /// @@ -363,6 +370,12 @@ impl Window { self.window.set_decorations(decorations) } + /// Change whether or not the window will always be on top of other windows. + #[inline] + pub fn set_always_on_top(&self, always_on_top: bool) { + self.window.set_always_on_top(always_on_top) + } + /// Sets the window icon. On Windows and X11, this is typically the small icon in the top-left /// corner of the titlebar. ///