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