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