[glib] Merge the wip/gapplication branch



commit 6427e93757dbc38cc53ffeb87ed814cdb9df0ed4
Author: Matthias Clasen <mclasen redhat com>
Date:   Mon Jun 7 13:25:39 2010 -0400

    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.

 docs/reference/gio/gio-docs.xml     |    4 +
 docs/reference/gio/gio-sections.txt |   32 +
 docs/reference/gio/gio.types        |    1 +
 gio/Makefile.am                     |    5 +
 gio/gappinfo.c                      |   42 +-
 gio/gapplication.c                  | 1058 +++++++++++++++++++++++++++++++++++
 gio/gapplication.h                  |  151 +++++
 gio/gdbusapplication.c              |  426 ++++++++++++++
 gio/gdbusconnection.h               |    2 +-
 gio/gdesktopappinfo.c               |   12 +
 gio/gio-marshal.list                |    3 +
 gio/gio.h                           |    1 +
 gio/gio.symbols                     |   21 +
 gio/gnullapplication.c              |   70 +++
 gio/tests/Makefile.am               |   20 +
 gio/tests/appinfo-test.c            |   20 +
 gio/tests/appinfo-test.desktop      |    4 +
 gio/tests/appinfo.c                 |   25 +
 gio/tests/application.c             |  134 +++++
 gio/tests/testapp.c                 |   79 +++
 gio/tests/testapps.c                |  533 ++++++++++++++++++
 21 files changed, 2626 insertions(+), 17 deletions(-)
---
diff --git a/docs/reference/gio/gio-docs.xml b/docs/reference/gio/gio-docs.xml
index 8fe0d98..0e092d1 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 f7e68d3..665a31e 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 5a4ff84..5073fc3 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 a2efeea..fe4053e 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 6feec82..38e3063 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 0000000..d974ed9
--- /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 0000000..bb70fe1
--- /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 0000000..012845d
--- /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 1a9597c..b203196 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 ddccf34..493a3cd 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 7899b9d..2170173 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 3e97e34..11cb725 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 55ff916..a366e22 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 0000000..6d255e6
--- /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 caab309..8fcdd9c 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 0000000..9d6a5ff
--- /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 0000000..becc1c4
--- /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 0000000..099068a
--- /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 0000000..5dba027
--- /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 0000000..8ed3091
--- /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 0000000..51db0b8
--- /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 ();
+}
+



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]