From b0ecda63703593a2f83b831411d9ef13eef96604 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 8 Jul 2024 18:38:36 -0600 Subject: [PATCH] x11 calloop 2 (#13955) Release Notes: - N/A --------- Co-authored-by: Max --- Cargo.lock | 20 +- crates/gpui/Cargo.toml | 1 - crates/gpui/src/platform/linux/dispatcher.rs | 9 +- .../src/platform/linux/headless/client.rs | 2 +- crates/gpui/src/platform/linux/platform.rs | 24 +- .../gpui/src/platform/linux/wayland/client.rs | 6 +- crates/gpui/src/platform/linux/x11/client.rs | 566 ++++++++---------- crates/gpui/src/platform/linux/x11/window.rs | 49 +- .../src/platform/linux/xdg_desktop_portal.rs | 52 +- 9 files changed, 287 insertions(+), 442 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6fcac40d20..e4b14b3e3a 100644 --- a/Cargo.lock +++ b/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", ] @@ -11081,7 +11067,7 @@ dependencies = [ "backtrace", "bytes 1.5.0", "libc", - "mio 0.8.11", + "mio", "num_cpus", "parking_lot", "pin-project-lite", diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index b71a10c07f..730c0e69a7 100644 --- a/crates/gpui/Cargo.toml +++ b/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 diff --git a/crates/gpui/src/platform/linux/dispatcher.rs b/crates/gpui/src/platform/linux/dispatcher.rs index 46da60999c..85526340b0 100644 --- a/crates/gpui/src/platform/linux/dispatcher.rs +++ b/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, main_sender: Sender, - main_waker: Option>, timer_sender: Sender, background_sender: flume::Sender, _background_threads: Vec>, @@ -31,7 +28,7 @@ pub(crate) struct LinuxDispatcher { } impl LinuxDispatcher { - pub fn new(main_sender: Sender, main_waker: Option>) -> Self { + pub fn new(main_sender: Sender) -> Self { let (background_sender, background_receiver) = flume::unbounded::(); 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) { diff --git a/crates/gpui/src/platform/linux/headless/client.rs b/crates/gpui/src/platform/linux/headless/client.rs index 7abc19cc4c..c7e50945d2 100644 --- a/crates/gpui/src/platform/linux/headless/client.rs +++ b/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(); diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index ad50290864..f7c8639973 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/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 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, + pub(crate) signal: LoopSignal, pub(crate) menus: Vec, } impl LinuxCommon { - pub fn new( - quit_signal: Box, - main_waker: Option>, - ) -> (Self, Channel) { + pub fn new(signal: LoopSignal) -> (Self, Channel) { let (main_sender, main_receiver) = calloop::channel::channel::(); 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 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 { diff --git a/crates/gpui/src/platform/linux/wayland/client.rs b/crates/gpui/src/platform/linux/wayland/client.rs index 00d2cd540f..0dbe05d1d8 100644 --- a/crates/gpui/src/platform/linux/wayland/client.rs +++ b/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::::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() { diff --git a/crates/gpui/src/platform/linux/x11/client.rs b/crates/gpui/src/platform/linux/x11/client.rs index e867ebeef3..4c9736e8ed 100644 --- a/crates/gpui/src/platform/linux/x11/client.rs +++ b/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 for EventHandlerError { } pub struct X11ClientState { - /// poll is in an Option so we can take it out in `run()` without - /// mutating self. - poll: Option, - quit_signal_rx: oneshot::Receiver<()>, - runnables: Channel, - xdp_event_source: XDPEventSource, + pub(crate) loop_handle: LoopHandle<'static, X11Client>, + pub(crate) event_loop: Option>, pub(crate) last_click: Instant, pub(crate) last_location: Point, pub(crate) current_count: usize, pub(crate) scale_factor: f32, + pub(crate) xcb_connection: Rc, 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>, - waker: Option>, -} - -impl ChannelQuitSignal { - fn new(waker: Option>) -> (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>); impl X11Client { pub(crate) fn new() -> Self { - let mut poll = mio::Poll::new().unwrap(); + let event_loop = EventLoop::try_new().unwrap(); - let waker = Arc::new(Waker::new(poll.registry(), WAKER_TOKEN).unwrap()); + let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal()); - let (quit_signal, quit_signal_rx) = ChannelQuitSignal::new(Some(waker.clone())); - let (common, runnables) = LinuxCommon::new(Box::new(quit_signal), Some(waker.clone())); + 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())); + // 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::( + 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"); + + 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 { - poll: Some(poll), - runnables, - - xdp_event_source, - quit_signal_rx, - common, - 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 = 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, Vec) { - 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 = 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) { - 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(&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(); + .expect("App is already running"); - 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(); - - 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::>(); - 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 { @@ -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 } diff --git a/crates/gpui/src/platform/linux/x11/window.rs b/crates/gpui/src/platform/linux/x11/window.rs index 0f97071155..e4f2ce14f6 100644 --- a/crates/gpui/src/platform/linux/x11/window.rs +++ b/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>, pub(crate) callbacks: Rc>, xcb_connection: Rc, - 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) -} diff --git a/crates/gpui/src/platform/linux/xdg_desktop_portal.rs b/crates/gpui/src/platform/linux/xdg_desktop_portal.rs index 4252f76583..b36e482639 100644 --- a/crates/gpui/src/platform/linux/xdg_desktop_portal.rs +++ b/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>) -> Self { + pub fn new(executor: &BackgroundExecutor) -> Self { let (sender, channel) = calloop::channel::channel(); let background = executor.clone(); executor .spawn(async move { - fn send_event( - sender: &Sender, - waker: &Option>, - event: T, - ) -> Result<(), std::sync::mpsc::SendError> { - 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::("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::("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 { - self.channel - .try_recv() - .map_err(|error| anyhow!("{}", error)) - } } impl EventSource for XDPEventSource {