GApplication dbus: use XML for introspection
[platform/upstream/glib.git] / gio / gapplicationimpl-dbus.c
1 /*
2  * Copyright © 2010 Codethink Limited
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General
15  * Public License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
17  * Boston, MA 02111-1307, USA.
18  *
19  * Authors: Ryan Lortie <desrt@desrt.ca>
20  */
21
22 #include "gapplicationimpl.h"
23
24 #include "gactiongroup.h"
25 #include "gactiongroupexporter.h"
26 #include "gdbusactiongroup.h"
27 #include "gapplication.h"
28 #include "gfile.h"
29 #include "gdbusconnection.h"
30 #include "gdbusintrospection.h"
31 #include "gdbuserror.h"
32 #include "gmenuexporter.h"
33
34 #include <string.h>
35 #include <stdio.h>
36
37 #include "gapplicationcommandline.h"
38 #include "gdbusmethodinvocation.h"
39
40 /* DBus Interface definition {{{1 */
41 static const gchar org_gtk_Application_xml[] =
42   "<node>"
43   "  <interface name='org.gtk.Application'>"
44   "    <method name='Activate'>"
45   "      <arg type='a{sv}' name='platform-data' direction='in'/>"
46   "    </method>"
47   "    <method name='Open'>"
48   "      <arg type='as' name='uris' direction='in'/>"
49   "      <arg type='s' name='hint' direction='in'/>"
50   "      <arg type='a{sv}' name='platform-data' direction='in'/>"
51   "    </method>"
52   "    <method name='CommandLine'>"
53   "      <arg type='o' name='path' direction='in'/>"
54   "      <arg type='aay' name='arguments' direction='in'/>"
55   "      <arg type='a{sv}' name='platform-data' direction='in'/>"
56   "      <arg type='i' name='exit-status' direction='out'/>"
57   "    </method>"
58   "  </interface>"
59   "</node>";
60
61 static GDBusInterfaceInfo *org_gtk_Application;
62
63 static const gchar org_gtk_private_CommandLine_xml[] =
64   "<node>"
65   "  <interface name='org.gtk.private.CommandLine'>"
66   "    <method name='Print'>"
67   "      <arg type='s' name='message' direction='in'/>"
68   "    </method>"
69   "    <method name='PrintError'>"
70   "      <arg type='s' name='message' direction='in'/>"
71   "    </method>"
72   "  </interface>"
73   "</node>";
74
75 static GDBusInterfaceInfo *org_gtk_private_CommandLine;
76
77 /* GApplication implementation {{{1 */
78 struct _GApplicationImpl
79 {
80   GDBusConnection *session_bus;
81   const gchar     *bus_name;
82   gchar           *object_path;
83   guint            object_id;
84   gboolean         actions_exported;
85   gboolean         menu_exported;
86   gpointer         app;
87 };
88
89
90 static GApplicationCommandLine *
91 g_dbus_command_line_new (GDBusMethodInvocation *invocation);
92
93
94 static void
95 g_application_impl_method_call (GDBusConnection       *connection,
96                                 const gchar           *sender,
97                                 const gchar           *object_path,
98                                 const gchar           *interface_name,
99                                 const gchar           *method_name,
100                                 GVariant              *parameters,
101                                 GDBusMethodInvocation *invocation,
102                                 gpointer               user_data)
103 {
104   GApplicationImpl *impl = user_data;
105   GApplicationClass *class;
106
107   class = G_APPLICATION_GET_CLASS (impl->app);
108
109   if (strcmp (method_name, "Activate") == 0)
110     {
111       GVariant *platform_data;
112
113       g_variant_get (parameters, "(@a{sv})", &platform_data);
114       class->before_emit (impl->app, platform_data);
115       g_signal_emit_by_name (impl->app, "activate");
116       class->after_emit (impl->app, platform_data);
117       g_variant_unref (platform_data);
118
119       g_dbus_method_invocation_return_value (invocation, NULL);
120     }
121
122   else if (strcmp (method_name, "Open") == 0)
123     {
124       GVariant *platform_data;
125       const gchar *hint;
126       GVariant *array;
127       GFile **files;
128       gint n, i;
129
130       g_variant_get (parameters, "(@ass@a{sv})",
131                      &array, &hint, &platform_data);
132
133       n = g_variant_n_children (array);
134       files = g_new (GFile *, n + 1);
135
136       for (i = 0; i < n; i++)
137         {
138           const gchar *uri;
139
140           g_variant_get_child (array, i, "&s", &uri);
141           files[i] = g_file_new_for_uri (uri);
142         }
143       g_variant_unref (array);
144       files[n] = NULL;
145
146       class->before_emit (impl->app, platform_data);
147       g_signal_emit_by_name (impl->app, "open", files, n, hint);
148       class->after_emit (impl->app, platform_data);
149
150       g_variant_unref (platform_data);
151
152       for (i = 0; i < n; i++)
153         g_object_unref (files[i]);
154       g_free (files);
155
156       g_dbus_method_invocation_return_value (invocation, NULL);
157     }
158
159   else if (strcmp (method_name, "CommandLine") == 0)
160     {
161       GApplicationCommandLine *cmdline;
162       GVariant *platform_data;
163       int status;
164
165       cmdline = g_dbus_command_line_new (invocation);
166       platform_data = g_variant_get_child_value (parameters, 2);
167       class->before_emit (impl->app, platform_data);
168       g_signal_emit_by_name (impl->app, "command-line", cmdline, &status);
169       g_application_command_line_set_exit_status (cmdline, status);
170       class->after_emit (impl->app, platform_data);
171       g_variant_unref (platform_data);
172       g_object_unref (cmdline);
173     }
174   else
175     g_assert_not_reached ();
176 }
177
178 static gchar *
179 application_path_from_appid (const gchar *appid)
180 {
181   gchar *appid_path, *iter;
182
183   appid_path = g_strconcat ("/", appid, NULL);
184   for (iter = appid_path; *iter; iter++)
185     {
186       if (*iter == '.')
187         *iter = '/';
188
189       if (*iter == '-')
190         *iter = '_';
191     }
192
193   return appid_path;
194 }
195
196 void
197 g_application_impl_destroy (GApplicationImpl *impl)
198 {
199   if (impl->session_bus)
200     {
201       if (impl->object_id)
202         g_dbus_connection_unregister_object (impl->session_bus,
203                                              impl->object_id);
204       if (impl->actions_exported)
205         g_action_group_dbus_export_stop (impl->app);
206       if (impl->menu_exported)
207         g_menu_model_dbus_export_stop (g_application_get_app_menu (impl->app));
208
209       g_dbus_connection_call (impl->session_bus,
210                               "org.freedesktop.DBus",
211                               "/org/freedesktop/DBus",
212                               "org.freedesktop.DBus",
213                               "ReleaseName",
214                               g_variant_new ("(s)",
215                                              impl->bus_name),
216                               NULL,
217                               G_DBUS_CALL_FLAGS_NONE,
218                               -1, NULL, NULL, NULL);
219
220       g_object_unref (impl->session_bus);
221       g_free (impl->object_path);
222     }
223   else
224     {
225       g_assert (impl->object_path == NULL);
226       g_assert (impl->object_id == 0);
227     }
228
229   g_slice_free (GApplicationImpl, impl);
230 }
231
232 GApplicationImpl *
233 g_application_impl_register (GApplication       *application,
234                              const gchar        *appid,
235                              GApplicationFlags   flags,
236                              GActionGroup      **remote_actions,
237                              GCancellable       *cancellable,
238                              GError            **error)
239 {
240   const static GDBusInterfaceVTable vtable = {
241     g_application_impl_method_call
242   };
243   GDBusActionGroup *actions;
244   GApplicationImpl *impl;
245   GVariant *reply;
246   guint32 rval;
247
248   impl = g_slice_new0 (GApplicationImpl);
249
250   impl->app = application;
251   impl->bus_name = appid;
252
253   impl->session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, cancellable, NULL);
254
255   if (impl->session_bus == NULL)
256     {
257       /* If we can't connect to the session bus, proceed as a normal
258        * non-unique application.
259        */
260       *remote_actions = NULL;
261       return impl;
262     }
263
264   impl->object_path = application_path_from_appid (appid);
265
266   /* Only try to be the primary instance if
267    * G_APPLICATION_IS_LAUNCHER was not specified.
268    */
269   if (~flags & G_APPLICATION_IS_LAUNCHER)
270     {
271       if (org_gtk_Application == NULL)
272         {
273           GError *error = NULL;
274           GDBusNodeInfo *info;
275
276           info = g_dbus_node_info_new_for_xml (org_gtk_Application_xml, &error);
277           if G_UNLIKELY (info == NULL)
278             g_error ("%s", error->message);
279           org_gtk_Application = g_dbus_node_info_lookup_interface (info, "org.gtk.Application");
280           g_assert (org_gtk_Application != NULL);
281           g_dbus_interface_info_ref (org_gtk_Application);
282           g_dbus_node_info_unref (info);
283         }
284
285       /* Attempt to become primary instance. */
286       impl->object_id =
287         g_dbus_connection_register_object (impl->session_bus,
288                                            impl->object_path,
289                                            org_gtk_Application,
290                                            &vtable, impl, NULL, error);
291
292       if (impl->object_id == 0)
293         {
294           g_object_unref (impl->session_bus);
295           g_free (impl->object_path);
296           impl->session_bus = NULL;
297           impl->object_path = NULL;
298
299           g_slice_free (GApplicationImpl, impl);
300           return NULL;
301         }
302
303       if (!g_action_group_dbus_export_start (impl->session_bus,
304                                              impl->object_path,
305                                              impl->app, error))
306         {
307           g_dbus_connection_unregister_object (impl->session_bus,
308                                                impl->object_id);
309
310           g_object_unref (impl->session_bus);
311           g_free (impl->object_path);
312           impl->session_bus = NULL;
313           impl->object_path = NULL;
314
315           g_slice_free (GApplicationImpl, impl);
316           return NULL;
317         }
318       impl->actions_exported = TRUE;
319
320       if (g_application_get_app_menu (impl->app))
321         {
322           if (!g_menu_model_dbus_export_start (impl->session_bus,
323                                                impl->object_path,
324                                                g_application_get_app_menu (impl->app),
325                                                error))
326             {
327               g_action_group_dbus_export_stop (impl->app);
328               impl->actions_exported = FALSE;
329
330               g_dbus_connection_unregister_object (impl->session_bus,
331                                                    impl->object_id);
332
333               g_object_unref (impl->session_bus);
334               g_free (impl->object_path);
335               impl->session_bus = NULL;
336               impl->object_path = NULL;
337
338               g_slice_free (GApplicationImpl, impl);
339               return NULL;
340             }
341           impl->menu_exported = TRUE;
342         }
343
344       /* DBUS_NAME_FLAG_DO_NOT_QUEUE: 0x4 */
345       reply = g_dbus_connection_call_sync (impl->session_bus,
346                                            "org.freedesktop.DBus",
347                                            "/org/freedesktop/DBus",
348                                            "org.freedesktop.DBus",
349                                            "RequestName",
350                                            g_variant_new ("(su)",
351                                                           impl->bus_name,
352                                                           0x4),
353                                            G_VARIANT_TYPE ("(u)"),
354                                            0, -1, cancellable, error);
355
356       if (reply == NULL)
357         {
358           g_dbus_connection_unregister_object (impl->session_bus,
359                                                impl->object_id);
360           impl->object_id = 0;
361
362           g_action_group_dbus_export_stop (impl->app);
363           impl->actions_exported = FALSE;
364
365           if (impl->menu_exported)
366             {
367               g_menu_model_dbus_export_stop (g_application_get_app_menu (impl->app));
368               impl->menu_exported = FALSE;
369             }
370
371           g_object_unref (impl->session_bus);
372           g_free (impl->object_path);
373           impl->session_bus = NULL;
374           impl->object_path = NULL;
375
376           g_slice_free (GApplicationImpl, impl);
377           return NULL;
378         }
379
380       g_variant_get (reply, "(u)", &rval);
381       g_variant_unref (reply);
382
383       /* DBUS_REQUEST_NAME_REPLY_EXISTS: 3 */
384       if (rval != 3)
385         {
386           /* We are the primary instance. */
387           g_dbus_connection_emit_signal (impl->session_bus,
388                                          NULL,
389                                          impl->object_path,
390                                          "org.gtk.Application",
391                                          "Hello",
392                                          g_variant_new ("(s)",
393                                                         impl->bus_name),
394                                          NULL);
395           *remote_actions = NULL;
396           return impl;
397         }
398
399       /* We didn't make it.  Drop our service-side stuff. */
400       g_dbus_connection_unregister_object (impl->session_bus,
401                                            impl->object_id);
402       impl->object_id = 0;
403       g_action_group_dbus_export_stop (impl->app);
404       impl->actions_exported = FALSE;
405       if (impl->menu_exported)
406         {
407           g_menu_model_dbus_export_stop (g_application_get_app_menu (impl->app));
408           impl->menu_exported = FALSE;
409         }
410
411       if (flags & G_APPLICATION_IS_SERVICE)
412         {
413           g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
414                        "Unable to acquire bus name `%s'", appid);
415           g_object_unref (impl->session_bus);
416           g_free (impl->object_path);
417
418           g_slice_free (GApplicationImpl, impl);
419           return NULL;
420         }
421     }
422
423   /* We are non-primary.  Try to get the primary's list of actions.
424    * This also serves as a mechanism to ensure that the primary exists
425    * (ie: DBus service files installed correctly, etc).
426    */
427   actions = g_dbus_action_group_new_sync (impl->session_bus, impl->bus_name, impl->object_path,
428                                           G_DBUS_ACTION_GROUP_FLAGS_NONE, NULL, error);
429
430   if (actions == NULL)
431     {
432       /* The primary appears not to exist.  Fail the registration. */
433       g_object_unref (impl->session_bus);
434       g_free (impl->object_path);
435       impl->session_bus = NULL;
436       impl->object_path = NULL;
437
438       g_slice_free (GApplicationImpl, impl);
439       return NULL;
440     }
441
442   *remote_actions = G_ACTION_GROUP (actions);
443
444   return impl;
445 }
446
447 void
448 g_application_impl_activate (GApplicationImpl *impl,
449                              GVariant         *platform_data)
450 {
451   g_dbus_connection_call (impl->session_bus,
452                           impl->bus_name,
453                           impl->object_path,
454                           "org.gtk.Application",
455                           "Activate",
456                           g_variant_new ("(@a{sv})", platform_data),
457                           NULL, 0, -1, NULL, NULL, NULL);
458 }
459
460 void
461 g_application_impl_open (GApplicationImpl  *impl,
462                          GFile            **files,
463                          gint               n_files,
464                          const gchar       *hint,
465                          GVariant          *platform_data)
466 {
467   GVariantBuilder builder;
468   gint i;
469
470   g_variant_builder_init (&builder, G_VARIANT_TYPE ("(assa{sv})"));
471   g_variant_builder_open (&builder, G_VARIANT_TYPE_STRING_ARRAY);
472   for (i = 0; i < n_files; i++)
473     {
474       gchar *uri = g_file_get_uri (files[i]);
475       g_variant_builder_add (&builder, "s", uri);
476       g_free (uri);
477     }
478   g_variant_builder_close (&builder);
479   g_variant_builder_add (&builder, "s", hint);
480   g_variant_builder_add_value (&builder, platform_data);
481
482   g_dbus_connection_call (impl->session_bus,
483                           impl->bus_name,
484                           impl->object_path,
485                           "org.gtk.Application",
486                           "Open",
487                           g_variant_builder_end (&builder),
488                           NULL, 0, -1, NULL, NULL, NULL);
489 }
490
491 static void
492 g_application_impl_cmdline_method_call (GDBusConnection       *connection,
493                                         const gchar           *sender,
494                                         const gchar           *object_path,
495                                         const gchar           *interface_name,
496                                         const gchar           *method_name,
497                                         GVariant              *parameters,
498                                         GDBusMethodInvocation *invocation,
499                                         gpointer               user_data)
500 {
501   const gchar *message;
502
503   g_variant_get_child (parameters, 0, "&s", &message);
504
505   if (strcmp (method_name, "Print") == 0)
506     g_print ("%s", message);
507   else if (strcmp (method_name, "PrintError") == 0)
508     g_printerr ("%s", message);
509   else
510     g_assert_not_reached ();
511
512   g_dbus_method_invocation_return_value (invocation, NULL);
513 }
514
515 typedef struct
516 {
517   GMainLoop *loop;
518   int status;
519 } CommandLineData;
520
521 static void
522 g_application_impl_cmdline_done (GObject      *source,
523                                  GAsyncResult *result,
524                                  gpointer      user_data)
525 {
526   CommandLineData *data = user_data;
527   GError *error = NULL;
528   GVariant *reply;
529
530   reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source),
531                                          result, &error);
532
533   if (reply != NULL)
534     {
535       g_variant_get (reply, "(i)", &data->status);
536       g_variant_unref (reply);
537     }
538
539   else
540     {
541       g_printerr ("%s\n", error->message);
542       g_error_free (error);
543       data->status = 1;
544     }
545
546   g_main_loop_quit (data->loop);
547 }
548
549 int
550 g_application_impl_command_line (GApplicationImpl  *impl,
551                                  gchar            **arguments,
552                                  GVariant          *platform_data)
553 {
554   const static GDBusInterfaceVTable vtable = {
555     g_application_impl_cmdline_method_call
556   };
557   const gchar *object_path = "/org/gtk/Application/CommandLine";
558   GMainContext *context;
559   CommandLineData data;
560   guint object_id;
561
562   context = g_main_context_new ();
563   data.loop = g_main_loop_new (context, FALSE);
564   g_main_context_push_thread_default (context);
565
566   if (org_gtk_private_CommandLine == NULL)
567     {
568       GError *error = NULL;
569       GDBusNodeInfo *info;
570
571       info = g_dbus_node_info_new_for_xml (org_gtk_private_CommandLine_xml, &error);
572       if G_UNLIKELY (info == NULL)
573         g_error ("%s", error->message);
574       org_gtk_private_CommandLine = g_dbus_node_info_lookup_interface (info, "org.gtk.private.CommandLine");
575       g_assert (org_gtk_private_CommandLine != NULL);
576       g_dbus_interface_info_ref (org_gtk_private_CommandLine);
577       g_dbus_node_info_unref (info);
578     }
579
580   object_id = g_dbus_connection_register_object (impl->session_bus, object_path,
581                                                  org_gtk_private_CommandLine,
582                                                  &vtable, &data, NULL, NULL);
583   /* In theory we should try other paths... */
584   g_assert (object_id != 0);
585
586   g_dbus_connection_call (impl->session_bus,
587                           impl->bus_name,
588                           impl->object_path,
589                           "org.gtk.Application",
590                           "CommandLine",
591                           g_variant_new ("(o^aay@a{sv})", object_path,
592                                          arguments, platform_data),
593                           G_VARIANT_TYPE ("(i)"), 0, G_MAXINT, NULL,
594                           g_application_impl_cmdline_done, &data);
595
596   g_main_loop_run (data.loop);
597
598   g_main_context_pop_thread_default (context);
599   g_main_context_unref (context);
600   g_main_loop_unref (data.loop);
601
602   return data.status;
603 }
604
605 void
606 g_application_impl_flush (GApplicationImpl *impl)
607 {
608   if (impl->session_bus)
609     g_dbus_connection_flush_sync (impl->session_bus, NULL, NULL);
610 }
611
612
613 /* GDBusCommandLine implementation {{{1 */
614
615 typedef GApplicationCommandLineClass GDBusCommandLineClass;
616 static GType g_dbus_command_line_get_type (void);
617 typedef struct
618 {
619   GApplicationCommandLine  parent_instance;
620   GDBusMethodInvocation   *invocation;
621
622   GDBusConnection *connection;
623   const gchar     *bus_name;
624   const gchar     *object_path;
625 } GDBusCommandLine;
626
627
628 G_DEFINE_TYPE (GDBusCommandLine,
629                g_dbus_command_line,
630                G_TYPE_APPLICATION_COMMAND_LINE)
631
632 static void
633 g_dbus_command_line_print_literal (GApplicationCommandLine *cmdline,
634                                    const gchar             *message)
635 {
636   GDBusCommandLine *gdbcl = (GDBusCommandLine *) cmdline;
637
638   g_dbus_connection_call (gdbcl->connection,
639                           gdbcl->bus_name,
640                           gdbcl->object_path,
641                           "org.gtk.private.CommandLine", "Print",
642                           g_variant_new ("(s)", message),
643                           NULL, 0, -1, NULL, NULL, NULL);
644 }
645
646 static void
647 g_dbus_command_line_printerr_literal (GApplicationCommandLine *cmdline,
648                                       const gchar             *message)
649 {
650   GDBusCommandLine *gdbcl = (GDBusCommandLine *) cmdline;
651
652   g_dbus_connection_call (gdbcl->connection,
653                           gdbcl->bus_name,
654                           gdbcl->object_path,
655                           "org.gtk.private.CommandLine", "PrintError",
656                           g_variant_new ("(s)", message),
657                           NULL, 0, -1, NULL, NULL, NULL);
658 }
659
660 static void
661 g_dbus_command_line_finalize (GObject *object)
662 {
663   GApplicationCommandLine *cmdline = G_APPLICATION_COMMAND_LINE (object);
664   GDBusCommandLine *gdbcl = (GDBusCommandLine *) object;
665   gint status;
666
667   status = g_application_command_line_get_exit_status (cmdline);
668
669   g_dbus_method_invocation_return_value (gdbcl->invocation,
670                                          g_variant_new ("(i)", status));
671   g_object_unref (gdbcl->invocation);
672
673   G_OBJECT_CLASS (g_dbus_command_line_parent_class)
674     ->finalize (object);
675 }
676
677 static void
678 g_dbus_command_line_init (GDBusCommandLine *gdbcl)
679 {
680 }
681
682 static void
683 g_dbus_command_line_class_init (GApplicationCommandLineClass *class)
684 {
685   GObjectClass *object_class = G_OBJECT_CLASS (class);
686
687   object_class->finalize = g_dbus_command_line_finalize;
688   class->printerr_literal = g_dbus_command_line_printerr_literal;
689   class->print_literal = g_dbus_command_line_print_literal;
690 }
691
692 static GApplicationCommandLine *
693 g_dbus_command_line_new (GDBusMethodInvocation *invocation)
694 {
695   GDBusCommandLine *gdbcl;
696   GVariant *args;
697
698   args = g_dbus_method_invocation_get_parameters (invocation);
699
700   gdbcl = g_object_new (g_dbus_command_line_get_type (),
701                         "arguments", g_variant_get_child_value (args, 1),
702                         "platform-data", g_variant_get_child_value (args, 2),
703                         NULL);
704   gdbcl->connection = g_dbus_method_invocation_get_connection (invocation);
705   gdbcl->bus_name = g_dbus_method_invocation_get_sender (invocation);
706   g_variant_get_child (args, 0, "&o", &gdbcl->object_path);
707   gdbcl->invocation = g_object_ref (invocation);
708
709   return G_APPLICATION_COMMAND_LINE (gdbcl);
710 }
711
712 /* Epilogue {{{1 */
713
714 /* vim:set foldmethod=marker: */