]> www.fi.muni.cz Git - evince.git/blob - cut-n-paste/smclient/eggsmclient-xsmp.c
8e13ff4a8bbf3fdcd6178bf199ab74f9d661ce78
[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   "start",
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       GKeyFile *key_file;
225       GError *err = NULL;
226       char *cmdline, **argv;
227       int argc;
228
229       key_file = egg_desktop_file_get_key_file (desktop_file);
230
231       if (xsmp->restart_style == SmRestartIfRunning)
232         {
233           if (g_key_file_has_key (key_file, EGG_DESKTOP_FILE_GROUP,
234                                   "X-GNOME-AutoRestart", NULL) &&
235               g_key_file_get_boolean (key_file, EGG_DESKTOP_FILE_GROUP,
236                                       "X-GNOME-AutoRestart", NULL))
237             xsmp->restart_style = SmRestartImmediately;
238         }
239
240       if (!xsmp->set_restart_command)
241         {
242           cmdline = egg_desktop_file_parse_exec (desktop_file, NULL, &err);
243           if (cmdline && g_shell_parse_argv (cmdline, &argc, &argv, &err))
244             {
245               egg_sm_client_set_restart_command (EGG_SM_CLIENT (xsmp),
246                                                  argc, (const char **)argv);
247               g_strfreev (argv);
248             }
249           else
250             {
251               g_warning ("Could not parse Exec line in desktop file: %s",
252                          err->message);
253               g_error_free (err);
254             }
255         }
256     }
257
258   if (!xsmp->set_restart_command)
259     xsmp->restart_command = g_strsplit (g_get_prgname (), " ", -1);
260
261   clone = generate_command (xsmp->restart_command, NULL, NULL);
262   restart = generate_command (xsmp->restart_command, xsmp->client_id, NULL);
263
264   g_debug ("Setting initial properties");
265
266   /* Program, CloneCommand, RestartCommand, and UserID are required.
267    * ProcessID isn't required, but the SM may be able to do something
268    * useful with it.
269    */
270   g_snprintf (pid_str, sizeof (pid_str), "%lu", (gulong) getpid ());
271   set_properties (xsmp,
272                   string_prop   (SmProgram, g_get_prgname ()),
273                   ptrarray_prop (SmCloneCommand, clone),
274                   ptrarray_prop (SmRestartCommand, restart),
275                   string_prop   (SmUserID, g_get_user_name ()),
276                   string_prop   (SmProcessID, pid_str),
277                   card8_prop    (SmRestartStyleHint, xsmp->restart_style),
278                   NULL);
279   g_ptr_array_free (clone, TRUE);
280   g_ptr_array_free (restart, TRUE);
281
282   if (desktop_file)
283     {
284       set_properties (xsmp,
285                       string_prop ("_GSM_DesktopFile", egg_desktop_file_get_source (desktop_file)),
286                       NULL);
287     }
288
289   update_pending_events (xsmp);
290   return FALSE;
291 }
292
293 /* This gets called from two different places: xsmp_die() (when the
294  * server asks us to disconnect) and process_ice_messages() (when the
295  * server disconnects unexpectedly).
296  */
297 static void
298 sm_client_xsmp_disconnect (EggSMClientXSMP *xsmp)
299 {
300   SmcConn connection;
301
302   if (!xsmp->connection)
303     return;
304
305   g_debug ("Disconnecting");
306
307   connection = xsmp->connection;
308   xsmp->connection = NULL;
309   SmcCloseConnection (connection, 0, NULL);
310   xsmp->state = XSMP_STATE_CONNECTION_CLOSED;
311
312   xsmp->waiting_to_save_myself = FALSE;
313   update_pending_events (xsmp);
314 }
315
316 static void
317 sm_client_xsmp_startup (EggSMClient *client,
318                         const char  *client_id)
319 {
320   EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client;
321   SmcCallbacks callbacks;
322   char *ret_client_id;
323   char error_string_ret[256];
324
325   xsmp->client_id = g_strdup (client_id);
326
327   ice_init ();
328   SmcSetErrorHandler (smc_error_handler);
329
330   callbacks.save_yourself.callback      = xsmp_save_yourself;
331   callbacks.die.callback                = xsmp_die;
332   callbacks.save_complete.callback      = xsmp_save_complete;
333   callbacks.shutdown_cancelled.callback = xsmp_shutdown_cancelled;
334
335   callbacks.save_yourself.client_data      = xsmp;
336   callbacks.die.client_data                = xsmp;
337   callbacks.save_complete.client_data      = xsmp;
338   callbacks.shutdown_cancelled.client_data = xsmp;
339
340   client_id = NULL;
341   error_string_ret[0] = '\0';
342   xsmp->connection =
343     SmcOpenConnection (NULL, xsmp, SmProtoMajor, SmProtoMinor,
344                        SmcSaveYourselfProcMask | SmcDieProcMask |
345                        SmcSaveCompleteProcMask |
346                        SmcShutdownCancelledProcMask,
347                        &callbacks,
348                        xsmp->client_id, &ret_client_id,
349                        sizeof (error_string_ret), error_string_ret);
350
351   if (!xsmp->connection)
352     {
353       g_warning ("Failed to connect to the session manager: %s\n",
354                  error_string_ret[0] ?
355                  error_string_ret : "no error message given");
356       xsmp->state = XSMP_STATE_CONNECTION_CLOSED;
357       return;
358     }
359
360   /* We expect a pointless initial SaveYourself if either (a) we
361    * didn't have an initial client ID, or (b) we DID have an initial
362    * client ID, but the server rejected it and gave us a new one.
363    */
364   if (!xsmp->client_id ||
365       (ret_client_id && strcmp (xsmp->client_id, ret_client_id) != 0))
366     xsmp->expecting_initial_save_yourself = TRUE;
367
368   if (ret_client_id)
369     {
370       g_free (xsmp->client_id);
371       xsmp->client_id = g_strdup (ret_client_id);
372       free (ret_client_id);
373
374       gdk_threads_enter ();
375       gdk_set_sm_client_id (xsmp->client_id);
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 merge_keyfiles (GKeyFile *dest, GKeyFile *source)
775 {
776   int g, k;
777   char **groups, **keys, *value;
778
779   groups = g_key_file_get_groups (source, NULL);
780   for (g = 0; groups[g]; g++)
781     {
782       keys = g_key_file_get_keys (source, groups[g], NULL, NULL);
783       for (k = 0; keys[k]; k++)
784         {
785           value = g_key_file_get_value (source, groups[g], keys[k], NULL);
786           if (value)
787             {
788               g_key_file_set_value (dest, groups[g], keys[k], value);
789               g_free (value);
790             }
791         }
792       g_strfreev (keys);
793     }
794   g_strfreev (groups);
795 }
796
797 static void
798 save_state (EggSMClientXSMP *xsmp)
799 {
800   GKeyFile *state_file;
801   char *state_file_path, *data;
802   EggDesktopFile *desktop_file;
803   GPtrArray *restart;
804   int offset, fd;
805
806   /* We set xsmp->state before emitting save_state, but our caller is
807    * responsible for setting it back afterward.
808    */
809   xsmp->state = XSMP_STATE_SAVE_YOURSELF;
810
811   state_file = egg_sm_client_save_state ((EggSMClient *)xsmp);
812   if (!state_file)
813     {
814       restart = generate_command (xsmp->restart_command, xsmp->client_id, NULL);
815       set_properties (xsmp,
816                       ptrarray_prop (SmRestartCommand, restart),
817                       NULL);
818       g_ptr_array_free (restart, TRUE);
819       delete_properties (xsmp, SmDiscardCommand, NULL);
820       return;
821     }
822
823   desktop_file = egg_get_desktop_file ();
824   if (desktop_file)
825     {
826       GKeyFile *merged_file;
827       char *exec;
828       int i;
829
830       merged_file = g_key_file_new ();
831       merge_keyfiles (merged_file, egg_desktop_file_get_key_file (desktop_file));
832       merge_keyfiles (merged_file, state_file);
833
834       g_key_file_free (state_file);
835       state_file = merged_file;
836
837       /* Update Exec key using "--sm-client-state-file %k" */
838       restart = generate_command (xsmp->restart_command,
839                                   NULL, "%k");
840       for (i = 0; i < restart->len; i++)
841         restart->pdata[i] = g_shell_quote (restart->pdata[i]);
842       g_ptr_array_add (restart, NULL);
843       exec = g_strjoinv (" ", (char **)restart->pdata);
844       g_strfreev ((char **)restart->pdata);
845       g_ptr_array_free (restart, FALSE);
846
847       g_key_file_set_string (state_file, EGG_DESKTOP_FILE_GROUP,
848                              EGG_DESKTOP_FILE_KEY_EXEC,
849                              exec);
850       g_free (exec);
851     }
852
853   /* Now write state_file to disk. (We can't use mktemp(), because
854    * that requires the filename to end with "XXXXXX", and we want
855    * it to end with ".desktop".)
856    */
857
858   data = g_key_file_to_data (state_file, NULL, NULL);
859   g_key_file_free (state_file);
860
861   offset = 0;
862   while (1)
863     {
864       state_file_path = g_strdup_printf ("%s%csession-state%c%s-%ld.%s",
865                                          g_get_user_config_dir (),
866                                          G_DIR_SEPARATOR, G_DIR_SEPARATOR,
867                                          g_get_prgname (),
868                                          (long)time (NULL) + offset,
869                                          desktop_file ? "desktop" : "state");
870
871       fd = open (state_file_path, O_WRONLY | O_CREAT | O_EXCL, 0644);
872       if (fd == -1)
873         {
874           if (errno == EEXIST)
875             {
876               offset++;
877               g_free (state_file_path);
878               continue;
879             }
880           else if (errno == ENOTDIR || errno == ENOENT)
881             {
882               char *sep = strrchr (state_file_path, G_DIR_SEPARATOR);
883
884               *sep = '\0';
885               if (g_mkdir_with_parents (state_file_path, 0755) != 0)
886                 {
887                   g_warning ("Could not create directory '%s'",
888                              state_file_path);
889                   g_free (state_file_path);
890                   state_file_path = NULL;
891                   break;
892                 }
893
894               continue;
895             }
896
897           g_warning ("Could not create file '%s': %s",
898                      state_file_path, g_strerror (errno));
899           g_free (state_file_path);
900           state_file_path = NULL;
901           break;
902         }
903
904       close (fd);
905       g_file_set_contents (state_file_path, data, -1, NULL);
906       break;
907     }
908   g_free (data);
909
910   restart = generate_command (xsmp->restart_command, xsmp->client_id,
911                               state_file_path);
912   set_properties (xsmp,
913                   ptrarray_prop (SmRestartCommand, restart),
914                   NULL);
915   g_ptr_array_free (restart, TRUE);
916
917   if (state_file_path)
918     {
919       set_properties (xsmp,
920                       array_prop (SmDiscardCommand,
921                                   "/bin/rm", "-rf", state_file_path,
922                                   NULL),
923                       NULL);
924       g_free (state_file_path);
925     }
926 }
927
928 static void
929 xsmp_interact (SmcConn   smc_conn,
930                SmPointer client_data)
931 {
932   EggSMClientXSMP *xsmp = client_data;
933   EggSMClient *client = client_data;
934
935   g_debug ("Received Interact message in state %s",
936            EGG_SM_CLIENT_XSMP_STATE (xsmp));
937
938   if (xsmp->state != XSMP_STATE_INTERACT_REQUEST)
939     {
940       fix_broken_state (xsmp, "Interact", TRUE, TRUE);
941       return;
942     }
943
944   xsmp->state = XSMP_STATE_INTERACT;
945   egg_sm_client_quit_requested (client);
946 }
947
948 static void
949 xsmp_die (SmcConn   smc_conn,
950           SmPointer client_data)
951 {
952   EggSMClientXSMP *xsmp = client_data;
953   EggSMClient *client = client_data;
954
955   g_debug ("Received Die message in state %s",
956            EGG_SM_CLIENT_XSMP_STATE (xsmp));
957
958   sm_client_xsmp_disconnect (xsmp);
959   egg_sm_client_quit (client);
960 }
961
962 static void
963 xsmp_save_complete (SmcConn   smc_conn,
964                     SmPointer client_data)
965 {
966   EggSMClientXSMP *xsmp = client_data;
967
968   g_debug ("Received SaveComplete message in state %s",
969            EGG_SM_CLIENT_XSMP_STATE (xsmp));
970
971   if (xsmp->state == XSMP_STATE_SAVE_YOURSELF_DONE)
972     xsmp->state = XSMP_STATE_IDLE;
973   else
974     fix_broken_state (xsmp, "SaveComplete", FALSE, FALSE);
975 }
976
977 static void
978 xsmp_shutdown_cancelled (SmcConn   smc_conn,
979                          SmPointer client_data)
980 {
981   EggSMClientXSMP *xsmp = client_data;
982   EggSMClient *client = client_data;
983
984   g_debug ("Received ShutdownCancelled message in state %s",
985            EGG_SM_CLIENT_XSMP_STATE (xsmp));
986
987   xsmp->shutting_down = FALSE;
988
989   if (xsmp->state == XSMP_STATE_SAVE_YOURSELF_DONE)
990     {
991       /* We've finished interacting and now the SM has agreed to
992        * cancel the shutdown.
993        */
994       xsmp->state = XSMP_STATE_IDLE;
995       egg_sm_client_quit_cancelled (client);
996     }
997   else if (xsmp->state == XSMP_STATE_SHUTDOWN_CANCELLED)
998     {
999       /* Hm... ok, so we got a shutdown SaveYourself, which got
1000        * cancelled, but the application was still interacting, so we
1001        * didn't tell it yet, and then *another* SaveYourself arrived,
1002        * which we must still be waiting to tell the app about, except
1003        * that now that SaveYourself has been cancelled too! Dizzy yet?
1004        */
1005       xsmp->waiting_to_save_myself = FALSE;
1006       update_pending_events (xsmp);
1007     }
1008   else
1009     {
1010       g_debug ("Sending SaveYourselfDone(False)");
1011       SmcSaveYourselfDone (xsmp->connection, False);
1012
1013       if (xsmp->state == XSMP_STATE_INTERACT)
1014         {
1015           /* The application is currently interacting, so we can't
1016            * tell it about the cancellation yet; we will wait until
1017            * after it calls egg_sm_client_will_quit().
1018            */
1019           xsmp->state = XSMP_STATE_SHUTDOWN_CANCELLED;
1020         }
1021       else
1022         {
1023           /* The shutdown was cancelled before the application got a
1024            * chance to interact.
1025            */
1026           xsmp->state = XSMP_STATE_IDLE;
1027         }
1028     }
1029 }
1030
1031 /* Utilities */
1032
1033 /* Create a restart/clone/Exec command based on @restart_command.
1034  * If @client_id is non-%NULL, add "--sm-client-id @client_id".
1035  * If @state_file is non-%NULL, add "--sm-client-state-file @state_file".
1036  *
1037  * None of the input strings are g_strdup()ed; the caller must keep
1038  * them around until it is done with the returned GPtrArray, and must
1039  * then free the array, but not its contents.
1040  */
1041 static GPtrArray *
1042 generate_command (char **restart_command, const char *client_id,
1043                   const char *state_file)
1044 {
1045   GPtrArray *cmd;
1046   int i;
1047
1048   cmd = g_ptr_array_new ();
1049   g_ptr_array_add (cmd, restart_command[0]);
1050
1051   if (client_id)
1052     {
1053       g_ptr_array_add (cmd, "--sm-client-id");
1054       g_ptr_array_add (cmd, (char *)client_id);
1055     }
1056
1057   if (state_file)
1058     {
1059       g_ptr_array_add (cmd, "--sm-client-state-file");
1060       g_ptr_array_add (cmd, (char *)state_file);
1061     }
1062
1063   for (i = 1; restart_command[i]; i++)
1064     g_ptr_array_add (cmd, restart_command[i]);
1065
1066   return cmd;
1067 }
1068
1069 /* Takes a NULL-terminated list of SmProp * values, created by
1070  * array_prop, ptrarray_prop, string_prop, card8_prop, sets them, and
1071  * frees them.
1072  */
1073 static void
1074 set_properties (EggSMClientXSMP *xsmp, ...)
1075 {
1076   GPtrArray *props;
1077   SmProp *prop;
1078   va_list ap;
1079   int i;
1080
1081   props = g_ptr_array_new ();
1082
1083   va_start (ap, xsmp);
1084   while ((prop = va_arg (ap, SmProp *)))
1085     g_ptr_array_add (props, prop);
1086   va_end (ap);
1087
1088   if (xsmp->connection)
1089     {
1090       SmcSetProperties (xsmp->connection, props->len,
1091                         (SmProp **)props->pdata);
1092     }
1093
1094   for (i = 0; i < props->len; i++)
1095     {
1096       prop = props->pdata[i];
1097       g_free (prop->vals);
1098       g_free (prop);
1099     }
1100   g_ptr_array_free (props, TRUE);
1101 }
1102
1103 /* Takes a NULL-terminated list of property names and deletes them. */
1104 static void
1105 delete_properties (EggSMClientXSMP *xsmp, ...)
1106 {
1107   GPtrArray *props;
1108   char *prop;
1109   va_list ap;
1110
1111   if (!xsmp->connection)
1112     return;
1113
1114   props = g_ptr_array_new ();
1115
1116   va_start (ap, xsmp);
1117   while ((prop = va_arg (ap, char *)))
1118     g_ptr_array_add (props, prop);
1119   va_end (ap);
1120
1121   SmcDeleteProperties (xsmp->connection, props->len,
1122                        (char **)props->pdata);
1123
1124   g_ptr_array_free (props, TRUE);
1125 }
1126
1127 /* Takes an array of strings and creates a LISTofARRAY8 property. The
1128  * strings are neither dupped nor freed; they need to remain valid
1129  * until you're done with the SmProp.
1130  */
1131 static SmProp *
1132 array_prop (const char *name, ...) 
1133 {
1134   SmProp *prop;
1135   SmPropValue pv;
1136   GArray *vals;
1137   char *value;
1138   va_list ap;
1139
1140   prop = g_new (SmProp, 1);
1141   prop->name = (char *)name;
1142   prop->type = SmLISTofARRAY8;
1143
1144   vals = g_array_new (FALSE, FALSE, sizeof (SmPropValue));
1145
1146   va_start (ap, name);
1147   while ((value = va_arg (ap, char *)))
1148     {
1149       pv.length = strlen (value);
1150       pv.value = value;
1151       g_array_append_val (vals, pv);
1152     }
1153
1154   prop->num_vals = vals->len;
1155   prop->vals = (SmPropValue *)vals->data;
1156
1157   g_array_free (vals, FALSE);
1158
1159   return prop;
1160 }
1161
1162 /* Takes a GPtrArray of strings and creates a LISTofARRAY8 property.
1163  * The array contents are neither dupped nor freed; they need to
1164  * remain valid until you're done with the SmProp.
1165  */
1166 static SmProp *
1167 ptrarray_prop (const char *name, GPtrArray *values)
1168 {
1169   SmProp *prop;
1170   SmPropValue pv;
1171   GArray *vals;
1172   int i;
1173
1174   prop = g_new (SmProp, 1);
1175   prop->name = (char *)name;
1176   prop->type = SmLISTofARRAY8;
1177
1178   vals = g_array_new (FALSE, FALSE, sizeof (SmPropValue));
1179
1180   for (i = 0; i < values->len; i++)
1181     {
1182       pv.length = strlen (values->pdata[i]);
1183       pv.value = values->pdata[i];
1184       g_array_append_val (vals, pv);
1185     }
1186
1187   prop->num_vals = vals->len;
1188   prop->vals = (SmPropValue *)vals->data;
1189
1190   g_array_free (vals, FALSE);
1191
1192   return prop;
1193 }
1194
1195 /* Takes a string and creates an ARRAY8 property. The string is
1196  * neither dupped nor freed; it needs to remain valid until you're
1197  * done with the SmProp.
1198  */
1199 static SmProp *
1200 string_prop (const char *name, const char *value)
1201 {
1202   SmProp *prop;
1203
1204   prop = g_new (SmProp, 1);
1205   prop->name = (char *)name;
1206   prop->type = SmARRAY8;
1207
1208   prop->num_vals = 1;
1209   prop->vals = g_new (SmPropValue, 1);
1210
1211   prop->vals[0].length = strlen (value);
1212   prop->vals[0].value = (char *)value;
1213
1214   return prop;
1215 }
1216
1217 /* Takes a char and creates a CARD8 property. */
1218 static SmProp *
1219 card8_prop (const char *name, unsigned char value)
1220 {
1221   SmProp *prop;
1222   char *card8val;
1223
1224   /* To avoid having to allocate and free prop->vals[0], we cheat and
1225    * make vals a 2-element-long array and then use the second element
1226    * to store value.
1227    */
1228
1229   prop = g_new (SmProp, 1);
1230   prop->name = (char *)name;
1231   prop->type = SmCARD8;
1232
1233   prop->num_vals = 1;
1234   prop->vals = g_new (SmPropValue, 2);
1235   card8val = (char *)(&prop->vals[1]);
1236   card8val[0] = value;
1237
1238   prop->vals[0].length = 1;
1239   prop->vals[0].value = card8val;
1240
1241   return prop;
1242 }
1243
1244 /* ICE code. This makes no effort to play nice with anyone else trying
1245  * to use libICE. Fortunately, no one uses libICE for anything other
1246  * than SM. (DCOP uses ICE, but it has its own private copy of
1247  * libICE.)
1248  *
1249  * When this moves to gtk, it will need to be cleverer, to avoid
1250  * tripping over old apps that use GnomeClient or that use libSM
1251  * directly.
1252  */
1253
1254 #include <X11/ICE/ICElib.h>
1255 #include <fcntl.h>
1256
1257 static void        ice_error_handler    (IceConn        ice_conn,
1258                                          Bool           swap,
1259                                          int            offending_minor_opcode,
1260                                          unsigned long  offending_sequence,
1261                                          int            error_class,
1262                                          int            severity,
1263                                          IcePointer     values);
1264 static void        ice_io_error_handler (IceConn        ice_conn);
1265 static void        ice_connection_watch (IceConn        ice_conn,
1266                                          IcePointer     client_data,
1267                                          Bool           opening,
1268                                          IcePointer    *watch_data);
1269
1270 static void
1271 ice_init (void)
1272 {
1273   IceSetIOErrorHandler (ice_io_error_handler);
1274   IceSetErrorHandler (ice_error_handler);
1275   IceAddConnectionWatch (ice_connection_watch, NULL);
1276 }
1277
1278 static gboolean
1279 process_ice_messages (IceConn ice_conn)
1280 {
1281   IceProcessMessagesStatus status;
1282
1283   gdk_threads_enter ();
1284   status = IceProcessMessages (ice_conn, NULL, NULL);
1285   gdk_threads_leave ();
1286
1287   switch (status)
1288     {
1289     case IceProcessMessagesSuccess:
1290       return TRUE;
1291
1292     case IceProcessMessagesIOError:
1293       sm_client_xsmp_disconnect (IceGetConnectionContext (ice_conn));
1294       return FALSE;
1295
1296     case IceProcessMessagesConnectionClosed:
1297       return FALSE;
1298
1299     default:
1300       g_assert_not_reached ();
1301     }
1302 }
1303
1304 static gboolean
1305 ice_iochannel_watch (GIOChannel   *channel,
1306                      GIOCondition  condition,
1307                      gpointer      client_data)
1308 {
1309   return process_ice_messages (client_data);
1310 }
1311
1312 static void
1313 ice_connection_watch (IceConn     ice_conn,
1314                       IcePointer  client_data,
1315                       Bool        opening,
1316                       IcePointer *watch_data)
1317 {
1318   guint watch_id;
1319
1320   if (opening)
1321     {
1322       GIOChannel *channel;
1323       int fd = IceConnectionNumber (ice_conn);
1324
1325       fcntl (fd, F_SETFD, fcntl (fd, F_GETFD, 0) | FD_CLOEXEC);
1326       channel = g_io_channel_unix_new (fd);
1327       watch_id = g_io_add_watch (channel, G_IO_IN | G_IO_ERR,
1328                                  ice_iochannel_watch, ice_conn);
1329       g_io_channel_unref (channel);
1330
1331       *watch_data = GUINT_TO_POINTER (watch_id);
1332     }
1333   else
1334     {
1335       watch_id = GPOINTER_TO_UINT (*watch_data);
1336       g_source_remove (watch_id);
1337     }
1338 }
1339
1340 static void
1341 ice_error_handler (IceConn       ice_conn,
1342                    Bool          swap,
1343                    int           offending_minor_opcode,
1344                    unsigned long offending_sequence,
1345                    int           error_class,
1346                    int           severity,
1347                    IcePointer    values)
1348 {
1349   /* Do nothing */
1350
1351
1352 static void
1353 ice_io_error_handler (IceConn ice_conn)
1354 {
1355   /* Do nothing */
1356
1357
1358 static void
1359 smc_error_handler (SmcConn       smc_conn,
1360                    Bool          swap,
1361                    int           offending_minor_opcode,
1362                    unsigned long offending_sequence,
1363                    int           error_class,
1364                    int           severity,
1365                    SmPointer     values)
1366 {
1367   /* Do nothing */
1368 }