AmendHub

Download

jcs

/

subtext

/

ansi.c

 

(View History)

jcs   logger: Add buffered logging, support logging without updating window Latest amendment: 245 on 2022-08-14

1 /*
2 * Copyright (c) 2021 joshua stein <jcs@jcs.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include <stdarg.h>
18 #include <stdio.h>
19 #include <string.h>
20
21 #include "ansi.h"
22 #include "session.h"
23 #include "util.h"
24
25 /*
26 * We may be outputting a bunch of ansi()s in a string, so they can't all
27 * return the same buffer. Rotate between a few on each iteration.
28 */
29 static char ansi_buf[10][32];
30 static short last_ansi_buf = 0;
31
32 const char *
33 ansi_bold(struct session *s)
34 {
35 static const char bold[] = "\33[1m";
36 if (s->vt100)
37 return bold;
38 return "";
39 }
40
41 const char *
42 ansi_reset(struct session *s)
43 {
44 static const char reset[] = "\33[0m";
45 if (s->vt100)
46 return reset;
47 return "";
48 }
49
50 char *
51 ansi(struct session *s, ...)
52 {
53 char ansi_tmp[10];
54 char *ansi_out;
55 va_list ap;
56 short attr, val, val2, len, n;
57
58 if (!s->vt100 && !s->vt52)
59 return "";
60
61 ansi_out = (char *)&ansi_buf[last_ansi_buf];
62 if (++last_ansi_buf >= nitems(ansi_buf))
63 last_ansi_buf = 0;
64
65 *ansi_out = '\0';
66
67 va_start(ap, s);
68 while ((attr = va_arg(ap, short)) != ANSI_END) {
69 len = 0;
70 switch (attr) {
71 case ANSI_RESET:
72 if (s->vt100)
73 len = strlcat(ansi_out, "\33[0m", sizeof(ansi_buf[0]));
74 break;
75 case ANSI_BOLD:
76 if (s->vt100)
77 len = strlcat(ansi_out, "\33[1m", sizeof(ansi_buf[0]));
78 break;
79 case ANSI_UNDERLINE:
80 if (s->vt100)
81 len = strlcat(ansi_out, "\33[4m", sizeof(ansi_buf[0]));
82 break;
83 case ANSI_REVERSE:
84 if (s->vt100)
85 len = strlcat(ansi_out, "\33[7m", sizeof(ansi_buf[0]));
86 break;
87
88 case ANSI_CLEAR_SCREEN:
89 if (s->vt100)
90 len = strlcat(ansi_out, "\33[2J", sizeof(ansi_buf[0]));
91 else if (s->vt52)
92 /* XXX: changes cursor position */
93 len = strlcat(ansi_out, "\33H\33J", sizeof(ansi_buf[0]));
94 break;
95 case ANSI_ERASE_LINE:
96 if (s->vt100)
97 /* XXX: this doesn't work on windows command-line telnet */
98 len = strlcat(ansi_out, "\33[2K", sizeof(ansi_buf[0]));
99 else if (s->vt52)
100 /* XXX: changes cursor position */
101 len = strlcat(ansi_out, "\r\33K", sizeof(ansi_buf[0]));
102 break;
103 case ANSI_BACKSPACE:
104 if (s->vt100)
105 len = strlcat(ansi_out, "\33[D \33[D", sizeof(ansi_buf[0]));
106 else if (s->vt52)
107 len = strlcat(ansi_out, "\33D \33D", sizeof(ansi_buf[0]));
108 break;
109
110 /* these require N args */
111 case ANSI_DOWN_N:
112 if (s->vt100) {
113 sprintf(ansi_tmp, "\33[%dB", va_arg(ap, short));
114 len = strlcat(ansi_out, ansi_tmp, sizeof(ansi_buf[0]));
115 } else if (s->vt52) {
116 val = va_arg(ap, short);
117 for (n = 0; n < val; n++)
118 len = strlcat(ansi_out, "\33B", sizeof(ansi_buf[0]));
119 }
120 break;
121 case ANSI_FORWARD_N:
122 if (s->vt100) {
123 sprintf(ansi_tmp, "\33[%dC", va_arg(ap, short));
124 len = strlcat(ansi_out, ansi_tmp, sizeof(ansi_buf[0]));
125 } else if (s->vt52) {
126 val = va_arg(ap, short);
127 for (n = 0; n < val; n++)
128 len = strlcat(ansi_out, "\33C", sizeof(ansi_buf[0]));
129 }
130 break;
131 case ANSI_BACK_N:
132 if (s->vt100) {
133 sprintf(ansi_tmp, "\33[%dD", va_arg(ap, short));
134 len = strlcat(ansi_out, ansi_tmp, sizeof(ansi_buf[0]));
135 } else if (s->vt52) {
136 val = va_arg(ap, short);
137 for (n = 0; n < val; n++)
138 len = strlcat(ansi_out, "\33D", sizeof(ansi_buf[0]));
139 }
140 break;
141 case ANSI_UP_N:
142 if (s->vt100) {
143 sprintf(ansi_tmp, "\33[%dF", va_arg(ap, short));
144 len = strlcat(ansi_out, ansi_tmp, sizeof(ansi_buf[0]));
145 } else if (s->vt52) {
146 val = va_arg(ap, short);
147 for (n = 0; n < val; n++)
148 len = strlcat(ansi_out, "\33A", sizeof(ansi_buf[0]));
149 }
150 break;
151 case ANSI_INSERT_LINES_N:
152 if (s->vt100) {
153 sprintf(ansi_tmp, "\33[%dL", va_arg(ap, short));
154 len = strlcat(ansi_out, ansi_tmp, sizeof(ansi_buf[0]));
155 } else if (s->vt52) {
156 val = va_arg(ap, short);
157 for (n = 0; n < val; n++)
158 len = strlcat(ansi_out, "\33L", sizeof(ansi_buf[0]));
159 }
160 break;
161 case ANSI_DELETE_LINES_N:
162 if (s->vt100) {
163 sprintf(ansi_tmp, "\33[%dM", va_arg(ap, short));
164 len = strlcat(ansi_out, ansi_tmp, sizeof(ansi_buf[0]));
165 } else if (s->vt52) {
166 val = va_arg(ap, short);
167 for (n = 0; n < val; n++)
168 len = strlcat(ansi_out, "\33M", sizeof(ansi_buf[0]));
169 }
170 break;
171 case ANSI_COL_N:
172 val = va_arg(ap, short);
173 if (val == 1)
174 len = strlcat(ansi_out, "\r", sizeof(ansi_buf[0]));
175 else {
176 if (s->vt100) {
177 /* \e[xG not supported on windows telnet, fake it */
178 sprintf(ansi_tmp, "\r\33[%dC", val);
179 len = strlcat(ansi_out, ansi_tmp, sizeof(ansi_buf[0]));
180 } else if (s->vt52) {
181 /* go to beginning of line, then move right N times */
182 strlcat(ansi_out, "\r", sizeof(ansi_buf[0]));
183 sprintf(ansi_tmp, "\33C");
184 for (n = 0; n < val; n++)
185 len = strlcat(ansi_out, ansi_tmp,
186 sizeof(ansi_buf[0]));
187 }
188 }
189 break;
190 case ANSI_CURSOR_LINE_COL:
191 val = va_arg(ap, short);
192 val2 = va_arg(ap, short);
193 if (s->vt100)
194 /* line, column */
195 sprintf(ansi_tmp, "\33[%d;%dH", val, val2);
196 else if (s->vt52)
197 /* column, line as chr(col)+31,chr(line)+31 */
198 sprintf(ansi_tmp, "\33Y%c%c", val2 + 31, val + 31);
199 len = strlcat(ansi_out, ansi_tmp, sizeof(ansi_buf[0]));
200 break;
201
202 case ANSI_SAVE_CURSOR:
203 if (s->vt100)
204 len = strlcat(ansi_out, "\33[s", sizeof(ansi_buf[0]));
205 else if (s->vt52)
206 len = strlcat(ansi_out, "\337", sizeof(ansi_buf[0]));
207 break;
208 case ANSI_RESTORE_SAVED_CURSOR:
209 if (s->vt100)
210 len = strlcat(ansi_out, "\33[u", sizeof(ansi_buf[0]));
211 else if (s->vt52)
212 len = strlcat(ansi_out, "\338", sizeof(ansi_buf[0]));
213 break;
214
215 default:
216 panic("ansi: Unknown ANSI attribute %d", attr);
217 }
218
219 if (len > sizeof(ansi_buf[0]))
220 panic("ansi: strlcat overflow; missing trailing NULL?");
221 }
222
223 va_end(ap);
224
225 return ansi_out;
226 }
227
228 size_t
229 ansi_strip(char *inp, char **outp)
230 {
231 char *ret;
232 size_t ilen = strlen(inp);
233 size_t n, olen = 0;
234 short in_csi;
235
236 if (outp)
237 *outp = xmalloc(ilen + 1, "ansi_strip");
238
239 for (n = 0, in_csi = 0; n < ilen; ) {
240 if (in_csi) {
241 /* eat "12;34;56" */
242 while (n < ilen) {
243 if ((inp[n] >= '0' && inp[n] <= '9') || inp[n] == ';')
244 n++;
245 else
246 break;
247 }
248
249 /* command character */
250 if (n < ilen)
251 n++;
252
253 in_csi = 0;
254 } else if (inp[n] == '\33' && inp[n + 1] == '[') {
255 in_csi = 1;
256 n += 2;
257 } else if (inp[n] == '\r') {
258 /* consider this an ansi character */
259 n++;
260 } else {
261 if (outp)
262 (*outp)[olen] = inp[n];
263 olen++;
264 n++;
265 }
266 }
267
268 return olen;
269 }
270
271 void
272 ansi_probe_screen_size(struct session *s)
273 {
274 short cols, rows;
275 size_t count = 0;
276 char c[2] = { 0 };
277
278 if (!s->vt100)
279 return;
280
281 /* save cursor position */
282 session_clear_input(s);
283 session_printf(s, "\33[s");
284
285 /* go to the (hopefully) edge of the screen */
286 session_printf(s, "\33[200B\33[200C");
287 session_flush(s);
288
289 /* report cursor position */
290 session_clear_input(s);
291 session_printf(s, "\33[6n");
292 session_flush(s);
293
294 session_wait_for_chars(s, 1500, 100);
295
296 /* \e[12;34R */
297 if (s->ibuf[0] == ESC && s->ibuf[1] == '[' &&
298 sscanf((char *)&s->ibuf + 2, "%d;%d%[R]", &rows, &cols, &c) == 3 &&
299 c[0] == 'R') {
300 if (cols >= MIN_TERMINAL_COLUMNS && rows >= MIN_TERMINAL_LINES) {
301 s->terminal_columns = cols;
302 s->terminal_lines = rows;
303 session_logf(s, "Probed terminal size of %dx%d", cols, rows);
304 } else {
305 session_logf(s, "Bogus terminal size probe response: %dx%d",
306 cols, rows);
307 }
308 }
309
310 /* restore saved cursor position */
311 session_clear_input(s);
312 session_printf(s, "\33[u");
313 session_flush(s);
314 }