tabbed-noxz

[fork] suckless tabbed - personal fork
git clone https://noxz.tech/git/tabbed-noxz.git
Log | Files | README | LICENSE

tabbed.c
1/*
2 * See LICENSE file for copyright and license details.
3 */
4
5#include <sys/wait.h>
6#include <locale.h>
7#include <signal.h>
8#include <stdarg.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12#include <unistd.h>
13#include <linux/limits.h>
14#include <X11/Xatom.h>
15#include <X11/keysym.h>
16#include <X11/Xlib.h>
17#include <X11/Xproto.h>
18#include <X11/Xresource.h>
19#include <X11/Xutil.h>
20#include <X11/XKBlib.h>
21#include <X11/Xft/Xft.h>
22
23#include "arg.h"
24
25/* XEMBED messages */
26#define XEMBED_EMBEDDED_NOTIFY          0
27#define XEMBED_WINDOW_ACTIVATE          1
28#define XEMBED_WINDOW_DEACTIVATE        2
29#define XEMBED_REQUEST_FOCUS            3
30#define XEMBED_FOCUS_IN                 4
31#define XEMBED_FOCUS_OUT                5
32#define XEMBED_FOCUS_NEXT               6
33#define XEMBED_FOCUS_PREV               7
34/* 8-9 were used for XEMBED_GRAB_KEY/XEMBED_UNGRAB_KEY */
35#define XEMBED_MODALITY_ON              10
36#define XEMBED_MODALITY_OFF             11
37#define XEMBED_REGISTER_ACCELERATOR     12
38#define XEMBED_UNREGISTER_ACCELERATOR   13
39#define XEMBED_ACTIVATE_ACCELERATOR     14
40
41/* Details for  XEMBED_FOCUS_IN: */
42#define XEMBED_FOCUS_CURRENT            0
43#define XEMBED_FOCUS_FIRST              1
44#define XEMBED_FOCUS_LAST               2
45
46/* Macros */
47#define MAX(a, b)               ((a) > (b) ? (a) : (b))
48#define MIN(a, b)               ((a) < (b) ? (a) : (b))
49#define LENGTH(x)               (sizeof((x)) / sizeof(*(x)))
50#define CLEANMASK(mask)         (mask & ~(numlockmask | LockMask))
51#define TEXTW(x)                (textnw(x, strlen(x)) + dc.font.height)
52
53enum { ColFG, ColBG, ColLast };       /* color */
54enum { WMProtocols, WMDelete, WMName, WMState, WMFullscreen,
55       XEmbed, WMSelectTab, WMLast }; /* default atoms */
56
57typedef union {
58	int i;
59	const void *v;
60} Arg;
61
62typedef struct {
63	unsigned int mod;
64	KeySym keysym;
65	void (*func)(const Arg *);
66	const Arg arg;
67} Key;
68
69typedef struct {
70	int x, y, w, h;
71	Bool visible;
72	XftColor pid[ColLast];
73	XftColor norm[ColLast];
74	XftColor sel[ColLast];
75	XftColor urg[ColLast];
76	Drawable drawable;
77	GC gc;
78	struct {
79		int ascent;
80		int descent;
81		int height;
82		XftFont *xfont;
83	} font;
84} DC; /* draw context */
85
86typedef struct {
87	char name[256];
88	Window win;
89	int tabx;
90	Bool urgent;
91	Bool closed;
92	pid_t pid;
93} Client;
94 
95/* Xresources preferences */
96enum resource_type {
97	STRING = 0,
98	INTEGER = 1,
99	FLOAT = 2
100};
101
102typedef struct {
103	char *name;
104	enum resource_type type;
105	void *dst;
106} ResourcePref;
107 
108
109/* function declarations */
110static void buttonpress(const XEvent *e);
111static void cleanup(void);
112static void clientmessage(const XEvent *e);
113static void config_init(void);
114static void configurenotify(const XEvent *e);
115static void configurerequest(const XEvent *e);
116static void createnotify(const XEvent *e);
117static void destroynotify(const XEvent *e);
118static void die(const char *errstr, ...);
119static void drawbar(void);
120static void drawtext(const char *text, XftColor col[ColLast]);
121static void *ecalloc(size_t n, size_t size);
122static void *erealloc(void *o, size_t size);
123static void expose(const XEvent *e);
124static void focus(int c);
125static void focusin(const XEvent *e);
126static void focusonce(const Arg *arg);
127static void focusurgent(const Arg *arg);
128static void fullscreen(const Arg *arg);
129static char *getatom(int a);
130static int getclient(Window w);
131static XftColor getcolor(const char *colstr);
132static int getfirsttab(void);
133static Bool gettextprop(Window w, Atom atom, char *text, unsigned int size);
134static void initfont(const char *fontstr);
135static Bool isprotodel(int c);
136static void keypress(const XEvent *e);
137static void killclient(const Arg *arg);
138static void manage(Window win);
139static void maprequest(const XEvent *e);
140static void motionnotify(const XEvent *e);
141static void move(const Arg *arg);
142static void movetab(const Arg *arg);
143static void propertynotify(const XEvent *e);
144static void resize(int c, int w, int h);
145static int resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst);
146static void rotate(const Arg *arg);
147static void run(void);
148static void sendxembed(int c, long msg, long detail, long d1, long d2);
149static void setcmd(int argc, char *argv[], int);
150static void setup(void);
151static void spawn(const Arg *arg);
152static int textnw(const char *text, unsigned int len);
153static void toggle(const Arg *arg);
154static void togglebar(const Arg *arg);
155static void unmanage(int c);
156static void unmapnotify(const XEvent *e);
157static void updatenumlockmask(void);
158static void updatetitle(int c);
159static int xerror(Display *dpy, XErrorEvent *ee);
160static void xsettitle(Window w, const char *str);
161
162/* variables */
163static int screen;
164static void (*handler[LASTEvent]) (const XEvent *) = {
165	[ButtonPress] = buttonpress,
166	[ClientMessage] = clientmessage,
167	[ConfigureNotify] = configurenotify,
168	[ConfigureRequest] = configurerequest,
169	[CreateNotify] = createnotify,
170	[DestroyNotify] = destroynotify,
171	[Expose] = expose,
172	[FocusIn] = focusin,
173	[KeyPress] = keypress,
174	[MapRequest] = maprequest,
175	[MotionNotify] = motionnotify,
176	[PropertyNotify] = propertynotify,
177	[UnmapNotify] = unmapnotify,
178};
179static int bh, obh, vbh, wx, wy, ww, wh;
180static unsigned int numlockmask;
181static Bool running = True, nextfocus, doinitspawn = True,
182            fillagain = False, closelastclient = False,
183            killclientsfirst = False;
184static Display *dpy;
185static DC dc;
186static Atom wmatom[WMLast];
187static Window root, win;
188static Client **clients;
189static int nclients, sel = -1, lastsel = -1;
190static int (*xerrorxlib)(Display *, XErrorEvent *);
191static int cmd_append_pos;
192static char winid[64];
193static char winpid[64];
194static char **cmd;
195static char *wmname = "tabbed";
196static pid_t nextpid;
197static const char *geometry;
198
199char *argv0;
200
201/* configuration, allows nested code to access above variables */
202#include "config.h"
203
204// Given a pid, return its cwd to buf
205int getpidcwd(pid_t pid, char* buf, size_t bufsiz) {
206	static const int proc_max = 20; // '/proc/4194304/cwd'
207	int sn_ret;
208	ssize_t rl_ret;
209	char path[proc_max];
210
211	sn_ret = snprintf(path, proc_max, "/proc/%d/cwd", pid);
212	if(sn_ret < 0 || sn_ret >= proc_max)
213		return -1;
214
215	rl_ret = readlink(path, buf, bufsiz);
216	if(rl_ret < 0 || rl_ret == bufsiz)
217		return -1;
218
219	buf[rl_ret] = 0;
220	return 0;
221}
222
223// Given a pid, return a reasonable guess at its child pid
224pid_t getchildpid(pid_t pid) {
225	// '/proc/4194304/task/4194304/children'
226	static const int proc_max = 40;
227	int sn_ret;
228	char path[proc_max];
229	FILE* f;
230
231	// guessing tid == pid
232	sn_ret = snprintf(path, proc_max, "/proc/%d/task/%d/children", pid, pid);
233	if (sn_ret < 0 || sn_ret >= proc_max)
234		return -1;
235
236	f = fopen(path, "r");
237	if (f == NULL)
238		return -1;
239
240	// guess first child
241	if (fscanf(f, "%d ", &pid) != 1)
242		return -1;
243
244	return pid;
245}
246
247void
248buttonpress(const XEvent *e)
249{
250	const XButtonPressedEvent *ev = &e->xbutton;
251	int i, fc;
252	Arg arg;
253
254	if (ev->y < 0 || ev->y > bh)
255		return;
256
257	if (ev->x < TEXTW(winpid) || ((fc = getfirsttab()) > 0 && ev->x < (TEXTW(before))) || ev->x < 0)
258		return;
259
260	for (i = fc; i < nclients; i++) {
261		if (clients[i]->tabx > ev->x) {
262			switch (ev->button) {
263			case Button1:
264				focus(i);
265				break;
266			case Button2:
267				focus(i);
268				killclient(NULL);
269				break;
270			case Button4: /* FALLTHROUGH */
271			case Button5:
272				arg.i = ev->button == Button4 ? -1 : 1;
273				rotate(&arg);
274				break;
275			}
276			break;
277		}
278	}
279}
280
281void
282cleanup(void)
283{
284	int i;
285
286	for (i = 0; i < nclients; i++) {
287		focus(i);
288		killclient(NULL);
289		XReparentWindow(dpy, clients[i]->win, root, 0, 0);
290		unmanage(i);
291	}
292	free(clients);
293	clients = NULL;
294
295	XFreePixmap(dpy, dc.drawable);
296	XFreeGC(dpy, dc.gc);
297	XDestroyWindow(dpy, win);
298	XSync(dpy, False);
299	free(cmd);
300}
301
302void
303clientmessage(const XEvent *e)
304{
305	const XClientMessageEvent *ev = &e->xclient;
306
307	if (ev->message_type == wmatom[WMProtocols] &&
308	    ev->data.l[0] == wmatom[WMDelete]) {
309		if (nclients > 1 && killclientsfirst) {
310			killclient(0);
311			return;
312		}
313		running = False;
314	}
315}
316
317void
318config_init(void)
319{
320	char *resm;
321	XrmDatabase db;
322	ResourcePref *p;
323
324	XrmInitialize();
325	resm = XResourceManagerString(dpy);
326	if (!resm)
327		return;
328
329	db = XrmGetStringDatabase(resm);
330	for (p = resources; p < resources + LENGTH(resources); p++)
331		resource_load(db, p->name, p->type, p->dst);
332}
333
334void
335configurenotify(const XEvent *e)
336{
337	const XConfigureEvent *ev = &e->xconfigure;
338
339	if (ev->window == win && (ev->width != ww || ev->height != wh)) {
340		ww = ev->width;
341		wh = ev->height;
342		XFreePixmap(dpy, dc.drawable);
343		dc.drawable = XCreatePixmap(dpy, root, ww, wh,
344		              DefaultDepth(dpy, screen));
345
346		if (!obh && (wh <= bh)) {
347			obh = bh;
348			bh = 0;
349		} else if (!bh && (wh > obh)) {
350			bh = obh;
351			obh = 0;
352		}
353
354		if (sel > -1)
355			resize(sel, ww, wh - bh);
356		XSync(dpy, False);
357	}
358}
359
360void
361configurerequest(const XEvent *e)
362{
363	const XConfigureRequestEvent *ev = &e->xconfigurerequest;
364	XWindowChanges wc;
365	int c;
366
367	if ((c = getclient(ev->window)) > -1) {
368		wc.x = 0;
369		wc.y = bh;
370		wc.width = ww;
371		wc.height = wh - bh;
372		wc.border_width = 0;
373		wc.sibling = ev->above;
374		wc.stack_mode = ev->detail;
375		XConfigureWindow(dpy, clients[c]->win, ev->value_mask, &wc);
376	}
377}
378
379void
380createnotify(const XEvent *e)
381{
382	const XCreateWindowEvent *ev = &e->xcreatewindow;
383
384	if (ev->window != win && getclient(ev->window) < 0)
385		manage(ev->window);
386}
387
388void
389destroynotify(const XEvent *e)
390{
391	const XDestroyWindowEvent *ev = &e->xdestroywindow;
392	int c;
393
394	if ((c = getclient(ev->window)) > -1)
395		unmanage(c);
396}
397
398void
399die(const char *errstr, ...)
400{
401	va_list ap;
402
403	va_start(ap, errstr);
404	vfprintf(stderr, errstr, ap);
405	va_end(ap);
406	exit(EXIT_FAILURE);
407}
408
409void
410drawbar(void)
411{
412	XftColor *col;
413	int c, cc, fc, width;
414	char *name = NULL;
415	char tabtitle[256];
416
417	if ((dc.visible ? vbh : 0) != bh) {
418		bh = dc.visible ? vbh : 0;
419		for (c = 0; c < nclients; c++)
420			XMoveResizeWindow(dpy, clients[c]->win, 0, bh, ww, wh - bh);
421	}
422
423	if (bh == 0)
424		return;
425
426	if (nclients == 0) {
427		dc.x = 0;
428		dc.w = ww;
429		XFetchName(dpy, win, &name);
430		drawtext(name ? name : "", dc.norm);
431		XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, ww, bh, 0, 0);
432		XSync(dpy, False);
433
434		return;
435	}
436
437	width = ww;
438	cc = ww / tabwidth;
439	if (nclients > cc)
440		cc = (ww - TEXTW(winpid) - TEXTW(before) - TEXTW(after)) / tabwidth;
441
442	if ((fc = getfirsttab()) + cc < nclients) {
443		dc.w = TEXTW(after);
444		dc.x = width - dc.w;
445		drawtext(after, dc.sel);
446		width -= dc.w;
447	}
448	dc.x = 0;
449
450	dc.w = TEXTW(winpid);
451	drawtext(winpid, dc.pid);
452	dc.x += dc.w;
453	width -= dc.w;
454
455	if (fc > 0) {
456		dc.w = TEXTW(before);
457		drawtext(before, dc.sel);
458		dc.x += dc.w;
459		width -= dc.w;
460	}
461
462	cc = MIN(cc, nclients);
463	for (c = fc; c < fc + cc; c++) {
464		dc.w = width / cc;
465		if (c == sel) {
466			col = dc.sel;
467			dc.w += width % cc;
468		} else {
469			col = clients[c]->urgent ? dc.urg : dc.norm;
470		}
471		snprintf(tabtitle, sizeof(tabtitle), "[%d] %s", c + 1, clients[c]->name);
472		drawtext(tabtitle, col);
473		dc.x += dc.w;
474		clients[c]->tabx = dc.x;
475	}
476	XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, ww, bh, 0, 0);
477	XSync(dpy, False);
478}
479
480void
481drawtext(const char *text, XftColor col[ColLast])
482{
483	int i, j, x, y, h, len, olen;
484	char buf[256];
485	XftDraw *d;
486	XRectangle r = { dc.x, dc.y, dc.w, dc.h };
487
488	XSetForeground(dpy, dc.gc, col[ColBG].pixel);
489	XFillRectangles(dpy, dc.drawable, dc.gc, &r, 1);
490	if (!text)
491		return;
492
493	olen = strlen(text);
494	h = dc.font.ascent + dc.font.descent;
495	y = dc.y + (dc.h / 2) - (h / 2) + dc.font.ascent;
496	x = dc.x + (h / 2);
497
498	/* shorten text if necessary */
499	for (len = MIN(olen, sizeof(buf));
500		len && textnw(text, len) > dc.w - h; len--);
501
502	if (!len)
503		return;
504
505	memcpy(buf, text, len);
506	if (len < olen)
507		for (i = len, j = strlen(titletrim); j && i; buf[--i] = titletrim[--j]);
508
509	d = XftDrawCreate(dpy, dc.drawable, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen));
510	XftDrawStringUtf8(d, &col[ColFG], dc.font.xfont, x, y, (XftChar8 *) buf, len);
511	XftDrawDestroy(d);
512}
513
514void *
515ecalloc(size_t n, size_t size)
516{
517	void *p;
518
519	if (!(p = calloc(n, size)))
520		die("%s: cannot calloc\n", argv0);
521	return p;
522}
523
524void *
525erealloc(void *o, size_t size)
526{
527	void *p;
528
529	if (!(p = realloc(o, size)))
530		die("%s: cannot realloc\n", argv0);
531	return p;
532}
533
534void
535expose(const XEvent *e)
536{
537	const XExposeEvent *ev = &e->xexpose;
538
539	if (ev->count == 0 && win == ev->window)
540		drawbar();
541}
542
543void
544focus(int c)
545{
546	char buf[BUFSIZ] = "tabbed-"VERSION" ::";
547	size_t i, n;
548	XWMHints* wmh;
549
550	/* If c, sel and clients are -1, raise tabbed-win itself */
551	if (nclients == 0) {
552		cmd[cmd_append_pos] = NULL;
553		for(i = 0, n = strlen(buf); cmd[i] && n < sizeof(buf); i++)
554			n += snprintf(&buf[n], sizeof(buf) - n, " %s", cmd[i]);
555
556		xsettitle(win, buf);
557		XRaiseWindow(dpy, win);
558
559		return;
560	}
561
562	if (c < 0 || c >= nclients)
563		return;
564
565	resize(c, ww, wh - bh);
566	XRaiseWindow(dpy, clients[c]->win);
567	XSetInputFocus(dpy, clients[c]->win, RevertToParent, CurrentTime);
568	sendxembed(c, XEMBED_FOCUS_IN, XEMBED_FOCUS_CURRENT, 0, 0);
569	sendxembed(c, XEMBED_WINDOW_ACTIVATE, 0, 0, 0);
570	xsettitle(win, clients[c]->name);
571
572	if (sel != c) {
573		lastsel = sel;
574		sel = c;
575	}
576
577	if (clients[c]->urgent && (wmh = XGetWMHints(dpy, clients[c]->win))) {
578		wmh->flags &= ~XUrgencyHint;
579		XSetWMHints(dpy, clients[c]->win, wmh);
580		clients[c]->urgent = False;
581		XFree(wmh);
582	}
583
584	drawbar();
585	XSync(dpy, False);
586}
587
588void
589focusin(const XEvent *e)
590{
591	const XFocusChangeEvent *ev = &e->xfocus;
592	int dummy;
593	Window focused;
594
595	if (ev->mode != NotifyUngrab) {
596		XGetInputFocus(dpy, &focused, &dummy);
597		if (focused == win)
598			focus(sel);
599	}
600}
601
602void
603focusonce(const Arg *arg)
604{
605	nextfocus = True;
606}
607
608void
609focusurgent(const Arg *arg)
610{
611	int c;
612
613	if (sel < 0)
614		return;
615
616	for (c = (sel + 1) % nclients; c != sel; c = (c + 1) % nclients) {
617		if (clients[c]->urgent) {
618			focus(c);
619			return;
620		}
621	}
622}
623
624void
625fullscreen(const Arg *arg)
626{
627	XEvent e;
628
629	e.type = ClientMessage;
630	e.xclient.window = win;
631	e.xclient.message_type = wmatom[WMState];
632	e.xclient.format = 32;
633	e.xclient.data.l[0] = 2;
634	e.xclient.data.l[1] = wmatom[WMFullscreen];
635	e.xclient.data.l[2] = 0;
636	XSendEvent(dpy, root, False, SubstructureNotifyMask, &e);
637}
638
639char *
640getatom(int a)
641{
642	static char buf[BUFSIZ];
643	Atom adummy;
644	int idummy;
645	unsigned long ldummy;
646	unsigned char *p = NULL;
647
648	XGetWindowProperty(dpy, win, wmatom[a], 0L, BUFSIZ, False, XA_STRING,
649	                   &adummy, &idummy, &ldummy, &ldummy, &p);
650	if (p)
651		strncpy(buf, (char *)p, LENGTH(buf)-1);
652	else
653		buf[0] = '\0';
654	XFree(p);
655
656	return buf;
657}
658
659int
660getclient(Window w)
661{
662	int i;
663
664	for (i = 0; i < nclients; i++) {
665		if (clients[i]->win == w)
666			return i;
667	}
668
669	return -1;
670}
671
672XftColor
673getcolor(const char *colstr)
674{
675	XftColor color;
676
677	if (!XftColorAllocName(dpy, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen), colstr, &color))
678		die("%s: cannot allocate color '%s'\n", argv0, colstr);
679
680	return color;
681}
682
683int
684getfirsttab(void)
685{
686	int cc, ret;
687
688	if (sel < 0)
689		return 0;
690
691	cc = ww / tabwidth;
692	if (nclients > cc)
693		cc = (ww - TEXTW(winpid) - TEXTW(before) - TEXTW(after)) / tabwidth;
694
695	ret = sel - cc / 2 + (cc + 1) % 2;
696	return ret < 0 ? 0 :
697	       ret + cc > nclients ? MAX(0, nclients - cc) :
698	       ret;
699}
700
701Bool
702gettextprop(Window w, Atom atom, char *text, unsigned int size)
703{
704	char **list = NULL;
705	int n;
706	XTextProperty name;
707
708	if (!text || size == 0)
709		return False;
710
711	text[0] = '\0';
712	XGetTextProperty(dpy, w, &name, atom);
713	if (!name.nitems)
714		return False;
715
716	if (name.encoding == XA_STRING) {
717		strncpy(text, (char *)name.value, size - 1);
718	} else if (XmbTextPropertyToTextList(dpy, &name, &list, &n) >= Success
719	           && n > 0 && *list) {
720		strncpy(text, *list, size - 1);
721		XFreeStringList(list);
722	}
723	text[size - 1] = '\0';
724	XFree(name.value);
725
726	return True;
727}
728
729void
730initfont(const char *fontstr)
731{
732	if (!(dc.font.xfont = XftFontOpenName(dpy, screen, fontstr))
733	    && !(dc.font.xfont = XftFontOpenName(dpy, screen, "fixed")))
734		die("error, cannot load font: '%s'\n", fontstr);
735
736	dc.font.ascent = dc.font.xfont->ascent;
737	dc.font.descent = dc.font.xfont->descent;
738	dc.font.height = dc.font.ascent + dc.font.descent;
739}
740
741Bool
742isprotodel(int c)
743{
744	int i, n;
745	Atom *protocols;
746	Bool ret = False;
747
748	if (XGetWMProtocols(dpy, clients[c]->win, &protocols, &n)) {
749		for (i = 0; !ret && i < n; i++) {
750			if (protocols[i] == wmatom[WMDelete])
751				ret = True;
752		}
753		XFree(protocols);
754	}
755
756	return ret;
757}
758
759void
760keypress(const XEvent *e)
761{
762	const XKeyEvent *ev = &e->xkey;
763	unsigned int i;
764	KeySym keysym;
765
766	keysym = XkbKeycodeToKeysym(dpy, (KeyCode)ev->keycode, 0, 0);
767	for (i = 0; i < LENGTH(keys); i++) {
768		if (keysym == keys[i].keysym &&
769		    CLEANMASK(keys[i].mod) == CLEANMASK(ev->state) &&
770		    keys[i].func)
771			keys[i].func(&(keys[i].arg));
772	}
773}
774
775void
776killclient(const Arg *arg)
777{
778	XEvent ev;
779
780	if (sel < 0)
781		return;
782
783	if (isprotodel(sel) && !clients[sel]->closed) {
784		ev.type = ClientMessage;
785		ev.xclient.window = clients[sel]->win;
786		ev.xclient.message_type = wmatom[WMProtocols];
787		ev.xclient.format = 32;
788		ev.xclient.data.l[0] = wmatom[WMDelete];
789		ev.xclient.data.l[1] = CurrentTime;
790		XSendEvent(dpy, clients[sel]->win, False, NoEventMask, &ev);
791		clients[sel]->closed = True;
792	} else {
793		XKillClient(dpy, clients[sel]->win);
794	}
795}
796
797void
798manage(Window w)
799{
800	updatenumlockmask();
801	{
802		int i, j, nextpos;
803		unsigned int modifiers[] = { 0, LockMask, numlockmask,
804		                             numlockmask | LockMask };
805		KeyCode code;
806		Client *c;
807		XEvent e;
808
809		XWithdrawWindow(dpy, w, 0);
810		XReparentWindow(dpy, w, win, 0, bh);
811		XSelectInput(dpy, w, PropertyChangeMask |
812		             StructureNotifyMask | EnterWindowMask);
813		XSync(dpy, False);
814
815		for (i = 0; i < LENGTH(keys); i++) {
816			if ((code = XKeysymToKeycode(dpy, keys[i].keysym))) {
817				for (j = 0; j < LENGTH(modifiers); j++) {
818					XGrabKey(dpy, code, keys[i].mod |
819					         modifiers[j], w, True,
820					         GrabModeAsync, GrabModeAsync);
821				}
822			}
823		}
824
825		c = ecalloc(1, sizeof *c);
826		c->win = w;
827		c->pid = nextpid;
828
829		nclients++;
830		clients = erealloc(clients, sizeof(Client *) * nclients);
831
832		if (npisrelative) {
833			nextpos = sel + newposition;
834		} else {
835			if (newposition < 0)
836				nextpos = nclients - newposition;
837			else
838				nextpos = newposition;
839		}
840		if (nextpos >= nclients)
841			nextpos = nclients - 1;
842		if (nextpos < 0)
843			nextpos = 0;
844
845		if (nclients > 1 && nextpos < nclients - 1)
846			memmove(&clients[nextpos + 1], &clients[nextpos],
847			        sizeof(Client *) * (nclients - nextpos - 1));
848
849		clients[nextpos] = c;
850		updatetitle(nextpos);
851
852		XLowerWindow(dpy, w);
853		XMapWindow(dpy, w);
854
855		e.xclient.window = w;
856		e.xclient.type = ClientMessage;
857		e.xclient.message_type = wmatom[XEmbed];
858		e.xclient.format = 32;
859		e.xclient.data.l[0] = CurrentTime;
860		e.xclient.data.l[1] = XEMBED_EMBEDDED_NOTIFY;
861		e.xclient.data.l[2] = 0;
862		e.xclient.data.l[3] = win;
863		e.xclient.data.l[4] = 0;
864		XSendEvent(dpy, root, False, NoEventMask, &e);
865
866		XSync(dpy, False);
867
868		/* Adjust sel before focus does set it to lastsel. */
869		if (sel >= nextpos)
870			sel++;
871		focus(nextfocus ? nextpos :
872		      sel < 0 ? 0 :
873		      sel);
874		nextfocus = focusnew;
875	}
876}
877
878void
879maprequest(const XEvent *e)
880{
881	const XMapRequestEvent *ev = &e->xmaprequest;
882
883	if (getclient(ev->window) < 0)
884		manage(ev->window);
885}
886
887void
888motionnotify(const XEvent *e)
889{
890	int i, fc;
891	const XMotionEvent *ev = &e->xmotion;
892	Arg arg;
893
894	if (sel < 0 || !(ev->state & Button1Mask) || ev->x < 0 ||
895	    ((fc = getfirsttab()) > 0 && ev->x < TEXTW(before)))
896		return;
897
898	for (i = fc; i < nclients; i++) {
899		if (clients[i]->tabx > ev->x) {
900			if (i == sel + (arg.i = 1) || i == sel + (arg.i = -1))
901				movetab(&arg);
902			break;
903		}
904	}
905}
906
907void
908move(const Arg *arg)
909{
910	int i;
911
912	if ((i = arg->i < nclients ? arg->i : nclients - 1) >= 0)
913		focus(i);
914}
915
916void
917movetab(const Arg *arg)
918{
919	int c;
920	Client *new;
921
922	if (sel < 0)
923		return;
924
925	c = (sel + arg->i) % nclients;
926	if (c < 0)
927		c += nclients;
928
929	if (c == sel)
930		return;
931
932	new = clients[sel];
933	if (sel < c)
934		memmove(&clients[sel], &clients[sel+1],
935		        sizeof(Client *) * (c - sel));
936	else
937		memmove(&clients[c+1], &clients[c],
938		        sizeof(Client *) * (sel - c));
939	clients[c] = new;
940	sel = c;
941
942	drawbar();
943}
944
945void
946propertynotify(const XEvent *e)
947{
948	const XPropertyEvent *ev = &e->xproperty;
949	XWMHints *wmh;
950	int c;
951	char* selection = NULL;
952	Arg arg;
953
954	if (ev->state == PropertyNewValue && ev->atom == wmatom[WMSelectTab]) {
955		selection = getatom(WMSelectTab);
956		if (!strncmp(selection, "0x", 2)) {
957			arg.i = getclient(strtoul(selection, NULL, 0));
958			move(&arg);
959		} else {
960			cmd[cmd_append_pos] = selection;
961			arg.v = cmd;
962			spawn(&arg);
963		}
964	} else if (ev->state == PropertyNewValue && ev->atom == XA_WM_HINTS &&
965	           (c = getclient(ev->window)) > -1 &&
966	           (wmh = XGetWMHints(dpy, clients[c]->win))) {
967		if (wmh->flags & XUrgencyHint) {
968			XFree(wmh);
969			wmh = XGetWMHints(dpy, win);
970			if (c != sel) {
971				if (urgentswitch && wmh &&
972				    !(wmh->flags & XUrgencyHint)) {
973					/* only switch, if tabbed was focused
974					 * since last urgency hint if WMHints
975					 * could not be received,
976					 * default to no switch */
977					focus(c);
978				} else {
979					/* if no switch should be performed,
980					 * mark tab as urgent */
981					clients[c]->urgent = True;
982					drawbar();
983				}
984			}
985			if (wmh && !(wmh->flags & XUrgencyHint)) {
986				/* update tabbed urgency hint
987				 * if not set already */
988				wmh->flags |= XUrgencyHint;
989				XSetWMHints(dpy, win, wmh);
990			}
991		}
992		XFree(wmh);
993	} else if (ev->state != PropertyDelete && ev->atom == XA_WM_NAME &&
994	           (c = getclient(ev->window)) > -1) {
995		updatetitle(c);
996	}
997}
998
999void
1000resize(int c, int w, int h)
1001{
1002	XConfigureEvent ce;
1003	XWindowChanges wc;
1004
1005	ce.x = 0;
1006	ce.y = wc.y = bh;
1007	ce.width = wc.width = w;
1008	ce.height = wc.height = h;
1009	ce.type = ConfigureNotify;
1010	ce.display = dpy;
1011	ce.event = clients[c]->win;
1012	ce.window = clients[c]->win;
1013	ce.above = None;
1014	ce.override_redirect = False;
1015	ce.border_width = 0;
1016
1017	XConfigureWindow(dpy, clients[c]->win, CWY | CWWidth | CWHeight, &wc);
1018	XSendEvent(dpy, clients[c]->win, False, StructureNotifyMask,
1019	           (XEvent *)&ce);
1020}
1021
1022int
1023resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst)
1024{
1025	char **sdst = dst;
1026	int *idst = dst;
1027	float *fdst = dst;
1028
1029	char fullname[256];
1030	char fullclass[256];
1031	char *type;
1032	XrmValue ret;
1033
1034	snprintf(fullname, sizeof(fullname), "%s.%s", "tabbed", name);
1035	snprintf(fullclass, sizeof(fullclass), "%s.%s", "tabbed", name);
1036	fullname[sizeof(fullname) - 1] = fullclass[sizeof(fullclass) - 1] = '\0';
1037
1038	XrmGetResource(db, fullname, fullclass, &type, &ret);
1039	if (ret.addr == NULL || strncmp("String", type, 64))
1040		return 1;
1041
1042	switch (rtype) {
1043	case STRING:
1044		*sdst = ret.addr;
1045		break;
1046	case INTEGER:
1047		*idst = strtoul(ret.addr, NULL, 10);
1048		break;
1049	case FLOAT:
1050		*fdst = strtof(ret.addr, NULL);
1051		break;
1052	}
1053	return 0;
1054}
1055
1056void
1057rotate(const Arg *arg)
1058{
1059	int nsel = -1;
1060
1061	if (sel < 0)
1062		return;
1063
1064	if (arg->i == 0) {
1065		if (lastsel > -1)
1066			focus(lastsel);
1067	} else if (sel > -1) {
1068		/* Rotating in an arg->i step around the clients. */
1069		nsel = sel + arg->i;
1070		while (nsel >= nclients)
1071			nsel -= nclients;
1072		while (nsel < 0)
1073			nsel += nclients;
1074		focus(nsel);
1075	}
1076}
1077
1078void
1079run(void)
1080{
1081	XEvent ev;
1082
1083	/* main event loop */
1084	XSync(dpy, False);
1085	drawbar();
1086	if (doinitspawn == True)
1087		spawn(NULL);
1088
1089	while (running) {
1090		XNextEvent(dpy, &ev);
1091		if (handler[ev.type])
1092			(handler[ev.type])(&ev); /* call handler */
1093	}
1094}
1095
1096void
1097sendxembed(int c, long msg, long detail, long d1, long d2)
1098{
1099	XEvent e = { 0 };
1100
1101	e.xclient.window = clients[c]->win;
1102	e.xclient.type = ClientMessage;
1103	e.xclient.message_type = wmatom[XEmbed];
1104	e.xclient.format = 32;
1105	e.xclient.data.l[0] = CurrentTime;
1106	e.xclient.data.l[1] = msg;
1107	e.xclient.data.l[2] = detail;
1108	e.xclient.data.l[3] = d1;
1109	e.xclient.data.l[4] = d2;
1110	XSendEvent(dpy, clients[c]->win, False, NoEventMask, &e);
1111}
1112
1113void
1114setcmd(int argc, char *argv[], int replace)
1115{
1116	int i;
1117
1118	cmd = ecalloc(argc + 3, sizeof(*cmd));
1119	if (argc == 0)
1120		return;
1121	for (i = 0; i < argc; i++)
1122		cmd[i] = argv[i];
1123	cmd[replace > 0 ? replace : argc] = winid;
1124	cmd_append_pos = argc + !replace;
1125	cmd[cmd_append_pos] = cmd[cmd_append_pos + 1] = NULL;
1126}
1127
1128void
1129setup(void)
1130{
1131	int bitm, tx, ty, tw, th, dh, dw, isfixed;
1132	XWMHints *wmh;
1133	XClassHint class_hint;
1134	XSizeHints *size_hint;
1135	struct sigaction sa;
1136	pid_t pid = getpid();
1137
1138	/* do not transform children into zombies when they terminate */
1139	sigemptyset(&sa.sa_mask);
1140	sa.sa_flags = SA_NOCLDSTOP | SA_NOCLDWAIT | SA_RESTART;
1141	sa.sa_handler = SIG_IGN;
1142	sigaction(SIGCHLD, &sa, NULL);
1143
1144	/* clean up any zombies that might have been inherited */
1145	while (waitpid(-1, NULL, WNOHANG) > 0);
1146
1147	/* init screen */
1148	screen = DefaultScreen(dpy);
1149	root = RootWindow(dpy, screen);
1150	initfont(font);
1151	vbh = dc.h = dc.font.height + 4;
1152	dc.visible = 1;
1153
1154	/* init atoms */
1155	wmatom[WMDelete] = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
1156	wmatom[WMFullscreen] = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN",
1157	                                   False);
1158	wmatom[WMName] = XInternAtom(dpy, "_NET_WM_NAME", False);
1159	wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False);
1160	wmatom[WMSelectTab] = XInternAtom(dpy, "_TABBED_SELECT_TAB", False);
1161	wmatom[WMState] = XInternAtom(dpy, "_NET_WM_STATE", False);
1162	wmatom[XEmbed] = XInternAtom(dpy, "_XEMBED", False);
1163
1164	/* init appearance */
1165	wx = 0;
1166	wy = 0;
1167	ww = 800;
1168	wh = 600;
1169	isfixed = 0;
1170
1171	if (geometry) {
1172		tx = ty = tw = th = 0;
1173		bitm = XParseGeometry(geometry, &tx, &ty, (unsigned *)&tw,
1174		                      (unsigned *)&th);
1175		if (bitm & XValue)
1176			wx = tx;
1177		if (bitm & YValue)
1178			wy = ty;
1179		if (bitm & WidthValue)
1180			ww = tw;
1181		if (bitm & HeightValue)
1182			wh = th;
1183		if (bitm & XNegative && wx == 0)
1184			wx = -1;
1185		if (bitm & YNegative && wy == 0)
1186			wy = -1;
1187		if (bitm & (HeightValue | WidthValue))
1188			isfixed = 1;
1189
1190		dw = DisplayWidth(dpy, screen);
1191		dh = DisplayHeight(dpy, screen);
1192		if (wx < 0)
1193			wx = dw + wx - ww - 1;
1194		if (wy < 0)
1195			wy = dh + wy - wh - 1;
1196	}
1197
1198	dc.pid[ColBG] = getcolor(pidbgcolor);
1199	dc.pid[ColFG] = getcolor(pidfgcolor);
1200	dc.norm[ColBG] = getcolor(normbgcolor);
1201	dc.norm[ColFG] = getcolor(normfgcolor);
1202	dc.sel[ColBG] = getcolor(selbgcolor);
1203	dc.sel[ColFG] = getcolor(selfgcolor);
1204	dc.urg[ColBG] = getcolor(urgbgcolor);
1205	dc.urg[ColFG] = getcolor(urgfgcolor);
1206	dc.drawable = XCreatePixmap(dpy, root, ww, wh,
1207	                            DefaultDepth(dpy, screen));
1208	dc.gc = XCreateGC(dpy, root, 0, 0);
1209
1210	win = XCreateSimpleWindow(dpy, root, wx, wy, ww, wh, 0,
1211	                          dc.norm[ColFG].pixel, dc.norm[ColBG].pixel);
1212	XMapRaised(dpy, win);
1213	XSelectInput(dpy, win, SubstructureNotifyMask | FocusChangeMask |
1214	             ButtonPressMask | ExposureMask | KeyPressMask |
1215	             PropertyChangeMask | StructureNotifyMask |
1216	             SubstructureRedirectMask | ButtonMotionMask);
1217	xerrorxlib = XSetErrorHandler(xerror);
1218
1219	class_hint.res_name = wmname;
1220	class_hint.res_class = "tabbed";
1221	XSetClassHint(dpy, win, &class_hint);
1222
1223	size_hint = XAllocSizeHints();
1224	size_hint->flags = PSize | PResizeInc | PBaseSize | PMinSize;
1225	size_hint->height = wh;
1226	size_hint->width = ww;
1227	size_hint->min_height = vbh + 1;
1228	size_hint->min_width = tabwidth;
1229
1230	if (isfixed) {
1231		size_hint->flags |= PMaxSize;
1232		size_hint->min_width = size_hint->max_width = ww;
1233		size_hint->min_height = size_hint->max_height = wh;
1234	}
1235	wmh = XAllocWMHints();
1236	XSetWMProperties(dpy, win, NULL, NULL, NULL, 0, size_hint, wmh, NULL);
1237	XFree(size_hint);
1238	XFree(wmh);
1239
1240	XSetWMProtocols(dpy, win, &wmatom[WMDelete], 1);
1241	XChangeProperty(dpy, win, XInternAtom(dpy, "_NET_WM_PID", False),
1242		XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1);
1243
1244	snprintf(winid, sizeof(winid), "%lu", win);
1245	snprintf(winpid, sizeof(winpid), "%X", pid);
1246	setenv("XEMBED", winid, 1);
1247
1248	nextfocus = focusnew;
1249	focus(-1);
1250}
1251
1252void
1253spawn(const Arg *arg)
1254{
1255	struct sigaction sa;
1256	char sel_cwd[PATH_MAX];
1257	pid_t pid = fork();
1258
1259	if (pid == 0) {
1260		if (dpy)
1261			close(ConnectionNumber(dpy));
1262
1263		setsid();
1264		if (sel >= 0 && clients[sel] && clients[sel]->pid > 0 &&
1265		    getpidcwd(getchildpid(clients[sel]->pid), sel_cwd, PATH_MAX) == 0) {
1266			chdir(sel_cwd);
1267		}
1268
1269		sigemptyset(&sa.sa_mask);
1270		sa.sa_flags = 0;
1271		sa.sa_handler = SIG_DFL;
1272		sigaction(SIGCHLD, &sa, NULL);
1273
1274		if (arg && arg->v) {
1275			execvp(((char **)arg->v)[0], (char **)arg->v);
1276			fprintf(stderr, "%s: execvp %s", argv0,
1277			        ((char **)arg->v)[0]);
1278		} else {
1279			cmd[cmd_append_pos] = NULL;
1280			execvp(cmd[0], cmd);
1281			fprintf(stderr, "%s: execvp %s", argv0, cmd[0]);
1282		}
1283		perror(" failed");
1284		exit(0);
1285	} else {
1286		nextpid = pid;
1287	}
1288}
1289
1290int
1291textnw(const char *text, unsigned int len)
1292{
1293	XGlyphInfo ext;
1294	XftTextExtentsUtf8(dpy, dc.font.xfont, (XftChar8 *) text, len, &ext);
1295	return ext.xOff;
1296}
1297
1298void
1299toggle(const Arg *arg)
1300{
1301    *(Bool*) arg->v = !*(Bool*) arg->v;
1302}
1303
1304void
1305togglebar(const Arg *arg)
1306{
1307	dc.visible = !dc.visible;
1308	drawbar();
1309}
1310
1311void
1312unmanage(int c)
1313{
1314	int i, j;
1315	unsigned int modifiers[] = { 0, LockMask, numlockmask,
1316	                             numlockmask | LockMask };
1317	KeyCode code;
1318
1319	if (c < 0 || c >= nclients) {
1320		drawbar();
1321		XSync(dpy, False);
1322		return;
1323	}
1324
1325	if (!nclients)
1326		return;
1327
1328	/* ungrab keys */
1329	for (i = 0; i < LENGTH(keys); i++) {
1330		if ((code = XKeysymToKeycode(dpy, keys[i].keysym))) {
1331			for (j = 0; j < LENGTH(modifiers); j++) {
1332				XUngrabKey(dpy, code, keys[i].mod | modifiers[j], clients[c]->win);
1333			}
1334		}
1335	}
1336
1337	if (c == 0) {
1338		/* First client. */
1339		nclients--;
1340		free(clients[0]);
1341		memmove(&clients[0], &clients[1], sizeof(Client *) * nclients);
1342	} else if (c == nclients - 1) {
1343		/* Last client. */
1344		nclients--;
1345		free(clients[c]);
1346		clients = erealloc(clients, sizeof(Client *) * nclients);
1347	} else {
1348		/* Somewhere inbetween. */
1349		free(clients[c]);
1350		memmove(&clients[c], &clients[c+1],
1351		        sizeof(Client *) * (nclients - (c + 1)));
1352		nclients--;
1353	}
1354
1355	if (nclients <= 0) {
1356		lastsel = sel = -1;
1357
1358		if (closelastclient)
1359			running = False;
1360		else if (fillagain && running)
1361			spawn(NULL);
1362	} else {
1363		if (lastsel >= nclients)
1364			lastsel = nclients - 1;
1365		else if (lastsel > c)
1366			lastsel--;
1367
1368		if (c == sel && lastsel >= 0) {
1369			focus(lastsel);
1370		} else {
1371			if (sel > c)
1372				sel--;
1373			if (sel >= nclients)
1374				sel = nclients - 1;
1375
1376			focus(sel);
1377		}
1378	}
1379
1380	drawbar();
1381	XSync(dpy, False);
1382}
1383
1384void
1385unmapnotify(const XEvent *e)
1386{
1387	const XUnmapEvent *ev = &e->xunmap;
1388	int c;
1389
1390	if ((c = getclient(ev->window)) > -1)
1391		unmanage(c);
1392}
1393
1394void
1395updatenumlockmask(void)
1396{
1397	unsigned int i, j;
1398	XModifierKeymap *modmap;
1399
1400	numlockmask = 0;
1401	modmap = XGetModifierMapping(dpy);
1402	for (i = 0; i < 8; i++) {
1403		for (j = 0; j < modmap->max_keypermod; j++) {
1404			if (modmap->modifiermap[i * modmap->max_keypermod + j]
1405			    == XKeysymToKeycode(dpy, XK_Num_Lock))
1406				numlockmask = (1 << i);
1407		}
1408	}
1409	XFreeModifiermap(modmap);
1410}
1411
1412void
1413updatetitle(int c)
1414{
1415	if (!gettextprop(clients[c]->win, wmatom[WMName], clients[c]->name,
1416	    sizeof(clients[c]->name)))
1417		gettextprop(clients[c]->win, XA_WM_NAME, clients[c]->name,
1418		            sizeof(clients[c]->name));
1419	if (sel == c)
1420		xsettitle(win, clients[c]->name);
1421	drawbar();
1422}
1423
1424/* There's no way to check accesses to destroyed windows, thus those cases are
1425 * ignored (especially on UnmapNotify's).  Other types of errors call Xlibs
1426 * default error handler, which may call exit.  */
1427int
1428xerror(Display *dpy, XErrorEvent *ee)
1429{
1430	if (ee->error_code == BadWindow
1431	    || (ee->request_code == X_SetInputFocus &&
1432	        ee->error_code == BadMatch)
1433	    || (ee->request_code == X_PolyText8 &&
1434	        ee->error_code == BadDrawable)
1435	    || (ee->request_code == X_PolyFillRectangle &&
1436	        ee->error_code == BadDrawable)
1437	    || (ee->request_code == X_PolySegment &&
1438	        ee->error_code == BadDrawable)
1439	    || (ee->request_code == X_ConfigureWindow &&
1440	        ee->error_code == BadMatch)
1441	    || (ee->request_code == X_GrabButton &&
1442	        ee->error_code == BadAccess)
1443	    || (ee->request_code == X_GrabKey &&
1444	        ee->error_code == BadAccess)
1445	    || (ee->request_code == X_CopyArea &&
1446	        ee->error_code == BadDrawable))
1447		return 0;
1448
1449	fprintf(stderr, "%s: fatal error: request code=%d, error code=%d\n",
1450	        argv0, ee->request_code, ee->error_code);
1451	return xerrorxlib(dpy, ee); /* may call exit */
1452}
1453
1454void
1455xsettitle(Window w, const char *str)
1456{
1457	XTextProperty xtp;
1458
1459	if (XmbTextListToTextProperty(dpy, (char **)&str, 1,
1460	    XCompoundTextStyle, &xtp) == Success) {
1461		XSetTextProperty(dpy, w, &xtp, wmatom[WMName]);
1462		XSetTextProperty(dpy, w, &xtp, XA_WM_NAME);
1463		XFree(xtp.value);
1464	}
1465}
1466
1467void
1468usage(void)
1469{
1470	die("usage: %s [-dfksv] [-g geometry] [-n name] [-p [s+/-]pos]\n"
1471	    "       [-r narg] [-o color] [-O color] [-t color] [-T color]\n"
1472	    "       [-u color] [-U color] command...\n", argv0);
1473}
1474
1475int
1476main(int argc, char *argv[])
1477{
1478	Bool detach = False;
1479	int replace = 0;
1480	char *pstr;
1481
1482	ARGBEGIN {
1483	case 'c':
1484		closelastclient = True;
1485		fillagain = False;
1486		break;
1487	case 'd':
1488		detach = True;
1489		break;
1490	case 'f':
1491		fillagain = True;
1492		break;
1493	case 'g':
1494		geometry = EARGF(usage());
1495		break;
1496	case 'k':
1497		killclientsfirst = True;
1498		break;
1499	case 'n':
1500		wmname = EARGF(usage());
1501		break;
1502	case 'O':
1503		normfgcolor = EARGF(usage());
1504		break;
1505	case 'o':
1506		normbgcolor = EARGF(usage());
1507		break;
1508	case 'p':
1509		pstr = EARGF(usage());
1510		if (pstr[0] == 's') {
1511			npisrelative = True;
1512			newposition = atoi(&pstr[1]);
1513		} else {
1514			newposition = atoi(pstr);
1515		}
1516		break;
1517	case 'r':
1518		replace = atoi(EARGF(usage()));
1519		break;
1520	case 's':
1521		doinitspawn = False;
1522		break;
1523	case 'T':
1524		selfgcolor = EARGF(usage());
1525		break;
1526	case 't':
1527		selbgcolor = EARGF(usage());
1528		break;
1529	case 'U':
1530		urgfgcolor = EARGF(usage());
1531		break;
1532	case 'u':
1533		urgbgcolor = EARGF(usage());
1534		break;
1535	case 'v':
1536		die("tabbed-"VERSION", © 2009-2016 tabbed engineers, "
1537		    "see LICENSE for details.\n");
1538		break;
1539	default:
1540		usage();
1541		break;
1542	} ARGEND;
1543
1544	if (argc < 1) {
1545		doinitspawn = False;
1546		fillagain = False;
1547	}
1548
1549	setcmd(argc, argv, replace);
1550
1551	if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
1552		fprintf(stderr, "%s: no locale support\n", argv0);
1553	if (!(dpy = XOpenDisplay(NULL)))
1554		die("%s: cannot open display\n", argv0);
1555
1556	config_init();
1557	setup();
1558	printf("0x%lx\n", win);
1559	fflush(NULL);
1560
1561	if (detach) {
1562		if (fork() == 0) {
1563			fclose(stdout);
1564		} else {
1565			if (dpy)
1566				close(ConnectionNumber(dpy));
1567			return EXIT_SUCCESS;
1568		}
1569	}
1570
1571	run();
1572	cleanup();
1573	XCloseDisplay(dpy);
1574
1575	return EXIT_SUCCESS;
1576}