wayland: Improve cursor (#10516)

apricotbucket28 created

Fixes the cursor not updating when (a) switching windows from another
program via a shortcut and (b) when cursor updates were triggered by
something other than moving the mouse (e.g. when scrolling or pressing a
key).

Release Notes:

- N/A

Change summary

crates/gpui/src/platform/linux/wayland/client.rs | 23 +++++++++++++----
crates/gpui/src/platform/linux/wayland/cursor.rs | 22 ++++++++++++----
2 files changed, 33 insertions(+), 12 deletions(-)

Detailed changes

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

@@ -95,6 +95,7 @@ impl Globals {
 
 pub(crate) struct WaylandClientState {
     globals: Globals,
+    wl_pointer: Option<wl_pointer::WlPointer>,
     // Surface to Window mapping
     windows: HashMap<ObjectId, WaylandWindowStatePtr>,
     // Output to scale mapping
@@ -240,6 +241,7 @@ impl WaylandClient {
 
         let mut state = Rc::new(RefCell::new(WaylandClientState {
             globals,
+            wl_pointer: None,
             output_scales: outputs,
             windows: HashMap::default(),
             common,
@@ -343,7 +345,15 @@ impl LinuxClient for WaylandClient {
         }
         .to_string();
 
-        self.0.borrow_mut().cursor_icon_name = cursor_icon_name;
+        let mut state = self.0.borrow_mut();
+        state.cursor_icon_name = cursor_icon_name.clone();
+        if state.mouse_focused_window.is_some() {
+            let wl_pointer = state
+                .wl_pointer
+                .clone()
+                .expect("window is focused by pointer");
+            state.cursor.set_icon(&wl_pointer, &cursor_icon_name);
+        }
     }
 
     fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R {
@@ -403,6 +413,7 @@ impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for WaylandClientStat
                 version,
             } => match &interface[..] {
                 "wl_seat" => {
+                    state.wl_pointer = None;
                     registry.bind::<wl_seat::WlSeat, _, _>(name, wl_seat_version(version), qh, ());
                 }
                 "wl_output" => {
@@ -582,7 +593,9 @@ impl Dispatch<wl_seat::WlSeat, ()> for WaylandClientStatePtr {
                 seat.get_keyboard(qh, ());
             }
             if capabilities.contains(wl_seat::Capability::Pointer) {
-                seat.get_pointer(qh, ());
+                let client = state.get_client();
+                let mut state = client.borrow_mut();
+                state.wl_pointer = Some(seat.get_pointer(qh, ()));
             }
         }
     }
@@ -789,10 +802,11 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
                 if let Some(window) = get_window(&mut state, &surface.id()) {
                     state.enter_token = Some(());
                     state.mouse_focused_window = Some(window.clone());
+                    state.cursor.mark_dirty();
                     state.cursor.set_serial_id(serial);
                     state
                         .cursor
-                        .set_icon(&wl_pointer, Some(cursor_icon_name.as_str()));
+                        .set_icon(&wl_pointer, cursor_icon_name.as_str());
                     drop(state);
                     window.set_focused(true);
                 }
@@ -823,9 +837,6 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
                     return;
                 }
                 state.mouse_location = Some(point(px(surface_x as f32), px(surface_y as f32)));
-                state
-                    .cursor
-                    .set_icon(&wl_pointer, Some(cursor_icon_name.as_str()));
 
                 if let Some(window) = state.mouse_focused_window.clone() {
                     let input = PlatformInput::MouseMove(MouseMoveEvent {

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

@@ -8,7 +8,7 @@ use wayland_cursor::{CursorImageBuffer, CursorTheme};
 
 pub(crate) struct Cursor {
     theme: Option<CursorTheme>,
-    current_icon_name: String,
+    current_icon_name: Option<String>,
     surface: WlSurface,
     serial_id: u32,
 }
@@ -24,19 +24,29 @@ impl Cursor {
     pub fn new(connection: &Connection, globals: &Globals, size: u32) -> Self {
         Self {
             theme: CursorTheme::load(&connection, globals.shm.clone(), size).log_err(),
-            current_icon_name: "default".to_string(),
+            current_icon_name: None,
             surface: globals.compositor.create_surface(&globals.qh, ()),
             serial_id: 0,
         }
     }
 
+    pub fn mark_dirty(&mut self) {
+        self.current_icon_name = None;
+    }
+
     pub fn set_serial_id(&mut self, serial_id: u32) {
         self.serial_id = serial_id;
     }
 
-    pub fn set_icon(&mut self, wl_pointer: &WlPointer, mut cursor_icon_name: Option<&str>) {
-        let mut cursor_icon_name = cursor_icon_name.unwrap_or("default");
-        if self.current_icon_name != cursor_icon_name {
+    pub fn set_icon(&mut self, wl_pointer: &WlPointer, mut cursor_icon_name: &str) {
+        let need_update = self
+            .current_icon_name
+            .as_ref()
+            .map_or(true, |current_icon_name| {
+                current_icon_name != cursor_icon_name
+            });
+
+        if need_update {
             if let Some(theme) = &mut self.theme {
                 let mut buffer: Option<&CursorImageBuffer>;
 
@@ -68,7 +78,7 @@ impl Cursor {
                     self.surface.damage(0, 0, width as i32, height as i32);
                     self.surface.commit();
 
-                    self.current_icon_name = cursor_icon_name.to_string();
+                    self.current_icon_name = Some(cursor_icon_name.to_string());
                 }
             } else {
                 log::warn!("Linux: Wayland: Unable to load cursor themes");