From c4d208d6c39660a16e72e59e5798fb6ed1347aa0 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 6eb55550988..d99d654af48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ And please only add new entries to the top of this list, right below the `# Unre - **Breaking:** Bump `ndk` version to 0.6, ndk-sys to `v0.3`, `ndk-glue` to `0.6`. - Remove no longer needed `WINIT_LINK_COLORSYNC` environment variable. - **Breaking:** Rename the `Exit` variant of `ControlFlow` to `ExitWithCode`, which holds a value to control the exit code after running. Add an `Exit` constant which aliases to `ExitWithCode(0)` instead to avoid major breakage. This shouldn't affect most existing programs. +- **Breaking:** On X11, device events are now ignored by default, use `EventLoop::set_filter_device_events` to opt-into device events again. # 0.26.1 (2022-01-05) diff --git a/src/event_loop.rs b/src/event_loop.rs index 17c01f5f58a..ff7a18a21a8 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -185,6 +185,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 b8e233653c9..4500af035df 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -670,7 +670,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 17f9436f580..e5dd3b60d23 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 };