]> git.armaanb.net Git - st.git/blobdiff - x.c
Switch to zenburn theme
[st.git] / x.c
diff --git a/x.c b/x.c
index 96944ee9963dda568dc9d391c1bb848648525578..9b26b96fba538e627738e74a3415b089fd19d73e 100644 (file)
--- a/x.c
+++ b/x.c
@@ -1,24 +1,25 @@
 /* See LICENSE for license details. */
 #include <errno.h>
+#include <math.h>
+#include <limits.h>
 #include <locale.h>
 #include <signal.h>
-#include <stdint.h>
 #include <sys/select.h>
 #include <time.h>
 #include <unistd.h>
 #include <libgen.h>
 #include <X11/Xatom.h>
 #include <X11/Xlib.h>
-#include <X11/Xutil.h>
 #include <X11/cursorfont.h>
 #include <X11/keysym.h>
 #include <X11/Xft/Xft.h>
 #include <X11/XKBlib.h>
 
-static char *argv0;
+char *argv0;
 #include "arg.h"
 #include "st.h"
 #include "win.h"
+#include "hb.h"
 
 /* types used in config.h */
 typedef struct {
@@ -29,9 +30,11 @@ typedef struct {
 } Shortcut;
 
 typedef struct {
-       uint b;
-       uint mask;
-       char *s;
+       uint mod;
+       uint button;
+       void (*func)(const Arg *);
+       const Arg arg;
+       uint  release;
 } MouseShortcut;
 
 typedef struct {
@@ -56,6 +59,7 @@ static void selpaste(const Arg *);
 static void zoom(const Arg *);
 static void zoomabs(const Arg *);
 static void zoomreset(const Arg *);
+static void ttysend(const Arg *);
 
 /* config.h for applying patches and the configuration. */
 #include "config.h"
@@ -75,15 +79,28 @@ typedef XftColor Color;
 typedef XftGlyphFontSpec GlyphFontSpec;
 
 /* Purely graphic info */
+typedef struct {
+       int tw, th; /* tty width and height */
+       int w, h; /* window width and height */
+       int ch; /* char height */
+       int cw; /* char width  */
+       int mode; /* window state/mode flags */
+       int cursor; /* cursor style */
+} TermWindow;
+
 typedef struct {
        Display *dpy;
        Colormap cmap;
        Window win;
        Drawable buf;
        GlyphFontSpec *specbuf; /* font spec buffer used for rendering */
-       Atom xembed, wmdeletewin, netwmname, netwmpid;
-       XIM xim;
-       XIC xic;
+       Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid;
+       struct {
+               XIM xim;
+               XIC xic;
+               XPoint spot;
+               XVaNestedList spotlist;
+       } ime;
        Draw draw;
        Visual *vis;
        XSetWindowAttributes attrs;
@@ -130,17 +147,23 @@ 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 xinit(void);
+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);
 static void xseturgency(int);
-static int x2col(int);
-static int y2row(int);
+static int evcol(XEvent *);
+static int evrow(XEvent *);
 
 static void expose(XEvent *);
 static void visibility(XEvent *);
@@ -149,6 +172,8 @@ 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 *);
 static void bmotion(XEvent *);
@@ -212,8 +237,9 @@ typedef struct {
 } Fontcache;
 
 /* Fontcache is an array now. A new font will be appended to the array. */
-static Fontcache frc[16];
+static Fontcache *frc = NULL;
 static int frclen = 0;
+static int frccap = 0;
 static char *usedfont = NULL;
 static double usedfontsize = 0;
 static double defaultfontsize = 0;
@@ -227,13 +253,16 @@ 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 int cursorblinks = 0;
+
 void
 clipcopy(const Arg *dummy)
 {
        Atom clipboard;
 
-       if (xsel.clipboard != NULL)
-               free(xsel.clipboard);
+       free(xsel.clipboard);
+       xsel.clipboard = NULL;
 
        if (xsel.primary != NULL) {
                xsel.clipboard = xstrdup(xsel.primary);
@@ -295,29 +324,33 @@ zoomreset(const Arg *arg)
        }
 }
 
-int
-x2col(int x)
+void
+ttysend(const Arg *arg)
 {
-       x -= borderpx;
-       x /= win.cw;
-
-       return LIMIT(x, 0, term.col-1);
+       ttywrite(arg->s, strlen(arg->s), 1);
 }
 
 int
-y2row(int y)
+evcol(XEvent *e)
 {
-       y -= borderpx;
-       y /= win.ch;
+       int x = e->xbutton.x - borderpx;
+       LIMIT(x, 0, win.tw - 1);
+       return x / win.cw;
+}
 
-       return LIMIT(y, 0, term.row-1);
+int
+evrow(XEvent *e)
+{
+       int y = e->xbutton.y - borderpx;
+       LIMIT(y, 0, win.th - 1);
+       return y / win.ch;
 }
 
 void
 mousesel(XEvent *e, int done)
 {
        int type, seltype = SEL_REGULAR;
-       uint state = e->xbutton.state & ~(Button1Mask | forceselmod);
+       uint state = e->xbutton.state & ~(Button1Mask | forcemousemod);
 
        for (type = 1; type < LEN(selmasks); ++type) {
                if (match(selmasks[type], state)) {
@@ -325,7 +358,7 @@ mousesel(XEvent *e, int done)
                        break;
                }
        }
-       selextend(x2col(e->xbutton.x), y2row(e->xbutton.y), seltype, done);
+       selextend(evcol(e), evrow(e), seltype, done);
        if (done)
                setsel(getsel(), e->xbutton.time);
 }
@@ -333,9 +366,8 @@ mousesel(XEvent *e, int done)
 void
 mousereport(XEvent *e)
 {
-       int x = x2col(e->xbutton.x), y = y2row(e->xbutton.y),
-           button = e->xbutton.button, state = e->xbutton.state,
-           len;
+       int len, x = evcol(e), y = evrow(e),
+           button = e->xbutton.button, state = e->xbutton.state;
        char buf[40];
        static int ox, oy;
 
@@ -357,7 +389,9 @@ mousereport(XEvent *e)
                        button = 3;
                } else {
                        button -= Button1;
-                       if (button >= 3)
+                       if (button >= 7)
+                               button += 128 - 7;
+                       else if (button >= 3)
                                button += 64 - 3;
                }
                if (e->xbutton.type == ButtonPress) {
@@ -394,25 +428,51 @@ 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, state) ||  /* exact or forced */
+                    match(ms->mod, state & ~forcemousemod))) {
+                       ms->func(&(ms->arg));
+                       return 1;
+               }
+       }
+
+       return 0;
+}
+
 void
 bpress(XEvent *e)
 {
        struct timespec now;
-       MouseShortcut *ms;
        int snap;
 
-       if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) {
+       if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) {
                mousereport(e);
                return;
        }
 
-       for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) {
-               if (e->xbutton.button == ms->b
-                               && match(ms->mask, e->xbutton.state)) {
-                       ttywrite(ms->s, strlen(ms->s), 1);
-                       return;
-               }
-       }
+       if (mouseaction(e, 0))
+               return;
 
        if (e->xbutton.button == Button1) {
                /*
@@ -430,7 +490,7 @@ bpress(XEvent *e)
                xsel.tclick2 = xsel.tclick1;
                xsel.tclick1 = now;
 
-               selstart(x2col(e->xbutton.x), y2row(e->xbutton.y), snap);
+               selstart(evcol(e), evrow(e), snap);
        }
 }
 
@@ -454,18 +514,16 @@ selnotify(XEvent *e)
        ulong nitems, ofs, rem;
        int format;
        uchar *data, *last, *repl;
-       Atom type, incratom, property;
+       Atom type, incratom, property = None;
 
        incratom = XInternAtom(xw.dpy, "INCR", 0);
 
        ofs = 0;
-       if (e->type == SelectionNotify) {
+       if (e->type == SelectionNotify)
                property = e->xselection.property;
-       } else if(e->type == PropertyNotify) {
+       else if (e->type == PropertyNotify)
                property = e->xproperty.atom;
-       } else {
-               return;
-       }
+
        if (property == None)
                return;
 
@@ -610,12 +668,15 @@ selrequest(XEvent *e)
 void
 setsel(char *str, Time t)
 {
+       if (!str)
+               return;
+
        free(xsel.primary);
        xsel.primary = str;
 
        XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t);
        if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win)
-               selclear_(NULL);
+               selclear();
 }
 
 void
@@ -627,21 +688,21 @@ xsetsel(char *str)
 void
 brelease(XEvent *e)
 {
-       if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) {
+       if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) {
                mousereport(e);
                return;
        }
 
-       if (e->xbutton.button == Button2)
-               selpaste(NULL);
-       else if (e->xbutton.button == Button1)
+       if (mouseaction(e, 1))
+               return;
+       if (e->xbutton.button == Button1)
                mousesel(e, 1);
 }
 
 void
 bmotion(XEvent *e)
 {
-       if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) {
+       if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) {
                mousereport(e);
                return;
        }
@@ -661,6 +722,8 @@ cresize(int width, int height)
 
        col = (win.w - 2 * borderpx) / win.cw;
        row = (win.h - 2 * borderpx) / win.ch;
+       col = MAX(1, col);
+       row = MAX(1, row);
 
        tresize(col, row);
        xresize(col, row);
@@ -670,8 +733,8 @@ cresize(int width, int height)
 void
 xresize(int col, int row)
 {
-       win.tw = MAX(1, col * win.cw);
-       win.th = MAX(1, row * win.ch);
+       win.tw = col * win.cw;
+       win.th = row * win.ch;
 
        XFreePixmap(xw.dpy, xw.buf);
        xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h,
@@ -720,20 +783,20 @@ xloadcols(void)
        static int loaded;
        Color *cp;
 
-       dc.collen = MAX(LEN(colorname), 256);
-       dc.col = xmalloc(dc.collen * sizeof(Color));
-
        if (loaded) {
                for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp)
                        XftColorFree(xw.dpy, xw.vis, xw.cmap, cp);
+       } else {
+               dc.collen = MAX(LEN(colorname), 256);
+               dc.col = xmalloc(dc.collen * sizeof(Color));
        }
 
        for (i = 0; i < dc.collen; i++)
                if (!xloadcolor(i, NULL, &dc.col[i])) {
                        if (colorname[i])
-                               die("Could not allocate color '%s'\n", colorname[i]);
+                               die("could not allocate color '%s'\n", colorname[i]);
                        else
-                               die("Could not allocate color %d\n", i);
+                               die("could not allocate color %d\n", i);
                }
        loaded = 1;
 }
@@ -746,7 +809,6 @@ xsetcolorname(int x, const char *name)
        if (!BETWEEN(x, 0, dc.collen))
                return 1;
 
-
        if (!xloadcolor(x, name, &ncolor))
                return 1;
 
@@ -773,19 +835,21 @@ xhints(void)
        XClassHint class = {opt_name ? opt_name : termname,
                            opt_class ? opt_class : termname};
        XWMHints wm = {.flags = InputHint, .input = 1};
-       XSizeHints *sizeh = NULL;
+       XSizeHints *sizeh;
 
        sizeh = XAllocSizeHints();
 
-       sizeh->flags = PSize | PResizeInc | PBaseSize;
+       sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize;
        sizeh->height = win.h;
        sizeh->width = win.w;
        sizeh->height_inc = win.ch;
        sizeh->width_inc = win.cw;
        sizeh->base_height = 2 * borderpx;
        sizeh->base_width = 2 * borderpx;
+       sizeh->min_height = win.ch + 2 * borderpx;
+       sizeh->min_width = win.cw + 2 * borderpx;
        if (xw.isfixed) {
-               sizeh->flags |= PMaxSize | PMinSize;
+               sizeh->flags |= PMaxSize;
                sizeh->min_width = sizeh->max_width = win.w;
                sizeh->min_height = sizeh->max_height = win.h;
        }
@@ -858,7 +922,7 @@ xloadfont(Font *f, FcPattern *pattern)
                if ((XftPatternGetInteger(f->match->pattern, "slant", 0,
                    &haveattr) != XftResultMatch) || haveattr < wantattr) {
                        f->badslant = 1;
-                       fputs("st: font slant does not match\n", stderr);
+                       fputs("font slant does not match\n", stderr);
                }
        }
 
@@ -867,7 +931,7 @@ xloadfont(Font *f, FcPattern *pattern)
                if ((XftPatternGetInteger(f->match->pattern, "weight", 0,
                    &haveattr) != XftResultMatch) || haveattr != wantattr) {
                        f->badweight = 1;
-                       fputs("st: font weight does not match\n", stderr);
+                       fputs("font weight does not match\n", stderr);
                }
        }
 
@@ -890,20 +954,18 @@ xloadfont(Font *f, FcPattern *pattern)
 }
 
 void
-xloadfonts(char *fontstr, double fontsize)
+xloadfonts(const char *fontstr, double fontsize)
 {
        FcPattern *pattern;
        double fontval;
-       float ceilf(float);
 
-       if (fontstr[0] == '-') {
+       if (fontstr[0] == '-')
                pattern = XftXlfdParse(fontstr, False, False);
-       } else {
-               pattern = FcNameParse((FcChar8 *)fontstr);
-       }
+       else
+               pattern = FcNameParse((const FcChar8 *)fontstr);
 
        if (!pattern)
-               die("st: can't open font %s\n", fontstr);
+               die("can't open font %s\n", fontstr);
 
        if (fontsize > 1) {
                FcPatternDel(pattern, FC_PIXEL_SIZE);
@@ -929,7 +991,7 @@ xloadfonts(char *fontstr, double fontsize)
        }
 
        if (xloadfont(&dc.font, pattern))
-               die("st: can't open font %s\n", fontstr);
+               die("can't open font %s\n", fontstr);
 
        if (usedfontsize < 0) {
                FcPatternGetDouble(dc.font.match->pattern,
@@ -946,17 +1008,17 @@ xloadfonts(char *fontstr, double fontsize)
        FcPatternDel(pattern, FC_SLANT);
        FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC);
        if (xloadfont(&dc.ifont, pattern))
-               die("st: can't open font %s\n", fontstr);
+               die("can't open font %s\n", fontstr);
 
        FcPatternDel(pattern, FC_WEIGHT);
        FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD);
        if (xloadfont(&dc.ibfont, pattern))
-               die("st: can't open font %s\n", fontstr);
+               die("can't open font %s\n", fontstr);
 
        FcPatternDel(pattern, FC_SLANT);
        FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN);
        if (xloadfont(&dc.bfont, pattern))
-               die("st: can't open font %s\n", fontstr);
+               die("can't open font %s\n", fontstr);
 
        FcPatternDestroy(pattern);
 }
@@ -973,6 +1035,9 @@ xunloadfont(Font *f)
 void
 xunloadfonts(void)
 {
+       /* Clear Harfbuzz font cache. */
+       hbunloadfonts();
+
        /* Free the loaded fonts in the font cache.  */
        while (frclen > 0)
                XftFontClose(xw.dpy, frc[--frclen].font);
@@ -983,8 +1048,62 @@ xunloadfonts(void)
        xunloadfont(&dc.ibfont);
 }
 
+int
+ximopen(Display *dpy)
+{
+       XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy };
+       XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy };
+
+       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)
+{
+       if (ximopen(dpy))
+               XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
+                                                ximinstantiate, NULL);
+}
+
+void
+ximdestroy(XIM xim, XPointer client, XPointer call)
+{
+       xw.ime.xim = NULL;
+       XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
+                                      ximinstantiate, NULL);
+       XFree(xw.ime.spotlist);
+}
+
+int
+xicdestroy(XIC xim, XPointer client, XPointer call)
+{
+       xw.ime.xic = NULL;
+       return 1;
+}
+
 void
-xinit(void)
+xinit(int cols, int rows)
 {
        XGCValues gcvalues;
        Cursor cursor;
@@ -993,13 +1112,13 @@ xinit(void)
        XColor xmousefg, xmousebg;
 
        if (!(xw.dpy = XOpenDisplay(NULL)))
-               die("Can't open display\n");
+               die("can't open display\n");
        xw.scr = XDefaultScreen(xw.dpy);
        xw.vis = XDefaultVisual(xw.dpy, xw.scr);
 
        /* font */
        if (!FcInit())
-               die("Could not init fontconfig.\n");
+               die("could not init fontconfig.\n");
 
        usedfont = (opt_font == NULL)? font : opt_font;
        xloadfonts(usedfont, 0);
@@ -1009,8 +1128,8 @@ xinit(void)
        xloadcols();
 
        /* adjust fixed window geometry */
-       win.w = 2 * borderpx + term.col * win.cw;
-       win.h = 2 * borderpx + term.row * win.ch;
+       win.w = 2 * borderpx + cols * win.cw;
+       win.h = 2 * borderpx + rows * win.ch;
        if (xw.gm & XNegative)
                xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2;
        if (xw.gm & YNegative)
@@ -1020,7 +1139,7 @@ xinit(void)
        xw.attrs.background_pixel = dc.col[defaultbg].pixel;
        xw.attrs.border_pixel = dc.col[defaultbg].pixel;
        xw.attrs.bit_gravity = NorthWestGravity;
-       xw.attrs.event_mask = FocusChangeMask | KeyPressMask
+       xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask
                | ExposureMask | VisibilityChangeMask | StructureNotifyMask
                | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask;
        xw.attrs.colormap = xw.cmap;
@@ -1042,28 +1161,16 @@ xinit(void)
        XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h);
 
        /* font spec buffer */
-       xw.specbuf = xmalloc(term.col * sizeof(GlyphFontSpec));
+       xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec));
 
        /* Xft rendering context */
        xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap);
 
        /* input methods */
-       if ((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) {
-               XSetLocaleModifiers("@im=local");
-               if ((xw.xim =  XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) {
-                       XSetLocaleModifiers("@im=");
-                       if ((xw.xim = XOpenIM(xw.dpy,
-                                       NULL, NULL, NULL)) == NULL) {
-                               die("XOpenIM failed. Could not open input"
-                                       " device.\n");
-                       }
-               }
+       if (!ximopen(xw.dpy)) {
+               XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
+                                              ximinstantiate, NULL);
        }
-       xw.xic = XCreateIC(xw.xim, XNInputStyle, XIMPreeditNothing
-                                          | XIMStatusNothing, XNClientWindow, xw.win,
-                                          XNFocusWindow, xw.win, NULL);
-       if (xw.xic == NULL)
-               die("XCreateIC failed. Could not obtain input method.\n");
 
        /* white cursor, black outline */
        cursor = XCreateFontCursor(xw.dpy, mouseshape);
@@ -1086,6 +1193,7 @@ xinit(void)
        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);
@@ -1094,8 +1202,8 @@ xinit(void)
 
        win.mode = MODE_NUMLOCK;
        resettitle();
-       XMapWindow(xw.dpy, xw.win);
        xhints();
+       XMapWindow(xw.dpy, xw.win);
        XSync(xw.dpy, False);
 
        clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1);
@@ -1129,7 +1237,7 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
                mode = glyphs[i].mode;
 
                /* Skip dummy wide-character spacing. */
-               if (mode == ATTR_WDUMMY)
+               if (mode & ATTR_WDUMMY)
                        continue;
 
                /* Determine font for glyph if different from previous glyph. */
@@ -1205,13 +1313,10 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
                        fontpattern = FcFontSetMatch(0, fcsets, 1,
                                        fcpattern, &fcres);
 
-                       /*
-                        * Overwrite or create the new cache entry.
-                        */
-                       if (frclen >= LEN(frc)) {
-                               frclen = LEN(frc) - 1;
-                               XftFontClose(xw.dpy, frc[frclen].font);
-                               frc[frclen].unicodep = 0;
+                       /* Allocate memory for the new cache entry. */
+                       if (frclen >= frccap) {
+                               frccap += 16;
+                               frc = xrealloc(frc, frccap * sizeof(Fontcache));
                        }
 
                        frc[frclen].font = XftFontOpenPattern(xw.dpy,
@@ -1239,6 +1344,9 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
                numspecs++;
        }
 
+       /* Harfbuzz transformation for ligatures. */
+       hbtransform(specs, glyphs, len, x, y);
+
        return numspecs;
 }
 
@@ -1337,15 +1445,16 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
        /* Intelligent cleaning up of the borders. */
        if (x == 0) {
                xclear(0, (y == 0)? 0 : winy, borderpx,
-                       winy + win.ch + ((y >= term.row-1)? win.h : 0));
+                       winy + win.ch +
+                       ((winy + win.ch >= borderpx + win.th)? win.h : 0));
        }
-       if (x + charlen >= term.col) {
+       if (winx + width >= borderpx + win.tw) {
                xclear(winx + width, (y == 0)? 0 : winy, win.w,
-                       ((y >= term.row-1)? win.h : (winy + win.ch)));
+                       ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch)));
        }
        if (y == 0)
                xclear(winx, 0, winx + width, borderpx);
-       if (y == term.row-1)
+       if (winy + win.ch >= borderpx + win.th)
                xclear(winx, winy + win.ch, winx + width, win.h);
 
        /* Clean up the region we want to draw to. */
@@ -1387,41 +1496,30 @@ xdrawglyph(Glyph g, int x, int y)
 }
 
 void
-xdrawcursor(void)
+xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len)
 {
-       static int oldx = 0, oldy = 0;
-       int curx;
-       Glyph g = {' ', ATTR_NULL, defaultbg, defaultcs}, og;
        Color drawcol;
 
-       LIMIT(oldx, 0, term.col-1);
-       LIMIT(oldy, 0, term.row-1);
-
-       curx = term.c.x;
-
-       /* adjust position if in dummy */
-       if (term.line[oldy][oldx].mode & ATTR_WDUMMY)
-               oldx--;
-       if (term.line[term.c.y][curx].mode & ATTR_WDUMMY)
-               curx--;
-
        /* remove the old cursor */
-       og = term.line[oldy][oldx];
-       if (selected(oldx, oldy))
+       if (selected(ox, oy))
                og.mode ^= ATTR_REVERSE;
-       xdrawglyph(og, oldx, oldy);
 
-       g.u = term.line[term.c.y][term.c.x].u;
-       g.mode |= term.line[term.c.y][term.c.x].mode &
-                 (ATTR_BOLD | ATTR_ITALIC | ATTR_UNDERLINE | ATTR_STRUCK);
+       /* Redraw the line where cursor was previously.
+        * It will restore the ligatures broken by the cursor. */
+       xdrawline(line, 0, oy, len);
+
+       if (IS_SET(MODE_HIDE))
+               return;
 
        /*
         * Select the right color for the right mode.
         */
+       g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE;
+
        if (IS_SET(MODE_REVERSE)) {
                g.mode |= ATTR_REVERSE;
                g.bg = defaultfg;
-               if (selected(term.c.x, term.c.y)) {
+               if (selected(cx, cy)) {
                        drawcol = dc.col[defaultcs];
                        g.fg = defaultrcs;
                } else {
@@ -1429,64 +1527,75 @@ xdrawcursor(void)
                        g.fg = defaultcs;
                }
        } else {
-               if (selected(term.c.x, term.c.y)) {
-                       drawcol = dc.col[defaultrcs];
+               if (selected(cx, cy)) {
                        g.fg = defaultfg;
                        g.bg = defaultrcs;
                } else {
-                       drawcol = dc.col[defaultcs];
+                       g.fg = defaultbg;
+                       g.bg = defaultcs;
                }
+               drawcol = dc.col[g.bg];
        }
 
-       if (IS_SET(MODE_HIDE))
-               return;
-
        /* draw the new one */
        if (IS_SET(MODE_FOCUSED)) {
                switch (win.cursor) {
-               case 7: /* st extension: snowman */
-                       utf8decode("☃", &g.u, UTF_SIZ);
-               case 0: /* Blinking Block */
-               case 1: /* Blinking Block (Default) */
-               case 2: /* Steady Block */
-                       g.mode |= term.line[term.c.y][curx].mode & ATTR_WIDE;
-                       xdrawglyph(g, term.c.x, term.c.y);
+               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 + curx * win.cw,
-                                       borderpx + (term.c.y + 1) * win.ch - \
+                                       borderpx + cx * win.cw,
+                                       borderpx + (cy + 1) * win.ch - \
                                                cursorthickness,
                                        win.cw, cursorthickness);
                        break;
                case 5: /* Blinking bar */
+                       if (IS_SET(MODE_BLINK))
+                               break;
+                       /* FALLTHROUGH */
                case 6: /* Steady bar */
                        XftDrawRect(xw.draw, &drawcol,
-                                       borderpx + curx * win.cw,
-                                       borderpx + term.c.y * win.ch,
+                                       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 = stcursor;
+                       xdrawglyph(g, cx, cy);
+                       break;
                }
        } else {
                XftDrawRect(xw.draw, &drawcol,
-                               borderpx + curx * win.cw,
-                               borderpx + term.c.y * win.ch,
+                               borderpx + cx * win.cw,
+                               borderpx + cy * win.ch,
                                win.cw - 1, 1);
                XftDrawRect(xw.draw, &drawcol,
-                               borderpx + curx * win.cw,
-                               borderpx + term.c.y * win.ch,
+                               borderpx + cx * win.cw,
+                               borderpx + cy * win.ch,
                                1, win.ch - 1);
                XftDrawRect(xw.draw, &drawcol,
-                               borderpx + (curx + 1) * win.cw - 1,
-                               borderpx + term.c.y * win.ch,
+                               borderpx + (cx + 1) * win.cw - 1,
+                               borderpx + cy * win.ch,
                                1, win.ch - 1);
                XftDrawRect(xw.draw, &drawcol,
-                               borderpx + curx * win.cw,
-                               borderpx + (term.c.y + 1) * win.ch - 1,
+                               borderpx + cx * win.cw,
+                               borderpx + (cy + 1) * win.ch - 1,
                                win.cw, 1);
        }
-       oldx = curx, oldy = term.c.y;
 }
 
 void
@@ -1498,11 +1607,24 @@ xsetenv(void)
        setenv("WINDOWID", buf, 1);
 }
 
+void
+xseticontitle(char *p)
+{
+       XTextProperty prop;
+       DEFAULT(p, opt_title);
+
+       Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle,
+                       &prop);
+       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, "st");
+       DEFAULT(p, opt_title);
 
        Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle,
                        &prop);
@@ -1558,6 +1680,18 @@ xfinishdraw(void)
                                defaultfg : defaultbg].pixel);
 }
 
+void
+xximspot(int x, int y)
+{
+       if (xw.ime.xic == NULL)
+               return;
+
+       xw.ime.spot.x = borderpx + x * win.cw;
+       xw.ime.spot.y = borderpx + (y + 1) * win.ch;
+
+       XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL);
+}
+
 void
 expose(XEvent *ev)
 {
@@ -1597,10 +1731,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;
 }
 
@@ -1632,13 +1768,15 @@ focus(XEvent *ev)
                return;
 
        if (ev->type == FocusIn) {
-               XSetICFocus(xw.xic);
+               if (xw.ime.xic)
+                       XSetICFocus(xw.ime.xic);
                win.mode |= MODE_FOCUSED;
                xseturgency(0);
                if (IS_SET(MODE_FOCUS))
                        ttywrite("\033[I", 3, 0);
        } else {
-               XUnsetICFocus(xw.xic);
+               if (xw.ime.xic)
+                       XUnsetICFocus(xw.ime.xic);
                win.mode &= ~MODE_FOCUSED;
                if (IS_SET(MODE_FOCUS))
                        ttywrite("\033[O", 3, 0);
@@ -1693,7 +1831,7 @@ kpress(XEvent *ev)
 {
        XKeyEvent *e = &ev->xkey;
        KeySym ksym;
-       char buf[32], *customkey;
+       char buf[64], *customkey;
        int len;
        Rune c;
        Status status;
@@ -1702,7 +1840,10 @@ kpress(XEvent *ev)
        if (IS_SET(MODE_KBDLOCK))
                return;
 
-       len = XmbLookupString(xw.xic, e, buf, sizeof buf, &ksym, &status);
+       if (xw.ime.xic)
+               len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status);
+       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)) {
@@ -1735,7 +1876,6 @@ kpress(XEvent *ev)
        ttywrite(buf, len, 1);
 }
 
-
 void
 cmessage(XEvent *e)
 {
@@ -1751,8 +1891,7 @@ cmessage(XEvent *e)
                        win.mode &= ~MODE_FOCUSED;
                }
        } else if (e->xclient.data.l[0] == xw.wmdeletewin) {
-               /* Send SIGHUP to shell */
-               kill(pid, SIGHUP);
+               ttyhangup();
                exit(0);
        }
 }
@@ -1772,9 +1911,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;
-       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 {
@@ -1792,85 +1931,84 @@ run(void)
                }
        } while (ev.type != MapNotify);
 
-       ttynew(opt_line, opt_io, opt_cmd);
+       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(cmdfd, &rfd);
+               FD_SET(ttyfd, &rfd);
                FD_SET(xfd, &rfd);
 
-               if (pselect(MAX(xfd, cmdfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) {
+               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(cmdfd, &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(cmdfd, &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;
        }
 }
 
@@ -1892,7 +2030,7 @@ main(int argc, char *argv[])
 {
        xw.l = xw.t = 0;
        xw.isfixed = False;
-       win.cursor = cursorshape;
+       xsetcursor(cursorstyle);
 
        ARGBEGIN {
        case 'a':
@@ -1932,23 +2070,25 @@ main(int argc, char *argv[])
                opt_embed = EARGF(usage());
                break;
        case 'v':
-               die("%s " VERSION " (c) 2010-2016 st engineers\n", argv0);
+               die("%s " VERSION "\n", argv0);
                break;
        default:
                usage();
        } ARGEND;
 
 run:
-       if (argc > 0) {
-               /* eat all remaining arguments */
+       if (argc > 0) /* eat all remaining arguments */
                opt_cmd = argv;
-               if (!opt_title && !opt_line)
-                       opt_title = basename(xstrdup(argv[0]));
-       }
+
+       if (!opt_title)
+               opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0];
+
        setlocale(LC_CTYPE, "");
        XSetLocaleModifiers("");
-       tnew(MAX(cols, 1), MAX(rows, 1));
-       xinit();
+       cols = MAX(cols, 1);
+       rows = MAX(rows, 1);
+       tnew(cols, rows);
+       xinit(cols, rows);
        xsetenv();
        selinit();
        run();