From 4ac2006cbc5a09a736bef2d23577430aadf88098 Mon Sep 17 00:00:00 2001 From: John Nunley Date: Wed, 31 May 2023 09:44:42 -0700 Subject: [PATCH] Replace mio with calloop in the X11 backend --- Cargo.toml | 3 +- src/platform_impl/linux/x11/mod.rs | 165 +++++++++++++------------- src/platform_impl/linux/x11/window.rs | 13 +- 3 files changed, 88 insertions(+), 93 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8f66bd06..871fb5fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"] -x11 = ["x11-dl", "mio", "percent-encoding", "xkbcommon-dl/x11"] +x11 = ["x11-dl", "percent-encoding", "xkbcommon-dl/x11"] wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "sctk", "fnv", "memmap2"] wayland-dlopen = ["wayland-backend/dlopen"] wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"] @@ -112,7 +112,6 @@ features = [ [target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies] libc = "0.2.64" -mio = { version = "0.8", features = ["os-ext"], optional = true } percent-encoding = { version = "2.0", optional = true } fnv = { version = "1.0.3", optional = true } sctk = { package = "smithay-client-toolkit", version = "0.17.0", default-features = false, features = ["calloop"], optional = true } diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 5f2ed0af..e0e166cb 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -17,24 +17,26 @@ pub(crate) use self::{ pub use self::xdisplay::{XError, XNotSupported}; +use calloop::channel::{channel, Channel, Event as ChanResult, Sender}; +use calloop::generic::Generic; +use calloop::{Dispatcher, EventLoop as Loop}; + use std::{ cell::{Cell, RefCell}, - collections::{HashMap, HashSet}, + collections::{HashMap, HashSet, VecDeque}, ffi::CStr, mem::{self, MaybeUninit}, ops::Deref, os::raw::*, + os::unix::io::RawFd, ptr, rc::Rc, slice, - sync::mpsc::{Receiver, Sender, TryRecvError}, sync::{mpsc, Arc, Weak}, time::{Duration, Instant}, }; use libc::{self, setlocale, LC_CTYPE}; - -use mio::{unix::SourceFd, Events, Interest, Poll, Token, Waker}; use raw_window_handle::{RawDisplayHandle, XlibDisplayHandle}; use self::{ @@ -54,47 +56,7 @@ use crate::{ window::WindowAttributes, }; -const X_TOKEN: Token = Token(0); -const USER_REDRAW_TOKEN: Token = Token(1); - -struct WakeSender { - sender: Sender, - waker: Arc, -} - -struct PeekableReceiver { - recv: Receiver, - first: Option, -} - -impl PeekableReceiver { - pub fn from_recv(recv: Receiver) -> Self { - Self { recv, first: None } - } - pub fn has_incoming(&mut self) -> bool { - if self.first.is_some() { - return true; - } - - match self.recv.try_recv() { - Ok(v) => { - self.first = Some(v); - true - } - Err(TryRecvError::Empty) => false, - Err(TryRecvError::Disconnected) => { - warn!("Channel was disconnected when checking incoming"); - false - } - } - } - pub fn try_recv(&mut self) -> Result { - if let Some(first) = self.first.take() { - return Ok(first); - } - self.recv.try_recv() - } -} +type X11Source = Generic; pub struct EventLoopWindowTarget { xconn: Arc, @@ -104,31 +66,40 @@ pub struct EventLoopWindowTarget { root: ffi::Window, ime: RefCell, windows: RefCell>>, - redraw_sender: WakeSender, + redraw_sender: Sender, device_events: Cell, _marker: ::std::marker::PhantomData, } pub struct EventLoop { - poll: Poll, - waker: Arc, + event_loop: Loop<'static, EventLoopState>, event_processor: EventProcessor, - redraw_receiver: PeekableReceiver, - user_receiver: PeekableReceiver, //waker.wake needs to be called whenever something gets sent user_sender: Sender, target: Rc>, + + /// The current state of the event loop. + state: EventLoopState, + + /// Dispatcher for redraw events. + redraw_dispatcher: Dispatcher<'static, Channel, EventLoopState>, +} + +struct EventLoopState { + /// Incoming user events. + user_events: VecDeque, + + /// Incoming redraw events. + redraw_events: VecDeque, } pub struct EventLoopProxy { user_sender: Sender, - waker: Arc, } impl Clone for EventLoopProxy { fn clone(&self) -> Self { EventLoopProxy { user_sender: self.user_sender.clone(), - waker: self.waker.clone(), } } } @@ -237,15 +208,41 @@ impl EventLoop { xconn.update_cached_wm_info(root); - let poll = Poll::new().unwrap(); - let waker = Arc::new(Waker::new(poll.registry(), USER_REDRAW_TOKEN).unwrap()); + // Create an event loop. + let event_loop = + Loop::>::try_new().expect("Failed to initialize the event loop"); + let handle = event_loop.handle(); - poll.registry() - .register(&mut SourceFd(&xconn.x11_fd), X_TOKEN, Interest::READABLE) - .unwrap(); + // Create the X11 event dispatcher. + let source = X11Source::new(xconn.x11_fd, calloop::Interest::READ, calloop::Mode::Level); + handle + .insert_source(source, |_, _, _| Ok(calloop::PostAction::Continue)) + .expect("Failed to register the X11 event dispatcher"); - let (user_sender, user_channel) = std::sync::mpsc::channel(); - let (redraw_sender, redraw_channel) = std::sync::mpsc::channel(); + // Create a channel for sending user events. + let (user_sender, user_channel) = channel(); + handle + .insert_source(user_channel, |ev, _, state| { + if let ChanResult::Msg(user) = ev { + state.user_events.push_back(user); + } + }) + .expect("Failed to register the user event channel with the event loop"); + + // Create a channel for handling redraw requests. + let (redraw_sender, redraw_channel) = channel(); + + // Create a dispatcher for the redraw channel such that we can dispatch it independent of the + // event loop. + let redraw_dispatcher = + Dispatcher::<_, EventLoopState>::new(redraw_channel, |ev, _, state| { + if let ChanResult::Msg(window_id) = ev { + state.redraw_events.push_back(window_id); + } + }); + handle + .register_dispatcher(redraw_dispatcher.clone()) + .expect("Failed to register the redraw event channel with the event loop"); let kb_state = KbdState::from_x11_xkb(unsafe { (xconn.xlib_xcb.XGetXCBConnection)(xconn.display) }) @@ -260,10 +257,7 @@ impl EventLoop { xconn, wm_delete_window, net_wm_ping, - redraw_sender: WakeSender { - sender: redraw_sender, // not used again so no clone - waker: waker.clone(), - }, + redraw_sender, device_events: Default::default(), }; @@ -310,20 +304,21 @@ impl EventLoop { event_processor.init_device(ffi::XIAllDevices); EventLoop { - poll, - waker, + event_loop, event_processor, - redraw_receiver: PeekableReceiver::from_recv(redraw_channel), - user_receiver: PeekableReceiver::from_recv(user_channel), user_sender, target, + redraw_dispatcher, + state: EventLoopState { + user_events: VecDeque::new(), + redraw_events: VecDeque::new(), + }, } } pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy { user_sender: self.user_sender.clone(), - waker: self.waker.clone(), } } @@ -372,7 +367,7 @@ impl EventLoop { // Empty the user event buffer { - while let Ok(event) = this.user_receiver.try_recv() { + while let Some(event) = this.state.user_events.pop_front() { sticky_exit_callback( crate::event::Event::UserEvent(event), &this.target, @@ -390,11 +385,19 @@ impl EventLoop { callback, ); } + + // Quickly dispatch all redraw events to avoid buffering them. + while let Ok(event) = this.redraw_dispatcher.as_source_mut().try_recv() { + this.state.redraw_events.push_back(event); + } + // Empty the redraw requests { let mut windows = HashSet::new(); - while let Ok(window_id) = this.redraw_receiver.try_recv() { + // Empty the channel. + + while let Some(window_id) = this.state.redraw_events.pop_front() { windows.insert(window_id); } @@ -464,7 +467,6 @@ impl EventLoop { } let mut control_flow = ControlFlow::default(); - let mut events = Events::with_capacity(8); let mut cause = StartCause::Init; // run the initial loop iteration @@ -475,22 +477,23 @@ impl EventLoop { break code; } let has_pending = self.event_processor.poll() - || self.user_receiver.has_incoming() - || self.redraw_receiver.has_incoming(); + || !self.state.user_events.is_empty() + || !self.state.redraw_events.is_empty(); if !has_pending { // Wait until - if let Err(e) = self.poll.poll(&mut events, iter_result.timeout) { - if e.raw_os_error() != Some(libc::EINTR) { - panic!("epoll returned an error: {e:?}"); - } + if let Err(error) = self + .event_loop + .dispatch(iter_result.timeout, &mut self.state) + .map_err(std::io::Error::from) + { + break error.raw_os_error().unwrap_or(1); } - events.clear(); if control_flow == ControlFlow::Wait { // We don't go straight into executing the event loop iteration, we instead go // to the start of this loop and check again if there's any pending event. We // must do this because during the execution of the iteration we sometimes wake - // the mio waker, and if the waker is already awaken before we call poll(), + // the calloop waker, and if the waker is already awaken before we call poll(), // then poll doesn't block, but it returns immediately. This caused the event // loop to run continuously even if the control_flow was `Wait` continue; @@ -544,8 +547,7 @@ impl EventLoop { control_flow, &mut |event, window_target, control_flow| { if let Event::RedrawRequested(crate::window::WindowId(wid)) = event { - wt.redraw_sender.sender.send(wid).unwrap(); - wt.redraw_sender.waker.wake().unwrap(); + wt.redraw_sender.send(wid).unwrap(); } else { callback(event, window_target, control_flow); } @@ -608,7 +610,6 @@ impl EventLoopProxy { self.user_sender .send(event) .map_err(|e| EventLoopClosed(e.0)) - .map(|_| self.waker.wake().unwrap()) } } diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index c651ff8c..adbcf34e 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -27,9 +27,9 @@ use crate::{ }; use super::{ - ffi, util, EventLoopWindowTarget, ImeRequest, ImeSender, WakeSender, WindowId, XConnection, - XError, + ffi, util, EventLoopWindowTarget, ImeRequest, ImeSender, WindowId, XConnection, XError, }; +use calloop::channel::Sender; #[derive(Debug)] pub struct SharedState { @@ -114,7 +114,7 @@ pub(crate) struct UnownedWindow { cursor_visible: Mutex, ime_sender: Mutex, pub shared_state: Mutex, - redraw_sender: WakeSender, + redraw_sender: Sender, } impl UnownedWindow { @@ -289,10 +289,7 @@ impl UnownedWindow { cursor_visible: Mutex::new(true), ime_sender: Mutex::new(event_loop.ime_sender.clone()), shared_state: SharedState::new(guessed_monitor, &window_attrs), - redraw_sender: WakeSender { - waker: event_loop.redraw_sender.waker.clone(), - sender: event_loop.redraw_sender.sender.clone(), - }, + redraw_sender: event_loop.redraw_sender.clone(), }; // Title must be set before mapping. Some tiling window managers (i.e. i3) use the window @@ -1594,10 +1591,8 @@ impl UnownedWindow { #[inline] pub fn request_redraw(&self) { self.redraw_sender - .sender .send(WindowId(self.xwindow as _)) .unwrap(); - self.redraw_sender.waker.wake().unwrap(); } #[inline]