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