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