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