1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsAlertsIconListener.h"
7 #include "imgIContainer.h"
8 #include "imgIRequest.h"
10 #include "nsServiceManagerUtils.h"
11 #include "nsSystemAlertsService.h"
12 #include "nsIAlertsService.h"
13 #include "nsICancelable.h"
14 #include "nsImageToPixbuf.h"
15 #include "nsIStringBundle.h"
16 #include "nsIObserverService.h"
18 #include "mozilla/XREAppData.h"
23 extern const mozilla::StaticXREAppData
* gAppData
;
25 static bool gHasActions
= false;
26 static bool gHasCaps
= false;
28 void* nsAlertsIconListener::libNotifyHandle
= nullptr;
29 bool nsAlertsIconListener::libNotifyNotAvail
= false;
30 nsAlertsIconListener::notify_is_initted_t
31 nsAlertsIconListener::notify_is_initted
= nullptr;
32 nsAlertsIconListener::notify_init_t
nsAlertsIconListener::notify_init
= nullptr;
33 nsAlertsIconListener::notify_get_server_caps_t
34 nsAlertsIconListener::notify_get_server_caps
= nullptr;
35 nsAlertsIconListener::notify_notification_new_t
36 nsAlertsIconListener::notify_notification_new
= nullptr;
37 nsAlertsIconListener::notify_notification_show_t
38 nsAlertsIconListener::notify_notification_show
= nullptr;
39 nsAlertsIconListener::notify_notification_set_icon_from_pixbuf_t
40 nsAlertsIconListener::notify_notification_set_icon_from_pixbuf
= nullptr;
41 nsAlertsIconListener::notify_notification_add_action_t
42 nsAlertsIconListener::notify_notification_add_action
= nullptr;
43 nsAlertsIconListener::notify_notification_close_t
44 nsAlertsIconListener::notify_notification_close
= nullptr;
45 nsAlertsIconListener::notify_notification_set_hint_t
46 nsAlertsIconListener::notify_notification_set_hint
= nullptr;
48 static void notify_action_cb(NotifyNotification
* notification
, gchar
* action
,
50 nsAlertsIconListener
* alert
= static_cast<nsAlertsIconListener
*>(user_data
);
51 alert
->SendCallback();
54 static void notify_closed_marshal(GClosure
* closure
, GValue
* return_value
,
56 const GValue
* param_values
,
57 gpointer invocation_hint
,
58 gpointer marshal_data
) {
59 MOZ_ASSERT(n_param_values
>= 1, "No object in params");
61 nsAlertsIconListener
* alert
=
62 static_cast<nsAlertsIconListener
*>(closure
->data
);
67 static GdkPixbuf
* GetPixbufFromImgRequest(imgIRequest
* aRequest
) {
68 nsCOMPtr
<imgIContainer
> image
;
69 nsresult rv
= aRequest
->GetImage(getter_AddRefs(image
));
74 int32_t width
= 0, height
= 0;
75 const int32_t kBytesPerPixel
= 4;
76 // DBUS_MAXIMUM_ARRAY_LENGTH is 64M, there is 60 bytes overhead
77 // for the hints array with only the image payload, 256 is used to give
78 // some breathing room.
79 const int32_t kMaxImageBytes
= 64 * 1024 * 1024 - 256;
80 image
->GetWidth(&width
);
81 image
->GetHeight(&height
);
82 if (width
* height
* kBytesPerPixel
> kMaxImageBytes
) {
83 // The image won't fit in a dbus array
87 return nsImageToPixbuf::ImageToPixbuf(image
);
90 NS_IMPL_ISUPPORTS(nsAlertsIconListener
, nsIAlertNotificationImageListener
,
91 nsIObserver
, nsISupportsWeakReference
)
93 nsAlertsIconListener::nsAlertsIconListener(nsSystemAlertsService
* aBackend
,
94 const nsAString
& aAlertName
)
95 : mAlertName(aAlertName
), mBackend(aBackend
), mNotification(nullptr) {
96 if (!libNotifyHandle
&& !libNotifyNotAvail
) {
97 libNotifyHandle
= dlopen("libnotify.so.4", RTLD_LAZY
);
98 if (!libNotifyHandle
) {
99 libNotifyHandle
= dlopen("libnotify.so.1", RTLD_LAZY
);
100 if (!libNotifyHandle
) {
101 libNotifyNotAvail
= true;
107 (notify_is_initted_t
)dlsym(libNotifyHandle
, "notify_is_initted");
108 notify_init
= (notify_init_t
)dlsym(libNotifyHandle
, "notify_init");
109 notify_get_server_caps
= (notify_get_server_caps_t
)dlsym(
110 libNotifyHandle
, "notify_get_server_caps");
111 notify_notification_new
= (notify_notification_new_t
)dlsym(
112 libNotifyHandle
, "notify_notification_new");
113 notify_notification_show
= (notify_notification_show_t
)dlsym(
114 libNotifyHandle
, "notify_notification_show");
115 notify_notification_set_icon_from_pixbuf
=
116 (notify_notification_set_icon_from_pixbuf_t
)dlsym(
117 libNotifyHandle
, "notify_notification_set_icon_from_pixbuf");
118 notify_notification_add_action
= (notify_notification_add_action_t
)dlsym(
119 libNotifyHandle
, "notify_notification_add_action");
120 notify_notification_close
= (notify_notification_close_t
)dlsym(
121 libNotifyHandle
, "notify_notification_close");
122 notify_notification_set_hint
= (notify_notification_set_hint_t
)dlsym(
123 libNotifyHandle
, "notify_notification_set_hint");
124 if (!notify_is_initted
|| !notify_init
|| !notify_get_server_caps
||
125 !notify_notification_new
|| !notify_notification_show
||
126 !notify_notification_set_icon_from_pixbuf
||
127 !notify_notification_add_action
|| !notify_notification_close
) {
128 dlclose(libNotifyHandle
);
129 libNotifyHandle
= nullptr;
134 nsAlertsIconListener::~nsAlertsIconListener() {
135 mBackend
->RemoveListener(mAlertName
, this);
136 // Don't dlclose libnotify as it uses atexit().
140 nsAlertsIconListener::OnImageMissing(nsISupports
*) {
141 // This notification doesn't have an image, or there was an error getting
142 // the image. Show the notification without an icon.
143 return ShowAlert(nullptr);
147 nsAlertsIconListener::OnImageReady(nsISupports
*, imgIRequest
* aRequest
) {
148 GdkPixbuf
* imagePixbuf
= GetPixbufFromImgRequest(aRequest
);
152 ShowAlert(imagePixbuf
);
153 g_object_unref(imagePixbuf
);
159 nsresult
nsAlertsIconListener::ShowAlert(GdkPixbuf
* aPixbuf
) {
160 if (!mBackend
->IsActiveListener(mAlertName
, this)) return NS_OK
;
162 mNotification
= notify_notification_new(mAlertTitle
.get(), mAlertText
.get(),
165 if (!mNotification
) return NS_ERROR_OUT_OF_MEMORY
;
167 nsCOMPtr
<nsIObserverService
> obsServ
=
168 do_GetService("@mozilla.org/observer-service;1");
169 if (obsServ
) obsServ
->AddObserver(this, "quit-application", true);
171 if (aPixbuf
) notify_notification_set_icon_from_pixbuf(mNotification
, aPixbuf
);
174 if (mAlertHasAction
) {
175 // What we put as the label doesn't matter here, if the action
176 // string is "default" then that makes the entire bubble clickable
177 // rather than creating a button.
178 notify_notification_add_action(mNotification
, "default", "Activate",
179 notify_action_cb
, this, nullptr);
182 if (notify_notification_set_hint
) {
183 // If MOZ_DESKTOP_FILE_NAME variable is set, use it as the application id,
184 // otherwise use gAppData->name
185 if (getenv("MOZ_DESKTOP_FILE_NAME")) {
186 // Send the desktop name to identify the application
187 // The desktop-entry is the part before the .desktop
188 notify_notification_set_hint(
189 mNotification
, "desktop-entry",
190 g_variant_new("s", getenv("MOZ_DESKTOP_FILE_NAME")));
192 notify_notification_set_hint(mNotification
, "desktop-entry",
193 g_variant_new("s", gAppData
->remotingName
));
197 // Fedora 10 calls NotifyNotification "closed" signal handlers with a
198 // different signature, so a marshaller is used instead of a C callback to
199 // get the user_data (this) in a parseable format. |closure| is created
200 // with a floating reference, which gets sunk by g_signal_connect_closure().
201 GClosure
* closure
= g_closure_new_simple(sizeof(GClosure
), this);
202 g_closure_set_marshal(closure
, notify_closed_marshal
);
204 g_signal_connect_closure(mNotification
, "closed", closure
, FALSE
);
205 GError
* error
= nullptr;
206 if (!notify_notification_show(mNotification
, &error
)) {
207 NS_WARNING(error
->message
);
209 return NS_ERROR_FAILURE
;
213 mAlertListener
->Observe(nullptr, "alertshow", mAlertCookie
.get());
218 void nsAlertsIconListener::SendCallback() {
220 mAlertListener
->Observe(nullptr, "alertclickcallback", mAlertCookie
.get());
223 void nsAlertsIconListener::SendClosed() {
225 g_object_unref(mNotification
);
226 mNotification
= nullptr;
232 nsAlertsIconListener::Observe(nsISupports
* aSubject
, const char* aTopic
,
233 const char16_t
* aData
) {
234 // We need to close any open notifications upon application exit, otherwise
235 // we will leak since libnotify holds a ref for us.
236 if (!nsCRT::strcmp(aTopic
, "quit-application") && mNotification
) {
237 g_signal_handler_disconnect(mNotification
, mClosureHandler
);
238 g_object_unref(mNotification
);
239 mNotification
= nullptr;
240 Release(); // equivalent to NS_RELEASE(this)
245 nsresult
nsAlertsIconListener::Close() {
247 mIconRequest
->Cancel(NS_BINDING_ABORTED
);
248 mIconRequest
= nullptr;
251 if (!mNotification
) {
256 GError
* error
= nullptr;
257 if (!notify_notification_close(mNotification
, &error
)) {
258 NS_WARNING(error
->message
);
260 return NS_ERROR_FAILURE
;
266 nsresult
nsAlertsIconListener::InitAlertAsync(nsIAlertNotification
* aAlert
,
267 nsIObserver
* aAlertListener
) {
268 if (!libNotifyHandle
) return NS_ERROR_FAILURE
;
270 if (!notify_is_initted()) {
271 // Give the name of this application to libnotify
272 nsCOMPtr
<nsIStringBundleService
> bundleService
=
273 do_GetService(NS_STRINGBUNDLE_CONTRACTID
);
275 nsAutoCString appShortName
;
277 nsCOMPtr
<nsIStringBundle
> bundle
;
278 bundleService
->CreateBundle("chrome://branding/locale/brand.properties",
279 getter_AddRefs(bundle
));
280 nsAutoString appName
;
283 bundle
->GetStringFromName("brandShortName", appName
);
284 CopyUTF16toUTF8(appName
, appShortName
);
287 "brand.properties not present, using default application name");
288 appShortName
.AssignLiteral("Mozilla");
291 appShortName
.AssignLiteral("Mozilla");
294 if (!notify_init(appShortName
.get())) return NS_ERROR_FAILURE
;
296 GList
* server_caps
= notify_get_server_caps();
299 for (GList
* cap
= server_caps
; cap
!= nullptr; cap
= cap
->next
) {
300 if (!strcmp((char*)cap
->data
, "actions")) {
305 g_list_foreach(server_caps
, (GFunc
)g_free
, nullptr);
306 g_list_free(server_caps
);
311 // if notify_get_server_caps() failed above we need to assume
312 // there is no notification-server to display anything
313 return NS_ERROR_FAILURE
;
316 nsresult rv
= aAlert
->GetTextClickable(&mAlertHasAction
);
317 NS_ENSURE_SUCCESS(rv
, rv
);
318 if (!gHasActions
&& mAlertHasAction
)
319 return NS_ERROR_FAILURE
; // No good, fallback to XUL
322 rv
= aAlert
->GetTitle(title
);
323 NS_ENSURE_SUCCESS(rv
, rv
);
324 // Workaround for a libnotify bug - blank titles aren't dealt with
325 // properly so we use a space
326 if (title
.IsEmpty()) {
327 mAlertTitle
= " "_ns
;
329 CopyUTF16toUTF8(title
, mAlertTitle
);
333 rv
= aAlert
->GetText(text
);
334 NS_ENSURE_SUCCESS(rv
, rv
);
335 CopyUTF16toUTF8(text
, mAlertText
);
337 mAlertListener
= aAlertListener
;
339 rv
= aAlert
->GetCookie(mAlertCookie
);
340 NS_ENSURE_SUCCESS(rv
, rv
);
342 return aAlert
->LoadImage(/* aTimeout = */ 0, this, /* aUserData = */ nullptr,
343 getter_AddRefs(mIconRequest
));
346 void nsAlertsIconListener::NotifyFinished() {
348 mAlertListener
->Observe(nullptr, "alertfinished", mAlertCookie
.get());