From ab41eddd8bcf462049b6646023723497ca09b0b3 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 11 Jun 2024 17:39:25 -0700 Subject: [PATCH] Fix cursors on some GNOME installations (#12914) This PR adds support for `org.gnome.desktop.interface`'s `cursor-theme` setting on Wayland. This should fix cursors not showing up on some GNOME installs. This PR also adds the wiring to watch the current cursor theme value. Thanks to @apricotbucket28 for helping debug the issue. Release Notes: - N/A --- .../gpui/src/platform/linux/wayland/client.rs | 32 ++++++- .../gpui/src/platform/linux/wayland/cursor.rs | 45 ++++++++- crates/gpui/src/platform/linux/x11/client.rs | 3 + .../src/platform/linux/xdg_desktop_portal.rs | 95 +++++++++++++------ 4 files changed, 145 insertions(+), 30 deletions(-) diff --git a/crates/gpui/src/platform/linux/wayland/client.rs b/crates/gpui/src/platform/linux/wayland/client.rs index 0f0c2c5b349ea4565409a1336eb9b579741dc983..a8b2871f40deb4eddda6fdc9f683ed9042583d88 100644 --- a/crates/gpui/src/platform/linux/wayland/client.rs +++ b/crates/gpui/src/platform/linux/wayland/client.rs @@ -63,7 +63,9 @@ use crate::platform::linux::is_within_click_distance; use crate::platform::linux::wayland::cursor::Cursor; use crate::platform::linux::wayland::serial::{SerialKind, SerialTracker}; use crate::platform::linux::wayland::window::WaylandWindow; -use crate::platform::linux::xdg_desktop_portal::{Event as XDPEvent, XDPEventSource}; +use crate::platform::linux::xdg_desktop_portal::{ + cursor_settings, Event as XDPEvent, XDPEventSource, +}; use crate::platform::linux::LinuxClient; use crate::platform::PlatformWindow; use crate::{ @@ -430,7 +432,7 @@ impl WaylandClient { let (primary, clipboard) = unsafe { create_clipboards_from_external(display) }; - let cursor = Cursor::new(&conn, &globals, 24); + let mut cursor = Cursor::new(&conn, &globals, 24); handle .insert_source(XDPEventSource::new(&common.background_executor), { @@ -446,10 +448,24 @@ impl WaylandClient { } } } + XDPEvent::CursorTheme(theme) => { + if let Some(client) = client.0.upgrade() { + let mut client = client.borrow_mut(); + client.cursor.set_theme(theme.as_str(), None); + } + } + XDPEvent::CursorSize(size) => { + if let Some(client) = client.0.upgrade() { + let mut client = client.borrow_mut(); + client.cursor.set_size(size); + } + } } }) .unwrap(); + let foreground = common.foreground_executor.clone(); + let mut state = Rc::new(RefCell::new(WaylandClientState { serial_tracker: SerialTracker::new(), globals, @@ -511,6 +527,18 @@ impl WaylandClient { pending_open_uri: None, })); + foreground + .spawn({ + let state = state.clone(); + async move { + if let Ok((theme, size)) = cursor_settings().await { + let mut state = state.borrow_mut(); + state.cursor.set_theme(theme.as_str(), size); + } + } + }) + .detach(); + WaylandSource::new(conn, event_queue) .insert(handle) .unwrap(); diff --git a/crates/gpui/src/platform/linux/wayland/cursor.rs b/crates/gpui/src/platform/linux/wayland/cursor.rs index 4ca4d4717e8cdf92ccb0a176d73bc58d60f8efd2..6a527650429a4eb6196184e3fedab5f7f21c929a 100644 --- a/crates/gpui/src/platform/linux/wayland/cursor.rs +++ b/crates/gpui/src/platform/linux/wayland/cursor.rs @@ -1,14 +1,18 @@ use crate::Globals; use util::ResultExt; -use wayland_client::protocol::wl_pointer::WlPointer; use wayland_client::protocol::wl_surface::WlSurface; +use wayland_client::protocol::{wl_pointer::WlPointer, wl_shm::WlShm}; use wayland_client::Connection; use wayland_cursor::{CursorImageBuffer, CursorTheme}; pub(crate) struct Cursor { theme: Option, + theme_name: Option, surface: WlSurface, + size: u32, + shm: WlShm, + connection: Connection, } impl Drop for Cursor { @@ -22,10 +26,49 @@ impl Cursor { pub fn new(connection: &Connection, globals: &Globals, size: u32) -> Self { Self { theme: CursorTheme::load(&connection, globals.shm.clone(), size).log_err(), + theme_name: None, surface: globals.compositor.create_surface(&globals.qh, ()), + shm: globals.shm.clone(), + connection: connection.clone(), + size, } } + pub fn set_theme(&mut self, theme_name: &str, size: Option) { + if let Some(size) = size { + self.size = size; + } + if let Some(theme) = + CursorTheme::load_from_name(&self.connection, self.shm.clone(), theme_name, self.size) + .log_err() + { + self.theme = Some(theme); + self.theme_name = Some(theme_name.to_string()); + } else if let Some(theme) = + CursorTheme::load(&self.connection, self.shm.clone(), self.size).log_err() + { + self.theme = Some(theme); + self.theme_name = None; + } + } + + pub fn set_size(&mut self, size: u32) { + self.size = size; + self.theme = self + .theme_name + .as_ref() + .and_then(|name| { + CursorTheme::load_from_name( + &self.connection, + self.shm.clone(), + name.as_str(), + self.size, + ) + .log_err() + }) + .or_else(|| CursorTheme::load(&self.connection, self.shm.clone(), self.size).log_err()); + } + 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>; diff --git a/crates/gpui/src/platform/linux/x11/client.rs b/crates/gpui/src/platform/linux/x11/client.rs index 5aa4546e74e2cb38670710254ffef14f05a61e19..b3b149d3c55397d5ac04d4faf2eb81874fa91921 100644 --- a/crates/gpui/src/platform/linux/x11/client.rs +++ b/crates/gpui/src/platform/linux/x11/client.rs @@ -359,6 +359,9 @@ impl X11Client { window.window.set_appearance(appearance); } } + XDPEvent::CursorTheme(_) | XDPEvent::CursorSize(_) => { + // noop, X11 manages this for us. + } } }) .unwrap(); diff --git a/crates/gpui/src/platform/linux/xdg_desktop_portal.rs b/crates/gpui/src/platform/linux/xdg_desktop_portal.rs index ef0430de8a9bc3c4667aa4c5a95e4ef29ec4e7a8..a0c5c02153aab1ad3b11f8002b2a92d2eb7e46fb 100644 --- a/crates/gpui/src/platform/linux/xdg_desktop_portal.rs +++ b/crates/gpui/src/platform/linux/xdg_desktop_portal.rs @@ -1,18 +1,18 @@ //! Provides a [calloop] event source from [XDG Desktop Portal] events //! -//! This module uses the [ashpd] crate and handles many async loop -use std::future::Future; +//! This module uses the [ashpd] crate use ashpd::desktop::settings::{ColorScheme, Settings}; -use calloop::channel::{Channel, Sender}; +use calloop::channel::Channel; use calloop::{EventSource, Poll, PostAction, Readiness, Token, TokenFactory}; use smol::stream::StreamExt; -use util::ResultExt; use crate::{BackgroundExecutor, WindowAppearance}; pub enum Event { WindowAppearance(WindowAppearance), + CursorTheme(String), + CursorSize(u32), } pub struct XDPEventSource { @@ -23,34 +23,62 @@ impl XDPEventSource { pub fn new(executor: &BackgroundExecutor) -> Self { let (sender, channel) = calloop::channel::channel(); - Self::spawn_observer(executor, Self::appearance_observer(sender.clone())); + let background = executor.clone(); - Self { channel } - } - - fn spawn_observer( - executor: &BackgroundExecutor, - to_spawn: impl Future> + Send + 'static, - ) { executor .spawn(async move { - to_spawn.await.log_err(); + let settings = Settings::new().await?; + + if let Ok(mut cursor_theme_changed) = settings + .receive_setting_changed_with_args( + "org.gnome.desktop.interface", + "cursor-theme", + ) + .await + { + let sender = sender.clone(); + background + .spawn(async move { + while let Some(theme) = cursor_theme_changed.next().await { + let theme = theme?; + sender.send(Event::CursorTheme(theme))?; + } + anyhow::Ok(()) + }) + .detach(); + } + + if let Ok(mut cursor_size_changed) = settings + .receive_setting_changed_with_args::( + "org.gnome.desktop.interface", + "cursor-size", + ) + .await + { + let sender = sender.clone(); + background + .spawn(async move { + while let Some(size) = cursor_size_changed.next().await { + let size = size?; + sender.send(Event::CursorSize(size))?; + } + anyhow::Ok(()) + }) + .detach(); + } + + let mut appearance_changed = settings.receive_color_scheme_changed().await?; + while let Some(scheme) = appearance_changed.next().await { + sender.send(Event::WindowAppearance(WindowAppearance::from_native( + scheme, + )))?; + } + + anyhow::Ok(()) }) - .detach() - } - - async fn appearance_observer(sender: Sender) -> Result<(), anyhow::Error> { - let settings = Settings::new().await?; + .detach(); - // We observe the color change during the execution of the application - let mut stream = settings.receive_color_scheme_changed().await?; - while let Some(scheme) = stream.next().await { - sender.send(Event::WindowAppearance(WindowAppearance::from_native( - scheme, - )))?; - } - - Ok(()) + Self { channel } } } @@ -142,3 +170,16 @@ pub fn should_auto_hide_scrollbars(executor: &BackgroundExecutor) -> Result Result<(String, Option), anyhow::Error> { + let settings = Settings::new().await?; + let cursor_theme = settings + .read::("org.gnome.desktop.interface", "cursor-theme") + .await?; + let cursor_size = settings + .read::("org.gnome.desktop.interface", "cursor-size") + .await + .ok(); + + Ok((cursor_theme, cursor_size)) +}