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