diff --git a/Cargo.lock b/Cargo.lock index 7297c76088b19a..cdab9f60a1289b 100644 --- a/Cargo.lock +++ b/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" diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index f173ccb043154a..7679761291b5f2 100644 --- a/crates/gpui/Cargo.toml +++ b/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"] } diff --git a/crates/gpui/src/platform/linux.rs b/crates/gpui/src/platform/linux.rs index 1341b3da08b2c8..3b61eee54e48c4 100644 --- a/crates/gpui/src/platform/linux.rs +++ b/crates/gpui/src/platform/linux.rs @@ -1,6 +1,3 @@ -//todo!(linux): remove this -#![allow(unused_variables)] - mod blade_atlas; mod blade_belt; mod blade_renderer; diff --git a/crates/gpui/src/platform/linux/blade_renderer.rs b/crates/gpui/src/platform/linux/blade_renderer.rs index 67935c47128446..35f27655061859 100644 --- a/crates/gpui/src/platform/linux/blade_renderer.rs +++ b/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, diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index 3da95555d749bb..330cc84aa2edd9 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/crates/gpui/src/platform/linux/platform.rs @@ -49,8 +49,8 @@ pub(crate) struct LinuxPlatformInner { } pub(crate) struct LinuxPlatform { - client: Arc, - inner: Arc, + client: Rc, + inner: Rc, } pub(crate) struct LinuxPlatformState { @@ -93,7 +93,7 @@ impl LinuxPlatform { let client_dispatcher: Arc = 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, state: Mutex, ) -> 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 = 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), } } } diff --git a/crates/gpui/src/platform/linux/wayland.rs b/crates/gpui/src/platform/linux/wayland.rs index 7621b5ee64654d..0078289ba2ee8f 100644 --- a/crates/gpui/src/platform/linux/wayland.rs +++ b/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::*; diff --git a/crates/gpui/src/platform/linux/wayland/client.rs b/crates/gpui/src/platform/linux/wayland/client.rs index 99680404aee4ae..f058833f4d0378 100644 --- a/crates/gpui/src/platform/linux/wayland/client.rs +++ b/crates/gpui/src/platform/linux/wayland/client.rs @@ -26,12 +26,12 @@ pub(crate) struct WaylandClientState { compositor: Option, buffer: Option, wm_base: Option, - windows: Vec<(xdg_surface::XdgSurface, Arc)>, - platform_inner: Arc, + windows: Vec<(xdg_surface::XdgSurface, Rc)>, + platform_inner: Rc, } pub(crate) struct WaylandClient { - platform_inner: Arc, + platform_inner: Rc, conn: Arc, state: Mutex, event_queue: Mutex>, @@ -39,16 +39,13 @@ pub(crate) struct WaylandClient { } impl WaylandClient { - pub(crate) fn new( - linux_platform_inner: Arc, - conn: Arc, - ) -> Self { + pub(crate) fn new(linux_platform_inner: Rc, conn: Arc) -> 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 = 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 = 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)) } } diff --git a/crates/gpui/src/platform/linux/wayland/display.rs b/crates/gpui/src/platform/linux/wayland/display.rs index b0c7cd25e6d98a..0d8b6dbd3f8310 100644 --- a/crates/gpui/src/platform/linux/wayland/display.rs +++ b/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 { - 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) diff --git a/crates/gpui/src/platform/linux/wayland/window.rs b/crates/gpui/src/platform/linux/wayland/window.rs index 84c92d5e5346c1..1842ed7d26a519 100644 --- a/crates/gpui/src/platform/linux/wayland/window.rs +++ b/crates/gpui/src/platform/linux/wayland/window.rs @@ -181,7 +181,7 @@ impl WaylandWindowState { } #[derive(Clone)] -pub(crate) struct WaylandWindow(pub(crate) Arc); +pub(crate) struct WaylandWindow(pub(crate) Rc); impl HasWindowHandle for WaylandWindow { fn window_handle(&self) -> Result, HandleError> { @@ -212,7 +212,7 @@ impl PlatformWindow for WaylandWindow { // todo!(linux) fn scale_factor(&self) -> f32 { - return 1f32; + 1f32 } //todo!(linux) diff --git a/crates/gpui/src/platform/linux/x11.rs b/crates/gpui/src/platform/linux/x11.rs index 8bbbdc795fe0c5..bcb2215b010e00 100644 --- a/crates/gpui/src/platform/linux/x11.rs +++ b/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::*; diff --git a/crates/gpui/src/platform/linux/x11/client.rs b/crates/gpui/src/platform/linux/x11/client.rs index 3d3842ced0eb37..fca487fc3736a2 100644 --- a/crates/gpui/src/platform/linux/x11/client.rs +++ b/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>, + xkb: xkbcommon::xkb::State, } pub(crate) struct X11Client { - platform_inner: Arc, + platform_inner: Rc, xcb_connection: Arc, x_root_index: i32, atoms: XcbAtoms, @@ -26,11 +29,22 @@ pub(crate) struct X11Client { impl X11Client { pub(crate) fn new( - inner: Arc, + inner: Rc, xcb_connection: Arc, 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, + })); + } _ => {} } diff --git a/crates/gpui/src/platform/linux/x11/event.rs b/crates/gpui/src/platform/linux/x11/event.rs new file mode 100644 index 00000000000000..f9860082da66d2 --- /dev/null +++ b/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 { + 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 { + 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; + }) +} diff --git a/crates/gpui/src/platform/linux/x11/window.rs b/crates/gpui/src/platform/linux/x11/window.rs index 8853966a9fe0b0..c834a951f1c4bd 100644 --- a/crates/gpui/src/platform/linux/x11/window.rs +++ b/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, scale_factor: f32, renderer: BladeRenderer, + input_handler: Option, } 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 { - 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)], }); } diff --git a/script/clippy b/script/clippy index 71dca2ffeffdf2..69b0bce192dbfc 100755 --- a/script/clippy +++ b/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 diff --git a/script/linux b/script/linux index b98f4b7d3ee0d0..b55d60217a8257 100755 --- a/script/linux +++ b/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