1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
3 session.c for the Openbox window manager
4 Copyright (c) 2003-2007 Dana Jansens
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.
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.
16 See the COPYING file for a copy of the GNU General Public License.
19 /* This session code is largely inspired by metacity code. */
25 GList
*session_saved_state
= NULL
;
26 gint session_desktop
= -1;
29 void session_startup(gint argc
, gchar
**argv
) {}
30 void session_shutdown(gboolean permanent
) {}
31 GList
* session_state_find(struct _ObClient
*c
) { return NULL
; }
41 #include "parser/parse.h"
48 # include <sys/types.h>
52 #include <X11/SM/SMlib.h>
54 #define SM_ERR_LEN 1024
56 static SmcConn sm_conn
;
58 static gchar
**sm_argv
;
60 /* Data saved from the first level save yourself */
62 ObClient
*focus_client
;
66 static gboolean
session_connect();
68 static void session_load_file(const gchar
*path
);
69 static gboolean
session_save_to_file(const ObSMSaveData
*savedata
);
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();
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
);
85 static gboolean
session_state_cmp(ObSessionState
*s
, ObClient
*c
);
86 static void session_state_free(ObSessionState
*state
);
88 void session_startup(gint argc
, gchar
**argv
)
92 if (!ob_sm_use
) return;
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
));
104 if (ob_sm_save_file
!= NULL
) {
106 ob_debug_type(OB_DEBUG_SM
, "Loading from session file %s\n",
108 session_load_file(ob_sm_save_file
);
113 /* this algo is from metacity */
114 filename
= g_strdup_printf("%u-%u-%u.obs",
118 ob_sm_save_file
= g_build_filename(dir
, filename
, NULL
);
122 if (session_connect()) {
123 session_setup_program();
124 session_setup_user();
125 session_setup_restart_style(TRUE
);
127 session_setup_priority();
128 session_setup_clone_command();
134 void session_shutdown(gboolean permanent
)
136 if (!ob_sm_use
) return;
139 /* if permanent is true then we will change our session state so that
140 the SM won't run us again */
142 session_setup_restart_style(FALSE
);
144 SmcCloseConnection(sm_conn
, 0, NULL
);
146 while (session_saved_state
) {
147 session_state_free(session_saved_state
->data
);
148 session_saved_state
= g_list_delete_link(session_saved_state
,
149 session_saved_state
);
154 /*! Connect to the session manager and set up our callback functions */
155 static gboolean
session_connect()
159 gchar sm_err
[SM_ERR_LEN
];
161 /* set up our callback functions */
162 cb
.save_yourself
.callback
= sm_save_yourself
;
163 cb
.save_yourself
.client_data
= NULL
;
164 cb
.die
.callback
= sm_die
;
165 cb
.die
.client_data
= NULL
;
166 cb
.save_complete
.callback
= sm_save_complete
;
167 cb
.save_complete
.client_data
= NULL
;
168 cb
.shutdown_cancelled
.callback
= sm_shutdown_cancelled
;
169 cb
.shutdown_cancelled
.client_data
= NULL
;
171 /* connect to the server */
173 ob_debug_type(OB_DEBUG_SM
, "Connecting to SM with id: %s\n",
174 oldid
? oldid
: "(null)");
175 sm_conn
= SmcOpenConnection(NULL
, NULL
, 1, 0,
176 SmcSaveYourselfProcMask
|
178 SmcSaveCompleteProcMask
|
179 SmcShutdownCancelledProcMask
,
180 &cb
, oldid
, &ob_sm_id
,
181 SM_ERR_LEN
-1, sm_err
);
183 ob_debug_type(OB_DEBUG_SM
, "Connected to SM with id: %s\n", ob_sm_id
);
185 ob_debug("Failed to connect to session manager: %s\n", sm_err
);
186 return sm_conn
!= NULL
;
189 static void session_setup_program()
193 .length
= strlen(sm_argv
[0]) + 1
196 .name
= g_strdup(SmProgram
),
197 .type
= g_strdup(SmARRAY8
),
201 SmProp
*list
= &prop
;
202 ob_debug_type(OB_DEBUG_SM
, "Setting program: %s\n", sm_argv
[0]);
203 SmcSetProperties(sm_conn
, 1, &list
);
208 static void session_setup_user()
210 char *user
= g_strdup(g_get_user_name());
214 .length
= strlen(user
) + 1
217 .name
= g_strdup(SmUserID
),
218 .type
= g_strdup(SmARRAY8
),
222 SmProp
*list
= &prop
;
223 ob_debug_type(OB_DEBUG_SM
, "Setting user: %s\n", user
);
224 SmcSetProperties(sm_conn
, 1, &list
);
230 static void session_setup_restart_style(gboolean restart
)
232 gchar restart_hint
= restart
? SmRestartImmediately
: SmRestartIfRunning
;
235 .value
= &restart_hint
,
239 .name
= g_strdup(SmRestartStyleHint
),
240 .type
= g_strdup(SmCARD8
),
244 SmProp
*list
= &prop
;
245 ob_debug_type(OB_DEBUG_SM
, "Setting restart: %d\n", restart
);
246 SmcSetProperties(sm_conn
, 1, &list
);
251 static void session_setup_pid()
253 gchar
*pid
= g_strdup_printf("%ld", (glong
) getpid());
257 .length
= strlen(pid
) + 1
260 .name
= g_strdup(SmProcessID
),
261 .type
= g_strdup(SmARRAY8
),
265 SmProp
*list
= &prop
;
266 ob_debug_type(OB_DEBUG_SM
, "Setting pid: %s\n", pid
);
267 SmcSetProperties(sm_conn
, 1, &list
);
273 /*! This is a gnome-session-manager extension */
274 static void session_setup_priority()
276 gchar priority
= 20; /* 20 is a lower prioity to run before other apps */
283 .name
= g_strdup("_GSM_Priority"),
284 .type
= g_strdup(SmCARD8
),
288 SmProp
*list
= &prop
;
289 ob_debug_type(OB_DEBUG_SM
, "Setting priority: %d\n", priority
);
290 SmcSetProperties(sm_conn
, 1, &list
);
295 static void session_setup_clone_command()
299 SmPropValue
*vals
= g_new(SmPropValue
, sm_argc
);
301 .name
= g_strdup(SmCloneCommand
),
302 .type
= g_strdup(SmLISTofARRAY8
),
306 SmProp
*list
= &prop
;
308 ob_debug_type(OB_DEBUG_SM
, "Setting clone command: (%d)\n", sm_argc
);
309 for (i
= 0; i
< sm_argc
; ++i
) {
310 vals
[i
].value
= sm_argv
[i
];
311 vals
[i
].length
= strlen(sm_argv
[i
]) + 1;
312 ob_debug_type(OB_DEBUG_SM
, " %s\n", vals
[i
].value
);
315 SmcSetProperties(sm_conn
, 1, &list
);
321 static void session_setup_restart_command()
325 SmPropValue
*vals
= g_new(SmPropValue
, sm_argc
+ 4);
327 .name
= g_strdup(SmRestartCommand
),
328 .type
= g_strdup(SmLISTofARRAY8
),
329 .num_vals
= sm_argc
+ 4,
332 SmProp
*list
= &prop
;
334 ob_debug_type(OB_DEBUG_SM
, "Setting restart command: (%d)\n", sm_argc
+4);
335 for (i
= 0; i
< sm_argc
; ++i
) {
336 vals
[i
].value
= sm_argv
[i
];
337 vals
[i
].length
= strlen(sm_argv
[i
]) + 1;
338 ob_debug_type(OB_DEBUG_SM
, " %s\n", vals
[i
].value
);
341 vals
[i
].value
= g_strdup("--sm-client-id");
342 vals
[i
].length
= strlen("--sm-client-id") + 1;
343 vals
[i
+1].value
= ob_sm_id
;
344 vals
[i
+1].length
= strlen(ob_sm_id
) + 1;
345 ob_debug_type(OB_DEBUG_SM
, " %s\n", vals
[i
].value
);
346 ob_debug_type(OB_DEBUG_SM
, " %s\n", vals
[i
+1].value
);
348 vals
[i
+2].value
= g_strdup("--sm-save-file");
349 vals
[i
+2].length
= strlen("--sm-save-file") + 1;
350 vals
[i
+3].value
= ob_sm_save_file
;
351 vals
[i
+3].length
= strlen(ob_sm_save_file
) + 1;
352 ob_debug_type(OB_DEBUG_SM
, " %s\n", vals
[i
+2].value
);
353 ob_debug_type(OB_DEBUG_SM
, " %s\n", vals
[i
+3].value
);
355 SmcSetProperties(sm_conn
, 1, &list
);
358 g_free(vals
[i
].value
);
359 g_free(vals
[i
+2].value
);
363 static ObSMSaveData
*sm_save_get_data()
365 ObSMSaveData
*savedata
= g_new0(ObSMSaveData
, 1);
366 savedata
->focus_client
= focus_client
;
367 savedata
->desktop
= screen_desktop
;
371 static void sm_save_yourself_2(SmcConn conn
, SmPointer data
)
374 ObSMSaveData
*savedata
= data
;
376 /* save the current state */
377 ob_debug_type(OB_DEBUG_SM
, "Session save phase 2 requested\n");
378 ob_debug_type(OB_DEBUG_SM
,
379 " Saving session to file '%s'\n", ob_sm_save_file
);
380 if (savedata
== NULL
)
381 savedata
= sm_save_get_data();
382 success
= session_save_to_file(savedata
);
385 /* tell the session manager how to restore this state */
386 if (success
) session_setup_restart_command();
388 ob_debug_type(OB_DEBUG_SM
, "Saving is done (success = %d)\n", success
);
389 SmcSaveYourselfDone(conn
, success
);
393 static void sm_save_yourself(SmcConn conn
, SmPointer data
, gint save_type
,
394 Bool shutdown
, gint interact_style
, Bool fast
)
396 ObSMSaveData
*savedata
= NULL
;
399 ob_debug_type(OB_DEBUG_SM
, "Session save requested\n");
401 vendor
= SmcVendor(sm_conn
);
402 ob_debug_type(OB_DEBUG_SM
, "Session manager's vendor: %s\n", vendor
);
404 if (!strcmp(vendor
, "KDE")) {
405 /* ksmserver guarantees that phase 1 will complete before allowing any
406 clients interaction, so we can save this sanely here before they
407 get messed up from interaction */
408 savedata
= sm_save_get_data();
412 if (!SmcRequestSaveYourselfPhase2(conn
, sm_save_yourself_2
, savedata
)) {
413 ob_debug_type(OB_DEBUG_SM
, "Requst for phase 2 failed\n");
415 SmcSaveYourselfDone(conn
, FALSE
);
419 static void sm_die(SmcConn conn
, SmPointer data
)
421 ob_debug_type(OB_DEBUG_SM
, "Die requested\n");
425 static void sm_save_complete(SmcConn conn
, SmPointer data
)
427 ob_debug_type(OB_DEBUG_SM
, "Save complete\n");
430 static void sm_shutdown_cancelled(SmcConn conn
, SmPointer data
)
432 ob_debug_type(OB_DEBUG_SM
, "Shutdown cancelled\n");
435 static gboolean
session_save_to_file(const ObSMSaveData
*savedata
)
439 gboolean success
= TRUE
;
441 f
= fopen(ob_sm_save_file
, "w");
444 g_message(_("Unable to save the session to '%s': %s"),
445 ob_sm_save_file
, g_strerror(errno
));
447 fprintf(f
, "<?xml version=\"1.0\"?>\n\n");
448 fprintf(f
, "<openbox_session>\n\n");
450 fprintf(f
, "<desktop>%d</desktop>\n", savedata
->desktop
);
452 /* they are ordered top to bottom in stacking order */
453 for (it
= stacking_list
; it
; it
= g_list_next(it
)) {
454 gint prex
, prey
, prew
, preh
;
458 if (WINDOW_IS_CLIENT(it
->data
))
459 c
= WINDOW_AS_CLIENT(it
->data
);
463 if (!client_normal(c
))
466 if (!c
->sm_client_id
) {
467 ob_debug_type(OB_DEBUG_SM
, "Client %s does not have a "
470 if (!c
->wm_command
) {
471 ob_debug_type(OB_DEBUG_SM
, "Client %s does not have an "
472 "oldskool wm_command set either. We won't "
473 "be saving its data\n",
479 ob_debug_type(OB_DEBUG_SM
, "Saving state for client %s\n",
484 prew
= c
->area
.width
;
485 preh
= c
->area
.height
;
487 prex
= c
->pre_fullscreen_area
.x
;
488 prey
= c
->pre_fullscreen_area
.x
;
489 prew
= c
->pre_fullscreen_area
.width
;
490 preh
= c
->pre_fullscreen_area
.height
;
493 prex
= c
->pre_max_area
.x
;
494 prew
= c
->pre_max_area
.width
;
497 prey
= c
->pre_max_area
.y
;
498 preh
= c
->pre_max_area
.height
;
502 fprintf(f
, "<window id=\"%s\">\n", c
->sm_client_id
);
504 fprintf(f
, "<window command=\"%s\">\n", c
->wm_command
);
506 t
= g_markup_escape_text(c
->name
, -1);
507 fprintf(f
, "\t<name>%s</name>\n", t
);
510 t
= g_markup_escape_text(c
->class, -1);
511 fprintf(f
, "\t<class>%s</class>\n", t
);
514 t
= g_markup_escape_text(c
->role
, -1);
515 fprintf(f
, "\t<role>%s</role>\n", t
);
518 fprintf(f
, "\t<windowtype>%d</windowtype>\n", c
->type
);
520 fprintf(f
, "\t<desktop>%d</desktop>\n", c
->desktop
);
521 fprintf(f
, "\t<x>%d</x>\n", prex
);
522 fprintf(f
, "\t<y>%d</y>\n", prey
);
523 fprintf(f
, "\t<width>%d</width>\n", prew
);
524 fprintf(f
, "\t<height>%d</height>\n", preh
);
526 fprintf(f
, "\t<shaded />\n");
528 fprintf(f
, "\t<iconic />\n");
530 fprintf(f
, "\t<skip_pager />\n");
532 fprintf(f
, "\t<skip_taskbar />\n");
534 fprintf(f
, "\t<fullscreen />\n");
536 fprintf(f
, "\t<above />\n");
538 fprintf(f
, "\t<below />\n");
540 fprintf(f
, "\t<max_horz />\n");
542 fprintf(f
, "\t<max_vert />\n");
544 fprintf(f
, "\t<undecorated />\n");
545 if (savedata
->focus_client
== c
)
546 fprintf(f
, "\t<focused />\n");
547 fprintf(f
, "</window>\n\n");
550 fprintf(f
, "</openbox_session>\n");
554 g_message(_("Error while saving the session to '%s': %s"),
555 ob_sm_save_file
, g_strerror(errno
));
563 static void session_state_free(ObSessionState
*state
)
567 g_free(state
->command
);
569 g_free(state
->class);
576 static gboolean
session_state_cmp(ObSessionState
*s
, ObClient
*c
)
578 ob_debug_type(OB_DEBUG_SM
, "Comparing client against saved state: \n");
579 ob_debug_type(OB_DEBUG_SM
, " client id: %s \n", c
->sm_client_id
);
580 ob_debug_type(OB_DEBUG_SM
, " client name: %s \n", c
->name
);
581 ob_debug_type(OB_DEBUG_SM
, " client class: %s \n", c
->class);
582 ob_debug_type(OB_DEBUG_SM
, " client role: %s \n", c
->role
);
583 ob_debug_type(OB_DEBUG_SM
, " client type: %s \n", c
->type
);
584 ob_debug_type(OB_DEBUG_SM
, " client command: %s \n",
585 c
->wm_command
? c
->wm_command
: "(null)");
586 ob_debug_type(OB_DEBUG_SM
, " state id: %s \n", s
->id
);
587 ob_debug_type(OB_DEBUG_SM
, " state name: %s \n", s
->name
);
588 ob_debug_type(OB_DEBUG_SM
, " state class: %s \n", s
->class);
589 ob_debug_type(OB_DEBUG_SM
, " state role: %s \n", s
->role
);
590 ob_debug_type(OB_DEBUG_SM
, " state type: %s \n", s
->type
);
591 ob_debug_type(OB_DEBUG_SM
, " state command: %s \n",
592 s
->command
? s
->command
: "(null)");
594 if ((c
->sm_client_id
&& s
->id
&& !strcmp(c
->sm_client_id
, s
->id
)) ||
595 (c
->wm_command
&& s
->command
&& !strcmp(c
->wm_command
, s
->command
)))
597 return (!strcmp(s
->name
, c
->name
) &&
598 !strcmp(s
->class, c
->class) &&
599 !strcmp(s
->role
, c
->role
) &&
600 /* the check for type is to catch broken clients, like
601 firefox, which open a different window on startup
602 with the same info as the one we saved. only do this
603 check for old windows that dont use xsmp, others should
605 (!s
->command
|| c
->type
== s
->type
));
610 GList
* session_state_find(ObClient
*c
)
614 for (it
= session_saved_state
; it
; it
= g_list_next(it
)) {
615 ObSessionState
*s
= it
->data
;
616 if (!s
->matched
&& session_state_cmp(s
, c
)) {
624 static void session_load_file(const gchar
*path
)
630 if (!parse_load(path
, "openbox_session", &doc
, &node
))
633 if ((n
= parse_find_node("desktop", node
->children
)))
634 session_desktop
= parse_int(doc
, n
);
636 for (node
= parse_find_node("window", node
->children
); node
!= NULL
;
637 node
= parse_find_node("window", node
->next
))
639 ObSessionState
*state
;
641 state
= g_new0(ObSessionState
, 1);
643 if (!parse_attr_string("id", node
, &state
->id
))
644 if (!parse_attr_string("command", node
, &state
->command
))
645 goto session_load_bail
;
646 if (!(n
= parse_find_node("name", node
->children
)))
647 goto session_load_bail
;
648 state
->name
= parse_string(doc
, n
);
649 if (!(n
= parse_find_node("class", node
->children
)))
650 goto session_load_bail
;
651 state
->class = parse_string(doc
, n
);
652 if (!(n
= parse_find_node("role", node
->children
)))
653 goto session_load_bail
;
654 state
->role
= parse_string(doc
, n
);
655 if (!(n
= parse_find_node("windowtype", node
->children
)))
656 goto session_load_bail
;
657 state
->type
= parse_int(doc
, n
);
658 if (!(n
= parse_find_node("desktop", node
->children
)))
659 goto session_load_bail
;
660 state
->desktop
= parse_int(doc
, n
);
661 if (!(n
= parse_find_node("x", node
->children
)))
662 goto session_load_bail
;
663 state
->x
= parse_int(doc
, n
);
664 if (!(n
= parse_find_node("y", node
->children
)))
665 goto session_load_bail
;
666 state
->y
= parse_int(doc
, n
);
667 if (!(n
= parse_find_node("width", node
->children
)))
668 goto session_load_bail
;
669 state
->w
= parse_int(doc
, n
);
670 if (!(n
= parse_find_node("height", node
->children
)))
671 goto session_load_bail
;
672 state
->h
= parse_int(doc
, n
);
675 parse_find_node("shaded", node
->children
) != NULL
;
677 parse_find_node("iconic", node
->children
) != NULL
;
679 parse_find_node("skip_pager", node
->children
) != NULL
;
680 state
->skip_taskbar
=
681 parse_find_node("skip_taskbar", node
->children
) != NULL
;
683 parse_find_node("fullscreen", node
->children
) != NULL
;
685 parse_find_node("above", node
->children
) != NULL
;
687 parse_find_node("below", node
->children
) != NULL
;
689 parse_find_node("max_horz", node
->children
) != NULL
;
691 parse_find_node("max_vert", node
->children
) != NULL
;
693 parse_find_node("undecorated", node
->children
) != NULL
;
695 parse_find_node("focused", node
->children
) != NULL
;
697 /* save this. they are in the file in stacking order, so preserve
699 session_saved_state
= g_list_append(session_saved_state
, state
);
703 session_state_free(state
);
706 /* Remove any duplicates. This means that if two windows (or more) are
707 saved with the same session state, we won't restore a session for any
708 of them because we don't know what window to put what on. AHEM FIREFOX.
710 This is going to be an O(2^n) kind of operation unfortunately.
712 for (it
= session_saved_state
; it
; it
= inext
) {
714 gboolean founddup
= FALSE
;
715 ObSessionState
*s1
= it
->data
;
717 inext
= g_list_next(it
);
719 for (jt
= g_list_next(it
); jt
; jt
= jnext
) {
720 ObSessionState
*s2
= jt
->data
;
723 jnext
= g_list_next(jt
);
725 if (s1
->id
&& s2
->id
)
726 match
= strcmp(s1
->id
, s2
->id
) == 0;
727 else if (s1
->command
&& s2
->command
)
728 match
= strcmp(s1
->command
, s2
->command
) == 0;
733 !strcmp(s1
->name
, s2
->name
) &&
734 !strcmp(s1
->class, s2
->class) &&
735 !strcmp(s1
->role
, s2
->role
))
737 session_state_free(s2
);
738 session_saved_state
=
739 g_list_delete_link(session_saved_state
, jt
);
745 session_state_free(s1
);
746 session_saved_state
= g_list_delete_link(session_saved_state
, it
);