merge more files
[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
28 #define MAX_CSI_ES_PARAMS 32
29
30 static inline void clamp_cursor_to_bounds(RoteTerm *rt)
31 {
32     if (rt->crow < 0) rt->curpos_dirty = true, rt->crow = 0;
33     if (rt->ccol < 0) rt->curpos_dirty = true, rt->ccol = 0;
34
35     if (rt->crow >= rt->rows) 
36         rt->curpos_dirty = true, rt->crow = rt->rows - 1;
37
38     if (rt->ccol >= rt->cols)
39         rt->curpos_dirty = true, rt->ccol = rt->cols - 1;
40 }
41
42 static void cursor_line_down(RoteTerm *rt)
43 {
44     int i;
45     rt->crow++;
46     rt->curpos_dirty = true;
47     if (rt->crow <= rt->pd->scrollbottom) return;
48
49     /* must scroll the scrolling region up by 1 line, and put cursor on 
50      * last line of it */
51     rt->crow = rt->pd->scrollbottom;
52
53     for (i = rt->pd->scrolltop; i < rt->pd->scrollbottom; i++) {
54         rt->line_dirty[i] = true;
55         memcpy(rt->cells[i], rt->cells[i+1], sizeof(RoteCell) * rt->cols);
56     }
57
58     rt->line_dirty[rt->pd->scrollbottom] = true;
59
60     /* clear last row of the scrolling region */
61     for (i = 0; i < rt->cols; i++) {
62         rt->cells[rt->pd->scrollbottom][i].ch = 0x20;
63         rt->cells[rt->pd->scrollbottom][i].attr = 0x70;
64     }
65
66 }
67
68 static void cursor_line_up(RoteTerm *rt)
69 {
70     int i;
71     rt->crow--;
72     rt->curpos_dirty = true;
73     if (rt->crow >= rt->pd->scrolltop) return;
74
75     /* must scroll the scrolling region up by 1 line, and put cursor on 
76      * first line of it */
77     rt->crow = rt->pd->scrolltop;
78
79     for (i = rt->pd->scrollbottom; i > rt->pd->scrolltop; i--) {
80         rt->line_dirty[i] = true;
81         memcpy(rt->cells[i], rt->cells[i-1], sizeof(RoteCell) * rt->cols);
82     }
83
84     rt->line_dirty[rt->pd->scrolltop] = true;
85
86     /* clear first row of the scrolling region */
87     for (i = 0; i < rt->cols; i++) {
88         rt->cells[rt->pd->scrolltop][i].ch = 0x20;
89         rt->cells[rt->pd->scrolltop][i].attr = 0x70;
90     }
91
92 }
93
94 static inline void put_normal_char(RoteTerm *rt, char c)
95 {
96     if (rt->ccol >= rt->cols) {
97         rt->ccol = 0;
98         cursor_line_down(rt);
99     }
100
101     if (rt->insert) {
102         int i;
103
104         for(i = rt->cols - 1; i >= rt->ccol+1; i--)
105             rt->cells[rt->crow][i] = rt->cells[rt->crow][i-1];
106     }
107
108     rt->cells[rt->crow][rt->ccol].ch = c;
109     rt->cells[rt->crow][rt->ccol].attr = rt->curattr;
110     rt->ccol++;
111
112     rt->line_dirty[rt->crow] = true;
113     rt->curpos_dirty = true;
114 }
115
116 static inline void put_graphmode_char(RoteTerm *rt, char c)
117 {
118     char nc;
119     /* do some very pitiful translation to regular ascii chars */
120     switch (c) {
121       case 'j': case 'k': case 'l': case 'm': case 'n': case 't': 
122       case 'u': case 'v': case 'w':
123         nc = '+'; break;
124       case 'x':
125         nc = '|'; break;
126       default:
127         nc = '%';
128     }
129
130     put_normal_char(rt, nc);
131 }
132
133 static inline void new_escape_sequence(RoteTerm *rt)
134 {
135     rt->pd->escaped = true;
136     rt->pd->esbuf_len = 0;
137     rt->pd->esbuf[0] = '\0';
138 }
139
140 static inline void cancel_escape_sequence(RoteTerm *rt)
141 {
142     rt->pd->escaped = false;
143     rt->pd->esbuf_len = 0;
144     rt->pd->esbuf[0] = '\0';
145 }
146
147 static void handle_control_char(RoteTerm *rt, char c)
148 {
149     switch (c) {
150       case '\r': rt->ccol = 0; break; /* carriage return */
151       case '\n':  /* line feed */
152                  rt->ccol = 0; cursor_line_down(rt);
153                  rt->curpos_dirty = true;
154                  break;
155       case '\b': /* backspace */
156                  if (rt->ccol > 0) rt->ccol--;
157                  rt->curpos_dirty = true;
158                  break;
159       case '\t': /* tab */
160                  rt->ccol += 8 - (rt->ccol % 8);
161                  clamp_cursor_to_bounds(rt);
162                  break;
163       case '\x1B': /* begin escape sequence (aborting previous one if any) */
164                  new_escape_sequence(rt);
165                  break;
166       case '\x0E': /* enter graphical character mode */
167                  rt->pd->graphmode = true;
168                  break;
169       case '\x0F': /* exit graphical character mode */
170                  rt->pd->graphmode = false;
171                  break;
172       case '\x9B': /* CSI character. Equivalent to ESC [ */
173                  new_escape_sequence(rt);
174                  rt->pd->esbuf[rt->pd->esbuf_len++] = '[';
175                  break;
176       case '\x18': case '\x1A': /* these interrupt escape sequences */
177                  cancel_escape_sequence(rt);
178                  break;
179       case '\a': /* bell */
180                  /* do nothing for now... maybe a visual bell would be nice? */
181                  break;
182 #ifdef DEBUG
183       default:
184                  fprintf(stderr, "Unrecognized control char: %d (^%c)\n", c, c + '@');
185                  break;
186 #endif
187     }
188 }
189
190 static inline bool is_valid_csi_ender(char c)
191 {
192     return (c >= 'a' && c <= 'z') ||
193         (c >= 'A' && c <= 'Z') ||
194         c == '@' || c == '`';
195 }
196
197 static void try_interpret_escape_seq(RoteTerm *rt)
198 {
199     char firstchar = rt->pd->esbuf[0];
200     char lastchar  = rt->pd->esbuf[rt->pd->esbuf_len-1];
201
202     if (!firstchar) return;  /* too early to do anything */
203
204     if (rt->pd->handler) {
205         /* call custom handler */
206 #ifdef DEBUG
207         fprintf(stderr, "Calling custom handler for ES <%s>.\n", rt->pd->esbuf);
208 #endif
209
210         int answer = (*(rt->pd->handler))(rt, rt->pd->esbuf);
211         if (answer == ROTE_HANDLERESULT_OK) {
212             /* successfully handled */
213 #ifdef DEBUG
214             fprintf(stderr, "Handler returned OK. Done with escape sequence.\n");
215 #endif
216
217             cancel_escape_sequence(rt);
218             return;
219         }
220         else if (answer == ROTE_HANDLERESULT_NOTYET) {
221             /* handler might handle it when more characters are appended to 
222              * it. So for now we don't interpret it */
223 #ifdef DEBUG
224             fprintf(stderr, "Handler returned NOTYET. Waiting for more chars.\n");
225 #endif
226
227             return;
228         }
229
230         /* If we got here then answer == ROTE_HANDLERESULT_NOWAY */
231         /* handler said it can't handle that escape sequence,
232          * but we can still try handling it ourselves, so 
233          * we proceed normally. */
234 #ifdef DEBUG
235         fprintf(stderr, "Handler returned NOWAY. Trying our handlers.\n");
236 #endif
237     }
238
239     /* interpret ESC-M as reverse line-feed */
240     if (firstchar == 'M') {
241         cursor_line_up(rt);
242         cancel_escape_sequence(rt);
243         return;
244     }
245
246     if (firstchar != '[' && firstchar != ']') {
247         /* unrecognized escape sequence. Let's forget about it. */
248 #ifdef DEBUG
249         fprintf(stderr, "Unrecognized ES: <%s>\n", rt->pd->esbuf);
250 #endif
251
252         cancel_escape_sequence(rt);
253         return;
254     }
255
256     if (firstchar == '[' && is_valid_csi_ender(lastchar)) {
257         /* we have a csi escape sequence: interpret it */
258         rote_es_interpret_csi(rt);
259         cancel_escape_sequence(rt);
260     }
261     else if (firstchar == ']' && lastchar == '\a') {
262         /* we have an xterm escape sequence: interpret it */
263
264         /* rote_es_interpret_xterm_es(rt);     -- TODO!*/
265 #ifdef DEBUG
266         fprintf(stderr, "Ignored XTerm ES.\n");
267 #endif
268         cancel_escape_sequence(rt);
269     }
270
271     /* if the escape sequence took up all available space and could
272      * not yet be parsed, abort it */
273     if (rt->pd->esbuf_len + 1 >= ESEQ_BUF_SIZE) cancel_escape_sequence(rt);
274 }
275
276 void rote_vt_inject(RoteTerm *rt, const char *data, int len)
277 {
278     int i;
279     for (i = 0; i < len; i++, data++) {
280         if (*data == 0) continue;  /* completely ignore NUL */
281         if (*data >= 1 && *data <= 31) {
282             handle_control_char(rt, *data);
283             continue;
284         }
285
286         if (rt->pd->escaped && rt->pd->esbuf_len < ESEQ_BUF_SIZE) {
287             /* append character to ongoing escape sequence */
288             rt->pd->esbuf[rt->pd->esbuf_len] = *data;
289             rt->pd->esbuf[++rt->pd->esbuf_len] = 0;
290
291             try_interpret_escape_seq(rt);
292         }
293         else if (rt->pd->graphmode)
294             put_graphmode_char(rt, *data);
295         else
296             put_normal_char(rt, *data);
297     }
298 }
299
300 /****************************************************************************/
301 /* CSI things                                                               */
302 /****************************************************************************/
303
304 /* interprets a 'set attribute' (SGR) CSI escape sequence */
305 static void interpret_csi_SGR(RoteTerm *rt, int param[], int pcount)
306 {
307     int i;
308
309     if (pcount == 0) {
310         /* special case: reset attributes */
311         rt->curattr = 0x70;
312         return;
313     }
314
315     for (i = 0; i < pcount; i++) {
316
317         // From http://vt100.net/docs/vt510-rm/SGR table 5-16
318         // 0    All attributes off
319         // 1    Bold
320         // 4    Underline
321         // 5    Blinking
322         // 7    Negative image
323         // 8    Invisible image
324         // 10   The ASCII character set is the current 7-bit
325         //      display character set (default) - SCO Console only.
326         // 11   Map Hex 00-7F of the PC character set codes
327         //      to the current 7-bit display character set
328         //      - SCO Console only.
329         // 12   Map Hex 80-FF of the current character set to
330         //      the current 7-bit display character set - SCO
331         //      Console only.
332         // 22   Bold off
333         // 24   Underline off
334         // 25   Blinking off
335         // 27   Negative image off
336         // 28   Invisible image off
337
338         if (param[i] == 0) rt->curattr = 0x70;
339         else if (param[i] == 1 || param[i] == 2 || param[i] == 4)  /* set bold */
340             ROTE_ATTR_MOD_BOLD(rt->curattr,1);
341         else if (param[i] == 5)  /* set blink */
342             ROTE_ATTR_MOD_BLINK(rt->curattr,1);
343         else if (param[i] == 7 || param[i] == 27) { /* reverse video */
344             int fg = ROTE_ATTR_FG(rt->curattr);
345             int bg = ROTE_ATTR_BG(rt->curattr);
346             ROTE_ATTR_MOD_FG(rt->curattr, bg);
347             ROTE_ATTR_MOD_BG(rt->curattr, fg);
348         }
349         else if (param[i] == 8) rt->curattr = 0x0;    /* invisible */
350         else if (param[i] == 22 || param[i] == 24) /* bold off */
351             ROTE_ATTR_MOD_BOLD(rt->curattr,0);
352         else if (param[i] == 25) /* blink off */
353             ROTE_ATTR_MOD_BLINK(rt->curattr,0);
354         else if (param[i] == 28) /* invisible off */
355             rt->curattr = 0x70;
356         else if (param[i] >= 30 && param[i] <= 37)    /* set fg */
357             ROTE_ATTR_MOD_FG(rt->curattr, param[i] - 30);
358         else if (param[i] >= 40 && param[i] <= 47)    /* set bg */
359             ROTE_ATTR_MOD_BG(rt->curattr, param[i] - 40);
360         else if (param[i] == 39)  /* reset foreground to default */
361             ROTE_ATTR_MOD_FG(rt->curattr, 7);
362         else if (param[i] == 49)  /* reset background to default */
363             ROTE_ATTR_MOD_BG(rt->curattr, 0);
364     }
365 }
366
367 /* interprets an 'erase display' (ED) escape sequence */
368 static void interpret_csi_ED(RoteTerm *rt, int param[], int pcount)
369 {
370     int r, c;
371     int start_row, start_col, end_row, end_col;
372
373     /* decide range */
374     if (pcount && param[0] == 2) 
375         start_row = 0, start_col = 0, end_row = rt->rows - 1,
376                   end_col = rt->cols - 1;
377
378     else if (pcount && param[0] == 1)
379         start_row = 0, start_col = 0, end_row = rt->crow,
380                   end_col = rt->ccol;
381
382     else start_row = rt->crow, start_col = rt->ccol,
383         end_row = rt->rows - 1, end_col = rt->cols - 1;
384
385     /* clean range */
386     for (r = start_row; r <= end_row; r++) {
387         rt->line_dirty[r] = true;
388
389         for (c = (r == start_row ? start_col : 0);
390              c <= (r == end_row ? end_col : rt->cols - 1);
391              c++) {
392             rt->cells[r][c].ch = 0x20;
393             rt->cells[r][c].attr = rt->curattr;
394         }
395     }
396 }
397
398 /* interprets a 'move cursor' (CUP) escape sequence */
399 static void interpret_csi_CUP(RoteTerm *rt, int param[], int pcount)
400 {
401     if (pcount == 0) {
402         /* special case */
403         rt->crow = rt->ccol = 0;
404         return;
405     }
406     else if (pcount < 2) return;  /* malformed */
407
408     rt->crow = param[0] - 1;  /* convert from 1-based to 0-based */
409     rt->ccol = param[1] - 1;  /* convert from 1-based to 0-based */
410
411     rt->curpos_dirty = true;
412
413     clamp_cursor_to_bounds(rt);
414 }
415
416 /* Interpret the 'relative mode' sequences: CUU, CUD, CUF, CUB, CNL,
417  * CPL, CHA, HPR, VPA, VPR, HPA */
418 static void interpret_csi_C(RoteTerm *rt, char verb, 
419                             int param[], int pcount) {
420     int n = (pcount && param[0] > 0) ? param[0] : 1;
421
422     switch (verb) {
423       case 'A':           rt->crow -= n; break;
424       case 'B': case 'e': rt->crow += n; break;
425       case 'C': case 'a': rt->ccol += n; break;
426       case 'D':           rt->ccol -= n; break;
427       case 'E':           rt->crow += n; rt->ccol = 0; break;
428       case 'F':           rt->crow -= n; rt->ccol = 0; break;
429       case 'G': case '`': rt->ccol  = param[0] - 1; break;
430       case 'd':           rt->crow  = param[0] - 1; break;
431     }
432
433     rt->curpos_dirty = true;
434     clamp_cursor_to_bounds(rt);
435 }
436
437 /* Interpret the 'erase line' escape sequence */
438 static void interpret_csi_EL(RoteTerm *rt, int param[], int pcount)
439 {
440     int erase_start, erase_end, i;
441     int cmd = pcount ? param[0] : 0;
442
443     switch (cmd) {
444       case 1:  erase_start = 0;           erase_end = rt->ccol;     break;
445       case 2:  erase_start = 0;           erase_end = rt->cols - 1; break;
446       default: erase_start = rt->ccol;    erase_end = rt->cols - 1; break;
447     }
448
449     for (i = erase_start; i <= erase_end; i++) {
450         rt->cells[rt->crow][i].ch = 0x20; 
451         rt->cells[rt->crow][i].attr = rt->curattr;
452     }
453
454     rt->line_dirty[rt->crow] = true;
455 }
456
457 /* Interpret the 'insert blanks' sequence (ICH) */
458 static void interpret_csi_ICH(RoteTerm *rt, int param[], int pcount)
459 {
460     int n = (pcount && param[0] > 0) ? param[0] : 1; 
461     int i;
462     for (i = rt->cols - 1; i >= rt->ccol + n; i--)
463         rt->cells[rt->crow][i] = rt->cells[rt->crow][i - n];
464     for (i = rt->ccol; i < rt->ccol + n; i++) {
465         rt->cells[rt->crow][i].ch = 0x20;
466         rt->cells[rt->crow][i].attr = rt->curattr;
467     }
468
469     rt->line_dirty[rt->crow] = true;
470 }
471
472 /* Interpret the 'delete chars' sequence (DCH) */
473 static void interpret_csi_DCH(RoteTerm *rt, int param[], int pcount)
474 {
475     int n = (pcount && param[0] > 0) ? param[0] : 1; 
476     int i;
477     for (i = rt->ccol; i < rt->cols; i++) {
478         if (i + n < rt->cols)
479             rt->cells[rt->crow][i] = rt->cells[rt->crow][i + n];
480         else {
481             rt->cells[rt->crow][i].ch = 0x20;
482             rt->cells[rt->crow][i].attr = rt->curattr;
483         }
484     }
485
486     rt->line_dirty[rt->crow] = true;
487 }
488
489 /* Interpret an 'insert line' sequence (IL) */
490 static void interpret_csi_IL(RoteTerm *rt, int param[], int pcount)
491 {
492     int n = (pcount && param[0] > 0) ? param[0] : 1;
493     int i, j;
494
495     for (i = rt->pd->scrollbottom; i >= rt->crow + n; i--) 
496         memcpy(rt->cells[i], rt->cells[i - n], sizeof(RoteCell) * rt->cols);
497
498     for (i = rt->crow; i < rt->crow + n && i <= rt->pd->scrollbottom; i++) {
499         rt->line_dirty[i] = true;
500         for (j = 0; j < rt->cols; j++) 
501             rt->cells[i][j].ch = 0x20, rt->cells[i][j].attr = rt->curattr;
502     }
503
504 }
505
506 /* Interpret a 'delete line' sequence (DL) */
507 static void interpret_csi_DL(RoteTerm *rt, int param[], int pcount)
508 {
509     int n = (pcount && param[0] > 0) ? param[0] : 1;
510     int i, j;
511
512     for (i = rt->crow; i <= rt->pd->scrollbottom; i++) {
513         rt->line_dirty[i] = true;
514         if (i + n <= rt->pd->scrollbottom)
515             memcpy(rt->cells[i], rt->cells[i+n], sizeof(RoteCell) * rt->cols);
516         else {
517             for (j = 0; j < rt->cols; j++)
518                 rt->cells[i][j].ch = 0x20, rt->cells[i][j].attr = rt->curattr;
519         }
520     }
521 }
522
523 /* Interpret an 'erase characters' (ECH) sequence */
524 static void interpret_csi_ECH(RoteTerm *rt, int param[], int pcount)
525 {
526     int n = (pcount && param[0] > 0) ? param[0] : 1;
527     int i;
528
529     for (i = rt->ccol; i < rt->ccol + n && i < rt->cols; i++) {
530         rt->cells[rt->crow][i].ch = 0x20;
531         rt->cells[rt->crow][i].attr = rt->curattr;
532     }
533
534     rt->line_dirty[rt->crow] = true;
535 }
536
537 /* Interpret a 'set scrolling region' (DECSTBM) sequence */
538 static void interpret_csi_DECSTBM(RoteTerm *rt, int param[], int pcount)
539 {
540     int newtop, newbottom;
541
542     if (!pcount) {
543         newtop = 0;
544         newbottom = rt->rows - 1;
545     }
546     else if (pcount < 2) return; /* malformed */
547     else {
548         newtop = param[0] - 1;
549         newbottom = param[1] - 1;
550     }
551
552     /* clamp to bounds */
553     if (newtop < 0) newtop = 0;
554     if (newtop >= rt->rows) newtop = rt->rows - 1;
555     if (newbottom < 0) newbottom = 0;
556     if (newbottom >= rt->rows) newbottom = rt->rows - 1;
557
558     /* check for range validity */
559     if (newtop > newbottom) return;
560     rt->pd->scrolltop = newtop;
561     rt->pd->scrollbottom = newbottom;
562 }
563
564 static void interpret_csi_SAVECUR(RoteTerm *rt, int param[], int pcount)
565 {
566     rt->pd->saved_x = rt->ccol;
567     rt->pd->saved_y = rt->crow;
568 }
569
570 static void interpret_csi_RESTORECUR(RoteTerm *rt, int param[], int pcount)
571 {
572     rt->ccol = rt->pd->saved_x;
573     rt->crow = rt->pd->saved_y;
574     rt->curpos_dirty = true;
575 }
576
577 void rote_es_interpret_csi(RoteTerm *rt)
578 {
579     static int csiparam[MAX_CSI_ES_PARAMS];
580     int param_count = 0;
581     const char *p = rt->pd->esbuf + 1;
582     char verb = rt->pd->esbuf[rt->pd->esbuf_len - 1];
583
584     if (!strncmp(rt->pd->esbuf, "[?", 2)) { /* private-mode CSI, ignore */
585 #ifdef DEBUG
586         fprintf(stderr, "Ignoring private-mode CSI: <%s>\n", rt->pd->esbuf);
587 #endif
588         return; 
589     }
590
591     /* parse numeric parameters */
592     while ((*p >= '0' && *p <= '9') || *p == ';') {
593         if (*p == ';') {
594             if (param_count >= MAX_CSI_ES_PARAMS) return; /* too long! */
595             csiparam[param_count++] = 0;
596         }
597         else {
598             if (param_count == 0) csiparam[param_count++] = 0;
599             csiparam[param_count - 1] *= 10;
600             csiparam[param_count - 1] += *p - '0';
601         }
602
603         p++;
604     }
605
606     /* delegate handling depending on command character (verb) */
607     switch (verb) {
608       case 'h':
609         if (param_count == 1 && csiparam[0] == 4) /* insert mode */ 
610             rt->insert = true;
611         break;
612       case 'l':
613         if (param_count == 1 && csiparam[0] == 4) /* replace mode */
614             rt->insert = false;
615         break;
616       case 'm': /* it's a 'set attribute' sequence */
617         interpret_csi_SGR(rt, csiparam, param_count); break;
618       case 'J': /* it's an 'erase display' sequence */
619         interpret_csi_ED(rt, csiparam, param_count); break;
620       case 'H': case 'f': /* it's a 'move cursor' sequence */
621         interpret_csi_CUP(rt, csiparam, param_count); break;
622       case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
623       case 'e': case 'a': case 'd': case '`':
624         /* it is a 'relative move' */
625         interpret_csi_C(rt, verb, csiparam, param_count); break;
626       case 'K': /* erase line */
627         interpret_csi_EL(rt, csiparam, param_count); break;
628       case '@': /* insert characters */
629         interpret_csi_ICH(rt, csiparam, param_count); break;
630       case 'P': /* delete characters */
631         interpret_csi_DCH(rt, csiparam, param_count); break;
632       case 'L': /* insert lines */
633         interpret_csi_IL(rt, csiparam, param_count); break;
634       case 'M': /* delete lines */
635         interpret_csi_DL(rt, csiparam, param_count); break;
636       case 'X': /* erase chars */
637         interpret_csi_ECH(rt, csiparam, param_count); break;
638       case 'r': /* set scrolling region */
639         interpret_csi_DECSTBM(rt, csiparam, param_count); break;
640       case 's': /* save cursor location */
641         interpret_csi_SAVECUR(rt, csiparam, param_count); break;
642       case 'u': /* restore cursor location */
643         interpret_csi_RESTORECUR(rt, csiparam, param_count); break;
644       default:
645 #ifdef DEBUG
646         fprintf(stderr, "Unrecogized CSI: verb=%c <%s>\n", 
647                 verb, rt->pd->esbuf); 
648 #endif
649         break;
650     }
651 }
652