1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/system/gnome/nsAlertsIconListener.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,323 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "nsAlertsIconListener.h" 1.10 +#include "imgIContainer.h" 1.11 +#include "imgILoader.h" 1.12 +#include "imgIRequest.h" 1.13 +#include "nsNetUtil.h" 1.14 +#include "nsIImageToPixbuf.h" 1.15 +#include "nsIStringBundle.h" 1.16 +#include "nsIObserverService.h" 1.17 +#include "nsCRT.h" 1.18 + 1.19 +#include <dlfcn.h> 1.20 +#include <gdk/gdk.h> 1.21 + 1.22 +static bool gHasActions = false; 1.23 +static bool gHasCaps = false; 1.24 + 1.25 +void* nsAlertsIconListener::libNotifyHandle = nullptr; 1.26 +bool nsAlertsIconListener::libNotifyNotAvail = false; 1.27 +nsAlertsIconListener::notify_is_initted_t nsAlertsIconListener::notify_is_initted = nullptr; 1.28 +nsAlertsIconListener::notify_init_t nsAlertsIconListener::notify_init = nullptr; 1.29 +nsAlertsIconListener::notify_get_server_caps_t nsAlertsIconListener::notify_get_server_caps = nullptr; 1.30 +nsAlertsIconListener::notify_notification_new_t nsAlertsIconListener::notify_notification_new = nullptr; 1.31 +nsAlertsIconListener::notify_notification_show_t nsAlertsIconListener::notify_notification_show = nullptr; 1.32 +nsAlertsIconListener::notify_notification_set_icon_from_pixbuf_t nsAlertsIconListener::notify_notification_set_icon_from_pixbuf = nullptr; 1.33 +nsAlertsIconListener::notify_notification_add_action_t nsAlertsIconListener::notify_notification_add_action = nullptr; 1.34 + 1.35 +static void notify_action_cb(NotifyNotification *notification, 1.36 + gchar *action, gpointer user_data) 1.37 +{ 1.38 + nsAlertsIconListener* alert = static_cast<nsAlertsIconListener*> (user_data); 1.39 + alert->SendCallback(); 1.40 +} 1.41 + 1.42 +static void notify_closed_marshal(GClosure* closure, 1.43 + GValue* return_value, 1.44 + guint n_param_values, 1.45 + const GValue* param_values, 1.46 + gpointer invocation_hint, 1.47 + gpointer marshal_data) 1.48 +{ 1.49 + NS_ABORT_IF_FALSE(n_param_values >= 1, "No object in params"); 1.50 + 1.51 + nsAlertsIconListener* alert = 1.52 + static_cast<nsAlertsIconListener*>(closure->data); 1.53 + alert->SendClosed(); 1.54 + NS_RELEASE(alert); 1.55 +} 1.56 + 1.57 +NS_IMPL_ISUPPORTS(nsAlertsIconListener, imgINotificationObserver, 1.58 + nsIObserver, nsISupportsWeakReference) 1.59 + 1.60 +nsAlertsIconListener::nsAlertsIconListener() 1.61 +: mLoadedFrame(false), 1.62 + mNotification(nullptr) 1.63 +{ 1.64 + if (!libNotifyHandle && !libNotifyNotAvail) { 1.65 + libNotifyHandle = dlopen("libnotify.so.4", RTLD_LAZY); 1.66 + if (!libNotifyHandle) { 1.67 + libNotifyHandle = dlopen("libnotify.so.1", RTLD_LAZY); 1.68 + if (!libNotifyHandle) { 1.69 + libNotifyNotAvail = true; 1.70 + return; 1.71 + } 1.72 + } 1.73 + 1.74 + notify_is_initted = (notify_is_initted_t)dlsym(libNotifyHandle, "notify_is_initted"); 1.75 + notify_init = (notify_init_t)dlsym(libNotifyHandle, "notify_init"); 1.76 + notify_get_server_caps = (notify_get_server_caps_t)dlsym(libNotifyHandle, "notify_get_server_caps"); 1.77 + notify_notification_new = (notify_notification_new_t)dlsym(libNotifyHandle, "notify_notification_new"); 1.78 + notify_notification_show = (notify_notification_show_t)dlsym(libNotifyHandle, "notify_notification_show"); 1.79 + notify_notification_set_icon_from_pixbuf = (notify_notification_set_icon_from_pixbuf_t)dlsym(libNotifyHandle, "notify_notification_set_icon_from_pixbuf"); 1.80 + notify_notification_add_action = (notify_notification_add_action_t)dlsym(libNotifyHandle, "notify_notification_add_action"); 1.81 + 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) { 1.82 + dlclose(libNotifyHandle); 1.83 + libNotifyHandle = nullptr; 1.84 + } 1.85 + } 1.86 +} 1.87 + 1.88 +nsAlertsIconListener::~nsAlertsIconListener() 1.89 +{ 1.90 + if (mIconRequest) 1.91 + mIconRequest->CancelAndForgetObserver(NS_BINDING_ABORTED); 1.92 + // Don't dlclose libnotify as it uses atexit(). 1.93 +} 1.94 + 1.95 +NS_IMETHODIMP 1.96 +nsAlertsIconListener::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData) 1.97 +{ 1.98 + if (aType == imgINotificationObserver::LOAD_COMPLETE) { 1.99 + return OnStopRequest(aRequest); 1.100 + } 1.101 + 1.102 + if (aType == imgINotificationObserver::FRAME_COMPLETE) { 1.103 + return OnStopFrame(aRequest); 1.104 + } 1.105 + 1.106 + return NS_OK; 1.107 +} 1.108 + 1.109 +nsresult 1.110 +nsAlertsIconListener::OnStopRequest(imgIRequest* aRequest) 1.111 +{ 1.112 + uint32_t imgStatus = imgIRequest::STATUS_ERROR; 1.113 + nsresult rv = aRequest->GetImageStatus(&imgStatus); 1.114 + NS_ENSURE_SUCCESS(rv, rv); 1.115 + if (imgStatus == imgIRequest::STATUS_ERROR && !mLoadedFrame) { 1.116 + // We have an error getting the image. Display the notification with no icon. 1.117 + ShowAlert(nullptr); 1.118 + } 1.119 + 1.120 + if (mIconRequest) { 1.121 + mIconRequest->Cancel(NS_BINDING_ABORTED); 1.122 + mIconRequest = nullptr; 1.123 + } 1.124 + return NS_OK; 1.125 +} 1.126 + 1.127 +nsresult 1.128 +nsAlertsIconListener::OnStopFrame(imgIRequest* aRequest) 1.129 +{ 1.130 + if (aRequest != mIconRequest) 1.131 + return NS_ERROR_FAILURE; 1.132 + 1.133 + if (mLoadedFrame) 1.134 + return NS_OK; // only use one frame 1.135 + 1.136 + nsCOMPtr<imgIContainer> image; 1.137 + nsresult rv = aRequest->GetImage(getter_AddRefs(image)); 1.138 + if (NS_FAILED(rv)) 1.139 + return rv; 1.140 + 1.141 + nsCOMPtr<nsIImageToPixbuf> imgToPixbuf = 1.142 + do_GetService("@mozilla.org/widget/image-to-gdk-pixbuf;1"); 1.143 + 1.144 + GdkPixbuf* imagePixbuf = imgToPixbuf->ConvertImageToPixbuf(image); 1.145 + if (!imagePixbuf) 1.146 + return NS_ERROR_FAILURE; 1.147 + 1.148 + ShowAlert(imagePixbuf); 1.149 + 1.150 + g_object_unref(imagePixbuf); 1.151 + 1.152 + mLoadedFrame = true; 1.153 + return NS_OK; 1.154 +} 1.155 + 1.156 +nsresult 1.157 +nsAlertsIconListener::ShowAlert(GdkPixbuf* aPixbuf) 1.158 +{ 1.159 + mNotification = notify_notification_new(mAlertTitle.get(), mAlertText.get(), 1.160 + nullptr, nullptr); 1.161 + 1.162 + if (!mNotification) 1.163 + return NS_ERROR_OUT_OF_MEMORY; 1.164 + 1.165 + if (aPixbuf) 1.166 + notify_notification_set_icon_from_pixbuf(mNotification, aPixbuf); 1.167 + 1.168 + NS_ADDREF(this); 1.169 + if (mAlertHasAction) { 1.170 + // What we put as the label doesn't matter here, if the action 1.171 + // string is "default" then that makes the entire bubble clickable 1.172 + // rather than creating a button. 1.173 + notify_notification_add_action(mNotification, "default", "Activate", 1.174 + notify_action_cb, this, nullptr); 1.175 + } 1.176 + 1.177 + // Fedora 10 calls NotifyNotification "closed" signal handlers with a 1.178 + // different signature, so a marshaller is used instead of a C callback to 1.179 + // get the user_data (this) in a parseable format. |closure| is created 1.180 + // with a floating reference, which gets sunk by g_signal_connect_closure(). 1.181 + GClosure* closure = g_closure_new_simple(sizeof(GClosure), this); 1.182 + g_closure_set_marshal(closure, notify_closed_marshal); 1.183 + mClosureHandler = g_signal_connect_closure(mNotification, "closed", closure, FALSE); 1.184 + gboolean result = notify_notification_show(mNotification, nullptr); 1.185 + 1.186 + return result ? NS_OK : NS_ERROR_FAILURE; 1.187 +} 1.188 + 1.189 +nsresult 1.190 +nsAlertsIconListener::StartRequest(const nsAString & aImageUrl) 1.191 +{ 1.192 + if (mIconRequest) { 1.193 + // Another icon request is already in flight. Kill it. 1.194 + mIconRequest->Cancel(NS_BINDING_ABORTED); 1.195 + mIconRequest = nullptr; 1.196 + } 1.197 + 1.198 + nsCOMPtr<nsIURI> imageUri; 1.199 + NS_NewURI(getter_AddRefs(imageUri), aImageUrl); 1.200 + if (!imageUri) 1.201 + return ShowAlert(nullptr); 1.202 + 1.203 + nsCOMPtr<imgILoader> il(do_GetService("@mozilla.org/image/loader;1")); 1.204 + if (!il) 1.205 + return ShowAlert(nullptr); 1.206 + 1.207 + // XXX: Hrmm.... Bypass cache, or isolate to imageUrl? 1.208 + return il->LoadImageXPCOM(imageUri, imageURI, nullptr, nullptr, nullptr, 1.209 + this, nullptr, nsIRequest::LOAD_NORMAL, nullptr, 1.210 + nullptr, getter_AddRefs(mIconRequest)); 1.211 +} 1.212 + 1.213 +void 1.214 +nsAlertsIconListener::SendCallback() 1.215 +{ 1.216 + if (mAlertListener) 1.217 + mAlertListener->Observe(nullptr, "alertclickcallback", mAlertCookie.get()); 1.218 +} 1.219 + 1.220 +void 1.221 +nsAlertsIconListener::SendClosed() 1.222 +{ 1.223 + if (mNotification) { 1.224 + g_object_unref(mNotification); 1.225 + mNotification = nullptr; 1.226 + } 1.227 + if (mAlertListener) 1.228 + mAlertListener->Observe(nullptr, "alertfinished", mAlertCookie.get()); 1.229 +} 1.230 + 1.231 +NS_IMETHODIMP 1.232 +nsAlertsIconListener::Observe(nsISupports *aSubject, const char *aTopic, 1.233 + const char16_t *aData) { 1.234 + // We need to close any open notifications upon application exit, otherwise 1.235 + // we will leak since libnotify holds a ref for us. 1.236 + if (!nsCRT::strcmp(aTopic, "quit-application") && mNotification) { 1.237 + g_signal_handler_disconnect(mNotification, mClosureHandler); 1.238 + g_object_unref(mNotification); 1.239 + mNotification = nullptr; 1.240 + Release(); // equivalent to NS_RELEASE(this) 1.241 + } 1.242 + return NS_OK; 1.243 +} 1.244 + 1.245 +nsresult 1.246 +nsAlertsIconListener::InitAlertAsync(const nsAString & aImageUrl, 1.247 + const nsAString & aAlertTitle, 1.248 + const nsAString & aAlertText, 1.249 + bool aAlertTextClickable, 1.250 + const nsAString & aAlertCookie, 1.251 + nsIObserver * aAlertListener) 1.252 +{ 1.253 + if (!libNotifyHandle) 1.254 + return NS_ERROR_FAILURE; 1.255 + 1.256 + if (!notify_is_initted()) { 1.257 + // Give the name of this application to libnotify 1.258 + nsCOMPtr<nsIStringBundleService> bundleService = 1.259 + do_GetService(NS_STRINGBUNDLE_CONTRACTID); 1.260 + 1.261 + nsAutoCString appShortName; 1.262 + if (bundleService) { 1.263 + nsCOMPtr<nsIStringBundle> bundle; 1.264 + bundleService->CreateBundle("chrome://branding/locale/brand.properties", 1.265 + getter_AddRefs(bundle)); 1.266 + nsAutoString appName; 1.267 + 1.268 + if (bundle) { 1.269 + bundle->GetStringFromName(MOZ_UTF16("brandShortName"), 1.270 + getter_Copies(appName)); 1.271 + appShortName = NS_ConvertUTF16toUTF8(appName); 1.272 + } else { 1.273 + NS_WARNING("brand.properties not present, using default application name"); 1.274 + appShortName.AssignLiteral("Mozilla"); 1.275 + } 1.276 + } else { 1.277 + appShortName.AssignLiteral("Mozilla"); 1.278 + } 1.279 + 1.280 + if (!notify_init(appShortName.get())) 1.281 + return NS_ERROR_FAILURE; 1.282 + 1.283 + GList *server_caps = notify_get_server_caps(); 1.284 + if (server_caps) { 1.285 + gHasCaps = true; 1.286 + for (GList* cap = server_caps; cap != nullptr; cap = cap->next) { 1.287 + if (!strcmp((char*) cap->data, "actions")) { 1.288 + gHasActions = true; 1.289 + break; 1.290 + } 1.291 + } 1.292 + g_list_foreach(server_caps, (GFunc)g_free, nullptr); 1.293 + g_list_free(server_caps); 1.294 + } 1.295 + } 1.296 + 1.297 + if (!gHasCaps) { 1.298 + // if notify_get_server_caps() failed above we need to assume 1.299 + // there is no notification-server to display anything 1.300 + return NS_ERROR_FAILURE; 1.301 + } 1.302 + 1.303 + if (!gHasActions && aAlertTextClickable) 1.304 + return NS_ERROR_FAILURE; // No good, fallback to XUL 1.305 + 1.306 + nsCOMPtr<nsIObserverService> obsServ = 1.307 + do_GetService("@mozilla.org/observer-service;1"); 1.308 + if (obsServ) 1.309 + obsServ->AddObserver(this, "quit-application", true); 1.310 + 1.311 + // Workaround for a libnotify bug - blank titles aren't dealt with 1.312 + // properly so we use a space 1.313 + if (aAlertTitle.IsEmpty()) { 1.314 + mAlertTitle = NS_LITERAL_CSTRING(" "); 1.315 + } else { 1.316 + mAlertTitle = NS_ConvertUTF16toUTF8(aAlertTitle); 1.317 + } 1.318 + 1.319 + mAlertText = NS_ConvertUTF16toUTF8(aAlertText); 1.320 + mAlertHasAction = aAlertTextClickable; 1.321 + 1.322 + mAlertListener = aAlertListener; 1.323 + mAlertCookie = aAlertCookie; 1.324 + 1.325 + return StartRequest(aImageUrl); 1.326 +}