diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 074f7fda93..b6415a6e50 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -715,7 +715,7 @@ pub enum PromptLevel { } /// The style of the cursor (pointer) -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum CursorStyle { /// The default cursor Arrow, diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index 8ecb639335..221674683e 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/crates/gpui/src/platform/linux/platform.rs @@ -28,6 +28,7 @@ use futures::channel::oneshot; use parking_lot::Mutex; use time::UtcOffset; use wayland_client::Connection; +use wayland_protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::Shape; use xkbcommon::xkb::{self, Keycode, Keysym, State}; use crate::platform::linux::wayland::WaylandClient; @@ -501,6 +502,58 @@ pub(super) unsafe fn read_fd(mut fd: FileDescriptor) -> Result { Ok(result) } +impl CursorStyle { + pub(super) fn to_shape(&self) -> Shape { + match self { + CursorStyle::Arrow => Shape::Default, + CursorStyle::IBeam => Shape::Text, + CursorStyle::Crosshair => Shape::Crosshair, + CursorStyle::ClosedHand => Shape::Grabbing, + CursorStyle::OpenHand => Shape::Grab, + CursorStyle::PointingHand => Shape::Pointer, + CursorStyle::ResizeLeft => Shape::WResize, + CursorStyle::ResizeRight => Shape::EResize, + CursorStyle::ResizeLeftRight => Shape::EwResize, + CursorStyle::ResizeUp => Shape::NResize, + CursorStyle::ResizeDown => Shape::SResize, + CursorStyle::ResizeUpDown => Shape::NsResize, + CursorStyle::DisappearingItem => Shape::Grabbing, // todo(linux) - couldn't find equivalent icon in linux + CursorStyle::IBeamCursorForVerticalLayout => Shape::VerticalText, + CursorStyle::OperationNotAllowed => Shape::NotAllowed, + CursorStyle::DragLink => Shape::Alias, + CursorStyle::DragCopy => Shape::Copy, + CursorStyle::ContextualMenu => Shape::ContextMenu, + } + } + + pub(super) fn to_icon_name(&self) -> String { + // Based on cursor names from https://gitlab.gnome.org/GNOME/adwaita-icon-theme (GNOME) + // and https://github.com/KDE/breeze (KDE). Both of them seem to be also derived from + // Web CSS cursor names: https://developer.mozilla.org/en-US/docs/Web/CSS/cursor#values + match self { + CursorStyle::Arrow => "arrow", + CursorStyle::IBeam => "text", + CursorStyle::Crosshair => "crosshair", + CursorStyle::ClosedHand => "grabbing", + CursorStyle::OpenHand => "grab", + CursorStyle::PointingHand => "pointer", + CursorStyle::ResizeLeft => "w-resize", + CursorStyle::ResizeRight => "e-resize", + CursorStyle::ResizeLeftRight => "ew-resize", + CursorStyle::ResizeUp => "n-resize", + CursorStyle::ResizeDown => "s-resize", + CursorStyle::ResizeUpDown => "ns-resize", + CursorStyle::DisappearingItem => "grabbing", // todo(linux) - couldn't find equivalent icon in linux + CursorStyle::IBeamCursorForVerticalLayout => "vertical-text", + CursorStyle::OperationNotAllowed => "not-allowed", + CursorStyle::DragLink => "alias", + CursorStyle::DragCopy => "copy", + CursorStyle::ContextualMenu => "context-menu", + } + .to_string() + } +} + impl Keystroke { pub(super) fn from_xkb(state: &State, modifiers: Modifiers, keycode: Keycode) -> Self { let mut modifiers = modifiers; diff --git a/crates/gpui/src/platform/linux/wayland/client.rs b/crates/gpui/src/platform/linux/wayland/client.rs index 9e463dbc1a..685b91fb4c 100644 --- a/crates/gpui/src/platform/linux/wayland/client.rs +++ b/crates/gpui/src/platform/linux/wayland/client.rs @@ -34,6 +34,10 @@ use wayland_client::{ }, Connection, Dispatch, Proxy, QueueHandle, }; +use wayland_protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::Shape; +use wayland_protocols::wp::cursor_shape::v1::client::{ + wp_cursor_shape_device_v1, wp_cursor_shape_manager_v1, +}; use wayland_protocols::wp::fractional_scale::v1::client::{ wp_fractional_scale_manager_v1, wp_fractional_scale_v1, }; @@ -68,6 +72,7 @@ const MIN_KEYCODE: u32 = 8; pub struct Globals { pub qh: QueueHandle, pub compositor: wl_compositor::WlCompositor, + pub cursor_shape_manager: Option, pub data_device_manager: Option, pub wm_base: xdg_wm_base::XdgWmBase, pub shm: wl_shm::WlShm, @@ -93,6 +98,7 @@ impl Globals { (), ) .unwrap(), + cursor_shape_manager: globals.bind(&qh, 1..=1, ()).ok(), data_device_manager: globals .bind( &qh, @@ -112,9 +118,11 @@ impl Globals { } pub(crate) struct WaylandClientState { - serial: u32, + serial: u32, // todo(linux): storing a general serial is wrong + pointer_serial: u32, globals: Globals, wl_pointer: Option, + cursor_shape_device: Option, data_device: Option, // Surface to Window mapping windows: HashMap, @@ -137,7 +145,7 @@ pub(crate) struct WaylandClientState { mouse_focused_window: Option, keyboard_focused_window: Option, loop_handle: LoopHandle<'static, WaylandClientStatePtr>, - cursor_icon_name: String, + cursor_style: Option, cursor: Cursor, clipboard: Option, primary: Option, @@ -197,6 +205,9 @@ impl WaylandClientStatePtr { if let Some(wl_pointer) = &state.wl_pointer { wl_pointer.release(); } + if let Some(cursor_shape_device) = &state.cursor_shape_device { + cursor_shape_device.destroy(); + } if let Some(data_device) = &state.data_device { data_device.release(); } @@ -289,8 +300,10 @@ impl WaylandClient { let mut state = Rc::new(RefCell::new(WaylandClientState { serial: 0, + pointer_serial: 0, globals, wl_pointer: None, + cursor_shape_device: None, data_device, output_scales: outputs, windows: HashMap::default(), @@ -330,8 +343,8 @@ impl WaylandClient { mouse_focused_window: None, keyboard_focused_window: None, loop_handle: handle.clone(), - cursor_icon_name: "arrow".to_string(), enter_token: None, + cursor_style: None, cursor, clipboard: Some(clipboard), primary: Some(primary), @@ -375,39 +388,28 @@ impl LinuxClient for WaylandClient { } fn set_cursor_style(&self, style: CursorStyle) { - // Based on cursor names from https://gitlab.gnome.org/GNOME/adwaita-icon-theme (GNOME) - // and https://github.com/KDE/breeze (KDE). Both of them seem to be also derived from - // Web CSS cursor names: https://developer.mozilla.org/en-US/docs/Web/CSS/cursor#values - let cursor_icon_name = match style { - CursorStyle::Arrow => "arrow", - CursorStyle::IBeam => "text", - CursorStyle::Crosshair => "crosshair", - CursorStyle::ClosedHand => "grabbing", - CursorStyle::OpenHand => "grab", - CursorStyle::PointingHand => "pointer", - CursorStyle::ResizeLeft => "w-resize", - CursorStyle::ResizeRight => "e-resize", - CursorStyle::ResizeLeftRight => "ew-resize", - CursorStyle::ResizeUp => "n-resize", - CursorStyle::ResizeDown => "s-resize", - CursorStyle::ResizeUpDown => "ns-resize", - CursorStyle::DisappearingItem => "grabbing", // todo(linux) - couldn't find equivalent icon in linux - CursorStyle::IBeamCursorForVerticalLayout => "vertical-text", - CursorStyle::OperationNotAllowed => "not-allowed", - CursorStyle::DragLink => "alias", - CursorStyle::DragCopy => "copy", - CursorStyle::ContextualMenu => "context-menu", - } - .to_string(); - let mut state = self.0.borrow_mut(); - state.cursor_icon_name = cursor_icon_name.clone(); - if state.mouse_focused_window.is_some() { - let wl_pointer = state - .wl_pointer - .clone() - .expect("window is focused by pointer"); - state.cursor.set_icon(&wl_pointer, &cursor_icon_name); + + let need_update = state + .cursor_style + .map_or(true, |current_style| current_style != style); + + if need_update { + let serial = state.pointer_serial; + state.cursor_style = Some(style); + + if let Some(cursor_shape_device) = &state.cursor_shape_device { + cursor_shape_device.set_shape(serial, style.to_shape()); + } else if state.mouse_focused_window.is_some() { + // cursor-shape-v1 isn't supported, set the cursor using a surface. + let wl_pointer = state + .wl_pointer + .clone() + .expect("window is focused by pointer"); + state + .cursor + .set_icon(&wl_pointer, serial, &style.to_icon_name()); + } } } @@ -516,6 +518,8 @@ impl Dispatch for WaylandClientStat } delegate_noop!(WaylandClientStatePtr: ignore wl_compositor::WlCompositor); +delegate_noop!(WaylandClientStatePtr: ignore wp_cursor_shape_device_v1::WpCursorShapeDeviceV1); +delegate_noop!(WaylandClientStatePtr: ignore wp_cursor_shape_manager_v1::WpCursorShapeManagerV1); delegate_noop!(WaylandClientStatePtr: ignore wl_data_device_manager::WlDataDeviceManager); delegate_noop!(WaylandClientStatePtr: ignore wl_shm::WlShm); delegate_noop!(WaylandClientStatePtr: ignore wl_shm_pool::WlShmPool); @@ -684,7 +688,13 @@ impl Dispatch for WaylandClientStatePtr { if capabilities.contains(wl_seat::Capability::Pointer) { let client = state.get_client(); let mut state = client.borrow_mut(); - state.wl_pointer = Some(seat.get_pointer(qh, ())); + let pointer = seat.get_pointer(qh, ()); + state.cursor_shape_device = state + .globals + .cursor_shape_manager + .as_ref() + .map(|cursor_shape_manager| cursor_shape_manager.get_pointer(&pointer, qh, ())); + state.wl_pointer = Some(pointer); } } } @@ -889,7 +899,6 @@ impl Dispatch for WaylandClientStatePtr { ) { let mut client = this.get_client(); let mut state = client.borrow_mut(); - let cursor_icon_name = state.cursor_icon_name.clone(); match event { wl_pointer::Event::Enter { @@ -899,17 +908,21 @@ impl Dispatch for WaylandClientStatePtr { surface_y, .. } => { - state.serial = serial; + state.pointer_serial = serial; state.mouse_location = Some(point(px(surface_x as f32), px(surface_y as f32))); if let Some(window) = get_window(&mut state, &surface.id()) { state.enter_token = Some(()); state.mouse_focused_window = Some(window.clone()); - state.cursor.mark_dirty(); - state.cursor.set_serial_id(serial); - state - .cursor - .set_icon(&wl_pointer, cursor_icon_name.as_str()); + if let Some(style) = state.cursor_style { + if let Some(cursor_shape_device) = &state.cursor_shape_device { + cursor_shape_device.set_shape(serial, style.to_shape()); + } else { + state + .cursor + .set_icon(&wl_pointer, serial, &style.to_icon_name()); + } + } drop(state); window.set_focused(true); } diff --git a/crates/gpui/src/platform/linux/wayland/cursor.rs b/crates/gpui/src/platform/linux/wayland/cursor.rs index b2b9bccd7f..4ca4d4717e 100644 --- a/crates/gpui/src/platform/linux/wayland/cursor.rs +++ b/crates/gpui/src/platform/linux/wayland/cursor.rs @@ -8,9 +8,7 @@ use wayland_cursor::{CursorImageBuffer, CursorTheme}; pub(crate) struct Cursor { theme: Option, - current_icon_name: Option, surface: WlSurface, - serial_id: u32, } impl Drop for Cursor { @@ -24,65 +22,39 @@ impl Cursor { pub fn new(connection: &Connection, globals: &Globals, size: u32) -> Self { Self { theme: CursorTheme::load(&connection, globals.shm.clone(), size).log_err(), - current_icon_name: None, surface: globals.compositor.create_surface(&globals.qh, ()), - serial_id: 0, } } - pub fn mark_dirty(&mut self) { - self.current_icon_name = None; - } + pub fn set_icon(&mut self, wl_pointer: &WlPointer, serial_id: u32, mut cursor_icon_name: &str) { + if let Some(theme) = &mut self.theme { + let mut buffer: Option<&CursorImageBuffer>; - pub fn set_serial_id(&mut self, serial_id: u32) { - self.serial_id = serial_id; - } - - pub fn set_icon(&mut self, wl_pointer: &WlPointer, mut cursor_icon_name: &str) { - let need_update = self - .current_icon_name - .as_ref() - .map_or(true, |current_icon_name| { - current_icon_name != cursor_icon_name - }); - - if need_update { - if let Some(theme) = &mut self.theme { - let mut buffer: Option<&CursorImageBuffer>; - - if let Some(cursor) = theme.get_cursor(&cursor_icon_name) { - buffer = Some(&cursor[0]); - } else if let Some(cursor) = theme.get_cursor("default") { - buffer = Some(&cursor[0]); - cursor_icon_name = "default"; - log::warn!( - "Linux: Wayland: Unable to get cursor icon: {}. Using default cursor icon", - cursor_icon_name - ); - } else { - buffer = None; - log::warn!("Linux: Wayland: Unable to get default cursor too!"); - } - - if let Some(buffer) = &mut buffer { - 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 = Some(cursor_icon_name.to_string()); - } + if let Some(cursor) = theme.get_cursor(&cursor_icon_name) { + buffer = Some(&cursor[0]); + } else if let Some(cursor) = theme.get_cursor("default") { + buffer = Some(&cursor[0]); + cursor_icon_name = "default"; + log::warn!( + "Linux: Wayland: Unable to get cursor icon: {}. Using default cursor icon", + cursor_icon_name + ); } else { - log::warn!("Linux: Wayland: Unable to load cursor themes"); + buffer = None; + log::warn!("Linux: Wayland: Unable to get default cursor too!"); } + + if let Some(buffer) = &mut buffer { + let (width, height) = buffer.dimensions(); + let (hot_x, hot_y) = buffer.hotspot(); + + wl_pointer.set_cursor(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(); + } + } else { + log::warn!("Linux: Wayland: Unable to load cursor themes"); } } }