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