Bug 1931425 - Limit how often moz-label's #setStyles runs r=reusable-components-revie...
[gecko.git] / widget / gtk / DBusService.cpp
blob694df804ce87c484792b4866948e164c8a3aea1b
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/. */
6 #include <sys/types.h>
7 #include <unistd.h>
8 #include <fcntl.h>
9 #include <errno.h>
10 #include <gdk/gdk.h>
11 #include "DBusService.h"
12 #include "nsAppRunner.h"
13 #include "mozilla/Unused.h"
14 #include "mozilla/GUniquePtr.h"
15 #include "mozilla/WidgetUtils.h"
16 #include <gio/gio.h>
17 #include "nsIObserverService.h"
18 #include "WidgetUtilsGtk.h"
19 #include "prproces.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
48 }();
49 return name;
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
58 }();
59 return path;
62 // See
63 // https://specifications.freedesktop.org/desktop-entry-spec/1.1/ar01s07.html
64 // for details
65 static const char* kIntrospectTemplate =
66 "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection "
67 "1.0//EN\"\n"
68 "\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n"
69 "<node>\n"
70 "<interface name='org.freedesktop.Application'>\n"
71 "<method name='Activate'>\n"
72 " <arg type='a{sv}' name='platform_data' direction='in'/>\n"
73 " </method>\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"
77 "</method>\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"
82 "</method>\n"
83 "</interface>\n"
84 "</node>\n";
86 bool DBusService::LaunchApp(const char* aCommand, const char** aURIList,
87 int aURIListLen) {
88 nsAutoCString param(mAppFile);
89 if (aCommand) {
90 param.Append(" ");
91 param.Append(aCommand);
93 for (int i = 0; aURIList && i < aURIListLen; i++) {
94 param.Append(" ");
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()),
100 nullptr};
101 int ret =
102 PR_CreateProcessDetached("/bin/sh", argv, nullptr, nullptr) != PR_FAILURE;
103 for (auto str : argv) {
104 free(str);
106 return ret;
109 // The Activate method is called when the application is started without
110 // files to open.
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,
116 G_DBUS_ERROR_FAILED,
117 "Failed to run target application.");
118 return;
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));
130 gsize uriNum = 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,
134 G_DBUS_ERROR_FAILED,
135 "Failed to run target application.");
136 return;
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!");
153 return;
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)
161 bool ret = false;
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);
169 if (!ret) {
170 g_dbus_method_invocation_return_error(aReply, G_DBUS_ERROR,
171 G_DBUS_ERROR_FAILED,
172 "Failed to run target application.");
173 return;
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",
189 aInterfaceName);
190 return;
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,
197 aInvocation);
198 } else if (strcmp("ActivateAction", aMethodName) == 0) {
199 static_cast<DBusService*>(aUserData)->HandleFreedesktopActivateAction(
200 aParameters, aInvocation);
201 } else {
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);
216 return nullptr;
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);
229 return false;
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",
241 error->message);
242 return;
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) {
252 g_warning(
253 "DBusService: g_dbus_connection_register_object() "
254 "failed! %s",
255 error->message);
256 return;
260 void DBusService::OnNameAcquired(GDBusConnection* aConnection) {
261 mConnection = aConnection;
264 void DBusService::OnNameLost(GDBusConnection* aConnection) {
265 mConnection = nullptr;
266 if (!mRegistrationId) {
267 return;
269 if (g_dbus_connection_unregister_object(aConnection, mRegistrationId)) {
270 mRegistrationId = 0;
274 bool DBusService::StartFreedesktopListener() {
275 if (mDBusID) {
276 // We're already connected so we don't need to reconnect
277 return false;
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);
296 this, nullptr);
298 if (!mDBusID) {
299 g_warning("DBusService: g_bus_own_name() failed!");
300 return false;
303 return true;
306 void DBusService::StopFreedesktopListener() {
307 OnNameLost(mConnection);
308 if (mDBusID) {
309 g_bus_unown_name(mDBusID);
310 mDBusID = 0;
312 mIntrospectionData = nullptr;