1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is mozilla.org code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Mozilla Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 2008
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : *
24 : * Alternatively, the contents of this file may be used under the terms of
25 : * either the GNU General Public License Version 2 or later (the "GPL"), or
26 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 : * in which case the provisions of the GPL or the LGPL are applicable instead
28 : * of those above. If you wish to allow use of your version of this file only
29 : * under the terms of either the GPL or the LGPL, and not to allow others to
30 : * use your version of this file under the terms of the MPL, indicate your
31 : * decision by deleting the provisions above and replace them with the notice
32 : * and other provisions required by the GPL or the LGPL. If you do not delete
33 : * the provisions above, a recipient may use your version of this file under
34 : * the terms of any one of the MPL, the GPL or the LGPL.
35 : *
36 : * ***** END LICENSE BLOCK ***** */
37 :
38 : #include "nsAlertsIconListener.h"
39 : #include "imgIContainer.h"
40 : #include "imgILoader.h"
41 : #include "imgIRequest.h"
42 : #include "nsNetUtil.h"
43 : #include "nsIImageToPixbuf.h"
44 : #include "nsIStringBundle.h"
45 : #include "nsIObserverService.h"
46 : #include "nsCRT.h"
47 :
48 : #include <gdk/gdk.h>
49 :
50 : // Compatibility macro for <libnotify-0.7
51 : #ifndef NOTIFY_CHECK_VERSION
52 : #define NOTIFY_CHECK_VERSION(x,y,z) 0
53 : #endif
54 :
55 : static bool gHasActions = false;
56 :
57 0 : static void notify_action_cb(NotifyNotification *notification,
58 : gchar *action, gpointer user_data)
59 : {
60 0 : nsAlertsIconListener* alert = static_cast<nsAlertsIconListener*> (user_data);
61 0 : alert->SendCallback();
62 0 : }
63 :
64 0 : static void notify_closed_marshal(GClosure* closure,
65 : GValue* return_value,
66 : guint n_param_values,
67 : const GValue* param_values,
68 : gpointer invocation_hint,
69 : gpointer marshal_data)
70 : {
71 0 : NS_ABORT_IF_FALSE(n_param_values >= 1, "No object in params");
72 :
73 : nsAlertsIconListener* alert =
74 0 : static_cast<nsAlertsIconListener*>(closure->data);
75 0 : alert->SendClosed();
76 0 : NS_RELEASE(alert);
77 0 : }
78 :
79 0 : NS_IMPL_ISUPPORTS4(nsAlertsIconListener, imgIContainerObserver,
80 : imgIDecoderObserver, nsIObserver, nsISupportsWeakReference)
81 :
82 0 : nsAlertsIconListener::nsAlertsIconListener()
83 : : mLoadedFrame(false),
84 0 : mNotification(NULL)
85 : {
86 0 : }
87 :
88 0 : nsAlertsIconListener::~nsAlertsIconListener()
89 : {
90 0 : if (mIconRequest)
91 0 : mIconRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
92 0 : }
93 :
94 : NS_IMETHODIMP
95 0 : nsAlertsIconListener::OnStartRequest(imgIRequest* aRequest)
96 : {
97 0 : return NS_OK;
98 : }
99 :
100 : NS_IMETHODIMP
101 0 : nsAlertsIconListener::OnStartDecode(imgIRequest* aRequest)
102 : {
103 0 : return NS_OK;
104 : }
105 :
106 :
107 : NS_IMETHODIMP
108 0 : nsAlertsIconListener::OnStartContainer(imgIRequest* aRequest,
109 : imgIContainer* aContainer)
110 : {
111 0 : return NS_OK;
112 : }
113 :
114 :
115 : NS_IMETHODIMP
116 0 : nsAlertsIconListener::OnStartFrame(imgIRequest* aRequest,
117 : PRUint32 aFrame)
118 : {
119 0 : return NS_OK;
120 : }
121 :
122 :
123 : NS_IMETHODIMP
124 0 : nsAlertsIconListener::OnDataAvailable(imgIRequest* aRequest,
125 : bool aCurrentFrame,
126 : const nsIntRect* aRect)
127 : {
128 0 : return NS_OK;
129 : }
130 :
131 : NS_IMETHODIMP
132 0 : nsAlertsIconListener::OnStopContainer(imgIRequest* aRequest,
133 : imgIContainer* aContainer)
134 : {
135 0 : return NS_OK;
136 : }
137 :
138 :
139 : NS_IMETHODIMP
140 0 : nsAlertsIconListener::OnStopDecode(imgIRequest* aRequest,
141 : nsresult status,
142 : const PRUnichar* statusArg)
143 : {
144 0 : return NS_OK;
145 : }
146 :
147 : NS_IMETHODIMP
148 0 : nsAlertsIconListener::FrameChanged(imgIRequest* aRequest,
149 : imgIContainer* aContainer,
150 : const nsIntRect* aDirtyRect)
151 : {
152 0 : return NS_OK;
153 : }
154 :
155 : NS_IMETHODIMP
156 0 : nsAlertsIconListener::OnStopRequest(imgIRequest* aRequest,
157 : bool aIsLastPart)
158 : {
159 0 : PRUint32 imgStatus = imgIRequest::STATUS_ERROR;
160 0 : nsresult rv = aRequest->GetImageStatus(&imgStatus);
161 0 : NS_ENSURE_SUCCESS(rv, rv);
162 0 : if (imgStatus == imgIRequest::STATUS_ERROR && !mLoadedFrame) {
163 : // We have an error getting the image. Display the notification with no icon.
164 0 : ShowAlert(NULL);
165 : }
166 :
167 0 : if (mIconRequest) {
168 0 : mIconRequest->Cancel(NS_BINDING_ABORTED);
169 0 : mIconRequest = nsnull;
170 : }
171 0 : return NS_OK;
172 : }
173 :
174 : NS_IMETHODIMP
175 0 : nsAlertsIconListener::OnDiscard(imgIRequest *aRequest)
176 : {
177 0 : return NS_OK;
178 : }
179 :
180 : NS_IMETHODIMP
181 0 : nsAlertsIconListener::OnImageIsAnimated(imgIRequest *aRequest)
182 : {
183 0 : return NS_OK;
184 : }
185 :
186 : NS_IMETHODIMP
187 0 : nsAlertsIconListener::OnStopFrame(imgIRequest* aRequest,
188 : PRUint32 aFrame)
189 : {
190 0 : if (aRequest != mIconRequest)
191 0 : return NS_ERROR_FAILURE;
192 :
193 0 : if (mLoadedFrame)
194 0 : return NS_OK; // only use one frame
195 :
196 0 : nsCOMPtr<imgIContainer> image;
197 0 : nsresult rv = aRequest->GetImage(getter_AddRefs(image));
198 0 : if (NS_FAILED(rv))
199 0 : return rv;
200 :
201 : nsCOMPtr<nsIImageToPixbuf> imgToPixbuf =
202 0 : do_GetService("@mozilla.org/widget/image-to-gdk-pixbuf;1");
203 :
204 0 : GdkPixbuf* imagePixbuf = imgToPixbuf->ConvertImageToPixbuf(image);
205 0 : if (!imagePixbuf)
206 0 : return NS_ERROR_FAILURE;
207 :
208 0 : ShowAlert(imagePixbuf);
209 :
210 0 : g_object_unref(imagePixbuf);
211 :
212 0 : mLoadedFrame = true;
213 0 : return NS_OK;
214 : }
215 :
216 : nsresult
217 0 : nsAlertsIconListener::ShowAlert(GdkPixbuf* aPixbuf)
218 : {
219 : mNotification = notify_notification_new(mAlertTitle.get(),
220 : mAlertText.get(),
221 : NULL
222 : // >=libnotify-0.7.0 has no support for attaching to widgets
223 : #if !NOTIFY_CHECK_VERSION(0,7,0)
224 : , NULL
225 : #endif
226 0 : );
227 :
228 0 : if (!mNotification)
229 0 : return NS_ERROR_OUT_OF_MEMORY;
230 :
231 0 : if (aPixbuf)
232 0 : notify_notification_set_icon_from_pixbuf(mNotification, aPixbuf);
233 :
234 0 : NS_ADDREF(this);
235 0 : if (mAlertHasAction) {
236 : // What we put as the label doesn't matter here, if the action
237 : // string is "default" then that makes the entire bubble clickable
238 : // rather than creating a button.
239 : notify_notification_add_action(mNotification, "default", "Activate",
240 0 : notify_action_cb, this, NULL);
241 : }
242 :
243 : // Fedora 10 calls NotifyNotification "closed" signal handlers with a
244 : // different signature, so a marshaller is used instead of a C callback to
245 : // get the user_data (this) in a parseable format. |closure| is created
246 : // with a floating reference, which gets sunk by g_signal_connect_closure().
247 0 : GClosure* closure = g_closure_new_simple(sizeof(GClosure), this);
248 0 : g_closure_set_marshal(closure, notify_closed_marshal);
249 0 : mClosureHandler = g_signal_connect_closure(mNotification, "closed", closure, FALSE);
250 0 : gboolean result = notify_notification_show(mNotification, NULL);
251 :
252 0 : return result ? NS_OK : NS_ERROR_FAILURE;
253 : }
254 :
255 : nsresult
256 0 : nsAlertsIconListener::StartRequest(const nsAString & aImageUrl)
257 : {
258 0 : if (mIconRequest) {
259 : // Another icon request is already in flight. Kill it.
260 0 : mIconRequest->Cancel(NS_BINDING_ABORTED);
261 0 : mIconRequest = nsnull;
262 : }
263 :
264 0 : nsCOMPtr<nsIURI> imageUri;
265 0 : NS_NewURI(getter_AddRefs(imageUri), aImageUrl);
266 0 : if (!imageUri)
267 0 : return ShowAlert(NULL);
268 :
269 0 : nsCOMPtr<imgILoader> il(do_GetService("@mozilla.org/image/loader;1"));
270 0 : if (!il)
271 0 : return ShowAlert(NULL);
272 :
273 0 : return il->LoadImage(imageUri, nsnull, nsnull, nsnull, nsnull, this,
274 : nsnull, nsIRequest::LOAD_NORMAL, nsnull, nsnull,
275 0 : nsnull, getter_AddRefs(mIconRequest));
276 : }
277 :
278 : void
279 0 : nsAlertsIconListener::SendCallback()
280 : {
281 0 : if (mAlertListener)
282 0 : mAlertListener->Observe(NULL, "alertclickcallback", mAlertCookie.get());
283 0 : }
284 :
285 : void
286 0 : nsAlertsIconListener::SendClosed()
287 : {
288 0 : if (mNotification) {
289 0 : g_object_unref(mNotification);
290 0 : mNotification = NULL;
291 : }
292 0 : if (mAlertListener)
293 0 : mAlertListener->Observe(NULL, "alertfinished", mAlertCookie.get());
294 0 : }
295 :
296 : NS_IMETHODIMP
297 0 : nsAlertsIconListener::Observe(nsISupports *aSubject, const char *aTopic,
298 : const PRUnichar *aData) {
299 : // We need to close any open notifications upon application exit, otherwise
300 : // we will leak since libnotify holds a ref for us.
301 0 : if (!nsCRT::strcmp(aTopic, "quit-application") && mNotification) {
302 0 : g_signal_handler_disconnect(mNotification, mClosureHandler);
303 0 : g_object_unref(mNotification);
304 0 : mNotification = NULL;
305 0 : Release(); // equivalent to NS_RELEASE(this)
306 : }
307 0 : return NS_OK;
308 : }
309 :
310 : nsresult
311 0 : nsAlertsIconListener::InitAlertAsync(const nsAString & aImageUrl,
312 : const nsAString & aAlertTitle,
313 : const nsAString & aAlertText,
314 : bool aAlertTextClickable,
315 : const nsAString & aAlertCookie,
316 : nsIObserver * aAlertListener)
317 : {
318 0 : if (!notify_is_initted()) {
319 : // Give the name of this application to libnotify
320 : nsCOMPtr<nsIStringBundleService> bundleService =
321 0 : do_GetService(NS_STRINGBUNDLE_CONTRACTID);
322 :
323 0 : nsCAutoString appShortName;
324 0 : if (bundleService) {
325 0 : nsCOMPtr<nsIStringBundle> bundle;
326 0 : bundleService->CreateBundle("chrome://branding/locale/brand.properties",
327 0 : getter_AddRefs(bundle));
328 0 : nsAutoString appName;
329 :
330 0 : if (bundle) {
331 0 : bundle->GetStringFromName(NS_LITERAL_STRING("brandShortName").get(),
332 0 : getter_Copies(appName));
333 0 : appShortName = NS_ConvertUTF16toUTF8(appName);
334 : } else {
335 0 : NS_WARNING("brand.properties not present, using default application name");
336 0 : appShortName.AssignLiteral("Mozilla");
337 : }
338 : } else {
339 0 : appShortName.AssignLiteral("Mozilla");
340 : }
341 :
342 0 : if (!notify_init(appShortName.get()))
343 0 : return NS_ERROR_FAILURE;
344 :
345 0 : GList *server_caps = notify_get_server_caps();
346 0 : if (server_caps) {
347 0 : for (GList* cap = server_caps; cap != NULL; cap = cap->next) {
348 0 : if (!strcmp((char*) cap->data, "actions")) {
349 0 : gHasActions = true;
350 0 : break;
351 : }
352 : }
353 0 : g_list_foreach(server_caps, (GFunc)g_free, NULL);
354 0 : g_list_free(server_caps);
355 : }
356 : }
357 :
358 0 : if (!gHasActions && aAlertTextClickable)
359 0 : return NS_ERROR_FAILURE; // No good, fallback to XUL
360 :
361 : nsCOMPtr<nsIObserverService> obsServ =
362 0 : do_GetService("@mozilla.org/observer-service;1");
363 0 : if (obsServ)
364 0 : obsServ->AddObserver(this, "quit-application", true);
365 :
366 : // Workaround for a libnotify bug - blank titles aren't dealt with
367 : // properly so we use a space
368 0 : if (aAlertTitle.IsEmpty()) {
369 0 : mAlertTitle = NS_LITERAL_CSTRING(" ");
370 : } else {
371 0 : mAlertTitle = NS_ConvertUTF16toUTF8(aAlertTitle);
372 : }
373 :
374 0 : mAlertText = NS_ConvertUTF16toUTF8(aAlertText);
375 0 : mAlertHasAction = aAlertTextClickable;
376 :
377 0 : mAlertListener = aAlertListener;
378 0 : mAlertCookie = aAlertCookie;
379 :
380 0 : return StartRequest(aImageUrl);
381 : }
|