/* * $Id$ * * ede-screensaver-conf, a tool to configure screensaver * Part of Equinox Desktop Environment (EDE). * Based on some Xine code I found somewhere * Copyright (c) 2009 Sanel Zukan * * This program is licensed under the terms of the * GNU General Public License version 2 or later. * See COPYING for the details. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "XScreenSaver.h" EDELIB_NS_USING(String) EDELIB_NS_USING(file_path) EDELIB_NS_USING(file_remove) EDELIB_NS_USING(file_rename) EDELIB_NS_USING(file_test) EDELIB_NS_USING(FILE_TEST_IS_DIR) EDELIB_NS_USING(run_async) EDELIB_NS_USING(dir_home) EDELIB_NS_USING(dir_empty) static Atom XA_SCREENSAVER; static Atom XA_SCREENSAVER_VERSION; static Atom XA_DEMO; static Atom XA_SELECT; static XErrorHandler old_handler = 0; static Bool got_bad_window = False; static int atoms_loaded = 0; static pid_t xscr_preview_pid = 0; static const char* xscr_folder_found = 0; extern char** environ; static const char* xscr_hacks_dirs[] = { PREFIX"/lib/xscreensaver/", "/usr/libexec/xscreensaver/", "/usr/lib/xscreensaver/", "/usr/X11R6/lib/xscreensaver/", "/usr/local/lib/xscreensaver/", "/lib/xscreensaver/", 0 }; static const char* xscr_hacks_config_dirs[] = { PREFIX"/share/xscreensaver/config/", "/usr/share/xscreensaver/config/", "/usr/local/share/xscreensaver/config/", 0 }; #define EAT_SPACES(c) \ do { \ while(*c && (*c == ' ' || *c == '\t')) c++; \ } while(0) static int bad_window_handler(Display *dpy, XErrorEvent *xevent) { if(xevent->error_code == BadWindow) { got_bad_window = True; return 0; } if(!old_handler) return 0; return (*old_handler)(dpy, xevent); } /* * convert "xx:xx:xx" to minutes * TODO: a better code would be great */ static int time_to_min(const char *t) { char nb[64]; const char *p = t; unsigned int i = 0; int ret = 0; for(; *p && *p != ':' && i < sizeof(nb); p++, i++) nb[i] = *p; p++; nb[i] = '\0'; ret = atoi(nb) * 60; i = 0; if(*p) { for(i = 0; *p && *p != ':' && i < sizeof(nb); p++, i++) nb[i] = *p; p++; } nb[i] = '\0'; ret += atoi(nb); return ret; } static void min_to_time(unsigned int t, String& ret) { ret.clear(); /* so we can re-use it again */ unsigned int hour = 0, min = 0; char buf[3]; const char* fmt; /* assure we do not get values that exceedes a day (24 * 60 = 1440) */ if(t >= 1440) { min = 2; } else if(t >= 60) { hour = t / 60; min = t % 60; } else { min = t; } /* construct 'xx:yy:zz' time format */ if(hour >= 10) fmt = "%i"; else fmt = "0%i"; snprintf(buf, sizeof(buf), fmt, hour); ret = buf; ret += ":"; if(min >= 10) fmt = "%i"; else fmt = "0%i"; snprintf(buf, sizeof(buf), fmt, min); ret += buf; ret += ":00"; } static void xscreensaver_init_atoms_once(Display *dpy) { if(atoms_loaded) return; XA_SCREENSAVER = XInternAtom(dpy, "SCREENSAVER", False); XA_SCREENSAVER_VERSION = XInternAtom(dpy, "_SCREENSAVER_VERSION", False); XA_DEMO = XInternAtom(dpy, "DEMO", False); XA_SELECT = XInternAtom(dpy, "SELECT", False); XSync(dpy, 0); atoms_loaded = 1; } static Window xscreensaver_find_own_window(Display *dpy) { Window root = RootWindow(dpy, DefaultScreen(dpy)); Window root2, parent, *childs; unsigned int nchilds; if(!XQueryTree(dpy, root, &root2, &parent, &childs, &nchilds)) return 0; if(root != root2) return 0; for(unsigned int i = 0; i < nchilds; i++) { Atom type; int format; unsigned long nitems, bytesafter; char *v; int status; XSync(dpy, False); got_bad_window = False; old_handler = XSetErrorHandler(bad_window_handler); status = XGetWindowProperty(dpy, childs[i], XA_SCREENSAVER_VERSION, 0, 200, False, XA_STRING, &type, &format, &nitems, &bytesafter, (unsigned char**)&v); XSync(dpy, False); XSetErrorHandler(old_handler); old_handler = 0; if(got_bad_window) { status = BadWindow; got_bad_window = False; } if(status == Success && type != None) { /* TODO: check if XFree(v) is needed */ Window ret = childs[i]; XFree(childs); return ret; } } XFree(childs); return 0; } #if 0 static void xscreensaver_run_hack(Display *dpy, Window id, long hack) { XEvent ev; ev.xany.type = ClientMessage; ev.xclient.display = dpy; ev.xclient.window = id; ev.xclient.message_type = XA_SCREENSAVER; ev.xclient.format = 32; ev.xclient.data.l[0] = XA_SELECT; ev.xclient.data.l[1] = hack; ev.xclient.data.l[2] = 0; /* ev.xclient.data.l[0] = XA_DEMO; ev.xclient.data.l[1] = 5000; // XA_DEMO protocol version ev.xclient.data.l[2] = hack; */ if(XSendEvent(dpy, id, False, 0, &ev) == 0) puts("XSendEvent() failed"); else XSync(dpy, 0); } #endif static const char *find_hacks_dir_once(void) { if(xscr_folder_found) return xscr_folder_found; for(int i = 0; xscr_hacks_dirs[i]; i++) { if(file_test(xscr_hacks_dirs[i], FILE_TEST_IS_DIR)) { xscr_folder_found = xscr_hacks_dirs[i]; break; } } return xscr_folder_found; } static SaverHack *hack_from_config(const char* path) { TiXmlDocument doc(path); if(!doc.LoadFile()) return NULL; TiXmlNode* el = doc.FirstChild("screensaver"); if(!el) return NULL; SaverHack* h = new SaverHack; h->exec = el->ToElement()->Attribute("name"); h->name = el->ToElement()->Attribute("_label"); /* * now try to find all '' tags and merge values * to form full command line */ for(el = el->FirstChildElement(); el; el = el->NextSibling()) { if(strcmp(el->Value(), "command") == 0) { h->exec += " "; h->exec += el->ToElement()->Attribute("arg"); } } return h; } static SaverPrefs *guess_config(void) { const char* config_dir = NULL, *hacks_dir; for(int i = 0; xscr_hacks_config_dirs[i]; i++) { if(file_test(xscr_hacks_config_dirs[i], FILE_TEST_IS_DIR) && !dir_empty(xscr_hacks_config_dirs[i])) { config_dir = xscr_hacks_config_dirs[i]; break; } } E_RETURN_VAL_IF_FAIL(config_dir, NULL); hacks_dir = find_hacks_dir_once(); E_RETURN_VAL_IF_FAIL(hacks_dir, NULL); /* * now do xscreensaver way: try to find xscreensaver hack programs and their matching * .xml files for name and command args */ DIR* dfd = opendir(hacks_dir); if(!dfd) return NULL; char config_path[256]; struct dirent* entry; SaverPrefs* sp = new SaverPrefs; /* some default values */ sp->curr_hack = 0; sp->timeout = 2; sp->mode = SAVER_ONE; sp->dpms_enabled = false; sp->dpms_standby = 20; sp->dpms_suspend = 40; sp->dpms_off = 60; unsigned int ind = 0; while(1) { entry = readdir(dfd); if(!entry) break; /* skip '.' and '..' */ if(entry->d_name[0] == '.' && (entry->d_name[1] == '\0' || entry->d_name[1] == '.' && entry->d_name[2] == '\0')) { continue; } snprintf(config_path, sizeof(config_path), "%s%s.xml", config_dir, entry->d_name); SaverHack* h = hack_from_config(config_path); if(h) { h->sindex = ind++; sp->hacks.push_back(h); } } return sp; } bool xscreensaver_run_daemon(Display *dpy) { xscreensaver_init_atoms_once(dpy); Window id = xscreensaver_find_own_window(dpy); /* if not running, try to manualy start it */ if(id == 0) { E_DEBUG(E_STRLOC ": xscreensaver daemon not running, starting it...\n"); String p = file_path("xscreensaver"); if(p.empty()) return false; /* run 'xscreensaver -nosplash' */ run_async("%s -nosplash", p.c_str()); usleep(250000); /* check again */ id = xscreensaver_find_own_window(dpy); if(id == 0) return false; } return true; } SaverPrefs *xscreensaver_read_config(void) { XrmDatabase db; XrmValue xrmv; char *type; /* * Luckily, xscreensaver uses X resource for storage * which saves me from parsing... jwz thanx !!! */ String path = dir_home(); path += "/.xscreensaver"; XrmInitialize(); db = XrmGetFileDatabase(path.c_str()); if(!db) { E_WARNING(E_STRLOC ": Unable to open xscreensaver config file, trying to guess...\n"); return guess_config(); } SaverPrefs *ret = new SaverPrefs; ret->curr_hack = 0; ret->timeout = 2; // in minutes ret->mode = SAVER_ONE; ret->dpms_enabled = false; ret->dpms_standby = ret->dpms_suspend = ret->dpms_off = 30; // in minutes if(XrmGetResource(db, "selected", "*", &type, &xrmv) == True && xrmv.addr != NULL) { /* * safe without checks since 0 (if atoi() fails) is first hack * in the list */ ret->curr_hack = atoi(xrmv.addr); } if(XrmGetResource(db, "mode", "*", &type, &xrmv) == True && xrmv.addr != NULL) { const char *v = xrmv.addr; if(!strcasecmp(v, "false") || !strcasecmp(v, "off") || !strcasecmp(v, "no")) ret->mode = SAVER_OFF; else if(!strcasecmp(v, "blank")) ret->mode = SAVER_BLANK; else if(!strcasecmp(v, "random")) ret->mode = SAVER_RANDOM; else ret->mode = SAVER_ONE; } if(XrmGetResource(db, "timeout", "*", &type, &xrmv) == True && xrmv.addr != NULL) ret->timeout = time_to_min(xrmv.addr); if(XrmGetResource(db, "dpmsEnabled", "*", &type, &xrmv) == True && xrmv.addr != NULL) { const char *v = xrmv.addr; if(!strcasecmp(v, "true") || !strcasecmp(v, "on") || !strcasecmp(v, "yes")) ret->dpms_enabled = true; } if(XrmGetResource(db, "dpmsStandby", "*", &type, &xrmv) == True && xrmv.addr != NULL) ret->dpms_standby = time_to_min(xrmv.addr); if(XrmGetResource(db, "dpmsSuspend", "*", &type, &xrmv) == True && xrmv.addr != NULL) ret->dpms_suspend = time_to_min(xrmv.addr); if(XrmGetResource(db, "dpmsOff", "*", &type, &xrmv) == True && xrmv.addr != NULL) ret->dpms_off = time_to_min(xrmv.addr); /* * Parse hacks (screensavers), skipping those that starts with '-'. Also, check if hack * contains name beside command (e.g. '"Qix (solid)" qix -root') and use it; otherwise use * capitalized command name. * * Note that to the each hack will be given index number; in the final GUI list they * will be ordered, but xscreensaver keeps own order for selecting that must be preserved. */ int nhacks = 0; if(XrmGetResource(db, "programs", "*", &type, &xrmv) == True) { char *programs = strdup(xrmv.addr); char *c = NULL; char *p = NULL; char buf[256]; unsigned int i; for(c = strtok(programs, "\n"); c; c = strtok(NULL, "\n"), nhacks++) { /* skip those marked for skipping */ if(c[0] == '-') continue; if((p = strstr(c, "GL:")) != NULL) { p += 3; c = p; } EAT_SPACES(c); if(*c == '"') { /* extract name from '"' */ c++; for(i = 0; i < sizeof(buf) && *c && *c != '"'; c++, i++) buf[i] = *c; buf[i] = '\0'; /* skip ending '"' */ c++; } else { /* store it so we could back for command line */ char *tc = c; /* or read command and capitalize it */ for(i = 0; i < sizeof(buf) && *tc && *tc != ' '; tc++, i++) buf[i] = *tc; buf[i] = '\0'; buf[0] = toupper(buf[0]); } SaverHack *h = new SaverHack; h->name = buf; h->sindex = nhacks; /* remove remainig gap */ EAT_SPACES(c); /* * now go for exec command * * FIXME: it will miss options spread in two or more lines (in Xresource sense) * so when read via XrmGetResource(), it will be merged in a long line with a bunch * of spaces and tabs. See 'crackberg' command */ for(i = 0; i < sizeof(buf) && *c && *c != '\t'; c++, i++) buf[i] = *c; buf[i] = '\0'; h->exec = buf; ret->hacks.push_back(h); } free(programs); } XrmDestroyDatabase(db); return ret; } void xscreensaver_save_config(SaverPrefs *sp) { String tmp_path = dir_home(); String path = tmp_path; tmp_path += "/.xscreensaver.tmp"; path +="/.xscreensaver"; FILE *fd = fopen(tmp_path.c_str(), "w"); if(!fd) { E_WARNING(E_STRLOC ": Unable to write temporary in %s! Nothing will be saved\n", tmp_path.c_str()); return; } fprintf(fd, "# XScreenSaver Preferences File\n"); fprintf(fd, "# Written by ede-screensaver-conf\n\n"); const char* val; switch(sp->mode) { case SAVER_OFF: val = "off"; break; case SAVER_BLANK: val = "blank"; break; case SAVER_RANDOM: val = "random"; break; default: val = "one"; break; } String tm; fprintf(fd, "mode: %s\n", val); fprintf(fd, "selected: %i\n", sp->curr_hack); min_to_time(sp->timeout, tm); fprintf(fd, "timeout: %s\n", tm.c_str()); if(sp->dpms_enabled) val = "yes"; else val = "no"; fprintf(fd, "dpmsEnabled: %s\n", val); min_to_time(sp->dpms_standby, tm); fprintf(fd, "dpmsStandby: %s\n", tm.c_str()); min_to_time(sp->dpms_suspend, tm); fprintf(fd, "dpmsSuspend: %s\n", tm.c_str()); min_to_time(sp->dpms_off, tm); fprintf(fd, "dpmsOff: %s\n", tm.c_str()); fprintf(fd, "programs:\t\t\\\n"); HackListIter it = sp->hacks.begin(), it_end = sp->hacks.end(); for(; it != it_end; ++it) fprintf(fd, "\t\t \"%s\" %s\t\t \\n\\\n", (*it)->name.c_str(), (*it)->exec.c_str()); fprintf(fd, "\n\n"); /* some defaults */ fprintf(fd, "sgiSaverExtension: True\n"); fprintf(fd, "xidleExtension: True\n"); fprintf(fd, "procInterrupts: True\n"); fprintf(fd, "GetViewPortIsFullOfLies: False\n"); fprintf(fd, "demoCommand: xscreensaver-demo\n"); fprintf(fd, "prefsCommand: xscreensaver-demo -prefs\n"); fclose(fd); /* * Now open it as Xresource database and merge with the real ~/.xscreensaver * file, so other values we didn't wrote/used are preserved. If it does not * exists, save it as ordinary file */ XrmInitialize(); XrmDatabase db = XrmGetFileDatabase(path.c_str()); if(db) { XrmCombineFileDatabase(tmp_path.c_str(), &db, True); /* and store it as ~/.xscreensaver */ XrmPutFileDatabase(db, path.c_str()); XrmDestroyDatabase(db); file_remove(tmp_path.c_str()); } else { file_rename(tmp_path.c_str(), path.c_str()); } } /* run screensaver in in FLTK window */ void xscreensaver_preview(int id, const char* name) { const char* hacks_folder = find_hacks_dir_once(); E_RETURN_IF_FAIL(hacks_folder); if(xscr_preview_pid) xscreensaver_kill_preview(); String cmd; cmd.printf("%s%s -window-id 0x%X", hacks_folder, name, id); pid_t f = fork(); switch((int)f) { case -1: E_WARNING(E_STRLOC ": Unable to fork screensaver process\n"); break; case 0: { usleep(250000); char* argv[4]; argv[0] = (char*)"sh"; argv[1] = (char*)"-c"; argv[2] = (char*)cmd.c_str(); argv[3] = NULL; execve("/bin/sh", argv, environ); /* never reached */ exit(1); break; } default: xscr_preview_pid = f; break; } } void xscreensaver_kill_preview(void) { if(xscr_preview_pid) { kill(xscr_preview_pid, SIGTERM); xscr_preview_pid = 0; } }