summaryrefslogtreecommitdiffstats
path: root/gnome-session
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:49:37 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:49:37 +0000
commit35504d91654321ff2b378229ff13150f53d5aad2 (patch)
treecb85edefc751b37c8423d78c5e5888f42cc01e4b /gnome-session
parentInitial commit. (diff)
downloadgnome-session-35504d91654321ff2b378229ff13150f53d5aad2.tar.xz
gnome-session-35504d91654321ff2b378229ff13150f53d5aad2.zip
Adding upstream version 43.0.upstream/43.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gnome-session')
-rw-r--r--gnome-session/README69
-rw-r--r--gnome-session/gdm-log.c205
-rw-r--r--gnome-session/gdm-log.h50
-rwxr-xr-xgnome-session/gnome-session.in32
-rw-r--r--gnome-session/gsm-app.c593
-rw-r--r--gnome-session/gsm-app.h119
-rw-r--r--gnome-session/gsm-autostart-app.c1511
-rw-r--r--gnome-session/gsm-autostart-app.h61
-rw-r--r--gnome-session/gsm-client.c583
-rw-r--r--gnome-session/gsm-client.h140
-rw-r--r--gnome-session/gsm-consolekit.c972
-rw-r--r--gnome-session/gsm-consolekit.h59
-rw-r--r--gnome-session/gsm-dbus-client.c441
-rw-r--r--gnome-session/gsm-dbus-client.h35
-rw-r--r--gnome-session/gsm-fail-whale-dialog.c461
-rw-r--r--gnome-session/gsm-fail-whale-dialog.h36
-rw-r--r--gnome-session/gsm-fail-whale.c66
-rw-r--r--gnome-session/gsm-fail-whale.h34
-rw-r--r--gnome-session/gsm-icon-names.h28
-rw-r--r--gnome-session/gsm-inhibitor-flag.h36
-rw-r--r--gnome-session/gsm-inhibitor.c649
-rw-r--r--gnome-session/gsm-inhibitor.h88
-rw-r--r--gnome-session/gsm-manager-logout-mode.h34
-rw-r--r--gnome-session/gsm-manager.c3928
-rw-r--r--gnome-session/gsm-manager.h121
-rw-r--r--gnome-session/gsm-presence-flag.h33
-rw-r--r--gnome-session/gsm-presence.c542
-rw-r--r--gnome-session/gsm-presence.h75
-rw-r--r--gnome-session/gsm-process-helper.c113
-rw-r--r--gnome-session/gsm-process-helper.h32
-rw-r--r--gnome-session/gsm-session-fill.c334
-rw-r--r--gnome-session/gsm-session-fill.h32
-rw-r--r--gnome-session/gsm-session-save.c293
-rw-r--r--gnome-session/gsm-session-save.h34
-rw-r--r--gnome-session/gsm-shell-extensions.c199
-rw-r--r--gnome-session/gsm-shell-extensions.h61
-rw-r--r--gnome-session/gsm-shell.c507
-rw-r--r--gnome-session/gsm-shell.h87
-rw-r--r--gnome-session/gsm-store.c408
-rw-r--r--gnome-session/gsm-store.h96
-rw-r--r--gnome-session/gsm-system.c288
-rw-r--r--gnome-session/gsm-system.h130
-rw-r--r--gnome-session/gsm-systemd.c1183
-rw-r--r--gnome-session/gsm-systemd.h59
-rw-r--r--gnome-session/gsm-util.c863
-rw-r--r--gnome-session/gsm-util.h69
-rw-r--r--gnome-session/gsm-xsmp-client.c1359
-rw-r--r--gnome-session/gsm-xsmp-client.h89
-rw-r--r--gnome-session/gsm-xsmp-server.c746
-rw-r--r--gnome-session/gsm-xsmp-server.h40
-rw-r--r--gnome-session/main.c636
-rw-r--r--gnome-session/meson.build111
-rw-r--r--gnome-session/org.gnome.SessionManager.App.xml43
-rw-r--r--gnome-session/org.gnome.SessionManager.Client.xml73
-rw-r--r--gnome-session/org.gnome.SessionManager.ClientPrivate.xml123
-rw-r--r--gnome-session/org.gnome.SessionManager.Inhibitor.xml66
-rw-r--r--gnome-session/org.gnome.SessionManager.Presence.xml95
-rw-r--r--gnome-session/org.gnome.SessionManager.xml492
-rw-r--r--gnome-session/test-client-dbus.c265
-rw-r--r--gnome-session/test-inhibit.c197
-rw-r--r--gnome-session/test-process-helper.c56
61 files changed, 20180 insertions, 0 deletions
diff --git a/gnome-session/README b/gnome-session/README
new file mode 100644
index 0000000..f0bd4cc
--- /dev/null
+++ b/gnome-session/README
@@ -0,0 +1,69 @@
+See also http://live.gnome.org/SessionManagement/NewGnomeSession
+
+Startup
+-------
+
+main() creates the GsmSession object representing the session (either
+failsafe or normal). gsm_session_new() reads the appropriate autostart
+and session files to create a list of GsmApps to be started.
+(GsmAppAutostart represents an autostarted app, and GsmAppResumed
+represents an app resumed from the previous saved session.)
+
+Startup is divided into 7 phases (GsmManagerPhase):
+
+ * GSM_MANAGER_PHASE_STARTUP covers gnome-session's internal
+ startup, which also includes starting dbus-daemon (if
+ it's not already running). Gnome-session starts up those
+ explicitly because it needs them for its own purposes.
+
+ * GSM_MANAGER_PHASE_EARLY_INITIALIZATION is the first phase of
+ "normal" startup (ie, startup controlled by .desktop files
+ rather than hardcoding). It covers the possible installation of
+ files in $HOME by gnome-initial-setup and must be done before
+ other components such as gnome-keyring use those files.
+
+ * GSM_MANAGER_PHASE_INITIALIZATION covers low-level stuff like
+ gnome-settings-daemon helpers, that need to be
+ running very early (before any windows are displayed).
+
+ Apps in this phase can make use of a D-Bus interface
+ (org.gnome.SessionManager.Setenv) to set environment variables
+ in gnome-session's environment. This can be used for things like
+ $GTK_MODULES, $GNOME_KEYRING_SOCKET, etc
+
+ * GSM_MANAGER_PHASE_WINDOW_MANAGER includes window managers and
+ compositing managers, and anything else that has to be running
+ before any windows are mapped
+
+ * GSM_MANAGER_PHASE_PANEL includes anything that permanently takes
+ up screen real estate (via EWMH struts). This is the first phase
+ where things actually appear on the screen.
+
+ * GSM_MANAGER_PHASE_DESKTOP includes anything that draws directly
+ on the desktop (eg, nautilus).
+
+ * GSM_MANAGER_PHASE_APPLICATION is everything else (normal apps,
+ tray icons, etc)
+
+For each startup phase, GsmSession launches the appropriate GsmApps.
+When apps connect to the XSMP or D-Bus servers, GsmClients are created
+and added to the session. The session tries to map these clients to
+GsmApps. GsmApps signal when they register (via XSMP or SN) or exit,
+and GsmSession uses this to decide when the phase is complete.
+
+FIXME: after starting the session, we need to run the DiscardCommands
+of resumed apps.
+
+
+Running/Shutdown
+----------------
+
+GSM_MANAGER_PHASE_RUNNING is pretty similar to the old gnome-session;
+mostly it just tracks XSMP clients, and watches for
+SmRestartImmediately clients exiting (NOTE: THIS DOESN'T HAPPEN YET).
+
+GsmClient is in theory not XSMP-specific, but it's very very
+XSMP-like, and the shutdown procedure is also very XSMP-like. This is
+just because there's no way to do XSMP shutdown correctly otherwise.
+However, GsmClientDBus will still be able to present a more sane
+protocol to its clients than GsmClient presents to it.
diff --git a/gnome-session/gdm-log.c b/gnome-session/gdm-log.c
new file mode 100644
index 0000000..c0dca41
--- /dev/null
+++ b/gnome-session/gdm-log.c
@@ -0,0 +1,205 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
+ *
+ * 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 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/>.
+ *
+ * Authors: William Jon McCann <mccann@jhu.edu>
+ *
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <signal.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <syslog.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#include "gdm-log.h"
+
+static gboolean initialized = FALSE;
+static int syslog_levels = (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING);
+
+static void
+log_level_to_priority_and_prefix (GLogLevelFlags log_level,
+ int *priorityp,
+ const char **prefixp)
+{
+ int priority;
+ const char *prefix;
+
+ /* Process the message prefix and priority */
+ switch (log_level & G_LOG_LEVEL_MASK) {
+ case G_LOG_FLAG_FATAL:
+ priority = LOG_EMERG;
+ prefix = "FATAL";
+ break;
+ case G_LOG_LEVEL_ERROR:
+ priority = LOG_ERR;
+ prefix = "ERROR";
+ break;
+ case G_LOG_LEVEL_CRITICAL:
+ priority = LOG_CRIT;
+ prefix = "CRITICAL";
+ break;
+ case G_LOG_LEVEL_WARNING:
+ priority = LOG_WARNING;
+ prefix = "WARNING";
+ break;
+ case G_LOG_LEVEL_MESSAGE:
+ priority = LOG_NOTICE;
+ prefix = "MESSAGE";
+ break;
+ case G_LOG_LEVEL_INFO:
+ priority = LOG_INFO;
+ prefix = "INFO";
+ break;
+ case G_LOG_LEVEL_DEBUG:
+ /* if debug was requested then bump this up to ERROR
+ * to ensure it is seen in a log */
+ if (syslog_levels & G_LOG_LEVEL_DEBUG) {
+ priority = LOG_WARNING;
+ prefix = "DEBUG(+)";
+ } else {
+ priority = LOG_DEBUG;
+ prefix = "DEBUG";
+ }
+ break;
+ default:
+ priority = LOG_DEBUG;
+ prefix = "UNKNOWN";
+ break;
+ }
+
+ if (priorityp != NULL) {
+ *priorityp = priority;
+ }
+ if (prefixp != NULL) {
+ *prefixp = prefix;
+ }
+}
+
+void
+gdm_log_default_handler (const gchar *log_domain,
+ GLogLevelFlags log_level,
+ const gchar *message,
+ gpointer unused_data)
+{
+ GString *gstring;
+ int priority;
+ const char *level_prefix;
+ char *string;
+ gboolean do_log;
+ gboolean is_fatal;
+
+ is_fatal = (log_level & G_LOG_FLAG_FATAL) != 0;
+
+ do_log = (log_level & syslog_levels);
+ if (! do_log) {
+ return;
+ }
+
+ if (! initialized) {
+ gdm_log_init ();
+ }
+
+ log_level_to_priority_and_prefix (log_level,
+ &priority,
+ &level_prefix);
+
+ gstring = g_string_new (NULL);
+
+ if (log_domain != NULL) {
+ g_string_append (gstring, log_domain);
+ g_string_append_c (gstring, '-');
+ }
+ g_string_append (gstring, level_prefix);
+
+ g_string_append (gstring, ": ");
+ if (message == NULL) {
+ g_string_append (gstring, "(NULL) message");
+ } else {
+ g_string_append (gstring, message);
+ }
+ if (is_fatal) {
+ g_string_append (gstring, "\naborting...\n");
+ } else {
+ g_string_append (gstring, "\n");
+ }
+
+ string = g_string_free (gstring, FALSE);
+
+ syslog (priority, "%s", string);
+
+ g_free (string);
+}
+
+void
+gdm_log_toggle_debug (void)
+{
+ if (syslog_levels & G_LOG_LEVEL_DEBUG) {
+ g_debug ("Debugging disabled");
+ syslog_levels &= ~G_LOG_LEVEL_DEBUG;
+ } else {
+ syslog_levels |= G_LOG_LEVEL_DEBUG;
+ g_debug ("Debugging enabled");
+ }
+}
+
+void
+gdm_log_set_debug (gboolean debug)
+{
+ if (debug) {
+ syslog_levels |= G_LOG_LEVEL_DEBUG;
+ g_debug ("Enabling debugging");
+ } else {
+ g_debug ("Disabling debugging");
+ syslog_levels &= ~G_LOG_LEVEL_DEBUG;
+ }
+}
+
+void
+gdm_log_init (void)
+{
+ const char *prg_name;
+ int options;
+
+ g_log_set_default_handler (gdm_log_default_handler, NULL);
+
+ prg_name = g_get_prgname ();
+
+ options = LOG_PID;
+#ifdef LOG_PERROR
+ options |= LOG_PERROR;
+#endif
+
+ openlog (prg_name, options, LOG_DAEMON);
+
+ initialized = TRUE;
+}
+
+void
+gdm_log_shutdown (void)
+{
+ closelog ();
+ initialized = FALSE;
+}
+
diff --git a/gnome-session/gdm-log.h b/gnome-session/gdm-log.h
new file mode 100644
index 0000000..33daf40
--- /dev/null
+++ b/gnome-session/gdm-log.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
+ *
+ * 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 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/>.
+ *
+ * Authors: William Jon McCann <mccann@jhu.edu>
+ *
+ */
+
+#ifndef __GDM_LOG_H
+#define __GDM_LOG_H
+
+#include <stdarg.h>
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+void gdm_log_default_handler (const gchar *log_domain,
+ GLogLevelFlags log_level,
+ const gchar *message,
+ gpointer unused_data);
+void gdm_log_set_debug (gboolean debug);
+void gdm_log_toggle_debug (void);
+void gdm_log_init (void);
+void gdm_log_shutdown (void);
+
+/* compatibility */
+#define gdm_fail g_critical
+#define gdm_error g_warning
+#define gdm_info g_message
+#define gdm_debug g_debug
+
+#define gdm_assert g_assert
+#define gdm_assert_not_reached g_assert_not_reached
+
+G_END_DECLS
+
+#endif /* __GDM_LOG_H */
diff --git a/gnome-session/gnome-session.in b/gnome-session/gnome-session.in
new file mode 100755
index 0000000..ddd1a59
--- /dev/null
+++ b/gnome-session/gnome-session.in
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+if [ "x$XDG_SESSION_TYPE" = "xwayland" ] &&
+ [ "x$XDG_SESSION_CLASS" != "xgreeter" ] &&
+ [ -n "$SHELL" ] &&
+ grep -q "$SHELL" /etc/shells &&
+ ! (echo "$SHELL" | grep -q "false") &&
+ ! (echo "$SHELL" | grep -q "nologin"); then
+ if [ "$1" != '-l' ]; then
+ exec bash -c "exec -l '$SHELL' -c '$0 -l $*'"
+ else
+ shift
+ fi
+fi
+
+SETTING=$(G_MESSAGES_DEBUG='' gsettings get org.gnome.system.locale region)
+REGION=${SETTING#\'}
+REGION=${REGION%\'}
+
+if [ -n "$REGION" ]; then
+ unset LC_TIME LC_NUMERIC LC_MONETARY LC_MEASUREMENT LC_PAPER
+
+ if [ "$LANG" != "$REGION" ] ; then
+ export LC_TIME=$REGION
+ export LC_NUMERIC=$REGION
+ export LC_MONETARY=$REGION
+ export LC_MEASUREMENT=$REGION
+ export LC_PAPER=$REGION
+ fi
+fi
+
+exec @libexecdir@/gnome-session-binary "$@"
diff --git a/gnome-session/gsm-app.c b/gnome-session/gsm-app.c
new file mode 100644
index 0000000..8da7bd1
--- /dev/null
+++ b/gnome-session/gsm-app.c
@@ -0,0 +1,593 @@
+/* -*- 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/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib.h>
+#include <string.h>
+
+#include "gsm-app.h"
+#include "org.gnome.SessionManager.App.h"
+
+/* If a component crashes twice within a minute, we count that as a fatal error */
+#define _GSM_APP_RESPAWN_RATELIMIT_SECONDS 60
+
+typedef struct
+{
+ char *id;
+ char *app_id;
+ int phase;
+ char *startup_id;
+ gboolean registered;
+ GTimeVal last_restart_time;
+ GDBusConnection *connection;
+ GsmExportedApp *skeleton;
+} GsmAppPrivate;
+
+
+enum {
+ EXITED,
+ DIED,
+ LAST_SIGNAL
+};
+
+static guint32 app_serial = 1;
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+enum {
+ PROP_0,
+ PROP_ID,
+ PROP_STARTUP_ID,
+ PROP_PHASE,
+ PROP_REGISTERED,
+ LAST_PROP
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GsmApp, gsm_app, G_TYPE_OBJECT)
+
+GQuark
+gsm_app_error_quark (void)
+{
+ static GQuark ret = 0;
+ if (ret == 0) {
+ ret = g_quark_from_static_string ("gsm_app_error");
+ }
+
+ return ret;
+
+}
+
+static gboolean
+gsm_app_get_app_id (GsmExportedApp *skeleton,
+ GDBusMethodInvocation *invocation,
+ GsmApp *app)
+{
+ const gchar *id;
+
+ id = GSM_APP_GET_CLASS (app)->impl_get_app_id (app);
+ gsm_exported_app_complete_get_app_id (skeleton, invocation, id);
+
+ return TRUE;
+}
+
+static gboolean
+gsm_app_get_startup_id (GsmExportedApp *skeleton,
+ GDBusMethodInvocation *invocation,
+ GsmApp *app)
+{
+ GsmAppPrivate *priv = gsm_app_get_instance_private (app);
+ const gchar *id;
+
+ id = g_strdup (priv->startup_id);
+ gsm_exported_app_complete_get_startup_id (skeleton, invocation, id);
+
+ return TRUE;
+}
+
+static gboolean
+gsm_app_get_phase (GsmExportedApp *skeleton,
+ GDBusMethodInvocation *invocation,
+ GsmApp *app)
+{
+ GsmAppPrivate *priv = gsm_app_get_instance_private (app);
+
+ gsm_exported_app_complete_get_phase (skeleton, invocation, priv->phase);
+ return TRUE;
+}
+
+static guint32
+get_next_app_serial (void)
+{
+ guint32 serial;
+
+ serial = app_serial++;
+
+ if ((gint32)app_serial < 0) {
+ app_serial = 1;
+ }
+
+ return serial;
+}
+
+static gboolean
+register_app (GsmApp *app)
+{
+ GsmAppPrivate *priv = gsm_app_get_instance_private (app);
+ GError *error;
+ GsmExportedApp *skeleton;
+
+ error = NULL;
+ priv->connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+ if (error != NULL) {
+ g_critical ("error getting session bus: %s", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ skeleton = gsm_exported_app_skeleton_new ();
+ priv->skeleton = skeleton;
+ g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (skeleton),
+ priv->connection, priv->id,
+ &error);
+
+ if (error != NULL) {
+ g_critical ("error registering app on session bus: %s", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ g_signal_connect (skeleton, "handle-get-app-id",
+ G_CALLBACK (gsm_app_get_app_id), app);
+ g_signal_connect (skeleton, "handle-get-phase",
+ G_CALLBACK (gsm_app_get_phase), app);
+ g_signal_connect (skeleton, "handle-get-startup-id",
+ G_CALLBACK (gsm_app_get_startup_id), app);
+
+ return TRUE;
+}
+
+static GObject *
+gsm_app_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_properties)
+{
+ GsmApp *app;
+ GsmAppPrivate *priv;
+ gboolean res;
+
+ app = GSM_APP (G_OBJECT_CLASS (gsm_app_parent_class)->constructor (type,
+ n_construct_properties,
+ construct_properties));
+ priv = gsm_app_get_instance_private (app);
+
+ g_free (priv->id);
+ priv->id = g_strdup_printf ("/org/gnome/SessionManager/App%u", get_next_app_serial ());
+
+ res = register_app (app);
+ if (! res) {
+ g_warning ("Unable to register app with session bus");
+ }
+
+ return G_OBJECT (app);
+}
+
+static void
+gsm_app_init (GsmApp *app)
+{
+}
+
+static void
+gsm_app_set_phase (GsmApp *app,
+ int phase)
+{
+ GsmAppPrivate *priv = gsm_app_get_instance_private (app);
+
+ g_return_if_fail (GSM_IS_APP (app));
+
+ priv->phase = phase;
+}
+
+static void
+gsm_app_set_id (GsmApp *app,
+ const char *id)
+{
+ GsmAppPrivate *priv = gsm_app_get_instance_private (app);
+
+ g_return_if_fail (GSM_IS_APP (app));
+
+ g_free (priv->id);
+
+ priv->id = g_strdup (id);
+ g_object_notify (G_OBJECT (app), "id");
+
+}
+static void
+gsm_app_set_startup_id (GsmApp *app,
+ const char *startup_id)
+{
+ GsmAppPrivate *priv = gsm_app_get_instance_private (app);
+
+ g_return_if_fail (GSM_IS_APP (app));
+
+ g_free (priv->startup_id);
+
+ priv->startup_id = g_strdup (startup_id);
+ g_object_notify (G_OBJECT (app), "startup-id");
+
+}
+
+static void
+gsm_app_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GsmApp *app = GSM_APP (object);
+
+ switch (prop_id) {
+ case PROP_STARTUP_ID:
+ gsm_app_set_startup_id (app, g_value_get_string (value));
+ break;
+ case PROP_ID:
+ gsm_app_set_id (app, g_value_get_string (value));
+ break;
+ case PROP_PHASE:
+ gsm_app_set_phase (app, g_value_get_int (value));
+ break;
+ case PROP_REGISTERED:
+ gsm_app_set_registered (app, g_value_get_boolean (value));
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+gsm_app_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GsmApp *app = GSM_APP (object);
+ GsmAppPrivate *priv = gsm_app_get_instance_private (app);
+
+ switch (prop_id) {
+ case PROP_STARTUP_ID:
+ g_value_set_string (value, priv->startup_id);
+ break;
+ case PROP_ID:
+ g_value_set_string (value, priv->id);
+ break;
+ case PROP_PHASE:
+ g_value_set_int (value, priv->phase);
+ break;
+ case PROP_REGISTERED:
+ g_value_set_boolean (value, priv->registered);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+gsm_app_dispose (GObject *object)
+{
+ GsmApp *app = GSM_APP (object);
+ GsmAppPrivate *priv = gsm_app_get_instance_private (app);
+
+ g_free (priv->startup_id);
+ priv->startup_id = NULL;
+
+ g_free (priv->id);
+ priv->id = NULL;
+
+ if (priv->skeleton != NULL) {
+ g_dbus_interface_skeleton_unexport_from_connection (G_DBUS_INTERFACE_SKELETON (priv->skeleton),
+ priv->connection);
+ g_clear_object (&priv->skeleton);
+ }
+
+ g_clear_object (&priv->connection);
+
+ G_OBJECT_CLASS (gsm_app_parent_class)->dispose (object);
+}
+
+static void
+gsm_app_class_init (GsmAppClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gsm_app_set_property;
+ object_class->get_property = gsm_app_get_property;
+ object_class->dispose = gsm_app_dispose;
+ object_class->constructor = gsm_app_constructor;
+
+ klass->impl_start = NULL;
+ klass->impl_get_app_id = NULL;
+ klass->impl_get_autorestart = NULL;
+ klass->impl_provides = NULL;
+ klass->impl_get_provides = NULL;
+ klass->impl_is_running = NULL;
+
+ g_object_class_install_property (object_class,
+ PROP_PHASE,
+ g_param_spec_int ("phase",
+ "Phase",
+ "Phase",
+ -1,
+ G_MAXINT,
+ -1,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_ID,
+ g_param_spec_string ("id",
+ "ID",
+ "ID",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+ g_object_class_install_property (object_class,
+ PROP_STARTUP_ID,
+ g_param_spec_string ("startup-id",
+ "startup ID",
+ "Session management startup ID",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class,
+ PROP_REGISTERED,
+ g_param_spec_boolean ("registered",
+ "Registered",
+ "Registered",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ signals[EXITED] =
+ g_signal_new ("exited",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmAppClass, exited),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1, G_TYPE_UCHAR);
+ signals[DIED] =
+ g_signal_new ("died",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmAppClass, died),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1, G_TYPE_INT);
+}
+
+const char *
+gsm_app_peek_id (GsmApp *app)
+{
+ GsmAppPrivate *priv = gsm_app_get_instance_private (app);
+
+ return priv->id;
+}
+
+const char *
+gsm_app_peek_app_id (GsmApp *app)
+{
+ return GSM_APP_GET_CLASS (app)->impl_get_app_id (app);
+}
+
+const char *
+gsm_app_peek_startup_id (GsmApp *app)
+{
+ GsmAppPrivate *priv = gsm_app_get_instance_private (app);
+
+ return priv->startup_id;
+}
+
+/**
+ * gsm_app_peek_phase:
+ * @app: a %GsmApp
+ *
+ * Returns @app's startup phase.
+ *
+ * Return value: @app's startup phase
+ **/
+GsmManagerPhase
+gsm_app_peek_phase (GsmApp *app)
+{
+ GsmAppPrivate *priv = gsm_app_get_instance_private (app);
+
+ g_return_val_if_fail (GSM_IS_APP (app), GSM_MANAGER_PHASE_APPLICATION);
+
+ return priv->phase;
+}
+
+gboolean
+gsm_app_peek_is_disabled (GsmApp *app)
+{
+ g_return_val_if_fail (GSM_IS_APP (app), FALSE);
+
+ if (GSM_APP_GET_CLASS (app)->impl_is_disabled) {
+ return GSM_APP_GET_CLASS (app)->impl_is_disabled (app);
+ } else {
+ return FALSE;
+ }
+}
+
+gboolean
+gsm_app_peek_is_conditionally_disabled (GsmApp *app)
+{
+ g_return_val_if_fail (GSM_IS_APP (app), FALSE);
+
+ if (GSM_APP_GET_CLASS (app)->impl_is_conditionally_disabled) {
+ return GSM_APP_GET_CLASS (app)->impl_is_conditionally_disabled (app);
+ } else {
+ return FALSE;
+ }
+}
+
+gboolean
+gsm_app_is_running (GsmApp *app)
+{
+ g_return_val_if_fail (GSM_IS_APP (app), FALSE);
+
+ if (GSM_APP_GET_CLASS (app)->impl_is_running) {
+ return GSM_APP_GET_CLASS (app)->impl_is_running (app);
+ } else {
+ return FALSE;
+ }
+}
+
+gboolean
+gsm_app_peek_autorestart (GsmApp *app)
+{
+ g_return_val_if_fail (GSM_IS_APP (app), FALSE);
+
+ if (GSM_APP_GET_CLASS (app)->impl_get_autorestart) {
+ return GSM_APP_GET_CLASS (app)->impl_get_autorestart (app);
+ } else {
+ return FALSE;
+ }
+}
+
+gboolean
+gsm_app_provides (GsmApp *app, const char *service)
+{
+ if (GSM_APP_GET_CLASS (app)->impl_provides) {
+ return GSM_APP_GET_CLASS (app)->impl_provides (app, service);
+ } else {
+ return FALSE;
+ }
+}
+
+char **
+gsm_app_get_provides (GsmApp *app)
+{
+ if (GSM_APP_GET_CLASS (app)->impl_get_provides) {
+ return GSM_APP_GET_CLASS (app)->impl_get_provides (app);
+ } else {
+ return NULL;
+ }
+}
+
+gboolean
+gsm_app_has_autostart_condition (GsmApp *app,
+ const char *condition)
+{
+
+ if (GSM_APP_GET_CLASS (app)->impl_has_autostart_condition) {
+ return GSM_APP_GET_CLASS (app)->impl_has_autostart_condition (app, condition);
+ } else {
+ return FALSE;
+ }
+}
+
+gboolean
+gsm_app_start (GsmApp *app,
+ GError **error)
+{
+ GsmAppPrivate *priv = gsm_app_get_instance_private (app);
+
+ g_debug ("Starting app: %s", priv->id);
+ return GSM_APP_GET_CLASS (app)->impl_start (app, error);
+}
+
+gboolean
+gsm_app_restart (GsmApp *app,
+ GError **error)
+{
+ GsmAppPrivate *priv = gsm_app_get_instance_private (app);
+ GTimeVal current_time;
+ g_debug ("Re-starting app: %s", priv->id);
+
+ g_get_current_time (&current_time);
+ if (priv->last_restart_time.tv_sec > 0
+ && (current_time.tv_sec - priv->last_restart_time.tv_sec) < _GSM_APP_RESPAWN_RATELIMIT_SECONDS) {
+ g_warning ("App '%s' respawning too quickly", gsm_app_peek_app_id (app));
+ g_set_error (error,
+ GSM_APP_ERROR,
+ GSM_APP_ERROR_RESTART_LIMIT,
+ "Component '%s' crashing too quickly",
+ gsm_app_peek_app_id (app));
+ return FALSE;
+ }
+ priv->last_restart_time = current_time;
+
+ return GSM_APP_GET_CLASS (app)->impl_restart (app, error);
+}
+
+gboolean
+gsm_app_stop (GsmApp *app,
+ GError **error)
+{
+ return GSM_APP_GET_CLASS (app)->impl_stop (app, error);
+}
+
+void
+gsm_app_exited (GsmApp *app,
+ guchar exit_code)
+{
+ g_return_if_fail (GSM_IS_APP (app));
+
+ g_signal_emit (app, signals[EXITED], 0, exit_code);
+}
+
+void
+gsm_app_died (GsmApp *app,
+ int signal)
+{
+ g_return_if_fail (GSM_IS_APP (app));
+
+ g_signal_emit (app, signals[DIED], 0, signal);
+}
+
+gboolean
+gsm_app_get_registered (GsmApp *app)
+{
+ GsmAppPrivate *priv = gsm_app_get_instance_private (app);
+
+ g_return_val_if_fail (GSM_IS_APP (app), FALSE);
+
+ return priv->registered;
+}
+
+void
+gsm_app_set_registered (GsmApp *app,
+ gboolean registered)
+{
+ GsmAppPrivate *priv = gsm_app_get_instance_private (app);
+
+ g_return_if_fail (GSM_IS_APP (app));
+
+ if (priv->registered != registered) {
+ priv->registered = registered;
+ g_object_notify (G_OBJECT (app), "registered");
+ }
+}
+
+gboolean
+gsm_app_save_to_keyfile (GsmApp *app,
+ GKeyFile *keyfile,
+ GError **error)
+{
+ GsmAppPrivate *priv = gsm_app_get_instance_private (app);
+
+ g_debug ("Saving app: %s", priv->id);
+ return GSM_APP_GET_CLASS (app)->impl_save_to_keyfile (app, keyfile, error);
+}
diff --git a/gnome-session/gsm-app.h b/gnome-session/gsm-app.h
new file mode 100644
index 0000000..2808a21
--- /dev/null
+++ b/gnome-session/gsm-app.h
@@ -0,0 +1,119 @@
+/* -*- 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/>.
+ */
+
+#ifndef __GSM_APP_H__
+#define __GSM_APP_H__
+
+#include <glib-object.h>
+#include <sys/types.h>
+
+
+#include "gsm-manager.h"
+#include "gsm-client.h"
+
+G_BEGIN_DECLS
+
+#define GSM_TYPE_APP (gsm_app_get_type ())
+G_DECLARE_DERIVABLE_TYPE (GsmApp, gsm_app, GSM, APP, GObject)
+
+struct _GsmAppClass
+{
+ GObjectClass parent_class;
+
+ /* signals */
+ void (*exited) (GsmApp *app,
+ guchar exit_code);
+ void (*died) (GsmApp *app,
+ int signal);
+
+ /* virtual methods */
+ gboolean (*impl_start) (GsmApp *app,
+ GError **error);
+ gboolean (*impl_restart) (GsmApp *app,
+ GError **error);
+ gboolean (*impl_stop) (GsmApp *app,
+ GError **error);
+ gboolean (*impl_provides) (GsmApp *app,
+ const char *service);
+ char ** (*impl_get_provides) (GsmApp *app);
+ gboolean (*impl_has_autostart_condition) (GsmApp *app,
+ const char *service);
+ gboolean (*impl_is_running) (GsmApp *app);
+
+ gboolean (*impl_get_autorestart) (GsmApp *app);
+ const char *(*impl_get_app_id) (GsmApp *app);
+ gboolean (*impl_is_disabled) (GsmApp *app);
+ gboolean (*impl_is_conditionally_disabled) (GsmApp *app);
+
+ gboolean (*impl_save_to_keyfile) (GsmApp *app,
+ GKeyFile *keyfile,
+ GError **error);
+};
+
+typedef enum
+{
+ GSM_APP_ERROR_GENERAL = 0,
+ GSM_APP_ERROR_RESTART_LIMIT,
+ GSM_APP_ERROR_START,
+ GSM_APP_ERROR_STOP,
+ GSM_APP_NUM_ERRORS
+} GsmAppError;
+
+#define GSM_APP_ERROR gsm_app_error_quark ()
+
+GQuark gsm_app_error_quark (void);
+
+gboolean gsm_app_peek_autorestart (GsmApp *app);
+
+const char *gsm_app_peek_id (GsmApp *app);
+const char *gsm_app_peek_app_id (GsmApp *app);
+const char *gsm_app_peek_startup_id (GsmApp *app);
+GsmManagerPhase gsm_app_peek_phase (GsmApp *app);
+gboolean gsm_app_peek_is_disabled (GsmApp *app);
+gboolean gsm_app_peek_is_conditionally_disabled (GsmApp *app);
+
+gboolean gsm_app_start (GsmApp *app,
+ GError **error);
+gboolean gsm_app_restart (GsmApp *app,
+ GError **error);
+gboolean gsm_app_stop (GsmApp *app,
+ GError **error);
+gboolean gsm_app_is_running (GsmApp *app);
+
+void gsm_app_exited (GsmApp *app,
+ guchar exit_code);
+void gsm_app_died (GsmApp *app,
+ int signal);
+
+gboolean gsm_app_provides (GsmApp *app,
+ const char *service);
+char **gsm_app_get_provides (GsmApp *app);
+gboolean gsm_app_has_autostart_condition (GsmApp *app,
+ const char *condition);
+gboolean gsm_app_get_registered (GsmApp *app);
+void gsm_app_set_registered (GsmApp *app,
+ gboolean registered);
+
+gboolean gsm_app_save_to_keyfile (GsmApp *app,
+ GKeyFile *keyfile,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* __GSM_APP_H__ */
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);
+}
diff --git a/gnome-session/gsm-autostart-app.h b/gnome-session/gsm-autostart-app.h
new file mode 100644
index 0000000..a7a5c28
--- /dev/null
+++ b/gnome-session/gsm-autostart-app.h
@@ -0,0 +1,61 @@
+/* -*- 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/>.
+ */
+
+#ifndef __GSM_AUTOSTART_APP_H__
+#define __GSM_AUTOSTART_APP_H__
+
+#include "gsm-app.h"
+
+#include <X11/SM/SMlib.h>
+
+G_BEGIN_DECLS
+
+#define GSM_TYPE_AUTOSTART_APP (gsm_autostart_app_get_type ())
+G_DECLARE_DERIVABLE_TYPE (GsmAutostartApp, gsm_autostart_app, GSM, AUTOSTART_APP, GsmApp)
+
+struct _GsmAutostartAppClass
+{
+ GsmAppClass parent_class;
+
+ /* signals */
+ void (*condition_changed) (GsmApp *app,
+ gboolean condition);
+};
+
+GsmApp *gsm_autostart_app_new (const char *desktop_file,
+ gboolean mask_systemd,
+ GError **error);
+
+void gsm_autostart_app_add_provides (GsmAutostartApp *aapp,
+ const char *provides);
+
+#define GSM_AUTOSTART_APP_SYSTEMD_KEY "X-GNOME-HiddenUnderSystemd"
+#define GSM_AUTOSTART_APP_ENABLED_KEY "X-GNOME-Autostart-enabled"
+#define GSM_AUTOSTART_APP_PHASE_KEY "X-GNOME-Autostart-Phase"
+#define GSM_AUTOSTART_APP_PROVIDES_KEY "X-GNOME-Provides"
+#define GSM_AUTOSTART_APP_STARTUP_ID_KEY "X-GNOME-Autostart-startup-id"
+#define GSM_AUTOSTART_APP_AUTORESTART_KEY "X-GNOME-AutoRestart"
+#define GSM_AUTOSTART_APP_DBUS_NAME_KEY "X-GNOME-DBus-Name"
+#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"
+
+G_END_DECLS
+
+#endif /* __GSM_AUTOSTART_APP_H__ */
diff --git a/gnome-session/gsm-client.c b/gnome-session/gsm-client.c
new file mode 100644
index 0000000..25f0bf7
--- /dev/null
+++ b/gnome-session/gsm-client.c
@@ -0,0 +1,583 @@
+/* -*- 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 "gsm-client.h"
+#include "org.gnome.SessionManager.Client.h"
+
+static guint32 client_serial = 1;
+
+typedef struct
+{
+ char *id;
+ char *startup_id;
+ char *app_id;
+ guint status;
+ GsmExportedClient *skeleton;
+ GDBusConnection *connection;
+} GsmClientPrivate;
+
+typedef enum {
+ PROP_STARTUP_ID = 1,
+ PROP_APP_ID,
+ PROP_STATUS,
+} GsmClientProperty;
+
+static GParamSpec *props[PROP_STATUS + 1] = { NULL, };
+
+enum {
+ DISCONNECTED,
+ END_SESSION_RESPONSE,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GsmClient, gsm_client, G_TYPE_OBJECT)
+
+#define GSM_CLIENT_DBUS_IFACE "org.gnome.SessionManager.Client"
+
+static const GDBusErrorEntry gsm_client_error_entries[] = {
+ { GSM_CLIENT_ERROR_GENERAL, GSM_CLIENT_DBUS_IFACE ".GeneralError" },
+ { GSM_CLIENT_ERROR_NOT_REGISTERED, GSM_CLIENT_DBUS_IFACE ".NotRegistered" }
+};
+
+GQuark
+gsm_client_error_quark (void)
+{
+ static volatile gsize quark_volatile = 0;
+
+ g_dbus_error_register_error_domain ("gsm_client_error",
+ &quark_volatile,
+ gsm_client_error_entries,
+ G_N_ELEMENTS (gsm_client_error_entries));
+ return quark_volatile;
+}
+
+static guint32
+get_next_client_serial (void)
+{
+ guint32 serial;
+
+ serial = client_serial++;
+
+ if ((gint32)client_serial < 0) {
+ client_serial = 1;
+ }
+
+ return serial;
+}
+
+static gboolean
+gsm_client_get_startup_id (GsmExportedClient *skeleton,
+ GDBusMethodInvocation *invocation,
+ GsmClient *client)
+{
+ GsmClientPrivate *priv = gsm_client_get_instance_private (client);
+
+ gsm_exported_client_complete_get_startup_id (skeleton, invocation, priv->startup_id);
+ return TRUE;
+}
+
+static gboolean
+gsm_client_get_app_id (GsmExportedClient *skeleton,
+ GDBusMethodInvocation *invocation,
+ GsmClient *client)
+{
+ GsmClientPrivate *priv = gsm_client_get_instance_private (client);
+
+ gsm_exported_client_complete_get_app_id (skeleton, invocation, priv->app_id);
+ return TRUE;
+}
+
+static gboolean
+gsm_client_get_restart_style_hint (GsmExportedClient *skeleton,
+ GDBusMethodInvocation *invocation,
+ GsmClient *client)
+{
+ guint hint;
+
+ hint = GSM_CLIENT_GET_CLASS (client)->impl_get_restart_style_hint (client);
+ gsm_exported_client_complete_get_restart_style_hint (skeleton, invocation, hint);
+ return TRUE;
+}
+
+static gboolean
+gsm_client_get_status (GsmExportedClient *skeleton,
+ GDBusMethodInvocation *invocation,
+ GsmClient *client)
+{
+ GsmClientPrivate *priv = gsm_client_get_instance_private (client);
+
+ gsm_exported_client_complete_get_status (skeleton, invocation, priv->status);
+ return TRUE;
+}
+
+static gboolean
+gsm_client_get_unix_process_id (GsmExportedClient *skeleton,
+ GDBusMethodInvocation *invocation,
+ GsmClient *client)
+{
+ guint pid;
+
+ pid = GSM_CLIENT_GET_CLASS (client)->impl_get_unix_process_id (client);
+ gsm_exported_client_complete_get_unix_process_id (skeleton, invocation, pid);
+ return TRUE;
+}
+
+static gboolean
+gsm_client_stop_dbus (GsmExportedClient *skeleton,
+ GDBusMethodInvocation *invocation,
+ GsmClient *client)
+{
+ GError *error = NULL;
+ gsm_client_stop (client, &error);
+
+ if (error != NULL) {
+ g_dbus_method_invocation_take_error (invocation, error);
+ } else {
+ gsm_exported_client_complete_stop (skeleton, invocation);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+register_client (GsmClient *client)
+{
+ GsmClientPrivate *priv = gsm_client_get_instance_private (client);
+ GError *error = NULL;
+ GsmExportedClient *skeleton;
+
+ priv->connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+ if (priv->connection == NULL) {
+ g_critical ("error getting session bus: %s", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ skeleton = gsm_exported_client_skeleton_new ();
+ priv->skeleton = skeleton;
+ g_debug ("exporting client to object path: %s", priv->id);
+ g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (skeleton),
+ priv->connection,
+ priv->id, &error);
+
+ if (error != NULL) {
+ g_critical ("error exporting client on session bus: %s", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ g_signal_connect (skeleton, "handle-get-app-id",
+ G_CALLBACK (gsm_client_get_app_id), client);
+ g_signal_connect (skeleton, "handle-get-restart-style-hint",
+ G_CALLBACK (gsm_client_get_restart_style_hint), client);
+ g_signal_connect (skeleton, "handle-get-startup-id",
+ G_CALLBACK (gsm_client_get_startup_id), client);
+ g_signal_connect (skeleton, "handle-get-status",
+ G_CALLBACK (gsm_client_get_status), client);
+ g_signal_connect (skeleton, "handle-get-unix-process-id",
+ G_CALLBACK (gsm_client_get_unix_process_id), client);
+ g_signal_connect (skeleton, "handle-stop",
+ G_CALLBACK (gsm_client_stop_dbus), client);
+
+ return TRUE;
+}
+
+static GObject *
+gsm_client_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_properties)
+{
+ GsmClientPrivate *priv;
+ GsmClient *client;
+ gboolean res;
+
+ client = GSM_CLIENT (G_OBJECT_CLASS (gsm_client_parent_class)->constructor (type,
+ n_construct_properties,
+ construct_properties));
+ priv = gsm_client_get_instance_private (client);
+
+ g_free (priv->id);
+ priv->id = g_strdup_printf ("/org/gnome/SessionManager/Client%u", get_next_client_serial ());
+
+ res = register_client (client);
+ if (! res) {
+ g_warning ("Unable to register client with session bus");
+ }
+
+ return G_OBJECT (client);
+}
+
+static void
+gsm_client_init (GsmClient *client)
+{
+}
+
+static void
+gsm_client_finalize (GObject *object)
+{
+ GsmClient *client;
+ GsmClientPrivate *priv;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GSM_IS_CLIENT (object));
+
+ client = GSM_CLIENT (object);
+ priv = gsm_client_get_instance_private (client);
+
+ g_return_if_fail (priv != NULL);
+
+ g_free (priv->id);
+ g_free (priv->startup_id);
+ g_free (priv->app_id);
+
+ if (priv->skeleton != NULL) {
+ g_dbus_interface_skeleton_unexport_from_connection (G_DBUS_INTERFACE_SKELETON (priv->skeleton),
+ priv->connection);
+ g_clear_object (&priv->skeleton);
+ }
+
+ g_clear_object (&priv->connection);
+
+ G_OBJECT_CLASS (gsm_client_parent_class)->finalize (object);
+}
+
+void
+gsm_client_set_status (GsmClient *client,
+ guint status)
+{
+ GsmClientPrivate *priv = gsm_client_get_instance_private (client);
+
+ g_return_if_fail (GSM_IS_CLIENT (client));
+ if (priv->status != status) {
+ priv->status = status;
+ g_object_notify_by_pspec (G_OBJECT (client), props[PROP_STATUS]);
+ }
+}
+
+static void
+gsm_client_set_startup_id (GsmClient *client,
+ const char *startup_id)
+{
+ GsmClientPrivate *priv = gsm_client_get_instance_private (client);
+
+ g_return_if_fail (GSM_IS_CLIENT (client));
+
+ g_free (priv->startup_id);
+
+ if (startup_id != NULL) {
+ priv->startup_id = g_strdup (startup_id);
+ } else {
+ priv->startup_id = g_strdup ("");
+ }
+ g_object_notify_by_pspec (G_OBJECT (client), props[PROP_STARTUP_ID]);
+}
+
+void
+gsm_client_set_app_id (GsmClient *client,
+ const char *app_id)
+{
+ GsmClientPrivate *priv = gsm_client_get_instance_private (client);
+
+ g_return_if_fail (GSM_IS_CLIENT (client));
+
+ g_free (priv->app_id);
+
+ if (app_id != NULL) {
+ priv->app_id = g_strdup (app_id);
+ } else {
+ priv->app_id = g_strdup ("");
+ }
+ g_object_notify_by_pspec (G_OBJECT (client), props[PROP_APP_ID]);
+}
+
+static void
+gsm_client_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GsmClient *self;
+
+ self = GSM_CLIENT (object);
+
+ switch ((GsmClientProperty) prop_id) {
+ case PROP_STARTUP_ID:
+ gsm_client_set_startup_id (self, g_value_get_string (value));
+ break;
+ case PROP_APP_ID:
+ gsm_client_set_app_id (self, g_value_get_string (value));
+ break;
+ case PROP_STATUS:
+ gsm_client_set_status (self, g_value_get_uint (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gsm_client_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GsmClient *self = GSM_CLIENT (object);
+ GsmClientPrivate *priv = gsm_client_get_instance_private (self);
+
+ switch ((GsmClientProperty) prop_id) {
+ case PROP_STARTUP_ID:
+ g_value_set_string (value, priv->startup_id);
+ break;
+ case PROP_APP_ID:
+ g_value_set_string (value, priv->app_id);
+ break;
+ case PROP_STATUS:
+ g_value_set_uint (value, priv->status);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+default_stop (GsmClient *client,
+ GError **error)
+{
+ g_return_val_if_fail (GSM_IS_CLIENT (client), FALSE);
+
+ g_warning ("Stop not implemented");
+
+ return TRUE;
+}
+
+static void
+gsm_client_dispose (GObject *object)
+{
+ GsmClient *client;
+ GsmClientPrivate *priv;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GSM_IS_CLIENT (object));
+
+ client = GSM_CLIENT (object);
+ priv = gsm_client_get_instance_private (client);
+
+ g_debug ("GsmClient: disposing %s", priv->id);
+
+ G_OBJECT_CLASS (gsm_client_parent_class)->dispose (object);
+}
+
+static void
+gsm_client_class_init (GsmClientClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = gsm_client_get_property;
+ object_class->set_property = gsm_client_set_property;
+ object_class->constructor = gsm_client_constructor;
+ object_class->finalize = gsm_client_finalize;
+ object_class->dispose = gsm_client_dispose;
+
+ klass->impl_stop = default_stop;
+
+ signals[DISCONNECTED] =
+ g_signal_new ("disconnected",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmClientClass, disconnected),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 0);
+ signals[END_SESSION_RESPONSE] =
+ g_signal_new ("end-session-response",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmClientClass, end_session_response),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 4, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_STRING);
+
+ props[PROP_STARTUP_ID] =
+ g_param_spec_string ("startup-id",
+ "startup-id",
+ "startup-id",
+ "",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+ props[PROP_APP_ID] =
+ g_param_spec_string ("app-id",
+ "app-id",
+ "app-id",
+ "",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+ props[PROP_STATUS] =
+ g_param_spec_uint ("status",
+ "status",
+ "status",
+ 0,
+ G_MAXINT,
+ GSM_CLIENT_UNREGISTERED,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (object_class, G_N_ELEMENTS (props), props);
+}
+
+const char *
+gsm_client_peek_id (GsmClient *client)
+{
+ GsmClientPrivate *priv = gsm_client_get_instance_private (client);
+
+ g_return_val_if_fail (GSM_IS_CLIENT (client), NULL);
+
+ return priv->id;
+}
+
+/**
+ * gsm_client_peek_app_id:
+ * @client: a #GsmClient.
+ *
+ * Note that the application ID might not be known; this happens when for XSMP
+ * clients that we did not start ourselves, for instance.
+ *
+ * Returns: the application ID of the client, or %NULL if no such ID is
+ * known. The string is owned by @client.
+ **/
+const char *
+gsm_client_peek_app_id (GsmClient *client)
+{
+ GsmClientPrivate *priv = gsm_client_get_instance_private (client);
+
+ g_return_val_if_fail (GSM_IS_CLIENT (client), NULL);
+
+ return priv->app_id;
+}
+
+const char *
+gsm_client_peek_startup_id (GsmClient *client)
+{
+ GsmClientPrivate *priv = gsm_client_get_instance_private (client);
+
+ g_return_val_if_fail (GSM_IS_CLIENT (client), NULL);
+
+ return priv->startup_id;
+}
+
+guint
+gsm_client_peek_status (GsmClient *client)
+{
+ GsmClientPrivate *priv = gsm_client_get_instance_private (client);
+
+ g_return_val_if_fail (GSM_IS_CLIENT (client), GSM_CLIENT_UNREGISTERED);
+
+ return priv->status;
+}
+
+guint
+gsm_client_peek_restart_style_hint (GsmClient *client)
+{
+ g_return_val_if_fail (GSM_IS_CLIENT (client), GSM_CLIENT_RESTART_NEVER);
+
+ return GSM_CLIENT_GET_CLASS (client)->impl_get_restart_style_hint (client);
+}
+
+/**
+ * gsm_client_get_app_name:
+ * @client: a #GsmClient.
+ *
+ * Returns: a copy of the application name of the client, or %NULL if no such
+ * name is known.
+ **/
+char *
+gsm_client_get_app_name (GsmClient *client)
+{
+ g_return_val_if_fail (GSM_IS_CLIENT (client), NULL);
+
+ return GSM_CLIENT_GET_CLASS (client)->impl_get_app_name (client);
+}
+
+gboolean
+gsm_client_cancel_end_session (GsmClient *client,
+ GError **error)
+{
+ g_return_val_if_fail (GSM_IS_CLIENT (client), FALSE);
+
+ return GSM_CLIENT_GET_CLASS (client)->impl_cancel_end_session (client, error);
+}
+
+
+gboolean
+gsm_client_query_end_session (GsmClient *client,
+ GsmClientEndSessionFlag flags,
+ GError **error)
+{
+ g_return_val_if_fail (GSM_IS_CLIENT (client), FALSE);
+
+ return GSM_CLIENT_GET_CLASS (client)->impl_query_end_session (client, flags, error);
+}
+
+gboolean
+gsm_client_end_session (GsmClient *client,
+ GsmClientEndSessionFlag flags,
+ GError **error)
+{
+ g_return_val_if_fail (GSM_IS_CLIENT (client), FALSE);
+
+ return GSM_CLIENT_GET_CLASS (client)->impl_end_session (client, flags, error);
+}
+
+gboolean
+gsm_client_stop (GsmClient *client,
+ GError **error)
+{
+ g_return_val_if_fail (GSM_IS_CLIENT (client), FALSE);
+
+ return GSM_CLIENT_GET_CLASS (client)->impl_stop (client, error);
+}
+
+void
+gsm_client_disconnected (GsmClient *client)
+{
+ g_signal_emit (client, signals[DISCONNECTED], 0);
+}
+
+GKeyFile *
+gsm_client_save (GsmClient *client,
+ GsmApp *app,
+ GError **error)
+{
+ g_return_val_if_fail (GSM_IS_CLIENT (client), NULL);
+
+ return GSM_CLIENT_GET_CLASS (client)->impl_save (client, app, error);
+}
+
+void
+gsm_client_end_session_response (GsmClient *client,
+ gboolean is_ok,
+ gboolean do_last,
+ gboolean cancel,
+ const char *reason)
+{
+ g_signal_emit (client, signals[END_SESSION_RESPONSE], 0,
+ is_ok, do_last, cancel, reason);
+}
diff --git a/gnome-session/gsm-client.h b/gnome-session/gsm-client.h
new file mode 100644
index 0000000..d3feba9
--- /dev/null
+++ b/gnome-session/gsm-client.h
@@ -0,0 +1,140 @@
+/* -*- 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/>.
+ */
+
+#ifndef __GSM_CLIENT_H__
+#define __GSM_CLIENT_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <sys/types.h>
+
+G_BEGIN_DECLS
+
+#define GSM_TYPE_CLIENT (gsm_client_get_type ())
+G_DECLARE_DERIVABLE_TYPE (GsmClient, gsm_client, GSM, CLIENT, GObject)
+
+typedef struct _GsmApp GsmApp;
+typedef struct _GsmClient GsmClient;
+typedef struct _GsmClientClass GsmClientClass;
+
+typedef enum {
+ GSM_CLIENT_UNREGISTERED = 0,
+ GSM_CLIENT_REGISTERED,
+ GSM_CLIENT_FINISHED,
+ GSM_CLIENT_FAILED
+} GsmClientStatus;
+
+typedef enum {
+ GSM_CLIENT_RESTART_NEVER = 0,
+ GSM_CLIENT_RESTART_IF_RUNNING,
+ GSM_CLIENT_RESTART_ANYWAY,
+ GSM_CLIENT_RESTART_IMMEDIATELY
+} GsmClientRestartStyle;
+
+typedef enum {
+ GSM_CLIENT_END_SESSION_FLAG_FORCEFUL = 1 << 0,
+ GSM_CLIENT_END_SESSION_FLAG_SAVE = 1 << 1,
+ GSM_CLIENT_END_SESSION_FLAG_LAST = 1 << 2
+} GsmClientEndSessionFlag;
+
+struct _GsmClientClass
+{
+ GObjectClass parent_class;
+
+ /* signals */
+ void (*disconnected) (GsmClient *client);
+ void (*end_session_response) (GsmClient *client,
+ gboolean ok,
+ gboolean do_last,
+ gboolean cancel,
+ const char *reason);
+
+ /* virtual methods */
+ char * (*impl_get_app_name) (GsmClient *client);
+ GsmClientRestartStyle (*impl_get_restart_style_hint) (GsmClient *client);
+ guint (*impl_get_unix_process_id) (GsmClient *client);
+ gboolean (*impl_query_end_session) (GsmClient *client,
+ GsmClientEndSessionFlag flags,
+ GError **error);
+ gboolean (*impl_end_session) (GsmClient *client,
+ GsmClientEndSessionFlag flags,
+ GError **error);
+ gboolean (*impl_cancel_end_session) (GsmClient *client,
+ GError **error);
+ gboolean (*impl_stop) (GsmClient *client,
+ GError **error);
+ GKeyFile * (*impl_save) (GsmClient *client,
+ GsmApp *app,
+ GError **error);
+};
+
+typedef enum
+{
+ GSM_CLIENT_ERROR_GENERAL = 0,
+ GSM_CLIENT_ERROR_NOT_REGISTERED,
+ GSM_CLIENT_NUM_ERRORS
+} GsmClientError;
+
+#define GSM_CLIENT_ERROR gsm_client_error_quark ()
+GQuark gsm_client_error_quark (void);
+
+const char *gsm_client_peek_id (GsmClient *client);
+
+
+const char * gsm_client_peek_startup_id (GsmClient *client);
+const char * gsm_client_peek_app_id (GsmClient *client);
+guint gsm_client_peek_restart_style_hint (GsmClient *client);
+guint gsm_client_peek_status (GsmClient *client);
+
+
+char *gsm_client_get_app_name (GsmClient *client);
+void gsm_client_set_app_id (GsmClient *client,
+ const char *app_id);
+void gsm_client_set_status (GsmClient *client,
+ guint status);
+
+gboolean gsm_client_end_session (GsmClient *client,
+ guint flags,
+ GError **error);
+gboolean gsm_client_query_end_session (GsmClient *client,
+ guint flags,
+ GError **error);
+gboolean gsm_client_cancel_end_session (GsmClient *client,
+ GError **error);
+
+void gsm_client_disconnected (GsmClient *client);
+
+GKeyFile *gsm_client_save (GsmClient *client,
+ GsmApp *app,
+ GError **error);
+
+gboolean gsm_client_stop (GsmClient *client,
+ GError **error);
+
+/* private */
+
+void gsm_client_end_session_response (GsmClient *client,
+ gboolean is_ok,
+ gboolean do_last,
+ gboolean cancel,
+ const char *reason);
+
+G_END_DECLS
+
+#endif /* __GSM_CLIENT_H__ */
diff --git a/gnome-session/gsm-consolekit.c b/gnome-session/gsm-consolekit.c
new file mode 100644
index 0000000..2598e80
--- /dev/null
+++ b/gnome-session/gsm-consolekit.c
@@ -0,0 +1,972 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Jon McCann <jmccann@redhat.com>
+ *
+ * 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, 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 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 <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <pwd.h>
+
+#include <glib.h>
+#include <glib-object.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <gio/gunixfdlist.h>
+
+#include "gsm-system.h"
+#include "gsm-consolekit.h"
+
+#define CK_NAME "org.freedesktop.ConsoleKit"
+
+#define CK_MANAGER_PATH "/org/freedesktop/ConsoleKit/Manager"
+#define CK_MANAGER_INTERFACE CK_NAME ".Manager"
+#define CK_SEAT_INTERFACE CK_NAME ".Seat"
+#define CK_SESSION_INTERFACE CK_NAME ".Session"
+
+
+struct _GsmConsolekitPrivate
+{
+ GDBusProxy *ck_proxy;
+ GDBusProxy *ck_session_proxy;
+
+ char *session_id;
+ gchar *session_path;
+
+ const gchar *inhibit_locks;
+ gint inhibit_fd;
+
+ gboolean is_active;
+
+ gint delay_inhibit_fd;
+ gboolean prepare_for_shutdown_expected;
+};
+
+enum {
+ PROP_0,
+ PROP_ACTIVE
+};
+
+static void gsm_consolekit_system_init (GsmSystemInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GsmConsolekit, gsm_consolekit, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GSM_TYPE_SYSTEM,
+ gsm_consolekit_system_init))
+
+static void
+drop_system_inhibitor (GsmConsolekit *manager)
+{
+ if (manager->priv->inhibit_fd != -1) {
+ g_debug ("GsmConsolekit: Dropping system inhibitor fd %d", manager->priv->inhibit_fd);
+ close (manager->priv->inhibit_fd);
+ manager->priv->inhibit_fd = -1;
+ }
+}
+
+static void
+drop_delay_inhibitor (GsmConsolekit *manager)
+{
+ if (manager->priv->delay_inhibit_fd != -1) {
+ g_debug ("GsmConsolekit: Dropping delay inhibitor");
+ close (manager->priv->delay_inhibit_fd);
+ manager->priv->delay_inhibit_fd = -1;
+ }
+}
+
+static void
+gsm_consolekit_finalize (GObject *object)
+{
+ GsmConsolekit *consolekit = GSM_CONSOLEKIT (object);
+
+ g_clear_object (&consolekit->priv->ck_proxy);
+ g_clear_object (&consolekit->priv->ck_session_proxy);
+ free (consolekit->priv->session_id);
+ g_free (consolekit->priv->session_path);
+
+ drop_system_inhibitor (consolekit);
+ drop_delay_inhibitor (consolekit);
+
+ G_OBJECT_CLASS (gsm_consolekit_parent_class)->finalize (object);
+}
+
+static void
+gsm_consolekit_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GsmConsolekit *self = GSM_CONSOLEKIT (object);
+
+ switch (prop_id) {
+ case PROP_ACTIVE:
+ self->priv->is_active = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gsm_consolekit_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GsmConsolekit *self = GSM_CONSOLEKIT (object);
+
+ switch (prop_id) {
+ case PROP_ACTIVE:
+ g_value_set_boolean (value, self->priv->is_active);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gsm_consolekit_class_init (GsmConsolekitClass *manager_class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (manager_class);
+
+ object_class->get_property = gsm_consolekit_get_property;
+ object_class->set_property = gsm_consolekit_set_property;
+ object_class->finalize = gsm_consolekit_finalize;
+
+ g_object_class_override_property (object_class, PROP_ACTIVE, "active");
+
+ g_type_class_add_private (manager_class, sizeof (GsmConsolekitPrivate));
+}
+
+static void ck_session_proxy_signal_cb (GDBusProxy *proxy,
+ const gchar *sender_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data);
+
+static void ck_proxy_signal_cb (GDBusProxy *proxy,
+ const gchar *sender_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data);
+
+static void
+ck_pid_get_session (GsmConsolekit *manager,
+ pid_t pid,
+ gchar **session_id)
+{
+ GVariant *res;
+
+ *session_id = NULL;
+
+ if (pid < 0) {
+ g_warning ("Calling GetSessionForUnixProcess failed."
+ "Invalid pid.");
+ return;
+ }
+
+ res = g_dbus_proxy_call_sync (manager->priv->ck_proxy,
+ "GetSessionForUnixProcess",
+ g_variant_new ("(u)", pid),
+ 0,
+ -1,
+ NULL,
+ NULL);
+ if (!res) {
+ g_warning ("Calling GetSessionForUnixProcess failed."
+ "Check that ConsoleKit is properly installed.");
+ return;
+ }
+
+ g_variant_get (res, "(o)", session_id);
+ g_variant_unref (res);
+}
+
+static void
+gsm_consolekit_init (GsmConsolekit *manager)
+{
+ GError *error = NULL;
+ GDBusConnection *bus;
+ GVariant *res;
+
+ manager->priv = G_TYPE_INSTANCE_GET_PRIVATE (manager,
+ GSM_TYPE_CONSOLEKIT,
+ GsmConsolekitPrivate);
+
+ manager->priv->inhibit_fd = -1;
+ manager->priv->delay_inhibit_fd = -1;
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (bus == NULL)
+ g_error ("Failed to connect to system bus: %s",
+ error->message);
+ manager->priv->ck_proxy =
+ g_dbus_proxy_new_sync (bus,
+ 0,
+ NULL,
+ CK_NAME,
+ CK_MANAGER_PATH,
+ CK_MANAGER_INTERFACE,
+ NULL,
+ &error);
+ if (manager->priv->ck_proxy == NULL) {
+ g_warning ("Failed to connect to consolekit: %s",
+ error->message);
+ g_clear_error (&error);
+ }
+
+ g_signal_connect (manager->priv->ck_proxy, "g-signal",
+ G_CALLBACK (ck_proxy_signal_cb), manager);
+
+ ck_pid_get_session (manager, getpid (), &manager->priv->session_id);
+
+ if (manager->priv->session_id == NULL) {
+ g_warning ("Could not get session id for session. Check that ConsoleKit is "
+ "properly installed.");
+ return;
+ }
+
+ /* in ConsoleKit, the session id is the session path */
+ manager->priv->session_path = g_strdup (manager->priv->session_id);
+
+ manager->priv->ck_session_proxy =
+ g_dbus_proxy_new_sync (bus,
+ 0,
+ NULL,
+ CK_NAME,
+ manager->priv->session_path,
+ CK_SESSION_INTERFACE,
+ NULL,
+ &error);
+ if (manager->priv->ck_proxy == NULL) {
+ g_warning ("Failed to connect to consolekit session: %s",
+ error->message);
+ g_clear_error (&error);
+ }
+
+ g_signal_connect (manager->priv->ck_session_proxy, "g-signal",
+ G_CALLBACK (ck_session_proxy_signal_cb), manager);
+
+ g_object_unref (bus);
+}
+
+static void
+emit_restart_complete (GsmConsolekit *manager,
+ GError *error)
+{
+ GError *call_error;
+
+ call_error = NULL;
+
+ if (error != NULL) {
+ call_error = g_error_new_literal (GSM_SYSTEM_ERROR,
+ GSM_SYSTEM_ERROR_RESTARTING,
+ error->message);
+ }
+
+ g_signal_emit_by_name (G_OBJECT (manager),
+ "request_completed", call_error);
+
+ if (call_error != NULL) {
+ g_error_free (call_error);
+ }
+}
+
+static void
+emit_stop_complete (GsmConsolekit *manager,
+ GError *error)
+{
+ GError *call_error;
+
+ call_error = NULL;
+
+ if (error != NULL) {
+ call_error = g_error_new_literal (GSM_SYSTEM_ERROR,
+ GSM_SYSTEM_ERROR_STOPPING,
+ error->message);
+ }
+
+ g_signal_emit_by_name (G_OBJECT (manager),
+ "request_completed", call_error);
+
+ if (call_error != NULL) {
+ g_error_free (call_error);
+ }
+}
+
+static void
+restart_done (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (source);
+ GsmConsolekit *manager = user_data;
+ GError *error = NULL;
+ GVariant *res;
+
+ res = g_dbus_proxy_call_finish (proxy, result, &error);
+
+ if (!res) {
+ g_warning ("Unable to restart system: %s", error->message);
+ emit_restart_complete (manager, error);
+ g_error_free (error);
+ } else {
+ emit_restart_complete (manager, NULL);
+ g_variant_unref (res);
+ }
+}
+
+static void
+gsm_consolekit_attempt_restart (GsmSystem *system)
+{
+ GsmConsolekit *manager = GSM_CONSOLEKIT (system);
+
+ /* Use Restart instead of Reboot because it will work on
+ * both CK and CK2 */
+ g_dbus_proxy_call (manager->priv->ck_proxy,
+ "Restart",
+ g_variant_new ("()"),
+ 0,
+ G_MAXINT,
+ NULL,
+ restart_done,
+ manager);
+}
+
+static void
+stop_done (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (source);
+ GsmConsolekit *manager = user_data;
+ GError *error = NULL;
+ GVariant *res;
+
+ res = g_dbus_proxy_call_finish (proxy, result, &error);
+
+ if (!res) {
+ g_warning ("Unable to stop system: %s", error->message);
+ emit_stop_complete (manager, error);
+ g_error_free (error);
+ } else {
+ emit_stop_complete (manager, NULL);
+ g_variant_unref (res);
+ }
+}
+
+static void
+gsm_consolekit_attempt_stop (GsmSystem *system)
+{
+ GsmConsolekit *manager = GSM_CONSOLEKIT (system);
+
+ /* Use Stop insetad of PowerOff because it will work with
+ * Ck and CK2. */
+ g_dbus_proxy_call (manager->priv->ck_proxy,
+ "Stop",
+ g_variant_new ("()"),
+ 0,
+ G_MAXINT,
+ NULL,
+ stop_done,
+ manager);
+}
+
+static void
+gsm_consolekit_set_session_idle (GsmSystem *system,
+ gboolean is_idle)
+{
+ GsmConsolekit *manager = GSM_CONSOLEKIT (system);
+
+ g_debug ("Updating consolekit idle status: %d", is_idle);
+ g_dbus_proxy_call_sync (manager->priv->ck_session_proxy,
+ "SetIdleHint",
+ g_variant_new ("(b)", is_idle),
+ 0,
+ G_MAXINT,
+ NULL, NULL);
+}
+
+static void
+ck_session_get_seat (GsmConsolekit *manager,
+ gchar **seat)
+{
+ GVariant *res;
+
+ *seat = NULL;
+
+ res = g_dbus_proxy_call_sync (manager->priv->ck_session_proxy,
+ "GetSeatId",
+ g_variant_new ("()"),
+ 0,
+ -1,
+ NULL, NULL);
+ if (!res) {
+ g_warning ("GsmConsoleKit: Calling GetSeatId failed.");
+ return;
+ }
+
+ g_variant_get (res, "(o)", seat);
+ g_variant_unref (res);
+}
+
+/* returns -1 on failure
+ * 0 seat is multi-session
+ * 1 seat is not multi-session
+ */
+static gint
+ck_seat_can_multi_session (GsmConsolekit *manager,
+ const gchar *seat)
+{
+ GDBusConnection *bus;
+ GVariant *res;
+ gboolean can_activate;
+
+
+ bus = g_dbus_proxy_get_connection (manager->priv->ck_proxy);
+ res = g_dbus_connection_call_sync (bus,
+ CK_NAME,
+ seat,
+ CK_SEAT_INTERFACE,
+ "CanActivateSessions",
+ g_variant_new ("()"),
+ G_VARIANT_TYPE_BOOLEAN,
+ 0,
+ -1,
+ NULL, NULL);
+ if (!res) {
+ g_warning ("GsmConsoleKit: Calling GetSeatId failed.");
+ return -1;
+ }
+
+ g_variant_get (res, "(b)", &can_activate);
+ g_variant_unref (res);
+
+ return can_activate == TRUE ? 1 : 0;
+}
+
+static gboolean
+gsm_consolekit_can_switch_user (GsmSystem *system)
+{
+ GsmConsolekit *manager = GSM_CONSOLEKIT (system);
+ gchar *seat;
+ gint ret;
+
+ ck_session_get_seat (manager, &seat);
+ ret = ck_seat_can_multi_session (manager, seat);
+ free (seat);
+
+ return ret > 0;
+}
+
+static gboolean
+gsm_consolekit_can_restart (GsmSystem *system)
+{
+ GsmConsolekit *manager = GSM_CONSOLEKIT (system);
+ GVariant *res;
+ gboolean can_restart;
+
+ res = g_dbus_proxy_call_sync (manager->priv->ck_proxy,
+ "CanRestart",
+ g_variant_new ("()"),
+ 0,
+ G_MAXINT,
+ NULL,
+ NULL);
+ if (!res) {
+ g_warning ("Calling CanRestart failed. Check that ConsoleKit is "
+ "properly installed.");
+ return FALSE;
+ }
+
+ g_variant_get (res, "(b)", &can_restart);
+ g_variant_unref (res);
+
+ return can_restart;
+}
+
+static gboolean
+gsm_consolekit_can_stop (GsmSystem *system)
+{
+ GsmConsolekit *manager = GSM_CONSOLEKIT (system);
+ GVariant *res;
+ gboolean can_stop;
+
+ res = g_dbus_proxy_call_sync (manager->priv->ck_proxy,
+ "CanStop",
+ g_variant_new ("()"),
+ 0,
+ G_MAXINT,
+ NULL,
+ NULL);
+ if (!res) {
+ g_warning ("Calling CanStop failed. Check that ConsoleKit is "
+ "properly installed.");
+ return FALSE;
+ }
+
+ g_variant_get (res, "(b)", &can_stop);
+ g_variant_unref (res);
+
+ return can_stop;
+}
+
+/* returns -1 on failure, 0 on success */
+static gint
+ck_session_get_class (GsmConsolekit *manager,
+ gchar **session_class)
+{
+ GVariant *res;
+
+ *session_class = NULL;
+
+ res = g_dbus_proxy_call_sync (manager->priv->ck_session_proxy,
+ "GetSessionClass",
+ g_variant_new ("()"),
+ 0,
+ -1,
+ NULL, NULL);
+ if (!res) {
+ g_warning ("GsmConsoleKit: Calling GetSessionClass failed.");
+ return -1;
+ }
+
+ g_variant_get (res, "(s)", session_class);
+ g_variant_unref (res);
+
+ return 0;
+}
+
+static gboolean
+gsm_consolekit_is_login_session (GsmSystem *system)
+{
+ GsmConsolekit *manager = GSM_CONSOLEKIT (system);
+ int res;
+ gboolean ret;
+ gchar *session_class = NULL;
+
+ ret = FALSE;
+
+ if (manager->priv->session_id == NULL) {
+ return ret;
+ }
+
+ res = ck_session_get_class (manager, &session_class);
+ if (res < 0) {
+ g_warning ("Could not get session class: %s", strerror (-res));
+ return FALSE;
+ }
+ ret = (g_strcmp0 (session_class, "greeter") == 0);
+ g_free (session_class);
+
+ return ret;
+}
+
+static gboolean
+gsm_consolekit_can_suspend (GsmSystem *system)
+{
+ GsmConsolekit *manager = GSM_CONSOLEKIT (system);
+ gchar *rv;
+ GVariant *res;
+ gboolean can_suspend;
+
+ res = g_dbus_proxy_call_sync (manager->priv->ck_proxy,
+ "CanSuspend",
+ NULL,
+ 0,
+ G_MAXINT,
+ NULL,
+ NULL);
+ if (!res) {
+ g_warning ("Calling CanSuspend failed. Check that ConsoleKit is "
+ "properly installed.");
+ return FALSE;
+ }
+
+ g_variant_get (res, "(s)", &rv);
+ g_variant_unref (res);
+
+ can_suspend = g_strcmp0 (rv, "yes") == 0 ||
+ g_strcmp0 (rv, "challenge") == 0;
+
+ g_free (rv);
+
+ return can_suspend;
+}
+
+static gboolean
+gsm_consolekit_can_hibernate (GsmSystem *system)
+{
+ GsmConsolekit *manager = GSM_CONSOLEKIT (system);
+ gchar *rv;
+ GVariant *res;
+ gboolean can_hibernate;
+
+ res = g_dbus_proxy_call_sync (manager->priv->ck_proxy,
+ "CanHibernate",
+ NULL,
+ 0,
+ G_MAXINT,
+ NULL,
+ NULL);
+ if (!res) {
+ g_warning ("Calling CanHibernate failed. Check that ConsoleKit is "
+ "properly installed.");
+ return FALSE;
+ }
+
+ g_variant_get (res, "(s)", &rv);
+ g_variant_unref (res);
+
+ can_hibernate = g_strcmp0 (rv, "yes") == 0 ||
+ g_strcmp0 (rv, "challenge") == 0;
+
+ g_free (rv);
+
+ return can_hibernate;
+}
+
+static void
+suspend_done (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (source);
+ GError *error = NULL;
+ GVariant *res;
+
+ res = g_dbus_proxy_call_finish (proxy, result, &error);
+
+ if (!res) {
+ g_warning ("Unable to suspend system: %s", error->message);
+ g_error_free (error);
+ } else {
+ g_variant_unref (res);
+ }
+}
+
+static void
+hibernate_done (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (source);
+ GError *error = NULL;
+ GVariant *res;
+
+ res = g_dbus_proxy_call_finish (proxy, result, &error);
+
+ if (!res) {
+ g_warning ("Unable to hibernate system: %s", error->message);
+ g_error_free (error);
+ } else {
+ g_variant_unref (res);
+ }
+}
+
+static void
+gsm_consolekit_suspend (GsmSystem *system)
+{
+ GsmConsolekit *manager = GSM_CONSOLEKIT (system);
+
+ g_dbus_proxy_call (manager->priv->ck_proxy,
+ "Suspend",
+ g_variant_new ("(b)", TRUE),
+ 0,
+ G_MAXINT,
+ NULL,
+ suspend_done,
+ manager);
+}
+
+static void
+gsm_consolekit_hibernate (GsmSystem *system)
+{
+ GsmConsolekit *manager = GSM_CONSOLEKIT (system);
+
+ g_dbus_proxy_call (manager->priv->ck_proxy,
+ "Hibernate",
+ g_variant_new ("(b)", TRUE),
+ 0,
+ G_MAXINT,
+ NULL,
+ hibernate_done,
+ manager);
+}
+
+static void
+inhibit_done (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (source);
+ GsmConsolekit *manager = GSM_CONSOLEKIT (user_data);
+ GError *error = NULL;
+ GVariant *res;
+ GUnixFDList *fd_list = NULL;
+ gint idx;
+
+ /* Drop any previous inhibit before recording the new one */
+ drop_system_inhibitor (manager);
+
+ res = g_dbus_proxy_call_with_unix_fd_list_finish (proxy, &fd_list, result, &error);
+
+ if (!res) {
+ g_warning ("Unable to inhibit system: %s", error->message);
+ g_error_free (error);
+ } else {
+ g_variant_get (res, "(h)", &idx);
+ manager->priv->inhibit_fd = g_unix_fd_list_get (fd_list, idx, &error);
+ if (manager->priv->inhibit_fd == -1) {
+ g_warning ("Failed to receive system inhibitor fd: %s", error->message);
+ g_error_free (error);
+ }
+ g_debug ("System inhibitor fd is %d", manager->priv->inhibit_fd);
+ g_object_unref (fd_list);
+ g_variant_unref (res);
+ }
+
+ /* Handle a race condition, where locks got unset during dbus call */
+ if (manager->priv->inhibit_locks == NULL) {
+ drop_system_inhibitor (manager);
+ }
+}
+
+static void
+gsm_consolekit_set_inhibitors (GsmSystem *system,
+ GsmInhibitorFlag flags)
+{
+ GsmConsolekit *manager = GSM_CONSOLEKIT (system);
+ const gchar *locks = NULL;
+ gboolean inhibit_logout;
+ gboolean inhibit_suspend;
+
+ inhibit_logout = (flags & GSM_INHIBITOR_FLAG_LOGOUT) != 0;
+ inhibit_suspend = (flags & GSM_INHIBITOR_FLAG_SUSPEND) != 0;
+
+ if (inhibit_logout && inhibit_suspend) {
+ locks = "sleep:shutdown";
+ } else if (inhibit_logout) {
+ locks = "shutdown";
+ } else if (inhibit_suspend) {
+ locks = "sleep";
+ }
+ manager->priv->inhibit_locks = locks;
+
+ if (locks != NULL) {
+ g_debug ("Adding system inhibitor on %s", locks);
+ g_dbus_proxy_call_with_unix_fd_list (manager->priv->ck_proxy,
+ "Inhibit",
+ g_variant_new ("(ssss)",
+ locks,
+ g_get_user_name (),
+ "user session inhibited",
+ "block"),
+ 0,
+ G_MAXINT,
+ NULL,
+ NULL,
+ inhibit_done,
+ manager);
+ } else {
+ drop_system_inhibitor (manager);
+ }
+}
+
+static void
+reboot_or_poweroff_done (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GsmConsolekit *consolekit = user_data;
+ GVariant *result;
+ GError *error = NULL;
+
+ result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source),
+ res,
+ &error);
+
+ if (result == NULL) {
+ if (!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED)) {
+ g_warning ("Shutdown failed: %s", error->message);
+ }
+ g_error_free (error);
+ drop_delay_inhibitor (consolekit);
+ g_debug ("GsmConsolekit: shutdown preparation failed");
+ consolekit->priv->prepare_for_shutdown_expected = FALSE;
+ g_signal_emit_by_name (consolekit, "shutdown-prepared", FALSE);
+ } else {
+ g_variant_unref (result);
+ }
+}
+
+static void
+gsm_consolekit_prepare_shutdown (GsmSystem *system,
+ gboolean restart)
+{
+ GsmConsolekit *consolekit = GSM_CONSOLEKIT (system);
+ GUnixFDList *fd_list;
+ GVariant *res;
+ GError *error = NULL;
+ gint idx;
+
+ g_debug ("GsmConsolekit: prepare shutdown");
+
+ res = g_dbus_proxy_call_with_unix_fd_list_sync (consolekit->priv->ck_proxy,
+ "Inhibit",
+ g_variant_new ("(ssss)",
+ "shutdown",
+ g_get_user_name (),
+ "Preparing to end the session",
+ "delay"),
+ 0,
+ G_MAXINT,
+ NULL,
+ &fd_list,
+ NULL,
+ &error);
+ if (res == NULL) {
+ g_warning ("Failed to get delay inhibitor: %s", error->message);
+ g_error_free (error);
+ /* We may fail here with CK and that's ok */
+ } else {
+ g_variant_get (res, "(h)", &idx);
+
+ consolekit->priv->delay_inhibit_fd = g_unix_fd_list_get (fd_list, idx, NULL);
+
+ g_debug ("GsmConsolekit: got delay inhibitor, fd = %d", consolekit->priv->delay_inhibit_fd);
+
+ g_variant_unref (res);
+ g_object_unref (fd_list);
+ }
+
+ consolekit->priv->prepare_for_shutdown_expected = TRUE;
+
+ g_dbus_proxy_call (consolekit->priv->ck_proxy,
+ restart ? "Reboot" : "PowerOff",
+ g_variant_new ("(b)", TRUE),
+ 0,
+ G_MAXINT,
+ NULL,
+ reboot_or_poweroff_done,
+ consolekit);
+}
+
+static void
+gsm_consolekit_complete_shutdown (GsmSystem *system)
+{
+ GsmConsolekit *consolekit = GSM_CONSOLEKIT (system);
+
+ /* remove delay inhibitor, if any */
+ drop_delay_inhibitor (consolekit);
+}
+
+static gboolean
+gsm_consolekit_is_last_session_for_user (GsmSystem *system)
+{
+ return FALSE;
+}
+
+static void
+gsm_consolekit_system_init (GsmSystemInterface *iface)
+{
+ iface->can_switch_user = gsm_consolekit_can_switch_user;
+ iface->can_stop = gsm_consolekit_can_stop;
+ iface->can_restart = gsm_consolekit_can_restart;
+ iface->can_suspend = gsm_consolekit_can_suspend;
+ iface->can_hibernate = gsm_consolekit_can_hibernate;
+ iface->attempt_stop = gsm_consolekit_attempt_stop;
+ iface->attempt_restart = gsm_consolekit_attempt_restart;
+ iface->suspend = gsm_consolekit_suspend;
+ iface->hibernate = gsm_consolekit_hibernate;
+ iface->set_session_idle = gsm_consolekit_set_session_idle;
+ iface->is_login_session = gsm_consolekit_is_login_session;
+ iface->set_inhibitors = gsm_consolekit_set_inhibitors;
+ iface->prepare_shutdown = gsm_consolekit_prepare_shutdown;
+ iface->complete_shutdown = gsm_consolekit_complete_shutdown;
+ iface->is_last_session_for_user = gsm_consolekit_is_last_session_for_user;
+}
+
+GsmConsolekit *
+gsm_consolekit_new (void)
+{
+ GsmConsolekit *manager;
+
+ manager = g_object_new (GSM_TYPE_CONSOLEKIT, NULL);
+
+ return manager;
+}
+
+static void
+ck_proxy_signal_cb (GDBusProxy *proxy,
+ const gchar *sender_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ GsmConsolekit *consolekit = user_data;
+ gboolean is_about_to_shutdown;
+
+ g_debug ("GsmConsolekit: received ConsoleKit signal: %s", signal_name);
+
+ if (g_strcmp0 (signal_name, "PrepareForShutdown") != 0) {
+ g_debug ("GsmConsolekit: ignoring %s signal", signal_name);
+ return;
+ }
+
+ g_variant_get (parameters, "(b)", &is_about_to_shutdown);
+ if (!is_about_to_shutdown) {
+ g_debug ("GsmConsolekit: ignoring %s signal since about-to-shutdown is FALSE", signal_name);
+ return;
+ }
+
+ if (consolekit->priv->prepare_for_shutdown_expected) {
+ g_debug ("GsmConsolekit: shutdown successfully prepared");
+ g_signal_emit_by_name (consolekit, "shutdown-prepared", TRUE);
+ consolekit->priv->prepare_for_shutdown_expected = FALSE;
+ }
+}
+
+static void
+ck_session_proxy_signal_cb (GDBusProxy *proxy,
+ const gchar *sender_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ GsmConsolekit *consolekit = user_data;
+ gboolean is_active;
+
+ g_debug ("GsmConsolekit: received ConsoleKit signal: %s", signal_name);
+
+ if (g_strcmp0 (signal_name, "ActiveChanged") != 0) {
+ g_debug ("GsmConsolekit: ignoring %s signal", signal_name);
+ return;
+ }
+
+ g_variant_get (parameters, "(b)", &is_active);
+ if (consolekit->priv->is_active != is_active) {
+ g_debug ("GsmConsolekit: session state changed");
+ consolekit->priv->is_active = is_active;
+ g_object_notify (G_OBJECT (consolekit), "active");
+ }
+}
diff --git a/gnome-session/gsm-consolekit.h b/gnome-session/gsm-consolekit.h
new file mode 100644
index 0000000..963a9fd
--- /dev/null
+++ b/gnome-session/gsm-consolekit.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Jon McCann <jmccann@redhat.com>
+ *
+ * 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
+ * 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/>.
+ *
+ * Authors:
+ * Jon McCann <jmccann@redhat.com>
+ */
+
+#ifndef __GSM_CONSOLEKIT_H__
+#define __GSM_CONSOLEKIT_H__
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GSM_TYPE_CONSOLEKIT (gsm_consolekit_get_type ())
+#define GSM_CONSOLEKIT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_CONSOLEKIT, GsmConsolekit))
+#define GSM_CONSOLEKIT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_CONSOLEKIT, GsmConsolekitClass))
+#define GSM_IS_CONSOLEKIT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_CONSOLEKIT))
+#define GSM_IS_CONSOLEKIT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_CONSOLEKIT))
+#define GSM_CONSOLEKIT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GSM_TYPE_CONSOLEKIT, GsmConsolekitClass))
+
+typedef struct _GsmConsolekit GsmConsolekit;
+typedef struct _GsmConsolekitClass GsmConsolekitClass;
+typedef struct _GsmConsolekitPrivate GsmConsolekitPrivate;
+
+struct _GsmConsolekit
+{
+ GObject parent;
+
+ GsmConsolekitPrivate *priv;
+};
+
+struct _GsmConsolekitClass
+{
+ GObjectClass parent_class;
+};
+
+GType gsm_consolekit_get_type (void);
+
+GsmConsolekit *gsm_consolekit_new (void) G_GNUC_MALLOC;
+
+G_END_DECLS
+
+#endif /* __GSM_CONSOLEKIT_H__ */
diff --git a/gnome-session/gsm-dbus-client.c b/gnome-session/gsm-dbus-client.c
new file mode 100644
index 0000000..cf3b1f1
--- /dev/null
+++ b/gnome-session/gsm-dbus-client.c
@@ -0,0 +1,441 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * 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 <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <gio/gio.h>
+
+#include "org.gnome.SessionManager.ClientPrivate.h"
+#include "gsm-dbus-client.h"
+
+#include "gsm-manager.h"
+#include "gsm-util.h"
+
+#define SM_DBUS_NAME "org.gnome.SessionManager"
+#define SM_DBUS_CLIENT_PRIVATE_INTERFACE "org.gnome.SessionManager.ClientPrivate"
+
+struct _GsmDBusClient
+{
+ GObject parent_instance;
+
+ char *bus_name;
+ GPid caller_pid;
+ GsmClientRestartStyle restart_style_hint;
+
+ GDBusConnection *connection;
+ GsmExportedClientPrivate *skeleton;
+ guint watch_id;
+};
+
+enum {
+ PROP_0,
+ PROP_BUS_NAME
+};
+
+G_DEFINE_TYPE (GsmDBusClient, gsm_dbus_client, GSM_TYPE_CLIENT)
+
+static gboolean
+setup_connection (GsmDBusClient *client)
+{
+ GError *error = NULL;
+
+ if (client->connection == NULL) {
+ client->connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+ if (error != NULL) {
+ g_debug ("GsmDbusClient: Couldn't connect to session bus: %s",
+ error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+handle_end_session_response (GsmExportedClientPrivate *skeleton,
+ GDBusMethodInvocation *invocation,
+ gboolean is_ok,
+ const char *reason,
+ GsmDBusClient *client)
+{
+ g_debug ("GsmDBusClient: got EndSessionResponse is-ok:%d reason=%s", is_ok, reason);
+ gsm_client_end_session_response (GSM_CLIENT (client),
+ is_ok, FALSE, FALSE, reason);
+
+ gsm_exported_client_private_complete_end_session_response (skeleton, invocation);
+ return TRUE;
+}
+
+static GObject *
+gsm_dbus_client_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_properties)
+{
+ GsmDBusClient *client;
+ GError *error = NULL;
+ GsmExportedClientPrivate *skeleton;
+
+ client = GSM_DBUS_CLIENT (G_OBJECT_CLASS (gsm_dbus_client_parent_class)->constructor (type,
+ n_construct_properties,
+ construct_properties));
+
+ if (! setup_connection (client)) {
+ g_object_unref (client);
+ return NULL;
+ }
+
+ skeleton = gsm_exported_client_private_skeleton_new ();
+ client->skeleton = skeleton;
+ g_debug ("exporting dbus client to object path: %s", gsm_client_peek_id (GSM_CLIENT (client)));
+ g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (skeleton),
+ client->connection,
+ gsm_client_peek_id (GSM_CLIENT (client)),
+ &error);
+
+ if (error != NULL) {
+ g_critical ("error exporting client private on session bus: %s", error->message);
+ g_error_free (error);
+ g_object_unref (client);
+ return NULL;
+ }
+
+ g_signal_connect (skeleton, "handle-end-session-response",
+ G_CALLBACK (handle_end_session_response), client);
+
+ return G_OBJECT (client);
+}
+
+static void
+gsm_dbus_client_init (GsmDBusClient *client)
+{
+}
+
+/* adapted from PolicyKit */
+static gboolean
+get_caller_info (GsmDBusClient *client,
+ const char *sender,
+ uid_t *calling_uid_out,
+ pid_t *calling_pid_out)
+{
+ g_autoptr(GDBusConnection) connection = NULL;
+ GError *error;
+ g_autoptr(GVariant) uid_variant = NULL;
+ g_autoptr(GVariant) pid_variant = NULL;
+ uid_t uid;
+ pid_t pid;
+
+ if (sender == NULL) {
+ return FALSE;
+ }
+
+ error = NULL;
+ connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+
+ if (error != NULL) {
+ g_warning ("error getting session bus: %s", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ uid_variant = g_dbus_connection_call_sync (connection,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ "GetConnectionUnixUser",
+ g_variant_new ("(s)", sender),
+ G_VARIANT_TYPE ("(u)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, &error);
+
+ if (error != NULL) {
+ g_debug ("GetConnectionUnixUser() failed: %s", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ pid_variant = g_dbus_connection_call_sync (connection,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ "GetConnectionUnixProcessID",
+ g_variant_new ("(s)", sender),
+ G_VARIANT_TYPE ("(u)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, &error);
+
+ if (error != NULL) {
+ g_debug ("GetConnectionUnixProcessID() failed: %s", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ g_variant_get (uid_variant, "(u)", &uid);
+ g_variant_get (pid_variant, "(u)", &pid);
+
+ if (calling_uid_out != NULL) {
+ *calling_uid_out = uid;
+ }
+ if (calling_pid_out != NULL) {
+ *calling_pid_out = pid;
+ }
+
+ g_debug ("uid = %d", uid);
+ g_debug ("pid = %d", pid);
+
+ return TRUE;
+}
+
+static void
+on_client_vanished (GDBusConnection *connection,
+ const char *name,
+ gpointer user_data)
+{
+ GsmDBusClient *client = user_data;
+
+ g_bus_unwatch_name (client->watch_id);
+ client->watch_id = 0;
+
+ gsm_client_disconnected (GSM_CLIENT (client));
+}
+
+static void
+gsm_dbus_client_set_bus_name (GsmDBusClient *client,
+ const char *bus_name)
+{
+ g_return_if_fail (GSM_IS_DBUS_CLIENT (client));
+
+ g_free (client->bus_name);
+
+ client->bus_name = g_strdup (bus_name);
+ g_object_notify (G_OBJECT (client), "bus-name");
+
+ if (!get_caller_info (client, bus_name, NULL, &client->caller_pid)) {
+ client->caller_pid = 0;
+ }
+
+ client->watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION,
+ bus_name,
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ NULL,
+ on_client_vanished,
+ client,
+ NULL);
+}
+
+const char *
+gsm_dbus_client_get_bus_name (GsmDBusClient *client)
+{
+ g_return_val_if_fail (GSM_IS_DBUS_CLIENT (client), NULL);
+
+ return client->bus_name;
+}
+
+static void
+gsm_dbus_client_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GsmDBusClient *self;
+
+ self = GSM_DBUS_CLIENT (object);
+
+ switch (prop_id) {
+ case PROP_BUS_NAME:
+ gsm_dbus_client_set_bus_name (self, g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gsm_dbus_client_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GsmDBusClient *self;
+
+ self = GSM_DBUS_CLIENT (object);
+
+ switch (prop_id) {
+ case PROP_BUS_NAME:
+ g_value_set_string (value, self->bus_name);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gsm_dbus_client_finalize (GObject *object)
+{
+ GsmDBusClient *client = (GsmDBusClient *) object;
+
+ g_free (client->bus_name);
+
+ if (client->skeleton != NULL) {
+ g_dbus_interface_skeleton_unexport_from_connection (G_DBUS_INTERFACE_SKELETON (client->skeleton),
+ client->connection);
+ g_clear_object (&client->skeleton);
+ }
+
+ g_clear_object (&client->connection);
+
+ if (client->watch_id != 0)
+ g_bus_unwatch_name (client->watch_id);
+
+ G_OBJECT_CLASS (gsm_dbus_client_parent_class)->finalize (object);
+}
+
+static GKeyFile *
+dbus_client_save (GsmClient *client,
+ GsmApp *app,
+ GError **error)
+{
+ g_debug ("GsmDBusClient: saving client with id %s",
+ gsm_client_peek_id (client));
+
+ /* FIXME: We still don't support client saving for D-Bus
+ * session clients */
+
+ return NULL;
+}
+
+static gboolean
+dbus_client_stop (GsmClient *client,
+ GError **error)
+{
+ GsmDBusClient *dbus_client = (GsmDBusClient *) client;
+ gsm_exported_client_private_emit_stop (dbus_client->skeleton);
+ return TRUE;
+}
+
+static char *
+dbus_client_get_app_name (GsmClient *client)
+{
+ /* Always use app-id instead */
+ return NULL;
+}
+
+static GsmClientRestartStyle
+dbus_client_get_restart_style_hint (GsmClient *client)
+{
+ return (GSM_DBUS_CLIENT (client)->restart_style_hint);
+}
+
+static guint
+dbus_client_get_unix_process_id (GsmClient *client)
+{
+ return (GSM_DBUS_CLIENT (client)->caller_pid);
+}
+
+static gboolean
+dbus_client_query_end_session (GsmClient *client,
+ GsmClientEndSessionFlag flags,
+ GError **error)
+{
+ GsmDBusClient *dbus_client = (GsmDBusClient *) client;
+
+ if (dbus_client->bus_name == NULL) {
+ g_set_error (error,
+ GSM_CLIENT_ERROR,
+ GSM_CLIENT_ERROR_NOT_REGISTERED,
+ "Client is not registered");
+ return FALSE;
+ }
+
+ g_debug ("GsmDBusClient: sending QueryEndSession signal to %s", dbus_client->bus_name);
+
+ gsm_exported_client_private_emit_query_end_session (dbus_client->skeleton, flags);
+ return TRUE;
+}
+
+static gboolean
+dbus_client_end_session (GsmClient *client,
+ GsmClientEndSessionFlag flags,
+ GError **error)
+{
+ GsmDBusClient *dbus_client = (GsmDBusClient *) client;
+
+ gsm_exported_client_private_emit_end_session (dbus_client->skeleton, flags);
+ return TRUE;
+}
+
+static gboolean
+dbus_client_cancel_end_session (GsmClient *client,
+ GError **error)
+{
+ GsmDBusClient *dbus_client = (GsmDBusClient *) client;
+ gsm_exported_client_private_emit_cancel_end_session (dbus_client->skeleton);
+ return TRUE;
+}
+
+static void
+gsm_dbus_client_class_init (GsmDBusClientClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GsmClientClass *client_class = GSM_CLIENT_CLASS (klass);
+
+ object_class->finalize = gsm_dbus_client_finalize;
+ object_class->constructor = gsm_dbus_client_constructor;
+ object_class->get_property = gsm_dbus_client_get_property;
+ object_class->set_property = gsm_dbus_client_set_property;
+
+ client_class->impl_save = dbus_client_save;
+ client_class->impl_stop = dbus_client_stop;
+ client_class->impl_query_end_session = dbus_client_query_end_session;
+ client_class->impl_end_session = dbus_client_end_session;
+ client_class->impl_cancel_end_session = dbus_client_cancel_end_session;
+ client_class->impl_get_app_name = dbus_client_get_app_name;
+ client_class->impl_get_restart_style_hint = dbus_client_get_restart_style_hint;
+ client_class->impl_get_unix_process_id = dbus_client_get_unix_process_id;
+
+ g_object_class_install_property (object_class,
+ PROP_BUS_NAME,
+ g_param_spec_string ("bus-name",
+ "bus-name",
+ "bus-name",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+}
+
+GsmClient *
+gsm_dbus_client_new (const char *startup_id,
+ const char *bus_name)
+{
+ GsmDBusClient *client;
+
+ client = g_object_new (GSM_TYPE_DBUS_CLIENT,
+ "startup-id", startup_id,
+ "bus-name", bus_name,
+ NULL);
+
+ return GSM_CLIENT (client);
+}
diff --git a/gnome-session/gsm-dbus-client.h b/gnome-session/gsm-dbus-client.h
new file mode 100644
index 0000000..f36853b
--- /dev/null
+++ b/gnome-session/gsm-dbus-client.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * 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/>.
+ */
+
+#ifndef __GSM_DBUS_CLIENT_H__
+#define __GSM_DBUS_CLIENT_H__
+
+#include "gsm-client.h"
+
+G_BEGIN_DECLS
+
+#define GSM_TYPE_DBUS_CLIENT (gsm_dbus_client_get_type ())
+G_DECLARE_FINAL_TYPE (GsmDBusClient, gsm_dbus_client, GSM, DBUS_CLIENT, GsmClient)
+
+GsmClient * gsm_dbus_client_new (const char *startup_id,
+ const char *bus_name);
+const char * gsm_dbus_client_get_bus_name (GsmDBusClient *client);
+
+G_END_DECLS
+
+#endif /* __GSM_DBUS_CLIENT_H__ */
diff --git a/gnome-session/gsm-fail-whale-dialog.c b/gnome-session/gsm-fail-whale-dialog.c
new file mode 100644
index 0000000..d6e8407
--- /dev/null
+++ b/gnome-session/gsm-fail-whale-dialog.c
@@ -0,0 +1,461 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2011 Red Hat, Inc.
+ * Copyright (C) 2019 Canonical Ltd.
+ *
+ * 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
+ * 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/>.
+ *
+ * Authors:
+ * Colin Walters <walters@verbum.org>
+ * Marco Trevisan <marco@ubuntu.com>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+
+#include <gtk/gtk.h>
+#ifdef GDK_WINDOWING_X11
+#include <gtk/gtkx.h>
+#endif
+
+#include "gsm-fail-whale-dialog.h"
+
+#include "gsm-icon-names.h"
+
+struct _GsmFailWhaleDialog
+{
+ GtkWindow parent;
+
+ gboolean debug_mode;
+ gboolean allow_logout;
+ gboolean extensions;
+ GdkMonitor *monitor;
+ GdkRectangle geometry;
+};
+
+G_DEFINE_TYPE (GsmFailWhaleDialog, gsm_fail_whale_dialog, GTK_TYPE_WINDOW);
+
+/* derived from tomboy */
+static void
+_window_override_user_time (GsmFailWhaleDialog *window)
+{
+ guint32 ev_time = gtk_get_current_event_time ();
+ GdkWindow *gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
+
+#ifdef GDK_WINDOWING_X11
+ if (!GDK_IS_X11_WINDOW (gdk_window))
+ return;
+
+ if (ev_time == 0) {
+ gint ev_mask = gtk_widget_get_events (GTK_WIDGET (window));
+ if (!(ev_mask & GDK_PROPERTY_CHANGE_MASK)) {
+ gtk_widget_add_events (GTK_WIDGET (window),
+ GDK_PROPERTY_CHANGE_MASK);
+ }
+
+ /*
+ * NOTE: Last resort for D-BUS or other non-interactive
+ * openings. Causes roundtrip to server. Lame.
+ */
+ ev_time = gdk_x11_get_server_time (gdk_window);
+ }
+
+ gdk_x11_window_set_user_time (gdk_window, ev_time);
+#endif
+}
+
+static void
+_window_move_resize_window (GsmFailWhaleDialog *window,
+ gboolean move,
+ gboolean resize)
+{
+ if (window->debug_mode)
+ return;
+
+ g_debug ("Move and/or resize window x=%d y=%d w=%d h=%d",
+ window->geometry.x,
+ window->geometry.y,
+ window->geometry.width,
+ window->geometry.height);
+
+ if (resize) {
+ gtk_window_resize (GTK_WINDOW (window),
+ window->geometry.width,
+ window->geometry.height);
+ }
+
+ if (move) {
+ gtk_window_move (GTK_WINDOW (window),
+ window->geometry.x,
+ window->geometry.y);
+ }
+}
+
+static void
+update_geometry (GsmFailWhaleDialog *fail_dialog)
+{
+ gdk_monitor_get_geometry (fail_dialog->monitor, &fail_dialog->geometry);
+}
+
+static void
+on_screen_size_changed (GdkScreen *screen,
+ GsmFailWhaleDialog *fail_dialog)
+{
+ gtk_widget_queue_resize (GTK_WIDGET (fail_dialog));
+}
+
+static void
+gsm_fail_whale_dialog_realize (GtkWidget *widget)
+{
+ if (GTK_WIDGET_CLASS (gsm_fail_whale_dialog_parent_class)->realize) {
+ GTK_WIDGET_CLASS (gsm_fail_whale_dialog_parent_class)->realize (widget);
+ }
+
+ _window_override_user_time (GSM_FAIL_WHALE_DIALOG (widget));
+ update_geometry (GSM_FAIL_WHALE_DIALOG (widget));
+ _window_move_resize_window (GSM_FAIL_WHALE_DIALOG (widget), TRUE, TRUE);
+
+ g_signal_connect (gtk_window_get_screen (GTK_WINDOW (widget)),
+ "size_changed",
+ G_CALLBACK (on_screen_size_changed),
+ widget);
+}
+
+static void
+gsm_fail_whale_dialog_unrealize (GtkWidget *widget)
+{
+ g_signal_handlers_disconnect_by_func (gtk_window_get_screen (GTK_WINDOW (widget)),
+ on_screen_size_changed,
+ widget);
+
+ if (GTK_WIDGET_CLASS (gsm_fail_whale_dialog_parent_class)->unrealize) {
+ GTK_WIDGET_CLASS (gsm_fail_whale_dialog_parent_class)->unrealize (widget);
+ }
+}
+
+static void
+gsm_fail_whale_dialog_size_request (GtkWidget *widget,
+ GtkRequisition *requisition)
+{
+ GsmFailWhaleDialog *fail_dialog;
+ GdkRectangle old_geometry;
+ int position_changed = FALSE;
+ int size_changed = FALSE;
+
+ fail_dialog = GSM_FAIL_WHALE_DIALOG (widget);
+
+ old_geometry = fail_dialog->geometry;
+
+ update_geometry (fail_dialog);
+
+ requisition->width = fail_dialog->geometry.width;
+ requisition->height = fail_dialog->geometry.height;
+
+ if (!gtk_widget_get_realized (widget)) {
+ return;
+ }
+
+ if (old_geometry.width != fail_dialog->geometry.width ||
+ old_geometry.height != fail_dialog->geometry.height) {
+ size_changed = TRUE;
+ }
+
+ if (old_geometry.x != fail_dialog->geometry.x ||
+ old_geometry.y != fail_dialog->geometry.y) {
+ position_changed = TRUE;
+ }
+
+ _window_move_resize_window (fail_dialog,
+ position_changed, size_changed);
+}
+
+static void
+gsm_fail_whale_dialog_get_preferred_width (GtkWidget *widget,
+ gint *minimal_width,
+ gint *natural_width)
+{
+ GtkRequisition requisition;
+
+ gsm_fail_whale_dialog_size_request (widget, &requisition);
+
+ *minimal_width = *natural_width = requisition.width;
+}
+
+static void
+gsm_fail_whale_dialog_get_preferred_width_for_height (GtkWidget *widget,
+ gint for_height,
+ gint *minimal_width,
+ gint *natural_width)
+{
+ GtkRequisition requisition;
+
+ gsm_fail_whale_dialog_size_request (widget, &requisition);
+
+ *minimal_width = *natural_width = requisition.width;
+}
+
+static void
+gsm_fail_whale_dialog_get_preferred_height (GtkWidget *widget,
+ gint *minimal_height,
+ gint *natural_height)
+{
+ GtkRequisition requisition;
+
+ gsm_fail_whale_dialog_size_request (widget, &requisition);
+
+ *minimal_height = *natural_height = requisition.height;
+}
+
+static void
+gsm_fail_whale_dialog_get_preferred_height_for_width (GtkWidget *widget,
+ gint for_width,
+ gint *minimal_height,
+ gint *natural_height)
+{
+ GtkRequisition requisition;
+
+ gsm_fail_whale_dialog_size_request (widget, &requisition);
+
+ *minimal_height = *natural_height = requisition.height;
+}
+
+static void
+gsm_fail_whale_dialog_class_init (GsmFailWhaleDialogClass *klass)
+{
+ GtkWidgetClass *widget_class;
+
+ widget_class = GTK_WIDGET_CLASS (klass);
+
+ widget_class->realize = gsm_fail_whale_dialog_realize;
+ widget_class->unrealize = gsm_fail_whale_dialog_unrealize;
+ widget_class->get_preferred_width = gsm_fail_whale_dialog_get_preferred_width;
+ widget_class->get_preferred_height = gsm_fail_whale_dialog_get_preferred_height;
+ widget_class->get_preferred_width_for_height = gsm_fail_whale_dialog_get_preferred_width_for_height;
+ widget_class->get_preferred_height_for_width = gsm_fail_whale_dialog_get_preferred_height_for_width;
+}
+
+static void
+on_logout_clicked (GtkWidget *button,
+ GsmFailWhaleDialog *fail_dialog)
+{
+ if (!fail_dialog->debug_mode) {
+ g_spawn_command_line_async ("gnome-session-quit --force", NULL);
+ }
+ gtk_main_quit ();
+}
+
+static void
+setup_window (GsmFailWhaleDialog *fail_dialog)
+{
+ GtkWidget *box;
+ GtkWidget *image;
+ GtkWidget *label;
+ GtkWidget *message_label;
+ GtkWidget *button_box;
+ GtkWidget *button;
+ GdkPixbuf *fail_icon;
+ GdkDisplay *display;
+ char *markup;
+ int scale_factor;
+ int i;
+
+ gtk_window_set_title (GTK_WINDOW (fail_dialog), "");
+ gtk_window_set_icon_name (GTK_WINDOW (fail_dialog), GSM_ICON_COMPUTER_FAIL);
+
+ gtk_window_set_skip_taskbar_hint (GTK_WINDOW (fail_dialog), TRUE);
+ gtk_window_set_keep_above (GTK_WINDOW (fail_dialog), TRUE);
+ gtk_window_stick (GTK_WINDOW (fail_dialog));
+ gtk_window_set_position (GTK_WINDOW (fail_dialog), GTK_WIN_POS_CENTER_ALWAYS);
+
+ /* only works if there is a window manager which is unlikely */
+ display = gtk_widget_get_display (GTK_WIDGET (fail_dialog));
+ for (i = 0; i < gdk_display_get_n_monitors (display); i++) {
+ if (gdk_display_get_monitor (display, i) == fail_dialog->monitor) {
+ GdkScreen *screen;
+
+ screen = gtk_widget_get_screen (GTK_WIDGET (fail_dialog));
+ gtk_window_fullscreen_on_monitor (GTK_WINDOW (fail_dialog),
+ screen, i);
+ break;
+ }
+ }
+
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10);
+ gtk_widget_set_valign (box, GTK_ALIGN_CENTER);
+ gtk_widget_show (box);
+
+ gtk_container_add (GTK_CONTAINER (fail_dialog), box);
+
+ scale_factor = gdk_monitor_get_scale_factor (fail_dialog->monitor);
+ fail_icon = gtk_icon_theme_load_icon_for_scale (gtk_icon_theme_get_default (),
+ GSM_ICON_COMPUTER_FAIL,
+ 128,
+ scale_factor,
+ 0,
+ NULL);
+ if (fail_icon != NULL) {
+ image = gtk_image_new_from_pixbuf (fail_icon);
+ gtk_widget_show (image);
+ gtk_box_pack_start (GTK_BOX (box), image, FALSE, FALSE, 0);
+ g_object_unref (fail_icon);
+ }
+
+ label = gtk_label_new (NULL);
+ markup = g_strdup_printf ("<b><big>%s</big></b>", _("Oh no! Something has gone wrong."));
+ gtk_label_set_markup (GTK_LABEL (label), markup);
+ g_free (markup);
+ gtk_widget_show (label);
+ gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
+
+ if (!fail_dialog->allow_logout)
+ message_label = gtk_label_new (_("A problem has occurred and the system can’t recover. Please contact a system administrator"));
+ else if (fail_dialog->extensions)
+ message_label = gtk_label_new (_("A problem has occurred and the system can’t recover. All extensions have been disabled as a precaution."));
+ else
+ message_label = gtk_label_new (_("A problem has occurred and the system can’t recover.\nPlease log out and try again."));
+
+ gtk_label_set_justify (GTK_LABEL (message_label), GTK_JUSTIFY_CENTER);
+ gtk_label_set_line_wrap (GTK_LABEL (message_label), TRUE);
+ gtk_widget_show (message_label);
+ gtk_box_pack_start (GTK_BOX (box),
+ message_label, FALSE, FALSE, 0);
+
+ button_box = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_container_set_border_width (GTK_CONTAINER (button_box), 20);
+ gtk_widget_show (button_box);
+ gtk_box_pack_end (GTK_BOX (box),
+ button_box, FALSE, FALSE, 0);
+
+ if (fail_dialog->allow_logout) {
+ button = gtk_button_new_with_mnemonic (_("_Log Out"));
+ gtk_widget_show (button);
+ gtk_box_pack_end (GTK_BOX (button_box),
+ button, FALSE, FALSE, 0);
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (on_logout_clicked), fail_dialog);
+ }
+}
+
+static void
+gsm_fail_whale_dialog_init (GsmFailWhaleDialog *fail_dialog)
+{
+}
+
+static gboolean debug_mode = FALSE;
+static gboolean allow_logout = FALSE;
+static gboolean extensions = FALSE;
+static GList *dialogs = NULL;
+
+static void
+create_fail_dialog (GdkMonitor *monitor)
+{
+ GsmFailWhaleDialog *fail_dialog;
+
+ fail_dialog = g_object_new (GSM_TYPE_FAIL_WHALE_DIALOG, NULL);
+ fail_dialog->debug_mode = debug_mode;
+ fail_dialog->allow_logout = allow_logout;
+ fail_dialog->extensions = extensions;
+ fail_dialog->monitor = monitor;
+
+ setup_window (fail_dialog);
+
+ g_signal_connect (fail_dialog, "destroy",
+ G_CALLBACK (gtk_main_quit), NULL);
+
+ gtk_widget_show (GTK_WIDGET (fail_dialog));
+
+ dialogs = g_list_prepend (dialogs, fail_dialog);
+}
+
+static void
+on_monitor_added (GdkDisplay *display,
+ GdkMonitor *monitor)
+{
+ create_fail_dialog (monitor);
+}
+
+static void
+on_monitor_removed (GdkDisplay *display,
+ GdkMonitor *monitor)
+{
+ GList *l;
+
+ for (l = dialogs; l;) {
+ GList *next = l->next;
+ GsmFailWhaleDialog *fail_dialog = l->data;
+
+ if (fail_dialog->monitor == monitor) {
+ dialogs = g_list_delete_link (dialogs, l);
+ gtk_widget_destroy (GTK_WIDGET (fail_dialog));
+ }
+ l = next;
+ }
+}
+
+int main (int argc, char *argv[])
+{
+ GOptionEntry entries[] = {
+ { "debug", 0, 0, G_OPTION_ARG_NONE, &debug_mode, N_("Enable debugging code"), NULL },
+ { "allow-logout", 0, 0, G_OPTION_ARG_NONE, &allow_logout, N_("Allow logout"), NULL },
+ { "extensions", 0, 0, G_OPTION_ARG_NONE, &extensions, N_("Show extension warning"), NULL },
+ { NULL, 0, 0, 0, NULL, NULL, NULL }
+ };
+
+ GError *error = NULL;
+ GdkDisplay *display;
+ int i;
+
+ bindtextdomain (GETTEXT_PACKAGE, LOCALE_DIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ if (!gtk_init_with_args (&argc, &argv, " - fail whale",
+ entries, GETTEXT_PACKAGE,
+ &error)) {
+ if (error != NULL) {
+ g_warning ("%s", error->message);
+ exit (1);
+ }
+
+ /* display server probably went away. Could be for legitimate reasons, could be for
+ * unexpected reasons. If it went away unexpectantly, that's logged elsewhere, so
+ * let's not add noise by logging here.
+ */
+ return 0;
+ }
+
+ /* Force-off allow_logout when running inside GDM, this is needed
+ * because the systemd service always passes --allow-logout
+ */
+ if (g_strcmp0 (g_getenv ("RUNNING_UNDER_GDM"), "true") == 0)
+ allow_logout = FALSE;
+
+ display = gdk_display_get_default ();
+ for (i = 0; i < gdk_display_get_n_monitors (display); i++) {
+ create_fail_dialog (gdk_display_get_monitor (display, i));
+ }
+
+ g_signal_connect (display, "monitor-added",
+ G_CALLBACK (on_monitor_added), &dialogs);
+ g_signal_connect (display, "monitor-removed",
+ G_CALLBACK (on_monitor_removed), &dialogs);
+
+ gtk_main ();
+
+ return 0;
+}
+
diff --git a/gnome-session/gsm-fail-whale-dialog.h b/gnome-session/gsm-fail-whale-dialog.h
new file mode 100644
index 0000000..d91028a
--- /dev/null
+++ b/gnome-session/gsm-fail-whale-dialog.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2011 Red Hat, Inc.
+ * Copyright (C) 2019 Canonical Ltd.
+ *
+ * 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
+ * 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/>.
+ *
+ * Authors:
+ * Colin Walters <walters@verbum.org>
+ * Marco Trevisan <marco@ubuntu.com>
+ */
+
+#ifndef __GSM_FAIL_WHALE_DIALOG_H__
+#define __GSM_FAIL_WHALE_DIALOG_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GSM_TYPE_FAIL_WHALE_DIALOG (gsm_fail_whale_dialog_get_type ())
+G_DECLARE_FINAL_TYPE (GsmFailWhaleDialog, gsm_fail_whale_dialog, GSM, FAIL_WHALE_DIALOG, GtkWindow);
+
+G_END_DECLS
+
+#endif /* __GSM_FAIL_WHALE_DIALOG_H__ */
diff --git a/gnome-session/gsm-fail-whale.c b/gnome-session/gsm-fail-whale.c
new file mode 100644
index 0000000..6ec0663
--- /dev/null
+++ b/gnome-session/gsm-fail-whale.c
@@ -0,0 +1,66 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ * gsm-fail-whale.c
+ * Copyright (C) 2012 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 <signal.h>
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+
+#include "gsm-fail-whale.h"
+#include "gsm-util.h"
+
+static void
+on_fail_whale_failed (void)
+{
+ raise (SIGTERM);
+}
+
+void
+gsm_fail_whale_dialog_we_failed (gboolean debug_mode,
+ gboolean allow_logout,
+ GsmShellExtensions *extensions)
+{
+ gint i;
+ gchar *argv[5];
+ static GPid pid = 0;
+
+ if (pid != 0) {
+ return;
+ }
+
+ i = 0;
+ argv[i++] = LIBEXECDIR "/gnome-session-failed";
+ if (debug_mode)
+ argv[i++] = "--debug";
+ if (allow_logout)
+ argv[i++] = "--allow-logout";
+ if (extensions != NULL && gsm_shell_extensions_n_extensions (extensions) > 0)
+ argv[i++] = "--extensions";
+ argv[i++] = NULL;
+
+ if (!g_spawn_async (NULL, argv, (char **) gsm_util_listenv (), G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid, NULL)) {
+ exit (1);
+ }
+
+ g_child_watch_add (pid,
+ (GChildWatchFunc)on_fail_whale_failed,
+ NULL);
+}
diff --git a/gnome-session/gsm-fail-whale.h b/gnome-session/gsm-fail-whale.h
new file mode 100644
index 0000000..b481e3c
--- /dev/null
+++ b/gnome-session/gsm-fail-whale.h
@@ -0,0 +1,34 @@
+/* gsm-fail-whale.h
+ * Copyright (C) 2012 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/>.
+ */
+
+#ifndef __GSM_FAIL_WHALE_H___
+#define __GSM_FAIL_WHALE_H__
+
+#include <glib.h>
+
+#include "gsm-shell-extensions.h"
+
+G_BEGIN_DECLS
+
+void gsm_fail_whale_dialog_we_failed (gboolean debug_mode,
+ gboolean allow_logout,
+ GsmShellExtensions *extensions);
+
+G_END_DECLS
+
+#endif /* __GSM_FAIL_WHALE_H__ */
+
diff --git a/gnome-session/gsm-icon-names.h b/gnome-session/gsm-icon-names.h
new file mode 100644
index 0000000..cca051b
--- /dev/null
+++ b/gnome-session/gsm-icon-names.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2011 Novell, 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/>.
+ */
+
+#ifndef __GSM_ICON_NAMES_H__
+#define __GSM_ICON_NAMES_H__
+
+#define GSM_ICON_COMPUTER_FAIL "computer-fail-symbolic"
+#define GSM_ICON_INHIBITOR_DEFAULT "gnome-windows"
+#define GSM_ICON_LOGOUT "system-log-out"
+#define GSM_ICON_SHUTDOWN "system-shutdown"
+#define GSM_ICON_XSMP_DEFAULT "system-run"
+
+#endif /*__GSM_ICON_NAMES_H__ */
diff --git a/gnome-session/gsm-inhibitor-flag.h b/gnome-session/gsm-inhibitor-flag.h
new file mode 100644
index 0000000..40698f9
--- /dev/null
+++ b/gnome-session/gsm-inhibitor-flag.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * 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/>.
+ */
+
+#ifndef __GSM_INHIBITOR_FLAG_H__
+#define __GSM_INHIBITOR_FLAG_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+ GSM_INHIBITOR_FLAG_LOGOUT = 1 << 0,
+ GSM_INHIBITOR_FLAG_SWITCH_USER = 1 << 1,
+ GSM_INHIBITOR_FLAG_SUSPEND = 1 << 2,
+ GSM_INHIBITOR_FLAG_IDLE = 1 << 3,
+ GSM_INHIBITOR_FLAG_AUTOMOUNT = 1 << 4
+} GsmInhibitorFlag;
+
+G_END_DECLS
+
+#endif /* __GSM_INHIBITOR_FLAG_H__ */
diff --git a/gnome-session/gsm-inhibitor.c b/gnome-session/gsm-inhibitor.c
new file mode 100644
index 0000000..a9a1f42
--- /dev/null
+++ b/gnome-session/gsm-inhibitor.c
@@ -0,0 +1,649 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * 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 <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "gsm-inhibitor.h"
+#include "org.gnome.SessionManager.Inhibitor.h"
+
+#include "gsm-util.h"
+
+static guint32 inhibitor_serial = 1;
+
+#define GSM_INHIBITOR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSM_TYPE_INHIBITOR, GsmInhibitorPrivate))
+
+struct GsmInhibitorPrivate
+{
+ char *id;
+ char *bus_name;
+ char *app_id;
+ char *client_id;
+ char *reason;
+ guint flags;
+ guint toplevel_xid;
+ guint cookie;
+ GDBusConnection *connection;
+ GsmExportedInhibitor *skeleton;
+
+ guint watch_id;
+};
+
+enum {
+ PROP_0,
+ PROP_BUS_NAME,
+ PROP_REASON,
+ PROP_APP_ID,
+ PROP_CLIENT_ID,
+ PROP_FLAGS,
+ PROP_TOPLEVEL_XID,
+ PROP_COOKIE
+};
+
+enum {
+ VANISHED,
+ LAST_SIGNAL
+};
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (GsmInhibitor, gsm_inhibitor, G_TYPE_OBJECT)
+
+#define GSM_INHIBITOR_DBUS_IFACE "org.gnome.SessionManager.Inhibitor"
+
+static const GDBusErrorEntry gsm_inhibitor_error_entries[] = {
+ { GSM_INHIBITOR_ERROR_GENERAL, GSM_INHIBITOR_DBUS_IFACE ".GeneralError" },
+ { GSM_INHIBITOR_ERROR_NOT_SET, GSM_INHIBITOR_DBUS_IFACE ".NotSet" }
+};
+
+GQuark
+gsm_inhibitor_error_quark (void)
+{
+ static volatile gsize quark_volatile = 0;
+
+ g_dbus_error_register_error_domain ("gsm_inhibitor_error",
+ &quark_volatile,
+ gsm_inhibitor_error_entries,
+ G_N_ELEMENTS (gsm_inhibitor_error_entries));
+ return quark_volatile;
+}
+
+static gboolean
+gsm_inhibitor_get_app_id (GsmExportedInhibitor *skeleton,
+ GDBusMethodInvocation *invocation,
+ GsmInhibitor *inhibitor)
+{
+ const gchar *id;
+
+ if (inhibitor->priv->app_id != NULL) {
+ id = inhibitor->priv->app_id;
+ } else {
+ id = "";
+ }
+
+ gsm_exported_inhibitor_complete_get_app_id (skeleton, invocation, id);
+
+ return TRUE;
+}
+
+static gboolean
+gsm_inhibitor_get_client_id (GsmExportedInhibitor *skeleton,
+ GDBusMethodInvocation *invocation,
+ GsmInhibitor *inhibitor)
+{
+ /* object paths are not allowed to be NULL or blank */
+ if (IS_STRING_EMPTY (inhibitor->priv->client_id)) {
+ g_dbus_method_invocation_return_error (invocation,
+ GSM_INHIBITOR_ERROR,
+ GSM_INHIBITOR_ERROR_NOT_SET,
+ "Value is not set");
+ return TRUE;
+ }
+
+ gsm_exported_inhibitor_complete_get_client_id (skeleton, invocation, inhibitor->priv->client_id);
+ g_debug ("GsmInhibitor: getting client-id = '%s'", inhibitor->priv->client_id);
+
+ return TRUE;
+}
+
+static gboolean
+gsm_inhibitor_get_reason (GsmExportedInhibitor *skeleton,
+ GDBusMethodInvocation *invocation,
+ GsmInhibitor *inhibitor)
+{
+ const gchar *reason;
+
+ if (inhibitor->priv->reason != NULL) {
+ reason = inhibitor->priv->reason;
+ } else {
+ reason = "";
+ }
+
+ gsm_exported_inhibitor_complete_get_reason (skeleton, invocation, reason);
+ return TRUE;
+}
+
+static gboolean
+gsm_inhibitor_get_flags (GsmExportedInhibitor *skeleton,
+ GDBusMethodInvocation *invocation,
+ GsmInhibitor *inhibitor)
+{
+ gsm_exported_inhibitor_complete_get_flags (skeleton, invocation, inhibitor->priv->flags);
+ return TRUE;
+}
+
+static gboolean
+gsm_inhibitor_get_toplevel_xid (GsmExportedInhibitor *skeleton,
+ GDBusMethodInvocation *invocation,
+ GsmInhibitor *inhibitor)
+{
+ gsm_exported_inhibitor_complete_get_toplevel_xid (skeleton, invocation, inhibitor->priv->toplevel_xid);
+ return TRUE;
+}
+
+static guint32
+get_next_inhibitor_serial (void)
+{
+ guint32 serial;
+
+ serial = inhibitor_serial++;
+
+ if ((gint32)inhibitor_serial < 0) {
+ inhibitor_serial = 1;
+ }
+
+ return serial;
+}
+
+static gboolean
+register_inhibitor (GsmInhibitor *inhibitor)
+{
+ GError *error;
+ GsmExportedInhibitor *skeleton;
+
+ error = NULL;
+ inhibitor->priv->connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+
+ if (error != NULL) {
+ g_critical ("error getting session bus: %s", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ skeleton = gsm_exported_inhibitor_skeleton_new ();
+ inhibitor->priv->skeleton = skeleton;
+ g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (skeleton),
+ inhibitor->priv->connection,
+ inhibitor->priv->id, &error);
+
+ if (error != NULL) {
+ g_critical ("error exporting inhibitor on session bus: %s", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ g_signal_connect (skeleton, "handle-get-app-id",
+ G_CALLBACK (gsm_inhibitor_get_app_id), inhibitor);
+ g_signal_connect (skeleton, "handle-get-client-id",
+ G_CALLBACK (gsm_inhibitor_get_client_id), inhibitor);
+ g_signal_connect (skeleton, "handle-get-flags",
+ G_CALLBACK (gsm_inhibitor_get_flags), inhibitor);
+ g_signal_connect (skeleton, "handle-get-reason",
+ G_CALLBACK (gsm_inhibitor_get_reason), inhibitor);
+ g_signal_connect (skeleton, "handle-get-toplevel-xid",
+ G_CALLBACK (gsm_inhibitor_get_toplevel_xid), inhibitor);
+
+ return TRUE;
+}
+
+static GObject *
+gsm_inhibitor_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_properties)
+{
+ GsmInhibitor *inhibitor;
+ gboolean res;
+
+ inhibitor = GSM_INHIBITOR (G_OBJECT_CLASS (gsm_inhibitor_parent_class)->constructor (type,
+ n_construct_properties,
+ construct_properties));
+
+ g_free (inhibitor->priv->id);
+ inhibitor->priv->id = g_strdup_printf ("/org/gnome/SessionManager/Inhibitor%u", get_next_inhibitor_serial ());
+ res = register_inhibitor (inhibitor);
+ if (! res) {
+ g_warning ("Unable to register inhibitor with session bus");
+ }
+
+ return G_OBJECT (inhibitor);
+}
+
+static void
+gsm_inhibitor_init (GsmInhibitor *inhibitor)
+{
+ inhibitor->priv = GSM_INHIBITOR_GET_PRIVATE (inhibitor);
+}
+
+static void
+on_inhibitor_vanished (GDBusConnection *connection,
+ const char *name,
+ gpointer user_data)
+{
+ GsmInhibitor *inhibitor = user_data;
+
+ g_bus_unwatch_name (inhibitor->priv->watch_id);
+ inhibitor->priv->watch_id = 0;
+
+ g_signal_emit (inhibitor, signals[VANISHED], 0);
+}
+
+static void
+gsm_inhibitor_set_bus_name (GsmInhibitor *inhibitor,
+ const char *bus_name)
+{
+ g_return_if_fail (GSM_IS_INHIBITOR (inhibitor));
+
+ g_free (inhibitor->priv->bus_name);
+
+ if (bus_name != NULL) {
+ inhibitor->priv->bus_name = g_strdup (bus_name);
+
+ inhibitor->priv->watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION,
+ bus_name,
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ NULL,
+ on_inhibitor_vanished,
+ inhibitor,
+ NULL);
+ } else {
+ inhibitor->priv->bus_name = g_strdup ("");
+ }
+ g_object_notify (G_OBJECT (inhibitor), "bus-name");
+}
+
+static void
+gsm_inhibitor_set_app_id (GsmInhibitor *inhibitor,
+ const char *app_id)
+{
+ g_return_if_fail (GSM_IS_INHIBITOR (inhibitor));
+
+ g_free (inhibitor->priv->app_id);
+
+ inhibitor->priv->app_id = g_strdup (app_id);
+ g_object_notify (G_OBJECT (inhibitor), "app-id");
+}
+
+static void
+gsm_inhibitor_set_client_id (GsmInhibitor *inhibitor,
+ const char *client_id)
+{
+ g_return_if_fail (GSM_IS_INHIBITOR (inhibitor));
+
+ g_free (inhibitor->priv->client_id);
+
+ g_debug ("GsmInhibitor: setting client-id = %s", client_id);
+
+ if (client_id != NULL) {
+ inhibitor->priv->client_id = g_strdup (client_id);
+ } else {
+ inhibitor->priv->client_id = g_strdup ("");
+ }
+ g_object_notify (G_OBJECT (inhibitor), "client-id");
+}
+
+static void
+gsm_inhibitor_set_reason (GsmInhibitor *inhibitor,
+ const char *reason)
+{
+ g_return_if_fail (GSM_IS_INHIBITOR (inhibitor));
+
+ g_free (inhibitor->priv->reason);
+
+ if (reason != NULL) {
+ inhibitor->priv->reason = g_strdup (reason);
+ } else {
+ inhibitor->priv->reason = g_strdup ("");
+ }
+ g_object_notify (G_OBJECT (inhibitor), "reason");
+}
+
+static void
+gsm_inhibitor_set_cookie (GsmInhibitor *inhibitor,
+ guint cookie)
+{
+ g_return_if_fail (GSM_IS_INHIBITOR (inhibitor));
+
+ if (inhibitor->priv->cookie != cookie) {
+ inhibitor->priv->cookie = cookie;
+ g_object_notify (G_OBJECT (inhibitor), "cookie");
+ }
+}
+
+static void
+gsm_inhibitor_set_flags (GsmInhibitor *inhibitor,
+ guint flags)
+{
+ g_return_if_fail (GSM_IS_INHIBITOR (inhibitor));
+
+ if (inhibitor->priv->flags != flags) {
+ inhibitor->priv->flags = flags;
+ g_object_notify (G_OBJECT (inhibitor), "flags");
+ }
+}
+
+static void
+gsm_inhibitor_set_toplevel_xid (GsmInhibitor *inhibitor,
+ guint xid)
+{
+ g_return_if_fail (GSM_IS_INHIBITOR (inhibitor));
+
+ if (inhibitor->priv->toplevel_xid != xid) {
+ inhibitor->priv->toplevel_xid = xid;
+ g_object_notify (G_OBJECT (inhibitor), "toplevel-xid");
+ }
+}
+
+const char *
+gsm_inhibitor_peek_bus_name (GsmInhibitor *inhibitor)
+{
+ g_return_val_if_fail (GSM_IS_INHIBITOR (inhibitor), NULL);
+
+ return inhibitor->priv->bus_name;
+}
+
+const char *
+gsm_inhibitor_peek_id (GsmInhibitor *inhibitor)
+{
+ g_return_val_if_fail (GSM_IS_INHIBITOR (inhibitor), NULL);
+
+ return inhibitor->priv->id;
+}
+
+const char *
+gsm_inhibitor_peek_app_id (GsmInhibitor *inhibitor)
+{
+ g_return_val_if_fail (GSM_IS_INHIBITOR (inhibitor), NULL);
+
+ return inhibitor->priv->app_id;
+}
+
+const char *
+gsm_inhibitor_peek_client_id (GsmInhibitor *inhibitor)
+{
+ g_return_val_if_fail (GSM_IS_INHIBITOR (inhibitor), NULL);
+
+ return inhibitor->priv->client_id;
+}
+
+const char *
+gsm_inhibitor_peek_reason (GsmInhibitor *inhibitor)
+{
+ g_return_val_if_fail (GSM_IS_INHIBITOR (inhibitor), NULL);
+
+ return inhibitor->priv->reason;
+}
+
+guint
+gsm_inhibitor_peek_flags (GsmInhibitor *inhibitor)
+{
+ g_return_val_if_fail (GSM_IS_INHIBITOR (inhibitor), 0);
+
+ return inhibitor->priv->flags;
+}
+
+guint
+gsm_inhibitor_peek_toplevel_xid (GsmInhibitor *inhibitor)
+{
+ g_return_val_if_fail (GSM_IS_INHIBITOR (inhibitor), 0);
+
+ return inhibitor->priv->toplevel_xid;
+}
+
+guint
+gsm_inhibitor_peek_cookie (GsmInhibitor *inhibitor)
+{
+ g_return_val_if_fail (GSM_IS_INHIBITOR (inhibitor), 0);
+
+ return inhibitor->priv->cookie;
+}
+
+static void
+gsm_inhibitor_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GsmInhibitor *self;
+
+ self = GSM_INHIBITOR (object);
+
+ switch (prop_id) {
+ case PROP_BUS_NAME:
+ gsm_inhibitor_set_bus_name (self, g_value_get_string (value));
+ break;
+ case PROP_APP_ID:
+ gsm_inhibitor_set_app_id (self, g_value_get_string (value));
+ break;
+ case PROP_CLIENT_ID:
+ gsm_inhibitor_set_client_id (self, g_value_get_string (value));
+ break;
+ case PROP_REASON:
+ gsm_inhibitor_set_reason (self, g_value_get_string (value));
+ break;
+ case PROP_FLAGS:
+ gsm_inhibitor_set_flags (self, g_value_get_uint (value));
+ break;
+ case PROP_COOKIE:
+ gsm_inhibitor_set_cookie (self, g_value_get_uint (value));
+ break;
+ case PROP_TOPLEVEL_XID:
+ gsm_inhibitor_set_toplevel_xid (self, g_value_get_uint (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gsm_inhibitor_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GsmInhibitor *self;
+
+ self = GSM_INHIBITOR (object);
+
+ switch (prop_id) {
+ case PROP_BUS_NAME:
+ g_value_set_string (value, self->priv->bus_name);
+ break;
+ case PROP_APP_ID:
+ g_value_set_string (value, self->priv->app_id);
+ break;
+ case PROP_CLIENT_ID:
+ g_value_set_string (value, self->priv->client_id);
+ break;
+ case PROP_REASON:
+ g_value_set_string (value, self->priv->reason);
+ break;
+ case PROP_FLAGS:
+ g_value_set_uint (value, self->priv->flags);
+ break;
+ case PROP_COOKIE:
+ g_value_set_uint (value, self->priv->cookie);
+ break;
+ case PROP_TOPLEVEL_XID:
+ g_value_set_uint (value, self->priv->toplevel_xid);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gsm_inhibitor_finalize (GObject *object)
+{
+ GsmInhibitor *inhibitor = (GsmInhibitor *) object;
+
+ g_free (inhibitor->priv->id);
+ g_free (inhibitor->priv->bus_name);
+ g_free (inhibitor->priv->app_id);
+ g_free (inhibitor->priv->client_id);
+ g_free (inhibitor->priv->reason);
+
+ if (inhibitor->priv->skeleton != NULL) {
+ g_dbus_interface_skeleton_unexport_from_connection (G_DBUS_INTERFACE_SKELETON (inhibitor->priv->skeleton),
+ inhibitor->priv->connection);
+ g_clear_object (&inhibitor->priv->skeleton);
+ }
+
+ if (inhibitor->priv->watch_id != 0) {
+ g_bus_unwatch_name (inhibitor->priv->watch_id);
+ }
+
+ g_clear_object (&inhibitor->priv->connection);
+
+ G_OBJECT_CLASS (gsm_inhibitor_parent_class)->finalize (object);
+}
+
+static void
+gsm_inhibitor_class_init (GsmInhibitorClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gsm_inhibitor_finalize;
+ object_class->constructor = gsm_inhibitor_constructor;
+ object_class->get_property = gsm_inhibitor_get_property;
+ object_class->set_property = gsm_inhibitor_set_property;
+
+ signals[VANISHED] =
+ g_signal_new ("vanished",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 0);
+ g_object_class_install_property (object_class,
+ PROP_BUS_NAME,
+ g_param_spec_string ("bus-name",
+ "bus-name",
+ "bus-name",
+ "",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+ g_object_class_install_property (object_class,
+ PROP_APP_ID,
+ g_param_spec_string ("app-id",
+ "app-id",
+ "app-id",
+ "",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+ g_object_class_install_property (object_class,
+ PROP_CLIENT_ID,
+ g_param_spec_string ("client-id",
+ "client-id",
+ "client-id",
+ "",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+ g_object_class_install_property (object_class,
+ PROP_REASON,
+ g_param_spec_string ("reason",
+ "reason",
+ "reason",
+ "",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+ g_object_class_install_property (object_class,
+ PROP_FLAGS,
+ g_param_spec_uint ("flags",
+ "flags",
+ "flags",
+ 0,
+ G_MAXINT,
+ 0,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+ g_object_class_install_property (object_class,
+ PROP_TOPLEVEL_XID,
+ g_param_spec_uint ("toplevel-xid",
+ "toplevel-xid",
+ "toplevel-xid",
+ 0,
+ G_MAXINT,
+ 0,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+ g_object_class_install_property (object_class,
+ PROP_COOKIE,
+ g_param_spec_uint ("cookie",
+ "cookie",
+ "cookie",
+ 0,
+ G_MAXINT,
+ 0,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ g_type_class_add_private (klass, sizeof (GsmInhibitorPrivate));
+}
+
+GsmInhibitor *
+gsm_inhibitor_new (const char *app_id,
+ guint toplevel_xid,
+ guint flags,
+ const char *reason,
+ const char *bus_name,
+ guint cookie)
+{
+ GsmInhibitor *inhibitor;
+
+ inhibitor = g_object_new (GSM_TYPE_INHIBITOR,
+ "app-id", app_id,
+ "reason", reason,
+ "bus-name", bus_name,
+ "flags", flags,
+ "toplevel-xid", toplevel_xid,
+ "cookie", cookie,
+ NULL);
+
+ return inhibitor;
+}
+
+GsmInhibitor *
+gsm_inhibitor_new_for_client (const char *client_id,
+ const char *app_id,
+ guint flags,
+ const char *reason,
+ const char *bus_name,
+ guint cookie)
+{
+ GsmInhibitor *inhibitor;
+
+ inhibitor = g_object_new (GSM_TYPE_INHIBITOR,
+ "client-id", client_id,
+ "app-id", app_id,
+ "reason", reason,
+ "bus-name", bus_name,
+ "flags", flags,
+ "cookie", cookie,
+ NULL);
+
+ return inhibitor;
+}
diff --git a/gnome-session/gsm-inhibitor.h b/gnome-session/gsm-inhibitor.h
new file mode 100644
index 0000000..5229fe1
--- /dev/null
+++ b/gnome-session/gsm-inhibitor.h
@@ -0,0 +1,88 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * 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/>.
+ */
+
+#ifndef __GSM_INHIBITOR_H__
+#define __GSM_INHIBITOR_H__
+
+#include <glib-object.h>
+#include <sys/types.h>
+
+#include "gsm-inhibitor-flag.h"
+
+G_BEGIN_DECLS
+
+#define GSM_TYPE_INHIBITOR (gsm_inhibitor_get_type ())
+#define GSM_INHIBITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_INHIBITOR, GsmInhibitor))
+#define GSM_INHIBITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_INHIBITOR, GsmInhibitorClass))
+#define GSM_IS_INHIBITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_INHIBITOR))
+#define GSM_IS_INHIBITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_INHIBITOR))
+#define GSM_INHIBITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSM_TYPE_INHIBITOR, GsmInhibitorClass))
+
+typedef struct _GsmInhibitor GsmInhibitor;
+typedef struct _GsmInhibitorClass GsmInhibitorClass;
+
+typedef struct GsmInhibitorPrivate GsmInhibitorPrivate;
+
+struct _GsmInhibitor
+{
+ GObject parent;
+ GsmInhibitorPrivate *priv;
+};
+
+struct _GsmInhibitorClass
+{
+ GObjectClass parent_class;
+};
+
+typedef enum
+{
+ GSM_INHIBITOR_ERROR_GENERAL = 0,
+ GSM_INHIBITOR_ERROR_NOT_SET,
+ GSM_INHIBITOR_NUM_ERRORS
+} GsmInhibitorError;
+
+#define GSM_INHIBITOR_ERROR gsm_inhibitor_error_quark ()
+GQuark gsm_inhibitor_error_quark (void);
+
+GType gsm_inhibitor_get_type (void) G_GNUC_CONST;
+
+GsmInhibitor * gsm_inhibitor_new (const char *app_id,
+ guint toplevel_xid,
+ guint flags,
+ const char *reason,
+ const char *bus_name,
+ guint cookie);
+GsmInhibitor * gsm_inhibitor_new_for_client (const char *client_id,
+ const char *app_id,
+ guint flags,
+ const char *reason,
+ const char *bus_name,
+ guint cookie);
+
+const char * gsm_inhibitor_peek_id (GsmInhibitor *inhibitor);
+const char * gsm_inhibitor_peek_app_id (GsmInhibitor *inhibitor);
+const char * gsm_inhibitor_peek_client_id (GsmInhibitor *inhibitor);
+const char * gsm_inhibitor_peek_reason (GsmInhibitor *inhibitor);
+const char * gsm_inhibitor_peek_bus_name (GsmInhibitor *inhibitor);
+guint gsm_inhibitor_peek_cookie (GsmInhibitor *inhibitor);
+guint gsm_inhibitor_peek_flags (GsmInhibitor *inhibitor);
+guint gsm_inhibitor_peek_toplevel_xid (GsmInhibitor *inhibitor);
+
+G_END_DECLS
+
+#endif /* __GSM_INHIBITOR_H__ */
diff --git a/gnome-session/gsm-manager-logout-mode.h b/gnome-session/gsm-manager-logout-mode.h
new file mode 100644
index 0000000..7b51751
--- /dev/null
+++ b/gnome-session/gsm-manager-logout-mode.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 William Jon McCann <jmccann@redhat.com>
+ *
+ * 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 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/>.
+ *
+ */
+
+
+#ifndef __GSM_MANAGER_LOGOUT_MODE_H
+#define __GSM_MANAGER_LOGOUT_MODE_H
+
+G_BEGIN_DECLS
+
+typedef enum {
+ GSM_MANAGER_LOGOUT_MODE_NORMAL = 0,
+ GSM_MANAGER_LOGOUT_MODE_NO_CONFIRMATION,
+ GSM_MANAGER_LOGOUT_MODE_FORCE
+} GsmManagerLogoutMode;
+
+G_END_DECLS
+
+#endif /* __GSM_MANAGER_LOGOUT_MODE_H */
diff --git a/gnome-session/gsm-manager.c b/gnome-session/gsm-manager.c
new file mode 100644
index 0000000..1b88b26
--- /dev/null
+++ b/gnome-session/gsm-manager.c
@@ -0,0 +1,3928 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 Novell, Inc.
+ * Copyright (C) 2008 Red Hat, Inc.
+ * Copyright (C) 2008 William Jon McCann <jmccann@redhat.com>
+ *
+ * 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 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 <stdlib.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <signal.h>
+#include <locale.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include "gsm-manager.h"
+#include "org.gnome.SessionManager.h"
+
+#ifdef ENABLE_SYSTEMD_JOURNAL
+#include <systemd/sd-journal.h>
+#endif
+
+#ifdef HAVE_SYSTEMD
+#include <systemd/sd-daemon.h>
+#else
+/* So we don't need to add ifdef's everywhere */
+#define sd_notify(u, m) do {} while (0)
+#define sd_notifyf(u, m, ...) do {} while (0)
+#endif
+
+#include "gsm-store.h"
+#include "gsm-inhibitor.h"
+#include "gsm-presence.h"
+#include "gsm-shell.h"
+
+#include "gsm-xsmp-server.h"
+#include "gsm-xsmp-client.h"
+#include "gsm-dbus-client.h"
+
+#include "gsm-autostart-app.h"
+
+#include "gsm-util.h"
+#include "gsm-icon-names.h"
+#include "gsm-system.h"
+#include "gsm-session-save.h"
+#include "gsm-shell-extensions.h"
+#include "gsm-fail-whale.h"
+
+/* UUIDs for log messages */
+#define GSM_MANAGER_STARTUP_SUCCEEDED_MSGID "0ce153587afa4095832d233c17a88001"
+#define GSM_MANAGER_UNRECOVERABLE_FAILURE_MSGID "10dd2dc188b54a5e98970f56499d1f73"
+
+#define GSM_MANAGER_DBUS_PATH "/org/gnome/SessionManager"
+#define GSM_MANAGER_DBUS_NAME "org.gnome.SessionManager"
+#define GSM_MANAGER_DBUS_IFACE "org.gnome.SessionManager"
+
+/* Probably about the longest amount of time someone could reasonably
+ * want to wait, at least for something happening more than once.
+ * We can get deployed on very slow media though like CDROM devices,
+ * often with complex stacking/compressing filesystems on top, which
+ * is not a recipie for speed. Particularly now that we throw up
+ * a fail whale if required components don't show up quickly enough,
+ * let's make this fairly long.
+ */
+#define GSM_MANAGER_PHASE_TIMEOUT 90 /* seconds */
+
+#define GDM_FLEXISERVER_COMMAND "gdmflexiserver"
+#define GDM_FLEXISERVER_ARGS "--startnew Standard"
+
+#define SESSION_SCHEMA "org.gnome.desktop.session"
+#define KEY_IDLE_DELAY "idle-delay"
+#define KEY_SESSION_NAME "session-name"
+
+#define GSM_MANAGER_SCHEMA "org.gnome.SessionManager"
+#define KEY_AUTOSAVE "auto-save-session"
+#define KEY_AUTOSAVE_ONE_SHOT "auto-save-session-one-shot"
+#define KEY_LOGOUT_PROMPT "logout-prompt"
+#define KEY_SHOW_FALLBACK_WARNING "show-fallback-warning"
+
+#define SCREENSAVER_SCHEMA "org.gnome.desktop.screensaver"
+#define KEY_SLEEP_LOCK "lock-enabled"
+
+#define LOCKDOWN_SCHEMA "org.gnome.desktop.lockdown"
+#define KEY_DISABLE_LOG_OUT "disable-log-out"
+#define KEY_DISABLE_USER_SWITCHING "disable-user-switching"
+
+static void app_registered (GsmApp *app, GParamSpec *spec, GsmManager *manager);
+
+typedef enum
+{
+ GSM_MANAGER_LOGOUT_NONE,
+ GSM_MANAGER_LOGOUT_LOGOUT,
+ GSM_MANAGER_LOGOUT_REBOOT,
+ GSM_MANAGER_LOGOUT_REBOOT_INTERACT,
+ GSM_MANAGER_LOGOUT_SHUTDOWN,
+ GSM_MANAGER_LOGOUT_SHUTDOWN_INTERACT,
+} GsmManagerLogoutType;
+
+typedef struct
+{
+ gboolean failsafe;
+ gboolean systemd_managed;
+ gboolean systemd_initialized;
+ gboolean manager_initialized;
+ GsmStore *clients;
+ GsmStore *inhibitors;
+ GsmInhibitorFlag inhibited_actions;
+ GsmStore *apps;
+ GsmPresence *presence;
+ GsmXsmpServer *xsmp_server;
+
+ char *session_name;
+ gboolean is_fallback_session : 1;
+
+ /* Current status */
+ GsmManagerPhase phase;
+ guint phase_timeout_id;
+ GSList *required_apps;
+ GSList *pending_apps;
+ GsmManagerLogoutMode logout_mode;
+ GSList *query_clients;
+ guint query_timeout_id;
+ /* This is used for GSM_MANAGER_PHASE_END_SESSION only at the moment,
+ * since it uses a sublist of all running client that replied in a
+ * specific way */
+ GSList *next_query_clients;
+ /* This is the action that will be done just before we exit */
+ GsmManagerLogoutType logout_type;
+
+ /* List of clients which were disconnected due to disabled condition
+ * and shouldn't be automatically restarted */
+ GSList *condition_clients;
+
+ GSList *pending_end_session_tasks;
+ GCancellable *end_session_cancellable;
+
+ GSettings *settings;
+ GSettings *session_settings;
+ GSettings *screensaver_settings;
+ GSettings *lockdown_settings;
+
+ GsmSystem *system;
+ GDBusConnection *connection;
+ GsmExportedManager *skeleton;
+ gboolean dbus_disconnected : 1;
+
+ GsmShell *shell;
+ guint shell_end_session_dialog_canceled_id;
+ guint shell_end_session_dialog_open_failed_id;
+ guint shell_end_session_dialog_confirmed_logout_id;
+ guint shell_end_session_dialog_confirmed_shutdown_id;
+ guint shell_end_session_dialog_confirmed_reboot_id;
+} GsmManagerPrivate;
+
+enum {
+ PROP_0,
+ PROP_CLIENT_STORE,
+ PROP_SESSION_NAME,
+ PROP_FALLBACK,
+ PROP_FAILSAFE,
+ PROP_SYSTEMD_MANAGED
+};
+
+enum {
+ PHASE_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0 };
+
+static void gsm_manager_class_init (GsmManagerClass *klass);
+static void gsm_manager_init (GsmManager *manager);
+
+static gboolean auto_save_is_enabled (GsmManager *manager);
+static void maybe_save_session (GsmManager *manager);
+
+static gboolean _log_out_is_locked_down (GsmManager *manager);
+
+static void _handle_client_end_session_response (GsmManager *manager,
+ GsmClient *client,
+ gboolean is_ok,
+ gboolean do_last,
+ gboolean cancel,
+ const char *reason);
+static void show_shell_end_session_dialog (GsmManager *manager,
+ GsmShellEndSessionDialogType type);
+static gpointer manager_object = NULL;
+
+G_DEFINE_TYPE_WITH_PRIVATE (GsmManager, gsm_manager, G_TYPE_OBJECT)
+
+static const GDBusErrorEntry gsm_manager_error_entries[] = {
+ { GSM_MANAGER_ERROR_GENERAL, GSM_MANAGER_DBUS_IFACE ".GeneralError" },
+ { GSM_MANAGER_ERROR_NOT_IN_INITIALIZATION, GSM_MANAGER_DBUS_IFACE ".NotInInitialization" },
+ { GSM_MANAGER_ERROR_NOT_IN_RUNNING, GSM_MANAGER_DBUS_IFACE ".NotInRunning" },
+ { GSM_MANAGER_ERROR_ALREADY_REGISTERED, GSM_MANAGER_DBUS_IFACE ".AlreadyRegistered" },
+ { GSM_MANAGER_ERROR_NOT_REGISTERED, GSM_MANAGER_DBUS_IFACE ".NotRegistered" },
+ { GSM_MANAGER_ERROR_INVALID_OPTION, GSM_MANAGER_DBUS_IFACE ".InvalidOption" },
+ { GSM_MANAGER_ERROR_LOCKED_DOWN, GSM_MANAGER_DBUS_IFACE ".LockedDown" }
+};
+
+GQuark
+gsm_manager_error_quark (void)
+{
+ static volatile gsize quark_volatile = 0;
+
+ g_dbus_error_register_error_domain ("gsm_manager_error",
+ &quark_volatile,
+ gsm_manager_error_entries,
+ G_N_ELEMENTS (gsm_manager_error_entries));
+ return quark_volatile;
+}
+
+static gboolean
+start_app_or_warn (GsmManager *manager,
+ GsmApp *app)
+{
+ gboolean res;
+ GError *error = NULL;
+
+ g_debug ("GsmManager: starting app '%s'", gsm_app_peek_id (app));
+
+ res = gsm_app_start (app, &error);
+ if (error != NULL) {
+ g_warning ("Failed to start app: %s", error->message);
+ g_clear_error (&error);
+ }
+ return res;
+}
+
+static gboolean
+is_app_required (GsmManager *manager,
+ GsmApp *app)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ return g_slist_find (priv->required_apps, app) != NULL;
+}
+
+static void
+on_required_app_failure (GsmManager *manager,
+ GsmApp *app)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ const gchar *app_id;
+ gboolean allow_logout;
+ GsmShellExtensions *extensions;
+
+ app_id = gsm_app_peek_app_id (app);
+
+ if (g_str_equal (app_id, "org.gnome.Shell.desktop")) {
+ extensions = g_object_new (GSM_TYPE_SHELL_EXTENSIONS, NULL);
+ gsm_shell_extensions_disable_all (extensions);
+ } else {
+ extensions = NULL;
+ }
+
+ if (gsm_system_is_login_session (priv->system)) {
+ allow_logout = FALSE;
+ } else {
+ allow_logout = !_log_out_is_locked_down (manager);
+ }
+
+#ifdef ENABLE_SYSTEMD_JOURNAL
+ sd_journal_send ("MESSAGE_ID=%s", GSM_MANAGER_UNRECOVERABLE_FAILURE_MSGID,
+ "PRIORITY=%d", 3,
+ "MESSAGE=Unrecoverable failure in required component %s", app_id,
+ NULL);
+#endif
+
+ gsm_fail_whale_dialog_we_failed (FALSE,
+ allow_logout,
+ extensions);
+}
+
+static void
+on_display_server_failure (GsmManager *manager,
+ GsmApp *app)
+{
+ const gchar *app_id;
+ GsmShellExtensions *extensions;
+
+ app_id = gsm_app_peek_app_id (app);
+
+ if (g_str_equal (app_id, "org.gnome.Shell.desktop")) {
+ extensions = g_object_new (GSM_TYPE_SHELL_EXTENSIONS, NULL);
+ gsm_shell_extensions_disable_all (extensions);
+
+ g_object_unref (extensions);
+ } else {
+ extensions = NULL;
+ }
+
+#ifdef ENABLE_SYSTEMD_JOURNAL
+ sd_journal_send ("MESSAGE_ID=%s", GSM_MANAGER_UNRECOVERABLE_FAILURE_MSGID,
+ "PRIORITY=%d", 3,
+ "MESSAGE=Unrecoverable failure in required component %s", app_id,
+ NULL);
+#endif
+
+ gsm_quit ();
+}
+
+static gboolean
+_debug_client (const char *id,
+ GsmClient *client,
+ GsmManager *manager)
+{
+ g_debug ("GsmManager: Client %s", gsm_client_peek_id (client));
+ return FALSE;
+}
+
+static void
+debug_clients (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ gsm_store_foreach (priv->clients,
+ (GsmStoreFunc)_debug_client,
+ manager);
+}
+
+static gboolean
+_find_by_cookie (const char *id,
+ GsmInhibitor *inhibitor,
+ guint *cookie_ap)
+{
+ guint cookie_b;
+
+ cookie_b = gsm_inhibitor_peek_cookie (inhibitor);
+
+ return (*cookie_ap == cookie_b);
+}
+
+static gboolean
+_client_has_startup_id (const char *id,
+ GsmClient *client,
+ const char *startup_id_a)
+{
+ const char *startup_id_b;
+
+ startup_id_b = gsm_client_peek_startup_id (client);
+ if (IS_STRING_EMPTY (startup_id_b)) {
+ return FALSE;
+ }
+
+ return (strcmp (startup_id_a, startup_id_b) == 0);
+}
+
+static void
+app_condition_changed (GsmApp *app,
+ gboolean condition,
+ GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ GsmClient *client;
+
+ g_debug ("GsmManager: app:%s condition changed condition:%d",
+ gsm_app_peek_id (app),
+ condition);
+
+ client = (GsmClient *)gsm_store_find (priv->clients,
+ (GsmStoreFunc)_client_has_startup_id,
+ (char *)gsm_app_peek_startup_id (app));
+
+ if (condition) {
+ if (!gsm_app_is_running (app) && client == NULL) {
+ start_app_or_warn (manager, app);
+ } else {
+ g_debug ("GsmManager: not starting - app still running '%s'", gsm_app_peek_id (app));
+ }
+ } else {
+ GError *error;
+ gboolean res;
+
+ if (client != NULL) {
+ /* Kill client in case condition if false and make sure it won't
+ * be automatically restarted by adding the client to
+ * condition_clients */
+ priv->condition_clients =
+ g_slist_prepend (priv->condition_clients, client);
+
+ g_debug ("GsmManager: stopping client %s for app", gsm_client_peek_id (client));
+
+ error = NULL;
+ res = gsm_client_stop (client, &error);
+ if (! res) {
+ g_warning ("Not able to stop app client from its condition: %s",
+ error->message);
+ g_error_free (error);
+ }
+ } 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 */
+ error = NULL;
+ res = gsm_app_stop (app, &error);
+ if (! res) {
+ g_warning ("Not able to stop app from its condition: %s",
+ error->message);
+ g_error_free (error);
+ }
+ }
+ }
+}
+
+static const char *
+phase_num_to_name (guint phase)
+{
+ const char *name;
+
+ switch (phase) {
+ case GSM_MANAGER_PHASE_STARTUP:
+ name = "STARTUP";
+ break;
+ case GSM_MANAGER_PHASE_EARLY_INITIALIZATION:
+ name = "EARLY_INITIALIZATION";
+ break;
+ case GSM_MANAGER_PHASE_PRE_DISPLAY_SERVER:
+ name = "PRE_DISPLAY_SERVER";
+ break;
+ case GSM_MANAGER_PHASE_DISPLAY_SERVER:
+ name = "DISPLAY_SERVER";
+ break;
+ case GSM_MANAGER_PHASE_INITIALIZATION:
+ name = "INITIALIZATION";
+ break;
+ case GSM_MANAGER_PHASE_WINDOW_MANAGER:
+ name = "WINDOW_MANAGER";
+ break;
+ case GSM_MANAGER_PHASE_PANEL:
+ name = "PANEL";
+ break;
+ case GSM_MANAGER_PHASE_DESKTOP:
+ name = "DESKTOP";
+ break;
+ case GSM_MANAGER_PHASE_APPLICATION:
+ name = "APPLICATION";
+ break;
+ case GSM_MANAGER_PHASE_RUNNING:
+ name = "RUNNING";
+ break;
+ case GSM_MANAGER_PHASE_QUERY_END_SESSION:
+ name = "QUERY_END_SESSION";
+ break;
+ case GSM_MANAGER_PHASE_END_SESSION:
+ name = "END_SESSION";
+ break;
+ case GSM_MANAGER_PHASE_EXIT:
+ name = "EXIT";
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ return name;
+}
+
+static void start_phase (GsmManager *manager);
+
+static void
+gsm_manager_quit (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ /* See the comment in request_reboot() for some more details about how
+ * this works. */
+
+ switch (priv->logout_type) {
+ case GSM_MANAGER_LOGOUT_LOGOUT:
+ case GSM_MANAGER_LOGOUT_NONE:
+ gsm_quit ();
+ break;
+ case GSM_MANAGER_LOGOUT_REBOOT:
+ case GSM_MANAGER_LOGOUT_REBOOT_INTERACT:
+ gsm_system_complete_shutdown (priv->system);
+ gsm_quit ();
+ break;
+ case GSM_MANAGER_LOGOUT_SHUTDOWN:
+ case GSM_MANAGER_LOGOUT_SHUTDOWN_INTERACT:
+ gsm_system_complete_shutdown (priv->system);
+ gsm_quit ();
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static gboolean do_query_end_session_exit (GsmManager *manager);
+
+static void
+end_phase (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ gboolean start_next_phase = TRUE;
+
+ g_debug ("GsmManager: ending phase %s",
+ phase_num_to_name (priv->phase));
+
+ g_slist_free (priv->pending_apps);
+ priv->pending_apps = NULL;
+
+ g_slist_free (priv->query_clients);
+ priv->query_clients = NULL;
+
+ g_slist_free (priv->next_query_clients);
+ priv->next_query_clients = NULL;
+
+ if (priv->query_timeout_id > 0) {
+ g_source_remove (priv->query_timeout_id);
+ priv->query_timeout_id = 0;
+ }
+ if (priv->phase_timeout_id > 0) {
+ g_source_remove (priv->phase_timeout_id);
+ priv->phase_timeout_id = 0;
+ }
+
+ switch (priv->phase) {
+ case GSM_MANAGER_PHASE_STARTUP:
+ case GSM_MANAGER_PHASE_EARLY_INITIALIZATION:
+ case GSM_MANAGER_PHASE_PRE_DISPLAY_SERVER:
+ case GSM_MANAGER_PHASE_DISPLAY_SERVER:
+ break;
+ case GSM_MANAGER_PHASE_INITIALIZATION:
+ priv->manager_initialized = TRUE;
+ /* Wait for systemd if it isn't initialized yet*/
+ if (priv->systemd_managed && !priv->systemd_initialized) {
+ sd_notify (0, "STATUS=GNOME Session Manager waiting for gnome-session-initialized.target (via signal)");
+ start_next_phase = FALSE;
+ }
+ break;
+ case GSM_MANAGER_PHASE_WINDOW_MANAGER:
+ case GSM_MANAGER_PHASE_PANEL:
+ case GSM_MANAGER_PHASE_DESKTOP:
+ case GSM_MANAGER_PHASE_APPLICATION:
+ break;
+ case GSM_MANAGER_PHASE_RUNNING:
+ if (_log_out_is_locked_down (manager)) {
+ g_warning ("Unable to logout: Logout has been locked down");
+ start_next_phase = FALSE;
+ }
+ break;
+ case GSM_MANAGER_PHASE_QUERY_END_SESSION:
+ if (!do_query_end_session_exit (manager))
+ start_next_phase = FALSE;
+ break;
+ case GSM_MANAGER_PHASE_END_SESSION:
+ maybe_save_session (manager);
+ break;
+ case GSM_MANAGER_PHASE_EXIT:
+ start_next_phase = FALSE;
+ gsm_manager_quit (manager);
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ if (start_next_phase) {
+ priv->phase++;
+ start_phase (manager);
+ }
+}
+
+static void
+app_event_during_startup (GsmManager *manager,
+ GsmApp *app)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ if (!(priv->phase < GSM_MANAGER_PHASE_APPLICATION))
+ return;
+
+ priv->pending_apps = g_slist_remove (priv->pending_apps, app);
+
+ if (priv->pending_apps == NULL) {
+ if (priv->phase_timeout_id > 0) {
+ g_source_remove (priv->phase_timeout_id);
+ priv->phase_timeout_id = 0;
+ }
+
+ end_phase (manager);
+ }
+}
+
+static gboolean
+is_app_display_server (GsmManager *manager,
+ GsmApp *app)
+{
+ GsmManagerPhase phase;
+
+ /* Apps can only really act as a display server if
+ * we're a wayland session.
+ */
+ if (g_strcmp0 (g_getenv ("XDG_SESSION_TYPE"), "wayland") != 0)
+ return FALSE;
+
+ phase = gsm_app_peek_phase (app);
+
+ return (phase == GSM_MANAGER_PHASE_DISPLAY_SERVER &&
+ is_app_required (manager, app));
+}
+
+static void
+_restart_app (GsmManager *manager,
+ GsmApp *app)
+{
+ GError *error = NULL;
+
+ if (is_app_display_server (manager, app)) {
+ on_display_server_failure (manager, app);
+ return;
+ }
+
+ if (!gsm_app_restart (app, &error)) {
+ if (is_app_required (manager, app)) {
+ on_required_app_failure (manager, app);
+ } else {
+ g_warning ("Error on restarting session managed app: %s", error->message);
+ }
+ g_clear_error (&error);
+
+ app_event_during_startup (manager, app);
+ }
+}
+
+static void
+app_died (GsmApp *app,
+ int signal,
+ GsmManager *manager)
+{
+ g_warning ("Application '%s' killed by signal %d", gsm_app_peek_app_id (app), signal);
+
+ if (gsm_app_get_registered (app) && gsm_app_peek_autorestart (app)) {
+ g_debug ("Component '%s' is autorestart, ignoring died signal",
+ gsm_app_peek_app_id (app));
+ return;
+ }
+
+ _restart_app (manager, app);
+
+ /* For now, we don't do anything with crashes from
+ * non-required apps after they hit the restart limit.
+ *
+ * Note that both required and not-required apps will be
+ * caught by ABRT/apport type infrastructure, and it'd be
+ * better to pick up the crash from there and do something
+ * un-intrusive about it generically.
+ */
+}
+
+static void
+app_exited (GsmApp *app,
+ guchar exit_code,
+ GsmManager *manager)
+{
+ if (exit_code != 0)
+ g_warning ("App '%s' exited with code %d", gsm_app_peek_app_id (app), exit_code);
+ else
+ g_debug ("App %s exited successfully", gsm_app_peek_app_id (app));
+
+ /* Consider that non-success exit status means "crash" for required components */
+ if (exit_code != 0 && is_app_required (manager, app)) {
+ if (gsm_app_get_registered (app) && gsm_app_peek_autorestart (app)) {
+ g_debug ("Component '%s' is autorestart, ignoring non-successful exit",
+ gsm_app_peek_app_id (app));
+ return;
+ }
+
+ _restart_app (manager, app);
+ } else {
+ app_event_during_startup (manager, app);
+ }
+}
+
+static void
+app_registered (GsmApp *app,
+ GParamSpec *spec,
+ GsmManager *manager)
+{
+ if (!gsm_app_get_registered (app)) {
+ return;
+ }
+
+ g_debug ("App %s registered", gsm_app_peek_app_id (app));
+
+ app_event_during_startup (manager, app);
+}
+
+static gboolean
+on_phase_timeout (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ GSList *a;
+
+ priv->phase_timeout_id = 0;
+
+ switch (priv->phase) {
+ case GSM_MANAGER_PHASE_STARTUP:
+ case GSM_MANAGER_PHASE_EARLY_INITIALIZATION:
+ case GSM_MANAGER_PHASE_PRE_DISPLAY_SERVER:
+ case GSM_MANAGER_PHASE_DISPLAY_SERVER:
+ case GSM_MANAGER_PHASE_INITIALIZATION:
+ case GSM_MANAGER_PHASE_WINDOW_MANAGER:
+ case GSM_MANAGER_PHASE_PANEL:
+ case GSM_MANAGER_PHASE_DESKTOP:
+ case GSM_MANAGER_PHASE_APPLICATION:
+ for (a = priv->pending_apps; a; a = a->next) {
+ GsmApp *app = a->data;
+ g_warning ("Application '%s' failed to register before timeout",
+ gsm_app_peek_app_id (app));
+ if (is_app_required (manager, app))
+ on_required_app_failure (manager, app);
+ }
+ break;
+ case GSM_MANAGER_PHASE_RUNNING:
+ break;
+ case GSM_MANAGER_PHASE_QUERY_END_SESSION:
+ case GSM_MANAGER_PHASE_END_SESSION:
+ break;
+ case GSM_MANAGER_PHASE_EXIT:
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ end_phase (manager);
+
+ return FALSE;
+}
+
+static gboolean
+_start_app (const char *id,
+ GsmApp *app,
+ GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ if (gsm_app_peek_phase (app) != priv->phase) {
+ goto out;
+ }
+
+ /* Keep track of app autostart condition in order to react
+ * accordingly in the future. */
+ g_signal_connect (app,
+ "condition-changed",
+ G_CALLBACK (app_condition_changed),
+ manager);
+
+ if (gsm_app_peek_is_disabled (app)
+ || gsm_app_peek_is_conditionally_disabled (app)) {
+ g_debug ("GsmManager: Skipping disabled app: %s", id);
+ goto out;
+ }
+
+ if (!start_app_or_warn (manager, app))
+ goto out;
+
+ if (priv->phase < GSM_MANAGER_PHASE_APPLICATION) {
+ /* Historical note - apparently,
+ * e.g. gnome-settings-daemon used to "daemonize", and
+ * so gnome-session assumes process exit means "ok
+ * we're done". Of course this is broken, we don't
+ * even distinguish between exit code 0 versus not-0,
+ * nor do we have any metadata which tells us a
+ * process is going to "daemonize" or not (and
+ * basically nothing should be anyways).
+ */
+ g_signal_connect (app,
+ "exited",
+ G_CALLBACK (app_exited),
+ manager);
+ g_signal_connect (app,
+ "notify::registered",
+ G_CALLBACK (app_registered),
+ manager);
+ g_signal_connect (app,
+ "died",
+ G_CALLBACK (app_died),
+ manager);
+ priv->pending_apps = g_slist_prepend (priv->pending_apps, app);
+ }
+ out:
+ return FALSE;
+}
+
+static void
+do_phase_startup (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ gsm_store_foreach (priv->apps,
+ (GsmStoreFunc)_start_app,
+ manager);
+
+ if (priv->pending_apps != NULL) {
+ if (priv->phase < GSM_MANAGER_PHASE_APPLICATION) {
+ priv->phase_timeout_id = g_timeout_add_seconds (GSM_MANAGER_PHASE_TIMEOUT,
+ (GSourceFunc)on_phase_timeout,
+ manager);
+ }
+ } else {
+ end_phase (manager);
+ }
+}
+
+typedef struct {
+ GsmManager *manager;
+ guint flags;
+} ClientEndSessionData;
+
+
+static gboolean
+_client_end_session (GsmClient *client,
+ ClientEndSessionData *data)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (data->manager);
+ gboolean ret;
+ GError *error;
+
+ error = NULL;
+ ret = gsm_client_end_session (client, data->flags, &error);
+ if (! ret) {
+ g_warning ("Unable to query client: %s", error->message);
+ g_error_free (error);
+ /* FIXME: what should we do if we can't communicate with client? */
+ } else {
+ g_debug ("GsmManager: adding client to end-session clients: %s", gsm_client_peek_id (client));
+ priv->query_clients = g_slist_prepend (priv->query_clients, client);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+_client_end_session_helper (const char *id,
+ GsmClient *client,
+ ClientEndSessionData *data)
+{
+ return _client_end_session (client, data);
+}
+
+static void
+complete_end_session_tasks (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ GSList *l;
+
+ for (l = priv->pending_end_session_tasks;
+ l != NULL;
+ l = l->next) {
+ GTask *task = G_TASK (l->data);
+ if (!g_task_return_error_if_cancelled (task))
+ g_task_return_boolean (task, TRUE);
+ }
+
+ g_slist_free_full (priv->pending_end_session_tasks,
+ (GDestroyNotify) g_object_unref);
+ priv->pending_end_session_tasks = NULL;
+}
+
+static void
+do_phase_end_session (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ ClientEndSessionData data;
+
+ complete_end_session_tasks (manager);
+
+ data.manager = manager;
+ data.flags = 0;
+
+ if (priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) {
+ data.flags |= GSM_CLIENT_END_SESSION_FLAG_FORCEFUL;
+ }
+ if (auto_save_is_enabled (manager)) {
+ data.flags |= GSM_CLIENT_END_SESSION_FLAG_SAVE;
+ }
+
+ if (priv->phase_timeout_id > 0) {
+ g_source_remove (priv->phase_timeout_id);
+ priv->phase_timeout_id = 0;
+ }
+
+ if (gsm_store_size (priv->clients) > 0) {
+ priv->phase_timeout_id = g_timeout_add_seconds (GSM_MANAGER_PHASE_TIMEOUT,
+ (GSourceFunc)on_phase_timeout,
+ manager);
+
+ gsm_store_foreach (priv->clients,
+ (GsmStoreFunc)_client_end_session_helper,
+ &data);
+ } else {
+ end_phase (manager);
+ }
+}
+
+static void
+do_phase_end_session_part_2 (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ ClientEndSessionData data;
+
+ data.manager = manager;
+ data.flags = 0;
+
+ if (priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) {
+ data.flags |= GSM_CLIENT_END_SESSION_FLAG_FORCEFUL;
+ }
+ if (auto_save_is_enabled (manager)) {
+ data.flags |= GSM_CLIENT_END_SESSION_FLAG_SAVE;
+ }
+ data.flags |= GSM_CLIENT_END_SESSION_FLAG_LAST;
+
+ /* keep the timeout that was started at the beginning of the
+ * GSM_MANAGER_PHASE_END_SESSION phase */
+
+ if (g_slist_length (priv->next_query_clients) > 0) {
+ g_slist_foreach (priv->next_query_clients,
+ (GFunc)_client_end_session,
+ &data);
+
+ g_slist_free (priv->next_query_clients);
+ priv->next_query_clients = NULL;
+ } else {
+ end_phase (manager);
+ }
+}
+
+static gboolean
+_client_stop (const char *id,
+ GsmClient *client,
+ gpointer user_data)
+{
+ gboolean ret;
+ GError *error;
+
+ error = NULL;
+ ret = gsm_client_stop (client, &error);
+ if (! ret) {
+ g_warning ("Unable to stop client: %s", error->message);
+ g_error_free (error);
+ /* FIXME: what should we do if we can't communicate with client? */
+ } else {
+ g_debug ("GsmManager: stopped client: %s", gsm_client_peek_id (client));
+ }
+
+ return FALSE;
+}
+
+#ifdef HAVE_SYSTEMD
+static void
+maybe_restart_user_bus (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ GsmSystem *system;
+ g_autoptr(GVariant) reply = NULL;
+ g_autoptr(GError) error = NULL;
+
+ if (priv->dbus_disconnected)
+ return;
+
+ system = gsm_get_system ();
+
+ if (!gsm_system_is_last_session_for_user (system))
+ return;
+
+ reply = g_dbus_connection_call_sync (priv->connection,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "StopUnit",
+ g_variant_new ("(ss)", "dbus.service", "fail"),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+
+ if (error != NULL) {
+ g_debug ("GsmManager: reloading user bus failed: %s", error->message);
+ }
+}
+#endif
+
+static void
+do_phase_exit (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ if (gsm_store_size (priv->clients) > 0) {
+ gsm_store_foreach (priv->clients,
+ (GsmStoreFunc)_client_stop,
+ NULL);
+ }
+
+#ifdef HAVE_SYSTEMD
+ if (!priv->systemd_managed)
+ maybe_restart_user_bus (manager);
+#endif
+
+ end_phase (manager);
+}
+
+static gboolean
+_client_query_end_session (const char *id,
+ GsmClient *client,
+ ClientEndSessionData *data)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (data->manager);
+ gboolean ret;
+ GError *error;
+
+ error = NULL;
+ ret = gsm_client_query_end_session (client, data->flags, &error);
+ if (! ret) {
+ g_warning ("Unable to query client: %s", error->message);
+ g_error_free (error);
+ /* FIXME: what should we do if we can't communicate with client? */
+ } else {
+ g_debug ("GsmManager: adding client to query clients: %s", gsm_client_peek_id (client));
+ priv->query_clients = g_slist_prepend (priv->query_clients, client);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+inhibitor_has_flag (gpointer key,
+ GsmInhibitor *inhibitor,
+ gpointer data)
+{
+ guint flag;
+ guint flags;
+
+ flag = GPOINTER_TO_UINT (data);
+
+ flags = gsm_inhibitor_peek_flags (inhibitor);
+
+ return (flags & flag);
+}
+
+static gboolean
+gsm_manager_is_logout_inhibited (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ GsmInhibitor *inhibitor;
+
+ if (priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) {
+ return FALSE;
+ }
+
+ if (priv->inhibitors == NULL) {
+ return FALSE;
+ }
+
+ inhibitor = (GsmInhibitor *)gsm_store_find (priv->inhibitors,
+ (GsmStoreFunc)inhibitor_has_flag,
+ GUINT_TO_POINTER (GSM_INHIBITOR_FLAG_LOGOUT));
+ if (inhibitor == NULL) {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static gboolean
+gsm_manager_is_idle_inhibited (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ GsmInhibitor *inhibitor;
+
+ if (priv->inhibitors == NULL) {
+ return FALSE;
+ }
+
+ inhibitor = (GsmInhibitor *)gsm_store_find (priv->inhibitors,
+ (GsmStoreFunc)inhibitor_has_flag,
+ GUINT_TO_POINTER (GSM_INHIBITOR_FLAG_IDLE));
+ if (inhibitor == NULL) {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static gboolean
+_client_cancel_end_session (const char *id,
+ GsmClient *client,
+ GsmManager *manager)
+{
+ gboolean res;
+ GError *error;
+
+ error = NULL;
+ res = gsm_client_cancel_end_session (client, &error);
+ if (! res) {
+ g_warning ("Unable to cancel end session: %s", error->message);
+ g_error_free (error);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+inhibitor_is_jit (gpointer key,
+ GsmInhibitor *inhibitor,
+ GsmManager *manager)
+{
+ gboolean matches;
+ const char *id;
+
+ id = gsm_inhibitor_peek_client_id (inhibitor);
+
+ matches = (id != NULL && id[0] != '\0');
+
+ return matches;
+}
+
+static void
+cancel_end_session (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ /* just ignore if received outside of shutdown */
+ if (priv->phase < GSM_MANAGER_PHASE_QUERY_END_SESSION) {
+ return;
+ }
+
+ /* switch back to running phase */
+ g_debug ("GsmManager: Cancelling the end of session");
+
+ g_cancellable_cancel (priv->end_session_cancellable);
+
+ gsm_manager_set_phase (manager, GSM_MANAGER_PHASE_RUNNING);
+ priv->logout_mode = GSM_MANAGER_LOGOUT_MODE_NORMAL;
+
+ priv->logout_type = GSM_MANAGER_LOGOUT_NONE;
+
+ /* clear all JIT inhibitors */
+ gsm_store_foreach_remove (priv->inhibitors,
+ (GsmStoreFunc)inhibitor_is_jit,
+ (gpointer)manager);
+
+ gsm_store_foreach (priv->clients,
+ (GsmStoreFunc)_client_cancel_end_session,
+ NULL);
+
+ start_phase (manager);
+}
+
+static void
+end_session_or_show_shell_dialog (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ gboolean logout_prompt;
+ GsmShellEndSessionDialogType type;
+ gboolean logout_inhibited;
+
+ switch (priv->logout_type) {
+ case GSM_MANAGER_LOGOUT_LOGOUT:
+ type = GSM_SHELL_END_SESSION_DIALOG_TYPE_LOGOUT;
+ break;
+ case GSM_MANAGER_LOGOUT_REBOOT:
+ case GSM_MANAGER_LOGOUT_REBOOT_INTERACT:
+ type = GSM_SHELL_END_SESSION_DIALOG_TYPE_RESTART;
+ break;
+ case GSM_MANAGER_LOGOUT_SHUTDOWN:
+ case GSM_MANAGER_LOGOUT_SHUTDOWN_INTERACT:
+ type = GSM_SHELL_END_SESSION_DIALOG_TYPE_SHUTDOWN;
+ break;
+ default:
+ g_warning ("Unexpected logout type %d when creating end session dialog",
+ priv->logout_type);
+ type = GSM_SHELL_END_SESSION_DIALOG_TYPE_LOGOUT;
+ break;
+ }
+
+ logout_inhibited = gsm_manager_is_logout_inhibited (manager);
+ logout_prompt = g_settings_get_boolean (priv->settings,
+ KEY_LOGOUT_PROMPT);
+
+ switch (priv->logout_mode) {
+ case GSM_MANAGER_LOGOUT_MODE_NORMAL:
+ if (logout_inhibited || logout_prompt) {
+ show_shell_end_session_dialog (manager, type);
+ } else {
+ end_phase (manager);
+ }
+ break;
+
+ case GSM_MANAGER_LOGOUT_MODE_NO_CONFIRMATION:
+ if (logout_inhibited) {
+ show_shell_end_session_dialog (manager, type);
+ } else {
+ end_phase (manager);
+ }
+ break;
+
+ case GSM_MANAGER_LOGOUT_MODE_FORCE:
+ end_phase (manager);
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+}
+
+static void
+query_end_session_complete (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ g_debug ("GsmManager: query end session complete");
+
+ /* Remove the timeout since this can be called from outside the timer
+ * and we don't want to have it called twice */
+ if (priv->query_timeout_id > 0) {
+ g_source_remove (priv->query_timeout_id);
+ priv->query_timeout_id = 0;
+ }
+
+ end_session_or_show_shell_dialog (manager);
+}
+
+static guint32
+generate_cookie (void)
+{
+ guint32 cookie;
+
+ cookie = (guint32)g_random_int_range (1, G_MAXINT32);
+
+ return cookie;
+}
+
+static guint32
+_generate_unique_cookie (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ guint32 cookie;
+
+ do {
+ cookie = generate_cookie ();
+ } while (gsm_store_find (priv->inhibitors, (GsmStoreFunc)_find_by_cookie, &cookie) != NULL);
+
+ return cookie;
+}
+
+static gboolean
+_on_query_end_session_timeout (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ GSList *l;
+
+ priv->query_timeout_id = 0;
+
+ g_debug ("GsmManager: query end session timed out");
+
+ for (l = priv->query_clients; l != NULL; l = l->next) {
+ guint cookie;
+ GsmInhibitor *inhibitor;
+ const char *bus_name;
+ char *app_id;
+
+ g_warning ("Client '%s' failed to reply before timeout",
+ gsm_client_peek_id (l->data));
+
+ /* Don't add "not responding" inhibitors if logout is forced
+ */
+ if (priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) {
+ continue;
+ }
+
+ /* Add JIT inhibit for unresponsive client */
+ if (GSM_IS_DBUS_CLIENT (l->data)) {
+ bus_name = gsm_dbus_client_get_bus_name (l->data);
+ } else {
+ bus_name = NULL;
+ }
+
+ app_id = g_strdup (gsm_client_peek_app_id (l->data));
+ if (IS_STRING_EMPTY (app_id)) {
+ /* XSMP clients don't give us an app id unless we start them */
+ g_free (app_id);
+ app_id = gsm_client_get_app_name (l->data);
+ }
+
+ cookie = _generate_unique_cookie (manager);
+ inhibitor = gsm_inhibitor_new_for_client (gsm_client_peek_id (l->data),
+ app_id,
+ GSM_INHIBITOR_FLAG_LOGOUT,
+ _("Not responding"),
+ bus_name,
+ cookie);
+ g_free (app_id);
+ gsm_store_add (priv->inhibitors, gsm_inhibitor_peek_id (inhibitor), G_OBJECT (inhibitor));
+ g_object_unref (inhibitor);
+ }
+
+ g_slist_free (priv->query_clients);
+ priv->query_clients = NULL;
+
+ query_end_session_complete (manager);
+
+ return FALSE;
+}
+
+static void
+do_phase_query_end_session (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ ClientEndSessionData data;
+
+ data.manager = manager;
+ data.flags = 0;
+
+ if (priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) {
+ data.flags |= GSM_CLIENT_END_SESSION_FLAG_FORCEFUL;
+ }
+ /* We only query if an app is ready to log out, so we don't use
+ * GSM_CLIENT_END_SESSION_FLAG_SAVE here.
+ */
+
+ debug_clients (manager);
+ g_debug ("GsmManager: sending query-end-session to clients (logout mode: %s)",
+ priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_NORMAL? "normal" :
+ priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE? "forceful":
+ "no confirmation");
+ gsm_store_foreach (priv->clients,
+ (GsmStoreFunc)_client_query_end_session,
+ &data);
+
+ /* This phase doesn't time out unless logout is forced. Typically, this
+ * separate timer is only used to show UI. */
+ priv->query_timeout_id = g_timeout_add_seconds (1, (GSourceFunc)_on_query_end_session_timeout, manager);
+}
+
+static void
+update_idle (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ if (gsm_manager_is_idle_inhibited (manager)) {
+ gsm_presence_set_idle_enabled (priv->presence, FALSE);
+ } else {
+ gsm_presence_set_idle_enabled (priv->presence, TRUE);
+ }
+}
+
+static void
+start_phase (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ g_debug ("GsmManager: starting phase %s\n",
+ phase_num_to_name (priv->phase));
+
+ /* reset state */
+ g_slist_free (priv->pending_apps);
+ priv->pending_apps = NULL;
+ g_slist_free (priv->query_clients);
+ priv->query_clients = NULL;
+ g_slist_free (priv->next_query_clients);
+ priv->next_query_clients = NULL;
+
+ if (priv->query_timeout_id > 0) {
+ g_source_remove (priv->query_timeout_id);
+ priv->query_timeout_id = 0;
+ }
+ if (priv->phase_timeout_id > 0) {
+ g_source_remove (priv->phase_timeout_id);
+ priv->phase_timeout_id = 0;
+ }
+
+ sd_notifyf (0, "STATUS=GNOME Session Manager phase is %s", phase_num_to_name (priv->phase));
+
+ switch (priv->phase) {
+ case GSM_MANAGER_PHASE_STARTUP:
+ case GSM_MANAGER_PHASE_EARLY_INITIALIZATION:
+ case GSM_MANAGER_PHASE_PRE_DISPLAY_SERVER:
+ do_phase_startup (manager);
+ break;
+ case GSM_MANAGER_PHASE_DISPLAY_SERVER:
+ sd_notify (0, "READY=1");
+ do_phase_startup (manager);
+ break;
+ case GSM_MANAGER_PHASE_INITIALIZATION:
+ case GSM_MANAGER_PHASE_WINDOW_MANAGER:
+ case GSM_MANAGER_PHASE_PANEL:
+ case GSM_MANAGER_PHASE_DESKTOP:
+ case GSM_MANAGER_PHASE_APPLICATION:
+ do_phase_startup (manager);
+ break;
+ case GSM_MANAGER_PHASE_RUNNING:
+#ifdef ENABLE_SYSTEMD_JOURNAL
+ sd_journal_send ("MESSAGE_ID=%s", GSM_MANAGER_STARTUP_SUCCEEDED_MSGID,
+ "PRIORITY=%d", 5,
+ "MESSAGE=Entering running state",
+ NULL);
+#endif
+ gsm_xsmp_server_start_accepting_new_clients (priv->xsmp_server);
+ if (priv->pending_end_session_tasks != NULL)
+ complete_end_session_tasks (manager);
+ g_object_unref (priv->end_session_cancellable);
+ priv->end_session_cancellable = g_cancellable_new ();
+ gsm_exported_manager_emit_session_running (priv->skeleton);
+ update_idle (manager);
+ break;
+ case GSM_MANAGER_PHASE_QUERY_END_SESSION:
+ gsm_xsmp_server_stop_accepting_new_clients (priv->xsmp_server);
+ do_phase_query_end_session (manager);
+ break;
+ case GSM_MANAGER_PHASE_END_SESSION:
+ sd_notify (0, "STOPPING=1");
+
+ do_phase_end_session (manager);
+ break;
+ case GSM_MANAGER_PHASE_EXIT:
+ sd_notify (0, "STOPPING=1");
+
+ do_phase_exit (manager);
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static gboolean
+_debug_app_for_phase (const char *id,
+ GsmApp *app,
+ gpointer data)
+{
+ guint phase;
+
+ phase = GPOINTER_TO_UINT (data);
+
+ if (gsm_app_peek_phase (app) != phase) {
+ return FALSE;
+ }
+
+ g_debug ("GsmManager:\tID: %s\tapp-id:%s\tis-disabled:%d\tis-conditionally-disabled:%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));
+
+ return FALSE;
+}
+
+static void
+debug_app_summary (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ guint phase;
+
+ g_debug ("GsmManager: App startup summary");
+ for (phase = GSM_MANAGER_PHASE_EARLY_INITIALIZATION; phase < GSM_MANAGER_PHASE_RUNNING; phase++) {
+ g_debug ("GsmManager: Phase %s", phase_num_to_name (phase));
+ gsm_store_foreach (priv->apps,
+ (GsmStoreFunc)_debug_app_for_phase,
+ GUINT_TO_POINTER (phase));
+ }
+}
+
+void
+gsm_manager_start (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ g_debug ("GsmManager: GSM starting to manage");
+
+ g_return_if_fail (GSM_IS_MANAGER (manager));
+
+ gsm_xsmp_server_start (priv->xsmp_server);
+ gsm_manager_set_phase (manager, GSM_MANAGER_PHASE_EARLY_INITIALIZATION);
+ debug_app_summary (manager);
+ start_phase (manager);
+}
+
+char *
+_gsm_manager_get_default_session (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ g_autoptr(GSettings) session_settings = NULL;
+
+ if (manager)
+ session_settings = g_object_ref (priv->session_settings);
+ else
+ session_settings = g_settings_new (SESSION_SCHEMA);
+ return g_settings_get_string (session_settings,
+ KEY_SESSION_NAME);
+}
+
+void
+_gsm_manager_set_active_session (GsmManager *manager,
+ const char *session_name,
+ gboolean is_fallback)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ g_free (priv->session_name);
+ priv->session_name = g_strdup (session_name);
+ priv->is_fallback_session = is_fallback;
+
+ gsm_exported_manager_set_session_name (priv->skeleton, session_name);
+}
+
+void
+_gsm_manager_set_renderer (GsmManager *manager,
+ const char *renderer)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ gsm_exported_manager_set_renderer (priv->skeleton, renderer);
+}
+
+static gboolean
+_app_has_app_id (const char *id,
+ GsmApp *app,
+ const char *app_id_a)
+{
+ const char *app_id_b;
+
+ app_id_b = gsm_app_peek_app_id (app);
+ return (app_id_b != NULL && strcmp (app_id_a, app_id_b) == 0);
+}
+
+static GsmApp *
+find_app_for_app_id (GsmManager *manager,
+ const char *app_id)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ GsmApp *app;
+
+ app = (GsmApp *)gsm_store_find (priv->apps,
+ (GsmStoreFunc)_app_has_app_id,
+ (char *)app_id);
+ return app;
+}
+
+static gboolean
+inhibitor_has_client_id (gpointer key,
+ GsmInhibitor *inhibitor,
+ const char *client_id_a)
+{
+ gboolean matches;
+ const char *client_id_b;
+
+ client_id_b = gsm_inhibitor_peek_client_id (inhibitor);
+
+ matches = FALSE;
+ if (! IS_STRING_EMPTY (client_id_a) && ! IS_STRING_EMPTY (client_id_b)) {
+ matches = (strcmp (client_id_a, client_id_b) == 0);
+ if (matches) {
+ g_debug ("GsmManager: removing JIT inhibitor for %s for reason '%s'",
+ gsm_inhibitor_peek_client_id (inhibitor),
+ gsm_inhibitor_peek_reason (inhibitor));
+ }
+ }
+
+ return matches;
+}
+
+static gboolean
+_app_has_startup_id (const char *id,
+ GsmApp *app,
+ const char *startup_id_a)
+{
+ const char *startup_id_b;
+
+ startup_id_b = gsm_app_peek_startup_id (app);
+
+ if (IS_STRING_EMPTY (startup_id_b)) {
+ return FALSE;
+ }
+
+ return (strcmp (startup_id_a, startup_id_b) == 0);
+}
+
+static GsmApp *
+find_app_for_startup_id (GsmManager *manager,
+ const char *startup_id)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ GsmApp *found_app;
+ GSList *a;
+
+ found_app = NULL;
+
+ /* If we're starting up the session, try to match the new client
+ * with one pending apps for the current phase. If not, try to match
+ * with any of the autostarted apps. */
+ if (priv->phase < GSM_MANAGER_PHASE_APPLICATION) {
+ for (a = priv->pending_apps; a != NULL; a = a->next) {
+ GsmApp *app = GSM_APP (a->data);
+
+ if (strcmp (startup_id, gsm_app_peek_startup_id (app)) == 0) {
+ found_app = app;
+ goto out;
+ }
+ }
+ } else {
+ GsmApp *app;
+
+ app = (GsmApp *)gsm_store_find (priv->apps,
+ (GsmStoreFunc)_app_has_startup_id,
+ (char *)startup_id);
+ if (app != NULL) {
+ found_app = app;
+ goto out;
+ }
+ }
+ out:
+ return found_app;
+}
+
+static void
+_disconnect_client (GsmManager *manager,
+ GsmClient *client)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ gboolean is_condition_client;
+ GsmApp *app;
+ const char *app_id;
+ const char *startup_id;
+ gboolean app_restart;
+ GsmClientRestartStyle client_restart_hint;
+
+ g_debug ("GsmManager: disconnect client: %s", gsm_client_peek_id (client));
+
+ /* take a ref so it doesn't get finalized */
+ g_object_ref (client);
+
+ gsm_client_set_status (client, GSM_CLIENT_FINISHED);
+
+ is_condition_client = FALSE;
+ if (g_slist_find (priv->condition_clients, client)) {
+ priv->condition_clients = g_slist_remove (priv->condition_clients, client);
+
+ is_condition_client = TRUE;
+ }
+
+ /* remove any inhibitors for this client */
+ gsm_store_foreach_remove (priv->inhibitors,
+ (GsmStoreFunc)inhibitor_has_client_id,
+ (gpointer)gsm_client_peek_id (client));
+
+ app = NULL;
+
+ /* first try to match on startup ID */
+ startup_id = gsm_client_peek_startup_id (client);
+ if (! IS_STRING_EMPTY (startup_id)) {
+ app = find_app_for_startup_id (manager, startup_id);
+
+ }
+
+ /* then try to find matching app-id */
+ if (app == NULL) {
+ app_id = gsm_client_peek_app_id (client);
+ if (! IS_STRING_EMPTY (app_id)) {
+ g_debug ("GsmManager: disconnect for app '%s'", app_id);
+ app = find_app_for_app_id (manager, app_id);
+ }
+ }
+
+ switch (priv->phase) {
+ case GSM_MANAGER_PHASE_QUERY_END_SESSION:
+ /* Instead of answering our end session query, the client just exited.
+ * Treat that as an "okay, end the session" answer.
+ *
+ * This call implicitly removes any inhibitors for the client, along
+ * with removing the client from the pending query list.
+ */
+ _handle_client_end_session_response (manager,
+ client,
+ TRUE,
+ FALSE,
+ FALSE,
+ "Client exited in "
+ "query end session phase "
+ "instead of end session "
+ "phase");
+ break;
+ case GSM_MANAGER_PHASE_END_SESSION:
+ if (! g_slist_find (priv->query_clients, client)) {
+ /* the client sent its EndSessionResponse and we already
+ * processed it.
+ */
+ break;
+ }
+
+ /* Client exited without sending EndSessionResponse.
+ * The likely reason is that its exit code is written in a way
+ * that never returns, and sending EndSessionResponse is handled
+ * in library code after the callback. Or maybe the application
+ * crashed while handling EndSession. Or it was lazy.
+ */
+ _handle_client_end_session_response (manager,
+ client,
+ TRUE,
+ FALSE,
+ FALSE,
+ "Client exited in "
+ "end session phase without "
+ "sending EndSessionResponse");
+ default:
+ /* do nothing */
+ break;
+ }
+
+ if (priv->dbus_disconnected && GSM_IS_DBUS_CLIENT (client)) {
+ g_debug ("GsmManager: dbus disconnected, not restarting application");
+ goto out;
+ }
+
+ if (app == NULL) {
+ g_debug ("GsmManager: unable to find application for client - not restarting");
+ goto out;
+ }
+
+ if (priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION) {
+ g_debug ("GsmManager: in shutdown, not restarting application");
+ goto out;
+ }
+
+ app_restart = gsm_app_peek_autorestart (app);
+ client_restart_hint = gsm_client_peek_restart_style_hint (client);
+
+ /* allow legacy clients to override the app info */
+ if (! app_restart
+ && client_restart_hint != GSM_CLIENT_RESTART_IMMEDIATELY) {
+ g_debug ("GsmManager: autorestart not set, not restarting application");
+ goto out;
+ }
+
+ if (is_condition_client) {
+ g_debug ("GsmManager: app conditionally disabled, not restarting application");
+ goto out;
+ }
+
+ g_debug ("GsmManager: restarting app");
+
+ _restart_app (manager, app);
+
+ out:
+ g_object_unref (client);
+}
+
+typedef struct {
+ const char *service_name;
+ GsmManager *manager;
+} RemoveClientData;
+
+static gboolean
+_disconnect_dbus_client (const char *id,
+ GsmClient *client,
+ RemoveClientData *data)
+{
+ const char *name;
+
+ if (! GSM_IS_DBUS_CLIENT (client)) {
+ return FALSE;
+ }
+
+ /* If no service name, then we simply disconnect all clients */
+ if (!data->service_name) {
+ _disconnect_client (data->manager, client);
+ return TRUE;
+ }
+
+ name = gsm_dbus_client_get_bus_name (GSM_DBUS_CLIENT (client));
+ if (IS_STRING_EMPTY (name)) {
+ return FALSE;
+ }
+
+ if (strcmp (data->service_name, name) == 0) {
+ _disconnect_client (data->manager, client);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * remove_clients_for_connection:
+ * @manager: a #GsmManager
+ * @service_name: a service name
+ *
+ * Disconnects clients that own @service_name.
+ *
+ * If @service_name is NULL, then disconnects all clients for the connection.
+ */
+static void
+remove_clients_for_connection (GsmManager *manager,
+ const char *service_name)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ RemoveClientData data;
+
+ data.service_name = service_name;
+ data.manager = manager;
+
+ /* disconnect dbus clients for name */
+ gsm_store_foreach_remove (priv->clients,
+ (GsmStoreFunc)_disconnect_dbus_client,
+ &data);
+
+ if (priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION
+ && gsm_store_size (priv->clients) == 0) {
+ g_debug ("GsmManager: last client disconnected - exiting");
+ end_phase (manager);
+ }
+}
+
+static void
+gsm_manager_set_failsafe (GsmManager *manager,
+ gboolean enabled)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ g_return_if_fail (GSM_IS_MANAGER (manager));
+
+ priv->failsafe = enabled;
+}
+
+gboolean
+gsm_manager_get_failsafe (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ g_return_val_if_fail (GSM_IS_MANAGER (manager), FALSE);
+
+ return priv->failsafe;
+}
+
+gboolean
+gsm_manager_get_systemd_managed (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ g_return_val_if_fail (GSM_IS_MANAGER (manager), FALSE);
+
+ return priv->systemd_managed;
+}
+
+static void
+on_client_disconnected (GsmClient *client,
+ GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ g_debug ("GsmManager: disconnect client");
+ _disconnect_client (manager, client);
+ gsm_store_remove (priv->clients, gsm_client_peek_id (client));
+ if (priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION
+ && gsm_store_size (priv->clients) == 0) {
+ g_debug ("GsmManager: last client disconnected - exiting");
+ end_phase (manager);
+ }
+}
+
+static gboolean
+on_xsmp_client_register_request (GsmXSMPClient *client,
+ char **id,
+ GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ gboolean handled;
+ char *new_id;
+ GsmApp *app;
+
+ handled = TRUE;
+ new_id = NULL;
+
+ if (priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION) {
+ goto out;
+ }
+
+ if (IS_STRING_EMPTY (*id)) {
+ new_id = gsm_util_generate_startup_id ();
+ } else {
+ GsmClient *client;
+
+ client = (GsmClient *)gsm_store_find (priv->clients,
+ (GsmStoreFunc)_client_has_startup_id,
+ *id);
+ /* We can't have two clients with the same id. */
+ if (client != NULL) {
+ goto out;
+ }
+
+ new_id = g_strdup (*id);
+ }
+
+ g_debug ("GsmManager: Adding new client %s to session", new_id);
+
+ g_signal_connect (client,
+ "disconnected",
+ G_CALLBACK (on_client_disconnected),
+ manager);
+
+ /* If it's a brand new client id, we just accept the client*/
+ if (IS_STRING_EMPTY (*id)) {
+ goto out;
+ }
+
+ app = find_app_for_startup_id (manager, new_id);
+ if (app != NULL) {
+ gsm_client_set_app_id (GSM_CLIENT (client), gsm_app_peek_app_id (app));
+ goto out;
+ }
+
+ /* app not found */
+ g_free (new_id);
+ new_id = NULL;
+
+ out:
+ g_free (*id);
+ *id = new_id;
+
+ return handled;
+}
+
+static void
+on_xsmp_client_register_confirmed (GsmXSMPClient *client,
+ const gchar *id,
+ GsmManager *manager)
+{
+ GsmApp *app;
+
+ app = find_app_for_startup_id (manager, id);
+
+ if (app != NULL) {
+ gsm_app_set_registered (app, TRUE);
+ }
+}
+
+static gboolean
+auto_save_is_enabled (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ /* Note that saving/restoring sessions is not really possible on systemd, as
+ * XSMP clients cannot be reliably mapped to .desktop files. */
+ if (priv->systemd_managed)
+ return FALSE;
+
+ return g_settings_get_boolean (priv->settings, KEY_AUTOSAVE_ONE_SHOT)
+ || g_settings_get_boolean (priv->settings, KEY_AUTOSAVE);
+}
+
+static void
+maybe_save_session (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ GError *error;
+
+ if (gsm_system_is_login_session (priv->system))
+ return;
+
+ /* We only allow session saving when session is running or when
+ * logging out */
+ if (priv->phase != GSM_MANAGER_PHASE_RUNNING &&
+ priv->phase != GSM_MANAGER_PHASE_END_SESSION) {
+ return;
+ }
+
+ if (!auto_save_is_enabled (manager)) {
+ gsm_session_save_clear ();
+ return;
+ }
+
+ error = NULL;
+ gsm_session_save (priv->clients, priv->apps, &error);
+
+ if (error) {
+ g_warning ("Error saving session: %s", error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+_handle_client_end_session_response (GsmManager *manager,
+ GsmClient *client,
+ gboolean is_ok,
+ gboolean do_last,
+ gboolean cancel,
+ const char *reason)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ /* just ignore if received outside of shutdown */
+ if (priv->phase < GSM_MANAGER_PHASE_QUERY_END_SESSION) {
+ return;
+ }
+
+ g_debug ("GsmManager: Response from end session request: is-ok=%d do-last=%d cancel=%d reason=%s", is_ok, do_last, cancel, reason ? reason :"");
+
+ if (cancel) {
+ cancel_end_session (manager);
+ return;
+ }
+
+ priv->query_clients = g_slist_remove (priv->query_clients, client);
+
+ if (! is_ok && priv->logout_mode != GSM_MANAGER_LOGOUT_MODE_FORCE) {
+ guint cookie;
+ GsmInhibitor *inhibitor;
+ char *app_id;
+ const char *bus_name;
+
+ /* FIXME: do we support updating the reason? */
+
+ /* Create JIT inhibit */
+ if (GSM_IS_DBUS_CLIENT (client)) {
+ bus_name = gsm_dbus_client_get_bus_name (GSM_DBUS_CLIENT (client));
+ } else {
+ bus_name = NULL;
+ }
+
+ app_id = g_strdup (gsm_client_peek_app_id (client));
+ if (IS_STRING_EMPTY (app_id)) {
+ /* XSMP clients don't give us an app id unless we start them */
+ g_free (app_id);
+ app_id = gsm_client_get_app_name (client);
+ }
+
+ cookie = _generate_unique_cookie (manager);
+ inhibitor = gsm_inhibitor_new_for_client (gsm_client_peek_id (client),
+ app_id,
+ GSM_INHIBITOR_FLAG_LOGOUT,
+ reason != NULL ? reason : _("Not responding"),
+ bus_name,
+ cookie);
+ g_free (app_id);
+ gsm_store_add (priv->inhibitors, gsm_inhibitor_peek_id (inhibitor), G_OBJECT (inhibitor));
+ g_object_unref (inhibitor);
+ } else {
+ gsm_store_foreach_remove (priv->inhibitors,
+ (GsmStoreFunc)inhibitor_has_client_id,
+ (gpointer)gsm_client_peek_id (client));
+ }
+
+ if (priv->phase == GSM_MANAGER_PHASE_QUERY_END_SESSION) {
+ if (priv->query_clients == NULL) {
+ query_end_session_complete (manager);
+ }
+ } else if (priv->phase == GSM_MANAGER_PHASE_END_SESSION) {
+ if (do_last) {
+ /* This only makes sense if we're in part 1 of
+ * GSM_MANAGER_PHASE_END_SESSION. Doing this in part 2
+ * can only happen because of a buggy client that loops
+ * wanting to be last again and again. The phase
+ * timeout will take care of this issue. */
+ priv->next_query_clients = g_slist_prepend (priv->next_query_clients,
+ client);
+ }
+
+ /* we can continue to the next step if all clients have replied
+ * and if there's no inhibitor */
+ if (priv->query_clients != NULL
+ || gsm_manager_is_logout_inhibited (manager)) {
+ return;
+ }
+
+ if (priv->next_query_clients != NULL) {
+ do_phase_end_session_part_2 (manager);
+ } else {
+ end_phase (manager);
+ }
+ }
+}
+
+static void
+on_client_end_session_response (GsmClient *client,
+ gboolean is_ok,
+ gboolean do_last,
+ gboolean cancel,
+ const char *reason,
+ GsmManager *manager)
+{
+ _handle_client_end_session_response (manager,
+ client,
+ is_ok,
+ do_last,
+ cancel,
+ reason);
+}
+
+static void
+on_xsmp_client_logout_request (GsmXSMPClient *client,
+ gboolean show_dialog,
+ GsmManager *manager)
+{
+ GError *error;
+ int logout_mode;
+
+ if (show_dialog) {
+ logout_mode = GSM_MANAGER_LOGOUT_MODE_NORMAL;
+ } else {
+ logout_mode = GSM_MANAGER_LOGOUT_MODE_NO_CONFIRMATION;
+ }
+
+ error = NULL;
+ gsm_manager_logout (manager, logout_mode, &error);
+ if (error != NULL) {
+ g_warning ("Unable to logout: %s", error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+on_store_client_added (GsmStore *store,
+ const char *id,
+ GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ GsmClient *client;
+
+ g_debug ("GsmManager: Client added: %s", id);
+
+ client = (GsmClient *)gsm_store_lookup (store, id);
+
+ /* a bit hacky */
+ if (GSM_IS_XSMP_CLIENT (client)) {
+ g_signal_connect (client,
+ "register-request",
+ G_CALLBACK (on_xsmp_client_register_request),
+ manager);
+ g_signal_connect (client,
+ "register-confirmed",
+ G_CALLBACK (on_xsmp_client_register_confirmed),
+ manager);
+ g_signal_connect (client,
+ "logout-request",
+ G_CALLBACK (on_xsmp_client_logout_request),
+ manager);
+ }
+
+ g_signal_connect (client,
+ "end-session-response",
+ G_CALLBACK (on_client_end_session_response),
+ manager);
+
+ gsm_exported_manager_emit_client_added (priv->skeleton, id);
+ /* FIXME: disconnect signal handler */
+}
+
+static void
+on_store_client_removed (GsmStore *store,
+ const char *id,
+ GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ g_debug ("GsmManager: Client removed: %s", id);
+
+ gsm_exported_manager_emit_client_removed (priv->skeleton, id);
+}
+
+static void
+gsm_manager_set_client_store (GsmManager *manager,
+ GsmStore *store)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ g_return_if_fail (GSM_IS_MANAGER (manager));
+
+ if (store != NULL) {
+ g_object_ref (store);
+ }
+
+ if (priv->clients != NULL) {
+ g_signal_handlers_disconnect_by_func (priv->clients,
+ on_store_client_added,
+ manager);
+ g_signal_handlers_disconnect_by_func (priv->clients,
+ on_store_client_removed,
+ manager);
+
+ g_object_unref (priv->clients);
+ }
+
+
+ g_debug ("GsmManager: setting client store %p", store);
+
+ priv->clients = store;
+
+ if (priv->clients != NULL) {
+ if (priv->xsmp_server)
+ g_object_unref (priv->xsmp_server);
+
+ priv->xsmp_server = gsm_xsmp_server_new (store);
+
+ g_signal_connect (priv->clients,
+ "added",
+ G_CALLBACK (on_store_client_added),
+ manager);
+ g_signal_connect (priv->clients,
+ "removed",
+ G_CALLBACK (on_store_client_removed),
+ manager);
+ }
+}
+
+static void
+gsm_manager_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GsmManager *self = GSM_MANAGER (object);
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (self);
+
+ switch (prop_id) {
+ case PROP_FAILSAFE:
+ gsm_manager_set_failsafe (self, g_value_get_boolean (value));
+ break;
+ case PROP_FALLBACK:
+ priv->is_fallback_session = g_value_get_boolean (value);
+ break;
+ case PROP_CLIENT_STORE:
+ gsm_manager_set_client_store (self, g_value_get_object (value));
+ break;
+ case PROP_SYSTEMD_MANAGED:
+ priv->systemd_managed = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gsm_manager_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GsmManager *self = GSM_MANAGER (object);
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (self);
+
+ switch (prop_id) {
+ case PROP_FAILSAFE:
+ g_value_set_boolean (value, priv->failsafe);
+ break;
+ case PROP_SESSION_NAME:
+ g_value_set_string (value, priv->session_name);
+ break;
+ case PROP_FALLBACK:
+ g_value_set_boolean (value, priv->is_fallback_session);
+ break;
+ case PROP_CLIENT_STORE:
+ g_value_set_object (value, priv->clients);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+_find_app_provides (const char *id,
+ GsmApp *app,
+ const char *service)
+{
+ return gsm_app_provides (app, service);
+}
+
+static GObject *
+gsm_manager_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_properties)
+{
+ GsmManager *manager;
+
+ manager = GSM_MANAGER (G_OBJECT_CLASS (gsm_manager_parent_class)->constructor (type,
+ n_construct_properties,
+ construct_properties));
+ return G_OBJECT (manager);
+}
+
+static void
+update_inhibited_actions (GsmManager *manager,
+ GsmInhibitorFlag new_inhibited_actions)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ if (priv->inhibited_actions == new_inhibited_actions)
+ return;
+
+ gsm_system_set_inhibitors (priv->system, new_inhibited_actions);
+
+ priv->inhibited_actions = new_inhibited_actions;
+ gsm_exported_manager_set_inhibited_actions (priv->skeleton,
+ priv->inhibited_actions);
+}
+
+static void
+on_inhibitor_vanished (GsmInhibitor *inhibitor,
+ GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ gsm_store_remove (priv->inhibitors, gsm_inhibitor_peek_id (inhibitor));
+}
+
+static void
+on_store_inhibitor_added (GsmStore *store,
+ const char *id,
+ GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ GsmInhibitor *i;
+ GsmInhibitorFlag new_inhibited_actions;
+
+ g_debug ("GsmManager: Inhibitor added: %s", id);
+
+ i = GSM_INHIBITOR (gsm_store_lookup (store, id));
+
+ new_inhibited_actions = priv->inhibited_actions | gsm_inhibitor_peek_flags (i);
+ update_inhibited_actions (manager, new_inhibited_actions);
+
+ g_signal_connect_object (i, "vanished", G_CALLBACK (on_inhibitor_vanished), manager, 0);
+
+ gsm_exported_manager_emit_inhibitor_added (priv->skeleton, id);
+
+ update_idle (manager);
+}
+
+static gboolean
+collect_inhibition_flags (const char *id,
+ GObject *object,
+ gpointer user_data)
+{
+ GsmInhibitorFlag *new_inhibited_actions = user_data;
+
+ *new_inhibited_actions |= gsm_inhibitor_peek_flags (GSM_INHIBITOR (object));
+
+ return FALSE;
+}
+
+static void
+on_store_inhibitor_removed (GsmStore *store,
+ const char *id,
+ GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ GsmInhibitorFlag new_inhibited_actions;
+
+ g_debug ("GsmManager: Inhibitor removed: %s", id);
+
+ new_inhibited_actions = 0;
+ gsm_store_foreach (priv->inhibitors,
+ collect_inhibition_flags,
+ &new_inhibited_actions);
+ update_inhibited_actions (manager, new_inhibited_actions);
+
+ gsm_exported_manager_emit_inhibitor_removed (priv->skeleton, id);
+
+ update_idle (manager);
+
+ if (priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION) {
+ end_session_or_show_shell_dialog (manager);
+ }
+}
+
+static void
+gsm_manager_dispose (GObject *object)
+{
+ GsmManager *manager = GSM_MANAGER (object);
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ g_debug ("GsmManager: disposing manager");
+
+ g_clear_object (&priv->end_session_cancellable);
+ g_clear_object (&priv->xsmp_server);
+ g_clear_pointer (&priv->session_name, g_free);
+
+ if (priv->clients != NULL) {
+ g_signal_handlers_disconnect_by_func (priv->clients,
+ on_store_client_added,
+ manager);
+ g_signal_handlers_disconnect_by_func (priv->clients,
+ on_store_client_removed,
+ manager);
+ g_object_unref (priv->clients);
+ priv->clients = NULL;
+ }
+
+ g_clear_object (&priv->apps);
+ g_slist_free (priv->required_apps);
+ priv->required_apps = NULL;
+ g_slist_free (priv->pending_apps);
+ priv->pending_apps = NULL;
+
+ if (priv->inhibitors != NULL) {
+ g_signal_handlers_disconnect_by_func (priv->inhibitors,
+ on_store_inhibitor_added,
+ manager);
+ g_signal_handlers_disconnect_by_func (priv->inhibitors,
+ on_store_inhibitor_removed,
+ manager);
+
+ g_object_unref (priv->inhibitors);
+ priv->inhibitors = NULL;
+ }
+
+ g_clear_object (&priv->presence);
+ g_clear_object (&priv->settings);
+ g_clear_object (&priv->session_settings);
+ g_clear_object (&priv->screensaver_settings);
+ g_clear_object (&priv->lockdown_settings);
+ g_clear_object (&priv->system);
+ g_clear_object (&priv->shell);
+
+ if (priv->skeleton != NULL) {
+ g_dbus_interface_skeleton_unexport_from_connection (G_DBUS_INTERFACE_SKELETON (priv->skeleton),
+ priv->connection);
+ g_clear_object (&priv->skeleton);
+ }
+
+ g_clear_object (&priv->connection);
+
+ G_OBJECT_CLASS (gsm_manager_parent_class)->dispose (object);
+}
+
+static void
+gsm_manager_class_init (GsmManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = gsm_manager_get_property;
+ object_class->set_property = gsm_manager_set_property;
+ object_class->constructor = gsm_manager_constructor;
+ object_class->dispose = gsm_manager_dispose;
+
+ signals [PHASE_CHANGED] =
+ g_signal_new ("phase-changed",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmManagerClass, phase_changed),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1, G_TYPE_STRING);
+
+ g_object_class_install_property (object_class,
+ PROP_FAILSAFE,
+ g_param_spec_boolean ("failsafe",
+ NULL,
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+ /**
+ * GsmManager::session-name
+ *
+ * Then name of the currently active session, typically "gnome" or "gnome-fallback".
+ * This may be the name of the configured default session, or the name of a fallback
+ * session in case we fell back.
+ */
+ g_object_class_install_property (object_class,
+ PROP_SESSION_NAME,
+ g_param_spec_string ("session-name",
+ NULL,
+ NULL,
+ NULL,
+ G_PARAM_READABLE));
+
+ /**
+ * GsmManager::fallback
+ *
+ * If %TRUE, the current session is running in the "fallback" mode;
+ * this is distinct from whether or not it was configured as default.
+ */
+ g_object_class_install_property (object_class,
+ PROP_FALLBACK,
+ g_param_spec_boolean ("fallback",
+ NULL,
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class,
+ PROP_CLIENT_STORE,
+ g_param_spec_object ("client-store",
+ NULL,
+ NULL,
+ GSM_TYPE_STORE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class,
+ PROP_SYSTEMD_MANAGED,
+ g_param_spec_boolean ("systemd-managed",
+ NULL,
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+on_presence_status_changed (GsmPresence *presence,
+ guint status,
+ GsmManager *manager)
+{
+ GsmSystem *system;
+
+ system = gsm_get_system ();
+ gsm_system_set_session_idle (system,
+ (status == GSM_PRESENCE_STATUS_IDLE));
+ g_object_unref (system);
+}
+
+static void
+on_gsm_system_active_changed (GsmSystem *system,
+ GParamSpec *pspec,
+ GsmManager *self)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (self);
+ gboolean is_active;
+
+ is_active = gsm_system_is_active (priv->system);
+
+ g_debug ("emitting SessionIsActive");
+ gsm_exported_manager_set_session_is_active (priv->skeleton, is_active);
+}
+
+static gboolean
+_log_out_is_locked_down (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ return g_settings_get_boolean (priv->lockdown_settings,
+ KEY_DISABLE_LOG_OUT);
+}
+
+static void
+complete_end_session_task (GsmManager *manager,
+ GAsyncResult *result,
+ GDBusMethodInvocation *invocation)
+{
+ GError *error = NULL;
+
+ if (!g_task_propagate_boolean (G_TASK (result), &error))
+ g_dbus_method_invocation_take_error (invocation, error);
+ else
+ g_dbus_method_invocation_return_value (invocation, NULL);
+}
+
+static void
+request_reboot (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ g_debug ("GsmManager: requesting reboot");
+
+ /* FIXME: We need to support a more structured shutdown here,
+ * but that's blocking on an improved ConsoleKit api.
+ *
+ * See https://bugzilla.gnome.org/show_bug.cgi?id=585614
+ */
+ priv->logout_type = GSM_MANAGER_LOGOUT_REBOOT_INTERACT;
+ end_phase (manager);
+}
+
+static void
+request_shutdown (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ g_debug ("GsmManager: requesting shutdown");
+
+ /* See the comment in request_reboot() for some more details about
+ * what work needs to be done here. */
+ priv->logout_type = GSM_MANAGER_LOGOUT_SHUTDOWN_INTERACT;
+ end_phase (manager);
+}
+
+static void
+request_logout (GsmManager *manager,
+ GsmManagerLogoutMode mode)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ g_debug ("GsmManager: requesting logout");
+
+ priv->logout_mode = mode;
+ priv->logout_type = GSM_MANAGER_LOGOUT_LOGOUT;
+
+ end_phase (manager);
+}
+
+static gboolean
+gsm_manager_shutdown (GsmExportedManager *skeleton,
+ GDBusMethodInvocation *invocation,
+ GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ GTask *task;
+
+ g_debug ("GsmManager: Shutdown called");
+
+ if (priv->phase < GSM_MANAGER_PHASE_RUNNING) {
+ g_dbus_method_invocation_return_error (invocation,
+ GSM_MANAGER_ERROR,
+ GSM_MANAGER_ERROR_NOT_IN_RUNNING,
+ "Shutdown interface is only available after the Running phase starts");
+ return TRUE;
+ }
+
+ if (_log_out_is_locked_down (manager)) {
+ g_dbus_method_invocation_return_error (invocation,
+ GSM_MANAGER_ERROR,
+ GSM_MANAGER_ERROR_LOCKED_DOWN,
+ "Logout has been locked down");
+ return TRUE;
+ }
+
+ task = g_task_new (manager, priv->end_session_cancellable, (GAsyncReadyCallback) complete_end_session_task, invocation);
+
+ priv->pending_end_session_tasks = g_slist_prepend (priv->pending_end_session_tasks,
+ task);
+
+ request_shutdown (manager);
+
+ return TRUE;
+}
+
+static gboolean
+gsm_manager_reboot (GsmExportedManager *skeleton,
+ GDBusMethodInvocation *invocation,
+ GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ GTask *task;
+
+ g_debug ("GsmManager: Reboot called");
+
+ if (priv->phase < GSM_MANAGER_PHASE_RUNNING) {
+ g_dbus_method_invocation_return_error (invocation,
+ GSM_MANAGER_ERROR,
+ GSM_MANAGER_ERROR_NOT_IN_RUNNING,
+ "Reboot interface is only available after the Running phase starts");
+ return TRUE;
+ }
+
+ if (_log_out_is_locked_down (manager)) {
+ g_dbus_method_invocation_return_error (invocation,
+ GSM_MANAGER_ERROR,
+ GSM_MANAGER_ERROR_LOCKED_DOWN,
+ "Logout has been locked down");
+ return TRUE;
+ }
+
+ task = g_task_new (manager, priv->end_session_cancellable, (GAsyncReadyCallback) complete_end_session_task, invocation);
+
+ priv->pending_end_session_tasks = g_slist_prepend (priv->pending_end_session_tasks,
+ task);
+
+ request_reboot (manager);
+
+ return TRUE;
+}
+
+static gboolean
+gsm_manager_can_shutdown (GsmExportedManager *skeleton,
+ GDBusMethodInvocation *invocation,
+ GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ gboolean shutdown_available;
+
+ g_debug ("GsmManager: CanShutdown called");
+
+ shutdown_available = !_log_out_is_locked_down (manager) &&
+ (gsm_system_can_stop (priv->system)
+ || gsm_system_can_restart (priv->system)
+ || gsm_system_can_suspend (priv->system)
+ || gsm_system_can_hibernate (priv->system));
+
+ gsm_exported_manager_complete_can_shutdown (skeleton, invocation, shutdown_available);
+
+ return TRUE;
+}
+
+static gboolean
+gsm_manager_can_reboot_to_firmware_setup (GsmExportedManager *skeleton,
+ GDBusMethodInvocation *invocation,
+ GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ gboolean reboot_to_firmware_available;
+
+ g_debug ("GsmManager: CanRebootToFirmwareSetup called");
+
+ reboot_to_firmware_available = !_log_out_is_locked_down (manager) &&
+ gsm_system_can_restart_to_firmware_setup (priv->system);
+
+ gsm_exported_manager_complete_can_reboot_to_firmware_setup (skeleton, invocation, reboot_to_firmware_available);
+
+ return TRUE;
+}
+
+static gboolean
+gsm_manager_set_reboot_to_firmware_setup (GsmExportedManager *skeleton,
+ GDBusMethodInvocation *invocation,
+ gboolean enable,
+ GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ g_debug ("GsmManager: SetRebootToFirmwareSetup called");
+
+ gsm_system_set_restart_to_firmware_setup (priv->system, enable);
+
+ gsm_exported_manager_complete_set_reboot_to_firmware_setup (skeleton, invocation);
+
+ return TRUE;
+}
+
+static gboolean
+gsm_manager_setenv (GsmExportedManager *skeleton,
+ GDBusMethodInvocation *invocation,
+ const char *variable,
+ const char *value,
+ GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ if (priv->phase > GSM_MANAGER_PHASE_INITIALIZATION) {
+ g_dbus_method_invocation_return_error (invocation,
+ GSM_MANAGER_ERROR,
+ GSM_MANAGER_ERROR_NOT_IN_INITIALIZATION,
+ "Setenv interface is only available during the DisplayServer and Initialization phase");
+ } else {
+ gsm_util_setenv (variable, value);
+ gsm_exported_manager_complete_setenv (skeleton, invocation);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+gsm_manager_initialized (GsmExportedManager *skeleton,
+ GDBusMethodInvocation *invocation,
+ GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ /* Signaled by helper when gnome-session-initialized.target is reached. */
+ if (!priv->systemd_managed) {
+ g_dbus_method_invocation_return_error (invocation,
+ GSM_MANAGER_ERROR,
+ GSM_MANAGER_ERROR_GENERAL,
+ "Initialized interface is only available when gnome-session is managed by systemd");
+ } else if (priv->systemd_initialized) {
+ g_dbus_method_invocation_return_error (invocation,
+ GSM_MANAGER_ERROR,
+ GSM_MANAGER_ERROR_NOT_IN_INITIALIZATION,
+ "Systemd initialization was already signaled");
+ } else if (priv->phase > GSM_MANAGER_PHASE_INITIALIZATION) {
+ g_dbus_method_invocation_return_error (invocation,
+ GSM_MANAGER_ERROR,
+ GSM_MANAGER_ERROR_NOT_IN_INITIALIZATION,
+ "Initialized interface is only available during startup");
+ } else {
+ priv->systemd_initialized = TRUE;
+
+ if (priv->manager_initialized) {
+ g_assert (priv->phase == GSM_MANAGER_PHASE_INITIALIZATION);
+ priv->phase++;
+ start_phase (manager);
+ }
+
+ gsm_exported_manager_complete_initialized (skeleton, invocation);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+is_valid_category (int category)
+{
+ int categories[] = {
+ LC_CTYPE,
+ LC_NUMERIC,
+ LC_TIME,
+ LC_COLLATE,
+ LC_MONETARY,
+ LC_MESSAGES,
+#if defined (LC_PAPER)
+ LC_PAPER,
+#endif
+#if defined (LC_NAME)
+ LC_NAME,
+#endif
+#if defined (LC_ADDRESS)
+ LC_ADDRESS,
+#endif
+#if defined (LC_TELEPHONE)
+ LC_TELEPHONE,
+#endif
+#if defined (LC_MEASUREMENT)
+ LC_MEASUREMENT,
+#endif
+#if defined (LC_IDENTIFICATION)
+ LC_IDENTIFICATION,
+#endif
+ LC_ALL
+ };
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS(categories); i++)
+ if (categories[i] == category)
+ return TRUE;
+
+ return FALSE;
+}
+
+static gboolean
+gsm_manager_get_locale (GsmExportedManager *skeleton,
+ GDBusMethodInvocation *invocation,
+ int category,
+ GsmManager *manager)
+{
+ if (!is_valid_category (category)) {
+ g_dbus_method_invocation_return_error (invocation,
+ GSM_MANAGER_ERROR,
+ GSM_MANAGER_ERROR_INVALID_OPTION,
+ "GetLocale doesn't support locale category '%d'", category);
+ } else {
+ const char *value;
+ value = setlocale (category, NULL);
+ if (value == NULL)
+ value = "";
+
+ gsm_exported_manager_complete_get_locale (skeleton, invocation, value);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+gsm_manager_initialization_error (GsmExportedManager *skeleton,
+ GDBusMethodInvocation *invocation,
+ const char *message,
+ gboolean fatal,
+ GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ if (priv->phase != GSM_MANAGER_PHASE_INITIALIZATION) {
+ g_dbus_method_invocation_return_error (invocation,
+ GSM_MANAGER_ERROR,
+ GSM_MANAGER_ERROR_NOT_IN_INITIALIZATION,
+ "InitializationError interface is only available during the Initialization phase");
+ return TRUE;
+ }
+
+ gsm_util_init_error (fatal, "%s", message);
+ gsm_exported_manager_complete_initialization_error (skeleton, invocation);
+
+ return TRUE;
+}
+
+static void
+user_logout (GsmManager *manager,
+ GsmManagerLogoutMode mode)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ if (priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION) {
+ priv->logout_mode = mode;
+ end_session_or_show_shell_dialog (manager);
+ return;
+ }
+
+ request_logout (manager, mode);
+}
+
+gboolean
+gsm_manager_logout (GsmManager *manager,
+ guint logout_mode,
+ GError **error)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ if (priv->phase < GSM_MANAGER_PHASE_RUNNING) {
+ g_set_error (error,
+ GSM_MANAGER_ERROR,
+ GSM_MANAGER_ERROR_NOT_IN_RUNNING,
+ "Logout interface is only available after the Running phase starts");
+ return FALSE;
+ }
+
+ if (_log_out_is_locked_down (manager)) {
+ g_set_error (error,
+ GSM_MANAGER_ERROR,
+ GSM_MANAGER_ERROR_LOCKED_DOWN,
+ "Logout has been locked down");
+ return FALSE;
+ }
+
+ switch (logout_mode) {
+ case GSM_MANAGER_LOGOUT_MODE_NORMAL:
+ case GSM_MANAGER_LOGOUT_MODE_NO_CONFIRMATION:
+ case GSM_MANAGER_LOGOUT_MODE_FORCE:
+ user_logout (manager, logout_mode);
+ break;
+
+ default:
+ g_debug ("Unknown logout mode option");
+
+ g_set_error (error,
+ GSM_MANAGER_ERROR,
+ GSM_MANAGER_ERROR_INVALID_OPTION,
+ "Unknown logout mode flag");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+gsm_manager_logout_dbus (GsmExportedManager *skeleton,
+ GDBusMethodInvocation *invocation,
+ guint logout_mode,
+ GsmManager *manager)
+{
+ GError *error = NULL;
+
+ g_debug ("GsmManager: Logout called");
+
+ if (!gsm_manager_logout (manager, logout_mode, &error)) {
+ g_dbus_method_invocation_take_error (invocation, error);
+ } else {
+ gsm_exported_manager_complete_logout (skeleton, invocation);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+gsm_manager_register_client (GsmExportedManager *skeleton,
+ GDBusMethodInvocation *invocation,
+ const char *app_id,
+ const char *startup_id,
+ GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ char *new_startup_id;
+ const char *sender;
+ GsmClient *client;
+ GsmApp *app;
+
+ app = NULL;
+ client = NULL;
+
+ g_debug ("GsmManager: RegisterClient %s", startup_id);
+
+ if (priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION) {
+ g_debug ("Unable to register client: shutting down");
+
+ g_dbus_method_invocation_return_error (invocation,
+ GSM_MANAGER_ERROR,
+ GSM_MANAGER_ERROR_NOT_IN_RUNNING,
+ "Unable to register client");
+ return TRUE;
+ }
+
+ if (IS_STRING_EMPTY (startup_id)) {
+ new_startup_id = gsm_util_generate_startup_id ();
+ } else {
+
+ client = (GsmClient *)gsm_store_find (priv->clients,
+ (GsmStoreFunc)_client_has_startup_id,
+ (char *)startup_id);
+ /* We can't have two clients with the same startup id. */
+ if (client != NULL) {
+ g_debug ("Unable to register client: already registered");
+
+ g_dbus_method_invocation_return_error (invocation,
+ GSM_MANAGER_ERROR,
+ GSM_MANAGER_ERROR_ALREADY_REGISTERED,
+ "Unable to register client");
+ return TRUE;
+ }
+
+ new_startup_id = g_strdup (startup_id);
+ }
+
+ g_debug ("GsmManager: Adding new client %s to session", new_startup_id);
+
+ if (app == NULL && !IS_STRING_EMPTY (startup_id)) {
+ app = find_app_for_startup_id (manager, startup_id);
+ }
+ if (app == NULL && !IS_STRING_EMPTY (app_id)) {
+ /* try to associate this app id with a known app */
+ app = find_app_for_app_id (manager, app_id);
+ }
+
+ sender = g_dbus_method_invocation_get_sender (invocation);
+ client = gsm_dbus_client_new (new_startup_id, sender);
+ if (client == NULL) {
+ g_debug ("Unable to create client");
+
+ g_dbus_method_invocation_return_error (invocation,
+ GSM_MANAGER_ERROR,
+ GSM_MANAGER_ERROR_GENERAL,
+ "Unable to register client");
+ return TRUE;
+ }
+
+ gsm_store_add (priv->clients, gsm_client_peek_id (client), G_OBJECT (client));
+ /* the store will own the ref */
+ g_object_unref (client);
+
+ g_signal_connect (client,
+ "disconnected",
+ G_CALLBACK (on_client_disconnected),
+ manager);
+
+ if (app != NULL) {
+ gsm_client_set_app_id (client, gsm_app_peek_app_id (app));
+ gsm_app_set_registered (app, TRUE);
+ } else {
+ /* if an app id is specified store it in the client
+ so we can save it later */
+ gsm_client_set_app_id (client, app_id);
+ }
+
+ gsm_client_set_status (client, GSM_CLIENT_REGISTERED);
+
+ g_assert (new_startup_id != NULL);
+ g_free (new_startup_id);
+
+ gsm_exported_manager_complete_register_client (skeleton, invocation, gsm_client_peek_id (client));
+
+ return TRUE;
+}
+
+static gboolean
+gsm_manager_unregister_client (GsmExportedManager *skeleton,
+ GDBusMethodInvocation *invocation,
+ const char *client_id,
+ GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ GsmClient *client;
+
+ g_debug ("GsmManager: UnregisterClient %s", client_id);
+
+ client = (GsmClient *)gsm_store_lookup (priv->clients, client_id);
+ if (client == NULL) {
+ g_debug ("Unable to unregister client: not registered");
+
+ g_dbus_method_invocation_return_error (invocation,
+ GSM_MANAGER_ERROR,
+ GSM_MANAGER_ERROR_NOT_REGISTERED,
+ "Unable to unregister client");
+ return TRUE;
+ }
+
+ /* don't disconnect client here, only change the status.
+ Wait until it leaves the bus before disconnecting it */
+ gsm_client_set_status (client, GSM_CLIENT_UNREGISTERED);
+
+ gsm_exported_manager_complete_unregister_client (skeleton, invocation);
+
+ return TRUE;
+}
+
+static gboolean
+gsm_manager_inhibit (GsmExportedManager *skeleton,
+ GDBusMethodInvocation *invocation,
+ const char *app_id,
+ guint toplevel_xid,
+ const char *reason,
+ guint flags,
+ GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ GsmInhibitor *inhibitor;
+ guint cookie;
+
+ g_debug ("GsmManager: Inhibit xid=%u app_id=%s reason=%s flags=%u",
+ toplevel_xid,
+ app_id,
+ reason,
+ flags);
+
+ if (priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) {
+ GError *new_error;
+
+ new_error = g_error_new (GSM_MANAGER_ERROR,
+ GSM_MANAGER_ERROR_GENERAL,
+ "Forced logout cannot be inhibited");
+ g_debug ("GsmManager: Unable to inhibit: %s", new_error->message);
+ g_dbus_method_invocation_take_error (invocation, new_error);
+ return TRUE;
+ }
+
+ if (IS_STRING_EMPTY (app_id)) {
+ GError *new_error;
+
+ new_error = g_error_new (GSM_MANAGER_ERROR,
+ GSM_MANAGER_ERROR_GENERAL,
+ "Application ID not specified");
+ g_debug ("GsmManager: Unable to inhibit: %s", new_error->message);
+ g_dbus_method_invocation_take_error (invocation, new_error);
+ return TRUE;
+ }
+
+ if (IS_STRING_EMPTY (reason)) {
+ GError *new_error;
+
+ new_error = g_error_new (GSM_MANAGER_ERROR,
+ GSM_MANAGER_ERROR_GENERAL,
+ "Reason not specified");
+ g_debug ("GsmManager: Unable to inhibit: %s", new_error->message);
+ g_dbus_method_invocation_take_error (invocation, new_error);
+ return FALSE;
+ }
+
+ if (flags == 0) {
+ GError *new_error;
+
+ new_error = g_error_new (GSM_MANAGER_ERROR,
+ GSM_MANAGER_ERROR_GENERAL,
+ "Invalid inhibit flags");
+ g_debug ("GsmManager: Unable to inhibit: %s", new_error->message);
+ g_dbus_method_invocation_take_error (invocation, new_error);
+ return FALSE;
+ }
+
+ cookie = _generate_unique_cookie (manager);
+ inhibitor = gsm_inhibitor_new (app_id,
+ toplevel_xid,
+ flags,
+ reason,
+ g_dbus_method_invocation_get_sender (invocation),
+ cookie);
+ gsm_store_add (priv->inhibitors, gsm_inhibitor_peek_id (inhibitor), G_OBJECT (inhibitor));
+ g_object_unref (inhibitor);
+
+ gsm_exported_manager_complete_inhibit (skeleton, invocation, cookie);
+
+ return TRUE;
+}
+
+static gboolean
+gsm_manager_uninhibit (GsmExportedManager *skeleton,
+ GDBusMethodInvocation *invocation,
+ guint cookie,
+ GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ GsmInhibitor *inhibitor;
+
+ g_debug ("GsmManager: Uninhibit %u", cookie);
+
+ inhibitor = (GsmInhibitor *)gsm_store_find (priv->inhibitors,
+ (GsmStoreFunc)_find_by_cookie,
+ &cookie);
+ if (inhibitor == NULL) {
+ GError *new_error;
+
+ new_error = g_error_new (GSM_MANAGER_ERROR,
+ GSM_MANAGER_ERROR_GENERAL,
+ "Unable to uninhibit: Invalid cookie");
+ g_debug ("Unable to uninhibit: %s", new_error->message);
+ g_dbus_method_invocation_take_error (invocation, new_error);
+ return TRUE;
+ }
+
+ g_debug ("GsmManager: removing inhibitor %s %u reason '%s' %u connection %s",
+ gsm_inhibitor_peek_app_id (inhibitor),
+ gsm_inhibitor_peek_toplevel_xid (inhibitor),
+ gsm_inhibitor_peek_reason (inhibitor),
+ gsm_inhibitor_peek_flags (inhibitor),
+ gsm_inhibitor_peek_bus_name (inhibitor));
+
+ gsm_store_remove (priv->inhibitors, gsm_inhibitor_peek_id (inhibitor));
+
+ gsm_exported_manager_complete_uninhibit (skeleton, invocation);
+
+ return TRUE;
+}
+
+static gboolean
+gsm_manager_is_inhibited (GsmExportedManager *skeleton,
+ GDBusMethodInvocation *invocation,
+ guint flags,
+ GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ GsmInhibitor *inhibitor;
+ gboolean is_inhibited;
+
+ if (priv->inhibitors == NULL
+ || gsm_store_size (priv->inhibitors) == 0) {
+ is_inhibited = FALSE;
+ } else {
+ inhibitor = (GsmInhibitor *) gsm_store_find (priv->inhibitors,
+ (GsmStoreFunc)inhibitor_has_flag,
+ GUINT_TO_POINTER (flags));
+ if (inhibitor == NULL) {
+ is_inhibited = FALSE;
+ } else {
+ is_inhibited = TRUE;
+ }
+ }
+
+ gsm_exported_manager_complete_is_inhibited (skeleton, invocation, is_inhibited);
+
+ return TRUE;
+}
+
+static gboolean
+listify_store_ids (char *id,
+ GObject *object,
+ GPtrArray **array)
+{
+ g_ptr_array_add (*array, g_strdup (id));
+ return FALSE;
+}
+
+static gboolean
+gsm_manager_get_clients (GsmExportedManager *skeleton,
+ GDBusMethodInvocation *invocation,
+ GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ GPtrArray *clients;
+
+ clients = g_ptr_array_new_with_free_func (g_free);
+ gsm_store_foreach (priv->clients,
+ (GsmStoreFunc) listify_store_ids,
+ &clients);
+ g_ptr_array_add (clients, NULL);
+
+ gsm_exported_manager_complete_get_clients (skeleton, invocation,
+ (const gchar * const *) clients->pdata);
+ g_ptr_array_unref (clients);
+
+ return TRUE;
+}
+
+static gboolean
+gsm_manager_get_inhibitors (GsmExportedManager *skeleton,
+ GDBusMethodInvocation *invocation,
+ GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ GPtrArray *inhibitors;
+
+ inhibitors = g_ptr_array_new_with_free_func (g_free);
+ gsm_store_foreach (priv->inhibitors,
+ (GsmStoreFunc) listify_store_ids,
+ &inhibitors);
+ g_ptr_array_add (inhibitors, NULL);
+
+ gsm_exported_manager_complete_get_inhibitors (skeleton, invocation,
+ (const gchar * const *) inhibitors->pdata);
+ g_ptr_array_unref (inhibitors);
+
+ return TRUE;
+}
+
+static gboolean
+_app_has_autostart_condition (const char *id,
+ GsmApp *app,
+ const char *condition)
+{
+ gboolean has;
+ gboolean disabled;
+
+ has = gsm_app_has_autostart_condition (app, condition);
+ disabled = gsm_app_peek_is_disabled (app);
+
+ return has && !disabled;
+}
+
+static gboolean
+gsm_manager_is_autostart_condition_handled (GsmExportedManager *skeleton,
+ GDBusMethodInvocation *invocation,
+ const char *condition,
+ GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ GsmApp *app;
+ gboolean handled;
+
+ app = (GsmApp *) gsm_store_find (priv->apps,(
+ GsmStoreFunc) _app_has_autostart_condition,
+ (char *)condition);
+
+ if (app != NULL) {
+ handled = TRUE;
+ } else {
+ handled = FALSE;
+ }
+
+ gsm_exported_manager_complete_is_autostart_condition_handled (skeleton, invocation, handled);
+
+ return TRUE;
+}
+
+static gboolean
+gsm_manager_is_session_running (GsmExportedManager *skeleton,
+ GDBusMethodInvocation *invocation,
+ GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ gsm_exported_manager_complete_is_session_running (skeleton, invocation,
+ priv->phase == GSM_MANAGER_PHASE_RUNNING);
+ return TRUE;
+}
+
+static void
+on_session_connection_closed (GDBusConnection *connection,
+ gboolean remote_peer_vanished,
+ GError *error,
+ gpointer user_data)
+{
+ GsmManager *manager;
+ GsmManagerPrivate *priv;
+
+ manager = GSM_MANAGER (user_data);
+ priv = gsm_manager_get_instance_private (manager);
+
+ g_debug ("GsmManager: dbus disconnected; disconnecting dbus clients...");
+ priv->dbus_disconnected = TRUE;
+ remove_clients_for_connection (manager, NULL);
+}
+
+static gboolean
+register_manager (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ GDBusConnection *connection;
+ GsmExportedManager *skeleton;
+ GError *error = NULL;
+
+ connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+
+ if (error != NULL) {
+ g_critical ("error getting session bus: %s", error->message);
+ g_error_free (error);
+
+ exit (1);
+ }
+
+ skeleton = gsm_exported_manager_skeleton_new ();
+ g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (skeleton),
+ connection,
+ GSM_MANAGER_DBUS_PATH, &error);
+
+ if (error != NULL) {
+ g_critical ("error exporting manager on session bus: %s", error->message);
+ g_error_free (error);
+
+ exit (1);
+ }
+
+ g_signal_connect (skeleton, "handle-can-reboot-to-firmware-setup",
+ G_CALLBACK (gsm_manager_can_reboot_to_firmware_setup), manager);
+ g_signal_connect (skeleton, "handle-can-shutdown",
+ G_CALLBACK (gsm_manager_can_shutdown), manager);
+ g_signal_connect (skeleton, "handle-get-clients",
+ G_CALLBACK (gsm_manager_get_clients), manager);
+ g_signal_connect (skeleton, "handle-get-inhibitors",
+ G_CALLBACK (gsm_manager_get_inhibitors), manager);
+ g_signal_connect (skeleton, "handle-get-locale",
+ G_CALLBACK (gsm_manager_get_locale), manager);
+ g_signal_connect (skeleton, "handle-inhibit",
+ G_CALLBACK (gsm_manager_inhibit), manager);
+ g_signal_connect (skeleton, "handle-initialization-error",
+ G_CALLBACK (gsm_manager_initialization_error), manager);
+ g_signal_connect (skeleton, "handle-is-autostart-condition-handled",
+ G_CALLBACK (gsm_manager_is_autostart_condition_handled), manager);
+ g_signal_connect (skeleton, "handle-is-inhibited",
+ G_CALLBACK (gsm_manager_is_inhibited), manager);
+ g_signal_connect (skeleton, "handle-is-session-running",
+ G_CALLBACK (gsm_manager_is_session_running), manager);
+ g_signal_connect (skeleton, "handle-logout",
+ G_CALLBACK (gsm_manager_logout_dbus), manager);
+ g_signal_connect (skeleton, "handle-reboot",
+ G_CALLBACK (gsm_manager_reboot), manager);
+ g_signal_connect (skeleton, "handle-register-client",
+ G_CALLBACK (gsm_manager_register_client), manager);
+ g_signal_connect (skeleton, "handle-set-reboot-to-firmware-setup",
+ G_CALLBACK (gsm_manager_set_reboot_to_firmware_setup), manager);
+ g_signal_connect (skeleton, "handle-setenv",
+ G_CALLBACK (gsm_manager_setenv), manager);
+ g_signal_connect (skeleton, "handle-initialized",
+ G_CALLBACK (gsm_manager_initialized), manager);
+ g_signal_connect (skeleton, "handle-shutdown",
+ G_CALLBACK (gsm_manager_shutdown), manager);
+ g_signal_connect (skeleton, "handle-uninhibit",
+ G_CALLBACK (gsm_manager_uninhibit), manager);
+ g_signal_connect (skeleton, "handle-unregister-client",
+ G_CALLBACK (gsm_manager_unregister_client), manager);
+
+ priv->dbus_disconnected = FALSE;
+ g_signal_connect (connection, "closed",
+ G_CALLBACK (on_session_connection_closed), manager);
+
+ priv->connection = connection;
+ priv->skeleton = skeleton;
+
+ g_signal_connect (priv->system, "notify::active",
+ G_CALLBACK (on_gsm_system_active_changed), manager);
+
+ /* cold-plug SessionIsActive */
+ on_gsm_system_active_changed (priv->system, NULL, manager);
+
+ return TRUE;
+}
+
+static gboolean
+idle_timeout_get_mapping (GValue *value,
+ GVariant *variant,
+ gpointer user_data)
+{
+ guint32 idle_timeout;
+
+ idle_timeout = g_variant_get_uint32 (variant);
+ g_value_set_uint (value, idle_timeout * 1000);
+
+ return TRUE;
+}
+
+static void
+gsm_manager_init (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ priv->settings = g_settings_new (GSM_MANAGER_SCHEMA);
+ priv->session_settings = g_settings_new (SESSION_SCHEMA);
+ priv->screensaver_settings = g_settings_new (SCREENSAVER_SCHEMA);
+ priv->lockdown_settings = g_settings_new (LOCKDOWN_SCHEMA);
+
+ priv->inhibitors = gsm_store_new ();
+ g_signal_connect (priv->inhibitors,
+ "added",
+ G_CALLBACK (on_store_inhibitor_added),
+ manager);
+ g_signal_connect (priv->inhibitors,
+ "removed",
+ G_CALLBACK (on_store_inhibitor_removed),
+ manager);
+
+ priv->apps = gsm_store_new ();
+
+ priv->presence = gsm_presence_new ();
+ g_signal_connect (priv->presence,
+ "status-changed",
+ G_CALLBACK (on_presence_status_changed),
+ manager);
+
+ g_settings_bind_with_mapping (priv->session_settings,
+ KEY_IDLE_DELAY,
+ priv->presence,
+ "idle-timeout",
+ G_SETTINGS_BIND_GET,
+ idle_timeout_get_mapping,
+ NULL,
+ NULL, NULL);
+
+ priv->system = gsm_get_system ();
+ priv->shell = gsm_get_shell ();
+ priv->end_session_cancellable = g_cancellable_new ();
+}
+
+GsmManager *
+gsm_manager_get (void)
+{
+ return manager_object;
+}
+
+GsmManager *
+gsm_manager_new (GsmStore *client_store,
+ gboolean failsafe,
+ gboolean systemd_managed)
+{
+ if (manager_object != NULL) {
+ g_object_ref (manager_object);
+ } else {
+ gboolean res;
+
+ manager_object = g_object_new (GSM_TYPE_MANAGER,
+ "client-store", client_store,
+ "failsafe", failsafe,
+ "systemd-managed", systemd_managed,
+ NULL);
+
+ g_object_add_weak_pointer (manager_object,
+ (gpointer *) &manager_object);
+ res = register_manager (manager_object);
+ if (! res) {
+ g_object_unref (manager_object);
+ return NULL;
+ }
+ }
+
+ return GSM_MANAGER (manager_object);
+}
+
+static void
+disconnect_shell_dialog_signals (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ if (priv->shell_end_session_dialog_canceled_id != 0) {
+ g_signal_handler_disconnect (priv->shell,
+ priv->shell_end_session_dialog_canceled_id);
+ priv->shell_end_session_dialog_canceled_id = 0;
+ }
+
+ if (priv->shell_end_session_dialog_confirmed_logout_id != 0) {
+ g_signal_handler_disconnect (priv->shell,
+ priv->shell_end_session_dialog_confirmed_logout_id);
+ priv->shell_end_session_dialog_confirmed_logout_id = 0;
+ }
+
+ if (priv->shell_end_session_dialog_confirmed_shutdown_id != 0) {
+ g_signal_handler_disconnect (priv->shell,
+ priv->shell_end_session_dialog_confirmed_shutdown_id);
+ priv->shell_end_session_dialog_confirmed_shutdown_id = 0;
+ }
+
+ if (priv->shell_end_session_dialog_confirmed_reboot_id != 0) {
+ g_signal_handler_disconnect (priv->shell,
+ priv->shell_end_session_dialog_confirmed_reboot_id);
+ priv->shell_end_session_dialog_confirmed_reboot_id = 0;
+ }
+
+ if (priv->shell_end_session_dialog_open_failed_id != 0) {
+ g_signal_handler_disconnect (priv->shell,
+ priv->shell_end_session_dialog_open_failed_id);
+ priv->shell_end_session_dialog_open_failed_id = 0;
+ }
+}
+
+static void
+on_shell_end_session_dialog_canceled (GsmShell *shell,
+ GsmManager *manager)
+{
+ cancel_end_session (manager);
+ disconnect_shell_dialog_signals (manager);
+}
+
+static void
+_handle_end_session_dialog_response (GsmManager *manager,
+ GsmManagerLogoutType logout_type)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ /* Note we're checking for END_SESSION here and
+ * QUERY_END_SESSION in the fallback cases elsewhere.
+ *
+ * That's because they run at different times in the logout
+ * process. The shell combines the inhibit and
+ * confirmation dialogs, so it gets displayed after we've collected
+ * inhibitors. The fallback code has two distinct dialogs, once of
+ * which we can (and do show) before collecting the inhibitors.
+ */
+ if (priv->phase >= GSM_MANAGER_PHASE_END_SESSION) {
+ /* Already shutting down, nothing more to do */
+ return;
+ }
+
+ priv->logout_mode = GSM_MANAGER_LOGOUT_MODE_FORCE;
+ priv->logout_type = logout_type;
+ end_phase (manager);
+}
+
+static void
+on_shell_end_session_dialog_confirmed_logout (GsmShell *shell,
+ GsmManager *manager)
+{
+ _handle_end_session_dialog_response (manager, GSM_MANAGER_LOGOUT_LOGOUT);
+ disconnect_shell_dialog_signals (manager);
+}
+
+static void
+on_shell_end_session_dialog_confirmed_shutdown (GsmShell *shell,
+ GsmManager *manager)
+{
+ _handle_end_session_dialog_response (manager, GSM_MANAGER_LOGOUT_SHUTDOWN);
+ disconnect_shell_dialog_signals (manager);
+}
+
+static void
+on_shell_end_session_dialog_confirmed_reboot (GsmShell *shell,
+ GsmManager *manager)
+{
+ _handle_end_session_dialog_response (manager, GSM_MANAGER_LOGOUT_REBOOT);
+ disconnect_shell_dialog_signals (manager);
+}
+
+static void
+connect_shell_dialog_signals (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ if (priv->shell_end_session_dialog_canceled_id != 0)
+ return;
+
+ priv->shell_end_session_dialog_canceled_id =
+ g_signal_connect (priv->shell,
+ "end-session-dialog-canceled",
+ G_CALLBACK (on_shell_end_session_dialog_canceled),
+ manager);
+
+ priv->shell_end_session_dialog_open_failed_id =
+ g_signal_connect (priv->shell,
+ "end-session-dialog-open-failed",
+ G_CALLBACK (on_shell_end_session_dialog_canceled),
+ manager);
+
+ priv->shell_end_session_dialog_confirmed_logout_id =
+ g_signal_connect (priv->shell,
+ "end-session-dialog-confirmed-logout",
+ G_CALLBACK (on_shell_end_session_dialog_confirmed_logout),
+ manager);
+
+ priv->shell_end_session_dialog_confirmed_shutdown_id =
+ g_signal_connect (priv->shell,
+ "end-session-dialog-confirmed-shutdown",
+ G_CALLBACK (on_shell_end_session_dialog_confirmed_shutdown),
+ manager);
+
+ priv->shell_end_session_dialog_confirmed_reboot_id =
+ g_signal_connect (priv->shell,
+ "end-session-dialog-confirmed-reboot",
+ G_CALLBACK (on_shell_end_session_dialog_confirmed_reboot),
+ manager);
+}
+
+static void
+show_shell_end_session_dialog (GsmManager *manager,
+ GsmShellEndSessionDialogType type)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ if (!gsm_shell_is_running (priv->shell))
+ return;
+
+ gsm_shell_open_end_session_dialog (priv->shell,
+ type,
+ priv->inhibitors);
+ connect_shell_dialog_signals (manager);
+}
+
+/*
+ dbus-send --session --type=method_call --print-reply
+ --dest=org.gnome.SessionManager
+ /org/gnome/SessionManager
+ org.freedesktop.DBus.Introspectable.Introspect
+*/
+
+gboolean
+gsm_manager_set_phase (GsmManager *manager,
+ GsmManagerPhase phase)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ g_return_val_if_fail (GSM_IS_MANAGER (manager), FALSE);
+ priv->phase = phase;
+ return (TRUE);
+}
+
+static void
+append_app (GsmManager *manager,
+ GsmApp *app,
+ gboolean is_required)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ const char *id;
+ const char *app_id;
+ GsmApp *dup;
+
+ id = gsm_app_peek_id (app);
+ if (IS_STRING_EMPTY (id)) {
+ g_debug ("GsmManager: not adding app: no id");
+ return;
+ }
+
+ dup = (GsmApp *)gsm_store_lookup (priv->apps, id);
+ if (dup != NULL) {
+ g_debug ("GsmManager: not adding app: already added");
+ return;
+ }
+
+ app_id = gsm_app_peek_app_id (app);
+ if (IS_STRING_EMPTY (app_id)) {
+ g_debug ("GsmManager: not adding app: no app-id");
+ return;
+ }
+
+ dup = find_app_for_app_id (manager, app_id);
+ if (dup != NULL) {
+ g_debug ("GsmManager: not adding app: app-id '%s' already exists", app_id);
+
+ if (is_required &&
+ !g_slist_find (priv->required_apps, dup)) {
+ g_debug ("GsmManager: making app '%s' required", gsm_app_peek_app_id (dup));
+ priv->required_apps = g_slist_prepend (priv->required_apps, dup);
+ }
+
+ return;
+ }
+
+ gsm_store_add (priv->apps, id, G_OBJECT (app));
+ if (is_required) {
+ g_debug ("GsmManager: adding required app %s", gsm_app_peek_app_id (app));
+ priv->required_apps = g_slist_prepend (priv->required_apps, app);
+ }
+}
+
+static gboolean
+add_autostart_app_internal (GsmManager *manager,
+ const char *path,
+ gboolean is_required)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ GsmApp *app;
+ char **internal_provides;
+ GError *error = NULL;
+
+ g_return_val_if_fail (GSM_IS_MANAGER (manager), FALSE);
+ g_return_val_if_fail (path != NULL, FALSE);
+
+ /* Note: if we cannot add the app because its service is already
+ * provided, because its app-id is taken, or because of any other
+ * reason meaning there is already an app playing its role, then we
+ * should make sure that relevant properties (like
+ * provides/is_required) are set in the pre-existing app if needed. */
+ app = gsm_autostart_app_new (path, priv->systemd_managed, &error);
+ if (app == NULL) {
+ g_warning ("%s", error->message);
+ g_clear_error (&error);
+ return FALSE;
+ }
+
+ internal_provides = gsm_app_get_provides (app);
+ if (internal_provides) {
+ int i;
+ gboolean provided = FALSE;
+
+ for (i = 0; internal_provides[i] != NULL; i++) {
+ GsmApp *dup;
+
+ dup = (GsmApp *)gsm_store_find (priv->apps,
+ (GsmStoreFunc)_find_app_provides,
+ (char *)internal_provides[i]);
+ if (dup != NULL) {
+ g_debug ("GsmManager: service '%s' is already provided", internal_provides[i]);
+
+ if (is_required &&
+ !g_slist_find (priv->required_apps, dup)) {
+ g_debug ("GsmManager: making app '%s' required", gsm_app_peek_app_id (dup));
+ priv->required_apps = g_slist_prepend (priv->required_apps, dup);
+ }
+
+ provided = TRUE;
+ break;
+ }
+ }
+
+ g_strfreev (internal_provides);
+
+ if (provided) {
+ g_object_unref (app);
+ return FALSE;
+ }
+ }
+
+ g_debug ("GsmManager: read %s", path);
+ append_app (manager, app, is_required);
+ g_object_unref (app);
+
+ return TRUE;
+}
+
+gboolean
+gsm_manager_add_autostart_app (GsmManager *manager,
+ const char *path)
+{
+ return add_autostart_app_internal (manager, path, FALSE);
+}
+
+/**
+ * gsm_manager_add_required_app:
+ * @manager: a #GsmManager
+ * @path: Path to desktop file
+ *
+ * Similar to gsm_manager_add_autostart_app(), except marks the
+ * component as being required; we then try harder to ensure
+ * it's running and inform the user if we can't.
+ *
+ */
+gboolean
+gsm_manager_add_required_app (GsmManager *manager,
+ const char *path)
+{
+ return add_autostart_app_internal (manager, path, TRUE);
+}
+
+
+gboolean
+gsm_manager_add_autostart_apps_from_dir (GsmManager *manager,
+ const char *path)
+{
+ GDir *dir;
+ const char *name;
+
+ g_return_val_if_fail (GSM_IS_MANAGER (manager), FALSE);
+ g_return_val_if_fail (path != NULL, FALSE);
+
+ g_debug ("GsmManager: *** Adding autostart apps for %s", path);
+
+ dir = g_dir_open (path, 0, NULL);
+ if (dir == NULL) {
+ return FALSE;
+ }
+
+ while ((name = g_dir_read_name (dir))) {
+ char *desktop_file;
+
+ if (!g_str_has_suffix (name, ".desktop")) {
+ continue;
+ }
+
+ desktop_file = g_build_filename (path, name, NULL);
+ gsm_manager_add_autostart_app (manager, desktop_file);
+ g_free (desktop_file);
+ }
+
+ g_dir_close (dir);
+
+ return TRUE;
+}
+
+static void
+on_shutdown_prepared (GsmSystem *system,
+ gboolean success,
+ GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+
+ g_debug ("GsmManager: on_shutdown_prepared, success: %d", success);
+ g_signal_handlers_disconnect_by_func (system, on_shutdown_prepared, manager);
+
+ if (success) {
+ /* move to end-session phase */
+ g_assert (priv->phase == GSM_MANAGER_PHASE_QUERY_END_SESSION);
+ priv->phase++;
+ start_phase (manager);
+ } else {
+ disconnect_shell_dialog_signals (manager);
+ gsm_shell_close_end_session_dialog (priv->shell);
+ /* back to running phase */
+ cancel_end_session (manager);
+ }
+}
+
+static gboolean
+do_query_end_session_exit (GsmManager *manager)
+{
+ GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager);
+ gboolean reboot = FALSE;
+ gboolean shutdown = FALSE;
+
+ switch (priv->logout_type) {
+ case GSM_MANAGER_LOGOUT_LOGOUT:
+ break;
+ case GSM_MANAGER_LOGOUT_REBOOT:
+ case GSM_MANAGER_LOGOUT_REBOOT_INTERACT:
+ reboot = TRUE;
+ break;
+ case GSM_MANAGER_LOGOUT_SHUTDOWN:
+ case GSM_MANAGER_LOGOUT_SHUTDOWN_INTERACT:
+ shutdown = TRUE;
+ break;
+ default:
+ g_warning ("Unexpected logout type %d in do_query_end_session_exit()",
+ priv->logout_type);
+ break;
+ }
+
+ if (reboot || shutdown) {
+ g_signal_connect (priv->system, "shutdown-prepared",
+ G_CALLBACK (on_shutdown_prepared), manager);
+ gsm_system_prepare_shutdown (priv->system, reboot);
+ return FALSE; /* don't leave query end session yet */
+ }
+
+ return TRUE; /* go to end session phase */
+}
diff --git a/gnome-session/gsm-manager.h b/gnome-session/gsm-manager.h
new file mode 100644
index 0000000..7cd5cc4
--- /dev/null
+++ b/gnome-session/gsm-manager.h
@@ -0,0 +1,121 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 William Jon McCann <jmccann@redhat.com>
+ *
+ * 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 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/>.
+ *
+ */
+
+
+#ifndef __GSM_MANAGER_H
+#define __GSM_MANAGER_H
+
+#include <glib-object.h>
+
+#include "gsm-store.h"
+#include "gsm-manager-logout-mode.h"
+
+G_BEGIN_DECLS
+
+#define GSM_TYPE_MANAGER (gsm_manager_get_type ())
+G_DECLARE_DERIVABLE_TYPE (GsmManager, gsm_manager, GSM, MANAGER, GObject)
+
+struct _GsmManagerClass
+{
+ GObjectClass parent_class;
+
+ void (* phase_changed) (GsmManager *manager,
+ const char *phase);
+};
+
+typedef enum {
+ /* gsm's own startup/initialization phase */
+ GSM_MANAGER_PHASE_STARTUP = 0,
+ /* gnome-initial-setup */
+ GSM_MANAGER_PHASE_EARLY_INITIALIZATION,
+ /* gnome-keyring-daemon */
+ GSM_MANAGER_PHASE_PRE_DISPLAY_SERVER,
+ /* wayland compositor and XWayland */
+ GSM_MANAGER_PHASE_DISPLAY_SERVER,
+ /* xrandr setup, gnome-settings-daemon, etc */
+ GSM_MANAGER_PHASE_INITIALIZATION,
+ /* window/compositing managers */
+ GSM_MANAGER_PHASE_WINDOW_MANAGER,
+ /* apps that will create _NET_WM_WINDOW_TYPE_PANEL windows */
+ GSM_MANAGER_PHASE_PANEL,
+ /* apps that will create _NET_WM_WINDOW_TYPE_DESKTOP windows */
+ GSM_MANAGER_PHASE_DESKTOP,
+ /* everything else */
+ GSM_MANAGER_PHASE_APPLICATION,
+ /* done launching */
+ GSM_MANAGER_PHASE_RUNNING,
+ /* shutting down */
+ GSM_MANAGER_PHASE_QUERY_END_SESSION,
+ GSM_MANAGER_PHASE_END_SESSION,
+ GSM_MANAGER_PHASE_EXIT
+} GsmManagerPhase;
+
+typedef enum
+{
+ GSM_MANAGER_ERROR_GENERAL = 0,
+ GSM_MANAGER_ERROR_NOT_IN_INITIALIZATION,
+ GSM_MANAGER_ERROR_NOT_IN_RUNNING,
+ GSM_MANAGER_ERROR_ALREADY_REGISTERED,
+ GSM_MANAGER_ERROR_NOT_REGISTERED,
+ GSM_MANAGER_ERROR_INVALID_OPTION,
+ GSM_MANAGER_ERROR_LOCKED_DOWN,
+ GSM_MANAGER_NUM_ERRORS
+} GsmManagerError;
+
+#define GSM_MANAGER_ERROR gsm_manager_error_quark ()
+GQuark gsm_manager_error_quark (void);
+
+GsmManager * gsm_manager_new (GsmStore *client_store,
+ gboolean failsafe,
+ gboolean systemd_managed);
+GsmManager * gsm_manager_get (void);
+
+gboolean gsm_manager_get_failsafe (GsmManager *manager);
+gboolean gsm_manager_get_systemd_managed (GsmManager *manager);
+
+gboolean gsm_manager_add_autostart_app (GsmManager *manager,
+ const char *path);
+gboolean gsm_manager_add_required_app (GsmManager *manager,
+ const char *path);
+gboolean gsm_manager_add_autostart_apps_from_dir (GsmManager *manager,
+ const char *path);
+gboolean gsm_manager_add_legacy_session_apps (GsmManager *manager,
+ const char *path);
+
+void gsm_manager_start (GsmManager *manager);
+
+char * _gsm_manager_get_default_session (GsmManager *manager);
+
+void _gsm_manager_set_active_session (GsmManager *manager,
+ const char *session_name,
+ gboolean is_fallback);
+
+void _gsm_manager_set_renderer (GsmManager *manager,
+ const char *renderer);
+
+gboolean gsm_manager_logout (GsmManager *manager,
+ guint logout_mode,
+ GError **error);
+
+gboolean gsm_manager_set_phase (GsmManager *manager,
+ GsmManagerPhase phase);
+
+G_END_DECLS
+
+#endif /* __GSM_MANAGER_H */
diff --git a/gnome-session/gsm-presence-flag.h b/gnome-session/gsm-presence-flag.h
new file mode 100644
index 0000000..f3bbca4
--- /dev/null
+++ b/gnome-session/gsm-presence-flag.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2009 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/>.
+ */
+
+#ifndef __GSM_PRESENCE_FLAG_H__
+#define __GSM_PRESENCE_FLAG_H__
+
+G_BEGIN_DECLS
+
+typedef enum {
+ GSM_PRESENCE_STATUS_AVAILABLE = 0,
+ GSM_PRESENCE_STATUS_INVISIBLE,
+ GSM_PRESENCE_STATUS_BUSY,
+ GSM_PRESENCE_STATUS_IDLE,
+} GsmPresenceStatus;
+
+G_END_DECLS
+
+#endif /* __GSM_PRESENCE_FLAG_H__ */
diff --git a/gnome-session/gsm-presence.c b/gnome-session/gsm-presence.c
new file mode 100644
index 0000000..f4aa488
--- /dev/null
+++ b/gnome-session/gsm-presence.c
@@ -0,0 +1,542 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2009 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 <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include <libgnome-desktop/gnome-idle-monitor.h>
+
+#include "gsm-presence.h"
+#include "org.gnome.SessionManager.Presence.h"
+
+#define GSM_PRESENCE_DBUS_IFACE "org.gnome.SessionManager.Presence"
+#define GSM_PRESENCE_DBUS_PATH "/org/gnome/SessionManager/Presence"
+
+#define GS_NAME "org.gnome.ScreenSaver"
+#define GS_PATH "/org/gnome/ScreenSaver"
+#define GS_INTERFACE "org.gnome.ScreenSaver"
+
+#define MAX_STATUS_TEXT 140
+
+#define GSM_PRESENCE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSM_TYPE_PRESENCE, GsmPresencePrivate))
+
+struct GsmPresencePrivate
+{
+ guint status;
+ guint saved_status;
+ char *status_text;
+ gboolean idle_enabled;
+ GnomeIdleMonitor *idle_monitor;
+ guint idle_watch_id;
+ guint idle_timeout;
+ gboolean screensaver_active;
+ GDBusConnection *connection;
+ GDBusProxy *screensaver_proxy;
+
+ GsmExportedPresence *skeleton;
+};
+
+enum {
+ PROP_0,
+ PROP_IDLE_ENABLED,
+ PROP_IDLE_TIMEOUT,
+};
+
+enum {
+ STATUS_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE (GsmPresence, gsm_presence, G_TYPE_OBJECT)
+
+static const GDBusErrorEntry gsm_presence_error_entries[] = {
+ { GSM_PRESENCE_ERROR_GENERAL, GSM_PRESENCE_DBUS_IFACE ".GeneralError" }
+};
+
+GQuark
+gsm_presence_error_quark (void)
+{
+ static volatile gsize quark_volatile = 0;
+
+ g_dbus_error_register_error_domain ("gsm_presence_error",
+ &quark_volatile,
+ gsm_presence_error_entries,
+ G_N_ELEMENTS (gsm_presence_error_entries));
+ return quark_volatile;
+}
+
+static void idle_became_active_cb (GnomeIdleMonitor *idle_monitor,
+ guint id,
+ gpointer user_data);
+
+static void
+gsm_presence_set_status (GsmPresence *presence,
+ guint status)
+{
+ if (status != presence->priv->status) {
+ presence->priv->status = status;
+ gsm_exported_presence_set_status (presence->priv->skeleton, status);
+ gsm_exported_presence_emit_status_changed (presence->priv->skeleton, presence->priv->status);
+ g_signal_emit (presence, signals[STATUS_CHANGED], 0, presence->priv->status);
+ }
+}
+
+static gboolean
+gsm_presence_set_status_text (GsmPresence *presence,
+ const char *status_text,
+ GError **error)
+{
+ g_return_val_if_fail (GSM_IS_PRESENCE (presence), FALSE);
+
+ g_free (presence->priv->status_text);
+ presence->priv->status_text = NULL;
+
+ /* check length */
+ if (status_text != NULL && strlen (status_text) > MAX_STATUS_TEXT) {
+ g_set_error (error,
+ GSM_PRESENCE_ERROR,
+ GSM_PRESENCE_ERROR_GENERAL,
+ "Status text too long");
+ return FALSE;
+ }
+
+ if (status_text != NULL) {
+ presence->priv->status_text = g_strdup (status_text);
+ } else {
+ presence->priv->status_text = g_strdup ("");
+ }
+
+ gsm_exported_presence_set_status_text (presence->priv->skeleton, presence->priv->status_text);
+ gsm_exported_presence_emit_status_text_changed (presence->priv->skeleton, presence->priv->status_text);
+ return TRUE;
+}
+
+static void
+set_session_idle (GsmPresence *presence,
+ gboolean is_idle)
+{
+ g_debug ("GsmPresence: setting idle: %d", is_idle);
+
+ if (is_idle) {
+ if (presence->priv->status == GSM_PRESENCE_STATUS_IDLE) {
+ g_debug ("GsmPresence: already idle, ignoring");
+ return;
+ }
+
+ /* save current status */
+ presence->priv->saved_status = presence->priv->status;
+ gsm_presence_set_status (presence, GSM_PRESENCE_STATUS_IDLE);
+
+ gnome_idle_monitor_add_user_active_watch (presence->priv->idle_monitor,
+ idle_became_active_cb,
+ presence,
+ NULL);
+ } else {
+ if (presence->priv->status != GSM_PRESENCE_STATUS_IDLE) {
+ g_debug ("GsmPresence: already not idle, ignoring");
+ return;
+ }
+
+ /* restore saved status */
+ gsm_presence_set_status (presence, presence->priv->saved_status);
+ g_debug ("GsmPresence: setting non-idle status %d", presence->priv->saved_status);
+ presence->priv->saved_status = GSM_PRESENCE_STATUS_AVAILABLE;
+ }
+}
+
+static void
+idle_became_idle_cb (GnomeIdleMonitor *idle_monitor,
+ guint id,
+ gpointer user_data)
+{
+ GsmPresence *presence = user_data;
+ set_session_idle (presence, TRUE);
+}
+
+static void
+idle_became_active_cb (GnomeIdleMonitor *idle_monitor,
+ guint id,
+ gpointer user_data)
+{
+ GsmPresence *presence = user_data;
+ set_session_idle (presence, FALSE);
+}
+
+static void
+reset_idle_watch (GsmPresence *presence)
+{
+ if (presence->priv->idle_watch_id > 0) {
+ g_debug ("GsmPresence: removing idle watch (%i)", presence->priv->idle_watch_id);
+ gnome_idle_monitor_remove_watch (presence->priv->idle_monitor,
+ presence->priv->idle_watch_id);
+ presence->priv->idle_watch_id = 0;
+ }
+
+ if (presence->priv->idle_enabled
+ && presence->priv->idle_timeout > 0) {
+ presence->priv->idle_watch_id = gnome_idle_monitor_add_idle_watch (presence->priv->idle_monitor,
+ presence->priv->idle_timeout,
+ idle_became_idle_cb,
+ presence,
+ NULL);
+ g_debug ("GsmPresence: adding idle watch (%i) for %d secs",
+ presence->priv->idle_watch_id,
+ presence->priv->idle_timeout / 1000);
+ }
+}
+
+static void
+on_screensaver_dbus_signal (GDBusProxy *proxy,
+ gchar *sender_name,
+ gchar *signal_name,
+ GVariant *parameters,
+ GsmPresence *presence)
+{
+ gboolean is_active;
+
+ if (g_strcmp0 (signal_name, "ActiveChanged") != 0) {
+ return;
+ }
+
+ g_variant_get (parameters, "(b)", &is_active);
+
+ if (presence->priv->screensaver_active != is_active) {
+ presence->priv->screensaver_active = is_active;
+ set_session_idle (presence, is_active);
+ }
+}
+
+static void
+screensaver_get_active_cb (GDBusProxy *screensaver_proxy,
+ GAsyncResult *res,
+ GsmPresence *presence)
+{
+ g_autoptr(GVariant) data = NULL;
+ g_autoptr(GError) error = NULL;
+ gboolean is_active;
+
+ data = g_dbus_proxy_call_finish (screensaver_proxy, res, &error);
+ if (!data) {
+ if (error) {
+ g_warning ("Could not retrieve current screensaver active state: %s",
+ error->message);
+ } else {
+ g_warning ("Could not retrieve current screensaver active state!");
+ }
+
+ return;
+ }
+
+ g_variant_get (data, "(b)", &is_active);
+ if (presence->priv->screensaver_active != is_active) {
+ presence->priv->screensaver_active = is_active;
+ set_session_idle (presence, is_active);
+ }
+}
+
+static void
+on_screensaver_name_owner_changed (GDBusProxy *screensaver_proxy,
+ GParamSpec *pspec,
+ GsmPresence *presence)
+{
+ gchar *name_owner;
+
+ name_owner = g_dbus_proxy_get_name_owner (screensaver_proxy);
+ if (name_owner == NULL) {
+ g_debug ("Detected that screensaver has left the bus");
+
+ presence->priv->screensaver_active = FALSE;
+ set_session_idle (presence, FALSE);
+ } else {
+ g_debug ("Detected that screensaver has aquired the bus");
+
+ g_dbus_proxy_call (presence->priv->screensaver_proxy,
+ "GetActive",
+ NULL,
+ G_DBUS_CALL_FLAGS_NO_AUTO_START,
+ 1000,
+ NULL,
+ (GAsyncReadyCallback) screensaver_get_active_cb,
+ presence);
+ }
+
+ g_free (name_owner);
+}
+
+static gboolean
+gsm_presence_set_status_text_dbus (GsmExportedPresence *skeleton,
+ GDBusMethodInvocation *invocation,
+ gchar *status_text,
+ GsmPresence *presence)
+{
+ GError *error = NULL;
+
+ if (gsm_presence_set_status_text (presence, status_text, &error)) {
+ gsm_exported_presence_complete_set_status_text (skeleton, invocation);
+ } else {
+ g_dbus_method_invocation_take_error (invocation, error);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+gsm_presence_set_status_dbus (GsmExportedPresence *skeleton,
+ GDBusMethodInvocation *invocation,
+ guint status,
+ GsmPresence *presence)
+{
+ gsm_presence_set_status (presence, status);
+ gsm_exported_presence_complete_set_status (skeleton, invocation);
+ return TRUE;
+}
+
+static gboolean
+register_presence (GsmPresence *presence)
+{
+ GError *error;
+ GsmExportedPresence *skeleton;
+
+ error = NULL;
+ presence->priv->connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+ if (error != NULL) {
+ g_critical ("error getting session bus: %s", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ skeleton = gsm_exported_presence_skeleton_new ();
+ presence->priv->skeleton = skeleton;
+ g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (skeleton),
+ presence->priv->connection,
+ GSM_PRESENCE_DBUS_PATH, &error);
+ if (error != NULL) {
+ g_critical ("error registering presence object on session bus: %s", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ g_signal_connect (skeleton, "handle-set-status",
+ G_CALLBACK (gsm_presence_set_status_dbus), presence);
+ g_signal_connect (skeleton, "handle-set-status-text",
+ G_CALLBACK (gsm_presence_set_status_text_dbus), presence);
+
+ return TRUE;
+}
+
+static GObject *
+gsm_presence_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_properties)
+{
+ GsmPresence *presence;
+ gboolean res;
+ GError *error = NULL;
+
+ presence = GSM_PRESENCE (G_OBJECT_CLASS (gsm_presence_parent_class)->constructor (type,
+ n_construct_properties,
+ construct_properties));
+
+ res = register_presence (presence);
+ if (! res) {
+ g_warning ("Unable to register presence with session bus");
+ }
+
+ /* This only connects to signals and resolves the current name owner
+ * synchronously. It is important to not auto-start the service!
+ */
+ presence->priv->screensaver_proxy = g_dbus_proxy_new_sync (presence->priv->connection,
+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START |
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
+ NULL,
+ GS_NAME,
+ GS_PATH,
+ GS_INTERFACE,
+ NULL, &error);
+ if (error != NULL) {
+ g_critical ("Unable to create a DBus proxy for GnomeScreensaver: %s",
+ error->message);
+ g_error_free (error);
+ } else {
+ g_signal_connect (presence->priv->screensaver_proxy, "notify::g-name-owner",
+ G_CALLBACK (on_screensaver_name_owner_changed), presence);
+ g_signal_connect (presence->priv->screensaver_proxy, "g-signal",
+ G_CALLBACK (on_screensaver_dbus_signal), presence);
+ }
+
+ return G_OBJECT (presence);
+}
+
+static void
+gsm_presence_init (GsmPresence *presence)
+{
+ presence->priv = GSM_PRESENCE_GET_PRIVATE (presence);
+
+ presence->priv->idle_monitor = gnome_idle_monitor_new ();
+}
+
+void
+gsm_presence_set_idle_enabled (GsmPresence *presence,
+ gboolean enabled)
+{
+ g_return_if_fail (GSM_IS_PRESENCE (presence));
+
+ if (presence->priv->idle_enabled != enabled) {
+ presence->priv->idle_enabled = enabled;
+ reset_idle_watch (presence);
+ g_object_notify (G_OBJECT (presence), "idle-enabled");
+
+ }
+}
+
+void
+gsm_presence_set_idle_timeout (GsmPresence *presence,
+ guint timeout)
+{
+ g_return_if_fail (GSM_IS_PRESENCE (presence));
+
+ if (timeout != presence->priv->idle_timeout) {
+ presence->priv->idle_timeout = timeout;
+ reset_idle_watch (presence);
+ g_object_notify (G_OBJECT (presence), "idle-timeout");
+ }
+}
+
+static void
+gsm_presence_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GsmPresence *self;
+
+ self = GSM_PRESENCE (object);
+
+ switch (prop_id) {
+ case PROP_IDLE_ENABLED:
+ gsm_presence_set_idle_enabled (self, g_value_get_boolean (value));
+ break;
+ case PROP_IDLE_TIMEOUT:
+ gsm_presence_set_idle_timeout (self, g_value_get_uint (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gsm_presence_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GsmPresence *self;
+
+ self = GSM_PRESENCE (object);
+
+ switch (prop_id) {
+ case PROP_IDLE_ENABLED:
+ g_value_set_boolean (value, self->priv->idle_enabled);
+ break;
+ case PROP_IDLE_TIMEOUT:
+ g_value_set_uint (value, self->priv->idle_timeout);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gsm_presence_finalize (GObject *object)
+{
+ GsmPresence *presence = (GsmPresence *) object;
+
+ if (presence->priv->idle_watch_id > 0) {
+ gnome_idle_monitor_remove_watch (presence->priv->idle_monitor,
+ presence->priv->idle_watch_id);
+ presence->priv->idle_watch_id = 0;
+ }
+
+ g_clear_pointer (&presence->priv->status_text, g_free);
+ g_clear_object (&presence->priv->idle_monitor);
+ g_clear_object (&presence->priv->screensaver_proxy);
+
+ G_OBJECT_CLASS (gsm_presence_parent_class)->finalize (object);
+}
+
+static void
+gsm_presence_class_init (GsmPresenceClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gsm_presence_finalize;
+ object_class->constructor = gsm_presence_constructor;
+ object_class->get_property = gsm_presence_get_property;
+ object_class->set_property = gsm_presence_set_property;
+
+ signals [STATUS_CHANGED] =
+ g_signal_new ("status-changed",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmPresenceClass, status_changed),
+ NULL,
+ NULL,
+ g_cclosure_marshal_VOID__UINT,
+ G_TYPE_NONE,
+ 1, G_TYPE_UINT);
+
+ g_object_class_install_property (object_class,
+ PROP_IDLE_ENABLED,
+ g_param_spec_boolean ("idle-enabled",
+ NULL,
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+ g_object_class_install_property (object_class,
+ PROP_IDLE_TIMEOUT,
+ g_param_spec_uint ("idle-timeout",
+ "idle timeout",
+ "idle timeout",
+ 0,
+ G_MAXINT,
+ 120000,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ g_type_class_add_private (klass, sizeof (GsmPresencePrivate));
+}
+
+GsmPresence *
+gsm_presence_new (void)
+{
+ GsmPresence *presence;
+
+ presence = g_object_new (GSM_TYPE_PRESENCE,
+ NULL);
+
+ return presence;
+}
diff --git a/gnome-session/gsm-presence.h b/gnome-session/gsm-presence.h
new file mode 100644
index 0000000..8646936
--- /dev/null
+++ b/gnome-session/gsm-presence.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2009 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/>.
+ */
+
+#ifndef __GSM_PRESENCE_H__
+#define __GSM_PRESENCE_H__
+
+#include <glib-object.h>
+#include <sys/types.h>
+
+#include "gsm-presence-flag.h"
+
+G_BEGIN_DECLS
+
+#define GSM_TYPE_PRESENCE (gsm_presence_get_type ())
+#define GSM_PRESENCE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_PRESENCE, GsmPresence))
+#define GSM_PRESENCE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_PRESENCE, GsmPresenceClass))
+#define GSM_IS_PRESENCE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_PRESENCE))
+#define GSM_IS_PRESENCE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_PRESENCE))
+#define GSM_PRESENCE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSM_TYPE_PRESENCE, GsmPresenceClass))
+
+typedef struct _GsmPresence GsmPresence;
+typedef struct _GsmPresenceClass GsmPresenceClass;
+
+typedef struct GsmPresencePrivate GsmPresencePrivate;
+
+struct _GsmPresence
+{
+ GObject parent;
+ GsmPresencePrivate *priv;
+};
+
+struct _GsmPresenceClass
+{
+ GObjectClass parent_class;
+
+ void (* status_changed) (GsmPresence *presence,
+ guint status);
+};
+
+typedef enum
+{
+ GSM_PRESENCE_ERROR_GENERAL = 0,
+ GSM_PRESENCE_NUM_ERRORS
+} GsmPresenceError;
+
+#define GSM_PRESENCE_ERROR gsm_presence_error_quark ()
+GQuark gsm_presence_error_quark (void);
+
+GType gsm_presence_get_type (void) G_GNUC_CONST;
+
+GsmPresence * gsm_presence_new (void);
+
+void gsm_presence_set_idle_enabled (GsmPresence *presence,
+ gboolean enabled);
+void gsm_presence_set_idle_timeout (GsmPresence *presence,
+ guint n_seconds);
+
+G_END_DECLS
+
+#endif /* __GSM_PRESENCE_H__ */
diff --git a/gnome-session/gsm-process-helper.c b/gnome-session/gsm-process-helper.c
new file mode 100644
index 0000000..01d581d
--- /dev/null
+++ b/gnome-session/gsm-process-helper.c
@@ -0,0 +1,113 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Novell, 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 <gio/gio.h>
+#include <glib/gi18n.h>
+
+#include "gsm-process-helper.h"
+
+typedef struct {
+ gboolean done;
+ GSubprocess *process;
+ gboolean caught_error;
+ GError **error;
+ GMainContext *maincontext;
+ GSource *timeout_source;
+} GsmProcessHelper;
+
+static void
+on_child_exited (GObject *source,
+ GAsyncResult *result,
+ gpointer data)
+{
+ GsmProcessHelper *helper = data;
+
+ helper->done = TRUE;
+
+ if (!g_subprocess_wait_check_finish ((GSubprocess*)source, result,
+ helper->caught_error ? NULL : helper->error))
+ helper->caught_error = TRUE;
+
+ g_clear_pointer (&helper->timeout_source, g_source_destroy);
+
+ g_main_context_wakeup (helper->maincontext);
+}
+
+static gboolean
+on_child_timeout (gpointer data)
+{
+ GsmProcessHelper *helper = data;
+
+ g_assert (!helper->done);
+
+ g_subprocess_force_exit (helper->process);
+
+ g_set_error_literal (helper->error,
+ G_IO_CHANNEL_ERROR,
+ G_IO_CHANNEL_ERROR_FAILED,
+ "Timed out");
+
+ helper->timeout_source = NULL;
+
+ return FALSE;
+}
+
+gboolean
+gsm_process_helper (const char *command_line,
+ unsigned int timeout,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ GsmProcessHelper helper = { 0, };
+ gchar **argv = NULL;
+ GMainContext *subcontext = NULL;
+
+ if (!g_shell_parse_argv (command_line, NULL, &argv, error))
+ goto out;
+
+ helper.error = error;
+
+ subcontext = g_main_context_new ();
+ g_main_context_push_thread_default (subcontext);
+
+ helper.process = g_subprocess_newv ((const char*const*)argv, 0, error);
+ if (!helper.process)
+ goto out;
+
+ g_subprocess_wait_async (helper.process, NULL, on_child_exited, &helper);
+
+ helper.timeout_source = g_timeout_source_new (timeout);
+
+ g_source_set_callback (helper.timeout_source, on_child_timeout, &helper, NULL);
+ g_source_attach (helper.timeout_source, subcontext);
+
+ while (!helper.done)
+ g_main_context_iteration (subcontext, TRUE);
+
+ ret = helper.caught_error;
+ out:
+ g_strfreev (argv);
+ if (subcontext) {
+ g_main_context_pop_thread_default (subcontext);
+ g_main_context_unref (subcontext);
+ }
+ g_clear_object (&helper.process);
+ return ret;
+}
diff --git a/gnome-session/gsm-process-helper.h b/gnome-session/gsm-process-helper.h
new file mode 100644
index 0000000..6ba6ea4
--- /dev/null
+++ b/gnome-session/gsm-process-helper.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Novell, 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/>.
+ */
+
+#ifndef __GSM_PROCESS_HELPER_H
+#define __GSM_PROCESS_HELPER_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+int gsm_process_helper (const char *command_line,
+ unsigned int timeout,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* __GSM_PROCESS_HELPER_H */
diff --git a/gnome-session/gsm-session-fill.c b/gnome-session/gsm-session-fill.c
new file mode 100644
index 0000000..93a6dbc
--- /dev/null
+++ b/gnome-session/gsm-session-fill.c
@@ -0,0 +1,334 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2006, 2010 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 "gsm-session-fill.h"
+
+#include "gsm-system.h"
+#include "gsm-manager.h"
+#include "gsm-process-helper.h"
+#include "gsm-util.h"
+
+#define GSM_KEYFILE_SESSION_GROUP "GNOME Session"
+#define GSM_KEYFILE_RUNNABLE_KEY "IsRunnableHelper"
+#define GSM_KEYFILE_FALLBACK_KEY "FallbackSession"
+#define GSM_KEYFILE_REQUIRED_COMPONENTS_KEY "RequiredComponents"
+
+/* See https://bugzilla.gnome.org/show_bug.cgi?id=641992 for discussion */
+#define GSM_RUNNABLE_HELPER_TIMEOUT 3000 /* ms */
+
+typedef void (*GsmFillHandleComponent) (const char *component,
+ const char *app_path,
+ gpointer user_data);
+
+static void
+handle_required_components (GKeyFile *keyfile,
+ gboolean look_in_saved_session,
+ GsmFillHandleComponent callback,
+ gpointer user_data)
+{
+ char **required_components;
+ int i;
+
+ g_assert (keyfile != NULL);
+ g_assert (callback != NULL);
+
+ required_components = g_key_file_get_string_list (keyfile,
+ GSM_KEYFILE_SESSION_GROUP,
+ GSM_KEYFILE_REQUIRED_COMPONENTS_KEY,
+ NULL, NULL);
+
+ if (!required_components)
+ return;
+
+ for (i = 0; required_components[i] != NULL; i++) {
+ char *app_path;
+
+ app_path = gsm_util_find_desktop_file_for_app_name (required_components[i],
+ look_in_saved_session, TRUE);
+ callback (required_components[i], app_path, user_data);
+ g_free (app_path);
+ }
+
+ g_strfreev (required_components);
+}
+
+static void
+check_required_components_helper (const char *component,
+ const char *app_path,
+ gpointer user_data)
+{
+ gboolean *error = user_data;
+
+ if (app_path == NULL) {
+ g_warning ("Unable to find required component '%s'", component);
+ *error = TRUE;
+ }
+}
+
+static gboolean
+check_required (GKeyFile *keyfile)
+{
+ gboolean error = FALSE;
+
+ g_debug ("fill: *** Checking required components");
+
+ handle_required_components (keyfile, FALSE,
+ check_required_components_helper, &error);
+
+ g_debug ("fill: *** Done checking required components");
+
+ return !error;
+}
+
+static void
+maybe_load_saved_session_apps (GsmManager *manager)
+{
+ GsmSystem *system;
+ gboolean is_login;
+
+ system = gsm_get_system ();
+ is_login = gsm_system_is_login_session (system);
+ g_object_unref (system);
+
+ if (is_login)
+ return;
+
+ gsm_manager_add_autostart_apps_from_dir (manager, gsm_util_get_saved_session_dir ());
+}
+
+static void
+append_required_components_helper (const char *component,
+ const char *app_path,
+ gpointer user_data)
+{
+ GsmManager *manager = user_data;
+
+ if (app_path == NULL)
+ g_warning ("Unable to find required component '%s'", component);
+ else
+ gsm_manager_add_required_app (manager, app_path);
+}
+
+
+static void
+load_standard_apps (GsmManager *manager,
+ GKeyFile *keyfile)
+{
+ /* Note that saving/restoring sessions is not really possible on systemd, as
+ * XSMP clients cannot be reliably mapped to .desktop files. */
+ g_debug ("fill: *** Adding required components");
+ handle_required_components (keyfile,
+ !gsm_manager_get_failsafe (manager) && !gsm_manager_get_systemd_managed (manager),
+ append_required_components_helper, manager);
+ g_debug ("fill: *** Done adding required components");
+
+ if (!gsm_manager_get_failsafe (manager)) {
+ char **autostart_dirs;
+ int i;
+
+ autostart_dirs = gsm_util_get_autostart_dirs ();
+
+ if (!gsm_manager_get_systemd_managed (manager))
+ maybe_load_saved_session_apps (manager);
+
+ for (i = 0; autostart_dirs[i]; i++) {
+ gsm_manager_add_autostart_apps_from_dir (manager,
+ autostart_dirs[i]);
+ }
+
+ g_strfreev (autostart_dirs);
+ }
+}
+
+static GKeyFile *
+get_session_keyfile_if_valid (const char *path)
+{
+ GKeyFile *keyfile;
+ gsize len;
+ char **list;
+
+ g_debug ("fill: *** Looking if %s is a valid session file", path);
+
+ keyfile = g_key_file_new ();
+
+ if (!g_key_file_load_from_file (keyfile, path, G_KEY_FILE_NONE, NULL)) {
+ g_debug ("Cannot use session '%s': non-existing or invalid file.", path);
+ goto error;
+ }
+
+ if (!g_key_file_has_group (keyfile, GSM_KEYFILE_SESSION_GROUP)) {
+ g_warning ("Cannot use session '%s': no '%s' group.", path, GSM_KEYFILE_SESSION_GROUP);
+ goto error;
+ }
+
+ list = g_key_file_get_string_list (keyfile,
+ GSM_KEYFILE_SESSION_GROUP,
+ GSM_KEYFILE_REQUIRED_COMPONENTS_KEY,
+ &len, NULL);
+ if (list)
+ g_strfreev (list);
+ if (len == 0)
+ g_warning ("Session '%s': no component in the session.", path);
+
+ return keyfile;
+
+error:
+ g_key_file_free (keyfile);
+ return NULL;
+}
+
+/**
+ * find_valid_session_keyfile:
+ * @session: name of session
+ *
+ * We look for the session file in XDG_CONFIG_HOME, XDG_CONFIG_DIRS and
+ * XDG_DATA_DIRS. This enables users and sysadmins to override a specific
+ * session that is shipped in XDG_DATA_DIRS.
+ */
+static GKeyFile *
+find_valid_session_keyfile (const char *session)
+{
+ GPtrArray *dirs;
+ const char * const *system_config_dirs;
+ const char * const *system_data_dirs;
+ int i;
+ GKeyFile *keyfile;
+ char *basename;
+
+ dirs = g_ptr_array_new ();
+
+ g_ptr_array_add (dirs, (gpointer) g_get_user_config_dir ());
+
+ system_config_dirs = g_get_system_config_dirs ();
+ for (i = 0; system_config_dirs[i]; i++)
+ g_ptr_array_add (dirs, (gpointer) system_config_dirs[i]);
+
+ system_data_dirs = g_get_system_data_dirs ();
+ for (i = 0; system_data_dirs[i]; i++)
+ g_ptr_array_add (dirs, (gpointer) system_data_dirs[i]);
+
+ keyfile = NULL;
+ basename = g_strdup_printf ("%s.session", session);
+
+ for (i = 0; i < dirs->len; i++) {
+ g_autofree gchar *path = g_build_filename (dirs->pdata[i], "gnome-session", "sessions", basename, NULL);
+ keyfile = get_session_keyfile_if_valid (path);
+ if (keyfile != NULL)
+ break;
+ }
+
+ if (dirs)
+ g_ptr_array_free (dirs, TRUE);
+ if (basename)
+ g_free (basename);
+
+ return keyfile;
+}
+
+static GKeyFile *
+get_session_keyfile (const char *session,
+ char **actual_session,
+ gboolean *is_fallback)
+{
+ GKeyFile *keyfile;
+ gboolean session_runnable;
+ char *value;
+ GError *error = NULL;
+
+ *actual_session = NULL;
+
+ g_debug ("fill: *** Getting session '%s'", session);
+
+ keyfile = find_valid_session_keyfile (session);
+
+ if (!keyfile)
+ return NULL;
+
+ session_runnable = TRUE;
+
+ value = g_key_file_get_string (keyfile,
+ GSM_KEYFILE_SESSION_GROUP, GSM_KEYFILE_RUNNABLE_KEY,
+ NULL);
+ if (!IS_STRING_EMPTY (value)) {
+ g_debug ("fill: *** Launching helper '%s' to know if session is runnable", value);
+ session_runnable = gsm_process_helper (value, GSM_RUNNABLE_HELPER_TIMEOUT, &error);
+ if (!session_runnable) {
+ g_warning ("Session '%s' runnable check failed: %s", session,
+ error->message);
+ g_clear_error (&error);
+ }
+ }
+ g_free (value);
+
+ if (session_runnable) {
+ session_runnable = check_required (keyfile);
+ }
+
+ if (session_runnable) {
+ *actual_session = g_strdup (session);
+ if (is_fallback)
+ *is_fallback = FALSE;
+ return keyfile;
+ }
+
+ g_debug ("fill: *** Session is not runnable");
+
+ /* We can't run this session, so try to use the fallback */
+ value = g_key_file_get_string (keyfile,
+ GSM_KEYFILE_SESSION_GROUP, GSM_KEYFILE_FALLBACK_KEY,
+ NULL);
+
+ g_key_file_free (keyfile);
+ keyfile = NULL;
+
+ if (!IS_STRING_EMPTY (value)) {
+ if (is_fallback)
+ *is_fallback = TRUE;
+ keyfile = get_session_keyfile (value, actual_session, NULL);
+ }
+ g_free (value);
+
+ return keyfile;
+}
+
+gboolean
+gsm_session_fill (GsmManager *manager,
+ const char *session)
+{
+ GKeyFile *keyfile;
+ gboolean is_fallback;
+ char *actual_session;
+
+ keyfile = get_session_keyfile (session, &actual_session, &is_fallback);
+
+ if (!keyfile)
+ return FALSE;
+
+ _gsm_manager_set_active_session (manager, actual_session, is_fallback);
+
+ g_free (actual_session);
+
+ load_standard_apps (manager, keyfile);
+
+ g_key_file_free (keyfile);
+
+ return TRUE;
+}
diff --git a/gnome-session/gsm-session-fill.h b/gnome-session/gsm-session-fill.h
new file mode 100644
index 0000000..470a441
--- /dev/null
+++ b/gnome-session/gsm-session-fill.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2006, 2010 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/>.
+ */
+
+#ifndef __GSM_SESSION_FILL_H
+#define __GSM_SESSION_FILL_H
+
+#include "gsm-manager.h"
+
+G_BEGIN_DECLS
+
+gboolean gsm_session_fill (GsmManager *manager,
+ const char *session);
+
+G_END_DECLS
+
+#endif /* __GSM_SESSION_FILL_H */
diff --git a/gnome-session/gsm-session-save.c b/gnome-session/gsm-session-save.c
new file mode 100644
index 0000000..e7f3653
--- /dev/null
+++ b/gnome-session/gsm-session-save.c
@@ -0,0 +1,293 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ * gsm-session-save.c
+ * Copyright (C) 2008 Lucas Rocha.
+ *
+ * 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 <glib.h>
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+
+#include "gsm-app.h"
+#include "gsm-util.h"
+#include "gsm-autostart-app.h"
+#include "gsm-client.h"
+
+#include "gsm-session-save.h"
+
+#define GSM_MANAGER_SCHEMA "org.gnome.SessionManager"
+#define KEY_AUTOSAVE_ONE_SHOT "auto-save-session-one-shot"
+
+
+static gboolean gsm_session_clear_saved_session (const char *directory,
+ GHashTable *discard_hash);
+
+typedef struct {
+ const char *dir;
+ GHashTable *discard_hash;
+ GsmStore *app_store;
+ GError **error;
+} SessionSaveData;
+
+static gboolean
+_app_has_app_id (const char *id,
+ GsmApp *app,
+ const char *app_id_a)
+{
+ const char *app_id_b;
+
+ app_id_b = gsm_app_peek_app_id (app);
+ return g_strcmp0 (app_id_a, app_id_b) == 0;
+}
+
+static gboolean
+save_one_client (char *id,
+ GObject *object,
+ SessionSaveData *data)
+{
+ GsmClient *client;
+ GKeyFile *keyfile;
+ GsmApp *app = NULL;
+ const char *app_id;
+ char *path = NULL;
+ char *filename = NULL;
+ char *contents = NULL;
+ gsize length = 0;
+ char *discard_exec;
+ g_autoptr(GError) local_error = NULL;
+
+ client = GSM_CLIENT (object);
+
+ app_id = gsm_client_peek_app_id (client);
+ if (!IS_STRING_EMPTY (app_id)) {
+ if (g_str_has_suffix (app_id, ".desktop"))
+ filename = g_strdup (app_id);
+ else
+ filename = g_strdup_printf ("%s.desktop", app_id);
+
+ path = g_build_filename (data->dir, filename, NULL);
+
+ app = (GsmApp *)gsm_store_find (data->app_store,
+ (GsmStoreFunc)_app_has_app_id,
+ (char *)app_id);
+ }
+ keyfile = gsm_client_save (client, app, &local_error);
+
+ if (keyfile == NULL || local_error) {
+ goto out;
+ }
+
+ contents = g_key_file_to_data (keyfile, &length, &local_error);
+
+ if (local_error) {
+ goto out;
+ }
+
+ if (!path || g_file_test (path, G_FILE_TEST_EXISTS)) {
+ if (filename)
+ g_free (filename);
+ if (path)
+ g_free (path);
+
+ filename = g_strdup_printf ("%s.desktop",
+ gsm_client_peek_startup_id (client));
+ path = g_build_filename (data->dir, filename, NULL);
+ }
+
+ g_file_set_contents (path,
+ contents,
+ length,
+ &local_error);
+
+ if (local_error) {
+ goto out;
+ }
+
+ discard_exec = g_key_file_get_string (keyfile,
+ G_KEY_FILE_DESKTOP_GROUP,
+ GSM_AUTOSTART_APP_DISCARD_KEY,
+ NULL);
+ if (discard_exec) {
+ g_hash_table_insert (data->discard_hash,
+ discard_exec, discard_exec);
+ }
+
+ g_debug ("GsmSessionSave: saved client %s to %s", id, filename);
+
+out:
+ if (keyfile != NULL) {
+ g_key_file_free (keyfile);
+ }
+
+ g_free (contents);
+ g_free (filename);
+ g_free (path);
+
+ /* in case of any error, stop saving session */
+ if (local_error) {
+ g_propagate_error (data->error, g_steal_pointer (&local_error));
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void
+gsm_session_save (GsmStore *client_store,
+ GsmStore *app_store,
+ GError **error)
+{
+ GSettings *settings;
+ const char *save_dir;
+ SessionSaveData data;
+
+ g_debug ("GsmSessionSave: Saving session");
+
+ /* Clear one shot key autosave in the event its set (so that it's actually
+ * one shot only)
+ */
+ settings = g_settings_new (GSM_MANAGER_SCHEMA);
+ g_settings_set_boolean (settings, KEY_AUTOSAVE_ONE_SHOT, FALSE);
+ g_object_unref (settings);
+
+ save_dir = gsm_util_get_saved_session_dir ();
+ if (save_dir == NULL) {
+ g_warning ("GsmSessionSave: cannot create saved session directory");
+ return;
+ }
+
+ data.dir = save_dir;
+ data.discard_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, NULL);
+ data.app_store = app_store;
+
+ /* remove old saved session */
+ gsm_session_clear_saved_session (save_dir, data.discard_hash);
+ data.error = error;
+
+ gsm_store_foreach (client_store,
+ (GsmStoreFunc) save_one_client,
+ &data);
+
+ g_hash_table_destroy (data.discard_hash);
+}
+
+static gboolean
+gsm_session_clear_one_client (const char *filename,
+ GHashTable *discard_hash)
+{
+ gboolean result = TRUE;
+ GKeyFile *key_file;
+ char *discard_exec = NULL;
+ char **envp;
+
+ g_debug ("GsmSessionSave: removing '%s' from saved session", filename);
+
+ envp = (char **) gsm_util_listenv ();
+ key_file = g_key_file_new ();
+ if (g_key_file_load_from_file (key_file, filename,
+ G_KEY_FILE_NONE, NULL)) {
+ char **argv;
+ int argc;
+
+ discard_exec = g_key_file_get_string (key_file,
+ G_KEY_FILE_DESKTOP_GROUP,
+ GSM_AUTOSTART_APP_DISCARD_KEY,
+ NULL);
+ if (!discard_exec)
+ goto out;
+
+ if (discard_hash && g_hash_table_lookup (discard_hash, discard_exec))
+ goto out;
+
+ if (!g_shell_parse_argv (discard_exec, &argc, &argv, NULL))
+ goto out;
+
+ result = g_spawn_async (NULL, argv, envp, G_SPAWN_SEARCH_PATH,
+ NULL, NULL, NULL, NULL) && result;
+
+ g_strfreev (argv);
+ } else {
+ result = FALSE;
+ }
+
+out:
+ if (key_file)
+ g_key_file_free (key_file);
+ if (discard_exec)
+ g_free (discard_exec);
+
+ result = (g_unlink (filename) == 0) && result;
+
+ return result;
+}
+
+static gboolean
+gsm_session_clear_saved_session (const char *directory,
+ GHashTable *discard_hash)
+{
+ GDir *dir;
+ const char *filename;
+ gboolean result = TRUE;
+ GError *error;
+
+ g_debug ("GsmSessionSave: clearing currently saved session at %s",
+ directory);
+
+ if (directory == NULL) {
+ return FALSE;
+ }
+
+ error = NULL;
+ dir = g_dir_open (directory, 0, &error);
+ if (error) {
+ g_warning ("GsmSessionSave: error loading saved session directory: %s", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ while ((filename = g_dir_read_name (dir))) {
+ char *path = g_build_filename (directory,
+ filename, NULL);
+
+ result = gsm_session_clear_one_client (path, discard_hash)
+ && result;
+
+ g_free (path);
+ }
+
+ g_dir_close (dir);
+
+ return result;
+}
+
+void
+gsm_session_save_clear (void)
+{
+ const char *save_dir;
+
+ g_debug ("GsmSessionSave: Clearing saved session");
+
+ save_dir = gsm_util_get_saved_session_dir ();
+ if (save_dir == NULL) {
+ g_warning ("GsmSessionSave: cannot create saved session directory");
+ return;
+ }
+
+ gsm_session_clear_saved_session (save_dir, NULL);
+}
diff --git a/gnome-session/gsm-session-save.h b/gnome-session/gsm-session-save.h
new file mode 100644
index 0000000..f55c9c1
--- /dev/null
+++ b/gnome-session/gsm-session-save.h
@@ -0,0 +1,34 @@
+/* gsm-session-save.h
+ * Copyright (C) 2008 Lucas Rocha.
+ *
+ * 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/>.
+ */
+
+#ifndef __GSM_SESSION_SAVE_H__
+#define __GSM_SESSION_SAVE_H__
+
+#include <glib.h>
+
+#include "gsm-store.h"
+
+G_BEGIN_DECLS
+
+void gsm_session_save (GsmStore *client_store,
+ GsmStore *app_store,
+ GError **error);
+void gsm_session_save_clear (void);
+
+G_END_DECLS
+
+#endif /* __GSM_SESSION_SAVE_H__ */
diff --git a/gnome-session/gsm-shell-extensions.c b/gnome-session/gsm-shell-extensions.c
new file mode 100644
index 0000000..73d898f
--- /dev/null
+++ b/gnome-session/gsm-shell-extensions.c
@@ -0,0 +1,199 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2011 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, 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 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/>.
+ *
+ * Authors:
+ * Jasper St. Pierre <jstpierre@mecheye.net>
+ */
+
+#include <config.h>
+
+#include <glib.h>
+#include <gio/gio.h>
+#include <json-glib/json-glib.h>
+
+#include "gsm-shell-extensions.h"
+
+#define SHELL_SCHEMA "org.gnome.shell"
+#define DISABLE_EXTENSIONS_KEY "disable-user-extensions"
+
+#define SHELL_EXTENSIONS_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSM_TYPE_SHELL_EXTENSIONS, GsmShellExtensionsPrivate))
+
+struct _GsmShellExtensionsPrivate
+{
+ GSettings *settings;
+ guint num_extensions;
+};
+
+G_DEFINE_TYPE (GsmShellExtensions, gsm_shell_extensions, G_TYPE_OBJECT);
+
+/**
+ * gsm_shell_extensions_finalize:
+ * @object: (in): A #GsmShellExtensions.
+ *
+ * Finalizer for a #GsmShellExtensions instance. Frees any resources held by
+ * the instance.
+ */
+static void
+gsm_shell_extensions_finalize (GObject *object)
+{
+ GsmShellExtensions *extensions = GSM_SHELL_EXTENSIONS (object);
+ GsmShellExtensionsPrivate *priv = extensions->priv;
+
+ g_clear_object (&priv->settings);
+
+ G_OBJECT_CLASS (gsm_shell_extensions_parent_class)->finalize (object);
+}
+
+/**
+ * gsm_shell_extensions_class_init:
+ * @klass: (in): A #GsmShellExtensionsClass.
+ *
+ * Initializes the #GsmShellExtensionsClass and prepares the vtable.
+ */
+static void
+gsm_shell_extensions_class_init (GsmShellExtensionsClass *klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = gsm_shell_extensions_finalize;
+ g_type_class_add_private (object_class, sizeof (GsmShellExtensionsPrivate));
+}
+
+static void
+gsm_shell_extensions_scan_dir (GsmShellExtensions *self,
+ GFile *dir)
+{
+ GFileEnumerator *enumerator;
+ GFileInfo *info;
+ JsonParser *metadata_parser;
+
+ metadata_parser = json_parser_new ();
+
+ enumerator = g_file_enumerate_children (dir,
+ "standard::*",
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ NULL);
+
+ if (enumerator == NULL)
+ return;
+
+ while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)) != NULL)
+ {
+ gchar *metadata_filename;
+ const gchar *metadata_uuid;
+ gchar *dir_uuid;
+ JsonObject *metadata_root;
+
+ dir_uuid = (char *) g_file_info_get_name (info);
+
+ metadata_filename = g_build_filename (g_file_get_path (dir),
+ dir_uuid,
+ "metadata.json",
+ NULL);
+
+ if (!json_parser_load_from_file (metadata_parser, metadata_filename, NULL))
+ continue;
+
+ g_free (metadata_filename);
+
+ metadata_root = json_node_get_object (json_parser_get_root (metadata_parser));
+
+ metadata_uuid = json_object_get_string_member (metadata_root, "uuid");
+ if (!g_str_equal (metadata_uuid, dir_uuid))
+ {
+ g_warning ("Extension with dirname '%s' does not match metadata's UUID of '%s'. Skipping.",
+ dir_uuid, metadata_uuid);
+ continue;
+ }
+
+ self->priv->num_extensions++;
+ }
+}
+
+static void
+gsm_shell_extensions_scan (GsmShellExtensions *self)
+{
+ gchar *dirname;
+ GFile *dir;
+ const gchar * const * system_data_dirs;
+ int i;
+
+ /* User data dir first. */
+ dirname = g_build_filename (g_get_user_data_dir (), "gnome-shell", "extensions", NULL);
+ dir = g_file_new_for_path (dirname);
+ g_free (dirname);
+
+ gsm_shell_extensions_scan_dir (self, dir);
+ g_object_unref (dir);
+
+ system_data_dirs = g_get_system_data_dirs ();
+ for (i = 0; system_data_dirs[i]; i++)
+ {
+ dirname = g_build_filename (system_data_dirs[i], "gnome-shell", "extensions", NULL);
+ dir = g_file_new_for_path (dirname);
+ g_free (dirname);
+
+ gsm_shell_extensions_scan_dir (self, dir);
+ g_object_unref (dir);
+ }
+}
+
+/**
+ * gsm_shell_extensions_init:
+ * @self: (in): A #GsmShellExtensions.
+ *
+ * Initializes the newly created #GsmShellExtensions instance.
+ */
+static void
+gsm_shell_extensions_init (GsmShellExtensions *self)
+{
+ GSettingsSchemaSource *source;
+ GSettingsSchema *schema;
+
+ self->priv = SHELL_EXTENSIONS_PRIVATE (self);
+
+ source = g_settings_schema_source_get_default ();
+ schema = g_settings_schema_source_lookup (source, SHELL_SCHEMA, TRUE);
+
+ if (schema != NULL)
+ {
+ self->priv->settings = g_settings_new_full (schema, NULL, NULL);
+ g_settings_schema_unref (schema);
+ }
+
+ if (self->priv->settings != NULL)
+ gsm_shell_extensions_scan (self);
+}
+
+gboolean
+gsm_shell_extensions_disable_all (GsmShellExtensions *self)
+{
+ return g_settings_set_boolean (self->priv->settings,
+ DISABLE_EXTENSIONS_KEY,
+ TRUE);
+}
+
+guint
+gsm_shell_extensions_n_extensions (GsmShellExtensions *self)
+{
+ if (self->priv->settings == NULL)
+ return 0;
+
+ return self->priv->num_extensions;
+}
diff --git a/gnome-session/gsm-shell-extensions.h b/gnome-session/gsm-shell-extensions.h
new file mode 100644
index 0000000..a3b3d06
--- /dev/null
+++ b/gnome-session/gsm-shell-extensions.h
@@ -0,0 +1,61 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2011 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, 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 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/>.
+ *
+ * Authors:
+ * Jasper St. Pierre <jstpierre@mecheye.net>
+ */
+
+#ifndef __GSM_SHELL_EXTENSIONS_H
+#define __GSM_SHELL_EXTENSIONS_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GSM_TYPE_SHELL_EXTENSIONS (gsm_shell_extensions_get_type ())
+#define GSM_SHELL_EXTENSIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_SHELL_EXTENSIONS, GsmShellExtensions))
+#define GSM_SHELL_EXTENSIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_SHELL_EXTENSIONS, GsmShellExtensionsClass))
+#define GSM_IS_SHELL_EXTENSIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_SHELL_EXTENSIONS))
+#define GSM_IS_SHELL_EXTENSIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_SHELL_EXTENSIONS))
+#define GSM_SHELL_EXTENSIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSM_TYPE_SHELL_EXTENSIONS, GsmShellExtensionsClass))
+
+typedef struct _GsmShellExtensions GsmShellExtensions;
+typedef struct _GsmShellExtensionsClass GsmShellExtensionsClass;
+typedef struct _GsmShellExtensionsPrivate GsmShellExtensionsPrivate;
+
+struct _GsmShellExtensions
+{
+ GObject parent;
+
+ /*< private >*/
+ GsmShellExtensionsPrivate *priv;
+};
+
+struct _GsmShellExtensionsClass
+{
+ GObjectClass parent_class;
+};
+
+GType gsm_shell_extensions_get_type (void) G_GNUC_CONST;
+
+gboolean gsm_shell_extensions_disable_all (GsmShellExtensions *self);
+
+guint gsm_shell_extensions_n_extensions (GsmShellExtensions *self);
+
+G_END_DECLS
+
+#endif /* __GSM_SHELL_EXTENSIONS_H */
diff --git a/gnome-session/gsm-shell.c b/gnome-session/gsm-shell.c
new file mode 100644
index 0000000..04cfa2f
--- /dev/null
+++ b/gnome-session/gsm-shell.c
@@ -0,0 +1,507 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 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, 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 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 <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib-object.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+
+#include "gsm-inhibitor.h"
+#include "gsm-shell.h"
+
+#define SHELL_NAME "org.gnome.Shell"
+#define SHELL_PATH "/org/gnome/Shell"
+#define SHELL_INTERFACE "org.gnome.Shell"
+
+#define SHELL_END_SESSION_DIALOG_PATH "/org/gnome/SessionManager/EndSessionDialog"
+#define SHELL_END_SESSION_DIALOG_INTERFACE "org.gnome.SessionManager.EndSessionDialog"
+
+#define AUTOMATIC_ACTION_TIMEOUT 60
+
+#define GSM_SHELL_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSM_TYPE_SHELL, GsmShellPrivate))
+
+struct _GsmShellPrivate
+{
+ GDBusProxy *end_session_dialog_proxy;
+ GsmStore *inhibitors;
+
+ guint32 is_running : 1;
+
+ gboolean dialog_is_open;
+ GsmShellEndSessionDialogType end_session_dialog_type;
+
+ guint update_idle_id;
+ guint watch_id;
+};
+
+enum {
+ PROP_0,
+ PROP_IS_RUNNING
+};
+
+enum {
+ END_SESSION_DIALOG_OPENED = 0,
+ END_SESSION_DIALOG_OPEN_FAILED,
+ END_SESSION_DIALOG_CLOSED,
+ END_SESSION_DIALOG_CANCELED,
+ END_SESSION_DIALOG_CONFIRMED_LOGOUT,
+ END_SESSION_DIALOG_CONFIRMED_SHUTDOWN,
+ END_SESSION_DIALOG_CONFIRMED_REBOOT,
+ NUMBER_OF_SIGNALS
+};
+
+static guint signals[NUMBER_OF_SIGNALS] = { 0 };
+
+static void gsm_shell_class_init (GsmShellClass *klass);
+static void gsm_shell_init (GsmShell *ck);
+static void gsm_shell_finalize (GObject *object);
+
+static void queue_end_session_dialog_update (GsmShell *shell);
+
+G_DEFINE_TYPE (GsmShell, gsm_shell, G_TYPE_OBJECT);
+
+static void
+gsm_shell_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GsmShell *shell = GSM_SHELL (object);
+
+ switch (prop_id) {
+ case PROP_IS_RUNNING:
+ g_value_set_boolean (value,
+ shell->priv->is_running);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object,
+ prop_id,
+ pspec);
+ }
+}
+
+static void
+gsm_shell_class_init (GsmShellClass *shell_class)
+{
+ GObjectClass *object_class;
+ GParamSpec *param_spec;
+
+ object_class = G_OBJECT_CLASS (shell_class);
+
+ object_class->finalize = gsm_shell_finalize;
+ object_class->get_property = gsm_shell_get_property;
+
+ param_spec = g_param_spec_boolean ("is-running",
+ "Is running",
+ "Whether GNOME Shell is running in the session",
+ FALSE,
+ G_PARAM_READABLE);
+
+ g_object_class_install_property (object_class, PROP_IS_RUNNING,
+ param_spec);
+
+ signals [END_SESSION_DIALOG_OPENED] =
+ g_signal_new ("end-session-dialog-opened",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmShellClass, end_session_dialog_opened),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ signals [END_SESSION_DIALOG_OPEN_FAILED] =
+ g_signal_new ("end-session-dialog-open-failed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmShellClass, end_session_dialog_open_failed),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ signals [END_SESSION_DIALOG_CLOSED] =
+ g_signal_new ("end-session-dialog-closed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmShellClass, end_session_dialog_closed),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ signals [END_SESSION_DIALOG_CANCELED] =
+ g_signal_new ("end-session-dialog-canceled",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmShellClass, end_session_dialog_canceled),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ signals [END_SESSION_DIALOG_CONFIRMED_LOGOUT] =
+ g_signal_new ("end-session-dialog-confirmed-logout",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmShellClass, end_session_dialog_confirmed_logout),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ signals [END_SESSION_DIALOG_CONFIRMED_SHUTDOWN] =
+ g_signal_new ("end-session-dialog-confirmed-shutdown",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmShellClass, end_session_dialog_confirmed_shutdown),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ signals [END_SESSION_DIALOG_CONFIRMED_REBOOT] =
+ g_signal_new ("end-session-dialog-confirmed-reboot",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmShellClass, end_session_dialog_confirmed_reboot),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ g_type_class_add_private (shell_class, sizeof (GsmShellPrivate));
+}
+
+static void
+on_shell_name_vanished (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ GsmShell *shell = user_data;
+ shell->priv->is_running = FALSE;
+}
+
+static void
+on_shell_name_appeared (GDBusConnection *connection,
+ const gchar *name,
+ const gchar *name_owner,
+ gpointer user_data)
+{
+ GsmShell *shell = user_data;
+ shell->priv->is_running = TRUE;
+}
+
+static void
+gsm_shell_ensure_connection (GsmShell *shell)
+{
+ if (shell->priv->watch_id != 0) {
+ return;
+ }
+
+ shell->priv->watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION,
+ SHELL_NAME,
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ on_shell_name_appeared,
+ on_shell_name_vanished,
+ shell, NULL);
+}
+
+static void
+gsm_shell_init (GsmShell *shell)
+{
+ shell->priv = GSM_SHELL_GET_PRIVATE (shell);
+
+ gsm_shell_ensure_connection (shell);
+}
+
+static void
+gsm_shell_finalize (GObject *object)
+{
+ GsmShell *shell;
+ GObjectClass *parent_class;
+
+ shell = GSM_SHELL (object);
+
+ parent_class = G_OBJECT_CLASS (gsm_shell_parent_class);
+
+ g_object_unref (shell->priv->inhibitors);
+
+ if (shell->priv->watch_id != 0) {
+ g_bus_unwatch_name (shell->priv->watch_id);
+ shell->priv->watch_id = 0;
+ }
+
+ if (parent_class->finalize != NULL) {
+ parent_class->finalize (object);
+ }
+}
+
+GsmShell *
+gsm_shell_new (void)
+{
+ GsmShell *shell;
+
+ shell = g_object_new (GSM_TYPE_SHELL, NULL);
+
+ return shell;
+}
+
+GsmShell *
+gsm_get_shell (void)
+{
+ static GsmShell *shell = NULL;
+
+ if (shell == NULL) {
+ shell = gsm_shell_new ();
+ }
+
+ return g_object_ref (shell);
+}
+
+gboolean
+gsm_shell_is_running (GsmShell *shell)
+{
+ gsm_shell_ensure_connection (shell);
+
+ return shell->priv->is_running;
+}
+
+static gboolean
+add_inhibitor_to_array (const char *id,
+ GsmInhibitor *inhibitor,
+ GVariantBuilder *builder)
+{
+ g_variant_builder_add (builder, "o", gsm_inhibitor_peek_id (inhibitor));
+ return FALSE;
+}
+
+static GVariant *
+get_array_from_store (GsmStore *inhibitors)
+{
+ GVariantBuilder builder;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("ao"));
+ gsm_store_foreach (inhibitors,
+ (GsmStoreFunc) add_inhibitor_to_array,
+ &builder);
+
+ return g_variant_builder_end (&builder);
+}
+
+static void
+on_open_finished (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GsmShell *shell = user_data;
+ GError *error;
+
+ if (shell->priv->update_idle_id != 0) {
+ g_source_remove (shell->priv->update_idle_id);
+ shell->priv->update_idle_id = 0;
+ }
+
+ shell->priv->dialog_is_open = FALSE;
+
+ error = NULL;
+ g_dbus_proxy_call_finish (G_DBUS_PROXY (source), result, &error);
+
+ if (error != NULL) {
+ g_warning ("Unable to open shell end session dialog: %s", error->message);
+ g_error_free (error);
+
+ g_signal_emit (G_OBJECT (shell), signals[END_SESSION_DIALOG_OPEN_FAILED], 0);
+ return;
+ }
+
+ g_signal_emit (G_OBJECT (shell), signals[END_SESSION_DIALOG_OPENED], 0);
+}
+
+static void
+on_end_session_dialog_dbus_signal (GDBusProxy *proxy,
+ gchar *sender_name,
+ gchar *signal_name,
+ GVariant *parameters,
+ GsmShell *shell)
+{
+ struct {
+ const char *name;
+ int index;
+ } signal_map[] = {
+ { "Closed", END_SESSION_DIALOG_CLOSED },
+ { "Canceled", END_SESSION_DIALOG_CANCELED },
+ { "ConfirmedLogout", END_SESSION_DIALOG_CONFIRMED_LOGOUT },
+ { "ConfirmedReboot", END_SESSION_DIALOG_CONFIRMED_REBOOT },
+ { "ConfirmedShutdown", END_SESSION_DIALOG_CONFIRMED_SHUTDOWN },
+ { NULL, -1 }
+ };
+ int signal_index = -1;
+ int i;
+
+ for (i = 0; signal_map[i].name != NULL; i++) {
+ if (g_strcmp0 (signal_map[i].name, signal_name) == 0) {
+ signal_index = signal_map[i].index;
+ break;
+ }
+ }
+
+ if (signal_index == -1)
+ return;
+
+ shell->priv->dialog_is_open = FALSE;
+
+ if (shell->priv->update_idle_id != 0) {
+ g_source_remove (shell->priv->update_idle_id);
+ shell->priv->update_idle_id = 0;
+ }
+
+ g_signal_handlers_disconnect_by_func (shell->priv->inhibitors,
+ G_CALLBACK (queue_end_session_dialog_update),
+ shell);
+
+ g_signal_emit (G_OBJECT (shell), signals[signal_index], 0);
+}
+
+static void
+on_end_session_dialog_name_owner_changed (GDBusProxy *proxy,
+ GParamSpec *pspec,
+ GsmShell *shell)
+{
+ gchar *name_owner;
+
+ name_owner = g_dbus_proxy_get_name_owner (proxy);
+ if (name_owner == NULL) {
+ g_clear_object (&shell->priv->end_session_dialog_proxy);
+ }
+
+ g_free (name_owner);
+}
+
+static gboolean
+on_need_end_session_dialog_update (GsmShell *shell)
+{
+ /* No longer need an update */
+ if (shell->priv->update_idle_id == 0)
+ return FALSE;
+
+ shell->priv->update_idle_id = 0;
+
+ gsm_shell_open_end_session_dialog (shell,
+ shell->priv->end_session_dialog_type,
+ shell->priv->inhibitors);
+ return FALSE;
+}
+
+static void
+queue_end_session_dialog_update (GsmShell *shell)
+{
+ if (shell->priv->update_idle_id != 0)
+ return;
+
+ shell->priv->update_idle_id = g_idle_add ((GSourceFunc) on_need_end_session_dialog_update,
+ shell);
+}
+
+gboolean
+gsm_shell_open_end_session_dialog (GsmShell *shell,
+ GsmShellEndSessionDialogType type,
+ GsmStore *inhibitors)
+{
+ GDBusProxy *proxy;
+ GError *error;
+
+ error = NULL;
+
+ if (shell->priv->dialog_is_open) {
+ g_return_val_if_fail (shell->priv->end_session_dialog_type == type,
+ FALSE);
+
+ return TRUE;
+ }
+
+ if (shell->priv->end_session_dialog_proxy == NULL) {
+ proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ SHELL_NAME,
+ SHELL_END_SESSION_DIALOG_PATH,
+ SHELL_END_SESSION_DIALOG_INTERFACE,
+ NULL, &error);
+
+ if (error != NULL) {
+ g_critical ("Could not connect to the shell: %s",
+ error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ shell->priv->end_session_dialog_proxy = proxy;
+
+ g_signal_connect (proxy, "notify::g-name-owner",
+ G_CALLBACK (on_end_session_dialog_name_owner_changed),
+ shell);
+ g_signal_connect (proxy, "g-signal",
+ G_CALLBACK (on_end_session_dialog_dbus_signal),
+ shell);
+ }
+
+ g_dbus_proxy_call (shell->priv->end_session_dialog_proxy,
+ "Open",
+ g_variant_new ("(uuu@ao)",
+ type,
+ 0,
+ AUTOMATIC_ACTION_TIMEOUT,
+ get_array_from_store (inhibitors)),
+ G_DBUS_CALL_FLAGS_NONE,
+ G_MAXINT, NULL,
+ on_open_finished, shell);
+
+ g_object_ref (inhibitors);
+
+ if (shell->priv->inhibitors != NULL) {
+ g_signal_handlers_disconnect_by_func (shell->priv->inhibitors,
+ G_CALLBACK (queue_end_session_dialog_update),
+ shell);
+ g_object_unref (shell->priv->inhibitors);
+ }
+
+ shell->priv->inhibitors = inhibitors;
+
+ g_signal_connect_swapped (inhibitors, "added",
+ G_CALLBACK (queue_end_session_dialog_update),
+ shell);
+
+ g_signal_connect_swapped (inhibitors, "removed",
+ G_CALLBACK (queue_end_session_dialog_update),
+ shell);
+
+ shell->priv->dialog_is_open = TRUE;
+ shell->priv->end_session_dialog_type = type;
+
+ return TRUE;
+}
+
+void
+gsm_shell_close_end_session_dialog (GsmShell *shell)
+{
+ if (!shell->priv->end_session_dialog_proxy)
+ return;
+
+ shell->priv->dialog_is_open = FALSE;
+
+ g_dbus_proxy_call (shell->priv->end_session_dialog_proxy,
+ "Close",
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, NULL, NULL);
+}
diff --git a/gnome-session/gsm-shell.h b/gnome-session/gsm-shell.h
new file mode 100644
index 0000000..e236493
--- /dev/null
+++ b/gnome-session/gsm-shell.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 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
+ * 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/>.
+ *
+ * Authors:
+ * Ray Strode <rstrode@redhat.com>
+ */
+
+#ifndef __GSM_SHELL_H__
+#define __GSM_SHELL_H__
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "gsm-store.h"
+
+G_BEGIN_DECLS
+
+#define GSM_TYPE_SHELL (gsm_shell_get_type ())
+#define GSM_SHELL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_SHELL, GsmShell))
+#define GSM_SHELL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_SHELL, GsmShellClass))
+#define GSM_IS_SHELL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_SHELL))
+#define GSM_IS_SHELL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_SHELL))
+#define GSM_SHELL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GSM_TYPE_SHELL, GsmShellClass))
+#define GSM_SHELL_ERROR (gsm_shell_error_quark ())
+
+typedef struct _GsmShell GsmShell;
+typedef struct _GsmShellClass GsmShellClass;
+typedef struct _GsmShellPrivate GsmShellPrivate;
+
+typedef enum
+{
+ GSM_SHELL_END_SESSION_DIALOG_TYPE_LOGOUT = 0,
+ GSM_SHELL_END_SESSION_DIALOG_TYPE_SHUTDOWN,
+ GSM_SHELL_END_SESSION_DIALOG_TYPE_RESTART,
+} GsmShellEndSessionDialogType;
+
+struct _GsmShell
+{
+ GObject parent;
+
+ GsmShellPrivate *priv;
+};
+
+struct _GsmShellClass
+{
+ GObjectClass parent_class;
+
+ void (* end_session_dialog_opened) (GsmShell *shell);
+ void (* end_session_dialog_open_failed) (GsmShell *shell);
+ void (* end_session_dialog_closed) (GsmShell *shell);
+ void (* end_session_dialog_canceled) (GsmShell *shell);
+
+ void (* end_session_dialog_confirmed_logout) (GsmShell *shell);
+ void (* end_session_dialog_confirmed_shutdown) (GsmShell *shell);
+ void (* end_session_dialog_confirmed_reboot) (GsmShell *shell);
+
+};
+
+GType gsm_shell_get_type (void);
+
+GsmShell *gsm_shell_new (void);
+
+GsmShell *gsm_get_shell (void);
+gboolean gsm_shell_is_running (GsmShell *shell);
+
+gboolean gsm_shell_open_end_session_dialog (GsmShell *shell,
+ GsmShellEndSessionDialogType type,
+ GsmStore *inhibitors);
+void gsm_shell_close_end_session_dialog (GsmShell *shell);
+
+G_END_DECLS
+
+#endif /* __GSM_SHELL_H__ */
diff --git a/gnome-session/gsm-store.c b/gnome-session/gsm-store.c
new file mode 100644
index 0000000..30b0770
--- /dev/null
+++ b/gnome-session/gsm-store.c
@@ -0,0 +1,408 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007-2008 William Jon McCann <mccann@jhu.edu>
+ *
+ * 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 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 <stdlib.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib-object.h>
+
+#include "gsm-store.h"
+
+#define GSM_STORE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSM_TYPE_STORE, GsmStorePrivate))
+
+struct GsmStorePrivate
+{
+ GHashTable *objects;
+ gboolean locked;
+};
+
+enum {
+ ADDED,
+ REMOVED,
+ LAST_SIGNAL
+};
+
+enum {
+ PROP_0,
+ PROP_LOCKED
+};
+
+static guint signals [LAST_SIGNAL] = { 0 };
+
+static void gsm_store_class_init (GsmStoreClass *klass);
+static void gsm_store_init (GsmStore *store);
+static void gsm_store_finalize (GObject *object);
+
+G_DEFINE_TYPE (GsmStore, gsm_store, G_TYPE_OBJECT)
+
+GQuark
+gsm_store_error_quark (void)
+{
+ static GQuark ret = 0;
+ if (ret == 0) {
+ ret = g_quark_from_static_string ("gsm_store_error");
+ }
+
+ return ret;
+}
+
+guint
+gsm_store_size (GsmStore *store)
+{
+ return g_hash_table_size (store->priv->objects);
+}
+
+gboolean
+gsm_store_remove (GsmStore *store,
+ const char *id)
+{
+ GObject *found;
+ gboolean removed;
+ char *id_copy;
+
+ g_return_val_if_fail (store != NULL, FALSE);
+
+ found = g_hash_table_lookup (store->priv->objects, id);
+ if (found == NULL) {
+ return FALSE;
+ }
+
+ id_copy = g_strdup (id);
+
+ g_object_ref (found);
+
+ removed = g_hash_table_remove (store->priv->objects, id_copy);
+ g_assert (removed);
+
+ g_signal_emit (store, signals [REMOVED], 0, id_copy);
+
+ g_object_unref (found);
+ g_free (id_copy);
+
+ return TRUE;
+}
+
+void
+gsm_store_foreach (GsmStore *store,
+ GsmStoreFunc func,
+ gpointer user_data)
+{
+ g_return_if_fail (store != NULL);
+ g_return_if_fail (func != NULL);
+
+ g_hash_table_find (store->priv->objects,
+ (GHRFunc)func,
+ user_data);
+}
+
+GObject *
+gsm_store_find (GsmStore *store,
+ GsmStoreFunc predicate,
+ gpointer user_data)
+{
+ GObject *object;
+
+ g_return_val_if_fail (store != NULL, NULL);
+ g_return_val_if_fail (predicate != NULL, NULL);
+
+ object = g_hash_table_find (store->priv->objects,
+ (GHRFunc)predicate,
+ user_data);
+ return object;
+}
+
+GObject *
+gsm_store_lookup (GsmStore *store,
+ const char *id)
+{
+ GObject *object;
+
+ g_return_val_if_fail (store != NULL, NULL);
+ g_return_val_if_fail (id != NULL, NULL);
+
+ object = g_hash_table_lookup (store->priv->objects, id);
+
+ return object;
+}
+
+
+typedef struct
+{
+ GsmStoreFunc func;
+ gpointer user_data;
+ GsmStore *store;
+ GList *removed;
+} WrapperData;
+
+static gboolean
+foreach_remove_wrapper (const char *id,
+ GObject *object,
+ WrapperData *data)
+{
+ gboolean res;
+
+ res = (data->func) (id, object, data->user_data);
+ if (res) {
+ data->removed = g_list_prepend (data->removed, g_strdup (id));
+ }
+
+ return res;
+}
+
+guint
+gsm_store_foreach_remove (GsmStore *store,
+ GsmStoreFunc func,
+ gpointer user_data)
+{
+ guint ret;
+ WrapperData data;
+
+ g_return_val_if_fail (store != NULL, 0);
+ g_return_val_if_fail (func != NULL, 0);
+
+ data.store = store;
+ data.user_data = user_data;
+ data.func = func;
+ data.removed = NULL;
+
+ ret = g_hash_table_foreach_remove (store->priv->objects,
+ (GHRFunc)foreach_remove_wrapper,
+ &data);
+
+ while (data.removed != NULL) {
+ char *id;
+ id = data.removed->data;
+ g_debug ("GsmStore: emitting removed for %s", id);
+ g_signal_emit (store, signals [REMOVED], 0, id);
+ g_free (data.removed->data);
+ data.removed->data = NULL;
+ data.removed = g_list_delete_link (data.removed, data.removed);
+ }
+
+ return ret;
+}
+
+static gboolean
+_remove_all (const char *id,
+ GObject *object,
+ gpointer data)
+{
+ return TRUE;
+}
+
+void
+gsm_store_clear (GsmStore *store)
+{
+ g_return_if_fail (store != NULL);
+
+ g_debug ("GsmStore: Clearing object store");
+
+ gsm_store_foreach_remove (store,
+ _remove_all,
+ NULL);
+}
+
+gboolean
+gsm_store_add (GsmStore *store,
+ const char *id,
+ GObject *object)
+{
+ g_return_val_if_fail (store != NULL, FALSE);
+ g_return_val_if_fail (id != NULL, FALSE);
+ g_return_val_if_fail (object != NULL, FALSE);
+
+ /* If we're locked, we don't accept any new session
+ objects. */
+ if (store->priv->locked) {
+ return FALSE;
+ }
+
+ g_debug ("GsmStore: Adding object id %s to store", id);
+
+ g_hash_table_insert (store->priv->objects,
+ g_strdup (id),
+ g_object_ref (object));
+
+ g_signal_emit (store, signals [ADDED], 0, id);
+
+ return TRUE;
+}
+
+void
+gsm_store_set_locked (GsmStore *store,
+ gboolean locked)
+{
+ g_return_if_fail (GSM_IS_STORE (store));
+
+ store->priv->locked = locked;
+}
+
+gboolean
+gsm_store_get_locked (GsmStore *store)
+{
+ g_return_val_if_fail (GSM_IS_STORE (store), FALSE);
+
+ return store->priv->locked;
+}
+
+static void
+gsm_store_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GsmStore *self;
+
+ self = GSM_STORE (object);
+
+ switch (prop_id) {
+ case PROP_LOCKED:
+ gsm_store_set_locked (self, g_value_get_boolean (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gsm_store_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GsmStore *self;
+
+ self = GSM_STORE (object);
+
+ switch (prop_id) {
+ case PROP_LOCKED:
+ g_value_set_boolean (value, self->priv->locked);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gsm_store_dispose (GObject *object)
+{
+ GsmStore *store;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GSM_IS_STORE (object));
+
+ store = GSM_STORE (object);
+
+ gsm_store_clear (store);
+
+ G_OBJECT_CLASS (gsm_store_parent_class)->dispose (object);
+}
+
+static void
+gsm_store_class_init (GsmStoreClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = gsm_store_get_property;
+ object_class->set_property = gsm_store_set_property;
+ object_class->finalize = gsm_store_finalize;
+ object_class->dispose = gsm_store_dispose;
+
+ signals [ADDED] =
+ g_signal_new ("added",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmStoreClass, added),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1, G_TYPE_STRING);
+ signals [REMOVED] =
+ g_signal_new ("removed",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmStoreClass, removed),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1, G_TYPE_STRING);
+ g_object_class_install_property (object_class,
+ PROP_LOCKED,
+ g_param_spec_boolean ("locked",
+ NULL,
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ g_type_class_add_private (klass, sizeof (GsmStorePrivate));
+}
+
+static void
+_destroy_object (GObject *object)
+{
+ g_debug ("GsmStore: Unreffing object: %p", object);
+ g_object_unref (object);
+}
+
+static void
+gsm_store_init (GsmStore *store)
+{
+
+ store->priv = GSM_STORE_GET_PRIVATE (store);
+
+ store->priv->objects = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ (GDestroyNotify) _destroy_object);
+}
+
+static void
+gsm_store_finalize (GObject *object)
+{
+ GsmStore *store;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GSM_IS_STORE (object));
+
+ store = GSM_STORE (object);
+
+ g_return_if_fail (store->priv != NULL);
+
+ g_hash_table_destroy (store->priv->objects);
+
+ G_OBJECT_CLASS (gsm_store_parent_class)->finalize (object);
+}
+
+GsmStore *
+gsm_store_new (void)
+{
+ GObject *object;
+
+ object = g_object_new (GSM_TYPE_STORE,
+ NULL);
+
+ return GSM_STORE (object);
+}
diff --git a/gnome-session/gsm-store.h b/gnome-session/gsm-store.h
new file mode 100644
index 0000000..0cfb991
--- /dev/null
+++ b/gnome-session/gsm-store.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007-2008 William Jon McCann <mccann@jhu.edu>
+ *
+ * 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 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/>.
+ *
+ */
+
+
+#ifndef __GSM_STORE_H
+#define __GSM_STORE_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GSM_TYPE_STORE (gsm_store_get_type ())
+#define GSM_STORE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GSM_TYPE_STORE, GsmStore))
+#define GSM_STORE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GSM_TYPE_STORE, GsmStoreClass))
+#define GSM_IS_STORE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GSM_TYPE_STORE))
+#define GSM_IS_STORE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GSM_TYPE_STORE))
+#define GSM_STORE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GSM_TYPE_STORE, GsmStoreClass))
+
+typedef struct GsmStorePrivate GsmStorePrivate;
+
+typedef struct
+{
+ GObject parent;
+ GsmStorePrivate *priv;
+} GsmStore;
+
+typedef struct
+{
+ GObjectClass parent_class;
+
+ void (* added) (GsmStore *store,
+ const char *id);
+ void (* removed) (GsmStore *store,
+ const char *id);
+} GsmStoreClass;
+
+typedef enum
+{
+ GSM_STORE_ERROR_GENERAL
+} GsmStoreError;
+
+#define GSM_STORE_ERROR gsm_store_error_quark ()
+
+typedef gboolean (*GsmStoreFunc) (const char *id,
+ GObject *object,
+ gpointer user_data);
+
+GQuark gsm_store_error_quark (void);
+GType gsm_store_get_type (void);
+
+GsmStore * gsm_store_new (void);
+
+gboolean gsm_store_get_locked (GsmStore *store);
+void gsm_store_set_locked (GsmStore *store,
+ gboolean locked);
+
+guint gsm_store_size (GsmStore *store);
+gboolean gsm_store_add (GsmStore *store,
+ const char *id,
+ GObject *object);
+void gsm_store_clear (GsmStore *store);
+gboolean gsm_store_remove (GsmStore *store,
+ const char *id);
+
+void gsm_store_foreach (GsmStore *store,
+ GsmStoreFunc func,
+ gpointer user_data);
+guint gsm_store_foreach_remove (GsmStore *store,
+ GsmStoreFunc func,
+ gpointer user_data);
+GObject * gsm_store_find (GsmStore *store,
+ GsmStoreFunc predicate,
+ gpointer user_data);
+GObject * gsm_store_lookup (GsmStore *store,
+ const char *id);
+
+
+G_END_DECLS
+
+#endif /* __GSM_STORE_H */
diff --git a/gnome-session/gsm-system.c b/gnome-session/gsm-system.c
new file mode 100644
index 0000000..39b59f9
--- /dev/null
+++ b/gnome-session/gsm-system.c
@@ -0,0 +1,288 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 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, 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 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 <glib-object.h>
+#include <glib/gi18n.h>
+
+#include "gsm-system.h"
+
+#include "gsm-systemd.h"
+
+#ifdef HAVE_CONSOLEKIT
+#include "gsm-consolekit.h"
+#endif
+
+enum {
+ REQUEST_COMPLETED,
+ SHUTDOWN_PREPARED,
+ LAST_SIGNAL
+};
+
+enum {
+ PROP_0,
+ PROP_ACTIVE
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_INTERFACE (GsmSystem, gsm_system, G_TYPE_OBJECT)
+
+static void
+gsm_system_default_init (GsmSystemInterface *iface)
+{
+ GParamSpec *pspec;
+ signals [REQUEST_COMPLETED] =
+ g_signal_new ("request-completed",
+ GSM_TYPE_SYSTEM,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmSystemInterface, request_completed),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1, G_TYPE_POINTER);
+ signals[SHUTDOWN_PREPARED] =
+ g_signal_new ("shutdown-prepared",
+ GSM_TYPE_SYSTEM,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmSystemInterface, shutdown_prepared),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1, G_TYPE_BOOLEAN);
+ pspec = g_param_spec_boolean ("active",
+ "Active",
+ "Whether or not session is active",
+ TRUE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+ g_object_interface_install_property (iface, pspec);
+}
+
+typedef GObject GsmSystemNull;
+typedef GObjectClass GsmSystemNullClass;
+
+static void do_nothing (void) { }
+static gboolean return_true (void) { return TRUE; }
+static gboolean return_false (void) { return TRUE; }
+
+static void
+gsm_system_null_init_iface (GsmSystemInterface *iface)
+{
+ iface->can_switch_user = (void *) return_false;
+ iface->can_stop = (void *) return_false;
+ iface->can_restart = (void *) return_false;
+ iface->can_restart_to_firmware_setup = (void *) return_false;
+ iface->set_restart_to_firmware_setup = (void *) do_nothing;
+ iface->can_suspend = (void *) return_false;
+ iface->can_hibernate = (void *) return_false;
+ iface->attempt_stop = (void *) do_nothing;
+ iface->attempt_restart = (void *) do_nothing;
+ iface->suspend = (void *) do_nothing;
+ iface->hibernate = (void *) do_nothing;
+ iface->set_session_idle = (void *) do_nothing;
+ iface->is_login_session = (void *) return_true;
+ iface->set_inhibitors = (void *) do_nothing;
+ iface->prepare_shutdown = (void *) do_nothing;
+ iface->complete_shutdown = (void *) do_nothing;
+ iface->is_last_session_for_user = (void *) return_false;
+}
+
+static void
+gsm_system_null_init (GsmSystemNull *gsn)
+{
+}
+
+static void
+gsm_system_null_get_property (GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ g_value_set_boolean (value, TRUE);
+}
+
+static void
+gsm_system_null_class_init (GsmSystemNullClass *class)
+{
+ class->get_property = gsm_system_null_get_property;
+ class->set_property = (void *) do_nothing;
+
+ g_object_class_override_property (class, 1, "active");
+}
+
+static GType gsm_system_null_get_type (void);
+G_DEFINE_TYPE_WITH_CODE (GsmSystemNull, gsm_system_null, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GSM_TYPE_SYSTEM, gsm_system_null_init_iface))
+
+GQuark
+gsm_system_error_quark (void)
+{
+ static GQuark error_quark = 0;
+
+ if (error_quark == 0) {
+ error_quark = g_quark_from_static_string ("gsm-system-error");
+ }
+
+ return error_quark;
+}
+
+gboolean
+gsm_system_can_switch_user (GsmSystem *system)
+{
+ return GSM_SYSTEM_GET_IFACE (system)->can_switch_user (system);
+}
+
+gboolean
+gsm_system_can_stop (GsmSystem *system)
+{
+ return GSM_SYSTEM_GET_IFACE (system)->can_stop (system);
+}
+
+gboolean
+gsm_system_can_restart (GsmSystem *system)
+{
+ return GSM_SYSTEM_GET_IFACE (system)->can_restart (system);
+}
+
+gboolean
+gsm_system_can_restart_to_firmware_setup (GsmSystem *system)
+{
+ return GSM_SYSTEM_GET_IFACE (system)->can_restart_to_firmware_setup (system);
+}
+
+void
+gsm_system_set_restart_to_firmware_setup (GsmSystem *system,
+ gboolean enable)
+{
+ GSM_SYSTEM_GET_IFACE (system)->set_restart_to_firmware_setup (system, enable);
+}
+
+gboolean
+gsm_system_can_suspend (GsmSystem *system)
+{
+ return GSM_SYSTEM_GET_IFACE (system)->can_suspend (system);
+}
+
+gboolean
+gsm_system_can_hibernate (GsmSystem *system)
+{
+ return GSM_SYSTEM_GET_IFACE (system)->can_hibernate (system);
+}
+
+void
+gsm_system_attempt_stop (GsmSystem *system)
+{
+ GSM_SYSTEM_GET_IFACE (system)->attempt_stop (system);
+}
+
+void
+gsm_system_attempt_restart (GsmSystem *system)
+{
+ GSM_SYSTEM_GET_IFACE (system)->attempt_restart (system);
+}
+
+void
+gsm_system_suspend (GsmSystem *system)
+{
+ GSM_SYSTEM_GET_IFACE (system)->suspend (system);
+}
+
+void
+gsm_system_hibernate (GsmSystem *system)
+{
+ GSM_SYSTEM_GET_IFACE (system)->hibernate (system);
+}
+
+void
+gsm_system_set_session_idle (GsmSystem *system,
+ gboolean is_idle)
+{
+ GSM_SYSTEM_GET_IFACE (system)->set_session_idle (system, is_idle);
+}
+
+void
+gsm_system_set_inhibitors (GsmSystem *system,
+ GsmInhibitorFlag flags)
+{
+ GSM_SYSTEM_GET_IFACE (system)->set_inhibitors (system, flags);
+}
+
+gboolean
+gsm_system_is_login_session (GsmSystem *system)
+{
+ return GSM_SYSTEM_GET_IFACE (system)->is_login_session (system);
+}
+
+gboolean
+gsm_system_is_last_session_for_user (GsmSystem *system)
+{
+ return GSM_SYSTEM_GET_IFACE (system)->is_last_session_for_user (system);
+}
+
+/**
+ * gsm_system_is_active:
+ *
+ * Returns: %TRUE if the current session is in the foreground
+ * Since: 3.8
+ */
+gboolean
+gsm_system_is_active (GsmSystem *system)
+{
+ gboolean is_active;
+ g_object_get ((GObject*)system, "active", &is_active, NULL);
+ return is_active;
+}
+
+void
+gsm_system_prepare_shutdown (GsmSystem *system,
+ gboolean restart)
+{
+ GSM_SYSTEM_GET_IFACE (system)->prepare_shutdown (system, restart);
+}
+
+void
+gsm_system_complete_shutdown (GsmSystem *system)
+{
+ GSM_SYSTEM_GET_IFACE (system)->complete_shutdown (system);
+}
+
+GsmSystem *
+gsm_get_system (void)
+{
+ static GsmSystem *system = NULL;
+
+ if (system == NULL) {
+ system = GSM_SYSTEM (gsm_systemd_new ());
+ if (system != NULL) {
+ g_debug ("Using systemd for session tracking");
+ }
+ }
+
+#ifdef HAVE_CONSOLEKIT
+ if (system == NULL) {
+ system = GSM_SYSTEM (gsm_consolekit_new ());
+ if (system != NULL) {
+ g_debug ("Using ConsoleKit for session tracking");
+ }
+ }
+#endif
+
+ if (system == NULL) {
+ system = g_object_new (gsm_system_null_get_type (), NULL);
+ g_warning ("Using null backend for session tracking");
+ }
+
+ return g_object_ref (system);
+}
diff --git a/gnome-session/gsm-system.h b/gnome-session/gsm-system.h
new file mode 100644
index 0000000..438434c
--- /dev/null
+++ b/gnome-session/gsm-system.h
@@ -0,0 +1,130 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Jon McCann <jmccann@redhat.com>
+ *
+ * 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
+ * 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/>.
+ *
+ * Authors:
+ * Jon McCann <jmccann@redhat.com>
+ */
+
+#ifndef __GSM_SYSTEM_H__
+#define __GSM_SYSTEM_H__
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "gsm-inhibitor.h"
+
+G_BEGIN_DECLS
+
+#define GSM_TYPE_SYSTEM (gsm_system_get_type ())
+#define GSM_SYSTEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_SYSTEM, GsmSystem))
+#define GSM_SYSTEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_SYSTEM, GsmSystemInterface))
+#define GSM_IS_SYSTEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_SYSTEM))
+#define GSM_SYSTEM_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE((obj), GSM_TYPE_SYSTEM, GsmSystemInterface))
+#define GSM_SYSTEM_ERROR (gsm_system_error_quark ())
+
+typedef struct _GsmSystem GsmSystem;
+typedef struct _GsmSystemInterface GsmSystemInterface;
+typedef enum _GsmSystemError GsmSystemError;
+
+struct _GsmSystemInterface
+{
+ GTypeInterface base_interface;
+
+ void (* request_completed) (GsmSystem *system,
+ GError *error);
+
+ void (* shutdown_prepared) (GsmSystem *system,
+ gboolean success);
+
+ gboolean (* can_switch_user) (GsmSystem *system);
+ gboolean (* can_stop) (GsmSystem *system);
+ gboolean (* can_restart) (GsmSystem *system);
+ gboolean (* can_restart_to_firmware_setup) (GsmSystem *system);
+ void (* set_restart_to_firmware_setup) (GsmSystem *system,
+ gboolean enable);
+ gboolean (* can_suspend) (GsmSystem *system);
+ gboolean (* can_hibernate) (GsmSystem *system);
+ void (* attempt_stop) (GsmSystem *system);
+ void (* attempt_restart) (GsmSystem *system);
+ void (* suspend) (GsmSystem *system);
+ void (* hibernate) (GsmSystem *system);
+ void (* set_session_idle) (GsmSystem *system,
+ gboolean is_idle);
+ gboolean (* is_login_session) (GsmSystem *system);
+ void (* set_inhibitors) (GsmSystem *system,
+ GsmInhibitorFlag flags);
+ void (* prepare_shutdown) (GsmSystem *system,
+ gboolean restart);
+ void (* complete_shutdown)(GsmSystem *system);
+ gboolean (* is_last_session_for_user) (GsmSystem *system);
+};
+
+enum _GsmSystemError {
+ GSM_SYSTEM_ERROR_RESTARTING = 0,
+ GSM_SYSTEM_ERROR_STOPPING
+};
+
+GType gsm_system_get_type (void);
+
+GQuark gsm_system_error_quark (void);
+
+GsmSystem *gsm_get_system (void);
+
+gboolean gsm_system_can_switch_user (GsmSystem *system);
+
+gboolean gsm_system_can_stop (GsmSystem *system);
+
+gboolean gsm_system_can_restart (GsmSystem *system);
+
+gboolean gsm_system_can_restart_to_firmware_setup (GsmSystem *system);
+
+void gsm_system_set_restart_to_firmware_setup (GsmSystem *system,
+ gboolean enable);
+
+gboolean gsm_system_can_suspend (GsmSystem *system);
+
+gboolean gsm_system_can_hibernate (GsmSystem *system);
+
+void gsm_system_attempt_stop (GsmSystem *system);
+
+void gsm_system_attempt_restart (GsmSystem *system);
+
+void gsm_system_suspend (GsmSystem *system);
+
+void gsm_system_hibernate (GsmSystem *system);
+
+void gsm_system_set_session_idle (GsmSystem *system,
+ gboolean is_idle);
+
+gboolean gsm_system_is_login_session (GsmSystem *system);
+
+gboolean gsm_system_is_last_session_for_user (GsmSystem *system);
+
+gboolean gsm_system_is_active (GsmSystem *system);
+
+void gsm_system_set_inhibitors (GsmSystem *system,
+ GsmInhibitorFlag flags);
+
+void gsm_system_prepare_shutdown (GsmSystem *system,
+ gboolean restart);
+void gsm_system_complete_shutdown (GsmSystem *system);
+
+
+
+G_END_DECLS
+
+#endif /* __GSM_SYSTEM_H__ */
diff --git a/gnome-session/gsm-systemd.c b/gnome-session/gsm-systemd.c
new file mode 100644
index 0000000..cf30a4b
--- /dev/null
+++ b/gnome-session/gsm-systemd.c
@@ -0,0 +1,1183 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2011 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, 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 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/>.
+ *
+ * Author: Matthias Clasen
+ */
+
+#include "config.h"
+#include "gsm-systemd.h"
+
+#ifdef HAVE_SYSTEMD
+
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <pwd.h>
+
+#include <systemd/sd-login.h>
+
+#include <glib.h>
+#include <glib-object.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <gio/gunixfdlist.h>
+
+#include "gsm-system.h"
+
+#define SD_NAME "org.freedesktop.login1"
+#define SD_PATH "/org/freedesktop/login1"
+#define SD_INTERFACE "org.freedesktop.login1.Manager"
+#define SD_SEAT_INTERFACE "org.freedesktop.login1.Seat"
+#define SD_SESSION_INTERFACE "org.freedesktop.login1.Session"
+
+#define SYSTEMD_SESSION_REQUIRE_ONLINE 0 /* active or online sessions only */
+
+struct _GsmSystemdPrivate
+{
+ GSource *sd_source;
+ GDBusProxy *sd_proxy;
+ char *session_id;
+ gchar *session_path;
+
+ const gchar *inhibit_locks;
+ gint inhibit_fd;
+
+ gboolean is_active;
+
+ gint delay_inhibit_fd;
+ gboolean prepare_for_shutdown_expected;
+};
+
+enum {
+ PROP_0,
+ PROP_ACTIVE
+};
+
+static void gsm_systemd_system_init (GsmSystemInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GsmSystemd, gsm_systemd, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GSM_TYPE_SYSTEM,
+ gsm_systemd_system_init))
+
+static void
+drop_system_inhibitor (GsmSystemd *manager)
+{
+ if (manager->priv->inhibit_fd != -1) {
+ g_debug ("GsmSystemd: Dropping system inhibitor fd %d", manager->priv->inhibit_fd);
+ close (manager->priv->inhibit_fd);
+ manager->priv->inhibit_fd = -1;
+ }
+}
+
+static void
+drop_delay_inhibitor (GsmSystemd *manager)
+{
+ if (manager->priv->delay_inhibit_fd != -1) {
+ g_debug ("GsmSystemd: Dropping delay inhibitor");
+ close (manager->priv->delay_inhibit_fd);
+ manager->priv->delay_inhibit_fd = -1;
+ }
+}
+
+static void
+gsm_systemd_finalize (GObject *object)
+{
+ GsmSystemd *systemd = GSM_SYSTEMD (object);
+
+ g_clear_object (&systemd->priv->sd_proxy);
+ free (systemd->priv->session_id);
+ g_free (systemd->priv->session_path);
+
+ if (systemd->priv->sd_source) {
+ g_source_destroy (systemd->priv->sd_source);
+ }
+
+ drop_system_inhibitor (systemd);
+ drop_delay_inhibitor (systemd);
+
+ G_OBJECT_CLASS (gsm_systemd_parent_class)->finalize (object);
+}
+
+static void
+gsm_systemd_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GsmSystemd *self = GSM_SYSTEMD (object);
+
+ switch (prop_id) {
+ case PROP_ACTIVE:
+ self->priv->is_active = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gsm_systemd_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GsmSystemd *self = GSM_SYSTEMD (object);
+
+ switch (prop_id) {
+ case PROP_ACTIVE:
+ g_value_set_boolean (value, self->priv->is_active);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gsm_systemd_class_init (GsmSystemdClass *manager_class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (manager_class);
+
+ object_class->get_property = gsm_systemd_get_property;
+ object_class->set_property = gsm_systemd_set_property;
+ object_class->finalize = gsm_systemd_finalize;
+
+ g_object_class_override_property (object_class, PROP_ACTIVE, "active");
+
+ g_type_class_add_private (manager_class, sizeof (GsmSystemdPrivate));
+}
+
+typedef struct
+{
+ GSource source;
+ GPollFD pollfd;
+ sd_login_monitor *monitor;
+} SdSource;
+
+static gboolean
+sd_source_prepare (GSource *source,
+ gint *timeout)
+{
+ *timeout = -1;
+ return FALSE;
+}
+
+static gboolean
+sd_source_check (GSource *source)
+{
+ SdSource *sd_source = (SdSource *)source;
+
+ return sd_source->pollfd.revents != 0;
+}
+
+static gboolean
+sd_source_dispatch (GSource *source,
+ GSourceFunc callback,
+ gpointer user_data)
+
+{
+ SdSource *sd_source = (SdSource *)source;
+ gboolean ret;
+
+ g_warn_if_fail (callback != NULL);
+
+ ret = (*callback) (user_data);
+
+ sd_login_monitor_flush (sd_source->monitor);
+ return ret;
+}
+
+static void
+sd_source_finalize (GSource *source)
+{
+ SdSource *sd_source = (SdSource*)source;
+
+ sd_login_monitor_unref (sd_source->monitor);
+}
+
+static GSourceFuncs sd_source_funcs = {
+ sd_source_prepare,
+ sd_source_check,
+ sd_source_dispatch,
+ sd_source_finalize
+};
+
+static GSource *
+sd_source_new (void)
+{
+ GSource *source;
+ SdSource *sd_source;
+ int ret;
+
+ source = g_source_new (&sd_source_funcs, sizeof (SdSource));
+ sd_source = (SdSource *)source;
+
+ if ((ret = sd_login_monitor_new ("session", &sd_source->monitor)) < 0) {
+ g_warning ("Error getting login monitor: %d", ret);
+ } else {
+ sd_source->pollfd.fd = sd_login_monitor_get_fd (sd_source->monitor);
+ sd_source->pollfd.events = G_IO_IN;
+ g_source_add_poll (source, &sd_source->pollfd);
+ }
+
+ return source;
+}
+
+static gboolean
+on_sd_source_changed (gpointer user_data)
+{
+ GsmSystemd *self = user_data;
+ int active_r;
+ gboolean active;
+
+ active_r = sd_session_is_active (self->priv->session_id);
+ if (active_r < 0)
+ active = FALSE;
+ else
+ active = active_r;
+ if (active != self->priv->is_active) {
+ self->priv->is_active = active;
+ g_object_notify (G_OBJECT (self), "active");
+ }
+
+ return TRUE;
+}
+
+static void sd_proxy_signal_cb (GDBusProxy *proxy,
+ const gchar *sender_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data);
+
+static gboolean
+_systemd_session_is_graphical (const char *session_id)
+{
+ const gchar * const graphical_session_types[] = { "wayland", "x11", "mir", NULL };
+ int saved_errno;
+ g_autofree gchar *type = NULL;
+
+ saved_errno = sd_session_get_type (session_id, &type);
+ if (saved_errno < 0) {
+ g_warning ("Couldn't get type for session '%s': %s",
+ session_id,
+ g_strerror (-saved_errno));
+ return FALSE;
+ }
+
+ if (!g_strv_contains (graphical_session_types, type)) {
+ g_debug ("Session '%s' is not a graphical session (type: '%s')",
+ session_id,
+ type);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+_systemd_session_is_active (const char *session_id)
+{
+ const gchar * const active_states[] = { "active", "online", NULL };
+ int saved_errno;
+ g_autofree gchar *state = NULL;
+
+ /*
+ * display sessions can be 'closing' if they are logged out but some
+ * processes are lingering; we shouldn't consider these (this is
+ * checking for a race condition since we specified
+ * SYSTEMD_SESSION_REQUIRE_ONLINE)
+ */
+ saved_errno = sd_session_get_state (session_id, &state);
+ if (saved_errno < 0) {
+ g_warning ("Couldn't get state for session '%s': %s",
+ session_id,
+ g_strerror (-saved_errno));
+ return FALSE;
+ }
+
+ if (!g_strv_contains (active_states, state)) {
+ g_debug ("Session '%s' is not active or online", session_id);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+gsm_systemd_find_session (char **session_id)
+{
+ char *local_session_id = NULL;
+ g_auto(GStrv) sessions = NULL;
+ int n_sessions;
+
+ g_return_val_if_fail (session_id != NULL, FALSE);
+
+ g_debug ("Finding a graphical session for user %d", getuid ());
+
+ n_sessions = sd_uid_get_sessions (getuid (),
+ SYSTEMD_SESSION_REQUIRE_ONLINE,
+ &sessions);
+
+ if (n_sessions < 0) {
+ g_critical ("Failed to get sessions for user %d", getuid ());
+ return FALSE;
+ }
+
+ for (int i = 0; i < n_sessions; ++i) {
+ g_debug ("Considering session '%s'", sessions[i]);
+
+ if (!_systemd_session_is_graphical (sessions[i]))
+ continue;
+
+ if (!_systemd_session_is_active (sessions[i]))
+ continue;
+
+ /*
+ * We get the sessions from newest to oldest, so take the last
+ * one we find that's good
+ */
+ local_session_id = sessions[i];
+ }
+
+ if (local_session_id == NULL)
+ return FALSE;
+
+ *session_id = g_strdup (local_session_id);
+
+ return TRUE;
+}
+
+static void
+gsm_systemd_init (GsmSystemd *manager)
+{
+ GError *error = NULL;
+ GDBusConnection *bus;
+ GVariant *res;
+
+ manager->priv = G_TYPE_INSTANCE_GET_PRIVATE (manager,
+ GSM_TYPE_SYSTEMD,
+ GsmSystemdPrivate);
+
+ manager->priv->inhibit_fd = -1;
+ manager->priv->delay_inhibit_fd = -1;
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (bus == NULL)
+ g_error ("Failed to connect to system bus: %s",
+ error->message);
+ manager->priv->sd_proxy =
+ g_dbus_proxy_new_sync (bus,
+ 0,
+ NULL,
+ SD_NAME,
+ SD_PATH,
+ SD_INTERFACE,
+ NULL,
+ &error);
+ if (manager->priv->sd_proxy == NULL) {
+ g_warning ("Failed to connect to systemd: %s",
+ error->message);
+ g_clear_error (&error);
+ }
+
+ g_signal_connect (manager->priv->sd_proxy, "g-signal",
+ G_CALLBACK (sd_proxy_signal_cb), manager);
+
+ gsm_systemd_find_session (&manager->priv->session_id);
+
+ if (manager->priv->session_id == NULL) {
+ g_warning ("Could not get session id for session. Check that logind is "
+ "properly installed and pam_systemd is getting used at login.");
+ return;
+ }
+
+ g_debug ("Found session ID: %s", manager->priv->session_id);
+
+ res = g_dbus_proxy_call_sync (manager->priv->sd_proxy,
+ "GetSession",
+ g_variant_new ("(s)", manager->priv->session_id),
+ 0,
+ G_MAXINT,
+ NULL,
+ &error);
+ if (res == NULL) {
+ g_warning ("Could not get session id for session. Check that logind is "
+ "properly installed and pam_systemd is getting used at login: %s",
+ error->message);
+ g_error_free (error);
+ return;
+ }
+
+ g_variant_get (res, "(o)", &manager->priv->session_path);
+ g_variant_unref (res);
+
+ manager->priv->sd_source = sd_source_new ();
+ g_source_set_callback (manager->priv->sd_source, on_sd_source_changed, manager, NULL);
+ g_source_attach (manager->priv->sd_source, NULL);
+
+ on_sd_source_changed (manager);
+
+ g_object_unref (bus);
+}
+
+static void
+emit_restart_complete (GsmSystemd *manager,
+ GError *error)
+{
+ GError *call_error;
+
+ call_error = NULL;
+
+ if (error != NULL) {
+ call_error = g_error_new_literal (GSM_SYSTEM_ERROR,
+ GSM_SYSTEM_ERROR_RESTARTING,
+ error->message);
+ }
+
+ g_signal_emit_by_name (G_OBJECT (manager),
+ "request_completed", call_error);
+
+ if (call_error != NULL) {
+ g_error_free (call_error);
+ }
+}
+
+static void
+emit_stop_complete (GsmSystemd *manager,
+ GError *error)
+{
+ GError *call_error;
+
+ call_error = NULL;
+
+ if (error != NULL) {
+ call_error = g_error_new_literal (GSM_SYSTEM_ERROR,
+ GSM_SYSTEM_ERROR_STOPPING,
+ error->message);
+ }
+
+ g_signal_emit_by_name (G_OBJECT (manager),
+ "request_completed", call_error);
+
+ if (call_error != NULL) {
+ g_error_free (call_error);
+ }
+}
+
+static void
+restart_done (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (source);
+ GsmSystemd *manager = user_data;
+ GError *error = NULL;
+ GVariant *res;
+
+ res = g_dbus_proxy_call_finish (proxy, result, &error);
+
+ if (!res) {
+ g_warning ("Unable to restart system: %s", error->message);
+ emit_restart_complete (manager, error);
+ g_error_free (error);
+ } else {
+ emit_restart_complete (manager, NULL);
+ g_variant_unref (res);
+ }
+}
+
+static void
+gsm_systemd_attempt_restart (GsmSystem *system)
+{
+ GsmSystemd *manager = GSM_SYSTEMD (system);
+
+ g_dbus_proxy_call (manager->priv->sd_proxy,
+ "Reboot",
+ g_variant_new ("(b)", TRUE),
+ 0,
+ G_MAXINT,
+ NULL,
+ restart_done,
+ manager);
+}
+
+static void
+stop_done (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (source);
+ GsmSystemd *manager = user_data;
+ GError *error = NULL;
+ GVariant *res;
+
+ res = g_dbus_proxy_call_finish (proxy, result, &error);
+
+ if (!res) {
+ g_warning ("Unable to stop system: %s", error->message);
+ emit_stop_complete (manager, error);
+ g_error_free (error);
+ } else {
+ emit_stop_complete (manager, NULL);
+ g_variant_unref (res);
+ }
+}
+
+static void
+gsm_systemd_attempt_stop (GsmSystem *system)
+{
+ GsmSystemd *manager = GSM_SYSTEMD (system);
+
+ g_dbus_proxy_call (manager->priv->sd_proxy,
+ "PowerOff",
+ g_variant_new ("(b)", TRUE),
+ 0,
+ G_MAXINT,
+ NULL,
+ stop_done,
+ manager);
+}
+
+static void
+gsm_systemd_set_session_idle (GsmSystem *system,
+ gboolean is_idle)
+{
+ GsmSystemd *manager = GSM_SYSTEMD (system);
+ GDBusConnection *bus;
+
+ if (manager->priv->session_path == NULL) {
+ g_warning ("Could not get session path for session. Check that logind is "
+ "properly installed and pam_systemd is getting used at login.");
+ return;
+ }
+
+ g_debug ("Updating systemd idle status: %d", is_idle);
+ bus = g_dbus_proxy_get_connection (manager->priv->sd_proxy);
+ g_dbus_connection_call (bus,
+ SD_NAME,
+ manager->priv->session_path,
+ SD_SESSION_INTERFACE,
+ "SetIdleHint",
+ g_variant_new ("(b)", is_idle),
+ G_VARIANT_TYPE_BOOLEAN,
+ 0,
+ G_MAXINT,
+ NULL, NULL, NULL);
+}
+
+static gboolean
+gsm_systemd_can_switch_user (GsmSystem *system)
+{
+ GsmSystemd *manager = GSM_SYSTEMD (system);
+ gchar *seat;
+ gint ret;
+
+ sd_session_get_seat (manager->priv->session_id, &seat);
+ ret = sd_seat_can_multi_session (seat);
+ free (seat);
+
+ return ret > 0;
+}
+
+static gboolean
+gsm_systemd_can_restart (GsmSystem *system)
+{
+ GsmSystemd *manager = GSM_SYSTEMD (system);
+ gchar *rv;
+ GVariant *res;
+ gboolean can_restart;
+
+ res = g_dbus_proxy_call_sync (manager->priv->sd_proxy,
+ "CanReboot",
+ NULL,
+ 0,
+ G_MAXINT,
+ NULL,
+ NULL);
+ if (!res) {
+ g_warning ("Calling CanReboot failed. Check that logind is "
+ "properly installed and pam_systemd is getting used at login.");
+ return FALSE;
+ }
+
+ g_variant_get (res, "(s)", &rv);
+ g_variant_unref (res);
+
+ can_restart = g_strcmp0 (rv, "yes") == 0 ||
+ g_strcmp0 (rv, "challenge") == 0;
+
+ g_free (rv);
+
+ return can_restart;
+}
+
+static gboolean
+gsm_systemd_can_restart_to_firmware_setup (GsmSystem *system)
+{
+ GsmSystemd *manager = GSM_SYSTEMD (system);
+ const gchar *rv;
+ GVariant *res;
+ gboolean can_restart;
+ GError *error = NULL;
+
+ res = g_dbus_proxy_call_sync (manager->priv->sd_proxy,
+ "CanRebootToFirmwareSetup",
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ G_MAXINT,
+ NULL,
+ &error);
+ if (!res) {
+ g_warning ("Calling CanRebootToFirmwareSetup failed. Check that logind is "
+ "properly installed and pam_systemd is getting used at login: %s",
+ error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ g_variant_get (res, "(&s)", &rv);
+
+ can_restart = g_strcmp0 (rv, "yes") == 0 ||
+ g_strcmp0 (rv, "challenge") == 0;
+
+ g_variant_unref (res);
+
+ return can_restart;
+}
+
+static void
+gsm_systemd_set_restart_to_firmware_setup (GsmSystem *system,
+ gboolean enable)
+{
+ GsmSystemd *manager = GSM_SYSTEMD (system);
+ GVariant *res;
+ GError *error = NULL;
+
+ res = g_dbus_proxy_call_sync (manager->priv->sd_proxy,
+ "SetRebootToFirmwareSetup",
+ g_variant_new ("(b)", enable),
+ G_DBUS_CALL_FLAGS_NONE,
+ G_MAXINT,
+ NULL,
+ &error);
+ if (!res) {
+ g_warning ("Calling SetRebootToFirmwareSetup failed. Check that logind is "
+ "properly installed and pam_systemd is getting used at login: %s",
+ error->message);
+ g_error_free (error);
+ }
+
+ g_variant_unref (res);
+}
+
+static gboolean
+gsm_systemd_can_stop (GsmSystem *system)
+{
+ GsmSystemd *manager = GSM_SYSTEMD (system);
+ gchar *rv;
+ GVariant *res;
+ gboolean can_stop;
+
+ res = g_dbus_proxy_call_sync (manager->priv->sd_proxy,
+ "CanPowerOff",
+ NULL,
+ 0,
+ G_MAXINT,
+ NULL,
+ NULL);
+ if (!res) {
+ g_warning ("Calling CanPowerOff failed. Check that logind is "
+ "properly installed and pam_systemd is getting used at login.");
+ return FALSE;
+ }
+
+ g_variant_get (res, "(s)", &rv);
+ g_variant_unref (res);
+
+ can_stop = g_strcmp0 (rv, "yes") == 0 ||
+ g_strcmp0 (rv, "challenge") == 0;
+
+ g_free (rv);
+
+ return can_stop;
+}
+
+static gboolean
+gsm_systemd_is_login_session (GsmSystem *system)
+{
+ GsmSystemd *manager = GSM_SYSTEMD (system);
+ int res;
+ gboolean ret;
+ gchar *session_class = NULL;
+
+ ret = FALSE;
+
+ if (manager->priv->session_id == NULL) {
+ return ret;
+ }
+
+ res = sd_session_get_class (manager->priv->session_id, &session_class);
+ if (res < 0) {
+ g_warning ("Could not get session class: %s", strerror (-res));
+ return FALSE;
+ }
+ ret = (g_strcmp0 (session_class, "greeter") == 0);
+ free (session_class);
+
+ return ret;
+}
+
+static gboolean
+gsm_systemd_can_suspend (GsmSystem *system)
+{
+ GsmSystemd *manager = GSM_SYSTEMD (system);
+ gchar *rv;
+ GVariant *res;
+ gboolean can_suspend;
+
+ res = g_dbus_proxy_call_sync (manager->priv->sd_proxy,
+ "CanSuspend",
+ NULL,
+ 0,
+ G_MAXINT,
+ NULL,
+ NULL);
+ if (!res) {
+ g_warning ("Calling CanSuspend failed. Check that logind is "
+ "properly installed and pam_systemd is getting used at login.");
+ return FALSE;
+ }
+
+ g_variant_get (res, "(s)", &rv);
+ g_variant_unref (res);
+
+ can_suspend = g_strcmp0 (rv, "yes") == 0 ||
+ g_strcmp0 (rv, "challenge") == 0;
+
+ g_free (rv);
+
+ return can_suspend;
+}
+
+static gboolean
+gsm_systemd_can_hibernate (GsmSystem *system)
+{
+ GsmSystemd *manager = GSM_SYSTEMD (system);
+ gchar *rv;
+ GVariant *res;
+ gboolean can_hibernate;
+
+ res = g_dbus_proxy_call_sync (manager->priv->sd_proxy,
+ "CanHibernate",
+ NULL,
+ 0,
+ G_MAXINT,
+ NULL,
+ NULL);
+ if (!res) {
+ g_warning ("Calling CanHibernate failed. Check that logind is "
+ "properly installed and pam_systemd is getting used at login.");
+ return FALSE;
+ }
+
+ g_variant_get (res, "(s)", &rv);
+ g_variant_unref (res);
+
+ can_hibernate = g_strcmp0 (rv, "yes") == 0 ||
+ g_strcmp0 (rv, "challenge") == 0;
+
+ g_free (rv);
+
+ return can_hibernate;
+}
+
+static void
+suspend_done (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (source);
+ GError *error = NULL;
+ GVariant *res;
+
+ res = g_dbus_proxy_call_finish (proxy, result, &error);
+
+ if (!res) {
+ g_warning ("Unable to suspend system: %s", error->message);
+ g_error_free (error);
+ } else {
+ g_variant_unref (res);
+ }
+}
+
+static void
+hibernate_done (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (source);
+ GError *error = NULL;
+ GVariant *res;
+
+ res = g_dbus_proxy_call_finish (proxy, result, &error);
+
+ if (!res) {
+ g_warning ("Unable to hibernate system: %s", error->message);
+ g_error_free (error);
+ } else {
+ g_variant_unref (res);
+ }
+}
+
+static void
+gsm_systemd_suspend (GsmSystem *system)
+{
+ GsmSystemd *manager = GSM_SYSTEMD (system);
+
+ g_dbus_proxy_call (manager->priv->sd_proxy,
+ "Suspend",
+ g_variant_new ("(b)", TRUE),
+ 0,
+ G_MAXINT,
+ NULL,
+ hibernate_done,
+ manager);
+}
+
+static void
+gsm_systemd_hibernate (GsmSystem *system)
+{
+ GsmSystemd *manager = GSM_SYSTEMD (system);
+
+ g_dbus_proxy_call (manager->priv->sd_proxy,
+ "Hibernate",
+ g_variant_new ("(b)", TRUE),
+ 0,
+ G_MAXINT,
+ NULL,
+ suspend_done,
+ manager);
+}
+
+static void
+inhibit_done (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (source);
+ GsmSystemd *manager = GSM_SYSTEMD (user_data);
+ GError *error = NULL;
+ GVariant *res;
+ GUnixFDList *fd_list = NULL;
+ gint idx;
+
+ /* Drop any previous inhibit before recording the new one */
+ drop_system_inhibitor (manager);
+
+ res = g_dbus_proxy_call_with_unix_fd_list_finish (proxy, &fd_list, result, &error);
+
+ if (!res) {
+ g_warning ("Unable to inhibit system: %s", error->message);
+ g_error_free (error);
+ } else {
+ g_variant_get (res, "(h)", &idx);
+ manager->priv->inhibit_fd = g_unix_fd_list_get (fd_list, idx, &error);
+ if (manager->priv->inhibit_fd == -1) {
+ g_warning ("Failed to receive system inhibitor fd: %s", error->message);
+ g_error_free (error);
+ }
+ g_debug ("System inhibitor fd is %d", manager->priv->inhibit_fd);
+ g_object_unref (fd_list);
+ g_variant_unref (res);
+ }
+
+ /* Handle a race condition, where locks got unset during dbus call */
+ if (manager->priv->inhibit_locks == NULL) {
+ drop_system_inhibitor (manager);
+ }
+}
+
+static void
+gsm_systemd_set_inhibitors (GsmSystem *system,
+ GsmInhibitorFlag flags)
+{
+ GsmSystemd *manager = GSM_SYSTEMD (system);
+ const gchar *locks = NULL;
+ gboolean inhibit_logout;
+ gboolean inhibit_suspend;
+
+ inhibit_logout = (flags & GSM_INHIBITOR_FLAG_LOGOUT) != 0;
+ inhibit_suspend = (flags & GSM_INHIBITOR_FLAG_SUSPEND) != 0;
+
+ if (inhibit_logout && inhibit_suspend) {
+ locks = "sleep:shutdown";
+ } else if (inhibit_logout) {
+ locks = "shutdown";
+ } else if (inhibit_suspend) {
+ locks = "sleep";
+ }
+ manager->priv->inhibit_locks = locks;
+
+ if (locks != NULL) {
+ g_debug ("Adding system inhibitor on %s", locks);
+ g_dbus_proxy_call_with_unix_fd_list (manager->priv->sd_proxy,
+ "Inhibit",
+ g_variant_new ("(ssss)",
+ locks,
+ g_get_user_name (),
+ "user session inhibited",
+ "block"),
+ 0,
+ G_MAXINT,
+ NULL,
+ NULL,
+ inhibit_done,
+ manager);
+ } else {
+ drop_system_inhibitor (manager);
+ }
+}
+
+static void
+reboot_or_poweroff_done (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GsmSystemd *systemd = user_data;
+ GVariant *result;
+ GError *error = NULL;
+
+ result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source),
+ res,
+ &error);
+
+ if (result == NULL) {
+ if (!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED)) {
+ g_warning ("Shutdown failed: %s", error->message);
+ }
+ g_error_free (error);
+ drop_delay_inhibitor (systemd);
+ g_debug ("GsmSystemd: shutdown preparation failed");
+ systemd->priv->prepare_for_shutdown_expected = FALSE;
+ g_signal_emit_by_name (systemd, "shutdown-prepared", FALSE);
+ } else {
+ g_variant_unref (result);
+ }
+}
+
+static void
+gsm_systemd_prepare_shutdown (GsmSystem *system,
+ gboolean restart)
+{
+ GsmSystemd *systemd = GSM_SYSTEMD (system);
+ GUnixFDList *fd_list;
+ GVariant *res;
+ GError *error = NULL;
+ gint idx;
+
+ g_debug ("GsmSystemd: prepare shutdown");
+
+ res = g_dbus_proxy_call_with_unix_fd_list_sync (systemd->priv->sd_proxy,
+ "Inhibit",
+ g_variant_new ("(ssss)",
+ "shutdown",
+ g_get_user_name (),
+ "Preparing to end the session",
+ "delay"),
+ 0,
+ G_MAXINT,
+ NULL,
+ &fd_list,
+ NULL,
+ &error);
+ if (res == NULL) {
+ g_warning ("Failed to get delay inhibitor: %s", error->message);
+ g_error_free (error);
+ g_signal_emit_by_name (systemd, "shutdown-prepared", FALSE);
+ return;
+ }
+
+ g_variant_get (res, "(h)", &idx);
+
+ systemd->priv->delay_inhibit_fd = g_unix_fd_list_get (fd_list, idx, NULL);
+
+ g_debug ("GsmSystemd: got delay inhibitor, fd = %d", systemd->priv->delay_inhibit_fd);
+
+ g_variant_unref (res);
+ g_object_unref (fd_list);
+
+ systemd->priv->prepare_for_shutdown_expected = TRUE;
+
+ g_dbus_proxy_call (systemd->priv->sd_proxy,
+ restart ? "Reboot" : "PowerOff",
+ g_variant_new ("(b)", TRUE),
+ 0,
+ G_MAXINT,
+ NULL,
+ reboot_or_poweroff_done,
+ systemd);
+}
+
+static void
+gsm_systemd_complete_shutdown (GsmSystem *system)
+{
+ GsmSystemd *systemd = GSM_SYSTEMD (system);
+
+ /* remove delay inhibitor, if any */
+ drop_delay_inhibitor (systemd);
+}
+
+static gboolean
+gsm_systemd_is_last_session_for_user (GsmSystem *system)
+{
+ char **sessions = NULL;
+ char *session = NULL;
+ gboolean is_last_session;
+ int ret, i;
+
+ if (!gsm_systemd_find_session (&session)) {
+ return FALSE;
+ }
+
+ ret = sd_uid_get_sessions (getuid (), FALSE, &sessions);
+
+ if (ret <= 0) {
+ free (session);
+ return FALSE;
+ }
+
+ is_last_session = TRUE;
+ for (i = 0; sessions[i]; i++) {
+ char *state = NULL;
+ char *type = NULL;
+
+ if (g_strcmp0 (sessions[i], session) == 0)
+ continue;
+
+ ret = sd_session_get_state (sessions[i], &state);
+
+ if (ret != 0)
+ continue;
+
+ if (g_strcmp0 (state, "closing") == 0) {
+ free (state);
+ continue;
+ }
+ free (state);
+
+ ret = sd_session_get_type (sessions[i], &type);
+
+ if (ret != 0)
+ continue;
+
+ if (g_strcmp0 (type, "x11") != 0 &&
+ g_strcmp0 (type, "wayland") != 0) {
+ free (type);
+ continue;
+ }
+
+ is_last_session = FALSE;
+ }
+
+ for (i = 0; sessions[i]; i++)
+ free (sessions[i]);
+ free (sessions);
+ free (session);
+
+ return is_last_session;
+}
+
+static void
+gsm_systemd_system_init (GsmSystemInterface *iface)
+{
+ iface->can_switch_user = gsm_systemd_can_switch_user;
+ iface->can_stop = gsm_systemd_can_stop;
+ iface->can_restart = gsm_systemd_can_restart;
+ iface->can_restart_to_firmware_setup = gsm_systemd_can_restart_to_firmware_setup;
+ iface->set_restart_to_firmware_setup = gsm_systemd_set_restart_to_firmware_setup;
+ iface->can_suspend = gsm_systemd_can_suspend;
+ iface->can_hibernate = gsm_systemd_can_hibernate;
+ iface->attempt_stop = gsm_systemd_attempt_stop;
+ iface->attempt_restart = gsm_systemd_attempt_restart;
+ iface->suspend = gsm_systemd_suspend;
+ iface->hibernate = gsm_systemd_hibernate;
+ iface->set_session_idle = gsm_systemd_set_session_idle;
+ iface->is_login_session = gsm_systemd_is_login_session;
+ iface->set_inhibitors = gsm_systemd_set_inhibitors;
+ iface->prepare_shutdown = gsm_systemd_prepare_shutdown;
+ iface->complete_shutdown = gsm_systemd_complete_shutdown;
+ iface->is_last_session_for_user = gsm_systemd_is_last_session_for_user;
+}
+
+GsmSystemd *
+gsm_systemd_new (void)
+{
+ GsmSystemd *manager;
+
+ /* logind is not running ? */
+ if (access("/run/systemd/seats/", F_OK) < 0)
+ return NULL;
+
+ manager = g_object_new (GSM_TYPE_SYSTEMD, NULL);
+
+ return manager;
+}
+
+static void
+sd_proxy_signal_cb (GDBusProxy *proxy,
+ const gchar *sender_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ GsmSystemd *systemd = user_data;
+ gboolean is_about_to_shutdown;
+
+ g_debug ("GsmSystemd: received logind signal: %s", signal_name);
+
+ if (g_strcmp0 (signal_name, "PrepareForShutdown") != 0) {
+ g_debug ("GsmSystemd: ignoring %s signal", signal_name);
+ return;
+ }
+
+ g_variant_get (parameters, "(b)", &is_about_to_shutdown);
+ if (!is_about_to_shutdown) {
+ g_debug ("GsmSystemd: ignoring %s signal since about-to-shutdown is FALSE", signal_name);
+ return;
+ }
+
+ if (systemd->priv->prepare_for_shutdown_expected) {
+ g_debug ("GsmSystemd: shutdown successfully prepared");
+ g_signal_emit_by_name (systemd, "shutdown-prepared", TRUE);
+ systemd->priv->prepare_for_shutdown_expected = FALSE;
+ }
+}
+
+#else
+
+GsmSystemd *
+gsm_systemd_new (void)
+{
+ return NULL;
+}
+
+#endif
+
diff --git a/gnome-session/gsm-systemd.h b/gnome-session/gsm-systemd.h
new file mode 100644
index 0000000..8fa6aa0
--- /dev/null
+++ b/gnome-session/gsm-systemd.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 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
+ * 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/>.
+ *
+ * Authors:
+ * Matthias Clasen <mclasen@redhat.com>
+ */
+
+#ifndef __GSM_SYSTEMD_H__
+#define __GSM_SYSTEMD_H__
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GSM_TYPE_SYSTEMD (gsm_systemd_get_type ())
+#define GSM_SYSTEMD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_SYSTEMD, GsmSystemd))
+#define GSM_SYSTEMD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_SYSTEMD, GsmSystemdClass))
+#define GSM_IS_SYSTEMD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_SYSTEMD))
+#define GSM_IS_SYSTEMD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_SYSTEMD))
+#define GSM_SYSTEMD_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GSM_TYPE_SYSTEMD, GsmSystemdClass))
+
+typedef struct _GsmSystemd GsmSystemd;
+typedef struct _GsmSystemdClass GsmSystemdClass;
+typedef struct _GsmSystemdPrivate GsmSystemdPrivate;
+
+struct _GsmSystemd
+{
+ GObject parent;
+
+ GsmSystemdPrivate *priv;
+};
+
+struct _GsmSystemdClass
+{
+ GObjectClass parent_class;
+};
+
+GType gsm_systemd_get_type (void);
+
+GsmSystemd *gsm_systemd_new (void) G_GNUC_MALLOC;
+
+G_END_DECLS
+
+#endif /* __GSM_SYSTEMD_H__ */
diff --git a/gnome-session/gsm-util.c b/gnome-session/gsm-util.c
new file mode 100644
index 0000000..f1e6489
--- /dev/null
+++ b/gnome-session/gsm-util.c
@@ -0,0 +1,863 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ * gsm-util.c
+ * Copyright (C) 2008 Lucas Rocha.
+ *
+ * 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 <stdlib.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <errno.h>
+#include <string.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+
+#include "gsm-util.h"
+
+static gchar *_saved_session_dir = NULL;
+static gchar **child_environment;
+
+/* These are variables that will not be passed on to subprocesses
+ * (either directly, via systemd or DBus).
+ * Some of these are blacklisted as they might end up in the wrong session
+ * (e.g. XDG_VTNR), others because they simply must never be passed on
+ * (NOTIFY_SOCKET).
+ */
+static const char * const variable_blacklist[] = {
+ "NOTIFY_SOCKET",
+ "XDG_SEAT",
+ "XDG_SESSION_ID",
+ "XDG_VTNR",
+ NULL
+};
+
+/* The following is copied from GDMs spawn_session function.
+ *
+ * Environment variables listed here will be copied into the user's service
+ * environments if they are set in gnome-session's environment. If they are
+ * not set in gnome-session's environment, they will be removed from the
+ * service environments. This is to protect against environment variables
+ * leaking from previous sessions (e.g. when switching from classic to
+ * default GNOME $GNOME_SHELL_SESSION_MODE will become unset).
+ */
+static const char * const variable_unsetlist[] = {
+ "DISPLAY",
+ "XAUTHORITY",
+ "WAYLAND_DISPLAY",
+ "WAYLAND_SOCKET",
+ "GNOME_SHELL_SESSION_MODE",
+ "GNOME_SETUP_DISPLAY",
+
+ /* None of the LC_* variables should survive a logout/login */
+ "LC_CTYPE",
+ "LC_NUMERIC",
+ "LC_TIME",
+ "LC_COLLATE",
+ "LC_MONETARY",
+ "LC_MESSAGES",
+ "LC_PAPER",
+ "LC_NAME",
+ "LC_ADDRESS",
+ "LC_TELEPHONE",
+ "LC_MEASUREMENT",
+ "LC_IDENTIFICATION",
+ "LC_ALL",
+
+ NULL
+};
+
+char *
+gsm_util_find_desktop_file_for_app_name (const char *name,
+ gboolean look_in_saved_session,
+ gboolean autostart_first)
+{
+ char *app_path;
+ char **app_dirs;
+ GKeyFile *key_file;
+ char *desktop_file;
+ int i;
+
+ app_path = NULL;
+
+ app_dirs = gsm_util_get_desktop_dirs (look_in_saved_session, autostart_first);
+
+ key_file = g_key_file_new ();
+
+ desktop_file = g_strdup_printf ("%s.desktop", name);
+
+ g_debug ("GsmUtil: Looking for file '%s'", desktop_file);
+
+ for (i = 0; app_dirs[i] != NULL; i++) {
+ g_debug ("GsmUtil: Looking in '%s'", app_dirs[i]);
+ }
+
+ g_key_file_load_from_dirs (key_file,
+ desktop_file,
+ (const char **) app_dirs,
+ &app_path,
+ G_KEY_FILE_NONE,
+ NULL);
+
+ if (app_path != NULL) {
+ g_debug ("GsmUtil: found in XDG dirs: '%s'", app_path);
+ }
+
+ /* look for gnome vendor prefix */
+ if (app_path == NULL) {
+ g_free (desktop_file);
+ desktop_file = g_strdup_printf ("gnome-%s.desktop", name);
+
+ g_key_file_load_from_dirs (key_file,
+ desktop_file,
+ (const char **) app_dirs,
+ &app_path,
+ G_KEY_FILE_NONE,
+ NULL);
+ if (app_path != NULL) {
+ g_debug ("GsmUtil: found in XDG dirs: '%s'", app_path);
+ }
+ }
+
+ g_free (desktop_file);
+ g_key_file_free (key_file);
+
+ g_strfreev (app_dirs);
+
+ return app_path;
+}
+
+static gboolean
+ensure_dir_exists (const char *dir)
+{
+ if (g_mkdir_with_parents (dir, 0700) == 0)
+ return TRUE;
+
+ g_warning ("GsmSessionSave: Failed to create directory %s: %s", dir, strerror (errno));
+
+ return FALSE;
+}
+
+gchar *
+gsm_util_get_empty_tmp_session_dir (void)
+{
+ char *tmp;
+ gboolean exists;
+
+ tmp = g_build_filename (g_get_user_config_dir (),
+ "gnome-session",
+ "saved-session.new",
+ NULL);
+
+ exists = ensure_dir_exists (tmp);
+
+ if (G_UNLIKELY (!exists)) {
+ g_warning ("GsmSessionSave: could not create directory for saved session: %s", tmp);
+ g_free (tmp);
+ return NULL;
+ } else {
+ /* make sure it's empty */
+ GDir *dir;
+ const char *filename;
+
+ dir = g_dir_open (tmp, 0, NULL);
+ if (dir) {
+ while ((filename = g_dir_read_name (dir))) {
+ char *path = g_build_filename (tmp, filename,
+ NULL);
+ g_unlink (path);
+ }
+ g_dir_close (dir);
+ }
+ }
+
+ return tmp;
+}
+
+const gchar *
+gsm_util_get_saved_session_dir (void)
+{
+ if (_saved_session_dir == NULL) {
+ gboolean exists;
+
+ _saved_session_dir =
+ g_build_filename (g_get_user_config_dir (),
+ "gnome-session",
+ "saved-session",
+ NULL);
+
+ exists = ensure_dir_exists (_saved_session_dir);
+
+ if (G_UNLIKELY (!exists)) {
+ static gboolean printed_warning = FALSE;
+
+ if (!printed_warning) {
+ g_warning ("GsmSessionSave: could not create directory for saved session: %s", _saved_session_dir);
+ printed_warning = TRUE;
+ }
+
+ _saved_session_dir = NULL;
+
+ return NULL;
+ }
+ }
+
+ return _saved_session_dir;
+}
+
+static char ** autostart_dirs;
+
+void
+gsm_util_set_autostart_dirs (char ** dirs)
+{
+ autostart_dirs = g_strdupv (dirs);
+}
+
+static char **
+gsm_util_get_standard_autostart_dirs (void)
+{
+ GPtrArray *dirs;
+ const char * const *system_config_dirs;
+ const char * const *system_data_dirs;
+ int i;
+
+ dirs = g_ptr_array_new ();
+
+ g_ptr_array_add (dirs,
+ g_build_filename (g_get_user_config_dir (),
+ "autostart", NULL));
+
+ system_data_dirs = g_get_system_data_dirs ();
+ for (i = 0; system_data_dirs[i]; i++) {
+ g_ptr_array_add (dirs,
+ g_build_filename (system_data_dirs[i],
+ "gnome", "autostart", NULL));
+ }
+
+ system_config_dirs = g_get_system_config_dirs ();
+ for (i = 0; system_config_dirs[i]; i++) {
+ g_ptr_array_add (dirs,
+ g_build_filename (system_config_dirs[i],
+ "autostart", NULL));
+ }
+
+ g_ptr_array_add (dirs, NULL);
+
+ return (char **) g_ptr_array_free (dirs, FALSE);
+}
+
+char **
+gsm_util_get_autostart_dirs ()
+{
+ if (autostart_dirs) {
+ return g_strdupv ((char **)autostart_dirs);
+ }
+
+ return gsm_util_get_standard_autostart_dirs ();
+}
+
+char **
+gsm_util_get_app_dirs ()
+{
+ GPtrArray *dirs;
+ const char * const *system_data_dirs;
+ int i;
+
+ dirs = g_ptr_array_new ();
+
+ g_ptr_array_add (dirs,
+ g_build_filename (g_get_user_data_dir (),
+ "applications",
+ NULL));
+
+ system_data_dirs = g_get_system_data_dirs ();
+ for (i = 0; system_data_dirs[i]; i++) {
+ g_ptr_array_add (dirs,
+ g_build_filename (system_data_dirs[i],
+ "applications",
+ NULL));
+ }
+
+ g_ptr_array_add (dirs, NULL);
+
+ return (char **) g_ptr_array_free (dirs, FALSE);
+}
+
+char **
+gsm_util_get_desktop_dirs (gboolean include_saved_session,
+ gboolean autostart_first)
+{
+ char **apps;
+ char **autostart;
+ char **standard_autostart;
+ char **result;
+ int size;
+ int i;
+
+ apps = gsm_util_get_app_dirs ();
+ autostart = gsm_util_get_autostart_dirs ();
+
+ /* Still, check the standard autostart dirs for things like fulfilling session reqs,
+ * if using a non-standard autostart dir for autostarting */
+ if (autostart_dirs != NULL)
+ standard_autostart = gsm_util_get_standard_autostart_dirs ();
+ else
+ standard_autostart = NULL;
+
+ size = 0;
+ for (i = 0; apps[i] != NULL; i++) { size++; }
+ for (i = 0; autostart[i] != NULL; i++) { size++; }
+ if (autostart_dirs != NULL)
+ for (i = 0; standard_autostart[i] != NULL; i++) { size++; }
+ if (include_saved_session)
+ size += 1;
+
+ result = g_new (char *, size + 1); /* including last NULL */
+
+ size = 0;
+
+ if (autostart_first) {
+ if (include_saved_session)
+ result[size++] = g_strdup (gsm_util_get_saved_session_dir ());
+
+ for (i = 0; autostart[i] != NULL; i++, size++) {
+ result[size] = autostart[i];
+ }
+ if (standard_autostart != NULL) {
+ for (i = 0; standard_autostart[i] != NULL; i++, size++) {
+ result[size] = standard_autostart[i];
+ }
+ }
+ for (i = 0; apps[i] != NULL; i++, size++) {
+ result[size] = apps[i];
+ }
+ } else {
+ for (i = 0; apps[i] != NULL; i++, size++) {
+ result[size] = apps[i];
+ }
+ if (standard_autostart != NULL) {
+ for (i = 0; standard_autostart[i] != NULL; i++, size++) {
+ result[size] = standard_autostart[i];
+ }
+ }
+ for (i = 0; autostart[i] != NULL; i++, size++) {
+ result[size] = autostart[i];
+ }
+
+ if (include_saved_session)
+ result[size++] = g_strdup (gsm_util_get_saved_session_dir ());
+ }
+
+ g_free (apps);
+ g_free (autostart);
+ g_free (standard_autostart);
+
+ result[size] = NULL;
+
+ return result;
+}
+
+gboolean
+gsm_util_text_is_blank (const char *str)
+{
+ if (str == NULL) {
+ return TRUE;
+ }
+
+ while (*str) {
+ if (!isspace(*str)) {
+ return FALSE;
+ }
+
+ str++;
+ }
+
+ return TRUE;
+}
+
+/**
+ * gsm_util_init_error:
+ * @fatal: whether or not the error is fatal to the login session
+ * @format: printf-style error message format
+ * @...: error message args
+ *
+ * Displays the error message to the user. If @fatal is %TRUE, gsm
+ * will exit after displaying the message.
+ *
+ * This should be called for major errors that occur before the
+ * session is up and running. (Notably, it positions the dialog box
+ * itself, since no window manager will be running yet.)
+ **/
+void
+gsm_util_init_error (gboolean fatal,
+ const char *format, ...)
+{
+ char *msg;
+ va_list args;
+ gchar *argv[13];
+
+ va_start (args, format);
+ msg = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ argv[0] = "zenity";
+ argv[1] = "--error";
+ argv[2] = "--class";
+ argv[3] = "mutter-dialog";
+ argv[4] = "--title";
+ argv[5] = "\"\"";
+ argv[6] = "--text";
+ argv[7] = msg;
+ argv[8] = "--icon-name";
+ argv[9] = "face-sad-symbolic";
+ argv[10] = "--ok-label";
+ argv[11] = _("_Log out");
+ argv[12] = NULL;
+
+ g_spawn_sync (NULL, argv, child_environment, 0, NULL, NULL, NULL, NULL, NULL, NULL);
+
+ g_free (msg);
+
+ if (fatal) {
+ exit (1);
+ }
+}
+
+/**
+ * gsm_util_generate_startup_id:
+ *
+ * Generates a new SM client ID.
+ *
+ * Return value: an SM client ID.
+ **/
+char *
+gsm_util_generate_startup_id (void)
+{
+ static int sequence = -1;
+ static guint rand1 = 0;
+ static guint rand2 = 0;
+ static pid_t pid = 0;
+ struct timeval tv;
+
+ /* The XSMP spec defines the ID as:
+ *
+ * Version: "1"
+ * Address type and address:
+ * "1" + an IPv4 address as 8 hex digits
+ * "2" + a DECNET address as 12 hex digits
+ * "6" + an IPv6 address as 32 hex digits
+ * Time stamp: milliseconds since UNIX epoch as 13 decimal digits
+ * Process-ID type and process-ID:
+ * "1" + POSIX PID as 10 decimal digits
+ * Sequence number as 4 decimal digits
+ *
+ * XSMP client IDs are supposed to be globally unique: if
+ * SmsGenerateClientID() is unable to determine a network
+ * address for the machine, it gives up and returns %NULL.
+ * GNOME and KDE have traditionally used a fourth address
+ * format in this case:
+ * "0" + 16 random hex digits
+ *
+ * We don't even bother trying SmsGenerateClientID(), since the
+ * user's IP address is probably "192.168.1.*" anyway, so a random
+ * number is actually more likely to be globally unique.
+ */
+
+ if (!rand1) {
+ rand1 = g_random_int ();
+ rand2 = g_random_int ();
+ pid = getpid ();
+ }
+
+ sequence = (sequence + 1) % 10000;
+ gettimeofday (&tv, NULL);
+ return g_strdup_printf ("10%.04x%.04x%.10lu%.3u%.10lu%.4d",
+ rand1,
+ rand2,
+ (unsigned long) tv.tv_sec,
+ (unsigned) tv.tv_usec,
+ (unsigned long) pid,
+ sequence);
+}
+
+static gboolean
+gsm_util_update_activation_environment (const char *variable,
+ const char *value,
+ GError **error)
+{
+ GDBusConnection *connection;
+ gboolean environment_updated;
+ GVariantBuilder builder;
+ GVariant *reply;
+ GError *bus_error = NULL;
+
+ environment_updated = FALSE;
+ connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error);
+
+ if (connection == NULL) {
+ return FALSE;
+ }
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}"));
+ g_variant_builder_add (&builder, "{ss}", variable, value);
+
+ reply = g_dbus_connection_call_sync (connection,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ "UpdateActivationEnvironment",
+ g_variant_new ("(@a{ss})",
+ g_variant_builder_end (&builder)),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, &bus_error);
+
+ if (bus_error != NULL) {
+ g_propagate_error (error, bus_error);
+ } else {
+ environment_updated = TRUE;
+ g_variant_unref (reply);
+ }
+
+ g_clear_object (&connection);
+
+ return environment_updated;
+}
+
+gboolean
+gsm_util_export_activation_environment (GError **error)
+{
+
+ GDBusConnection *connection;
+ gboolean environment_updated = FALSE;
+ char **entry_names;
+ int i = 0;
+ GVariantBuilder builder;
+ GRegex *name_regex, *value_regex;
+ GVariant *reply;
+ GError *bus_error = NULL;
+
+ connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error);
+
+ if (connection == NULL) {
+ return FALSE;
+ }
+
+ name_regex = g_regex_new ("^[a-zA-Z_][a-zA-Z0-9_]*$", G_REGEX_OPTIMIZE, 0, error);
+
+ if (name_regex == NULL) {
+ return FALSE;
+ }
+
+ value_regex = g_regex_new ("^(?:[ \t\n]|[^[:cntrl:]])*$", G_REGEX_OPTIMIZE, 0, error);
+
+ if (value_regex == NULL) {
+ return FALSE;
+ }
+
+ if (child_environment == NULL) {
+ child_environment = g_listenv ();
+ }
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}"));
+ for (entry_names = g_listenv (); entry_names[i] != NULL; i++) {
+ const char *entry_name = entry_names[i];
+ const char *entry_value = g_getenv (entry_name);
+
+ if (g_strv_contains (variable_blacklist, entry_name))
+ continue;
+
+ if (!g_utf8_validate (entry_name, -1, NULL) ||
+ !g_regex_match (name_regex, entry_name, 0, NULL) ||
+ !g_utf8_validate (entry_value, -1, NULL) ||
+ !g_regex_match (value_regex, entry_value, 0, NULL)) {
+
+ g_message ("Environment variable is unsafe to export to dbus: %s", entry_name);
+ continue;
+ }
+
+ child_environment = g_environ_setenv (child_environment,
+ entry_name, entry_value,
+ TRUE);
+ g_variant_builder_add (&builder, "{ss}", entry_name, entry_value);
+ }
+ g_regex_unref (name_regex);
+ g_regex_unref (value_regex);
+
+ g_strfreev (entry_names);
+
+ reply = g_dbus_connection_call_sync (connection,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ "UpdateActivationEnvironment",
+ g_variant_new ("(@a{ss})",
+ g_variant_builder_end (&builder)),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, &bus_error);
+
+ if (bus_error != NULL) {
+ g_propagate_error (error, bus_error);
+ } else {
+ environment_updated = TRUE;
+ g_variant_unref (reply);
+ }
+
+ g_clear_object (&connection);
+
+ return environment_updated;
+}
+
+#ifdef HAVE_SYSTEMD
+gboolean
+gsm_util_export_user_environment (GError **error)
+{
+
+ GDBusConnection *connection;
+ gboolean environment_updated = FALSE;
+ char **entries;
+ int i = 0;
+ GVariantBuilder builder;
+ GRegex *regex;
+ GVariant *reply;
+ GError *bus_error = NULL;
+
+ connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error);
+
+ if (connection == NULL) {
+ return FALSE;
+ }
+
+ regex = g_regex_new ("^[a-zA-Z_][a-zA-Z0-9_]*=(?:[ \t\n]|[^[:cntrl:]])*$", G_REGEX_OPTIMIZE, 0, error);
+
+ if (regex == NULL) {
+ return FALSE;
+ }
+
+ entries = g_get_environ ();
+
+ for (i = 0; variable_blacklist[i] != NULL; i++)
+ entries = g_environ_unsetenv (entries, variable_blacklist[i]);
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("(asas)"));
+
+ g_variant_builder_open (&builder, G_VARIANT_TYPE ("as"));
+ for (i = 0; variable_unsetlist[i] != NULL; i++)
+ g_variant_builder_add (&builder, "s", variable_unsetlist[i]);
+ for (i = 0; variable_blacklist[i] != NULL; i++)
+ g_variant_builder_add (&builder, "s", variable_blacklist[i]);
+ g_variant_builder_close (&builder);
+
+ g_variant_builder_open (&builder, G_VARIANT_TYPE ("as"));
+ for (i = 0; entries[i] != NULL; i++) {
+ const char *entry = entries[i];
+
+ if (!g_utf8_validate (entry, -1, NULL) ||
+ !g_regex_match (regex, entry, 0, NULL)) {
+
+ g_message ("Environment entry is unsafe to upload into user environment: %s", entry);
+ continue;
+ }
+
+ g_variant_builder_add (&builder, "s", entry);
+ }
+ g_variant_builder_close (&builder);
+ g_regex_unref (regex);
+
+ g_strfreev (entries);
+
+ reply = g_dbus_connection_call_sync (connection,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "UnsetAndSetEnvironment",
+ g_variant_builder_end (&builder),
+ NULL,
+ G_DBUS_CALL_FLAGS_NO_AUTO_START,
+ -1, NULL, &bus_error);
+
+ if (bus_error != NULL) {
+ g_propagate_error (error, bus_error);
+ } else {
+ environment_updated = TRUE;
+ g_variant_unref (reply);
+ }
+
+ g_clear_object (&connection);
+
+ return environment_updated;
+}
+
+static gboolean
+gsm_util_update_user_environment (const char *variable,
+ const char *value,
+ GError **error)
+{
+ GDBusConnection *connection;
+ gboolean environment_updated;
+ char *entry;
+ GVariantBuilder builder;
+ GVariant *reply;
+ GError *bus_error = NULL;
+
+ environment_updated = FALSE;
+ connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error);
+
+ if (connection == NULL) {
+ return FALSE;
+ }
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
+ entry = g_strdup_printf ("%s=%s", variable, value);
+ g_variant_builder_add (&builder, "s", entry);
+ g_free (entry);
+
+ reply = g_dbus_connection_call_sync (connection,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "SetEnvironment",
+ g_variant_new ("(@as)",
+ g_variant_builder_end (&builder)),
+ NULL,
+ G_DBUS_CALL_FLAGS_NO_AUTO_START,
+ -1, NULL, &bus_error);
+
+ if (bus_error != NULL) {
+ g_propagate_error (error, bus_error);
+ } else {
+ environment_updated = TRUE;
+ g_variant_unref (reply);
+ }
+
+ g_clear_object (&connection);
+
+ return environment_updated;
+}
+
+gboolean
+gsm_util_start_systemd_unit (const char *unit,
+ const char *mode,
+ GError **error)
+{
+ g_autoptr(GDBusConnection) connection = NULL;
+ g_autoptr(GVariant) reply = NULL;
+ GError *bus_error = NULL;
+
+ connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error);
+
+ if (connection == NULL)
+ return FALSE;
+
+ reply = g_dbus_connection_call_sync (connection,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "StartUnit",
+ g_variant_new ("(ss)",
+ unit, mode),
+ NULL,
+ G_DBUS_CALL_FLAGS_NO_AUTO_START,
+ -1, NULL, &bus_error);
+
+ if (bus_error != NULL) {
+ g_propagate_error (error, bus_error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+gsm_util_systemd_reset_failed (GError **error)
+{
+ g_autoptr(GDBusConnection) connection = NULL;
+ g_autoptr(GVariant) reply = NULL;
+ GError *bus_error = NULL;
+
+ connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error);
+
+ if (connection == NULL)
+ return FALSE;
+
+ reply = g_dbus_connection_call_sync (connection,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "ResetFailed",
+ NULL,
+ NULL,
+ G_DBUS_CALL_FLAGS_NO_AUTO_START,
+ -1, NULL, &bus_error);
+
+ if (bus_error != NULL) {
+ g_propagate_error (error, bus_error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+#endif
+
+void
+gsm_util_setenv (const char *variable,
+ const char *value)
+{
+ GError *error = NULL;
+
+ if (child_environment == NULL)
+ child_environment = g_listenv ();
+
+ if (!value)
+ child_environment = g_environ_unsetenv (child_environment, variable);
+ else
+ child_environment = g_environ_setenv (child_environment, variable, value, TRUE);
+
+ /* If this fails it isn't fatal, it means some things like session
+ * management and keyring won't work in activated clients.
+ */
+ if (!gsm_util_update_activation_environment (variable, value, &error)) {
+ g_warning ("Could not make bus activated clients aware of %s=%s environment variable: %s", variable, value, error->message);
+ g_clear_error (&error);
+ }
+
+#ifdef HAVE_SYSTEMD
+ /* If this fails, the system user session won't get the updated environment
+ */
+ if (!gsm_util_update_user_environment (variable, value, &error)) {
+ g_debug ("Could not make systemd aware of %s=%s environment variable: %s", variable, value, error->message);
+ g_clear_error (&error);
+ }
+#endif
+}
+
+const char * const *
+gsm_util_listenv (void)
+{
+ return (const char * const *) child_environment;
+
+}
+
+const char * const *
+gsm_util_get_variable_blacklist (void)
+{
+ return variable_blacklist;
+}
diff --git a/gnome-session/gsm-util.h b/gnome-session/gsm-util.h
new file mode 100644
index 0000000..bc26a21
--- /dev/null
+++ b/gnome-session/gsm-util.h
@@ -0,0 +1,69 @@
+/* gsm-util.h
+ * Copyright (C) 2008 Lucas Rocha.
+ *
+ * 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/>.
+ */
+
+#ifndef __GSM_UTIL_H__
+#define __GSM_UTIL_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#define IS_STRING_EMPTY(x) ((x)==NULL||(x)[0]=='\0')
+
+char * gsm_util_find_desktop_file_for_app_name (const char *app_name,
+ gboolean look_in_saved_session,
+ gboolean autostart_first);
+
+gchar *gsm_util_get_empty_tmp_session_dir (void);
+
+const char *gsm_util_get_saved_session_dir (void);
+
+gchar** gsm_util_get_app_dirs (void);
+
+gchar** gsm_util_get_autostart_dirs (void);
+void gsm_util_set_autostart_dirs (char **dirs);
+
+gchar ** gsm_util_get_desktop_dirs (gboolean include_saved_session,
+ gboolean autostart_first);
+
+gboolean gsm_util_text_is_blank (const char *str);
+
+void gsm_util_init_error (gboolean fatal,
+ const char *format, ...) G_GNUC_PRINTF (2, 3);
+
+char * gsm_util_generate_startup_id (void);
+
+void gsm_util_setenv (const char *variable,
+ const char *value);
+const char * const * gsm_util_listenv (void);
+const char * const * gsm_util_get_variable_blacklist(void);
+
+gboolean gsm_util_export_activation_environment (GError **error);
+#ifdef HAVE_SYSTEMD
+gboolean gsm_util_export_user_environment (GError **error);
+gboolean gsm_util_start_systemd_unit (const char *unit,
+ const char *mode,
+ GError **error);
+gboolean gsm_util_systemd_reset_failed (GError **error);
+
+#endif
+
+void gsm_quit (void);
+
+G_END_DECLS
+
+#endif /* __GSM_UTIL_H__ */
diff --git a/gnome-session/gsm-xsmp-client.c b/gnome-session/gsm-xsmp-client.c
new file mode 100644
index 0000000..8a30926
--- /dev/null
+++ b/gnome-session/gsm-xsmp-client.c
@@ -0,0 +1,1359 @@
+/* -*- 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 <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+
+#include "gsm-xsmp-client.h"
+
+#include "gsm-util.h"
+#include "gsm-autostart-app.h"
+#include "gsm-icon-names.h"
+#include "gsm-manager.h"
+
+#define GsmDesktopFile "_GSM_DesktopFile"
+
+#define GSM_XSMP_CLIENT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSM_TYPE_XSMP_CLIENT, GsmXSMPClientPrivate))
+
+struct GsmXSMPClientPrivate
+{
+
+ SmsConn conn;
+ IceConn ice_connection;
+
+ guint watch_id;
+
+ char *description;
+ GPtrArray *props;
+
+ /* SaveYourself state */
+ int current_save_yourself;
+ int next_save_yourself;
+ guint next_save_yourself_allow_interact : 1;
+};
+
+enum {
+ PROP_0,
+ PROP_ICE_CONNECTION
+};
+
+enum {
+ REGISTER_REQUEST,
+ REGISTER_CONFIRMED,
+ LOGOUT_REQUEST,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (GsmXSMPClient, gsm_xsmp_client, GSM_TYPE_CLIENT)
+
+static gboolean
+client_iochannel_watch (GIOChannel *channel,
+ GIOCondition condition,
+ GsmXSMPClient *client)
+{
+ gboolean keep_going;
+
+ g_object_ref (client);
+ switch (IceProcessMessages (client->priv->ice_connection, NULL, NULL)) {
+ case IceProcessMessagesSuccess:
+ keep_going = TRUE;
+ break;
+
+ case IceProcessMessagesIOError:
+ g_debug ("GsmXSMPClient: IceProcessMessagesIOError on '%s'", client->priv->description);
+ gsm_client_set_status (GSM_CLIENT (client), GSM_CLIENT_FAILED);
+ /* Emitting "disconnected" will eventually cause
+ * IceCloseConnection() to be called.
+ */
+ gsm_client_disconnected (GSM_CLIENT (client));
+ keep_going = FALSE;
+ break;
+
+ case IceProcessMessagesConnectionClosed:
+ g_debug ("GsmXSMPClient: IceProcessMessagesConnectionClosed on '%s'",
+ client->priv->description);
+ client->priv->ice_connection = NULL;
+ keep_going = FALSE;
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+ g_object_unref (client);
+
+ return keep_going;
+}
+
+static SmProp *
+find_property (GsmXSMPClient *client,
+ const char *name,
+ int *index)
+{
+ SmProp *prop;
+ int i;
+
+ for (i = 0; i < client->priv->props->len; i++) {
+ prop = client->priv->props->pdata[i];
+
+ if (!strcmp (prop->name, name)) {
+ if (index) {
+ *index = i;
+ }
+ return prop;
+ }
+ }
+
+ return NULL;
+}
+
+static void
+set_description (GsmXSMPClient *client)
+{
+ SmProp *prop;
+ const char *id;
+
+ prop = find_property (client, SmProgram, NULL);
+ id = gsm_client_peek_startup_id (GSM_CLIENT (client));
+
+ g_free (client->priv->description);
+ if (prop) {
+ client->priv->description = g_strdup_printf ("%p [%.*s %s]",
+ client,
+ prop->vals[0].length,
+ (char *)prop->vals[0].value,
+ id);
+ } else if (id != NULL) {
+ client->priv->description = g_strdup_printf ("%p [%s]", client, id);
+ } else {
+ client->priv->description = g_strdup_printf ("%p", client);
+ }
+}
+
+static void
+setup_connection (GsmXSMPClient *client)
+{
+ GIOChannel *channel;
+ int fd;
+
+ g_debug ("GsmXSMPClient: Setting up new connection");
+
+ fd = IceConnectionNumber (client->priv->ice_connection);
+ fcntl (fd, F_SETFD, fcntl (fd, F_GETFD, 0) | FD_CLOEXEC);
+ channel = g_io_channel_unix_new (fd);
+ client->priv->watch_id = g_io_add_watch (channel,
+ G_IO_IN | G_IO_ERR,
+ (GIOFunc)client_iochannel_watch,
+ client);
+ g_io_channel_unref (channel);
+
+ set_description (client);
+
+ g_debug ("GsmXSMPClient: New client '%s'", client->priv->description);
+}
+
+static GObject *
+gsm_xsmp_client_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_properties)
+{
+ GsmXSMPClient *client;
+
+ client = GSM_XSMP_CLIENT (G_OBJECT_CLASS (gsm_xsmp_client_parent_class)->constructor (type,
+ n_construct_properties,
+ construct_properties));
+ setup_connection (client);
+
+ return G_OBJECT (client);
+}
+
+static void
+gsm_xsmp_client_init (GsmXSMPClient *client)
+{
+ client->priv = GSM_XSMP_CLIENT_GET_PRIVATE (client);
+
+ client->priv->props = g_ptr_array_new ();
+ client->priv->current_save_yourself = -1;
+ client->priv->next_save_yourself = -1;
+ client->priv->next_save_yourself_allow_interact = FALSE;
+}
+
+
+static void
+delete_property (GsmXSMPClient *client,
+ const char *name)
+{
+ int index;
+ SmProp *prop;
+
+ prop = find_property (client, name, &index);
+ if (!prop) {
+ return;
+ }
+
+#if 0
+ /* This is wrong anyway; we can't unconditionally run the current
+ * discard command; if this client corresponds to a GsmAppResumed,
+ * and the current discard command is identical to the app's
+ * discard_command, then we don't run the discard command now,
+ * because that would delete a saved state we may want to resume
+ * again later.
+ */
+ if (!strcmp (name, SmDiscardCommand)) {
+ gsm_client_run_discard (GSM_CLIENT (client));
+ }
+#endif
+
+ g_ptr_array_remove_index_fast (client->priv->props, index);
+ SmFreeProperty (prop);
+}
+
+
+static void
+debug_print_property (SmProp *prop)
+{
+ GString *tmp;
+ int i;
+
+ switch (prop->type[0]) {
+ case 'C': /* CARD8 */
+ g_debug ("GsmXSMPClient: %s = %d", prop->name, *(unsigned char *)prop->vals[0].value);
+ break;
+
+ case 'A': /* ARRAY8 */
+ g_debug ("GsmXSMPClient: %s = '%s'", prop->name, (char *)prop->vals[0].value);
+ break;
+
+ case 'L': /* LISTofARRAY8 */
+ tmp = g_string_new (NULL);
+ for (i = 0; i < prop->num_vals; i++) {
+ g_string_append_printf (tmp, "'%.*s' ", prop->vals[i].length,
+ (char *)prop->vals[i].value);
+ }
+ g_debug ("GsmXSMPClient: %s = %s", prop->name, tmp->str);
+ g_string_free (tmp, TRUE);
+ break;
+
+ default:
+ g_debug ("GsmXSMPClient: %s = ??? (%s)", prop->name, prop->type);
+ break;
+ }
+}
+
+
+static void
+set_properties_callback (SmsConn conn,
+ SmPointer manager_data,
+ int num_props,
+ SmProp **props)
+{
+ GsmXSMPClient *client = manager_data;
+ int i;
+
+ g_debug ("GsmXSMPClient: Set properties from client '%s'", client->priv->description);
+
+ for (i = 0; i < num_props; i++) {
+ delete_property (client, props[i]->name);
+ g_ptr_array_add (client->priv->props, props[i]);
+
+ debug_print_property (props[i]);
+
+ if (!strcmp (props[i]->name, SmProgram))
+ set_description (client);
+ }
+
+ free (props);
+
+}
+
+static void
+delete_properties_callback (SmsConn conn,
+ SmPointer manager_data,
+ int num_props,
+ char **prop_names)
+{
+ GsmXSMPClient *client = manager_data;
+ int i;
+
+ g_debug ("GsmXSMPClient: Delete properties from '%s'", client->priv->description);
+
+ for (i = 0; i < num_props; i++) {
+ delete_property (client, prop_names[i]);
+
+ g_debug (" %s", prop_names[i]);
+ }
+
+ free (prop_names);
+}
+
+static void
+get_properties_callback (SmsConn conn,
+ SmPointer manager_data)
+{
+ GsmXSMPClient *client = manager_data;
+
+ g_debug ("GsmXSMPClient: Get properties request from '%s'", client->priv->description);
+
+ SmsReturnProperties (conn,
+ client->priv->props->len,
+ (SmProp **)client->priv->props->pdata);
+}
+
+static char *
+prop_to_command (SmProp *prop)
+{
+ GString *str;
+ int i, j;
+ gboolean need_quotes;
+
+ str = g_string_new (NULL);
+ for (i = 0; i < prop->num_vals; i++) {
+ char *val = prop->vals[i].value;
+
+ need_quotes = FALSE;
+ for (j = 0; j < prop->vals[i].length; j++) {
+ if (!g_ascii_isalnum (val[j]) && !strchr ("-_=:./", val[j])) {
+ need_quotes = TRUE;
+ break;
+ }
+ }
+
+ if (i > 0) {
+ g_string_append_c (str, ' ');
+ }
+
+ if (!need_quotes) {
+ g_string_append_printf (str,
+ "%.*s",
+ prop->vals[i].length,
+ (char *)prop->vals[i].value);
+ } else {
+ g_string_append_c (str, '\'');
+ while (val < (char *)prop->vals[i].value + prop->vals[i].length) {
+ if (*val == '\'') {
+ g_string_append (str, "'\''");
+ } else {
+ g_string_append_c (str, *val);
+ }
+ val++;
+ }
+ g_string_append_c (str, '\'');
+ }
+ }
+
+ return g_string_free (str, FALSE);
+}
+
+static char *
+xsmp_get_restart_command (GsmClient *client)
+{
+ SmProp *prop;
+
+ prop = find_property (GSM_XSMP_CLIENT (client), SmRestartCommand, NULL);
+
+ if (!prop || strcmp (prop->type, SmLISTofARRAY8) != 0) {
+ return NULL;
+ }
+
+ return prop_to_command (prop);
+}
+
+static char *
+xsmp_get_discard_command (GsmClient *client)
+{
+ SmProp *prop;
+
+ prop = find_property (GSM_XSMP_CLIENT (client), SmDiscardCommand, NULL);
+
+ if (!prop || strcmp (prop->type, SmLISTofARRAY8) != 0) {
+ return NULL;
+ }
+
+ return prop_to_command (prop);
+}
+
+static void
+do_save_yourself (GsmXSMPClient *client,
+ int save_type,
+ gboolean allow_interact)
+{
+ g_assert (client->priv->conn != NULL);
+
+ if (client->priv->next_save_yourself != -1) {
+ /* Either we're currently doing a shutdown and there's a checkpoint
+ * queued after it, or vice versa. Either way, the new SaveYourself
+ * is redundant.
+ */
+ g_debug ("GsmXSMPClient: skipping redundant SaveYourself for '%s'",
+ client->priv->description);
+ } else if (client->priv->current_save_yourself != -1) {
+ g_debug ("GsmXSMPClient: queuing new SaveYourself for '%s'",
+ client->priv->description);
+ client->priv->next_save_yourself = save_type;
+ client->priv->next_save_yourself_allow_interact = allow_interact;
+ } else {
+ client->priv->current_save_yourself = save_type;
+ /* make sure we don't have anything queued */
+ client->priv->next_save_yourself = -1;
+ client->priv->next_save_yourself_allow_interact = FALSE;
+
+ switch (save_type) {
+ case SmSaveLocal:
+ /* Save state */
+ SmsSaveYourself (client->priv->conn,
+ SmSaveLocal,
+ FALSE,
+ SmInteractStyleNone,
+ FALSE);
+ break;
+
+ default:
+ /* Logout */
+ if (!allow_interact) {
+ SmsSaveYourself (client->priv->conn,
+ save_type, /* save type */
+ TRUE, /* shutdown */
+ SmInteractStyleNone, /* interact style */
+ TRUE); /* fast */
+ } else {
+ SmsSaveYourself (client->priv->conn,
+ save_type, /* save type */
+ TRUE, /* shutdown */
+ SmInteractStyleAny, /* interact style */
+ FALSE /* fast */);
+ }
+ break;
+ }
+ }
+}
+
+static void
+xsmp_save_yourself_phase2 (GsmClient *client)
+{
+ GsmXSMPClient *xsmp = (GsmXSMPClient *) client;
+
+ g_debug ("GsmXSMPClient: xsmp_save_yourself_phase2 ('%s')", xsmp->priv->description);
+
+ SmsSaveYourselfPhase2 (xsmp->priv->conn);
+}
+
+static void
+xsmp_interact (GsmClient *client)
+{
+ GsmXSMPClient *xsmp = (GsmXSMPClient *) client;
+
+ g_debug ("GsmXSMPClient: xsmp_interact ('%s')", xsmp->priv->description);
+
+ SmsInteract (xsmp->priv->conn);
+}
+
+static gboolean
+xsmp_cancel_end_session (GsmClient *client,
+ GError **error)
+{
+ GsmXSMPClient *xsmp = (GsmXSMPClient *) client;
+
+ g_debug ("GsmXSMPClient: xsmp_cancel_end_session ('%s')", xsmp->priv->description);
+
+ if (xsmp->priv->conn == NULL) {
+ g_set_error (error,
+ GSM_CLIENT_ERROR,
+ GSM_CLIENT_ERROR_NOT_REGISTERED,
+ "Client is not registered");
+ return FALSE;
+ }
+
+ SmsShutdownCancelled (xsmp->priv->conn);
+
+ /* reset the state */
+ xsmp->priv->current_save_yourself = -1;
+ xsmp->priv->next_save_yourself = -1;
+ xsmp->priv->next_save_yourself_allow_interact = FALSE;
+
+ return TRUE;
+}
+
+static char *
+get_desktop_file_path (GsmXSMPClient *client)
+{
+ SmProp *prop;
+ char *desktop_file_path = NULL;
+ const char *program_name;
+
+ /* XSMP clients using eggsmclient defines a special property
+ * pointing to their respective desktop entry file */
+ prop = find_property (client, GsmDesktopFile, NULL);
+
+ if (prop) {
+ GFile *file = g_file_new_for_uri (prop->vals[0].value);
+ desktop_file_path = g_file_get_path (file);
+ g_object_unref (file);
+ goto out;
+ }
+
+ /* If we can't get desktop file from GsmDesktopFile then we
+ * try to find the desktop file from its program name */
+ prop = find_property (client, SmProgram, NULL);
+
+ if (!prop) {
+ goto out;
+ }
+
+ program_name = prop->vals[0].value;
+ desktop_file_path =
+ gsm_util_find_desktop_file_for_app_name (program_name,
+ TRUE, FALSE);
+
+out:
+ g_debug ("GsmXSMPClient: desktop file for client %s is %s",
+ gsm_client_peek_id (GSM_CLIENT (client)),
+ desktop_file_path ? desktop_file_path : "(null)");
+
+ return desktop_file_path;
+}
+
+static void
+set_desktop_file_keys_from_client (GsmClient *client,
+ GKeyFile *keyfile)
+{
+ SmProp *prop;
+ const char *name;
+ char *comment;
+
+ prop = find_property (GSM_XSMP_CLIENT (client), SmProgram, NULL);
+ if (prop) {
+ name = prop->vals[0].value;
+ } else {
+ /* It'd be really surprising to reach this code: if we're here,
+ * then the XSMP client already has set several XSMP
+ * properties. But it could still be that SmProgram is not set.
+ */
+ name = _("Remembered Application");
+ }
+
+ comment = g_strdup_printf ("Client %s which was automatically saved",
+ gsm_client_peek_startup_id (client));
+
+ g_key_file_set_string (keyfile,
+ G_KEY_FILE_DESKTOP_GROUP,
+ G_KEY_FILE_DESKTOP_KEY_NAME,
+ name);
+
+ g_key_file_set_string (keyfile,
+ G_KEY_FILE_DESKTOP_GROUP,
+ G_KEY_FILE_DESKTOP_KEY_COMMENT,
+ comment);
+
+ g_key_file_set_string (keyfile,
+ G_KEY_FILE_DESKTOP_GROUP,
+ G_KEY_FILE_DESKTOP_KEY_ICON,
+ GSM_ICON_XSMP_DEFAULT);
+
+ g_key_file_set_string (keyfile,
+ G_KEY_FILE_DESKTOP_GROUP,
+ G_KEY_FILE_DESKTOP_KEY_TYPE,
+ "Application");
+
+ g_key_file_set_boolean (keyfile,
+ G_KEY_FILE_DESKTOP_GROUP,
+ G_KEY_FILE_DESKTOP_KEY_STARTUP_NOTIFY,
+ TRUE);
+
+ g_free (comment);
+}
+
+static GKeyFile *
+create_client_key_file (GsmClient *client,
+ const char *desktop_file_path,
+ GError **error) {
+ GKeyFile *keyfile;
+
+ keyfile = g_key_file_new ();
+
+ if (desktop_file_path != NULL) {
+ g_key_file_load_from_file (keyfile,
+ desktop_file_path,
+ G_KEY_FILE_KEEP_COMMENTS |
+ G_KEY_FILE_KEEP_TRANSLATIONS,
+ error);
+ } else {
+ set_desktop_file_keys_from_client (client, keyfile);
+ }
+
+ return keyfile;
+}
+
+static GsmClientRestartStyle
+xsmp_get_restart_style_hint (GsmClient *client);
+
+static GKeyFile *
+xsmp_save (GsmClient *client,
+ GsmApp *app,
+ GError **error)
+{
+ GsmClientRestartStyle restart_style;
+
+ GKeyFile *keyfile = NULL;
+ char *desktop_file_path = NULL;
+ char *exec_program = NULL;
+ char *exec_discard = NULL;
+ char *startup_id = NULL;
+ GError *local_error;
+
+ g_debug ("GsmXSMPClient: saving client with id %s",
+ gsm_client_peek_id (client));
+
+ local_error = NULL;
+
+ restart_style = xsmp_get_restart_style_hint (client);
+ if (restart_style == GSM_CLIENT_RESTART_NEVER) {
+ goto out;
+ }
+
+ exec_program = xsmp_get_restart_command (client);
+ if (!exec_program) {
+ goto out;
+ }
+
+ desktop_file_path = get_desktop_file_path (GSM_XSMP_CLIENT (client));
+
+ /* this can accept desktop_file_path == NULL */
+ keyfile = create_client_key_file (client,
+ desktop_file_path,
+ &local_error);
+
+ if (local_error) {
+ goto out;
+ }
+
+ g_object_get (client,
+ "startup-id", &startup_id,
+ NULL);
+
+ g_key_file_set_string (keyfile,
+ G_KEY_FILE_DESKTOP_GROUP,
+ GSM_AUTOSTART_APP_STARTUP_ID_KEY,
+ startup_id);
+
+ g_key_file_set_string (keyfile,
+ G_KEY_FILE_DESKTOP_GROUP,
+ G_KEY_FILE_DESKTOP_KEY_EXEC,
+ exec_program);
+
+ exec_discard = xsmp_get_discard_command (client);
+ if (exec_discard)
+ g_key_file_set_string (keyfile,
+ G_KEY_FILE_DESKTOP_GROUP,
+ GSM_AUTOSTART_APP_DISCARD_KEY,
+ exec_discard);
+
+ if (app != NULL) {
+ gsm_app_save_to_keyfile (app, keyfile, &local_error);
+
+ if (local_error) {
+ goto out;
+ }
+ }
+
+out:
+ g_free (desktop_file_path);
+ g_free (exec_program);
+ g_free (exec_discard);
+ g_free (startup_id);
+
+ if (local_error != NULL) {
+ g_propagate_error (error, local_error);
+ g_key_file_free (keyfile);
+
+ return NULL;
+ }
+
+ return keyfile;
+}
+
+static gboolean
+xsmp_stop (GsmClient *client,
+ GError **error)
+{
+ GsmXSMPClient *xsmp = (GsmXSMPClient *) client;
+
+ g_debug ("GsmXSMPClient: xsmp_stop ('%s')", xsmp->priv->description);
+
+ if (xsmp->priv->conn == NULL) {
+ g_set_error (error,
+ GSM_CLIENT_ERROR,
+ GSM_CLIENT_ERROR_NOT_REGISTERED,
+ "Client is not registered");
+ return FALSE;
+ }
+
+ SmsDie (xsmp->priv->conn);
+
+ return TRUE;
+}
+
+static gboolean
+xsmp_query_end_session (GsmClient *client,
+ GsmClientEndSessionFlag flags,
+ GError **error)
+{
+ gboolean allow_interact;
+ int save_type;
+
+ if (GSM_XSMP_CLIENT (client)->priv->conn == NULL) {
+ g_set_error (error,
+ GSM_CLIENT_ERROR,
+ GSM_CLIENT_ERROR_NOT_REGISTERED,
+ "Client is not registered");
+ return FALSE;
+ }
+
+ allow_interact = !(flags & GSM_CLIENT_END_SESSION_FLAG_FORCEFUL);
+
+ /* we don't want to save the session state, but we just want to know if
+ * there's user data the client has to save and we want to give the
+ * client a chance to tell the user about it. This is consistent with
+ * the manager not setting GSM_CLIENT_END_SESSION_FLAG_SAVE for this
+ * phase. */
+ save_type = SmSaveGlobal;
+
+ do_save_yourself (GSM_XSMP_CLIENT (client), save_type, allow_interact);
+ return TRUE;
+}
+
+static gboolean
+xsmp_end_session (GsmClient *client,
+ GsmClientEndSessionFlag flags,
+ GError **error)
+{
+ gboolean phase2;
+
+ if (GSM_XSMP_CLIENT (client)->priv->conn == NULL) {
+ g_set_error (error,
+ GSM_CLIENT_ERROR,
+ GSM_CLIENT_ERROR_NOT_REGISTERED,
+ "Client is not registered");
+ return FALSE;
+ }
+
+ phase2 = (flags & GSM_CLIENT_END_SESSION_FLAG_LAST);
+
+ if (phase2) {
+ xsmp_save_yourself_phase2 (client);
+ } else {
+ gboolean allow_interact;
+ int save_type;
+
+ /* we gave a chance to interact to the app during
+ * xsmp_query_end_session(), now it's too late to interact */
+ allow_interact = FALSE;
+
+ if (flags & GSM_CLIENT_END_SESSION_FLAG_SAVE) {
+ save_type = SmSaveBoth;
+ } else {
+ save_type = SmSaveGlobal;
+ }
+
+ do_save_yourself (GSM_XSMP_CLIENT (client),
+ save_type, allow_interact);
+ }
+
+ return TRUE;
+}
+
+static char *
+xsmp_get_app_name (GsmClient *client)
+{
+ SmProp *prop;
+ char *name = NULL;
+
+ prop = find_property (GSM_XSMP_CLIENT (client), SmProgram, NULL);
+ if (prop) {
+ name = prop_to_command (prop);
+ }
+
+ return name;
+}
+
+static void
+gsm_client_set_ice_connection (GsmXSMPClient *client,
+ gpointer conn)
+{
+ client->priv->ice_connection = conn;
+}
+
+static void
+gsm_xsmp_client_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GsmXSMPClient *self;
+
+ self = GSM_XSMP_CLIENT (object);
+
+ switch (prop_id) {
+ case PROP_ICE_CONNECTION:
+ gsm_client_set_ice_connection (self, g_value_get_pointer (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gsm_xsmp_client_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GsmXSMPClient *self;
+
+ self = GSM_XSMP_CLIENT (object);
+
+ switch (prop_id) {
+ case PROP_ICE_CONNECTION:
+ g_value_set_pointer (value, self->priv->ice_connection);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gsm_xsmp_client_disconnect (GsmXSMPClient *client)
+{
+ if (client->priv->watch_id > 0) {
+ g_source_remove (client->priv->watch_id);
+ }
+
+ if (client->priv->conn != NULL) {
+ SmsCleanUp (client->priv->conn);
+ }
+
+ if (client->priv->ice_connection != NULL) {
+ IceSetShutdownNegotiation (client->priv->ice_connection, FALSE);
+ IceCloseConnection (client->priv->ice_connection);
+ }
+}
+
+static void
+gsm_xsmp_client_finalize (GObject *object)
+{
+ GsmXSMPClient *client = (GsmXSMPClient *) object;
+
+ g_debug ("GsmXSMPClient: xsmp_finalize (%s)", client->priv->description);
+ gsm_xsmp_client_disconnect (client);
+
+ g_free (client->priv->description);
+ g_ptr_array_foreach (client->priv->props, (GFunc)SmFreeProperty, NULL);
+ g_ptr_array_free (client->priv->props, TRUE);
+
+ G_OBJECT_CLASS (gsm_xsmp_client_parent_class)->finalize (object);
+}
+
+static gboolean
+_boolean_handled_accumulator (GSignalInvocationHint *ihint,
+ GValue *return_accu,
+ const GValue *handler_return,
+ gpointer dummy)
+{
+ gboolean continue_emission;
+ gboolean signal_handled;
+
+ signal_handled = g_value_get_boolean (handler_return);
+ g_value_set_boolean (return_accu, signal_handled);
+ continue_emission = !signal_handled;
+
+ return continue_emission;
+}
+
+static GsmClientRestartStyle
+xsmp_get_restart_style_hint (GsmClient *client)
+{
+ SmProp *prop;
+ GsmClientRestartStyle hint;
+
+ g_debug ("GsmXSMPClient: getting restart style");
+ hint = GSM_CLIENT_RESTART_IF_RUNNING;
+
+ prop = find_property (GSM_XSMP_CLIENT (client), SmRestartStyleHint, NULL);
+
+ if (!prop || strcmp (prop->type, SmCARD8) != 0) {
+ return GSM_CLIENT_RESTART_IF_RUNNING;
+ }
+
+ switch (((unsigned char *)prop->vals[0].value)[0]) {
+ case SmRestartIfRunning:
+ hint = GSM_CLIENT_RESTART_IF_RUNNING;
+ break;
+ case SmRestartAnyway:
+ hint = GSM_CLIENT_RESTART_ANYWAY;
+ break;
+ case SmRestartImmediately:
+ hint = GSM_CLIENT_RESTART_IMMEDIATELY;
+ break;
+ case SmRestartNever:
+ hint = GSM_CLIENT_RESTART_NEVER;
+ break;
+ default:
+ break;
+ }
+
+ return hint;
+}
+
+static gboolean
+_parse_value_as_uint (const char *value,
+ guint *uintval)
+{
+ char *end_of_valid_uint;
+ gulong ulong_value;
+ guint uint_value;
+
+ errno = 0;
+ ulong_value = strtoul (value, &end_of_valid_uint, 10);
+
+ if (*value == '\0' || *end_of_valid_uint != '\0') {
+ return FALSE;
+ }
+
+ uint_value = ulong_value;
+ if (uint_value != ulong_value || errno == ERANGE) {
+ return FALSE;
+ }
+
+ *uintval = uint_value;
+
+ return TRUE;
+}
+
+static guint
+xsmp_get_unix_process_id (GsmClient *client)
+{
+ SmProp *prop;
+ guint pid;
+ gboolean res;
+
+ g_debug ("GsmXSMPClient: getting pid");
+
+ prop = find_property (GSM_XSMP_CLIENT (client), SmProcessID, NULL);
+
+ if (!prop || strcmp (prop->type, SmARRAY8) != 0) {
+ return 0;
+ }
+
+ pid = 0;
+ res = _parse_value_as_uint ((char *)prop->vals[0].value, &pid);
+ if (! res) {
+ pid = 0;
+ }
+
+ return pid;
+}
+
+static void
+gsm_xsmp_client_class_init (GsmXSMPClientClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GsmClientClass *client_class = GSM_CLIENT_CLASS (klass);
+
+ object_class->finalize = gsm_xsmp_client_finalize;
+ object_class->constructor = gsm_xsmp_client_constructor;
+ object_class->get_property = gsm_xsmp_client_get_property;
+ object_class->set_property = gsm_xsmp_client_set_property;
+
+ client_class->impl_save = xsmp_save;
+ client_class->impl_stop = xsmp_stop;
+ client_class->impl_query_end_session = xsmp_query_end_session;
+ client_class->impl_end_session = xsmp_end_session;
+ client_class->impl_cancel_end_session = xsmp_cancel_end_session;
+ client_class->impl_get_app_name = xsmp_get_app_name;
+ client_class->impl_get_restart_style_hint = xsmp_get_restart_style_hint;
+ client_class->impl_get_unix_process_id = xsmp_get_unix_process_id;
+
+ signals[REGISTER_REQUEST] =
+ g_signal_new ("register-request",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmXSMPClientClass, register_request),
+ _boolean_handled_accumulator,
+ NULL,
+ NULL,
+ G_TYPE_BOOLEAN,
+ 1, G_TYPE_POINTER);
+ signals[REGISTER_CONFIRMED] =
+ g_signal_new ("register-confirmed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmXSMPClientClass, register_confirmed),
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1, G_TYPE_POINTER);
+ signals[LOGOUT_REQUEST] =
+ g_signal_new ("logout-request",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmXSMPClientClass, logout_request),
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1, G_TYPE_BOOLEAN);
+
+ g_object_class_install_property (object_class,
+ PROP_ICE_CONNECTION,
+ g_param_spec_pointer ("ice-connection",
+ "ice-connection",
+ "ice-connection",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_type_class_add_private (klass, sizeof (GsmXSMPClientPrivate));
+}
+
+GsmClient *
+gsm_xsmp_client_new (IceConn ice_conn)
+{
+ GsmXSMPClient *xsmp;
+
+ xsmp = g_object_new (GSM_TYPE_XSMP_CLIENT,
+ "ice-connection", ice_conn,
+ NULL);
+
+ return GSM_CLIENT (xsmp);
+}
+
+static Status
+register_client_callback (SmsConn conn,
+ SmPointer manager_data,
+ char *previous_id)
+{
+ GsmXSMPClient *client = manager_data;
+ gboolean handled;
+ char *id;
+
+ g_debug ("GsmXSMPClient: Client '%s' received RegisterClient(%s)",
+ client->priv->description,
+ previous_id ? previous_id : "NULL");
+
+
+ /* There are three cases:
+ * 1. id is NULL - we'll use a new one
+ * 2. id is known - we'll use known one
+ * 3. id is unknown - this is an error
+ */
+ id = g_strdup (previous_id);
+
+ handled = FALSE;
+ g_signal_emit (client, signals[REGISTER_REQUEST], 0, &id, &handled);
+ if (! handled) {
+ g_debug ("GsmXSMPClient: RegisterClient not handled!");
+ g_free (id);
+ free (previous_id);
+ g_assert_not_reached ();
+ return FALSE;
+ }
+
+ if (IS_STRING_EMPTY (id)) {
+ g_debug ("GsmXSMPClient: rejected: invalid previous_id");
+ free (previous_id);
+ return FALSE;
+ }
+
+ g_object_set (client, "startup-id", id, NULL);
+
+ set_description (client);
+
+ g_debug ("GsmXSMPClient: Sending RegisterClientReply to '%s'", client->priv->description);
+
+ SmsRegisterClientReply (conn, id);
+
+ if (IS_STRING_EMPTY (previous_id)) {
+ /* Send the initial SaveYourself. */
+ g_debug ("GsmXSMPClient: Sending initial SaveYourself");
+ SmsSaveYourself (conn, SmSaveLocal, False, SmInteractStyleNone, False);
+ client->priv->current_save_yourself = SmSaveLocal;
+ }
+
+ gsm_client_set_status (GSM_CLIENT (client), GSM_CLIENT_REGISTERED);
+
+ g_signal_emit (client, signals[REGISTER_CONFIRMED], 0, id);
+
+ g_free (id);
+ free (previous_id);
+
+ return TRUE;
+}
+
+
+static void
+save_yourself_request_callback (SmsConn conn,
+ SmPointer manager_data,
+ int save_type,
+ Bool shutdown,
+ int interact_style,
+ Bool fast,
+ Bool global)
+{
+ GsmXSMPClient *client = manager_data;
+
+ g_debug ("GsmXSMPClient: Client '%s' received SaveYourselfRequest(%s, %s, %s, %s, %s)",
+ client->priv->description,
+ save_type == SmSaveLocal ? "SmSaveLocal" :
+ save_type == SmSaveGlobal ? "SmSaveGlobal" : "SmSaveBoth",
+ shutdown ? "Shutdown" : "!Shutdown",
+ interact_style == SmInteractStyleAny ? "SmInteractStyleAny" :
+ interact_style == SmInteractStyleErrors ? "SmInteractStyleErrors" :
+ "SmInteractStyleNone", fast ? "Fast" : "!Fast",
+ global ? "Global" : "!Global");
+
+ /* Examining the g_debug above, you can see that there are a total
+ * of 72 different combinations of options that this could have been
+ * called with. However, most of them are stupid.
+ *
+ * If @shutdown and @global are both TRUE, that means the caller is
+ * requesting that a logout message be sent to all clients, so we do
+ * that. We use @fast to decide whether or not to show a
+ * confirmation dialog. (This isn't really what @fast is for, but
+ * the old gnome-session and ksmserver both interpret it that way,
+ * so we do too.) We ignore @save_type because we pick the correct
+ * save_type ourselves later based on user prefs, dialog choices,
+ * etc, and we ignore @interact_style, because clients have not used
+ * it correctly consistently enough to make it worth honoring.
+ *
+ * If @shutdown is TRUE and @global is FALSE, the caller is
+ * confused, so we ignore the request.
+ *
+ * If @shutdown is FALSE and @save_type is SmSaveGlobal or
+ * SmSaveBoth, then the client wants us to ask some or all open
+ * applications to save open files to disk, but NOT quit. This is
+ * silly and so we ignore the request.
+ *
+ * If @shutdown is FALSE and @save_type is SmSaveLocal, then the
+ * client wants us to ask some or all open applications to update
+ * their current saved state, but not log out. At the moment, the
+ * code only supports this for the !global case (ie, a client
+ * requesting that it be allowed to update *its own* saved state,
+ * but not having everyone else update their saved state).
+ */
+
+ if (shutdown && global) {
+ g_debug ("GsmXSMPClient: initiating shutdown");
+ g_signal_emit (client, signals[LOGOUT_REQUEST], 0, !fast);
+ } else if (!shutdown && !global) {
+ g_debug ("GsmXSMPClient: initiating checkpoint");
+ do_save_yourself (client, SmSaveLocal, TRUE);
+ } else {
+ g_debug ("GsmXSMPClient: ignoring");
+ }
+}
+
+static void
+save_yourself_phase2_request_callback (SmsConn conn,
+ SmPointer manager_data)
+{
+ GsmXSMPClient *client = manager_data;
+
+ g_debug ("GsmXSMPClient: Client '%s' received SaveYourselfPhase2Request",
+ client->priv->description);
+
+ client->priv->current_save_yourself = -1;
+
+ /* this is a valid response to SaveYourself and therefore
+ may be a response to a QES or ES */
+ gsm_client_end_session_response (GSM_CLIENT (client),
+ TRUE, TRUE, FALSE,
+ NULL);
+}
+
+static void
+interact_request_callback (SmsConn conn,
+ SmPointer manager_data,
+ int dialog_type)
+{
+ GsmXSMPClient *client = manager_data;
+#if 0
+ gboolean res;
+ GError *error;
+#endif
+
+ g_debug ("GsmXSMPClient: Client '%s' received InteractRequest(%s)",
+ client->priv->description,
+ dialog_type == SmDialogNormal ? "Dialog" : "Errors");
+
+ gsm_client_end_session_response (GSM_CLIENT (client),
+ FALSE, FALSE, FALSE,
+ _("This program is blocking logout."));
+
+#if 0
+ /* Can't just call back with Interact because session client
+ grabs the keyboard! So, we try to get it to release
+ grabs by telling it we've cancelled the shutdown.
+ This grabbing is clearly bullshit and is not supported by
+ the client spec or protocol spec.
+ */
+ res = xsmp_cancel_end_session (GSM_CLIENT (client), &error);
+ if (! res) {
+ g_warning ("Unable to cancel end session: %s", error->message);
+ g_error_free (error);
+ }
+#endif
+ xsmp_interact (GSM_CLIENT (client));
+}
+
+static void
+interact_done_callback (SmsConn conn,
+ SmPointer manager_data,
+ Bool cancel_shutdown)
+{
+ GsmXSMPClient *client = manager_data;
+
+ g_debug ("GsmXSMPClient: Client '%s' received InteractDone(cancel_shutdown = %s)",
+ client->priv->description,
+ cancel_shutdown ? "True" : "False");
+
+ gsm_client_end_session_response (GSM_CLIENT (client),
+ TRUE, FALSE, cancel_shutdown,
+ NULL);
+}
+
+static void
+save_yourself_done_callback (SmsConn conn,
+ SmPointer manager_data,
+ Bool success)
+{
+ GsmXSMPClient *client = manager_data;
+
+ g_debug ("GsmXSMPClient: Client '%s' received SaveYourselfDone(success = %s)",
+ client->priv->description,
+ success ? "True" : "False");
+
+ if (client->priv->current_save_yourself != -1) {
+ SmsSaveComplete (client->priv->conn);
+ client->priv->current_save_yourself = -1;
+ }
+
+ /* If success is false then the application couldn't save data. Nothing
+ * the session manager can do about, though. FIXME: we could display a
+ * dialog about this, I guess. */
+ gsm_client_end_session_response (GSM_CLIENT (client),
+ TRUE, FALSE, FALSE,
+ NULL);
+
+ if (client->priv->next_save_yourself) {
+ int save_type = client->priv->next_save_yourself;
+ gboolean allow_interact = client->priv->next_save_yourself_allow_interact;
+
+ client->priv->next_save_yourself = -1;
+ client->priv->next_save_yourself_allow_interact = -1;
+ do_save_yourself (client, save_type, allow_interact);
+ }
+}
+
+static void
+close_connection_callback (SmsConn conn,
+ SmPointer manager_data,
+ int count,
+ char **reason_msgs)
+{
+ GsmXSMPClient *client = manager_data;
+ int i;
+
+ g_debug ("GsmXSMPClient: Client '%s' received CloseConnection", client->priv->description);
+ for (i = 0; i < count; i++) {
+ g_debug ("GsmXSMPClient: close reason: '%s'", reason_msgs[i]);
+ }
+ SmFreeReasons (count, reason_msgs);
+
+ gsm_client_set_status (GSM_CLIENT (client), GSM_CLIENT_FINISHED);
+ gsm_client_disconnected (GSM_CLIENT (client));
+}
+
+void
+gsm_xsmp_client_connect (GsmXSMPClient *client,
+ SmsConn conn,
+ unsigned long *mask_ret,
+ SmsCallbacks *callbacks_ret)
+{
+ client->priv->conn = conn;
+
+ g_debug ("GsmXSMPClient: Initializing client %s", client->priv->description);
+
+ *mask_ret = 0;
+
+ *mask_ret |= SmsRegisterClientProcMask;
+ callbacks_ret->register_client.callback = register_client_callback;
+ callbacks_ret->register_client.manager_data = client;
+
+ *mask_ret |= SmsInteractRequestProcMask;
+ callbacks_ret->interact_request.callback = interact_request_callback;
+ callbacks_ret->interact_request.manager_data = client;
+
+ *mask_ret |= SmsInteractDoneProcMask;
+ callbacks_ret->interact_done.callback = interact_done_callback;
+ callbacks_ret->interact_done.manager_data = client;
+
+ *mask_ret |= SmsSaveYourselfRequestProcMask;
+ callbacks_ret->save_yourself_request.callback = save_yourself_request_callback;
+ callbacks_ret->save_yourself_request.manager_data = client;
+
+ *mask_ret |= SmsSaveYourselfP2RequestProcMask;
+ callbacks_ret->save_yourself_phase2_request.callback = save_yourself_phase2_request_callback;
+ callbacks_ret->save_yourself_phase2_request.manager_data = client;
+
+ *mask_ret |= SmsSaveYourselfDoneProcMask;
+ callbacks_ret->save_yourself_done.callback = save_yourself_done_callback;
+ callbacks_ret->save_yourself_done.manager_data = client;
+
+ *mask_ret |= SmsCloseConnectionProcMask;
+ callbacks_ret->close_connection.callback = close_connection_callback;
+ callbacks_ret->close_connection.manager_data = client;
+
+ *mask_ret |= SmsSetPropertiesProcMask;
+ callbacks_ret->set_properties.callback = set_properties_callback;
+ callbacks_ret->set_properties.manager_data = client;
+
+ *mask_ret |= SmsDeletePropertiesProcMask;
+ callbacks_ret->delete_properties.callback = delete_properties_callback;
+ callbacks_ret->delete_properties.manager_data = client;
+
+ *mask_ret |= SmsGetPropertiesProcMask;
+ callbacks_ret->get_properties.callback = get_properties_callback;
+ callbacks_ret->get_properties.manager_data = client;
+}
+
+void
+gsm_xsmp_client_save_state (GsmXSMPClient *client)
+{
+ g_return_if_fail (GSM_IS_XSMP_CLIENT (client));
+}
diff --git a/gnome-session/gsm-xsmp-client.h b/gnome-session/gsm-xsmp-client.h
new file mode 100644
index 0000000..1bb2797
--- /dev/null
+++ b/gnome-session/gsm-xsmp-client.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 Novell, 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/>.
+ */
+
+#ifndef __GSM_XSMP_CLIENT_H__
+#define __GSM_XSMP_CLIENT_H__
+
+#include "gsm-client.h"
+
+#include <X11/SM/SMlib.h>
+
+G_BEGIN_DECLS
+
+#define GSM_TYPE_XSMP_CLIENT (gsm_xsmp_client_get_type ())
+#define GSM_XSMP_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_XSMP_CLIENT, GsmXSMPClient))
+#define GSM_XSMP_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_XSMP_CLIENT, GsmXSMPClientClass))
+#define GSM_IS_XSMP_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_XSMP_CLIENT))
+#define GSM_IS_XSMP_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_XSMP_CLIENT))
+#define GSM_XSMP_CLIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSM_TYPE_XSMP_CLIENT, GsmXSMPClientClass))
+
+typedef struct _GsmXSMPClient GsmXSMPClient;
+typedef struct _GsmXSMPClientClass GsmXSMPClientClass;
+
+typedef struct GsmXSMPClientPrivate GsmXSMPClientPrivate;
+
+struct _GsmXSMPClient
+{
+ GsmClient parent;
+ GsmXSMPClientPrivate *priv;
+};
+
+struct _GsmXSMPClientClass
+{
+ GsmClientClass parent_class;
+
+ /* signals */
+ gboolean (*register_request) (GsmXSMPClient *client,
+ char **client_id);
+ void (*register_confirmed) (GsmXSMPClient *client,
+ const char *client_id);
+ gboolean (*logout_request) (GsmXSMPClient *client,
+ gboolean prompt);
+
+
+ void (*saved_state) (GsmXSMPClient *client);
+
+ void (*request_phase2) (GsmXSMPClient *client);
+
+ void (*request_interaction) (GsmXSMPClient *client);
+ void (*interaction_done) (GsmXSMPClient *client,
+ gboolean cancel_shutdown);
+
+ void (*save_yourself_done) (GsmXSMPClient *client);
+
+};
+
+GType gsm_xsmp_client_get_type (void) G_GNUC_CONST;
+
+GsmClient *gsm_xsmp_client_new (IceConn ice_conn);
+
+void gsm_xsmp_client_connect (GsmXSMPClient *client,
+ SmsConn conn,
+ unsigned long *mask_ret,
+ SmsCallbacks *callbacks_ret);
+
+void gsm_xsmp_client_save_state (GsmXSMPClient *client);
+void gsm_xsmp_client_save_yourself (GsmXSMPClient *client,
+ gboolean save_state);
+void gsm_xsmp_client_save_yourself_phase2 (GsmXSMPClient *client);
+void gsm_xsmp_client_interact (GsmXSMPClient *client);
+void gsm_xsmp_client_shutdown_cancelled (GsmXSMPClient *client);
+
+G_END_DECLS
+
+#endif /* __GSM_XSMP_CLIENT_H__ */
diff --git a/gnome-session/gsm-xsmp-server.c b/gnome-session/gsm-xsmp-server.c
new file mode 100644
index 0000000..e05c6d7
--- /dev/null
+++ b/gnome-session/gsm-xsmp-server.c
@@ -0,0 +1,746 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 Novell, Inc.
+ * Copyright (C) 2008 Red Hat, Inc.
+ * Copyright (C) 2008 William Jon McCann <jmccann@redhat.com>
+ *
+ * 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 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 <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib-object.h>
+
+#include <X11/ICE/ICElib.h>
+#include <X11/ICE/ICEutil.h>
+#include <X11/ICE/ICEconn.h>
+#include <X11/SM/SMlib.h>
+
+/* Get the proto for _IceTransNoListen */
+#define ICE_t
+#define TRANS_SERVER
+#include <X11/Xtrans/Xtrans.h>
+#undef ICE_t
+#undef TRANS_SERVER
+
+#include "gsm-xsmp-server.h"
+#include "gsm-xsmp-client.h"
+#include "gsm-util.h"
+
+/* ICEauthority stuff */
+/* Various magic numbers stolen from iceauth.c */
+#define GSM_ICE_AUTH_RETRIES 10
+#define GSM_ICE_AUTH_INTERVAL 2 /* 2 seconds */
+#define GSM_ICE_AUTH_LOCK_TIMEOUT 600 /* 10 minutes */
+
+#define GSM_ICE_MAGIC_COOKIE_AUTH_NAME "MIT-MAGIC-COOKIE-1"
+#define GSM_ICE_MAGIC_COOKIE_LEN 16
+
+#define GSM_XSMP_SERVER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSM_TYPE_XSMP_SERVER, GsmXsmpServerPrivate))
+
+struct _GsmXsmpServer
+{
+ GObject parent_instance;
+
+ GsmStore *client_store;
+
+ IceListenObj *xsmp_sockets;
+ int num_xsmp_sockets;
+ int num_local_xsmp_sockets;
+ gboolean stopping;
+};
+
+enum {
+ PROP_0,
+ PROP_CLIENT_STORE
+};
+
+static void gsm_xsmp_server_class_init (GsmXsmpServerClass *klass);
+static void gsm_xsmp_server_init (GsmXsmpServer *xsmp_server);
+static void gsm_xsmp_server_finalize (GObject *object);
+
+static gpointer xsmp_server_object = NULL;
+
+G_DEFINE_TYPE (GsmXsmpServer, gsm_xsmp_server, G_TYPE_OBJECT)
+
+typedef struct {
+ GsmXsmpServer *server;
+ IceListenObj listener;
+} GsmIceConnectionData;
+
+typedef struct {
+ guint watch_id;
+ guint protocol_timeout;
+} GsmIceConnectionWatch;
+
+static void
+disconnect_ice_connection (IceConn ice_conn)
+{
+ IceSetShutdownNegotiation (ice_conn, FALSE);
+ IceCloseConnection (ice_conn);
+}
+
+static void
+free_ice_connection_watch (GsmIceConnectionWatch *data)
+{
+ if (data->watch_id) {
+ g_source_remove (data->watch_id);
+ data->watch_id = 0;
+ }
+
+ if (data->protocol_timeout) {
+ g_source_remove (data->protocol_timeout);
+ data->protocol_timeout = 0;
+ }
+
+ g_free (data);
+}
+
+static gboolean
+ice_protocol_timeout (IceConn ice_conn)
+{
+ GsmIceConnectionWatch *data;
+
+ g_debug ("GsmXsmpServer: ice_protocol_timeout for IceConn %p with status %d",
+ ice_conn, IceConnectionStatus (ice_conn));
+
+ data = ice_conn->context;
+
+ free_ice_connection_watch (data);
+ disconnect_ice_connection (ice_conn);
+
+ return FALSE;
+}
+
+static gboolean
+auth_iochannel_watch (GIOChannel *source,
+ GIOCondition condition,
+ IceConn ice_conn)
+{
+
+ GsmIceConnectionWatch *data;
+ gboolean keep_going;
+
+ data = ice_conn->context;
+
+ switch (IceProcessMessages (ice_conn, NULL, NULL)) {
+ case IceProcessMessagesSuccess:
+ keep_going = TRUE;
+ break;
+ case IceProcessMessagesIOError:
+ g_debug ("GsmXsmpServer: IceProcessMessages returned IceProcessMessagesIOError");
+ free_ice_connection_watch (data);
+ disconnect_ice_connection (ice_conn);
+ keep_going = FALSE;
+ break;
+ case IceProcessMessagesConnectionClosed:
+ g_debug ("GsmXsmpServer: IceProcessMessages returned IceProcessMessagesConnectionClosed");
+ free_ice_connection_watch (data);
+ keep_going = FALSE;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return keep_going;
+}
+
+/* IceAcceptConnection returns a new ICE connection that is in a "pending" state,
+ * this is because authentification may be necessary.
+ * So we've to authenticate it, before accept_xsmp_connection() is called.
+ * Then each GsmXSMPClient will have its own IceConn watcher
+ */
+static void
+auth_ice_connection (IceConn ice_conn)
+{
+ GIOChannel *channel;
+ GsmIceConnectionWatch *data;
+ int fd;
+
+ g_debug ("GsmXsmpServer: auth_ice_connection()");
+
+ fd = IceConnectionNumber (ice_conn);
+ fcntl (fd, F_SETFD, fcntl (fd, F_GETFD, 0) | FD_CLOEXEC);
+ channel = g_io_channel_unix_new (fd);
+
+ data = g_new0 (GsmIceConnectionWatch, 1);
+ ice_conn->context = data;
+
+ data->protocol_timeout = g_timeout_add_seconds (5,
+ (GSourceFunc)ice_protocol_timeout,
+ ice_conn);
+ data->watch_id = g_io_add_watch (channel,
+ G_IO_IN | G_IO_ERR,
+ (GIOFunc)auth_iochannel_watch,
+ ice_conn);
+ g_io_channel_unref (channel);
+}
+
+/* This is called (by glib via xsmp->ice_connection_watch) when a
+ * connection is first received on the ICE listening socket.
+ */
+static gboolean
+accept_ice_connection (GIOChannel *source,
+ GIOCondition condition,
+ GsmIceConnectionData *data)
+{
+ IceConn ice_conn;
+ IceAcceptStatus status;
+
+ g_debug ("GsmXsmpServer: accept_ice_connection()");
+
+ ice_conn = IceAcceptConnection (data->listener, &status);
+ if (status != IceAcceptSuccess) {
+ g_debug ("GsmXsmpServer: IceAcceptConnection returned %d", status);
+ return TRUE;
+ }
+
+ auth_ice_connection (ice_conn);
+
+ return TRUE;
+}
+
+void
+gsm_xsmp_server_start (GsmXsmpServer *server)
+{
+ GIOChannel *channel;
+ int i;
+
+ for (i = 0; i < server->num_local_xsmp_sockets; i++) {
+ GsmIceConnectionData *data;
+
+ data = g_new0 (GsmIceConnectionData, 1);
+ data->server = server;
+ data->listener = server->xsmp_sockets[i];
+
+ channel = g_io_channel_unix_new (IceGetListenConnectionNumber (server->xsmp_sockets[i]));
+ g_io_add_watch_full (channel,
+ G_PRIORITY_DEFAULT,
+ G_IO_IN | G_IO_HUP | G_IO_ERR,
+ (GIOFunc)accept_ice_connection,
+ data,
+ (GDestroyNotify)g_free);
+ g_io_channel_unref (channel);
+ }
+}
+
+void
+gsm_xsmp_server_stop_accepting_new_clients (GsmXsmpServer *server)
+{
+ g_return_if_fail (GSM_IS_XSMP_SERVER (server));
+ g_debug ("gsm_xsmp_server_stop_accepting_new_clients");
+ server->stopping = TRUE;
+}
+
+void
+gsm_xsmp_server_start_accepting_new_clients (GsmXsmpServer *server)
+{
+ g_return_if_fail (GSM_IS_XSMP_SERVER (server));
+ g_debug ("gsm_xsmp_server_start");
+ server->stopping = FALSE;
+}
+
+static void
+gsm_xsmp_server_set_client_store (GsmXsmpServer *xsmp_server,
+ GsmStore *store)
+{
+ g_return_if_fail (GSM_IS_XSMP_SERVER (xsmp_server));
+
+ if (store != NULL) {
+ g_object_ref (store);
+ }
+
+ if (xsmp_server->client_store != NULL) {
+ g_object_unref (xsmp_server->client_store);
+ }
+
+ xsmp_server->client_store = store;
+}
+
+static void
+gsm_xsmp_server_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GsmXsmpServer *self;
+
+ self = GSM_XSMP_SERVER (object);
+
+ switch (prop_id) {
+ case PROP_CLIENT_STORE:
+ gsm_xsmp_server_set_client_store (self, g_value_get_object (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gsm_xsmp_server_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GsmXsmpServer *self;
+
+ self = GSM_XSMP_SERVER (object);
+
+ switch (prop_id) {
+ case PROP_CLIENT_STORE:
+ g_value_set_object (value, self->client_store);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+/* This is called (by libSM) when XSMP is initiated on an ICE
+ * connection that was already accepted by accept_ice_connection.
+ */
+static Status
+accept_xsmp_connection (SmsConn sms_conn,
+ GsmXsmpServer *server,
+ unsigned long *mask_ret,
+ SmsCallbacks *callbacks_ret,
+ char **failure_reason_ret)
+{
+ IceConn ice_conn;
+ GsmClient *client;
+ GsmIceConnectionWatch *data;
+
+ if (server->stopping) {
+ g_debug ("GsmXsmpServer: In shutdown, rejecting new client");
+
+ *failure_reason_ret = strdup (_("Refusing new client connection because the session is currently being shut down\n"));
+ return FALSE;
+ }
+
+ ice_conn = SmsGetIceConnection (sms_conn);
+ data = ice_conn->context;
+
+ /* Each GsmXSMPClient has its own IceConn watcher */
+ free_ice_connection_watch (data);
+
+ client = gsm_xsmp_client_new (ice_conn);
+
+ gsm_store_add (server->client_store, gsm_client_peek_id (client), G_OBJECT (client));
+ /* the store will own the ref */
+ g_object_unref (client);
+
+ gsm_xsmp_client_connect (GSM_XSMP_CLIENT (client), sms_conn, mask_ret, callbacks_ret);
+
+ return TRUE;
+}
+
+static void
+ice_error_handler (IceConn conn,
+ Bool swap,
+ int offending_minor_opcode,
+ unsigned long offending_sequence,
+ int error_class,
+ int severity,
+ IcePointer values)
+{
+ g_debug ("GsmXsmpServer: ice_error_handler (%p, %s, %d, %lx, %d, %d)",
+ conn, swap ? "TRUE" : "FALSE", offending_minor_opcode,
+ offending_sequence, error_class, severity);
+
+ if (severity == IceCanContinue) {
+ return;
+ }
+
+ /* FIXME: the ICElib docs are completely vague about what we're
+ * supposed to do in this case. Need to verify that calling
+ * IceCloseConnection() here is guaranteed to cause neither
+ * free-memory-reads nor leaks.
+ */
+ IceCloseConnection (conn);
+}
+
+static void
+ice_io_error_handler (IceConn conn)
+{
+ g_debug ("GsmXsmpServer: ice_io_error_handler (%p)", conn);
+
+ /* We don't need to do anything here; the next call to
+ * IceProcessMessages() for this connection will receive
+ * IceProcessMessagesIOError and we can handle the error there.
+ */
+}
+
+static void
+sms_error_handler (SmsConn conn,
+ Bool swap,
+ int offending_minor_opcode,
+ unsigned long offending_sequence_num,
+ int error_class,
+ int severity,
+ IcePointer values)
+{
+ g_debug ("GsmXsmpServer: sms_error_handler (%p, %s, %d, %lx, %d, %d)",
+ conn, swap ? "TRUE" : "FALSE", offending_minor_opcode,
+ offending_sequence_num, error_class, severity);
+
+ /* We don't need to do anything here; if the connection needs to be
+ * closed, libSM will do that itself.
+ */
+}
+
+static IceAuthFileEntry *
+auth_entry_new (const char *protocol,
+ const char *network_id)
+{
+ IceAuthFileEntry *file_entry;
+ IceAuthDataEntry data_entry;
+
+ file_entry = malloc (sizeof (IceAuthFileEntry));
+
+ file_entry->protocol_name = strdup (protocol);
+ file_entry->protocol_data = NULL;
+ file_entry->protocol_data_length = 0;
+ file_entry->network_id = strdup (network_id);
+ file_entry->auth_name = strdup (GSM_ICE_MAGIC_COOKIE_AUTH_NAME);
+ file_entry->auth_data = IceGenerateMagicCookie (GSM_ICE_MAGIC_COOKIE_LEN);
+ file_entry->auth_data_length = GSM_ICE_MAGIC_COOKIE_LEN;
+
+ /* Also create an in-memory copy, which is what the server will
+ * actually use for checking client auth.
+ */
+ data_entry.protocol_name = file_entry->protocol_name;
+ data_entry.network_id = file_entry->network_id;
+ data_entry.auth_name = file_entry->auth_name;
+ data_entry.auth_data = file_entry->auth_data;
+ data_entry.auth_data_length = file_entry->auth_data_length;
+ IceSetPaAuthData (1, &data_entry);
+
+ return file_entry;
+}
+
+static gboolean
+update_iceauthority (GsmXsmpServer *server,
+ gboolean adding)
+{
+ char *filename;
+ char **our_network_ids;
+ FILE *fp;
+ IceAuthFileEntry *auth_entry;
+ GSList *entries;
+ GSList *e;
+ int i;
+ int ret;
+ gboolean ok = FALSE;
+
+ filename = IceAuthFileName ();
+ do {
+ ret = IceLockAuthFile (filename,
+ GSM_ICE_AUTH_RETRIES,
+ GSM_ICE_AUTH_INTERVAL,
+ GSM_ICE_AUTH_LOCK_TIMEOUT);
+
+ } while (ret != IceAuthLockSuccess && errno == EINTR);
+
+ if (ret != IceAuthLockSuccess) {
+ g_warning ("IceLockAuthFile failed: %m");
+ return FALSE;
+ }
+
+ our_network_ids = g_malloc (server->num_local_xsmp_sockets * sizeof (char *));
+ for (i = 0; i < server->num_local_xsmp_sockets; i++) {
+ our_network_ids[i] = IceGetListenConnectionString (server->xsmp_sockets[i]);
+ }
+
+ entries = NULL;
+
+ fp = fopen (filename, "r+");
+ if (fp != NULL) {
+ while ((auth_entry = IceReadAuthFileEntry (fp)) != NULL) {
+ /* Skip/delete entries with no network ID (invalid), or with
+ * our network ID; if we're starting up, an entry with our
+ * ID must be a stale entry left behind by an old process,
+ * and if we're shutting down, it won't be valid in the
+ * future, so either way we want to remove it from the list.
+ */
+ if (!auth_entry->network_id) {
+ IceFreeAuthFileEntry (auth_entry);
+ continue;
+ }
+
+ for (i = 0; i < server->num_local_xsmp_sockets; i++) {
+ if (!strcmp (auth_entry->network_id, our_network_ids[i])) {
+ IceFreeAuthFileEntry (auth_entry);
+ break;
+ }
+ }
+ if (i != server->num_local_xsmp_sockets) {
+ continue;
+ }
+
+ entries = g_slist_prepend (entries, auth_entry);
+ }
+
+ rewind (fp);
+ } else {
+ int fd;
+
+ if (errno != ENOENT) {
+ g_warning ("Unable to read ICE authority file %s: %m", filename);
+ goto cleanup;
+ }
+
+ fd = open (filename, O_CREAT | O_WRONLY, 0600);
+ fp = fdopen (fd, "w");
+ if (!fp) {
+ g_warning ("Unable to write to ICE authority file: %s", filename);
+ if (fd != -1) {
+ close (fd);
+ }
+ goto cleanup;
+ }
+ }
+
+ if (adding) {
+ for (i = 0; i < server->num_local_xsmp_sockets; i++) {
+ entries = g_slist_append (entries,
+ auth_entry_new ("ICE", our_network_ids[i]));
+ entries = g_slist_prepend (entries,
+ auth_entry_new ("XSMP", our_network_ids[i]));
+ }
+ }
+
+ for (e = entries; e; e = e->next) {
+ IceAuthFileEntry *auth_entry = e->data;
+ IceWriteAuthFileEntry (fp, auth_entry);
+ IceFreeAuthFileEntry (auth_entry);
+ }
+ g_slist_free (entries);
+
+ fclose (fp);
+ ok = TRUE;
+
+ cleanup:
+ IceUnlockAuthFile (filename);
+ for (i = 0; i < server->num_local_xsmp_sockets; i++) {
+ free (our_network_ids[i]);
+ }
+ g_free (our_network_ids);
+
+ return ok;
+}
+
+
+static void
+setup_listener (GsmXsmpServer *server)
+{
+ char error[256];
+ mode_t saved_umask;
+ char *network_id_list;
+ int i;
+ int res;
+
+ /* Set up sane error handlers */
+ IceSetErrorHandler (ice_error_handler);
+ IceSetIOErrorHandler (ice_io_error_handler);
+ SmsSetErrorHandler (sms_error_handler);
+
+ /* Initialize libSM; we pass NULL for hostBasedAuthProc to disable
+ * host-based authentication.
+ */
+ res = SmsInitialize (PACKAGE,
+ VERSION,
+ (SmsNewClientProc)accept_xsmp_connection,
+ server,
+ NULL,
+ sizeof (error),
+ error);
+ if (! res) {
+ gsm_util_init_error (TRUE, "Could not initialize libSM: %s", error);
+ }
+
+ /* By default, IceListenForConnections will open one socket for each
+ * transport type known to X. We don't want connections from remote
+ * hosts, so for security reasons it would be best if ICE didn't
+ * even open any non-local sockets. So we use an internal ICElib
+ * method to disable them here. Unfortunately, there is no way to
+ * ask X what transport types it knows about, so we're forced to
+ * guess.
+ */
+ _IceTransNoListen ("tcp");
+
+ /* Create the XSMP socket. Older versions of IceListenForConnections
+ * have a bug which causes the umask to be set to 0 on certain types
+ * of failures. Probably not an issue on any modern systems, but
+ * we'll play it safe.
+ */
+ saved_umask = umask (0);
+ umask (saved_umask);
+ res = IceListenForConnections (&server->num_xsmp_sockets,
+ &server->xsmp_sockets,
+ sizeof (error),
+ error);
+ if (! res) {
+ gsm_util_init_error (TRUE, _("Could not create ICE listening socket: %s"), error);
+ }
+
+ umask (saved_umask);
+
+ /* Find the local sockets in the returned socket list and move them
+ * to the start of the list.
+ */
+ for (i = server->num_local_xsmp_sockets = 0; i < server->num_xsmp_sockets; i++) {
+ char *id = IceGetListenConnectionString (server->xsmp_sockets[i]);
+
+ if (!strncmp (id, "local/", sizeof ("local/") - 1) ||
+ !strncmp (id, "unix/", sizeof ("unix/") - 1)) {
+ if (i > server->num_local_xsmp_sockets) {
+ IceListenObj tmp;
+ tmp = server->xsmp_sockets[i];
+ server->xsmp_sockets[i] = server->xsmp_sockets[server->num_local_xsmp_sockets];
+ server->xsmp_sockets[server->num_local_xsmp_sockets] = tmp;
+ }
+ server->num_local_xsmp_sockets++;
+ }
+ free (id);
+ }
+
+ if (server->num_local_xsmp_sockets == 0) {
+ gsm_util_init_error (TRUE, "IceListenForConnections did not return a local listener!");
+ }
+
+ if (server->num_local_xsmp_sockets != server->num_xsmp_sockets) {
+ /* Xtrans was apparently compiled with support for some
+ * non-local transport besides TCP (which we disabled above); we
+ * won't create IO watches on those extra sockets, so
+ * connections to them will never be noticed, but they're still
+ * there, which is inelegant.
+ *
+ * If the g_warning below is triggering for you and you want to
+ * stop it, the fix is to add additional _IceTransNoListen()
+ * calls above.
+ */
+ network_id_list = IceComposeNetworkIdList (server->num_xsmp_sockets - server->num_local_xsmp_sockets,
+ server->xsmp_sockets + server->num_local_xsmp_sockets);
+ g_warning ("IceListenForConnections returned %d non-local listeners: %s",
+ server->num_xsmp_sockets - server->num_local_xsmp_sockets,
+ network_id_list);
+ free (network_id_list);
+ }
+
+ /* Update .ICEauthority with new auth entries for our socket */
+ if (!update_iceauthority (server, TRUE)) {
+ /* FIXME: is this really fatal? Hm... */
+ gsm_util_init_error (TRUE,
+ "Could not update ICEauthority file %s",
+ IceAuthFileName ());
+ }
+
+ network_id_list = IceComposeNetworkIdList (server->num_local_xsmp_sockets,
+ server->xsmp_sockets);
+
+ gsm_util_setenv ("SESSION_MANAGER", network_id_list);
+ g_debug ("GsmXsmpServer: SESSION_MANAGER=%s\n", network_id_list);
+ free (network_id_list);
+}
+
+static GObject *
+gsm_xsmp_server_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_properties)
+{
+ GsmXsmpServer *xsmp_server;
+
+ xsmp_server = GSM_XSMP_SERVER (G_OBJECT_CLASS (gsm_xsmp_server_parent_class)->constructor (type,
+ n_construct_properties,
+ construct_properties));
+ setup_listener (xsmp_server);
+
+ return G_OBJECT (xsmp_server);
+}
+
+static void
+gsm_xsmp_server_class_init (GsmXsmpServerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = gsm_xsmp_server_get_property;
+ object_class->set_property = gsm_xsmp_server_set_property;
+ object_class->constructor = gsm_xsmp_server_constructor;
+ object_class->finalize = gsm_xsmp_server_finalize;
+
+ g_object_class_install_property (object_class,
+ PROP_CLIENT_STORE,
+ g_param_spec_object ("client-store",
+ NULL,
+ NULL,
+ GSM_TYPE_STORE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+}
+
+static void
+gsm_xsmp_server_init (GsmXsmpServer *xsmp_server)
+{
+}
+
+static void
+gsm_xsmp_server_finalize (GObject *object)
+{
+ GsmXsmpServer *xsmp_server;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GSM_IS_XSMP_SERVER (object));
+
+ xsmp_server = GSM_XSMP_SERVER (object);
+
+ g_return_if_fail (xsmp_server != NULL);
+
+ IceFreeListenObjs (xsmp_server->num_xsmp_sockets,
+ xsmp_server->xsmp_sockets);
+
+ if (xsmp_server->client_store != NULL) {
+ g_object_unref (xsmp_server->client_store);
+ }
+
+ G_OBJECT_CLASS (gsm_xsmp_server_parent_class)->finalize (object);
+}
+
+GsmXsmpServer *
+gsm_xsmp_server_new (GsmStore *client_store)
+{
+ if (xsmp_server_object != NULL) {
+ g_object_ref (xsmp_server_object);
+ } else {
+ xsmp_server_object = g_object_new (GSM_TYPE_XSMP_SERVER,
+ "client-store", client_store,
+ NULL);
+
+ g_object_add_weak_pointer (xsmp_server_object,
+ (gpointer *) &xsmp_server_object);
+ }
+
+ return GSM_XSMP_SERVER (xsmp_server_object);
+}
diff --git a/gnome-session/gsm-xsmp-server.h b/gnome-session/gsm-xsmp-server.h
new file mode 100644
index 0000000..95d704d
--- /dev/null
+++ b/gnome-session/gsm-xsmp-server.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 William Jon McCann <jmccann@redhat.com>
+ *
+ * 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 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/>.
+ *
+ */
+
+
+#ifndef __GSM_XSMP_SERVER_H
+#define __GSM_XSMP_SERVER_H
+
+#include <glib-object.h>
+
+#include "gsm-store.h"
+
+G_BEGIN_DECLS
+
+#define GSM_TYPE_XSMP_SERVER (gsm_xsmp_server_get_type ())
+G_DECLARE_FINAL_TYPE (GsmXsmpServer, gsm_xsmp_server, GSM, XSMP_SERVER, GObject)
+
+GsmXsmpServer * gsm_xsmp_server_new (GsmStore *client_store);
+void gsm_xsmp_server_start (GsmXsmpServer *server);
+void gsm_xsmp_server_stop_accepting_new_clients (GsmXsmpServer *server);
+void gsm_xsmp_server_start_accepting_new_clients (GsmXsmpServer *server);
+
+G_END_DECLS
+
+#endif /* __GSM_XSMP_SERVER_H */
diff --git a/gnome-session/main.c b/gnome-session/main.c
new file mode 100644
index 0000000..6f03324
--- /dev/null
+++ b/gnome-session/main.c
@@ -0,0 +1,636 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2006 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 <libintl.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <locale.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/stat.h>
+
+#include <glib/gi18n.h>
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <glib-unix.h>
+#include <gio/gio.h>
+
+#include "gdm-log.h"
+
+#include "gsm-util.h"
+#include "gsm-manager.h"
+#include "gsm-session-fill.h"
+#include "gsm-store.h"
+#include "gsm-system.h"
+#include "gsm-fail-whale.h"
+
+#ifdef ENABLE_SYSTEMD_JOURNAL
+#include <systemd/sd-journal.h>
+#endif
+
+#define GSM_DBUS_NAME "org.gnome.SessionManager"
+
+static gboolean systemd_service = FALSE;
+static gboolean use_systemd = USE_SYSTEMD_SESSION;
+static gboolean failsafe = FALSE;
+static gboolean show_version = FALSE;
+static gboolean debug = FALSE;
+static gboolean please_fail = FALSE;
+static gboolean disable_acceleration_check = FALSE;
+static const char *session_name = NULL;
+static GsmManager *manager = NULL;
+static char *gl_renderer = NULL;
+
+static GMainLoop *loop;
+
+void
+gsm_quit (void)
+{
+ g_main_loop_quit (loop);
+}
+
+static void
+gsm_main (void)
+{
+ if (loop == NULL)
+ loop = g_main_loop_new (NULL, TRUE);
+
+ g_main_loop_run (loop);
+}
+
+static void
+on_name_lost (GDBusConnection *connection,
+ const char *name,
+ gpointer data)
+{
+ if (connection == NULL) {
+ g_warning ("Lost name on bus: %s", name);
+ gsm_fail_whale_dialog_we_failed (TRUE, TRUE, NULL);
+ } else {
+ g_debug ("Calling name lost callback function");
+
+ /*
+ * When the signal handler gets a shutdown signal, it calls
+ * this function to inform GsmManager to not restart
+ * applications in the off chance a handler is already queued
+ * to dispatch following the below call to gtk_main_quit.
+ */
+ gsm_manager_set_phase (manager, GSM_MANAGER_PHASE_EXIT);
+
+ gsm_quit ();
+ }
+}
+
+static gboolean
+term_or_int_signal_cb (gpointer data)
+{
+ g_autoptr(GError) error = NULL;
+ GsmManager *manager = (GsmManager *)data;
+
+ /* let the fatal signals interrupt us */
+ g_debug ("Caught SIGINT/SIGTERM, shutting down normally.");
+
+ if (!gsm_manager_logout (manager, GSM_MANAGER_LOGOUT_MODE_FORCE, &error)) {
+ if (g_error_matches (error, GSM_MANAGER_ERROR, GSM_MANAGER_ERROR_NOT_IN_RUNNING)) {
+ gsm_quit ();
+ return FALSE;
+ }
+
+ g_critical ("Failed to log out: %s", error->message);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+sigusr2_cb (gpointer data)
+{
+ g_debug ("-------- MARK --------");
+ return TRUE;
+}
+
+static gboolean
+sigusr1_cb (gpointer data)
+{
+ gdm_log_toggle_debug ();
+ return TRUE;
+}
+
+static void
+on_name_acquired (GDBusConnection *connection,
+ const char *name,
+ gpointer data)
+{
+ gsm_manager_start (manager);
+}
+
+static void
+create_manager (void)
+{
+ GsmStore *client_store;
+
+ client_store = gsm_store_new ();
+ manager = gsm_manager_new (client_store, failsafe, systemd_service);
+ g_object_unref (client_store);
+
+ g_unix_signal_add (SIGTERM, term_or_int_signal_cb, manager);
+ g_unix_signal_add (SIGINT, term_or_int_signal_cb, manager);
+ g_unix_signal_add (SIGUSR1, sigusr1_cb, manager);
+ g_unix_signal_add (SIGUSR2, sigusr2_cb, manager);
+
+ if (IS_STRING_EMPTY (session_name)) {
+ session_name = _gsm_manager_get_default_session (manager);
+ }
+
+ if (!gsm_session_fill (manager, session_name)) {
+ gsm_fail_whale_dialog_we_failed (FALSE, TRUE, NULL);
+ }
+
+ _gsm_manager_set_renderer (manager, gl_renderer);
+}
+
+static void
+on_bus_acquired (GDBusConnection *connection,
+ const char *name,
+ gpointer data)
+{
+ create_manager ();
+}
+
+static guint
+acquire_name (void)
+{
+ return g_bus_own_name (G_BUS_TYPE_SESSION,
+ GSM_DBUS_NAME,
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ on_bus_acquired,
+ on_name_acquired,
+ on_name_lost,
+ NULL, NULL);
+}
+
+static gboolean
+require_dbus_session (int argc,
+ char **argv,
+ GError **error)
+{
+ char **new_argv;
+ int i;
+
+ if (g_getenv ("DBUS_SESSION_BUS_ADDRESS"))
+ return TRUE;
+
+ /* Just a sanity check to prevent infinite recursion if
+ * dbus-launch fails to set DBUS_SESSION_BUS_ADDRESS
+ */
+ g_return_val_if_fail (!g_str_has_prefix (argv[0], "dbus-launch"),
+ TRUE);
+
+ /* +2 for our new arguments, +1 for NULL */
+ new_argv = g_malloc ((argc + 3) * sizeof (*argv));
+
+ new_argv[0] = "dbus-launch";
+ new_argv[1] = "--exit-with-session";
+ for (i = 0; i < argc; i++) {
+ new_argv[i + 2] = argv[i];
+ }
+ new_argv[i + 2] = NULL;
+
+ if (!execvp ("dbus-launch", new_argv)) {
+ g_set_error (error,
+ G_SPAWN_ERROR,
+ G_SPAWN_ERROR_FAILED,
+ "No session bus and could not exec dbus-launch: %s",
+ g_strerror (errno));
+ return FALSE;
+ }
+
+ /* Should not be reached */
+ return TRUE;
+}
+
+static gboolean
+check_gl (GError **error)
+{
+ int status;
+ char *argv[] = { LIBEXECDIR "/gnome-session-check-accelerated", NULL };
+
+ if (getenv ("DISPLAY") == NULL) {
+ /* Not connected to X11, someone else will take care of checking GL */
+ return TRUE;
+ }
+
+ if (!g_spawn_sync (NULL, (char **) argv, NULL, 0, NULL, NULL, &gl_renderer, NULL,
+ &status, error)) {
+ return FALSE;
+ }
+
+#if GLIB_CHECK_VERSION(2, 70, 0)
+ return g_spawn_check_wait_status (status, error);
+#else
+ return g_spawn_check_exit_status (status, error);
+#endif
+}
+
+static void
+initialize_gio (void)
+{
+ char *disable_fuse = NULL;
+ char *use_vfs = NULL;
+
+ disable_fuse = g_strdup (g_getenv ("GVFS_DISABLE_FUSE"));
+ use_vfs = g_strdup (g_getenv ("GIO_USE_VFS"));
+
+ g_setenv ("GVFS_DISABLE_FUSE", "1", TRUE);
+ g_setenv ("GIO_USE_VFS", "local", TRUE);
+ g_vfs_get_default ();
+
+ if (use_vfs) {
+ g_setenv ("GIO_USE_VFS", use_vfs, TRUE);
+ g_free (use_vfs);
+ } else {
+ g_unsetenv ("GIO_USE_VFS");
+ }
+
+ if (disable_fuse) {
+ g_setenv ("GVFS_DISABLE_FUSE", disable_fuse, TRUE);
+ g_free (disable_fuse);
+ } else {
+ g_unsetenv ("GVFS_DISABLE_FUSE");
+ }
+}
+
+#ifdef ENABLE_SYSTEMD_SESSION
+static gboolean
+leader_term_or_int_signal_cb (gpointer data)
+{
+ gint fifo_fd = GPOINTER_TO_INT (data);
+
+ /* Start a shutdown explicitly. */
+ gsm_util_start_systemd_unit ("gnome-session-shutdown.target", "replace-irreversibly", NULL);
+
+ if (fifo_fd >= 0) {
+ int res;
+
+ /* If we have a fifo, try to signal the other side. */
+ res = write (fifo_fd, "S", 1);
+ if (res < 0) {
+ g_warning ("Error signaling shutdown to monitoring process: %m");
+ gsm_quit ();
+ }
+ } else {
+ /* Otherwise quit immediately as we cannot wait on systemd */
+ gsm_quit ();
+ }
+
+ return G_SOURCE_REMOVE;
+}
+
+/**
+ * systemd_leader_run:
+ *
+ * This is the session leader when running under systemd, i.e. it is the only
+ * process that is *not* managed by the systemd user instance, this process
+ * is the one executed by the GDM helpers and is part of the session scope in
+ * the system systemd instance.
+ *
+ * This process works together with a service running in the user systemd
+ * instance (currently gnome-session-ctl@monitor.service):
+ *
+ * - It needs to signal shutdown to the user session when receiving SIGTERM
+ *
+ * - It needs to quit just after the user session is done
+ *
+ * - The monitor instance needs to know if this process was killed
+ *
+ * All this is achieved by opening a named fifo in a well known location.
+ * If this process receives SIGTERM or SIGINT then it will write a single byte
+ * causing the monitor service to signal STOPPING=1 to systemd, triggering a
+ * clean shutdown, solving the first item. The other two items are solved by
+ * waiting for EOF/HUP on both sides and quitting immediately when receiving
+ * that signal.
+ *
+ * As an example, a shutdown might look as follows:
+ *
+ * - session-X.scope for user is stopped
+ * - Leader process receive SIGTERM
+ * - Leader sends single byte
+ * - Monitor process receives byte and signals STOPPING=1
+ * - Systemd user instance starts session teardown
+ * - Session is torn down, last job run is stopping monitor process (SIGTERM)
+ * - Monitor process quits, closing FD in the process
+ * - Leader process receives HUP and quits
+ * - GDM shuts down its processes in the users scope
+ *
+ * The result is that the session is stopped cleanly.
+ */
+static void
+systemd_leader_run(void)
+{
+ g_autofree char *fifo_name = NULL;
+ int res;
+ int fifo_fd;
+
+ fifo_name = g_strdup_printf ("%s/gnome-session-leader-fifo",
+ g_get_user_runtime_dir ());
+ res = mkfifo (fifo_name, 0666);
+ if (res < 0 && errno != EEXIST)
+ g_warning ("Error creating FIFO: %m");
+
+ fifo_fd = g_open (fifo_name, O_WRONLY | O_CLOEXEC, 0666);
+ if (fifo_fd >= 0) {
+ struct stat buf;
+
+ res = fstat (fifo_fd, &buf);
+ if (res < 0) {
+ g_warning ("Unable to watch systemd session: fstat failed with %m");
+ close (fifo_fd);
+ fifo_fd = -1;
+ } else if (!(buf.st_mode & S_IFIFO)) {
+ g_warning ("Unable to watch systemd session: FD is not a FIFO");
+ close (fifo_fd);
+ fifo_fd = -1;
+ } else {
+ g_unix_fd_add (fifo_fd, G_IO_HUP, (GUnixFDSourceFunc) gsm_quit, NULL);
+ }
+ } else {
+ g_warning ("Unable to watch systemd session: Opening FIFO failed with %m");
+ }
+
+ g_unix_signal_add (SIGTERM, leader_term_or_int_signal_cb, GINT_TO_POINTER (fifo_fd));
+ g_unix_signal_add (SIGINT, leader_term_or_int_signal_cb, GINT_TO_POINTER (fifo_fd));
+
+ /* Sleep until we receive HUP or are killed. */
+ gsm_main ();
+ exit(0);
+}
+#endif /* ENABLE_SYSTEMD_SESSION */
+
+int
+main (int argc, char **argv)
+{
+ GError *error = NULL;
+ static char **override_autostart_dirs = NULL;
+ static char *opt_session_name = NULL;
+ const char *debug_string = NULL;
+ const char *env_override_autostart_dirs = NULL;
+ g_auto(GStrv) env_override_autostart_dirs_v = NULL;
+ gboolean gl_failed = FALSE;
+ guint name_owner_id;
+ GOptionContext *options;
+ static GOptionEntry entries[] = {
+#ifdef ENABLE_SYSTEMD_SESSION
+ { "systemd-service", 0, 0, G_OPTION_ARG_NONE, &systemd_service, N_("Running as systemd service"), NULL },
+ { "systemd", 0, 0, G_OPTION_ARG_NONE, &use_systemd, N_("Use systemd session management"), NULL },
+#endif
+ { "builtin", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &use_systemd, N_("Use builtin session management (rather than the systemd based one)"), NULL },
+ { "autostart", 'a', 0, G_OPTION_ARG_STRING_ARRAY, &override_autostart_dirs, N_("Override standard autostart directories"), N_("AUTOSTART_DIR") },
+ { "session", 0, 0, G_OPTION_ARG_STRING, &opt_session_name, N_("Session to use"), N_("SESSION_NAME") },
+ { "debug", 0, 0, G_OPTION_ARG_NONE, &debug, N_("Enable debugging code"), NULL },
+ { "failsafe", 'f', 0, G_OPTION_ARG_NONE, &failsafe, N_("Do not load user-specified applications"), NULL },
+ { "version", 0, 0, G_OPTION_ARG_NONE, &show_version, N_("Version of this application"), NULL },
+ /* Translators: the 'fail whale' is the black dialog we show when something goes seriously wrong */
+ { "whale", 0, 0, G_OPTION_ARG_NONE, &please_fail, N_("Show the fail whale dialog for testing"), NULL },
+ { "disable-acceleration-check", 0, 0, G_OPTION_ARG_NONE, &disable_acceleration_check, N_("Disable hardware acceleration check"), NULL },
+ { NULL, 0, 0, 0, NULL, NULL, NULL }
+ };
+
+ /* Make sure that we have a session bus */
+ if (!require_dbus_session (argc, argv, &error)) {
+ gsm_util_init_error (TRUE, "%s", error->message);
+ }
+
+ /* From 3.14 GDM sets XDG_CURRENT_DESKTOP. For compatibility with
+ * older versions of GDM, other display managers, and startx,
+ * set a fallback value if we don't find it set.
+ */
+ if (g_getenv ("XDG_CURRENT_DESKTOP") == NULL) {
+ g_setenv("XDG_CURRENT_DESKTOP", "GNOME", TRUE);
+ gsm_util_setenv ("XDG_CURRENT_DESKTOP", "GNOME");
+ }
+
+ /* Make sure we initialize gio in a way that does not autostart any daemon */
+ initialize_gio ();
+
+ setlocale (LC_ALL, "");
+ bindtextdomain (GETTEXT_PACKAGE, LOCALE_DIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ debug_string = g_getenv ("GNOME_SESSION_DEBUG");
+ if (debug_string != NULL) {
+ debug = atoi (debug_string) == 1;
+ }
+
+ error = NULL;
+ options = g_option_context_new (_(" — the GNOME session manager"));
+ g_option_context_add_main_entries (options, entries, GETTEXT_PACKAGE);
+ g_option_context_parse (options, &argc, &argv, &error);
+ if (error != NULL) {
+ g_warning ("%s", error->message);
+ exit (1);
+ }
+
+ g_option_context_free (options);
+
+ /* Rebind stdout/stderr to the journal explicitly, so that
+ * journald picks ups the nicer "gnome-session" as the program
+ * name instead of whatever shell script GDM happened to use.
+ */
+#ifdef ENABLE_SYSTEMD_JOURNAL
+ if (!debug) {
+ int journalfd;
+
+ journalfd = sd_journal_stream_fd (PACKAGE, LOG_INFO, 0);
+ if (journalfd >= 0) {
+ dup2(journalfd, 1);
+ dup2(journalfd, 2);
+ }
+ }
+#endif
+
+ gdm_log_init ();
+ gdm_log_set_debug (debug);
+
+ if (systemd_service) {
+ /* XXX: This is an optimization, but we actually need to do
+ * it right now as the DISPLAY environment might leak
+ * into the new session from an old run. */
+ g_debug ("hardware acceleration already done if needed");
+ } else if (disable_acceleration_check) {
+ g_debug ("hardware acceleration check is disabled");
+ } else {
+ /* Check GL, if it doesn't work out then force software fallback */
+ if (!check_gl (&error)) {
+ gl_failed = TRUE;
+
+ g_debug ("hardware acceleration check failed: %s",
+ error? error->message : "");
+ g_clear_error (&error);
+ if (g_getenv ("LIBGL_ALWAYS_SOFTWARE") == NULL) {
+ g_setenv ("LIBGL_ALWAYS_SOFTWARE", "1", TRUE);
+ if (!check_gl (&error)) {
+ g_warning ("software acceleration check failed: %s",
+ error? error->message : "");
+ g_clear_error (&error);
+ } else {
+ gl_failed = FALSE;
+ }
+ }
+ }
+ }
+
+ if (show_version) {
+ g_print ("%s %s\n", argv [0], VERSION);
+ exit (0);
+ }
+
+ if (gl_failed) {
+ gsm_fail_whale_dialog_we_failed (FALSE, TRUE, NULL);
+ gsm_main ();
+ exit (1);
+ }
+
+ if (please_fail) {
+ gsm_fail_whale_dialog_we_failed (TRUE, TRUE, NULL);
+ gsm_main ();
+ exit (1);
+ }
+
+ env_override_autostart_dirs = g_getenv ("GNOME_SESSION_AUTOSTART_DIR");
+
+ if (env_override_autostart_dirs != NULL && env_override_autostart_dirs[0] != '\0') {
+ env_override_autostart_dirs_v = g_strsplit (env_override_autostart_dirs, ":", 0);
+ gsm_util_set_autostart_dirs (env_override_autostart_dirs_v);
+ } else {
+ gsm_util_set_autostart_dirs (override_autostart_dirs);
+
+ /* Export the override autostart dirs parameter to the environment
+ * in case we are running on systemd. */
+ if (override_autostart_dirs) {
+ g_autofree char *autostart_dirs = NULL;
+ autostart_dirs = g_strjoinv (":", override_autostart_dirs);
+ g_setenv ("GNOME_SESSION_AUTOSTART_DIR", autostart_dirs, TRUE);
+ }
+ }
+
+ gsm_util_export_activation_environment (&error);
+ if (error) {
+ g_warning ("Failed to upload environment to DBus: %s", error->message);
+ g_clear_error (&error);
+ }
+
+ session_name = opt_session_name;
+
+#ifdef HAVE_SYSTEMD
+ gsm_util_export_user_environment (&error);
+ if (error && !g_getenv ("RUNNING_UNDER_GDM"))
+ g_warning ("Failed to upload environment to systemd: %s", error->message);
+ g_clear_error (&error);
+#endif
+
+#ifdef ENABLE_SYSTEMD_SESSION
+ if (use_systemd && !systemd_service) {
+ g_autofree gchar *gnome_session_target;
+ const gchar *session_type;
+
+ session_type = g_getenv ("XDG_SESSION_TYPE");
+
+ /* We really need to resolve the session name at this point,
+ * which requires talking to GSettings internally. */
+ if (IS_STRING_EMPTY (session_name)) {
+ session_name = _gsm_manager_get_default_session (NULL);
+ }
+
+ /* Reset all failed units; we are going to start a lof ot things and
+ * really do not want to run into errors because units have failed
+ * in a previous session
+ */
+ gsm_util_systemd_reset_failed (&error);
+ if (error && !g_getenv ("RUNNING_UNDER_GDM"))
+ g_warning ("Failed to reset failed state of units: %s", error->message);
+ g_clear_error (&error);
+
+ /* We don't escape the name (i.e. we leave any '-' intact). */
+ gnome_session_target = g_strdup_printf ("gnome-session-%s@%s.target", session_type, session_name);
+ if (gsm_util_start_systemd_unit (gnome_session_target, "fail", &error)) {
+ /* We started the unit, open fifo and sleep forever. */
+ systemd_leader_run ();
+ exit(0);
+ }
+
+ /* We could not start the unit, fall back. */
+ if (g_getenv ("RUNNING_UNDER_GDM"))
+ g_message ("Falling back to non-systemd startup procedure. This is expected to happen for GDM sessions.");
+ else
+ g_warning ("Falling back to non-systemd startup procedure due to error: %s", error->message);
+ g_clear_error (&error);
+ }
+#endif /* ENABLE_SYSTEMD_SESSION */
+
+ {
+ gchar *ibus_path;
+
+ ibus_path = g_find_program_in_path("ibus-daemon");
+
+ if (ibus_path) {
+ const gchar *p;
+ p = g_getenv ("QT_IM_MODULE");
+ if (!p || !*p)
+ p = "ibus";
+ gsm_util_setenv ("QT_IM_MODULE", p);
+ p = g_getenv ("XMODIFIERS");
+ if (!p || !*p)
+ p = "@im=ibus";
+ gsm_util_setenv ("XMODIFIERS", p);
+ }
+
+ g_free (ibus_path);
+ }
+
+ /* We want to use the GNOME menus which has the designed categories.
+ */
+ gsm_util_setenv ("XDG_MENU_PREFIX", "gnome-");
+
+ /* Talk to logind before acquiring a name, since it does synchronous
+ * calls at initialization time that invoke a main loop and if we
+ * already owned a name, then we would service too early during
+ * that main loop.
+ */
+ g_object_unref (gsm_get_system ());
+
+ name_owner_id = acquire_name ();
+
+ gsm_main ();
+
+#ifdef HAVE_SYSTEMD
+ gsm_util_export_user_environment (NULL);
+#endif
+
+ g_clear_object (&manager);
+ g_free (gl_renderer);
+
+ g_bus_unown_name (name_owner_id);
+ gdm_log_shutdown ();
+
+ return 0;
+}
diff --git a/gnome-session/meson.build b/gnome-session/meson.build
new file mode 100644
index 0000000..7d2f3c0
--- /dev/null
+++ b/gnome-session/meson.build
@@ -0,0 +1,111 @@
+script_conf = configuration_data()
+script_conf.set('libexecdir', session_libexecdir)
+
+script = 'gnome-session'
+
+configure_file(
+ input: script + '.in',
+ output: script,
+ install: true,
+ install_dir: session_bindir,
+ configuration: script_conf
+)
+
+libgsmutil = static_library(
+ 'gsmutil',
+ sources: 'gsm-util.c',
+ include_directories: top_inc,
+ dependencies: session_deps
+)
+
+sources = files(
+ 'gsm-app.c',
+ 'gsm-autostart-app.c',
+ 'gsm-client.c',
+ 'gsm-dbus-client.c',
+ 'gsm-fail-whale.c',
+ 'gsm-inhibitor.c',
+ 'gdm-log.c',
+ 'gsm-manager.c',
+ 'gsm-presence.c',
+ 'gsm-process-helper.c',
+ 'gsm-session-fill.c',
+ 'gsm-session-save.c',
+ 'gsm-shell-extensions.c',
+ 'gsm-shell.c',
+ 'gsm-store.c',
+ 'gsm-system.c',
+ 'gsm-systemd.c',
+ 'gsm-xsmp-client.c',
+ 'gsm-xsmp-server.c',
+ 'main.c'
+)
+
+dbus_ifaces = [
+ ['org.gnome.SessionManager', 'ExportedManager'],
+ ['org.gnome.SessionManager.Client', 'ExportedClient'],
+ ['org.gnome.SessionManager.ClientPrivate', 'ExportedClientPrivate'],
+ ['org.gnome.SessionManager.App', 'ExportedApp'],
+ ['org.gnome.SessionManager.Inhibitor', 'ExportedInhibitor'],
+ ['org.gnome.SessionManager.Presence', 'ExportedPresence']
+]
+
+foreach iface: dbus_ifaces
+ sources += gnome.gdbus_codegen(
+ iface[0],
+ iface[0] + '.xml',
+ interface_prefix: iface[0] + '.',
+ namespace: 'Gsm',
+ annotations: [iface[0], 'org.gtk.GDBus.C.Name', iface[1]]
+ )
+endforeach
+
+if enable_consolekit
+ sources += files('gsm-consolekit.c')
+endif
+
+cflags = [
+ '-DLOCALE_DIR="@0@"'.format(session_localedir),
+ '-DDATA_DIR="@0@"'.format(session_pkgdatadir),
+ '-DLIBEXECDIR="@0@"'.format(session_libexecdir)
+]
+
+executable(
+ meson.project_name() + '-binary',
+ sources,
+ include_directories: top_inc,
+ dependencies: session_bin_deps,
+ c_args: cflags,
+ link_with: libgsmutil,
+ install: true,
+ install_dir: session_libexecdir
+)
+
+sources = files('gsm-fail-whale-dialog.c')
+
+cflags = '-DLOCALE_DIR="@0@"'.format(session_localedir)
+
+executable(
+ meson.project_name() + '-failed',
+ sources,
+ include_directories: top_inc,
+ dependencies: gtk_dep,
+ c_args: cflags,
+ install: true,
+ install_dir: session_libexecdir
+)
+
+units = [
+ ['test-inhibit', [], session_deps + [gtk_dep]],
+ ['test-client-dbus', [], [gio_dep]],
+ ['test-process-helper', files('gsm-process-helper.c'), [gio_dep]]
+]
+
+foreach unit: units
+ executable(
+ unit[0],
+ [unit[0] + '.c'] + unit[1],
+ include_directories: top_inc,
+ dependencies: unit[2]
+ )
+endforeach
diff --git a/gnome-session/org.gnome.SessionManager.App.xml b/gnome-session/org.gnome.SessionManager.App.xml
new file mode 100644
index 0000000..9f6e1b3
--- /dev/null
+++ b/gnome-session/org.gnome.SessionManager.App.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
+ <interface name="org.gnome.SessionManager.App">
+ <method name="GetAppId">
+ <arg type="s" name="app_id" direction="out">
+ <doc:doc>
+ <doc:summary>The identifier for the application</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Return the application ID.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+ <method name="GetStartupId">
+ <arg type="s" name="startup_id" direction="out">
+ <doc:doc>
+ <doc:summary>The startup identifier</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Return the startup ID associated with this application.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+ <method name="GetPhase">
+ <arg type="u" name="phase" direction="out">
+ <doc:doc>
+ <doc:summary>The application startup phase</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Return the startup phase of this application.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+
+ </interface>
+</node>
diff --git a/gnome-session/org.gnome.SessionManager.Client.xml b/gnome-session/org.gnome.SessionManager.Client.xml
new file mode 100644
index 0000000..cf7476a
--- /dev/null
+++ b/gnome-session/org.gnome.SessionManager.Client.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
+ <interface name="org.gnome.SessionManager.Client">
+ <method name="GetAppId">
+ <arg type="s" name="app_id" direction="out">
+ <doc:doc>
+ <doc:summary>The identifier for the associated application</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Return the application ID associated with this client.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+ <method name="GetStartupId">
+ <arg type="s" name="startup_id" direction="out">
+ <doc:doc>
+ <doc:summary>The startup identifier</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Return the startup ID associated with this client.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+ <method name="GetRestartStyleHint">
+ <arg type="u" name="hint" direction="out">
+ <doc:doc>
+ <doc:summary>The restart style hint</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Return the restart style hint for this client.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+ <method name="GetUnixProcessId">
+ <arg type="u" name="pid" direction="out">
+ <doc:doc>
+ <doc:summary>The Unix process identifier</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Return the Unix process identifier for this client.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+ <method name="GetStatus">
+ <arg type="u" name="status" direction="out">
+ <doc:doc>
+ <doc:summary>The client status</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Return the status of this client.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+ <method name="Stop">
+ <doc:doc>
+ <doc:description>
+ <doc:para>Initiate a request to terminate this application via XSMP.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+ </interface>
+</node>
diff --git a/gnome-session/org.gnome.SessionManager.ClientPrivate.xml b/gnome-session/org.gnome.SessionManager.ClientPrivate.xml
new file mode 100644
index 0000000..a167065
--- /dev/null
+++ b/gnome-session/org.gnome.SessionManager.ClientPrivate.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
+ <interface name="org.gnome.SessionManager.ClientPrivate">
+ <method name="EndSessionResponse">
+ <arg name="is_ok" type="b" direction="in">
+ <doc:doc>
+ <doc:summary>Whether or not it is OK to preceed</doc:summary>
+ </doc:doc>
+ </arg>
+ <arg name="reason" type="s" direction="in">
+ <doc:doc>
+ <doc:summary>The reason string</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>This method should be used by the client in response to
+ the <doc:ref type="signal" to="org.gnome.SessionManager.ClientPrivate::QueryEndSession">QueryEndSession</doc:ref>
+ and <doc:ref type="signal" to="org.gnome.SessionManager.ClientPrivate::EndSession">EndSession</doc:ref> signals.
+ </doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+
+ <signal name="Stop">
+ <doc:doc>
+ <doc:summary>Stop client</doc:summary>
+ <doc:description>
+ <doc:para>
+ The client should stop and remove itself from the session in
+ response to this signal.
+ </doc:para>
+ </doc:description>
+ </doc:doc>
+ </signal>
+
+ <signal name="QueryEndSession">
+ <arg name="flags" type="u">
+ <doc:doc>
+ <doc:summary>Flags</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>This signal is used to inform the client that the
+ session is about to end. The client must respond by
+ calling
+ <doc:ref type="method" to="org.gnome.SessionManager.ClientPrivate.EndSessionResponse">EndSessionResponse</doc:ref>
+ within one second of the signal emission.
+ </doc:para>
+ <doc:para>
+ The flags may include:
+ <doc:list>
+ <doc:item>
+ <doc:term>1</doc:term>
+ <doc:definition>Logout is forced.
+ <doc:ref type="method" to="org.gnome.SessionManager.ClientPrivate.EndSessionResponse">EndSessionResponse</doc:ref>
+ reason and any inhibit from client will be
+ ignored.</doc:definition>
+ </doc:item>
+ </doc:list>
+ </doc:para>
+ <doc:para>
+ If the client responds with an EndSessionResponse is-ok
+ argument equal to FALSE and a reason then this reason may
+ be displayed to the user.
+ </doc:para>
+ <doc:para>
+ The client must not attempt to perform any actions or
+ interact with the user in response to this signal. Any
+ actions required for a clean shutdown should take place in
+ response to the
+ <doc:ref type="signal" to="org.gnome.SessionManager.ClientPrivate::EndSession">EndSession</doc:ref> signal.
+ </doc:para>
+ <doc:para>
+ The client should limit operations until either a
+ <doc:ref type="signal" to="org.gnome.SessionManager.ClientPrivate::EndSession">EndSession</doc:ref>
+ <doc:ref type="signal" to="org.gnome.SessionManager.ClientPrivate::CancelEndSession">CancelEndSession</doc:ref>
+ signal is received.
+ </doc:para>
+ </doc:description>
+ </doc:doc>
+ </signal>
+
+ <signal name="EndSession">
+ <arg name="flags" type="u">
+ <doc:doc>
+ <doc:summary>Flags</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>This signal is used to inform the client that the
+ session is about to end. The client must respond by
+ calling
+ <doc:ref type="method" to="org.gnome.SessionManager.ClientPrivate.EndSessionResponse">EndSessionResponse</doc:ref>
+ within ten seconds of the signal emission.
+ </doc:para>
+ <doc:para>
+ The client must not attempt to interact with the user in
+ response to this signal. The application will be given a
+ maxium of ten seconds to perform any actions required for
+ a clean shutdown.
+ </doc:para>
+ </doc:description>
+ </doc:doc>
+ </signal>
+
+ <signal name="CancelEndSession">
+ <doc:doc>
+ <doc:description>
+ <doc:para>
+ This signal indicates to the client that a previous emission of
+ <doc:ref type="signal" to="org.gnome.SessionManager.ClientPrivate::QueryEndSession">QueryEndSession</doc:ref>
+ has been cancelled. The client should resume normal operations.
+ </doc:para>
+ </doc:description>
+ </doc:doc>
+ </signal>
+
+ </interface>
+</node>
diff --git a/gnome-session/org.gnome.SessionManager.Inhibitor.xml b/gnome-session/org.gnome.SessionManager.Inhibitor.xml
new file mode 100644
index 0000000..342d2a8
--- /dev/null
+++ b/gnome-session/org.gnome.SessionManager.Inhibitor.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
+ <interface name="org.gnome.SessionManager.Inhibitor">
+ <method name="GetAppId">
+ <arg type="s" name="app_id" direction="out">
+ <doc:doc>
+ <doc:summary>The identifier for the associated application</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Return the application ID associated with this inhibit.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+ <method name="GetClientId">
+ <arg type="o" name="client_id" direction="out">
+ <doc:doc>
+ <doc:summary>The object path of the associated client</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Return the client object path associated with this inhibit.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+ <method name="GetReason">
+ <arg type="s" name="reason" direction="out">
+ <doc:doc>
+ <doc:summary>The reason for the inhibit</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Return the reason for the inhibit</doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+ <method name="GetFlags">
+ <arg type="u" name="flags" direction="out">
+ <doc:doc>
+ <doc:summary>The flags that determine the scope of the inhibit</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Return the flags that determine the scope of the inhibit</doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+ <method name="GetToplevelXid">
+ <arg type="u" name="xid" direction="out">
+ <doc:doc>
+ <doc:summary>X11 toplevel window identifier associated with this inhibit. Zero if not set.</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Return the X11 toplevel window identifier associated with this inhibit. Zero if not set.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+ </interface>
+</node>
diff --git a/gnome-session/org.gnome.SessionManager.Presence.xml b/gnome-session/org.gnome.SessionManager.Presence.xml
new file mode 100644
index 0000000..0441970
--- /dev/null
+++ b/gnome-session/org.gnome.SessionManager.Presence.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
+ <interface name="org.gnome.SessionManager.Presence">
+
+ <property name="status" type="u" access="readwrite">
+ <doc:doc>
+ <doc:description>
+ <doc:para>
+ The status of the session.
+ </doc:para>
+ <doc:para>
+ The status parameter must be one of the following:
+ <doc:list>
+ <doc:item>
+ <doc:term>0</doc:term>
+ <doc:definition>Available</doc:definition>
+ </doc:item>
+ <doc:item>
+ <doc:term>1</doc:term>
+ <doc:definition>Invisible</doc:definition>
+ </doc:item>
+ <doc:item>
+ <doc:term>2</doc:term>
+ <doc:definition>Busy</doc:definition>
+ </doc:item>
+ <doc:item>
+ <doc:term>3</doc:term>
+ <doc:definition>Idle</doc:definition>
+ </doc:item>
+ </doc:list>
+ </doc:para>
+ </doc:description>
+ </doc:doc>
+ </property>
+ <property name="status-text" type="s" access="readwrite">
+ <doc:doc>
+ <doc:description>
+ <doc:para>The descriptive status for the session.
+ </doc:para>
+ </doc:description>
+ </doc:doc>
+ </property>
+ <method name="SetStatus">
+ <arg type="u" name="status" direction="in">
+ <doc:doc>
+ <doc:summary>The status value</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Set the status value of the session.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+ <method name="SetStatusText">
+ <arg type="s" name="status_text" direction="in">
+ <doc:doc>
+ <doc:summary>The descriptive status for the session.</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Set the descriptive status text for the session.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+
+ <signal name="StatusChanged">
+ <arg name="status" type="u">
+ <doc:doc>
+ <doc:summary>The new status value</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Indicates that the session status value has changed.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </signal>
+ <signal name="StatusTextChanged">
+ <arg name="status_text" type="s">
+ <doc:doc>
+ <doc:summary>The new status text</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Indicates that the descriptive session status text has changed.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </signal>
+
+ </interface>
+</node>
diff --git a/gnome-session/org.gnome.SessionManager.xml b/gnome-session/org.gnome.SessionManager.xml
new file mode 100644
index 0000000..ce4b8c0
--- /dev/null
+++ b/gnome-session/org.gnome.SessionManager.xml
@@ -0,0 +1,492 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
+ <interface name="org.gnome.SessionManager">
+
+ <!-- Initialization phase interfaces -->
+
+ <method name="Setenv">
+ <arg name="variable" type="s" direction="in">
+ <doc:doc>
+ <doc:summary>The variable name</doc:summary>
+ </doc:doc>
+ </arg>
+ <arg name="value" type="s" direction="in">
+ <doc:doc>
+ <doc:summary>The value</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Adds the variable name to the application launch environment with the specified value. May only be used during the Session Manager initialization phase.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+
+ <method name="GetLocale">
+ <arg name="category" type="i" direction="in">
+ <doc:doc>
+ <doc:summary>The locale category</doc:summary>
+ </doc:doc>
+ </arg>
+ <arg name="value" type="s" direction="out">
+ <doc:doc>
+ <doc:summary>The value</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Reads the current state of the specific locale category.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+
+ <method name="InitializationError">
+ <arg name="message" type="s" direction="in">
+ <doc:doc>
+ <doc:summary>The error message</doc:summary>
+ </doc:doc>
+ </arg>
+ <arg name="fatal" type="b" direction="in">
+ <doc:doc>
+ <doc:summary>Whether the error should be treated as fatal</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>May be used by applications launched during the Session Manager initialization phase to indicate there was a problem.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+
+ <method name="Initialized">
+ <doc:doc>
+ <doc:description>
+ <doc:para>Run from systemd to signal that gnome-session-initialized.target has been reached.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+
+ <!-- Running phase interfaces -->
+
+ <method name="RegisterClient">
+ <arg type="s" name="app_id" direction="in">
+ <doc:doc>
+ <doc:summary>The application identifier</doc:summary>
+ </doc:doc>
+ </arg>
+ <arg type="s" name="client_startup_id" direction="in">
+ <doc:doc>
+ <doc:summary>Client startup identifier</doc:summary>
+ </doc:doc>
+ </arg>
+ <arg type="o" name="client_id" direction="out">
+ <doc:doc>
+ <doc:summary>The object path of the newly registered client</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Register the caller as a Session Management client.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+
+ <method name="UnregisterClient">
+ <arg type="o" name="client_id" direction="in">
+ <doc:doc>
+ <doc:summary>The object path of the client</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Unregister the specified client from Session Management.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+
+ <method name="Inhibit">
+ <arg type="s" name="app_id" direction="in">
+ <doc:doc>
+ <doc:summary>The application identifier</doc:summary>
+ </doc:doc>
+ </arg>
+ <arg type="u" name="toplevel_xid" direction="in">
+ <doc:doc>
+ <doc:summary>The toplevel X window identifier</doc:summary>
+ </doc:doc>
+ </arg>
+ <arg type="s" name="reason" direction="in">
+ <doc:doc>
+ <doc:summary>The reason for the inhibit</doc:summary>
+ </doc:doc>
+ </arg>
+ <arg type="u" name="flags" direction="in">
+ <doc:doc>
+ <doc:summary>Flags that specify what should be inhibited</doc:summary>
+ </doc:doc>
+ </arg>
+ <arg type="u" name="inhibit_cookie" direction="out">
+ <doc:doc>
+ <doc:summary>The cookie</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:summary>
+ Proactively indicates that the calling application is performing an action that should not be interrupted and sets a reason to be displayed to the user when an interruption is about to take place.
+ </doc:summary>
+ <doc:description>
+ <doc:para>Applications should invoke this method when they begin an operation that
+ should not be interrupted, such as creating a CD or DVD. The types of actions
+ that may be blocked are specified by the flags parameter. When the application
+ completes the operation it should call <doc:ref type="method" to="org.gnome.SessionManager.Uninhibit">Uninhibit()</doc:ref>
+ or disconnect from the session bus.
+ </doc:para>
+ <doc:para>
+ Applications should not expect that they will always be able to block the
+ action. In most cases, users will be given the option to force the action
+ to take place.
+ </doc:para>
+ <doc:para>
+ Reasons should be short and to the point.
+ </doc:para>
+ <doc:para>
+ The flags parameter must include at least one of the following:
+ <doc:list>
+ <doc:item>
+ <doc:term>1</doc:term>
+ <doc:definition>Inhibit logging out</doc:definition>
+ </doc:item>
+ <doc:item>
+ <doc:term>2</doc:term>
+ <doc:definition>Inhibit user switching</doc:definition>
+ </doc:item>
+ <doc:item>
+ <doc:term>4</doc:term>
+ <doc:definition>Inhibit suspending the session or computer</doc:definition>
+ </doc:item>
+ <doc:item>
+ <doc:term>8</doc:term>
+ <doc:definition>Inhibit the session being marked as idle</doc:definition>
+ </doc:item>
+ <doc:item>
+ <doc:term>16</doc:term>
+ <doc:definition>Inhibit auto-mounting removable media for the session</doc:definition>
+ </doc:item>
+ </doc:list>
+ Values for flags may be bitwise or'ed together.
+ </doc:para>
+ <doc:para>
+ The returned cookie is used to uniquely identify this request. It should be used
+ as an argument to <doc:ref type="method" to="org.gnome.SessionManager.Uninhibit">Uninhibit()</doc:ref> in
+ order to remove the request.
+ </doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+
+ <method name="Uninhibit">
+ <arg type="u" name="inhibit_cookie" direction="in">
+ <doc:doc>
+ <doc:summary>The cookie</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Cancel a previous call to <doc:ref type="method" to="org.gnome.SessionManager.Inhibit">Inhibit()</doc:ref> identified by the cookie.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+
+ <method name="IsInhibited">
+ <arg type="u" name="flags" direction="in">
+ <doc:doc>
+ <doc:summary>Flags that specify what should be inhibited</doc:summary>
+ </doc:doc>
+ </arg>
+ <arg type="b" name="is_inhibited" direction="out">
+ <doc:doc>
+ <doc:summary>Returns TRUE if any of the operations in the bitfield flags are inhibited</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Determine if operation(s) specified by the flags
+ are currently inhibited. Flags are same as those accepted
+ by the
+ <doc:ref type="method" to="org.gnome.SessionManager.Inhibit">Inhibit()</doc:ref>
+ method.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+
+ <method name="GetClients">
+ <arg name="clients" direction="out" type="ao">
+ <doc:doc>
+ <doc:summary>an array of client IDs</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>This gets a list of all the <doc:ref type="interface" to="org.gnome.SessionManager.Client">Clients</doc:ref>
+ that are currently known to the session manager.</doc:para>
+ <doc:para>Each Client ID is an D-Bus object path for the object that implements the
+ <doc:ref type="interface" to="org.gnome.SessionManager.Client">Client</doc:ref> interface.</doc:para>
+ </doc:description>
+ <doc:seealso><doc:ref type="interface" to="org.gnome.SessionManager.Client">org.gnome.SessionManager.Client</doc:ref></doc:seealso>
+ </doc:doc>
+ </method>
+
+ <method name="GetInhibitors">
+ <arg name="inhibitors" direction="out" type="ao">
+ <doc:doc>
+ <doc:summary>an array of inhibitor IDs</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>This gets a list of all the <doc:ref type="interface" to="org.gnome.SessionManager.Inhibitor">Inhibitors</doc:ref>
+ that are currently known to the session manager.</doc:para>
+ <doc:para>Each Inhibitor ID is an D-Bus object path for the object that implements the
+ <doc:ref type="interface" to="org.gnome.SessionManager.Inhibitor">Inhibitor</doc:ref> interface.</doc:para>
+ </doc:description>
+ <doc:seealso><doc:ref type="interface" to="org.gnome.SessionManager.Inhibitor">org.gnome.SessionManager.Inhibitor</doc:ref></doc:seealso>
+ </doc:doc>
+ </method>
+
+
+ <method name="IsAutostartConditionHandled">
+ <arg name="condition" direction="in" type="s">
+ <doc:doc>
+ <doc:summary>The autostart condition string</doc:summary>
+ </doc:doc>
+ </arg>
+ <arg name="handled" direction="out" type="b">
+ <doc:doc>
+ <doc:summary>True if condition is handled, false otherwise</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Allows the caller to determine whether the session manager is
+ handling changes to the specified autostart condition.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+
+ <method name="Shutdown">
+ <doc:doc>
+ <doc:description>
+ <doc:para>Request a shutdown dialog.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+
+ <method name="Reboot">
+ <doc:doc>
+ <doc:description>
+ <doc:para>Request a reboot dialog.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+
+ <method name="CanShutdown">
+ <arg name="is_available" direction="out" type="b">
+ <doc:doc>
+ <doc:summary>True if shutdown is available to the user, false otherwise</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Allows the caller to determine whether or not it's okay to show
+ a shutdown option in the UI</doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+
+ <method name="SetRebootToFirmwareSetup">
+ <arg name="enable" direction="in" type="b">
+ <doc:doc>
+ <doc:summary>Whether we should reboot into setup</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Allows the caller to indicate to the system's firmware to boot into setup mode</doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+
+ <method name="CanRebootToFirmwareSetup">
+ <arg name="is_available" direction="out" type="b">
+ <doc:doc>
+ <doc:summary>True if boot into setup mode is available to the user, false otherwise</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Allows the caller to determine whether or not it's okay to show
+ a reboot to firmware option in the UI</doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+
+ <method name="Logout">
+ <arg name="mode" type="u" direction="in">
+ <doc:doc>
+ <doc:summary>The type of logout that is being requested</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Request a logout dialog</doc:para>
+ <doc:para>
+ Allowed values for the mode parameter are:
+ <doc:list>
+ <doc:item>
+ <doc:term>0</doc:term>
+ <doc:definition>Normal.</doc:definition>
+ </doc:item>
+ <doc:item>
+ <doc:term>1</doc:term>
+ <doc:definition>No confirmation interface should be shown.</doc:definition>
+ </doc:item>
+ <doc:item>
+ <doc:term>2</doc:term>
+ <doc:definition>Forcefully logout. No confirmation will be shown and any inhibitors will be ignored.</doc:definition>
+ </doc:item>
+ </doc:list>
+ Values for flags may be bitwise or'ed together.
+ </doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+
+ <method name="IsSessionRunning">
+ <arg name="running" direction="out" type="b">
+ <doc:doc>
+ <doc:summary>True if the session has entered the Running phase, false otherwise</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Allows the caller to determine whether the session manager
+ has entered the Running phase, in case the client missed the
+ SessionRunning signal.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+
+ <!-- Signals -->
+
+ <signal name="ClientAdded">
+ <arg name="id" type="o">
+ <doc:doc>
+ <doc:summary>The object path for the added client</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Emitted when a client has been added to the session manager.
+ </doc:para>
+ </doc:description>
+ </doc:doc>
+ </signal>
+ <signal name="ClientRemoved">
+ <arg name="id" type="o">
+ <doc:doc>
+ <doc:summary>The object path for the removed client</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Emitted when a client has been removed from the session manager.
+ </doc:para>
+ </doc:description>
+ </doc:doc>
+ </signal>
+
+ <signal name="InhibitorAdded">
+ <arg name="id" type="o">
+ <doc:doc>
+ <doc:summary>The object path for the added inhibitor</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Emitted when an inhibitor has been added to the session manager.
+ </doc:para>
+ </doc:description>
+ </doc:doc>
+ </signal>
+ <signal name="InhibitorRemoved">
+ <arg name="id" type="o">
+ <doc:doc>
+ <doc:summary>The object path for the removed inhibitor</doc:summary>
+ </doc:doc>
+ </arg>
+ <doc:doc>
+ <doc:description>
+ <doc:para>Emitted when an inhibitor has been removed from the session manager.
+ </doc:para>
+ </doc:description>
+ </doc:doc>
+ </signal>
+
+ <signal name="SessionRunning">
+ <doc:doc>
+ <doc:description>
+ <doc:para>Indicates the session has entered the Running phase.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </signal>
+
+ <signal name="SessionOver">
+ <doc:doc>
+ <doc:description>
+ <doc:para>Indicates the session is about to end.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </signal>
+
+ <!-- Properties -->
+
+ <property name="SessionName" type="s" access="read">
+ <doc:doc>
+ <doc:description>
+ <doc:para>The name of the session that has been loaded.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </property>
+
+ <property name="Renderer" type="s" access="read">
+ <doc:doc>
+ <doc:description>
+ <doc:para>The renderer for the session that has been loaded.
+ At the moment this supports GL and GLES, and is only used for the
+ X session.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </property>
+
+ <property name="SessionIsActive" type="b" access="read">
+ <doc:doc>
+ <doc:description>
+ <doc:para>If true, the session is currently in the
+ foreground and available for user input.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </property>
+
+ <property name="InhibitedActions" type="u" access="read">
+ <doc:doc>
+ <doc:description>
+ <doc:para>A bitmask of flags to indicate which actions
+ are inhibited. See the Inhibit() function's description
+ for a list of possible values.</doc:para>
+ </doc:description>
+ </doc:doc>
+ </property>
+
+ </interface>
+</node>
diff --git a/gnome-session/test-client-dbus.c b/gnome-session/test-client-dbus.c
new file mode 100644
index 0000000..7d6f9e4
--- /dev/null
+++ b/gnome-session/test-client-dbus.c
@@ -0,0 +1,265 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * 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
+ * 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <gio/gio.h>
+
+#define SM_DBUS_NAME "org.gnome.SessionManager"
+#define SM_DBUS_PATH "/org/gnome/SessionManager"
+#define SM_DBUS_INTERFACE "org.gnome.SessionManager"
+
+#define SM_CLIENT_DBUS_INTERFACE "org.gnome.SessionManager.ClientPrivate"
+
+static GDBusConnection *connection = NULL;
+static GDBusProxy *sm_proxy = NULL;
+static char *client_id = NULL;
+static GDBusProxy *client_proxy = NULL;
+static GMainLoop *main_loop = NULL;
+
+static gboolean
+session_manager_connect (void)
+{
+
+ if (connection == NULL) {
+ GError *error;
+
+ error = NULL;
+ connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+ if (connection == NULL) {
+ g_message ("Failed to connect to the session bus: %s",
+ error->message);
+ g_error_free (error);
+ exit (1);
+ }
+ }
+
+ sm_proxy = g_dbus_proxy_new_sync (connection,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ SM_DBUS_NAME,
+ SM_DBUS_PATH,
+ SM_DBUS_INTERFACE,
+ NULL, NULL);
+ return (sm_proxy != NULL);
+}
+
+static void
+on_client_query_end_session (GDBusProxy *proxy,
+ GVariant *parameters)
+{
+ GError *error;
+ gboolean is_ok;
+ const char *reason;
+ guint flags;
+ GVariant *reply;
+
+ is_ok = FALSE;
+ reason = "Unsaved files";
+ g_variant_get (parameters, "(u)", &flags);
+
+ g_debug ("Got query end session signal flags=%u", flags);
+
+ error = NULL;
+ reply = g_dbus_proxy_call_sync (proxy,
+ "EndSessionResponse",
+ g_variant_new ("(bs)",
+ is_ok,
+ reason),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, &error);
+
+ if (error != NULL) {
+ g_warning ("Failed to respond to EndSession: %s", error->message);
+ g_error_free (error);
+ } else {
+ g_variant_unref (reply);
+ }
+}
+
+static void
+on_client_end_session (GVariant *parameters)
+{
+ guint flags;
+ g_variant_get (parameters, "(u)", &flags);
+ g_debug ("Got end session signal flags=%u", flags);
+}
+
+static void
+on_client_cancel_end_session (void)
+{
+ g_debug ("Got end session cancelled signal");
+}
+
+static void
+on_client_stop (void)
+{
+ g_debug ("Got client stop signal");
+ g_main_loop_quit (main_loop);
+}
+
+static void
+on_client_dbus_signal (GDBusProxy *proxy,
+ gchar *sender_name,
+ gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ if (g_strcmp0 (signal_name, "Stop") == 0) {
+ on_client_stop ();
+ } else if (g_strcmp0 (signal_name, "CancelEndSession") == 0) {
+ on_client_cancel_end_session ();
+ } else if (g_strcmp0 (signal_name, "EndSession") == 0) {
+ on_client_end_session (parameters);
+ } else if (g_strcmp0 (signal_name, "QueryEndSession") == 0) {
+ on_client_query_end_session (proxy, parameters);
+ }
+}
+
+static gboolean
+register_client (void)
+{
+ GError *error;
+ GVariant *object_path_variant;
+ const char *startup_id;
+ const char *app_id;
+
+ app_id = "gedit";
+
+ startup_id = g_getenv ("DESKTOP_AUTOSTART_ID");
+ if (!startup_id) {
+ startup_id = "";
+ }
+
+ error = NULL;
+ object_path_variant = g_dbus_proxy_call_sync (sm_proxy,
+ "RegisterClient",
+ g_variant_new ("(ss)",
+ app_id,
+ startup_id),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, &error);
+
+ if (error != NULL) {
+ g_warning ("Failed to register client: %s", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ g_variant_get (object_path_variant, "(o)", &client_id);
+ g_debug ("Client registered with session manager: %s", client_id);
+
+ client_proxy = g_dbus_proxy_new_sync (connection,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ SM_DBUS_NAME,
+ client_id,
+ SM_CLIENT_DBUS_INTERFACE,
+ NULL, NULL);
+ if (client_proxy != NULL) {
+ g_signal_connect (client_proxy, "g-signal",
+ G_CALLBACK (on_client_dbus_signal), NULL);
+ }
+
+ g_variant_unref (object_path_variant);
+
+ return (client_proxy != NULL);
+}
+
+static gboolean
+session_manager_disconnect (void)
+{
+ if (sm_proxy != NULL) {
+ g_object_unref (sm_proxy);
+ sm_proxy = NULL;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+unregister_client (void)
+{
+ GError *error;
+ GVariant *reply;
+
+ error = NULL;
+ reply = g_dbus_proxy_call_sync (sm_proxy,
+ "UnregisterClient",
+ g_variant_new ("(o)", client_id),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, &error);
+ if (error != NULL) {
+ g_warning ("Failed to unregister client: %s", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ g_free (client_id);
+ client_id = NULL;
+
+ g_variant_unref (reply);
+
+ return TRUE;
+}
+
+static gboolean
+quit_test (gpointer data)
+{
+ g_main_loop_quit (main_loop);
+ return FALSE;
+}
+
+int
+main (int argc,
+ char *argv[])
+{
+ gboolean res;
+
+ g_log_set_always_fatal (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING);
+
+ res = session_manager_connect ();
+ if (! res) {
+ g_warning ("Unable to connect to session manager");
+ exit (1);
+ }
+
+ res = register_client ();
+ if (! res) {
+ g_warning ("Unable to register client with session manager");
+ }
+
+ main_loop = g_main_loop_new (NULL, FALSE);
+
+ g_timeout_add_seconds (30, quit_test, NULL);
+
+ g_main_loop_run (main_loop);
+ g_main_loop_unref (main_loop);
+
+ unregister_client ();
+ session_manager_disconnect ();
+
+ return 0;
+}
diff --git a/gnome-session/test-inhibit.c b/gnome-session/test-inhibit.c
new file mode 100644
index 0000000..0a8417e
--- /dev/null
+++ b/gnome-session/test-inhibit.c
@@ -0,0 +1,197 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * 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
+ * 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+
+#define SM_DBUS_NAME "org.gnome.SessionManager"
+#define SM_DBUS_PATH "/org/gnome/SessionManager"
+#define SM_DBUS_INTERFACE "org.gnome.SessionManager"
+
+static GDBusConnection *connection = NULL;
+static GDBusProxy *sm_proxy = NULL;
+static guint cookie = 0;
+
+static gboolean
+session_manager_connect (void)
+{
+
+ if (connection == NULL) {
+ GError *error;
+
+ error = NULL;
+ connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+ if (connection == NULL) {
+ g_message ("Failed to connect to the session bus: %s",
+ error->message);
+ g_error_free (error);
+ exit (1);
+ }
+ }
+
+ sm_proxy = g_dbus_proxy_new_sync (connection,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ SM_DBUS_NAME,
+ SM_DBUS_PATH,
+ SM_DBUS_INTERFACE,
+ NULL, NULL);
+ return (sm_proxy != NULL);
+}
+
+typedef enum {
+ GSM_INHIBITOR_FLAG_LOGOUT = 1 << 0,
+ GSM_INHIBITOR_FLAG_SWITCH_USER = 1 << 1,
+ GSM_INHIBITOR_FLAG_SUSPEND = 1 << 2
+} GsmInhibitFlag;
+
+static gboolean
+do_inhibit_for_window (GdkWindow *window)
+{
+ GError *error;
+ GVariant *cookie_variant;
+ const char *app_id;
+ const char *reason;
+ guint toplevel_xid;
+ guint flags;
+
+#if 1
+ app_id = "nautilus-cd-burner";
+ reason = "A CD burn is in progress.";
+#else
+ app_id = "nautilus";
+ reason = "A file transfer is in progress.";
+#endif
+ toplevel_xid = gdk_x11_window_get_xid (window);
+ flags = GSM_INHIBITOR_FLAG_LOGOUT
+ | GSM_INHIBITOR_FLAG_SWITCH_USER
+ | GSM_INHIBITOR_FLAG_SUSPEND;
+
+ error = NULL;
+ cookie_variant = g_dbus_proxy_call_sync (sm_proxy,
+ "Inhibit",
+ g_variant_new ("(susu)",
+ app_id,
+ toplevel_xid,
+ reason,
+ flags),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, &error);
+
+ if (error != NULL) {
+ g_warning ("Failed to inhibit: %s", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ g_variant_get (cookie_variant, "(u)", &cookie);
+ g_debug ("Inhibiting session manager: %u", cookie);
+ g_variant_unref (cookie_variant);
+
+ return TRUE;
+}
+static gboolean
+session_manager_disconnect (void)
+{
+ g_clear_object (&sm_proxy);
+ g_clear_object (&connection);
+
+ return TRUE;
+}
+
+static gboolean
+do_uninhibit (void)
+{
+ GError *error;
+ GVariant *reply;
+
+ error = NULL;
+ reply = g_dbus_proxy_call_sync (sm_proxy,
+ "Uninhibit",
+ g_variant_new ("(u)", cookie),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, &error);
+ if (error != NULL) {
+ g_warning ("Failed to uninhibit: %s", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ g_variant_unref (reply);
+
+ cookie = 0;
+
+ return TRUE;
+}
+
+static void
+on_widget_show (GtkWidget *dialog,
+ gpointer data)
+{
+ gboolean res;
+
+ res = do_inhibit_for_window (gtk_widget_get_window (dialog));
+ if (! res) {
+ g_warning ("Unable to register client with session manager");
+ }
+}
+
+int
+main (int argc,
+ char *argv[])
+{
+ gboolean res;
+ GtkWidget *dialog;
+
+ g_log_set_always_fatal (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING);
+
+ gtk_init (&argc, &argv);
+
+ res = session_manager_connect ();
+ if (! res) {
+ g_warning ("Unable to connect to session manager");
+ exit (1);
+ }
+
+ g_timeout_add_seconds (30, (GSourceFunc)gtk_main_quit, NULL);
+
+ dialog = gtk_message_dialog_new (NULL,
+ 0,
+ GTK_MESSAGE_INFO,
+ GTK_BUTTONS_CANCEL,
+ "Inhibiting logout, switch user, and suspend.");
+
+ g_signal_connect (dialog, "response", G_CALLBACK (gtk_main_quit), NULL);
+ g_signal_connect (dialog, "show", G_CALLBACK (on_widget_show), NULL);
+ gtk_widget_show (dialog);
+
+ gtk_main ();
+
+ do_uninhibit ();
+ session_manager_disconnect ();
+
+ return 0;
+}
diff --git a/gnome-session/test-process-helper.c b/gnome-session/test-process-helper.c
new file mode 100644
index 0000000..e48caa8
--- /dev/null
+++ b/gnome-session/test-process-helper.c
@@ -0,0 +1,56 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 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
+ * 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 <stdlib.h>
+
+#include "gsm-process-helper.h"
+
+int
+main (int argc,
+ char *argv[])
+{
+ char *command_line = "xeyes";
+ int timeout = 500;
+ GError *error = NULL;
+
+ if (argc > 3) {
+ g_printerr ("Too many arguments.\n");
+ g_printerr ("Usage: %s [COMMAND] [TIMEOUT]\n", argv[0]);
+ return 1;
+ }
+
+ if (argc >= 2)
+ command_line = argv[1];
+ if (argc >= 3) {
+ int i = atoi (argv[2]);
+ if (i > 0)
+ timeout = i;
+ }
+
+ if (!gsm_process_helper (command_line, timeout, &error)) {
+ g_warning ("%s", error->message);
+ g_clear_error (&error);
+ } else {
+ g_print ("Command exited successfully.\n");
+ }
+
+ return 0;
+}