2b5fae213552f82447280421cd5d560b2c305330
[apps/madtty.git] / madtty / madtty.c
1 /*
2     LICENSE INFORMATION:
3     This program is free software; you can redistribute it and/or
4     modify it under the terms of the GNU Lesser General Public
5     License (LGPL) as published by the Free Software Foundation.
6
7     Please refer to the COPYING file for more information.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12     General Public License for more details.
13
14     You should have received a copy of the GNU Lesser General Public
15     License along with this program; if not, write to the Free Software
16     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
17
18     Copyright © 2004 Bruno T. C. de Oliveira
19     Copyright © 2006 Pierre Habouzit
20  */
21
22 #include <ctype.h>
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <locale.h>
26 #include <pty.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <sys/ioctl.h>
31
32 #include "madtty.h"
33
34 static char const * const keytable[KEY_MAX+1] = {
35     ['\n']          = "\r",
36     [KEY_UP]        = "\e[A",
37     [KEY_DOWN]      = "\e[B",
38     [KEY_RIGHT]     = "\e[C",
39     [KEY_LEFT]      = "\e[D",
40     [KEY_BACKSPACE] = "\b",
41     [KEY_HOME]      = "\e[1~",
42     [KEY_IC]        = "\e[2~",
43     [KEY_DC]        = "\e[3~",
44     [KEY_END]       = "\e[4~",
45     [KEY_PPAGE]     = "\e[5~",
46     [KEY_NPAGE]     = "\e[6~",
47     [KEY_SUSPEND]   = "\x1A",  /* Ctrl+Z gets mapped to this */
48     [KEY_F(1)]      = "\e[[A",
49     [KEY_F(2)]      = "\e[[B",
50     [KEY_F(3)]      = "\e[[C",
51     [KEY_F(4)]      = "\e[[D",
52     [KEY_F(5)]      = "\e[[E",
53     [KEY_F(6)]      = "\e[17~",
54     [KEY_F(7)]      = "\e[18~",
55     [KEY_F(8)]      = "\e[19~",
56     [KEY_F(9)]      = "\e[20~",
57     [KEY_F(10)]     = "\e[21~",
58 };
59
60 static void mtty_row_set(mtty_row_t *row, int start, int len, uint16_t attr)
61 {
62     wmemset(row->text + start, 0, len);
63     for (int i = start; i < len + start; i++) {
64         row->attr[i] = attr;
65     }
66 }
67
68 __attribute__((noinline))
69 static void mtty_row_roll(mtty_row_t *start, mtty_row_t *end, int count)
70 {
71     int n = end - start;
72
73     count %= n;
74     if (count < 0)
75         count += n;
76
77     if (count) {
78         mtty_row_t *buf = alloca(count * sizeof(mtty_row_t));
79
80         memcpy(buf, start, count * sizeof(mtty_row_t));
81         memmove(start, start + count, (n - count) * sizeof(mtty_row_t));
82         memcpy(end - count, buf, count * sizeof(mtty_row_t));
83     }
84 }
85
86 static void clamp_cursor_to_bounds(madtty_t *rt)
87 {
88     if (rt->curs_row < rt->lines) {
89         rt->curs_row = rt->lines;
90     }
91     if (rt->curs_row >= rt->lines + rt->rows) {
92         rt->curs_row = rt->lines + rt->rows - 1;
93     }
94
95     if (rt->curs_col < 0) {
96         rt->curs_col = 0;
97     }
98     if (rt->curs_col >= rt->cols) {
99         rt->curs_col = rt->cols - 1;
100     }
101 }
102
103 static void cursor_line_down(madtty_t *rt)
104 {
105     rt->curs_row++;
106     if (rt->curs_row < rt->scroll_bot)
107         return;
108
109     rt->curs_row = rt->scroll_bot - 1;
110     mtty_row_roll(rt->scroll_top, rt->scroll_bot, 1);
111     mtty_row_set(rt->curs_row, 0, rt->cols, 0);
112 }
113
114 static void cursor_line_up(madtty_t *rt)
115 {
116     rt->curs_row--;
117     if (rt->curs_row >= rt->scroll_top)
118         return;
119
120     /* must scroll the scrolling region up by 1 line, and put cursor on
121      * first line of it */
122     rt->curs_row = rt->scroll_top;
123     mtty_row_roll(rt->scroll_top, rt->scroll_bot, -1);
124     mtty_row_set(rt->curs_row, 0, rt->cols, 0);
125 }
126
127 static uint16_t build_attrs(unsigned curattrs)
128 {
129     return ((curattrs & ~A_COLOR) | COLOR_PAIR(curattrs & 0xff))
130         >> NCURSES_ATTR_SHIFT;
131 }
132
133 static void put_normal_char(madtty_t *rt, wchar_t c)
134 {
135     mtty_row_t *tmp;
136
137     if (rt->curs_col >= rt->cols) {
138         rt->curs_col = 0;
139         cursor_line_down(rt);
140     }
141
142     tmp = rt->curs_row;
143
144     if (rt->insert) {
145         wmemmove(tmp->text + rt->curs_col + 1, tmp->text + rt->curs_col,
146                  (rt->cols - rt->curs_col - 1));
147         memmove(tmp->attr + rt->curs_col + 1, tmp->attr + rt->curs_col,
148                 (rt->cols - rt->curs_col - 1) * sizeof(tmp->attr[0]));
149     }
150
151     tmp->text[rt->curs_col] = c;
152     tmp->attr[rt->curs_col] = build_attrs(rt->curattrs);
153     rt->curs_col++;
154 }
155
156 static void put_graphmode_char(madtty_t *rt, int c)
157 {
158     char nc;
159     /* do some very pitiful translation to regular ascii chars */
160     switch (c) {
161       case 'j': case 'k': case 'l': case 'm': case 'n': case 't':
162       case 'u': case 'v': case 'w':
163         nc = '+'; break;
164       case 'x':
165         nc = '|'; break;
166       default:
167         nc = '%';
168     }
169
170     put_normal_char(rt, nc);
171 }
172
173 static void new_escape_sequence(madtty_t *rt)
174 {
175     rt->escaped = true;
176     rt->esbuf_len = 0;
177     rt->esbuf[0] = '\0';
178 }
179
180 static void cancel_escape_sequence(madtty_t *rt)
181 {
182     rt->escaped = false;
183     rt->esbuf_len = 0;
184     rt->esbuf[0] = '\0';
185 }
186
187 static void handle_control_char(madtty_t *rt, int c)
188 {
189     switch (c) {
190       case '\r':  /* carriage return */
191         rt->curs_col = 0;
192         break;
193
194       case '\n':  /* line feed */
195         rt->curs_col = 0;
196         cursor_line_down(rt);
197         break;
198
199       case '\b': /* backspace */
200         if (rt->curs_col > 0)
201             rt->curs_col--;
202         break;
203
204       case '\t': /* tab */
205         rt->curs_col = (rt->curs_col + 8) & ~7;
206         clamp_cursor_to_bounds(rt);
207         break;
208
209       case '\x1b': /* begin escape sequence (aborting previous one if any) */
210         new_escape_sequence(rt);
211         break;
212
213       case '\x0e': /* enter graphical character mode */
214         rt->graphmode = true;
215         break;
216
217       case '\x0f': /* exit graphical character mode */
218         rt->graphmode = false;
219         break;
220
221       case '\x9b': /* CSI character. Equivalent to ESC [ */
222         new_escape_sequence(rt);
223         rt->esbuf[rt->esbuf_len++] = '[';
224         break;
225
226       case '\x18':
227       case '\x1a': /* these interrupt escape sequences */
228         cancel_escape_sequence(rt);
229         break;
230
231       case '\a': /* bell */
232         /* do nothing for now... maybe a visual bell would be nice? */
233         break;
234     }
235 }
236
237 static bool is_valid_csi_ender(int c)
238 {
239     return (c >= 'a' && c <= 'z')
240         || (c >= 'A' && c <= 'Z')
241         || (c == '@' || c == '`');
242 }
243
244 /* interprets a 'set attribute' (SGR) CSI escape sequence */
245 static void interpret_csi_SGR(madtty_t *rt, int param[], int pcount)
246 {
247     int i;
248
249     if (pcount == 0) {
250         /* special case: reset attributes */
251         rt->curattrs = A_NORMAL;
252         return;
253     }
254
255     for (i = 0; i < pcount; i++) {
256         switch (param[i]) {
257 #define CASE(x, op)  case x: op; break
258             CASE(0,  rt->curattrs = A_NORMAL);
259             CASE(1,  rt->curattrs |= A_BOLD);
260             CASE(4,  rt->curattrs |= A_UNDERLINE);
261             CASE(5,  rt->curattrs |= A_BLINK);
262             CASE(6,  rt->curattrs |= A_BLINK);
263             CASE(7,  rt->curattrs |= A_REVERSE);
264             CASE(8,  rt->curattrs |= A_INVIS);
265             CASE(22, rt->curattrs &= ~A_BOLD);
266             CASE(24, rt->curattrs &= ~A_UNDERLINE);
267             CASE(25, rt->curattrs &= ~A_BLINK);
268             CASE(27, rt->curattrs &= ~A_REVERSE);
269             CASE(28, rt->curattrs &= ~A_INVIS);
270
271           case 30 ... 37:
272             rt->curattrs &= ~0xf0;
273             rt->curattrs |= (param[i] - 29) << 4;
274             break;
275
276           case 39:
277             rt->curattrs &= ~0xf0;
278             break;
279
280           case 40 ... 47:
281             rt->curattrs &= ~0x0f;
282             rt->curattrs |= (param[i] - 39);
283             break;
284
285           case 49:
286             rt->curattrs &= ~0x0f;
287             break;
288
289           default:
290             break;
291         }
292     }
293 }
294
295 /* interprets an 'erase display' (ED) escape sequence */
296 static void interpret_csi_ED(madtty_t *rt, int param[], int pcount)
297 {
298     int r;
299     int start_row, start_col, end_row, end_col;
300
301     /* decide range */
302     if (pcount && param[0] == 2) {
303         start_row = 0;
304         start_col = 0;
305         end_row = rt->rows - 1;
306         end_col = rt->cols - 1;
307     } else
308     if (pcount && param[0] == 1) {
309         start_row = 0;
310         start_col = 0;
311         end_row = rt->curs_row - rt->lines;
312         end_col = rt->curs_col;
313     } else {
314         start_row = rt->curs_row - rt->lines;
315         start_col = rt->curs_col;
316         end_row = rt->rows - 1;
317         end_col = rt->cols - 1;
318     }
319
320     /* clean range */
321     mtty_row_set(rt->lines + start_row, start_col, rt->cols - start_col,
322                  build_attrs(rt->curattrs));
323     for (r = start_row + 1; r < end_row; r++) {
324         mtty_row_set(rt->lines + r, 0, rt->cols, build_attrs(rt->curattrs));
325     }
326     mtty_row_set(rt->lines + end_row, 0, end_col + 1,
327                  build_attrs(rt->curattrs));
328 }
329
330 /* interprets a 'move cursor' (CUP) escape sequence */
331 static void interpret_csi_CUP(madtty_t *rt, int param[], int pcount)
332 {
333     if (pcount == 0) {
334         /* special case */
335         rt->curs_row = rt->lines;
336         rt->curs_col = 0;
337         return;
338     } else
339     if (pcount < 2) {
340         return;  /* malformed */
341     }
342
343     rt->curs_row = rt->lines + param[0] - 1;  /* convert from 1-based to 0-based */
344     rt->curs_col = param[1] - 1;  /* convert from 1-based to 0-based */
345
346     clamp_cursor_to_bounds(rt);
347 }
348
349 /* Interpret the 'relative mode' sequences: CUU, CUD, CUF, CUB, CNL,
350  * CPL, CHA, HPR, VPA, VPR, HPA */
351 static void interpret_csi_C(madtty_t *rt, char verb, int param[], int pcount)
352 {
353     int n = (pcount && param[0] > 0) ? param[0] : 1;
354
355     switch (verb) {
356       case 'A':           rt->curs_row -= n; break;
357       case 'B': case 'e': rt->curs_row += n; break;
358       case 'C': case 'a': rt->curs_col += n; break;
359       case 'D':           rt->curs_col -= n; break;
360       case 'E':           rt->curs_row += n; rt->curs_col = 0; break;
361       case 'F':           rt->curs_row -= n; rt->curs_col = 0; break;
362       case 'G': case '`': rt->curs_col  = param[0] - 1; break;
363       case 'd':           rt->curs_row  = rt->lines + param[0] - 1; break;
364     }
365
366     clamp_cursor_to_bounds(rt);
367 }
368
369 /* Interpret the 'erase line' escape sequence */
370 static void interpret_csi_EL(madtty_t *rt, int param[], int pcount)
371 {
372     int start, len;
373     int cmd = pcount ? param[0] : 0;
374
375     switch (cmd) {
376       case 1:
377         start = 0;
378         len   = rt->curs_col + 1;
379         break;
380       case 2:
381         start = 0;
382         len   = rt->cols;
383         break;
384       default:
385         start = rt->curs_col;
386         len   = rt->cols - start;
387         break;
388     }
389
390     mtty_row_set(rt->curs_row, start, len, build_attrs(rt->curattrs));
391 }
392
393 /* Interpret the 'insert blanks' sequence (ICH) */
394 static void interpret_csi_ICH(madtty_t *rt, int param[], int pcount)
395 {
396     mtty_row_t *row = rt->curs_row;
397     int n = (pcount && param[0] > 0) ? param[0] : 1;
398     int i;
399
400     for (i = rt->cols - 1; i >= rt->curs_col + n; i--) {
401         row->text[i] = row->text[i - n];
402         row->attr[i] = row->attr[i - n];
403     }
404
405     mtty_row_set(row, rt->curs_col, n, build_attrs(rt->curattrs));
406 }
407
408 /* Interpret the 'delete chars' sequence (DCH) */
409 static void interpret_csi_DCH(madtty_t *rt, int param[], int pcount)
410 {
411     mtty_row_t *row = rt->curs_row;
412     int n = (pcount && param[0] > 0) ? param[0] : 1;
413     int i;
414
415     for (i = rt->curs_col; i < rt->cols; i++) {
416         if (i + n < rt->cols) {
417             row->text[i] = row->text[i + n];
418             row->attr[i] = row->attr[i + n];
419         } else {
420             row->text[i] = 0;
421             row->attr[i] = build_attrs(rt->curattrs);
422         }
423     }
424 }
425
426 /* Interpret an 'insert line' sequence (IL) */
427 static void interpret_csi_IL(madtty_t *rt, int param[], int pcount)
428 {
429     int n = (pcount && param[0] > 0) ? param[0] : 1;
430
431     if (rt->curs_row + n >= rt->scroll_bot) {
432         for (mtty_row_t *row = rt->curs_row; row < rt->scroll_bot; row++) {
433             mtty_row_set(row, 0, rt->cols, build_attrs(rt->curattrs));
434         }
435     } else {
436         mtty_row_roll(rt->curs_row, rt->scroll_bot, n);
437         for (mtty_row_t *row = rt->curs_row; row < rt->curs_row + n; row++) {
438             mtty_row_set(row, 0, rt->cols, build_attrs(rt->curattrs));
439         }
440     }
441
442 }
443
444 /* Interpret a 'delete line' sequence (DL) */
445 static void interpret_csi_DL(madtty_t *rt, int param[], int pcount)
446 {
447     int n = (pcount && param[0] > 0) ? param[0] : 1;
448
449     if (rt->curs_row + n >= rt->scroll_bot) {
450         for (mtty_row_t *row = rt->curs_row; row < rt->scroll_bot; row++) {
451             mtty_row_set(row, 0, rt->cols, build_attrs(rt->curattrs));
452         }
453     } else {
454         mtty_row_roll(rt->curs_row, rt->scroll_bot, -n);
455
456         for (mtty_row_t *row = rt->scroll_bot - n; row < rt->scroll_bot; row++) {
457             mtty_row_set(row, 0, rt->cols, build_attrs(rt->curattrs));
458         }
459     }
460 }
461
462 /* Interpret an 'erase characters' (ECH) sequence */
463 static void interpret_csi_ECH(madtty_t *rt, int param[], int pcount)
464 {
465     int n = (pcount && param[0] > 0) ? param[0] : 1;
466
467     if (rt->curs_col + n < rt->cols) {
468         n = rt->cols - rt->curs_col;
469     }
470     mtty_row_set(rt->curs_row, rt->curs_col, n, build_attrs(rt->curattrs));
471 }
472
473 /* Interpret a 'set scrolling region' (DECSTBM) sequence */
474 static void interpret_csi_DECSTBM(madtty_t *rt, int param[], int pcount)
475 {
476     int new_top, new_bot;
477
478     switch (pcount) {
479       case 0:
480         rt->scroll_top = rt->lines;
481         rt->scroll_bot = rt->lines + rt->rows;
482         break;
483       default:
484         return; /* malformed */
485
486       case 2:
487         new_top = param[0] - 1;
488         new_bot = param[1] - 1;
489
490         /* clamp to bounds */
491         if (new_top < 0)
492             new_top = 0;
493         if (new_top >= rt->rows)
494             new_top = rt->rows - 1;
495         if (new_bot < 0)
496             new_bot = 0;
497         if (new_bot >= rt->rows)
498             new_bot = rt->rows;
499
500         /* check for range validity */
501         if (new_top < new_bot) {
502             rt->scroll_top = rt->lines + new_top;
503             rt->scroll_bot = rt->lines + new_bot;
504         }
505         break;
506     }
507 }
508
509 static void es_interpret_csi(madtty_t *rt)
510 {
511     static int csiparam[MAX_CSI_ES_PARAMS];
512     int param_count = 0;
513     const char *p = rt->esbuf + 1;
514     char verb = rt->esbuf[rt->esbuf_len - 1];
515
516     if (!strncmp(rt->esbuf, "[?", 2)) { /* private-mode CSI, ignore */
517         return;
518     }
519
520     /* parse numeric parameters */
521     while (isdigit((unsigned char)*p) || *p == ';') {
522         if (*p == ';') {
523             if (param_count >= MAX_CSI_ES_PARAMS) return; /* too long! */
524             csiparam[param_count++] = 0;
525         } else {
526             if (param_count == 0) csiparam[param_count++] = 0;
527             csiparam[param_count - 1] *= 10;
528             csiparam[param_count - 1] += *p - '0';
529         }
530
531         p++;
532     }
533
534     /* delegate handling depending on command character (verb) */
535     switch (verb) {
536       case 'h':
537         if (param_count == 1 && csiparam[0] == 4) /* insert mode */
538             rt->insert = true;
539         break;
540       case 'l':
541         if (param_count == 1 && csiparam[0] == 4) /* replace mode */
542             rt->insert = false;
543         break;
544       case 'm': /* it's a 'set attribute' sequence */
545         interpret_csi_SGR(rt, csiparam, param_count); break;
546       case 'J': /* it's an 'erase display' sequence */
547         interpret_csi_ED(rt, csiparam, param_count); break;
548       case 'H': case 'f': /* it's a 'move cursor' sequence */
549         interpret_csi_CUP(rt, csiparam, param_count); break;
550       case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
551       case 'e': case 'a': case 'd': case '`':
552         /* it is a 'relative move' */
553         interpret_csi_C(rt, verb, csiparam, param_count); break;
554       case 'K': /* erase line */
555         interpret_csi_EL(rt, csiparam, param_count); break;
556       case '@': /* insert characters */
557         interpret_csi_ICH(rt, csiparam, param_count); break;
558       case 'P': /* delete characters */
559         interpret_csi_DCH(rt, csiparam, param_count); break;
560       case 'L': /* insert lines */
561         interpret_csi_IL(rt, csiparam, param_count); break;
562       case 'M': /* delete lines */
563         interpret_csi_DL(rt, csiparam, param_count); break;
564       case 'X': /* erase chars */
565         interpret_csi_ECH(rt, csiparam, param_count); break;
566       case 'r': /* set scrolling region */
567         interpret_csi_DECSTBM(rt, csiparam, param_count); break;
568       case 's': /* save cursor location */
569         rt->curs_srow = rt->curs_row - rt->lines;
570         rt->curs_scol = rt->curs_col;
571         break;
572       case 'u': /* restore cursor location */
573         rt->curs_row = rt->lines + rt->curs_srow;
574         rt->curs_col = rt->curs_scol;
575         clamp_cursor_to_bounds(rt);
576         break;
577       default:
578         break;
579     }
580 }
581
582 static void try_interpret_escape_seq(madtty_t *rt)
583 {
584     char firstchar = rt->esbuf[0];
585     char lastchar  = rt->esbuf[rt->esbuf_len-1];
586
587     if (!firstchar)
588         return;  /* too early to do anything */
589
590     /* interpret ESC-M as reverse line-feed */
591     if (firstchar == 'M') {
592         cursor_line_up(rt);
593         cancel_escape_sequence(rt);
594         return;
595     }
596
597     if (firstchar != '[' && firstchar != ']') {
598         /* unrecognized escape sequence. Let's forget about it. */
599         cancel_escape_sequence(rt);
600         return;
601     }
602
603     if (firstchar == '[' && is_valid_csi_ender(lastchar)) {
604         es_interpret_csi(rt);
605         cancel_escape_sequence(rt);
606     } else if (firstchar == ']' && lastchar == '\a') {
607         /* we have an xterm escape sequence: interpret it */
608
609         /* es_interpret_xterm_es(rt);     -- TODO!*/
610         cancel_escape_sequence(rt);
611     }
612
613     /* if the escape sequence took up all available space and could
614      * not yet be parsed, abort it */
615     if (rt->esbuf_len + 1 >= ESEQ_BUF_SIZE)
616         cancel_escape_sequence(rt);
617 }
618
619 int madtty_process(madtty_t *rt)
620 {
621     int res, pos = 0;
622
623     if (rt->pty < 0) {
624         errno = EINVAL;
625         return -1;
626     }
627
628     res = read(rt->pty, rt->rbuf + rt->rbuf_len,
629                sizeof(rt->rbuf) - rt->rbuf_len);
630     if (res < 0)
631         return -1;
632
633     rt->rbuf_len += res;
634     while (pos < rt->rbuf_len) {
635         wchar_t wc;
636         ssize_t len;
637
638         len = (ssize_t)mbrtowc(&wc, rt->rbuf + pos, rt->rbuf_len - pos,
639                                &rt->ps);
640         if (len < 0) {
641             rt->rbuf_len -= pos;
642             memmove(rt->rbuf, rt->rbuf + pos, rt->rbuf_len);
643             return len == -2 ? 0 : -1;
644         }
645
646         pos += len ? len : 1;
647
648         if (wc < ' ') {
649             handle_control_char(rt, wc);
650             continue;
651         }
652
653         if (rt->escaped && rt->esbuf_len < ESEQ_BUF_SIZE) {
654             /* append character to ongoing escape sequence */
655             rt->esbuf[rt->esbuf_len]   = wc;
656             rt->esbuf[++rt->esbuf_len] = 0;
657
658             try_interpret_escape_seq(rt);
659         } else
660         if (rt->graphmode) {
661             put_graphmode_char(rt, wc);
662         } else {
663             put_normal_char(rt, wc);
664         }
665     }
666
667     rt->rbuf_len -= pos;
668     memmove(rt->rbuf, rt->rbuf + pos, rt->rbuf_len);
669     return 0;
670 }
671
672 madtty_t *madtty_create(int rows, int cols)
673 {
674     madtty_t *rt;
675     int i;
676
677     if (rows <= 0 || cols <= 0)
678         return NULL;
679
680     rt = (madtty_t*)calloc(sizeof(madtty_t), 1);
681     if (!rt)
682         return NULL;
683
684     /* record dimensions */
685     rt->rows = rows;
686     rt->cols = cols;
687
688     /* default mode is replace */
689     rt->insert = false; 
690
691     /* create the cell matrix */
692     rt->lines = (mtty_row_t*)calloc(sizeof(mtty_row_t), rt->rows);
693     for (i = 0; i < rt->rows; i++) {
694         rt->lines[i].text = (wchar_t *)calloc(sizeof(wchar_t), rt->cols);
695         rt->lines[i].attr = (uint16_t *)calloc(sizeof(uint16_t), rt->cols);
696     }
697
698     rt->pty = -1;  /* no pty for now */
699
700     /* initialization of other public fields */
701     rt->curs_row = rt->lines;
702     rt->curs_col = 0;
703     rt->curattrs = A_NORMAL;  /* white text over black background */
704
705     /* initial scrolling area is the whole window */
706     rt->scroll_top = rt->lines;
707     rt->scroll_bot = rt->lines + rt->rows;
708
709     return rt;
710 }
711
712 void madtty_destroy(madtty_t *rt)
713 {
714     int i;
715     if (!rt)
716         return;
717
718     for (i = 0; i < rt->rows; i++) {
719         free(rt->lines[i].text);
720         free(rt->lines[i].attr);
721     }
722     free(rt->lines);
723     free(rt);
724 }
725
726 void madtty_draw(madtty_t *rt, WINDOW *win, int srow, int scol)
727 {
728     int i, j;
729
730     for (i = 0; i < rt->rows; i++) {
731         mtty_row_t *row = rt->lines + i;
732         wmove(win, srow + i, scol);
733
734         for (j = 0; j < rt->cols; j++) {
735             if (!j || row->attr[j] != row->attr[j - 1])
736                 wattrset(win, (attr_t)row->attr[j] << NCURSES_ATTR_SHIFT);
737             if (row->text[j] >= 128) {
738                 char buf[MB_CUR_MAX + 1];
739                 int len;
740
741                 len = wcrtomb(buf, row->text[j], NULL);
742                 waddnstr(win, buf, len);
743             } else {
744                 waddch(win, row->text[j] > ' ' ? row->text[j] : ' ');
745             }
746         }
747     }
748
749     wmove(win, srow + rt->curs_row - rt->lines, scol + rt->curs_col);
750 }
751
752 /******************************************************/
753
754 pid_t madtty_forkpty(madtty_t *rt, const char *path, const char *argv[])
755 {
756     struct winsize ws;
757     pid_t pid;
758
759     ws.ws_row    = rt->rows;
760     ws.ws_col    = rt->cols;
761     ws.ws_xpixel = ws.ws_ypixel = 0;
762
763     pid = forkpty(&rt->pty, NULL, NULL, &ws);
764     if (pid < 0)
765         return -1;
766
767     if (pid == 0) {
768         setsid();
769
770         setenv("TERM", "linux", 1);
771         execv(path, (char *const*)argv);
772         fprintf(stderr, "\nexecv() failed.\nCommand: '%s'\n", argv[0]);
773         exit(1);
774     }
775
776     return rt->childpid = pid;
777 }
778
779 static int madtty_write(madtty_t *rt, const char *data, int len)
780 {
781     int res;
782
783     if (rt->pty < 0) {
784         errno = EINVAL;
785         return -1;
786     }
787
788 again:
789     res = write(rt->pty, data, len);
790     if (res < 0) {
791         if (errno == EINTR || errno == EAGAIN)
792             goto again;
793     }
794
795     return res;
796 }
797
798 void madtty_keypress(madtty_t *rt, int keycode)
799 {
800     char c = (char)keycode;
801     const char *buf;
802     int len;
803
804     if (keycode >= 0 && keycode < KEY_MAX && keytable[keycode]) {
805         buf = keytable[keycode];
806         len = strlen(keytable[keycode]);
807     } else {
808         buf = &c;
809         len = 1;
810     }
811
812     while (len > 0) {
813         int res = madtty_write(rt, buf, len);
814         if (res < 0)
815             return;
816
817         buf += res;
818         len -= res;
819     }
820 }
821
822 void madtty_initialize(void)
823 {
824     setlocale(LC_ALL, "");
825     initscr();
826     noecho();
827     start_color();
828     use_default_colors();
829     raw();
830     nodelay(stdscr, TRUE);
831     keypad(stdscr, TRUE);
832
833     for (int i = -1; i < 8; i++) {
834         for (int j = -1; j < 8; j++) {
835             init_pair((i + 1) * 16 + j + 1, i, j);
836         }
837     }
838 }