From: Chris Coulson <chrisccoulson@ubuntu.com>
Date: Wed, 15 Aug 2018 17:13:26 +0200
Subject: Add support for autostarting applications after a delay

Make it possible to delay autostarting an application for a time
period defined by the "X-GNOME-Autostart-Delay" key in the applications
desktop file
Bug: https://bugzilla.gnome.org/show_bug.cgi?id=608402
---
 gnome-session/gsm-app.c           | 18 ++++++++++++++-
 gnome-session/gsm-app.h           |  2 ++
 gnome-session/gsm-autostart-app.c | 26 ++++++++++++++++++++++
 gnome-session/gsm-autostart-app.h |  1 +
 gnome-session/gsm-manager.c       | 46 ++++++++++++++++++++++++++++++++++++---
 5 files changed, 89 insertions(+), 4 deletions(-)

diff --git a/gnome-session/gsm-app.c b/gnome-session/gsm-app.c
index d1ef89a..3f6a8ec 100644
--- a/gnome-session/gsm-app.c
+++ b/gnome-session/gsm-app.c
@@ -316,6 +316,7 @@ gsm_app_class_init (GsmAppClass *klass)
         klass->impl_provides = NULL;
         klass->impl_get_provides = NULL;
         klass->impl_is_running = NULL;
+        klass->impl_peek_autostart_delay = NULL;
 
         g_object_class_install_property (object_class,
                                          PROP_PHASE,
@@ -518,7 +519,22 @@ gboolean
 gsm_app_stop (GsmApp  *app,
               GError **error)
 {
-        return GSM_APP_GET_CLASS (app)->impl_stop (app, error);
+        if (gsm_app_is_running (app))
+                return GSM_APP_GET_CLASS (app)->impl_stop (app, error);
+
+        return TRUE;
+}
+
+int
+gsm_app_peek_autostart_delay (GsmApp *app)
+{
+        g_return_val_if_fail (GSM_IS_APP (app), FALSE);
+
+        if (GSM_APP_GET_CLASS (app)->impl_peek_autostart_delay) {
+                return GSM_APP_GET_CLASS (app)->impl_peek_autostart_delay (app);
+        } else {
+                return 0;
+        }
 }
 
 void
diff --git a/gnome-session/gsm-app.h b/gnome-session/gsm-app.h
index f38b3be..85cb97e 100644
--- a/gnome-session/gsm-app.h
+++ b/gnome-session/gsm-app.h
@@ -63,6 +63,7 @@ struct _GsmAppClass
                                                        GError    **error);
         gboolean    (*impl_stop)                      (GsmApp     *app,
                                                        GError    **error);
+        int         (*impl_peek_autostart_delay)      (GsmApp     *app);
         gboolean    (*impl_provides)                  (GsmApp     *app,
                                                        const char *service);
         char **     (*impl_get_provides)              (GsmApp     *app);
@@ -124,6 +125,7 @@ gboolean         gsm_app_has_autostart_condition        (GsmApp     *app,
 gboolean         gsm_app_get_registered                 (GsmApp     *app);
 void             gsm_app_set_registered                 (GsmApp     *app,
                                                          gboolean  registered);
+int              gsm_app_peek_autostart_delay           (GsmApp     *app);
 
 gboolean         gsm_app_save_to_keyfile                (GsmApp    *app,
                                                          GKeyFile  *keyfile,
diff --git a/gnome-session/gsm-autostart-app.c b/gnome-session/gsm-autostart-app.c
index 01ba5f9..cfb63ce 100644
--- a/gnome-session/gsm-autostart-app.c
+++ b/gnome-session/gsm-autostart-app.c
@@ -20,6 +20,7 @@
 #include <config.h>
 
 #include <ctype.h>
+#include <stdlib.h>
 #include <string.h>
 #include <sys/wait.h>
 #include <errno.h>
@@ -69,6 +70,7 @@ struct _GsmAutostartAppPrivate {
         char                 *condition_string;
         gboolean              condition;
         gboolean              autorestart;
+        int                   autostart_delay;
 
         GFileMonitor         *condition_monitor;
         guint                 condition_notify_id;
@@ -107,6 +109,7 @@ gsm_autostart_app_init (GsmAutostartApp *app)
         app->priv->pid = -1;
         app->priv->condition_monitor = NULL;
         app->priv->condition = FALSE;
+        app->priv->autostart_delay = -1;
 }
 
 static gboolean
@@ -628,6 +631,20 @@ load_desktop_file (GsmAutostartApp  *app)
                                                                    "AutostartCondition");
         setup_condition_monitor (app);
 
+        const char *delay;
+        delay = g_desktop_app_info_get_string (app->priv->app_info,
+                                               GSM_AUTOSTART_APP_DELAY_KEY);
+
+        if (delay != NULL) {
+                app->priv->autostart_delay = strtol (delay, NULL, 10);
+
+                if (app->priv->autostart_delay < 0) {
+                        g_warning ("Invalid autostart delay of %d for %s", app->priv->autostart_delay,
+                                   gsm_app_peek_id (GSM_APP (app)));
+                        app->priv->autostart_delay = -1;
+                }
+        }
+
         g_object_set (app,
                       "phase", phase,
                       "startup-id", startup_id,
@@ -1344,6 +1361,14 @@ gsm_autostart_app_get_app_id (GsmApp *app)
         return g_app_info_get_id (G_APP_INFO (GSM_AUTOSTART_APP (app)->priv->app_info));
 }
 
+static int
+gsm_autostart_app_peek_autostart_delay (GsmApp *app)
+{
+        GsmAutostartApp *aapp = GSM_AUTOSTART_APP (app);
+
+        return aapp->priv->autostart_delay;
+}
+
 static gboolean
 gsm_autostart_app_initable_init (GInitable *initable,
                                  GCancellable *cancellable,
@@ -1460,6 +1485,7 @@ gsm_autostart_app_class_init (GsmAutostartAppClass *klass)
         app_class->impl_has_autostart_condition = gsm_autostart_app_has_autostart_condition;
         app_class->impl_get_app_id = gsm_autostart_app_get_app_id;
         app_class->impl_get_autorestart = gsm_autostart_app_get_autorestart;
+        app_class->impl_peek_autostart_delay = gsm_autostart_app_peek_autostart_delay;
         app_class->impl_save_to_keyfile = gsm_autostart_app_save_to_keyfile;
 
         g_object_class_install_property (object_class,
diff --git a/gnome-session/gsm-autostart-app.h b/gnome-session/gsm-autostart-app.h
index 17faf75..eb1a146 100644
--- a/gnome-session/gsm-autostart-app.h
+++ b/gnome-session/gsm-autostart-app.h
@@ -72,6 +72,7 @@ void    gsm_autostart_app_add_provides       (GsmAutostartApp *aapp,
 #define GSM_AUTOSTART_APP_DBUS_PATH_KEY   "X-GNOME-DBus-Path"
 #define GSM_AUTOSTART_APP_DBUS_ARGS_KEY   "X-GNOME-DBus-Start-Arguments"
 #define GSM_AUTOSTART_APP_DISCARD_KEY     "X-GNOME-Autostart-discard-exec"
+#define GSM_AUTOSTART_APP_DELAY_KEY       "X-GNOME-Autostart-Delay"
 
 G_END_DECLS
 
diff --git a/gnome-session/gsm-manager.c b/gnome-session/gsm-manager.c
index d41bdca..984e5c9 100644
--- a/gnome-session/gsm-manager.c
+++ b/gnome-session/gsm-manager.c
@@ -411,7 +411,8 @@ app_condition_changed (GsmApp     *app,
                 } else {
                         g_debug ("GsmManager: stopping app %s", gsm_app_peek_id (app));
 
-                        /* If we don't have a client then we should try to kill the app */
+                        /* If we don't have a client then we should try to kill the app ,
+                         * if it is running */
                         error = NULL;
                         res = gsm_app_stop (app, &error);
                         if (! res) {
@@ -745,11 +746,37 @@ on_phase_timeout (GsmManager *manager)
         return FALSE;
 }
 
+static gboolean
+_autostart_delay_timeout (GsmApp *app)
+{
+        GError *error = NULL;
+        gboolean res;
+
+        if (!gsm_app_peek_is_disabled (app)
+            && !gsm_app_peek_is_conditionally_disabled (app)) {
+                res = gsm_app_start (app, &error);
+                if (!res) {
+                        if (error != NULL) {
+                                g_warning ("Could not launch application '%s': %s",
+                                           gsm_app_peek_app_id (app),
+                                           error->message);
+                                g_error_free (error);
+                        }
+                }
+        }
+
+        g_object_unref (app);
+
+        return FALSE;
+}
+
 static gboolean
 _start_app (const char *id,
             GsmApp     *app,
             GsmManager *manager)
 {
+        int      delay;
+
         if (gsm_app_peek_phase (app) != manager->priv->phase) {
                 goto out;
         }
@@ -767,6 +794,18 @@ _start_app (const char *id,
                 goto out;
         }
 
+        /* Only accept an autostart delay for the application phase */
+        if (manager->priv->phase == GSM_MANAGER_PHASE_APPLICATION) {
+                delay = gsm_app_peek_autostart_delay (app);
+                if (delay > 0) {
+                        g_timeout_add_seconds (delay,
+                                               (GSourceFunc)_autostart_delay_timeout,
+                                               g_object_ref (app));
+                        g_debug ("GsmManager: %s is scheduled to start in %d seconds", id, delay);
+                        goto out;
+                }
+        }
+
         if (!start_app_or_warn (manager, app))
                 goto out;
 
@@ -1432,11 +1471,12 @@ _debug_app_for_phase (const char *id,
                 return FALSE;
         }
 
-        g_debug ("GsmManager:\tID: %s\tapp-id:%s\tis-disabled:%d\tis-conditionally-disabled:%d",
+        g_debug ("GsmManager:\tID: %s\tapp-id:%s\tis-disabled:%d\tis-conditionally-disabled:%d\tis-delayed:%d",
                  gsm_app_peek_id (app),
                  gsm_app_peek_app_id (app),
                  gsm_app_peek_is_disabled (app),
-                 gsm_app_peek_is_conditionally_disabled (app));
+                 gsm_app_peek_is_conditionally_disabled (app),
+                 (gsm_app_peek_autostart_delay (app) > 0));
 
         return FALSE;
 }
