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