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