X-Git-Url: https://git.xinqibao.xyz/st.git/blobdiff_plain/08e06ef079e5190c7f219c7e50044745c57cea86..086cd61511aa5ca4cbef0048137bb9ae0467d283:/st.c

diff --git a/st.c b/st.c
index 6dcf0c7..f6e606b 100644
--- a/st.c
+++ b/st.c
@@ -44,7 +44,7 @@
 #endif
 
 #define USAGE \
-	"st " VERSION " (c) 2010-2012 st engineers\n" \
+	"st " VERSION " (c) 2010-2013 st engineers\n" \
 	"usage: st [-v] [-c class] [-f font] [-g geometry] [-o file]" \
 	" [-t title] [-w windowid] [-e command ...]\n"
 
@@ -53,12 +53,12 @@
 #define XEMBED_FOCUS_OUT 5
 
 /* Arbitrary sizes */
-#define ESC_BUF_SIZ   256
+#define UTF_SIZ       4
+#define ESC_BUF_SIZ   (128*UTF_SIZ)
 #define ESC_ARG_SIZ   16
-#define STR_BUF_SIZ   256
-#define STR_ARG_SIZ   16
+#define STR_BUF_SIZ   ESC_BUF_SIZ
+#define STR_ARG_SIZ   ESC_ARG_SIZ
 #define DRAW_BUF_SIZ  20*1024
-#define UTF_SIZ       4
 #define XK_ANY_MOD    UINT_MAX
 #define XK_NO_MOD     0
 
@@ -117,7 +117,8 @@ enum term_mode {
 	MODE_KBDLOCK     = 256,
 	MODE_HIDE	 = 512,
 	MODE_ECHO	 = 1024,
-	MODE_APPCURSOR	 = 2048
+	MODE_APPCURSOR	 = 2048,
+	MODE_MOUSESGR    = 4096,
 };
 
 enum escape_state {
@@ -152,7 +153,7 @@ typedef struct {
 	uchar state; /* state flags    */
 } Glyph;
 
-typedef Glyph* Line;
+typedef Glyph *Line;
 
 typedef struct {
 	Glyph attr;	 /* current char attributes */
@@ -168,7 +169,7 @@ typedef struct {
 	int len;	       /* raw string length */
 	char priv;
 	int arg[ESC_ARG_SIZ];
-	int narg;	      /* nb of args */
+	int narg;	       /* nb of args */
 	char mode;
 } CSIEscape;
 
@@ -203,7 +204,7 @@ typedef struct {
 	Display *dpy;
 	Colourmap cmap;
 	Window win;
-	XdbeBackBuffer buf;
+	Drawable buf;
 	Atom xembed, wmdeletewin;
 	XIM xim;
 	XIC xic;
@@ -213,8 +214,7 @@ typedef struct {
 	bool isfixed; /* is fixed geometry? */
 	int fx, fy, fw, fh; /* fixed geometry */
 	int tw, th; /* tty width and height */
-	int w;	/* window width */
-	int h;	/* window height */
+	int w, h; /* window width and height */
 	int ch; /* char height */
 	int cw; /* char width  */
 	char state; /* focus, redraw, visible */
@@ -284,11 +284,12 @@ typedef struct {
 typedef struct {
 	Colour col[LEN(colorname) < 256 ? 256 : LEN(colorname)];
 	Font font, bfont, ifont, ibfont;
+	GC gc;
 } DC;
 
 static void die(const char *, ...);
 static void draw(void);
-static void redraw(void);
+static void redraw(int);
 static void drawregion(int, int, int, int);
 static void execsh(void);
 static void sigchld(int);
@@ -303,7 +304,7 @@ static void strhandle(void);
 static void strparse(void);
 static void strreset(void);
 
-static void tclearregion(int, int, int, int);
+static void tclearregion(int, int, int, int, int);
 static void tcursor(int);
 static void tdeletechar(int);
 static void tdeleteline(int);
@@ -414,6 +415,8 @@ static char *opt_embed = NULL;
 static char *opt_class = NULL;
 static char *opt_font = NULL;
 
+bool usedbe = False;
+
 static char *usedfont = NULL;
 static int usedfontsize = 0;
 
@@ -436,7 +439,7 @@ typedef struct {
  * the current length of used elements.
  */
 
-static Fontcache frc[256];
+static Fontcache frc[1024];
 static int frccur = -1, frclen = 0;
 
 ssize_t
@@ -664,11 +667,10 @@ getbuttoninfo(XEvent *e) {
 
 void
 mousereport(XEvent *e) {
-	int x = x2col(e->xbutton.x);
-	int y = y2row(e->xbutton.y);
-	int button = e->xbutton.button;
-	int state = e->xbutton.state;
-	char buf[] = { '\033', '[', 'M', 0, 32+x+1, 32+y+1 };
+	int x = x2col(e->xbutton.x), y = y2row(e->xbutton.y),
+	    button = e->xbutton.button, state = e->xbutton.state,
+	    len;
+	char buf[40];
 	static int ob, ox, oy;
 
 	/* from urxvt */
@@ -677,7 +679,9 @@ mousereport(XEvent *e) {
 			return;
 		button = ob + 32;
 		ox = x, oy = y;
-	} else if(e->xbutton.type == ButtonRelease || button == AnyButton) {
+	} else if(!IS_SET(MODE_MOUSESGR)
+			&& (e->xbutton.type == ButtonRelease
+				|| button == AnyButton)) {
 		button = 3;
 	} else {
 		button -= Button1;
@@ -689,11 +693,23 @@ mousereport(XEvent *e) {
 		}
 	}
 
-	buf[3] = 32 + button + (state & ShiftMask ? 4 : 0)
+	button += (state & ShiftMask   ? 4  : 0)
 		+ (state & Mod4Mask    ? 8  : 0)
 		+ (state & ControlMask ? 16 : 0);
 
-	ttywrite(buf, sizeof(buf));
+	len = 0;
+	if(IS_SET(MODE_MOUSESGR)) {
+		len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c",
+				button, x+1, y+1,
+				e->xbutton.type == ButtonRelease ? 'm' : 'M');
+	} else if(x < 223 && y < 223) {
+		len = snprintf(buf, sizeof(buf), "\033[M%c%c%c",
+				32+button, 32+x+1, 32+y+1);
+	} else {
+		return;
+	}
+
+	ttywrite(buf, len);
 }
 
 void
@@ -1111,7 +1127,7 @@ treset(void) {
 	term.bot = term.row - 1;
 	term.mode = MODE_WRAP;
 
-	tclearregion(0, 0, term.col-1, term.row-1);
+	tclearregion(0, 0, term.col-1, term.row-1, 0);
 	tmoveto(0, 0);
 	tcursor(CURSOR_SAVE);
 }
@@ -1155,7 +1171,7 @@ tscrolldown(int orig, int n) {
 
 	LIMIT(n, 0, term.bot-orig+1);
 
-	tclearregion(0, term.bot-n+1, term.col-1, term.bot);
+	tclearregion(0, term.bot-n+1, term.col-1, term.bot, 0);
 
 	for(i = term.bot; i >= orig+n; i--) {
 		temp = term.line[i];
@@ -1175,7 +1191,7 @@ tscrollup(int orig, int n) {
 	Line temp;
 	LIMIT(n, 0, term.bot-orig+1);
 
-	tclearregion(0, orig, term.col-1, orig+n-1);
+	tclearregion(0, orig, term.col-1, orig+n-1, 0);
 
 	for(i = orig; i <= term.bot-n; i++) {
 		 temp = term.line[i];
@@ -1303,7 +1319,7 @@ tsetchar(char *c, Glyph *attr, int x, int y) {
 }
 
 void
-tclearregion(int x1, int y1, int x2, int y2) {
+tclearregion(int x1, int y1, int x2, int y2, int bce) {
 	int x, y, temp;
 
 	if(x1 > x2)
@@ -1318,8 +1334,15 @@ tclearregion(int x1, int y1, int x2, int y2) {
 
 	for(y = y1; y <= y2; y++) {
 		term.dirty[y] = 1;
-		for(x = x1; x <= x2; x++)
-			term.line[y][x].state = 0;
+		for(x = x1; x <= x2; x++) {
+			if(bce) {
+				term.line[y][x] = term.c.attr;
+				memcpy(term.line[y][x].c, " ", 2);
+				term.line[y][x].state |= GLYPH_SET;
+			} else {
+				term.line[y][x].state = 0;
+			}
+		}
 	}
 }
 
@@ -1332,13 +1355,13 @@ tdeletechar(int n) {
 	term.dirty[term.c.y] = 1;
 
 	if(src >= term.col) {
-		tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
+		tclearregion(term.c.x, term.c.y, term.col-1, term.c.y, 0);
 		return;
 	}
 
 	memmove(&term.line[term.c.y][dst], &term.line[term.c.y][src],
 			size * sizeof(Glyph));
-	tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
+	tclearregion(term.col-n, term.c.y, term.col-1, term.c.y, 0);
 }
 
 void
@@ -1350,13 +1373,13 @@ tinsertblank(int n) {
 	term.dirty[term.c.y] = 1;
 
 	if(dst >= term.col) {
-		tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
+		tclearregion(term.c.x, term.c.y, term.col-1, term.c.y, 0);
 		return;
 	}
 
 	memmove(&term.line[term.c.y][dst], &term.line[term.c.y][src],
 			size * sizeof(Glyph));
-	tclearregion(src, term.c.y, dst - 1, term.c.y);
+	tclearregion(src, term.c.y, dst - 1, term.c.y, 0);
 }
 
 void
@@ -1390,13 +1413,14 @@ tsetattr(int *attr, int l) {
 		case 1:
 			term.c.attr.mode |= ATTR_BOLD;
 			break;
-		case 3: /* enter standout (highlight) */
+		case 3:
 			term.c.attr.mode |= ATTR_ITALIC;
 			break;
 		case 4:
 			term.c.attr.mode |= ATTR_UNDERLINE;
 			break;
-		case 5:
+		case 5: /* slow blink */
+		case 6: /* rapid blink */
 			term.c.attr.mode |= ATTR_BLINK;
 			break;
 		case 7:
@@ -1406,13 +1430,14 @@ tsetattr(int *attr, int l) {
 		case 22:
 			term.c.attr.mode &= ~ATTR_BOLD;
 			break;
-		case 23: /* leave standout (highlight) mode */
+		case 23:
 			term.c.attr.mode &= ~ATTR_ITALIC;
 			break;
 		case 24:
 			term.c.attr.mode &= ~ATTR_UNDERLINE;
 			break;
 		case 25:
+		case 26:
 			term.c.attr.mode &= ~ATTR_BLINK;
 			break;
 		case 27:
@@ -1508,7 +1533,7 @@ tsetmode(bool priv, bool set, int *args, int narg) {
 				mode = term.mode;
 				MODBIT(term.mode, set, MODE_REVERSE);
 				if(mode != term.mode)
-					redraw();
+					redraw(REDRAW_TIMEOUT);
 				break;
 			case 6: /* DECOM -- Origin */
 				MODBIT(term.c.state, set, CURSOR_ORIGIN);
@@ -1532,16 +1557,23 @@ tsetmode(bool priv, bool set, int *args, int narg) {
 				break;
 			case 1000: /* 1000,1002: enable xterm mouse report */
 				MODBIT(term.mode, set, MODE_MOUSEBTN);
+				MODBIT(term.mode, 0, MODE_MOUSEMOTION);
 				break;
 			case 1002:
 				MODBIT(term.mode, set, MODE_MOUSEMOTION);
+				MODBIT(term.mode, 0, MODE_MOUSEBTN);
+				break;
+			case 1006:
+				MODBIT(term.mode, set, MODE_MOUSESGR);
 				break;
 			case 1049: /* = 1047 and 1048 */
 			case 47:
 			case 1047: {
 				alt = IS_SET(MODE_ALTSCREEN);
-				if(alt)
-					tclearregion(0, 0, term.col-1, term.row-1);
+				if(alt) {
+					tclearregion(0, 0, term.col-1,
+							term.row-1, 0);
+				}
 				if(set ^ alt)		/* set is always 1 or 0 */
 					tswapscreen();
 				if(*args != 1049)
@@ -1660,17 +1692,19 @@ csihandle(void) {
 		sel.bx = -1;
 		switch(csiescseq.arg[0]) {
 		case 0: /* below */
-			tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
-			if(term.c.y < term.row-1)
-				tclearregion(0, term.c.y+1, term.col-1, term.row-1);
+			tclearregion(term.c.x, term.c.y, term.col-1, term.c.y, 1);
+			if(term.c.y < term.row-1) {
+				tclearregion(0, term.c.y+1, term.col-1,
+						term.row-1, 1);
+			}
 			break;
 		case 1: /* above */
 			if(term.c.y > 1)
-				tclearregion(0, 0, term.col-1, term.c.y-1);
-			tclearregion(0, term.c.y, term.c.x, term.c.y);
+				tclearregion(0, 0, term.col-1, term.c.y-1, 1);
+			tclearregion(0, term.c.y, term.c.x, term.c.y, 1);
 			break;
 		case 2: /* all */
-			tclearregion(0, 0, term.col-1, term.row-1);
+			tclearregion(0, 0, term.col-1, term.row-1, 1);
 			break;
 		default:
 			goto unknown;
@@ -1679,13 +1713,14 @@ csihandle(void) {
 	case 'K': /* EL -- Clear line */
 		switch(csiescseq.arg[0]) {
 		case 0: /* right */
-			tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
+			tclearregion(term.c.x, term.c.y, term.col-1,
+					term.c.y, 1);
 			break;
 		case 1: /* left */
-			tclearregion(0, term.c.y, term.c.x, term.c.y);
+			tclearregion(0, term.c.y, term.c.x, term.c.y, 1);
 			break;
 		case 2: /* all */
-			tclearregion(0, term.c.y, term.col-1, term.c.y);
+			tclearregion(0, term.c.y, term.col-1, term.c.y, 1);
 			break;
 		}
 		break;
@@ -1710,7 +1745,8 @@ csihandle(void) {
 		break;
 	case 'X': /* ECH -- Erase <n> char */
 		DEFAULT(csiescseq.arg[0], 1);
-		tclearregion(term.c.x, term.c.y, term.c.x + csiescseq.arg[0], term.c.y);
+		tclearregion(term.c.x, term.c.y,
+				term.c.x + csiescseq.arg[0] - 1, term.c.y, 1);
 		break;
 	case 'P': /* DCH -- Delete <n> char */
 		DEFAULT(csiescseq.arg[0], 1);
@@ -1911,12 +1947,13 @@ tputc(char *c, int len) {
 
 	if(iofd != -1) {
 		if (xwrite(iofd, c, len) < 0) {
-			fprintf(stderr, "Error writting in %s:%s\n",
+			fprintf(stderr, "Error writing in %s:%s\n",
 				opt_io, strerror(errno));
 			close(iofd);
 			iofd = -1;
 		}
 	}
+
 	/*
 	 * STR sequences must be checked before anything else
 	 * because it can use some control codes as part of the sequence.
@@ -1931,10 +1968,23 @@ tputc(char *c, int len) {
 			strhandle();
 			break;
 		default:
-			strescseq.buf[strescseq.len++] = ascii;
-			if(strescseq.len+1 >= STR_BUF_SIZ) {
-				term.esc = 0;
-				strhandle();
+			if(strescseq.len + len < sizeof(strescseq.buf)) {
+				memmove(&strescseq.buf[strescseq.len], c, len);
+				strescseq.len += len;
+			} else {
+			/*
+			 * Here is a bug in terminals. If the user never sends
+			 * some code to stop the str or esc command, then st
+			 * will stop responding. But this is better than
+			 * silently failing with unknown characters. At least
+			 * then users will report back.
+			 *
+			 * In the case users ever get fixed, here is the code:
+			 */
+			/*
+			 * term.esc = 0;
+			 * strhandle();
+			 */
 			}
 		}
 		return;
@@ -1971,10 +2021,13 @@ tputc(char *c, int len) {
 			term.esc = ESC_START;
 			return;
 		case '\016':	/* SO */
-			term.c.attr.mode |= ATTR_GFX;
-			return;
 		case '\017':	/* SI */
-			term.c.attr.mode &= ~ATTR_GFX;
+			/*
+			 * Different charsets are hard to handle. Applications
+			 * should use the right alt charset escapes for the
+			 * only reason they still exist: line drawing. The
+			 * rest is incompatible history st should not support.
+			 */
 			return;
 		case '\032':	/* SUB */
 		case '\030':	/* CAN */
@@ -2155,9 +2208,11 @@ tresize(int col, int row) {
 	/* free unneeded rows */
 	i = 0;
 	if(slide > 0) {
-		/* slide screen to keep cursor where we expect it -
+		/*
+		 * slide screen to keep cursor where we expect it -
 		 * tscrollup would work here, but we can optimize to
-		 * memmove because we're freeing the earlier lines */
+		 * memmove because we're freeing the earlier lines
+		 */
 		for(/* i = 0 */; i < slide; i++) {
 			free(term.line[i]);
 			free(term.alt[i]);
@@ -2218,6 +2273,14 @@ xresize(int col, int row) {
 	xw.tw = MAX(1, col * xw.cw);
 	xw.th = MAX(1, row * xw.ch);
 
+	if(!usedbe) {
+		XFreePixmap(xw.dpy, xw.buf);
+		xw.buf = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.h,
+				DefaultDepth(xw.dpy, xw.scr));
+		XSetForeground(xw.dpy, dc.gc, dc.col[IS_SET(MODE_REVERSE) ? defaultfg : defaultbg].pixel);
+		XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, xw.w, xw.h);
+	}
+
 	XftDrawChange(xw.draw, xw.buf);
 }
 
@@ -2379,7 +2442,9 @@ xloadfonts(char *fontstr, int fontsize) {
 	xw.cw = dc.font.width;
 	xw.ch = dc.font.height;
 
+	FcPatternDel(pattern, FC_SLANT);
 	FcPatternDel(pattern, FC_WEIGHT);
+	FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN);
 	FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD);
 	if(xloadfont(&dc.bfont, pattern))
 		die("st: can't open font %s\n", fontstr);
@@ -2390,6 +2455,7 @@ xloadfonts(char *fontstr, int fontsize) {
 		die("st: can't open font %s\n", fontstr);
 
 	FcPatternDel(pattern, FC_WEIGHT);
+	FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_MEDIUM);
 	if(xloadfont(&dc.ifont, pattern))
 		die("st: can't open font %s\n", fontstr);
 
@@ -2397,8 +2463,7 @@ xloadfonts(char *fontstr, int fontsize) {
 }
 
 void
-xunloadfonts(void)
-{
+xunloadfonts(void) {
 	int i, ip;
 
 	/*
@@ -2428,17 +2493,17 @@ xunloadfonts(void)
 }
 
 void
-xzoom(const Arg *arg)
-{
+xzoom(const Arg *arg) {
 	xunloadfonts();
 	xloadfonts(usedfont, usedfontsize + arg->i);
 	cresize(0, 0);
-	draw();
+	redraw(0);
 }
 
 void
 xinit(void) {
 	XSetWindowAttributes attrs;
+	XGCValues gcvalues;
 	Cursor cursor;
 	Window parent;
 	int sw, sh, major, minor;
@@ -2478,6 +2543,7 @@ xinit(void) {
 		xw.fy = 0;
 	}
 
+	/* Events */
 	attrs.background_pixel = dc.col[defaultbg].pixel;
 	attrs.border_pixel = dc.col[defaultbg].pixel;
 	attrs.bit_gravity = NorthWestGravity;
@@ -2486,7 +2552,8 @@ xinit(void) {
 		| ButtonMotionMask | ButtonPressMask | ButtonReleaseMask;
 	attrs.colormap = xw.cmap;
 
-	parent = opt_embed ? strtol(opt_embed, NULL, 0) : XRootWindow(xw.dpy, xw.scr);
+	parent = opt_embed ? strtol(opt_embed, NULL, 0) : \
+			XRootWindow(xw.dpy, xw.scr);
 	xw.win = XCreateWindow(xw.dpy, parent, xw.fx, xw.fy,
 			xw.w, xw.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput,
 			xw.vis,
@@ -2495,17 +2562,41 @@ xinit(void) {
 			&attrs);
 
 	/* double buffering */
-	if(!XdbeQueryExtension(xw.dpy, &major, &minor))
-		die("Xdbe extension is not present\n");
-	xw.buf = XdbeAllocateBackBufferName(xw.dpy, xw.win, XdbeCopied);
+	/*
+	if(XdbeQueryExtension(xw.dpy, &major, &minor)) {
+		xw.buf = XdbeAllocateBackBufferName(xw.dpy, xw.win,
+				XdbeBackground);
+		usedbe = True;
+	} else {
+	*/
+		memset(&gcvalues, 0, sizeof(gcvalues));
+		gcvalues.graphics_exposures = False;
+		dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures,
+				&gcvalues);
+		xw.buf = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.h,
+				DefaultDepth(xw.dpy, xw.scr));
+		XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel);
+		XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, xw.w, xw.h);
+		//xw.buf = xw.win;
+	/*
+	}
+	*/
 
 	/* Xft rendering context */
 	xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap);
 
 	/* input methods */
-	xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL);
-	if(xw.xim == NULL)
-		die("XOpenIM failed. Could not open input device.\n");
+	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");
+			}
+		}
+	}
 	xw.xic = XCreateIC(xw.xim, XNInputStyle, XIMPreeditNothing
 					   | XIMStatusNothing, XNClientWindow, xw.win,
 					   XNFocusWindow, xw.win, NULL);
@@ -2542,7 +2633,6 @@ xdraws(char *s, Glyph base, int x, int y, int charlen, int bytelen) {
 	FcPattern *fcpattern, *fontpattern;
 	FcFontSet *fcsets[] = { NULL };
 	FcCharSet *fccharset;
-	XGlyphInfo extents;
 	Colour *fg = &dc.col[base.fg], *bg = &dc.col[base.bg],
 		 *temp, revfg, revbg;
 	XRenderColor colfg, colbg;
@@ -2787,13 +2877,16 @@ xresettitle(void) {
 }
 
 void
-redraw(void) {
-	struct timespec tv = {0, REDRAW_TIMEOUT * 1000};
+redraw(int timeout) {
+	struct timespec tv = {0, timeout * 1000};
 
 	tfulldirt();
 	draw();
-	XSync(xw.dpy, False); /* necessary for a good tput flash */
-	nanosleep(&tv, NULL);
+
+	if(timeout > 0) {
+		nanosleep(&tv, NULL);
+		XSync(xw.dpy, False); /* necessary for a good tput flash */
+	}
 }
 
 void
@@ -2801,7 +2894,13 @@ draw(void) {
 	XdbeSwapInfo swpinfo[1] = {{xw.win, XdbeCopied}};
 
 	drawregion(0, 0, term.col, term.row);
-	XdbeSwapBuffers(xw.dpy, swpinfo, 1);
+	if(usedbe) {
+		XdbeSwapBuffers(xw.dpy, swpinfo, 1);
+	} else {
+		XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, xw.w,
+				xw.h, 0, 0);
+		XSetForeground(xw.dpy, dc.gc, dc.col[IS_SET(MODE_REVERSE) ? defaultfg : defaultbg].pixel);
+	}
 }
 
 void
@@ -2840,6 +2939,7 @@ drawregion(int x1, int y1, int x2, int y2) {
 					ox = x;
 					base = new;
 				}
+
 				sl = utf8size(new.c);
 				memcpy(buf+ib, new.c, sl);
 				ib += sl;
@@ -2860,6 +2960,7 @@ expose(XEvent *ev) {
 		if(!e->count)
 			xw.state &= ~WIN_REDRAW;
 	}
+	redraw(0);
 }
 
 void
@@ -2890,6 +2991,11 @@ xseturgency(int add) {
 
 void
 focus(XEvent *ev) {
+	XFocusChangeEvent *e = &ev->xfocus;
+
+	if(e->mode == NotifyGrab)
+		return;
+
 	if(ev->type == FocusIn) {
 		XSetICFocus(xw.xic);
 		xw.state |= WIN_FOCUSED;
@@ -3013,8 +3119,10 @@ kpress(XEvent *ev) {
 
 void
 cmessage(XEvent *e) {
-	/* See xembed specs
-	   http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html */
+	/*
+	 * See xembed specs
+	 *  http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html
+	 */
 	if(e->xclient.message_type == xw.xembed && e->xclient.format == 32) {
 		if(e->xclient.data.l[1] == XEMBED_FOCUS_IN) {
 			xw.state |= WIN_FOCUSED;
@@ -3030,8 +3138,7 @@ cmessage(XEvent *e) {
 }
 
 void
-cresize(int width, int height)
-{
+cresize(int width, int height) {
 	int col, row;
 
 	if(width != 0)
@@ -3059,10 +3166,12 @@ void
 run(void) {
 	XEvent ev;
 	fd_set rfd;
-	int xfd = XConnectionNumber(xw.dpy), i;
-	struct timeval drawtimeout, *tv = NULL;
+	int xfd = XConnectionNumber(xw.dpy), xev;
+	struct timeval drawtimeout, *tv = NULL, now, last;
+
+	gettimeofday(&last, NULL);
 
-	for(i = 0;; i++) {
+	for(xev = actionfps;;) {
 		FD_ZERO(&rfd);
 		FD_SET(cmdfd, &rfd);
 		FD_SET(xfd, &rfd);
@@ -3072,35 +3181,36 @@ run(void) {
 			die("select failed: %s\n", SERRNO);
 		}
 
-		/*
-		 * Stop after a certain number of reads so the user does not
-		 * feel like the system is stuttering.
-		 */
-		if(i < 1000 && FD_ISSET(cmdfd, &rfd)) {
+		gettimeofday(&now, NULL);
+		/* usecs until (next) frame */
+		drawtimeout.tv_sec = 0;
+		drawtimeout.tv_usec = (1000/xfps) * 1000;
+		tv = &drawtimeout;
+
+		if(FD_ISSET(cmdfd, &rfd))
 			ttyread();
 
-			/*
-			 * Just wait a bit so it isn't disturbing the
-			 * user and the system is able to write something.
-			 */
-			drawtimeout.tv_sec = 0;
-			drawtimeout.tv_usec = 5;
-			tv = &drawtimeout;
-			continue;
-		}
-		i = 0;
-		tv = NULL;
+		if(FD_ISSET(xfd, &rfd))
+			xev = actionfps;
 
-		while(XPending(xw.dpy)) {
-			XNextEvent(xw.dpy, &ev);
-			if(XFilterEvent(&ev, None))
-				continue;
-			if(handler[ev.type])
-				(handler[ev.type])(&ev);
-		}
+		if(TIMEDIFF(now, last) > (xev ? (1000/xfps) : (1000/actionfps))) {
+			while(XPending(xw.dpy)) {
+				XNextEvent(xw.dpy, &ev);
+				if(XFilterEvent(&ev, None))
+					continue;
+				if(handler[ev.type])
+					(handler[ev.type])(&ev);
+			}
+
+			draw();
+			XFlush(xw.dpy);
+			last = now;
 
-		draw();
-		XFlush(xw.dpy);
+			if(xev && !FD_ISSET(xfd, &rfd))
+				xev--;
+			if(!FD_ISSET(cmdfd, &rfd) && !FD_ISSET(xfd, &rfd))
+				tv = NULL;
+		}
 	}
 }