diff --git a/docs/reference/gio/Makefile.am b/docs/reference/gio/Makefile.am
index 112626b3f..42907caef 100644
--- a/docs/reference/gio/Makefile.am
+++ b/docs/reference/gio/Makefile.am
@@ -146,6 +146,7 @@ man_MANS =
if ENABLE_MAN
man_MANS += \
+ gapplication.1 \
gio-querymodules.1 \
glib-compile-schemas.1 \
glib-compile-resources.1 \
diff --git a/docs/reference/gio/gapplication.xml b/docs/reference/gio/gapplication.xml
new file mode 100644
index 000000000..13e3f2300
--- /dev/null
+++ b/docs/reference/gio/gapplication.xml
@@ -0,0 +1,352 @@
+
+
+ gapplication
+ GIO
+
+
+ Developer
+ Ryan
+ Lortie
+
+
+
+
+
+ gapplication
+ 1
+ User Commands
+
+
+
+ gapplication
+ D-Bus application launcher
+
+
+
+
+ gapplication
+ help
+ COMMAND
+
+
+ gapplication
+ version
+
+
+ gapplication
+ list-apps
+
+
+ gapplication
+ launch
+ APPID
+
+
+ gapplication
+ launch
+ APPID
+ FILE
+
+
+ gapplication
+ list-actions
+ APPID
+
+
+ gapplication
+ action
+ APPID
+ ACTION
+ PARAMETER
+
+
+
+
+ Description
+
+
+ gapplication is a commandline implementation of the client-side of the
+ org.freedesktop.Application interface as specified by the freedesktop.org
+ Desktop Entry Specification.
+
+
+
+ gapplication can be used to start applications that have
+ DBusActivatable set to true in their .desktop
+ files and can be used to send messages to already-running instances of other applications.
+
+
+
+ It is possible for applications to refer to gapplication in the Exec
+ line of their .desktop file to maintain backwards compatibility
+ with implementations that do not directly support DBusActivatable.
+
+
+
+ gapplication ships as part of GLib.
+
+
+
+
+ Commands
+
+
+ Global commands
+
+
+
+
+ help
+ COMMAND
+
+
+
+ Displays a short synopsis of the available commands or provides detailed help on a specific
+ command.
+
+
+
+
+
+
+ version
+
+
+
+ Prints the GLib version whence gapplication came.
+
+
+
+
+
+
+ list-apps
+
+
+
+ Prints a list of all application IDs that are known to support D-Bus activation. This list is
+ generated by scanning .desktop files as per the current
+ XDG_DATA_DIRS.
+
+
+
+
+
+
+ launch
+ APPID
+ FILE
+
+
+
+ Launches an application.
+
+
+ The first parameter is the application ID in the familiar "reverse DNS" style (eg:
+ 'org.gnome.app') without the .desktop
+ suffix.
+
+
+ Optionally, if additional parameters are given, they are treated as the names of files to open and
+ may be filenames or URIs. If no files are given then the application is simply activated.
+
+
+
+
+
+
+ list-actions
+ APPID
+
+
+
+ List the actions declared in the application's .desktop
+ file. The parameter is the application ID, as above.
+
+
+
+
+
+
+ action
+ APPID
+ ACTION
+ PARAMETER
+
+
+
+ Invokes the named action (in the same way as would occur when activating an action specified in
+ the .desktop file).
+
+
+ The application ID (as above) is the first parameter. The action name follows.
+
+
+ Optionally, following the action name can be one parameter, in GVariant format, given as a single
+ argument. Make sure to use sufficient quoting.
+
+
+
+
+
+
+
+
+
+ Examples
+
+
+ From the commandline
+
+
+ Launching an application:
+
+
+
+ gapplication launch org.example.fooview
+
+
+
+ Opening a file with an application:
+
+
+
+ gapplication launch org.example.fooview ~/file.foo
+
+
+
+ Opening many files with an application:
+
+
+
+ gapplication launch org.example.fooview ~/foos/*.foo
+
+
+
+ Invoking an action on an application:
+
+
+
+ gapplication action org.example.fooview create
+
+
+
+ Invoking an action on an application, with an action:
+
+
+
+ gapplication action org.example.fooview show-item '"item_id_828739"'
+
+
+
+
+
+ From the Exec lines of a .desktop file
+
+
+
+ The commandline interface of gapplication was designed so that it could be used
+ directly from the Exec line of a .desktop
+ file.
+
+
+
+ You might want to do this to allow for backwards compatibility with implementations of the specification
+ that do not understand how to do D-Bus activation, without having to install a separate utility program.
+
+
+
+ Consider the following example:
+
+
+
+ [Desktop Entry]
+ Version=1.1
+ Type=Application
+ Name=Foo Viewer
+ DBusActivatable=true
+ MimeType=image/x-foo;
+ Exec=gapplication launch org.example.fooview %F
+ Actions=gallery;create;
+
+ [Desktop Action gallery]
+ Name=Browse Gallery
+ Exec=gapplication action org.example.fooview gallery
+
+ [Desktop Action create]
+ Name=Create a new Foo!
+ Exec=gapplication action org.example.fooview create
+
+
+
+
+ From a script
+
+
+ If installing an application that supports D-Bus activation you may still want to put a file in
+ /usr/bin so that your program can be started from a terminal.
+
+
+
+ It is possible for this file to be a shell script. The script can handle arguments such as --help and
+ --version directly. It can also parse other command line arguments and convert them to uses of
+ gapplication to activate the application, open files, or invoke actions.
+
+
+
+ Here is a simplified example, as may be installed in /usr/bin/fooview:
+
+
+
+ #!/bin/sh
+
+ case "$1" in
+ --help)
+ echo "see 'man fooview' for more information"
+ ;;
+
+ --version)
+ echo "fooview 1.2"
+ ;;
+
+ --gallery)
+ gapplication action org.example.fooview gallery
+ ;;
+
+ --create)
+ gapplication action org.example.fooview create
+ ;;
+
+ -*)
+ echo "unrecognised commandline argument"
+ exit 1
+ ;;
+
+ *)
+ gapplication launch org.example.fooview "$@"
+ ;;
+ esac
+
+
+
+
+
+ See also
+
+ Desktop Entry Specification,
+
+ gdbus
+ 1
+ ,
+
+ xdg-open
+ 1
+ ,
+
+ desktop-file-validate
+ 1
+
+
+
+
+
diff --git a/gio/.gitignore b/gio/.gitignore
index be25b26e7..5aa5588f3 100644
--- a/gio/.gitignore
+++ b/gio/.gitignore
@@ -14,3 +14,4 @@ gnetworking.h
gresource
gschema-compile
gsettings
+gapplication
diff --git a/gio/Makefile.am b/gio/Makefile.am
index ba596e502..7d2496626 100644
--- a/gio/Makefile.am
+++ b/gio/Makefile.am
@@ -720,8 +720,17 @@ gdbus_LDADD = libgio-2.0.la \
$(top_builddir)/glib/libglib-2.0.la \
$(top_builddir)/gobject/libgobject-2.0.la
+# ------------------------------------------------------------------------
+# gapplication(1) tool
+bin_PROGRAMS += gapplication
+gapplication_SOURCES = gapplication-tool.c
+gapplication_LDADD = libgio-2.0.la \
+ $(top_builddir)/glib/libglib-2.0.la \
+ $(top_builddir)/gobject/libgobject-2.0.la
+
completiondir = $(datadir)/bash-completion/completions
completion_DATA = \
+ completion/gapplication \
completion/gdbus \
completion/gsettings \
completion/gresource
diff --git a/gio/completion/gapplication b/gio/completion/gapplication
new file mode 100644
index 000000000..565025bd8
--- /dev/null
+++ b/gio/completion/gapplication
@@ -0,0 +1,55 @@
+
+# Check for bash
+[ -z "$BASH_VERSION" ] && return
+
+####################################################################################################
+
+__app() {
+ case "${COMP_CWORD}" in
+ 1)
+ COMPREPLY=($(compgen -W "help version list-apps launch action list-actions" -- "${COMP_WORDS[1]}"))
+ return 0
+ ;;
+
+ 2)
+ case "${COMP_WORDS[1]}" in
+ launch|action|list-actions)
+ COMPREPLY=($(compgen -W "`gapplication list-apps`" -- "${COMP_WORDS[2]}"))
+ return 0
+ ;;
+
+ *)
+ COMPREPLY=()
+ return 0
+ ;;
+ esac
+ ;;
+ esac
+
+ # Otherwise, what we will do is based on the command in ${COMP_WORDS[1]}
+ case "${COMP_WORDS[1]}" in
+ action)
+ # Word 3 is the action name. This is the only one we can help with.
+ if [ "${COMP_CWORD}" == 3 ]; then
+ COMPREPLY=($(compgen -W "`gapplication list-actions "${COMP_WORDS[2]}"`" -- "${COMP_WORDS[3]}"))
+ return 0
+ else
+ COMPREPLY=()
+ return 0
+ fi
+ ;;
+ launch)
+ # Filenames...
+ COMPREPLY=($(compgen -A file "${COMP_WORDS[COMP_CWORD]}"))
+ return 0
+ ;;
+ *)
+ # Nothing else should be out this far...
+ COMPREPLY=()
+ return 0
+ esac
+}
+
+####################################################################################################
+
+complete -F __app gapplication
diff --git a/gio/gapplication-tool.c b/gio/gapplication-tool.c
new file mode 100644
index 000000000..c1529090e
--- /dev/null
+++ b/gio/gapplication-tool.c
@@ -0,0 +1,463 @@
+/*
+ * 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 of the licence, 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie
+ */
+
+#include "config.h"
+
+#include
+
+#include
+#include
+
+#include
+#include
+
+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")
+ },
+ { "list-apps", N_("List applications"),
+ N_("List the installed D-Bus activatable applications (by .desktop files)")
+ },
+ { "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 relative 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)
+ {
+ gint 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)
+ {
+ gint maxwidth;
+ gint 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
+ {
+ gint maxwidth;
+ gint 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] = '/';
+
+ 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 (¶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)
+ {
+ g_printerr (_("error parsing action parameter: %s\n"), error->message);
+ g_variant_builder_clear (¶ms);
+ g_error_free (error);
+ 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);
+}