From a88d2e079d6d521b51c8c20244a29851c142ed36 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Tue, 17 Jan 2023 03:30:14 +0200 Subject: [PATCH] On Windows and MacOS, add `Window::has_focus` --- CHANGELOG.md | 1 + src/platform_impl/android/mod.rs | 11 ++++++++++- src/platform_impl/ios/uikit/window.rs | 3 +++ src/platform_impl/ios/window.rs | 4 ++++ src/platform_impl/linux/mod.rs | 4 ++++ .../linux/wayland/seat/keyboard/handlers.rs | 8 ++++++++ src/platform_impl/linux/wayland/window/mod.rs | 10 ++++++++++ src/platform_impl/linux/wayland/window/shim.rs | 6 ++++++ src/platform_impl/linux/x11/event_processor.rs | 8 ++++++++ src/platform_impl/linux/x11/window.rs | 6 ++++++ src/platform_impl/macos/appkit/window.rs | 3 +++ src/platform_impl/macos/window.rs | 4 ++++ src/platform_impl/orbital/window.rs | 5 +++++ src/platform_impl/web/event_loop/window_target.rs | 7 +++++++ src/platform_impl/web/window.rs | 9 ++++++++- src/platform_impl/windows/window.rs | 5 +++++ src/window.rs | 10 ++++++++++ 17 files changed, 102 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 203b7000..67a2873d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ And please only add new entries to the top of this list, right below the `# Unre # Unreleased +- Add `Window::has_focus`. - On Windows, fix `Window::set_minimized(false)` not working for windows minimized by `Win + D` hotkey. - **Breaking:** On Web, touch input no longer fires `WindowEvent::Cursor*`, `WindowEvent::MouseInput`, or `DeviceEvent::MouseMotion` like other platforms, but instead it fires `WindowEvent::Touch`. - **Breaking:** Removed platform specific `WindowBuilder::with_parent` API in favor of `WindowBuilder::with_parent_window`. diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 78dc14fd..9c2c7606 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -5,7 +5,7 @@ use std::{ hash::Hash, sync::{ atomic::{AtomicBool, Ordering}, - mpsc, Arc, + mpsc, Arc, RwLock, }, time::{Duration, Instant}, }; @@ -14,6 +14,7 @@ use android_activity::input::{InputEvent, KeyAction, Keycode, MotionAction}; use android_activity::{ AndroidApp, AndroidAppWaker, ConfigurationRef, InputStatus, MainEvent, Rect, }; +use once_cell::sync::Lazy; use raw_window_handle::{ AndroidDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, }; @@ -27,6 +28,8 @@ use crate::{ window::{self, CursorGrabMode, ResizeDirection, Theme, WindowButtons, WindowLevel}, }; +static HAS_FOCUS: Lazy> = Lazy::new(|| RwLock::new(true)); + fn ndk_keycode_to_virtualkeycode(keycode: Keycode) -> Option { match keycode { Keycode::A => Some(VirtualKeyCode::A), @@ -394,6 +397,7 @@ impl EventLoop { warn!("TODO: find a way to notify application of content rect change"); } MainEvent::GainedFocus => { + *HAS_FOCUS.write().unwrap() = true; sticky_exit_callback( event::Event::WindowEvent { window_id: window::WindowId(WindowId), @@ -405,6 +409,7 @@ impl EventLoop { ); } MainEvent::LostFocus => { + *HAS_FOCUS.write().unwrap() = false; sticky_exit_callback( event::Event::WindowEvent { window_id: window::WindowId(WindowId), @@ -1064,6 +1069,10 @@ impl Window { None } + pub fn has_focus(&self) -> bool { + *HAS_FOCUS.read().unwrap() + } + pub fn title(&self) -> String { String::new() } diff --git a/src/platform_impl/ios/uikit/window.rs b/src/platform_impl/ios/uikit/window.rs index 190cf3c1..ed78e56f 100644 --- a/src/platform_impl/ios/uikit/window.rs +++ b/src/platform_impl/ios/uikit/window.rs @@ -28,5 +28,8 @@ extern_methods!( #[sel(makeKeyAndVisible)] pub fn makeKeyAndVisible(&self); + + #[sel(isKeyWindow)] + pub fn isKeyWindow(&self) -> bool; } ); diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index b4ed3dcd..1f0061b2 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -348,6 +348,10 @@ impl Inner { None } + pub fn has_focus(&self) -> bool { + self.window.isKeyWindow() + } + #[inline] pub fn set_theme(&self, _theme: Option) { warn!("`Window::set_theme` is ignored on iOS"); diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index cd3863e9..8e9d2edf 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -605,6 +605,10 @@ impl Window { } #[inline] + pub fn has_focus(&self) -> bool { + x11_or_wayland!(match self; Window(window) => window.has_focus()) + } + pub fn title(&self) -> String { x11_or_wayland!(match self; Window(window) => window.title()) } diff --git a/src/platform_impl/linux/wayland/seat/keyboard/handlers.rs b/src/platform_impl/linux/wayland/seat/keyboard/handlers.rs index 7c320973..f12704af 100644 --- a/src/platform_impl/linux/wayland/seat/keyboard/handlers.rs +++ b/src/platform_impl/linux/wayland/seat/keyboard/handlers.rs @@ -1,5 +1,7 @@ //! Handling of various keyboard events. +use std::sync::atomic::Ordering; + use sctk::reexports::client::protocol::wl_keyboard::KeyState; use sctk::seat::keyboard::Event as KeyboardEvent; @@ -22,6 +24,9 @@ pub(super) fn handle_keyboard( KeyboardEvent::Enter { surface, .. } => { let window_id = wayland::make_wid(&surface); + let window_handle = winit_state.window_map.get_mut(&window_id).unwrap(); + window_handle.has_focus.store(true, Ordering::Relaxed); + // Window gained focus. event_sink.push_window_event(WindowEvent::Focused(true), window_id); @@ -44,6 +49,9 @@ pub(super) fn handle_keyboard( ); } + let window_handle = winit_state.window_map.get_mut(&window_id).unwrap(); + window_handle.has_focus.store(false, Ordering::Relaxed); + // Window lost focus. event_sink.push_window_event(WindowEvent::Focused(false), window_id); diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index fdcee917..d51f5df9 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -79,6 +79,9 @@ pub struct Window { /// Grabbing mode. cursor_grab_mode: Mutex, + + /// Whether the window has keyboard focus. + has_focus: Arc, } impl Window { @@ -244,6 +247,7 @@ impl Window { window.surface().commit(); let size = Arc::new(Mutex::new(LogicalSize::new(width, height))); + let has_focus = Arc::new(AtomicBool::new(true)); // We should trigger redraw and commit the surface for the newly created window. let mut window_user_request = WindowUserRequest::new(); @@ -258,6 +262,7 @@ impl Window { &event_loop_window_target.env, window, size.clone(), + has_focus.clone(), window_requests.clone(), ); @@ -318,6 +323,7 @@ impl Window { resizeable: AtomicBool::new(attributes.resizable), decorated: AtomicBool::new(attributes.decorations), cursor_grab_mode: Mutex::new(CursorGrabMode::None), + has_focus, }; Ok(window) @@ -650,6 +656,10 @@ impl Window { } #[inline] + pub fn has_focus(&self) -> bool { + self.has_focus.load(Ordering::Relaxed) + } + pub fn title(&self) -> String { String::new() } diff --git a/src/platform_impl/linux/wayland/window/shim.rs b/src/platform_impl/linux/wayland/window/shim.rs index d43df558..0560c44c 100644 --- a/src/platform_impl/linux/wayland/window/shim.rs +++ b/src/platform_impl/linux/wayland/window/shim.rs @@ -1,5 +1,6 @@ use std::cell::Cell; use std::mem::ManuallyDrop; +use std::sync::atomic::AtomicBool; use std::sync::{Arc, Mutex}; use sctk::reexports::client::protocol::wl_compositor::WlCompositor; @@ -154,6 +155,9 @@ pub struct WindowHandle { /// Whether the window is resizable. pub is_resizable: Cell, + /// Whether the window has keyboard focus. + pub has_focus: Arc, + /// Allow IME events for that window. pub ime_allowed: Cell, @@ -187,6 +191,7 @@ impl WindowHandle { env: &Environment, window: Window, size: Arc>>, + has_focus: Arc, pending_window_requests: Arc>>, ) -> Self { let xdg_activation = env.get_global::(); @@ -209,6 +214,7 @@ impl WindowHandle { attention_requested: Cell::new(false), compositor, ime_allowed: Cell::new(false), + has_focus, } } diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 11c9a6a9..2e81cb54 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -930,6 +930,10 @@ impl EventProcessor { let window_id = mkwid(xev.event); let position = PhysicalPosition::new(xev.event_x, xev.event_y); + if let Some(window) = self.with_window(xev.event, Arc::clone) { + window.shared_state_lock().has_focus = true; + } + callback(Event::WindowEvent { window_id, event: Focused(true), @@ -1002,6 +1006,10 @@ impl EventProcessor { event: WindowEvent::ModifiersChanged(ModifiersState::empty()), }); + if let Some(window) = self.with_window(xev.event, Arc::clone) { + window.shared_state_lock().has_focus = false; + } + callback(Event::WindowEvent { window_id, event: Focused(false), diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index ec91c13b..9f61d22d 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -55,6 +55,7 @@ pub struct SharedState { pub resize_increments: Option, pub base_size: Option, pub visibility: Visibility, + pub has_focus: bool, } #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -94,6 +95,7 @@ impl SharedState { max_inner_size: None, resize_increments: None, base_size: None, + has_focus: true, }) } } @@ -1602,6 +1604,10 @@ impl UnownedWindow { } #[inline] + pub fn has_focus(&self) -> bool { + self.shared_state_lock().has_focus + } + pub fn title(&self) -> String { String::new() } diff --git a/src/platform_impl/macos/appkit/window.rs b/src/platform_impl/macos/appkit/window.rs index f294a8e8..9b74a65e 100644 --- a/src/platform_impl/macos/appkit/window.rs +++ b/src/platform_impl/macos/appkit/window.rs @@ -192,6 +192,9 @@ extern_methods!( #[sel(isVisible)] pub fn isVisible(&self) -> bool; + #[sel(isKeyWindow)] + pub fn isKeyWindow(&self) -> bool; + #[sel(isZoomed)] pub fn isZoomed(&self) -> bool; diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index e6cedd91..c9ab15a3 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -1215,6 +1215,10 @@ impl WinitWindow { } #[inline] + pub fn has_focus(&self) -> bool { + self.isKeyWindow() + } + pub fn set_theme(&self, theme: Option) { set_ns_theme(theme); self.lock_shared_state("set_theme").current_theme = theme.or_else(|| Some(get_ns_theme())); diff --git a/src/platform_impl/orbital/window.rs b/src/platform_impl/orbital/window.rs index fd1aa9eb..384684f6 100644 --- a/src/platform_impl/orbital/window.rs +++ b/src/platform_impl/orbital/window.rs @@ -393,6 +393,11 @@ impl Window { None } + #[inline] + pub fn has_focus(&self) -> bool { + false + } + #[inline] pub fn set_theme(&self, _theme: Option) {} } diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 94d17209..17aafd94 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -56,6 +56,7 @@ impl EventLoopWindowTarget { canvas: &Rc>, id: WindowId, prevent_default: bool, + has_focus: Rc>, ) { self.runner.add_canvas(RootWindowId(id), canvas); let mut canvas = canvas.borrow_mut(); @@ -65,7 +66,9 @@ impl EventLoopWindowTarget { canvas.on_touch_end(prevent_default); let runner = self.runner.clone(); + let has_focus_clone = has_focus.clone(); canvas.on_blur(move || { + *has_focus_clone.borrow_mut() = false; runner.send_event(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::Focused(false), @@ -73,7 +76,9 @@ impl EventLoopWindowTarget { }); let runner = self.runner.clone(); + let has_focus_clone = has_focus.clone(); canvas.on_focus(move || { + *has_focus_clone.borrow_mut() = true; runner.send_event(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::Focused(true), @@ -191,6 +196,8 @@ impl EventLoopWindowTarget { let runner_touch = self.runner.clone(); canvas.on_mouse_press( move |pointer_id, position, button, modifiers| { + *has_focus.borrow_mut() = true; + // A mouse down event may come in without any prior CursorMoved events, // therefore we should send a CursorMoved event to make sure that the // user code has the correct cursor position. diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 4a558298..48e0ba44 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -23,6 +23,7 @@ pub struct Window { register_redraw_request: Box, resize_notify_fn: Box)>, destroy_fn: Option>, + has_focus: Rc>, } impl Window { @@ -42,7 +43,8 @@ impl Window { let register_redraw_request = Box::new(move || runner.request_redraw(RootWI(id))); - target.register(&canvas, id, prevent_default); + let has_focus = Rc::new(RefCell::new(false)); + target.register(&canvas, id, prevent_default, has_focus.clone()); let runner = target.runner.clone(); let resize_notify_fn = Box::new(move |new_size| { @@ -62,6 +64,7 @@ impl Window { register_redraw_request, resize_notify_fn, destroy_fn: Some(destroy_fn), + has_focus, }; backend::set_canvas_size( @@ -399,6 +402,10 @@ impl Window { } #[inline] + pub fn has_focus(&self) -> bool { + *self.has_focus.borrow() + } + pub fn title(&self) -> String { String::new() } diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 7ba8c44a..a82a42f0 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -773,6 +773,11 @@ impl Window { } #[inline] + pub fn has_focus(&self) -> bool { + let window_state = self.window_state.lock().unwrap(); + window_state.has_active_focus() + } + pub fn title(&self) -> String { let len = unsafe { GetWindowTextLengthW(self.window.0) } + 1; let mut buf = vec![0; len as usize]; diff --git a/src/window.rs b/src/window.rs index f9e52338..ca2d4ed2 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1022,6 +1022,16 @@ impl Window { self.window.focus_window() } + /// Gets whether the window has keyboard focus. + /// + /// This queries the same state information as [`WindowEvent::Focused`]. + /// + /// [`WindowEvent::Focused`]: crate::event::WindowEvent::Focused + #[inline] + pub fn has_focus(&self) -> bool { + self.window.has_focus() + } + /// 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.