sites

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

st-appsync-0.8.3.diff (7593B)


      1 From 97bdda00d211f989ee42c02a08e96b41800544f4 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 fdbacfd..d44c28e 100644
     59 --- a/config.def.h
     60 +++ b/config.def.h
     61 @@ -52,6 +52,12 @@ int allowaltscreen = 1;
     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 0ce6ac2..d53b882 100644
     76 --- a/st.c
     77 +++ b/st.c
     78 @@ -232,6 +232,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 @@ -995,6 +1025,7 @@ tsetdirtattr(int attr)
    141  void
    142  tfulldirt(void)
    143  {
    144 +	tsync_end();
    145  	tsetdirt(0, term.row-1);
    146  }
    147  
    148 @@ -1901,6 +1932,12 @@ strhandle(void)
    149  		return;
    150  	case 'P': /* DCS -- Device Control String */
    151  		term.mode |= ESC_DCS;
    152 +		/* https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec */
    153 +		if (strstr(strescseq.buf, "=1s") == strescseq.buf)
    154 +			tsync_begin(), term.mode &= ~ESC_DCS;  /* BSU */
    155 +		else if (strstr(strescseq.buf, "=2s") == strescseq.buf)
    156 +			tsync_end(), term.mode &= ~ESC_DCS;  /* ESU */
    157 +		return;
    158  	case '_': /* APC -- Application Program Command */
    159  	case '^': /* PM -- Privacy Message */
    160  		return;
    161 @@ -2454,6 +2491,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) && !IS_SET(MODE_SIXEL)) {
    170  			/* process a complete utf8 char */
    171 @@ -2464,6 +2504,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 e2abc98..0a781db 100644
    184 --- a/st.info
    185 +++ b/st.info
    186 @@ -188,6 +188,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 cbbd11f..38b08a8 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: 43a395ae91f7d67ce694e65edeaa7bbc720dd027
    257 prerequisite-patch-id: d7d5e516bc74afe094ffbfc3edb19c11d49df4e7
    258 -- 
    259 2.17.1
    260