SDL: wayland: Refactor animated cursor handling

From 69692de8b8ab3de82b57bce5c109d6d5f5841b25 Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Fri, 10 Oct 2025 13:41:50 -0400
Subject: [PATCH] wayland: Refactor animated cursor handling

Generalizes the animated cursor info, so it can be used for custom cursors as well.
---
 src/video/wayland/SDL_waylandevents_c.h  |   7 +-
 src/video/wayland/SDL_waylandmouse.c     | 242 +++++++++++++++--------
 src/video/wayland/SDL_waylandshmbuffer.c |  78 +++++++-
 src/video/wayland/SDL_waylandshmbuffer.h |  23 ++-
 src/video/wayland/SDL_waylandwindow.c    |   4 +-
 src/video/wayland/SDL_waylandwindow.h    |   2 +-
 6 files changed, 255 insertions(+), 101 deletions(-)

diff --git a/src/video/wayland/SDL_waylandevents_c.h b/src/video/wayland/SDL_waylandevents_c.h
index 03c44375f6369..077c2659f4b54 100644
--- a/src/video/wayland/SDL_waylandevents_c.h
+++ b/src/video/wayland/SDL_waylandevents_c.h
@@ -182,10 +182,11 @@ typedef struct SDL_WaylandSeat
             struct wl_surface *surface;
             struct wp_viewport *viewport;
 
-            // Animation state for legacy animated cursors
+            // Animation state for cursors
+            void *cursor_handle;
             struct wl_callback *frame_callback;
-            Uint64 last_frame_callback_time_ns;
-            Uint64 current_frame_time_ns;
+            Uint64 last_frame_callback_time_ms;
+            Uint32 current_frame_time_ms;
             int current_frame;
         } cursor_state;
     } pointer;
diff --git a/src/video/wayland/SDL_waylandmouse.c b/src/video/wayland/SDL_waylandmouse.c
index e92adedd710a4..3ff173c70eded 100644
--- a/src/video/wayland/SDL_waylandmouse.c
+++ b/src/video/wayland/SDL_waylandmouse.c
@@ -48,42 +48,45 @@ static bool Wayland_SetRelativeMouseMode(bool enabled);
 
 typedef struct
 {
-    struct Wayland_SHMBuffer shmBuffer;
+    Wayland_SHMPool *shmPool;
     double scale;
     struct wl_list node;
 } Wayland_ScaledCustomCursor;
 
 typedef struct
 {
-    SDL_Surface *sdl_cursor_surface;
     int hot_x;
     int hot_y;
     struct wl_list scaled_cursor_cache;
+    SDL_Surface *sdl_cursor_surfaces[];
 } Wayland_CustomCursor;
 
 typedef struct
 {
-    struct wl_buffer *wl_buffer;
-    Uint64 duration_ns;
-} Wayland_SystemCursorFrame;
+    int size;
+    struct wl_list node;
+    struct wl_buffer *buffers[];
+} Wayland_CachedSystemCursor;
 
 typedef struct
 {
-    Wayland_SystemCursorFrame *frames;
-    Uint64 total_duration_ns;
-    int num_frames;
     SDL_SystemCursor id;
+    struct wl_list cursor_buffer_cache;
 } Wayland_SystemCursor;
 
 struct SDL_CursorData
 {
+    // Cursor animation data.
+    Uint32 *frame_durations_ms;
+    Uint32 total_duration_ms;
+    int num_frames;
+    bool is_system_cursor;
+
     union
     {
         Wayland_CustomCursor custom;
         Wayland_SystemCursor system;
     } cursor_data;
-
-    bool is_system_cursor;
 };
 
 static int dbus_cursor_size;
@@ -285,6 +288,21 @@ static void Wayland_DBusFinishCursorProperties(void)
 
 #endif
 
+static struct wl_buffer *Wayland_SeatGetCursorFrame(SDL_WaylandSeat *seat, int frame_index)
+{
+    SDL_CursorData *data = seat->pointer.current_cursor;
+
+    if (data) {
+        if (!data->is_system_cursor) {
+            return ((Wayland_ScaledCustomCursor *)(seat->pointer.cursor_state.cursor_handle))->shmPool->buffers[frame_index].wl_buffer;
+        } else {
+            return ((Wayland_CachedSystemCursor *)(seat->pointer.cursor_state.cursor_handle))->buffers[frame_index];
+        }
+    }
+
+    return NULL;
+}
+
 static void cursor_frame_done(void *data, struct wl_callback *cb, uint32_t time);
 struct wl_callback_listener cursor_frame_listener = {
     cursor_frame_done
@@ -293,34 +311,42 @@ struct wl_callback_listener cursor_frame_listener = {
 static void cursor_frame_done(void *data, struct wl_callback *cb, uint32_t time)
 {
     SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data;
-    SDL_CursorData *c = (struct SDL_CursorData *)seat->pointer.current_cursor;
+    if (!seat->pointer.current_cursor) {
+        return;
+    }
 
-    const Uint64 now = SDL_GetTicksNS();
-    const Uint64 elapsed = (now - seat->pointer.cursor_state.last_frame_callback_time_ns) % c->cursor_data.system.total_duration_ns;
-    Uint64 advance = 0;
+    Uint32 *frames = seat->pointer.current_cursor->frame_durations_ms;
+    SDL_CursorData *c = seat->pointer.current_cursor;
+
+    const Uint64 now = SDL_GetTicks();
+    const Uint32 elapsed = (now - seat->pointer.cursor_state.last_frame_callback_time_ms) % c->total_duration_ms;
+    Uint32 advance = 0;
     int next = seat->pointer.cursor_state.current_frame;
 
     wl_callback_destroy(cb);
     seat->pointer.cursor_state.frame_callback = wl_surface_frame(seat->pointer.cursor_state.surface);
     wl_callback_add_listener(seat->pointer.cursor_state.frame_callback, &cursor_frame_listener, data);
 
-    seat->pointer.cursor_state.current_frame_time_ns += elapsed;
+    seat->pointer.cursor_state.current_frame_time_ms += elapsed;
 
     // Calculate the next frame based on the elapsed duration.
-    for (Uint64 t = c->cursor_data.system.frames[next].duration_ns; t <= seat->pointer.cursor_state.current_frame_time_ns; t += c->cursor_data.system.frames[next].duration_ns) {
-        next = (next + 1) % c->cursor_data.system.num_frames;
+    for (Uint32 t = frames[next]; t <= seat->pointer.cursor_state.current_frame_time_ms; t += frames[next]) {
+        next = (next + 1) % c->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_ns) {
+        if (!frames[next]) {
             break;
         }
     }
 
-    seat->pointer.cursor_state.current_frame_time_ns -= advance;
-    seat->pointer.cursor_state.last_frame_callback_time_ns = now;
+    seat->pointer.cursor_state.current_frame_time_ms -= advance;
+    seat->pointer.cursor_state.last_frame_callback_time_ms = now;
     seat->pointer.cursor_state.current_frame = next;
-    wl_surface_attach(seat->pointer.cursor_state.surface, c->cursor_data.system.frames[next].wl_buffer, 0, 0);
+
+    struct wl_buffer *buffer = Wayland_SeatGetCursorFrame(seat, next);
+    wl_surface_attach(seat->pointer.cursor_state.surface, buffer, 0, 0);
+
     if (wl_surface_get_version(seat->pointer.cursor_state.surface) >= WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION) {
         wl_surface_damage_buffer(seat->pointer.cursor_state.surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32);
     } else {
@@ -329,10 +355,39 @@ static void cursor_frame_done(void *data, struct wl_callback *cb, uint32_t time)
     wl_surface_commit(seat->pointer.cursor_state.surface);
 }
 
-static bool Wayland_GetSystemCursor(SDL_VideoData *vdata, SDL_CursorData *cdata, int *scale, int *dst_size, int *hot_x, int *hot_y)
+static Wayland_CachedSystemCursor *Wayland_CacheSystemCursor(SDL_CursorData *cdata, struct wl_cursor *cursor, int size)
+{
+    Wayland_CachedSystemCursor *cache = NULL;
+
+    // Is this cursor already cached at the target scale?
+    if (!WAYLAND_wl_list_empty(&cdata->cursor_data.system.cursor_buffer_cache)) {
+        Wayland_CachedSystemCursor *c = NULL;
+        wl_list_for_each (c, &cdata->cursor_data.system.cursor_buffer_cache, node) {
+            if (c->size == size) {
+                cache = c;
+                break;
+            }
+        }
+    }
+
+    if (!cache) {
+        cache = SDL_calloc(1, sizeof(Wayland_CachedSystemCursor) + (sizeof(struct wl_buffer *) * cdata->num_frames));
+
+        cache->size = size;
+        for (int i = 0; i < cdata->num_frames; ++i) {
+            cache->buffers[i] = WAYLAND_wl_cursor_image_get_buffer(cursor->images[i]);
+        }
+
+        WAYLAND_wl_list_insert(&cdata->cursor_data.system.cursor_buffer_cache, &cache->node);
+    }
+
+    return cache;
+}
+
+static bool Wayland_GetSystemCursor(SDL_CursorData *cdata, SDL_WaylandSeat *seat, int *scale, int *dst_size, int *hot_x, int *hot_y)
 {
+    SDL_VideoData *vdata = seat->display;
     struct wl_cursor_theme *theme = NULL;
-    struct wl_cursor *cursor;
     const char *css_name = "default";
     const char *fallback_name = NULL;
     double scale_factor = 1.0;
@@ -383,7 +438,7 @@ static bool Wayland_GetSystemCursor(SDL_VideoData *vdata, SDL_CursorData *cdata,
     }
 
     css_name = SDL_GetCSSCursorName(cdata->cursor_data.system.id, &fallback_name);
-    cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, css_name);
+    struct wl_cursor *cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, css_name);
     if (!cursor && fallback_name) {
         cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, fallback_name);
     }
@@ -400,21 +455,19 @@ static bool Wayland_GetSystemCursor(SDL_VideoData *vdata, SDL_CursorData *cdata,
         return false;
     }
 
-    if (cdata->cursor_data.system.num_frames != cursor->image_count) {
-        SDL_free(cdata->cursor_data.system.frames);
-        cdata->cursor_data.system.frames = SDL_calloc(cursor->image_count, sizeof(Wayland_SystemCursorFrame));
-        if (!cdata->cursor_data.system.frames) {
-            return false;
-        }
-    }
-
     // ... Set the cursor data, finally.
-    cdata->cursor_data.system.num_frames = cursor->image_count;
-    cdata->cursor_data.system.total_duration_ns = 0;
-    for (int i = 0; i < cursor->image_count; ++i) {
-        cdata->cursor_data.system.frames[i].wl_buffer = WAYLAND_wl_cursor_image_get_buffer(cursor->images[i]);
-        cdata->cursor_data.system.frames[i].duration_ns = SDL_MS_TO_NS((Uint64)cursor->images[i]->delay);
-        cdata->cursor_data.system.total_duration_ns += cdata->cursor_data.system.frames[i].duration_ns;
+    cdata->num_frames = cursor->image_count;
+    Wayland_CachedSystemCursor *c = Wayland_CacheSystemCursor(cdata, cursor, theme_size);
+    seat->pointer.cursor_state.cursor_handle = c;
+
+    if (cursor->image_count > 1 && !cdata->frame_durations_ms) {
+        cdata->total_duration_ms = 0;
+        cdata->frame_durations_ms = SDL_calloc(cursor->image_count, sizeof(Uint32));
+
+        for (int i = 0; i < cursor->image_count; ++i) {
+            cdata->frame_durations_ms[i] = cursor->images[i]->delay;
+            cdata->total_duration_ms += cursor->images[i]->delay;
+        }
     }
 
     *scale = SDL_ceil(scale_factor) == scale_factor ? (int)scale_factor : 0;
@@ -447,14 +500,14 @@ static bool Wayland_GetSystemCursor(SDL_VideoData *vdata, SDL_CursorData *cdata,
     return true;
 }
 
-static Wayland_ScaledCustomCursor *Wayland_CacheScaledCustomCursor(SDL_CursorData *cdata, double scale)
+static Wayland_ScaledCustomCursor *Wayland_CacheScaledCustomCursor(SDL_CursorData *cursor, double scale)
 {
     Wayland_ScaledCustomCursor *cache = NULL;
 
     // Is this cursor already cached at the target scale?
-    if (!WAYLAND_wl_list_empty(&cdata->cursor_data.custom.scaled_cursor_cache)) {
+    if (!WAYLAND_wl_list_empty(&cursor->cursor_data.custom.scaled_cursor_cache)) {
         Wayland_ScaledCustomCursor *c = NULL;
-        wl_list_for_each (c, &cdata->cursor_data.custom.scaled_cursor_cache, node) {
+        wl_list_for_each (c, &cursor->cursor_data.custom.scaled_cursor_cache, node) {
             if (c->scale == scale) {
                 cache = c;
                 break;
@@ -468,41 +521,50 @@ static Wayland_ScaledCustomCursor *Wayland_CacheScaledCustomCursor(SDL_CursorDat
             return NULL;
         }
 
-        SDL_Surface *surface = SDL_GetSurfaceImage(cdata->cursor_data.custom.sdl_cursor_surface, (float)scale);
+        SDL_Surface *surface = SDL_GetSurfaceImage(cursor->cursor_data.custom.sdl_cursor_surfaces[0], (float)scale);
         if (!surface) {
             SDL_free(cache);
             return NULL;
         }
 
         // Allocate the shared memory buffer for this cursor.
-        if (!Wayland_AllocSHMBuffer(surface->w, surface->h, &cache->shmBuffer)) {
+        cache->shmPool = Wayland_AllocSHMPool(surface->w, surface->h, cursor->num_frames);
+        if (!cache->shmPool) {
             SDL_free(cache);
             SDL_DestroySurface(surface);
             return NULL;
         }
 
-        // Wayland requires premultiplied alpha for its surfaces.
-        SDL_PremultiplyAlpha(surface->w, surface->h,
-                             surface->format, surface->pixels, surface->pitch,
-                             SDL_PIXELFORMAT_ARGB8888, cache->shmBuffer.shm_data, surface->w * 4, true);
+        for (int i = 0; i < cursor->num_frames; ++i) {
+            if (!surface) {
+                surface = SDL_GetSurfaceImage(cursor->cursor_data.custom.sdl_cursor_surfaces[i], (float)scale);
+            }
+
+            // Wayland requires premultiplied alpha for its surfaces.
+            SDL_PremultiplyAlpha(surface->w, surface->h,
+                                 surface->format, surface->pixels, surface->pitch,
+                                 SDL_PIXELFORMAT_ARGB8888, cache->shmPool->buffers[i].shm_data, surface->w * 4, true);
+
+            SDL_DestroySurface(surface);
+            surface = NULL;
+        }
 
         cache->scale = scale;
-        WAYLAND_wl_list_insert(&cdata->cursor_data.custom.scaled_cursor_cache, &cache->node);
-        SDL_DestroySurface(surface);
+        WAYLAND_wl_list_insert(&cursor->cursor_data.custom.scaled_cursor_cache, &cache->node);
     }
 
     return cache;
 }
 
-static bool Wayland_GetCustomCursor(SDL_Cursor *cursor, struct wl_buffer **buffer, int *scale, int *dst_width, int *dst_height, int *hot_x, int *hot_y)
+static bool Wayland_GetCustomCursor(SDL_CursorData *cursor, SDL_WaylandSeat *seat, int *scale, int *dst_width, int *dst_height, int *hot_x, int *hot_y)
 {
     SDL_VideoDevice *vd = SDL_GetVideoDevice();
     SDL_VideoData *wd = vd->internal;
-    SDL_CursorData *data = cursor->internal;
+    Wayland_CustomCursor *custom_cursor = &cursor->cursor_data.custom;
     SDL_Window *focus = SDL_GetMouseFocus();
     double scale_factor = 1.0;
 
-    if (focus && SDL_SurfaceHasAlternateImages(data->cursor_data.custom.sdl_cursor_surface)) {
+    if (focus && SDL_SurfaceHasAlternateImages(custom_cursor->sdl_cursor_surfaces[0])) {
         scale_factor = focus->internal->scale_factor;
     }
 
@@ -511,17 +573,17 @@ static bool Wayland_GetCustomCursor(SDL_Cursor *cursor, struct wl_buffer **buffe
         scale_factor = SDL_ceil(scale_factor);
     }
 
-    Wayland_ScaledCustomCursor *c = Wayland_CacheScaledCustomCursor(data, scale_factor);
+    Wayland_ScaledCustomCursor *c = Wayland_CacheScaledCustomCursor(cursor, scale_factor);
     if (!c) {
         return false;
     }
 
-    *buffer = c->shmBuffer.wl_buffer;
+    seat->pointer.cursor_state.cursor_handle = c;
     *scale = SDL_ceil(scale_factor) == scale_factor ? (int)scale_factor : 0;
-    *dst_width = data->cursor_data.custom.sdl_cursor_surface->w;
-    *dst_height = data->cursor_data.custom.sdl_cursor_surface->h;
-    *hot_x = data->cursor_data.custom.hot_x;
-    *hot_y = data->cursor_data.custom.hot_y;
+    *dst_width = custom_cursor->sdl_cursor_surfaces[0]->w;
+    *dst_height = custom_cursor->sdl_cursor_surfaces[0]->h;
+    *hot_x = custom_cursor->hot_x;
+    *hot_y = custom_cursor->hot_y;
 
     return true;
 }
@@ -531,17 +593,18 @@ static SDL_Cursor *Wayland_CreateCursor(SDL_Surface *surface, int hot_x, int hot
     SDL_Cursor *cursor = SDL_calloc(1, sizeof(*cursor));
 
     if (cursor) {
-        SDL_CursorData *data = SDL_calloc(1, sizeof(*data));
+        SDL_CursorData *data = SDL_calloc(1, sizeof(*data) + sizeof(SDL_Surface *));
         if (!data) {
             SDL_free(cursor);
             return NULL;
         }
         cursor->internal = data;
         WAYLAND_wl_list_init(&data->cursor_data.custom.scaled_cursor_cache);
+        data->num_frames = 1;
         data->cursor_data.custom.hot_x = hot_x;
         data->cursor_data.custom.hot_y = hot_y;
 
-        data->cursor_data.custom.sdl_cursor_surface = surface;
+        data->cursor_data.custom.sdl_cursor_surfaces[0] = surface;
         ++surface->refcount;
 
         // If the cursor has only one size, just prepare it now.
@@ -565,6 +628,7 @@ static SDL_Cursor *Wayland_CreateSystemCursor(SDL_SystemCursor id)
         }
         cursor->internal = cdata;
 
+        WAYLAND_wl_list_init(&cdata->cursor_data.system.cursor_buffer_cache);
         cdata->cursor_data.system.id = id;
         cdata->is_system_cursor = true;
     }
@@ -596,22 +660,29 @@ static void Wayland_FreeCursorData(SDL_CursorData *d)
                 wl_surface_attach(seat->pointer.cursor_state.surface, NULL, 0, 0);
             }
 
+
             seat->pointer.current_cursor = NULL;
         }
     }
 
-    // Buffers for system cursors must not be destroyed.
     if (d->is_system_cursor) {
-        SDL_free(d->cursor_data.system.frames);
+        Wayland_CachedSystemCursor *c, *temp;
+        wl_list_for_each_safe(c, temp, &d->cursor_data.system.cursor_buffer_cache, node) {
+            SDL_free(c);
+        }
     } else {
         Wayland_ScaledCustomCursor *c, *temp;
         wl_list_for_each_safe(c, temp, &d->cursor_data.custom.scaled_cursor_cache, node) {
-            Wayland_ReleaseSHMBuffer(&c->shmBuffer);
+            Wayland_ReleaseSHMPool(c->shmPool);
             SDL_free(c);
         }
 
-        SDL_DestroySurface(d->cursor_data.custom.sdl_cursor_surface);
+        for (int i = 0; i < d->num_frames; ++i) {
+            SDL_DestroySurface(d->cursor_data.custom.sdl_cursor_surfaces[i]);
+        }
     }
+
+    SDL_free(d->frame_durations_ms);
 }
 
 static void Wayland_FreeCursor(SDL_Cursor *cursor)
@@ -707,13 +778,12 @@ static void Wayland_SetSystemCursorShape(SDL_WaylandSeat *seat, SDL_SystemCursor
 static void Wayland_SeatSetCursor(SDL_WaylandSeat *seat, SDL_Cursor *cursor)
 {
     if (seat->pointer.wl_pointer) {
-        struct wl_buffer *buffer = NULL;
+        SDL_CursorData *cursor_data = cursor ? cursor->internal : NULL;
         int scale = 1;
         int dst_width = 0;
         int dst_height = 0;
         int hot_x;
         int hot_y;
-        SDL_CursorData *cursor_data = cursor ? cursor->internal : NULL;
 
         // Stop the frame callback for old animated cursors.
         if (seat->pointer.cursor_state.frame_callback && cursor_data != seat->pointer.current_cursor) {
@@ -746,34 +816,33 @@ static void Wayland_SeatSetCursor(SDL_WaylandSeat *seat, SDL_Cursor *cursor)
                     return;
                 }
 
-                if (!Wayland_GetSystemCursor(seat->display, cursor_data, &scale, &dst_width, &hot_x, &hot_y)) {
+                if (!Wayland_GetSystemCursor(cursor_data, seat, &scale, &dst_width, &hot_x, &hot_y)) {
                     return;
                 }
 
                 dst_height = dst_width;
-
-                if (!seat->pointer.cursor_state.surface) {
-                    seat->pointer.cursor_state.surface = wl_compositor_create_surface(seat->display->compositor);
-                }
-                wl_surface_attach(seat->pointer.cursor_state.surface, cursor_data->cursor_data.system.frames[0].wl_buffer, 0, 0);
-
-                // If more than one frame is available, create a frame callback to run the animation.
-                if (cursor_data->cursor_data.system.num_frames > 1) {
-                    seat->pointer.cursor_state.last_frame_callback_time_ns = SDL_GetTicks();
-                    seat->pointer.cursor_state.current_frame_time_ns = 0;
-                    seat->pointer.cursor_state.current_frame = 0;
-                    seat->pointer.cursor_state.frame_callback = wl_surface_frame(seat->pointer.cursor_state.surface);
-                    wl_callback_add_listener(seat->pointer.cursor_state.frame_callback, &cursor_frame_listener, seat);
-                }
             } else {
-                if (!Wayland_GetCustomCursor(cursor, &buffer, &scale, &dst_width, &dst_height, &hot_x, &hot_y)) {
+                if (!Wayland_GetCustomCursor(cursor_data, seat, &scale, &dst_width, &dst_height, &hot_x, &hot_y)) {
                     return;
                 }
+            }
 
-                if (!seat->pointer.cursor_state.surface) {
-                    seat->pointer.cursor_state.surface = wl_compositor_create_surface(seat->display->compositor);
-                }
-                wl_surface_attach(seat->pointer.cursor_state.surface, buffer, 0, 0);
+            seat->pointer.current_cursor = cursor_data;
+
+            if (!seat->pointer.cursor_state.surface) {
+                seat->pointer.cursor_state.surface = wl_compositor_create_surface(seat->display->compositor);
+            }
+
+            struct wl_buffer *buffer = Wayland_SeatGetCursorFrame(seat, 0);
+            wl_surface_attach(seat->pointer.cursor_state.surface, buffer, 0, 0);
+
+            // If more than one frame is available, create a frame callback to run the animation.
+            if (cursor_data->num_frames > 1) {
+                seat->pointer.cursor_state.last_frame_callback_time_ms = SDL_GetTicks();
+                seat->pointer.cursor_state.current_frame_time_ms = 0;
+                seat->pointer.cursor_state.current_frame = 0;
+                seat->pointer.cursor_state.frame_callback = wl_surface_frame(seat->pointer.cursor_state.surface);
+                wl_callback_add_listener(seat->pointer.cursor_state.frame_callback, &cursor_frame_listener, seat);
             }
 
             // A scale value of 0 indicates that a viewport with the returned destination size should be used.
@@ -800,7 +869,6 @@ static void Wayland_SeatSetCursor(SDL_WaylandSeat *seat, SDL_Cursor *cursor)
                 wl_surface_damage(seat->pointer.cursor_state.surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32);
             }
 
-            seat->pointer.current_cursor = cursor_data;
             wl_surface_commit(seat->pointer.cursor_state.surface);
         } else {
             seat->pointer.current_cursor = NULL;
diff --git a/src/video/wayland/SDL_waylandshmbuffer.c b/src/video/wayland/SDL_waylandshmbuffer.c
index 8fc6d9e5b5f41..77b3c8429147f 100644
--- a/src/video/wayland/SDL_waylandshmbuffer.c
+++ b/src/video/wayland/SDL_waylandshmbuffer.c
@@ -114,11 +114,10 @@ static struct wl_buffer_listener buffer_listener = {
     buffer_handle_release
 };
 
-bool Wayland_AllocSHMBuffer(int width, int height, struct Wayland_SHMBuffer *shmBuffer)
+bool Wayland_AllocSHMBuffer(int width, int height, Wayland_SHMBuffer *shmBuffer)
 {
     SDL_VideoDevice *vd = SDL_GetVideoDevice();
     SDL_VideoData *data = vd->internal;
-    struct wl_shm_pool *shm_pool;
     const Uint32 SHM_FMT = WL_SHM_FORMAT_ARGB8888;
 
     if (!shmBuffer) {
@@ -142,7 +141,7 @@ bool Wayland_AllocSHMBuffer(int width, int height, struct Wayland_SHMBuffer *shm
 
     SDL_assert(shmBuffer->shm_data != NULL);
 
-    shm_pool = wl_shm_create_pool(data->shm, shm_fd, shmBuffer->shm_data_size);
+    struct wl_shm_pool *shm_pool = wl_shm_create_pool(data->shm, shm_fd, shmBuffer->shm_data_size);
     shmBuffer->wl_buffer = wl_shm_pool_create_buffer(shm_pool, 0, width, height, stride, SHM_FMT);
     wl_buffer_add_listener(shmBuffer->wl_buffer, &buffer_listener, shmBuffer);
 
@@ -152,7 +151,7 @@ bool Wayland_AllocSHMBuffer(int width, int height, struct Wayland_SHMBuffer *shm
     return true;
 }
 
-void Wayland_ReleaseSHMBuffer(struct Wayland_SHMBuffer *shmBuffer)
+void Wayland_ReleaseSHMBuffer(Wayland_SHMBuffer *shmBuffer)
 {
     if (shmBuffer) {
         if (shmBuffer->wl_buffer) {
@@ -167,4 +166,75 @@ void Wayland_ReleaseSHMBuffer(struct Wayland_SHMBuffer *shmBuffer)
     }
 }
 
+Wayland_SHMPool *Wayland_AllocSHMPool(int width, int height, int buffer_count)
+{
+    SDL_VideoDevice *vd = SDL_GetVideoDevice();
+    SDL_VideoData *data = vd->internal;
+    const Uint32 SHM_FMT = WL_SHM_FORMAT_ARGB8888;
+
+    if (buffer_count <= 0) {
+        SDL_InvalidParamError("count");
+        return NULL;
+    }
+
+    Wayland_SHMPool *shmPool = SDL_calloc(buffer_count, sizeof(Wayland_SHMPool) + (sizeof(Wayland_SHMBuffer) * buffer_count));
+    if (!shmPool) {
+        return NULL;
+    }
+
+    const int stride = width * 4;
+    const int element_size = stride * height;
+    const int element_offset = (element_size + 15) & (~15);
+    shmPool->internal.shm_pool_size = element_offset * buffer_count;
+    shmPool->buffer_count = buffer_count;
+
+    const int shm_fd = CreateTempFD(shmPool->internal.shm_pool_size);
+    if (shm_fd < 0) {
+        SDL_free(shmPool);
+        SDL_SetError("Creating SHM buffer failed.");
+        return NULL;
+    }
+
+    shmPool->internal.shm_pool_handle = mmap(NULL, shmPool->internal.shm_pool_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
+    if (shmPool->internal.shm_pool_handle == MAP_FAILED) {
+        shmPool->internal.shm_pool_handle = NULL;
+        close(shm_fd);
+        SDL_free(shmPool);
+        SDL_SetError("mmap() failed.");
+        return NULL;
+    }
+
+    SDL_assert(shmPool->internal.shm_pool_handle != NULL);
+
+    struct wl_shm_pool *shm_pool = wl_shm_create_pool(data->shm, shm_fd, shmPool->internal.shm_pool_size);
+
+    for (size_t i = 0; i < buffer_count; i++) {
+        shmPool->buffers[i].shm_data = (Uint8 *)shmPool->internal.shm_pool_handle + (element_offset * i);
+        shmPool->buffers[i].wl_buffer = wl_shm_pool_create_buffer(shm_pool, element_offset * i, width, height, stride, SHM_FMT);
+        wl_buffer_add_listener(shmPool->buffers[i].wl_buffer, &buffer_listener, shmPool);
+    }
+
+    wl_shm_pool_destroy(shm_pool);
+    close(shm_fd);
+
+    return shmPool;
+}
+
+void Wayland_ReleaseSHMPool(Wayland_SHMPool *shmPool)
+{
+    if (shmPool) {
+        for (int i = 0; i < shmPool->buffer_count; ++i) {
+            if (shmPool->buffers[i].wl_buffer) {
+                wl_buffer_destroy(shmPool->buffers[i].wl_buffer);
+            }
+        }
+
+        if (shmPool->internal.shm_pool_handle) {
+            munmap(shmPool->internal.shm_pool_handle, shmPool->internal.shm_pool_size);
+        }
+
+        SDL_free(shmPool);
+    }
+}
+
 #endif
diff --git a/src/video/wayland/SDL_waylandshmbuffer.h b/src/video/wayland/SDL_waylandshmbuffer.h
index f6f49a4e3b90b..4410fd6e56975 100644
--- a/src/video/wayland/SDL_waylandshmbuffer.h
+++ b/src/video/wayland/SDL_waylandshmbuffer.h
@@ -24,15 +24,30 @@
 #ifndef SDL_waylandshmbuffer_h_
 #define SDL_waylandshmbuffer_h_
 
-struct Wayland_SHMBuffer
+typedef struct
 {
     struct wl_buffer *wl_buffer;
     void *shm_data;
     int shm_data_size;
-};
+} Wayland_SHMBuffer;
+
+typedef struct
+{
+    struct
+    {
+        void *shm_pool_handle;
+        int shm_pool_size;
+    } internal;
+
+    int buffer_count;
+    Wayland_SHMBuffer buffers[];
+} Wayland_SHMPool;
 
 // Allocates an SHM buffer with the format WL_SHM_FORMAT_ARGB8888
-extern bool Wayland_AllocSHMBuffer(int width, int height, struct Wayland_SHMBuffer *shmBuffer);
-extern void Wayland_ReleaseSHMBuffer(struct Wayland_SHMBuffer *shmBuffer);
+extern bool Wayland_AllocSHMBuffer(int width, int height, Wayland_SHMBuffer *shmBuffer);
+extern void Wayland_ReleaseSHMBuffer(Wayland_SHMBuffer *shmBuffer);
+
+extern Wayland_SHMPool *Wayland_AllocSHMPool(int width, int height, int buffer_count);
+extern void Wayland_ReleaseSHMPool(Wayland_SHMPool *shmPool);
 
 #endif
diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c
index 5917f2552ca06..46337c89e12c5 100644
--- a/src/video/wayland/SDL_waylandwindow.c
+++ b/src/video/wayland/SDL_waylandwindow.c
@@ -2988,14 +2988,14 @@ bool Wayland_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surfa
     wind->icon_buffer_count = 0;
 
     wind->xdg_toplevel_icon_v1 = xdg_toplevel_icon_manager_v1_create_icon(_this->internal->xdg_toplevel_icon_manager_v1);
-    wind->icon_buffers = SDL_calloc(image_count, sizeof(struct Wayland_SHMBuffer));
+    wind->icon_buffers = SDL_calloc(image_count, sizeof(Wayland_SHMBuffer));
     if (!wind->icon_buffers) {
         goto failure_cleanup;
     }
 
     for (int i = 0; i < image_count; ++i) {
         if (images[i]->w == images[i]->h) {
-            struct Wayland_SHMBuffer *buffer = &wind->icon_buffers[wind->icon_buffer_count];
+            Wayland_SHMBuffer *buffer = &wind->icon_buffers[wind->icon_buffer_count];
 
             if (!Wayland_AllocSHMBuffer(images[i]->w, images[i]->h, buffer)) {
                 SDL_SetError("wayland: failed to allocate SHM buffer for the icon");
diff --git a/src/video/wayland/SDL_waylandwindow.h b/src/video/wayland/SDL_waylandwindow.h
index e16f2d9d1a8ae..50f8c528d0d6b 100644
--- a/src/video/wayland/SDL_waylandwindow.h
+++ b/src/video/wayland/SDL_waylandwindow.h
@@ -127,7 +127,7 @@ struct SDL_WindowData
     char *app_id;
     double scale_factor;
 
-    struct Wayland_SHMBuffer *icon_buffers;
+    Wayland_SHMBuffer *icon_buffers;
     int icon_buffer_count;
 
     // Keyboard, pointer, and touch focus refcount.