sistema_progs

Programas para customizar o meu entorno de traballo nos meus equipos persoais
Log | Files | Refs

tabbed.c (28180B)


      1 /*
      2  * See LICENSE file for copyright and license details.
      3  */
      4 
      5 #include <sys/wait.h>
      6 #include <locale.h>
      7 #include <stdarg.h>
      8 #include <unistd.h>
      9 #include <signal.h>
     10 #include <stdio.h>
     11 #include <string.h>
     12 #include <stdlib.h>
     13 #include <X11/Xatom.h>
     14 #include <X11/Xlib.h>
     15 #include <X11/Xproto.h>
     16 #include <X11/Xutil.h>
     17 #include <X11/XKBlib.h>
     18 
     19 #include "arg.h"
     20 
     21 /* XEMBED messages */
     22 #define XEMBED_EMBEDDED_NOTIFY          0
     23 #define XEMBED_WINDOW_ACTIVATE          1
     24 #define XEMBED_WINDOW_DEACTIVATE        2
     25 #define XEMBED_REQUEST_FOCUS            3
     26 #define XEMBED_FOCUS_IN                 4
     27 #define XEMBED_FOCUS_OUT                5
     28 #define XEMBED_FOCUS_NEXT               6
     29 #define XEMBED_FOCUS_PREV               7
     30 /* 8-9 were used for XEMBED_GRAB_KEY/XEMBED_UNGRAB_KEY */
     31 #define XEMBED_MODALITY_ON              10
     32 #define XEMBED_MODALITY_OFF             11
     33 #define XEMBED_REGISTER_ACCELERATOR     12
     34 #define XEMBED_UNREGISTER_ACCELERATOR   13
     35 #define XEMBED_ACTIVATE_ACCELERATOR     14
     36 
     37 /* Details for  XEMBED_FOCUS_IN: */
     38 #define XEMBED_FOCUS_CURRENT            0
     39 #define XEMBED_FOCUS_FIRST              1
     40 #define XEMBED_FOCUS_LAST               2
     41 
     42 /* Macros */
     43 #define MAX(a, b)                ((a) > (b) ? (a) : (b))
     44 #define MIN(a, b)                ((a) < (b) ? (a) : (b))
     45 #define LENGTH(x)                (sizeof((x)) / sizeof(*(x)))
     46 #define CLEANMASK(mask)          (mask & ~(numlockmask|LockMask))
     47 #define TEXTW(x)                 (textnw(x, strlen(x)) + dc.font.height)
     48 
     49 enum { ColFG, ColBG, ColLast };                         /* color */
     50 enum { WMProtocols, WMDelete, WMName, WMState, WMFullscreen,
     51 	XEmbed, WMSelectTab, WMLast };                      /* default atoms */
     52 
     53 typedef union {
     54 	int i;
     55 	const void *v;
     56 } Arg;
     57 
     58 typedef struct {
     59 	unsigned int mod;
     60 	KeySym keysym;
     61 	void (*func)(const Arg *);
     62 	const Arg arg;
     63 } Key;
     64 
     65 typedef struct {
     66 	int x, y, w, h;
     67 	unsigned long norm[ColLast];
     68 	unsigned long sel[ColLast];
     69 	Drawable drawable;
     70 	GC gc;
     71 	struct {
     72 		int ascent;
     73 		int descent;
     74 		int height;
     75 		XFontSet set;
     76 		XFontStruct *xfont;
     77 	} font;
     78 } DC; /* draw context */
     79 
     80 typedef struct Client {
     81 	char name[256];
     82 	Window win;
     83 	int tabx;
     84 	Bool mapped;
     85 	Bool closed;
     86 } Client;
     87 
     88 /* function declarations */
     89 static void buttonpress(const XEvent *e);
     90 static void cleanup(void);
     91 static void clientmessage(const XEvent *e);
     92 static void configurenotify(const XEvent *e);
     93 static void configurerequest(const XEvent *e);
     94 static void createnotify(const XEvent *e);
     95 static void destroynotify(const XEvent *e);
     96 static void die(const char *errstr, ...);
     97 static void drawbar(void);
     98 static void drawtext(const char *text, unsigned long col[ColLast]);
     99 static void *emallocz(size_t size);
    100 static void *erealloc(void *o, size_t size);
    101 static void expose(const XEvent *e);
    102 static void focus(int c);
    103 static void focusin(const XEvent *e);
    104 static void focusonce(const Arg *arg);
    105 static void fullscreen(const Arg *arg);
    106 static char* getatom(int a);
    107 static int getclient(Window w);
    108 static unsigned long getcolor(const char *colstr);
    109 static int getfirsttab(void);
    110 static Bool gettextprop(Window w, Atom atom, char *text, unsigned int size);
    111 static void initfont(const char *fontstr);
    112 static Bool isprotodel(int c);
    113 static void keypress(const XEvent *e);
    114 static void killclient(const Arg *arg);
    115 static void manage(Window win);
    116 static void maprequest(const XEvent *e);
    117 static void move(const Arg *arg);
    118 static void movetab(const Arg *arg);
    119 static void propertynotify(const XEvent *e);
    120 static void resize(int c, int w, int h);
    121 static void rotate(const Arg *arg);
    122 static void run(void);
    123 static void sendxembed(int c, long msg, long detail, long d1, long d2);
    124 static void setup(void);
    125 static void setcmd(int argc, char *argv[], int);
    126 static void sigchld(int unused);
    127 static void spawn(const Arg *arg);
    128 static int textnw(const char *text, unsigned int len);
    129 static void unmanage(int c);
    130 static void updatenumlockmask(void);
    131 static void updatetitle(int c);
    132 static int xerror(Display *dpy, XErrorEvent *ee);
    133 static void xsettitle(Window w, const char *str);
    134 
    135 /* variables */
    136 static int screen;
    137 static void (*handler[LASTEvent]) (const XEvent *) = {
    138 	[ButtonPress] = buttonpress,
    139 	[ClientMessage] = clientmessage,
    140 	[ConfigureNotify] = configurenotify,
    141 	[ConfigureRequest] = configurerequest,
    142 	[CreateNotify] = createnotify,
    143 	[DestroyNotify] = destroynotify,
    144 	[Expose] = expose,
    145 	[FocusIn] = focusin,
    146 	[KeyPress] = keypress,
    147 	[MapRequest] = maprequest,
    148 	[PropertyNotify] = propertynotify,
    149 };
    150 static int bh, wx, wy, ww, wh;
    151 static unsigned int numlockmask = 0;
    152 static Bool running = True, nextfocus, doinitspawn = True,
    153 	    fillagain = False, closelastclient = False;
    154 static Display *dpy;
    155 static DC dc;
    156 static Atom wmatom[WMLast];
    157 static Window root, win;
    158 static Client **clients = NULL;
    159 static int nclients = 0, sel = -1, lastsel = -1;
    160 static int (*xerrorxlib)(Display *, XErrorEvent *);
    161 static int cmd_append_pos = 0;
    162 static char winid[64];
    163 static char **cmd = NULL;
    164 static char *wmname = "tabbed";
    165 static const char *geometry = NULL;
    166 
    167 char *argv0;
    168 
    169 /* configuration, allows nested code to access above variables */
    170 #include "config.h"
    171 
    172 void
    173 buttonpress(const XEvent *e) {
    174 	const XButtonPressedEvent *ev = &e->xbutton;
    175 	int i;
    176 	int fc;
    177 	Arg arg;
    178 
    179 	fc = getfirsttab();
    180 
    181 	if((fc > 0 && ev->x < TEXTW(before)) || ev->x < 0)
    182 		return;
    183 
    184 	if(ev->y < 0 || ev-> y > bh)
    185 		return;
    186 
    187 	for(i = (fc > 0) ? fc : 0; i < nclients; i++) {
    188 		if(clients[i]->tabx > ev->x) {
    189 			switch(ev->button) {
    190 			case Button1:
    191 				focus(i);
    192 				break;
    193 			case Button2:
    194 				focus(i);
    195 				killclient(NULL);
    196 				break;
    197 			case Button4:
    198 			case Button5:
    199 				arg.i = ev->button == Button4 ? -1 : 1;
    200 				rotate(&arg);
    201 				break;
    202 			}
    203 			break;
    204 		}
    205 	}
    206 }
    207 
    208 void
    209 cleanup(void) {
    210 	int i;
    211 
    212 	for(i = 0; i < nclients; i++) {
    213 		focus(i);
    214 		killclient(NULL);
    215 		killclient(NULL);
    216 		XReparentWindow(dpy, clients[i]->win, root, 0, 0);
    217 		unmanage(i);
    218 	}
    219 	free(clients);
    220 	clients = NULL;
    221 
    222 	if(dc.font.set) {
    223 		XFreeFontSet(dpy, dc.font.set);
    224 	} else {
    225 		XFreeFont(dpy, dc.font.xfont);
    226 	}
    227 
    228 	XFreePixmap(dpy, dc.drawable);
    229 	XFreeGC(dpy, dc.gc);
    230 	XDestroyWindow(dpy, win);
    231 	XSync(dpy, False);
    232 	free(cmd);
    233 }
    234 
    235 void
    236 clientmessage(const XEvent *e) {
    237 	const XClientMessageEvent *ev = &e->xclient;
    238 
    239 	if(ev->message_type == wmatom[WMProtocols]
    240 			&& ev->data.l[0] == wmatom[WMDelete]) {
    241 		running = False;
    242 	}
    243 }
    244 
    245 void
    246 configurenotify(const XEvent *e) {
    247 	const XConfigureEvent *ev = &e->xconfigure;
    248 
    249 	if(ev->window == win && (ev->width != ww || ev->height != wh)) {
    250 		ww = ev->width;
    251 		wh = ev->height;
    252 		XFreePixmap(dpy, dc.drawable);
    253 		dc.drawable = XCreatePixmap(dpy, root, ww, wh,
    254 				DefaultDepth(dpy, screen));
    255 		if(sel > -1)
    256 			resize(sel, ww, wh - bh);
    257 		XSync(dpy, False);
    258 	}
    259 }
    260 
    261 void
    262 configurerequest(const XEvent *e) {
    263 	const XConfigureRequestEvent *ev = &e->xconfigurerequest;
    264 	XWindowChanges wc;
    265 	int c;
    266 
    267 	if((c = getclient(ev->window)) > -1) {
    268 		wc.x = 0;
    269 		wc.y = bh;
    270 		wc.width = ww;
    271 		wc.height = wh - bh;
    272 		wc.border_width = 0;
    273 		wc.sibling = ev->above;
    274 		wc.stack_mode = ev->detail;
    275 		XConfigureWindow(dpy, clients[c]->win, ev->value_mask, &wc);
    276 	}
    277 }
    278 
    279 void
    280 createnotify(const XEvent *e) {
    281 	const XCreateWindowEvent *ev = &e->xcreatewindow;
    282 
    283 	if(ev->window != win && getclient(ev->window) < 0)
    284 		manage(ev->window);
    285 }
    286 
    287 void
    288 destroynotify(const XEvent *e) {
    289 	const XDestroyWindowEvent *ev = &e->xdestroywindow;
    290 	int c;
    291 
    292 	if((c = getclient(ev->window)) > -1)
    293 		unmanage(c);
    294 }
    295 
    296 void
    297 die(const char *errstr, ...) {
    298 	va_list ap;
    299 
    300 	va_start(ap, errstr);
    301 	vfprintf(stderr, errstr, ap);
    302 	va_end(ap);
    303 	exit(EXIT_FAILURE);
    304 }
    305 
    306 void
    307 drawbar(void) {
    308 	unsigned long *col;
    309 	int c, fc, width, n = 0;
    310 	char *name = NULL;
    311 
    312 	if(nclients == 0) {
    313 		dc.x = 0;
    314 		dc.w = ww;
    315 		XFetchName(dpy, win, &name);
    316 		drawtext(name ? name : "", dc.norm);
    317 		XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, ww, bh, 0, 0);
    318 		XSync(dpy, False);
    319 
    320 		return;
    321 	}
    322 
    323 	width = ww;
    324 	clients[nclients-1]->tabx = -1;
    325 	fc = getfirsttab();
    326 	if(fc > -1)
    327 		n = nclients - fc;
    328 
    329 	if((n * tabwidth) > width) {
    330 		dc.w = TEXTW(after);
    331 		dc.x = width - dc.w;
    332 		drawtext(after, dc.sel);
    333 		width -= dc.w;
    334 	}
    335 	dc.x = 0;
    336 
    337 	if(fc > 0) {
    338 		dc.w = TEXTW(before);
    339 		drawtext(before, dc.sel);
    340 		dc.x += dc.w;
    341 		width -= dc.w;
    342 	}
    343 
    344 	for(c = (fc > 0)? fc : 0; c < nclients && dc.x < width; c++) {
    345 		dc.w = tabwidth;
    346 		if(c == sel) {
    347 			col = dc.sel;
    348 			if((n * tabwidth) > width) {
    349 				dc.w += width % tabwidth;
    350 			} else {
    351 				dc.w = width - (n - 1) * tabwidth;
    352 			}
    353 		} else {
    354 			col = dc.norm;
    355 		}
    356 		drawtext(clients[c]->name, col);
    357 		dc.x += dc.w;
    358 		clients[c]->tabx = dc.x;
    359 	}
    360 	XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, ww, bh, 0, 0);
    361 	XSync(dpy, False);
    362 }
    363 
    364 void
    365 drawtext(const char *text, unsigned long col[ColLast]) {
    366 	int i, x, y, h, len, olen;
    367 	char buf[256];
    368 	XRectangle r = { dc.x, dc.y, dc.w, dc.h };
    369 
    370 	XSetForeground(dpy, dc.gc, col[ColBG]);
    371 	XFillRectangles(dpy, dc.drawable, dc.gc, &r, 1);
    372 	if(!text)
    373 		return;
    374 
    375 	olen = strlen(text);
    376 	h = dc.font.ascent + dc.font.descent;
    377 	y = dc.y + (dc.h / 2) - (h / 2) + dc.font.ascent;
    378 	x = dc.x + (h / 2);
    379 
    380 	/* shorten text if necessary */
    381 	for(len = MIN(olen, sizeof(buf));
    382 			len && textnw(text, len) > dc.w - h; len--);
    383 	if(!len)
    384 		return;
    385 
    386 	memcpy(buf, text, len);
    387 	if(len < olen) {
    388 		for(i = len; i && i > len - 3; buf[--i] = '.');
    389 	}
    390 
    391 	XSetForeground(dpy, dc.gc, col[ColFG]);
    392 	if(dc.font.set) {
    393 		XmbDrawString(dpy, dc.drawable, dc.font.set,
    394 				dc.gc, x, y, buf, len);
    395 	} else {
    396 		XDrawString(dpy, dc.drawable, dc.gc, x, y, buf, len);
    397 	}
    398 }
    399 
    400 void *
    401 emallocz(size_t size) {
    402 	void *p;
    403 
    404 	if(!(p = calloc(1, size)))
    405 		die("tabbed: cannot malloc\n");
    406 	return p;
    407 }
    408 
    409 void *
    410 erealloc(void *o, size_t size) {
    411 	void *p;
    412 
    413 	if(!(p = realloc(o, size)))
    414 		die("tabbed: cannot realloc\n");
    415 	return p;
    416 }
    417 
    418 void
    419 expose(const XEvent *e) {
    420 	const XExposeEvent *ev = &e->xexpose;
    421 
    422 	if(ev->count == 0 && win == ev->window)
    423 		drawbar();
    424 }
    425 
    426 void
    427 focus(int c) {
    428 	char buf[BUFSIZ] = "tabbed-"VERSION" ::";
    429 	size_t i, n;
    430 
    431 	/* If c, sel and clients are -1, raise tabbed-win itself */
    432 	if(nclients == 0) {
    433 		cmd[cmd_append_pos] = NULL;
    434 		for(i = 0, n = strlen(buf); cmd[i] && n < sizeof(buf); i++)
    435 			n += snprintf(&buf[n], sizeof(buf) - n, " %s", cmd[i]);
    436 
    437 		xsettitle(win, buf);
    438 		XRaiseWindow(dpy, win);
    439 
    440 		return;
    441 	}
    442 
    443 	if(c < 0 || c >= nclients)
    444 		return;
    445 
    446 	resize(c, ww, wh - bh);
    447 	XRaiseWindow(dpy, clients[c]->win);
    448 	XSetInputFocus(dpy, clients[c]->win, RevertToParent, CurrentTime);
    449 	sendxembed(c, XEMBED_FOCUS_IN, XEMBED_FOCUS_CURRENT, 0, 0);
    450 	sendxembed(c, XEMBED_WINDOW_ACTIVATE, 0, 0, 0);
    451 	xsettitle(win, clients[c]->name);
    452 
    453 	/* If sel is already c, change nothing. */
    454 	if(sel != c) {
    455 		lastsel = sel;
    456 		sel = c;
    457 	}
    458 
    459 	drawbar();
    460 	XSync(dpy, False);
    461 }
    462 
    463 void
    464 focusin(const XEvent *e) {
    465 	const XFocusChangeEvent *ev = &e->xfocus;
    466 	int dummy;
    467 	Window focused;
    468 
    469 	if(ev->mode != NotifyUngrab) {
    470 		XGetInputFocus(dpy, &focused, &dummy);
    471 		if(focused == win)
    472 			focus(sel);
    473 	}
    474 }
    475 
    476 void
    477 focusonce(const Arg *arg) {
    478 	nextfocus = True;
    479 }
    480 
    481 void
    482 fullscreen(const Arg *arg) {
    483 	XEvent e;
    484 
    485 	e.type = ClientMessage;
    486 	e.xclient.window = win;
    487 	e.xclient.message_type = wmatom[WMState];
    488 	e.xclient.format = 32;
    489 	e.xclient.data.l[0] = 2;
    490 	e.xclient.data.l[1] = wmatom[WMFullscreen];
    491 	e.xclient.data.l[2] = 0;
    492 	XSendEvent(dpy, root, False, SubstructureNotifyMask, &e);
    493 }
    494 
    495 char *
    496 getatom(int a) {
    497 	static char buf[BUFSIZ];
    498 	Atom adummy;
    499 	int idummy;
    500 	unsigned long ldummy;
    501 	unsigned char *p = NULL;
    502 
    503 	XGetWindowProperty(dpy, win, wmatom[a], 0L, BUFSIZ, False, XA_STRING,
    504 			&adummy, &idummy, &ldummy, &ldummy, &p);
    505 	if(p) {
    506 		strncpy(buf, (char *)p, LENGTH(buf)-1);
    507 	} else {
    508 		buf[0] = '\0';
    509 	}
    510 	XFree(p);
    511 
    512 	return buf;
    513 }
    514 
    515 int
    516 getclient(Window w) {
    517 	int i;
    518 
    519 	for(i = 0; i < nclients; i++) {
    520 		if(clients[i]->win == w)
    521 			return i;
    522 	}
    523 
    524 	return -1;
    525 }
    526 
    527 unsigned long
    528 getcolor(const char *colstr) {
    529 	Colormap cmap = DefaultColormap(dpy, screen);
    530 	XColor color;
    531 
    532 	if(!XAllocNamedColor(dpy, cmap, colstr, &color, &color))
    533 		die("tabbed: cannot allocate color '%s'\n", colstr);
    534 
    535 	return color.pixel;
    536 }
    537 
    538 int
    539 getfirsttab(void) {
    540 	int c, n, fc;
    541 
    542 	if(sel < 0)
    543 		return -1;
    544 
    545 	c = sel;
    546 	fc = 0;
    547 	n = nclients;
    548 	if((n * tabwidth) > ww) {
    549 		for(; (c * tabwidth) > (ww / 2)
    550 				&& (n * tabwidth) > ww;
    551 				c--, n--, fc++);
    552 	}
    553 
    554 	return fc;
    555 }
    556 
    557 Bool
    558 gettextprop(Window w, Atom atom, char *text, unsigned int size) {
    559 	char **list = NULL;
    560 	int n;
    561 	XTextProperty name;
    562 
    563 	if(!text || size == 0)
    564 		return False;
    565 
    566 	text[0] = '\0';
    567 	XGetTextProperty(dpy, w, &name, atom);
    568 	if(!name.nitems)
    569 		return False;
    570 
    571 	if(name.encoding == XA_STRING) {
    572 		strncpy(text, (char *)name.value, size - 1);
    573 	} else {
    574 		if(XmbTextPropertyToTextList(dpy, &name, &list, &n) >= Success
    575 				&& n > 0 && *list) {
    576 			strncpy(text, *list, size - 1);
    577 			XFreeStringList(list);
    578 		}
    579 	}
    580 	text[size - 1] = '\0';
    581 	XFree(name.value);
    582 
    583 	return True;
    584 }
    585 
    586 void
    587 initfont(const char *fontstr) {
    588 	char *def, **missing, **font_names;
    589 	int i, n;
    590 	XFontStruct **xfonts;
    591 
    592 	missing = NULL;
    593 	if(dc.font.set)
    594 		XFreeFontSet(dpy, dc.font.set);
    595 
    596 	dc.font.set = XCreateFontSet(dpy, fontstr, &missing, &n, &def);
    597 	if(missing) {
    598 		while(n--)
    599 			fprintf(stderr, "tabbed: missing fontset: %s\n", missing[n]);
    600 		XFreeStringList(missing);
    601 	}
    602 
    603 	if(dc.font.set) {
    604 		dc.font.ascent = dc.font.descent = 0;
    605 		n = XFontsOfFontSet(dc.font.set, &xfonts, &font_names);
    606 		for(i = 0, dc.font.ascent = 0, dc.font.descent = 0; i < n; i++) {
    607 			dc.font.ascent = MAX(dc.font.ascent, (*xfonts)->ascent);
    608 			dc.font.descent = MAX(dc.font.descent,(*xfonts)->descent);
    609 			xfonts++;
    610 		}
    611 	} else {
    612 		if(dc.font.xfont)
    613 			XFreeFont(dpy, dc.font.xfont);
    614 		dc.font.xfont = NULL;
    615 		if(!(dc.font.xfont = XLoadQueryFont(dpy, fontstr))
    616 				&& !(dc.font.xfont = XLoadQueryFont(dpy, "fixed"))) {
    617 			die("tabbed: cannot load font: '%s'\n", fontstr);
    618 		}
    619 
    620 		dc.font.ascent = dc.font.xfont->ascent;
    621 		dc.font.descent = dc.font.xfont->descent;
    622 	}
    623 	dc.font.height = dc.font.ascent + dc.font.descent;
    624 }
    625 
    626 Bool
    627 isprotodel(int c) {
    628 	int i, n;
    629 	Atom *protocols;
    630 	Bool ret = False;
    631 
    632 	if(XGetWMProtocols(dpy, clients[c]->win, &protocols, &n)) {
    633 		for(i = 0; !ret && i < n; i++) {
    634 			if(protocols[i] == wmatom[WMDelete])
    635 				ret = True;
    636 		}
    637 		XFree(protocols);
    638 	}
    639 
    640 	return ret;
    641 }
    642 
    643 void
    644 keypress(const XEvent *e) {
    645 	const XKeyEvent *ev = &e->xkey;
    646 	unsigned int i;
    647 	KeySym keysym;
    648 
    649 	keysym = XkbKeycodeToKeysym(dpy, (KeyCode)ev->keycode, 0, 0);
    650 	for(i = 0; i < LENGTH(keys); i++) {
    651 		if(keysym == keys[i].keysym
    652 				&& CLEANMASK(keys[i].mod) == CLEANMASK(ev->state)
    653 				&& keys[i].func) {
    654 			keys[i].func(&(keys[i].arg));
    655 		}
    656 	}
    657 }
    658 
    659 void
    660 killclient(const Arg *arg) {
    661 	XEvent ev;
    662 
    663 	if(sel < 0)
    664 		return;
    665 
    666 	if(isprotodel(sel) && !clients[sel]->closed) {
    667 		ev.type = ClientMessage;
    668 		ev.xclient.window = clients[sel]->win;
    669 		ev.xclient.message_type = wmatom[WMProtocols];
    670 		ev.xclient.format = 32;
    671 		ev.xclient.data.l[0] = wmatom[WMDelete];
    672 		ev.xclient.data.l[1] = CurrentTime;
    673 		XSendEvent(dpy, clients[sel]->win, False, NoEventMask, &ev);
    674 		clients[sel]->closed = True;
    675 	} else {
    676 		XKillClient(dpy, clients[sel]->win);
    677 	}
    678 }
    679 
    680 void
    681 manage(Window w) {
    682 	updatenumlockmask();
    683 	{
    684 		int i, j, nextpos;
    685 		unsigned int modifiers[] = { 0, LockMask, numlockmask,
    686 			numlockmask|LockMask };
    687 		KeyCode code;
    688 		Client *c;
    689 		XEvent e;
    690 
    691 		XWithdrawWindow(dpy, w, 0);
    692 		XReparentWindow(dpy, w, win, 0, bh);
    693 		XSelectInput(dpy, w, PropertyChangeMask
    694 				|StructureNotifyMask|EnterWindowMask);
    695 		XSync(dpy, False);
    696 
    697 		for(i = 0; i < LENGTH(keys); i++) {
    698 			if((code = XKeysymToKeycode(dpy, keys[i].keysym))) {
    699 				for(j = 0; j < LENGTH(modifiers); j++) {
    700 					XGrabKey(dpy, code, keys[i].mod
    701 							| modifiers[j], w,
    702 						 True, GrabModeAsync,
    703 						 GrabModeAsync);
    704 				}
    705 			}
    706 		}
    707 
    708 		c = emallocz(sizeof(*c));
    709 		c->win = w;
    710 
    711 		nclients++;
    712 		clients = erealloc(clients, sizeof(Client *) * nclients);
    713 
    714 		if(npisrelative) {
    715 			nextpos = sel + newposition;
    716 		} else {
    717 			if(newposition < 0) {
    718 				nextpos = nclients - newposition;
    719 			} else {
    720 				nextpos = newposition;
    721 			}
    722 		}
    723 		if(nextpos >= nclients)
    724 			nextpos = nclients - 1;
    725 		if(nextpos < 0)
    726 			nextpos = 0;
    727 
    728 		if(nclients > 1 && nextpos < nclients - 1) {
    729 			memmove(&clients[nextpos + 1], &clients[nextpos],
    730 					sizeof(Client *) *
    731 					(nclients - nextpos - 1));
    732 		}
    733 		clients[nextpos] = c;
    734 		updatetitle(nextpos);
    735 
    736 		XLowerWindow(dpy, w);
    737 		XMapWindow(dpy, w);
    738 
    739 		e.xclient.window = w;
    740 		e.xclient.type = ClientMessage;
    741 		e.xclient.message_type = wmatom[XEmbed];
    742 		e.xclient.format = 32;
    743 		e.xclient.data.l[0] = CurrentTime;
    744 		e.xclient.data.l[1] = XEMBED_EMBEDDED_NOTIFY;
    745 		e.xclient.data.l[2] = 0;
    746 		e.xclient.data.l[3] = win;
    747 		e.xclient.data.l[4] = 0;
    748 		XSendEvent(dpy, root, False, NoEventMask, &e);
    749 
    750 		XSync(dpy, False);
    751 
    752 		/* Adjust sel before focus does set it to lastsel. */
    753 		if(sel >= nextpos)
    754 			sel++;
    755 		focus((nextfocus)? nextpos : ((sel < 0)? 0 : sel));
    756 		nextfocus = foreground;
    757 	}
    758 }
    759 
    760 void
    761 maprequest(const XEvent *e) {
    762 	const XMapRequestEvent *ev = &e->xmaprequest;
    763 
    764 	if(getclient(ev->window) < 0)
    765 		manage(ev->window);
    766 }
    767 
    768 void
    769 move(const Arg *arg) {
    770 	if(arg->i >= 0 && arg->i < nclients)
    771 		focus(arg->i);
    772 }
    773 
    774 void
    775 movetab(const Arg *arg) {
    776 	int c;
    777 	Client *new;
    778 
    779 	if(sel < 0 || (arg->i == 0))
    780 		return;
    781 
    782 	c = sel + arg->i;
    783 	while(c >= nclients)
    784 		c -= nclients;
    785 	while(c < 0)
    786 		c += nclients;
    787 
    788 	new = clients[c];
    789 	clients[c] = clients[sel];
    790 	clients[sel] = new;
    791 
    792 	sel = c;
    793 
    794 	drawbar();
    795 }
    796 
    797 void
    798 propertynotify(const XEvent *e) {
    799 	const XPropertyEvent *ev = &e->xproperty;
    800 	int c;
    801 	char* selection = NULL;
    802 	Arg arg;
    803 
    804 	if(ev->state == PropertyNewValue && ev->atom == wmatom[WMSelectTab]) {
    805 		selection = getatom(WMSelectTab);
    806 		if(!strncmp(selection, "0x", 2)) {
    807 			arg.i = getclient(strtoul(selection, NULL, 0));
    808 			move(&arg);
    809 		} else {
    810 			cmd[cmd_append_pos] = selection;
    811 			arg.v = cmd;
    812 			spawn(&arg);
    813 		}
    814 	} else if(ev->state != PropertyDelete && ev->atom == XA_WM_NAME
    815 			&& (c = getclient(ev->window)) > -1) {
    816 		updatetitle(c);
    817 	}
    818 }
    819 
    820 void
    821 resize(int c, int w, int h) {
    822 	XConfigureEvent ce;
    823 	XWindowChanges wc;
    824 
    825 	ce.x = 0;
    826 	ce.y = bh;
    827 	ce.width = wc.width = w;
    828 	ce.height = wc.height = h;
    829 	ce.type = ConfigureNotify;
    830 	ce.display = dpy;
    831 	ce.event = clients[c]->win;
    832 	ce.window = clients[c]->win;
    833 	ce.above = None;
    834 	ce.override_redirect = False;
    835 	ce.border_width = 0;
    836 
    837 	XConfigureWindow(dpy, clients[c]->win, CWWidth|CWHeight, &wc);
    838 	XSendEvent(dpy, clients[c]->win, False, StructureNotifyMask,
    839 			(XEvent *)&ce);
    840 }
    841 
    842 void
    843 rotate(const Arg *arg) {
    844 	int nsel = -1;
    845 
    846 	if(sel < 0)
    847 		return;
    848 
    849 	if(arg->i == 0) {
    850 		if(lastsel > -1)
    851 			focus(lastsel);
    852 	} else if(sel > -1) {
    853 		/* Rotating in an arg->i step around the clients. */
    854 		nsel = sel + arg->i;
    855 		while(nsel >= nclients)
    856 			nsel -= nclients;
    857 		while(nsel < 0)
    858 			nsel += nclients;
    859 		focus(nsel);
    860 	}
    861 }
    862 
    863 void
    864 run(void) {
    865 	XEvent ev;
    866 
    867 	/* main event loop */
    868 	XSync(dpy, False);
    869 	drawbar();
    870 	if(doinitspawn == True)
    871 		spawn(NULL);
    872 
    873 	while(running) {
    874 		XNextEvent(dpy, &ev);
    875 		if(handler[ev.type])
    876 			(handler[ev.type])(&ev); /* call handler */
    877 	}
    878 }
    879 
    880 void
    881 sendxembed(int c, long msg, long detail, long d1, long d2) {
    882 	XEvent e = { 0 };
    883 
    884 	e.xclient.window = clients[c]->win;
    885 	e.xclient.type = ClientMessage;
    886 	e.xclient.message_type = wmatom[XEmbed];
    887 	e.xclient.format = 32;
    888 	e.xclient.data.l[0] = CurrentTime;
    889 	e.xclient.data.l[1] = msg;
    890 	e.xclient.data.l[2] = detail;
    891 	e.xclient.data.l[3] = d1;
    892 	e.xclient.data.l[4] = d2;
    893 	XSendEvent(dpy, clients[c]->win, False, NoEventMask, &e);
    894 }
    895 
    896 void
    897 setcmd(int argc, char *argv[], int replace) {
    898 	int i;
    899 
    900 	cmd = emallocz((argc+3) * sizeof(*cmd));
    901 	if (argc == 0)
    902 		return;
    903 	for(i = 0; i < argc; i++)
    904 		cmd[i] = argv[i];
    905 	cmd[(replace > 0)? replace : argc] = winid;
    906 	cmd_append_pos = argc + !replace;
    907 	cmd[cmd_append_pos] = cmd[cmd_append_pos+1] = NULL;
    908 }
    909 
    910 void
    911 setup(void) {
    912 	int bitm, tx, ty, tw, th, dh, dw, isfixed;
    913 	XClassHint class_hint;
    914 	XSizeHints *size_hint;
    915 
    916 	/* clean up any zombies immediately */
    917 	sigchld(0);
    918 
    919 	/* init screen */
    920 	screen = DefaultScreen(dpy);
    921 	root = RootWindow(dpy, screen);
    922 	initfont(font);
    923 	bh = dc.h = dc.font.height + 2;
    924 
    925 	/* init atoms */
    926 	wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False);
    927 	wmatom[WMDelete] = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
    928 	wmatom[XEmbed] = XInternAtom(dpy, "_XEMBED", False);
    929 	wmatom[WMName] = XInternAtom(dpy, "_NET_WM_NAME", False);
    930 	wmatom[WMState] = XInternAtom(dpy, "_NET_WM_STATE", False);
    931 	wmatom[WMFullscreen] = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False);
    932 	wmatom[WMSelectTab] = XInternAtom(dpy, "_TABBED_SELECT_TAB", False);
    933 
    934 	/* init appearance */
    935 	wx = 0;
    936 	wy = 0;
    937 	ww = 800;
    938 	wh = 600;
    939 	isfixed = 0;
    940 
    941 	if(geometry) {
    942 		tx = ty = tw = th = 0;
    943 		bitm = XParseGeometry(geometry, &tx, &ty, (unsigned *)&tw,
    944 				(unsigned *)&th);
    945 		if(bitm & XValue)
    946 			wx = tx;
    947 		if(bitm & YValue)
    948 			wy = ty;
    949 		if(bitm & WidthValue)
    950 			ww = tw;
    951 		if(bitm & HeightValue)
    952 			wh = th;
    953 		if(bitm & XNegative && wx == 0)
    954 			wx = -1;
    955 		if(bitm & YNegative && wy == 0)
    956 			wy = -1;
    957 		if(bitm & (HeightValue|WidthValue))
    958 			isfixed = 1;
    959 
    960 		dw = DisplayWidth(dpy, screen);
    961 		dh = DisplayHeight(dpy, screen);
    962 		if(wx < 0)
    963 			wx = dw + wx - ww - 1;
    964 		if(wy < 0)
    965 			wy = dh + wy - wh - 1;
    966 	}
    967 
    968 	dc.norm[ColBG] = getcolor(normbgcolor);
    969 	dc.norm[ColFG] = getcolor(normfgcolor);
    970 	dc.sel[ColBG] = getcolor(selbgcolor);
    971 	dc.sel[ColFG] = getcolor(selfgcolor);
    972 	dc.drawable = XCreatePixmap(dpy, root, ww, wh,
    973 			DefaultDepth(dpy, screen));
    974 	dc.gc = XCreateGC(dpy, root, 0, 0);
    975 	if(!dc.font.set)
    976 		XSetFont(dpy, dc.gc, dc.font.xfont->fid);
    977 
    978 	win = XCreateSimpleWindow(dpy, root, wx, wy, ww, wh, 0,
    979 			dc.norm[ColFG], dc.norm[ColBG]);
    980 	XMapRaised(dpy, win);
    981 	XSelectInput(dpy, win, SubstructureNotifyMask|FocusChangeMask|
    982 			ButtonPressMask|ExposureMask|KeyPressMask|PropertyChangeMask|
    983 			StructureNotifyMask|SubstructureRedirectMask);
    984 	xerrorxlib = XSetErrorHandler(xerror);
    985 
    986 	class_hint.res_name = wmname;
    987 	class_hint.res_class = "tabbed";
    988 	XSetClassHint(dpy, win, &class_hint);
    989 
    990 	size_hint = XAllocSizeHints();
    991 	if(!isfixed) {
    992 		size_hint->flags = PSize;
    993 		size_hint->height = wh;
    994 		size_hint->width = ww;
    995 	} else {
    996 		size_hint->flags = PMaxSize | PMinSize;
    997 		size_hint->min_width = size_hint->max_width = ww;
    998 		size_hint->min_height = size_hint->max_height = wh;
    999 	}
   1000 	XSetWMProperties(dpy, win, NULL, NULL, NULL, 0, size_hint, NULL, NULL);
   1001 	XFree(size_hint);
   1002 
   1003 	XSetWMProtocols(dpy, win, &wmatom[WMDelete], 1);
   1004 
   1005 	snprintf(winid, sizeof(winid), "%lu", win);
   1006 	setenv("XEMBED", winid, 1);
   1007 
   1008 	nextfocus = foreground;
   1009 	focus(-1);
   1010 }
   1011 
   1012 void
   1013 sigchld(int unused) {
   1014 	if(signal(SIGCHLD, sigchld) == SIG_ERR)
   1015 		die("tabbed: cannot install SIGCHLD handler");
   1016 
   1017 	while(0 < waitpid(-1, NULL, WNOHANG));
   1018 }
   1019 
   1020 void
   1021 spawn(const Arg *arg) {
   1022 	if(fork() == 0) {
   1023 		if(dpy)
   1024 			close(ConnectionNumber(dpy));
   1025 
   1026 		setsid();
   1027 		if(arg && arg->v) {
   1028 			execvp(((char **)arg->v)[0], (char **)arg->v);
   1029 			fprintf(stderr, "tabbed: execvp %s",
   1030 					((char **)arg->v)[0]);
   1031 		} else {
   1032 			cmd[cmd_append_pos] = NULL;
   1033 			execvp(cmd[0], cmd);
   1034 			fprintf(stderr, "tabbed: execvp %s", cmd[0]);
   1035 		}
   1036 		perror(" failed");
   1037 		exit(0);
   1038 	}
   1039 }
   1040 
   1041 int
   1042 textnw(const char *text, unsigned int len) {
   1043 	XRectangle r;
   1044 
   1045 	if(dc.font.set) {
   1046 		XmbTextExtents(dc.font.set, text, len, NULL, &r);
   1047 
   1048 		return r.width;
   1049 	}
   1050 
   1051 	return XTextWidth(dc.font.xfont, text, len);
   1052 }
   1053 
   1054 void
   1055 unmanage(int c) {
   1056 	if(c < 0 || c >= nclients) {
   1057 		drawbar();
   1058 		XSync(dpy, False);
   1059 		return;
   1060 	}
   1061 
   1062 	if(!nclients) {
   1063 		return;
   1064 	} else if(c == 0) {
   1065 		/* First client. */
   1066 		nclients--;
   1067 		free(clients[0]);
   1068 		memmove(&clients[0], &clients[1], sizeof(Client *) * nclients);
   1069 	} else if(c == nclients - 1) {
   1070 		/* Last client. */
   1071 		nclients--;
   1072 		free(clients[c]);
   1073 		clients = erealloc(clients, sizeof(Client *) * nclients);
   1074 	} else {
   1075 		/* Somewhere inbetween. */
   1076 		free(clients[c]);
   1077 		memmove(&clients[c], &clients[c+1],
   1078 				sizeof(Client *) * (nclients - (c + 1)));
   1079 		nclients--;
   1080 	}
   1081 
   1082 	if(nclients <= 0) {
   1083 		sel = -1;
   1084 		lastsel = -1;
   1085 
   1086 		if (closelastclient) {
   1087 			running = False;
   1088 		} else if (fillagain && running) {
   1089 			spawn(NULL);
   1090 		}
   1091 	} else {
   1092 		if(c == lastsel) {
   1093 			lastsel = -1;
   1094 		} else if(lastsel > c) {
   1095 			lastsel--;
   1096 		}
   1097 		lastsel = MIN(lastsel, nclients - 1);
   1098 
   1099 		if(c == sel) {
   1100 			/* Note that focus() will never set lastsel == sel,
   1101 			 * so if here lastsel == sel, it was decreased by above if() clause
   1102 			 * and was actually (sel + 1) before.
   1103 			 */
   1104 			if(lastsel > 0) {
   1105 				focus(lastsel);
   1106 			} else {
   1107 				focus(0);
   1108 				lastsel = 1;
   1109 			}
   1110 		} else {
   1111 			if(sel > c)
   1112 				sel -= 1;
   1113 			if(sel >= nclients)
   1114 				sel = nclients - 1;
   1115 
   1116 			focus(sel);
   1117 		}
   1118 	}
   1119 
   1120 	drawbar();
   1121 	XSync(dpy, False);
   1122 }
   1123 
   1124 void
   1125 updatenumlockmask(void) {
   1126 	unsigned int i, j;
   1127 	XModifierKeymap *modmap;
   1128 
   1129 	numlockmask = 0;
   1130 	modmap = XGetModifierMapping(dpy);
   1131 	for(i = 0; i < 8; i++) {
   1132 		for(j = 0; j < modmap->max_keypermod; j++) {
   1133 			if(modmap->modifiermap[i * modmap->max_keypermod + j]
   1134 					== XKeysymToKeycode(dpy,
   1135 						XK_Num_Lock)) {
   1136 				numlockmask = (1 << i);
   1137 			}
   1138 		}
   1139 	}
   1140 	XFreeModifiermap(modmap);
   1141 }
   1142 
   1143 void
   1144 updatetitle(int c) {
   1145 	if(!gettextprop(clients[c]->win, wmatom[WMName],
   1146 				clients[c]->name, sizeof(clients[c]->name))) {
   1147 		gettextprop(clients[c]->win, XA_WM_NAME,
   1148 				clients[c]->name, sizeof(clients[c]->name));
   1149 	}
   1150 	if(sel == c)
   1151 		xsettitle(win, clients[c]->name);
   1152 	drawbar();
   1153 }
   1154 
   1155 /* There's no way to check accesses to destroyed windows, thus those cases are
   1156  * ignored (especially on UnmapNotify's).  Other types of errors call Xlibs
   1157  * default error handler, which may call exit.  */
   1158 int
   1159 xerror(Display *dpy, XErrorEvent *ee) {
   1160 	if(ee->error_code == BadWindow
   1161 			|| (ee->request_code == X_SetInputFocus
   1162 				&& ee->error_code == BadMatch)
   1163 			|| (ee->request_code == X_PolyText8
   1164 				&& ee->error_code == BadDrawable)
   1165 			|| (ee->request_code == X_PolyFillRectangle
   1166 				&& ee->error_code == BadDrawable)
   1167 			|| (ee->request_code == X_PolySegment
   1168 				&& ee->error_code == BadDrawable)
   1169 			|| (ee->request_code == X_ConfigureWindow
   1170 				&& ee->error_code == BadMatch)
   1171 			|| (ee->request_code == X_GrabButton
   1172 				&& ee->error_code == BadAccess)
   1173 			|| (ee->request_code == X_GrabKey
   1174 				&& ee->error_code == BadAccess)
   1175 			|| (ee->request_code == X_CopyArea
   1176 				&& ee->error_code == BadDrawable)) {
   1177 		return 0;
   1178 	}
   1179 
   1180 	fprintf(stderr, "tabbed: fatal error: request code=%d, error code=%d\n",
   1181 			ee->request_code, ee->error_code);
   1182 	return xerrorxlib(dpy, ee); /* may call exit */
   1183 }
   1184 
   1185 void
   1186 xsettitle(Window w, const char *str) {
   1187 	XTextProperty xtp;
   1188 
   1189 	if(XmbTextListToTextProperty(dpy, (char **)&str, 1, XCompoundTextStyle,
   1190 				&xtp) == Success) {
   1191 		XSetTextProperty(dpy, w, &xtp, wmatom[WMName]);
   1192 		XSetTextProperty(dpy, w, &xtp, XA_WM_NAME);
   1193 		XFree(xtp.value);
   1194 	}
   1195 }
   1196 
   1197 char *argv0;
   1198 
   1199 void
   1200 usage(void) {
   1201 	die("usage: %s [-dfhsv] [-g geometry] [-n name] [-p [s+/-]pos] [-r narg] "
   1202 		"[-u color] [-U color] [-t color] [-T color] command...\n", argv0);
   1203 }
   1204 
   1205 int
   1206 main(int argc, char *argv[]) {
   1207 	Bool detach = False;
   1208 	int replace = 0;
   1209 	char *pstr;
   1210 
   1211 	ARGBEGIN {
   1212 	case 'c':
   1213 		closelastclient = True;
   1214 		fillagain = False;
   1215 		break;
   1216 	case 'd':
   1217 		detach = True;
   1218 		break;
   1219 	case 'f':
   1220 		fillagain = True;
   1221 		break;
   1222 	case 'g':
   1223 		geometry = EARGF(usage());
   1224 		break;
   1225 	case 'n':
   1226 		wmname = EARGF(usage());
   1227 		break;
   1228 	case 'p':
   1229 		pstr = EARGF(usage());
   1230 		if(pstr[0] == 's') {
   1231 			npisrelative = True;
   1232 			newposition = atoi(&pstr[1]);
   1233 		} else {
   1234 			newposition = atoi(pstr);
   1235 		}
   1236 		break;
   1237 	case 'r':
   1238 		replace = atoi(EARGF(usage()));
   1239 		break;
   1240 	case 's':
   1241 		doinitspawn = False;
   1242 		break;
   1243 	case 'v':
   1244 		die("tabbed-"VERSION", © 2009-2012"
   1245 			" tabbed engineers, see LICENSE"
   1246 			" for details.\n");
   1247 		break;
   1248 	case 't':
   1249 		selbgcolor = EARGF(usage());
   1250 		break;
   1251 	case 'T':
   1252 		selfgcolor = EARGF(usage());
   1253 		break;
   1254 	case 'u':
   1255 		normbgcolor = EARGF(usage());
   1256 		break;
   1257 	case 'U':
   1258 		normfgcolor = EARGF(usage());
   1259 		break;
   1260 	default:
   1261 	case 'h':
   1262 		usage();
   1263 	} ARGEND;
   1264 
   1265 	if(argc < 1) {
   1266 		doinitspawn = False;
   1267 		fillagain = False;
   1268 	}
   1269 
   1270 	setcmd(argc, argv, replace);
   1271 
   1272 	if(!setlocale(LC_CTYPE, "") || !XSupportsLocale())
   1273 		fprintf(stderr, "tabbed: no locale support\n");
   1274 	if(!(dpy = XOpenDisplay(NULL)))
   1275 		die("tabbed: cannot open display\n");
   1276 
   1277 	setup();
   1278 	printf("0x%lx\n", win);
   1279 	fflush(NULL);
   1280 
   1281 	if(detach) {
   1282 		if(fork() == 0) {
   1283 			fclose(stdout);
   1284 		} else {
   1285 			if(dpy)
   1286 				close(ConnectionNumber(dpy));
   1287 			return EXIT_SUCCESS;
   1288 		}
   1289 	}
   1290 
   1291 	run();
   1292 	cleanup();
   1293 	XCloseDisplay(dpy);
   1294 
   1295 	return EXIT_SUCCESS;
   1296 }
   1297