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