/* -*- 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 #include #include #include #include #include "DBusService.h" #include "nsAppRunner.h" #include "mozilla/Unused.h" #include "mozilla/GUniquePtr.h" #include "mozilla/WidgetUtils.h" #include #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 = "\n" "\n" "\n" "\n" " \n" " \n" " \n" " \n" " \n" "\n" "\n" " \n" " \n" " \n" "\n" "\n" "\n"; bool DBusService::LaunchApp(const char* aCommand, const char** aURIList, int aURIListLen) { nsAutoCString param(mAppFile); if (aCommand) { param.Append(" "); param.Append(aCommand); } for (int i = 0; aURIList && i < aURIListLen; i++) { param.Append(" "); GUniquePtr escUri(g_shell_quote(aURIList[i])); param.Append(escUri.get()); } char* argv[] = {strdup("/bin/sh"), strdup("-c"), strdup(param.get()), nullptr}; int ret = PR_CreateProcessDetached("/bin/sh", argv, nullptr, nullptr) != PR_FAILURE; for (auto str : argv) { free(str); } 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 variant = dont_AddRef(g_variant_get_child_value(aParameters, 0)); gsize uriNum = 0; GUniquePtr 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 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(aUserData)->HandleFreedesktopActivate( aParameters, aInvocation); } else if (strcmp("Open", aMethodName) == 0) { static_cast(aUserData)->HandleFreedesktopOpen(aParameters, aInvocation); } else if (strcmp("ActivateAction", aMethodName) == 0) { static_cast(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 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(aUserData)->OnBusAcquired(aConnection); }, [](GDBusConnection* aConnection, const gchar*, gpointer aUserData) -> void { static_cast(aUserData)->OnNameAcquired(aConnection); }, [](GDBusConnection* aConnection, const gchar*, gpointer aUserData) -> void { static_cast(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; }