diff options
| -rw-r--r-- | config.def.h | 11 | ||||
| -rw-r--r-- | x.c | 120 | 
2 files changed, 65 insertions, 66 deletions
| diff --git a/config.def.h b/config.def.h index 0895a1f..fdbacfd 100644 --- a/config.def.h +++ b/config.def.h @@ -43,9 +43,14 @@ static unsigned int tripleclicktimeout = 600;  /* alt screens */  int allowaltscreen = 1; -/* frames per second st should at maximum draw to the screen */ -static unsigned int xfps = 120; -static unsigned int actionfps = 30; +/* + * draw latency range in ms - from new content/keypress/etc until drawing. + * within this range, st draws when content stops arriving (idle). mostly it's + * near minlatency, but it waits longer for slow updates to avoid partial draw. + * low minlatency will tear/flicker more, as it can "detect" idle too early. + */ +static double minlatency = 8; +static double maxlatency = 33;  /*   * blinking timeout (set to 0 to disable blinking) for the terminal blinking @@ -1867,10 +1867,9 @@ run(void)  	XEvent ev;  	int w = win.w, h = win.h;  	fd_set rfd; -	int xfd = XConnectionNumber(xw.dpy), xev, blinkset = 0, dodraw = 0; -	int ttyfd; -	struct timespec drawtimeout, *tv = NULL, now, last, lastblink; -	long deltatime; +	int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing; +	struct timespec seltv, *tv, now, lastblink, trigger; +	double timeout;  	/* Waiting for window mapping */  	do { @@ -1891,82 +1890,77 @@ run(void)  	ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd);  	cresize(w, h); -	clock_gettime(CLOCK_MONOTONIC, &last); -	lastblink = last; - -	for (xev = actionfps;;) { +	for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) {  		FD_ZERO(&rfd);  		FD_SET(ttyfd, &rfd);  		FD_SET(xfd, &rfd); +		if (XPending(xw.dpy)) +			timeout = 0;  /* existing events might not set xfd */ + +		seltv.tv_sec = timeout / 1E3; +		seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec); +		tv = timeout >= 0 ? &seltv : NULL; +  		if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) {  			if (errno == EINTR)  				continue;  			die("select failed: %s\n", strerror(errno));  		} -		if (FD_ISSET(ttyfd, &rfd)) { -			ttyread(); -			if (blinktimeout) { -				blinkset = tattrset(ATTR_BLINK); -				if (!blinkset) -					MODBIT(win.mode, 0, MODE_BLINK); -			} -		} +		clock_gettime(CLOCK_MONOTONIC, &now); -		if (FD_ISSET(xfd, &rfd)) -			xev = actionfps; +		if (FD_ISSET(ttyfd, &rfd)) +			ttyread(); -		clock_gettime(CLOCK_MONOTONIC, &now); -		drawtimeout.tv_sec = 0; -		drawtimeout.tv_nsec =  (1000 * 1E6)/ xfps; -		tv = &drawtimeout; - -		dodraw = 0; -		if (blinktimeout && TIMEDIFF(now, lastblink) > blinktimeout) { -			tsetdirtattr(ATTR_BLINK); -			win.mode ^= MODE_BLINK; -			lastblink = now; -			dodraw = 1; -		} -		deltatime = TIMEDIFF(now, last); -		if (deltatime > 1000 / (xev ? xfps : actionfps)) { -			dodraw = 1; -			last = now; +		xev = 0; +		while (XPending(xw.dpy)) { +			xev = 1; +			XNextEvent(xw.dpy, &ev); +			if (XFilterEvent(&ev, None)) +				continue; +			if (handler[ev.type]) +				(handler[ev.type])(&ev);  		} -		if (dodraw) { -			while (XPending(xw.dpy)) { -				XNextEvent(xw.dpy, &ev); -				if (XFilterEvent(&ev, None)) -					continue; -				if (handler[ev.type]) -					(handler[ev.type])(&ev); +		/* +		 * To reduce flicker and tearing, when new content or event +		 * triggers drawing, we first wait a bit to ensure we got +		 * everything, and if nothing new arrives - we draw. +		 * We start with trying to wait minlatency ms. If more content +		 * arrives sooner, we retry with shorter and shorter preiods, +		 * 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; +				drawing = 1;  			} +			timeout = (maxlatency - TIMEDIFF(now, trigger)) \ +			          / maxlatency * minlatency; +			if (timeout > 0) +				continue;  /* we have time, try to find idle */ +		} -			draw(); -			XFlush(xw.dpy); - -			if (xev && !FD_ISSET(xfd, &rfd)) -				xev--; -			if (!FD_ISSET(ttyfd, &rfd) && !FD_ISSET(xfd, &rfd)) { -				if (blinkset) { -					if (TIMEDIFF(now, lastblink) \ -							> blinktimeout) { -						drawtimeout.tv_nsec = 1000; -					} else { -						drawtimeout.tv_nsec = (1E6 * \ -							(blinktimeout - \ -							TIMEDIFF(now, -								lastblink))); -					} -					drawtimeout.tv_sec = \ -					    drawtimeout.tv_nsec / 1E9; -					drawtimeout.tv_nsec %= (long)1E9; -				} else { -					tv = NULL; -				} +		/* idle detected or maxlatency exhausted -> draw */ +		timeout = -1; +		if (blinktimeout && 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;  	}  } | 
