diff --git a/crates/gpui/src/platform/linux/x11/client.rs b/crates/gpui/src/platform/linux/x11/client.rs index f2ad5b9a86..cbae79a8d2 100644 --- a/crates/gpui/src/platform/linux/x11/client.rs +++ b/crates/gpui/src/platform/linux/x11/client.rs @@ -13,7 +13,6 @@ 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 _}; @@ -46,7 +45,7 @@ use crate::platform::linux::xdg_desktop_portal::{Event as XDPEvent, XDPEventSour pub(super) const XINPUT_MASTER_DEVICE: u16 = 1; pub(crate) struct WindowRef { - window: X11WindowStatePtr, + pub window: X11WindowStatePtr, refresh_event_token: RegistrationToken, } @@ -292,13 +291,39 @@ impl X11Client { .insert_source( Generic::new_with_error::( fd, - calloop::Interest::READ, + calloop::Interest::BOTH, calloop::Mode::Level, ), { let xcb_connection = xcb_connection.clone(); move |_readiness, _, client| { + let windows = client + .0 + .borrow() + .windows + .values() + .map(|window_ref| window_ref.window.clone()) + .collect::>(); + while let Some(event) = xcb_connection.poll_for_event()? { + for window in &windows { + let last_render_at; + let refresh_rate; + { + let window_state = window.state.borrow(); + last_render_at = window_state.last_render_at; + refresh_rate = window_state.refresh_rate; + } + + if let Some(last_render_at) = last_render_at { + if last_render_at.elapsed() >= refresh_rate { + window.refresh(); + } + } else { + window.refresh(); + } + } + let mut state = client.0.borrow_mut(); if state.ximc.is_none() || state.xim_handler.is_none() { drop(state); @@ -955,60 +980,18 @@ 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 state = client.0.borrow_mut(); - state - .xcb_connection - .send_event( - false, - x_window, - xproto::EventMask::EXPOSURE, - xproto::ExposeEvent { - response_type: xproto::EXPOSE_EVENT, - sequence: 0, - window: x_window, - x: 0, - y: 0, - width: 0, - height: 0, - count: 1, - }, - ) - .unwrap(); - let _ = state.xcb_connection.flush().unwrap(); + let window = window.0.clone(); + let refresh_rate = window.state.borrow().refresh_rate; + move |mut instant, (), _| { + window.refresh(); + // Take into account that some frames have been skipped let now = Instant::now(); while instant < now { - instant += refresh_duration; + instant += refresh_rate; } calloop::timer::TimeoutAction::ToInstant(instant) } @@ -1146,15 +1129,6 @@ 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 { - 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 c7c8cf0708..0d0404bd93 100644 --- a/crates/gpui/src/platform/linux/x11/window.rs +++ b/crates/gpui/src/platform/linux/x11/window.rs @@ -14,6 +14,7 @@ use util::{maybe, ResultExt}; use x11rb::{ connection::Connection, protocol::{ + randr::{self, ConnectionExt as _}, xinput::{self, ConnectionExt as _}, xproto::{ self, ClientMessageEvent, ConnectionExt as _, EventMask, TranslateCoordinatesReply, @@ -31,6 +32,7 @@ use std::{ ptr::NonNull, rc::Rc, sync::{self, Arc}, + time::{Duration, Instant}, }; use super::{X11Display, XINPUT_MASTER_DEVICE}; @@ -159,6 +161,8 @@ pub struct Callbacks { pub struct X11WindowState { pub destroyed: bool, + pub last_render_at: Option, + pub refresh_rate: Duration, client: X11ClientStatePtr, executor: ForegroundExecutor, atoms: XcbAtoms, @@ -389,6 +393,31 @@ 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, @@ -405,6 +434,8 @@ impl X11WindowState { appearance, handle, destroyed: false, + last_render_at: None, + refresh_rate, }) } @@ -574,6 +605,11 @@ impl X11WindowStatePtr { let mut cb = self.callbacks.borrow_mut(); if let Some(ref mut fun) = cb.request_frame { fun(); + + self.state + .borrow_mut() + .last_render_at + .replace(Instant::now()); } } @@ -1020,3 +1056,12 @@ impl PlatformWindow for X11Window { false } } + +// 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 { + 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) +}