diff options
author | Perberos <[email protected]> | 2011-12-01 23:03:59 -0300 |
---|---|---|
committer | Perberos <[email protected]> | 2011-12-01 23:03:59 -0300 |
commit | 52d7aadcc57f3fa09653d315fc1a7fef52ae6bca (patch) | |
tree | 93f7e38ac79b2592d48d22e6912aeddfd227ffab /mate-session/gsm-xsmp-client.c | |
download | mate-session-manager-52d7aadcc57f3fa09653d315fc1a7fef52ae6bca.tar.bz2 mate-session-manager-52d7aadcc57f3fa09653d315fc1a7fef52ae6bca.tar.xz |
moving from https://github.com/perberos/mate-desktop-environment
Diffstat (limited to 'mate-session/gsm-xsmp-client.c')
-rw-r--r-- | mate-session/gsm-xsmp-client.c | 1332 |
1 files changed, 1332 insertions, 0 deletions
diff --git a/mate-session/gsm-xsmp-client.c b/mate-session/gsm-xsmp-client.c new file mode 100644 index 0000000..ce0c92f --- /dev/null +++ b/mate-session/gsm-xsmp-client.c @@ -0,0 +1,1332 @@ +/* -*- 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include "config.h" + +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <errno.h> + +#include <gio/gio.h> +#include <glib/gi18n.h> + +#include "gsm-xsmp-client.h" +#include "gsm-marshal.h" + +#include "gsm-util.h" +#include "gsm-autostart-app.h" +#include "gsm-manager.h" + +#define GsmDesktopFile "_GSM_DesktopFile" + +#define IS_STRING_EMPTY(x) ((x)==NULL||(x)[0]=='\0') + +#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, + 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; + char **dirs; + 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); + program_name = prop->vals[0].value; + + dirs = gsm_util_get_autostart_dirs (); + + desktop_file_path = + gsm_util_find_desktop_file_for_app_name (program_name, + dirs); + + g_strfreev (dirs); + +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; + char *name; + char *comment; + + prop = find_property (GSM_XSMP_CLIENT (client), SmProgram, NULL); + name = g_strdup (prop->vals[0].value); + + 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, + "system-run"); + + 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 (name); + 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, + 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)); + + 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); + +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, + guint 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, + guint 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; + + prop = find_property (GSM_XSMP_CLIENT (client), SmProgram, NULL); + 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, + gsm_marshal_BOOLEAN__POINTER, + G_TYPE_BOOLEAN, + 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, + 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; + + 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_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 mate-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)); +} |