diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /toolkit/system/gnome | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/system/gnome')
-rw-r--r-- | toolkit/system/gnome/components.conf | 28 | ||||
-rw-r--r-- | toolkit/system/gnome/moz.build | 32 | ||||
-rw-r--r-- | toolkit/system/gnome/nsAlertsIconListener.cpp | 352 | ||||
-rw-r--r-- | toolkit/system/gnome/nsAlertsIconListener.h | 100 | ||||
-rw-r--r-- | toolkit/system/gnome/nsGIOService.cpp | 734 | ||||
-rw-r--r-- | toolkit/system/gnome/nsGIOService.h | 26 | ||||
-rw-r--r-- | toolkit/system/gnome/nsGSettingsService.cpp | 319 | ||||
-rw-r--r-- | toolkit/system/gnome/nsGSettingsService.h | 29 | ||||
-rw-r--r-- | toolkit/system/gnome/nsSystemAlertsService.cpp | 123 | ||||
-rw-r--r-- | toolkit/system/gnome/nsSystemAlertsService.h | 43 |
10 files changed, 1786 insertions, 0 deletions
diff --git a/toolkit/system/gnome/components.conf b/toolkit/system/gnome/components.conf new file mode 100644 index 0000000000..1aa0ab1048 --- /dev/null +++ b/toolkit/system/gnome/components.conf @@ -0,0 +1,28 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Classes = [ + { + 'cid': '{e3a1f3c9-3ae1-4b40-a5e0-7b457fc9a9ad}', + 'contract_ids': ['@mozilla.org/gio-service;1'], + 'type': 'nsGIOService', + 'headers': ['/toolkit/system/gnome/nsGIOService.h'], + }, + { + 'cid': '{bfd4a9d8-d886-4161-81ef-8868da114170}', + 'contract_ids': ['@mozilla.org/gsettings-service;1'], + 'type': 'nsGSettingsService', + 'headers': ['/toolkit/system/gnome/nsGSettingsService.h'], + 'init_method': 'Init', + }, + { + 'cid': '{84e11f80-ca55-11dd-ad8b-0800200c9a66}', + 'contract_ids': ['@mozilla.org/system-alerts-service;1'], + 'type': 'nsSystemAlertsService', + 'headers': ['/toolkit/system/gnome/nsSystemAlertsService.h'], + 'init_method': 'Init', + }, +] diff --git a/toolkit/system/gnome/moz.build b/toolkit/system/gnome/moz.build new file mode 100644 index 0000000000..0fc94ed9e3 --- /dev/null +++ b/toolkit/system/gnome/moz.build @@ -0,0 +1,32 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +with Files("**"): + BUG_COMPONENT = ("Firefox", "Shell Integration") + +SOURCES += [ + "nsAlertsIconListener.cpp", + "nsSystemAlertsService.cpp", +] + +SOURCES += [ + "nsGIOService.cpp", + "nsGSettingsService.cpp", +] + +XPCOM_MANIFESTS += [ + "components.conf", +] + +FINAL_LIBRARY = "xul" + +LOCAL_INCLUDES += [ + "/toolkit/components/build/", +] + +CXXFLAGS += CONFIG["GLIB_CFLAGS"] +CXXFLAGS += CONFIG["MOZ_DBUS_GLIB_CFLAGS"] +CXXFLAGS += CONFIG["TK_CFLAGS"] diff --git a/toolkit/system/gnome/nsAlertsIconListener.cpp b/toolkit/system/gnome/nsAlertsIconListener.cpp new file mode 100644 index 0000000000..9c3d796c9d --- /dev/null +++ b/toolkit/system/gnome/nsAlertsIconListener.cpp @@ -0,0 +1,352 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsAlertsIconListener.h" +#include "imgIContainer.h" +#include "imgIRequest.h" +#include "nsNetUtil.h" +#include "nsServiceManagerUtils.h" +#include "nsSystemAlertsService.h" +#include "nsIAlertsService.h" +#include "nsICancelable.h" +#include "nsIImageToPixbuf.h" +#include "nsIStringBundle.h" +#include "nsIObserverService.h" +#include "nsCRT.h" +#include "mozilla/XREAppData.h" + +#include <dlfcn.h> +#include <gdk/gdk.h> + +extern const mozilla::StaticXREAppData* gAppData; + +static bool gHasActions = false; +static bool gHasCaps = false; + +void* nsAlertsIconListener::libNotifyHandle = nullptr; +bool nsAlertsIconListener::libNotifyNotAvail = false; +nsAlertsIconListener::notify_is_initted_t + nsAlertsIconListener::notify_is_initted = nullptr; +nsAlertsIconListener::notify_init_t nsAlertsIconListener::notify_init = nullptr; +nsAlertsIconListener::notify_get_server_caps_t + nsAlertsIconListener::notify_get_server_caps = nullptr; +nsAlertsIconListener::notify_notification_new_t + nsAlertsIconListener::notify_notification_new = nullptr; +nsAlertsIconListener::notify_notification_show_t + nsAlertsIconListener::notify_notification_show = nullptr; +nsAlertsIconListener::notify_notification_set_icon_from_pixbuf_t + nsAlertsIconListener::notify_notification_set_icon_from_pixbuf = nullptr; +nsAlertsIconListener::notify_notification_add_action_t + nsAlertsIconListener::notify_notification_add_action = nullptr; +nsAlertsIconListener::notify_notification_close_t + nsAlertsIconListener::notify_notification_close = nullptr; +nsAlertsIconListener::notify_notification_set_hint_t + nsAlertsIconListener::notify_notification_set_hint = nullptr; + +static void notify_action_cb(NotifyNotification* notification, gchar* action, + gpointer user_data) { + nsAlertsIconListener* alert = static_cast<nsAlertsIconListener*>(user_data); + alert->SendCallback(); +} + +static void notify_closed_marshal(GClosure* closure, GValue* return_value, + guint n_param_values, + const GValue* param_values, + gpointer invocation_hint, + gpointer marshal_data) { + MOZ_ASSERT(n_param_values >= 1, "No object in params"); + + nsAlertsIconListener* alert = + static_cast<nsAlertsIconListener*>(closure->data); + alert->SendClosed(); + NS_RELEASE(alert); +} + +static GdkPixbuf* GetPixbufFromImgRequest(imgIRequest* aRequest) { + nsCOMPtr<imgIContainer> image; + nsresult rv = aRequest->GetImage(getter_AddRefs(image)); + if (NS_FAILED(rv)) { + return nullptr; + } + + int32_t width = 0, height = 0; + const int32_t kBytesPerPixel = 4; + // DBUS_MAXIMUM_ARRAY_LENGTH is 64M, there is 60 bytes overhead + // for the hints array with only the image payload, 256 is used to give + // some breathing room. + const int32_t kMaxImageBytes = 64 * 1024 * 1024 - 256; + image->GetWidth(&width); + image->GetHeight(&height); + if (width * height * kBytesPerPixel > kMaxImageBytes) { + // The image won't fit in a dbus array + return nullptr; + } + + nsCOMPtr<nsIImageToPixbuf> imgToPixbuf = + do_GetService("@mozilla.org/widget/image-to-gdk-pixbuf;1"); + + return imgToPixbuf->ConvertImageToPixbuf(image); +} + +NS_IMPL_ISUPPORTS(nsAlertsIconListener, nsIAlertNotificationImageListener, + nsIObserver, nsISupportsWeakReference) + +nsAlertsIconListener::nsAlertsIconListener(nsSystemAlertsService* aBackend, + const nsAString& aAlertName) + : mAlertName(aAlertName), mBackend(aBackend), mNotification(nullptr) { + if (!libNotifyHandle && !libNotifyNotAvail) { + libNotifyHandle = dlopen("libnotify.so.4", RTLD_LAZY); + if (!libNotifyHandle) { + libNotifyHandle = dlopen("libnotify.so.1", RTLD_LAZY); + if (!libNotifyHandle) { + libNotifyNotAvail = true; + return; + } + } + + notify_is_initted = + (notify_is_initted_t)dlsym(libNotifyHandle, "notify_is_initted"); + notify_init = (notify_init_t)dlsym(libNotifyHandle, "notify_init"); + notify_get_server_caps = (notify_get_server_caps_t)dlsym( + libNotifyHandle, "notify_get_server_caps"); + notify_notification_new = (notify_notification_new_t)dlsym( + libNotifyHandle, "notify_notification_new"); + notify_notification_show = (notify_notification_show_t)dlsym( + libNotifyHandle, "notify_notification_show"); + notify_notification_set_icon_from_pixbuf = + (notify_notification_set_icon_from_pixbuf_t)dlsym( + libNotifyHandle, "notify_notification_set_icon_from_pixbuf"); + notify_notification_add_action = (notify_notification_add_action_t)dlsym( + libNotifyHandle, "notify_notification_add_action"); + notify_notification_close = (notify_notification_close_t)dlsym( + libNotifyHandle, "notify_notification_close"); + notify_notification_set_hint = (notify_notification_set_hint_t)dlsym( + libNotifyHandle, "notify_notification_set_hint"); + 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) { + dlclose(libNotifyHandle); + libNotifyHandle = nullptr; + } + } +} + +nsAlertsIconListener::~nsAlertsIconListener() { + mBackend->RemoveListener(mAlertName, this); + // Don't dlclose libnotify as it uses atexit(). +} + +NS_IMETHODIMP +nsAlertsIconListener::OnImageMissing(nsISupports*) { + // This notification doesn't have an image, or there was an error getting + // the image. Show the notification without an icon. + return ShowAlert(nullptr); +} + +NS_IMETHODIMP +nsAlertsIconListener::OnImageReady(nsISupports*, imgIRequest* aRequest) { + GdkPixbuf* imagePixbuf = GetPixbufFromImgRequest(aRequest); + if (!imagePixbuf) { + ShowAlert(nullptr); + } else { + ShowAlert(imagePixbuf); + g_object_unref(imagePixbuf); + } + + return NS_OK; +} + +nsresult nsAlertsIconListener::ShowAlert(GdkPixbuf* aPixbuf) { + if (!mBackend->IsActiveListener(mAlertName, this)) return NS_OK; + + mNotification = notify_notification_new(mAlertTitle.get(), mAlertText.get(), + nullptr, nullptr); + + if (!mNotification) return NS_ERROR_OUT_OF_MEMORY; + + nsCOMPtr<nsIObserverService> obsServ = + do_GetService("@mozilla.org/observer-service;1"); + if (obsServ) obsServ->AddObserver(this, "quit-application", true); + + if (aPixbuf) notify_notification_set_icon_from_pixbuf(mNotification, aPixbuf); + + NS_ADDREF(this); + if (mAlertHasAction) { + // What we put as the label doesn't matter here, if the action + // string is "default" then that makes the entire bubble clickable + // rather than creating a button. + notify_notification_add_action(mNotification, "default", "Activate", + notify_action_cb, this, nullptr); + } + + if (notify_notification_set_hint) { + // If MOZ_DESKTOP_FILE_NAME variable is set, use it as the application id, + // otherwise use gAppData->name + if (getenv("MOZ_DESKTOP_FILE_NAME")) { + // Send the desktop name to identify the application + // The desktop-entry is the part before the .desktop + notify_notification_set_hint( + mNotification, "desktop-entry", + g_variant_new("s", getenv("MOZ_DESKTOP_FILE_NAME"))); + } else { + notify_notification_set_hint(mNotification, "desktop-entry", + g_variant_new("s", gAppData->remotingName)); + } + } + + // Fedora 10 calls NotifyNotification "closed" signal handlers with a + // different signature, so a marshaller is used instead of a C callback to + // get the user_data (this) in a parseable format. |closure| is created + // with a floating reference, which gets sunk by g_signal_connect_closure(). + GClosure* closure = g_closure_new_simple(sizeof(GClosure), this); + g_closure_set_marshal(closure, notify_closed_marshal); + mClosureHandler = + g_signal_connect_closure(mNotification, "closed", closure, FALSE); + GError* error = nullptr; + if (!notify_notification_show(mNotification, &error)) { + NS_WARNING(error->message); + g_error_free(error); + return NS_ERROR_FAILURE; + } + + if (mAlertListener) + mAlertListener->Observe(nullptr, "alertshow", mAlertCookie.get()); + + return NS_OK; +} + +void nsAlertsIconListener::SendCallback() { + if (mAlertListener) + mAlertListener->Observe(nullptr, "alertclickcallback", mAlertCookie.get()); +} + +void nsAlertsIconListener::SendClosed() { + if (mNotification) { + g_object_unref(mNotification); + mNotification = nullptr; + } + NotifyFinished(); +} + +NS_IMETHODIMP +nsAlertsIconListener::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + // We need to close any open notifications upon application exit, otherwise + // we will leak since libnotify holds a ref for us. + if (!nsCRT::strcmp(aTopic, "quit-application") && mNotification) { + g_signal_handler_disconnect(mNotification, mClosureHandler); + g_object_unref(mNotification); + mNotification = nullptr; + Release(); // equivalent to NS_RELEASE(this) + } + return NS_OK; +} + +nsresult nsAlertsIconListener::Close() { + if (mIconRequest) { + mIconRequest->Cancel(NS_BINDING_ABORTED); + mIconRequest = nullptr; + } + + if (!mNotification) { + NotifyFinished(); + return NS_OK; + } + + GError* error = nullptr; + if (!notify_notification_close(mNotification, &error)) { + NS_WARNING(error->message); + g_error_free(error); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult nsAlertsIconListener::InitAlertAsync(nsIAlertNotification* aAlert, + nsIObserver* aAlertListener) { + if (!libNotifyHandle) return NS_ERROR_FAILURE; + + if (!notify_is_initted()) { + // Give the name of this application to libnotify + nsCOMPtr<nsIStringBundleService> bundleService = + do_GetService(NS_STRINGBUNDLE_CONTRACTID); + + nsAutoCString appShortName; + if (bundleService) { + nsCOMPtr<nsIStringBundle> bundle; + bundleService->CreateBundle("chrome://branding/locale/brand.properties", + getter_AddRefs(bundle)); + nsAutoString appName; + + if (bundle) { + bundle->GetStringFromName("brandShortName", appName); + CopyUTF16toUTF8(appName, appShortName); + } else { + NS_WARNING( + "brand.properties not present, using default application name"); + appShortName.AssignLiteral("Mozilla"); + } + } else { + appShortName.AssignLiteral("Mozilla"); + } + + if (!notify_init(appShortName.get())) return NS_ERROR_FAILURE; + + GList* server_caps = notify_get_server_caps(); + if (server_caps) { + gHasCaps = true; + for (GList* cap = server_caps; cap != nullptr; cap = cap->next) { + if (!strcmp((char*)cap->data, "actions")) { + gHasActions = true; + break; + } + } + g_list_foreach(server_caps, (GFunc)g_free, nullptr); + g_list_free(server_caps); + } + } + + if (!gHasCaps) { + // if notify_get_server_caps() failed above we need to assume + // there is no notification-server to display anything + return NS_ERROR_FAILURE; + } + + nsresult rv = aAlert->GetTextClickable(&mAlertHasAction); + NS_ENSURE_SUCCESS(rv, rv); + if (!gHasActions && mAlertHasAction) + return NS_ERROR_FAILURE; // No good, fallback to XUL + + nsAutoString title; + rv = aAlert->GetTitle(title); + NS_ENSURE_SUCCESS(rv, rv); + // Workaround for a libnotify bug - blank titles aren't dealt with + // properly so we use a space + if (title.IsEmpty()) { + mAlertTitle = " "_ns; + } else { + CopyUTF16toUTF8(title, mAlertTitle); + } + + nsAutoString text; + rv = aAlert->GetText(text); + NS_ENSURE_SUCCESS(rv, rv); + CopyUTF16toUTF8(text, mAlertText); + + mAlertListener = aAlertListener; + + rv = aAlert->GetCookie(mAlertCookie); + NS_ENSURE_SUCCESS(rv, rv); + + return aAlert->LoadImage(/* aTimeout = */ 0, this, /* aUserData = */ nullptr, + getter_AddRefs(mIconRequest)); +} + +void nsAlertsIconListener::NotifyFinished() { + if (mAlertListener) + mAlertListener->Observe(nullptr, "alertfinished", mAlertCookie.get()); +} diff --git a/toolkit/system/gnome/nsAlertsIconListener.h b/toolkit/system/gnome/nsAlertsIconListener.h new file mode 100644 index 0000000000..fc98e0ff1f --- /dev/null +++ b/toolkit/system/gnome/nsAlertsIconListener.h @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsAlertsIconListener_h__ +#define nsAlertsIconListener_h__ + +#include "nsCOMPtr.h" +#include "nsIAlertsService.h" +#include "nsString.h" +#include "nsIObserver.h" +#include "nsWeakReference.h" + +#include <gdk-pixbuf/gdk-pixbuf.h> + +class nsIAlertNotification; +class nsICancelable; +class nsSystemAlertsService; + +struct NotifyNotification; + +class nsAlertsIconListener : public nsIAlertNotificationImageListener, + public nsIObserver, + public nsSupportsWeakReference { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIALERTNOTIFICATIONIMAGELISTENER + NS_DECL_NSIOBSERVER + + nsAlertsIconListener(nsSystemAlertsService* aBackend, + const nsAString& aAlertName); + + nsresult InitAlertAsync(nsIAlertNotification* aAlert, + nsIObserver* aAlertListener); + nsresult Close(); + + void SendCallback(); + void SendClosed(); + + protected: + virtual ~nsAlertsIconListener(); + + /** + * The only difference between libnotify.so.4 and libnotify.so.1 for these + * symbols is that notify_notification_new takes three arguments in + * libnotify.so.4 and four in libnotify.so.1. Passing the fourth argument as + * NULL is binary compatible. + */ + typedef void (*NotifyActionCallback)(NotifyNotification*, char*, gpointer); + typedef bool (*notify_is_initted_t)(void); + typedef bool (*notify_init_t)(const char*); + typedef GList* (*notify_get_server_caps_t)(void); + typedef NotifyNotification* (*notify_notification_new_t)(const char*, + const char*, + const char*, + const char*); + typedef bool (*notify_notification_show_t)(void*, GError**); + typedef void (*notify_notification_set_icon_from_pixbuf_t)(void*, GdkPixbuf*); + typedef void (*notify_notification_add_action_t)(void*, const char*, + const char*, + NotifyActionCallback, + gpointer, GFreeFunc); + typedef bool (*notify_notification_close_t)(void*, GError**); + typedef void (*notify_notification_set_hint_t)(NotifyNotification*, + const char*, GVariant*); + + nsCOMPtr<nsICancelable> mIconRequest; + nsCString mAlertTitle; + nsCString mAlertText; + + nsCOMPtr<nsIObserver> mAlertListener; + nsString mAlertCookie; + nsString mAlertName; + + RefPtr<nsSystemAlertsService> mBackend; + + bool mAlertHasAction; + + static void* libNotifyHandle; + static bool libNotifyNotAvail; + static notify_is_initted_t notify_is_initted; + static notify_init_t notify_init; + static notify_get_server_caps_t notify_get_server_caps; + static notify_notification_new_t notify_notification_new; + static notify_notification_show_t notify_notification_show; + static notify_notification_set_icon_from_pixbuf_t + notify_notification_set_icon_from_pixbuf; + static notify_notification_add_action_t notify_notification_add_action; + static notify_notification_close_t notify_notification_close; + static notify_notification_set_hint_t notify_notification_set_hint; + NotifyNotification* mNotification; + gulong mClosureHandler; + + nsresult ShowAlert(GdkPixbuf* aPixbuf); + + void NotifyFinished(); +}; + +#endif diff --git a/toolkit/system/gnome/nsGIOService.cpp b/toolkit/system/gnome/nsGIOService.cpp new file mode 100644 index 0000000000..867e6c0ee0 --- /dev/null +++ b/toolkit/system/gnome/nsGIOService.cpp @@ -0,0 +1,734 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsGIOService.h" +#include "nsString.h" +#include "nsIURI.h" +#include "nsTArray.h" +#include "nsStringEnumerator.h" +#include "nsIMIMEInfo.h" +#include "nsComponentManagerUtils.h" +#include "nsArray.h" +#include "nsPrintfCString.h" +#include "mozilla/Preferences.h" + +#include <gio/gio.h> +#include <gtk/gtk.h> +#ifdef MOZ_ENABLE_DBUS +# include <dbus/dbus-glib.h> +# include <dbus/dbus-glib-lowlevel.h> +#endif + +using namespace mozilla; + +// s. a. the code gtk_should_use_portal() uses to detect if in flatpak env +// https://gitlab.gnome.org/GNOME/gtk/-/blob/4300a5c609306ce77cbc8a3580c19201dccd8d13/gdk/gdk.c#L472 +static bool GetFlatpakPortalEnv() { + bool shouldUsePortal; + if (g_file_test("/.flatpak-info", G_FILE_TEST_EXISTS)) { + shouldUsePortal = true; + } else { + const char* portalEnvString = g_getenv("GTK_USE_PORTAL"); + shouldUsePortal = portalEnvString != nullptr && atoi(portalEnvString) != 0; + } + return shouldUsePortal; +} + +static bool GetShouldUseFlatpakPortal() { + static bool sFlatpakPortalEnv = GetFlatpakPortalEnv(); + return Preferences::HasUserValue("widget.use-xdg-desktop-portal") + ? Preferences::GetBool("widget.use-xdg-desktop-portal", false) + : sFlatpakPortalEnv; +} + +class nsFlatpakHandlerApp : public nsIHandlerApp { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIHANDLERAPP + nsFlatpakHandlerApp() = default; + + private: + virtual ~nsFlatpakHandlerApp() = default; +}; + +NS_IMPL_ISUPPORTS(nsFlatpakHandlerApp, nsIHandlerApp) + +NS_IMETHODIMP +nsFlatpakHandlerApp::GetName(nsAString& aName) { + aName.AssignLiteral("System Handler"); + return NS_OK; +} + +NS_IMETHODIMP +nsFlatpakHandlerApp::SetName(const nsAString& aName) { + // We don't implement SetName because flatpak system handler name is fixed + return NS_OK; +} + +NS_IMETHODIMP +nsFlatpakHandlerApp::GetDetailedDescription(nsAString& aDetailedDescription) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsFlatpakHandlerApp::SetDetailedDescription( + const nsAString& aDetailedDescription) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsFlatpakHandlerApp::Equals(nsIHandlerApp* aHandlerApp, bool* _retval) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsFlatpakHandlerApp::LaunchWithURI( + nsIURI* aUri, mozilla::dom::BrowsingContext* aBrowsingContext) { + nsCString spec; + aUri->GetSpec(spec); + GError* error = nullptr; + + // The TMPDIR where files are downloaded when user choose to open them + // needs to be accessible from sandbox and host. The default settings + // TMPDIR=/tmp is accessible only to the sandbox. That can be the reason + // why the gtk_show_uri fails there. + // The workaround is to set TMPDIR environment variable in sandbox to + // $XDG_CACHE_HOME/tmp before executing Firefox. + gtk_show_uri(nullptr, spec.get(), GDK_CURRENT_TIME, &error); + if (error) { + NS_WARNING( + nsPrintfCString("Cannot launch flatpak handler: %s", error->message) + .get()); + g_error_free(error); + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +/** + * Get command without any additional arguments + * @param aCommandWithArguments full commandline input string + * @param aCommand string for storing command without arguments + * @return NS_ERROR_FAILURE when unable to parse commandline + */ +static nsresult GetCommandFromCommandline( + nsACString const& aCommandWithArguments, nsACString& aCommand) { + GError* error = nullptr; + gchar** argv = nullptr; + if (!g_shell_parse_argv(aCommandWithArguments.BeginReading(), nullptr, &argv, + &error) || + !argv[0]) { + g_warning("Cannot parse command with arguments: %s", error->message); + g_error_free(error); + g_strfreev(argv); + return NS_ERROR_FAILURE; + } + aCommand.Assign(argv[0]); + g_strfreev(argv); + return NS_OK; +} + +class nsGIOMimeApp final : public nsIGIOMimeApp { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIHANDLERAPP + NS_DECL_NSIGIOMIMEAPP + + explicit nsGIOMimeApp(GAppInfo* aApp) : mApp(aApp) {} + + private: + ~nsGIOMimeApp() { g_object_unref(mApp); } + + GAppInfo* mApp; +}; + +NS_IMPL_ISUPPORTS(nsGIOMimeApp, nsIGIOMimeApp, nsIHandlerApp) + +NS_IMETHODIMP +nsGIOMimeApp::GetId(nsACString& aId) { + aId.Assign(g_app_info_get_id(mApp)); + return NS_OK; +} + +NS_IMETHODIMP +nsGIOMimeApp::GetName(nsAString& aName) { + aName.Assign(NS_ConvertUTF8toUTF16(g_app_info_get_name(mApp))); + return NS_OK; +} + +NS_IMETHODIMP +nsGIOMimeApp::SetName(const nsAString& aName) { + // We don't implement SetName because we're using mGIOMimeApp instance for + // obtaining application name + return NS_OK; +} + +NS_IMETHODIMP +nsGIOMimeApp::GetCommand(nsACString& aCommand) { + const char* cmd = g_app_info_get_commandline(mApp); + if (!cmd) return NS_ERROR_FAILURE; + aCommand.Assign(cmd); + return NS_OK; +} + +NS_IMETHODIMP +nsGIOMimeApp::GetExpectsURIs(int32_t* aExpects) { + *aExpects = g_app_info_supports_uris(mApp); + return NS_OK; +} + +NS_IMETHODIMP +nsGIOMimeApp::GetDetailedDescription(nsAString& aDetailedDescription) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsGIOMimeApp::SetDetailedDescription(const nsAString& aDetailedDescription) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsGIOMimeApp::Equals(nsIHandlerApp* aHandlerApp, bool* _retval) { + if (!aHandlerApp) return NS_ERROR_FAILURE; + + // Compare with nsILocalHandlerApp instance by name + nsCOMPtr<nsILocalHandlerApp> localHandlerApp = do_QueryInterface(aHandlerApp); + if (localHandlerApp) { + nsAutoString theirName; + nsAutoString thisName; + GetName(thisName); + localHandlerApp->GetName(theirName); + *_retval = thisName.Equals(theirName); + return NS_OK; + } + + // Compare with nsIGIOMimeApp instance by command with stripped arguments + nsCOMPtr<nsIGIOMimeApp> gioMimeApp = do_QueryInterface(aHandlerApp); + if (gioMimeApp) { + nsAutoCString thisCommandline, thisCommand; + nsresult rv = GetCommand(thisCommandline); + NS_ENSURE_SUCCESS(rv, rv); + + rv = GetCommandFromCommandline(thisCommandline, thisCommand); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString theirCommandline, theirCommand; + gioMimeApp->GetCommand(theirCommandline); + NS_ENSURE_SUCCESS(rv, rv); + + rv = GetCommandFromCommandline(theirCommandline, theirCommand); + NS_ENSURE_SUCCESS(rv, rv); + + *_retval = thisCommand.Equals(theirCommand); + return NS_OK; + } + + // We can only compare with nsILocalHandlerApp and nsGIOMimeApp + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP +nsGIOMimeApp::LaunchWithURI(nsIURI* aUri, + mozilla::dom::BrowsingContext* aBrowsingContext) { + GList uris = {0}; + nsCString spec; + aUri->GetSpec(spec); + // nsPromiseFlatCString flatUri(aUri); + uris.data = const_cast<char*>(spec.get()); + + GError* error = nullptr; + gboolean result = g_app_info_launch_uris(mApp, &uris, nullptr, &error); + + if (!result) { + g_warning("Cannot launch application: %s", error->message); + g_error_free(error); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +class GIOUTF8StringEnumerator final : public nsStringEnumeratorBase { + ~GIOUTF8StringEnumerator() = default; + + public: + GIOUTF8StringEnumerator() : mIndex(0) {} + + NS_DECL_ISUPPORTS + NS_DECL_NSIUTF8STRINGENUMERATOR + + using nsStringEnumeratorBase::GetNext; + + nsTArray<nsCString> mStrings; + uint32_t mIndex; +}; + +NS_IMPL_ISUPPORTS(GIOUTF8StringEnumerator, nsIUTF8StringEnumerator, + nsIStringEnumerator) + +NS_IMETHODIMP +GIOUTF8StringEnumerator::HasMore(bool* aResult) { + *aResult = mIndex < mStrings.Length(); + return NS_OK; +} + +NS_IMETHODIMP +GIOUTF8StringEnumerator::GetNext(nsACString& aResult) { + if (mIndex >= mStrings.Length()) return NS_ERROR_UNEXPECTED; + + aResult.Assign(mStrings[mIndex]); + ++mIndex; + return NS_OK; +} + +NS_IMETHODIMP +nsGIOMimeApp::GetSupportedURISchemes(nsIUTF8StringEnumerator** aSchemes) { + *aSchemes = nullptr; + + RefPtr<GIOUTF8StringEnumerator> array = new GIOUTF8StringEnumerator(); + NS_ENSURE_TRUE(array, NS_ERROR_OUT_OF_MEMORY); + + GVfs* gvfs = g_vfs_get_default(); + + if (!gvfs) { + g_warning("Cannot get GVfs object."); + return NS_ERROR_OUT_OF_MEMORY; + } + + const gchar* const* uri_schemes = g_vfs_get_supported_uri_schemes(gvfs); + + while (*uri_schemes != nullptr) { + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier. + array->mStrings.AppendElement(*uri_schemes); + uri_schemes++; + } + + array.forget(aSchemes); + return NS_OK; +} + +NS_IMETHODIMP +nsGIOMimeApp::SetAsDefaultForMimeType(nsACString const& aMimeType) { + char* content_type = + g_content_type_from_mime_type(PromiseFlatCString(aMimeType).get()); + if (!content_type) return NS_ERROR_FAILURE; + GError* error = nullptr; + g_app_info_set_as_default_for_type(mApp, content_type, &error); + if (error) { + g_warning("Cannot set application as default for MIME type (%s): %s", + PromiseFlatCString(aMimeType).get(), error->message); + g_error_free(error); + g_free(content_type); + return NS_ERROR_FAILURE; + } + + g_free(content_type); + return NS_OK; +} +/** + * Set default application for files with given extensions + * @param fileExts string of space separated extensions + * @return NS_OK when application was set as default for given extensions, + * NS_ERROR_FAILURE otherwise + */ +NS_IMETHODIMP +nsGIOMimeApp::SetAsDefaultForFileExtensions(nsACString const& fileExts) { + GError* error = nullptr; + char* extensions = g_strdup(PromiseFlatCString(fileExts).get()); + char* ext_pos = extensions; + char* space_pos; + + while ((space_pos = strchr(ext_pos, ' ')) || (*ext_pos != '\0')) { + if (space_pos) { + *space_pos = '\0'; + } + g_app_info_set_as_default_for_extension(mApp, ext_pos, &error); + if (error) { + g_warning("Cannot set application as default for extension (%s): %s", + ext_pos, error->message); + g_error_free(error); + g_free(extensions); + return NS_ERROR_FAILURE; + } + if (space_pos) { + ext_pos = space_pos + 1; + } else { + *ext_pos = '\0'; + } + } + g_free(extensions); + return NS_OK; +} + +/** + * Set default application for URI's of a particular scheme + * @param aURIScheme string containing the URI scheme + * @return NS_OK when application was set as default for URI scheme, + * NS_ERROR_FAILURE otherwise + */ +NS_IMETHODIMP +nsGIOMimeApp::SetAsDefaultForURIScheme(nsACString const& aURIScheme) { + GError* error = nullptr; + nsAutoCString contentType("x-scheme-handler/"); + contentType.Append(aURIScheme); + + g_app_info_set_as_default_for_type(mApp, contentType.get(), &error); + if (error) { + g_warning("Cannot set application as default for URI scheme (%s): %s", + PromiseFlatCString(aURIScheme).get(), error->message); + g_error_free(error); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsGIOService, nsIGIOService) + +NS_IMETHODIMP +nsGIOService::GetMimeTypeFromExtension(const nsACString& aExtension, + nsACString& aMimeType) { + nsAutoCString fileExtToUse("file."); + fileExtToUse.Append(aExtension); + + gboolean result_uncertain; + char* content_type = + g_content_type_guess(fileExtToUse.get(), nullptr, 0, &result_uncertain); + if (!content_type) return NS_ERROR_FAILURE; + + char* mime_type = g_content_type_get_mime_type(content_type); + if (!mime_type) { + g_free(content_type); + return NS_ERROR_FAILURE; + } + + aMimeType.Assign(mime_type); + + g_free(mime_type); + g_free(content_type); + + return NS_OK; +} +// used in nsGNOMERegistry +// ----------------------------------------------------------------------------- +NS_IMETHODIMP +nsGIOService::GetAppForURIScheme(const nsACString& aURIScheme, + nsIHandlerApp** aApp) { + *aApp = nullptr; + + // Application in flatpak sandbox does not have access to the list + // of installed applications on the system. We use generic + // nsFlatpakHandlerApp which forwards launch call to the system. + if (GetShouldUseFlatpakPortal()) { + nsFlatpakHandlerApp* mozApp = new nsFlatpakHandlerApp(); + NS_ADDREF(*aApp = mozApp); + return NS_OK; + } + + GAppInfo* app_info = g_app_info_get_default_for_uri_scheme( + PromiseFlatCString(aURIScheme).get()); + if (app_info) { + nsGIOMimeApp* mozApp = new nsGIOMimeApp(app_info); + NS_ADDREF(*aApp = mozApp); + } else { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +nsGIOService::GetAppsForURIScheme(const nsACString& aURIScheme, + nsIMutableArray** aResult) { + // We don't need to return the nsFlatpakHandlerApp here because + // it would be skipped by the callers anyway. + // The preferred handler is provided by GetAppForURIScheme. + // This method returns all possible application handlers + // including preferred one. The callers skips the preferred + // handler in this list to avoid duplicate records in the list + // they create. + nsCOMPtr<nsIMutableArray> handlersArray = + do_CreateInstance(NS_ARRAY_CONTRACTID); + + nsAutoCString contentType("x-scheme-handler/"); + contentType.Append(aURIScheme); + + GList* appInfoList = g_app_info_get_all_for_type(contentType.get()); + // g_app_info_get_all_for_type returns NULL when no appinfo is found + // or error occurs (contentType is NULL). We are fine with empty app list + // and we're sure that contentType is not NULL, so we won't return failure. + if (appInfoList) { + GList* appInfo = appInfoList; + while (appInfo) { + nsCOMPtr<nsIGIOMimeApp> mimeApp = + new nsGIOMimeApp(G_APP_INFO(appInfo->data)); + handlersArray->AppendElement(mimeApp); + appInfo = appInfo->next; + } + g_list_free(appInfoList); + } + NS_ADDREF(*aResult = handlersArray); + return NS_OK; +} + +NS_IMETHODIMP +nsGIOService::GetAppForMimeType(const nsACString& aMimeType, + nsIHandlerApp** aApp) { + *aApp = nullptr; + + // Flatpak does not reveal installed application to the sandbox, + // we need to create generic system handler. + if (GetShouldUseFlatpakPortal()) { + nsFlatpakHandlerApp* mozApp = new nsFlatpakHandlerApp(); + NS_ADDREF(*aApp = mozApp); + return NS_OK; + } + + char* content_type = + g_content_type_from_mime_type(PromiseFlatCString(aMimeType).get()); + if (!content_type) return NS_ERROR_FAILURE; + + // GIO returns "unknown" appinfo for the application/octet-stream, which is + // useless. It's better to fallback to create appinfo from file extension + // later. + if (g_content_type_is_unknown(content_type)) { + return NS_ERROR_NOT_AVAILABLE; + } + +#if defined(__OpenBSD__) && defined(MOZ_SANDBOX) + // g_app_info_get_default_for_type will fail on OpenBSD's veiled filesystem + // since we most likely don't have direct access to the binaries that are + // registered as defaults for this type. Fake it up by just executing + // xdg-open via gio-launch-desktop (which we do have access to) and letting + // it figure out which program to execute for this MIME type + GAppInfo* app_info = g_app_info_create_from_commandline( + "/usr/local/bin/xdg-open", + nsPrintfCString("System default for %s", content_type).get(), + G_APP_INFO_CREATE_NONE, NULL); +#else + GAppInfo* app_info = g_app_info_get_default_for_type(content_type, false); +#endif + if (app_info) { + nsGIOMimeApp* mozApp = new nsGIOMimeApp(app_info); + NS_ENSURE_TRUE(mozApp, NS_ERROR_OUT_OF_MEMORY); + NS_ADDREF(*aApp = mozApp); + } else { + g_free(content_type); + return NS_ERROR_FAILURE; + } + g_free(content_type); + return NS_OK; +} + +NS_IMETHODIMP +nsGIOService::GetDescriptionForMimeType(const nsACString& aMimeType, + nsACString& aDescription) { + char* content_type = + g_content_type_from_mime_type(PromiseFlatCString(aMimeType).get()); + if (!content_type) return NS_ERROR_FAILURE; + + char* desc = g_content_type_get_description(content_type); + if (!desc) { + g_free(content_type); + return NS_ERROR_FAILURE; + } + + aDescription.Assign(desc); + g_free(content_type); + g_free(desc); + return NS_OK; +} + +NS_IMETHODIMP +nsGIOService::ShowURI(nsIURI* aURI) { + nsAutoCString spec; + nsresult rv = aURI->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + GError* error = nullptr; + if (!g_app_info_launch_default_for_uri(spec.get(), nullptr, &error)) { + g_warning("Could not launch default application for URI: %s", + error->message); + g_error_free(error); + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +nsGIOService::ShowURIForInput(const nsACString& aUri) { + GFile* file = g_file_new_for_commandline_arg(PromiseFlatCString(aUri).get()); + char* spec = g_file_get_uri(file); + nsresult rv = NS_ERROR_FAILURE; + GError* error = nullptr; + + g_app_info_launch_default_for_uri(spec, nullptr, &error); + if (error) { + g_warning("Cannot launch default application: %s", error->message); + g_error_free(error); + } else { + rv = NS_OK; + } + g_object_unref(file); + g_free(spec); + + return rv; +} + +NS_IMETHODIMP +nsGIOService::OrgFreedesktopFileManager1ShowItems(const nsACString& aPath) { +#ifndef MOZ_ENABLE_DBUS + return NS_ERROR_FAILURE; +#else + GError* error = nullptr; + static bool org_freedesktop_FileManager1_exists = true; + + if (!org_freedesktop_FileManager1_exists) { + return NS_ERROR_NOT_AVAILABLE; + } + + DBusGConnection* dbusGConnection = dbus_g_bus_get(DBUS_BUS_SESSION, &error); + + if (!dbusGConnection) { + if (error) { + g_printerr("Failed to open connection to session bus: %s\n", + error->message); + g_error_free(error); + } + return NS_ERROR_FAILURE; + } + + char* uri = + g_filename_to_uri(PromiseFlatCString(aPath).get(), nullptr, nullptr); + if (uri == nullptr) { + return NS_ERROR_FAILURE; + } + + DBusConnection* dbusConnection = + dbus_g_connection_get_connection(dbusGConnection); + // Make sure we do not exit the entire program if DBus connection get lost. + dbus_connection_set_exit_on_disconnect(dbusConnection, false); + + DBusGProxy* dbusGProxy = dbus_g_proxy_new_for_name( + dbusGConnection, "org.freedesktop.FileManager1", + "/org/freedesktop/FileManager1", "org.freedesktop.FileManager1"); + + const char* uris[2] = {uri, nullptr}; + gboolean rv_dbus_call = + dbus_g_proxy_call(dbusGProxy, "ShowItems", nullptr, G_TYPE_STRV, uris, + G_TYPE_STRING, "", G_TYPE_INVALID, G_TYPE_INVALID); + + g_object_unref(dbusGProxy); + dbus_g_connection_unref(dbusGConnection); + g_free(uri); + + if (!rv_dbus_call) { + org_freedesktop_FileManager1_exists = false; + return NS_ERROR_NOT_AVAILABLE; + } + + return NS_OK; +#endif +} + +/** + * Find GIO Mime App from given commandline. + * This is different from CreateAppFromCommand because instead of creating the + * GIO Mime App in case it's not found in the GIO application list, the method + * returns error. + * @param aCmd command with parameters used to start the application + * @return NS_OK when application is found, NS_ERROR_NOT_AVAILABLE otherwise + */ +NS_IMETHODIMP +nsGIOService::FindAppFromCommand(nsACString const& aCmd, + nsIGIOMimeApp** aAppInfo) { + GAppInfo *app_info = nullptr, *app_info_from_list = nullptr; + GList* apps = g_app_info_get_all(); + GList* apps_p = apps; + + // Try to find relevant and existing GAppInfo in all installed application + // We do this by comparing each GAppInfo's executable with out own + while (apps_p) { + app_info_from_list = (GAppInfo*)apps_p->data; + if (!app_info) { + // If the executable is not absolute, get it's full path + char* executable = + g_find_program_in_path(g_app_info_get_executable(app_info_from_list)); + + if (executable && + strcmp(executable, PromiseFlatCString(aCmd).get()) == 0) { + g_object_ref(app_info_from_list); + app_info = app_info_from_list; + } + g_free(executable); + } + + g_object_unref(app_info_from_list); + apps_p = apps_p->next; + } + g_list_free(apps); + if (app_info) { + nsGIOMimeApp* app = new nsGIOMimeApp(app_info); + NS_ENSURE_TRUE(app, NS_ERROR_OUT_OF_MEMORY); + NS_ADDREF(*aAppInfo = app); + return NS_OK; + } + + *aAppInfo = nullptr; + return NS_ERROR_NOT_AVAILABLE; +} + +/** + * Create application info for specified command and application name. + * Command arguments are ignored and the "%u" is always added. + * @param cmd command to execute + * @param appName application name + * @param appInfo location where created GAppInfo is stored + * @return NS_OK when object is created, NS_ERROR_FILE_NOT_FOUND when executable + * is not found in the system path or NS_ERROR_FAILURE otherwise. + */ +NS_IMETHODIMP +nsGIOService::CreateAppFromCommand(nsACString const& cmd, + nsACString const& appName, + nsIGIOMimeApp** appInfo) { + GError* error = nullptr; + *appInfo = nullptr; + + // Using G_APP_INFO_CREATE_SUPPORTS_URIS calling + // g_app_info_create_from_commandline appends %u to the cmd even when cmd + // already contains this parameter. To avoid that we're going to remove + // arguments before passing to it. + nsAutoCString commandWithoutArgs; + nsresult rv = GetCommandFromCommandline(cmd, commandWithoutArgs); + NS_ENSURE_SUCCESS(rv, rv); + GAppInfo* app_info = g_app_info_create_from_commandline( + commandWithoutArgs.BeginReading(), PromiseFlatCString(appName).get(), + G_APP_INFO_CREATE_SUPPORTS_URIS, &error); + if (!app_info) { + g_warning("Cannot create application info from command: %s", + error->message); + g_error_free(error); + return NS_ERROR_FAILURE; + } + + // Check if executable exist in path + gchar* executableWithFullPath = + g_find_program_in_path(commandWithoutArgs.BeginReading()); + if (!executableWithFullPath) { + return NS_ERROR_FILE_NOT_FOUND; + } + g_free(executableWithFullPath); + + nsGIOMimeApp* mozApp = new nsGIOMimeApp(app_info); + NS_ENSURE_TRUE(mozApp, NS_ERROR_OUT_OF_MEMORY); + NS_ADDREF(*appInfo = mozApp); + return NS_OK; +} + +NS_IMETHODIMP +nsGIOService::ShouldUseFlatpakPortal(bool* aRes) { + *aRes = GetShouldUseFlatpakPortal(); + return NS_OK; +} diff --git a/toolkit/system/gnome/nsGIOService.h b/toolkit/system/gnome/nsGIOService.h new file mode 100644 index 0000000000..e83c724de2 --- /dev/null +++ b/toolkit/system/gnome/nsGIOService.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsGIOService_h_ +#define nsGIOService_h_ + +#include "nsIGIOService.h" + +#define NS_GIOSERVICE_CID \ + { \ + 0xe3a1f3c9, 0x3ae1, 0x4b40, { \ + 0xa5, 0xe0, 0x7b, 0x45, 0x7f, 0xc9, 0xa9, 0xad \ + } \ + } + +class nsGIOService final : public nsIGIOService { + ~nsGIOService() = default; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIGIOSERVICE +}; + +#endif diff --git a/toolkit/system/gnome/nsGSettingsService.cpp b/toolkit/system/gnome/nsGSettingsService.cpp new file mode 100644 index 0000000000..36962c6554 --- /dev/null +++ b/toolkit/system/gnome/nsGSettingsService.cpp @@ -0,0 +1,319 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" + +#include "nsGSettingsService.h" +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsMemory.h" +#include "prlink.h" +#include "nsComponentManagerUtils.h" +#include "nsIMutableArray.h" +#include "nsISupportsPrimitives.h" + +#include <glib.h> +#include <glib-object.h> + +using namespace mozilla; + +typedef struct _GSettings GSettings; +typedef struct _GVariantType GVariantType; +typedef struct _GVariant GVariant; + +#ifndef G_VARIANT_TYPE_INT32 +# define G_VARIANT_TYPE_INT32 ((const GVariantType*)"i") +# define G_VARIANT_TYPE_BOOLEAN ((const GVariantType*)"b") +# define G_VARIANT_TYPE_STRING ((const GVariantType*)"s") +# define G_VARIANT_TYPE_OBJECT_PATH ((const GVariantType*)"o") +# define G_VARIANT_TYPE_SIGNATURE ((const GVariantType*)"g") +#endif +#ifndef G_VARIANT_TYPE_STRING_ARRAY +# define G_VARIANT_TYPE_STRING_ARRAY ((const GVariantType*)"as") +#endif + +#define GSETTINGS_FUNCTIONS \ + FUNC(g_settings_new, GSettings*, (const char* schema)) \ + FUNC(g_settings_list_schemas, const char* const*, (void)) \ + FUNC(g_settings_list_keys, char**, (GSettings * settings)) \ + FUNC(g_settings_get_value, GVariant*, \ + (GSettings * settings, const char* key)) \ + FUNC(g_settings_set_value, gboolean, \ + (GSettings * settings, const char* key, GVariant* value)) \ + FUNC(g_settings_range_check, gboolean, \ + (GSettings * settings, const char* key, GVariant* value)) \ + FUNC(g_variant_get_int32, gint32, (GVariant * variant)) \ + FUNC(g_variant_get_boolean, gboolean, (GVariant * variant)) \ + FUNC(g_variant_get_string, const char*, (GVariant * value, gsize * length)) \ + FUNC(g_variant_get_strv, const char**, (GVariant * value, gsize * length)) \ + FUNC(g_variant_is_of_type, gboolean, \ + (GVariant * value, const GVariantType* type)) \ + FUNC(g_variant_new_int32, GVariant*, (gint32 value)) \ + FUNC(g_variant_new_boolean, GVariant*, (gboolean value)) \ + FUNC(g_variant_new_string, GVariant*, (const char* string)) \ + FUNC(g_variant_unref, void, (GVariant * value)) + +#define FUNC(name, type, params) \ + typedef type(*_##name##_fn) params; \ + static _##name##_fn _##name; + +GSETTINGS_FUNCTIONS + +#undef FUNC + +#define g_settings_new _g_settings_new +#define g_settings_list_schemas _g_settings_list_schemas +#define g_settings_list_keys _g_settings_list_keys +#define g_settings_get_value _g_settings_get_value +#define g_settings_set_value _g_settings_set_value +#define g_settings_range_check _g_settings_range_check +#define g_variant_get_int32 _g_variant_get_int32 +#define g_variant_get_boolean _g_variant_get_boolean +#define g_variant_get_string _g_variant_get_string +#define g_variant_get_strv _g_variant_get_strv +#define g_variant_is_of_type _g_variant_is_of_type +#define g_variant_new_int32 _g_variant_new_int32 +#define g_variant_new_boolean _g_variant_new_boolean +#define g_variant_new_string _g_variant_new_string +#define g_variant_unref _g_variant_unref + +static PRLibrary* gioLib = nullptr; + +class nsGSettingsCollection final : public nsIGSettingsCollection { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIGSETTINGSCOLLECTION + + explicit nsGSettingsCollection(GSettings* aSettings) + : mSettings(aSettings), mKeys(nullptr) {} + + private: + ~nsGSettingsCollection(); + + bool KeyExists(const nsACString& aKey); + bool SetValue(const nsACString& aKey, GVariant* aValue); + + GSettings* mSettings; + char** mKeys; +}; + +nsGSettingsCollection::~nsGSettingsCollection() { + g_strfreev(mKeys); + g_object_unref(mSettings); +} + +bool nsGSettingsCollection::KeyExists(const nsACString& aKey) { + if (!mKeys) mKeys = g_settings_list_keys(mSettings); + + for (uint32_t i = 0; mKeys[i] != nullptr; i++) { + if (aKey.Equals(mKeys[i])) return true; + } + + return false; +} + +bool nsGSettingsCollection::SetValue(const nsACString& aKey, GVariant* aValue) { + if (!KeyExists(aKey) || + !g_settings_range_check(mSettings, PromiseFlatCString(aKey).get(), + aValue)) { + g_variant_unref(aValue); + return false; + } + + return g_settings_set_value(mSettings, PromiseFlatCString(aKey).get(), + aValue); +} + +NS_IMPL_ISUPPORTS(nsGSettingsCollection, nsIGSettingsCollection) + +NS_IMETHODIMP +nsGSettingsCollection::SetString(const nsACString& aKey, + const nsACString& aValue) { + GVariant* value = g_variant_new_string(PromiseFlatCString(aValue).get()); + if (!value) return NS_ERROR_OUT_OF_MEMORY; + + bool res = SetValue(aKey, value); + + return res ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsGSettingsCollection::SetBoolean(const nsACString& aKey, bool aValue) { + GVariant* value = g_variant_new_boolean(aValue); + if (!value) return NS_ERROR_OUT_OF_MEMORY; + + bool res = SetValue(aKey, value); + + return res ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsGSettingsCollection::SetInt(const nsACString& aKey, int32_t aValue) { + GVariant* value = g_variant_new_int32(aValue); + if (!value) return NS_ERROR_OUT_OF_MEMORY; + + bool res = SetValue(aKey, value); + + return res ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsGSettingsCollection::GetString(const nsACString& aKey, nsACString& aResult) { + if (!KeyExists(aKey)) return NS_ERROR_INVALID_ARG; + + GVariant* value = + g_settings_get_value(mSettings, PromiseFlatCString(aKey).get()); + if (!g_variant_is_of_type(value, G_VARIANT_TYPE_STRING) && + !g_variant_is_of_type(value, G_VARIANT_TYPE_OBJECT_PATH) && + !g_variant_is_of_type(value, G_VARIANT_TYPE_SIGNATURE)) { + g_variant_unref(value); + return NS_ERROR_FAILURE; + } + + aResult.Assign(g_variant_get_string(value, nullptr)); + g_variant_unref(value); + + return NS_OK; +} + +NS_IMETHODIMP +nsGSettingsCollection::GetBoolean(const nsACString& aKey, bool* aResult) { + NS_ENSURE_ARG_POINTER(aResult); + + if (!KeyExists(aKey)) return NS_ERROR_INVALID_ARG; + + GVariant* value = + g_settings_get_value(mSettings, PromiseFlatCString(aKey).get()); + if (!g_variant_is_of_type(value, G_VARIANT_TYPE_BOOLEAN)) { + g_variant_unref(value); + return NS_ERROR_FAILURE; + } + + gboolean res = g_variant_get_boolean(value); + *aResult = res ? true : false; + g_variant_unref(value); + + return NS_OK; +} + +NS_IMETHODIMP +nsGSettingsCollection::GetInt(const nsACString& aKey, int32_t* aResult) { + NS_ENSURE_ARG_POINTER(aResult); + + if (!KeyExists(aKey)) return NS_ERROR_INVALID_ARG; + + GVariant* value = + g_settings_get_value(mSettings, PromiseFlatCString(aKey).get()); + if (!g_variant_is_of_type(value, G_VARIANT_TYPE_INT32)) { + g_variant_unref(value); + return NS_ERROR_FAILURE; + } + + *aResult = g_variant_get_int32(value); + g_variant_unref(value); + + return NS_OK; +} + +// These types are local to nsGSettingsService::Init, but ISO C++98 doesn't +// allow a template (ArrayLength) to be instantiated based on a local type. +// Boo-urns! +typedef void (*nsGSettingsFunc)(); +struct nsGSettingsDynamicFunction { + const char* functionName; + nsGSettingsFunc* function; +}; + +NS_IMETHODIMP +nsGSettingsCollection::GetStringList(const nsACString& aKey, + nsIArray** aResult) { + if (!KeyExists(aKey)) return NS_ERROR_INVALID_ARG; + + nsCOMPtr<nsIMutableArray> items(do_CreateInstance(NS_ARRAY_CONTRACTID)); + if (!items) { + return NS_ERROR_OUT_OF_MEMORY; + } + + GVariant* value = + g_settings_get_value(mSettings, PromiseFlatCString(aKey).get()); + + if (!g_variant_is_of_type(value, G_VARIANT_TYPE_STRING_ARRAY)) { + g_variant_unref(value); + return NS_ERROR_FAILURE; + } + + const gchar** gs_strings = g_variant_get_strv(value, nullptr); + if (!gs_strings) { + // empty array + items.forget(aResult); + g_variant_unref(value); + return NS_OK; + } + + const gchar** p_gs_strings = gs_strings; + while (*p_gs_strings != nullptr) { + nsCOMPtr<nsISupportsCString> obj( + do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID)); + if (obj) { + obj->SetData(nsDependentCString(*p_gs_strings)); + items->AppendElement(obj); + } + p_gs_strings++; + } + g_free(gs_strings); + items.forget(aResult); + g_variant_unref(value); + return NS_OK; +} + +nsresult nsGSettingsService::Init() { +#define FUNC(name, type, params) {#name, (nsGSettingsFunc*)&_##name}, + static const nsGSettingsDynamicFunction kGSettingsSymbols[] = { + GSETTINGS_FUNCTIONS}; +#undef FUNC + + if (!gioLib) { + gioLib = PR_LoadLibrary("libgio-2.0.so.0"); + if (!gioLib) return NS_ERROR_FAILURE; + } + + for (auto GSettingsSymbol : kGSettingsSymbols) { + *GSettingsSymbol.function = + PR_FindFunctionSymbol(gioLib, GSettingsSymbol.functionName); + if (!*GSettingsSymbol.function) { + return NS_ERROR_FAILURE; + } + } + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsGSettingsService, nsIGSettingsService) + +nsGSettingsService::~nsGSettingsService() { + if (gioLib) { + PR_UnloadLibrary(gioLib); + gioLib = nullptr; + } +} + +NS_IMETHODIMP +nsGSettingsService::GetCollectionForSchema( + const nsACString& schema, nsIGSettingsCollection** collection) { + NS_ENSURE_ARG_POINTER(collection); + + const char* const* schemas = g_settings_list_schemas(); + + for (uint32_t i = 0; schemas[i] != nullptr; i++) { + if (schema.Equals(schemas[i])) { + GSettings* settings = g_settings_new(PromiseFlatCString(schema).get()); + nsGSettingsCollection* mozGSettings = new nsGSettingsCollection(settings); + NS_ADDREF(*collection = mozGSettings); + return NS_OK; + } + } + + return NS_ERROR_FAILURE; +} diff --git a/toolkit/system/gnome/nsGSettingsService.h b/toolkit/system/gnome/nsGSettingsService.h new file mode 100644 index 0000000000..1eb592e4aa --- /dev/null +++ b/toolkit/system/gnome/nsGSettingsService.h @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsGSettingsService_h_ +#define nsGSettingsService_h_ + +#include "nsIGSettingsService.h" + +#define NS_GSETTINGSSERVICE_CID \ + { \ + 0xbfd4a9d8, 0xd886, 0x4161, { \ + 0x81, 0xef, 0x88, 0x68, 0xda, 0x11, 0x41, 0x70 \ + } \ + } + +class nsGSettingsService final : public nsIGSettingsService { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIGSETTINGSSERVICE + + nsresult Init(); + + private: + ~nsGSettingsService(); +}; + +#endif diff --git a/toolkit/system/gnome/nsSystemAlertsService.cpp b/toolkit/system/gnome/nsSystemAlertsService.cpp new file mode 100644 index 0000000000..3f442cc58f --- /dev/null +++ b/toolkit/system/gnome/nsSystemAlertsService.cpp @@ -0,0 +1,123 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsComponentManagerUtils.h" +#include "nsXULAppAPI.h" +#include "nsSystemAlertsService.h" +#include "nsAlertsIconListener.h" + +NS_IMPL_ADDREF(nsSystemAlertsService) +NS_IMPL_RELEASE(nsSystemAlertsService) + +NS_INTERFACE_MAP_BEGIN(nsSystemAlertsService) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAlertsService) + NS_INTERFACE_MAP_ENTRY(nsIAlertsService) + NS_INTERFACE_MAP_ENTRY(nsIAlertsDoNotDisturb) +NS_INTERFACE_MAP_END + +nsSystemAlertsService::nsSystemAlertsService() = default; + +nsSystemAlertsService::~nsSystemAlertsService() = default; + +nsresult nsSystemAlertsService::Init() { return NS_OK; } + +NS_IMETHODIMP nsSystemAlertsService::ShowAlertNotification( + const nsAString& aImageUrl, const nsAString& aAlertTitle, + const nsAString& aAlertText, bool aAlertTextClickable, + const nsAString& aAlertCookie, nsIObserver* aAlertListener, + const nsAString& aAlertName, const nsAString& aBidi, const nsAString& aLang, + const nsAString& aData, nsIPrincipal* aPrincipal, bool aInPrivateBrowsing, + bool aRequireInteraction) { + nsCOMPtr<nsIAlertNotification> alert = + do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID); + NS_ENSURE_TRUE(alert, NS_ERROR_FAILURE); + nsresult rv = + alert->Init(aAlertName, aImageUrl, aAlertTitle, aAlertText, + aAlertTextClickable, aAlertCookie, aBidi, aLang, aData, + aPrincipal, aInPrivateBrowsing, aRequireInteraction); + NS_ENSURE_SUCCESS(rv, rv); + return ShowAlert(alert, aAlertListener); +} + +NS_IMETHODIMP nsSystemAlertsService::ShowPersistentNotification( + const nsAString& aPersistentData, nsIAlertNotification* aAlert, + nsIObserver* aAlertListener) { + return ShowAlert(aAlert, aAlertListener); +} + +NS_IMETHODIMP nsSystemAlertsService::ShowAlert(nsIAlertNotification* aAlert, + nsIObserver* aAlertListener) { + NS_ENSURE_ARG(aAlert); + + nsAutoString alertName; + nsresult rv = aAlert->GetName(alertName); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<nsAlertsIconListener> alertListener = + new nsAlertsIconListener(this, alertName); + if (!alertListener) return NS_ERROR_OUT_OF_MEMORY; + + if (mSuppressForScreenSharing) { + alertListener->SendClosed(); + return NS_OK; + } + + AddListener(alertName, alertListener); + return alertListener->InitAlertAsync(aAlert, aAlertListener); +} + +NS_IMETHODIMP nsSystemAlertsService::CloseAlert(const nsAString& aAlertName) { + RefPtr<nsAlertsIconListener> listener = mActiveListeners.Get(aAlertName); + if (!listener) { + return NS_OK; + } + mActiveListeners.Remove(aAlertName); + return listener->Close(); +} + +NS_IMETHODIMP nsSystemAlertsService::GetManualDoNotDisturb(bool* aRetVal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsSystemAlertsService::SetManualDoNotDisturb(bool aDoNotDisturb) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsSystemAlertsService::GetSuppressForScreenSharing( + bool* aRetVal) { + NS_ENSURE_ARG(aRetVal); + *aRetVal = mSuppressForScreenSharing; + return NS_OK; +} + +NS_IMETHODIMP nsSystemAlertsService::SetSuppressForScreenSharing( + bool aSuppress) { + mSuppressForScreenSharing = aSuppress; + return NS_OK; +} + +bool nsSystemAlertsService::IsActiveListener(const nsAString& aAlertName, + nsAlertsIconListener* aListener) { + return mActiveListeners.Get(aAlertName) == aListener; +} + +void nsSystemAlertsService::AddListener(const nsAString& aAlertName, + nsAlertsIconListener* aListener) { + RefPtr<nsAlertsIconListener> oldListener = mActiveListeners.Get(aAlertName); + mActiveListeners.Put(aAlertName, aListener); + if (oldListener) { + // If an alert with this name already exists, close it. + oldListener->Close(); + } +} + +void nsSystemAlertsService::RemoveListener(const nsAString& aAlertName, + nsAlertsIconListener* aListener) { + if (IsActiveListener(aAlertName, aListener)) { + // The alert may have been replaced; only remove it from the active + // listeners map if it's the same. + mActiveListeners.Remove(aAlertName); + } +} diff --git a/toolkit/system/gnome/nsSystemAlertsService.h b/toolkit/system/gnome/nsSystemAlertsService.h new file mode 100644 index 0000000000..c737822a47 --- /dev/null +++ b/toolkit/system/gnome/nsSystemAlertsService.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsSystemAlertsService_h__ +#define nsSystemAlertsService_h__ + +#include "nsIAlertsService.h" +#include "nsDataHashtable.h" +#include "nsCOMPtr.h" + +class nsAlertsIconListener; + +class nsSystemAlertsService : public nsIAlertsService, + public nsIAlertsDoNotDisturb { + public: + NS_DECL_NSIALERTSSERVICE + NS_DECL_NSIALERTSDONOTDISTURB + NS_DECL_ISUPPORTS + + nsSystemAlertsService(); + + nsresult Init(); + + bool IsActiveListener(const nsAString& aAlertName, + nsAlertsIconListener* aListener); + void RemoveListener(const nsAString& aAlertName, + nsAlertsIconListener* aListener); + + protected: + virtual ~nsSystemAlertsService(); + + void AddListener(const nsAString& aAlertName, + nsAlertsIconListener* aListener); + + nsDataHashtable<nsStringHashKey, nsAlertsIconListener*> mActiveListeners; + + private: + bool mSuppressForScreenSharing = false; +}; + +#endif /* nsSystemAlertsService_h__ */ |