1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
11 #include "DBusService.h"
12 #include "nsAppRunner.h"
13 #include "mozilla/Unused.h"
14 #include "mozilla/GUniquePtr.h"
15 #include "mozilla/WidgetUtils.h"
17 #include "nsIObserverService.h"
18 #include "WidgetUtilsGtk.h"
20 #include "mozilla/XREAppData.h"
21 #include "nsPrintfCString.h"
23 using namespace mozilla
;
24 using namespace mozilla::widget
;
26 DBusService::~DBusService() { StopFreedesktopListener(); }
28 bool DBusService::Init() { return StartFreedesktopListener(); }
30 void DBusService::Run() {
31 GMainLoop
* loop
= g_main_loop_new(nullptr, false);
32 g_main_loop_run(loop
);
33 g_main_loop_unref(loop
);
36 // Mozilla has old GIO version in build roots
37 #define G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE GBusNameOwnerFlags(1 << 2)
39 #define DBUS_BUS_NAME_TEMPLATE "org.mozilla.%s"
40 #define DBUS_OBJECT_PATH_TEMPLATE "/org/mozilla/%s"
42 static const char* GetDBusBusName() {
43 static const char* name
= []() {
44 nsAutoCString appName
;
45 gAppData
->GetDBusAppName(appName
);
46 return ToNewCString(nsPrintfCString(DBUS_BUS_NAME_TEMPLATE
,
47 appName
.get())); // Intentionally leak
52 static const char* GetDBusObjectPath() {
53 static const char* path
= []() {
54 nsAutoCString appName
;
55 gAppData
->GetDBusAppName(appName
);
56 return ToNewCString(nsPrintfCString(DBUS_OBJECT_PATH_TEMPLATE
,
57 appName
.get())); // Intentionally leak
63 // https://specifications.freedesktop.org/desktop-entry-spec/1.1/ar01s07.html
65 static const char* kIntrospectTemplate
=
66 "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection "
68 "\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n"
70 "<interface name='org.freedesktop.Application'>\n"
71 "<method name='Activate'>\n"
72 " <arg type='a{sv}' name='platform_data' direction='in'/>\n"
74 " <method name='Open'>\n"
75 " <arg type='as' name='uris' direction='in'/>\n"
76 " <arg type='a{sv}' name='platform_data' direction='in'/>\n"
78 "<method name='ActivateAction'>\n"
79 " <arg type='s' name='action_name' direction='in'/>\n"
80 " <arg type='av' name='parameter' direction='in'/>\n"
81 " <arg type='a{sv}' name='platform_data' direction='in'/>\n"
86 bool DBusService::LaunchApp(const char* aCommand
, const char** aURIList
,
88 nsAutoCString
param(mAppFile
);
91 param
.Append(aCommand
);
93 for (int i
= 0; aURIList
&& i
< aURIListLen
; i
++) {
95 GUniquePtr
<char> escUri(g_shell_quote(aURIList
[i
]));
96 param
.Append(escUri
.get());
99 char* argv
[] = {strdup("/bin/sh"), strdup("-c"), strdup(param
.get()),
102 PR_CreateProcessDetached("/bin/sh", argv
, nullptr, nullptr) != PR_FAILURE
;
103 for (auto str
: argv
) {
109 // The Activate method is called when the application is started without
111 // Open :: (a{sv}) → ()
112 void DBusService::HandleFreedesktopActivate(GVariant
* aParameters
,
113 GDBusMethodInvocation
* aReply
) {
114 if (!LaunchApp(nullptr, nullptr, 0)) {
115 g_dbus_method_invocation_return_error(aReply
, G_DBUS_ERROR
,
117 "Failed to run target application.");
120 g_dbus_method_invocation_return_value(aReply
, nullptr);
123 // The Open method is called when the application is started with files.
124 // The array of strings is an array of URIs, in UTF-8.
125 // Open :: (as,a{sv}) → ()
126 void DBusService::HandleFreedesktopOpen(GVariant
* aParameters
,
127 GDBusMethodInvocation
* aReply
) {
128 RefPtr
<GVariant
> variant
=
129 dont_AddRef(g_variant_get_child_value(aParameters
, 0));
131 GUniquePtr
<const char*> uriArray(g_variant_get_strv(variant
, &uriNum
));
132 if (!LaunchApp(nullptr, uriArray
.get(), uriNum
)) {
133 g_dbus_method_invocation_return_error(aReply
, G_DBUS_ERROR
,
135 "Failed to run target application.");
138 g_dbus_method_invocation_return_value(aReply
, nullptr);
141 // The ActivateAction method is called when Desktop Actions are activated.
142 // The action-name parameter is the name of the action.
143 // ActivateAction :: (s,av,a{sv}) → ()
144 void DBusService::HandleFreedesktopActivateAction(
145 GVariant
* aParameters
, GDBusMethodInvocation
* aReply
) {
146 const char* actionName
;
148 // aParameters is "(s,av,a{sv})" type
149 RefPtr
<GVariant
> r
= dont_AddRef(g_variant_get_child_value(aParameters
, 0));
150 if (!(actionName
= g_variant_get_string(r
, nullptr))) {
151 g_dbus_method_invocation_return_error(
152 aReply
, G_DBUS_ERROR
, G_DBUS_ERROR_INVALID_ARGS
, "Wrong params!");
156 // TODO: Read av params and pass them to LaunchApp?
158 // actionName matches desktop action defined in .desktop file.
159 // We implement it for .desktop file shipped by flatpak
160 // (taskcluster/docker/firefox-flatpak/org.mozilla.firefox.desktop)
162 if (!strcmp(actionName
, "new-window")) {
163 ret
= LaunchApp(nullptr, nullptr, 0);
164 } else if (!strcmp(actionName
, "new-private-window")) {
165 ret
= LaunchApp("--private-window", nullptr, 0);
166 } else if (!strcmp(actionName
, "profile-manager-window")) {
167 ret
= LaunchApp("--ProfileManager", nullptr, 0);
170 g_dbus_method_invocation_return_error(aReply
, G_DBUS_ERROR
,
172 "Failed to run target application.");
175 g_dbus_method_invocation_return_value(aReply
, nullptr);
178 static void HandleMethodCall(GDBusConnection
* aConnection
, const gchar
* aSender
,
179 const gchar
* aObjectPath
,
180 const gchar
* aInterfaceName
,
181 const gchar
* aMethodName
, GVariant
* aParameters
,
182 GDBusMethodInvocation
* aInvocation
,
183 gpointer aUserData
) {
184 MOZ_ASSERT(aUserData
);
185 MOZ_ASSERT(NS_IsMainThread());
187 if (strcmp("org.freedesktop.Application", aInterfaceName
) != 0) {
188 g_warning("DBusService: HandleMethodCall() wrong interface name %s",
192 if (strcmp("Activate", aMethodName
) == 0) {
193 static_cast<DBusService
*>(aUserData
)->HandleFreedesktopActivate(
194 aParameters
, aInvocation
);
195 } else if (strcmp("Open", aMethodName
) == 0) {
196 static_cast<DBusService
*>(aUserData
)->HandleFreedesktopOpen(aParameters
,
198 } else if (strcmp("ActivateAction", aMethodName
) == 0) {
199 static_cast<DBusService
*>(aUserData
)->HandleFreedesktopActivateAction(
200 aParameters
, aInvocation
);
202 g_warning("DBusService: HandleMethodCall() wrong method %s", aMethodName
);
206 static GVariant
* HandleGetProperty(GDBusConnection
* aConnection
,
207 const gchar
* aSender
,
208 const gchar
* aObjectPath
,
209 const gchar
* aInterfaceName
,
210 const gchar
* aPropertyName
, GError
** aError
,
211 gpointer aUserData
) {
212 MOZ_ASSERT(aUserData
);
213 MOZ_ASSERT(NS_IsMainThread());
214 g_set_error(aError
, G_IO_ERROR
, G_IO_ERROR_FAILED
,
215 "%s:%s setting is not supported", aInterfaceName
, aPropertyName
);
219 static gboolean
HandleSetProperty(GDBusConnection
* aConnection
,
220 const gchar
* aSender
,
221 const gchar
* aObjectPath
,
222 const gchar
* aInterfaceName
,
223 const gchar
* aPropertyName
, GVariant
* aValue
,
224 GError
** aError
, gpointer aUserData
) {
225 MOZ_ASSERT(aUserData
);
226 MOZ_ASSERT(NS_IsMainThread());
227 g_set_error(aError
, G_IO_ERROR
, G_IO_ERROR_FAILED
,
228 "%s:%s setting is not supported", aInterfaceName
, aPropertyName
);
232 static const GDBusInterfaceVTable gInterfaceVTable
= {
233 HandleMethodCall
, HandleGetProperty
, HandleSetProperty
};
235 void DBusService::OnBusAcquired(GDBusConnection
* aConnection
) {
236 GUniquePtr
<GError
> error
;
237 mIntrospectionData
= dont_AddRef(g_dbus_node_info_new_for_xml(
238 kIntrospectTemplate
, getter_Transfers(error
)));
239 if (!mIntrospectionData
) {
240 g_warning("DBusService: g_dbus_node_info_new_for_xml() failed! %s",
245 mRegistrationId
= g_dbus_connection_register_object(
246 aConnection
, GetDBusObjectPath(), mIntrospectionData
->interfaces
[0],
247 &gInterfaceVTable
, this, /* user_data */
248 nullptr, /* user_data_free_func */
249 getter_Transfers(error
)); /* GError** */
251 if (mRegistrationId
== 0) {
253 "DBusService: g_dbus_connection_register_object() "
260 void DBusService::OnNameAcquired(GDBusConnection
* aConnection
) {
261 mConnection
= aConnection
;
264 void DBusService::OnNameLost(GDBusConnection
* aConnection
) {
265 mConnection
= nullptr;
266 if (!mRegistrationId
) {
269 if (g_dbus_connection_unregister_object(aConnection
, mRegistrationId
)) {
274 bool DBusService::StartFreedesktopListener() {
276 // We're already connected so we don't need to reconnect
280 mDBusID
= g_bus_own_name(
281 // if org.mozilla.Firefox is taken it means we're already running
282 // so use G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE and quit.
283 G_BUS_TYPE_SESSION
, GetDBusBusName(), G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE
,
284 [](GDBusConnection
* aConnection
, const gchar
*,
285 gpointer aUserData
) -> void {
286 static_cast<DBusService
*>(aUserData
)->OnBusAcquired(aConnection
);
288 [](GDBusConnection
* aConnection
, const gchar
*,
289 gpointer aUserData
) -> void {
290 static_cast<DBusService
*>(aUserData
)->OnNameAcquired(aConnection
);
292 [](GDBusConnection
* aConnection
, const gchar
*,
293 gpointer aUserData
) -> void {
294 static_cast<DBusService
*>(aUserData
)->OnNameLost(aConnection
);
299 g_warning("DBusService: g_bus_own_name() failed!");
306 void DBusService::StopFreedesktopListener() {
307 OnNameLost(mConnection
);
309 g_bus_unown_name(mDBusID
);
312 mIntrospectionData
= nullptr;