hook gvariant vectors up to kdbus
[platform/upstream/glib.git] / gio / gapplication-tool.c
1 /*
2  * Copyright © 2013 Canonical 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 licence, 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 Public
15  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
16  *
17  * Author: Ryan Lortie <desrt@desrt.ca>
18  */
19
20 #include "config.h"
21
22 #include <gio/gdesktopappinfo.h>
23
24 #include <glib/gi18n.h>
25 #include <gio/gio.h>
26
27 #include <string.h>
28 #include <locale.h>
29
30 struct help_topic
31 {
32   const gchar *command;
33   const gchar *summary;
34   const gchar *description;
35   const gchar *synopsis;
36 };
37
38 struct help_substvar
39 {
40   const gchar *var;
41   const gchar *description;
42 };
43
44 static const struct help_topic topics[] = {
45   { "help",         N_("Print help"),
46                     N_("Print help"),
47                     N_("[COMMAND]")
48   },
49   { "version",      N_("Print version"),
50                     N_("Print version information and exit")
51   },
52   { "list-apps",    N_("List applications"),
53                     N_("List the installed D-Bus activatable applications (by .desktop files)")
54   },
55   { "launch",       N_("Launch an application"),
56                     N_("Launch the application (with optional files to open)"),
57                     N_("APPID [FILE...]")
58   },
59   { "action",       N_("Activate an action"),
60                     N_("Invoke an action on the application"),
61                     N_("APPID ACTION [PARAMETER]")
62   },
63   { "list-actions", N_("List available actions"),
64                     N_("List static actions for an application (from .desktop file)"),
65                     N_("APPID")
66   }
67 };
68
69 static const struct help_substvar substvars[] = {
70   { N_("COMMAND"),   N_("The command to print detailed help for")                             },
71   { N_("APPID"),     N_("Application identifier in D-Bus format (eg: org.example.viewer)")    },
72   { N_("FILE"),      N_("Optional relative or relative filenames, or URIs to open")           },
73   { N_("ACTION"),    N_("The action name to invoke")                                          },
74   { N_("PARAMETER"), N_("Optional parameter to the action invocation, in GVariant format")    }
75 };
76
77 static int
78 app_help (gboolean     requested,
79           const gchar *command)
80 {
81   const struct help_topic *topic = NULL;
82   GString *string;
83
84   string = g_string_new (NULL);
85
86   if (command)
87     {
88       gint i;
89
90       for (i = 0; i < G_N_ELEMENTS (topics); i++)
91         if (g_str_equal (topics[i].command, command))
92           topic = &topics[i];
93
94       if (!topic)
95         {
96           g_string_printf (string, _("Unknown command %s\n\n"), command);
97           requested = FALSE;
98         }
99     }
100
101   g_string_append (string, _("Usage:\n"));
102
103   if (topic)
104     {
105       gint maxwidth;
106       gint i;
107
108       g_string_append_printf (string, "\n  %s %s %s\n\n", "gapplication",
109                               topic->command, topic->synopsis ? _(topic->synopsis) : "");
110       g_string_append_printf (string, "%s\n\n", _(topic->description));
111
112       if (topic->synopsis)
113         {
114           g_string_append (string, _("Arguments:\n"));
115
116           maxwidth = 0;
117           for (i = 0; i < G_N_ELEMENTS (substvars); i++)
118             if (strstr (topic->synopsis, substvars[i].var))
119               maxwidth = MAX(maxwidth, strlen (_(substvars[i].var)));
120
121           for (i = 0; i < G_N_ELEMENTS (substvars); i++)
122             if (strstr (topic->synopsis, substvars[i].var))
123               g_string_append_printf (string, "  %-*.*s   %s\n", maxwidth, maxwidth,
124                                       _(substvars[i].var), _(substvars[i].description));
125           g_string_append (string, "\n");
126         }
127     }
128   else
129     {
130       gint maxwidth;
131       gint i;
132
133       g_string_append_printf (string, "\n  %s %s %s\n\n", "gapplication", _("COMMAND"), _("[ARGS...]"));
134       g_string_append_printf (string, _("Commands:\n"));
135
136       maxwidth = 0;
137       for (i = 0; i < G_N_ELEMENTS (topics); i++)
138         maxwidth = MAX(maxwidth, strlen (topics[i].command));
139
140       for (i = 0; i < G_N_ELEMENTS (topics); i++)
141         g_string_append_printf (string, "  %-*.*s   %s\n", maxwidth, maxwidth,
142                                 topics[i].command, _(topics[i].summary));
143
144       g_string_append (string, "\n");
145       /* Translators: do not translate 'help', but please translate 'COMMAND'. */
146       g_string_append_printf (string, _("Use '%s help COMMAND' to get detailed help.\n\n"), "gapplication");
147     }
148
149   if (requested)
150     g_print ("%s", string->str);
151   else
152     g_printerr ("%s\n", string->str);
153
154   g_string_free (string, TRUE);
155
156   return requested ? 0 : 1;
157 }
158
159 static gboolean
160 app_check_name (gchar       **args,
161                 const gchar  *command)
162 {
163   if (args[0] == NULL)
164     {
165       g_printerr (_("%s command requires an application id to directly follow\n\n"), command);
166       return FALSE;
167     }
168
169   if (!g_dbus_is_name (args[0]))
170     {
171       g_printerr (_("invalid application id: '%s'\n"), args[0]);
172       return FALSE;
173     }
174
175   return TRUE;
176 }
177
178 static int
179 app_no_args (const gchar *command)
180 {
181   /* Translators: %s is replaced with a command name like 'list-actions' */
182   g_printerr (_("'%s' takes no arguments\n\n"), command);
183   return app_help (FALSE, command);
184 }
185
186 static int
187 app_version (gchar **args)
188 {
189   if (g_strv_length (args))
190     return app_no_args ("version");
191
192   g_print (PACKAGE_VERSION "\n");
193   return 0;
194 }
195
196 static int
197 app_list (gchar **args)
198 {
199   GList *apps;
200
201   if (g_strv_length (args))
202     return app_no_args ("list");
203
204   apps = g_app_info_get_all ();
205
206   while (apps)
207     {
208       GDesktopAppInfo *info = apps->data;
209
210       if (G_IS_DESKTOP_APP_INFO (info))
211         if (g_desktop_app_info_get_boolean (info, "DBusActivatable"))
212           {
213             const gchar *filename;
214
215             filename = g_app_info_get_id (G_APP_INFO (info));
216             if (g_str_has_suffix (filename, ".desktop"))
217               {
218                 gchar *id;
219
220                 id = g_strndup (filename, strlen (filename) - 8);
221                 g_print ("%s\n", id);
222                 g_free (id);
223               }
224           }
225
226       apps = g_list_delete_link (apps, apps);
227       g_object_unref (info);
228     }
229
230   return 0;
231 }
232
233 static gchar *
234 app_path_for_id (const gchar *app_id)
235 {
236   gchar *path;
237   gint i;
238
239   path = g_strconcat ("/", app_id, NULL);
240   for (i = 0; path[i]; i++)
241     {
242       if (path[i] == '.')
243         path[i] = '/';
244       if (path[i] == '-')
245         path[i] = '_';
246     }
247
248   return path;
249 }
250
251 static int
252 app_call (const gchar *app_id,
253           const gchar *method_name,
254           GVariant    *parameters)
255 {
256   GDBusConnection *session;
257   GError *error = NULL;
258   gchar *object_path;
259   GVariant *result;
260
261
262   session = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
263   if (!session)
264     {
265       g_variant_unref (g_variant_ref_sink (parameters));
266       g_printerr (_("unable to connect to D-Bus: %s\n"), error->message);
267       g_error_free (error);
268       return 1;
269     }
270
271   object_path = app_path_for_id (app_id);
272
273   result = g_dbus_connection_call_sync (session, app_id, object_path, "org.freedesktop.Application",
274                                         method_name, parameters, G_VARIANT_TYPE_UNIT,
275                                         G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
276
277   g_free (object_path);
278
279   if (result)
280     {
281       g_variant_unref (result);
282       return 0;
283     }
284   else
285     {
286       g_printerr (_("error sending %s message to application: %s\n"), method_name, error->message);
287       g_error_free (error);
288       return 1;
289     }
290 }
291
292 static GVariant *
293 app_get_platform_data (void)
294 {
295   GVariantBuilder builder;
296   const gchar *startup_id;
297
298   g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
299
300   if ((startup_id = g_getenv ("DESKTOP_STARTUP_ID")))
301     g_variant_builder_add (&builder, "{sv}", "desktop-startup-id", g_variant_new_string (startup_id));
302
303   return g_variant_builder_end (&builder);
304 }
305
306 static int
307 app_action (gchar **args)
308 {
309   GVariantBuilder params;
310   const gchar *name;
311
312   if (!app_check_name (args, "action"))
313     return 1;
314
315   if (args[1] == NULL)
316     {
317       g_printerr (_("action name must be given after application id\n"));
318       return 1;
319     }
320
321   name = args[1];
322
323   if (!g_action_name_is_valid (name))
324     {
325       g_printerr (_("invalid action name: '%s'\n"
326                     "action names must consist of only alphanumerics, '-' and '.'\n"), name);
327       return 1;
328     }
329
330   g_variant_builder_init (&params, G_VARIANT_TYPE ("av"));
331
332   if (args[2])
333     {
334       GError *error = NULL;
335       GVariant *parameter;
336
337       parameter = g_variant_parse (NULL, args[2], NULL, NULL, &error);
338
339       if (!parameter)
340         {
341           gchar *context;
342
343           context = g_variant_parse_error_print_context (error, args[2]);
344           g_printerr (_("error parsing action parameter: %s\n"), context);
345           g_variant_builder_clear (&params);
346           g_error_free (error);
347           g_free (context);
348           return 1;
349         }
350
351       g_variant_builder_add (&params, "v", parameter);
352       g_variant_unref (parameter);
353
354       if (args[3])
355         {
356           g_printerr (_("actions accept a maximum of one parameter\n"));
357           g_variant_builder_clear (&params);
358           return 1;
359         }
360     }
361
362   return app_call (args[0], "ActivateAction", g_variant_new ("(sav@a{sv})", name, &params, app_get_platform_data ()));
363 }
364
365 static int
366 app_activate (const gchar *app_id)
367 {
368   return app_call (app_id, "Activate", g_variant_new ("(@a{sv})", app_get_platform_data ()));
369 }
370
371 static int
372 app_launch (gchar **args)
373 {
374   GVariantBuilder files;
375   gint i;
376
377   if (!app_check_name (args, "launch"))
378     return 1;
379
380   if (args[1] == NULL)
381     return app_activate (args[0]);
382
383   g_variant_builder_init (&files, G_VARIANT_TYPE_STRING_ARRAY);
384
385   for (i = 1; args[i]; i++)
386     {
387       GFile *file;
388
389       /* "This operation never fails" */
390       file = g_file_new_for_commandline_arg (args[i]);
391       g_variant_builder_add_value (&files, g_variant_new_take_string (g_file_get_uri (file)));
392       g_object_unref (file);
393     }
394
395   return app_call (args[0], "Open", g_variant_new ("(as@a{sv})", &files, app_get_platform_data ()));
396 }
397
398 static int
399 app_list_actions (gchar **args)
400 {
401   const gchar * const *actions;
402   GDesktopAppInfo *app_info;
403   gchar *filename;
404   gint i;
405
406   if (!app_check_name (args, "list-actions"))
407     return 1;
408
409   if (args[1])
410     {
411       g_printerr (_("list-actions command takes only the application id"));
412       app_help (FALSE, "list-actions");
413     }
414
415   filename = g_strconcat (args[0], ".desktop", NULL);
416   app_info = g_desktop_app_info_new (filename);
417   g_free (filename);
418
419   if (app_info == NULL)
420     {
421       g_printerr (_("unable to find desktop file for application %s\n"), args[0]);
422       return 1;
423     }
424
425   actions = g_desktop_app_info_list_actions (app_info);
426
427   for (i = 0; actions[i]; i++)
428     g_print ("%s\n", actions[i]);
429
430   g_object_unref (app_info);
431
432   return 0;
433 }
434
435 int
436 main (int argc, char **argv)
437 {
438   setlocale (LC_ALL, "");
439   textdomain (GETTEXT_PACKAGE);
440   bindtextdomain (GETTEXT_PACKAGE, GLIB_LOCALE_DIR);
441 #ifdef HAVE_BIND_TEXTDOMAIN_CODESET
442   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
443 #endif
444
445   if (argc < 2)
446     return app_help (TRUE, NULL);
447
448   if (g_str_equal (argv[1], "help"))
449     return app_help (TRUE, argv[2]);
450
451   if (g_str_equal (argv[1], "version"))
452     return app_version (argv + 2);
453
454   if (g_str_equal (argv[1], "list-apps"))
455     return app_list (argv + 2);
456
457   if (g_str_equal (argv[1], "launch"))
458     return app_launch (argv + 2);
459
460   if (g_str_equal (argv[1], "action"))
461     return app_action (argv + 2);
462
463   if (g_str_equal (argv[1], "list-actions"))
464     return app_list_actions (argv + 2);
465
466   g_printerr (_("unrecognised command: %s\n\n"), argv[1]);
467
468   return app_help (FALSE, NULL);
469 }