move session code out of openbox.c all into session.c
[mikachu/openbox.git] / openbox / session.c
1 /* This session code is largely inspired by metacity code. */
2
3 #ifndef USE_SM
4
5 #include "session.h"
6 #include "client.h"
7
8 GList *session_saved_state;
9
10 void session_startup(int *argc, char ***argv) {}
11 void session_shutdown() {}
12 GList* session_state_find(ObClient *c) { return NULL; }
13 gboolean session_state_cmp(ObSessionState *s, ObClient *c) { return FALSE; }
14 void session_state_free(ObSessionState *state) {}
15
16 #else
17
18 #include "debug.h"
19 #include "openbox.h"
20 #include "session.h"
21 #include "client.h"
22 #include "prop.h"
23 #include "gettext.h"
24 #include "parser/parse.h"
25
26 #include <time.h>
27 #include <errno.h>
28 #include <stdio.h>
29
30 #ifdef HAVE_UNISTD_H
31 #  include <sys/types.h>
32 #  include <unistd.h>
33 #endif
34
35 #include <X11/SM/SMlib.h>
36
37 GList *session_saved_state;
38
39 static gboolean    sm_disable;
40 static SmcConn     sm_conn;
41 static gchar      *save_file;
42 static gchar      *sm_id;
43 static gint        sm_argc;
44 static gchar     **sm_argv;
45
46 static void session_load(char *path);
47 static gboolean session_save();
48
49 static void sm_save_yourself(SmcConn conn, SmPointer data, int save_type,
50                              Bool shutdown, int interact_style, Bool fast);
51 static void sm_die(SmcConn conn, SmPointer data);
52 static void sm_save_complete(SmcConn conn, SmPointer data);
53 static void sm_shutdown_cancelled(SmcConn conn, SmPointer data);
54
55 static void save_commands()
56 {
57     SmProp *props[2];
58     SmProp prop_cmd = { SmCloneCommand, SmLISTofARRAY8, 1, };
59     SmProp prop_res = { SmRestartCommand, SmLISTofARRAY8, };
60     gint i;
61
62     prop_cmd.vals = g_new(SmPropValue, sm_argc);
63     prop_cmd.num_vals = sm_argc;
64     for (i = 0; i < sm_argc; ++i) {
65         prop_cmd.vals[i].value = sm_argv[i];
66         prop_cmd.vals[i].length = strlen(sm_argv[i]);
67     }
68
69     prop_res.vals = g_new(SmPropValue, sm_argc + 2);
70     prop_res.num_vals = sm_argc + 2;
71     for (i = 0; i < sm_argc; ++i) { 
72         prop_res.vals[i].value = sm_argv[i];
73         prop_res.vals[i].length = strlen(sm_argv[i]);
74     }
75
76     if (save_file) {
77         prop_res.vals[i].value = "--sm-save-file";
78         prop_res.vals[i++].length = strlen("--sm-save-file");
79         prop_res.vals[i].value = save_file;
80         prop_res.vals[i++].length = strlen(save_file);
81     } else {
82         prop_res.vals[i].value = "--sm-client-id";
83         prop_res.vals[i++].length = strlen("--sm-client-id");
84         prop_res.vals[i].value = sm_id;
85         prop_res.vals[i++].length = strlen(sm_id);
86     }
87
88     props[0] = &prop_res;
89     props[1] = &prop_cmd;
90     SmcSetProperties(sm_conn, 2, props);
91
92     g_free(prop_res.vals);
93     g_free(prop_cmd.vals);
94 }
95
96 static void remove_two_args(int *argc, char ***argv, int index)
97 {
98     int i;
99
100     for (i = index; i < index + 2; ++i)
101         (*argv)[i] = (*argv)[i+2];
102     *argc -= 2;
103 }
104
105 static void parse_args(int *argc, char ***argv)
106 {
107     int i;
108
109     for (i = 1; i < *argc; ++i) {
110         if (!strcmp((*argv)[i], "--sm-client-id")) {
111             if (i == *argc - 1) /* no args left */
112                 g_printerr(_("--sm-client-id requires an argument\n"));
113             else {
114                 sm_id = g_strdup((*argv)[i+1]);
115                 remove_two_args(argc, argv, i);
116                 ++i;
117             }
118         } else if (!strcmp((*argv)[i], "--sm-save-file")) {
119             if (i == *argc - 1) /* no args left */
120                 g_printerr(_("--sm-save-file requires an argument\n"));
121             else {
122                 save_file = g_strdup((*argv)[i+1]);
123                 remove_two_args(argc, argv, i);
124                 ++i;
125             }
126         } else if (!strcmp((*argv)[i], "--sm-disable")) {
127             sm_disable = TRUE;
128         }
129     }
130 }
131
132 void session_startup(int *argc, char ***argv)
133 {
134 #define SM_ERR_LEN 1024
135
136     SmcCallbacks cb;
137     char sm_err[SM_ERR_LEN];
138
139     parse_args(argc, argv);
140
141     if (sm_disable)
142         return;
143
144     if (save_file)
145         session_load(save_file);
146
147     sm_argc = *argc;
148     sm_argv = *argv;
149
150     cb.save_yourself.callback = sm_save_yourself;
151     cb.save_yourself.client_data = NULL;
152
153     cb.die.callback = sm_die;
154     cb.die.client_data = NULL;
155
156     cb.save_complete.callback = sm_save_complete;
157     cb.save_complete.client_data = NULL;
158
159     cb.shutdown_cancelled.callback = sm_shutdown_cancelled;
160     cb.shutdown_cancelled.client_data = NULL;
161
162     sm_conn = SmcOpenConnection(NULL, NULL, 1, 0,
163                                 SmcSaveYourselfProcMask |
164                                 SmcDieProcMask |
165                                 SmcSaveCompleteProcMask |
166                                 SmcShutdownCancelledProcMask,
167                                 &cb, sm_id, &sm_id,
168                                 SM_ERR_LEN, sm_err);
169     if (sm_conn == NULL)
170         g_warning("Failed to connect to session manager: %s", sm_err);
171     else {
172         SmPropValue val_prog;
173         SmPropValue val_uid;
174         SmPropValue val_hint; 
175         SmPropValue val_pri;
176         SmPropValue val_pid;
177         SmProp prop_prog = { SmProgram, SmARRAY8, 1, };
178         SmProp prop_uid = { SmUserID, SmARRAY8, 1, };
179         SmProp prop_hint = { SmRestartStyleHint, SmCARD8, 1, };
180         SmProp prop_pid = { SmProcessID, SmARRAY8, 1, };
181         SmProp prop_pri = { "_GSM_Priority", SmCARD8, 1, };
182         SmProp *props[6];
183         gchar hint, pri;
184         gchar pid[32];
185
186         val_prog.value = sm_argv[0];
187         val_prog.length = strlen(sm_argv[0]);
188
189         val_uid.value = g_strdup(g_get_user_name());
190         val_uid.length = strlen(val_uid.value);
191
192         hint = SmRestartImmediately;
193         val_hint.value = &hint;
194         val_hint.length = 1;
195
196         sprintf(pid, "%ld", (long)getpid());
197         val_pid.value = pid;
198         val_pid.length = strlen(pid);
199
200         /* priority with gnome-session-manager, low to run before other apps */
201         pri = 20;
202         val_pri.value = &pri;
203         val_pri.length = 1;
204
205         prop_prog.vals = &val_prog;
206         prop_uid.vals = &val_uid;
207         prop_hint.vals = &val_hint;
208         prop_pid.vals = &val_pid;
209         prop_pri.vals = &val_pri;
210
211         props[0] = &prop_prog;
212         props[1] = &prop_uid;
213         props[2] = &prop_hint;
214         props[3] = &prop_pid;
215         props[4] = &prop_pri;
216
217         SmcSetProperties(sm_conn, 5, props);
218
219         g_free(val_uid.value);
220
221         save_commands();
222     }
223 }
224
225 void session_shutdown()
226 {
227     g_free(save_file);
228     g_free(sm_id);
229
230     if (sm_conn) {
231         SmPropValue val_hint;
232         SmProp prop_hint = { SmRestartStyleHint, SmCARD8, 1, };
233         SmProp *props[1];
234         gulong hint;
235
236         /* when we exit, we want to reset this to a more friendly state */
237         hint = SmRestartIfRunning;
238         val_hint.value = &hint;
239         val_hint.length = 1;
240
241         prop_hint.vals = &val_hint;
242
243         props[0] = &prop_hint;
244
245         SmcSetProperties(sm_conn, 1, props);
246
247         SmcCloseConnection(sm_conn, 0, NULL);
248
249         while (session_saved_state) {
250             session_state_free(session_saved_state->data);
251             session_saved_state = g_list_delete_link(session_saved_state,
252                                                      session_saved_state);
253         }
254     }
255 }
256
257 static void sm_save_yourself_phase2(SmcConn conn, SmPointer data)
258 {
259     gboolean success;
260
261     success = session_save();
262     save_commands();
263
264     SmcSaveYourselfDone(conn, success);
265 }
266
267 static void sm_save_yourself(SmcConn conn, SmPointer data, int save_type,
268                              Bool shutdown, int interact_style, Bool fast)
269 {
270     if (!SmcRequestSaveYourselfPhase2(conn, sm_save_yourself_phase2, data)) {
271         ob_debug("SAVE YOURSELF PHASE 2 failed\n");
272         SmcSaveYourselfDone(conn, FALSE);
273     }
274 }
275
276 static void sm_die(SmcConn conn, SmPointer data)
277 {
278     ob_exit();
279 }
280
281 static void sm_save_complete(SmcConn conn, SmPointer data)
282 {
283 }
284
285 static void sm_shutdown_cancelled(SmcConn conn, SmPointer data)
286 {
287 }
288
289 static gboolean session_save()
290 {
291     gchar *filename;
292     FILE *f;
293     GList *it;
294     gboolean success = TRUE;
295
296     /* this algo is from metacity */
297     filename = g_strdup_printf("%d-%d-%u.obs",
298                                (int) time(NULL),
299                                (int) getpid(),
300                                g_random_int());
301     save_file = g_build_filename(g_get_home_dir(), ".openbox", "sessions",
302                                  filename, NULL);
303     g_free(filename);
304
305     f = fopen(save_file, "w");
306     if (!f) {
307         success = FALSE;
308         g_warning("unable to save the session to %s: %s",
309                   save_file, strerror(errno));
310     } else {
311         guint stack_pos = 0;
312
313         fprintf(f, "<?xml version=\"1.0\"?>\n\n");
314         fprintf(f, "<openbox_session id=\"%s\">\n\n", sm_id);
315
316         for (it = stacking_list; it; it = g_list_next(it)) {
317             guint num;
318             gint32 *dimensions;
319             gint prex, prey, prew, preh;
320             ObClient *c;
321             gchar *client_id, *t;
322
323             if (WINDOW_IS_CLIENT(it->data))
324                 c = WINDOW_AS_CLIENT(it->data);
325             else
326                 continue;
327
328             if (!client_normal(c))
329                 continue;
330
331             if (!(client_id = client_get_sm_client_id(c)))
332                 continue;
333
334             prex = c->area.x;
335             prey = c->area.y;
336             prew = c->area.width;
337             preh = c->area.height;
338             if (PROP_GETA32(c->window, openbox_premax, cardinal,
339                             (guint32**)&dimensions, &num)) {
340                 if (num == 4) {
341                     prex = dimensions[0];
342                     prey = dimensions[1];
343                     prew = dimensions[2];
344                     preh = dimensions[3];
345                 }
346                 g_free(dimensions);
347             }
348
349             fprintf(f, "<window id=\"%s\">\n", client_id);
350
351             t = g_markup_escape_text(c->name, -1);
352             fprintf(f, "\t<name>%s</name>\n", t);
353             g_free(t);
354
355             t = g_markup_escape_text(c->class, -1);
356             fprintf(f, "\t<class>%s</class>\n", t);
357             g_free(t);
358
359             t = g_markup_escape_text(c->role, -1);
360             fprintf(f, "\t<role>%s</role>\n", t);
361             g_free(t);
362
363             fprintf(f, "\t<desktop>%d</desktop>\n", c->desktop);
364             fprintf(f, "\t<stacking>%d</stacking>\n", stack_pos);
365             fprintf(f, "\t<x>%d</x>\n", prex);
366             fprintf(f, "\t<y>%d</y>\n", prey);
367             fprintf(f, "\t<width>%d</width>\n", prew);
368             fprintf(f, "\t<height>%d</height>\n", preh);
369             if (c->shaded)
370                 fprintf(f, "\t<shaded />\n");
371             if (c->iconic)
372                 fprintf(f, "\t<iconic />\n");
373             if (c->skip_pager)
374                 fprintf(f, "\t<skip_pager />\n");
375             if (c->skip_taskbar)
376                 fprintf(f, "\t<skip_taskbar />\n");
377             if (c->fullscreen)
378                 fprintf(f, "\t<fullscreen />\n");
379             if (c->above)
380                 fprintf(f, "\t<above />\n");
381             if (c->below)
382                 fprintf(f, "\t<below />\n");
383             if (c->max_horz)
384                 fprintf(f, "\t<max_horz />\n");
385             if (c->max_vert)
386                 fprintf(f, "\t<max_vert />\n");
387             fprintf(f, "</window>\n\n");
388
389             ++stack_pos;
390
391             g_free(client_id);
392         }
393
394         fprintf(f, "</openbox_session>\n");
395
396         if (fflush(f)) {
397             success = FALSE;
398             g_warning("error while saving the session to %s: %s",
399                       save_file, strerror(errno));
400         }
401         fclose(f);
402     }
403
404     return success;
405 }
406
407 void session_state_free(ObSessionState *state)
408 {
409     if (state) {
410         g_free(state->id);
411         g_free(state->name);
412         g_free(state->class);
413         g_free(state->role);
414
415         g_free(state);
416     }
417 }
418
419 gboolean session_state_cmp(ObSessionState *s, ObClient *c)
420 {
421     gchar *client_id;
422
423     if (!(client_id = client_get_sm_client_id(c)))
424         return FALSE;
425     if (strcmp(s->id, client_id)) {
426         g_free(client_id);
427         return FALSE;
428     }
429     g_free(client_id);
430     if (strcmp(s->name, c->name))
431         return FALSE;
432     if (strcmp(s->class, c->class))
433         return FALSE;
434     if (strcmp(s->role, c->role))
435         return FALSE;
436     return TRUE;
437 }
438
439 GList* session_state_find(ObClient *c)
440 {
441     GList *it;
442
443     for (it = session_saved_state; it; it = g_list_next(it)) {
444         ObSessionState *s = it->data;
445         if (!s->matched && session_state_cmp(s, c)) {
446             s->matched = TRUE;
447             break;
448         }
449     }
450     return it;
451 }
452
453 static gint stack_sort(const ObSessionState *s1, const ObSessionState *s2)
454 {
455     return s1->stacking - s2->stacking;
456 }
457
458 static void session_load(char *path)
459 {
460     xmlDocPtr doc;
461     xmlNodePtr node, n;
462     gchar *id;
463
464     if (!parse_load(path, "openbox_session", &doc, &node))
465         return;
466
467     if (!parse_attr_string("id", node, &id))
468         return;
469     g_free(sm_id);
470     sm_id = id;
471
472     node = parse_find_node("window", node->xmlChildrenNode);
473     while (node) {
474         ObSessionState *state;
475
476         state = g_new0(ObSessionState, 1);
477
478         if (!parse_attr_string("id", node, &state->id))
479             goto session_load_bail;
480         if (!(n = parse_find_node("name", node->xmlChildrenNode)))
481             goto session_load_bail;
482         state->name = parse_string(doc, n);
483         if (!(n = parse_find_node("class", node->xmlChildrenNode)))
484             goto session_load_bail;
485         state->class = parse_string(doc, n);
486         if (!(n = parse_find_node("role", node->xmlChildrenNode)))
487             goto session_load_bail;
488         state->role = parse_string(doc, n);
489         if (!(n = parse_find_node("stacking", node->xmlChildrenNode)))
490             goto session_load_bail;
491         state->stacking = parse_int(doc, n);
492         if (!(n = parse_find_node("desktop", node->xmlChildrenNode)))
493             goto session_load_bail;
494         state->desktop = parse_int(doc, n);
495         if (!(n = parse_find_node("x", node->xmlChildrenNode)))
496             goto session_load_bail;
497         state->x = parse_int(doc, n);
498         if (!(n = parse_find_node("y", node->xmlChildrenNode)))
499             goto session_load_bail;
500         state->y = parse_int(doc, n);
501         if (!(n = parse_find_node("width", node->xmlChildrenNode)))
502             goto session_load_bail;
503         state->w = parse_int(doc, n);
504         if (!(n = parse_find_node("height", node->xmlChildrenNode)))
505             goto session_load_bail;
506         state->h = parse_int(doc, n);
507
508         state->shaded =
509             parse_find_node("shaded", node->xmlChildrenNode) != NULL;
510         state->iconic =
511             parse_find_node("iconic", node->xmlChildrenNode) != NULL;
512         state->skip_pager =
513             parse_find_node("skip_pager", node->xmlChildrenNode) != NULL;
514         state->skip_taskbar =
515             parse_find_node("skip_taskbar", node->xmlChildrenNode) != NULL;
516         state->fullscreen =
517             parse_find_node("fullscreen", node->xmlChildrenNode) != NULL;
518         state->above =
519             parse_find_node("above", node->xmlChildrenNode) != NULL;
520         state->below =
521             parse_find_node("below", node->xmlChildrenNode) != NULL;
522         state->max_horz =
523             parse_find_node("max_horz", node->xmlChildrenNode) != NULL;
524         state->max_vert =
525             parse_find_node("max_vert", node->xmlChildrenNode) != NULL;
526         
527         /* save this */
528         session_saved_state = g_list_prepend(session_saved_state, state);
529         goto session_load_ok;
530
531     session_load_bail:
532         session_state_free(state);
533
534     session_load_ok:
535
536         node = parse_find_node("window", node->next);
537     }
538
539     /* sort them by their stacking order */
540     session_saved_state = g_list_sort(session_saved_state,
541                                       (GCompareFunc)stack_sort);
542
543     xmlFreeDoc(doc);
544 }
545
546 #endif