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
session_startup(gint argc,gchar ** argv)33 void session_startup(gint argc, gchar **argv) {}
session_shutdown(gboolean permanent)34 void session_shutdown(gboolean permanent) {}
session_state_find(struct _ObClient * c)35 GList* session_state_find(struct _ObClient *c) { return NULL; }
session_request_logout(gboolean silent)36 void session_request_logout(gboolean silent) {}
session_connected(void)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/xml.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 
session_startup(gint argc,gchar ** argv)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 
session_shutdown(gboolean permanent)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 
session_connected(void)163 gboolean session_connected(void)
164 {
165     return !!sm_conn;
166 }
167 
168 /*! Connect to the session manager and set up our callback functions */
session_connect(void)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 
session_setup_program(void)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 
session_setup_user(void)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 
session_setup_restart_style(gboolean restart)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 
session_setup_pid(void)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 */
session_setup_priority(void)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 
session_setup_clone_command(void)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 
session_setup_restart_command(void)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 
sm_save_get_data(void)377 static ObSMSaveData *sm_save_get_data(void)
378 {
379     ObSMSaveData *savedata = g_slice_new0(ObSMSaveData);
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 
sm_save_yourself_2(SmcConn conn,SmPointer data)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_slice_free(ObSMSaveData, 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 
sm_save_yourself(SmcConn conn,SmPointer data,gint save_type,Bool shutdown,gint interact_style,Bool fast)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_slice_free(ObSMSaveData, savedata);
447         SmcSaveYourselfDone(conn, FALSE);
448     }
449 }
450 
sm_die(SmcConn conn,SmPointer data)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 
sm_save_complete(SmcConn conn,SmPointer data)457 static void sm_save_complete(SmcConn conn, SmPointer data)
458 {
459     ob_debug_type(OB_DEBUG_SM, "Save complete");
460 }
461 
sm_shutdown_cancelled(SmcConn conn,SmPointer data)462 static void sm_shutdown_cancelled(SmcConn conn, SmPointer data)
463 {
464     ob_debug_type(OB_DEBUG_SM, "Shutdown cancelled");
465 }
466 
session_save_to_file(const ObSMSaveData * savedata)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 
session_state_free(ObSessionState * state)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_slice_free(ObSessionState, state);
634     }
635 }
636 
session_state_cmp(ObSessionState * s,ObClient * c)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 
session_state_find(ObClient * c)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 
session_load_file(const gchar * path)685 static void session_load_file(const gchar *path)
686 {
687     ObtXmlInst *i;
688     xmlNodePtr node, n, m;
689     GList *it, *inext;
690 
691     i = obt_xml_instance_new();
692 
693     if (!obt_xml_load_file(i, path, "openbox_session")) {
694         ob_debug_type(OB_DEBUG_SM, "ERROR: session file is missing root node");
695         obt_xml_instance_unref(i);
696         return;
697     }
698     node = obt_xml_root(i);
699 
700     if ((n = obt_xml_find_node(node->children, "desktop")))
701         session_desktop = obt_xml_node_int(n);
702 
703     if ((n = obt_xml_find_node(node->children, "numdesktops")))
704         session_num_desktops = obt_xml_node_int(n);
705 
706     if ((n = obt_xml_find_node(node->children, "desktoplayout"))) {
707         /* make sure they are all there for it to be valid */
708         if ((m = obt_xml_find_node(n->children, "orientation")))
709             session_desktop_layout.orientation = obt_xml_node_int(m);
710         if (m && (m = obt_xml_find_node(n->children, "startcorner")))
711             session_desktop_layout.start_corner = obt_xml_node_int(m);
712         if (m && (m = obt_xml_find_node(n->children, "columns")))
713             session_desktop_layout.columns = obt_xml_node_int(m);
714         if (m && (m = obt_xml_find_node(n->children, "rows")))
715             session_desktop_layout.rows = obt_xml_node_int(m);
716         session_desktop_layout_present = m != NULL;
717     }
718 
719     if ((n = obt_xml_find_node(node->children, "desktopnames"))) {
720         for (m = obt_xml_find_node(n->children, "name"); m;
721              m = obt_xml_find_node(m->next, "name"))
722         {
723             session_desktop_names = g_slist_append(session_desktop_names,
724                                                    obt_xml_node_string(m));
725         }
726     }
727 
728     ob_debug_type(OB_DEBUG_SM, "loading windows");
729     for (node = obt_xml_find_node(node->children, "window"); node != NULL;
730          node = obt_xml_find_node(node->next, "window"))
731     {
732         ObSessionState *state;
733 
734         state = g_slice_new0(ObSessionState);
735 
736         if (!obt_xml_attr_string(node, "id", &state->id))
737             if (!obt_xml_attr_string(node, "command", &state->command))
738             goto session_load_bail;
739         if (!(n = obt_xml_find_node(node->children, "name")))
740             goto session_load_bail;
741         state->name = obt_xml_node_string(n);
742         if (!(n = obt_xml_find_node(node->children, "class")))
743             goto session_load_bail;
744         state->class = obt_xml_node_string(n);
745         if (!(n = obt_xml_find_node(node->children, "role")))
746             goto session_load_bail;
747         state->role = obt_xml_node_string(n);
748         if (!(n = obt_xml_find_node(node->children, "windowtype")))
749             goto session_load_bail;
750         state->type = obt_xml_node_int(n);
751         if (!(n = obt_xml_find_node(node->children, "desktop")))
752             goto session_load_bail;
753         state->desktop = obt_xml_node_int(n);
754         if (!(n = obt_xml_find_node(node->children, "x")))
755             goto session_load_bail;
756         state->x = obt_xml_node_int(n);
757         if (!(n = obt_xml_find_node(node->children, "y")))
758             goto session_load_bail;
759         state->y = obt_xml_node_int(n);
760         if (!(n = obt_xml_find_node(node->children, "width")))
761             goto session_load_bail;
762         state->w = obt_xml_node_int(n);
763         if (!(n = obt_xml_find_node(node->children, "height")))
764             goto session_load_bail;
765         state->h = obt_xml_node_int(n);
766 
767         state->shaded =
768             obt_xml_find_node(node->children, "shaded") != NULL;
769         state->iconic =
770             obt_xml_find_node(node->children, "iconic") != NULL;
771         state->skip_pager =
772             obt_xml_find_node(node->children, "skip_pager") != NULL;
773         state->skip_taskbar =
774             obt_xml_find_node(node->children, "skip_taskbar") != NULL;
775         state->fullscreen =
776             obt_xml_find_node(node->children, "fullscreen") != NULL;
777         state->above =
778             obt_xml_find_node(node->children, "above") != NULL;
779         state->below =
780             obt_xml_find_node(node->children, "below") != NULL;
781         state->max_horz =
782             obt_xml_find_node(node->children, "max_horz") != NULL;
783         state->max_vert =
784             obt_xml_find_node(node->children, "max_vert") != NULL;
785         state->undecorated =
786             obt_xml_find_node(node->children, "undecorated") != NULL;
787         state->focused =
788             obt_xml_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_xml_instance_unref(i);
848 }
849 
session_request_logout(gboolean silent)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
867