Merge branch 'backport' into work
[dana/openbox.git] / openbox / session.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    session.c for the Openbox window manager
4    Copyright (c) 2003-2007   Dana Jansens
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    See the COPYING file for a copy of the GNU General Public License.
17 */
18
19 /* This session code is largely inspired by metacity code. */
20
21 #include "session.h"
22
23 struct _ObClient;
24
25 GList *session_saved_state = NULL;
26 gint session_desktop = -1;
27 gint session_num_desktops = 0;
28 gboolean session_desktop_layout_present = FALSE;
29 ObDesktopLayout session_desktop_layout;
30 GSList *session_desktop_names = NULL;
31
32 #ifndef USE_SM
33 void session_startup(gint argc, gchar **argv) {}
34 void session_shutdown(gboolean permanent) {}
35 GList* session_state_find(struct _ObClient *c) { return NULL; }
36 void session_request_logout(gboolean silent) {}
37 #else
38
39 #include "debug.h"
40 #include "openbox.h"
41 #include "client.h"
42 #include "focus.h"
43 #include "gettext.h"
44 #include "obt/parse.h"
45 #include "obt/paths.h"
46
47 #include <time.h>
48 #include <errno.h>
49 #include <stdio.h>
50
51 #ifdef HAVE_UNISTD_H
52 #  include <sys/types.h>
53 #  include <unistd.h>
54 #endif
55
56 #include <X11/SM/SMlib.h>
57
58 #define SM_ERR_LEN 1024
59
60 static SmcConn  sm_conn;
61 static gint     sm_argc;
62 static gchar  **sm_argv;
63
64 /* Data saved from the first level save yourself */
65 typedef struct {
66     ObClient *focus_client;
67     gint      desktop;
68 } ObSMSaveData;
69
70 static gboolean session_connect();
71
72 static void session_load_file(const gchar *path);
73 static gboolean session_save_to_file(const ObSMSaveData *savedata);
74
75 static void session_setup_program();
76 static void session_setup_user();
77 static void session_setup_restart_style(gboolean restart);
78 static void session_setup_pid();
79 static void session_setup_priority();
80 static void session_setup_clone_command();
81 static void session_setup_restart_command();
82
83 static void sm_save_yourself(SmcConn conn, SmPointer data, gint save_type,
84                              Bool shutdown, gint interact_style, Bool fast);
85 static void sm_die(SmcConn conn, SmPointer data);
86 static void sm_save_complete(SmcConn conn, SmPointer data);
87 static void sm_shutdown_cancelled(SmcConn conn, SmPointer data);
88
89 static gboolean session_state_cmp(ObSessionState *s, ObClient *c);
90 static void session_state_free(ObSessionState *state);
91
92 void session_startup(gint argc, gchar **argv)
93 {
94     gchar *dir;
95     ObtPaths *p;
96
97     if (!ob_sm_use) return;
98
99     sm_argc = argc;
100     sm_argv = argv;
101
102     p = obt_paths_new();
103     dir = g_build_filename(obt_paths_cache_home(p),
104                            "openbox", "sessions", NULL);
105     obt_paths_unref(p), p = NULL;
106
107     if (!obt_paths_mkdir_path(dir, 0700)) {
108         g_message(_("Unable to make directory \"%s\": %s"),
109                   dir, g_strerror(errno));
110     }
111
112     if (ob_sm_save_file != NULL) {
113         if (ob_sm_restore) {
114             ob_debug_type(OB_DEBUG_SM, "Loading from session file %s",
115                           ob_sm_save_file);
116             session_load_file(ob_sm_save_file);
117         }
118     } else {
119         gchar *filename;
120
121         /* this algo is from metacity */
122         filename = g_strdup_printf("%u-%u-%u.obs",
123                                    (guint)time(NULL),
124                                    (guint)getpid(),
125                                    g_random_int());
126         ob_sm_save_file = g_build_filename(dir, filename, NULL);
127         g_free(filename);
128     }
129
130     if (session_connect()) {
131         session_setup_program();
132         session_setup_user();
133         session_setup_restart_style(TRUE);
134         session_setup_pid();
135         session_setup_priority();
136         session_setup_clone_command();
137     }
138
139     g_free(dir);
140 }
141
142 void session_shutdown(gboolean permanent)
143 {
144     if (!ob_sm_use) return;
145
146     if (sm_conn) {
147         /* if permanent is true then we will change our session state so that
148            the SM won't run us again */
149         if (permanent)
150             session_setup_restart_style(FALSE);
151
152         SmcCloseConnection(sm_conn, 0, NULL);
153
154         while (session_saved_state) {
155             session_state_free(session_saved_state->data);
156             session_saved_state = g_list_delete_link(session_saved_state,
157                                                      session_saved_state);
158         }
159     }
160 }
161
162 /*! Connect to the session manager and set up our callback functions */
163 static gboolean session_connect(void)
164 {
165     SmcCallbacks cb;
166     gchar *oldid;
167     gchar sm_err[SM_ERR_LEN];
168
169     /* set up our callback functions */
170     cb.save_yourself.callback = sm_save_yourself;
171     cb.save_yourself.client_data = NULL;
172     cb.die.callback = sm_die;
173     cb.die.client_data = NULL;
174     cb.save_complete.callback = sm_save_complete;
175     cb.save_complete.client_data = NULL;
176     cb.shutdown_cancelled.callback = sm_shutdown_cancelled;
177     cb.shutdown_cancelled.client_data = NULL;
178
179     /* connect to the server */
180     oldid = ob_sm_id;
181     ob_debug_type(OB_DEBUG_SM, "Connecting to SM with id: %s",
182                   oldid ? oldid : "(null)");
183     sm_conn = SmcOpenConnection(NULL, NULL, 1, 0,
184                                 SmcSaveYourselfProcMask |
185                                 SmcDieProcMask |
186                                 SmcSaveCompleteProcMask |
187                                 SmcShutdownCancelledProcMask,
188                                 &cb, oldid, &ob_sm_id,
189                                 SM_ERR_LEN-1, sm_err);
190     g_free(oldid);
191     ob_debug_type(OB_DEBUG_SM, "Connected to SM with id: %s", ob_sm_id);
192     if (sm_conn == NULL)
193         ob_debug("Failed to connect to session manager: %s", sm_err);
194     return sm_conn != NULL;
195 }
196
197 static void session_setup_program(void)
198 {
199     SmPropValue vals = {
200         .value = sm_argv[0],
201         .length = strlen(sm_argv[0]) + 1
202     };
203     SmProp prop = {
204         .name = g_strdup(SmProgram),
205         .type = g_strdup(SmARRAY8),
206         .num_vals = 1,
207         .vals = &vals
208     };
209     SmProp *list = &prop;
210     ob_debug_type(OB_DEBUG_SM, "Setting program: %s", sm_argv[0]);
211     SmcSetProperties(sm_conn, 1, &list);
212     g_free(prop.name);
213     g_free(prop.type);
214 }
215
216 static void session_setup_user(void)
217 {
218     char *user = g_strdup(g_get_user_name());
219
220     SmPropValue vals = {
221         .value = user,
222         .length = strlen(user) + 1
223     };
224     SmProp prop = {
225         .name = g_strdup(SmUserID),
226         .type = g_strdup(SmARRAY8),
227         .num_vals = 1,
228         .vals = &vals
229     };
230     SmProp *list = &prop;
231     ob_debug_type(OB_DEBUG_SM, "Setting user: %s", user);
232     SmcSetProperties(sm_conn, 1, &list);
233     g_free(prop.name);
234     g_free(prop.type);
235     g_free(user);
236 }
237
238 static void session_setup_restart_style(gboolean restart)
239 {
240     gchar restart_hint = restart ? SmRestartImmediately : SmRestartIfRunning;
241
242     SmPropValue vals = {
243         .value = &restart_hint,
244         .length = 1
245     };
246     SmProp prop = {
247         .name = g_strdup(SmRestartStyleHint),
248         .type = g_strdup(SmCARD8),
249         .num_vals = 1,
250         .vals = &vals
251     };
252     SmProp *list = &prop;
253     ob_debug_type(OB_DEBUG_SM, "Setting restart: %d", restart);
254     SmcSetProperties(sm_conn, 1, &list);
255     g_free(prop.name);
256     g_free(prop.type);
257 }
258
259 static void session_setup_pid(void)
260 {
261     gchar *pid = g_strdup_printf("%ld", (glong) getpid());
262
263     SmPropValue vals = {
264         .value = pid,
265         .length = strlen(pid) + 1
266     };
267     SmProp prop = {
268         .name = g_strdup(SmProcessID),
269         .type = g_strdup(SmARRAY8),
270         .num_vals = 1,
271         .vals = &vals
272     };
273     SmProp *list = &prop;
274     ob_debug_type(OB_DEBUG_SM, "Setting pid: %s", pid);
275     SmcSetProperties(sm_conn, 1, &list);
276     g_free(prop.name);
277     g_free(prop.type);
278     g_free(pid);
279 }
280
281 /*! This is a gnome-session-manager extension */
282 static void session_setup_priority(void)
283 {
284     gchar priority = 20; /* 20 is a lower prioity to run before other apps */
285
286     SmPropValue vals = {
287         .value = &priority,
288         .length = 1
289     };
290     SmProp prop = {
291         .name = g_strdup("_GSM_Priority"),
292         .type = g_strdup(SmCARD8),
293         .num_vals = 1,
294         .vals = &vals
295     };
296     SmProp *list = &prop;
297     ob_debug_type(OB_DEBUG_SM, "Setting priority: %d", priority);
298     SmcSetProperties(sm_conn, 1, &list);
299     g_free(prop.name);
300     g_free(prop.type);
301 }
302
303 static void session_setup_clone_command(void)
304 {
305     gint i;
306
307     SmPropValue *vals = g_new(SmPropValue, sm_argc);
308     SmProp prop = {
309         .name = g_strdup(SmCloneCommand),
310         .type = g_strdup(SmLISTofARRAY8),
311         .num_vals = sm_argc,
312         .vals = vals
313     };
314     SmProp *list = &prop;
315
316     ob_debug_type(OB_DEBUG_SM, "Setting clone command: (%d)", sm_argc);
317     for (i = 0; i < sm_argc; ++i) {
318         vals[i].value = sm_argv[i];
319         vals[i].length = strlen(sm_argv[i]) + 1;
320         ob_debug_type(OB_DEBUG_SM, "    %s", vals[i].value);
321     }
322
323     SmcSetProperties(sm_conn, 1, &list);
324     g_free(prop.name);
325     g_free(prop.type);
326     g_free(vals);
327 }
328
329 static void session_setup_restart_command(void)
330 {
331     gint i;
332
333     SmPropValue *vals = g_new(SmPropValue, sm_argc + 4);
334     SmProp prop = {
335         .name = g_strdup(SmRestartCommand),
336         .type = g_strdup(SmLISTofARRAY8),
337         .num_vals = sm_argc + 4,
338         .vals = vals
339     };
340     SmProp *list = &prop;
341
342     ob_debug_type(OB_DEBUG_SM, "Setting restart command: (%d)", sm_argc+4);
343     for (i = 0; i < sm_argc; ++i) {
344         vals[i].value = sm_argv[i];
345         vals[i].length = strlen(sm_argv[i]) + 1;
346         ob_debug_type(OB_DEBUG_SM, "    %s", vals[i].value);
347     }
348
349     vals[i].value = g_strdup("--sm-client-id");
350     vals[i].length = strlen("--sm-client-id") + 1;
351     vals[i+1].value = ob_sm_id;
352     vals[i+1].length = strlen(ob_sm_id) + 1;
353     ob_debug_type(OB_DEBUG_SM, "    %s", vals[i].value);
354     ob_debug_type(OB_DEBUG_SM, "    %s", vals[i+1].value);
355
356     vals[i+2].value = g_strdup("--sm-save-file");
357     vals[i+2].length = strlen("--sm-save-file") + 1;
358     vals[i+3].value = ob_sm_save_file;
359     vals[i+3].length = strlen(ob_sm_save_file) + 1;
360     ob_debug_type(OB_DEBUG_SM, "    %s", vals[i+2].value);
361     ob_debug_type(OB_DEBUG_SM, "    %s", vals[i+3].value);
362
363     SmcSetProperties(sm_conn, 1, &list);
364     g_free(prop.name);
365     g_free(prop.type);
366     g_free(vals[i].value);
367     g_free(vals[i+2].value);
368     g_free(vals);
369 }
370
371 static ObSMSaveData *sm_save_get_data(void)
372 {
373     ObSMSaveData *savedata = g_new0(ObSMSaveData, 1);
374     /* save the active desktop and client.
375        we don't bother to preemptively save the other desktop state like
376        number and names of desktops, cuz those shouldn't be changing during
377        the save.. */
378     savedata->focus_client = focus_client;
379     savedata->desktop = screen_desktop;
380     return savedata;
381 }
382
383 static void sm_save_yourself_2(SmcConn conn, SmPointer data)
384 {
385     gboolean success;
386     ObSMSaveData *savedata = data;
387
388     /* save the current state */
389     ob_debug_type(OB_DEBUG_SM, "Session save phase 2 requested");
390     ob_debug_type(OB_DEBUG_SM,
391                   "  Saving session to file '%s'", ob_sm_save_file);
392     if (savedata == NULL)
393         savedata = sm_save_get_data();
394     success = session_save_to_file(savedata);
395     g_free(savedata);
396
397     /* tell the session manager how to restore this state */
398     if (success) session_setup_restart_command();
399
400     ob_debug_type(OB_DEBUG_SM, "Saving is done (success = %d)", success);
401     SmcSaveYourselfDone(conn, success);
402 }
403
404 static void sm_save_yourself(SmcConn conn, SmPointer data, gint save_type,
405                              Bool shutdown, gint interact_style, Bool fast)
406 {
407     ObSMSaveData *savedata = NULL;
408     gchar *vendor;
409
410 #ifdef DEBUG
411     {
412         const gchar *sname =
413             (save_type == SmSaveLocal ? "SmSaveLocal" :
414              (save_type == SmSaveGlobal ? "SmSaveGlobal" :
415               (save_type == SmSaveBoth ? "SmSaveBoth" : "INVALID!!")));
416         ob_debug_type(OB_DEBUG_SM, "Session save requested, type %s\n", sname);
417     }
418 #endif
419
420     if (save_type == SmSaveGlobal) {
421         /* we have no data to save.  we only store state to get back to where
422            we were, we don't keep open writable files or anything */
423         SmcSaveYourselfDone(conn, TRUE);
424         return;
425     }
426
427     vendor = SmcVendor(sm_conn);
428     ob_debug_type(OB_DEBUG_SM, "Session manager's vendor: %s", vendor);
429
430     if (!strcmp(vendor, "KDE")) {
431         /* ksmserver guarantees that phase 1 will complete before allowing any
432            clients interaction, so we can save this sanely here before they
433            get messed up from interaction */
434         savedata = sm_save_get_data();
435     }
436     free(vendor);
437
438     if (!SmcRequestSaveYourselfPhase2(conn, sm_save_yourself_2, savedata)) {
439         ob_debug_type(OB_DEBUG_SM, "Requst for phase 2 failed");
440         g_free(savedata);
441         SmcSaveYourselfDone(conn, FALSE);
442     }
443 }
444
445 static void sm_die(SmcConn conn, SmPointer data)
446 {
447     ob_debug_type(OB_DEBUG_SM, "Die requested");
448     ob_exit(0);
449 }
450
451 static void sm_save_complete(SmcConn conn, SmPointer data)
452 {
453     ob_debug_type(OB_DEBUG_SM, "Save complete");
454 }
455
456 static void sm_shutdown_cancelled(SmcConn conn, SmPointer data)
457 {
458     ob_debug_type(OB_DEBUG_SM, "Shutdown cancelled");
459 }
460
461 static gboolean session_save_to_file(const ObSMSaveData *savedata)
462 {
463     FILE *f;
464     GList *it;
465     gboolean success = TRUE;
466
467     f = fopen(ob_sm_save_file, "w");
468     if (!f) {
469         success = FALSE;
470         g_message(_("Unable to save the session to \"%s\": %s"),
471                   ob_sm_save_file, g_strerror(errno));
472     } else {
473         fprintf(f, "<?xml version=\"1.0\"?>\n\n");
474         fprintf(f, "<openbox_session>\n\n");
475
476         fprintf(f, "<desktop>%d</desktop>\n", savedata->desktop);
477
478         fprintf(f, "<numdesktops>%d</numdesktops>\n", screen_num_desktops);
479
480         fprintf(f, "<desktoplayout>\n");
481         fprintf(f, "  <orientation>%d</orientation>\n",
482                 screen_desktop_layout.orientation);
483         fprintf(f, "  <startcorner>%d</startcorner>\n",
484                 screen_desktop_layout.start_corner);
485         fprintf(f, "  <columns>%d</columns>\n",
486                 screen_desktop_layout.columns);
487         fprintf(f, "  <rows>%d</rows>\n",
488                 screen_desktop_layout.rows);
489         fprintf(f, "</desktoplayout>\n");
490
491         if (screen_desktop_names) {
492             gint i;
493
494             fprintf(f, "<desktopnames>\n");
495             for (i = 0; screen_desktop_names[i]; ++i)
496                 fprintf(f, "  <name>%s</name>\n", screen_desktop_names[i]);
497             fprintf(f, "</desktopnames>\n");
498         }
499
500         /* they are ordered top to bottom in stacking order */
501         for (it = stacking_list; it; it = g_list_next(it)) {
502             gint prex, prey, prew, preh;
503             ObClient *c;
504             gchar *t;
505
506             if (WINDOW_IS_CLIENT(it->data))
507                 c = WINDOW_AS_CLIENT(it->data);
508             else
509                 continue;
510
511             if (!client_normal(c))
512                 continue;
513
514             if (!c->sm_client_id) {
515                 ob_debug_type(OB_DEBUG_SM, "Client %s does not have a "
516                               "session id set",
517                               c->title);
518                 if (!c->wm_command) {
519                     ob_debug_type(OB_DEBUG_SM, "Client %s does not have an "
520                                   "oldskool wm_command set either. We won't "
521                                   "be saving its data",
522                                   c->title);
523                     continue;
524                 }
525             }
526
527             ob_debug_type(OB_DEBUG_SM, "Saving state for client %s",
528                           c->title);
529
530             prex = c->area.x;
531             prey = c->area.y;
532             prew = c->area.width;
533             preh = c->area.height;
534             if (c->fullscreen) {
535                 prex = c->pre_fullscreen_area.x;
536                 prey = c->pre_fullscreen_area.x;
537                 prew = c->pre_fullscreen_area.width;
538                 preh = c->pre_fullscreen_area.height;
539             }
540             if (c->max_horz) {
541                 prex = c->pre_max_area.x;
542                 prew = c->pre_max_area.width;
543             }
544             if (c->max_vert) {
545                 prey = c->pre_max_area.y;
546                 preh = c->pre_max_area.height;
547             }
548
549             if (c->sm_client_id)
550                 fprintf(f, "<window id=\"%s\">\n", c->sm_client_id);
551             else
552                 fprintf(f, "<window command=\"%s\">\n", c->wm_command);
553
554             t = g_markup_escape_text(c->name, -1);
555             fprintf(f, "\t<name>%s</name>\n", t);
556             g_free(t);
557
558             t = g_markup_escape_text(c->class, -1);
559             fprintf(f, "\t<class>%s</class>\n", t);
560             g_free(t);
561
562             t = g_markup_escape_text(c->role, -1);
563             fprintf(f, "\t<role>%s</role>\n", t);
564             g_free(t);
565
566             fprintf(f, "\t<windowtype>%d</windowtype>\n", c->type);
567
568             fprintf(f, "\t<desktop>%d</desktop>\n", c->desktop);
569             fprintf(f, "\t<x>%d</x>\n", prex);
570             fprintf(f, "\t<y>%d</y>\n", prey);
571             fprintf(f, "\t<width>%d</width>\n", prew);
572             fprintf(f, "\t<height>%d</height>\n", preh);
573             if (c->shaded)
574                 fprintf(f, "\t<shaded />\n");
575             if (c->iconic)
576                 fprintf(f, "\t<iconic />\n");
577             if (c->skip_pager)
578                 fprintf(f, "\t<skip_pager />\n");
579             if (c->skip_taskbar)
580                 fprintf(f, "\t<skip_taskbar />\n");
581             if (c->fullscreen)
582                 fprintf(f, "\t<fullscreen />\n");
583             if (c->above)
584                 fprintf(f, "\t<above />\n");
585             if (c->below)
586                 fprintf(f, "\t<below />\n");
587             if (c->max_horz)
588                 fprintf(f, "\t<max_horz />\n");
589             if (c->max_vert)
590                 fprintf(f, "\t<max_vert />\n");
591             if (c->undecorated)
592                 fprintf(f, "\t<undecorated />\n");
593             if (savedata->focus_client == c)
594                 fprintf(f, "\t<focused />\n");
595             fprintf(f, "</window>\n\n");
596         }
597
598         fprintf(f, "</openbox_session>\n");
599
600         if (fflush(f)) {
601             success = FALSE;
602             g_message(_("Error while saving the session to \"%s\": %s"),
603                       ob_sm_save_file, g_strerror(errno));
604         }
605         fclose(f);
606     }
607
608     return success;
609 }
610
611 static void session_state_free(ObSessionState *state)
612 {
613     if (state) {
614         g_free(state->id);
615         g_free(state->command);
616         g_free(state->name);
617         g_free(state->class);
618         g_free(state->role);
619
620         g_free(state);
621     }
622 }
623
624 static gboolean session_state_cmp(ObSessionState *s, ObClient *c)
625 {
626     ob_debug_type(OB_DEBUG_SM, "Comparing client against saved state: ");
627     ob_debug_type(OB_DEBUG_SM, "  client id: %s ", c->sm_client_id);
628     ob_debug_type(OB_DEBUG_SM, "  client name: %s ", c->name);
629     ob_debug_type(OB_DEBUG_SM, "  client class: %s ", c->class);
630     ob_debug_type(OB_DEBUG_SM, "  client role: %s ", c->role);
631     ob_debug_type(OB_DEBUG_SM, "  client type: %d ", c->type);
632     ob_debug_type(OB_DEBUG_SM, "  client command: %s ",
633                   c->wm_command ? c->wm_command : "(null)");
634     ob_debug_type(OB_DEBUG_SM, "  state id: %s ", s->id);
635     ob_debug_type(OB_DEBUG_SM, "  state name: %s ", s->name);
636     ob_debug_type(OB_DEBUG_SM, "  state class: %s ", s->class);
637     ob_debug_type(OB_DEBUG_SM, "  state role: %s ", s->role);
638     ob_debug_type(OB_DEBUG_SM, "  state type: %d ", s->type);
639     ob_debug_type(OB_DEBUG_SM, "  state command: %s ",
640                   s->command ? s->command : "(null)");
641
642     if ((c->sm_client_id && s->id && !strcmp(c->sm_client_id, s->id)) ||
643         (c->wm_command && s->command && !strcmp(c->wm_command, s->command)))
644     {
645         return (!strcmp(s->name, c->name) &&
646                 !strcmp(s->class, c->class) &&
647                 !strcmp(s->role, c->role) &&
648                 /* the check for type is to catch broken clients, like
649                    firefox, which open a different window on startup
650                    with the same info as the one we saved. only do this
651                    check for old windows that dont use xsmp, others should
652                    know better ! */
653                 (!s->command || c->type == s->type));
654     }
655     return FALSE;
656 }
657
658 GList* session_state_find(ObClient *c)
659 {
660     GList *it;
661
662     for (it = session_saved_state; it; it = g_list_next(it)) {
663         ObSessionState *s = it->data;
664         if (!s->matched && session_state_cmp(s, c)) {
665             s->matched = TRUE;
666             break;
667         }
668     }
669     return it;
670 }
671
672 static void session_load_file(const gchar *path)
673 {
674     ObtParseInst *i;
675     xmlNodePtr node, n, m;
676     GList *it, *inext;
677
678     i = obt_parse_instance_new();
679
680     if (!obt_parse_load_file(i, path, "openbox_session")) {
681         ob_debug_type(OB_DEBUG_SM, "ERROR: session file is missing root node");
682         obt_parse_instance_unref(i);
683         return;
684     }
685     node = obt_parse_root(i);
686
687     if ((n = obt_parse_find_node(node->children, "desktop")))
688         session_desktop = obt_parse_node_int(n);
689
690     if ((n = obt_parse_find_node(node->children, "numdesktops")))
691         session_num_desktops = obt_parse_node_int(n);
692
693     if ((n = obt_parse_find_node(node->children, "desktoplayout"))) {
694         /* make sure they are all there for it to be valid */
695         if ((m = obt_parse_find_node(n->children, "orientation")))
696             session_desktop_layout.orientation = obt_parse_node_int(m);
697         if (m && (m = obt_parse_find_node(n->children, "startcorner")))
698             session_desktop_layout.start_corner = obt_parse_node_int(m);
699         if (m && (m = obt_parse_find_node(n->children, "columns")))
700             session_desktop_layout.columns = obt_parse_node_int(m);
701         if (m && (m = obt_parse_find_node(n->children, "rows")))
702             session_desktop_layout.rows = obt_parse_node_int(m);
703         session_desktop_layout_present = m != NULL;
704     }
705
706     if ((n = obt_parse_find_node(node->children, "desktopnames"))) {
707         for (m = obt_parse_find_node(n->children, "name"); m;
708              m = obt_parse_find_node(m->next, "name"))
709         {
710             session_desktop_names = g_slist_append(session_desktop_names,
711                                                    obt_parse_node_string(m));
712         }
713     }
714
715     ob_debug_type(OB_DEBUG_SM, "loading windows");
716     for (node = obt_parse_find_node(node->children, "window"); node != NULL;
717          node = obt_parse_find_node(node->next, "window"))
718     {
719         ObSessionState *state;
720
721         state = g_new0(ObSessionState, 1);
722
723         if (!obt_parse_attr_string(node, "id", &state->id))
724             if (!obt_parse_attr_string(node, "command", &state->command))
725             goto session_load_bail;
726         if (!(n = obt_parse_find_node(node->children, "name")))
727             goto session_load_bail;
728         state->name = obt_parse_node_string(n);
729         if (!(n = obt_parse_find_node(node->children, "class")))
730             goto session_load_bail;
731         state->class = obt_parse_node_string(n);
732         if (!(n = obt_parse_find_node(node->children, "role")))
733             goto session_load_bail;
734         state->role = obt_parse_node_string(n);
735         if (!(n = obt_parse_find_node(node->children, "windowtype")))
736             goto session_load_bail;
737         state->type = obt_parse_node_int(n);
738         if (!(n = obt_parse_find_node(node->children, "desktop")))
739             goto session_load_bail;
740         state->desktop = obt_parse_node_int(n);
741         if (!(n = obt_parse_find_node(node->children, "x")))
742             goto session_load_bail;
743         state->x = obt_parse_node_int(n);
744         if (!(n = obt_parse_find_node(node->children, "y")))
745             goto session_load_bail;
746         state->y = obt_parse_node_int(n);
747         if (!(n = obt_parse_find_node(node->children, "width")))
748             goto session_load_bail;
749         state->w = obt_parse_node_int(n);
750         if (!(n = obt_parse_find_node(node->children, "height")))
751             goto session_load_bail;
752         state->h = obt_parse_node_int(n);
753
754         state->shaded =
755             obt_parse_find_node(node->children, "shaded") != NULL;
756         state->iconic =
757             obt_parse_find_node(node->children, "iconic") != NULL;
758         state->skip_pager =
759             obt_parse_find_node(node->children, "skip_pager") != NULL;
760         state->skip_taskbar =
761             obt_parse_find_node(node->children, "skip_taskbar") != NULL;
762         state->fullscreen =
763             obt_parse_find_node(node->children, "fullscreen") != NULL;
764         state->above =
765             obt_parse_find_node(node->children, "above") != NULL;
766         state->below =
767             obt_parse_find_node(node->children, "below") != NULL;
768         state->max_horz =
769             obt_parse_find_node(node->children, "max_horz") != NULL;
770         state->max_vert =
771             obt_parse_find_node(node->children, "max_vert") != NULL;
772         state->undecorated =
773             obt_parse_find_node(node->children, "undecorated") != NULL;
774         state->focused =
775             obt_parse_find_node(node->children, "focused") != NULL;
776
777         /* save this. they are in the file in stacking order, so preserve
778            that order here */
779         session_saved_state = g_list_append(session_saved_state, state);
780         ob_debug_type(OB_DEBUG_SM, "loaded %s", state->name);
781         continue;
782
783     session_load_bail:
784         ob_debug_type(OB_DEBUG_SM, "loading FAILED");
785         session_state_free(state);
786     }
787
788     /* Remove any duplicates.  This means that if two windows (or more) are
789        saved with the same session state, we won't restore a session for any
790        of them because we don't know what window to put what on. AHEM FIREFOX.
791
792        This is going to be an O(2^n) kind of operation unfortunately.
793     */
794     for (it = session_saved_state; it; it = inext) {
795         GList *jt, *jnext;
796         gboolean founddup = FALSE;
797         ObSessionState *s1 = it->data;
798
799         inext = g_list_next(it);
800
801         for (jt = g_list_next(it); jt; jt = jnext) {
802             ObSessionState *s2 = jt->data;
803             gboolean match;
804
805             jnext = g_list_next(jt);
806
807             if (s1->id && s2->id)
808                 match = strcmp(s1->id, s2->id) == 0;
809             else if (s1->command && s2->command)
810                 match = strcmp(s1->command, s2->command) == 0;
811             else
812                 match = FALSE;
813
814             if (match &&
815                 !strcmp(s1->name, s2->name) &&
816                 !strcmp(s1->class, s2->class) &&
817                 !strcmp(s1->role, s2->role))
818             {
819                 ob_debug_type(OB_DEBUG_SM, "removing duplicate %s", s2->name);
820                 session_state_free(s2);
821                 session_saved_state =
822                     g_list_delete_link(session_saved_state, jt);
823                 founddup = TRUE;
824             }
825         }
826
827         if (founddup) {
828             ob_debug_type(OB_DEBUG_SM, "removing duplicate %s", s1->name);
829             session_state_free(s1);
830             session_saved_state = g_list_delete_link(session_saved_state, it);
831         }
832     }
833
834     obt_parse_instance_unref(i);
835 }
836
837 void session_request_logout(gboolean silent)
838 {
839     if (sm_conn) {
840         SmcRequestSaveYourself(sm_conn,
841                                SmSaveGlobal,
842                                TRUE, /* logout */
843                                (silent ?
844                                 SmInteractStyleNone : SmInteractStyleAny),
845                                TRUE,  /* if false, with GSM, it shows the old
846                                          logout prompt */
847                                TRUE); /* global */
848     }
849     else
850         g_message(_("Not connected to a session manager"));
851 }
852
853 #endif