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