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