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