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