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}