From 5bbd4f8f72901425432a35915d79d0bee0c96cce Mon Sep 17 00:00:00 2001 From: "Ngo Iok Ui (Wu Yu Wei)" Date: Wed, 17 Aug 2022 20:22:26 +0800 Subject: [PATCH] Add DeviceEventFilter on Windows (#465) * Add DeviceEventFilter on Windows * Add change file * use winit implementation instead Co-authored-by: ajtribick Co-authored-by: Wu Yu Wei Co-authored-by: amrbashir Co-authored-by: ajtribick --- .changes/filter-windows.md | 7 ++++++ src/event_loop.rs | 33 +++++++++++++++++++++++++ src/platform_impl/windows/event_loop.rs | 18 +++++++++----- src/platform_impl/windows/raw_input.rs | 17 ++++++++++--- 4 files changed, 66 insertions(+), 9 deletions(-) create mode 100644 .changes/filter-windows.md diff --git a/.changes/filter-windows.md b/.changes/filter-windows.md new file mode 100644 index 000000000..84ce3d413 --- /dev/null +++ b/.changes/filter-windows.md @@ -0,0 +1,7 @@ +--- +"tao": minor +--- + +* Add DeviceEventFilter on Windows. +* **Breaking**: On Windows, device events are now ignored for unfocused windows by default, use `EventLoopWindowTarget::set_device_event_filter` to set the filter level. + diff --git a/src/event_loop.rs b/src/event_loop.rs index 491d6f840..90714caa5 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -211,6 +211,22 @@ impl EventLoopWindowTarget { pub fn primary_monitor(&self) -> Option { self.p.primary_monitor() } + + /// Change [`DeviceEvent`] filter mode. + /// + /// Since the [`DeviceEvent`] capture can lead to high CPU usage for unfocused windows, winit + /// will ignore them by default for unfocused windows on Linux/BSD. This method allows changing + /// this filter at runtime to explicitly capture them again. + /// + /// ## Platform-specific + /// + /// - ** Linux / macOS / iOS / Android / Web**: Unsupported. + /// + /// [`DeviceEvent`]: crate::event::DeviceEvent + pub fn set_device_event_filter(&self, _filter: DeviceEventFilter) { + #[cfg(target_os = "windows")] + self.p.set_device_event_filter(_filter); + } } unsafe impl HasRawDisplayHandle for EventLoopWindowTarget { @@ -262,3 +278,20 @@ impl fmt::Display for EventLoopClosed { } impl error::Error for EventLoopClosed {} + +/// Fiter controlling the propagation of device events. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub enum DeviceEventFilter { + /// Always filter out device events. + Always, + /// Filter out device events while the window is not focused. + Unfocused, + /// Report all device events regardless of window focus. + Never, +} + +impl Default for DeviceEventFilter { + fn default() -> Self { + Self::Unfocused + } +} diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 42532325d..ea553b4e4 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -45,7 +45,7 @@ use crate::{ accelerator::AcceleratorId, dpi::{PhysicalPosition, PhysicalSize}, event::{DeviceEvent, Event, Force, RawKeyEvent, Touch, TouchPhase, WindowEvent}, - event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, + event_loop::{ControlFlow, DeviceEventFilter, EventLoopClosed, EventLoopWindowTarget as RootELW}, keyboard::{KeyCode, ModifiersState}, monitor::MonitorHandle as RootMonitorHandle, platform_impl::platform::{ @@ -188,7 +188,7 @@ impl EventLoop { let runner_shared = Rc::new(EventLoopRunner::new(thread_msg_target, wait_thread_id)); let thread_msg_sender = subclass_event_target_window(thread_msg_target, runner_shared.clone()); - raw_input::register_all_mice_and_keyboards_for_raw_input(thread_msg_target); + raw_input::register_all_mice_and_keyboards_for_raw_input(thread_msg_target, Default::default()); EventLoop { thread_msg_sender, @@ -310,6 +310,10 @@ impl EventLoopWindowTarget { pub fn raw_display_handle(&self) -> RawDisplayHandle { RawDisplayHandle::Windows(WindowsDisplayHandle::empty()) } + + pub fn set_device_event_filter(&self, filter: DeviceEventFilter) { + raw_input::register_all_mice_and_keyboards_for_raw_input(self.thread_msg_target, filter); + } } fn main_thread_id() -> u32 { @@ -2100,10 +2104,6 @@ unsafe extern "system" fn thread_event_target_callback( ) -> LRESULT { let subclass_input = Box::from_raw(subclass_input_ptr as *mut ThreadMsgTargetSubclassInput); - if msg != WM_PAINT { - RedrawWindow(window, ptr::null(), HRGN::default(), RDW_INTERNALPAINT); - } - let mut subclass_removed = false; // I decided to bind the closure to `callback` and pass it to catch_unwind rather than passing @@ -2113,6 +2113,7 @@ unsafe extern "system" fn thread_event_target_callback( win32wm::WM_NCDESTROY => { remove_event_target_window_subclass::(window); subclass_removed = true; + RedrawWindow(window, ptr::null(), HRGN::default(), RDW_INTERNALPAINT); LRESULT(0) } // Because WM_PAINT comes after all other messages, we use it during modal loops to detect @@ -2154,6 +2155,7 @@ unsafe extern "system" fn thread_event_target_callback( device_id: wrap_device_id(lparam.0), event, }); + RedrawWindow(window, ptr::null(), HRGN::default(), RDW_INTERNALPAINT); LRESULT(0) } @@ -2161,6 +2163,7 @@ unsafe extern "system" fn thread_event_target_callback( win32wm::WM_INPUT => { if let Some(data) = raw_input::get_raw_input_data(HRAWINPUT(lparam.0)) { handle_raw_input(&subclass_input, data); + RedrawWindow(window, ptr::null(), HRGN::default(), RDW_INTERNALPAINT); } DefSubclassProc(window, msg, wparam, lparam) @@ -2170,11 +2173,13 @@ unsafe extern "system" fn thread_event_target_callback( if let Ok(event) = subclass_input.user_event_receiver.recv() { subclass_input.send_event(Event::UserEvent(event)); } + RedrawWindow(window, ptr::null(), HRGN::default(), RDW_INTERNALPAINT); LRESULT(0) } _ if msg == *EXEC_MSG_ID => { let mut function: ThreadExecFn = Box::from_raw(wparam.0 as *mut _); function(); + RedrawWindow(window, ptr::null(), HRGN::default(), RDW_INTERNALPAINT); LRESULT(0) } _ if msg == *PROCESS_NEW_EVENTS_MSG_ID => { @@ -2209,6 +2214,7 @@ unsafe extern "system" fn thread_event_target_callback( } } subclass_input.event_loop_runner.poll(); + RedrawWindow(window, ptr::null(), HRGN::default(), RDW_INTERNALPAINT); LRESULT(0) } _ => DefSubclassProc(window, msg, wparam, lparam), diff --git a/src/platform_impl/windows/raw_input.rs b/src/platform_impl/windows/raw_input.rs index 8c2e87448..d120bcb9f 100644 --- a/src/platform_impl/windows/raw_input.rs +++ b/src/platform_impl/windows/raw_input.rs @@ -15,7 +15,7 @@ use windows::Win32::{ }, }; -use crate::{event::ElementState, platform_impl::platform::util}; +use crate::{event::ElementState, event_loop::DeviceEventFilter, platform_impl::platform::util}; #[allow(dead_code)] pub fn get_raw_input_device_list() -> Option> { @@ -130,10 +130,21 @@ pub fn register_raw_input_devices(devices: &[RAWINPUTDEVICE]) -> bool { success.as_bool() } -pub fn register_all_mice_and_keyboards_for_raw_input(window_handle: HWND) -> bool { +pub fn register_all_mice_and_keyboards_for_raw_input( + mut window_handle: HWND, + filter: DeviceEventFilter, +) -> bool { // RIDEV_DEVNOTIFY: receive hotplug events // RIDEV_INPUTSINK: receive events even if we're not in the foreground - let flags = RAWINPUTDEVICE_FLAGS(RIDEV_DEVNOTIFY.0 | RIDEV_INPUTSINK.0); + // RIDEV_REMOVE: don't receive device events (requires NULL hwndTarget) + let flags = match filter { + DeviceEventFilter::Always => { + window_handle = HWND(0); + RIDEV_REMOVE + } + DeviceEventFilter::Unfocused => RIDEV_DEVNOTIFY, + DeviceEventFilter::Never => RIDEV_DEVNOTIFY | RIDEV_INPUTSINK, + }; let devices: [RAWINPUTDEVICE; 2] = [ RAWINPUTDEVICE {