diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a536e60..aaeebeaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ - On X11, fix `Window::request_redraw` not waking the event loop. - On Wayland, the keypad arrow keys are now recognized. - **Breaking** Rename `desktop::EventLoopExtDesktop` to `run_return::EventLoopExtRunReturn`. +- Added `request_user_attention` method to `Window`. +- **Breaking:** On macOS, removed `WindowExt::request_user_attention`, use `Window::request_user_attention`. +- **Breaking:** On X11, removed `WindowExt::set_urgent`, use `Window::request_user_attention`. - On Wayland, default font size in CSD increased from 11 to 17. # 0.23.0 (2020-10-02) diff --git a/src/platform/macos.rs b/src/platform/macos.rs index f6f18d44..f66076b9 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -9,26 +9,6 @@ use crate::{ window::{Window, WindowBuilder}, }; -/// Corresponds to `NSRequestUserAttentionType`. -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum RequestUserAttentionType { - /// Corresponds to `NSCriticalRequest`. - /// - /// Dock icon will bounce until the application is focused. - Critical, - - /// Corresponds to `NSInformationalRequest`. - /// - /// Dock icon will bounce once. - Informational, -} - -impl Default for RequestUserAttentionType { - fn default() -> Self { - RequestUserAttentionType::Critical - } -} - /// Additional methods on `Window` that are specific to MacOS. pub trait WindowExtMacOS { /// Returns a pointer to the cocoa `NSWindow` that is used by this window. @@ -41,10 +21,6 @@ pub trait WindowExtMacOS { /// The pointer will become invalid when the `Window` is destroyed. fn ns_view(&self) -> *mut c_void; - /// Request user attention, causing the application's dock icon to bounce. - /// Note that this has no effect if the application is already focused. - fn request_user_attention(&self, request_type: RequestUserAttentionType); - /// Returns whether or not the window is in simple fullscreen mode. fn simple_fullscreen(&self) -> bool; @@ -75,11 +51,6 @@ impl WindowExtMacOS for Window { self.window.ns_view() } - #[inline] - fn request_user_attention(&self, request_type: RequestUserAttentionType) { - self.window.request_user_attention(request_type) - } - #[inline] fn simple_fullscreen(&self) -> bool { self.window.simple_fullscreen() diff --git a/src/platform/unix.rs b/src/platform/unix.rs index 1c464f6d..8662e456 100644 --- a/src/platform/unix.rs +++ b/src/platform/unix.rs @@ -213,10 +213,6 @@ pub trait WindowExtUnix { #[cfg(feature = "x11")] fn xlib_xconnection(&self) -> Option>; - /// Set window urgency hint (`XUrgencyHint`). Only relevant on X. - #[cfg(feature = "x11")] - 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). @@ -297,16 +293,6 @@ impl WindowExtUnix for Window { } } - #[inline] - #[cfg(feature = "x11")] - fn set_urgent(&self, is_urgent: bool) { - match self.window { - LinuxWindow::X(ref w) => w.set_urgent(is_urgent), - #[cfg(feature = "wayland")] - _ => (), - } - } - #[inline] #[cfg(feature = "x11")] fn xcb_connection(&self) -> Option<*mut raw::c_void> { diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 0b4812e3..eae00d19 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -484,6 +484,8 @@ impl Window { pub fn set_ime_position(&self, _position: Position) {} + pub fn request_user_attention(&self, _request_type: Option) {} + pub fn set_cursor_icon(&self, _: window::CursorIcon) {} pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> { diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index d83b4941..90c1fc2d 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -22,7 +22,9 @@ use crate::{ }, monitor, view, EventLoopWindowTarget, MonitorHandle, }, - window::{CursorIcon, Fullscreen, WindowAttributes, WindowId as RootWindowId}, + window::{ + CursorIcon, Fullscreen, UserAttentionType, WindowAttributes, WindowId as RootWindowId, + }, }; pub struct Inner { @@ -260,6 +262,10 @@ impl Inner { warn!("`Window::set_ime_position` is ignored on iOS") } + pub fn request_user_attention(&self, _request_type: Option) { + warn!("`Window::request_user_attention` is ignored on iOS") + } + // Allow directly accessing the current monitor internally without unwrapping. fn current_monitor_inner(&self) -> RootMonitorHandle { unsafe { diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 7bc91bde..5dea52c0 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -30,7 +30,7 @@ use crate::{ event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, icon::Icon, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, - window::{CursorIcon, Fullscreen, WindowAttributes}, + window::{CursorIcon, Fullscreen, UserAttentionType, WindowAttributes}, }; pub(crate) use crate::icon::RgbaIcon as PlatformIcon; @@ -418,6 +418,16 @@ impl Window { x11_or_wayland!(match self; Window(w) => w.set_ime_position(position)) } + #[inline] + pub fn request_user_attention(&self, _request_type: Option) { + match self { + #[cfg(feature = "x11")] + &Window::X(ref w) => w.request_user_attention(_request_type), + #[cfg(feature = "wayland")] + _ => (), + } + } + #[inline] pub fn request_redraw(&self) { x11_or_wayland!(match self; Window(w) => w.request_redraw()) diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 47be20c8..0fa64cf1 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -22,7 +22,7 @@ use crate::{ MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode, }, - window::{CursorIcon, Fullscreen, Icon, WindowAttributes}, + window::{CursorIcon, Fullscreen, Icon, UserAttentionType, WindowAttributes}, }; use super::{ffi, util, EventLoopWindowTarget, ImeSender, WindowId, XConnection, XError}; @@ -523,23 +523,6 @@ impl UnownedWindow { ) } - #[inline] - pub fn set_urgent(&self, is_urgent: bool) { - let mut wm_hints = self - .xconn - .get_wm_hints(self.xwindow) - .expect("`XGetWMHints` failed"); - if is_urgent { - (*wm_hints).flags |= ffi::XUrgencyHint; - } else { - (*wm_hints).flags &= !ffi::XUrgencyHint; - } - self.xconn - .set_wm_hints(self.xwindow, wm_hints) - .flush() - .expect("Failed to set urgency hint"); - } - fn set_netwm( &self, operation: util::StateOperation, @@ -1306,6 +1289,23 @@ impl UnownedWindow { self.set_ime_position_physical(x, y); } + #[inline] + pub fn request_user_attention(&self, request_type: Option) { + let mut wm_hints = self + .xconn + .get_wm_hints(self.xwindow) + .expect("`XGetWMHints` failed"); + if request_type.is_some() { + (*wm_hints).flags |= ffi::XUrgencyHint; + } else { + (*wm_hints).flags &= !ffi::XUrgencyHint; + } + self.xconn + .set_wm_hints(self.xwindow, wm_hints) + .flush() + .expect("Failed to set urgency hint"); + } + #[inline] pub fn id(&self) -> WindowId { WindowId(self.xwindow) diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index b28c860f..7e69b53d 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -16,7 +16,7 @@ use crate::{ error::{ExternalError, NotSupportedError, OsError as RootOsError}, icon::Icon, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, - platform::macos::{ActivationPolicy, RequestUserAttentionType, WindowExtMacOS}, + platform::macos::{ActivationPolicy, WindowExtMacOS}, platform_impl::platform::{ app_state::AppState, app_state::INTERRUPT_EVENT_LOOP_EXIT, @@ -28,7 +28,9 @@ use crate::{ window_delegate::new_delegate, OsError, }, - window::{CursorIcon, Fullscreen, WindowAttributes, WindowId as RootWindowId}, + window::{ + CursorIcon, Fullscreen, UserAttentionType, WindowAttributes, WindowId as RootWindowId, + }, }; use cocoa::{ appkit::{ @@ -977,6 +979,19 @@ impl UnownedWindow { } } + #[inline] + pub fn request_user_attention(&self, request_type: Option) { + let ns_request_type = request_type.map(|ty| match ty { + UserAttentionType::Critical => NSRequestUserAttentionType::NSCriticalRequest, + UserAttentionType::Informational => NSRequestUserAttentionType::NSInformationalRequest, + }); + unsafe { + if let Some(ty) = ns_request_type { + NSApp().requestUserAttention_(ty); + } + } + } + #[inline] // Allow directly accessing the current monitor internally without unwrapping. pub(crate) fn current_monitor_inner(&self) -> RootMonitorHandle { @@ -1030,18 +1045,6 @@ impl WindowExtMacOS for UnownedWindow { *self.ns_view as *mut _ } - #[inline] - fn request_user_attention(&self, request_type: RequestUserAttentionType) { - unsafe { - NSApp().requestUserAttention_(match request_type { - RequestUserAttentionType::Critical => NSRequestUserAttentionType::NSCriticalRequest, - RequestUserAttentionType::Informational => { - NSRequestUserAttentionType::NSInformationalRequest - } - }); - } - } - #[inline] fn simple_fullscreen(&self) -> bool { let shared_state_lock = self.shared_state.lock().unwrap(); diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 4719044e..463705bf 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -3,7 +3,9 @@ use crate::error::{ExternalError, NotSupportedError, OsError as RootOE}; use crate::event; use crate::icon::Icon; use crate::monitor::MonitorHandle as RootMH; -use crate::window::{CursorIcon, Fullscreen, WindowAttributes, WindowId as RootWI}; +use crate::window::{ + CursorIcon, Fullscreen, UserAttentionType, WindowAttributes, WindowId as RootWI, +}; use raw_window_handle::web::WebHandle; @@ -268,6 +270,11 @@ impl Window { // Currently a no-op as it does not seem there is good support for this on web } + #[inline] + pub fn request_user_attention(&self, _request_type: Option) { + // Currently an intentional no-op + } + #[inline] // Allow directly accessing the current monitor internally without unwrapping. fn current_monitor_inner(&self) -> RootMH { diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index b5334750..592307b7 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -43,7 +43,7 @@ use crate::{ window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState}, PlatformSpecificWindowBuilderAttributes, WindowId, }, - window::{CursorIcon, Fullscreen, WindowAttributes}, + window::{CursorIcon, Fullscreen, UserAttentionType, WindowAttributes}, }; /// The Win32 implementation of the main `Window` object. @@ -621,6 +621,37 @@ impl Window { warn!("`Window::set_ime_position` is ignored on Windows") } + #[inline] + pub fn request_user_attention(&self, request_type: Option) { + let window = self.window.clone(); + let active_window_handle = unsafe { winuser::GetActiveWindow() }; + if window.0 == active_window_handle { + return; + } + + self.thread_executor.execute_in_thread(move || unsafe { + let (flags, count) = request_type + .map(|ty| match ty { + UserAttentionType::Critical => { + (winuser::FLASHW_ALL | winuser::FLASHW_TIMERNOFG, u32::MAX) + } + UserAttentionType::Informational => { + (winuser::FLASHW_TRAY | winuser::FLASHW_TIMERNOFG, 0) + } + }) + .unwrap_or((winuser::FLASHW_STOP, 0)); + + let mut flash_info = winuser::FLASHWINFO { + cbSize: mem::size_of::() as UINT, + hwnd: window.0, + dwFlags: flags, + uCount: count, + dwTimeout: 0, + }; + winuser::FlashWindowEx(&mut flash_info); + }); + } + #[inline] pub fn is_dark_mode(&self) -> bool { self.window_state.lock().is_dark_mode diff --git a/src/window.rs b/src/window.rs index c8f45d10..b0951bc8 100644 --- a/src/window.rs +++ b/src/window.rs @@ -682,6 +682,23 @@ impl Window { pub fn set_ime_position>(&self, position: P) { self.window.set_ime_position(position.into()) } + + /// Requests user attention to the window, this has no effect if the application + /// is already focused. How requesting for user attention manifests is platform dependent, + /// see `UserAttentionType` for details. + /// + /// Providing `None` will unset the request for user attention. Unsetting the request for + /// user attention might not be done automatically by the WM when the window receives input. + /// + /// ## Platform-specific + /// + /// - **iOS / Android / Web / Wayland:** Unsupported. + /// - **macOS:** `None` has no effect. + /// - **X11:** Requests for user attention must be manually cleared. + #[inline] + pub fn request_user_attention(&self, request_type: Option) { + self.window.request_user_attention(request_type) + } } /// Cursor functions. @@ -874,3 +891,24 @@ pub enum Theme { Light, Dark, } + +/// ## Platform-specific +/// +/// - **X11:** Sets the WM's `XUrgencyHint`. No distinction between `Critical` and `Informational`. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum UserAttentionType { + /// ## Platform-specific + /// - **macOS:** Bounces the dock icon until the application is in focus. + /// - **Windows:** Flashes both the window and the taskbar button until the application is in focus. + Critical, + /// ## Platform-specific + /// - **macOS:** Bounces the dock icon once. + /// - **Windows:** Flashes the taskbar button until the application is in focus. + Informational, +} + +impl Default for UserAttentionType { + fn default() -> Self { + UserAttentionType::Informational + } +}