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