289172e1055200abe2384a890a54d85c19c9f9ab
[apps/madtty.git] / 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 static void mtty_row_roll(mtty_row_t *start, mtty_row_t *end, int count)
69 {
70     int n = end - start;
71
72     count %= n;
73     if (count < 0)
74         count += n;
75
76     if (count) {
77         mtty_row_t *buf = alloca(count * sizeof(mtty_row_t));
78
79         memcpy(buf, start, count * sizeof(mtty_row_t));
80         memmove(start, start + count, (n - count) * sizeof(mtty_row_t));
81         memcpy(end - count, buf, count * sizeof(mtty_row_t));
82     }
83 }
84
85 static void clamp_cursor_to_bounds(madtty_t *rt)
86 {
87     if (rt->curs_row < rt->lines) {
88         rt->curs_row = rt->lines;
89     }
90     if (rt->curs_row >= rt->lines + rt->rows) {
91         rt->curs_row = rt->lines + rt->rows - 1;
92     }
93
94     if (rt->curs_col < 0) {
95         rt->curs_col = 0;
96     }
97     if (rt->curs_col >= rt->cols) {
98         rt->curs_col = rt->cols - 1;
99     }
100 }
101
102 static void cursor_line_down(madtty_t *rt)
103 {
104     rt->curs_row++;
105     if (rt->curs_row < rt->scroll_bot)
106         return;
107
108     rt->curs_row = rt->scroll_bot - 1;
109     mtty_row_roll(rt->scroll_top, rt->scroll_bot, 1);
110     mtty_row_set(rt->curs_row, 0, rt->cols, 0);
111 }
112
113 static void cursor_line_up(madtty_t *rt)
114 {
115     rt->curs_row--;
116     if (rt->curs_row >= rt->scroll_top)
117         return;
118
119     /* must scroll the scrolling region up by 1 line, and put cursor on
120      * first line of it */
121     rt->curs_row = rt->scroll_top;
122     mtty_row_roll(rt->scroll_top, rt->scroll_bot, -1);
123     mtty_row_set(rt->curs_row, 0, rt->cols, 0);
124 }
125
126 __attribute__((const))
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     mtty_row_t *row, *start, *end;
299     attr_t attr = build_attrs(rt->curattrs);
300
301     /* decide range */
302     if (pcount && param[0] == 2) {
303         start = rt->lines;
304         end   = rt->lines + rt->rows;
305     } else
306     if (pcount && param[0] == 1) {
307         start = rt->lines;
308         end   = rt->curs_row;
309         mtty_row_set(rt->curs_row, 0, rt->curs_col + 1, attr);
310     } else {
311         mtty_row_set(rt->curs_row, rt->curs_col,
312                      rt->cols - rt->curs_col, attr);
313         start = rt->curs_row + 1;
314         end   = rt->lines + rt->rows;
315     }
316
317     for (row = start; row < end; row++) {
318         mtty_row_set(row, 0, rt->cols, attr);
319     }
320 }
321
322 /* interprets a 'move cursor' (CUP) escape sequence */
323 static void interpret_csi_CUP(madtty_t *rt, int param[], int pcount)
324 {
325     if (pcount == 0) {
326         /* special case */
327         rt->curs_row = rt->lines;
328         rt->curs_col = 0;
329         return;
330     } else
331     if (pcount < 2) {
332         return;  /* malformed */
333     }
334
335     rt->curs_row = rt->lines + param[0] - 1;  /* convert from 1-based to 0-based */
336     rt->curs_col = param[1] - 1;  /* convert from 1-based to 0-based */
337
338     clamp_cursor_to_bounds(rt);
339 }
340
341 /* Interpret the 'relative mode' sequences: CUU, CUD, CUF, CUB, CNL,
342  * CPL, CHA, HPR, VPA, VPR, HPA */
343 static void interpret_csi_C(madtty_t *rt, char verb, int param[], int pcount)
344 {
345     int n = (pcount && param[0] > 0) ? param[0] : 1;
346
347     switch (verb) {
348       case 'A':           rt->curs_row -= n; break;
349       case 'B': case 'e': rt->curs_row += n; break;
350       case 'C': case 'a': rt->curs_col += n; break;
351       case 'D':           rt->curs_col -= n; break;
352       case 'E':           rt->curs_row += n; rt->curs_col = 0; break;
353       case 'F':           rt->curs_row -= n; rt->curs_col = 0; break;
354       case 'G': case '`': rt->curs_col  = param[0] - 1; break;
355       case 'd':           rt->curs_row  = rt->lines + param[0] - 1; break;
356     }
357
358     clamp_cursor_to_bounds(rt);
359 }
360
361 /* Interpret the 'erase line' escape sequence */
362 static void interpret_csi_EL(madtty_t *rt, int param[], int pcount)
363 {
364     int start, len;
365     int cmd = pcount ? param[0] : 0;
366
367     switch (cmd) {
368       case 1:
369         start = 0;
370         len   = rt->curs_col + 1;
371         break;
372       case 2:
373         start = 0;
374         len   = rt->cols;
375         break;
376       default:
377         start = rt->curs_col;
378         len   = rt->cols - start;
379         break;
380     }
381
382     mtty_row_set(rt->curs_row, start, len, build_attrs(rt->curattrs));
383 }
384
385 /* Interpret the 'insert blanks' sequence (ICH) */
386 static void interpret_csi_ICH(madtty_t *rt, int param[], int pcount)
387 {
388     mtty_row_t *row = rt->curs_row;
389     int n = (pcount && param[0] > 0) ? param[0] : 1;
390     int i;
391
392     if (rt->curs_col + n > rt->cols) {
393         n = rt->cols - rt->curs_col;
394     }
395
396     for (i = rt->cols - 1; i >= rt->curs_col + n; i--) {
397         row->text[i] = row->text[i - n];
398         row->attr[i] = row->attr[i - n];
399     }
400
401     mtty_row_set(row, rt->curs_col, n, build_attrs(rt->curattrs));
402 }
403
404 /* Interpret the 'delete chars' sequence (DCH) */
405 static void interpret_csi_DCH(madtty_t *rt, int param[], int pcount)
406 {
407     mtty_row_t *row = rt->curs_row;
408     int n = (pcount && param[0] > 0) ? param[0] : 1;
409     int i;
410
411     if (rt->curs_col + n > rt->cols) {
412         n = rt->cols - rt->curs_col;
413     }
414
415     for (i = rt->curs_col; i < rt->cols - n; i++) {
416         row->text[i] = row->text[i + n];
417         row->attr[i] = row->attr[i + n];
418     }
419
420     mtty_row_set(row, rt->cols - n, n, build_attrs(rt->curattrs));
421 }
422
423 /* Interpret an 'insert line' sequence (IL) */
424 static void interpret_csi_IL(madtty_t *rt, int param[], int pcount)
425 {
426     int n = (pcount && param[0] > 0) ? param[0] : 1;
427
428     if (rt->curs_row + n >= rt->scroll_bot) {
429         for (mtty_row_t *row = rt->curs_row; row < rt->scroll_bot; row++) {
430             mtty_row_set(row, 0, rt->cols, build_attrs(rt->curattrs));
431         }
432     } else {
433         mtty_row_roll(rt->curs_row, rt->scroll_bot, n);
434         for (mtty_row_t *row = rt->curs_row; row < rt->curs_row + n; row++) {
435             mtty_row_set(row, 0, rt->cols, build_attrs(rt->curattrs));
436         }
437     }
438
439 }
440
441 /* Interpret a 'delete line' sequence (DL) */
442 static void interpret_csi_DL(madtty_t *rt, int param[], int pcount)
443 {
444     int n = (pcount && param[0] > 0) ? param[0] : 1;
445
446     if (rt->curs_row + n >= rt->scroll_bot) {
447         for (mtty_row_t *row = rt->curs_row; row < rt->scroll_bot; row++) {
448             mtty_row_set(row, 0, rt->cols, build_attrs(rt->curattrs));
449         }
450     } else {
451         mtty_row_roll(rt->curs_row, rt->scroll_bot, -n);
452         for (mtty_row_t *row = rt->scroll_bot - n; row < rt->scroll_bot; row++) {
453             mtty_row_set(row, 0, rt->cols, build_attrs(rt->curattrs));
454         }
455     }
456 }
457
458 /* Interpret an 'erase characters' (ECH) sequence */
459 static void interpret_csi_ECH(madtty_t *rt, int param[], int pcount)
460 {
461     int n = (pcount && param[0] > 0) ? param[0] : 1;
462
463     if (rt->curs_col + n < rt->cols) {
464         n = rt->cols - rt->curs_col;
465     }
466     mtty_row_set(rt->curs_row, rt->curs_col, n, build_attrs(rt->curattrs));
467 }
468
469 /* Interpret a 'set scrolling region' (DECSTBM) sequence */
470 static void interpret_csi_DECSTBM(madtty_t *rt, int param[], int pcount)
471 {
472     int new_top, new_bot;
473
474     switch (pcount) {
475       case 0:
476         rt->scroll_top = rt->lines;
477         rt->scroll_bot = rt->lines + rt->rows;
478         break;
479       default:
480         return; /* malformed */
481
482       case 2:
483         new_top = param[0] - 1;
484         new_bot = param[1];
485
486         /* clamp to bounds */
487         if (new_top < 0)
488             new_top = 0;
489         if (new_top >= rt->rows)
490             new_top = rt->rows - 1;
491         if (new_bot < 0)
492             new_bot = 0;
493         if (new_bot >= rt->rows)
494             new_bot = rt->rows;
495
496         /* check for range validity */
497         if (new_top < new_bot) {
498             rt->scroll_top = rt->lines + new_top;
499             rt->scroll_bot = rt->lines + new_bot;
500         }
501         break;
502     }
503 }
504
505 static void es_interpret_csi(madtty_t *rt)
506 {
507     static int csiparam[MAX_CSI_ES_PARAMS];
508     int param_count = 0;
509     const char *p = rt->esbuf + 1;
510     char verb = rt->esbuf[rt->esbuf_len - 1];
511
512     if (!strncmp(rt->esbuf, "[?", 2)) { /* private-mode CSI, ignore */
513         return;
514     }
515
516     /* parse numeric parameters */
517     while (isdigit((unsigned char)*p) || *p == ';') {
518         if (*p == ';') {
519             if (param_count >= MAX_CSI_ES_PARAMS) return; /* too long! */
520             csiparam[param_count++] = 0;
521         } else {
522             if (param_count == 0) csiparam[param_count++] = 0;
523             csiparam[param_count - 1] *= 10;
524             csiparam[param_count - 1] += *p - '0';
525         }
526
527         p++;
528     }
529
530     /* delegate handling depending on command character (verb) */
531     switch (verb) {
532       case 'h':
533         if (param_count == 1 && csiparam[0] == 4) /* insert mode */
534             rt->insert = true;
535         break;
536       case 'l':
537         if (param_count == 1 && csiparam[0] == 4) /* replace mode */
538             rt->insert = false;
539         break;
540       case 'm': /* it's a 'set attribute' sequence */
541         interpret_csi_SGR(rt, csiparam, param_count); break;
542       case 'J': /* it's an 'erase display' sequence */
543         interpret_csi_ED(rt, csiparam, param_count); break;
544       case 'H': case 'f': /* it's a 'move cursor' sequence */
545         interpret_csi_CUP(rt, csiparam, param_count); break;
546       case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
547       case 'e': case 'a': case 'd': case '`':
548         /* it is a 'relative move' */
549         interpret_csi_C(rt, verb, csiparam, param_count); break;
550       case 'K': /* erase line */
551         interpret_csi_EL(rt, csiparam, param_count); break;
552       case '@': /* insert characters */
553         interpret_csi_ICH(rt, csiparam, param_count); break;
554       case 'P': /* delete characters */
555         interpret_csi_DCH(rt, csiparam, param_count); break;
556       case 'L': /* insert lines */
557         interpret_csi_IL(rt, csiparam, param_count); break;
558       case 'M': /* delete lines */
559         interpret_csi_DL(rt, csiparam, param_count); break;
560       case 'X': /* erase chars */
561         interpret_csi_ECH(rt, csiparam, param_count); break;
562       case 'r': /* set scrolling region */
563         interpret_csi_DECSTBM(rt, csiparam, param_count); break;
564       case 's': /* save cursor location */
565         rt->curs_srow = rt->curs_row - rt->lines;
566         rt->curs_scol = rt->curs_col;
567         break;
568       case 'u': /* restore cursor location */
569         rt->curs_row = rt->lines + rt->curs_srow;
570         rt->curs_col = rt->curs_scol;
571         clamp_cursor_to_bounds(rt);
572         break;
573       default:
574         break;
575     }
576 }
577
578 static void try_interpret_escape_seq(madtty_t *rt)
579 {
580     char firstchar = rt->esbuf[0];
581     char lastchar  = rt->esbuf[rt->esbuf_len-1];
582
583     if (!firstchar)
584         return;  /* too early to do anything */
585
586     /* interpret ESC-M as reverse line-feed */
587     if (firstchar == 'M') {
588         cursor_line_up(rt);
589         cancel_escape_sequence(rt);
590         return;
591     }
592
593     if (firstchar != '[' && firstchar != ']') {
594         /* unrecognized escape sequence. Let's forget about it. */
595         cancel_escape_sequence(rt);
596         return;
597     }
598
599     if (firstchar == '[' && is_valid_csi_ender(lastchar)) {
600         es_interpret_csi(rt);
601         cancel_escape_sequence(rt);
602     } else if (firstchar == ']' && lastchar == '\a') {
603         /* we have an xterm escape sequence: interpret it */
604
605         /* es_interpret_xterm_es(rt);     -- TODO!*/
606         cancel_escape_sequence(rt);
607     }
608
609     /* if the escape sequence took up all available space and could
610      * not yet be parsed, abort it */
611     if (rt->esbuf_len + 1 >= ESEQ_BUF_SIZE)
612         cancel_escape_sequence(rt);
613 }
614
615 int madtty_process(madtty_t *rt)
616 {
617     int res, pos = 0;
618
619     if (rt->pty < 0) {
620         errno = EINVAL;
621         return -1;
622     }
623
624     res = read(rt->pty, rt->rbuf + rt->rbuf_len,
625                sizeof(rt->rbuf) - rt->rbuf_len);
626     if (res < 0)
627         return -1;
628
629     rt->rbuf_len += res;
630     while (pos < rt->rbuf_len) {
631         wchar_t wc;
632         ssize_t len;
633
634         len = (ssize_t)mbrtowc(&wc, rt->rbuf + pos, rt->rbuf_len - pos,
635                                &rt->ps);
636         if (len < 0) {
637             rt->rbuf_len -= pos;
638             memmove(rt->rbuf, rt->rbuf + pos, rt->rbuf_len);
639             return len == -2 ? 0 : -1;
640         }
641
642         pos += len ? len : 1;
643
644         if (wc < ' ') {
645             handle_control_char(rt, wc);
646             continue;
647         }
648
649         if (rt->escaped && rt->esbuf_len < ESEQ_BUF_SIZE) {
650             /* append character to ongoing escape sequence */
651             rt->esbuf[rt->esbuf_len]   = wc;
652             rt->esbuf[++rt->esbuf_len] = 0;
653
654             try_interpret_escape_seq(rt);
655         } else
656         if (rt->graphmode) {
657             put_graphmode_char(rt, wc);
658         } else {
659             put_normal_char(rt, wc);
660         }
661     }
662
663     rt->rbuf_len -= pos;
664     memmove(rt->rbuf, rt->rbuf + pos, rt->rbuf_len);
665     return 0;
666 }
667
668 madtty_t *madtty_create(int rows, int cols)
669 {
670     madtty_t *rt;
671     int i;
672
673     if (rows <= 0 || cols <= 0)
674         return NULL;
675
676     rt = (madtty_t*)calloc(sizeof(madtty_t), 1);
677     if (!rt)
678         return NULL;
679
680     /* record dimensions */
681     rt->rows = rows;
682     rt->cols = cols;
683
684     /* default mode is replace */
685     rt->insert = false; 
686
687     /* create the cell matrix */
688     rt->lines = (mtty_row_t*)calloc(sizeof(mtty_row_t), rt->rows);
689     for (i = 0; i < rt->rows; i++) {
690         rt->lines[i].text = (wchar_t *)calloc(sizeof(wchar_t), rt->cols);
691         rt->lines[i].attr = (uint16_t *)calloc(sizeof(uint16_t), rt->cols);
692     }
693
694     rt->pty = -1;  /* no pty for now */
695
696     /* initialization of other public fields */
697     rt->curs_row = rt->lines;
698     rt->curs_col = 0;
699     rt->curattrs = A_NORMAL;  /* white text over black background */
700
701     /* initial scrolling area is the whole window */
702     rt->scroll_top = rt->lines;
703     rt->scroll_bot = rt->lines + rt->rows;
704
705     return rt;
706 }
707
708 void madtty_destroy(madtty_t *rt)
709 {
710     int i;
711     if (!rt)
712         return;
713
714     for (i = 0; i < rt->rows; i++) {
715         free(rt->lines[i].text);
716         free(rt->lines[i].attr);
717     }
718     free(rt->lines);
719     free(rt);
720 }
721
722 void madtty_draw(madtty_t *rt, WINDOW *win, int srow, int scol)
723 {
724     int i, j;
725
726     for (i = 0; i < rt->rows; i++) {
727         mtty_row_t *row = rt->lines + i;
728         wmove(win, srow + i, scol);
729
730         for (j = 0; j < rt->cols; j++) {
731             if (!j || row->attr[j] != row->attr[j - 1])
732                 wattrset(win, (attr_t)row->attr[j] << NCURSES_ATTR_SHIFT);
733             if (row->text[j] >= 128) {
734                 char buf[MB_CUR_MAX + 1];
735                 int len;
736
737                 len = wcrtomb(buf, row->text[j], NULL);
738                 waddnstr(win, buf, len);
739             } else {
740                 waddch(win, row->text[j] > ' ' ? row->text[j] : ' ');
741             }
742         }
743     }
744
745     wmove(win, srow + rt->curs_row - rt->lines, scol + rt->curs_col);
746 }
747
748 /******************************************************/
749
750 pid_t madtty_forkpty(madtty_t *rt, const char *path, const char *argv[])
751 {
752     struct winsize ws;
753     pid_t pid;
754
755     ws.ws_row    = rt->rows;
756     ws.ws_col    = rt->cols;
757     ws.ws_xpixel = ws.ws_ypixel = 0;
758
759     pid = forkpty(&rt->pty, NULL, NULL, &ws);
760     if (pid < 0)
761         return -1;
762
763     if (pid == 0) {
764         setsid();
765
766         setenv("TERM", "linux", 1);
767         execv(path, (char *const*)argv);
768         fprintf(stderr, "\nexecv() failed.\nCommand: '%s'\n", argv[0]);
769         exit(1);
770     }
771
772     return rt->childpid = pid;
773 }
774
775 static int madtty_write(madtty_t *rt, const char *data, int len)
776 {
777     int res;
778
779     if (rt->pty < 0) {
780         errno = EINVAL;
781         return -1;
782     }
783
784 again:
785     res = write(rt->pty, data, len);
786     if (res < 0) {
787         if (errno == EINTR || errno == EAGAIN)
788             goto again;
789     }
790
791     return res;
792 }
793
794 void madtty_keypress(madtty_t *rt, int keycode)
795 {
796     char c = (char)keycode;
797     const char *buf;
798     int len;
799
800     if (keycode >= 0 && keycode < KEY_MAX && keytable[keycode]) {
801         buf = keytable[keycode];
802         len = strlen(keytable[keycode]);
803     } else {
804         buf = &c;
805         len = 1;
806     }
807
808     while (len > 0) {
809         int res = madtty_write(rt, buf, len);
810         if (res < 0)
811             return;
812
813         buf += res;
814         len -= res;
815     }
816 }
817
818 void madtty_initialize(void)
819 {
820     setlocale(LC_ALL, "");
821     initscr();
822     noecho();
823     start_color();
824     use_default_colors();
825     raw();
826     nodelay(stdscr, TRUE);
827     keypad(stdscr, TRUE);
828
829     for (int i = -1; i < 8; i++) {
830         for (int j = -1; j < 8; j++) {
831             init_pair((i + 1) * 16 + j + 1, i, j);
832         }
833     }
834 }