Linux/x11 input handling (#7811)

Dzmitry Malyshau and Mikayla Maki created

Implements the basics of keyboard and mouse handling.
Some keys will need special treatment, like Backspace/Delete. In this
PR, all keys are treated as append-only. Leaving this for a follow-up.

I used @gabydd 's branch as a reference (thank you!) as well as
https://github.com/xkbcommon/libxkbcommon/blob/master/doc/quick-guide.md
For future work, I'll also use
https://github.com/xkbcommon/libxkbcommon/blob/master/tools/interactive-x11.c

All commits are separately compileable and reviewable.

Release Notes:
- N/A

---------

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

Change summary

Cargo.lock                                        |  19 ++
crates/gpui/Cargo.toml                            |   4 
crates/gpui/src/platform/linux.rs                 |   3 
crates/gpui/src/platform/linux/blade_renderer.rs  |   4 
crates/gpui/src/platform/linux/platform.rs        |  36 +++-
crates/gpui/src/platform/linux/wayland.rs         |   3 
crates/gpui/src/platform/linux/wayland/client.rs  |  17 +-
crates/gpui/src/platform/linux/wayland/display.rs |   4 
crates/gpui/src/platform/linux/wayland/window.rs  |   4 
crates/gpui/src/platform/linux/x11.rs             |   4 
crates/gpui/src/platform/linux/x11/client.rs      | 118 ++++++++++++++++
crates/gpui/src/platform/linux/x11/event.rs       |  34 ++++
crates/gpui/src/platform/linux/x11/window.rs      |  51 +++++-
script/clippy                                     |   2 
script/linux                                      |   3 
15 files changed, 253 insertions(+), 53 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -3528,6 +3528,7 @@ dependencies = [
  "wayland-client",
  "wayland-protocols",
  "xcb",
+ "xkbcommon",
 ]
 
 [[package]]
@@ -10840,6 +10841,24 @@ dependencies = [
  "quick-xml 0.30.0",
 ]
 
+[[package]]
+name = "xkbcommon"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13867d259930edc7091a6c41b4ce6eee464328c6ff9659b7e4c668ca20d4c91e"
+dependencies = [
+ "as-raw-xcb-connection",
+ "libc",
+ "memmap2 0.8.0",
+ "xkeysym",
+]
+
+[[package]]
+name = "xkeysym"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "054a8e68b76250b253f671d1268cb7f1ae089ec35e195b2efb2a4e9a836d0621"
+
 [[package]]
 name = "xmlparser"
 version = "0.13.5"

crates/gpui/Cargo.toml 🔗

@@ -96,7 +96,8 @@ objc = "0.2"
 
 [target.'cfg(target_os = "linux")'.dependencies]
 flume = "0.11"
-xcb = { version = "1.3", features = ["as-raw-xcb-connection", "present", "randr"] }
+# todo!(linux) - Technically do not use `randr`, but it doesn't compile otherwise
+xcb = { version = "1.3", features = ["as-raw-xcb-connection", "present", "randr", "xkb"] }
 wayland-client= { version = "0.31.2" }
 wayland-protocols = { version = "0.31.2", features = ["client"] }
 wayland-backend = { version = "0.3.3", features = ["client_system"] }
@@ -106,3 +107,4 @@ blade-graphics = { git = "https://github.com/kvark/blade", rev = "c4f951a88b3457
 blade-macros = { git = "https://github.com/kvark/blade", rev = "c4f951a88b345724cb952e920ad30e39851f7760" }
 bytemuck = "1"
 cosmic-text = "0.10.0"
+xkbcommon = { version = "0.7", features = ["x11"] }

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

@@ -458,7 +458,7 @@ impl BladeRenderer {
                         sprites,
                     } => {
                         let tex_info = self.atlas.get_texture_info(texture_id);
-                        let instance_buf = self.instance_belt.alloc_data(&sprites, &self.gpu);
+                        let instance_buf = self.instance_belt.alloc_data(sprites, &self.gpu);
                         let mut encoder = pass.with(&self.pipelines.mono_sprites);
                         encoder.bind(
                             0,
@@ -476,7 +476,7 @@ impl BladeRenderer {
                         sprites,
                     } => {
                         let tex_info = self.atlas.get_texture_info(texture_id);
-                        let instance_buf = self.instance_belt.alloc_data(&sprites, &self.gpu);
+                        let instance_buf = self.instance_belt.alloc_data(sprites, &self.gpu);
                         let mut encoder = pass.with(&self.pipelines.poly_sprites);
                         encoder.bind(
                             0,

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

@@ -49,8 +49,8 @@ pub(crate) struct LinuxPlatformInner {
 }
 
 pub(crate) struct LinuxPlatform {
-    client: Arc<dyn Client>,
-    inner: Arc<LinuxPlatformInner>,
+    client: Rc<dyn Client>,
+    inner: Rc<LinuxPlatformInner>,
 }
 
 pub(crate) struct LinuxPlatformState {
@@ -93,7 +93,7 @@ impl LinuxPlatform {
         let client_dispatcher: Arc<dyn ClientDispatcher + Send + Sync> =
             Arc::new(WaylandClientDispatcher::new(&conn));
         let dispatcher = Arc::new(LinuxDispatcher::new(main_sender, &client_dispatcher));
-        let inner = Arc::new(LinuxPlatformInner {
+        let inner = Rc::new(LinuxPlatformInner {
             background_executor: BackgroundExecutor::new(dispatcher.clone()),
             foreground_executor: ForegroundExecutor::new(dispatcher.clone()),
             main_receiver,
@@ -101,10 +101,10 @@ impl LinuxPlatform {
             callbacks,
             state,
         });
-        let client = Arc::new(WaylandClient::new(Arc::clone(&inner), Arc::clone(&conn)));
+        let client = Rc::new(WaylandClient::new(Rc::clone(&inner), Arc::clone(&conn)));
         Self {
             client,
-            inner: Arc::clone(&inner),
+            inner: Rc::clone(&inner),
         }
     }
 
@@ -115,15 +115,27 @@ impl LinuxPlatform {
         callbacks: Mutex<Callbacks>,
         state: Mutex<LinuxPlatformState>,
     ) -> Self {
-        let (xcb_connection, x_root_index) =
-            xcb::Connection::connect_with_extensions(None, &[xcb::Extension::Present], &[])
-                .unwrap();
+        let (xcb_connection, x_root_index) = xcb::Connection::connect_with_extensions(
+            None,
+            &[xcb::Extension::Present, 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,
+            }))
+            .unwrap();
+        assert!(xkb_ver.supported());
+
         let atoms = XcbAtoms::intern_all(&xcb_connection).unwrap();
         let xcb_connection = Arc::new(xcb_connection);
         let client_dispatcher: Arc<dyn ClientDispatcher + Send + Sync> =
             Arc::new(X11ClientDispatcher::new(&xcb_connection, x_root_index));
         let dispatcher = Arc::new(LinuxDispatcher::new(main_sender, &client_dispatcher));
-        let inner = Arc::new(LinuxPlatformInner {
+        let inner = Rc::new(LinuxPlatformInner {
             background_executor: BackgroundExecutor::new(dispatcher.clone()),
             foreground_executor: ForegroundExecutor::new(dispatcher.clone()),
             main_receiver,
@@ -131,15 +143,15 @@ impl LinuxPlatform {
             callbacks,
             state,
         });
-        let client = Arc::new(X11Client::new(
-            Arc::clone(&inner),
+        let client = Rc::new(X11Client::new(
+            Rc::clone(&inner),
             xcb_connection,
             x_root_index,
             atoms,
         ));
         Self {
             client,
-            inner: Arc::clone(&inner),
+            inner: Rc::clone(&inner),
         }
     }
 }

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

@@ -1,3 +1,6 @@
+//todo!(linux): remove this once the relevant functionality has been implemented
+#![allow(unused_variables)]
+
 pub(crate) use client::*;
 pub(crate) use client_dispatcher::*;
 

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

@@ -26,12 +26,12 @@ pub(crate) struct WaylandClientState {
     compositor: Option<wl_compositor::WlCompositor>,
     buffer: Option<wl_buffer::WlBuffer>,
     wm_base: Option<xdg_wm_base::XdgWmBase>,
-    windows: Vec<(xdg_surface::XdgSurface, Arc<WaylandWindowState>)>,
-    platform_inner: Arc<LinuxPlatformInner>,
+    windows: Vec<(xdg_surface::XdgSurface, Rc<WaylandWindowState>)>,
+    platform_inner: Rc<LinuxPlatformInner>,
 }
 
 pub(crate) struct WaylandClient {
-    platform_inner: Arc<LinuxPlatformInner>,
+    platform_inner: Rc<LinuxPlatformInner>,
     conn: Arc<Connection>,
     state: Mutex<WaylandClientState>,
     event_queue: Mutex<EventQueue<WaylandClientState>>,
@@ -39,16 +39,13 @@ pub(crate) struct WaylandClient {
 }
 
 impl WaylandClient {
-    pub(crate) fn new(
-        linux_platform_inner: Arc<LinuxPlatformInner>,
-        conn: Arc<Connection>,
-    ) -> Self {
+    pub(crate) fn new(linux_platform_inner: Rc<LinuxPlatformInner>, conn: Arc<Connection>) -> Self {
         let state = WaylandClientState {
             compositor: None,
             buffer: None,
             wm_base: None,
             windows: Vec::new(),
-            platform_inner: Arc::clone(&linux_platform_inner),
+            platform_inner: Rc::clone(&linux_platform_inner),
         };
         let event_queue: EventQueue<WaylandClientState> = conn.new_event_queue();
         let qh = event_queue.handle();
@@ -109,14 +106,14 @@ impl Client for WaylandClient {
         wl_surface.frame(&self.qh, wl_surface.clone());
         wl_surface.commit();
 
-        let window_state: Arc<WaylandWindowState> = Arc::new(WaylandWindowState::new(
+        let window_state = Rc::new(WaylandWindowState::new(
             &self.conn,
             wl_surface.clone(),
             Arc::new(toplevel),
             options,
         ));
 
-        state.windows.push((xdg_surface, Arc::clone(&window_state)));
+        state.windows.push((xdg_surface, Rc::clone(&window_state)));
         Box::new(WaylandWindow(window_state))
     }
 }

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

@@ -10,12 +10,12 @@ pub(crate) struct WaylandDisplay {}
 impl PlatformDisplay for WaylandDisplay {
     // todo!(linux)
     fn id(&self) -> DisplayId {
-        return DisplayId(123); // return some fake data so it doesn't panic
+        DisplayId(123) // return some fake data so it doesn't panic
     }
 
     // todo!(linux)
     fn uuid(&self) -> anyhow::Result<Uuid> {
-        return Ok(Uuid::from_bytes([0; 16])); // return some fake data so it doesn't panic
+        Ok(Uuid::from_bytes([0; 16])) // return some fake data so it doesn't panic
     }
 
     // todo!(linux)

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

@@ -181,7 +181,7 @@ impl WaylandWindowState {
 }
 
 #[derive(Clone)]
-pub(crate) struct WaylandWindow(pub(crate) Arc<WaylandWindowState>);
+pub(crate) struct WaylandWindow(pub(crate) Rc<WaylandWindowState>);
 
 impl HasWindowHandle for WaylandWindow {
     fn window_handle(&self) -> Result<WindowHandle<'_>, HandleError> {
@@ -212,7 +212,7 @@ impl PlatformWindow for WaylandWindow {
 
     // todo!(linux)
     fn scale_factor(&self) -> f32 {
-        return 1f32;
+        1f32
     }
 
     //todo!(linux)

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

@@ -1,9 +1,11 @@
 mod client;
 mod client_dispatcher;
-pub mod display;
+mod display;
+mod event;
 mod window;
 
 pub(crate) use client::*;
 pub(crate) use client_dispatcher::*;
 pub(crate) use display::*;
+pub(crate) use event::*;
 pub(crate) use window::*;

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

@@ -1,8 +1,8 @@
-use std::rc::Rc;
-use std::sync::Arc;
+use std::{rc::Rc, sync::Arc};
 
 use parking_lot::Mutex;
-use xcb::{x, Xid};
+use xcb::{x, Xid as _};
+use xkbcommon::xkb;
 
 use collections::HashMap;
 
@@ -10,14 +10,17 @@ use crate::platform::linux::client::Client;
 use crate::platform::{
     LinuxPlatformInner, PlatformWindow, X11Display, X11Window, X11WindowState, XcbAtoms,
 };
-use crate::{AnyWindowHandle, Bounds, DisplayId, PlatformDisplay, Point, Size, WindowOptions};
+use crate::{
+    AnyWindowHandle, Bounds, DisplayId, PlatformDisplay, PlatformInput, Point, Size, WindowOptions,
+};
 
 pub(crate) struct X11ClientState {
     pub(crate) windows: HashMap<x::Window, Rc<X11WindowState>>,
+    xkb: xkbcommon::xkb::State,
 }
 
 pub(crate) struct X11Client {
-    platform_inner: Arc<LinuxPlatformInner>,
+    platform_inner: Rc<LinuxPlatformInner>,
     xcb_connection: Arc<xcb::Connection>,
     x_root_index: i32,
     atoms: XcbAtoms,
@@ -26,11 +29,22 @@ pub(crate) struct X11Client {
 
 impl X11Client {
     pub(crate) fn new(
-        inner: Arc<LinuxPlatformInner>,
+        inner: Rc<LinuxPlatformInner>,
         xcb_connection: Arc<xcb::Connection>,
         x_root_index: i32,
         atoms: XcbAtoms,
     ) -> Self {
+        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(
+            &xkb_context,
+            &xcb_connection,
+            xkb_device_id,
+            xkb::KEYMAP_COMPILE_NO_FLAGS,
+        );
+        let xkb_state =
+            xkb::x11::state_new_from_device(&xkb_keymap, &xcb_connection, xkb_device_id);
+
         Self {
             platform_inner: inner,
             xcb_connection,
@@ -38,6 +52,7 @@ impl X11Client {
             atoms,
             state: Mutex::new(X11ClientState {
                 windows: HashMap::default(),
+                xkb: xkb_state,
             }),
         }
     }
@@ -91,6 +106,97 @@ impl Client for X11Client {
                     window.request_refresh();
                 }
                 xcb::Event::Present(xcb::present::Event::IdleNotify(_ev)) => {}
+                xcb::Event::X(x::Event::KeyPress(ev)) => {
+                    let window = self.get_window(ev.event());
+                    let modifiers = super::modifiers_from_state(ev.state());
+                    let key = {
+                        let code = ev.detail().into();
+                        let mut state = self.state.lock();
+                        let key = state.xkb.key_get_utf8(code);
+                        state.xkb.update_key(code, xkb::KeyDirection::Down);
+                        key
+                    };
+                    window.handle_input(PlatformInput::KeyDown(crate::KeyDownEvent {
+                        keystroke: crate::Keystroke {
+                            modifiers,
+                            key,
+                            ime_key: None,
+                        },
+                        is_held: false,
+                    }));
+                }
+                xcb::Event::X(x::Event::KeyRelease(ev)) => {
+                    let window = self.get_window(ev.event());
+                    let modifiers = super::modifiers_from_state(ev.state());
+                    let key = {
+                        let code = ev.detail().into();
+                        let mut state = self.state.lock();
+                        let key = state.xkb.key_get_utf8(code);
+                        state.xkb.update_key(code, xkb::KeyDirection::Up);
+                        key
+                    };
+                    window.handle_input(PlatformInput::KeyUp(crate::KeyUpEvent {
+                        keystroke: crate::Keystroke {
+                            modifiers,
+                            key,
+                            ime_key: None,
+                        },
+                    }));
+                }
+                xcb::Event::X(x::Event::ButtonPress(ev)) => {
+                    let window = self.get_window(ev.event());
+                    let modifiers = super::modifiers_from_state(ev.state());
+                    let position =
+                        Point::new((ev.event_x() as f32).into(), (ev.event_y() as f32).into());
+                    if let Some(button) = super::button_of_key(ev.detail()) {
+                        window.handle_input(PlatformInput::MouseDown(crate::MouseDownEvent {
+                            button,
+                            position,
+                            modifiers,
+                            click_count: 1,
+                        }));
+                    } else {
+                        log::warn!("Unknown button press: {ev:?}");
+                    }
+                }
+                xcb::Event::X(x::Event::ButtonRelease(ev)) => {
+                    let window = self.get_window(ev.event());
+                    let modifiers = super::modifiers_from_state(ev.state());
+                    let position =
+                        Point::new((ev.event_x() as f32).into(), (ev.event_y() as f32).into());
+                    if let Some(button) = super::button_of_key(ev.detail()) {
+                        window.handle_input(PlatformInput::MouseUp(crate::MouseUpEvent {
+                            button,
+                            position,
+                            modifiers,
+                            click_count: 1,
+                        }));
+                    }
+                }
+                xcb::Event::X(x::Event::MotionNotify(ev)) => {
+                    let window = self.get_window(ev.event());
+                    let pressed_button = super::button_from_state(ev.state());
+                    let position =
+                        Point::new((ev.event_x() as f32).into(), (ev.event_y() as f32).into());
+                    let modifiers = super::modifiers_from_state(ev.state());
+                    window.handle_input(PlatformInput::MouseMove(crate::MouseMoveEvent {
+                        pressed_button,
+                        position,
+                        modifiers,
+                    }));
+                }
+                xcb::Event::X(x::Event::LeaveNotify(ev)) => {
+                    let window = self.get_window(ev.event());
+                    let pressed_button = super::button_from_state(ev.state());
+                    let position =
+                        Point::new((ev.event_x() as f32).into(), (ev.event_y() as f32).into());
+                    let modifiers = super::modifiers_from_state(ev.state());
+                    window.handle_input(PlatformInput::MouseExited(crate::MouseExitEvent {
+                        pressed_button,
+                        position,
+                        modifiers,
+                    }));
+                }
                 _ => {}
             }
 

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

@@ -0,0 +1,34 @@
+use xcb::x;
+
+use crate::{Modifiers, MouseButton};
+
+pub(crate) fn button_of_key(detail: x::Button) -> Option<MouseButton> {
+    Some(match detail {
+        1 => MouseButton::Left,
+        2 => MouseButton::Middle,
+        3 => MouseButton::Right,
+        _ => return None,
+    })
+}
+
+pub(crate) fn modifiers_from_state(state: x::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),
+        function: false,
+    }
+}
+
+pub(crate) fn button_from_state(state: x::KeyButMask) -> Option<MouseButton> {
+    Some(if state.contains(x::KeyButMask::BUTTON1) {
+        MouseButton::Left
+    } else if state.contains(x::KeyButMask::BUTTON2) {
+        MouseButton::Middle
+    } else if state.contains(x::KeyButMask::BUTTON3) {
+        MouseButton::Right
+    } else {
+        return None;
+    })
+}

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

@@ -13,15 +13,12 @@ use std::{
 use blade_graphics as gpu;
 use parking_lot::Mutex;
 use raw_window_handle as rwh;
-use xcb::{
-    x::{self, StackMode},
-    Xid as _,
-};
+use xcb::{x, Xid as _};
 
 use crate::platform::linux::blade_renderer::BladeRenderer;
 use crate::{
-    Bounds, GlobalPixels, Pixels, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
-    Size, WindowAppearance, WindowBounds, WindowOptions, X11Display,
+    Bounds, GlobalPixels, Pixels, PlatformDisplay, PlatformInput, PlatformInputHandler,
+    PlatformWindow, Point, Size, WindowAppearance, WindowBounds, WindowOptions, X11Display,
 };
 
 #[derive(Default)]
@@ -52,6 +49,7 @@ struct LinuxWindowInner {
     bounds: Bounds<i32>,
     scale_factor: f32,
     renderer: BladeRenderer,
+    input_handler: Option<PlatformInputHandler>,
 }
 
 impl LinuxWindowInner {
@@ -152,7 +150,19 @@ impl X11WindowState {
         let xcb_values = [
             x::Cw::BackPixel(screen.white_pixel()),
             x::Cw::EventMask(
-                x::EventMask::EXPOSURE | x::EventMask::STRUCTURE_NOTIFY | x::EventMask::KEY_PRESS,
+                x::EventMask::EXPOSURE
+                    | x::EventMask::STRUCTURE_NOTIFY
+                    | 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,
             ),
         ];
 
@@ -238,7 +248,7 @@ impl X11WindowState {
 
         // Note: this has to be done after the GPU init, or otherwise
         // the sizes are immediately invalidated.
-        let gpu_extent = query_render_extent(&xcb_connection, x_window);
+        let gpu_extent = query_render_extent(xcb_connection, x_window);
 
         Self {
             xcb_connection: Arc::clone(xcb_connection),
@@ -250,6 +260,7 @@ impl X11WindowState {
                 bounds,
                 scale_factor: 1.0,
                 renderer: BladeRenderer::new(gpu, gpu_extent),
+                input_handler: None,
             }),
         }
     }
@@ -313,6 +324,20 @@ impl X11WindowState {
             })
             .unwrap();
     }
+
+    pub fn handle_input(&self, input: PlatformInput) {
+        if let Some(ref mut fun) = self.callbacks.lock().input {
+            if fun(input.clone()) {
+                return;
+            }
+        }
+        if let PlatformInput::KeyDown(event) = input {
+            let mut inner = self.inner.lock();
+            if let Some(ref mut input_handler) = inner.input_handler {
+                input_handler.replace_text_in_range(None, &event.keystroke.key);
+            }
+        }
+    }
 }
 
 impl PlatformWindow for X11Window {
@@ -356,12 +381,12 @@ impl PlatformWindow for X11Window {
         self
     }
 
-    //todo!(linux)
-    fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {}
+    fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
+        self.0.inner.lock().input_handler = Some(input_handler);
+    }
 
-    //todo!(linux)
     fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
-        None
+        self.0.inner.lock().input_handler.take()
     }
 
     //todo!(linux)
@@ -378,7 +403,7 @@ 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(StackMode::Above)],
+            value_list: &[x::ConfigWindow::StackMode(x::StackMode::Above)],
         });
     }
 

script/clippy 🔗

@@ -6,4 +6,4 @@ set -euxo pipefail
 # so specify those here, and disable the rest until Zed's workspace
 # will have more fixes & suppression for the standard lint set
 cargo clippy --release --workspace --all-features --all-targets -- -A clippy::all -D clippy::dbg_macro -D clippy::todo
-cargo clippy -p gpui
+cargo clippy -p gpui -- -D warnings

script/linux 🔗

@@ -12,6 +12,7 @@ if [[ -n $apt ]]; then
     libfontconfig-dev
     vulkan-validationlayers*
     libwayland-dev
+    libxkbcommon-x11-dev
   )
   $maysudo "$apt" install -y "${deps[@]}"
   exit 0
@@ -26,6 +27,7 @@ if [[ -n $dnf ]]; then
     fontconfig-devel
     vulkan-validation-layers
     wayland-devel
+    libxkbcommon-x11-devel
   )
   $maysudo "$dnf" install -y "${deps[@]}"
   exit 0
@@ -40,6 +42,7 @@ if [[ -n $pacman ]]; then
     fontconfig
     vulkan-validation-layers
     wayland
+    libxkbcommon-x11
   )
   $maysudo "$pacman" -S --needed --noconfirm "${deps[@]}"
   exit 0