wayland: Refactor serial usage (#11388)

apricotbucket28 created

Adds a `SerialTracker` type which helps simplify serial handling.

Release Notes:

- N/A

Change summary

crates/gpui/src/platform/linux/wayland.rs        |  1 
crates/gpui/src/platform/linux/wayland/client.rs | 42 +++-----
crates/gpui/src/platform/linux/wayland/serial.rs | 91 ++++++++++++++++++
3 files changed, 108 insertions(+), 26 deletions(-)

Detailed changes

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

@@ -55,6 +55,7 @@ use super::super::{open_uri_internal, read_fd, DOUBLE_CLICK_INTERVAL};
 use super::window::{WaylandWindowState, WaylandWindowStatePtr};
 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::LinuxClient;
 use crate::platform::PlatformWindow;
@@ -124,8 +125,7 @@ impl Globals {
 }
 
 pub(crate) struct WaylandClientState {
-    serial: u32, // todo(linux): storing a general serial is wrong
-    pointer_serial: u32,
+    serial_tracker: SerialTracker,
     globals: Globals,
     wl_seat: wl_seat::WlSeat, // todo(linux): multi-seat support
     wl_pointer: Option<wl_pointer::WlPointer>,
@@ -315,8 +315,7 @@ impl WaylandClient {
         let cursor = Cursor::new(&conn, &globals, 24);
 
         let mut state = Rc::new(RefCell::new(WaylandClientState {
-            serial: 0,
-            pointer_serial: 0,
+            serial_tracker: SerialTracker::new(),
             globals,
             wl_seat: seat,
             wl_pointer: None,
@@ -414,7 +413,7 @@ impl LinuxClient for WaylandClient {
             .map_or(true, |current_style| current_style != style);
 
         if need_update {
-            let serial = state.pointer_serial;
+            let serial = state.serial_tracker.get(SerialKind::MouseEnter);
             state.cursor_style = Some(style);
 
             if let Some(cursor_shape_device) = &state.cursor_shape_device {
@@ -440,7 +439,8 @@ impl LinuxClient for WaylandClient {
         ) {
             state.pending_open_uri = Some(uri.to_owned());
             let token = activation.get_activation_token(&state.globals.qh, ());
-            token.set_serial(state.serial, &state.wl_seat);
+            let serial = state.serial_tracker.get(SerialKind::MousePress);
+            token.set_serial(serial, &state.wl_seat);
             token.set_surface(&window.surface());
             token.commit();
         } else {
@@ -692,7 +692,7 @@ impl Dispatch<xdg_toplevel::XdgToplevel, ObjectId> for WaylandClientStatePtr {
 
 impl Dispatch<xdg_wm_base::XdgWmBase, ()> for WaylandClientStatePtr {
     fn event(
-        this: &mut Self,
+        _: &mut Self,
         wm_base: &xdg_wm_base::XdgWmBase,
         event: <xdg_wm_base::XdgWmBase as Proxy>::Event,
         _: &(),
@@ -700,9 +700,6 @@ impl Dispatch<xdg_wm_base::XdgWmBase, ()> for WaylandClientStatePtr {
         _: &QueueHandle<Self>,
     ) {
         if let xdg_wm_base::Event::Ping { serial } = event {
-            let client = this.get_client();
-            let mut state = client.borrow_mut();
-            state.serial = serial;
             wm_base.pong(serial);
         }
     }
@@ -802,10 +799,7 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
                 };
                 state.keymap_state = Some(xkb::State::new(&keymap));
             }
-            wl_keyboard::Event::Enter {
-                serial, surface, ..
-            } => {
-                state.serial = serial;
+            wl_keyboard::Event::Enter { surface, .. } => {
                 state.keyboard_focused_window = get_window(&mut state, &surface.id());
                 state.enter_token = Some(());
 
@@ -814,10 +808,7 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
                     window.set_focused(true);
                 }
             }
-            wl_keyboard::Event::Leave {
-                serial, surface, ..
-            } => {
-                state.serial = serial;
+            wl_keyboard::Event::Leave { surface, .. } => {
                 let keyboard_focused_window = get_window(&mut state, &surface.id());
                 state.keyboard_focused_window = None;
                 state.enter_token.take();
@@ -828,14 +819,12 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
                 }
             }
             wl_keyboard::Event::Modifiers {
-                serial,
                 mods_depressed,
                 mods_latched,
                 mods_locked,
                 group,
                 ..
             } => {
-                state.serial = serial;
                 let focused_window = state.keyboard_focused_window.clone();
                 let Some(focused_window) = focused_window else {
                     return;
@@ -853,12 +842,12 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
                 focused_window.handle_input(input);
             }
             wl_keyboard::Event::Key {
+                serial,
                 key,
                 state: WEnum::Value(key_state),
-                serial,
                 ..
             } => {
-                state.serial = serial;
+                state.serial_tracker.update(SerialKind::KeyPress, serial);
 
                 let focused_window = state.keyboard_focused_window.clone();
                 let Some(focused_window) = focused_window else {
@@ -971,7 +960,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
                 surface_y,
                 ..
             } => {
-                state.pointer_serial = serial;
+                state.serial_tracker.update(SerialKind::MouseEnter, serial);
                 state.mouse_location = Some(point(px(surface_x as f32), px(surface_y as f32)));
 
                 if let Some(window) = get_window(&mut state, &surface.id()) {
@@ -1041,7 +1030,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
                 state: WEnum::Value(button_state),
                 ..
             } => {
-                state.serial = serial;
+                state.serial_tracker.update(SerialKind::MousePress, serial);
                 let button = linux_button_to_gpui(button);
                 let Some(button) = button else { return };
                 if state.mouse_focused_window.is_none() {
@@ -1299,7 +1288,7 @@ impl Dispatch<wl_data_device::WlDataDevice, ()> for WaylandClientStatePtr {
                 y,
                 id: data_offer,
             } => {
-                state.serial = serial;
+                state.serial_tracker.update(SerialKind::DataDevice, serial);
                 if let Some(data_offer) = data_offer {
                     let Some(drag_window) = get_window(&mut state, &surface.id()) else {
                         return;
@@ -1429,7 +1418,8 @@ impl Dispatch<wl_data_offer::WlDataOffer, ()> for WaylandClientStatePtr {
         match event {
             wl_data_offer::Event::Offer { mime_type } => {
                 if mime_type == FILE_LIST_MIME_TYPE {
-                    data_offer.accept(state.serial, Some(mime_type));
+                    let serial = state.serial_tracker.get(SerialKind::DataDevice);
+                    data_offer.accept(serial, Some(mime_type));
                 }
             }
             _ => {}

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

@@ -0,0 +1,91 @@
+use std::time::Instant;
+
+use collections::HashMap;
+
+#[derive(Debug, Hash, PartialEq, Eq)]
+pub(crate) enum SerialKind {
+    DataDevice,
+    MouseEnter,
+    MousePress,
+    KeyPress,
+}
+
+#[derive(Debug)]
+struct SerialData {
+    serial: u32,
+    time: Instant,
+}
+
+impl SerialData {
+    fn new(value: u32) -> Self {
+        Self {
+            serial: value,
+            time: Instant::now(),
+        }
+    }
+}
+
+#[derive(Debug)]
+/// Helper for tracking of different serial kinds.
+pub(crate) struct SerialTracker {
+    serials: HashMap<SerialKind, SerialData>,
+}
+
+impl SerialTracker {
+    pub fn new() -> Self {
+        Self {
+            serials: HashMap::default(),
+        }
+    }
+
+    pub fn update(&mut self, kind: SerialKind, value: u32) {
+        self.serials.insert(kind, SerialData::new(value));
+    }
+
+    /// Returns the latest tracked serial of the provided [`SerialKind`]
+    ///
+    /// Will return 0 if not tracked.
+    pub fn get(&self, kind: SerialKind) -> u32 {
+        self.serials
+            .get(&kind)
+            .map(|serial_data| serial_data.serial)
+            .unwrap_or(0)
+    }
+
+    /// Returns the newest serial of any of the provided [`SerialKind`]
+    pub fn get_newest_of(&self, kinds: &[SerialKind]) -> u32 {
+        kinds
+            .iter()
+            .filter_map(|kind| self.serials.get(&kind))
+            .max_by_key(|serial_data| serial_data.time)
+            .map(|serial_data| serial_data.serial)
+            .unwrap_or(0)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_serial_tracker() {
+        let mut tracker = SerialTracker::new();
+
+        tracker.update(SerialKind::KeyPress, 100);
+        tracker.update(SerialKind::MousePress, 50);
+        tracker.update(SerialKind::MouseEnter, 300);
+
+        assert_eq!(
+            tracker.get_newest_of(&[SerialKind::KeyPress, SerialKind::MousePress]),
+            50
+        );
+        assert_eq!(tracker.get(SerialKind::DataDevice), 0);
+
+        tracker.update(SerialKind::KeyPress, 2000);
+        assert_eq!(tracker.get(SerialKind::KeyPress), 2000);
+        assert_eq!(
+            tracker.get_newest_of(&[SerialKind::KeyPress, SerialKind::MousePress]),
+            2000
+        );
+    }
+}