From b371b406d5fea019c074a3b6b68a765e3f2d9c99 Mon Sep 17 00:00:00 2001 From: Max de Danschutter <43446207+maxded@users.noreply.github.com> Date: Wed, 19 May 2021 18:39:53 +0200 Subject: [PATCH] Implemented `focus_window` (#1944) --- CHANGELOG.md | 4 +++ src/platform_impl/android/mod.rs | 2 ++ src/platform_impl/ios/window.rs | 4 +++ src/platform_impl/linux/mod.rs | 8 ++++++ src/platform_impl/linux/x11/ffi.rs | 5 ++++ src/platform_impl/linux/x11/window.rs | 35 +++++++++++++++++++++++++++ src/platform_impl/macos/window.rs | 15 ++++++++++++ src/platform_impl/web/window.rs | 5 ++++ src/platform_impl/windows/window.rs | 14 +++++++++++ src/window.rs | 15 ++++++++++++ 10 files changed, 107 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d88ee086..49f6d595 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# Unreleased + +- Added `Window::focus_window`to bring the window to the front and set input focus. + # 0.25.0 (2021-05-15) - **Breaking:** On macOS, replace `WindowBuilderExtMacOS::with_activation_policy` with `EventLoopExtMacOS::set_activation_policy` diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 4dc3bb4a..de69bc92 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -539,6 +539,8 @@ impl Window { pub fn set_ime_position(&self, _position: Position) {} + pub fn focus_window(&self) {} + pub fn request_user_attention(&self, _request_type: Option) {} pub fn set_cursor_icon(&self, _: window::CursorIcon) {} diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 1f6e5591..b6d32ffd 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -271,6 +271,10 @@ impl Inner { warn!("`Window::set_ime_position` is ignored on iOS") } + pub fn focus_window(&self) { + warn!("`Window::set_focus` is ignored on iOS") + } + pub fn request_user_attention(&self, _request_type: Option) { warn!("`Window::request_user_attention` is ignored on iOS") } diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 3bf2b047..2953271d 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -430,6 +430,14 @@ impl Window { } #[inline] + pub fn focus_window(&self) { + match self { + #[cfg(feature = "x11")] + &Window::X(ref w) => w.focus_window(), + #[cfg(feature = "wayland")] + _ => (), + } + } pub fn request_user_attention(&self, _request_type: Option) { match self { #[cfg(feature = "x11")] diff --git a/src/platform_impl/linux/x11/ffi.rs b/src/platform_impl/linux/x11/ffi.rs index 6d7c2089..8027e569 100644 --- a/src/platform_impl/linux/x11/ffi.rs +++ b/src/platform_impl/linux/x11/ffi.rs @@ -1,4 +1,9 @@ +use x11_dl::xmd::CARD32; pub use x11_dl::{ error::OpenError, keysym::*, xcursor::*, xinput::*, xinput2::*, xlib::*, xlib_xcb::*, xrandr::*, xrender::*, }; + +// Isn't defined by x11_dl +#[allow(non_upper_case_globals)] +pub const IconicState: CARD32 = 3; diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 26d3b5dd..1e5a32a2 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -1340,6 +1340,41 @@ impl UnownedWindow { self.set_ime_position_physical(x, y); } + #[inline] + pub fn focus_window(&self) { + let state_atom = unsafe { self.xconn.get_atom_unchecked(b"WM_STATE\0") }; + let state_type_atom = unsafe { self.xconn.get_atom_unchecked(b"CARD32\0") }; + let is_minimized = if let Ok(state) = + self.xconn + .get_property(self.xwindow, state_atom, state_type_atom) + { + state.contains(&(ffi::IconicState as c_ulong)) + } else { + false + }; + let is_visible = match self.shared_state.lock().visibility { + Visibility::Yes => true, + Visibility::YesWait | Visibility::No => false, + }; + + if is_visible && !is_minimized { + let atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_ACTIVE_WINDOW\0") }; + let flusher = self.xconn.send_client_msg( + self.xwindow, + self.root, + atom, + Some(ffi::SubstructureRedirectMask | ffi::SubstructureNotifyMask), + [1, ffi::CurrentTime as c_long, 0, 0, 0], + ); + if let Err(e) = flusher.flush() { + log::error!( + "`flush` returned an error when focusing the window. Error was: {}", + e + ); + } + } + } + #[inline] pub fn request_user_attention(&self, request_type: Option) { let mut wm_hints = self diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 50c11fe8..51a37a33 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -970,6 +970,21 @@ impl UnownedWindow { } } + #[inline] + pub fn focus_window(&self) { + let is_minimized: BOOL = unsafe { msg_send![*self.ns_window, isMiniaturized] }; + let is_minimized = is_minimized == YES; + let is_visible: BOOL = unsafe { msg_send![*self.ns_window, isVisible] }; + let is_visible = is_visible == YES; + + if !is_minimized && is_visible { + unsafe { + NSApp().activateIgnoringOtherApps_(YES); + util::make_key_and_order_front_async(*self.ns_window); + } + } + } + #[inline] pub fn request_user_attention(&self, request_type: Option) { let ns_request_type = request_type.map(|ty| match ty { diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index a51210ca..9e1b8425 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -281,6 +281,11 @@ impl Window { // Currently a no-op as it does not seem there is good support for this on web } + #[inline] + pub fn focus_window(&self) { + // 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 diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 2e5e8e57..081009dc 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -685,6 +685,20 @@ impl Window { pub fn theme(&self) -> Theme { self.window_state.lock().current_theme } + + #[inline] + pub fn focus_window(&self) { + let window = self.window.clone(); + let window_flags = self.window_state.lock().window_flags(); + + let is_visible = window_flags.contains(WindowFlags::VISIBLE); + let is_minimized = window_flags.contains(WindowFlags::MINIMIZED); + let is_foreground = window.0 == unsafe { winuser::GetForegroundWindow() }; + + if is_visible && !is_minimized && !is_foreground { + unsafe { force_window_active(window.0) }; + } + } } impl Drop for Window { diff --git a/src/window.rs b/src/window.rs index 079c7112..4c649c42 100644 --- a/src/window.rs +++ b/src/window.rs @@ -731,6 +731,21 @@ impl Window { self.window.set_ime_position(position.into()) } + /// Brings the window to the front and sets input focus. Has no effect if the window is + /// already in focus, minimized, or not visible. + /// + /// This method steals input focus from other applications. Do not use this method unless + /// you are certain that's what the user wants. Focus stealing can cause an extremely disruptive + /// user experience. + /// + /// ## Platform-specific + /// + /// - **iOS / Android / Web / Wayland:** Unsupported. + #[inline] + pub fn focus_window(&self) { + self.window.focus_window() + } + /// 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.