Skip to content

Commit e232ec1

Browse files
chrisduerrkchibisov
authored andcommitted
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 `EventLoopWindowTarget::set_filter_device_events`. Fixes (rust-windowing#1634) on Linux.
1 parent 9253029 commit e232ec1

File tree

5 files changed

+108
-32
lines changed

5 files changed

+108
-32
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ And please only add new entries to the top of this list, right below the `# Unre
5050
- Added `Window::set_ime_allowed` supported on desktop platforms.
5151
- **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.
5252
- On macOS, `WindowEvent::Resized` is now emitted in `frameDidChange` instead of `windowDidResize`.
53+
- **Breaking:** On X11, device events are now ignored for unfocused windows by default, use `EventLoopWindowTarget::set_device_event_filter` to set the filter level.
5354

5455
# 0.26.1 (2022-01-05)
5556

src/event_loop.rs

+39
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,28 @@ impl<T> EventLoopWindowTarget<T> {
286286
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
287287
self.p.primary_monitor()
288288
}
289+
290+
/// Change [`DeviceEvent`] filter mode.
291+
///
292+
/// Since the [`DeviceEvent`] capture can lead to high CPU usage for unfocused windows, winit
293+
/// will ignore them by default for unfocused windows on Linux/BSD. This method allows changing
294+
/// this filter at runtime to explicitly capture them again.
295+
///
296+
/// ## Platform-specific
297+
///
298+
/// - **Wayland / Windows / macOS / iOS / Android / Web**: Unsupported.
299+
///
300+
/// [`DeviceEvent`]: crate::event::DeviceEvent
301+
pub fn set_device_event_filter(&mut self, _filter: DeviceEventFilter) {
302+
#[cfg(any(
303+
target_os = "linux",
304+
target_os = "dragonfly",
305+
target_os = "freebsd",
306+
target_os = "netbsd",
307+
target_os = "openbsd"
308+
))]
309+
self.p.set_device_event_filter(_filter);
310+
}
289311
}
290312

291313
/// Used to send custom events to `EventLoop`.
@@ -330,3 +352,20 @@ impl<T> fmt::Display for EventLoopClosed<T> {
330352
}
331353

332354
impl<T: fmt::Debug> error::Error for EventLoopClosed<T> {}
355+
356+
/// Fiter controlling the propagation of device events.
357+
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
358+
pub enum DeviceEventFilter {
359+
/// Always filter out device events.
360+
Always,
361+
/// Filter out device events while the window is not focused.
362+
Unfocused,
363+
/// Report all device events regardless of window focus.
364+
Never,
365+
}
366+
367+
impl Default for DeviceEventFilter {
368+
fn default() -> Self {
369+
Self::Unfocused
370+
}
371+
}

src/platform_impl/linux/mod.rs

+14-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ use crate::{
3030
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
3131
error::{ExternalError, NotSupportedError, OsError as RootOsError},
3232
event::Event,
33-
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
33+
event_loop::{
34+
ControlFlow, DeviceEventFilter, EventLoopClosed, EventLoopWindowTarget as RootELW,
35+
},
3436
icon::Icon,
3537
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
3638
window::{CursorIcon, Fullscreen, UserAttentionType, WindowAttributes},
@@ -732,7 +734,7 @@ impl<T: 'static> EventLoop<T> {
732734
}
733735

734736
pub fn window_target(&self) -> &crate::event_loop::EventLoopWindowTarget<T> {
735-
x11_or_wayland!(match self; EventLoop(evl) => evl.window_target())
737+
x11_or_wayland!(match self; EventLoop(evlp) => evlp.window_target())
736738
}
737739
}
738740

@@ -793,6 +795,16 @@ impl<T> EventLoopWindowTarget<T> {
793795
}
794796
}
795797
}
798+
799+
#[inline]
800+
pub fn set_device_event_filter(&mut self, _filter: DeviceEventFilter) {
801+
match *self {
802+
#[cfg(feature = "wayland")]
803+
EventLoopWindowTarget::Wayland(_) => (),
804+
#[cfg(feature = "x11")]
805+
EventLoopWindowTarget::X(ref mut evlp) => evlp.set_device_event_filter(_filter),
806+
}
807+
}
796808
}
797809

798810
fn sticky_exit_callback<T, F>(

src/platform_impl/linux/x11/event_processor.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ impl<T: 'static> EventProcessor<T> {
4949
let mut devices = self.devices.borrow_mut();
5050
if let Some(info) = DeviceInfo::get(&wt.xconn, device) {
5151
for info in info.iter() {
52-
devices.insert(DeviceId(info.deviceid), Device::new(self, info));
52+
devices.insert(DeviceId(info.deviceid), Device::new(info));
5353
}
5454
}
5555
}
@@ -907,6 +907,8 @@ impl<T: 'static> EventProcessor<T> {
907907
if self.active_window != Some(xev.event) {
908908
self.active_window = Some(xev.event);
909909

910+
wt.update_device_event_filter(true);
911+
910912
let window_id = mkwid(xev.event);
911913
let position = PhysicalPosition::new(xev.event_x, xev.event_y);
912914

@@ -956,6 +958,7 @@ impl<T: 'static> EventProcessor<T> {
956958
if !self.window_exists(xev.event) {
957959
return;
958960
}
961+
959962
wt.ime
960963
.borrow_mut()
961964
.unfocus(xev.event)
@@ -964,6 +967,8 @@ impl<T: 'static> EventProcessor<T> {
964967
if self.active_window.take() == Some(xev.event) {
965968
let window_id = mkwid(xev.event);
966969

970+
wt.update_device_event_filter(false);
971+
967972
// Issue key release events for all pressed keys
968973
Self::handle_pressed_keys(
969974
wt,

src/platform_impl/linux/x11/mod.rs

+48-29
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ use self::{
5050
use crate::{
5151
error::OsError as RootOsError,
5252
event::{Event, StartCause},
53-
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
53+
event_loop::{
54+
ControlFlow, DeviceEventFilter, EventLoopClosed, EventLoopWindowTarget as RootELW,
55+
},
5456
platform_impl::{platform::sticky_exit_callback, PlatformSpecificWindowBuilderAttributes},
5557
window::WindowAttributes,
5658
};
@@ -105,6 +107,7 @@ pub struct EventLoopWindowTarget<T> {
105107
ime: RefCell<Ime>,
106108
windows: RefCell<HashMap<WindowId, Weak<UnownedWindow>>>,
107109
redraw_sender: WakeSender<WindowId>,
110+
device_event_filter: DeviceEventFilter,
108111
_marker: ::std::marker::PhantomData<T>,
109112
}
110113

@@ -229,21 +232,27 @@ impl<T: 'static> EventLoop<T> {
229232
let (user_sender, user_channel) = std::sync::mpsc::channel();
230233
let (redraw_sender, redraw_channel) = std::sync::mpsc::channel();
231234

235+
let window_target = EventLoopWindowTarget {
236+
ime,
237+
root,
238+
windows: Default::default(),
239+
_marker: ::std::marker::PhantomData,
240+
ime_sender,
241+
xconn,
242+
wm_delete_window,
243+
net_wm_ping,
244+
redraw_sender: WakeSender {
245+
sender: redraw_sender, // not used again so no clone
246+
waker: waker.clone(),
247+
},
248+
device_event_filter: Default::default(),
249+
};
250+
251+
// Set initial device event filter.
252+
window_target.update_device_event_filter(true);
253+
232254
let target = Rc::new(RootELW {
233-
p: super::EventLoopWindowTarget::X(EventLoopWindowTarget {
234-
ime,
235-
root,
236-
windows: Default::default(),
237-
_marker: ::std::marker::PhantomData,
238-
ime_sender,
239-
xconn,
240-
wm_delete_window,
241-
net_wm_ping,
242-
redraw_sender: WakeSender {
243-
sender: redraw_sender, // not used again so no clone
244-
waker: waker.clone(),
245-
},
246-
}),
255+
p: super::EventLoopWindowTarget::X(window_target),
247256
_marker: ::std::marker::PhantomData,
248257
});
249258

@@ -524,6 +533,29 @@ impl<T> EventLoopWindowTarget<T> {
524533
pub fn x_connection(&self) -> &Arc<XConnection> {
525534
&self.xconn
526535
}
536+
537+
pub fn set_device_event_filter(&mut self, filter: DeviceEventFilter) {
538+
self.device_event_filter = filter;
539+
}
540+
541+
/// Update the device event filter based on window focus.
542+
pub fn update_device_event_filter(&self, focus: bool) {
543+
let filter_events = self.device_event_filter == DeviceEventFilter::Never
544+
|| (self.device_event_filter == DeviceEventFilter::Unfocused && !focus);
545+
546+
let mut mask = 0;
547+
if !filter_events {
548+
mask = ffi::XI_RawMotionMask
549+
| ffi::XI_RawButtonPressMask
550+
| ffi::XI_RawButtonReleaseMask
551+
| ffi::XI_RawKeyPressMask
552+
| ffi::XI_RawKeyReleaseMask;
553+
}
554+
555+
self.xconn
556+
.select_xinput_events(self.root, ffi::XIAllDevices, mask)
557+
.queue();
558+
}
527559
}
528560

529561
impl<T: 'static> EventLoopProxy<T> {
@@ -698,24 +730,11 @@ enum ScrollOrientation {
698730
}
699731

700732
impl Device {
701-
fn new<T: 'static>(el: &EventProcessor<T>, info: &ffi::XIDeviceInfo) -> Self {
733+
fn new(info: &ffi::XIDeviceInfo) -> Self {
702734
let name = unsafe { CStr::from_ptr(info.name).to_string_lossy() };
703735
let mut scroll_axes = Vec::new();
704736

705-
let wt = get_xtarget(&el.target);
706-
707737
if Device::physical_device(info) {
708-
// Register for global raw events
709-
let mask = ffi::XI_RawMotionMask
710-
| ffi::XI_RawButtonPressMask
711-
| ffi::XI_RawButtonReleaseMask
712-
| ffi::XI_RawKeyPressMask
713-
| ffi::XI_RawKeyReleaseMask;
714-
// The request buffer is flushed when we poll for events
715-
wt.xconn
716-
.select_xinput_events(wt.root, info.deviceid, mask)
717-
.queue();
718-
719738
// Identify scroll axes
720739
for class_ptr in Device::classes(info) {
721740
let class = unsafe { &**class_ptr };

0 commit comments

Comments
 (0)