Add 'request_user_attention' to Window

This commit introduces a cross platform way to request a user attention
to the window via a 'request_user_attention' method on a Window struct.
This method is inspired by macOS's 'request_user_attention' method and
thus reuses its signature and semantics to some extent.
This commit is contained in:
Max de Danschutter 2020-11-27 03:03:08 +01:00 committed by GitHub
parent f79efec7ef
commit 0861a353d6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 136 additions and 79 deletions

View file

@ -13,6 +13,9 @@
- On X11, fix `Window::request_redraw` not waking the event loop. - On X11, fix `Window::request_redraw` not waking the event loop.
- On Wayland, the keypad arrow keys are now recognized. - On Wayland, the keypad arrow keys are now recognized.
- **Breaking** Rename `desktop::EventLoopExtDesktop` to `run_return::EventLoopExtRunReturn`. - **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. - On Wayland, default font size in CSD increased from 11 to 17.
# 0.23.0 (2020-10-02) # 0.23.0 (2020-10-02)

View file

@ -9,26 +9,6 @@ use crate::{
window::{Window, WindowBuilder}, 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. /// Additional methods on `Window` that are specific to MacOS.
pub trait WindowExtMacOS { pub trait WindowExtMacOS {
/// Returns a pointer to the cocoa `NSWindow` that is used by this window. /// 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. /// The pointer will become invalid when the `Window` is destroyed.
fn ns_view(&self) -> *mut c_void; 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. /// Returns whether or not the window is in simple fullscreen mode.
fn simple_fullscreen(&self) -> bool; fn simple_fullscreen(&self) -> bool;
@ -75,11 +51,6 @@ impl WindowExtMacOS for Window {
self.window.ns_view() self.window.ns_view()
} }
#[inline]
fn request_user_attention(&self, request_type: RequestUserAttentionType) {
self.window.request_user_attention(request_type)
}
#[inline] #[inline]
fn simple_fullscreen(&self) -> bool { fn simple_fullscreen(&self) -> bool {
self.window.simple_fullscreen() self.window.simple_fullscreen()

View file

@ -213,10 +213,6 @@ pub trait WindowExtUnix {
#[cfg(feature = "x11")] #[cfg(feature = "x11")]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>>; fn xlib_xconnection(&self) -> Option<Arc<XConnection>>;
/// 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`. /// 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). /// 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] #[inline]
#[cfg(feature = "x11")] #[cfg(feature = "x11")]
fn xcb_connection(&self) -> Option<*mut raw::c_void> { fn xcb_connection(&self) -> Option<*mut raw::c_void> {

View file

@ -484,6 +484,8 @@ impl Window {
pub fn set_ime_position(&self, _position: Position) {} pub fn set_ime_position(&self, _position: Position) {}
pub fn request_user_attention(&self, _request_type: Option<window::UserAttentionType>) {}
pub fn set_cursor_icon(&self, _: window::CursorIcon) {} pub fn set_cursor_icon(&self, _: window::CursorIcon) {}
pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> { pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> {

View file

@ -22,7 +22,9 @@ use crate::{
}, },
monitor, view, EventLoopWindowTarget, MonitorHandle, monitor, view, EventLoopWindowTarget, MonitorHandle,
}, },
window::{CursorIcon, Fullscreen, WindowAttributes, WindowId as RootWindowId}, window::{
CursorIcon, Fullscreen, UserAttentionType, WindowAttributes, WindowId as RootWindowId,
},
}; };
pub struct Inner { pub struct Inner {
@ -260,6 +262,10 @@ impl Inner {
warn!("`Window::set_ime_position` is ignored on iOS") warn!("`Window::set_ime_position` is ignored on iOS")
} }
pub fn request_user_attention(&self, _request_type: Option<UserAttentionType>) {
warn!("`Window::request_user_attention` is ignored on iOS")
}
// Allow directly accessing the current monitor internally without unwrapping. // Allow directly accessing the current monitor internally without unwrapping.
fn current_monitor_inner(&self) -> RootMonitorHandle { fn current_monitor_inner(&self) -> RootMonitorHandle {
unsafe { unsafe {

View file

@ -30,7 +30,7 @@ use crate::{
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
icon::Icon, icon::Icon,
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
window::{CursorIcon, Fullscreen, WindowAttributes}, window::{CursorIcon, Fullscreen, UserAttentionType, WindowAttributes},
}; };
pub(crate) use crate::icon::RgbaIcon as PlatformIcon; 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)) x11_or_wayland!(match self; Window(w) => w.set_ime_position(position))
} }
#[inline]
pub fn request_user_attention(&self, _request_type: Option<UserAttentionType>) {
match self {
#[cfg(feature = "x11")]
&Window::X(ref w) => w.request_user_attention(_request_type),
#[cfg(feature = "wayland")]
_ => (),
}
}
#[inline] #[inline]
pub fn request_redraw(&self) { pub fn request_redraw(&self) {
x11_or_wayland!(match self; Window(w) => w.request_redraw()) x11_or_wayland!(match self; Window(w) => w.request_redraw())

View file

@ -22,7 +22,7 @@ use crate::{
MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes, MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes,
VideoMode as PlatformVideoMode, VideoMode as PlatformVideoMode,
}, },
window::{CursorIcon, Fullscreen, Icon, WindowAttributes}, window::{CursorIcon, Fullscreen, Icon, UserAttentionType, WindowAttributes},
}; };
use super::{ffi, util, EventLoopWindowTarget, ImeSender, WindowId, XConnection, XError}; 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( fn set_netwm(
&self, &self,
operation: util::StateOperation, operation: util::StateOperation,
@ -1306,6 +1289,23 @@ impl UnownedWindow {
self.set_ime_position_physical(x, y); self.set_ime_position_physical(x, y);
} }
#[inline]
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
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] #[inline]
pub fn id(&self) -> WindowId { pub fn id(&self) -> WindowId {
WindowId(self.xwindow) WindowId(self.xwindow)

View file

@ -16,7 +16,7 @@ use crate::{
error::{ExternalError, NotSupportedError, OsError as RootOsError}, error::{ExternalError, NotSupportedError, OsError as RootOsError},
icon::Icon, icon::Icon,
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
platform::macos::{ActivationPolicy, RequestUserAttentionType, WindowExtMacOS}, platform::macos::{ActivationPolicy, WindowExtMacOS},
platform_impl::platform::{ platform_impl::platform::{
app_state::AppState, app_state::AppState,
app_state::INTERRUPT_EVENT_LOOP_EXIT, app_state::INTERRUPT_EVENT_LOOP_EXIT,
@ -28,7 +28,9 @@ use crate::{
window_delegate::new_delegate, window_delegate::new_delegate,
OsError, OsError,
}, },
window::{CursorIcon, Fullscreen, WindowAttributes, WindowId as RootWindowId}, window::{
CursorIcon, Fullscreen, UserAttentionType, WindowAttributes, WindowId as RootWindowId,
},
}; };
use cocoa::{ use cocoa::{
appkit::{ appkit::{
@ -977,6 +979,19 @@ impl UnownedWindow {
} }
} }
#[inline]
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
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] #[inline]
// Allow directly accessing the current monitor internally without unwrapping. // Allow directly accessing the current monitor internally without unwrapping.
pub(crate) fn current_monitor_inner(&self) -> RootMonitorHandle { pub(crate) fn current_monitor_inner(&self) -> RootMonitorHandle {
@ -1030,18 +1045,6 @@ impl WindowExtMacOS for UnownedWindow {
*self.ns_view as *mut _ *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] #[inline]
fn simple_fullscreen(&self) -> bool { fn simple_fullscreen(&self) -> bool {
let shared_state_lock = self.shared_state.lock().unwrap(); let shared_state_lock = self.shared_state.lock().unwrap();

View file

@ -3,7 +3,9 @@ use crate::error::{ExternalError, NotSupportedError, OsError as RootOE};
use crate::event; use crate::event;
use crate::icon::Icon; use crate::icon::Icon;
use crate::monitor::MonitorHandle as RootMH; 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; 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 // 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<UserAttentionType>) {
// Currently an intentional no-op
}
#[inline] #[inline]
// Allow directly accessing the current monitor internally without unwrapping. // Allow directly accessing the current monitor internally without unwrapping.
fn current_monitor_inner(&self) -> RootMH { fn current_monitor_inner(&self) -> RootMH {

View file

@ -43,7 +43,7 @@ use crate::{
window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState}, window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState},
PlatformSpecificWindowBuilderAttributes, WindowId, PlatformSpecificWindowBuilderAttributes, WindowId,
}, },
window::{CursorIcon, Fullscreen, WindowAttributes}, window::{CursorIcon, Fullscreen, UserAttentionType, WindowAttributes},
}; };
/// The Win32 implementation of the main `Window` object. /// The Win32 implementation of the main `Window` object.
@ -621,6 +621,37 @@ impl Window {
warn!("`Window::set_ime_position` is ignored on Windows") warn!("`Window::set_ime_position` is ignored on Windows")
} }
#[inline]
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
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::<winuser::FLASHWINFO>() as UINT,
hwnd: window.0,
dwFlags: flags,
uCount: count,
dwTimeout: 0,
};
winuser::FlashWindowEx(&mut flash_info);
});
}
#[inline] #[inline]
pub fn is_dark_mode(&self) -> bool { pub fn is_dark_mode(&self) -> bool {
self.window_state.lock().is_dark_mode self.window_state.lock().is_dark_mode

View file

@ -682,6 +682,23 @@ impl Window {
pub fn set_ime_position<P: Into<Position>>(&self, position: P) { pub fn set_ime_position<P: Into<Position>>(&self, position: P) {
self.window.set_ime_position(position.into()) 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<UserAttentionType>) {
self.window.request_user_attention(request_type)
}
} }
/// Cursor functions. /// Cursor functions.
@ -874,3 +891,24 @@ pub enum Theme {
Light, Light,
Dark, 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
}
}