linux_notify.c 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  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. #include "internal.h"
  8. #include "linux_notify.h"
  9. #include <stdlib.h>
  10. #define NOTIFICATIONS_SERVICE "org.freedesktop.Notifications"
  11. #define NOTIFICATIONS_PATH "/org/freedesktop/Notifications"
  12. #define NOTIFICATIONS_IFACE "org.freedesktop.Notifications"
  13. static inline void cleanup_free(void *p) { free(*(void**)p); }
  14. #define RAII_ALLOC(type, name, initializer) __attribute__((cleanup(cleanup_free))) type *name = initializer
  15. typedef struct {
  16. notification_id_type next_id;
  17. GLFWDBusnotificationcreatedfun callback;
  18. void *data;
  19. } NotificationCreatedData;
  20. static GLFWDBusnotificationactivatedfun activated_handler = NULL;
  21. void
  22. glfw_dbus_set_user_notification_activated_handler(GLFWDBusnotificationactivatedfun handler) {
  23. activated_handler = handler;
  24. }
  25. void
  26. notification_created(DBusMessage *msg, const char* errmsg, void *data) {
  27. if (errmsg) {
  28. _glfwInputError(GLFW_PLATFORM_ERROR, "Notify: Failed to create notification error: %s", errmsg);
  29. if (data) free(data);
  30. return;
  31. }
  32. uint32_t id;
  33. if (!glfw_dbus_get_args(msg, "Failed to get Notification uid", DBUS_TYPE_UINT32, &id, DBUS_TYPE_INVALID)) return;
  34. NotificationCreatedData *ncd = (NotificationCreatedData*)data;
  35. if (ncd) {
  36. if (ncd->callback) ncd->callback(ncd->next_id, id, ncd->data);
  37. free(ncd);
  38. }
  39. }
  40. static DBusHandlerResult
  41. message_handler(DBusConnection *conn UNUSED, DBusMessage *msg, void *user_data UNUSED) {
  42. /* printf("session_bus message_handler invoked interface: %s member: %s\n", dbus_message_get_interface(msg), dbus_message_get_member(msg)); */
  43. if (dbus_message_is_signal(msg, NOTIFICATIONS_IFACE, "ActionInvoked")) {
  44. uint32_t id;
  45. const char *action = NULL;
  46. if (glfw_dbus_get_args(msg, "Failed to get args from ActionInvoked notification signal",
  47. DBUS_TYPE_UINT32, &id, DBUS_TYPE_STRING, &action, DBUS_TYPE_INVALID)) {
  48. if (activated_handler) {
  49. activated_handler(id, 2, action);
  50. return DBUS_HANDLER_RESULT_HANDLED;
  51. }
  52. }
  53. }
  54. if (dbus_message_is_signal(msg, NOTIFICATIONS_IFACE, "ActivationToken")) {
  55. uint32_t id;
  56. const char *token = NULL;
  57. if (glfw_dbus_get_args(msg, "Failed to get args from ActivationToken notification signal",
  58. DBUS_TYPE_UINT32, &id, DBUS_TYPE_STRING, &token, DBUS_TYPE_INVALID)) {
  59. if (activated_handler) {
  60. activated_handler(id, 1, token);
  61. return DBUS_HANDLER_RESULT_HANDLED;
  62. }
  63. }
  64. }
  65. if (dbus_message_is_signal(msg, NOTIFICATIONS_IFACE, "NotificationClosed")) {
  66. uint32_t id;
  67. if (glfw_dbus_get_args(msg, "Failed to get args from NotificationClosed notification signal",
  68. DBUS_TYPE_UINT32, &id, DBUS_TYPE_INVALID)) {
  69. if (activated_handler) {
  70. activated_handler(id, 0, "");
  71. return DBUS_HANDLER_RESULT_HANDLED;
  72. }
  73. }
  74. }
  75. return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
  76. }
  77. static bool
  78. cancel_user_notification(DBusConnection *session_bus, uint32_t *id) {
  79. return glfw_dbus_call_method_no_reply(session_bus, NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE, "CloseNotification", DBUS_TYPE_UINT32, id, DBUS_TYPE_INVALID);
  80. }
  81. notification_id_type
  82. glfw_dbus_send_user_notification(const GLFWDBUSNotificationData *n, GLFWDBusnotificationcreatedfun callback, void *user_data) {
  83. DBusConnection *session_bus = glfw_dbus_session_bus();
  84. if (!session_bus) return 0;
  85. if (n->timeout == -9999 && n->urgency == 255) return cancel_user_notification(session_bus, user_data) ? 1 : 0;
  86. static DBusConnection *added_signal_match = NULL;
  87. if (added_signal_match != session_bus) {
  88. dbus_bus_add_match(session_bus, "type='signal',interface='" NOTIFICATIONS_IFACE "',member='ActionInvoked'", NULL);
  89. dbus_bus_add_match(session_bus, "type='signal',interface='" NOTIFICATIONS_IFACE "',member='NotificationClosed'", NULL);
  90. dbus_bus_add_match(session_bus, "type='signal',interface='" NOTIFICATIONS_IFACE "',member='ActivationToken'", NULL);
  91. dbus_connection_add_filter(session_bus, message_handler, NULL, NULL);
  92. added_signal_match = session_bus;
  93. }
  94. RAII_ALLOC(NotificationCreatedData, data, malloc(sizeof(NotificationCreatedData)));
  95. if (!data) return 0;
  96. static notification_id_type notification_id = 0;
  97. data->next_id = ++notification_id;
  98. data->callback = callback; data->data = user_data;
  99. if (!data->next_id) data->next_id = ++notification_id;
  100. RAII_MSG(msg, dbus_message_new_method_call(NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE, "Notify"));
  101. if (!msg) { return 0; }
  102. DBusMessageIter args, array, variant, dict;
  103. dbus_message_iter_init_append(msg, &args);
  104. #define check_call(func, ...) if (!func(__VA_ARGS__)) { _glfwInputError(GLFW_PLATFORM_ERROR, "%s", "Out of memory allocating DBUS message for notification\n"); return 0; }
  105. #define APPEND(to, type, val) check_call(dbus_message_iter_append_basic, &to, type, &val);
  106. APPEND(args, DBUS_TYPE_STRING, n->app_name)
  107. APPEND(args, DBUS_TYPE_UINT32, n->replaces)
  108. APPEND(args, DBUS_TYPE_STRING, n->icon)
  109. APPEND(args, DBUS_TYPE_STRING, n->summary)
  110. APPEND(args, DBUS_TYPE_STRING, n->body)
  111. check_call(dbus_message_iter_open_container, &args, DBUS_TYPE_ARRAY, "s", &array);
  112. if (n->action_name) {
  113. static const char* default_action = "default";
  114. APPEND(array, DBUS_TYPE_STRING, default_action);
  115. APPEND(array, DBUS_TYPE_STRING, n->action_name);
  116. }
  117. check_call(dbus_message_iter_close_container, &args, &array);
  118. check_call(dbus_message_iter_open_container, &args, DBUS_TYPE_ARRAY, "{sv}", &array);
  119. #define append_sv_dictionary_entry(k, val_type, val) { \
  120. check_call(dbus_message_iter_open_container, &array, DBUS_TYPE_DICT_ENTRY, NULL, &dict); \
  121. static const char *key = k; \
  122. APPEND(dict, DBUS_TYPE_STRING, key); \
  123. check_call(dbus_message_iter_open_container, &dict, DBUS_TYPE_VARIANT, val_type##_AS_STRING, &variant); \
  124. APPEND(variant, val_type, val); \
  125. check_call(dbus_message_iter_close_container, &dict, &variant); \
  126. check_call(dbus_message_iter_close_container, &array, &dict); \
  127. }
  128. append_sv_dictionary_entry("urgency", DBUS_TYPE_BYTE, n->urgency);
  129. check_call(dbus_message_iter_close_container, &args, &array);
  130. APPEND(args, DBUS_TYPE_INT32, n->timeout)
  131. #undef check_call
  132. #undef APPEND
  133. if (!call_method_with_msg(session_bus, msg, 5000, notification_created, data)) return 0;
  134. notification_id_type ans = data->next_id;
  135. data = NULL;
  136. return ans;
  137. }