x11: create window and route events

Dzmitry Malyshau created

Change summary

crates/gpui/src/platform/linux.rs                |   2 
crates/gpui/src/platform/linux/blade_atlas.rs    |  20 ++
crates/gpui/src/platform/linux/blade_renderer.rs |  11 +
crates/gpui/src/platform/linux/platform.rs       | 123 +++++++++++++++--
crates/gpui/src/platform/linux/window.rs         |  90 ++++++++----
5 files changed, 194 insertions(+), 52 deletions(-)

Detailed changes

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

@@ -1,5 +1,6 @@
 mod blade_atlas;
 mod blade_belt;
+mod blade_renderer;
 mod dispatcher;
 mod display;
 mod platform;
@@ -14,3 +15,4 @@ pub(crate) use text_system::*;
 pub(crate) use window::*;
 
 use blade_belt::*;
+use blade_renderer::*;

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

@@ -22,6 +22,22 @@ struct BladeAtlasState {
     tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
 }
 
+impl BladeAtlasState {
+    fn destroy(&mut self) {
+        for texture in self.monochrome_textures.drain(..) {
+            self.gpu.destroy_texture(texture.raw);
+        }
+        for texture in self.polychrome_textures.drain(..) {
+            self.gpu.destroy_texture(texture.raw);
+        }
+        for texture in self.path_textures.drain(..) {
+            self.gpu.destroy_texture(texture.raw);
+        }
+        self.gpu.destroy_command_encoder(&mut self.gpu_encoder);
+        self.upload_belt.destroy(&self.gpu);
+    }
+}
+
 impl BladeAtlas {
     pub(crate) fn new(gpu: &Arc<blade::Context>) -> Self {
         BladeAtlas(Mutex::new(BladeAtlasState {
@@ -41,6 +57,10 @@ impl BladeAtlas {
         }))
     }
 
+    pub(crate) fn destroy(&self) {
+        self.0.lock().destroy();
+    }
+
     pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) {
         let mut lock = self.0.lock();
         let textures = match texture_kind {

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

@@ -2,11 +2,13 @@
 
 use crate::{
     Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
-    ForegroundExecutor, Keymap, LinuxDispatcher, LinuxDisplay, LinuxTextSystem, LinuxWindow, Menu,
-    PathPromptOptions, Platform, PlatformDisplay, PlatformInput, PlatformTextSystem,
-    PlatformWindow, Result, SemanticVersion, Task, WindowOptions,
+    ForegroundExecutor, Keymap, LinuxDispatcher, LinuxDisplay, LinuxTextSystem, LinuxWindow,
+    LinuxWindowState, LinuxWindowStatePtr, Menu, PathPromptOptions, Platform, PlatformDisplay,
+    PlatformInput, PlatformTextSystem, PlatformWindow, Result, SemanticVersion, Task,
+    WindowOptions,
 };
 
+use collections::{HashMap, HashSet};
 use futures::channel::oneshot;
 use parking_lot::Mutex;
 
@@ -17,16 +19,48 @@ use std::{
     time::Duration,
 };
 use time::UtcOffset;
-use x11rb::{connection::Connection as _, rust_connection::RustConnection};
+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,
+        }
+    }
+}
+
 pub(crate) struct LinuxPlatformState {
     x11_connection: RustConnection,
     x11_root_index: usize,
-    gpu: Arc<blade::Context>,
+    atoms: WmAtoms,
     background_executor: BackgroundExecutor,
     foreground_executor: ForegroundExecutor,
+    windows: HashMap<u32, LinuxWindowStatePtr>,
     text_system: Arc<LinuxTextSystem>,
 }
 
@@ -39,24 +73,17 @@ 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 dispatcher = Arc::new(LinuxDispatcher::new());
-        let gpu = Arc::new(
-            unsafe {
-                blade::Context::init(blade::ContextDesc {
-                    validation: cfg!(debug_assertions),
-                    capture: false,
-                })
-            }
-            .unwrap(),
-        );
 
         Self(Mutex::new(LinuxPlatformState {
             x11_connection,
             x11_root_index,
-            gpu,
+            atoms,
             background_executor: BackgroundExecutor::new(dispatcher.clone()),
             foreground_executor: ForegroundExecutor::new(dispatcher),
+            windows: HashMap::default(),
             text_system: Arc::new(LinuxTextSystem::new()),
         }))
     }
@@ -76,7 +103,58 @@ impl Platform for LinuxPlatform {
     }
 
     fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
-        on_finish_launching()
+        on_finish_launching();
+
+        let mut need_repaint = HashSet::<u32>::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 mut lock = self.0.lock();
+                        let data = event.data.as_data32();
+                        if data[0] == lock.atoms.delete_window {
+                            {
+                                let mut window = lock.windows[&event.window].lock();
+                                window.destroy();
+                            }
+                            lock.windows.remove(&event.window);
+                        }
+                    }
+                    Event::Error(error) => {
+                        log::error!("X11 error {:?}", error);
+                    }
+                    _ => {}
+                }
+
+                let lock = self.0.lock();
+                event_option = lock.x11_connection.poll_for_event().unwrap();
+            }
+
+            for x11_window in need_repaint.drain() {
+                let lock = self.0.lock();
+                let mut window = lock.windows[&x11_window].lock();
+                window.paint();
+                lock.x11_connection.flush().unwrap();
+            }
+        }
     }
 
     fn quit(&self) {}
@@ -118,14 +196,19 @@ impl Platform for LinuxPlatform {
         handle: AnyWindowHandle,
         options: WindowOptions,
     ) -> Box<dyn PlatformWindow> {
-        let lock = self.0.lock();
-        Box::new(LinuxWindow::new(
+        let mut lock = self.0.lock();
+        let win_id = lock.x11_connection.generate_id().unwrap();
+
+        let window_ptr = LinuxWindowState::new_ptr(
             options,
             handle,
             &lock.x11_connection,
             lock.x11_root_index,
-            &lock.gpu,
-        ))
+            win_id,
+            &lock.atoms,
+        );
+        lock.windows.insert(win_id, window_ptr.clone());
+        Box::new(LinuxWindow(window_ptr))
     }
 
     fn set_display_link_output_callback(

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

@@ -1,8 +1,9 @@
+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,
+    WindowOptions, WmAtoms,
 };
 use collections::HashMap;
 use parking_lot::Mutex;
@@ -21,45 +22,37 @@ use x11rb::{
 
 pub(crate) struct LinuxWindowState {
     display: Rc<dyn PlatformDisplay>,
-    win_id: u32,
+    x11_window: u32,
+    window_bounds: WindowBounds,
+    content_size: Size<Pixels>,
     sprite_atlas: Arc<BladeAtlas>,
+    renderer: BladeRenderer,
 }
 
+pub(crate) type LinuxWindowStatePtr = Arc<Mutex<LinuxWindowState>>;
 #[derive(Clone)]
-pub(crate) struct LinuxWindow(pub(crate) Arc<Mutex<LinuxWindowState>>);
+pub(crate) struct LinuxWindow(pub(crate) LinuxWindowStatePtr);
 
-impl LinuxWindow {
-    pub fn new(
+impl LinuxWindowState {
+    pub fn new_ptr(
         options: WindowOptions,
         handle: AnyWindowHandle,
         x11_connection: &RustConnection,
         x11_main_screen_index: usize,
-        gpu: &Arc<blade::Context>,
-    ) -> Self {
+        x11_window: u32,
+        atoms: &WmAtoms,
+    ) -> LinuxWindowStatePtr {
         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)
@@ -75,7 +68,7 @@ impl LinuxWindow {
         x11_connection
             .create_window(
                 x11rb::COPY_DEPTH_FROM_PARENT,
-                win_id,
+                x11_window,
                 screen.root,
                 bound_x,
                 bound_y,
@@ -93,7 +86,7 @@ impl LinuxWindow {
                 x11_connection
                     .change_property8(
                         PropMode::REPLACE,
-                        win_id,
+                        x11_window,
                         AtomEnum::WM_NAME,
                         AtomEnum::STRING,
                         title.as_bytes(),
@@ -104,30 +97,63 @@ impl LinuxWindow {
         x11_connection
             .change_property32(
                 PropMode::REPLACE,
-                win_id,
-                wm_protocols,
+                x11_window,
+                atoms.protocols,
                 AtomEnum::ATOM,
-                &[wm_delete_window],
+                &[atoms.delete_window],
             )
             .unwrap();
 
-        x11_connection.map_window(win_id).unwrap();
+        x11_connection.map_window(x11_window).unwrap();
+        x11_connection.flush().unwrap();
 
-        Self(Arc::new(Mutex::new(LinuxWindowState {
+        let gpu = Arc::new(
+            unsafe {
+                blade::Context::init(blade::ContextDesc {
+                    validation: cfg!(debug_assertions),
+                    capture: false,
+                })
+            }
+            .unwrap(),
+        );
+
+        Arc::new(Mutex::new(Self {
             display: Rc::new(LinuxDisplay::new(x11_connection, x11_screen_index)),
-            win_id,
-            sprite_atlas: Arc::new(BladeAtlas::new(gpu)),
-        })))
+            x11_window,
+            window_bounds: options.bounds,
+            content_size: Size {
+                width: Pixels(bound_width as f32),
+                height: Pixels(bound_height as f32),
+            },
+            sprite_atlas: Arc::new(BladeAtlas::new(&gpu)),
+            renderer: BladeRenderer::new(gpu),
+        }))
+    }
+
+    pub fn resize(&mut self, width: u16, height: u16) {
+        self.content_size = Size {
+            width: Pixels(width as f32),
+            height: Pixels(height as f32),
+        };
+    }
+
+    pub fn destroy(&mut self) {
+        self.sprite_atlas.destroy();
+    }
+
+    pub fn paint(&mut self) {
+        //TODO
     }
 }
 
 impl PlatformWindow for LinuxWindow {
     fn bounds(&self) -> WindowBounds {
-        unimplemented!()
+        //TODO: update when window moves
+        self.0.lock().window_bounds
     }
 
     fn content_size(&self) -> Size<Pixels> {
-        unimplemented!()
+        self.0.lock().content_size
     }
 
     fn scale_factor(&self) -> f32 {