Updated FSF's address
[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     if (path[i] == '.')
242       path[i] = '/';
243
244   return path;
245 }
246
247 static int
248 app_call (const gchar *app_id,
249           const gchar *method_name,
250           GVariant    *parameters)
251 {
252   GDBusConnection *session;
253   GError *error = NULL;
254   gchar *object_path;
255   GVariant *result;
256
257
258   session = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
259   if (!session)
260     {
261       g_variant_unref (g_variant_ref_sink (parameters));
262       g_printerr (_("unable to connect to D-Bus: %s\n"), error->message);
263       g_error_free (error);
264       return 1;
265     }
266
267   object_path = app_path_for_id (app_id);
268
269   result = g_dbus_connection_call_sync (session, app_id, object_path, "org.freedesktop.Application",
270                                         method_name, parameters, G_VARIANT_TYPE_UNIT,
271                                         G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
272
273   g_free (object_path);
274
275   if (result)
276     {
277       g_variant_unref (result);
278       return 0;
279     }
280   else
281     {
282       g_printerr (_("error sending %s message to application: %s\n"), method_name, error->message);
283       g_error_free (error);
284       return 1;
285     }
286 }
287
288 static GVariant *
289 app_get_platform_data (void)
290 {
291   GVariantBuilder builder;
292   const gchar *startup_id;
293
294   g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
295
296   if ((startup_id = g_getenv ("DESKTOP_STARTUP_iD")))
297     g_variant_builder_add (&builder, "{sv}", "desktop-startup-id", g_variant_new_string (startup_id));
298
299   return g_variant_builder_end (&builder);
300 }
301
302 static int
303 app_action (gchar **args)
304 {
305   GVariantBuilder params;
306   const gchar *name;
307
308   if (!app_check_name (args, "action"))
309     return 1;
310
311   if (args[1] == NULL)
312     {
313       g_printerr (_("action name must be given after application id\n"));
314       return 1;
315     }
316
317   name = args[1];
318
319   if (!g_action_name_is_valid (name))
320     {
321       g_printerr (_("invalid action name: '%s'\n"
322                     "action names must consist of only alphanumerics, '-' and '.'\n"), name);
323       return 1;
324     }
325
326   g_variant_builder_init (&params, G_VARIANT_TYPE ("av"));
327
328   if (args[2])
329     {
330       GError *error = NULL;
331       GVariant *parameter;
332
333       parameter = g_variant_parse (NULL, args[2], NULL, NULL, &error);
334
335       if (!parameter)
336         {
337           gchar *context;
338
339           context = g_variant_parse_error_print_context (error, args[2]);
340           g_printerr (_("error parsing action parameter: %s\n"), context);
341           g_variant_builder_clear (&params);
342           g_error_free (error);
343           g_free (context);
344           return 1;
345         }
346
347       g_variant_builder_add (&params, "v", parameter);
348       g_variant_unref (parameter);
349
350       if (args[3])
351         {
352           g_printerr (_("actions accept a maximum of one parameter\n"));
353           g_variant_builder_clear (&params);
354           return 1;
355         }
356     }
357
358   return app_call (args[0], "ActivateAction", g_variant_new ("(sav@a{sv})", name, &params, app_get_platform_data ()));
359 }
360
361 static int
362 app_activate (const gchar *app_id)
363 {
364   return app_call (app_id, "Activate", g_variant_new ("(@a{sv})", app_get_platform_data ()));
365 }
366
367 static int
368 app_launch (gchar **args)
369 {
370   GVariantBuilder files;
371   gint i;
372
373   if (!app_check_name (args, "launch"))
374     return 1;
375
376   if (args[1] == NULL)
377     return app_activate (args[0]);
378
379   g_variant_builder_init (&files, G_VARIANT_TYPE_STRING_ARRAY);
380
381   for (i = 1; args[i]; i++)
382     {
383       GFile *file;
384
385       /* "This operation never fails" */
386       file = g_file_new_for_commandline_arg (args[i]);
387       g_variant_builder_add_value (&files, g_variant_new_take_string (g_file_get_uri (file)));
388       g_object_unref (file);
389     }
390
391   return app_call (args[0], "Open", g_variant_new ("(as@a{sv})", &files, app_get_platform_data ()));
392 }
393
394 static int
395 app_list_actions (gchar **args)
396 {
397   const gchar * const *actions;
398   GDesktopAppInfo *app_info;
399   gchar *filename;
400   gint i;
401
402   if (!app_check_name (args, "list-actions"))
403     return 1;
404
405   if (args[1])
406     {
407       g_printerr (_("list-actions command takes only the application id"));
408       app_help (FALSE, "list-actions");
409     }
410
411   filename = g_strconcat (args[0], ".desktop", NULL);
412   app_info = g_desktop_app_info_new (filename);
413   g_free (filename);
414
415   if (app_info == NULL)
416     {
417       g_printerr (_("unable to find desktop file for application %s\n"), args[0]);
418       return 1;
419     }
420
421   actions = g_desktop_app_info_list_actions (app_info);
422
423   for (i = 0; actions[i]; i++)
424     g_print ("%s\n", actions[i]);
425
426   g_object_unref (app_info);
427
428   return 0;
429 }
430
431 int
432 main (int argc, char **argv)
433 {
434   setlocale (LC_ALL, "");
435   textdomain (GETTEXT_PACKAGE);
436   bindtextdomain (GETTEXT_PACKAGE, GLIB_LOCALE_DIR);
437 #ifdef HAVE_BIND_TEXTDOMAIN_CODESET
438   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
439 #endif
440
441   if (argc < 2)
442     return app_help (TRUE, NULL);
443
444   if (g_str_equal (argv[1], "help"))
445     return app_help (TRUE, argv[2]);
446
447   if (g_str_equal (argv[1], "version"))
448     return app_version (argv + 2);
449
450   if (g_str_equal (argv[1], "list-apps"))
451     return app_list (argv + 2);
452
453   if (g_str_equal (argv[1], "launch"))
454     return app_launch (argv + 2);
455
456   if (g_str_equal (argv[1], "action"))
457     return app_action (argv + 2);
458
459   if (g_str_equal (argv[1], "list-actions"))
460     return app_list_actions (argv + 2);
461
462   g_printerr (_("unrecognised command: %s\n\n"), argv[1]);
463
464   return app_help (FALSE, NULL);
465 }