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:
parent
37ffa86043
commit
6a6dbe3aa1
8 changed files with 174 additions and 20 deletions
18
Cargo.lock
generated
18
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -4,5 +4,6 @@
|
|||
pub(crate) use client::*;
|
||||
|
||||
mod client;
|
||||
mod cursor;
|
||||
mod display;
|
||||
mod window;
|
||||
|
|
|
@ -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 {
|
||||
|
|
67
crates/gpui/src/platform/linux/wayland/cursor.rs
Normal file
67
crates/gpui/src/platform/linux/wayland/cursor.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue