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