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