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