/src/glib/gio/gapplicationimpl-dbus.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright © 2010 Codethink Limited |
3 | | * |
4 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
5 | | * |
6 | | * This library is free software; you can redistribute it and/or |
7 | | * modify it under the terms of the GNU Lesser General Public |
8 | | * License as published by the Free Software Foundation; either |
9 | | * version 2.1 of the License, or (at your option) any later version. |
10 | | * |
11 | | * This library is distributed in the hope that it will be useful, |
12 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | | * Lesser General Public License for more details. |
15 | | * |
16 | | * You should have received a copy of the GNU Lesser General |
17 | | * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. |
18 | | * |
19 | | * Authors: Ryan Lortie <desrt@desrt.ca> |
20 | | */ |
21 | | |
22 | | #include "config.h" |
23 | | |
24 | | #include "gapplicationimpl.h" |
25 | | |
26 | | #include "gactiongroup.h" |
27 | | #include "gactiongroupexporter.h" |
28 | | #include "gremoteactiongroup.h" |
29 | | #include "gdbusactiongroup-private.h" |
30 | | #include "gapplication.h" |
31 | | #include "gfile.h" |
32 | | #include "gdbusconnection.h" |
33 | | #include "gdbusintrospection.h" |
34 | | #include "gdbuserror.h" |
35 | | #include "glib/gstdio.h" |
36 | | |
37 | | #include <string.h> |
38 | | #include <stdio.h> |
39 | | |
40 | | #include "gapplicationcommandline.h" |
41 | | #include "gdbusmethodinvocation.h" |
42 | | |
43 | | #ifdef G_OS_UNIX |
44 | | #include "gunixinputstream.h" |
45 | | #include "gunixfdlist.h" |
46 | | #endif |
47 | | |
48 | | /* D-Bus Interface definition {{{1 */ |
49 | | |
50 | | /* For documentation of these interfaces, see |
51 | | * https://wiki.gnome.org/Projects/GLib/GApplication/DBusAPI |
52 | | */ |
53 | | static const gchar org_gtk_Application_xml[] = |
54 | | "<node>" |
55 | | "<interface name='org.gtk.Application'>" |
56 | | "<method name='Activate'>" |
57 | | "<arg type='a{sv}' name='platform-data' direction='in'/>" |
58 | | "</method>" |
59 | | "<method name='Open'>" |
60 | | "<arg type='as' name='uris' direction='in'/>" |
61 | | "<arg type='s' name='hint' direction='in'/>" |
62 | | "<arg type='a{sv}' name='platform-data' direction='in'/>" |
63 | | "</method>" |
64 | | "<method name='CommandLine'>" |
65 | | "<arg type='o' name='path' direction='in'/>" |
66 | | "<arg type='aay' name='arguments' direction='in'/>" |
67 | | "<arg type='a{sv}' name='platform-data' direction='in'/>" |
68 | | "<arg type='i' name='exit-status' direction='out'/>" |
69 | | "</method>" |
70 | | "<property name='Busy' type='b' access='read'/>" |
71 | | "</interface>" |
72 | | "</node>"; |
73 | | |
74 | | static GDBusInterfaceInfo *org_gtk_Application; |
75 | | |
76 | | static const gchar org_freedesktop_Application_xml[] = |
77 | | "<node>" |
78 | | "<interface name='org.freedesktop.Application'>" |
79 | | "<method name='Activate'>" |
80 | | "<arg type='a{sv}' name='platform-data' direction='in'/>" |
81 | | "</method>" |
82 | | "<method name='Open'>" |
83 | | "<arg type='as' name='uris' direction='in'/>" |
84 | | "<arg type='a{sv}' name='platform-data' direction='in'/>" |
85 | | "</method>" |
86 | | "<method name='ActivateAction'>" |
87 | | "<arg type='s' name='action-name' direction='in'/>" |
88 | | "<arg type='av' name='parameter' direction='in'/>" |
89 | | "<arg type='a{sv}' name='platform-data' direction='in'/>" |
90 | | "</method>" |
91 | | "</interface>" |
92 | | "</node>"; |
93 | | |
94 | | static GDBusInterfaceInfo *org_freedesktop_Application; |
95 | | |
96 | | static const gchar org_gtk_private_CommandLine_xml[] = |
97 | | "<node>" |
98 | | "<interface name='org.gtk.private.CommandLine'>" |
99 | | "<method name='Print'>" |
100 | | "<arg type='s' name='message' direction='in'/>" |
101 | | "</method>" |
102 | | "<method name='PrintError'>" |
103 | | "<arg type='s' name='message' direction='in'/>" |
104 | | "</method>" |
105 | | "</interface>" |
106 | | "</node>"; |
107 | | |
108 | | static GDBusInterfaceInfo *org_gtk_private_CommandLine; |
109 | | |
110 | | /* GApplication implementation {{{1 */ |
111 | | struct _GApplicationImpl |
112 | | { |
113 | | GDBusConnection *session_bus; |
114 | | GActionGroup *exported_actions; |
115 | | const gchar *bus_name; |
116 | | guint name_lost_signal; |
117 | | |
118 | | gchar *object_path; |
119 | | guint object_id; |
120 | | guint fdo_object_id; |
121 | | guint actions_id; |
122 | | |
123 | | gboolean properties_live; |
124 | | gboolean primary; |
125 | | gboolean busy; |
126 | | gboolean registered; |
127 | | GApplication *app; |
128 | | }; |
129 | | |
130 | | |
131 | | static GApplicationCommandLine * |
132 | | g_dbus_command_line_new (GDBusMethodInvocation *invocation); |
133 | | |
134 | | static GVariant * |
135 | | g_application_impl_get_property (GDBusConnection *connection, |
136 | | const gchar *sender, |
137 | | const gchar *object_path, |
138 | | const gchar *interface_name, |
139 | | const gchar *property_name, |
140 | | GError **error, |
141 | | gpointer user_data) |
142 | 0 | { |
143 | 0 | GApplicationImpl *impl = user_data; |
144 | |
|
145 | 0 | if (strcmp (property_name, "Busy") == 0) |
146 | 0 | return g_variant_new_boolean (impl->busy); |
147 | | |
148 | 0 | g_assert_not_reached (); |
149 | | |
150 | 0 | return NULL; |
151 | 0 | } |
152 | | |
153 | | static void |
154 | | send_property_change (GApplicationImpl *impl) |
155 | 0 | { |
156 | 0 | GVariantBuilder builder; |
157 | |
|
158 | 0 | g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); |
159 | 0 | g_variant_builder_add (&builder, |
160 | 0 | "{sv}", |
161 | 0 | "Busy", g_variant_new_boolean (impl->busy)); |
162 | |
|
163 | 0 | g_dbus_connection_emit_signal (impl->session_bus, |
164 | 0 | NULL, |
165 | 0 | impl->object_path, |
166 | 0 | "org.freedesktop.DBus.Properties", |
167 | 0 | "PropertiesChanged", |
168 | 0 | g_variant_new ("(sa{sv}as)", |
169 | 0 | "org.gtk.Application", |
170 | 0 | &builder, |
171 | 0 | NULL), |
172 | 0 | NULL); |
173 | 0 | } |
174 | | |
175 | | static void |
176 | | g_application_impl_method_call (GDBusConnection *connection, |
177 | | const gchar *sender, |
178 | | const gchar *object_path, |
179 | | const gchar *interface_name, |
180 | | const gchar *method_name, |
181 | | GVariant *parameters, |
182 | | GDBusMethodInvocation *invocation, |
183 | | gpointer user_data) |
184 | 0 | { |
185 | 0 | GApplicationImpl *impl = user_data; |
186 | 0 | GApplicationClass *class; |
187 | |
|
188 | 0 | class = G_APPLICATION_GET_CLASS (impl->app); |
189 | |
|
190 | 0 | if (strcmp (method_name, "Activate") == 0) |
191 | 0 | { |
192 | 0 | GVariant *platform_data; |
193 | | |
194 | | /* Completely the same for both freedesktop and gtk interfaces */ |
195 | |
|
196 | 0 | g_variant_get (parameters, "(@a{sv})", &platform_data); |
197 | |
|
198 | 0 | class->before_emit (impl->app, platform_data); |
199 | 0 | g_signal_emit_by_name (impl->app, "activate"); |
200 | 0 | class->after_emit (impl->app, platform_data); |
201 | 0 | g_variant_unref (platform_data); |
202 | |
|
203 | 0 | g_dbus_method_invocation_return_value (invocation, NULL); |
204 | 0 | } |
205 | | |
206 | 0 | else if (strcmp (method_name, "Open") == 0) |
207 | 0 | { |
208 | 0 | GApplicationFlags flags; |
209 | 0 | GVariant *platform_data; |
210 | 0 | const gchar *hint; |
211 | 0 | GVariant *array; |
212 | 0 | GFile **files; |
213 | 0 | gint n, i; |
214 | |
|
215 | 0 | flags = g_application_get_flags (impl->app); |
216 | 0 | if ((flags & G_APPLICATION_HANDLES_OPEN) == 0) |
217 | 0 | { |
218 | 0 | g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, "Application does not open files"); |
219 | 0 | return; |
220 | 0 | } |
221 | | |
222 | | /* freedesktop interface has no hint parameter */ |
223 | 0 | if (g_str_equal (interface_name, "org.freedesktop.Application")) |
224 | 0 | { |
225 | 0 | g_variant_get (parameters, "(@as@a{sv})", &array, &platform_data); |
226 | 0 | hint = ""; |
227 | 0 | } |
228 | 0 | else |
229 | 0 | g_variant_get (parameters, "(@as&s@a{sv})", &array, &hint, &platform_data); |
230 | |
|
231 | 0 | n = g_variant_n_children (array); |
232 | 0 | files = g_new (GFile *, n + 1); |
233 | |
|
234 | 0 | for (i = 0; i < n; i++) |
235 | 0 | { |
236 | 0 | const gchar *uri; |
237 | |
|
238 | 0 | g_variant_get_child (array, i, "&s", &uri); |
239 | 0 | files[i] = g_file_new_for_uri (uri); |
240 | 0 | } |
241 | 0 | g_variant_unref (array); |
242 | 0 | files[n] = NULL; |
243 | |
|
244 | 0 | class->before_emit (impl->app, platform_data); |
245 | 0 | g_signal_emit_by_name (impl->app, "open", files, n, hint); |
246 | 0 | class->after_emit (impl->app, platform_data); |
247 | |
|
248 | 0 | g_variant_unref (platform_data); |
249 | |
|
250 | 0 | for (i = 0; i < n; i++) |
251 | 0 | g_object_unref (files[i]); |
252 | 0 | g_free (files); |
253 | |
|
254 | 0 | g_dbus_method_invocation_return_value (invocation, NULL); |
255 | 0 | } |
256 | | |
257 | 0 | else if (strcmp (method_name, "CommandLine") == 0) |
258 | 0 | { |
259 | 0 | GApplicationFlags flags; |
260 | 0 | GApplicationCommandLine *cmdline; |
261 | 0 | GVariant *platform_data; |
262 | 0 | int status; |
263 | |
|
264 | 0 | flags = g_application_get_flags (impl->app); |
265 | 0 | if ((flags & G_APPLICATION_HANDLES_COMMAND_LINE) == 0) |
266 | 0 | { |
267 | 0 | g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, |
268 | 0 | "Application does not handle command line arguments"); |
269 | 0 | return; |
270 | 0 | } |
271 | | |
272 | | /* Only on the GtkApplication interface */ |
273 | | |
274 | 0 | cmdline = g_dbus_command_line_new (invocation); |
275 | 0 | platform_data = g_variant_get_child_value (parameters, 2); |
276 | 0 | class->before_emit (impl->app, platform_data); |
277 | 0 | g_signal_emit_by_name (impl->app, "command-line", cmdline, &status); |
278 | 0 | g_application_command_line_set_exit_status (cmdline, status); |
279 | 0 | class->after_emit (impl->app, platform_data); |
280 | 0 | g_variant_unref (platform_data); |
281 | 0 | g_object_unref (cmdline); |
282 | 0 | } |
283 | 0 | else if (g_str_equal (method_name, "ActivateAction")) |
284 | 0 | { |
285 | 0 | GVariant *parameter = NULL; |
286 | 0 | GVariant *platform_data; |
287 | 0 | GVariantIter *iter; |
288 | 0 | const gchar *name; |
289 | 0 | const GVariantType *parameter_type = NULL; |
290 | | |
291 | | /* Only on the freedesktop interface */ |
292 | |
|
293 | 0 | g_variant_get (parameters, "(&sav@a{sv})", &name, &iter, &platform_data); |
294 | 0 | g_variant_iter_next (iter, "v", ¶meter); |
295 | 0 | g_variant_iter_free (iter); |
296 | | |
297 | | /* Check the action exists and the parameter type matches. */ |
298 | 0 | if (!g_action_group_query_action (impl->exported_actions, |
299 | 0 | name, NULL, ¶meter_type, |
300 | 0 | NULL, NULL, NULL)) |
301 | 0 | { |
302 | 0 | g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, |
303 | 0 | "Unknown action ‘%s’", name); |
304 | 0 | g_clear_pointer (¶meter, g_variant_unref); |
305 | 0 | g_variant_unref (platform_data); |
306 | 0 | return; |
307 | 0 | } |
308 | | |
309 | 0 | if (!((parameter_type == NULL && parameter == NULL) || |
310 | 0 | (parameter_type != NULL && parameter != NULL && g_variant_is_of_type (parameter, parameter_type)))) |
311 | 0 | { |
312 | 0 | g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, |
313 | 0 | "Invalid parameter for action ‘%s’: expected type %s but got type %s", |
314 | 0 | name, |
315 | 0 | (parameter_type != NULL) ? (const gchar *) parameter_type : "()", |
316 | 0 | (parameter != NULL) ? g_variant_get_type_string (parameter) : "()"); |
317 | 0 | g_clear_pointer (¶meter, g_variant_unref); |
318 | 0 | g_variant_unref (platform_data); |
319 | 0 | return; |
320 | 0 | } |
321 | | |
322 | 0 | class->before_emit (impl->app, platform_data); |
323 | 0 | g_action_group_activate_action (impl->exported_actions, name, parameter); |
324 | 0 | class->after_emit (impl->app, platform_data); |
325 | |
|
326 | 0 | if (parameter) |
327 | 0 | g_variant_unref (parameter); |
328 | |
|
329 | 0 | g_variant_unref (platform_data); |
330 | |
|
331 | 0 | g_dbus_method_invocation_return_value (invocation, NULL); |
332 | 0 | } |
333 | 0 | else |
334 | 0 | g_assert_not_reached (); |
335 | 0 | } |
336 | | |
337 | | static gchar * |
338 | | application_path_from_appid (const gchar *appid) |
339 | 0 | { |
340 | 0 | gchar *appid_path, *iter; |
341 | |
|
342 | 0 | if (appid == NULL) |
343 | | /* this is a private implementation detail */ |
344 | 0 | return g_strdup ("/org/gtk/Application/anonymous"); |
345 | | |
346 | 0 | appid_path = g_strconcat ("/", appid, NULL); |
347 | 0 | for (iter = appid_path; *iter; iter++) |
348 | 0 | { |
349 | 0 | if (*iter == '.') |
350 | 0 | *iter = '/'; |
351 | |
|
352 | 0 | if (*iter == '-') |
353 | 0 | *iter = '_'; |
354 | 0 | } |
355 | |
|
356 | 0 | return appid_path; |
357 | 0 | } |
358 | | |
359 | | static void g_application_impl_stop_primary (GApplicationImpl *impl); |
360 | | |
361 | | static void |
362 | | name_lost (GDBusConnection *bus, |
363 | | const char *sender_name, |
364 | | const char *object_path, |
365 | | const char *interface_name, |
366 | | const char *signal_name, |
367 | | GVariant *parameters, |
368 | | gpointer user_data) |
369 | 0 | { |
370 | 0 | GApplicationImpl *impl = user_data; |
371 | 0 | gboolean handled; |
372 | |
|
373 | 0 | impl->primary = FALSE; |
374 | 0 | g_application_impl_stop_primary (impl); |
375 | 0 | g_signal_emit_by_name (impl->app, "name-lost", &handled); |
376 | 0 | } |
377 | | |
378 | | /* Attempt to become the primary instance. |
379 | | * |
380 | | * Returns %TRUE if everything went OK, regardless of if we became the |
381 | | * primary instance or not. %FALSE is reserved for when something went |
382 | | * seriously wrong (and @error will be set too, in that case). |
383 | | * |
384 | | * After a %TRUE return, impl->primary will be TRUE if we were |
385 | | * successful. |
386 | | */ |
387 | | static gboolean |
388 | | g_application_impl_attempt_primary (GApplicationImpl *impl, |
389 | | GCancellable *cancellable, |
390 | | GError **error) |
391 | 0 | { |
392 | 0 | static const GDBusInterfaceVTable vtable = { |
393 | 0 | g_application_impl_method_call, |
394 | 0 | g_application_impl_get_property, |
395 | 0 | NULL, /* set_property */ |
396 | 0 | { 0 } |
397 | 0 | }; |
398 | 0 | GApplicationClass *app_class = G_APPLICATION_GET_CLASS (impl->app); |
399 | 0 | GBusNameOwnerFlags name_owner_flags; |
400 | 0 | GApplicationFlags app_flags; |
401 | 0 | GVariant *reply; |
402 | 0 | guint32 rval; |
403 | 0 | GError *local_error = NULL; |
404 | |
|
405 | 0 | if (org_gtk_Application == NULL) |
406 | 0 | { |
407 | 0 | GError *my_error = NULL; |
408 | 0 | GDBusNodeInfo *info; |
409 | |
|
410 | 0 | info = g_dbus_node_info_new_for_xml (org_gtk_Application_xml, &my_error); |
411 | 0 | if G_UNLIKELY (info == NULL) |
412 | 0 | g_error ("%s", my_error->message); |
413 | 0 | org_gtk_Application = g_dbus_node_info_lookup_interface (info, "org.gtk.Application"); |
414 | 0 | g_assert (org_gtk_Application != NULL); |
415 | 0 | g_dbus_interface_info_ref (org_gtk_Application); |
416 | 0 | g_dbus_node_info_unref (info); |
417 | |
|
418 | 0 | info = g_dbus_node_info_new_for_xml (org_freedesktop_Application_xml, &my_error); |
419 | 0 | if G_UNLIKELY (info == NULL) |
420 | 0 | g_error ("%s", my_error->message); |
421 | 0 | org_freedesktop_Application = g_dbus_node_info_lookup_interface (info, "org.freedesktop.Application"); |
422 | 0 | g_assert (org_freedesktop_Application != NULL); |
423 | 0 | g_dbus_interface_info_ref (org_freedesktop_Application); |
424 | 0 | g_dbus_node_info_unref (info); |
425 | 0 | } |
426 | | |
427 | | /* We could possibly have been D-Bus activated as a result of incoming |
428 | | * requests on either the application or actiongroup interfaces. |
429 | | * Because of how GDBus dispatches messages, we need to ensure that |
430 | | * both of those things are registered before we attempt to request |
431 | | * our name. |
432 | | * |
433 | | * The action group need not be populated yet, as long as it happens |
434 | | * before we return to the mainloop. The reason for that is because |
435 | | * GDBus does the check to make sure the object exists from the worker |
436 | | * thread but doesn't actually dispatch the action invocation until we |
437 | | * hit the mainloop in this thread. There is also no danger of |
438 | | * receiving 'activate' or 'open' signals until after 'startup' runs, |
439 | | * for the same reason. |
440 | | */ |
441 | 0 | impl->object_id = g_dbus_connection_register_object (impl->session_bus, impl->object_path, |
442 | 0 | org_gtk_Application, &vtable, impl, NULL, error); |
443 | |
|
444 | 0 | if (impl->object_id == 0) |
445 | 0 | return FALSE; |
446 | | |
447 | 0 | impl->fdo_object_id = g_dbus_connection_register_object (impl->session_bus, impl->object_path, |
448 | 0 | org_freedesktop_Application, &vtable, impl, NULL, error); |
449 | |
|
450 | 0 | if (impl->fdo_object_id == 0) |
451 | 0 | return FALSE; |
452 | | |
453 | 0 | impl->actions_id = g_dbus_connection_export_action_group (impl->session_bus, impl->object_path, |
454 | 0 | impl->exported_actions, error); |
455 | |
|
456 | 0 | if (impl->actions_id == 0) |
457 | 0 | return FALSE; |
458 | | |
459 | 0 | impl->registered = TRUE; |
460 | 0 | if (!app_class->dbus_register (impl->app, |
461 | 0 | impl->session_bus, |
462 | 0 | impl->object_path, |
463 | 0 | &local_error)) |
464 | 0 | { |
465 | 0 | g_return_val_if_fail (local_error != NULL, FALSE); |
466 | 0 | g_propagate_error (error, g_steal_pointer (&local_error)); |
467 | 0 | return FALSE; |
468 | 0 | } |
469 | | |
470 | 0 | g_return_val_if_fail (local_error == NULL, FALSE); |
471 | | |
472 | 0 | if (impl->bus_name == NULL) |
473 | 0 | { |
474 | | /* If this is a non-unique application then it is sufficient to |
475 | | * have our object paths registered. We can return now. |
476 | | * |
477 | | * Note: non-unique applications always act as primary-instance. |
478 | | */ |
479 | 0 | impl->primary = TRUE; |
480 | 0 | return TRUE; |
481 | 0 | } |
482 | | |
483 | | /* If this is a unique application then we need to attempt to own |
484 | | * the well-known name and fall back to remote mode (!is_primary) |
485 | | * in the case that we can't do that. |
486 | | */ |
487 | 0 | name_owner_flags = G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE; |
488 | 0 | app_flags = g_application_get_flags (impl->app); |
489 | |
|
490 | 0 | if (app_flags & G_APPLICATION_ALLOW_REPLACEMENT) |
491 | 0 | { |
492 | 0 | impl->name_lost_signal = g_dbus_connection_signal_subscribe (impl->session_bus, |
493 | 0 | "org.freedesktop.DBus", |
494 | 0 | "org.freedesktop.DBus", |
495 | 0 | "NameLost", |
496 | 0 | "/org/freedesktop/DBus", |
497 | 0 | impl->bus_name, |
498 | 0 | G_DBUS_SIGNAL_FLAGS_NONE, |
499 | 0 | name_lost, |
500 | 0 | impl, |
501 | 0 | NULL); |
502 | |
|
503 | 0 | name_owner_flags |= G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT; |
504 | 0 | } |
505 | 0 | if (app_flags & G_APPLICATION_REPLACE) |
506 | 0 | name_owner_flags |= G_BUS_NAME_OWNER_FLAGS_REPLACE; |
507 | |
|
508 | 0 | reply = g_dbus_connection_call_sync (impl->session_bus, |
509 | 0 | "org.freedesktop.DBus", |
510 | 0 | "/org/freedesktop/DBus", |
511 | 0 | "org.freedesktop.DBus", |
512 | 0 | "RequestName", |
513 | 0 | g_variant_new ("(su)", impl->bus_name, name_owner_flags), |
514 | 0 | G_VARIANT_TYPE ("(u)"), |
515 | 0 | 0, -1, cancellable, error); |
516 | |
|
517 | 0 | if (reply == NULL) |
518 | 0 | return FALSE; |
519 | | |
520 | 0 | g_variant_get (reply, "(u)", &rval); |
521 | 0 | g_variant_unref (reply); |
522 | | |
523 | | /* DBUS_REQUEST_NAME_REPLY_EXISTS: 3 */ |
524 | 0 | impl->primary = (rval != 3); |
525 | |
|
526 | 0 | if (!impl->primary && impl->name_lost_signal) |
527 | 0 | { |
528 | 0 | g_dbus_connection_signal_unsubscribe (impl->session_bus, impl->name_lost_signal); |
529 | 0 | impl->name_lost_signal = 0; |
530 | 0 | } |
531 | |
|
532 | 0 | return TRUE; |
533 | 0 | } |
534 | | |
535 | | /* Stop doing the things that the primary instance does. |
536 | | * |
537 | | * This should be called if attempting to become the primary instance |
538 | | * failed (in order to clean up any partial success) and should also |
539 | | * be called when freeing the GApplication. |
540 | | * |
541 | | * It is safe to call this multiple times. |
542 | | */ |
543 | | static void |
544 | | g_application_impl_stop_primary (GApplicationImpl *impl) |
545 | 0 | { |
546 | 0 | GApplicationClass *app_class = G_APPLICATION_GET_CLASS (impl->app); |
547 | |
|
548 | 0 | if (impl->registered) |
549 | 0 | { |
550 | 0 | app_class->dbus_unregister (impl->app, |
551 | 0 | impl->session_bus, |
552 | 0 | impl->object_path); |
553 | 0 | impl->registered = FALSE; |
554 | 0 | } |
555 | |
|
556 | 0 | if (impl->object_id) |
557 | 0 | { |
558 | 0 | g_dbus_connection_unregister_object (impl->session_bus, impl->object_id); |
559 | 0 | impl->object_id = 0; |
560 | 0 | } |
561 | |
|
562 | 0 | if (impl->fdo_object_id) |
563 | 0 | { |
564 | 0 | g_dbus_connection_unregister_object (impl->session_bus, impl->fdo_object_id); |
565 | 0 | impl->fdo_object_id = 0; |
566 | 0 | } |
567 | |
|
568 | 0 | if (impl->actions_id) |
569 | 0 | { |
570 | 0 | g_dbus_connection_unexport_action_group (impl->session_bus, impl->actions_id); |
571 | 0 | impl->actions_id = 0; |
572 | 0 | } |
573 | |
|
574 | 0 | if (impl->name_lost_signal) |
575 | 0 | { |
576 | 0 | g_dbus_connection_signal_unsubscribe (impl->session_bus, impl->name_lost_signal); |
577 | 0 | impl->name_lost_signal = 0; |
578 | 0 | } |
579 | |
|
580 | 0 | if (impl->primary && impl->bus_name) |
581 | 0 | { |
582 | 0 | g_dbus_connection_call (impl->session_bus, "org.freedesktop.DBus", |
583 | 0 | "/org/freedesktop/DBus", "org.freedesktop.DBus", |
584 | 0 | "ReleaseName", g_variant_new ("(s)", impl->bus_name), |
585 | 0 | NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); |
586 | 0 | impl->primary = FALSE; |
587 | 0 | } |
588 | 0 | } |
589 | | |
590 | | void |
591 | | g_application_impl_set_busy_state (GApplicationImpl *impl, |
592 | | gboolean busy) |
593 | 0 | { |
594 | 0 | if (impl->busy != busy) |
595 | 0 | { |
596 | 0 | impl->busy = busy; |
597 | 0 | send_property_change (impl); |
598 | 0 | } |
599 | 0 | } |
600 | | |
601 | | void |
602 | | g_application_impl_destroy (GApplicationImpl *impl) |
603 | 0 | { |
604 | 0 | g_application_impl_stop_primary (impl); |
605 | |
|
606 | 0 | if (impl->session_bus) |
607 | 0 | g_object_unref (impl->session_bus); |
608 | |
|
609 | 0 | g_free (impl->object_path); |
610 | |
|
611 | 0 | g_slice_free (GApplicationImpl, impl); |
612 | 0 | } |
613 | | |
614 | | GApplicationImpl * |
615 | | g_application_impl_register (GApplication *application, |
616 | | const gchar *appid, |
617 | | GApplicationFlags flags, |
618 | | GActionGroup *exported_actions, |
619 | | GRemoteActionGroup **remote_actions, |
620 | | GCancellable *cancellable, |
621 | | GError **error) |
622 | 0 | { |
623 | 0 | GDBusActionGroup *actions; |
624 | 0 | GApplicationImpl *impl; |
625 | |
|
626 | 0 | g_assert ((flags & G_APPLICATION_NON_UNIQUE) || appid != NULL); |
627 | | |
628 | 0 | impl = g_slice_new0 (GApplicationImpl); |
629 | |
|
630 | 0 | impl->app = application; |
631 | 0 | impl->exported_actions = exported_actions; |
632 | | |
633 | | /* non-unique applications do not attempt to acquire a bus name */ |
634 | 0 | if (~flags & G_APPLICATION_NON_UNIQUE) |
635 | 0 | impl->bus_name = appid; |
636 | |
|
637 | 0 | impl->session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, cancellable, NULL); |
638 | |
|
639 | 0 | if (impl->session_bus == NULL) |
640 | 0 | { |
641 | | /* If we can't connect to the session bus, proceed as a normal |
642 | | * non-unique application. |
643 | | */ |
644 | 0 | *remote_actions = NULL; |
645 | 0 | return impl; |
646 | 0 | } |
647 | | |
648 | 0 | impl->object_path = application_path_from_appid (appid); |
649 | | |
650 | | /* Only try to be the primary instance if |
651 | | * G_APPLICATION_IS_LAUNCHER was not specified. |
652 | | */ |
653 | 0 | if (~flags & G_APPLICATION_IS_LAUNCHER) |
654 | 0 | { |
655 | 0 | if (!g_application_impl_attempt_primary (impl, cancellable, error)) |
656 | 0 | { |
657 | 0 | g_application_impl_destroy (impl); |
658 | 0 | return NULL; |
659 | 0 | } |
660 | | |
661 | 0 | if (impl->primary) |
662 | 0 | return impl; |
663 | | |
664 | | /* We didn't make it. Drop our service-side stuff. */ |
665 | 0 | g_application_impl_stop_primary (impl); |
666 | |
|
667 | 0 | if (flags & G_APPLICATION_IS_SERVICE) |
668 | 0 | { |
669 | 0 | g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, |
670 | 0 | "Unable to acquire bus name '%s'", appid); |
671 | 0 | g_application_impl_destroy (impl); |
672 | |
|
673 | 0 | return NULL; |
674 | 0 | } |
675 | 0 | } |
676 | | |
677 | | /* We are non-primary. Try to get the primary's list of actions. |
678 | | * This also serves as a mechanism to ensure that the primary exists |
679 | | * (ie: D-Bus service files installed correctly, etc). |
680 | | */ |
681 | 0 | actions = g_dbus_action_group_get (impl->session_bus, impl->bus_name, impl->object_path); |
682 | 0 | if (!g_dbus_action_group_sync (actions, cancellable, error)) |
683 | 0 | { |
684 | | /* The primary appears not to exist. Fail the registration. */ |
685 | 0 | g_application_impl_destroy (impl); |
686 | 0 | g_object_unref (actions); |
687 | |
|
688 | 0 | return NULL; |
689 | 0 | } |
690 | | |
691 | 0 | *remote_actions = G_REMOTE_ACTION_GROUP (actions); |
692 | |
|
693 | 0 | return impl; |
694 | 0 | } |
695 | | |
696 | | void |
697 | | g_application_impl_activate (GApplicationImpl *impl, |
698 | | GVariant *platform_data) |
699 | 0 | { |
700 | 0 | g_dbus_connection_call (impl->session_bus, |
701 | 0 | impl->bus_name, |
702 | 0 | impl->object_path, |
703 | 0 | "org.gtk.Application", |
704 | 0 | "Activate", |
705 | 0 | g_variant_new ("(@a{sv})", platform_data), |
706 | 0 | NULL, 0, -1, NULL, NULL, NULL); |
707 | 0 | } |
708 | | |
709 | | void |
710 | | g_application_impl_open (GApplicationImpl *impl, |
711 | | GFile **files, |
712 | | gint n_files, |
713 | | const gchar *hint, |
714 | | GVariant *platform_data) |
715 | 0 | { |
716 | 0 | GVariantBuilder builder; |
717 | 0 | gint i; |
718 | |
|
719 | 0 | g_variant_builder_init (&builder, G_VARIANT_TYPE ("(assa{sv})")); |
720 | 0 | g_variant_builder_open (&builder, G_VARIANT_TYPE_STRING_ARRAY); |
721 | 0 | for (i = 0; i < n_files; i++) |
722 | 0 | { |
723 | 0 | gchar *uri = g_file_get_uri (files[i]); |
724 | 0 | g_variant_builder_add (&builder, "s", uri); |
725 | 0 | g_free (uri); |
726 | 0 | } |
727 | 0 | g_variant_builder_close (&builder); |
728 | 0 | g_variant_builder_add (&builder, "s", hint); |
729 | 0 | g_variant_builder_add_value (&builder, platform_data); |
730 | |
|
731 | 0 | g_dbus_connection_call (impl->session_bus, |
732 | 0 | impl->bus_name, |
733 | 0 | impl->object_path, |
734 | 0 | "org.gtk.Application", |
735 | 0 | "Open", |
736 | 0 | g_variant_builder_end (&builder), |
737 | 0 | NULL, 0, -1, NULL, NULL, NULL); |
738 | 0 | } |
739 | | |
740 | | static void |
741 | | g_application_impl_cmdline_method_call (GDBusConnection *connection, |
742 | | const gchar *sender, |
743 | | const gchar *object_path, |
744 | | const gchar *interface_name, |
745 | | const gchar *method_name, |
746 | | GVariant *parameters, |
747 | | GDBusMethodInvocation *invocation, |
748 | | gpointer user_data) |
749 | 0 | { |
750 | 0 | const gchar *message; |
751 | |
|
752 | 0 | g_variant_get_child (parameters, 0, "&s", &message); |
753 | |
|
754 | 0 | if (strcmp (method_name, "Print") == 0) |
755 | 0 | g_print ("%s", message); |
756 | 0 | else if (strcmp (method_name, "PrintError") == 0) |
757 | 0 | g_printerr ("%s", message); |
758 | 0 | else |
759 | 0 | g_assert_not_reached (); |
760 | | |
761 | 0 | g_dbus_method_invocation_return_value (invocation, NULL); |
762 | 0 | } |
763 | | |
764 | | typedef struct |
765 | | { |
766 | | GMainLoop *loop; |
767 | | int status; |
768 | | } CommandLineData; |
769 | | |
770 | | static void |
771 | | g_application_impl_cmdline_done (GObject *source, |
772 | | GAsyncResult *result, |
773 | | gpointer user_data) |
774 | 0 | { |
775 | 0 | CommandLineData *data = user_data; |
776 | 0 | GError *error = NULL; |
777 | 0 | GVariant *reply; |
778 | |
|
779 | 0 | #ifdef G_OS_UNIX |
780 | 0 | reply = g_dbus_connection_call_with_unix_fd_list_finish (G_DBUS_CONNECTION (source), NULL, result, &error); |
781 | | #else |
782 | | reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error); |
783 | | #endif |
784 | | |
785 | |
|
786 | 0 | if (reply != NULL) |
787 | 0 | { |
788 | 0 | g_variant_get (reply, "(i)", &data->status); |
789 | 0 | g_variant_unref (reply); |
790 | 0 | } |
791 | | |
792 | 0 | else |
793 | 0 | { |
794 | 0 | g_printerr ("%s\n", error->message); |
795 | 0 | g_error_free (error); |
796 | 0 | data->status = 1; |
797 | 0 | } |
798 | |
|
799 | 0 | g_main_loop_quit (data->loop); |
800 | 0 | } |
801 | | |
802 | | int |
803 | | g_application_impl_command_line (GApplicationImpl *impl, |
804 | | const gchar * const *arguments, |
805 | | GVariant *platform_data) |
806 | 0 | { |
807 | 0 | static const GDBusInterfaceVTable vtable = { |
808 | 0 | g_application_impl_cmdline_method_call, NULL, NULL, { 0 } |
809 | 0 | }; |
810 | 0 | const gchar *object_path = "/org/gtk/Application/CommandLine"; |
811 | 0 | GMainContext *context; |
812 | 0 | CommandLineData data; |
813 | 0 | guint object_id G_GNUC_UNUSED /* when compiling with G_DISABLE_ASSERT */; |
814 | |
|
815 | 0 | context = g_main_context_new (); |
816 | 0 | data.loop = g_main_loop_new (context, FALSE); |
817 | 0 | g_main_context_push_thread_default (context); |
818 | |
|
819 | 0 | if (org_gtk_private_CommandLine == NULL) |
820 | 0 | { |
821 | 0 | GError *error = NULL; |
822 | 0 | GDBusNodeInfo *info; |
823 | |
|
824 | 0 | info = g_dbus_node_info_new_for_xml (org_gtk_private_CommandLine_xml, &error); |
825 | 0 | if G_UNLIKELY (info == NULL) |
826 | 0 | g_error ("%s", error->message); |
827 | 0 | org_gtk_private_CommandLine = g_dbus_node_info_lookup_interface (info, "org.gtk.private.CommandLine"); |
828 | 0 | g_assert (org_gtk_private_CommandLine != NULL); |
829 | 0 | g_dbus_interface_info_ref (org_gtk_private_CommandLine); |
830 | 0 | g_dbus_node_info_unref (info); |
831 | 0 | } |
832 | | |
833 | 0 | object_id = g_dbus_connection_register_object (impl->session_bus, object_path, |
834 | 0 | org_gtk_private_CommandLine, |
835 | 0 | &vtable, &data, NULL, NULL); |
836 | | /* In theory we should try other paths... */ |
837 | 0 | g_assert (object_id != 0); |
838 | | |
839 | 0 | #ifdef G_OS_UNIX |
840 | 0 | { |
841 | 0 | GError *error = NULL; |
842 | 0 | GUnixFDList *fd_list; |
843 | | |
844 | | /* send along the stdin in case |
845 | | * g_application_command_line_get_stdin_data() is called |
846 | | */ |
847 | 0 | fd_list = g_unix_fd_list_new (); |
848 | 0 | g_unix_fd_list_append (fd_list, 0, &error); |
849 | 0 | g_assert_no_error (error); |
850 | |
|
851 | 0 | g_dbus_connection_call_with_unix_fd_list (impl->session_bus, impl->bus_name, impl->object_path, |
852 | 0 | "org.gtk.Application", "CommandLine", |
853 | 0 | g_variant_new ("(o^aay@a{sv})", object_path, arguments, platform_data), |
854 | 0 | G_VARIANT_TYPE ("(i)"), 0, G_MAXINT, fd_list, NULL, |
855 | 0 | g_application_impl_cmdline_done, &data); |
856 | 0 | g_object_unref (fd_list); |
857 | 0 | } |
858 | | #else |
859 | | g_dbus_connection_call (impl->session_bus, impl->bus_name, impl->object_path, |
860 | | "org.gtk.Application", "CommandLine", |
861 | | g_variant_new ("(o^aay@a{sv})", object_path, arguments, platform_data), |
862 | | G_VARIANT_TYPE ("(i)"), 0, G_MAXINT, NULL, |
863 | | g_application_impl_cmdline_done, &data); |
864 | | #endif |
865 | |
|
866 | 0 | g_main_loop_run (data.loop); |
867 | |
|
868 | 0 | g_main_context_pop_thread_default (context); |
869 | 0 | g_main_context_unref (context); |
870 | 0 | g_main_loop_unref (data.loop); |
871 | |
|
872 | 0 | return data.status; |
873 | 0 | } |
874 | | |
875 | | void |
876 | | g_application_impl_flush (GApplicationImpl *impl) |
877 | 0 | { |
878 | 0 | if (impl->session_bus) |
879 | 0 | g_dbus_connection_flush_sync (impl->session_bus, NULL, NULL); |
880 | 0 | } |
881 | | |
882 | | GDBusConnection * |
883 | | g_application_impl_get_dbus_connection (GApplicationImpl *impl) |
884 | 0 | { |
885 | 0 | return impl->session_bus; |
886 | 0 | } |
887 | | |
888 | | const gchar * |
889 | | g_application_impl_get_dbus_object_path (GApplicationImpl *impl) |
890 | 0 | { |
891 | 0 | return impl->object_path; |
892 | 0 | } |
893 | | |
894 | | /* GDBusCommandLine implementation {{{1 */ |
895 | | |
896 | | typedef GApplicationCommandLineClass GDBusCommandLineClass; |
897 | | static GType g_dbus_command_line_get_type (void); |
898 | | typedef struct |
899 | | { |
900 | | GApplicationCommandLine parent_instance; |
901 | | GDBusMethodInvocation *invocation; |
902 | | |
903 | | GDBusConnection *connection; |
904 | | const gchar *bus_name; |
905 | | const gchar *object_path; |
906 | | } GDBusCommandLine; |
907 | | |
908 | | |
909 | | G_DEFINE_TYPE (GDBusCommandLine, |
910 | | g_dbus_command_line, |
911 | | G_TYPE_APPLICATION_COMMAND_LINE) |
912 | | |
913 | | static void |
914 | | g_dbus_command_line_print_literal (GApplicationCommandLine *cmdline, |
915 | | const gchar *message) |
916 | 0 | { |
917 | 0 | GDBusCommandLine *gdbcl = (GDBusCommandLine *) cmdline; |
918 | |
|
919 | 0 | g_dbus_connection_call (gdbcl->connection, |
920 | 0 | gdbcl->bus_name, |
921 | 0 | gdbcl->object_path, |
922 | 0 | "org.gtk.private.CommandLine", "Print", |
923 | 0 | g_variant_new ("(s)", message), |
924 | 0 | NULL, 0, -1, NULL, NULL, NULL); |
925 | 0 | } |
926 | | |
927 | | static void |
928 | | g_dbus_command_line_printerr_literal (GApplicationCommandLine *cmdline, |
929 | | const gchar *message) |
930 | 0 | { |
931 | 0 | GDBusCommandLine *gdbcl = (GDBusCommandLine *) cmdline; |
932 | |
|
933 | 0 | g_dbus_connection_call (gdbcl->connection, |
934 | 0 | gdbcl->bus_name, |
935 | 0 | gdbcl->object_path, |
936 | 0 | "org.gtk.private.CommandLine", "PrintError", |
937 | 0 | g_variant_new ("(s)", message), |
938 | 0 | NULL, 0, -1, NULL, NULL, NULL); |
939 | 0 | } |
940 | | |
941 | | static GInputStream * |
942 | | g_dbus_command_line_get_stdin (GApplicationCommandLine *cmdline) |
943 | 0 | { |
944 | 0 | #ifdef G_OS_UNIX |
945 | 0 | GDBusCommandLine *gdbcl = (GDBusCommandLine *) cmdline; |
946 | 0 | GInputStream *result = NULL; |
947 | 0 | GDBusMessage *message; |
948 | 0 | GUnixFDList *fd_list; |
949 | |
|
950 | 0 | message = g_dbus_method_invocation_get_message (gdbcl->invocation); |
951 | 0 | fd_list = g_dbus_message_get_unix_fd_list (message); |
952 | |
|
953 | 0 | if (fd_list && g_unix_fd_list_get_length (fd_list)) |
954 | 0 | { |
955 | 0 | gint *fds, n_fds, i; |
956 | |
|
957 | 0 | fds = g_unix_fd_list_steal_fds (fd_list, &n_fds); |
958 | 0 | result = g_unix_input_stream_new (fds[0], TRUE); |
959 | 0 | for (i = 1; i < n_fds; i++) |
960 | 0 | (void) g_close (fds[i], NULL); |
961 | 0 | g_free (fds); |
962 | 0 | } |
963 | |
|
964 | 0 | return result; |
965 | | #else |
966 | | return NULL; |
967 | | #endif |
968 | 0 | } |
969 | | |
970 | | static void |
971 | | g_dbus_command_line_finalize (GObject *object) |
972 | 0 | { |
973 | 0 | GApplicationCommandLine *cmdline = G_APPLICATION_COMMAND_LINE (object); |
974 | 0 | GDBusCommandLine *gdbcl = (GDBusCommandLine *) object; |
975 | 0 | gint status; |
976 | |
|
977 | 0 | status = g_application_command_line_get_exit_status (cmdline); |
978 | |
|
979 | 0 | g_dbus_method_invocation_return_value (gdbcl->invocation, |
980 | 0 | g_variant_new ("(i)", status)); |
981 | 0 | g_object_unref (gdbcl->invocation); |
982 | |
|
983 | 0 | G_OBJECT_CLASS (g_dbus_command_line_parent_class) |
984 | 0 | ->finalize (object); |
985 | 0 | } |
986 | | |
987 | | static void |
988 | | g_dbus_command_line_init (GDBusCommandLine *gdbcl) |
989 | 0 | { |
990 | 0 | } |
991 | | |
992 | | static void |
993 | | g_dbus_command_line_class_init (GApplicationCommandLineClass *class) |
994 | 0 | { |
995 | 0 | GObjectClass *object_class = G_OBJECT_CLASS (class); |
996 | |
|
997 | 0 | object_class->finalize = g_dbus_command_line_finalize; |
998 | 0 | class->printerr_literal = g_dbus_command_line_printerr_literal; |
999 | 0 | class->print_literal = g_dbus_command_line_print_literal; |
1000 | 0 | class->get_stdin = g_dbus_command_line_get_stdin; |
1001 | 0 | } |
1002 | | |
1003 | | static GApplicationCommandLine * |
1004 | | g_dbus_command_line_new (GDBusMethodInvocation *invocation) |
1005 | 0 | { |
1006 | 0 | GDBusCommandLine *gdbcl; |
1007 | 0 | GVariant *args; |
1008 | 0 | GVariant *arguments, *platform_data; |
1009 | |
|
1010 | 0 | args = g_dbus_method_invocation_get_parameters (invocation); |
1011 | |
|
1012 | 0 | arguments = g_variant_get_child_value (args, 1); |
1013 | 0 | platform_data = g_variant_get_child_value (args, 2); |
1014 | 0 | gdbcl = g_object_new (g_dbus_command_line_get_type (), |
1015 | 0 | "arguments", arguments, |
1016 | 0 | "platform-data", platform_data, |
1017 | 0 | NULL); |
1018 | 0 | g_variant_unref (arguments); |
1019 | 0 | g_variant_unref (platform_data); |
1020 | |
|
1021 | 0 | gdbcl->connection = g_dbus_method_invocation_get_connection (invocation); |
1022 | 0 | gdbcl->bus_name = g_dbus_method_invocation_get_sender (invocation); |
1023 | 0 | g_variant_get_child (args, 0, "&o", &gdbcl->object_path); |
1024 | 0 | gdbcl->invocation = g_object_ref (invocation); |
1025 | |
|
1026 | 0 | return G_APPLICATION_COMMAND_LINE (gdbcl); |
1027 | 0 | } |
1028 | | |
1029 | | /* Epilogue {{{1 */ |
1030 | | |
1031 | | /* vim:set foldmethod=marker: */ |