summaryrefslogtreecommitdiffstats
path: root/gnome-session/gsm-manager.c
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/gsm-manager.c
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/gsm-manager.c')
-rw-r--r--gnome-session/gsm-manager.c3928
1 files changed, 3928 insertions, 0 deletions
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 */
+}