mirror of
https://github.com/libsdl-org/SDL.git
synced 2025-09-05 19:08:12 +00:00
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.
This commit is contained in:
@@ -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;
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user