linux: wayland: implement cursor style handling (#8632)

Release Notes:

- Implemented cursor style changing in wayland


[zed_cursor_wayland.webm](https://github.com/zed-industries/zed/assets/12579216/cbc03f85-41c1-4687-88b5-2aa5612d7129)

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
Joel Selvaraj 2024-03-03 13:28:20 -06:00 committed by GitHub
parent 37ffa86043
commit 6a6dbe3aa1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 174 additions and 20 deletions

View file

@ -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"] }

View file

@ -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<Rc<dyn PlatformDisplay>>;
@ -11,4 +11,5 @@ pub trait Client {
handle: AnyWindowHandle,
options: WindowOptions,
) -> Box<dyn PlatformWindow>;
fn set_cursor_style(&self, style: CursorStyle);
}

View file

@ -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 {

View file

@ -4,5 +4,6 @@
pub(crate) use client::*;
mod client;
mod cursor;
mod display;
mod window;

View file

@ -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<wp_viewporter::WpViewporter>,
fractional_scale_manager: Option<wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1>,
decoration_manager: Option<zxdg_decoration_manager_v1::ZxdgDecorationManagerV1>,
@ -63,8 +65,16 @@ pub(crate) struct WaylandClientStateInner {
loop_handle: Rc<LoopHandle<'static, ()>>,
}
pub(crate) struct CursorState {
cursor_icon_name: String,
cursor: Option<Cursor>,
}
#[derive(Clone)]
pub(crate) struct WaylandClientState(Rc<RefCell<WaylandClientStateInner>>);
pub(crate) struct WaylandClientState {
client_state_inner: Rc<RefCell<WaylandClientStateInner>>,
cursor_state: Rc<RefCell<CursorState>>,
}
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<dyn PlatformWindow> {
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<wl_registry::WlRegistry, GlobalListContents> for WaylandClientState {
@ -263,7 +311,7 @@ impl Dispatch<WlCallback, Arc<WlSurface>> for WaylandClientState {
_: &Connection,
qh: &QueueHandle<Self>,
) {
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<xdg_surface::XdgSurface, ()> for WaylandClientState {
_: &Connection,
_: &QueueHandle<Self>,
) {
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<xdg_toplevel::XdgToplevel, ()> for WaylandClientState {
_: &Connection,
_: &QueueHandle<Self>,
) {
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<wl_keyboard::WlKeyboard, ()> for WaylandClientState {
conn: &Connection,
qh: &QueueHandle<Self>,
) {
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<wl_keyboard::WlKeyboard, ()> 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<wl_pointer::WlPointer, ()> for WaylandClientState {
conn: &Connection,
qh: &QueueHandle<Self>,
) {
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<wl_pointer::WlPointer, ()> 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<wl_pointer::WlPointer, ()> for WaylandClientState {
modifiers: state.modifiers,
}),
);
cursor.set_icon(&wl_pointer, cursor_icon_name);
}
wl_pointer::Event::Button {
button,
@ -693,6 +754,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> 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<wp_fractional_scale_v1::WpFractionalScaleV1, ObjectId> for Wayland
_: &Connection,
_: &QueueHandle<Self>,
) {
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<zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1, ObjectId>
_: &Connection,
_: &QueueHandle<Self>,
) {
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 {

View file

@ -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<CursorTheme, InvalidId>,
current_icon_name: String,
surface: WlSurface,
serial_id: u32,
}
impl Cursor {
pub fn new(
connection: &Connection,
compositor: &WlCompositor,
qh: &QueueHandle<WaylandClientState>,
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");
}
}
}
}

View file

@ -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: