Commit 9defb6b1 authored by Allison Karlitskaya's avatar Allison Karlitskaya

New gapplication(1) tool

This is essentially a commandline implementation of the client-side of
the org.freedesktop.Application D-Bus interface.

It includes support for tab-completion based on desktop files and their
contents.

https://bugzilla.gnome.org/show_bug.cgi?id=704218
parent 4e1e36a7
......@@ -146,6 +146,7 @@ man_MANS =
if ENABLE_MAN
man_MANS += \
gapplication.1 \
gio-querymodules.1 \
glib-compile-schemas.1 \
glib-compile-resources.1 \
......
<refentry id="gapplication-tool" lang="en">
<refentryinfo>
<title>gapplication</title>
<productname>GIO</productname>
<authorgroup>
<author>
<contrib>Developer</contrib>
<firstname>Ryan</firstname>
<surname>Lortie</surname>
</author>
</authorgroup>
</refentryinfo>
<refmeta>
<refentrytitle>gapplication</refentrytitle>
<manvolnum>1</manvolnum>
<refmiscinfo class="manual">User Commands</refmiscinfo>
</refmeta>
<refnamediv>
<refname>gapplication</refname>
<refpurpose>D-Bus application launcher</refpurpose>
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis>
<command>gapplication</command>
<arg choice="plain">help</arg>
<arg choice="opt"><replaceable>COMMAND</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>gapplication</command>
<arg choice="plain">version</arg>
</cmdsynopsis>
<cmdsynopsis>
<command>gapplication</command>
<arg choice="plain">list-apps</arg>
</cmdsynopsis>
<cmdsynopsis>
<command>gapplication</command>
<arg choice="plain">launch</arg>
<arg choice="plain"><replaceable>APPID</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>gapplication</command>
<arg choice="plain">launch</arg>
<arg choice="plain"><replaceable>APPID</replaceable></arg>
<arg choice="opt" rep="repeat"><replaceable>FILE</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>gapplication</command>
<arg choice="plain">list-actions</arg>
<arg choice="plain"><replaceable>APPID</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>gapplication</command>
<arg choice="plain">action</arg>
<arg choice="plain"><replaceable>APPID</replaceable></arg>
<arg choice="plain"><replaceable>ACTION</replaceable></arg>
<arg choice="opt"><replaceable>PARAMETER</replaceable></arg>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
<command>gapplication</command> is a commandline implementation of the client-side of the
<interfacename>org.freedesktop.Application</interfacename> interface as specified by the freedesktop.org
Desktop Entry Specification.
</para>
<para>
<command>gapplication</command> can be used to start applications that have
<varname>DBusActivatable</varname> set to <literal>true</literal> in their <filename>.desktop</filename>
files and can be used to send messages to already-running instances of other applications.
</para>
<para>
It is possible for applications to refer to <command>gapplication</command> in the <varname>Exec</varname>
line of their <filename class='extension'>.desktop</filename> file to maintain backwards compatibility
with implementations that do not directly support <varname>DBusActivatable</varname>.
</para>
<para>
<command>gapplication</command> ships as part of <application>GLib</application>.
</para>
</refsect1>
<refsect1>
<title>Commands</title>
<refsect2>
<title>Global commands</title>
<variablelist>
<varlistentry>
<term>
<command>help</command>
<arg choice="opt"><replaceable>COMMAND</replaceable></arg>
</term>
<listitem>
<para>
Displays a short synopsis of the available commands or provides detailed help on a specific
command.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<command>version</command>
</term>
<listitem>
<para>
Prints the GLib version whence <command>gapplication</command> came.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<command>list-apps</command>
</term>
<listitem>
<para>
Prints a list of all application IDs that are known to support D-Bus activation. This list is
generated by scanning <filename class='extension'>.desktop</filename> files as per the current
<envar>XDG_DATA_DIRS</envar>.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<command>launch</command>
<arg choice="plain"><replaceable>APPID</replaceable></arg>
<arg choice="opt" rep="repeat"><replaceable>FILE</replaceable></arg>
</term>
<listitem>
<para>
Launches an application.
</para>
<para>
The first parameter is the application ID in the familiar "reverse DNS" style (eg:
'<literal>org.gnome.app</literal>') without the <filename class='extension'>.desktop</filename>
suffix.
</para>
<para>
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.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<command>list-actions</command>
<arg choice="plain"><replaceable>APPID</replaceable></arg>
</term>
<listitem>
<para>
List the actions declared in the application's <filename class='extension'>.desktop</filename>
file. The parameter is the application ID, as above.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<command>action</command>
<arg choice="plain"><replaceable>APPID</replaceable></arg>
<arg choice="plain"><replaceable>ACTION</replaceable></arg>
<arg choice="opt"><replaceable>PARAMETER</replaceable></arg>
</term>
<listitem>
<para>
Invokes the named action (in the same way as would occur when activating an action specified in
the <filename class='extension'>.desktop</filename> file).
</para>
<para>
The application ID (as above) is the first parameter. The action name follows.
</para>
<para>
Optionally, following the action name can be one parameter, in GVariant format, given as a single
argument. Make sure to use sufficient quoting.
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect2>
</refsect1>
<refsect1>
<title>Examples</title>
<refsect2>
<title>From the commandline</title>
<para>
Launching an application:
</para>
<programlisting>
gapplication launch org.example.fooview
</programlisting>
<para>
Opening a file with an application:
</para>
<programlisting>
gapplication launch org.example.fooview ~/file.foo
</programlisting>
<para>
Opening many files with an application:
</para>
<programlisting>
gapplication launch org.example.fooview ~/foos/*.foo
</programlisting>
<para>
Invoking an action on an application:
</para>
<programlisting>
gapplication action org.example.fooview create
</programlisting>
<para>
Invoking an action on an application, with an action:
</para>
<programlisting>
gapplication action org.example.fooview show-item '"item_id_828739"'
</programlisting>
</refsect2>
<refsect2>
<title>
From the <varname>Exec</varname> lines of a <filename class='extension'>.desktop</filename> file
</title>
<para>
The commandline interface of <command>gapplication</command> was designed so that it could be used
directly from the <varname>Exec</varname> line of a <filename class='extension'>.desktop</filename>
file.
</para>
<para>
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.
</para>
<para>
Consider the following example:
</para>
<programlisting>
[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
</programlisting>
</refsect2>
<refsect2>
<title>From a script</title>
<para>
If installing an application that supports D-Bus activation you may still want to put a file in
<filename class='directory'>/usr/bin</filename> so that your program can be started from a terminal.
</para>
<para>
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
<command>gapplication</command> to activate the application, open files, or invoke actions.
</para>
<para>
Here is a simplified example, as may be installed in <filename>/usr/bin/fooview</filename>:
</para>
<programlisting>
#!/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
</programlisting>
</refsect2>
</refsect1>
<refsect1>
<title>See also</title>
<para>
<ulink url='http://standards.freedesktop.org/desktop-entry-spec/latest/'>Desktop Entry Specification</ulink>,
<citerefentry>
<refentrytitle>gdbus</refentrytitle>
<manvolnum>1</manvolnum>
</citerefentry>,
<citerefentry>
<refentrytitle>xdg-open</refentrytitle>
<manvolnum>1</manvolnum>
</citerefentry>,
<citerefentry>
<refentrytitle>desktop-file-validate</refentrytitle>
<manvolnum>1</manvolnum>
</citerefentry>
</para>
</refsect1>
</refentry>
......@@ -14,3 +14,4 @@ gnetworking.h
gresource
gschema-compile
gsettings
gapplication
......@@ -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
......
# 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
/*
* 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 <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")
},
{ "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;