x.c (54531B)
1 /* See LICENSE for license details. */ 2 #include <errno.h> 3 #include <math.h> 4 #include <limits.h> 5 #include <locale.h> 6 #include <signal.h> 7 #include <sys/select.h> 8 #include <time.h> 9 #include <unistd.h> 10 #include <libgen.h> 11 #include <X11/Xatom.h> 12 #include <X11/Xlib.h> 13 #include <X11/cursorfont.h> 14 #include <X11/keysym.h> 15 #include <X11/Xft/Xft.h> 16 #include <X11/XKBlib.h> 17 #include <X11/Xresource.h> 18 19 char *argv0; 20 #include "arg.h" 21 #include "st.h" 22 #include "win.h" 23 #include "hb.h" 24 25 /* types used in config.h */ 26 typedef struct { 27 uint mod; 28 KeySym keysym; 29 void (*func)(const Arg *); 30 const Arg arg; 31 } Shortcut; 32 33 typedef struct { 34 uint mod; 35 uint button; 36 void (*func)(const Arg *); 37 const Arg arg; 38 uint release; 39 } MouseShortcut; 40 41 typedef struct { 42 KeySym k; 43 uint mask; 44 char *s; 45 /* three-valued logic variables: 0 indifferent, 1 on, -1 off */ 46 signed char appkey; /* application keypad */ 47 signed char appcursor; /* application cursor */ 48 } Key; 49 50 /* Xresources preferences */ 51 enum resource_type { 52 STRING = 0, 53 INTEGER = 1, 54 FLOAT = 2 55 }; 56 57 typedef struct { 58 char *name; 59 enum resource_type type; 60 void *dst; 61 } ResourcePref; 62 63 /* X modifiers */ 64 #define XK_ANY_MOD UINT_MAX 65 #define XK_NO_MOD 0 66 #define XK_SWITCH_MOD (1<<13|1<<14) 67 68 /* function definitions used in config.h */ 69 static void clipcopy(const Arg *); 70 static void clippaste(const Arg *); 71 static void numlock(const Arg *); 72 static void selpaste(const Arg *); 73 static void changealpha(const Arg *); 74 static void zoom(const Arg *); 75 static void zoomabs(const Arg *); 76 static void zoomreset(const Arg *); 77 static void ttysend(const Arg *); 78 79 /* config.h for applying patches and the configuration. */ 80 #include "config.h" 81 82 /* XEMBED messages */ 83 #define XEMBED_FOCUS_IN 4 84 #define XEMBED_FOCUS_OUT 5 85 86 /* macros */ 87 #define IS_SET(flag) ((win.mode & (flag)) != 0) 88 #define TRUERED(x) (((x) & 0xff0000) >> 8) 89 #define TRUEGREEN(x) (((x) & 0xff00)) 90 #define TRUEBLUE(x) (((x) & 0xff) << 8) 91 92 typedef XftDraw *Draw; 93 typedef XftColor Color; 94 typedef XftGlyphFontSpec GlyphFontSpec; 95 96 /* Purely graphic info */ 97 typedef struct { 98 int tw, th; /* tty width and height */ 99 int w, h; /* window width and height */ 100 int ch; /* char height */ 101 int cw; /* char width */ 102 int mode; /* window state/mode flags */ 103 int cursor; /* cursor style */ 104 } TermWindow; 105 106 typedef struct { 107 Display *dpy; 108 Colormap cmap; 109 Window win; 110 Drawable buf; 111 GlyphFontSpec *specbuf; /* font spec buffer used for rendering */ 112 Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid; 113 struct { 114 XIM xim; 115 XIC xic; 116 XPoint spot; 117 XVaNestedList spotlist; 118 } ime; 119 Draw draw; 120 Visual *vis; 121 XSetWindowAttributes attrs; 122 int scr; 123 int isfixed; /* is fixed geometry? */ 124 int depth; /* bit depth */ 125 int l, t; /* left and top offset */ 126 int gm; /* geometry mask */ 127 } XWindow; 128 129 typedef struct { 130 Atom xtarget; 131 char *primary, *clipboard; 132 struct timespec tclick1; 133 struct timespec tclick2; 134 } XSelection; 135 136 /* Font structure */ 137 #define Font Font_ 138 typedef struct { 139 int height; 140 int width; 141 int ascent; 142 int descent; 143 int badslant; 144 int badweight; 145 short lbearing; 146 short rbearing; 147 XftFont *match; 148 FcFontSet *set; 149 FcPattern *pattern; 150 } Font; 151 152 /* Drawing Context */ 153 typedef struct { 154 Color *col; 155 size_t collen; 156 Font font, bfont, ifont, ibfont; 157 GC gc; 158 } DC; 159 160 static inline ushort sixd_to_16bit(int); 161 static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); 162 static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); 163 static void xdrawglyph(Glyph, int, int); 164 static void xclear(int, int, int, int); 165 static int xgeommasktogravity(int); 166 static int ximopen(Display *); 167 static void ximinstantiate(Display *, XPointer, XPointer); 168 static void ximdestroy(XIM, XPointer, XPointer); 169 static int xicdestroy(XIC, XPointer, XPointer); 170 static void xinit(int, int); 171 static void cresize(int, int); 172 static void xresize(int, int); 173 static void xhints(void); 174 static int xloadcolor(int, const char *, Color *); 175 static int xloadfont(Font *, FcPattern *); 176 static int xloadsparefont(FcPattern *, int); 177 static void xloadsparefonts(void); 178 static void xloadfonts(const char *, double); 179 static void xunloadfont(Font *); 180 static void xunloadfonts(void); 181 static void xsetenv(void); 182 static void xseturgency(int); 183 static int evcol(XEvent *); 184 static int evrow(XEvent *); 185 static float clamp(float, float, float); 186 187 static void expose(XEvent *); 188 static void visibility(XEvent *); 189 static void unmap(XEvent *); 190 static void kpress(XEvent *); 191 static void cmessage(XEvent *); 192 static void resize(XEvent *); 193 static void focus(XEvent *); 194 static uint buttonmask(uint); 195 static int mouseaction(XEvent *, uint); 196 static void brelease(XEvent *); 197 static void bpress(XEvent *); 198 static void bmotion(XEvent *); 199 static void propnotify(XEvent *); 200 static void selnotify(XEvent *); 201 static void selclear_(XEvent *); 202 static void selrequest(XEvent *); 203 static void setsel(char *, Time); 204 static void mousesel(XEvent *, int); 205 static void mousereport(XEvent *); 206 static char *kmap(KeySym, uint); 207 static int match(uint, uint); 208 209 static void run(void); 210 static void usage(void); 211 212 static void (*handler[LASTEvent])(XEvent *) = { 213 [KeyPress] = kpress, 214 [ClientMessage] = cmessage, 215 [ConfigureNotify] = resize, 216 [VisibilityNotify] = visibility, 217 [UnmapNotify] = unmap, 218 [Expose] = expose, 219 [FocusIn] = focus, 220 [FocusOut] = focus, 221 [MotionNotify] = bmotion, 222 [ButtonPress] = bpress, 223 [ButtonRelease] = brelease, 224 /* 225 * Uncomment if you want the selection to disappear when you select something 226 * different in another window. 227 */ 228 /* [SelectionClear] = selclear_, */ 229 [SelectionNotify] = selnotify, 230 /* 231 * PropertyNotify is only turned on when there is some INCR transfer happening 232 * for the selection retrieval. 233 */ 234 [PropertyNotify] = propnotify, 235 [SelectionRequest] = selrequest, 236 }; 237 238 /* Globals */ 239 static DC dc; 240 static XWindow xw; 241 static XSelection xsel; 242 static TermWindow win; 243 244 /* Font Ring Cache */ 245 enum { 246 FRC_NORMAL, 247 FRC_ITALIC, 248 FRC_BOLD, 249 FRC_ITALICBOLD 250 }; 251 252 typedef struct { 253 XftFont *font; 254 int flags; 255 Rune unicodep; 256 } Fontcache; 257 258 /* Fontcache is an array now. A new font will be appended to the array. */ 259 static Fontcache *frc = NULL; 260 static int frclen = 0; 261 static int frccap = 0; 262 static char *usedfont = NULL; 263 static double usedfontsize = 0; 264 static double defaultfontsize = 0; 265 266 static char *opt_alpha = NULL; 267 static char *opt_class = NULL; 268 static char **opt_cmd = NULL; 269 static char *opt_embed = NULL; 270 static char *opt_font = NULL; 271 static char *opt_io = NULL; 272 static char *opt_line = NULL; 273 static char *opt_name = NULL; 274 static char *opt_title = NULL; 275 276 static int focused = 0; 277 278 static int oldbutton = 3; /* button event on startup: 3 = release */ 279 static uint buttons; /* bit field of pressed buttons */ 280 281 void 282 clipcopy(const Arg *dummy) 283 { 284 Atom clipboard; 285 286 free(xsel.clipboard); 287 xsel.clipboard = NULL; 288 289 if (xsel.primary != NULL) { 290 xsel.clipboard = xstrdup(xsel.primary); 291 clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); 292 XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); 293 } 294 } 295 296 void 297 clippaste(const Arg *dummy) 298 { 299 Atom clipboard; 300 301 clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); 302 XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard, 303 xw.win, CurrentTime); 304 } 305 306 void 307 selpaste(const Arg *dummy) 308 { 309 XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY, 310 xw.win, CurrentTime); 311 } 312 313 void 314 numlock(const Arg *dummy) 315 { 316 win.mode ^= MODE_NUMLOCK; 317 } 318 319 void 320 changealpha(const Arg *arg) 321 { 322 if((alpha > 0 && arg->f < 0) || (alpha < 1 && arg->f > 0)) 323 alpha += arg->f; 324 alpha = clamp(alpha, 0.0, 1.0); 325 alphaUnfocus = clamp(alpha-alphaOffset, 0.0, 1.0); 326 327 xloadcols(); 328 redraw(); 329 } 330 331 void 332 zoom(const Arg *arg) 333 { 334 Arg larg; 335 336 larg.f = usedfontsize + arg->f; 337 zoomabs(&larg); 338 } 339 340 void 341 zoomabs(const Arg *arg) 342 { 343 xunloadfonts(); 344 xloadfonts(usedfont, arg->f); 345 xloadsparefonts(); 346 cresize(0, 0); 347 redraw(); 348 xhints(); 349 } 350 351 void 352 zoomreset(const Arg *arg) 353 { 354 Arg larg; 355 356 if (defaultfontsize > 0) { 357 larg.f = defaultfontsize; 358 zoomabs(&larg); 359 } 360 } 361 362 void 363 ttysend(const Arg *arg) 364 { 365 ttywrite(arg->s, strlen(arg->s), 1); 366 } 367 368 int 369 evcol(XEvent *e) 370 { 371 int x = e->xbutton.x - borderpx; 372 LIMIT(x, 0, win.tw - 1); 373 return x / win.cw; 374 } 375 376 int 377 evrow(XEvent *e) 378 { 379 int y = e->xbutton.y - borderpx; 380 LIMIT(y, 0, win.th - 1); 381 return y / win.ch; 382 } 383 384 float 385 clamp(float value, float lower, float upper) { 386 if(value < lower) 387 return lower; 388 if(value > upper) 389 return upper; 390 return value; 391 } 392 393 void 394 mousesel(XEvent *e, int done) 395 { 396 int type, seltype = SEL_REGULAR; 397 uint state = e->xbutton.state & ~(Button1Mask | forcemousemod); 398 399 for (type = 1; type < LEN(selmasks); ++type) { 400 if (match(selmasks[type], state)) { 401 seltype = type; 402 break; 403 } 404 } 405 selextend(evcol(e), evrow(e), seltype, done); 406 if (done) 407 setsel(getsel(), e->xbutton.time); 408 } 409 410 void 411 mousereport(XEvent *e) 412 { 413 int len, x = evcol(e), y = evrow(e), 414 button = e->xbutton.button, state = e->xbutton.state; 415 char buf[40]; 416 static int ox, oy; 417 418 /* from urxvt */ 419 if (e->xbutton.type == MotionNotify) { 420 if (x == ox && y == oy) 421 return; 422 if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY)) 423 return; 424 /* MOUSE_MOTION: no reporting if no button is pressed */ 425 if (IS_SET(MODE_MOUSEMOTION) && oldbutton == 3) 426 return; 427 428 button = oldbutton + 32; 429 ox = x; 430 oy = y; 431 } else { 432 if (!IS_SET(MODE_MOUSESGR) && e->xbutton.type == ButtonRelease) { 433 button = 3; 434 } else { 435 button -= Button1; 436 if (button >= 3) 437 button += 64 - 3; 438 } 439 if (e->xbutton.type == ButtonPress) { 440 oldbutton = button; 441 ox = x; 442 oy = y; 443 } else if (e->xbutton.type == ButtonRelease) { 444 oldbutton = 3; 445 /* MODE_MOUSEX10: no button release reporting */ 446 if (IS_SET(MODE_MOUSEX10)) 447 return; 448 if (button == 64 || button == 65) 449 return; 450 } 451 } 452 453 if (!IS_SET(MODE_MOUSEX10)) { 454 button += ((state & ShiftMask ) ? 4 : 0) 455 + ((state & Mod4Mask ) ? 8 : 0) 456 + ((state & ControlMask) ? 16 : 0); 457 } 458 459 if (IS_SET(MODE_MOUSESGR)) { 460 len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", 461 button, x+1, y+1, 462 e->type == ButtonRelease ? 'm' : 'M'); 463 } else if (x < 223 && y < 223) { 464 len = snprintf(buf, sizeof(buf), "\033[M%c%c%c", 465 32+button, 32+x+1, 32+y+1); 466 } else { 467 return; 468 } 469 470 ttywrite(buf, len, 0); 471 } 472 473 uint 474 buttonmask(uint button) 475 { 476 return button == Button1 ? Button1Mask 477 : button == Button2 ? Button2Mask 478 : button == Button3 ? Button3Mask 479 : button == Button4 ? Button4Mask 480 : button == Button5 ? Button5Mask 481 : 0; 482 } 483 484 int 485 mouseaction(XEvent *e, uint release) 486 { 487 MouseShortcut *ms; 488 489 /* ignore Button<N>mask for Button<N> - it's set on release */ 490 uint state = e->xbutton.state & ~buttonmask(e->xbutton.button); 491 492 for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { 493 if (ms->release == release && 494 ms->button == e->xbutton.button && 495 (match(ms->mod, state) || /* exact or forced */ 496 match(ms->mod, state & ~forcemousemod))) { 497 ms->func(&(ms->arg)); 498 return 1; 499 } 500 } 501 502 return 0; 503 } 504 505 void 506 bpress(XEvent *e) 507 { 508 int btn = e->xbutton.button; 509 struct timespec now; 510 int snap; 511 512 if (1 <= btn && btn <= 11) 513 buttons |= 1 << (btn-1); 514 515 if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { 516 mousereport(e); 517 return; 518 } 519 520 if (mouseaction(e, 0)) 521 return; 522 523 if (btn == Button1) { 524 /* 525 * If the user clicks below predefined timeouts specific 526 * snapping behaviour is exposed. 527 */ 528 clock_gettime(CLOCK_MONOTONIC, &now); 529 if (TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout) { 530 snap = SNAP_LINE; 531 } else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) { 532 snap = SNAP_WORD; 533 } else { 534 snap = 0; 535 } 536 xsel.tclick2 = xsel.tclick1; 537 xsel.tclick1 = now; 538 539 selstart(evcol(e), evrow(e), snap); 540 } 541 } 542 543 void 544 propnotify(XEvent *e) 545 { 546 XPropertyEvent *xpev; 547 Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); 548 549 xpev = &e->xproperty; 550 if (xpev->state == PropertyNewValue && 551 (xpev->atom == XA_PRIMARY || 552 xpev->atom == clipboard)) { 553 selnotify(e); 554 } 555 } 556 557 void 558 selnotify(XEvent *e) 559 { 560 ulong nitems, ofs, rem; 561 int format; 562 uchar *data, *last, *repl; 563 Atom type, incratom, property = None; 564 565 incratom = XInternAtom(xw.dpy, "INCR", 0); 566 567 ofs = 0; 568 if (e->type == SelectionNotify) 569 property = e->xselection.property; 570 else if (e->type == PropertyNotify) 571 property = e->xproperty.atom; 572 573 if (property == None) 574 return; 575 576 do { 577 if (XGetWindowProperty(xw.dpy, xw.win, property, ofs, 578 BUFSIZ/4, False, AnyPropertyType, 579 &type, &format, &nitems, &rem, 580 &data)) { 581 fprintf(stderr, "Clipboard allocation failed\n"); 582 return; 583 } 584 585 if (e->type == PropertyNotify && nitems == 0 && rem == 0) { 586 /* 587 * If there is some PropertyNotify with no data, then 588 * this is the signal of the selection owner that all 589 * data has been transferred. We won't need to receive 590 * PropertyNotify events anymore. 591 */ 592 MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask); 593 XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, 594 &xw.attrs); 595 } 596 597 if (type == incratom) { 598 /* 599 * Activate the PropertyNotify events so we receive 600 * when the selection owner does send us the next 601 * chunk of data. 602 */ 603 MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask); 604 XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, 605 &xw.attrs); 606 607 /* 608 * Deleting the property is the transfer start signal. 609 */ 610 XDeleteProperty(xw.dpy, xw.win, (int)property); 611 continue; 612 } 613 614 /* 615 * As seen in getsel: 616 * Line endings are inconsistent in the terminal and GUI world 617 * copy and pasting. When receiving some selection data, 618 * replace all '\n' with '\r'. 619 * FIXME: Fix the computer world. 620 */ 621 repl = data; 622 last = data + nitems * format / 8; 623 while ((repl = memchr(repl, '\n', last - repl))) { 624 *repl++ = '\r'; 625 } 626 627 if (IS_SET(MODE_BRCKTPASTE) && ofs == 0) 628 ttywrite("\033[200~", 6, 0); 629 ttywrite((char *)data, nitems * format / 8, 1); 630 if (IS_SET(MODE_BRCKTPASTE) && rem == 0) 631 ttywrite("\033[201~", 6, 0); 632 XFree(data); 633 /* number of 32-bit chunks returned */ 634 ofs += nitems * format / 32; 635 } while (rem > 0); 636 637 /* 638 * Deleting the property again tells the selection owner to send the 639 * next data chunk in the property. 640 */ 641 XDeleteProperty(xw.dpy, xw.win, (int)property); 642 } 643 644 void 645 xclipcopy(void) 646 { 647 clipcopy(NULL); 648 } 649 650 void 651 selclear_(XEvent *e) 652 { 653 selclear(); 654 } 655 656 void 657 selrequest(XEvent *e) 658 { 659 XSelectionRequestEvent *xsre; 660 XSelectionEvent xev; 661 Atom xa_targets, string, clipboard; 662 char *seltext; 663 664 xsre = (XSelectionRequestEvent *) e; 665 xev.type = SelectionNotify; 666 xev.requestor = xsre->requestor; 667 xev.selection = xsre->selection; 668 xev.target = xsre->target; 669 xev.time = xsre->time; 670 if (xsre->property == None) 671 xsre->property = xsre->target; 672 673 /* reject */ 674 xev.property = None; 675 676 xa_targets = XInternAtom(xw.dpy, "TARGETS", 0); 677 if (xsre->target == xa_targets) { 678 /* respond with the supported type */ 679 string = xsel.xtarget; 680 XChangeProperty(xsre->display, xsre->requestor, xsre->property, 681 XA_ATOM, 32, PropModeReplace, 682 (uchar *) &string, 1); 683 xev.property = xsre->property; 684 } else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) { 685 /* 686 * xith XA_STRING non ascii characters may be incorrect in the 687 * requestor. It is not our problem, use utf8. 688 */ 689 clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); 690 if (xsre->selection == XA_PRIMARY) { 691 seltext = xsel.primary; 692 } else if (xsre->selection == clipboard) { 693 seltext = xsel.clipboard; 694 } else { 695 fprintf(stderr, 696 "Unhandled clipboard selection 0x%lx\n", 697 xsre->selection); 698 return; 699 } 700 if (seltext != NULL) { 701 XChangeProperty(xsre->display, xsre->requestor, 702 xsre->property, xsre->target, 703 8, PropModeReplace, 704 (uchar *)seltext, strlen(seltext)); 705 xev.property = xsre->property; 706 } 707 } 708 709 /* all done, send a notification to the listener */ 710 if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev)) 711 fprintf(stderr, "Error sending SelectionNotify event\n"); 712 } 713 714 void 715 setsel(char *str, Time t) 716 { 717 if (!str) 718 return; 719 720 free(xsel.primary); 721 xsel.primary = str; 722 723 XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t); 724 if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win) 725 selclear(); 726 } 727 728 void 729 xsetsel(char *str) 730 { 731 setsel(str, CurrentTime); 732 } 733 734 void 735 brelease(XEvent *e) 736 { 737 int btn = e->xbutton.button; 738 739 if (1 <= btn && btn <= 11) 740 buttons &= ~(1 << (btn-1)); 741 742 if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { 743 mousereport(e); 744 return; 745 } 746 747 if (mouseaction(e, 1)) 748 return; 749 if (btn == Button1) 750 mousesel(e, 1); 751 } 752 753 void 754 bmotion(XEvent *e) 755 { 756 if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { 757 mousereport(e); 758 return; 759 } 760 761 mousesel(e, 0); 762 } 763 764 void 765 cresize(int width, int height) 766 { 767 int col, row; 768 769 if (width != 0) 770 win.w = width; 771 if (height != 0) 772 win.h = height; 773 774 col = (win.w - 2 * borderpx) / win.cw; 775 row = (win.h - 2 * borderpx) / win.ch; 776 col = MAX(1, col); 777 row = MAX(1, row); 778 779 tresize(col, row); 780 xresize(col, row); 781 ttyresize(win.tw, win.th); 782 } 783 784 void 785 xresize(int col, int row) 786 { 787 win.tw = col * win.cw; 788 win.th = row * win.ch; 789 790 XFreePixmap(xw.dpy, xw.buf); 791 xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, 792 xw.depth); 793 XftDrawChange(xw.draw, xw.buf); 794 xclear(0, 0, win.w, win.h); 795 796 /* resize to new width */ 797 xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); 798 } 799 800 ushort 801 sixd_to_16bit(int x) 802 { 803 return x == 0 ? 0 : 0x3737 + 0x2828 * x; 804 } 805 806 int 807 xloadcolor(int i, const char *name, Color *ncolor) 808 { 809 XRenderColor color = { .alpha = 0xffff }; 810 811 if (!name) { 812 if (BETWEEN(i, 16, 255)) { /* 256 color */ 813 if (i < 6*6*6+16) { /* same colors as xterm */ 814 color.red = sixd_to_16bit( ((i-16)/36)%6 ); 815 color.green = sixd_to_16bit( ((i-16)/6) %6 ); 816 color.blue = sixd_to_16bit( ((i-16)/1) %6 ); 817 } else { /* greyscale */ 818 color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16)); 819 color.green = color.blue = color.red; 820 } 821 return XftColorAllocValue(xw.dpy, xw.vis, 822 xw.cmap, &color, ncolor); 823 } else 824 name = colorname[i]; 825 } 826 827 return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); 828 } 829 830 void 831 xloadalpha(void) 832 { 833 float const usedAlpha = focused ? alpha : alphaUnfocus; 834 if (opt_alpha) alpha = strtof(opt_alpha, NULL); 835 dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * usedAlpha); 836 dc.col[defaultbg].pixel &= 0x00FFFFFF; 837 dc.col[defaultbg].pixel |= (unsigned char)(0xff * usedAlpha) << 24; 838 } 839 840 void 841 xloadcols(void) 842 { 843 int i; 844 static int loaded; 845 Color *cp; 846 847 if (!loaded) { 848 dc.collen = 1 + (defaultbg = MAX(LEN(colorname), 256)); 849 dc.col = xmalloc(dc.collen * sizeof(Color)); 850 } 851 852 for (i = 0; i+1 < dc.collen; i++) 853 if (!xloadcolor(i, NULL, &dc.col[i])) { 854 if (colorname[i]) 855 die("could not allocate color '%s'\n", colorname[i]); 856 else 857 die("could not allocate color %d\n", i); 858 } 859 860 if (dc.collen) // cannot die, as the color is already loaded. 861 xloadcolor(background, NULL, &dc.col[defaultbg]); 862 863 xloadalpha(); 864 loaded = 1; 865 } 866 867 int 868 xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b) 869 { 870 if (!BETWEEN(x, 0, dc.collen)) 871 return 1; 872 873 *r = dc.col[x].color.red >> 8; 874 *g = dc.col[x].color.green >> 8; 875 *b = dc.col[x].color.blue >> 8; 876 877 return 0; 878 } 879 880 int 881 xsetcolorname(int x, const char *name) 882 { 883 Color ncolor; 884 885 if (!BETWEEN(x, 0, dc.collen)) 886 return 1; 887 888 if (!xloadcolor(x, name, &ncolor)) 889 return 1; 890 891 XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); 892 dc.col[x] = ncolor; 893 894 return 0; 895 } 896 897 /* 898 * Absolute coordinates. 899 */ 900 void 901 xclear(int x1, int y1, int x2, int y2) 902 { 903 XftDrawRect(xw.draw, 904 &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg], 905 x1, y1, x2-x1, y2-y1); 906 } 907 908 void 909 xhints(void) 910 { 911 XClassHint class = {opt_name ? opt_name : "st", 912 opt_class ? opt_class : "St"}; 913 XWMHints wm = {.flags = InputHint, .input = 1}; 914 XSizeHints *sizeh; 915 916 sizeh = XAllocSizeHints(); 917 918 sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize; 919 sizeh->height = win.h; 920 sizeh->width = win.w; 921 sizeh->height_inc = win.ch; 922 sizeh->width_inc = win.cw; 923 sizeh->base_height = 2 * borderpx; 924 sizeh->base_width = 2 * borderpx; 925 sizeh->min_height = win.ch + 2 * borderpx; 926 sizeh->min_width = win.cw + 2 * borderpx; 927 if (xw.isfixed) { 928 sizeh->flags |= PMaxSize; 929 sizeh->min_width = sizeh->max_width = win.w; 930 sizeh->min_height = sizeh->max_height = win.h; 931 } 932 if (xw.gm & (XValue|YValue)) { 933 sizeh->flags |= USPosition | PWinGravity; 934 sizeh->x = xw.l; 935 sizeh->y = xw.t; 936 sizeh->win_gravity = xgeommasktogravity(xw.gm); 937 } 938 939 XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, 940 &class); 941 XFree(sizeh); 942 } 943 944 int 945 xgeommasktogravity(int mask) 946 { 947 switch (mask & (XNegative|YNegative)) { 948 case 0: 949 return NorthWestGravity; 950 case XNegative: 951 return NorthEastGravity; 952 case YNegative: 953 return SouthWestGravity; 954 } 955 956 return SouthEastGravity; 957 } 958 959 int 960 xloadfont(Font *f, FcPattern *pattern) 961 { 962 FcPattern *configured; 963 FcPattern *match; 964 FcResult result; 965 XGlyphInfo extents; 966 int wantattr, haveattr; 967 968 /* 969 * Manually configure instead of calling XftMatchFont 970 * so that we can use the configured pattern for 971 * "missing glyph" lookups. 972 */ 973 configured = FcPatternDuplicate(pattern); 974 if (!configured) 975 return 1; 976 977 FcConfigSubstitute(NULL, configured, FcMatchPattern); 978 XftDefaultSubstitute(xw.dpy, xw.scr, configured); 979 980 match = FcFontMatch(NULL, configured, &result); 981 if (!match) { 982 FcPatternDestroy(configured); 983 return 1; 984 } 985 986 if (!(f->match = XftFontOpenPattern(xw.dpy, match))) { 987 FcPatternDestroy(configured); 988 FcPatternDestroy(match); 989 return 1; 990 } 991 992 if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) == 993 XftResultMatch)) { 994 /* 995 * Check if xft was unable to find a font with the appropriate 996 * slant but gave us one anyway. Try to mitigate. 997 */ 998 if ((XftPatternGetInteger(f->match->pattern, "slant", 0, 999 &haveattr) != XftResultMatch) || haveattr < wantattr) { 1000 f->badslant = 1; 1001 fputs("font slant does not match\n", stderr); 1002 } 1003 } 1004 1005 if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) == 1006 XftResultMatch)) { 1007 if ((XftPatternGetInteger(f->match->pattern, "weight", 0, 1008 &haveattr) != XftResultMatch) || haveattr != wantattr) { 1009 f->badweight = 1; 1010 fputs("font weight does not match\n", stderr); 1011 } 1012 } 1013 1014 XftTextExtentsUtf8(xw.dpy, f->match, 1015 (const FcChar8 *) ascii_printable, 1016 strlen(ascii_printable), &extents); 1017 1018 f->set = NULL; 1019 f->pattern = configured; 1020 1021 f->ascent = f->match->ascent; 1022 f->descent = f->match->descent; 1023 f->lbearing = 0; 1024 f->rbearing = f->match->max_advance_width; 1025 1026 f->height = f->ascent + f->descent; 1027 f->width = DIVCEIL(extents.xOff, strlen(ascii_printable)); 1028 1029 return 0; 1030 } 1031 1032 void 1033 xloadfonts(const char *fontstr, double fontsize) 1034 { 1035 FcPattern *pattern; 1036 double fontval; 1037 1038 if (fontstr[0] == '-') 1039 pattern = XftXlfdParse(fontstr, False, False); 1040 else 1041 pattern = FcNameParse((const FcChar8 *)fontstr); 1042 1043 if (!pattern) 1044 die("can't open font %s\n", fontstr); 1045 1046 if (fontsize > 1) { 1047 FcPatternDel(pattern, FC_PIXEL_SIZE); 1048 FcPatternDel(pattern, FC_SIZE); 1049 FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize); 1050 usedfontsize = fontsize; 1051 } else { 1052 if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == 1053 FcResultMatch) { 1054 usedfontsize = fontval; 1055 } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) == 1056 FcResultMatch) { 1057 usedfontsize = -1; 1058 } else { 1059 /* 1060 * Default font size is 12, if none given. This is to 1061 * have a known usedfontsize value. 1062 */ 1063 FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); 1064 usedfontsize = 12; 1065 } 1066 defaultfontsize = usedfontsize; 1067 } 1068 1069 if (xloadfont(&dc.font, pattern)) 1070 die("can't open font %s\n", fontstr); 1071 1072 if (usedfontsize < 0) { 1073 FcPatternGetDouble(dc.font.match->pattern, 1074 FC_PIXEL_SIZE, 0, &fontval); 1075 usedfontsize = fontval; 1076 if (fontsize == 0) 1077 defaultfontsize = fontval; 1078 } 1079 1080 /* Setting character width and height. */ 1081 win.cw = ceilf(dc.font.width * cwscale); 1082 win.ch = ceilf(dc.font.height * chscale); 1083 1084 FcPatternDel(pattern, FC_SLANT); 1085 FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); 1086 if (xloadfont(&dc.ifont, pattern)) 1087 die("can't open font %s\n", fontstr); 1088 1089 FcPatternDel(pattern, FC_WEIGHT); 1090 FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); 1091 if (xloadfont(&dc.ibfont, pattern)) 1092 die("can't open font %s\n", fontstr); 1093 1094 FcPatternDel(pattern, FC_SLANT); 1095 FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); 1096 if (xloadfont(&dc.bfont, pattern)) 1097 die("can't open font %s\n", fontstr); 1098 1099 FcPatternDestroy(pattern); 1100 } 1101 1102 int 1103 xloadsparefont(FcPattern *pattern, int flags) 1104 { 1105 FcPattern *match; 1106 FcResult result; 1107 1108 match = FcFontMatch(NULL, pattern, &result); 1109 if (!match) { 1110 return 1; 1111 } 1112 1113 if (!(frc[frclen].font = XftFontOpenPattern(xw.dpy, match))) { 1114 FcPatternDestroy(match); 1115 return 1; 1116 } 1117 1118 frc[frclen].flags = flags; 1119 /* Believe U+0000 glyph will present in each default font */ 1120 frc[frclen].unicodep = 0; 1121 frclen++; 1122 1123 return 0; 1124 } 1125 1126 void 1127 xloadsparefonts(void) 1128 { 1129 FcPattern *pattern; 1130 double sizeshift, fontval; 1131 int fc; 1132 char **fp; 1133 1134 if (frclen != 0) 1135 die("can't embed spare fonts. cache isn't empty"); 1136 1137 /* Calculate count of spare fonts */ 1138 fc = sizeof(font2) / sizeof(*font2); 1139 if (fc == 0) 1140 return; 1141 1142 /* Allocate memory for cache entries. */ 1143 if (frccap < 4 * fc) { 1144 frccap += 4 * fc - frccap; 1145 frc = xrealloc(frc, frccap * sizeof(Fontcache)); 1146 } 1147 1148 for (fp = font2; fp - font2 < fc; ++fp) { 1149 1150 if (**fp == '-') 1151 pattern = XftXlfdParse(*fp, False, False); 1152 else 1153 pattern = FcNameParse((FcChar8 *)*fp); 1154 1155 if (!pattern) 1156 die("can't open spare font %s\n", *fp); 1157 1158 if (defaultfontsize > 0) { 1159 sizeshift = usedfontsize - defaultfontsize; 1160 if (sizeshift != 0 && 1161 FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == 1162 FcResultMatch) { 1163 fontval += sizeshift; 1164 FcPatternDel(pattern, FC_PIXEL_SIZE); 1165 FcPatternDel(pattern, FC_SIZE); 1166 FcPatternAddDouble(pattern, FC_PIXEL_SIZE, fontval); 1167 } 1168 } 1169 1170 FcPatternAddBool(pattern, FC_SCALABLE, 1); 1171 1172 FcConfigSubstitute(NULL, pattern, FcMatchPattern); 1173 XftDefaultSubstitute(xw.dpy, xw.scr, pattern); 1174 1175 if (xloadsparefont(pattern, FRC_NORMAL)) 1176 die("can't open spare font %s\n", *fp); 1177 1178 FcPatternDel(pattern, FC_SLANT); 1179 FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); 1180 if (xloadsparefont(pattern, FRC_ITALIC)) 1181 die("can't open spare font %s\n", *fp); 1182 1183 FcPatternDel(pattern, FC_WEIGHT); 1184 FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); 1185 if (xloadsparefont(pattern, FRC_ITALICBOLD)) 1186 die("can't open spare font %s\n", *fp); 1187 1188 FcPatternDel(pattern, FC_SLANT); 1189 FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); 1190 if (xloadsparefont(pattern, FRC_BOLD)) 1191 die("can't open spare font %s\n", *fp); 1192 1193 FcPatternDestroy(pattern); 1194 } 1195 } 1196 1197 void 1198 xunloadfont(Font *f) 1199 { 1200 XftFontClose(xw.dpy, f->match); 1201 FcPatternDestroy(f->pattern); 1202 if (f->set) 1203 FcFontSetDestroy(f->set); 1204 } 1205 1206 void 1207 xunloadfonts(void) 1208 { 1209 /* Clear Harfbuzz font cache. */ 1210 hbunloadfonts(); 1211 1212 /* Free the loaded fonts in the font cache. */ 1213 while (frclen > 0) 1214 XftFontClose(xw.dpy, frc[--frclen].font); 1215 1216 xunloadfont(&dc.font); 1217 xunloadfont(&dc.bfont); 1218 xunloadfont(&dc.ifont); 1219 xunloadfont(&dc.ibfont); 1220 } 1221 1222 int 1223 ximopen(Display *dpy) 1224 { 1225 XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy }; 1226 XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy }; 1227 1228 xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL); 1229 if (xw.ime.xim == NULL) 1230 return 0; 1231 1232 if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL)) 1233 fprintf(stderr, "XSetIMValues: " 1234 "Could not set XNDestroyCallback.\n"); 1235 1236 xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot, 1237 NULL); 1238 1239 if (xw.ime.xic == NULL) { 1240 xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle, 1241 XIMPreeditNothing | XIMStatusNothing, 1242 XNClientWindow, xw.win, 1243 XNDestroyCallback, &icdestroy, 1244 NULL); 1245 } 1246 if (xw.ime.xic == NULL) 1247 fprintf(stderr, "XCreateIC: Could not create input context.\n"); 1248 1249 return 1; 1250 } 1251 1252 void 1253 ximinstantiate(Display *dpy, XPointer client, XPointer call) 1254 { 1255 if (ximopen(dpy)) 1256 XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, 1257 ximinstantiate, NULL); 1258 } 1259 1260 void 1261 ximdestroy(XIM xim, XPointer client, XPointer call) 1262 { 1263 xw.ime.xim = NULL; 1264 XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, 1265 ximinstantiate, NULL); 1266 XFree(xw.ime.spotlist); 1267 } 1268 1269 int 1270 xicdestroy(XIC xim, XPointer client, XPointer call) 1271 { 1272 xw.ime.xic = NULL; 1273 return 1; 1274 } 1275 1276 void 1277 xinit(int cols, int rows) 1278 { 1279 XGCValues gcvalues; 1280 Cursor cursor; 1281 Window parent; 1282 pid_t thispid = getpid(); 1283 XColor xmousefg, xmousebg; 1284 XWindowAttributes attr; 1285 XVisualInfo vis; 1286 1287 xw.scr = XDefaultScreen(xw.dpy); 1288 1289 if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) { 1290 parent = XRootWindow(xw.dpy, xw.scr); 1291 xw.depth = 32; 1292 } else { 1293 XGetWindowAttributes(xw.dpy, parent, &attr); 1294 xw.depth = attr.depth; 1295 } 1296 1297 XMatchVisualInfo(xw.dpy, xw.scr, xw.depth, TrueColor, &vis); 1298 xw.vis = vis.visual; 1299 1300 /* font */ 1301 if (!FcInit()) 1302 die("could not init fontconfig.\n"); 1303 1304 usedfont = (opt_font == NULL)? font : opt_font; 1305 xloadfonts(usedfont, 0); 1306 1307 /* spare fonts */ 1308 xloadsparefonts(); 1309 1310 /* colors */ 1311 xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None); 1312 xloadcols(); 1313 1314 /* adjust fixed window geometry */ 1315 win.w = 2 * borderpx + cols * win.cw; 1316 win.h = 2 * borderpx + rows * win.ch; 1317 if (xw.gm & XNegative) 1318 xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; 1319 if (xw.gm & YNegative) 1320 xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2; 1321 1322 /* Events */ 1323 xw.attrs.background_pixel = dc.col[defaultbg].pixel; 1324 xw.attrs.border_pixel = dc.col[defaultbg].pixel; 1325 xw.attrs.bit_gravity = NorthWestGravity; 1326 xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask 1327 | ExposureMask | VisibilityChangeMask | StructureNotifyMask 1328 | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; 1329 xw.attrs.colormap = xw.cmap; 1330 1331 xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, 1332 win.w, win.h, 0, xw.depth, InputOutput, 1333 xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity 1334 | CWEventMask | CWColormap, &xw.attrs); 1335 1336 memset(&gcvalues, 0, sizeof(gcvalues)); 1337 gcvalues.graphics_exposures = False; 1338 xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, xw.depth); 1339 dc.gc = XCreateGC(xw.dpy, xw.buf, GCGraphicsExposures, &gcvalues); 1340 XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); 1341 XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); 1342 1343 /* font spec buffer */ 1344 xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); 1345 1346 /* Xft rendering context */ 1347 xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); 1348 1349 /* input methods */ 1350 if (!ximopen(xw.dpy)) { 1351 XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, 1352 ximinstantiate, NULL); 1353 } 1354 1355 /* white cursor, black outline */ 1356 cursor = XCreateFontCursor(xw.dpy, mouseshape); 1357 XDefineCursor(xw.dpy, xw.win, cursor); 1358 1359 if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) { 1360 xmousefg.red = 0xffff; 1361 xmousefg.green = 0xffff; 1362 xmousefg.blue = 0xffff; 1363 } 1364 1365 if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) { 1366 xmousebg.red = 0x0000; 1367 xmousebg.green = 0x0000; 1368 xmousebg.blue = 0x0000; 1369 } 1370 1371 XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg); 1372 1373 xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False); 1374 xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); 1375 xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); 1376 xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False); 1377 XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); 1378 1379 xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False); 1380 XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32, 1381 PropModeReplace, (uchar *)&thispid, 1); 1382 1383 win.mode = MODE_NUMLOCK; 1384 resettitle(); 1385 xhints(); 1386 XMapWindow(xw.dpy, xw.win); 1387 XSync(xw.dpy, False); 1388 1389 clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1); 1390 clock_gettime(CLOCK_MONOTONIC, &xsel.tclick2); 1391 xsel.primary = NULL; 1392 xsel.clipboard = NULL; 1393 xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); 1394 if (xsel.xtarget == None) 1395 xsel.xtarget = XA_STRING; 1396 1397 boxdraw_xinit(xw.dpy, xw.cmap, xw.draw, xw.vis); 1398 } 1399 1400 int 1401 xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) 1402 { 1403 float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; 1404 ushort mode, prevmode = USHRT_MAX; 1405 Font *font = &dc.font; 1406 int frcflags = FRC_NORMAL; 1407 float runewidth = win.cw; 1408 Rune rune; 1409 FT_UInt glyphidx; 1410 FcResult fcres; 1411 FcPattern *fcpattern, *fontpattern; 1412 FcFontSet *fcsets[] = { NULL }; 1413 FcCharSet *fccharset; 1414 int i, f, numspecs = 0; 1415 1416 for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { 1417 /* Fetch rune and mode for current glyph. */ 1418 rune = glyphs[i].u; 1419 mode = glyphs[i].mode; 1420 1421 /* Skip dummy wide-character spacing. */ 1422 if (mode & ATTR_WDUMMY) 1423 continue; 1424 1425 /* Determine font for glyph if different from previous glyph. */ 1426 if (prevmode != mode) { 1427 prevmode = mode; 1428 font = &dc.font; 1429 frcflags = FRC_NORMAL; 1430 runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); 1431 if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { 1432 font = &dc.ibfont; 1433 frcflags = FRC_ITALICBOLD; 1434 } else if (mode & ATTR_ITALIC) { 1435 font = &dc.ifont; 1436 frcflags = FRC_ITALIC; 1437 } else if (mode & ATTR_BOLD) { 1438 font = &dc.bfont; 1439 frcflags = FRC_BOLD; 1440 } 1441 yp = winy + font->ascent; 1442 } 1443 1444 if (mode & ATTR_BOXDRAW) { 1445 /* minor shoehorning: boxdraw uses only this ushort */ 1446 glyphidx = boxdrawindex(&glyphs[i]); 1447 } else { 1448 /* Lookup character index with default font. */ 1449 glyphidx = XftCharIndex(xw.dpy, font->match, rune); 1450 } 1451 if (glyphidx) { 1452 specs[numspecs].font = font->match; 1453 specs[numspecs].glyph = glyphidx; 1454 specs[numspecs].x = (short)xp; 1455 specs[numspecs].y = (short)yp; 1456 xp += runewidth; 1457 numspecs++; 1458 continue; 1459 } 1460 1461 /* Fallback on font cache, search the font cache for match. */ 1462 for (f = 0; f < frclen; f++) { 1463 glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); 1464 /* Everything correct. */ 1465 if (glyphidx && frc[f].flags == frcflags) 1466 break; 1467 /* We got a default font for a not found glyph. */ 1468 if (!glyphidx && frc[f].flags == frcflags 1469 && frc[f].unicodep == rune) { 1470 break; 1471 } 1472 } 1473 1474 /* Nothing was found. Use fontconfig to find matching font. */ 1475 if (f >= frclen) { 1476 if (!font->set) 1477 font->set = FcFontSort(0, font->pattern, 1478 1, 0, &fcres); 1479 fcsets[0] = font->set; 1480 1481 /* 1482 * Nothing was found in the cache. Now use 1483 * some dozen of Fontconfig calls to get the 1484 * font for one single character. 1485 * 1486 * Xft and fontconfig are design failures. 1487 */ 1488 fcpattern = FcPatternDuplicate(font->pattern); 1489 fccharset = FcCharSetCreate(); 1490 1491 FcCharSetAddChar(fccharset, rune); 1492 FcPatternAddCharSet(fcpattern, FC_CHARSET, 1493 fccharset); 1494 FcPatternAddBool(fcpattern, FC_SCALABLE, 1); 1495 1496 FcConfigSubstitute(0, fcpattern, 1497 FcMatchPattern); 1498 FcDefaultSubstitute(fcpattern); 1499 1500 fontpattern = FcFontSetMatch(0, fcsets, 1, 1501 fcpattern, &fcres); 1502 1503 /* Allocate memory for the new cache entry. */ 1504 if (frclen >= frccap) { 1505 frccap += 16; 1506 frc = xrealloc(frc, frccap * sizeof(Fontcache)); 1507 } 1508 1509 frc[frclen].font = XftFontOpenPattern(xw.dpy, 1510 fontpattern); 1511 if (!frc[frclen].font) 1512 die("XftFontOpenPattern failed seeking fallback font: %s\n", 1513 strerror(errno)); 1514 frc[frclen].flags = frcflags; 1515 frc[frclen].unicodep = rune; 1516 1517 glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); 1518 1519 f = frclen; 1520 frclen++; 1521 1522 FcPatternDestroy(fcpattern); 1523 FcCharSetDestroy(fccharset); 1524 } 1525 1526 specs[numspecs].font = frc[f].font; 1527 specs[numspecs].glyph = glyphidx; 1528 specs[numspecs].x = (short)xp; 1529 specs[numspecs].y = (short)yp; 1530 xp += runewidth; 1531 numspecs++; 1532 } 1533 1534 /* Harfbuzz transformation for ligatures. */ 1535 hbtransform(specs, glyphs, len, x, y); 1536 1537 return numspecs; 1538 } 1539 1540 void 1541 xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) 1542 { 1543 int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); 1544 int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, 1545 width = charlen * win.cw; 1546 Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; 1547 XRenderColor colfg, colbg; 1548 XRectangle r; 1549 1550 /* Fallback on color display for attributes not supported by the font */ 1551 if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) { 1552 if (dc.ibfont.badslant || dc.ibfont.badweight) 1553 base.fg = defaultattr; 1554 } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) || 1555 (base.mode & ATTR_BOLD && dc.bfont.badweight)) { 1556 base.fg = defaultattr; 1557 } 1558 1559 if (IS_TRUECOL(base.fg)) { 1560 colfg.alpha = 0xffff; 1561 colfg.red = TRUERED(base.fg); 1562 colfg.green = TRUEGREEN(base.fg); 1563 colfg.blue = TRUEBLUE(base.fg); 1564 XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg); 1565 fg = &truefg; 1566 } else { 1567 fg = &dc.col[base.fg]; 1568 } 1569 1570 if (IS_TRUECOL(base.bg)) { 1571 colbg.alpha = 0xffff; 1572 colbg.green = TRUEGREEN(base.bg); 1573 colbg.red = TRUERED(base.bg); 1574 colbg.blue = TRUEBLUE(base.bg); 1575 XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg); 1576 bg = &truebg; 1577 } else { 1578 bg = &dc.col[base.bg]; 1579 } 1580 1581 /* Change basic system colors [0-7] to bright system colors [8-15] */ 1582 if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7)) 1583 fg = &dc.col[base.fg + 8]; 1584 1585 if (IS_SET(MODE_REVERSE)) { 1586 if (fg == &dc.col[defaultfg]) { 1587 fg = &dc.col[defaultbg]; 1588 } else { 1589 colfg.red = ~fg->color.red; 1590 colfg.green = ~fg->color.green; 1591 colfg.blue = ~fg->color.blue; 1592 colfg.alpha = fg->color.alpha; 1593 XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, 1594 &revfg); 1595 fg = &revfg; 1596 } 1597 1598 if (bg == &dc.col[defaultbg]) { 1599 bg = &dc.col[defaultfg]; 1600 } else { 1601 colbg.red = ~bg->color.red; 1602 colbg.green = ~bg->color.green; 1603 colbg.blue = ~bg->color.blue; 1604 colbg.alpha = bg->color.alpha; 1605 XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, 1606 &revbg); 1607 bg = &revbg; 1608 } 1609 } 1610 1611 if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) { 1612 colfg.red = fg->color.red / 2; 1613 colfg.green = fg->color.green / 2; 1614 colfg.blue = fg->color.blue / 2; 1615 colfg.alpha = fg->color.alpha; 1616 XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); 1617 fg = &revfg; 1618 } 1619 1620 if (base.mode & ATTR_REVERSE) { 1621 temp = fg; 1622 fg = bg; 1623 bg = temp; 1624 } 1625 1626 if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK) 1627 fg = bg; 1628 1629 if (base.mode & ATTR_INVISIBLE) 1630 fg = bg; 1631 1632 /* Intelligent cleaning up of the borders. */ 1633 if (x == 0) { 1634 xclear(0, (y == 0)? 0 : winy, borderpx, 1635 winy + win.ch + 1636 ((winy + win.ch >= borderpx + win.th)? win.h : 0)); 1637 } 1638 if (winx + width >= borderpx + win.tw) { 1639 xclear(winx + width, (y == 0)? 0 : winy, win.w, 1640 ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch))); 1641 } 1642 if (y == 0) 1643 xclear(winx, 0, winx + width, borderpx); 1644 if (winy + win.ch >= borderpx + win.th) 1645 xclear(winx, winy + win.ch, winx + width, win.h); 1646 1647 /* Clean up the region we want to draw to. */ 1648 XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); 1649 1650 /* Set the clip region because Xft is sometimes dirty. */ 1651 r.x = 0; 1652 r.y = 0; 1653 r.height = win.ch; 1654 r.width = width; 1655 XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); 1656 1657 if (base.mode & ATTR_BOXDRAW) { 1658 drawboxes(winx, winy, width / len, win.ch, fg, bg, specs, len); 1659 } else { 1660 /* Render the glyphs. */ 1661 XftDrawGlyphFontSpec(xw.draw, fg, specs, len); 1662 } 1663 1664 /* Render underline and strikethrough. */ 1665 if (base.mode & ATTR_UNDERLINE) { 1666 XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1, 1667 width, 1); 1668 } 1669 1670 if (base.mode & ATTR_STRUCK) { 1671 XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent * chscale / 3, 1672 width, 1); 1673 } 1674 1675 /* Reset clip to none. */ 1676 XftDrawSetClip(xw.draw, 0); 1677 } 1678 1679 void 1680 xdrawglyph(Glyph g, int x, int y) 1681 { 1682 int numspecs; 1683 XftGlyphFontSpec spec; 1684 1685 numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); 1686 xdrawglyphfontspecs(&spec, g, numspecs, x, y); 1687 } 1688 1689 void 1690 xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len) 1691 { 1692 Color drawcol; 1693 1694 /* remove the old cursor */ 1695 if (selected(ox, oy)) 1696 og.mode ^= ATTR_REVERSE; 1697 1698 /* Redraw the line where cursor was previously. 1699 * It will restore the ligatures broken by the cursor. */ 1700 xdrawline(line, 0, oy, len); 1701 1702 if (IS_SET(MODE_HIDE)) 1703 return; 1704 1705 /* 1706 * Select the right color for the right mode. 1707 */ 1708 g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE|ATTR_BOXDRAW; 1709 1710 if (IS_SET(MODE_REVERSE)) { 1711 g.mode |= ATTR_REVERSE; 1712 g.bg = defaultfg; 1713 if (selected(cx, cy)) { 1714 drawcol = dc.col[defaultcs]; 1715 g.fg = defaultrcs; 1716 } else { 1717 drawcol = dc.col[defaultrcs]; 1718 g.fg = defaultcs; 1719 } 1720 } else { 1721 if (selected(cx, cy)) { 1722 g.fg = defaultfg; 1723 g.bg = defaultrcs; 1724 } else { 1725 g.fg = defaultbg; 1726 g.bg = defaultcs; 1727 } 1728 drawcol = dc.col[g.bg]; 1729 } 1730 1731 /* draw the new one */ 1732 if (IS_SET(MODE_FOCUSED)) { 1733 switch (win.cursor) { 1734 case 7: /* st extension */ 1735 g.u = 0x2603; /* snowman (U+2603) */ 1736 xdrawglyph(g, cx, cy); 1737 break; 1738 case 0: /* Blinking Block */ 1739 case 1: /* Blinking Block (Default) */ 1740 if (IS_SET(MODE_BLINK)) 1741 break; 1742 /* FALLTHROUGH */ 1743 case 2: /* Steady Block */ 1744 xdrawglyph(g, cx, cy); 1745 break; 1746 case 3: /* Blinking Underline */ 1747 if (IS_SET(MODE_BLINK)) 1748 break; 1749 /* FALLTHROUGH */ 1750 case 4: /* Steady Underline */ 1751 XftDrawRect(xw.draw, &drawcol, 1752 borderpx + cx * win.cw, 1753 borderpx + (cy + 1) * win.ch - \ 1754 cursorthickness, 1755 win.cw, cursorthickness); 1756 break; 1757 case 5: /* Blinking bar */ 1758 if (IS_SET(MODE_BLINK)) 1759 break; 1760 /* FALLTHROUGH */ 1761 case 6: /* Steady bar */ 1762 XftDrawRect(xw.draw, &drawcol, 1763 borderpx + cx * win.cw, 1764 borderpx + cy * win.ch, 1765 cursorthickness, win.ch); 1766 break; 1767 } 1768 } else { 1769 XftDrawRect(xw.draw, &drawcol, 1770 borderpx + cx * win.cw, 1771 borderpx + cy * win.ch, 1772 win.cw - 1, 1); 1773 XftDrawRect(xw.draw, &drawcol, 1774 borderpx + cx * win.cw, 1775 borderpx + cy * win.ch, 1776 1, win.ch - 1); 1777 XftDrawRect(xw.draw, &drawcol, 1778 borderpx + (cx + 1) * win.cw - 1, 1779 borderpx + cy * win.ch, 1780 1, win.ch - 1); 1781 XftDrawRect(xw.draw, &drawcol, 1782 borderpx + cx * win.cw, 1783 borderpx + (cy + 1) * win.ch - 1, 1784 win.cw, 1); 1785 } 1786 } 1787 1788 void 1789 xsetenv(void) 1790 { 1791 char buf[sizeof(long) * 8 + 1]; 1792 1793 snprintf(buf, sizeof(buf), "%lu", xw.win); 1794 setenv("WINDOWID", buf, 1); 1795 } 1796 1797 void 1798 xseticontitle(char *p) 1799 { 1800 XTextProperty prop; 1801 DEFAULT(p, opt_title); 1802 1803 if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, 1804 &prop) != Success) 1805 return; 1806 XSetWMIconName(xw.dpy, xw.win, &prop); 1807 XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname); 1808 XFree(prop.value); 1809 } 1810 1811 void 1812 xsettitle(char *p) 1813 { 1814 XTextProperty prop; 1815 DEFAULT(p, opt_title); 1816 1817 if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, 1818 &prop) != Success) 1819 return; 1820 XSetWMName(xw.dpy, xw.win, &prop); 1821 XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); 1822 XFree(prop.value); 1823 } 1824 1825 int 1826 xstartdraw(void) 1827 { 1828 return IS_SET(MODE_VISIBLE); 1829 } 1830 1831 void 1832 xdrawline(Line line, int x1, int y1, int x2) 1833 { 1834 int i, x, ox, numspecs; 1835 Glyph base, new; 1836 XftGlyphFontSpec *specs = xw.specbuf; 1837 1838 numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); 1839 i = ox = 0; 1840 for (x = x1; x < x2 && i < numspecs; x++) { 1841 new = line[x]; 1842 if (new.mode == ATTR_WDUMMY) 1843 continue; 1844 if (selected(x, y1)) 1845 new.mode ^= ATTR_REVERSE; 1846 if (i > 0 && ATTRCMP(base, new)) { 1847 xdrawglyphfontspecs(specs, base, i, ox, y1); 1848 specs += i; 1849 numspecs -= i; 1850 i = 0; 1851 } 1852 if (i == 0) { 1853 ox = x; 1854 base = new; 1855 } 1856 i++; 1857 } 1858 if (i > 0) 1859 xdrawglyphfontspecs(specs, base, i, ox, y1); 1860 } 1861 1862 void 1863 xfinishdraw(void) 1864 { 1865 XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, 1866 win.h, 0, 0); 1867 XSetForeground(xw.dpy, dc.gc, 1868 dc.col[IS_SET(MODE_REVERSE)? 1869 defaultfg : defaultbg].pixel); 1870 } 1871 1872 void 1873 xximspot(int x, int y) 1874 { 1875 if (xw.ime.xic == NULL) 1876 return; 1877 1878 xw.ime.spot.x = borderpx + x * win.cw; 1879 xw.ime.spot.y = borderpx + (y + 1) * win.ch; 1880 1881 XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL); 1882 } 1883 1884 void 1885 expose(XEvent *ev) 1886 { 1887 redraw(); 1888 } 1889 1890 void 1891 visibility(XEvent *ev) 1892 { 1893 XVisibilityEvent *e = &ev->xvisibility; 1894 1895 MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE); 1896 } 1897 1898 void 1899 unmap(XEvent *ev) 1900 { 1901 win.mode &= ~MODE_VISIBLE; 1902 } 1903 1904 void 1905 xsetpointermotion(int set) 1906 { 1907 MODBIT(xw.attrs.event_mask, set, PointerMotionMask); 1908 XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); 1909 } 1910 1911 void 1912 xsetmode(int set, unsigned int flags) 1913 { 1914 int mode = win.mode; 1915 MODBIT(win.mode, set, flags); 1916 if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE)) 1917 redraw(); 1918 } 1919 1920 int 1921 xsetcursor(int cursor) 1922 { 1923 if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */ 1924 return 1; 1925 win.cursor = cursor; 1926 return 0; 1927 } 1928 1929 void 1930 xseturgency(int add) 1931 { 1932 XWMHints *h = XGetWMHints(xw.dpy, xw.win); 1933 1934 MODBIT(h->flags, add, XUrgencyHint); 1935 XSetWMHints(xw.dpy, xw.win, h); 1936 XFree(h); 1937 } 1938 1939 void 1940 xbell(void) 1941 { 1942 if (!(IS_SET(MODE_FOCUSED))) 1943 xseturgency(1); 1944 if (bellvolume) 1945 XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL); 1946 } 1947 1948 void 1949 focus(XEvent *ev) 1950 { 1951 XFocusChangeEvent *e = &ev->xfocus; 1952 1953 if (e->mode == NotifyGrab) 1954 return; 1955 1956 if (ev->type == FocusIn) { 1957 if (xw.ime.xic) 1958 XSetICFocus(xw.ime.xic); 1959 win.mode |= MODE_FOCUSED; 1960 xseturgency(0); 1961 if (IS_SET(MODE_FOCUS)) 1962 ttywrite("\033[I", 3, 0); 1963 if (!focused) { 1964 focused = 1; 1965 xloadcols(); 1966 tfulldirt(); 1967 } 1968 } else { 1969 if (xw.ime.xic) 1970 XUnsetICFocus(xw.ime.xic); 1971 win.mode &= ~MODE_FOCUSED; 1972 if (IS_SET(MODE_FOCUS)) 1973 ttywrite("\033[O", 3, 0); 1974 if (focused) { 1975 focused = 0; 1976 xloadcols(); 1977 tfulldirt(); 1978 } 1979 } 1980 } 1981 1982 int 1983 match(uint mask, uint state) 1984 { 1985 return mask == XK_ANY_MOD || mask == (state & ~ignoremod); 1986 } 1987 1988 char* 1989 kmap(KeySym k, uint state) 1990 { 1991 Key *kp; 1992 int i; 1993 1994 /* Check for mapped keys out of X11 function keys. */ 1995 for (i = 0; i < LEN(mappedkeys); i++) { 1996 if (mappedkeys[i] == k) 1997 break; 1998 } 1999 if (i == LEN(mappedkeys)) { 2000 if ((k & 0xFFFF) < 0xFD00) 2001 return NULL; 2002 } 2003 2004 for (kp = key; kp < key + LEN(key); kp++) { 2005 if (kp->k != k) 2006 continue; 2007 2008 if (!match(kp->mask, state)) 2009 continue; 2010 2011 if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0) 2012 continue; 2013 if (IS_SET(MODE_NUMLOCK) && kp->appkey == 2) 2014 continue; 2015 2016 if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0) 2017 continue; 2018 2019 return kp->s; 2020 } 2021 2022 return NULL; 2023 } 2024 2025 void 2026 kpress(XEvent *ev) 2027 { 2028 XKeyEvent *e = &ev->xkey; 2029 KeySym ksym; 2030 char *buf = NULL, *customkey; 2031 int len = 0; 2032 int buf_size = 64; 2033 int critical = - 1; 2034 Rune c; 2035 Status status; 2036 Shortcut *bp; 2037 2038 if (IS_SET(MODE_KBDLOCK)) 2039 return; 2040 2041 reallocbuf: 2042 if (critical > 0) 2043 goto cleanup; 2044 if (buf) 2045 free(buf); 2046 2047 buf = xmalloc((buf_size) * sizeof(char)); 2048 critical += 1; 2049 2050 if (xw.ime.xic) { 2051 len = XmbLookupString(xw.ime.xic, e, buf, buf_size, &ksym, &status); 2052 if (status == XBufferOverflow) { 2053 buf_size = len; 2054 goto reallocbuf; 2055 } 2056 } else { 2057 // Not sure how to fix this and if it is fixable 2058 // but at least it does write something into the buffer 2059 // so it is not as critical 2060 len = XLookupString(e, buf, buf_size, &ksym, NULL); 2061 } 2062 /* 1. shortcuts */ 2063 for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { 2064 if (ksym == bp->keysym && match(bp->mod, e->state)) { 2065 bp->func(&(bp->arg)); 2066 goto cleanup; 2067 } 2068 } 2069 2070 /* 2. custom keys from config.h */ 2071 if ((customkey = kmap(ksym, e->state))) { 2072 ttywrite(customkey, strlen(customkey), 1); 2073 goto cleanup; 2074 } 2075 2076 /* 3. composed string from input method */ 2077 if (len == 0) 2078 goto cleanup; 2079 if (len == 1 && e->state & Mod1Mask) { 2080 if (IS_SET(MODE_8BIT)) { 2081 if (*buf < 0177) { 2082 c = *buf | 0x80; 2083 len = utf8encode(c, buf); 2084 } 2085 } else { 2086 buf[1] = buf[0]; 2087 buf[0] = '\033'; 2088 len = 2; 2089 } 2090 } 2091 if (len <= buf_size) 2092 ttywrite(buf, len, 1); 2093 cleanup: 2094 if (buf) 2095 free(buf); 2096 } 2097 2098 void 2099 cmessage(XEvent *e) 2100 { 2101 /* 2102 * See xembed specs 2103 * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html 2104 */ 2105 if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) { 2106 if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) { 2107 win.mode |= MODE_FOCUSED; 2108 xseturgency(0); 2109 } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) { 2110 win.mode &= ~MODE_FOCUSED; 2111 } 2112 } else if (e->xclient.data.l[0] == xw.wmdeletewin) { 2113 ttyhangup(); 2114 exit(0); 2115 } 2116 } 2117 2118 void 2119 resize(XEvent *e) 2120 { 2121 if (e->xconfigure.width == win.w && e->xconfigure.height == win.h) 2122 return; 2123 2124 cresize(e->xconfigure.width, e->xconfigure.height); 2125 } 2126 2127 void 2128 run(void) 2129 { 2130 XEvent ev; 2131 int w = win.w, h = win.h; 2132 fd_set rfd; 2133 int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing; 2134 struct timespec seltv, *tv, now, lastblink, trigger; 2135 double timeout; 2136 int blinkcursor; 2137 2138 /* Waiting for window mapping */ 2139 do { 2140 XNextEvent(xw.dpy, &ev); 2141 /* 2142 * This XFilterEvent call is required because of XOpenIM. It 2143 * does filter out the key event and some client message for 2144 * the input method too. 2145 */ 2146 if (XFilterEvent(&ev, None)) 2147 continue; 2148 if (ev.type == ConfigureNotify) { 2149 w = ev.xconfigure.width; 2150 h = ev.xconfigure.height; 2151 } 2152 } while (ev.type != MapNotify); 2153 2154 ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd); 2155 cresize(w, h); 2156 2157 for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) { 2158 FD_ZERO(&rfd); 2159 FD_SET(ttyfd, &rfd); 2160 FD_SET(xfd, &rfd); 2161 2162 if (XPending(xw.dpy)) 2163 timeout = 0; /* existing events might not set xfd */ 2164 2165 seltv.tv_sec = timeout / 1E3; 2166 seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec); 2167 tv = timeout >= 0 ? &seltv : NULL; 2168 2169 if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) { 2170 if (errno == EINTR) 2171 continue; 2172 die("select failed: %s\n", strerror(errno)); 2173 } 2174 clock_gettime(CLOCK_MONOTONIC, &now); 2175 2176 if (FD_ISSET(ttyfd, &rfd)) 2177 ttyread(); 2178 2179 xev = 0; 2180 while (XPending(xw.dpy)) { 2181 xev = 1; 2182 XNextEvent(xw.dpy, &ev); 2183 if (XFilterEvent(&ev, None)) 2184 continue; 2185 if (handler[ev.type]) 2186 (handler[ev.type])(&ev); 2187 } 2188 2189 /* 2190 * To reduce flicker and tearing, when new content or event 2191 * triggers drawing, we first wait a bit to ensure we got 2192 * everything, and if nothing new arrives - we draw. 2193 * We start with trying to wait minlatency ms. If more content 2194 * arrives sooner, we retry with shorter and shorter periods, 2195 * and eventually draw even without idle after maxlatency ms. 2196 * Typically this results in low latency while interacting, 2197 * maximum latency intervals during `cat huge.txt`, and perfect 2198 * sync with periodic updates from animations/key-repeats/etc. 2199 */ 2200 if (FD_ISSET(ttyfd, &rfd) || xev) { 2201 if (!drawing) { 2202 trigger = now; 2203 if (IS_SET(MODE_BLINK)) { 2204 win.mode ^= MODE_BLINK; 2205 } 2206 lastblink = now; 2207 drawing = 1; 2208 } 2209 timeout = (maxlatency - TIMEDIFF(now, trigger)) \ 2210 / maxlatency * minlatency; 2211 if (timeout > 0) 2212 continue; /* we have time, try to find idle */ 2213 } 2214 2215 /* idle detected or maxlatency exhausted -> draw */ 2216 timeout = -1; 2217 blinkcursor = win.cursor == 0 || win.cursor == 1 || 2218 win.cursor == 3 || win.cursor == 5; 2219 if (blinktimeout && (blinkcursor || tattrset(ATTR_BLINK))) { 2220 timeout = blinktimeout - TIMEDIFF(now, lastblink); 2221 if (timeout <= 0) { 2222 if (-timeout > blinktimeout) /* start visible */ 2223 win.mode |= MODE_BLINK; 2224 win.mode ^= MODE_BLINK; 2225 tsetdirtattr(ATTR_BLINK); 2226 lastblink = now; 2227 timeout = blinktimeout; 2228 } 2229 } 2230 2231 draw(); 2232 XFlush(xw.dpy); 2233 drawing = 0; 2234 } 2235 } 2236 2237 int 2238 resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst) 2239 { 2240 char **sdst = dst; 2241 int *idst = dst; 2242 float *fdst = dst; 2243 2244 char fullname[256]; 2245 char fullclass[256]; 2246 char *type; 2247 XrmValue ret; 2248 2249 snprintf(fullname, sizeof(fullname), "%s.%s", 2250 opt_name ? opt_name : "st", name); 2251 snprintf(fullclass, sizeof(fullclass), "%s.%s", 2252 opt_class ? opt_class : "St", name); 2253 fullname[sizeof(fullname) - 1] = fullclass[sizeof(fullclass) - 1] = '\0'; 2254 2255 XrmGetResource(db, fullname, fullclass, &type, &ret); 2256 if (ret.addr == NULL || strncmp("String", type, 64)) 2257 return 1; 2258 2259 switch (rtype) { 2260 case STRING: 2261 *sdst = ret.addr; 2262 break; 2263 case INTEGER: 2264 *idst = strtoul(ret.addr, NULL, 10); 2265 break; 2266 case FLOAT: 2267 *fdst = strtof(ret.addr, NULL); 2268 break; 2269 } 2270 return 0; 2271 } 2272 2273 void 2274 config_init(void) 2275 { 2276 char *resm; 2277 XrmDatabase db; 2278 ResourcePref *p; 2279 2280 XrmInitialize(); 2281 resm = XResourceManagerString(xw.dpy); 2282 if (!resm) 2283 return; 2284 2285 db = XrmGetStringDatabase(resm); 2286 for (p = resources; p < resources + LEN(resources); p++) 2287 resource_load(db, p->name, p->type, p->dst); 2288 } 2289 2290 void 2291 usage(void) 2292 { 2293 die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]" 2294 " [-n name] [-o file]\n" 2295 " [-T title] [-t title] [-w windowid]" 2296 " [[-e] command [args ...]]\n" 2297 " %s [-aiv] [-c class] [-f font] [-g geometry]" 2298 " [-n name] [-o file]\n" 2299 " [-T title] [-t title] [-w windowid] -l line" 2300 " [stty_args ...]\n", argv0, argv0); 2301 } 2302 2303 int 2304 main(int argc, char *argv[]) 2305 { 2306 xw.l = xw.t = 0; 2307 xw.isfixed = False; 2308 // xsetcursor(cursorshape); 2309 win.cursor = cursorstyle; 2310 2311 ARGBEGIN { 2312 case 'a': 2313 allowaltscreen = 0; 2314 break; 2315 case 'A': 2316 opt_alpha = EARGF(usage()); 2317 break; 2318 case 'c': 2319 opt_class = EARGF(usage()); 2320 break; 2321 case 'e': 2322 if (argc > 0) 2323 --argc, ++argv; 2324 goto run; 2325 case 'f': 2326 opt_font = EARGF(usage()); 2327 break; 2328 case 'g': 2329 xw.gm = XParseGeometry(EARGF(usage()), 2330 &xw.l, &xw.t, &cols, &rows); 2331 break; 2332 case 'i': 2333 xw.isfixed = 1; 2334 break; 2335 case 'o': 2336 opt_io = EARGF(usage()); 2337 break; 2338 case 'l': 2339 opt_line = EARGF(usage()); 2340 break; 2341 case 'n': 2342 opt_name = EARGF(usage()); 2343 break; 2344 case 't': 2345 case 'T': 2346 opt_title = EARGF(usage()); 2347 break; 2348 case 'w': 2349 opt_embed = EARGF(usage()); 2350 break; 2351 case 'v': 2352 die("%s " VERSION "\n", argv0); 2353 break; 2354 default: 2355 usage(); 2356 } ARGEND; 2357 2358 run: 2359 if (argc > 0) /* eat all remaining arguments */ 2360 opt_cmd = argv; 2361 2362 if (!opt_title) 2363 opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0]; 2364 2365 setlocale(LC_CTYPE, ""); 2366 XSetLocaleModifiers(""); 2367 2368 if(!(xw.dpy = XOpenDisplay(NULL))) 2369 die("Can't open display\n"); 2370 2371 config_init(); 2372 cols = MAX(cols, 1); 2373 rows = MAX(rows, 1); 2374 defaultbg = MAX(LEN(colorname), 256); 2375 alphaUnfocus = alpha-alphaOffset; 2376 tnew(cols, rows); 2377 xinit(cols, rows); 2378 xsetenv(); 2379 selinit(); 2380 run(); 2381 2382 return 0; 2383 } 2384