wayland: Avoid reloading cursor theme on every cursor style change (#32832)

Michael Sloan created

Release Notes:

- N/A

Change summary

crates/gpui/src/platform/linux/wayland/client.rs |   2 
crates/gpui/src/platform/linux/wayland/cursor.rs | 103 +++++++++--------
2 files changed, 58 insertions(+), 47 deletions(-)

Detailed changes

crates/gpui/src/platform/linux/wayland/client.rs 🔗

@@ -537,7 +537,7 @@ 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());
+                            client.cursor.set_theme(theme);
                         }
                     }
                     XDPEvent::CursorSize(size) => {

crates/gpui/src/platform/linux/wayland/cursor.rs 🔗

@@ -1,6 +1,6 @@
 use crate::Globals;
 use crate::platform::linux::{DEFAULT_CURSOR_ICON_NAME, log_cursor_icon_warning};
-use anyhow::anyhow;
+use anyhow::{Context as _, anyhow};
 use util::ResultExt;
 
 use wayland_client::Connection;
@@ -9,75 +9,85 @@ use wayland_client::protocol::{wl_pointer::WlPointer, wl_shm::WlShm};
 use wayland_cursor::{CursorImageBuffer, CursorTheme};
 
 pub(crate) struct Cursor {
-    theme: Option<CursorTheme>,
-    theme_name: Option<String>,
-    theme_size: u32,
-    surface: WlSurface,
+    loaded_theme: Option<LoadedTheme>,
     size: u32,
+    scaled_size: u32,
+    surface: WlSurface,
     shm: WlShm,
     connection: Connection,
 }
 
+pub(crate) struct LoadedTheme {
+    theme: CursorTheme,
+    name: Option<String>,
+    scaled_size: u32,
+}
+
 impl Drop for Cursor {
     fn drop(&mut self) {
-        self.theme.take();
+        self.loaded_theme.take();
         self.surface.destroy();
     }
 }
 
 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,
-            theme_size: size,
+        let mut this = Self {
+            loaded_theme: None,
+            size,
+            scaled_size: size,
             surface: globals.compositor.create_surface(&globals.qh, ()),
             shm: globals.shm.clone(),
             connection: connection.clone(),
-            size,
-        }
+        };
+        this.set_theme_internal(None);
+        this
     }
 
-    pub fn set_theme(&mut self, theme_name: &str) {
-        if let Some(theme) = CursorTheme::load_from_name(
-            &self.connection,
-            self.shm.clone(),
-            theme_name,
-            self.theme_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.theme_size).log_err()
+    fn set_theme_internal(&mut self, theme_name: Option<String>) {
+        if let Some(loaded_theme) = self.loaded_theme.as_ref() {
+            if loaded_theme.name == theme_name && loaded_theme.scaled_size == self.scaled_size {
+                return;
+            }
+        }
+        let result = if let Some(theme_name) = theme_name.as_ref() {
+            CursorTheme::load_from_name(
+                &self.connection,
+                self.shm.clone(),
+                theme_name,
+                self.scaled_size,
+            )
+        } else {
+            CursorTheme::load(&self.connection, self.shm.clone(), self.scaled_size)
+        };
+        if let Some(theme) = result
+            .context("Wayland: Failed to load cursor theme")
+            .log_err()
         {
-            self.theme = Some(theme);
-            self.theme_name = None;
+            self.loaded_theme = Some(LoadedTheme {
+                theme,
+                name: theme_name.map(|name| name.to_string()),
+                scaled_size: self.scaled_size,
+            });
         }
     }
 
-    fn set_theme_size(&mut self, theme_size: u32) {
-        self.theme = self
-            .theme_name
+    pub fn set_theme(&mut self, theme_name: String) {
+        self.set_theme_internal(Some(theme_name));
+    }
+
+    fn set_scaled_size(&mut self, scaled_size: u32) {
+        self.scaled_size = scaled_size;
+        let theme_name = self
+            .loaded_theme
             .as_ref()
-            .and_then(|name| {
-                CursorTheme::load_from_name(
-                    &self.connection,
-                    self.shm.clone(),
-                    name.as_str(),
-                    theme_size,
-                )
-                .log_err()
-            })
-            .or_else(|| {
-                CursorTheme::load(&self.connection, self.shm.clone(), theme_size).log_err()
-            });
+            .and_then(|loaded_theme| loaded_theme.name.clone());
+        self.set_theme_internal(theme_name);
     }
 
     pub fn set_size(&mut self, size: u32) {
         self.size = size;
-        self.set_theme_size(size);
+        self.set_scaled_size(size);
     }
 
     pub fn set_icon(
@@ -87,12 +97,13 @@ impl Cursor {
         mut cursor_icon_names: &[&str],
         scale: i32,
     ) {
-        self.set_theme_size(self.size * scale as u32);
+        self.set_scaled_size(self.size * scale as u32);
 
-        let Some(theme) = &mut self.theme else {
+        let Some(loaded_theme) = &mut self.loaded_theme else {
             log::warn!("Wayland: Unable to load cursor themes");
             return;
         };
+        let mut theme = &mut loaded_theme.theme;
 
         let mut buffer: &CursorImageBuffer;
         'outer: {
@@ -115,7 +126,7 @@ impl Cursor {
                 log_cursor_icon_warning(anyhow!(
                     "wayland: Unable to fallback on default cursor icon '{}' for theme '{}'",
                     DEFAULT_CURSOR_ICON_NAME,
-                    self.theme_name.as_deref().unwrap_or("default")
+                    loaded_theme.name.as_deref().unwrap_or("default")
                 ));
                 return;
             }