resize works in fact :P
[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 <assert.h>
23 #include <ctype.h>
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <locale.h>
27 #include <pty.h>
28 #include <signal.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <sys/types.h>
33
34 #include "madtty.h"
35
36 #define IS_CONTROL(ch) !((ch) & 0xffffff60UL)
37
38 enum {
39     C0_NUL = 0x00,
40             C0_SOH, C0_STX, C0_ETX, C0_EOT, C0_ENQ, C0_ACK, C0_BEL,
41     C0_BS , C0_HT , C0_LF , C0_VT , C0_FF , C0_CR , C0_SO , C0_SI ,
42     C0_DLE, C0_DC1, C0_DC2, D0_DC3, C0_DC4, C0_NAK, C0_SYN, C0_ETB,
43     C0_CAN, C0_EM , C0_SUB, C0_ESC, C0_IS4, C0_IS3, C0_IS2, C0_IS1,
44 };
45
46 enum {
47     C1_40 = 0x40,
48             C1_41 , C1_BPH, C1_NBH, C1_44 , C1_NEL, C1_SSA, C1_ESA,
49     C1_HTS, C1_HTJ, C1_VTS, C1_PLD, C1_PLU, C1_RI , C1_SS2, C1_SS3,
50     C1_DCS, C1_PU1, C1_PU2, C1_STS, C1_CCH, C1_MW , C1_SPA, C1_EPA,
51     C1_SOS, C1_59 , C1_SCI, C1_CSI, CS_ST , C1_OSC, C1_PM , C1_APC,
52 };
53
54 enum {
55     CSI_ICH = 0x40,
56              CSI_CUU, CSI_CUD, CSI_CUF, CSI_CUB, CSI_CNL, CSI_CPL, CSI_CHA,
57     CSI_CUP, CSI_CHT, CSI_ED , CSI_EL , CSI_IL , CSI_DL , CSI_EF , CSI_EA ,
58     CSI_DCH, CSI_SEE, CSI_CPR, CSI_SU , CSI_SD , CSI_NP , CSI_PP , CSI_CTC,
59     CSI_ECH, CSI_CVT, CSI_CBT, CSI_SRS, CSI_PTX, CSI_SDS, CSI_SIMD, CSI_5F,
60     CSI_HPA, CSI_HPR, CSI_REP, CSI_DA , CSI_VPA, CSI_VPR, CSI_HVP, CSI_TBC,
61     CSI_SM , CSI_MC , CSI_HPB, CSI_VPB, CSI_RM , CSI_SGR, CSI_DSR, CSI_DAQ,
62     CSI_70 , CSI_71 , CSI_72 , CSI_73 , CSI_74 , CSI_75 , CSI_76 , CSI_77 ,
63     CSI_78 , CSI_79 , CSI_7A , CSI_7B , CSI_7C , CSI_7D , CSI_7E , CSI_7F
64 };
65
66 struct mtty_row_t {
67     wchar_t  *text;
68     uint16_t *attr;
69     unsigned dirty : 1;
70 };
71
72 static char const * const keytable[KEY_MAX+1] = {
73     ['\n']          = "\r",
74     [KEY_UP]        = "\e[A",
75     [KEY_DOWN]      = "\e[B",
76     [KEY_RIGHT]     = "\e[C",
77     [KEY_LEFT]      = "\e[D",
78     [KEY_BACKSPACE] = "\177",
79     [KEY_HOME]      = "\e[1~",
80     [KEY_IC]        = "\e[2~",
81     [KEY_DC]        = "\e[3~",
82     [KEY_END]       = "\e[4~",
83     [KEY_PPAGE]     = "\e[5~",
84     [KEY_NPAGE]     = "\e[6~",
85     [KEY_SUSPEND]   = "\x1A",  /* Ctrl+Z gets mapped to this */
86     [KEY_F(1)]      = "\e[[A",
87     [KEY_F(2)]      = "\e[[B",
88     [KEY_F(3)]      = "\e[[C",
89     [KEY_F(4)]      = "\e[[D",
90     [KEY_F(5)]      = "\e[[E",
91     [KEY_F(6)]      = "\e[17~",
92     [KEY_F(7)]      = "\e[18~",
93     [KEY_F(8)]      = "\e[19~",
94     [KEY_F(9)]      = "\e[20~",
95     [KEY_F(10)]     = "\e[21~",
96 };
97
98 static void mtty_row_set(mtty_row_t *row, int start, int len, uint16_t attr)
99 {
100     row->dirty = true;
101     wmemset(row->text + start, 0, len);
102     for (int i = start; i < len + start; i++) {
103         row->attr[i] = attr;
104     }
105 }
106
107 static void mtty_row_roll(mtty_row_t *start, mtty_row_t *end, int count)
108 {
109     int n = end - start;
110
111     count %= n;
112     if (count < 0)
113         count += n;
114
115     if (count) {
116         mtty_row_t *buf = alloca(count * sizeof(mtty_row_t));
117
118         memcpy(buf, start, count * sizeof(mtty_row_t));
119         memmove(start, start + count, (n - count) * sizeof(mtty_row_t));
120         memcpy(end - count, buf, count * sizeof(mtty_row_t));
121         for (mtty_row_t *row = start; row < end; row++) {
122             row->dirty = true;
123         }
124     }
125 }
126
127 static void clamp_cursor_to_bounds(madtty_t *rt)
128 {
129     if (rt->curs_row < rt->lines) {
130         rt->curs_row = rt->lines;
131     }
132     if (rt->curs_row >= rt->lines + rt->rows) {
133         rt->curs_row = rt->lines + rt->rows - 1;
134     }
135
136     if (rt->curs_col < 0) {
137         rt->curs_col = 0;
138     }
139     if (rt->curs_col >= rt->cols) {
140         rt->curs_col = rt->cols - 1;
141     }
142 }
143
144 static void cursor_line_down(madtty_t *rt)
145 {
146     rt->curs_row++;
147     if (rt->curs_row < rt->scroll_bot)
148         return;
149
150     rt->curs_row = rt->scroll_bot - 1;
151     mtty_row_roll(rt->scroll_top, rt->scroll_bot, 1);
152     mtty_row_set(rt->curs_row, 0, rt->cols, 0);
153 }
154
155 __attribute__((const))
156 static uint16_t build_attrs(unsigned curattrs)
157 {
158     return ((curattrs & ~A_COLOR) | COLOR_PAIR(curattrs & 0xff))
159         >> NCURSES_ATTR_SHIFT;
160 }
161
162 static void new_escape_sequence(madtty_t *rt)
163 {
164     rt->escaped = true;
165     rt->elen    = 0;
166     rt->ebuf[0] = '\0';
167 }
168
169 static void cancel_escape_sequence(madtty_t *rt)
170 {
171     rt->escaped = false;
172     rt->elen    = 0;
173     rt->ebuf[0] = '\0';
174 }
175
176 static bool is_valid_csi_ender(int c)
177 {
178     return (c >= 'a' && c <= 'z')
179         || (c >= 'A' && c <= 'Z')
180         || (c == '@' || c == '`');
181 }
182
183 /* interprets a 'set attribute' (SGR) CSI escape sequence */
184 static void interpret_csi_SGR(madtty_t *rt, int param[], int pcount)
185 {
186     int i;
187
188     if (pcount == 0) {
189         /* special case: reset attributes */
190         rt->curattrs = A_NORMAL;
191         return;
192     }
193
194     for (i = 0; i < pcount; i++) {
195         switch (param[i]) {
196 #define CASE(x, op)  case x: op; break
197             CASE(0,  rt->curattrs = A_NORMAL);
198             CASE(1,  rt->curattrs |= A_BOLD);
199             CASE(4,  rt->curattrs |= A_UNDERLINE);
200             CASE(5,  rt->curattrs |= A_BLINK);
201             CASE(6,  rt->curattrs |= A_BLINK);
202             CASE(7,  rt->curattrs |= A_REVERSE);
203             CASE(8,  rt->curattrs |= A_INVIS);
204             CASE(22, rt->curattrs &= ~A_BOLD);
205             CASE(24, rt->curattrs &= ~A_UNDERLINE);
206             CASE(25, rt->curattrs &= ~A_BLINK);
207             CASE(27, rt->curattrs &= ~A_REVERSE);
208             CASE(28, rt->curattrs &= ~A_INVIS);
209
210           case 30 ... 37:
211             rt->curattrs &= ~0xf0;
212             rt->curattrs |= (param[i] - 29) << 4;
213             break;
214
215           case 39:
216             rt->curattrs &= ~0xf0;
217             break;
218
219           case 40 ... 47:
220             rt->curattrs &= ~0x0f;
221             rt->curattrs |= (param[i] - 39);
222             break;
223
224           case 49:
225             rt->curattrs &= ~0x0f;
226             break;
227
228           default:
229             break;
230         }
231     }
232 }
233
234 /* interprets an 'erase display' (ED) escape sequence */
235 static void interpret_csi_ED(madtty_t *rt, int param[], int pcount)
236 {
237     mtty_row_t *row, *start, *end;
238     attr_t attr = build_attrs(rt->curattrs);
239
240     /* decide range */
241     if (pcount && param[0] == 2) {
242         start = rt->lines;
243         end   = rt->lines + rt->rows;
244     } else
245     if (pcount && param[0] == 1) {
246         start = rt->lines;
247         end   = rt->curs_row;
248         mtty_row_set(rt->curs_row, 0, rt->curs_col + 1, attr);
249     } else {
250         mtty_row_set(rt->curs_row, rt->curs_col,
251                      rt->cols - rt->curs_col, attr);
252         start = rt->curs_row + 1;
253         end   = rt->lines + rt->rows;
254     }
255
256     for (row = start; row < end; row++) {
257         mtty_row_set(row, 0, rt->cols, attr);
258     }
259 }
260
261 /* interprets a 'move cursor' (CUP) escape sequence */
262 static void interpret_csi_CUP(madtty_t *rt, int param[], int pcount)
263 {
264     if (pcount == 0) {
265         /* special case */
266         rt->curs_row = rt->lines;
267         rt->curs_col = 0;
268         return;
269     } else
270     if (pcount < 2) {
271         return;  /* malformed */
272     }
273
274     rt->curs_row = rt->lines + param[0] - 1;  /* convert from 1-based to 0-based */
275     rt->curs_col = param[1] - 1;  /* convert from 1-based to 0-based */
276
277     clamp_cursor_to_bounds(rt);
278 }
279
280 /* Interpret the 'relative mode' sequences: CUU, CUD, CUF, CUB, CNL,
281  * CPL, CHA, HPR, VPA, VPR, HPA */
282 static void interpret_csi_C(madtty_t *rt, char verb, int param[], int pcount)
283 {
284     int n = (pcount && param[0] > 0) ? param[0] : 1;
285
286     switch (verb) {
287       case 'A':           rt->curs_row -= n; break;
288       case 'B': case 'e': rt->curs_row += n; break;
289       case 'C': case 'a': rt->curs_col += n; break;
290       case 'D':           rt->curs_col -= n; break;
291       case 'E':           rt->curs_row += n; rt->curs_col = 0; break;
292       case 'F':           rt->curs_row -= n; rt->curs_col = 0; break;
293       case 'G': case '`': rt->curs_col  = param[0] - 1; break;
294       case 'd':           rt->curs_row  = rt->lines + param[0] - 1; break;
295     }
296
297     clamp_cursor_to_bounds(rt);
298 }
299
300 /* Interpret the 'erase line' escape sequence */
301 static void interpret_csi_EL(madtty_t *rt, int param[], int pcount)
302 {
303     attr_t attr = build_attrs(rt->curattrs);
304
305     switch (pcount ? param[0] : 0) {
306       case 1:
307         mtty_row_set(rt->curs_row, 0, rt->curs_col + 1, attr);
308         break;
309       case 2:
310         mtty_row_set(rt->curs_row, 0, rt->cols, attr);
311         break;
312       default:
313         mtty_row_set(rt->curs_row, rt->curs_col, rt->cols - rt->curs_col,
314                      attr);
315         break;
316     }
317 }
318
319 /* Interpret the 'insert blanks' sequence (ICH) */
320 static void interpret_csi_ICH(madtty_t *rt, int param[], int pcount)
321 {
322     mtty_row_t *row = rt->curs_row;
323     int n = (pcount && param[0] > 0) ? param[0] : 1;
324     int i;
325
326     if (rt->curs_col + n > rt->cols) {
327         n = rt->cols - rt->curs_col;
328     }
329
330     for (i = rt->cols - 1; i >= rt->curs_col + n; i--) {
331         row->text[i] = row->text[i - n];
332         row->attr[i] = row->attr[i - n];
333     }
334
335     mtty_row_set(row, rt->curs_col, n, build_attrs(rt->curattrs));
336 }
337
338 /* Interpret the 'delete chars' sequence (DCH) */
339 static void interpret_csi_DCH(madtty_t *rt, int param[], int pcount)
340 {
341     mtty_row_t *row = rt->curs_row;
342     int n = (pcount && param[0] > 0) ? param[0] : 1;
343     int i;
344
345     if (rt->curs_col + n > rt->cols) {
346         n = rt->cols - rt->curs_col;
347     }
348
349     for (i = rt->curs_col; i < rt->cols - n; i++) {
350         row->text[i] = row->text[i + n];
351         row->attr[i] = row->attr[i + n];
352     }
353
354     mtty_row_set(row, rt->cols - n, n, build_attrs(rt->curattrs));
355 }
356
357 /* Interpret a 'scroll reverse' (SR) */
358 static void interpret_csi_SR(madtty_t *rt)
359 {
360     mtty_row_roll(rt->scroll_top, rt->scroll_bot, -1);
361     mtty_row_set(rt->scroll_top, 0, rt->cols, build_attrs(rt->curattrs));
362 }
363
364 /* Interpret an 'insert line' sequence (IL) */
365 static void interpret_csi_IL(madtty_t *rt, int param[], int pcount)
366 {
367     int n = (pcount && param[0] > 0) ? param[0] : 1;
368
369     if (rt->curs_row + n >= rt->scroll_bot) {
370         for (mtty_row_t *row = rt->curs_row; row < rt->scroll_bot; row++) {
371             mtty_row_set(row, 0, rt->cols, build_attrs(rt->curattrs));
372         }
373     } else {
374         mtty_row_roll(rt->curs_row, rt->scroll_bot, -n);
375         for (mtty_row_t *row = rt->curs_row; row < rt->curs_row + n; row++) {
376             mtty_row_set(row, 0, rt->cols, build_attrs(rt->curattrs));
377         }
378     }
379 }
380
381 /* Interpret a 'delete line' sequence (DL) */
382 static void interpret_csi_DL(madtty_t *rt, int param[], int pcount)
383 {
384     int n = (pcount && param[0] > 0) ? param[0] : 1;
385
386     if (rt->curs_row + n >= rt->scroll_bot) {
387         for (mtty_row_t *row = rt->curs_row; row < rt->scroll_bot; row++) {
388             mtty_row_set(row, 0, rt->cols, build_attrs(rt->curattrs));
389         }
390     } else {
391         mtty_row_roll(rt->curs_row, rt->scroll_bot, n);
392         for (mtty_row_t *row = rt->scroll_bot - n; row < rt->scroll_bot; row++) {
393             mtty_row_set(row, 0, rt->cols, build_attrs(rt->curattrs));
394         }
395     }
396 }
397
398 /* Interpret an 'erase characters' (ECH) sequence */
399 static void interpret_csi_ECH(madtty_t *rt, int param[], int pcount)
400 {
401     int n = (pcount && param[0] > 0) ? param[0] : 1;
402
403     if (rt->curs_col + n < rt->cols) {
404         n = rt->cols - rt->curs_col;
405     }
406     mtty_row_set(rt->curs_row, rt->curs_col, n, build_attrs(rt->curattrs));
407 }
408
409 /* Interpret a 'set scrolling region' (DECSTBM) sequence */
410 static void interpret_csi_DECSTBM(madtty_t *rt, int param[], int pcount)
411 {
412     int new_top, new_bot;
413
414     switch (pcount) {
415       case 0:
416         rt->scroll_top = rt->lines;
417         rt->scroll_bot = rt->lines + rt->rows;
418         break;
419       default:
420         return; /* malformed */
421
422       case 2:
423         new_top = param[0] - 1;
424         new_bot = param[1];
425
426         /* clamp to bounds */
427         if (new_top < 0)
428             new_top = 0;
429         if (new_top >= rt->rows)
430             new_top = rt->rows - 1;
431         if (new_bot < 0)
432             new_bot = 0;
433         if (new_bot >= rt->rows)
434             new_bot = rt->rows;
435
436         /* check for range validity */
437         if (new_top < new_bot) {
438             rt->scroll_top = rt->lines + new_top;
439             rt->scroll_bot = rt->lines + new_bot;
440         }
441         break;
442     }
443 }
444
445 static void es_interpret_csi(madtty_t *rt)
446 {
447     static int csiparam[MAX_CSI_ES_PARAMS];
448     int param_count = 0;
449     const char *p = rt->ebuf + 1;
450     char verb = rt->ebuf[rt->elen - 1];
451
452     p += rt->ebuf[1] == '?'; /* CSI private mode */
453
454     /* parse numeric parameters */
455     while (isdigit((unsigned char)*p) || *p == ';') {
456         if (*p == ';') {
457             if (param_count >= MAX_CSI_ES_PARAMS) return; /* too long! */
458             csiparam[param_count++] = 0;
459         } else {
460             if (param_count == 0) csiparam[param_count++] = 0;
461             csiparam[param_count - 1] *= 10;
462             csiparam[param_count - 1] += *p - '0';
463         }
464
465         p++;
466     }
467
468     if (rt->ebuf[1] == '?') {
469         switch (verb) {
470           case 'l':
471             if (csiparam[0] == 25)
472                 rt->curshid = true;
473             break;
474
475           case 'h':
476             if (csiparam[0] == 25)
477                 rt->curshid = false;
478             break;
479         }
480     }
481
482     /* delegate handling depending on command character (verb) */
483     switch (verb) {
484       case 'h':
485         if (param_count == 1 && csiparam[0] == 4) /* insert mode */
486             rt->insert = true;
487         break;
488       case 'l':
489         if (param_count == 1 && csiparam[0] == 4) /* replace mode */
490             rt->insert = false;
491         break;
492       case 'm': /* it's a 'set attribute' sequence */
493         interpret_csi_SGR(rt, csiparam, param_count); break;
494       case 'J': /* it's an 'erase display' sequence */
495         interpret_csi_ED(rt, csiparam, param_count); break;
496       case 'H': case 'f': /* it's a 'move cursor' sequence */
497         interpret_csi_CUP(rt, csiparam, param_count); break;
498       case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
499       case 'e': case 'a': case 'd': case '`':
500         /* it is a 'relative move' */
501         interpret_csi_C(rt, verb, csiparam, param_count); break;
502       case 'K': /* erase line */
503         interpret_csi_EL(rt, csiparam, param_count); break;
504       case '@': /* insert characters */
505         interpret_csi_ICH(rt, csiparam, param_count); break;
506       case 'P': /* delete characters */
507         interpret_csi_DCH(rt, csiparam, param_count); break;
508       case 'L': /* insert lines */
509         interpret_csi_IL(rt, csiparam, param_count); break;
510       case 'M': /* delete lines */
511         interpret_csi_DL(rt, csiparam, param_count); break;
512       case 'X': /* erase chars */
513         interpret_csi_ECH(rt, csiparam, param_count); break;
514       case 'r': /* set scrolling region */
515         interpret_csi_DECSTBM(rt, csiparam, param_count); break;
516       case 's': /* save cursor location */
517         rt->curs_srow = rt->curs_row - rt->lines;
518         rt->curs_scol = rt->curs_col;
519         break;
520       case 'u': /* restore cursor location */
521         rt->curs_row = rt->lines + rt->curs_srow;
522         rt->curs_col = rt->curs_scol;
523         clamp_cursor_to_bounds(rt);
524         break;
525       default:
526         break;
527     }
528 }
529
530 static void try_interpret_escape_seq(madtty_t *rt)
531 {
532     char lastchar  = rt->ebuf[rt->elen-1];
533
534     switch (*rt->ebuf) {
535       case '\0':
536         return;
537
538       case 'M':
539         interpret_csi_SR(rt);
540         cancel_escape_sequence(rt);
541         return;
542
543       case '(':
544       case ')':
545         if (rt->elen == 2)
546             goto cancel;
547         break;
548
549       case ']': /* xterm thing */
550         if (lastchar == '\a')
551             goto cancel;
552         break;
553
554       default:
555         goto cancel;
556
557       case '[':
558         if (is_valid_csi_ender(lastchar)) {
559             es_interpret_csi(rt);
560             cancel_escape_sequence(rt);
561             return;
562         }
563         break;
564     }
565
566     if (rt->elen + 1 >= (int)sizeof(rt->ebuf)) {
567         int i;
568 cancel:
569 #if 0
570         fprintf(stderr, "cancelled: \\033");
571         for (i = 0; i < rt->elen; i++) {
572             int c = rt->ebuf[i];
573             if (isprint(c) && c >= ' ') {
574                 fputc(c, stderr);
575             } else {
576                 fprintf(stderr, "\\%03o", c);
577             }
578         }
579         fputc('\n', stderr);
580 #endif
581         cancel_escape_sequence(rt);
582     }
583 }
584
585 static void madtty_process_nonprinting(madtty_t *rt, wchar_t wc)
586 {
587     switch (wc) {
588       case C0_ESC:
589         new_escape_sequence(rt);
590         break;
591
592       case C0_BEL:
593         /* do nothing for now... maybe a visual bell would be nice? */
594         break;
595
596       case C0_BS:
597         if (rt->curs_col > 0)
598             rt->curs_col--;
599         break;
600
601       case C0_HT: /* tab */
602         rt->curs_col = (rt->curs_col + 8) & ~7;
603         if (rt->curs_col >= rt->cols)
604             rt->curs_col = rt->cols - 1;
605         break;
606
607       case C0_CR:
608         rt->curs_col = 0;
609         break;
610
611       case C0_VT:
612       case C0_FF:
613       case C0_LF:
614         cursor_line_down(rt);
615         break;
616
617       case C0_SO:               /* shift out - acs */
618         rt->graphmode = true;
619         break;
620       case C0_SI:               /* shift in - acs */
621         rt->graphmode = false;
622         break;
623     }
624 }
625
626 void madtty_putc(madtty_t *rt, wchar_t wc)
627 {
628     if (!rt->seen_input) {
629         rt->seen_input = 1;
630         kill(-rt->childpid, SIGWINCH);
631     }
632
633 #if 0
634     if (wc == '\n' || (wc >= ' ' && isprint(wc))) {
635         fputc(wc, stderr);
636     } else {
637         fprintf(stderr, "\\%03o", wc);
638     }
639 #endif
640
641     if (rt->escaped) {
642         assert (rt->elen + 1 < (int)sizeof(rt->ebuf));
643         rt->ebuf[rt->elen]   = wc;
644         rt->ebuf[++rt->elen] = '\0';
645         try_interpret_escape_seq(rt);
646     } else if (IS_CONTROL(wc)) {
647         madtty_process_nonprinting(rt, wc);
648     } else {
649         mtty_row_t *tmp;
650
651         if (rt->graphmode) {
652             // vt100 special graphics and line drawing
653             // 5f-7e standard vt100
654             // 40-5e rxvt extension for extra curses acs chars
655             static uint16_t vt100_0[62] = { // 41 .. 7e
656                         0x2191, 0x2193, 0x2192, 0x2190, 0x2588, 0x259a, 0x2603, // 41-47 hi mr. snowman!
657                      0,      0,      0,      0,      0,      0,      0,      0, // 48-4f
658                      0,      0,      0,      0,      0,      0,      0,      0, // 50-57
659                      0,      0,      0,      0,      0,      0,      0, 0x0020, // 58-5f
660                 0x25c6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1, // 60-67
661                 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba, // 68-6f
662                 0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c, // 70-77
663                 0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7,         // 78-7e
664             };
665
666             if (wc >= 0x41 && wc <= 0x7e && vt100_0[wc - 0x41]) {
667                 wc = vt100_0[wc - 0x41];
668                 // width = 1; // vt100 line drawing characters are always single-width
669             }
670         }
671
672         if (rt->curs_col >= rt->cols) {
673             rt->curs_col = 0;
674             cursor_line_down(rt);
675         }
676
677         tmp = rt->curs_row;
678
679         if (rt->insert) {
680             wmemmove(tmp->text + rt->curs_col + 1, tmp->text + rt->curs_col,
681                      (rt->cols - rt->curs_col - 1));
682             memmove(tmp->attr + rt->curs_col + 1, tmp->attr + rt->curs_col,
683                     (rt->cols - rt->curs_col - 1) * sizeof(tmp->attr[0]));
684         }
685
686         tmp->text[rt->curs_col] = wc;
687         tmp->attr[rt->curs_col] = build_attrs(rt->curattrs);
688         tmp->dirty = true;
689         rt->curs_col++;
690     }
691 }
692
693 int madtty_process(madtty_t *rt)
694 {
695     int res, pos = 0;
696
697     if (rt->pty < 0) {
698         errno = EINVAL;
699         return -1;
700     }
701
702     res = read(rt->pty, rt->rbuf + rt->rlen, sizeof(rt->rbuf) - rt->rlen);
703     if (res < 0)
704         return -1;
705
706     rt->rlen += res;
707     while (pos < rt->rlen) {
708         wchar_t wc;
709         ssize_t len;
710
711         len = (ssize_t)mbrtowc(&wc, rt->rbuf + pos, rt->rlen - pos, &rt->ps);
712         if (len == -2) {
713             rt->rlen -= pos;
714             memmove(rt->rbuf, rt->rbuf + pos, rt->rlen);
715             return 0;
716         }
717
718         if (len == -1) {
719             len = 1;
720             wc  = rt->rbuf[pos];
721         }
722
723         pos += len ? len : 1;
724         madtty_putc(rt, wc);
725     }
726
727     rt->rlen -= pos;
728     memmove(rt->rbuf, rt->rbuf + pos, rt->rlen);
729     return 0;
730 }
731
732 madtty_t *madtty_create(int rows, int cols)
733 {
734     madtty_t *rt;
735     int i;
736
737     if (rows <= 0 || cols <= 0)
738         return NULL;
739
740     rt = (madtty_t*)calloc(sizeof(madtty_t), 1);
741     if (!rt)
742         return NULL;
743
744     /* record dimensions */
745     rt->rows = rows;
746     rt->cols = cols;
747
748     /* default mode is replace */
749     rt->insert = false; 
750
751     /* create the cell matrix */
752     rt->lines = (mtty_row_t*)calloc(sizeof(mtty_row_t), rt->rows);
753     for (i = 0; i < rt->rows; i++) {
754         rt->lines[i].text = (wchar_t *)calloc(sizeof(wchar_t), rt->cols);
755         rt->lines[i].attr = (uint16_t *)calloc(sizeof(uint16_t), rt->cols);
756     }
757
758     rt->pty = -1;  /* no pty for now */
759
760     /* initialization of other public fields */
761     rt->curs_row = rt->lines;
762     rt->curs_col = 0;
763     rt->curattrs = A_NORMAL;  /* white text over black background */
764
765     /* initial scrolling area is the whole window */
766     rt->scroll_top = rt->lines;
767     rt->scroll_bot = rt->lines + rt->rows;
768
769     return rt;
770 }
771
772 void madtty_destroy(madtty_t *rt)
773 {
774     int i;
775     if (!rt)
776         return;
777
778     for (i = 0; i < rt->rows; i++) {
779         free(rt->lines[i].text);
780         free(rt->lines[i].attr);
781     }
782     free(rt->lines);
783     free(rt);
784 }
785
786 void madtty_draw(madtty_t *rt, WINDOW *win, int srow, int scol)
787 {
788     curs_set(0);
789     for (int i = 0; i < rt->rows; i++) {
790         mtty_row_t *row = rt->lines + i;
791
792
793         if (!row->dirty)
794             continue;
795
796         wmove(win, srow + i, scol);
797         for (int j = 0; j < rt->cols; j++) {
798             if (!j || row->attr[j] != row->attr[j - 1])
799                 wattrset(win, (attr_t)row->attr[j] << NCURSES_ATTR_SHIFT);
800             if (row->text[j] >= 128) {
801                 char buf[MB_CUR_MAX + 1];
802                 int len;
803
804                 len = wcrtomb(buf, row->text[j], NULL);
805                 waddnstr(win, buf, len);
806             } else {
807                 waddch(win, row->text[j] > ' ' ? row->text[j] : ' ');
808             }
809         }
810         row->dirty = false;
811     }
812
813     wmove(win, srow + rt->curs_row - rt->lines, scol + rt->curs_col);
814     curs_set(!rt->curshid);
815 }
816
817 /******************************************************/
818
819 pid_t madtty_forkpty(madtty_t *rt, const char *path, const char *argv[])
820 {
821     struct winsize ws;
822     pid_t pid;
823
824     ws.ws_row    = rt->rows;
825     ws.ws_col    = rt->cols;
826     ws.ws_xpixel = ws.ws_ypixel = 0;
827
828     pid = forkpty(&rt->pty, NULL, NULL, &ws);
829     if (pid < 0)
830         return -1;
831
832     if (pid == 0) {
833         setsid();
834         setenv("TERM", "rxvt", 1);
835         execv(path, (char *const*)argv);
836         fprintf(stderr, "\nexecv() failed.\nCommand: '%s'\n", argv[0]);
837         exit(1);
838     }
839
840     return rt->childpid = pid;
841 }
842
843 void madtty_keypress(madtty_t *rt, int keycode)
844 {
845     char c = (char)keycode;
846     const char *buf;
847     int len;
848
849 #if 0
850     if (keycode == KEY_F(1)) {
851 #define MIN(a, b) ((a < (b)) ? a : (b))
852         kill(-rt->childpid, SIGWINCH);
853         rt->scroll_bot = MIN(rt->scroll_bot, rt->lines + rt->rows);
854         rt->curs_row = MIN(rt->curs_row, rt->lines + rt->rows);
855         printf(stderr, "%d\n", rt->rows);
856         return;
857     }
858 #endif
859     if (keycode >= 0 && keycode < KEY_MAX && keytable[keycode]) {
860         buf = keytable[keycode];
861         len = strlen(keytable[keycode]);
862     } else {
863         buf = &c;
864         len = 1;
865     }
866
867     while (len > 0) {
868         int res = write(rt->pty, buf, len);
869         if (res < 0 && errno != EAGAIN && errno != EINTR)
870             return;
871
872         buf += res;
873         len -= res;
874     }
875 }
876
877 void madtty_initialize(void)
878 {
879     setlocale(LC_ALL, "");
880     initscr();
881     start_color();
882     use_default_colors();
883     noecho();
884     raw();
885     nodelay(stdscr, TRUE);
886     keypad(stdscr, TRUE);
887
888     for (int i = -1; i < 8; i++) {
889         for (int j = -1; j < 8; j++) {
890             init_pair((i + 1) * 16 + j + 1, i, j);
891         }
892     }
893 }