/*
* Copyright 2010 Johan Veenhuizen
*/
#include <errno.h>
#include <fcntl.h>
#include <locale.h>
#include <poll.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <X11/Xlib.h>
#include <X11/Xresource.h>
#include <X11/Xutil.h>
#include "wind.h"
#include "deleven.xbm"
#include "delodd.xbm"
static int errhandler(Display *, XErrorEvent *);
static void onsignal(int);
static int waitevent(void);
static void usage(FILE *);
static struct listener *getlistener(Window);
enum runlevel runlevel = RL_STARTUP;
static DEFINE_BITMAP(deleven);
static DEFINE_BITMAP(delodd);
struct bitmap *deletebitmap;
static const char *progname;
// Print X errors to standard error
static Bool verbose = False;
// The display name used in call to XOpenDisplay
const char *displayname = NULL;
// The last X error reported
const char *xerror = NULL;
Display *dpy;
unsigned scr;
Window root;
unsigned long foregroundpixel;
unsigned long backgroundpixel;
unsigned long hlforegroundpixel;
unsigned long hlbackgroundpixel;
GC foreground;
GC background;
GC hlforeground;
GC hlbackground;
int lineheight;
int halfleading;
struct font *font;
struct fontcolor *fhighlight;
struct fontcolor *fnormal;
Atom WM_CHANGE_STATE;
Atom WM_DELETE_WINDOW;
Atom WM_PROTOCOLS;
Atom WM_STATE;
Atom WM_TAKE_FOCUS;
static int selfpipe[2] = { -1, -1 };
static struct pollfd pollfd[2];
static XContext listeners;
/*
* Print formatted error message
*/
void errorf(const char *fmt, ...)
{
va_list ap;
fprintf(stderr, "%s: ", progname);
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fprintf(stderr, "\n");
}
void setlistener(Window w, const struct listener *l)
{
if (l == NULL)
XDeleteContext(dpy, w, listeners);
else
XSaveContext(dpy, w, listeners, (XPointer)l);
}
static struct listener *getlistener(Window w)
{
struct listener *l;
if (XFindContext(dpy, w, listeners, (XPointer *)&l) == 0)
return l;
else
return NULL;
}
int redirect(XEvent *e, Window w)
{
struct listener *l = getlistener(w);
if (l == NULL)
return -1;
l->function(l->pointer, e);
return 0;
}
static int errhandler(Display *dpy, XErrorEvent *e)
{
static char buf[128];
buf[0] = '\0';
XGetErrorText(dpy, e->error_code, buf, sizeof buf);
if (verbose)
errorf("Xlib: %s", buf);
xerror = buf;
return 0;
}
static void onsignal(int signo)
{
int e = errno;
char c = signo;
write(selfpipe[1], &c, 1);
errno = e;
}
static int waitevent(void)
{
XSync(dpy, False);
if (XQLength(dpy) > 0)
return 0;
for (;;) {
int r;
do {
r = poll(pollfd, 2, -1);
} while (r == -1 && (errno == EINTR || errno == EAGAIN));
if (r == -1) {
errorf("poll: %s", strerror(errno));
return -1;
} else if (pollfd[1].revents == POLLIN) {
// signal received
char c;
read(pollfd[1].fd, &c, 1);
return -1;
} else if (pollfd[0].revents == POLLIN) {
return 0;
} else if (pollfd[0].revents == POLLHUP ||
pollfd[0].revents == POLLERR) {
errorf("X connection broken");
return -1;
}
}
}
static void usage(FILE *f)
{
fprintf(f,"usage: %s [-hv]"
" [-n number]"
" [-t font]"
" [-f color]"
" [-b color]"
" [-F color]"
" [-B color]"
" [display]\n", progname);
}
int main(int argc, char *argv[])
{
progname = argv[0];
// The Xmb* functions use LC_CTYPE
setlocale(LC_CTYPE, "");
srand((unsigned)time(NULL));
runlevel = RL_STARTUP;
char *ftname = NULL;
char *fname = "rgb:00/00/00";
char *bname = "rgb:ff/ff/ff";
char *hlfname = "rgb:00/00/00";
char *hlbname = "rgb:7f/ff/ff";
Desk ndesk = 0;
int opt;
while ((opt = getopt(argc, argv, "B:b:F:f:hn:t:v")) != -1)
switch (opt) {
case 'B':
hlbname = optarg;
break;
case 'b':
bname = optarg;
break;
case 'F':
hlfname = optarg;
break;
case 'f':
fname = optarg;
break;
case 'h':
printf("%s\n", PACKAGE_STRING);
usage(stdout);
exit(0);
case 'n':
ndesk = strtoul(optarg, NULL, 10);
break;
case 't':
ftname = optarg;
break;
case 'v':
verbose = True;
break;
default:
usage(stderr);
exit(1);
}
if (optind < argc)
displayname = argv[optind++];
if (optind < argc) {
errorf("unexpected argument -- %s", argv[optind]);
usage(stderr);
exit(1);
}
XSetErrorHandler(errhandler);
if ((dpy = XOpenDisplay(displayname)) == NULL) {
errorf("cannot open display \"%s\"",
XDisplayName(displayname));
exit(1);
}
scr = DefaultScreen(dpy);
root = DefaultRootWindow(dpy);
font = ftload(ftname);
if (font == NULL) {
errorf("cannot load font");
exit(1);
}
fnormal = ftloadcolor(fname);
fhighlight = ftloadcolor(hlfname);
if (fnormal == NULL || fhighlight == NULL) {
errorf("cannot load font colors");
exit(1);
}
halfleading = (3 * font->size / 10) / 2;
lineheight = font->size + 2 * halfleading;
if (lineheight % 2 == 0)
deletebitmap = &deleven;
else
deletebitmap = &delodd;
foregroundpixel = getpixel(fname);
backgroundpixel = getpixel(bname);
hlforegroundpixel = getpixel(hlfname);
hlbackgroundpixel = getpixel(hlbname);
foreground = XCreateGC(dpy, root, GCForeground | GCBackground,
&(XGCValues){
.foreground = foregroundpixel,
.background = backgroundpixel });
background = XCreateGC(dpy, root, GCForeground | GCBackground,
&(XGCValues){
.foreground = backgroundpixel,
.background = foregroundpixel });
hlforeground = XCreateGC(dpy, root, GCForeground | GCBackground,
&(XGCValues){
.foreground = hlforegroundpixel,
.background = hlbackgroundpixel });
hlbackground = XCreateGC(dpy, root, GCForeground | GCBackground,
&(XGCValues){
.foreground = hlbackgroundpixel,
.background = hlforegroundpixel });
listeners = XUniqueContext();
if (pipe(selfpipe) == -1) {
errorf("cannot create pipe: %s", strerror(errno));
exit(1);
}
fcntl(selfpipe[0], F_SETFL, O_NONBLOCK);
fcntl(selfpipe[1], F_SETFL, O_NONBLOCK);
pollfd[0].fd = ConnectionNumber(dpy);
pollfd[0].events = POLLIN;
pollfd[1].fd = selfpipe[0];
pollfd[1].events = POLLIN;
struct sigaction sa;
sigfillset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sa.sa_handler = onsignal;
struct sigaction osa;
sigaction(SIGHUP, NULL, &osa);
if (osa.sa_handler != SIG_IGN)
sigaction(SIGHUP, &sa, NULL);
sigaction(SIGINT, NULL, &osa);
if (osa.sa_handler != SIG_IGN)
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, NULL, &osa);
if (osa.sa_handler != SIG_IGN)
sigaction(SIGTERM, &sa, NULL);
WM_CHANGE_STATE = XInternAtom(dpy, "WM_CHANGE_STATE", False);
WM_DELETE_WINDOW = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
WM_PROTOCOLS = XInternAtom(dpy, "WM_PROTOCOLS", False);
WM_STATE = XInternAtom(dpy, "WM_STATE", False);
WM_TAKE_FOCUS = XInternAtom(dpy, "WM_TAKE_FOCUS", False);
initroot();
ewmh_startwm();
mwm_startwm();
if (ndesk != 0)
setndesk(ndesk);
XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime);
manageall();
refocus(CurrentTime);
runlevel = RL_NORMAL;
while (waitevent() != -1) {
XEvent e;
XNextEvent(dpy, &e);
if (redirect(&e, e.xany.window) == -1) {
/*
* EWMH specifies some root window client
* messages with a non-root event window,
* so we need to redirect those manually.
*/
if (e.type == ClientMessage)
redirect(&e, root);
}
restack();
}
runlevel = RL_SHUTDOWN;
// We make sure the focused window stays on top
// when we map windows from other desktops, and
// to warp the pointer so that focus is not lost.
Window w = None;
struct geometry g;
struct client *c = getfocus();
if (c != NULL) {
cpopgrp(c);
restack();
w = cgetwin(c);
g = cgetgeom(c);
}
unmanageall();
if (w != None)
XWarpPointer(dpy, None, w, 0, 0, 0, 0,
g.width / 2, g.height / 2);
XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime);
ewmh_stopwm();
ftfreecolor(fnormal);
ftfreecolor(fhighlight);
ftfree(font);
XFreeGC(dpy, foreground);
XFreeGC(dpy, background);
XFreeGC(dpy, hlforeground);
XFreeGC(dpy, hlbackground);
XCloseDisplay(dpy);
return 0;
}