summaryrefslogtreecommitdiff
path: root/patches/st-appsync-20200618-b27a383.diff
blob: 4736325098728af2826eb85ddd32d5551efdc90e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
From 8c9c920325fa10440a96736ba58ec647a0365e22 Mon Sep 17 00:00:00 2001
From: "Avi Halachmi (:avih)" <avihpit@yahoo.com>
Date: Sat, 18 Apr 2020 13:56:11 +0300
Subject: [PATCH] application-sync: support Synchronized-Updates

See https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec

In a nutshell: allow an application to suspend drawing until it has
completed some output - so that the terminal will not flicker/tear by
rendering partial content. If the end-of-suspension sequence doesn't
arrive, the terminal bails out after a timeout (default: 200 ms).

The feature is supported and pioneered by iTerm2. There are probably
very few other terminals or applications which support this feature
currently.

One notable application which does support it is tmux (master as of
2020-04-18) - where cursor flicker is completely avoided when a pane
has new content. E.g. run in one pane: `while :; do cat x.c; done'
while the cursor is at another pane.

The terminfo string `Sync' added to `st.info' is also a tmux extension
which tmux detects automatically when `st.info` is installed.

Notes:

- Draw-suspension begins on BSU sequence (Begin-Synchronized-Update),
  and ends on ESU sequence (End-Synchronized-Update).

- BSU, ESU are "\033P=1s\033\\", "\033P=2s\033\\" respectively (DCS).

- SU doesn't support nesting - BSU begins or extends, ESU always ends.

- ESU without BSU is ignored.

- BSU after BSU extends (resets the timeout), so an application could
  send BSU in a loop and keep drawing suspended - exactly like it can
  not-draw anything in a loop. But as soon as it exits/aborted then
  drawing is resumed according to the timeout even without ESU.

- This implementation focuses on ESU and doesn't really care about BSU
  in the sense that it tries hard to draw exactly once ESU arrives (if
  it's not too soon after the last draw - according to minlatency),
  and doesn't try to draw the content just up-to BSU. These two sides
  complement eachother - not-drawing on BSU increases the chance that
  ESU is not too soon after the last draw. This approach was chosen
  because the application's main focus is that ESU indicates to the
  terminal that the content is now ready - and that's when we try to
  draw.
---
 config.def.h |  6 ++++++
 st.c         | 48 ++++++++++++++++++++++++++++++++++++++++++++++--
 st.info      |  1 +
 x.c          | 22 +++++++++++++++++++---
 4 files changed, 72 insertions(+), 5 deletions(-)

diff --git a/config.def.h b/config.def.h
index 6f05dce..80d768e 100644
--- a/config.def.h
+++ b/config.def.h
@@ -56,6 +56,12 @@ int allowwindowops = 0;
 static double minlatency = 8;
 static double maxlatency = 33;
 
+/*
+ * Synchronized-Update timeout in ms
+ * https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec
+ */
+static uint su_timeout = 200;
+
 /*
  * blinking timeout (set to 0 to disable blinking) for the terminal blinking
  * attribute.
diff --git a/st.c b/st.c
index 76b7e0d..0582e77 100644
--- a/st.c
+++ b/st.c
@@ -231,6 +231,33 @@ static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
 static Rune utfmin[UTF_SIZ + 1] = {       0,    0,  0x80,  0x800,  0x10000};
 static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
 
+#include <time.h>
+static int su = 0;
+struct timespec sutv;
+
+static void
+tsync_begin()
+{
+	clock_gettime(CLOCK_MONOTONIC, &sutv);
+	su = 1;
+}
+
+static void
+tsync_end()
+{
+	su = 0;
+}
+
+int
+tinsync(uint timeout)
+{
+	struct timespec now;
+	if (su && !clock_gettime(CLOCK_MONOTONIC, &now)
+	       && TIMEDIFF(now, sutv) >= timeout)
+		su = 0;
+	return su;
+}
+
 ssize_t
 xwrite(int fd, const char *s, size_t len)
 {
@@ -818,6 +845,9 @@ ttynew(char *line, char *cmd, char *out, char **args)
 	return cmdfd;
 }
 
+static int twrite_aborted = 0;
+int ttyread_pending() { return twrite_aborted; }
+
 size_t
 ttyread(void)
 {
@@ -826,7 +856,7 @@ ttyread(void)
 	int ret, written;
 
 	/* append read bytes to unprocessed bytes */
-	ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
+	ret = twrite_aborted ? 1 : read(cmdfd, buf+buflen, LEN(buf)-buflen);
 
 	switch (ret) {
 	case 0:
@@ -834,7 +864,7 @@ ttyread(void)
 	case -1:
 		die("couldn't read from shell: %s\n", strerror(errno));
 	default:
-		buflen += ret;
+		buflen += twrite_aborted ? 0 : ret;
 		written = twrite(buf, buflen, 0);
 		buflen -= written;
 		/* keep any incomplete UTF-8 byte sequence for the next call */
@@ -994,6 +1024,7 @@ tsetdirtattr(int attr)
 void
 tfulldirt(void)
 {
+	tsync_end();
 	tsetdirt(0, term.row-1);
 }
 
@@ -1895,6 +1926,12 @@ strhandle(void)
 		xsettitle(strescseq.args[0]);
 		return;
 	case 'P': /* DCS -- Device Control String */
+		/* https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec */
+		if (strstr(strescseq.buf, "=1s") == strescseq.buf)
+			tsync_begin();  /* BSU */
+		else if (strstr(strescseq.buf, "=2s") == strescseq.buf)
+			tsync_end();  /* ESU */
+		return;
 	case '_': /* APC -- Application Program Command */
 	case '^': /* PM -- Privacy Message */
 		return;
@@ -2436,6 +2473,9 @@ twrite(const char *buf, int buflen, int show_ctrl)
 	Rune u;
 	int n;
 
+	int su0 = su;
+	twrite_aborted = 0;
+
 	for (n = 0; n < buflen; n += charsize) {
 		if (IS_SET(MODE_UTF8)) {
 			/* process a complete utf8 char */
@@ -2446,6 +2486,10 @@ twrite(const char *buf, int buflen, int show_ctrl)
 			u = buf[n] & 0xFF;
 			charsize = 1;
 		}
+		if (su0 && !su) {
+			twrite_aborted = 1;
+			break;  // ESU - allow rendering before a new BSU
+		}
 		if (show_ctrl && ISCONTROL(u)) {
 			if (u & 0x80) {
 				u &= 0x7f;
diff --git a/st.info b/st.info
index 8201ad6..b32b446 100644
--- a/st.info
+++ b/st.info
@@ -191,6 +191,7 @@ st-mono| simpleterm monocolor,
 	Ms=\E]52;%p1%s;%p2%s\007,
 	Se=\E[2 q,
 	Ss=\E[%p1%d q,
+	Sync=\EP=%p1%ds\E\\,
 
 st| simpleterm,
 	use=st-mono,
diff --git a/x.c b/x.c
index 210f184..27ff4e2 100644
--- a/x.c
+++ b/x.c
@@ -1861,6 +1861,9 @@ resize(XEvent *e)
 	cresize(e->xconfigure.width, e->xconfigure.height);
 }
 
+int tinsync(uint);
+int ttyread_pending();
+
 void
 run(void)
 {
@@ -1895,7 +1898,7 @@ run(void)
 		FD_SET(ttyfd, &rfd);
 		FD_SET(xfd, &rfd);
 
-		if (XPending(xw.dpy))
+		if (XPending(xw.dpy) || ttyread_pending())
 			timeout = 0;  /* existing events might not set xfd */
 
 		seltv.tv_sec = timeout / 1E3;
@@ -1909,7 +1912,8 @@ run(void)
 		}
 		clock_gettime(CLOCK_MONOTONIC, &now);
 
-		if (FD_ISSET(ttyfd, &rfd))
+		int ttyin = FD_ISSET(ttyfd, &rfd) || ttyread_pending();
+		if (ttyin)
 			ttyread();
 
 		xev = 0;
@@ -1933,7 +1937,7 @@ run(void)
 		 * maximum latency intervals during `cat huge.txt`, and perfect
 		 * sync with periodic updates from animations/key-repeats/etc.
 		 */
-		if (FD_ISSET(ttyfd, &rfd) || xev) {
+		if (ttyin || xev) {
 			if (!drawing) {
 				trigger = now;
 				drawing = 1;
@@ -1944,6 +1948,18 @@ run(void)
 				continue;  /* we have time, try to find idle */
 		}
 
+		if (tinsync(su_timeout)) {
+			/*
+			 * on synchronized-update draw-suspension: don't reset
+			 * drawing so that we draw ASAP once we can (just after
+			 * ESU). it won't be too soon because we already can
+			 * draw now but we skip. we set timeout > 0 to draw on
+			 * SU-timeout even without new content.
+			 */
+			timeout = minlatency;
+			continue;
+		}
+
 		/* idle detected or maxlatency exhausted -> draw */
 		timeout = -1;
 		if (blinktimeout && tattrset(ATTR_BLINK)) {

base-commit: b27a383a3acc7decf00e6e889fca265430b5d329
-- 
2.17.1