diff options
Diffstat (limited to '')
-rw-r--r-- | gnome-session/gsm-autostart-app.c | 1511 |
1 files changed, 1511 insertions, 0 deletions
diff --git a/gnome-session/gsm-autostart-app.c b/gnome-session/gsm-autostart-app.c new file mode 100644 index 0000000..8204d4f --- /dev/null +++ b/gnome-session/gsm-autostart-app.c @@ -0,0 +1,1511 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <ctype.h> +#include <string.h> +#include <sys/wait.h> +#include <errno.h> + +#include <glib.h> +#include <gio/gio.h> +#include <gio/gdesktopappinfo.h> + +#define GNOME_DESKTOP_USE_UNSTABLE_API +#include <libgnome-desktop/gnome-systemd.h> + +#ifdef HAVE_SYSTEMD +#ifdef ENABLE_SYSTEMD_JOURNAL +#include <systemd/sd-journal.h> +#endif +#include <systemd/sd-daemon.h> +#endif + +#include "gsm-autostart-app.h" +#include "gsm-util.h" + +enum { + AUTOSTART_LAUNCH_SPAWN = 0, + AUTOSTART_LAUNCH_ACTIVATE +}; + +enum { + GSM_CONDITION_NONE = 0, + GSM_CONDITION_IF_EXISTS = 1, + GSM_CONDITION_UNLESS_EXISTS = 2, + GSM_CONDITION_GSETTINGS = 3, + GSM_CONDITION_IF_SESSION = 4, + GSM_CONDITION_UNLESS_SESSION = 5, + GSM_CONDITION_UNKNOWN = 6 +}; + +#define GSM_SESSION_CLIENT_DBUS_INTERFACE "org.gnome.SessionClient" + +typedef struct +{ + gboolean mask_systemd; + char *desktop_filename; + char *desktop_id; + char *startup_id; + + GDesktopAppInfo *app_info; + /* provides defined in session definition */ + GSList *session_provides; + + /* desktop file state */ + char *condition_string; + gboolean condition; + gboolean autorestart; + + GFileMonitor *condition_monitor; + guint condition_notify_id; + GSettings *condition_settings; + + int launch_type; + GPid pid; + guint child_watch_id; +} GsmAutostartAppPrivate; + +enum { + CONDITION_CHANGED, + LAST_SIGNAL +}; + +typedef enum { + PROP_DESKTOP_FILENAME = 1, + PROP_MASK_SYSTEMD, +} GsmAutostartAppProperty; + +static GParamSpec *props[PROP_MASK_SYSTEMD + 1] = { NULL, }; + +static guint signals[LAST_SIGNAL] = { 0 }; + +static void gsm_autostart_app_initable_iface_init (GInitableIface *iface); + +G_DEFINE_TYPE_WITH_CODE (GsmAutostartApp, gsm_autostart_app, GSM_TYPE_APP, + G_ADD_PRIVATE (GsmAutostartApp) + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, gsm_autostart_app_initable_iface_init)) + +static void +gsm_autostart_app_init (GsmAutostartApp *app) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app); + + priv->pid = -1; + priv->condition_monitor = NULL; + priv->condition = FALSE; +} + +static gboolean +is_disabled (GsmApp *app) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (app)); + + /* GSM_AUTOSTART_APP_ENABLED_KEY key, used by old gnome-session */ + if (g_desktop_app_info_has_key (priv->app_info, + GSM_AUTOSTART_APP_ENABLED_KEY) && + !g_desktop_app_info_get_boolean (priv->app_info, + GSM_AUTOSTART_APP_ENABLED_KEY)) { + g_debug ("app %s is disabled by " GSM_AUTOSTART_APP_ENABLED_KEY, + gsm_app_peek_id (app)); + return TRUE; + } + + /* Hidden key, used by autostart spec */ + if (g_desktop_app_info_get_is_hidden (priv->app_info)) { + g_debug ("app %s is disabled by Hidden", + gsm_app_peek_id (app)); + return TRUE; + } + + /* Check OnlyShowIn/NotShowIn/TryExec */ + if (!g_desktop_app_info_get_show_in (priv->app_info, NULL)) { + g_debug ("app %s is not for the current desktop", + gsm_app_peek_id (app)); + return TRUE; + } + + /* Check if app is systemd enabled and mask-systemd is set. */ + if (priv->mask_systemd && + g_desktop_app_info_has_key (priv->app_info, + GSM_AUTOSTART_APP_SYSTEMD_KEY) && + g_desktop_app_info_get_boolean (priv->app_info, + GSM_AUTOSTART_APP_SYSTEMD_KEY)) { + g_debug ("app %s is disabled by " GSM_AUTOSTART_APP_SYSTEMD_KEY, + gsm_app_peek_id (app)); + return TRUE; + } + + /* Do not check AutostartCondition - this method is only to determine + if the app is unconditionally disabled */ + + return FALSE; +} + +static gboolean +parse_condition_string (const char *condition_string, + guint *condition_kindp, + char **keyp) +{ + const char *space; + const char *key; + int len; + guint kind; + + space = condition_string + strcspn (condition_string, " "); + len = space - condition_string; + key = space; + while (isspace ((unsigned char)*key)) { + key++; + } + + kind = GSM_CONDITION_UNKNOWN; + + if (!g_ascii_strncasecmp (condition_string, "if-exists", len) && key) { + kind = GSM_CONDITION_IF_EXISTS; + } else if (!g_ascii_strncasecmp (condition_string, "unless-exists", len) && key) { + kind = GSM_CONDITION_UNLESS_EXISTS; + } else if (!g_ascii_strncasecmp (condition_string, "GSettings", len)) { + kind = GSM_CONDITION_GSETTINGS; + } else if (!g_ascii_strncasecmp (condition_string, "GNOME3", len)) { + condition_string = key; + space = condition_string + strcspn (condition_string, " "); + len = space - condition_string; + key = space; + while (isspace ((unsigned char)*key)) { + key++; + } + if (!g_ascii_strncasecmp (condition_string, "if-session", len) && key) { + kind = GSM_CONDITION_IF_SESSION; + } else if (!g_ascii_strncasecmp (condition_string, "unless-session", len) && key) { + kind = GSM_CONDITION_UNLESS_SESSION; + } + } + + if (kind == GSM_CONDITION_UNKNOWN) { + key = NULL; + } + + if (keyp != NULL) { + *keyp = g_strdup (key); + } + + if (condition_kindp != NULL) { + *condition_kindp = kind; + } + + return (kind != GSM_CONDITION_UNKNOWN); +} + +static void +if_exists_condition_cb (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event, + GsmApp *app) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (app)); + gboolean condition = FALSE; + + switch (event) { + case G_FILE_MONITOR_EVENT_CREATED: + condition = TRUE; + break; + case G_FILE_MONITOR_EVENT_DELETED: + condition = FALSE; + break; + default: + /* Ignore any other monitor event */ + return; + } + + /* Emit only if the condition actually changed */ + if (condition != priv->condition) { + priv->condition = condition; + g_signal_emit (app, signals[CONDITION_CHANGED], 0, condition); + } +} + +static void +unless_exists_condition_cb (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event, + GsmApp *app) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (app)); + gboolean condition = FALSE; + + switch (event) { + case G_FILE_MONITOR_EVENT_CREATED: + condition = FALSE; + break; + case G_FILE_MONITOR_EVENT_DELETED: + condition = TRUE; + break; + default: + /* Ignore any other monitor event */ + return; + } + + /* Emit only if the condition actually changed */ + if (condition != priv->condition) { + priv->condition = condition; + g_signal_emit (app, signals[CONDITION_CHANGED], 0, condition); + } +} + +static void +gsettings_condition_cb (GSettings *settings, + const char *key, + gpointer user_data) +{ + GsmApp *app = GSM_APP (user_data); + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (app)); + gboolean condition; + + g_return_if_fail (GSM_IS_APP (user_data)); + + condition = g_settings_get_boolean (settings, key); + + g_debug ("GsmAutostartApp: app:%s condition changed condition:%d", + gsm_app_peek_id (app), + condition); + + /* Emit only if the condition actually changed */ + if (condition != priv->condition) { + priv->condition = condition; + g_signal_emit (app, signals[CONDITION_CHANGED], 0, condition); + } +} + +static gboolean +setup_gsettings_condition_monitor (GsmAutostartApp *app, + const char *key) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app); + GSettingsSchemaSource *source; + GSettingsSchema *schema; + GSettings *settings; + GSettingsSchemaKey *schema_key; + const GVariantType *key_type; + char **elems; + gboolean retval = FALSE; + char *signal; + + retval = FALSE; + + schema = NULL; + + elems = g_strsplit (key, " ", 2); + + if (elems == NULL) + goto out; + + if (elems[0] == NULL || elems[1] == NULL) + goto out; + + source = g_settings_schema_source_get_default (); + + schema = g_settings_schema_source_lookup (source, elems[0], TRUE); + + if (schema == NULL) + goto out; + + if (!g_settings_schema_has_key (schema, elems[1])) + goto out; + + schema_key = g_settings_schema_get_key (schema, elems[1]); + + g_assert (schema_key != NULL); + + key_type = g_settings_schema_key_get_value_type (schema_key); + + g_settings_schema_key_unref (schema_key); + + g_assert (key_type != NULL); + + if (!g_variant_type_equal (key_type, G_VARIANT_TYPE_BOOLEAN)) + goto out; + + settings = g_settings_new_full (schema, NULL, NULL); + retval = g_settings_get_boolean (settings, elems[1]); + + signal = g_strdup_printf ("changed::%s", elems[1]); + g_signal_connect (G_OBJECT (settings), signal, + G_CALLBACK (gsettings_condition_cb), app); + g_free (signal); + + priv->condition_settings = settings; + +out: + if (schema) + g_settings_schema_unref (schema); + g_strfreev (elems); + + return retval; +} + +static void +if_session_condition_cb (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + GsmApp *app = GSM_APP (user_data); + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (app)); + char *session_name; + char *key; + gboolean condition; + + g_return_if_fail (GSM_IS_APP (user_data)); + + parse_condition_string (priv->condition_string, NULL, &key); + + g_object_get (object, "session-name", &session_name, NULL); + condition = strcmp (session_name, key) == 0; + g_free (session_name); + + g_free (key); + + g_debug ("GsmAutostartApp: app:%s condition changed condition:%d", + gsm_app_peek_id (app), + condition); + + /* Emit only if the condition actually changed */ + if (condition != priv->condition) { + priv->condition = condition; + g_signal_emit (app, signals[CONDITION_CHANGED], 0, condition); + } +} + +static void +unless_session_condition_cb (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + GsmApp *app = GSM_APP (user_data); + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (app)); + char *session_name; + char *key; + gboolean condition; + + g_return_if_fail (GSM_IS_APP (user_data)); + + parse_condition_string (priv->condition_string, NULL, &key); + + g_object_get (object, "session-name", &session_name, NULL); + condition = strcmp (session_name, key) != 0; + g_free (session_name); + + g_free (key); + + g_debug ("GsmAutostartApp: app:%s condition changed condition:%d", + gsm_app_peek_id (app), + condition); + + /* Emit only if the condition actually changed */ + if (condition != priv->condition) { + priv->condition = condition; + g_signal_emit (app, signals[CONDITION_CHANGED], 0, condition); + } +} + +static char * +resolve_conditional_file_path (const char *file) +{ + if (g_path_is_absolute (file)) + return g_strdup (file); + return g_build_filename (g_get_user_config_dir (), file, NULL); +} + +static void +setup_condition_monitor (GsmAutostartApp *app) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app); + guint kind; + char *key; + gboolean res; + gboolean disabled; + + if (priv->condition_monitor != NULL) { + g_file_monitor_cancel (priv->condition_monitor); + } + + if (priv->condition_string == NULL) { + return; + } + + /* if it is disabled outright there is no point in monitoring */ + if (is_disabled (GSM_APP (app))) { + return; + } + + key = NULL; + res = parse_condition_string (priv->condition_string, &kind, &key); + if (! res) { + g_free (key); + return; + } + + if (key == NULL) { + return; + } + + if (kind == GSM_CONDITION_IF_EXISTS) { + char *file_path; + GFile *file; + + file_path = resolve_conditional_file_path (key); + disabled = !g_file_test (file_path, G_FILE_TEST_EXISTS); + + file = g_file_new_for_path (file_path); + priv->condition_monitor = g_file_monitor_file (file, 0, NULL, NULL); + + g_signal_connect (priv->condition_monitor, "changed", + G_CALLBACK (if_exists_condition_cb), + app); + + g_object_unref (file); + g_free (file_path); + } else if (kind == GSM_CONDITION_UNLESS_EXISTS) { + char *file_path; + GFile *file; + + file_path = resolve_conditional_file_path (key); + disabled = g_file_test (file_path, G_FILE_TEST_EXISTS); + + file = g_file_new_for_path (file_path); + priv->condition_monitor = g_file_monitor_file (file, 0, NULL, NULL); + + g_signal_connect (priv->condition_monitor, "changed", + G_CALLBACK (unless_exists_condition_cb), + app); + + g_object_unref (file); + g_free (file_path); + } else if (kind == GSM_CONDITION_GSETTINGS) { + disabled = !setup_gsettings_condition_monitor (app, key); + } else if (kind == GSM_CONDITION_IF_SESSION) { + GsmManager *manager; + char *session_name; + + /* get the singleton */ + manager = gsm_manager_get (); + + g_object_get (manager, "session-name", &session_name, NULL); + disabled = strcmp (session_name, key) != 0; + + g_signal_connect (manager, "notify::session-name", + G_CALLBACK (if_session_condition_cb), app); + g_free (session_name); + } else if (kind == GSM_CONDITION_UNLESS_SESSION) { + GsmManager *manager; + char *session_name; + + /* get the singleton */ + manager = gsm_manager_get (); + + g_object_get (manager, "session-name", &session_name, NULL); + disabled = strcmp (session_name, key) == 0; + + g_signal_connect (manager, "notify::session-name", + G_CALLBACK (unless_session_condition_cb), app); + g_free (session_name); + } else { + disabled = TRUE; + } + + g_free (key); + + if (disabled) { + /* FIXME: cache the disabled value? */ + } +} + +static void +load_desktop_file (GsmAutostartApp *app) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app); + char *dbus_name; + char *startup_id; + char *phase_str; + int phase; + gboolean res; + + g_assert (priv->app_info != NULL); + + phase_str = g_desktop_app_info_get_string (priv->app_info, + GSM_AUTOSTART_APP_PHASE_KEY); + if (phase_str != NULL) { + if (strcmp (phase_str, "EarlyInitialization") == 0) { + phase = GSM_MANAGER_PHASE_EARLY_INITIALIZATION; + } else if (strcmp (phase_str, "PreDisplayServer") == 0) { + phase = GSM_MANAGER_PHASE_PRE_DISPLAY_SERVER; + } else if (strcmp (phase_str, "DisplayServer") == 0) { + phase = GSM_MANAGER_PHASE_DISPLAY_SERVER; + } else if (strcmp (phase_str, "Initialization") == 0) { + phase = GSM_MANAGER_PHASE_INITIALIZATION; + } else if (strcmp (phase_str, "WindowManager") == 0) { + phase = GSM_MANAGER_PHASE_WINDOW_MANAGER; + } else if (strcmp (phase_str, "Panel") == 0) { + phase = GSM_MANAGER_PHASE_PANEL; + } else if (strcmp (phase_str, "Desktop") == 0) { + phase = GSM_MANAGER_PHASE_DESKTOP; + } else { + phase = GSM_MANAGER_PHASE_APPLICATION; + } + + g_free (phase_str); + } else { + phase = GSM_MANAGER_PHASE_APPLICATION; + } + + dbus_name = g_desktop_app_info_get_string (priv->app_info, + GSM_AUTOSTART_APP_DBUS_NAME_KEY); + if (dbus_name != NULL) { + priv->launch_type = AUTOSTART_LAUNCH_ACTIVATE; + } else { + priv->launch_type = AUTOSTART_LAUNCH_SPAWN; + } + + /* this must only be done on first load */ + switch (priv->launch_type) { + case AUTOSTART_LAUNCH_SPAWN: + startup_id = + g_desktop_app_info_get_string (priv->app_info, + GSM_AUTOSTART_APP_STARTUP_ID_KEY); + + if (startup_id == NULL) { + startup_id = gsm_util_generate_startup_id (); + } + break; + case AUTOSTART_LAUNCH_ACTIVATE: + startup_id = g_strdup (dbus_name); + break; + default: + g_assert_not_reached (); + } + + res = g_desktop_app_info_has_key (priv->app_info, + GSM_AUTOSTART_APP_AUTORESTART_KEY); + if (res) { + priv->autorestart = g_desktop_app_info_get_boolean (priv->app_info, + GSM_AUTOSTART_APP_AUTORESTART_KEY); + } else { + priv->autorestart = FALSE; + } + + g_free (priv->condition_string); + priv->condition_string = g_desktop_app_info_get_string (priv->app_info, + "AutostartCondition"); + setup_condition_monitor (app); + + g_object_set (app, + "phase", phase, + "startup-id", startup_id, + NULL); + + g_free (startup_id); + g_free (dbus_name); +} + +static void +gsm_autostart_app_set_desktop_filename (GsmAutostartApp *app, + const char *desktop_filename) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app); + + if (g_strcmp0 (priv->desktop_filename, desktop_filename) == 0) + return; + + if (priv->app_info != NULL) { + g_clear_object (&priv->app_info); + g_clear_pointer (&priv->desktop_filename, g_free); + g_clear_pointer (&priv->desktop_id, g_free); + } + + if (desktop_filename != NULL) { + priv->desktop_filename = g_strdup (desktop_filename); + priv->desktop_id = g_path_get_basename (desktop_filename); + } + + g_object_notify_by_pspec (G_OBJECT (app), props[PROP_DESKTOP_FILENAME]); +} + +static void +gsm_autostart_app_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GsmAutostartApp *self = GSM_AUTOSTART_APP (object); + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (self); + + switch ((GsmAutostartAppProperty) prop_id) { + case PROP_DESKTOP_FILENAME: + gsm_autostart_app_set_desktop_filename (self, g_value_get_string (value)); + break; + case PROP_MASK_SYSTEMD: + if (priv->mask_systemd != g_value_get_boolean (value)) { + priv->mask_systemd = g_value_get_boolean (value); + g_object_notify_by_pspec (object, props[PROP_MASK_SYSTEMD]); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gsm_autostart_app_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GsmAutostartApp *self = GSM_AUTOSTART_APP (object); + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (self); + + switch ((GsmAutostartAppProperty) prop_id) { + case PROP_DESKTOP_FILENAME: + if (priv->app_info != NULL) { + g_value_set_string (value, g_desktop_app_info_get_filename (priv->app_info)); + } else { + g_value_set_string (value, NULL); + } + break; + case PROP_MASK_SYSTEMD: + g_value_set_boolean (value, priv->mask_systemd); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gsm_autostart_app_dispose (GObject *object) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (object)); + + g_clear_pointer (&priv->startup_id, g_free); + + if (priv->session_provides) { + g_slist_free_full (priv->session_provides, g_free); + priv->session_provides = NULL; + } + + g_clear_pointer (&priv->condition_string, g_free); + g_clear_object (&priv->condition_settings); + g_clear_object (&priv->app_info); + g_clear_pointer (&priv->desktop_filename, g_free); + g_clear_pointer (&priv->desktop_id, g_free); + + if (priv->child_watch_id > 0) { + g_source_remove (priv->child_watch_id); + priv->child_watch_id = 0; + } + + if (priv->condition_monitor) { + g_file_monitor_cancel (priv->condition_monitor); + } + + G_OBJECT_CLASS (gsm_autostart_app_parent_class)->dispose (object); +} + +static gboolean +is_running (GsmApp *app) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (app)); + gboolean is; + + /* is running if pid is still valid or + * or a client is connected + */ + /* FIXME: check client */ + is = (priv->pid != -1); + + return is; +} + +static gboolean +is_conditionally_disabled (GsmApp *app) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (app)); + gboolean res; + gboolean disabled; + char *key; + guint kind; + + /* Check AutostartCondition */ + if (priv->condition_string == NULL) { + return FALSE; + } + + key = NULL; + res = parse_condition_string (priv->condition_string, &kind, &key); + if (! res) { + g_free (key); + return TRUE; + } + + if (key == NULL) { + return TRUE; + } + + if (kind == GSM_CONDITION_IF_EXISTS) { + char *file_path; + + file_path = resolve_conditional_file_path (key); + disabled = !g_file_test (file_path, G_FILE_TEST_EXISTS); + g_free (file_path); + } else if (kind == GSM_CONDITION_UNLESS_EXISTS) { + char *file_path; + + file_path = resolve_conditional_file_path (key); + disabled = g_file_test (file_path, G_FILE_TEST_EXISTS); + g_free (file_path); + } else if (kind == GSM_CONDITION_GSETTINGS && + priv->condition_settings != NULL) { + char **elems; + elems = g_strsplit (key, " ", 2); + disabled = !g_settings_get_boolean (priv->condition_settings, elems[1]); + g_strfreev (elems); + } else if (kind == GSM_CONDITION_IF_SESSION) { + GsmManager *manager; + char *session_name; + + /* get the singleton */ + manager = gsm_manager_get (); + + g_object_get (manager, "session-name", &session_name, NULL); + disabled = strcmp (session_name, key) != 0; + g_free (session_name); + } else if (kind == GSM_CONDITION_UNLESS_SESSION) { + GsmManager *manager; + char *session_name; + + /* get the singleton */ + manager = gsm_manager_get (); + + g_object_get (manager, "session-name", &session_name, NULL); + disabled = strcmp (session_name, key) == 0; + g_free (session_name); + } else { + disabled = TRUE; + } + + /* Set initial condition */ + priv->condition = !disabled; + + g_free (key); + + return disabled; +} + +static void +app_exited (GPid pid, + int status, + GsmAutostartApp *app) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app); + + g_debug ("GsmAutostartApp: (pid:%d) done (%s:%d)", + (int) pid, + WIFEXITED (status) ? "status" + : WIFSIGNALED (status) ? "signal" + : "unknown", + WIFEXITED (status) ? WEXITSTATUS (status) + : WIFSIGNALED (status) ? WTERMSIG (status) + : -1); + + g_spawn_close_pid (priv->pid); + priv->pid = -1; + priv->child_watch_id = 0; + + if (WIFEXITED (status)) { + gsm_app_exited (GSM_APP (app), WEXITSTATUS (status)); + } else if (WIFSIGNALED (status)) { + gsm_app_died (GSM_APP (app), WTERMSIG (status)); + } +} + +static int +_signal_pid (int pid, + int signal) +{ + int status; + + /* perhaps block sigchld */ + g_debug ("GsmAutostartApp: sending signal %d to process %d", signal, pid); + errno = 0; + status = kill (pid, signal); + + if (status < 0) { + if (errno == ESRCH) { + g_warning ("Child process %d was already dead.", + (int)pid); + } else { + g_warning ("Couldn't kill child process %d: %s", + pid, + g_strerror (errno)); + } + } + + /* perhaps unblock sigchld */ + + return status; +} + +static gboolean +autostart_app_stop_spawn (GsmAutostartApp *app, + GError **error) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app); + int res; + + if (priv->pid < 1) { + g_set_error (error, + GSM_APP_ERROR, + GSM_APP_ERROR_STOP, + "Not running"); + return FALSE; + } + + res = _signal_pid (priv->pid, SIGTERM); + if (res != 0) { + g_set_error (error, + GSM_APP_ERROR, + GSM_APP_ERROR_STOP, + "Unable to stop: %s", + g_strerror (errno)); + return FALSE; + } + + return TRUE; +} + +static gboolean +autostart_app_stop_activate (GsmAutostartApp *app, + GError **error) +{ + return TRUE; +} + +static gboolean +gsm_autostart_app_stop (GsmApp *app, + GError **error) +{ + GsmAutostartApp *self = GSM_AUTOSTART_APP (app); + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (self); + gboolean ret; + + g_return_val_if_fail (priv->app_info != NULL, FALSE); + + switch (priv->launch_type) { + case AUTOSTART_LAUNCH_SPAWN: + ret = autostart_app_stop_spawn (self, error); + break; + case AUTOSTART_LAUNCH_ACTIVATE: + ret = autostart_app_stop_activate (self, error); + break; + default: + g_assert_not_reached (); + break; + } + + return ret; +} + +static void +app_launched (GAppLaunchContext *ctx, + GAppInfo *appinfo, + GVariant *platform_data, + gpointer data) +{ + GsmAutostartApp *app = data; + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app); + gint pid; + gchar *sn_id; + + pid = 0; + sn_id = NULL; + + g_variant_lookup (platform_data, "pid", "i", &pid); + g_variant_lookup (platform_data, "startup-notification-id", "s", &sn_id); + priv->pid = pid; + priv->startup_id = sn_id; + + /* We are not interested in the result. */ + gnome_start_systemd_scope (priv->desktop_id, + pid, + NULL, + NULL, + NULL, NULL, NULL); +} + +#ifdef ENABLE_SYSTEMD_JOURNAL +static void +on_child_setup (GsmAutostartApp *app) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app); + int standard_output, standard_error; + + /* The FALSE means programs aren't expected to prefix each + * line with <n> prefix to specify priority. + */ + standard_output = sd_journal_stream_fd (priv->desktop_id, + LOG_INFO, + FALSE); + standard_error = sd_journal_stream_fd (priv->desktop_id, + LOG_WARNING, + FALSE); + + if (standard_output >= 0) { + dup2 (standard_output, STDOUT_FILENO); + close (standard_output); + } + + if (standard_error >= 0) { + dup2 (standard_error, STDERR_FILENO); + close (standard_error); + } +} +#endif + +static gboolean +autostart_app_start_spawn (GsmAutostartApp *app, + GError **error) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app); + gboolean success; + GError *local_error; + const char *startup_id; + const char * const *variable_blacklist; + const char * const *child_environment; + int i; + GAppLaunchContext *ctx; + GSpawnChildSetupFunc child_setup_func = NULL; + gpointer child_setup_data = NULL; + guint handler; + + startup_id = gsm_app_peek_startup_id (GSM_APP (app)); + g_assert (startup_id != NULL); + + g_debug ("GsmAutostartApp: starting %s: command=%s startup-id=%s", priv->desktop_id, g_app_info_get_commandline (G_APP_INFO (priv->app_info)), startup_id); + + g_free (priv->startup_id); + local_error = NULL; + ctx = g_app_launch_context_new (); + + variable_blacklist = gsm_util_get_variable_blacklist (); + for (i = 0; variable_blacklist[i] != NULL; i++) + g_app_launch_context_unsetenv (ctx, variable_blacklist[i]); + + child_environment = gsm_util_listenv (); + for (i = 0; child_environment[i] != NULL; i++) { + char **environment_tuple; + const char *key; + const char *value; + + environment_tuple = g_strsplit (child_environment[i], "=", 2); + key = environment_tuple[0]; + value = environment_tuple[1]; + + if (value != NULL) + g_app_launch_context_setenv (ctx, key, value); + + g_strfreev (environment_tuple); + } + + if (startup_id != NULL) { + g_app_launch_context_setenv (ctx, "DESKTOP_AUTOSTART_ID", startup_id); + } + +#ifdef ENABLE_SYSTEMD_JOURNAL + if (sd_booted () > 0) { + child_setup_func = (GSpawnChildSetupFunc) on_child_setup; + child_setup_data = app; + } +#endif + + handler = g_signal_connect (ctx, "launched", G_CALLBACK (app_launched), app); + success = g_desktop_app_info_launch_uris_as_manager (priv->app_info, + NULL, + ctx, + G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH, + child_setup_func, child_setup_data, + NULL, NULL, + &local_error); + g_signal_handler_disconnect (ctx, handler); + + if (success) { + if (priv->pid > 0) { + g_debug ("GsmAutostartApp: started pid:%d", priv->pid); + priv->child_watch_id = g_child_watch_add (priv->pid, + (GChildWatchFunc)app_exited, + app); + } + } else { + g_set_error (error, + GSM_APP_ERROR, + GSM_APP_ERROR_START, + "Unable to start application: %s", local_error->message); + g_error_free (local_error); + } + + return success; +} + +static void +start_notify (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GError *error; + GsmAutostartApp *app = GSM_AUTOSTART_APP (user_data); + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app); + + error = NULL; + + g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error); + + if (error != NULL) { + g_warning ("GsmAutostartApp: Error starting application: %s", error->message); + g_error_free (error); + } else { + g_debug ("GsmAutostartApp: Started application %s", priv->desktop_id); + } +} + +static gboolean +autostart_app_start_activate (GsmAutostartApp *app, + GError **error) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app); + const char *name; + char *path; + char *arguments; + GDBusConnection *bus; + GError *local_error; + + local_error = NULL; + bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &local_error); + if (local_error != NULL) { + g_warning ("error getting session bus: %s", local_error->message); + g_propagate_error (error, local_error); + return FALSE; + } + + name = gsm_app_peek_startup_id (GSM_APP (app)); + g_assert (name != NULL); + + path = g_desktop_app_info_get_string (priv->app_info, + GSM_AUTOSTART_APP_DBUS_PATH_KEY); + if (path == NULL) { + /* just pick one? */ + path = g_strdup ("/"); + } + + arguments = g_desktop_app_info_get_string (priv->app_info, + GSM_AUTOSTART_APP_DBUS_ARGS_KEY); + + g_dbus_connection_call (bus, + name, + path, + GSM_SESSION_CLIENT_DBUS_INTERFACE, + "Start", + g_variant_new ("(s)", arguments), + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, + start_notify, app); + g_object_unref (bus); + + return TRUE; +} + +static gboolean +gsm_autostart_app_start (GsmApp *app, + GError **error) +{ + GsmAutostartApp *self = GSM_AUTOSTART_APP (app); + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (self); + gboolean ret; + + g_return_val_if_fail (priv->app_info != NULL, FALSE); + + switch (priv->launch_type) { + case AUTOSTART_LAUNCH_SPAWN: + ret = autostart_app_start_spawn (self, error); + break; + case AUTOSTART_LAUNCH_ACTIVATE: + ret = autostart_app_start_activate (self, error); + break; + default: + g_assert_not_reached (); + break; + } + + return ret; +} + +static gboolean +gsm_autostart_app_restart (GsmApp *app, + GError **error) +{ + GError *local_error; + gboolean res; + + /* ignore stop errors - it is fine if it is already stopped */ + local_error = NULL; + res = gsm_app_stop (app, &local_error); + if (! res) { + g_debug ("GsmAutostartApp: Couldn't stop app: %s", local_error->message); + g_error_free (local_error); + } + + res = gsm_app_start (app, &local_error); + if (! res) { + g_propagate_error (error, local_error); + return FALSE; + } + + return TRUE; +} + +static gboolean +gsm_autostart_app_provides (GsmApp *app, + const char *service) +{ + gchar *provides_str; + char **provides; + gsize i; + GSList *l; + GsmAutostartApp *self = GSM_AUTOSTART_APP (app); + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (self); + + g_return_val_if_fail (GSM_IS_APP (app), FALSE); + + if (priv->app_info == NULL) { + return FALSE; + } + + for (l = priv->session_provides; l != NULL; l = l->next) { + if (!strcmp (l->data, service)) + return TRUE; + } + + provides_str = g_desktop_app_info_get_string (priv->app_info, + GSM_AUTOSTART_APP_PROVIDES_KEY); + if (!provides_str) { + return FALSE; + } + provides = g_strsplit (provides_str, ";", -1); + g_free (provides_str); + + for (i = 0; provides[i]; i++) { + if (!strcmp (provides[i], service)) { + g_strfreev (provides); + return TRUE; + } + } + + g_strfreev (provides); + + return FALSE; +} + +static char ** +gsm_autostart_app_get_provides (GsmApp *app) +{ + GsmAutostartApp *self = GSM_AUTOSTART_APP (app); + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (self); + gchar *provides_str; + char **provides; + gsize provides_len; + char **result; + gsize result_len; + int i; + GSList *l; + + g_return_val_if_fail (GSM_IS_APP (app), NULL); + + if (priv->app_info == NULL) { + return NULL; + } + + provides_str = g_desktop_app_info_get_string (priv->app_info, + GSM_AUTOSTART_APP_PROVIDES_KEY); + + if (provides_str == NULL) { + return NULL; + } + + provides = g_strsplit (provides_str, ";", -1); + provides_len = g_strv_length (provides); + g_free (provides_str); + + if (!priv->session_provides) { + return provides; + } + + result_len = provides_len + g_slist_length (priv->session_provides); + result = g_new (char *, result_len + 1); /* including last NULL */ + + for (i = 0; provides[i] != NULL; i++) + result[i] = provides[i]; + g_free (provides); + + for (l = priv->session_provides; l != NULL; l = l->next, i++) + result[i] = g_strdup (l->data); + + result[i] = NULL; + + g_assert (i == result_len); + + return result; +} + +void +gsm_autostart_app_add_provides (GsmAutostartApp *aapp, + const char *provides) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (aapp); + + g_return_if_fail (GSM_IS_AUTOSTART_APP (aapp)); + + priv->session_provides = g_slist_prepend (priv->session_provides, + g_strdup (provides)); +} + +static gboolean +gsm_autostart_app_has_autostart_condition (GsmApp *app, + const char *condition) +{ + GsmAutostartApp *self = GSM_AUTOSTART_APP (app); + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (self); + + g_return_val_if_fail (GSM_IS_APP (app), FALSE); + g_return_val_if_fail (condition != NULL, FALSE); + + if (priv->condition_string == NULL) { + return FALSE; + } + + if (strcmp (priv->condition_string, condition) == 0) { + return TRUE; + } + + return FALSE; +} + +static gboolean +gsm_autostart_app_get_autorestart (GsmApp *app) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (app)); + gboolean res; + gboolean autorestart; + + if (priv->app_info == NULL) { + return FALSE; + } + + autorestart = FALSE; + + res = g_desktop_app_info_has_key (priv->app_info, + GSM_AUTOSTART_APP_AUTORESTART_KEY); + if (res) { + autorestart = g_desktop_app_info_get_boolean (priv->app_info, + GSM_AUTOSTART_APP_AUTORESTART_KEY); + } + + return autorestart; +} + +static const char * +gsm_autostart_app_get_app_id (GsmApp *app) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (app)); + + if (priv->app_info == NULL) { + return NULL; + } + + return g_app_info_get_id (G_APP_INFO (priv->app_info)); +} + +static gboolean +gsm_autostart_app_initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + GsmAutostartApp *app = GSM_AUTOSTART_APP (initable); + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app); + + g_assert (priv->desktop_filename != NULL); + priv->app_info = g_desktop_app_info_new_from_filename (priv->desktop_filename); + if (priv->app_info == NULL) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Could not parse desktop file %s or it references a not found TryExec binary", priv->desktop_id); + return FALSE; + } + + load_desktop_file (app); + + return TRUE; +} + +static gboolean +gsm_autostart_app_save_to_keyfile (GsmApp *base_app, + GKeyFile *keyfile, + GError **error) +{ + GsmAutostartApp *app = GSM_AUTOSTART_APP (base_app); + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app); + char **provides = NULL; + char *dbus_name; + char *phase; + gboolean res; + + provides = gsm_app_get_provides (base_app); + if (provides != NULL) { + g_key_file_set_string_list (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + GSM_AUTOSTART_APP_PROVIDES_KEY, + (const char * const *) + provides, + g_strv_length (provides)); + g_strfreev (provides); + } + + phase = g_desktop_app_info_get_string (priv->app_info, + GSM_AUTOSTART_APP_PHASE_KEY); + if (phase != NULL) { + g_key_file_set_string (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + GSM_AUTOSTART_APP_PHASE_KEY, + phase); + g_free (phase); + } + + dbus_name = g_desktop_app_info_get_string (priv->app_info, + GSM_AUTOSTART_APP_DBUS_NAME_KEY); + if (dbus_name != NULL) { + g_key_file_set_string (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + GSM_AUTOSTART_APP_DBUS_NAME_KEY, + dbus_name); + g_free (dbus_name); + } + + res = g_desktop_app_info_has_key (priv->app_info, + GSM_AUTOSTART_APP_AUTORESTART_KEY); + if (res) { + g_key_file_set_boolean (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + GSM_AUTOSTART_APP_AUTORESTART_KEY, + g_desktop_app_info_get_boolean (priv->app_info, + GSM_AUTOSTART_APP_AUTORESTART_KEY)); + } + + res = g_desktop_app_info_has_key (priv->app_info, + GSM_AUTOSTART_APP_AUTORESTART_KEY); + if (res) { + char *autostart_condition; + + autostart_condition = g_desktop_app_info_get_string (priv->app_info, "AutostartCondition"); + + g_key_file_set_string (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + "AutostartCondition", + autostart_condition); + g_free (autostart_condition); + } + + return TRUE; +} + +static void +gsm_autostart_app_initable_iface_init (GInitableIface *iface) +{ + iface->init = gsm_autostart_app_initable_init; +} + +static void +gsm_autostart_app_class_init (GsmAutostartAppClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GsmAppClass *app_class = GSM_APP_CLASS (klass); + + object_class->set_property = gsm_autostart_app_set_property; + object_class->get_property = gsm_autostart_app_get_property; + object_class->dispose = gsm_autostart_app_dispose; + + app_class->impl_is_disabled = is_disabled; + app_class->impl_is_conditionally_disabled = is_conditionally_disabled; + app_class->impl_is_running = is_running; + app_class->impl_start = gsm_autostart_app_start; + app_class->impl_restart = gsm_autostart_app_restart; + app_class->impl_stop = gsm_autostart_app_stop; + app_class->impl_provides = gsm_autostart_app_provides; + app_class->impl_get_provides = gsm_autostart_app_get_provides; + 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_save_to_keyfile = gsm_autostart_app_save_to_keyfile; + + props[PROP_DESKTOP_FILENAME] = + g_param_spec_string ("desktop-filename", + "Desktop filename", + "Freedesktop .desktop file", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + props[PROP_MASK_SYSTEMD] = + g_param_spec_boolean ("mask-systemd", + "Mask if systemd started", + "Mask if GNOME systemd flag is set in desktop file", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (object_class, G_N_ELEMENTS (props), props); + + signals[CONDITION_CHANGED] = + g_signal_new ("condition-changed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsmAutostartAppClass, condition_changed), + NULL, NULL, NULL, + G_TYPE_NONE, + 1, + G_TYPE_BOOLEAN); +} + +GsmApp * +gsm_autostart_app_new (const char *desktop_file, + gboolean mask_systemd, + GError **error) +{ + return (GsmApp*) g_initable_new (GSM_TYPE_AUTOSTART_APP, NULL, error, + "desktop-filename", desktop_file, + "mask-systemd", mask_systemd, + NULL); +} |