/* -*- 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street - Suite 500, Boston, MA 02110-1335, USA.
 *
 */

#include "config.h"

#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <signal.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 <canberra.h>

#include "csm-manager.h"
#include "csm-exported-manager.h"
#include "csm-exported-dialog.h"

#include "csm-store.h"
#include "csm-inhibitor.h"
#include "csm-presence.h"

#include "csm-xsmp-server.h"
#include "csm-xsmp-client.h"
#include "csm-dbus-client.h"

#include "csm-autostart-app.h"

#include "csm-util.h"
#include "mdm.h"
#include "csm-system.h"
#include "csm-session-save.h"
#include "inhibit-dialog-info.h"

#define CSM_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CSM_TYPE_MANAGER, CsmManagerPrivate))

#define CSM_MANAGER_DBUS_PATH "/org/gnome/SessionManager"
#define CSM_MANAGER_DBUS_NAME "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 CSM_MANAGER_PHASE_TIMEOUT 30 /* seconds */

#define MDM_FLEXISERVER_COMMAND "mdmflexiserver"
#define MDM_FLEXISERVER_ARGS    "--startnew Standard"

#define GDM_FLEXISERVER_COMMAND "gdmflexiserver"
#define GDM_FLEXISERVER_ARGS    "--startnew Standard"

#define SESSION_SCHEMA            "org.cinnamon.desktop.session"
#define KEY_IDLE_DELAY            "idle-delay"
#define KEY_SESSION_NAME          "session-name"

#define CSM_MANAGER_SCHEMA        "org.cinnamon.SessionManager"
#define KEY_AUTOSAVE              "auto-save-session"
#define KEY_LOGOUT_PROMPT         "logout-prompt"
#define KEY_FORCE_GTK_END_SESSION "force-gtk-end-session-dialog"
#define KEY_BLACKLIST             "autostart-blacklist"
#define KEY_PREFER_HYBRID_SLEEP   "prefer-hybrid-sleep"
#define KEY_SUSPEND_HIBERNATE     "suspend-then-hibernate"
#define KEY_DEBUG                 "debug"

#define POWER_SETTINGS_SCHEMA     "org.cinnamon.settings-daemon.plugins.power"
#define KEY_LOCK_ON_SUSPEND       "lock-on-suspend"

#define LOCKDOWN_SCHEMA           "org.cinnamon.desktop.lockdown"
#define KEY_DISABLE_LOG_OUT       "disable-log-out"
#define KEY_DISABLE_USER_SWITCHING "disable-user-switching"

#define CINNAMON_SCHEMA           "org.cinnamon"
#define KEY_ENABLE_POLKIT_AGENT   "enable-polkit-agent"
#define POLKIT_MATE_AGENT_ID      "polkit-mate-authentication-agent-1"

static void app_registered (CsmApp     *app, CsmManager *manager);

typedef enum
{
        CSM_MANAGER_LOGOUT_NONE,
        CSM_MANAGER_LOGOUT_LOGOUT,
        CSM_MANAGER_LOGOUT_REBOOT,
        CSM_MANAGER_LOGOUT_REBOOT_INTERACT,
        CSM_MANAGER_LOGOUT_REBOOT_MDM,
        CSM_MANAGER_LOGOUT_SHUTDOWN,
        CSM_MANAGER_LOGOUT_SHUTDOWN_INTERACT,
        CSM_MANAGER_LOGOUT_SHUTDOWN_MDM
} CsmManagerLogoutType;

struct CsmManagerPrivate
{
        gboolean                failsafe;
        CsmStore               *clients;
        CsmStore               *inhibitors;
        CsmInhibitorFlag        inhibited_actions;
        CsmStore               *apps;
        CsmPresence            *presence;
        CsmXsmpServer          *xsmp_server;

        char                   *session_name;
        gboolean                is_fallback_session : 1;

        /* Current status */
        CsmManagerPhase         phase;
        guint                   phase_timeout_id;
        GSList                 *required_apps;
        GSList                 *pending_apps;
        CsmManagerLogoutMode    logout_mode;
        GSList                 *query_clients;
        guint                   query_timeout_id;
        /* This is used for CSM_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 */
        CsmManagerLogoutType    logout_type;

        /* List of clients which were disconnected due to disabled condition
         * and shouldn't be automatically restarted */
        GSList                 *condition_clients;

        GSettings              *settings;
        GSettings              *session_settings;
        GSettings              *power_settings;
        GSettings              *lockdown_settings;

        CsmSystem              *system;

        GDBusProxy             *bus_proxy;
        GDBusConnection        *connection;
        CsmExportedManager     *skeleton;
        CsmExportedDialog      *dialog_skeleton;
        GDBusProxy             *cinnamon_proxy;

        CsmLogoutAction         dialog_action;

        gboolean                dbus_disconnected : 1;
        guint                   name_owner_id;

        ca_context             *ca;
        gboolean               logout_sound_is_playing;

};

enum {
        PROP_0,
        PROP_CLIENT_STORE,
        PROP_SESSION_NAME,
        PROP_FALLBACK,
        PROP_FAILSAFE
};

enum {
        PHASE_CHANGED,
        LAST_SIGNAL
};

typedef enum {
        DIALOG_MODE_REBOOT,
        DIALOG_MODE_SHUTDOWN,
        DIALOG_MODE_LOGOUT
} DialogMode;

static guint signals [LAST_SIGNAL] = { 0 };

static void     show_shutdown_dialog (CsmManager *manager,
                                      gboolean    is_reboot);
static void     close_end_session_dialog     (CsmManager *manager);
static void     show_logout_dialog   (CsmManager *manager);

static void     user_logout (CsmManager           *manager,
                             CsmManagerLogoutMode  mode);
static void     request_shutdown (CsmManager *manager);
static void     request_reboot (CsmManager *manager);
static void     request_logout (CsmManager *manager, CsmManagerLogoutMode mode);
static void     request_suspend (CsmManager *manager);
static void     request_switch_user (CsmManager *manager);
static void     request_hibernate (CsmManager *manager);

static void     maybe_save_session   (CsmManager *manager);
static void     maybe_play_logout_sound (CsmManager *manager);

static gboolean _log_out_is_locked_down     (CsmManager *manager);
static gboolean _switch_user_is_locked_down (CsmManager *manager);

static void     _handle_client_end_session_response (CsmManager *manager,
                                                     CsmClient  *client,
                                                     gboolean    is_ok,
                                                     gboolean    do_last,
                                                     gboolean    cancel,
                                                     const char *reason);
static gpointer manager_object = NULL;

G_DEFINE_TYPE (CsmManager, csm_manager, G_TYPE_OBJECT)

#define CSM_MANAGER_DBUS_IFACE "org.gnome.SessionManager"

static const GDBusErrorEntry csm_manager_error_entries[] = {
        { CSM_MANAGER_ERROR_GENERAL,               CSM_MANAGER_DBUS_IFACE ".GeneralError" },
        { CSM_MANAGER_ERROR_NOT_IN_INITIALIZATION, CSM_MANAGER_DBUS_IFACE ".NotInInitialization" },
        { CSM_MANAGER_ERROR_NOT_IN_RUNNING,        CSM_MANAGER_DBUS_IFACE ".NotInRunning" },
        { CSM_MANAGER_ERROR_ALREADY_REGISTERED,    CSM_MANAGER_DBUS_IFACE ".AlreadyRegistered" },
        { CSM_MANAGER_ERROR_NOT_REGISTERED,        CSM_MANAGER_DBUS_IFACE ".NotRegistered" },
        { CSM_MANAGER_ERROR_INVALID_OPTION,        CSM_MANAGER_DBUS_IFACE ".InvalidOption" },
        { CSM_MANAGER_ERROR_LOCKED_DOWN,           CSM_MANAGER_DBUS_IFACE ".LockedDown" }
};

GQuark
csm_manager_error_quark (void)
{
        static volatile gsize quark_volatile = 0;

        g_dbus_error_register_error_domain ("csm_manager_error",
                                            &quark_volatile,
                                            csm_manager_error_entries,
                                            G_N_ELEMENTS (csm_manager_error_entries));

        return quark_volatile;
}

static gboolean
start_app_or_warn (CsmManager *manager,
                   CsmApp     *app)
{
        gboolean res;
        GError *error = NULL;

        g_debug ("CsmManager: starting app '%s'", csm_app_peek_id (app));

        res = csm_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 (CsmManager *manager,
                 CsmApp     *app)
{
        return g_slist_find (manager->priv->required_apps, app) != NULL;
}

static void
on_required_app_failure (CsmManager  *manager,
                         CsmApp      *app)
{
        if (csm_util_is_wayland_session () && csm_app_provides (app, "windowmanager")) {
                /* In Wayland, if cinnamon-wayland fails to start there's no compositor, so there's
                nothing left to keep the session up for - terminate it and go back to the DM. Any
                other required component failing is survivable, and killing the session over it
                loses the user's work. */
                csm_util_init_error (TRUE, "A program required by the session failed to start. App ID: '%s'. Startup ID: '%s'. The session will be terminated.",
                                     csm_app_peek_app_id (app),
                                     csm_app_peek_startup_id (app));
        } else {
                /* In Xorg, even if Cinnamon fails to start we want to show nemo-desktop and cinnamon-killer-daemon.
                So if a required component fails, we still keep the session alive. */
                csm_util_init_error (FALSE, "A program required by the session failed to start. App ID: '%s'. Startup ID: '%s'.",
                                     csm_app_peek_app_id (app),
                                     csm_app_peek_startup_id (app));
        }
}

static gboolean
_debug_client (const char *id,
               CsmClient  *client,
               CsmManager *manager)
{
        g_debug ("CsmManager: Client %s", csm_client_peek_id (client));
        return FALSE;
}

static void
debug_clients (CsmManager *manager)
{
        csm_store_foreach (manager->priv->clients,
                           (CsmStoreFunc)_debug_client,
                           manager);
}

static gboolean
_debug_inhibitor (const char    *id,
                  CsmInhibitor  *inhibitor,
                  CsmManager    *manager)
{
        g_debug ("CsmManager: Inhibitor app:%s client:%s bus-name:%s reason:%s",
                 csm_inhibitor_peek_app_id (inhibitor),
                 csm_inhibitor_peek_client_id (inhibitor),
                 csm_inhibitor_peek_bus_name (inhibitor),
                 csm_inhibitor_peek_reason (inhibitor));
        return FALSE;
}

static void
debug_inhibitors (CsmManager *manager)
{
        csm_store_foreach (manager->priv->inhibitors,
                           (CsmStoreFunc)_debug_inhibitor,
                           manager);
}

static gboolean
_find_by_cookie (const char   *id,
                 CsmInhibitor *inhibitor,
                 guint        *cookie_ap)
{
        guint cookie_b;

        cookie_b = csm_inhibitor_peek_cookie (inhibitor);

        return (*cookie_ap == cookie_b);
}

static gboolean
_client_has_startup_id (const char *id,
                        CsmClient  *client,
                        const char *startup_id_a)
{
        const char *startup_id_b;

        startup_id_b = csm_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 (CsmApp     *app,
                       gboolean    condition,
                       CsmManager *manager)
{
        CsmClient *client;

        g_debug ("CsmManager: app:%s condition changed condition:%d",
                 csm_app_peek_id (app),
                 condition);

        client = (CsmClient *)csm_store_find (manager->priv->clients,
                                              (CsmStoreFunc)_client_has_startup_id,
                                              (char *)csm_app_peek_startup_id (app));

        if (condition) {
                if (!csm_app_is_running (app) && client == NULL) {
                        start_app_or_warn (manager, app);
                } else {
                        g_debug ("CsmManager: not starting - app still running '%s'", csm_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 */
                        manager->priv->condition_clients =
                                g_slist_prepend (manager->priv->condition_clients, client);

                        g_debug ("CsmManager: stopping client %s for app", csm_client_peek_id (client));

                        error = NULL;
                        res = csm_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 ("CsmManager: stopping app %s", csm_app_peek_id (app));

                        /* If we don't have a client then we should try to kill the app ,
                         * if it is running */
                        error = NULL;
                        if (csm_app_is_running (app)) {
                                res = csm_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 CSM_MANAGER_PHASE_STARTUP:
                name = "STARTUP";
                break;
        case CSM_MANAGER_PHASE_EARLY_INITIALIZATION:
                name = "EARLY_INITIALIZATION";
                break;
        case CSM_MANAGER_PHASE_PRE_DISPLAY_SERVER:
                name = "PRE_DISPLAY_SERVER";
                break;
        case CSM_MANAGER_PHASE_DISPLAY_SERVER:
                name = "DISPLAY_SERVER";
                break;
        case CSM_MANAGER_PHASE_INITIALIZATION:
                name = "INITIALIZATION";
                break;
        case CSM_MANAGER_PHASE_WINDOW_MANAGER:
                name = "WINDOW_MANAGER";
                break;
        case CSM_MANAGER_PHASE_PANEL:
                name = "PANEL";
                break;
        case CSM_MANAGER_PHASE_DESKTOP:
                name = "DESKTOP";
                break;
        case CSM_MANAGER_PHASE_APPLICATION:
                name = "APPLICATION";
                break;
        case CSM_MANAGER_PHASE_RUNNING:
                name = "RUNNING";
                break;
        case CSM_MANAGER_PHASE_QUERY_END_SESSION:
                name = "QUERY_END_SESSION";
                break;
        case CSM_MANAGER_PHASE_END_SESSION:
                name = "END_SESSION";
                break;
        case CSM_MANAGER_PHASE_EXIT:
                name = "EXIT";
                break;
        default:
                g_assert_not_reached ();
                break;
        }

        return name;
}

static void start_phase (CsmManager *manager);

static void
quit_request_failed (CsmSystem *system,
                        GError    *error,
                        gpointer   user_data)
{
        g_warning ("Using an MDM logout action to shutdown/reboot the system.");
        MdmLogoutAction fallback_action = GPOINTER_TO_INT (user_data);
        mdm_set_logout_action (fallback_action);
        csm_quit ();
}

static void
csm_manager_quit (CsmManager *manager)
{
        /* See the comment in request_reboot() for some more details about how
         * this works. */

        csm_store_clear (manager->priv->inhibitors);

        switch (manager->priv->logout_type) {
        case CSM_MANAGER_LOGOUT_LOGOUT:
                csm_quit ();
                break;
        case CSM_MANAGER_LOGOUT_REBOOT:
        case CSM_MANAGER_LOGOUT_REBOOT_INTERACT:
                g_warning ("Requesting system restart...");
                mdm_set_logout_action (MDM_LOGOUT_ACTION_NONE);
                g_signal_connect (manager->priv->system,
                                  "request-failed",
                                  G_CALLBACK (quit_request_failed),
                                  GINT_TO_POINTER (MDM_LOGOUT_ACTION_REBOOT));
                csm_system_attempt_restart (manager->priv->system);
                break;
        case CSM_MANAGER_LOGOUT_REBOOT_MDM:
                mdm_set_logout_action (MDM_LOGOUT_ACTION_REBOOT);
                csm_quit ();
                break;
        case CSM_MANAGER_LOGOUT_SHUTDOWN:
        case CSM_MANAGER_LOGOUT_SHUTDOWN_INTERACT:  
                g_warning ("Requesting system shutdown...");
                mdm_set_logout_action (MDM_LOGOUT_ACTION_NONE);
                g_signal_connect (manager->priv->system,
                                  "request-failed",
                                  G_CALLBACK (quit_request_failed),
                                  GINT_TO_POINTER (MDM_LOGOUT_ACTION_SHUTDOWN));
                csm_system_attempt_stop (manager->priv->system);
                break;
        case CSM_MANAGER_LOGOUT_SHUTDOWN_MDM:
                mdm_set_logout_action (MDM_LOGOUT_ACTION_SHUTDOWN);
                csm_quit ();
                break;
        default:
                g_assert_not_reached ();
                break;
        }
}

static void
end_phase (CsmManager *manager)
{
        gboolean start_next_phase = TRUE;

        g_debug ("CsmManager: ending phase %s",
                 phase_num_to_name (manager->priv->phase));

        g_slist_free (manager->priv->pending_apps);
        manager->priv->pending_apps = NULL;

        g_slist_free (manager->priv->query_clients);
        manager->priv->query_clients = NULL;

        g_slist_free (manager->priv->next_query_clients);
        manager->priv->next_query_clients = NULL;

        if (manager->priv->query_timeout_id > 0) {
                g_source_remove (manager->priv->query_timeout_id);
                manager->priv->query_timeout_id = 0;
        }
        if (manager->priv->phase_timeout_id > 0) {
                g_source_remove (manager->priv->phase_timeout_id);
                manager->priv->phase_timeout_id = 0;
        }

        switch (manager->priv->phase) {
        case CSM_MANAGER_PHASE_STARTUP:
        case CSM_MANAGER_PHASE_EARLY_INITIALIZATION:
        case CSM_MANAGER_PHASE_PRE_DISPLAY_SERVER:
        case CSM_MANAGER_PHASE_DISPLAY_SERVER:
        case CSM_MANAGER_PHASE_INITIALIZATION:
        case CSM_MANAGER_PHASE_WINDOW_MANAGER:
        case CSM_MANAGER_PHASE_PANEL:
        case CSM_MANAGER_PHASE_DESKTOP:
        case CSM_MANAGER_PHASE_APPLICATION:
                break;
        case CSM_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 CSM_MANAGER_PHASE_QUERY_END_SESSION:
                break;
        case CSM_MANAGER_PHASE_END_SESSION:
                maybe_play_logout_sound (manager);
                maybe_save_session (manager);
                break;
        case CSM_MANAGER_PHASE_EXIT:
                start_next_phase = FALSE;
                csm_manager_quit (manager);
                break;
        default:
                g_assert_not_reached ();
                break;
        }

        if (start_next_phase) {
                manager->priv->phase++;
                start_phase (manager);
        }
}

static void
app_event_during_startup (CsmManager *manager,
                          CsmApp     *app)
{
        if (!(manager->priv->phase < CSM_MANAGER_PHASE_APPLICATION))
                return;

        manager->priv->pending_apps = g_slist_remove (manager->priv->pending_apps, app);

        if (manager->priv->pending_apps == NULL) {
                if (manager->priv->phase_timeout_id > 0) {
                        g_source_remove (manager->priv->phase_timeout_id);
                        manager->priv->phase_timeout_id = 0;
                }

                end_phase (manager);
        }
}

static void
_restart_app (CsmManager *manager,
              CsmApp     *app)
{
        GError *error = NULL;

        /* In a Wayland session, Cinnamon is the compositor and display server.
        Restarting it after a crash would leave a bare compositor running while
        everything that depended on the display server (apps, the settings daemon,
        etc.) is already dead. Terminate the session instead and return the user
        to the login manager. */
        if (csm_util_is_wayland_session () && csm_app_provides (app, "windowmanager")) {
                g_critical ("Cinnamon ('%s') exited unexpectedly. Terminating the session.",
                            csm_app_peek_app_id (app));
                csm_quit ();
                return;
        }

        if (!csm_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 (CsmApp     *app,
          int         signal,
          CsmManager *manager)
{
        g_warning ("Application '%s' killed by signal %d", csm_app_peek_app_id (app), signal);

        if (csm_app_peek_autorestart (app)) {
                g_debug ("Component '%s' is autorestart, ignoring died signal",
                         csm_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 (CsmApp     *app,
            guchar      exit_code,
            CsmManager *manager)
{
        g_debug ("App %s exited with %d", csm_app_peek_app_id (app), exit_code);

        /* Consider that non-success exit status means "crash" for required components */
        if (exit_code != 0 && is_app_required (manager, app)) {
                if (csm_app_peek_autorestart (app)) {
                        g_debug ("Component '%s' is autorestart, ignoring non-successful exit",
                                 csm_app_peek_app_id (app));
                        return;
                }

                _restart_app (manager, app);
        } else {
                app_event_during_startup (manager, app);
        }
}

static void
app_registered (CsmApp     *app,
                CsmManager *manager)
{
        g_debug ("App %s registered", csm_app_peek_app_id (app));

        app_event_during_startup (manager, app);
}

static gboolean
on_phase_timeout (CsmManager *manager)
{
        GSList *a;

        manager->priv->phase_timeout_id = 0;

        switch (manager->priv->phase) {
        case CSM_MANAGER_PHASE_STARTUP:
        case CSM_MANAGER_PHASE_EARLY_INITIALIZATION:
        case CSM_MANAGER_PHASE_PRE_DISPLAY_SERVER:
        case CSM_MANAGER_PHASE_DISPLAY_SERVER:
        case CSM_MANAGER_PHASE_INITIALIZATION:
        case CSM_MANAGER_PHASE_WINDOW_MANAGER:
        case CSM_MANAGER_PHASE_PANEL:
        case CSM_MANAGER_PHASE_DESKTOP:
        case CSM_MANAGER_PHASE_APPLICATION:
                for (a = manager->priv->pending_apps; a; a = a->next) {
                        CsmApp *app = a->data;
                        g_warning ("Application '%s' failed to register before timeout",
                                   csm_app_peek_app_id (app));
                        if (is_app_required (manager, app))
                                on_required_app_failure (manager, app);
                }
                break;
        case CSM_MANAGER_PHASE_RUNNING:
                break;
        case CSM_MANAGER_PHASE_QUERY_END_SESSION:
        case CSM_MANAGER_PHASE_END_SESSION:
                break;
        case CSM_MANAGER_PHASE_EXIT:
                break;
        default:
                g_assert_not_reached ();
                break;
        }

        end_phase (manager);

        return FALSE;
}

static gboolean
_autostart_delay_timeout (CsmApp *app)
{
        GError *error = NULL;
        gboolean res;

        if (!csm_app_peek_is_disabled (app)
            && !csm_app_peek_is_conditionally_disabled (app)) {
                res = csm_app_start (app, &error);
                if (!res) {
                        if (error != NULL) {
                                g_warning ("Could not launch application '%s': %s",
                                           csm_app_peek_app_id (app),
                                           error->message);
                                g_error_free (error);
                        }
                }
        }

        g_object_unref (app);

        return FALSE;
}

static gboolean
_start_app (const char *id,
            CsmApp     *app,
            CsmManager *manager)
{
        int      delay;

        if (csm_app_peek_phase (app) != manager->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 (csm_app_peek_is_disabled (app)
            || csm_app_peek_is_conditionally_disabled (app)) {
                g_debug ("CsmManager: Skipping disabled app: %s", id);
                goto out;
        }

        delay = csm_app_peek_autostart_delay (app);
        if (delay > 0) {
                g_timeout_add_seconds (delay,
                                       (GSourceFunc)_autostart_delay_timeout,
                                       g_object_ref (app));
                g_debug ("CsmManager: %s is scheduled to start in %d seconds", id, delay);
                goto out;
        }

        if (!start_app_or_warn (manager, app))
                goto out;

        if (manager->priv->phase < CSM_MANAGER_PHASE_APPLICATION) {
                /* Historical note - apparently,
                 * e.g. gnome-settings-daemon used to "daemonize", and
                 * so cinnamon-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,
                                  "registered",
                                  G_CALLBACK (app_registered),
                                  manager);
                g_signal_connect (app,
                                  "died",
                                  G_CALLBACK (app_died),
                                  manager);
                manager->priv->pending_apps = g_slist_prepend (manager->priv->pending_apps, app);
        }
 out:
        return FALSE;
}

static void
do_phase_startup (CsmManager *manager)
{
        csm_store_foreach (manager->priv->apps,
                           (CsmStoreFunc)_start_app,
                           manager);

        if (manager->priv->pending_apps != NULL) {
                if (manager->priv->phase < CSM_MANAGER_PHASE_APPLICATION) {
                        manager->priv->phase_timeout_id = g_timeout_add_seconds (CSM_MANAGER_PHASE_TIMEOUT,
                                                                                 (GSourceFunc)on_phase_timeout,
                                                                                 manager);
                }
        } else {
                end_phase (manager);
        }
}

typedef struct {
        CsmManager *manager;
        guint       flags;
} ClientEndSessionData;


static gboolean
_client_end_session (CsmClient            *client,
                     ClientEndSessionData *data)
{
        gboolean ret;
        GError  *error;

        error = NULL;
        ret = csm_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 ("CsmManager: adding client to end-session clients: %s", csm_client_peek_id (client));
                data->manager->priv->query_clients = g_slist_prepend (data->manager->priv->query_clients,
                                                                      client);
        }

        return FALSE;
}

static gboolean
_client_end_session_helper (const char           *id,
                            CsmClient            *client,
                            ClientEndSessionData *data)
{
        return _client_end_session (client, data);
}

static void
do_phase_end_session (CsmManager *manager)
{
        ClientEndSessionData data;

        data.manager = manager;
        data.flags = 0;

        close_end_session_dialog (manager);

        csm_util_stop_systemd_unit ("cinnamon-session.target", "replace", NULL);

        if (manager->priv->logout_mode == CSM_MANAGER_LOGOUT_MODE_FORCE) {
                data.flags |= CSM_CLIENT_END_SESSION_FLAG_FORCEFUL;
        }
        if (csm_manager_get_autosave_enabled (manager)) {
                data.flags |= CSM_CLIENT_END_SESSION_FLAG_SAVE;
        }

        if (manager->priv->phase_timeout_id > 0) {
                g_source_remove (manager->priv->phase_timeout_id);
                manager->priv->phase_timeout_id = 0;
        }

        if (csm_store_size (manager->priv->clients) > 0) {
                manager->priv->phase_timeout_id = g_timeout_add_seconds (CSM_MANAGER_PHASE_TIMEOUT,
                                                                         (GSourceFunc)on_phase_timeout,
                                                                         manager);

                csm_store_foreach (manager->priv->clients,
                                   (CsmStoreFunc)_client_end_session_helper,
                                   &data);
        } else {
                end_phase (manager);
        }
}

static void
do_phase_end_session_part_2 (CsmManager *manager)
{
        ClientEndSessionData data;

        data.manager = manager;
        data.flags = 0;

        if (manager->priv->logout_mode == CSM_MANAGER_LOGOUT_MODE_FORCE) {
                data.flags |= CSM_CLIENT_END_SESSION_FLAG_FORCEFUL;
        }
        if (csm_manager_get_autosave_enabled (manager)) {
                data.flags |= CSM_CLIENT_END_SESSION_FLAG_SAVE;
        }
        data.flags |= CSM_CLIENT_END_SESSION_FLAG_LAST;

        /* keep the timeout that was started at the beginning of the
         * CSM_MANAGER_PHASE_END_SESSION phase */

        if (g_slist_length (manager->priv->next_query_clients) > 0) {
                g_slist_foreach (manager->priv->next_query_clients,
                                 (GFunc)_client_end_session,
                                 &data);

                g_slist_free (manager->priv->next_query_clients);
                manager->priv->next_query_clients = NULL;
        } else {
                end_phase (manager);
        }
}

static gboolean
_client_stop (const char *id,
              CsmClient  *client,
              gpointer    user_data)
{
        gboolean ret;
        GError  *error;

        error = NULL;
        ret = csm_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 ("CsmManager: stopped client: %s", csm_client_peek_id (client));
        }

        return FALSE;
}

static void
do_phase_exit (CsmManager *manager)
{
        if (csm_store_size (manager->priv->clients) > 0) {
                csm_store_foreach (manager->priv->clients,
                                   (CsmStoreFunc)_client_stop,
                                   NULL);
        }
        end_phase (manager);
}

static gboolean
_client_query_end_session (const char           *id,
                           CsmClient            *client,
                           ClientEndSessionData *data)
{
        gboolean ret;
        GError  *error;

        error = NULL;
        ret = csm_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 ("CsmManager: adding client to query clients: %s", csm_client_peek_id (client));
                data->manager->priv->query_clients = g_slist_prepend (data->manager->priv->query_clients,
                                                                      client);
        }

        return FALSE;
}

static gboolean
inhibitor_has_flag (gpointer      key,
                    CsmInhibitor *inhibitor,
                    gpointer      data)
{
        guint flag;
        guint flags;

        flag = GPOINTER_TO_UINT (data);

        flags = csm_inhibitor_peek_flags (inhibitor);

        return (flags & flag);
}

static gboolean
csm_manager_is_logout_inhibited (CsmManager *manager)
{
        CsmInhibitor *inhibitor;

        if (manager->priv->logout_mode == CSM_MANAGER_LOGOUT_MODE_FORCE) {
                return FALSE;
        }

        if (manager->priv->inhibitors == NULL) {
                return FALSE;
        }

        inhibitor = (CsmInhibitor *)csm_store_find (manager->priv->inhibitors,
                                                    (CsmStoreFunc)inhibitor_has_flag,
                                                    GUINT_TO_POINTER (CSM_INHIBITOR_FLAG_LOGOUT));
        if (inhibitor == NULL) {
                return FALSE;
        }
        return TRUE;
}

static gboolean
csm_manager_is_idle_inhibited (CsmManager *manager)
{
        CsmInhibitor *inhibitor;

        if (manager->priv->inhibitors == NULL) {
                return FALSE;
        }

        inhibitor = (CsmInhibitor *)csm_store_find (manager->priv->inhibitors,
                                                    (CsmStoreFunc)inhibitor_has_flag,
                                                    GUINT_TO_POINTER (CSM_INHIBITOR_FLAG_IDLE));
        if (inhibitor == NULL) {
                return FALSE;
        }
        return TRUE;
}

static gboolean
_client_cancel_end_session (const char *id,
                            CsmClient  *client,
                            CsmManager *manager)
{
        gboolean res;
        GError  *error;

        error = NULL;
        res = csm_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,
                  CsmInhibitor *inhibitor,
                  CsmManager   *manager)
{
        gboolean    matches;
        const char *id;

        id = csm_inhibitor_peek_client_id (inhibitor);

        matches = (id != NULL && id[0] != '\0');

        return matches;
}

static void
cancel_end_session (CsmManager *manager)
{
        /* just ignore if received outside of shutdown */
        if (manager->priv->phase < CSM_MANAGER_PHASE_QUERY_END_SESSION) {
                return;
        }

        /* switch back to running phase */
        g_debug ("CsmManager: Cancelling the end of session");

        /* remove the dialog before we remove the inhibitors, else the dialog
         * will activate itself automatically when the last inhibitor will be
         * removed */
        close_end_session_dialog (manager);

        /* clear all JIT inhibitors */
        csm_store_foreach_remove (manager->priv->inhibitors,
                                  (CsmStoreFunc)inhibitor_is_jit,
                                  (gpointer)manager);

        csm_store_foreach (manager->priv->clients,
                           (CsmStoreFunc)_client_cancel_end_session,
                           NULL);

        csm_manager_set_phase (manager, CSM_MANAGER_PHASE_RUNNING);
        manager->priv->logout_mode = CSM_MANAGER_LOGOUT_MODE_NORMAL;

        manager->priv->logout_type = CSM_MANAGER_LOGOUT_NONE;
        mdm_set_logout_action (MDM_LOGOUT_ACTION_NONE);

        manager->priv->dialog_action = CSM_LOGOUT_ACTION_UNDEFINED;

        start_phase (manager);
}

static gboolean
process_is_running (const char * name)
{
        int num_processes;
        gchar *command = g_strdup_printf ("pidof %s | wc -l", name);
        FILE *fp = popen(command, "r");
        fscanf(fp, "%d", &num_processes);
        pclose(fp);
        g_free (command);

        if (num_processes > 0) {
                return TRUE;
        } else {
                return FALSE;
        }
}

static gboolean
sleep_lock_is_enabled (CsmManager *manager)
{
        return g_settings_get_boolean (manager->priv->power_settings,
                                       KEY_LOCK_ON_SUSPEND);
}

static void
manager_perhaps_lock (CsmManager *manager)
{
        GError   *error;
        gboolean  ret;

        /* only lock if the user has selected 'lock-on-suspend' in power prefs */
        if (!sleep_lock_is_enabled (manager)) {
                return;
        }

        /* do this sync to ensure it's on the screen when we start suspending */
        error = NULL;
        ret = g_spawn_command_line_sync ("cinnamon-screensaver-command --lock", NULL, NULL, NULL, &error);
        if (!ret) {
                g_warning ("Couldn't lock screen: %s", error->message);
                g_error_free (error);
        }
}

static void
flexiserver_launch (const gchar **argv)
{
        GError  *error;

        if (!g_spawn_async (NULL,
                            (gchar **) argv,
                            NULL,
                            G_SPAWN_SEARCH_PATH,
                            NULL,
                            NULL,
                            NULL,
                            &error)) {
                if (error) {
                        g_debug ("CsmManager: Unable to start MDM greeter: %s", error->message);
                        g_error_free (error);
                }
        }
}

static void
manager_switch_user (CsmManager *manager)
{
        close_end_session_dialog (manager);

        /* We have to do this here and in request_switch_user() because this
         * function can be called at a later time, not just directly after
         * request_switch_user(). */
        if (_switch_user_is_locked_down (manager)) {
                g_warning ("Unable to switch user: User switching has been locked down");
                return;
        }

        if (process_is_running ("mdm")) {
                const gchar *command[] = {
                     MDM_FLEXISERVER_COMMAND,
                     MDM_FLEXISERVER_ARGS,
                     NULL
                };

                flexiserver_launch (command);
        } else if (process_is_running("gdm") || process_is_running("gdm3")) {
                const gchar *command[] = {
                     GDM_FLEXISERVER_COMMAND,
                     GDM_FLEXISERVER_ARGS,
                     NULL
                };

                manager_perhaps_lock (manager);
                flexiserver_launch (command);
        } else if (g_getenv ("XDG_SEAT_PATH")) {
                GError *error;
                GDBusProxyFlags flags = G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START;
                GDBusProxy *proxy = NULL;

                error = NULL;

                proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
                                                       flags,
                                                       NULL,
                                                       "org.freedesktop.DisplayManager",
                                                       g_getenv ("XDG_SEAT_PATH"),
                                                       "org.freedesktop.DisplayManager.Seat",
                                                       NULL,
                                                       &error);
                

                if (proxy != NULL) {
                        manager_perhaps_lock (manager);
                        g_dbus_proxy_call (proxy,
                                           "SwitchToGreeter",
                                           g_variant_new ("()"),
                                           G_DBUS_CALL_FLAGS_NONE,
                                           -1,
                                           NULL,
                                           NULL,
                                           NULL);
                        g_object_unref (proxy);
                } else {
                        if (error) {
                                g_debug ("CsmManager: Unable to start LightDM greeter: %s", error->message);
                                g_error_free (error);
                        }
                }
        }
}

static void
manager_attempt_hibernate (CsmManager *manager)
{
        close_end_session_dialog (manager);

        /* lock the screen before we try anything.  If it all fails, at least the screen is locked
         * (if preferences dictate it) */
        manager_perhaps_lock (manager);

        if (csm_system_can_hibernate (manager->priv->system)) {
                csm_system_hibernate (manager->priv->system);
        }
}

static void
manager_attempt_suspend (CsmManager *manager)
{
        close_end_session_dialog (manager);

        /* lock the screen before we try anything.  If it all fails, at least the screen is locked
         * (if preferences dictate it) */
        manager_perhaps_lock (manager);

        if (g_settings_get_boolean (manager->priv->settings, KEY_PREFER_HYBRID_SLEEP) &&
            csm_system_can_hybrid_sleep (manager->priv->system)) {
                csm_system_hybrid_sleep (manager->priv->system);
        } else if (csm_system_can_suspend (manager->priv->system)) {
                csm_system_suspend (manager->priv->system, g_settings_get_boolean (manager->priv->settings, KEY_SUSPEND_HIBERNATE));
        }
}

static void
do_inhibit_dialog_action (CsmManager *manager)
{
        g_debug ("Dialog told us to ignore inhibitors, performing original action: %d", manager->priv->dialog_action);

        switch (manager->priv->dialog_action) {
        case CSM_LOGOUT_ACTION_SWITCH_USER:
                manager_switch_user (manager);
                break;
        case CSM_LOGOUT_ACTION_HIBERNATE:
                manager_attempt_hibernate (manager);
                break;
        case CSM_LOGOUT_ACTION_SLEEP:
                manager_attempt_suspend (manager);
                break;
        case CSM_LOGOUT_ACTION_SHUTDOWN:
        case CSM_LOGOUT_ACTION_REBOOT:
        case CSM_LOGOUT_ACTION_LOGOUT:
                manager->priv->logout_mode = CSM_MANAGER_LOGOUT_MODE_FORCE;
                end_phase (manager);
                break;
        default:
                g_assert_not_reached ();
                break;
        }
}

static void
emit_inhibitor_info_to_dialog (CsmManager *manager, CsmLogoutAction action)
{
    if (action == CSM_LOGOUT_ACTION_UNDEFINED) {
        return;
    }
    g_debug ("Sending inhibitor info to end-session dialog");
    manager->priv->dialog_action = action;

    GVariant *dialog_inhibit_info = build_inhibitor_list_for_dialog (manager->priv->inhibitors,
                                                                     manager->priv->clients,
                                                                     action);

    csm_exported_dialog_emit_inhibitors_changed (manager->priv->dialog_skeleton, dialog_inhibit_info);
}

static void
end_session_or_report_inhibitors (CsmManager *manager)
{
        CsmLogoutAction action;

        if (! csm_manager_is_logout_inhibited (manager)) {
                end_phase (manager);
                return;
        }

        switch (manager->priv->logout_type) {
        case CSM_MANAGER_LOGOUT_LOGOUT:
                action = CSM_LOGOUT_ACTION_LOGOUT;
                break;
        case CSM_MANAGER_LOGOUT_REBOOT:
        case CSM_MANAGER_LOGOUT_REBOOT_INTERACT:
        case CSM_MANAGER_LOGOUT_REBOOT_MDM:
                action = CSM_LOGOUT_ACTION_REBOOT;
                break;
        case CSM_MANAGER_LOGOUT_SHUTDOWN:
        case CSM_MANAGER_LOGOUT_SHUTDOWN_INTERACT:
        case CSM_MANAGER_LOGOUT_SHUTDOWN_MDM:
                action = CSM_LOGOUT_ACTION_SHUTDOWN;
                break;
        default:
                g_warning ("Unexpected logout type %d when generating inhibit dialog info",
                           manager->priv->logout_type);
                action = CSM_LOGOUT_ACTION_LOGOUT;
                break;
        }

        emit_inhibitor_info_to_dialog (manager, action);
}


static void
query_end_session_complete (CsmManager *manager)
{

        g_debug ("CsmManager: 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 (manager->priv->query_timeout_id > 0) {
                g_source_remove (manager->priv->query_timeout_id);
                manager->priv->query_timeout_id = 0;
        }

        end_session_or_report_inhibitors (manager);
}

static guint32
generate_cookie (void)
{
        guint32 cookie;

        cookie = (guint32)g_random_int_range (1, G_MAXINT32);

        return cookie;
}

static guint32
_generate_unique_cookie (CsmManager *manager)
{
        guint32 cookie;

        do {
                cookie = generate_cookie ();
        } while (csm_store_find (manager->priv->inhibitors, (CsmStoreFunc)_find_by_cookie, &cookie) != NULL);

        return cookie;
}

static gboolean
_on_query_end_session_timeout (CsmManager *manager)
{
        GSList *l;

        manager->priv->query_timeout_id = 0;

        g_debug ("CsmManager: query end session timed out");

        for (l = manager->priv->query_clients; l != NULL; l = l->next) {
                guint         cookie;
                CsmInhibitor *inhibitor;
                const char   *bus_name;
                char         *app_id;

                g_warning ("Client '%s' failed to reply before timeout",
                           csm_client_peek_id (l->data));

                /* Don't add "not responding" inhibitors if logout is forced
                 */
                if (manager->priv->logout_mode == CSM_MANAGER_LOGOUT_MODE_FORCE) {
                        continue;
                }

                /* Add JIT inhibit for unresponsive client */
                if (CSM_IS_DBUS_CLIENT (l->data)) {
                        bus_name = csm_dbus_client_get_bus_name (l->data);
                } else {
                        bus_name = NULL;
                }

                app_id = g_strdup (csm_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 = csm_client_get_app_name (l->data);
                }

                cookie = _generate_unique_cookie (manager);
                inhibitor = csm_inhibitor_new_for_client (csm_client_peek_id (l->data),
                                                          app_id,
                                                          CSM_INHIBITOR_FLAG_LOGOUT,
                                                          _("Not responding"),
                                                          bus_name,
                                                          cookie);
                g_free (app_id);
                csm_store_add (manager->priv->inhibitors, csm_inhibitor_peek_id (inhibitor), G_OBJECT (inhibitor));
                g_object_unref (inhibitor);
        }

        g_slist_free (manager->priv->query_clients);
        manager->priv->query_clients = NULL;

        query_end_session_complete (manager);

        return FALSE;
}

static void
do_phase_query_end_session (CsmManager *manager)
{
        ClientEndSessionData data;

        data.manager = manager;
        data.flags = 0;

        if (manager->priv->logout_mode == CSM_MANAGER_LOGOUT_MODE_FORCE) {
                data.flags |= CSM_CLIENT_END_SESSION_FLAG_FORCEFUL;
        }
        /* We only query if an app is ready to log out, so we don't use
         * CSM_CLIENT_END_SESSION_FLAG_SAVE here.
         */

        debug_clients (manager);
        g_debug ("CsmManager: sending query-end-session to clients (logout mode: %s)",
                 manager->priv->logout_mode == CSM_MANAGER_LOGOUT_MODE_NORMAL? "normal" :
                 manager->priv->logout_mode == CSM_MANAGER_LOGOUT_MODE_FORCE? "forceful":
                 "no confirmation");
        csm_store_foreach (manager->priv->clients,
                           (CsmStoreFunc)_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. */
        manager->priv->query_timeout_id = g_timeout_add_seconds (1, (GSourceFunc)_on_query_end_session_timeout, manager);
}

static void
update_idle (CsmManager *manager)
{
        if (csm_manager_is_idle_inhibited (manager)) {
                csm_presence_set_idle_enabled (manager->priv->presence, FALSE);
        } else {
                csm_presence_set_idle_enabled (manager->priv->presence, TRUE);
        }
}

static void
start_phase (CsmManager *manager)
{

        g_debug ("CsmManager: starting phase %s",
                 phase_num_to_name (manager->priv->phase));

        /* reset state */
        g_slist_free (manager->priv->pending_apps);
        manager->priv->pending_apps = NULL;
        g_slist_free (manager->priv->query_clients);
        manager->priv->query_clients = NULL;
        g_slist_free (manager->priv->next_query_clients);
        manager->priv->next_query_clients = NULL;

        if (manager->priv->query_timeout_id > 0) {
                g_source_remove (manager->priv->query_timeout_id);
                manager->priv->query_timeout_id = 0;
        }
        if (manager->priv->phase_timeout_id > 0) {
                g_source_remove (manager->priv->phase_timeout_id);
                manager->priv->phase_timeout_id = 0;
        }

        switch (manager->priv->phase) {
        case CSM_MANAGER_PHASE_STARTUP:
        case CSM_MANAGER_PHASE_EARLY_INITIALIZATION:
        case CSM_MANAGER_PHASE_PRE_DISPLAY_SERVER:
        case CSM_MANAGER_PHASE_DISPLAY_SERVER:
        case CSM_MANAGER_PHASE_INITIALIZATION:
        case CSM_MANAGER_PHASE_WINDOW_MANAGER:
        case CSM_MANAGER_PHASE_PANEL:
        case CSM_MANAGER_PHASE_DESKTOP:
        case CSM_MANAGER_PHASE_APPLICATION:
                do_phase_startup (manager);
                break;
        case CSM_MANAGER_PHASE_RUNNING:
                csm_xsmp_server_start_accepting_new_clients (manager->priv->xsmp_server);
                csm_exported_manager_emit_session_running (manager->priv->skeleton);
                update_idle (manager);
                csm_util_start_systemd_unit ("cinnamon-session.target", "replace", NULL);
                break;
        case CSM_MANAGER_PHASE_QUERY_END_SESSION:
                csm_xsmp_server_stop_accepting_new_clients (manager->priv->xsmp_server);
                do_phase_query_end_session (manager);
                break;
        case CSM_MANAGER_PHASE_END_SESSION:
                csm_exported_manager_emit_session_over (manager->priv->skeleton);
                do_phase_end_session (manager);
                break;
        case CSM_MANAGER_PHASE_EXIT:
                do_phase_exit (manager);
                break;
        default:
                g_assert_not_reached ();
                break;
        }
}

static gboolean
_debug_app_for_phase (const char *id,
                      CsmApp     *app,
                      gpointer    data)
{
        guint phase;

        phase = GPOINTER_TO_UINT (data);

        if (csm_app_peek_phase (app) != phase) {
                return FALSE;
        }

        g_debug ("CsmManager:\tID: %s\tapp-id:%s\tis-disabled:%d\tis-conditionally-disabled:%d\tis-delayed:%d",
                 csm_app_peek_id (app),
                 csm_app_peek_app_id (app),
                 csm_app_peek_is_disabled (app),
                 csm_app_peek_is_conditionally_disabled (app),
                 (csm_app_peek_autostart_delay (app) > 0));

        return FALSE;
}

static void
debug_app_summary (CsmManager *manager)
{
        guint phase;

        g_debug ("CsmManager: App startup summary");
        for (phase = CSM_MANAGER_PHASE_EARLY_INITIALIZATION; phase < CSM_MANAGER_PHASE_RUNNING; phase++) {
                g_debug ("CsmManager: Phase %s", phase_num_to_name (phase));
                csm_store_foreach (manager->priv->apps,
                                   (CsmStoreFunc)_debug_app_for_phase,
                                   GUINT_TO_POINTER (phase));
        }
}

void
csm_manager_start (CsmManager *manager)
{
        g_debug ("CsmManager: CSM starting to manage");

        g_return_if_fail (CSM_IS_MANAGER (manager));

        csm_xsmp_server_start (manager->priv->xsmp_server);
        csm_manager_set_phase (manager, CSM_MANAGER_PHASE_EARLY_INITIALIZATION);
        debug_app_summary (manager);
        start_phase (manager);
}

const char *
_csm_manager_get_default_session (CsmManager     *manager)
{
        return g_settings_get_string (manager->priv->session_settings,
                                      KEY_SESSION_NAME);
}

void
_csm_manager_set_active_session (CsmManager     *manager,
                                 const char     *session_name,
                                 gboolean        is_fallback)
{
        g_free (manager->priv->session_name);
        manager->priv->session_name = g_strdup (session_name);
        manager->priv->is_fallback_session = is_fallback;

        csm_exported_manager_set_session_name (manager->priv->skeleton,
                                               session_name);
}

void
_csm_manager_export_login_session_id (CsmManager *manager)
{
        g_autofree gchar *session_id = NULL;

        session_id = csm_system_get_login_session_id (manager->priv->system);

        csm_exported_manager_set_session_id (manager->priv->skeleton,
                                             session_id);
}

static gboolean
_app_has_app_id (const char   *id,
                 CsmApp       *app,
                 const char   *app_id_a)
{
        const char *app_id_b;

        app_id_b = csm_app_peek_app_id (app);
        return (app_id_b != NULL && strcmp (app_id_a, app_id_b) == 0);
}

static CsmApp *
find_app_for_app_id (CsmManager *manager,
                     const char *app_id)
{
        CsmApp *app;
        app = (CsmApp *)csm_store_find (manager->priv->apps,
                                        (CsmStoreFunc)_app_has_app_id,
                                        (char *)app_id);
        return app;
}

static gboolean
inhibitor_has_client_id (gpointer      key,
                         CsmInhibitor *inhibitor,
                         const char   *client_id_a)
{
        gboolean    matches;
        const char *client_id_b;

        client_id_b = csm_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 ("CsmManager: removing JIT inhibitor for %s for reason '%s'",
                                 csm_inhibitor_peek_client_id (inhibitor),
                                 csm_inhibitor_peek_reason (inhibitor));
                }
        }

        return matches;
}

static gboolean
_app_has_startup_id (const char *id,
                     CsmApp     *app,
                     const char *startup_id_a)
{
        const char *startup_id_b;

        startup_id_b = csm_app_peek_startup_id (app);

        if (IS_STRING_EMPTY (startup_id_b)) {
                return FALSE;
        }

        return (strcmp (startup_id_a, startup_id_b) == 0);
}

static CsmApp *
find_app_for_startup_id (CsmManager *manager,
                        const char *startup_id)
{
        CsmApp *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 (manager->priv->phase < CSM_MANAGER_PHASE_APPLICATION) {
                for (a = manager->priv->pending_apps; a != NULL; a = a->next) {
                        CsmApp *app = CSM_APP (a->data);

                        if (strcmp (startup_id, csm_app_peek_startup_id (app)) == 0) {
                                found_app = app;
                                goto out;
                        }
                }
        } else {
                CsmApp *app;

                app = (CsmApp *)csm_store_find (manager->priv->apps,
                                                (CsmStoreFunc)_app_has_startup_id,
                                                (char *)startup_id);
                if (app != NULL) {
                        found_app = app;
                        goto out;
                }
        }
 out:
        return found_app;
}

static gboolean
restart_cinnamon_launcher (CsmManager *manager)
{
    CsmApp *app;

    app = find_app_for_app_id (manager, "cinnamon.desktop");

    if (app) {
        GError *error = NULL;

        g_debug ("CsmManager: Restarting cinnamon-launcher");

        if (!csm_app_restart (app, &error)) {
            g_warning ("CsmManager: Unable to restart cinnamon-launcher: %s", error->message);
            g_error_free (error);
        }
    } else {
        g_warning ("CsmManager: Unable to find the cinnamon-launcher");
    }

    return G_SOURCE_REMOVE;
}

static gboolean
csm_manager_setenv (CsmExportedManager    *skeleton,
                    GDBusMethodInvocation *invocation,
                    const gchar           *variable,
                    const gchar           *value,
                    CsmManager            *manager)
{
        if (!CSM_IS_MANAGER (manager)) {
                g_dbus_method_invocation_return_error (invocation,
                                                       CSM_MANAGER_ERROR,
                                                       CSM_MANAGER_ERROR_GENERAL,
                                                       "CSM_IS_MANAGER failed on csm_manager_setenv");

                return TRUE;
        }

        if (manager->priv->phase > CSM_MANAGER_PHASE_INITIALIZATION) {
                g_dbus_method_invocation_return_error (invocation,
                                                       CSM_MANAGER_ERROR,
                                                       CSM_MANAGER_ERROR_NOT_IN_INITIALIZATION,
                                                       "Setenv interface is only available during the Initialization phase");

                return TRUE;
        }

        csm_util_setenv (variable, value);

        csm_exported_manager_complete_setenv (skeleton, invocation);

        return TRUE;
}

static gboolean
csm_manager_initialization_error (CsmExportedManager    *skeleton,
                                  GDBusMethodInvocation *invocation,
                                  const char            *message,
                                  gboolean               fatal,
                                  CsmManager            *manager)
{
        if (!CSM_IS_MANAGER (manager)) {
                g_dbus_method_invocation_return_error (invocation,
                                                       CSM_MANAGER_ERROR,
                                                       CSM_MANAGER_ERROR_GENERAL,
                                                       "CSM_IS_MANAGER failed on csm_manager_initialization_error");

                return TRUE;
        }

        if (manager->priv->phase != CSM_MANAGER_PHASE_INITIALIZATION) {
                g_dbus_method_invocation_return_error (invocation,
                                                       CSM_MANAGER_ERROR,
                                                       CSM_MANAGER_ERROR_NOT_IN_INITIALIZATION,
                                                       "InitializationError interface is only available during the Initialization phase");

                return TRUE;
        }

        csm_util_init_error (fatal, "%s", message);

        csm_exported_manager_complete_initialization_error (skeleton, invocation);

        return TRUE;
}

static gboolean
csm_manager_register_client (CsmExportedManager      *skeleton,
                             GDBusMethodInvocation   *invocation,
                             const char              *app_id,
                             const char              *startup_id,
                             CsmManager              *manager)
{
        char      *new_startup_id;
        const char *sender;
        CsmClient *client;
        CsmApp    *app;

        if (!CSM_IS_MANAGER (manager)) {
                g_dbus_method_invocation_return_error (invocation,
                                                       CSM_MANAGER_ERROR,
                                                       CSM_MANAGER_ERROR_GENERAL,
                                                       "CSM_IS_MANAGER failed on csm_manager_register_client");

                return TRUE;
        }

        app = NULL;
        client = NULL;

        g_debug ("CsmManager: RegisterClient %s", startup_id);

        if (manager->priv->phase >= CSM_MANAGER_PHASE_QUERY_END_SESSION) {
                g_debug ("Unable to register client: shutting down");

                g_dbus_method_invocation_return_error (invocation,
                                                       CSM_MANAGER_ERROR,
                                                       CSM_MANAGER_ERROR_NOT_IN_RUNNING,
                                                       "Unable to register client");

                return TRUE;
        }

        if (IS_STRING_EMPTY (startup_id)) {
                new_startup_id = csm_util_generate_startup_id ();
        } else {
                client = (CsmClient *)csm_store_find (manager->priv->clients,
                                                      (CsmStoreFunc)_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,
                                                               CSM_MANAGER_ERROR,
                                                               CSM_MANAGER_ERROR_ALREADY_REGISTERED,
                                                               "Unable to register client");

                        return TRUE;
                }

                new_startup_id = g_strdup (startup_id);
        }

        g_debug ("CsmManager: 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 = csm_dbus_client_new (new_startup_id, sender);

        if (client == NULL) {
                g_debug ("Unable to create client");

                g_dbus_method_invocation_return_error (invocation,
                                                       CSM_MANAGER_ERROR,
                                                       CSM_MANAGER_ERROR_GENERAL,
                                                       "Unable to register client");

                return TRUE;
        }

        csm_store_add (manager->priv->clients, csm_client_peek_id (client), G_OBJECT (client));
        /* the store will own the ref */
        g_object_unref (client);

        if (app != NULL) {
                csm_client_set_app_id (client, csm_app_peek_app_id (app));
                csm_app_registered (app);
        } else {
                /* if an app id is specified store it in the client
                   so we can save it later */
                csm_client_set_app_id (client, app_id);
        }

        csm_client_set_status (client, CSM_CLIENT_REGISTERED);

        g_assert (new_startup_id != NULL);
        g_free (new_startup_id);

        csm_exported_manager_complete_register_client (skeleton,
                                                       invocation,
                                                       csm_client_peek_id (client));

        return TRUE;
}

static gboolean
csm_manager_unregister_client (CsmExportedManager     *skeleton,
                               GDBusMethodInvocation  *invocation,
                               const char             *client_id,
                               CsmManager             *manager)
{
        CsmClient *client;

        if (!CSM_IS_MANAGER (manager)) {
                g_dbus_method_invocation_return_error (invocation,
                                                       CSM_MANAGER_ERROR,
                                                       CSM_MANAGER_ERROR_GENERAL,
                                                       "CSM_IS_MANAGER failed on csm_manager_register_client");

                return TRUE;
        }

        g_debug ("CsmManager: UnregisterClient %s", client_id);

        client = (CsmClient *)csm_store_lookup (manager->priv->clients, client_id);
        if (client == NULL) {
                g_debug ("Unable to unregister client: not registered");

                g_dbus_method_invocation_return_error (invocation,
                                                       CSM_MANAGER_ERROR,
                                                       CSM_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 */
        csm_client_set_status (client, CSM_CLIENT_UNREGISTERED);

        csm_exported_manager_complete_unregister_client (skeleton,
                                                         invocation);

        return TRUE;
}

static gboolean
csm_manager_inhibit (CsmExportedManager      *skeleton,
                     GDBusMethodInvocation   *invocation,
                     const char              *app_id,
                     guint                    toplevel_xid,
                     const char              *reason,
                     guint                    flags,
                     CsmManager              *manager)
{
        CsmInhibitor *inhibitor;
        guint         cookie;

        g_debug ("CsmManager: Inhibit xid=%u app_id=%s reason=%s flags=%u",
                 toplevel_xid,
                 app_id,
                 reason,
                 flags);

        if (manager->priv->logout_mode == CSM_MANAGER_LOGOUT_MODE_FORCE) {
                g_debug ("CsmManager: Unable to inhibit: Forced logout cannot be inhibited");

                g_dbus_method_invocation_return_error (invocation,
                                                       CSM_MANAGER_ERROR,
                                                       CSM_MANAGER_ERROR_GENERAL,
                                                       "Forced logout cannot be inhibited");

                return TRUE;
        }

        if (IS_STRING_EMPTY (app_id)) {
                g_debug ("CsmManager: Unable to inhibit: Application ID not specified");

                g_dbus_method_invocation_return_error (invocation,
                                                       CSM_MANAGER_ERROR,
                                                       CSM_MANAGER_ERROR_GENERAL,
                                                       "Application ID not specified");

                return TRUE;
        }

        if (IS_STRING_EMPTY (reason)) {
                g_debug ("CsmManager: Unable to inhibit: Reason not specific");

                g_dbus_method_invocation_return_error (invocation,
                                                       CSM_MANAGER_ERROR,
                                                       CSM_MANAGER_ERROR_GENERAL,
                                                       "Reason not specified");

                return TRUE;
        }

        if (flags == 0) {
                g_debug ("CsmManager: Unable to inhibit: Invalid inhibit flags");

                g_dbus_method_invocation_return_error (invocation,
                                                       CSM_MANAGER_ERROR,
                                                       CSM_MANAGER_ERROR_GENERAL,
                                                       "Invalid inhibit flags");

                return TRUE;
        }

        cookie = _generate_unique_cookie (manager);
        inhibitor = csm_inhibitor_new (app_id,
                                       toplevel_xid,
                                       flags,
                                       reason,
                                       g_dbus_method_invocation_get_sender (invocation),
                                       cookie);
        csm_store_add (manager->priv->inhibitors, csm_inhibitor_peek_id (inhibitor), G_OBJECT (inhibitor));
        g_object_unref (inhibitor);

        csm_exported_manager_complete_inhibit (skeleton,
                                               invocation,
                                               cookie);

        return TRUE;
}

static gboolean
csm_manager_uninhibit (CsmExportedManager    *skeleton,
                       GDBusMethodInvocation *invocation,
                       guint                  cookie,
                       CsmManager            *manager)
{
        CsmInhibitor *inhibitor;

        g_debug ("CsmManager: Uninhibit %u", cookie);

        inhibitor = (CsmInhibitor *)csm_store_find (manager->priv->inhibitors,
                                                    (CsmStoreFunc)_find_by_cookie,
                                                    &cookie);
        if (inhibitor == NULL) {
                g_debug ("Unable to uninhibit: Invalid cookie");

                g_dbus_method_invocation_return_error (invocation,
                                                       CSM_MANAGER_ERROR,
                                                       CSM_MANAGER_ERROR_GENERAL,
                                                       "Unable to uninhibit: Invalid cookie");

                return TRUE;
        }

        g_debug ("CsmManager: removing inhibitor %s %u reason '%s' %u connection %s",
                 csm_inhibitor_peek_app_id (inhibitor),
                 csm_inhibitor_peek_toplevel_xid (inhibitor),
                 csm_inhibitor_peek_reason (inhibitor),
                 csm_inhibitor_peek_flags (inhibitor),
                 csm_inhibitor_peek_bus_name (inhibitor));

        csm_store_remove (manager->priv->inhibitors, csm_inhibitor_peek_id (inhibitor));

        csm_exported_manager_complete_uninhibit (skeleton,
                                                 invocation);

        return TRUE;
}

static gboolean
csm_manager_is_inhibited (CsmExportedManager    *skeleton,
                          GDBusMethodInvocation *invocation,
                          guint                  flags,
                          CsmManager            *manager)
{
        gboolean is_inhibited;

        is_inhibited = FALSE;

        if (manager->priv->inhibitors == NULL
            || csm_store_size (manager->priv->inhibitors) == 0) {
                is_inhibited = FALSE;
        } else {
                CsmInhibitor *inhibitor;

                inhibitor = (CsmInhibitor *) csm_store_find (manager->priv->inhibitors,
                                                             (CsmStoreFunc)inhibitor_has_flag,
                                                             GUINT_TO_POINTER (flags));

                is_inhibited = inhibitor != NULL;
        }

        csm_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
csm_manager_get_clients (CsmExportedManager     *skeleton,
                         GDBusMethodInvocation  *invocation,
                         CsmManager             *manager)
{
        GPtrArray *clients;

        clients = g_ptr_array_new_with_free_func (g_free);

        csm_store_foreach (manager->priv->clients,
                           (CsmStoreFunc)listify_store_ids,
                           &clients);

        g_ptr_array_add (clients, NULL);

        csm_exported_manager_complete_get_clients (skeleton,
                                                   invocation,
                                                   (const gchar * const *) clients->pdata);
        g_ptr_array_unref (clients);

        return TRUE;
}

static gboolean
csm_manager_get_inhibitors (CsmExportedManager     *skeleton,
                            GDBusMethodInvocation  *invocation,
                            CsmManager             *manager)
{
        GPtrArray *inhibitors;

        inhibitors = g_ptr_array_new_with_free_func (g_free);

        csm_store_foreach (manager->priv->inhibitors,
                           (CsmStoreFunc)listify_store_ids,
                           &inhibitors);

        g_ptr_array_add (inhibitors, NULL);

        csm_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,
                              CsmApp     *app,
                              const char *condition)
{
        gboolean has;
        gboolean disabled;

        has = csm_app_has_autostart_condition (app, condition);
        disabled = csm_app_peek_is_disabled (app);

        return has && !disabled;
}

static gboolean
csm_manager_is_autostart_condition_handled (CsmExportedManager    *skeleton,
                                            GDBusMethodInvocation *invocation,
                                            const char            *condition,
                                            CsmManager            *manager)
{
        CsmApp *app;
        gboolean handled;

        app = (CsmApp *) csm_store_find (manager->priv->apps,(
                                         CsmStoreFunc) _app_has_autostart_condition,
                                         (char *)condition);

        if (app != NULL) {
                handled = TRUE;
        } else {
                handled = FALSE;
        }

        csm_exported_manager_complete_is_autostart_condition_handled (skeleton,
                                                                      invocation,
                                                                      handled);

        return TRUE;
}

static gboolean
csm_manager_shutdown (CsmExportedManager    *skeleton,
                      GDBusMethodInvocation *invocation,
                      CsmManager            *manager)
{
        g_debug ("CsmManager: Shutdown called");

        if (manager->priv->phase != CSM_MANAGER_PHASE_RUNNING) {
                g_dbus_method_invocation_return_error (invocation,
                                                       CSM_MANAGER_ERROR,
                                                       CSM_MANAGER_ERROR_NOT_IN_RUNNING,
                                                       "Shutdown interface is only available during the Running phase");

                return TRUE;
        }

        if (_log_out_is_locked_down (manager)) {
                g_dbus_method_invocation_return_error (invocation,
                                                       CSM_MANAGER_ERROR,
                                                       CSM_MANAGER_ERROR_LOCKED_DOWN,
                                                       "Logout has been locked down");

                return TRUE;
        }

        show_shutdown_dialog (manager, FALSE);

        csm_exported_manager_complete_shutdown (skeleton,
                                                invocation);

        return TRUE;
}

static gboolean
csm_manager_reboot (CsmExportedManager     *skeleton,
                    GDBusMethodInvocation  *invocation,
                    CsmManager  *manager)
{
        g_debug ("CsmManager: Reboot called");

        if (manager->priv->phase != CSM_MANAGER_PHASE_RUNNING) {
                g_dbus_method_invocation_return_error (invocation,
                                                       CSM_MANAGER_ERROR,
                                                       CSM_MANAGER_ERROR_NOT_IN_RUNNING,
                                                       "Reboot interface is only available during the Running phase");

                return TRUE;
        }

        if (_log_out_is_locked_down (manager)) {
                g_dbus_method_invocation_return_error (invocation,
                                                       CSM_MANAGER_ERROR,
                                                       CSM_MANAGER_ERROR_LOCKED_DOWN,
                                                       "Logout has been locked down");

                return TRUE;
        }
 
        show_shutdown_dialog (manager, TRUE);

        csm_exported_manager_complete_reboot (skeleton,
                                              invocation);

        return TRUE;
}

static gboolean
csm_manager_can_shutdown (CsmExportedManager    *skeleton,
                          GDBusMethodInvocation *invocation,
                          CsmManager            *manager)
{
        gboolean shutdown_available;

        g_debug ("CsmManager: CanShutdown called");

        shutdown_available = !_log_out_is_locked_down (manager) &&
                             (csm_system_can_stop (manager->priv->system)
                              || csm_system_can_restart (manager->priv->system)
                              || csm_system_can_suspend (manager->priv->system)
                              || csm_system_can_hibernate (manager->priv->system));

        csm_exported_manager_complete_can_shutdown (skeleton,
                                                    invocation,
                                                    shutdown_available);

        return TRUE;
}

static gboolean
csm_manager_logout_dbus (CsmExportedManager    *skeleton,
                         GDBusMethodInvocation *invocation,
                         guint                  logout_mode,
                         CsmManager            *manager)
{
        g_debug ("CsmManager: Logout called");

        if (manager->priv->phase != CSM_MANAGER_PHASE_RUNNING) {
                g_dbus_method_invocation_return_error (invocation,
                                                       CSM_MANAGER_ERROR,
                                                       CSM_MANAGER_ERROR_NOT_IN_RUNNING,
                                                       "Logout interface is only available during the Running phase");

                return TRUE;
        }

        if (_log_out_is_locked_down (manager)) {
                g_dbus_method_invocation_return_error (invocation,
                                                       CSM_MANAGER_ERROR,
                                                       CSM_MANAGER_ERROR_LOCKED_DOWN,
                                                       "Logout has been locked down");

                return TRUE;
        }

        switch (logout_mode) {
        case CSM_MANAGER_LOGOUT_MODE_NORMAL:
        case CSM_MANAGER_LOGOUT_MODE_NO_CONFIRMATION:
        case CSM_MANAGER_LOGOUT_MODE_FORCE:
                user_logout (manager, logout_mode);
                break;
        default:
                g_debug ("Unknown logout mode option");

                g_dbus_method_invocation_return_error (invocation,
                                                       CSM_MANAGER_ERROR,
                                                       CSM_MANAGER_ERROR_INVALID_OPTION,
                                                       "Unknown logout mode flag");

                return TRUE;
        }

        csm_exported_manager_complete_logout (skeleton,
                                              invocation);

        return TRUE;
}

static gboolean
csm_manager_is_session_running (CsmExportedManager    *skeleton,
                                GDBusMethodInvocation *invocation,
                                CsmManager            *manager)
{

        csm_exported_manager_complete_is_session_running (skeleton,
                                                          invocation,
                                                          (manager->priv->phase == CSM_MANAGER_PHASE_RUNNING));

        return TRUE;
}

static gboolean
csm_manager_request_shutdown (CsmExportedManager    *skeleton,
                              GDBusMethodInvocation *invocation,
                              CsmManager            *manager)
{
        g_debug ("CsmManager: RequestShutdown called");

        if (manager->priv->phase != CSM_MANAGER_PHASE_RUNNING) {
                g_dbus_method_invocation_return_error (invocation,
                                                       CSM_MANAGER_ERROR,
                                                       CSM_MANAGER_ERROR_NOT_IN_RUNNING,
                                                       "RequestShutdown interface is only available during the Running phase");

                return TRUE;
        }

        request_shutdown (manager);

        csm_exported_manager_complete_request_shutdown (skeleton,
                                                        invocation);

        return TRUE;
}

static gboolean
csm_manager_request_reboot (CsmExportedManager     *skeleton,
                            GDBusMethodInvocation  *invocation,
                            CsmManager *manager)
{
        g_debug ("CsmManager: RequestReboot called");

        if (manager->priv->phase != CSM_MANAGER_PHASE_RUNNING) {
                g_dbus_method_invocation_return_error (invocation,
                                                       CSM_MANAGER_ERROR,
                                                       CSM_MANAGER_ERROR_NOT_IN_RUNNING,
                                                       "RequestReboot interface is only available during the Running phase");

                return TRUE;
        }

        request_reboot (manager);

        csm_exported_manager_complete_request_reboot (skeleton,
                                                      invocation);

        return TRUE;
}

static gboolean
csm_manager_restart_cinnamon_launcher (CsmExportedManager     *skeleton,
                                       GDBusMethodInvocation  *invocation,
                                       CsmManager             *manager)
{
        g_debug ("CsmManager: RestartCinnamonLauncher called");

        if (manager->priv->phase != CSM_MANAGER_PHASE_RUNNING) {
                g_dbus_method_invocation_return_error (invocation,
                                                       CSM_MANAGER_ERROR,
                                                       CSM_MANAGER_ERROR_NOT_IN_RUNNING,
                                                       "RestartCinnamonLauncher interface is only available during the Running phase");

                return TRUE;
        }

        g_idle_add ((GSourceFunc)restart_cinnamon_launcher, manager);

        csm_exported_manager_complete_restart_cinnamon_launcher (skeleton,
                                                                 invocation);

        return TRUE;
}

static void
_disconnect_client (CsmManager *manager,
                    CsmClient  *client)
{
        gboolean              is_condition_client;
        CsmApp               *app;
        const char           *app_id;
        const char           *startup_id;
        gboolean              app_restart;
        CsmClientRestartStyle client_restart_hint;

        g_debug ("CsmManager: disconnect client: %s", csm_client_peek_id (client));

        /* take a ref so it doesn't get finalized */
        g_object_ref (client);

        csm_client_set_status (client, CSM_CLIENT_FINISHED);

        is_condition_client = FALSE;
        if (g_slist_find (manager->priv->condition_clients, client)) {
                manager->priv->condition_clients = g_slist_remove (manager->priv->condition_clients, client);

                is_condition_client = TRUE;
        }

        /* remove any inhibitors for this client */
        csm_store_foreach_remove (manager->priv->inhibitors,
                                  (CsmStoreFunc)inhibitor_has_client_id,
                                  (gpointer)csm_client_peek_id (client));

        app = NULL;

        /* first try to match on startup ID */
        startup_id = csm_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 = csm_client_peek_app_id (client);
                if (! IS_STRING_EMPTY (app_id)) {
                        g_debug ("CsmManager: disconnect for app '%s'", app_id);
                        app = find_app_for_app_id (manager, app_id);
                }
        }

        switch (manager->priv->phase) {
            case CSM_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 CSM_MANAGER_PHASE_END_SESSION:
                if (! g_slist_find (manager->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 (manager->priv->dbus_disconnected && CSM_IS_DBUS_CLIENT (client)) {
                g_debug ("CsmManager: dbus disconnected, not restarting application");
                goto out;
        }

        if (app == NULL) {
                g_debug ("CsmManager: unable to find application for client - not restarting");
                goto out;
        }

        if (manager->priv->phase >= CSM_MANAGER_PHASE_QUERY_END_SESSION) {
                g_debug ("CsmManager: in shutdown, not restarting application");
                goto out;
        }

        app_restart = csm_app_peek_autorestart (app);
        client_restart_hint = csm_client_peek_restart_style_hint (client);

        /* allow legacy clients to override the app info */
        if (! app_restart
            && client_restart_hint != CSM_CLIENT_RESTART_IMMEDIATELY) {
                g_debug ("CsmManager: autorestart not set, not restarting application");
                goto out;
        }

        if (is_condition_client) {
                g_debug ("CsmManager: app conditionally disabled, not restarting application");
                goto out;
        }

        g_debug ("CsmManager: restarting app");

        _restart_app (manager, app);

 out:
        g_object_unref (client);
}

typedef struct {
        const char *service_name;
        CsmManager *manager;
} RemoveClientData;

static gboolean
_disconnect_dbus_client (const char       *id,
                         CsmClient        *client,
                         RemoveClientData *data)
{
        const char *name;

        if (! CSM_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 = csm_dbus_client_get_bus_name (CSM_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 #CsmManager
 * @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 (CsmManager *manager,
                               const char *service_name)
{
        RemoveClientData data;

        data.service_name = service_name;
        data.manager = manager;

        /* disconnect dbus clients for name */
        csm_store_foreach_remove (manager->priv->clients,
                                  (CsmStoreFunc)_disconnect_dbus_client,
                                  &data);

        if (manager->priv->phase >= CSM_MANAGER_PHASE_QUERY_END_SESSION
            && csm_store_size (manager->priv->clients) == 0) {
                g_debug ("CsmManager: last client disconnected - exiting");
                end_phase (manager);
        }
}

static gboolean
inhibitor_has_bus_name (gpointer          key,
                        CsmInhibitor     *inhibitor,
                        RemoveClientData *data)
{
        gboolean    matches;
        const char *bus_name_b;

        bus_name_b = csm_inhibitor_peek_bus_name (inhibitor);

        matches = FALSE;
        if (! IS_STRING_EMPTY (data->service_name) && ! IS_STRING_EMPTY (bus_name_b)) {
                matches = (strcmp (data->service_name, bus_name_b) == 0);
                if (matches) {
                        g_debug ("CsmManager: removing inhibitor from %s for reason '%s' on connection %s",
                                 csm_inhibitor_peek_app_id (inhibitor),
                                 csm_inhibitor_peek_reason (inhibitor),
                                 csm_inhibitor_peek_bus_name (inhibitor));
                }
        }

        return matches;
}

static void
remove_inhibitors_for_connection (CsmManager *manager,
                                  const char *service_name)
{
        RemoveClientData data;

        data.service_name = service_name;
        data.manager = manager;

        debug_inhibitors (manager);

        csm_store_foreach_remove (manager->priv->inhibitors,
                                  (CsmStoreFunc)inhibitor_has_bus_name,
                                  &data);
}

static void
on_dbus_proxy_signal (GDBusProxy *proxy,
                      gchar      *sender_name,
                      gchar      *signal_name,
                      GVariant   *parameters,
                      gpointer    user_data)
{
    CsmManager *manager;
    gchar *name, *old_owner, *new_owner;

    manager = CSM_MANAGER (user_data);

    if (g_strcmp0 (signal_name, "NameOwnerChanged") != 0) {
            return;
    }

    g_variant_get (parameters, "(sss)",
                   &name,
                   &old_owner,
                   &new_owner);

    if (strlen (new_owner) == 0 && strlen (old_owner) > 0) {
        /* service removed */
        remove_inhibitors_for_connection (manager, old_owner);
        remove_clients_for_connection (manager, old_owner);

        if (g_strcmp0 (name, "org.Cinnamon") == 0) {
            g_debug ("org.Cinnamon has lost its owner (Cinnamon is not running)");
        }
    } else if (strlen (old_owner) == 0 && strlen (new_owner) > 0) {
        /* service added */
        if (g_strcmp0 (name, "org.Cinnamon") == 0) {
            g_debug ("org.Cinnamon has an owner (Cinnamon is running)");
        }

        /* use this if we support automatically registering
         * well known bus names */
    }

    g_free (name);
    g_free (old_owner);
    g_free (new_owner);
}

static void
on_bus_connection_closed (GDBusConnection *connection,
                          gboolean         remote_peer_vanished,
                          GError          *error,
                          gpointer         user_data)
{
        CsmManager *manager;

        manager = CSM_MANAGER (user_data);

        g_debug ("CsmManager: dbus disconnected; disconnecting dbus clients...");

        manager->priv->dbus_disconnected = TRUE;
        remove_clients_for_connection (manager, NULL);
}

static gboolean csm_manager_is_switch_user_inhibited (CsmManager *manager);
static gboolean csm_manager_is_suspend_inhibited (CsmManager *manager);

static gboolean
handle_dialog_method_call (CsmExportedDialog     *dialog_skeleton,
                           GDBusMethodInvocation *invocation,
                           gpointer               user_data)
{
    CsmManager *manager = CSM_MANAGER (user_data);
    const gchar *method_name = g_dbus_method_invocation_get_method_name (invocation);

    g_debug("Dialog method received: %s", method_name);

    if (g_strcmp0 (method_name, "Suspend") == 0) {
        request_suspend (manager);
        csm_exported_dialog_complete_suspend (dialog_skeleton, invocation);
    }
    else
    if (g_strcmp0 (method_name, "Hibernate") == 0) {
        request_hibernate (manager);
        csm_exported_dialog_complete_hibernate (dialog_skeleton, invocation);
    }
    else
    if (g_strcmp0 (method_name, "Restart") == 0) {
        request_reboot (manager);
        csm_exported_dialog_complete_restart (dialog_skeleton, invocation);
    }
    else
    if (g_strcmp0 (method_name, "Shutdown") == 0) {
        request_shutdown (manager);
        csm_exported_dialog_complete_shutdown (dialog_skeleton, invocation);
    }
    else
    if (g_strcmp0 (method_name, "SwitchUser") == 0) {
        request_switch_user (manager);
        csm_exported_dialog_complete_switch_user (dialog_skeleton, invocation);
    }
    else
    if (g_strcmp0 (method_name, "Logout") == 0) {
        request_logout (manager, CSM_MANAGER_LOGOUT_MODE_NO_CONFIRMATION);
        csm_exported_dialog_complete_logout (dialog_skeleton, invocation);
    }
    else
    if (g_strcmp0 (method_name, "Cancel") == 0) {
        if (manager->priv->dialog_action != CSM_LOGOUT_ACTION_UNDEFINED) {
            g_debug ("session-manager dialog was cancelled during inhibit phase");
            cancel_end_session (manager);
        }
        else
        {
            g_debug ("session-manager dialog was cancelled");
        }

        close_end_session_dialog (manager);
        csm_exported_dialog_complete_logout (dialog_skeleton, invocation);
    }
    else
    if (g_strcmp0 (method_name, "IgnoreInhibitors") == 0) {
        close_end_session_dialog (manager);
        do_inhibit_dialog_action (manager);
        csm_exported_dialog_complete_ignore_inhibitors (dialog_skeleton, invocation);
    }
    else
    if (g_strcmp0 (method_name, "GetCapabilities") == 0) {
        GVariant *ret;
        gboolean can_hybrid_sleep = g_settings_get_boolean (manager->priv->settings, KEY_PREFER_HYBRID_SLEEP) &&
                              csm_system_can_hybrid_sleep (manager->priv->system);

        ret = g_variant_new ("(bbbbbbb)",
                             !(_switch_user_is_locked_down (manager) || csm_manager_is_switch_user_inhibited (manager)),
                             (!_log_out_is_locked_down (manager)) && csm_system_can_stop (manager->priv->system),
                             (!_log_out_is_locked_down (manager)) && csm_system_can_restart (manager->priv->system),
                             can_hybrid_sleep,
                             csm_system_can_suspend (manager->priv->system),
                             csm_system_can_hibernate (manager->priv->system),
                             (!csm_manager_is_logout_inhibited (manager)));

        csm_exported_dialog_complete_get_capabilities (dialog_skeleton, invocation, ret);
    }
    else {
        g_warning ("Unknown method name %s", method_name);
        g_dbus_method_invocation_return_error (invocation,
                                               CSM_MANAGER_ERROR,
                                               CSM_MANAGER_ERROR_INVALID_METHOD,
                                               "Unknown method name %s",
                                               method_name);
    }

    return TRUE;
}

typedef struct
{
    const gchar  *signal_name;
    gpointer      callback;
} SkeletonSignal;

static SkeletonSignal skeleton_signals[] = {
    // signal name                              callback
    { "handle-setenv",                          csm_manager_setenv },
    { "handle-initialization-error",            csm_manager_initialization_error },
    { "handle-register-client",                 csm_manager_register_client },
    { "handle-unregister-client",               csm_manager_unregister_client },
    { "handle-inhibit",                         csm_manager_inhibit },
    { "handle-uninhibit",                       csm_manager_uninhibit },
    { "handle-is-inhibited",                    csm_manager_is_inhibited },
    { "handle-get-clients",                     csm_manager_get_clients },
    { "handle-get-inhibitors",                  csm_manager_get_inhibitors },
    { "handle-is-autostart-condition-handled",  csm_manager_is_autostart_condition_handled },
    { "handle-shutdown",                        csm_manager_shutdown },
    { "handle-reboot",                          csm_manager_reboot },
    { "handle-can-shutdown",                    csm_manager_can_shutdown },
    { "handle-logout",                          csm_manager_logout_dbus },
    { "handle-is-session-running",              csm_manager_is_session_running },
    { "handle-request-shutdown",                csm_manager_request_shutdown },
    { "handle-request-reboot",                  csm_manager_request_reboot },
    { "handle-restart-cinnamon-launcher",       csm_manager_restart_cinnamon_launcher }
};

static SkeletonSignal dialog_skeleton_signals[] = {
    // signal name                              callback
    { "handle-suspend",                         handle_dialog_method_call },
    { "handle-hibernate",                       handle_dialog_method_call },
    { "handle-restart",                         handle_dialog_method_call },
    { "handle-shutdown",                        handle_dialog_method_call },
    { "handle-switch-user",                     handle_dialog_method_call },
    { "handle-logout",                          handle_dialog_method_call },
    { "handle-cancel",                          handle_dialog_method_call },
    { "handle-ignore-inhibitors",               handle_dialog_method_call },
    { "handle-get-capabilities",                handle_dialog_method_call },
};

static gboolean
register_manager (CsmManager *manager)
{
        GDBusInterfaceSkeleton *skeleton;
        GError *error = NULL;
        gint i;

        error = NULL;
        manager->priv->connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
        if (manager->priv->connection == NULL) {
                if (error != NULL) {
                        g_critical ("error getting session bus: %s", error->message);
                        g_error_free (error);
                }
                exit (1);
        }

        g_signal_connect (manager->priv->connection,
                          "closed",
                          G_CALLBACK (on_bus_connection_closed),
                           manager);

        manager->priv->dbus_disconnected = FALSE;

        manager->priv->bus_proxy = g_dbus_proxy_new_sync (manager->priv->connection,
                                                          G_DBUS_PROXY_FLAGS_NONE,
                                                          NULL,
                                                          "org.freedesktop.DBus",
                                                          "/org/freedesktop/DBus",
                                                          "org.freedesktop.DBus",
                                                          NULL,
                                                          &error);

        g_signal_connect (manager->priv->bus_proxy,
                          "g-signal",
                          G_CALLBACK (on_dbus_proxy_signal),
                          manager);

        manager->priv->cinnamon_proxy = g_dbus_proxy_new_sync (manager->priv->connection,
                                                               G_DBUS_PROXY_FLAGS_NONE,
                                                               NULL,
                                                               "org.Cinnamon",
                                                               "/org/Cinnamon",
                                                               "org.Cinnamon",
                                                               NULL,
                                                               &error);

        skeleton = G_DBUS_INTERFACE_SKELETON (csm_exported_manager_skeleton_new ());
        manager->priv->skeleton = CSM_EXPORTED_MANAGER (skeleton);

        g_dbus_interface_skeleton_export (skeleton,
                                          manager->priv->connection,
                                          CSM_MANAGER_DBUS_PATH,
                                          &error);

        if (error != NULL) {
                g_critical ("error exporting manager skeleton: %s", error->message);
                g_error_free (error);

                exit(1);
        }

        for (i = 0; i < G_N_ELEMENTS (skeleton_signals); i++) {
                SkeletonSignal sig = skeleton_signals[i];

                g_signal_connect (skeleton,
                                  sig.signal_name,
                                  G_CALLBACK (sig.callback),
                                  manager);
        }

        skeleton = G_DBUS_INTERFACE_SKELETON (csm_exported_dialog_skeleton_new ());
        manager->priv->dialog_skeleton = CSM_EXPORTED_DIALOG (skeleton);

        g_dbus_interface_skeleton_export (skeleton,
                                          manager->priv->connection,
                                          CSM_MANAGER_DBUS_PATH,
                                          &error);

        for (i = 0; i < G_N_ELEMENTS (dialog_skeleton_signals); i++) {
                SkeletonSignal sig = dialog_skeleton_signals[i];

                g_signal_connect (skeleton,
                                  sig.signal_name,
                                  G_CALLBACK (sig.callback),
                                  manager);
        }

        return TRUE;
}


static void
csm_manager_set_failsafe (CsmManager *manager,
                          gboolean    enabled)
{
        g_return_if_fail (CSM_IS_MANAGER (manager));

        manager->priv->failsafe = enabled;
}

gboolean
csm_manager_get_failsafe (CsmManager *manager)
{
        g_return_val_if_fail (CSM_IS_MANAGER (manager), FALSE);

	return manager->priv->failsafe;
}

static void
on_client_disconnected (CsmClient  *client,
                        CsmManager *manager)
{
        g_debug ("CsmManager: disconnect client");
        _disconnect_client (manager, client);
        csm_store_remove (manager->priv->clients, csm_client_peek_id (client));
        if (manager->priv->phase >= CSM_MANAGER_PHASE_QUERY_END_SESSION
            && csm_store_size (manager->priv->clients) == 0) {
                g_debug ("CsmManager: last client disconnected - exiting");
                end_phase (manager);
        }
}

static gboolean
on_xsmp_client_register_request (CsmXSMPClient *client,
                                 char         **id,
                                 CsmManager    *manager)
{
        gboolean handled;
        char    *new_id;
        CsmApp  *app;

        handled = TRUE;
        new_id = NULL;

        if (manager->priv->phase >= CSM_MANAGER_PHASE_QUERY_END_SESSION) {
                goto out;
        }

        if (IS_STRING_EMPTY (*id)) {
                new_id = csm_util_generate_startup_id ();
        } else {
                CsmClient *client;

                client = (CsmClient *)csm_store_find (manager->priv->clients,
                                                      (CsmStoreFunc)_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 ("CsmManager: 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) {
                csm_client_set_app_id (CSM_CLIENT (client), csm_app_peek_app_id (app));
                csm_app_registered (app);
                goto out;
        }

        /* app not found */
        g_free (new_id);
        new_id = NULL;

 out:
        g_free (*id);
        *id = new_id;

        return handled;
}

static void
_finished_playing_logout_sound (ca_context *c, uint32_t id, int error, void *userdata) 
{
    g_warning ("Finished playing logout sound");
    CsmManager *manager = (CsmManager *) userdata;
    manager->priv->logout_sound_is_playing = FALSE;    
    g_warning ("Resuming logout sequence...");
}

static void
maybe_play_logout_sound (CsmManager *manager)
{
    GSettings *settings = g_settings_new ("org.cinnamon.sounds");
        gboolean enabled = g_settings_get_boolean(settings, "logout-enabled");
        gchar *sound = g_settings_get_string (settings, "logout-file");
        if (enabled) {
            if (sound) {
                if (g_file_test (sound, G_FILE_TEST_EXISTS)) {
                    g_warning ("Playing logout sound '%s'", sound);
                    manager->priv->logout_sound_is_playing = TRUE;
                    ca_context_create (&manager->priv->ca);
                    ca_context_set_driver (manager->priv->ca, "pulse");
                    ca_context_change_props (manager->priv->ca, 0, CA_PROP_APPLICATION_ID, "org.gnome.VolumeControl", NULL);
                    ca_proplist *proplist = NULL;
                    ca_proplist_create(&proplist);
                    ca_proplist_sets(proplist, CA_PROP_MEDIA_FILENAME, sound);
                    int result = ca_context_play_full(manager->priv->ca, 0, proplist, _finished_playing_logout_sound, manager); 
                    if (result != CA_SUCCESS) {
                        g_warning ("Logout sound failed to play, skipping.");
                        manager->priv->logout_sound_is_playing = FALSE;
                    }
                }
            }
        }
        g_free(sound);
        g_object_unref (settings);    

        while (manager->priv->logout_sound_is_playing) {
            sleep(1);
        }
}

static void
maybe_save_session (CsmManager *manager)
{
        GError *error;

        if (csm_system_is_login_session (manager->priv->system))
                return;

        /* We only allow session saving when session is running or when
         * logging out */
        if (manager->priv->phase != CSM_MANAGER_PHASE_RUNNING &&
            manager->priv->phase != CSM_MANAGER_PHASE_END_SESSION) {
                return;
        }

        if (!csm_manager_get_autosave_enabled (manager)) {
                csm_session_save_clear ();
                return;
        }

        error = NULL;
        csm_session_save (manager->priv->clients, &error);

        if (error) {
                g_warning ("Error saving session: %s", error->message);
                g_error_free (error);
        }
}

static void
_handle_client_end_session_response (CsmManager *manager,
                                     CsmClient  *client,
                                     gboolean    is_ok,
                                     gboolean    do_last,
                                     gboolean    cancel,
                                     const char *reason)
{
        /* just ignore if received outside of shutdown */
        if (manager->priv->phase < CSM_MANAGER_PHASE_QUERY_END_SESSION) {
                return;
        }

        g_debug ("CsmManager: 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;
        }

        manager->priv->query_clients = g_slist_remove (manager->priv->query_clients, client);

        if (! is_ok && manager->priv->logout_mode != CSM_MANAGER_LOGOUT_MODE_FORCE) {
                guint         cookie;
                CsmInhibitor *inhibitor;
                char         *app_id;
                const char   *bus_name;

                /* FIXME: do we support updating the reason? */

                /* Create JIT inhibit */
                if (CSM_IS_DBUS_CLIENT (client)) {
                        bus_name = csm_dbus_client_get_bus_name (CSM_DBUS_CLIENT (client));
                } else {
                        bus_name = NULL;
                }

                app_id = g_strdup (csm_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 = csm_client_get_app_name (client);
                }

                cookie = _generate_unique_cookie (manager);
                inhibitor = csm_inhibitor_new_for_client (csm_client_peek_id (client),
                                                          app_id,
                                                          CSM_INHIBITOR_FLAG_LOGOUT,
                                                          reason != NULL ? reason : _("Not responding"),
                                                          bus_name,
                                                          cookie);
                g_free (app_id);
                csm_store_add (manager->priv->inhibitors, csm_inhibitor_peek_id (inhibitor), G_OBJECT (inhibitor));
                g_object_unref (inhibitor);
        } else {
                csm_store_foreach_remove (manager->priv->inhibitors,
                                          (CsmStoreFunc)inhibitor_has_client_id,
                                          (gpointer)csm_client_peek_id (client));
        }

        if (manager->priv->phase == CSM_MANAGER_PHASE_QUERY_END_SESSION) { 
                if (manager->priv->query_clients == NULL) {
                        query_end_session_complete (manager);
                }
        } else if (manager->priv->phase == CSM_MANAGER_PHASE_END_SESSION) {
                if (do_last) {
                        /* This only makes sense if we're in part 1 of
                         * CSM_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. */
                        manager->priv->next_query_clients = g_slist_prepend (manager->priv->next_query_clients,
                                                                             client);
                }

                /* we can continue to the next step if all clients have replied
                 * and if there's no inhibitor */
                if (manager->priv->query_clients != NULL
                    || csm_manager_is_logout_inhibited (manager)) {
                        return;
                }

                if (manager->priv->next_query_clients != NULL) {
                        do_phase_end_session_part_2 (manager);
                } else {
                        end_phase (manager);
                }
        }
}

static void
on_client_end_session_response (CsmClient  *client,
                                gboolean    is_ok,
                                gboolean    do_last,
                                gboolean    cancel,
                                const char *reason,
                                CsmManager *manager)
{
        _handle_client_end_session_response (manager,
                                             client,
                                             is_ok,
                                             do_last,
                                             cancel,
                                             reason);
}

gboolean
csm_manager_logout (CsmManager *manager,
                    guint       logout_mode,
                    GError    **error)
{
        g_debug ("CsmManager: Logout called");

        g_return_val_if_fail (CSM_IS_MANAGER (manager), FALSE);

        if (manager->priv->phase != CSM_MANAGER_PHASE_RUNNING) {
                g_set_error (error,
                             CSM_MANAGER_ERROR,
                             CSM_MANAGER_ERROR_NOT_IN_RUNNING,
                             "Logout interface is only available during the Running phase");
                return FALSE;
        }

        if (_log_out_is_locked_down (manager)) {
                g_set_error (error,
                             CSM_MANAGER_ERROR,
                             CSM_MANAGER_ERROR_LOCKED_DOWN,
                             "Logout has been locked down");
                return FALSE;
        }

        switch (logout_mode) {
        case CSM_MANAGER_LOGOUT_MODE_NORMAL:
        case CSM_MANAGER_LOGOUT_MODE_NO_CONFIRMATION:
        case CSM_MANAGER_LOGOUT_MODE_FORCE:
                user_logout (manager, logout_mode);
                break;

        default:
                g_debug ("Unknown logout mode option");

                g_set_error (error,
                             CSM_MANAGER_ERROR,
                             CSM_MANAGER_ERROR_INVALID_OPTION,
                             "Unknown logout mode flag");
                return FALSE;
        }

        return TRUE;
}


static void
on_xsmp_client_logout_request (CsmXSMPClient *client,
                               gboolean       show_dialog,
                               CsmManager    *manager)
{
        GError *error;
        int     logout_mode;

        if (show_dialog) {
                logout_mode = CSM_MANAGER_LOGOUT_MODE_NORMAL;
        } else {
                logout_mode = CSM_MANAGER_LOGOUT_MODE_NO_CONFIRMATION;
        }

        error = NULL;

        csm_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 (CsmStore   *store,
                       const char *id,
                       CsmManager *manager)
{
        CsmClient *client;

        g_debug ("CsmManager: Client added: %s", id);

        client = (CsmClient *)csm_store_lookup (store, id);

        /* a bit hacky */
        if (CSM_IS_XSMP_CLIENT (client)) {
                g_signal_connect (client,
                                  "register-request",
                                  G_CALLBACK (on_xsmp_client_register_request),
                                  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);

        g_signal_connect (client,
                          "disconnected",
                          G_CALLBACK (on_client_disconnected),
                          manager);

        csm_exported_manager_emit_client_added (manager->priv->skeleton, id);
}

static void
on_store_client_removed (CsmStore   *store,
                         const char *id,
                         CsmManager *manager)
{
        g_debug ("CsmManager: Client removed: %s", id);

        csm_exported_manager_emit_client_removed (manager->priv->skeleton, id);
}

static void
csm_manager_set_client_store (CsmManager *manager,
                              CsmStore   *store)
{
        g_return_if_fail (CSM_IS_MANAGER (manager));

        if (store != NULL) {
                g_object_ref (store);
        }

        if (manager->priv->clients != NULL) {
                g_signal_handlers_disconnect_by_func (manager->priv->clients,
                                                      on_store_client_added,
                                                      manager);
                g_signal_handlers_disconnect_by_func (manager->priv->clients,
                                                      on_store_client_removed,
                                                      manager);

                g_object_unref (manager->priv->clients);
        }


        g_debug ("CsmManager: setting client store %p", store);

        manager->priv->clients = store;

        if (manager->priv->clients != NULL) {
            if (manager->priv->xsmp_server)
                    g_object_unref (manager->priv->xsmp_server);

                manager->priv->xsmp_server = csm_xsmp_server_new (store);

                g_signal_connect (manager->priv->clients,
                                  "added",
                                  G_CALLBACK (on_store_client_added),
                                  manager);
                g_signal_connect (manager->priv->clients,
                                  "removed",
                                  G_CALLBACK (on_store_client_removed),
                                  manager);
        }
}

static void
csm_manager_set_property (GObject       *object,
                          guint          prop_id,
                          const GValue  *value,
                          GParamSpec    *pspec)
{
        CsmManager *self;

        self = CSM_MANAGER (object);

        switch (prop_id) {
        case PROP_FAILSAFE:
                csm_manager_set_failsafe (self, g_value_get_boolean (value));
                break;
         case PROP_FALLBACK:
                self->priv->is_fallback_session = g_value_get_boolean (value);
                break;
         case PROP_CLIENT_STORE:
                csm_manager_set_client_store (self, g_value_get_object (value));
                break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                break;
        }
}

static void
csm_manager_get_property (GObject    *object,
                          guint       prop_id,
                          GValue     *value,
                          GParamSpec *pspec)
{
        CsmManager *self;

        self = CSM_MANAGER (object);

        switch (prop_id) {
        case PROP_FAILSAFE:
                g_value_set_boolean (value, self->priv->failsafe);
                break;
        case PROP_SESSION_NAME:
                g_value_set_string (value, self->priv->session_name);
                break;
        case PROP_FALLBACK:
                g_value_set_boolean (value, self->priv->is_fallback_session);
                break;
        case PROP_CLIENT_STORE:
                g_value_set_object (value, self->priv->clients);
                break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                break;
        }
}

static gboolean
_find_app_provides (const char *id,
                    CsmApp     *app,
                    const char *service)
{
        return csm_app_provides (app, service);
}

static GObject *
csm_manager_constructor (GType                  type,
                         guint                  n_construct_properties,
                         GObjectConstructParam *construct_properties)
{
        CsmManager *manager;

        manager = CSM_MANAGER (G_OBJECT_CLASS (csm_manager_parent_class)->constructor (type,
                                                                                       n_construct_properties,
                                                                                       construct_properties));
        return G_OBJECT (manager);
}

static void
update_inhibited_actions (CsmManager *manager,
                          CsmInhibitorFlag new_inhibited_actions)
{
        if (manager->priv->inhibited_actions == new_inhibited_actions)
                return;

        manager->priv->inhibited_actions = new_inhibited_actions;

        g_debug ("CsmManager: new inhibit flag: %d", new_inhibited_actions);

        csm_exported_manager_set_inhibited_actions (manager->priv->skeleton,
                                                    manager->priv->inhibited_actions);

        g_dbus_interface_skeleton_flush (G_DBUS_INTERFACE_SKELETON (manager->priv->skeleton));

        emit_inhibitor_info_to_dialog (manager, manager->priv->dialog_action);
}

static void
on_store_inhibitor_added (CsmStore   *store,
                          const char *id,
                          CsmManager *manager)
{
        CsmInhibitor *i;
        CsmInhibitorFlag new_inhibited_actions;

        g_debug ("CsmManager: Inhibitor added: %s", id);

        i = CSM_INHIBITOR (csm_store_lookup (store, id));
        csm_system_add_inhibitor (manager->priv->system, id,
                                  csm_inhibitor_peek_flags (i));

        new_inhibited_actions = manager->priv->inhibited_actions | csm_inhibitor_peek_flags (i);
        update_inhibited_actions (manager, new_inhibited_actions);

        csm_exported_manager_emit_inhibitor_added (manager->priv->skeleton, id);

        update_idle (manager);
}

static gboolean
collect_inhibition_flags (const char *id,
                          GObject    *object,
                          gpointer    user_data)
{
        CsmInhibitorFlag *new_inhibited_actions = user_data;

        *new_inhibited_actions |= csm_inhibitor_peek_flags (CSM_INHIBITOR (object));

        return FALSE;
}

static void
on_store_inhibitor_removed (CsmStore   *store,
                            const char *id,
                            CsmManager *manager)
{
        CsmInhibitorFlag new_inhibited_actions;

        g_debug ("CsmManager: Inhibitor removed: %s", id);

        csm_system_remove_inhibitor (manager->priv->system, id);

        new_inhibited_actions = 0;
        csm_store_foreach (manager->priv->inhibitors,
                           collect_inhibition_flags,
                           &new_inhibited_actions);
        update_inhibited_actions (manager, new_inhibited_actions);

        csm_exported_manager_emit_inhibitor_removed (manager->priv->skeleton, id);

        update_idle (manager);
}

static void
csm_manager_dispose (GObject *object)
{
        CsmManager *manager = CSM_MANAGER (object);

        g_debug ("CsmManager: disposing manager");

        close_end_session_dialog (manager);

        g_clear_object (&manager->priv->xsmp_server);

        g_clear_object (&manager->priv->bus_proxy);
        g_clear_object (&manager->priv->cinnamon_proxy);

        if (manager->priv->clients != NULL) {
                g_signal_handlers_disconnect_by_func (manager->priv->clients,
                                                      on_store_client_added,
                                                      manager);
                g_signal_handlers_disconnect_by_func (manager->priv->clients,
                                                      on_store_client_removed,
                                                      manager);
                g_object_unref (manager->priv->clients);
                manager->priv->clients = NULL;
        }

        if (manager->priv->apps != NULL) {
                g_object_unref (manager->priv->apps);
                manager->priv->apps = NULL;
        }

        g_slist_free (manager->priv->required_apps);
        manager->priv->required_apps = NULL;

        if (manager->priv->inhibitors != NULL) {
                g_signal_handlers_disconnect_by_func (manager->priv->inhibitors,
                                                      on_store_inhibitor_added,
                                                      manager);
                g_signal_handlers_disconnect_by_func (manager->priv->inhibitors,
                                                      on_store_inhibitor_removed,
                                                      manager);

                g_object_unref (manager->priv->inhibitors);
                manager->priv->inhibitors = NULL;
        }

        if (manager->priv->presence != NULL) {
                g_object_unref (manager->priv->presence);
                manager->priv->presence = NULL;
        }

        if (manager->priv->settings) {
                g_object_unref (manager->priv->settings);
                manager->priv->settings = NULL;
        }

        if (manager->priv->session_settings) {
                g_object_unref (manager->priv->session_settings);
                manager->priv->session_settings = NULL;
        }

        if (manager->priv->power_settings) {
                g_object_unref (manager->priv->power_settings);
                manager->priv->power_settings = NULL;
        }

        if (manager->priv->lockdown_settings) {
                g_object_unref (manager->priv->lockdown_settings);
                manager->priv->lockdown_settings = NULL;
        }

        if (manager->priv->system != NULL) {
                g_object_unref (manager->priv->system);
                manager->priv->system = NULL;
        }

        if (manager->priv->skeleton != NULL) {
                g_dbus_interface_skeleton_unexport_from_connection (G_DBUS_INTERFACE_SKELETON (manager->priv->skeleton),
                                                                    manager->priv->connection);
                g_clear_object (&manager->priv->skeleton);
        }

        g_signal_handlers_disconnect_by_func (manager->priv->connection,
                                              on_bus_connection_closed,
                                              manager);
        g_clear_object (&manager->priv->connection);

        G_OBJECT_CLASS (csm_manager_parent_class)->dispose (object);
}

static void
csm_manager_class_init (CsmManagerClass *klass)
{
        GObjectClass   *object_class = G_OBJECT_CLASS (klass);

        object_class->get_property = csm_manager_get_property;
        object_class->set_property = csm_manager_set_property;
        object_class->constructor = csm_manager_constructor;
        object_class->dispose = csm_manager_dispose;

        signals [PHASE_CHANGED] =
                g_signal_new ("phase-changed",
                              G_TYPE_FROM_CLASS (object_class),
                              G_SIGNAL_RUN_LAST,
                              G_STRUCT_OFFSET (CsmManagerClass, phase_changed),
                              NULL,
                              NULL,
                              g_cclosure_marshal_VOID__STRING,
                              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));

        /**
         * CsmManager::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));

        /**
         * CsmManager::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,
                                                              CSM_TYPE_STORE,
                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT));

        g_type_class_add_private (klass, sizeof (CsmManagerPrivate));
}

static void
on_presence_status_changed (CsmPresence  *presence,
                            guint         status,
                            CsmManager   *manager)
{
        CsmSystem *system;

        system = csm_get_system ();
        csm_system_set_session_idle (system,
                                     (status == CSM_PRESENCE_STATUS_IDLE));
        g_object_unref (system);
}

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
csm_manager_init (CsmManager *manager)
{

        manager->priv = CSM_MANAGER_GET_PRIVATE (manager);

        manager->priv->settings = g_settings_new (CSM_MANAGER_SCHEMA);
        manager->priv->session_settings = g_settings_new (SESSION_SCHEMA);
        manager->priv->power_settings = g_settings_new (POWER_SETTINGS_SCHEMA);
        manager->priv->lockdown_settings = g_settings_new (LOCKDOWN_SCHEMA);

        manager->priv->inhibitors = csm_store_new ();
        g_signal_connect (manager->priv->inhibitors,
                          "added",
                          G_CALLBACK (on_store_inhibitor_added),
                          manager);
        g_signal_connect (manager->priv->inhibitors,
                          "removed",
                          G_CALLBACK (on_store_inhibitor_removed),
                          manager);

        manager->priv->apps = csm_store_new ();

        manager->priv->presence = csm_presence_new ();
        g_signal_connect (manager->priv->presence,
                          "status-changed",
                          G_CALLBACK (on_presence_status_changed),
                          manager);

        g_settings_bind_with_mapping (manager->priv->session_settings,
                                      KEY_IDLE_DELAY,
                                      manager->priv->presence,
                                      "idle-timeout",
                                      G_SETTINGS_BIND_GET,
                                      idle_timeout_get_mapping,
                                      NULL,
                                      NULL, NULL);

        manager->priv->system = csm_get_system ();
}

CsmManager *
csm_manager_get (void)
{
        return manager_object;
}

CsmManager *
csm_manager_new (CsmStore *client_store,
                 gboolean  failsafe)
{
        if (manager_object != NULL) {
                g_object_ref (manager_object);
        } else {
                gboolean res;

                manager_object = g_object_new (CSM_TYPE_MANAGER,
                                               "client-store", client_store,
                                               "failsafe", failsafe,
                                               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 CSM_MANAGER (manager_object);
}

static gboolean
csm_manager_is_switch_user_inhibited (CsmManager *manager)
{
        CsmInhibitor *inhibitor;

        if (manager->priv->inhibitors == NULL) {
                return FALSE;
        }

        inhibitor = (CsmInhibitor *)csm_store_find (manager->priv->inhibitors,
                                                    (CsmStoreFunc)inhibitor_has_flag,
                                                    GUINT_TO_POINTER (CSM_INHIBITOR_FLAG_SWITCH_USER));
        if (inhibitor == NULL) {
                return FALSE;
        }
        return TRUE;
}

static gboolean
csm_manager_is_suspend_inhibited (CsmManager *manager)
{
        CsmInhibitor *inhibitor;

        if (manager->priv->inhibitors == NULL) {
                return FALSE;
        }

        inhibitor = (CsmInhibitor *)csm_store_find (manager->priv->inhibitors,
                                                    (CsmStoreFunc)inhibitor_has_flag,
                                                    GUINT_TO_POINTER (CSM_INHIBITOR_FLAG_SUSPEND));
        if (inhibitor == NULL) {
                return FALSE;
        }
        return TRUE;
}

static void
request_reboot (CsmManager *manager)
{
        g_debug ("CsmManager: 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
         */
        manager->priv->logout_type = CSM_MANAGER_LOGOUT_REBOOT_INTERACT;
        end_phase (manager);
}

static void
request_shutdown (CsmManager *manager)
{
        g_debug ("CsmManager: requesting shutdown");

        /* See the comment in request_reboot() for some more details about
         * what work needs to be done here. */
        manager->priv->logout_type = CSM_MANAGER_LOGOUT_SHUTDOWN_INTERACT;
        end_phase (manager);
}

static void
request_suspend (CsmManager *manager)
{
        g_debug ("CsmManager: requesting suspend");

        if (! csm_manager_is_suspend_inhibited (manager)) {
                manager_attempt_suspend (manager);
                return;
        }

        emit_inhibitor_info_to_dialog (manager, CSM_LOGOUT_ACTION_SLEEP);
}

static void
request_hibernate (CsmManager *manager)
{
        g_debug ("CsmManager: requesting hibernate");

        /* hibernate uses suspend inhibit */
        if (! csm_manager_is_suspend_inhibited (manager)) {
                manager_attempt_hibernate (manager);
                return;
        }

        emit_inhibitor_info_to_dialog (manager, CSM_LOGOUT_ACTION_HIBERNATE);
}

static void
request_logout (CsmManager           *manager,
                CsmManagerLogoutMode  mode)
{
        g_debug ("CsmManager: requesting logout");

        manager->priv->logout_mode = mode;
        manager->priv->logout_type = CSM_MANAGER_LOGOUT_LOGOUT;

        end_phase (manager);
}

static void
request_switch_user (CsmManager *manager)
{
        g_debug ("CsmManager: requesting user switch");

	/* See comment in manager_switch_user() to understand why we do this in
	 * both functions. */
	if (_switch_user_is_locked_down (manager)) {
		g_warning ("Unable to switch user: User switching has been locked down");
		return;
	}

    if (! csm_manager_is_switch_user_inhibited (manager)) {
            manager_switch_user (manager);
            return;
    }

    emit_inhibitor_info_to_dialog (manager, CSM_LOGOUT_ACTION_SWITCH_USER);
}

static GSubprocess *dialog_process = NULL;
static GCancellable *dialog_cancellable = NULL;

static void
on_dialog_exited (GObject *source,
                  GAsyncResult *result,
                  gpointer user_data)
{
    GError *error = NULL;
    g_debug ("Session quit dialog exited");

    if (!g_subprocess_wait_finish ((GSubprocess *) source,
                                   result,
                                   &error)) {
        if (error != NULL) {
            g_critical ("The session-manager dialog did not exit successfully: %s", error->message);
            g_error_free (error);
        }
    }

    g_clear_object (&dialog_process);
    g_clear_object (&dialog_cancellable);
}

static void
on_dialog_read (GObject *source,
                GAsyncResult *result,
                gpointer user_data)
{
    GInputStream *stream = G_INPUT_STREAM (source);

    g_debug ("Session quit dialog read output");

    gssize read = g_input_stream_read_finish (stream, result, NULL);
    if (read > 0) {
        g_debug ("Session quit dialog (self-owned) output: %.*s", (gint) read, (gchar *) user_data);
    } else {
        g_debug ("Session quit dialog (self-owned) stdout closed");
        g_free (user_data);
        g_input_stream_close (stream, NULL, NULL);
        return;
    }

    g_input_stream_read_async (stream,
                               user_data,
                               1024,
                               G_PRIORITY_DEFAULT,
                               NULL,
                               (GAsyncReadyCallback) on_dialog_read,
                               user_data);
}

static void
close_end_session_dialog (CsmManager *manager)
{
    g_debug ("Closing end session dialog");

    if (dialog_process != NULL) {
        g_subprocess_send_signal (dialog_process, SIGTERM);
        return;
    }

    // If Cinnamon has already left the bus (e.g. it crashed and we're tearing
    // down), there's nothing to close. This uses cinnamon_proxy's cached name
    // owner, which may lag behind a NameOwnerChanged we haven't dispatched yet -
    // that's fine here, the session is ending regardless.
    gchar *owner = g_dbus_proxy_get_name_owner (manager->priv->cinnamon_proxy);
    if (owner == NULL) {
        g_debug ("Cinnamon is not on the bus, nothing to close");
        return;
    }
    g_free (owner);

    GError *error = NULL;
    GVariant *ret = NULL;

    ret = g_dbus_proxy_call_sync (manager->priv->cinnamon_proxy,
                                  "CloseEndSessionDialog",
                                  NULL,
                                  G_DBUS_CALL_FLAGS_NONE,
                                  -1,
                                  NULL,
                                  &error);

    if (error != NULL) {
        g_critical ("Unable to close Cinnamon's end session dialog: %s", error->message);
        g_error_free (error);
    } else {
        g_variant_unref (ret);
    }
}

static void
launch_gtk_dialog (CsmManager *manager,
               DialogMode  mode)
{
    GError *error;
    const gchar *flag;
    g_debug ("Trying to launch fallback (Gtk) end-session dialog");

    if (dialog_process != NULL) {
        g_debug ("There's already a session-manager dialog");
        return;
    }

    // If we're spawning the dialog and *already* in the query phase,
    // we can assume something went wrong previously, so reset back
    // to the RUNNING phase.
    if (manager->priv->phase == CSM_MANAGER_PHASE_QUERY_END_SESSION) {
        g_critical ("Session not in running phase, previous dialog may have exited unexpectedly");
        cancel_end_session (manager);
    }

    switch (mode) {
        case DIALOG_MODE_LOGOUT:
            flag = "--logout";
            break;
        case DIALOG_MODE_REBOOT:
            flag = "--reboot";
            break;
        case DIALOG_MODE_SHUTDOWN:
            flag = "--power-off";
            
            break;
        default:
            g_warning ("Unknown dialog mode: %d", mode);
            return;
    }

    const gchar *argv[] = {
        "cinnamon-session-quit",
        "--sm-owned",
        flag,
        NULL
    };

    gboolean debugging = g_settings_get_boolean (manager->priv->settings, KEY_DEBUG);
    GSubprocessFlags flags = G_SUBPROCESS_FLAGS_NONE;

    if (debugging) {
        flags = G_SUBPROCESS_FLAGS_STDERR_PIPE;
    }

    error = NULL;
    dialog_process = g_subprocess_newv (argv, flags, &error);

    if (dialog_process == NULL) {
        if (error != NULL) {
            g_critical ("Failed to launch session-manager dialog: %s", error->message);
            g_error_free (error);
            return;
        }
    }

    if (debugging) {
        GInputStream *stdout = g_subprocess_get_stderr_pipe (dialog_process);
        guint8 *buffer = g_malloc (1024);
        g_input_stream_read_async (G_INPUT_STREAM (stdout),
                                   buffer,
                                   1024,
                                   G_PRIORITY_DEFAULT,
                                   NULL,
                                   (GAsyncReadyCallback) on_dialog_read,
                                   buffer);
    }

    dialog_cancellable = g_cancellable_new ();

    g_subprocess_wait_async (dialog_process,
                              dialog_cancellable,
                              (GAsyncReadyCallback) on_dialog_exited,
                              manager);
}

static gboolean
launch_cinnamon_dialog (CsmManager  *manager,
                        DialogMode   mode,
                        GError     **error)
{
    GVariant *ret = NULL;

    g_debug ("Requesting Cinnamon display its end-session dialog");

    ret = g_dbus_proxy_call_sync (manager->priv->cinnamon_proxy,
                                  "ShowEndSessionDialog",
                                  g_variant_new ("(i)", mode),
                                  G_DBUS_CALL_FLAGS_NONE,
                                  -1,
                                  NULL,
                                  error);

    if (*error != NULL) {
        return FALSE;
    }
    g_debug ("launch ret is: %p", ret);

    g_variant_unref (ret);

    return TRUE;
}

static void
show_end_session_dialog (CsmManager *manager,
                         DialogMode  mode)
{
    GError *error = NULL;
    
    if (g_settings_get_boolean (manager->priv->settings, KEY_FORCE_GTK_END_SESSION)) {
        g_debug ("Using Gtk end-session dialog based on settings.");
        launch_gtk_dialog (manager, mode);
        return;
    }

    if (!launch_cinnamon_dialog (manager, mode, &error)) {
        if (error->code == G_DBUS_ERROR_NAME_HAS_NO_OWNER) {
            g_debug ("Cinnamon is not running, falling back to Gtk end-session dialog");
        } else {
            g_critical ("Failed to launch Cinnamon's end session dialog, falling back to Gtk end-session dialog: %s", error->message);
        }

        g_error_free (error);
        launch_gtk_dialog (manager, mode);
    }
}

static void
show_shutdown_dialog (CsmManager *manager,
                      gboolean    is_reboot)
{
        if (manager->priv->phase > CSM_MANAGER_PHASE_QUERY_END_SESSION) {
                /* Already shutting down, nothing more to do */
                return;
        }

        manager->priv->logout_mode = CSM_MANAGER_LOGOUT_MODE_NORMAL;

        show_end_session_dialog (manager, is_reboot ? DIALOG_MODE_REBOOT : DIALOG_MODE_SHUTDOWN);
}

static void
show_logout_dialog (CsmManager *manager)
{
        if (manager->priv->phase > CSM_MANAGER_PHASE_QUERY_END_SESSION) {
                /* Already shutting down, nothing more to do */
                return;
        }

        manager->priv->logout_mode = CSM_MANAGER_LOGOUT_MODE_NORMAL;

        show_end_session_dialog (manager, DIALOG_MODE_LOGOUT);
}

static void
user_logout (CsmManager           *manager,
             CsmManagerLogoutMode  mode)
{
        gboolean logout_prompt;

        if (manager->priv->phase >= CSM_MANAGER_PHASE_QUERY_END_SESSION) {
                /* Already shutting down, nothing more to do */
                return;
        }

        logout_prompt = g_settings_get_boolean (manager->priv->settings,
                                                KEY_LOGOUT_PROMPT);

        /* If the shell isn't running, and this isn't a non-interative logout request,
         * and the user has their settings configured to show a confirmation dialog for
         * logout, then go ahead and show the fallback confirmation dialog now.
         *
         * If the shell is running, then the confirmation dialog and inhibitor dialog are
         * combined, so we'll show it at a later stage in the logout process.
         */
        if (mode == CSM_MANAGER_LOGOUT_MODE_NORMAL && logout_prompt) {
                show_logout_dialog (manager);
        } else {
                request_logout (manager, mode);
        }
}

/*
  dbus-send --session --type=method_call --print-reply
      --dest=org.gnome.SessionManager
      /org/gnome/SessionManager
      org.freedesktop.DBus.Introspectable.Introspect
*/

gboolean
csm_manager_set_phase (CsmManager      *manager,
                       CsmManagerPhase  phase)
{
        g_return_val_if_fail (CSM_IS_MANAGER (manager), FALSE);
        manager->priv->phase = phase;
        return (TRUE);
}

static gboolean
_log_out_is_locked_down (CsmManager *manager)
{
        return g_settings_get_boolean (manager->priv->lockdown_settings,
                                       KEY_DISABLE_LOG_OUT);
}

static gboolean
_switch_user_is_locked_down (CsmManager *manager)
{
        return g_settings_get_boolean (manager->priv->lockdown_settings,
                                       KEY_DISABLE_USER_SWITCHING);
}


static void
append_app (CsmManager *manager,
            CsmApp     *app,
            const char *provides,
            gboolean    is_required)
{
        const char *id;
        const char *app_id;
        CsmApp     *dup;

        id = csm_app_peek_id (app);
        if (IS_STRING_EMPTY (id)) {
                g_debug ("CsmManager: not adding app: no id");
                return;
        }

        dup = (CsmApp *)csm_store_lookup (manager->priv->apps, id);
        if (dup != NULL) {
                g_debug ("CsmManager: not adding app: already added");
                return;
        }

        app_id = csm_app_peek_app_id (app);
        if (IS_STRING_EMPTY (app_id)) {
                g_debug ("CsmManager: not adding app: no app-id");
                return;
        }

        dup = find_app_for_app_id (manager, app_id);
        if (dup != NULL) {
                g_debug ("CsmManager: not adding app: app-id '%s' already exists", app_id);

                if (provides && CSM_IS_AUTOSTART_APP (dup))
                        csm_autostart_app_add_provides (CSM_AUTOSTART_APP (dup), provides);

                if (is_required &&
                    !g_slist_find (manager->priv->required_apps, dup)) {
                        g_debug ("CsmManager: making app '%s' required", csm_app_peek_app_id (dup));
                        manager->priv->required_apps = g_slist_prepend (manager->priv->required_apps, dup);
                }

                return;
        }

        csm_store_add (manager->priv->apps, id, G_OBJECT (app));
        if (is_required) {
                g_debug ("CsmManager: adding required app %s", csm_app_peek_app_id (app));
                manager->priv->required_apps = g_slist_prepend (manager->priv->required_apps, app);
        }
}

static gboolean
add_autostart_app_internal (CsmManager *manager,
                            const char *path,
                            const char *provides,
                            gboolean    is_required)
{
        CsmApp  *app;
        char   **internal_provides;

        g_return_val_if_fail (CSM_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. */

        /* first check to see if service is already provided */
        if (provides != NULL) {
                CsmApp *dup;

                dup = (CsmApp *)csm_store_find (manager->priv->apps,
                                                (CsmStoreFunc)_find_app_provides,
                                                (char *)provides);
                if (dup != NULL) {
                        g_debug ("CsmManager: service '%s' is already provided", provides);

                        if (is_required &&
                            !g_slist_find (manager->priv->required_apps, dup)) {
                                g_debug ("CsmManager: making app '%s' required", csm_app_peek_app_id (dup));
                                manager->priv->required_apps = g_slist_prepend (manager->priv->required_apps, dup);
                        }

                        return FALSE;
                }
        }

        app = csm_autostart_app_new (path);

        if (app == NULL) {
                return FALSE;
        }

        internal_provides = csm_app_get_provides (app);
        if (internal_provides) {
                int i;
                gboolean provided = FALSE;

                for (i = 0; internal_provides[i] != NULL; i++) {
                        CsmApp *dup;

                        dup = (CsmApp *)csm_store_find (manager->priv->apps,
                                                        (CsmStoreFunc)_find_app_provides,
                                                        (char *)internal_provides[i]);
                        if (dup != NULL) {
                                g_debug ("CsmManager: service '%s' is already provided", internal_provides[i]);

                                if (is_required &&
                                    !g_slist_find (manager->priv->required_apps, dup)) {
                                        g_debug ("CsmManager: making app '%s' required", csm_app_peek_app_id (dup));
                                        manager->priv->required_apps = g_slist_prepend (manager->priv->required_apps, dup);
                                }

                                provided = TRUE;
                                break;
                        }
                }

                g_strfreev (internal_provides);

                if (provided) {
                        g_object_unref (app);
                        return FALSE;
                }
        }

        if (provides)
                csm_autostart_app_add_provides (CSM_AUTOSTART_APP (app), provides);

        g_debug ("CsmManager: read %s", path);
        append_app (manager, app, provides, is_required);
        g_object_unref (app);

        return TRUE;
}

gboolean
csm_manager_add_autostart_app (CsmManager *manager,
                               const char *path,
                               const char *provides)
{
        return add_autostart_app_internal (manager,
                                           path,
                                           provides,
                                           FALSE);
}

/**
 * csm_manager_add_required_app:
 * @manager: a #CsmManager
 * @path: Path to desktop file
 * @provides: What the component provides, as a space separated list
 *
 * Similar to csm_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
csm_manager_add_required_app (CsmManager *manager,
                              const char *path,
                              const char *provides)
{
        return add_autostart_app_internal (manager,
                                           path,
                                           provides,
                                           TRUE);
}


gboolean
csm_manager_add_autostart_apps_from_dir (CsmManager *manager,
                                         const char *path)
{
        GDir       *dir;
        const char *name;

        g_return_val_if_fail (CSM_IS_MANAGER (manager), FALSE);
        g_return_val_if_fail (path != NULL, FALSE);

        g_debug ("CsmManager: *** 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") ||
                    csm_manager_get_app_is_blacklisted (manager, name)) {
                        continue;
                }

                desktop_file = g_build_filename (path, name, NULL);
                csm_manager_add_autostart_app (manager, desktop_file, NULL);
                g_free (desktop_file);
        }

        g_dir_close (dir);

        return TRUE;
}


static gboolean
mate_polkit_agent_should_be_skipped (const char *name)
{
    /* /etc/xdg/autostart launches the MATE polkit agent unconditionally, but
     * it only makes sense as an X11 fallback when our own agent is disabled.
     * Otherwise it just races Cinnamon's internal agent, gets rejected by
     * polkitd, and adds log noise at startup; on Wayland it's useless either
     * way. cinnamon-launcher still starts it by hand when it drops to the
     * fallback MATE panel, then kills it when restarting Cinnamon. */
    if (g_strstr_len (name, -1, POLKIT_MATE_AGENT_ID) == NULL)
        return FALSE;

    if (csm_util_is_wayland_session ())
        return TRUE;

    GSettings *settings = g_settings_new (CINNAMON_SCHEMA);
    gboolean internal_agent_enabled = g_settings_get_boolean (settings, KEY_ENABLE_POLKIT_AGENT);
    g_object_unref (settings);

    return internal_agent_enabled;
}

gboolean
csm_manager_get_app_is_blacklisted (CsmManager *manager,
                                    const char *name)
{
    g_return_val_if_fail (CSM_IS_MANAGER (manager), FALSE);

    if (mate_polkit_agent_should_be_skipped (name))
        return TRUE;

    gchar **gs_blacklist = g_settings_get_strv(manager->priv->settings, KEY_BLACKLIST);
    GList *list = NULL;
    gboolean ret = FALSE;

    int i;
    for (i = 0; i < g_strv_length (gs_blacklist); i++)
        list = g_list_append (list, g_strdup (gs_blacklist[i]));

    GList *l;

    for (l = list; l != NULL; l = l->next) {
        gchar *ptr = g_strstr_len (name, -1, l->data);
        if (ptr != NULL) {
            ret = TRUE;
            break;
        }
    }

    g_list_free_full (list, g_free);
    g_strfreev (gs_blacklist);
    return ret;
}

gboolean
csm_manager_get_autosave_enabled (CsmManager *manager)
{
    return g_settings_get_boolean (manager->priv->settings,
                                   KEY_AUTOSAVE);
}
