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