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"] }
apricotbucket28 created
Adds cursor style support to X11

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(-)
@@ -120,6 +120,7 @@ x11rb = { version = "0.13.0", features = [
"xkb",
"randr",
"xinput",
+ "cursor",
"resource_manager",
] }
xkbcommon = { version = "0.7", features = ["wayland", "x11"] }
@@ -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<xproto::Window>,
pub(crate) xkb: xkbc::State,
+ pub(crate) cursor_handle: cursor::Handle,
+ pub(crate) cursor_styles: HashMap<xproto::Window, CursorStyle>,
+ pub(crate) cursor_cache: HashMap<CursorStyle, xproto::Cursor>,
+
pub(crate) scroll_class_data: Vec<xinput::DeviceClassDataScroll>,
pub(crate) scroll_x: Option<f32>,
pub(crate) scroll_y: Option<f32>,
@@ -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::<Clipboard>::new().unwrap();
let primary = X11ClipboardContext::<Primary>::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);