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