From 4ec2ea8cb9709c6ec5e87028e2e568425b8c19d7 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 9cfed902216..ab65bcc889d 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 `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 d6a15cfa9a4..531ffafd1e2 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -256,6 +256,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 6552a8227d6..72127c491a8 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -732,7 +732,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 0db198bbab7..73a90c99d84 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)); } } } diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 491e37d6d9c..bb8313e2ba1 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -283,6 +283,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(), @@ -698,24 +714,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 };