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