summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Clasen <mclasen@redhat.com>2010-06-07 13:25:39 -0400
committerMatthias Clasen <mclasen@redhat.com>2010-06-07 13:48:42 -0400
commit6427e93757dbc38cc53ffeb87ed814cdb9df0ed4 (patch)
tree6ca2795a4c2872ad40795a03991093665d6e802f
parentaf78f6d418788fa76a2c78298896f9c656d8eb85 (diff)
Merge the wip/gapplication branch
This adds a GApplication object to GIO, which is the core of an application support class, supporting - uniqueness - exporting actions (simple scripting) - standard actions (quit, activate) The implementation for Linux uses D-Bus, takes a name on the session bus, and exports a org.gtk.Application interface. Implementations for Win32 and OS X are still missing.
-rw-r--r--docs/reference/gio/gio-docs.xml4
-rw-r--r--docs/reference/gio/gio-sections.txt32
-rw-r--r--docs/reference/gio/gio.types1
-rw-r--r--gio/Makefile.am5
-rw-r--r--gio/gappinfo.c42
-rw-r--r--gio/gapplication.c1058
-rw-r--r--gio/gapplication.h151
-rw-r--r--gio/gdbusapplication.c426
-rw-r--r--gio/gdbusconnection.h2
-rw-r--r--gio/gdesktopappinfo.c12
-rw-r--r--gio/gio-marshal.list3
-rw-r--r--gio/gio.h1
-rw-r--r--gio/gio.symbols21
-rw-r--r--gio/gnullapplication.c70
-rw-r--r--gio/tests/Makefile.am20
-rw-r--r--gio/tests/appinfo-test.c20
-rw-r--r--gio/tests/appinfo-test.desktop4
-rw-r--r--gio/tests/appinfo.c25
-rw-r--r--gio/tests/application.c134
-rw-r--r--gio/tests/testapp.c79
-rw-r--r--gio/tests/testapps.c533
21 files changed, 2626 insertions, 17 deletions
diff --git a/docs/reference/gio/gio-docs.xml b/docs/reference/gio/gio-docs.xml
index 8fe0d987b..0e092d169 100644
--- a/docs/reference/gio/gio-docs.xml
+++ b/docs/reference/gio/gio-docs.xml
@@ -160,6 +160,10 @@
<xi:include href="xml/gpermission.xml"/>
<xi:include href="xml/gsimplepermission.xml"/>
</chapter>
+ <chapter id="application">
+ <title>Application support</title>
+ <xi:include href="xml/gapplication.xml"/>
+ </chapter>
<chapter id="extending">
<title>Extending GIO</title>
<xi:include href="xml/gvfs.xml"/>
diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt
index f7e68d3df..665a31ea4 100644
--- a/docs/reference/gio/gio-sections.txt
+++ b/docs/reference/gio/gio-sections.txt
@@ -2619,3 +2619,35 @@ g_simple_permission_new
<SUBSECTION Standard>
g_simple_permission_get_type
</SECTION>
+
+<FILE>gapplication</FILE>
+GApplication
+GApplicationClass
+
+g_application_new
+g_application_new_and_register
+g_application_register_with_data
+g_application_format_activation_data
+g_application_get_instance
+g_application_get_id
+g_application_add_action
+g_application_remove_action
+g_application_list_actions
+g_application_set_action_enabled
+g_application_get_action_enabled
+g_application_get_action_description
+g_application_invoke_action
+g_application_run
+g_application_quit
+g_application_is_remote
+<SUBSECTION Standard>
+G_TYPE_APPLICATION
+G_APPLICATION
+G_APPLICATION_CLASS
+G_IS_APPLICATION
+G_IS_APPLICATION_CLASS
+G_APPLICATION_GET_CLASS
+<SUBSECTION Private>
+GApplicationPrivate
+g_application_get_type
+</SECTION>
diff --git a/docs/reference/gio/gio.types b/docs/reference/gio/gio.types
index 5a4ff84a7..5073fc36f 100644
--- a/docs/reference/gio/gio.types
+++ b/docs/reference/gio/gio.types
@@ -1,6 +1,7 @@
g_app_info_create_flags_get_type
g_app_info_get_type
g_app_launch_context_get_type
+g_application_get_type
g_ask_password_flags_get_type
g_async_initable_get_type
g_async_result_get_type
diff --git a/gio/Makefile.am b/gio/Makefile.am
index a2efeea13..fe4053e19 100644
--- a/gio/Makefile.am
+++ b/gio/Makefile.am
@@ -268,6 +268,8 @@ SUBDIRS += tests
libgio_2_0_la_SOURCES = \
gappinfo.c \
+ gapplication.c \
+ gapplication.h \
gasynchelper.c \
gasynchelper.h \
gasyncinitable.c \
@@ -374,6 +376,8 @@ libgio_2_0_la_SOURCES = \
$(marshal_sources) \
$(NULL)
+EXTRA_DIST += gnullapplication.c gdbusapplication.c
+
$(libgio_2_0_la_OBJECTS): $(marshal_sources)
libgio_2_0_la_LIBADD = \
@@ -427,6 +431,7 @@ gio-win32-res.o: gio.rc
gio_headers = \
gappinfo.h \
+ gapplication.h \
gasyncinitable.h \
gasyncresult.h \
gbufferedinputstream.h \
diff --git a/gio/gappinfo.c b/gio/gappinfo.c
index 6feec823c..38e3063ae 100644
--- a/gio/gappinfo.c
+++ b/gio/gappinfo.c
@@ -33,7 +33,7 @@
* @short_description: Application information and launch contexts
* @include: gio/gio.h
*
- * #GAppInfo and #GAppLaunchContext are used for describing and launching
+ * #GAppInfo and #GAppLaunchContext are used for describing and launching
* applications installed on the system.
*
* As of GLib 2.20, URIs will always be converted to POSIX paths
@@ -117,9 +117,9 @@ g_app_info_dup (GAppInfo *appinfo)
/**
* g_app_info_equal:
- * @appinfo1: the first #GAppInfo.
+ * @appinfo1: the first #GAppInfo.
* @appinfo2: the second #GAppInfo.
- *
+ *
* Checks if two #GAppInfo<!-- -->s are equal.
*
* Returns: %TRUE if @appinfo1 is equal to @appinfo2. %FALSE otherwise.
@@ -461,7 +461,7 @@ g_app_info_get_icon (GAppInfo *appinfo)
* @launch_context: a #GAppLaunchContext or %NULL
* @error: a #GError
*
- * Launches the application. Passes @files to the launched application
+ * Launches the application. Passes @files to the launched application
* as arguments, using the optional @launch_context to get information
* about the details of the launcher (like what screen it is on).
* On error, @error will be set accordingly.
@@ -476,8 +476,17 @@ g_app_info_get_icon (GAppInfo *appinfo)
* unsupported uris with strange formats like mailto:), so if you have
* a textual uri you want to pass in as argument, consider using
* g_app_info_launch_uris() instead.
- *
- * Returns: %TRUE on successful launch, %FALSE otherwise.
+ *
+ * On UNIX, this function sets the <envvar>GIO_LAUNCHED_DESKTOP_FILE</envvar>
+ * environment variable with the path of the launched desktop file and
+ * <envvar>GIO_LAUNCHED_DESKTOP_FILE_PID</envvar> to the process
+ * id of the launched process. This can be used to ignore
+ * <envvar>GIO_LAUNCHED_DESKTOP_FILE</envvar>, should it be inherited
+ * by further processes. The <envvar>DISPLAY</envvar> and
+ * <envvar>DESKTOP_STARTUP_ID</envvar> environment variables are also
+ * set, based on information provided in @launch_context.
+ *
+ * Returns: %TRUE on successful launch, %FALSE otherwise.
**/
gboolean
g_app_info_launch (GAppInfo *appinfo,
@@ -540,11 +549,11 @@ g_app_info_supports_files (GAppInfo *appinfo)
/**
* g_app_info_launch_uris:
* @appinfo: a #GAppInfo
- * @uris: a #GList containing URIs to launch.
+ * @uris: a #GList containing URIs to launch.
* @launch_context: a #GAppLaunchContext or %NULL
* @error: a #GError
*
- * Launches the application. Passes @uris to the launched application
+ * Launches the application. Passes @uris to the launched application
* as arguments, using the optional @launch_context to get information
* about the details of the launcher (like what screen it is on).
* On error, @error will be set accordingly.
@@ -555,7 +564,7 @@ g_app_info_supports_files (GAppInfo *appinfo)
* can fail to start if it runs into problems during startup. There is
* no way to detect this.
*
- * Returns: %TRUE on successful launch, %FALSE otherwise.
+ * Returns: %TRUE on successful launch, %FALSE otherwise.
**/
gboolean
g_app_info_launch_uris (GAppInfo *appinfo,
@@ -600,7 +609,7 @@ g_app_info_should_show (GAppInfo *appinfo)
* @launch_context: an optional #GAppLaunchContext.
* @error: a #GError.
*
- * Utility function that launches the default application
+ * Utility function that launches the default application
* registered to handle the specified uri. Synchronous I/O
* is done on the uri to detect the type of the file if
* required.
@@ -726,10 +735,10 @@ g_app_launch_context_init (GAppLaunchContext *launch_context)
* @info: a #GAppInfo
* @files: a #GList of #GFile objects
*
- * Gets the display string for the display. This is used to ensure new
- * applications are started on the same display as the launching
- * application.
- *
+ * Gets the display string for the @context. This is used to ensure new
+ * applications are started on the same display as the launching
+ * application, by setting the <envvar>DISPLAY</envvar> environment variable.
+ *
* Returns: a display string for the display.
**/
char *
@@ -757,13 +766,14 @@ g_app_launch_context_get_display (GAppLaunchContext *context,
* @files: a #GList of of #GFile objects
*
* Initiates startup notification for the application and returns the
- * DESKTOP_STARTUP_ID for the launched operation, if supported.
+ * <envvar>DESKTOP_STARTUP_ID</envvar> for the launched operation,
+ * if supported.
*
* Startup notification IDs are defined in the <ulink
* url="http://standards.freedesktop.org/startup-notification-spec/startup-notification-latest.txt">
* FreeDesktop.Org Startup Notifications standard</ulink>.
*
- * Returns: a startup notification ID for the application, or %NULL if
+ * Returns: a startup notification ID for the application, or %NULL if
* not supported.
**/
char *
diff --git a/gio/gapplication.c b/gio/gapplication.c
new file mode 100644
index 000000000..d974ed9fa
--- /dev/null
+++ b/gio/gapplication.c
@@ -0,0 +1,1058 @@
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright © 2010 Red Hat, Inc
+ *
+ * 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 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Colin Walters <walters@verbum.org>
+ * Emmanuele Bassi <ebassi@linux.intel.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <gobject/gvaluecollector.h>
+
+#include "gapplication.h"
+#include "gio-marshal.h"
+#include "glibintl.h"
+
+#include "gioerror.h"
+
+#include "gdbusconnection.h"
+#include "gdbusintrospection.h"
+#include "gdbusmethodinvocation.h"
+
+#include "gioalias.h"
+
+/**
+ * SECTION: gapplication
+ * @title: GApplication
+ * @short_description: Core application class
+ *
+ * A #GApplication is the foundation of an application, unique for
+ * a given application identifier. The #GApplication wraps some
+ * low-level platform-specific services; it's expected that most
+ * software will use a higher-level application class such as
+ * #GtkApplication or #MxApplication.
+ *
+ * In addition to single-instance-ness, #GApplication provides support
+ * for 'actions', which can be presented to the user in a platform-specific
+ * way (e.g. Windows 7 jump lists). Note that these are just simple actions
+ * without parameters. For more flexible scriptability, implementing a
+ * a separate D-Bus interface is recommended, see e.g.
+ * <xref linkend="gdbus-convenience"/>.
+ *
+ * Before using #GApplication, you must choose an "application identifier".
+ * The expected form of an application identifier is very close to that of
+ * of a <ulink url="http://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names-interface">DBus bus name</ulink>.
+ * Examples include: "com.example.MyApp" "org.example.internal-apps.Calculator"
+ * For convenience, the restrictions on application identifiers are reproduced
+ * here:
+ * <itemizedlist>
+ * <listitem>Application identifiers must contain only the ASCII characters "[A-Z][a-z][0-9]_-" and must not begin with a digit.</listitem>
+ * <listitem>Application identifiers must contain at least one '.' (period) character (and thus at least two elements).</listitem>
+ * <listitem>Application identifiers must not begin with a '.' (period) character.</listitem>
+ * <listitem>Application identifiers must not exceed 255 characters.</listitem>
+ * </itemizedlist>
+ *
+ * <refsect2><title>D-Bus implementation</title>
+ * <para>
+ * On UNIX systems using D-Bus, #GApplication is implemented by claiming the
+ * application identifier as a bus name on the session bus. The implementation
+ * exports an object at the object path that is created by replacing '.' with
+ * '/' in the application identifier (e.g. the object path for the
+ * application id 'org.gtk.TestApp' is '/org/gtk/TestApp'). The object
+ * implements the org.gtk.Application interface.
+ * </para>
+ * <classsynopsis class="interface">
+ * <ooclass><interfacename>org.gtk.Application</interfacename></ooclass>
+ * <methodsynopsis>
+ * <void/>
+ * <methodname>Activate</methodname>
+ * <methodparam><modifier>in</modifier><type>aay</type><parameter>arguments</parameter></methodparam>
+ * <methodparam><modifier>in</modifier><type>a{sv}</type><parameter>data</parameter></methodparam>
+ * </methodsynopsis>
+ * <methodsynopsis>
+ * <void/>
+ * <methodname>InvokeAction</methodname>
+ * <methodparam><modifier>in</modifier><type>s</type><parameter>action</parameter></methodparam>
+ * <methodparam><modifier>in</modifier><type>u</type><parameter>timestamp</parameter></methodparam>
+ * </methodsynopsis>
+ * <methodsynopsis>
+ * <type>a{s(sb)}</type>
+ * <methodname>ListActions</methodname>
+ * <void/>
+ * </methodsynopsis>
+ * <methodsynopsis>
+ * <void/>
+ * <methodname>Quit</methodname>
+ * <methodparam><modifier>in</modifier><type>u</type><parameter>timestamp</parameter></methodparam>
+ * </methodsynopsis>
+ * <methodsynopsis>
+ * <modifier>Signal</modifier>
+ * <void/>
+ * <methodname>ActionsChanged</methodname>
+ * <void/>
+ * </methodsynopsis>
+ * </classsynopsis>
+ * <para>
+ * The <methodname>Activate</methodname> function is called on the existing
+ * application instance when a second instance fails to take the bus name.
+ * @arguments contains the commandline arguments given to the second instance
+ * and @data contains platform-specific additional data, see
+ * g_application_format_activation_data().
+ * </para>
+ * <para>
+ * The <methodname>InvokeAction</methodname> function can be called to invoke
+ * one of the actions exported by the application. The @timestamp parameter
+ * should be taken from the user event that triggered the method call (e.g.
+ * a button press event).
+ * </para>
+ * <para>
+ * The <methodname>ListActions</methodname> function returns a dictionary
+ * with the exported actions of the application. The keys of the dictionary
+ * are the action names, and the values are structs containing the description
+ * for the action and a boolean that represents if the action is enabled or not.
+ * </para>
+ * <para>
+ * The <methodname>Quit</methodname> function can be called to terminate
+ * the application. The @timestamp parameter should be taken from the user
+ * event that triggered the method call (e.g. a button press event).
+ * </para>
+ * <para>
+ * The <methodname>ActionsChanged</methodname> signal is emitted when the
+ * exported actions change (i.e. an action is added, removed, enabled,
+ * disabled, or otherwise changed).
+ * </para>
+ * </refsect2>
+ *
+ * Since: 2.24
+ */
+
+G_DEFINE_TYPE (GApplication, g_application, G_TYPE_OBJECT);
+
+enum
+{
+ PROP_0,
+
+ PROP_APPID,
+ PROP_DEFAULT_QUIT,
+ PROP_IS_REMOTE
+};
+
+enum
+{
+ QUIT,
+ ACTION,
+ PREPARE_ACTIVATION,
+
+ LAST_SIGNAL
+};
+
+static guint application_signals[LAST_SIGNAL] = { 0 };
+
+typedef struct {
+ char *name;
+ char *description;
+ guint enabled : 1;
+} GApplicationAction;
+
+struct _GApplicationPrivate
+{
+ char *appid;
+ GHashTable *actions; /* name -> GApplicationAction */
+ GMainLoop *mainloop;
+
+ guint default_quit : 1;
+ guint is_remote : 1;
+
+ guint actions_changed_id;
+
+#ifdef G_OS_UNIX
+ char *dbus_path;
+ GDBusConnection *session_bus;
+#endif
+};
+
+static GApplication *primary_application = NULL;
+static GHashTable *instances_for_appid = NULL;
+
+static void _g_application_platform_init (GApplication *app);
+static gboolean _g_application_platform_acquire_single_instance (GApplication *app,
+ GError **error);
+static void _g_application_platform_remote_invoke_action (GApplication *app,
+ const char *action,
+ guint timestamp);
+static void _g_application_platform_remote_quit (GApplication *app,
+ guint timestamp);
+static void _g_application_platform_activate (GApplication *app,
+ GVariant *data) G_GNUC_NORETURN;
+static void _g_application_platform_on_actions_changed (GApplication *app);
+
+#ifdef G_OS_UNIX
+#include "gdbusapplication.c"
+#else
+#include "gnullapplication.c"
+#endif
+
+static gboolean
+_g_application_validate_id (const char *id)
+{
+ gboolean allow_dot;
+
+ if (strlen (id) > 255)
+ return FALSE;
+
+ if (!g_ascii_isalpha (*id))
+ return FALSE;
+
+ id++;
+ allow_dot = FALSE;
+ for (; *id; id++)
+ {
+ if (g_ascii_isalnum (*id) || (*id == '-') || (*id == '_'))
+ allow_dot = TRUE;
+ else if (allow_dot && *id == '.')
+ allow_dot = FALSE;
+ else
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static gpointer
+init_appid_statics (gpointer data)
+{
+ instances_for_appid = g_hash_table_new (g_str_hash, g_str_equal);
+ return NULL;
+}
+
+static GApplication *
+application_for_appid (const char *appid)
+{
+ static GOnce appid_once = G_ONCE_INIT;
+
+ g_once (&appid_once, init_appid_statics, NULL);
+
+ return g_hash_table_lookup (instances_for_appid, appid);
+}
+
+static gboolean
+g_application_default_quit (GApplication *application,
+ guint timestamp)
+{
+ g_return_val_if_fail (application->priv->mainloop != NULL, FALSE);
+ g_main_loop_quit (application->priv->mainloop);
+
+ return TRUE;
+}
+
+static void
+g_application_default_run (GApplication *application)
+{
+ if (application->priv->mainloop == NULL)
+ application->priv->mainloop = g_main_loop_new (NULL, TRUE);
+
+ g_main_loop_run (application->priv->mainloop);
+}
+
+static void
+_g_application_handle_activation (GApplication *app,
+ int argc,
+ char **argv,
+ GVariant *platform_data)
+{
+ GVariantBuilder builder;
+ GVariant *message;
+ int i;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("(aaya{sv})"));
+ g_variant_builder_open (&builder, G_VARIANT_TYPE ("aay"));
+
+ for (i = 1; i < argc; i++)
+ {
+ int j;
+ guint8 *argv_bytes;
+
+ g_variant_builder_open (&builder, G_VARIANT_TYPE ("ay"));
+
+ argv_bytes = (guint8*) argv[i];
+ for (j = 0; argv_bytes[j]; j++)
+ g_variant_builder_add_value (&builder,
+ g_variant_new_byte (argv_bytes[j]));
+ g_variant_builder_close (&builder);
+ }
+ g_variant_builder_close (&builder);
+
+ if (platform_data)
+ g_variant_builder_add (&builder, "@a{sv}", platform_data);
+ else
+ {
+ g_variant_builder_open (&builder, G_VARIANT_TYPE ("a{sv}"));
+ g_variant_builder_close (&builder);
+ }
+
+ message = g_variant_builder_end (&builder);
+ _g_application_platform_activate (app, message);
+ g_variant_unref (message);
+}
+
+static gboolean
+timeout_handle_actions_changed (gpointer user_data)
+{
+ GApplication *application = user_data;
+
+ application->priv->actions_changed_id = 0;
+
+ _g_application_platform_on_actions_changed (application);
+
+ return FALSE;
+}
+
+static inline void
+queue_actions_change_notification (GApplication *application)
+{
+ GApplicationPrivate *priv = application->priv;
+
+ if (priv->actions_changed_id == 0)
+ priv->actions_changed_id = g_timeout_add (0, timeout_handle_actions_changed, application);
+}
+
+static void
+g_application_action_free (gpointer data)
+{
+ if (G_LIKELY (data != NULL))
+ {
+ GApplicationAction *action = data;
+
+ g_free (action->name);
+ g_free (action->description);
+
+ g_slice_free (GApplicationAction, action);
+ }
+}
+
+
+/**
+ * g_application_new:
+ * @appid: System-dependent application identifier
+ *
+ * Create a new #GApplication. The application is initially in
+ * "remote" mode. Almost certainly, you want to call
+ * g_application_register() immediately after this function, which
+ * will finish initialization.
+ *
+ * As a convenience, this function is defined to call g_type_init() as
+ * its very first action.
+ *
+ * Returns: (transfer full): An application instance
+ *
+ * Since: 2.26
+ */
+GApplication *
+g_application_new (const char *appid)
+{
+ g_type_init ();
+
+ return G_APPLICATION (g_object_new (G_TYPE_APPLICATION, "appid", appid, NULL));
+}
+
+/**
+ * g_application_register:
+ * @application: A #GApplication
+ * @argc: System argument count
+ * @argv: (array length=argc): System argument vector
+ * @platform_data: (allow-none): Arbitrary platform-specific data, must have signature "a{sv}"
+ *
+ * Ensure the current process is the unique owner of the application.
+ * If successful, the #GApplication:is-remote property will be changed
+ * to %FALSE, and it is safe to continue creating other resources
+ * such as graphics windows.
+ *
+ * If the given @appid is already running in another process, the
+ * #GApplication:default-exit property will be evaluated. If it's
+ * %TRUE, then a platform-specific action such as bringing any
+ * graphics windows associated with the application to the foreground
+ * may be initiated. After that, the current process will terminate.
+ * If %FALSE, then the application remains in the #GApplication:is-remote
+ * state, and you can e.g. call g_application_invoke_action().
+ */
+void
+g_application_register_with_data (GApplication *application,
+ int argc,
+ char **argv,
+ GVariant *platform_data)
+{
+ g_return_if_fail (application->priv->appid != NULL);
+ g_return_if_fail (application->priv->is_remote);
+ g_return_if_fail (platform_data == NULL
+ || strcmp (g_variant_get_type_string (platform_data), "a{sv}") == 0);
+
+ if (!_g_application_platform_acquire_single_instance (application, NULL))
+ {
+ if (application->priv->default_quit)
+ _g_application_handle_activation (application, argc, argv, platform_data);
+ else
+ return;
+ }
+
+ application->priv->is_remote = FALSE;
+
+ _g_application_platform_init (application);
+}
+
+/**
+ * g_application_new_and_register:
+ * @appid: An application identifier
+ * @argc: System argument count
+ * @argv: (array length=argc): System argument vector
+ *
+ * This is a convenience function which combines g_application_new()
+ * with g_application_register_with_data().
+ */
+GApplication *
+g_application_new_and_register (const char *appid,
+ int argc,
+ char **argv)
+{
+ GApplication *app = g_application_new (appid);
+ g_application_register_with_data (app, argc, argv, NULL);
+ return app;
+}
+
+/**
+ * g_application_add_action:
+ * @application: a #GApplication
+ * @name: the action name
+ * @description: the action description; can be a translatable
+ * string
+ *
+ * Adds an action @name to the list of exported actions of @application.
+ *
+ * It is an error to call this function if @application is a proxy for
+ * a remote application.
+ *
+ * You can invoke an action using g_application_invoke_action().
+ *
+ * The newly added action is enabled by default; you can call
+ * g_application_set_action_enabled() to disable it.
+ *
+ * Since: 2.26
+ */
+void
+g_application_add_action (GApplication *application,
+ const gchar *name,
+ const gchar *description)
+{
+ GApplicationPrivate *priv;
+ GApplicationAction *action;
+
+ g_return_if_fail (G_IS_APPLICATION (application));
+ g_return_if_fail (name != NULL && *name != '\0');
+ g_return_if_fail (!application->priv->is_remote);
+
+ priv = application->priv;
+
+ g_return_if_fail (g_hash_table_lookup (priv->actions, name) == NULL);
+
+ action = g_slice_new (GApplicationAction);
+ action->name = g_strdup (name);
+ action->description = g_strdup (description);
+ action->enabled = TRUE;
+
+ g_hash_table_insert (priv->actions, action->name, action);
+ queue_actions_change_notification (application);
+}
+
+/**
+ * g_application_remove_action:
+ * @application: a #GApplication
+ * @name: the name of the action to remove
+ *
+ * Removes the action @name from the list of exported actions of @application.
+ *
+ * It is an error to call this function if @application is a proxy for
+ * a remote application.
+ *
+ * Since: 2.26
+ */
+void
+g_application_remove_action (GApplication *application,
+ const gchar *name)
+{
+ GApplicationPrivate *priv;
+
+ g_return_if_fail (G_IS_APPLICATION (application));
+ g_return_if_fail (name != NULL && *name != '\0');
+ g_return_if_fail (!application->priv->is_remote);
+
+ priv = application->priv;
+
+ g_return_if_fail (g_hash_table_lookup (priv->actions, name) != NULL);
+
+ g_hash_table_remove (priv->actions, name);
+ queue_actions_change_notification (application);
+}
+
+/**
+ * g_application_invoke_action:
+ * @application: a #GApplication
+ * @name: the name of the action to invoke
+ * @timestamp: the timestamp that is going to be passed to
+ * the #GApplication::action signal
+ *
+ * Invokes the action @name of the passed #GApplication.
+ *
+ * This function has different behavior depending on whether @application
+ * is acting as a proxy for another process. In the normal case where
+ * the current process is hosting the application, and the specified
+ * action exists and is enabled, the #GApplication::action signal will
+ * be emitted.
+ *
+ * If @application is a proxy, then the specified action will be invoked
+ * in the remote process. It is not necessary to call
+ * g_application_add_action() in the current process in order to invoke
+ * one remotely.
+ *
+ * Since: 2.26
+ */
+void
+g_application_invoke_action (GApplication *application,
+ const char *name,
+ guint timestamp)
+{
+ GApplicationPrivate *priv;
+ GApplicationAction *action;
+
+ g_return_if_fail (G_IS_APPLICATION (application));
+ g_return_if_fail (name != NULL);
+
+ priv = application->priv;
+
+ if (priv->is_remote)
+ {
+ _g_application_platform_remote_invoke_action (application, name, timestamp);
+ return;
+ }
+
+ action = g_hash_table_lookup (priv->actions, name);
+ g_return_if_fail (action != NULL);
+ if (!action->enabled)
+ return;
+
+ g_signal_emit (application, application_signals[ACTION],
+ g_quark_from_string (name),
+ name,
+ timestamp);
+}
+
+/**
+ * g_application_list_actions:
+ * @application: a #GApplication
+ *
+ * Retrieves the list of action names currently exported by @application.
+ *
+ * It is an error to call this function if @application is a proxy for
+ * a remote application.
+ *
+ * Return value: (transfer full): a newly allocation, %NULL-terminated array
+ * of strings containing action names; use g_strfreev() to free the
+ * resources used by the returned array
+ *
+ * Since: 2.26
+ */
+gchar **
+g_application_list_actions (GApplication *application)
+{
+ GApplicationPrivate *priv;
+ GHashTableIter iter;
+ gpointer key;
+ gchar **retval;
+ gint i;
+
+ g_return_val_if_fail (G_IS_APPLICATION (application), NULL);
+ g_return_val_if_fail (!application->priv->is_remote, NULL);
+
+ priv = application->priv;
+
+ retval = g_new (gchar*, g_hash_table_size (priv->actions));
+
+ i = 0;
+ g_hash_table_iter_init (&iter, priv->actions);
+ while (g_hash_table_iter_next (&iter, &key, NULL))
+ retval[i++] = g_strdup (key);
+
+ retval[i] = NULL;
+
+ return retval;
+}
+
+/**
+ * g_application_set_action_enabled:
+ * @application: a #GApplication
+ * @name: the name of the application
+ * @enabled: whether to enable or disable the action @name
+ *
+ * Sets whether the action @name inside @application should be enabled
+ * or disabled.
+ *
+ * It is an error to call this function if @application is a proxy for
+ * a remote application.
+ *
+ * Invoking a disabled action will not result in the #GApplication::action
+ * signal being emitted.
+ *
+ * Since: 2.26
+ */
+void
+g_application_set_action_enabled (GApplication *application,
+ const gchar *name,
+ gboolean enabled)
+{
+ GApplicationAction *action;
+
+ g_return_if_fail (G_IS_APPLICATION (application));
+ g_return_if_fail (name != NULL);
+ g_return_if_fail (!application->priv->is_remote);
+
+ enabled = !!enabled;
+
+ action = g_hash_table_lookup (application->priv->actions, name);
+ g_return_if_fail (action != NULL);
+ if (action->enabled == enabled)
+ return;
+
+ action->enabled = enabled;
+
+ queue_actions_change_notification (application);
+}
+
+
+/**
+ * g_application_get_action_description:
+ * @application: a #GApplication
+ * @name: Action name
+ *
+ * Gets the description of the action @name.
+ *
+ * It is an error to call this function if @application is a proxy for
+ * a remote application.
+ *
+ * Returns: Description for the given action named @name
+ *
+ * Since: 2.26
+ */
+G_CONST_RETURN gchar *
+g_application_get_action_description (GApplication *application,
+ const gchar *name)
+{
+ GApplicationAction *action;
+
+ g_return_val_if_fail (G_IS_APPLICATION (application), NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+ g_return_val_if_fail (!application->priv->is_remote, NULL);
+
+ action = g_hash_table_lookup (application->priv->actions, name);
+ g_return_val_if_fail (action != NULL, NULL);
+
+ return action->description;
+}
+
+
+/**
+ * g_application_get_action_enabled:
+ * @application: a #GApplication
+ * @name: the name of the action
+ *
+ * Retrieves whether the action @name is enabled or not.
+ *
+ * See g_application_set_action_enabled().
+ *
+ * It is an error to call this function if @application is a proxy for
+ * a remote application.
+ *
+ * Return value: %TRUE if the action was enabled, and %FALSE otherwise
+ *
+ * Since: 2.26
+ */
+gboolean
+g_application_get_action_enabled (GApplication *application,
+ const gchar *name)
+{
+ GApplicationAction *action;
+
+ g_return_val_if_fail (G_IS_APPLICATION (application), FALSE);
+ g_return_val_if_fail (name != NULL, FALSE);
+ g_return_val_if_fail (!application->priv->is_remote, FALSE);
+
+ action = g_hash_table_lookup (application->priv->actions, name);
+ g_return_val_if_fail (action != NULL, FALSE);
+
+ return action->enabled;
+}
+
+/**
+ * g_application_run:
+ * @application: a #GApplication
+ *
+ * Starts the application.
+ *
+ * The default implementation of this virtual function will simply run
+ * a main loop.
+ *
+ * It is an error to call this function if @application is a proxy for
+ * a remote application.
+ *
+ * Since: 2.26
+ */
+void
+g_application_run (GApplication *application)
+{
+ g_return_if_fail (G_IS_APPLICATION (application));
+ g_return_if_fail (!application->priv->is_remote);
+
+ G_APPLICATION_GET_CLASS (application)->run (application);
+}
+
+/**
+ * g_application_quit:
+ * @application: a #GApplication
+ * @timestamp: Platform-specific event timestamp, may be 0 for default
+ *
+ * Request that the application quits.
+ *
+ * This function has different behavior depending on whether @application
+ * is acting as a proxy for another process. In the normal case where
+ * the current process is hosting the application, the default
+ * implementation will quit the main loop created by g_application_run().
+ *
+ * If @application is a proxy, then the remote process will be asked
+ * to quit.
+ *
+ * Returns: %TRUE if the application accepted the request, %FALSE otherwise
+ *
+ * Since: 2.26
+ */
+gboolean
+g_application_quit (GApplication *application,
+ guint timestamp)
+{
+ gboolean retval = FALSE;
+
+ g_return_val_if_fail (G_IS_APPLICATION (application), FALSE);
+
+ if (application->priv->is_remote)
+ {
+ _g_application_platform_remote_quit (application, timestamp);
+ retval = TRUE;
+ }
+ else
+ g_signal_emit (application, application_signals[QUIT], 0, timestamp, &retval);
+
+ return retval;
+}
+
+/**
+ * g_application_get_instance:
+ *
+ * In the normal case where there is exactly one #GApplication instance
+ * in this process, return that instance. If there are multiple, the
+ * first one created will be returned. Otherwise, return %NULL.
+ *
+ * Returns: (transfer none): The primary instance of #GApplication,
+ * or %NULL if none is set
+ *
+ * Since: 2.26
+ */
+GApplication *
+g_application_get_instance (void)
+{
+ return primary_application;
+}
+
+/**
+ * g_application_get_id:
+ *
+ * Retrieves the platform-specific identifier for the #GApplication.
+ *
+ * Return value: The platform-specific identifier. The returned string
+ * is owned by the #GApplication instance and it should never be
+ * modified or freed
+ *
+ * Since: 2.26
+ */
+G_CONST_RETURN char *
+g_application_get_id (GApplication *application)
+{
+ g_return_val_if_fail (G_IS_APPLICATION (application), NULL);
+
+ return application->priv->appid;
+}
+
+/**
+ * g_application_is_remote:
+ * @application: a #GApplication
+ *
+ * Returns: %TRUE if this object represents a proxy for a remote application.
+ */
+gboolean
+g_application_is_remote (GApplication *application)
+{
+ g_return_val_if_fail (G_IS_APPLICATION (application), FALSE);
+
+ return application->priv->is_remote;
+}
+
+static void
+g_application_init (GApplication *app)
+{
+ app->priv = G_TYPE_INSTANCE_GET_PRIVATE (app,
+ G_TYPE_APPLICATION,
+ GApplicationPrivate);
+
+ app->priv->actions = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL,
+ g_application_action_free);
+ app->priv->default_quit = TRUE;
+ app->priv->is_remote = TRUE;
+}
+
+static void
+g_application_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GApplication *app = G_APPLICATION (object);
+
+ switch (prop_id)
+ {
+ case PROP_APPID:
+ g_value_set_string (value, g_application_get_id (app));
+ break;
+
+ case PROP_DEFAULT_QUIT:
+ g_value_set_boolean (value, app->priv->default_quit);
+ break;
+
+ case PROP_IS_REMOTE:
+ g_value_set_boolean (value, g_application_is_remote (app));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+g_application_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GApplication *app = G_APPLICATION (object);
+
+ switch (prop_id)
+ {
+ case PROP_APPID:
+ g_return_if_fail (_g_application_validate_id (g_value_get_string (value)));
+ app->priv->appid = g_value_dup_string (value);
+ break;
+
+ case PROP_DEFAULT_QUIT:
+ app->priv->default_quit = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static GObject*
+g_application_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_params)
+{
+ GApplication *app;
+ GObject *object;
+ const char *appid = NULL;
+ guint i;
+
+ for (i = 0; i < n_construct_properties; i++)
+ {
+ GObjectConstructParam *param = &construct_params[i];
+ if (strcmp (param->pspec->name, "appid") == 0)
+ appid = g_value_get_string (param->value);
+ }
+
+ g_return_val_if_fail (appid != NULL, NULL);
+
+ app = application_for_appid (appid);
+ if (app != NULL)
+ return g_object_ref (app);
+
+ object = (* G_OBJECT_CLASS (g_application_parent_class)->constructor) (type,
+ n_construct_properties,
+ construct_params);
+ app = G_APPLICATION (object);
+
+ if (primary_application == NULL)
+ primary_application = app;
+ g_hash_table_insert (instances_for_appid, g_strdup (appid), app);
+
+ return object;
+}
+
+static void
+g_application_finalize (GObject *object)
+{
+ GApplication *app = G_APPLICATION (object);
+
+ g_free (app->priv->appid);
+ if (app->priv->actions)
+ g_hash_table_unref (app->priv->actions);
+ if (app->priv->actions_changed_id)
+ g_source_remove (app->priv->actions_changed_id);
+ if (app->priv->mainloop)
+ g_main_loop_unref (app->priv->mainloop);
+
+#ifdef G_OS_UNIX
+ g_free (app->priv->dbus_path);
+ if (app->priv->session_bus)
+ g_object_unref (app->priv->session_bus);
+#endif
+
+ G_OBJECT_CLASS (g_application_parent_class)->finalize (object);
+}
+
+static void
+g_application_class_init (GApplicationClass *klass)
+{
+ GObjectClass *gobject_class G_GNUC_UNUSED = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (GApplicationPrivate));
+
+ gobject_class->constructor = g_application_constructor;
+ gobject_class->set_property = g_application_set_property;
+ gobject_class->get_property = g_application_get_property;
+
+ gobject_class->finalize = g_application_finalize;
+
+ klass->run = g_application_default_run;
+ klass->quit = g_application_default_quit;
+
+ application_signals[QUIT] =
+ g_signal_new (g_intern_static_string ("quit"),
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GApplicationClass, quit),
+ g_signal_accumulator_true_handled, NULL,
+ _gio_marshal_BOOLEAN__UINT,
+ G_TYPE_BOOLEAN, 1,
+ G_TYPE_UINT);
+
+ application_signals[ACTION] =
+ g_signal_new (g_intern_static_string ("action"),
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_DETAILED,
+ G_STRUCT_OFFSET (GApplicationClass, action),
+ NULL, NULL,
+ _gio_marshal_VOID__STRING_INT,
+ G_TYPE_NONE, 2,
+ G_TYPE_STRING,
+ G_TYPE_INT);
+
+ /**
+ * GApplication::prepare-activation:
+ * @arguments: A #GVariant with the signature "aay"
+ * @platform_data: A #GVariant with the signature "a{sv}"
+ *
+ * This signal is emitted when a non-primary process for a given
+ * application is invoked while your application is running; for
+ * example, when a file browser launches your program to open a
+ * file. The raw operating system arguments are passed in the
+ * @arguments variant. Additional platform-dependent data is
+ * stored in @platform_data.
+ */
+
+ application_signals[PREPARE_ACTIVATION] =
+ g_signal_new (g_intern_static_string ("prepare-activation"),
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GApplicationClass, prepare_activation),
+ NULL, NULL,
+ _gio_marshal_VOID__BOXED_BOXED,
+ G_TYPE_NONE, 2,
+ G_TYPE_VARIANT,
+ G_TYPE_VARIANT);
+
+ /**
+ * GApplication:appid:
+ *
+ * The unique identifier for this application. See the documentation for
+ * #GApplication for more information about this property.
+ *
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_APPID,
+ g_param_spec_string ("appid",
+ P_("Application ID"),
+ P_("Identifier for this application"),
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GApplication:default-quit:
+ *
+ * By default, if a different process is running this application, the
+ * process will be exited. Set this property to %FALSE to allow custom
+ * interaction with the remote process.
+ *
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_DEFAULT_QUIT,
+ g_param_spec_boolean ("default-quit",
+ P_("Default Quit"),
+ P_("Exit the process by default"),
+ TRUE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+
+ /**
+ * GApplication:is-remote:
+ *
+ * This property is %TRUE if this application instance represents a proxy
+ * to the instance of this application in another process.
+ *
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_IS_REMOTE,
+ g_param_spec_boolean ("is-remote",
+ P_("Is Remote"),
+ P_("Whether this application is a proxy for another process"),
+ TRUE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+}
+
+#define __G_APPLICATION_C__
+#include "gioaliasdef.c"
diff --git a/gio/gapplication.h b/gio/gapplication.h
new file mode 100644
index 000000000..bb70fe1fa
--- /dev/null
+++ b/gio/gapplication.h
@@ -0,0 +1,151 @@
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright © 2010 Red Hat, Inc
+ *
+ * This program 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.
+ *
+ * Authors: Colin Walters <walters@verbum.org>
+ * Emmanuele Bassi <ebassi@linux.intel.com>
+ */
+
+#if !defined (__GIO_GIO_H_INSIDE__) && !defined (GIO_COMPILATION)
+#error "Only <gio/gio.h> can be included directly."
+#endif
+
+#ifndef __G_APPLICATION_H__
+#define __G_APPLICATION_H__
+
+#include <glib-object.h>
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_APPLICATION (g_application_get_type ())
+#define G_APPLICATION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_APPLICATION, GApplication))
+#define G_APPLICATION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), G_TYPE_APPLICATION, GApplicationClass))
+#define G_IS_APPLICATION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), G_TYPE_APPLICATION))
+#define G_IS_APPLICATION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), G_TYPE_APPLICATION))
+#define G_APPLICATION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), G_TYPE_APPLICATION, GApplicationClass))
+
+typedef struct _GApplication GApplication;
+typedef struct _GApplicationPrivate GApplicationPrivate;
+typedef struct _GApplicationClass GApplicationClass;
+
+/**
+ * GApplication:
+ *
+ * The <structname>GApplication</structname> structure contains private
+ * data and should only be accessed using the provided API
+ *
+ * Since: 2.26
+ */
+struct _GApplication
+{
+ /*< private >*/
+ GObject parent_instance;
+
+ GApplicationPrivate *priv;
+};
+
+/**
+ * GApplicationClass:
+ * @action: class handler for the #GApplication::action signal
+ * @quit: class handler for the #GApplication::quit signal
+ * @prepare_activation: class handler for the #GApplication::prepare-activation signal
+ * @run: virtual function, called by g_application_run()
+ * @format_activation_data: virtual function, called by g_application_format_activation_data()
+ *
+ * The <structname>GApplicationClass</structname> structure contains
+ * private data only
+ *
+ * Since: 2.26
+ */
+struct _GApplicationClass
+{
+ /*< private >*/
+ GObjectClass parent_class;
+
+ /*< public >*/
+ /* signals */
+ void (* action) (GApplication *application,
+ const gchar *action_name,
+ guint timestamp);
+ gboolean (* quit) (GApplication *application,
+ guint timestamp);
+ void (* prepare_activation) (GApplication *application,
+ GVariant *arguments,
+ GVariant *platform_data);
+
+ /* vfuncs */
+ void (* run) (GApplication *application);
+ void (*format_activation_data) (GApplication *application,
+ GVariantBuilder *builder);
+
+ /*< private >*/
+ /* Padding for future expansion */
+ void (*_g_reserved1) (void);
+ void (*_g_reserved2) (void);
+ void (*_g_reserved3) (void);
+ void (*_g_reserved4) (void);
+ void (*_g_reserved5) (void);
+ void (*_g_reserved6) (void);
+};
+GType g_application_get_type (void) G_GNUC_CONST;
+
+GApplication * g_application_new (const char *appid);
+
+void g_application_register_with_data (GApplication *application,
+ int argc,
+ char **argv,
+ GVariant *platform_data);
+
+GApplication * g_application_new_and_register (const char *appid,
+ int argc,
+ char **argv);
+
+GApplication * g_application_get_instance (void);
+G_CONST_RETURN gchar * g_application_get_id (GApplication *application);
+
+void g_application_add_action (GApplication *application,
+ const char *name,
+ const char *description);
+void g_application_remove_action (GApplication *application,
+ const char *name);
+gchar ** g_application_list_actions (GApplication *application);
+void g_application_set_action_enabled (GApplication *application,
+ const char *name,
+ gboolean enabled);
+gboolean g_application_get_action_enabled (GApplication *application,
+ const char *name);
+G_CONST_RETURN gchar * g_application_get_action_description (GApplication *application,
+ const char *name);
+void g_application_invoke_action (GApplication *application,
+ const char *name,
+ guint timestamp);
+
+void g_application_run (GApplication *application);
+gboolean g_application_quit (GApplication *app,
+ guint timestamp);
+
+gboolean g_application_is_remote (GApplication *application);
+
+void g_application_format_activation_data (GApplication *app,
+ GVariantBuilder *builder);
+
+
+G_END_DECLS
+
+#endif /* __G_APPLICATION_H__ */
diff --git a/gio/gdbusapplication.c b/gio/gdbusapplication.c
new file mode 100644
index 000000000..012845db7
--- /dev/null
+++ b/gio/gdbusapplication.c
@@ -0,0 +1,426 @@
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright © 2010 Red Hat, Inc
+ *
+ * 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 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Colin Walters <walters@verbum.org>
+ */
+
+#define G_APPLICATION_IFACE "org.gtk.Application"
+
+static void
+application_dbus_method_call (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ GApplication *app = G_APPLICATION (user_data);
+
+ if (method_name == NULL && *method_name == '\0')
+ return;
+
+ if (strcmp (method_name, "Quit") == 0)
+ {
+ guint32 timestamp;
+ g_variant_get (parameters, "(u)", &timestamp);
+
+ g_dbus_method_invocation_return_value (invocation, NULL);
+
+ g_application_quit (app, timestamp);
+ }
+ else if (strcmp (method_name, "ListActions") == 0)
+ {
+ GHashTableIter iter;
+ GApplicationAction *value;
+ GVariantBuilder builder;
+ GVariant *return_args;
+ GVariant *result;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
+ g_hash_table_iter_init (&iter, app->priv->actions);
+ while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&value))
+ g_variant_builder_add (&builder, "{s(sb)}",
+ value->name,
+ value->description ? value->description : "",
+ value->enabled);
+
+ result = g_variant_builder_end (&builder);
+ return_args = g_variant_new_tuple (&result, 1);
+ g_dbus_method_invocation_return_value (invocation, return_args);
+ g_variant_unref (return_args);
+ g_variant_unref (result);
+ }
+ else if (strcmp (method_name, "InvokeAction") == 0)
+ {
+ const char *action_name;
+ guint32 timestamp;
+ GApplicationAction *action;
+
+ g_variant_get (parameters, "(su)", &action_name, &timestamp);
+
+ action = g_hash_table_lookup (app->priv->actions, action_name);
+
+ if (!action)
+ {
+ char *errmsg = g_strdup_printf ("Invalid action: %s", action_name);
+ g_dbus_method_invocation_return_dbus_error (invocation, G_APPLICATION_IFACE ".InvalidAction", errmsg);
+ g_free (errmsg);
+ return;
+ }
+
+ g_signal_emit (app, application_signals[ACTION], g_quark_from_string (action_name), action_name, (guint)timestamp);
+
+ g_dbus_method_invocation_return_value (invocation, NULL);
+ }
+ else if (strcmp (method_name, "Activate") == 0)
+ {
+ GVariant *args;
+ GVariant *platform_data;
+
+ g_variant_get (parameters, "(@aay@a{sv})", &args, &platform_data);
+
+ g_signal_emit (app, application_signals[PREPARE_ACTIVATION], 0, args, platform_data);
+
+ g_variant_unref (args);
+ g_variant_unref (platform_data);
+
+ g_dbus_method_invocation_return_value (invocation, NULL);
+ }
+}
+
+static const GDBusArgInfo application_quit_in_args[] =
+{
+ {
+ -1,
+ "timestamp",
+ "u",
+ NULL
+ }
+};
+
+static const GDBusArgInfo * const application_quit_in_args_p[] = {
+ &application_quit_in_args[0],
+ NULL
+};
+
+static const GDBusArgInfo application_list_actions_out_args[] =
+{
+ {
+ -1,
+ "actions",
+ "a{s(sb)}",
+ NULL
+ }
+};
+
+static const GDBusArgInfo * const application_list_actions_out_args_p[] = {
+ &application_list_actions_out_args[0],
+ NULL
+};
+
+static const GDBusArgInfo application_invoke_action_in_args[] =
+{
+ {
+ -1,
+ "action",
+ "s",
+ NULL
+ },
+ {
+ -1,
+ "timestamp",
+ "u",
+ NULL
+ }
+};
+
+static const GDBusArgInfo * const application_invoke_action_in_args_p[] = {
+ &application_invoke_action_in_args[0],
+ &application_invoke_action_in_args[1],
+ NULL
+};
+
+static const GDBusMethodInfo application_quit_method_info =
+{
+ -1,
+ "Quit",
+ (GDBusArgInfo **) &application_quit_in_args_p,
+ NULL,
+ NULL
+};
+
+static const GDBusMethodInfo application_list_actions_method_info =
+{
+ -1,
+ "ListActions",
+ NULL,
+ (GDBusArgInfo **) &application_list_actions_out_args_p,
+ NULL
+};
+
+static const GDBusMethodInfo application_invoke_action_method_info =
+{
+ -1,
+ "InvokeAction",
+ (GDBusArgInfo **) &application_invoke_action_in_args_p,
+ NULL,
+ NULL
+};
+
+static const GDBusArgInfo application_activate_in_args[] =
+{
+ {
+ -1,
+ "arguments",
+ "aay",
+ NULL
+ },
+ {
+ -1,
+ "data",
+ "a{sv}",
+ NULL
+ }
+};
+
+static const GDBusArgInfo * const application_activate_in_args_p[] = {
+ &application_activate_in_args[0],
+ &application_activate_in_args[1],
+ NULL
+};
+
+static const GDBusMethodInfo application_activate_method_info =
+{
+ -1,
+ "Activate",
+ (GDBusArgInfo **) &application_activate_in_args_p,
+ NULL,
+ NULL
+};
+
+static const GDBusMethodInfo * const application_dbus_method_info_p[] =
+{
+ &application_quit_method_info,
+ &application_list_actions_method_info,
+ &application_invoke_action_method_info,
+ &application_activate_method_info,
+ NULL
+};
+
+static const GDBusSignalInfo application_dbus_signal_info[] =
+{
+ {
+ -1,
+ "ActionsChanged",
+ NULL,
+ NULL
+ }
+};
+
+static const GDBusSignalInfo * const application_dbus_signal_info_p[] = {
+ &application_dbus_signal_info[0],
+ NULL
+};
+
+static const GDBusInterfaceInfo application_dbus_interface_info =
+{
+ -1,
+ G_APPLICATION_IFACE,
+ (GDBusMethodInfo **) application_dbus_method_info_p,
+ (GDBusSignalInfo **) application_dbus_signal_info_p,
+ NULL,
+};
+
+static GDBusInterfaceVTable application_dbus_vtable =
+{
+ application_dbus_method_call,
+ NULL,
+ NULL
+};
+
+static char *
+application_path_from_appid (const char *appid)
+{
+ char *appid_path, *iter;
+
+
+ appid_path = g_strconcat ("/", appid, NULL);
+ for (iter = appid_path; *iter; iter++)
+ {
+ if (*iter == '.')
+ *iter = '/';
+ }
+
+ return appid_path;
+}
+
+static void
+ensure_bus (GApplication *app)
+{
+ GError *error = NULL;
+
+ if (app->priv->session_bus == NULL)
+ app->priv->session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+ if (app->priv->session_bus == NULL)
+ {
+ g_error ("%s", error->message);
+ g_error_free (error);
+ }
+
+ if (app->priv->dbus_path == NULL)
+ app->priv->dbus_path = application_path_from_appid (app->priv->appid);
+}
+
+static void
+_g_application_platform_init (GApplication *app)
+{
+ GError *error = NULL;
+ guint registration_id;
+
+ ensure_bus (app);
+
+ registration_id = g_dbus_connection_register_object (app->priv->session_bus,
+ app->priv->dbus_path,
+ &application_dbus_interface_info,
+ &application_dbus_vtable,
+ app, NULL,
+ &error);
+ if (registration_id == 0)
+ {
+ g_error ("%s", error->message);
+ g_error_free (error);
+ }
+}
+
+static gboolean
+_g_application_platform_acquire_single_instance (GApplication *app,
+ GError **error)
+{
+ GVariant *request_result;
+ guint32 request_status;
+
+ ensure_bus (app);
+ if (app->priv->session_bus == NULL)
+ return FALSE;
+
+ request_result = g_dbus_connection_call_sync (app->priv->session_bus,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ "RequestName",
+ g_variant_new ("(su)", app->priv->appid, 0x4),
+ NULL, 0, -1, NULL, error);
+
+ if (request_result == NULL)
+ return FALSE;
+
+ if (strcmp (g_variant_get_type_string (request_result), "(u)") == 0)
+ g_variant_get (request_result, "(u)", &request_status);
+ else
+ request_status = 0;
+
+ g_variant_unref (request_result);
+
+ if (request_status != 1 && request_status != 4)
+ {
+ if (request_status == 3)
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Another process has name \"%s\"", app->priv->appid);
+ else
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Unknown error");
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+_g_application_platform_on_actions_changed (GApplication *app)
+{
+ g_dbus_connection_emit_signal (app->priv->session_bus, NULL,
+ app->priv->dbus_path,
+ G_APPLICATION_IFACE,
+ "ActionsChanged", NULL, NULL);
+}
+
+static void
+_g_application_platform_remote_invoke_action (GApplication *app,
+ const char *action,
+ guint timestamp)
+{
+ GVariant *result;
+
+ ensure_bus (app);
+
+ result = g_dbus_connection_call_sync (app->priv->session_bus,
+ app->priv->appid,
+ app->priv->dbus_path,
+ G_APPLICATION_IFACE,
+ "InvokeAction",
+ g_variant_new ("(su)",
+ action,
+ timestamp),
+ NULL, 0, -1, NULL, NULL);
+ if (result)
+ g_variant_unref (result);
+}
+
+static void
+_g_application_platform_remote_quit (GApplication *app,
+ guint timestamp)
+{
+ GVariant *result;
+
+ ensure_bus (app);
+
+ result = g_dbus_connection_call_sync (app->priv->session_bus,
+ app->priv->appid,
+ app->priv->dbus_path,
+ G_APPLICATION_IFACE,
+ "Quit",
+ g_variant_new ("(u)",
+ timestamp),
+ NULL, 0, -1, NULL, NULL);
+ if (result)
+ g_variant_unref (result);
+}
+
+static void
+_g_application_platform_activate (GApplication *app,
+ GVariant *data)
+{
+ GVariant *result;
+
+ ensure_bus (app);
+
+ result = g_dbus_connection_call_sync (app->priv->session_bus,
+ app->priv->appid,
+ app->priv->dbus_path,
+ G_APPLICATION_IFACE,
+ "Activate",
+ data,
+ NULL, 0, -1, NULL, NULL);
+
+ if (result)
+ g_variant_unref (result);
+
+ exit (0);
+}
diff --git a/gio/gdbusconnection.h b/gio/gdbusconnection.h
index 1a9597c13..b203196a2 100644
--- a/gio/gdbusconnection.h
+++ b/gio/gdbusconnection.h
@@ -434,7 +434,7 @@ gboolean g_dbus_connection_unregister_subtree (GDBusConnection
* @connection: A #GDBusConnection.
* @sender_name: The unique bus name of the sender of the signal.
* @object_path: The object path that the signal was emitted on.
- * @interface_name: The name of the signal.
+ * @interface_name: The name of the interface.
* @signal_name: The name of the signal.
* @parameters: A #GVariant tuple with parameters for the signal.
* @user_data: User data passed when subscribing to the signal.
diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c
index ddccf3497..493a3cdc3 100644
--- a/gio/gdesktopappinfo.c
+++ b/gio/gdesktopappinfo.c
@@ -879,6 +879,7 @@ typedef struct
{
char *display;
char *sn_id;
+ char *desktop_file;
} ChildSetupData;
static void
@@ -891,6 +892,16 @@ child_setup (gpointer user_data)
if (data->sn_id)
g_setenv ("DESKTOP_STARTUP_ID", data->sn_id, TRUE);
+
+ if (data->desktop_file)
+ {
+ gchar pid[20];
+
+ g_setenv ("GIO_LAUNCHED_DESKTOP_FILE", data->desktop_file, TRUE);
+
+ g_snprintf (pid, 20, "%d", getpid ());
+ g_setenv ("GIO_LAUNCHED_DESKTOP_FILE_PID", pid, TRUE);
+ }
}
static gboolean
@@ -927,6 +938,7 @@ g_desktop_app_info_launch_uris (GAppInfo *appinfo,
data.display = NULL;
data.sn_id = NULL;
+ data.desktop_file = info->filename;
if (launch_context)
{
diff --git a/gio/gio-marshal.list b/gio/gio-marshal.list
index 7899b9d69..217017393 100644
--- a/gio/gio-marshal.list
+++ b/gio/gio-marshal.list
@@ -9,3 +9,6 @@ BOOL:UINT
VOID:STRING,STRING,BOXED
VOID:BOOL,BOXED
VOID:BOXED,BOXED
+VOID:INT
+VOID:STRING,INT
+VOID:BOXED,BOXED
diff --git a/gio/gio.h b/gio/gio.h
index 3e97e342b..11cb725d5 100644
--- a/gio/gio.h
+++ b/gio/gio.h
@@ -28,6 +28,7 @@
#include <gio/giotypes.h>
#include <gio/gappinfo.h>
+#include <gio/gapplication.h>
#include <gio/gasyncinitable.h>
#include <gio/gasyncresult.h>
#include <gio/gbufferedinputstream.h>
diff --git a/gio/gio.symbols b/gio/gio.symbols
index 55ff916ac..a366e226c 100644
--- a/gio/gio.symbols
+++ b/gio/gio.symbols
@@ -26,6 +26,27 @@ g_vfs_get_local
#endif
#endif
+#if IN_HEADER(__G_APPLICATION_H__)
+#if IN_FILE(__G_APPLICATION_C__)
+g_application_get_type G_GNUC_CONST
+g_application_new
+g_application_new_and_register
+g_application_get_instance
+g_application_get_id
+g_application_set_action_enabled
+g_application_get_action_enabled
+g_application_get_action_description
+g_application_add_action
+g_application_remove_action
+g_application_register_with_data
+g_application_invoke_action
+g_application_list_actions
+g_application_run
+g_application_quit
+g_application_is_remote
+#endif
+#endif
+
#if IN_HEADER(__G_APP_INFO_H__)
#if IN_FILE(__G_APP_INFO_C__)
g_app_info_get_type G_GNUC_CONST
diff --git a/gio/gnullapplication.c b/gio/gnullapplication.c
new file mode 100644
index 000000000..6d255e642
--- /dev/null
+++ b/gio/gnullapplication.c
@@ -0,0 +1,70 @@
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright © 2010 Red Hat, Inc
+ *
+ * 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 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Colin Walters <walters@verbum.org>
+ */
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "gioerror.h"
+
+static void
+_g_application_platform_init (GApplication *app)
+{
+}
+
+static gboolean
+_g_application_platform_acquire_single_instance (GApplication *app,
+ GError **error)
+{
+ return TRUE;
+}
+
+static void
+_g_application_platform_on_actions_changed (GApplication *app)
+{
+}
+
+static void
+_g_application_platform_remote_invoke_action (GApplication *app,
+ const char *action,
+ guint timestamp)
+{
+}
+
+static void
+_g_application_platform_remote_quit (GApplication *app,
+ guint timestamp)
+{
+}
+
+static void
+_g_application_platform_default_quit (void)
+{
+ exit (0);
+}
+
+static void
+_g_application_platform_activate (GApplication *app,
+ GVariant *data)
+{
+ exit (0);
+}
+
diff --git a/gio/tests/Makefile.am b/gio/tests/Makefile.am
index caab30912..8fcdd9cb7 100644
--- a/gio/tests/Makefile.am
+++ b/gio/tests/Makefile.am
@@ -54,6 +54,9 @@ TEST_PROGS += \
gdbus-error \
gdbus-peer \
gdbus-exit-on-close \
+ application \
+ testapps \
+ appinfo \
$(NULL)
endif
@@ -73,6 +76,8 @@ SAMPLE_PROGS = \
gdbus-example-subtree \
gdbus-example-peer \
gdbus-example-proxy-subclass \
+ testapp \
+ appinfo-test \
$(NULL)
@@ -248,6 +253,21 @@ gdbus_example_proxy_subclass_LDADD = $(progs_ldadd)
gdbus_example_export_SOURCES = gdbus-example-export.c
gdbus_example_export_LDADD = $(progs_ldadd)
+application_SOURCES = application.c
+application_LDADD = $(progs_ldadd)
+
+appinfo_SOURCES = appinfo.c
+appinfo_LDADD = $(progs_ldadd)
+
+appinfo_test_SOURCES = appinfo-test.c
+appinfo_test_LDADD = $(progs_ldadd)
+
+testapp_SOURCES = testapp.c
+testapp_LDADD = $(progs_ldadd)
+
+testapps_SOURCES = testapps.c
+testapps_LDADD = $(progs_ldadd)
+
EXTRA_DIST += \
socket-common.c \
org.gtk.test.gschema \
diff --git a/gio/tests/appinfo-test.c b/gio/tests/appinfo-test.c
new file mode 100644
index 000000000..9d6a5ff3c
--- /dev/null
+++ b/gio/tests/appinfo-test.c
@@ -0,0 +1,20 @@
+#include <stdlib.h>
+#include <gio/gio.h>
+
+int
+main (int argc, char *argv[])
+{
+ const gchar *envvar;
+ gint pid_from_env;
+
+ envvar = g_getenv ("GIO_LAUNCHED_DESKTOP_FILE_PID");
+ g_assert (envvar != NULL);
+ pid_from_env = atoi (envvar);
+ g_assert_cmpint (pid_from_env, ==, getpid ());
+
+ envvar = g_getenv ("GIO_LAUNCHED_DESKTOP_FILE");
+ g_assert_cmpstr (envvar, ==, SRCDIR "/appinfo-test.desktop");
+
+ return 0;
+}
+
diff --git a/gio/tests/appinfo-test.desktop b/gio/tests/appinfo-test.desktop
new file mode 100644
index 000000000..becc1c4a6
--- /dev/null
+++ b/gio/tests/appinfo-test.desktop
@@ -0,0 +1,4 @@
+[Desktop Entry]
+Type=Application
+Name=appinfo-test
+Exec=./appinfo-test
diff --git a/gio/tests/appinfo.c b/gio/tests/appinfo.c
new file mode 100644
index 000000000..099068a1b
--- /dev/null
+++ b/gio/tests/appinfo.c
@@ -0,0 +1,25 @@
+#include <gio/gio.h>
+#include <gio/gdesktopappinfo.h>
+
+static void
+test_launch (void)
+{
+ GAppInfo *appinfo;
+
+ appinfo = (GAppInfo*)g_desktop_app_info_new_from_filename (SRCDIR "/appinfo-test.desktop");
+ g_assert (appinfo != NULL);
+
+ g_assert (g_app_info_launch (appinfo, NULL, NULL, NULL));
+}
+
+int
+main (int argc, char *argv[])
+{
+ g_type_init ();
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/appinfo/launch", test_launch);
+
+ return g_test_run ();
+}
+
diff --git a/gio/tests/application.c b/gio/tests/application.c
new file mode 100644
index 000000000..5dba027d7
--- /dev/null
+++ b/gio/tests/application.c
@@ -0,0 +1,134 @@
+#include <stdlib.h>
+#include <gio.h>
+#include <gstdio.h>
+
+enum
+{
+ INVOKE_ACTION,
+ CHECK_ACTION,
+ DISABLE_ACTION,
+ INVOKE_DISABLED_ACTION,
+ CHECK_DISABLED_ACTION,
+ END
+};
+
+static guint timestamp = 0;
+static gint state = -1;
+static gboolean action_invoked = FALSE;
+
+static void
+on_app_action (GApplication *application,
+ const gchar *action_name,
+ guint action_timestamp)
+{
+ if (g_test_verbose ())
+ g_print ("Action '%s' invoked (timestamp: %u, expected: %u)\n",
+ action_name,
+ action_timestamp,
+ timestamp);
+
+ g_assert_cmpstr (action_name, ==, "About");
+ g_assert_cmpint (action_timestamp, ==, timestamp);
+
+ action_invoked = TRUE;
+}
+
+static gboolean
+check_invoke_action (gpointer data)
+{
+ GApplication *application = data;
+
+ if (state == INVOKE_ACTION)
+ {
+ timestamp = (guint) time (NULL);
+
+ if (g_test_verbose ())
+ g_print ("Invoking About...\n");
+
+ g_application_invoke_action (application, "About", timestamp);
+ state = CHECK_ACTION;
+ return TRUE;
+ }
+
+ if (state == CHECK_ACTION)
+ {
+ if (g_test_verbose ())
+ g_print ("Verifying About invocation...\n");
+
+ g_assert (action_invoked);
+ state = DISABLE_ACTION;
+ return TRUE;
+ }
+
+ if (state == DISABLE_ACTION)
+ {
+ if (g_test_verbose ())
+ g_print ("Disabling About...\n");
+
+ g_application_set_action_enabled (application, "About", FALSE);
+ action_invoked = FALSE;
+ state = INVOKE_DISABLED_ACTION;
+ return TRUE;
+ }
+
+ if (state == INVOKE_DISABLED_ACTION)
+ {
+ if (g_test_verbose ())
+ g_print ("Invoking disabled About action...\n");
+
+ g_application_invoke_action (application, "About", (guint) time (NULL));
+ state = CHECK_DISABLED_ACTION;
+ return TRUE;
+ }
+
+ if (state == CHECK_DISABLED_ACTION)
+ {
+ if (g_test_verbose ())
+ g_print ("Verifying lack of About invocation...\n");
+
+ g_assert (!action_invoked);
+ state = END;
+ return TRUE;
+ }
+
+ if (state == END)
+ {
+ if (g_test_verbose ())
+ g_print ("Test complete\n");
+
+ g_application_quit (application, (guint) time (NULL));
+ return FALSE;
+ }
+
+ g_assert_not_reached ();
+}
+
+static void
+test_basic (void)
+{
+ GApplication *app;
+
+ app = g_application_new_and_register ("org.gtk.TestApplication", 0, NULL);
+ g_application_add_action (app, "About", "Print an about message");
+
+ g_signal_connect (app, "action::About", G_CALLBACK (on_app_action), NULL);
+
+ state = INVOKE_ACTION;
+ g_timeout_add (100, check_invoke_action, app);
+
+ g_application_run (app);
+
+ g_assert (state == END);
+ g_object_unref (app);
+}
+
+int
+main (int argc, char *argv[])
+{
+ g_type_init ();
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/application/basic", test_basic);
+
+ return g_test_run ();
+}
diff --git a/gio/tests/testapp.c b/gio/tests/testapp.c
new file mode 100644
index 000000000..8ed309185
--- /dev/null
+++ b/gio/tests/testapp.c
@@ -0,0 +1,79 @@
+#include <stdlib.h>
+#include <gio.h>
+#include <gstdio.h>
+#include <string.h>
+
+static gboolean action3_added = FALSE;
+
+static void
+on_app_action (GApplication *application,
+ const gchar *action_name,
+ guint action_timestamp)
+{
+ if (strcmp (action_name, "action1") == 0)
+ exit (1);
+ else if (strcmp (action_name, "action2") == 0)
+ {
+ if (action3_added)
+ g_application_remove_action (application, "action3");
+ else
+ g_application_add_action (application, "action3", "An extra action");
+ action3_added = !action3_added;
+ }
+}
+
+static gboolean
+invoke_action1 (gpointer data)
+{
+ GApplication *app = data;
+
+ g_application_invoke_action (app, "action1", 0);
+
+ return FALSE;
+}
+
+static void
+on_app_activated (GApplication *application,
+ GVariant *args,
+ GVariant *platform_data)
+{
+ char *str;
+
+ g_print ("got args: ");
+ str = g_variant_print (args, TRUE);
+ g_print ("%s ", str);
+ g_free (str);
+ str = g_variant_print (platform_data, TRUE);
+ g_print ("%s\n", str);
+ g_free (str);
+}
+
+int
+main (int argc, char *argv[])
+{
+ GApplication *app;
+ GMainLoop *loop;
+
+ app = g_application_new ("org.gtk.test.app");
+ if (!(argc > 1 && strcmp (argv[1], "--non-unique") == 0))
+ g_application_register_with_data (app, argc, argv, NULL);
+
+ if (g_application_is_remote (app))
+ {
+ g_timeout_add (1000, invoke_action1, app);
+ loop = g_main_loop_new (NULL, FALSE);
+ g_main_loop_run (loop);
+ }
+ else
+ {
+ g_application_add_action (app, "action1", "Action1");
+ g_application_add_action (app, "action2", "Action2");
+ g_signal_connect (app, "action",
+ G_CALLBACK (on_app_action), NULL);
+ g_signal_connect (app, "prepare-activation",
+ G_CALLBACK (on_app_activated), NULL);
+ g_application_run (app);
+ }
+
+ return 0;
+}
diff --git a/gio/tests/testapps.c b/gio/tests/testapps.c
new file mode 100644
index 000000000..51db0b879
--- /dev/null
+++ b/gio/tests/testapps.c
@@ -0,0 +1,533 @@
+#include <gio/gio.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+
+static gint appeared;
+static gint disappeared;
+static gint changed;
+static gboolean died;
+static gboolean timed_out;
+GPid pid;
+
+static void
+name_appeared (GDBusConnection *connection,
+ const gchar *name,
+ const gchar *name_owner,
+ gpointer user_data)
+{
+ GMainLoop *loop = user_data;
+
+ appeared++;
+
+ if (loop)
+ g_main_loop_quit (loop);
+}
+
+static void
+name_disappeared (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ GMainLoop *loop = user_data;
+
+ disappeared++;
+
+ if (loop)
+ g_main_loop_quit (loop);
+}
+
+static gboolean
+start_application (gpointer data)
+{
+ gchar *argv[] = { "./testapp", NULL };
+
+ g_assert (g_spawn_async (NULL, argv, NULL, 0, NULL, NULL, &pid, NULL));
+
+ return FALSE;
+}
+
+static gboolean
+run_application_sync (gpointer data)
+{
+ GMainLoop *loop = data;
+
+ g_assert (g_spawn_command_line_sync ("./testapp", NULL, NULL, NULL, NULL));
+
+ if (loop)
+ g_main_loop_quit (loop);
+
+ return FALSE;
+}
+
+static gboolean
+timeout (gpointer data)
+{
+ GMainLoop *loop = data;
+
+ timed_out = TRUE;
+
+ g_main_loop_quit (loop);
+
+ return TRUE;
+}
+
+/* This test starts an application, checks that its name appears
+ * on the bus, then starts it again and checks that the second
+ * instance exits right away.
+ */
+static void
+test_unique (void)
+{
+ GMainLoop *loop;
+ gint watch;
+ guint id1, id2, id3;
+
+ appeared = 0;
+ timed_out = FALSE;
+
+ loop = g_main_loop_new (NULL, FALSE);
+ id1 = g_timeout_add (5000, timeout, loop);
+
+ watch = g_bus_watch_name (G_BUS_TYPE_SESSION,
+ "org.gtk.test.app",
+ 0,
+ name_appeared,
+ NULL,
+ loop,
+ NULL);
+
+ id2 = g_timeout_add (0, start_application, loop);
+
+ g_main_loop_run (loop);
+
+ g_assert_cmpint (appeared, ==, 1);
+
+ id3 = g_timeout_add (0, run_application_sync, loop);
+
+ g_main_loop_run (loop);
+
+ g_assert_cmpint (appeared, ==, 1);
+ g_assert_cmpint (timed_out, ==, FALSE);
+
+ g_bus_unwatch_name (watch);
+
+ kill (pid, SIGTERM);
+
+ g_main_loop_unref (loop);
+ g_source_remove (id1);
+ g_source_remove (id2);
+ g_source_remove (id3);
+}
+
+static gboolean
+quit_app (gpointer data)
+{
+ GDBusConnection *connection;
+ GVariant *res;
+
+ connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+ res = g_dbus_connection_call_sync (connection,
+ "org.gtk.test.app",
+ "/org/gtk/test/app",
+ "org.gtk.Application",
+ "Quit",
+ g_variant_new ("(u)", 0),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ NULL);
+ if (res)
+ g_variant_unref (res);
+
+ return FALSE;
+}
+
+static void
+child_is_dead (GPid pid,
+ gint status,
+ gpointer data)
+{
+ GMainLoop *loop = data;
+
+ died++;
+
+ g_assert (WIFEXITED (status) && WEXITSTATUS(status) == 0);
+
+ if (loop)
+ g_main_loop_quit (loop);
+}
+
+/* This test start an application, checks that its name appears on
+ * the bus, then calls Quit, and verifies that the name disappears
+ * and the application exits.
+ */
+static void
+test_quit (void)
+{
+ GMainLoop *loop;
+ gint watch;
+ guint id1, id2, id3;
+ gchar *argv[] = { "./testapp", NULL };
+
+ appeared = 0;
+ disappeared = 0;
+ died = FALSE;
+ timed_out = FALSE;
+
+ loop = g_main_loop_new (NULL, FALSE);
+ watch = g_bus_watch_name (G_BUS_TYPE_SESSION,
+ "org.gtk.test.app",
+ 0,
+ name_appeared,
+ name_disappeared,
+ NULL,
+ NULL);
+
+ g_assert (g_spawn_async (NULL, argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid, NULL));
+
+ id1 = g_child_watch_add (pid, child_is_dead, loop);
+
+ id2 = g_timeout_add (500, quit_app, NULL);
+
+ id3 = g_timeout_add (5000, timeout, loop);
+
+ g_main_loop_run (loop);
+ g_assert_cmpint (timed_out, ==, FALSE);
+ g_assert_cmpint (appeared, ==, 1);
+ g_assert_cmpint (disappeared, >=, 1);
+ g_assert_cmpint (died, ==, TRUE);
+
+ g_bus_unwatch_name (watch);
+
+ g_main_loop_unref (loop);
+ g_source_remove (id1);
+ g_source_remove (id2);
+ g_source_remove (id3);
+}
+
+static gboolean
+_g_strv_has_string (const gchar* const * haystack,
+ const gchar *needle)
+{
+ guint n;
+
+ for (n = 0; haystack != NULL && haystack[n] != NULL; n++)
+ {
+ if (g_strcmp0 (haystack[n], needle) == 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static gchar **
+list_actions (void)
+{
+ GDBusConnection *connection;
+ GVariant *res;
+ gchar **strv;
+ gchar *str;
+ GVariantIter *iter;
+ gint i;
+
+ connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+ res = g_dbus_connection_call_sync (connection,
+ "org.gtk.test.app",
+ "/org/gtk/test/app",
+ "org.gtk.Application",
+ "ListActions",
+ NULL,
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ NULL);
+
+ strv = g_new0 (gchar *, 32);
+ g_variant_get (res, "(a{s(sb)})", &iter);
+ i = 0;
+ while (g_variant_iter_loop (iter, "{s(sb)}", &str, NULL, NULL))
+ {
+ strv[i] = g_strdup (str);
+ i++;
+ g_assert (i < 32);
+ }
+ g_variant_iter_free (iter);
+
+ strv[i] = NULL;
+
+ g_variant_unref (res);
+ g_object_unref (connection);
+
+ return strv;
+}
+
+/* This test start an application, waits for its name to appear on
+ * the bus, then calls ListActions, and verifies that it gets the expected
+ * actions back.
+ */
+static void
+test_list_actions (void)
+{
+ GMainLoop *loop;
+ gchar *argv[] = { "./testapp", NULL };
+ gchar **actions;
+ gint watch;
+
+ appeared = 0;
+
+ loop = g_main_loop_new (NULL, FALSE);
+ watch = g_bus_watch_name (G_BUS_TYPE_SESSION,
+ "org.gtk.test.app",
+ 0,
+ name_appeared,
+ NULL,
+ loop,
+ NULL);
+
+ g_assert (g_spawn_async (NULL, argv, NULL, 0, NULL, NULL, &pid, NULL));
+ if (!appeared)
+ g_main_loop_run (loop);
+ g_main_loop_unref (loop);
+
+ actions = list_actions ();
+
+ g_assert (g_strv_length (actions) == 2);
+ g_assert (_g_strv_has_string ((const char *const *)actions, "action1"));
+ g_assert (_g_strv_has_string ((const char *const *)actions, "action2"));
+
+ g_strfreev (actions);
+
+ kill (pid, SIGTERM);
+
+ g_bus_unwatch_name (watch);
+}
+
+static gboolean
+invoke_action (gpointer data)
+{
+ const gchar *action = data;
+ GDBusConnection *connection;
+ GVariant *res;
+
+ connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+ res = g_dbus_connection_call_sync (connection,
+ "org.gtk.test.app",
+ "/org/gtk/test/app",
+ "org.gtk.Application",
+ "InvokeAction",
+ g_variant_new ("(su)",
+ action,
+ 0),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ NULL);
+ if (res)
+ g_variant_unref (res);
+ g_object_unref (connection);
+
+ return FALSE;
+}
+
+static void
+exit_with_code_1 (GPid pid,
+ gint status,
+ gpointer data)
+{
+ GMainLoop *loop = data;
+
+ died++;
+
+ g_assert (WIFEXITED (status) && WEXITSTATUS(status) == 1);
+
+ if (loop)
+ g_main_loop_quit (loop);
+}
+
+/* This test starts an application, waits for it to appear,
+ * then invokes 'action1' and checks that it causes the application
+ * to exit with an exit code of 1.
+ */
+static void
+test_invoke (void)
+{
+ GMainLoop *loop;
+ gint watch;
+ gchar *argv[] = { "./testapp", NULL };
+ guint id1, id2, id3;
+
+ appeared = 0;
+ disappeared = 0;
+ died = FALSE;
+ timed_out = FALSE;
+
+ loop = g_main_loop_new (NULL, FALSE);
+ watch = g_bus_watch_name (G_BUS_TYPE_SESSION,
+ "org.gtk.test.app",
+ 0,
+ name_appeared,
+ name_disappeared,
+ NULL,
+ NULL);
+
+ g_assert (g_spawn_async (NULL, argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid, NULL));
+
+ id1 = g_child_watch_add (pid, exit_with_code_1, loop);
+
+ id2 = g_timeout_add (500, invoke_action, "action1");
+
+ id3 = g_timeout_add (5000, timeout, loop);
+
+ g_main_loop_run (loop);
+ g_assert_cmpint (timed_out, ==, FALSE);
+ g_assert_cmpint (appeared, >=, 1);
+ g_assert_cmpint (disappeared, >=, 1);
+ g_assert_cmpint (died, ==, TRUE);
+
+ g_bus_unwatch_name (watch);
+ g_main_loop_unref (loop);
+ g_source_remove (id1);
+ g_source_remove (id2);
+ g_source_remove (id3);
+
+ kill (pid, SIGTERM);
+}
+
+static void
+test_remote (void)
+{
+ GMainLoop *loop;
+ gint watch;
+ GPid pid1, pid2;
+ gchar *argv[] = { "./testapp", NULL, NULL };
+
+ appeared = 0;
+ timed_out = FALSE;
+
+ loop = g_main_loop_new (NULL, FALSE);
+ g_timeout_add (5000, timeout, loop);
+
+ watch = g_bus_watch_name (G_BUS_TYPE_SESSION,
+ "org.gtk.test.app",
+ 0,
+ name_appeared,
+ NULL,
+ loop,
+ NULL);
+
+ g_assert (g_spawn_async (NULL, argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid1, NULL));
+ g_child_watch_add (pid1, exit_with_code_1, loop);
+
+ g_main_loop_run (loop);
+
+ g_assert_cmpint (appeared, ==, 1);
+
+ argv[1] = "--non-unique";
+ g_assert (g_spawn_async (NULL, argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid2, NULL));
+
+ g_main_loop_run (loop);
+
+ g_assert_cmpint (appeared, ==, 1);
+ g_assert_cmpint (timed_out, ==, FALSE);
+
+ g_main_loop_unref (loop);
+ g_bus_unwatch_name (watch);
+
+ kill (pid1, SIGTERM);
+ kill (pid2, SIGTERM);
+}
+
+static void
+actions_changed (GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ GMainLoop *loop = user_data;
+
+ g_assert_cmpstr (interface_name, ==, "org.gtk.Application");
+ g_assert_cmpstr (signal_name, ==, "ActionsChanged");
+
+ changed++;
+
+ g_main_loop_quit (loop);
+}
+
+static void
+test_change_action (void)
+{
+ GMainLoop *loop;
+ gint watch;
+ guint id;
+ GPid pid1;
+ gchar *argv[] = { "./testapp", NULL, NULL };
+ GDBusConnection *connection;
+
+ appeared = 0;
+ changed = 0;
+ timed_out = FALSE;
+
+ loop = g_main_loop_new (NULL, FALSE);
+ g_timeout_add (5000, timeout, loop);
+
+ watch = g_bus_watch_name (G_BUS_TYPE_SESSION,
+ "org.gtk.test.app",
+ 0,
+ name_appeared,
+ NULL,
+ loop,
+ NULL);
+
+ g_assert (g_spawn_async (NULL, argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid1, NULL));
+ g_main_loop_run (loop);
+
+ g_assert_cmpint (appeared, ==, 1);
+
+ connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+ id = g_dbus_connection_signal_subscribe (connection,
+ NULL,
+ "org.gtk.Application",
+ "ActionsChanged",
+ "/org/gtk/test/app",
+ NULL,
+ actions_changed,
+ loop,
+ NULL);
+
+ g_timeout_add (1000, invoke_action, "action2");
+
+ g_main_loop_run (loop);
+
+ g_assert_cmpint (changed, >, 0);
+ g_assert_cmpint (timed_out, ==, FALSE);
+
+ g_dbus_connection_signal_unsubscribe (connection, id);
+ g_object_unref (connection);
+ g_main_loop_unref (loop);
+ g_bus_unwatch_name (watch);
+
+ kill (pid1, SIGTERM);
+}
+
+int
+main (int argc, char *argv[])
+{
+ g_type_init ();
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/application/unique", test_unique);
+ g_test_add_func ("/application/quit", test_quit);
+ g_test_add_func ("/application/list-actions", test_list_actions);
+ g_test_add_func ("/application/invoke", test_invoke);
+ g_test_add_func ("/application/remote", test_remote);
+ g_test_add_func ("/application/change-action", test_change_action);
+
+ return g_test_run ();
+}
+