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