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