X11: Continuous Presentation (#7762)

Dzmitry Malyshau and Mikayla Maki created

Alternative to #7758, which doesn't involve adding a new trait method
`request_draw`.
Somehow, my whole screen goes blinking black with this when moving the
window, so not ready for landing.

Release Notes:
- N/A

---------

Co-authored-by: Mikayla Maki <mikayla@zed.dev>

Change summary

crates/gpui/Cargo.toml                       |  2 
crates/gpui/src/app.rs                       |  4 ++
crates/gpui/src/gpui.rs                      |  1 
crates/gpui/src/platform/linux.rs            |  1 
crates/gpui/src/platform/linux/platform.rs   |  4 ++
crates/gpui/src/platform/linux/x11/client.rs | 28 +++++++++++--------
crates/gpui/src/platform/linux/x11/window.rs | 24 ++++++++++++++++
crates/live_kit_client/Cargo.toml            |  6 ++--
crates/live_kit_client/src/test.rs           | 31 +++++++++++++++++++--
9 files changed, 78 insertions(+), 23 deletions(-)

Detailed changes

crates/gpui/Cargo.toml 🔗

@@ -96,7 +96,7 @@ objc = "0.2"
 
 [target.'cfg(target_os = "linux")'.dependencies]
 flume = "0.11"
-xcb = { version = "1.3", features = ["as-raw-xcb-connection"] }
+xcb = { version = "1.3", features = ["as-raw-xcb-connection", "present", "randr"] }
 as-raw-xcb-connection = "1"
 #TODO: use these on all platforms
 blade-graphics = { git = "https://github.com/kvark/blade", rev = "c4f951a88b345724cb952e920ad30e39851f7760" }

crates/gpui/src/app.rs 🔗

@@ -111,6 +111,9 @@ impl App {
     /// Builds an app with the given asset source.
     #[allow(clippy::new_without_default)]
     pub fn new() -> Self {
+        #[cfg(any(test, feature = "test-support"))]
+        log::info!("GPUI was compiled in test mode");
+
         Self(AppContext::new(
             current_platform(),
             Arc::new(()),
@@ -676,7 +679,6 @@ impl AppContext {
                     self.update_window(window, |_, cx| cx.draw()).unwrap();
                 }
 
-                #[allow(clippy::collapsible_else_if)]
                 if self.pending_effects.is_empty() {
                     break;
                 }

crates/gpui/src/gpui.rs 🔗

@@ -61,6 +61,7 @@
 
 #![deny(missing_docs)]
 #![allow(clippy::type_complexity)]
+#![allow(clippy::collapsible_else_if)]
 
 #[macro_use]
 mod action;

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

@@ -12,7 +12,6 @@ pub(crate) use blade_atlas::*;
 pub(crate) use dispatcher::*;
 pub(crate) use platform::*;
 pub(crate) use text_system::*;
-pub(crate) use x11::display::*;
 pub(crate) use x11::*;
 
 use blade_belt::*;

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

@@ -64,7 +64,9 @@ impl Default for LinuxPlatform {
 
 impl LinuxPlatform {
     pub(crate) fn new() -> Self {
-        let (xcb_connection, x_root_index) = xcb::Connection::connect(None).unwrap();
+        let (xcb_connection, x_root_index) =
+            xcb::Connection::connect_with_extensions(None, &[xcb::Extension::Present], &[])
+                .unwrap();
         let atoms = XcbAtoms::intern_all(&xcb_connection).unwrap();
 
         let xcb_connection = Arc::new(xcb_connection);

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

@@ -41,6 +41,11 @@ impl X11Client {
             }),
         }
     }
+
+    fn get_window(&self, win: x::Window) -> Rc<X11WindowState> {
+        let state = self.state.lock();
+        Rc::clone(&state.windows[&win])
+    }
 }
 
 impl Client for X11Client {
@@ -58,18 +63,14 @@ impl Client for X11Client {
                             // window "x" button clicked by user, we gracefully exit
                             let window = self.state.lock().windows.remove(&ev.window()).unwrap();
                             window.destroy();
-                            let mut state = self.state.lock();
+                            let state = self.state.lock();
                             self.platform_inner.state.lock().quit_requested |=
                                 state.windows.is_empty();
                         }
                     }
                 }
                 xcb::Event::X(x::Event::Expose(ev)) => {
-                    let window = {
-                        let state = self.state.lock();
-                        Rc::clone(&state.windows[&ev.window()])
-                    };
-                    window.expose();
+                    self.get_window(ev.window()).refresh();
                 }
                 xcb::Event::X(x::Event::ConfigureNotify(ev)) => {
                     let bounds = Bounds {
@@ -82,12 +83,14 @@ impl Client for X11Client {
                             height: ev.height().into(),
                         },
                     };
-                    let window = {
-                        let state = self.state.lock();
-                        Rc::clone(&state.windows[&ev.window()])
-                    };
-                    window.configure(bounds)
+                    self.get_window(ev.window()).configure(bounds)
+                }
+                xcb::Event::Present(xcb::present::Event::CompleteNotify(ev)) => {
+                    let window = self.get_window(ev.window());
+                    window.refresh();
+                    window.request_refresh();
                 }
+                xcb::Event::Present(xcb::present::Event::IdleNotify(_ev)) => {}
                 _ => {}
             }
 
@@ -117,7 +120,7 @@ impl Client for X11Client {
 
     fn open_window(
         &self,
-        handle: AnyWindowHandle,
+        _handle: AnyWindowHandle,
         options: WindowOptions,
     ) -> Box<dyn PlatformWindow> {
         let x_window = self.xcb_connection.generate_id();
@@ -129,6 +132,7 @@ impl Client for X11Client {
             x_window,
             &self.atoms,
         ));
+        window_ptr.request_refresh();
 
         self.state
             .lock()

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

@@ -202,6 +202,16 @@ impl X11WindowState {
             })
             .unwrap();
 
+        let fake_id = xcb_connection.generate_id();
+        xcb_connection
+            .send_and_check_request(&xcb::present::SelectInput {
+                eid: fake_id,
+                window: x_window,
+                //Note: also consider `IDLE_NOTIFY`
+                event_mask: xcb::present::EventMask::COMPLETE_NOTIFY,
+            })
+            .unwrap();
+
         xcb_connection.send_request(&x::MapWindow { window: x_window });
         xcb_connection.flush().unwrap();
 
@@ -258,7 +268,7 @@ impl X11WindowState {
         self.xcb_connection.flush().unwrap();
     }
 
-    pub fn expose(&self) {
+    pub fn refresh(&self) {
         let mut cb = self.callbacks.lock();
         if let Some(ref mut fun) = cb.request_frame {
             fun();
@@ -291,6 +301,18 @@ impl X11WindowState {
             }
         }
     }
+
+    pub fn request_refresh(&self) {
+        self.xcb_connection
+            .send_and_check_request(&xcb::present::NotifyMsc {
+                window: self.x_window,
+                serial: 0,
+                target_msc: 0,
+                divisor: 1,
+                remainder: 0,
+            })
+            .unwrap();
+    }
 }
 
 impl PlatformWindow for X11Window {

crates/live_kit_client/Cargo.toml 🔗

@@ -39,10 +39,10 @@ postage.workspace = true
 [target.'cfg(target_os = "macos")'.dependencies]
 core-foundation = "0.9.3"
 
-[target.'cfg(not(target_os = "macos"))'.dependencies]
+[target.'cfg(all(not(target_os = "macos")))'.dependencies]
 async-trait = { workspace = true }
-collections = { workspace = true, features = ["test-support"] }
-gpui = { workspace = true, features = ["test-support"] }
+collections = { workspace = true }
+gpui = { workspace = true }
 live_kit_server.workspace = true
 nanoid = "0.4"
 

crates/live_kit_client/src/test.rs 🔗

@@ -73,6 +73,8 @@ impl TestServer {
     }
 
     pub async fn create_room(&self, room: String) -> Result<()> {
+        //todo!(linux): Remove this once the cross-platform LiveKit implementation is merged
+        #[cfg(any(test, feature = "test-support"))]
         self.executor.simulate_random_delay().await;
         let mut server_rooms = self.rooms.lock();
         if server_rooms.contains_key(&room) {
@@ -85,6 +87,8 @@ impl TestServer {
 
     async fn delete_room(&self, room: String) -> Result<()> {
         // TODO: clear state associated with all `Room`s.
+        //todo!(linux): Remove this once the cross-platform LiveKit implementation is merged
+        #[cfg(any(test, feature = "test-support"))]
         self.executor.simulate_random_delay().await;
         let mut server_rooms = self.rooms.lock();
         server_rooms
@@ -94,7 +98,10 @@ impl TestServer {
     }
 
     async fn join_room(&self, token: String, client_room: Arc<Room>) -> Result<()> {
+        //todo!(linux): Remove this once the cross-platform LiveKit implementation is merged
+        #[cfg(any(test, feature = "test-support"))]
         self.executor.simulate_random_delay().await;
+
         let claims = live_kit_server::token::validate(&token, &self.secret_key)?;
         let identity = claims.sub.unwrap().to_string();
         let room_name = claims.video.room.unwrap();
@@ -140,6 +147,8 @@ impl TestServer {
     }
 
     async fn leave_room(&self, token: String) -> Result<()> {
+        //todo!(linux): Remove this once the cross-platform LiveKit implementation is merged
+        #[cfg(any(test, feature = "test-support"))]
         self.executor.simulate_random_delay().await;
         let claims = live_kit_server::token::validate(&token, &self.secret_key)?;
         let identity = claims.sub.unwrap().to_string();
@@ -160,8 +169,10 @@ impl TestServer {
 
     async fn remove_participant(&self, room_name: String, identity: String) -> Result<()> {
         // TODO: clear state associated with the `Room`.
-
+        //todo!(linux): Remove this once the cross-platform LiveKit implementation is merged
+        #[cfg(any(test, feature = "test-support"))]
         self.executor.simulate_random_delay().await;
+
         let mut server_rooms = self.rooms.lock();
         let room = server_rooms
             .get_mut(&room_name)
@@ -182,6 +193,8 @@ impl TestServer {
         identity: String,
         permission: proto::ParticipantPermission,
     ) -> Result<()> {
+        //todo!(linux): Remove this once the cross-platform LiveKit implementation is merged
+        #[cfg(any(test, feature = "test-support"))]
         self.executor.simulate_random_delay().await;
         let mut server_rooms = self.rooms.lock();
         let room = server_rooms
@@ -192,6 +205,8 @@ impl TestServer {
     }
 
     pub async fn disconnect_client(&self, client_identity: String) {
+        //todo!(linux): Remove this once the cross-platform LiveKit implementation is merged
+        #[cfg(any(test, feature = "test-support"))]
         self.executor.simulate_random_delay().await;
         let mut server_rooms = self.rooms.lock();
         for room in server_rooms.values_mut() {
@@ -206,6 +221,8 @@ impl TestServer {
         token: String,
         local_track: LocalVideoTrack,
     ) -> Result<Sid> {
+        //todo!(linux): Remove this once the cross-platform LiveKit implementation is merged
+        #[cfg(any(test, feature = "test-support"))]
         self.executor.simulate_random_delay().await;
         let claims = live_kit_server::token::validate(&token, &self.secret_key)?;
         let identity = claims.sub.unwrap().to_string();
@@ -259,7 +276,10 @@ impl TestServer {
         token: String,
         _local_track: &LocalAudioTrack,
     ) -> Result<Sid> {
+        //todo!(linux): Remove this once the cross-platform LiveKit implementation is merged
+        #[cfg(any(test, feature = "test-support"))]
         self.executor.simulate_random_delay().await;
+
         let claims = live_kit_server::token::validate(&token, &self.secret_key)?;
         let identity = claims.sub.unwrap().to_string();
         let room_name = claims.video.room.unwrap();
@@ -539,8 +559,13 @@ impl Room {
     pub fn display_sources(self: &Arc<Self>) -> impl Future<Output = Result<Vec<MacOSDisplay>>> {
         let this = self.clone();
         async move {
-            let server = this.test_server();
-            server.executor.simulate_random_delay().await;
+            //todo!(linux): Remove this once the cross-platform LiveKit implementation is merged
+            #[cfg(any(test, feature = "test-support"))]
+            {
+                let server = this.test_server();
+                server.executor.simulate_random_delay().await;
+            }
+
             Ok(this.0.lock().display_sources.clone())
         }
     }