firefox/D209910.1715685533.diff
2024-05-13 13:25:18 +02:00

420 lines
15 KiB
Diff

diff --git a/widget/gtk/DBusService.h b/widget/gtk/DBusService.h
new file mode 100644
--- /dev/null
+++ b/widget/gtk/DBusService.h
@@ -0,0 +1,67 @@
+/* -*- 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 DBusService_h__
+#define DBusService_h__
+
+#include <glib.h>
+#include <gio/gio.h>
+#include "mozilla/RefPtr.h"
+#include "mozilla/GRefPtr.h"
+
+namespace mozilla::widget {
+
+class DBusService final {
+ public:
+ explicit DBusService(const char* aAppFile) : mAppFile(aAppFile) {}
+ ~DBusService();
+
+ // nsBaseAppShell overrides:
+ bool Init();
+ void Run();
+
+ void StartDBusListening();
+ void StopDBusListening();
+
+ static void DBusSessionSleepCallback(GDBusProxy* aProxy, gchar* aSenderName,
+ gchar* aSignalName,
+ GVariant* aParameters,
+ gpointer aUserData);
+ static void DBusTimedatePropertiesChangedCallback(GDBusProxy* aProxy,
+ gchar* aSenderName,
+ gchar* aSignalName,
+ GVariant* aParameters,
+ gpointer aUserData);
+ static void DBusConnectClientResponse(GObject* aObject, GAsyncResult* aResult,
+ gpointer aUserData);
+
+ bool LaunchApp(const char* aCommand, const char** aURIList, int aURIListLen);
+
+ void HandleFreedesktopActivate(GVariant* aParameters,
+ GDBusMethodInvocation* aReply);
+ void HandleFreedesktopOpen(GVariant* aParameters,
+ GDBusMethodInvocation* aReply);
+ void HandleFreedesktopActivateAction(GVariant* aParameters,
+ GDBusMethodInvocation* aReply);
+
+ bool StartFreedesktopListener();
+ void StopFreedesktopListener();
+
+ void OnBusAcquired(GDBusConnection* aConnection);
+ void OnNameAcquired(GDBusConnection* aConnection);
+ void OnNameLost(GDBusConnection* aConnection);
+
+ private:
+ // The connection is owned by DBus library
+ uint mDBusID = 0;
+ uint mRegistrationId = 0;
+ GDBusConnection* mConnection = nullptr;
+ RefPtr<GDBusNodeInfo> mIntrospectionData;
+ const char* mAppFile = nullptr;
+};
+
+} // namespace mozilla::widget
+
+#endif // DBusService_h__
diff --git a/widget/gtk/DBusService.cpp b/widget/gtk/DBusService.cpp
new file mode 100644
--- /dev/null
+++ b/widget/gtk/DBusService.cpp
@@ -0,0 +1,327 @@
+/* -*- 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 <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <gdk/gdk.h>
+#include "DBusService.h"
+#include "nsAppRunner.h"
+#include "mozilla/Unused.h"
+#include "mozilla/GUniquePtr.h"
+#include "mozilla/WidgetUtils.h"
+#include <gio/gio.h>
+#include "nsIObserverService.h"
+#include "WidgetUtilsGtk.h"
+#include "prproces.h"
+#include "mozilla/XREAppData.h"
+#include "nsPrintfCString.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+DBusService::~DBusService() { StopFreedesktopListener(); }
+
+bool DBusService::Init() { return StartFreedesktopListener(); }
+
+void DBusService::Run() {
+ GMainLoop* loop = g_main_loop_new(nullptr, false);
+ g_main_loop_run(loop);
+ g_main_loop_unref(loop);
+}
+
+// Mozilla has old GIO version in build roots
+#define G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE GBusNameOwnerFlags(1 << 2)
+
+#define DBUS_BUS_NAME_TEMPLATE "org.mozilla.%s"
+#define DBUS_OBJECT_PATH_TEMPLATE "/org/mozilla/%s"
+
+static const char* GetDBusBusName() {
+ static const char* name = []() {
+ nsAutoCString appName;
+ gAppData->GetDBusAppName(appName);
+ return ToNewCString(nsPrintfCString(DBUS_BUS_NAME_TEMPLATE,
+ appName.get())); // Intentionally leak
+ }();
+ return name;
+}
+
+static const char* GetDBusObjectPath() {
+ static const char* path = []() {
+ nsAutoCString appName;
+ gAppData->GetDBusAppName(appName);
+ return ToNewCString(nsPrintfCString(DBUS_OBJECT_PATH_TEMPLATE,
+ appName.get())); // Intentionally leak
+ }();
+ return path;
+}
+
+// See
+// https://specifications.freedesktop.org/desktop-entry-spec/1.1/ar01s07.html
+// for details
+static const char* kIntrospectTemplate =
+ "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection "
+ "1.0//EN\"\n"
+ "\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n"
+ "<node>\n"
+ "<interface name='org.freedesktop.Application'>\n"
+ "<method name='Activate'>\n"
+ " <arg type='a{sv}' name='platform_data' direction='in'/>\n"
+ " </method>\n"
+ " <method name='Open'>\n"
+ " <arg type='as' name='uris' direction='in'/>\n"
+ " <arg type='a{sv}' name='platform_data' direction='in'/>\n"
+ "</method>\n"
+ "<method name='ActivateAction'>\n"
+ " <arg type='s' name='action_name' direction='in'/>\n"
+ " <arg type='av' name='parameter' direction='in'/>\n"
+ " <arg type='a{sv}' name='platform_data' direction='in'/>\n"
+ "</method>\n"
+ "</interface>\n"
+ "</node>\n";
+
+bool DBusService::LaunchApp(const char* aCommand, const char** aURIList,
+ int aURIListLen) {
+ // Allocate space for all uris, executable name, command if supplied and
+ // null terminator
+ int paramsNum = aURIListLen + 2;
+ if (aCommand) {
+ paramsNum++;
+ }
+
+ char** argv = (char**)moz_xmalloc(sizeof(char*) * paramsNum);
+ int argc = 0;
+ argv[argc++] = strdup(mAppFile);
+ if (aCommand) {
+ argv[argc++] = strdup(aCommand);
+ }
+ for (int i = 0; aURIList && i < aURIListLen; i++) {
+ argv[argc++] = strdup(aURIList[i]);
+ }
+ argv[argc++] = nullptr;
+
+ nsAutoCString exePath;
+ nsCOMPtr<nsIFile> lf;
+ bool ret = false;
+ if (NS_SUCCEEDED(XRE_GetBinaryPath(getter_AddRefs(lf)))) {
+ if (NS_SUCCEEDED(lf->GetNativePath(exePath))) {
+ ret = (PR_CreateProcessDetached(exePath.get(), argv, nullptr, nullptr) !=
+ PR_FAILURE);
+ }
+ }
+
+ for (int i = 0; i < argc; i++) {
+ free(argv[i]);
+ }
+ free(argv);
+ return ret;
+}
+
+// The Activate method is called when the application is started without
+// files to open.
+// Open :: (a{sv}) → ()
+void DBusService::HandleFreedesktopActivate(GVariant* aParameters,
+ GDBusMethodInvocation* aReply) {
+ if (!LaunchApp(nullptr, nullptr, 0)) {
+ g_dbus_method_invocation_return_error(aReply, G_DBUS_ERROR,
+ G_DBUS_ERROR_FAILED,
+ "Failed to run target application.");
+ return;
+ }
+ g_dbus_method_invocation_return_value(aReply, nullptr);
+}
+
+// The Open method is called when the application is started with files.
+// The array of strings is an array of URIs, in UTF-8.
+// Open :: (as,a{sv}) → ()
+void DBusService::HandleFreedesktopOpen(GVariant* aParameters,
+ GDBusMethodInvocation* aReply) {
+ RefPtr<GVariant> variant =
+ dont_AddRef(g_variant_get_child_value(aParameters, 0));
+ gsize uriNum = 0;
+ GUniquePtr<const char*> uriArray(g_variant_get_strv(variant, &uriNum));
+ if (!LaunchApp(nullptr, uriArray.get(), uriNum)) {
+ g_dbus_method_invocation_return_error(aReply, G_DBUS_ERROR,
+ G_DBUS_ERROR_FAILED,
+ "Failed to run target application.");
+ return;
+ }
+ g_dbus_method_invocation_return_value(aReply, nullptr);
+}
+
+// The ActivateAction method is called when Desktop Actions are activated.
+// The action-name parameter is the name of the action.
+// ActivateAction :: (s,av,a{sv}) → ()
+void DBusService::HandleFreedesktopActivateAction(
+ GVariant* aParameters, GDBusMethodInvocation* aReply) {
+ const char* actionName;
+
+ // aParameters is "(s,av,a{sv})" type
+ RefPtr<GVariant> r = dont_AddRef(g_variant_get_child_value(aParameters, 0));
+ if (!(actionName = g_variant_get_string(r, nullptr))) {
+ g_dbus_method_invocation_return_error(
+ aReply, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Wrong params!");
+ return;
+ }
+
+ // TODO: Read av params and pass them to LaunchApp?
+
+ // actionName matches desktop action defined in .desktop file.
+ // We implement it for .desktop file shipped by flatpak
+ // (taskcluster/docker/firefox-flatpak/org.mozilla.firefox.desktop)
+ bool ret = false;
+ if (!strcmp(actionName, "new-window")) {
+ ret = LaunchApp(nullptr, nullptr, 0);
+ } else if (!strcmp(actionName, "new-private-window")) {
+ ret = LaunchApp("--private-window", nullptr, 0);
+ } else if (!strcmp(actionName, "profile-manager-window")) {
+ ret = LaunchApp("--ProfileManager", nullptr, 0);
+ }
+ if (!ret) {
+ g_dbus_method_invocation_return_error(aReply, G_DBUS_ERROR,
+ G_DBUS_ERROR_FAILED,
+ "Failed to run target application.");
+ return;
+ }
+ g_dbus_method_invocation_return_value(aReply, nullptr);
+}
+
+static void HandleMethodCall(GDBusConnection* aConnection, const gchar* aSender,
+ const gchar* aObjectPath,
+ const gchar* aInterfaceName,
+ const gchar* aMethodName, GVariant* aParameters,
+ GDBusMethodInvocation* aInvocation,
+ gpointer aUserData) {
+ MOZ_ASSERT(aUserData);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (strcmp("org.freedesktop.Application", aInterfaceName) != 0) {
+ g_warning("DBusService: HandleMethodCall() wrong interface name %s",
+ aInterfaceName);
+ return;
+ }
+ if (strcmp("Activate", aMethodName) == 0) {
+ static_cast<DBusService*>(aUserData)->HandleFreedesktopActivate(
+ aParameters, aInvocation);
+ } else if (strcmp("Open", aMethodName) == 0) {
+ static_cast<DBusService*>(aUserData)->HandleFreedesktopOpen(aParameters,
+ aInvocation);
+ } else if (strcmp("ActivateAction", aMethodName) == 0) {
+ static_cast<DBusService*>(aUserData)->HandleFreedesktopActivateAction(
+ aParameters, aInvocation);
+ } else {
+ g_warning("DBusService: HandleMethodCall() wrong method %s", aMethodName);
+ }
+}
+
+static GVariant* HandleGetProperty(GDBusConnection* aConnection,
+ const gchar* aSender,
+ const gchar* aObjectPath,
+ const gchar* aInterfaceName,
+ const gchar* aPropertyName, GError** aError,
+ gpointer aUserData) {
+ MOZ_ASSERT(aUserData);
+ MOZ_ASSERT(NS_IsMainThread());
+ g_set_error(aError, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "%s:%s setting is not supported", aInterfaceName, aPropertyName);
+ return nullptr;
+}
+
+static gboolean HandleSetProperty(GDBusConnection* aConnection,
+ const gchar* aSender,
+ const gchar* aObjectPath,
+ const gchar* aInterfaceName,
+ const gchar* aPropertyName, GVariant* aValue,
+ GError** aError, gpointer aUserData) {
+ MOZ_ASSERT(aUserData);
+ MOZ_ASSERT(NS_IsMainThread());
+ g_set_error(aError, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "%s:%s setting is not supported", aInterfaceName, aPropertyName);
+ return false;
+}
+
+static const GDBusInterfaceVTable gInterfaceVTable = {
+ HandleMethodCall, HandleGetProperty, HandleSetProperty};
+
+void DBusService::OnBusAcquired(GDBusConnection* aConnection) {
+ GUniquePtr<GError> error;
+ mIntrospectionData = dont_AddRef(g_dbus_node_info_new_for_xml(
+ kIntrospectTemplate, getter_Transfers(error)));
+ if (!mIntrospectionData) {
+ g_warning("DBusService: g_dbus_node_info_new_for_xml() failed! %s",
+ error->message);
+ return;
+ }
+
+ mRegistrationId = g_dbus_connection_register_object(
+ aConnection, GetDBusObjectPath(), mIntrospectionData->interfaces[0],
+ &gInterfaceVTable, this, /* user_data */
+ nullptr, /* user_data_free_func */
+ getter_Transfers(error)); /* GError** */
+
+ if (mRegistrationId == 0) {
+ g_warning(
+ "DBusService: g_dbus_connection_register_object() "
+ "failed! %s",
+ error->message);
+ return;
+ }
+}
+
+void DBusService::OnNameAcquired(GDBusConnection* aConnection) {
+ mConnection = aConnection;
+}
+
+void DBusService::OnNameLost(GDBusConnection* aConnection) {
+ mConnection = nullptr;
+ if (!mRegistrationId) {
+ return;
+ }
+ if (g_dbus_connection_unregister_object(aConnection, mRegistrationId)) {
+ mRegistrationId = 0;
+ }
+}
+
+bool DBusService::StartFreedesktopListener() {
+ if (mDBusID) {
+ // We're already connected so we don't need to reconnect
+ return false;
+ }
+
+ mDBusID = g_bus_own_name(
+ // if org.mozilla.Firefox is taken it means we're already running
+ // so use G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE and quit.
+ G_BUS_TYPE_SESSION, GetDBusBusName(), G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE,
+ [](GDBusConnection* aConnection, const gchar*,
+ gpointer aUserData) -> void {
+ static_cast<DBusService*>(aUserData)->OnBusAcquired(aConnection);
+ },
+ [](GDBusConnection* aConnection, const gchar*,
+ gpointer aUserData) -> void {
+ static_cast<DBusService*>(aUserData)->OnNameAcquired(aConnection);
+ },
+ [](GDBusConnection* aConnection, const gchar*,
+ gpointer aUserData) -> void {
+ static_cast<DBusService*>(aUserData)->OnNameLost(aConnection);
+ },
+ this, nullptr);
+
+ if (!mDBusID) {
+ g_warning("DBusService: g_bus_own_name() failed!");
+ return false;
+ }
+
+ return true;
+}
+
+void DBusService::StopFreedesktopListener() {
+ OnNameLost(mConnection);
+ if (mDBusID) {
+ g_bus_unown_name(mDBusID);
+ mDBusID = 0;
+ }
+ mIntrospectionData = nullptr;
+}
diff --git a/widget/gtk/moz.build b/widget/gtk/moz.build
--- a/widget/gtk/moz.build
+++ b/widget/gtk/moz.build
@@ -172,9 +172,10 @@
"AsyncDBus.h",
]
UNIFIED_SOURCES += [
"AsyncDBus.cpp",
"DBusMenu.cpp",
+ "DBusService.cpp",
]
CXXFLAGS += CONFIG["MOZ_DBUS_CFLAGS"]
CXXFLAGS += ["-Werror=switch"]