libnotify_notification.cc 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. // Copyright (c) 2015 GitHub, Inc.
  2. // Use of this source code is governed by the MIT license that can be
  3. // found in the LICENSE file.
  4. #include "shell/browser/notifications/linux/libnotify_notification.h"
  5. #include <string>
  6. #include "base/containers/flat_set.h"
  7. #include "base/files/file_enumerator.h"
  8. #include "base/functional/bind.h"
  9. #include "base/logging.h"
  10. #include "base/process/process_handle.h"
  11. #include "base/strings/utf_string_conversions.h"
  12. #include "shell/browser/notifications/notification_delegate.h"
  13. #include "shell/browser/ui/gtk_util.h"
  14. #include "shell/common/application_info.h"
  15. #include "shell/common/platform_util.h"
  16. #include "third_party/skia/include/core/SkBitmap.h"
  17. #include "ui/gtk/gtk_util.h" // nogncheck
  18. namespace electron {
  19. namespace {
  20. LibNotifyLoader libnotify_loader_;
  21. const base::flat_set<std::string>& GetServerCapabilities() {
  22. static base::flat_set<std::string> caps;
  23. if (caps.empty()) {
  24. auto* capabilities = libnotify_loader_.notify_get_server_caps();
  25. for (auto* l = capabilities; l != nullptr; l = l->next)
  26. caps.insert(static_cast<const char*>(l->data));
  27. g_list_free_full(capabilities, g_free);
  28. }
  29. return caps;
  30. }
  31. bool HasCapability(const std::string& capability) {
  32. return GetServerCapabilities().contains(capability);
  33. }
  34. bool NotifierSupportsActions() {
  35. if (getenv("ELECTRON_USE_UBUNTU_NOTIFIER"))
  36. return false;
  37. return HasCapability("actions");
  38. }
  39. void log_and_clear_error(GError* error, const char* context) {
  40. LOG(ERROR) << context << ": domain=" << error->domain
  41. << " code=" << error->code << " message=\"" << error->message
  42. << '"';
  43. g_error_free(error);
  44. }
  45. } // namespace
  46. // static
  47. bool LibnotifyNotification::Initialize() {
  48. if (!libnotify_loader_.Load("libnotify.so.4") && // most common one
  49. !libnotify_loader_.Load("libnotify.so.5") &&
  50. !libnotify_loader_.Load("libnotify.so.1") &&
  51. !libnotify_loader_.Load("libnotify.so")) {
  52. LOG(WARNING) << "Unable to find libnotify; notifications disabled";
  53. return false;
  54. }
  55. if (!libnotify_loader_.notify_is_initted() &&
  56. !libnotify_loader_.notify_init(GetApplicationName().c_str())) {
  57. LOG(WARNING) << "Unable to initialize libnotify; notifications disabled";
  58. return false;
  59. }
  60. return true;
  61. }
  62. LibnotifyNotification::LibnotifyNotification(NotificationDelegate* delegate,
  63. NotificationPresenter* presenter)
  64. : Notification(delegate, presenter) {}
  65. LibnotifyNotification::~LibnotifyNotification() {
  66. if (notification_) {
  67. g_signal_handlers_disconnect_by_data(notification_, this);
  68. g_object_unref(notification_);
  69. }
  70. }
  71. void LibnotifyNotification::Show(const NotificationOptions& options) {
  72. notification_ = libnotify_loader_.notify_notification_new(
  73. base::UTF16ToUTF8(options.title).c_str(),
  74. base::UTF16ToUTF8(options.msg).c_str(), nullptr);
  75. signal_ = ScopedGSignal(
  76. notification_, "closed",
  77. base::BindRepeating(&LibnotifyNotification::OnNotificationClosed,
  78. base::Unretained(this)));
  79. // NB: On Unity and on any other DE using Notify-OSD, adding a notification
  80. // action will cause the notification to display as a modal dialog box.
  81. if (NotifierSupportsActions()) {
  82. libnotify_loader_.notify_notification_add_action(
  83. notification_, "default", "View", OnNotificationView, this, nullptr);
  84. }
  85. NotifyUrgency urgency = NOTIFY_URGENCY_NORMAL;
  86. if (options.urgency == u"critical") {
  87. urgency = NOTIFY_URGENCY_CRITICAL;
  88. } else if (options.urgency == u"low") {
  89. urgency = NOTIFY_URGENCY_LOW;
  90. }
  91. // Set the urgency level of the notification.
  92. libnotify_loader_.notify_notification_set_urgency(notification_, urgency);
  93. if (!options.icon.drawsNothing()) {
  94. GdkPixbuf* pixbuf = gtk_util::GdkPixbufFromSkBitmap(options.icon);
  95. libnotify_loader_.notify_notification_set_image_from_pixbuf(notification_,
  96. pixbuf);
  97. g_object_unref(pixbuf);
  98. }
  99. // Set the timeout duration for the notification
  100. bool neverTimeout = options.timeout_type == u"never";
  101. int timeout = (neverTimeout) ? NOTIFY_EXPIRES_NEVER : NOTIFY_EXPIRES_DEFAULT;
  102. libnotify_loader_.notify_notification_set_timeout(notification_, timeout);
  103. if (!options.tag.empty()) {
  104. GQuark id = g_quark_from_string(options.tag.c_str());
  105. g_object_set(G_OBJECT(notification_), "id", id, nullptr);
  106. }
  107. // Always try to append notifications.
  108. // Unique tags can be used to prevent this.
  109. if (HasCapability("append")) {
  110. libnotify_loader_.notify_notification_set_hint(
  111. notification_, "append", g_variant_new_string("true"));
  112. } else if (HasCapability("x-canonical-append")) {
  113. libnotify_loader_.notify_notification_set_hint(
  114. notification_, "x-canonical-append", g_variant_new_string("true"));
  115. }
  116. // Send the desktop name to identify the application
  117. // The desktop-entry is the part before the .desktop
  118. std::string desktop_id = platform_util::GetXdgAppId();
  119. if (!desktop_id.empty()) {
  120. libnotify_loader_.notify_notification_set_hint(
  121. notification_, "desktop-entry",
  122. g_variant_new_string(desktop_id.c_str()));
  123. }
  124. libnotify_loader_.notify_notification_set_hint(
  125. notification_, "sender-pid",
  126. g_variant_new_int64(base::GetCurrentProcId()));
  127. GError* error = nullptr;
  128. libnotify_loader_.notify_notification_show(notification_, &error);
  129. if (error) {
  130. log_and_clear_error(error, "notify_notification_show");
  131. NotificationFailed();
  132. return;
  133. }
  134. if (delegate())
  135. delegate()->NotificationDisplayed();
  136. }
  137. void LibnotifyNotification::Dismiss() {
  138. if (!notification_) {
  139. return;
  140. }
  141. GError* error = nullptr;
  142. on_dismissing_ = true;
  143. libnotify_loader_.notify_notification_close(notification_, &error);
  144. if (error) {
  145. log_and_clear_error(error, "notify_notification_close");
  146. }
  147. on_dismissing_ = false;
  148. }
  149. void LibnotifyNotification::OnNotificationClosed(
  150. NotifyNotification* notification) {
  151. NotificationDismissed(!on_dismissing_);
  152. }
  153. void LibnotifyNotification::OnNotificationView(NotifyNotification* notification,
  154. char* action,
  155. gpointer user_data) {
  156. LibnotifyNotification* that = static_cast<LibnotifyNotification*>(user_data);
  157. DCHECK(that);
  158. that->NotificationClicked();
  159. }
  160. } // namespace electron