linux_desktop_settings.c 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  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. static const char* supported_namespaces[2] = {FDO_DESKTOP_NAMESPACE, GNOME_DESKTOP_NAMESPACE};
  17. #define FDO_APPEARANCE_KEY "color-scheme"
  18. static char theme_name[128] = {0};
  19. static int theme_size = -1;
  20. static GLFWColorScheme appearance = GLFW_COLOR_SCHEME_NO_PREFERENCE;
  21. static bool cursor_theme_changed = false, appearance_initialized = false;
  22. #define HANDLER(name) static void name(DBusMessage *msg, const char* errmsg, void *data) { \
  23. (void)data; \
  24. if (errmsg) { \
  25. _glfwInputError(GLFW_PLATFORM_ERROR, "%s: failed with error: %s", #name, errmsg); \
  26. return; \
  27. }
  28. HANDLER(get_color_scheme)
  29. uint32_t val;
  30. DBusMessageIter iter, variant_iter;
  31. if (!dbus_message_iter_init(msg, &iter)) return;
  32. dbus_message_iter_recurse(&iter, &variant_iter);
  33. int type = dbus_message_iter_get_arg_type(&variant_iter);
  34. if (type != DBUS_TYPE_UINT32) {
  35. _glfwInputError(GLFW_PLATFORM_ERROR, "ReadOne for color-scheme did not return a uint32"); return;
  36. }
  37. dbus_message_iter_get_basic(&variant_iter, &val);
  38. if (val < 3) appearance = val;
  39. }
  40. GLFWColorScheme
  41. glfw_current_system_color_theme(bool query_if_unintialized) {
  42. if (!appearance_initialized && query_if_unintialized) {
  43. appearance_initialized = true;
  44. DBusConnection *session_bus = glfw_dbus_session_bus();
  45. if (session_bus) {
  46. const char *namespace = FDO_DESKTOP_NAMESPACE, *key = FDO_APPEARANCE_KEY;
  47. glfw_dbus_call_blocking_method(session_bus, DESKTOP_SERVICE, DESKTOP_PATH, DESKTOP_INTERFACE, "ReadOne", DBUS_TIMEOUT_USE_DEFAULT,
  48. get_color_scheme, NULL, DBUS_TYPE_STRING, &namespace, DBUS_TYPE_STRING, &key, DBUS_TYPE_INVALID);
  49. }
  50. }
  51. return appearance;
  52. }
  53. static void
  54. process_fdo_setting(const char *key, DBusMessageIter *value) {
  55. if (strcmp(key, FDO_APPEARANCE_KEY) == 0) {
  56. if (dbus_message_iter_get_arg_type(value) == DBUS_TYPE_UINT32) {
  57. uint32_t val;
  58. dbus_message_iter_get_basic(value, &val);
  59. if (val > 2) val = 0;
  60. if (!appearance_initialized) {
  61. appearance_initialized = true;
  62. if (val != appearance) {
  63. appearance = val;
  64. _glfwInputColorScheme(appearance, true);
  65. }
  66. }
  67. }
  68. }
  69. }
  70. static void
  71. process_gnome_setting(const char *key, DBusMessageIter *value) {
  72. if (strcmp(key, "cursor-size") == 0) {
  73. if (dbus_message_iter_get_arg_type(value) == DBUS_TYPE_INT32) {
  74. int32_t sz;
  75. dbus_message_iter_get_basic(value, &sz);
  76. if (sz > 0 && sz != theme_size) {
  77. theme_size = sz;
  78. cursor_theme_changed = true;
  79. }
  80. }
  81. } else if (strcmp(key, "cursor-theme") == 0) {
  82. if (dbus_message_iter_get_arg_type(value) == DBUS_TYPE_STRING) {
  83. const char *name;
  84. dbus_message_iter_get_basic(value, &name);
  85. if (name) {
  86. strncpy(theme_name, name, sizeof(theme_name) - 1);
  87. cursor_theme_changed = true;
  88. }
  89. }
  90. }
  91. }
  92. static void
  93. process_settings_dict(DBusMessageIter *array_iter, void(process_setting)(const char *, DBusMessageIter*)) {
  94. DBusMessageIter item_iter, value_iter;
  95. while (dbus_message_iter_get_arg_type(array_iter) == DBUS_TYPE_DICT_ENTRY) {
  96. dbus_message_iter_recurse(array_iter, &item_iter);
  97. if (dbus_message_iter_get_arg_type(&item_iter) == DBUS_TYPE_STRING) {
  98. const char *key;
  99. dbus_message_iter_get_basic(&item_iter, &key);
  100. if (dbus_message_iter_next(&item_iter) && dbus_message_iter_get_arg_type(&item_iter) == DBUS_TYPE_VARIANT) {
  101. dbus_message_iter_recurse(&item_iter, &value_iter);
  102. process_setting(key, &value_iter);
  103. }
  104. }
  105. if (!dbus_message_iter_next(array_iter)) break;
  106. }
  107. }
  108. HANDLER(process_desktop_settings)
  109. cursor_theme_changed = false;
  110. DBusMessageIter root, array, item, settings;
  111. dbus_message_iter_init(msg, &root);
  112. #define die(...) { _glfwInputError(GLFW_PLATFORM_ERROR, __VA_ARGS__); return; }
  113. if (dbus_message_iter_get_arg_type(&root) != DBUS_TYPE_ARRAY) die("Reply to request for desktop settings is not an array");
  114. dbus_message_iter_recurse(&root, &array);
  115. while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) {
  116. dbus_message_iter_recurse(&array, &item);
  117. if (dbus_message_iter_get_arg_type(&item) == DBUS_TYPE_STRING) {
  118. const char *namespace;
  119. dbus_message_iter_get_basic(&item, &namespace);
  120. if (dbus_message_iter_next(&item) && dbus_message_iter_get_arg_type(&item) == DBUS_TYPE_ARRAY) {
  121. dbus_message_iter_recurse(&item, &settings);
  122. if (strcmp(namespace, FDO_DESKTOP_NAMESPACE) == 0) {
  123. process_settings_dict(&settings, process_fdo_setting);
  124. } else if (strcmp(namespace, GNOME_DESKTOP_NAMESPACE) == 0) {
  125. process_settings_dict(&settings, process_gnome_setting);
  126. }
  127. }
  128. }
  129. if (!dbus_message_iter_next(&array)) break;
  130. }
  131. #undef die
  132. #ifndef _GLFW_X11
  133. if (cursor_theme_changed) _glfwPlatformChangeCursorTheme();
  134. #endif
  135. }
  136. #undef HANDLER
  137. static bool
  138. read_desktop_settings(DBusConnection *session_bus) {
  139. RAII_MSG(msg, dbus_message_new_method_call(DESKTOP_SERVICE, DESKTOP_PATH, DESKTOP_INTERFACE, "ReadAll"));
  140. if (!msg) return false;
  141. DBusMessageIter iter, array_iter;
  142. dbus_message_iter_init_append(msg, &iter);
  143. if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &array_iter)) { return false; }
  144. for (unsigned i = 0; i < arraysz(supported_namespaces); ++i) {
  145. if (!dbus_message_iter_append_basic(&array_iter, DBUS_TYPE_STRING, &supported_namespaces[i])) return false;
  146. }
  147. if (!dbus_message_iter_close_container(&iter, &array_iter)) { return false; }
  148. return call_method_with_msg(session_bus, msg, DBUS_TIMEOUT_USE_DEFAULT, process_desktop_settings, NULL, false);
  149. }
  150. void
  151. glfw_current_cursor_theme(const char **theme, int *size) {
  152. *theme = theme_name[0] ? theme_name : NULL;
  153. *size = (theme_size > 0 && theme_size < 2048) ? theme_size : 32;
  154. }
  155. static void
  156. get_cursor_theme_from_env(void) {
  157. const char *q = getenv("XCURSOR_THEME");
  158. if (q) strncpy(theme_name, q, sizeof(theme_name)-1);
  159. const char *env = getenv("XCURSOR_SIZE");
  160. theme_size = 32;
  161. if (env) {
  162. const int retval = atoi(env);
  163. if (retval > 0 && retval < 2048) theme_size = retval;
  164. }
  165. }
  166. static void
  167. on_color_scheme_change(DBusMessage *message) {
  168. DBusMessageIter iter[2];
  169. dbus_message_iter_init (message, &iter[0]);
  170. int current_type;
  171. while ((current_type = dbus_message_iter_get_arg_type (&iter[0])) != DBUS_TYPE_INVALID) {
  172. if (current_type == DBUS_TYPE_VARIANT) {
  173. dbus_message_iter_recurse(&iter[0], &iter[1]);
  174. if (dbus_message_iter_get_arg_type(&iter[1]) == DBUS_TYPE_UINT32) {
  175. uint32_t val = 0;
  176. dbus_message_iter_get_basic(&iter[1], &val);
  177. if (val > 2) val = 0;
  178. if (val != appearance) {
  179. appearance = val;
  180. appearance_initialized = true;
  181. _glfwInputColorScheme(appearance, false);
  182. }
  183. }
  184. break;
  185. }
  186. dbus_message_iter_next(&iter[0]);
  187. }
  188. }
  189. static DBusHandlerResult
  190. setting_changed(DBusConnection *conn UNUSED, DBusMessage *msg, void *user_data UNUSED) {
  191. /* printf("session_bus settings_changed invoked interface: %s member: %s\n", dbus_message_get_interface(msg), dbus_message_get_member(msg)); */
  192. if (dbus_message_is_signal(msg, DESKTOP_INTERFACE, "SettingChanged")) {
  193. const char *namespace = NULL, *key = NULL;
  194. 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)) {
  195. if (strcmp(namespace, FDO_DESKTOP_NAMESPACE) == 0) {
  196. if (strcmp(key, FDO_APPEARANCE_KEY) == 0) {
  197. on_color_scheme_change(msg);
  198. }
  199. }
  200. }
  201. }
  202. return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
  203. }
  204. void
  205. glfw_initialize_desktop_settings(void) {
  206. get_cursor_theme_from_env();
  207. DBusConnection *session_bus = glfw_dbus_session_bus();
  208. if (session_bus) {
  209. if (!read_desktop_settings(session_bus)) _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to read desktop settings, make sure you have the desktop portal running.");
  210. dbus_bus_add_match(session_bus, "type='signal',interface='" DESKTOP_INTERFACE "',member='SettingChanged'", NULL);
  211. dbus_connection_add_filter(session_bus, setting_changed, NULL, NULL);
  212. }
  213. }