Initial implementation of proper HiDPI cursor support on Wayland.

This commit is contained in:
TheDaemoness 2020-05-26 20:51:34 -07:00 committed by Kovid Goyal
parent b0ad44bcf2
commit 7c3c87abf6
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
6 changed files with 283 additions and 48 deletions

View File

@ -78,6 +78,7 @@
"wl_init.c",
"wl_monitor.c",
"wl_window.c",
"wl_cursors.c",
"posix_thread.c",
"xkb_glfw.c",
"dbus_glfw.c",

176
glfw/wl_cursors.c vendored Normal file
View File

@ -0,0 +1,176 @@
// Future devs supporting whatever Wayland protocol stabilizes for cursor selection: see _themeAdd.
#include "wl_cursors.h"
#include "internal.h"
#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct {
struct wl_cursor_theme *theme;
int px;
int refcount;
} _themeData;
struct _wlCursorThemeManager {
size_t count;
/** Pointer to the head of an unsorted array of themes with no sentinel.
*
* The lack of sort (and thus forcing a linear search) is intentional;
* in most cases, users are likely to have 1-2 different cursor sizes loaded.
* For those cases, we get no benefit from sorting and added constant overheads.
*
* Don't change this to a flexible array member becuase that complicates growing/shrinking.
*/
_themeData *themes;
};
static void
_themeInit(_themeData *dest, const char *name, int px) {
dest->px = px;
dest->refcount = 1;
if(_glfw.wl.shm) {
dest->theme = wl_cursor_theme_load(name, px, _glfw.wl.shm);
if(!dest->theme) {
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Unable to load cursor theme");
}
} else {
dest->theme = NULL;
}
}
static struct wl_cursor_theme*
_themeAdd(int px, _wlCursorThemeManager *manager) {
++manager->count;
_themeData *temp = realloc(manager->themes, sizeof(_themeData)*manager->count);
if(!temp) {
_glfwInputError(GLFW_OUT_OF_MEMORY,
"OOM during cursor theme management.");
return NULL;
} else {
manager->themes = temp;
_themeInit(manager->themes + manager->count-1,
getenv("XCURSOR_THEME"),
px);
return manager->themes[manager->count-1].theme;
}
}
//WARNING: No input safety checks.
static inline void _themeInc(_themeData
*theme) {
++(theme->refcount);
}
//WARNING: No input safety checks.
// In particular, doesn't check if theme is actually managed by the manager.
static void
_themeDec(_themeData *theme, _wlCursorThemeManager *manager) {
if(--(theme->refcount) == 0) {
wl_cursor_theme_destroy(theme->theme);
if(--(manager->count) > 0) {
const _themeData *last_theme = (manager->themes)+(manager->count);
*theme = *last_theme;
_themeData *temp = realloc(manager->themes, (manager->count)*sizeof(_themeData));
//^ The chances of this failing are very slim, but one never knows.
if(temp) {
manager->themes = temp;
}
// We're shrinking here, so it's not catastrophic if realloc fails.
} else {
free(manager->themes);
manager->themes = NULL;
}
}
}
static _wlCursorThemeManager _default = {0, NULL};
_wlCursorThemeManager*
_wlCursorThemeManagerDefault() {
return &_default;
}
void
_wlCursorThemeManagerDestroy(_wlCursorThemeManager *manager) {
if(manager) {
for(size_t i = 0; i < manager->count; ++i) {
wl_cursor_theme_destroy(manager->themes[i].theme);
}
free(manager->themes);
}
}
static struct wl_cursor_theme*
_wlCursorThemeManagerGet(_wlCursorThemeManager *manager, int px) {
_themeData *themedata = NULL;
for(size_t i = 0; i < manager->count; ++i) {
_themeData
*temp = manager->themes+i;
if(temp->px == px) {
themedata = temp;
break;
}
}
if(themedata != NULL) {
_themeInc(themedata);
return themedata->theme;
} else {
return _themeAdd(px, manager);
}
}
struct wl_cursor_theme*
_wlCursorThemeManage(_wlCursorThemeManager *manager, struct wl_cursor_theme *theme, int px) {
//WARNING: Multiple returns.
if(manager == NULL) {
return NULL;
}
if(theme != NULL) {
// Search for the provided theme in the manager.
_themeData *themedata = NULL;
for(size_t i = 0; i < manager->count; ++i) {
_themeData *temp = manager->themes+i;
if(temp->theme == theme) {
themedata = temp;
break;
}
}
if(themedata != NULL) {
// Search succeeded. Check if we can avoid unnecessary operations.
if(themedata->px == px) {
return theme;
} else {
_themeDec(themedata, manager);
}
} else {
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland internal: managed theme isn't in the provided manager");
return theme;
//^ This is probably the sanest behavior for this situation: do nothing.
}
}
if(px > 0) {
return _wlCursorThemeManagerGet(manager, px);
} else {
return NULL;
}
}
int
_wlCursorPxFromScale(int scale) {
const char *envStr = getenv("XCURSOR_SIZE");
if(envStr != NULL) {
const int retval = atoi(envStr);
//^ atoi here is fine since 0 is an invalid value.
if(retval > 0 && retval <= INT_MAX/scale) {
return retval*scale;
}
}
return 32*scale;
}

31
glfw/wl_cursors.h vendored Normal file
View File

@ -0,0 +1,31 @@
// Declarations for a HiDPI-aware cursor theme manager.
#include <wayland-cursor.h>
typedef struct _wlCursorThemeManager _wlCursorThemeManager;
/** Returns a pointer to a wlCursorThemeManagerInstance.
* Repeatedly calling this function will return the same instance.
*
* The retrieved instance must be destroyed with _wlCursorThemeManagerDestroy.
*/
_wlCursorThemeManager* _wlCursorThemeManagerDefault(void);
/** Set a wl_cursor_theme pointer variable to a pointer to a managed cursor theme.
* Pass the desired px as the third argument.
* Returns a pointer to a managed theme, or NULL if the desired px is 0 or an error occurs.
*
* The passed theme pointer must either be NULL or a pointer to a theme managed by the passed manager.
* The provided pointer may be invalidated if it's non-NULL.
*/
struct wl_cursor_theme*
_wlCursorThemeManage(_wlCursorThemeManager*, struct wl_cursor_theme*, int);
/** Helper method to determine the appropriate size in pixels for a given scale.
*
* Reads XCURSOR_SIZE if it's set and is valid, else defaults to 32*scale.
*/
void _wlCursorThemeManagerDestroy(_wlCursorThemeManager*);
int _wlCursorPxFromScale(int);

46
glfw/wl_init.c vendored
View File

@ -140,14 +140,20 @@ static void pointerHandleLeave(void* data UNUSED,
_glfw.wl.cursorPreviousShape = GLFW_INVALID_CURSOR;
}
static void setCursor(GLFWCursorShape shape)
static void setCursor(GLFWCursorShape shape, _GLFWwindow* window)
{
struct wl_buffer* buffer;
struct wl_cursor* cursor;
struct wl_cursor_image* image;
struct wl_surface* surface = _glfw.wl.cursorSurface;
const int scale = window->wl.scale;
cursor = _glfwLoadCursor(shape);
window->wl.cursorTheme = _wlCursorThemeManage(
_glfw.wl.cursorThemeManager,
window->wl.cursorTheme,
_wlCursorPxFromScale(scale)
);
cursor = _glfwLoadCursor(shape, window->wl.cursorTheme);
if (!cursor) return;
// TODO: handle animated cursors too.
image = cursor->images[0];
@ -160,8 +166,9 @@ static void setCursor(GLFWCursorShape shape)
return;
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial,
surface,
image->hotspot_x,
image->hotspot_y);
image->hotspot_x / scale,
image->hotspot_y / scale);
wl_surface_set_buffer_scale(surface, scale);
wl_surface_attach(surface, buffer, 0, 0);
wl_surface_damage(surface, 0, 0,
image->width, image->height);
@ -225,7 +232,7 @@ static void pointerHandleMotion(void* data UNUSED,
assert(0);
}
if (_glfw.wl.cursorPreviousShape != cursorShape)
setCursor(cursorShape);
setCursor(cursorShape, window);
}
static void pointerHandleButton(void* data UNUSED,
@ -775,26 +782,14 @@ int _glfwPlatformInit(void)
if (_glfw.wl.shm)
{
const char *cursorTheme = getenv("XCURSOR_THEME"), *cursorSizeStr = getenv("XCURSOR_SIZE");
char *cursorSizeEnd;
int cursorSize = 32;
if (cursorSizeStr)
{
errno = 0;
long cursorSizeLong = strtol(cursorSizeStr, &cursorSizeEnd, 10);
if (!*cursorSizeEnd && !errno && cursorSizeLong > 0 && cursorSizeLong <= INT_MAX)
cursorSize = (int)cursorSizeLong;
}
_glfw.wl.cursorTheme = wl_cursor_theme_load(cursorTheme, cursorSize, _glfw.wl.shm);
if (!_glfw.wl.cursorTheme)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Unable to load default cursor theme");
return false;
}
_glfw.wl.cursorSurface =
_glfw.wl.cursorThemeManager = _wlCursorThemeManagerDefault();
_glfw.wl.cursorSurface =
wl_compositor_create_surface(_glfw.wl.compositor);
}
else
{
_glfw.wl.cursorThemeManager = NULL;
}
return true;
}
@ -814,8 +809,6 @@ void _glfwPlatformTerminate(void)
glfw_xkb_release(&_glfw.wl.xkb);
glfw_dbus_terminate(&_glfw.wl.dbus);
if (_glfw.wl.cursorTheme)
wl_cursor_theme_destroy(_glfw.wl.cursorTheme);
if (_glfw.wl.cursor.handle)
{
_glfw_dlclose(_glfw.wl.cursor.handle);
@ -824,6 +817,9 @@ void _glfwPlatformTerminate(void)
if (_glfw.wl.cursorSurface)
wl_surface_destroy(_glfw.wl.cursorSurface);
if (_glfw.wl.cursorThemeManager) {
_wlCursorThemeManagerDestroy(_glfw.wl.cursorThemeManager);
}
if (_glfw.wl.subcompositor)
wl_subcompositor_destroy(_glfw.wl.subcompositor);
if (_glfw.wl.compositor)

20
glfw/wl_platform.h vendored
View File

@ -76,7 +76,10 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
#define _GLFW_PLATFORM_CONTEXT_STATE
#define _GLFW_PLATFORM_LIBRARY_CONTEXT_STATE
struct wl_cursor_image {
#include "wl_cursors.h"
//^ Includes <wayland-cursor.h>
/**struct wl_cursor_image {
uint32_t width;
uint32_t height;
uint32_t hotspot_x;
@ -87,7 +90,7 @@ struct wl_cursor {
unsigned int image_count;
struct wl_cursor_image** images;
char* name;
};
};*/
typedef struct wl_cursor_theme* (* PFN_wl_cursor_theme_load)(const char*, int, struct wl_shm*);
typedef void (* PFN_wl_cursor_theme_destroy)(struct wl_cursor_theme*);
typedef struct wl_cursor* (* PFN_wl_cursor_theme_get_cursor)(struct wl_cursor_theme*, const char*);
@ -148,6 +151,7 @@ typedef struct _GLFWwindowWayland
_GLFWcursor* currentCursor;
double cursorPosX, cursorPosY;
struct wl_cursor_theme* cursorTheme;
char* title;
char appId[256];
@ -236,7 +240,6 @@ typedef struct _GLFWlibraryWayland
int compositorVersion;
int seatVersion;
struct wl_cursor_theme* cursorTheme;
struct wl_surface* cursorSurface;
GLFWCursorShape cursorPreviousShape;
uint32_t pointerSerial;
@ -278,6 +281,8 @@ typedef struct _GLFWlibraryWayland
size_t dataOffersCounter;
_GLFWWaylandDataOffer dataOffers[8];
char* primarySelectionString;
_wlCursorThemeManager* cursorThemeManager;
} _GLFWlibraryWayland;
// Wayland-specific per-monitor data
@ -291,8 +296,7 @@ typedef struct _GLFWmonitorWayland
int x;
int y;
int scale;
} _GLFWmonitorWayland;
} _GLFWmonitorWayland;
// Wayland-specific per-cursor data
//
@ -303,6 +307,10 @@ typedef struct _GLFWcursorWayland
int width, height;
int xhot, yhot;
int currentImage;
/** The scale of the cursor, or 0 if the cursor should be loaded late, or -1 if the cursor variable itself is unused. */
int scale;
/** Cursor shape stored to allow late cursor loading in setCursorImage. */
GLFWCursorShape shape;
} _GLFWcursorWayland;
@ -310,5 +318,5 @@ void _glfwAddOutputWayland(uint32_t name, uint32_t version);
void _glfwSetupWaylandDataDevice(void);
void _glfwSetupWaylandPrimarySelectionDevice(void);
void animateCursorImage(id_type timer_id, void *data);
struct wl_cursor* _glfwLoadCursor(GLFWCursorShape);
struct wl_cursor* _glfwLoadCursor(GLFWCursorShape, struct wl_cursor_theme*);
void destroy_data_offer(_GLFWWaylandDataOffer*);

57
glfw/wl_window.c vendored
View File

@ -73,6 +73,9 @@ static bool checkScaleChange(_GLFWwindow* window)
{
window->wl.scale = scale;
wl_surface_set_buffer_scale(window->wl.surface, scale);
window->wl.cursorTheme = _wlCursorThemeManage(_glfw.wl.cursorThemeManager,
window->wl.cursorTheme,
_wlCursorPxFromScale(scale));
return true;
}
if (window->wl.monitorsCount > 0 && !window->wl.initial_scale_notified) {
@ -680,17 +683,26 @@ static bool createXdgSurface(_GLFWwindow* window)
}
static void
setCursorImage(_GLFWcursorWayland* cursorWayland)
setCursorImage(_GLFWwindow* window, _GLFWcursorWayland* cursorWayland)
{
struct wl_cursor_image* image;
struct wl_buffer* buffer;
struct wl_surface* surface = _glfw.wl.cursorSurface;
const int scale = window->wl.scale;
if (!cursorWayland->cursor) {
if (cursorWayland->scale < 0) {
buffer = cursorWayland->buffer;
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 0);
} else
{
if(cursorWayland->scale != scale) {
struct wl_cursor *newCursor = _glfwLoadCursor(cursorWayland->shape, window->wl.cursorTheme);
if(newCursor != NULL) {
cursorWayland->cursor = newCursor;
} else {
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: late cursor load failed; proceeding with existing cursor");
}
}
image = cursorWayland->cursor->images[cursorWayland->currentImage];
buffer = wl_cursor_image_get_buffer(image);
if (image->delay) {
@ -711,8 +723,9 @@ setCursorImage(_GLFWcursorWayland* cursorWayland)
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial,
surface,
cursorWayland->xhot,
cursorWayland->yhot);
cursorWayland->xhot / scale,
cursorWayland->yhot / scale);
wl_surface_set_buffer_scale(surface, scale);
wl_surface_attach(surface, buffer, 0, 0);
wl_surface_damage(surface, 0, 0,
cursorWayland->width, cursorWayland->height);
@ -728,7 +741,7 @@ incrementCursorImage(_GLFWwindow* window)
{
cursor->wl.currentImage += 1;
cursor->wl.currentImage %= cursor->wl.cursor->image_count;
setCursorImage(&cursor->wl);
setCursorImage(window, &cursor->wl);
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, cursor->wl.cursor->image_count > 1);
return;
}
@ -804,24 +817,24 @@ handleEvents(monotonic_t timeout)
}
static struct wl_cursor*
try_cursor_names(int arg_count, ...) {
try_cursor_names(struct wl_cursor_theme* theme, int arg_count, ...) {
struct wl_cursor* ans = NULL;
va_list ap;
va_start(ap, arg_count);
for (int i = 0; i < arg_count && !ans; i++) {
const char *name = va_arg(ap, const char *);
ans = wl_cursor_theme_get_cursor(_glfw.wl.cursorTheme, name);
ans = wl_cursor_theme_get_cursor(theme, name);
}
va_end(ap);
return ans;
}
struct wl_cursor* _glfwLoadCursor(GLFWCursorShape shape)
struct wl_cursor* _glfwLoadCursor(GLFWCursorShape shape, struct wl_cursor_theme* theme)
{
static bool warnings[GLFW_INVALID_CURSOR] = {0};
#define NUMARGS(...) (sizeof((const char*[]){__VA_ARGS__})/sizeof(const char*))
#define C(name, ...) case name: { \
ans = try_cursor_names(NUMARGS(__VA_ARGS__), __VA_ARGS__); \
ans = try_cursor_names(theme, NUMARGS(__VA_ARGS__), __VA_ARGS__); \
if (!ans && !warnings[name]) {\
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Could not find standard cursor: %s", #name); \
warnings[name] = true; \
@ -901,6 +914,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
}
window->wl.currentCursor = NULL;
// Don't set window->wl.cursorTheme to NULL here.
window->wl.monitors = calloc(1, sizeof(_GLFWmonitor*));
window->wl.monitorsCount = 0;
@ -911,6 +925,12 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
void _glfwPlatformDestroyWindow(_GLFWwindow* window)
{
if(window->wl.cursorTheme) {
_wlCursorThemeManage(_glfw.wl.cursorThemeManager,
window->wl.cursorTheme,
0);
window->wl.cursorTheme = NULL;
}
if (window == _glfw.wl.pointerFocus)
{
_glfw.wl.pointerFocus = NULL;
@ -929,6 +949,7 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
window->context.destroy(window);
destroyDecorations(window);
if (window->wl.xdg.decoration)
zxdg_toplevel_decoration_v1_destroy(window->wl.xdg.decoration);
@ -1317,17 +1338,19 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
cursor->wl.height = image->height;
cursor->wl.xhot = xhot;
cursor->wl.yhot = yhot;
cursor->wl.scale = -1;
cursor->wl.shape = GLFW_INVALID_CURSOR;
return true;
}
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, GLFWCursorShape shape)
{
struct wl_cursor* standardCursor;
standardCursor = _glfwLoadCursor(shape);
if (!standardCursor) return false;
cursor->wl.cursor = standardCursor;
// Don't actually load the cursor at this point,
// because there's not enough info to be properly HiDPI aware.
cursor->wl.cursor = NULL;
cursor->wl.currentImage = 0;
cursor->wl.scale = 0;
cursor->wl.shape = shape;
return true;
}
@ -1470,10 +1493,10 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
if (window->cursorMode == GLFW_CURSOR_NORMAL)
{
if (cursor)
setCursorImage(&cursor->wl);
setCursorImage(window, &cursor->wl);
else
{
defaultCursor = _glfwLoadCursor(GLFW_ARROW_CURSOR);
defaultCursor = _glfwLoadCursor(GLFW_ARROW_CURSOR, window->wl.cursorTheme);
if (!defaultCursor) return;
_GLFWcursorWayland cursorWayland = {
defaultCursor,
@ -1482,7 +1505,7 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
0, 0,
0
};
setCursorImage(&cursorWayland);
setCursorImage(window, &cursorWayland);
}
}
else if (window->cursorMode == GLFW_CURSOR_DISABLED)