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