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
30struct help_topic
31{
32 const gchar *command;
33 const gchar *summary;
34 const gchar *description;
35 const gchar *synopsis;
36};
37
38struct help_substvar
39{
40 const gchar *var;
41 const gchar *description;
42};
43
44static 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
71static 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
79static int
80app_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 (v1: topics[i].command, v2: 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, format: "\n %s %s %s\n\n", "gapplication",
111 topic->command, topic->synopsis ? _(topic->synopsis) : "");
112 g_string_append_printf (string, format: "%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 (haystack: topic->synopsis, needle: 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 (haystack: topic->synopsis, needle: substvars[i].var))
125 g_string_append_printf (string, format: " %-*.*s %s\n", maxwidth, maxwidth,
126 _(substvars[i].var), _(substvars[i].description));
127 g_string_append (string, val: "\n");
128 }
129 }
130 else
131 {
132 guint maxwidth;
133 gsize i;
134
135 g_string_append_printf (string, format: "\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, format: " %-*.*s %s\n", maxwidth, maxwidth,
144 topics[i].command, _(topics[i].summary));
145
146 g_string_append (string, val: "\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 (format: "%s", string->str);
153 else
154 g_printerr (format: "%s\n", string->str);
155
156 g_string_free (string, TRUE);
157
158 return requested ? 0 : 1;
159}
160
161static gboolean
162app_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 (string: args[0]))
172 {
173 g_printerr (_("invalid application id: “%s”\n"), args[0]);
174 return FALSE;
175 }
176
177 return TRUE;
178}
179
180static int
181app_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
188static int
189app_version (gchar **args)
190{
191 if (g_strv_length (str_array: args))
192 return app_no_args (command: "version");
193
194 g_print (PACKAGE_VERSION "\n");
195 return 0;
196}
197
198static int
199app_list (gchar **args)
200{
201 GList *apps;
202
203 if (g_strv_length (str_array: args))
204 return app_no_args (command: "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, key: "DBusActivatable"))
214 {
215 const gchar *filename;
216
217 filename = g_app_info_get_id (G_APP_INFO (info));
218 if (g_str_has_suffix (str: filename, suffix: ".desktop"))
219 {
220 gchar *id;
221
222 id = g_strndup (str: filename, n: strlen (s: filename) - 8);
223 g_print (format: "%s\n", id);
224 g_free (mem: id);
225 }
226 }
227
228 apps = g_list_delete_link (list: apps, link_: apps);
229 g_object_unref (object: info);
230 }
231
232 return 0;
233}
234
235static gchar *
236app_path_for_id (const gchar *app_id)
237{
238 gchar *path;
239 gint i;
240
241 path = g_strconcat (string1: "/", 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
253static int
254app_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 (bus_type: G_BUS_TYPE_SESSION, NULL, error: &error);
265 if (!session)
266 {
267 g_variant_unref (value: g_variant_ref_sink (value: 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 (connection: session, bus_name: app_id, object_path, interface_name: "org.freedesktop.Application",
276 method_name, parameters, G_VARIANT_TYPE_UNIT,
277 flags: G_DBUS_CALL_FLAGS_NONE, timeout_msec: -1, NULL, error: &error);
278
279 g_free (mem: object_path);
280
281 if (result)
282 {
283 g_variant_unref (value: 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
294static GVariant *
295app_get_platform_data (void)
296{
297 GVariantBuilder builder;
298 const gchar *startup_id;
299
300 g_variant_builder_init (builder: &builder, G_VARIANT_TYPE_VARDICT);
301
302 if ((startup_id = g_getenv (variable: "DESKTOP_STARTUP_ID")))
303 g_variant_builder_add (builder: &builder, format_string: "{sv}", "desktop-startup-id", g_variant_new_string (string: startup_id));
304
305 return g_variant_builder_end (builder: &builder);
306}
307
308static int
309app_action (gchar **args)
310{
311 GVariantBuilder params;
312 const gchar *name;
313
314 if (!app_check_name (args, command: "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 (action_name: 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 (builder: &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, text: args[2], NULL, NULL, error: &error);
340
341 if (!parameter)
342 {
343 gchar *context;
344
345 context = g_variant_parse_error_print_context (error, source_str: args[2]);
346 g_printerr (_("error parsing action parameter: %s\n"), context);
347 g_variant_builder_clear (builder: &params);
348 g_error_free (error);
349 g_free (mem: context);
350 return 1;
351 }
352
353 g_variant_builder_add (builder: &params, format_string: "v", parameter);
354 g_variant_unref (value: parameter);
355
356 if (args[3])
357 {
358 g_printerr (_("actions accept a maximum of one parameter\n"));
359 g_variant_builder_clear (builder: &params);
360 return 1;
361 }
362 }
363
364 return app_call (app_id: args[0], method_name: "ActivateAction", parameters: g_variant_new (format_string: "(sav@a{sv})", name, &params, app_get_platform_data ()));
365}
366
367static int
368app_activate (const gchar *app_id)
369{
370 return app_call (app_id, method_name: "Activate", parameters: g_variant_new (format_string: "(@a{sv})", app_get_platform_data ()));
371}
372
373static int
374app_launch (gchar **args)
375{
376 GVariantBuilder files;
377 gint i;
378
379 if (!app_check_name (args, command: "launch"))
380 return 1;
381
382 if (args[1] == NULL)
383 return app_activate (app_id: args[0]);
384
385 g_variant_builder_init (builder: &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 (arg: args[i]);
393 g_variant_builder_add_value (builder: &files, value: g_variant_new_take_string (string: g_file_get_uri (file)));
394 g_object_unref (object: file);
395 }
396
397 return app_call (app_id: args[0], method_name: "Open", parameters: g_variant_new (format_string: "(as@a{sv})", &files, app_get_platform_data ()));
398}
399
400static int
401app_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, command: "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, command: "list-actions");
415 }
416
417 filename = g_strconcat (string1: args[0], ".desktop", NULL);
418 app_info = g_desktop_app_info_new (desktop_id: filename);
419 g_free (mem: 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 (info: app_info);
428
429 for (i = 0; actions[i]; i++)
430 g_print (format: "%s\n", actions[i]);
431
432 g_object_unref (object: app_info);
433
434 return 0;
435}
436
437int
438main (int argc, char **argv)
439{
440 setlocale (LC_ALL, locale: "");
441 textdomain (GETTEXT_PACKAGE);
442 bindtextdomain (GETTEXT_PACKAGE, GLIB_LOCALE_DIR);
443#ifdef HAVE_BIND_TEXTDOMAIN_CODESET
444 bind_textdomain_codeset (GETTEXT_PACKAGE, codeset: "UTF-8");
445#endif
446
447 if (argc < 2)
448 return app_help (TRUE, NULL);
449
450 if (g_str_equal (v1: argv[1], v2: "help"))
451 return app_help (TRUE, command: argv[2]);
452
453 if (g_str_equal (v1: argv[1], v2: "version"))
454 return app_version (args: argv + 2);
455
456 if (g_str_equal (v1: argv[1], v2: "list-apps"))
457 return app_list (args: argv + 2);
458
459 if (g_str_equal (v1: argv[1], v2: "launch"))
460 return app_launch (args: argv + 2);
461
462 if (g_str_equal (v1: argv[1], v2: "action"))
463 return app_action (args: argv + 2);
464
465 if (g_str_equal (v1: argv[1], v2: "list-actions"))
466 return app_list_actions (args: argv + 2);
467
468 g_printerr (_("unrecognised command: %s\n\n"), argv[1]);
469
470 return app_help (FALSE, NULL);
471}
472

source code of gtk/subprojects/glib/gio/gapplication-tool.c