linux: port from x11rb to xcb and hook up RawWindowHandle

Dzmitry Malyshau created

Change summary

crates/gpui/Cargo.toml                        |   6 
crates/gpui/src/platform/linux/blade_atlas.rs |   1 
crates/gpui/src/platform/linux/display.rs     |  17 +-
crates/gpui/src/platform/linux/platform.rs    | 145 +++++++-----------
crates/gpui/src/platform/linux/text_system.rs |   4 
crates/gpui/src/platform/linux/window.rs      | 157 +++++++++++---------
6 files changed, 162 insertions(+), 168 deletions(-)

Detailed changes

crates/gpui/Cargo.toml 🔗

@@ -36,6 +36,7 @@ env_logger = { version = "0.9", optional = true }
 etagere = "0.2"
 futures.workspace = true
 gpui_macros = { path = "../gpui_macros" }
+font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "d97147f" }
 image = "0.23"
 itertools = "0.10"
 lazy_static.workspace = true
@@ -48,7 +49,7 @@ parking_lot.workspace = true
 pathfinder_geometry = "0.5"
 postage.workspace = true
 rand.workspace = true
-raw-window-handle = "0.6.0"
+raw-window-handle = "0.5.0"
 refineable.workspace = true
 resvg = "0.14"
 schemars.workspace = true
@@ -96,4 +97,5 @@ objc = "0.2"
 
 [target.'cfg(target_os = "linux")'.dependencies]
 flume = "0.11"
-x11rb = "0.13"
+xcb = { version = "1.3", features = ["as-raw-xcb-connection"] }
+as-raw-xcb-connection = "1"

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

@@ -5,7 +5,6 @@ use crate::{
 };
 use anyhow::Result;
 use collections::FxHashMap;
-use derive_more::{Deref, DerefMut};
 use etagere::BucketedAtlasAllocator;
 use parking_lot::Mutex;
 use std::{borrow::Cow, sync::Arc};

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

@@ -1,25 +1,24 @@
-use crate::{point, size, Bounds, DisplayId, GlobalPixels, PlatformDisplay, Size};
+use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Size};
 use anyhow::Result;
 use uuid::Uuid;
-use x11rb::{connection::Connection as _, rust_connection::RustConnection};
 
 #[derive(Debug)]
 pub(crate) struct LinuxDisplay {
-    x11_screen_index: usize,
+    x_screen_index: i32,
     bounds: Bounds<GlobalPixels>,
     uuid: Uuid,
 }
 
 impl LinuxDisplay {
-    pub(crate) fn new(xc: &RustConnection, x11_screen_index: usize) -> Self {
-        let screen = &xc.setup().roots[x11_screen_index];
+    pub(crate) fn new(xc: &xcb::Connection, x_screen_index: i32) -> Self {
+        let screen = xc.get_setup().roots().nth(x_screen_index as usize).unwrap();
         Self {
-            x11_screen_index,
+            x_screen_index,
             bounds: Bounds {
                 origin: Default::default(),
                 size: Size {
-                    width: GlobalPixels(screen.width_in_pixels as f32),
-                    height: GlobalPixels(screen.height_in_pixels as f32),
+                    width: GlobalPixels(screen.width_in_pixels() as f32),
+                    height: GlobalPixels(screen.height_in_pixels() as f32),
                 },
             },
             uuid: Uuid::from_bytes([0; 16]),
@@ -29,7 +28,7 @@ impl LinuxDisplay {
 
 impl PlatformDisplay for LinuxDisplay {
     fn id(&self) -> DisplayId {
-        DisplayId(self.x11_screen_index as u32)
+        DisplayId(self.x_screen_index as u32)
     }
 
     fn uuid(&self) -> Result<Uuid> {

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

@@ -19,48 +19,28 @@ use std::{
     time::Duration,
 };
 use time::UtcOffset;
-use x11rb::{
-    connection::Connection as _,
-    protocol::{
-        xproto::{Atom, ConnectionExt as _},
-        Event,
-    },
-    rust_connection::RustConnection,
-};
-
-pub(crate) struct LinuxPlatform(Mutex<LinuxPlatformState>);
-
-pub(crate) struct WmAtoms {
-    pub protocols: Atom,
-    pub delete_window: Atom,
-}
-
-impl WmAtoms {
-    fn new(x11_connection: &RustConnection) -> Self {
-        Self {
-            protocols: x11_connection
-                .intern_atom(false, b"WM_PROTOCOLS")
-                .unwrap()
-                .reply()
-                .unwrap()
-                .atom,
-            delete_window: x11_connection
-                .intern_atom(false, b"WM_DELETE_WINDOW")
-                .unwrap()
-                .reply()
-                .unwrap()
-                .atom,
-        }
+use xcb::{x, Xid as _};
+
+xcb::atoms_struct! {
+    #[derive(Debug)]
+    pub(crate) struct XcbAtoms {
+        pub wm_protocols    => b"WM_PROTOCOLS",
+        pub wm_del_window   => b"WM_DELETE_WINDOW",
+        wm_state        => b"_NET_WM_STATE",
+        wm_state_maxv   => b"_NET_WM_STATE_MAXIMIZED_VERT",
+        wm_state_maxh   => b"_NET_WM_STATE_MAXIMIZED_HORZ",
     }
 }
 
+pub(crate) struct LinuxPlatform(Mutex<LinuxPlatformState>);
+
 pub(crate) struct LinuxPlatformState {
-    x11_connection: RustConnection,
-    x11_root_index: usize,
-    atoms: WmAtoms,
+    xcb_connection: xcb::Connection,
+    x_root_index: i32,
+    atoms: XcbAtoms,
     background_executor: BackgroundExecutor,
     foreground_executor: ForegroundExecutor,
-    windows: HashMap<u32, LinuxWindowStatePtr>,
+    windows: HashMap<x::Window, LinuxWindowStatePtr>,
     text_system: Arc<LinuxTextSystem>,
 }
 
@@ -72,14 +52,14 @@ impl Default for LinuxPlatform {
 
 impl LinuxPlatform {
     pub(crate) fn new() -> Self {
-        let (x11_connection, x11_root_index) = x11rb::connect(None).unwrap();
-        let atoms = WmAtoms::new(&x11_connection);
+        let (xcb_connection, x_root_index) = xcb::Connection::connect(None).unwrap();
+        let atoms = XcbAtoms::intern_all(&xcb_connection).unwrap();
 
         let dispatcher = Arc::new(LinuxDispatcher::new());
 
         Self(Mutex::new(LinuxPlatformState {
-            x11_connection,
-            x11_root_index,
+            xcb_connection,
+            x_root_index,
             atoms,
             background_executor: BackgroundExecutor::new(dispatcher.clone()),
             foreground_executor: ForegroundExecutor::new(dispatcher),
@@ -105,54 +85,44 @@ impl Platform for LinuxPlatform {
     fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
         on_finish_launching();
 
-        let mut need_repaint = HashSet::<u32>::default();
+        let mut need_repaint = HashSet::<x::Window>::default();
 
         while !self.0.lock().windows.is_empty() {
-            let event = self.0.lock().x11_connection.wait_for_event().unwrap();
-            let mut event_option = Some(event);
-            while let Some(event) = event_option {
-                match event {
-                    Event::Expose(event) => {
-                        if event.count == 0 {
-                            need_repaint.insert(event.window);
-                        }
-                    }
-                    Event::ConfigureNotify(event) => {
-                        let lock = self.0.lock();
-                        let mut window = lock.windows[&event.window].lock();
-                        window.resize(event.width, event.height);
-                    }
-                    Event::MotionNotify(_event) => {
-                        //mouse_position = (event.event_x, event.event_y);
-                        //need_repaint.insert(event.window);
-                    }
-                    Event::MapNotify(_) => {}
-                    Event::ClientMessage(event) => {
+            let event = self.0.lock().xcb_connection.wait_for_event().unwrap();
+            match event {
+                xcb::Event::X(x::Event::ClientMessage(ev)) => {
+                    if let x::ClientMessageData::Data32([atom, ..]) = ev.data() {
                         let mut lock = self.0.lock();
-                        let data = event.data.as_data32();
-                        if data[0] == lock.atoms.delete_window {
+                        if atom == lock.atoms.wm_del_window.resource_id() {
+                            // window "x" button clicked by user, we gracefully exit
                             {
-                                let mut window = lock.windows[&event.window].lock();
+                                let mut window = lock.windows[&ev.window()].lock();
                                 window.destroy();
                             }
-                            lock.windows.remove(&event.window);
+                            lock.windows.remove(&ev.window());
+                            break;
                         }
                     }
-                    Event::Error(error) => {
-                        log::error!("X11 error {:?}", error);
-                    }
-                    _ => {}
                 }
-
-                let lock = self.0.lock();
-                event_option = lock.x11_connection.poll_for_event().unwrap();
+                _ => {} /*
+                        Event::Expose(event) => {
+                            if event.count == 0 {
+                                need_repaint.insert(event.window);
+                            }
+                        }
+                        Event::ConfigureNotify(event) => {
+                            let lock = self.0.lock();
+                            let mut window = lock.windows[&event.window].lock();
+                            window.resize(event.width, event.height);
+                        }
+                        _ => {}*/
             }
 
-            for x11_window in need_repaint.drain() {
+            for x_window in need_repaint.drain() {
                 let lock = self.0.lock();
-                let mut window = lock.windows[&x11_window].lock();
+                let mut window = lock.windows[&x_window].lock();
                 window.paint();
-                lock.x11_connection.flush().unwrap();
+                lock.xcb_connection.flush();
             }
         }
     }
@@ -171,10 +141,13 @@ impl Platform for LinuxPlatform {
 
     fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
         let lock = self.0.lock();
-        let setup = lock.x11_connection.setup();
-        (0..setup.roots.len())
-            .map(|id| {
-                Rc::new(LinuxDisplay::new(&lock.x11_connection, id)) as Rc<dyn PlatformDisplay>
+        let setup = lock.xcb_connection.get_setup();
+        setup
+            .roots()
+            .enumerate()
+            .map(|(root_id, _)| {
+                Rc::new(LinuxDisplay::new(&lock.xcb_connection, root_id as i32))
+                    as Rc<dyn PlatformDisplay>
             })
             .collect()
     }
@@ -182,8 +155,8 @@ impl Platform for LinuxPlatform {
     fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
         let lock = self.0.lock();
         Some(Rc::new(LinuxDisplay::new(
-            &lock.x11_connection,
-            id.0 as usize,
+            &lock.xcb_connection,
+            id.0 as i32,
         )))
     }
 
@@ -197,17 +170,17 @@ impl Platform for LinuxPlatform {
         options: WindowOptions,
     ) -> Box<dyn PlatformWindow> {
         let mut lock = self.0.lock();
-        let win_id = lock.x11_connection.generate_id().unwrap();
+        let x_window = lock.xcb_connection.generate_id();
 
         let window_ptr = LinuxWindowState::new_ptr(
             options,
             handle,
-            &lock.x11_connection,
-            lock.x11_root_index,
-            win_id,
+            &lock.xcb_connection,
+            lock.x_root_index,
+            x_window,
             &lock.atoms,
         );
-        lock.windows.insert(win_id, window_ptr.clone());
+        lock.windows.insert(x_window, window_ptr.clone());
         Box::new(LinuxWindow(window_ptr))
     }
 

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

@@ -15,7 +15,7 @@ use font_kit::{
 };
 use parking_lot::RwLock;
 use smallvec::SmallVec;
-use std::sync::Arc;
+use std::{borrow::Cow};
 
 pub(crate) struct LinuxTextSystem(RwLock<LinuxTextSystemState>);
 
@@ -54,7 +54,7 @@ impl Default for LinuxTextSystem {
 
 #[allow(unused)]
 impl PlatformTextSystem for LinuxTextSystem {
-    fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()> {
+    fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
         Ok(()) //TODO
     }
     fn all_font_names(&self) -> Vec<String> {

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

@@ -1,28 +1,19 @@
 use super::BladeRenderer;
 use crate::{
-    px, AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, BladeAtlas, Bounds, KeyDownEvent,
-    Keystroke, LinuxDisplay, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
-    PlatformInputHandler, PlatformWindow, Point, Size, TileId, WindowAppearance, WindowBounds,
-    WindowOptions, WmAtoms,
+    AnyWindowHandle, BladeAtlas, LinuxDisplay, Pixels, PlatformDisplay, PlatformInputHandler,
+    PlatformWindow, Point, Size, WindowAppearance, WindowBounds, WindowOptions, XcbAtoms,
 };
-use collections::HashMap;
 use parking_lot::Mutex;
 use std::{
-    rc::{Rc, Weak},
+    ffi::c_void,
+    rc::Rc,
     sync::{self, Arc},
 };
-use x11rb::{
-    connection::Connection as _,
-    protocol::xproto::{
-        AtomEnum, ConnectionExt as _, CreateWindowAux, EventMask, PropMode, WindowClass,
-    },
-    rust_connection::RustConnection,
-    wrapper::ConnectionExt as _,
-};
+use xcb::{x, Xid as _};
 
 pub(crate) struct LinuxWindowState {
     display: Rc<dyn PlatformDisplay>,
-    x11_window: u32,
+    x_window: x::Window,
     window_bounds: WindowBounds,
     content_size: Size<Pixels>,
     sprite_atlas: Arc<BladeAtlas>,
@@ -33,29 +24,53 @@ pub(crate) type LinuxWindowStatePtr = Arc<Mutex<LinuxWindowState>>;
 #[derive(Clone)]
 pub(crate) struct LinuxWindow(pub(crate) LinuxWindowStatePtr);
 
+struct RawWindow {
+    connection: *mut c_void,
+    screen_id: i32,
+    window_id: u32,
+}
+unsafe impl raw_window_handle::HasRawWindowHandle for RawWindow {
+    fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
+        let mut wh = raw_window_handle::XcbWindowHandle::empty();
+        wh.window = self.window_id;
+        wh.into()
+    }
+}
+unsafe impl raw_window_handle::HasRawDisplayHandle for RawWindow {
+    fn raw_display_handle(&self) -> raw_window_handle::RawDisplayHandle {
+        let mut dh = raw_window_handle::XcbDisplayHandle::empty();
+        dh.connection = self.connection;
+        dh.screen = self.screen_id;
+        dh.into()
+    }
+}
+
 impl LinuxWindowState {
     pub fn new_ptr(
         options: WindowOptions,
         handle: AnyWindowHandle,
-        x11_connection: &RustConnection,
-        x11_main_screen_index: usize,
-        x11_window: u32,
-        atoms: &WmAtoms,
+        xcb_connection: &xcb::Connection,
+        x_main_screen_index: i32,
+        x_window: x::Window,
+        atoms: &XcbAtoms,
     ) -> LinuxWindowStatePtr {
-        let x11_screen_index = options
+        let x_screen_index = options
             .display_id
-            .map_or(x11_main_screen_index, |did| did.0 as usize);
-        let screen = &x11_connection.setup().roots[x11_screen_index];
+            .map_or(x_main_screen_index, |did| did.0 as i32);
+        let screen = xcb_connection
+            .get_setup()
+            .roots()
+            .nth(x_screen_index as usize)
+            .unwrap();
 
-        let win_aux = CreateWindowAux::new()
-            .event_mask(
-                EventMask::EXPOSURE | EventMask::STRUCTURE_NOTIFY | EventMask::POINTER_MOTION,
-            )
-            .background_pixel(screen.white_pixel);
+        let xcb_values = [
+            x::Cw::BackPixel(screen.white_pixel()),
+            x::Cw::EventMask(x::EventMask::EXPOSURE | x::EventMask::KEY_PRESS),
+        ];
 
         let (bound_x, bound_y, bound_width, bound_height) = match options.bounds {
             WindowBounds::Fullscreen | WindowBounds::Maximized => {
-                (0, 0, screen.width_in_pixels, screen.height_in_pixels)
+                (0, 0, screen.width_in_pixels(), screen.height_in_pixels())
             }
             WindowBounds::Fixed(bounds) => (
                 bounds.origin.x.0 as i16,
@@ -65,61 +80,67 @@ impl LinuxWindowState {
             ),
         };
 
-        x11_connection
-            .create_window(
-                x11rb::COPY_DEPTH_FROM_PARENT,
-                x11_window,
-                screen.root,
-                bound_x,
-                bound_y,
-                bound_width,
-                bound_height,
-                0,
-                WindowClass::INPUT_OUTPUT,
-                0,
-                &win_aux,
-            )
-            .unwrap();
+        xcb_connection.send_request(&x::CreateWindow {
+            depth: x::COPY_FROM_PARENT as u8,
+            wid: x_window,
+            parent: screen.root(),
+            x: bound_x,
+            y: bound_y,
+            width: bound_width,
+            height: bound_height,
+            border_width: 0,
+            class: x::WindowClass::InputOutput,
+            visual: screen.root_visual(),
+            value_list: &xcb_values,
+        });
 
         if let Some(titlebar) = options.titlebar {
             if let Some(title) = titlebar.title {
-                x11_connection
-                    .change_property8(
-                        PropMode::REPLACE,
-                        x11_window,
-                        AtomEnum::WM_NAME,
-                        AtomEnum::STRING,
-                        title.as_bytes(),
-                    )
-                    .unwrap();
+                xcb_connection.send_request(&x::ChangeProperty {
+                    mode: x::PropMode::Replace,
+                    window: x_window,
+                    property: x::ATOM_WM_NAME,
+                    r#type: x::ATOM_STRING,
+                    data: title.as_bytes(),
+                });
             }
         }
-        x11_connection
-            .change_property32(
-                PropMode::REPLACE,
-                x11_window,
-                atoms.protocols,
-                AtomEnum::ATOM,
-                &[atoms.delete_window],
-            )
+        xcb_connection
+            .send_and_check_request(&x::ChangeProperty {
+                mode: x::PropMode::Replace,
+                window: x_window,
+                property: atoms.wm_protocols,
+                r#type: x::ATOM_ATOM,
+                data: &[atoms.wm_del_window],
+            })
             .unwrap();
 
-        x11_connection.map_window(x11_window).unwrap();
-        x11_connection.flush().unwrap();
+        xcb_connection.send_request(&x::MapWindow { window: x_window });
+        xcb_connection.flush().unwrap();
 
+        let raw_window = RawWindow {
+            connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection(
+                xcb_connection,
+            ) as *mut _,
+            screen_id: x_screen_index,
+            window_id: x_window.resource_id(),
+        };
         let gpu = Arc::new(
             unsafe {
-                blade::Context::init(blade::ContextDesc {
-                    validation: cfg!(debug_assertions),
-                    capture: false,
-                })
+                blade::Context::init_windowed(
+                    &raw_window,
+                    blade::ContextDesc {
+                        validation: cfg!(debug_assertions),
+                        capture: false,
+                    },
+                )
             }
             .unwrap(),
         );
 
         Arc::new(Mutex::new(Self {
-            display: Rc::new(LinuxDisplay::new(x11_connection, x11_screen_index)),
-            x11_window,
+            display: Rc::new(LinuxDisplay::new(xcb_connection, x_screen_index)),
+            x_window,
             window_bounds: options.bounds,
             content_size: Size {
                 width: Pixels(bound_width as f32),