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