Line data Source code
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/. */
5 :
6 : #include "nsAlertsIconListener.h"
7 : #include "imgIContainer.h"
8 : #include "imgIRequest.h"
9 : #include "nsNetUtil.h"
10 : #include "nsServiceManagerUtils.h"
11 : #include "nsSystemAlertsService.h"
12 : #include "nsIAlertsService.h"
13 : #include "nsICancelable.h"
14 : #include "nsIImageToPixbuf.h"
15 : #include "nsIStringBundle.h"
16 : #include "nsIObserverService.h"
17 : #include "nsIURI.h"
18 : #include "nsCRT.h"
19 :
20 : #include <dlfcn.h>
21 : #include <gdk/gdk.h>
22 :
23 : static bool gHasActions = false;
24 : static bool gHasCaps = false;
25 :
26 : void* nsAlertsIconListener::libNotifyHandle = nullptr;
27 : bool nsAlertsIconListener::libNotifyNotAvail = false;
28 : nsAlertsIconListener::notify_is_initted_t nsAlertsIconListener::notify_is_initted = nullptr;
29 : nsAlertsIconListener::notify_init_t nsAlertsIconListener::notify_init = nullptr;
30 : nsAlertsIconListener::notify_get_server_caps_t nsAlertsIconListener::notify_get_server_caps = nullptr;
31 : nsAlertsIconListener::notify_notification_new_t nsAlertsIconListener::notify_notification_new = nullptr;
32 : nsAlertsIconListener::notify_notification_show_t nsAlertsIconListener::notify_notification_show = nullptr;
33 : nsAlertsIconListener::notify_notification_set_icon_from_pixbuf_t nsAlertsIconListener::notify_notification_set_icon_from_pixbuf = nullptr;
34 : nsAlertsIconListener::notify_notification_add_action_t nsAlertsIconListener::notify_notification_add_action = nullptr;
35 : nsAlertsIconListener::notify_notification_close_t nsAlertsIconListener::notify_notification_close = nullptr;
36 :
37 0 : static void notify_action_cb(NotifyNotification *notification,
38 : gchar *action, gpointer user_data)
39 : {
40 0 : nsAlertsIconListener* alert = static_cast<nsAlertsIconListener*> (user_data);
41 0 : alert->SendCallback();
42 0 : }
43 :
44 0 : static void notify_closed_marshal(GClosure* closure,
45 : GValue* return_value,
46 : guint n_param_values,
47 : const GValue* param_values,
48 : gpointer invocation_hint,
49 : gpointer marshal_data)
50 : {
51 0 : MOZ_ASSERT(n_param_values >= 1, "No object in params");
52 :
53 : nsAlertsIconListener* alert =
54 0 : static_cast<nsAlertsIconListener*>(closure->data);
55 0 : alert->SendClosed();
56 0 : NS_RELEASE(alert);
57 0 : }
58 :
59 : static GdkPixbuf*
60 0 : GetPixbufFromImgRequest(imgIRequest* aRequest)
61 : {
62 0 : nsCOMPtr<imgIContainer> image;
63 0 : nsresult rv = aRequest->GetImage(getter_AddRefs(image));
64 0 : if (NS_FAILED(rv)) {
65 0 : return nullptr;
66 : }
67 :
68 : nsCOMPtr<nsIImageToPixbuf> imgToPixbuf =
69 0 : do_GetService("@mozilla.org/widget/image-to-gdk-pixbuf;1");
70 :
71 0 : return imgToPixbuf->ConvertImageToPixbuf(image);
72 : }
73 :
74 0 : NS_IMPL_ISUPPORTS(nsAlertsIconListener, nsIAlertNotificationImageListener,
75 : nsIObserver, nsISupportsWeakReference)
76 :
77 0 : nsAlertsIconListener::nsAlertsIconListener(nsSystemAlertsService* aBackend,
78 0 : const nsAString& aAlertName)
79 : : mAlertName(aAlertName),
80 : mBackend(aBackend),
81 0 : mNotification(nullptr)
82 : {
83 0 : if (!libNotifyHandle && !libNotifyNotAvail) {
84 0 : libNotifyHandle = dlopen("libnotify.so.4", RTLD_LAZY);
85 0 : if (!libNotifyHandle) {
86 0 : libNotifyHandle = dlopen("libnotify.so.1", RTLD_LAZY);
87 0 : if (!libNotifyHandle) {
88 0 : libNotifyNotAvail = true;
89 0 : return;
90 : }
91 : }
92 :
93 0 : notify_is_initted = (notify_is_initted_t)dlsym(libNotifyHandle, "notify_is_initted");
94 0 : notify_init = (notify_init_t)dlsym(libNotifyHandle, "notify_init");
95 0 : notify_get_server_caps = (notify_get_server_caps_t)dlsym(libNotifyHandle, "notify_get_server_caps");
96 0 : notify_notification_new = (notify_notification_new_t)dlsym(libNotifyHandle, "notify_notification_new");
97 0 : notify_notification_show = (notify_notification_show_t)dlsym(libNotifyHandle, "notify_notification_show");
98 0 : notify_notification_set_icon_from_pixbuf = (notify_notification_set_icon_from_pixbuf_t)dlsym(libNotifyHandle, "notify_notification_set_icon_from_pixbuf");
99 0 : notify_notification_add_action = (notify_notification_add_action_t)dlsym(libNotifyHandle, "notify_notification_add_action");
100 0 : notify_notification_close = (notify_notification_close_t)dlsym(libNotifyHandle, "notify_notification_close");
101 0 : if (!notify_is_initted || !notify_init || !notify_get_server_caps || !notify_notification_new || !notify_notification_show || !notify_notification_set_icon_from_pixbuf || !notify_notification_add_action || !notify_notification_close) {
102 0 : dlclose(libNotifyHandle);
103 0 : libNotifyHandle = nullptr;
104 : }
105 : }
106 : }
107 :
108 0 : nsAlertsIconListener::~nsAlertsIconListener()
109 : {
110 0 : mBackend->RemoveListener(mAlertName, this);
111 : // Don't dlclose libnotify as it uses atexit().
112 0 : }
113 :
114 : NS_IMETHODIMP
115 0 : nsAlertsIconListener::OnImageMissing(nsISupports*)
116 : {
117 : // This notification doesn't have an image, or there was an error getting
118 : // the image. Show the notification without an icon.
119 0 : return ShowAlert(nullptr);
120 : }
121 :
122 : NS_IMETHODIMP
123 0 : nsAlertsIconListener::OnImageReady(nsISupports*, imgIRequest* aRequest)
124 : {
125 0 : GdkPixbuf* imagePixbuf = GetPixbufFromImgRequest(aRequest);
126 0 : if (!imagePixbuf) {
127 0 : ShowAlert(nullptr);
128 : } else {
129 0 : ShowAlert(imagePixbuf);
130 0 : g_object_unref(imagePixbuf);
131 : }
132 :
133 0 : return NS_OK;
134 : }
135 :
136 : nsresult
137 0 : nsAlertsIconListener::ShowAlert(GdkPixbuf* aPixbuf)
138 : {
139 0 : if (!mBackend->IsActiveListener(mAlertName, this))
140 0 : return NS_OK;
141 :
142 0 : mNotification = notify_notification_new(mAlertTitle.get(), mAlertText.get(),
143 : nullptr, nullptr);
144 :
145 0 : if (!mNotification)
146 0 : return NS_ERROR_OUT_OF_MEMORY;
147 :
148 : nsCOMPtr<nsIObserverService> obsServ =
149 0 : do_GetService("@mozilla.org/observer-service;1");
150 0 : if (obsServ)
151 0 : obsServ->AddObserver(this, "quit-application", true);
152 :
153 0 : if (aPixbuf)
154 0 : notify_notification_set_icon_from_pixbuf(mNotification, aPixbuf);
155 :
156 0 : NS_ADDREF(this);
157 0 : if (mAlertHasAction) {
158 : // What we put as the label doesn't matter here, if the action
159 : // string is "default" then that makes the entire bubble clickable
160 : // rather than creating a button.
161 0 : notify_notification_add_action(mNotification, "default", "Activate",
162 0 : notify_action_cb, this, nullptr);
163 : }
164 :
165 : // Fedora 10 calls NotifyNotification "closed" signal handlers with a
166 : // different signature, so a marshaller is used instead of a C callback to
167 : // get the user_data (this) in a parseable format. |closure| is created
168 : // with a floating reference, which gets sunk by g_signal_connect_closure().
169 0 : GClosure* closure = g_closure_new_simple(sizeof(GClosure), this);
170 0 : g_closure_set_marshal(closure, notify_closed_marshal);
171 0 : mClosureHandler = g_signal_connect_closure(mNotification, "closed", closure, FALSE);
172 0 : GError* error = nullptr;
173 0 : if (!notify_notification_show(mNotification, &error)) {
174 0 : NS_WARNING(error->message);
175 0 : g_error_free(error);
176 0 : return NS_ERROR_FAILURE;
177 : }
178 :
179 0 : if (mAlertListener)
180 0 : mAlertListener->Observe(nullptr, "alertshow", mAlertCookie.get());
181 :
182 0 : return NS_OK;
183 : }
184 :
185 : void
186 0 : nsAlertsIconListener::SendCallback()
187 : {
188 0 : if (mAlertListener)
189 0 : mAlertListener->Observe(nullptr, "alertclickcallback", mAlertCookie.get());
190 0 : }
191 :
192 : void
193 0 : nsAlertsIconListener::SendClosed()
194 : {
195 0 : if (mNotification) {
196 0 : g_object_unref(mNotification);
197 0 : mNotification = nullptr;
198 : }
199 0 : NotifyFinished();
200 0 : }
201 :
202 : NS_IMETHODIMP
203 0 : nsAlertsIconListener::Observe(nsISupports *aSubject, const char *aTopic,
204 : const char16_t *aData) {
205 : // We need to close any open notifications upon application exit, otherwise
206 : // we will leak since libnotify holds a ref for us.
207 0 : if (!nsCRT::strcmp(aTopic, "quit-application") && mNotification) {
208 0 : g_signal_handler_disconnect(mNotification, mClosureHandler);
209 0 : g_object_unref(mNotification);
210 0 : mNotification = nullptr;
211 0 : Release(); // equivalent to NS_RELEASE(this)
212 : }
213 0 : return NS_OK;
214 : }
215 :
216 : nsresult
217 0 : nsAlertsIconListener::Close()
218 : {
219 0 : if (mIconRequest) {
220 0 : mIconRequest->Cancel(NS_BINDING_ABORTED);
221 0 : mIconRequest = nullptr;
222 : }
223 :
224 0 : if (!mNotification) {
225 0 : NotifyFinished();
226 0 : return NS_OK;
227 : }
228 :
229 0 : GError* error = nullptr;
230 0 : if (!notify_notification_close(mNotification, &error)) {
231 0 : NS_WARNING(error->message);
232 0 : g_error_free(error);
233 0 : return NS_ERROR_FAILURE;
234 : }
235 :
236 0 : return NS_OK;
237 : }
238 :
239 : nsresult
240 0 : nsAlertsIconListener::InitAlertAsync(nsIAlertNotification* aAlert,
241 : nsIObserver* aAlertListener)
242 : {
243 0 : if (!libNotifyHandle)
244 0 : return NS_ERROR_FAILURE;
245 :
246 0 : if (!notify_is_initted()) {
247 : // Give the name of this application to libnotify
248 : nsCOMPtr<nsIStringBundleService> bundleService =
249 0 : do_GetService(NS_STRINGBUNDLE_CONTRACTID);
250 :
251 0 : nsAutoCString appShortName;
252 0 : if (bundleService) {
253 0 : nsCOMPtr<nsIStringBundle> bundle;
254 0 : bundleService->CreateBundle("chrome://branding/locale/brand.properties",
255 0 : getter_AddRefs(bundle));
256 0 : nsAutoString appName;
257 :
258 0 : if (bundle) {
259 0 : bundle->GetStringFromName(u"brandShortName",
260 0 : getter_Copies(appName));
261 0 : appShortName = NS_ConvertUTF16toUTF8(appName);
262 : } else {
263 0 : NS_WARNING("brand.properties not present, using default application name");
264 0 : appShortName.AssignLiteral("Mozilla");
265 : }
266 : } else {
267 0 : appShortName.AssignLiteral("Mozilla");
268 : }
269 :
270 0 : if (!notify_init(appShortName.get()))
271 0 : return NS_ERROR_FAILURE;
272 :
273 0 : GList *server_caps = notify_get_server_caps();
274 0 : if (server_caps) {
275 0 : gHasCaps = true;
276 0 : for (GList* cap = server_caps; cap != nullptr; cap = cap->next) {
277 0 : if (!strcmp((char*) cap->data, "actions")) {
278 0 : gHasActions = true;
279 0 : break;
280 : }
281 : }
282 0 : g_list_foreach(server_caps, (GFunc)g_free, nullptr);
283 0 : g_list_free(server_caps);
284 : }
285 : }
286 :
287 0 : if (!gHasCaps) {
288 : // if notify_get_server_caps() failed above we need to assume
289 : // there is no notification-server to display anything
290 0 : return NS_ERROR_FAILURE;
291 : }
292 :
293 0 : nsresult rv = aAlert->GetTextClickable(&mAlertHasAction);
294 0 : NS_ENSURE_SUCCESS(rv, rv);
295 0 : if (!gHasActions && mAlertHasAction)
296 0 : return NS_ERROR_FAILURE; // No good, fallback to XUL
297 :
298 0 : nsAutoString title;
299 0 : rv = aAlert->GetTitle(title);
300 0 : NS_ENSURE_SUCCESS(rv, rv);
301 : // Workaround for a libnotify bug - blank titles aren't dealt with
302 : // properly so we use a space
303 0 : if (title.IsEmpty()) {
304 0 : mAlertTitle = NS_LITERAL_CSTRING(" ");
305 : } else {
306 0 : mAlertTitle = NS_ConvertUTF16toUTF8(title);
307 : }
308 :
309 0 : nsAutoString text;
310 0 : rv = aAlert->GetText(text);
311 0 : NS_ENSURE_SUCCESS(rv, rv);
312 0 : mAlertText = NS_ConvertUTF16toUTF8(text);
313 :
314 0 : mAlertListener = aAlertListener;
315 :
316 0 : rv = aAlert->GetCookie(mAlertCookie);
317 0 : NS_ENSURE_SUCCESS(rv, rv);
318 :
319 0 : return aAlert->LoadImage(/* aTimeout = */ 0, this, /* aUserData = */ nullptr,
320 0 : getter_AddRefs(mIconRequest));
321 : }
322 :
323 0 : void nsAlertsIconListener::NotifyFinished()
324 : {
325 0 : if (mAlertListener)
326 0 : mAlertListener->Observe(nullptr, "alertfinished", mAlertCookie.get());
327 0 : }
|