michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsAlertsIconListener.h" michael@0: #include "imgIContainer.h" michael@0: #include "imgILoader.h" michael@0: #include "imgIRequest.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsIImageToPixbuf.h" michael@0: #include "nsIStringBundle.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsCRT.h" michael@0: michael@0: #include michael@0: #include michael@0: michael@0: static bool gHasActions = false; michael@0: static bool gHasCaps = false; michael@0: michael@0: void* nsAlertsIconListener::libNotifyHandle = nullptr; michael@0: bool nsAlertsIconListener::libNotifyNotAvail = false; michael@0: nsAlertsIconListener::notify_is_initted_t nsAlertsIconListener::notify_is_initted = nullptr; michael@0: nsAlertsIconListener::notify_init_t nsAlertsIconListener::notify_init = nullptr; michael@0: nsAlertsIconListener::notify_get_server_caps_t nsAlertsIconListener::notify_get_server_caps = nullptr; michael@0: nsAlertsIconListener::notify_notification_new_t nsAlertsIconListener::notify_notification_new = nullptr; michael@0: nsAlertsIconListener::notify_notification_show_t nsAlertsIconListener::notify_notification_show = nullptr; michael@0: nsAlertsIconListener::notify_notification_set_icon_from_pixbuf_t nsAlertsIconListener::notify_notification_set_icon_from_pixbuf = nullptr; michael@0: nsAlertsIconListener::notify_notification_add_action_t nsAlertsIconListener::notify_notification_add_action = nullptr; michael@0: michael@0: static void notify_action_cb(NotifyNotification *notification, michael@0: gchar *action, gpointer user_data) michael@0: { michael@0: nsAlertsIconListener* alert = static_cast (user_data); michael@0: alert->SendCallback(); michael@0: } michael@0: michael@0: static void notify_closed_marshal(GClosure* closure, michael@0: GValue* return_value, michael@0: guint n_param_values, michael@0: const GValue* param_values, michael@0: gpointer invocation_hint, michael@0: gpointer marshal_data) michael@0: { michael@0: NS_ABORT_IF_FALSE(n_param_values >= 1, "No object in params"); michael@0: michael@0: nsAlertsIconListener* alert = michael@0: static_cast(closure->data); michael@0: alert->SendClosed(); michael@0: NS_RELEASE(alert); michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsAlertsIconListener, imgINotificationObserver, michael@0: nsIObserver, nsISupportsWeakReference) michael@0: michael@0: nsAlertsIconListener::nsAlertsIconListener() michael@0: : mLoadedFrame(false), michael@0: mNotification(nullptr) michael@0: { michael@0: if (!libNotifyHandle && !libNotifyNotAvail) { michael@0: libNotifyHandle = dlopen("libnotify.so.4", RTLD_LAZY); michael@0: if (!libNotifyHandle) { michael@0: libNotifyHandle = dlopen("libnotify.so.1", RTLD_LAZY); michael@0: if (!libNotifyHandle) { michael@0: libNotifyNotAvail = true; michael@0: return; michael@0: } michael@0: } michael@0: michael@0: notify_is_initted = (notify_is_initted_t)dlsym(libNotifyHandle, "notify_is_initted"); michael@0: notify_init = (notify_init_t)dlsym(libNotifyHandle, "notify_init"); michael@0: notify_get_server_caps = (notify_get_server_caps_t)dlsym(libNotifyHandle, "notify_get_server_caps"); michael@0: notify_notification_new = (notify_notification_new_t)dlsym(libNotifyHandle, "notify_notification_new"); michael@0: notify_notification_show = (notify_notification_show_t)dlsym(libNotifyHandle, "notify_notification_show"); michael@0: notify_notification_set_icon_from_pixbuf = (notify_notification_set_icon_from_pixbuf_t)dlsym(libNotifyHandle, "notify_notification_set_icon_from_pixbuf"); michael@0: notify_notification_add_action = (notify_notification_add_action_t)dlsym(libNotifyHandle, "notify_notification_add_action"); michael@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) { michael@0: dlclose(libNotifyHandle); michael@0: libNotifyHandle = nullptr; michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsAlertsIconListener::~nsAlertsIconListener() michael@0: { michael@0: if (mIconRequest) michael@0: mIconRequest->CancelAndForgetObserver(NS_BINDING_ABORTED); michael@0: // Don't dlclose libnotify as it uses atexit(). michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsAlertsIconListener::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData) michael@0: { michael@0: if (aType == imgINotificationObserver::LOAD_COMPLETE) { michael@0: return OnStopRequest(aRequest); michael@0: } michael@0: michael@0: if (aType == imgINotificationObserver::FRAME_COMPLETE) { michael@0: return OnStopFrame(aRequest); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsAlertsIconListener::OnStopRequest(imgIRequest* aRequest) michael@0: { michael@0: uint32_t imgStatus = imgIRequest::STATUS_ERROR; michael@0: nsresult rv = aRequest->GetImageStatus(&imgStatus); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (imgStatus == imgIRequest::STATUS_ERROR && !mLoadedFrame) { michael@0: // We have an error getting the image. Display the notification with no icon. michael@0: ShowAlert(nullptr); michael@0: } michael@0: michael@0: if (mIconRequest) { michael@0: mIconRequest->Cancel(NS_BINDING_ABORTED); michael@0: mIconRequest = nullptr; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsAlertsIconListener::OnStopFrame(imgIRequest* aRequest) michael@0: { michael@0: if (aRequest != mIconRequest) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: if (mLoadedFrame) michael@0: return NS_OK; // only use one frame michael@0: michael@0: nsCOMPtr image; michael@0: nsresult rv = aRequest->GetImage(getter_AddRefs(image)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: nsCOMPtr imgToPixbuf = michael@0: do_GetService("@mozilla.org/widget/image-to-gdk-pixbuf;1"); michael@0: michael@0: GdkPixbuf* imagePixbuf = imgToPixbuf->ConvertImageToPixbuf(image); michael@0: if (!imagePixbuf) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: ShowAlert(imagePixbuf); michael@0: michael@0: g_object_unref(imagePixbuf); michael@0: michael@0: mLoadedFrame = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsAlertsIconListener::ShowAlert(GdkPixbuf* aPixbuf) michael@0: { michael@0: mNotification = notify_notification_new(mAlertTitle.get(), mAlertText.get(), michael@0: nullptr, nullptr); michael@0: michael@0: if (!mNotification) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: if (aPixbuf) michael@0: notify_notification_set_icon_from_pixbuf(mNotification, aPixbuf); michael@0: michael@0: NS_ADDREF(this); michael@0: if (mAlertHasAction) { michael@0: // What we put as the label doesn't matter here, if the action michael@0: // string is "default" then that makes the entire bubble clickable michael@0: // rather than creating a button. michael@0: notify_notification_add_action(mNotification, "default", "Activate", michael@0: notify_action_cb, this, nullptr); michael@0: } michael@0: michael@0: // Fedora 10 calls NotifyNotification "closed" signal handlers with a michael@0: // different signature, so a marshaller is used instead of a C callback to michael@0: // get the user_data (this) in a parseable format. |closure| is created michael@0: // with a floating reference, which gets sunk by g_signal_connect_closure(). michael@0: GClosure* closure = g_closure_new_simple(sizeof(GClosure), this); michael@0: g_closure_set_marshal(closure, notify_closed_marshal); michael@0: mClosureHandler = g_signal_connect_closure(mNotification, "closed", closure, FALSE); michael@0: gboolean result = notify_notification_show(mNotification, nullptr); michael@0: michael@0: return result ? NS_OK : NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsresult michael@0: nsAlertsIconListener::StartRequest(const nsAString & aImageUrl) michael@0: { michael@0: if (mIconRequest) { michael@0: // Another icon request is already in flight. Kill it. michael@0: mIconRequest->Cancel(NS_BINDING_ABORTED); michael@0: mIconRequest = nullptr; michael@0: } michael@0: michael@0: nsCOMPtr imageUri; michael@0: NS_NewURI(getter_AddRefs(imageUri), aImageUrl); michael@0: if (!imageUri) michael@0: return ShowAlert(nullptr); michael@0: michael@0: nsCOMPtr il(do_GetService("@mozilla.org/image/loader;1")); michael@0: if (!il) michael@0: return ShowAlert(nullptr); michael@0: michael@0: // XXX: Hrmm.... Bypass cache, or isolate to imageUrl? michael@0: return il->LoadImageXPCOM(imageUri, imageURI, nullptr, nullptr, nullptr, michael@0: this, nullptr, nsIRequest::LOAD_NORMAL, nullptr, michael@0: nullptr, getter_AddRefs(mIconRequest)); michael@0: } michael@0: michael@0: void michael@0: nsAlertsIconListener::SendCallback() michael@0: { michael@0: if (mAlertListener) michael@0: mAlertListener->Observe(nullptr, "alertclickcallback", mAlertCookie.get()); michael@0: } michael@0: michael@0: void michael@0: nsAlertsIconListener::SendClosed() michael@0: { michael@0: if (mNotification) { michael@0: g_object_unref(mNotification); michael@0: mNotification = nullptr; michael@0: } michael@0: if (mAlertListener) michael@0: mAlertListener->Observe(nullptr, "alertfinished", mAlertCookie.get()); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsAlertsIconListener::Observe(nsISupports *aSubject, const char *aTopic, michael@0: const char16_t *aData) { michael@0: // We need to close any open notifications upon application exit, otherwise michael@0: // we will leak since libnotify holds a ref for us. michael@0: if (!nsCRT::strcmp(aTopic, "quit-application") && mNotification) { michael@0: g_signal_handler_disconnect(mNotification, mClosureHandler); michael@0: g_object_unref(mNotification); michael@0: mNotification = nullptr; michael@0: Release(); // equivalent to NS_RELEASE(this) michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsAlertsIconListener::InitAlertAsync(const nsAString & aImageUrl, michael@0: const nsAString & aAlertTitle, michael@0: const nsAString & aAlertText, michael@0: bool aAlertTextClickable, michael@0: const nsAString & aAlertCookie, michael@0: nsIObserver * aAlertListener) michael@0: { michael@0: if (!libNotifyHandle) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: if (!notify_is_initted()) { michael@0: // Give the name of this application to libnotify michael@0: nsCOMPtr bundleService = michael@0: do_GetService(NS_STRINGBUNDLE_CONTRACTID); michael@0: michael@0: nsAutoCString appShortName; michael@0: if (bundleService) { michael@0: nsCOMPtr bundle; michael@0: bundleService->CreateBundle("chrome://branding/locale/brand.properties", michael@0: getter_AddRefs(bundle)); michael@0: nsAutoString appName; michael@0: michael@0: if (bundle) { michael@0: bundle->GetStringFromName(MOZ_UTF16("brandShortName"), michael@0: getter_Copies(appName)); michael@0: appShortName = NS_ConvertUTF16toUTF8(appName); michael@0: } else { michael@0: NS_WARNING("brand.properties not present, using default application name"); michael@0: appShortName.AssignLiteral("Mozilla"); michael@0: } michael@0: } else { michael@0: appShortName.AssignLiteral("Mozilla"); michael@0: } michael@0: michael@0: if (!notify_init(appShortName.get())) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: GList *server_caps = notify_get_server_caps(); michael@0: if (server_caps) { michael@0: gHasCaps = true; michael@0: for (GList* cap = server_caps; cap != nullptr; cap = cap->next) { michael@0: if (!strcmp((char*) cap->data, "actions")) { michael@0: gHasActions = true; michael@0: break; michael@0: } michael@0: } michael@0: g_list_foreach(server_caps, (GFunc)g_free, nullptr); michael@0: g_list_free(server_caps); michael@0: } michael@0: } michael@0: michael@0: if (!gHasCaps) { michael@0: // if notify_get_server_caps() failed above we need to assume michael@0: // there is no notification-server to display anything michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (!gHasActions && aAlertTextClickable) michael@0: return NS_ERROR_FAILURE; // No good, fallback to XUL michael@0: michael@0: nsCOMPtr obsServ = michael@0: do_GetService("@mozilla.org/observer-service;1"); michael@0: if (obsServ) michael@0: obsServ->AddObserver(this, "quit-application", true); michael@0: michael@0: // Workaround for a libnotify bug - blank titles aren't dealt with michael@0: // properly so we use a space michael@0: if (aAlertTitle.IsEmpty()) { michael@0: mAlertTitle = NS_LITERAL_CSTRING(" "); michael@0: } else { michael@0: mAlertTitle = NS_ConvertUTF16toUTF8(aAlertTitle); michael@0: } michael@0: michael@0: mAlertText = NS_ConvertUTF16toUTF8(aAlertText); michael@0: mAlertHasAction = aAlertTextClickable; michael@0: michael@0: mAlertListener = aAlertListener; michael@0: mAlertCookie = aAlertCookie; michael@0: michael@0: return StartRequest(aImageUrl); michael@0: }