X-Git-Url: https://git.xinqibao.xyz/st.git/blobdiff_plain/e6dd0f825da9bf68c7642f45afd069500879f5e2..9de853a98da8fe0f458b244970f0e0d3e9b38a50:/st.c?ds=inline

diff --git a/st.c b/st.c
index 4f5cf9e..b052b2b 100644
--- a/st.c
+++ b/st.c
@@ -6,7 +6,6 @@
 #include <locale.h>
 #include <pwd.h>
 #include <stdarg.h>
-#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -58,7 +57,6 @@ char *argv0;
 #define ESC_ARG_SIZ   16
 #define STR_BUF_SIZ   ESC_BUF_SIZ
 #define STR_ARG_SIZ   ESC_ARG_SIZ
-#define DRAW_BUF_SIZ  20*1024
 #define XK_ANY_MOD    UINT_MAX
 #define XK_NO_MOD     0
 #define XK_SWITCH_MOD (1<<13)
@@ -72,6 +70,7 @@ char *argv0;
 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177')
 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
+#define ISDELIM(u) (utf8strchr(worddelimiters, u) != NULL)
 #define LIMIT(x, a, b)    (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
 #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || (a).bg != (b).bg)
 #define IS_SET(flag) ((term.mode & (flag)) != 0)
@@ -86,18 +85,19 @@ char *argv0;
 
 
 enum glyph_attribute {
-	ATTR_NULL      = 0,
-	ATTR_BOLD      = 1 << 0,
-	ATTR_FAINT     = 1 << 1,
-	ATTR_ITALIC    = 1 << 2,
-	ATTR_UNDERLINE = 1 << 3,
-	ATTR_BLINK     = 1 << 4,
-	ATTR_REVERSE   = 1 << 5,
-	ATTR_INVISIBLE = 1 << 6,
-	ATTR_STRUCK    = 1 << 7,
-	ATTR_WRAP      = 1 << 8,
-	ATTR_WIDE      = 1 << 9,
-	ATTR_WDUMMY    = 1 << 10,
+	ATTR_NULL       = 0,
+	ATTR_BOLD       = 1 << 0,
+	ATTR_FAINT      = 1 << 1,
+	ATTR_ITALIC     = 1 << 2,
+	ATTR_UNDERLINE  = 1 << 3,
+	ATTR_BLINK      = 1 << 4,
+	ATTR_REVERSE    = 1 << 5,
+	ATTR_INVISIBLE  = 1 << 6,
+	ATTR_STRUCK     = 1 << 7,
+	ATTR_WRAP       = 1 << 8,
+	ATTR_WIDE       = 1 << 9,
+	ATTR_WDUMMY     = 1 << 10,
+	ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
 };
 
 enum cursor_movement {
@@ -158,8 +158,13 @@ enum escape_state {
 
 enum window_state {
 	WIN_VISIBLE = 1,
-	WIN_REDRAW  = 2,
-	WIN_FOCUSED = 4
+	WIN_FOCUSED = 2
+};
+
+enum selection_mode {
+	SEL_IDLE = 0,
+	SEL_EMPTY = 1,
+	SEL_READY = 2
 };
 
 enum selection_type {
@@ -177,11 +182,13 @@ typedef unsigned int uint;
 typedef unsigned long ulong;
 typedef unsigned short ushort;
 
+typedef uint_least32_t Rune;
+
 typedef XftDraw *Draw;
 typedef XftColor Color;
 
 typedef struct {
-	char c[UTF_SIZ]; /* character code */
+	Rune u;           /* character code */
 	ushort mode;      /* attribute flags */
 	uint32_t fg;      /* foreground  */
 	uint32_t bg;      /* background  */
@@ -223,7 +230,8 @@ typedef struct {
 	int col;      /* nb col */
 	Line *line;   /* screen */
 	Line *alt;    /* alternate screen */
-	bool *dirty;  /* dirtyness of lines */
+	int *dirty;  /* dirtyness of lines */
+	XftGlyphFontSpec *specbuf; /* font spec buffer used for rendering */
 	TCursor c;    /* cursor */
 	int top;      /* top    scroll limit */
 	int bot;      /* bottom scroll limit */
@@ -232,8 +240,8 @@ typedef struct {
 	char trantbl[4]; /* charset table translation */
 	int charset;  /* current charset */
 	int icharset; /* selected charset for sequence */
-	bool numlock; /* lock numbers in keyboard */
-	bool *tabs;
+	int numlock; /* lock numbers in keyboard */
+	int *tabs;
 } Term;
 
 /* Purely graphic info */
@@ -249,7 +257,7 @@ typedef struct {
 	Visual *vis;
 	XSetWindowAttributes attrs;
 	int scr;
-	bool isfixed; /* is fixed geometry? */
+	int isfixed; /* is fixed geometry? */
 	int l, t; /* left and top offset */
 	int gm; /* geometry mask */
 	int tw, th; /* tty width and height */
@@ -293,7 +301,7 @@ typedef struct {
 
 	char *primary, *clipboard;
 	Atom xtarget;
-	bool alt;
+	int alt;
 	struct timespec tclick1;
 	struct timespec tclick2;
 } Selection;
@@ -352,6 +360,7 @@ static void draw(void);
 static void redraw(void);
 static void drawregion(int, int, int, int);
 static void execsh(void);
+static void stty(void);
 static void sigchld(int);
 static void run(void);
 
@@ -382,25 +391,25 @@ static void tmoveato(int, int);
 static void tnew(int, int);
 static void tnewline(int);
 static void tputtab(int);
-static void tputc(char *, int);
+static void tputc(Rune);
 static void treset(void);
 static void tresize(int, int);
 static void tscrollup(int, int);
 static void tscrolldown(int, int);
 static void tsetattr(int *, int);
-static void tsetchar(char *, Glyph *, int, int);
+static void tsetchar(Rune, Glyph *, int, int);
 static void tsetscroll(int, int);
 static void tswapscreen(void);
 static void tsetdirt(int, int);
 static void tsetdirtattr(int);
-static void tsetmode(bool, bool, int *, int);
+static void tsetmode(int, int, int *, int);
 static void tfulldirt(void);
-static void techo(char *, int);
+static void techo(Rune);
 static void tcontrolcode(uchar );
 static void tdectest(char );
 static int32_t tdefcolor(int *, int *, int);
 static void tdeftran(char);
-static inline bool match(uint, uint);
+static inline int match(uint, uint);
 static void ttynew(void);
 static void ttyread(void);
 static void ttyresize(void);
@@ -409,7 +418,9 @@ static void ttywrite(const char *, size_t);
 static void tstrsequence(uchar);
 
 static inline ushort sixd_to_16bit(int);
-static void xdraws(char *, Glyph, int, int, int, int);
+static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int);
+static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int);
+static void xdrawglyph(Glyph, int, int);
 static void xhints(void);
 static void xclear(int, int, int, int);
 static void xdrawcursor(void);
@@ -423,7 +434,7 @@ static void xsettitle(char *);
 static void xresettitle(void);
 static void xsetpointermotion(int);
 static void xseturgency(int);
-static void xsetsel(char *);
+static void xsetsel(char *, Time);
 static void xtermclear(int, int, int, int);
 static void xunloadfont(Font *);
 static void xunloadfonts(void);
@@ -447,22 +458,22 @@ static void selrequest(XEvent *);
 
 static void selinit(void);
 static void selnormalize(void);
-static inline bool selected(int, int);
+static inline int selected(int, int);
 static char *getsel(void);
-static void selcopy(void);
+static void selcopy(Time);
 static void selscroll(int, int);
-static void selsnap(int, int *, int *, int);
+static void selsnap(int *, int *, int);
 static int x2col(int);
 static int y2row(int);
 static void getbuttoninfo(XEvent *);
 static void mousereport(XEvent *);
 
-static size_t utf8decode(char *, long *, size_t);
-static long utf8decodebyte(char, size_t *);
-static size_t utf8encode(long, char *, size_t);
-static char utf8encodebyte(long, size_t);
-static size_t utf8len(char *);
-static size_t utf8validate(long *, size_t);
+static size_t utf8decode(char *, Rune *, size_t);
+static Rune utf8decodebyte(char, size_t *);
+static size_t utf8encode(Rune, char *);
+static char utf8encodebyte(Rune, size_t);
+static char *utf8strchr(char *s, Rune u);
+static size_t utf8validate(Rune *, size_t);
 
 static ssize_t xwrite(int, const char *, size_t);
 static void *xmalloc(size_t);
@@ -501,13 +512,14 @@ static STREscape strescseq;
 static int cmdfd;
 static pid_t pid;
 static Selection sel;
-static int iofd = STDOUT_FILENO;
+static int iofd = 1;
 static char **opt_cmd = NULL;
 static char *opt_io = NULL;
 static char *opt_title = NULL;
 static char *opt_embed = NULL;
 static char *opt_class = NULL;
 static char *opt_font = NULL;
+static char *opt_line = NULL;
 static int oldbutton = 3; /* button event on startup: 3 = release */
 
 static char *usedfont = NULL;
@@ -516,8 +528,8 @@ static double defaultfontsize = 0;
 
 static uchar utfbyte[UTF_SIZ + 1] = {0x80,    0, 0xC0, 0xE0, 0xF0};
 static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
-static long utfmin[UTF_SIZ + 1] = {       0,    0,  0x80,  0x800,  0x10000};
-static long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
+static Rune utfmin[UTF_SIZ + 1] = {       0,    0,  0x80,  0x800,  0x10000};
+static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
 
 /* Font Ring Cache */
 enum {
@@ -530,7 +542,7 @@ enum {
 typedef struct {
 	XftFont *font;
 	int flags;
-	long unicodep;
+	Rune unicodep;
 } Fontcache;
 
 /* Fontcache is an array now. A new font will be appended to the array. */
@@ -578,9 +590,9 @@ xstrdup(char *s) {
 }
 
 size_t
-utf8decode(char *c, long *u, size_t clen) {
+utf8decode(char *c, Rune *u, size_t clen) {
 	size_t i, j, len, type;
-	long udecoded;
+	Rune udecoded;
 
 	*u = UTF_INVALID;
 	if(!clen)
@@ -600,7 +612,7 @@ utf8decode(char *c, long *u, size_t clen) {
 	return len;
 }
 
-long
+Rune
 utf8decodebyte(char c, size_t *i) {
 	for(*i = 0; *i < LEN(utfmask); ++(*i))
 		if(((uchar)c & utfmask[*i]) == utfbyte[*i])
@@ -609,11 +621,11 @@ utf8decodebyte(char c, size_t *i) {
 }
 
 size_t
-utf8encode(long u, char *c, size_t clen) {
+utf8encode(Rune u, char *c) {
 	size_t len, i;
 
 	len = utf8validate(&u, 0);
-	if(clen < len)
+	if(len > UTF_SIZ)
 		return 0;
 	for(i = len - 1; i != 0; --i) {
 		c[i] = utf8encodebyte(u, 0);
@@ -624,17 +636,27 @@ utf8encode(long u, char *c, size_t clen) {
 }
 
 char
-utf8encodebyte(long u, size_t i) {
+utf8encodebyte(Rune u, size_t i) {
 	return utfbyte[i] | (u & ~utfmask[i]);
 }
 
-size_t
-utf8len(char *c) {
-	return utf8decode(c, &(long){0}, UTF_SIZ);
+char *
+utf8strchr(char *s, Rune u) {
+	Rune r;
+	size_t i, j, len;
+
+	len = strlen(s);
+	for(i = 0, j = 0; i < len; i += j) {
+		if(!(j = utf8decode(&s[i], &r, len - i)))
+			break;
+		if(r == u)
+			return &(s[i]);
+	}
+	return NULL;
 }
 
 size_t
-utf8validate(long *u, size_t i) {
+utf8validate(Rune *u, size_t i) {
 	if(!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
 		*u = UTF_INVALID;
 	for(i = 1; *u > utfmax[i]; ++i)
@@ -646,7 +668,7 @@ void
 selinit(void) {
 	memset(&sel.tclick1, 0, sizeof(sel.tclick1));
 	memset(&sel.tclick2, 0, sizeof(sel.tclick2));
-	sel.mode = 0;
+	sel.mode = SEL_IDLE;
 	sel.ob.x = -1;
 	sel.primary = NULL;
 	sel.clipboard = NULL;
@@ -671,13 +693,14 @@ y2row(int y) {
 	return LIMIT(y, 0, term.row-1);
 }
 
-int tlinelen(int y) {
+int
+tlinelen(int y) {
 	int i = term.col;
 
 	if(term.line[y][i - 1].mode & ATTR_WRAP)
 		return i;
 
-	while(i > 0 && term.line[y][i - 1].c[0] == ' ')
+	while(i > 0 && term.line[y][i - 1].u == ' ')
 		--i;
 
 	return i;
@@ -687,18 +710,18 @@ void
 selnormalize(void) {
 	int i;
 
-	if(sel.ob.y == sel.oe.y || sel.type == SEL_RECTANGULAR) {
-		sel.nb.x = MIN(sel.ob.x, sel.oe.x);
-		sel.ne.x = MAX(sel.ob.x, sel.oe.x);
-	} else {
+	if(sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
 		sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
 		sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
+	} else {
+		sel.nb.x = MIN(sel.ob.x, sel.oe.x);
+		sel.ne.x = MAX(sel.ob.x, sel.oe.x);
 	}
 	sel.nb.y = MIN(sel.ob.y, sel.oe.y);
 	sel.ne.y = MAX(sel.ob.y, sel.oe.y);
 
-	selsnap(sel.snap, &sel.nb.x, &sel.nb.y, -1);
-	selsnap(sel.snap, &sel.ne.x, &sel.ne.y, +1);
+	selsnap(&sel.nb.x, &sel.nb.y, -1);
+	selsnap(&sel.ne.x, &sel.ne.y, +1);
 
 	/* expand selection over line breaks */
 	if (sel.type == SEL_RECTANGULAR)
@@ -710,8 +733,11 @@ selnormalize(void) {
 		sel.ne.x = term.col - 1;
 }
 
-bool
+int
 selected(int x, int y) {
+	if(sel.mode == SEL_EMPTY)
+		return 0;
+
 	if(sel.type == SEL_RECTANGULAR)
 		return BETWEEN(y, sel.nb.y, sel.ne.y)
 		    && BETWEEN(x, sel.nb.x, sel.ne.x);
@@ -722,19 +748,19 @@ selected(int x, int y) {
 }
 
 void
-selsnap(int mode, int *x, int *y, int direction) {
+selsnap(int *x, int *y, int direction) {
 	int newx, newy, xt, yt;
-	bool delim, prevdelim;
+	int delim, prevdelim;
 	Glyph *gp, *prevgp;
 
-	switch(mode) {
+	switch(sel.snap) {
 	case SNAP_WORD:
 		/*
 		 * Snap around if the word wraps around at the end or
 		 * beginning of a line.
 		 */
 		prevgp = &term.line[*y][*x];
-		prevdelim = strchr(worddelimiters, prevgp->c[0]) != NULL;
+		prevdelim = ISDELIM(prevgp->u);
 		for(;;) {
 			newx = *x + direction;
 			newy = *y;
@@ -756,9 +782,9 @@ selsnap(int mode, int *x, int *y, int direction) {
 				break;
 
 			gp = &term.line[newy][newx];
-			delim = strchr(worddelimiters, gp->c[0]) != NULL;
+			delim = ISDELIM(gp->u);
 			if(!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
-					|| (delim && gp->c[0] != prevgp->c[0])))
+					|| (delim && gp->u != prevgp->u)))
 				break;
 
 			*x = newx;
@@ -774,15 +800,15 @@ selsnap(int mode, int *x, int *y, int direction) {
 		 * previous line will be selected.
 		 */
 		*x = (direction < 0) ? 0 : term.col - 1;
-		if(direction < 0 && *y > 0) {
+		if(direction < 0) {
 			for(; *y > 0; *y += direction) {
 				if(!(term.line[*y-1][term.col-1].mode
 						& ATTR_WRAP)) {
 					break;
 				}
 			}
-		} else if(direction > 0 && *y < term.row-1) {
-			for(; *y < term.row; *y += direction) {
+		} else if(direction > 0) {
+			for(; *y < term.row-1; *y += direction) {
 				if(!(term.line[*y][term.col-1].mode
 						& ATTR_WRAP)) {
 					break;
@@ -857,12 +883,11 @@ mousereport(XEvent *e) {
 	}
 
 	if(!IS_SET(MODE_MOUSEX10)) {
-		button += (state & ShiftMask   ? 4  : 0)
-			+ (state & Mod4Mask    ? 8  : 0)
-			+ (state & ControlMask ? 16 : 0);
+		button += ((state & ShiftMask  ) ? 4  : 0)
+			+ ((state & Mod4Mask   ) ? 8  : 0)
+			+ ((state & ControlMask) ? 16 : 0);
 	}
 
-	len = 0;
 	if(IS_SET(MODE_MOUSESGR)) {
 		len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c",
 				button, x+1, y+1,
@@ -900,7 +925,7 @@ bpress(XEvent *e) {
 
 		/* Clear previous selection, logically and visually. */
 		selclear(NULL);
-		sel.mode = 1;
+		sel.mode = SEL_EMPTY;
 		sel.type = SEL_REGULAR;
 		sel.oe.x = sel.ob.x = x2col(e->xbutton.x);
 		sel.oe.y = sel.ob.y = y2row(e->xbutton.y);
@@ -918,14 +943,9 @@ bpress(XEvent *e) {
 		}
 		selnormalize();
 
-		/*
-		 * Draw selection, unless it's regular and we don't want to
-		 * make clicks visible
-		 */
-		if(sel.snap != 0) {
-			sel.mode++;
-			tsetdirt(sel.nb.y, sel.ne.y);
-		}
+		if(sel.snap != 0)
+			sel.mode = SEL_READY;
+		tsetdirt(sel.nb.y, sel.ne.y);
 		sel.tclick2 = sel.tclick1;
 		sel.tclick1 = now;
 	}
@@ -934,7 +954,7 @@ bpress(XEvent *e) {
 char *
 getsel(void) {
 	char *str, *ptr;
-	int y, bufsize, size, lastx, linelen;
+	int y, bufsize, lastx, linelen;
 	Glyph *gp, *last;
 
 	if(sel.ob.x == -1)
@@ -955,16 +975,14 @@ getsel(void) {
 			lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
 		}
 		last = &term.line[y][MIN(lastx, linelen-1)];
-		while(last >= gp && last->c[0] == ' ')
+		while(last >= gp && last->u == ' ')
 			--last;
 
 		for( ; gp <= last; ++gp) {
 			if(gp->mode & ATTR_WDUMMY)
 				continue;
 
-			size = utf8len(gp->c);
-			memcpy(ptr, gp->c, size);
-			ptr += size;
+			ptr += utf8encode(gp->u, ptr);
 		}
 
 		/*
@@ -984,8 +1002,8 @@ getsel(void) {
 }
 
 void
-selcopy(void) {
-	xsetsel(getsel());
+selcopy(Time t) {
+	xsetsel(getsel(), t);
 }
 
 void
@@ -997,7 +1015,7 @@ selnotify(XEvent *e) {
 	XSelectionEvent *xsev;
 
 	ofs = 0;
-	xsev = (XSelectionEvent *)e;
+	xsev = &e->xselection;
 	if (xsev->property == None)
 	    return;
 	do {
@@ -1066,6 +1084,7 @@ void
 selclear(XEvent *e) {
 	if(sel.ob.x == -1)
 		return;
+	sel.mode = SEL_IDLE;
 	sel.ob.x = -1;
 	tsetdirt(sel.nb.y, sel.ne.y);
 }
@@ -1083,6 +1102,9 @@ selrequest(XEvent *e) {
 	xev.selection = xsre->selection;
 	xev.target = xsre->target;
 	xev.time = xsre->time;
+	if (xsre->property == None)
+		xsre->property = xsre->target;
+
 	/* reject */
 	xev.property = None;
 
@@ -1120,16 +1142,18 @@ selrequest(XEvent *e) {
 	}
 
 	/* all done, send a notification to the listener */
-	if(!XSendEvent(xsre->display, xsre->requestor, True, 0, (XEvent *) &xev))
+	if(!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev))
 		fprintf(stderr, "Error sending SelectionNotify event\n");
 }
 
 void
-xsetsel(char *str) {
+xsetsel(char *str, Time t) {
 	free(sel.primary);
 	sel.primary = str;
 
-	XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, CurrentTime);
+	XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t);
+	if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win)
+		selclear(0);
 }
 
 void
@@ -1142,13 +1166,12 @@ brelease(XEvent *e) {
 	if(e->xbutton.button == Button2) {
 		selpaste(NULL);
 	} else if(e->xbutton.button == Button1) {
-		if(sel.mode < 2) {
-			selclear(NULL);
-		} else {
+		if(sel.mode == SEL_READY) {
 			getbuttoninfo(e);
-			selcopy();
-		}
-		sel.mode = 0;
+			selcopy(e->xbutton.time);
+		} else
+			selclear(NULL);
+		sel.mode = SEL_IDLE;
 		tsetdirt(sel.nb.y, sel.ne.y);
 	}
 }
@@ -1165,7 +1188,7 @@ bmotion(XEvent *e) {
 	if(!sel.mode)
 		return;
 
-	sel.mode++;
+	sel.mode = SEL_READY;
 	oldey = sel.oe.y;
 	oldex = sel.oe.x;
 	oldsby = sel.nb.y;
@@ -1183,7 +1206,7 @@ die(const char *errstr, ...) {
 	va_start(ap, errstr);
 	vfprintf(stderr, errstr, ap);
 	va_end(ap);
-	exit(EXIT_FAILURE);
+	exit(1);
 }
 
 void
@@ -1232,20 +1255,48 @@ execsh(void) {
 	signal(SIGALRM, SIG_DFL);
 
 	execvp(prog, args);
-	_exit(EXIT_FAILURE);
+	_exit(1);
 }
 
 void
 sigchld(int a) {
-	int stat, ret;
+	int stat;
+	pid_t p;
 
-	if(waitpid(pid, &stat, 0) < 0)
+	if((p = waitpid(pid, &stat, WNOHANG)) < 0)
 		die("Waiting for pid %hd failed: %s\n", pid, strerror(errno));
 
-	ret = WIFEXITED(stat) ? WEXITSTATUS(stat) : EXIT_FAILURE;
-	if (ret != EXIT_SUCCESS)
+	if(pid != p)
+		return;
+
+	if (!WIFEXITED(stat) || WEXITSTATUS(stat))
 		die("child finished with error '%d'\n", stat);
-	exit(EXIT_SUCCESS);
+	exit(0);
+}
+
+
+void
+stty(void)
+{
+	char cmd[_POSIX_ARG_MAX], **p, *q, *s;
+	size_t n, siz;
+
+	if((n = strlen(stty_args)) > sizeof(cmd)-1)
+		die("incorrect stty parameters\n");
+	memcpy(cmd, stty_args, n);
+	q = cmd + n;
+	siz = sizeof(cmd) - n;
+	for(p = opt_cmd; p && (s = *p); ++p) {
+		if((n = strlen(s)) > siz-1)
+			die("stty parameter length too long\n");
+		*q++ = ' ';
+		q = memcpy(q, s, n);
+		q += n;
+		siz-= n + 1;
+	}
+	*q = '\0';
+	if (system(cmd) != 0)
+	    perror("Couldn't call stty");
 }
 
 void
@@ -1253,6 +1304,25 @@ ttynew(void) {
 	int m, s;
 	struct winsize w = {term.row, term.col, 0, 0};
 
+	if(opt_io) {
+		term.mode |= MODE_PRINT;
+		iofd = (!strcmp(opt_io, "-")) ?
+			  1 : open(opt_io, O_WRONLY | O_CREAT, 0666);
+		if(iofd < 0) {
+			fprintf(stderr, "Error opening %s:%s\n",
+				opt_io, strerror(errno));
+		}
+	}
+
+	if (opt_line) {
+		if((cmdfd = open(opt_line, O_RDWR)) < 0)
+			die("open line failed: %s\n", strerror(errno));
+		close(0);
+		dup(cmdfd);
+		stty();
+		return;
+	}
+
 	/* seems to work fine on linux, openbsd and freebsd */
 	if(openpty(&m, &s, NULL, NULL, &w) < 0)
 		die("openpty failed: %s\n", strerror(errno));
@@ -1262,10 +1332,11 @@ ttynew(void) {
 		die("fork failed\n");
 		break;
 	case 0:
+		close(iofd);
 		setsid(); /* create a new process group */
-		dup2(s, STDIN_FILENO);
-		dup2(s, STDOUT_FILENO);
-		dup2(s, STDERR_FILENO);
+		dup2(s, 0);
+		dup2(s, 1);
+		dup2(s, 2);
 		if(ioctl(s, TIOCSCTTY, NULL) < 0)
 			die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
 		close(s);
@@ -1276,16 +1347,6 @@ ttynew(void) {
 		close(s);
 		cmdfd = m;
 		signal(SIGCHLD, sigchld);
-		if(opt_io) {
-			term.mode |= MODE_PRINT;
-			iofd = (!strcmp(opt_io, "-")) ?
-				  STDOUT_FILENO :
-				  open(opt_io, O_WRONLY | O_CREAT, 0666);
-			if(iofd < 0) {
-				fprintf(stderr, "Error opening %s:%s\n",
-					opt_io, strerror(errno));
-			}
-		}
 		break;
 	}
 }
@@ -1295,9 +1356,8 @@ ttyread(void) {
 	static char buf[BUFSIZ];
 	static int buflen = 0;
 	char *ptr;
-	char s[UTF_SIZ];
 	int charsize; /* size of utf8 char in bytes */
-	long unicodep;
+	Rune unicodep;
 	int ret;
 
 	/* append read bytes to unprocessed bytes */
@@ -1308,8 +1368,7 @@ ttyread(void) {
 	buflen += ret;
 	ptr = buf;
 	while((charsize = utf8decode(ptr, &unicodep, buflen))) {
-		utf8encode(unicodep, s, UTF_SIZ);
-		tputc(s, charsize);
+		tputc(unicodep);
 		ptr += charsize;
 		buflen -= charsize;
 	}
@@ -1326,9 +1385,16 @@ ttywrite(const char *s, size_t n) {
 
 void
 ttysend(char *s, size_t n) {
+	int len;
+	Rune u;
+
 	ttywrite(s, n);
 	if(IS_SET(MODE_ECHO))
-		techo(s, n);
+		while((len = utf8decode(s, &u, n)) > 0) {
+			techo(u);
+			n -= len;
+			s += len;
+		}
 }
 
 void
@@ -1390,7 +1456,7 @@ tfulldirt(void) {
 void
 tcursor(int mode) {
 	static TCursor c[2];
-	bool alt = IS_SET(MODE_ALTSCREEN);
+	int alt = IS_SET(MODE_ALTSCREEN);
 
 	if(mode == CURSOR_SAVE) {
 		c[alt] = term.c;
@@ -1416,7 +1482,7 @@ treset(void) {
 	term.top = 0;
 	term.bot = term.row - 1;
 	term.mode = MODE_WRAP;
-	memset(term.trantbl, sizeof(term.trantbl), CS_USA);
+	memset(term.trantbl, CS_USA, sizeof(term.trantbl));
 	term.charset = 0;
 
 	for(i = 0; i < 2; i++) {
@@ -1577,7 +1643,7 @@ tmoveto(int x, int y) {
 }
 
 void
-tsetchar(char *c, Glyph *attr, int x, int y) {
+tsetchar(Rune u, Glyph *attr, int x, int y) {
 	static char *vt100_0[62] = { /* 0x41 - 0x7e */
 		"↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
 		0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
@@ -1592,25 +1658,23 @@ tsetchar(char *c, Glyph *attr, int x, int y) {
 	/*
 	 * The table is proudly stolen from rxvt.
 	 */
-	if(term.trantbl[term.charset] == CS_GRAPHIC0) {
-		if(BETWEEN(c[0], 0x41, 0x7e) && vt100_0[c[0] - 0x41]) {
-			c = vt100_0[c[0] - 0x41];
-		}
-	}
+	if(term.trantbl[term.charset] == CS_GRAPHIC0 &&
+	   BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
+		utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
 
 	if(term.line[y][x].mode & ATTR_WIDE) {
 		if(x+1 < term.col) {
-			term.line[y][x+1].c[0] = ' ';
+			term.line[y][x+1].u = ' ';
 			term.line[y][x+1].mode &= ~ATTR_WDUMMY;
 		}
 	} else if(term.line[y][x].mode & ATTR_WDUMMY) {
-		term.line[y][x-1].c[0] = ' ';
+		term.line[y][x-1].u = ' ';
 		term.line[y][x-1].mode &= ~ATTR_WIDE;
 	}
 
 	term.dirty[y] = 1;
 	term.line[y][x] = *attr;
-	memcpy(term.line[y][x].c, c, UTF_SIZ);
+	term.line[y][x].u = u;
 }
 
 void
@@ -1637,7 +1701,7 @@ tclearregion(int x1, int y1, int x2, int y2) {
 			gp->fg = term.c.attr.fg;
 			gp->bg = term.c.attr.bg;
 			gp->mode = 0;
-			memcpy(gp->c, " ", 2);
+			gp->u = ' ';
 		}
 	}
 }
@@ -1704,7 +1768,7 @@ tdefcolor(int *attr, int *npar, int l) {
 		b = attr[*npar + 4];
 		*npar += 4;
 		if(!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
-			fprintf(stderr, "erresc: bad rgb color (%d,%d,%d)\n",
+			fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
 				r, g, b);
 		else
 			idx = TRUECOLOR(r, g, b);
@@ -1851,9 +1915,9 @@ tsetscroll(int t, int b) {
 }
 
 void
-tsetmode(bool priv, bool set, int *args, int narg) {
+tsetmode(int priv, int set, int *args, int narg) {
 	int *lim, mode;
-	bool alt;
+	int alt;
 
 	for(lim = args + narg; args < lim; ++args) {
 		if(priv) {
@@ -2226,8 +2290,7 @@ strhandle(void) {
 
 	term.esc &= ~(ESC_STR_END|ESC_STR);
 	strparse();
-	narg = strescseq.narg;
-	par = atoi(strescseq.args[0]);
+	par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
 
 	switch(strescseq.type) {
 	case ']': /* OSC -- Operating System Command */
@@ -2358,13 +2421,14 @@ tdumpsel(void) {
 
 void
 tdumpline(int n) {
+	char buf[UTF_SIZ];
 	Glyph *bp, *end;
 
 	bp = &term.line[n][0];
 	end = &bp[MIN(tlinelen(n), term.col) - 1];
-	if(bp != end || bp->c[0] != ' ') {
+	if(bp != end || bp->u != ' ') {
 		for( ;bp <= end; ++bp)
-			tprinter(bp->c, utf8len(bp->c));
+			tprinter(buf, utf8encode(bp->u, buf));
 	}
 	tprinter("\n", 1);
 }
@@ -2390,30 +2454,22 @@ tputtab(int n) {
 			for(--x; x > 0 && !term.tabs[x]; --x)
 				/* nothing */ ;
 	}
-	tmoveto(x, term.c.y);
+	term.c.x = LIMIT(x, 0, term.col-1);
 }
 
 void
-techo(char *buf, int len) {
-	for(; len > 0; buf++, len--) {
-		char c = *buf;
-
-		if(ISCONTROL((uchar) c)) { /* control code */
-			if(c & 0x80) {
-				c &= 0x7f;
-				tputc("^", 1);
-				tputc("[", 1);
-			} else if(c != '\n' && c != '\r' && c != '\t') {
-				c ^= 0x40;
-				tputc("^", 1);
-			}
-			tputc(&c, 1);
-		} else {
-			break;
+techo(Rune u) {
+	if(ISCONTROL(u)) { /* control code */
+		if(u & 0x80) {
+			u &= 0x7f;
+			tputc('^');
+			tputc('[');
+		} else if(u != '\n' && u != '\r' && u != '\t') {
+			u ^= 0x40;
+			tputc('^');
 		}
 	}
-	if(len)
-		tputc(buf, len);
+	tputc(u);
 }
 
 void
@@ -2431,13 +2487,12 @@ tdeftran(char ascii) {
 
 void
 tdectest(char c) {
-	static char E[UTF_SIZ] = "E";
 	int x, y;
 
 	if(c == '8') { /* DEC screen alignment test. */
 		for(x = 0; x < term.col; ++x) {
 			for(y = 0; y < term.row; ++y)
-				tsetchar(E, &term.c.attr, x, y);
+				tsetchar('E', &term.c.attr, x, y);
 		}
 	}
 }
@@ -2461,13 +2516,10 @@ tstrsequence(uchar c) {
 	strreset();
 	strescseq.type = c;
 	term.esc |= ESC_STR;
-	return;
 }
 
 void
 tcontrolcode(uchar ascii) {
-	static char question[UTF_SIZ] = "?";
-
 	switch(ascii) {
 	case '\t':   /* HT */
 		tputtab(1);
@@ -2505,7 +2557,7 @@ tcontrolcode(uchar ascii) {
 		term.charset = 1 - (ascii - '\016');
 		return;
 	case '\032': /* SUB */
-		tsetchar(question, &term.c.attr, term.c.x, term.c.y);
+		tsetchar('?', &term.c.attr, term.c.x, term.c.y);
 	case '\030': /* CAN */
 		csireset();
 		break;
@@ -2543,7 +2595,6 @@ tcontrolcode(uchar ascii) {
 	}
 	/* only CAN, SUB, \a and C1 chars interrupt a sequence */
 	term.esc &= ~(ESC_STR_END|ESC_STR);
-	return;
 }
 
 /*
@@ -2630,28 +2681,21 @@ eschandle(uchar ascii) {
 }
 
 void
-tputc(char *c, int len) {
-	uchar ascii;
-	bool control;
-	long unicodep;
-	int width;
+tputc(Rune u) {
+	char c[UTF_SIZ];
+	int control;
+	int width, len;
 	Glyph *gp;
 
-	if(len == 1) {
+	len = utf8encode(u, c);
+	if((width = wcwidth(u)) == -1) {
+		memcpy(c, "\357\277\275", 4); /* UTF_INVALID */
 		width = 1;
-		unicodep = ascii = *c;
-	} else {
-		utf8decode(c, &unicodep, UTF_SIZ);
-		if ((width = wcwidth(unicodep)) == -1) {
-			c = "\357\277\275";	/* UTF_INVALID */
-			width = 1;
-		}
-		ascii = unicodep;
 	}
 
 	if(IS_SET(MODE_PRINT))
 		tprinter(c, len);
-	control = ISCONTROL(unicodep);
+	control = ISCONTROL(u);
 
 	/*
 	 * STR sequence must be checked before anything else
@@ -2660,10 +2704,8 @@ tputc(char *c, int len) {
 	 * character.
 	 */
 	if(term.esc & ESC_STR) {
-		if(len == 1 &&
-		   (ascii == '\a' || ascii == 030 ||
-		    ascii == 032  || ascii == 033 ||
-		    ISCONTROLC1(unicodep))) {
+		if(u == '\a' || u == 030 || u == 032 || u == 033 ||
+		   ISCONTROLC1(u)) {
 			term.esc &= ~(ESC_START|ESC_STR);
 			term.esc |= ESC_STR_END;
 		} else if(strescseq.len + len < sizeof(strescseq.buf) - 1) {
@@ -2694,15 +2736,15 @@ tputc(char *c, int len) {
 	 * they must not cause conflicts with sequences.
 	 */
 	if(control) {
-		tcontrolcode(ascii);
+		tcontrolcode(u);
 		/*
 		 * control codes are not shown ever
 		 */
 		return;
 	} else if(term.esc & ESC_START) {
 		if(term.esc & ESC_CSI) {
-			csiescseq.buf[csiescseq.len++] = ascii;
-			if(BETWEEN(ascii, 0x40, 0x7E)
+			csiescseq.buf[csiescseq.len++] = u;
+			if(BETWEEN(u, 0x40, 0x7E)
 					|| csiescseq.len >= \
 					sizeof(csiescseq.buf)-1) {
 				term.esc = 0;
@@ -2711,11 +2753,11 @@ tputc(char *c, int len) {
 			}
 			return;
 		} else if(term.esc & ESC_ALTCHARSET) {
-			tdeftran(ascii);
+			tdeftran(u);
 		} else if(term.esc & ESC_TEST) {
-			tdectest(ascii);
+			tdectest(u);
 		} else {
-			if (!eschandle(ascii))
+			if (!eschandle(u))
 				return;
 			/* sequence already finished */
 		}
@@ -2744,12 +2786,12 @@ tputc(char *c, int len) {
 		gp = &term.line[term.c.y][term.c.x];
 	}
 
-	tsetchar(c, &term.c.attr, term.c.x, term.c.y);
+	tsetchar(u, &term.c.attr, term.c.x, term.c.y);
 
 	if(width == 2) {
 		gp->mode |= ATTR_WIDE;
 		if(term.c.x+1 < term.col) {
-			gp[1].c[0] = '\0';
+			gp[1].u = '\0';
 			gp[1].mode = ATTR_WDUMMY;
 		}
 	}
@@ -2765,7 +2807,7 @@ tresize(int col, int row) {
 	int i;
 	int minrow = MIN(row, term.row);
 	int mincol = MIN(col, term.col);
-	bool *bp;
+	int *bp;
 	TCursor c;
 
 	if(col < 1 || row < 1) {
@@ -2783,7 +2825,8 @@ tresize(int col, int row) {
 		free(term.line[i]);
 		free(term.alt[i]);
 	}
-	if(i > 0) {
+	/* ensure that both src and dst are not NULL */
+	if (i > 0) {
 		memmove(term.line, term.line + i, row * sizeof(Line));
 		memmove(term.alt, term.alt + i, row * sizeof(Line));
 	}
@@ -2792,6 +2835,9 @@ tresize(int col, int row) {
 		free(term.alt[i]);
 	}
 
+	/* resize to new width */
+	term.specbuf = xrealloc(term.specbuf, col * sizeof(XftGlyphFontSpec));
+
 	/* resize to new height */
 	term.line = xrealloc(term.line, row * sizeof(Line));
 	term.alt  = xrealloc(term.alt,  row * sizeof(Line));
@@ -2857,82 +2903,58 @@ sixd_to_16bit(int x) {
 	return x == 0 ? 0 : 0x3737 + 0x2828 * x;
 }
 
+int
+xloadcolor(int i, const char *name, Color *ncolor) {
+	XRenderColor color = { .alpha = 0xffff };
+
+	if(!name) {
+		if(BETWEEN(i, 16, 255)) { /* 256 color */
+			if(i < 6*6*6+16) { /* same colors as xterm */
+				color.red   = sixd_to_16bit( ((i-16)/36)%6 );
+				color.green = sixd_to_16bit( ((i-16)/6) %6 );
+				color.blue  = sixd_to_16bit( ((i-16)/1) %6 );
+			} else { /* greyscale */
+				color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16));
+				color.green = color.blue = color.red;
+			}
+			return XftColorAllocValue(xw.dpy, xw.vis,
+			                          xw.cmap, &color, ncolor);
+		} else
+			name = colorname[i];
+	}
+	return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor);
+}
+
 void
 xloadcols(void) {
 	int i;
-	XRenderColor color = { .alpha = 0xffff };
-	static bool loaded;
+	static int loaded;
 	Color *cp;
 
 	if(loaded) {
-		for (cp = dc.col; cp < dc.col + LEN(dc.col); ++cp)
+		for (cp = dc.col; cp < &dc.col[LEN(dc.col)]; ++cp)
 			XftColorFree(xw.dpy, xw.vis, xw.cmap, cp);
 	}
 
-	/* load colors [0-15] and [256-LEN(colorname)] (config.h) */
-	for(i = 0; i < LEN(colorname); i++) {
-		if(!colorname[i])
-			continue;
-		if(!XftColorAllocName(xw.dpy, xw.vis, xw.cmap, colorname[i], &dc.col[i])) {
-			die("Could not allocate color '%s'\n", colorname[i]);
+	for(i = 0; i < LEN(dc.col); i++)
+		if(!xloadcolor(i, NULL, &dc.col[i])) {
+			if(colorname[i])
+				die("Could not allocate color '%s'\n", colorname[i]);
+			else
+				die("Could not allocate color %d\n", i);
 		}
-	}
-
-	/* load colors [16-231] ; same colors as xterm */
-	for(i = 16; i < 6*6*6+16; i++) {
-		color.red   = sixd_to_16bit( ((i-16)/36)%6 );
-		color.green = sixd_to_16bit( ((i-16)/6) %6 );
-		color.blue  = sixd_to_16bit( ((i-16)/1) %6 );
-		if(!XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &color, &dc.col[i]))
-			die("Could not allocate color %d\n", i);
-	}
-
-	/* load colors [232-255] ; grayscale */
-	for(; i < 256; i++) {
-		color.red = color.green = color.blue = 0x0808 + 0x0a0a * (i-(6*6*6+16));
-		if(!XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &color, &dc.col[i]))
-			die("Could not allocate color %d\n", i);
-	}
-	loaded = true;
+	loaded = 1;
 }
 
 int
 xsetcolorname(int x, const char *name) {
-	XRenderColor color = { .alpha = 0xffff };
 	Color ncolor;
 
-	if(!BETWEEN(x, 0, LEN(colorname)))
+	if(!BETWEEN(x, 0, LEN(dc.col)))
 		return 1;
 
-	if(!name) {
-		if(BETWEEN(x, 16, 16 + 215)) { /* 256 color */
-			color.red   = sixd_to_16bit( ((x-16)/36)%6 );
-			color.green = sixd_to_16bit( ((x-16)/6) %6 );
-			color.blue  = sixd_to_16bit( ((x-16)/1) %6 );
-			if(!XftColorAllocValue(xw.dpy, xw.vis,
-						xw.cmap, &color, &ncolor)) {
-				return 1;
-			}
-
-			XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]);
-			dc.col[x] = ncolor;
-			return 0;
-		} else if(BETWEEN(x, 16 + 216, 255)) { /* greyscale */
-			color.red = color.green = color.blue = \
-				    0x0808 + 0x0a0a * (x - (16 + 216));
-			if(!XftColorAllocValue(xw.dpy, xw.vis,
-						xw.cmap, &color, &ncolor)) {
-				return 1;
-			}
 
-			XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]);
-			dc.col[x] = ncolor;
-			return 0;
-		} else { /* system colors */
-			name = colorname[x];
-		}
-	}
-	if(!XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, &ncolor))
+	if(!xloadcolor(x, name, &ncolor))
 		return 1;
 
 	XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]);
@@ -2975,7 +2997,7 @@ xhints(void) {
 	sizeh->width_inc = xw.cw;
 	sizeh->base_height = 2 * borderpx;
 	sizeh->base_width = 2 * borderpx;
-	if(xw.isfixed == True) {
+	if(xw.isfixed) {
 		sizeh->flags |= PMaxSize | PMinSize;
 		sizeh->min_width = sizeh->max_width = xw.w;
 		sizeh->min_height = sizeh->max_height = xw.h;
@@ -3036,7 +3058,6 @@ xloadfont(Font *f, FcPattern *pattern) {
 void
 xloadfonts(char *fontstr, double fontsize) {
 	FcPattern *pattern;
-	FcResult r_sz, r_psz;
 	double fontval;
 	float ceilf(float);
 
@@ -3055,11 +3076,11 @@ xloadfonts(char *fontstr, double fontsize) {
 		FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize);
 		usedfontsize = fontsize;
 	} else {
-		r_psz = FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval);
-		r_sz = FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval);
-		if(r_psz == FcResultMatch) {
+		if(FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) ==
+				FcResultMatch) {
 			usedfontsize = fontval;
-		} else if(r_sz == FcResultMatch) {
+		} else if(FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) ==
+				FcResultMatch) {
 			usedfontsize = -1;
 		} else {
 			/*
@@ -3132,14 +3153,14 @@ void
 xzoom(const Arg *arg) {
 	Arg larg;
 
-	larg.i = usedfontsize + arg->i;
+	larg.f = usedfontsize + arg->f;
 	xzoomabs(&larg);
 }
 
 void
 xzoomabs(const Arg *arg) {
 	xunloadfonts();
-	xloadfonts(usedfont, arg->i);
+	xloadfonts(usedfont, arg->f);
 	cresize(0, 0);
 	redraw();
 	xhints();
@@ -3150,7 +3171,7 @@ xzoomreset(const Arg *arg) {
 	Arg larg;
 
 	if(defaultfontsize > 0) {
-		larg.i = defaultfontsize;
+		larg.f = defaultfontsize;
 		xzoomabs(&larg);
 	}
 }
@@ -3254,38 +3275,154 @@ xinit(void) {
 	XSync(xw.dpy, False);
 }
 
-void
-xdraws(char *s, Glyph base, int x, int y, int charlen, int bytelen) {
-	int winx = borderpx + x * xw.cw, winy = borderpx + y * xw.ch,
-	    width = charlen * xw.cw, xp, i;
-	int frcflags, charexists;
-	int u8fl, u8fblen, u8cblen, doesexist;
-	char *u8c, *u8fs;
-	long unicodep;
+int
+xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)
+{
+	float winx = borderpx + x * xw.cw, winy = borderpx + y * xw.ch, xp, yp;
+	ushort mode, prevmode = USHRT_MAX;
 	Font *font = &dc.font;
+	int frcflags = FRC_NORMAL;
+	float runewidth = xw.cw;
+	Rune rune;
+	FT_UInt glyphidx;
 	FcResult fcres;
 	FcPattern *fcpattern, *fontpattern;
 	FcFontSet *fcsets[] = { NULL };
 	FcCharSet *fccharset;
+	int i, f, numspecs = 0;
+
+	for(i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) {
+		/* Fetch rune and mode for current glyph. */
+		rune = glyphs[i].u;
+		mode = glyphs[i].mode;
+
+		/* Skip dummy wide-character spacing. */
+		if(mode == ATTR_WDUMMY)
+			continue;
+
+		/* Determine font for glyph if different from previous glyph. */
+		if(prevmode != mode) {
+			prevmode = mode;
+			font = &dc.font;
+			frcflags = FRC_NORMAL;
+			runewidth = xw.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f);
+			if((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) {
+				font = &dc.ibfont;
+				frcflags = FRC_ITALICBOLD;
+			} else if(mode & ATTR_ITALIC) {
+				font = &dc.ifont;
+				frcflags = FRC_ITALIC;
+			} else if(mode & ATTR_BOLD) {
+				font = &dc.bfont;
+				frcflags = FRC_BOLD;
+			}
+			yp = winy + font->ascent;
+		}
+
+		/* Lookup character index with default font. */
+		glyphidx = XftCharIndex(xw.dpy, font->match, rune);
+		if(glyphidx) {
+			specs[numspecs].font = font->match;
+			specs[numspecs].glyph = glyphidx;
+			specs[numspecs].x = (short)xp;
+			specs[numspecs].y = (short)yp;
+			xp += runewidth;
+			numspecs++;
+			continue;
+		}
+
+		/* Fallback on font cache, search the font cache for match. */
+		for(f = 0; f < frclen; f++) {
+			glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune);
+			/* Everything correct. */
+			if(glyphidx && frc[f].flags == frcflags)
+				break;
+			/* We got a default font for a not found glyph. */
+			if(!glyphidx && frc[f].flags == frcflags
+					&& frc[f].unicodep == rune) {
+				break;
+			}
+		}
+
+		/* Nothing was found. Use fontconfig to find matching font. */
+		if(f >= frclen) {
+			if(!font->set)
+				font->set = FcFontSort(0, font->pattern,
+				                       1, 0, &fcres);
+			fcsets[0] = font->set;
+
+			/*
+			 * Nothing was found in the cache. Now use
+			 * some dozen of Fontconfig calls to get the
+			 * font for one single character.
+			 *
+			 * Xft and fontconfig are design failures.
+			 */
+			fcpattern = FcPatternDuplicate(font->pattern);
+			fccharset = FcCharSetCreate();
+
+			FcCharSetAddChar(fccharset, rune);
+			FcPatternAddCharSet(fcpattern, FC_CHARSET,
+					fccharset);
+			FcPatternAddBool(fcpattern, FC_SCALABLE, 1);
+
+			FcConfigSubstitute(0, fcpattern,
+					FcMatchPattern);
+			FcDefaultSubstitute(fcpattern);
+
+			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;
+			}
+
+			frc[frclen].font = XftFontOpenPattern(xw.dpy,
+					fontpattern);
+			frc[frclen].flags = frcflags;
+			frc[frclen].unicodep = rune;
+
+			glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune);
+
+			f = frclen;
+			frclen++;
+
+			FcPatternDestroy(fcpattern);
+			FcCharSetDestroy(fccharset);
+		}
+
+		specs[numspecs].font = frc[f].font;
+		specs[numspecs].glyph = glyphidx;
+		specs[numspecs].x = (short)xp;
+		specs[numspecs].y = (short)(winy + frc[f].font->ascent);
+		xp += runewidth;
+		numspecs++;
+	}
+
+	return numspecs;
+}
+
+void
+xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) {
+	int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1);
+	int winx = borderpx + x * xw.cw, winy = borderpx + y * xw.ch,
+	    width = charlen * xw.cw;
 	Color *fg, *bg, *temp, revfg, revbg, truefg, truebg;
 	XRenderColor colfg, colbg;
 	XRectangle r;
-	int oneatatime;
-
-	frcflags = FRC_NORMAL;
 
-	if(base.mode & ATTR_ITALIC) {
-		if(base.fg == defaultfg)
+	/* Determine foreground and background colors based on mode. */
+	if(base.fg == defaultfg) {
+		if(base.mode & ATTR_ITALIC)
 			base.fg = defaultitalic;
-		font = &dc.ifont;
-		frcflags = FRC_ITALIC;
-	} else if((base.mode & ATTR_ITALIC) && (base.mode & ATTR_BOLD)) {
-		if(base.fg == defaultfg)
+		else if((base.mode & ATTR_ITALIC) && (base.mode & ATTR_BOLD))
 			base.fg = defaultitalic;
-		font = &dc.ibfont;
-		frcflags = FRC_ITALICBOLD;
-	} else if(base.mode & ATTR_UNDERLINE) {
-		if(base.fg == defaultfg)
+		else if(base.mode & ATTR_UNDERLINE)
 			base.fg = defaultunderline;
 	}
 
@@ -3311,22 +3448,9 @@ xdraws(char *s, Glyph base, int x, int y, int charlen, int bytelen) {
 		bg = &dc.col[base.bg];
 	}
 
-	if(base.mode & ATTR_BOLD) {
-		/*
-		 * change basic system colors [0-7]
-		 * to bright system colors [8-15]
-		 */
-		if(BETWEEN(base.fg, 0, 7) && !(base.mode & ATTR_FAINT))
-			fg = &dc.col[base.fg + 8];
-
-		if(base.mode & ATTR_ITALIC) {
-			font = &dc.ibfont;
-			frcflags = FRC_ITALICBOLD;
-		} else {
-			font = &dc.bfont;
-			frcflags = FRC_BOLD;
-		}
-	}
+	/* Change basic system colors [0-7] to bright system colors [8-15] */
+	if((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7))
+		fg = &dc.col[base.fg + 8];
 
 	if(IS_SET(MODE_REVERSE)) {
 		if(fg == &dc.col[defaultfg]) {
@@ -3360,7 +3484,7 @@ xdraws(char *s, Glyph base, int x, int y, int charlen, int bytelen) {
 		bg = temp;
 	}
 
-	if(base.mode & ATTR_FAINT && !(base.mode & ATTR_BOLD)) {
+	if((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) {
 		colfg.red = fg->color.red / 2;
 		colfg.green = fg->color.green / 2;
 		colfg.blue = fg->color.blue / 2;
@@ -3398,136 +3522,17 @@ xdraws(char *s, Glyph base, int x, int y, int charlen, int bytelen) {
 	r.width = width;
 	XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1);
 
-	for(xp = winx; bytelen > 0;) {
-		/*
-		 * Search for the range in the to be printed string of glyphs
-		 * that are in the main font. Then print that range. If
-		 * some glyph is found that is not in the font, do the
-		 * fallback dance.
-		 */
-		u8fs = s;
-		u8fblen = 0;
-		u8fl = 0;
-		oneatatime = font->width != xw.cw;
-		for(;;) {
-			u8c = s;
-			u8cblen = utf8decode(s, &unicodep, UTF_SIZ);
-			s += u8cblen;
-			bytelen -= u8cblen;
-
-			doesexist = XftCharExists(xw.dpy, font->match, unicodep);
-			if(doesexist) {
-					u8fl++;
-					u8fblen += u8cblen;
-					if(!oneatatime && bytelen > 0)
-							continue;
-			}
-
-			if(u8fl > 0) {
-				XftDrawStringUtf8(xw.draw, fg,
-						font->match, xp,
-						winy + font->ascent,
-						(FcChar8 *)u8fs,
-						u8fblen);
-				xp += xw.cw * u8fl;
-			}
-			break;
-		}
-		if(doesexist) {
-			if(oneatatime)
-				continue;
-			break;
-		}
-
-		/* Search the font cache. */
-		for(i = 0; i < frclen; i++) {
-			charexists = XftCharExists(xw.dpy, frc[i].font, unicodep);
-			/* Everything correct. */
-			if(charexists && frc[i].flags == frcflags)
-				break;
-			/* We got a default font for a not found glyph. */
-			if(!charexists && frc[i].flags == frcflags \
-					&& frc[i].unicodep == unicodep) {
-				break;
-			}
-		}
-
-		/* Nothing was found. */
-		if(i >= frclen) {
-			if(!font->set)
-				font->set = FcFontSort(0, font->pattern,
-				                       FcTrue, 0, &fcres);
-			fcsets[0] = font->set;
-
-			/*
-			 * Nothing was found in the cache. Now use
-			 * some dozen of Fontconfig calls to get the
-			 * font for one single character.
-			 *
-			 * Xft and fontconfig are design failures.
-			 */
-			fcpattern = FcPatternDuplicate(font->pattern);
-			fccharset = FcCharSetCreate();
-
-			FcCharSetAddChar(fccharset, unicodep);
-			FcPatternAddCharSet(fcpattern, FC_CHARSET,
-					fccharset);
-			FcPatternAddBool(fcpattern, FC_SCALABLE,
-					FcTrue);
-
-			FcConfigSubstitute(0, fcpattern,
-					FcMatchPattern);
-			FcDefaultSubstitute(fcpattern);
-
-			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;
-			}
-
-			frc[frclen].font = XftFontOpenPattern(xw.dpy,
-					fontpattern);
-			frc[frclen].flags = frcflags;
-			frc[frclen].unicodep = unicodep;
-
-			i = frclen;
-			frclen++;
-
-			FcPatternDestroy(fcpattern);
-			FcCharSetDestroy(fccharset);
-		}
-
-		XftDrawStringUtf8(xw.draw, fg, frc[i].font,
-				xp, winy + frc[i].font->ascent,
-				(FcChar8 *)u8c, u8cblen);
-
-		xp += xw.cw * wcwidth(unicodep);
-	}
-
-	/*
-	 * This is how the loop above actually should be. Why does the
-	 * application have to care about font details?
-	 *
-	 * I have to repeat: Xft and Fontconfig are design failures.
-	 */
-	/*
-	XftDrawStringUtf8(xw.draw, fg, font->set, winx,
-			winy + font->ascent, (FcChar8 *)s, bytelen);
-	*/
+	/* Render the glyphs. */
+	XftDrawGlyphFontSpec(xw.draw, fg, specs, len);
 
+	/* Render underline and strikethrough. */
 	if(base.mode & ATTR_UNDERLINE) {
-		XftDrawRect(xw.draw, fg, winx, winy + font->ascent + 1,
+		XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1,
 				width, 1);
 	}
 
 	if(base.mode & ATTR_STRUCK) {
-		XftDrawRect(xw.draw, fg, winx, winy + 2 * font->ascent / 3,
+		XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 3,
 				width, 1);
 	}
 
@@ -3535,11 +3540,19 @@ xdraws(char *s, Glyph base, int x, int y, int charlen, int bytelen) {
 	XftDrawSetClip(xw.draw, 0);
 }
 
+void
+xdrawglyph(Glyph g, int x, int y) {
+	int numspecs;
+	XftGlyphFontSpec spec;
+	numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y);
+	xdrawglyphfontspecs(&spec, g, numspecs, x, y);
+}
+
 void
 xdrawcursor(void) {
 	static int oldx = 0, oldy = 0;
-	int sl, width, curx;
-	Glyph g = {{' '}, ATTR_NULL, defaultbg, defaultcs};
+	int curx;
+	Glyph g = {' ', ATTR_NULL, defaultbg, defaultcs};
 
 	LIMIT(oldx, 0, term.col-1);
 	LIMIT(oldy, 0, term.row-1);
@@ -3552,13 +3565,10 @@ xdrawcursor(void) {
 	if(term.line[term.c.y][curx].mode & ATTR_WDUMMY)
 		curx--;
 
-	memcpy(g.c, term.line[term.c.y][term.c.x].c, UTF_SIZ);
+	g.u = term.line[term.c.y][term.c.x].u;
 
 	/* remove the old cursor */
-	sl = utf8len(term.line[oldy][oldx].c);
-	width = (term.line[oldy][oldx].mode & ATTR_WIDE)? 2 : 1;
-	xdraws(term.line[oldy][oldx].c, term.line[oldy][oldx], oldx,
-			oldy, width, sl);
+	xdrawglyph(term.line[oldy][oldx], oldx, oldy);
 
 	if(IS_SET(MODE_HIDE))
 		return;
@@ -3575,10 +3585,8 @@ xdrawcursor(void) {
 						g.bg = defaultfg;
 					}
 
-				sl = utf8len(g.c);
-				width = (term.line[term.c.y][curx].mode & ATTR_WIDE)\
-					? 2 : 1;
-				xdraws(g.c, g, term.c.x, term.c.y, width, sl);
+				g.mode |= term.line[term.c.y][curx].mode & ATTR_WIDE;
+				xdrawglyph(g, term.c.x, term.c.y);
 				break;
 			case 3: /* Blinking Underline */
 			case 4: /* Steady Underline */
@@ -3651,11 +3659,10 @@ draw(void) {
 
 void
 drawregion(int x1, int y1, int x2, int y2) {
-	int ic, ib, x, y, ox, sl;
+	int i, x, y, ox, numspecs;
 	Glyph base, new;
-	char buf[DRAW_BUF_SIZ];
-	bool ena_sel = sel.ob.x != -1 && sel.alt == IS_SET(MODE_ALTSCREEN);
-	long unicodep;
+	XftGlyphFontSpec* specs;
+	int ena_sel = sel.ob.x != -1 && sel.alt == IS_SET(MODE_ALTSCREEN);
 
 	if(!(xw.state & WIN_VISIBLE))
 		return;
@@ -3666,43 +3673,37 @@ drawregion(int x1, int y1, int x2, int y2) {
 
 		xtermclear(0, y, term.col, y);
 		term.dirty[y] = 0;
-		base = term.line[y][0];
-		ic = ib = ox = 0;
-		for(x = x1; x < x2; x++) {
+
+		specs = term.specbuf;
+		numspecs = xmakeglyphfontspecs(specs, &term.line[y][x1], x2 - x1, x1, y);
+
+		i = ox = 0;
+		for(x = x1; x < x2 && i < numspecs; x++) {
 			new = term.line[y][x];
 			if(new.mode == ATTR_WDUMMY)
 				continue;
 			if(ena_sel && selected(x, y))
 				new.mode ^= ATTR_REVERSE;
-			if(ib > 0 && (ATTRCMP(base, new)
-					|| ib >= DRAW_BUF_SIZ-UTF_SIZ)) {
-				xdraws(buf, base, ox, y, ic, ib);
-				ic = ib = 0;
+			if(i > 0 && ATTRCMP(base, new)) {
+				xdrawglyphfontspecs(specs, base, i, ox, y);
+				specs += i;
+				numspecs -= i;
+				i = 0;
 			}
-			if(ib == 0) {
+			if(i == 0) {
 				ox = x;
 				base = new;
 			}
-
-			sl = utf8decode(new.c, &unicodep, UTF_SIZ);
-			memcpy(buf+ib, new.c, sl);
-			ib += sl;
-			ic += (new.mode & ATTR_WIDE)? 2 : 1;
+			i++;
 		}
-		if(ib > 0)
-			xdraws(buf, base, ox, y, ic, ib);
+		if(i > 0)
+			xdrawglyphfontspecs(specs, base, i, ox, y);
 	}
 	xdrawcursor();
 }
 
 void
 expose(XEvent *ev) {
-	XExposeEvent *e = &ev->xexpose;
-
-	if(xw.state & WIN_REDRAW) {
-		if(!e->count)
-			xw.state &= ~WIN_REDRAW;
-	}
 	redraw();
 }
 
@@ -3710,12 +3711,7 @@ void
 visibility(XEvent *ev) {
 	XVisibilityEvent *e = &ev->xvisibility;
 
-	if(e->state == VisibilityFullyObscured) {
-		xw.state &= ~WIN_VISIBLE;
-	} else if(!(xw.state & WIN_VISIBLE)) {
-		/* need a full redraw for next Expose, not just a buf copy */
-		xw.state |= WIN_VISIBLE | WIN_REDRAW;
-	}
+	MODBIT(xw.state, e->state != VisibilityFullyObscured, WIN_VISIBLE);
 }
 
 void
@@ -3759,7 +3755,7 @@ focus(XEvent *ev) {
 	}
 }
 
-bool
+int
 match(uint mask, uint state) {
 	return mask == XK_ANY_MOD || mask == (state & ~ignoremod);
 }
@@ -3814,7 +3810,7 @@ kpress(XEvent *ev) {
 	KeySym ksym;
 	char buf[32], *customkey;
 	int len;
-	long c;
+	Rune c;
 	Status status;
 	Shortcut *bp;
 
@@ -3843,7 +3839,7 @@ kpress(XEvent *ev) {
 		if(IS_SET(MODE_8BIT)) {
 			if(*buf < 0177) {
 				c = *buf | 0x80;
-				len = utf8encode(c, buf, UTF_SIZ);
+				len = utf8encode(c, buf);
 			}
 		} else {
 			buf[1] = buf[0];
@@ -3871,7 +3867,7 @@ cmessage(XEvent *e) {
 	} else if(e->xclient.data.l[0] == xw.wmdeletewin) {
 		/* Send SIGHUP to shell */
 		kill(pid, SIGHUP);
-		exit(EXIT_SUCCESS);
+		exit(0);
 	}
 }
 
@@ -3912,6 +3908,13 @@ run(void) {
 	/* Waiting for window mapping */
 	do {
 		XNextEvent(xw.dpy, &ev);
+		/*
+		 * XFilterEvent is required to be called after you using XOpenIM,
+		 * this is not unnecessary.It does not only filter the key event,
+		 * but some clientmessage for input method as well.
+		 */
+		if(XFilterEvent(&ev, None))
+			continue;
 		if(ev.type == ConfigureNotify) {
 			w = ev.xconfigure.width;
 			h = ev.xconfigure.height;
@@ -3948,7 +3951,7 @@ run(void) {
 
 		clock_gettime(CLOCK_MONOTONIC, &now);
 		drawtimeout.tv_sec = 0;
-		drawtimeout.tv_nsec = (1000/xfps) * 1E6;
+		drawtimeout.tv_nsec =  (1000 * 1E6)/ xfps;
 		tv = &drawtimeout;
 
 		dodraw = 0;
@@ -3959,8 +3962,7 @@ run(void) {
 			dodraw = 1;
 		}
 		deltatime = TIMEDIFF(now, last);
-		if(deltatime > (xev? (1000/xfps) : (1000/actionfps))
-				|| deltatime < 0) {
+		if(deltatime > 1000 / (xev ? xfps : actionfps)) {
 			dodraw = 1;
 			last = now;
 		}
@@ -4003,14 +4005,16 @@ run(void) {
 
 void
 usage(void) {
-	die("%s " VERSION " (c) 2010-2015 st engineers\n" \
+	die("%s " VERSION " (c) 2010-2015 st engineers\n"
 	"usage: st [-a] [-v] [-c class] [-f font] [-g geometry] [-o file]\n"
-	"          [-i] [-t title] [-w windowid] [-e command ...]\n", argv0);
+	"          [-i] [-t title] [-w windowid] [-e command ...] [command ...]\n"
+	"       st [-a] [-v] [-c class] [-f font] [-g geometry] [-o file]\n"
+	"          [-i] [-t title] [-w windowid] [-l line] [stty_args ...]\n",
+	argv0);
 }
 
 int
 main(int argc, char *argv[]) {
-	char *titles;
 	uint cols = 80, rows = 24;
 
 	xw.l = xw.t = 0;
@@ -4019,20 +4023,14 @@ main(int argc, char *argv[]) {
 
 	ARGBEGIN {
 	case 'a':
-		allowaltscreen = false;
+		allowaltscreen = 0;
 		break;
 	case 'c':
 		opt_class = EARGF(usage());
 		break;
 	case 'e':
-		/* eat all remaining arguments */
-		if(argc > 1) {
-			opt_cmd = &argv[1];
-			if(argv[1] != NULL && opt_title == NULL) {
-				titles = xstrdup(argv[1]);
-				opt_title = basename(titles);
-			}
-		}
+		if(argc > 0)
+			--argc, ++argv;
 		goto run;
 	case 'f':
 		opt_font = EARGF(usage());
@@ -4042,11 +4040,14 @@ main(int argc, char *argv[]) {
 				&xw.l, &xw.t, &cols, &rows);
 		break;
 	case 'i':
-		xw.isfixed = True;
+		xw.isfixed = 1;
 		break;
 	case 'o':
 		opt_io = EARGF(usage());
 		break;
+	case 'l':
+		opt_line = EARGF(usage());
+		break;
 	case 't':
 		opt_title = EARGF(usage());
 		break;
@@ -4059,6 +4060,12 @@ main(int argc, char *argv[]) {
 	} ARGEND;
 
 run:
+	if(argc > 0) {
+		/* eat all remaining arguments */
+		opt_cmd = argv;
+		if(!opt_title && !opt_line)
+			opt_title = basename(xstrdup(argv[0]));
+	}
 	setlocale(LC_CTYPE, "");
 	XSetLocaleModifiers("");
 	tnew(MAX(cols, 1), MAX(rows, 1));