linux_desktop_settings.c 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. /*
  2. * linux_cursor_settings.c
  3. * Copyright (C) 2021 Kovid Goyal <kovid at kovidgoyal.net>
  4. *
  5. * Distributed under terms of the GPL3 license.
  6. */
  7. #include "linux_desktop_settings.h"
  8. #include <stdlib.h>
  9. #include <strings.h>
  10. #include <string.h>
  11. #define DESKTOP_SERVICE "org.freedesktop.portal.Desktop"
  12. #define DESKTOP_PATH "/org/freedesktop/portal/desktop"
  13. #define DESKTOP_INTERFACE "org.freedesktop.portal.Settings"
  14. #define GNOME_DESKTOP_NAMESPACE "org.gnome.desktop.interface"
  15. #define FDO_DESKTOP_NAMESPACE "org.freedesktop.appearance"
  16. #define FDO_APPEARANCE_KEY "color-scheme"
  17. static char theme_name[128] = {0};
  18. static int theme_size = -1;
  19. static GLFWColorScheme appearance = GLFW_COLOR_SCHEME_NO_PREFERENCE;
  20. static bool cursor_theme_changed = false;
  21. GLFWColorScheme
  22. glfw_current_system_color_theme(void) {
  23. return appearance;
  24. }
  25. #define HANDLER(name) static void name(DBusMessage *msg, const char* errmsg, void *data) { \
  26. (void)data; \
  27. if (errmsg) { \
  28. _glfwInputError(GLFW_PLATFORM_ERROR, "%s: failed with error: %s", #name, errmsg); \
  29. return; \
  30. }
  31. static void
  32. process_fdo_setting(const char *key, DBusMessageIter *value) {
  33. if (strcmp(key, FDO_APPEARANCE_KEY) == 0) {
  34. if (dbus_message_iter_get_arg_type(value) == DBUS_TYPE_UINT32) {
  35. uint32_t val;
  36. dbus_message_iter_get_basic(value, &val);
  37. if (val > 2) val = 0;
  38. appearance = val;
  39. }
  40. }
  41. }
  42. static void
  43. process_gnome_setting(const char *key, DBusMessageIter *value) {
  44. if (strcmp(key, "cursor-size") == 0) {
  45. if (dbus_message_iter_get_arg_type(value) == DBUS_TYPE_INT32) {
  46. int32_t sz;
  47. dbus_message_iter_get_basic(value, &sz);
  48. if (sz > 0 && sz != theme_size) {
  49. theme_size = sz;
  50. cursor_theme_changed = true;
  51. }
  52. }
  53. } else if (strcmp(key, "cursor-theme") == 0) {
  54. if (dbus_message_iter_get_arg_type(value) == DBUS_TYPE_STRING) {
  55. const char *name;
  56. dbus_message_iter_get_basic(value, &name);
  57. if (name) {
  58. strncpy(theme_name, name, sizeof(theme_name) - 1);
  59. cursor_theme_changed = true;
  60. }
  61. }
  62. }
  63. }
  64. static void
  65. process_settings_dict(DBusMessageIter *array_iter, void(process_setting)(const char *, DBusMessageIter*)) {
  66. DBusMessageIter item_iter, value_iter;
  67. while (dbus_message_iter_get_arg_type(array_iter) == DBUS_TYPE_DICT_ENTRY) {
  68. dbus_message_iter_recurse(array_iter, &item_iter);
  69. if (dbus_message_iter_get_arg_type(&item_iter) == DBUS_TYPE_STRING) {
  70. const char *key;
  71. dbus_message_iter_get_basic(&item_iter, &key);
  72. if (dbus_message_iter_next(&item_iter) && dbus_message_iter_get_arg_type(&item_iter) == DBUS_TYPE_VARIANT) {
  73. dbus_message_iter_recurse(&item_iter, &value_iter);
  74. process_setting(key, &value_iter);
  75. }
  76. }
  77. if (!dbus_message_iter_next(array_iter)) break;
  78. }
  79. }
  80. HANDLER(process_desktop_settings)
  81. cursor_theme_changed = false;
  82. DBusMessageIter root, array, item, settings;
  83. dbus_message_iter_init(msg, &root);
  84. #define die(...) { _glfwInputError(GLFW_PLATFORM_ERROR, __VA_ARGS__); return; }
  85. if (dbus_message_iter_get_arg_type(&root) != DBUS_TYPE_ARRAY) die("Reply to request for desktop settings is not an array");
  86. dbus_message_iter_recurse(&root, &array);
  87. while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) {
  88. dbus_message_iter_recurse(&array, &item);
  89. if (dbus_message_iter_get_arg_type(&item) == DBUS_TYPE_STRING) {
  90. const char *namespace;
  91. dbus_message_iter_get_basic(&item, &namespace);
  92. if (dbus_message_iter_next(&item) && dbus_message_iter_get_arg_type(&item) == DBUS_TYPE_ARRAY) {
  93. dbus_message_iter_recurse(&item, &settings);
  94. if (strcmp(namespace, FDO_DESKTOP_NAMESPACE) == 0) {
  95. process_settings_dict(&settings, process_fdo_setting);
  96. } else if (strcmp(namespace, GNOME_DESKTOP_NAMESPACE) == 0) {
  97. process_settings_dict(&settings, process_gnome_setting);
  98. }
  99. }
  100. }
  101. if (!dbus_message_iter_next(&array)) break;
  102. }
  103. #undef die
  104. if (cursor_theme_changed) _glfwPlatformChangeCursorTheme();
  105. }
  106. #undef HANDLER
  107. static bool
  108. read_desktop_settings(DBusConnection *session_bus) {
  109. RAII_MSG(msg, dbus_message_new_method_call(DESKTOP_SERVICE, DESKTOP_PATH, DESKTOP_INTERFACE, "ReadAll"));
  110. if (!msg) return false;
  111. DBusMessageIter iter, array_iter;
  112. dbus_message_iter_init_append(msg, &iter);
  113. if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &array_iter)) { return false; }
  114. if (!dbus_message_iter_close_container(&iter, &array_iter)) { return false; }
  115. bool ok = call_method_with_msg(session_bus, msg, DBUS_TIMEOUT_USE_DEFAULT, process_desktop_settings, NULL);
  116. return ok;
  117. }
  118. void
  119. glfw_current_cursor_theme(const char **theme, int *size) {
  120. *theme = theme_name[0] ? theme_name : NULL;
  121. *size = (theme_size > 0 && theme_size < 2048) ? theme_size : 32;
  122. }
  123. static void
  124. get_cursor_theme_from_env(void) {
  125. const char *q = getenv("XCURSOR_THEME");
  126. if (q) strncpy(theme_name, q, sizeof(theme_name)-1);
  127. const char *env = getenv("XCURSOR_SIZE");
  128. theme_size = 32;
  129. if (env) {
  130. const int retval = atoi(env);
  131. if (retval > 0 && retval < 2048) theme_size = retval;
  132. }
  133. }
  134. static void
  135. on_color_scheme_change(DBusMessage *message) {
  136. DBusMessageIter iter[2];
  137. dbus_message_iter_init (message, &iter[0]);
  138. int current_type;
  139. while ((current_type = dbus_message_iter_get_arg_type (&iter[0])) != DBUS_TYPE_INVALID) {
  140. if (current_type == DBUS_TYPE_VARIANT) {
  141. dbus_message_iter_recurse(&iter[0], &iter[1]);
  142. if (dbus_message_iter_get_arg_type(&iter[1]) == DBUS_TYPE_UINT32) {
  143. uint32_t val = 0;
  144. dbus_message_iter_get_basic(&iter[1], &val);
  145. if (val > 2) val = 0;
  146. if (val != appearance) {
  147. appearance = val;
  148. _glfwInputColorScheme(appearance);
  149. }
  150. }
  151. break;
  152. }
  153. dbus_message_iter_next(&iter[0]);
  154. }
  155. }
  156. static DBusHandlerResult
  157. setting_changed(DBusConnection *conn UNUSED, DBusMessage *msg, void *user_data UNUSED) {
  158. /* printf("session_bus settings_changed invoked interface: %s member: %s\n", dbus_message_get_interface(msg), dbus_message_get_member(msg)); */
  159. if (dbus_message_is_signal(msg, DESKTOP_INTERFACE, "SettingChanged")) {
  160. const char *namespace = NULL, *key = NULL;
  161. if (glfw_dbus_get_args(msg, "Failed to get namespace and key from SettingChanged notification signal", DBUS_TYPE_STRING, &namespace, DBUS_TYPE_STRING, &key, DBUS_TYPE_INVALID)) {
  162. if (strcmp(namespace, FDO_DESKTOP_NAMESPACE) == 0) {
  163. if (strcmp(key, FDO_APPEARANCE_KEY) == 0) {
  164. on_color_scheme_change(msg);
  165. }
  166. }
  167. }
  168. }
  169. return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
  170. }
  171. void
  172. glfw_initialize_desktop_settings(void) {
  173. get_cursor_theme_from_env();
  174. DBusConnection *session_bus = glfw_dbus_session_bus();
  175. if (session_bus) {
  176. if (!read_desktop_settings(session_bus)) _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to read desktop settings, make sure you have the desktop portal running.");
  177. dbus_bus_add_match(session_bus, "type='signal',interface='" DESKTOP_INTERFACE "',member='SettingChanged'", NULL);
  178. dbus_connection_add_filter(session_bus, setting_changed, NULL, NULL);
  179. }
  180. }