]> www.fi.muni.cz Git - evince.git/blob - cut-n-paste/smclient/eggsmclient-xsmp.c
Update smclient from libegg trunk.
[evince.git] / cut-n-paste / smclient / eggsmclient-xsmp.c
1 /*
2  * Copyright (C) 2007 Novell, Inc.
3  *
4  * Inspired by various other pieces of code including GsmClient (C)
5  * 2001 Havoc Pennington, GnomeClient (C) 1998 Carsten Schaar, and twm
6  * session code (C) 1998 The Open Group.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA 02111-1307, USA.
22  */
23
24 #include "config.h"
25
26 #include "eggsmclient.h"
27 #include "eggsmclient-private.h"
28
29 #include "eggdesktopfile.h"
30
31 #include <errno.h>
32 #include <fcntl.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <unistd.h>
36 #include <X11/SM/SMlib.h>
37
38 #include <gdk/gdk.h>
39
40 #define EGG_TYPE_SM_CLIENT_XSMP            (egg_sm_client_xsmp_get_type ())
41 #define EGG_SM_CLIENT_XSMP(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT_XSMP, EggSMClientXSMP))
42 #define EGG_SM_CLIENT_XSMP_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT_XSMP, EggSMClientXSMPClass))
43 #define EGG_IS_SM_CLIENT_XSMP(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT_XSMP))
44 #define EGG_IS_SM_CLIENT_XSMP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT_XSMP))
45 #define EGG_SM_CLIENT_XSMP_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT_XSMP, EggSMClientXSMPClass))
46
47 typedef struct _EggSMClientXSMP        EggSMClientXSMP;
48 typedef struct _EggSMClientXSMPClass   EggSMClientXSMPClass;
49
50 /* These mostly correspond to the similarly-named states in section
51  * 9.1 of the XSMP spec. Some of the states there aren't represented
52  * here, because we don't need them. SHUTDOWN_CANCELLED is slightly
53  * different from the spec; we use it when the client is IDLE after a
54  * ShutdownCancelled message, but the application is still interacting
55  * and doesn't know the shutdown has been cancelled yet.
56  */
57 typedef enum
58 {
59   XSMP_STATE_IDLE,
60   XSMP_STATE_SAVE_YOURSELF,
61   XSMP_STATE_INTERACT_REQUEST,
62   XSMP_STATE_INTERACT,
63   XSMP_STATE_SAVE_YOURSELF_DONE,
64   XSMP_STATE_SHUTDOWN_CANCELLED,
65   XSMP_STATE_CONNECTION_CLOSED
66 } EggSMClientXSMPState;
67
68 static const char *state_names[] = {
69   "idle",
70   "save-yourself",
71   "interact-request",
72   "interact",
73   "save-yourself-done",
74   "shutdown-cancelled",
75   "connection-closed"
76 };
77
78 #define EGG_SM_CLIENT_XSMP_STATE(xsmp) (state_names[(xsmp)->state])
79
80 struct _EggSMClientXSMP
81 {
82   EggSMClient parent;
83
84   SmcConn connection;
85   char *client_id;
86
87   EggSMClientXSMPState state;
88   char **restart_command;
89   gboolean set_restart_command;
90   int restart_style;
91
92   guint idle;
93
94   /* Current SaveYourself state */
95   guint expecting_initial_save_yourself : 1;
96   guint need_save_state : 1;
97   guint need_quit_requested : 1;
98   guint interact_errors : 1;
99   guint shutting_down : 1;
100
101   /* Todo list */
102   guint waiting_to_set_initial_properties : 1;
103   guint waiting_to_emit_quit : 1;
104   guint waiting_to_emit_quit_cancelled : 1;
105   guint waiting_to_save_myself : 1;
106
107 };
108
109 struct _EggSMClientXSMPClass
110 {
111   EggSMClientClass parent_class;
112
113 };
114
115 static void     sm_client_xsmp_startup (EggSMClient *client,
116                                         const char  *client_id);
117 static void     sm_client_xsmp_set_restart_command (EggSMClient  *client,
118                                                     int           argc,
119                                                     const char  **argv);
120 static void     sm_client_xsmp_will_quit (EggSMClient *client,
121                                           gboolean     will_quit);
122 static gboolean sm_client_xsmp_end_session (EggSMClient         *client,
123                                             EggSMClientEndStyle  style,
124                                             gboolean  request_confirmation);
125
126 static void xsmp_save_yourself      (SmcConn   smc_conn,
127                                      SmPointer client_data,
128                                      int       save_style,
129                                      Bool      shutdown,
130                                      int       interact_style,
131                                      Bool      fast);
132 static void xsmp_die                (SmcConn   smc_conn,
133                                      SmPointer client_data);
134 static void xsmp_save_complete      (SmcConn   smc_conn,
135                                      SmPointer client_data);
136 static void xsmp_shutdown_cancelled (SmcConn   smc_conn,
137                                      SmPointer client_data);
138 static void xsmp_interact           (SmcConn   smc_conn,
139                                      SmPointer client_data);
140
141 static SmProp *array_prop        (const char    *name,
142                                   ...);
143 static SmProp *ptrarray_prop     (const char    *name,
144                                   GPtrArray     *values);
145 static SmProp *string_prop       (const char    *name,
146                                   const char    *value);
147 static SmProp *card8_prop        (const char    *name,
148                                   unsigned char  value);
149
150 static void set_properties         (EggSMClientXSMP *xsmp, ...);
151 static void delete_properties      (EggSMClientXSMP *xsmp, ...);
152
153 static GPtrArray *generate_command (char       **restart_command,
154                                     const char  *client_id,
155                                     const char  *state_file);
156
157 static void save_state            (EggSMClientXSMP *xsmp);
158 static void do_save_yourself      (EggSMClientXSMP *xsmp);
159 static void update_pending_events (EggSMClientXSMP *xsmp);
160
161 static void     ice_init             (void);
162 static gboolean process_ice_messages (IceConn       ice_conn);
163 static void     smc_error_handler    (SmcConn       smc_conn,
164                                       Bool          swap,
165                                       int           offending_minor_opcode,
166                                       unsigned long offending_sequence,
167                                       int           error_class,
168                                       int           severity,
169                                       SmPointer     values);
170
171 G_DEFINE_TYPE (EggSMClientXSMP, egg_sm_client_xsmp, EGG_TYPE_SM_CLIENT)
172
173 static void
174 egg_sm_client_xsmp_init (EggSMClientXSMP *xsmp)
175 {
176   xsmp->state = XSMP_STATE_CONNECTION_CLOSED;
177   xsmp->connection = NULL;
178   xsmp->restart_style = SmRestartIfRunning;
179 }
180
181 static void
182 egg_sm_client_xsmp_class_init (EggSMClientXSMPClass *klass)
183 {
184   EggSMClientClass *sm_client_class = EGG_SM_CLIENT_CLASS (klass);
185
186   sm_client_class->startup             = sm_client_xsmp_startup;
187   sm_client_class->set_restart_command = sm_client_xsmp_set_restart_command;
188   sm_client_class->will_quit           = sm_client_xsmp_will_quit;
189   sm_client_class->end_session         = sm_client_xsmp_end_session;
190 }
191
192 EggSMClient *
193 egg_sm_client_xsmp_new (void)
194 {
195   if (!g_getenv ("SESSION_MANAGER"))
196     return NULL;
197
198   return g_object_new (EGG_TYPE_SM_CLIENT_XSMP, NULL);
199 }
200
201 static gboolean
202 sm_client_xsmp_set_initial_properties (gpointer user_data)
203 {
204   EggSMClientXSMP *xsmp = user_data;
205   EggDesktopFile *desktop_file;
206   GPtrArray *clone, *restart;
207   char pid_str[64];
208
209   if (xsmp->idle)
210     {
211       g_source_remove (xsmp->idle);
212       xsmp->idle = 0;
213     }
214   xsmp->waiting_to_set_initial_properties = FALSE;
215
216   if (egg_sm_client_get_mode () == EGG_SM_CLIENT_MODE_NO_RESTART)
217     xsmp->restart_style = SmRestartNever;
218
219   /* Parse info out of desktop file */
220   desktop_file = egg_get_desktop_file ();
221   if (desktop_file)
222     {
223       GError *err = NULL;
224       char *cmdline, **argv;
225       int argc;
226
227       if (xsmp->restart_style == SmRestartIfRunning)
228         {
229           if (egg_desktop_file_get_boolean (desktop_file, 
230                                             "X-GNOME-AutoRestart", NULL))
231             xsmp->restart_style = SmRestartImmediately;
232         }
233
234       if (!xsmp->set_restart_command)
235         {
236           cmdline = egg_desktop_file_parse_exec (desktop_file, NULL, &err);
237           if (cmdline && g_shell_parse_argv (cmdline, &argc, &argv, &err))
238             {
239               egg_sm_client_set_restart_command (EGG_SM_CLIENT (xsmp),
240                                                  argc, (const char **)argv);
241               g_strfreev (argv);
242             }
243           else
244             {
245               g_warning ("Could not parse Exec line in desktop file: %s",
246                          err->message);
247               g_error_free (err);
248             }
249           g_free (cmdline);
250         }
251     }
252
253   if (!xsmp->set_restart_command)
254     xsmp->restart_command = g_strsplit (g_get_prgname (), " ", -1);
255
256   clone = generate_command (xsmp->restart_command, NULL, NULL);
257   restart = generate_command (xsmp->restart_command, xsmp->client_id, NULL);
258
259   g_debug ("Setting initial properties");
260
261   /* Program, CloneCommand, RestartCommand, and UserID are required.
262    * ProcessID isn't required, but the SM may be able to do something
263    * useful with it.
264    */
265   g_snprintf (pid_str, sizeof (pid_str), "%lu", (gulong) getpid ());
266   set_properties (xsmp,
267                   string_prop   (SmProgram, g_get_prgname ()),
268                   ptrarray_prop (SmCloneCommand, clone),
269                   ptrarray_prop (SmRestartCommand, restart),
270                   string_prop   (SmUserID, g_get_user_name ()),
271                   string_prop   (SmProcessID, pid_str),
272                   card8_prop    (SmRestartStyleHint, xsmp->restart_style),
273                   NULL);
274   g_ptr_array_free (clone, TRUE);
275   g_ptr_array_free (restart, TRUE);
276
277   if (desktop_file)
278     {
279       set_properties (xsmp,
280                       string_prop ("_GSM_DesktopFile", egg_desktop_file_get_source (desktop_file)),
281                       NULL);
282     }
283
284   update_pending_events (xsmp);
285   return FALSE;
286 }
287
288 /* This gets called from two different places: xsmp_die() (when the
289  * server asks us to disconnect) and process_ice_messages() (when the
290  * server disconnects unexpectedly).
291  */
292 static void
293 sm_client_xsmp_disconnect (EggSMClientXSMP *xsmp)
294 {
295   SmcConn connection;
296
297   if (!xsmp->connection)
298     return;
299
300   g_debug ("Disconnecting");
301
302   connection = xsmp->connection;
303   xsmp->connection = NULL;
304   SmcCloseConnection (connection, 0, NULL);
305   xsmp->state = XSMP_STATE_CONNECTION_CLOSED;
306
307   xsmp->waiting_to_save_myself = FALSE;
308   update_pending_events (xsmp);
309 }
310
311 static void
312 sm_client_xsmp_startup (EggSMClient *client,
313                         const char  *client_id)
314 {
315   EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client;
316   SmcCallbacks callbacks;
317   char *ret_client_id;
318   char error_string_ret[256];
319
320   xsmp->client_id = g_strdup (client_id);
321
322   ice_init ();
323   SmcSetErrorHandler (smc_error_handler);
324
325   callbacks.save_yourself.callback      = xsmp_save_yourself;
326   callbacks.die.callback                = xsmp_die;
327   callbacks.save_complete.callback      = xsmp_save_complete;
328   callbacks.shutdown_cancelled.callback = xsmp_shutdown_cancelled;
329
330   callbacks.save_yourself.client_data      = xsmp;
331   callbacks.die.client_data                = xsmp;
332   callbacks.save_complete.client_data      = xsmp;
333   callbacks.shutdown_cancelled.client_data = xsmp;
334
335   client_id = NULL;
336   error_string_ret[0] = '\0';
337   xsmp->connection =
338     SmcOpenConnection (NULL, xsmp, SmProtoMajor, SmProtoMinor,
339                        SmcSaveYourselfProcMask | SmcDieProcMask |
340                        SmcSaveCompleteProcMask |
341                        SmcShutdownCancelledProcMask,
342                        &callbacks,
343                        xsmp->client_id, &ret_client_id,
344                        sizeof (error_string_ret), error_string_ret);
345
346   if (!xsmp->connection)
347     {
348       g_warning ("Failed to connect to the session manager: %s\n",
349                  error_string_ret[0] ?
350                  error_string_ret : "no error message given");
351       xsmp->state = XSMP_STATE_CONNECTION_CLOSED;
352       return;
353     }
354
355   /* We expect a pointless initial SaveYourself if either (a) we
356    * didn't have an initial client ID, or (b) we DID have an initial
357    * client ID, but the server rejected it and gave us a new one.
358    */
359   if (!xsmp->client_id ||
360       (ret_client_id && strcmp (xsmp->client_id, ret_client_id) != 0))
361     xsmp->expecting_initial_save_yourself = TRUE;
362
363   if (ret_client_id)
364     {
365       g_free (xsmp->client_id);
366       xsmp->client_id = g_strdup (ret_client_id);
367       free (ret_client_id);
368
369       gdk_threads_enter ();
370       gdk_set_sm_client_id (xsmp->client_id);
371       gdk_threads_leave ();
372
373       g_debug ("Got client ID \"%s\"", xsmp->client_id);
374     }
375
376   xsmp->state = XSMP_STATE_IDLE;
377
378   /* Do not set the initial properties until we reach the main loop,
379    * so that the application has a chance to call
380    * egg_set_desktop_file(). (This may also help the session manager
381    * have a better idea of when the application is fully up and
382    * running.)
383    */
384   xsmp->waiting_to_set_initial_properties = TRUE;
385   xsmp->idle = g_idle_add (sm_client_xsmp_set_initial_properties, client);
386 }
387
388 static void
389 sm_client_xsmp_set_restart_command (EggSMClient  *client,
390                                     int           argc,
391                                     const char  **argv)
392 {
393   EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client;
394   int i;
395
396   g_strfreev (xsmp->restart_command);
397
398   xsmp->restart_command = g_new (char *, argc + 1);
399   for (i = 0; i < argc; i++)
400     xsmp->restart_command[i] = g_strdup (argv[i]);
401   xsmp->restart_command[i] = NULL;
402
403   xsmp->set_restart_command = TRUE;
404 }
405
406 static void
407 sm_client_xsmp_will_quit (EggSMClient *client,
408                           gboolean     will_quit)
409 {
410   EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client;
411
412   if (xsmp->state == XSMP_STATE_CONNECTION_CLOSED)
413     {
414       /* The session manager has already exited! Schedule a quit
415        * signal.
416        */
417       xsmp->waiting_to_emit_quit = TRUE;
418       update_pending_events (xsmp);
419       return;
420     }
421   else if (xsmp->state == XSMP_STATE_SHUTDOWN_CANCELLED)
422     {
423       /* We received a ShutdownCancelled message while the application
424        * was interacting; Schedule a quit_cancelled signal.
425        */
426       xsmp->waiting_to_emit_quit_cancelled = TRUE;
427       update_pending_events (xsmp);
428       return;
429     }
430
431   g_return_if_fail (xsmp->state == XSMP_STATE_INTERACT);
432
433   g_debug ("Sending InteractDone(%s)", will_quit ? "False" : "True");
434   SmcInteractDone (xsmp->connection, !will_quit);
435
436   if (will_quit && xsmp->need_save_state)
437     save_state (xsmp);
438
439   g_debug ("Sending SaveYourselfDone(%s)", will_quit ? "True" : "False");
440   SmcSaveYourselfDone (xsmp->connection, will_quit);
441   xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE;
442 }
443
444 static gboolean
445 sm_client_xsmp_end_session (EggSMClient         *client,
446                             EggSMClientEndStyle  style,
447                             gboolean             request_confirmation)
448 {
449   EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client;
450   int save_type;
451
452   /* To end the session via XSMP, we have to send a
453    * SaveYourselfRequest. We aren't allowed to do that if anything
454    * else is going on, but we don't want to expose this fact to the
455    * application. So we do our best to patch things up here...
456    *
457    * In the worst case, this method might block for some length of
458    * time in process_ice_messages, but the only time that code path is
459    * honestly likely to get hit is if the application tries to end the
460    * session as the very first thing it does, in which case it
461    * probably won't actually block anyway. It's not worth gunking up
462    * the API to try to deal nicely with the other 0.01% of cases where
463    * this happens.
464    */
465
466   while (xsmp->state != XSMP_STATE_IDLE ||
467          xsmp->expecting_initial_save_yourself)
468     {
469       /* If we're already shutting down, we don't need to do anything. */
470       if (xsmp->shutting_down)
471         return TRUE;
472
473       switch (xsmp->state)
474         {
475         case XSMP_STATE_CONNECTION_CLOSED:
476           return FALSE;
477
478         case XSMP_STATE_SAVE_YOURSELF:
479           /* Trying to log out from the save_state callback? Whatever.
480            * Abort the save_state.
481            */
482           SmcSaveYourselfDone (xsmp->connection, FALSE);
483           xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE;
484           break;
485
486         case XSMP_STATE_INTERACT_REQUEST:
487         case XSMP_STATE_INTERACT:
488         case XSMP_STATE_SHUTDOWN_CANCELLED:
489           /* Already in a shutdown-related state, just ignore
490            * the new shutdown request...
491            */
492           return TRUE;
493
494         case XSMP_STATE_IDLE:
495           if (xsmp->waiting_to_set_initial_properties)
496             sm_client_xsmp_set_initial_properties (xsmp);
497
498           if (!xsmp->expecting_initial_save_yourself)
499             break;
500           /* else fall through */
501
502         case XSMP_STATE_SAVE_YOURSELF_DONE:
503           /* We need to wait for some response from the server.*/
504           process_ice_messages (SmcGetIceConnection (xsmp->connection));
505           break;
506
507         default:
508           /* Hm... shouldn't happen */
509           return FALSE;
510         }
511     }
512
513   /* xfce4-session will do the wrong thing if we pass SmSaveGlobal and
514    * the user chooses to save the session. But gnome-session will do
515    * the wrong thing if we pass SmSaveBoth and the user chooses NOT to
516    * save the session... Sigh.
517    */
518   if (!strcmp (SmcVendor (xsmp->connection), "xfce4-session"))
519     save_type = SmSaveBoth;
520   else
521     save_type = SmSaveGlobal;
522
523   g_debug ("Sending SaveYourselfRequest(SmSaveGlobal, Shutdown, SmInteractStyleAny, %sFast)", request_confirmation ? "!" : "");
524   SmcRequestSaveYourself (xsmp->connection,
525                           save_type,
526                           True, /* shutdown */
527                           SmInteractStyleAny,
528                           !request_confirmation, /* fast */
529                           True /* global */);
530   return TRUE;
531 }
532
533 static gboolean
534 idle_do_pending_events (gpointer data)
535 {
536   EggSMClientXSMP *xsmp = data;
537   EggSMClient *client = data;
538
539   gdk_threads_enter ();
540
541   xsmp->idle = 0;
542
543   if (xsmp->waiting_to_emit_quit)
544     {
545       xsmp->waiting_to_emit_quit = FALSE;
546       egg_sm_client_quit (client);
547       goto out;
548     }
549
550   if (xsmp->waiting_to_emit_quit_cancelled)
551     {
552       xsmp->waiting_to_emit_quit_cancelled = FALSE;
553       egg_sm_client_quit_cancelled (client);
554       xsmp->state = XSMP_STATE_IDLE;
555     }
556
557   if (xsmp->waiting_to_save_myself)
558     {
559       xsmp->waiting_to_save_myself = FALSE;
560       do_save_yourself (xsmp);
561     }
562
563  out:
564   gdk_threads_leave ();
565   return FALSE;
566 }
567
568 static void
569 update_pending_events (EggSMClientXSMP *xsmp)
570 {
571   gboolean want_idle =
572     xsmp->waiting_to_emit_quit ||
573     xsmp->waiting_to_emit_quit_cancelled ||
574     xsmp->waiting_to_save_myself;
575
576   if (want_idle)
577     {
578       if (xsmp->idle == 0)
579         xsmp->idle = g_idle_add (idle_do_pending_events, xsmp);
580     }
581   else
582     {
583       if (xsmp->idle != 0)
584         g_source_remove (xsmp->idle);
585       xsmp->idle = 0;
586     }
587 }
588
589 static void
590 fix_broken_state (EggSMClientXSMP *xsmp, const char *message,
591                   gboolean send_interact_done,
592                   gboolean send_save_yourself_done)
593 {
594   g_warning ("Received XSMP %s message in state %s: client or server error",
595              message, EGG_SM_CLIENT_XSMP_STATE (xsmp));
596
597   /* Forget any pending SaveYourself plans we had */
598   xsmp->waiting_to_save_myself = FALSE;
599   update_pending_events (xsmp);
600
601   if (send_interact_done)
602     SmcInteractDone (xsmp->connection, False);
603   if (send_save_yourself_done)
604     SmcSaveYourselfDone (xsmp->connection, True);
605
606   xsmp->state = send_save_yourself_done ? XSMP_STATE_SAVE_YOURSELF_DONE : XSMP_STATE_IDLE;
607 }
608
609 /* SM callbacks */
610
611 static void
612 xsmp_save_yourself (SmcConn   smc_conn,
613                     SmPointer client_data,
614                     int       save_type,
615                     Bool      shutdown,
616                     int       interact_style,
617                     Bool      fast)
618 {
619   EggSMClientXSMP *xsmp = client_data;
620   gboolean wants_quit_requested;
621
622   g_debug ("Received SaveYourself(%s, %s, %s, %s) in state %s",
623            save_type == SmSaveLocal ? "SmSaveLocal" :
624            save_type == SmSaveGlobal ? "SmSaveGlobal" : "SmSaveBoth",
625            shutdown ? "Shutdown" : "!Shutdown",
626            interact_style == SmInteractStyleAny ? "SmInteractStyleAny" :
627            interact_style == SmInteractStyleErrors ? "SmInteractStyleErrors" :
628            "SmInteractStyleNone", fast ? "Fast" : "!Fast",
629            EGG_SM_CLIENT_XSMP_STATE (xsmp));
630
631   if (xsmp->state != XSMP_STATE_IDLE &&
632       xsmp->state != XSMP_STATE_SHUTDOWN_CANCELLED)
633     {
634       fix_broken_state (xsmp, "SaveYourself", FALSE, TRUE);
635       return;
636     }
637
638   if (xsmp->waiting_to_set_initial_properties)
639     sm_client_xsmp_set_initial_properties (xsmp);
640
641   /* If this is the initial SaveYourself, ignore it; we've already set
642    * properties and there's no reason to actually save state too.
643    */
644   if (xsmp->expecting_initial_save_yourself)
645     {
646       xsmp->expecting_initial_save_yourself = FALSE;
647
648       if (save_type == SmSaveLocal &&
649           interact_style == SmInteractStyleNone &&
650           !shutdown && !fast)
651         {
652           g_debug ("Sending SaveYourselfDone(True) for initial SaveYourself");
653           SmcSaveYourselfDone (xsmp->connection, True);
654           /* As explained in the comment at the end of
655            * do_save_yourself(), SAVE_YOURSELF_DONE is the correct
656            * state here, not IDLE.
657            */
658           xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE;
659           return;
660         }
661       else
662         g_warning ("First SaveYourself was not the expected one!");
663     }
664
665   /* Even ignoring the "fast" flag completely, there are still 18
666    * different combinations of save_type, shutdown and interact_style.
667    * We interpret them as follows:
668    *
669    *   Type  Shutdown  Interact  Interpretation
670    *     G      F       A/E/N    do nothing (1)
671    *     G      T         N      do nothing (1)*
672    *     G      T        A/E     quit_requested (2)
673    *    L/B     F       A/E/N    save_state (3)
674    *    L/B     T         N      save_state (3)*
675    *    L/B     T        A/E     quit_requested, then save_state (4)
676    *
677    *   1. Do nothing, because the SM asked us to do something
678    *      uninteresting (save open files, but then don't quit
679    *      afterward) or rude (save open files without asking the user
680    *      for confirmation).
681    *
682    *   2. Request interaction and then emit ::quit_requested. This
683    *      perhaps isn't quite correct for the SmInteractStyleErrors
684    *      case, but we don't care.
685    *
686    *   3. Emit ::save_state. The SmSaveBoth SaveYourselfs in these
687    *      rows essentially get demoted to SmSaveLocal, because their
688    *      Global halves correspond to "do nothing".
689    *
690    *   4. Request interaction, emit ::quit_requested, and then emit
691    *      ::save_state after interacting. This is the SmSaveBoth
692    *      equivalent of #2, but we also promote SmSaveLocal shutdown
693    *      SaveYourselfs to SmSaveBoth here, because we want to give
694    *      the user a chance to save open files before quitting.
695    *
696    * (* It would be nice if we could do something useful when the
697    * session manager sends a SaveYourself with shutdown True and
698    * SmInteractStyleNone. But we can't, so we just pretend it didn't
699    * even tell us it was shutting down. The docs for ::quit mention
700    * that it might not always be preceded by ::quit_requested.)
701    */
702
703   /* As an optimization, we don't actually request interaction and
704    * emit ::quit_requested if the application isn't listening to the
705    * signal.
706    */
707   wants_quit_requested = g_signal_has_handler_pending (xsmp, g_signal_lookup ("quit_requested", EGG_TYPE_SM_CLIENT), 0, FALSE);
708
709   xsmp->need_save_state     = (save_type != SmSaveGlobal);
710   xsmp->need_quit_requested = (shutdown && wants_quit_requested &&
711                                interact_style != SmInteractStyleNone);
712   xsmp->interact_errors     = (interact_style == SmInteractStyleErrors);
713
714   xsmp->shutting_down       = shutdown;
715
716   do_save_yourself (xsmp);
717 }
718
719 static void
720 do_save_yourself (EggSMClientXSMP *xsmp)
721 {
722   if (xsmp->state == XSMP_STATE_SHUTDOWN_CANCELLED)
723     {
724       /* The SM cancelled a previous SaveYourself, but we haven't yet
725        * had a chance to tell the application, so we can't start
726        * processing this SaveYourself yet.
727        */
728       xsmp->waiting_to_save_myself = TRUE;
729       update_pending_events (xsmp);
730       return;
731     }
732
733   if (xsmp->need_quit_requested)
734     {
735       xsmp->state = XSMP_STATE_INTERACT_REQUEST;
736
737       g_debug ("Sending InteractRequest(%s)",
738                xsmp->interact_errors ? "Error" : "Normal");
739       SmcInteractRequest (xsmp->connection,
740                           xsmp->interact_errors ? SmDialogError : SmDialogNormal,
741                           xsmp_interact,
742                           xsmp);
743       return;
744     }
745
746   if (xsmp->need_save_state)
747     {
748       save_state (xsmp);
749
750       /* Though unlikely, the client could have been disconnected
751        * while the application was saving its state.
752        */
753       if (!xsmp->connection)
754          return;
755     }
756
757   g_debug ("Sending SaveYourselfDone(True)");
758   SmcSaveYourselfDone (xsmp->connection, True);
759
760   /* The client state diagram in the XSMP spec says that after a
761    * non-shutdown SaveYourself, we go directly back to "idle". But
762    * everything else in both the XSMP spec and the libSM docs
763    * disagrees.
764    */
765   xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE;
766 }
767
768 static void
769 save_state (EggSMClientXSMP *xsmp)
770 {
771   GKeyFile *state_file;
772   char *state_file_path, *data;
773   EggDesktopFile *desktop_file;
774   GPtrArray *restart;
775   int offset, fd;
776
777   /* We set xsmp->state before emitting save_state, but our caller is
778    * responsible for setting it back afterward.
779    */
780   xsmp->state = XSMP_STATE_SAVE_YOURSELF;
781
782   state_file = egg_sm_client_save_state ((EggSMClient *)xsmp);
783   if (!state_file)
784     {
785       restart = generate_command (xsmp->restart_command, xsmp->client_id, NULL);
786       set_properties (xsmp,
787                       ptrarray_prop (SmRestartCommand, restart),
788                       NULL);
789       g_ptr_array_free (restart, TRUE);
790       delete_properties (xsmp, SmDiscardCommand, NULL);
791       return;
792     }
793
794   desktop_file = egg_get_desktop_file ();
795   if (desktop_file)
796     {
797       GKeyFile *merged_file;
798
799       merged_file = g_key_file_new ();
800       if (g_key_file_load_from_file (merged_file,
801                                      egg_desktop_file_get_source (desktop_file),
802                                      G_KEY_FILE_KEEP_COMMENTS |
803                                      G_KEY_FILE_KEEP_TRANSLATIONS, NULL))
804         {
805           guint g, k, i;
806           char **groups, **keys, *value, *exec;
807
808           groups = g_key_file_get_groups (state_file, NULL);
809           for (g = 0; groups[g]; g++)
810             {
811               keys = g_key_file_get_keys (state_file, groups[g], NULL, NULL);
812               for (k = 0; keys[k]; k++)
813                 {
814                   value = g_key_file_get_value (state_file, groups[g],
815                                                 keys[k], NULL);
816                   if (value)
817                     {
818                       g_key_file_set_value (merged_file, groups[g],
819                                             keys[k], value);
820                       g_free (value);
821                     }
822                 }
823               g_strfreev (keys);
824             }
825           g_strfreev (groups);
826
827           g_key_file_free (state_file);
828           state_file = merged_file;
829
830           /* Update Exec key using "--sm-client-state-file %k" */
831           restart = generate_command (xsmp->restart_command,
832                                       NULL, "%k");
833           for (i = 0; i < restart->len; i++)
834             restart->pdata[i] = g_shell_quote (restart->pdata[i]);
835           g_ptr_array_add (restart, NULL);
836           exec = g_strjoinv (" ", (char **)restart->pdata);
837           g_strfreev ((char **)restart->pdata);
838           g_ptr_array_free (restart, FALSE);
839
840           g_key_file_set_string (state_file, EGG_DESKTOP_FILE_GROUP,
841                                  EGG_DESKTOP_FILE_KEY_EXEC,
842                                  exec);
843           g_free (exec);
844
845         }
846     }
847
848   /* Now write state_file to disk. (We can't use mktemp(), because
849    * that requires the filename to end with "XXXXXX", and we want
850    * it to end with ".desktop".)
851    */
852
853   data = g_key_file_to_data (state_file, NULL, NULL);
854   g_key_file_free (state_file);
855
856   offset = 0;
857   while (1)
858     {
859       state_file_path = g_strdup_printf ("%s%csession-state%c%s-%ld.%s",
860                                          g_get_user_config_dir (),
861                                          G_DIR_SEPARATOR, G_DIR_SEPARATOR,
862                                          g_get_prgname (),
863                                          (long)time (NULL) + offset,
864                                          desktop_file ? "desktop" : "state");
865
866       fd = open (state_file_path, O_WRONLY | O_CREAT | O_EXCL, 0644);
867       if (fd == -1)
868         {
869           if (errno == EEXIST)
870             {
871               offset++;
872               g_free (state_file_path);
873               continue;
874             }
875           else if (errno == ENOTDIR || errno == ENOENT)
876             {
877               char *sep = strrchr (state_file_path, G_DIR_SEPARATOR);
878
879               *sep = '\0';
880               if (g_mkdir_with_parents (state_file_path, 0755) != 0)
881                 {
882                   g_warning ("Could not create directory '%s'",
883                              state_file_path);
884                   g_free (state_file_path);
885                   state_file_path = NULL;
886                   break;
887                 }
888
889               continue;
890             }
891
892           g_warning ("Could not create file '%s': %s",
893                      state_file_path, g_strerror (errno));
894           g_free (state_file_path);
895           state_file_path = NULL;
896           break;
897         }
898
899       close (fd);
900       g_file_set_contents (state_file_path, data, -1, NULL);
901       break;
902     }
903   g_free (data);
904
905   restart = generate_command (xsmp->restart_command, xsmp->client_id,
906                               state_file_path);
907   set_properties (xsmp,
908                   ptrarray_prop (SmRestartCommand, restart),
909                   NULL);
910   g_ptr_array_free (restart, TRUE);
911
912   if (state_file_path)
913     {
914       set_properties (xsmp,
915                       array_prop (SmDiscardCommand,
916                                   "/bin/rm", "-rf", state_file_path,
917                                   NULL),
918                       NULL);
919       g_free (state_file_path);
920     }
921 }
922
923 static void
924 xsmp_interact (SmcConn   smc_conn,
925                SmPointer client_data)
926 {
927   EggSMClientXSMP *xsmp = client_data;
928   EggSMClient *client = client_data;
929
930   g_debug ("Received Interact message in state %s",
931            EGG_SM_CLIENT_XSMP_STATE (xsmp));
932
933   if (xsmp->state != XSMP_STATE_INTERACT_REQUEST)
934     {
935       fix_broken_state (xsmp, "Interact", TRUE, TRUE);
936       return;
937     }
938
939   xsmp->state = XSMP_STATE_INTERACT;
940   egg_sm_client_quit_requested (client);
941 }
942
943 static void
944 xsmp_die (SmcConn   smc_conn,
945           SmPointer client_data)
946 {
947   EggSMClientXSMP *xsmp = client_data;
948   EggSMClient *client = client_data;
949
950   g_debug ("Received Die message in state %s",
951            EGG_SM_CLIENT_XSMP_STATE (xsmp));
952
953   sm_client_xsmp_disconnect (xsmp);
954   egg_sm_client_quit (client);
955 }
956
957 static void
958 xsmp_save_complete (SmcConn   smc_conn,
959                     SmPointer client_data)
960 {
961   EggSMClientXSMP *xsmp = client_data;
962
963   g_debug ("Received SaveComplete message in state %s",
964            EGG_SM_CLIENT_XSMP_STATE (xsmp));
965
966   if (xsmp->state == XSMP_STATE_SAVE_YOURSELF_DONE)
967     xsmp->state = XSMP_STATE_IDLE;
968   else
969     fix_broken_state (xsmp, "SaveComplete", FALSE, FALSE);
970 }
971
972 static void
973 xsmp_shutdown_cancelled (SmcConn   smc_conn,
974                          SmPointer client_data)
975 {
976   EggSMClientXSMP *xsmp = client_data;
977   EggSMClient *client = client_data;
978
979   g_debug ("Received ShutdownCancelled message in state %s",
980            EGG_SM_CLIENT_XSMP_STATE (xsmp));
981
982   xsmp->shutting_down = FALSE;
983
984   if (xsmp->state == XSMP_STATE_SAVE_YOURSELF_DONE)
985     {
986       /* We've finished interacting and now the SM has agreed to
987        * cancel the shutdown.
988        */
989       xsmp->state = XSMP_STATE_IDLE;
990       egg_sm_client_quit_cancelled (client);
991     }
992   else if (xsmp->state == XSMP_STATE_SHUTDOWN_CANCELLED)
993     {
994       /* Hm... ok, so we got a shutdown SaveYourself, which got
995        * cancelled, but the application was still interacting, so we
996        * didn't tell it yet, and then *another* SaveYourself arrived,
997        * which we must still be waiting to tell the app about, except
998        * that now that SaveYourself has been cancelled too! Dizzy yet?
999        */
1000       xsmp->waiting_to_save_myself = FALSE;
1001       update_pending_events (xsmp);
1002     }
1003   else
1004     {
1005       g_debug ("Sending SaveYourselfDone(False)");
1006       SmcSaveYourselfDone (xsmp->connection, False);
1007
1008       if (xsmp->state == XSMP_STATE_INTERACT)
1009         {
1010           /* The application is currently interacting, so we can't
1011            * tell it about the cancellation yet; we will wait until
1012            * after it calls egg_sm_client_will_quit().
1013            */
1014           xsmp->state = XSMP_STATE_SHUTDOWN_CANCELLED;
1015         }
1016       else
1017         {
1018           /* The shutdown was cancelled before the application got a
1019            * chance to interact.
1020            */
1021           xsmp->state = XSMP_STATE_IDLE;
1022         }
1023     }
1024 }
1025
1026 /* Utilities */
1027
1028 /* Create a restart/clone/Exec command based on @restart_command.
1029  * If @client_id is non-%NULL, add "--sm-client-id @client_id".
1030  * If @state_file is non-%NULL, add "--sm-client-state-file @state_file".
1031  *
1032  * None of the input strings are g_strdup()ed; the caller must keep
1033  * them around until it is done with the returned GPtrArray, and must
1034  * then free the array, but not its contents.
1035  */
1036 static GPtrArray *
1037 generate_command (char **restart_command, const char *client_id,
1038                   const char *state_file)
1039 {
1040   GPtrArray *cmd;
1041   int i;
1042
1043   cmd = g_ptr_array_new ();
1044   g_ptr_array_add (cmd, restart_command[0]);
1045
1046   if (client_id)
1047     {
1048       g_ptr_array_add (cmd, "--sm-client-id");
1049       g_ptr_array_add (cmd, (char *)client_id);
1050     }
1051
1052   if (state_file)
1053     {
1054       g_ptr_array_add (cmd, "--sm-client-state-file");
1055       g_ptr_array_add (cmd, (char *)state_file);
1056     }
1057
1058   for (i = 1; restart_command[i]; i++)
1059     g_ptr_array_add (cmd, restart_command[i]);
1060
1061   return cmd;
1062 }
1063
1064 /* Takes a NULL-terminated list of SmProp * values, created by
1065  * array_prop, ptrarray_prop, string_prop, card8_prop, sets them, and
1066  * frees them.
1067  */
1068 static void
1069 set_properties (EggSMClientXSMP *xsmp, ...)
1070 {
1071   GPtrArray *props;
1072   SmProp *prop;
1073   va_list ap;
1074   guint i;
1075
1076   props = g_ptr_array_new ();
1077
1078   va_start (ap, xsmp);
1079   while ((prop = va_arg (ap, SmProp *)))
1080     g_ptr_array_add (props, prop);
1081   va_end (ap);
1082
1083   if (xsmp->connection)
1084     {
1085       SmcSetProperties (xsmp->connection, props->len,
1086                         (SmProp **)props->pdata);
1087     }
1088
1089   for (i = 0; i < props->len; i++)
1090     {
1091       prop = props->pdata[i];
1092       g_free (prop->vals);
1093       g_free (prop);
1094     }
1095   g_ptr_array_free (props, TRUE);
1096 }
1097
1098 /* Takes a NULL-terminated list of property names and deletes them. */
1099 static void
1100 delete_properties (EggSMClientXSMP *xsmp, ...)
1101 {
1102   GPtrArray *props;
1103   char *prop;
1104   va_list ap;
1105
1106   if (!xsmp->connection)
1107     return;
1108
1109   props = g_ptr_array_new ();
1110
1111   va_start (ap, xsmp);
1112   while ((prop = va_arg (ap, char *)))
1113     g_ptr_array_add (props, prop);
1114   va_end (ap);
1115
1116   SmcDeleteProperties (xsmp->connection, props->len,
1117                        (char **)props->pdata);
1118
1119   g_ptr_array_free (props, TRUE);
1120 }
1121
1122 /* Takes an array of strings and creates a LISTofARRAY8 property. The
1123  * strings are neither dupped nor freed; they need to remain valid
1124  * until you're done with the SmProp.
1125  */
1126 static SmProp *
1127 array_prop (const char *name, ...) 
1128 {
1129   SmProp *prop;
1130   SmPropValue pv;
1131   GArray *vals;
1132   char *value;
1133   va_list ap;
1134
1135   prop = g_new (SmProp, 1);
1136   prop->name = (char *)name;
1137   prop->type = SmLISTofARRAY8;
1138
1139   vals = g_array_new (FALSE, FALSE, sizeof (SmPropValue));
1140
1141   va_start (ap, name);
1142   while ((value = va_arg (ap, char *)))
1143     {
1144       pv.length = strlen (value);
1145       pv.value = value;
1146       g_array_append_val (vals, pv);
1147     }
1148
1149   prop->num_vals = vals->len;
1150   prop->vals = (SmPropValue *)vals->data;
1151
1152   g_array_free (vals, FALSE);
1153
1154   return prop;
1155 }
1156
1157 /* Takes a GPtrArray of strings and creates a LISTofARRAY8 property.
1158  * The array contents are neither dupped nor freed; they need to
1159  * remain valid until you're done with the SmProp.
1160  */
1161 static SmProp *
1162 ptrarray_prop (const char *name, GPtrArray *values)
1163 {
1164   SmProp *prop;
1165   SmPropValue pv;
1166   GArray *vals;
1167   guint i;
1168
1169   prop = g_new (SmProp, 1);
1170   prop->name = (char *)name;
1171   prop->type = SmLISTofARRAY8;
1172
1173   vals = g_array_new (FALSE, FALSE, sizeof (SmPropValue));
1174
1175   for (i = 0; i < values->len; i++)
1176     {
1177       pv.length = strlen (values->pdata[i]);
1178       pv.value = values->pdata[i];
1179       g_array_append_val (vals, pv);
1180     }
1181
1182   prop->num_vals = vals->len;
1183   prop->vals = (SmPropValue *)vals->data;
1184
1185   g_array_free (vals, FALSE);
1186
1187   return prop;
1188 }
1189
1190 /* Takes a string and creates an ARRAY8 property. The string is
1191  * neither dupped nor freed; it needs to remain valid until you're
1192  * done with the SmProp.
1193  */
1194 static SmProp *
1195 string_prop (const char *name, const char *value)
1196 {
1197   SmProp *prop;
1198
1199   prop = g_new (SmProp, 1);
1200   prop->name = (char *)name;
1201   prop->type = SmARRAY8;
1202
1203   prop->num_vals = 1;
1204   prop->vals = g_new (SmPropValue, 1);
1205
1206   prop->vals[0].length = strlen (value);
1207   prop->vals[0].value = (char *)value;
1208
1209   return prop;
1210 }
1211
1212 /* Takes a char and creates a CARD8 property. */
1213 static SmProp *
1214 card8_prop (const char *name, unsigned char value)
1215 {
1216   SmProp *prop;
1217   char *card8val;
1218
1219   /* To avoid having to allocate and free prop->vals[0], we cheat and
1220    * make vals a 2-element-long array and then use the second element
1221    * to store value.
1222    */
1223
1224   prop = g_new (SmProp, 1);
1225   prop->name = (char *)name;
1226   prop->type = SmCARD8;
1227
1228   prop->num_vals = 1;
1229   prop->vals = g_new (SmPropValue, 2);
1230   card8val = (char *)(&prop->vals[1]);
1231   card8val[0] = value;
1232
1233   prop->vals[0].length = 1;
1234   prop->vals[0].value = card8val;
1235
1236   return prop;
1237 }
1238
1239 /* ICE code. This makes no effort to play nice with anyone else trying
1240  * to use libICE. Fortunately, no one uses libICE for anything other
1241  * than SM. (DCOP uses ICE, but it has its own private copy of
1242  * libICE.)
1243  *
1244  * When this moves to gtk, it will need to be cleverer, to avoid
1245  * tripping over old apps that use GnomeClient or that use libSM
1246  * directly.
1247  */
1248
1249 #include <X11/ICE/ICElib.h>
1250 #include <fcntl.h>
1251
1252 static void        ice_error_handler    (IceConn        ice_conn,
1253                                          Bool           swap,
1254                                          int            offending_minor_opcode,
1255                                          unsigned long  offending_sequence,
1256                                          int            error_class,
1257                                          int            severity,
1258                                          IcePointer     values);
1259 static void        ice_io_error_handler (IceConn        ice_conn);
1260 static void        ice_connection_watch (IceConn        ice_conn,
1261                                          IcePointer     client_data,
1262                                          Bool           opening,
1263                                          IcePointer    *watch_data);
1264
1265 static void
1266 ice_init (void)
1267 {
1268   IceSetIOErrorHandler (ice_io_error_handler);
1269   IceSetErrorHandler (ice_error_handler);
1270   IceAddConnectionWatch (ice_connection_watch, NULL);
1271 }
1272
1273 static gboolean
1274 process_ice_messages (IceConn ice_conn)
1275 {
1276   IceProcessMessagesStatus status;
1277
1278   gdk_threads_enter ();
1279   status = IceProcessMessages (ice_conn, NULL, NULL);
1280   gdk_threads_leave ();
1281
1282   switch (status)
1283     {
1284     case IceProcessMessagesSuccess:
1285       return TRUE;
1286
1287     case IceProcessMessagesIOError:
1288       sm_client_xsmp_disconnect (IceGetConnectionContext (ice_conn));
1289       return FALSE;
1290
1291     case IceProcessMessagesConnectionClosed:
1292       return FALSE;
1293
1294     default:
1295       g_assert_not_reached ();
1296     }
1297 }
1298
1299 static gboolean
1300 ice_iochannel_watch (GIOChannel   *channel,
1301                      GIOCondition  condition,
1302                      gpointer      client_data)
1303 {
1304   return process_ice_messages (client_data);
1305 }
1306
1307 static void
1308 ice_connection_watch (IceConn     ice_conn,
1309                       IcePointer  client_data,
1310                       Bool        opening,
1311                       IcePointer *watch_data)
1312 {
1313   guint watch_id;
1314
1315   if (opening)
1316     {
1317       GIOChannel *channel;
1318       int fd = IceConnectionNumber (ice_conn);
1319
1320       fcntl (fd, F_SETFD, fcntl (fd, F_GETFD, 0) | FD_CLOEXEC);
1321       channel = g_io_channel_unix_new (fd);
1322       watch_id = g_io_add_watch (channel, G_IO_IN | G_IO_ERR,
1323                                  ice_iochannel_watch, ice_conn);
1324       g_io_channel_unref (channel);
1325
1326       *watch_data = GUINT_TO_POINTER (watch_id);
1327     }
1328   else
1329     {
1330       watch_id = GPOINTER_TO_UINT (*watch_data);
1331       g_source_remove (watch_id);
1332     }
1333 }
1334
1335 static void
1336 ice_error_handler (IceConn       ice_conn,
1337                    Bool          swap,
1338                    int           offending_minor_opcode,
1339                    unsigned long offending_sequence,
1340                    int           error_class,
1341                    int           severity,
1342                    IcePointer    values)
1343 {
1344   /* Do nothing */
1345
1346
1347 static void
1348 ice_io_error_handler (IceConn ice_conn)
1349 {
1350   /* Do nothing */
1351
1352
1353 static void
1354 smc_error_handler (SmcConn       smc_conn,
1355                    Bool          swap,
1356                    int           offending_minor_opcode,
1357                    unsigned long offending_sequence,
1358                    int           error_class,
1359                    int           severity,
1360                    SmPointer     values)
1361 {
1362   /* Do nothing */
1363 }