sites

public wiki contents of suckless.org
git clone git://git.suckless.org/sites
Log | Files | Refs

st-appsync-20200618-b27a383.diff (7468B)


      1 From 8c9c920325fa10440a96736ba58ec647a0365e22 Mon Sep 17 00:00:00 2001
      2 From: "Avi Halachmi (:avih)" <avihpit@yahoo.com>
      3 Date: Sat, 18 Apr 2020 13:56:11 +0300
      4 Subject: [PATCH] application-sync: support Synchronized-Updates
      5 
      6 See https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec
      7 
      8 In a nutshell: allow an application to suspend drawing until it has
      9 completed some output - so that the terminal will not flicker/tear by
     10 rendering partial content. If the end-of-suspension sequence doesn't
     11 arrive, the terminal bails out after a timeout (default: 200 ms).
     12 
     13 The feature is supported and pioneered by iTerm2. There are probably
     14 very few other terminals or applications which support this feature
     15 currently.
     16 
     17 One notable application which does support it is tmux (master as of
     18 2020-04-18) - where cursor flicker is completely avoided when a pane
     19 has new content. E.g. run in one pane: `while :; do cat x.c; done'
     20 while the cursor is at another pane.
     21 
     22 The terminfo string `Sync' added to `st.info' is also a tmux extension
     23 which tmux detects automatically when `st.info` is installed.
     24 
     25 Notes:
     26 
     27 - Draw-suspension begins on BSU sequence (Begin-Synchronized-Update),
     28   and ends on ESU sequence (End-Synchronized-Update).
     29 
     30 - BSU, ESU are "\033P=1s\033\\", "\033P=2s\033\\" respectively (DCS).
     31 
     32 - SU doesn't support nesting - BSU begins or extends, ESU always ends.
     33 
     34 - ESU without BSU is ignored.
     35 
     36 - BSU after BSU extends (resets the timeout), so an application could
     37   send BSU in a loop and keep drawing suspended - exactly like it can
     38   not-draw anything in a loop. But as soon as it exits/aborted then
     39   drawing is resumed according to the timeout even without ESU.
     40 
     41 - This implementation focuses on ESU and doesn't really care about BSU
     42   in the sense that it tries hard to draw exactly once ESU arrives (if
     43   it's not too soon after the last draw - according to minlatency),
     44   and doesn't try to draw the content just up-to BSU. These two sides
     45   complement eachother - not-drawing on BSU increases the chance that
     46   ESU is not too soon after the last draw. This approach was chosen
     47   because the application's main focus is that ESU indicates to the
     48   terminal that the content is now ready - and that's when we try to
     49   draw.
     50 ---
     51  config.def.h |  6 ++++++
     52  st.c         | 48 ++++++++++++++++++++++++++++++++++++++++++++++--
     53  st.info      |  1 +
     54  x.c          | 22 +++++++++++++++++++---
     55  4 files changed, 72 insertions(+), 5 deletions(-)
     56 
     57 diff --git a/config.def.h b/config.def.h
     58 index 6f05dce..80d768e 100644
     59 --- a/config.def.h
     60 +++ b/config.def.h
     61 @@ -56,6 +56,12 @@ int allowwindowops = 0;
     62  static double minlatency = 8;
     63  static double maxlatency = 33;
     64  
     65 +/*
     66 + * Synchronized-Update timeout in ms
     67 + * https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec
     68 + */
     69 +static uint su_timeout = 200;
     70 +
     71  /*
     72   * blinking timeout (set to 0 to disable blinking) for the terminal blinking
     73   * attribute.
     74 diff --git a/st.c b/st.c
     75 index 76b7e0d..0582e77 100644
     76 --- a/st.c
     77 +++ b/st.c
     78 @@ -231,6 +231,33 @@ static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
     79  static Rune utfmin[UTF_SIZ + 1] = {       0,    0,  0x80,  0x800,  0x10000};
     80  static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
     81  
     82 +#include <time.h>
     83 +static int su = 0;
     84 +struct timespec sutv;
     85 +
     86 +static void
     87 +tsync_begin()
     88 +{
     89 +	clock_gettime(CLOCK_MONOTONIC, &sutv);
     90 +	su = 1;
     91 +}
     92 +
     93 +static void
     94 +tsync_end()
     95 +{
     96 +	su = 0;
     97 +}
     98 +
     99 +int
    100 +tinsync(uint timeout)
    101 +{
    102 +	struct timespec now;
    103 +	if (su && !clock_gettime(CLOCK_MONOTONIC, &now)
    104 +	       && TIMEDIFF(now, sutv) >= timeout)
    105 +		su = 0;
    106 +	return su;
    107 +}
    108 +
    109  ssize_t
    110  xwrite(int fd, const char *s, size_t len)
    111  {
    112 @@ -818,6 +845,9 @@ ttynew(char *line, char *cmd, char *out, char **args)
    113  	return cmdfd;
    114  }
    115  
    116 +static int twrite_aborted = 0;
    117 +int ttyread_pending() { return twrite_aborted; }
    118 +
    119  size_t
    120  ttyread(void)
    121  {
    122 @@ -826,7 +856,7 @@ ttyread(void)
    123  	int ret, written;
    124  
    125  	/* append read bytes to unprocessed bytes */
    126 -	ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
    127 +	ret = twrite_aborted ? 1 : read(cmdfd, buf+buflen, LEN(buf)-buflen);
    128  
    129  	switch (ret) {
    130  	case 0:
    131 @@ -834,7 +864,7 @@ ttyread(void)
    132  	case -1:
    133  		die("couldn't read from shell: %s\n", strerror(errno));
    134  	default:
    135 -		buflen += ret;
    136 +		buflen += twrite_aborted ? 0 : ret;
    137  		written = twrite(buf, buflen, 0);
    138  		buflen -= written;
    139  		/* keep any incomplete UTF-8 byte sequence for the next call */
    140 @@ -994,6 +1024,7 @@ tsetdirtattr(int attr)
    141  void
    142  tfulldirt(void)
    143  {
    144 +	tsync_end();
    145  	tsetdirt(0, term.row-1);
    146  }
    147  
    148 @@ -1895,6 +1926,12 @@ strhandle(void)
    149  		xsettitle(strescseq.args[0]);
    150  		return;
    151  	case 'P': /* DCS -- Device Control String */
    152 +		/* https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec */
    153 +		if (strstr(strescseq.buf, "=1s") == strescseq.buf)
    154 +			tsync_begin();  /* BSU */
    155 +		else if (strstr(strescseq.buf, "=2s") == strescseq.buf)
    156 +			tsync_end();  /* ESU */
    157 +		return;
    158  	case '_': /* APC -- Application Program Command */
    159  	case '^': /* PM -- Privacy Message */
    160  		return;
    161 @@ -2436,6 +2473,9 @@ twrite(const char *buf, int buflen, int show_ctrl)
    162  	Rune u;
    163  	int n;
    164  
    165 +	int su0 = su;
    166 +	twrite_aborted = 0;
    167 +
    168  	for (n = 0; n < buflen; n += charsize) {
    169  		if (IS_SET(MODE_UTF8)) {
    170  			/* process a complete utf8 char */
    171 @@ -2446,6 +2486,10 @@ twrite(const char *buf, int buflen, int show_ctrl)
    172  			u = buf[n] & 0xFF;
    173  			charsize = 1;
    174  		}
    175 +		if (su0 && !su) {
    176 +			twrite_aborted = 1;
    177 +			break;  // ESU - allow rendering before a new BSU
    178 +		}
    179  		if (show_ctrl && ISCONTROL(u)) {
    180  			if (u & 0x80) {
    181  				u &= 0x7f;
    182 diff --git a/st.info b/st.info
    183 index 8201ad6..b32b446 100644
    184 --- a/st.info
    185 +++ b/st.info
    186 @@ -191,6 +191,7 @@ st-mono| simpleterm monocolor,
    187  	Ms=\E]52;%p1%s;%p2%s\007,
    188  	Se=\E[2 q,
    189  	Ss=\E[%p1%d q,
    190 +	Sync=\EP=%p1%ds\E\\,
    191  
    192  st| simpleterm,
    193  	use=st-mono,
    194 diff --git a/x.c b/x.c
    195 index 210f184..27ff4e2 100644
    196 --- a/x.c
    197 +++ b/x.c
    198 @@ -1861,6 +1861,9 @@ resize(XEvent *e)
    199  	cresize(e->xconfigure.width, e->xconfigure.height);
    200  }
    201  
    202 +int tinsync(uint);
    203 +int ttyread_pending();
    204 +
    205  void
    206  run(void)
    207  {
    208 @@ -1895,7 +1898,7 @@ run(void)
    209  		FD_SET(ttyfd, &rfd);
    210  		FD_SET(xfd, &rfd);
    211  
    212 -		if (XPending(xw.dpy))
    213 +		if (XPending(xw.dpy) || ttyread_pending())
    214  			timeout = 0;  /* existing events might not set xfd */
    215  
    216  		seltv.tv_sec = timeout / 1E3;
    217 @@ -1909,7 +1912,8 @@ run(void)
    218  		}
    219  		clock_gettime(CLOCK_MONOTONIC, &now);
    220  
    221 -		if (FD_ISSET(ttyfd, &rfd))
    222 +		int ttyin = FD_ISSET(ttyfd, &rfd) || ttyread_pending();
    223 +		if (ttyin)
    224  			ttyread();
    225  
    226  		xev = 0;
    227 @@ -1933,7 +1937,7 @@ run(void)
    228  		 * maximum latency intervals during `cat huge.txt`, and perfect
    229  		 * sync with periodic updates from animations/key-repeats/etc.
    230  		 */
    231 -		if (FD_ISSET(ttyfd, &rfd) || xev) {
    232 +		if (ttyin || xev) {
    233  			if (!drawing) {
    234  				trigger = now;
    235  				drawing = 1;
    236 @@ -1944,6 +1948,18 @@ run(void)
    237  				continue;  /* we have time, try to find idle */
    238  		}
    239  
    240 +		if (tinsync(su_timeout)) {
    241 +			/*
    242 +			 * on synchronized-update draw-suspension: don't reset
    243 +			 * drawing so that we draw ASAP once we can (just after
    244 +			 * ESU). it won't be too soon because we already can
    245 +			 * draw now but we skip. we set timeout > 0 to draw on
    246 +			 * SU-timeout even without new content.
    247 +			 */
    248 +			timeout = minlatency;
    249 +			continue;
    250 +		}
    251 +
    252  		/* idle detected or maxlatency exhausted -> draw */
    253  		timeout = -1;
    254  		if (blinktimeout && tattrset(ATTR_BLINK)) {
    255 
    256 base-commit: b27a383a3acc7decf00e6e889fca265430b5d329
    257 -- 
    258 2.17.1
    259