Download
jcs
/subtext
/ansi.c
(View History)
jcs ansi: Fix panic message | Latest amendment: 425 on 2023-03-15 |
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, n; |
57 | size_t len; |
58 | |
59 | ansi_out = (char *)&ansi_buf[last_ansi_buf]; |
60 | if (++last_ansi_buf >= nitems(ansi_buf)) |
61 | last_ansi_buf = 0; |
62 | |
63 | *ansi_out = '\0'; |
64 | |
65 | va_start(ap, s); |
66 | while ((attr = va_arg(ap, short)) != ANSI_END) { |
67 | len = 0; |
68 | switch (attr) { |
69 | case ANSI_RESET: |
70 | if (s->vt100) |
71 | len = strlcat(ansi_out, "\33[0m", sizeof(ansi_buf[0])); |
72 | break; |
73 | case ANSI_BOLD: |
74 | if (s->vt100) |
75 | len = strlcat(ansi_out, "\33[1m", sizeof(ansi_buf[0])); |
76 | break; |
77 | case ANSI_UNDERLINE: |
78 | if (s->vt100) |
79 | len = strlcat(ansi_out, "\33[4m", sizeof(ansi_buf[0])); |
80 | break; |
81 | case ANSI_REVERSE: |
82 | if (s->vt100) |
83 | len = strlcat(ansi_out, "\33[7m", sizeof(ansi_buf[0])); |
84 | break; |
85 | |
86 | case ANSI_CLEAR_SCREEN: |
87 | if (s->vt100) |
88 | len = strlcat(ansi_out, "\33[2J", sizeof(ansi_buf[0])); |
89 | else if (s->vt52) |
90 | /* XXX: changes cursor position */ |
91 | len = strlcat(ansi_out, "\33H\33J", sizeof(ansi_buf[0])); |
92 | break; |
93 | case ANSI_ERASE_LINE: |
94 | if (s->vt100) |
95 | /* XXX: this doesn't work on windows command-line telnet */ |
96 | len = strlcat(ansi_out, "\33[2K", sizeof(ansi_buf[0])); |
97 | else if (s->vt52) |
98 | /* XXX: changes cursor position */ |
99 | len = strlcat(ansi_out, "\r\33K", sizeof(ansi_buf[0])); |
100 | break; |
101 | case ANSI_BACKSPACE: |
102 | if (s->vt100) |
103 | len = strlcat(ansi_out, "\33[D \33[D", sizeof(ansi_buf[0])); |
104 | else if (s->vt52) |
105 | len = strlcat(ansi_out, "\33D \33D", sizeof(ansi_buf[0])); |
106 | else |
107 | len = strlcat(ansi_out, "\10 \10", 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 ANSI_END?"); |
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 | size_t ilen = strlen(inp); |
232 | size_t n, olen = 0; |
233 | short in_csi; |
234 | |
235 | if (outp) { |
236 | *outp = xmalloc(ilen + 1); |
237 | if (*outp == NULL) |
238 | return 0; |
239 | } |
240 | |
241 | for (n = 0, in_csi = 0; n < ilen; ) { |
242 | if (in_csi) { |
243 | /* eat "12;34;56" */ |
244 | while (n < ilen) { |
245 | if ((inp[n] >= '0' && inp[n] <= '9') || inp[n] == ';') |
246 | n++; |
247 | else |
248 | break; |
249 | } |
250 | |
251 | /* command character */ |
252 | if (n < ilen) |
253 | n++; |
254 | |
255 | in_csi = 0; |
256 | } else if (inp[n] == '\33' && inp[n + 1] == '[') { |
257 | in_csi = 1; |
258 | n += 2; |
259 | } else if (inp[n] == '\r') { |
260 | /* consider this an ansi character */ |
261 | n++; |
262 | } else { |
263 | if (outp) |
264 | (*outp)[olen] = inp[n]; |
265 | olen++; |
266 | n++; |
267 | } |
268 | } |
269 | |
270 | return olen; |
271 | } |
272 | |
273 | void |
274 | ansi_probe_screen_size(struct session *s) |
275 | { |
276 | short cols, rows; |
277 | char c[2] = { 0 }; |
278 | |
279 | if (!s->vt100) |
280 | return; |
281 | |
282 | /* save cursor position */ |
283 | session_clear_input(s); |
284 | session_printf(s, "\33[s"); |
285 | |
286 | /* go to the (hopefully) edge of the screen */ |
287 | session_printf(s, "\33[200B\33[200C"); |
288 | session_flush(s); |
289 | |
290 | /* report cursor position */ |
291 | session_clear_input(s); |
292 | session_printf(s, "\33[6n"); |
293 | session_flush(s); |
294 | |
295 | session_wait_for_chars(s, 1500, 100); |
296 | |
297 | /* \e[12;34R */ |
298 | if (s->ibuf[0] == ESC && s->ibuf[1] == '[' && |
299 | sscanf((char *)&s->ibuf + 2, "%d;%d%1[R]", &rows, &cols, &c) == 3 && |
300 | c[0] == 'R') { |
301 | if (cols >= MIN_TERMINAL_COLUMNS && rows >= MIN_TERMINAL_LINES) { |
302 | s->terminal_columns = cols; |
303 | s->terminal_lines = rows; |
304 | session_logf(s, "Probed terminal size of %dx%d", cols, rows); |
305 | } else { |
306 | session_logf(s, "Bogus terminal size probe response: %dx%d", |
307 | cols, rows); |
308 | } |
309 | } |
310 | |
311 | /* restore saved cursor position */ |
312 | session_clear_input(s); |
313 | session_printf(s, "\33[u"); |
314 | session_flush(s); |
315 | } |