From ade647861a671eca584d25432722ac2661854748 Mon Sep 17 00:00:00 2001 From: Josselin Mouette Date: Mon, 21 Jun 2010 15:22:23 -0400 Subject: [PATCH 08/19] Add "Remember Currently Running Applications" button This adds back session saving that's not at logout. --- capplet/gsm-properties-dialog.c | 63 ++++++++- capplet/meson.build | 3 +- data/session-properties.ui | 12 ++ gnome-session/gsm-client.c | 10 ++ gnome-session/gsm-client.h | 6 + gnome-session/gsm-dbus-client.c | 14 ++ gnome-session/gsm-manager.c | 150 ++++++++++++++++++++- gnome-session/gsm-manager.h | 3 + gnome-session/gsm-xsmp-client.c | 37 +++++ gnome-session/gsm-xsmp-client.h | 3 +- gnome-session/org.gnome.SessionManager.xml | 8 ++ meson.build | 1 + 12 files changed, 305 insertions(+), 5 deletions(-) diff --git a/capplet/gsm-properties-dialog.c b/capplet/gsm-properties-dialog.c index 33812b8b..d2be778b 100644 --- a/capplet/gsm-properties-dialog.c +++ b/capplet/gsm-properties-dialog.c @@ -5,70 +5,77 @@ * Copyright (C) 2008 Lucas Rocha. * 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ #include "config.h" #include #include #include #include "gsm-properties-dialog.h" #include "gsm-app-dialog.h" #include "gsm-util.h" #include "gsp-app.h" #include "gsp-app-manager.h" +#include +#include + +#define GSM_SERVICE_DBUS "org.gnome.SessionManager" +#define GSM_PATH_DBUS "/org/gnome/SessionManager" +#define GSM_INTERFACE_DBUS "org.gnome.SessionManager" #define GSM_PROPERTIES_DIALOG_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSM_TYPE_PROPERTIES_DIALOG, GsmPropertiesDialogPrivate)) #define GTKBUILDER_FILE "session-properties.ui" #define CAPPLET_TREEVIEW_WIDGET_NAME "session_properties_treeview" #define CAPPLET_ADD_WIDGET_NAME "session_properties_add_button" #define CAPPLET_DELETE_WIDGET_NAME "session_properties_delete_button" #define CAPPLET_EDIT_WIDGET_NAME "session_properties_edit_button" #define CAPPLET_SAVE_WIDGET_NAME "session_properties_save_button" +#define CAPPLET_SESSION_SAVED_WIDGET_NAME "session_properties_session_saved_label" #define CAPPLET_REMEMBER_WIDGET_NAME "session_properties_remember_toggle" #define STARTUP_APP_ICON "system-run" #define SPC_SETTINGS_SCHEMA "org.gnome.SessionManager" #define SPC_SETTINGS_AUTOSAVE_KEY "auto-save-session" struct GsmPropertiesDialogPrivate { GtkBuilder *xml; GtkListStore *list_store; GtkTreeModel *tree_filter; GtkTreeView *treeview; GtkWidget *add_button; GtkWidget *delete_button; GtkWidget *edit_button; GSettings *settings; GspAppManager *manager; }; enum { STORE_COL_VISIBLE = 0, STORE_COL_ENABLED, STORE_COL_GICON, STORE_COL_DESCRIPTION, STORE_COL_APP, STORE_COL_SEARCH, @@ -430,65 +437,119 @@ on_edit_app_clicked (GtkWidget *widget, char *exec; char *comment; edit_dialog = gsm_app_dialog_new (gsp_app_get_name (app), gsp_app_get_exec (app), gsp_app_get_comment (app)); gtk_window_set_transient_for (GTK_WINDOW (edit_dialog), GTK_WINDOW (dialog)); if (gsm_app_dialog_run (GSM_APP_DIALOG (edit_dialog), &name, &exec, &comment)) { gsp_app_update (app, name, comment, exec); g_free (name); g_free (exec); g_free (comment); } g_object_unref (app); } } static void on_row_activated (GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, GsmPropertiesDialog *dialog) { on_edit_app_clicked (NULL, dialog); } +static void +session_saved_message (GsmPropertiesDialog *dialog, + const char *msg, + gboolean is_error) +{ + GtkLabel *label; + gchar *markup; + label = GTK_LABEL (gtk_builder_get_object (dialog->priv->xml, CAPPLET_SESSION_SAVED_WIDGET_NAME)); + if (is_error) + markup = g_markup_printf_escaped ("%s", msg); + else + markup = g_markup_escape_text (msg, -1); + gtk_label_set_markup (label, markup); + g_free (markup); +} + +static void +session_saved_cb (DBusGProxy *proxy, + DBusGProxyCall *call_id, + void *user_data) +{ + gboolean res; + GsmPropertiesDialog *dialog = user_data; + + res = dbus_g_proxy_end_call (proxy, call_id, NULL, G_TYPE_INVALID); + if (res) + session_saved_message (dialog, _("Your session has been saved."), FALSE); + else + session_saved_message (dialog, _("Failed to save session"), TRUE); + + g_object_unref (proxy); +} + static void on_save_session_clicked (GtkWidget *widget, GsmPropertiesDialog *dialog) { - g_debug ("Session saving is not implemented yet!"); + DBusGConnection *conn; + DBusGProxy *proxy; + DBusGProxyCall *call; + + conn = dbus_g_bus_get (DBUS_BUS_SESSION, NULL); + if (conn == NULL) { + session_saved_message (dialog, _("Could not connect to the session bus"), TRUE); + return; + } + + proxy = dbus_g_proxy_new_for_name (conn, GSM_SERVICE_DBUS, GSM_PATH_DBUS, GSM_INTERFACE_DBUS); + if (proxy == NULL) { + session_saved_message (dialog, _("Could not connect to the session manager"), TRUE); + return; + } + + call = dbus_g_proxy_begin_call (proxy, "SaveSession", session_saved_cb, dialog, NULL, G_TYPE_INVALID); + if (call == NULL) { + session_saved_message (dialog, _("Failed to save session"), TRUE); + g_object_unref (proxy); + return; + } } static void setup_dialog (GsmPropertiesDialog *dialog) { GtkTreeView *treeview; GtkWidget *button; GtkTreeModel *tree_filter; GtkTreeViewColumn *column; GtkCellRenderer *renderer; GtkTreeSelection *selection; GtkTargetList *targetlist; gtk_dialog_add_buttons (GTK_DIALOG (dialog), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL); dialog->priv->list_store = gtk_list_store_new (NUMBER_OF_COLUMNS, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_ICON, G_TYPE_STRING, G_TYPE_OBJECT, G_TYPE_STRING); tree_filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (dialog->priv->list_store), NULL); g_object_unref (dialog->priv->list_store); dialog->priv->tree_filter = tree_filter; gtk_tree_model_filter_set_visible_column (GTK_TREE_MODEL_FILTER (tree_filter), diff --git a/capplet/meson.build b/capplet/meson.build index 8dad9c80..ae6cb6b9 100644 --- a/capplet/meson.build +++ b/capplet/meson.build @@ -1,36 +1,37 @@ install_data( install_dir: session_bindir ) deps = session_deps + [ glib_dep, gtk_dep, x11_dep, sm_dep, - ice_dep + ice_dep, + dbus_glib_dep ] cflags = [ '-DLOCALE_DIR="@0@"'.format(session_localedir), '-DGTKBUILDER_DIR="@0@"'.format(session_pkgdatadir) ] sources = files( '../gnome-session/gsm-util.c', 'gsm-app-dialog.c', 'gsm-properties-dialog.c', 'gsp-app.c', 'gsp-app-manager.c', 'gsp-keyfile.c', 'main.c' ) executable( 'gnome-session-properties', sources, include_directories: [ top_inc, include_directories('../gnome-session') ], dependencies: deps, c_args: cflags, install: true, install_dir: session_bindir ) diff --git a/data/session-properties.ui b/data/session-properties.ui index 47a30f78..b43759ff 100644 --- a/data/session-properties.ui +++ b/data/session-properties.ui @@ -133,108 +133,120 @@ True False 12 6 _Automatically remember running applications when logging out True True False True 0.5 True False False 0 True False + True True True True False 4 True False gtk-save False False 0 True False _Remember Currently Running Applications True True True 1 False False 0 False False 1 + + + True + True + + + False + False + 2 + + 1 True False Options 1 False True False 6 3 2 12 6 True False 12 diff --git a/gnome-session/gsm-client.c b/gnome-session/gsm-client.c index 6828ad44..3f216b22 100644 --- a/gnome-session/gsm-client.c +++ b/gnome-session/gsm-client.c @@ -514,50 +514,60 @@ gsm_client_query_end_session (GsmClient *client, 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); } +gboolean +gsm_client_request_save (GsmClient *client, + guint flags, + GError **error) +{ + g_return_val_if_fail (GSM_IS_CLIENT (client), FALSE); + + return GSM_CLIENT_GET_CLASS (client)->impl_request_save (client, flags, error); +} + GKeyFile * gsm_client_save (GsmClient *client, GsmApp *app, GError **error) { g_return_val_if_fail (GSM_IS_CLIENT (client), FALSE); 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 index 78cb15d8..19c9cd8d 100644 --- a/gnome-session/gsm-client.h +++ b/gnome-session/gsm-client.h @@ -64,91 +64,97 @@ struct _GsmClient GObject parent; GsmClientPrivate *priv; }; 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); + gboolean (*impl_request_save) (GsmClient *client, + guint flags, + 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); GType gsm_client_get_type (void) G_GNUC_CONST; 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); +gboolean gsm_client_request_save (GsmClient *client, + guint flags, + GError **error); 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-dbus-client.c b/gnome-session/gsm-dbus-client.c index 6e9b002b..5793f830 100644 --- a/gnome-session/gsm-dbus-client.c +++ b/gnome-session/gsm-dbus-client.c @@ -300,60 +300,73 @@ gsm_dbus_client_get_property (GObject *object, case PROP_BUS_NAME: g_value_set_string (value, self->priv->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->priv->bus_name); if (client->priv->skeleton != NULL) { g_dbus_interface_skeleton_unexport_from_connection (G_DBUS_INTERFACE_SKELETON (client->priv->skeleton), client->priv->connection); g_clear_object (&client->priv->skeleton); } g_clear_object (&client->priv->connection); if (client->priv->watch_id != 0) g_bus_unwatch_name (client->priv->watch_id); G_OBJECT_CLASS (gsm_dbus_client_parent_class)->finalize (object); } +static gboolean +dbus_client_request_save (GsmClient *client, + guint flags, + GError **error) +{ + g_debug ("GsmDBusClient: sending save request to client with id %s", + gsm_client_peek_id (client)); + + /* FIXME: The protocol does not support this */ + + return FALSE; +} + 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->priv->skeleton); return TRUE; } static char * dbus_client_get_app_name (GsmClient *client) { /* Always use app-id instead */ return NULL; } @@ -394,60 +407,61 @@ 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->priv->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->priv->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_request_save = dbus_client_request_save; 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)); g_type_class_add_private (klass, sizeof (GsmDBusClientPrivate)); } 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); diff --git a/gnome-session/gsm-manager.c b/gnome-session/gsm-manager.c index 4bb81e04..ede4186a 100644 --- a/gnome-session/gsm-manager.c +++ b/gnome-session/gsm-manager.c @@ -54,60 +54,61 @@ #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" #define GSM_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSM_TYPE_MANAGER, GsmManagerPrivate)) /* 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 GSM_MANAGER_SAVE_SESSION_TIMEOUT 2 #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, @@ -1172,60 +1173,123 @@ end_session_or_show_shell_dialog (GsmManager *manager) 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) { 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 (manager->priv->query_timeout_id > 0) { g_source_remove (manager->priv->query_timeout_id); manager->priv->query_timeout_id = 0; } end_session_or_show_shell_dialog (manager); } +static gboolean +_client_request_save (GsmClient *client, + ClientEndSessionData *data) +{ + gboolean ret; + GError *error; + + error = NULL; + ret = gsm_client_request_save (client, data->flags, &error); + if (ret) { + g_debug ("GsmManager: adding client to query clients: %s", gsm_client_peek_id (client)); + data->manager->priv->query_clients = g_slist_prepend (data->manager->priv->query_clients, + client); + } else if (error) { + g_debug ("GsmManager: unable to query client: %s", error->message); + g_error_free (error); + } + + return FALSE; +} + +static gboolean +_client_request_save_helper (const char *id, + GsmClient *client, + ClientEndSessionData *data) +{ + return _client_request_save (client, data); +} + +static void +query_save_session_complete (GsmManager *manager) +{ + GError *error = NULL; + + if (g_slist_length (manager->priv->next_query_clients) > 0) { + ClientEndSessionData data; + + data.manager = manager; + data.flags = GSM_CLIENT_END_SESSION_FLAG_LAST; + + g_slist_foreach (manager->priv->next_query_clients, + (GFunc)_client_request_save, + &data); + + g_slist_free (manager->priv->next_query_clients); + manager->priv->next_query_clients = NULL; + + return; + } + + if (manager->priv->query_timeout_id > 0) { + g_source_remove (manager->priv->query_timeout_id); + manager->priv->query_timeout_id = 0; + } + + gsm_session_save (manager->priv->clients, &error); + + if (error) { + g_warning ("Error saving session: %s", error->message); + g_error_free (error); + } +} + 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) { guint32 cookie; do { cookie = generate_cookie (); } while (gsm_store_find (manager->priv->inhibitors, (GsmStoreFunc)_find_by_cookie, &cookie) != NULL); return cookie; } static gboolean _on_query_end_session_timeout (GsmManager *manager) { GSList *l; manager->priv->query_timeout_id = 0; g_debug ("GsmManager: query end session timed out"); @@ -1252,60 +1316,75 @@ _on_query_end_session_timeout (GsmManager *manager) 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 (manager->priv->inhibitors, gsm_inhibitor_peek_id (inhibitor), G_OBJECT (inhibitor)); g_object_unref (inhibitor); } g_slist_free (manager->priv->query_clients); manager->priv->query_clients = NULL; query_end_session_complete (manager); return FALSE; } +static gboolean +_on_query_save_session_timeout (GsmManager *manager) +{ + manager->priv->query_timeout_id = 0; + + g_debug ("GsmManager: query to save session timed out"); + + g_slist_free (manager->priv->query_clients); + manager->priv->query_clients = NULL; + + query_save_session_complete (manager); + + return FALSE; +} + static void do_phase_query_end_session (GsmManager *manager) { ClientEndSessionData data; data.manager = manager; data.flags = 0; if (manager->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)", manager->priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_NORMAL? "normal" : manager->priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE? "forceful": "no confirmation"); gsm_store_foreach (manager->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. */ manager->priv->query_timeout_id = g_timeout_add_seconds (1, (GSourceFunc)_on_query_end_session_timeout, manager); } static void @@ -1848,67 +1927,86 @@ maybe_save_session (GsmManager *manager) return; /* We only allow session saving when session is running or when * logging out */ if (manager->priv->phase != GSM_MANAGER_PHASE_RUNNING && manager->priv->phase != GSM_MANAGER_PHASE_END_SESSION) { return; } if (!auto_save_is_enabled (manager)) { return; } error = NULL; gsm_session_save (manager->priv->clients, manager->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) { - /* just ignore if received outside of shutdown */ - if (manager->priv->phase < GSM_MANAGER_PHASE_QUERY_END_SESSION) { + /* just ignore if we are not yet running */ + if (manager->priv->phase < GSM_MANAGER_PHASE_RUNNING) { 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 (manager->priv->phase == GSM_MANAGER_PHASE_RUNNING) { + /* Ignore responses when no requests were sent */ + if (manager->priv->query_clients == NULL) { + return; + } + + manager->priv->query_clients = g_slist_remove (manager->priv->query_clients, client); + + if (do_last) { + manager->priv->next_query_clients = g_slist_prepend (manager->priv->next_query_clients, + client); + } + + if (manager->priv->query_clients == NULL) { + query_save_session_complete (manager); + } + return; + } + if (cancel) { cancel_end_session (manager); return; } manager->priv->query_clients = g_slist_remove (manager->priv->query_clients, client); if (! is_ok && manager->priv->logout_mode != 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); @@ -1968,85 +2066,98 @@ on_client_end_session_response (GsmClient *client, _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_xsmp_client_save_request (GsmXSMPClient *client, + gboolean show_dialog, + GsmManager *manager) +{ + g_debug ("GsmManager: save_request"); + gsm_manager_save_session (manager, NULL); +} + static void on_store_client_added (GsmStore *store, const char *id, GsmManager *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, + "save-request", + G_CALLBACK (on_xsmp_client_save_request), + manager); } g_signal_connect (client, "end-session-response", G_CALLBACK (on_client_end_session_response), manager); gsm_exported_manager_emit_client_added (manager->priv->skeleton, id); /* FIXME: disconnect signal handler */ } static void on_store_client_removed (GsmStore *store, const char *id, GsmManager *manager) { g_debug ("GsmManager: Client removed: %s", id); gsm_exported_manager_emit_client_removed (manager->priv->skeleton, id); } static void gsm_manager_set_client_store (GsmManager *manager, GsmStore *store) { g_return_if_fail (GSM_IS_MANAGER (manager)); if (store != NULL) { g_object_ref (store); } @@ -2625,60 +2736,95 @@ gsm_manager_initialization_error (GsmExportedManager *skeleton, gboolean fatal, GsmManager *manager) { if (manager->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) { if (manager->priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION) { manager->priv->logout_mode = mode; end_session_or_show_shell_dialog (manager); return; } request_logout (manager, mode); } +gboolean +gsm_manager_save_session (GsmManager *manager, + GError **error) +{ + ClientEndSessionData data; + + g_debug ("GsmManager: SaveSession called"); + + g_return_val_if_fail (GSM_IS_MANAGER (manager), FALSE); + + if (manager->priv->phase != GSM_MANAGER_PHASE_RUNNING) { + g_set_error (error, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_NOT_IN_RUNNING, + "SaveSession interface is only available during the Running phase"); + return FALSE; + } + + data.manager = manager; + data.flags = 0; + gsm_store_foreach (manager->priv->clients, + (GsmStoreFunc)_client_request_save_helper, + &data); + + if (manager->priv->query_clients) { + manager->priv->query_timeout_id = g_timeout_add_seconds (GSM_MANAGER_SAVE_SESSION_TIMEOUT, + (GSourceFunc)_on_query_save_session_timeout, + manager); + return TRUE; + } else { + g_debug ("GsmManager: Nothing to save"); + return FALSE; + } +} + gboolean gsm_manager_logout (GsmManager *manager, guint logout_mode, GError **error) { if (manager->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"); diff --git a/gnome-session/gsm-manager.h b/gnome-session/gsm-manager.h index bc440cb0..4d14aa34 100644 --- a/gnome-session/gsm-manager.h +++ b/gnome-session/gsm-manager.h @@ -96,40 +96,43 @@ GQuark gsm_manager_error_quark (void); GType gsm_manager_get_type (void); GsmManager * gsm_manager_new (GsmStore *client_store, gboolean failsafe); GsmManager * gsm_manager_get (void); gboolean gsm_manager_get_failsafe (GsmManager *manager); gboolean gsm_manager_add_autostart_app (GsmManager *manager, const char *path, const char *provides); gboolean gsm_manager_add_required_app (GsmManager *manager, const char *path, const char *provides); 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); const 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_save_session (GsmManager *manager, + GError **error); + 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-xsmp-client.c b/gnome-session/gsm-xsmp-client.c index 8a30926f..cbecd68c 100644 --- a/gnome-session/gsm-xsmp-client.c +++ b/gnome-session/gsm-xsmp-client.c @@ -39,60 +39,61 @@ #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, + SAVE_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; @@ -472,60 +473,84 @@ xsmp_interact (GsmClient *client) 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 gboolean +xsmp_request_save (GsmClient *client, + guint flags, + GError **error) +{ + GsmXSMPClient *xsmp = (GsmXSMPClient *) client; + + g_debug ("GsmXSMPClient: xsmp_request_save ('%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; + } + + if (flags & GSM_CLIENT_END_SESSION_FLAG_LAST) + xsmp_save_yourself_phase2 (client); + else + do_save_yourself (xsmp, SmSaveLocal, 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); @@ -964,100 +989,112 @@ xsmp_get_unix_process_id (GsmClient *client) 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_request_save = xsmp_request_save; 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); + signals[SAVE_REQUEST] = + g_signal_new ("save-request", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsmXSMPClientClass, save_request), + NULL, + NULL, + g_cclosure_marshal_VOID__BOOLEAN, + 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; diff --git a/gnome-session/gsm-xsmp-client.h b/gnome-session/gsm-xsmp-client.h index 1bb27975..6b95c51b 100644 --- a/gnome-session/gsm-xsmp-client.h +++ b/gnome-session/gsm-xsmp-client.h @@ -27,61 +27,62 @@ 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); - + gboolean (*save_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 diff --git a/gnome-session/org.gnome.SessionManager.xml b/gnome-session/org.gnome.SessionManager.xml index 580ec356..29eb0990 100644 --- a/gnome-session/org.gnome.SessionManager.xml +++ b/gnome-session/org.gnome.SessionManager.xml @@ -255,60 +255,68 @@ 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. + + + + Request to save session + + + + 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 The type of logout that is being requested Request a logout dialog Allowed values for the mode parameter are: 0 Normal. diff --git a/meson.build b/meson.build index 9a16d5b1..24221bb6 100644 --- a/meson.build +++ b/meson.build @@ -72,60 +72,61 @@ if enable_deprecation_flags endif compiler_flags = [] if session_debug test_cflags = [ '-Werror=format=2', '-Werror=implicit-function-declaration', '-Werror=init-self', '-Werror=missing-include-dirs', '-Werror=missing-prototypes', '-Werror=pointer-arith', '-Werror=return-type', '-Wnested-externs', '-Wstrict-prototypes' ] compiler_flags += cc.get_supported_arguments(test_cflags) endif add_project_arguments(common_flags + compiler_flags, language: 'c') glib_req_version = '>= 2.46.0' gio_dep = dependency('gio-2.0', version: glib_req_version) glib_dep = dependency('glib-2.0', version: glib_req_version) gtk_dep = dependency('gtk+-3.0', version: '>= 3.18.0') xtrans_dep = dependency('xtrans') ice_dep = dependency('ice') sm_dep = dependency('sm') x11_dep = dependency('x11') +dbus_glib_dep = dependency('dbus-glib-1') session_deps = [ gio_dep, glib_dep, dependency('gnome-desktop-3.0', version: '>= 3.18.0'), dependency('json-glib-1.0', version: '>= 0.10') ] session_bin_deps = session_deps + [ xtrans_dep, ice_dep, sm_dep ] # Check for session selector GTK+ UI enable_session_selector = get_option('session_selector') # Check for session tracking backend session_tracking = 'null backend' enable_systemd = get_option('systemd') enable_systemd_journal = get_option('systemd_journal') enable_consolekit = get_option('consolekit') if enable_systemd or enable_consolekit session_bin_deps += dependency('gio-unix-2.0', version: glib_req_version) # Check for systemd if enable_systemd libsystemd_dep = dependency('libsystemd', version: '>= 209', required: false) session_bin_deps += libsystemd_dep -- 2.17.0