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