Initial revision
[apps/madtty.git] / inject.c
1 #include "rote.h"
2 #include "roteprivate.h"
3 #include "inject_csi.h"
4 #include <string.h>
5
6 static void cursor_line_down(RoteTerm *rt) {
7    int i;
8    rt->crow++;
9    rt->curpos_dirty = true;
10    if (rt->crow <= rt->pd->scrollbottom) return;
11
12    /* must scroll the scrolling region up by 1 line, and put cursor on 
13     * last line of it */
14    rt->crow = rt->pd->scrollbottom;
15    
16    for (i = rt->pd->scrolltop; i < rt->pd->scrollbottom; i++) {
17       rt->line_dirty[i] = true;
18       memcpy(rt->cells[i], rt->cells[i+1], sizeof(RoteCell) * rt->cols);
19    }
20       
21    rt->line_dirty[rt->pd->scrollbottom] = true;
22
23    /* clear last row of the scrolling region */
24    for (i = 0; i < rt->cols; i++) {
25       rt->cells[rt->pd->scrollbottom][i].ch = 0x20;
26       rt->cells[rt->pd->scrollbottom][i].attr = 0x70;
27    }
28
29 }
30
31 static void cursor_line_up(RoteTerm *rt) {
32    int i;
33    rt->crow--;
34    rt->curpos_dirty = true;
35    if (rt->crow >= rt->pd->scrolltop) return;
36
37    /* must scroll the scrolling region up by 1 line, and put cursor on 
38     * first line of it */
39    rt->crow = rt->pd->scrolltop;
40    
41    for (i = rt->pd->scrollbottom; i > rt->pd->scrolltop; i--) {
42       rt->line_dirty[i] = true;
43       memcpy(rt->cells[i], rt->cells[i-1], sizeof(RoteCell) * rt->cols);
44    }
45       
46    rt->line_dirty[rt->pd->scrolltop] = true;
47
48    /* clear first row of the scrolling region */
49    for (i = 0; i < rt->cols; i++) {
50       rt->cells[rt->pd->scrolltop][i].ch = 0x20;
51       rt->cells[rt->pd->scrolltop][i].attr = 0x70;
52    }
53
54 }
55
56 static inline void put_normal_char(RoteTerm *rt, char c) {
57    rt->cells[rt->crow][rt->ccol].ch = c;
58    rt->cells[rt->crow][rt->ccol].attr = rt->curattr;
59    rt->ccol++;
60
61    rt->line_dirty[rt->crow] = true;
62    rt->curpos_dirty = true;
63
64    if (rt->ccol >= rt->cols) {
65       rt->ccol = 0;
66       cursor_line_down(rt);
67    }
68 }
69
70 static inline void put_graphmode_char(RoteTerm *rt, char c) {
71    char nc;
72    /* do some very pitiful translation to regular ascii chars */
73    switch (c) {
74       case 'j': case 'k': case 'l': case 'm': case 'n': case 't': 
75                                     case 'u': case 'v': case 'w':
76          nc = '+'; break;
77       case 'x':
78          nc = '|'; break;
79       default:
80          nc = '%';
81    }
82
83    put_normal_char(rt, nc);
84 }
85
86 static inline void new_escape_sequence(RoteTerm *rt) {
87    rt->pd->escaped = true;
88    rt->pd->esbuf_len = 0;
89    rt->pd->esbuf[0] = '\0';
90 }
91
92 static inline void cancel_escape_sequence(RoteTerm *rt) {
93    rt->pd->escaped = false;
94    rt->pd->esbuf_len = 0;
95    rt->pd->esbuf[0] = '\0';
96 }
97
98 static void handle_control_char(RoteTerm *rt, char c) {
99    switch (c) {
100       case '\r': rt->ccol = 0; break; /* carriage return */
101       case '\n':  /* line feed */
102          rt->ccol = 0; cursor_line_down(rt);
103          rt->curpos_dirty = true;
104          break;
105       case '\b': /* backspace */
106          if (rt->ccol > 0) rt->ccol--;
107          rt->curpos_dirty = true;
108          break;
109       case '\t': /* tab */
110          while (rt->ccol % 8) put_normal_char(rt, ' ');
111          break;
112       case '\x1B': /* begin escape sequence (aborting previous one if any) */
113          new_escape_sequence(rt);
114          break;
115       case '\x0E': /* enter graphical character mode */
116          rt->pd->graphmode = true;
117          break;
118       case '\x0F': /* exit graphical character mode */
119          rt->pd->graphmode = false;
120          break;
121       case '\x9B': /* CSI character. Equivalent to ESC [ */
122          new_escape_sequence(rt);
123          rt->pd->esbuf[rt->pd->esbuf_len++] = '[';
124          break;
125       case '\x18': case '\x1A': /* these interrupt escape sequences */
126          cancel_escape_sequence(rt);
127          break;
128       case '\a': /* bell */
129          /* do nothing for now... maybe a visual bell would be nice? */
130          break;
131       #ifdef DEBUG
132       default:
133          fprintf(stderr, "Unrecognized control char: %d (^%c)\n", c, c + '@');
134          break;
135       #endif
136    }
137 }
138
139 static inline bool is_valid_csi_ender(char c) {
140    return (c >= 'a' && c <= 'z') ||
141           (c >= 'A' && c <= 'Z') ||
142           c == '@' || c == '`';
143 }
144
145 static void try_interpret_escape_seq(RoteTerm *rt) {
146    char firstchar = rt->pd->esbuf[0];
147    char lastchar  = rt->pd->esbuf[rt->pd->esbuf_len-1];
148
149    if (!firstchar) return;  /* too early to do anything */
150
151    if (rt->pd->handler) {
152       /* call custom handler */
153       #ifdef DEBUG
154       fprintf(stderr, "Calling custom handler for ES <%s>.\n", rt->pd->esbuf);
155       #endif
156
157       int answer = (*(rt->pd->handler))(rt, rt->pd->esbuf);
158       if (answer == ROTE_HANDLERESULT_OK) {
159          /* successfully handled */
160          #ifdef DEBUG
161          fprintf(stderr, "Handler returned OK. Done with escape sequence.\n");
162          #endif
163
164          cancel_escape_sequence(rt);
165          return;
166       }
167       else if (answer == ROTE_HANDLERESULT_NOTYET) {
168          /* handler might handle it when more characters are appended to 
169           * it. So for now we don't interpret it */
170          #ifdef DEBUG
171          fprintf(stderr, "Handler returned NOTYET. Waiting for more chars.\n");
172          #endif
173
174          return;
175       }
176    
177       /* If we got here then answer == ROTE_HANDLERESULT_NOWAY */
178       /* handler said it can't handle that escape sequence,
179        * but we can still try handling it ourselves, so 
180        * we proceed normally. */
181       #ifdef DEBUG
182       fprintf(stderr, "Handler returned NOWAY. Trying our handlers.\n");
183       #endif
184    }
185
186    /* interpret ESC-M as reverse line-feed */
187    if (firstchar == 'M') {
188       cursor_line_up(rt);
189       cancel_escape_sequence(rt);
190       return;
191    }
192
193    if (firstchar != '[' && firstchar != ']') {
194       /* unrecognized escape sequence. Let's forget about it. */
195       #ifdef DEBUG
196       fprintf(stderr, "Unrecognized ES: <%s>\n", rt->pd->esbuf);
197       #endif
198
199       cancel_escape_sequence(rt);
200       return;
201    }
202
203    if (firstchar == '[' && is_valid_csi_ender(lastchar)) {
204       /* we have a csi escape sequence: interpret it */
205       rote_es_interpret_csi(rt);
206       cancel_escape_sequence(rt);
207    }
208    else if (firstchar == ']' && lastchar == '\a') {
209       /* we have an xterm escape sequence: interpret it */
210
211       /* rote_es_interpret_xterm_es(rt);     -- TODO!*/
212       #ifdef DEBUG
213       fprintf(stderr, "Ignored XTerm ES.\n");
214       #endif
215       cancel_escape_sequence(rt);
216    }
217
218    /* if the escape sequence took up all available space and could
219     * not yet be parsed, abort it */
220    if (rt->pd->esbuf_len + 1 >= ESEQ_BUF_SIZE) cancel_escape_sequence(rt);
221 }
222    
223 void rote_vt_inject(RoteTerm *rt, const char *data, int len) {
224    int i;
225    for (i = 0; i < len; i++, data++) {
226       if (*data == 0) continue;  /* completely ignore NUL */
227       if (*data >= 1 && *data <= 31) {
228          handle_control_char(rt, *data);
229          continue;
230       }
231
232       if (rt->pd->escaped && rt->pd->esbuf_len < ESEQ_BUF_SIZE) {
233          /* append character to ongoing escape sequence */
234          rt->pd->esbuf[rt->pd->esbuf_len] = *data;
235          rt->pd->esbuf[++rt->pd->esbuf_len] = 0;
236
237          try_interpret_escape_seq(rt);
238       }
239       else if (rt->pd->graphmode)
240          put_graphmode_char(rt, *data);
241       else
242          put_normal_char(rt, *data);
243    }
244 }
245