diff --git a/crates/gpui/src/platform/linux/x11/client.rs b/crates/gpui/src/platform/linux/x11/client.rs index 0d4ba42797decd0150b776235e3ee5207086199f..4b2310026a539b7e24660da101c1ee9b38bd4e2c 100644 --- a/crates/gpui/src/platform/linux/x11/client.rs +++ b/crates/gpui/src/platform/linux/x11/client.rs @@ -28,7 +28,7 @@ use x11rb::{ protocol::xkb::ConnectionExt as _, protocol::xproto::{ AtomEnum, ChangeWindowAttributesAux, ClientMessageData, ClientMessageEvent, - ConnectionExt as _, EventMask, KeyPressEvent, + ConnectionExt as _, EventMask, KeyPressEvent, Visibility, }, protocol::{Event, randr, render, xinput, xkb, xproto}, resource_manager::Database, @@ -78,7 +78,10 @@ pub(crate) const XINPUT_ALL_DEVICE_GROUPS: xinput::DeviceId = 1; pub(crate) struct WindowRef { window: X11WindowStatePtr, - refresh_event_token: RegistrationToken, + refresh_state: Option, + expose_event_received: bool, + last_visibility: Visibility, + is_mapped: bool, } impl WindowRef { @@ -95,6 +98,16 @@ impl Deref for WindowRef { } } +enum RefreshState { + Hidden { + refresh_rate: Duration, + }, + PeriodicRefresh { + refresh_rate: Duration, + event_loop_token: RegistrationToken, + }, +} + #[derive(Debug)] #[non_exhaustive] pub enum EventHandlerError { @@ -220,7 +233,14 @@ impl X11ClientStatePtr { let mut state = client.0.borrow_mut(); if let Some(window_ref) = state.windows.remove(&x_window) { - state.loop_handle.remove(window_ref.refresh_event_token); + match window_ref.refresh_state { + Some(RefreshState::PeriodicRefresh { + event_loop_token, .. + }) => { + state.loop_handle.remove(event_loop_token); + } + _ => {} + } } if state.mouse_focused_window == Some(x_window) { state.mouse_focused_window = None; @@ -550,10 +570,9 @@ impl X11Client { } for window in windows_to_refresh.into_iter() { - if let Some(window) = self.get_window(window) { - window.refresh(RequestFrameOptions { - require_presentation: true, - }); + let mut state = self.0.borrow_mut(); + if let Some(window) = state.windows.get_mut(&window) { + window.expose_event_received = true; } } @@ -661,6 +680,27 @@ impl X11Client { fn handle_event(&self, event: Event) -> Option<()> { match event { + Event::UnmapNotify(event) => { + let mut state = self.0.borrow_mut(); + if let Some(window_ref) = state.windows.get_mut(&event.window) { + window_ref.is_mapped = false; + } + state.update_refresh_loop(event.window); + } + Event::MapNotify(event) => { + let mut state = self.0.borrow_mut(); + if let Some(window_ref) = state.windows.get_mut(&event.window) { + window_ref.is_mapped = true; + } + state.update_refresh_loop(event.window); + } + Event::VisibilityNotify(event) => { + let mut state = self.0.borrow_mut(); + if let Some(window_ref) = state.windows.get_mut(&event.window) { + window_ref.last_visibility = event.state; + } + state.update_refresh_loop(event.window); + } Event::ClientMessage(event) => { let window = self.get_window(event.window)?; let [atom, arg1, arg2, arg3, arg4] = event.data.as_data32(); @@ -1383,61 +1423,12 @@ impl LinuxClient for X11Client { ) .unwrap(); - 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(Default::default()); - } - 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, + refresh_state: None, + expose_event_received: false, + last_visibility: Visibility::UNOBSCURED, + is_mapped: false, }; state.windows.insert(x_window, window_ref); @@ -1618,6 +1609,119 @@ impl LinuxClient for X11Client { } } +impl X11ClientState { + fn update_refresh_loop(&mut self, x_window: xproto::Window) { + let Some(window_ref) = self.windows.get_mut(&x_window) else { + return; + }; + let is_visible = window_ref.is_mapped + && !matches!(window_ref.last_visibility, Visibility::FULLY_OBSCURED); + match (is_visible, window_ref.refresh_state.take()) { + (false, refresh_state @ Some(RefreshState::Hidden { .. })) + | (false, refresh_state @ None) + | (true, refresh_state @ Some(RefreshState::PeriodicRefresh { .. })) => { + window_ref.refresh_state = refresh_state; + } + ( + false, + Some(RefreshState::PeriodicRefresh { + refresh_rate, + event_loop_token, + }), + ) => { + self.loop_handle.remove(event_loop_token); + window_ref.refresh_state = Some(RefreshState::Hidden { refresh_rate }); + } + (true, Some(RefreshState::Hidden { refresh_rate })) => { + let event_loop_token = self.start_refresh_loop(x_window, refresh_rate); + let Some(window_ref) = self.windows.get_mut(&x_window) else { + return; + }; + window_ref.refresh_state = Some(RefreshState::PeriodicRefresh { + refresh_rate, + event_loop_token, + }); + } + (true, None) => { + let screen_resources = self + .xcb_connection + .randr_get_screen_resources_current(x_window) + .unwrap() + .reply() + .expect("Could not find available screens"); + + // Ideally this would be re-queried when the window changes screens, but there + // doesn't seem to be an efficient / straightforward way to do this. Should also be + // updated when screen configurations change. + let refresh_rate = mode_refresh_rate( + screen_resources + .crtcs + .iter() + .find_map(|crtc| { + let crtc_info = self + .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 event_loop_token = self.start_refresh_loop(x_window, refresh_rate); + let Some(window_ref) = self.windows.get_mut(&x_window) else { + return; + }; + window_ref.refresh_state = Some(RefreshState::PeriodicRefresh { + refresh_rate, + event_loop_token, + }); + } + } + } + + #[must_use] + fn start_refresh_loop( + &self, + x_window: xproto::Window, + refresh_rate: Duration, + ) -> RegistrationToken { + self.loop_handle + .insert_source(calloop::timer::Timer::immediate(), { + move |mut instant, (), client| { + let xcb_connection = { + let mut state = client.0.borrow_mut(); + let xcb_connection = state.xcb_connection.clone(); + if let Some(window) = state.windows.get_mut(&x_window) { + let expose_event_received = window.expose_event_received; + window.expose_event_received = false; + let window = window.window.clone(); + drop(state); + window.refresh(RequestFrameOptions { + require_presentation: expose_event_received, + }); + } + 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_rate; + } + calloop::timer::TimeoutAction::ToInstant(instant) + } + }) + .expect("Failed to initialize refresh timer") + } +} + // 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 { diff --git a/crates/gpui/src/platform/linux/x11/window.rs b/crates/gpui/src/platform/linux/x11/window.rs index 288abc4506e20e51fefc218e84a557c07730a294..1e3b6c3c72d40165c5b2606751f585a315805b25 100644 --- a/crates/gpui/src/platform/linux/x11/window.rs +++ b/crates/gpui/src/platform/linux/x11/window.rs @@ -20,7 +20,7 @@ use x11rb::{ protocol::{ sync, xinput::{self, ConnectionExt as _}, - xproto::{self, ClientMessageEvent, ConnectionExt, EventMask, TranslateCoordinatesReply}, + xproto::{self, ClientMessageEvent, ConnectionExt, TranslateCoordinatesReply}, }, wrapper::ConnectionExt as _, xcb_ffi::XCBConnection, @@ -407,7 +407,8 @@ impl X11WindowState { | xproto::EventMask::FOCUS_CHANGE | xproto::EventMask::KEY_PRESS | xproto::EventMask::KEY_RELEASE - | EventMask::PROPERTY_CHANGE, + | xproto::EventMask::PROPERTY_CHANGE + | xproto::EventMask::VISIBILITY_CHANGE, ); let mut bounds = params.bounds.to_device_pixels(scale_factor); @@ -785,7 +786,7 @@ impl X11Window { self.0.xcb.send_event( false, state.x_root_window, - EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY, + xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY, message, ), ) @@ -836,7 +837,7 @@ impl X11Window { self.0.xcb.send_event( false, state.x_root_window, - EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY, + xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY, message, ), )?; @@ -921,7 +922,7 @@ impl X11WindowStatePtr { state.fullscreen = false; state.maximized_vertical = false; state.maximized_horizontal = false; - state.hidden = true; + state.hidden = false; for atom in atoms { if atom == state.atoms._NET_WM_STATE_FOCUSED { @@ -1343,7 +1344,7 @@ impl PlatformWindow for X11Window { self.0.xcb.send_event( false, state.x_root_window, - EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY, + xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY, message, ), ) @@ -1452,7 +1453,7 @@ impl PlatformWindow for X11Window { self.0.xcb.send_event( false, state.x_root_window, - EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY, + xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY, message, ), )