x11 calloop 2 (#13955)

Conrad Irwin and Max created

Release Notes:

- N/A

---------

Co-authored-by: Max <max@zed.dev>

Change summary

Cargo.lock                                           |  20 
crates/gpui/Cargo.toml                               |   1 
crates/gpui/src/platform/linux/dispatcher.rs         |   9 
crates/gpui/src/platform/linux/headless/client.rs    |   2 
crates/gpui/src/platform/linux/platform.rs           |  24 
crates/gpui/src/platform/linux/wayland/client.rs     |   6 
crates/gpui/src/platform/linux/x11/client.rs         | 570 ++++++-------
crates/gpui/src/platform/linux/x11/window.rs         |  49 -
crates/gpui/src/platform/linux/xdg_desktop_portal.rs |  52 
9 files changed, 289 insertions(+), 444 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -4888,7 +4888,6 @@ dependencies = [
  "log",
  "media",
  "metal",
- "mio 1.0.0",
  "num_cpus",
  "objc",
  "oo7",
@@ -5689,7 +5688,7 @@ dependencies = [
  "fnv",
  "lazy_static",
  "libc",
- "mio 0.8.11",
+ "mio",
  "rand 0.8.5",
  "serde",
  "tempfile",
@@ -6619,19 +6618,6 @@ dependencies = [
  "windows-sys 0.48.0",
 ]
 
-[[package]]
-name = "mio"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4929e1f84c5e54c3ec6141cd5d8b5a5c055f031f80cf78f2072920173cb4d880"
-dependencies = [
- "hermit-abi 0.3.9",
- "libc",
- "log",
- "wasi 0.11.0+wasi-snapshot-preview1",
- "windows-sys 0.52.0",
-]
-
 [[package]]
 name = "miow"
 version = "0.6.0"
@@ -6870,7 +6856,7 @@ dependencies = [
  "kqueue",
  "libc",
  "log",
- "mio 0.8.11",
+ "mio",
  "walkdir",
  "windows-sys 0.48.0",
 ]
@@ -11080,7 +11066,7 @@ dependencies = [
  "backtrace",
  "bytes 1.5.0",
  "libc",
- "mio 0.8.11",
+ "mio",
  "num_cpus",
  "parking_lot",
  "pin-project-lite",

crates/gpui/Cargo.toml 🔗

@@ -141,7 +141,6 @@ xim = { git = "https://github.com/npmania/xim-rs", rev = "27132caffc5b9bc9c432ca
 ] }
 font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "5a5c4d4", features = ["source-fontconfig-dlopen"] }
 x11-clipboard = "0.9.2"
-mio = { version = "1.0.0", features = ["os-poll", "os-ext"] }
 
 [target.'cfg(windows)'.dependencies]
 windows.workspace = true

crates/gpui/src/platform/linux/dispatcher.rs 🔗

@@ -5,11 +5,9 @@ use calloop::{
     timer::TimeoutAction,
     EventLoop,
 };
-use mio::Waker;
 use parking::{Parker, Unparker};
 use parking_lot::Mutex;
 use std::{
-    sync::Arc,
     thread,
     time::{Duration, Instant},
 };
@@ -23,7 +21,6 @@ struct TimerAfter {
 pub(crate) struct LinuxDispatcher {
     parker: Mutex<Parker>,
     main_sender: Sender<Runnable>,
-    main_waker: Option<Arc<Waker>>,
     timer_sender: Sender<TimerAfter>,
     background_sender: flume::Sender<Runnable>,
     _background_threads: Vec<thread::JoinHandle<()>>,
@@ -31,7 +28,7 @@ pub(crate) struct LinuxDispatcher {
 }
 
 impl LinuxDispatcher {
-    pub fn new(main_sender: Sender<Runnable>, main_waker: Option<Arc<Waker>>) -> Self {
+    pub fn new(main_sender: Sender<Runnable>) -> Self {
         let (background_sender, background_receiver) = flume::unbounded::<Runnable>();
         let thread_count = std::thread::available_parallelism()
             .map(|i| i.get())
@@ -91,7 +88,6 @@ impl LinuxDispatcher {
         Self {
             parker: Mutex::new(Parker::new()),
             main_sender,
-            main_waker,
             timer_sender,
             background_sender,
             _background_threads: background_threads,
@@ -111,9 +107,6 @@ impl PlatformDispatcher for LinuxDispatcher {
 
     fn dispatch_on_main_thread(&self, runnable: Runnable) {
         self.main_sender.send(runnable).ok();
-        if let Some(main_waker) = self.main_waker.as_ref() {
-            main_waker.wake().ok();
-        }
     }
 
     fn dispatch_after(&self, duration: Duration, runnable: Runnable) {

crates/gpui/src/platform/linux/headless/client.rs 🔗

@@ -22,7 +22,7 @@ impl HeadlessClient {
     pub(crate) fn new() -> Self {
         let event_loop = EventLoop::try_new().unwrap();
 
-        let (common, main_receiver) = LinuxCommon::new(Box::new(event_loop.get_signal()), None);
+        let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal());
 
         let handle = event_loop.handle();
 

crates/gpui/src/platform/linux/platform.rs 🔗

@@ -28,7 +28,6 @@ use calloop::{EventLoop, LoopHandle, LoopSignal};
 use filedescriptor::FileDescriptor;
 use flume::{Receiver, Sender};
 use futures::channel::oneshot;
-use mio::Waker;
 use parking_lot::Mutex;
 use time::UtcOffset;
 use util::ResultExt;
@@ -88,16 +87,6 @@ pub(crate) struct PlatformHandlers {
     pub(crate) validate_app_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
 }
 
-pub trait QuitSignal {
-    fn quit(&mut self);
-}
-
-impl QuitSignal for LoopSignal {
-    fn quit(&mut self) {
-        self.stop();
-    }
-}
-
 pub(crate) struct LinuxCommon {
     pub(crate) background_executor: BackgroundExecutor,
     pub(crate) foreground_executor: ForegroundExecutor,
@@ -105,20 +94,17 @@ pub(crate) struct LinuxCommon {
     pub(crate) appearance: WindowAppearance,
     pub(crate) auto_hide_scrollbars: bool,
     pub(crate) callbacks: PlatformHandlers,
-    pub(crate) quit_signal: Box<dyn QuitSignal>,
+    pub(crate) signal: LoopSignal,
     pub(crate) menus: Vec<OwnedMenu>,
 }
 
 impl LinuxCommon {
-    pub fn new(
-        quit_signal: Box<dyn QuitSignal>,
-        main_waker: Option<Arc<Waker>>,
-    ) -> (Self, Channel<Runnable>) {
+    pub fn new(signal: LoopSignal) -> (Self, Channel<Runnable>) {
         let (main_sender, main_receiver) = calloop::channel::channel::<Runnable>();
         let text_system = Arc::new(CosmicTextSystem::new());
         let callbacks = PlatformHandlers::default();
 
-        let dispatcher = Arc::new(LinuxDispatcher::new(main_sender.clone(), main_waker));
+        let dispatcher = Arc::new(LinuxDispatcher::new(main_sender.clone()));
 
         let background_executor = BackgroundExecutor::new(dispatcher.clone());
 
@@ -129,7 +115,7 @@ impl LinuxCommon {
             appearance: WindowAppearance::Light,
             auto_hide_scrollbars: false,
             callbacks,
-            quit_signal,
+            signal,
             menus: Vec::new(),
         };
 
@@ -163,7 +149,7 @@ impl<P: LinuxClient + 'static> Platform for P {
     }
 
     fn quit(&self) {
-        self.with_common(|common| common.quit_signal.quit());
+        self.with_common(|common| common.signal.stop());
     }
 
     fn compositor_name(&self) -> &'static str {

crates/gpui/src/platform/linux/wayland/client.rs 🔗

@@ -326,7 +326,7 @@ impl WaylandClientStatePtr {
             }
         }
         if state.windows.is_empty() {
-            state.common.quit_signal.quit();
+            state.common.signal.stop();
         }
     }
 }
@@ -422,7 +422,7 @@ impl WaylandClient {
 
         let event_loop = EventLoop::<WaylandClientStatePtr>::try_new().unwrap();
 
-        let (common, main_receiver) = LinuxCommon::new(Box::new(event_loop.get_signal()), None);
+        let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal());
 
         let handle = event_loop.handle();
         handle
@@ -459,7 +459,7 @@ impl WaylandClient {
         let mut cursor = Cursor::new(&conn, &globals, 24);
 
         handle
-            .insert_source(XDPEventSource::new(&common.background_executor, None), {
+            .insert_source(XDPEventSource::new(&common.background_executor), {
                 move |event, _, client| match event {
                     XDPEvent::WindowAppearance(appearance) => {
                         if let Some(client) = client.0.upgrade() {

crates/gpui/src/platform/linux/x11/client.rs 🔗

@@ -1,27 +1,23 @@
 use std::cell::RefCell;
 use std::collections::HashSet;
 use std::ops::Deref;
-use std::os::fd::AsRawFd;
 use std::path::PathBuf;
 use std::rc::{Rc, Weak};
-use std::sync::Arc;
 use std::time::{Duration, Instant};
 
-use anyhow::Context;
-use async_task::Runnable;
-use calloop::channel::Channel;
+use calloop::generic::{FdWrapper, Generic};
+use calloop::{EventLoop, LoopHandle, RegistrationToken};
 
 use collections::HashMap;
-
-use futures::channel::oneshot;
-use mio::{Interest, Token, Waker};
 use util::ResultExt;
+
 use x11rb::connection::{Connection, RequestConnection};
 use x11rb::cursor;
 use x11rb::errors::ConnectionError;
+use x11rb::protocol::randr::ConnectionExt as _;
 use x11rb::protocol::xinput::ConnectionExt;
 use x11rb::protocol::xkb::ConnectionExt as _;
-use x11rb::protocol::xproto::{ChangeWindowAttributesAux, ConnectionExt as _};
+use x11rb::protocol::xproto::{ChangeWindowAttributesAux, ConnectionExt as _, KeyPressEvent};
 use x11rb::protocol::{randr, render, xinput, xkb, xproto, Event};
 use x11rb::resource_manager::Database;
 use x11rb::xcb_ffi::XCBConnection;
@@ -35,7 +31,7 @@ use crate::platform::{LinuxCommon, PlatformWindow};
 use crate::{
     modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, ClipboardItem, CursorStyle,
     DisplayId, Keystroke, Modifiers, ModifiersChangedEvent, Pixels, Platform, PlatformDisplay,
-    PlatformInput, Point, QuitSignal, ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
+    PlatformInput, Point, ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
 };
 
 use super::{button_of_key, modifiers_from_state, pressed_button_from_mask};
@@ -51,6 +47,7 @@ pub(super) const XINPUT_MASTER_DEVICE: u16 = 1;
 
 pub(crate) struct WindowRef {
     window: X11WindowStatePtr,
+    refresh_event_token: RegistrationToken,
 }
 
 impl WindowRef {
@@ -98,18 +95,15 @@ impl From<xim::ClientError> for EventHandlerError {
 }
 
 pub struct X11ClientState {
-    /// poll is in an Option so we can take it out in `run()` without
-    /// mutating self.
-    poll: Option<mio::Poll>,
-    quit_signal_rx: oneshot::Receiver<()>,
-    runnables: Channel<Runnable>,
-    xdp_event_source: XDPEventSource,
+    pub(crate) loop_handle: LoopHandle<'static, X11Client>,
+    pub(crate) event_loop: Option<calloop::EventLoop<'static, X11Client>>,
 
     pub(crate) last_click: Instant,
     pub(crate) last_location: Point<Pixels>,
     pub(crate) current_count: usize,
 
     pub(crate) scale_factor: f32,
+
     pub(crate) xcb_connection: Rc<XCBConnection>,
     pub(crate) x_root_index: usize,
     pub(crate) _resource_database: Database,
@@ -146,11 +140,8 @@ impl X11ClientStatePtr {
         let client = X11Client(self.0.upgrade().expect("client already dropped"));
         let mut state = client.0.borrow_mut();
 
-        if state.windows.remove(&x_window).is_none() {
-            log::warn!(
-                "failed to remove X window {} from client state, does not exist",
-                x_window
-            );
+        if let Some(window_ref) = state.windows.remove(&x_window) {
+            state.loop_handle.remove(window_ref.refresh_event_token);
         }
         if state.mouse_focused_window == Some(x_window) {
             state.mouse_focused_window = None;
@@ -161,36 +152,7 @@ impl X11ClientStatePtr {
         state.cursor_styles.remove(&x_window);
 
         if state.windows.is_empty() {
-            state.common.quit_signal.quit();
-        }
-    }
-}
-
-struct ChannelQuitSignal {
-    tx: Option<oneshot::Sender<()>>,
-    waker: Option<Arc<Waker>>,
-}
-
-impl ChannelQuitSignal {
-    fn new(waker: Option<Arc<Waker>>) -> (Self, oneshot::Receiver<()>) {
-        let (tx, rx) = oneshot::channel::<()>();
-
-        let quit_signal = ChannelQuitSignal {
-            tx: Some(tx),
-            waker,
-        };
-
-        (quit_signal, rx)
-    }
-}
-
-impl QuitSignal for ChannelQuitSignal {
-    fn quit(&mut self) {
-        if let Some(tx) = self.tx.take() {
-            tx.send(()).log_err();
-            if let Some(waker) = self.waker.as_ref() {
-                waker.wake().ok();
-            }
+            state.common.signal.stop();
         }
     }
 }
@@ -200,12 +162,27 @@ pub(crate) struct X11Client(Rc<RefCell<X11ClientState>>);
 
 impl X11Client {
     pub(crate) fn new() -> Self {
-        let mut poll = mio::Poll::new().unwrap();
-
-        let waker = Arc::new(Waker::new(poll.registry(), WAKER_TOKEN).unwrap());
-
-        let (quit_signal, quit_signal_rx) = ChannelQuitSignal::new(Some(waker.clone()));
-        let (common, runnables) = LinuxCommon::new(Box::new(quit_signal), Some(waker.clone()));
+        let event_loop = EventLoop::try_new().unwrap();
+
+        let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal());
+
+        let handle = event_loop.handle();
+
+        handle
+            .insert_source(main_receiver, {
+                let handle = handle.clone();
+                move |event, _, _: &mut X11Client| {
+                    if let calloop::channel::Event::Msg(runnable) = event {
+                        // Insert the runnables as idle callbacks, so we make sure that user-input and X11
+                        // events have higher priority and runnables are only worked off after the event
+                        // callbacks.
+                        handle.insert_idle(|_| {
+                            runnable.run();
+                        });
+                    }
+                }
+            })
+            .unwrap();
 
         let (xcb_connection, x_root_index) = XCBConnection::connect(None).unwrap();
         xcb_connection
@@ -304,18 +281,47 @@ impl X11Client {
             None
         };
 
-        let xdp_event_source =
-            XDPEventSource::new(&common.background_executor, Some(waker.clone()));
-
-        X11Client(Rc::new(RefCell::new(X11ClientState {
-            poll: Some(poll),
-            runnables,
+        // Safety: Safe if xcb::Connection always returns a valid fd
+        let fd = unsafe { FdWrapper::new(Rc::clone(&xcb_connection)) };
+
+        handle
+            .insert_source(
+                Generic::new_with_error::<EventHandlerError>(
+                    fd,
+                    calloop::Interest::READ,
+                    calloop::Mode::Level,
+                ),
+                {
+                    let xcb_connection = xcb_connection.clone();
+                    move |_readiness, _, client| {
+                        client.process_x11_events(&xcb_connection)?;
+                        Ok(calloop::PostAction::Continue)
+                    }
+                },
+            )
+            .expect("Failed to initialize x11 event source");
 
-            xdp_event_source,
-            quit_signal_rx,
-            common,
+        handle
+            .insert_source(XDPEventSource::new(&common.background_executor), {
+                move |event, _, client| match event {
+                    XDPEvent::WindowAppearance(appearance) => {
+                        client.with_common(|common| common.appearance = appearance);
+                        for (_, window) in &mut client.0.borrow_mut().windows {
+                            window.window.set_appearance(appearance);
+                        }
+                    }
+                    XDPEvent::CursorTheme(_) | XDPEvent::CursorSize(_) => {
+                        // noop, X11 manages this for us.
+                    }
+                }
+            })
+            .unwrap();
 
+        X11Client(Rc::new(RefCell::new(X11ClientState {
             modifiers: Modifiers::default(),
+            event_loop: Some(event_loop),
+            loop_handle: handle,
+            common,
             last_click: Instant::now(),
             last_location: Point::new(px(0.0), px(0.0)),
             current_count: 0,
@@ -349,6 +355,125 @@ impl X11Client {
         })))
     }
 
+    pub fn process_x11_events(
+        &self,
+        xcb_connection: &XCBConnection,
+    ) -> Result<(), EventHandlerError> {
+        loop {
+            let mut events = Vec::new();
+            let mut windows_to_refresh = HashSet::new();
+
+            let mut last_key_release = None;
+            let mut last_key_press: Option<KeyPressEvent> = None;
+
+            loop {
+                match xcb_connection.poll_for_event() {
+                    Ok(Some(event)) => {
+                        match event {
+                            Event::Expose(expose_event) => {
+                                windows_to_refresh.insert(expose_event.window);
+                            }
+                            Event::KeyRelease(_) => {
+                                last_key_release = Some(event);
+                            }
+                            Event::KeyPress(key_press) => {
+                                if let Some(last_press) = last_key_press.as_ref() {
+                                    if last_press.detail == key_press.detail {
+                                        continue;
+                                    }
+                                }
+
+                                if let Some(Event::KeyRelease(key_release)) =
+                                    last_key_release.take()
+                                {
+                                    // We ignore that last KeyRelease if it's too close to this KeyPress,
+                                    // suggesting that it's auto-generated by X11 as a key-repeat event.
+                                    if key_release.detail != key_press.detail
+                                        || key_press.time.saturating_sub(key_release.time) > 20
+                                    {
+                                        events.push(Event::KeyRelease(key_release));
+                                    }
+                                }
+                                events.push(Event::KeyPress(key_press));
+                                last_key_press = Some(key_press);
+                            }
+                            _ => {
+                                if let Some(release_event) = last_key_release.take() {
+                                    events.push(release_event);
+                                }
+                                events.push(event);
+                            }
+                        }
+                    }
+                    Ok(None) => {
+                        // Add any remaining stored KeyRelease event
+                        if let Some(release_event) = last_key_release.take() {
+                            events.push(release_event);
+                        }
+                        break;
+                    }
+                    Err(e) => {
+                        log::warn!("error polling for X11 events: {e:?}");
+                        break;
+                    }
+                }
+            }
+
+            if events.is_empty() && windows_to_refresh.is_empty() {
+                break;
+            }
+
+            for window in windows_to_refresh.into_iter() {
+                if let Some(window) = self.get_window(window) {
+                    window.refresh();
+                }
+            }
+
+            for event in events.into_iter() {
+                let mut state = self.0.borrow_mut();
+                if state.ximc.is_none() || state.xim_handler.is_none() {
+                    drop(state);
+                    self.handle_event(event);
+                    continue;
+                }
+
+                let mut ximc = state.ximc.take().unwrap();
+                let mut xim_handler = state.xim_handler.take().unwrap();
+                let xim_connected = xim_handler.connected;
+                drop(state);
+
+                let xim_filtered = match ximc.filter_event(&event, &mut xim_handler) {
+                    Ok(handled) => handled,
+                    Err(err) => {
+                        log::error!("XIMClientError: {}", err);
+                        false
+                    }
+                };
+                let xim_callback_event = xim_handler.last_callback_event.take();
+
+                let mut state = self.0.borrow_mut();
+                state.ximc = Some(ximc);
+                state.xim_handler = Some(xim_handler);
+                drop(state);
+
+                if let Some(event) = xim_callback_event {
+                    self.handle_xim_callback_event(event);
+                }
+
+                if xim_filtered {
+                    continue;
+                }
+
+                if xim_connected {
+                    self.xim_handle_event(event);
+                } else {
+                    self.handle_event(event);
+                }
+            }
+        }
+        Ok(())
+    }
+
     pub fn enable_ime(&self) {
         let mut state = self.0.borrow_mut();
         if state.ximc.is_none() {
@@ -411,117 +536,6 @@ impl X11Client {
             .map(|window_reference| window_reference.window.clone())
     }
 
-    fn read_x11_events(&self) -> (HashSet<u32>, Vec<Event>) {
-        let mut events = Vec::new();
-        let mut windows_to_refresh = HashSet::new();
-        let mut state = self.0.borrow_mut();
-
-        let mut last_key_release: Option<Event> = None;
-
-        loop {
-            match state.xcb_connection.poll_for_event() {
-                Ok(Some(event)) => {
-                    if let Event::Expose(expose_event) = event {
-                        windows_to_refresh.insert(expose_event.window);
-                    } else {
-                        match event {
-                            Event::KeyRelease(_) => {
-                                last_key_release = Some(event);
-                            }
-                            Event::KeyPress(key_press) => {
-                                if let Some(Event::KeyRelease(key_release)) =
-                                    last_key_release.take()
-                                {
-                                    // We ignore that last KeyRelease if it's too close to this KeyPress,
-                                    // suggesting that it's auto-generated by X11 as a key-repeat event.
-                                    if key_release.detail != key_press.detail
-                                        || key_press.time.wrapping_sub(key_release.time) > 20
-                                    {
-                                        events.push(Event::KeyRelease(key_release));
-                                    }
-                                }
-                                events.push(Event::KeyPress(key_press));
-                            }
-                            _ => {
-                                if let Some(release_event) = last_key_release.take() {
-                                    events.push(release_event);
-                                }
-                                events.push(event);
-                            }
-                        }
-                    }
-                }
-                Ok(None) => {
-                    // Add any remaining stored KeyRelease event
-                    if let Some(release_event) = last_key_release.take() {
-                        events.push(release_event);
-                    }
-                    break;
-                }
-                Err(e) => {
-                    log::warn!("error polling for X11 events: {e:?}");
-                    break;
-                }
-            }
-        }
-
-        (windows_to_refresh, events)
-    }
-
-    fn process_x11_events(&self, events: Vec<Event>) {
-        log::trace!(
-            "main thread: processing X11 events. events: {}",
-            events.len()
-        );
-
-        for event in events.into_iter() {
-            log::trace!("main thread: processing X11 event: {:?}", event);
-
-            let mut state = self.0.borrow_mut();
-            if state.ximc.is_none() || state.xim_handler.is_none() {
-                drop(state);
-                self.handle_event(event);
-                continue;
-            }
-
-            let mut ximc = state.ximc.take().unwrap();
-            let mut xim_handler = state.xim_handler.take().unwrap();
-            let xim_connected = xim_handler.connected;
-            drop(state);
-
-            // let xim_filtered = false;
-            let xim_filtered = match ximc.filter_event(&event, &mut xim_handler) {
-                Ok(handled) => handled,
-                Err(err) => {
-                    log::error!("XIMClientError: {}", err);
-                    false
-                }
-            };
-            let xim_callback_event = xim_handler.last_callback_event.take();
-
-            let mut state = self.0.borrow_mut();
-            state.ximc = Some(ximc);
-            state.xim_handler = Some(xim_handler);
-
-            if let Some(event) = xim_callback_event {
-                drop(state);
-                self.handle_xim_callback_event(event);
-            } else {
-                drop(state);
-            }
-
-            if xim_filtered {
-                continue;
-            }
-
-            if xim_connected {
-                self.xim_handle_event(event);
-            } else {
-                self.handle_event(event);
-            }
-        }
-    }
-
     fn handle_event(&self, event: Event) -> Option<()> {
         match event {
             Event::ClientMessage(event) => {
@@ -561,10 +575,6 @@ impl X11Client {
                 let window = self.get_window(event.window)?;
                 window.property_notify(event);
             }
-            Event::Expose(event) => {
-                let window = self.get_window(event.window)?;
-                window.refresh();
-            }
             Event::FocusIn(event) if event.mode == xproto::NotifyMode::NORMAL => {
                 let window = self.get_window(event.event)?;
                 window.set_focused(true);
@@ -979,13 +989,11 @@ impl X11Client {
     }
 }
 
-const XCB_CONNECTION_TOKEN: Token = Token(0);
-const WAKER_TOKEN: Token = Token(1);
-
 impl LinuxClient for X11Client {
     fn compositor_name(&self) -> &'static str {
         "X11"
     }
+
     fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R {
         f(&mut self.0.borrow_mut().common)
     }
@@ -1051,8 +1059,61 @@ impl LinuxClient for X11Client {
             state.common.appearance,
         )?;
 
+        let screen_resources = state
+            .xcb_connection
+            .randr_get_screen_resources(x_window)
+            .unwrap()
+            .reply()
+            .expect("Could not find available screens");
+
+        let mode = screen_resources
+            .crtcs
+            .iter()
+            .find_map(|crtc| {
+                let crtc_info = state
+                    .xcb_connection
+                    .randr_get_crtc_info(*crtc, x11rb::CURRENT_TIME)
+                    .ok()?
+                    .reply()
+                    .ok()?;
+
+                screen_resources
+                    .modes
+                    .iter()
+                    .find(|m| m.id == crtc_info.mode)
+            })
+            .expect("Unable to find screen refresh rate");
+
+        let refresh_event_token = state
+            .loop_handle
+            .insert_source(calloop::timer::Timer::immediate(), {
+                let refresh_duration = mode_refresh_rate(mode);
+                move |mut instant, (), client| {
+                    let xcb_connection = {
+                        let state = client.0.borrow_mut();
+                        let xcb_connection = state.xcb_connection.clone();
+                        if let Some(window) = state.windows.get(&x_window) {
+                            let window = window.window.clone();
+                            drop(state);
+                            window.refresh();
+                        }
+                        xcb_connection
+                    };
+                    client.process_x11_events(&xcb_connection).log_err();
+
+                    // Take into account that some frames have been skipped
+                    let now = Instant::now();
+                    while instant < now {
+                        instant += refresh_duration;
+                    }
+                    calloop::timer::TimeoutAction::ToInstant(instant)
+                }
+            })
+            .expect("Failed to initialize refresh timer");
+
         let window_ref = WindowRef {
             window: window.0.clone(),
+            refresh_event_token,
         };
 
         state.windows.insert(x_window, window_ref);
@@ -1181,134 +1242,14 @@ impl LinuxClient for X11Client {
     }
 
     fn run(&self) {
-        let mut poll = self
+        let mut event_loop = self
             .0
             .borrow_mut()
-            .poll
+            .event_loop
             .take()
-            .context("no poll set on X11Client. calling run more than once is not possible")
-            .unwrap();
-
-        let xcb_fd = self.0.borrow().xcb_connection.as_raw_fd();
-        let mut xcb_source = mio::unix::SourceFd(&xcb_fd);
-        poll.registry()
-            .register(&mut xcb_source, XCB_CONNECTION_TOKEN, Interest::READABLE)
-            .unwrap();
-
-        let mut events = mio::Events::with_capacity(1024);
-        let mut next_refresh_needed = Instant::now();
-
-        'run_loop: loop {
-            let poll_timeout = next_refresh_needed - Instant::now();
-            // We rounding the poll_timeout down so `mio` doesn't round it up to the next higher milliseconds
-            let poll_timeout = Duration::from_millis(poll_timeout.as_millis() as u64);
-
-            if poll_timeout >= Duration::from_millis(1) {
-                let _ = poll.poll(&mut events, Some(poll_timeout));
-            };
-
-            let mut state = self.0.borrow_mut();
-
-            // Check if we need to quit
-            if let Ok(Some(())) = state.quit_signal_rx.try_recv() {
-                return;
-            }
-
-            // Redraw windows
-            let now = Instant::now();
-            if now > next_refresh_needed {
-                // This will be pulled down to 16ms (or less) if a window is open
-                let mut frame_length = Duration::from_millis(100);
-
-                let mut windows = vec![];
-                for (_, window_ref) in state.windows.iter() {
-                    if !window_ref.window.state.borrow().destroyed {
-                        frame_length = frame_length.min(window_ref.window.refresh_rate());
-                        windows.push(window_ref.window.clone());
-                    }
-                }
-
-                drop(state);
-
-                for window in windows {
-                    window.refresh();
-                }
-
-                state = self.0.borrow_mut();
-
-                // In the case that we're looping a bit too fast, slow down
-                next_refresh_needed = now.max(next_refresh_needed) + frame_length;
-            }
-
-            // X11 events
-            drop(state);
-
-            loop {
-                let (x_windows, events) = self.read_x11_events();
-                for x_window in x_windows {
-                    if let Some(window) = self.get_window(x_window) {
-                        log::trace!(
-                            "main thread: refreshing window {} due expose event",
-                            window.x_window
-                        );
-                        window.refresh();
-                    }
-                }
-
-                if events.len() == 0 {
-                    break;
-                }
-                self.process_x11_events(events);
-
-                // When X11 is sending us events faster than we can handle we'll
-                // let the frame rate drop to 10fps to try and avoid getting too behind.
-                if Instant::now() > next_refresh_needed + Duration::from_millis(80) {
-                    continue 'run_loop;
-                }
-            }
-
-            state = self.0.borrow_mut();
-
-            // Runnables
-            while let Ok(runnable) = state.runnables.try_recv() {
-                drop(state);
-
-                let start = Instant::now();
-
-                runnable.run();
-
-                log::trace!("main thread: ran runnable. took: {:?}", start.elapsed());
-
-                state = self.0.borrow_mut();
+            .expect("App is already running");
 
-                if Instant::now() + Duration::from_millis(1) >= next_refresh_needed {
-                    continue 'run_loop;
-                }
-            }
-
-            // XDG events
-            if let Ok(event) = state.xdp_event_source.try_recv() {
-                log::trace!("main thread: XDG event");
-                match event {
-                    XDPEvent::WindowAppearance(appearance) => {
-                        let mut windows = state
-                            .windows
-                            .values()
-                            .map(|window| window.window.clone())
-                            .collect::<Vec<_>>();
-                        drop(state);
-
-                        self.with_common(|common| common.appearance = appearance);
-                        for mut window in windows {
-                            window.set_appearance(appearance);
-                        }
-                    }
-                    XDPEvent::CursorTheme(_) | XDPEvent::CursorSize(_) => {
-                        // noop, X11 manages this for us.
-                    }
-                };
-            };
-        }
+        event_loop.run(None, &mut self.clone(), |_| {}).log_err();
     }
 
     fn active_window(&self) -> Option<AnyWindowHandle> {
@@ -1322,6 +1263,19 @@ impl LinuxClient for X11Client {
     }
 }
 
+// Adatpted from:
+// https://docs.rs/winit/0.29.11/src/winit/platform_impl/linux/x11/monitor.rs.html#103-111
+pub fn mode_refresh_rate(mode: &randr::ModeInfo) -> Duration {
+    if mode.dot_clock == 0 || mode.htotal == 0 || mode.vtotal == 0 {
+        return Duration::from_millis(16);
+    }
+
+    let millihertz = mode.dot_clock as u64 * 1_000 / (mode.htotal as u64 * mode.vtotal as u64);
+    let micros = 1_000_000_000 / millihertz;
+    log::info!("Refreshing at {} micros", micros);
+    Duration::from_micros(micros)
+}
+
 fn fp3232_to_f32(value: xinput::Fp3232) -> f32 {
     value.integral as f32 + value.frac as f32 / u32::MAX as f32
 }

crates/gpui/src/platform/linux/x11/window.rs 🔗

@@ -15,7 +15,6 @@ use util::{maybe, ResultExt};
 use x11rb::{
     connection::Connection,
     protocol::{
-        randr::{self, ConnectionExt as _},
         sync,
         xinput::{self, ConnectionExt as _},
         xproto::{self, ClientMessageEvent, ConnectionExt, EventMask, TranslateCoordinatesReply},
@@ -26,7 +25,7 @@ use x11rb::{
 
 use std::{
     cell::RefCell, ffi::c_void, mem::size_of, num::NonZeroU32, ops::Div, ptr::NonNull, rc::Rc,
-    sync::Arc, time::Duration,
+    sync::Arc,
 };
 
 use super::{X11Display, XINPUT_MASTER_DEVICE};
@@ -220,7 +219,6 @@ pub struct Callbacks {
 
 pub struct X11WindowState {
     pub destroyed: bool,
-    refresh_rate: Duration,
     client: X11ClientStatePtr,
     executor: ForegroundExecutor,
     atoms: XcbAtoms,
@@ -257,7 +255,7 @@ pub(crate) struct X11WindowStatePtr {
     pub state: Rc<RefCell<X11WindowState>>,
     pub(crate) callbacks: Rc<RefCell<Callbacks>>,
     xcb_connection: Rc<XCBConnection>,
-    pub x_window: xproto::Window,
+    x_window: xproto::Window,
 }
 
 impl rwh::HasWindowHandle for RawWindow {
@@ -493,31 +491,6 @@ impl X11WindowState {
         };
         xcb_connection.map_window(x_window).unwrap();
 
-        let screen_resources = xcb_connection
-            .randr_get_screen_resources(x_window)
-            .unwrap()
-            .reply()
-            .expect("Could not find available screens");
-
-        let mode = screen_resources
-            .crtcs
-            .iter()
-            .find_map(|crtc| {
-                let crtc_info = xcb_connection
-                    .randr_get_crtc_info(*crtc, x11rb::CURRENT_TIME)
-                    .ok()?
-                    .reply()
-                    .ok()?;
-
-                screen_resources
-                    .modes
-                    .iter()
-                    .find(|m| m.id == crtc_info.mode)
-            })
-            .expect("Unable to find screen refresh rate");
-
-        let refresh_rate = mode_refresh_rate(&mode);
-
         Ok(Self {
             client,
             executor,
@@ -545,7 +518,6 @@ impl X11WindowState {
             edge_constraints: None,
             counter_id: sync_request_counter,
             last_sync_counter: None,
-            refresh_rate,
         })
     }
 
@@ -953,10 +925,6 @@ impl X11WindowStatePtr {
             (fun)()
         }
     }
-
-    pub fn refresh_rate(&self) -> Duration {
-        self.state.borrow().refresh_rate
-    }
 }
 
 impl PlatformWindow for X11Window {
@@ -1369,16 +1337,3 @@ impl PlatformWindow for X11Window {
         }
     }
 }
-
-// Adapted from:
-// https://docs.rs/winit/0.29.11/src/winit/platform_impl/linux/x11/monitor.rs.html#103-111
-pub fn mode_refresh_rate(mode: &randr::ModeInfo) -> Duration {
-    if mode.dot_clock == 0 || mode.htotal == 0 || mode.vtotal == 0 {
-        return Duration::from_millis(16);
-    }
-
-    let millihertz = mode.dot_clock as u64 * 1_000 / (mode.htotal as u64 * mode.vtotal as u64);
-    let micros = 1_000_000_000 / millihertz;
-    log::info!("Refreshing at {} micros", micros);
-    Duration::from_micros(micros)
-}

crates/gpui/src/platform/linux/xdg_desktop_portal.rs 🔗

@@ -2,13 +2,9 @@
 //!
 //! This module uses the [ashpd] crate
 
-use std::sync::Arc;
-
-use anyhow::anyhow;
 use ashpd::desktop::settings::{ColorScheme, Settings};
-use calloop::channel::{Channel, Sender};
+use calloop::channel::Channel;
 use calloop::{EventSource, Poll, PostAction, Readiness, Token, TokenFactory};
-use mio::Waker;
 use smol::stream::StreamExt;
 
 use crate::{BackgroundExecutor, WindowAppearance};
@@ -24,45 +20,31 @@ pub struct XDPEventSource {
 }
 
 impl XDPEventSource {
-    pub fn new(executor: &BackgroundExecutor, waker: Option<Arc<Waker>>) -> Self {
+    pub fn new(executor: &BackgroundExecutor) -> Self {
         let (sender, channel) = calloop::channel::channel();
 
         let background = executor.clone();
 
         executor
             .spawn(async move {
-                fn send_event<T>(
-                    sender: &Sender<T>,
-                    waker: &Option<Arc<Waker>>,
-                    event: T,
-                ) -> Result<(), std::sync::mpsc::SendError<T>> {
-                    sender.send(event)?;
-                    if let Some(waker) = waker {
-                        waker.wake().ok();
-                    };
-                    Ok(())
-                }
-
                 let settings = Settings::new().await?;
 
                 if let Ok(initial_appearance) = settings.color_scheme().await {
-                    send_event(
-                        &sender,
-                        &waker,
-                        Event::WindowAppearance(WindowAppearance::from_native(initial_appearance)),
-                    )?;
+                    sender.send(Event::WindowAppearance(WindowAppearance::from_native(
+                        initial_appearance,
+                    )))?;
                 }
                 if let Ok(initial_theme) = settings
                     .read::<String>("org.gnome.desktop.interface", "cursor-theme")
                     .await
                 {
-                    send_event(&sender, &waker, Event::CursorTheme(initial_theme))?;
+                    sender.send(Event::CursorTheme(initial_theme))?;
                 }
                 if let Ok(initial_size) = settings
                     .read::<u32>("org.gnome.desktop.interface", "cursor-size")
                     .await
                 {
-                    send_event(&sender, &waker, Event::CursorSize(initial_size))?;
+                    sender.send(Event::CursorSize(initial_size))?;
                 }
 
                 if let Ok(mut cursor_theme_changed) = settings
@@ -73,12 +55,11 @@ impl XDPEventSource {
                     .await
                 {
                     let sender = sender.clone();
-                    let waker = waker.clone();
                     background
                         .spawn(async move {
                             while let Some(theme) = cursor_theme_changed.next().await {
                                 let theme = theme?;
-                                send_event(&sender, &waker, Event::CursorTheme(theme))?;
+                                sender.send(Event::CursorTheme(theme))?;
                             }
                             anyhow::Ok(())
                         })
@@ -93,12 +74,11 @@ impl XDPEventSource {
                     .await
                 {
                     let sender = sender.clone();
-                    let waker = waker.clone();
                     background
                         .spawn(async move {
                             while let Some(size) = cursor_size_changed.next().await {
                                 let size = size?;
-                                send_event(&sender, &waker, Event::CursorSize(size))?;
+                                sender.send(Event::CursorSize(size))?;
                             }
                             anyhow::Ok(())
                         })
@@ -107,11 +87,9 @@ impl XDPEventSource {
 
                 let mut appearance_changed = settings.receive_color_scheme_changed().await?;
                 while let Some(scheme) = appearance_changed.next().await {
-                    send_event(
-                        &sender,
-                        &waker,
-                        Event::WindowAppearance(WindowAppearance::from_native(scheme)),
-                    )?;
+                    sender.send(Event::WindowAppearance(WindowAppearance::from_native(
+                        scheme,
+                    )))?;
                 }
 
                 anyhow::Ok(())
@@ -120,12 +98,6 @@ impl XDPEventSource {
 
         Self { channel }
     }
-
-    pub fn try_recv(&self) -> anyhow::Result<Event> {
-        self.channel
-            .try_recv()
-            .map_err(|error| anyhow!("{}", error))
-    }
 }
 
 impl EventSource for XDPEventSource {