ede/ede-screensaver-conf/XScreenSaver.cpp

660 lines
15 KiB
C++

/*
* $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 <karijes@equinox-project.org>
*
* 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 <config.h>
#endif
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <dirent.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xresource.h>
#include <edelib/File.h>
#include <edelib/FileTest.h>
#include <edelib/Run.h>
#include <edelib/Directory.h>
#include <edelib/TiXml.h>
#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 '<command>' 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;
}
}