linux: hook up X11rb for Window creation

Dzmitry Malyshau created

Change summary

Cargo.lock                                 |  28 ++++++
crates/gpui/Cargo.toml                     |   1 
crates/gpui/src/platform/linux/display.rs  |  35 ++++++-
crates/gpui/src/platform/linux/platform.rs |  27 +++++
crates/gpui/src/platform/linux/window.rs   | 101 ++++++++++++++++++++++-
5 files changed, 174 insertions(+), 18 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -3088,6 +3088,16 @@ dependencies = [
  "version_check",
 ]
 
+[[package]]
+name = "gethostname"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818"
+dependencies = [
+ "libc",
+ "windows-targets 0.48.5",
+]
+
 [[package]]
 name = "getrandom"
 version = "0.1.16"
@@ -3277,6 +3287,7 @@ dependencies = [
  "util",
  "uuid 1.4.1",
  "waker-fn",
+ "x11rb",
 ]
 
 [[package]]
@@ -10273,6 +10284,23 @@ dependencies = [
  "tap",
 ]
 
+[[package]]
+name = "x11rb"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8f25ead8c7e4cba123243a6367da5d3990e0d3affa708ea19dce96356bd9f1a"
+dependencies = [
+ "gethostname",
+ "rustix 0.38.30",
+ "x11rb-protocol",
+]
+
+[[package]]
+name = "x11rb-protocol"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e63e71c4b8bd9ffec2c963173a4dc4cbde9ee96961d4fcb4429db9929b606c34"
+
 [[package]]
 name = "xattr"
 version = "0.2.3"

crates/gpui/Cargo.toml 🔗

@@ -96,3 +96,4 @@ objc = "0.2"
 
 [target.'cfg(target_os = "linux")'.dependencies]
 flume = "0.11"
+x11rb = "0.13"

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

@@ -1,23 +1,42 @@
-use crate::{point, size, Bounds, DisplayId, GlobalPixels, PlatformDisplay};
+use crate::{point, size, 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;
+pub(crate) struct LinuxDisplay {
+    x11_screen_index: usize,
+    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];
+        Self {
+            x11_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),
+                },
+            },
+            uuid: Uuid::from_bytes([0; 16]),
+        }
+    }
+}
 
 impl PlatformDisplay for LinuxDisplay {
     fn id(&self) -> DisplayId {
-        DisplayId(0)
+        DisplayId(self.x11_screen_index as u32)
     }
 
     fn uuid(&self) -> Result<Uuid> {
-        Ok(Uuid::from_bytes([0; 16]))
+        Ok(self.uuid)
     }
 
     fn bounds(&self) -> Bounds<GlobalPixels> {
-        Bounds {
-            origin: point(GlobalPixels(0.0), GlobalPixels(0.0)),
-            size: size(GlobalPixels(100.0), GlobalPixels(100.0)),
-        }
+        self.bounds
     }
 }

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

@@ -17,10 +17,13 @@ use std::{
     time::Duration,
 };
 use time::UtcOffset;
+use x11rb::{connection::Connection as _, rust_connection::RustConnection};
 
 pub(crate) struct LinuxPlatform(Mutex<LinuxPlatformState>);
 
 pub(crate) struct LinuxPlatformState {
+    x11_connection: RustConnection,
+    x11_root_index: usize,
     gpu: Arc<blade::Context>,
     background_executor: BackgroundExecutor,
     foreground_executor: ForegroundExecutor,
@@ -35,17 +38,22 @@ impl Default for LinuxPlatform {
 
 impl LinuxPlatform {
     pub(crate) fn new() -> Self {
+        let (x11_connection, x11_root_index) = x11rb::connect(None).unwrap();
+
         let dispatcher = Arc::new(LinuxDispatcher::new());
         let gpu = Arc::new(
             unsafe {
                 blade::Context::init(blade::ContextDesc {
-                    validation: true, //FIXME
+                    validation: cfg!(debug_assertions),
                     capture: false,
                 })
             }
             .unwrap(),
         );
+
         Self(Mutex::new(LinuxPlatformState {
+            x11_connection,
+            x11_root_index,
             gpu,
             background_executor: BackgroundExecutor::new(dispatcher.clone()),
             foreground_executor: ForegroundExecutor::new(dispatcher),
@@ -84,11 +92,21 @@ impl Platform for LinuxPlatform {
     fn unhide_other_apps(&self) {}
 
     fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
-        Vec::new()
+        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>
+            })
+            .collect()
     }
 
     fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
-        None
+        let lock = self.0.lock();
+        Some(Rc::new(LinuxDisplay::new(
+            &lock.x11_connection,
+            id.0 as usize,
+        )))
     }
 
     fn active_window(&self) -> Option<AnyWindowHandle> {
@@ -104,7 +122,8 @@ impl Platform for LinuxPlatform {
         Box::new(LinuxWindow::new(
             options,
             handle,
-            Rc::new(LinuxDisplay),
+            &lock.x11_connection,
+            lock.x11_root_index,
             &lock.gpu,
         ))
     }

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

@@ -1,7 +1,8 @@
 use crate::{
     px, AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, BladeAtlas, Bounds, KeyDownEvent,
-    Keystroke, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler,
-    PlatformWindow, Point, Size, TileId, WindowAppearance, WindowBounds, WindowOptions,
+    Keystroke, LinuxDisplay, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
+    PlatformInputHandler, PlatformWindow, Point, Size, TileId, WindowAppearance, WindowBounds,
+    WindowOptions,
 };
 use collections::HashMap;
 use parking_lot::Mutex;
@@ -9,9 +10,18 @@ use std::{
     rc::{Rc, Weak},
     sync::{self, Arc},
 };
+use x11rb::{
+    connection::Connection as _,
+    protocol::xproto::{
+        AtomEnum, ConnectionExt as _, CreateWindowAux, EventMask, PropMode, WindowClass,
+    },
+    rust_connection::RustConnection,
+    wrapper::ConnectionExt as _,
+};
 
 pub(crate) struct LinuxWindowState {
-    display: Rc<dyn crate::PlatformDisplay>,
+    display: Rc<dyn PlatformDisplay>,
+    win_id: u32,
     sprite_atlas: Arc<BladeAtlas>,
 }
 
@@ -22,11 +32,90 @@ impl LinuxWindow {
     pub fn new(
         options: WindowOptions,
         handle: AnyWindowHandle,
-        display: Rc<dyn PlatformDisplay>,
+        x11_connection: &RustConnection,
+        x11_main_screen_index: usize,
         gpu: &Arc<blade::Context>,
     ) -> Self {
+        let x11_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];
+
+        let win_id = x11_connection.generate_id().unwrap();
+        let win_aux = CreateWindowAux::new()
+            .event_mask(
+                EventMask::EXPOSURE | EventMask::STRUCTURE_NOTIFY | EventMask::POINTER_MOTION,
+            )
+            .background_pixel(screen.white_pixel);
+
+        let wm_protocols = x11_connection
+            .intern_atom(false, b"WM_PROTOCOLS")
+            .unwrap()
+            .reply()
+            .unwrap()
+            .atom;
+        let wm_delete_window = x11_connection
+            .intern_atom(false, b"WM_DELETE_WINDOW")
+            .unwrap()
+            .reply()
+            .unwrap()
+            .atom;
+        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)
+            }
+            WindowBounds::Fixed(bounds) => (
+                bounds.origin.x.0 as i16,
+                bounds.origin.y.0 as i16,
+                bounds.size.width.0 as u16,
+                bounds.size.height.0 as u16,
+            ),
+        };
+
+        x11_connection
+            .create_window(
+                x11rb::COPY_DEPTH_FROM_PARENT,
+                win_id,
+                screen.root,
+                bound_x,
+                bound_y,
+                bound_width,
+                bound_height,
+                0,
+                WindowClass::INPUT_OUTPUT,
+                0,
+                &win_aux,
+            )
+            .unwrap();
+
+        if let Some(titlebar) = options.titlebar {
+            if let Some(title) = titlebar.title {
+                x11_connection
+                    .change_property8(
+                        PropMode::REPLACE,
+                        win_id,
+                        AtomEnum::WM_NAME,
+                        AtomEnum::STRING,
+                        title.as_bytes(),
+                    )
+                    .unwrap();
+            }
+        }
+        x11_connection
+            .change_property32(
+                PropMode::REPLACE,
+                win_id,
+                wm_protocols,
+                AtomEnum::ATOM,
+                &[wm_delete_window],
+            )
+            .unwrap();
+
+        x11_connection.map_window(win_id).unwrap();
+
         Self(Arc::new(Mutex::new(LinuxWindowState {
-            display,
+            display: Rc::new(LinuxDisplay::new(x11_connection, x11_screen_index)),
+            win_id,
             sprite_atlas: Arc::new(BladeAtlas::new(gpu)),
         })))
     }
@@ -53,7 +142,7 @@ impl PlatformWindow for LinuxWindow {
         unimplemented!()
     }
 
-    fn display(&self) -> Rc<dyn crate::PlatformDisplay> {
+    fn display(&self) -> Rc<dyn PlatformDisplay> {
         Rc::clone(&self.0.lock().display)
     }