gpui: Switch to `x11rb` (#9113)

Rajesh Malviya and Mikayla Maki created

Switch to using `x11rb` crate instead of current `xcb` crate for gpui's
x11 platform.

Also fixes the crash on resize, and white flashing on resize.

Release Notes:

- N/A

---------

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

Change summary

Cargo.lock                                    |  16 
crates/gpui/Cargo.toml                        |   2 
crates/gpui/src/platform/linux/x11/client.rs  | 294 +++++++++++---------
crates/gpui/src/platform/linux/x11/display.rs |  13 
crates/gpui/src/platform/linux/x11/event.rs   |  22 
crates/gpui/src/platform/linux/x11/window.rs  | 225 ++++++++--------
6 files changed, 292 insertions(+), 280 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -4409,7 +4409,7 @@ dependencies = [
  "wayland-cursor",
  "wayland-protocols",
  "windows 0.53.0",
- "xcb",
+ "x11rb",
  "xkbcommon",
 ]
 
@@ -12516,7 +12516,9 @@ version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f8f25ead8c7e4cba123243a6367da5d3990e0d3affa708ea19dce96356bd9f1a"
 dependencies = [
+ "as-raw-xcb-connection",
  "gethostname",
+ "libc",
  "rustix 0.38.30",
  "x11rb-protocol",
 ]
@@ -12536,18 +12538,6 @@ dependencies = [
  "libc",
 ]
 
-[[package]]
-name = "xcb"
-version = "1.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d27b37e69b8c05bfadcd968eb1a4fe27c9c52565b727f88512f43b89567e262"
-dependencies = [
- "as-raw-xcb-connection",
- "bitflags 1.3.2",
- "libc",
- "quick-xml 0.30.0",
-]
-
 [[package]]
 name = "xcursor"
 version = "0.3.5"

crates/gpui/Cargo.toml 🔗

@@ -108,7 +108,7 @@ copypasta = "0.10.1"
 [target.'cfg(target_os = "linux")'.dependencies]
 open = "5.0.1"
 ashpd = "0.7.0"
-xcb = { version = "1.3", features = ["as-raw-xcb-connection", "randr", "xkb"] }
+x11rb = { version = "0.13.0", features = ["allow-unsafe-code", "xkb", "randr"] }
 wayland-client = { version = "0.31.2" }
 wayland-cursor = "0.31.1"
 wayland-protocols = { version = "0.31.2", features = [

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

@@ -2,18 +2,25 @@ use std::cell::RefCell;
 use std::rc::Rc;
 use std::time::Duration;
 
-use xcb::{x, Xid as _};
-use xkbcommon::xkb;
-
 use collections::HashMap;
 use copypasta::x11_clipboard::{Clipboard, Primary, X11ClipboardContext};
 use copypasta::ClipboardProvider;
 
+use x11rb::connection::{Connection, RequestConnection};
+use x11rb::errors::ConnectionError;
+use x11rb::protocol::randr::ConnectionExt as _;
+use x11rb::protocol::xkb::ConnectionExt as _;
+use x11rb::protocol::xproto::ConnectionExt as _;
+use x11rb::protocol::{randr, xkb, xproto, Event};
+use x11rb::xcb_ffi::XCBConnection;
+use xkbc::x11::ffi::{XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION};
+use xkbcommon::xkb as xkbc;
+
 use crate::platform::linux::client::Client;
 use crate::platform::{LinuxPlatformInner, PlatformWindow};
 use crate::{
     AnyWindowHandle, Bounds, CursorStyle, DisplayId, PlatformDisplay, PlatformInput, Point,
-    ScrollDelta, Size, TouchPhase,
+    ScrollDelta, Size, TouchPhase, WindowParams,
 };
 
 use super::{X11Display, X11Window, X11WindowState, XcbAtoms};
@@ -28,55 +35,56 @@ struct WindowRef {
 }
 
 struct X11ClientState {
-    windows: HashMap<x::Window, WindowRef>,
-    xkb: xkbcommon::xkb::State,
+    windows: HashMap<xproto::Window, WindowRef>,
+    xkb: xkbc::State,
     clipboard: Rc<RefCell<X11ClipboardContext<Clipboard>>>,
     primary: Rc<RefCell<X11ClipboardContext<Primary>>>,
 }
 
 pub(crate) struct X11Client {
     platform_inner: Rc<LinuxPlatformInner>,
-    xcb_connection: Rc<xcb::Connection>,
-    x_root_index: i32,
+    xcb_connection: Rc<XCBConnection>,
+    x_root_index: usize,
     atoms: XcbAtoms,
     state: RefCell<X11ClientState>,
 }
 
 impl X11Client {
     pub(crate) fn new(inner: Rc<LinuxPlatformInner>) -> Rc<Self> {
-        let (xcb_connection, x_root_index) = xcb::Connection::connect_with_extensions(
-            None,
-            &[xcb::Extension::RandR, xcb::Extension::Xkb],
-            &[],
-        )
-        .unwrap();
-
-        let xkb_ver = xcb_connection
-            .wait_for_reply(xcb_connection.send_request(&xcb::xkb::UseExtension {
-                wanted_major: xcb::xkb::MAJOR_VERSION as u16,
-                wanted_minor: xcb::xkb::MINOR_VERSION as u16,
-            }))
+        let (xcb_connection, x_root_index) = XCBConnection::connect(None).unwrap();
+        xcb_connection
+            .prefetch_extension_information(xkb::X11_EXTENSION_NAME)
+            .unwrap();
+        xcb_connection
+            .prefetch_extension_information(randr::X11_EXTENSION_NAME)
             .unwrap();
-        assert!(xkb_ver.supported());
 
-        let atoms = XcbAtoms::intern_all(&xcb_connection).unwrap();
-        let xcb_connection = Rc::new(xcb_connection);
+        let atoms = XcbAtoms::new(&xcb_connection).unwrap();
+        let xkb = xcb_connection
+            .xkb_use_extension(XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION)
+            .unwrap();
+
+        let atoms = atoms.reply().unwrap();
+        let xkb = xkb.reply().unwrap();
+        assert!(xkb.supported);
 
         let xkb_state = {
-            let xkb_context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
-            let xkb_device_id = xkb::x11::get_core_keyboard_device_id(&xcb_connection);
-            let xkb_keymap = xkb::x11::keymap_new_from_device(
+            let xkb_context = xkbc::Context::new(xkbc::CONTEXT_NO_FLAGS);
+            let xkb_device_id = xkbc::x11::get_core_keyboard_device_id(&xcb_connection);
+            let xkb_keymap = xkbc::x11::keymap_new_from_device(
                 &xkb_context,
                 &xcb_connection,
                 xkb_device_id,
-                xkb::KEYMAP_COMPILE_NO_FLAGS,
+                xkbc::KEYMAP_COMPILE_NO_FLAGS,
             );
-            xkb::x11::state_new_from_device(&xkb_keymap, &xcb_connection, xkb_device_id)
+            xkbc::x11::state_new_from_device(&xkb_keymap, &xcb_connection, xkb_device_id)
         };
 
         let clipboard = X11ClipboardContext::<Clipboard>::new().unwrap();
         let primary = X11ClipboardContext::<Primary>::new().unwrap();
 
+        let xcb_connection = Rc::new(xcb_connection);
+
         let client: Rc<X11Client> = Rc::new(Self {
             platform_inner: inner.clone(),
             xcb_connection: Rc::clone(&xcb_connection),
@@ -96,7 +104,7 @@ impl X11Client {
         inner
             .loop_handle
             .insert_source(
-                Generic::new_with_error::<xcb::Error>(
+                Generic::new_with_error::<ConnectionError>(
                     fd,
                     calloop::Interest::READ,
                     calloop::Mode::Level,
@@ -116,69 +124,68 @@ impl X11Client {
         client
     }
 
-    fn get_window(&self, win: x::Window) -> Option<Rc<X11WindowState>> {
+    fn get_window(&self, win: xproto::Window) -> Option<Rc<X11WindowState>> {
         let state = self.state.borrow();
         state.windows.get(&win).map(|wr| Rc::clone(&wr.state))
     }
 
-    fn handle_event(&self, event: xcb::Event) -> Option<()> {
+    fn handle_event(&self, event: Event) -> Option<()> {
         match event {
-            xcb::Event::X(x::Event::ClientMessage(event)) => {
-                if let x::ClientMessageData::Data32([atom, ..]) = event.data() {
-                    if atom == self.atoms.wm_del_window.resource_id() {
-                        // window "x" button clicked by user, we gracefully exit
-                        let window_ref = self
-                            .state
-                            .borrow_mut()
-                            .windows
-                            .remove(&event.window())
-                            .unwrap();
-
-                        self.platform_inner
-                            .loop_handle
-                            .remove(window_ref.refresh_event_token);
-                        window_ref.state.destroy();
-
-                        if self.state.borrow().windows.is_empty() {
-                            self.platform_inner.loop_signal.stop();
-                        }
+            Event::ClientMessage(event) => {
+                let [atom, ..] = event.data.as_data32();
+                if atom == self.atoms.WM_DELETE_WINDOW {
+                    // window "x" button clicked by user, we gracefully exit
+                    let window_ref = self
+                        .state
+                        .borrow_mut()
+                        .windows
+                        .remove(&event.window)
+                        .unwrap();
+
+                    self.platform_inner
+                        .loop_handle
+                        .remove(window_ref.refresh_event_token);
+                    window_ref.state.destroy();
+
+                    if self.state.borrow().windows.is_empty() {
+                        self.platform_inner.loop_signal.stop();
                     }
                 }
             }
-            xcb::Event::X(x::Event::ConfigureNotify(event)) => {
+            Event::ConfigureNotify(event) => {
                 let bounds = Bounds {
                     origin: Point {
-                        x: event.x().into(),
-                        y: event.y().into(),
+                        x: event.x.into(),
+                        y: event.y.into(),
                     },
                     size: Size {
-                        width: event.width().into(),
-                        height: event.height().into(),
+                        width: event.width.into(),
+                        height: event.height.into(),
                     },
                 };
-                let window = self.get_window(event.window())?;
+                let window = self.get_window(event.window)?;
                 window.configure(bounds);
             }
-            xcb::Event::X(x::Event::Expose(event)) => {
-                let window = self.get_window(event.window())?;
+            Event::Expose(event) => {
+                let window = self.get_window(event.window)?;
                 window.refresh();
             }
-            xcb::Event::X(x::Event::FocusIn(event)) => {
-                let window = self.get_window(event.event())?;
+            Event::FocusIn(event) => {
+                let window = self.get_window(event.event)?;
                 window.set_focused(true);
             }
-            xcb::Event::X(x::Event::FocusOut(event)) => {
-                let window = self.get_window(event.event())?;
+            Event::FocusOut(event) => {
+                let window = self.get_window(event.event)?;
                 window.set_focused(false);
             }
-            xcb::Event::X(x::Event::KeyPress(event)) => {
-                let window = self.get_window(event.event())?;
-                let modifiers = super::modifiers_from_state(event.state());
+            Event::KeyPress(event) => {
+                let window = self.get_window(event.event)?;
+                let modifiers = super::modifiers_from_state(event.state);
                 let keystroke = {
-                    let code = event.detail().into();
+                    let code = event.detail.into();
                     let mut state = self.state.borrow_mut();
                     let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
-                    state.xkb.update_key(code, xkb::KeyDirection::Down);
+                    state.xkb.update_key(code, xkbc::KeyDirection::Down);
                     keystroke
                 };
 
@@ -187,36 +194,34 @@ impl X11Client {
                     is_held: false,
                 }));
             }
-            xcb::Event::X(x::Event::KeyRelease(event)) => {
-                let window = self.get_window(event.event())?;
-                let modifiers = super::modifiers_from_state(event.state());
+            Event::KeyRelease(event) => {
+                let window = self.get_window(event.event)?;
+                let modifiers = super::modifiers_from_state(event.state);
                 let keystroke = {
-                    let code = event.detail().into();
+                    let code = event.detail.into();
                     let mut state = self.state.borrow_mut();
                     let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
-                    state.xkb.update_key(code, xkb::KeyDirection::Up);
+                    state.xkb.update_key(code, xkbc::KeyDirection::Up);
                     keystroke
                 };
 
                 window.handle_input(PlatformInput::KeyUp(crate::KeyUpEvent { keystroke }));
             }
-            xcb::Event::X(x::Event::ButtonPress(event)) => {
-                let window = self.get_window(event.event())?;
-                let modifiers = super::modifiers_from_state(event.state());
-                let position = Point::new(
-                    (event.event_x() as f32).into(),
-                    (event.event_y() as f32).into(),
-                );
-                if let Some(button) = super::button_of_key(event.detail()) {
+            Event::ButtonPress(event) => {
+                let window = self.get_window(event.event)?;
+                let modifiers = super::modifiers_from_state(event.state);
+                let position =
+                    Point::new((event.event_x as f32).into(), (event.event_y as f32).into());
+                if let Some(button) = super::button_of_key(event.detail) {
                     window.handle_input(PlatformInput::MouseDown(crate::MouseDownEvent {
                         button,
                         position,
                         modifiers,
                         click_count: 1,
                     }));
-                } else if event.detail() >= 4 && event.detail() <= 5 {
+                } else if event.detail >= 4 && event.detail <= 5 {
                     // https://stackoverflow.com/questions/15510472/scrollwheel-event-in-x11
-                    let delta_x = if event.detail() == 4 { 1.0 } else { -1.0 };
+                    let delta_x = if event.detail == 4 { 1.0 } else { -1.0 };
                     window.handle_input(PlatformInput::ScrollWheel(crate::ScrollWheelEvent {
                         position,
                         delta: ScrollDelta::Lines(Point::new(0.0, delta_x)),
@@ -227,14 +232,12 @@ impl X11Client {
                     log::warn!("Unknown button press: {event:?}");
                 }
             }
-            xcb::Event::X(x::Event::ButtonRelease(event)) => {
-                let window = self.get_window(event.event())?;
-                let modifiers = super::modifiers_from_state(event.state());
-                let position = Point::new(
-                    (event.event_x() as f32).into(),
-                    (event.event_y() as f32).into(),
-                );
-                if let Some(button) = super::button_of_key(event.detail()) {
+            Event::ButtonRelease(event) => {
+                let window = self.get_window(event.event)?;
+                let modifiers = super::modifiers_from_state(event.state);
+                let position =
+                    Point::new((event.event_x as f32).into(), (event.event_y as f32).into());
+                if let Some(button) = super::button_of_key(event.detail) {
                     window.handle_input(PlatformInput::MouseUp(crate::MouseUpEvent {
                         button,
                         position,
@@ -243,28 +246,24 @@ impl X11Client {
                     }));
                 }
             }
-            xcb::Event::X(x::Event::MotionNotify(event)) => {
-                let window = self.get_window(event.event())?;
-                let pressed_button = super::button_from_state(event.state());
-                let position = Point::new(
-                    (event.event_x() as f32).into(),
-                    (event.event_y() as f32).into(),
-                );
-                let modifiers = super::modifiers_from_state(event.state());
+            Event::MotionNotify(event) => {
+                let window = self.get_window(event.event)?;
+                let pressed_button = super::button_from_state(event.state);
+                let position =
+                    Point::new((event.event_x as f32).into(), (event.event_y as f32).into());
+                let modifiers = super::modifiers_from_state(event.state);
                 window.handle_input(PlatformInput::MouseMove(crate::MouseMoveEvent {
                     pressed_button,
                     position,
                     modifiers,
                 }));
             }
-            xcb::Event::X(x::Event::LeaveNotify(event)) => {
-                let window = self.get_window(event.event())?;
-                let pressed_button = super::button_from_state(event.state());
-                let position = Point::new(
-                    (event.event_x() as f32).into(),
-                    (event.event_y() as f32).into(),
-                );
-                let modifiers = super::modifiers_from_state(event.state());
+            Event::LeaveNotify(event) => {
+                let window = self.get_window(event.event)?;
+                let pressed_button = super::button_from_state(event.state);
+                let position =
+                    Point::new((event.event_x as f32).into(), (event.event_y as f32).into());
+                let modifiers = super::modifiers_from_state(event.state);
                 window.handle_input(PlatformInput::MouseExited(crate::MouseExitEvent {
                     pressed_button,
                     position,
@@ -280,21 +279,23 @@ impl X11Client {
 
 impl Client for X11Client {
     fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
-        let setup = self.xcb_connection.get_setup();
+        let setup = self.xcb_connection.setup();
         setup
-            .roots()
+            .roots
+            .iter()
             .enumerate()
             .filter_map(|(root_id, _)| {
-                Some(
-                    Rc::new(X11Display::new(&self.xcb_connection, root_id as i32)?)
-                        as Rc<dyn PlatformDisplay>,
-                )
+                Some(Rc::new(X11Display::new(&self.xcb_connection, root_id)?)
+                    as Rc<dyn PlatformDisplay>)
             })
             .collect()
     }
 
     fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
-        Some(Rc::new(X11Display::new(&self.xcb_connection, id.0 as i32)?))
+        Some(Rc::new(X11Display::new(
+            &self.xcb_connection,
+            id.0 as usize,
+        )?))
     }
 
     fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
@@ -307,36 +308,44 @@ impl Client for X11Client {
     fn open_window(
         &self,
         _handle: AnyWindowHandle,
-        params: crate::WindowParams,
+        options: WindowParams,
     ) -> Box<dyn PlatformWindow> {
-        let x_window = self.xcb_connection.generate_id();
+        let x_window = self.xcb_connection.generate_id().unwrap();
 
         let window_ptr = Rc::new(X11WindowState::new(
-            params,
+            options,
             &self.xcb_connection,
             self.x_root_index,
             x_window,
             &self.atoms,
         ));
 
-        let cookie = self
+        let screen_resources = self
             .xcb_connection
-            .send_request(&xcb::randr::GetScreenResourcesCurrent { window: x_window });
-        let screen_resources = self.xcb_connection.wait_for_reply(cookie).expect("TODO");
+            .randr_get_screen_resources(x_window)
+            .unwrap()
+            .reply()
+            .expect("TODO");
+
         let mode = screen_resources
-            .crtcs()
+            .crtcs
             .iter()
             .find_map(|crtc| {
-                let cookie = self.xcb_connection.send_request(&xcb::randr::GetCrtcInfo {
-                    crtc: crtc.to_owned(),
-                    config_timestamp: xcb::x::Time::CurrentTime as u32,
-                });
-                let crtc_info = self.xcb_connection.wait_for_reply(cookie).expect("TODO");
-
-                let mode_id = crtc_info.mode().resource_id();
-                screen_resources.modes().iter().find(|m| m.id == mode_id)
+                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("Missing screen mode for crtc specified mode id");
+            .expect("Unable to find screen refresh rate");
+
+        // .expect("Missing screen mode for crtc specified mode id");
 
         let refresh_event_token = self
             .platform_inner
@@ -345,13 +354,24 @@ impl Client for X11Client {
                 let refresh_duration = mode_refresh_rate(mode);
                 let xcb_connection = Rc::clone(&self.xcb_connection);
                 move |mut instant, (), _| {
-                    xcb_connection.send_request(&x::SendEvent {
-                        propagate: false,
-                        destination: x::SendEventDest::Window(x_window),
-                        event_mask: x::EventMask::EXPOSURE,
-                        event: &x::ExposeEvent::new(x_window, 0, 0, 0, 0, 1),
-                    });
-                    let _ = xcb_connection.flush();
+                    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 _ = xcb_connection.flush().unwrap();
                     // Take into account that some frames have been skipped
                     let now = time::Instant::now();
                     while instant < now {
@@ -384,7 +404,7 @@ impl Client 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: &xcb::randr::ModeInfo) -> Duration {
+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);

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

@@ -1,25 +1,26 @@
 use anyhow::Result;
 use uuid::Uuid;
+use x11rb::{connection::Connection as _, xcb_ffi::XCBConnection};
 
 use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Size};
 
 #[derive(Debug)]
 pub(crate) struct X11Display {
-    x_screen_index: i32,
+    x_screen_index: usize,
     bounds: Bounds<GlobalPixels>,
     uuid: Uuid,
 }
 
 impl X11Display {
-    pub(crate) fn new(xc: &xcb::Connection, x_screen_index: i32) -> Option<Self> {
-        let screen = xc.get_setup().roots().nth(x_screen_index as usize)?;
+    pub(crate) fn new(xc: &XCBConnection, x_screen_index: usize) -> Option<Self> {
+        let screen = xc.setup().roots.get(x_screen_index).unwrap();
         Some(Self {
-            x_screen_index,
+            x_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]),

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

@@ -1,8 +1,8 @@
-use xcb::x;
+use x11rb::protocol::xproto;
 
 use crate::{Modifiers, MouseButton, NavigationDirection};
 
-pub(crate) fn button_of_key(detail: x::Button) -> Option<MouseButton> {
+pub(crate) fn button_of_key(detail: xproto::Button) -> Option<MouseButton> {
     Some(match detail {
         1 => MouseButton::Left,
         2 => MouseButton::Middle,
@@ -13,22 +13,22 @@ pub(crate) fn button_of_key(detail: x::Button) -> Option<MouseButton> {
     })
 }
 
-pub(crate) fn modifiers_from_state(state: x::KeyButMask) -> Modifiers {
+pub(crate) fn modifiers_from_state(state: xproto::KeyButMask) -> Modifiers {
     Modifiers {
-        control: state.contains(x::KeyButMask::CONTROL),
-        alt: state.contains(x::KeyButMask::MOD1),
-        shift: state.contains(x::KeyButMask::SHIFT),
-        command: state.contains(x::KeyButMask::MOD4),
+        control: state.contains(xproto::KeyButMask::CONTROL),
+        alt: state.contains(xproto::KeyButMask::MOD1),
+        shift: state.contains(xproto::KeyButMask::SHIFT),
+        command: state.contains(xproto::KeyButMask::MOD4),
         function: false,
     }
 }
 
-pub(crate) fn button_from_state(state: x::KeyButMask) -> Option<MouseButton> {
-    Some(if state.contains(x::KeyButMask::BUTTON1) {
+pub(crate) fn button_from_state(state: xproto::KeyButMask) -> Option<MouseButton> {
+    Some(if state.contains(xproto::KeyButMask::BUTTON1) {
         MouseButton::Left
-    } else if state.contains(x::KeyButMask::BUTTON2) {
+    } else if state.contains(xproto::KeyButMask::BUTTON2) {
         MouseButton::Middle
-    } else if state.contains(x::KeyButMask::BUTTON3) {
+    } else if state.contains(xproto::KeyButMask::BUTTON3) {
         MouseButton::Right
     } else {
         return None;

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

@@ -9,10 +9,11 @@ use crate::{
 use blade_graphics as gpu;
 use parking_lot::Mutex;
 use raw_window_handle as rwh;
-
-use xcb::{
-    x::{self, StackMode},
-    Xid as _,
+use x11rb::{
+    connection::Connection,
+    protocol::xproto::{self, ConnectionExt as _, CreateWindowAux},
+    wrapper::ConnectionExt,
+    xcb_ffi::XCBConnection,
 };
 
 use std::{
@@ -40,14 +41,13 @@ struct Callbacks {
     appearance_changed: Option<Box<dyn FnMut()>>,
 }
 
-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",
+x11rb::atom_manager! {
+    pub XcbAtoms: AtomsCookie {
+        WM_PROTOCOLS,
+        WM_DELETE_WINDOW,
+        _NET_WM_STATE,
+        _NET_WM_STATE_MAXIMIZED_VERT,
+        _NET_WM_STATE_MAXIMIZED_HORZ,
     }
 }
 
@@ -68,30 +68,31 @@ impl LinuxWindowInner {
     }
 }
 
-fn query_render_extent(xcb_connection: &xcb::Connection, x_window: x::Window) -> gpu::Extent {
-    let cookie = xcb_connection.send_request(&x::GetGeometry {
-        drawable: x::Drawable::Window(x_window),
-    });
-    let reply = xcb_connection.wait_for_reply(cookie).unwrap();
+fn query_render_extent(xcb_connection: &XCBConnection, x_window: xproto::Window) -> gpu::Extent {
+    let reply = xcb_connection
+        .get_geometry(x_window)
+        .unwrap()
+        .reply()
+        .unwrap();
     gpu::Extent {
-        width: reply.width() as u32,
-        height: reply.height() as u32,
+        width: reply.width as u32,
+        height: reply.height as u32,
         depth: 1,
     }
 }
 
 struct RawWindow {
     connection: *mut c_void,
-    screen_id: i32,
+    screen_id: usize,
     window_id: u32,
     visual_id: u32,
 }
 
 pub(crate) struct X11WindowState {
-    xcb_connection: Rc<xcb::Connection>,
+    xcb_connection: Rc<XCBConnection>,
     display: Rc<dyn PlatformDisplay>,
     raw: RawWindow,
-    x_window: x::Window,
+    x_window: xproto::Window,
     callbacks: RefCell<Callbacks>,
     inner: RefCell<LinuxWindowInner>,
 }
@@ -112,7 +113,7 @@ unsafe impl blade_rwh::HasRawDisplayHandle for RawWindow {
     fn raw_display_handle(&self) -> blade_rwh::RawDisplayHandle {
         let mut dh = blade_rwh::XcbDisplayHandle::empty();
         dh.connection = self.connection;
-        dh.screen = self.screen_id;
+        dh.screen = self.screen_id as i32;
         dh.into()
     }
 }
@@ -130,7 +131,7 @@ impl rwh::HasDisplayHandle for X11Window {
     fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
         Ok(unsafe {
             let non_zero = NonNull::new(self.0.raw.connection).unwrap();
-            let handle = rwh::XcbDisplayHandle::new(Some(non_zero), self.0.raw.screen_id);
+            let handle = rwh::XcbDisplayHandle::new(Some(non_zero), self.0.raw.screen_id as i32);
             rwh::DisplayHandle::borrow_raw(handle.into())
         })
     }
@@ -139,76 +140,76 @@ impl rwh::HasDisplayHandle for X11Window {
 impl X11WindowState {
     pub fn new(
         params: WindowParams,
-        xcb_connection: &Rc<xcb::Connection>,
-        x_main_screen_index: i32,
-        x_window: x::Window,
+        xcb_connection: &Rc<XCBConnection>,
+        x_main_screen_index: usize,
+        x_window: xproto::Window,
         atoms: &XcbAtoms,
     ) -> Self {
         let x_screen_index = params
             .display_id
-            .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();
+            .map_or(x_main_screen_index, |did| did.0 as usize);
+        let screen = xcb_connection.setup().roots.get(x_screen_index).unwrap();
+
+        let win_aux = xproto::CreateWindowAux::new().event_mask(
+            xproto::EventMask::EXPOSURE
+                | xproto::EventMask::STRUCTURE_NOTIFY
+                | xproto::EventMask::ENTER_WINDOW
+                | xproto::EventMask::LEAVE_WINDOW
+                | xproto::EventMask::FOCUS_CHANGE
+                | xproto::EventMask::KEY_PRESS
+                | xproto::EventMask::KEY_RELEASE
+                | xproto::EventMask::BUTTON_PRESS
+                | xproto::EventMask::BUTTON_RELEASE
+                | xproto::EventMask::POINTER_MOTION
+                | xproto::EventMask::BUTTON1_MOTION
+                | xproto::EventMask::BUTTON2_MOTION
+                | xproto::EventMask::BUTTON3_MOTION
+                | xproto::EventMask::BUTTON4_MOTION
+                | xproto::EventMask::BUTTON5_MOTION
+                | xproto::EventMask::BUTTON_MOTION,
+        );
 
-        let xcb_values = [
-            x::Cw::BackPixel(screen.white_pixel()),
-            x::Cw::EventMask(
-                x::EventMask::EXPOSURE
-                    | x::EventMask::STRUCTURE_NOTIFY
-                    | x::EventMask::ENTER_WINDOW
-                    | x::EventMask::LEAVE_WINDOW
-                    | x::EventMask::FOCUS_CHANGE
-                    | x::EventMask::KEY_PRESS
-                    | x::EventMask::KEY_RELEASE
-                    | x::EventMask::BUTTON_PRESS
-                    | x::EventMask::BUTTON_RELEASE
-                    | x::EventMask::POINTER_MOTION
-                    | x::EventMask::BUTTON1_MOTION
-                    | x::EventMask::BUTTON2_MOTION
-                    | x::EventMask::BUTTON3_MOTION
-                    | x::EventMask::BUTTON4_MOTION
-                    | x::EventMask::BUTTON5_MOTION
-                    | x::EventMask::BUTTON_MOTION,
-            ),
-        ];
-
-        xcb_connection.send_request(&x::CreateWindow {
-            depth: x::COPY_FROM_PARENT as u8,
-            wid: x_window,
-            parent: screen.root(),
-            x: params.bounds.origin.x.0 as i16,
-            y: params.bounds.origin.y.0 as i16,
-            width: params.bounds.size.width.0 as u16,
-            height: params.bounds.size.height.0 as u16,
-            border_width: 0,
-            class: x::WindowClass::InputOutput,
-            visual: screen.root_visual(),
-            value_list: &xcb_values,
-        });
+        xcb_connection
+            .create_window(
+                x11rb::COPY_FROM_PARENT as _,
+                x_window,
+                screen.root,
+                params.bounds.origin.x.0 as i16,
+                params.bounds.origin.y.0 as i16,
+                params.bounds.size.width.0 as u16,
+                params.bounds.size.height.0 as u16,
+                0,
+                xproto::WindowClass::INPUT_OUTPUT,
+                screen.root_visual,
+                &win_aux,
+            )
+            .unwrap();
 
         if let Some(titlebar) = params.titlebar {
             if let Some(title) = titlebar.title {
-                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(),
-                });
+                xcb_connection
+                    .change_property8(
+                        xproto::PropMode::REPLACE,
+                        x_window,
+                        xproto::AtomEnum::WM_NAME,
+                        xproto::AtomEnum::STRING,
+                        title.as_bytes(),
+                    )
+                    .unwrap();
             }
         }
-        xcb_connection.send_request(&x::ChangeProperty {
-            mode: x::PropMode::Replace,
-            window: x_window,
-            property: atoms.wm_protocols,
-            r#type: x::ATOM_ATOM,
-            data: &[atoms.wm_del_window],
-        });
-
-        xcb_connection.send_request(&x::MapWindow { window: x_window });
+
+        xcb_connection
+            .change_property32(
+                xproto::PropMode::REPLACE,
+                x_window,
+                atoms.WM_PROTOCOLS,
+                xproto::AtomEnum::ATOM,
+                &[atoms.WM_DELETE_WINDOW],
+            )
+            .unwrap();
+
+        xcb_connection.map_window(x_window).unwrap();
         xcb_connection.flush().unwrap();
 
         let raw = RawWindow {
@@ -216,8 +217,8 @@ impl X11WindowState {
                 xcb_connection,
             ) as *mut _,
             screen_id: x_screen_index,
-            window_id: x_window.resource_id(),
-            visual_id: screen.root_visual(),
+            window_id: x_window,
+            visual_id: screen.root_visual,
         };
         let gpu = Arc::new(
             unsafe {
@@ -254,12 +255,8 @@ impl X11WindowState {
 
     pub fn destroy(&self) {
         self.inner.borrow_mut().renderer.destroy();
-        self.xcb_connection.send_request(&x::UnmapWindow {
-            window: self.x_window,
-        });
-        self.xcb_connection.send_request(&x::DestroyWindow {
-            window: self.x_window,
-        });
+        self.xcb_connection.unmap_window(self.x_window).unwrap();
+        self.xcb_connection.destroy_window(self.x_window).unwrap();
         if let Some(fun) = self.callbacks.borrow_mut().close.take() {
             fun();
         }
@@ -359,14 +356,14 @@ impl PlatformWindow for X11Window {
     }
 
     fn mouse_position(&self) -> Point<Pixels> {
-        let cookie = self.0.xcb_connection.send_request(&x::QueryPointer {
-            window: self.0.x_window,
-        });
-        let reply: x::QueryPointerReply = self.0.xcb_connection.wait_for_reply(cookie).unwrap();
-        Point::new(
-            (reply.root_x() as u32).into(),
-            (reply.root_y() as u32).into(),
-        )
+        let reply = self
+            .0
+            .xcb_connection
+            .query_pointer(self.0.x_window)
+            .unwrap()
+            .reply()
+            .unwrap();
+        Point::new((reply.root_x as u32).into(), (reply.root_y as u32).into())
     }
 
     // todo(linux)
@@ -397,20 +394,24 @@ impl PlatformWindow for X11Window {
     }
 
     fn activate(&self) {
-        self.0.xcb_connection.send_request(&x::ConfigureWindow {
-            window: self.0.x_window,
-            value_list: &[x::ConfigWindow::StackMode(x::StackMode::Above)],
-        });
+        let win_aux = xproto::ConfigureWindowAux::new().stack_mode(xproto::StackMode::ABOVE);
+        self.0
+            .xcb_connection
+            .configure_window(self.0.x_window, &win_aux)
+            .unwrap();
     }
 
     fn set_title(&mut self, title: &str) {
-        self.0.xcb_connection.send_request(&x::ChangeProperty {
-            mode: x::PropMode::Replace,
-            window: self.0.x_window,
-            property: x::ATOM_WM_NAME,
-            r#type: x::ATOM_STRING,
-            data: title.as_bytes(),
-        });
+        self.0
+            .xcb_connection
+            .change_property8(
+                xproto::PropMode::REPLACE,
+                self.0.x_window,
+                xproto::AtomEnum::WM_NAME,
+                xproto::AtomEnum::STRING,
+                title.as_bytes(),
+            )
+            .unwrap();
     }
 
     // todo(linux)
@@ -443,7 +444,7 @@ impl PlatformWindow for X11Window {
 
     // todo(linux)
     fn is_full_screen(&self) -> bool {
-        unimplemented!()
+        false
     }
 
     fn on_request_frame(&self, callback: Box<dyn FnMut()>) {