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