| /* |
| * Copyright © 2013 Canonical Limited |
| * |
| * SPDX-License-Identifier: LGPL-2.1-or-later |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, see <http://www.gnu.org/licenses/>. |
| * |
| * Author: Ryan Lortie <desrt@desrt.ca> |
| */ |
| |
| #include "config.h" |
| |
| #include <gio/gdesktopappinfo.h> |
| |
| #include <glib/gi18n.h> |
| #include <gio/gio.h> |
| |
| #include <string.h> |
| #include <locale.h> |
| |
| struct help_topic |
| { |
| const gchar *command; |
| const gchar *summary; |
| const gchar *description; |
| const gchar *synopsis; |
| }; |
| |
| struct help_substvar |
| { |
| const gchar *var; |
| const gchar *description; |
| }; |
| |
| static const struct help_topic topics[] = { |
| { "help", N_("Print help"), |
| N_("Print help"), |
| N_("[COMMAND]") |
| }, |
| { "version", N_("Print version"), |
| N_("Print version information and exit"), |
| NULL |
| }, |
| { "list-apps", N_("List applications"), |
| N_("List the installed D-Bus activatable applications (by .desktop files)"), |
| NULL |
| }, |
| { "launch", N_("Launch an application"), |
| N_("Launch the application (with optional files to open)"), |
| N_("APPID [FILE…]") |
| }, |
| { "action", N_("Activate an action"), |
| N_("Invoke an action on the application"), |
| N_("APPID ACTION [PARAMETER]") |
| }, |
| { "list-actions", N_("List available actions"), |
| N_("List static actions for an application (from .desktop file)"), |
| N_("APPID") |
| } |
| }; |
| |
| static const struct help_substvar substvars[] = { |
| { N_("COMMAND"), N_("The command to print detailed help for") }, |
| { N_("APPID"), N_("Application identifier in D-Bus format (eg: org.example.viewer)") }, |
| { N_("FILE"), N_("Optional relative or absolute filenames, or URIs to open") }, |
| { N_("ACTION"), N_("The action name to invoke") }, |
| { N_("PARAMETER"), N_("Optional parameter to the action invocation, in GVariant format") } |
| }; |
| |
| static int |
| app_help (gboolean requested, |
| const gchar *command) |
| { |
| const struct help_topic *topic = NULL; |
| GString *string; |
| |
| string = g_string_new (NULL); |
| |
| if (command) |
| { |
| gsize i; |
| |
| for (i = 0; i < G_N_ELEMENTS (topics); i++) |
| if (g_str_equal (topics[i].command, command)) |
| topic = &topics[i]; |
| |
| if (!topic) |
| { |
| g_string_printf (string, _("Unknown command %s\n\n"), command); |
| requested = FALSE; |
| } |
| } |
| |
| g_string_append (string, _("Usage:\n")); |
| |
| if (topic) |
| { |
| guint maxwidth; |
| gsize i; |
| |
| g_string_append_printf (string, "\n %s %s %s\n\n", "gapplication", |
| topic->command, topic->synopsis ? _(topic->synopsis) : ""); |
| g_string_append_printf (string, "%s\n\n", _(topic->description)); |
| |
| if (topic->synopsis) |
| { |
| g_string_append (string, _("Arguments:\n")); |
| |
| maxwidth = 0; |
| for (i = 0; i < G_N_ELEMENTS (substvars); i++) |
| if (strstr (topic->synopsis, substvars[i].var)) |
| maxwidth = MAX(maxwidth, strlen (_(substvars[i].var))); |
| |
| for (i = 0; i < G_N_ELEMENTS (substvars); i++) |
| if (strstr (topic->synopsis, substvars[i].var)) |
| g_string_append_printf (string, " %-*.*s %s\n", maxwidth, maxwidth, |
| _(substvars[i].var), _(substvars[i].description)); |
| g_string_append (string, "\n"); |
| } |
| } |
| else |
| { |
| guint maxwidth; |
| gsize i; |
| |
| g_string_append_printf (string, "\n %s %s %s\n\n", "gapplication", _("COMMAND"), _("[ARGS…]")); |
| g_string_append_printf (string, _("Commands:\n")); |
| |
| maxwidth = 0; |
| for (i = 0; i < G_N_ELEMENTS (topics); i++) |
| maxwidth = MAX(maxwidth, strlen (topics[i].command)); |
| |
| for (i = 0; i < G_N_ELEMENTS (topics); i++) |
| g_string_append_printf (string, " %-*.*s %s\n", maxwidth, maxwidth, |
| topics[i].command, _(topics[i].summary)); |
| |
| g_string_append (string, "\n"); |
| /* Translators: do not translate 'help', but please translate 'COMMAND'. */ |
| g_string_append_printf (string, _("Use “%s help COMMAND” to get detailed help.\n\n"), "gapplication"); |
| } |
| |
| if (requested) |
| g_print ("%s", string->str); |
| else |
| g_printerr ("%s\n", string->str); |
| |
| g_string_free (string, TRUE); |
| |
| return requested ? 0 : 1; |
| } |
| |
| static gboolean |
| app_check_name (gchar **args, |
| const gchar *command) |
| { |
| if (args[0] == NULL) |
| { |
| g_printerr (_("%s command requires an application id to directly follow\n\n"), command); |
| return FALSE; |
| } |
| |
| if (!g_dbus_is_name (args[0])) |
| { |
| g_printerr (_("invalid application id: “%s”\n"), args[0]); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| static int |
| app_no_args (const gchar *command) |
| { |
| /* Translators: %s is replaced with a command name like 'list-actions' */ |
| g_printerr (_("“%s” takes no arguments\n\n"), command); |
| return app_help (FALSE, command); |
| } |
| |
| static int |
| app_version (gchar **args) |
| { |
| if (g_strv_length (args)) |
| return app_no_args ("version"); |
| |
| g_print (PACKAGE_VERSION "\n"); |
| return 0; |
| } |
| |
| static int |
| app_list (gchar **args) |
| { |
| GList *apps; |
| |
| if (g_strv_length (args)) |
| return app_no_args ("list"); |
| |
| apps = g_app_info_get_all (); |
| |
| while (apps) |
| { |
| GDesktopAppInfo *info = apps->data; |
| |
| if (G_IS_DESKTOP_APP_INFO (info)) |
| if (g_desktop_app_info_get_boolean (info, "DBusActivatable")) |
| { |
| const gchar *filename; |
| |
| filename = g_app_info_get_id (G_APP_INFO (info)); |
| if (g_str_has_suffix (filename, ".desktop")) |
| { |
| gchar *id; |
| |
| id = g_strndup (filename, strlen (filename) - 8); |
| g_print ("%s\n", id); |
| g_free (id); |
| } |
| } |
| |
| apps = g_list_delete_link (apps, apps); |
| g_object_unref (info); |
| } |
| |
| return 0; |
| } |
| |
| static gchar * |
| app_path_for_id (const gchar *app_id) |
| { |
| gchar *path; |
| gint i; |
| |
| path = g_strconcat ("/", app_id, NULL); |
| for (i = 0; path[i]; i++) |
| { |
| if (path[i] == '.') |
| path[i] = '/'; |
| if (path[i] == '-') |
| path[i] = '_'; |
| } |
| |
| return path; |
| } |
| |
| static int |
| app_call (const gchar *app_id, |
| const gchar *method_name, |
| GVariant *parameters) |
| { |
| GDBusConnection *session; |
| GError *error = NULL; |
| gchar *object_path; |
| GVariant *result; |
| |
| |
| session = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); |
| if (!session) |
| { |
| g_variant_unref (g_variant_ref_sink (parameters)); |
| g_printerr (_("unable to connect to D-Bus: %s\n"), error->message); |
| g_error_free (error); |
| return 1; |
| } |
| |
| object_path = app_path_for_id (app_id); |
| |
| result = g_dbus_connection_call_sync (session, app_id, object_path, "org.freedesktop.Application", |
| method_name, parameters, G_VARIANT_TYPE_UNIT, |
| G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); |
| |
| g_free (object_path); |
| |
| if (result) |
| { |
| g_variant_unref (result); |
| return 0; |
| } |
| else |
| { |
| g_printerr (_("error sending %s message to application: %s\n"), method_name, error->message); |
| g_error_free (error); |
| return 1; |
| } |
| } |
| |
| static GVariant * |
| app_get_platform_data (void) |
| { |
| GVariantBuilder builder; |
| const gchar *startup_id; |
| |
| g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); |
| |
| if ((startup_id = g_getenv ("DESKTOP_STARTUP_ID"))) |
| g_variant_builder_add (&builder, "{sv}", "desktop-startup-id", g_variant_new_string (startup_id)); |
| |
| if ((startup_id = g_getenv ("XDG_ACTIVATION_TOKEN"))) |
| g_variant_builder_add (&builder, "{sv}", "activation-token", g_variant_new_string (startup_id)); |
| |
| return g_variant_builder_end (&builder); |
| } |
| |
| static int |
| app_action (gchar **args) |
| { |
| GVariantBuilder params; |
| const gchar *name; |
| |
| if (!app_check_name (args, "action")) |
| return 1; |
| |
| if (args[1] == NULL) |
| { |
| g_printerr (_("action name must be given after application id\n")); |
| return 1; |
| } |
| |
| name = args[1]; |
| |
| if (!g_action_name_is_valid (name)) |
| { |
| g_printerr (_("invalid action name: “%s”\n" |
| "action names must consist of only alphanumerics, “-” and “.”\n"), name); |
| return 1; |
| } |
| |
| g_variant_builder_init (¶ms, G_VARIANT_TYPE ("av")); |
| |
| if (args[2]) |
| { |
| GError *error = NULL; |
| GVariant *parameter; |
| |
| parameter = g_variant_parse (NULL, args[2], NULL, NULL, &error); |
| |
| if (!parameter) |
| { |
| gchar *context; |
| |
| context = g_variant_parse_error_print_context (error, args[2]); |
| g_printerr (_("error parsing action parameter: %s\n"), context); |
| g_variant_builder_clear (¶ms); |
| g_error_free (error); |
| g_free (context); |
| return 1; |
| } |
| |
| g_variant_builder_add (¶ms, "v", parameter); |
| g_variant_unref (parameter); |
| |
| if (args[3]) |
| { |
| g_printerr (_("actions accept a maximum of one parameter\n")); |
| g_variant_builder_clear (¶ms); |
| return 1; |
| } |
| } |
| |
| return app_call (args[0], "ActivateAction", g_variant_new ("(sav@a{sv})", name, ¶ms, app_get_platform_data ())); |
| } |
| |
| static int |
| app_activate (const gchar *app_id) |
| { |
| return app_call (app_id, "Activate", g_variant_new ("(@a{sv})", app_get_platform_data ())); |
| } |
| |
| static int |
| app_launch (gchar **args) |
| { |
| GVariantBuilder files; |
| gint i; |
| |
| if (!app_check_name (args, "launch")) |
| return 1; |
| |
| if (args[1] == NULL) |
| return app_activate (args[0]); |
| |
| g_variant_builder_init (&files, G_VARIANT_TYPE_STRING_ARRAY); |
| |
| for (i = 1; args[i]; i++) |
| { |
| GFile *file; |
| |
| /* "This operation never fails" */ |
| file = g_file_new_for_commandline_arg (args[i]); |
| g_variant_builder_add_value (&files, g_variant_new_take_string (g_file_get_uri (file))); |
| g_object_unref (file); |
| } |
| |
| return app_call (args[0], "Open", g_variant_new ("(as@a{sv})", &files, app_get_platform_data ())); |
| } |
| |
| static int |
| app_list_actions (gchar **args) |
| { |
| const gchar * const *actions; |
| GDesktopAppInfo *app_info; |
| gchar *filename; |
| gint i; |
| |
| if (!app_check_name (args, "list-actions")) |
| return 1; |
| |
| if (args[1]) |
| { |
| g_printerr (_("list-actions command takes only the application id")); |
| app_help (FALSE, "list-actions"); |
| } |
| |
| filename = g_strconcat (args[0], ".desktop", NULL); |
| app_info = g_desktop_app_info_new (filename); |
| g_free (filename); |
| |
| if (app_info == NULL) |
| { |
| g_printerr (_("unable to find desktop file for application %s\n"), args[0]); |
| return 1; |
| } |
| |
| actions = g_desktop_app_info_list_actions (app_info); |
| |
| for (i = 0; actions[i]; i++) |
| g_print ("%s\n", actions[i]); |
| |
| g_object_unref (app_info); |
| |
| return 0; |
| } |
| |
| int |
| main (int argc, char **argv) |
| { |
| setlocale (LC_ALL, ""); |
| textdomain (GETTEXT_PACKAGE); |
| bindtextdomain (GETTEXT_PACKAGE, GLIB_LOCALE_DIR); |
| #ifdef HAVE_BIND_TEXTDOMAIN_CODESET |
| bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); |
| #endif |
| |
| if (argc < 2) |
| return app_help (TRUE, NULL); |
| |
| if (g_str_equal (argv[1], "help")) |
| return app_help (TRUE, argv[2]); |
| |
| if (g_str_equal (argv[1], "version")) |
| return app_version (argv + 2); |
| |
| if (g_str_equal (argv[1], "list-apps")) |
| return app_list (argv + 2); |
| |
| if (g_str_equal (argv[1], "launch")) |
| return app_launch (argv + 2); |
| |
| if (g_str_equal (argv[1], "action")) |
| return app_action (argv + 2); |
| |
| if (g_str_equal (argv[1], "list-actions")) |
| return app_list_actions (argv + 2); |
| |
| g_printerr (_("unrecognised command: %s\n\n"), argv[1]); |
| |
| return app_help (FALSE, NULL); |
| } |