From 63a50ec0666cf6d3fd2c3dc08cf89c2e1f0e2ec8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 25 Mar 2021 12:20:13 +0530 Subject: [PATCH] Use the correct mouse cursor theme on GNOME Relies on a working desktop settings portal (xdg-desktop-portal-gtk) --- glfw/internal.h | 1 + glfw/linux_desktop_settings.c | 109 ++++++++++++++++++++++++++++++++++ glfw/linux_desktop_settings.h | 14 +++++ glfw/source-info.json | 2 + glfw/wl_cursors.c | 46 +++++++------- glfw/wl_init.c | 2 + glfw/wl_platform.h | 1 - glfw/wl_window.c | 25 +++++--- 8 files changed, 166 insertions(+), 34 deletions(-) create mode 100644 glfw/linux_desktop_settings.c create mode 100644 glfw/linux_desktop_settings.h diff --git a/glfw/internal.h b/glfw/internal.h index 85e6da567..fa150cb1d 100644 --- a/glfw/internal.h +++ b/glfw/internal.h @@ -729,6 +729,7 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled); void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, bool enabled); void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity); void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev); +void _glfwPlatformChangeCursorTheme(void); void _glfwPlatformPollEvents(void); void _glfwPlatformWaitEvents(void); diff --git a/glfw/linux_desktop_settings.c b/glfw/linux_desktop_settings.c new file mode 100644 index 000000000..1686f2b97 --- /dev/null +++ b/glfw/linux_desktop_settings.c @@ -0,0 +1,109 @@ +/* + * linux_cursor_settings.c + * Copyright (C) 2021 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#include "linux_desktop_settings.h" +#include +#include +#include + +static const char *DESKTOP_SERVICE = "org.freedesktop.portal.Desktop"; +static const char *DESKTOP_PATH = "/org/freedesktop/portal/desktop"; +static const char *DESKTOP_INTERFACE = "org.freedesktop.portal.Settings"; +static const char *GNOME_DESKTOP_NAMESPACE = "org.gnome.desktop.interface"; + + +static char theme_name[64] = {0}; +static int theme_size = -1; +static bool gnome_cursor_theme_read = false, gnome_cursor_size_read = false; + +static bool +parse_dbus_message_for_type(DBusMessage *const reply, const char *errmsg, const int type, void *value) { + DBusMessageIter iter[3]; + dbus_message_iter_init(reply, &iter[0]); +#define FAIL { _glfwInputError(GLFW_PLATFORM_ERROR, "%s", errmsg); return false; } + if (dbus_message_iter_get_arg_type(&iter[0]) != DBUS_TYPE_VARIANT) FAIL; + dbus_message_iter_recurse(&iter[0], &iter[1]); + if (dbus_message_iter_get_arg_type(&iter[1]) != DBUS_TYPE_VARIANT) FAIL; + dbus_message_iter_recurse(&iter[1], &iter[2]); + if (dbus_message_iter_get_arg_type(&iter[2]) != type) FAIL; + dbus_message_iter_get_basic(&iter[2], value); + return true; +#undef FAIL +} + +#define HANDLER(name) void name(DBusMessage *msg, const char* errmsg, void *data) { \ + (void)data; \ + if (errmsg) { \ + _glfwInputError(GLFW_PLATFORM_ERROR, "%s: failed with error: %s", #name, errmsg); \ + return; \ + } + +HANDLER(on_gnome_cursor_theme_read) + const char *name; + if (!parse_dbus_message_for_type(msg, "Failed to get cursor theme name from reply", DBUS_TYPE_STRING, &name)) return; + if (name && name[0]) { + gnome_cursor_theme_read = true; + strncpy(theme_name, name, sizeof(theme_name) - 1); + if (gnome_cursor_size_read) _glfwPlatformChangeCursorTheme(); + } +} + +HANDLER(on_gnome_cursor_size_read) + int32_t sz; + if (!parse_dbus_message_for_type(msg, "Failed to get cursor theme size from reply", DBUS_TYPE_INT32, &sz)) return; + gnome_cursor_size_read = true; + theme_size = sz; + if (gnome_cursor_theme_read) _glfwPlatformChangeCursorTheme(); +} +#undef HANDLER + + +static bool +call_read(DBusConnection *session_bus, dbus_pending_callback callback, const char *namespace, const char *key) { + return glfw_dbus_call_method_with_reply( + session_bus, DESKTOP_SERVICE, DESKTOP_PATH, DESKTOP_INTERFACE, "Read", DBUS_TIMEOUT_USE_DEFAULT, + callback, NULL, DBUS_TYPE_STRING, &namespace, DBUS_TYPE_STRING, &key, DBUS_TYPE_INVALID); +} + +static void +get_from_gnome(void) { + theme_size = 32; + DBusConnection *session_bus = glfw_dbus_session_bus(); + if (session_bus) { + const char *theme_key = "cursor-theme"; + call_read(session_bus, on_gnome_cursor_theme_read, GNOME_DESKTOP_NAMESPACE, theme_key); + const char *size_key = "cursor-size"; + call_read(session_bus, on_gnome_cursor_size_read, GNOME_DESKTOP_NAMESPACE, size_key); + } +} + + +void +glfw_current_cursor_theme(const char **theme, int *size) { + *theme = theme_name[0] ? theme_name : NULL; + *size = (theme_size > 0 && theme_size < 2048) ? theme_size : 32; +} + +static void +get_cursor_theme_from_env(void) { + const char *q = getenv("XCURSOR_THEME"); + if (q) strncpy(theme_name, q, sizeof(theme_name)-1); + const char *env = getenv("XCURSOR_SIZE"); + theme_size = 32; + if (env) { + const int retval = atoi(env); + if (retval > 0 && retval < 2048) theme_size = retval; + } +} + +void +glfw_initialize_desktop_settings(void) { + get_cursor_theme_from_env(); + const char *desktop = getenv("XDG_CURRENT_DESKTOP"); + bool is_gnome = desktop && strncasecmp(desktop, "GNOME", sizeof("GNOME") - 1) == 0; + if (is_gnome) get_from_gnome(); +} diff --git a/glfw/linux_desktop_settings.h b/glfw/linux_desktop_settings.h new file mode 100644 index 000000000..212974815 --- /dev/null +++ b/glfw/linux_desktop_settings.h @@ -0,0 +1,14 @@ +/* + * Copyright (C) 2021 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#pragma once + +#include "dbus_glfw.h" +#include "internal.h" + + +void glfw_initialize_desktop_settings(void); +void glfw_current_cursor_theme(const char **theme, int *size); diff --git a/glfw/source-info.json b/glfw/source-info.json index c333e8508..b08c77154 100644 --- a/glfw/source-info.json +++ b/glfw/source-info.json @@ -65,6 +65,7 @@ "linux_joystick.h", "null_joystick.h", "linux_notify.h", + "linux_desktop_settings.h", "main_loop.h" ], "protocols": [ @@ -91,6 +92,7 @@ "osmesa_context.c", "backend_utils.c", "linux_joystick.c", + "linux_desktop_settings.c", "null_joystick.c", "linux_notify.c" ] diff --git a/glfw/wl_cursors.c b/glfw/wl_cursors.c index b3aef51b3..13f4d772d 100644 --- a/glfw/wl_cursors.c +++ b/glfw/wl_cursors.c @@ -1,6 +1,7 @@ // Future devs supporting whatever Wayland protocol stabilizes for cursor selection: see _themeAdd. #include "internal.h" +#include "linux_desktop_settings.h" #include #include @@ -8,38 +9,35 @@ #include #include +static GLFWWLCursorThemes cursor_themes; + static int pixels_from_scale(int scale) { - static bool queried_env = false; - static int factor = 32; - if (!queried_env) { - const char *env = getenv("XCURSOR_SIZE"); - if (env) { - const int retval = atoi(env); - if (retval > 0 && retval < 2048) factor = retval; - } - queried_env = true; - } + int factor; + const char* name; + glfw_current_cursor_theme(&name, &factor); return factor * scale; } struct wl_cursor_theme* glfw_wlc_theme_for_scale(int scale) { - GLFWWLCursorThemes *t = &_glfw.wl.cursor_themes; - for (size_t i = 0; i < t->count; i++) { - if (t->themes[i].scale == scale) return t->themes[i].theme; + for (size_t i = 0; i < cursor_themes.count; i++) { + if (cursor_themes.themes[i].scale == scale) return cursor_themes.themes[i].theme; } - if (t->count >= t->capacity) { - t->themes = realloc(t->themes, sizeof(GLFWWLCursorTheme) * (t->count + 16)); - if (!t->themes) { + if (cursor_themes.count >= cursor_themes.capacity) { + cursor_themes.themes = realloc(cursor_themes.themes, sizeof(GLFWWLCursorTheme) * (cursor_themes.count + 16)); + if (!cursor_themes.themes) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Out of memory allocating space for cursor themes"); return NULL; } - t->capacity = t->count + 16; + cursor_themes.capacity = cursor_themes.count + 16; } - struct wl_cursor_theme *ans = wl_cursor_theme_load(getenv("XCURSOR_THEME"), pixels_from_scale(scale), _glfw.wl.shm); + int factor; + const char* name; + glfw_current_cursor_theme(&name, &factor); + struct wl_cursor_theme *ans = wl_cursor_theme_load(name, pixels_from_scale(scale), _glfw.wl.shm); if (!ans) { _glfwInputError( GLFW_PLATFORM_ERROR, "Wayland: wl_cursor_theme_load failed at scale: %d pixels: %d", @@ -47,7 +45,7 @@ glfw_wlc_theme_for_scale(int scale) { ); return NULL; } - GLFWWLCursorTheme *theme = t->themes + t->count++; + GLFWWLCursorTheme *theme = cursor_themes.themes + cursor_themes.count++; theme->scale = scale; theme->theme = ans; return ans; @@ -55,11 +53,9 @@ glfw_wlc_theme_for_scale(int scale) { void glfw_wlc_destroy(void) { - GLFWWLCursorThemes *t = &_glfw.wl.cursor_themes; - - for (size_t i = 0; i < t->count; i++) { - wl_cursor_theme_destroy(t->themes[i].theme); + for (size_t i = 0; i < cursor_themes.count; i++) { + wl_cursor_theme_destroy(cursor_themes.themes[i].theme); } - free(t->themes); - t->themes = NULL; t->capacity = 0; t->count = 0; + free(cursor_themes.themes); + cursor_themes.themes = NULL; cursor_themes.capacity = 0; cursor_themes.count = 0; } diff --git a/glfw/wl_init.c b/glfw/wl_init.c index c978dd58a..010ce9e24 100644 --- a/glfw/wl_init.c +++ b/glfw/wl_init.c @@ -29,6 +29,7 @@ #define _GNU_SOURCE #include "internal.h" #include "backend_utils.h" +#include "linux_desktop_settings.h" #include "../kitty/monotonic.h" #include @@ -763,6 +764,7 @@ int _glfwPlatformInit(void) "Wayland: Failed to initialize event loop data"); } glfw_dbus_init(&_glfw.wl.dbus, &_glfw.wl.eventLoopData); + glfw_initialize_desktop_settings(); _glfw.wl.keyRepeatInfo.keyRepeatTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-key-repeat", ms_to_monotonic_t(500ll), 0, true, dispatchPendingKeyRepeats, NULL, NULL); _glfw.wl.cursorAnimationTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-cursor-animation", ms_to_monotonic_t(500ll), 0, true, animateCursorImage, NULL, NULL); diff --git a/glfw/wl_platform.h b/glfw/wl_platform.h index f29da2564..82b438667 100644 --- a/glfw/wl_platform.h +++ b/glfw/wl_platform.h @@ -263,7 +263,6 @@ typedef struct _GLFWlibraryWayland size_t dataOffersCounter; _GLFWWaylandDataOffer dataOffers[8]; char* primarySelectionString; - GLFWWLCursorThemes cursor_themes; } _GLFWlibraryWayland; // Wayland-specific per-monitor data diff --git a/glfw/wl_window.c b/glfw/wl_window.c index dffcb4beb..d089fceec 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -42,8 +42,8 @@ #include -static void setCursorImage(_GLFWwindow* window) -{ +static void +setCursorImage(_GLFWwindow* window, bool on_theme_change) { _GLFWcursorWayland defaultCursor = {.shape = GLFW_ARROW_CURSOR}; _GLFWcursorWayland* cursorWayland = window->cursor ? &window->cursor->wl : &defaultCursor; struct wl_cursor_image* image = NULL; @@ -54,9 +54,8 @@ static void setCursorImage(_GLFWwindow* window) if (cursorWayland->scale < 0) { buffer = cursorWayland->buffer; toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 0); - } else - { - if (cursorWayland->scale != scale) { + } else { + if (on_theme_change || cursorWayland->scale != scale) { struct wl_cursor *newCursor = NULL; struct wl_cursor_theme *theme = glfw_wlc_theme_for_scale(scale); if (theme) newCursor = _glfwLoadCursor(cursorWayland->shape, theme); @@ -131,7 +130,7 @@ static bool checkScaleChange(_GLFWwindow* window) { window->wl.scale = scale; wl_surface_set_buffer_scale(window->wl.surface, scale); - setCursorImage(window); + setCursorImage(window, false); return true; } if (window->wl.monitorsCount > 0 && !window->wl.initial_scale_notified) { @@ -746,7 +745,7 @@ static void incrementCursorImage(_GLFWwindow* window) { cursor->wl.currentImage += 1; cursor->wl.currentImage %= cursor->wl.cursor->image_count; - setCursorImage(window); + setCursorImage(window, false); toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, cursor->wl.cursor->image_count > 1); return; } @@ -1521,7 +1520,7 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor) if (window->cursorMode == GLFW_CURSOR_NORMAL) { - setCursorImage(window); + setCursorImage(window, false); } else if (window->cursorMode == GLFW_CURSOR_DISABLED) { @@ -2114,6 +2113,16 @@ frame_handle_redraw(void *data, struct wl_callback *callback, uint32_t time UNUS wl_callback_destroy(callback); } +void +_glfwPlatformChangeCursorTheme(void) { + glfw_wlc_destroy(); + _GLFWwindow *w = _glfw.windowListHead; + while (w) { + setCursorImage(w, true); + w = w->next; + } + +} ////////////////////////////////////////////////////////////////////////// ////// GLFW native API //////