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