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