]> Sergey Matveev's repositories - st.git/blobdiff - x.c
Optimisations
[st.git] / x.c
diff --git a/x.c b/x.c
index b4886177273fd308baa2e86de1db8eb00b64e7a9..6cf403dc755fa1d0c9abf28b923d021a67937e6a 100644 (file)
--- a/x.c
+++ b/x.c
@@ -15,7 +15,7 @@
 #include <X11/Xft/Xft.h>
 #include <X11/XKBlib.h>
 
-static char *argv0;
+char *argv0;
 #include "arg.h"
 #include "st.h"
 #include "win.h"
@@ -48,7 +48,7 @@ typedef struct {
 /* X modifiers */
 #define XK_ANY_MOD    UINT_MAX
 #define XK_NO_MOD     0
-#define XK_SWITCH_MOD (1<<13)
+#define XK_SWITCH_MOD (1<<13|1<<14)
 
 /* function definitions used in config.h */
 static void clipcopy(const Arg *);
@@ -93,7 +93,7 @@ typedef struct {
        Window win;
        Drawable buf;
        GlyphFontSpec *specbuf; /* font spec buffer used for rendering */
-       Atom xembed, wmdeletewin, netwmname, netwmpid;
+       Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid;
        struct {
                XIM xim;
                XIC xic;
@@ -146,16 +146,17 @@ static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int);
 static void xdrawglyph(Glyph, int, int);
 static void xclear(int, int, int, int);
 static int xgeommasktogravity(int);
-static void ximopen(Display *);
+static int ximopen(Display *);
 static void ximinstantiate(Display *, XPointer, XPointer);
 static void ximdestroy(XIM, XPointer, XPointer);
+static int xicdestroy(XIC, XPointer, XPointer);
 static void xinit(int, int);
 static void cresize(int, int);
 static void xresize(int, int);
 static void xhints(void);
 static int xloadcolor(int, const char *, Color *);
 static int xloadfont(Font *, FcPattern *);
-static void xloadfonts(char *, double);
+static void xloadfonts(const char *, double);
 static void xunloadfont(Font *);
 static void xunloadfonts(void);
 static void xsetenv(void);
@@ -170,6 +171,7 @@ static void kpress(XEvent *);
 static void cmessage(XEvent *);
 static void resize(XEvent *);
 static void focus(XEvent *);
+static uint buttonmask(uint);
 static int mouseaction(XEvent *, uint);
 static void brelease(XEvent *);
 static void bpress(XEvent *);
@@ -250,7 +252,8 @@ static char *opt_line  = NULL;
 static char *opt_name  = NULL;
 static char *opt_title = NULL;
 
-static int oldbutton = 3; /* button event on startup: 3 = release */
+static uint buttons; /* bit field of pressed buttons */
+static int cursorblinks = 0;
 
 void
 clipcopy(const Arg *dummy)
@@ -362,59 +365,68 @@ mousesel(XEvent *e, int done)
 void
 mousereport(XEvent *e)
 {
-       int len, x = evcol(e), y = evrow(e),
-           button = e->xbutton.button, state = e->xbutton.state;
+       int len, btn, code;
+       int x = evcol(e), y = evrow(e);
+       int state = e->xbutton.state;
        char buf[40];
        static int ox, oy;
 
-       /* from urxvt */
-       if (e->xbutton.type == MotionNotify) {
+       if (e->type == MotionNotify) {
                if (x == ox && y == oy)
                        return;
                if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY))
                        return;
-               /* MOUSE_MOTION: no reporting if no button is pressed */
-               if (IS_SET(MODE_MOUSEMOTION) && oldbutton == 3)
+               /* MODE_MOUSEMOTION: no reporting if no button is pressed */
+               if (IS_SET(MODE_MOUSEMOTION) && buttons == 0)
                        return;
-
-               button = oldbutton + 32;
-               ox = x;
-               oy = y;
+               /* Set btn to lowest-numbered pressed button, or 12 if no
+                * buttons are pressed. */
+               for (btn = 1; btn <= 11 && !(buttons & (1<<(btn-1))); btn++)
+                       ;
+               code = 32;
        } else {
-               if (!IS_SET(MODE_MOUSESGR) && e->xbutton.type == ButtonRelease) {
-                       button = 3;
-               } else {
-                       button -= Button1;
-                       if (button >= 3)
-                               button += 64 - 3;
-               }
-               if (e->xbutton.type == ButtonPress) {
-                       oldbutton = button;
-                       ox = x;
-                       oy = y;
-               } else if (e->xbutton.type == ButtonRelease) {
-                       oldbutton = 3;
+               btn = e->xbutton.button;
+               /* Only buttons 1 through 11 can be encoded */
+               if (btn < 1 || btn > 11)
+                       return;
+               if (e->type == ButtonRelease) {
                        /* MODE_MOUSEX10: no button release reporting */
                        if (IS_SET(MODE_MOUSEX10))
                                return;
-                       if (button == 64 || button == 65)
+                       /* Don't send release events for the scroll wheel */
+                       if (btn == 4 || btn == 5)
                                return;
                }
+               code = 0;
        }
 
+       ox = x;
+       oy = y;
+
+       /* Encode btn into code. If no button is pressed for a motion event in
+        * MODE_MOUSEMANY, then encode it as a release. */
+       if ((!IS_SET(MODE_MOUSESGR) && e->type == ButtonRelease) || btn == 12)
+               code += 3;
+       else if (btn >= 8)
+               code += 128 + btn - 8;
+       else if (btn >= 4)
+               code += 64 + btn - 4;
+       else
+               code += btn - 1;
+
        if (!IS_SET(MODE_MOUSEX10)) {
-               button += ((state & ShiftMask  ) ? 4  : 0)
-                       + ((state & Mod4Mask   ) ? 8  : 0)
-                       + ((state & ControlMask) ? 16 : 0);
+               code += ((state & ShiftMask  ) ?  4 : 0)
+                     + ((state & Mod1Mask   ) ?  8 : 0) /* meta key: alt */
+                     + ((state & ControlMask) ? 16 : 0);
        }
 
        if (IS_SET(MODE_MOUSESGR)) {
                len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c",
-                               button, x+1, y+1,
-                               e->xbutton.type == ButtonRelease ? 'm' : 'M');
+                               code, x+1, y+1,
+                               e->type == ButtonRelease ? 'm' : 'M');
        } else if (x < 223 && y < 223) {
                len = snprintf(buf, sizeof(buf), "\033[M%c%c%c",
-                               32+button, 32+x+1, 32+y+1);
+                               32+code, 32+x+1, 32+y+1);
        } else {
                return;
        }
@@ -422,16 +434,30 @@ mousereport(XEvent *e)
        ttywrite(buf, len, 0);
 }
 
+uint
+buttonmask(uint button)
+{
+       return button == Button1 ? Button1Mask
+            : button == Button2 ? Button2Mask
+            : button == Button3 ? Button3Mask
+            : button == Button4 ? Button4Mask
+            : button == Button5 ? Button5Mask
+            : 0;
+}
+
 int
 mouseaction(XEvent *e, uint release)
 {
        MouseShortcut *ms;
 
+       /* ignore Button<N>mask for Button<N> - it's set on release */
+       uint state = e->xbutton.state & ~buttonmask(e->xbutton.button);
+
        for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) {
                if (ms->release == release &&
                    ms->button == e->xbutton.button &&
-                   (match(ms->mod, e->xbutton.state) ||  /* exact or forced */
-                    match(ms->mod, e->xbutton.state & ~forcemousemod))) {
+                   (match(ms->mod, state) ||  /* exact or forced */
+                    match(ms->mod, state & ~forcemousemod))) {
                        ms->func(&(ms->arg));
                        return 1;
                }
@@ -443,9 +469,13 @@ mouseaction(XEvent *e, uint release)
 void
 bpress(XEvent *e)
 {
+       int btn = e->xbutton.button;
        struct timespec now;
        int snap;
 
+       if (1 <= btn && btn <= 11)
+               buttons |= 1 << (btn-1);
+
        if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) {
                mousereport(e);
                return;
@@ -454,7 +484,7 @@ bpress(XEvent *e)
        if (mouseaction(e, 0))
                return;
 
-       if (e->xbutton.button == Button1) {
+       if (btn == Button1) {
                /*
                 * If the user clicks below predefined timeouts specific
                 * snapping behaviour is exposed.
@@ -668,6 +698,11 @@ xsetsel(char *str)
 void
 brelease(XEvent *e)
 {
+       int btn = e->xbutton.button;
+
+       if (1 <= btn && btn <= 11)
+               buttons &= ~(1 << (btn-1));
+
        if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) {
                mousereport(e);
                return;
@@ -675,7 +710,7 @@ brelease(XEvent *e)
 
        if (mouseaction(e, 1))
                return;
-       if (e->xbutton.button == Button1)
+       if (btn == Button1)
                mousesel(e, 1);
 }
 
@@ -781,12 +816,25 @@ xloadcols(void)
        loaded = 1;
 }
 
+int
+xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b)
+{
+       if (!BETWEEN(x, 0, dc.collen - 1))
+               return 1;
+
+       *r = dc.col[x].color.red >> 8;
+       *g = dc.col[x].color.green >> 8;
+       *b = dc.col[x].color.blue >> 8;
+
+       return 0;
+}
+
 int
 xsetcolorname(int x, const char *name)
 {
        Color ncolor;
 
-       if (!BETWEEN(x, 0, dc.collen))
+       if (!BETWEEN(x, 0, dc.collen - 1))
                return 1;
 
        if (!xloadcolor(x, name, &ncolor))
@@ -934,7 +982,7 @@ xloadfont(Font *f, FcPattern *pattern)
 }
 
 void
-xloadfonts(char *fontstr, double fontsize)
+xloadfonts(const char *fontstr, double fontsize)
 {
        FcPattern *pattern;
        double fontval;
@@ -942,7 +990,7 @@ xloadfonts(char *fontstr, double fontsize)
        if (fontstr[0] == '-')
                pattern = XftXlfdParse(fontstr, False, False);
        else
-               pattern = FcNameParse((FcChar8 *)fontstr);
+               pattern = FcNameParse((const FcChar8 *)fontstr);
 
        if (!pattern)
                die("can't open font %s\n", fontstr);
@@ -1025,48 +1073,60 @@ xunloadfonts(void)
        xunloadfont(&dc.ibfont);
 }
 
-void
+int
 ximopen(Display *dpy)
 {
-       XIMCallback destroy = { .client_data = NULL, .callback = ximdestroy };
+       XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy };
+       XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy };
 
-       if ((xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) {
-               XSetLocaleModifiers("@im=local");
-               if ((xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) {
-                       XSetLocaleModifiers("@im=");
-                       if ((xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL)
-                               die("XOpenIM failed. Could not open input device.\n");
-               }
-       }
-       if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &destroy, NULL) != NULL)
-               die("XSetIMValues failed. Could not set input method value.\n");
-       xw.xic = XCreateIC(xw.ime.xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
-                          XNClientWindow, xw.win, XNFocusWindow, xw.win, NULL);
-       if (xw.xic == NULL)
-               die("XCreateIC failed. Could not obtain input method.\n");
+       xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL);
+       if (xw.ime.xim == NULL)
+               return 0;
+
+       if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL))
+               fprintf(stderr, "XSetIMValues: "
+                               "Could not set XNDestroyCallback.\n");
 
        xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot,
                                              NULL);
+
+       if (xw.ime.xic == NULL) {
+               xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle,
+                                      XIMPreeditNothing | XIMStatusNothing,
+                                      XNClientWindow, xw.win,
+                                      XNDestroyCallback, &icdestroy,
+                                      NULL);
+       }
+       if (xw.ime.xic == NULL)
+               fprintf(stderr, "XCreateIC: Could not create input context.\n");
+
+       return 1;
 }
 
 void
 ximinstantiate(Display *dpy, XPointer client, XPointer call)
 {
-       ximopen(dpy);
-       XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
-                                       ximinstantiate, NULL);
+       if (ximopen(dpy))
+               XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
+                                                ximinstantiate, NULL);
 }
 
 void
 ximdestroy(XIM xim, XPointer client, XPointer call)
 {
        xw.ime.xim = NULL;
-       xw.ime.xic = NULL;
        XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
-                                       ximinstantiate, NULL);
+                                      ximinstantiate, NULL);
        XFree(xw.ime.spotlist);
 }
 
+int
+xicdestroy(XIC xim, XPointer client, XPointer call)
+{
+       xw.ime.xic = NULL;
+       return 1;
+}
+
 void
 xinit(int cols, int rows)
 {
@@ -1132,29 +1192,15 @@ xinit(int cols, int rows)
        xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap);
 
        /* input methods */
-       ximopen(xw.dpy);
-
-       /* white cursor, black outline */
-       cursor = XCreateFontCursor(xw.dpy, mouseshape);
-       XDefineCursor(xw.dpy, xw.win, cursor);
-
-       if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) {
-               xmousefg.red   = 0xffff;
-               xmousefg.green = 0xffff;
-               xmousefg.blue  = 0xffff;
-       }
-
-       if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) {
-               xmousebg.red   = 0x0000;
-               xmousebg.green = 0x0000;
-               xmousebg.blue  = 0x0000;
+       if (!ximopen(xw.dpy)) {
+               XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
+                                              ximinstantiate, NULL);
        }
 
-       XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg);
-
        xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False);
        xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False);
        xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False);
+       xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False);
        XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1);
 
        xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False);
@@ -1318,12 +1364,7 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
        XRenderColor colfg, colbg;
        XRectangle r;
 
-       /* Fallback on color display for attributes not supported by the font */
-       if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) {
-               if (dc.ibfont.badslant || dc.ibfont.badweight)
-                       base.fg = defaultattr;
-       } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) ||
-           (base.mode & ATTR_BOLD && dc.bfont.badweight)) {
+       if (base.mode & ATTR_ITALIC) {
                base.fg = defaultattr;
        }
 
@@ -1430,12 +1471,12 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
 
        /* Render underline and strikethrough. */
        if (base.mode & ATTR_UNDERLINE) {
-               XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1,
+               XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1,
                                width, 1);
        }
 
        if (base.mode & ATTR_STRUCK) {
-               XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 3,
+               XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent * chscale / 3,
                                width, 1);
        }
 
@@ -1495,15 +1536,19 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
        /* draw the new one */
        if (IS_SET(MODE_FOCUSED)) {
                switch (win.cursor) {
-               case 7: /* st extension: snowman (U+2603) */
-                       g.u = 0x2603;
-               case 0: /* Blinking Block */
-               case 1: /* Blinking Block (Default) */
-               case 2: /* Steady Block */
+               case 0: /* Blinking block */
+               case 1: /* Blinking block (default) */
+                       if (IS_SET(MODE_BLINK))
+                               break;
+                       /* FALLTHROUGH */
+               case 2: /* Steady block */
                        xdrawglyph(g, cx, cy);
                        break;
-               case 3: /* Blinking Underline */
-               case 4: /* Steady Underline */
+               case 3: /* Blinking underline */
+                       if (IS_SET(MODE_BLINK))
+                               break;
+                       /* FALLTHROUGH */
+               case 4: /* Steady underline */
                        XftDrawRect(xw.draw, &drawcol,
                                        borderpx + cx * win.cw,
                                        borderpx + (cy + 1) * win.ch - \
@@ -1511,12 +1556,23 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
                                        win.cw, cursorthickness);
                        break;
                case 5: /* Blinking bar */
+                       if (IS_SET(MODE_BLINK))
+                               break;
+                       /* FALLTHROUGH */
                case 6: /* Steady bar */
                        XftDrawRect(xw.draw, &drawcol,
                                        borderpx + cx * win.cw,
                                        borderpx + cy * win.ch,
                                        cursorthickness, win.ch);
                        break;
+               case 7: /* Blinking st cursor */
+                       if (IS_SET(MODE_BLINK))
+                               break;
+                       /* FALLTHROUGH */
+               case 8: /* Steady st cursor */
+                       g.u = 0x2603; /* snowman (U+2603) */
+                       xdrawglyph(g, cx, cy);
+                       break;
                }
        } else {
                XftDrawRect(xw.draw, &drawcol,
@@ -1547,14 +1603,35 @@ xsetenv(void)
        setenv("WINDOWID", buf, 1);
 }
 
+void
+xseticontitle(char *p)
+{
+       XTextProperty prop;
+       DEFAULT(p, opt_title);
+
+       if (p[0] == '\0')
+               p = opt_title;
+
+       if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle,
+                                       &prop) != Success)
+               return;
+       XSetWMIconName(xw.dpy, xw.win, &prop);
+       XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname);
+       XFree(prop.value);
+}
+
 void
 xsettitle(char *p)
 {
        XTextProperty prop;
        DEFAULT(p, opt_title);
 
-       Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle,
-                       &prop);
+       if (p[0] == '\0')
+               p = opt_title;
+
+       if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle,
+                                       &prop) != Success)
+               return;
        XSetWMName(xw.dpy, xw.win, &prop);
        XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname);
        XFree(prop.value);
@@ -1658,10 +1735,12 @@ xsetmode(int set, unsigned int flags)
 int
 xsetcursor(int cursor)
 {
-       DEFAULT(cursor, 1);
-       if (!BETWEEN(cursor, 0, 6))
+       if (!BETWEEN(cursor, 0, 8)) /* 7-8: st extensions */
                return 1;
        win.cursor = cursor;
+       cursorblinks = win.cursor == 0 || win.cursor == 1 ||
+                      win.cursor == 3 || win.cursor == 5 ||
+                      win.cursor == 7;
        return 0;
 }
 
@@ -1755,7 +1834,7 @@ void
 kpress(XEvent *ev)
 {
        XKeyEvent *e = &ev->xkey;
-       KeySym ksym;
+       KeySym ksym = NoSymbol;
        char buf[64], *customkey;
        int len;
        Rune c;
@@ -1765,7 +1844,13 @@ kpress(XEvent *ev)
        if (IS_SET(MODE_KBDLOCK))
                return;
 
-       len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status);
+       if (xw.ime.xic) {
+               len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status);
+               if (status == XBufferOverflow)
+                       return;
+       } else {
+               len = XLookupString(e, buf, sizeof buf, &ksym, NULL);
+       }
        /* 1. shortcuts */
        for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) {
                if (ksym == bp->keysym && match(bp->mod, e->state)) {
@@ -1833,10 +1918,9 @@ run(void)
        XEvent ev;
        int w = win.w, h = win.h;
        fd_set rfd;
-       int xfd = XConnectionNumber(xw.dpy), xev, blinkset = 0, dodraw = 0;
-       int ttyfd;
-       struct timespec drawtimeout, *tv = NULL, now, last, lastblink;
-       long deltatime;
+       int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing;
+       struct timespec seltv, *tv, now, lastblink, trigger;
+       double timeout;
 
        /* Waiting for window mapping */
        do {
@@ -1857,82 +1941,81 @@ run(void)
        ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd);
        cresize(w, h);
 
-       clock_gettime(CLOCK_MONOTONIC, &last);
-       lastblink = last;
-
-       for (xev = actionfps;;) {
+       for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) {
                FD_ZERO(&rfd);
                FD_SET(ttyfd, &rfd);
                FD_SET(xfd, &rfd);
 
+               if (XPending(xw.dpy))
+                       timeout = 0;  /* existing events might not set xfd */
+
+               seltv.tv_sec = timeout / 1E3;
+               seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec);
+               tv = timeout >= 0 ? &seltv : NULL;
+
                if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) {
                        if (errno == EINTR)
                                continue;
                        die("select failed: %s\n", strerror(errno));
                }
-               if (FD_ISSET(ttyfd, &rfd)) {
-                       ttyread();
-                       if (blinktimeout) {
-                               blinkset = tattrset(ATTR_BLINK);
-                               if (!blinkset)
-                                       MODBIT(win.mode, 0, MODE_BLINK);
-                       }
-               }
+               clock_gettime(CLOCK_MONOTONIC, &now);
 
-               if (FD_ISSET(xfd, &rfd))
-                       xev = actionfps;
+               if (FD_ISSET(ttyfd, &rfd))
+                       ttyread();
 
-               clock_gettime(CLOCK_MONOTONIC, &now);
-               drawtimeout.tv_sec = 0;
-               drawtimeout.tv_nsec =  (1000 * 1E6)/ xfps;
-               tv = &drawtimeout;
-
-               dodraw = 0;
-               if (blinktimeout && TIMEDIFF(now, lastblink) > blinktimeout) {
-                       tsetdirtattr(ATTR_BLINK);
-                       win.mode ^= MODE_BLINK;
-                       lastblink = now;
-                       dodraw = 1;
-               }
-               deltatime = TIMEDIFF(now, last);
-               if (deltatime > 1000 / (xev ? xfps : actionfps)) {
-                       dodraw = 1;
-                       last = now;
+               xev = 0;
+               while (XPending(xw.dpy)) {
+                       xev = 1;
+                       XNextEvent(xw.dpy, &ev);
+                       if (XFilterEvent(&ev, None))
+                               continue;
+                       if (handler[ev.type])
+                               (handler[ev.type])(&ev);
                }
 
-               if (dodraw) {
-                       while (XPending(xw.dpy)) {
-                               XNextEvent(xw.dpy, &ev);
-                               if (XFilterEvent(&ev, None))
-                                       continue;
-                               if (handler[ev.type])
-                                       (handler[ev.type])(&ev);
+               /*
+                * To reduce flicker and tearing, when new content or event
+                * triggers drawing, we first wait a bit to ensure we got
+                * everything, and if nothing new arrives - we draw.
+                * We start with trying to wait minlatency ms. If more content
+                * arrives sooner, we retry with shorter and shorter periods,
+                * and eventually draw even without idle after maxlatency ms.
+                * Typically this results in low latency while interacting,
+                * maximum latency intervals during `cat huge.txt`, and perfect
+                * sync with periodic updates from animations/key-repeats/etc.
+                */
+               if (FD_ISSET(ttyfd, &rfd) || xev) {
+                       if (!drawing) {
+                               trigger = now;
+                               if (IS_SET(MODE_BLINK)) {
+                                       win.mode ^= MODE_BLINK;
+                               }
+                               lastblink = now;
+                               drawing = 1;
                        }
+                       timeout = (maxlatency - TIMEDIFF(now, trigger)) \
+                                 / maxlatency * minlatency;
+                       if (timeout > 0)
+                               continue;  /* we have time, try to find idle */
+               }
 
-                       draw();
-                       XFlush(xw.dpy);
-
-                       if (xev && !FD_ISSET(xfd, &rfd))
-                               xev--;
-                       if (!FD_ISSET(ttyfd, &rfd) && !FD_ISSET(xfd, &rfd)) {
-                               if (blinkset) {
-                                       if (TIMEDIFF(now, lastblink) \
-                                                       > blinktimeout) {
-                                               drawtimeout.tv_nsec = 1000;
-                                       } else {
-                                               drawtimeout.tv_nsec = (1E6 * \
-                                                       (blinktimeout - \
-                                                       TIMEDIFF(now,
-                                                               lastblink)));
-                                       }
-                                       drawtimeout.tv_sec = \
-                                           drawtimeout.tv_nsec / 1E9;
-                                       drawtimeout.tv_nsec %= (long)1E9;
-                               } else {
-                                       tv = NULL;
-                               }
+               /* idle detected or maxlatency exhausted -> draw */
+               timeout = -1;
+               if (blinktimeout && (cursorblinks || tattrset(ATTR_BLINK))) {
+                       timeout = blinktimeout - TIMEDIFF(now, lastblink);
+                       if (timeout <= 0) {
+                               if (-timeout > blinktimeout) /* start visible */
+                                       win.mode |= MODE_BLINK;
+                               win.mode ^= MODE_BLINK;
+                               tsetdirtattr(ATTR_BLINK);
+                               lastblink = now;
+                               timeout = blinktimeout;
                        }
                }
+
+               draw();
+               XFlush(xw.dpy);
+               drawing = 0;
        }
 }
 
@@ -1954,7 +2037,7 @@ main(int argc, char *argv[])
 {
        xw.l = xw.t = 0;
        xw.isfixed = False;
-       win.cursor = cursorshape;
+       xsetcursor(cursorshape);
 
        ARGBEGIN {
        case 'a':