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