From 091e7cb3957a7ae8fd92acda195255710c048251 Mon Sep 17 00:00:00 2001 From: apricotbucket28 <71973804+apricotbucket28@users.noreply.github.com> Date: Mon, 6 May 2024 17:05:00 -0300 Subject: [PATCH] x11: Cursor style support (#11237) Adds cursor style support to X11 ![image](https://github.com/zed-industries/zed/assets/71973804/e5a2414f-4d80-4963-93d2-e4a15878a718) Release Notes: - N/A --- crates/gpui/Cargo.toml | 1 + crates/gpui/src/platform/linux/x11/client.rs | 69 +++++++++++++++++--- 2 files changed, 61 insertions(+), 9 deletions(-) diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 1b1a135b12..ed0e63f9db 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -120,6 +120,7 @@ x11rb = { version = "0.13.0", features = [ "xkb", "randr", "xinput", + "cursor", "resource_manager", ] } xkbcommon = { version = "0.7", features = ["wayland", "x11"] } diff --git a/crates/gpui/src/platform/linux/x11/client.rs b/crates/gpui/src/platform/linux/x11/client.rs index 4d8c2b0520..b1d6917d7a 100644 --- a/crates/gpui/src/platform/linux/x11/client.rs +++ b/crates/gpui/src/platform/linux/x11/client.rs @@ -3,19 +3,21 @@ use std::ops::Deref; use std::rc::{Rc, Weak}; use std::time::{Duration, Instant}; -use calloop::{EventLoop, LoopHandle}; +use calloop::generic::{FdWrapper, Generic}; +use calloop::{EventLoop, LoopHandle, RegistrationToken}; use collections::HashMap; use copypasta::x11_clipboard::{Clipboard, Primary, X11ClipboardContext}; use copypasta::ClipboardProvider; use util::ResultExt; use x11rb::connection::{Connection, RequestConnection}; +use x11rb::cursor; use x11rb::errors::ConnectionError; use x11rb::protocol::randr::ConnectionExt as _; use x11rb::protocol::xinput::{ConnectionExt, ScrollClass}; use x11rb::protocol::xkb::ConnectionExt as _; -use x11rb::protocol::xproto::ConnectionExt as _; -use x11rb::protocol::{randr, xinput, xkb, xproto, Event}; +use x11rb::protocol::xproto::{ChangeWindowAttributesAux, ConnectionExt as _}; +use x11rb::protocol::{randr, render, xinput, xkb, xproto, Event}; use x11rb::resource_manager::Database; use x11rb::xcb_ffi::XCBConnection; use xkbc::x11::ffi::{XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION}; @@ -36,10 +38,6 @@ use super::{ use super::{button_of_key, modifiers_from_state}; use crate::platform::linux::is_within_click_distance; use crate::platform::linux::platform::DOUBLE_CLICK_INTERVAL; -use calloop::{ - generic::{FdWrapper, Generic}, - RegistrationToken, -}; pub(crate) struct WindowRef { window: X11WindowStatePtr, @@ -72,6 +70,10 @@ pub struct X11ClientState { pub(crate) focused_window: Option, pub(crate) xkb: xkbc::State, + pub(crate) cursor_handle: cursor::Handle, + pub(crate) cursor_styles: HashMap, + pub(crate) cursor_cache: HashMap, + pub(crate) scroll_class_data: Vec, pub(crate) scroll_x: Option, pub(crate) scroll_y: Option, @@ -93,6 +95,8 @@ impl X11ClientStatePtr { state.loop_handle.remove(window_ref.refresh_event_token); } + state.cursor_styles.remove(&x_window); + if state.windows.is_empty() { state.common.signal.stop(); } @@ -123,6 +127,9 @@ impl X11Client { xcb_connection .prefetch_extension_information(randr::X11_EXTENSION_NAME) .unwrap(); + xcb_connection + .prefetch_extension_information(render::X11_EXTENSION_NAME) + .unwrap(); xcb_connection .prefetch_extension_information(xinput::X11_EXTENSION_NAME) .unwrap(); @@ -210,6 +217,11 @@ impl X11Client { .map(|dpi: f32| dpi / 96.0) .unwrap_or(1.0); + let cursor_handle = cursor::Handle::new(&xcb_connection, x_root_index, &resource_database) + .unwrap() + .reply() + .unwrap(); + let clipboard = X11ClipboardContext::::new().unwrap(); let primary = X11ClipboardContext::::new().unwrap(); @@ -254,6 +266,10 @@ impl X11Client { focused_window: None, xkb: xkb_state, + cursor_handle, + cursor_styles: HashMap::default(), + cursor_cache: HashMap::default(), + scroll_class_data, scroll_x: None, scroll_y: None, @@ -672,8 +688,43 @@ impl LinuxClient for X11Client { Box::new(window) } - //todo(linux) - fn set_cursor_style(&self, _style: CursorStyle) {} + fn set_cursor_style(&self, style: CursorStyle) { + let mut state = self.0.borrow_mut(); + let Some(focused_window) = state.focused_window else { + return; + }; + let current_style = state + .cursor_styles + .get(&focused_window) + .unwrap_or(&CursorStyle::Arrow); + if *current_style == style { + return; + } + + let cursor = match state.cursor_cache.get(&style) { + Some(cursor) => *cursor, + None => { + let cursor = state + .cursor_handle + .load_cursor(&state.xcb_connection, &style.to_icon_name()) + .expect("failed to load cursor"); + state.cursor_cache.insert(style, cursor); + cursor + } + }; + + state.cursor_styles.insert(focused_window, style); + state + .xcb_connection + .change_window_attributes( + focused_window, + &ChangeWindowAttributesAux { + cursor: Some(cursor), + ..Default::default() + }, + ) + .expect("failed to change window cursor"); + } fn open_uri(&self, uri: &str) { open_uri_internal(uri, None);