diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cfed90221..84e6b0ba7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ And please only add new entries to the top of this list, right below the `# Unre - Added `Window::set_ime_allowed` supported on desktop platforms. - **Breaking:** IME input on desktop platforms won't be received unless it's explicitly allowed via `Window::set_ime_allowed` and new `WindowEvent::Ime` events are handled. - On macOS, `WindowEvent::Resized` is now emitted in `frameDidChange` instead of `windowDidResize`. +- **Breaking:** On X11, device events are now ignored for unfocused windows by default, use `EventLoopWindowTarget::set_device_event_filter` to set the filter level. # 0.26.1 (2022-01-05) diff --git a/src/event_loop.rs b/src/event_loop.rs index d6a15cfa9a..58181b3f80 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -286,6 +286,28 @@ 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 + /// + /// - **Wayland / Windows / macOS / iOS / Android / Web**: Unsupported. + /// + /// [`DeviceEvent`]: crate::event::DeviceEvent + pub fn set_device_event_filter(&mut self, _filter: DeviceEventFilter) { + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + self.p.set_device_event_filter(_filter); + } } /// Used to send custom events to `EventLoop`. @@ -330,3 +352,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/linux/mod.rs b/src/platform_impl/linux/mod.rs index 6552a8227d..39511dd79e 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -30,7 +30,9 @@ use crate::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, error::{ExternalError, NotSupportedError, OsError as RootOsError}, event::Event, - event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, + event_loop::{ + ControlFlow, DeviceEventFilter, EventLoopClosed, EventLoopWindowTarget as RootELW, + }, icon::Icon, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, window::{CursorIcon, Fullscreen, UserAttentionType, WindowAttributes}, @@ -732,7 +734,7 @@ impl EventLoop { } pub fn window_target(&self) -> &crate::event_loop::EventLoopWindowTarget { - x11_or_wayland!(match self; EventLoop(evl) => evl.window_target()) + x11_or_wayland!(match self; EventLoop(evlp) => evlp.window_target()) } } @@ -793,6 +795,16 @@ impl EventLoopWindowTarget { } } } + + #[inline] + pub fn set_device_event_filter(&mut self, _filter: DeviceEventFilter) { + match *self { + #[cfg(feature = "wayland")] + EventLoopWindowTarget::Wayland(_) => (), + #[cfg(feature = "x11")] + EventLoopWindowTarget::X(ref mut evlp) => evlp.set_device_event_filter(_filter), + } + } } fn sticky_exit_callback( diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 0db198bbab..8ec326526c 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -49,7 +49,7 @@ impl EventProcessor { let mut devices = self.devices.borrow_mut(); if let Some(info) = DeviceInfo::get(&wt.xconn, device) { for info in info.iter() { - devices.insert(DeviceId(info.deviceid), Device::new(self, info)); + devices.insert(DeviceId(info.deviceid), Device::new(info)); } } } @@ -907,6 +907,8 @@ impl EventProcessor { if self.active_window != Some(xev.event) { self.active_window = Some(xev.event); + wt.update_device_event_filter(true); + let window_id = mkwid(xev.event); let position = PhysicalPosition::new(xev.event_x, xev.event_y); @@ -956,6 +958,7 @@ impl EventProcessor { if !self.window_exists(xev.event) { return; } + wt.ime .borrow_mut() .unfocus(xev.event) @@ -964,6 +967,8 @@ impl EventProcessor { if self.active_window.take() == Some(xev.event) { let window_id = mkwid(xev.event); + wt.update_device_event_filter(false); + // Issue key release events for all pressed keys Self::handle_pressed_keys( wt, diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 491e37d6d9..a38b9030ec 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -50,7 +50,9 @@ use self::{ use crate::{ error::OsError as RootOsError, event::{Event, StartCause}, - event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, + event_loop::{ + ControlFlow, DeviceEventFilter, EventLoopClosed, EventLoopWindowTarget as RootELW, + }, platform_impl::{platform::sticky_exit_callback, PlatformSpecificWindowBuilderAttributes}, window::WindowAttributes, }; @@ -105,6 +107,7 @@ pub struct EventLoopWindowTarget { ime: RefCell, windows: RefCell>>, redraw_sender: WakeSender, + device_event_filter: DeviceEventFilter, _marker: ::std::marker::PhantomData, } @@ -229,21 +232,27 @@ impl EventLoop { let (user_sender, user_channel) = std::sync::mpsc::channel(); let (redraw_sender, redraw_channel) = std::sync::mpsc::channel(); + let window_target = EventLoopWindowTarget { + ime, + root, + windows: Default::default(), + _marker: ::std::marker::PhantomData, + ime_sender, + xconn, + wm_delete_window, + net_wm_ping, + redraw_sender: WakeSender { + sender: redraw_sender, // not used again so no clone + waker: waker.clone(), + }, + device_event_filter: Default::default(), + }; + + // Set initial device event filter. + window_target.update_device_event_filter(true); + let target = Rc::new(RootELW { - p: super::EventLoopWindowTarget::X(EventLoopWindowTarget { - ime, - root, - windows: Default::default(), - _marker: ::std::marker::PhantomData, - ime_sender, - xconn, - wm_delete_window, - net_wm_ping, - redraw_sender: WakeSender { - sender: redraw_sender, // not used again so no clone - waker: waker.clone(), - }, - }), + p: super::EventLoopWindowTarget::X(window_target), _marker: ::std::marker::PhantomData, }); @@ -524,6 +533,29 @@ impl EventLoopWindowTarget { pub fn x_connection(&self) -> &Arc { &self.xconn } + + pub fn set_device_event_filter(&mut self, filter: DeviceEventFilter) { + self.device_event_filter = filter; + } + + /// Update the device event filter based on window focus. + pub fn update_device_event_filter(&self, focus: bool) { + let filter_events = self.device_event_filter == DeviceEventFilter::Never + || (self.device_event_filter == DeviceEventFilter::Unfocused && !focus); + + let mut mask = 0; + if !filter_events { + mask = ffi::XI_RawMotionMask + | ffi::XI_RawButtonPressMask + | ffi::XI_RawButtonReleaseMask + | ffi::XI_RawKeyPressMask + | ffi::XI_RawKeyReleaseMask; + } + + self.xconn + .select_xinput_events(self.root, ffi::XIAllDevices, mask) + .queue(); + } } impl EventLoopProxy { @@ -698,24 +730,11 @@ enum ScrollOrientation { } impl Device { - fn new(el: &EventProcessor, info: &ffi::XIDeviceInfo) -> Self { + fn new(info: &ffi::XIDeviceInfo) -> Self { let name = unsafe { CStr::from_ptr(info.name).to_string_lossy() }; let mut scroll_axes = Vec::new(); - let wt = get_xtarget(&el.target); - if Device::physical_device(info) { - // Register for global raw events - let mask = ffi::XI_RawMotionMask - | ffi::XI_RawButtonPressMask - | ffi::XI_RawButtonReleaseMask - | ffi::XI_RawKeyPressMask - | ffi::XI_RawKeyReleaseMask; - // The request buffer is flushed when we poll for events - wt.xconn - .select_xinput_events(wt.root, info.deviceid, mask) - .queue(); - // Identify scroll axes for class_ptr in Device::classes(info) { let class = unsafe { &**class_ptr };