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