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