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