diff --git a/Cargo.lock b/Cargo.lock index ce408e1017..34c5933844 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4248,6 +4248,7 @@ dependencies = [ "waker-fn", "wayland-backend", "wayland-client", + "wayland-cursor", "wayland-protocols", "windows 0.53.0", "xcb", @@ -11501,6 +11502,17 @@ dependencies = [ "wayland-scanner", ] +[[package]] +name = "wayland-cursor" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71ce5fa868dd13d11a0d04c5e2e65726d0897be8de247c0c5a65886e283231ba" +dependencies = [ + "rustix 0.38.30", + "wayland-client", + "xcursor", +] + [[package]] name = "wayland-protocols" version = "0.31.2" @@ -12198,6 +12210,12 @@ dependencies = [ "quick-xml 0.30.0", ] +[[package]] +name = "xcursor" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911" + [[package]] name = "xdg-home" version = "1.1.0" diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 297c8a06ed..da6fd0af36 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -106,6 +106,7 @@ open = "5.0.1" ashpd = "0.7.0" xcb = { version = "1.3", features = ["as-raw-xcb-connection", "randr", "xkb"] } wayland-client= { version = "0.31.2" } +wayland-cursor = "0.31.1" wayland-protocols = { version = "0.31.2", features = ["client", "staging", "unstable"] } wayland-backend = { version = "0.3.3", features = ["client_system"] } xkbcommon = { version = "0.7", features = ["wayland", "x11"] } diff --git a/crates/gpui/src/platform/linux/client.rs b/crates/gpui/src/platform/linux/client.rs index 0d71e4ef00..d74aac7369 100644 --- a/crates/gpui/src/platform/linux/client.rs +++ b/crates/gpui/src/platform/linux/client.rs @@ -1,7 +1,7 @@ use std::rc::Rc; use crate::platform::PlatformWindow; -use crate::{AnyWindowHandle, DisplayId, PlatformDisplay, WindowOptions}; +use crate::{AnyWindowHandle, CursorStyle, DisplayId, PlatformDisplay, WindowOptions}; pub trait Client { fn displays(&self) -> Vec>; @@ -11,4 +11,5 @@ pub trait Client { handle: AnyWindowHandle, options: WindowOptions, ) -> Box; + fn set_cursor_style(&self, style: CursorStyle); } diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index feadee0ad6..cad0d1074b 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/crates/gpui/src/platform/linux/platform.rs @@ -342,8 +342,9 @@ impl Platform for LinuxPlatform { )) } - // todo(linux) - fn set_cursor_style(&self, style: CursorStyle) {} + fn set_cursor_style(&self, style: CursorStyle) { + self.client.set_cursor_style(style) + } // todo(linux) fn should_auto_hide_scrollbars(&self) -> bool { diff --git a/crates/gpui/src/platform/linux/wayland.rs b/crates/gpui/src/platform/linux/wayland.rs index 79f3aa223c..ebb592d375 100644 --- a/crates/gpui/src/platform/linux/wayland.rs +++ b/crates/gpui/src/platform/linux/wayland.rs @@ -4,5 +4,6 @@ pub(crate) use client::*; mod client; +mod cursor; mod display; mod window; diff --git a/crates/gpui/src/platform/linux/wayland/client.rs b/crates/gpui/src/platform/linux/wayland/client.rs index 957515c8ff..0bbd716e06 100644 --- a/crates/gpui/src/platform/linux/wayland/client.rs +++ b/crates/gpui/src/platform/linux/wayland/client.rs @@ -32,12 +32,13 @@ use xkbcommon::xkb::ffi::XKB_KEYMAP_FORMAT_TEXT_V1; use xkbcommon::xkb::{self, Keycode, KEYMAP_COMPILE_NO_FLAGS}; use crate::platform::linux::client::Client; +use crate::platform::linux::wayland::cursor::Cursor; use crate::platform::linux::wayland::window::{WaylandDecorationState, WaylandWindow}; use crate::platform::{LinuxPlatformInner, PlatformWindow}; use crate::{ - platform::linux::wayland::window::WaylandWindowState, AnyWindowHandle, DisplayId, KeyDownEvent, - KeyUpEvent, Keystroke, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, - NavigationDirection, Pixels, PlatformDisplay, PlatformInput, Point, ScrollDelta, + platform::linux::wayland::window::WaylandWindowState, AnyWindowHandle, CursorStyle, DisplayId, + KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, + MouseUpEvent, NavigationDirection, Pixels, PlatformDisplay, PlatformInput, Point, ScrollDelta, ScrollWheelEvent, TouchPhase, WindowOptions, }; @@ -47,6 +48,7 @@ const MIN_KEYCODE: u32 = 8; pub(crate) struct WaylandClientStateInner { compositor: wl_compositor::WlCompositor, wm_base: xdg_wm_base::XdgWmBase, + shm: wl_shm::WlShm, viewporter: Option, fractional_scale_manager: Option, decoration_manager: Option, @@ -63,8 +65,16 @@ pub(crate) struct WaylandClientStateInner { loop_handle: Rc>, } +pub(crate) struct CursorState { + cursor_icon_name: String, + cursor: Option, +} + #[derive(Clone)] -pub(crate) struct WaylandClientState(Rc>); +pub(crate) struct WaylandClientState { + client_state_inner: Rc>, + cursor_state: Rc>, +} pub(crate) struct KeyRepeat { characters_per_second: u32, @@ -104,6 +114,7 @@ impl WaylandClient { let mut state_inner = Rc::new(RefCell::new(WaylandClientStateInner { compositor: globals.bind(&qh, 1..=1, ()).unwrap(), wm_base: globals.bind(&qh, 1..=1, ()).unwrap(), + shm: globals.bind(&qh, 1..=1, ()).unwrap(), viewporter: globals.bind(&qh, 1..=1, ()).ok(), fractional_scale_manager: globals.bind(&qh, 1..=1, ()).ok(), decoration_manager: globals.bind(&qh, 1..=1, ()).ok(), @@ -131,9 +142,17 @@ impl WaylandClient { loop_handle: Rc::clone(&linux_platform_inner.loop_handle), })); + let mut cursor_state = Rc::new(RefCell::new(CursorState { + cursor_icon_name: "arrow".to_string(), + cursor: None, + })); + let source = WaylandSource::new(conn, event_queue); - let mut state = WaylandClientState(Rc::clone(&state_inner)); + let mut state = WaylandClientState { + client_state_inner: Rc::clone(&state_inner), + cursor_state: Rc::clone(&cursor_state), + }; linux_platform_inner .loop_handle .insert_source(source, move |_, queue, _| { @@ -143,7 +162,10 @@ impl WaylandClient { Self { platform_inner: linux_platform_inner, - state: WaylandClientState(state_inner), + state: WaylandClientState { + client_state_inner: state_inner, + cursor_state, + }, qh: Arc::new(qh), } } @@ -163,7 +185,7 @@ impl Client for WaylandClient { handle: AnyWindowHandle, options: WindowOptions, ) -> Box { - let mut state = self.state.0.borrow_mut(); + let mut state = self.state.client_state_inner.borrow_mut(); let wl_surface = state.compositor.create_surface(&self.qh, ()); let xdg_surface = state.wm_base.get_xdg_surface(&wl_surface, &self.qh, ()); @@ -217,6 +239,32 @@ impl Client for WaylandClient { state.windows.push((xdg_surface, Rc::clone(&window_state))); Box::new(WaylandWindow(window_state)) } + + fn set_cursor_style(&self, style: CursorStyle) { + let cursor_icon_name = match style { + CursorStyle::Arrow => "arrow".to_string(), + CursorStyle::IBeam => "text".to_string(), + CursorStyle::Crosshair => "crosshair".to_string(), + CursorStyle::ClosedHand => "grabbing".to_string(), + CursorStyle::OpenHand => "openhand".to_string(), + CursorStyle::PointingHand => "hand".to_string(), + CursorStyle::ResizeLeft => "w-resize".to_string(), + CursorStyle::ResizeRight => "e-resize".to_string(), + CursorStyle::ResizeLeftRight => "ew-resize".to_string(), + CursorStyle::ResizeUp => "n-resize".to_string(), + CursorStyle::ResizeDown => "s-resize".to_string(), + CursorStyle::ResizeUpDown => "ns-resize".to_string(), + CursorStyle::DisappearingItem => "grabbing".to_string(), // todo!(linux) - couldn't find equivalent icon in linux + CursorStyle::IBeamCursorForVerticalLayout => "vertical-text".to_string(), + CursorStyle::OperationNotAllowed => "not-allowed".to_string(), + CursorStyle::DragLink => "dnd-link".to_string(), + CursorStyle::DragCopy => "dnd-copy".to_string(), + CursorStyle::ContextualMenu => "context-menu".to_string(), + }; + + let mut cursor_state = self.state.cursor_state.borrow_mut(); + cursor_state.cursor_icon_name = cursor_icon_name; + } } impl Dispatch for WaylandClientState { @@ -263,7 +311,7 @@ impl Dispatch> for WaylandClientState { _: &Connection, qh: &QueueHandle, ) { - let mut state = state.0.borrow_mut(); + let mut state = state.client_state_inner.borrow_mut(); if let wl_callback::Event::Done { .. } = event { for window in &state.windows { if window.1.surface.id() == surf.id() { @@ -285,7 +333,7 @@ impl Dispatch for WaylandClientState { _: &Connection, _: &QueueHandle, ) { - let mut state = state.0.borrow_mut(); + let mut state = state.client_state_inner.borrow_mut(); if let xdg_surface::Event::Configure { serial, .. } = event { xdg_surface.ack_configure(serial); for window in &state.windows { @@ -308,7 +356,7 @@ impl Dispatch for WaylandClientState { _: &Connection, _: &QueueHandle, ) { - let mut state = state.0.borrow_mut(); + let mut state = state.client_state_inner.borrow_mut(); if let xdg_toplevel::Event::Configure { width, height, @@ -386,7 +434,7 @@ impl Dispatch for WaylandClientState { conn: &Connection, qh: &QueueHandle, ) { - let mut state = this.0.borrow_mut(); + let mut state = this.client_state_inner.borrow_mut(); match event { wl_keyboard::Event::RepeatInfo { rate, delay } => { state.repeat.characters_per_second = rate as u32; @@ -497,7 +545,7 @@ impl Dispatch for WaylandClientState { let this = this.clone(); let timer = Timer::from_duration(delay); - let state_ = Rc::clone(&this.0); + let state_ = Rc::clone(&this.client_state_inner); state .loop_handle .insert_source(timer, move |event, _metadata, shared_data| { @@ -567,9 +615,18 @@ impl Dispatch for WaylandClientState { conn: &Connection, qh: &QueueHandle, ) { - let mut state = state.0.borrow_mut(); + let mut cursor_state = state.cursor_state.borrow_mut(); + let mut state = state.client_state_inner.borrow_mut(); + + if cursor_state.cursor.is_none() { + cursor_state.cursor = Some(Cursor::new(&conn, &state.compositor, &qh, &state.shm, 24)); + } + let cursor_icon_name = cursor_state.cursor_icon_name.clone(); + let mut cursor: &mut Cursor = cursor_state.cursor.as_mut().unwrap(); + match event { wl_pointer::Event::Enter { + serial, surface, surface_x, surface_y, @@ -578,11 +635,14 @@ impl Dispatch for WaylandClientState { let mut mouse_focused_window = None; for window in &state.windows { if window.1.surface.id() == surface.id() { + window.1.set_focused(true); mouse_focused_window = Some(Rc::clone(&window.1)); } } if mouse_focused_window.is_some() { state.mouse_focused_window = mouse_focused_window; + cursor.set_serial_id(serial); + cursor.set_icon(&wl_pointer, cursor_icon_name); } state.mouse_location = Some(Point { @@ -610,6 +670,7 @@ impl Dispatch for WaylandClientState { modifiers: state.modifiers, }), ); + cursor.set_icon(&wl_pointer, cursor_icon_name); } wl_pointer::Event::Button { button, @@ -693,6 +754,7 @@ impl Dispatch for WaylandClientState { pressed_button: None, modifiers: Modifiers::default(), })); + focused_window.set_focused(false); } state.mouse_focused_window = None; state.mouse_location = None; @@ -711,7 +773,7 @@ impl Dispatch for Wayland _: &Connection, _: &QueueHandle, ) { - let mut state = state.0.borrow_mut(); + let mut state = state.client_state_inner.borrow_mut(); if let wp_fractional_scale_v1::Event::PreferredScale { scale, .. } = event { for window in &state.windows { if window.0.id() == *id { @@ -734,7 +796,7 @@ impl Dispatch _: &Connection, _: &QueueHandle, ) { - let mut state = state.0.borrow_mut(); + let mut state = state.client_state_inner.borrow_mut(); if let zxdg_toplevel_decoration_v1::Event::Configure { mode, .. } = event { for window in &state.windows { if window.0.id() == *surface_id { diff --git a/crates/gpui/src/platform/linux/wayland/cursor.rs b/crates/gpui/src/platform/linux/wayland/cursor.rs new file mode 100644 index 0000000000..8b641972e3 --- /dev/null +++ b/crates/gpui/src/platform/linux/wayland/cursor.rs @@ -0,0 +1,67 @@ +use crate::platform::linux::wayland::WaylandClientState; +use wayland_backend::client::InvalidId; +use wayland_client::protocol::wl_compositor::WlCompositor; +use wayland_client::protocol::wl_pointer::WlPointer; +use wayland_client::protocol::wl_shm::WlShm; +use wayland_client::protocol::wl_surface::WlSurface; +use wayland_client::{Connection, QueueHandle}; +use wayland_cursor::{CursorImageBuffer, CursorTheme}; + +pub(crate) struct Cursor { + theme: Result, + current_icon_name: String, + surface: WlSurface, + serial_id: u32, +} + +impl Cursor { + pub fn new( + connection: &Connection, + compositor: &WlCompositor, + qh: &QueueHandle, + shm: &WlShm, + size: u32, + ) -> Self { + Self { + theme: CursorTheme::load(&connection, shm.clone(), size), + current_icon_name: "".to_string(), + surface: compositor.create_surface(qh, ()), + serial_id: 0, + } + } + + pub fn set_serial_id(&mut self, serial_id: u32) { + self.serial_id = serial_id; + } + + pub fn set_icon(&mut self, wl_pointer: &WlPointer, cursor_icon_name: String) { + if self.current_icon_name != cursor_icon_name { + if self.theme.is_ok() { + if let Some(cursor) = self.theme.as_mut().unwrap().get_cursor(&cursor_icon_name) { + let buffer: &CursorImageBuffer = &cursor[0]; + let (width, height) = buffer.dimensions(); + let (hot_x, hot_y) = buffer.hotspot(); + + wl_pointer.set_cursor( + self.serial_id, + Some(&self.surface), + hot_x as i32, + hot_y as i32, + ); + self.surface.attach(Some(&buffer), 0, 0); + self.surface.damage(0, 0, width as i32, height as i32); + self.surface.commit(); + + self.current_icon_name = cursor_icon_name; + } else { + log::warn!( + "Linux: Wayland: Unable to get cursor icon: {}", + cursor_icon_name + ); + } + } else { + log::warn!("Linux: Wayland: Unable to load cursor themes"); + } + } + } +} diff --git a/crates/gpui/src/platform/linux/x11/client.rs b/crates/gpui/src/platform/linux/x11/client.rs index 8af38b512d..dd01102276 100644 --- a/crates/gpui/src/platform/linux/x11/client.rs +++ b/crates/gpui/src/platform/linux/x11/client.rs @@ -10,8 +10,8 @@ use collections::HashMap; use crate::platform::linux::client::Client; use crate::platform::{LinuxPlatformInner, PlatformWindow}; use crate::{ - AnyWindowHandle, Bounds, DisplayId, PlatformDisplay, PlatformInput, Point, ScrollDelta, Size, - TouchPhase, WindowOptions, + AnyWindowHandle, Bounds, CursorStyle, DisplayId, PlatformDisplay, PlatformInput, Point, + ScrollDelta, Size, TouchPhase, WindowOptions, }; use super::{X11Display, X11Window, X11WindowState, XcbAtoms}; @@ -351,6 +351,9 @@ impl Client for X11Client { self.state.borrow_mut().windows.insert(x_window, window_ref); Box::new(X11Window(window_ptr)) } + + //todo!(linux) + fn set_cursor_style(&self, _style: CursorStyle) {} } // Adatpted from: