st-appsync-20200618-b27a383.diff - 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)" <[email protected]> | |
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 bli… | |
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, 0x… | |
79 static Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10… | |
80 static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10F… | |
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)-buf… | |
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… | |
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/synchroni… | |
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 B… | |
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 … | |
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… | |
229 * sync with periodic updates from animations/key-repea… | |
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… | |
238 } | |
239 | |
240 + if (tinsync(su_timeout)) { | |
241 + /* | |
242 + * on synchronized-update draw-suspension: don'… | |
243 + * drawing so that we draw ASAP once we can (ju… | |
244 + * ESU). it won't be too soon because we alread… | |
245 + * draw now but we skip. we set timeout > 0 to … | |
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 |