first
[dcc-suckless-config] / based-simple-term / patches / st-appsync-20200618-b27a383.diff
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