blob: 7ad06229976f1d6f04602154714a1998e541cd53 [file] [log] [blame] [edit]
/*
* Copyright © 2013 Canonical Limited
*
* 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));
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 (&params, 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 (&params);
g_error_free (error);
g_free (context);
return 1;
}
g_variant_builder_add (&params, "v", parameter);
g_variant_unref (parameter);
if (args[3])
{
g_printerr (_("actions accept a maximum of one parameter\n"));
g_variant_builder_clear (&params);
return 1;
}
}
return app_call (args[0], "ActivateAction", g_variant_new ("(sav@a{sv})", name, &params, 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);
}