/* * $Id$ * * Evoke, head honcho of everything * Part of Equinox Desktop Environment (EDE). * Copyright (c) 2000-2007 EDE Authors. * * This program is licensed under terms of the * GNU General Public License version 2 or newer. * See COPYING for details. */ #include "Log.h" #include "Logout.h" #include "EvokeService.h" #include "Splash.h" #include "Spawn.h" #include "Crash.h" #include #include #include #include #include #include #include #include #include #include // getpid #include // #include // free #include // strdup, memset #include // error codes void resolve_path(const edelib::String& imgdir, edelib::String& img, bool have_imgdir) { if(img.empty()) return; const char* im = img.c_str(); if(!edelib::file_exists(im) && have_imgdir) { img = edelib::build_filename("/", imgdir.c_str(), im); im = img.c_str(); if(!edelib::file_exists(im)) { // no file, send then empty img.clear(); } } } int get_int_property_value(Atom at) { Atom real; int format; unsigned long n, extra; unsigned char* prop; int status = XGetWindowProperty(fl_display, RootWindow(fl_display, fl_screen), at, 0L, 0x7fffffff, False, XA_CARDINAL, &real, &format, &n, &extra, (unsigned char**)&prop); int ret = -1; if(status != Success && !prop) return ret; ret = int(*(long*)prop); XFree(prop); return ret; } int get_string_property_value(Atom at, char* txt, int txt_len) { XTextProperty names; XGetTextProperty(fl_display, RootWindow(fl_display, fl_screen), &names, at); if(!names.nitems || !names.value) return 0; char** vnames; int nsz = 0; if(!XTextPropertyToStringList(&names, &vnames, &nsz)) { XFree(names.value); return 0; } strncpy(txt, vnames[0], txt_len); txt[txt_len] = '\0'; XFreeStringList(vnames); return 1; } void service_watcher_cb(int pid, int signum) { EvokeService::instance()->service_watcher(pid, signum); } EvokeService::EvokeService() : is_running(0), logfile(NULL), pidfile(NULL), lockfile(NULL) { top_win = NULL; } EvokeService::~EvokeService() { if(logfile) delete logfile; if(lockfile) { edelib::file_remove(lockfile); free(lockfile); } if(pidfile) { edelib::file_remove(pidfile); free(pidfile); } processes.clear(); } EvokeService* EvokeService::instance(void) { static EvokeService es; return &es; } bool EvokeService::setup_logging(const char* file) { if(!file) logfile = new DummyLog(); else logfile = new RealLog(); if(!logfile->open(file)) { delete logfile; return false; } return true; } bool EvokeService::setup_pid(const char* file, const char* lock) { if(!file) return false; if(edelib::file_exists(lock)) return false; FILE* l = fopen(lock, "w"); if(!l) return false; fprintf(l, " "); fclose(l); lockfile = strdup(lock); FILE* f = fopen(file, "w"); if(!f) return false; fprintf(f, "%i", getpid()); fclose(f); pidfile = strdup(file); return true; } bool EvokeService::init_splash(const char* config, bool no_splash, bool dry_run) { edelib::Config c; if(!c.load(config)) return false; char buff[1024]; bool have_imgdir = false; c.get("evoke", "ImagesDirectory", buff, sizeof(buff)); // no evoke section if(c.error() == edelib::CONF_ERR_SECTION) return false; edelib::String imgdir; if(c.error() == edelib::CONF_SUCCESS) { imgdir = buff; have_imgdir = true; } edelib::String splashimg; if(c.get("evoke", "Splash", buff, sizeof(buff))) splashimg = buff; // Startup key must exists if(!c.get("evoke", "Startup", buff, sizeof(buff))) return false; StringList vs; edelib::stringtok(vs, buff, ","); // nothing, fine, do nothing unsigned int sz = vs.size(); if(sz == 0) return true; EvokeClient ec; const char* key_name; for(StringListIter it = vs.begin(); it != vs.end(); ++it) { key_name = (*it).c_str(); edelib::str_trim((char*)key_name); // probably listed but not the same key; also Exec value must exists if(!c.get(key_name, "Exec", buff, sizeof(buff))) continue; else ec.exec = buff; if(c.get(key_name, "Description", buff, sizeof(buff))) ec.desc = buff; if(c.get(key_name, "Icon", buff, sizeof(buff))) ec.icon = buff; clients.push_back(ec); } /* * Now, before data is send to Splash, resolve directories * since Splash expects that. */ resolve_path(imgdir, splashimg, have_imgdir); for(ClientListIter it = clients.begin(); it != clients.end(); ++it) resolve_path(imgdir, (*it).icon, have_imgdir); Splash sp(no_splash, dry_run); sp.set_clients(&clients); sp.set_background(&splashimg); sp.run(); return true; } /* * This is implementation of Autostart Spec (http://standards.freedesktop.org/autostart-spec/autostart-spec-0.5.html). * The Autostart Directories are $XDG_CONFIG_DIRS/autostart. * If the same filename is located under multiple Autostart Directories * only the file under the most important directory should be used. * Example: If $XDG_CONFIG_HOME is not set the Autostart Directory in the user's home * directory is ~/.config/autostart/ * Example: If $XDG_CONFIG_DIRS is not set the system wide Autostart Directory * is /etc/xdg/autostart/ * Example: If $XDG_CONFIG_HOME and $XDG_CONFIG_DIRS are not set and the two * files /etc/xdg/autostart/foo.desktop and ~/.config/autostart/foo.desktop exist * then only the file ~/.config/autostart/foo.desktop will be used because ~/.config/autostart/ * is more important than /etc/xdg/autostart/ * If Hidden key is set true in .desktop file, file MUST be ignored. * OnlyShowIn and NotShowIn (list of strings identifying desktop environments) if (or if not) * contains environment name, MUST not be started/not started. * TryExec is same as for .desktop spec. */ void EvokeService::init_autostart(void) { edelib::String home = edelib::user_config_dir(); home += "/autostart/"; // TODO } void EvokeService::setup_atoms(Display* d) { // with them must be send '1' or property will be ignored (except _EDE_EVOKE_SPAWN) _ede_shutdown_all = XInternAtom(d, "_EDE_EVOKE_SHUTDOWN_ALL", False); _ede_evoke_quit = XInternAtom(d, "_EDE_EVOKE_QUIT", False); _ede_spawn = XInternAtom(d, "_EDE_EVOKE_SPAWN", False); } void EvokeService::quit_x11(void) { int ret = fl_ask(_("Nice quitting is not implemented yet and this will forcefully kill\nall running applications. Make sure to save what needs to be saved.\nSo, would you like to continue ?")); if(ret) stop(); #if 0 /* * This code is working, but not as I would like to see. * It will (mostly) call XKillClient() for _every_ window * (including i.e. buttons in some app), and that is not * nice way to say good bye. This must be implemented in * wm since it only knows what is actuall window and what not. * For now quit_x11() will simply quit itself, quitting X11 * session (if it holds it), which will in turn forcefully kill * all windows. */ Window dummy, *wins; Window root = RootWindow(fl_display, fl_screen); unsigned int n; if(!XQueryTree(fl_display, root, &dummy, &dummy, &wins, &n)) return; Atom _wm_protocols = XInternAtom(fl_display, "WM_PROTOCOLS", False); Atom _wm_delete_window = XInternAtom(fl_display, "WM_DELETE_WINDOW", False); Atom* protocols; int np; XEvent ev; bool have_quit = 0; for(unsigned int i = 0; i < n; i++) { if(wins[i] == root) continue; have_quit = 0; if(XGetWMProtocols(fl_display, wins[i], &protocols, &np)) { for(int j = 0; j < np; j++) { if(protocols[j] == _wm_delete_window) { have_quit = 1; break; } } } if(have_quit) { memset(&ev, 0, sizeof(ev)); ev.xclient.type = ClientMessage; ev.xclient.window = wins[i]; ev.xclient.message_type = _wm_protocols; ev.xclient.format = 32; ev.xclient.data.l[0] = (long)_wm_delete_window; ev.xclient.data.l[1] = (long)fl_event_time; XSendEvent(fl_display, wins[i], False, 0L, &ev); } else { XKillClient(fl_display, wins[i]); } } XFree((void*)wins); stop(); #endif } /* * Monitor starting service and report if staring * failed. Also if one of runned services crashed * attach gdb on it pid and run backtrace. */ void EvokeService::service_watcher(int pid, int signum) { printf("got %i\n", signum); if(signum == SPAWN_CHILD_CRASHED) { EvokeProcess pc; if(find_and_unregister_process(pid, pc)) { printf("%s crashed with core dump\n", pc.cmd.c_str()); CrashDialog cdialog; cdialog.set_data(pc.cmd.c_str()); cdialog.run(); return; } } else if(signum == SPAWN_CHILD_KILLED) { printf("child %i killed\n", pid); } else { printf("child %i exited with %i \n", pid, signum); } unregister_process(pid); } /* * Execute program. It's return status * will be reported via service_watcher() */ void EvokeService::run_program(const char* cmd) { EASSERT(cmd != NULL); pid_t child; int r = spawn_program_with_core(cmd, service_watcher_cb, &child); if(r != 0) fl_alert("Unable to start %s. Got code %i", cmd, r); else register_process(cmd, child); } void EvokeService::register_process(const char* cmd, pid_t pid) { EvokeProcess pc; pc.cmd = cmd; pc.pid = pid; printf("registering %s with %i\n", cmd, pid); processes.push_back(pc); } void EvokeService::unregister_process(pid_t pid) { if(processes.empty()) return; ProcessListIter it = processes.begin(); ProcessListIter it_end = processes.end(); while(it != it_end) { if((*it).pid == pid) { printf("Found %s with pid %i, cleaning...\n", (*it).cmd.c_str(), pid); processes.erase(it); return; } ++it; } } bool EvokeService::find_and_unregister_process(pid_t pid, EvokeProcess& pc) { if(processes.empty()) return 0; ProcessListIter it = processes.begin(); ProcessListIter it_end = processes.end(); while(it != it_end) { if((*it).pid == pid) { printf("Found %s with pid %i, cleaning...\n", (*it).cmd.c_str(), pid); pc.cmd = (*it).cmd; pc.pid = pid; processes.erase(it); return 1; } ++it; } return 0; } int EvokeService::handle(const XEvent* ev) { logfile->printf("Got event %i\n", ev->type); if(ev->type == MapNotify) { if(top_win) { // for splash to keep it at top (working in old edewm) XRaiseWindow(fl_display, fl_xid(top_win)); return 1; } } else if(ev->type == ConfigureNotify) { if(ev->xconfigure.event == DefaultRootWindow(fl_display) && top_win) { // splash too, but keep window under other wm's XRaiseWindow(fl_display, fl_xid(top_win)); return 1; } } else if(ev->type == PropertyNotify) { if(ev->xproperty.atom == _ede_spawn) { char buff[1024]; if(get_string_property_value(_ede_spawn, buff, sizeof(buff))) { logfile->printf("Got _EVOKE_SPAWN with %s. Starting client...\n", buff); run_program(buff); } else { logfile->printf("Got _EVOKE_SPAWN with malformed data. Ignoring...\n"); } return 1; } if(ev->xproperty.atom == _ede_evoke_quit) { int val = get_int_property_value(_ede_evoke_quit); if(val == 1) { logfile->printf("Got accepted _EDE_EVOKE_QUIT\n"); stop(); } else logfile->printf("Got _EDE_EVOKE_QUIT with bad code (%i). Ignoring...\n", val); return 1; } if(ev->xproperty.atom == _ede_shutdown_all) { int val = get_int_property_value(_ede_shutdown_all); if(val == 1) { logfile->printf("Got accepted _EDE_EVOKE_SHUTDOWN_ALL\n"); int dw = DisplayWidth(fl_display, fl_screen); int dh = DisplayHeight(fl_display, fl_screen); printf("got %i\n", logout_dialog(dw, dh)); // quit_x11(); } else logfile->printf("Got _EDE_EVOKE_SHUTDOWN_ALL with bad code (%i). Ignoring...\n", val); return 1; } } return 0; }