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