linux_notify.c 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  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. static notification_id_type notification_id = 0;
  16. typedef struct {
  17. notification_id_type next_id;
  18. GLFWDBusnotificationcreatedfun callback;
  19. void *data;
  20. } NotificationCreatedData;
  21. static GLFWDBusnotificationactivatedfun activated_handler = NULL;
  22. void
  23. glfw_dbus_set_user_notification_activated_handler(GLFWDBusnotificationactivatedfun handler) {
  24. activated_handler = handler;
  25. }
  26. void
  27. notification_created(DBusMessage *msg, const char* errmsg, void *data) {
  28. if (errmsg) {
  29. _glfwInputError(GLFW_PLATFORM_ERROR, "Notify: Failed to create notification error: %s", errmsg);
  30. if (data) free(data);
  31. return;
  32. }
  33. uint32_t id;
  34. if (!glfw_dbus_get_args(msg, "Failed to get Notification uid", DBUS_TYPE_UINT32, &id, DBUS_TYPE_INVALID)) return;
  35. NotificationCreatedData *ncd = (NotificationCreatedData*)data;
  36. if (ncd->callback) ncd->callback(ncd->next_id, id, ncd->data);
  37. if (data) free(data);
  38. }
  39. static DBusHandlerResult
  40. message_handler(DBusConnection *conn UNUSED, DBusMessage *msg, void *user_data UNUSED) {
  41. /* printf("session_bus message_handler invoked interface: %s member: %s\n", dbus_message_get_interface(msg), dbus_message_get_member(msg)); */
  42. if (dbus_message_is_signal(msg, NOTIFICATIONS_IFACE, "ActionInvoked")) {
  43. uint32_t id;
  44. const char *action;
  45. if (glfw_dbus_get_args(msg, "Failed to get args from ActionInvoked notification signal",
  46. DBUS_TYPE_UINT32, &id, DBUS_TYPE_STRING, &action, DBUS_TYPE_INVALID)) {
  47. if (activated_handler) {
  48. activated_handler(id, action);
  49. return DBUS_HANDLER_RESULT_HANDLED;
  50. }
  51. }
  52. }
  53. return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
  54. }
  55. notification_id_type
  56. glfw_dbus_send_user_notification(const char *app_name, const char* icon, const char *summary, const char *body, const char* action_name, int32_t timeout, int urgency, GLFWDBusnotificationcreatedfun callback, void *user_data) {
  57. DBusConnection *session_bus = glfw_dbus_session_bus();
  58. static DBusConnection *added_signal_match = NULL;
  59. if (!session_bus) return 0;
  60. if (added_signal_match != session_bus) {
  61. dbus_bus_add_match(session_bus, "type='signal',interface='" NOTIFICATIONS_IFACE "',member='ActionInvoked'", NULL);
  62. dbus_connection_add_filter(session_bus, message_handler, NULL, NULL);
  63. added_signal_match = session_bus;
  64. }
  65. RAII_ALLOC(NotificationCreatedData, data, malloc(sizeof(NotificationCreatedData)));
  66. if (!data) return 0;
  67. data->next_id = ++notification_id;
  68. data->callback = callback; data->data = user_data;
  69. if (!data->next_id) data->next_id = ++notification_id;
  70. uint32_t replaces_id = 0;
  71. RAII_MSG(msg, dbus_message_new_method_call(NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE, "Notify"));
  72. if (!msg) { return 0; }
  73. DBusMessageIter args, array, variant, dict;
  74. dbus_message_iter_init_append(msg, &args);
  75. #define check_call(func, ...) if (!func(__VA_ARGS__)) { _glfwInputError(GLFW_PLATFORM_ERROR, "%s", "Out of memory allocating DBUS message for notification\n"); return 0; }
  76. #define APPEND(to, type, val) check_call(dbus_message_iter_append_basic, &to, type, &val);
  77. APPEND(args, DBUS_TYPE_STRING, app_name)
  78. APPEND(args, DBUS_TYPE_UINT32, replaces_id)
  79. APPEND(args, DBUS_TYPE_STRING, icon)
  80. APPEND(args, DBUS_TYPE_STRING, summary)
  81. APPEND(args, DBUS_TYPE_STRING, body)
  82. check_call(dbus_message_iter_open_container, &args, DBUS_TYPE_ARRAY, "s", &array);
  83. if (action_name) {
  84. static const char* default_action = "default";
  85. APPEND(array, DBUS_TYPE_STRING, default_action);
  86. APPEND(array, DBUS_TYPE_STRING, action_name);
  87. }
  88. check_call(dbus_message_iter_close_container, &args, &array);
  89. check_call(dbus_message_iter_open_container, &args, DBUS_TYPE_ARRAY, "{sv}", &array);
  90. check_call(dbus_message_iter_open_container, &array, DBUS_TYPE_DICT_ENTRY, NULL, &dict);
  91. static const char* urgency_key = "urgency";
  92. APPEND(dict, DBUS_TYPE_STRING, urgency_key);
  93. check_call(dbus_message_iter_open_container, &dict, DBUS_TYPE_VARIANT, DBUS_TYPE_BYTE_AS_STRING, &variant);
  94. uint8_t urgencyb = urgency & 3;
  95. APPEND(variant, DBUS_TYPE_BYTE, urgencyb);
  96. check_call(dbus_message_iter_close_container, &dict, &variant);
  97. check_call(dbus_message_iter_close_container, &array, &dict);
  98. check_call(dbus_message_iter_close_container, &args, &array);
  99. APPEND(args, DBUS_TYPE_INT32, timeout)
  100. #undef check_call
  101. #undef APPEND
  102. if (!call_method_with_msg(session_bus, msg, 5000, notification_created, data)) return 0;
  103. notification_id_type ans = data->next_id;
  104. data = NULL;
  105. return ans;
  106. }