linux_notify.c 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. /*
  2. * linux_notify.c
  3. * Copyright (C) 2019 Kovid Goyal <kovid at kovidgoyal.net>
  4. *
  5. * Distributed under terms of the GPL3 license.
  6. */
  7. #define _POSIX_C_SOURCE 200809L
  8. #include "internal.h"
  9. #include "linux_notify.h"
  10. #include <stdlib.h>
  11. #include <string.h>
  12. #define NOTIFICATIONS_SERVICE "org.freedesktop.Notifications"
  13. #define NOTIFICATIONS_PATH "/org/freedesktop/Notifications"
  14. #define NOTIFICATIONS_IFACE "org.freedesktop.Notifications"
  15. static inline void cleanup_free(void *p) { free(*(void**)p); }
  16. #define RAII_ALLOC(type, name, initializer) __attribute__((cleanup(cleanup_free))) type *name = initializer
  17. typedef struct {
  18. notification_id_type next_id;
  19. GLFWDBusnotificationcreatedfun callback;
  20. void *data;
  21. } NotificationCreatedData;
  22. static GLFWDBusnotificationactivatedfun activated_handler = NULL;
  23. void
  24. glfw_dbus_set_user_notification_activated_handler(GLFWDBusnotificationactivatedfun handler) {
  25. activated_handler = handler;
  26. }
  27. void
  28. notification_created(DBusMessage *msg, const char* errmsg, void *data) {
  29. if (errmsg) {
  30. _glfwInputError(GLFW_PLATFORM_ERROR, "Notify: Failed to create notification error: %s", errmsg);
  31. if (data) free(data);
  32. return;
  33. }
  34. uint32_t id;
  35. if (!glfw_dbus_get_args(msg, "Failed to get Notification uid", DBUS_TYPE_UINT32, &id, DBUS_TYPE_INVALID)) return;
  36. NotificationCreatedData *ncd = (NotificationCreatedData*)data;
  37. if (ncd) {
  38. if (ncd->callback) ncd->callback(ncd->next_id, id, ncd->data);
  39. free(ncd);
  40. }
  41. }
  42. static DBusHandlerResult
  43. message_handler(DBusConnection *conn UNUSED, DBusMessage *msg, void *user_data UNUSED) {
  44. /* printf("session_bus message_handler invoked interface: %s member: %s\n", dbus_message_get_interface(msg), dbus_message_get_member(msg)); */
  45. if (dbus_message_is_signal(msg, NOTIFICATIONS_IFACE, "ActionInvoked")) {
  46. uint32_t id;
  47. const char *action = NULL;
  48. if (glfw_dbus_get_args(msg, "Failed to get args from ActionInvoked notification signal",
  49. DBUS_TYPE_UINT32, &id, DBUS_TYPE_STRING, &action, DBUS_TYPE_INVALID)) {
  50. if (activated_handler) {
  51. activated_handler(id, 2, action);
  52. return DBUS_HANDLER_RESULT_HANDLED;
  53. }
  54. }
  55. }
  56. if (dbus_message_is_signal(msg, NOTIFICATIONS_IFACE, "ActivationToken")) {
  57. uint32_t id;
  58. const char *token = NULL;
  59. if (glfw_dbus_get_args(msg, "Failed to get args from ActivationToken notification signal",
  60. DBUS_TYPE_UINT32, &id, DBUS_TYPE_STRING, &token, DBUS_TYPE_INVALID)) {
  61. if (activated_handler) {
  62. activated_handler(id, 1, token);
  63. return DBUS_HANDLER_RESULT_HANDLED;
  64. }
  65. }
  66. }
  67. if (dbus_message_is_signal(msg, NOTIFICATIONS_IFACE, "NotificationClosed")) {
  68. uint32_t id;
  69. if (glfw_dbus_get_args(msg, "Failed to get args from NotificationClosed notification signal",
  70. DBUS_TYPE_UINT32, &id, DBUS_TYPE_INVALID)) {
  71. if (activated_handler) {
  72. activated_handler(id, 0, "");
  73. return DBUS_HANDLER_RESULT_HANDLED;
  74. }
  75. }
  76. }
  77. return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
  78. }
  79. static bool
  80. cancel_user_notification(DBusConnection *session_bus, uint32_t *id) {
  81. return glfw_dbus_call_method_no_reply(session_bus, NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE, "CloseNotification", DBUS_TYPE_UINT32, id, DBUS_TYPE_INVALID);
  82. }
  83. static void
  84. got_capabilities(DBusMessage *msg, const char* err, void* data UNUSED) {
  85. if (err) {
  86. _glfwInputError(GLFW_PLATFORM_ERROR, "Notify: Failed to get server capabilities error: %s", err);
  87. return;
  88. }
  89. #define check_call(func, err, ...) if (!func(__VA_ARGS__)) { _glfwInputError(GLFW_PLATFORM_ERROR, "Notify: GetCapabilities: %s", err); return; }
  90. DBusMessageIter iter, array_iter;
  91. check_call(dbus_message_iter_init, "message has no parameters", msg, &iter);
  92. if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRING) {
  93. _glfwInputError(GLFW_PLATFORM_ERROR, "Notify: GetCapabilities: %s", "reply is not an array of strings");
  94. return;
  95. }
  96. dbus_message_iter_recurse(&iter, &array_iter);
  97. char buf[2048] = {0}, *p = buf, *end = buf + sizeof(buf);
  98. while (dbus_message_iter_get_arg_type(&array_iter) == DBUS_TYPE_STRING) {
  99. const char *str;
  100. dbus_message_iter_get_basic(&array_iter, &str);
  101. size_t len = strlen(str);
  102. if (len && p + len + 2 < end) { p = stpcpy(p, str); *(p++) = '\n'; }
  103. dbus_message_iter_next(&array_iter);
  104. }
  105. if (activated_handler) activated_handler(0, -1, buf);
  106. #undef check_call
  107. }
  108. static bool
  109. get_capabilities(DBusConnection *session_bus) {
  110. return glfw_dbus_call_method_with_reply(session_bus, NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE, "GetCapabilities", 60, got_capabilities, NULL, DBUS_TYPE_INVALID);
  111. }
  112. notification_id_type
  113. glfw_dbus_send_user_notification(const GLFWDBUSNotificationData *n, GLFWDBusnotificationcreatedfun callback, void *user_data) {
  114. DBusConnection *session_bus = glfw_dbus_session_bus();
  115. if (!session_bus) return 0;
  116. if (n->timeout == -9999 && n->urgency == 255) return cancel_user_notification(session_bus, user_data) ? 1 : 0;
  117. if (n->timeout == -99999 && n->urgency == 255) return get_capabilities(session_bus) ? 1 : 0;
  118. static DBusConnection *added_signal_match = NULL;
  119. if (added_signal_match != session_bus) {
  120. dbus_bus_add_match(session_bus, "type='signal',interface='" NOTIFICATIONS_IFACE "',member='ActionInvoked'", NULL);
  121. dbus_bus_add_match(session_bus, "type='signal',interface='" NOTIFICATIONS_IFACE "',member='NotificationClosed'", NULL);
  122. dbus_bus_add_match(session_bus, "type='signal',interface='" NOTIFICATIONS_IFACE "',member='ActivationToken'", NULL);
  123. dbus_connection_add_filter(session_bus, message_handler, NULL, NULL);
  124. added_signal_match = session_bus;
  125. }
  126. RAII_ALLOC(NotificationCreatedData, data, malloc(sizeof(NotificationCreatedData)));
  127. if (!data) return 0;
  128. static notification_id_type notification_id = 0;
  129. data->next_id = ++notification_id;
  130. data->callback = callback; data->data = user_data;
  131. if (!data->next_id) data->next_id = ++notification_id;
  132. RAII_MSG(msg, dbus_message_new_method_call(NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE, "Notify"));
  133. if (!msg) { return 0; }
  134. DBusMessageIter args, array, variant, dict;
  135. dbus_message_iter_init_append(msg, &args);
  136. #define check_call(func, ...) if (!func(__VA_ARGS__)) { _glfwInputError(GLFW_PLATFORM_ERROR, "%s", "Out of memory allocating DBUS message for notification\n"); return 0; }
  137. #define APPEND(to, type, val) check_call(dbus_message_iter_append_basic, &to, type, &val);
  138. APPEND(args, DBUS_TYPE_STRING, n->app_name)
  139. APPEND(args, DBUS_TYPE_UINT32, n->replaces)
  140. APPEND(args, DBUS_TYPE_STRING, n->icon)
  141. APPEND(args, DBUS_TYPE_STRING, n->summary)
  142. APPEND(args, DBUS_TYPE_STRING, n->body)
  143. check_call(dbus_message_iter_open_container, &args, DBUS_TYPE_ARRAY, "s", &array);
  144. if (n->actions) {
  145. for (size_t i = 0; i < n->num_actions; i++) {
  146. APPEND(array, DBUS_TYPE_STRING, n->actions[i]);
  147. }
  148. }
  149. check_call(dbus_message_iter_close_container, &args, &array);
  150. check_call(dbus_message_iter_open_container, &args, DBUS_TYPE_ARRAY, "{sv}", &array);
  151. #define append_sv_dictionary_entry(k, val_type, val) { \
  152. check_call(dbus_message_iter_open_container, &array, DBUS_TYPE_DICT_ENTRY, NULL, &dict); \
  153. static const char *key = k; \
  154. APPEND(dict, DBUS_TYPE_STRING, key); \
  155. check_call(dbus_message_iter_open_container, &dict, DBUS_TYPE_VARIANT, val_type##_AS_STRING, &variant); \
  156. APPEND(variant, val_type, val); \
  157. check_call(dbus_message_iter_close_container, &dict, &variant); \
  158. check_call(dbus_message_iter_close_container, &array, &dict); \
  159. }
  160. append_sv_dictionary_entry("urgency", DBUS_TYPE_BYTE, n->urgency);
  161. if (n->category && n->category[0]) append_sv_dictionary_entry("category", DBUS_TYPE_STRING, n->category);
  162. if (n->muted) append_sv_dictionary_entry("suppress-sound", DBUS_TYPE_BOOLEAN, n->muted);
  163. check_call(dbus_message_iter_close_container, &args, &array);
  164. APPEND(args, DBUS_TYPE_INT32, n->timeout)
  165. #undef check_call
  166. #undef APPEND
  167. if (!call_method_with_msg(session_bus, msg, 5000, notification_created, data, false)) return 0;
  168. notification_id_type ans = data->next_id;
  169. data = NULL;
  170. return ans;
  171. }