From 8e54698aa6e3c0cf02d0b628403f047f28ea7fd0 Mon Sep 17 00:00:00 2001 From: Ethan Lee Date: Wed, 22 Sep 2021 13:26:44 -0400 Subject: [PATCH] wayland: Add support for high-DPI cursors --- src/video/wayland/SDL_waylandmouse.c | 230 ++++++++++++++++----------- src/video/wayland/SDL_waylandmouse.h | 2 +- src/video/wayland/SDL_waylandvideo.c | 6 +- src/video/wayland/SDL_waylandvideo.h | 8 +- 4 files changed, 147 insertions(+), 99 deletions(-) diff --git a/src/video/wayland/SDL_waylandmouse.c b/src/video/wayland/SDL_waylandmouse.c index a0df26bdf..23663ebe1 100644 --- a/src/video/wayland/SDL_waylandmouse.c +++ b/src/video/wayland/SDL_waylandmouse.c @@ -47,11 +47,110 @@ typedef struct { int hot_x, hot_y; int w, h; - /* Either a preloaded cursor, or one we created ourselves */ - struct wl_cursor *cursor; + /* shm_data is non-NULL for custom cursors. + * When shm_data is NULL, system_cursor must be valid + */ + SDL_SystemCursor system_cursor; void *shm_data; } Wayland_CursorData; +static SDL_bool +wayland_get_system_cursor(SDL_VideoData *vdata, Wayland_CursorData *cdata, float *scale) +{ + struct wl_cursor_theme *theme = NULL; + struct wl_cursor *cursor; + + SDL_Window *focus; + SDL_WindowData *focusdata; + int i; + + /* FIXME: We need to be able to query the cursor size from the desktop at + * some point! For a while this was 32, but when testing on real desktops it + * seems like most of them default to 24. We'll need a protocol to get this + * for real, but for now this is a pretty safe bet. + * -flibit + */ + int size = 24; + + /* First, find the appropriate theme based on the current scale... */ + focus = SDL_GetMouse()->focus; + if (focus == NULL) { + /* Nothing to see here, bail. */ + return SDL_FALSE; + } + focusdata = focus->driverdata; + *scale = focusdata->scale_factor; + size *= focusdata->scale_factor; + for (i = 0; i < vdata->num_cursor_themes; i += 1) { + if (vdata->cursor_themes[i].size == size) { + theme = vdata->cursor_themes[i].theme; + break; + } + } + if (theme == NULL) { + vdata->cursor_themes = SDL_realloc(vdata->cursor_themes, + sizeof(SDL_WaylandCursorTheme) * (vdata->num_cursor_themes + 1)); + if (vdata->cursor_themes == NULL) { + SDL_OutOfMemory(); + return SDL_FALSE; + } + theme = WAYLAND_wl_cursor_theme_load(NULL, size, vdata->shm); + vdata->cursor_themes[vdata->num_cursor_themes++].theme = theme; + } + + /* Next, find the cursor from the theme... */ + switch(cdata->system_cursor) + { + case SDL_SYSTEM_CURSOR_ARROW: + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "left_ptr"); + break; + case SDL_SYSTEM_CURSOR_IBEAM: + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "xterm"); + break; + case SDL_SYSTEM_CURSOR_WAIT: + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "watch"); + break; + case SDL_SYSTEM_CURSOR_CROSSHAIR: + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "hand1"); + break; + case SDL_SYSTEM_CURSOR_WAITARROW: + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "watch"); + break; + case SDL_SYSTEM_CURSOR_SIZENWSE: + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "hand1"); + break; + case SDL_SYSTEM_CURSOR_SIZENESW: + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "hand1"); + break; + case SDL_SYSTEM_CURSOR_SIZEWE: + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "hand1"); + break; + case SDL_SYSTEM_CURSOR_SIZENS: + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "hand1"); + break; + case SDL_SYSTEM_CURSOR_SIZEALL: + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "hand1"); + break; + case SDL_SYSTEM_CURSOR_NO: + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "xterm"); + break; + case SDL_SYSTEM_CURSOR_HAND: + cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "hand1"); + break; + default: + SDL_assert(0); + return SDL_FALSE; + } + + /* ... Set the cursor data, finally. */ + cdata->buffer = WAYLAND_wl_cursor_image_get_buffer(cursor->images[0]); + cdata->hot_x = cursor->images[0]->hotspot_x; + cdata->hot_y = cursor->images[0]->hotspot_y; + cdata->w = cursor->images[0]->width; + cdata->h = cursor->images[0]->height; + return SDL_TRUE; +} + static int wayland_create_tmp_file(off_t size) { @@ -192,30 +291,30 @@ Wayland_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y) } static SDL_Cursor * -CreateCursorFromWlCursor(SDL_VideoData *d, struct wl_cursor *wlcursor) +Wayland_CreateSystemCursor(SDL_SystemCursor id) { + SDL_VideoData *data = SDL_GetVideoDevice()->driverdata; SDL_Cursor *cursor; cursor = SDL_calloc(1, sizeof (*cursor)); if (cursor) { - Wayland_CursorData *data = SDL_calloc (1, sizeof (Wayland_CursorData)); - if (!data) { + Wayland_CursorData *cdata = SDL_calloc (1, sizeof (Wayland_CursorData)); + if (!cdata) { SDL_OutOfMemory(); SDL_free(cursor); return NULL; } - cursor->driverdata = (void *) data; + cursor->driverdata = (void *) cdata; - data->buffer = WAYLAND_wl_cursor_image_get_buffer(wlcursor->images[0]); - data->surface = wl_compositor_create_surface(d->compositor); - wl_surface_set_user_data(data->surface, NULL); - data->hot_x = wlcursor->images[0]->hotspot_x; - data->hot_y = wlcursor->images[0]->hotspot_y; - data->w = wlcursor->images[0]->width; - data->h = wlcursor->images[0]->height; - data->cursor= wlcursor; + cdata->surface = wl_compositor_create_surface(data->compositor); + wl_surface_set_user_data(cdata->surface, NULL); + + /* Note that we can't actually set any other cursor properties, as this + * is output-specific. See wayland_get_system_cursor for the rest! + */ + cdata->system_cursor = id; } else { - SDL_OutOfMemory (); + SDL_OutOfMemory(); } return cursor; @@ -224,66 +323,7 @@ CreateCursorFromWlCursor(SDL_VideoData *d, struct wl_cursor *wlcursor) static SDL_Cursor * Wayland_CreateDefaultCursor() { - SDL_VideoDevice *device = SDL_GetVideoDevice(); - SDL_VideoData *data = device->driverdata; - - return CreateCursorFromWlCursor (data, - WAYLAND_wl_cursor_theme_get_cursor(data->cursor_theme, - "left_ptr")); -} - -static SDL_Cursor * -Wayland_CreateSystemCursor(SDL_SystemCursor id) -{ - SDL_VideoDevice *vd = SDL_GetVideoDevice(); - SDL_VideoData *d = vd->driverdata; - - struct wl_cursor *cursor = NULL; - - switch(id) - { - default: - SDL_assert(0); - return NULL; - case SDL_SYSTEM_CURSOR_ARROW: - cursor = WAYLAND_wl_cursor_theme_get_cursor(d->cursor_theme, "left_ptr"); - break; - case SDL_SYSTEM_CURSOR_IBEAM: - cursor = WAYLAND_wl_cursor_theme_get_cursor(d->cursor_theme, "xterm"); - break; - case SDL_SYSTEM_CURSOR_WAIT: - cursor = WAYLAND_wl_cursor_theme_get_cursor(d->cursor_theme, "watch"); - break; - case SDL_SYSTEM_CURSOR_CROSSHAIR: - cursor = WAYLAND_wl_cursor_theme_get_cursor(d->cursor_theme, "hand1"); - break; - case SDL_SYSTEM_CURSOR_WAITARROW: - cursor = WAYLAND_wl_cursor_theme_get_cursor(d->cursor_theme, "watch"); - break; - case SDL_SYSTEM_CURSOR_SIZENWSE: - cursor = WAYLAND_wl_cursor_theme_get_cursor(d->cursor_theme, "hand1"); - break; - case SDL_SYSTEM_CURSOR_SIZENESW: - cursor = WAYLAND_wl_cursor_theme_get_cursor(d->cursor_theme, "hand1"); - break; - case SDL_SYSTEM_CURSOR_SIZEWE: - cursor = WAYLAND_wl_cursor_theme_get_cursor(d->cursor_theme, "hand1"); - break; - case SDL_SYSTEM_CURSOR_SIZENS: - cursor = WAYLAND_wl_cursor_theme_get_cursor(d->cursor_theme, "hand1"); - break; - case SDL_SYSTEM_CURSOR_SIZEALL: - cursor = WAYLAND_wl_cursor_theme_get_cursor(d->cursor_theme, "hand1"); - break; - case SDL_SYSTEM_CURSOR_NO: - cursor = WAYLAND_wl_cursor_theme_get_cursor(d->cursor_theme, "xterm"); - break; - case SDL_SYSTEM_CURSOR_HAND: - cursor = WAYLAND_wl_cursor_theme_get_cursor(d->cursor_theme, "hand1"); - break; - } - - return CreateCursorFromWlCursor(d, cursor); + return Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); } static void @@ -300,14 +340,14 @@ Wayland_FreeCursor(SDL_Cursor *cursor) if (!d) return; - if (d->buffer && !d->cursor) + if (d->buffer && d->shm_data) wl_buffer_destroy(d->buffer); if (d->surface) wl_surface_destroy(d->surface); /* Not sure what's meant to happen to shm_data */ - SDL_free (cursor->driverdata); + SDL_free(cursor->driverdata); SDL_free(cursor); } @@ -317,8 +357,8 @@ Wayland_ShowCursor(SDL_Cursor *cursor) SDL_VideoDevice *vd = SDL_GetVideoDevice(); SDL_VideoData *d = vd->driverdata; struct SDL_WaylandInput *input = d->input; - struct wl_pointer *pointer = d->pointer; + float scale = 1.0f; if (!pointer) return -1; @@ -327,22 +367,26 @@ Wayland_ShowCursor(SDL_Cursor *cursor) { Wayland_CursorData *data = cursor->driverdata; - wl_pointer_set_cursor (pointer, - input->pointer_enter_serial, - data->surface, - data->hot_x, - data->hot_y); + /* TODO: High-DPI custom cursors? -flibit */ + if (data->shm_data == NULL) { + if (!wayland_get_system_cursor(d, data, &scale)) { + return -1; + } + } + + wl_surface_set_buffer_scale(data->surface, scale); + wl_pointer_set_cursor(pointer, + input->pointer_enter_serial, + data->surface, + data->hot_x / scale, + data->hot_y / scale); wl_surface_attach(data->surface, data->buffer, 0, 0); wl_surface_damage(data->surface, 0, 0, data->w, data->h); wl_surface_commit(data->surface); } else { - wl_pointer_set_cursor (pointer, - input->pointer_enter_serial, - NULL, - 0, - 0); + wl_pointer_set_cursor(pointer, input->pointer_enter_serial, NULL, 0, 0); } return 0; @@ -389,10 +433,12 @@ Wayland_InitMouse(void) } void -Wayland_FiniMouse(void) +Wayland_FiniMouse(SDL_VideoData *data) { - /* This effectively assumes that nobody else - * touches SDL_Mouse which is effectively - * a singleton */ + int i; + for (i = 0; i < data->num_cursor_themes; i += 1) { + WAYLAND_wl_cursor_theme_destroy(data->cursor_themes[i].theme); + } + SDL_free(data->cursor_themes); } #endif /* SDL_VIDEO_DRIVER_WAYLAND */ diff --git a/src/video/wayland/SDL_waylandmouse.h b/src/video/wayland/SDL_waylandmouse.h index 51bfaf5d0..79efaf7e8 100644 --- a/src/video/wayland/SDL_waylandmouse.h +++ b/src/video/wayland/SDL_waylandmouse.h @@ -26,6 +26,6 @@ #if SDL_VIDEO_DRIVER_WAYLAND extern void Wayland_InitMouse(void); -extern void Wayland_FiniMouse(void); +extern void Wayland_FiniMouse(SDL_VideoData *data); #endif diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c index 1cd67aaf4..03464d193 100644 --- a/src/video/wayland/SDL_waylandvideo.c +++ b/src/video/wayland/SDL_waylandvideo.c @@ -495,7 +495,6 @@ display_handle_global(void *data, struct wl_registry *registry, uint32_t id, xdg_wm_base_add_listener(d->shell.xdg, &shell_listener_xdg, NULL); } else if (SDL_strcmp(interface, "wl_shm") == 0) { d->shm = wl_registry_bind(registry, id, &wl_shm_interface, 1); - d->cursor_theme = WAYLAND_wl_cursor_theme_load(NULL, 32, d->shm); } else if (SDL_strcmp(interface, "zwp_relative_pointer_manager_v1") == 0) { Wayland_display_add_relative_pointer_manager(d, id); } else if (SDL_strcmp(interface, "zwp_pointer_constraints_v1") == 0) { @@ -618,7 +617,7 @@ Wayland_VideoQuit(_THIS) SDL_VideoData *data = _this->driverdata; int i, j; - Wayland_FiniMouse (); + Wayland_FiniMouse(data); for (i = 0; i < _this->num_displays; ++i) { SDL_VideoDisplay *display = &_this->displays[i]; @@ -671,9 +670,6 @@ Wayland_VideoQuit(_THIS) if (data->shm) wl_shm_destroy(data->shm); - if (data->cursor_theme) - WAYLAND_wl_cursor_theme_destroy(data->cursor_theme); - if (data->shell.xdg) xdg_wm_base_destroy(data->shell.xdg); diff --git a/src/video/wayland/SDL_waylandvideo.h b/src/video/wayland/SDL_waylandvideo.h index 60336b6cc..2d8b6323a 100644 --- a/src/video/wayland/SDL_waylandvideo.h +++ b/src/video/wayland/SDL_waylandvideo.h @@ -41,13 +41,19 @@ struct qt_surface_extension; struct qt_windowmanager; #endif /* SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */ +typedef struct { + struct wl_cursor_theme *theme; + int size; +} SDL_WaylandCursorTheme; + typedef struct { struct wl_display *display; int display_disconnected; struct wl_registry *registry; struct wl_compositor *compositor; struct wl_shm *shm; - struct wl_cursor_theme *cursor_theme; + SDL_WaylandCursorTheme *cursor_themes; + int num_cursor_themes; struct wl_pointer *pointer; struct { struct xdg_wm_base *xdg;