From 35504d91654321ff2b378229ff13150f53d5aad2 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:49:37 +0200 Subject: Adding upstream version 43.0. Signed-off-by: Daniel Baumann --- gnome-session/README | 69 + gnome-session/gdm-log.c | 205 + gnome-session/gdm-log.h | 50 + gnome-session/gnome-session.in | 32 + gnome-session/gsm-app.c | 593 +++ gnome-session/gsm-app.h | 119 + gnome-session/gsm-autostart-app.c | 1511 ++++++++ gnome-session/gsm-autostart-app.h | 61 + gnome-session/gsm-client.c | 583 +++ gnome-session/gsm-client.h | 140 + gnome-session/gsm-consolekit.c | 972 +++++ gnome-session/gsm-consolekit.h | 59 + gnome-session/gsm-dbus-client.c | 441 +++ gnome-session/gsm-dbus-client.h | 35 + gnome-session/gsm-fail-whale-dialog.c | 461 +++ gnome-session/gsm-fail-whale-dialog.h | 36 + gnome-session/gsm-fail-whale.c | 66 + gnome-session/gsm-fail-whale.h | 34 + gnome-session/gsm-icon-names.h | 28 + gnome-session/gsm-inhibitor-flag.h | 36 + gnome-session/gsm-inhibitor.c | 649 ++++ gnome-session/gsm-inhibitor.h | 88 + gnome-session/gsm-manager-logout-mode.h | 34 + gnome-session/gsm-manager.c | 3928 ++++++++++++++++++++ gnome-session/gsm-manager.h | 121 + gnome-session/gsm-presence-flag.h | 33 + gnome-session/gsm-presence.c | 542 +++ gnome-session/gsm-presence.h | 75 + gnome-session/gsm-process-helper.c | 113 + gnome-session/gsm-process-helper.h | 32 + gnome-session/gsm-session-fill.c | 334 ++ gnome-session/gsm-session-fill.h | 32 + gnome-session/gsm-session-save.c | 293 ++ gnome-session/gsm-session-save.h | 34 + gnome-session/gsm-shell-extensions.c | 199 + gnome-session/gsm-shell-extensions.h | 61 + gnome-session/gsm-shell.c | 507 +++ gnome-session/gsm-shell.h | 87 + gnome-session/gsm-store.c | 408 ++ gnome-session/gsm-store.h | 96 + gnome-session/gsm-system.c | 288 ++ gnome-session/gsm-system.h | 130 + gnome-session/gsm-systemd.c | 1183 ++++++ gnome-session/gsm-systemd.h | 59 + gnome-session/gsm-util.c | 863 +++++ gnome-session/gsm-util.h | 69 + gnome-session/gsm-xsmp-client.c | 1359 +++++++ gnome-session/gsm-xsmp-client.h | 89 + gnome-session/gsm-xsmp-server.c | 746 ++++ gnome-session/gsm-xsmp-server.h | 40 + gnome-session/main.c | 636 ++++ gnome-session/meson.build | 111 + gnome-session/org.gnome.SessionManager.App.xml | 43 + gnome-session/org.gnome.SessionManager.Client.xml | 73 + .../org.gnome.SessionManager.ClientPrivate.xml | 123 + .../org.gnome.SessionManager.Inhibitor.xml | 66 + .../org.gnome.SessionManager.Presence.xml | 95 + gnome-session/org.gnome.SessionManager.xml | 492 +++ gnome-session/test-client-dbus.c | 265 ++ gnome-session/test-inhibit.c | 197 + gnome-session/test-process-helper.c | 56 + 61 files changed, 20180 insertions(+) create mode 100644 gnome-session/README create mode 100644 gnome-session/gdm-log.c create mode 100644 gnome-session/gdm-log.h create mode 100755 gnome-session/gnome-session.in create mode 100644 gnome-session/gsm-app.c create mode 100644 gnome-session/gsm-app.h create mode 100644 gnome-session/gsm-autostart-app.c create mode 100644 gnome-session/gsm-autostart-app.h create mode 100644 gnome-session/gsm-client.c create mode 100644 gnome-session/gsm-client.h create mode 100644 gnome-session/gsm-consolekit.c create mode 100644 gnome-session/gsm-consolekit.h create mode 100644 gnome-session/gsm-dbus-client.c create mode 100644 gnome-session/gsm-dbus-client.h create mode 100644 gnome-session/gsm-fail-whale-dialog.c create mode 100644 gnome-session/gsm-fail-whale-dialog.h create mode 100644 gnome-session/gsm-fail-whale.c create mode 100644 gnome-session/gsm-fail-whale.h create mode 100644 gnome-session/gsm-icon-names.h create mode 100644 gnome-session/gsm-inhibitor-flag.h create mode 100644 gnome-session/gsm-inhibitor.c create mode 100644 gnome-session/gsm-inhibitor.h create mode 100644 gnome-session/gsm-manager-logout-mode.h create mode 100644 gnome-session/gsm-manager.c create mode 100644 gnome-session/gsm-manager.h create mode 100644 gnome-session/gsm-presence-flag.h create mode 100644 gnome-session/gsm-presence.c create mode 100644 gnome-session/gsm-presence.h create mode 100644 gnome-session/gsm-process-helper.c create mode 100644 gnome-session/gsm-process-helper.h create mode 100644 gnome-session/gsm-session-fill.c create mode 100644 gnome-session/gsm-session-fill.h create mode 100644 gnome-session/gsm-session-save.c create mode 100644 gnome-session/gsm-session-save.h create mode 100644 gnome-session/gsm-shell-extensions.c create mode 100644 gnome-session/gsm-shell-extensions.h create mode 100644 gnome-session/gsm-shell.c create mode 100644 gnome-session/gsm-shell.h create mode 100644 gnome-session/gsm-store.c create mode 100644 gnome-session/gsm-store.h create mode 100644 gnome-session/gsm-system.c create mode 100644 gnome-session/gsm-system.h create mode 100644 gnome-session/gsm-systemd.c create mode 100644 gnome-session/gsm-systemd.h create mode 100644 gnome-session/gsm-util.c create mode 100644 gnome-session/gsm-util.h create mode 100644 gnome-session/gsm-xsmp-client.c create mode 100644 gnome-session/gsm-xsmp-client.h create mode 100644 gnome-session/gsm-xsmp-server.c create mode 100644 gnome-session/gsm-xsmp-server.h create mode 100644 gnome-session/main.c create mode 100644 gnome-session/meson.build create mode 100644 gnome-session/org.gnome.SessionManager.App.xml create mode 100644 gnome-session/org.gnome.SessionManager.Client.xml create mode 100644 gnome-session/org.gnome.SessionManager.ClientPrivate.xml create mode 100644 gnome-session/org.gnome.SessionManager.Inhibitor.xml create mode 100644 gnome-session/org.gnome.SessionManager.Presence.xml create mode 100644 gnome-session/org.gnome.SessionManager.xml create mode 100644 gnome-session/test-client-dbus.c create mode 100644 gnome-session/test-inhibit.c create mode 100644 gnome-session/test-process-helper.c (limited to 'gnome-session') diff --git a/gnome-session/README b/gnome-session/README new file mode 100644 index 0000000..f0bd4cc --- /dev/null +++ b/gnome-session/README @@ -0,0 +1,69 @@ +See also http://live.gnome.org/SessionManagement/NewGnomeSession + +Startup +------- + +main() creates the GsmSession object representing the session (either +failsafe or normal). gsm_session_new() reads the appropriate autostart +and session files to create a list of GsmApps to be started. +(GsmAppAutostart represents an autostarted app, and GsmAppResumed +represents an app resumed from the previous saved session.) + +Startup is divided into 7 phases (GsmManagerPhase): + + * GSM_MANAGER_PHASE_STARTUP covers gnome-session's internal + startup, which also includes starting dbus-daemon (if + it's not already running). Gnome-session starts up those + explicitly because it needs them for its own purposes. + + * GSM_MANAGER_PHASE_EARLY_INITIALIZATION is the first phase of + "normal" startup (ie, startup controlled by .desktop files + rather than hardcoding). It covers the possible installation of + files in $HOME by gnome-initial-setup and must be done before + other components such as gnome-keyring use those files. + + * GSM_MANAGER_PHASE_INITIALIZATION covers low-level stuff like + gnome-settings-daemon helpers, that need to be + running very early (before any windows are displayed). + + Apps in this phase can make use of a D-Bus interface + (org.gnome.SessionManager.Setenv) to set environment variables + in gnome-session's environment. This can be used for things like + $GTK_MODULES, $GNOME_KEYRING_SOCKET, etc + + * GSM_MANAGER_PHASE_WINDOW_MANAGER includes window managers and + compositing managers, and anything else that has to be running + before any windows are mapped + + * GSM_MANAGER_PHASE_PANEL includes anything that permanently takes + up screen real estate (via EWMH struts). This is the first phase + where things actually appear on the screen. + + * GSM_MANAGER_PHASE_DESKTOP includes anything that draws directly + on the desktop (eg, nautilus). + + * GSM_MANAGER_PHASE_APPLICATION is everything else (normal apps, + tray icons, etc) + +For each startup phase, GsmSession launches the appropriate GsmApps. +When apps connect to the XSMP or D-Bus servers, GsmClients are created +and added to the session. The session tries to map these clients to +GsmApps. GsmApps signal when they register (via XSMP or SN) or exit, +and GsmSession uses this to decide when the phase is complete. + +FIXME: after starting the session, we need to run the DiscardCommands +of resumed apps. + + +Running/Shutdown +---------------- + +GSM_MANAGER_PHASE_RUNNING is pretty similar to the old gnome-session; +mostly it just tracks XSMP clients, and watches for +SmRestartImmediately clients exiting (NOTE: THIS DOESN'T HAPPEN YET). + +GsmClient is in theory not XSMP-specific, but it's very very +XSMP-like, and the shutdown procedure is also very XSMP-like. This is +just because there's no way to do XSMP shutdown correctly otherwise. +However, GsmClientDBus will still be able to present a more sane +protocol to its clients than GsmClient presents to it. diff --git a/gnome-session/gdm-log.c b/gnome-session/gdm-log.c new file mode 100644 index 0000000..c0dca41 --- /dev/null +++ b/gnome-session/gdm-log.c @@ -0,0 +1,205 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * Authors: William Jon McCann + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "gdm-log.h" + +static gboolean initialized = FALSE; +static int syslog_levels = (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING); + +static void +log_level_to_priority_and_prefix (GLogLevelFlags log_level, + int *priorityp, + const char **prefixp) +{ + int priority; + const char *prefix; + + /* Process the message prefix and priority */ + switch (log_level & G_LOG_LEVEL_MASK) { + case G_LOG_FLAG_FATAL: + priority = LOG_EMERG; + prefix = "FATAL"; + break; + case G_LOG_LEVEL_ERROR: + priority = LOG_ERR; + prefix = "ERROR"; + break; + case G_LOG_LEVEL_CRITICAL: + priority = LOG_CRIT; + prefix = "CRITICAL"; + break; + case G_LOG_LEVEL_WARNING: + priority = LOG_WARNING; + prefix = "WARNING"; + break; + case G_LOG_LEVEL_MESSAGE: + priority = LOG_NOTICE; + prefix = "MESSAGE"; + break; + case G_LOG_LEVEL_INFO: + priority = LOG_INFO; + prefix = "INFO"; + break; + case G_LOG_LEVEL_DEBUG: + /* if debug was requested then bump this up to ERROR + * to ensure it is seen in a log */ + if (syslog_levels & G_LOG_LEVEL_DEBUG) { + priority = LOG_WARNING; + prefix = "DEBUG(+)"; + } else { + priority = LOG_DEBUG; + prefix = "DEBUG"; + } + break; + default: + priority = LOG_DEBUG; + prefix = "UNKNOWN"; + break; + } + + if (priorityp != NULL) { + *priorityp = priority; + } + if (prefixp != NULL) { + *prefixp = prefix; + } +} + +void +gdm_log_default_handler (const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer unused_data) +{ + GString *gstring; + int priority; + const char *level_prefix; + char *string; + gboolean do_log; + gboolean is_fatal; + + is_fatal = (log_level & G_LOG_FLAG_FATAL) != 0; + + do_log = (log_level & syslog_levels); + if (! do_log) { + return; + } + + if (! initialized) { + gdm_log_init (); + } + + log_level_to_priority_and_prefix (log_level, + &priority, + &level_prefix); + + gstring = g_string_new (NULL); + + if (log_domain != NULL) { + g_string_append (gstring, log_domain); + g_string_append_c (gstring, '-'); + } + g_string_append (gstring, level_prefix); + + g_string_append (gstring, ": "); + if (message == NULL) { + g_string_append (gstring, "(NULL) message"); + } else { + g_string_append (gstring, message); + } + if (is_fatal) { + g_string_append (gstring, "\naborting...\n"); + } else { + g_string_append (gstring, "\n"); + } + + string = g_string_free (gstring, FALSE); + + syslog (priority, "%s", string); + + g_free (string); +} + +void +gdm_log_toggle_debug (void) +{ + if (syslog_levels & G_LOG_LEVEL_DEBUG) { + g_debug ("Debugging disabled"); + syslog_levels &= ~G_LOG_LEVEL_DEBUG; + } else { + syslog_levels |= G_LOG_LEVEL_DEBUG; + g_debug ("Debugging enabled"); + } +} + +void +gdm_log_set_debug (gboolean debug) +{ + if (debug) { + syslog_levels |= G_LOG_LEVEL_DEBUG; + g_debug ("Enabling debugging"); + } else { + g_debug ("Disabling debugging"); + syslog_levels &= ~G_LOG_LEVEL_DEBUG; + } +} + +void +gdm_log_init (void) +{ + const char *prg_name; + int options; + + g_log_set_default_handler (gdm_log_default_handler, NULL); + + prg_name = g_get_prgname (); + + options = LOG_PID; +#ifdef LOG_PERROR + options |= LOG_PERROR; +#endif + + openlog (prg_name, options, LOG_DAEMON); + + initialized = TRUE; +} + +void +gdm_log_shutdown (void) +{ + closelog (); + initialized = FALSE; +} + diff --git a/gnome-session/gdm-log.h b/gnome-session/gdm-log.h new file mode 100644 index 0000000..33daf40 --- /dev/null +++ b/gnome-session/gdm-log.h @@ -0,0 +1,50 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * Authors: William Jon McCann + * + */ + +#ifndef __GDM_LOG_H +#define __GDM_LOG_H + +#include +#include + +G_BEGIN_DECLS + +void gdm_log_default_handler (const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer unused_data); +void gdm_log_set_debug (gboolean debug); +void gdm_log_toggle_debug (void); +void gdm_log_init (void); +void gdm_log_shutdown (void); + +/* compatibility */ +#define gdm_fail g_critical +#define gdm_error g_warning +#define gdm_info g_message +#define gdm_debug g_debug + +#define gdm_assert g_assert +#define gdm_assert_not_reached g_assert_not_reached + +G_END_DECLS + +#endif /* __GDM_LOG_H */ diff --git a/gnome-session/gnome-session.in b/gnome-session/gnome-session.in new file mode 100755 index 0000000..ddd1a59 --- /dev/null +++ b/gnome-session/gnome-session.in @@ -0,0 +1,32 @@ +#!/bin/sh + +if [ "x$XDG_SESSION_TYPE" = "xwayland" ] && + [ "x$XDG_SESSION_CLASS" != "xgreeter" ] && + [ -n "$SHELL" ] && + grep -q "$SHELL" /etc/shells && + ! (echo "$SHELL" | grep -q "false") && + ! (echo "$SHELL" | grep -q "nologin"); then + if [ "$1" != '-l' ]; then + exec bash -c "exec -l '$SHELL' -c '$0 -l $*'" + else + shift + fi +fi + +SETTING=$(G_MESSAGES_DEBUG='' gsettings get org.gnome.system.locale region) +REGION=${SETTING#\'} +REGION=${REGION%\'} + +if [ -n "$REGION" ]; then + unset LC_TIME LC_NUMERIC LC_MONETARY LC_MEASUREMENT LC_PAPER + + if [ "$LANG" != "$REGION" ] ; then + export LC_TIME=$REGION + export LC_NUMERIC=$REGION + export LC_MONETARY=$REGION + export LC_MEASUREMENT=$REGION + export LC_PAPER=$REGION + fi +fi + +exec @libexecdir@/gnome-session-binary "$@" diff --git a/gnome-session/gsm-app.c b/gnome-session/gsm-app.c new file mode 100644 index 0000000..8da7bd1 --- /dev/null +++ b/gnome-session/gsm-app.c @@ -0,0 +1,593 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "gsm-app.h" +#include "org.gnome.SessionManager.App.h" + +/* If a component crashes twice within a minute, we count that as a fatal error */ +#define _GSM_APP_RESPAWN_RATELIMIT_SECONDS 60 + +typedef struct +{ + char *id; + char *app_id; + int phase; + char *startup_id; + gboolean registered; + GTimeVal last_restart_time; + GDBusConnection *connection; + GsmExportedApp *skeleton; +} GsmAppPrivate; + + +enum { + EXITED, + DIED, + LAST_SIGNAL +}; + +static guint32 app_serial = 1; + +static guint signals[LAST_SIGNAL] = { 0 }; + +enum { + PROP_0, + PROP_ID, + PROP_STARTUP_ID, + PROP_PHASE, + PROP_REGISTERED, + LAST_PROP +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GsmApp, gsm_app, G_TYPE_OBJECT) + +GQuark +gsm_app_error_quark (void) +{ + static GQuark ret = 0; + if (ret == 0) { + ret = g_quark_from_static_string ("gsm_app_error"); + } + + return ret; + +} + +static gboolean +gsm_app_get_app_id (GsmExportedApp *skeleton, + GDBusMethodInvocation *invocation, + GsmApp *app) +{ + const gchar *id; + + id = GSM_APP_GET_CLASS (app)->impl_get_app_id (app); + gsm_exported_app_complete_get_app_id (skeleton, invocation, id); + + return TRUE; +} + +static gboolean +gsm_app_get_startup_id (GsmExportedApp *skeleton, + GDBusMethodInvocation *invocation, + GsmApp *app) +{ + GsmAppPrivate *priv = gsm_app_get_instance_private (app); + const gchar *id; + + id = g_strdup (priv->startup_id); + gsm_exported_app_complete_get_startup_id (skeleton, invocation, id); + + return TRUE; +} + +static gboolean +gsm_app_get_phase (GsmExportedApp *skeleton, + GDBusMethodInvocation *invocation, + GsmApp *app) +{ + GsmAppPrivate *priv = gsm_app_get_instance_private (app); + + gsm_exported_app_complete_get_phase (skeleton, invocation, priv->phase); + return TRUE; +} + +static guint32 +get_next_app_serial (void) +{ + guint32 serial; + + serial = app_serial++; + + if ((gint32)app_serial < 0) { + app_serial = 1; + } + + return serial; +} + +static gboolean +register_app (GsmApp *app) +{ + GsmAppPrivate *priv = gsm_app_get_instance_private (app); + GError *error; + GsmExportedApp *skeleton; + + error = NULL; + priv->connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + if (error != NULL) { + g_critical ("error getting session bus: %s", error->message); + g_error_free (error); + return FALSE; + } + + skeleton = gsm_exported_app_skeleton_new (); + priv->skeleton = skeleton; + g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (skeleton), + priv->connection, priv->id, + &error); + + if (error != NULL) { + g_critical ("error registering app on session bus: %s", error->message); + g_error_free (error); + return FALSE; + } + + g_signal_connect (skeleton, "handle-get-app-id", + G_CALLBACK (gsm_app_get_app_id), app); + g_signal_connect (skeleton, "handle-get-phase", + G_CALLBACK (gsm_app_get_phase), app); + g_signal_connect (skeleton, "handle-get-startup-id", + G_CALLBACK (gsm_app_get_startup_id), app); + + return TRUE; +} + +static GObject * +gsm_app_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GsmApp *app; + GsmAppPrivate *priv; + gboolean res; + + app = GSM_APP (G_OBJECT_CLASS (gsm_app_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + priv = gsm_app_get_instance_private (app); + + g_free (priv->id); + priv->id = g_strdup_printf ("/org/gnome/SessionManager/App%u", get_next_app_serial ()); + + res = register_app (app); + if (! res) { + g_warning ("Unable to register app with session bus"); + } + + return G_OBJECT (app); +} + +static void +gsm_app_init (GsmApp *app) +{ +} + +static void +gsm_app_set_phase (GsmApp *app, + int phase) +{ + GsmAppPrivate *priv = gsm_app_get_instance_private (app); + + g_return_if_fail (GSM_IS_APP (app)); + + priv->phase = phase; +} + +static void +gsm_app_set_id (GsmApp *app, + const char *id) +{ + GsmAppPrivate *priv = gsm_app_get_instance_private (app); + + g_return_if_fail (GSM_IS_APP (app)); + + g_free (priv->id); + + priv->id = g_strdup (id); + g_object_notify (G_OBJECT (app), "id"); + +} +static void +gsm_app_set_startup_id (GsmApp *app, + const char *startup_id) +{ + GsmAppPrivate *priv = gsm_app_get_instance_private (app); + + g_return_if_fail (GSM_IS_APP (app)); + + g_free (priv->startup_id); + + priv->startup_id = g_strdup (startup_id); + g_object_notify (G_OBJECT (app), "startup-id"); + +} + +static void +gsm_app_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GsmApp *app = GSM_APP (object); + + switch (prop_id) { + case PROP_STARTUP_ID: + gsm_app_set_startup_id (app, g_value_get_string (value)); + break; + case PROP_ID: + gsm_app_set_id (app, g_value_get_string (value)); + break; + case PROP_PHASE: + gsm_app_set_phase (app, g_value_get_int (value)); + break; + case PROP_REGISTERED: + gsm_app_set_registered (app, g_value_get_boolean (value)); + break; + default: + break; + } +} + +static void +gsm_app_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GsmApp *app = GSM_APP (object); + GsmAppPrivate *priv = gsm_app_get_instance_private (app); + + switch (prop_id) { + case PROP_STARTUP_ID: + g_value_set_string (value, priv->startup_id); + break; + case PROP_ID: + g_value_set_string (value, priv->id); + break; + case PROP_PHASE: + g_value_set_int (value, priv->phase); + break; + case PROP_REGISTERED: + g_value_set_boolean (value, priv->registered); + break; + default: + break; + } +} + +static void +gsm_app_dispose (GObject *object) +{ + GsmApp *app = GSM_APP (object); + GsmAppPrivate *priv = gsm_app_get_instance_private (app); + + g_free (priv->startup_id); + priv->startup_id = NULL; + + g_free (priv->id); + priv->id = NULL; + + if (priv->skeleton != NULL) { + g_dbus_interface_skeleton_unexport_from_connection (G_DBUS_INTERFACE_SKELETON (priv->skeleton), + priv->connection); + g_clear_object (&priv->skeleton); + } + + g_clear_object (&priv->connection); + + G_OBJECT_CLASS (gsm_app_parent_class)->dispose (object); +} + +static void +gsm_app_class_init (GsmAppClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = gsm_app_set_property; + object_class->get_property = gsm_app_get_property; + object_class->dispose = gsm_app_dispose; + object_class->constructor = gsm_app_constructor; + + klass->impl_start = NULL; + klass->impl_get_app_id = NULL; + klass->impl_get_autorestart = NULL; + klass->impl_provides = NULL; + klass->impl_get_provides = NULL; + klass->impl_is_running = NULL; + + g_object_class_install_property (object_class, + PROP_PHASE, + g_param_spec_int ("phase", + "Phase", + "Phase", + -1, + G_MAXINT, + -1, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_ID, + g_param_spec_string ("id", + "ID", + "ID", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_STARTUP_ID, + g_param_spec_string ("startup-id", + "startup ID", + "Session management startup ID", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, + PROP_REGISTERED, + g_param_spec_boolean ("registered", + "Registered", + "Registered", + FALSE, + G_PARAM_READWRITE)); + + signals[EXITED] = + g_signal_new ("exited", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsmAppClass, exited), + NULL, NULL, NULL, + G_TYPE_NONE, + 1, G_TYPE_UCHAR); + signals[DIED] = + g_signal_new ("died", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsmAppClass, died), + NULL, NULL, NULL, + G_TYPE_NONE, + 1, G_TYPE_INT); +} + +const char * +gsm_app_peek_id (GsmApp *app) +{ + GsmAppPrivate *priv = gsm_app_get_instance_private (app); + + return priv->id; +} + +const char * +gsm_app_peek_app_id (GsmApp *app) +{ + return GSM_APP_GET_CLASS (app)->impl_get_app_id (app); +} + +const char * +gsm_app_peek_startup_id (GsmApp *app) +{ + GsmAppPrivate *priv = gsm_app_get_instance_private (app); + + return priv->startup_id; +} + +/** + * gsm_app_peek_phase: + * @app: a %GsmApp + * + * Returns @app's startup phase. + * + * Return value: @app's startup phase + **/ +GsmManagerPhase +gsm_app_peek_phase (GsmApp *app) +{ + GsmAppPrivate *priv = gsm_app_get_instance_private (app); + + g_return_val_if_fail (GSM_IS_APP (app), GSM_MANAGER_PHASE_APPLICATION); + + return priv->phase; +} + +gboolean +gsm_app_peek_is_disabled (GsmApp *app) +{ + g_return_val_if_fail (GSM_IS_APP (app), FALSE); + + if (GSM_APP_GET_CLASS (app)->impl_is_disabled) { + return GSM_APP_GET_CLASS (app)->impl_is_disabled (app); + } else { + return FALSE; + } +} + +gboolean +gsm_app_peek_is_conditionally_disabled (GsmApp *app) +{ + g_return_val_if_fail (GSM_IS_APP (app), FALSE); + + if (GSM_APP_GET_CLASS (app)->impl_is_conditionally_disabled) { + return GSM_APP_GET_CLASS (app)->impl_is_conditionally_disabled (app); + } else { + return FALSE; + } +} + +gboolean +gsm_app_is_running (GsmApp *app) +{ + g_return_val_if_fail (GSM_IS_APP (app), FALSE); + + if (GSM_APP_GET_CLASS (app)->impl_is_running) { + return GSM_APP_GET_CLASS (app)->impl_is_running (app); + } else { + return FALSE; + } +} + +gboolean +gsm_app_peek_autorestart (GsmApp *app) +{ + g_return_val_if_fail (GSM_IS_APP (app), FALSE); + + if (GSM_APP_GET_CLASS (app)->impl_get_autorestart) { + return GSM_APP_GET_CLASS (app)->impl_get_autorestart (app); + } else { + return FALSE; + } +} + +gboolean +gsm_app_provides (GsmApp *app, const char *service) +{ + if (GSM_APP_GET_CLASS (app)->impl_provides) { + return GSM_APP_GET_CLASS (app)->impl_provides (app, service); + } else { + return FALSE; + } +} + +char ** +gsm_app_get_provides (GsmApp *app) +{ + if (GSM_APP_GET_CLASS (app)->impl_get_provides) { + return GSM_APP_GET_CLASS (app)->impl_get_provides (app); + } else { + return NULL; + } +} + +gboolean +gsm_app_has_autostart_condition (GsmApp *app, + const char *condition) +{ + + if (GSM_APP_GET_CLASS (app)->impl_has_autostart_condition) { + return GSM_APP_GET_CLASS (app)->impl_has_autostart_condition (app, condition); + } else { + return FALSE; + } +} + +gboolean +gsm_app_start (GsmApp *app, + GError **error) +{ + GsmAppPrivate *priv = gsm_app_get_instance_private (app); + + g_debug ("Starting app: %s", priv->id); + return GSM_APP_GET_CLASS (app)->impl_start (app, error); +} + +gboolean +gsm_app_restart (GsmApp *app, + GError **error) +{ + GsmAppPrivate *priv = gsm_app_get_instance_private (app); + GTimeVal current_time; + g_debug ("Re-starting app: %s", priv->id); + + g_get_current_time (¤t_time); + if (priv->last_restart_time.tv_sec > 0 + && (current_time.tv_sec - priv->last_restart_time.tv_sec) < _GSM_APP_RESPAWN_RATELIMIT_SECONDS) { + g_warning ("App '%s' respawning too quickly", gsm_app_peek_app_id (app)); + g_set_error (error, + GSM_APP_ERROR, + GSM_APP_ERROR_RESTART_LIMIT, + "Component '%s' crashing too quickly", + gsm_app_peek_app_id (app)); + return FALSE; + } + priv->last_restart_time = current_time; + + return GSM_APP_GET_CLASS (app)->impl_restart (app, error); +} + +gboolean +gsm_app_stop (GsmApp *app, + GError **error) +{ + return GSM_APP_GET_CLASS (app)->impl_stop (app, error); +} + +void +gsm_app_exited (GsmApp *app, + guchar exit_code) +{ + g_return_if_fail (GSM_IS_APP (app)); + + g_signal_emit (app, signals[EXITED], 0, exit_code); +} + +void +gsm_app_died (GsmApp *app, + int signal) +{ + g_return_if_fail (GSM_IS_APP (app)); + + g_signal_emit (app, signals[DIED], 0, signal); +} + +gboolean +gsm_app_get_registered (GsmApp *app) +{ + GsmAppPrivate *priv = gsm_app_get_instance_private (app); + + g_return_val_if_fail (GSM_IS_APP (app), FALSE); + + return priv->registered; +} + +void +gsm_app_set_registered (GsmApp *app, + gboolean registered) +{ + GsmAppPrivate *priv = gsm_app_get_instance_private (app); + + g_return_if_fail (GSM_IS_APP (app)); + + if (priv->registered != registered) { + priv->registered = registered; + g_object_notify (G_OBJECT (app), "registered"); + } +} + +gboolean +gsm_app_save_to_keyfile (GsmApp *app, + GKeyFile *keyfile, + GError **error) +{ + GsmAppPrivate *priv = gsm_app_get_instance_private (app); + + g_debug ("Saving app: %s", priv->id); + return GSM_APP_GET_CLASS (app)->impl_save_to_keyfile (app, keyfile, error); +} diff --git a/gnome-session/gsm-app.h b/gnome-session/gsm-app.h new file mode 100644 index 0000000..2808a21 --- /dev/null +++ b/gnome-session/gsm-app.h @@ -0,0 +1,119 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef __GSM_APP_H__ +#define __GSM_APP_H__ + +#include +#include + + +#include "gsm-manager.h" +#include "gsm-client.h" + +G_BEGIN_DECLS + +#define GSM_TYPE_APP (gsm_app_get_type ()) +G_DECLARE_DERIVABLE_TYPE (GsmApp, gsm_app, GSM, APP, GObject) + +struct _GsmAppClass +{ + GObjectClass parent_class; + + /* signals */ + void (*exited) (GsmApp *app, + guchar exit_code); + void (*died) (GsmApp *app, + int signal); + + /* virtual methods */ + gboolean (*impl_start) (GsmApp *app, + GError **error); + gboolean (*impl_restart) (GsmApp *app, + GError **error); + gboolean (*impl_stop) (GsmApp *app, + GError **error); + gboolean (*impl_provides) (GsmApp *app, + const char *service); + char ** (*impl_get_provides) (GsmApp *app); + gboolean (*impl_has_autostart_condition) (GsmApp *app, + const char *service); + gboolean (*impl_is_running) (GsmApp *app); + + gboolean (*impl_get_autorestart) (GsmApp *app); + const char *(*impl_get_app_id) (GsmApp *app); + gboolean (*impl_is_disabled) (GsmApp *app); + gboolean (*impl_is_conditionally_disabled) (GsmApp *app); + + gboolean (*impl_save_to_keyfile) (GsmApp *app, + GKeyFile *keyfile, + GError **error); +}; + +typedef enum +{ + GSM_APP_ERROR_GENERAL = 0, + GSM_APP_ERROR_RESTART_LIMIT, + GSM_APP_ERROR_START, + GSM_APP_ERROR_STOP, + GSM_APP_NUM_ERRORS +} GsmAppError; + +#define GSM_APP_ERROR gsm_app_error_quark () + +GQuark gsm_app_error_quark (void); + +gboolean gsm_app_peek_autorestart (GsmApp *app); + +const char *gsm_app_peek_id (GsmApp *app); +const char *gsm_app_peek_app_id (GsmApp *app); +const char *gsm_app_peek_startup_id (GsmApp *app); +GsmManagerPhase gsm_app_peek_phase (GsmApp *app); +gboolean gsm_app_peek_is_disabled (GsmApp *app); +gboolean gsm_app_peek_is_conditionally_disabled (GsmApp *app); + +gboolean gsm_app_start (GsmApp *app, + GError **error); +gboolean gsm_app_restart (GsmApp *app, + GError **error); +gboolean gsm_app_stop (GsmApp *app, + GError **error); +gboolean gsm_app_is_running (GsmApp *app); + +void gsm_app_exited (GsmApp *app, + guchar exit_code); +void gsm_app_died (GsmApp *app, + int signal); + +gboolean gsm_app_provides (GsmApp *app, + const char *service); +char **gsm_app_get_provides (GsmApp *app); +gboolean gsm_app_has_autostart_condition (GsmApp *app, + const char *condition); +gboolean gsm_app_get_registered (GsmApp *app); +void gsm_app_set_registered (GsmApp *app, + gboolean registered); + +gboolean gsm_app_save_to_keyfile (GsmApp *app, + GKeyFile *keyfile, + GError **error); + +G_END_DECLS + +#endif /* __GSM_APP_H__ */ diff --git a/gnome-session/gsm-autostart-app.c b/gnome-session/gsm-autostart-app.c new file mode 100644 index 0000000..8204d4f --- /dev/null +++ b/gnome-session/gsm-autostart-app.c @@ -0,0 +1,1511 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#define GNOME_DESKTOP_USE_UNSTABLE_API +#include + +#ifdef HAVE_SYSTEMD +#ifdef ENABLE_SYSTEMD_JOURNAL +#include +#endif +#include +#endif + +#include "gsm-autostart-app.h" +#include "gsm-util.h" + +enum { + AUTOSTART_LAUNCH_SPAWN = 0, + AUTOSTART_LAUNCH_ACTIVATE +}; + +enum { + GSM_CONDITION_NONE = 0, + GSM_CONDITION_IF_EXISTS = 1, + GSM_CONDITION_UNLESS_EXISTS = 2, + GSM_CONDITION_GSETTINGS = 3, + GSM_CONDITION_IF_SESSION = 4, + GSM_CONDITION_UNLESS_SESSION = 5, + GSM_CONDITION_UNKNOWN = 6 +}; + +#define GSM_SESSION_CLIENT_DBUS_INTERFACE "org.gnome.SessionClient" + +typedef struct +{ + gboolean mask_systemd; + char *desktop_filename; + char *desktop_id; + char *startup_id; + + GDesktopAppInfo *app_info; + /* provides defined in session definition */ + GSList *session_provides; + + /* desktop file state */ + char *condition_string; + gboolean condition; + gboolean autorestart; + + GFileMonitor *condition_monitor; + guint condition_notify_id; + GSettings *condition_settings; + + int launch_type; + GPid pid; + guint child_watch_id; +} GsmAutostartAppPrivate; + +enum { + CONDITION_CHANGED, + LAST_SIGNAL +}; + +typedef enum { + PROP_DESKTOP_FILENAME = 1, + PROP_MASK_SYSTEMD, +} GsmAutostartAppProperty; + +static GParamSpec *props[PROP_MASK_SYSTEMD + 1] = { NULL, }; + +static guint signals[LAST_SIGNAL] = { 0 }; + +static void gsm_autostart_app_initable_iface_init (GInitableIface *iface); + +G_DEFINE_TYPE_WITH_CODE (GsmAutostartApp, gsm_autostart_app, GSM_TYPE_APP, + G_ADD_PRIVATE (GsmAutostartApp) + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, gsm_autostart_app_initable_iface_init)) + +static void +gsm_autostart_app_init (GsmAutostartApp *app) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app); + + priv->pid = -1; + priv->condition_monitor = NULL; + priv->condition = FALSE; +} + +static gboolean +is_disabled (GsmApp *app) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (app)); + + /* GSM_AUTOSTART_APP_ENABLED_KEY key, used by old gnome-session */ + if (g_desktop_app_info_has_key (priv->app_info, + GSM_AUTOSTART_APP_ENABLED_KEY) && + !g_desktop_app_info_get_boolean (priv->app_info, + GSM_AUTOSTART_APP_ENABLED_KEY)) { + g_debug ("app %s is disabled by " GSM_AUTOSTART_APP_ENABLED_KEY, + gsm_app_peek_id (app)); + return TRUE; + } + + /* Hidden key, used by autostart spec */ + if (g_desktop_app_info_get_is_hidden (priv->app_info)) { + g_debug ("app %s is disabled by Hidden", + gsm_app_peek_id (app)); + return TRUE; + } + + /* Check OnlyShowIn/NotShowIn/TryExec */ + if (!g_desktop_app_info_get_show_in (priv->app_info, NULL)) { + g_debug ("app %s is not for the current desktop", + gsm_app_peek_id (app)); + return TRUE; + } + + /* Check if app is systemd enabled and mask-systemd is set. */ + if (priv->mask_systemd && + g_desktop_app_info_has_key (priv->app_info, + GSM_AUTOSTART_APP_SYSTEMD_KEY) && + g_desktop_app_info_get_boolean (priv->app_info, + GSM_AUTOSTART_APP_SYSTEMD_KEY)) { + g_debug ("app %s is disabled by " GSM_AUTOSTART_APP_SYSTEMD_KEY, + gsm_app_peek_id (app)); + return TRUE; + } + + /* Do not check AutostartCondition - this method is only to determine + if the app is unconditionally disabled */ + + return FALSE; +} + +static gboolean +parse_condition_string (const char *condition_string, + guint *condition_kindp, + char **keyp) +{ + const char *space; + const char *key; + int len; + guint kind; + + space = condition_string + strcspn (condition_string, " "); + len = space - condition_string; + key = space; + while (isspace ((unsigned char)*key)) { + key++; + } + + kind = GSM_CONDITION_UNKNOWN; + + if (!g_ascii_strncasecmp (condition_string, "if-exists", len) && key) { + kind = GSM_CONDITION_IF_EXISTS; + } else if (!g_ascii_strncasecmp (condition_string, "unless-exists", len) && key) { + kind = GSM_CONDITION_UNLESS_EXISTS; + } else if (!g_ascii_strncasecmp (condition_string, "GSettings", len)) { + kind = GSM_CONDITION_GSETTINGS; + } else if (!g_ascii_strncasecmp (condition_string, "GNOME3", len)) { + condition_string = key; + space = condition_string + strcspn (condition_string, " "); + len = space - condition_string; + key = space; + while (isspace ((unsigned char)*key)) { + key++; + } + if (!g_ascii_strncasecmp (condition_string, "if-session", len) && key) { + kind = GSM_CONDITION_IF_SESSION; + } else if (!g_ascii_strncasecmp (condition_string, "unless-session", len) && key) { + kind = GSM_CONDITION_UNLESS_SESSION; + } + } + + if (kind == GSM_CONDITION_UNKNOWN) { + key = NULL; + } + + if (keyp != NULL) { + *keyp = g_strdup (key); + } + + if (condition_kindp != NULL) { + *condition_kindp = kind; + } + + return (kind != GSM_CONDITION_UNKNOWN); +} + +static void +if_exists_condition_cb (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event, + GsmApp *app) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (app)); + gboolean condition = FALSE; + + switch (event) { + case G_FILE_MONITOR_EVENT_CREATED: + condition = TRUE; + break; + case G_FILE_MONITOR_EVENT_DELETED: + condition = FALSE; + break; + default: + /* Ignore any other monitor event */ + return; + } + + /* Emit only if the condition actually changed */ + if (condition != priv->condition) { + priv->condition = condition; + g_signal_emit (app, signals[CONDITION_CHANGED], 0, condition); + } +} + +static void +unless_exists_condition_cb (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event, + GsmApp *app) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (app)); + gboolean condition = FALSE; + + switch (event) { + case G_FILE_MONITOR_EVENT_CREATED: + condition = FALSE; + break; + case G_FILE_MONITOR_EVENT_DELETED: + condition = TRUE; + break; + default: + /* Ignore any other monitor event */ + return; + } + + /* Emit only if the condition actually changed */ + if (condition != priv->condition) { + priv->condition = condition; + g_signal_emit (app, signals[CONDITION_CHANGED], 0, condition); + } +} + +static void +gsettings_condition_cb (GSettings *settings, + const char *key, + gpointer user_data) +{ + GsmApp *app = GSM_APP (user_data); + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (app)); + gboolean condition; + + g_return_if_fail (GSM_IS_APP (user_data)); + + condition = g_settings_get_boolean (settings, key); + + g_debug ("GsmAutostartApp: app:%s condition changed condition:%d", + gsm_app_peek_id (app), + condition); + + /* Emit only if the condition actually changed */ + if (condition != priv->condition) { + priv->condition = condition; + g_signal_emit (app, signals[CONDITION_CHANGED], 0, condition); + } +} + +static gboolean +setup_gsettings_condition_monitor (GsmAutostartApp *app, + const char *key) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app); + GSettingsSchemaSource *source; + GSettingsSchema *schema; + GSettings *settings; + GSettingsSchemaKey *schema_key; + const GVariantType *key_type; + char **elems; + gboolean retval = FALSE; + char *signal; + + retval = FALSE; + + schema = NULL; + + elems = g_strsplit (key, " ", 2); + + if (elems == NULL) + goto out; + + if (elems[0] == NULL || elems[1] == NULL) + goto out; + + source = g_settings_schema_source_get_default (); + + schema = g_settings_schema_source_lookup (source, elems[0], TRUE); + + if (schema == NULL) + goto out; + + if (!g_settings_schema_has_key (schema, elems[1])) + goto out; + + schema_key = g_settings_schema_get_key (schema, elems[1]); + + g_assert (schema_key != NULL); + + key_type = g_settings_schema_key_get_value_type (schema_key); + + g_settings_schema_key_unref (schema_key); + + g_assert (key_type != NULL); + + if (!g_variant_type_equal (key_type, G_VARIANT_TYPE_BOOLEAN)) + goto out; + + settings = g_settings_new_full (schema, NULL, NULL); + retval = g_settings_get_boolean (settings, elems[1]); + + signal = g_strdup_printf ("changed::%s", elems[1]); + g_signal_connect (G_OBJECT (settings), signal, + G_CALLBACK (gsettings_condition_cb), app); + g_free (signal); + + priv->condition_settings = settings; + +out: + if (schema) + g_settings_schema_unref (schema); + g_strfreev (elems); + + return retval; +} + +static void +if_session_condition_cb (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + GsmApp *app = GSM_APP (user_data); + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (app)); + char *session_name; + char *key; + gboolean condition; + + g_return_if_fail (GSM_IS_APP (user_data)); + + parse_condition_string (priv->condition_string, NULL, &key); + + g_object_get (object, "session-name", &session_name, NULL); + condition = strcmp (session_name, key) == 0; + g_free (session_name); + + g_free (key); + + g_debug ("GsmAutostartApp: app:%s condition changed condition:%d", + gsm_app_peek_id (app), + condition); + + /* Emit only if the condition actually changed */ + if (condition != priv->condition) { + priv->condition = condition; + g_signal_emit (app, signals[CONDITION_CHANGED], 0, condition); + } +} + +static void +unless_session_condition_cb (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + GsmApp *app = GSM_APP (user_data); + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (app)); + char *session_name; + char *key; + gboolean condition; + + g_return_if_fail (GSM_IS_APP (user_data)); + + parse_condition_string (priv->condition_string, NULL, &key); + + g_object_get (object, "session-name", &session_name, NULL); + condition = strcmp (session_name, key) != 0; + g_free (session_name); + + g_free (key); + + g_debug ("GsmAutostartApp: app:%s condition changed condition:%d", + gsm_app_peek_id (app), + condition); + + /* Emit only if the condition actually changed */ + if (condition != priv->condition) { + priv->condition = condition; + g_signal_emit (app, signals[CONDITION_CHANGED], 0, condition); + } +} + +static char * +resolve_conditional_file_path (const char *file) +{ + if (g_path_is_absolute (file)) + return g_strdup (file); + return g_build_filename (g_get_user_config_dir (), file, NULL); +} + +static void +setup_condition_monitor (GsmAutostartApp *app) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app); + guint kind; + char *key; + gboolean res; + gboolean disabled; + + if (priv->condition_monitor != NULL) { + g_file_monitor_cancel (priv->condition_monitor); + } + + if (priv->condition_string == NULL) { + return; + } + + /* if it is disabled outright there is no point in monitoring */ + if (is_disabled (GSM_APP (app))) { + return; + } + + key = NULL; + res = parse_condition_string (priv->condition_string, &kind, &key); + if (! res) { + g_free (key); + return; + } + + if (key == NULL) { + return; + } + + if (kind == GSM_CONDITION_IF_EXISTS) { + char *file_path; + GFile *file; + + file_path = resolve_conditional_file_path (key); + disabled = !g_file_test (file_path, G_FILE_TEST_EXISTS); + + file = g_file_new_for_path (file_path); + priv->condition_monitor = g_file_monitor_file (file, 0, NULL, NULL); + + g_signal_connect (priv->condition_monitor, "changed", + G_CALLBACK (if_exists_condition_cb), + app); + + g_object_unref (file); + g_free (file_path); + } else if (kind == GSM_CONDITION_UNLESS_EXISTS) { + char *file_path; + GFile *file; + + file_path = resolve_conditional_file_path (key); + disabled = g_file_test (file_path, G_FILE_TEST_EXISTS); + + file = g_file_new_for_path (file_path); + priv->condition_monitor = g_file_monitor_file (file, 0, NULL, NULL); + + g_signal_connect (priv->condition_monitor, "changed", + G_CALLBACK (unless_exists_condition_cb), + app); + + g_object_unref (file); + g_free (file_path); + } else if (kind == GSM_CONDITION_GSETTINGS) { + disabled = !setup_gsettings_condition_monitor (app, key); + } else if (kind == GSM_CONDITION_IF_SESSION) { + GsmManager *manager; + char *session_name; + + /* get the singleton */ + manager = gsm_manager_get (); + + g_object_get (manager, "session-name", &session_name, NULL); + disabled = strcmp (session_name, key) != 0; + + g_signal_connect (manager, "notify::session-name", + G_CALLBACK (if_session_condition_cb), app); + g_free (session_name); + } else if (kind == GSM_CONDITION_UNLESS_SESSION) { + GsmManager *manager; + char *session_name; + + /* get the singleton */ + manager = gsm_manager_get (); + + g_object_get (manager, "session-name", &session_name, NULL); + disabled = strcmp (session_name, key) == 0; + + g_signal_connect (manager, "notify::session-name", + G_CALLBACK (unless_session_condition_cb), app); + g_free (session_name); + } else { + disabled = TRUE; + } + + g_free (key); + + if (disabled) { + /* FIXME: cache the disabled value? */ + } +} + +static void +load_desktop_file (GsmAutostartApp *app) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app); + char *dbus_name; + char *startup_id; + char *phase_str; + int phase; + gboolean res; + + g_assert (priv->app_info != NULL); + + phase_str = g_desktop_app_info_get_string (priv->app_info, + GSM_AUTOSTART_APP_PHASE_KEY); + if (phase_str != NULL) { + if (strcmp (phase_str, "EarlyInitialization") == 0) { + phase = GSM_MANAGER_PHASE_EARLY_INITIALIZATION; + } else if (strcmp (phase_str, "PreDisplayServer") == 0) { + phase = GSM_MANAGER_PHASE_PRE_DISPLAY_SERVER; + } else if (strcmp (phase_str, "DisplayServer") == 0) { + phase = GSM_MANAGER_PHASE_DISPLAY_SERVER; + } else if (strcmp (phase_str, "Initialization") == 0) { + phase = GSM_MANAGER_PHASE_INITIALIZATION; + } else if (strcmp (phase_str, "WindowManager") == 0) { + phase = GSM_MANAGER_PHASE_WINDOW_MANAGER; + } else if (strcmp (phase_str, "Panel") == 0) { + phase = GSM_MANAGER_PHASE_PANEL; + } else if (strcmp (phase_str, "Desktop") == 0) { + phase = GSM_MANAGER_PHASE_DESKTOP; + } else { + phase = GSM_MANAGER_PHASE_APPLICATION; + } + + g_free (phase_str); + } else { + phase = GSM_MANAGER_PHASE_APPLICATION; + } + + dbus_name = g_desktop_app_info_get_string (priv->app_info, + GSM_AUTOSTART_APP_DBUS_NAME_KEY); + if (dbus_name != NULL) { + priv->launch_type = AUTOSTART_LAUNCH_ACTIVATE; + } else { + priv->launch_type = AUTOSTART_LAUNCH_SPAWN; + } + + /* this must only be done on first load */ + switch (priv->launch_type) { + case AUTOSTART_LAUNCH_SPAWN: + startup_id = + g_desktop_app_info_get_string (priv->app_info, + GSM_AUTOSTART_APP_STARTUP_ID_KEY); + + if (startup_id == NULL) { + startup_id = gsm_util_generate_startup_id (); + } + break; + case AUTOSTART_LAUNCH_ACTIVATE: + startup_id = g_strdup (dbus_name); + break; + default: + g_assert_not_reached (); + } + + res = g_desktop_app_info_has_key (priv->app_info, + GSM_AUTOSTART_APP_AUTORESTART_KEY); + if (res) { + priv->autorestart = g_desktop_app_info_get_boolean (priv->app_info, + GSM_AUTOSTART_APP_AUTORESTART_KEY); + } else { + priv->autorestart = FALSE; + } + + g_free (priv->condition_string); + priv->condition_string = g_desktop_app_info_get_string (priv->app_info, + "AutostartCondition"); + setup_condition_monitor (app); + + g_object_set (app, + "phase", phase, + "startup-id", startup_id, + NULL); + + g_free (startup_id); + g_free (dbus_name); +} + +static void +gsm_autostart_app_set_desktop_filename (GsmAutostartApp *app, + const char *desktop_filename) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app); + + if (g_strcmp0 (priv->desktop_filename, desktop_filename) == 0) + return; + + if (priv->app_info != NULL) { + g_clear_object (&priv->app_info); + g_clear_pointer (&priv->desktop_filename, g_free); + g_clear_pointer (&priv->desktop_id, g_free); + } + + if (desktop_filename != NULL) { + priv->desktop_filename = g_strdup (desktop_filename); + priv->desktop_id = g_path_get_basename (desktop_filename); + } + + g_object_notify_by_pspec (G_OBJECT (app), props[PROP_DESKTOP_FILENAME]); +} + +static void +gsm_autostart_app_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GsmAutostartApp *self = GSM_AUTOSTART_APP (object); + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (self); + + switch ((GsmAutostartAppProperty) prop_id) { + case PROP_DESKTOP_FILENAME: + gsm_autostart_app_set_desktop_filename (self, g_value_get_string (value)); + break; + case PROP_MASK_SYSTEMD: + if (priv->mask_systemd != g_value_get_boolean (value)) { + priv->mask_systemd = g_value_get_boolean (value); + g_object_notify_by_pspec (object, props[PROP_MASK_SYSTEMD]); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gsm_autostart_app_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GsmAutostartApp *self = GSM_AUTOSTART_APP (object); + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (self); + + switch ((GsmAutostartAppProperty) prop_id) { + case PROP_DESKTOP_FILENAME: + if (priv->app_info != NULL) { + g_value_set_string (value, g_desktop_app_info_get_filename (priv->app_info)); + } else { + g_value_set_string (value, NULL); + } + break; + case PROP_MASK_SYSTEMD: + g_value_set_boolean (value, priv->mask_systemd); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gsm_autostart_app_dispose (GObject *object) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (object)); + + g_clear_pointer (&priv->startup_id, g_free); + + if (priv->session_provides) { + g_slist_free_full (priv->session_provides, g_free); + priv->session_provides = NULL; + } + + g_clear_pointer (&priv->condition_string, g_free); + g_clear_object (&priv->condition_settings); + g_clear_object (&priv->app_info); + g_clear_pointer (&priv->desktop_filename, g_free); + g_clear_pointer (&priv->desktop_id, g_free); + + if (priv->child_watch_id > 0) { + g_source_remove (priv->child_watch_id); + priv->child_watch_id = 0; + } + + if (priv->condition_monitor) { + g_file_monitor_cancel (priv->condition_monitor); + } + + G_OBJECT_CLASS (gsm_autostart_app_parent_class)->dispose (object); +} + +static gboolean +is_running (GsmApp *app) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (app)); + gboolean is; + + /* is running if pid is still valid or + * or a client is connected + */ + /* FIXME: check client */ + is = (priv->pid != -1); + + return is; +} + +static gboolean +is_conditionally_disabled (GsmApp *app) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (app)); + gboolean res; + gboolean disabled; + char *key; + guint kind; + + /* Check AutostartCondition */ + if (priv->condition_string == NULL) { + return FALSE; + } + + key = NULL; + res = parse_condition_string (priv->condition_string, &kind, &key); + if (! res) { + g_free (key); + return TRUE; + } + + if (key == NULL) { + return TRUE; + } + + if (kind == GSM_CONDITION_IF_EXISTS) { + char *file_path; + + file_path = resolve_conditional_file_path (key); + disabled = !g_file_test (file_path, G_FILE_TEST_EXISTS); + g_free (file_path); + } else if (kind == GSM_CONDITION_UNLESS_EXISTS) { + char *file_path; + + file_path = resolve_conditional_file_path (key); + disabled = g_file_test (file_path, G_FILE_TEST_EXISTS); + g_free (file_path); + } else if (kind == GSM_CONDITION_GSETTINGS && + priv->condition_settings != NULL) { + char **elems; + elems = g_strsplit (key, " ", 2); + disabled = !g_settings_get_boolean (priv->condition_settings, elems[1]); + g_strfreev (elems); + } else if (kind == GSM_CONDITION_IF_SESSION) { + GsmManager *manager; + char *session_name; + + /* get the singleton */ + manager = gsm_manager_get (); + + g_object_get (manager, "session-name", &session_name, NULL); + disabled = strcmp (session_name, key) != 0; + g_free (session_name); + } else if (kind == GSM_CONDITION_UNLESS_SESSION) { + GsmManager *manager; + char *session_name; + + /* get the singleton */ + manager = gsm_manager_get (); + + g_object_get (manager, "session-name", &session_name, NULL); + disabled = strcmp (session_name, key) == 0; + g_free (session_name); + } else { + disabled = TRUE; + } + + /* Set initial condition */ + priv->condition = !disabled; + + g_free (key); + + return disabled; +} + +static void +app_exited (GPid pid, + int status, + GsmAutostartApp *app) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app); + + g_debug ("GsmAutostartApp: (pid:%d) done (%s:%d)", + (int) pid, + WIFEXITED (status) ? "status" + : WIFSIGNALED (status) ? "signal" + : "unknown", + WIFEXITED (status) ? WEXITSTATUS (status) + : WIFSIGNALED (status) ? WTERMSIG (status) + : -1); + + g_spawn_close_pid (priv->pid); + priv->pid = -1; + priv->child_watch_id = 0; + + if (WIFEXITED (status)) { + gsm_app_exited (GSM_APP (app), WEXITSTATUS (status)); + } else if (WIFSIGNALED (status)) { + gsm_app_died (GSM_APP (app), WTERMSIG (status)); + } +} + +static int +_signal_pid (int pid, + int signal) +{ + int status; + + /* perhaps block sigchld */ + g_debug ("GsmAutostartApp: sending signal %d to process %d", signal, pid); + errno = 0; + status = kill (pid, signal); + + if (status < 0) { + if (errno == ESRCH) { + g_warning ("Child process %d was already dead.", + (int)pid); + } else { + g_warning ("Couldn't kill child process %d: %s", + pid, + g_strerror (errno)); + } + } + + /* perhaps unblock sigchld */ + + return status; +} + +static gboolean +autostart_app_stop_spawn (GsmAutostartApp *app, + GError **error) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app); + int res; + + if (priv->pid < 1) { + g_set_error (error, + GSM_APP_ERROR, + GSM_APP_ERROR_STOP, + "Not running"); + return FALSE; + } + + res = _signal_pid (priv->pid, SIGTERM); + if (res != 0) { + g_set_error (error, + GSM_APP_ERROR, + GSM_APP_ERROR_STOP, + "Unable to stop: %s", + g_strerror (errno)); + return FALSE; + } + + return TRUE; +} + +static gboolean +autostart_app_stop_activate (GsmAutostartApp *app, + GError **error) +{ + return TRUE; +} + +static gboolean +gsm_autostart_app_stop (GsmApp *app, + GError **error) +{ + GsmAutostartApp *self = GSM_AUTOSTART_APP (app); + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (self); + gboolean ret; + + g_return_val_if_fail (priv->app_info != NULL, FALSE); + + switch (priv->launch_type) { + case AUTOSTART_LAUNCH_SPAWN: + ret = autostart_app_stop_spawn (self, error); + break; + case AUTOSTART_LAUNCH_ACTIVATE: + ret = autostart_app_stop_activate (self, error); + break; + default: + g_assert_not_reached (); + break; + } + + return ret; +} + +static void +app_launched (GAppLaunchContext *ctx, + GAppInfo *appinfo, + GVariant *platform_data, + gpointer data) +{ + GsmAutostartApp *app = data; + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app); + gint pid; + gchar *sn_id; + + pid = 0; + sn_id = NULL; + + g_variant_lookup (platform_data, "pid", "i", &pid); + g_variant_lookup (platform_data, "startup-notification-id", "s", &sn_id); + priv->pid = pid; + priv->startup_id = sn_id; + + /* We are not interested in the result. */ + gnome_start_systemd_scope (priv->desktop_id, + pid, + NULL, + NULL, + NULL, NULL, NULL); +} + +#ifdef ENABLE_SYSTEMD_JOURNAL +static void +on_child_setup (GsmAutostartApp *app) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app); + int standard_output, standard_error; + + /* The FALSE means programs aren't expected to prefix each + * line with prefix to specify priority. + */ + standard_output = sd_journal_stream_fd (priv->desktop_id, + LOG_INFO, + FALSE); + standard_error = sd_journal_stream_fd (priv->desktop_id, + LOG_WARNING, + FALSE); + + if (standard_output >= 0) { + dup2 (standard_output, STDOUT_FILENO); + close (standard_output); + } + + if (standard_error >= 0) { + dup2 (standard_error, STDERR_FILENO); + close (standard_error); + } +} +#endif + +static gboolean +autostart_app_start_spawn (GsmAutostartApp *app, + GError **error) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app); + gboolean success; + GError *local_error; + const char *startup_id; + const char * const *variable_blacklist; + const char * const *child_environment; + int i; + GAppLaunchContext *ctx; + GSpawnChildSetupFunc child_setup_func = NULL; + gpointer child_setup_data = NULL; + guint handler; + + startup_id = gsm_app_peek_startup_id (GSM_APP (app)); + g_assert (startup_id != NULL); + + g_debug ("GsmAutostartApp: starting %s: command=%s startup-id=%s", priv->desktop_id, g_app_info_get_commandline (G_APP_INFO (priv->app_info)), startup_id); + + g_free (priv->startup_id); + local_error = NULL; + ctx = g_app_launch_context_new (); + + variable_blacklist = gsm_util_get_variable_blacklist (); + for (i = 0; variable_blacklist[i] != NULL; i++) + g_app_launch_context_unsetenv (ctx, variable_blacklist[i]); + + child_environment = gsm_util_listenv (); + for (i = 0; child_environment[i] != NULL; i++) { + char **environment_tuple; + const char *key; + const char *value; + + environment_tuple = g_strsplit (child_environment[i], "=", 2); + key = environment_tuple[0]; + value = environment_tuple[1]; + + if (value != NULL) + g_app_launch_context_setenv (ctx, key, value); + + g_strfreev (environment_tuple); + } + + if (startup_id != NULL) { + g_app_launch_context_setenv (ctx, "DESKTOP_AUTOSTART_ID", startup_id); + } + +#ifdef ENABLE_SYSTEMD_JOURNAL + if (sd_booted () > 0) { + child_setup_func = (GSpawnChildSetupFunc) on_child_setup; + child_setup_data = app; + } +#endif + + handler = g_signal_connect (ctx, "launched", G_CALLBACK (app_launched), app); + success = g_desktop_app_info_launch_uris_as_manager (priv->app_info, + NULL, + ctx, + G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH, + child_setup_func, child_setup_data, + NULL, NULL, + &local_error); + g_signal_handler_disconnect (ctx, handler); + + if (success) { + if (priv->pid > 0) { + g_debug ("GsmAutostartApp: started pid:%d", priv->pid); + priv->child_watch_id = g_child_watch_add (priv->pid, + (GChildWatchFunc)app_exited, + app); + } + } else { + g_set_error (error, + GSM_APP_ERROR, + GSM_APP_ERROR_START, + "Unable to start application: %s", local_error->message); + g_error_free (local_error); + } + + return success; +} + +static void +start_notify (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GError *error; + GsmAutostartApp *app = GSM_AUTOSTART_APP (user_data); + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app); + + error = NULL; + + g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error); + + if (error != NULL) { + g_warning ("GsmAutostartApp: Error starting application: %s", error->message); + g_error_free (error); + } else { + g_debug ("GsmAutostartApp: Started application %s", priv->desktop_id); + } +} + +static gboolean +autostart_app_start_activate (GsmAutostartApp *app, + GError **error) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app); + const char *name; + char *path; + char *arguments; + GDBusConnection *bus; + GError *local_error; + + local_error = NULL; + bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &local_error); + if (local_error != NULL) { + g_warning ("error getting session bus: %s", local_error->message); + g_propagate_error (error, local_error); + return FALSE; + } + + name = gsm_app_peek_startup_id (GSM_APP (app)); + g_assert (name != NULL); + + path = g_desktop_app_info_get_string (priv->app_info, + GSM_AUTOSTART_APP_DBUS_PATH_KEY); + if (path == NULL) { + /* just pick one? */ + path = g_strdup ("/"); + } + + arguments = g_desktop_app_info_get_string (priv->app_info, + GSM_AUTOSTART_APP_DBUS_ARGS_KEY); + + g_dbus_connection_call (bus, + name, + path, + GSM_SESSION_CLIENT_DBUS_INTERFACE, + "Start", + g_variant_new ("(s)", arguments), + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, + start_notify, app); + g_object_unref (bus); + + return TRUE; +} + +static gboolean +gsm_autostart_app_start (GsmApp *app, + GError **error) +{ + GsmAutostartApp *self = GSM_AUTOSTART_APP (app); + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (self); + gboolean ret; + + g_return_val_if_fail (priv->app_info != NULL, FALSE); + + switch (priv->launch_type) { + case AUTOSTART_LAUNCH_SPAWN: + ret = autostart_app_start_spawn (self, error); + break; + case AUTOSTART_LAUNCH_ACTIVATE: + ret = autostart_app_start_activate (self, error); + break; + default: + g_assert_not_reached (); + break; + } + + return ret; +} + +static gboolean +gsm_autostart_app_restart (GsmApp *app, + GError **error) +{ + GError *local_error; + gboolean res; + + /* ignore stop errors - it is fine if it is already stopped */ + local_error = NULL; + res = gsm_app_stop (app, &local_error); + if (! res) { + g_debug ("GsmAutostartApp: Couldn't stop app: %s", local_error->message); + g_error_free (local_error); + } + + res = gsm_app_start (app, &local_error); + if (! res) { + g_propagate_error (error, local_error); + return FALSE; + } + + return TRUE; +} + +static gboolean +gsm_autostart_app_provides (GsmApp *app, + const char *service) +{ + gchar *provides_str; + char **provides; + gsize i; + GSList *l; + GsmAutostartApp *self = GSM_AUTOSTART_APP (app); + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (self); + + g_return_val_if_fail (GSM_IS_APP (app), FALSE); + + if (priv->app_info == NULL) { + return FALSE; + } + + for (l = priv->session_provides; l != NULL; l = l->next) { + if (!strcmp (l->data, service)) + return TRUE; + } + + provides_str = g_desktop_app_info_get_string (priv->app_info, + GSM_AUTOSTART_APP_PROVIDES_KEY); + if (!provides_str) { + return FALSE; + } + provides = g_strsplit (provides_str, ";", -1); + g_free (provides_str); + + for (i = 0; provides[i]; i++) { + if (!strcmp (provides[i], service)) { + g_strfreev (provides); + return TRUE; + } + } + + g_strfreev (provides); + + return FALSE; +} + +static char ** +gsm_autostart_app_get_provides (GsmApp *app) +{ + GsmAutostartApp *self = GSM_AUTOSTART_APP (app); + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (self); + gchar *provides_str; + char **provides; + gsize provides_len; + char **result; + gsize result_len; + int i; + GSList *l; + + g_return_val_if_fail (GSM_IS_APP (app), NULL); + + if (priv->app_info == NULL) { + return NULL; + } + + provides_str = g_desktop_app_info_get_string (priv->app_info, + GSM_AUTOSTART_APP_PROVIDES_KEY); + + if (provides_str == NULL) { + return NULL; + } + + provides = g_strsplit (provides_str, ";", -1); + provides_len = g_strv_length (provides); + g_free (provides_str); + + if (!priv->session_provides) { + return provides; + } + + result_len = provides_len + g_slist_length (priv->session_provides); + result = g_new (char *, result_len + 1); /* including last NULL */ + + for (i = 0; provides[i] != NULL; i++) + result[i] = provides[i]; + g_free (provides); + + for (l = priv->session_provides; l != NULL; l = l->next, i++) + result[i] = g_strdup (l->data); + + result[i] = NULL; + + g_assert (i == result_len); + + return result; +} + +void +gsm_autostart_app_add_provides (GsmAutostartApp *aapp, + const char *provides) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (aapp); + + g_return_if_fail (GSM_IS_AUTOSTART_APP (aapp)); + + priv->session_provides = g_slist_prepend (priv->session_provides, + g_strdup (provides)); +} + +static gboolean +gsm_autostart_app_has_autostart_condition (GsmApp *app, + const char *condition) +{ + GsmAutostartApp *self = GSM_AUTOSTART_APP (app); + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (self); + + g_return_val_if_fail (GSM_IS_APP (app), FALSE); + g_return_val_if_fail (condition != NULL, FALSE); + + if (priv->condition_string == NULL) { + return FALSE; + } + + if (strcmp (priv->condition_string, condition) == 0) { + return TRUE; + } + + return FALSE; +} + +static gboolean +gsm_autostart_app_get_autorestart (GsmApp *app) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (app)); + gboolean res; + gboolean autorestart; + + if (priv->app_info == NULL) { + return FALSE; + } + + autorestart = FALSE; + + res = g_desktop_app_info_has_key (priv->app_info, + GSM_AUTOSTART_APP_AUTORESTART_KEY); + if (res) { + autorestart = g_desktop_app_info_get_boolean (priv->app_info, + GSM_AUTOSTART_APP_AUTORESTART_KEY); + } + + return autorestart; +} + +static const char * +gsm_autostart_app_get_app_id (GsmApp *app) +{ + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (app)); + + if (priv->app_info == NULL) { + return NULL; + } + + return g_app_info_get_id (G_APP_INFO (priv->app_info)); +} + +static gboolean +gsm_autostart_app_initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + GsmAutostartApp *app = GSM_AUTOSTART_APP (initable); + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app); + + g_assert (priv->desktop_filename != NULL); + priv->app_info = g_desktop_app_info_new_from_filename (priv->desktop_filename); + if (priv->app_info == NULL) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Could not parse desktop file %s or it references a not found TryExec binary", priv->desktop_id); + return FALSE; + } + + load_desktop_file (app); + + return TRUE; +} + +static gboolean +gsm_autostart_app_save_to_keyfile (GsmApp *base_app, + GKeyFile *keyfile, + GError **error) +{ + GsmAutostartApp *app = GSM_AUTOSTART_APP (base_app); + GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app); + char **provides = NULL; + char *dbus_name; + char *phase; + gboolean res; + + provides = gsm_app_get_provides (base_app); + if (provides != NULL) { + g_key_file_set_string_list (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + GSM_AUTOSTART_APP_PROVIDES_KEY, + (const char * const *) + provides, + g_strv_length (provides)); + g_strfreev (provides); + } + + phase = g_desktop_app_info_get_string (priv->app_info, + GSM_AUTOSTART_APP_PHASE_KEY); + if (phase != NULL) { + g_key_file_set_string (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + GSM_AUTOSTART_APP_PHASE_KEY, + phase); + g_free (phase); + } + + dbus_name = g_desktop_app_info_get_string (priv->app_info, + GSM_AUTOSTART_APP_DBUS_NAME_KEY); + if (dbus_name != NULL) { + g_key_file_set_string (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + GSM_AUTOSTART_APP_DBUS_NAME_KEY, + dbus_name); + g_free (dbus_name); + } + + res = g_desktop_app_info_has_key (priv->app_info, + GSM_AUTOSTART_APP_AUTORESTART_KEY); + if (res) { + g_key_file_set_boolean (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + GSM_AUTOSTART_APP_AUTORESTART_KEY, + g_desktop_app_info_get_boolean (priv->app_info, + GSM_AUTOSTART_APP_AUTORESTART_KEY)); + } + + res = g_desktop_app_info_has_key (priv->app_info, + GSM_AUTOSTART_APP_AUTORESTART_KEY); + if (res) { + char *autostart_condition; + + autostart_condition = g_desktop_app_info_get_string (priv->app_info, "AutostartCondition"); + + g_key_file_set_string (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + "AutostartCondition", + autostart_condition); + g_free (autostart_condition); + } + + return TRUE; +} + +static void +gsm_autostart_app_initable_iface_init (GInitableIface *iface) +{ + iface->init = gsm_autostart_app_initable_init; +} + +static void +gsm_autostart_app_class_init (GsmAutostartAppClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GsmAppClass *app_class = GSM_APP_CLASS (klass); + + object_class->set_property = gsm_autostart_app_set_property; + object_class->get_property = gsm_autostart_app_get_property; + object_class->dispose = gsm_autostart_app_dispose; + + app_class->impl_is_disabled = is_disabled; + app_class->impl_is_conditionally_disabled = is_conditionally_disabled; + app_class->impl_is_running = is_running; + app_class->impl_start = gsm_autostart_app_start; + app_class->impl_restart = gsm_autostart_app_restart; + app_class->impl_stop = gsm_autostart_app_stop; + app_class->impl_provides = gsm_autostart_app_provides; + app_class->impl_get_provides = gsm_autostart_app_get_provides; + app_class->impl_has_autostart_condition = gsm_autostart_app_has_autostart_condition; + app_class->impl_get_app_id = gsm_autostart_app_get_app_id; + app_class->impl_get_autorestart = gsm_autostart_app_get_autorestart; + app_class->impl_save_to_keyfile = gsm_autostart_app_save_to_keyfile; + + props[PROP_DESKTOP_FILENAME] = + g_param_spec_string ("desktop-filename", + "Desktop filename", + "Freedesktop .desktop file", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + props[PROP_MASK_SYSTEMD] = + g_param_spec_boolean ("mask-systemd", + "Mask if systemd started", + "Mask if GNOME systemd flag is set in desktop file", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (object_class, G_N_ELEMENTS (props), props); + + signals[CONDITION_CHANGED] = + g_signal_new ("condition-changed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsmAutostartAppClass, condition_changed), + NULL, NULL, NULL, + G_TYPE_NONE, + 1, + G_TYPE_BOOLEAN); +} + +GsmApp * +gsm_autostart_app_new (const char *desktop_file, + gboolean mask_systemd, + GError **error) +{ + return (GsmApp*) g_initable_new (GSM_TYPE_AUTOSTART_APP, NULL, error, + "desktop-filename", desktop_file, + "mask-systemd", mask_systemd, + NULL); +} diff --git a/gnome-session/gsm-autostart-app.h b/gnome-session/gsm-autostart-app.h new file mode 100644 index 0000000..a7a5c28 --- /dev/null +++ b/gnome-session/gsm-autostart-app.h @@ -0,0 +1,61 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef __GSM_AUTOSTART_APP_H__ +#define __GSM_AUTOSTART_APP_H__ + +#include "gsm-app.h" + +#include + +G_BEGIN_DECLS + +#define GSM_TYPE_AUTOSTART_APP (gsm_autostart_app_get_type ()) +G_DECLARE_DERIVABLE_TYPE (GsmAutostartApp, gsm_autostart_app, GSM, AUTOSTART_APP, GsmApp) + +struct _GsmAutostartAppClass +{ + GsmAppClass parent_class; + + /* signals */ + void (*condition_changed) (GsmApp *app, + gboolean condition); +}; + +GsmApp *gsm_autostart_app_new (const char *desktop_file, + gboolean mask_systemd, + GError **error); + +void gsm_autostart_app_add_provides (GsmAutostartApp *aapp, + const char *provides); + +#define GSM_AUTOSTART_APP_SYSTEMD_KEY "X-GNOME-HiddenUnderSystemd" +#define GSM_AUTOSTART_APP_ENABLED_KEY "X-GNOME-Autostart-enabled" +#define GSM_AUTOSTART_APP_PHASE_KEY "X-GNOME-Autostart-Phase" +#define GSM_AUTOSTART_APP_PROVIDES_KEY "X-GNOME-Provides" +#define GSM_AUTOSTART_APP_STARTUP_ID_KEY "X-GNOME-Autostart-startup-id" +#define GSM_AUTOSTART_APP_AUTORESTART_KEY "X-GNOME-AutoRestart" +#define GSM_AUTOSTART_APP_DBUS_NAME_KEY "X-GNOME-DBus-Name" +#define GSM_AUTOSTART_APP_DBUS_PATH_KEY "X-GNOME-DBus-Path" +#define GSM_AUTOSTART_APP_DBUS_ARGS_KEY "X-GNOME-DBus-Start-Arguments" +#define GSM_AUTOSTART_APP_DISCARD_KEY "X-GNOME-Autostart-discard-exec" + +G_END_DECLS + +#endif /* __GSM_AUTOSTART_APP_H__ */ diff --git a/gnome-session/gsm-client.c b/gnome-session/gsm-client.c new file mode 100644 index 0000000..25f0bf7 --- /dev/null +++ b/gnome-session/gsm-client.c @@ -0,0 +1,583 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include "config.h" + +#include "gsm-client.h" +#include "org.gnome.SessionManager.Client.h" + +static guint32 client_serial = 1; + +typedef struct +{ + char *id; + char *startup_id; + char *app_id; + guint status; + GsmExportedClient *skeleton; + GDBusConnection *connection; +} GsmClientPrivate; + +typedef enum { + PROP_STARTUP_ID = 1, + PROP_APP_ID, + PROP_STATUS, +} GsmClientProperty; + +static GParamSpec *props[PROP_STATUS + 1] = { NULL, }; + +enum { + DISCONNECTED, + END_SESSION_RESPONSE, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GsmClient, gsm_client, G_TYPE_OBJECT) + +#define GSM_CLIENT_DBUS_IFACE "org.gnome.SessionManager.Client" + +static const GDBusErrorEntry gsm_client_error_entries[] = { + { GSM_CLIENT_ERROR_GENERAL, GSM_CLIENT_DBUS_IFACE ".GeneralError" }, + { GSM_CLIENT_ERROR_NOT_REGISTERED, GSM_CLIENT_DBUS_IFACE ".NotRegistered" } +}; + +GQuark +gsm_client_error_quark (void) +{ + static volatile gsize quark_volatile = 0; + + g_dbus_error_register_error_domain ("gsm_client_error", + &quark_volatile, + gsm_client_error_entries, + G_N_ELEMENTS (gsm_client_error_entries)); + return quark_volatile; +} + +static guint32 +get_next_client_serial (void) +{ + guint32 serial; + + serial = client_serial++; + + if ((gint32)client_serial < 0) { + client_serial = 1; + } + + return serial; +} + +static gboolean +gsm_client_get_startup_id (GsmExportedClient *skeleton, + GDBusMethodInvocation *invocation, + GsmClient *client) +{ + GsmClientPrivate *priv = gsm_client_get_instance_private (client); + + gsm_exported_client_complete_get_startup_id (skeleton, invocation, priv->startup_id); + return TRUE; +} + +static gboolean +gsm_client_get_app_id (GsmExportedClient *skeleton, + GDBusMethodInvocation *invocation, + GsmClient *client) +{ + GsmClientPrivate *priv = gsm_client_get_instance_private (client); + + gsm_exported_client_complete_get_app_id (skeleton, invocation, priv->app_id); + return TRUE; +} + +static gboolean +gsm_client_get_restart_style_hint (GsmExportedClient *skeleton, + GDBusMethodInvocation *invocation, + GsmClient *client) +{ + guint hint; + + hint = GSM_CLIENT_GET_CLASS (client)->impl_get_restart_style_hint (client); + gsm_exported_client_complete_get_restart_style_hint (skeleton, invocation, hint); + return TRUE; +} + +static gboolean +gsm_client_get_status (GsmExportedClient *skeleton, + GDBusMethodInvocation *invocation, + GsmClient *client) +{ + GsmClientPrivate *priv = gsm_client_get_instance_private (client); + + gsm_exported_client_complete_get_status (skeleton, invocation, priv->status); + return TRUE; +} + +static gboolean +gsm_client_get_unix_process_id (GsmExportedClient *skeleton, + GDBusMethodInvocation *invocation, + GsmClient *client) +{ + guint pid; + + pid = GSM_CLIENT_GET_CLASS (client)->impl_get_unix_process_id (client); + gsm_exported_client_complete_get_unix_process_id (skeleton, invocation, pid); + return TRUE; +} + +static gboolean +gsm_client_stop_dbus (GsmExportedClient *skeleton, + GDBusMethodInvocation *invocation, + GsmClient *client) +{ + GError *error = NULL; + gsm_client_stop (client, &error); + + if (error != NULL) { + g_dbus_method_invocation_take_error (invocation, error); + } else { + gsm_exported_client_complete_stop (skeleton, invocation); + } + + return TRUE; +} + +static gboolean +register_client (GsmClient *client) +{ + GsmClientPrivate *priv = gsm_client_get_instance_private (client); + GError *error = NULL; + GsmExportedClient *skeleton; + + priv->connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + if (priv->connection == NULL) { + g_critical ("error getting session bus: %s", error->message); + g_error_free (error); + return FALSE; + } + + skeleton = gsm_exported_client_skeleton_new (); + priv->skeleton = skeleton; + g_debug ("exporting client to object path: %s", priv->id); + g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (skeleton), + priv->connection, + priv->id, &error); + + if (error != NULL) { + g_critical ("error exporting client on session bus: %s", error->message); + g_error_free (error); + return FALSE; + } + + g_signal_connect (skeleton, "handle-get-app-id", + G_CALLBACK (gsm_client_get_app_id), client); + g_signal_connect (skeleton, "handle-get-restart-style-hint", + G_CALLBACK (gsm_client_get_restart_style_hint), client); + g_signal_connect (skeleton, "handle-get-startup-id", + G_CALLBACK (gsm_client_get_startup_id), client); + g_signal_connect (skeleton, "handle-get-status", + G_CALLBACK (gsm_client_get_status), client); + g_signal_connect (skeleton, "handle-get-unix-process-id", + G_CALLBACK (gsm_client_get_unix_process_id), client); + g_signal_connect (skeleton, "handle-stop", + G_CALLBACK (gsm_client_stop_dbus), client); + + return TRUE; +} + +static GObject * +gsm_client_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GsmClientPrivate *priv; + GsmClient *client; + gboolean res; + + client = GSM_CLIENT (G_OBJECT_CLASS (gsm_client_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + priv = gsm_client_get_instance_private (client); + + g_free (priv->id); + priv->id = g_strdup_printf ("/org/gnome/SessionManager/Client%u", get_next_client_serial ()); + + res = register_client (client); + if (! res) { + g_warning ("Unable to register client with session bus"); + } + + return G_OBJECT (client); +} + +static void +gsm_client_init (GsmClient *client) +{ +} + +static void +gsm_client_finalize (GObject *object) +{ + GsmClient *client; + GsmClientPrivate *priv; + + g_return_if_fail (object != NULL); + g_return_if_fail (GSM_IS_CLIENT (object)); + + client = GSM_CLIENT (object); + priv = gsm_client_get_instance_private (client); + + g_return_if_fail (priv != NULL); + + g_free (priv->id); + g_free (priv->startup_id); + g_free (priv->app_id); + + if (priv->skeleton != NULL) { + g_dbus_interface_skeleton_unexport_from_connection (G_DBUS_INTERFACE_SKELETON (priv->skeleton), + priv->connection); + g_clear_object (&priv->skeleton); + } + + g_clear_object (&priv->connection); + + G_OBJECT_CLASS (gsm_client_parent_class)->finalize (object); +} + +void +gsm_client_set_status (GsmClient *client, + guint status) +{ + GsmClientPrivate *priv = gsm_client_get_instance_private (client); + + g_return_if_fail (GSM_IS_CLIENT (client)); + if (priv->status != status) { + priv->status = status; + g_object_notify_by_pspec (G_OBJECT (client), props[PROP_STATUS]); + } +} + +static void +gsm_client_set_startup_id (GsmClient *client, + const char *startup_id) +{ + GsmClientPrivate *priv = gsm_client_get_instance_private (client); + + g_return_if_fail (GSM_IS_CLIENT (client)); + + g_free (priv->startup_id); + + if (startup_id != NULL) { + priv->startup_id = g_strdup (startup_id); + } else { + priv->startup_id = g_strdup (""); + } + g_object_notify_by_pspec (G_OBJECT (client), props[PROP_STARTUP_ID]); +} + +void +gsm_client_set_app_id (GsmClient *client, + const char *app_id) +{ + GsmClientPrivate *priv = gsm_client_get_instance_private (client); + + g_return_if_fail (GSM_IS_CLIENT (client)); + + g_free (priv->app_id); + + if (app_id != NULL) { + priv->app_id = g_strdup (app_id); + } else { + priv->app_id = g_strdup (""); + } + g_object_notify_by_pspec (G_OBJECT (client), props[PROP_APP_ID]); +} + +static void +gsm_client_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GsmClient *self; + + self = GSM_CLIENT (object); + + switch ((GsmClientProperty) prop_id) { + case PROP_STARTUP_ID: + gsm_client_set_startup_id (self, g_value_get_string (value)); + break; + case PROP_APP_ID: + gsm_client_set_app_id (self, g_value_get_string (value)); + break; + case PROP_STATUS: + gsm_client_set_status (self, g_value_get_uint (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gsm_client_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GsmClient *self = GSM_CLIENT (object); + GsmClientPrivate *priv = gsm_client_get_instance_private (self); + + switch ((GsmClientProperty) prop_id) { + case PROP_STARTUP_ID: + g_value_set_string (value, priv->startup_id); + break; + case PROP_APP_ID: + g_value_set_string (value, priv->app_id); + break; + case PROP_STATUS: + g_value_set_uint (value, priv->status); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +default_stop (GsmClient *client, + GError **error) +{ + g_return_val_if_fail (GSM_IS_CLIENT (client), FALSE); + + g_warning ("Stop not implemented"); + + return TRUE; +} + +static void +gsm_client_dispose (GObject *object) +{ + GsmClient *client; + GsmClientPrivate *priv; + + g_return_if_fail (object != NULL); + g_return_if_fail (GSM_IS_CLIENT (object)); + + client = GSM_CLIENT (object); + priv = gsm_client_get_instance_private (client); + + g_debug ("GsmClient: disposing %s", priv->id); + + G_OBJECT_CLASS (gsm_client_parent_class)->dispose (object); +} + +static void +gsm_client_class_init (GsmClientClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = gsm_client_get_property; + object_class->set_property = gsm_client_set_property; + object_class->constructor = gsm_client_constructor; + object_class->finalize = gsm_client_finalize; + object_class->dispose = gsm_client_dispose; + + klass->impl_stop = default_stop; + + signals[DISCONNECTED] = + g_signal_new ("disconnected", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsmClientClass, disconnected), + NULL, NULL, NULL, + G_TYPE_NONE, + 0); + signals[END_SESSION_RESPONSE] = + g_signal_new ("end-session-response", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsmClientClass, end_session_response), + NULL, NULL, NULL, + G_TYPE_NONE, + 4, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_STRING); + + props[PROP_STARTUP_ID] = + g_param_spec_string ("startup-id", + "startup-id", + "startup-id", + "", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + props[PROP_APP_ID] = + g_param_spec_string ("app-id", + "app-id", + "app-id", + "", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + props[PROP_STATUS] = + g_param_spec_uint ("status", + "status", + "status", + 0, + G_MAXINT, + GSM_CLIENT_UNREGISTERED, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (object_class, G_N_ELEMENTS (props), props); +} + +const char * +gsm_client_peek_id (GsmClient *client) +{ + GsmClientPrivate *priv = gsm_client_get_instance_private (client); + + g_return_val_if_fail (GSM_IS_CLIENT (client), NULL); + + return priv->id; +} + +/** + * gsm_client_peek_app_id: + * @client: a #GsmClient. + * + * Note that the application ID might not be known; this happens when for XSMP + * clients that we did not start ourselves, for instance. + * + * Returns: the application ID of the client, or %NULL if no such ID is + * known. The string is owned by @client. + **/ +const char * +gsm_client_peek_app_id (GsmClient *client) +{ + GsmClientPrivate *priv = gsm_client_get_instance_private (client); + + g_return_val_if_fail (GSM_IS_CLIENT (client), NULL); + + return priv->app_id; +} + +const char * +gsm_client_peek_startup_id (GsmClient *client) +{ + GsmClientPrivate *priv = gsm_client_get_instance_private (client); + + g_return_val_if_fail (GSM_IS_CLIENT (client), NULL); + + return priv->startup_id; +} + +guint +gsm_client_peek_status (GsmClient *client) +{ + GsmClientPrivate *priv = gsm_client_get_instance_private (client); + + g_return_val_if_fail (GSM_IS_CLIENT (client), GSM_CLIENT_UNREGISTERED); + + return priv->status; +} + +guint +gsm_client_peek_restart_style_hint (GsmClient *client) +{ + g_return_val_if_fail (GSM_IS_CLIENT (client), GSM_CLIENT_RESTART_NEVER); + + return GSM_CLIENT_GET_CLASS (client)->impl_get_restart_style_hint (client); +} + +/** + * gsm_client_get_app_name: + * @client: a #GsmClient. + * + * Returns: a copy of the application name of the client, or %NULL if no such + * name is known. + **/ +char * +gsm_client_get_app_name (GsmClient *client) +{ + g_return_val_if_fail (GSM_IS_CLIENT (client), NULL); + + return GSM_CLIENT_GET_CLASS (client)->impl_get_app_name (client); +} + +gboolean +gsm_client_cancel_end_session (GsmClient *client, + GError **error) +{ + g_return_val_if_fail (GSM_IS_CLIENT (client), FALSE); + + return GSM_CLIENT_GET_CLASS (client)->impl_cancel_end_session (client, error); +} + + +gboolean +gsm_client_query_end_session (GsmClient *client, + GsmClientEndSessionFlag flags, + GError **error) +{ + g_return_val_if_fail (GSM_IS_CLIENT (client), FALSE); + + return GSM_CLIENT_GET_CLASS (client)->impl_query_end_session (client, flags, error); +} + +gboolean +gsm_client_end_session (GsmClient *client, + GsmClientEndSessionFlag flags, + GError **error) +{ + g_return_val_if_fail (GSM_IS_CLIENT (client), FALSE); + + return GSM_CLIENT_GET_CLASS (client)->impl_end_session (client, flags, error); +} + +gboolean +gsm_client_stop (GsmClient *client, + GError **error) +{ + g_return_val_if_fail (GSM_IS_CLIENT (client), FALSE); + + return GSM_CLIENT_GET_CLASS (client)->impl_stop (client, error); +} + +void +gsm_client_disconnected (GsmClient *client) +{ + g_signal_emit (client, signals[DISCONNECTED], 0); +} + +GKeyFile * +gsm_client_save (GsmClient *client, + GsmApp *app, + GError **error) +{ + g_return_val_if_fail (GSM_IS_CLIENT (client), NULL); + + return GSM_CLIENT_GET_CLASS (client)->impl_save (client, app, error); +} + +void +gsm_client_end_session_response (GsmClient *client, + gboolean is_ok, + gboolean do_last, + gboolean cancel, + const char *reason) +{ + g_signal_emit (client, signals[END_SESSION_RESPONSE], 0, + is_ok, do_last, cancel, reason); +} diff --git a/gnome-session/gsm-client.h b/gnome-session/gsm-client.h new file mode 100644 index 0000000..d3feba9 --- /dev/null +++ b/gnome-session/gsm-client.h @@ -0,0 +1,140 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef __GSM_CLIENT_H__ +#define __GSM_CLIENT_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +#define GSM_TYPE_CLIENT (gsm_client_get_type ()) +G_DECLARE_DERIVABLE_TYPE (GsmClient, gsm_client, GSM, CLIENT, GObject) + +typedef struct _GsmApp GsmApp; +typedef struct _GsmClient GsmClient; +typedef struct _GsmClientClass GsmClientClass; + +typedef enum { + GSM_CLIENT_UNREGISTERED = 0, + GSM_CLIENT_REGISTERED, + GSM_CLIENT_FINISHED, + GSM_CLIENT_FAILED +} GsmClientStatus; + +typedef enum { + GSM_CLIENT_RESTART_NEVER = 0, + GSM_CLIENT_RESTART_IF_RUNNING, + GSM_CLIENT_RESTART_ANYWAY, + GSM_CLIENT_RESTART_IMMEDIATELY +} GsmClientRestartStyle; + +typedef enum { + GSM_CLIENT_END_SESSION_FLAG_FORCEFUL = 1 << 0, + GSM_CLIENT_END_SESSION_FLAG_SAVE = 1 << 1, + GSM_CLIENT_END_SESSION_FLAG_LAST = 1 << 2 +} GsmClientEndSessionFlag; + +struct _GsmClientClass +{ + GObjectClass parent_class; + + /* signals */ + void (*disconnected) (GsmClient *client); + void (*end_session_response) (GsmClient *client, + gboolean ok, + gboolean do_last, + gboolean cancel, + const char *reason); + + /* virtual methods */ + char * (*impl_get_app_name) (GsmClient *client); + GsmClientRestartStyle (*impl_get_restart_style_hint) (GsmClient *client); + guint (*impl_get_unix_process_id) (GsmClient *client); + gboolean (*impl_query_end_session) (GsmClient *client, + GsmClientEndSessionFlag flags, + GError **error); + gboolean (*impl_end_session) (GsmClient *client, + GsmClientEndSessionFlag flags, + GError **error); + gboolean (*impl_cancel_end_session) (GsmClient *client, + GError **error); + gboolean (*impl_stop) (GsmClient *client, + GError **error); + GKeyFile * (*impl_save) (GsmClient *client, + GsmApp *app, + GError **error); +}; + +typedef enum +{ + GSM_CLIENT_ERROR_GENERAL = 0, + GSM_CLIENT_ERROR_NOT_REGISTERED, + GSM_CLIENT_NUM_ERRORS +} GsmClientError; + +#define GSM_CLIENT_ERROR gsm_client_error_quark () +GQuark gsm_client_error_quark (void); + +const char *gsm_client_peek_id (GsmClient *client); + + +const char * gsm_client_peek_startup_id (GsmClient *client); +const char * gsm_client_peek_app_id (GsmClient *client); +guint gsm_client_peek_restart_style_hint (GsmClient *client); +guint gsm_client_peek_status (GsmClient *client); + + +char *gsm_client_get_app_name (GsmClient *client); +void gsm_client_set_app_id (GsmClient *client, + const char *app_id); +void gsm_client_set_status (GsmClient *client, + guint status); + +gboolean gsm_client_end_session (GsmClient *client, + guint flags, + GError **error); +gboolean gsm_client_query_end_session (GsmClient *client, + guint flags, + GError **error); +gboolean gsm_client_cancel_end_session (GsmClient *client, + GError **error); + +void gsm_client_disconnected (GsmClient *client); + +GKeyFile *gsm_client_save (GsmClient *client, + GsmApp *app, + GError **error); + +gboolean gsm_client_stop (GsmClient *client, + GError **error); + +/* private */ + +void gsm_client_end_session_response (GsmClient *client, + gboolean is_ok, + gboolean do_last, + gboolean cancel, + const char *reason); + +G_END_DECLS + +#endif /* __GSM_CLIENT_H__ */ diff --git a/gnome-session/gsm-consolekit.c b/gnome-session/gsm-consolekit.c new file mode 100644 index 0000000..2598e80 --- /dev/null +++ b/gnome-session/gsm-consolekit.c @@ -0,0 +1,972 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Jon McCann + * + * 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 . + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "gsm-system.h" +#include "gsm-consolekit.h" + +#define CK_NAME "org.freedesktop.ConsoleKit" + +#define CK_MANAGER_PATH "/org/freedesktop/ConsoleKit/Manager" +#define CK_MANAGER_INTERFACE CK_NAME ".Manager" +#define CK_SEAT_INTERFACE CK_NAME ".Seat" +#define CK_SESSION_INTERFACE CK_NAME ".Session" + + +struct _GsmConsolekitPrivate +{ + GDBusProxy *ck_proxy; + GDBusProxy *ck_session_proxy; + + char *session_id; + gchar *session_path; + + const gchar *inhibit_locks; + gint inhibit_fd; + + gboolean is_active; + + gint delay_inhibit_fd; + gboolean prepare_for_shutdown_expected; +}; + +enum { + PROP_0, + PROP_ACTIVE +}; + +static void gsm_consolekit_system_init (GsmSystemInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (GsmConsolekit, gsm_consolekit, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GSM_TYPE_SYSTEM, + gsm_consolekit_system_init)) + +static void +drop_system_inhibitor (GsmConsolekit *manager) +{ + if (manager->priv->inhibit_fd != -1) { + g_debug ("GsmConsolekit: Dropping system inhibitor fd %d", manager->priv->inhibit_fd); + close (manager->priv->inhibit_fd); + manager->priv->inhibit_fd = -1; + } +} + +static void +drop_delay_inhibitor (GsmConsolekit *manager) +{ + if (manager->priv->delay_inhibit_fd != -1) { + g_debug ("GsmConsolekit: Dropping delay inhibitor"); + close (manager->priv->delay_inhibit_fd); + manager->priv->delay_inhibit_fd = -1; + } +} + +static void +gsm_consolekit_finalize (GObject *object) +{ + GsmConsolekit *consolekit = GSM_CONSOLEKIT (object); + + g_clear_object (&consolekit->priv->ck_proxy); + g_clear_object (&consolekit->priv->ck_session_proxy); + free (consolekit->priv->session_id); + g_free (consolekit->priv->session_path); + + drop_system_inhibitor (consolekit); + drop_delay_inhibitor (consolekit); + + G_OBJECT_CLASS (gsm_consolekit_parent_class)->finalize (object); +} + +static void +gsm_consolekit_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GsmConsolekit *self = GSM_CONSOLEKIT (object); + + switch (prop_id) { + case PROP_ACTIVE: + self->priv->is_active = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gsm_consolekit_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GsmConsolekit *self = GSM_CONSOLEKIT (object); + + switch (prop_id) { + case PROP_ACTIVE: + g_value_set_boolean (value, self->priv->is_active); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gsm_consolekit_class_init (GsmConsolekitClass *manager_class) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (manager_class); + + object_class->get_property = gsm_consolekit_get_property; + object_class->set_property = gsm_consolekit_set_property; + object_class->finalize = gsm_consolekit_finalize; + + g_object_class_override_property (object_class, PROP_ACTIVE, "active"); + + g_type_class_add_private (manager_class, sizeof (GsmConsolekitPrivate)); +} + +static void ck_session_proxy_signal_cb (GDBusProxy *proxy, + const gchar *sender_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data); + +static void ck_proxy_signal_cb (GDBusProxy *proxy, + const gchar *sender_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data); + +static void +ck_pid_get_session (GsmConsolekit *manager, + pid_t pid, + gchar **session_id) +{ + GVariant *res; + + *session_id = NULL; + + if (pid < 0) { + g_warning ("Calling GetSessionForUnixProcess failed." + "Invalid pid."); + return; + } + + res = g_dbus_proxy_call_sync (manager->priv->ck_proxy, + "GetSessionForUnixProcess", + g_variant_new ("(u)", pid), + 0, + -1, + NULL, + NULL); + if (!res) { + g_warning ("Calling GetSessionForUnixProcess failed." + "Check that ConsoleKit is properly installed."); + return; + } + + g_variant_get (res, "(o)", session_id); + g_variant_unref (res); +} + +static void +gsm_consolekit_init (GsmConsolekit *manager) +{ + GError *error = NULL; + GDBusConnection *bus; + GVariant *res; + + manager->priv = G_TYPE_INSTANCE_GET_PRIVATE (manager, + GSM_TYPE_CONSOLEKIT, + GsmConsolekitPrivate); + + manager->priv->inhibit_fd = -1; + manager->priv->delay_inhibit_fd = -1; + + bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (bus == NULL) + g_error ("Failed to connect to system bus: %s", + error->message); + manager->priv->ck_proxy = + g_dbus_proxy_new_sync (bus, + 0, + NULL, + CK_NAME, + CK_MANAGER_PATH, + CK_MANAGER_INTERFACE, + NULL, + &error); + if (manager->priv->ck_proxy == NULL) { + g_warning ("Failed to connect to consolekit: %s", + error->message); + g_clear_error (&error); + } + + g_signal_connect (manager->priv->ck_proxy, "g-signal", + G_CALLBACK (ck_proxy_signal_cb), manager); + + ck_pid_get_session (manager, getpid (), &manager->priv->session_id); + + if (manager->priv->session_id == NULL) { + g_warning ("Could not get session id for session. Check that ConsoleKit is " + "properly installed."); + return; + } + + /* in ConsoleKit, the session id is the session path */ + manager->priv->session_path = g_strdup (manager->priv->session_id); + + manager->priv->ck_session_proxy = + g_dbus_proxy_new_sync (bus, + 0, + NULL, + CK_NAME, + manager->priv->session_path, + CK_SESSION_INTERFACE, + NULL, + &error); + if (manager->priv->ck_proxy == NULL) { + g_warning ("Failed to connect to consolekit session: %s", + error->message); + g_clear_error (&error); + } + + g_signal_connect (manager->priv->ck_session_proxy, "g-signal", + G_CALLBACK (ck_session_proxy_signal_cb), manager); + + g_object_unref (bus); +} + +static void +emit_restart_complete (GsmConsolekit *manager, + GError *error) +{ + GError *call_error; + + call_error = NULL; + + if (error != NULL) { + call_error = g_error_new_literal (GSM_SYSTEM_ERROR, + GSM_SYSTEM_ERROR_RESTARTING, + error->message); + } + + g_signal_emit_by_name (G_OBJECT (manager), + "request_completed", call_error); + + if (call_error != NULL) { + g_error_free (call_error); + } +} + +static void +emit_stop_complete (GsmConsolekit *manager, + GError *error) +{ + GError *call_error; + + call_error = NULL; + + if (error != NULL) { + call_error = g_error_new_literal (GSM_SYSTEM_ERROR, + GSM_SYSTEM_ERROR_STOPPING, + error->message); + } + + g_signal_emit_by_name (G_OBJECT (manager), + "request_completed", call_error); + + if (call_error != NULL) { + g_error_free (call_error); + } +} + +static void +restart_done (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GDBusProxy *proxy = G_DBUS_PROXY (source); + GsmConsolekit *manager = user_data; + GError *error = NULL; + GVariant *res; + + res = g_dbus_proxy_call_finish (proxy, result, &error); + + if (!res) { + g_warning ("Unable to restart system: %s", error->message); + emit_restart_complete (manager, error); + g_error_free (error); + } else { + emit_restart_complete (manager, NULL); + g_variant_unref (res); + } +} + +static void +gsm_consolekit_attempt_restart (GsmSystem *system) +{ + GsmConsolekit *manager = GSM_CONSOLEKIT (system); + + /* Use Restart instead of Reboot because it will work on + * both CK and CK2 */ + g_dbus_proxy_call (manager->priv->ck_proxy, + "Restart", + g_variant_new ("()"), + 0, + G_MAXINT, + NULL, + restart_done, + manager); +} + +static void +stop_done (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GDBusProxy *proxy = G_DBUS_PROXY (source); + GsmConsolekit *manager = user_data; + GError *error = NULL; + GVariant *res; + + res = g_dbus_proxy_call_finish (proxy, result, &error); + + if (!res) { + g_warning ("Unable to stop system: %s", error->message); + emit_stop_complete (manager, error); + g_error_free (error); + } else { + emit_stop_complete (manager, NULL); + g_variant_unref (res); + } +} + +static void +gsm_consolekit_attempt_stop (GsmSystem *system) +{ + GsmConsolekit *manager = GSM_CONSOLEKIT (system); + + /* Use Stop insetad of PowerOff because it will work with + * Ck and CK2. */ + g_dbus_proxy_call (manager->priv->ck_proxy, + "Stop", + g_variant_new ("()"), + 0, + G_MAXINT, + NULL, + stop_done, + manager); +} + +static void +gsm_consolekit_set_session_idle (GsmSystem *system, + gboolean is_idle) +{ + GsmConsolekit *manager = GSM_CONSOLEKIT (system); + + g_debug ("Updating consolekit idle status: %d", is_idle); + g_dbus_proxy_call_sync (manager->priv->ck_session_proxy, + "SetIdleHint", + g_variant_new ("(b)", is_idle), + 0, + G_MAXINT, + NULL, NULL); +} + +static void +ck_session_get_seat (GsmConsolekit *manager, + gchar **seat) +{ + GVariant *res; + + *seat = NULL; + + res = g_dbus_proxy_call_sync (manager->priv->ck_session_proxy, + "GetSeatId", + g_variant_new ("()"), + 0, + -1, + NULL, NULL); + if (!res) { + g_warning ("GsmConsoleKit: Calling GetSeatId failed."); + return; + } + + g_variant_get (res, "(o)", seat); + g_variant_unref (res); +} + +/* returns -1 on failure + * 0 seat is multi-session + * 1 seat is not multi-session + */ +static gint +ck_seat_can_multi_session (GsmConsolekit *manager, + const gchar *seat) +{ + GDBusConnection *bus; + GVariant *res; + gboolean can_activate; + + + bus = g_dbus_proxy_get_connection (manager->priv->ck_proxy); + res = g_dbus_connection_call_sync (bus, + CK_NAME, + seat, + CK_SEAT_INTERFACE, + "CanActivateSessions", + g_variant_new ("()"), + G_VARIANT_TYPE_BOOLEAN, + 0, + -1, + NULL, NULL); + if (!res) { + g_warning ("GsmConsoleKit: Calling GetSeatId failed."); + return -1; + } + + g_variant_get (res, "(b)", &can_activate); + g_variant_unref (res); + + return can_activate == TRUE ? 1 : 0; +} + +static gboolean +gsm_consolekit_can_switch_user (GsmSystem *system) +{ + GsmConsolekit *manager = GSM_CONSOLEKIT (system); + gchar *seat; + gint ret; + + ck_session_get_seat (manager, &seat); + ret = ck_seat_can_multi_session (manager, seat); + free (seat); + + return ret > 0; +} + +static gboolean +gsm_consolekit_can_restart (GsmSystem *system) +{ + GsmConsolekit *manager = GSM_CONSOLEKIT (system); + GVariant *res; + gboolean can_restart; + + res = g_dbus_proxy_call_sync (manager->priv->ck_proxy, + "CanRestart", + g_variant_new ("()"), + 0, + G_MAXINT, + NULL, + NULL); + if (!res) { + g_warning ("Calling CanRestart failed. Check that ConsoleKit is " + "properly installed."); + return FALSE; + } + + g_variant_get (res, "(b)", &can_restart); + g_variant_unref (res); + + return can_restart; +} + +static gboolean +gsm_consolekit_can_stop (GsmSystem *system) +{ + GsmConsolekit *manager = GSM_CONSOLEKIT (system); + GVariant *res; + gboolean can_stop; + + res = g_dbus_proxy_call_sync (manager->priv->ck_proxy, + "CanStop", + g_variant_new ("()"), + 0, + G_MAXINT, + NULL, + NULL); + if (!res) { + g_warning ("Calling CanStop failed. Check that ConsoleKit is " + "properly installed."); + return FALSE; + } + + g_variant_get (res, "(b)", &can_stop); + g_variant_unref (res); + + return can_stop; +} + +/* returns -1 on failure, 0 on success */ +static gint +ck_session_get_class (GsmConsolekit *manager, + gchar **session_class) +{ + GVariant *res; + + *session_class = NULL; + + res = g_dbus_proxy_call_sync (manager->priv->ck_session_proxy, + "GetSessionClass", + g_variant_new ("()"), + 0, + -1, + NULL, NULL); + if (!res) { + g_warning ("GsmConsoleKit: Calling GetSessionClass failed."); + return -1; + } + + g_variant_get (res, "(s)", session_class); + g_variant_unref (res); + + return 0; +} + +static gboolean +gsm_consolekit_is_login_session (GsmSystem *system) +{ + GsmConsolekit *manager = GSM_CONSOLEKIT (system); + int res; + gboolean ret; + gchar *session_class = NULL; + + ret = FALSE; + + if (manager->priv->session_id == NULL) { + return ret; + } + + res = ck_session_get_class (manager, &session_class); + if (res < 0) { + g_warning ("Could not get session class: %s", strerror (-res)); + return FALSE; + } + ret = (g_strcmp0 (session_class, "greeter") == 0); + g_free (session_class); + + return ret; +} + +static gboolean +gsm_consolekit_can_suspend (GsmSystem *system) +{ + GsmConsolekit *manager = GSM_CONSOLEKIT (system); + gchar *rv; + GVariant *res; + gboolean can_suspend; + + res = g_dbus_proxy_call_sync (manager->priv->ck_proxy, + "CanSuspend", + NULL, + 0, + G_MAXINT, + NULL, + NULL); + if (!res) { + g_warning ("Calling CanSuspend failed. Check that ConsoleKit is " + "properly installed."); + return FALSE; + } + + g_variant_get (res, "(s)", &rv); + g_variant_unref (res); + + can_suspend = g_strcmp0 (rv, "yes") == 0 || + g_strcmp0 (rv, "challenge") == 0; + + g_free (rv); + + return can_suspend; +} + +static gboolean +gsm_consolekit_can_hibernate (GsmSystem *system) +{ + GsmConsolekit *manager = GSM_CONSOLEKIT (system); + gchar *rv; + GVariant *res; + gboolean can_hibernate; + + res = g_dbus_proxy_call_sync (manager->priv->ck_proxy, + "CanHibernate", + NULL, + 0, + G_MAXINT, + NULL, + NULL); + if (!res) { + g_warning ("Calling CanHibernate failed. Check that ConsoleKit is " + "properly installed."); + return FALSE; + } + + g_variant_get (res, "(s)", &rv); + g_variant_unref (res); + + can_hibernate = g_strcmp0 (rv, "yes") == 0 || + g_strcmp0 (rv, "challenge") == 0; + + g_free (rv); + + return can_hibernate; +} + +static void +suspend_done (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GDBusProxy *proxy = G_DBUS_PROXY (source); + GError *error = NULL; + GVariant *res; + + res = g_dbus_proxy_call_finish (proxy, result, &error); + + if (!res) { + g_warning ("Unable to suspend system: %s", error->message); + g_error_free (error); + } else { + g_variant_unref (res); + } +} + +static void +hibernate_done (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GDBusProxy *proxy = G_DBUS_PROXY (source); + GError *error = NULL; + GVariant *res; + + res = g_dbus_proxy_call_finish (proxy, result, &error); + + if (!res) { + g_warning ("Unable to hibernate system: %s", error->message); + g_error_free (error); + } else { + g_variant_unref (res); + } +} + +static void +gsm_consolekit_suspend (GsmSystem *system) +{ + GsmConsolekit *manager = GSM_CONSOLEKIT (system); + + g_dbus_proxy_call (manager->priv->ck_proxy, + "Suspend", + g_variant_new ("(b)", TRUE), + 0, + G_MAXINT, + NULL, + suspend_done, + manager); +} + +static void +gsm_consolekit_hibernate (GsmSystem *system) +{ + GsmConsolekit *manager = GSM_CONSOLEKIT (system); + + g_dbus_proxy_call (manager->priv->ck_proxy, + "Hibernate", + g_variant_new ("(b)", TRUE), + 0, + G_MAXINT, + NULL, + hibernate_done, + manager); +} + +static void +inhibit_done (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GDBusProxy *proxy = G_DBUS_PROXY (source); + GsmConsolekit *manager = GSM_CONSOLEKIT (user_data); + GError *error = NULL; + GVariant *res; + GUnixFDList *fd_list = NULL; + gint idx; + + /* Drop any previous inhibit before recording the new one */ + drop_system_inhibitor (manager); + + res = g_dbus_proxy_call_with_unix_fd_list_finish (proxy, &fd_list, result, &error); + + if (!res) { + g_warning ("Unable to inhibit system: %s", error->message); + g_error_free (error); + } else { + g_variant_get (res, "(h)", &idx); + manager->priv->inhibit_fd = g_unix_fd_list_get (fd_list, idx, &error); + if (manager->priv->inhibit_fd == -1) { + g_warning ("Failed to receive system inhibitor fd: %s", error->message); + g_error_free (error); + } + g_debug ("System inhibitor fd is %d", manager->priv->inhibit_fd); + g_object_unref (fd_list); + g_variant_unref (res); + } + + /* Handle a race condition, where locks got unset during dbus call */ + if (manager->priv->inhibit_locks == NULL) { + drop_system_inhibitor (manager); + } +} + +static void +gsm_consolekit_set_inhibitors (GsmSystem *system, + GsmInhibitorFlag flags) +{ + GsmConsolekit *manager = GSM_CONSOLEKIT (system); + const gchar *locks = NULL; + gboolean inhibit_logout; + gboolean inhibit_suspend; + + inhibit_logout = (flags & GSM_INHIBITOR_FLAG_LOGOUT) != 0; + inhibit_suspend = (flags & GSM_INHIBITOR_FLAG_SUSPEND) != 0; + + if (inhibit_logout && inhibit_suspend) { + locks = "sleep:shutdown"; + } else if (inhibit_logout) { + locks = "shutdown"; + } else if (inhibit_suspend) { + locks = "sleep"; + } + manager->priv->inhibit_locks = locks; + + if (locks != NULL) { + g_debug ("Adding system inhibitor on %s", locks); + g_dbus_proxy_call_with_unix_fd_list (manager->priv->ck_proxy, + "Inhibit", + g_variant_new ("(ssss)", + locks, + g_get_user_name (), + "user session inhibited", + "block"), + 0, + G_MAXINT, + NULL, + NULL, + inhibit_done, + manager); + } else { + drop_system_inhibitor (manager); + } +} + +static void +reboot_or_poweroff_done (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + GsmConsolekit *consolekit = user_data; + GVariant *result; + GError *error = NULL; + + result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), + res, + &error); + + if (result == NULL) { + if (!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED)) { + g_warning ("Shutdown failed: %s", error->message); + } + g_error_free (error); + drop_delay_inhibitor (consolekit); + g_debug ("GsmConsolekit: shutdown preparation failed"); + consolekit->priv->prepare_for_shutdown_expected = FALSE; + g_signal_emit_by_name (consolekit, "shutdown-prepared", FALSE); + } else { + g_variant_unref (result); + } +} + +static void +gsm_consolekit_prepare_shutdown (GsmSystem *system, + gboolean restart) +{ + GsmConsolekit *consolekit = GSM_CONSOLEKIT (system); + GUnixFDList *fd_list; + GVariant *res; + GError *error = NULL; + gint idx; + + g_debug ("GsmConsolekit: prepare shutdown"); + + res = g_dbus_proxy_call_with_unix_fd_list_sync (consolekit->priv->ck_proxy, + "Inhibit", + g_variant_new ("(ssss)", + "shutdown", + g_get_user_name (), + "Preparing to end the session", + "delay"), + 0, + G_MAXINT, + NULL, + &fd_list, + NULL, + &error); + if (res == NULL) { + g_warning ("Failed to get delay inhibitor: %s", error->message); + g_error_free (error); + /* We may fail here with CK and that's ok */ + } else { + g_variant_get (res, "(h)", &idx); + + consolekit->priv->delay_inhibit_fd = g_unix_fd_list_get (fd_list, idx, NULL); + + g_debug ("GsmConsolekit: got delay inhibitor, fd = %d", consolekit->priv->delay_inhibit_fd); + + g_variant_unref (res); + g_object_unref (fd_list); + } + + consolekit->priv->prepare_for_shutdown_expected = TRUE; + + g_dbus_proxy_call (consolekit->priv->ck_proxy, + restart ? "Reboot" : "PowerOff", + g_variant_new ("(b)", TRUE), + 0, + G_MAXINT, + NULL, + reboot_or_poweroff_done, + consolekit); +} + +static void +gsm_consolekit_complete_shutdown (GsmSystem *system) +{ + GsmConsolekit *consolekit = GSM_CONSOLEKIT (system); + + /* remove delay inhibitor, if any */ + drop_delay_inhibitor (consolekit); +} + +static gboolean +gsm_consolekit_is_last_session_for_user (GsmSystem *system) +{ + return FALSE; +} + +static void +gsm_consolekit_system_init (GsmSystemInterface *iface) +{ + iface->can_switch_user = gsm_consolekit_can_switch_user; + iface->can_stop = gsm_consolekit_can_stop; + iface->can_restart = gsm_consolekit_can_restart; + iface->can_suspend = gsm_consolekit_can_suspend; + iface->can_hibernate = gsm_consolekit_can_hibernate; + iface->attempt_stop = gsm_consolekit_attempt_stop; + iface->attempt_restart = gsm_consolekit_attempt_restart; + iface->suspend = gsm_consolekit_suspend; + iface->hibernate = gsm_consolekit_hibernate; + iface->set_session_idle = gsm_consolekit_set_session_idle; + iface->is_login_session = gsm_consolekit_is_login_session; + iface->set_inhibitors = gsm_consolekit_set_inhibitors; + iface->prepare_shutdown = gsm_consolekit_prepare_shutdown; + iface->complete_shutdown = gsm_consolekit_complete_shutdown; + iface->is_last_session_for_user = gsm_consolekit_is_last_session_for_user; +} + +GsmConsolekit * +gsm_consolekit_new (void) +{ + GsmConsolekit *manager; + + manager = g_object_new (GSM_TYPE_CONSOLEKIT, NULL); + + return manager; +} + +static void +ck_proxy_signal_cb (GDBusProxy *proxy, + const gchar *sender_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + GsmConsolekit *consolekit = user_data; + gboolean is_about_to_shutdown; + + g_debug ("GsmConsolekit: received ConsoleKit signal: %s", signal_name); + + if (g_strcmp0 (signal_name, "PrepareForShutdown") != 0) { + g_debug ("GsmConsolekit: ignoring %s signal", signal_name); + return; + } + + g_variant_get (parameters, "(b)", &is_about_to_shutdown); + if (!is_about_to_shutdown) { + g_debug ("GsmConsolekit: ignoring %s signal since about-to-shutdown is FALSE", signal_name); + return; + } + + if (consolekit->priv->prepare_for_shutdown_expected) { + g_debug ("GsmConsolekit: shutdown successfully prepared"); + g_signal_emit_by_name (consolekit, "shutdown-prepared", TRUE); + consolekit->priv->prepare_for_shutdown_expected = FALSE; + } +} + +static void +ck_session_proxy_signal_cb (GDBusProxy *proxy, + const gchar *sender_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + GsmConsolekit *consolekit = user_data; + gboolean is_active; + + g_debug ("GsmConsolekit: received ConsoleKit signal: %s", signal_name); + + if (g_strcmp0 (signal_name, "ActiveChanged") != 0) { + g_debug ("GsmConsolekit: ignoring %s signal", signal_name); + return; + } + + g_variant_get (parameters, "(b)", &is_active); + if (consolekit->priv->is_active != is_active) { + g_debug ("GsmConsolekit: session state changed"); + consolekit->priv->is_active = is_active; + g_object_notify (G_OBJECT (consolekit), "active"); + } +} diff --git a/gnome-session/gsm-consolekit.h b/gnome-session/gsm-consolekit.h new file mode 100644 index 0000000..963a9fd --- /dev/null +++ b/gnome-session/gsm-consolekit.h @@ -0,0 +1,59 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Jon McCann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * Authors: + * Jon McCann + */ + +#ifndef __GSM_CONSOLEKIT_H__ +#define __GSM_CONSOLEKIT_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GSM_TYPE_CONSOLEKIT (gsm_consolekit_get_type ()) +#define GSM_CONSOLEKIT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_CONSOLEKIT, GsmConsolekit)) +#define GSM_CONSOLEKIT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_CONSOLEKIT, GsmConsolekitClass)) +#define GSM_IS_CONSOLEKIT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_CONSOLEKIT)) +#define GSM_IS_CONSOLEKIT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_CONSOLEKIT)) +#define GSM_CONSOLEKIT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GSM_TYPE_CONSOLEKIT, GsmConsolekitClass)) + +typedef struct _GsmConsolekit GsmConsolekit; +typedef struct _GsmConsolekitClass GsmConsolekitClass; +typedef struct _GsmConsolekitPrivate GsmConsolekitPrivate; + +struct _GsmConsolekit +{ + GObject parent; + + GsmConsolekitPrivate *priv; +}; + +struct _GsmConsolekitClass +{ + GObjectClass parent_class; +}; + +GType gsm_consolekit_get_type (void); + +GsmConsolekit *gsm_consolekit_new (void) G_GNUC_MALLOC; + +G_END_DECLS + +#endif /* __GSM_CONSOLEKIT_H__ */ diff --git a/gnome-session/gsm-dbus-client.c b/gnome-session/gsm-dbus-client.c new file mode 100644 index 0000000..cf3b1f1 --- /dev/null +++ b/gnome-session/gsm-dbus-client.c @@ -0,0 +1,441 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "org.gnome.SessionManager.ClientPrivate.h" +#include "gsm-dbus-client.h" + +#include "gsm-manager.h" +#include "gsm-util.h" + +#define SM_DBUS_NAME "org.gnome.SessionManager" +#define SM_DBUS_CLIENT_PRIVATE_INTERFACE "org.gnome.SessionManager.ClientPrivate" + +struct _GsmDBusClient +{ + GObject parent_instance; + + char *bus_name; + GPid caller_pid; + GsmClientRestartStyle restart_style_hint; + + GDBusConnection *connection; + GsmExportedClientPrivate *skeleton; + guint watch_id; +}; + +enum { + PROP_0, + PROP_BUS_NAME +}; + +G_DEFINE_TYPE (GsmDBusClient, gsm_dbus_client, GSM_TYPE_CLIENT) + +static gboolean +setup_connection (GsmDBusClient *client) +{ + GError *error = NULL; + + if (client->connection == NULL) { + client->connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + if (error != NULL) { + g_debug ("GsmDbusClient: Couldn't connect to session bus: %s", + error->message); + g_error_free (error); + return FALSE; + } + } + + return TRUE; +} + +static gboolean +handle_end_session_response (GsmExportedClientPrivate *skeleton, + GDBusMethodInvocation *invocation, + gboolean is_ok, + const char *reason, + GsmDBusClient *client) +{ + g_debug ("GsmDBusClient: got EndSessionResponse is-ok:%d reason=%s", is_ok, reason); + gsm_client_end_session_response (GSM_CLIENT (client), + is_ok, FALSE, FALSE, reason); + + gsm_exported_client_private_complete_end_session_response (skeleton, invocation); + return TRUE; +} + +static GObject * +gsm_dbus_client_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GsmDBusClient *client; + GError *error = NULL; + GsmExportedClientPrivate *skeleton; + + client = GSM_DBUS_CLIENT (G_OBJECT_CLASS (gsm_dbus_client_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + + if (! setup_connection (client)) { + g_object_unref (client); + return NULL; + } + + skeleton = gsm_exported_client_private_skeleton_new (); + client->skeleton = skeleton; + g_debug ("exporting dbus client to object path: %s", gsm_client_peek_id (GSM_CLIENT (client))); + g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (skeleton), + client->connection, + gsm_client_peek_id (GSM_CLIENT (client)), + &error); + + if (error != NULL) { + g_critical ("error exporting client private on session bus: %s", error->message); + g_error_free (error); + g_object_unref (client); + return NULL; + } + + g_signal_connect (skeleton, "handle-end-session-response", + G_CALLBACK (handle_end_session_response), client); + + return G_OBJECT (client); +} + +static void +gsm_dbus_client_init (GsmDBusClient *client) +{ +} + +/* adapted from PolicyKit */ +static gboolean +get_caller_info (GsmDBusClient *client, + const char *sender, + uid_t *calling_uid_out, + pid_t *calling_pid_out) +{ + g_autoptr(GDBusConnection) connection = NULL; + GError *error; + g_autoptr(GVariant) uid_variant = NULL; + g_autoptr(GVariant) pid_variant = NULL; + uid_t uid; + pid_t pid; + + if (sender == NULL) { + return FALSE; + } + + error = NULL; + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + + if (error != NULL) { + g_warning ("error getting session bus: %s", error->message); + g_error_free (error); + return FALSE; + } + + uid_variant = g_dbus_connection_call_sync (connection, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "GetConnectionUnixUser", + g_variant_new ("(s)", sender), + G_VARIANT_TYPE ("(u)"), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, &error); + + if (error != NULL) { + g_debug ("GetConnectionUnixUser() failed: %s", error->message); + g_error_free (error); + return FALSE; + } + + pid_variant = g_dbus_connection_call_sync (connection, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "GetConnectionUnixProcessID", + g_variant_new ("(s)", sender), + G_VARIANT_TYPE ("(u)"), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, &error); + + if (error != NULL) { + g_debug ("GetConnectionUnixProcessID() failed: %s", error->message); + g_error_free (error); + return FALSE; + } + + g_variant_get (uid_variant, "(u)", &uid); + g_variant_get (pid_variant, "(u)", &pid); + + if (calling_uid_out != NULL) { + *calling_uid_out = uid; + } + if (calling_pid_out != NULL) { + *calling_pid_out = pid; + } + + g_debug ("uid = %d", uid); + g_debug ("pid = %d", pid); + + return TRUE; +} + +static void +on_client_vanished (GDBusConnection *connection, + const char *name, + gpointer user_data) +{ + GsmDBusClient *client = user_data; + + g_bus_unwatch_name (client->watch_id); + client->watch_id = 0; + + gsm_client_disconnected (GSM_CLIENT (client)); +} + +static void +gsm_dbus_client_set_bus_name (GsmDBusClient *client, + const char *bus_name) +{ + g_return_if_fail (GSM_IS_DBUS_CLIENT (client)); + + g_free (client->bus_name); + + client->bus_name = g_strdup (bus_name); + g_object_notify (G_OBJECT (client), "bus-name"); + + if (!get_caller_info (client, bus_name, NULL, &client->caller_pid)) { + client->caller_pid = 0; + } + + client->watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION, + bus_name, + G_BUS_NAME_WATCHER_FLAGS_NONE, + NULL, + on_client_vanished, + client, + NULL); +} + +const char * +gsm_dbus_client_get_bus_name (GsmDBusClient *client) +{ + g_return_val_if_fail (GSM_IS_DBUS_CLIENT (client), NULL); + + return client->bus_name; +} + +static void +gsm_dbus_client_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GsmDBusClient *self; + + self = GSM_DBUS_CLIENT (object); + + switch (prop_id) { + case PROP_BUS_NAME: + gsm_dbus_client_set_bus_name (self, g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gsm_dbus_client_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GsmDBusClient *self; + + self = GSM_DBUS_CLIENT (object); + + switch (prop_id) { + case PROP_BUS_NAME: + g_value_set_string (value, self->bus_name); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gsm_dbus_client_finalize (GObject *object) +{ + GsmDBusClient *client = (GsmDBusClient *) object; + + g_free (client->bus_name); + + if (client->skeleton != NULL) { + g_dbus_interface_skeleton_unexport_from_connection (G_DBUS_INTERFACE_SKELETON (client->skeleton), + client->connection); + g_clear_object (&client->skeleton); + } + + g_clear_object (&client->connection); + + if (client->watch_id != 0) + g_bus_unwatch_name (client->watch_id); + + G_OBJECT_CLASS (gsm_dbus_client_parent_class)->finalize (object); +} + +static GKeyFile * +dbus_client_save (GsmClient *client, + GsmApp *app, + GError **error) +{ + g_debug ("GsmDBusClient: saving client with id %s", + gsm_client_peek_id (client)); + + /* FIXME: We still don't support client saving for D-Bus + * session clients */ + + return NULL; +} + +static gboolean +dbus_client_stop (GsmClient *client, + GError **error) +{ + GsmDBusClient *dbus_client = (GsmDBusClient *) client; + gsm_exported_client_private_emit_stop (dbus_client->skeleton); + return TRUE; +} + +static char * +dbus_client_get_app_name (GsmClient *client) +{ + /* Always use app-id instead */ + return NULL; +} + +static GsmClientRestartStyle +dbus_client_get_restart_style_hint (GsmClient *client) +{ + return (GSM_DBUS_CLIENT (client)->restart_style_hint); +} + +static guint +dbus_client_get_unix_process_id (GsmClient *client) +{ + return (GSM_DBUS_CLIENT (client)->caller_pid); +} + +static gboolean +dbus_client_query_end_session (GsmClient *client, + GsmClientEndSessionFlag flags, + GError **error) +{ + GsmDBusClient *dbus_client = (GsmDBusClient *) client; + + if (dbus_client->bus_name == NULL) { + g_set_error (error, + GSM_CLIENT_ERROR, + GSM_CLIENT_ERROR_NOT_REGISTERED, + "Client is not registered"); + return FALSE; + } + + g_debug ("GsmDBusClient: sending QueryEndSession signal to %s", dbus_client->bus_name); + + gsm_exported_client_private_emit_query_end_session (dbus_client->skeleton, flags); + return TRUE; +} + +static gboolean +dbus_client_end_session (GsmClient *client, + GsmClientEndSessionFlag flags, + GError **error) +{ + GsmDBusClient *dbus_client = (GsmDBusClient *) client; + + gsm_exported_client_private_emit_end_session (dbus_client->skeleton, flags); + return TRUE; +} + +static gboolean +dbus_client_cancel_end_session (GsmClient *client, + GError **error) +{ + GsmDBusClient *dbus_client = (GsmDBusClient *) client; + gsm_exported_client_private_emit_cancel_end_session (dbus_client->skeleton); + return TRUE; +} + +static void +gsm_dbus_client_class_init (GsmDBusClientClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GsmClientClass *client_class = GSM_CLIENT_CLASS (klass); + + object_class->finalize = gsm_dbus_client_finalize; + object_class->constructor = gsm_dbus_client_constructor; + object_class->get_property = gsm_dbus_client_get_property; + object_class->set_property = gsm_dbus_client_set_property; + + client_class->impl_save = dbus_client_save; + client_class->impl_stop = dbus_client_stop; + client_class->impl_query_end_session = dbus_client_query_end_session; + client_class->impl_end_session = dbus_client_end_session; + client_class->impl_cancel_end_session = dbus_client_cancel_end_session; + client_class->impl_get_app_name = dbus_client_get_app_name; + client_class->impl_get_restart_style_hint = dbus_client_get_restart_style_hint; + client_class->impl_get_unix_process_id = dbus_client_get_unix_process_id; + + g_object_class_install_property (object_class, + PROP_BUS_NAME, + g_param_spec_string ("bus-name", + "bus-name", + "bus-name", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); +} + +GsmClient * +gsm_dbus_client_new (const char *startup_id, + const char *bus_name) +{ + GsmDBusClient *client; + + client = g_object_new (GSM_TYPE_DBUS_CLIENT, + "startup-id", startup_id, + "bus-name", bus_name, + NULL); + + return GSM_CLIENT (client); +} diff --git a/gnome-session/gsm-dbus-client.h b/gnome-session/gsm-dbus-client.h new file mode 100644 index 0000000..f36853b --- /dev/null +++ b/gnome-session/gsm-dbus-client.h @@ -0,0 +1,35 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef __GSM_DBUS_CLIENT_H__ +#define __GSM_DBUS_CLIENT_H__ + +#include "gsm-client.h" + +G_BEGIN_DECLS + +#define GSM_TYPE_DBUS_CLIENT (gsm_dbus_client_get_type ()) +G_DECLARE_FINAL_TYPE (GsmDBusClient, gsm_dbus_client, GSM, DBUS_CLIENT, GsmClient) + +GsmClient * gsm_dbus_client_new (const char *startup_id, + const char *bus_name); +const char * gsm_dbus_client_get_bus_name (GsmDBusClient *client); + +G_END_DECLS + +#endif /* __GSM_DBUS_CLIENT_H__ */ diff --git a/gnome-session/gsm-fail-whale-dialog.c b/gnome-session/gsm-fail-whale-dialog.c new file mode 100644 index 0000000..d6e8407 --- /dev/null +++ b/gnome-session/gsm-fail-whale-dialog.c @@ -0,0 +1,461 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2011 Red Hat, Inc. + * Copyright (C) 2019 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * Authors: + * Colin Walters + * Marco Trevisan + */ + +#include + +#include + +#include + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "gsm-fail-whale-dialog.h" + +#include "gsm-icon-names.h" + +struct _GsmFailWhaleDialog +{ + GtkWindow parent; + + gboolean debug_mode; + gboolean allow_logout; + gboolean extensions; + GdkMonitor *monitor; + GdkRectangle geometry; +}; + +G_DEFINE_TYPE (GsmFailWhaleDialog, gsm_fail_whale_dialog, GTK_TYPE_WINDOW); + +/* derived from tomboy */ +static void +_window_override_user_time (GsmFailWhaleDialog *window) +{ + guint32 ev_time = gtk_get_current_event_time (); + GdkWindow *gdk_window = gtk_widget_get_window (GTK_WIDGET (window)); + +#ifdef GDK_WINDOWING_X11 + if (!GDK_IS_X11_WINDOW (gdk_window)) + return; + + if (ev_time == 0) { + gint ev_mask = gtk_widget_get_events (GTK_WIDGET (window)); + if (!(ev_mask & GDK_PROPERTY_CHANGE_MASK)) { + gtk_widget_add_events (GTK_WIDGET (window), + GDK_PROPERTY_CHANGE_MASK); + } + + /* + * NOTE: Last resort for D-BUS or other non-interactive + * openings. Causes roundtrip to server. Lame. + */ + ev_time = gdk_x11_get_server_time (gdk_window); + } + + gdk_x11_window_set_user_time (gdk_window, ev_time); +#endif +} + +static void +_window_move_resize_window (GsmFailWhaleDialog *window, + gboolean move, + gboolean resize) +{ + if (window->debug_mode) + return; + + g_debug ("Move and/or resize window x=%d y=%d w=%d h=%d", + window->geometry.x, + window->geometry.y, + window->geometry.width, + window->geometry.height); + + if (resize) { + gtk_window_resize (GTK_WINDOW (window), + window->geometry.width, + window->geometry.height); + } + + if (move) { + gtk_window_move (GTK_WINDOW (window), + window->geometry.x, + window->geometry.y); + } +} + +static void +update_geometry (GsmFailWhaleDialog *fail_dialog) +{ + gdk_monitor_get_geometry (fail_dialog->monitor, &fail_dialog->geometry); +} + +static void +on_screen_size_changed (GdkScreen *screen, + GsmFailWhaleDialog *fail_dialog) +{ + gtk_widget_queue_resize (GTK_WIDGET (fail_dialog)); +} + +static void +gsm_fail_whale_dialog_realize (GtkWidget *widget) +{ + if (GTK_WIDGET_CLASS (gsm_fail_whale_dialog_parent_class)->realize) { + GTK_WIDGET_CLASS (gsm_fail_whale_dialog_parent_class)->realize (widget); + } + + _window_override_user_time (GSM_FAIL_WHALE_DIALOG (widget)); + update_geometry (GSM_FAIL_WHALE_DIALOG (widget)); + _window_move_resize_window (GSM_FAIL_WHALE_DIALOG (widget), TRUE, TRUE); + + g_signal_connect (gtk_window_get_screen (GTK_WINDOW (widget)), + "size_changed", + G_CALLBACK (on_screen_size_changed), + widget); +} + +static void +gsm_fail_whale_dialog_unrealize (GtkWidget *widget) +{ + g_signal_handlers_disconnect_by_func (gtk_window_get_screen (GTK_WINDOW (widget)), + on_screen_size_changed, + widget); + + if (GTK_WIDGET_CLASS (gsm_fail_whale_dialog_parent_class)->unrealize) { + GTK_WIDGET_CLASS (gsm_fail_whale_dialog_parent_class)->unrealize (widget); + } +} + +static void +gsm_fail_whale_dialog_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + GsmFailWhaleDialog *fail_dialog; + GdkRectangle old_geometry; + int position_changed = FALSE; + int size_changed = FALSE; + + fail_dialog = GSM_FAIL_WHALE_DIALOG (widget); + + old_geometry = fail_dialog->geometry; + + update_geometry (fail_dialog); + + requisition->width = fail_dialog->geometry.width; + requisition->height = fail_dialog->geometry.height; + + if (!gtk_widget_get_realized (widget)) { + return; + } + + if (old_geometry.width != fail_dialog->geometry.width || + old_geometry.height != fail_dialog->geometry.height) { + size_changed = TRUE; + } + + if (old_geometry.x != fail_dialog->geometry.x || + old_geometry.y != fail_dialog->geometry.y) { + position_changed = TRUE; + } + + _window_move_resize_window (fail_dialog, + position_changed, size_changed); +} + +static void +gsm_fail_whale_dialog_get_preferred_width (GtkWidget *widget, + gint *minimal_width, + gint *natural_width) +{ + GtkRequisition requisition; + + gsm_fail_whale_dialog_size_request (widget, &requisition); + + *minimal_width = *natural_width = requisition.width; +} + +static void +gsm_fail_whale_dialog_get_preferred_width_for_height (GtkWidget *widget, + gint for_height, + gint *minimal_width, + gint *natural_width) +{ + GtkRequisition requisition; + + gsm_fail_whale_dialog_size_request (widget, &requisition); + + *minimal_width = *natural_width = requisition.width; +} + +static void +gsm_fail_whale_dialog_get_preferred_height (GtkWidget *widget, + gint *minimal_height, + gint *natural_height) +{ + GtkRequisition requisition; + + gsm_fail_whale_dialog_size_request (widget, &requisition); + + *minimal_height = *natural_height = requisition.height; +} + +static void +gsm_fail_whale_dialog_get_preferred_height_for_width (GtkWidget *widget, + gint for_width, + gint *minimal_height, + gint *natural_height) +{ + GtkRequisition requisition; + + gsm_fail_whale_dialog_size_request (widget, &requisition); + + *minimal_height = *natural_height = requisition.height; +} + +static void +gsm_fail_whale_dialog_class_init (GsmFailWhaleDialogClass *klass) +{ + GtkWidgetClass *widget_class; + + widget_class = GTK_WIDGET_CLASS (klass); + + widget_class->realize = gsm_fail_whale_dialog_realize; + widget_class->unrealize = gsm_fail_whale_dialog_unrealize; + widget_class->get_preferred_width = gsm_fail_whale_dialog_get_preferred_width; + widget_class->get_preferred_height = gsm_fail_whale_dialog_get_preferred_height; + widget_class->get_preferred_width_for_height = gsm_fail_whale_dialog_get_preferred_width_for_height; + widget_class->get_preferred_height_for_width = gsm_fail_whale_dialog_get_preferred_height_for_width; +} + +static void +on_logout_clicked (GtkWidget *button, + GsmFailWhaleDialog *fail_dialog) +{ + if (!fail_dialog->debug_mode) { + g_spawn_command_line_async ("gnome-session-quit --force", NULL); + } + gtk_main_quit (); +} + +static void +setup_window (GsmFailWhaleDialog *fail_dialog) +{ + GtkWidget *box; + GtkWidget *image; + GtkWidget *label; + GtkWidget *message_label; + GtkWidget *button_box; + GtkWidget *button; + GdkPixbuf *fail_icon; + GdkDisplay *display; + char *markup; + int scale_factor; + int i; + + gtk_window_set_title (GTK_WINDOW (fail_dialog), ""); + gtk_window_set_icon_name (GTK_WINDOW (fail_dialog), GSM_ICON_COMPUTER_FAIL); + + gtk_window_set_skip_taskbar_hint (GTK_WINDOW (fail_dialog), TRUE); + gtk_window_set_keep_above (GTK_WINDOW (fail_dialog), TRUE); + gtk_window_stick (GTK_WINDOW (fail_dialog)); + gtk_window_set_position (GTK_WINDOW (fail_dialog), GTK_WIN_POS_CENTER_ALWAYS); + + /* only works if there is a window manager which is unlikely */ + display = gtk_widget_get_display (GTK_WIDGET (fail_dialog)); + for (i = 0; i < gdk_display_get_n_monitors (display); i++) { + if (gdk_display_get_monitor (display, i) == fail_dialog->monitor) { + GdkScreen *screen; + + screen = gtk_widget_get_screen (GTK_WIDGET (fail_dialog)); + gtk_window_fullscreen_on_monitor (GTK_WINDOW (fail_dialog), + screen, i); + break; + } + } + + box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10); + gtk_widget_set_valign (box, GTK_ALIGN_CENTER); + gtk_widget_show (box); + + gtk_container_add (GTK_CONTAINER (fail_dialog), box); + + scale_factor = gdk_monitor_get_scale_factor (fail_dialog->monitor); + fail_icon = gtk_icon_theme_load_icon_for_scale (gtk_icon_theme_get_default (), + GSM_ICON_COMPUTER_FAIL, + 128, + scale_factor, + 0, + NULL); + if (fail_icon != NULL) { + image = gtk_image_new_from_pixbuf (fail_icon); + gtk_widget_show (image); + gtk_box_pack_start (GTK_BOX (box), image, FALSE, FALSE, 0); + g_object_unref (fail_icon); + } + + label = gtk_label_new (NULL); + markup = g_strdup_printf ("%s", _("Oh no! Something has gone wrong.")); + gtk_label_set_markup (GTK_LABEL (label), markup); + g_free (markup); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0); + + if (!fail_dialog->allow_logout) + message_label = gtk_label_new (_("A problem has occurred and the system can’t recover. Please contact a system administrator")); + else if (fail_dialog->extensions) + message_label = gtk_label_new (_("A problem has occurred and the system can’t recover. All extensions have been disabled as a precaution.")); + else + message_label = gtk_label_new (_("A problem has occurred and the system can’t recover.\nPlease log out and try again.")); + + gtk_label_set_justify (GTK_LABEL (message_label), GTK_JUSTIFY_CENTER); + gtk_label_set_line_wrap (GTK_LABEL (message_label), TRUE); + gtk_widget_show (message_label); + gtk_box_pack_start (GTK_BOX (box), + message_label, FALSE, FALSE, 0); + + button_box = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL); + gtk_container_set_border_width (GTK_CONTAINER (button_box), 20); + gtk_widget_show (button_box); + gtk_box_pack_end (GTK_BOX (box), + button_box, FALSE, FALSE, 0); + + if (fail_dialog->allow_logout) { + button = gtk_button_new_with_mnemonic (_("_Log Out")); + gtk_widget_show (button); + gtk_box_pack_end (GTK_BOX (button_box), + button, FALSE, FALSE, 0); + g_signal_connect (button, "clicked", + G_CALLBACK (on_logout_clicked), fail_dialog); + } +} + +static void +gsm_fail_whale_dialog_init (GsmFailWhaleDialog *fail_dialog) +{ +} + +static gboolean debug_mode = FALSE; +static gboolean allow_logout = FALSE; +static gboolean extensions = FALSE; +static GList *dialogs = NULL; + +static void +create_fail_dialog (GdkMonitor *monitor) +{ + GsmFailWhaleDialog *fail_dialog; + + fail_dialog = g_object_new (GSM_TYPE_FAIL_WHALE_DIALOG, NULL); + fail_dialog->debug_mode = debug_mode; + fail_dialog->allow_logout = allow_logout; + fail_dialog->extensions = extensions; + fail_dialog->monitor = monitor; + + setup_window (fail_dialog); + + g_signal_connect (fail_dialog, "destroy", + G_CALLBACK (gtk_main_quit), NULL); + + gtk_widget_show (GTK_WIDGET (fail_dialog)); + + dialogs = g_list_prepend (dialogs, fail_dialog); +} + +static void +on_monitor_added (GdkDisplay *display, + GdkMonitor *monitor) +{ + create_fail_dialog (monitor); +} + +static void +on_monitor_removed (GdkDisplay *display, + GdkMonitor *monitor) +{ + GList *l; + + for (l = dialogs; l;) { + GList *next = l->next; + GsmFailWhaleDialog *fail_dialog = l->data; + + if (fail_dialog->monitor == monitor) { + dialogs = g_list_delete_link (dialogs, l); + gtk_widget_destroy (GTK_WIDGET (fail_dialog)); + } + l = next; + } +} + +int main (int argc, char *argv[]) +{ + GOptionEntry entries[] = { + { "debug", 0, 0, G_OPTION_ARG_NONE, &debug_mode, N_("Enable debugging code"), NULL }, + { "allow-logout", 0, 0, G_OPTION_ARG_NONE, &allow_logout, N_("Allow logout"), NULL }, + { "extensions", 0, 0, G_OPTION_ARG_NONE, &extensions, N_("Show extension warning"), NULL }, + { NULL, 0, 0, 0, NULL, NULL, NULL } + }; + + GError *error = NULL; + GdkDisplay *display; + int i; + + bindtextdomain (GETTEXT_PACKAGE, LOCALE_DIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + if (!gtk_init_with_args (&argc, &argv, " - fail whale", + entries, GETTEXT_PACKAGE, + &error)) { + if (error != NULL) { + g_warning ("%s", error->message); + exit (1); + } + + /* display server probably went away. Could be for legitimate reasons, could be for + * unexpected reasons. If it went away unexpectantly, that's logged elsewhere, so + * let's not add noise by logging here. + */ + return 0; + } + + /* Force-off allow_logout when running inside GDM, this is needed + * because the systemd service always passes --allow-logout + */ + if (g_strcmp0 (g_getenv ("RUNNING_UNDER_GDM"), "true") == 0) + allow_logout = FALSE; + + display = gdk_display_get_default (); + for (i = 0; i < gdk_display_get_n_monitors (display); i++) { + create_fail_dialog (gdk_display_get_monitor (display, i)); + } + + g_signal_connect (display, "monitor-added", + G_CALLBACK (on_monitor_added), &dialogs); + g_signal_connect (display, "monitor-removed", + G_CALLBACK (on_monitor_removed), &dialogs); + + gtk_main (); + + return 0; +} + diff --git a/gnome-session/gsm-fail-whale-dialog.h b/gnome-session/gsm-fail-whale-dialog.h new file mode 100644 index 0000000..d91028a --- /dev/null +++ b/gnome-session/gsm-fail-whale-dialog.h @@ -0,0 +1,36 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2011 Red Hat, Inc. + * Copyright (C) 2019 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * Authors: + * Colin Walters + * Marco Trevisan + */ + +#ifndef __GSM_FAIL_WHALE_DIALOG_H__ +#define __GSM_FAIL_WHALE_DIALOG_H__ + +#include + +G_BEGIN_DECLS + +#define GSM_TYPE_FAIL_WHALE_DIALOG (gsm_fail_whale_dialog_get_type ()) +G_DECLARE_FINAL_TYPE (GsmFailWhaleDialog, gsm_fail_whale_dialog, GSM, FAIL_WHALE_DIALOG, GtkWindow); + +G_END_DECLS + +#endif /* __GSM_FAIL_WHALE_DIALOG_H__ */ diff --git a/gnome-session/gsm-fail-whale.c b/gnome-session/gsm-fail-whale.c new file mode 100644 index 0000000..6ec0663 --- /dev/null +++ b/gnome-session/gsm-fail-whale.c @@ -0,0 +1,66 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * gsm-fail-whale.c + * Copyright (C) 2012 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include + +#include +#include + +#include +#include + +#include "gsm-fail-whale.h" +#include "gsm-util.h" + +static void +on_fail_whale_failed (void) +{ + raise (SIGTERM); +} + +void +gsm_fail_whale_dialog_we_failed (gboolean debug_mode, + gboolean allow_logout, + GsmShellExtensions *extensions) +{ + gint i; + gchar *argv[5]; + static GPid pid = 0; + + if (pid != 0) { + return; + } + + i = 0; + argv[i++] = LIBEXECDIR "/gnome-session-failed"; + if (debug_mode) + argv[i++] = "--debug"; + if (allow_logout) + argv[i++] = "--allow-logout"; + if (extensions != NULL && gsm_shell_extensions_n_extensions (extensions) > 0) + argv[i++] = "--extensions"; + argv[i++] = NULL; + + if (!g_spawn_async (NULL, argv, (char **) gsm_util_listenv (), G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid, NULL)) { + exit (1); + } + + g_child_watch_add (pid, + (GChildWatchFunc)on_fail_whale_failed, + NULL); +} diff --git a/gnome-session/gsm-fail-whale.h b/gnome-session/gsm-fail-whale.h new file mode 100644 index 0000000..b481e3c --- /dev/null +++ b/gnome-session/gsm-fail-whale.h @@ -0,0 +1,34 @@ +/* gsm-fail-whale.h + * Copyright (C) 2012 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef __GSM_FAIL_WHALE_H___ +#define __GSM_FAIL_WHALE_H__ + +#include + +#include "gsm-shell-extensions.h" + +G_BEGIN_DECLS + +void gsm_fail_whale_dialog_we_failed (gboolean debug_mode, + gboolean allow_logout, + GsmShellExtensions *extensions); + +G_END_DECLS + +#endif /* __GSM_FAIL_WHALE_H__ */ + diff --git a/gnome-session/gsm-icon-names.h b/gnome-session/gsm-icon-names.h new file mode 100644 index 0000000..cca051b --- /dev/null +++ b/gnome-session/gsm-icon-names.h @@ -0,0 +1,28 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2011 Novell, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef __GSM_ICON_NAMES_H__ +#define __GSM_ICON_NAMES_H__ + +#define GSM_ICON_COMPUTER_FAIL "computer-fail-symbolic" +#define GSM_ICON_INHIBITOR_DEFAULT "gnome-windows" +#define GSM_ICON_LOGOUT "system-log-out" +#define GSM_ICON_SHUTDOWN "system-shutdown" +#define GSM_ICON_XSMP_DEFAULT "system-run" + +#endif /*__GSM_ICON_NAMES_H__ */ diff --git a/gnome-session/gsm-inhibitor-flag.h b/gnome-session/gsm-inhibitor-flag.h new file mode 100644 index 0000000..40698f9 --- /dev/null +++ b/gnome-session/gsm-inhibitor-flag.h @@ -0,0 +1,36 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef __GSM_INHIBITOR_FLAG_H__ +#define __GSM_INHIBITOR_FLAG_H__ + +#include + +G_BEGIN_DECLS + +typedef enum { + GSM_INHIBITOR_FLAG_LOGOUT = 1 << 0, + GSM_INHIBITOR_FLAG_SWITCH_USER = 1 << 1, + GSM_INHIBITOR_FLAG_SUSPEND = 1 << 2, + GSM_INHIBITOR_FLAG_IDLE = 1 << 3, + GSM_INHIBITOR_FLAG_AUTOMOUNT = 1 << 4 +} GsmInhibitorFlag; + +G_END_DECLS + +#endif /* __GSM_INHIBITOR_FLAG_H__ */ diff --git a/gnome-session/gsm-inhibitor.c b/gnome-session/gsm-inhibitor.c new file mode 100644 index 0000000..a9a1f42 --- /dev/null +++ b/gnome-session/gsm-inhibitor.c @@ -0,0 +1,649 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "gsm-inhibitor.h" +#include "org.gnome.SessionManager.Inhibitor.h" + +#include "gsm-util.h" + +static guint32 inhibitor_serial = 1; + +#define GSM_INHIBITOR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSM_TYPE_INHIBITOR, GsmInhibitorPrivate)) + +struct GsmInhibitorPrivate +{ + char *id; + char *bus_name; + char *app_id; + char *client_id; + char *reason; + guint flags; + guint toplevel_xid; + guint cookie; + GDBusConnection *connection; + GsmExportedInhibitor *skeleton; + + guint watch_id; +}; + +enum { + PROP_0, + PROP_BUS_NAME, + PROP_REASON, + PROP_APP_ID, + PROP_CLIENT_ID, + PROP_FLAGS, + PROP_TOPLEVEL_XID, + PROP_COOKIE +}; + +enum { + VANISHED, + LAST_SIGNAL +}; +static guint signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE (GsmInhibitor, gsm_inhibitor, G_TYPE_OBJECT) + +#define GSM_INHIBITOR_DBUS_IFACE "org.gnome.SessionManager.Inhibitor" + +static const GDBusErrorEntry gsm_inhibitor_error_entries[] = { + { GSM_INHIBITOR_ERROR_GENERAL, GSM_INHIBITOR_DBUS_IFACE ".GeneralError" }, + { GSM_INHIBITOR_ERROR_NOT_SET, GSM_INHIBITOR_DBUS_IFACE ".NotSet" } +}; + +GQuark +gsm_inhibitor_error_quark (void) +{ + static volatile gsize quark_volatile = 0; + + g_dbus_error_register_error_domain ("gsm_inhibitor_error", + &quark_volatile, + gsm_inhibitor_error_entries, + G_N_ELEMENTS (gsm_inhibitor_error_entries)); + return quark_volatile; +} + +static gboolean +gsm_inhibitor_get_app_id (GsmExportedInhibitor *skeleton, + GDBusMethodInvocation *invocation, + GsmInhibitor *inhibitor) +{ + const gchar *id; + + if (inhibitor->priv->app_id != NULL) { + id = inhibitor->priv->app_id; + } else { + id = ""; + } + + gsm_exported_inhibitor_complete_get_app_id (skeleton, invocation, id); + + return TRUE; +} + +static gboolean +gsm_inhibitor_get_client_id (GsmExportedInhibitor *skeleton, + GDBusMethodInvocation *invocation, + GsmInhibitor *inhibitor) +{ + /* object paths are not allowed to be NULL or blank */ + if (IS_STRING_EMPTY (inhibitor->priv->client_id)) { + g_dbus_method_invocation_return_error (invocation, + GSM_INHIBITOR_ERROR, + GSM_INHIBITOR_ERROR_NOT_SET, + "Value is not set"); + return TRUE; + } + + gsm_exported_inhibitor_complete_get_client_id (skeleton, invocation, inhibitor->priv->client_id); + g_debug ("GsmInhibitor: getting client-id = '%s'", inhibitor->priv->client_id); + + return TRUE; +} + +static gboolean +gsm_inhibitor_get_reason (GsmExportedInhibitor *skeleton, + GDBusMethodInvocation *invocation, + GsmInhibitor *inhibitor) +{ + const gchar *reason; + + if (inhibitor->priv->reason != NULL) { + reason = inhibitor->priv->reason; + } else { + reason = ""; + } + + gsm_exported_inhibitor_complete_get_reason (skeleton, invocation, reason); + return TRUE; +} + +static gboolean +gsm_inhibitor_get_flags (GsmExportedInhibitor *skeleton, + GDBusMethodInvocation *invocation, + GsmInhibitor *inhibitor) +{ + gsm_exported_inhibitor_complete_get_flags (skeleton, invocation, inhibitor->priv->flags); + return TRUE; +} + +static gboolean +gsm_inhibitor_get_toplevel_xid (GsmExportedInhibitor *skeleton, + GDBusMethodInvocation *invocation, + GsmInhibitor *inhibitor) +{ + gsm_exported_inhibitor_complete_get_toplevel_xid (skeleton, invocation, inhibitor->priv->toplevel_xid); + return TRUE; +} + +static guint32 +get_next_inhibitor_serial (void) +{ + guint32 serial; + + serial = inhibitor_serial++; + + if ((gint32)inhibitor_serial < 0) { + inhibitor_serial = 1; + } + + return serial; +} + +static gboolean +register_inhibitor (GsmInhibitor *inhibitor) +{ + GError *error; + GsmExportedInhibitor *skeleton; + + error = NULL; + inhibitor->priv->connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + + if (error != NULL) { + g_critical ("error getting session bus: %s", error->message); + g_error_free (error); + return FALSE; + } + + skeleton = gsm_exported_inhibitor_skeleton_new (); + inhibitor->priv->skeleton = skeleton; + g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (skeleton), + inhibitor->priv->connection, + inhibitor->priv->id, &error); + + if (error != NULL) { + g_critical ("error exporting inhibitor on session bus: %s", error->message); + g_error_free (error); + return FALSE; + } + + g_signal_connect (skeleton, "handle-get-app-id", + G_CALLBACK (gsm_inhibitor_get_app_id), inhibitor); + g_signal_connect (skeleton, "handle-get-client-id", + G_CALLBACK (gsm_inhibitor_get_client_id), inhibitor); + g_signal_connect (skeleton, "handle-get-flags", + G_CALLBACK (gsm_inhibitor_get_flags), inhibitor); + g_signal_connect (skeleton, "handle-get-reason", + G_CALLBACK (gsm_inhibitor_get_reason), inhibitor); + g_signal_connect (skeleton, "handle-get-toplevel-xid", + G_CALLBACK (gsm_inhibitor_get_toplevel_xid), inhibitor); + + return TRUE; +} + +static GObject * +gsm_inhibitor_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GsmInhibitor *inhibitor; + gboolean res; + + inhibitor = GSM_INHIBITOR (G_OBJECT_CLASS (gsm_inhibitor_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + + g_free (inhibitor->priv->id); + inhibitor->priv->id = g_strdup_printf ("/org/gnome/SessionManager/Inhibitor%u", get_next_inhibitor_serial ()); + res = register_inhibitor (inhibitor); + if (! res) { + g_warning ("Unable to register inhibitor with session bus"); + } + + return G_OBJECT (inhibitor); +} + +static void +gsm_inhibitor_init (GsmInhibitor *inhibitor) +{ + inhibitor->priv = GSM_INHIBITOR_GET_PRIVATE (inhibitor); +} + +static void +on_inhibitor_vanished (GDBusConnection *connection, + const char *name, + gpointer user_data) +{ + GsmInhibitor *inhibitor = user_data; + + g_bus_unwatch_name (inhibitor->priv->watch_id); + inhibitor->priv->watch_id = 0; + + g_signal_emit (inhibitor, signals[VANISHED], 0); +} + +static void +gsm_inhibitor_set_bus_name (GsmInhibitor *inhibitor, + const char *bus_name) +{ + g_return_if_fail (GSM_IS_INHIBITOR (inhibitor)); + + g_free (inhibitor->priv->bus_name); + + if (bus_name != NULL) { + inhibitor->priv->bus_name = g_strdup (bus_name); + + inhibitor->priv->watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION, + bus_name, + G_BUS_NAME_WATCHER_FLAGS_NONE, + NULL, + on_inhibitor_vanished, + inhibitor, + NULL); + } else { + inhibitor->priv->bus_name = g_strdup (""); + } + g_object_notify (G_OBJECT (inhibitor), "bus-name"); +} + +static void +gsm_inhibitor_set_app_id (GsmInhibitor *inhibitor, + const char *app_id) +{ + g_return_if_fail (GSM_IS_INHIBITOR (inhibitor)); + + g_free (inhibitor->priv->app_id); + + inhibitor->priv->app_id = g_strdup (app_id); + g_object_notify (G_OBJECT (inhibitor), "app-id"); +} + +static void +gsm_inhibitor_set_client_id (GsmInhibitor *inhibitor, + const char *client_id) +{ + g_return_if_fail (GSM_IS_INHIBITOR (inhibitor)); + + g_free (inhibitor->priv->client_id); + + g_debug ("GsmInhibitor: setting client-id = %s", client_id); + + if (client_id != NULL) { + inhibitor->priv->client_id = g_strdup (client_id); + } else { + inhibitor->priv->client_id = g_strdup (""); + } + g_object_notify (G_OBJECT (inhibitor), "client-id"); +} + +static void +gsm_inhibitor_set_reason (GsmInhibitor *inhibitor, + const char *reason) +{ + g_return_if_fail (GSM_IS_INHIBITOR (inhibitor)); + + g_free (inhibitor->priv->reason); + + if (reason != NULL) { + inhibitor->priv->reason = g_strdup (reason); + } else { + inhibitor->priv->reason = g_strdup (""); + } + g_object_notify (G_OBJECT (inhibitor), "reason"); +} + +static void +gsm_inhibitor_set_cookie (GsmInhibitor *inhibitor, + guint cookie) +{ + g_return_if_fail (GSM_IS_INHIBITOR (inhibitor)); + + if (inhibitor->priv->cookie != cookie) { + inhibitor->priv->cookie = cookie; + g_object_notify (G_OBJECT (inhibitor), "cookie"); + } +} + +static void +gsm_inhibitor_set_flags (GsmInhibitor *inhibitor, + guint flags) +{ + g_return_if_fail (GSM_IS_INHIBITOR (inhibitor)); + + if (inhibitor->priv->flags != flags) { + inhibitor->priv->flags = flags; + g_object_notify (G_OBJECT (inhibitor), "flags"); + } +} + +static void +gsm_inhibitor_set_toplevel_xid (GsmInhibitor *inhibitor, + guint xid) +{ + g_return_if_fail (GSM_IS_INHIBITOR (inhibitor)); + + if (inhibitor->priv->toplevel_xid != xid) { + inhibitor->priv->toplevel_xid = xid; + g_object_notify (G_OBJECT (inhibitor), "toplevel-xid"); + } +} + +const char * +gsm_inhibitor_peek_bus_name (GsmInhibitor *inhibitor) +{ + g_return_val_if_fail (GSM_IS_INHIBITOR (inhibitor), NULL); + + return inhibitor->priv->bus_name; +} + +const char * +gsm_inhibitor_peek_id (GsmInhibitor *inhibitor) +{ + g_return_val_if_fail (GSM_IS_INHIBITOR (inhibitor), NULL); + + return inhibitor->priv->id; +} + +const char * +gsm_inhibitor_peek_app_id (GsmInhibitor *inhibitor) +{ + g_return_val_if_fail (GSM_IS_INHIBITOR (inhibitor), NULL); + + return inhibitor->priv->app_id; +} + +const char * +gsm_inhibitor_peek_client_id (GsmInhibitor *inhibitor) +{ + g_return_val_if_fail (GSM_IS_INHIBITOR (inhibitor), NULL); + + return inhibitor->priv->client_id; +} + +const char * +gsm_inhibitor_peek_reason (GsmInhibitor *inhibitor) +{ + g_return_val_if_fail (GSM_IS_INHIBITOR (inhibitor), NULL); + + return inhibitor->priv->reason; +} + +guint +gsm_inhibitor_peek_flags (GsmInhibitor *inhibitor) +{ + g_return_val_if_fail (GSM_IS_INHIBITOR (inhibitor), 0); + + return inhibitor->priv->flags; +} + +guint +gsm_inhibitor_peek_toplevel_xid (GsmInhibitor *inhibitor) +{ + g_return_val_if_fail (GSM_IS_INHIBITOR (inhibitor), 0); + + return inhibitor->priv->toplevel_xid; +} + +guint +gsm_inhibitor_peek_cookie (GsmInhibitor *inhibitor) +{ + g_return_val_if_fail (GSM_IS_INHIBITOR (inhibitor), 0); + + return inhibitor->priv->cookie; +} + +static void +gsm_inhibitor_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GsmInhibitor *self; + + self = GSM_INHIBITOR (object); + + switch (prop_id) { + case PROP_BUS_NAME: + gsm_inhibitor_set_bus_name (self, g_value_get_string (value)); + break; + case PROP_APP_ID: + gsm_inhibitor_set_app_id (self, g_value_get_string (value)); + break; + case PROP_CLIENT_ID: + gsm_inhibitor_set_client_id (self, g_value_get_string (value)); + break; + case PROP_REASON: + gsm_inhibitor_set_reason (self, g_value_get_string (value)); + break; + case PROP_FLAGS: + gsm_inhibitor_set_flags (self, g_value_get_uint (value)); + break; + case PROP_COOKIE: + gsm_inhibitor_set_cookie (self, g_value_get_uint (value)); + break; + case PROP_TOPLEVEL_XID: + gsm_inhibitor_set_toplevel_xid (self, g_value_get_uint (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gsm_inhibitor_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GsmInhibitor *self; + + self = GSM_INHIBITOR (object); + + switch (prop_id) { + case PROP_BUS_NAME: + g_value_set_string (value, self->priv->bus_name); + break; + case PROP_APP_ID: + g_value_set_string (value, self->priv->app_id); + break; + case PROP_CLIENT_ID: + g_value_set_string (value, self->priv->client_id); + break; + case PROP_REASON: + g_value_set_string (value, self->priv->reason); + break; + case PROP_FLAGS: + g_value_set_uint (value, self->priv->flags); + break; + case PROP_COOKIE: + g_value_set_uint (value, self->priv->cookie); + break; + case PROP_TOPLEVEL_XID: + g_value_set_uint (value, self->priv->toplevel_xid); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gsm_inhibitor_finalize (GObject *object) +{ + GsmInhibitor *inhibitor = (GsmInhibitor *) object; + + g_free (inhibitor->priv->id); + g_free (inhibitor->priv->bus_name); + g_free (inhibitor->priv->app_id); + g_free (inhibitor->priv->client_id); + g_free (inhibitor->priv->reason); + + if (inhibitor->priv->skeleton != NULL) { + g_dbus_interface_skeleton_unexport_from_connection (G_DBUS_INTERFACE_SKELETON (inhibitor->priv->skeleton), + inhibitor->priv->connection); + g_clear_object (&inhibitor->priv->skeleton); + } + + if (inhibitor->priv->watch_id != 0) { + g_bus_unwatch_name (inhibitor->priv->watch_id); + } + + g_clear_object (&inhibitor->priv->connection); + + G_OBJECT_CLASS (gsm_inhibitor_parent_class)->finalize (object); +} + +static void +gsm_inhibitor_class_init (GsmInhibitorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gsm_inhibitor_finalize; + object_class->constructor = gsm_inhibitor_constructor; + object_class->get_property = gsm_inhibitor_get_property; + object_class->set_property = gsm_inhibitor_set_property; + + signals[VANISHED] = + g_signal_new ("vanished", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, + 0); + g_object_class_install_property (object_class, + PROP_BUS_NAME, + g_param_spec_string ("bus-name", + "bus-name", + "bus-name", + "", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_APP_ID, + g_param_spec_string ("app-id", + "app-id", + "app-id", + "", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_CLIENT_ID, + g_param_spec_string ("client-id", + "client-id", + "client-id", + "", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_REASON, + g_param_spec_string ("reason", + "reason", + "reason", + "", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_FLAGS, + g_param_spec_uint ("flags", + "flags", + "flags", + 0, + G_MAXINT, + 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_TOPLEVEL_XID, + g_param_spec_uint ("toplevel-xid", + "toplevel-xid", + "toplevel-xid", + 0, + G_MAXINT, + 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_COOKIE, + g_param_spec_uint ("cookie", + "cookie", + "cookie", + 0, + G_MAXINT, + 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_type_class_add_private (klass, sizeof (GsmInhibitorPrivate)); +} + +GsmInhibitor * +gsm_inhibitor_new (const char *app_id, + guint toplevel_xid, + guint flags, + const char *reason, + const char *bus_name, + guint cookie) +{ + GsmInhibitor *inhibitor; + + inhibitor = g_object_new (GSM_TYPE_INHIBITOR, + "app-id", app_id, + "reason", reason, + "bus-name", bus_name, + "flags", flags, + "toplevel-xid", toplevel_xid, + "cookie", cookie, + NULL); + + return inhibitor; +} + +GsmInhibitor * +gsm_inhibitor_new_for_client (const char *client_id, + const char *app_id, + guint flags, + const char *reason, + const char *bus_name, + guint cookie) +{ + GsmInhibitor *inhibitor; + + inhibitor = g_object_new (GSM_TYPE_INHIBITOR, + "client-id", client_id, + "app-id", app_id, + "reason", reason, + "bus-name", bus_name, + "flags", flags, + "cookie", cookie, + NULL); + + return inhibitor; +} diff --git a/gnome-session/gsm-inhibitor.h b/gnome-session/gsm-inhibitor.h new file mode 100644 index 0000000..5229fe1 --- /dev/null +++ b/gnome-session/gsm-inhibitor.h @@ -0,0 +1,88 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef __GSM_INHIBITOR_H__ +#define __GSM_INHIBITOR_H__ + +#include +#include + +#include "gsm-inhibitor-flag.h" + +G_BEGIN_DECLS + +#define GSM_TYPE_INHIBITOR (gsm_inhibitor_get_type ()) +#define GSM_INHIBITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_INHIBITOR, GsmInhibitor)) +#define GSM_INHIBITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_INHIBITOR, GsmInhibitorClass)) +#define GSM_IS_INHIBITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_INHIBITOR)) +#define GSM_IS_INHIBITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_INHIBITOR)) +#define GSM_INHIBITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSM_TYPE_INHIBITOR, GsmInhibitorClass)) + +typedef struct _GsmInhibitor GsmInhibitor; +typedef struct _GsmInhibitorClass GsmInhibitorClass; + +typedef struct GsmInhibitorPrivate GsmInhibitorPrivate; + +struct _GsmInhibitor +{ + GObject parent; + GsmInhibitorPrivate *priv; +}; + +struct _GsmInhibitorClass +{ + GObjectClass parent_class; +}; + +typedef enum +{ + GSM_INHIBITOR_ERROR_GENERAL = 0, + GSM_INHIBITOR_ERROR_NOT_SET, + GSM_INHIBITOR_NUM_ERRORS +} GsmInhibitorError; + +#define GSM_INHIBITOR_ERROR gsm_inhibitor_error_quark () +GQuark gsm_inhibitor_error_quark (void); + +GType gsm_inhibitor_get_type (void) G_GNUC_CONST; + +GsmInhibitor * gsm_inhibitor_new (const char *app_id, + guint toplevel_xid, + guint flags, + const char *reason, + const char *bus_name, + guint cookie); +GsmInhibitor * gsm_inhibitor_new_for_client (const char *client_id, + const char *app_id, + guint flags, + const char *reason, + const char *bus_name, + guint cookie); + +const char * gsm_inhibitor_peek_id (GsmInhibitor *inhibitor); +const char * gsm_inhibitor_peek_app_id (GsmInhibitor *inhibitor); +const char * gsm_inhibitor_peek_client_id (GsmInhibitor *inhibitor); +const char * gsm_inhibitor_peek_reason (GsmInhibitor *inhibitor); +const char * gsm_inhibitor_peek_bus_name (GsmInhibitor *inhibitor); +guint gsm_inhibitor_peek_cookie (GsmInhibitor *inhibitor); +guint gsm_inhibitor_peek_flags (GsmInhibitor *inhibitor); +guint gsm_inhibitor_peek_toplevel_xid (GsmInhibitor *inhibitor); + +G_END_DECLS + +#endif /* __GSM_INHIBITOR_H__ */ diff --git a/gnome-session/gsm-manager-logout-mode.h b/gnome-session/gsm-manager-logout-mode.h new file mode 100644 index 0000000..7b51751 --- /dev/null +++ b/gnome-session/gsm-manager-logout-mode.h @@ -0,0 +1,34 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 William Jon McCann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + */ + + +#ifndef __GSM_MANAGER_LOGOUT_MODE_H +#define __GSM_MANAGER_LOGOUT_MODE_H + +G_BEGIN_DECLS + +typedef enum { + GSM_MANAGER_LOGOUT_MODE_NORMAL = 0, + GSM_MANAGER_LOGOUT_MODE_NO_CONFIRMATION, + GSM_MANAGER_LOGOUT_MODE_FORCE +} GsmManagerLogoutMode; + +G_END_DECLS + +#endif /* __GSM_MANAGER_LOGOUT_MODE_H */ diff --git a/gnome-session/gsm-manager.c b/gnome-session/gsm-manager.c new file mode 100644 index 0000000..1b88b26 --- /dev/null +++ b/gnome-session/gsm-manager.c @@ -0,0 +1,3928 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + * Copyright (C) 2008 William Jon McCann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "gsm-manager.h" +#include "org.gnome.SessionManager.h" + +#ifdef ENABLE_SYSTEMD_JOURNAL +#include +#endif + +#ifdef HAVE_SYSTEMD +#include +#else +/* So we don't need to add ifdef's everywhere */ +#define sd_notify(u, m) do {} while (0) +#define sd_notifyf(u, m, ...) do {} while (0) +#endif + +#include "gsm-store.h" +#include "gsm-inhibitor.h" +#include "gsm-presence.h" +#include "gsm-shell.h" + +#include "gsm-xsmp-server.h" +#include "gsm-xsmp-client.h" +#include "gsm-dbus-client.h" + +#include "gsm-autostart-app.h" + +#include "gsm-util.h" +#include "gsm-icon-names.h" +#include "gsm-system.h" +#include "gsm-session-save.h" +#include "gsm-shell-extensions.h" +#include "gsm-fail-whale.h" + +/* UUIDs for log messages */ +#define GSM_MANAGER_STARTUP_SUCCEEDED_MSGID "0ce153587afa4095832d233c17a88001" +#define GSM_MANAGER_UNRECOVERABLE_FAILURE_MSGID "10dd2dc188b54a5e98970f56499d1f73" + +#define GSM_MANAGER_DBUS_PATH "/org/gnome/SessionManager" +#define GSM_MANAGER_DBUS_NAME "org.gnome.SessionManager" +#define GSM_MANAGER_DBUS_IFACE "org.gnome.SessionManager" + +/* Probably about the longest amount of time someone could reasonably + * want to wait, at least for something happening more than once. + * We can get deployed on very slow media though like CDROM devices, + * often with complex stacking/compressing filesystems on top, which + * is not a recipie for speed. Particularly now that we throw up + * a fail whale if required components don't show up quickly enough, + * let's make this fairly long. + */ +#define GSM_MANAGER_PHASE_TIMEOUT 90 /* seconds */ + +#define GDM_FLEXISERVER_COMMAND "gdmflexiserver" +#define GDM_FLEXISERVER_ARGS "--startnew Standard" + +#define SESSION_SCHEMA "org.gnome.desktop.session" +#define KEY_IDLE_DELAY "idle-delay" +#define KEY_SESSION_NAME "session-name" + +#define GSM_MANAGER_SCHEMA "org.gnome.SessionManager" +#define KEY_AUTOSAVE "auto-save-session" +#define KEY_AUTOSAVE_ONE_SHOT "auto-save-session-one-shot" +#define KEY_LOGOUT_PROMPT "logout-prompt" +#define KEY_SHOW_FALLBACK_WARNING "show-fallback-warning" + +#define SCREENSAVER_SCHEMA "org.gnome.desktop.screensaver" +#define KEY_SLEEP_LOCK "lock-enabled" + +#define LOCKDOWN_SCHEMA "org.gnome.desktop.lockdown" +#define KEY_DISABLE_LOG_OUT "disable-log-out" +#define KEY_DISABLE_USER_SWITCHING "disable-user-switching" + +static void app_registered (GsmApp *app, GParamSpec *spec, GsmManager *manager); + +typedef enum +{ + GSM_MANAGER_LOGOUT_NONE, + GSM_MANAGER_LOGOUT_LOGOUT, + GSM_MANAGER_LOGOUT_REBOOT, + GSM_MANAGER_LOGOUT_REBOOT_INTERACT, + GSM_MANAGER_LOGOUT_SHUTDOWN, + GSM_MANAGER_LOGOUT_SHUTDOWN_INTERACT, +} GsmManagerLogoutType; + +typedef struct +{ + gboolean failsafe; + gboolean systemd_managed; + gboolean systemd_initialized; + gboolean manager_initialized; + GsmStore *clients; + GsmStore *inhibitors; + GsmInhibitorFlag inhibited_actions; + GsmStore *apps; + GsmPresence *presence; + GsmXsmpServer *xsmp_server; + + char *session_name; + gboolean is_fallback_session : 1; + + /* Current status */ + GsmManagerPhase phase; + guint phase_timeout_id; + GSList *required_apps; + GSList *pending_apps; + GsmManagerLogoutMode logout_mode; + GSList *query_clients; + guint query_timeout_id; + /* This is used for GSM_MANAGER_PHASE_END_SESSION only at the moment, + * since it uses a sublist of all running client that replied in a + * specific way */ + GSList *next_query_clients; + /* This is the action that will be done just before we exit */ + GsmManagerLogoutType logout_type; + + /* List of clients which were disconnected due to disabled condition + * and shouldn't be automatically restarted */ + GSList *condition_clients; + + GSList *pending_end_session_tasks; + GCancellable *end_session_cancellable; + + GSettings *settings; + GSettings *session_settings; + GSettings *screensaver_settings; + GSettings *lockdown_settings; + + GsmSystem *system; + GDBusConnection *connection; + GsmExportedManager *skeleton; + gboolean dbus_disconnected : 1; + + GsmShell *shell; + guint shell_end_session_dialog_canceled_id; + guint shell_end_session_dialog_open_failed_id; + guint shell_end_session_dialog_confirmed_logout_id; + guint shell_end_session_dialog_confirmed_shutdown_id; + guint shell_end_session_dialog_confirmed_reboot_id; +} GsmManagerPrivate; + +enum { + PROP_0, + PROP_CLIENT_STORE, + PROP_SESSION_NAME, + PROP_FALLBACK, + PROP_FAILSAFE, + PROP_SYSTEMD_MANAGED +}; + +enum { + PHASE_CHANGED, + LAST_SIGNAL +}; + +static guint signals [LAST_SIGNAL] = { 0 }; + +static void gsm_manager_class_init (GsmManagerClass *klass); +static void gsm_manager_init (GsmManager *manager); + +static gboolean auto_save_is_enabled (GsmManager *manager); +static void maybe_save_session (GsmManager *manager); + +static gboolean _log_out_is_locked_down (GsmManager *manager); + +static void _handle_client_end_session_response (GsmManager *manager, + GsmClient *client, + gboolean is_ok, + gboolean do_last, + gboolean cancel, + const char *reason); +static void show_shell_end_session_dialog (GsmManager *manager, + GsmShellEndSessionDialogType type); +static gpointer manager_object = NULL; + +G_DEFINE_TYPE_WITH_PRIVATE (GsmManager, gsm_manager, G_TYPE_OBJECT) + +static const GDBusErrorEntry gsm_manager_error_entries[] = { + { GSM_MANAGER_ERROR_GENERAL, GSM_MANAGER_DBUS_IFACE ".GeneralError" }, + { GSM_MANAGER_ERROR_NOT_IN_INITIALIZATION, GSM_MANAGER_DBUS_IFACE ".NotInInitialization" }, + { GSM_MANAGER_ERROR_NOT_IN_RUNNING, GSM_MANAGER_DBUS_IFACE ".NotInRunning" }, + { GSM_MANAGER_ERROR_ALREADY_REGISTERED, GSM_MANAGER_DBUS_IFACE ".AlreadyRegistered" }, + { GSM_MANAGER_ERROR_NOT_REGISTERED, GSM_MANAGER_DBUS_IFACE ".NotRegistered" }, + { GSM_MANAGER_ERROR_INVALID_OPTION, GSM_MANAGER_DBUS_IFACE ".InvalidOption" }, + { GSM_MANAGER_ERROR_LOCKED_DOWN, GSM_MANAGER_DBUS_IFACE ".LockedDown" } +}; + +GQuark +gsm_manager_error_quark (void) +{ + static volatile gsize quark_volatile = 0; + + g_dbus_error_register_error_domain ("gsm_manager_error", + &quark_volatile, + gsm_manager_error_entries, + G_N_ELEMENTS (gsm_manager_error_entries)); + return quark_volatile; +} + +static gboolean +start_app_or_warn (GsmManager *manager, + GsmApp *app) +{ + gboolean res; + GError *error = NULL; + + g_debug ("GsmManager: starting app '%s'", gsm_app_peek_id (app)); + + res = gsm_app_start (app, &error); + if (error != NULL) { + g_warning ("Failed to start app: %s", error->message); + g_clear_error (&error); + } + return res; +} + +static gboolean +is_app_required (GsmManager *manager, + GsmApp *app) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + return g_slist_find (priv->required_apps, app) != NULL; +} + +static void +on_required_app_failure (GsmManager *manager, + GsmApp *app) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + const gchar *app_id; + gboolean allow_logout; + GsmShellExtensions *extensions; + + app_id = gsm_app_peek_app_id (app); + + if (g_str_equal (app_id, "org.gnome.Shell.desktop")) { + extensions = g_object_new (GSM_TYPE_SHELL_EXTENSIONS, NULL); + gsm_shell_extensions_disable_all (extensions); + } else { + extensions = NULL; + } + + if (gsm_system_is_login_session (priv->system)) { + allow_logout = FALSE; + } else { + allow_logout = !_log_out_is_locked_down (manager); + } + +#ifdef ENABLE_SYSTEMD_JOURNAL + sd_journal_send ("MESSAGE_ID=%s", GSM_MANAGER_UNRECOVERABLE_FAILURE_MSGID, + "PRIORITY=%d", 3, + "MESSAGE=Unrecoverable failure in required component %s", app_id, + NULL); +#endif + + gsm_fail_whale_dialog_we_failed (FALSE, + allow_logout, + extensions); +} + +static void +on_display_server_failure (GsmManager *manager, + GsmApp *app) +{ + const gchar *app_id; + GsmShellExtensions *extensions; + + app_id = gsm_app_peek_app_id (app); + + if (g_str_equal (app_id, "org.gnome.Shell.desktop")) { + extensions = g_object_new (GSM_TYPE_SHELL_EXTENSIONS, NULL); + gsm_shell_extensions_disable_all (extensions); + + g_object_unref (extensions); + } else { + extensions = NULL; + } + +#ifdef ENABLE_SYSTEMD_JOURNAL + sd_journal_send ("MESSAGE_ID=%s", GSM_MANAGER_UNRECOVERABLE_FAILURE_MSGID, + "PRIORITY=%d", 3, + "MESSAGE=Unrecoverable failure in required component %s", app_id, + NULL); +#endif + + gsm_quit (); +} + +static gboolean +_debug_client (const char *id, + GsmClient *client, + GsmManager *manager) +{ + g_debug ("GsmManager: Client %s", gsm_client_peek_id (client)); + return FALSE; +} + +static void +debug_clients (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + gsm_store_foreach (priv->clients, + (GsmStoreFunc)_debug_client, + manager); +} + +static gboolean +_find_by_cookie (const char *id, + GsmInhibitor *inhibitor, + guint *cookie_ap) +{ + guint cookie_b; + + cookie_b = gsm_inhibitor_peek_cookie (inhibitor); + + return (*cookie_ap == cookie_b); +} + +static gboolean +_client_has_startup_id (const char *id, + GsmClient *client, + const char *startup_id_a) +{ + const char *startup_id_b; + + startup_id_b = gsm_client_peek_startup_id (client); + if (IS_STRING_EMPTY (startup_id_b)) { + return FALSE; + } + + return (strcmp (startup_id_a, startup_id_b) == 0); +} + +static void +app_condition_changed (GsmApp *app, + gboolean condition, + GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GsmClient *client; + + g_debug ("GsmManager: app:%s condition changed condition:%d", + gsm_app_peek_id (app), + condition); + + client = (GsmClient *)gsm_store_find (priv->clients, + (GsmStoreFunc)_client_has_startup_id, + (char *)gsm_app_peek_startup_id (app)); + + if (condition) { + if (!gsm_app_is_running (app) && client == NULL) { + start_app_or_warn (manager, app); + } else { + g_debug ("GsmManager: not starting - app still running '%s'", gsm_app_peek_id (app)); + } + } else { + GError *error; + gboolean res; + + if (client != NULL) { + /* Kill client in case condition if false and make sure it won't + * be automatically restarted by adding the client to + * condition_clients */ + priv->condition_clients = + g_slist_prepend (priv->condition_clients, client); + + g_debug ("GsmManager: stopping client %s for app", gsm_client_peek_id (client)); + + error = NULL; + res = gsm_client_stop (client, &error); + if (! res) { + g_warning ("Not able to stop app client from its condition: %s", + error->message); + g_error_free (error); + } + } else { + g_debug ("GsmManager: stopping app %s", gsm_app_peek_id (app)); + + /* If we don't have a client then we should try to kill the app */ + error = NULL; + res = gsm_app_stop (app, &error); + if (! res) { + g_warning ("Not able to stop app from its condition: %s", + error->message); + g_error_free (error); + } + } + } +} + +static const char * +phase_num_to_name (guint phase) +{ + const char *name; + + switch (phase) { + case GSM_MANAGER_PHASE_STARTUP: + name = "STARTUP"; + break; + case GSM_MANAGER_PHASE_EARLY_INITIALIZATION: + name = "EARLY_INITIALIZATION"; + break; + case GSM_MANAGER_PHASE_PRE_DISPLAY_SERVER: + name = "PRE_DISPLAY_SERVER"; + break; + case GSM_MANAGER_PHASE_DISPLAY_SERVER: + name = "DISPLAY_SERVER"; + break; + case GSM_MANAGER_PHASE_INITIALIZATION: + name = "INITIALIZATION"; + break; + case GSM_MANAGER_PHASE_WINDOW_MANAGER: + name = "WINDOW_MANAGER"; + break; + case GSM_MANAGER_PHASE_PANEL: + name = "PANEL"; + break; + case GSM_MANAGER_PHASE_DESKTOP: + name = "DESKTOP"; + break; + case GSM_MANAGER_PHASE_APPLICATION: + name = "APPLICATION"; + break; + case GSM_MANAGER_PHASE_RUNNING: + name = "RUNNING"; + break; + case GSM_MANAGER_PHASE_QUERY_END_SESSION: + name = "QUERY_END_SESSION"; + break; + case GSM_MANAGER_PHASE_END_SESSION: + name = "END_SESSION"; + break; + case GSM_MANAGER_PHASE_EXIT: + name = "EXIT"; + break; + default: + g_assert_not_reached (); + break; + } + + return name; +} + +static void start_phase (GsmManager *manager); + +static void +gsm_manager_quit (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + /* See the comment in request_reboot() for some more details about how + * this works. */ + + switch (priv->logout_type) { + case GSM_MANAGER_LOGOUT_LOGOUT: + case GSM_MANAGER_LOGOUT_NONE: + gsm_quit (); + break; + case GSM_MANAGER_LOGOUT_REBOOT: + case GSM_MANAGER_LOGOUT_REBOOT_INTERACT: + gsm_system_complete_shutdown (priv->system); + gsm_quit (); + break; + case GSM_MANAGER_LOGOUT_SHUTDOWN: + case GSM_MANAGER_LOGOUT_SHUTDOWN_INTERACT: + gsm_system_complete_shutdown (priv->system); + gsm_quit (); + break; + default: + g_assert_not_reached (); + break; + } +} + +static gboolean do_query_end_session_exit (GsmManager *manager); + +static void +end_phase (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + gboolean start_next_phase = TRUE; + + g_debug ("GsmManager: ending phase %s", + phase_num_to_name (priv->phase)); + + g_slist_free (priv->pending_apps); + priv->pending_apps = NULL; + + g_slist_free (priv->query_clients); + priv->query_clients = NULL; + + g_slist_free (priv->next_query_clients); + priv->next_query_clients = NULL; + + if (priv->query_timeout_id > 0) { + g_source_remove (priv->query_timeout_id); + priv->query_timeout_id = 0; + } + if (priv->phase_timeout_id > 0) { + g_source_remove (priv->phase_timeout_id); + priv->phase_timeout_id = 0; + } + + switch (priv->phase) { + case GSM_MANAGER_PHASE_STARTUP: + case GSM_MANAGER_PHASE_EARLY_INITIALIZATION: + case GSM_MANAGER_PHASE_PRE_DISPLAY_SERVER: + case GSM_MANAGER_PHASE_DISPLAY_SERVER: + break; + case GSM_MANAGER_PHASE_INITIALIZATION: + priv->manager_initialized = TRUE; + /* Wait for systemd if it isn't initialized yet*/ + if (priv->systemd_managed && !priv->systemd_initialized) { + sd_notify (0, "STATUS=GNOME Session Manager waiting for gnome-session-initialized.target (via signal)"); + start_next_phase = FALSE; + } + break; + case GSM_MANAGER_PHASE_WINDOW_MANAGER: + case GSM_MANAGER_PHASE_PANEL: + case GSM_MANAGER_PHASE_DESKTOP: + case GSM_MANAGER_PHASE_APPLICATION: + break; + case GSM_MANAGER_PHASE_RUNNING: + if (_log_out_is_locked_down (manager)) { + g_warning ("Unable to logout: Logout has been locked down"); + start_next_phase = FALSE; + } + break; + case GSM_MANAGER_PHASE_QUERY_END_SESSION: + if (!do_query_end_session_exit (manager)) + start_next_phase = FALSE; + break; + case GSM_MANAGER_PHASE_END_SESSION: + maybe_save_session (manager); + break; + case GSM_MANAGER_PHASE_EXIT: + start_next_phase = FALSE; + gsm_manager_quit (manager); + break; + default: + g_assert_not_reached (); + break; + } + + if (start_next_phase) { + priv->phase++; + start_phase (manager); + } +} + +static void +app_event_during_startup (GsmManager *manager, + GsmApp *app) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + if (!(priv->phase < GSM_MANAGER_PHASE_APPLICATION)) + return; + + priv->pending_apps = g_slist_remove (priv->pending_apps, app); + + if (priv->pending_apps == NULL) { + if (priv->phase_timeout_id > 0) { + g_source_remove (priv->phase_timeout_id); + priv->phase_timeout_id = 0; + } + + end_phase (manager); + } +} + +static gboolean +is_app_display_server (GsmManager *manager, + GsmApp *app) +{ + GsmManagerPhase phase; + + /* Apps can only really act as a display server if + * we're a wayland session. + */ + if (g_strcmp0 (g_getenv ("XDG_SESSION_TYPE"), "wayland") != 0) + return FALSE; + + phase = gsm_app_peek_phase (app); + + return (phase == GSM_MANAGER_PHASE_DISPLAY_SERVER && + is_app_required (manager, app)); +} + +static void +_restart_app (GsmManager *manager, + GsmApp *app) +{ + GError *error = NULL; + + if (is_app_display_server (manager, app)) { + on_display_server_failure (manager, app); + return; + } + + if (!gsm_app_restart (app, &error)) { + if (is_app_required (manager, app)) { + on_required_app_failure (manager, app); + } else { + g_warning ("Error on restarting session managed app: %s", error->message); + } + g_clear_error (&error); + + app_event_during_startup (manager, app); + } +} + +static void +app_died (GsmApp *app, + int signal, + GsmManager *manager) +{ + g_warning ("Application '%s' killed by signal %d", gsm_app_peek_app_id (app), signal); + + if (gsm_app_get_registered (app) && gsm_app_peek_autorestart (app)) { + g_debug ("Component '%s' is autorestart, ignoring died signal", + gsm_app_peek_app_id (app)); + return; + } + + _restart_app (manager, app); + + /* For now, we don't do anything with crashes from + * non-required apps after they hit the restart limit. + * + * Note that both required and not-required apps will be + * caught by ABRT/apport type infrastructure, and it'd be + * better to pick up the crash from there and do something + * un-intrusive about it generically. + */ +} + +static void +app_exited (GsmApp *app, + guchar exit_code, + GsmManager *manager) +{ + if (exit_code != 0) + g_warning ("App '%s' exited with code %d", gsm_app_peek_app_id (app), exit_code); + else + g_debug ("App %s exited successfully", gsm_app_peek_app_id (app)); + + /* Consider that non-success exit status means "crash" for required components */ + if (exit_code != 0 && is_app_required (manager, app)) { + if (gsm_app_get_registered (app) && gsm_app_peek_autorestart (app)) { + g_debug ("Component '%s' is autorestart, ignoring non-successful exit", + gsm_app_peek_app_id (app)); + return; + } + + _restart_app (manager, app); + } else { + app_event_during_startup (manager, app); + } +} + +static void +app_registered (GsmApp *app, + GParamSpec *spec, + GsmManager *manager) +{ + if (!gsm_app_get_registered (app)) { + return; + } + + g_debug ("App %s registered", gsm_app_peek_app_id (app)); + + app_event_during_startup (manager, app); +} + +static gboolean +on_phase_timeout (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GSList *a; + + priv->phase_timeout_id = 0; + + switch (priv->phase) { + case GSM_MANAGER_PHASE_STARTUP: + case GSM_MANAGER_PHASE_EARLY_INITIALIZATION: + case GSM_MANAGER_PHASE_PRE_DISPLAY_SERVER: + case GSM_MANAGER_PHASE_DISPLAY_SERVER: + case GSM_MANAGER_PHASE_INITIALIZATION: + case GSM_MANAGER_PHASE_WINDOW_MANAGER: + case GSM_MANAGER_PHASE_PANEL: + case GSM_MANAGER_PHASE_DESKTOP: + case GSM_MANAGER_PHASE_APPLICATION: + for (a = priv->pending_apps; a; a = a->next) { + GsmApp *app = a->data; + g_warning ("Application '%s' failed to register before timeout", + gsm_app_peek_app_id (app)); + if (is_app_required (manager, app)) + on_required_app_failure (manager, app); + } + break; + case GSM_MANAGER_PHASE_RUNNING: + break; + case GSM_MANAGER_PHASE_QUERY_END_SESSION: + case GSM_MANAGER_PHASE_END_SESSION: + break; + case GSM_MANAGER_PHASE_EXIT: + break; + default: + g_assert_not_reached (); + break; + } + + end_phase (manager); + + return FALSE; +} + +static gboolean +_start_app (const char *id, + GsmApp *app, + GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + if (gsm_app_peek_phase (app) != priv->phase) { + goto out; + } + + /* Keep track of app autostart condition in order to react + * accordingly in the future. */ + g_signal_connect (app, + "condition-changed", + G_CALLBACK (app_condition_changed), + manager); + + if (gsm_app_peek_is_disabled (app) + || gsm_app_peek_is_conditionally_disabled (app)) { + g_debug ("GsmManager: Skipping disabled app: %s", id); + goto out; + } + + if (!start_app_or_warn (manager, app)) + goto out; + + if (priv->phase < GSM_MANAGER_PHASE_APPLICATION) { + /* Historical note - apparently, + * e.g. gnome-settings-daemon used to "daemonize", and + * so gnome-session assumes process exit means "ok + * we're done". Of course this is broken, we don't + * even distinguish between exit code 0 versus not-0, + * nor do we have any metadata which tells us a + * process is going to "daemonize" or not (and + * basically nothing should be anyways). + */ + g_signal_connect (app, + "exited", + G_CALLBACK (app_exited), + manager); + g_signal_connect (app, + "notify::registered", + G_CALLBACK (app_registered), + manager); + g_signal_connect (app, + "died", + G_CALLBACK (app_died), + manager); + priv->pending_apps = g_slist_prepend (priv->pending_apps, app); + } + out: + return FALSE; +} + +static void +do_phase_startup (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + gsm_store_foreach (priv->apps, + (GsmStoreFunc)_start_app, + manager); + + if (priv->pending_apps != NULL) { + if (priv->phase < GSM_MANAGER_PHASE_APPLICATION) { + priv->phase_timeout_id = g_timeout_add_seconds (GSM_MANAGER_PHASE_TIMEOUT, + (GSourceFunc)on_phase_timeout, + manager); + } + } else { + end_phase (manager); + } +} + +typedef struct { + GsmManager *manager; + guint flags; +} ClientEndSessionData; + + +static gboolean +_client_end_session (GsmClient *client, + ClientEndSessionData *data) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (data->manager); + gboolean ret; + GError *error; + + error = NULL; + ret = gsm_client_end_session (client, data->flags, &error); + if (! ret) { + g_warning ("Unable to query client: %s", error->message); + g_error_free (error); + /* FIXME: what should we do if we can't communicate with client? */ + } else { + g_debug ("GsmManager: adding client to end-session clients: %s", gsm_client_peek_id (client)); + priv->query_clients = g_slist_prepend (priv->query_clients, client); + } + + return FALSE; +} + +static gboolean +_client_end_session_helper (const char *id, + GsmClient *client, + ClientEndSessionData *data) +{ + return _client_end_session (client, data); +} + +static void +complete_end_session_tasks (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GSList *l; + + for (l = priv->pending_end_session_tasks; + l != NULL; + l = l->next) { + GTask *task = G_TASK (l->data); + if (!g_task_return_error_if_cancelled (task)) + g_task_return_boolean (task, TRUE); + } + + g_slist_free_full (priv->pending_end_session_tasks, + (GDestroyNotify) g_object_unref); + priv->pending_end_session_tasks = NULL; +} + +static void +do_phase_end_session (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + ClientEndSessionData data; + + complete_end_session_tasks (manager); + + data.manager = manager; + data.flags = 0; + + if (priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) { + data.flags |= GSM_CLIENT_END_SESSION_FLAG_FORCEFUL; + } + if (auto_save_is_enabled (manager)) { + data.flags |= GSM_CLIENT_END_SESSION_FLAG_SAVE; + } + + if (priv->phase_timeout_id > 0) { + g_source_remove (priv->phase_timeout_id); + priv->phase_timeout_id = 0; + } + + if (gsm_store_size (priv->clients) > 0) { + priv->phase_timeout_id = g_timeout_add_seconds (GSM_MANAGER_PHASE_TIMEOUT, + (GSourceFunc)on_phase_timeout, + manager); + + gsm_store_foreach (priv->clients, + (GsmStoreFunc)_client_end_session_helper, + &data); + } else { + end_phase (manager); + } +} + +static void +do_phase_end_session_part_2 (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + ClientEndSessionData data; + + data.manager = manager; + data.flags = 0; + + if (priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) { + data.flags |= GSM_CLIENT_END_SESSION_FLAG_FORCEFUL; + } + if (auto_save_is_enabled (manager)) { + data.flags |= GSM_CLIENT_END_SESSION_FLAG_SAVE; + } + data.flags |= GSM_CLIENT_END_SESSION_FLAG_LAST; + + /* keep the timeout that was started at the beginning of the + * GSM_MANAGER_PHASE_END_SESSION phase */ + + if (g_slist_length (priv->next_query_clients) > 0) { + g_slist_foreach (priv->next_query_clients, + (GFunc)_client_end_session, + &data); + + g_slist_free (priv->next_query_clients); + priv->next_query_clients = NULL; + } else { + end_phase (manager); + } +} + +static gboolean +_client_stop (const char *id, + GsmClient *client, + gpointer user_data) +{ + gboolean ret; + GError *error; + + error = NULL; + ret = gsm_client_stop (client, &error); + if (! ret) { + g_warning ("Unable to stop client: %s", error->message); + g_error_free (error); + /* FIXME: what should we do if we can't communicate with client? */ + } else { + g_debug ("GsmManager: stopped client: %s", gsm_client_peek_id (client)); + } + + return FALSE; +} + +#ifdef HAVE_SYSTEMD +static void +maybe_restart_user_bus (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GsmSystem *system; + g_autoptr(GVariant) reply = NULL; + g_autoptr(GError) error = NULL; + + if (priv->dbus_disconnected) + return; + + system = gsm_get_system (); + + if (!gsm_system_is_last_session_for_user (system)) + return; + + reply = g_dbus_connection_call_sync (priv->connection, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StopUnit", + g_variant_new ("(ss)", "dbus.service", "fail"), + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + + if (error != NULL) { + g_debug ("GsmManager: reloading user bus failed: %s", error->message); + } +} +#endif + +static void +do_phase_exit (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + if (gsm_store_size (priv->clients) > 0) { + gsm_store_foreach (priv->clients, + (GsmStoreFunc)_client_stop, + NULL); + } + +#ifdef HAVE_SYSTEMD + if (!priv->systemd_managed) + maybe_restart_user_bus (manager); +#endif + + end_phase (manager); +} + +static gboolean +_client_query_end_session (const char *id, + GsmClient *client, + ClientEndSessionData *data) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (data->manager); + gboolean ret; + GError *error; + + error = NULL; + ret = gsm_client_query_end_session (client, data->flags, &error); + if (! ret) { + g_warning ("Unable to query client: %s", error->message); + g_error_free (error); + /* FIXME: what should we do if we can't communicate with client? */ + } else { + g_debug ("GsmManager: adding client to query clients: %s", gsm_client_peek_id (client)); + priv->query_clients = g_slist_prepend (priv->query_clients, client); + } + + return FALSE; +} + +static gboolean +inhibitor_has_flag (gpointer key, + GsmInhibitor *inhibitor, + gpointer data) +{ + guint flag; + guint flags; + + flag = GPOINTER_TO_UINT (data); + + flags = gsm_inhibitor_peek_flags (inhibitor); + + return (flags & flag); +} + +static gboolean +gsm_manager_is_logout_inhibited (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GsmInhibitor *inhibitor; + + if (priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) { + return FALSE; + } + + if (priv->inhibitors == NULL) { + return FALSE; + } + + inhibitor = (GsmInhibitor *)gsm_store_find (priv->inhibitors, + (GsmStoreFunc)inhibitor_has_flag, + GUINT_TO_POINTER (GSM_INHIBITOR_FLAG_LOGOUT)); + if (inhibitor == NULL) { + return FALSE; + } + return TRUE; +} + +static gboolean +gsm_manager_is_idle_inhibited (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GsmInhibitor *inhibitor; + + if (priv->inhibitors == NULL) { + return FALSE; + } + + inhibitor = (GsmInhibitor *)gsm_store_find (priv->inhibitors, + (GsmStoreFunc)inhibitor_has_flag, + GUINT_TO_POINTER (GSM_INHIBITOR_FLAG_IDLE)); + if (inhibitor == NULL) { + return FALSE; + } + return TRUE; +} + +static gboolean +_client_cancel_end_session (const char *id, + GsmClient *client, + GsmManager *manager) +{ + gboolean res; + GError *error; + + error = NULL; + res = gsm_client_cancel_end_session (client, &error); + if (! res) { + g_warning ("Unable to cancel end session: %s", error->message); + g_error_free (error); + } + + return FALSE; +} + +static gboolean +inhibitor_is_jit (gpointer key, + GsmInhibitor *inhibitor, + GsmManager *manager) +{ + gboolean matches; + const char *id; + + id = gsm_inhibitor_peek_client_id (inhibitor); + + matches = (id != NULL && id[0] != '\0'); + + return matches; +} + +static void +cancel_end_session (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + /* just ignore if received outside of shutdown */ + if (priv->phase < GSM_MANAGER_PHASE_QUERY_END_SESSION) { + return; + } + + /* switch back to running phase */ + g_debug ("GsmManager: Cancelling the end of session"); + + g_cancellable_cancel (priv->end_session_cancellable); + + gsm_manager_set_phase (manager, GSM_MANAGER_PHASE_RUNNING); + priv->logout_mode = GSM_MANAGER_LOGOUT_MODE_NORMAL; + + priv->logout_type = GSM_MANAGER_LOGOUT_NONE; + + /* clear all JIT inhibitors */ + gsm_store_foreach_remove (priv->inhibitors, + (GsmStoreFunc)inhibitor_is_jit, + (gpointer)manager); + + gsm_store_foreach (priv->clients, + (GsmStoreFunc)_client_cancel_end_session, + NULL); + + start_phase (manager); +} + +static void +end_session_or_show_shell_dialog (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + gboolean logout_prompt; + GsmShellEndSessionDialogType type; + gboolean logout_inhibited; + + switch (priv->logout_type) { + case GSM_MANAGER_LOGOUT_LOGOUT: + type = GSM_SHELL_END_SESSION_DIALOG_TYPE_LOGOUT; + break; + case GSM_MANAGER_LOGOUT_REBOOT: + case GSM_MANAGER_LOGOUT_REBOOT_INTERACT: + type = GSM_SHELL_END_SESSION_DIALOG_TYPE_RESTART; + break; + case GSM_MANAGER_LOGOUT_SHUTDOWN: + case GSM_MANAGER_LOGOUT_SHUTDOWN_INTERACT: + type = GSM_SHELL_END_SESSION_DIALOG_TYPE_SHUTDOWN; + break; + default: + g_warning ("Unexpected logout type %d when creating end session dialog", + priv->logout_type); + type = GSM_SHELL_END_SESSION_DIALOG_TYPE_LOGOUT; + break; + } + + logout_inhibited = gsm_manager_is_logout_inhibited (manager); + logout_prompt = g_settings_get_boolean (priv->settings, + KEY_LOGOUT_PROMPT); + + switch (priv->logout_mode) { + case GSM_MANAGER_LOGOUT_MODE_NORMAL: + if (logout_inhibited || logout_prompt) { + show_shell_end_session_dialog (manager, type); + } else { + end_phase (manager); + } + break; + + case GSM_MANAGER_LOGOUT_MODE_NO_CONFIRMATION: + if (logout_inhibited) { + show_shell_end_session_dialog (manager, type); + } else { + end_phase (manager); + } + break; + + case GSM_MANAGER_LOGOUT_MODE_FORCE: + end_phase (manager); + break; + default: + g_assert_not_reached (); + break; + } + +} + +static void +query_end_session_complete (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + g_debug ("GsmManager: query end session complete"); + + /* Remove the timeout since this can be called from outside the timer + * and we don't want to have it called twice */ + if (priv->query_timeout_id > 0) { + g_source_remove (priv->query_timeout_id); + priv->query_timeout_id = 0; + } + + end_session_or_show_shell_dialog (manager); +} + +static guint32 +generate_cookie (void) +{ + guint32 cookie; + + cookie = (guint32)g_random_int_range (1, G_MAXINT32); + + return cookie; +} + +static guint32 +_generate_unique_cookie (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + guint32 cookie; + + do { + cookie = generate_cookie (); + } while (gsm_store_find (priv->inhibitors, (GsmStoreFunc)_find_by_cookie, &cookie) != NULL); + + return cookie; +} + +static gboolean +_on_query_end_session_timeout (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GSList *l; + + priv->query_timeout_id = 0; + + g_debug ("GsmManager: query end session timed out"); + + for (l = priv->query_clients; l != NULL; l = l->next) { + guint cookie; + GsmInhibitor *inhibitor; + const char *bus_name; + char *app_id; + + g_warning ("Client '%s' failed to reply before timeout", + gsm_client_peek_id (l->data)); + + /* Don't add "not responding" inhibitors if logout is forced + */ + if (priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) { + continue; + } + + /* Add JIT inhibit for unresponsive client */ + if (GSM_IS_DBUS_CLIENT (l->data)) { + bus_name = gsm_dbus_client_get_bus_name (l->data); + } else { + bus_name = NULL; + } + + app_id = g_strdup (gsm_client_peek_app_id (l->data)); + if (IS_STRING_EMPTY (app_id)) { + /* XSMP clients don't give us an app id unless we start them */ + g_free (app_id); + app_id = gsm_client_get_app_name (l->data); + } + + cookie = _generate_unique_cookie (manager); + inhibitor = gsm_inhibitor_new_for_client (gsm_client_peek_id (l->data), + app_id, + GSM_INHIBITOR_FLAG_LOGOUT, + _("Not responding"), + bus_name, + cookie); + g_free (app_id); + gsm_store_add (priv->inhibitors, gsm_inhibitor_peek_id (inhibitor), G_OBJECT (inhibitor)); + g_object_unref (inhibitor); + } + + g_slist_free (priv->query_clients); + priv->query_clients = NULL; + + query_end_session_complete (manager); + + return FALSE; +} + +static void +do_phase_query_end_session (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + ClientEndSessionData data; + + data.manager = manager; + data.flags = 0; + + if (priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) { + data.flags |= GSM_CLIENT_END_SESSION_FLAG_FORCEFUL; + } + /* We only query if an app is ready to log out, so we don't use + * GSM_CLIENT_END_SESSION_FLAG_SAVE here. + */ + + debug_clients (manager); + g_debug ("GsmManager: sending query-end-session to clients (logout mode: %s)", + priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_NORMAL? "normal" : + priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE? "forceful": + "no confirmation"); + gsm_store_foreach (priv->clients, + (GsmStoreFunc)_client_query_end_session, + &data); + + /* This phase doesn't time out unless logout is forced. Typically, this + * separate timer is only used to show UI. */ + priv->query_timeout_id = g_timeout_add_seconds (1, (GSourceFunc)_on_query_end_session_timeout, manager); +} + +static void +update_idle (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + if (gsm_manager_is_idle_inhibited (manager)) { + gsm_presence_set_idle_enabled (priv->presence, FALSE); + } else { + gsm_presence_set_idle_enabled (priv->presence, TRUE); + } +} + +static void +start_phase (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + g_debug ("GsmManager: starting phase %s\n", + phase_num_to_name (priv->phase)); + + /* reset state */ + g_slist_free (priv->pending_apps); + priv->pending_apps = NULL; + g_slist_free (priv->query_clients); + priv->query_clients = NULL; + g_slist_free (priv->next_query_clients); + priv->next_query_clients = NULL; + + if (priv->query_timeout_id > 0) { + g_source_remove (priv->query_timeout_id); + priv->query_timeout_id = 0; + } + if (priv->phase_timeout_id > 0) { + g_source_remove (priv->phase_timeout_id); + priv->phase_timeout_id = 0; + } + + sd_notifyf (0, "STATUS=GNOME Session Manager phase is %s", phase_num_to_name (priv->phase)); + + switch (priv->phase) { + case GSM_MANAGER_PHASE_STARTUP: + case GSM_MANAGER_PHASE_EARLY_INITIALIZATION: + case GSM_MANAGER_PHASE_PRE_DISPLAY_SERVER: + do_phase_startup (manager); + break; + case GSM_MANAGER_PHASE_DISPLAY_SERVER: + sd_notify (0, "READY=1"); + do_phase_startup (manager); + break; + case GSM_MANAGER_PHASE_INITIALIZATION: + case GSM_MANAGER_PHASE_WINDOW_MANAGER: + case GSM_MANAGER_PHASE_PANEL: + case GSM_MANAGER_PHASE_DESKTOP: + case GSM_MANAGER_PHASE_APPLICATION: + do_phase_startup (manager); + break; + case GSM_MANAGER_PHASE_RUNNING: +#ifdef ENABLE_SYSTEMD_JOURNAL + sd_journal_send ("MESSAGE_ID=%s", GSM_MANAGER_STARTUP_SUCCEEDED_MSGID, + "PRIORITY=%d", 5, + "MESSAGE=Entering running state", + NULL); +#endif + gsm_xsmp_server_start_accepting_new_clients (priv->xsmp_server); + if (priv->pending_end_session_tasks != NULL) + complete_end_session_tasks (manager); + g_object_unref (priv->end_session_cancellable); + priv->end_session_cancellable = g_cancellable_new (); + gsm_exported_manager_emit_session_running (priv->skeleton); + update_idle (manager); + break; + case GSM_MANAGER_PHASE_QUERY_END_SESSION: + gsm_xsmp_server_stop_accepting_new_clients (priv->xsmp_server); + do_phase_query_end_session (manager); + break; + case GSM_MANAGER_PHASE_END_SESSION: + sd_notify (0, "STOPPING=1"); + + do_phase_end_session (manager); + break; + case GSM_MANAGER_PHASE_EXIT: + sd_notify (0, "STOPPING=1"); + + do_phase_exit (manager); + break; + default: + g_assert_not_reached (); + break; + } +} + +static gboolean +_debug_app_for_phase (const char *id, + GsmApp *app, + gpointer data) +{ + guint phase; + + phase = GPOINTER_TO_UINT (data); + + if (gsm_app_peek_phase (app) != phase) { + return FALSE; + } + + g_debug ("GsmManager:\tID: %s\tapp-id:%s\tis-disabled:%d\tis-conditionally-disabled:%d", + gsm_app_peek_id (app), + gsm_app_peek_app_id (app), + gsm_app_peek_is_disabled (app), + gsm_app_peek_is_conditionally_disabled (app)); + + return FALSE; +} + +static void +debug_app_summary (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + guint phase; + + g_debug ("GsmManager: App startup summary"); + for (phase = GSM_MANAGER_PHASE_EARLY_INITIALIZATION; phase < GSM_MANAGER_PHASE_RUNNING; phase++) { + g_debug ("GsmManager: Phase %s", phase_num_to_name (phase)); + gsm_store_foreach (priv->apps, + (GsmStoreFunc)_debug_app_for_phase, + GUINT_TO_POINTER (phase)); + } +} + +void +gsm_manager_start (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + g_debug ("GsmManager: GSM starting to manage"); + + g_return_if_fail (GSM_IS_MANAGER (manager)); + + gsm_xsmp_server_start (priv->xsmp_server); + gsm_manager_set_phase (manager, GSM_MANAGER_PHASE_EARLY_INITIALIZATION); + debug_app_summary (manager); + start_phase (manager); +} + +char * +_gsm_manager_get_default_session (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + g_autoptr(GSettings) session_settings = NULL; + + if (manager) + session_settings = g_object_ref (priv->session_settings); + else + session_settings = g_settings_new (SESSION_SCHEMA); + return g_settings_get_string (session_settings, + KEY_SESSION_NAME); +} + +void +_gsm_manager_set_active_session (GsmManager *manager, + const char *session_name, + gboolean is_fallback) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + g_free (priv->session_name); + priv->session_name = g_strdup (session_name); + priv->is_fallback_session = is_fallback; + + gsm_exported_manager_set_session_name (priv->skeleton, session_name); +} + +void +_gsm_manager_set_renderer (GsmManager *manager, + const char *renderer) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + gsm_exported_manager_set_renderer (priv->skeleton, renderer); +} + +static gboolean +_app_has_app_id (const char *id, + GsmApp *app, + const char *app_id_a) +{ + const char *app_id_b; + + app_id_b = gsm_app_peek_app_id (app); + return (app_id_b != NULL && strcmp (app_id_a, app_id_b) == 0); +} + +static GsmApp * +find_app_for_app_id (GsmManager *manager, + const char *app_id) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GsmApp *app; + + app = (GsmApp *)gsm_store_find (priv->apps, + (GsmStoreFunc)_app_has_app_id, + (char *)app_id); + return app; +} + +static gboolean +inhibitor_has_client_id (gpointer key, + GsmInhibitor *inhibitor, + const char *client_id_a) +{ + gboolean matches; + const char *client_id_b; + + client_id_b = gsm_inhibitor_peek_client_id (inhibitor); + + matches = FALSE; + if (! IS_STRING_EMPTY (client_id_a) && ! IS_STRING_EMPTY (client_id_b)) { + matches = (strcmp (client_id_a, client_id_b) == 0); + if (matches) { + g_debug ("GsmManager: removing JIT inhibitor for %s for reason '%s'", + gsm_inhibitor_peek_client_id (inhibitor), + gsm_inhibitor_peek_reason (inhibitor)); + } + } + + return matches; +} + +static gboolean +_app_has_startup_id (const char *id, + GsmApp *app, + const char *startup_id_a) +{ + const char *startup_id_b; + + startup_id_b = gsm_app_peek_startup_id (app); + + if (IS_STRING_EMPTY (startup_id_b)) { + return FALSE; + } + + return (strcmp (startup_id_a, startup_id_b) == 0); +} + +static GsmApp * +find_app_for_startup_id (GsmManager *manager, + const char *startup_id) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GsmApp *found_app; + GSList *a; + + found_app = NULL; + + /* If we're starting up the session, try to match the new client + * with one pending apps for the current phase. If not, try to match + * with any of the autostarted apps. */ + if (priv->phase < GSM_MANAGER_PHASE_APPLICATION) { + for (a = priv->pending_apps; a != NULL; a = a->next) { + GsmApp *app = GSM_APP (a->data); + + if (strcmp (startup_id, gsm_app_peek_startup_id (app)) == 0) { + found_app = app; + goto out; + } + } + } else { + GsmApp *app; + + app = (GsmApp *)gsm_store_find (priv->apps, + (GsmStoreFunc)_app_has_startup_id, + (char *)startup_id); + if (app != NULL) { + found_app = app; + goto out; + } + } + out: + return found_app; +} + +static void +_disconnect_client (GsmManager *manager, + GsmClient *client) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + gboolean is_condition_client; + GsmApp *app; + const char *app_id; + const char *startup_id; + gboolean app_restart; + GsmClientRestartStyle client_restart_hint; + + g_debug ("GsmManager: disconnect client: %s", gsm_client_peek_id (client)); + + /* take a ref so it doesn't get finalized */ + g_object_ref (client); + + gsm_client_set_status (client, GSM_CLIENT_FINISHED); + + is_condition_client = FALSE; + if (g_slist_find (priv->condition_clients, client)) { + priv->condition_clients = g_slist_remove (priv->condition_clients, client); + + is_condition_client = TRUE; + } + + /* remove any inhibitors for this client */ + gsm_store_foreach_remove (priv->inhibitors, + (GsmStoreFunc)inhibitor_has_client_id, + (gpointer)gsm_client_peek_id (client)); + + app = NULL; + + /* first try to match on startup ID */ + startup_id = gsm_client_peek_startup_id (client); + if (! IS_STRING_EMPTY (startup_id)) { + app = find_app_for_startup_id (manager, startup_id); + + } + + /* then try to find matching app-id */ + if (app == NULL) { + app_id = gsm_client_peek_app_id (client); + if (! IS_STRING_EMPTY (app_id)) { + g_debug ("GsmManager: disconnect for app '%s'", app_id); + app = find_app_for_app_id (manager, app_id); + } + } + + switch (priv->phase) { + case GSM_MANAGER_PHASE_QUERY_END_SESSION: + /* Instead of answering our end session query, the client just exited. + * Treat that as an "okay, end the session" answer. + * + * This call implicitly removes any inhibitors for the client, along + * with removing the client from the pending query list. + */ + _handle_client_end_session_response (manager, + client, + TRUE, + FALSE, + FALSE, + "Client exited in " + "query end session phase " + "instead of end session " + "phase"); + break; + case GSM_MANAGER_PHASE_END_SESSION: + if (! g_slist_find (priv->query_clients, client)) { + /* the client sent its EndSessionResponse and we already + * processed it. + */ + break; + } + + /* Client exited without sending EndSessionResponse. + * The likely reason is that its exit code is written in a way + * that never returns, and sending EndSessionResponse is handled + * in library code after the callback. Or maybe the application + * crashed while handling EndSession. Or it was lazy. + */ + _handle_client_end_session_response (manager, + client, + TRUE, + FALSE, + FALSE, + "Client exited in " + "end session phase without " + "sending EndSessionResponse"); + default: + /* do nothing */ + break; + } + + if (priv->dbus_disconnected && GSM_IS_DBUS_CLIENT (client)) { + g_debug ("GsmManager: dbus disconnected, not restarting application"); + goto out; + } + + if (app == NULL) { + g_debug ("GsmManager: unable to find application for client - not restarting"); + goto out; + } + + if (priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION) { + g_debug ("GsmManager: in shutdown, not restarting application"); + goto out; + } + + app_restart = gsm_app_peek_autorestart (app); + client_restart_hint = gsm_client_peek_restart_style_hint (client); + + /* allow legacy clients to override the app info */ + if (! app_restart + && client_restart_hint != GSM_CLIENT_RESTART_IMMEDIATELY) { + g_debug ("GsmManager: autorestart not set, not restarting application"); + goto out; + } + + if (is_condition_client) { + g_debug ("GsmManager: app conditionally disabled, not restarting application"); + goto out; + } + + g_debug ("GsmManager: restarting app"); + + _restart_app (manager, app); + + out: + g_object_unref (client); +} + +typedef struct { + const char *service_name; + GsmManager *manager; +} RemoveClientData; + +static gboolean +_disconnect_dbus_client (const char *id, + GsmClient *client, + RemoveClientData *data) +{ + const char *name; + + if (! GSM_IS_DBUS_CLIENT (client)) { + return FALSE; + } + + /* If no service name, then we simply disconnect all clients */ + if (!data->service_name) { + _disconnect_client (data->manager, client); + return TRUE; + } + + name = gsm_dbus_client_get_bus_name (GSM_DBUS_CLIENT (client)); + if (IS_STRING_EMPTY (name)) { + return FALSE; + } + + if (strcmp (data->service_name, name) == 0) { + _disconnect_client (data->manager, client); + return TRUE; + } + + return FALSE; +} + +/** + * remove_clients_for_connection: + * @manager: a #GsmManager + * @service_name: a service name + * + * Disconnects clients that own @service_name. + * + * If @service_name is NULL, then disconnects all clients for the connection. + */ +static void +remove_clients_for_connection (GsmManager *manager, + const char *service_name) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + RemoveClientData data; + + data.service_name = service_name; + data.manager = manager; + + /* disconnect dbus clients for name */ + gsm_store_foreach_remove (priv->clients, + (GsmStoreFunc)_disconnect_dbus_client, + &data); + + if (priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION + && gsm_store_size (priv->clients) == 0) { + g_debug ("GsmManager: last client disconnected - exiting"); + end_phase (manager); + } +} + +static void +gsm_manager_set_failsafe (GsmManager *manager, + gboolean enabled) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + g_return_if_fail (GSM_IS_MANAGER (manager)); + + priv->failsafe = enabled; +} + +gboolean +gsm_manager_get_failsafe (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + g_return_val_if_fail (GSM_IS_MANAGER (manager), FALSE); + + return priv->failsafe; +} + +gboolean +gsm_manager_get_systemd_managed (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + g_return_val_if_fail (GSM_IS_MANAGER (manager), FALSE); + + return priv->systemd_managed; +} + +static void +on_client_disconnected (GsmClient *client, + GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + g_debug ("GsmManager: disconnect client"); + _disconnect_client (manager, client); + gsm_store_remove (priv->clients, gsm_client_peek_id (client)); + if (priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION + && gsm_store_size (priv->clients) == 0) { + g_debug ("GsmManager: last client disconnected - exiting"); + end_phase (manager); + } +} + +static gboolean +on_xsmp_client_register_request (GsmXSMPClient *client, + char **id, + GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + gboolean handled; + char *new_id; + GsmApp *app; + + handled = TRUE; + new_id = NULL; + + if (priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION) { + goto out; + } + + if (IS_STRING_EMPTY (*id)) { + new_id = gsm_util_generate_startup_id (); + } else { + GsmClient *client; + + client = (GsmClient *)gsm_store_find (priv->clients, + (GsmStoreFunc)_client_has_startup_id, + *id); + /* We can't have two clients with the same id. */ + if (client != NULL) { + goto out; + } + + new_id = g_strdup (*id); + } + + g_debug ("GsmManager: Adding new client %s to session", new_id); + + g_signal_connect (client, + "disconnected", + G_CALLBACK (on_client_disconnected), + manager); + + /* If it's a brand new client id, we just accept the client*/ + if (IS_STRING_EMPTY (*id)) { + goto out; + } + + app = find_app_for_startup_id (manager, new_id); + if (app != NULL) { + gsm_client_set_app_id (GSM_CLIENT (client), gsm_app_peek_app_id (app)); + goto out; + } + + /* app not found */ + g_free (new_id); + new_id = NULL; + + out: + g_free (*id); + *id = new_id; + + return handled; +} + +static void +on_xsmp_client_register_confirmed (GsmXSMPClient *client, + const gchar *id, + GsmManager *manager) +{ + GsmApp *app; + + app = find_app_for_startup_id (manager, id); + + if (app != NULL) { + gsm_app_set_registered (app, TRUE); + } +} + +static gboolean +auto_save_is_enabled (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + /* Note that saving/restoring sessions is not really possible on systemd, as + * XSMP clients cannot be reliably mapped to .desktop files. */ + if (priv->systemd_managed) + return FALSE; + + return g_settings_get_boolean (priv->settings, KEY_AUTOSAVE_ONE_SHOT) + || g_settings_get_boolean (priv->settings, KEY_AUTOSAVE); +} + +static void +maybe_save_session (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GError *error; + + if (gsm_system_is_login_session (priv->system)) + return; + + /* We only allow session saving when session is running or when + * logging out */ + if (priv->phase != GSM_MANAGER_PHASE_RUNNING && + priv->phase != GSM_MANAGER_PHASE_END_SESSION) { + return; + } + + if (!auto_save_is_enabled (manager)) { + gsm_session_save_clear (); + return; + } + + error = NULL; + gsm_session_save (priv->clients, priv->apps, &error); + + if (error) { + g_warning ("Error saving session: %s", error->message); + g_error_free (error); + } +} + +static void +_handle_client_end_session_response (GsmManager *manager, + GsmClient *client, + gboolean is_ok, + gboolean do_last, + gboolean cancel, + const char *reason) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + /* just ignore if received outside of shutdown */ + if (priv->phase < GSM_MANAGER_PHASE_QUERY_END_SESSION) { + return; + } + + g_debug ("GsmManager: Response from end session request: is-ok=%d do-last=%d cancel=%d reason=%s", is_ok, do_last, cancel, reason ? reason :""); + + if (cancel) { + cancel_end_session (manager); + return; + } + + priv->query_clients = g_slist_remove (priv->query_clients, client); + + if (! is_ok && priv->logout_mode != GSM_MANAGER_LOGOUT_MODE_FORCE) { + guint cookie; + GsmInhibitor *inhibitor; + char *app_id; + const char *bus_name; + + /* FIXME: do we support updating the reason? */ + + /* Create JIT inhibit */ + if (GSM_IS_DBUS_CLIENT (client)) { + bus_name = gsm_dbus_client_get_bus_name (GSM_DBUS_CLIENT (client)); + } else { + bus_name = NULL; + } + + app_id = g_strdup (gsm_client_peek_app_id (client)); + if (IS_STRING_EMPTY (app_id)) { + /* XSMP clients don't give us an app id unless we start them */ + g_free (app_id); + app_id = gsm_client_get_app_name (client); + } + + cookie = _generate_unique_cookie (manager); + inhibitor = gsm_inhibitor_new_for_client (gsm_client_peek_id (client), + app_id, + GSM_INHIBITOR_FLAG_LOGOUT, + reason != NULL ? reason : _("Not responding"), + bus_name, + cookie); + g_free (app_id); + gsm_store_add (priv->inhibitors, gsm_inhibitor_peek_id (inhibitor), G_OBJECT (inhibitor)); + g_object_unref (inhibitor); + } else { + gsm_store_foreach_remove (priv->inhibitors, + (GsmStoreFunc)inhibitor_has_client_id, + (gpointer)gsm_client_peek_id (client)); + } + + if (priv->phase == GSM_MANAGER_PHASE_QUERY_END_SESSION) { + if (priv->query_clients == NULL) { + query_end_session_complete (manager); + } + } else if (priv->phase == GSM_MANAGER_PHASE_END_SESSION) { + if (do_last) { + /* This only makes sense if we're in part 1 of + * GSM_MANAGER_PHASE_END_SESSION. Doing this in part 2 + * can only happen because of a buggy client that loops + * wanting to be last again and again. The phase + * timeout will take care of this issue. */ + priv->next_query_clients = g_slist_prepend (priv->next_query_clients, + client); + } + + /* we can continue to the next step if all clients have replied + * and if there's no inhibitor */ + if (priv->query_clients != NULL + || gsm_manager_is_logout_inhibited (manager)) { + return; + } + + if (priv->next_query_clients != NULL) { + do_phase_end_session_part_2 (manager); + } else { + end_phase (manager); + } + } +} + +static void +on_client_end_session_response (GsmClient *client, + gboolean is_ok, + gboolean do_last, + gboolean cancel, + const char *reason, + GsmManager *manager) +{ + _handle_client_end_session_response (manager, + client, + is_ok, + do_last, + cancel, + reason); +} + +static void +on_xsmp_client_logout_request (GsmXSMPClient *client, + gboolean show_dialog, + GsmManager *manager) +{ + GError *error; + int logout_mode; + + if (show_dialog) { + logout_mode = GSM_MANAGER_LOGOUT_MODE_NORMAL; + } else { + logout_mode = GSM_MANAGER_LOGOUT_MODE_NO_CONFIRMATION; + } + + error = NULL; + gsm_manager_logout (manager, logout_mode, &error); + if (error != NULL) { + g_warning ("Unable to logout: %s", error->message); + g_error_free (error); + } +} + +static void +on_store_client_added (GsmStore *store, + const char *id, + GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GsmClient *client; + + g_debug ("GsmManager: Client added: %s", id); + + client = (GsmClient *)gsm_store_lookup (store, id); + + /* a bit hacky */ + if (GSM_IS_XSMP_CLIENT (client)) { + g_signal_connect (client, + "register-request", + G_CALLBACK (on_xsmp_client_register_request), + manager); + g_signal_connect (client, + "register-confirmed", + G_CALLBACK (on_xsmp_client_register_confirmed), + manager); + g_signal_connect (client, + "logout-request", + G_CALLBACK (on_xsmp_client_logout_request), + manager); + } + + g_signal_connect (client, + "end-session-response", + G_CALLBACK (on_client_end_session_response), + manager); + + gsm_exported_manager_emit_client_added (priv->skeleton, id); + /* FIXME: disconnect signal handler */ +} + +static void +on_store_client_removed (GsmStore *store, + const char *id, + GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + g_debug ("GsmManager: Client removed: %s", id); + + gsm_exported_manager_emit_client_removed (priv->skeleton, id); +} + +static void +gsm_manager_set_client_store (GsmManager *manager, + GsmStore *store) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + g_return_if_fail (GSM_IS_MANAGER (manager)); + + if (store != NULL) { + g_object_ref (store); + } + + if (priv->clients != NULL) { + g_signal_handlers_disconnect_by_func (priv->clients, + on_store_client_added, + manager); + g_signal_handlers_disconnect_by_func (priv->clients, + on_store_client_removed, + manager); + + g_object_unref (priv->clients); + } + + + g_debug ("GsmManager: setting client store %p", store); + + priv->clients = store; + + if (priv->clients != NULL) { + if (priv->xsmp_server) + g_object_unref (priv->xsmp_server); + + priv->xsmp_server = gsm_xsmp_server_new (store); + + g_signal_connect (priv->clients, + "added", + G_CALLBACK (on_store_client_added), + manager); + g_signal_connect (priv->clients, + "removed", + G_CALLBACK (on_store_client_removed), + manager); + } +} + +static void +gsm_manager_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GsmManager *self = GSM_MANAGER (object); + GsmManagerPrivate *priv = gsm_manager_get_instance_private (self); + + switch (prop_id) { + case PROP_FAILSAFE: + gsm_manager_set_failsafe (self, g_value_get_boolean (value)); + break; + case PROP_FALLBACK: + priv->is_fallback_session = g_value_get_boolean (value); + break; + case PROP_CLIENT_STORE: + gsm_manager_set_client_store (self, g_value_get_object (value)); + break; + case PROP_SYSTEMD_MANAGED: + priv->systemd_managed = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gsm_manager_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GsmManager *self = GSM_MANAGER (object); + GsmManagerPrivate *priv = gsm_manager_get_instance_private (self); + + switch (prop_id) { + case PROP_FAILSAFE: + g_value_set_boolean (value, priv->failsafe); + break; + case PROP_SESSION_NAME: + g_value_set_string (value, priv->session_name); + break; + case PROP_FALLBACK: + g_value_set_boolean (value, priv->is_fallback_session); + break; + case PROP_CLIENT_STORE: + g_value_set_object (value, priv->clients); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +_find_app_provides (const char *id, + GsmApp *app, + const char *service) +{ + return gsm_app_provides (app, service); +} + +static GObject * +gsm_manager_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GsmManager *manager; + + manager = GSM_MANAGER (G_OBJECT_CLASS (gsm_manager_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + return G_OBJECT (manager); +} + +static void +update_inhibited_actions (GsmManager *manager, + GsmInhibitorFlag new_inhibited_actions) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + if (priv->inhibited_actions == new_inhibited_actions) + return; + + gsm_system_set_inhibitors (priv->system, new_inhibited_actions); + + priv->inhibited_actions = new_inhibited_actions; + gsm_exported_manager_set_inhibited_actions (priv->skeleton, + priv->inhibited_actions); +} + +static void +on_inhibitor_vanished (GsmInhibitor *inhibitor, + GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + gsm_store_remove (priv->inhibitors, gsm_inhibitor_peek_id (inhibitor)); +} + +static void +on_store_inhibitor_added (GsmStore *store, + const char *id, + GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GsmInhibitor *i; + GsmInhibitorFlag new_inhibited_actions; + + g_debug ("GsmManager: Inhibitor added: %s", id); + + i = GSM_INHIBITOR (gsm_store_lookup (store, id)); + + new_inhibited_actions = priv->inhibited_actions | gsm_inhibitor_peek_flags (i); + update_inhibited_actions (manager, new_inhibited_actions); + + g_signal_connect_object (i, "vanished", G_CALLBACK (on_inhibitor_vanished), manager, 0); + + gsm_exported_manager_emit_inhibitor_added (priv->skeleton, id); + + update_idle (manager); +} + +static gboolean +collect_inhibition_flags (const char *id, + GObject *object, + gpointer user_data) +{ + GsmInhibitorFlag *new_inhibited_actions = user_data; + + *new_inhibited_actions |= gsm_inhibitor_peek_flags (GSM_INHIBITOR (object)); + + return FALSE; +} + +static void +on_store_inhibitor_removed (GsmStore *store, + const char *id, + GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GsmInhibitorFlag new_inhibited_actions; + + g_debug ("GsmManager: Inhibitor removed: %s", id); + + new_inhibited_actions = 0; + gsm_store_foreach (priv->inhibitors, + collect_inhibition_flags, + &new_inhibited_actions); + update_inhibited_actions (manager, new_inhibited_actions); + + gsm_exported_manager_emit_inhibitor_removed (priv->skeleton, id); + + update_idle (manager); + + if (priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION) { + end_session_or_show_shell_dialog (manager); + } +} + +static void +gsm_manager_dispose (GObject *object) +{ + GsmManager *manager = GSM_MANAGER (object); + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + g_debug ("GsmManager: disposing manager"); + + g_clear_object (&priv->end_session_cancellable); + g_clear_object (&priv->xsmp_server); + g_clear_pointer (&priv->session_name, g_free); + + if (priv->clients != NULL) { + g_signal_handlers_disconnect_by_func (priv->clients, + on_store_client_added, + manager); + g_signal_handlers_disconnect_by_func (priv->clients, + on_store_client_removed, + manager); + g_object_unref (priv->clients); + priv->clients = NULL; + } + + g_clear_object (&priv->apps); + g_slist_free (priv->required_apps); + priv->required_apps = NULL; + g_slist_free (priv->pending_apps); + priv->pending_apps = NULL; + + if (priv->inhibitors != NULL) { + g_signal_handlers_disconnect_by_func (priv->inhibitors, + on_store_inhibitor_added, + manager); + g_signal_handlers_disconnect_by_func (priv->inhibitors, + on_store_inhibitor_removed, + manager); + + g_object_unref (priv->inhibitors); + priv->inhibitors = NULL; + } + + g_clear_object (&priv->presence); + g_clear_object (&priv->settings); + g_clear_object (&priv->session_settings); + g_clear_object (&priv->screensaver_settings); + g_clear_object (&priv->lockdown_settings); + g_clear_object (&priv->system); + g_clear_object (&priv->shell); + + if (priv->skeleton != NULL) { + g_dbus_interface_skeleton_unexport_from_connection (G_DBUS_INTERFACE_SKELETON (priv->skeleton), + priv->connection); + g_clear_object (&priv->skeleton); + } + + g_clear_object (&priv->connection); + + G_OBJECT_CLASS (gsm_manager_parent_class)->dispose (object); +} + +static void +gsm_manager_class_init (GsmManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = gsm_manager_get_property; + object_class->set_property = gsm_manager_set_property; + object_class->constructor = gsm_manager_constructor; + object_class->dispose = gsm_manager_dispose; + + signals [PHASE_CHANGED] = + g_signal_new ("phase-changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsmManagerClass, phase_changed), + NULL, NULL, NULL, + G_TYPE_NONE, + 1, G_TYPE_STRING); + + g_object_class_install_property (object_class, + PROP_FAILSAFE, + g_param_spec_boolean ("failsafe", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + /** + * GsmManager::session-name + * + * Then name of the currently active session, typically "gnome" or "gnome-fallback". + * This may be the name of the configured default session, or the name of a fallback + * session in case we fell back. + */ + g_object_class_install_property (object_class, + PROP_SESSION_NAME, + g_param_spec_string ("session-name", + NULL, + NULL, + NULL, + G_PARAM_READABLE)); + + /** + * GsmManager::fallback + * + * If %TRUE, the current session is running in the "fallback" mode; + * this is distinct from whether or not it was configured as default. + */ + g_object_class_install_property (object_class, + PROP_FALLBACK, + g_param_spec_boolean ("fallback", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, + PROP_CLIENT_STORE, + g_param_spec_object ("client-store", + NULL, + NULL, + GSM_TYPE_STORE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, + PROP_SYSTEMD_MANAGED, + g_param_spec_boolean ("systemd-managed", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); +} + +static void +on_presence_status_changed (GsmPresence *presence, + guint status, + GsmManager *manager) +{ + GsmSystem *system; + + system = gsm_get_system (); + gsm_system_set_session_idle (system, + (status == GSM_PRESENCE_STATUS_IDLE)); + g_object_unref (system); +} + +static void +on_gsm_system_active_changed (GsmSystem *system, + GParamSpec *pspec, + GsmManager *self) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (self); + gboolean is_active; + + is_active = gsm_system_is_active (priv->system); + + g_debug ("emitting SessionIsActive"); + gsm_exported_manager_set_session_is_active (priv->skeleton, is_active); +} + +static gboolean +_log_out_is_locked_down (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + return g_settings_get_boolean (priv->lockdown_settings, + KEY_DISABLE_LOG_OUT); +} + +static void +complete_end_session_task (GsmManager *manager, + GAsyncResult *result, + GDBusMethodInvocation *invocation) +{ + GError *error = NULL; + + if (!g_task_propagate_boolean (G_TASK (result), &error)) + g_dbus_method_invocation_take_error (invocation, error); + else + g_dbus_method_invocation_return_value (invocation, NULL); +} + +static void +request_reboot (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + g_debug ("GsmManager: requesting reboot"); + + /* FIXME: We need to support a more structured shutdown here, + * but that's blocking on an improved ConsoleKit api. + * + * See https://bugzilla.gnome.org/show_bug.cgi?id=585614 + */ + priv->logout_type = GSM_MANAGER_LOGOUT_REBOOT_INTERACT; + end_phase (manager); +} + +static void +request_shutdown (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + g_debug ("GsmManager: requesting shutdown"); + + /* See the comment in request_reboot() for some more details about + * what work needs to be done here. */ + priv->logout_type = GSM_MANAGER_LOGOUT_SHUTDOWN_INTERACT; + end_phase (manager); +} + +static void +request_logout (GsmManager *manager, + GsmManagerLogoutMode mode) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + g_debug ("GsmManager: requesting logout"); + + priv->logout_mode = mode; + priv->logout_type = GSM_MANAGER_LOGOUT_LOGOUT; + + end_phase (manager); +} + +static gboolean +gsm_manager_shutdown (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GTask *task; + + g_debug ("GsmManager: Shutdown called"); + + if (priv->phase < GSM_MANAGER_PHASE_RUNNING) { + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_NOT_IN_RUNNING, + "Shutdown interface is only available after the Running phase starts"); + return TRUE; + } + + if (_log_out_is_locked_down (manager)) { + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_LOCKED_DOWN, + "Logout has been locked down"); + return TRUE; + } + + task = g_task_new (manager, priv->end_session_cancellable, (GAsyncReadyCallback) complete_end_session_task, invocation); + + priv->pending_end_session_tasks = g_slist_prepend (priv->pending_end_session_tasks, + task); + + request_shutdown (manager); + + return TRUE; +} + +static gboolean +gsm_manager_reboot (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GTask *task; + + g_debug ("GsmManager: Reboot called"); + + if (priv->phase < GSM_MANAGER_PHASE_RUNNING) { + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_NOT_IN_RUNNING, + "Reboot interface is only available after the Running phase starts"); + return TRUE; + } + + if (_log_out_is_locked_down (manager)) { + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_LOCKED_DOWN, + "Logout has been locked down"); + return TRUE; + } + + task = g_task_new (manager, priv->end_session_cancellable, (GAsyncReadyCallback) complete_end_session_task, invocation); + + priv->pending_end_session_tasks = g_slist_prepend (priv->pending_end_session_tasks, + task); + + request_reboot (manager); + + return TRUE; +} + +static gboolean +gsm_manager_can_shutdown (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + gboolean shutdown_available; + + g_debug ("GsmManager: CanShutdown called"); + + shutdown_available = !_log_out_is_locked_down (manager) && + (gsm_system_can_stop (priv->system) + || gsm_system_can_restart (priv->system) + || gsm_system_can_suspend (priv->system) + || gsm_system_can_hibernate (priv->system)); + + gsm_exported_manager_complete_can_shutdown (skeleton, invocation, shutdown_available); + + return TRUE; +} + +static gboolean +gsm_manager_can_reboot_to_firmware_setup (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + gboolean reboot_to_firmware_available; + + g_debug ("GsmManager: CanRebootToFirmwareSetup called"); + + reboot_to_firmware_available = !_log_out_is_locked_down (manager) && + gsm_system_can_restart_to_firmware_setup (priv->system); + + gsm_exported_manager_complete_can_reboot_to_firmware_setup (skeleton, invocation, reboot_to_firmware_available); + + return TRUE; +} + +static gboolean +gsm_manager_set_reboot_to_firmware_setup (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + gboolean enable, + GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + g_debug ("GsmManager: SetRebootToFirmwareSetup called"); + + gsm_system_set_restart_to_firmware_setup (priv->system, enable); + + gsm_exported_manager_complete_set_reboot_to_firmware_setup (skeleton, invocation); + + return TRUE; +} + +static gboolean +gsm_manager_setenv (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + const char *variable, + const char *value, + GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + if (priv->phase > GSM_MANAGER_PHASE_INITIALIZATION) { + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_NOT_IN_INITIALIZATION, + "Setenv interface is only available during the DisplayServer and Initialization phase"); + } else { + gsm_util_setenv (variable, value); + gsm_exported_manager_complete_setenv (skeleton, invocation); + } + + return TRUE; +} + +static gboolean +gsm_manager_initialized (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + /* Signaled by helper when gnome-session-initialized.target is reached. */ + if (!priv->systemd_managed) { + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_GENERAL, + "Initialized interface is only available when gnome-session is managed by systemd"); + } else if (priv->systemd_initialized) { + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_NOT_IN_INITIALIZATION, + "Systemd initialization was already signaled"); + } else if (priv->phase > GSM_MANAGER_PHASE_INITIALIZATION) { + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_NOT_IN_INITIALIZATION, + "Initialized interface is only available during startup"); + } else { + priv->systemd_initialized = TRUE; + + if (priv->manager_initialized) { + g_assert (priv->phase == GSM_MANAGER_PHASE_INITIALIZATION); + priv->phase++; + start_phase (manager); + } + + gsm_exported_manager_complete_initialized (skeleton, invocation); + } + + return TRUE; +} + +static gboolean +is_valid_category (int category) +{ + int categories[] = { + LC_CTYPE, + LC_NUMERIC, + LC_TIME, + LC_COLLATE, + LC_MONETARY, + LC_MESSAGES, +#if defined (LC_PAPER) + LC_PAPER, +#endif +#if defined (LC_NAME) + LC_NAME, +#endif +#if defined (LC_ADDRESS) + LC_ADDRESS, +#endif +#if defined (LC_TELEPHONE) + LC_TELEPHONE, +#endif +#if defined (LC_MEASUREMENT) + LC_MEASUREMENT, +#endif +#if defined (LC_IDENTIFICATION) + LC_IDENTIFICATION, +#endif + LC_ALL + }; + guint i; + + for (i = 0; i < G_N_ELEMENTS(categories); i++) + if (categories[i] == category) + return TRUE; + + return FALSE; +} + +static gboolean +gsm_manager_get_locale (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + int category, + GsmManager *manager) +{ + if (!is_valid_category (category)) { + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_INVALID_OPTION, + "GetLocale doesn't support locale category '%d'", category); + } else { + const char *value; + value = setlocale (category, NULL); + if (value == NULL) + value = ""; + + gsm_exported_manager_complete_get_locale (skeleton, invocation, value); + } + + return TRUE; +} + +static gboolean +gsm_manager_initialization_error (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + const char *message, + gboolean fatal, + GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + if (priv->phase != GSM_MANAGER_PHASE_INITIALIZATION) { + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_NOT_IN_INITIALIZATION, + "InitializationError interface is only available during the Initialization phase"); + return TRUE; + } + + gsm_util_init_error (fatal, "%s", message); + gsm_exported_manager_complete_initialization_error (skeleton, invocation); + + return TRUE; +} + +static void +user_logout (GsmManager *manager, + GsmManagerLogoutMode mode) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + if (priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION) { + priv->logout_mode = mode; + end_session_or_show_shell_dialog (manager); + return; + } + + request_logout (manager, mode); +} + +gboolean +gsm_manager_logout (GsmManager *manager, + guint logout_mode, + GError **error) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + if (priv->phase < GSM_MANAGER_PHASE_RUNNING) { + g_set_error (error, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_NOT_IN_RUNNING, + "Logout interface is only available after the Running phase starts"); + return FALSE; + } + + if (_log_out_is_locked_down (manager)) { + g_set_error (error, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_LOCKED_DOWN, + "Logout has been locked down"); + return FALSE; + } + + switch (logout_mode) { + case GSM_MANAGER_LOGOUT_MODE_NORMAL: + case GSM_MANAGER_LOGOUT_MODE_NO_CONFIRMATION: + case GSM_MANAGER_LOGOUT_MODE_FORCE: + user_logout (manager, logout_mode); + break; + + default: + g_debug ("Unknown logout mode option"); + + g_set_error (error, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_INVALID_OPTION, + "Unknown logout mode flag"); + return FALSE; + } + + return TRUE; +} + +static gboolean +gsm_manager_logout_dbus (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + guint logout_mode, + GsmManager *manager) +{ + GError *error = NULL; + + g_debug ("GsmManager: Logout called"); + + if (!gsm_manager_logout (manager, logout_mode, &error)) { + g_dbus_method_invocation_take_error (invocation, error); + } else { + gsm_exported_manager_complete_logout (skeleton, invocation); + } + + return TRUE; +} + +static gboolean +gsm_manager_register_client (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + const char *app_id, + const char *startup_id, + GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + char *new_startup_id; + const char *sender; + GsmClient *client; + GsmApp *app; + + app = NULL; + client = NULL; + + g_debug ("GsmManager: RegisterClient %s", startup_id); + + if (priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION) { + g_debug ("Unable to register client: shutting down"); + + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_NOT_IN_RUNNING, + "Unable to register client"); + return TRUE; + } + + if (IS_STRING_EMPTY (startup_id)) { + new_startup_id = gsm_util_generate_startup_id (); + } else { + + client = (GsmClient *)gsm_store_find (priv->clients, + (GsmStoreFunc)_client_has_startup_id, + (char *)startup_id); + /* We can't have two clients with the same startup id. */ + if (client != NULL) { + g_debug ("Unable to register client: already registered"); + + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_ALREADY_REGISTERED, + "Unable to register client"); + return TRUE; + } + + new_startup_id = g_strdup (startup_id); + } + + g_debug ("GsmManager: Adding new client %s to session", new_startup_id); + + if (app == NULL && !IS_STRING_EMPTY (startup_id)) { + app = find_app_for_startup_id (manager, startup_id); + } + if (app == NULL && !IS_STRING_EMPTY (app_id)) { + /* try to associate this app id with a known app */ + app = find_app_for_app_id (manager, app_id); + } + + sender = g_dbus_method_invocation_get_sender (invocation); + client = gsm_dbus_client_new (new_startup_id, sender); + if (client == NULL) { + g_debug ("Unable to create client"); + + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_GENERAL, + "Unable to register client"); + return TRUE; + } + + gsm_store_add (priv->clients, gsm_client_peek_id (client), G_OBJECT (client)); + /* the store will own the ref */ + g_object_unref (client); + + g_signal_connect (client, + "disconnected", + G_CALLBACK (on_client_disconnected), + manager); + + if (app != NULL) { + gsm_client_set_app_id (client, gsm_app_peek_app_id (app)); + gsm_app_set_registered (app, TRUE); + } else { + /* if an app id is specified store it in the client + so we can save it later */ + gsm_client_set_app_id (client, app_id); + } + + gsm_client_set_status (client, GSM_CLIENT_REGISTERED); + + g_assert (new_startup_id != NULL); + g_free (new_startup_id); + + gsm_exported_manager_complete_register_client (skeleton, invocation, gsm_client_peek_id (client)); + + return TRUE; +} + +static gboolean +gsm_manager_unregister_client (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + const char *client_id, + GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GsmClient *client; + + g_debug ("GsmManager: UnregisterClient %s", client_id); + + client = (GsmClient *)gsm_store_lookup (priv->clients, client_id); + if (client == NULL) { + g_debug ("Unable to unregister client: not registered"); + + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_NOT_REGISTERED, + "Unable to unregister client"); + return TRUE; + } + + /* don't disconnect client here, only change the status. + Wait until it leaves the bus before disconnecting it */ + gsm_client_set_status (client, GSM_CLIENT_UNREGISTERED); + + gsm_exported_manager_complete_unregister_client (skeleton, invocation); + + return TRUE; +} + +static gboolean +gsm_manager_inhibit (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + const char *app_id, + guint toplevel_xid, + const char *reason, + guint flags, + GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GsmInhibitor *inhibitor; + guint cookie; + + g_debug ("GsmManager: Inhibit xid=%u app_id=%s reason=%s flags=%u", + toplevel_xid, + app_id, + reason, + flags); + + if (priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) { + GError *new_error; + + new_error = g_error_new (GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_GENERAL, + "Forced logout cannot be inhibited"); + g_debug ("GsmManager: Unable to inhibit: %s", new_error->message); + g_dbus_method_invocation_take_error (invocation, new_error); + return TRUE; + } + + if (IS_STRING_EMPTY (app_id)) { + GError *new_error; + + new_error = g_error_new (GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_GENERAL, + "Application ID not specified"); + g_debug ("GsmManager: Unable to inhibit: %s", new_error->message); + g_dbus_method_invocation_take_error (invocation, new_error); + return TRUE; + } + + if (IS_STRING_EMPTY (reason)) { + GError *new_error; + + new_error = g_error_new (GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_GENERAL, + "Reason not specified"); + g_debug ("GsmManager: Unable to inhibit: %s", new_error->message); + g_dbus_method_invocation_take_error (invocation, new_error); + return FALSE; + } + + if (flags == 0) { + GError *new_error; + + new_error = g_error_new (GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_GENERAL, + "Invalid inhibit flags"); + g_debug ("GsmManager: Unable to inhibit: %s", new_error->message); + g_dbus_method_invocation_take_error (invocation, new_error); + return FALSE; + } + + cookie = _generate_unique_cookie (manager); + inhibitor = gsm_inhibitor_new (app_id, + toplevel_xid, + flags, + reason, + g_dbus_method_invocation_get_sender (invocation), + cookie); + gsm_store_add (priv->inhibitors, gsm_inhibitor_peek_id (inhibitor), G_OBJECT (inhibitor)); + g_object_unref (inhibitor); + + gsm_exported_manager_complete_inhibit (skeleton, invocation, cookie); + + return TRUE; +} + +static gboolean +gsm_manager_uninhibit (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + guint cookie, + GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GsmInhibitor *inhibitor; + + g_debug ("GsmManager: Uninhibit %u", cookie); + + inhibitor = (GsmInhibitor *)gsm_store_find (priv->inhibitors, + (GsmStoreFunc)_find_by_cookie, + &cookie); + if (inhibitor == NULL) { + GError *new_error; + + new_error = g_error_new (GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_GENERAL, + "Unable to uninhibit: Invalid cookie"); + g_debug ("Unable to uninhibit: %s", new_error->message); + g_dbus_method_invocation_take_error (invocation, new_error); + return TRUE; + } + + g_debug ("GsmManager: removing inhibitor %s %u reason '%s' %u connection %s", + gsm_inhibitor_peek_app_id (inhibitor), + gsm_inhibitor_peek_toplevel_xid (inhibitor), + gsm_inhibitor_peek_reason (inhibitor), + gsm_inhibitor_peek_flags (inhibitor), + gsm_inhibitor_peek_bus_name (inhibitor)); + + gsm_store_remove (priv->inhibitors, gsm_inhibitor_peek_id (inhibitor)); + + gsm_exported_manager_complete_uninhibit (skeleton, invocation); + + return TRUE; +} + +static gboolean +gsm_manager_is_inhibited (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + guint flags, + GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GsmInhibitor *inhibitor; + gboolean is_inhibited; + + if (priv->inhibitors == NULL + || gsm_store_size (priv->inhibitors) == 0) { + is_inhibited = FALSE; + } else { + inhibitor = (GsmInhibitor *) gsm_store_find (priv->inhibitors, + (GsmStoreFunc)inhibitor_has_flag, + GUINT_TO_POINTER (flags)); + if (inhibitor == NULL) { + is_inhibited = FALSE; + } else { + is_inhibited = TRUE; + } + } + + gsm_exported_manager_complete_is_inhibited (skeleton, invocation, is_inhibited); + + return TRUE; +} + +static gboolean +listify_store_ids (char *id, + GObject *object, + GPtrArray **array) +{ + g_ptr_array_add (*array, g_strdup (id)); + return FALSE; +} + +static gboolean +gsm_manager_get_clients (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GPtrArray *clients; + + clients = g_ptr_array_new_with_free_func (g_free); + gsm_store_foreach (priv->clients, + (GsmStoreFunc) listify_store_ids, + &clients); + g_ptr_array_add (clients, NULL); + + gsm_exported_manager_complete_get_clients (skeleton, invocation, + (const gchar * const *) clients->pdata); + g_ptr_array_unref (clients); + + return TRUE; +} + +static gboolean +gsm_manager_get_inhibitors (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GPtrArray *inhibitors; + + inhibitors = g_ptr_array_new_with_free_func (g_free); + gsm_store_foreach (priv->inhibitors, + (GsmStoreFunc) listify_store_ids, + &inhibitors); + g_ptr_array_add (inhibitors, NULL); + + gsm_exported_manager_complete_get_inhibitors (skeleton, invocation, + (const gchar * const *) inhibitors->pdata); + g_ptr_array_unref (inhibitors); + + return TRUE; +} + +static gboolean +_app_has_autostart_condition (const char *id, + GsmApp *app, + const char *condition) +{ + gboolean has; + gboolean disabled; + + has = gsm_app_has_autostart_condition (app, condition); + disabled = gsm_app_peek_is_disabled (app); + + return has && !disabled; +} + +static gboolean +gsm_manager_is_autostart_condition_handled (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + const char *condition, + GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GsmApp *app; + gboolean handled; + + app = (GsmApp *) gsm_store_find (priv->apps,( + GsmStoreFunc) _app_has_autostart_condition, + (char *)condition); + + if (app != NULL) { + handled = TRUE; + } else { + handled = FALSE; + } + + gsm_exported_manager_complete_is_autostart_condition_handled (skeleton, invocation, handled); + + return TRUE; +} + +static gboolean +gsm_manager_is_session_running (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + gsm_exported_manager_complete_is_session_running (skeleton, invocation, + priv->phase == GSM_MANAGER_PHASE_RUNNING); + return TRUE; +} + +static void +on_session_connection_closed (GDBusConnection *connection, + gboolean remote_peer_vanished, + GError *error, + gpointer user_data) +{ + GsmManager *manager; + GsmManagerPrivate *priv; + + manager = GSM_MANAGER (user_data); + priv = gsm_manager_get_instance_private (manager); + + g_debug ("GsmManager: dbus disconnected; disconnecting dbus clients..."); + priv->dbus_disconnected = TRUE; + remove_clients_for_connection (manager, NULL); +} + +static gboolean +register_manager (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GDBusConnection *connection; + GsmExportedManager *skeleton; + GError *error = NULL; + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + + if (error != NULL) { + g_critical ("error getting session bus: %s", error->message); + g_error_free (error); + + exit (1); + } + + skeleton = gsm_exported_manager_skeleton_new (); + g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (skeleton), + connection, + GSM_MANAGER_DBUS_PATH, &error); + + if (error != NULL) { + g_critical ("error exporting manager on session bus: %s", error->message); + g_error_free (error); + + exit (1); + } + + g_signal_connect (skeleton, "handle-can-reboot-to-firmware-setup", + G_CALLBACK (gsm_manager_can_reboot_to_firmware_setup), manager); + g_signal_connect (skeleton, "handle-can-shutdown", + G_CALLBACK (gsm_manager_can_shutdown), manager); + g_signal_connect (skeleton, "handle-get-clients", + G_CALLBACK (gsm_manager_get_clients), manager); + g_signal_connect (skeleton, "handle-get-inhibitors", + G_CALLBACK (gsm_manager_get_inhibitors), manager); + g_signal_connect (skeleton, "handle-get-locale", + G_CALLBACK (gsm_manager_get_locale), manager); + g_signal_connect (skeleton, "handle-inhibit", + G_CALLBACK (gsm_manager_inhibit), manager); + g_signal_connect (skeleton, "handle-initialization-error", + G_CALLBACK (gsm_manager_initialization_error), manager); + g_signal_connect (skeleton, "handle-is-autostart-condition-handled", + G_CALLBACK (gsm_manager_is_autostart_condition_handled), manager); + g_signal_connect (skeleton, "handle-is-inhibited", + G_CALLBACK (gsm_manager_is_inhibited), manager); + g_signal_connect (skeleton, "handle-is-session-running", + G_CALLBACK (gsm_manager_is_session_running), manager); + g_signal_connect (skeleton, "handle-logout", + G_CALLBACK (gsm_manager_logout_dbus), manager); + g_signal_connect (skeleton, "handle-reboot", + G_CALLBACK (gsm_manager_reboot), manager); + g_signal_connect (skeleton, "handle-register-client", + G_CALLBACK (gsm_manager_register_client), manager); + g_signal_connect (skeleton, "handle-set-reboot-to-firmware-setup", + G_CALLBACK (gsm_manager_set_reboot_to_firmware_setup), manager); + g_signal_connect (skeleton, "handle-setenv", + G_CALLBACK (gsm_manager_setenv), manager); + g_signal_connect (skeleton, "handle-initialized", + G_CALLBACK (gsm_manager_initialized), manager); + g_signal_connect (skeleton, "handle-shutdown", + G_CALLBACK (gsm_manager_shutdown), manager); + g_signal_connect (skeleton, "handle-uninhibit", + G_CALLBACK (gsm_manager_uninhibit), manager); + g_signal_connect (skeleton, "handle-unregister-client", + G_CALLBACK (gsm_manager_unregister_client), manager); + + priv->dbus_disconnected = FALSE; + g_signal_connect (connection, "closed", + G_CALLBACK (on_session_connection_closed), manager); + + priv->connection = connection; + priv->skeleton = skeleton; + + g_signal_connect (priv->system, "notify::active", + G_CALLBACK (on_gsm_system_active_changed), manager); + + /* cold-plug SessionIsActive */ + on_gsm_system_active_changed (priv->system, NULL, manager); + + return TRUE; +} + +static gboolean +idle_timeout_get_mapping (GValue *value, + GVariant *variant, + gpointer user_data) +{ + guint32 idle_timeout; + + idle_timeout = g_variant_get_uint32 (variant); + g_value_set_uint (value, idle_timeout * 1000); + + return TRUE; +} + +static void +gsm_manager_init (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + priv->settings = g_settings_new (GSM_MANAGER_SCHEMA); + priv->session_settings = g_settings_new (SESSION_SCHEMA); + priv->screensaver_settings = g_settings_new (SCREENSAVER_SCHEMA); + priv->lockdown_settings = g_settings_new (LOCKDOWN_SCHEMA); + + priv->inhibitors = gsm_store_new (); + g_signal_connect (priv->inhibitors, + "added", + G_CALLBACK (on_store_inhibitor_added), + manager); + g_signal_connect (priv->inhibitors, + "removed", + G_CALLBACK (on_store_inhibitor_removed), + manager); + + priv->apps = gsm_store_new (); + + priv->presence = gsm_presence_new (); + g_signal_connect (priv->presence, + "status-changed", + G_CALLBACK (on_presence_status_changed), + manager); + + g_settings_bind_with_mapping (priv->session_settings, + KEY_IDLE_DELAY, + priv->presence, + "idle-timeout", + G_SETTINGS_BIND_GET, + idle_timeout_get_mapping, + NULL, + NULL, NULL); + + priv->system = gsm_get_system (); + priv->shell = gsm_get_shell (); + priv->end_session_cancellable = g_cancellable_new (); +} + +GsmManager * +gsm_manager_get (void) +{ + return manager_object; +} + +GsmManager * +gsm_manager_new (GsmStore *client_store, + gboolean failsafe, + gboolean systemd_managed) +{ + if (manager_object != NULL) { + g_object_ref (manager_object); + } else { + gboolean res; + + manager_object = g_object_new (GSM_TYPE_MANAGER, + "client-store", client_store, + "failsafe", failsafe, + "systemd-managed", systemd_managed, + NULL); + + g_object_add_weak_pointer (manager_object, + (gpointer *) &manager_object); + res = register_manager (manager_object); + if (! res) { + g_object_unref (manager_object); + return NULL; + } + } + + return GSM_MANAGER (manager_object); +} + +static void +disconnect_shell_dialog_signals (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + if (priv->shell_end_session_dialog_canceled_id != 0) { + g_signal_handler_disconnect (priv->shell, + priv->shell_end_session_dialog_canceled_id); + priv->shell_end_session_dialog_canceled_id = 0; + } + + if (priv->shell_end_session_dialog_confirmed_logout_id != 0) { + g_signal_handler_disconnect (priv->shell, + priv->shell_end_session_dialog_confirmed_logout_id); + priv->shell_end_session_dialog_confirmed_logout_id = 0; + } + + if (priv->shell_end_session_dialog_confirmed_shutdown_id != 0) { + g_signal_handler_disconnect (priv->shell, + priv->shell_end_session_dialog_confirmed_shutdown_id); + priv->shell_end_session_dialog_confirmed_shutdown_id = 0; + } + + if (priv->shell_end_session_dialog_confirmed_reboot_id != 0) { + g_signal_handler_disconnect (priv->shell, + priv->shell_end_session_dialog_confirmed_reboot_id); + priv->shell_end_session_dialog_confirmed_reboot_id = 0; + } + + if (priv->shell_end_session_dialog_open_failed_id != 0) { + g_signal_handler_disconnect (priv->shell, + priv->shell_end_session_dialog_open_failed_id); + priv->shell_end_session_dialog_open_failed_id = 0; + } +} + +static void +on_shell_end_session_dialog_canceled (GsmShell *shell, + GsmManager *manager) +{ + cancel_end_session (manager); + disconnect_shell_dialog_signals (manager); +} + +static void +_handle_end_session_dialog_response (GsmManager *manager, + GsmManagerLogoutType logout_type) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + /* Note we're checking for END_SESSION here and + * QUERY_END_SESSION in the fallback cases elsewhere. + * + * That's because they run at different times in the logout + * process. The shell combines the inhibit and + * confirmation dialogs, so it gets displayed after we've collected + * inhibitors. The fallback code has two distinct dialogs, once of + * which we can (and do show) before collecting the inhibitors. + */ + if (priv->phase >= GSM_MANAGER_PHASE_END_SESSION) { + /* Already shutting down, nothing more to do */ + return; + } + + priv->logout_mode = GSM_MANAGER_LOGOUT_MODE_FORCE; + priv->logout_type = logout_type; + end_phase (manager); +} + +static void +on_shell_end_session_dialog_confirmed_logout (GsmShell *shell, + GsmManager *manager) +{ + _handle_end_session_dialog_response (manager, GSM_MANAGER_LOGOUT_LOGOUT); + disconnect_shell_dialog_signals (manager); +} + +static void +on_shell_end_session_dialog_confirmed_shutdown (GsmShell *shell, + GsmManager *manager) +{ + _handle_end_session_dialog_response (manager, GSM_MANAGER_LOGOUT_SHUTDOWN); + disconnect_shell_dialog_signals (manager); +} + +static void +on_shell_end_session_dialog_confirmed_reboot (GsmShell *shell, + GsmManager *manager) +{ + _handle_end_session_dialog_response (manager, GSM_MANAGER_LOGOUT_REBOOT); + disconnect_shell_dialog_signals (manager); +} + +static void +connect_shell_dialog_signals (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + if (priv->shell_end_session_dialog_canceled_id != 0) + return; + + priv->shell_end_session_dialog_canceled_id = + g_signal_connect (priv->shell, + "end-session-dialog-canceled", + G_CALLBACK (on_shell_end_session_dialog_canceled), + manager); + + priv->shell_end_session_dialog_open_failed_id = + g_signal_connect (priv->shell, + "end-session-dialog-open-failed", + G_CALLBACK (on_shell_end_session_dialog_canceled), + manager); + + priv->shell_end_session_dialog_confirmed_logout_id = + g_signal_connect (priv->shell, + "end-session-dialog-confirmed-logout", + G_CALLBACK (on_shell_end_session_dialog_confirmed_logout), + manager); + + priv->shell_end_session_dialog_confirmed_shutdown_id = + g_signal_connect (priv->shell, + "end-session-dialog-confirmed-shutdown", + G_CALLBACK (on_shell_end_session_dialog_confirmed_shutdown), + manager); + + priv->shell_end_session_dialog_confirmed_reboot_id = + g_signal_connect (priv->shell, + "end-session-dialog-confirmed-reboot", + G_CALLBACK (on_shell_end_session_dialog_confirmed_reboot), + manager); +} + +static void +show_shell_end_session_dialog (GsmManager *manager, + GsmShellEndSessionDialogType type) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + if (!gsm_shell_is_running (priv->shell)) + return; + + gsm_shell_open_end_session_dialog (priv->shell, + type, + priv->inhibitors); + connect_shell_dialog_signals (manager); +} + +/* + dbus-send --session --type=method_call --print-reply + --dest=org.gnome.SessionManager + /org/gnome/SessionManager + org.freedesktop.DBus.Introspectable.Introspect +*/ + +gboolean +gsm_manager_set_phase (GsmManager *manager, + GsmManagerPhase phase) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + g_return_val_if_fail (GSM_IS_MANAGER (manager), FALSE); + priv->phase = phase; + return (TRUE); +} + +static void +append_app (GsmManager *manager, + GsmApp *app, + gboolean is_required) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + const char *id; + const char *app_id; + GsmApp *dup; + + id = gsm_app_peek_id (app); + if (IS_STRING_EMPTY (id)) { + g_debug ("GsmManager: not adding app: no id"); + return; + } + + dup = (GsmApp *)gsm_store_lookup (priv->apps, id); + if (dup != NULL) { + g_debug ("GsmManager: not adding app: already added"); + return; + } + + app_id = gsm_app_peek_app_id (app); + if (IS_STRING_EMPTY (app_id)) { + g_debug ("GsmManager: not adding app: no app-id"); + return; + } + + dup = find_app_for_app_id (manager, app_id); + if (dup != NULL) { + g_debug ("GsmManager: not adding app: app-id '%s' already exists", app_id); + + if (is_required && + !g_slist_find (priv->required_apps, dup)) { + g_debug ("GsmManager: making app '%s' required", gsm_app_peek_app_id (dup)); + priv->required_apps = g_slist_prepend (priv->required_apps, dup); + } + + return; + } + + gsm_store_add (priv->apps, id, G_OBJECT (app)); + if (is_required) { + g_debug ("GsmManager: adding required app %s", gsm_app_peek_app_id (app)); + priv->required_apps = g_slist_prepend (priv->required_apps, app); + } +} + +static gboolean +add_autostart_app_internal (GsmManager *manager, + const char *path, + gboolean is_required) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GsmApp *app; + char **internal_provides; + GError *error = NULL; + + g_return_val_if_fail (GSM_IS_MANAGER (manager), FALSE); + g_return_val_if_fail (path != NULL, FALSE); + + /* Note: if we cannot add the app because its service is already + * provided, because its app-id is taken, or because of any other + * reason meaning there is already an app playing its role, then we + * should make sure that relevant properties (like + * provides/is_required) are set in the pre-existing app if needed. */ + app = gsm_autostart_app_new (path, priv->systemd_managed, &error); + if (app == NULL) { + g_warning ("%s", error->message); + g_clear_error (&error); + return FALSE; + } + + internal_provides = gsm_app_get_provides (app); + if (internal_provides) { + int i; + gboolean provided = FALSE; + + for (i = 0; internal_provides[i] != NULL; i++) { + GsmApp *dup; + + dup = (GsmApp *)gsm_store_find (priv->apps, + (GsmStoreFunc)_find_app_provides, + (char *)internal_provides[i]); + if (dup != NULL) { + g_debug ("GsmManager: service '%s' is already provided", internal_provides[i]); + + if (is_required && + !g_slist_find (priv->required_apps, dup)) { + g_debug ("GsmManager: making app '%s' required", gsm_app_peek_app_id (dup)); + priv->required_apps = g_slist_prepend (priv->required_apps, dup); + } + + provided = TRUE; + break; + } + } + + g_strfreev (internal_provides); + + if (provided) { + g_object_unref (app); + return FALSE; + } + } + + g_debug ("GsmManager: read %s", path); + append_app (manager, app, is_required); + g_object_unref (app); + + return TRUE; +} + +gboolean +gsm_manager_add_autostart_app (GsmManager *manager, + const char *path) +{ + return add_autostart_app_internal (manager, path, FALSE); +} + +/** + * gsm_manager_add_required_app: + * @manager: a #GsmManager + * @path: Path to desktop file + * + * Similar to gsm_manager_add_autostart_app(), except marks the + * component as being required; we then try harder to ensure + * it's running and inform the user if we can't. + * + */ +gboolean +gsm_manager_add_required_app (GsmManager *manager, + const char *path) +{ + return add_autostart_app_internal (manager, path, TRUE); +} + + +gboolean +gsm_manager_add_autostart_apps_from_dir (GsmManager *manager, + const char *path) +{ + GDir *dir; + const char *name; + + g_return_val_if_fail (GSM_IS_MANAGER (manager), FALSE); + g_return_val_if_fail (path != NULL, FALSE); + + g_debug ("GsmManager: *** Adding autostart apps for %s", path); + + dir = g_dir_open (path, 0, NULL); + if (dir == NULL) { + return FALSE; + } + + while ((name = g_dir_read_name (dir))) { + char *desktop_file; + + if (!g_str_has_suffix (name, ".desktop")) { + continue; + } + + desktop_file = g_build_filename (path, name, NULL); + gsm_manager_add_autostart_app (manager, desktop_file); + g_free (desktop_file); + } + + g_dir_close (dir); + + return TRUE; +} + +static void +on_shutdown_prepared (GsmSystem *system, + gboolean success, + GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + g_debug ("GsmManager: on_shutdown_prepared, success: %d", success); + g_signal_handlers_disconnect_by_func (system, on_shutdown_prepared, manager); + + if (success) { + /* move to end-session phase */ + g_assert (priv->phase == GSM_MANAGER_PHASE_QUERY_END_SESSION); + priv->phase++; + start_phase (manager); + } else { + disconnect_shell_dialog_signals (manager); + gsm_shell_close_end_session_dialog (priv->shell); + /* back to running phase */ + cancel_end_session (manager); + } +} + +static gboolean +do_query_end_session_exit (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + gboolean reboot = FALSE; + gboolean shutdown = FALSE; + + switch (priv->logout_type) { + case GSM_MANAGER_LOGOUT_LOGOUT: + break; + case GSM_MANAGER_LOGOUT_REBOOT: + case GSM_MANAGER_LOGOUT_REBOOT_INTERACT: + reboot = TRUE; + break; + case GSM_MANAGER_LOGOUT_SHUTDOWN: + case GSM_MANAGER_LOGOUT_SHUTDOWN_INTERACT: + shutdown = TRUE; + break; + default: + g_warning ("Unexpected logout type %d in do_query_end_session_exit()", + priv->logout_type); + break; + } + + if (reboot || shutdown) { + g_signal_connect (priv->system, "shutdown-prepared", + G_CALLBACK (on_shutdown_prepared), manager); + gsm_system_prepare_shutdown (priv->system, reboot); + return FALSE; /* don't leave query end session yet */ + } + + return TRUE; /* go to end session phase */ +} diff --git a/gnome-session/gsm-manager.h b/gnome-session/gsm-manager.h new file mode 100644 index 0000000..7cd5cc4 --- /dev/null +++ b/gnome-session/gsm-manager.h @@ -0,0 +1,121 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 William Jon McCann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + */ + + +#ifndef __GSM_MANAGER_H +#define __GSM_MANAGER_H + +#include + +#include "gsm-store.h" +#include "gsm-manager-logout-mode.h" + +G_BEGIN_DECLS + +#define GSM_TYPE_MANAGER (gsm_manager_get_type ()) +G_DECLARE_DERIVABLE_TYPE (GsmManager, gsm_manager, GSM, MANAGER, GObject) + +struct _GsmManagerClass +{ + GObjectClass parent_class; + + void (* phase_changed) (GsmManager *manager, + const char *phase); +}; + +typedef enum { + /* gsm's own startup/initialization phase */ + GSM_MANAGER_PHASE_STARTUP = 0, + /* gnome-initial-setup */ + GSM_MANAGER_PHASE_EARLY_INITIALIZATION, + /* gnome-keyring-daemon */ + GSM_MANAGER_PHASE_PRE_DISPLAY_SERVER, + /* wayland compositor and XWayland */ + GSM_MANAGER_PHASE_DISPLAY_SERVER, + /* xrandr setup, gnome-settings-daemon, etc */ + GSM_MANAGER_PHASE_INITIALIZATION, + /* window/compositing managers */ + GSM_MANAGER_PHASE_WINDOW_MANAGER, + /* apps that will create _NET_WM_WINDOW_TYPE_PANEL windows */ + GSM_MANAGER_PHASE_PANEL, + /* apps that will create _NET_WM_WINDOW_TYPE_DESKTOP windows */ + GSM_MANAGER_PHASE_DESKTOP, + /* everything else */ + GSM_MANAGER_PHASE_APPLICATION, + /* done launching */ + GSM_MANAGER_PHASE_RUNNING, + /* shutting down */ + GSM_MANAGER_PHASE_QUERY_END_SESSION, + GSM_MANAGER_PHASE_END_SESSION, + GSM_MANAGER_PHASE_EXIT +} GsmManagerPhase; + +typedef enum +{ + GSM_MANAGER_ERROR_GENERAL = 0, + GSM_MANAGER_ERROR_NOT_IN_INITIALIZATION, + GSM_MANAGER_ERROR_NOT_IN_RUNNING, + GSM_MANAGER_ERROR_ALREADY_REGISTERED, + GSM_MANAGER_ERROR_NOT_REGISTERED, + GSM_MANAGER_ERROR_INVALID_OPTION, + GSM_MANAGER_ERROR_LOCKED_DOWN, + GSM_MANAGER_NUM_ERRORS +} GsmManagerError; + +#define GSM_MANAGER_ERROR gsm_manager_error_quark () +GQuark gsm_manager_error_quark (void); + +GsmManager * gsm_manager_new (GsmStore *client_store, + gboolean failsafe, + gboolean systemd_managed); +GsmManager * gsm_manager_get (void); + +gboolean gsm_manager_get_failsafe (GsmManager *manager); +gboolean gsm_manager_get_systemd_managed (GsmManager *manager); + +gboolean gsm_manager_add_autostart_app (GsmManager *manager, + const char *path); +gboolean gsm_manager_add_required_app (GsmManager *manager, + const char *path); +gboolean gsm_manager_add_autostart_apps_from_dir (GsmManager *manager, + const char *path); +gboolean gsm_manager_add_legacy_session_apps (GsmManager *manager, + const char *path); + +void gsm_manager_start (GsmManager *manager); + +char * _gsm_manager_get_default_session (GsmManager *manager); + +void _gsm_manager_set_active_session (GsmManager *manager, + const char *session_name, + gboolean is_fallback); + +void _gsm_manager_set_renderer (GsmManager *manager, + const char *renderer); + +gboolean gsm_manager_logout (GsmManager *manager, + guint logout_mode, + GError **error); + +gboolean gsm_manager_set_phase (GsmManager *manager, + GsmManagerPhase phase); + +G_END_DECLS + +#endif /* __GSM_MANAGER_H */ diff --git a/gnome-session/gsm-presence-flag.h b/gnome-session/gsm-presence-flag.h new file mode 100644 index 0000000..f3bbca4 --- /dev/null +++ b/gnome-session/gsm-presence-flag.h @@ -0,0 +1,33 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2009 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef __GSM_PRESENCE_FLAG_H__ +#define __GSM_PRESENCE_FLAG_H__ + +G_BEGIN_DECLS + +typedef enum { + GSM_PRESENCE_STATUS_AVAILABLE = 0, + GSM_PRESENCE_STATUS_INVISIBLE, + GSM_PRESENCE_STATUS_BUSY, + GSM_PRESENCE_STATUS_IDLE, +} GsmPresenceStatus; + +G_END_DECLS + +#endif /* __GSM_PRESENCE_FLAG_H__ */ diff --git a/gnome-session/gsm-presence.c b/gnome-session/gsm-presence.c new file mode 100644 index 0000000..f4aa488 --- /dev/null +++ b/gnome-session/gsm-presence.c @@ -0,0 +1,542 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2009 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#define GNOME_DESKTOP_USE_UNSTABLE_API +#include + +#include "gsm-presence.h" +#include "org.gnome.SessionManager.Presence.h" + +#define GSM_PRESENCE_DBUS_IFACE "org.gnome.SessionManager.Presence" +#define GSM_PRESENCE_DBUS_PATH "/org/gnome/SessionManager/Presence" + +#define GS_NAME "org.gnome.ScreenSaver" +#define GS_PATH "/org/gnome/ScreenSaver" +#define GS_INTERFACE "org.gnome.ScreenSaver" + +#define MAX_STATUS_TEXT 140 + +#define GSM_PRESENCE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSM_TYPE_PRESENCE, GsmPresencePrivate)) + +struct GsmPresencePrivate +{ + guint status; + guint saved_status; + char *status_text; + gboolean idle_enabled; + GnomeIdleMonitor *idle_monitor; + guint idle_watch_id; + guint idle_timeout; + gboolean screensaver_active; + GDBusConnection *connection; + GDBusProxy *screensaver_proxy; + + GsmExportedPresence *skeleton; +}; + +enum { + PROP_0, + PROP_IDLE_ENABLED, + PROP_IDLE_TIMEOUT, +}; + +enum { + STATUS_CHANGED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0, }; + +G_DEFINE_TYPE (GsmPresence, gsm_presence, G_TYPE_OBJECT) + +static const GDBusErrorEntry gsm_presence_error_entries[] = { + { GSM_PRESENCE_ERROR_GENERAL, GSM_PRESENCE_DBUS_IFACE ".GeneralError" } +}; + +GQuark +gsm_presence_error_quark (void) +{ + static volatile gsize quark_volatile = 0; + + g_dbus_error_register_error_domain ("gsm_presence_error", + &quark_volatile, + gsm_presence_error_entries, + G_N_ELEMENTS (gsm_presence_error_entries)); + return quark_volatile; +} + +static void idle_became_active_cb (GnomeIdleMonitor *idle_monitor, + guint id, + gpointer user_data); + +static void +gsm_presence_set_status (GsmPresence *presence, + guint status) +{ + if (status != presence->priv->status) { + presence->priv->status = status; + gsm_exported_presence_set_status (presence->priv->skeleton, status); + gsm_exported_presence_emit_status_changed (presence->priv->skeleton, presence->priv->status); + g_signal_emit (presence, signals[STATUS_CHANGED], 0, presence->priv->status); + } +} + +static gboolean +gsm_presence_set_status_text (GsmPresence *presence, + const char *status_text, + GError **error) +{ + g_return_val_if_fail (GSM_IS_PRESENCE (presence), FALSE); + + g_free (presence->priv->status_text); + presence->priv->status_text = NULL; + + /* check length */ + if (status_text != NULL && strlen (status_text) > MAX_STATUS_TEXT) { + g_set_error (error, + GSM_PRESENCE_ERROR, + GSM_PRESENCE_ERROR_GENERAL, + "Status text too long"); + return FALSE; + } + + if (status_text != NULL) { + presence->priv->status_text = g_strdup (status_text); + } else { + presence->priv->status_text = g_strdup (""); + } + + gsm_exported_presence_set_status_text (presence->priv->skeleton, presence->priv->status_text); + gsm_exported_presence_emit_status_text_changed (presence->priv->skeleton, presence->priv->status_text); + return TRUE; +} + +static void +set_session_idle (GsmPresence *presence, + gboolean is_idle) +{ + g_debug ("GsmPresence: setting idle: %d", is_idle); + + if (is_idle) { + if (presence->priv->status == GSM_PRESENCE_STATUS_IDLE) { + g_debug ("GsmPresence: already idle, ignoring"); + return; + } + + /* save current status */ + presence->priv->saved_status = presence->priv->status; + gsm_presence_set_status (presence, GSM_PRESENCE_STATUS_IDLE); + + gnome_idle_monitor_add_user_active_watch (presence->priv->idle_monitor, + idle_became_active_cb, + presence, + NULL); + } else { + if (presence->priv->status != GSM_PRESENCE_STATUS_IDLE) { + g_debug ("GsmPresence: already not idle, ignoring"); + return; + } + + /* restore saved status */ + gsm_presence_set_status (presence, presence->priv->saved_status); + g_debug ("GsmPresence: setting non-idle status %d", presence->priv->saved_status); + presence->priv->saved_status = GSM_PRESENCE_STATUS_AVAILABLE; + } +} + +static void +idle_became_idle_cb (GnomeIdleMonitor *idle_monitor, + guint id, + gpointer user_data) +{ + GsmPresence *presence = user_data; + set_session_idle (presence, TRUE); +} + +static void +idle_became_active_cb (GnomeIdleMonitor *idle_monitor, + guint id, + gpointer user_data) +{ + GsmPresence *presence = user_data; + set_session_idle (presence, FALSE); +} + +static void +reset_idle_watch (GsmPresence *presence) +{ + if (presence->priv->idle_watch_id > 0) { + g_debug ("GsmPresence: removing idle watch (%i)", presence->priv->idle_watch_id); + gnome_idle_monitor_remove_watch (presence->priv->idle_monitor, + presence->priv->idle_watch_id); + presence->priv->idle_watch_id = 0; + } + + if (presence->priv->idle_enabled + && presence->priv->idle_timeout > 0) { + presence->priv->idle_watch_id = gnome_idle_monitor_add_idle_watch (presence->priv->idle_monitor, + presence->priv->idle_timeout, + idle_became_idle_cb, + presence, + NULL); + g_debug ("GsmPresence: adding idle watch (%i) for %d secs", + presence->priv->idle_watch_id, + presence->priv->idle_timeout / 1000); + } +} + +static void +on_screensaver_dbus_signal (GDBusProxy *proxy, + gchar *sender_name, + gchar *signal_name, + GVariant *parameters, + GsmPresence *presence) +{ + gboolean is_active; + + if (g_strcmp0 (signal_name, "ActiveChanged") != 0) { + return; + } + + g_variant_get (parameters, "(b)", &is_active); + + if (presence->priv->screensaver_active != is_active) { + presence->priv->screensaver_active = is_active; + set_session_idle (presence, is_active); + } +} + +static void +screensaver_get_active_cb (GDBusProxy *screensaver_proxy, + GAsyncResult *res, + GsmPresence *presence) +{ + g_autoptr(GVariant) data = NULL; + g_autoptr(GError) error = NULL; + gboolean is_active; + + data = g_dbus_proxy_call_finish (screensaver_proxy, res, &error); + if (!data) { + if (error) { + g_warning ("Could not retrieve current screensaver active state: %s", + error->message); + } else { + g_warning ("Could not retrieve current screensaver active state!"); + } + + return; + } + + g_variant_get (data, "(b)", &is_active); + if (presence->priv->screensaver_active != is_active) { + presence->priv->screensaver_active = is_active; + set_session_idle (presence, is_active); + } +} + +static void +on_screensaver_name_owner_changed (GDBusProxy *screensaver_proxy, + GParamSpec *pspec, + GsmPresence *presence) +{ + gchar *name_owner; + + name_owner = g_dbus_proxy_get_name_owner (screensaver_proxy); + if (name_owner == NULL) { + g_debug ("Detected that screensaver has left the bus"); + + presence->priv->screensaver_active = FALSE; + set_session_idle (presence, FALSE); + } else { + g_debug ("Detected that screensaver has aquired the bus"); + + g_dbus_proxy_call (presence->priv->screensaver_proxy, + "GetActive", + NULL, + G_DBUS_CALL_FLAGS_NO_AUTO_START, + 1000, + NULL, + (GAsyncReadyCallback) screensaver_get_active_cb, + presence); + } + + g_free (name_owner); +} + +static gboolean +gsm_presence_set_status_text_dbus (GsmExportedPresence *skeleton, + GDBusMethodInvocation *invocation, + gchar *status_text, + GsmPresence *presence) +{ + GError *error = NULL; + + if (gsm_presence_set_status_text (presence, status_text, &error)) { + gsm_exported_presence_complete_set_status_text (skeleton, invocation); + } else { + g_dbus_method_invocation_take_error (invocation, error); + } + + return TRUE; +} + +static gboolean +gsm_presence_set_status_dbus (GsmExportedPresence *skeleton, + GDBusMethodInvocation *invocation, + guint status, + GsmPresence *presence) +{ + gsm_presence_set_status (presence, status); + gsm_exported_presence_complete_set_status (skeleton, invocation); + return TRUE; +} + +static gboolean +register_presence (GsmPresence *presence) +{ + GError *error; + GsmExportedPresence *skeleton; + + error = NULL; + presence->priv->connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + if (error != NULL) { + g_critical ("error getting session bus: %s", error->message); + g_error_free (error); + return FALSE; + } + + skeleton = gsm_exported_presence_skeleton_new (); + presence->priv->skeleton = skeleton; + g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (skeleton), + presence->priv->connection, + GSM_PRESENCE_DBUS_PATH, &error); + if (error != NULL) { + g_critical ("error registering presence object on session bus: %s", error->message); + g_error_free (error); + return FALSE; + } + + g_signal_connect (skeleton, "handle-set-status", + G_CALLBACK (gsm_presence_set_status_dbus), presence); + g_signal_connect (skeleton, "handle-set-status-text", + G_CALLBACK (gsm_presence_set_status_text_dbus), presence); + + return TRUE; +} + +static GObject * +gsm_presence_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GsmPresence *presence; + gboolean res; + GError *error = NULL; + + presence = GSM_PRESENCE (G_OBJECT_CLASS (gsm_presence_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + + res = register_presence (presence); + if (! res) { + g_warning ("Unable to register presence with session bus"); + } + + /* This only connects to signals and resolves the current name owner + * synchronously. It is important to not auto-start the service! + */ + presence->priv->screensaver_proxy = g_dbus_proxy_new_sync (presence->priv->connection, + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START | + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, + NULL, + GS_NAME, + GS_PATH, + GS_INTERFACE, + NULL, &error); + if (error != NULL) { + g_critical ("Unable to create a DBus proxy for GnomeScreensaver: %s", + error->message); + g_error_free (error); + } else { + g_signal_connect (presence->priv->screensaver_proxy, "notify::g-name-owner", + G_CALLBACK (on_screensaver_name_owner_changed), presence); + g_signal_connect (presence->priv->screensaver_proxy, "g-signal", + G_CALLBACK (on_screensaver_dbus_signal), presence); + } + + return G_OBJECT (presence); +} + +static void +gsm_presence_init (GsmPresence *presence) +{ + presence->priv = GSM_PRESENCE_GET_PRIVATE (presence); + + presence->priv->idle_monitor = gnome_idle_monitor_new (); +} + +void +gsm_presence_set_idle_enabled (GsmPresence *presence, + gboolean enabled) +{ + g_return_if_fail (GSM_IS_PRESENCE (presence)); + + if (presence->priv->idle_enabled != enabled) { + presence->priv->idle_enabled = enabled; + reset_idle_watch (presence); + g_object_notify (G_OBJECT (presence), "idle-enabled"); + + } +} + +void +gsm_presence_set_idle_timeout (GsmPresence *presence, + guint timeout) +{ + g_return_if_fail (GSM_IS_PRESENCE (presence)); + + if (timeout != presence->priv->idle_timeout) { + presence->priv->idle_timeout = timeout; + reset_idle_watch (presence); + g_object_notify (G_OBJECT (presence), "idle-timeout"); + } +} + +static void +gsm_presence_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GsmPresence *self; + + self = GSM_PRESENCE (object); + + switch (prop_id) { + case PROP_IDLE_ENABLED: + gsm_presence_set_idle_enabled (self, g_value_get_boolean (value)); + break; + case PROP_IDLE_TIMEOUT: + gsm_presence_set_idle_timeout (self, g_value_get_uint (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gsm_presence_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GsmPresence *self; + + self = GSM_PRESENCE (object); + + switch (prop_id) { + case PROP_IDLE_ENABLED: + g_value_set_boolean (value, self->priv->idle_enabled); + break; + case PROP_IDLE_TIMEOUT: + g_value_set_uint (value, self->priv->idle_timeout); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gsm_presence_finalize (GObject *object) +{ + GsmPresence *presence = (GsmPresence *) object; + + if (presence->priv->idle_watch_id > 0) { + gnome_idle_monitor_remove_watch (presence->priv->idle_monitor, + presence->priv->idle_watch_id); + presence->priv->idle_watch_id = 0; + } + + g_clear_pointer (&presence->priv->status_text, g_free); + g_clear_object (&presence->priv->idle_monitor); + g_clear_object (&presence->priv->screensaver_proxy); + + G_OBJECT_CLASS (gsm_presence_parent_class)->finalize (object); +} + +static void +gsm_presence_class_init (GsmPresenceClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gsm_presence_finalize; + object_class->constructor = gsm_presence_constructor; + object_class->get_property = gsm_presence_get_property; + object_class->set_property = gsm_presence_set_property; + + signals [STATUS_CHANGED] = + g_signal_new ("status-changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsmPresenceClass, status_changed), + NULL, + NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, + 1, G_TYPE_UINT); + + g_object_class_install_property (object_class, + PROP_IDLE_ENABLED, + g_param_spec_boolean ("idle-enabled", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_IDLE_TIMEOUT, + g_param_spec_uint ("idle-timeout", + "idle timeout", + "idle timeout", + 0, + G_MAXINT, + 120000, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_type_class_add_private (klass, sizeof (GsmPresencePrivate)); +} + +GsmPresence * +gsm_presence_new (void) +{ + GsmPresence *presence; + + presence = g_object_new (GSM_TYPE_PRESENCE, + NULL); + + return presence; +} diff --git a/gnome-session/gsm-presence.h b/gnome-session/gsm-presence.h new file mode 100644 index 0000000..8646936 --- /dev/null +++ b/gnome-session/gsm-presence.h @@ -0,0 +1,75 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2009 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef __GSM_PRESENCE_H__ +#define __GSM_PRESENCE_H__ + +#include +#include + +#include "gsm-presence-flag.h" + +G_BEGIN_DECLS + +#define GSM_TYPE_PRESENCE (gsm_presence_get_type ()) +#define GSM_PRESENCE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_PRESENCE, GsmPresence)) +#define GSM_PRESENCE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_PRESENCE, GsmPresenceClass)) +#define GSM_IS_PRESENCE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_PRESENCE)) +#define GSM_IS_PRESENCE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_PRESENCE)) +#define GSM_PRESENCE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSM_TYPE_PRESENCE, GsmPresenceClass)) + +typedef struct _GsmPresence GsmPresence; +typedef struct _GsmPresenceClass GsmPresenceClass; + +typedef struct GsmPresencePrivate GsmPresencePrivate; + +struct _GsmPresence +{ + GObject parent; + GsmPresencePrivate *priv; +}; + +struct _GsmPresenceClass +{ + GObjectClass parent_class; + + void (* status_changed) (GsmPresence *presence, + guint status); +}; + +typedef enum +{ + GSM_PRESENCE_ERROR_GENERAL = 0, + GSM_PRESENCE_NUM_ERRORS +} GsmPresenceError; + +#define GSM_PRESENCE_ERROR gsm_presence_error_quark () +GQuark gsm_presence_error_quark (void); + +GType gsm_presence_get_type (void) G_GNUC_CONST; + +GsmPresence * gsm_presence_new (void); + +void gsm_presence_set_idle_enabled (GsmPresence *presence, + gboolean enabled); +void gsm_presence_set_idle_timeout (GsmPresence *presence, + guint n_seconds); + +G_END_DECLS + +#endif /* __GSM_PRESENCE_H__ */ diff --git a/gnome-session/gsm-process-helper.c b/gnome-session/gsm-process-helper.c new file mode 100644 index 0000000..01d581d --- /dev/null +++ b/gnome-session/gsm-process-helper.c @@ -0,0 +1,113 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2010 Novell, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include + +#include +#include + +#include "gsm-process-helper.h" + +typedef struct { + gboolean done; + GSubprocess *process; + gboolean caught_error; + GError **error; + GMainContext *maincontext; + GSource *timeout_source; +} GsmProcessHelper; + +static void +on_child_exited (GObject *source, + GAsyncResult *result, + gpointer data) +{ + GsmProcessHelper *helper = data; + + helper->done = TRUE; + + if (!g_subprocess_wait_check_finish ((GSubprocess*)source, result, + helper->caught_error ? NULL : helper->error)) + helper->caught_error = TRUE; + + g_clear_pointer (&helper->timeout_source, g_source_destroy); + + g_main_context_wakeup (helper->maincontext); +} + +static gboolean +on_child_timeout (gpointer data) +{ + GsmProcessHelper *helper = data; + + g_assert (!helper->done); + + g_subprocess_force_exit (helper->process); + + g_set_error_literal (helper->error, + G_IO_CHANNEL_ERROR, + G_IO_CHANNEL_ERROR_FAILED, + "Timed out"); + + helper->timeout_source = NULL; + + return FALSE; +} + +gboolean +gsm_process_helper (const char *command_line, + unsigned int timeout, + GError **error) +{ + gboolean ret = FALSE; + GsmProcessHelper helper = { 0, }; + gchar **argv = NULL; + GMainContext *subcontext = NULL; + + if (!g_shell_parse_argv (command_line, NULL, &argv, error)) + goto out; + + helper.error = error; + + subcontext = g_main_context_new (); + g_main_context_push_thread_default (subcontext); + + helper.process = g_subprocess_newv ((const char*const*)argv, 0, error); + if (!helper.process) + goto out; + + g_subprocess_wait_async (helper.process, NULL, on_child_exited, &helper); + + helper.timeout_source = g_timeout_source_new (timeout); + + g_source_set_callback (helper.timeout_source, on_child_timeout, &helper, NULL); + g_source_attach (helper.timeout_source, subcontext); + + while (!helper.done) + g_main_context_iteration (subcontext, TRUE); + + ret = helper.caught_error; + out: + g_strfreev (argv); + if (subcontext) { + g_main_context_pop_thread_default (subcontext); + g_main_context_unref (subcontext); + } + g_clear_object (&helper.process); + return ret; +} diff --git a/gnome-session/gsm-process-helper.h b/gnome-session/gsm-process-helper.h new file mode 100644 index 0000000..6ba6ea4 --- /dev/null +++ b/gnome-session/gsm-process-helper.h @@ -0,0 +1,32 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2010 Novell, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef __GSM_PROCESS_HELPER_H +#define __GSM_PROCESS_HELPER_H + +#include + +G_BEGIN_DECLS + +int gsm_process_helper (const char *command_line, + unsigned int timeout, + GError **error); + +G_END_DECLS + +#endif /* __GSM_PROCESS_HELPER_H */ diff --git a/gnome-session/gsm-session-fill.c b/gnome-session/gsm-session-fill.c new file mode 100644 index 0000000..93a6dbc --- /dev/null +++ b/gnome-session/gsm-session-fill.c @@ -0,0 +1,334 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2006, 2010 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include + +#include "gsm-session-fill.h" + +#include "gsm-system.h" +#include "gsm-manager.h" +#include "gsm-process-helper.h" +#include "gsm-util.h" + +#define GSM_KEYFILE_SESSION_GROUP "GNOME Session" +#define GSM_KEYFILE_RUNNABLE_KEY "IsRunnableHelper" +#define GSM_KEYFILE_FALLBACK_KEY "FallbackSession" +#define GSM_KEYFILE_REQUIRED_COMPONENTS_KEY "RequiredComponents" + +/* See https://bugzilla.gnome.org/show_bug.cgi?id=641992 for discussion */ +#define GSM_RUNNABLE_HELPER_TIMEOUT 3000 /* ms */ + +typedef void (*GsmFillHandleComponent) (const char *component, + const char *app_path, + gpointer user_data); + +static void +handle_required_components (GKeyFile *keyfile, + gboolean look_in_saved_session, + GsmFillHandleComponent callback, + gpointer user_data) +{ + char **required_components; + int i; + + g_assert (keyfile != NULL); + g_assert (callback != NULL); + + required_components = g_key_file_get_string_list (keyfile, + GSM_KEYFILE_SESSION_GROUP, + GSM_KEYFILE_REQUIRED_COMPONENTS_KEY, + NULL, NULL); + + if (!required_components) + return; + + for (i = 0; required_components[i] != NULL; i++) { + char *app_path; + + app_path = gsm_util_find_desktop_file_for_app_name (required_components[i], + look_in_saved_session, TRUE); + callback (required_components[i], app_path, user_data); + g_free (app_path); + } + + g_strfreev (required_components); +} + +static void +check_required_components_helper (const char *component, + const char *app_path, + gpointer user_data) +{ + gboolean *error = user_data; + + if (app_path == NULL) { + g_warning ("Unable to find required component '%s'", component); + *error = TRUE; + } +} + +static gboolean +check_required (GKeyFile *keyfile) +{ + gboolean error = FALSE; + + g_debug ("fill: *** Checking required components"); + + handle_required_components (keyfile, FALSE, + check_required_components_helper, &error); + + g_debug ("fill: *** Done checking required components"); + + return !error; +} + +static void +maybe_load_saved_session_apps (GsmManager *manager) +{ + GsmSystem *system; + gboolean is_login; + + system = gsm_get_system (); + is_login = gsm_system_is_login_session (system); + g_object_unref (system); + + if (is_login) + return; + + gsm_manager_add_autostart_apps_from_dir (manager, gsm_util_get_saved_session_dir ()); +} + +static void +append_required_components_helper (const char *component, + const char *app_path, + gpointer user_data) +{ + GsmManager *manager = user_data; + + if (app_path == NULL) + g_warning ("Unable to find required component '%s'", component); + else + gsm_manager_add_required_app (manager, app_path); +} + + +static void +load_standard_apps (GsmManager *manager, + GKeyFile *keyfile) +{ + /* Note that saving/restoring sessions is not really possible on systemd, as + * XSMP clients cannot be reliably mapped to .desktop files. */ + g_debug ("fill: *** Adding required components"); + handle_required_components (keyfile, + !gsm_manager_get_failsafe (manager) && !gsm_manager_get_systemd_managed (manager), + append_required_components_helper, manager); + g_debug ("fill: *** Done adding required components"); + + if (!gsm_manager_get_failsafe (manager)) { + char **autostart_dirs; + int i; + + autostart_dirs = gsm_util_get_autostart_dirs (); + + if (!gsm_manager_get_systemd_managed (manager)) + maybe_load_saved_session_apps (manager); + + for (i = 0; autostart_dirs[i]; i++) { + gsm_manager_add_autostart_apps_from_dir (manager, + autostart_dirs[i]); + } + + g_strfreev (autostart_dirs); + } +} + +static GKeyFile * +get_session_keyfile_if_valid (const char *path) +{ + GKeyFile *keyfile; + gsize len; + char **list; + + g_debug ("fill: *** Looking if %s is a valid session file", path); + + keyfile = g_key_file_new (); + + if (!g_key_file_load_from_file (keyfile, path, G_KEY_FILE_NONE, NULL)) { + g_debug ("Cannot use session '%s': non-existing or invalid file.", path); + goto error; + } + + if (!g_key_file_has_group (keyfile, GSM_KEYFILE_SESSION_GROUP)) { + g_warning ("Cannot use session '%s': no '%s' group.", path, GSM_KEYFILE_SESSION_GROUP); + goto error; + } + + list = g_key_file_get_string_list (keyfile, + GSM_KEYFILE_SESSION_GROUP, + GSM_KEYFILE_REQUIRED_COMPONENTS_KEY, + &len, NULL); + if (list) + g_strfreev (list); + if (len == 0) + g_warning ("Session '%s': no component in the session.", path); + + return keyfile; + +error: + g_key_file_free (keyfile); + return NULL; +} + +/** + * find_valid_session_keyfile: + * @session: name of session + * + * We look for the session file in XDG_CONFIG_HOME, XDG_CONFIG_DIRS and + * XDG_DATA_DIRS. This enables users and sysadmins to override a specific + * session that is shipped in XDG_DATA_DIRS. + */ +static GKeyFile * +find_valid_session_keyfile (const char *session) +{ + GPtrArray *dirs; + const char * const *system_config_dirs; + const char * const *system_data_dirs; + int i; + GKeyFile *keyfile; + char *basename; + + dirs = g_ptr_array_new (); + + g_ptr_array_add (dirs, (gpointer) g_get_user_config_dir ()); + + system_config_dirs = g_get_system_config_dirs (); + for (i = 0; system_config_dirs[i]; i++) + g_ptr_array_add (dirs, (gpointer) system_config_dirs[i]); + + system_data_dirs = g_get_system_data_dirs (); + for (i = 0; system_data_dirs[i]; i++) + g_ptr_array_add (dirs, (gpointer) system_data_dirs[i]); + + keyfile = NULL; + basename = g_strdup_printf ("%s.session", session); + + for (i = 0; i < dirs->len; i++) { + g_autofree gchar *path = g_build_filename (dirs->pdata[i], "gnome-session", "sessions", basename, NULL); + keyfile = get_session_keyfile_if_valid (path); + if (keyfile != NULL) + break; + } + + if (dirs) + g_ptr_array_free (dirs, TRUE); + if (basename) + g_free (basename); + + return keyfile; +} + +static GKeyFile * +get_session_keyfile (const char *session, + char **actual_session, + gboolean *is_fallback) +{ + GKeyFile *keyfile; + gboolean session_runnable; + char *value; + GError *error = NULL; + + *actual_session = NULL; + + g_debug ("fill: *** Getting session '%s'", session); + + keyfile = find_valid_session_keyfile (session); + + if (!keyfile) + return NULL; + + session_runnable = TRUE; + + value = g_key_file_get_string (keyfile, + GSM_KEYFILE_SESSION_GROUP, GSM_KEYFILE_RUNNABLE_KEY, + NULL); + if (!IS_STRING_EMPTY (value)) { + g_debug ("fill: *** Launching helper '%s' to know if session is runnable", value); + session_runnable = gsm_process_helper (value, GSM_RUNNABLE_HELPER_TIMEOUT, &error); + if (!session_runnable) { + g_warning ("Session '%s' runnable check failed: %s", session, + error->message); + g_clear_error (&error); + } + } + g_free (value); + + if (session_runnable) { + session_runnable = check_required (keyfile); + } + + if (session_runnable) { + *actual_session = g_strdup (session); + if (is_fallback) + *is_fallback = FALSE; + return keyfile; + } + + g_debug ("fill: *** Session is not runnable"); + + /* We can't run this session, so try to use the fallback */ + value = g_key_file_get_string (keyfile, + GSM_KEYFILE_SESSION_GROUP, GSM_KEYFILE_FALLBACK_KEY, + NULL); + + g_key_file_free (keyfile); + keyfile = NULL; + + if (!IS_STRING_EMPTY (value)) { + if (is_fallback) + *is_fallback = TRUE; + keyfile = get_session_keyfile (value, actual_session, NULL); + } + g_free (value); + + return keyfile; +} + +gboolean +gsm_session_fill (GsmManager *manager, + const char *session) +{ + GKeyFile *keyfile; + gboolean is_fallback; + char *actual_session; + + keyfile = get_session_keyfile (session, &actual_session, &is_fallback); + + if (!keyfile) + return FALSE; + + _gsm_manager_set_active_session (manager, actual_session, is_fallback); + + g_free (actual_session); + + load_standard_apps (manager, keyfile); + + g_key_file_free (keyfile); + + return TRUE; +} diff --git a/gnome-session/gsm-session-fill.h b/gnome-session/gsm-session-fill.h new file mode 100644 index 0000000..470a441 --- /dev/null +++ b/gnome-session/gsm-session-fill.h @@ -0,0 +1,32 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2006, 2010 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef __GSM_SESSION_FILL_H +#define __GSM_SESSION_FILL_H + +#include "gsm-manager.h" + +G_BEGIN_DECLS + +gboolean gsm_session_fill (GsmManager *manager, + const char *session); + +G_END_DECLS + +#endif /* __GSM_SESSION_FILL_H */ diff --git a/gnome-session/gsm-session-save.c b/gnome-session/gsm-session-save.c new file mode 100644 index 0000000..e7f3653 --- /dev/null +++ b/gnome-session/gsm-session-save.c @@ -0,0 +1,293 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * gsm-session-save.c + * Copyright (C) 2008 Lucas Rocha. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include + +#include +#include +#include + +#include "gsm-app.h" +#include "gsm-util.h" +#include "gsm-autostart-app.h" +#include "gsm-client.h" + +#include "gsm-session-save.h" + +#define GSM_MANAGER_SCHEMA "org.gnome.SessionManager" +#define KEY_AUTOSAVE_ONE_SHOT "auto-save-session-one-shot" + + +static gboolean gsm_session_clear_saved_session (const char *directory, + GHashTable *discard_hash); + +typedef struct { + const char *dir; + GHashTable *discard_hash; + GsmStore *app_store; + GError **error; +} SessionSaveData; + +static gboolean +_app_has_app_id (const char *id, + GsmApp *app, + const char *app_id_a) +{ + const char *app_id_b; + + app_id_b = gsm_app_peek_app_id (app); + return g_strcmp0 (app_id_a, app_id_b) == 0; +} + +static gboolean +save_one_client (char *id, + GObject *object, + SessionSaveData *data) +{ + GsmClient *client; + GKeyFile *keyfile; + GsmApp *app = NULL; + const char *app_id; + char *path = NULL; + char *filename = NULL; + char *contents = NULL; + gsize length = 0; + char *discard_exec; + g_autoptr(GError) local_error = NULL; + + client = GSM_CLIENT (object); + + app_id = gsm_client_peek_app_id (client); + if (!IS_STRING_EMPTY (app_id)) { + if (g_str_has_suffix (app_id, ".desktop")) + filename = g_strdup (app_id); + else + filename = g_strdup_printf ("%s.desktop", app_id); + + path = g_build_filename (data->dir, filename, NULL); + + app = (GsmApp *)gsm_store_find (data->app_store, + (GsmStoreFunc)_app_has_app_id, + (char *)app_id); + } + keyfile = gsm_client_save (client, app, &local_error); + + if (keyfile == NULL || local_error) { + goto out; + } + + contents = g_key_file_to_data (keyfile, &length, &local_error); + + if (local_error) { + goto out; + } + + if (!path || g_file_test (path, G_FILE_TEST_EXISTS)) { + if (filename) + g_free (filename); + if (path) + g_free (path); + + filename = g_strdup_printf ("%s.desktop", + gsm_client_peek_startup_id (client)); + path = g_build_filename (data->dir, filename, NULL); + } + + g_file_set_contents (path, + contents, + length, + &local_error); + + if (local_error) { + goto out; + } + + discard_exec = g_key_file_get_string (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + GSM_AUTOSTART_APP_DISCARD_KEY, + NULL); + if (discard_exec) { + g_hash_table_insert (data->discard_hash, + discard_exec, discard_exec); + } + + g_debug ("GsmSessionSave: saved client %s to %s", id, filename); + +out: + if (keyfile != NULL) { + g_key_file_free (keyfile); + } + + g_free (contents); + g_free (filename); + g_free (path); + + /* in case of any error, stop saving session */ + if (local_error) { + g_propagate_error (data->error, g_steal_pointer (&local_error)); + + return TRUE; + } + + return FALSE; +} + +void +gsm_session_save (GsmStore *client_store, + GsmStore *app_store, + GError **error) +{ + GSettings *settings; + const char *save_dir; + SessionSaveData data; + + g_debug ("GsmSessionSave: Saving session"); + + /* Clear one shot key autosave in the event its set (so that it's actually + * one shot only) + */ + settings = g_settings_new (GSM_MANAGER_SCHEMA); + g_settings_set_boolean (settings, KEY_AUTOSAVE_ONE_SHOT, FALSE); + g_object_unref (settings); + + save_dir = gsm_util_get_saved_session_dir (); + if (save_dir == NULL) { + g_warning ("GsmSessionSave: cannot create saved session directory"); + return; + } + + data.dir = save_dir; + data.discard_hash = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); + data.app_store = app_store; + + /* remove old saved session */ + gsm_session_clear_saved_session (save_dir, data.discard_hash); + data.error = error; + + gsm_store_foreach (client_store, + (GsmStoreFunc) save_one_client, + &data); + + g_hash_table_destroy (data.discard_hash); +} + +static gboolean +gsm_session_clear_one_client (const char *filename, + GHashTable *discard_hash) +{ + gboolean result = TRUE; + GKeyFile *key_file; + char *discard_exec = NULL; + char **envp; + + g_debug ("GsmSessionSave: removing '%s' from saved session", filename); + + envp = (char **) gsm_util_listenv (); + key_file = g_key_file_new (); + if (g_key_file_load_from_file (key_file, filename, + G_KEY_FILE_NONE, NULL)) { + char **argv; + int argc; + + discard_exec = g_key_file_get_string (key_file, + G_KEY_FILE_DESKTOP_GROUP, + GSM_AUTOSTART_APP_DISCARD_KEY, + NULL); + if (!discard_exec) + goto out; + + if (discard_hash && g_hash_table_lookup (discard_hash, discard_exec)) + goto out; + + if (!g_shell_parse_argv (discard_exec, &argc, &argv, NULL)) + goto out; + + result = g_spawn_async (NULL, argv, envp, G_SPAWN_SEARCH_PATH, + NULL, NULL, NULL, NULL) && result; + + g_strfreev (argv); + } else { + result = FALSE; + } + +out: + if (key_file) + g_key_file_free (key_file); + if (discard_exec) + g_free (discard_exec); + + result = (g_unlink (filename) == 0) && result; + + return result; +} + +static gboolean +gsm_session_clear_saved_session (const char *directory, + GHashTable *discard_hash) +{ + GDir *dir; + const char *filename; + gboolean result = TRUE; + GError *error; + + g_debug ("GsmSessionSave: clearing currently saved session at %s", + directory); + + if (directory == NULL) { + return FALSE; + } + + error = NULL; + dir = g_dir_open (directory, 0, &error); + if (error) { + g_warning ("GsmSessionSave: error loading saved session directory: %s", error->message); + g_error_free (error); + return FALSE; + } + + while ((filename = g_dir_read_name (dir))) { + char *path = g_build_filename (directory, + filename, NULL); + + result = gsm_session_clear_one_client (path, discard_hash) + && result; + + g_free (path); + } + + g_dir_close (dir); + + return result; +} + +void +gsm_session_save_clear (void) +{ + const char *save_dir; + + g_debug ("GsmSessionSave: Clearing saved session"); + + save_dir = gsm_util_get_saved_session_dir (); + if (save_dir == NULL) { + g_warning ("GsmSessionSave: cannot create saved session directory"); + return; + } + + gsm_session_clear_saved_session (save_dir, NULL); +} diff --git a/gnome-session/gsm-session-save.h b/gnome-session/gsm-session-save.h new file mode 100644 index 0000000..f55c9c1 --- /dev/null +++ b/gnome-session/gsm-session-save.h @@ -0,0 +1,34 @@ +/* gsm-session-save.h + * Copyright (C) 2008 Lucas Rocha. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef __GSM_SESSION_SAVE_H__ +#define __GSM_SESSION_SAVE_H__ + +#include + +#include "gsm-store.h" + +G_BEGIN_DECLS + +void gsm_session_save (GsmStore *client_store, + GsmStore *app_store, + GError **error); +void gsm_session_save_clear (void); + +G_END_DECLS + +#endif /* __GSM_SESSION_SAVE_H__ */ diff --git a/gnome-session/gsm-shell-extensions.c b/gnome-session/gsm-shell-extensions.c new file mode 100644 index 0000000..73d898f --- /dev/null +++ b/gnome-session/gsm-shell-extensions.c @@ -0,0 +1,199 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2011 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * Authors: + * Jasper St. Pierre + */ + +#include + +#include +#include +#include + +#include "gsm-shell-extensions.h" + +#define SHELL_SCHEMA "org.gnome.shell" +#define DISABLE_EXTENSIONS_KEY "disable-user-extensions" + +#define SHELL_EXTENSIONS_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSM_TYPE_SHELL_EXTENSIONS, GsmShellExtensionsPrivate)) + +struct _GsmShellExtensionsPrivate +{ + GSettings *settings; + guint num_extensions; +}; + +G_DEFINE_TYPE (GsmShellExtensions, gsm_shell_extensions, G_TYPE_OBJECT); + +/** + * gsm_shell_extensions_finalize: + * @object: (in): A #GsmShellExtensions. + * + * Finalizer for a #GsmShellExtensions instance. Frees any resources held by + * the instance. + */ +static void +gsm_shell_extensions_finalize (GObject *object) +{ + GsmShellExtensions *extensions = GSM_SHELL_EXTENSIONS (object); + GsmShellExtensionsPrivate *priv = extensions->priv; + + g_clear_object (&priv->settings); + + G_OBJECT_CLASS (gsm_shell_extensions_parent_class)->finalize (object); +} + +/** + * gsm_shell_extensions_class_init: + * @klass: (in): A #GsmShellExtensionsClass. + * + * Initializes the #GsmShellExtensionsClass and prepares the vtable. + */ +static void +gsm_shell_extensions_class_init (GsmShellExtensionsClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->finalize = gsm_shell_extensions_finalize; + g_type_class_add_private (object_class, sizeof (GsmShellExtensionsPrivate)); +} + +static void +gsm_shell_extensions_scan_dir (GsmShellExtensions *self, + GFile *dir) +{ + GFileEnumerator *enumerator; + GFileInfo *info; + JsonParser *metadata_parser; + + metadata_parser = json_parser_new (); + + enumerator = g_file_enumerate_children (dir, + "standard::*", + G_FILE_QUERY_INFO_NONE, + NULL, + NULL); + + if (enumerator == NULL) + return; + + while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)) != NULL) + { + gchar *metadata_filename; + const gchar *metadata_uuid; + gchar *dir_uuid; + JsonObject *metadata_root; + + dir_uuid = (char *) g_file_info_get_name (info); + + metadata_filename = g_build_filename (g_file_get_path (dir), + dir_uuid, + "metadata.json", + NULL); + + if (!json_parser_load_from_file (metadata_parser, metadata_filename, NULL)) + continue; + + g_free (metadata_filename); + + metadata_root = json_node_get_object (json_parser_get_root (metadata_parser)); + + metadata_uuid = json_object_get_string_member (metadata_root, "uuid"); + if (!g_str_equal (metadata_uuid, dir_uuid)) + { + g_warning ("Extension with dirname '%s' does not match metadata's UUID of '%s'. Skipping.", + dir_uuid, metadata_uuid); + continue; + } + + self->priv->num_extensions++; + } +} + +static void +gsm_shell_extensions_scan (GsmShellExtensions *self) +{ + gchar *dirname; + GFile *dir; + const gchar * const * system_data_dirs; + int i; + + /* User data dir first. */ + dirname = g_build_filename (g_get_user_data_dir (), "gnome-shell", "extensions", NULL); + dir = g_file_new_for_path (dirname); + g_free (dirname); + + gsm_shell_extensions_scan_dir (self, dir); + g_object_unref (dir); + + system_data_dirs = g_get_system_data_dirs (); + for (i = 0; system_data_dirs[i]; i++) + { + dirname = g_build_filename (system_data_dirs[i], "gnome-shell", "extensions", NULL); + dir = g_file_new_for_path (dirname); + g_free (dirname); + + gsm_shell_extensions_scan_dir (self, dir); + g_object_unref (dir); + } +} + +/** + * gsm_shell_extensions_init: + * @self: (in): A #GsmShellExtensions. + * + * Initializes the newly created #GsmShellExtensions instance. + */ +static void +gsm_shell_extensions_init (GsmShellExtensions *self) +{ + GSettingsSchemaSource *source; + GSettingsSchema *schema; + + self->priv = SHELL_EXTENSIONS_PRIVATE (self); + + source = g_settings_schema_source_get_default (); + schema = g_settings_schema_source_lookup (source, SHELL_SCHEMA, TRUE); + + if (schema != NULL) + { + self->priv->settings = g_settings_new_full (schema, NULL, NULL); + g_settings_schema_unref (schema); + } + + if (self->priv->settings != NULL) + gsm_shell_extensions_scan (self); +} + +gboolean +gsm_shell_extensions_disable_all (GsmShellExtensions *self) +{ + return g_settings_set_boolean (self->priv->settings, + DISABLE_EXTENSIONS_KEY, + TRUE); +} + +guint +gsm_shell_extensions_n_extensions (GsmShellExtensions *self) +{ + if (self->priv->settings == NULL) + return 0; + + return self->priv->num_extensions; +} diff --git a/gnome-session/gsm-shell-extensions.h b/gnome-session/gsm-shell-extensions.h new file mode 100644 index 0000000..a3b3d06 --- /dev/null +++ b/gnome-session/gsm-shell-extensions.h @@ -0,0 +1,61 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2011 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * Authors: + * Jasper St. Pierre + */ + +#ifndef __GSM_SHELL_EXTENSIONS_H +#define __GSM_SHELL_EXTENSIONS_H + +#include + +G_BEGIN_DECLS + +#define GSM_TYPE_SHELL_EXTENSIONS (gsm_shell_extensions_get_type ()) +#define GSM_SHELL_EXTENSIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_SHELL_EXTENSIONS, GsmShellExtensions)) +#define GSM_SHELL_EXTENSIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_SHELL_EXTENSIONS, GsmShellExtensionsClass)) +#define GSM_IS_SHELL_EXTENSIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_SHELL_EXTENSIONS)) +#define GSM_IS_SHELL_EXTENSIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_SHELL_EXTENSIONS)) +#define GSM_SHELL_EXTENSIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSM_TYPE_SHELL_EXTENSIONS, GsmShellExtensionsClass)) + +typedef struct _GsmShellExtensions GsmShellExtensions; +typedef struct _GsmShellExtensionsClass GsmShellExtensionsClass; +typedef struct _GsmShellExtensionsPrivate GsmShellExtensionsPrivate; + +struct _GsmShellExtensions +{ + GObject parent; + + /*< private >*/ + GsmShellExtensionsPrivate *priv; +}; + +struct _GsmShellExtensionsClass +{ + GObjectClass parent_class; +}; + +GType gsm_shell_extensions_get_type (void) G_GNUC_CONST; + +gboolean gsm_shell_extensions_disable_all (GsmShellExtensions *self); + +guint gsm_shell_extensions_n_extensions (GsmShellExtensions *self); + +G_END_DECLS + +#endif /* __GSM_SHELL_EXTENSIONS_H */ diff --git a/gnome-session/gsm-shell.c b/gnome-session/gsm-shell.c new file mode 100644 index 0000000..04cfa2f --- /dev/null +++ b/gnome-session/gsm-shell.c @@ -0,0 +1,507 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2010 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include "config.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include "gsm-inhibitor.h" +#include "gsm-shell.h" + +#define SHELL_NAME "org.gnome.Shell" +#define SHELL_PATH "/org/gnome/Shell" +#define SHELL_INTERFACE "org.gnome.Shell" + +#define SHELL_END_SESSION_DIALOG_PATH "/org/gnome/SessionManager/EndSessionDialog" +#define SHELL_END_SESSION_DIALOG_INTERFACE "org.gnome.SessionManager.EndSessionDialog" + +#define AUTOMATIC_ACTION_TIMEOUT 60 + +#define GSM_SHELL_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSM_TYPE_SHELL, GsmShellPrivate)) + +struct _GsmShellPrivate +{ + GDBusProxy *end_session_dialog_proxy; + GsmStore *inhibitors; + + guint32 is_running : 1; + + gboolean dialog_is_open; + GsmShellEndSessionDialogType end_session_dialog_type; + + guint update_idle_id; + guint watch_id; +}; + +enum { + PROP_0, + PROP_IS_RUNNING +}; + +enum { + END_SESSION_DIALOG_OPENED = 0, + END_SESSION_DIALOG_OPEN_FAILED, + END_SESSION_DIALOG_CLOSED, + END_SESSION_DIALOG_CANCELED, + END_SESSION_DIALOG_CONFIRMED_LOGOUT, + END_SESSION_DIALOG_CONFIRMED_SHUTDOWN, + END_SESSION_DIALOG_CONFIRMED_REBOOT, + NUMBER_OF_SIGNALS +}; + +static guint signals[NUMBER_OF_SIGNALS] = { 0 }; + +static void gsm_shell_class_init (GsmShellClass *klass); +static void gsm_shell_init (GsmShell *ck); +static void gsm_shell_finalize (GObject *object); + +static void queue_end_session_dialog_update (GsmShell *shell); + +G_DEFINE_TYPE (GsmShell, gsm_shell, G_TYPE_OBJECT); + +static void +gsm_shell_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GsmShell *shell = GSM_SHELL (object); + + switch (prop_id) { + case PROP_IS_RUNNING: + g_value_set_boolean (value, + shell->priv->is_running); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, + prop_id, + pspec); + } +} + +static void +gsm_shell_class_init (GsmShellClass *shell_class) +{ + GObjectClass *object_class; + GParamSpec *param_spec; + + object_class = G_OBJECT_CLASS (shell_class); + + object_class->finalize = gsm_shell_finalize; + object_class->get_property = gsm_shell_get_property; + + param_spec = g_param_spec_boolean ("is-running", + "Is running", + "Whether GNOME Shell is running in the session", + FALSE, + G_PARAM_READABLE); + + g_object_class_install_property (object_class, PROP_IS_RUNNING, + param_spec); + + signals [END_SESSION_DIALOG_OPENED] = + g_signal_new ("end-session-dialog-opened", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsmShellClass, end_session_dialog_opened), + NULL, NULL, NULL, + G_TYPE_NONE, 0); + + signals [END_SESSION_DIALOG_OPEN_FAILED] = + g_signal_new ("end-session-dialog-open-failed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsmShellClass, end_session_dialog_open_failed), + NULL, NULL, NULL, + G_TYPE_NONE, 0); + + signals [END_SESSION_DIALOG_CLOSED] = + g_signal_new ("end-session-dialog-closed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsmShellClass, end_session_dialog_closed), + NULL, NULL, NULL, + G_TYPE_NONE, 0); + + signals [END_SESSION_DIALOG_CANCELED] = + g_signal_new ("end-session-dialog-canceled", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsmShellClass, end_session_dialog_canceled), + NULL, NULL, NULL, + G_TYPE_NONE, 0); + + signals [END_SESSION_DIALOG_CONFIRMED_LOGOUT] = + g_signal_new ("end-session-dialog-confirmed-logout", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsmShellClass, end_session_dialog_confirmed_logout), + NULL, NULL, NULL, + G_TYPE_NONE, 0); + + signals [END_SESSION_DIALOG_CONFIRMED_SHUTDOWN] = + g_signal_new ("end-session-dialog-confirmed-shutdown", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsmShellClass, end_session_dialog_confirmed_shutdown), + NULL, NULL, NULL, + G_TYPE_NONE, 0); + + signals [END_SESSION_DIALOG_CONFIRMED_REBOOT] = + g_signal_new ("end-session-dialog-confirmed-reboot", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsmShellClass, end_session_dialog_confirmed_reboot), + NULL, NULL, NULL, + G_TYPE_NONE, 0); + + g_type_class_add_private (shell_class, sizeof (GsmShellPrivate)); +} + +static void +on_shell_name_vanished (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + GsmShell *shell = user_data; + shell->priv->is_running = FALSE; +} + +static void +on_shell_name_appeared (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + gpointer user_data) +{ + GsmShell *shell = user_data; + shell->priv->is_running = TRUE; +} + +static void +gsm_shell_ensure_connection (GsmShell *shell) +{ + if (shell->priv->watch_id != 0) { + return; + } + + shell->priv->watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION, + SHELL_NAME, + G_BUS_NAME_WATCHER_FLAGS_NONE, + on_shell_name_appeared, + on_shell_name_vanished, + shell, NULL); +} + +static void +gsm_shell_init (GsmShell *shell) +{ + shell->priv = GSM_SHELL_GET_PRIVATE (shell); + + gsm_shell_ensure_connection (shell); +} + +static void +gsm_shell_finalize (GObject *object) +{ + GsmShell *shell; + GObjectClass *parent_class; + + shell = GSM_SHELL (object); + + parent_class = G_OBJECT_CLASS (gsm_shell_parent_class); + + g_object_unref (shell->priv->inhibitors); + + if (shell->priv->watch_id != 0) { + g_bus_unwatch_name (shell->priv->watch_id); + shell->priv->watch_id = 0; + } + + if (parent_class->finalize != NULL) { + parent_class->finalize (object); + } +} + +GsmShell * +gsm_shell_new (void) +{ + GsmShell *shell; + + shell = g_object_new (GSM_TYPE_SHELL, NULL); + + return shell; +} + +GsmShell * +gsm_get_shell (void) +{ + static GsmShell *shell = NULL; + + if (shell == NULL) { + shell = gsm_shell_new (); + } + + return g_object_ref (shell); +} + +gboolean +gsm_shell_is_running (GsmShell *shell) +{ + gsm_shell_ensure_connection (shell); + + return shell->priv->is_running; +} + +static gboolean +add_inhibitor_to_array (const char *id, + GsmInhibitor *inhibitor, + GVariantBuilder *builder) +{ + g_variant_builder_add (builder, "o", gsm_inhibitor_peek_id (inhibitor)); + return FALSE; +} + +static GVariant * +get_array_from_store (GsmStore *inhibitors) +{ + GVariantBuilder builder; + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("ao")); + gsm_store_foreach (inhibitors, + (GsmStoreFunc) add_inhibitor_to_array, + &builder); + + return g_variant_builder_end (&builder); +} + +static void +on_open_finished (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GsmShell *shell = user_data; + GError *error; + + if (shell->priv->update_idle_id != 0) { + g_source_remove (shell->priv->update_idle_id); + shell->priv->update_idle_id = 0; + } + + shell->priv->dialog_is_open = FALSE; + + error = NULL; + g_dbus_proxy_call_finish (G_DBUS_PROXY (source), result, &error); + + if (error != NULL) { + g_warning ("Unable to open shell end session dialog: %s", error->message); + g_error_free (error); + + g_signal_emit (G_OBJECT (shell), signals[END_SESSION_DIALOG_OPEN_FAILED], 0); + return; + } + + g_signal_emit (G_OBJECT (shell), signals[END_SESSION_DIALOG_OPENED], 0); +} + +static void +on_end_session_dialog_dbus_signal (GDBusProxy *proxy, + gchar *sender_name, + gchar *signal_name, + GVariant *parameters, + GsmShell *shell) +{ + struct { + const char *name; + int index; + } signal_map[] = { + { "Closed", END_SESSION_DIALOG_CLOSED }, + { "Canceled", END_SESSION_DIALOG_CANCELED }, + { "ConfirmedLogout", END_SESSION_DIALOG_CONFIRMED_LOGOUT }, + { "ConfirmedReboot", END_SESSION_DIALOG_CONFIRMED_REBOOT }, + { "ConfirmedShutdown", END_SESSION_DIALOG_CONFIRMED_SHUTDOWN }, + { NULL, -1 } + }; + int signal_index = -1; + int i; + + for (i = 0; signal_map[i].name != NULL; i++) { + if (g_strcmp0 (signal_map[i].name, signal_name) == 0) { + signal_index = signal_map[i].index; + break; + } + } + + if (signal_index == -1) + return; + + shell->priv->dialog_is_open = FALSE; + + if (shell->priv->update_idle_id != 0) { + g_source_remove (shell->priv->update_idle_id); + shell->priv->update_idle_id = 0; + } + + g_signal_handlers_disconnect_by_func (shell->priv->inhibitors, + G_CALLBACK (queue_end_session_dialog_update), + shell); + + g_signal_emit (G_OBJECT (shell), signals[signal_index], 0); +} + +static void +on_end_session_dialog_name_owner_changed (GDBusProxy *proxy, + GParamSpec *pspec, + GsmShell *shell) +{ + gchar *name_owner; + + name_owner = g_dbus_proxy_get_name_owner (proxy); + if (name_owner == NULL) { + g_clear_object (&shell->priv->end_session_dialog_proxy); + } + + g_free (name_owner); +} + +static gboolean +on_need_end_session_dialog_update (GsmShell *shell) +{ + /* No longer need an update */ + if (shell->priv->update_idle_id == 0) + return FALSE; + + shell->priv->update_idle_id = 0; + + gsm_shell_open_end_session_dialog (shell, + shell->priv->end_session_dialog_type, + shell->priv->inhibitors); + return FALSE; +} + +static void +queue_end_session_dialog_update (GsmShell *shell) +{ + if (shell->priv->update_idle_id != 0) + return; + + shell->priv->update_idle_id = g_idle_add ((GSourceFunc) on_need_end_session_dialog_update, + shell); +} + +gboolean +gsm_shell_open_end_session_dialog (GsmShell *shell, + GsmShellEndSessionDialogType type, + GsmStore *inhibitors) +{ + GDBusProxy *proxy; + GError *error; + + error = NULL; + + if (shell->priv->dialog_is_open) { + g_return_val_if_fail (shell->priv->end_session_dialog_type == type, + FALSE); + + return TRUE; + } + + if (shell->priv->end_session_dialog_proxy == NULL) { + proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + SHELL_NAME, + SHELL_END_SESSION_DIALOG_PATH, + SHELL_END_SESSION_DIALOG_INTERFACE, + NULL, &error); + + if (error != NULL) { + g_critical ("Could not connect to the shell: %s", + error->message); + g_error_free (error); + return FALSE; + } + + shell->priv->end_session_dialog_proxy = proxy; + + g_signal_connect (proxy, "notify::g-name-owner", + G_CALLBACK (on_end_session_dialog_name_owner_changed), + shell); + g_signal_connect (proxy, "g-signal", + G_CALLBACK (on_end_session_dialog_dbus_signal), + shell); + } + + g_dbus_proxy_call (shell->priv->end_session_dialog_proxy, + "Open", + g_variant_new ("(uuu@ao)", + type, + 0, + AUTOMATIC_ACTION_TIMEOUT, + get_array_from_store (inhibitors)), + G_DBUS_CALL_FLAGS_NONE, + G_MAXINT, NULL, + on_open_finished, shell); + + g_object_ref (inhibitors); + + if (shell->priv->inhibitors != NULL) { + g_signal_handlers_disconnect_by_func (shell->priv->inhibitors, + G_CALLBACK (queue_end_session_dialog_update), + shell); + g_object_unref (shell->priv->inhibitors); + } + + shell->priv->inhibitors = inhibitors; + + g_signal_connect_swapped (inhibitors, "added", + G_CALLBACK (queue_end_session_dialog_update), + shell); + + g_signal_connect_swapped (inhibitors, "removed", + G_CALLBACK (queue_end_session_dialog_update), + shell); + + shell->priv->dialog_is_open = TRUE; + shell->priv->end_session_dialog_type = type; + + return TRUE; +} + +void +gsm_shell_close_end_session_dialog (GsmShell *shell) +{ + if (!shell->priv->end_session_dialog_proxy) + return; + + shell->priv->dialog_is_open = FALSE; + + g_dbus_proxy_call (shell->priv->end_session_dialog_proxy, + "Close", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, NULL, NULL); +} diff --git a/gnome-session/gsm-shell.h b/gnome-session/gsm-shell.h new file mode 100644 index 0000000..e236493 --- /dev/null +++ b/gnome-session/gsm-shell.h @@ -0,0 +1,87 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2010 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * Authors: + * Ray Strode + */ + +#ifndef __GSM_SHELL_H__ +#define __GSM_SHELL_H__ + +#include +#include + +#include "gsm-store.h" + +G_BEGIN_DECLS + +#define GSM_TYPE_SHELL (gsm_shell_get_type ()) +#define GSM_SHELL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_SHELL, GsmShell)) +#define GSM_SHELL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_SHELL, GsmShellClass)) +#define GSM_IS_SHELL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_SHELL)) +#define GSM_IS_SHELL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_SHELL)) +#define GSM_SHELL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GSM_TYPE_SHELL, GsmShellClass)) +#define GSM_SHELL_ERROR (gsm_shell_error_quark ()) + +typedef struct _GsmShell GsmShell; +typedef struct _GsmShellClass GsmShellClass; +typedef struct _GsmShellPrivate GsmShellPrivate; + +typedef enum +{ + GSM_SHELL_END_SESSION_DIALOG_TYPE_LOGOUT = 0, + GSM_SHELL_END_SESSION_DIALOG_TYPE_SHUTDOWN, + GSM_SHELL_END_SESSION_DIALOG_TYPE_RESTART, +} GsmShellEndSessionDialogType; + +struct _GsmShell +{ + GObject parent; + + GsmShellPrivate *priv; +}; + +struct _GsmShellClass +{ + GObjectClass parent_class; + + void (* end_session_dialog_opened) (GsmShell *shell); + void (* end_session_dialog_open_failed) (GsmShell *shell); + void (* end_session_dialog_closed) (GsmShell *shell); + void (* end_session_dialog_canceled) (GsmShell *shell); + + void (* end_session_dialog_confirmed_logout) (GsmShell *shell); + void (* end_session_dialog_confirmed_shutdown) (GsmShell *shell); + void (* end_session_dialog_confirmed_reboot) (GsmShell *shell); + +}; + +GType gsm_shell_get_type (void); + +GsmShell *gsm_shell_new (void); + +GsmShell *gsm_get_shell (void); +gboolean gsm_shell_is_running (GsmShell *shell); + +gboolean gsm_shell_open_end_session_dialog (GsmShell *shell, + GsmShellEndSessionDialogType type, + GsmStore *inhibitors); +void gsm_shell_close_end_session_dialog (GsmShell *shell); + +G_END_DECLS + +#endif /* __GSM_SHELL_H__ */ diff --git a/gnome-session/gsm-store.c b/gnome-session/gsm-store.c new file mode 100644 index 0000000..30b0770 --- /dev/null +++ b/gnome-session/gsm-store.c @@ -0,0 +1,408 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007-2008 William Jon McCann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "gsm-store.h" + +#define GSM_STORE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSM_TYPE_STORE, GsmStorePrivate)) + +struct GsmStorePrivate +{ + GHashTable *objects; + gboolean locked; +}; + +enum { + ADDED, + REMOVED, + LAST_SIGNAL +}; + +enum { + PROP_0, + PROP_LOCKED +}; + +static guint signals [LAST_SIGNAL] = { 0 }; + +static void gsm_store_class_init (GsmStoreClass *klass); +static void gsm_store_init (GsmStore *store); +static void gsm_store_finalize (GObject *object); + +G_DEFINE_TYPE (GsmStore, gsm_store, G_TYPE_OBJECT) + +GQuark +gsm_store_error_quark (void) +{ + static GQuark ret = 0; + if (ret == 0) { + ret = g_quark_from_static_string ("gsm_store_error"); + } + + return ret; +} + +guint +gsm_store_size (GsmStore *store) +{ + return g_hash_table_size (store->priv->objects); +} + +gboolean +gsm_store_remove (GsmStore *store, + const char *id) +{ + GObject *found; + gboolean removed; + char *id_copy; + + g_return_val_if_fail (store != NULL, FALSE); + + found = g_hash_table_lookup (store->priv->objects, id); + if (found == NULL) { + return FALSE; + } + + id_copy = g_strdup (id); + + g_object_ref (found); + + removed = g_hash_table_remove (store->priv->objects, id_copy); + g_assert (removed); + + g_signal_emit (store, signals [REMOVED], 0, id_copy); + + g_object_unref (found); + g_free (id_copy); + + return TRUE; +} + +void +gsm_store_foreach (GsmStore *store, + GsmStoreFunc func, + gpointer user_data) +{ + g_return_if_fail (store != NULL); + g_return_if_fail (func != NULL); + + g_hash_table_find (store->priv->objects, + (GHRFunc)func, + user_data); +} + +GObject * +gsm_store_find (GsmStore *store, + GsmStoreFunc predicate, + gpointer user_data) +{ + GObject *object; + + g_return_val_if_fail (store != NULL, NULL); + g_return_val_if_fail (predicate != NULL, NULL); + + object = g_hash_table_find (store->priv->objects, + (GHRFunc)predicate, + user_data); + return object; +} + +GObject * +gsm_store_lookup (GsmStore *store, + const char *id) +{ + GObject *object; + + g_return_val_if_fail (store != NULL, NULL); + g_return_val_if_fail (id != NULL, NULL); + + object = g_hash_table_lookup (store->priv->objects, id); + + return object; +} + + +typedef struct +{ + GsmStoreFunc func; + gpointer user_data; + GsmStore *store; + GList *removed; +} WrapperData; + +static gboolean +foreach_remove_wrapper (const char *id, + GObject *object, + WrapperData *data) +{ + gboolean res; + + res = (data->func) (id, object, data->user_data); + if (res) { + data->removed = g_list_prepend (data->removed, g_strdup (id)); + } + + return res; +} + +guint +gsm_store_foreach_remove (GsmStore *store, + GsmStoreFunc func, + gpointer user_data) +{ + guint ret; + WrapperData data; + + g_return_val_if_fail (store != NULL, 0); + g_return_val_if_fail (func != NULL, 0); + + data.store = store; + data.user_data = user_data; + data.func = func; + data.removed = NULL; + + ret = g_hash_table_foreach_remove (store->priv->objects, + (GHRFunc)foreach_remove_wrapper, + &data); + + while (data.removed != NULL) { + char *id; + id = data.removed->data; + g_debug ("GsmStore: emitting removed for %s", id); + g_signal_emit (store, signals [REMOVED], 0, id); + g_free (data.removed->data); + data.removed->data = NULL; + data.removed = g_list_delete_link (data.removed, data.removed); + } + + return ret; +} + +static gboolean +_remove_all (const char *id, + GObject *object, + gpointer data) +{ + return TRUE; +} + +void +gsm_store_clear (GsmStore *store) +{ + g_return_if_fail (store != NULL); + + g_debug ("GsmStore: Clearing object store"); + + gsm_store_foreach_remove (store, + _remove_all, + NULL); +} + +gboolean +gsm_store_add (GsmStore *store, + const char *id, + GObject *object) +{ + g_return_val_if_fail (store != NULL, FALSE); + g_return_val_if_fail (id != NULL, FALSE); + g_return_val_if_fail (object != NULL, FALSE); + + /* If we're locked, we don't accept any new session + objects. */ + if (store->priv->locked) { + return FALSE; + } + + g_debug ("GsmStore: Adding object id %s to store", id); + + g_hash_table_insert (store->priv->objects, + g_strdup (id), + g_object_ref (object)); + + g_signal_emit (store, signals [ADDED], 0, id); + + return TRUE; +} + +void +gsm_store_set_locked (GsmStore *store, + gboolean locked) +{ + g_return_if_fail (GSM_IS_STORE (store)); + + store->priv->locked = locked; +} + +gboolean +gsm_store_get_locked (GsmStore *store) +{ + g_return_val_if_fail (GSM_IS_STORE (store), FALSE); + + return store->priv->locked; +} + +static void +gsm_store_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GsmStore *self; + + self = GSM_STORE (object); + + switch (prop_id) { + case PROP_LOCKED: + gsm_store_set_locked (self, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gsm_store_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GsmStore *self; + + self = GSM_STORE (object); + + switch (prop_id) { + case PROP_LOCKED: + g_value_set_boolean (value, self->priv->locked); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gsm_store_dispose (GObject *object) +{ + GsmStore *store; + + g_return_if_fail (object != NULL); + g_return_if_fail (GSM_IS_STORE (object)); + + store = GSM_STORE (object); + + gsm_store_clear (store); + + G_OBJECT_CLASS (gsm_store_parent_class)->dispose (object); +} + +static void +gsm_store_class_init (GsmStoreClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = gsm_store_get_property; + object_class->set_property = gsm_store_set_property; + object_class->finalize = gsm_store_finalize; + object_class->dispose = gsm_store_dispose; + + signals [ADDED] = + g_signal_new ("added", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsmStoreClass, added), + NULL, NULL, NULL, + G_TYPE_NONE, + 1, G_TYPE_STRING); + signals [REMOVED] = + g_signal_new ("removed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsmStoreClass, removed), + NULL, NULL, NULL, + G_TYPE_NONE, + 1, G_TYPE_STRING); + g_object_class_install_property (object_class, + PROP_LOCKED, + g_param_spec_boolean ("locked", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_type_class_add_private (klass, sizeof (GsmStorePrivate)); +} + +static void +_destroy_object (GObject *object) +{ + g_debug ("GsmStore: Unreffing object: %p", object); + g_object_unref (object); +} + +static void +gsm_store_init (GsmStore *store) +{ + + store->priv = GSM_STORE_GET_PRIVATE (store); + + store->priv->objects = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) _destroy_object); +} + +static void +gsm_store_finalize (GObject *object) +{ + GsmStore *store; + + g_return_if_fail (object != NULL); + g_return_if_fail (GSM_IS_STORE (object)); + + store = GSM_STORE (object); + + g_return_if_fail (store->priv != NULL); + + g_hash_table_destroy (store->priv->objects); + + G_OBJECT_CLASS (gsm_store_parent_class)->finalize (object); +} + +GsmStore * +gsm_store_new (void) +{ + GObject *object; + + object = g_object_new (GSM_TYPE_STORE, + NULL); + + return GSM_STORE (object); +} diff --git a/gnome-session/gsm-store.h b/gnome-session/gsm-store.h new file mode 100644 index 0000000..0cfb991 --- /dev/null +++ b/gnome-session/gsm-store.h @@ -0,0 +1,96 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007-2008 William Jon McCann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + */ + + +#ifndef __GSM_STORE_H +#define __GSM_STORE_H + +#include + +G_BEGIN_DECLS + +#define GSM_TYPE_STORE (gsm_store_get_type ()) +#define GSM_STORE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GSM_TYPE_STORE, GsmStore)) +#define GSM_STORE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GSM_TYPE_STORE, GsmStoreClass)) +#define GSM_IS_STORE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GSM_TYPE_STORE)) +#define GSM_IS_STORE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GSM_TYPE_STORE)) +#define GSM_STORE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GSM_TYPE_STORE, GsmStoreClass)) + +typedef struct GsmStorePrivate GsmStorePrivate; + +typedef struct +{ + GObject parent; + GsmStorePrivate *priv; +} GsmStore; + +typedef struct +{ + GObjectClass parent_class; + + void (* added) (GsmStore *store, + const char *id); + void (* removed) (GsmStore *store, + const char *id); +} GsmStoreClass; + +typedef enum +{ + GSM_STORE_ERROR_GENERAL +} GsmStoreError; + +#define GSM_STORE_ERROR gsm_store_error_quark () + +typedef gboolean (*GsmStoreFunc) (const char *id, + GObject *object, + gpointer user_data); + +GQuark gsm_store_error_quark (void); +GType gsm_store_get_type (void); + +GsmStore * gsm_store_new (void); + +gboolean gsm_store_get_locked (GsmStore *store); +void gsm_store_set_locked (GsmStore *store, + gboolean locked); + +guint gsm_store_size (GsmStore *store); +gboolean gsm_store_add (GsmStore *store, + const char *id, + GObject *object); +void gsm_store_clear (GsmStore *store); +gboolean gsm_store_remove (GsmStore *store, + const char *id); + +void gsm_store_foreach (GsmStore *store, + GsmStoreFunc func, + gpointer user_data); +guint gsm_store_foreach_remove (GsmStore *store, + GsmStoreFunc func, + gpointer user_data); +GObject * gsm_store_find (GsmStore *store, + GsmStoreFunc predicate, + gpointer user_data); +GObject * gsm_store_lookup (GsmStore *store, + const char *id); + + +G_END_DECLS + +#endif /* __GSM_STORE_H */ diff --git a/gnome-session/gsm-system.c b/gnome-session/gsm-system.c new file mode 100644 index 0000000..39b59f9 --- /dev/null +++ b/gnome-session/gsm-system.c @@ -0,0 +1,288 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include "config.h" + +#include +#include + +#include "gsm-system.h" + +#include "gsm-systemd.h" + +#ifdef HAVE_CONSOLEKIT +#include "gsm-consolekit.h" +#endif + +enum { + REQUEST_COMPLETED, + SHUTDOWN_PREPARED, + LAST_SIGNAL +}; + +enum { + PROP_0, + PROP_ACTIVE +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_INTERFACE (GsmSystem, gsm_system, G_TYPE_OBJECT) + +static void +gsm_system_default_init (GsmSystemInterface *iface) +{ + GParamSpec *pspec; + signals [REQUEST_COMPLETED] = + g_signal_new ("request-completed", + GSM_TYPE_SYSTEM, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsmSystemInterface, request_completed), + NULL, NULL, NULL, + G_TYPE_NONE, + 1, G_TYPE_POINTER); + signals[SHUTDOWN_PREPARED] = + g_signal_new ("shutdown-prepared", + GSM_TYPE_SYSTEM, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsmSystemInterface, shutdown_prepared), + NULL, NULL, NULL, + G_TYPE_NONE, + 1, G_TYPE_BOOLEAN); + pspec = g_param_spec_boolean ("active", + "Active", + "Whether or not session is active", + TRUE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT); + g_object_interface_install_property (iface, pspec); +} + +typedef GObject GsmSystemNull; +typedef GObjectClass GsmSystemNullClass; + +static void do_nothing (void) { } +static gboolean return_true (void) { return TRUE; } +static gboolean return_false (void) { return TRUE; } + +static void +gsm_system_null_init_iface (GsmSystemInterface *iface) +{ + iface->can_switch_user = (void *) return_false; + iface->can_stop = (void *) return_false; + iface->can_restart = (void *) return_false; + iface->can_restart_to_firmware_setup = (void *) return_false; + iface->set_restart_to_firmware_setup = (void *) do_nothing; + iface->can_suspend = (void *) return_false; + iface->can_hibernate = (void *) return_false; + iface->attempt_stop = (void *) do_nothing; + iface->attempt_restart = (void *) do_nothing; + iface->suspend = (void *) do_nothing; + iface->hibernate = (void *) do_nothing; + iface->set_session_idle = (void *) do_nothing; + iface->is_login_session = (void *) return_true; + iface->set_inhibitors = (void *) do_nothing; + iface->prepare_shutdown = (void *) do_nothing; + iface->complete_shutdown = (void *) do_nothing; + iface->is_last_session_for_user = (void *) return_false; +} + +static void +gsm_system_null_init (GsmSystemNull *gsn) +{ +} + +static void +gsm_system_null_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + g_value_set_boolean (value, TRUE); +} + +static void +gsm_system_null_class_init (GsmSystemNullClass *class) +{ + class->get_property = gsm_system_null_get_property; + class->set_property = (void *) do_nothing; + + g_object_class_override_property (class, 1, "active"); +} + +static GType gsm_system_null_get_type (void); +G_DEFINE_TYPE_WITH_CODE (GsmSystemNull, gsm_system_null, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GSM_TYPE_SYSTEM, gsm_system_null_init_iface)) + +GQuark +gsm_system_error_quark (void) +{ + static GQuark error_quark = 0; + + if (error_quark == 0) { + error_quark = g_quark_from_static_string ("gsm-system-error"); + } + + return error_quark; +} + +gboolean +gsm_system_can_switch_user (GsmSystem *system) +{ + return GSM_SYSTEM_GET_IFACE (system)->can_switch_user (system); +} + +gboolean +gsm_system_can_stop (GsmSystem *system) +{ + return GSM_SYSTEM_GET_IFACE (system)->can_stop (system); +} + +gboolean +gsm_system_can_restart (GsmSystem *system) +{ + return GSM_SYSTEM_GET_IFACE (system)->can_restart (system); +} + +gboolean +gsm_system_can_restart_to_firmware_setup (GsmSystem *system) +{ + return GSM_SYSTEM_GET_IFACE (system)->can_restart_to_firmware_setup (system); +} + +void +gsm_system_set_restart_to_firmware_setup (GsmSystem *system, + gboolean enable) +{ + GSM_SYSTEM_GET_IFACE (system)->set_restart_to_firmware_setup (system, enable); +} + +gboolean +gsm_system_can_suspend (GsmSystem *system) +{ + return GSM_SYSTEM_GET_IFACE (system)->can_suspend (system); +} + +gboolean +gsm_system_can_hibernate (GsmSystem *system) +{ + return GSM_SYSTEM_GET_IFACE (system)->can_hibernate (system); +} + +void +gsm_system_attempt_stop (GsmSystem *system) +{ + GSM_SYSTEM_GET_IFACE (system)->attempt_stop (system); +} + +void +gsm_system_attempt_restart (GsmSystem *system) +{ + GSM_SYSTEM_GET_IFACE (system)->attempt_restart (system); +} + +void +gsm_system_suspend (GsmSystem *system) +{ + GSM_SYSTEM_GET_IFACE (system)->suspend (system); +} + +void +gsm_system_hibernate (GsmSystem *system) +{ + GSM_SYSTEM_GET_IFACE (system)->hibernate (system); +} + +void +gsm_system_set_session_idle (GsmSystem *system, + gboolean is_idle) +{ + GSM_SYSTEM_GET_IFACE (system)->set_session_idle (system, is_idle); +} + +void +gsm_system_set_inhibitors (GsmSystem *system, + GsmInhibitorFlag flags) +{ + GSM_SYSTEM_GET_IFACE (system)->set_inhibitors (system, flags); +} + +gboolean +gsm_system_is_login_session (GsmSystem *system) +{ + return GSM_SYSTEM_GET_IFACE (system)->is_login_session (system); +} + +gboolean +gsm_system_is_last_session_for_user (GsmSystem *system) +{ + return GSM_SYSTEM_GET_IFACE (system)->is_last_session_for_user (system); +} + +/** + * gsm_system_is_active: + * + * Returns: %TRUE if the current session is in the foreground + * Since: 3.8 + */ +gboolean +gsm_system_is_active (GsmSystem *system) +{ + gboolean is_active; + g_object_get ((GObject*)system, "active", &is_active, NULL); + return is_active; +} + +void +gsm_system_prepare_shutdown (GsmSystem *system, + gboolean restart) +{ + GSM_SYSTEM_GET_IFACE (system)->prepare_shutdown (system, restart); +} + +void +gsm_system_complete_shutdown (GsmSystem *system) +{ + GSM_SYSTEM_GET_IFACE (system)->complete_shutdown (system); +} + +GsmSystem * +gsm_get_system (void) +{ + static GsmSystem *system = NULL; + + if (system == NULL) { + system = GSM_SYSTEM (gsm_systemd_new ()); + if (system != NULL) { + g_debug ("Using systemd for session tracking"); + } + } + +#ifdef HAVE_CONSOLEKIT + if (system == NULL) { + system = GSM_SYSTEM (gsm_consolekit_new ()); + if (system != NULL) { + g_debug ("Using ConsoleKit for session tracking"); + } + } +#endif + + if (system == NULL) { + system = g_object_new (gsm_system_null_get_type (), NULL); + g_warning ("Using null backend for session tracking"); + } + + return g_object_ref (system); +} diff --git a/gnome-session/gsm-system.h b/gnome-session/gsm-system.h new file mode 100644 index 0000000..438434c --- /dev/null +++ b/gnome-session/gsm-system.h @@ -0,0 +1,130 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Jon McCann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * Authors: + * Jon McCann + */ + +#ifndef __GSM_SYSTEM_H__ +#define __GSM_SYSTEM_H__ + +#include +#include + +#include "gsm-inhibitor.h" + +G_BEGIN_DECLS + +#define GSM_TYPE_SYSTEM (gsm_system_get_type ()) +#define GSM_SYSTEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_SYSTEM, GsmSystem)) +#define GSM_SYSTEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_SYSTEM, GsmSystemInterface)) +#define GSM_IS_SYSTEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_SYSTEM)) +#define GSM_SYSTEM_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE((obj), GSM_TYPE_SYSTEM, GsmSystemInterface)) +#define GSM_SYSTEM_ERROR (gsm_system_error_quark ()) + +typedef struct _GsmSystem GsmSystem; +typedef struct _GsmSystemInterface GsmSystemInterface; +typedef enum _GsmSystemError GsmSystemError; + +struct _GsmSystemInterface +{ + GTypeInterface base_interface; + + void (* request_completed) (GsmSystem *system, + GError *error); + + void (* shutdown_prepared) (GsmSystem *system, + gboolean success); + + gboolean (* can_switch_user) (GsmSystem *system); + gboolean (* can_stop) (GsmSystem *system); + gboolean (* can_restart) (GsmSystem *system); + gboolean (* can_restart_to_firmware_setup) (GsmSystem *system); + void (* set_restart_to_firmware_setup) (GsmSystem *system, + gboolean enable); + gboolean (* can_suspend) (GsmSystem *system); + gboolean (* can_hibernate) (GsmSystem *system); + void (* attempt_stop) (GsmSystem *system); + void (* attempt_restart) (GsmSystem *system); + void (* suspend) (GsmSystem *system); + void (* hibernate) (GsmSystem *system); + void (* set_session_idle) (GsmSystem *system, + gboolean is_idle); + gboolean (* is_login_session) (GsmSystem *system); + void (* set_inhibitors) (GsmSystem *system, + GsmInhibitorFlag flags); + void (* prepare_shutdown) (GsmSystem *system, + gboolean restart); + void (* complete_shutdown)(GsmSystem *system); + gboolean (* is_last_session_for_user) (GsmSystem *system); +}; + +enum _GsmSystemError { + GSM_SYSTEM_ERROR_RESTARTING = 0, + GSM_SYSTEM_ERROR_STOPPING +}; + +GType gsm_system_get_type (void); + +GQuark gsm_system_error_quark (void); + +GsmSystem *gsm_get_system (void); + +gboolean gsm_system_can_switch_user (GsmSystem *system); + +gboolean gsm_system_can_stop (GsmSystem *system); + +gboolean gsm_system_can_restart (GsmSystem *system); + +gboolean gsm_system_can_restart_to_firmware_setup (GsmSystem *system); + +void gsm_system_set_restart_to_firmware_setup (GsmSystem *system, + gboolean enable); + +gboolean gsm_system_can_suspend (GsmSystem *system); + +gboolean gsm_system_can_hibernate (GsmSystem *system); + +void gsm_system_attempt_stop (GsmSystem *system); + +void gsm_system_attempt_restart (GsmSystem *system); + +void gsm_system_suspend (GsmSystem *system); + +void gsm_system_hibernate (GsmSystem *system); + +void gsm_system_set_session_idle (GsmSystem *system, + gboolean is_idle); + +gboolean gsm_system_is_login_session (GsmSystem *system); + +gboolean gsm_system_is_last_session_for_user (GsmSystem *system); + +gboolean gsm_system_is_active (GsmSystem *system); + +void gsm_system_set_inhibitors (GsmSystem *system, + GsmInhibitorFlag flags); + +void gsm_system_prepare_shutdown (GsmSystem *system, + gboolean restart); +void gsm_system_complete_shutdown (GsmSystem *system); + + + +G_END_DECLS + +#endif /* __GSM_SYSTEM_H__ */ diff --git a/gnome-session/gsm-systemd.c b/gnome-session/gsm-systemd.c new file mode 100644 index 0000000..cf30a4b --- /dev/null +++ b/gnome-session/gsm-systemd.c @@ -0,0 +1,1183 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2011 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * Author: Matthias Clasen + */ + +#include "config.h" +#include "gsm-systemd.h" + +#ifdef HAVE_SYSTEMD + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include "gsm-system.h" + +#define SD_NAME "org.freedesktop.login1" +#define SD_PATH "/org/freedesktop/login1" +#define SD_INTERFACE "org.freedesktop.login1.Manager" +#define SD_SEAT_INTERFACE "org.freedesktop.login1.Seat" +#define SD_SESSION_INTERFACE "org.freedesktop.login1.Session" + +#define SYSTEMD_SESSION_REQUIRE_ONLINE 0 /* active or online sessions only */ + +struct _GsmSystemdPrivate +{ + GSource *sd_source; + GDBusProxy *sd_proxy; + char *session_id; + gchar *session_path; + + const gchar *inhibit_locks; + gint inhibit_fd; + + gboolean is_active; + + gint delay_inhibit_fd; + gboolean prepare_for_shutdown_expected; +}; + +enum { + PROP_0, + PROP_ACTIVE +}; + +static void gsm_systemd_system_init (GsmSystemInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (GsmSystemd, gsm_systemd, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GSM_TYPE_SYSTEM, + gsm_systemd_system_init)) + +static void +drop_system_inhibitor (GsmSystemd *manager) +{ + if (manager->priv->inhibit_fd != -1) { + g_debug ("GsmSystemd: Dropping system inhibitor fd %d", manager->priv->inhibit_fd); + close (manager->priv->inhibit_fd); + manager->priv->inhibit_fd = -1; + } +} + +static void +drop_delay_inhibitor (GsmSystemd *manager) +{ + if (manager->priv->delay_inhibit_fd != -1) { + g_debug ("GsmSystemd: Dropping delay inhibitor"); + close (manager->priv->delay_inhibit_fd); + manager->priv->delay_inhibit_fd = -1; + } +} + +static void +gsm_systemd_finalize (GObject *object) +{ + GsmSystemd *systemd = GSM_SYSTEMD (object); + + g_clear_object (&systemd->priv->sd_proxy); + free (systemd->priv->session_id); + g_free (systemd->priv->session_path); + + if (systemd->priv->sd_source) { + g_source_destroy (systemd->priv->sd_source); + } + + drop_system_inhibitor (systemd); + drop_delay_inhibitor (systemd); + + G_OBJECT_CLASS (gsm_systemd_parent_class)->finalize (object); +} + +static void +gsm_systemd_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GsmSystemd *self = GSM_SYSTEMD (object); + + switch (prop_id) { + case PROP_ACTIVE: + self->priv->is_active = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gsm_systemd_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GsmSystemd *self = GSM_SYSTEMD (object); + + switch (prop_id) { + case PROP_ACTIVE: + g_value_set_boolean (value, self->priv->is_active); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gsm_systemd_class_init (GsmSystemdClass *manager_class) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (manager_class); + + object_class->get_property = gsm_systemd_get_property; + object_class->set_property = gsm_systemd_set_property; + object_class->finalize = gsm_systemd_finalize; + + g_object_class_override_property (object_class, PROP_ACTIVE, "active"); + + g_type_class_add_private (manager_class, sizeof (GsmSystemdPrivate)); +} + +typedef struct +{ + GSource source; + GPollFD pollfd; + sd_login_monitor *monitor; +} SdSource; + +static gboolean +sd_source_prepare (GSource *source, + gint *timeout) +{ + *timeout = -1; + return FALSE; +} + +static gboolean +sd_source_check (GSource *source) +{ + SdSource *sd_source = (SdSource *)source; + + return sd_source->pollfd.revents != 0; +} + +static gboolean +sd_source_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data) + +{ + SdSource *sd_source = (SdSource *)source; + gboolean ret; + + g_warn_if_fail (callback != NULL); + + ret = (*callback) (user_data); + + sd_login_monitor_flush (sd_source->monitor); + return ret; +} + +static void +sd_source_finalize (GSource *source) +{ + SdSource *sd_source = (SdSource*)source; + + sd_login_monitor_unref (sd_source->monitor); +} + +static GSourceFuncs sd_source_funcs = { + sd_source_prepare, + sd_source_check, + sd_source_dispatch, + sd_source_finalize +}; + +static GSource * +sd_source_new (void) +{ + GSource *source; + SdSource *sd_source; + int ret; + + source = g_source_new (&sd_source_funcs, sizeof (SdSource)); + sd_source = (SdSource *)source; + + if ((ret = sd_login_monitor_new ("session", &sd_source->monitor)) < 0) { + g_warning ("Error getting login monitor: %d", ret); + } else { + sd_source->pollfd.fd = sd_login_monitor_get_fd (sd_source->monitor); + sd_source->pollfd.events = G_IO_IN; + g_source_add_poll (source, &sd_source->pollfd); + } + + return source; +} + +static gboolean +on_sd_source_changed (gpointer user_data) +{ + GsmSystemd *self = user_data; + int active_r; + gboolean active; + + active_r = sd_session_is_active (self->priv->session_id); + if (active_r < 0) + active = FALSE; + else + active = active_r; + if (active != self->priv->is_active) { + self->priv->is_active = active; + g_object_notify (G_OBJECT (self), "active"); + } + + return TRUE; +} + +static void sd_proxy_signal_cb (GDBusProxy *proxy, + const gchar *sender_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data); + +static gboolean +_systemd_session_is_graphical (const char *session_id) +{ + const gchar * const graphical_session_types[] = { "wayland", "x11", "mir", NULL }; + int saved_errno; + g_autofree gchar *type = NULL; + + saved_errno = sd_session_get_type (session_id, &type); + if (saved_errno < 0) { + g_warning ("Couldn't get type for session '%s': %s", + session_id, + g_strerror (-saved_errno)); + return FALSE; + } + + if (!g_strv_contains (graphical_session_types, type)) { + g_debug ("Session '%s' is not a graphical session (type: '%s')", + session_id, + type); + return FALSE; + } + + return TRUE; +} + +static gboolean +_systemd_session_is_active (const char *session_id) +{ + const gchar * const active_states[] = { "active", "online", NULL }; + int saved_errno; + g_autofree gchar *state = NULL; + + /* + * display sessions can be 'closing' if they are logged out but some + * processes are lingering; we shouldn't consider these (this is + * checking for a race condition since we specified + * SYSTEMD_SESSION_REQUIRE_ONLINE) + */ + saved_errno = sd_session_get_state (session_id, &state); + if (saved_errno < 0) { + g_warning ("Couldn't get state for session '%s': %s", + session_id, + g_strerror (-saved_errno)); + return FALSE; + } + + if (!g_strv_contains (active_states, state)) { + g_debug ("Session '%s' is not active or online", session_id); + return FALSE; + } + + return TRUE; +} + +static gboolean +gsm_systemd_find_session (char **session_id) +{ + char *local_session_id = NULL; + g_auto(GStrv) sessions = NULL; + int n_sessions; + + g_return_val_if_fail (session_id != NULL, FALSE); + + g_debug ("Finding a graphical session for user %d", getuid ()); + + n_sessions = sd_uid_get_sessions (getuid (), + SYSTEMD_SESSION_REQUIRE_ONLINE, + &sessions); + + if (n_sessions < 0) { + g_critical ("Failed to get sessions for user %d", getuid ()); + return FALSE; + } + + for (int i = 0; i < n_sessions; ++i) { + g_debug ("Considering session '%s'", sessions[i]); + + if (!_systemd_session_is_graphical (sessions[i])) + continue; + + if (!_systemd_session_is_active (sessions[i])) + continue; + + /* + * We get the sessions from newest to oldest, so take the last + * one we find that's good + */ + local_session_id = sessions[i]; + } + + if (local_session_id == NULL) + return FALSE; + + *session_id = g_strdup (local_session_id); + + return TRUE; +} + +static void +gsm_systemd_init (GsmSystemd *manager) +{ + GError *error = NULL; + GDBusConnection *bus; + GVariant *res; + + manager->priv = G_TYPE_INSTANCE_GET_PRIVATE (manager, + GSM_TYPE_SYSTEMD, + GsmSystemdPrivate); + + manager->priv->inhibit_fd = -1; + manager->priv->delay_inhibit_fd = -1; + + bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (bus == NULL) + g_error ("Failed to connect to system bus: %s", + error->message); + manager->priv->sd_proxy = + g_dbus_proxy_new_sync (bus, + 0, + NULL, + SD_NAME, + SD_PATH, + SD_INTERFACE, + NULL, + &error); + if (manager->priv->sd_proxy == NULL) { + g_warning ("Failed to connect to systemd: %s", + error->message); + g_clear_error (&error); + } + + g_signal_connect (manager->priv->sd_proxy, "g-signal", + G_CALLBACK (sd_proxy_signal_cb), manager); + + gsm_systemd_find_session (&manager->priv->session_id); + + if (manager->priv->session_id == NULL) { + g_warning ("Could not get session id for session. Check that logind is " + "properly installed and pam_systemd is getting used at login."); + return; + } + + g_debug ("Found session ID: %s", manager->priv->session_id); + + res = g_dbus_proxy_call_sync (manager->priv->sd_proxy, + "GetSession", + g_variant_new ("(s)", manager->priv->session_id), + 0, + G_MAXINT, + NULL, + &error); + if (res == NULL) { + g_warning ("Could not get session id for session. Check that logind is " + "properly installed and pam_systemd is getting used at login: %s", + error->message); + g_error_free (error); + return; + } + + g_variant_get (res, "(o)", &manager->priv->session_path); + g_variant_unref (res); + + manager->priv->sd_source = sd_source_new (); + g_source_set_callback (manager->priv->sd_source, on_sd_source_changed, manager, NULL); + g_source_attach (manager->priv->sd_source, NULL); + + on_sd_source_changed (manager); + + g_object_unref (bus); +} + +static void +emit_restart_complete (GsmSystemd *manager, + GError *error) +{ + GError *call_error; + + call_error = NULL; + + if (error != NULL) { + call_error = g_error_new_literal (GSM_SYSTEM_ERROR, + GSM_SYSTEM_ERROR_RESTARTING, + error->message); + } + + g_signal_emit_by_name (G_OBJECT (manager), + "request_completed", call_error); + + if (call_error != NULL) { + g_error_free (call_error); + } +} + +static void +emit_stop_complete (GsmSystemd *manager, + GError *error) +{ + GError *call_error; + + call_error = NULL; + + if (error != NULL) { + call_error = g_error_new_literal (GSM_SYSTEM_ERROR, + GSM_SYSTEM_ERROR_STOPPING, + error->message); + } + + g_signal_emit_by_name (G_OBJECT (manager), + "request_completed", call_error); + + if (call_error != NULL) { + g_error_free (call_error); + } +} + +static void +restart_done (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GDBusProxy *proxy = G_DBUS_PROXY (source); + GsmSystemd *manager = user_data; + GError *error = NULL; + GVariant *res; + + res = g_dbus_proxy_call_finish (proxy, result, &error); + + if (!res) { + g_warning ("Unable to restart system: %s", error->message); + emit_restart_complete (manager, error); + g_error_free (error); + } else { + emit_restart_complete (manager, NULL); + g_variant_unref (res); + } +} + +static void +gsm_systemd_attempt_restart (GsmSystem *system) +{ + GsmSystemd *manager = GSM_SYSTEMD (system); + + g_dbus_proxy_call (manager->priv->sd_proxy, + "Reboot", + g_variant_new ("(b)", TRUE), + 0, + G_MAXINT, + NULL, + restart_done, + manager); +} + +static void +stop_done (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GDBusProxy *proxy = G_DBUS_PROXY (source); + GsmSystemd *manager = user_data; + GError *error = NULL; + GVariant *res; + + res = g_dbus_proxy_call_finish (proxy, result, &error); + + if (!res) { + g_warning ("Unable to stop system: %s", error->message); + emit_stop_complete (manager, error); + g_error_free (error); + } else { + emit_stop_complete (manager, NULL); + g_variant_unref (res); + } +} + +static void +gsm_systemd_attempt_stop (GsmSystem *system) +{ + GsmSystemd *manager = GSM_SYSTEMD (system); + + g_dbus_proxy_call (manager->priv->sd_proxy, + "PowerOff", + g_variant_new ("(b)", TRUE), + 0, + G_MAXINT, + NULL, + stop_done, + manager); +} + +static void +gsm_systemd_set_session_idle (GsmSystem *system, + gboolean is_idle) +{ + GsmSystemd *manager = GSM_SYSTEMD (system); + GDBusConnection *bus; + + if (manager->priv->session_path == NULL) { + g_warning ("Could not get session path for session. Check that logind is " + "properly installed and pam_systemd is getting used at login."); + return; + } + + g_debug ("Updating systemd idle status: %d", is_idle); + bus = g_dbus_proxy_get_connection (manager->priv->sd_proxy); + g_dbus_connection_call (bus, + SD_NAME, + manager->priv->session_path, + SD_SESSION_INTERFACE, + "SetIdleHint", + g_variant_new ("(b)", is_idle), + G_VARIANT_TYPE_BOOLEAN, + 0, + G_MAXINT, + NULL, NULL, NULL); +} + +static gboolean +gsm_systemd_can_switch_user (GsmSystem *system) +{ + GsmSystemd *manager = GSM_SYSTEMD (system); + gchar *seat; + gint ret; + + sd_session_get_seat (manager->priv->session_id, &seat); + ret = sd_seat_can_multi_session (seat); + free (seat); + + return ret > 0; +} + +static gboolean +gsm_systemd_can_restart (GsmSystem *system) +{ + GsmSystemd *manager = GSM_SYSTEMD (system); + gchar *rv; + GVariant *res; + gboolean can_restart; + + res = g_dbus_proxy_call_sync (manager->priv->sd_proxy, + "CanReboot", + NULL, + 0, + G_MAXINT, + NULL, + NULL); + if (!res) { + g_warning ("Calling CanReboot failed. Check that logind is " + "properly installed and pam_systemd is getting used at login."); + return FALSE; + } + + g_variant_get (res, "(s)", &rv); + g_variant_unref (res); + + can_restart = g_strcmp0 (rv, "yes") == 0 || + g_strcmp0 (rv, "challenge") == 0; + + g_free (rv); + + return can_restart; +} + +static gboolean +gsm_systemd_can_restart_to_firmware_setup (GsmSystem *system) +{ + GsmSystemd *manager = GSM_SYSTEMD (system); + const gchar *rv; + GVariant *res; + gboolean can_restart; + GError *error = NULL; + + res = g_dbus_proxy_call_sync (manager->priv->sd_proxy, + "CanRebootToFirmwareSetup", + NULL, + G_DBUS_CALL_FLAGS_NONE, + G_MAXINT, + NULL, + &error); + if (!res) { + g_warning ("Calling CanRebootToFirmwareSetup failed. Check that logind is " + "properly installed and pam_systemd is getting used at login: %s", + error->message); + g_error_free (error); + return FALSE; + } + + g_variant_get (res, "(&s)", &rv); + + can_restart = g_strcmp0 (rv, "yes") == 0 || + g_strcmp0 (rv, "challenge") == 0; + + g_variant_unref (res); + + return can_restart; +} + +static void +gsm_systemd_set_restart_to_firmware_setup (GsmSystem *system, + gboolean enable) +{ + GsmSystemd *manager = GSM_SYSTEMD (system); + GVariant *res; + GError *error = NULL; + + res = g_dbus_proxy_call_sync (manager->priv->sd_proxy, + "SetRebootToFirmwareSetup", + g_variant_new ("(b)", enable), + G_DBUS_CALL_FLAGS_NONE, + G_MAXINT, + NULL, + &error); + if (!res) { + g_warning ("Calling SetRebootToFirmwareSetup failed. Check that logind is " + "properly installed and pam_systemd is getting used at login: %s", + error->message); + g_error_free (error); + } + + g_variant_unref (res); +} + +static gboolean +gsm_systemd_can_stop (GsmSystem *system) +{ + GsmSystemd *manager = GSM_SYSTEMD (system); + gchar *rv; + GVariant *res; + gboolean can_stop; + + res = g_dbus_proxy_call_sync (manager->priv->sd_proxy, + "CanPowerOff", + NULL, + 0, + G_MAXINT, + NULL, + NULL); + if (!res) { + g_warning ("Calling CanPowerOff failed. Check that logind is " + "properly installed and pam_systemd is getting used at login."); + return FALSE; + } + + g_variant_get (res, "(s)", &rv); + g_variant_unref (res); + + can_stop = g_strcmp0 (rv, "yes") == 0 || + g_strcmp0 (rv, "challenge") == 0; + + g_free (rv); + + return can_stop; +} + +static gboolean +gsm_systemd_is_login_session (GsmSystem *system) +{ + GsmSystemd *manager = GSM_SYSTEMD (system); + int res; + gboolean ret; + gchar *session_class = NULL; + + ret = FALSE; + + if (manager->priv->session_id == NULL) { + return ret; + } + + res = sd_session_get_class (manager->priv->session_id, &session_class); + if (res < 0) { + g_warning ("Could not get session class: %s", strerror (-res)); + return FALSE; + } + ret = (g_strcmp0 (session_class, "greeter") == 0); + free (session_class); + + return ret; +} + +static gboolean +gsm_systemd_can_suspend (GsmSystem *system) +{ + GsmSystemd *manager = GSM_SYSTEMD (system); + gchar *rv; + GVariant *res; + gboolean can_suspend; + + res = g_dbus_proxy_call_sync (manager->priv->sd_proxy, + "CanSuspend", + NULL, + 0, + G_MAXINT, + NULL, + NULL); + if (!res) { + g_warning ("Calling CanSuspend failed. Check that logind is " + "properly installed and pam_systemd is getting used at login."); + return FALSE; + } + + g_variant_get (res, "(s)", &rv); + g_variant_unref (res); + + can_suspend = g_strcmp0 (rv, "yes") == 0 || + g_strcmp0 (rv, "challenge") == 0; + + g_free (rv); + + return can_suspend; +} + +static gboolean +gsm_systemd_can_hibernate (GsmSystem *system) +{ + GsmSystemd *manager = GSM_SYSTEMD (system); + gchar *rv; + GVariant *res; + gboolean can_hibernate; + + res = g_dbus_proxy_call_sync (manager->priv->sd_proxy, + "CanHibernate", + NULL, + 0, + G_MAXINT, + NULL, + NULL); + if (!res) { + g_warning ("Calling CanHibernate failed. Check that logind is " + "properly installed and pam_systemd is getting used at login."); + return FALSE; + } + + g_variant_get (res, "(s)", &rv); + g_variant_unref (res); + + can_hibernate = g_strcmp0 (rv, "yes") == 0 || + g_strcmp0 (rv, "challenge") == 0; + + g_free (rv); + + return can_hibernate; +} + +static void +suspend_done (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GDBusProxy *proxy = G_DBUS_PROXY (source); + GError *error = NULL; + GVariant *res; + + res = g_dbus_proxy_call_finish (proxy, result, &error); + + if (!res) { + g_warning ("Unable to suspend system: %s", error->message); + g_error_free (error); + } else { + g_variant_unref (res); + } +} + +static void +hibernate_done (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GDBusProxy *proxy = G_DBUS_PROXY (source); + GError *error = NULL; + GVariant *res; + + res = g_dbus_proxy_call_finish (proxy, result, &error); + + if (!res) { + g_warning ("Unable to hibernate system: %s", error->message); + g_error_free (error); + } else { + g_variant_unref (res); + } +} + +static void +gsm_systemd_suspend (GsmSystem *system) +{ + GsmSystemd *manager = GSM_SYSTEMD (system); + + g_dbus_proxy_call (manager->priv->sd_proxy, + "Suspend", + g_variant_new ("(b)", TRUE), + 0, + G_MAXINT, + NULL, + hibernate_done, + manager); +} + +static void +gsm_systemd_hibernate (GsmSystem *system) +{ + GsmSystemd *manager = GSM_SYSTEMD (system); + + g_dbus_proxy_call (manager->priv->sd_proxy, + "Hibernate", + g_variant_new ("(b)", TRUE), + 0, + G_MAXINT, + NULL, + suspend_done, + manager); +} + +static void +inhibit_done (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GDBusProxy *proxy = G_DBUS_PROXY (source); + GsmSystemd *manager = GSM_SYSTEMD (user_data); + GError *error = NULL; + GVariant *res; + GUnixFDList *fd_list = NULL; + gint idx; + + /* Drop any previous inhibit before recording the new one */ + drop_system_inhibitor (manager); + + res = g_dbus_proxy_call_with_unix_fd_list_finish (proxy, &fd_list, result, &error); + + if (!res) { + g_warning ("Unable to inhibit system: %s", error->message); + g_error_free (error); + } else { + g_variant_get (res, "(h)", &idx); + manager->priv->inhibit_fd = g_unix_fd_list_get (fd_list, idx, &error); + if (manager->priv->inhibit_fd == -1) { + g_warning ("Failed to receive system inhibitor fd: %s", error->message); + g_error_free (error); + } + g_debug ("System inhibitor fd is %d", manager->priv->inhibit_fd); + g_object_unref (fd_list); + g_variant_unref (res); + } + + /* Handle a race condition, where locks got unset during dbus call */ + if (manager->priv->inhibit_locks == NULL) { + drop_system_inhibitor (manager); + } +} + +static void +gsm_systemd_set_inhibitors (GsmSystem *system, + GsmInhibitorFlag flags) +{ + GsmSystemd *manager = GSM_SYSTEMD (system); + const gchar *locks = NULL; + gboolean inhibit_logout; + gboolean inhibit_suspend; + + inhibit_logout = (flags & GSM_INHIBITOR_FLAG_LOGOUT) != 0; + inhibit_suspend = (flags & GSM_INHIBITOR_FLAG_SUSPEND) != 0; + + if (inhibit_logout && inhibit_suspend) { + locks = "sleep:shutdown"; + } else if (inhibit_logout) { + locks = "shutdown"; + } else if (inhibit_suspend) { + locks = "sleep"; + } + manager->priv->inhibit_locks = locks; + + if (locks != NULL) { + g_debug ("Adding system inhibitor on %s", locks); + g_dbus_proxy_call_with_unix_fd_list (manager->priv->sd_proxy, + "Inhibit", + g_variant_new ("(ssss)", + locks, + g_get_user_name (), + "user session inhibited", + "block"), + 0, + G_MAXINT, + NULL, + NULL, + inhibit_done, + manager); + } else { + drop_system_inhibitor (manager); + } +} + +static void +reboot_or_poweroff_done (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + GsmSystemd *systemd = user_data; + GVariant *result; + GError *error = NULL; + + result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), + res, + &error); + + if (result == NULL) { + if (!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED)) { + g_warning ("Shutdown failed: %s", error->message); + } + g_error_free (error); + drop_delay_inhibitor (systemd); + g_debug ("GsmSystemd: shutdown preparation failed"); + systemd->priv->prepare_for_shutdown_expected = FALSE; + g_signal_emit_by_name (systemd, "shutdown-prepared", FALSE); + } else { + g_variant_unref (result); + } +} + +static void +gsm_systemd_prepare_shutdown (GsmSystem *system, + gboolean restart) +{ + GsmSystemd *systemd = GSM_SYSTEMD (system); + GUnixFDList *fd_list; + GVariant *res; + GError *error = NULL; + gint idx; + + g_debug ("GsmSystemd: prepare shutdown"); + + res = g_dbus_proxy_call_with_unix_fd_list_sync (systemd->priv->sd_proxy, + "Inhibit", + g_variant_new ("(ssss)", + "shutdown", + g_get_user_name (), + "Preparing to end the session", + "delay"), + 0, + G_MAXINT, + NULL, + &fd_list, + NULL, + &error); + if (res == NULL) { + g_warning ("Failed to get delay inhibitor: %s", error->message); + g_error_free (error); + g_signal_emit_by_name (systemd, "shutdown-prepared", FALSE); + return; + } + + g_variant_get (res, "(h)", &idx); + + systemd->priv->delay_inhibit_fd = g_unix_fd_list_get (fd_list, idx, NULL); + + g_debug ("GsmSystemd: got delay inhibitor, fd = %d", systemd->priv->delay_inhibit_fd); + + g_variant_unref (res); + g_object_unref (fd_list); + + systemd->priv->prepare_for_shutdown_expected = TRUE; + + g_dbus_proxy_call (systemd->priv->sd_proxy, + restart ? "Reboot" : "PowerOff", + g_variant_new ("(b)", TRUE), + 0, + G_MAXINT, + NULL, + reboot_or_poweroff_done, + systemd); +} + +static void +gsm_systemd_complete_shutdown (GsmSystem *system) +{ + GsmSystemd *systemd = GSM_SYSTEMD (system); + + /* remove delay inhibitor, if any */ + drop_delay_inhibitor (systemd); +} + +static gboolean +gsm_systemd_is_last_session_for_user (GsmSystem *system) +{ + char **sessions = NULL; + char *session = NULL; + gboolean is_last_session; + int ret, i; + + if (!gsm_systemd_find_session (&session)) { + return FALSE; + } + + ret = sd_uid_get_sessions (getuid (), FALSE, &sessions); + + if (ret <= 0) { + free (session); + return FALSE; + } + + is_last_session = TRUE; + for (i = 0; sessions[i]; i++) { + char *state = NULL; + char *type = NULL; + + if (g_strcmp0 (sessions[i], session) == 0) + continue; + + ret = sd_session_get_state (sessions[i], &state); + + if (ret != 0) + continue; + + if (g_strcmp0 (state, "closing") == 0) { + free (state); + continue; + } + free (state); + + ret = sd_session_get_type (sessions[i], &type); + + if (ret != 0) + continue; + + if (g_strcmp0 (type, "x11") != 0 && + g_strcmp0 (type, "wayland") != 0) { + free (type); + continue; + } + + is_last_session = FALSE; + } + + for (i = 0; sessions[i]; i++) + free (sessions[i]); + free (sessions); + free (session); + + return is_last_session; +} + +static void +gsm_systemd_system_init (GsmSystemInterface *iface) +{ + iface->can_switch_user = gsm_systemd_can_switch_user; + iface->can_stop = gsm_systemd_can_stop; + iface->can_restart = gsm_systemd_can_restart; + iface->can_restart_to_firmware_setup = gsm_systemd_can_restart_to_firmware_setup; + iface->set_restart_to_firmware_setup = gsm_systemd_set_restart_to_firmware_setup; + iface->can_suspend = gsm_systemd_can_suspend; + iface->can_hibernate = gsm_systemd_can_hibernate; + iface->attempt_stop = gsm_systemd_attempt_stop; + iface->attempt_restart = gsm_systemd_attempt_restart; + iface->suspend = gsm_systemd_suspend; + iface->hibernate = gsm_systemd_hibernate; + iface->set_session_idle = gsm_systemd_set_session_idle; + iface->is_login_session = gsm_systemd_is_login_session; + iface->set_inhibitors = gsm_systemd_set_inhibitors; + iface->prepare_shutdown = gsm_systemd_prepare_shutdown; + iface->complete_shutdown = gsm_systemd_complete_shutdown; + iface->is_last_session_for_user = gsm_systemd_is_last_session_for_user; +} + +GsmSystemd * +gsm_systemd_new (void) +{ + GsmSystemd *manager; + + /* logind is not running ? */ + if (access("/run/systemd/seats/", F_OK) < 0) + return NULL; + + manager = g_object_new (GSM_TYPE_SYSTEMD, NULL); + + return manager; +} + +static void +sd_proxy_signal_cb (GDBusProxy *proxy, + const gchar *sender_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + GsmSystemd *systemd = user_data; + gboolean is_about_to_shutdown; + + g_debug ("GsmSystemd: received logind signal: %s", signal_name); + + if (g_strcmp0 (signal_name, "PrepareForShutdown") != 0) { + g_debug ("GsmSystemd: ignoring %s signal", signal_name); + return; + } + + g_variant_get (parameters, "(b)", &is_about_to_shutdown); + if (!is_about_to_shutdown) { + g_debug ("GsmSystemd: ignoring %s signal since about-to-shutdown is FALSE", signal_name); + return; + } + + if (systemd->priv->prepare_for_shutdown_expected) { + g_debug ("GsmSystemd: shutdown successfully prepared"); + g_signal_emit_by_name (systemd, "shutdown-prepared", TRUE); + systemd->priv->prepare_for_shutdown_expected = FALSE; + } +} + +#else + +GsmSystemd * +gsm_systemd_new (void) +{ + return NULL; +} + +#endif + diff --git a/gnome-session/gsm-systemd.h b/gnome-session/gsm-systemd.h new file mode 100644 index 0000000..8fa6aa0 --- /dev/null +++ b/gnome-session/gsm-systemd.h @@ -0,0 +1,59 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * Authors: + * Matthias Clasen + */ + +#ifndef __GSM_SYSTEMD_H__ +#define __GSM_SYSTEMD_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GSM_TYPE_SYSTEMD (gsm_systemd_get_type ()) +#define GSM_SYSTEMD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_SYSTEMD, GsmSystemd)) +#define GSM_SYSTEMD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_SYSTEMD, GsmSystemdClass)) +#define GSM_IS_SYSTEMD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_SYSTEMD)) +#define GSM_IS_SYSTEMD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_SYSTEMD)) +#define GSM_SYSTEMD_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GSM_TYPE_SYSTEMD, GsmSystemdClass)) + +typedef struct _GsmSystemd GsmSystemd; +typedef struct _GsmSystemdClass GsmSystemdClass; +typedef struct _GsmSystemdPrivate GsmSystemdPrivate; + +struct _GsmSystemd +{ + GObject parent; + + GsmSystemdPrivate *priv; +}; + +struct _GsmSystemdClass +{ + GObjectClass parent_class; +}; + +GType gsm_systemd_get_type (void); + +GsmSystemd *gsm_systemd_new (void) G_GNUC_MALLOC; + +G_END_DECLS + +#endif /* __GSM_SYSTEMD_H__ */ diff --git a/gnome-session/gsm-util.c b/gnome-session/gsm-util.c new file mode 100644 index 0000000..f1e6489 --- /dev/null +++ b/gnome-session/gsm-util.c @@ -0,0 +1,863 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * gsm-util.c + * Copyright (C) 2008 Lucas Rocha. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "gsm-util.h" + +static gchar *_saved_session_dir = NULL; +static gchar **child_environment; + +/* These are variables that will not be passed on to subprocesses + * (either directly, via systemd or DBus). + * Some of these are blacklisted as they might end up in the wrong session + * (e.g. XDG_VTNR), others because they simply must never be passed on + * (NOTIFY_SOCKET). + */ +static const char * const variable_blacklist[] = { + "NOTIFY_SOCKET", + "XDG_SEAT", + "XDG_SESSION_ID", + "XDG_VTNR", + NULL +}; + +/* The following is copied from GDMs spawn_session function. + * + * Environment variables listed here will be copied into the user's service + * environments if they are set in gnome-session's environment. If they are + * not set in gnome-session's environment, they will be removed from the + * service environments. This is to protect against environment variables + * leaking from previous sessions (e.g. when switching from classic to + * default GNOME $GNOME_SHELL_SESSION_MODE will become unset). + */ +static const char * const variable_unsetlist[] = { + "DISPLAY", + "XAUTHORITY", + "WAYLAND_DISPLAY", + "WAYLAND_SOCKET", + "GNOME_SHELL_SESSION_MODE", + "GNOME_SETUP_DISPLAY", + + /* None of the LC_* variables should survive a logout/login */ + "LC_CTYPE", + "LC_NUMERIC", + "LC_TIME", + "LC_COLLATE", + "LC_MONETARY", + "LC_MESSAGES", + "LC_PAPER", + "LC_NAME", + "LC_ADDRESS", + "LC_TELEPHONE", + "LC_MEASUREMENT", + "LC_IDENTIFICATION", + "LC_ALL", + + NULL +}; + +char * +gsm_util_find_desktop_file_for_app_name (const char *name, + gboolean look_in_saved_session, + gboolean autostart_first) +{ + char *app_path; + char **app_dirs; + GKeyFile *key_file; + char *desktop_file; + int i; + + app_path = NULL; + + app_dirs = gsm_util_get_desktop_dirs (look_in_saved_session, autostart_first); + + key_file = g_key_file_new (); + + desktop_file = g_strdup_printf ("%s.desktop", name); + + g_debug ("GsmUtil: Looking for file '%s'", desktop_file); + + for (i = 0; app_dirs[i] != NULL; i++) { + g_debug ("GsmUtil: Looking in '%s'", app_dirs[i]); + } + + g_key_file_load_from_dirs (key_file, + desktop_file, + (const char **) app_dirs, + &app_path, + G_KEY_FILE_NONE, + NULL); + + if (app_path != NULL) { + g_debug ("GsmUtil: found in XDG dirs: '%s'", app_path); + } + + /* look for gnome vendor prefix */ + if (app_path == NULL) { + g_free (desktop_file); + desktop_file = g_strdup_printf ("gnome-%s.desktop", name); + + g_key_file_load_from_dirs (key_file, + desktop_file, + (const char **) app_dirs, + &app_path, + G_KEY_FILE_NONE, + NULL); + if (app_path != NULL) { + g_debug ("GsmUtil: found in XDG dirs: '%s'", app_path); + } + } + + g_free (desktop_file); + g_key_file_free (key_file); + + g_strfreev (app_dirs); + + return app_path; +} + +static gboolean +ensure_dir_exists (const char *dir) +{ + if (g_mkdir_with_parents (dir, 0700) == 0) + return TRUE; + + g_warning ("GsmSessionSave: Failed to create directory %s: %s", dir, strerror (errno)); + + return FALSE; +} + +gchar * +gsm_util_get_empty_tmp_session_dir (void) +{ + char *tmp; + gboolean exists; + + tmp = g_build_filename (g_get_user_config_dir (), + "gnome-session", + "saved-session.new", + NULL); + + exists = ensure_dir_exists (tmp); + + if (G_UNLIKELY (!exists)) { + g_warning ("GsmSessionSave: could not create directory for saved session: %s", tmp); + g_free (tmp); + return NULL; + } else { + /* make sure it's empty */ + GDir *dir; + const char *filename; + + dir = g_dir_open (tmp, 0, NULL); + if (dir) { + while ((filename = g_dir_read_name (dir))) { + char *path = g_build_filename (tmp, filename, + NULL); + g_unlink (path); + } + g_dir_close (dir); + } + } + + return tmp; +} + +const gchar * +gsm_util_get_saved_session_dir (void) +{ + if (_saved_session_dir == NULL) { + gboolean exists; + + _saved_session_dir = + g_build_filename (g_get_user_config_dir (), + "gnome-session", + "saved-session", + NULL); + + exists = ensure_dir_exists (_saved_session_dir); + + if (G_UNLIKELY (!exists)) { + static gboolean printed_warning = FALSE; + + if (!printed_warning) { + g_warning ("GsmSessionSave: could not create directory for saved session: %s", _saved_session_dir); + printed_warning = TRUE; + } + + _saved_session_dir = NULL; + + return NULL; + } + } + + return _saved_session_dir; +} + +static char ** autostart_dirs; + +void +gsm_util_set_autostart_dirs (char ** dirs) +{ + autostart_dirs = g_strdupv (dirs); +} + +static char ** +gsm_util_get_standard_autostart_dirs (void) +{ + GPtrArray *dirs; + const char * const *system_config_dirs; + const char * const *system_data_dirs; + int i; + + dirs = g_ptr_array_new (); + + g_ptr_array_add (dirs, + g_build_filename (g_get_user_config_dir (), + "autostart", NULL)); + + system_data_dirs = g_get_system_data_dirs (); + for (i = 0; system_data_dirs[i]; i++) { + g_ptr_array_add (dirs, + g_build_filename (system_data_dirs[i], + "gnome", "autostart", NULL)); + } + + system_config_dirs = g_get_system_config_dirs (); + for (i = 0; system_config_dirs[i]; i++) { + g_ptr_array_add (dirs, + g_build_filename (system_config_dirs[i], + "autostart", NULL)); + } + + g_ptr_array_add (dirs, NULL); + + return (char **) g_ptr_array_free (dirs, FALSE); +} + +char ** +gsm_util_get_autostart_dirs () +{ + if (autostart_dirs) { + return g_strdupv ((char **)autostart_dirs); + } + + return gsm_util_get_standard_autostart_dirs (); +} + +char ** +gsm_util_get_app_dirs () +{ + GPtrArray *dirs; + const char * const *system_data_dirs; + int i; + + dirs = g_ptr_array_new (); + + g_ptr_array_add (dirs, + g_build_filename (g_get_user_data_dir (), + "applications", + NULL)); + + system_data_dirs = g_get_system_data_dirs (); + for (i = 0; system_data_dirs[i]; i++) { + g_ptr_array_add (dirs, + g_build_filename (system_data_dirs[i], + "applications", + NULL)); + } + + g_ptr_array_add (dirs, NULL); + + return (char **) g_ptr_array_free (dirs, FALSE); +} + +char ** +gsm_util_get_desktop_dirs (gboolean include_saved_session, + gboolean autostart_first) +{ + char **apps; + char **autostart; + char **standard_autostart; + char **result; + int size; + int i; + + apps = gsm_util_get_app_dirs (); + autostart = gsm_util_get_autostart_dirs (); + + /* Still, check the standard autostart dirs for things like fulfilling session reqs, + * if using a non-standard autostart dir for autostarting */ + if (autostart_dirs != NULL) + standard_autostart = gsm_util_get_standard_autostart_dirs (); + else + standard_autostart = NULL; + + size = 0; + for (i = 0; apps[i] != NULL; i++) { size++; } + for (i = 0; autostart[i] != NULL; i++) { size++; } + if (autostart_dirs != NULL) + for (i = 0; standard_autostart[i] != NULL; i++) { size++; } + if (include_saved_session) + size += 1; + + result = g_new (char *, size + 1); /* including last NULL */ + + size = 0; + + if (autostart_first) { + if (include_saved_session) + result[size++] = g_strdup (gsm_util_get_saved_session_dir ()); + + for (i = 0; autostart[i] != NULL; i++, size++) { + result[size] = autostart[i]; + } + if (standard_autostart != NULL) { + for (i = 0; standard_autostart[i] != NULL; i++, size++) { + result[size] = standard_autostart[i]; + } + } + for (i = 0; apps[i] != NULL; i++, size++) { + result[size] = apps[i]; + } + } else { + for (i = 0; apps[i] != NULL; i++, size++) { + result[size] = apps[i]; + } + if (standard_autostart != NULL) { + for (i = 0; standard_autostart[i] != NULL; i++, size++) { + result[size] = standard_autostart[i]; + } + } + for (i = 0; autostart[i] != NULL; i++, size++) { + result[size] = autostart[i]; + } + + if (include_saved_session) + result[size++] = g_strdup (gsm_util_get_saved_session_dir ()); + } + + g_free (apps); + g_free (autostart); + g_free (standard_autostart); + + result[size] = NULL; + + return result; +} + +gboolean +gsm_util_text_is_blank (const char *str) +{ + if (str == NULL) { + return TRUE; + } + + while (*str) { + if (!isspace(*str)) { + return FALSE; + } + + str++; + } + + return TRUE; +} + +/** + * gsm_util_init_error: + * @fatal: whether or not the error is fatal to the login session + * @format: printf-style error message format + * @...: error message args + * + * Displays the error message to the user. If @fatal is %TRUE, gsm + * will exit after displaying the message. + * + * This should be called for major errors that occur before the + * session is up and running. (Notably, it positions the dialog box + * itself, since no window manager will be running yet.) + **/ +void +gsm_util_init_error (gboolean fatal, + const char *format, ...) +{ + char *msg; + va_list args; + gchar *argv[13]; + + va_start (args, format); + msg = g_strdup_vprintf (format, args); + va_end (args); + + argv[0] = "zenity"; + argv[1] = "--error"; + argv[2] = "--class"; + argv[3] = "mutter-dialog"; + argv[4] = "--title"; + argv[5] = "\"\""; + argv[6] = "--text"; + argv[7] = msg; + argv[8] = "--icon-name"; + argv[9] = "face-sad-symbolic"; + argv[10] = "--ok-label"; + argv[11] = _("_Log out"); + argv[12] = NULL; + + g_spawn_sync (NULL, argv, child_environment, 0, NULL, NULL, NULL, NULL, NULL, NULL); + + g_free (msg); + + if (fatal) { + exit (1); + } +} + +/** + * gsm_util_generate_startup_id: + * + * Generates a new SM client ID. + * + * Return value: an SM client ID. + **/ +char * +gsm_util_generate_startup_id (void) +{ + static int sequence = -1; + static guint rand1 = 0; + static guint rand2 = 0; + static pid_t pid = 0; + struct timeval tv; + + /* The XSMP spec defines the ID as: + * + * Version: "1" + * Address type and address: + * "1" + an IPv4 address as 8 hex digits + * "2" + a DECNET address as 12 hex digits + * "6" + an IPv6 address as 32 hex digits + * Time stamp: milliseconds since UNIX epoch as 13 decimal digits + * Process-ID type and process-ID: + * "1" + POSIX PID as 10 decimal digits + * Sequence number as 4 decimal digits + * + * XSMP client IDs are supposed to be globally unique: if + * SmsGenerateClientID() is unable to determine a network + * address for the machine, it gives up and returns %NULL. + * GNOME and KDE have traditionally used a fourth address + * format in this case: + * "0" + 16 random hex digits + * + * We don't even bother trying SmsGenerateClientID(), since the + * user's IP address is probably "192.168.1.*" anyway, so a random + * number is actually more likely to be globally unique. + */ + + if (!rand1) { + rand1 = g_random_int (); + rand2 = g_random_int (); + pid = getpid (); + } + + sequence = (sequence + 1) % 10000; + gettimeofday (&tv, NULL); + return g_strdup_printf ("10%.04x%.04x%.10lu%.3u%.10lu%.4d", + rand1, + rand2, + (unsigned long) tv.tv_sec, + (unsigned) tv.tv_usec, + (unsigned long) pid, + sequence); +} + +static gboolean +gsm_util_update_activation_environment (const char *variable, + const char *value, + GError **error) +{ + GDBusConnection *connection; + gboolean environment_updated; + GVariantBuilder builder; + GVariant *reply; + GError *bus_error = NULL; + + environment_updated = FALSE; + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error); + + if (connection == NULL) { + return FALSE; + } + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}")); + g_variant_builder_add (&builder, "{ss}", variable, value); + + reply = g_dbus_connection_call_sync (connection, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "UpdateActivationEnvironment", + g_variant_new ("(@a{ss})", + g_variant_builder_end (&builder)), + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, &bus_error); + + if (bus_error != NULL) { + g_propagate_error (error, bus_error); + } else { + environment_updated = TRUE; + g_variant_unref (reply); + } + + g_clear_object (&connection); + + return environment_updated; +} + +gboolean +gsm_util_export_activation_environment (GError **error) +{ + + GDBusConnection *connection; + gboolean environment_updated = FALSE; + char **entry_names; + int i = 0; + GVariantBuilder builder; + GRegex *name_regex, *value_regex; + GVariant *reply; + GError *bus_error = NULL; + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error); + + if (connection == NULL) { + return FALSE; + } + + name_regex = g_regex_new ("^[a-zA-Z_][a-zA-Z0-9_]*$", G_REGEX_OPTIMIZE, 0, error); + + if (name_regex == NULL) { + return FALSE; + } + + value_regex = g_regex_new ("^(?:[ \t\n]|[^[:cntrl:]])*$", G_REGEX_OPTIMIZE, 0, error); + + if (value_regex == NULL) { + return FALSE; + } + + if (child_environment == NULL) { + child_environment = g_listenv (); + } + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}")); + for (entry_names = g_listenv (); entry_names[i] != NULL; i++) { + const char *entry_name = entry_names[i]; + const char *entry_value = g_getenv (entry_name); + + if (g_strv_contains (variable_blacklist, entry_name)) + continue; + + if (!g_utf8_validate (entry_name, -1, NULL) || + !g_regex_match (name_regex, entry_name, 0, NULL) || + !g_utf8_validate (entry_value, -1, NULL) || + !g_regex_match (value_regex, entry_value, 0, NULL)) { + + g_message ("Environment variable is unsafe to export to dbus: %s", entry_name); + continue; + } + + child_environment = g_environ_setenv (child_environment, + entry_name, entry_value, + TRUE); + g_variant_builder_add (&builder, "{ss}", entry_name, entry_value); + } + g_regex_unref (name_regex); + g_regex_unref (value_regex); + + g_strfreev (entry_names); + + reply = g_dbus_connection_call_sync (connection, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "UpdateActivationEnvironment", + g_variant_new ("(@a{ss})", + g_variant_builder_end (&builder)), + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, &bus_error); + + if (bus_error != NULL) { + g_propagate_error (error, bus_error); + } else { + environment_updated = TRUE; + g_variant_unref (reply); + } + + g_clear_object (&connection); + + return environment_updated; +} + +#ifdef HAVE_SYSTEMD +gboolean +gsm_util_export_user_environment (GError **error) +{ + + GDBusConnection *connection; + gboolean environment_updated = FALSE; + char **entries; + int i = 0; + GVariantBuilder builder; + GRegex *regex; + GVariant *reply; + GError *bus_error = NULL; + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error); + + if (connection == NULL) { + return FALSE; + } + + regex = g_regex_new ("^[a-zA-Z_][a-zA-Z0-9_]*=(?:[ \t\n]|[^[:cntrl:]])*$", G_REGEX_OPTIMIZE, 0, error); + + if (regex == NULL) { + return FALSE; + } + + entries = g_get_environ (); + + for (i = 0; variable_blacklist[i] != NULL; i++) + entries = g_environ_unsetenv (entries, variable_blacklist[i]); + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("(asas)")); + + g_variant_builder_open (&builder, G_VARIANT_TYPE ("as")); + for (i = 0; variable_unsetlist[i] != NULL; i++) + g_variant_builder_add (&builder, "s", variable_unsetlist[i]); + for (i = 0; variable_blacklist[i] != NULL; i++) + g_variant_builder_add (&builder, "s", variable_blacklist[i]); + g_variant_builder_close (&builder); + + g_variant_builder_open (&builder, G_VARIANT_TYPE ("as")); + for (i = 0; entries[i] != NULL; i++) { + const char *entry = entries[i]; + + if (!g_utf8_validate (entry, -1, NULL) || + !g_regex_match (regex, entry, 0, NULL)) { + + g_message ("Environment entry is unsafe to upload into user environment: %s", entry); + continue; + } + + g_variant_builder_add (&builder, "s", entry); + } + g_variant_builder_close (&builder); + g_regex_unref (regex); + + g_strfreev (entries); + + reply = g_dbus_connection_call_sync (connection, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "UnsetAndSetEnvironment", + g_variant_builder_end (&builder), + NULL, + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, NULL, &bus_error); + + if (bus_error != NULL) { + g_propagate_error (error, bus_error); + } else { + environment_updated = TRUE; + g_variant_unref (reply); + } + + g_clear_object (&connection); + + return environment_updated; +} + +static gboolean +gsm_util_update_user_environment (const char *variable, + const char *value, + GError **error) +{ + GDBusConnection *connection; + gboolean environment_updated; + char *entry; + GVariantBuilder builder; + GVariant *reply; + GError *bus_error = NULL; + + environment_updated = FALSE; + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error); + + if (connection == NULL) { + return FALSE; + } + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("as")); + entry = g_strdup_printf ("%s=%s", variable, value); + g_variant_builder_add (&builder, "s", entry); + g_free (entry); + + reply = g_dbus_connection_call_sync (connection, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "SetEnvironment", + g_variant_new ("(@as)", + g_variant_builder_end (&builder)), + NULL, + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, NULL, &bus_error); + + if (bus_error != NULL) { + g_propagate_error (error, bus_error); + } else { + environment_updated = TRUE; + g_variant_unref (reply); + } + + g_clear_object (&connection); + + return environment_updated; +} + +gboolean +gsm_util_start_systemd_unit (const char *unit, + const char *mode, + GError **error) +{ + g_autoptr(GDBusConnection) connection = NULL; + g_autoptr(GVariant) reply = NULL; + GError *bus_error = NULL; + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error); + + if (connection == NULL) + return FALSE; + + reply = g_dbus_connection_call_sync (connection, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartUnit", + g_variant_new ("(ss)", + unit, mode), + NULL, + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, NULL, &bus_error); + + if (bus_error != NULL) { + g_propagate_error (error, bus_error); + return FALSE; + } + + return TRUE; +} + +gboolean +gsm_util_systemd_reset_failed (GError **error) +{ + g_autoptr(GDBusConnection) connection = NULL; + g_autoptr(GVariant) reply = NULL; + GError *bus_error = NULL; + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error); + + if (connection == NULL) + return FALSE; + + reply = g_dbus_connection_call_sync (connection, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ResetFailed", + NULL, + NULL, + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, NULL, &bus_error); + + if (bus_error != NULL) { + g_propagate_error (error, bus_error); + return FALSE; + } + + return TRUE; +} +#endif + +void +gsm_util_setenv (const char *variable, + const char *value) +{ + GError *error = NULL; + + if (child_environment == NULL) + child_environment = g_listenv (); + + if (!value) + child_environment = g_environ_unsetenv (child_environment, variable); + else + child_environment = g_environ_setenv (child_environment, variable, value, TRUE); + + /* If this fails it isn't fatal, it means some things like session + * management and keyring won't work in activated clients. + */ + if (!gsm_util_update_activation_environment (variable, value, &error)) { + g_warning ("Could not make bus activated clients aware of %s=%s environment variable: %s", variable, value, error->message); + g_clear_error (&error); + } + +#ifdef HAVE_SYSTEMD + /* If this fails, the system user session won't get the updated environment + */ + if (!gsm_util_update_user_environment (variable, value, &error)) { + g_debug ("Could not make systemd aware of %s=%s environment variable: %s", variable, value, error->message); + g_clear_error (&error); + } +#endif +} + +const char * const * +gsm_util_listenv (void) +{ + return (const char * const *) child_environment; + +} + +const char * const * +gsm_util_get_variable_blacklist (void) +{ + return variable_blacklist; +} diff --git a/gnome-session/gsm-util.h b/gnome-session/gsm-util.h new file mode 100644 index 0000000..bc26a21 --- /dev/null +++ b/gnome-session/gsm-util.h @@ -0,0 +1,69 @@ +/* gsm-util.h + * Copyright (C) 2008 Lucas Rocha. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef __GSM_UTIL_H__ +#define __GSM_UTIL_H__ + +#include + +G_BEGIN_DECLS + +#define IS_STRING_EMPTY(x) ((x)==NULL||(x)[0]=='\0') + +char * gsm_util_find_desktop_file_for_app_name (const char *app_name, + gboolean look_in_saved_session, + gboolean autostart_first); + +gchar *gsm_util_get_empty_tmp_session_dir (void); + +const char *gsm_util_get_saved_session_dir (void); + +gchar** gsm_util_get_app_dirs (void); + +gchar** gsm_util_get_autostart_dirs (void); +void gsm_util_set_autostart_dirs (char **dirs); + +gchar ** gsm_util_get_desktop_dirs (gboolean include_saved_session, + gboolean autostart_first); + +gboolean gsm_util_text_is_blank (const char *str); + +void gsm_util_init_error (gboolean fatal, + const char *format, ...) G_GNUC_PRINTF (2, 3); + +char * gsm_util_generate_startup_id (void); + +void gsm_util_setenv (const char *variable, + const char *value); +const char * const * gsm_util_listenv (void); +const char * const * gsm_util_get_variable_blacklist(void); + +gboolean gsm_util_export_activation_environment (GError **error); +#ifdef HAVE_SYSTEMD +gboolean gsm_util_export_user_environment (GError **error); +gboolean gsm_util_start_systemd_unit (const char *unit, + const char *mode, + GError **error); +gboolean gsm_util_systemd_reset_failed (GError **error); + +#endif + +void gsm_quit (void); + +G_END_DECLS + +#endif /* __GSM_UTIL_H__ */ diff --git a/gnome-session/gsm-xsmp-client.c b/gnome-session/gsm-xsmp-client.c new file mode 100644 index 0000000..8a30926 --- /dev/null +++ b/gnome-session/gsm-xsmp-client.c @@ -0,0 +1,1359 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "gsm-xsmp-client.h" + +#include "gsm-util.h" +#include "gsm-autostart-app.h" +#include "gsm-icon-names.h" +#include "gsm-manager.h" + +#define GsmDesktopFile "_GSM_DesktopFile" + +#define GSM_XSMP_CLIENT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSM_TYPE_XSMP_CLIENT, GsmXSMPClientPrivate)) + +struct GsmXSMPClientPrivate +{ + + SmsConn conn; + IceConn ice_connection; + + guint watch_id; + + char *description; + GPtrArray *props; + + /* SaveYourself state */ + int current_save_yourself; + int next_save_yourself; + guint next_save_yourself_allow_interact : 1; +}; + +enum { + PROP_0, + PROP_ICE_CONNECTION +}; + +enum { + REGISTER_REQUEST, + REGISTER_CONFIRMED, + LOGOUT_REQUEST, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE (GsmXSMPClient, gsm_xsmp_client, GSM_TYPE_CLIENT) + +static gboolean +client_iochannel_watch (GIOChannel *channel, + GIOCondition condition, + GsmXSMPClient *client) +{ + gboolean keep_going; + + g_object_ref (client); + switch (IceProcessMessages (client->priv->ice_connection, NULL, NULL)) { + case IceProcessMessagesSuccess: + keep_going = TRUE; + break; + + case IceProcessMessagesIOError: + g_debug ("GsmXSMPClient: IceProcessMessagesIOError on '%s'", client->priv->description); + gsm_client_set_status (GSM_CLIENT (client), GSM_CLIENT_FAILED); + /* Emitting "disconnected" will eventually cause + * IceCloseConnection() to be called. + */ + gsm_client_disconnected (GSM_CLIENT (client)); + keep_going = FALSE; + break; + + case IceProcessMessagesConnectionClosed: + g_debug ("GsmXSMPClient: IceProcessMessagesConnectionClosed on '%s'", + client->priv->description); + client->priv->ice_connection = NULL; + keep_going = FALSE; + break; + + default: + g_assert_not_reached (); + } + g_object_unref (client); + + return keep_going; +} + +static SmProp * +find_property (GsmXSMPClient *client, + const char *name, + int *index) +{ + SmProp *prop; + int i; + + for (i = 0; i < client->priv->props->len; i++) { + prop = client->priv->props->pdata[i]; + + if (!strcmp (prop->name, name)) { + if (index) { + *index = i; + } + return prop; + } + } + + return NULL; +} + +static void +set_description (GsmXSMPClient *client) +{ + SmProp *prop; + const char *id; + + prop = find_property (client, SmProgram, NULL); + id = gsm_client_peek_startup_id (GSM_CLIENT (client)); + + g_free (client->priv->description); + if (prop) { + client->priv->description = g_strdup_printf ("%p [%.*s %s]", + client, + prop->vals[0].length, + (char *)prop->vals[0].value, + id); + } else if (id != NULL) { + client->priv->description = g_strdup_printf ("%p [%s]", client, id); + } else { + client->priv->description = g_strdup_printf ("%p", client); + } +} + +static void +setup_connection (GsmXSMPClient *client) +{ + GIOChannel *channel; + int fd; + + g_debug ("GsmXSMPClient: Setting up new connection"); + + fd = IceConnectionNumber (client->priv->ice_connection); + fcntl (fd, F_SETFD, fcntl (fd, F_GETFD, 0) | FD_CLOEXEC); + channel = g_io_channel_unix_new (fd); + client->priv->watch_id = g_io_add_watch (channel, + G_IO_IN | G_IO_ERR, + (GIOFunc)client_iochannel_watch, + client); + g_io_channel_unref (channel); + + set_description (client); + + g_debug ("GsmXSMPClient: New client '%s'", client->priv->description); +} + +static GObject * +gsm_xsmp_client_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GsmXSMPClient *client; + + client = GSM_XSMP_CLIENT (G_OBJECT_CLASS (gsm_xsmp_client_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + setup_connection (client); + + return G_OBJECT (client); +} + +static void +gsm_xsmp_client_init (GsmXSMPClient *client) +{ + client->priv = GSM_XSMP_CLIENT_GET_PRIVATE (client); + + client->priv->props = g_ptr_array_new (); + client->priv->current_save_yourself = -1; + client->priv->next_save_yourself = -1; + client->priv->next_save_yourself_allow_interact = FALSE; +} + + +static void +delete_property (GsmXSMPClient *client, + const char *name) +{ + int index; + SmProp *prop; + + prop = find_property (client, name, &index); + if (!prop) { + return; + } + +#if 0 + /* This is wrong anyway; we can't unconditionally run the current + * discard command; if this client corresponds to a GsmAppResumed, + * and the current discard command is identical to the app's + * discard_command, then we don't run the discard command now, + * because that would delete a saved state we may want to resume + * again later. + */ + if (!strcmp (name, SmDiscardCommand)) { + gsm_client_run_discard (GSM_CLIENT (client)); + } +#endif + + g_ptr_array_remove_index_fast (client->priv->props, index); + SmFreeProperty (prop); +} + + +static void +debug_print_property (SmProp *prop) +{ + GString *tmp; + int i; + + switch (prop->type[0]) { + case 'C': /* CARD8 */ + g_debug ("GsmXSMPClient: %s = %d", prop->name, *(unsigned char *)prop->vals[0].value); + break; + + case 'A': /* ARRAY8 */ + g_debug ("GsmXSMPClient: %s = '%s'", prop->name, (char *)prop->vals[0].value); + break; + + case 'L': /* LISTofARRAY8 */ + tmp = g_string_new (NULL); + for (i = 0; i < prop->num_vals; i++) { + g_string_append_printf (tmp, "'%.*s' ", prop->vals[i].length, + (char *)prop->vals[i].value); + } + g_debug ("GsmXSMPClient: %s = %s", prop->name, tmp->str); + g_string_free (tmp, TRUE); + break; + + default: + g_debug ("GsmXSMPClient: %s = ??? (%s)", prop->name, prop->type); + break; + } +} + + +static void +set_properties_callback (SmsConn conn, + SmPointer manager_data, + int num_props, + SmProp **props) +{ + GsmXSMPClient *client = manager_data; + int i; + + g_debug ("GsmXSMPClient: Set properties from client '%s'", client->priv->description); + + for (i = 0; i < num_props; i++) { + delete_property (client, props[i]->name); + g_ptr_array_add (client->priv->props, props[i]); + + debug_print_property (props[i]); + + if (!strcmp (props[i]->name, SmProgram)) + set_description (client); + } + + free (props); + +} + +static void +delete_properties_callback (SmsConn conn, + SmPointer manager_data, + int num_props, + char **prop_names) +{ + GsmXSMPClient *client = manager_data; + int i; + + g_debug ("GsmXSMPClient: Delete properties from '%s'", client->priv->description); + + for (i = 0; i < num_props; i++) { + delete_property (client, prop_names[i]); + + g_debug (" %s", prop_names[i]); + } + + free (prop_names); +} + +static void +get_properties_callback (SmsConn conn, + SmPointer manager_data) +{ + GsmXSMPClient *client = manager_data; + + g_debug ("GsmXSMPClient: Get properties request from '%s'", client->priv->description); + + SmsReturnProperties (conn, + client->priv->props->len, + (SmProp **)client->priv->props->pdata); +} + +static char * +prop_to_command (SmProp *prop) +{ + GString *str; + int i, j; + gboolean need_quotes; + + str = g_string_new (NULL); + for (i = 0; i < prop->num_vals; i++) { + char *val = prop->vals[i].value; + + need_quotes = FALSE; + for (j = 0; j < prop->vals[i].length; j++) { + if (!g_ascii_isalnum (val[j]) && !strchr ("-_=:./", val[j])) { + need_quotes = TRUE; + break; + } + } + + if (i > 0) { + g_string_append_c (str, ' '); + } + + if (!need_quotes) { + g_string_append_printf (str, + "%.*s", + prop->vals[i].length, + (char *)prop->vals[i].value); + } else { + g_string_append_c (str, '\''); + while (val < (char *)prop->vals[i].value + prop->vals[i].length) { + if (*val == '\'') { + g_string_append (str, "'\''"); + } else { + g_string_append_c (str, *val); + } + val++; + } + g_string_append_c (str, '\''); + } + } + + return g_string_free (str, FALSE); +} + +static char * +xsmp_get_restart_command (GsmClient *client) +{ + SmProp *prop; + + prop = find_property (GSM_XSMP_CLIENT (client), SmRestartCommand, NULL); + + if (!prop || strcmp (prop->type, SmLISTofARRAY8) != 0) { + return NULL; + } + + return prop_to_command (prop); +} + +static char * +xsmp_get_discard_command (GsmClient *client) +{ + SmProp *prop; + + prop = find_property (GSM_XSMP_CLIENT (client), SmDiscardCommand, NULL); + + if (!prop || strcmp (prop->type, SmLISTofARRAY8) != 0) { + return NULL; + } + + return prop_to_command (prop); +} + +static void +do_save_yourself (GsmXSMPClient *client, + int save_type, + gboolean allow_interact) +{ + g_assert (client->priv->conn != NULL); + + if (client->priv->next_save_yourself != -1) { + /* Either we're currently doing a shutdown and there's a checkpoint + * queued after it, or vice versa. Either way, the new SaveYourself + * is redundant. + */ + g_debug ("GsmXSMPClient: skipping redundant SaveYourself for '%s'", + client->priv->description); + } else if (client->priv->current_save_yourself != -1) { + g_debug ("GsmXSMPClient: queuing new SaveYourself for '%s'", + client->priv->description); + client->priv->next_save_yourself = save_type; + client->priv->next_save_yourself_allow_interact = allow_interact; + } else { + client->priv->current_save_yourself = save_type; + /* make sure we don't have anything queued */ + client->priv->next_save_yourself = -1; + client->priv->next_save_yourself_allow_interact = FALSE; + + switch (save_type) { + case SmSaveLocal: + /* Save state */ + SmsSaveYourself (client->priv->conn, + SmSaveLocal, + FALSE, + SmInteractStyleNone, + FALSE); + break; + + default: + /* Logout */ + if (!allow_interact) { + SmsSaveYourself (client->priv->conn, + save_type, /* save type */ + TRUE, /* shutdown */ + SmInteractStyleNone, /* interact style */ + TRUE); /* fast */ + } else { + SmsSaveYourself (client->priv->conn, + save_type, /* save type */ + TRUE, /* shutdown */ + SmInteractStyleAny, /* interact style */ + FALSE /* fast */); + } + break; + } + } +} + +static void +xsmp_save_yourself_phase2 (GsmClient *client) +{ + GsmXSMPClient *xsmp = (GsmXSMPClient *) client; + + g_debug ("GsmXSMPClient: xsmp_save_yourself_phase2 ('%s')", xsmp->priv->description); + + SmsSaveYourselfPhase2 (xsmp->priv->conn); +} + +static void +xsmp_interact (GsmClient *client) +{ + GsmXSMPClient *xsmp = (GsmXSMPClient *) client; + + g_debug ("GsmXSMPClient: xsmp_interact ('%s')", xsmp->priv->description); + + SmsInteract (xsmp->priv->conn); +} + +static gboolean +xsmp_cancel_end_session (GsmClient *client, + GError **error) +{ + GsmXSMPClient *xsmp = (GsmXSMPClient *) client; + + g_debug ("GsmXSMPClient: xsmp_cancel_end_session ('%s')", xsmp->priv->description); + + if (xsmp->priv->conn == NULL) { + g_set_error (error, + GSM_CLIENT_ERROR, + GSM_CLIENT_ERROR_NOT_REGISTERED, + "Client is not registered"); + return FALSE; + } + + SmsShutdownCancelled (xsmp->priv->conn); + + /* reset the state */ + xsmp->priv->current_save_yourself = -1; + xsmp->priv->next_save_yourself = -1; + xsmp->priv->next_save_yourself_allow_interact = FALSE; + + return TRUE; +} + +static char * +get_desktop_file_path (GsmXSMPClient *client) +{ + SmProp *prop; + char *desktop_file_path = NULL; + const char *program_name; + + /* XSMP clients using eggsmclient defines a special property + * pointing to their respective desktop entry file */ + prop = find_property (client, GsmDesktopFile, NULL); + + if (prop) { + GFile *file = g_file_new_for_uri (prop->vals[0].value); + desktop_file_path = g_file_get_path (file); + g_object_unref (file); + goto out; + } + + /* If we can't get desktop file from GsmDesktopFile then we + * try to find the desktop file from its program name */ + prop = find_property (client, SmProgram, NULL); + + if (!prop) { + goto out; + } + + program_name = prop->vals[0].value; + desktop_file_path = + gsm_util_find_desktop_file_for_app_name (program_name, + TRUE, FALSE); + +out: + g_debug ("GsmXSMPClient: desktop file for client %s is %s", + gsm_client_peek_id (GSM_CLIENT (client)), + desktop_file_path ? desktop_file_path : "(null)"); + + return desktop_file_path; +} + +static void +set_desktop_file_keys_from_client (GsmClient *client, + GKeyFile *keyfile) +{ + SmProp *prop; + const char *name; + char *comment; + + prop = find_property (GSM_XSMP_CLIENT (client), SmProgram, NULL); + if (prop) { + name = prop->vals[0].value; + } else { + /* It'd be really surprising to reach this code: if we're here, + * then the XSMP client already has set several XSMP + * properties. But it could still be that SmProgram is not set. + */ + name = _("Remembered Application"); + } + + comment = g_strdup_printf ("Client %s which was automatically saved", + gsm_client_peek_startup_id (client)); + + g_key_file_set_string (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + G_KEY_FILE_DESKTOP_KEY_NAME, + name); + + g_key_file_set_string (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + G_KEY_FILE_DESKTOP_KEY_COMMENT, + comment); + + g_key_file_set_string (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + G_KEY_FILE_DESKTOP_KEY_ICON, + GSM_ICON_XSMP_DEFAULT); + + g_key_file_set_string (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + G_KEY_FILE_DESKTOP_KEY_TYPE, + "Application"); + + g_key_file_set_boolean (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + G_KEY_FILE_DESKTOP_KEY_STARTUP_NOTIFY, + TRUE); + + g_free (comment); +} + +static GKeyFile * +create_client_key_file (GsmClient *client, + const char *desktop_file_path, + GError **error) { + GKeyFile *keyfile; + + keyfile = g_key_file_new (); + + if (desktop_file_path != NULL) { + g_key_file_load_from_file (keyfile, + desktop_file_path, + G_KEY_FILE_KEEP_COMMENTS | + G_KEY_FILE_KEEP_TRANSLATIONS, + error); + } else { + set_desktop_file_keys_from_client (client, keyfile); + } + + return keyfile; +} + +static GsmClientRestartStyle +xsmp_get_restart_style_hint (GsmClient *client); + +static GKeyFile * +xsmp_save (GsmClient *client, + GsmApp *app, + GError **error) +{ + GsmClientRestartStyle restart_style; + + GKeyFile *keyfile = NULL; + char *desktop_file_path = NULL; + char *exec_program = NULL; + char *exec_discard = NULL; + char *startup_id = NULL; + GError *local_error; + + g_debug ("GsmXSMPClient: saving client with id %s", + gsm_client_peek_id (client)); + + local_error = NULL; + + restart_style = xsmp_get_restart_style_hint (client); + if (restart_style == GSM_CLIENT_RESTART_NEVER) { + goto out; + } + + exec_program = xsmp_get_restart_command (client); + if (!exec_program) { + goto out; + } + + desktop_file_path = get_desktop_file_path (GSM_XSMP_CLIENT (client)); + + /* this can accept desktop_file_path == NULL */ + keyfile = create_client_key_file (client, + desktop_file_path, + &local_error); + + if (local_error) { + goto out; + } + + g_object_get (client, + "startup-id", &startup_id, + NULL); + + g_key_file_set_string (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + GSM_AUTOSTART_APP_STARTUP_ID_KEY, + startup_id); + + g_key_file_set_string (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + G_KEY_FILE_DESKTOP_KEY_EXEC, + exec_program); + + exec_discard = xsmp_get_discard_command (client); + if (exec_discard) + g_key_file_set_string (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + GSM_AUTOSTART_APP_DISCARD_KEY, + exec_discard); + + if (app != NULL) { + gsm_app_save_to_keyfile (app, keyfile, &local_error); + + if (local_error) { + goto out; + } + } + +out: + g_free (desktop_file_path); + g_free (exec_program); + g_free (exec_discard); + g_free (startup_id); + + if (local_error != NULL) { + g_propagate_error (error, local_error); + g_key_file_free (keyfile); + + return NULL; + } + + return keyfile; +} + +static gboolean +xsmp_stop (GsmClient *client, + GError **error) +{ + GsmXSMPClient *xsmp = (GsmXSMPClient *) client; + + g_debug ("GsmXSMPClient: xsmp_stop ('%s')", xsmp->priv->description); + + if (xsmp->priv->conn == NULL) { + g_set_error (error, + GSM_CLIENT_ERROR, + GSM_CLIENT_ERROR_NOT_REGISTERED, + "Client is not registered"); + return FALSE; + } + + SmsDie (xsmp->priv->conn); + + return TRUE; +} + +static gboolean +xsmp_query_end_session (GsmClient *client, + GsmClientEndSessionFlag flags, + GError **error) +{ + gboolean allow_interact; + int save_type; + + if (GSM_XSMP_CLIENT (client)->priv->conn == NULL) { + g_set_error (error, + GSM_CLIENT_ERROR, + GSM_CLIENT_ERROR_NOT_REGISTERED, + "Client is not registered"); + return FALSE; + } + + allow_interact = !(flags & GSM_CLIENT_END_SESSION_FLAG_FORCEFUL); + + /* we don't want to save the session state, but we just want to know if + * there's user data the client has to save and we want to give the + * client a chance to tell the user about it. This is consistent with + * the manager not setting GSM_CLIENT_END_SESSION_FLAG_SAVE for this + * phase. */ + save_type = SmSaveGlobal; + + do_save_yourself (GSM_XSMP_CLIENT (client), save_type, allow_interact); + return TRUE; +} + +static gboolean +xsmp_end_session (GsmClient *client, + GsmClientEndSessionFlag flags, + GError **error) +{ + gboolean phase2; + + if (GSM_XSMP_CLIENT (client)->priv->conn == NULL) { + g_set_error (error, + GSM_CLIENT_ERROR, + GSM_CLIENT_ERROR_NOT_REGISTERED, + "Client is not registered"); + return FALSE; + } + + phase2 = (flags & GSM_CLIENT_END_SESSION_FLAG_LAST); + + if (phase2) { + xsmp_save_yourself_phase2 (client); + } else { + gboolean allow_interact; + int save_type; + + /* we gave a chance to interact to the app during + * xsmp_query_end_session(), now it's too late to interact */ + allow_interact = FALSE; + + if (flags & GSM_CLIENT_END_SESSION_FLAG_SAVE) { + save_type = SmSaveBoth; + } else { + save_type = SmSaveGlobal; + } + + do_save_yourself (GSM_XSMP_CLIENT (client), + save_type, allow_interact); + } + + return TRUE; +} + +static char * +xsmp_get_app_name (GsmClient *client) +{ + SmProp *prop; + char *name = NULL; + + prop = find_property (GSM_XSMP_CLIENT (client), SmProgram, NULL); + if (prop) { + name = prop_to_command (prop); + } + + return name; +} + +static void +gsm_client_set_ice_connection (GsmXSMPClient *client, + gpointer conn) +{ + client->priv->ice_connection = conn; +} + +static void +gsm_xsmp_client_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GsmXSMPClient *self; + + self = GSM_XSMP_CLIENT (object); + + switch (prop_id) { + case PROP_ICE_CONNECTION: + gsm_client_set_ice_connection (self, g_value_get_pointer (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gsm_xsmp_client_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GsmXSMPClient *self; + + self = GSM_XSMP_CLIENT (object); + + switch (prop_id) { + case PROP_ICE_CONNECTION: + g_value_set_pointer (value, self->priv->ice_connection); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gsm_xsmp_client_disconnect (GsmXSMPClient *client) +{ + if (client->priv->watch_id > 0) { + g_source_remove (client->priv->watch_id); + } + + if (client->priv->conn != NULL) { + SmsCleanUp (client->priv->conn); + } + + if (client->priv->ice_connection != NULL) { + IceSetShutdownNegotiation (client->priv->ice_connection, FALSE); + IceCloseConnection (client->priv->ice_connection); + } +} + +static void +gsm_xsmp_client_finalize (GObject *object) +{ + GsmXSMPClient *client = (GsmXSMPClient *) object; + + g_debug ("GsmXSMPClient: xsmp_finalize (%s)", client->priv->description); + gsm_xsmp_client_disconnect (client); + + g_free (client->priv->description); + g_ptr_array_foreach (client->priv->props, (GFunc)SmFreeProperty, NULL); + g_ptr_array_free (client->priv->props, TRUE); + + G_OBJECT_CLASS (gsm_xsmp_client_parent_class)->finalize (object); +} + +static gboolean +_boolean_handled_accumulator (GSignalInvocationHint *ihint, + GValue *return_accu, + const GValue *handler_return, + gpointer dummy) +{ + gboolean continue_emission; + gboolean signal_handled; + + signal_handled = g_value_get_boolean (handler_return); + g_value_set_boolean (return_accu, signal_handled); + continue_emission = !signal_handled; + + return continue_emission; +} + +static GsmClientRestartStyle +xsmp_get_restart_style_hint (GsmClient *client) +{ + SmProp *prop; + GsmClientRestartStyle hint; + + g_debug ("GsmXSMPClient: getting restart style"); + hint = GSM_CLIENT_RESTART_IF_RUNNING; + + prop = find_property (GSM_XSMP_CLIENT (client), SmRestartStyleHint, NULL); + + if (!prop || strcmp (prop->type, SmCARD8) != 0) { + return GSM_CLIENT_RESTART_IF_RUNNING; + } + + switch (((unsigned char *)prop->vals[0].value)[0]) { + case SmRestartIfRunning: + hint = GSM_CLIENT_RESTART_IF_RUNNING; + break; + case SmRestartAnyway: + hint = GSM_CLIENT_RESTART_ANYWAY; + break; + case SmRestartImmediately: + hint = GSM_CLIENT_RESTART_IMMEDIATELY; + break; + case SmRestartNever: + hint = GSM_CLIENT_RESTART_NEVER; + break; + default: + break; + } + + return hint; +} + +static gboolean +_parse_value_as_uint (const char *value, + guint *uintval) +{ + char *end_of_valid_uint; + gulong ulong_value; + guint uint_value; + + errno = 0; + ulong_value = strtoul (value, &end_of_valid_uint, 10); + + if (*value == '\0' || *end_of_valid_uint != '\0') { + return FALSE; + } + + uint_value = ulong_value; + if (uint_value != ulong_value || errno == ERANGE) { + return FALSE; + } + + *uintval = uint_value; + + return TRUE; +} + +static guint +xsmp_get_unix_process_id (GsmClient *client) +{ + SmProp *prop; + guint pid; + gboolean res; + + g_debug ("GsmXSMPClient: getting pid"); + + prop = find_property (GSM_XSMP_CLIENT (client), SmProcessID, NULL); + + if (!prop || strcmp (prop->type, SmARRAY8) != 0) { + return 0; + } + + pid = 0; + res = _parse_value_as_uint ((char *)prop->vals[0].value, &pid); + if (! res) { + pid = 0; + } + + return pid; +} + +static void +gsm_xsmp_client_class_init (GsmXSMPClientClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GsmClientClass *client_class = GSM_CLIENT_CLASS (klass); + + object_class->finalize = gsm_xsmp_client_finalize; + object_class->constructor = gsm_xsmp_client_constructor; + object_class->get_property = gsm_xsmp_client_get_property; + object_class->set_property = gsm_xsmp_client_set_property; + + client_class->impl_save = xsmp_save; + client_class->impl_stop = xsmp_stop; + client_class->impl_query_end_session = xsmp_query_end_session; + client_class->impl_end_session = xsmp_end_session; + client_class->impl_cancel_end_session = xsmp_cancel_end_session; + client_class->impl_get_app_name = xsmp_get_app_name; + client_class->impl_get_restart_style_hint = xsmp_get_restart_style_hint; + client_class->impl_get_unix_process_id = xsmp_get_unix_process_id; + + signals[REGISTER_REQUEST] = + g_signal_new ("register-request", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsmXSMPClientClass, register_request), + _boolean_handled_accumulator, + NULL, + NULL, + G_TYPE_BOOLEAN, + 1, G_TYPE_POINTER); + signals[REGISTER_CONFIRMED] = + g_signal_new ("register-confirmed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsmXSMPClientClass, register_confirmed), + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, G_TYPE_POINTER); + signals[LOGOUT_REQUEST] = + g_signal_new ("logout-request", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsmXSMPClientClass, logout_request), + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, G_TYPE_BOOLEAN); + + g_object_class_install_property (object_class, + PROP_ICE_CONNECTION, + g_param_spec_pointer ("ice-connection", + "ice-connection", + "ice-connection", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_type_class_add_private (klass, sizeof (GsmXSMPClientPrivate)); +} + +GsmClient * +gsm_xsmp_client_new (IceConn ice_conn) +{ + GsmXSMPClient *xsmp; + + xsmp = g_object_new (GSM_TYPE_XSMP_CLIENT, + "ice-connection", ice_conn, + NULL); + + return GSM_CLIENT (xsmp); +} + +static Status +register_client_callback (SmsConn conn, + SmPointer manager_data, + char *previous_id) +{ + GsmXSMPClient *client = manager_data; + gboolean handled; + char *id; + + g_debug ("GsmXSMPClient: Client '%s' received RegisterClient(%s)", + client->priv->description, + previous_id ? previous_id : "NULL"); + + + /* There are three cases: + * 1. id is NULL - we'll use a new one + * 2. id is known - we'll use known one + * 3. id is unknown - this is an error + */ + id = g_strdup (previous_id); + + handled = FALSE; + g_signal_emit (client, signals[REGISTER_REQUEST], 0, &id, &handled); + if (! handled) { + g_debug ("GsmXSMPClient: RegisterClient not handled!"); + g_free (id); + free (previous_id); + g_assert_not_reached (); + return FALSE; + } + + if (IS_STRING_EMPTY (id)) { + g_debug ("GsmXSMPClient: rejected: invalid previous_id"); + free (previous_id); + return FALSE; + } + + g_object_set (client, "startup-id", id, NULL); + + set_description (client); + + g_debug ("GsmXSMPClient: Sending RegisterClientReply to '%s'", client->priv->description); + + SmsRegisterClientReply (conn, id); + + if (IS_STRING_EMPTY (previous_id)) { + /* Send the initial SaveYourself. */ + g_debug ("GsmXSMPClient: Sending initial SaveYourself"); + SmsSaveYourself (conn, SmSaveLocal, False, SmInteractStyleNone, False); + client->priv->current_save_yourself = SmSaveLocal; + } + + gsm_client_set_status (GSM_CLIENT (client), GSM_CLIENT_REGISTERED); + + g_signal_emit (client, signals[REGISTER_CONFIRMED], 0, id); + + g_free (id); + free (previous_id); + + return TRUE; +} + + +static void +save_yourself_request_callback (SmsConn conn, + SmPointer manager_data, + int save_type, + Bool shutdown, + int interact_style, + Bool fast, + Bool global) +{ + GsmXSMPClient *client = manager_data; + + g_debug ("GsmXSMPClient: Client '%s' received SaveYourselfRequest(%s, %s, %s, %s, %s)", + client->priv->description, + save_type == SmSaveLocal ? "SmSaveLocal" : + save_type == SmSaveGlobal ? "SmSaveGlobal" : "SmSaveBoth", + shutdown ? "Shutdown" : "!Shutdown", + interact_style == SmInteractStyleAny ? "SmInteractStyleAny" : + interact_style == SmInteractStyleErrors ? "SmInteractStyleErrors" : + "SmInteractStyleNone", fast ? "Fast" : "!Fast", + global ? "Global" : "!Global"); + + /* Examining the g_debug above, you can see that there are a total + * of 72 different combinations of options that this could have been + * called with. However, most of them are stupid. + * + * If @shutdown and @global are both TRUE, that means the caller is + * requesting that a logout message be sent to all clients, so we do + * that. We use @fast to decide whether or not to show a + * confirmation dialog. (This isn't really what @fast is for, but + * the old gnome-session and ksmserver both interpret it that way, + * so we do too.) We ignore @save_type because we pick the correct + * save_type ourselves later based on user prefs, dialog choices, + * etc, and we ignore @interact_style, because clients have not used + * it correctly consistently enough to make it worth honoring. + * + * If @shutdown is TRUE and @global is FALSE, the caller is + * confused, so we ignore the request. + * + * If @shutdown is FALSE and @save_type is SmSaveGlobal or + * SmSaveBoth, then the client wants us to ask some or all open + * applications to save open files to disk, but NOT quit. This is + * silly and so we ignore the request. + * + * If @shutdown is FALSE and @save_type is SmSaveLocal, then the + * client wants us to ask some or all open applications to update + * their current saved state, but not log out. At the moment, the + * code only supports this for the !global case (ie, a client + * requesting that it be allowed to update *its own* saved state, + * but not having everyone else update their saved state). + */ + + if (shutdown && global) { + g_debug ("GsmXSMPClient: initiating shutdown"); + g_signal_emit (client, signals[LOGOUT_REQUEST], 0, !fast); + } else if (!shutdown && !global) { + g_debug ("GsmXSMPClient: initiating checkpoint"); + do_save_yourself (client, SmSaveLocal, TRUE); + } else { + g_debug ("GsmXSMPClient: ignoring"); + } +} + +static void +save_yourself_phase2_request_callback (SmsConn conn, + SmPointer manager_data) +{ + GsmXSMPClient *client = manager_data; + + g_debug ("GsmXSMPClient: Client '%s' received SaveYourselfPhase2Request", + client->priv->description); + + client->priv->current_save_yourself = -1; + + /* this is a valid response to SaveYourself and therefore + may be a response to a QES or ES */ + gsm_client_end_session_response (GSM_CLIENT (client), + TRUE, TRUE, FALSE, + NULL); +} + +static void +interact_request_callback (SmsConn conn, + SmPointer manager_data, + int dialog_type) +{ + GsmXSMPClient *client = manager_data; +#if 0 + gboolean res; + GError *error; +#endif + + g_debug ("GsmXSMPClient: Client '%s' received InteractRequest(%s)", + client->priv->description, + dialog_type == SmDialogNormal ? "Dialog" : "Errors"); + + gsm_client_end_session_response (GSM_CLIENT (client), + FALSE, FALSE, FALSE, + _("This program is blocking logout.")); + +#if 0 + /* Can't just call back with Interact because session client + grabs the keyboard! So, we try to get it to release + grabs by telling it we've cancelled the shutdown. + This grabbing is clearly bullshit and is not supported by + the client spec or protocol spec. + */ + res = xsmp_cancel_end_session (GSM_CLIENT (client), &error); + if (! res) { + g_warning ("Unable to cancel end session: %s", error->message); + g_error_free (error); + } +#endif + xsmp_interact (GSM_CLIENT (client)); +} + +static void +interact_done_callback (SmsConn conn, + SmPointer manager_data, + Bool cancel_shutdown) +{ + GsmXSMPClient *client = manager_data; + + g_debug ("GsmXSMPClient: Client '%s' received InteractDone(cancel_shutdown = %s)", + client->priv->description, + cancel_shutdown ? "True" : "False"); + + gsm_client_end_session_response (GSM_CLIENT (client), + TRUE, FALSE, cancel_shutdown, + NULL); +} + +static void +save_yourself_done_callback (SmsConn conn, + SmPointer manager_data, + Bool success) +{ + GsmXSMPClient *client = manager_data; + + g_debug ("GsmXSMPClient: Client '%s' received SaveYourselfDone(success = %s)", + client->priv->description, + success ? "True" : "False"); + + if (client->priv->current_save_yourself != -1) { + SmsSaveComplete (client->priv->conn); + client->priv->current_save_yourself = -1; + } + + /* If success is false then the application couldn't save data. Nothing + * the session manager can do about, though. FIXME: we could display a + * dialog about this, I guess. */ + gsm_client_end_session_response (GSM_CLIENT (client), + TRUE, FALSE, FALSE, + NULL); + + if (client->priv->next_save_yourself) { + int save_type = client->priv->next_save_yourself; + gboolean allow_interact = client->priv->next_save_yourself_allow_interact; + + client->priv->next_save_yourself = -1; + client->priv->next_save_yourself_allow_interact = -1; + do_save_yourself (client, save_type, allow_interact); + } +} + +static void +close_connection_callback (SmsConn conn, + SmPointer manager_data, + int count, + char **reason_msgs) +{ + GsmXSMPClient *client = manager_data; + int i; + + g_debug ("GsmXSMPClient: Client '%s' received CloseConnection", client->priv->description); + for (i = 0; i < count; i++) { + g_debug ("GsmXSMPClient: close reason: '%s'", reason_msgs[i]); + } + SmFreeReasons (count, reason_msgs); + + gsm_client_set_status (GSM_CLIENT (client), GSM_CLIENT_FINISHED); + gsm_client_disconnected (GSM_CLIENT (client)); +} + +void +gsm_xsmp_client_connect (GsmXSMPClient *client, + SmsConn conn, + unsigned long *mask_ret, + SmsCallbacks *callbacks_ret) +{ + client->priv->conn = conn; + + g_debug ("GsmXSMPClient: Initializing client %s", client->priv->description); + + *mask_ret = 0; + + *mask_ret |= SmsRegisterClientProcMask; + callbacks_ret->register_client.callback = register_client_callback; + callbacks_ret->register_client.manager_data = client; + + *mask_ret |= SmsInteractRequestProcMask; + callbacks_ret->interact_request.callback = interact_request_callback; + callbacks_ret->interact_request.manager_data = client; + + *mask_ret |= SmsInteractDoneProcMask; + callbacks_ret->interact_done.callback = interact_done_callback; + callbacks_ret->interact_done.manager_data = client; + + *mask_ret |= SmsSaveYourselfRequestProcMask; + callbacks_ret->save_yourself_request.callback = save_yourself_request_callback; + callbacks_ret->save_yourself_request.manager_data = client; + + *mask_ret |= SmsSaveYourselfP2RequestProcMask; + callbacks_ret->save_yourself_phase2_request.callback = save_yourself_phase2_request_callback; + callbacks_ret->save_yourself_phase2_request.manager_data = client; + + *mask_ret |= SmsSaveYourselfDoneProcMask; + callbacks_ret->save_yourself_done.callback = save_yourself_done_callback; + callbacks_ret->save_yourself_done.manager_data = client; + + *mask_ret |= SmsCloseConnectionProcMask; + callbacks_ret->close_connection.callback = close_connection_callback; + callbacks_ret->close_connection.manager_data = client; + + *mask_ret |= SmsSetPropertiesProcMask; + callbacks_ret->set_properties.callback = set_properties_callback; + callbacks_ret->set_properties.manager_data = client; + + *mask_ret |= SmsDeletePropertiesProcMask; + callbacks_ret->delete_properties.callback = delete_properties_callback; + callbacks_ret->delete_properties.manager_data = client; + + *mask_ret |= SmsGetPropertiesProcMask; + callbacks_ret->get_properties.callback = get_properties_callback; + callbacks_ret->get_properties.manager_data = client; +} + +void +gsm_xsmp_client_save_state (GsmXSMPClient *client) +{ + g_return_if_fail (GSM_IS_XSMP_CLIENT (client)); +} diff --git a/gnome-session/gsm-xsmp-client.h b/gnome-session/gsm-xsmp-client.h new file mode 100644 index 0000000..1bb2797 --- /dev/null +++ b/gnome-session/gsm-xsmp-client.h @@ -0,0 +1,89 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 Novell, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef __GSM_XSMP_CLIENT_H__ +#define __GSM_XSMP_CLIENT_H__ + +#include "gsm-client.h" + +#include + +G_BEGIN_DECLS + +#define GSM_TYPE_XSMP_CLIENT (gsm_xsmp_client_get_type ()) +#define GSM_XSMP_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_XSMP_CLIENT, GsmXSMPClient)) +#define GSM_XSMP_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_XSMP_CLIENT, GsmXSMPClientClass)) +#define GSM_IS_XSMP_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_XSMP_CLIENT)) +#define GSM_IS_XSMP_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_XSMP_CLIENT)) +#define GSM_XSMP_CLIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSM_TYPE_XSMP_CLIENT, GsmXSMPClientClass)) + +typedef struct _GsmXSMPClient GsmXSMPClient; +typedef struct _GsmXSMPClientClass GsmXSMPClientClass; + +typedef struct GsmXSMPClientPrivate GsmXSMPClientPrivate; + +struct _GsmXSMPClient +{ + GsmClient parent; + GsmXSMPClientPrivate *priv; +}; + +struct _GsmXSMPClientClass +{ + GsmClientClass parent_class; + + /* signals */ + gboolean (*register_request) (GsmXSMPClient *client, + char **client_id); + void (*register_confirmed) (GsmXSMPClient *client, + const char *client_id); + gboolean (*logout_request) (GsmXSMPClient *client, + gboolean prompt); + + + void (*saved_state) (GsmXSMPClient *client); + + void (*request_phase2) (GsmXSMPClient *client); + + void (*request_interaction) (GsmXSMPClient *client); + void (*interaction_done) (GsmXSMPClient *client, + gboolean cancel_shutdown); + + void (*save_yourself_done) (GsmXSMPClient *client); + +}; + +GType gsm_xsmp_client_get_type (void) G_GNUC_CONST; + +GsmClient *gsm_xsmp_client_new (IceConn ice_conn); + +void gsm_xsmp_client_connect (GsmXSMPClient *client, + SmsConn conn, + unsigned long *mask_ret, + SmsCallbacks *callbacks_ret); + +void gsm_xsmp_client_save_state (GsmXSMPClient *client); +void gsm_xsmp_client_save_yourself (GsmXSMPClient *client, + gboolean save_state); +void gsm_xsmp_client_save_yourself_phase2 (GsmXSMPClient *client); +void gsm_xsmp_client_interact (GsmXSMPClient *client); +void gsm_xsmp_client_shutdown_cancelled (GsmXSMPClient *client); + +G_END_DECLS + +#endif /* __GSM_XSMP_CLIENT_H__ */ diff --git a/gnome-session/gsm-xsmp-server.c b/gnome-session/gsm-xsmp-server.c new file mode 100644 index 0000000..e05c6d7 --- /dev/null +++ b/gnome-session/gsm-xsmp-server.c @@ -0,0 +1,746 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + * Copyright (C) 2008 William Jon McCann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +/* Get the proto for _IceTransNoListen */ +#define ICE_t +#define TRANS_SERVER +#include +#undef ICE_t +#undef TRANS_SERVER + +#include "gsm-xsmp-server.h" +#include "gsm-xsmp-client.h" +#include "gsm-util.h" + +/* ICEauthority stuff */ +/* Various magic numbers stolen from iceauth.c */ +#define GSM_ICE_AUTH_RETRIES 10 +#define GSM_ICE_AUTH_INTERVAL 2 /* 2 seconds */ +#define GSM_ICE_AUTH_LOCK_TIMEOUT 600 /* 10 minutes */ + +#define GSM_ICE_MAGIC_COOKIE_AUTH_NAME "MIT-MAGIC-COOKIE-1" +#define GSM_ICE_MAGIC_COOKIE_LEN 16 + +#define GSM_XSMP_SERVER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSM_TYPE_XSMP_SERVER, GsmXsmpServerPrivate)) + +struct _GsmXsmpServer +{ + GObject parent_instance; + + GsmStore *client_store; + + IceListenObj *xsmp_sockets; + int num_xsmp_sockets; + int num_local_xsmp_sockets; + gboolean stopping; +}; + +enum { + PROP_0, + PROP_CLIENT_STORE +}; + +static void gsm_xsmp_server_class_init (GsmXsmpServerClass *klass); +static void gsm_xsmp_server_init (GsmXsmpServer *xsmp_server); +static void gsm_xsmp_server_finalize (GObject *object); + +static gpointer xsmp_server_object = NULL; + +G_DEFINE_TYPE (GsmXsmpServer, gsm_xsmp_server, G_TYPE_OBJECT) + +typedef struct { + GsmXsmpServer *server; + IceListenObj listener; +} GsmIceConnectionData; + +typedef struct { + guint watch_id; + guint protocol_timeout; +} GsmIceConnectionWatch; + +static void +disconnect_ice_connection (IceConn ice_conn) +{ + IceSetShutdownNegotiation (ice_conn, FALSE); + IceCloseConnection (ice_conn); +} + +static void +free_ice_connection_watch (GsmIceConnectionWatch *data) +{ + if (data->watch_id) { + g_source_remove (data->watch_id); + data->watch_id = 0; + } + + if (data->protocol_timeout) { + g_source_remove (data->protocol_timeout); + data->protocol_timeout = 0; + } + + g_free (data); +} + +static gboolean +ice_protocol_timeout (IceConn ice_conn) +{ + GsmIceConnectionWatch *data; + + g_debug ("GsmXsmpServer: ice_protocol_timeout for IceConn %p with status %d", + ice_conn, IceConnectionStatus (ice_conn)); + + data = ice_conn->context; + + free_ice_connection_watch (data); + disconnect_ice_connection (ice_conn); + + return FALSE; +} + +static gboolean +auth_iochannel_watch (GIOChannel *source, + GIOCondition condition, + IceConn ice_conn) +{ + + GsmIceConnectionWatch *data; + gboolean keep_going; + + data = ice_conn->context; + + switch (IceProcessMessages (ice_conn, NULL, NULL)) { + case IceProcessMessagesSuccess: + keep_going = TRUE; + break; + case IceProcessMessagesIOError: + g_debug ("GsmXsmpServer: IceProcessMessages returned IceProcessMessagesIOError"); + free_ice_connection_watch (data); + disconnect_ice_connection (ice_conn); + keep_going = FALSE; + break; + case IceProcessMessagesConnectionClosed: + g_debug ("GsmXsmpServer: IceProcessMessages returned IceProcessMessagesConnectionClosed"); + free_ice_connection_watch (data); + keep_going = FALSE; + break; + default: + g_assert_not_reached (); + } + + return keep_going; +} + +/* IceAcceptConnection returns a new ICE connection that is in a "pending" state, + * this is because authentification may be necessary. + * So we've to authenticate it, before accept_xsmp_connection() is called. + * Then each GsmXSMPClient will have its own IceConn watcher + */ +static void +auth_ice_connection (IceConn ice_conn) +{ + GIOChannel *channel; + GsmIceConnectionWatch *data; + int fd; + + g_debug ("GsmXsmpServer: auth_ice_connection()"); + + fd = IceConnectionNumber (ice_conn); + fcntl (fd, F_SETFD, fcntl (fd, F_GETFD, 0) | FD_CLOEXEC); + channel = g_io_channel_unix_new (fd); + + data = g_new0 (GsmIceConnectionWatch, 1); + ice_conn->context = data; + + data->protocol_timeout = g_timeout_add_seconds (5, + (GSourceFunc)ice_protocol_timeout, + ice_conn); + data->watch_id = g_io_add_watch (channel, + G_IO_IN | G_IO_ERR, + (GIOFunc)auth_iochannel_watch, + ice_conn); + g_io_channel_unref (channel); +} + +/* This is called (by glib via xsmp->ice_connection_watch) when a + * connection is first received on the ICE listening socket. + */ +static gboolean +accept_ice_connection (GIOChannel *source, + GIOCondition condition, + GsmIceConnectionData *data) +{ + IceConn ice_conn; + IceAcceptStatus status; + + g_debug ("GsmXsmpServer: accept_ice_connection()"); + + ice_conn = IceAcceptConnection (data->listener, &status); + if (status != IceAcceptSuccess) { + g_debug ("GsmXsmpServer: IceAcceptConnection returned %d", status); + return TRUE; + } + + auth_ice_connection (ice_conn); + + return TRUE; +} + +void +gsm_xsmp_server_start (GsmXsmpServer *server) +{ + GIOChannel *channel; + int i; + + for (i = 0; i < server->num_local_xsmp_sockets; i++) { + GsmIceConnectionData *data; + + data = g_new0 (GsmIceConnectionData, 1); + data->server = server; + data->listener = server->xsmp_sockets[i]; + + channel = g_io_channel_unix_new (IceGetListenConnectionNumber (server->xsmp_sockets[i])); + g_io_add_watch_full (channel, + G_PRIORITY_DEFAULT, + G_IO_IN | G_IO_HUP | G_IO_ERR, + (GIOFunc)accept_ice_connection, + data, + (GDestroyNotify)g_free); + g_io_channel_unref (channel); + } +} + +void +gsm_xsmp_server_stop_accepting_new_clients (GsmXsmpServer *server) +{ + g_return_if_fail (GSM_IS_XSMP_SERVER (server)); + g_debug ("gsm_xsmp_server_stop_accepting_new_clients"); + server->stopping = TRUE; +} + +void +gsm_xsmp_server_start_accepting_new_clients (GsmXsmpServer *server) +{ + g_return_if_fail (GSM_IS_XSMP_SERVER (server)); + g_debug ("gsm_xsmp_server_start"); + server->stopping = FALSE; +} + +static void +gsm_xsmp_server_set_client_store (GsmXsmpServer *xsmp_server, + GsmStore *store) +{ + g_return_if_fail (GSM_IS_XSMP_SERVER (xsmp_server)); + + if (store != NULL) { + g_object_ref (store); + } + + if (xsmp_server->client_store != NULL) { + g_object_unref (xsmp_server->client_store); + } + + xsmp_server->client_store = store; +} + +static void +gsm_xsmp_server_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GsmXsmpServer *self; + + self = GSM_XSMP_SERVER (object); + + switch (prop_id) { + case PROP_CLIENT_STORE: + gsm_xsmp_server_set_client_store (self, g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gsm_xsmp_server_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GsmXsmpServer *self; + + self = GSM_XSMP_SERVER (object); + + switch (prop_id) { + case PROP_CLIENT_STORE: + g_value_set_object (value, self->client_store); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/* This is called (by libSM) when XSMP is initiated on an ICE + * connection that was already accepted by accept_ice_connection. + */ +static Status +accept_xsmp_connection (SmsConn sms_conn, + GsmXsmpServer *server, + unsigned long *mask_ret, + SmsCallbacks *callbacks_ret, + char **failure_reason_ret) +{ + IceConn ice_conn; + GsmClient *client; + GsmIceConnectionWatch *data; + + if (server->stopping) { + g_debug ("GsmXsmpServer: In shutdown, rejecting new client"); + + *failure_reason_ret = strdup (_("Refusing new client connection because the session is currently being shut down\n")); + return FALSE; + } + + ice_conn = SmsGetIceConnection (sms_conn); + data = ice_conn->context; + + /* Each GsmXSMPClient has its own IceConn watcher */ + free_ice_connection_watch (data); + + client = gsm_xsmp_client_new (ice_conn); + + gsm_store_add (server->client_store, gsm_client_peek_id (client), G_OBJECT (client)); + /* the store will own the ref */ + g_object_unref (client); + + gsm_xsmp_client_connect (GSM_XSMP_CLIENT (client), sms_conn, mask_ret, callbacks_ret); + + return TRUE; +} + +static void +ice_error_handler (IceConn conn, + Bool swap, + int offending_minor_opcode, + unsigned long offending_sequence, + int error_class, + int severity, + IcePointer values) +{ + g_debug ("GsmXsmpServer: ice_error_handler (%p, %s, %d, %lx, %d, %d)", + conn, swap ? "TRUE" : "FALSE", offending_minor_opcode, + offending_sequence, error_class, severity); + + if (severity == IceCanContinue) { + return; + } + + /* FIXME: the ICElib docs are completely vague about what we're + * supposed to do in this case. Need to verify that calling + * IceCloseConnection() here is guaranteed to cause neither + * free-memory-reads nor leaks. + */ + IceCloseConnection (conn); +} + +static void +ice_io_error_handler (IceConn conn) +{ + g_debug ("GsmXsmpServer: ice_io_error_handler (%p)", conn); + + /* We don't need to do anything here; the next call to + * IceProcessMessages() for this connection will receive + * IceProcessMessagesIOError and we can handle the error there. + */ +} + +static void +sms_error_handler (SmsConn conn, + Bool swap, + int offending_minor_opcode, + unsigned long offending_sequence_num, + int error_class, + int severity, + IcePointer values) +{ + g_debug ("GsmXsmpServer: sms_error_handler (%p, %s, %d, %lx, %d, %d)", + conn, swap ? "TRUE" : "FALSE", offending_minor_opcode, + offending_sequence_num, error_class, severity); + + /* We don't need to do anything here; if the connection needs to be + * closed, libSM will do that itself. + */ +} + +static IceAuthFileEntry * +auth_entry_new (const char *protocol, + const char *network_id) +{ + IceAuthFileEntry *file_entry; + IceAuthDataEntry data_entry; + + file_entry = malloc (sizeof (IceAuthFileEntry)); + + file_entry->protocol_name = strdup (protocol); + file_entry->protocol_data = NULL; + file_entry->protocol_data_length = 0; + file_entry->network_id = strdup (network_id); + file_entry->auth_name = strdup (GSM_ICE_MAGIC_COOKIE_AUTH_NAME); + file_entry->auth_data = IceGenerateMagicCookie (GSM_ICE_MAGIC_COOKIE_LEN); + file_entry->auth_data_length = GSM_ICE_MAGIC_COOKIE_LEN; + + /* Also create an in-memory copy, which is what the server will + * actually use for checking client auth. + */ + data_entry.protocol_name = file_entry->protocol_name; + data_entry.network_id = file_entry->network_id; + data_entry.auth_name = file_entry->auth_name; + data_entry.auth_data = file_entry->auth_data; + data_entry.auth_data_length = file_entry->auth_data_length; + IceSetPaAuthData (1, &data_entry); + + return file_entry; +} + +static gboolean +update_iceauthority (GsmXsmpServer *server, + gboolean adding) +{ + char *filename; + char **our_network_ids; + FILE *fp; + IceAuthFileEntry *auth_entry; + GSList *entries; + GSList *e; + int i; + int ret; + gboolean ok = FALSE; + + filename = IceAuthFileName (); + do { + ret = IceLockAuthFile (filename, + GSM_ICE_AUTH_RETRIES, + GSM_ICE_AUTH_INTERVAL, + GSM_ICE_AUTH_LOCK_TIMEOUT); + + } while (ret != IceAuthLockSuccess && errno == EINTR); + + if (ret != IceAuthLockSuccess) { + g_warning ("IceLockAuthFile failed: %m"); + return FALSE; + } + + our_network_ids = g_malloc (server->num_local_xsmp_sockets * sizeof (char *)); + for (i = 0; i < server->num_local_xsmp_sockets; i++) { + our_network_ids[i] = IceGetListenConnectionString (server->xsmp_sockets[i]); + } + + entries = NULL; + + fp = fopen (filename, "r+"); + if (fp != NULL) { + while ((auth_entry = IceReadAuthFileEntry (fp)) != NULL) { + /* Skip/delete entries with no network ID (invalid), or with + * our network ID; if we're starting up, an entry with our + * ID must be a stale entry left behind by an old process, + * and if we're shutting down, it won't be valid in the + * future, so either way we want to remove it from the list. + */ + if (!auth_entry->network_id) { + IceFreeAuthFileEntry (auth_entry); + continue; + } + + for (i = 0; i < server->num_local_xsmp_sockets; i++) { + if (!strcmp (auth_entry->network_id, our_network_ids[i])) { + IceFreeAuthFileEntry (auth_entry); + break; + } + } + if (i != server->num_local_xsmp_sockets) { + continue; + } + + entries = g_slist_prepend (entries, auth_entry); + } + + rewind (fp); + } else { + int fd; + + if (errno != ENOENT) { + g_warning ("Unable to read ICE authority file %s: %m", filename); + goto cleanup; + } + + fd = open (filename, O_CREAT | O_WRONLY, 0600); + fp = fdopen (fd, "w"); + if (!fp) { + g_warning ("Unable to write to ICE authority file: %s", filename); + if (fd != -1) { + close (fd); + } + goto cleanup; + } + } + + if (adding) { + for (i = 0; i < server->num_local_xsmp_sockets; i++) { + entries = g_slist_append (entries, + auth_entry_new ("ICE", our_network_ids[i])); + entries = g_slist_prepend (entries, + auth_entry_new ("XSMP", our_network_ids[i])); + } + } + + for (e = entries; e; e = e->next) { + IceAuthFileEntry *auth_entry = e->data; + IceWriteAuthFileEntry (fp, auth_entry); + IceFreeAuthFileEntry (auth_entry); + } + g_slist_free (entries); + + fclose (fp); + ok = TRUE; + + cleanup: + IceUnlockAuthFile (filename); + for (i = 0; i < server->num_local_xsmp_sockets; i++) { + free (our_network_ids[i]); + } + g_free (our_network_ids); + + return ok; +} + + +static void +setup_listener (GsmXsmpServer *server) +{ + char error[256]; + mode_t saved_umask; + char *network_id_list; + int i; + int res; + + /* Set up sane error handlers */ + IceSetErrorHandler (ice_error_handler); + IceSetIOErrorHandler (ice_io_error_handler); + SmsSetErrorHandler (sms_error_handler); + + /* Initialize libSM; we pass NULL for hostBasedAuthProc to disable + * host-based authentication. + */ + res = SmsInitialize (PACKAGE, + VERSION, + (SmsNewClientProc)accept_xsmp_connection, + server, + NULL, + sizeof (error), + error); + if (! res) { + gsm_util_init_error (TRUE, "Could not initialize libSM: %s", error); + } + + /* By default, IceListenForConnections will open one socket for each + * transport type known to X. We don't want connections from remote + * hosts, so for security reasons it would be best if ICE didn't + * even open any non-local sockets. So we use an internal ICElib + * method to disable them here. Unfortunately, there is no way to + * ask X what transport types it knows about, so we're forced to + * guess. + */ + _IceTransNoListen ("tcp"); + + /* Create the XSMP socket. Older versions of IceListenForConnections + * have a bug which causes the umask to be set to 0 on certain types + * of failures. Probably not an issue on any modern systems, but + * we'll play it safe. + */ + saved_umask = umask (0); + umask (saved_umask); + res = IceListenForConnections (&server->num_xsmp_sockets, + &server->xsmp_sockets, + sizeof (error), + error); + if (! res) { + gsm_util_init_error (TRUE, _("Could not create ICE listening socket: %s"), error); + } + + umask (saved_umask); + + /* Find the local sockets in the returned socket list and move them + * to the start of the list. + */ + for (i = server->num_local_xsmp_sockets = 0; i < server->num_xsmp_sockets; i++) { + char *id = IceGetListenConnectionString (server->xsmp_sockets[i]); + + if (!strncmp (id, "local/", sizeof ("local/") - 1) || + !strncmp (id, "unix/", sizeof ("unix/") - 1)) { + if (i > server->num_local_xsmp_sockets) { + IceListenObj tmp; + tmp = server->xsmp_sockets[i]; + server->xsmp_sockets[i] = server->xsmp_sockets[server->num_local_xsmp_sockets]; + server->xsmp_sockets[server->num_local_xsmp_sockets] = tmp; + } + server->num_local_xsmp_sockets++; + } + free (id); + } + + if (server->num_local_xsmp_sockets == 0) { + gsm_util_init_error (TRUE, "IceListenForConnections did not return a local listener!"); + } + + if (server->num_local_xsmp_sockets != server->num_xsmp_sockets) { + /* Xtrans was apparently compiled with support for some + * non-local transport besides TCP (which we disabled above); we + * won't create IO watches on those extra sockets, so + * connections to them will never be noticed, but they're still + * there, which is inelegant. + * + * If the g_warning below is triggering for you and you want to + * stop it, the fix is to add additional _IceTransNoListen() + * calls above. + */ + network_id_list = IceComposeNetworkIdList (server->num_xsmp_sockets - server->num_local_xsmp_sockets, + server->xsmp_sockets + server->num_local_xsmp_sockets); + g_warning ("IceListenForConnections returned %d non-local listeners: %s", + server->num_xsmp_sockets - server->num_local_xsmp_sockets, + network_id_list); + free (network_id_list); + } + + /* Update .ICEauthority with new auth entries for our socket */ + if (!update_iceauthority (server, TRUE)) { + /* FIXME: is this really fatal? Hm... */ + gsm_util_init_error (TRUE, + "Could not update ICEauthority file %s", + IceAuthFileName ()); + } + + network_id_list = IceComposeNetworkIdList (server->num_local_xsmp_sockets, + server->xsmp_sockets); + + gsm_util_setenv ("SESSION_MANAGER", network_id_list); + g_debug ("GsmXsmpServer: SESSION_MANAGER=%s\n", network_id_list); + free (network_id_list); +} + +static GObject * +gsm_xsmp_server_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GsmXsmpServer *xsmp_server; + + xsmp_server = GSM_XSMP_SERVER (G_OBJECT_CLASS (gsm_xsmp_server_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + setup_listener (xsmp_server); + + return G_OBJECT (xsmp_server); +} + +static void +gsm_xsmp_server_class_init (GsmXsmpServerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = gsm_xsmp_server_get_property; + object_class->set_property = gsm_xsmp_server_set_property; + object_class->constructor = gsm_xsmp_server_constructor; + object_class->finalize = gsm_xsmp_server_finalize; + + g_object_class_install_property (object_class, + PROP_CLIENT_STORE, + g_param_spec_object ("client-store", + NULL, + NULL, + GSM_TYPE_STORE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); +} + +static void +gsm_xsmp_server_init (GsmXsmpServer *xsmp_server) +{ +} + +static void +gsm_xsmp_server_finalize (GObject *object) +{ + GsmXsmpServer *xsmp_server; + + g_return_if_fail (object != NULL); + g_return_if_fail (GSM_IS_XSMP_SERVER (object)); + + xsmp_server = GSM_XSMP_SERVER (object); + + g_return_if_fail (xsmp_server != NULL); + + IceFreeListenObjs (xsmp_server->num_xsmp_sockets, + xsmp_server->xsmp_sockets); + + if (xsmp_server->client_store != NULL) { + g_object_unref (xsmp_server->client_store); + } + + G_OBJECT_CLASS (gsm_xsmp_server_parent_class)->finalize (object); +} + +GsmXsmpServer * +gsm_xsmp_server_new (GsmStore *client_store) +{ + if (xsmp_server_object != NULL) { + g_object_ref (xsmp_server_object); + } else { + xsmp_server_object = g_object_new (GSM_TYPE_XSMP_SERVER, + "client-store", client_store, + NULL); + + g_object_add_weak_pointer (xsmp_server_object, + (gpointer *) &xsmp_server_object); + } + + return GSM_XSMP_SERVER (xsmp_server_object); +} diff --git a/gnome-session/gsm-xsmp-server.h b/gnome-session/gsm-xsmp-server.h new file mode 100644 index 0000000..95d704d --- /dev/null +++ b/gnome-session/gsm-xsmp-server.h @@ -0,0 +1,40 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 William Jon McCann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + */ + + +#ifndef __GSM_XSMP_SERVER_H +#define __GSM_XSMP_SERVER_H + +#include + +#include "gsm-store.h" + +G_BEGIN_DECLS + +#define GSM_TYPE_XSMP_SERVER (gsm_xsmp_server_get_type ()) +G_DECLARE_FINAL_TYPE (GsmXsmpServer, gsm_xsmp_server, GSM, XSMP_SERVER, GObject) + +GsmXsmpServer * gsm_xsmp_server_new (GsmStore *client_store); +void gsm_xsmp_server_start (GsmXsmpServer *server); +void gsm_xsmp_server_stop_accepting_new_clients (GsmXsmpServer *server); +void gsm_xsmp_server_start_accepting_new_clients (GsmXsmpServer *server); + +G_END_DECLS + +#endif /* __GSM_XSMP_SERVER_H */ diff --git a/gnome-session/main.c b/gnome-session/main.c new file mode 100644 index 0000000..6f03324 --- /dev/null +++ b/gnome-session/main.c @@ -0,0 +1,636 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2006 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "gdm-log.h" + +#include "gsm-util.h" +#include "gsm-manager.h" +#include "gsm-session-fill.h" +#include "gsm-store.h" +#include "gsm-system.h" +#include "gsm-fail-whale.h" + +#ifdef ENABLE_SYSTEMD_JOURNAL +#include +#endif + +#define GSM_DBUS_NAME "org.gnome.SessionManager" + +static gboolean systemd_service = FALSE; +static gboolean use_systemd = USE_SYSTEMD_SESSION; +static gboolean failsafe = FALSE; +static gboolean show_version = FALSE; +static gboolean debug = FALSE; +static gboolean please_fail = FALSE; +static gboolean disable_acceleration_check = FALSE; +static const char *session_name = NULL; +static GsmManager *manager = NULL; +static char *gl_renderer = NULL; + +static GMainLoop *loop; + +void +gsm_quit (void) +{ + g_main_loop_quit (loop); +} + +static void +gsm_main (void) +{ + if (loop == NULL) + loop = g_main_loop_new (NULL, TRUE); + + g_main_loop_run (loop); +} + +static void +on_name_lost (GDBusConnection *connection, + const char *name, + gpointer data) +{ + if (connection == NULL) { + g_warning ("Lost name on bus: %s", name); + gsm_fail_whale_dialog_we_failed (TRUE, TRUE, NULL); + } else { + g_debug ("Calling name lost callback function"); + + /* + * When the signal handler gets a shutdown signal, it calls + * this function to inform GsmManager to not restart + * applications in the off chance a handler is already queued + * to dispatch following the below call to gtk_main_quit. + */ + gsm_manager_set_phase (manager, GSM_MANAGER_PHASE_EXIT); + + gsm_quit (); + } +} + +static gboolean +term_or_int_signal_cb (gpointer data) +{ + g_autoptr(GError) error = NULL; + GsmManager *manager = (GsmManager *)data; + + /* let the fatal signals interrupt us */ + g_debug ("Caught SIGINT/SIGTERM, shutting down normally."); + + if (!gsm_manager_logout (manager, GSM_MANAGER_LOGOUT_MODE_FORCE, &error)) { + if (g_error_matches (error, GSM_MANAGER_ERROR, GSM_MANAGER_ERROR_NOT_IN_RUNNING)) { + gsm_quit (); + return FALSE; + } + + g_critical ("Failed to log out: %s", error->message); + } + + return FALSE; +} + +static gboolean +sigusr2_cb (gpointer data) +{ + g_debug ("-------- MARK --------"); + return TRUE; +} + +static gboolean +sigusr1_cb (gpointer data) +{ + gdm_log_toggle_debug (); + return TRUE; +} + +static void +on_name_acquired (GDBusConnection *connection, + const char *name, + gpointer data) +{ + gsm_manager_start (manager); +} + +static void +create_manager (void) +{ + GsmStore *client_store; + + client_store = gsm_store_new (); + manager = gsm_manager_new (client_store, failsafe, systemd_service); + g_object_unref (client_store); + + g_unix_signal_add (SIGTERM, term_or_int_signal_cb, manager); + g_unix_signal_add (SIGINT, term_or_int_signal_cb, manager); + g_unix_signal_add (SIGUSR1, sigusr1_cb, manager); + g_unix_signal_add (SIGUSR2, sigusr2_cb, manager); + + if (IS_STRING_EMPTY (session_name)) { + session_name = _gsm_manager_get_default_session (manager); + } + + if (!gsm_session_fill (manager, session_name)) { + gsm_fail_whale_dialog_we_failed (FALSE, TRUE, NULL); + } + + _gsm_manager_set_renderer (manager, gl_renderer); +} + +static void +on_bus_acquired (GDBusConnection *connection, + const char *name, + gpointer data) +{ + create_manager (); +} + +static guint +acquire_name (void) +{ + return g_bus_own_name (G_BUS_TYPE_SESSION, + GSM_DBUS_NAME, + G_BUS_NAME_OWNER_FLAGS_NONE, + on_bus_acquired, + on_name_acquired, + on_name_lost, + NULL, NULL); +} + +static gboolean +require_dbus_session (int argc, + char **argv, + GError **error) +{ + char **new_argv; + int i; + + if (g_getenv ("DBUS_SESSION_BUS_ADDRESS")) + return TRUE; + + /* Just a sanity check to prevent infinite recursion if + * dbus-launch fails to set DBUS_SESSION_BUS_ADDRESS + */ + g_return_val_if_fail (!g_str_has_prefix (argv[0], "dbus-launch"), + TRUE); + + /* +2 for our new arguments, +1 for NULL */ + new_argv = g_malloc ((argc + 3) * sizeof (*argv)); + + new_argv[0] = "dbus-launch"; + new_argv[1] = "--exit-with-session"; + for (i = 0; i < argc; i++) { + new_argv[i + 2] = argv[i]; + } + new_argv[i + 2] = NULL; + + if (!execvp ("dbus-launch", new_argv)) { + g_set_error (error, + G_SPAWN_ERROR, + G_SPAWN_ERROR_FAILED, + "No session bus and could not exec dbus-launch: %s", + g_strerror (errno)); + return FALSE; + } + + /* Should not be reached */ + return TRUE; +} + +static gboolean +check_gl (GError **error) +{ + int status; + char *argv[] = { LIBEXECDIR "/gnome-session-check-accelerated", NULL }; + + if (getenv ("DISPLAY") == NULL) { + /* Not connected to X11, someone else will take care of checking GL */ + return TRUE; + } + + if (!g_spawn_sync (NULL, (char **) argv, NULL, 0, NULL, NULL, &gl_renderer, NULL, + &status, error)) { + return FALSE; + } + +#if GLIB_CHECK_VERSION(2, 70, 0) + return g_spawn_check_wait_status (status, error); +#else + return g_spawn_check_exit_status (status, error); +#endif +} + +static void +initialize_gio (void) +{ + char *disable_fuse = NULL; + char *use_vfs = NULL; + + disable_fuse = g_strdup (g_getenv ("GVFS_DISABLE_FUSE")); + use_vfs = g_strdup (g_getenv ("GIO_USE_VFS")); + + g_setenv ("GVFS_DISABLE_FUSE", "1", TRUE); + g_setenv ("GIO_USE_VFS", "local", TRUE); + g_vfs_get_default (); + + if (use_vfs) { + g_setenv ("GIO_USE_VFS", use_vfs, TRUE); + g_free (use_vfs); + } else { + g_unsetenv ("GIO_USE_VFS"); + } + + if (disable_fuse) { + g_setenv ("GVFS_DISABLE_FUSE", disable_fuse, TRUE); + g_free (disable_fuse); + } else { + g_unsetenv ("GVFS_DISABLE_FUSE"); + } +} + +#ifdef ENABLE_SYSTEMD_SESSION +static gboolean +leader_term_or_int_signal_cb (gpointer data) +{ + gint fifo_fd = GPOINTER_TO_INT (data); + + /* Start a shutdown explicitly. */ + gsm_util_start_systemd_unit ("gnome-session-shutdown.target", "replace-irreversibly", NULL); + + if (fifo_fd >= 0) { + int res; + + /* If we have a fifo, try to signal the other side. */ + res = write (fifo_fd, "S", 1); + if (res < 0) { + g_warning ("Error signaling shutdown to monitoring process: %m"); + gsm_quit (); + } + } else { + /* Otherwise quit immediately as we cannot wait on systemd */ + gsm_quit (); + } + + return G_SOURCE_REMOVE; +} + +/** + * systemd_leader_run: + * + * This is the session leader when running under systemd, i.e. it is the only + * process that is *not* managed by the systemd user instance, this process + * is the one executed by the GDM helpers and is part of the session scope in + * the system systemd instance. + * + * This process works together with a service running in the user systemd + * instance (currently gnome-session-ctl@monitor.service): + * + * - It needs to signal shutdown to the user session when receiving SIGTERM + * + * - It needs to quit just after the user session is done + * + * - The monitor instance needs to know if this process was killed + * + * All this is achieved by opening a named fifo in a well known location. + * If this process receives SIGTERM or SIGINT then it will write a single byte + * causing the monitor service to signal STOPPING=1 to systemd, triggering a + * clean shutdown, solving the first item. The other two items are solved by + * waiting for EOF/HUP on both sides and quitting immediately when receiving + * that signal. + * + * As an example, a shutdown might look as follows: + * + * - session-X.scope for user is stopped + * - Leader process receive SIGTERM + * - Leader sends single byte + * - Monitor process receives byte and signals STOPPING=1 + * - Systemd user instance starts session teardown + * - Session is torn down, last job run is stopping monitor process (SIGTERM) + * - Monitor process quits, closing FD in the process + * - Leader process receives HUP and quits + * - GDM shuts down its processes in the users scope + * + * The result is that the session is stopped cleanly. + */ +static void +systemd_leader_run(void) +{ + g_autofree char *fifo_name = NULL; + int res; + int fifo_fd; + + fifo_name = g_strdup_printf ("%s/gnome-session-leader-fifo", + g_get_user_runtime_dir ()); + res = mkfifo (fifo_name, 0666); + if (res < 0 && errno != EEXIST) + g_warning ("Error creating FIFO: %m"); + + fifo_fd = g_open (fifo_name, O_WRONLY | O_CLOEXEC, 0666); + if (fifo_fd >= 0) { + struct stat buf; + + res = fstat (fifo_fd, &buf); + if (res < 0) { + g_warning ("Unable to watch systemd session: fstat failed with %m"); + close (fifo_fd); + fifo_fd = -1; + } else if (!(buf.st_mode & S_IFIFO)) { + g_warning ("Unable to watch systemd session: FD is not a FIFO"); + close (fifo_fd); + fifo_fd = -1; + } else { + g_unix_fd_add (fifo_fd, G_IO_HUP, (GUnixFDSourceFunc) gsm_quit, NULL); + } + } else { + g_warning ("Unable to watch systemd session: Opening FIFO failed with %m"); + } + + g_unix_signal_add (SIGTERM, leader_term_or_int_signal_cb, GINT_TO_POINTER (fifo_fd)); + g_unix_signal_add (SIGINT, leader_term_or_int_signal_cb, GINT_TO_POINTER (fifo_fd)); + + /* Sleep until we receive HUP or are killed. */ + gsm_main (); + exit(0); +} +#endif /* ENABLE_SYSTEMD_SESSION */ + +int +main (int argc, char **argv) +{ + GError *error = NULL; + static char **override_autostart_dirs = NULL; + static char *opt_session_name = NULL; + const char *debug_string = NULL; + const char *env_override_autostart_dirs = NULL; + g_auto(GStrv) env_override_autostart_dirs_v = NULL; + gboolean gl_failed = FALSE; + guint name_owner_id; + GOptionContext *options; + static GOptionEntry entries[] = { +#ifdef ENABLE_SYSTEMD_SESSION + { "systemd-service", 0, 0, G_OPTION_ARG_NONE, &systemd_service, N_("Running as systemd service"), NULL }, + { "systemd", 0, 0, G_OPTION_ARG_NONE, &use_systemd, N_("Use systemd session management"), NULL }, +#endif + { "builtin", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &use_systemd, N_("Use builtin session management (rather than the systemd based one)"), NULL }, + { "autostart", 'a', 0, G_OPTION_ARG_STRING_ARRAY, &override_autostart_dirs, N_("Override standard autostart directories"), N_("AUTOSTART_DIR") }, + { "session", 0, 0, G_OPTION_ARG_STRING, &opt_session_name, N_("Session to use"), N_("SESSION_NAME") }, + { "debug", 0, 0, G_OPTION_ARG_NONE, &debug, N_("Enable debugging code"), NULL }, + { "failsafe", 'f', 0, G_OPTION_ARG_NONE, &failsafe, N_("Do not load user-specified applications"), NULL }, + { "version", 0, 0, G_OPTION_ARG_NONE, &show_version, N_("Version of this application"), NULL }, + /* Translators: the 'fail whale' is the black dialog we show when something goes seriously wrong */ + { "whale", 0, 0, G_OPTION_ARG_NONE, &please_fail, N_("Show the fail whale dialog for testing"), NULL }, + { "disable-acceleration-check", 0, 0, G_OPTION_ARG_NONE, &disable_acceleration_check, N_("Disable hardware acceleration check"), NULL }, + { NULL, 0, 0, 0, NULL, NULL, NULL } + }; + + /* Make sure that we have a session bus */ + if (!require_dbus_session (argc, argv, &error)) { + gsm_util_init_error (TRUE, "%s", error->message); + } + + /* From 3.14 GDM sets XDG_CURRENT_DESKTOP. For compatibility with + * older versions of GDM, other display managers, and startx, + * set a fallback value if we don't find it set. + */ + if (g_getenv ("XDG_CURRENT_DESKTOP") == NULL) { + g_setenv("XDG_CURRENT_DESKTOP", "GNOME", TRUE); + gsm_util_setenv ("XDG_CURRENT_DESKTOP", "GNOME"); + } + + /* Make sure we initialize gio in a way that does not autostart any daemon */ + initialize_gio (); + + setlocale (LC_ALL, ""); + bindtextdomain (GETTEXT_PACKAGE, LOCALE_DIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + debug_string = g_getenv ("GNOME_SESSION_DEBUG"); + if (debug_string != NULL) { + debug = atoi (debug_string) == 1; + } + + error = NULL; + options = g_option_context_new (_(" — the GNOME session manager")); + g_option_context_add_main_entries (options, entries, GETTEXT_PACKAGE); + g_option_context_parse (options, &argc, &argv, &error); + if (error != NULL) { + g_warning ("%s", error->message); + exit (1); + } + + g_option_context_free (options); + + /* Rebind stdout/stderr to the journal explicitly, so that + * journald picks ups the nicer "gnome-session" as the program + * name instead of whatever shell script GDM happened to use. + */ +#ifdef ENABLE_SYSTEMD_JOURNAL + if (!debug) { + int journalfd; + + journalfd = sd_journal_stream_fd (PACKAGE, LOG_INFO, 0); + if (journalfd >= 0) { + dup2(journalfd, 1); + dup2(journalfd, 2); + } + } +#endif + + gdm_log_init (); + gdm_log_set_debug (debug); + + if (systemd_service) { + /* XXX: This is an optimization, but we actually need to do + * it right now as the DISPLAY environment might leak + * into the new session from an old run. */ + g_debug ("hardware acceleration already done if needed"); + } else if (disable_acceleration_check) { + g_debug ("hardware acceleration check is disabled"); + } else { + /* Check GL, if it doesn't work out then force software fallback */ + if (!check_gl (&error)) { + gl_failed = TRUE; + + g_debug ("hardware acceleration check failed: %s", + error? error->message : ""); + g_clear_error (&error); + if (g_getenv ("LIBGL_ALWAYS_SOFTWARE") == NULL) { + g_setenv ("LIBGL_ALWAYS_SOFTWARE", "1", TRUE); + if (!check_gl (&error)) { + g_warning ("software acceleration check failed: %s", + error? error->message : ""); + g_clear_error (&error); + } else { + gl_failed = FALSE; + } + } + } + } + + if (show_version) { + g_print ("%s %s\n", argv [0], VERSION); + exit (0); + } + + if (gl_failed) { + gsm_fail_whale_dialog_we_failed (FALSE, TRUE, NULL); + gsm_main (); + exit (1); + } + + if (please_fail) { + gsm_fail_whale_dialog_we_failed (TRUE, TRUE, NULL); + gsm_main (); + exit (1); + } + + env_override_autostart_dirs = g_getenv ("GNOME_SESSION_AUTOSTART_DIR"); + + if (env_override_autostart_dirs != NULL && env_override_autostart_dirs[0] != '\0') { + env_override_autostart_dirs_v = g_strsplit (env_override_autostart_dirs, ":", 0); + gsm_util_set_autostart_dirs (env_override_autostart_dirs_v); + } else { + gsm_util_set_autostart_dirs (override_autostart_dirs); + + /* Export the override autostart dirs parameter to the environment + * in case we are running on systemd. */ + if (override_autostart_dirs) { + g_autofree char *autostart_dirs = NULL; + autostart_dirs = g_strjoinv (":", override_autostart_dirs); + g_setenv ("GNOME_SESSION_AUTOSTART_DIR", autostart_dirs, TRUE); + } + } + + gsm_util_export_activation_environment (&error); + if (error) { + g_warning ("Failed to upload environment to DBus: %s", error->message); + g_clear_error (&error); + } + + session_name = opt_session_name; + +#ifdef HAVE_SYSTEMD + gsm_util_export_user_environment (&error); + if (error && !g_getenv ("RUNNING_UNDER_GDM")) + g_warning ("Failed to upload environment to systemd: %s", error->message); + g_clear_error (&error); +#endif + +#ifdef ENABLE_SYSTEMD_SESSION + if (use_systemd && !systemd_service) { + g_autofree gchar *gnome_session_target; + const gchar *session_type; + + session_type = g_getenv ("XDG_SESSION_TYPE"); + + /* We really need to resolve the session name at this point, + * which requires talking to GSettings internally. */ + if (IS_STRING_EMPTY (session_name)) { + session_name = _gsm_manager_get_default_session (NULL); + } + + /* Reset all failed units; we are going to start a lof ot things and + * really do not want to run into errors because units have failed + * in a previous session + */ + gsm_util_systemd_reset_failed (&error); + if (error && !g_getenv ("RUNNING_UNDER_GDM")) + g_warning ("Failed to reset failed state of units: %s", error->message); + g_clear_error (&error); + + /* We don't escape the name (i.e. we leave any '-' intact). */ + gnome_session_target = g_strdup_printf ("gnome-session-%s@%s.target", session_type, session_name); + if (gsm_util_start_systemd_unit (gnome_session_target, "fail", &error)) { + /* We started the unit, open fifo and sleep forever. */ + systemd_leader_run (); + exit(0); + } + + /* We could not start the unit, fall back. */ + if (g_getenv ("RUNNING_UNDER_GDM")) + g_message ("Falling back to non-systemd startup procedure. This is expected to happen for GDM sessions."); + else + g_warning ("Falling back to non-systemd startup procedure due to error: %s", error->message); + g_clear_error (&error); + } +#endif /* ENABLE_SYSTEMD_SESSION */ + + { + gchar *ibus_path; + + ibus_path = g_find_program_in_path("ibus-daemon"); + + if (ibus_path) { + const gchar *p; + p = g_getenv ("QT_IM_MODULE"); + if (!p || !*p) + p = "ibus"; + gsm_util_setenv ("QT_IM_MODULE", p); + p = g_getenv ("XMODIFIERS"); + if (!p || !*p) + p = "@im=ibus"; + gsm_util_setenv ("XMODIFIERS", p); + } + + g_free (ibus_path); + } + + /* We want to use the GNOME menus which has the designed categories. + */ + gsm_util_setenv ("XDG_MENU_PREFIX", "gnome-"); + + /* Talk to logind before acquiring a name, since it does synchronous + * calls at initialization time that invoke a main loop and if we + * already owned a name, then we would service too early during + * that main loop. + */ + g_object_unref (gsm_get_system ()); + + name_owner_id = acquire_name (); + + gsm_main (); + +#ifdef HAVE_SYSTEMD + gsm_util_export_user_environment (NULL); +#endif + + g_clear_object (&manager); + g_free (gl_renderer); + + g_bus_unown_name (name_owner_id); + gdm_log_shutdown (); + + return 0; +} diff --git a/gnome-session/meson.build b/gnome-session/meson.build new file mode 100644 index 0000000..7d2f3c0 --- /dev/null +++ b/gnome-session/meson.build @@ -0,0 +1,111 @@ +script_conf = configuration_data() +script_conf.set('libexecdir', session_libexecdir) + +script = 'gnome-session' + +configure_file( + input: script + '.in', + output: script, + install: true, + install_dir: session_bindir, + configuration: script_conf +) + +libgsmutil = static_library( + 'gsmutil', + sources: 'gsm-util.c', + include_directories: top_inc, + dependencies: session_deps +) + +sources = files( + 'gsm-app.c', + 'gsm-autostart-app.c', + 'gsm-client.c', + 'gsm-dbus-client.c', + 'gsm-fail-whale.c', + 'gsm-inhibitor.c', + 'gdm-log.c', + 'gsm-manager.c', + 'gsm-presence.c', + 'gsm-process-helper.c', + 'gsm-session-fill.c', + 'gsm-session-save.c', + 'gsm-shell-extensions.c', + 'gsm-shell.c', + 'gsm-store.c', + 'gsm-system.c', + 'gsm-systemd.c', + 'gsm-xsmp-client.c', + 'gsm-xsmp-server.c', + 'main.c' +) + +dbus_ifaces = [ + ['org.gnome.SessionManager', 'ExportedManager'], + ['org.gnome.SessionManager.Client', 'ExportedClient'], + ['org.gnome.SessionManager.ClientPrivate', 'ExportedClientPrivate'], + ['org.gnome.SessionManager.App', 'ExportedApp'], + ['org.gnome.SessionManager.Inhibitor', 'ExportedInhibitor'], + ['org.gnome.SessionManager.Presence', 'ExportedPresence'] +] + +foreach iface: dbus_ifaces + sources += gnome.gdbus_codegen( + iface[0], + iface[0] + '.xml', + interface_prefix: iface[0] + '.', + namespace: 'Gsm', + annotations: [iface[0], 'org.gtk.GDBus.C.Name', iface[1]] + ) +endforeach + +if enable_consolekit + sources += files('gsm-consolekit.c') +endif + +cflags = [ + '-DLOCALE_DIR="@0@"'.format(session_localedir), + '-DDATA_DIR="@0@"'.format(session_pkgdatadir), + '-DLIBEXECDIR="@0@"'.format(session_libexecdir) +] + +executable( + meson.project_name() + '-binary', + sources, + include_directories: top_inc, + dependencies: session_bin_deps, + c_args: cflags, + link_with: libgsmutil, + install: true, + install_dir: session_libexecdir +) + +sources = files('gsm-fail-whale-dialog.c') + +cflags = '-DLOCALE_DIR="@0@"'.format(session_localedir) + +executable( + meson.project_name() + '-failed', + sources, + include_directories: top_inc, + dependencies: gtk_dep, + c_args: cflags, + install: true, + install_dir: session_libexecdir +) + +units = [ + ['test-inhibit', [], session_deps + [gtk_dep]], + ['test-client-dbus', [], [gio_dep]], + ['test-process-helper', files('gsm-process-helper.c'), [gio_dep]] +] + +foreach unit: units + executable( + unit[0], + [unit[0] + '.c'] + unit[1], + include_directories: top_inc, + dependencies: unit[2] + ) +endforeach diff --git a/gnome-session/org.gnome.SessionManager.App.xml b/gnome-session/org.gnome.SessionManager.App.xml new file mode 100644 index 0000000..9f6e1b3 --- /dev/null +++ b/gnome-session/org.gnome.SessionManager.App.xml @@ -0,0 +1,43 @@ + + + + + + + + The identifier for the application + + + + + Return the application ID. + + + + + + + The startup identifier + + + + + Return the startup ID associated with this application. + + + + + + + The application startup phase + + + + + Return the startup phase of this application. + + + + + + diff --git a/gnome-session/org.gnome.SessionManager.Client.xml b/gnome-session/org.gnome.SessionManager.Client.xml new file mode 100644 index 0000000..cf7476a --- /dev/null +++ b/gnome-session/org.gnome.SessionManager.Client.xml @@ -0,0 +1,73 @@ + + + + + + + + The identifier for the associated application + + + + + Return the application ID associated with this client. + + + + + + + The startup identifier + + + + + Return the startup ID associated with this client. + + + + + + + The restart style hint + + + + + Return the restart style hint for this client. + + + + + + + The Unix process identifier + + + + + Return the Unix process identifier for this client. + + + + + + + The client status + + + + + Return the status of this client. + + + + + + + Initiate a request to terminate this application via XSMP. + + + + + diff --git a/gnome-session/org.gnome.SessionManager.ClientPrivate.xml b/gnome-session/org.gnome.SessionManager.ClientPrivate.xml new file mode 100644 index 0000000..a167065 --- /dev/null +++ b/gnome-session/org.gnome.SessionManager.ClientPrivate.xml @@ -0,0 +1,123 @@ + + + + + + + + Whether or not it is OK to preceed + + + + + The reason string + + + + + This method should be used by the client in response to + the QueryEndSession + and EndSession signals. + + + + + + + + Stop client + + + The client should stop and remove itself from the session in + response to this signal. + + + + + + + + + Flags + + + + + This signal is used to inform the client that the + session is about to end. The client must respond by + calling + EndSessionResponse + within one second of the signal emission. + + + The flags may include: + + + 1 + Logout is forced. + EndSessionResponse + reason and any inhibit from client will be + ignored. + + + + + If the client responds with an EndSessionResponse is-ok + argument equal to FALSE and a reason then this reason may + be displayed to the user. + + + The client must not attempt to perform any actions or + interact with the user in response to this signal. Any + actions required for a clean shutdown should take place in + response to the + EndSession signal. + + + The client should limit operations until either a + EndSession + CancelEndSession + signal is received. + + + + + + + + + Flags + + + + + This signal is used to inform the client that the + session is about to end. The client must respond by + calling + EndSessionResponse + within ten seconds of the signal emission. + + + The client must not attempt to interact with the user in + response to this signal. The application will be given a + maxium of ten seconds to perform any actions required for + a clean shutdown. + + + + + + + + + + This signal indicates to the client that a previous emission of + QueryEndSession + has been cancelled. The client should resume normal operations. + + + + + + + diff --git a/gnome-session/org.gnome.SessionManager.Inhibitor.xml b/gnome-session/org.gnome.SessionManager.Inhibitor.xml new file mode 100644 index 0000000..342d2a8 --- /dev/null +++ b/gnome-session/org.gnome.SessionManager.Inhibitor.xml @@ -0,0 +1,66 @@ + + + + + + + + The identifier for the associated application + + + + + Return the application ID associated with this inhibit. + + + + + + + The object path of the associated client + + + + + Return the client object path associated with this inhibit. + + + + + + + The reason for the inhibit + + + + + Return the reason for the inhibit + + + + + + + The flags that determine the scope of the inhibit + + + + + Return the flags that determine the scope of the inhibit + + + + + + + X11 toplevel window identifier associated with this inhibit. Zero if not set. + + + + + Return the X11 toplevel window identifier associated with this inhibit. Zero if not set. + + + + + diff --git a/gnome-session/org.gnome.SessionManager.Presence.xml b/gnome-session/org.gnome.SessionManager.Presence.xml new file mode 100644 index 0000000..0441970 --- /dev/null +++ b/gnome-session/org.gnome.SessionManager.Presence.xml @@ -0,0 +1,95 @@ + + + + + + + + + + The status of the session. + + + The status parameter must be one of the following: + + + 0 + Available + + + 1 + Invisible + + + 2 + Busy + + + 3 + Idle + + + + + + + + + + The descriptive status for the session. + + + + + + + + The status value + + + + + Set the status value of the session. + + + + + + + The descriptive status for the session. + + + + + Set the descriptive status text for the session. + + + + + + + + The new status value + + + + + Indicates that the session status value has changed. + + + + + + + The new status text + + + + + Indicates that the descriptive session status text has changed. + + + + + + diff --git a/gnome-session/org.gnome.SessionManager.xml b/gnome-session/org.gnome.SessionManager.xml new file mode 100644 index 0000000..ce4b8c0 --- /dev/null +++ b/gnome-session/org.gnome.SessionManager.xml @@ -0,0 +1,492 @@ + + + + + + + + + + + The variable name + + + + + The value + + + + + Adds the variable name to the application launch environment with the specified value. May only be used during the Session Manager initialization phase. + + + + + + + + The locale category + + + + + The value + + + + + Reads the current state of the specific locale category. + + + + + + + + The error message + + + + + Whether the error should be treated as fatal + + + + + May be used by applications launched during the Session Manager initialization phase to indicate there was a problem. + + + + + + + + Run from systemd to signal that gnome-session-initialized.target has been reached. + + + + + + + + + + The application identifier + + + + + Client startup identifier + + + + + The object path of the newly registered client + + + + + Register the caller as a Session Management client. + + + + + + + + The object path of the client + + + + + Unregister the specified client from Session Management. + + + + + + + + The application identifier + + + + + The toplevel X window identifier + + + + + The reason for the inhibit + + + + + Flags that specify what should be inhibited + + + + + The cookie + + + + + Proactively indicates that the calling application is performing an action that should not be interrupted and sets a reason to be displayed to the user when an interruption is about to take place. + + + Applications should invoke this method when they begin an operation that + should not be interrupted, such as creating a CD or DVD. The types of actions + that may be blocked are specified by the flags parameter. When the application + completes the operation it should call Uninhibit() + or disconnect from the session bus. + + + Applications should not expect that they will always be able to block the + action. In most cases, users will be given the option to force the action + to take place. + + + Reasons should be short and to the point. + + + The flags parameter must include at least one of the following: + + + 1 + Inhibit logging out + + + 2 + Inhibit user switching + + + 4 + Inhibit suspending the session or computer + + + 8 + Inhibit the session being marked as idle + + + 16 + Inhibit auto-mounting removable media for the session + + + Values for flags may be bitwise or'ed together. + + + The returned cookie is used to uniquely identify this request. It should be used + as an argument to Uninhibit() in + order to remove the request. + + + + + + + + + The cookie + + + + + Cancel a previous call to Inhibit() identified by the cookie. + + + + + + + + Flags that specify what should be inhibited + + + + + Returns TRUE if any of the operations in the bitfield flags are inhibited + + + + + Determine if operation(s) specified by the flags + are currently inhibited. Flags are same as those accepted + by the + Inhibit() + method. + + + + + + + + an array of client IDs + + + + + This gets a list of all the Clients + that are currently known to the session manager. + Each Client ID is an D-Bus object path for the object that implements the + Client interface. + + org.gnome.SessionManager.Client + + + + + + + an array of inhibitor IDs + + + + + This gets a list of all the Inhibitors + that are currently known to the session manager. + Each Inhibitor ID is an D-Bus object path for the object that implements the + Inhibitor interface. + + org.gnome.SessionManager.Inhibitor + + + + + + + + The autostart condition string + + + + + True if condition is handled, false otherwise + + + + + Allows the caller to determine whether the session manager is + handling changes to the specified autostart condition. + + + + + + + + Request a shutdown dialog. + + + + + + + + Request a reboot dialog. + + + + + + + + True if shutdown is available to the user, false otherwise + + + + + Allows the caller to determine whether or not it's okay to show + a shutdown option in the UI + + + + + + + + Whether we should reboot into setup + + + + + Allows the caller to indicate to the system's firmware to boot into setup mode + + + + + + + + True if boot into setup mode is available to the user, false otherwise + + + + + Allows the caller to determine whether or not it's okay to show + a reboot to firmware option in the UI + + + + + + + + The type of logout that is being requested + + + + + Request a logout dialog + + Allowed values for the mode parameter are: + + + 0 + Normal. + + + 1 + No confirmation interface should be shown. + + + 2 + Forcefully logout. No confirmation will be shown and any inhibitors will be ignored. + + + Values for flags may be bitwise or'ed together. + + + + + + + + + True if the session has entered the Running phase, false otherwise + + + + + Allows the caller to determine whether the session manager + has entered the Running phase, in case the client missed the + SessionRunning signal. + + + + + + + + + + The object path for the added client + + + + + Emitted when a client has been added to the session manager. + + + + + + + + The object path for the removed client + + + + + Emitted when a client has been removed from the session manager. + + + + + + + + + The object path for the added inhibitor + + + + + Emitted when an inhibitor has been added to the session manager. + + + + + + + + The object path for the removed inhibitor + + + + + Emitted when an inhibitor has been removed from the session manager. + + + + + + + + + Indicates the session has entered the Running phase. + + + + + + + + Indicates the session is about to end. + + + + + + + + + + The name of the session that has been loaded. + + + + + + + + The renderer for the session that has been loaded. + At the moment this supports GL and GLES, and is only used for the + X session. + + + + + + + + If true, the session is currently in the + foreground and available for user input. + + + + + + + + A bitmask of flags to indicate which actions + are inhibited. See the Inhibit() function's description + for a list of possible values. + + + + + + diff --git a/gnome-session/test-client-dbus.c b/gnome-session/test-client-dbus.c new file mode 100644 index 0000000..7d6f9e4 --- /dev/null +++ b/gnome-session/test-client-dbus.c @@ -0,0 +1,265 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include + +#include +#include + +#define SM_DBUS_NAME "org.gnome.SessionManager" +#define SM_DBUS_PATH "/org/gnome/SessionManager" +#define SM_DBUS_INTERFACE "org.gnome.SessionManager" + +#define SM_CLIENT_DBUS_INTERFACE "org.gnome.SessionManager.ClientPrivate" + +static GDBusConnection *connection = NULL; +static GDBusProxy *sm_proxy = NULL; +static char *client_id = NULL; +static GDBusProxy *client_proxy = NULL; +static GMainLoop *main_loop = NULL; + +static gboolean +session_manager_connect (void) +{ + + if (connection == NULL) { + GError *error; + + error = NULL; + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + if (connection == NULL) { + g_message ("Failed to connect to the session bus: %s", + error->message); + g_error_free (error); + exit (1); + } + } + + sm_proxy = g_dbus_proxy_new_sync (connection, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + SM_DBUS_NAME, + SM_DBUS_PATH, + SM_DBUS_INTERFACE, + NULL, NULL); + return (sm_proxy != NULL); +} + +static void +on_client_query_end_session (GDBusProxy *proxy, + GVariant *parameters) +{ + GError *error; + gboolean is_ok; + const char *reason; + guint flags; + GVariant *reply; + + is_ok = FALSE; + reason = "Unsaved files"; + g_variant_get (parameters, "(u)", &flags); + + g_debug ("Got query end session signal flags=%u", flags); + + error = NULL; + reply = g_dbus_proxy_call_sync (proxy, + "EndSessionResponse", + g_variant_new ("(bs)", + is_ok, + reason), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, &error); + + if (error != NULL) { + g_warning ("Failed to respond to EndSession: %s", error->message); + g_error_free (error); + } else { + g_variant_unref (reply); + } +} + +static void +on_client_end_session (GVariant *parameters) +{ + guint flags; + g_variant_get (parameters, "(u)", &flags); + g_debug ("Got end session signal flags=%u", flags); +} + +static void +on_client_cancel_end_session (void) +{ + g_debug ("Got end session cancelled signal"); +} + +static void +on_client_stop (void) +{ + g_debug ("Got client stop signal"); + g_main_loop_quit (main_loop); +} + +static void +on_client_dbus_signal (GDBusProxy *proxy, + gchar *sender_name, + gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + if (g_strcmp0 (signal_name, "Stop") == 0) { + on_client_stop (); + } else if (g_strcmp0 (signal_name, "CancelEndSession") == 0) { + on_client_cancel_end_session (); + } else if (g_strcmp0 (signal_name, "EndSession") == 0) { + on_client_end_session (parameters); + } else if (g_strcmp0 (signal_name, "QueryEndSession") == 0) { + on_client_query_end_session (proxy, parameters); + } +} + +static gboolean +register_client (void) +{ + GError *error; + GVariant *object_path_variant; + const char *startup_id; + const char *app_id; + + app_id = "gedit"; + + startup_id = g_getenv ("DESKTOP_AUTOSTART_ID"); + if (!startup_id) { + startup_id = ""; + } + + error = NULL; + object_path_variant = g_dbus_proxy_call_sync (sm_proxy, + "RegisterClient", + g_variant_new ("(ss)", + app_id, + startup_id), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, &error); + + if (error != NULL) { + g_warning ("Failed to register client: %s", error->message); + g_error_free (error); + return FALSE; + } + + g_variant_get (object_path_variant, "(o)", &client_id); + g_debug ("Client registered with session manager: %s", client_id); + + client_proxy = g_dbus_proxy_new_sync (connection, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + SM_DBUS_NAME, + client_id, + SM_CLIENT_DBUS_INTERFACE, + NULL, NULL); + if (client_proxy != NULL) { + g_signal_connect (client_proxy, "g-signal", + G_CALLBACK (on_client_dbus_signal), NULL); + } + + g_variant_unref (object_path_variant); + + return (client_proxy != NULL); +} + +static gboolean +session_manager_disconnect (void) +{ + if (sm_proxy != NULL) { + g_object_unref (sm_proxy); + sm_proxy = NULL; + } + + return TRUE; +} + +static gboolean +unregister_client (void) +{ + GError *error; + GVariant *reply; + + error = NULL; + reply = g_dbus_proxy_call_sync (sm_proxy, + "UnregisterClient", + g_variant_new ("(o)", client_id), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, &error); + if (error != NULL) { + g_warning ("Failed to unregister client: %s", error->message); + g_error_free (error); + return FALSE; + } + + g_free (client_id); + client_id = NULL; + + g_variant_unref (reply); + + return TRUE; +} + +static gboolean +quit_test (gpointer data) +{ + g_main_loop_quit (main_loop); + return FALSE; +} + +int +main (int argc, + char *argv[]) +{ + gboolean res; + + g_log_set_always_fatal (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING); + + res = session_manager_connect (); + if (! res) { + g_warning ("Unable to connect to session manager"); + exit (1); + } + + res = register_client (); + if (! res) { + g_warning ("Unable to register client with session manager"); + } + + main_loop = g_main_loop_new (NULL, FALSE); + + g_timeout_add_seconds (30, quit_test, NULL); + + g_main_loop_run (main_loop); + g_main_loop_unref (main_loop); + + unregister_client (); + session_manager_disconnect (); + + return 0; +} diff --git a/gnome-session/test-inhibit.c b/gnome-session/test-inhibit.c new file mode 100644 index 0000000..0a8417e --- /dev/null +++ b/gnome-session/test-inhibit.c @@ -0,0 +1,197 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include + +#include +#include + +#define SM_DBUS_NAME "org.gnome.SessionManager" +#define SM_DBUS_PATH "/org/gnome/SessionManager" +#define SM_DBUS_INTERFACE "org.gnome.SessionManager" + +static GDBusConnection *connection = NULL; +static GDBusProxy *sm_proxy = NULL; +static guint cookie = 0; + +static gboolean +session_manager_connect (void) +{ + + if (connection == NULL) { + GError *error; + + error = NULL; + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + if (connection == NULL) { + g_message ("Failed to connect to the session bus: %s", + error->message); + g_error_free (error); + exit (1); + } + } + + sm_proxy = g_dbus_proxy_new_sync (connection, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + SM_DBUS_NAME, + SM_DBUS_PATH, + SM_DBUS_INTERFACE, + NULL, NULL); + return (sm_proxy != NULL); +} + +typedef enum { + GSM_INHIBITOR_FLAG_LOGOUT = 1 << 0, + GSM_INHIBITOR_FLAG_SWITCH_USER = 1 << 1, + GSM_INHIBITOR_FLAG_SUSPEND = 1 << 2 +} GsmInhibitFlag; + +static gboolean +do_inhibit_for_window (GdkWindow *window) +{ + GError *error; + GVariant *cookie_variant; + const char *app_id; + const char *reason; + guint toplevel_xid; + guint flags; + +#if 1 + app_id = "nautilus-cd-burner"; + reason = "A CD burn is in progress."; +#else + app_id = "nautilus"; + reason = "A file transfer is in progress."; +#endif + toplevel_xid = gdk_x11_window_get_xid (window); + flags = GSM_INHIBITOR_FLAG_LOGOUT + | GSM_INHIBITOR_FLAG_SWITCH_USER + | GSM_INHIBITOR_FLAG_SUSPEND; + + error = NULL; + cookie_variant = g_dbus_proxy_call_sync (sm_proxy, + "Inhibit", + g_variant_new ("(susu)", + app_id, + toplevel_xid, + reason, + flags), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, &error); + + if (error != NULL) { + g_warning ("Failed to inhibit: %s", error->message); + g_error_free (error); + return FALSE; + } + + g_variant_get (cookie_variant, "(u)", &cookie); + g_debug ("Inhibiting session manager: %u", cookie); + g_variant_unref (cookie_variant); + + return TRUE; +} +static gboolean +session_manager_disconnect (void) +{ + g_clear_object (&sm_proxy); + g_clear_object (&connection); + + return TRUE; +} + +static gboolean +do_uninhibit (void) +{ + GError *error; + GVariant *reply; + + error = NULL; + reply = g_dbus_proxy_call_sync (sm_proxy, + "Uninhibit", + g_variant_new ("(u)", cookie), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, &error); + if (error != NULL) { + g_warning ("Failed to uninhibit: %s", error->message); + g_error_free (error); + return FALSE; + } + + g_variant_unref (reply); + + cookie = 0; + + return TRUE; +} + +static void +on_widget_show (GtkWidget *dialog, + gpointer data) +{ + gboolean res; + + res = do_inhibit_for_window (gtk_widget_get_window (dialog)); + if (! res) { + g_warning ("Unable to register client with session manager"); + } +} + +int +main (int argc, + char *argv[]) +{ + gboolean res; + GtkWidget *dialog; + + g_log_set_always_fatal (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING); + + gtk_init (&argc, &argv); + + res = session_manager_connect (); + if (! res) { + g_warning ("Unable to connect to session manager"); + exit (1); + } + + g_timeout_add_seconds (30, (GSourceFunc)gtk_main_quit, NULL); + + dialog = gtk_message_dialog_new (NULL, + 0, + GTK_MESSAGE_INFO, + GTK_BUTTONS_CANCEL, + "Inhibiting logout, switch user, and suspend."); + + g_signal_connect (dialog, "response", G_CALLBACK (gtk_main_quit), NULL); + g_signal_connect (dialog, "show", G_CALLBACK (on_widget_show), NULL); + gtk_widget_show (dialog); + + gtk_main (); + + do_uninhibit (); + session_manager_disconnect (); + + return 0; +} diff --git a/gnome-session/test-process-helper.c b/gnome-session/test-process-helper.c new file mode 100644 index 0000000..e48caa8 --- /dev/null +++ b/gnome-session/test-process-helper.c @@ -0,0 +1,56 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2010 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + */ + +#include + +#include + +#include "gsm-process-helper.h" + +int +main (int argc, + char *argv[]) +{ + char *command_line = "xeyes"; + int timeout = 500; + GError *error = NULL; + + if (argc > 3) { + g_printerr ("Too many arguments.\n"); + g_printerr ("Usage: %s [COMMAND] [TIMEOUT]\n", argv[0]); + return 1; + } + + if (argc >= 2) + command_line = argv[1]; + if (argc >= 3) { + int i = atoi (argv[2]); + if (i > 0) + timeout = i; + } + + if (!gsm_process_helper (command_line, timeout, &error)) { + g_warning ("%s", error->message); + g_clear_error (&error); + } else { + g_print ("Command exited successfully.\n"); + } + + return 0; +} -- cgit v1.2.3