From fc60c5a5c5673eda87c0f916aab24603e585a30b Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Wed, 16 Feb 2022 21:07:54 +0100 Subject: [PATCH] Add X11 opt-in function for device events Previously on X11, by default all global events were broadcasted to every Winit application. This unnecessarily drains battery due to excessive CPU usage when moving the mouse. To resolve this, device events are now ignored by default and users must manually opt into it using `EventLoop::set_filter_device_events`. Fixes (#1634) on Linux. --- CHANGELOG.md | 1 + src/event_loop.rs | 22 +++++++++++++ src/platform_impl/linux/mod.rs | 10 +++++- .../linux/x11/event_processor.rs | 2 +- src/platform_impl/linux/x11/mod.rs | 31 ++++++++++--------- 5 files changed, 50 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5798fe9c5ff..b09b73fa50b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ And please only add new entries to the top of this list, right below the `# Unre - On Wayland, fix polling during consecutive `EventLoop::run_return` invocations. - On Windows, fix race issue creating fullscreen windows with `WindowBuilder::with_fullscreen` - On Android, `virtual_keycode` for `KeyboardInput` events is now filled in where a suitable match is found. +- **Breaking:** On X11, device events are now ignored for unfocused windows by default, use `EventLoop::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 25c3ccd45df..c6ead85c989 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -221,6 +221,28 @@ impl EventLoop { event_loop_proxy: self.event_loop.create_proxy(), } } + + /// Change [`DeviceEvent`] filter mode. + /// + /// Since the [`DeviceEvent`] capture can lead to high CPU usage for unfocused windows, winit + /// will ignore them by default 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_filter_device_events(&self, filter_events: bool) { + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + self.event_loop.set_filter_device_events(filter_events); + } } impl Deref for EventLoop { diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 16f66e34b44..2c01ada5877 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -709,7 +709,15 @@ 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()) + } + + #[cfg(feature = "x11")] + pub fn set_filter_device_events(&self, filter_events: bool) { + match self { + EventLoop::X(evlp) => evlp.set_filter_device_events(filter_events), + EventLoop::Wayland(_) => (), + } } } diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 77cc55309fa..281b7798713 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -45,7 +45,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)); } } } diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index e0a2995e774..a7d4a721489 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -280,6 +280,22 @@ impl EventLoop { } } + pub fn set_filter_device_events(&self, filter_events: bool) { + let mut mask = 0; + if !filter_events { + mask = ffi::XI_RawMotionMask + | ffi::XI_RawButtonPressMask + | ffi::XI_RawButtonReleaseMask + | ffi::XI_RawKeyPressMask + | ffi::XI_RawKeyReleaseMask; + } + + let wt = get_xtarget(&self.event_processor.target); + wt.xconn + .select_xinput_events(wt.root, ffi::XIAllDevices, mask) + .queue(); + } + pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy { user_sender: self.user_sender.clone(), @@ -695,24 +711,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 };