Skip to content

Commit

Permalink
Add DeviceEventFilter on Windows (#465)
Browse files Browse the repository at this point in the history
* Add DeviceEventFilter on Windows

* Add change file

* use winit implementation instead

Co-authored-by: ajtribick <[email protected]>

Co-authored-by: Wu Yu Wei <[email protected]>
Co-authored-by: amrbashir <[email protected]>
Co-authored-by: ajtribick <[email protected]>
  • Loading branch information
4 people authored Aug 17, 2022
1 parent 6d8cc7e commit 5bbd4f8
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 9 deletions.
7 changes: 7 additions & 0 deletions .changes/filter-windows.md
Original file line number Diff line number Diff line change
@@ -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.

33 changes: 33 additions & 0 deletions src/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,22 @@ impl<T> EventLoopWindowTarget<T> {
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
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<T> HasRawDisplayHandle for EventLoopWindowTarget<T> {
Expand Down Expand Up @@ -262,3 +278,20 @@ impl<T> fmt::Display for EventLoopClosed<T> {
}

impl<T: fmt::Debug> error::Error for EventLoopClosed<T> {}

/// 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
}
}
18 changes: 12 additions & 6 deletions src/platform_impl/windows/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -188,7 +188,7 @@ impl<T: 'static> EventLoop<T> {
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,
Expand Down Expand Up @@ -310,6 +310,10 @@ impl<T> EventLoopWindowTarget<T> {
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 {
Expand Down Expand Up @@ -2100,10 +2104,6 @@ unsafe extern "system" fn thread_event_target_callback<T: 'static>(
) -> LRESULT {
let subclass_input = Box::from_raw(subclass_input_ptr as *mut ThreadMsgTargetSubclassInput<T>);

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
Expand All @@ -2113,6 +2113,7 @@ unsafe extern "system" fn thread_event_target_callback<T: 'static>(
win32wm::WM_NCDESTROY => {
remove_event_target_window_subclass::<T>(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
Expand Down Expand Up @@ -2154,13 +2155,15 @@ unsafe extern "system" fn thread_event_target_callback<T: 'static>(
device_id: wrap_device_id(lparam.0),
event,
});
RedrawWindow(window, ptr::null(), HRGN::default(), RDW_INTERNALPAINT);

LRESULT(0)
}

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)
Expand All @@ -2170,11 +2173,13 @@ unsafe extern "system" fn thread_event_target_callback<T: 'static>(
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 => {
Expand Down Expand Up @@ -2209,6 +2214,7 @@ unsafe extern "system" fn thread_event_target_callback<T: 'static>(
}
}
subclass_input.event_loop_runner.poll();
RedrawWindow(window, ptr::null(), HRGN::default(), RDW_INTERNALPAINT);
LRESULT(0)
}
_ => DefSubclassProc(window, msg, wparam, lparam),
Expand Down
17 changes: 14 additions & 3 deletions src/platform_impl/windows/raw_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<RAWINPUTDEVICELIST>> {
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit 5bbd4f8

Please sign in to comment.