SDL: wayland: Fix animated cursor timing

From fcb8a2c016c6846afe2567423d8e7211edd77aad Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Fri, 20 Sep 2024 13:50:16 -0400
Subject: [PATCH] wayland: Fix animated cursor timing

Adjust the frame timing so it will still advance if the frame callback fires faster than the frame duration.
---
 src/video/wayland/SDL_waylandmouse.c | 17 ++++++++++++-----
 1 file changed, 12 insertions(+), 5 deletions(-)

diff --git a/src/video/wayland/SDL_waylandmouse.c b/src/video/wayland/SDL_waylandmouse.c
index 576e898a9c619..7b21a25491ad8 100644
--- a/src/video/wayland/SDL_waylandmouse.c
+++ b/src/video/wayland/SDL_waylandmouse.c
@@ -72,7 +72,8 @@ typedef struct
 {
     Wayland_SystemCursorFrame *frames;
     struct wl_callback *frame_callback;
-    Uint64 last_frame_time_ms;
+    Uint64 last_frame_callback_time_ms;
+    Uint64 current_frame_time_ms;
     Uint32 total_duration;
     int num_frames;
     int current_frame;
@@ -304,16 +305,20 @@ static void cursor_frame_done(void *data, struct wl_callback *cb, uint32_t time)
     SDL_CursorData *c = (SDL_CursorData *)data;
 
     const Uint64 now = SDL_GetTicks();
-    const Uint64 elapsed = (now - c->cursor_data.system.last_frame_time_ms) % c->cursor_data.system.total_duration;
+    const Uint64 elapsed = (now - c->cursor_data.system.last_frame_callback_time_ms) % c->cursor_data.system.total_duration;
+    Uint64 advance = 0;
     int next = c->cursor_data.system.current_frame;
 
     wl_callback_destroy(cb);
     c->cursor_data.system.frame_callback = wl_surface_frame(c->surface);
     wl_callback_add_listener(c->cursor_data.system.frame_callback, &cursor_frame_listener, data);
 
+    c->cursor_data.system.current_frame_time_ms += elapsed;
+
     // Calculate the next frame based on the elapsed duration.
-    for (Uint64 t = c->cursor_data.system.frames[next].duration; t <= elapsed; t += c->cursor_data.system.frames[next].duration) {
+    for (Uint64 t = c->cursor_data.system.frames[next].duration; t <= c->cursor_data.system.current_frame_time_ms; t += c->cursor_data.system.frames[next].duration) {
         next = (next + 1) % c->cursor_data.system.num_frames;
+        advance = t;
 
         // Make sure we don't end up in an infinite loop if a cursor has frame durations of 0.
         if (!c->cursor_data.system.frames[next].duration) {
@@ -321,7 +326,8 @@ static void cursor_frame_done(void *data, struct wl_callback *cb, uint32_t time)
         }
     }
 
-    c->cursor_data.system.last_frame_time_ms = now;
+    c->cursor_data.system.current_frame_time_ms -= advance;
+    c->cursor_data.system.last_frame_callback_time_ms = now;
     c->cursor_data.system.current_frame = next;
     wl_surface_attach(c->surface, c->cursor_data.system.frames[next].wl_buffer, 0, 0);
     if (wl_surface_get_version(c->surface) >= WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION) {
@@ -711,7 +717,8 @@ static bool Wayland_ShowCursor(SDL_Cursor *cursor)
 
             // If more than one frame is available, create a frame callback to run the animation.
             if (data->cursor_data.system.num_frames > 1) {
-                data->cursor_data.system.last_frame_time_ms = SDL_GetTicks();
+                data->cursor_data.system.last_frame_callback_time_ms = SDL_GetTicks();
+                data->cursor_data.system.current_frame_time_ms = 0;
                 data->cursor_data.system.current_frame = 0;
                 data->cursor_data.system.frame_callback = wl_surface_frame(data->surface);
                 wl_callback_add_listener(data->cursor_data.system.frame_callback, &cursor_frame_listener, data);