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