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 |
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 (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 | |
161 | static gboolean |
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 (string: 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 |
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 |
189 | app_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 | |
198 | static int |
199 | app_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 | |
235 | static gchar * |
236 | app_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 | |
253 | static int |
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 (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 | |
294 | static GVariant * |
295 | app_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 | |
308 | static int |
309 | app_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: ¶ms, 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: ¶ms); |
348 | g_error_free (error); |
349 | g_free (mem: context); |
350 | return 1; |
351 | } |
352 | |
353 | g_variant_builder_add (builder: ¶ms, 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: ¶ms); |
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, ¶ms, app_get_platform_data ())); |
365 | } |
366 | |
367 | static int |
368 | app_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 | |
373 | static int |
374 | app_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 | |
400 | static int |
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, 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 | |
437 | int |
438 | main (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 | |