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