tabbed.c (31701B)
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 = nclients - 1; i >= 0; 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 XWMHints* win_wmh; 519 520 /* If c, sel and clients are -1, raise tabbed-win itself */ 521 if (nclients == 0) { 522 cmd[cmd_append_pos] = NULL; 523 for(i = 0, n = strlen(buf); cmd[i] && n < sizeof(buf); i++) 524 n += snprintf(&buf[n], sizeof(buf) - n, " %s", cmd[i]); 525 526 xsettitle(win, buf); 527 XRaiseWindow(dpy, win); 528 529 return; 530 } 531 532 if (c < 0 || c >= nclients) 533 return; 534 535 resize(c, ww, wh - bh); 536 XRaiseWindow(dpy, clients[c]->win); 537 XSetInputFocus(dpy, clients[c]->win, RevertToParent, CurrentTime); 538 sendxembed(c, XEMBED_FOCUS_IN, XEMBED_FOCUS_CURRENT, 0, 0); 539 sendxembed(c, XEMBED_WINDOW_ACTIVATE, 0, 0, 0); 540 xsettitle(win, clients[c]->name); 541 542 if (sel != c) { 543 lastsel = sel; 544 sel = c; 545 } 546 547 if (clients[c]->urgent && (wmh = XGetWMHints(dpy, clients[c]->win))) { 548 wmh->flags &= ~XUrgencyHint; 549 XSetWMHints(dpy, clients[c]->win, wmh); 550 clients[c]->urgent = False; 551 XFree(wmh); 552 553 /* 554 * gnome-shell will not stop notifying us about urgency, 555 * if we clear only the client hint and don't clear the 556 * hint from the main container window 557 */ 558 if ((win_wmh = XGetWMHints(dpy, win))) { 559 win_wmh->flags &= ~XUrgencyHint; 560 XSetWMHints(dpy, win, win_wmh); 561 XFree(win_wmh); 562 } 563 } 564 565 drawbar(); 566 XSync(dpy, False); 567 } 568 569 void 570 focusin(const XEvent *e) 571 { 572 const XFocusChangeEvent *ev = &e->xfocus; 573 int dummy; 574 Window focused; 575 576 if (ev->mode != NotifyUngrab) { 577 XGetInputFocus(dpy, &focused, &dummy); 578 if (focused == win) 579 focus(sel); 580 } 581 } 582 583 void 584 focusonce(const Arg *arg) 585 { 586 nextfocus = True; 587 } 588 589 void 590 focusurgent(const Arg *arg) 591 { 592 int c; 593 594 if (sel < 0) 595 return; 596 597 for (c = (sel + 1) % nclients; c != sel; c = (c + 1) % nclients) { 598 if (clients[c]->urgent) { 599 focus(c); 600 return; 601 } 602 } 603 } 604 605 void 606 fullscreen(const Arg *arg) 607 { 608 XEvent e; 609 610 e.type = ClientMessage; 611 e.xclient.window = win; 612 e.xclient.message_type = wmatom[WMState]; 613 e.xclient.format = 32; 614 e.xclient.data.l[0] = 2; 615 e.xclient.data.l[1] = wmatom[WMFullscreen]; 616 e.xclient.data.l[2] = 0; 617 XSendEvent(dpy, root, False, SubstructureNotifyMask, &e); 618 } 619 620 char * 621 getatom(int a) 622 { 623 static char buf[BUFSIZ]; 624 Atom adummy; 625 int idummy; 626 unsigned long ldummy; 627 unsigned char *p = NULL; 628 629 XGetWindowProperty(dpy, win, wmatom[a], 0L, BUFSIZ, False, XA_STRING, 630 &adummy, &idummy, &ldummy, &ldummy, &p); 631 if (p) 632 strncpy(buf, (char *)p, LENGTH(buf)-1); 633 else 634 buf[0] = '\0'; 635 XFree(p); 636 637 return buf; 638 } 639 640 int 641 getclient(Window w) 642 { 643 int i; 644 645 for (i = 0; i < nclients; i++) { 646 if (clients[i]->win == w) 647 return i; 648 } 649 650 return -1; 651 } 652 653 XftColor 654 getcolor(const char *colstr) 655 { 656 XftColor color; 657 658 if (!XftColorAllocName(dpy, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen), colstr, &color)) 659 die("%s: cannot allocate color '%s'\n", argv0, colstr); 660 661 return color; 662 } 663 664 int 665 getfirsttab(void) 666 { 667 int cc, ret; 668 669 if (sel < 0) 670 return 0; 671 672 if (stacked) { 673 cc = wh / 2 / vbh; 674 } else { 675 cc = ww / tabwidth; 676 if (nclients > cc) 677 cc = (ww - TEXTW(before) - TEXTW(after)) / tabwidth; 678 } 679 680 ret = sel - cc / 2 + (cc + 1) % 2; 681 return ret < 0 ? 0 : 682 ret + cc > nclients ? MAX(0, nclients - cc) : 683 ret; 684 } 685 686 Bool 687 gettextprop(Window w, Atom atom, char *text, unsigned int size) 688 { 689 char **list = NULL; 690 int n; 691 XTextProperty name; 692 693 if (!text || size == 0) 694 return False; 695 696 text[0] = '\0'; 697 XGetTextProperty(dpy, w, &name, atom); 698 if (!name.nitems) 699 return False; 700 701 if (name.encoding == XA_STRING) { 702 strncpy(text, (char *)name.value, size - 1); 703 } else if (XmbTextPropertyToTextList(dpy, &name, &list, &n) >= Success 704 && n > 0 && *list) { 705 strncpy(text, *list, size - 1); 706 XFreeStringList(list); 707 } 708 text[size - 1] = '\0'; 709 XFree(name.value); 710 711 return True; 712 } 713 714 void 715 initfont(const char *fontstr) 716 { 717 if (!(dc.font.xfont = XftFontOpenName(dpy, screen, fontstr)) 718 && !(dc.font.xfont = XftFontOpenName(dpy, screen, "fixed"))) 719 die("error, cannot load font: '%s'\n", fontstr); 720 721 dc.font.ascent = dc.font.xfont->ascent; 722 dc.font.descent = dc.font.xfont->descent; 723 dc.font.height = dc.font.ascent + dc.font.descent; 724 } 725 726 Bool 727 isprotodel(int c) 728 { 729 int i, n; 730 Atom *protocols; 731 Bool ret = False; 732 733 if (XGetWMProtocols(dpy, clients[c]->win, &protocols, &n)) { 734 for (i = 0; !ret && i < n; i++) { 735 if (protocols[i] == wmatom[WMDelete]) 736 ret = True; 737 } 738 XFree(protocols); 739 } 740 741 return ret; 742 } 743 744 void 745 keypress(const XEvent *e) 746 { 747 const XKeyEvent *ev = &e->xkey; 748 unsigned int i; 749 KeySym keysym; 750 751 keysym = XkbKeycodeToKeysym(dpy, (KeyCode)ev->keycode, 0, 0); 752 for (i = 0; i < LENGTH(keys); i++) { 753 if (keysym == keys[i].keysym && 754 CLEANMASK(keys[i].mod) == CLEANMASK(ev->state) && 755 keys[i].func) 756 keys[i].func(&(keys[i].arg)); 757 } 758 } 759 760 void 761 killclient(const Arg *arg) 762 { 763 XEvent ev; 764 765 if (sel < 0) 766 return; 767 768 if (isprotodel(sel) && !clients[sel]->closed) { 769 ev.type = ClientMessage; 770 ev.xclient.window = clients[sel]->win; 771 ev.xclient.message_type = wmatom[WMProtocols]; 772 ev.xclient.format = 32; 773 ev.xclient.data.l[0] = wmatom[WMDelete]; 774 ev.xclient.data.l[1] = CurrentTime; 775 XSendEvent(dpy, clients[sel]->win, False, NoEventMask, &ev); 776 clients[sel]->closed = True; 777 } else { 778 XKillClient(dpy, clients[sel]->win); 779 } 780 } 781 782 void 783 manage(Window w) 784 { 785 updatenumlockmask(); 786 { 787 int i, j, nextpos; 788 unsigned int modifiers[] = { 0, LockMask, numlockmask, 789 numlockmask | LockMask }; 790 KeyCode code; 791 Client *c; 792 XEvent e; 793 794 XWithdrawWindow(dpy, w, 0); 795 XReparentWindow(dpy, w, win, 0, bh); 796 XSelectInput(dpy, w, PropertyChangeMask | 797 StructureNotifyMask | EnterWindowMask); 798 XSync(dpy, False); 799 800 for (i = 0; i < LENGTH(keys); i++) { 801 if ((code = XKeysymToKeycode(dpy, keys[i].keysym))) { 802 for (j = 0; j < LENGTH(modifiers); j++) { 803 XGrabKey(dpy, code, keys[i].mod | 804 modifiers[j], w, True, 805 GrabModeAsync, GrabModeAsync); 806 } 807 } 808 } 809 810 c = ecalloc(1, sizeof *c); 811 c->win = w; 812 813 nclients++; 814 clients = erealloc(clients, sizeof(Client *) * nclients); 815 816 if(npisrelative) { 817 nextpos = sel + newposition; 818 } else { 819 if (newposition < 0) 820 nextpos = nclients - newposition; 821 else 822 nextpos = newposition; 823 } 824 if (nextpos >= nclients) 825 nextpos = nclients - 1; 826 if (nextpos < 0) 827 nextpos = 0; 828 829 if (nclients > 1 && nextpos < nclients - 1) 830 memmove(&clients[nextpos + 1], &clients[nextpos], 831 sizeof(Client *) * (nclients - nextpos - 1)); 832 833 clients[nextpos] = c; 834 updatetitle(nextpos); 835 836 XLowerWindow(dpy, w); 837 XMapWindow(dpy, w); 838 839 e.xclient.window = w; 840 e.xclient.type = ClientMessage; 841 e.xclient.message_type = wmatom[XEmbed]; 842 e.xclient.format = 32; 843 e.xclient.data.l[0] = CurrentTime; 844 e.xclient.data.l[1] = XEMBED_EMBEDDED_NOTIFY; 845 e.xclient.data.l[2] = 0; 846 e.xclient.data.l[3] = win; 847 e.xclient.data.l[4] = 0; 848 XSendEvent(dpy, root, False, NoEventMask, &e); 849 850 XSync(dpy, False); 851 852 /* Adjust sel before focus does set it to lastsel. */ 853 if (sel >= nextpos) 854 sel++; 855 focus(nextfocus ? nextpos : 856 sel < 0 ? 0 : 857 sel); 858 nextfocus = foreground; 859 } 860 } 861 862 void 863 maprequest(const XEvent *e) 864 { 865 const XMapRequestEvent *ev = &e->xmaprequest; 866 867 if (getclient(ev->window) < 0) 868 manage(ev->window); 869 } 870 871 void 872 move(const Arg *arg) 873 { 874 if (arg->i >= 0 && arg->i < nclients) 875 focus(arg->i); 876 } 877 878 void 879 movetab(const Arg *arg) 880 { 881 int c; 882 Client *new; 883 884 if (sel < 0) 885 return; 886 887 c = (sel + arg->i) % nclients; 888 if (c < 0) 889 c += nclients; 890 891 if (c == sel) 892 return; 893 894 new = clients[sel]; 895 if (sel < c) 896 memmove(&clients[sel], &clients[sel+1], 897 sizeof(Client *) * (c - sel)); 898 else 899 memmove(&clients[c+1], &clients[c], 900 sizeof(Client *) * (sel - c)); 901 clients[c] = new; 902 sel = c; 903 904 drawbar(); 905 } 906 907 void 908 propertynotify(const XEvent *e) 909 { 910 const XPropertyEvent *ev = &e->xproperty; 911 XWMHints *wmh; 912 int c; 913 char* selection = NULL; 914 Arg arg; 915 916 if (ev->state == PropertyNewValue && ev->atom == wmatom[WMSelectTab]) { 917 selection = getatom(WMSelectTab); 918 if (!strncmp(selection, "0x", 2)) { 919 arg.i = getclient(strtoul(selection, NULL, 0)); 920 move(&arg); 921 } else { 922 cmd[cmd_append_pos] = selection; 923 arg.v = cmd; 924 spawn(&arg); 925 } 926 } else if (ev->state == PropertyNewValue && ev->atom == XA_WM_HINTS && 927 (c = getclient(ev->window)) > -1 && 928 (wmh = XGetWMHints(dpy, clients[c]->win))) { 929 if (wmh->flags & XUrgencyHint) { 930 XFree(wmh); 931 wmh = XGetWMHints(dpy, win); 932 if (c != sel) { 933 if (urgentswitch && wmh && 934 !(wmh->flags & XUrgencyHint)) { 935 /* only switch, if tabbed was focused 936 * since last urgency hint if WMHints 937 * could not be received, 938 * default to no switch */ 939 focus(c); 940 } else { 941 /* if no switch should be performed, 942 * mark tab as urgent */ 943 clients[c]->urgent = True; 944 drawbar(); 945 } 946 } 947 if (wmh && !(wmh->flags & XUrgencyHint)) { 948 /* update tabbed urgency hint 949 * if not set already */ 950 wmh->flags |= XUrgencyHint; 951 XSetWMHints(dpy, win, wmh); 952 } 953 } 954 XFree(wmh); 955 } else if (ev->state != PropertyDelete && ev->atom == XA_WM_NAME && 956 (c = getclient(ev->window)) > -1) { 957 updatetitle(c); 958 } 959 } 960 961 void 962 resize(int c, int w, int h) 963 { 964 XConfigureEvent ce; 965 XWindowChanges wc; 966 967 ce.x = 0; 968 ce.y = wc.y = bh; 969 ce.width = wc.width = w; 970 ce.height = wc.height = h; 971 ce.type = ConfigureNotify; 972 ce.display = dpy; 973 ce.event = clients[c]->win; 974 ce.window = clients[c]->win; 975 ce.above = None; 976 ce.override_redirect = False; 977 ce.border_width = 0; 978 979 XConfigureWindow(dpy, clients[c]->win, CWY | CWWidth | CWHeight, &wc); 980 XSendEvent(dpy, clients[c]->win, False, StructureNotifyMask, 981 (XEvent *)&ce); 982 } 983 984 void 985 rotate(const Arg *arg) 986 { 987 int nsel = -1; 988 989 if (sel < 0) 990 return; 991 992 if (arg->i == 0) { 993 if (lastsel > -1) 994 focus(lastsel); 995 } else if (sel > -1) { 996 /* Rotating in an arg->i step around the clients. */ 997 nsel = sel + arg->i; 998 while (nsel >= nclients) 999 nsel -= nclients; 1000 while (nsel < 0) 1001 nsel += nclients; 1002 focus(nsel); 1003 } 1004 } 1005 1006 void 1007 run(void) 1008 { 1009 XEvent ev; 1010 1011 /* main event loop */ 1012 XSync(dpy, False); 1013 drawbar(); 1014 if (doinitspawn == True) 1015 spawn(NULL); 1016 1017 while (running) { 1018 XNextEvent(dpy, &ev); 1019 if (handler[ev.type]) 1020 (handler[ev.type])(&ev); /* call handler */ 1021 } 1022 } 1023 1024 void 1025 sendxembed(int c, long msg, long detail, long d1, long d2) 1026 { 1027 XEvent e = { 0 }; 1028 1029 e.xclient.window = clients[c]->win; 1030 e.xclient.type = ClientMessage; 1031 e.xclient.message_type = wmatom[XEmbed]; 1032 e.xclient.format = 32; 1033 e.xclient.data.l[0] = CurrentTime; 1034 e.xclient.data.l[1] = msg; 1035 e.xclient.data.l[2] = detail; 1036 e.xclient.data.l[3] = d1; 1037 e.xclient.data.l[4] = d2; 1038 XSendEvent(dpy, clients[c]->win, False, NoEventMask, &e); 1039 } 1040 1041 void 1042 setcmd(int argc, char *argv[], int replace) 1043 { 1044 int i; 1045 1046 cmd = ecalloc(argc + 3, sizeof(*cmd)); 1047 if (argc == 0) 1048 return; 1049 for (i = 0; i < argc; i++) 1050 cmd[i] = argv[i]; 1051 cmd[replace > 0 ? replace : argc] = winid; 1052 cmd_append_pos = argc + !replace; 1053 cmd[cmd_append_pos] = cmd[cmd_append_pos + 1] = NULL; 1054 } 1055 1056 void 1057 setup(void) 1058 { 1059 int bitm, tx, ty, tw, th, dh, dw, isfixed; 1060 XWMHints *wmh; 1061 XClassHint class_hint; 1062 XSizeHints *size_hint; 1063 struct sigaction sa; 1064 1065 /* do not transform children into zombies when they terminate */ 1066 sigemptyset(&sa.sa_mask); 1067 sa.sa_flags = SA_NOCLDSTOP | SA_NOCLDWAIT | SA_RESTART; 1068 sa.sa_handler = SIG_IGN; 1069 sigaction(SIGCHLD, &sa, NULL); 1070 1071 /* clean up any zombies that might have been inherited */ 1072 while (waitpid(-1, NULL, WNOHANG) > 0); 1073 1074 /* init screen */ 1075 screen = DefaultScreen(dpy); 1076 root = RootWindow(dpy, screen); 1077 initfont(font); 1078 vbh = dc.h = dc.font.height + 2; 1079 1080 /* init atoms */ 1081 wmatom[WMDelete] = XInternAtom(dpy, "WM_DELETE_WINDOW", False); 1082 wmatom[WMFullscreen] = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", 1083 False); 1084 wmatom[WMName] = XInternAtom(dpy, "_NET_WM_NAME", False); 1085 wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False); 1086 wmatom[WMSelectTab] = XInternAtom(dpy, "_TABBED_SELECT_TAB", False); 1087 wmatom[WMState] = XInternAtom(dpy, "_NET_WM_STATE", False); 1088 wmatom[XEmbed] = XInternAtom(dpy, "_XEMBED", False); 1089 1090 /* init appearance */ 1091 wx = 0; 1092 wy = 0; 1093 ww = 800; 1094 wh = 600; 1095 isfixed = 0; 1096 1097 if (geometry) { 1098 tx = ty = tw = th = 0; 1099 bitm = XParseGeometry(geometry, &tx, &ty, (unsigned *)&tw, 1100 (unsigned *)&th); 1101 if (bitm & XValue) 1102 wx = tx; 1103 if (bitm & YValue) 1104 wy = ty; 1105 if (bitm & WidthValue) 1106 ww = tw; 1107 if (bitm & HeightValue) 1108 wh = th; 1109 if (bitm & XNegative && wx == 0) 1110 wx = -1; 1111 if (bitm & YNegative && wy == 0) 1112 wy = -1; 1113 if (bitm & (HeightValue | WidthValue)) 1114 isfixed = 1; 1115 1116 dw = DisplayWidth(dpy, screen); 1117 dh = DisplayHeight(dpy, screen); 1118 if (wx < 0) 1119 wx = dw + wx - ww - 1; 1120 if (wy < 0) 1121 wy = dh + wy - wh - 1; 1122 } 1123 1124 dc.norm[ColBG] = getcolor(normbgcolor); 1125 dc.norm[ColFG] = getcolor(normfgcolor); 1126 dc.sel[ColBG] = getcolor(selbgcolor); 1127 dc.sel[ColFG] = getcolor(selfgcolor); 1128 dc.urg[ColBG] = getcolor(urgbgcolor); 1129 dc.urg[ColFG] = getcolor(urgfgcolor); 1130 dc.drawable = XCreatePixmap(dpy, root, ww, wh, 1131 DefaultDepth(dpy, screen)); 1132 dc.gc = XCreateGC(dpy, root, 0, 0); 1133 1134 win = XCreateSimpleWindow(dpy, root, wx, wy, ww, wh, 0, 1135 dc.norm[ColFG].pixel, dc.norm[ColBG].pixel); 1136 XMapRaised(dpy, win); 1137 XSelectInput(dpy, win, SubstructureNotifyMask | FocusChangeMask | 1138 ButtonPressMask | ExposureMask | KeyPressMask | 1139 PropertyChangeMask | StructureNotifyMask | 1140 SubstructureRedirectMask); 1141 xerrorxlib = XSetErrorHandler(xerror); 1142 1143 class_hint.res_name = wmname; 1144 class_hint.res_class = "tabbed"; 1145 XSetClassHint(dpy, win, &class_hint); 1146 1147 size_hint = XAllocSizeHints(); 1148 if (!isfixed) { 1149 size_hint->flags = PSize | PMinSize; 1150 size_hint->height = wh; 1151 size_hint->width = ww; 1152 size_hint->min_height = bh + 1; 1153 } else { 1154 size_hint->flags = PMaxSize | PMinSize; 1155 size_hint->min_width = size_hint->max_width = ww; 1156 size_hint->min_height = size_hint->max_height = wh; 1157 } 1158 wmh = XAllocWMHints(); 1159 XSetWMProperties(dpy, win, NULL, NULL, NULL, 0, size_hint, wmh, NULL); 1160 XFree(size_hint); 1161 XFree(wmh); 1162 1163 XSetWMProtocols(dpy, win, &wmatom[WMDelete], 1); 1164 1165 snprintf(winid, sizeof(winid), "%lu", win); 1166 setenv("XEMBED", winid, 1); 1167 1168 nextfocus = foreground; 1169 focus(-1); 1170 } 1171 1172 void 1173 spawn(const Arg *arg) 1174 { 1175 struct sigaction sa; 1176 1177 if (fork() == 0) { 1178 if(dpy) 1179 close(ConnectionNumber(dpy)); 1180 1181 setsid(); 1182 1183 sigemptyset(&sa.sa_mask); 1184 sa.sa_flags = 0; 1185 sa.sa_handler = SIG_DFL; 1186 sigaction(SIGCHLD, &sa, NULL); 1187 1188 if (arg && arg->v) { 1189 execvp(((char **)arg->v)[0], (char **)arg->v); 1190 fprintf(stderr, "%s: execvp %s", argv0, 1191 ((char **)arg->v)[0]); 1192 } else { 1193 cmd[cmd_append_pos] = NULL; 1194 execvp(cmd[0], cmd); 1195 fprintf(stderr, "%s: execvp %s", argv0, cmd[0]); 1196 } 1197 perror(" failed"); 1198 exit(0); 1199 } 1200 } 1201 1202 int 1203 textnw(const char *text, unsigned int len) 1204 { 1205 XGlyphInfo ext; 1206 XftTextExtentsUtf8(dpy, dc.font.xfont, (XftChar8 *) text, len, &ext); 1207 return ext.xOff; 1208 } 1209 1210 void 1211 toggle(const Arg *arg) 1212 { 1213 *(Bool*) arg->v = !*(Bool*) arg->v; 1214 } 1215 1216 void 1217 unmanage(int c) 1218 { 1219 if (c < 0 || c >= nclients) { 1220 drawbar(); 1221 XSync(dpy, False); 1222 return; 1223 } 1224 1225 if (!nclients) 1226 return; 1227 1228 if (c == 0) { 1229 /* First client. */ 1230 nclients--; 1231 free(clients[0]); 1232 memmove(&clients[0], &clients[1], sizeof(Client *) * nclients); 1233 } else if (c == nclients - 1) { 1234 /* Last client. */ 1235 nclients--; 1236 free(clients[c]); 1237 clients = erealloc(clients, sizeof(Client *) * nclients); 1238 } else { 1239 /* Somewhere inbetween. */ 1240 free(clients[c]); 1241 memmove(&clients[c], &clients[c+1], 1242 sizeof(Client *) * (nclients - (c + 1))); 1243 nclients--; 1244 } 1245 1246 if (nclients <= 0) { 1247 lastsel = sel = -1; 1248 1249 if (closelastclient) 1250 running = False; 1251 else if (fillagain && running) 1252 spawn(NULL); 1253 } else { 1254 if (lastsel >= nclients) 1255 lastsel = nclients - 1; 1256 else if (lastsel > c) 1257 lastsel--; 1258 1259 if (c == sel && lastsel >= 0) { 1260 focus(lastsel); 1261 } else { 1262 if (sel > c) 1263 sel--; 1264 if (sel >= nclients) 1265 sel = nclients - 1; 1266 1267 focus(sel); 1268 } 1269 } 1270 1271 drawbar(); 1272 XSync(dpy, False); 1273 } 1274 1275 void 1276 unmapnotify(const XEvent *e) 1277 { 1278 const XUnmapEvent *ev = &e->xunmap; 1279 int c; 1280 1281 if ((c = getclient(ev->window)) > -1) 1282 unmanage(c); 1283 } 1284 1285 void 1286 updatenumlockmask(void) 1287 { 1288 unsigned int i, j; 1289 XModifierKeymap *modmap; 1290 1291 numlockmask = 0; 1292 modmap = XGetModifierMapping(dpy); 1293 for (i = 0; i < 8; i++) { 1294 for (j = 0; j < modmap->max_keypermod; j++) { 1295 if (modmap->modifiermap[i * modmap->max_keypermod + j] 1296 == XKeysymToKeycode(dpy, XK_Num_Lock)) 1297 numlockmask = (1 << i); 1298 } 1299 } 1300 XFreeModifiermap(modmap); 1301 } 1302 1303 void 1304 updatetitle(int c) 1305 { 1306 if (!gettextprop(clients[c]->win, wmatom[WMName], clients[c]->name, 1307 sizeof(clients[c]->name))) 1308 gettextprop(clients[c]->win, XA_WM_NAME, clients[c]->name, 1309 sizeof(clients[c]->name)); 1310 if (sel == c) 1311 xsettitle(win, clients[c]->name); 1312 drawbar(); 1313 } 1314 1315 /* There's no way to check accesses to destroyed windows, thus those cases are 1316 * ignored (especially on UnmapNotify's). Other types of errors call Xlibs 1317 * default error handler, which may call exit. */ 1318 int 1319 xerror(Display *dpy, XErrorEvent *ee) 1320 { 1321 if (ee->error_code == BadWindow 1322 || (ee->request_code == X_SetInputFocus && 1323 ee->error_code == BadMatch) 1324 || (ee->request_code == X_PolyText8 && 1325 ee->error_code == BadDrawable) 1326 || (ee->request_code == X_PolyFillRectangle && 1327 ee->error_code == BadDrawable) 1328 || (ee->request_code == X_PolySegment && 1329 ee->error_code == BadDrawable) 1330 || (ee->request_code == X_ConfigureWindow && 1331 ee->error_code == BadMatch) 1332 || (ee->request_code == X_GrabButton && 1333 ee->error_code == BadAccess) 1334 || (ee->request_code == X_GrabKey && 1335 ee->error_code == BadAccess) 1336 || (ee->request_code == X_CopyArea && 1337 ee->error_code == BadDrawable)) 1338 return 0; 1339 1340 fprintf(stderr, "%s: fatal error: request code=%d, error code=%d\n", 1341 argv0, ee->request_code, ee->error_code); 1342 return xerrorxlib(dpy, ee); /* may call exit */ 1343 } 1344 1345 void 1346 xsettitle(Window w, const char *str) 1347 { 1348 XTextProperty xtp; 1349 1350 if (XmbTextListToTextProperty(dpy, (char **)&str, 1, 1351 XUTF8StringStyle, &xtp) == Success) { 1352 XSetTextProperty(dpy, w, &xtp, wmatom[WMName]); 1353 XSetTextProperty(dpy, w, &xtp, XA_WM_NAME); 1354 XFree(xtp.value); 1355 } 1356 } 1357 1358 void 1359 usage(void) 1360 { 1361 die("usage: %s [-adfksv] [-g geometry] [-n name] [-p [s+/-]pos]\n" 1362 " [-r narg] [-o color] [-O color] [-t color] [-T color]\n" 1363 " [-u color] [-U color] command...\n", argv0); 1364 } 1365 1366 int 1367 main(int argc, char *argv[]) 1368 { 1369 Bool detach = False; 1370 int replace = 0; 1371 char *pstr; 1372 1373 ARGBEGIN { 1374 case 'a': 1375 autoHide = True; 1376 break; 1377 case 'c': 1378 closelastclient = True; 1379 fillagain = False; 1380 break; 1381 case 'd': 1382 detach = True; 1383 break; 1384 case 'f': 1385 fillagain = True; 1386 break; 1387 case 'g': 1388 geometry = EARGF(usage()); 1389 break; 1390 case 'k': 1391 killclientsfirst = True; 1392 break; 1393 case 'n': 1394 wmname = EARGF(usage()); 1395 break; 1396 case 'O': 1397 normfgcolor = EARGF(usage()); 1398 break; 1399 case 'o': 1400 normbgcolor = EARGF(usage()); 1401 break; 1402 case 'p': 1403 pstr = EARGF(usage()); 1404 if (pstr[0] == 's') { 1405 npisrelative = True; 1406 newposition = atoi(&pstr[1]); 1407 } else { 1408 newposition = atoi(pstr); 1409 } 1410 break; 1411 case 'r': 1412 replace = atoi(EARGF(usage())); 1413 break; 1414 case 'S': 1415 stacked = True; 1416 break; 1417 case 's': 1418 doinitspawn = False; 1419 break; 1420 case 'T': 1421 selfgcolor = EARGF(usage()); 1422 break; 1423 case 't': 1424 selbgcolor = EARGF(usage()); 1425 break; 1426 case 'U': 1427 urgfgcolor = EARGF(usage()); 1428 break; 1429 case 'u': 1430 urgbgcolor = EARGF(usage()); 1431 break; 1432 case 'v': 1433 die("tabbed-"VERSION", © 2009-2016 tabbed engineers, " 1434 "see LICENSE for details.\n"); 1435 break; 1436 default: 1437 usage(); 1438 break; 1439 } ARGEND; 1440 1441 if (argc < 1) { 1442 doinitspawn = False; 1443 fillagain = False; 1444 } 1445 1446 setcmd(argc, argv, replace); 1447 1448 if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) 1449 fprintf(stderr, "%s: no locale support\n", argv0); 1450 if (!(dpy = XOpenDisplay(NULL))) 1451 die("%s: cannot open display\n", argv0); 1452 1453 setup(); 1454 printf("0x%lx\n", win); 1455 fflush(NULL); 1456 1457 if (detach) { 1458 if (fork() == 0) { 1459 fclose(stdout); 1460 } else { 1461 if (dpy) 1462 close(ConnectionNumber(dpy)); 1463 return EXIT_SUCCESS; 1464 } 1465 } 1466 1467 run(); 1468 cleanup(); 1469 XCloseDisplay(dpy); 1470 1471 return EXIT_SUCCESS; 1472 }