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