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