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