From ef5d56de5137c3231d3a84d1ae50001177726436 Mon Sep 17 00:00:00 2001 From: Frank Praznik Date: Mon, 27 May 2024 13:12:43 -0400 Subject: [PATCH] wayland: Implement animated system cursors when not using the cursor shape protocol If a system cursor has more than one frame, create a frame callback to run the animation and attach new buffers as necessary to animate the cursor. --- src/video/wayland/SDL_waylandevents_c.h | 1 + src/video/wayland/SDL_waylandmouse.c | 154 ++++++++++++++++++++---- 2 files changed, 130 insertions(+), 25 deletions(-) diff --git a/src/video/wayland/SDL_waylandevents_c.h b/src/video/wayland/SDL_waylandevents_c.h index 5559901c4e..f1eff1fd82 100644 --- a/src/video/wayland/SDL_waylandevents_c.h +++ b/src/video/wayland/SDL_waylandevents_c.h @@ -110,6 +110,7 @@ struct SDL_WaylandInput struct zwp_input_timestamps_v1 *touch_timestamps; SDL_WindowData *pointer_focus; SDL_WindowData *keyboard_focus; + struct Wayland_CursorData *current_cursor; Uint32 keyboard_id; Uint32 pointer_id; uint32_t pointer_enter_serial; diff --git a/src/video/wayland/SDL_waylandmouse.c b/src/video/wayland/SDL_waylandmouse.c index 2108ea8ce9..5f8379409d 100644 --- a/src/video/wayland/SDL_waylandmouse.c +++ b/src/video/wayland/SDL_waylandmouse.c @@ -47,16 +47,39 @@ static int Wayland_SetRelativeMouseMode(SDL_bool enabled); typedef struct { struct Wayland_SHMBuffer shmBuffer; +} Wayland_CustomCursor; + +typedef struct +{ + struct wl_buffer *wl_buffer; + Uint32 duration; +} Wayland_SystemCursorFrame; + +typedef struct +{ + Wayland_SystemCursorFrame *frames; + struct wl_callback *frame_callback; + Uint64 last_frame_time_ms; + Uint32 total_duration; + int num_frames; + int current_frame; + SDL_SystemCursor id; +} Wayland_SystemCursor; + +struct Wayland_CursorData +{ + union + { + struct Wayland_SHMBuffer custom; + Wayland_SystemCursor system; + } cursor_data; struct wl_surface *surface; int hot_x, hot_y; int w, h; - /* shmBuffer.shm_data is non-NULL for custom cursors. - * When shm_data is NULL, system_cursor must be valid - */ - SDL_SystemCursor system_cursor; -} Wayland_CursorData; + SDL_bool is_system_cursor; +}; static int dbus_cursor_size; static char *dbus_cursor_theme; @@ -257,7 +280,45 @@ static void Wayland_DBusFinishCursorProperties() #endif -static SDL_bool wayland_get_system_cursor(SDL_VideoData *vdata, Wayland_CursorData *cdata, float *scale) +static void cursor_frame_done(void *data, struct wl_callback *cb, uint32_t time); +struct wl_callback_listener cursor_frame_listener = { + cursor_frame_done +}; + +static void cursor_frame_done(void *data, struct wl_callback *cb, uint32_t time) +{ + struct Wayland_CursorData *c = (struct Wayland_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; + 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); + + /* 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) { + next = (next + 1) % c->cursor_data.system.num_frames; + + /* 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) { + break; + } + } + + c->cursor_data.system.last_frame_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) { + wl_surface_damage_buffer(c->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32); + } else { + wl_surface_damage(c->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32); + } + wl_surface_commit(c->surface); +} + +static SDL_bool wayland_get_system_cursor(SDL_VideoData *vdata, struct Wayland_CursorData *cdata, float *scale) { struct wl_cursor_theme *theme = NULL; struct wl_cursor *cursor; @@ -268,7 +329,6 @@ static SDL_bool wayland_get_system_cursor(SDL_VideoData *vdata, Wayland_CursorDa SDL_Window *focus; SDL_WindowData *focusdata; - int i; /* Fallback envvar if the DBus properties don't exist */ if (size <= 0) { @@ -291,7 +351,7 @@ static SDL_bool wayland_get_system_cursor(SDL_VideoData *vdata, Wayland_CursorDa /* Cursors use integer scaling. */ *scale = SDL_ceilf(focusdata->windowed_scale_factor); size *= *scale; - for (i = 0; i < vdata->num_cursor_themes; i += 1) { + for (int i = 0; i < vdata->num_cursor_themes; i += 1) { if (vdata->cursor_themes[i].size == size) { theme = vdata->cursor_themes[i].theme; break; @@ -317,7 +377,7 @@ static SDL_bool wayland_get_system_cursor(SDL_VideoData *vdata, Wayland_CursorDa vdata->cursor_themes[vdata->num_cursor_themes++].theme = theme; } - css_name = SDL_GetCSSCursorName(cdata->system_cursor, &fallback_name); + css_name = SDL_GetCSSCursorName(cdata->cursor_data.system.id, &fallback_name); cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, css_name); if (!cursor && fallback_name) { cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, fallback_name); @@ -335,8 +395,22 @@ static SDL_bool wayland_get_system_cursor(SDL_VideoData *vdata, Wayland_CursorDa return SDL_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 SDL_FALSE; + } + } + /* ... Set the cursor data, finally. */ - cdata->shmBuffer.wl_buffer = WAYLAND_wl_cursor_image_get_buffer(cursor->images[0]); + cdata->cursor_data.system.num_frames = cursor->image_count; + cdata->cursor_data.system.total_duration = 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 = cursor->images[i]->delay; + cdata->cursor_data.system.total_duration += cursor->images[i]->delay; + } cdata->hot_x = cursor->images[0]->hotspot_x; cdata->hot_y = cursor->images[0]->hotspot_y; cdata->w = cursor->images[0]->width; @@ -350,7 +424,7 @@ static SDL_Cursor *Wayland_CreateCursor(SDL_Surface *surface, int hot_x, int hot if (cursor) { SDL_VideoDevice *vd = SDL_GetVideoDevice(); SDL_VideoData *wd = vd->driverdata; - Wayland_CursorData *data = SDL_calloc(1, sizeof(Wayland_CursorData)); + struct Wayland_CursorData *data = SDL_calloc(1, sizeof(struct Wayland_CursorData)); if (!data) { SDL_free(cursor); return NULL; @@ -358,7 +432,7 @@ static SDL_Cursor *Wayland_CreateCursor(SDL_Surface *surface, int hot_x, int hot cursor->driverdata = (void *)data; /* Allocate shared memory buffer for this cursor */ - if (Wayland_AllocSHMBuffer(surface->w, surface->h, &data->shmBuffer) != 0) { + if (Wayland_AllocSHMBuffer(surface->w, surface->h, &data->cursor_data.custom) != 0) { SDL_free(cursor->driverdata); SDL_free(cursor); return NULL; @@ -367,7 +441,7 @@ static SDL_Cursor *Wayland_CreateCursor(SDL_Surface *surface, int hot_x, int hot /* Wayland requires premultiplied alpha for its surfaces. */ SDL_PremultiplyAlpha(surface->w, surface->h, surface->format->format, surface->pixels, surface->pitch, - SDL_PIXELFORMAT_ARGB8888, data->shmBuffer.shm_data, surface->w * 4); + SDL_PIXELFORMAT_ARGB8888, data->cursor_data.custom.shm_data, surface->w * 4); data->surface = wl_compositor_create_surface(wd->compositor); wl_surface_set_user_data(data->surface, NULL); @@ -386,7 +460,7 @@ static SDL_Cursor *Wayland_CreateSystemCursor(SDL_SystemCursor id) SDL_VideoData *data = SDL_GetVideoDevice()->driverdata; SDL_Cursor *cursor = SDL_calloc(1, sizeof(*cursor)); if (cursor) { - Wayland_CursorData *cdata = SDL_calloc(1, sizeof(Wayland_CursorData)); + struct Wayland_CursorData *cdata = SDL_calloc(1, sizeof(struct Wayland_CursorData)); if (!cdata) { SDL_free(cursor); return NULL; @@ -403,7 +477,8 @@ static SDL_Cursor *Wayland_CreateSystemCursor(SDL_SystemCursor id) wl_surface_set_user_data(cdata->surface, NULL); } - cdata->system_cursor = id; + cdata->cursor_data.system.id = id; + cdata->is_system_cursor = SDL_TRUE; } return cursor; @@ -414,13 +489,16 @@ static SDL_Cursor *Wayland_CreateDefaultCursor(void) return Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); } -static void Wayland_FreeCursorData(Wayland_CursorData *d) +static void Wayland_FreeCursorData(struct Wayland_CursorData *d) { /* Buffers for system cursors must not be destroyed. */ - if (d->shmBuffer.shm_data) { - Wayland_ReleaseSHMBuffer(&d->shmBuffer); + if (d->is_system_cursor) { + if (d->cursor_data.system.frame_callback) { + wl_callback_destroy(d->cursor_data.system.frame_callback); + } + SDL_free(d->cursor_data.system.frames); } else { - d->shmBuffer.wl_buffer = NULL; + Wayland_ReleaseSHMBuffer(&d->cursor_data.custom); } if (d->surface) { @@ -440,7 +518,7 @@ static void Wayland_FreeCursor(SDL_Cursor *cursor) return; } - Wayland_FreeCursorData((Wayland_CursorData *)cursor->driverdata); + Wayland_FreeCursorData((struct Wayland_CursorData *)cursor->driverdata); SDL_free(cursor->driverdata); SDL_free(cursor); @@ -531,15 +609,23 @@ static int Wayland_ShowCursor(SDL_Cursor *cursor) return -1; } + /* Stop the frame callback for old animated cursors. */ + if (input->current_cursor && input->current_cursor->is_system_cursor && + input->current_cursor->cursor_data.system.frame_callback) { + wl_callback_destroy(input->current_cursor->cursor_data.system.frame_callback); + input->current_cursor->cursor_data.system.frame_callback = NULL; + } + if (cursor) { - Wayland_CursorData *data = cursor->driverdata; + struct Wayland_CursorData *data = cursor->driverdata; /* TODO: High-DPI custom cursors? -flibit */ - if (!data->shmBuffer.shm_data) { + if (data->is_system_cursor) { if (input->cursor_shape) { - Wayland_SetSystemCursorShape(input, data->system_cursor); + Wayland_SetSystemCursorShape(input, data->cursor_data.system.id); input->cursor_visible = SDL_TRUE; + input->current_cursor = data; if (input->relative_mode_override) { Wayland_input_disable_relative_pointer(input); @@ -558,11 +644,28 @@ static int Wayland_ShowCursor(SDL_Cursor *cursor) data->surface, data->hot_x / scale, data->hot_y / scale); - wl_surface_attach(data->surface, data->shmBuffer.wl_buffer, 0, 0); - wl_surface_damage(data->surface, 0, 0, data->w, data->h); + if (data->is_system_cursor) { + wl_surface_attach(data->surface, 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 (data->cursor_data.system.num_frames > 1) { + data->cursor_data.system.last_frame_time_ms = SDL_GetTicks(); + 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); + } + } else { + wl_surface_attach(data->surface, data->cursor_data.custom.wl_buffer, 0, 0); + } + if (wl_surface_get_version(data->surface) >= WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION) { + wl_surface_damage_buffer(data->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32); + } else { + wl_surface_damage(data->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32); + } wl_surface_commit(data->surface); input->cursor_visible = SDL_TRUE; + input->current_cursor = data; if (input->relative_mode_override) { Wayland_input_disable_relative_pointer(input); @@ -571,6 +674,7 @@ static int Wayland_ShowCursor(SDL_Cursor *cursor) } else { input->cursor_visible = SDL_FALSE; + input->current_cursor = NULL; wl_pointer_set_cursor(pointer, input->pointer_enter_serial, NULL, 0, 0); }