summaryrefslogtreecommitdiffstats
path: root/gnome-session/gsm-systemd.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--gnome-session/gsm-systemd.c1189
1 files changed, 1189 insertions, 0 deletions
diff --git a/gnome-session/gsm-systemd.c b/gnome-session/gsm-systemd.c
new file mode 100644
index 0000000..6ac6c9b
--- /dev/null
+++ b/gnome-session/gsm-systemd.c
@@ -0,0 +1,1189 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Matthias Clasen
+ */
+
+#include "config.h"
+#include "gsm-systemd.h"
+
+#ifdef HAVE_SYSTEMD
+
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <pwd.h>
+
+#include <systemd/sd-login.h>
+
+#include <glib.h>
+#include <glib-object.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <gio/gunixfdlist.h>
+
+#include "gsm-system.h"
+
+#define SD_NAME "org.freedesktop.login1"
+#define SD_PATH "/org/freedesktop/login1"
+#define SD_INTERFACE "org.freedesktop.login1.Manager"
+#define SD_SEAT_INTERFACE "org.freedesktop.login1.Seat"
+#define SD_SESSION_INTERFACE "org.freedesktop.login1.Session"
+
+#define SYSTEMD_SESSION_REQUIRE_ONLINE 0 /* active or online sessions only */
+
+struct _GsmSystemdPrivate
+{
+ GSource *sd_source;
+ GDBusProxy *sd_proxy;
+ char *session_id;
+ gchar *session_path;
+
+ GSList *inhibitors;
+ gint inhibit_fd;
+
+ gboolean is_active;
+
+ gint delay_inhibit_fd;
+ gboolean prepare_for_shutdown_expected;
+};
+
+enum {
+ PROP_0,
+ PROP_ACTIVE
+};
+
+static void gsm_systemd_system_init (GsmSystemInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GsmSystemd, gsm_systemd, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GSM_TYPE_SYSTEM,
+ gsm_systemd_system_init))
+
+static void
+drop_system_inhibitor (GsmSystemd *manager)
+{
+ if (manager->priv->inhibit_fd != -1) {
+ g_debug ("GsmSystemd: Dropping system inhibitor");
+ close (manager->priv->inhibit_fd);
+ manager->priv->inhibit_fd = -1;
+ }
+}
+
+static void
+drop_delay_inhibitor (GsmSystemd *manager)
+{
+ if (manager->priv->delay_inhibit_fd != -1) {
+ g_debug ("GsmSystemd: Dropping delay inhibitor");
+ close (manager->priv->delay_inhibit_fd);
+ manager->priv->delay_inhibit_fd = -1;
+ }
+}
+
+static void
+gsm_systemd_finalize (GObject *object)
+{
+ GsmSystemd *systemd = GSM_SYSTEMD (object);
+
+ g_clear_object (&systemd->priv->sd_proxy);
+ free (systemd->priv->session_id);
+ g_free (systemd->priv->session_path);
+
+ if (systemd->priv->sd_source) {
+ g_source_destroy (systemd->priv->sd_source);
+ }
+
+ if (systemd->priv->inhibitors != NULL) {
+ g_slist_free_full (systemd->priv->inhibitors, g_free);
+ }
+ drop_system_inhibitor (systemd);
+ drop_delay_inhibitor (systemd);
+
+ G_OBJECT_CLASS (gsm_systemd_parent_class)->finalize (object);
+}
+
+static void
+gsm_systemd_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GsmSystemd *self = GSM_SYSTEMD (object);
+
+ switch (prop_id) {
+ case PROP_ACTIVE:
+ self->priv->is_active = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gsm_systemd_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GsmSystemd *self = GSM_SYSTEMD (object);
+
+ switch (prop_id) {
+ case PROP_ACTIVE:
+ g_value_set_boolean (value, self->priv->is_active);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gsm_systemd_class_init (GsmSystemdClass *manager_class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (manager_class);
+
+ object_class->get_property = gsm_systemd_get_property;
+ object_class->set_property = gsm_systemd_set_property;
+ object_class->finalize = gsm_systemd_finalize;
+
+ g_object_class_override_property (object_class, PROP_ACTIVE, "active");
+
+ g_type_class_add_private (manager_class, sizeof (GsmSystemdPrivate));
+}
+
+typedef struct
+{
+ GSource source;
+ GPollFD pollfd;
+ sd_login_monitor *monitor;
+} SdSource;
+
+static gboolean
+sd_source_prepare (GSource *source,
+ gint *timeout)
+{
+ *timeout = -1;
+ return FALSE;
+}
+
+static gboolean
+sd_source_check (GSource *source)
+{
+ SdSource *sd_source = (SdSource *)source;
+
+ return sd_source->pollfd.revents != 0;
+}
+
+static gboolean
+sd_source_dispatch (GSource *source,
+ GSourceFunc callback,
+ gpointer user_data)
+
+{
+ SdSource *sd_source = (SdSource *)source;
+ gboolean ret;
+
+ g_warn_if_fail (callback != NULL);
+
+ ret = (*callback) (user_data);
+
+ sd_login_monitor_flush (sd_source->monitor);
+ return ret;
+}
+
+static void
+sd_source_finalize (GSource *source)
+{
+ SdSource *sd_source = (SdSource*)source;
+
+ sd_login_monitor_unref (sd_source->monitor);
+}
+
+static GSourceFuncs sd_source_funcs = {
+ sd_source_prepare,
+ sd_source_check,
+ sd_source_dispatch,
+ sd_source_finalize
+};
+
+static GSource *
+sd_source_new (void)
+{
+ GSource *source;
+ SdSource *sd_source;
+ int ret;
+
+ source = g_source_new (&sd_source_funcs, sizeof (SdSource));
+ sd_source = (SdSource *)source;
+
+ if ((ret = sd_login_monitor_new ("session", &sd_source->monitor)) < 0) {
+ g_warning ("Error getting login monitor: %d", ret);
+ } else {
+ sd_source->pollfd.fd = sd_login_monitor_get_fd (sd_source->monitor);
+ sd_source->pollfd.events = G_IO_IN;
+ g_source_add_poll (source, &sd_source->pollfd);
+ }
+
+ return source;
+}
+
+static gboolean
+on_sd_source_changed (gpointer user_data)
+{
+ GsmSystemd *self = user_data;
+ int active_r;
+ gboolean active;
+
+ active_r = sd_session_is_active (self->priv->session_id);
+ if (active_r < 0)
+ active = FALSE;
+ else
+ active = active_r;
+ if (active != self->priv->is_active) {
+ self->priv->is_active = active;
+ g_object_notify (G_OBJECT (self), "active");
+ }
+
+ return TRUE;
+}
+
+static void sd_proxy_signal_cb (GDBusProxy *proxy,
+ const gchar *sender_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data);
+
+static gboolean
+_systemd_session_is_graphical (const char *session_id)
+{
+ const gchar * const graphical_session_types[] = { "wayland", "x11", "mir", NULL };
+ int saved_errno;
+ g_autofree gchar *type = NULL;
+
+ saved_errno = sd_session_get_type (session_id, &type);
+ if (saved_errno < 0) {
+ g_warning ("Couldn't get type for session '%s': %s",
+ session_id,
+ g_strerror (-saved_errno));
+ return FALSE;
+ }
+
+ if (!g_strv_contains (graphical_session_types, type)) {
+ g_debug ("Session '%s' is not a graphical session (type: '%s')",
+ session_id,
+ type);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+_systemd_session_is_active (const char *session_id)
+{
+ const gchar * const active_states[] = { "active", "online", NULL };
+ int saved_errno;
+ g_autofree gchar *state = NULL;
+
+ /*
+ * display sessions can be 'closing' if they are logged out but some
+ * processes are lingering; we shouldn't consider these (this is
+ * checking for a race condition since we specified
+ * SYSTEMD_SESSION_REQUIRE_ONLINE)
+ */
+ saved_errno = sd_session_get_state (session_id, &state);
+ if (saved_errno < 0) {
+ g_warning ("Couldn't get state for session '%s': %s",
+ session_id,
+ g_strerror (-saved_errno));
+ return FALSE;
+ }
+
+ if (!g_strv_contains (active_states, state)) {
+ g_debug ("Session '%s' is not active or online", session_id);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+gsm_systemd_find_session (char **session_id)
+{
+ char *local_session_id = NULL;
+ g_auto(GStrv) sessions = NULL;
+ int n_sessions;
+
+ g_return_val_if_fail (session_id != NULL, FALSE);
+
+ g_debug ("Finding a graphical session for user %d", getuid ());
+
+ n_sessions = sd_uid_get_sessions (getuid (),
+ SYSTEMD_SESSION_REQUIRE_ONLINE,
+ &sessions);
+
+ if (n_sessions < 0) {
+ g_critical ("Failed to get sessions for user %d", getuid ());
+ return FALSE;
+ }
+
+ for (int i = 0; i < n_sessions; ++i) {
+ g_debug ("Considering session '%s'", sessions[i]);
+
+ if (!_systemd_session_is_graphical (sessions[i]))
+ continue;
+
+ if (!_systemd_session_is_active (sessions[i]))
+ continue;
+
+ /*
+ * We get the sessions from newest to oldest, so take the last
+ * one we find that's good
+ */
+ local_session_id = sessions[i];
+ }
+
+ if (local_session_id == NULL)
+ return FALSE;
+
+ *session_id = g_strdup (local_session_id);
+
+ return TRUE;
+}
+
+static void
+gsm_systemd_init (GsmSystemd *manager)
+{
+ GError *error = NULL;
+ GDBusConnection *bus;
+ GVariant *res;
+
+ manager->priv = G_TYPE_INSTANCE_GET_PRIVATE (manager,
+ GSM_TYPE_SYSTEMD,
+ GsmSystemdPrivate);
+
+ manager->priv->inhibit_fd = -1;
+ manager->priv->delay_inhibit_fd = -1;
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (bus == NULL)
+ g_error ("Failed to connect to system bus: %s",
+ error->message);
+ manager->priv->sd_proxy =
+ g_dbus_proxy_new_sync (bus,
+ 0,
+ NULL,
+ SD_NAME,
+ SD_PATH,
+ SD_INTERFACE,
+ NULL,
+ &error);
+ if (manager->priv->sd_proxy == NULL) {
+ g_warning ("Failed to connect to systemd: %s",
+ error->message);
+ g_clear_error (&error);
+ }
+
+ g_signal_connect (manager->priv->sd_proxy, "g-signal",
+ G_CALLBACK (sd_proxy_signal_cb), manager);
+
+ gsm_systemd_find_session (&manager->priv->session_id);
+
+ if (manager->priv->session_id == NULL) {
+ g_warning ("Could not get session id for session. Check that logind is "
+ "properly installed and pam_systemd is getting used at login.");
+ return;
+ }
+
+ g_debug ("Found session ID: %s", manager->priv->session_id);
+
+ res = g_dbus_proxy_call_sync (manager->priv->sd_proxy,
+ "GetSession",
+ g_variant_new ("(s)", manager->priv->session_id),
+ 0,
+ G_MAXINT,
+ NULL,
+ &error);
+ if (res == NULL) {
+ g_warning ("Could not get session id for session. Check that logind is "
+ "properly installed and pam_systemd is getting used at login: %s",
+ error->message);
+ g_error_free (error);
+ return;
+ }
+
+ g_variant_get (res, "(o)", &manager->priv->session_path);
+ g_variant_unref (res);
+
+ manager->priv->sd_source = sd_source_new ();
+ g_source_set_callback (manager->priv->sd_source, on_sd_source_changed, manager, NULL);
+ g_source_attach (manager->priv->sd_source, NULL);
+
+ on_sd_source_changed (manager);
+
+ g_object_unref (bus);
+}
+
+static void
+emit_restart_complete (GsmSystemd *manager,
+ GError *error)
+{
+ GError *call_error;
+
+ call_error = NULL;
+
+ if (error != NULL) {
+ call_error = g_error_new_literal (GSM_SYSTEM_ERROR,
+ GSM_SYSTEM_ERROR_RESTARTING,
+ error->message);
+ }
+
+ g_signal_emit_by_name (G_OBJECT (manager),
+ "request_completed", call_error);
+
+ if (call_error != NULL) {
+ g_error_free (call_error);
+ }
+}
+
+static void
+emit_stop_complete (GsmSystemd *manager,
+ GError *error)
+{
+ GError *call_error;
+
+ call_error = NULL;
+
+ if (error != NULL) {
+ call_error = g_error_new_literal (GSM_SYSTEM_ERROR,
+ GSM_SYSTEM_ERROR_STOPPING,
+ error->message);
+ }
+
+ g_signal_emit_by_name (G_OBJECT (manager),
+ "request_completed", call_error);
+
+ if (call_error != NULL) {
+ g_error_free (call_error);
+ }
+}
+
+static void
+restart_done (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (source);
+ GsmSystemd *manager = user_data;
+ GError *error = NULL;
+ GVariant *res;
+
+ res = g_dbus_proxy_call_finish (proxy, result, &error);
+
+ if (!res) {
+ g_warning ("Unable to restart system: %s", error->message);
+ emit_restart_complete (manager, error);
+ g_error_free (error);
+ } else {
+ emit_restart_complete (manager, NULL);
+ g_variant_unref (res);
+ }
+}
+
+static void
+gsm_systemd_attempt_restart (GsmSystem *system)
+{
+ GsmSystemd *manager = GSM_SYSTEMD (system);
+
+ g_dbus_proxy_call (manager->priv->sd_proxy,
+ "Reboot",
+ g_variant_new ("(b)", TRUE),
+ 0,
+ G_MAXINT,
+ NULL,
+ restart_done,
+ manager);
+}
+
+static void
+stop_done (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (source);
+ GsmSystemd *manager = user_data;
+ GError *error = NULL;
+ GVariant *res;
+
+ res = g_dbus_proxy_call_finish (proxy, result, &error);
+
+ if (!res) {
+ g_warning ("Unable to stop system: %s", error->message);
+ emit_stop_complete (manager, error);
+ g_error_free (error);
+ } else {
+ emit_stop_complete (manager, NULL);
+ g_variant_unref (res);
+ }
+}
+
+static void
+gsm_systemd_attempt_stop (GsmSystem *system)
+{
+ GsmSystemd *manager = GSM_SYSTEMD (system);
+
+ g_dbus_proxy_call (manager->priv->sd_proxy,
+ "PowerOff",
+ g_variant_new ("(b)", TRUE),
+ 0,
+ G_MAXINT,
+ NULL,
+ stop_done,
+ manager);
+}
+
+static void
+gsm_systemd_set_session_idle (GsmSystem *system,
+ gboolean is_idle)
+{
+ GsmSystemd *manager = GSM_SYSTEMD (system);
+ GDBusConnection *bus;
+
+ if (manager->priv->session_path == NULL) {
+ g_warning ("Could not get session path for session. Check that logind is "
+ "properly installed and pam_systemd is getting used at login.");
+ return;
+ }
+
+ g_debug ("Updating systemd idle status: %d", is_idle);
+ bus = g_dbus_proxy_get_connection (manager->priv->sd_proxy);
+ g_dbus_connection_call (bus,
+ SD_NAME,
+ manager->priv->session_path,
+ SD_SESSION_INTERFACE,
+ "SetIdleHint",
+ g_variant_new ("(b)", is_idle),
+ G_VARIANT_TYPE_BOOLEAN,
+ 0,
+ G_MAXINT,
+ NULL, NULL, NULL);
+}
+
+static gboolean
+gsm_systemd_can_switch_user (GsmSystem *system)
+{
+ GsmSystemd *manager = GSM_SYSTEMD (system);
+ gchar *seat;
+ gint ret;
+
+ sd_session_get_seat (manager->priv->session_id, &seat);
+ ret = sd_seat_can_multi_session (seat);
+ free (seat);
+
+ return ret > 0;
+}
+
+static gboolean
+gsm_systemd_can_restart (GsmSystem *system)
+{
+ GsmSystemd *manager = GSM_SYSTEMD (system);
+ gchar *rv;
+ GVariant *res;
+ gboolean can_restart;
+
+ res = g_dbus_proxy_call_sync (manager->priv->sd_proxy,
+ "CanReboot",
+ NULL,
+ 0,
+ G_MAXINT,
+ NULL,
+ NULL);
+ if (!res) {
+ g_warning ("Calling CanReboot failed. Check that logind is "
+ "properly installed and pam_systemd is getting used at login.");
+ return FALSE;
+ }
+
+ g_variant_get (res, "(s)", &rv);
+ g_variant_unref (res);
+
+ can_restart = g_strcmp0 (rv, "yes") == 0 ||
+ g_strcmp0 (rv, "challenge") == 0;
+
+ g_free (rv);
+
+ return can_restart;
+}
+
+static gboolean
+gsm_systemd_can_restart_to_firmware_setup (GsmSystem *system)
+{
+ GsmSystemd *manager = GSM_SYSTEMD (system);
+ const gchar *rv;
+ GVariant *res;
+ gboolean can_restart;
+ GError *error = NULL;
+
+ res = g_dbus_proxy_call_sync (manager->priv->sd_proxy,
+ "CanRebootToFirmwareSetup",
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ G_MAXINT,
+ NULL,
+ &error);
+ if (!res) {
+ g_warning ("Calling CanRebootToFirmwareSetup failed. Check that logind is "
+ "properly installed and pam_systemd is getting used at login: %s",
+ error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ g_variant_get (res, "(&s)", &rv);
+
+ can_restart = g_strcmp0 (rv, "yes") == 0 ||
+ g_strcmp0 (rv, "challenge") == 0;
+
+ g_variant_unref (res);
+
+ return can_restart;
+}
+
+static void
+gsm_systemd_set_restart_to_firmware_setup (GsmSystem *system,
+ gboolean enable)
+{
+ GsmSystemd *manager = GSM_SYSTEMD (system);
+ GVariant *res;
+ GError *error = NULL;
+
+ res = g_dbus_proxy_call_sync (manager->priv->sd_proxy,
+ "SetRebootToFirmwareSetup",
+ g_variant_new ("(b)", enable),
+ G_DBUS_CALL_FLAGS_NONE,
+ G_MAXINT,
+ NULL,
+ &error);
+ if (!res) {
+ g_warning ("Calling SetRebootToFirmwareSetup failed. Check that logind is "
+ "properly installed and pam_systemd is getting used at login: %s",
+ error->message);
+ g_error_free (error);
+ }
+
+ g_variant_unref (res);
+}
+
+static gboolean
+gsm_systemd_can_stop (GsmSystem *system)
+{
+ GsmSystemd *manager = GSM_SYSTEMD (system);
+ gchar *rv;
+ GVariant *res;
+ gboolean can_stop;
+
+ res = g_dbus_proxy_call_sync (manager->priv->sd_proxy,
+ "CanPowerOff",
+ NULL,
+ 0,
+ G_MAXINT,
+ NULL,
+ NULL);
+ if (!res) {
+ g_warning ("Calling CanPowerOff failed. Check that logind is "
+ "properly installed and pam_systemd is getting used at login.");
+ return FALSE;
+ }
+
+ g_variant_get (res, "(s)", &rv);
+ g_variant_unref (res);
+
+ can_stop = g_strcmp0 (rv, "yes") == 0 ||
+ g_strcmp0 (rv, "challenge") == 0;
+
+ g_free (rv);
+
+ return can_stop;
+}
+
+static gboolean
+gsm_systemd_is_login_session (GsmSystem *system)
+{
+ GsmSystemd *manager = GSM_SYSTEMD (system);
+ int res;
+ gboolean ret;
+ gchar *session_class = NULL;
+
+ ret = FALSE;
+
+ if (manager->priv->session_id == NULL) {
+ return ret;
+ }
+
+ res = sd_session_get_class (manager->priv->session_id, &session_class);
+ if (res < 0) {
+ g_warning ("Could not get session class: %s", strerror (-res));
+ return FALSE;
+ }
+ ret = (g_strcmp0 (session_class, "greeter") == 0);
+ free (session_class);
+
+ return ret;
+}
+
+static gboolean
+gsm_systemd_can_suspend (GsmSystem *system)
+{
+ GsmSystemd *manager = GSM_SYSTEMD (system);
+ gchar *rv;
+ GVariant *res;
+ gboolean can_suspend;
+
+ res = g_dbus_proxy_call_sync (manager->priv->sd_proxy,
+ "CanSuspend",
+ NULL,
+ 0,
+ G_MAXINT,
+ NULL,
+ NULL);
+ if (!res) {
+ g_warning ("Calling CanSuspend failed. Check that logind is "
+ "properly installed and pam_systemd is getting used at login.");
+ return FALSE;
+ }
+
+ g_variant_get (res, "(s)", &rv);
+ g_variant_unref (res);
+
+ can_suspend = g_strcmp0 (rv, "yes") == 0 ||
+ g_strcmp0 (rv, "challenge") == 0;
+
+ g_free (rv);
+
+ return can_suspend;
+}
+
+static gboolean
+gsm_systemd_can_hibernate (GsmSystem *system)
+{
+ GsmSystemd *manager = GSM_SYSTEMD (system);
+ gchar *rv;
+ GVariant *res;
+ gboolean can_hibernate;
+
+ res = g_dbus_proxy_call_sync (manager->priv->sd_proxy,
+ "CanHibernate",
+ NULL,
+ 0,
+ G_MAXINT,
+ NULL,
+ NULL);
+ if (!res) {
+ g_warning ("Calling CanHibernate failed. Check that logind is "
+ "properly installed and pam_systemd is getting used at login.");
+ return FALSE;
+ }
+
+ g_variant_get (res, "(s)", &rv);
+ g_variant_unref (res);
+
+ can_hibernate = g_strcmp0 (rv, "yes") == 0 ||
+ g_strcmp0 (rv, "challenge") == 0;
+
+ g_free (rv);
+
+ return can_hibernate;
+}
+
+static void
+suspend_done (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (source);
+ GError *error = NULL;
+ GVariant *res;
+
+ res = g_dbus_proxy_call_finish (proxy, result, &error);
+
+ if (!res) {
+ g_warning ("Unable to suspend system: %s", error->message);
+ g_error_free (error);
+ } else {
+ g_variant_unref (res);
+ }
+}
+
+static void
+hibernate_done (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (source);
+ GError *error = NULL;
+ GVariant *res;
+
+ res = g_dbus_proxy_call_finish (proxy, result, &error);
+
+ if (!res) {
+ g_warning ("Unable to hibernate system: %s", error->message);
+ g_error_free (error);
+ } else {
+ g_variant_unref (res);
+ }
+}
+
+static void
+gsm_systemd_suspend (GsmSystem *system)
+{
+ GsmSystemd *manager = GSM_SYSTEMD (system);
+
+ g_dbus_proxy_call (manager->priv->sd_proxy,
+ "Suspend",
+ g_variant_new ("(b)", TRUE),
+ 0,
+ G_MAXINT,
+ NULL,
+ hibernate_done,
+ manager);
+}
+
+static void
+gsm_systemd_hibernate (GsmSystem *system)
+{
+ GsmSystemd *manager = GSM_SYSTEMD (system);
+
+ g_dbus_proxy_call (manager->priv->sd_proxy,
+ "Hibernate",
+ g_variant_new ("(b)", TRUE),
+ 0,
+ G_MAXINT,
+ NULL,
+ suspend_done,
+ manager);
+}
+
+static void
+inhibit_done (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (source);
+ GsmSystemd *manager = GSM_SYSTEMD (user_data);
+ GError *error = NULL;
+ GVariant *res;
+ GUnixFDList *fd_list = NULL;
+ gint idx;
+
+ res = g_dbus_proxy_call_with_unix_fd_list_finish (proxy, &fd_list, result, &error);
+
+ if (!res) {
+ g_warning ("Unable to inhibit system: %s", error->message);
+ g_error_free (error);
+ } else {
+ g_variant_get (res, "(h)", &idx);
+ manager->priv->inhibit_fd = g_unix_fd_list_get (fd_list, idx, &error);
+ if (manager->priv->inhibit_fd == -1) {
+ g_warning ("Failed to receive system inhibitor fd: %s", error->message);
+ g_error_free (error);
+ }
+ g_debug ("System inhibitor fd is %d", manager->priv->inhibit_fd);
+ g_object_unref (fd_list);
+ g_variant_unref (res);
+ }
+
+ if (manager->priv->inhibitors == NULL) {
+ drop_system_inhibitor (manager);
+ }
+}
+
+static void
+gsm_systemd_add_inhibitor (GsmSystem *system,
+ const gchar *id,
+ GsmInhibitorFlag flag)
+{
+ GsmSystemd *manager = GSM_SYSTEMD (system);
+
+ if ((flag & GSM_INHIBITOR_FLAG_SUSPEND) == 0)
+ return;
+
+ if (manager->priv->inhibitors == NULL) {
+ g_debug ("Adding system inhibitor");
+ g_dbus_proxy_call_with_unix_fd_list (manager->priv->sd_proxy,
+ "Inhibit",
+ g_variant_new ("(ssss)",
+ "sleep:shutdown",
+ g_get_user_name (),
+ "user session inhibited",
+ "block"),
+ 0,
+ G_MAXINT,
+ NULL,
+ NULL,
+ inhibit_done,
+ manager);
+ }
+ manager->priv->inhibitors = g_slist_prepend (manager->priv->inhibitors, g_strdup (id));
+}
+
+static void
+gsm_systemd_remove_inhibitor (GsmSystem *system,
+ const gchar *id)
+{
+ GsmSystemd *manager = GSM_SYSTEMD (system);
+ GSList *l;
+
+ l = g_slist_find_custom (manager->priv->inhibitors, id, (GCompareFunc)g_strcmp0);
+ if (l == NULL)
+ return;
+
+ g_free (l->data);
+ manager->priv->inhibitors = g_slist_delete_link (manager->priv->inhibitors, l);
+ if (manager->priv->inhibitors == NULL) {
+ drop_system_inhibitor (manager);
+ }
+}
+
+static void
+reboot_or_poweroff_done (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GsmSystemd *systemd = user_data;
+ GVariant *result;
+ GError *error = NULL;
+
+ result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source),
+ res,
+ &error);
+
+ if (result == NULL) {
+ if (!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED)) {
+ g_warning ("Shutdown failed: %s", error->message);
+ }
+ g_error_free (error);
+ drop_delay_inhibitor (systemd);
+ g_debug ("GsmSystemd: shutdown preparation failed");
+ systemd->priv->prepare_for_shutdown_expected = FALSE;
+ g_signal_emit_by_name (systemd, "shutdown-prepared", FALSE);
+ } else {
+ g_variant_unref (result);
+ }
+}
+
+static void
+gsm_systemd_prepare_shutdown (GsmSystem *system,
+ gboolean restart)
+{
+ GsmSystemd *systemd = GSM_SYSTEMD (system);
+ GUnixFDList *fd_list;
+ GVariant *res;
+ GError *error = NULL;
+ gint idx;
+
+ g_debug ("GsmSystemd: prepare shutdown");
+
+ res = g_dbus_proxy_call_with_unix_fd_list_sync (systemd->priv->sd_proxy,
+ "Inhibit",
+ g_variant_new ("(ssss)",
+ "shutdown",
+ g_get_user_name (),
+ "Preparing to end the session",
+ "delay"),
+ 0,
+ G_MAXINT,
+ NULL,
+ &fd_list,
+ NULL,
+ &error);
+ if (res == NULL) {
+ g_warning ("Failed to get delay inhibitor: %s", error->message);
+ g_error_free (error);
+ g_signal_emit_by_name (systemd, "shutdown-prepared", FALSE);
+ return;
+ }
+
+ g_variant_get (res, "(h)", &idx);
+
+ systemd->priv->delay_inhibit_fd = g_unix_fd_list_get (fd_list, idx, NULL);
+
+ g_debug ("GsmSystemd: got delay inhibitor, fd = %d", systemd->priv->delay_inhibit_fd);
+
+ g_variant_unref (res);
+ g_object_unref (fd_list);
+
+ systemd->priv->prepare_for_shutdown_expected = TRUE;
+
+ g_dbus_proxy_call (systemd->priv->sd_proxy,
+ restart ? "Reboot" : "PowerOff",
+ g_variant_new ("(b)", TRUE),
+ 0,
+ G_MAXINT,
+ NULL,
+ reboot_or_poweroff_done,
+ systemd);
+}
+
+static void
+gsm_systemd_complete_shutdown (GsmSystem *system)
+{
+ GsmSystemd *systemd = GSM_SYSTEMD (system);
+
+ /* remove delay inhibitor, if any */
+ drop_delay_inhibitor (systemd);
+}
+
+static gboolean
+gsm_systemd_is_last_session_for_user (GsmSystem *system)
+{
+ char **sessions = NULL;
+ char *session = NULL;
+ gboolean is_last_session;
+ int ret, i;
+
+ if (!gsm_systemd_find_session (&session)) {
+ return FALSE;
+ }
+
+ ret = sd_uid_get_sessions (getuid (), FALSE, &sessions);
+
+ if (ret <= 0) {
+ free (session);
+ return FALSE;
+ }
+
+ is_last_session = TRUE;
+ for (i = 0; sessions[i]; i++) {
+ char *state = NULL;
+ char *type = NULL;
+
+ if (g_strcmp0 (sessions[i], session) == 0)
+ continue;
+
+ ret = sd_session_get_state (sessions[i], &state);
+
+ if (ret != 0)
+ continue;
+
+ if (g_strcmp0 (state, "closing") == 0) {
+ free (state);
+ continue;
+ }
+ free (state);
+
+ ret = sd_session_get_type (sessions[i], &type);
+
+ if (ret != 0)
+ continue;
+
+ if (g_strcmp0 (type, "x11") != 0 &&
+ g_strcmp0 (type, "wayland") != 0) {
+ free (type);
+ continue;
+ }
+
+ is_last_session = FALSE;
+ }
+
+ for (i = 0; sessions[i]; i++)
+ free (sessions[i]);
+ free (sessions);
+ free (session);
+
+ return is_last_session;
+}
+
+static void
+gsm_systemd_system_init (GsmSystemInterface *iface)
+{
+ iface->can_switch_user = gsm_systemd_can_switch_user;
+ iface->can_stop = gsm_systemd_can_stop;
+ iface->can_restart = gsm_systemd_can_restart;
+ iface->can_restart_to_firmware_setup = gsm_systemd_can_restart_to_firmware_setup;
+ iface->set_restart_to_firmware_setup = gsm_systemd_set_restart_to_firmware_setup;
+ iface->can_suspend = gsm_systemd_can_suspend;
+ iface->can_hibernate = gsm_systemd_can_hibernate;
+ iface->attempt_stop = gsm_systemd_attempt_stop;
+ iface->attempt_restart = gsm_systemd_attempt_restart;
+ iface->suspend = gsm_systemd_suspend;
+ iface->hibernate = gsm_systemd_hibernate;
+ iface->set_session_idle = gsm_systemd_set_session_idle;
+ iface->is_login_session = gsm_systemd_is_login_session;
+ iface->add_inhibitor = gsm_systemd_add_inhibitor;
+ iface->remove_inhibitor = gsm_systemd_remove_inhibitor;
+ iface->prepare_shutdown = gsm_systemd_prepare_shutdown;
+ iface->complete_shutdown = gsm_systemd_complete_shutdown;
+ iface->is_last_session_for_user = gsm_systemd_is_last_session_for_user;
+}
+
+GsmSystemd *
+gsm_systemd_new (void)
+{
+ GsmSystemd *manager;
+
+ /* logind is not running ? */
+ if (access("/run/systemd/seats/", F_OK) < 0)
+ return NULL;
+
+ manager = g_object_new (GSM_TYPE_SYSTEMD, NULL);
+
+ return manager;
+}
+
+static void
+sd_proxy_signal_cb (GDBusProxy *proxy,
+ const gchar *sender_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ GsmSystemd *systemd = user_data;
+ gboolean is_about_to_shutdown;
+
+ g_debug ("GsmSystemd: received logind signal: %s", signal_name);
+
+ if (g_strcmp0 (signal_name, "PrepareForShutdown") != 0) {
+ g_debug ("GsmSystemd: ignoring %s signal", signal_name);
+ return;
+ }
+
+ g_variant_get (parameters, "(b)", &is_about_to_shutdown);
+ if (!is_about_to_shutdown) {
+ g_debug ("GsmSystemd: ignoring %s signal since about-to-shutdown is FALSE", signal_name);
+ return;
+ }
+
+ if (systemd->priv->prepare_for_shutdown_expected) {
+ g_debug ("GsmSystemd: shutdown successfully prepared");
+ g_signal_emit_by_name (systemd, "shutdown-prepared", TRUE);
+ systemd->priv->prepare_for_shutdown_expected = FALSE;
+ }
+}
+
+#else
+
+GsmSystemd *
+gsm_systemd_new (void)
+{
+ return NULL;
+}
+
+#endif
+