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