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