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