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