/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*- session.m for the Azalea window manager Copyright (c) 2006 Yen-Ju Chen session.c for the Openbox window manager Copyright (c) 2003 Ben Jansens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. See the COPYING file for a copy of the GNU General Public License. */ /* This session code is largely inspired by metacity code. */ #import "session.h" #import NSMutableArray *session_saved_state; @implementation AZSessionState - (NSComparisonResult) compareStacking: (AZSessionState *) other { if ([self stacking] < [other stacking]) return NSOrderedAscending; else if ([self stacking] == [other stacking]) return NSOrderedSame; else return NSOrderedDescending; } - (NSString *) identifier { return iden; } - (NSString *) name { return name; } - (NSString *) class { return class; } - (NSString *) role { return role; } - (unsigned int) stacking { return stacking; } - (unsigned int) desktop { return desktop; } - (int) x { return x; } - (int) y { return y; } - (int) w { return w; } - (int )h { return h; } - (BOOL) shaded { return shaded; } - (BOOL) iconic { return iconic; } - (BOOL) skip_pager { return skip_pager; } - (BOOL) skip_taskbar { return skip_taskbar; } - (BOOL) fullscreen { return fullscreen; } - (BOOL) above { return above; } - (BOOL) below { return below; } - (BOOL) max_horz { return max_horz; } - (BOOL) max_vert { return max_vert; } - (BOOL) matched { return matched; } - (void) set_identifier: (NSString *) i { ASSIGNCOPY(iden, i); } - (void) set_name: (NSString *) n { ASSIGNCOPY(name, n); } - (void) set_class: (NSString *) c { ASSIGNCOPY(class, c); } - (void) set_role: (NSString *) r { ASSIGNCOPY(role, r); } - (void) set_stacking: (unsigned int) s { stacking = s; } - (void) set_desktop: (unsigned int) d { desktop = d; } - (void) set_x: (int) _x { x = _x; } - (void) set_y: (int) _y { y = _y; } - (void) set_w: (int) _w { w = _w; } - (void) set_h: (int) _h { h = _h; } - (void) set_shaded: (BOOL) s { shaded = s; } - (void) set_iconic: (BOOL) i { iconic = i; } - (void) set_skip_pager: (BOOL) s { skip_pager = s; } - (void) set_skip_taskbar: (BOOL) s { skip_taskbar = s; } - (void) set_fullscreen: (BOOL) f { fullscreen = f; } - (void) set_above: (BOOL) a { above = a; } - (void) set_below: (BOOL) b { below = b; } - (void) set_max_horz: (BOOL) m { max_horz = m; } - (void) set_max_vert: (BOOL) m { max_vert = m; } - (void) set_matched: (BOOL) m { matched = m; } - (void) dealloc { DESTROY(iden); DESTROY(name); DESTROY(class); DESTROY(role); [super dealloc]; } @end #ifndef USE_SM void session_startup(int *argc, char ***argv) {} void session_shutdown() {} AZSessionState *session_state_find(AZClient *c) { return nil; } BOOL session_state_cmp(AZSessionState *s, AZClient *c) { return NO; } #else #import "AZDock.h" #import "AZDebug.h" #import "AZClient.h" #import "openbox.h" #include "prop.h" #include "parse.h" #include #include #include #ifdef HAVE_UNISTD_H # include # include #endif #include static BOOL sm_disable; static SmcConn sm_conn; static NSString *save_file; static NSString *sm_id; static int sm_argc; static char **sm_argv; static NSString *sm_sessions_path; static void session_load(NSString *path); static BOOL session_save(); static void sm_save_yourself(SmcConn conn, SmPointer data, int save_type, Bool shutdown, int interact_style, Bool fast); static void sm_die(SmcConn conn, SmPointer data); static void sm_save_complete(SmcConn conn, SmPointer data); static void sm_shutdown_cancelled(SmcConn conn, SmPointer data); static void save_commands() { SmProp *props[2]; SmProp prop_cmd = { SmCloneCommand, SmLISTofARRAY8, 1, }; SmProp prop_res = { SmRestartCommand, SmLISTofARRAY8, }; int i; prop_cmd.vals = calloc(sizeof(SmPropValue), sm_argc); prop_cmd.num_vals = sm_argc; for (i = 0; i < sm_argc; ++i) { prop_cmd.vals[i].value = sm_argv[i]; prop_cmd.vals[i].length = strlen(sm_argv[i]); } prop_res.vals = calloc(sizeof(SmPropValue), sm_argc + 2); prop_res.num_vals = sm_argc + 2; for (i = 0; i < sm_argc; ++i) { prop_res.vals[i].value = sm_argv[i]; prop_res.vals[i].length = strlen(sm_argv[i]); } prop_res.vals[i].value = "--sm-save-file"; prop_res.vals[i++].length = strlen("--sm-save-file"); prop_res.vals[i].value = (char*)[save_file fileSystemRepresentation]; prop_res.vals[i++].length = strlen([save_file fileSystemRepresentation]); props[0] = &prop_res; props[1] = &prop_cmd; SmcSetProperties(sm_conn, 2, props); free(prop_res.vals); free(prop_cmd.vals); } static void remove_args(int *argc, char ***argv, int index, int num) { int i; for (i = index; i < index + num; ++i) (*argv)[i] = (*argv)[i+num]; *argc -= num; } static void parse_args(int *argc, char ***argv) { int i; for (i = 1; i < *argc; ++i) { if (!strcmp((*argv)[i], "--sm-client-id")) { if (i == *argc - 1) /* no args left */ NSLog(@"Error: --sm-client-id requires an argument\n"); else { ASSIGN(sm_id, ([NSString stringWithCString: (*argv)[i+1]])); remove_args(argc, argv, i, 2); ++i; } } else if (!strcmp((*argv)[i], "--sm-save-file")) { if (i == *argc - 1) /* no args left */ NSLog(@"Error: --sm-save-file requires an argument\n"); else { ASSIGN(save_file, ([NSString stringWithCString: (*argv)[i+1]])); remove_args(argc, argv, i, 2); ++i; } } else if (!strcmp((*argv)[i], "--sm-disable")) { sm_disable = YES; remove_args(argc, argv, i, 1); } } } void session_startup(int *argc, char ***argv) { #define SM_ERR_LEN 1024 SmcCallbacks cb; char sm_err[SM_ERR_LEN]; parse_args(argc, argv); if (sm_disable) return; ASSIGN(sm_sessions_path, ([NSString pathWithComponents: [NSArray arrayWithObjects: parse_xdg_data_home_path(), @"openbox", @"sessions", nil]])); if (!parse_mkdir_path(sm_sessions_path, 0700)) NSLog(@"Warning: Unable to make directory '%@'", sm_sessions_path); session_saved_state = [[NSMutableArray alloc] init]; if (save_file) session_load(save_file); else { NSString *filename; /* this algo is from metacity */ filename = [NSString stringWithFormat: @"%d-%d-%u.obs", (int)time(NULL), (int)getpid(), random()]; ASSIGN(save_file, [sm_sessions_path stringByAppendingPathComponent: filename]); } sm_argc = *argc; sm_argv = *argv; cb.save_yourself.callback = sm_save_yourself; cb.save_yourself.client_data = NULL; cb.die.callback = sm_die; cb.die.client_data = NULL; cb.save_complete.callback = sm_save_complete; cb.save_complete.client_data = NULL; cb.shutdown_cancelled.callback = sm_shutdown_cancelled; cb.shutdown_cancelled.client_data = NULL; char *_sm_id = (char*)[sm_id cString]; sm_conn = SmcOpenConnection(NULL, NULL, 1, 0, SmcSaveYourselfProcMask | SmcDieProcMask | SmcSaveCompleteProcMask | SmcShutdownCancelledProcMask, &cb, _sm_id, &_sm_id, SM_ERR_LEN, sm_err); if (_sm_id) ASSIGN(sm_id, ([NSString stringWithCString: _sm_id])); else { [sm_id release]; sm_id = nil; } if (sm_conn == NULL) AZDebug("Failed to connect to session manager: %s\n", sm_err); else { SmPropValue val_prog; SmPropValue val_uid; SmPropValue val_hint; SmPropValue val_pri; SmPropValue val_pid; SmProp prop_prog = { SmProgram, SmARRAY8, 1, }; SmProp prop_uid = { SmUserID, SmARRAY8, 1, }; SmProp prop_hint = { SmRestartStyleHint, SmCARD8, 1, }; SmProp prop_pid = { SmProcessID, SmARRAY8, 1, }; SmProp prop_pri = { "_GSM_Priority", SmCARD8, 1, }; SmProp *props[6]; char hint, pri; char *pid; val_prog.value = sm_argv[0]; val_prog.length = strlen(sm_argv[0]); val_uid.value = (char*)[NSUserName() cString]; val_uid.length = strlen(val_uid.value); hint = SmRestartImmediately; val_hint.value = &hint; val_hint.length = 1; pid = (char*)[[NSString stringWithFormat: @"%ld", (long)getpid()] cString]; val_pid.value = pid; val_pid.length = strlen(pid); /* priority with gnome-session-manager, low to run before other apps */ pri = 20; val_pri.value = &pri; val_pri.length = 1; prop_prog.vals = &val_prog; prop_uid.vals = &val_uid; prop_hint.vals = &val_hint; prop_pid.vals = &val_pid; prop_pri.vals = &val_pri; props[0] = &prop_prog; props[1] = &prop_uid; props[2] = &prop_hint; props[3] = &prop_pid; props[4] = &prop_pri; SmcSetProperties(sm_conn, 5, props); save_commands(); } } void session_shutdown() { DESTROY(sm_sessions_path); DESTROY(save_file); DESTROY(sm_id); if (sm_conn) { SmPropValue val_hint; SmProp prop_hint = { SmRestartStyleHint, SmCARD8, 1, }; SmProp *props[1]; unsigned long hint; /* when we exit, we want to reset this to a more friendly state */ hint = SmRestartIfRunning; val_hint.value = &hint; val_hint.length = 1; prop_hint.vals = &val_hint; props[0] = &prop_hint; SmcSetProperties(sm_conn, 1, props); SmcCloseConnection(sm_conn, 0, NULL); DESTROY(session_saved_state); } } static void sm_save_yourself_phase2(SmcConn conn, SmPointer data) { BOOL success; success = session_save(); save_commands(); SmcSaveYourselfDone(conn, success); } static void sm_save_yourself(SmcConn conn, SmPointer data, int save_type, Bool shutdown, int interact_style, Bool fast) { if (!SmcRequestSaveYourselfPhase2(conn, sm_save_yourself_phase2, data)) { AZDebug("SAVE YOURSELF PHASE 2 failed\n"); SmcSaveYourselfDone(conn, NO); } } static void sm_die(SmcConn conn, SmPointer data) { ob_exit(0); } static void sm_save_complete(SmcConn conn, SmPointer data) { } static void sm_shutdown_cancelled(SmcConn conn, SmPointer data) { } static BOOL session_save() { FILE *f; BOOL success = YES; int i, count = [[AZStacking stacking] count]; f = fopen([save_file fileSystemRepresentation], "w"); if (!f) { success = NO; NSLog(@"Warning: unable to save the session to %@", save_file); } else { unsigned int stack_pos = 0; fprintf(f, "\n\n"); fprintf(f, "\n\n", [sm_id cString]); for (i = 0; i < count; i++) { id temp = [[AZStacking stacking] windowAtIndex: i]; int prex, prey, prew, preh; AZClient *c; char *t; if (WINDOW_IS_CLIENT(temp)) c = (AZClient *)temp; else continue; if (![c normal]) continue; if (![c sm_client_id]) continue; prex = [c area].x; prey = [c area].y; prew = [c area].width; preh = [c area].height; if ([c fullscreen]) { prex = [c pre_fullscreen_area].x; prey = [c pre_fullscreen_area].x; prew = [c pre_fullscreen_area].width; preh = [c pre_fullscreen_area].height; } if ([c max_horz]) { prex = [c pre_max_area].x; prew = [c pre_max_area].width; } if ([c max_vert]) { prey = [c pre_max_area].y; preh = [c pre_max_area].height; } fprintf(f, "\n", (char*)[[c sm_client_id] cString]); t = (char*)[[[c name] stringByEscapingXML] cString]; fprintf(f, "\t%s\n", t); t = (char*)[[[c class] stringByEscapingXML] cString]; fprintf(f, "\t%s\n", t); t = (char*)[[[c role] stringByEscapingXML] cString]; fprintf(f, "\t%s\n", t); fprintf(f, "\t%d\n", [c desktop]); fprintf(f, "\t%d\n", stack_pos); fprintf(f, "\t%d\n", prex); fprintf(f, "\t%d\n", prey); fprintf(f, "\t%d\n", prew); fprintf(f, "\t%d\n", preh); if ([c shaded]) fprintf(f, "\t\n"); if ([c iconic]) fprintf(f, "\t\n"); if ([c skip_pager]) fprintf(f, "\t\n"); if ([c skip_taskbar]) fprintf(f, "\t\n"); if ([c fullscreen]) fprintf(f, "\t\n"); if ([c above]) fprintf(f, "\t\n"); if ([c below]) fprintf(f, "\t\n"); if ([c max_horz]) fprintf(f, "\t\n"); if ([c max_vert]) fprintf(f, "\t\n"); fprintf(f, "\n\n"); ++stack_pos; } fprintf(f, "\n"); if (fflush(f)) { success = NO; NSLog(@"Warning: error while saving the session to %@", save_file); } fclose(f); } return success; } BOOL session_state_cmp(AZSessionState *s, AZClient *c) { return ([c sm_client_id] && [[s identifier] isEqualToString: [c sm_client_id]] && [[s name] isEqualToString: [c name]] && [[s class] isEqualToString: [c class]] && [[s role] isEqualToString: [c role]]); /* Considering nil == nil ? */ } AZSessionState *session_state_find(AZClient *c) { int i, count = [session_saved_state count]; AZSessionState *s = nil; for (i = 0; i < count; i++) { s = [session_saved_state objectAtIndex: i]; if (![s matched] && session_state_cmp(s, c)) { [s set_matched: YES]; return s; } } return nil; } static void session_load(NSString *path) { xmlDocPtr doc; xmlNodePtr node, n; NSString *id; if (!parse_load(path, "openbox_session", &doc, &node)) return; if (!parse_attr_string("id", node, &id)) return; [sm_id release]; sm_id = [id copy]; node = parse_find_node("window", node->children); while (node) { AZSessionState *state = [[AZSessionState alloc] init]; NSString *_id; if (!parse_attr_string("id", node, &_id)) { goto session_load_bail; } else { [state set_identifier: _id]; } if (!(n = parse_find_node("name", node->children))) goto session_load_bail; [state set_name: parse_string(doc, n)]; if (!(n = parse_find_node("class", node->children))) goto session_load_bail; [state set_class: parse_string(doc, n)]; if (!(n = parse_find_node("role", node->children))) goto session_load_bail; [state set_role: parse_string(doc, n)]; if (!(n = parse_find_node("stacking", node->children))) goto session_load_bail; [state set_stacking: parse_int(doc, n)]; if (!(n = parse_find_node("desktop", node->children))) goto session_load_bail; [state set_desktop: parse_int(doc, n)]; if (!(n = parse_find_node("x", node->children))) goto session_load_bail; [state set_x: parse_int(doc, n)]; if (!(n = parse_find_node("y", node->children))) goto session_load_bail; [state set_y: parse_int(doc, n)]; if (!(n = parse_find_node("width", node->children))) goto session_load_bail; [state set_w: parse_int(doc, n)]; if (!(n = parse_find_node("height", node->children))) goto session_load_bail; [state set_h: parse_int(doc, n)]; [state set_shaded: parse_find_node("shaded", node->children) != NULL]; [state set_iconic: parse_find_node("iconic", node->children) != NULL]; [state set_skip_pager: parse_find_node("skip_pager", node->children) != NULL]; [state set_skip_taskbar: parse_find_node("skip_taskbar", node->children) != NULL]; [state set_fullscreen: parse_find_node("fullscreen", node->children) != NULL]; [state set_above: parse_find_node("above", node->children) != NULL]; [state set_below: parse_find_node("below", node->children) != NULL]; [state set_max_horz: parse_find_node("max_horz", node->children) != NULL]; [state set_max_vert: parse_find_node("max_vert", node->children) != NULL]; /* save this */ [session_saved_state addObject: state]; [state release]; state = NULL; goto session_load_ok; session_load_bail: [state dealloc]; state = NULL; session_load_ok: node = parse_find_node("window", node->next); } /* sort them by their stacking order */ [session_saved_state sortUsingSelector: @selector(compareStacking:)]; xmlFreeDoc(doc); } #endif