xref: /netbsd-src/external/bsd/less/dist/lesstest/lt_screen.c (revision e4a6e799a67c2028562d75b4e61407b22434aa36)
1 #include <stdio.h>
2 #include <string.h>
3 #include <unistd.h>
4 #include <stdlib.h>
5 #include <fcntl.h>
6 #include <signal.h>
7 #include "lt_types.h"
8 #include "wchar.h"
9 
10 static const char version[] = "lt_screen|v=1";
11 
12 #define ERROR_CHAR       ' '
13 #define WIDESHADOW_CHAR  ((wchar)0)
14 static char const* osc8_start[] = { "\033]8;", NULL };
15 static char const* osc8_end[] = { "\033\\", "\7", NULL };
16 
17 #define NUM_LASTCH 4 // must be >= strlen(osc8_*[*])
18 static wchar lastch[NUM_LASTCH];
19 static int lastch_curr = 0;
20 
usage(void)21 int usage(void) {
22 	fprintf(stderr, "usage: lt_screen [-w width] [-h height] [-qv]\n");
23 	return 0;
24 }
25 
26 // ------------------------------------------------------------------
27 
28 #define MAX_PARAMS         3
29 
30 typedef struct ScreenChar {
31 	wchar ch;
32 	Attr attr;
33 	Color fg_color;
34 	Color bg_color;
35 } ScreenChar;
36 
37 typedef struct ScreenState {
38 	ScreenChar* chars;
39 	int w;
40 	int h;
41 	int cx;
42 	int cy;
43 	Attr curr_attr;
44 	Color curr_fg_color;
45 	Color curr_bg_color;
46 	int param_top;
47 	int params[MAX_PARAMS+1];
48 	int in_esc;
49 	int in_osc8;
50 } ScreenState;
51 
52 static ScreenState screen;
53 static int ttyin; // input text and control sequences
54 static int ttyout; // output for screen dump
55 static int quiet = 0;
56 static int verbose = 0;
57 
58 // ------------------------------------------------------------------
59 
60 // Initialize ScreenState.
screen_init(void)61 static void screen_init(void) {
62 	screen.w = 80;
63 	screen.h = 24;
64 	screen.cx = 0;
65 	screen.cy = 0;
66 	screen.in_esc = 0;
67 	screen.in_osc8 = 0;
68 	screen.curr_attr = 0;
69 	screen.curr_fg_color = screen.curr_bg_color = NULL_COLOR;
70 	screen.param_top = -1;
71 	screen.params[0] = 0;
72 }
73 
num_params(void)74 static int num_params(void) {
75 	return screen.param_top+1;
76 }
77 
param_print(void)78 static void param_print(void) {
79 	int i;
80 	fprintf(stderr, "(");
81 	for (i = 0; i < num_params(); ++i)
82 		fprintf(stderr, "%d ", screen.params[i]);
83 	fprintf(stderr, ")");
84 }
85 
param_clear(void)86 static void param_clear(void) {
87 	screen.param_top = -1;
88 }
89 
param_push(int v)90 static void param_push(int v) {
91 	if (screen.param_top >= (int) countof(screen.params)-1) {
92 		param_clear();
93 		return;
94 	}
95 	screen.params[++screen.param_top] = v;
96 }
97 
param_pop(void)98 static int param_pop(void){
99 	if (num_params() == 0)
100 		return -1; // missing param
101 	return screen.params[screen.param_top--];
102 }
103 
screen_x(int x)104 static int screen_x(int x) {
105 	if (x < 0) x = 0;
106 	if (x >= screen.w) x = screen.w-1;
107 	return x;
108 }
109 
screen_y(int y)110 static int screen_y(int y) {
111 	if (y < 0) y = 0;
112 	if (y >= screen.h) y = screen.h-1;
113 	return y;
114 }
115 
116 // Return the char at a given screen position.
screen_char(int x,int y)117 static ScreenChar* screen_char(int x, int y) {
118 	x = screen_x(x);
119 	y = screen_y(y);
120 	return &screen.chars[y * screen.w + x];
121 }
122 
123 // Step the cursor after printing a char.
screen_incr(int * px,int * py)124 static int screen_incr(int* px, int* py) {
125 	if (++(*px) >= screen.w) {
126 		*px = 0;
127 		if (++(*py) >= screen.h) {
128 			*py = 0;
129 			return 0;
130 		}
131 	}
132 	return 1;
133 }
134 
135 // Set the value, attributes and colors of a char on the screen.
screen_char_set(int x,int y,wchar ch,Attr attr,Color fg_color,Color bg_color)136 static void screen_char_set(int x, int y, wchar ch, Attr attr, Color fg_color, Color bg_color) {
137 	ScreenChar* sc = screen_char(x, y);
138 	sc->ch = ch;
139 	sc->attr = attr;
140 	sc->fg_color = fg_color;
141 	sc->bg_color = bg_color;
142 }
143 
screen_clear(int x,int y,int count)144 static int screen_clear(int x, int y, int count) {
145 	while (count-- > 0) {
146 		screen_char_set(x, y, '_', 0, NULL_COLOR, NULL_COLOR);
147 		screen_incr(&x, &y);
148 	}
149 	return 1;
150 }
151 
store_hex(byte ** pp,int val)152 static void store_hex(byte** pp, int val) {
153 	char hexchar[] = "0123456789ABCDEF";
154 	*(*pp)++ = hexchar[(val >> 4) & 0xf];
155 	*(*pp)++ = hexchar[val & 0xf];
156 }
157 
158 // Print an encoded image of the current screen to ttyout.
159 // The LTS_CHAR_* metachars encode changes of color and attribute.
screen_read(int x,int y,int count)160 static int screen_read(int x, int y, int count) {
161 	Attr attr = 0;
162 	int fg_color = NULL_COLOR;
163 	int bg_color = NULL_COLOR;
164 	while (count-- > 0) {
165 		byte buf[32];
166 		byte* bufp = buf;
167 		ScreenChar* sc = screen_char(x, y);
168 		if (sc->attr != attr) {
169 			attr = sc->attr;
170 			*bufp++ = LTS_CHAR_ATTR;
171 			store_hex(&bufp, attr);
172 		}
173 		if (sc->fg_color != fg_color) {
174 			fg_color = sc->fg_color;
175 			*bufp++ = LTS_CHAR_FG_COLOR;
176 			store_hex(&bufp, fg_color);
177 		}
178 		if (sc->bg_color != bg_color) {
179 			bg_color = sc->bg_color;
180 			*bufp++ = LTS_CHAR_BG_COLOR;
181 			store_hex(&bufp, bg_color);
182 		}
183 		if (x == screen.cx && y == screen.cy)
184 			*bufp++ = LTS_CHAR_CURSOR;
185 		if (sc->ch == '\\' || sc->ch == LTS_CHAR_ATTR || sc->ch == LTS_CHAR_FG_COLOR || sc->ch == LTS_CHAR_BG_COLOR || sc->ch == LTS_CHAR_CURSOR)
186 			*bufp++ = '\\';
187 		store_wchar(&bufp, sc->ch);
188 		write(ttyout, buf, bufp-buf);
189 		screen_incr(&x, &y);
190 	}
191 	write(ttyout, "\n", 1);
192 	return 1;
193 }
194 
screen_move(int x,int y)195 static int screen_move(int x, int y) {
196 	screen.cx = x;
197 	screen.cy = y;
198 	return 1;
199 }
200 
screen_cr(void)201 static int screen_cr(void) {
202 	screen.cx = 0;
203 	return 1;
204 }
205 
screen_bs(void)206 static int screen_bs(void) {
207 	if (screen.cx <= 0) return 0;
208 	--screen.cx;
209 	return 1;
210 }
211 
screen_scroll(void)212 static int screen_scroll(void) {
213 	int len = screen.w * (screen.h-1);
214 	memmove(screen_char(0,0), screen_char(0,1), len * sizeof(ScreenChar));
215 	screen_clear(0, screen.h-1, screen.w);
216 	return 1;
217 }
218 
screen_rscroll(void)219 static int screen_rscroll(void) {
220 	int len = screen.w * (screen.h-1);
221 	memmove(screen_char(0,1), screen_char(0,0), len * sizeof(ScreenChar));
222 	screen_clear(0, 0, screen.w);
223 	return 1;
224 }
225 
screen_set_attr(int attr)226 static int screen_set_attr(int attr) {
227 	screen.curr_attr |= attr;
228 	if (verbose) fprintf(stderr, "[%d,%d] set_attr(%d)=%d\n", screen.cx, screen.cy, attr, screen.curr_attr);
229 	return 1;
230 }
231 
screen_clear_attr(int attr)232 static int screen_clear_attr(int attr) {
233 	screen.curr_attr &= ~attr;
234 	if (verbose) fprintf(stderr, "[%d,%d] clr_attr(%d)=%d\n", screen.cx, screen.cy, attr, screen.curr_attr);
235 	return 1;
236 }
237 
238 // ------------------------------------------------------------------
239 // lt_screen supports certain ANSI color values.
240 // This simplifies testing SGR sequences with less -R
241 // compared to inventing custom color sequences.
screen_set_color(int color)242 static int screen_set_color(int color) {
243 	int ret = 0;
244 	switch (color) {
245 	case 1:  ret = screen_set_attr(ATTR_BOLD); break;
246 	case 4:  ret = screen_set_attr(ATTR_UNDERLINE); break;
247 	case 5:
248 	case 6:  ret = screen_set_attr(ATTR_BLINK); break;
249 	case 7:  ret = screen_set_attr(ATTR_STANDOUT); break;
250 	case 21:
251 	case 22: ret = screen_clear_attr(ATTR_BOLD); break;
252 	case 24: ret = screen_clear_attr(ATTR_UNDERLINE); break;
253 	case 25: ret = screen_clear_attr(ATTR_BLINK); break;
254 	case 27: ret = screen_clear_attr(ATTR_STANDOUT); break;
255 	// case 38: break;
256 	// case 48: break;
257 	default:
258 		if (color <= 0) {
259 			screen.curr_fg_color = screen.curr_bg_color = NULL_COLOR;
260 			screen.curr_attr = 0;
261 			ret = 1;
262 		} else if ((color >= 30 && color <= 37) || (color >= 90 && color <= 97)) {
263 			screen.curr_fg_color = color;
264 			ret = 1;
265 		} else if ((color >= 40 && color <= 47) || (color >= 100 && color <= 107)) {
266 			screen.curr_bg_color = color;
267 			ret = 1;
268 		} else {
269 			fprintf(stderr, "[%d,%d] unrecognized color %d\n", screen.cx, screen.cy, color);
270 		}
271 		if (verbose) fprintf(stderr, "[%d,%d] set_color(%d)=%d/%d\n", screen.cx, screen.cy, color, screen.curr_fg_color, screen.curr_bg_color);
272 		break;
273 	}
274 	return ret;
275 }
276 
277 // ------------------------------------------------------------------
278 
beep(void)279 static void beep(void) {
280 	if (!quiet)
281 		fprintf(stderr, "\7");
282 }
283 
284 // Execute an escape sequence ending with a given char.
exec_esc(wchar ch)285 static int exec_esc(wchar ch) {
286 	int x, y, count;
287 	if (verbose) {
288 		fprintf(stderr, "exec ESC-%c ", (char)ch);
289 		param_print();
290 		fprintf(stderr, "\n");
291 	}
292 	switch (ch) {
293 	case 'A': // clear all
294 		return screen_clear(0, 0, screen.w * screen.h);
295 	case 'L': // clear from cursor to end of line
296 		return screen_clear(screen.cx, screen.cy, screen.w - screen.cx);
297 	case 'S': // clear from cursor to end of screen
298 		return screen_clear(screen.cx, screen.cy,
299 			(screen.w - screen.cx) + (screen.h - screen.cy -1) * screen.w);
300 	case 'R': // read N3 chars starting at (N1,N2)
301 		count = param_pop();
302 		y = param_pop();
303 		x = param_pop();
304 		if (x < 0) x = 0;
305 		if (y < 0) y = 0;
306 		if (count < 0) count = 0;
307 		return screen_read(x, y, count);
308 	case 'j': // jump cursor to (N1,N2)
309 		y = param_pop();
310 		x = param_pop();
311 		if (x < 0) x = 0;
312 		if (y < 0) y = 0;
313 		return screen_move(x, y);
314 	case 'g': // visual bell
315 		return 0;
316 	case 'h': // cursor home
317 		return screen_move(0, 0);
318 	case 'l': // cursor lower left
319 		return screen_move(0, screen.h-1);
320 	case 'r': // reverse scroll
321 		return screen_rscroll();
322 	case '<': // cursor left to start of line
323 		return screen_cr();
324 	case 'e': // exit bold
325 		return screen_clear_attr(ATTR_BOLD);
326 	case 'b': // enter blink
327 		return screen_set_attr(ATTR_BLINK);
328 	case 'c': // exit blink
329 		return screen_clear_attr(ATTR_BLINK);
330 	case 'm': // SGR (Select  Graphics Rendition)
331 		if (num_params() == 0) {
332 			screen_set_color(-1);
333 		} else {
334 			while (num_params() > 0)
335 				screen_set_color(param_pop());
336 		}
337 		return 0;
338 	case '?': // print version string
339 		write(ttyout, version, strlen(version));
340 		return 1;
341 	default:
342 		return 0;
343 	}
344 }
345 
346 // Print a char on the screen.
347 // Handles cursor movement and scrolling.
add_char(wchar ch)348 static int add_char(wchar ch) {
349 	//if (verbose) fprintf(stderr, "add (%c) %lx at %d,%d\n", (char)ch, (long)ch, screen.cx, screen.cy);
350 	screen_char_set(screen.cx, screen.cy, ch, screen.curr_attr, screen.curr_fg_color, screen.curr_bg_color);
351 	int fits = 1;
352 	int zero_width = (is_composing_char(ch) ||
353 	    (screen.cx > 0 && is_combining_char(screen_char(screen.cx-1,screen.cy)->ch, ch)));
354 	if (!zero_width) {
355 		fits = screen_incr(&screen.cx, &screen.cy);
356 		if (fits) {
357 			if (is_wide_char(ch)) {
358 				// The "shadow" is the second column used by a wide char.
359 				screen_char_set(screen.cx, screen.cy, WIDESHADOW_CHAR, 0, NULL_COLOR, NULL_COLOR);
360 				fits = screen_incr(&screen.cx, &screen.cy);
361 			} else {
362 				ScreenChar* sc = screen_char(screen.cx, screen.cy);
363 				if (sc->ch == WIDESHADOW_CHAR) {
364 					// We overwrote the first half of a wide character.
365 					// Change the orphaned shadow to an error char.
366 					screen_char_set(screen.cx, screen.cy, ERROR_CHAR, screen.curr_attr, NULL_COLOR, NULL_COLOR);
367 				}
368 			}
369 		}
370 	}
371 	if (!fits) { // Wrap at bottom of screen = scroll
372 		screen.cx = 0;
373 		screen.cy = screen.h-1;
374 		return screen_scroll();
375 	}
376 	return 1;
377 }
378 
379 // Remember the last few chars sent to the screen.
add_last(wchar ch)380 static void add_last(wchar ch) {
381 	lastch[lastch_curr++] = ch;
382 	if (lastch_curr >= NUM_LASTCH) lastch_curr = 0;
383 }
384 
385 // Do the last entered characters match a string?
last_matches_str(char const * str)386 static int last_matches_str(char const* str) {
387 	int ci = lastch_curr;
388 	int si;
389 	for (si = strlen(str)-1; si >= 0; --si) {
390 		ci = (ci > 0) ? ci-1 : NUM_LASTCH-1;
391 		if (str[si] != lastch[ci]) return 0;
392 	}
393 	return 1;
394 }
395 
396 // Do the last entered characters match any one of a list of strings?
last_matches(const char * const * tbl)397 static int last_matches(const char* const* tbl) {
398 	int ti;
399 	for (ti = 0; tbl[ti] != NULL; ++ti) {
400 		if (last_matches_str(tbl[ti]))
401 			return 1;
402 	}
403 	return 0;
404 }
405 
406 // Handle a char sent to the screen while it is receiving an escape sequence.
process_esc(wchar ch)407 static int process_esc(wchar ch) {
408 	int ok = 1;
409 	if (screen.in_osc8) {
410 		if (last_matches(osc8_end)) {
411 			screen.in_osc8 = screen.in_esc = 0;
412 		} else {
413 			// Discard everything between osc8_start and osc8_end.
414 		}
415 	} else if (last_matches(osc8_start)) {
416 		param_pop(); // pop the '8'
417 		screen.in_osc8 = 1;
418 	} else if (ch >= '0' && ch <= '9') {
419 		int d = (num_params() == 0) ? 0 : screen.params[screen.param_top--];
420 		param_push(10 * d + ch - '0');
421 	} else if (ch == ';') {
422 		param_push(0);
423 	} else if (ch == '[' || ch == ']') {
424 		; // Ignore ANSI marker
425 	} else { // end of escape sequence
426 		screen.in_esc = 0;
427 		ok = exec_esc(ch);
428 		param_clear();
429 	}
430 	return ok;
431 }
432 
433 // Handle a char sent to the screen.
434 // Normally it is just printed, but some control chars are handled specially.
process_char(wchar ch)435 static int process_char(wchar ch) {
436 	int ok = 1;
437 	add_last(ch);
438 	if (screen.in_esc) {
439 		ok = process_esc(ch);
440 	} else if (ch == ESC) {
441 		screen.in_esc = 1;
442 	} else if (ch == '\r') {
443 		screen_cr();
444 	} else if (ch == '\b') {
445 		screen_bs();
446 	} else if (ch == '\n') {
447 		if (screen.cy < screen.h-1)
448 			++screen.cy;
449 		else
450 			screen_scroll();
451 		screen_cr(); // auto CR
452 	} else if (ch == '\7') {
453 		beep();
454 	} else if (ch == '\t') {
455 		ok = add_char(' '); // hardware tabs not supported
456 	} else if (ch >= '\40') { // printable char
457 		ok = add_char(ch);
458 	}
459 	return ok;
460 }
461 
462 // ------------------------------------------------------------------
463 
setup(int argc,char ** argv)464 static int setup(int argc, char** argv) {
465 	int ch;
466 	screen_init();
467 	while ((ch = getopt(argc, argv, "h:qvw:")) != -1) {
468 		switch (ch) {
469 		case 'h':
470 			screen.h = atoi(optarg);
471 			break;
472 		case 'q':
473 			quiet = 1;
474 			break;
475 		case 'v':
476 			++verbose;
477 			break;
478 		case 'w':
479 			screen.w = atoi(optarg);
480 			break;
481 		default:
482 			return usage();
483 		}
484 	}
485 	int len = screen.w * screen.h;
486 	screen.chars = malloc(len * sizeof(ScreenChar));
487 	screen_clear(0, 0, len);
488 	if (optind >= argc) {
489 		ttyin = 0;
490 		ttyout = 1;
491 	} else {
492 		ttyin = ttyout = open(argv[optind], O_RDWR);
493 		if (ttyin < 0) {
494 			fprintf(stderr, "cannot open %s\n", argv[optind]);
495 			return 0;
496 		}
497 	}
498 	return 1;
499 }
500 
set_signal(int signum,void (* handler)(int))501 static void set_signal(int signum, void (*handler)(int)) {
502 	struct sigaction sa;
503 	sa.sa_handler = handler;
504 	sa.sa_flags = 0;
505 	sigemptyset(&sa.sa_mask);
506 	sigaction(signum, &sa, NULL);
507 }
508 
main(int argc,char ** argv)509 int main(int argc, char** argv) {
510 	set_signal(SIGINT,  SIG_IGN);
511 	set_signal(SIGQUIT, SIG_IGN);
512 	set_signal(SIGKILL, SIG_IGN);
513 	if (!setup(argc, argv))
514 		return RUN_ERR;
515 	for (;;) {
516 		wchar ch = read_wchar(ttyin);
517 		//if (verbose) fprintf(stderr, "screen read %c (%lx)\n", pr_ascii(ch), ch);
518 		if (ch == 0)
519 			break;
520 		if (!process_char(ch))
521 			beep();
522 	}
523 	return RUN_OK;
524 }
525