]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - terminal.c
D'oh, trivial typo which was completely breaking log-all-output.
[PuTTY.git] / terminal.c
1 #include <windows.h>
2
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <ctype.h>
6
7 #include <time.h>
8 #include <assert.h>
9 #include "putty.h"
10 #include "tree234.h"
11
12 #define VT52_PLUS
13
14 #define CL_ANSIMIN      0x0001         /* Codes in all ANSI like terminals. */
15 #define CL_VT100        0x0002         /* VT100 */
16 #define CL_VT100AVO     0x0004         /* VT100 +AVO; 132x24 (not 132x14) & attrs */
17 #define CL_VT102        0x0008         /* VT102 */
18 #define CL_VT220        0x0010         /* VT220 */
19 #define CL_VT320        0x0020         /* VT320 */
20 #define CL_VT420        0x0040         /* VT420 */
21 #define CL_VT510        0x0080         /* VT510, NB VT510 includes ANSI */
22 #define CL_VT340TEXT    0x0100         /* VT340 extensions that appear in the VT420 */
23 #define CL_SCOANSI      0x1000         /* SCOANSI not in ANSIMIN. */
24 #define CL_ANSI         0x2000         /* ANSI ECMA-48 not in the VT100..VT420 */
25 #define CL_OTHER        0x4000         /* Others, Xterm, linux, putty, dunno, etc */
26
27 #define TM_VT100        (CL_ANSIMIN|CL_VT100)
28 #define TM_VT100AVO     (TM_VT100|CL_VT100AVO)
29 #define TM_VT102        (TM_VT100AVO|CL_VT102)
30 #define TM_VT220        (TM_VT102|CL_VT220)
31 #define TM_VTXXX        (TM_VT220|CL_VT340TEXT|CL_VT510|CL_VT420|CL_VT320)
32 #define TM_SCOANSI      (CL_ANSIMIN|CL_SCOANSI)
33
34 #define TM_PUTTY        (0xFFFF)
35
36 #define compatibility(x) \
37     if ( ((CL_##x)&compatibility_level) == 0 ) {        \
38        termstate=TOPLEVEL;                              \
39        break;                                           \
40     }
41 #define compatibility2(x,y) \
42     if ( ((CL_##x|CL_##y)&compatibility_level) == 0 ) { \
43        termstate=TOPLEVEL;                              \
44        break;                                           \
45     }
46
47 #define has_compat(x) ( ((CL_##x)&compatibility_level) != 0 )
48
49 static int compatibility_level = TM_PUTTY;
50
51 static tree234 *scrollback;            /* lines scrolled off top of screen */
52 static tree234 *screen;                /* lines on primary screen */
53 static tree234 *alt_screen;            /* lines on alternate screen */
54 static int disptop;                    /* distance scrolled back (0 or -ve) */
55
56 static unsigned long *cpos;            /* cursor position (convenience) */
57
58 static unsigned long *disptext;        /* buffer of text on real screen */
59 static unsigned long *dispcurs;        /* location of cursor on real screen */
60 static unsigned long curstype;         /* type of cursor on real screen */
61
62 #define VBELL_TIMEOUT 100              /* millisecond len of visual bell */
63
64 struct beeptime {
65     struct beeptime *next;
66     long ticks;
67 };
68 static struct beeptime *beephead, *beeptail;
69 int nbeeps;
70 int beep_overloaded;
71 long lastbeep;
72
73 #define TSIZE (sizeof(unsigned long))
74 #define fix_cpos do { cpos = lineptr(curs.y) + curs.x; } while(0)
75
76 static unsigned long curr_attr, save_attr;
77 static unsigned long erase_char = ERASE_CHAR;
78
79 typedef struct {
80     int y, x;
81 } pos;
82 #define poslt(p1,p2) ( (p1).y < (p2).y || ( (p1).y == (p2).y && (p1).x < (p2).x ) )
83 #define posle(p1,p2) ( (p1).y < (p2).y || ( (p1).y == (p2).y && (p1).x <= (p2).x ) )
84 #define poseq(p1,p2) ( (p1).y == (p2).y && (p1).x == (p2).x )
85 #define posdiff(p1,p2) ( ((p1).y - (p2).y) * (cols+1) + (p1).x - (p2).x )
86 #define incpos(p) ( (p).x == cols ? ((p).x = 0, (p).y++, 1) : ((p).x++, 0) )
87 #define decpos(p) ( (p).x == 0 ? ((p).x = cols, (p).y--, 1) : ((p).x--, 0) )
88
89 static bufchain inbuf;                 /* terminal input buffer */
90 static pos curs;                       /* cursor */
91 static pos savecurs;                   /* saved cursor position */
92 static int marg_t, marg_b;             /* scroll margins */
93 static int dec_om;                     /* DEC origin mode flag */
94 static int wrap, wrapnext;             /* wrap flags */
95 static int insert;                     /* insert-mode flag */
96 static int cset;                       /* 0 or 1: which char set */
97 static int save_cset, save_csattr;     /* saved with cursor position */
98 static int save_utf;                   /* saved with cursor position */
99 static int rvideo;                     /* global reverse video flag */
100 static int rvbell_timeout;             /* for ESC[?5hESC[?5l vbell */
101 static int cursor_on;                  /* cursor enabled flag */
102 static int reset_132;                  /* Flag ESC c resets to 80 cols */
103 static int use_bce;                    /* Use Background coloured erase */
104 static int blinker;                    /* When blinking is the cursor on ? */
105 static int tblinker;                   /* When the blinking text is on */
106 static int blink_is_real;              /* Actually blink blinking text */
107 static int term_echoing;               /* Does terminal want local echo? */
108 static int term_editing;               /* Does terminal want local edit? */
109 static int sco_acs, save_sco_acs;      /* CSI 10,11,12m -> OEM charset */
110 static int vt52_bold;                  /* Force bold on non-bold colours */
111 static int utf_state;                  /* Is there a pending UTF-8 character */
112 static int utf_char;                   /* and what is it so far. */
113 static int utf_size;                   /* The size of the UTF character. */
114
115 static int xterm_mouse;                /* send mouse messages to app */
116
117 static unsigned long cset_attr[2];
118
119 /*
120  * Saved settings on the alternate screen.
121  */
122 static int alt_x, alt_y, alt_om, alt_wrap, alt_wnext, alt_ins, alt_cset, alt_sco_acs, alt_utf;
123 static int alt_t, alt_b;
124 static int alt_which;
125
126 #define ARGS_MAX 32                    /* max # of esc sequence arguments */
127 #define ARG_DEFAULT 0                  /* if an arg isn't specified */
128 #define def(a,d) ( (a) == ARG_DEFAULT ? (d) : (a) )
129 static int esc_args[ARGS_MAX];
130 static int esc_nargs;
131 static int esc_query;
132 #define ANSI(x,y)       ((x)+((y)<<8))
133 #define ANSI_QUE(x)     ANSI(x,TRUE)
134
135 #define OSC_STR_MAX 2048
136 static int osc_strlen;
137 static char osc_string[OSC_STR_MAX + 1];
138 static int osc_w;
139
140 static char id_string[1024] = "\033[?6c";
141
142 static unsigned char *tabs;
143
144 static enum {
145     TOPLEVEL,
146     SEEN_ESC,
147     SEEN_CSI,
148     SEEN_OSC,
149     SEEN_OSC_W,
150
151     DO_CTRLS,
152
153     SEEN_OSC_P,
154     OSC_STRING, OSC_MAYBE_ST,
155     VT52_ESC,
156     VT52_Y1,
157     VT52_Y2,
158     VT52_FG,
159     VT52_BG
160 } termstate;
161
162 static enum {
163     NO_SELECTION, ABOUT_TO, DRAGGING, SELECTED
164 } selstate;
165 static enum {
166     SM_CHAR, SM_WORD, SM_LINE
167 } selmode;
168 static pos selstart, selend, selanchor;
169
170 static short wordness[256] = {
171     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
172     0, 0, 0, 0, 0, 0, 0, 0,            /* 01 */
173     0, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
174     2, 2, 1, 1, 1, 1, 1, 1,            /* 23 */
175     1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
176     2, 2, 2, 1, 1, 1, 1, 2,            /* 45 */
177     1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
178     2, 2, 2, 1, 1, 1, 1, 1,            /* 67 */
179     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
180     1, 1, 1, 1, 1, 1, 1, 1,            /* 89 */
181     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
182     1, 1, 1, 1, 1, 1, 1, 1,            /* AB */
183     2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1,
184     2, 2, 2, 2, 2, 2, 2, 2,            /* CD */
185     2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1,
186     2, 2, 2, 2, 2, 2, 2, 2,            /* EF */
187 };
188
189 #define sel_nl_sz  (sizeof(sel_nl)/sizeof(wchar_t))
190 static wchar_t sel_nl[] = SEL_NL;
191 static wchar_t *paste_buffer = 0;
192 static int paste_len, paste_pos, paste_hold;
193
194 /*
195  * Internal prototypes.
196  */
197 static void do_paint(Context, int);
198 static void erase_lots(int, int, int);
199 static void swap_screen(int);
200 static void update_sbar(void);
201 static void deselect(void);
202 /* log session to file stuff ... */
203 static FILE *lgfp = NULL;
204 static void logtraffic(unsigned char c, int logmode);
205 static void xlatlognam(char *d, char *s, char *hostname, struct tm *tm);
206
207 /*
208  * Resize a line to make it `cols' columns wide.
209  */
210 unsigned long *resizeline(unsigned long *line, int cols)
211 {
212     int i, oldlen;
213     unsigned long lineattrs;
214
215     if (line[0] != (unsigned long)cols) {
216         /*
217          * This line is the wrong length, which probably means it
218          * hasn't been accessed since a resize. Resize it now.
219          */
220         oldlen = line[0];
221         lineattrs = line[oldlen + 1];
222         line = srealloc(line, TSIZE * (2 + cols));
223         line[0] = cols;
224         for (i = oldlen; i < cols; i++)
225             line[i + 1] = ERASE_CHAR;
226         line[cols + 1] = lineattrs & LATTR_MODE;
227     }
228
229     return line;
230 }
231
232 /*
233  * Retrieve a line of the screen or of the scrollback, according to
234  * whether the y coordinate is non-negative or negative
235  * (respectively).
236  */
237 unsigned long *lineptr(int y, int lineno)
238 {
239     unsigned long *line, *newline;
240     tree234 *whichtree;
241     int treeindex;
242
243     if (y >= 0) {
244         whichtree = screen;
245         treeindex = y;
246     } else {
247         whichtree = scrollback;
248         treeindex = y + count234(scrollback);
249     }
250     line = index234(whichtree, treeindex);
251
252     /* We assume that we don't screw up and retrieve something out of range. */
253     assert(line != NULL);
254
255     newline = resizeline(line, cols);
256     if (newline != line) {
257         delpos234(whichtree, treeindex);
258         addpos234(whichtree, newline, treeindex);
259         line = newline;
260     }
261
262     return line + 1;
263 }
264
265 #define lineptr(x) lineptr(x,__LINE__)
266 /*
267  * Set up power-on settings for the terminal.
268  */
269 static void power_on(void)
270 {
271     curs.x = curs.y = alt_x = alt_y = savecurs.x = savecurs.y = 0;
272     alt_t = marg_t = 0;
273     if (rows != -1)
274         alt_b = marg_b = rows - 1;
275     else
276         alt_b = marg_b = 0;
277     if (cols != -1) {
278         int i;
279         for (i = 0; i < cols; i++)
280             tabs[i] = (i % 8 == 0 ? TRUE : FALSE);
281     }
282     alt_om = dec_om = cfg.dec_om;
283     alt_wnext = wrapnext = alt_ins = insert = FALSE;
284     alt_wrap = wrap = cfg.wrap_mode;
285     alt_cset = cset = 0;
286     alt_utf = utf = 0;
287     alt_sco_acs = sco_acs = 0;
288     cset_attr[0] = cset_attr[1] = ATTR_ASCII;
289     rvideo = 0;
290     in_vbell = FALSE;
291     cursor_on = 1;
292     big_cursor = 0;
293     save_attr = curr_attr = ATTR_DEFAULT;
294     term_editing = term_echoing = FALSE;
295     ldisc_send(NULL, 0, 0);            /* cause ldisc to notice changes */
296     app_cursor_keys = cfg.app_cursor;
297     app_keypad_keys = cfg.app_keypad;
298     use_bce = cfg.bce;
299     blink_is_real = cfg.blinktext;
300     erase_char = ERASE_CHAR;
301     alt_which = 0;
302     {
303         int i;
304         for (i = 0; i < 256; i++)
305             wordness[i] = cfg.wordness[i];
306     }
307     if (screen) {
308         swap_screen(1);
309         erase_lots(FALSE, TRUE, TRUE);
310         swap_screen(0);
311         erase_lots(FALSE, TRUE, TRUE);
312     }
313 }
314
315 /*
316  * Force a screen update.
317  */
318 void term_update(void)
319 {
320     Context ctx;
321     ctx = get_ctx();
322     if (ctx) {
323         int need_sbar_update = seen_disp_event;
324         if ((seen_key_event && (cfg.scroll_on_key)) ||
325             (seen_disp_event && (cfg.scroll_on_disp))) {
326             disptop = 0;               /* return to main screen */
327             seen_disp_event = seen_key_event = 0;
328             need_sbar_update = TRUE;
329         }
330         if (need_sbar_update)
331             update_sbar();
332         do_paint(ctx, TRUE);
333         sys_cursor(curs.x, curs.y - disptop);
334         free_ctx(ctx);
335     }
336 }
337
338 /*
339  * Same as power_on(), but an external function.
340  */
341 void term_pwron(void)
342 {
343     power_on();
344     fix_cpos;
345     disptop = 0;
346     deselect();
347     term_update();
348 }
349
350 /*
351  * Clear the scrollback.
352  */
353 void term_clrsb(void)
354 {
355     unsigned long *line;
356     disptop = 0;
357     while ((line = delpos234(scrollback, 0)) != NULL) {
358         sfree(line);
359     }
360     update_sbar();
361 }
362
363 /*
364  * Initialise the terminal.
365  */
366 void term_init(void)
367 {
368     screen = alt_screen = scrollback = NULL;
369     disptop = 0;
370     disptext = dispcurs = NULL;
371     tabs = NULL;
372     deselect();
373     rows = cols = -1;
374     power_on();
375     beephead = beeptail = NULL;
376     nbeeps = 0;
377     lastbeep = FALSE;
378     beep_overloaded = FALSE;
379 }
380
381 /*
382  * Set up the terminal for a given size.
383  */
384 void term_size(int newrows, int newcols, int newsavelines)
385 {
386     tree234 *newalt;
387     unsigned long *newdisp, *line;
388     int i, j;
389     int sblen;
390     int save_alt_which = alt_which;
391
392     if (newrows == rows && newcols == cols && newsavelines == savelines)
393         return;                        /* nothing to do */
394
395     deselect();
396     swap_screen(0);
397
398     alt_t = marg_t = 0;
399     alt_b = marg_b = newrows - 1;
400
401     if (rows == -1) {
402         scrollback = newtree234(NULL);
403         screen = newtree234(NULL);
404         rows = 0;
405     }
406
407     /*
408      * Resize the screen and scrollback. We only need to shift
409      * lines around within our data structures, because lineptr()
410      * will take care of resizing each individual line if
411      * necessary. So:
412      * 
413      *  - If the new screen and the old screen differ in length, we
414      *    must shunt some lines in from the scrollback or out to
415      *    the scrollback.
416      * 
417      *  - If doing that fails to provide us with enough material to
418      *    fill the new screen (i.e. the number of rows needed in
419      *    the new screen exceeds the total number in the previous
420      *    screen+scrollback), we must invent some blank lines to
421      *    cover the gap.
422      * 
423      *  - Then, if the new scrollback length is less than the
424      *    amount of scrollback we actually have, we must throw some
425      *    away.
426      */
427     sblen = count234(scrollback);
428     /* Do this loop to expand the screen if newrows > rows */
429     for (i = rows; i < newrows; i++) {
430         if (sblen > 0) {
431             line = delpos234(scrollback, --sblen);
432         } else {
433             line = smalloc(TSIZE * (newcols + 2));
434             line[0] = newcols;
435             for (j = 0; j <= newcols; j++)
436                 line[j + 1] = ERASE_CHAR;
437         }
438         addpos234(screen, line, 0);
439     }
440     /* Do this loop to shrink the screen if newrows < rows */
441     for (i = newrows; i < rows; i++) {
442         line = delpos234(screen, 0);
443         addpos234(scrollback, line, sblen++);
444     }
445     assert(count234(screen) == newrows);
446     while (sblen > newsavelines) {
447         line = delpos234(scrollback, 0);
448         sfree(line);
449         sblen--;
450     }
451     assert(count234(scrollback) <= newsavelines);
452     disptop = 0;
453
454     newdisp = smalloc(newrows * (newcols + 1) * TSIZE);
455     for (i = 0; i < newrows * (newcols + 1); i++)
456         newdisp[i] = ATTR_INVALID;
457     sfree(disptext);
458     disptext = newdisp;
459     dispcurs = NULL;
460
461     newalt = newtree234(NULL);
462     for (i = 0; i < newrows; i++) {
463         line = smalloc(TSIZE * (newcols + 2));
464         line[0] = newcols;
465         for (j = 0; j <= newcols; j++)
466             line[j + 1] = erase_char;
467         addpos234(newalt, line, i);
468     }
469     if (alt_screen) {
470         while (NULL != (line = delpos234(alt_screen, 0)))
471             sfree(line);
472         freetree234(alt_screen);
473     }
474     alt_screen = newalt;
475
476     tabs = srealloc(tabs, newcols * sizeof(*tabs));
477     {
478         int i;
479         for (i = (cols > 0 ? cols : 0); i < newcols; i++)
480             tabs[i] = (i % 8 == 0 ? TRUE : FALSE);
481     }
482
483     if (rows > 0)
484         curs.y += newrows - rows;
485     if (curs.y < 0)
486         curs.y = 0;
487     if (curs.y >= newrows)
488         curs.y = newrows - 1;
489     if (curs.x >= newcols)
490         curs.x = newcols - 1;
491     alt_x = alt_y = 0;
492     wrapnext = alt_wnext = FALSE;
493
494     rows = newrows;
495     cols = newcols;
496     savelines = newsavelines;
497     fix_cpos;
498
499     swap_screen(save_alt_which);
500
501     update_sbar();
502     term_update();
503     back->size();
504 }
505
506 /*
507  * Swap screens.
508  */
509 static void swap_screen(int which)
510 {
511     int t;
512     tree234 *ttr;
513
514     if (which == alt_which)
515         return;
516
517     alt_which = which;
518
519     ttr = alt_screen;
520     alt_screen = screen;
521     screen = ttr;
522     t = curs.x;
523     curs.x = alt_x;
524     alt_x = t;
525     t = curs.y;
526     curs.y = alt_y;
527     alt_y = t;
528     t = marg_t;
529     marg_t = alt_t;
530     alt_t = t;
531     t = marg_b;
532     marg_b = alt_b;
533     alt_b = t;
534     t = dec_om;
535     dec_om = alt_om;
536     alt_om = t;
537     t = wrap;
538     wrap = alt_wrap;
539     alt_wrap = t;
540     t = wrapnext;
541     wrapnext = alt_wnext;
542     alt_wnext = t;
543     t = insert;
544     insert = alt_ins;
545     alt_ins = t;
546     t = cset;
547     cset = alt_cset;
548     alt_cset = t;
549     t = utf;
550     utf = alt_utf;
551     alt_utf = t;
552     t = sco_acs;
553     sco_acs = alt_sco_acs;
554     alt_sco_acs = t;
555
556     fix_cpos;
557 }
558
559 /*
560  * Update the scroll bar.
561  */
562 static void update_sbar(void)
563 {
564     int nscroll;
565
566     nscroll = count234(scrollback);
567
568     set_sbar(nscroll + rows, nscroll + disptop, rows);
569 }
570
571 /*
572  * Check whether the region bounded by the two pointers intersects
573  * the scroll region, and de-select the on-screen selection if so.
574  */
575 static void check_selection(pos from, pos to)
576 {
577     if (poslt(from, selend) && poslt(selstart, to))
578         deselect();
579 }
580
581 /*
582  * Scroll the screen. (`lines' is +ve for scrolling forward, -ve
583  * for backward.) `sb' is TRUE if the scrolling is permitted to
584  * affect the scrollback buffer.
585  * 
586  * NB this function invalidates all pointers into lines of the
587  * screen data structures. In particular, you MUST call fix_cpos
588  * after calling scroll() and before doing anything else that
589  * uses the cpos shortcut pointer.
590  */
591 static void scroll(int topline, int botline, int lines, int sb)
592 {
593     unsigned long *line, *line2;
594     int i, seltop;
595
596     if (topline != 0 || alt_which != 0)
597         sb = FALSE;
598
599     if (lines < 0) {
600         while (lines < 0) {
601             line = delpos234(screen, botline);
602             line = resizeline(line, cols);
603             for (i = 0; i < cols; i++)
604                 line[i + 1] = erase_char;
605             line[cols + 1] = 0;
606             addpos234(screen, line, topline);
607
608             if (selstart.y >= topline && selstart.y <= botline) {
609                 selstart.y++;
610                 if (selstart.y > botline) {
611                     selstart.y = botline;
612                     selstart.x = 0;
613                 }
614             }
615             if (selend.y >= topline && selend.y <= botline) {
616                 selend.y++;
617                 if (selend.y > botline) {
618                     selend.y = botline;
619                     selend.x = 0;
620                 }
621             }
622
623             lines++;
624         }
625     } else {
626         while (lines > 0) {
627             line = delpos234(screen, topline);
628             if (sb && savelines > 0) {
629                 int sblen = count234(scrollback);
630                 /*
631                  * We must add this line to the scrollback. We'll
632                  * remove a line from the top of the scrollback to
633                  * replace it, or allocate a new one if the
634                  * scrollback isn't full.
635                  */
636                 if (sblen == savelines) {
637                     sblen--, line2 = delpos234(scrollback, 0);
638                 } else {
639                     line2 = smalloc(TSIZE * (cols + 2));
640                     line2[0] = cols;
641                 }
642                 addpos234(scrollback, line, sblen);
643                 line = line2;
644
645                 /*
646                  * If the user is currently looking at part of the
647                  * scrollback, and they haven't enabled any options
648                  * that are going to reset the scrollback as a
649                  * result of this movement, then the chances are
650                  * they'd like to keep looking at the same line. So
651                  * we move their viewpoint at the same rate as the
652                  * scroll, at least until their viewpoint hits the
653                  * top end of the scrollback buffer, at which point
654                  * we don't have the choice any more.
655                  * 
656                  * Thanks to Jan Holmen Holsten for the idea and
657                  * initial implementation.
658                  */
659                 if (disptop > -savelines && disptop < 0)
660                     disptop--;
661             }
662             line = resizeline(line, cols);
663             for (i = 0; i < cols; i++)
664                 line[i + 1] = erase_char;
665             line[cols + 1] = 0;
666             addpos234(screen, line, botline);
667
668             /*
669              * If the selection endpoints move into the scrollback,
670              * we keep them moving until they hit the top. However,
671              * of course, if the line _hasn't_ moved into the
672              * scrollback then we don't do this, and cut them off
673              * at the top of the scroll region.
674              * 
675              * This applies to selstart and selend (for an existing
676              * selection), and also selanchor (for one being
677              * selected as we speak).
678              */
679             seltop = sb ? -savelines : 0;
680
681             if (selstart.y >= seltop && selstart.y <= botline) {
682                 selstart.y--;
683                 if (selstart.y < seltop) {
684                     selstart.y = seltop;
685                     selstart.x = 0;
686                 }
687             }
688             if (selend.y >= seltop && selend.y <= botline) {
689                 selend.y--;
690                 if (selend.y < seltop) {
691                     selend.y = seltop;
692                     selend.x = 0;
693                 }
694             }
695             if (selanchor.y >= seltop && selanchor.y <= botline) {
696                 selanchor.y--;
697                 if (selanchor.y < seltop) {
698                     selanchor.y = seltop;
699                     selanchor.x = 0;
700                 }
701             }
702
703             lines--;
704         }
705     }
706 }
707
708 /*
709  * Move the cursor to a given position, clipping at boundaries. We
710  * may or may not want to clip at the scroll margin: marg_clip is 0
711  * not to, 1 to disallow _passing_ the margins, and 2 to disallow
712  * even _being_ outside the margins.
713  */
714 static void move(int x, int y, int marg_clip)
715 {
716     if (x < 0)
717         x = 0;
718     if (x >= cols)
719         x = cols - 1;
720     if (marg_clip) {
721         if ((curs.y >= marg_t || marg_clip == 2) && y < marg_t)
722             y = marg_t;
723         if ((curs.y <= marg_b || marg_clip == 2) && y > marg_b)
724             y = marg_b;
725     }
726     if (y < 0)
727         y = 0;
728     if (y >= rows)
729         y = rows - 1;
730     curs.x = x;
731     curs.y = y;
732     fix_cpos;
733     wrapnext = FALSE;
734 }
735
736 /*
737  * Save or restore the cursor and SGR mode.
738  */
739 static void save_cursor(int save)
740 {
741     if (save) {
742         savecurs = curs;
743         save_attr = curr_attr;
744         save_cset = cset;
745         save_utf = utf;
746         save_csattr = cset_attr[cset];
747         save_sco_acs = sco_acs;
748     } else {
749         curs = savecurs;
750         /* Make sure the window hasn't shrunk since the save */
751         if (curs.x >= cols)
752             curs.x = cols - 1;
753         if (curs.y >= rows)
754             curs.y = rows - 1;
755
756         curr_attr = save_attr;
757         cset = save_cset;
758         utf = save_utf;
759         cset_attr[cset] = save_csattr;
760         sco_acs = save_sco_acs;
761         fix_cpos;
762         if (use_bce)
763             erase_char = (' ' | ATTR_ASCII |
764                          (curr_attr & (ATTR_FGMASK | ATTR_BGMASK)));
765     }
766 }
767
768 /*
769  * Erase a large portion of the screen: the whole screen, or the
770  * whole line, or parts thereof.
771  */
772 static void erase_lots(int line_only, int from_begin, int to_end)
773 {
774     pos start, end;
775     int erase_lattr;
776     unsigned long *ldata;
777
778     if (line_only) {
779         start.y = curs.y;
780         start.x = 0;
781         end.y = curs.y + 1;
782         end.x = 0;
783         erase_lattr = FALSE;
784     } else {
785         start.y = 0;
786         start.x = 0;
787         end.y = rows;
788         end.x = 0;
789         erase_lattr = TRUE;
790     }
791     if (!from_begin) {
792         start = curs;
793     }
794     if (!to_end) {
795         end = curs;
796         incpos(end);
797     }
798     check_selection(start, end);
799
800     /* Clear screen also forces a full window redraw, just in case. */
801     if (start.y == 0 && start.x == 0 && end.y == rows)
802         term_invalidate();
803
804     ldata = lineptr(start.y);
805     while (poslt(start, end)) {
806         if (start.x == cols && !erase_lattr)
807             ldata[start.x] &= ~LATTR_WRAPPED;
808         else
809             ldata[start.x] = erase_char;
810         if (incpos(start) && start.y < rows)
811             ldata = lineptr(start.y);
812     }
813 }
814
815 /*
816  * Insert or delete characters within the current line. n is +ve if
817  * insertion is desired, and -ve for deletion.
818  */
819 static void insch(int n)
820 {
821     int dir = (n < 0 ? -1 : +1);
822     int m;
823     pos cursplus;
824     unsigned long *ldata;
825
826     n = (n < 0 ? -n : n);
827     if (n > cols - curs.x)
828         n = cols - curs.x;
829     m = cols - curs.x - n;
830     cursplus.y = curs.y;
831     cursplus.x = curs.x + n;
832     check_selection(curs, cursplus);
833     ldata = lineptr(curs.y);
834     if (dir < 0) {
835         memmove(ldata + curs.x, ldata + curs.x + n, m * TSIZE);
836         while (n--)
837             ldata[curs.x + m++] = erase_char;
838     } else {
839         memmove(ldata + curs.x + n, ldata + curs.x, m * TSIZE);
840         while (n--)
841             ldata[curs.x + n] = erase_char;
842     }
843 }
844
845 /*
846  * Toggle terminal mode `mode' to state `state'. (`query' indicates
847  * whether the mode is a DEC private one or a normal one.)
848  */
849 static void toggle_mode(int mode, int query, int state)
850 {
851     long ticks;
852
853     if (query)
854         switch (mode) {
855           case 1:                      /* application cursor keys */
856             app_cursor_keys = state;
857             break;
858           case 2:                      /* VT52 mode */
859             vt52_mode = !state;
860             if (vt52_mode) {
861                 blink_is_real = FALSE;
862                 vt52_bold = FALSE;
863             } else {
864                 blink_is_real = cfg.blinktext;
865             }
866             break;
867           case 3:                      /* 80/132 columns */
868             deselect();
869             request_resize(state ? 132 : 80, rows);
870             reset_132 = state;
871             break;
872           case 5:                      /* reverse video */
873             /*
874              * Toggle reverse video. If we receive an OFF within the
875              * visual bell timeout period after an ON, we trigger an
876              * effective visual bell, so that ESC[?5hESC[?5l will
877              * always be an actually _visible_ visual bell.
878              */
879             ticks = GetTickCount();
880             if (rvideo && !state &&    /* we're turning it off */
881                 ticks < rvbell_timeout) {       /* and it's not long since it was turned on */
882                 in_vbell = TRUE;       /* we may clear rvideo but we set in_vbell */
883                 if (vbell_timeout < rvbell_timeout)     /* don't move vbell end forward */
884                     vbell_timeout = rvbell_timeout;     /* vbell end is at least then */
885             } else if (!rvideo && state) {
886                 /* This is an ON, so we notice the time and save it. */
887                 rvbell_timeout = ticks + VBELL_TIMEOUT;
888             }
889             rvideo = state;
890             seen_disp_event = TRUE;
891             if (state)
892                 term_update();
893             break;
894           case 6:                      /* DEC origin mode */
895             dec_om = state;
896             break;
897           case 7:                      /* auto wrap */
898             wrap = state;
899             break;
900           case 8:                      /* auto key repeat */
901             repeat_off = !state;
902             break;
903           case 10:                     /* set local edit mode */
904             term_editing = state;
905             ldisc_send(NULL, 0, 0);    /* cause ldisc to notice changes */
906             break;
907           case 25:                     /* enable/disable cursor */
908             compatibility2(OTHER, VT220);
909             cursor_on = state;
910             seen_disp_event = TRUE;
911             break;
912           case 47:                     /* alternate screen */
913             compatibility(OTHER);
914             deselect();
915             swap_screen(state);
916             disptop = 0;
917             break;
918           case 1000:                   /* xterm mouse 1 */
919             xterm_mouse = state ? 1 : 0;
920             set_raw_mouse_mode(state);
921             break;
922           case 1002:                   /* xterm mouse 2 */
923             xterm_mouse = state ? 2 : 0;
924             set_raw_mouse_mode(state);
925             break;
926     } else
927         switch (mode) {
928           case 4:                      /* set insert mode */
929             compatibility(VT102);
930             insert = state;
931             break;
932           case 12:                     /* set echo mode */
933             term_echoing = !state;
934             ldisc_send(NULL, 0, 0);    /* cause ldisc to notice changes */
935             break;
936           case 20:                     /* Return sends ... */
937             cr_lf_return = state;
938             break;
939           case 34:                     /* Make cursor BIG */
940             compatibility2(OTHER, VT220);
941             big_cursor = !state;
942         }
943 }
944
945 /*
946  * Process an OSC sequence: set window title or icon name.
947  */
948 static void do_osc(void)
949 {
950     if (osc_w) {
951         while (osc_strlen--)
952             wordness[(unsigned char) osc_string[osc_strlen]] = esc_args[0];
953     } else {
954         osc_string[osc_strlen] = '\0';
955         switch (esc_args[0]) {
956           case 0:
957           case 1:
958             set_icon(osc_string);
959             if (esc_args[0] == 1)
960                 break;
961             /* fall through: parameter 0 means set both */
962           case 2:
963           case 21:
964             set_title(osc_string);
965             break;
966         }
967     }
968 }
969
970 /*
971  * Remove everything currently in `inbuf' and stick it up on the
972  * in-memory display. There's a big state machine in here to
973  * process escape sequences...
974  */
975 void term_out(void)
976 {
977     int c, unget;
978     unsigned char localbuf[256], *chars;
979     int nchars = 0;
980
981     unget = -1;
982
983     while (nchars > 0 || bufchain_size(&inbuf) > 0) {
984         if (unget == -1) {
985             if (nchars == 0) {
986                 void *ret;
987                 bufchain_prefix(&inbuf, &ret, &nchars);
988                 if (nchars > sizeof(localbuf))
989                     nchars = sizeof(localbuf);
990                 memcpy(localbuf, ret, nchars);
991                 bufchain_consume(&inbuf, nchars);
992                 chars = localbuf;
993                 assert(chars != NULL);
994             }
995             c = *chars++;
996             nchars--;
997
998             /*
999              * Optionally log the session traffic to a file. Useful for
1000              * debugging and possibly also useful for actual logging.
1001              */
1002             if (cfg.logtype == LGTYP_DEBUG)
1003                 logtraffic((unsigned char) c, LGTYP_DEBUG);
1004         } else {
1005             c = unget;
1006             unget = -1;
1007         }
1008
1009         /* Note only VT220+ are 8-bit VT102 is seven bit, it shouldn't even
1010          * be able to display 8-bit characters, but I'll let that go 'cause
1011          * of i18n.
1012          */
1013
1014         /* First see about all those translations. */
1015         if (termstate == TOPLEVEL) {
1016             if (in_utf)
1017                 switch (utf_state) {
1018                   case 0:
1019                     if (c < 0x80) {
1020                         /* UTF-8 must be stateless so we ignore iso2022. */
1021                         if (unitab_ctrl[c] != 0xFF) 
1022                              c = unitab_ctrl[c];
1023                         else c = ((unsigned char)c) | ATTR_ASCII;
1024                         break;
1025                     } else if ((c & 0xe0) == 0xc0) {
1026                         utf_size = utf_state = 1;
1027                         utf_char = (c & 0x1f);
1028                     } else if ((c & 0xf0) == 0xe0) {
1029                         utf_size = utf_state = 2;
1030                         utf_char = (c & 0x0f);
1031                     } else if ((c & 0xf8) == 0xf0) {
1032                         utf_size = utf_state = 3;
1033                         utf_char = (c & 0x07);
1034                     } else if ((c & 0xfc) == 0xf8) {
1035                         utf_size = utf_state = 4;
1036                         utf_char = (c & 0x03);
1037                     } else if ((c & 0xfe) == 0xfc) {
1038                         utf_size = utf_state = 5;
1039                         utf_char = (c & 0x01);
1040                     } else {
1041                         c = UCSERR;
1042                         break;
1043                     }
1044                     continue;
1045                   case 1:
1046                   case 2:
1047                   case 3:
1048                   case 4:
1049                   case 5:
1050                     if ((c & 0xC0) != 0x80) {
1051                         unget = c;
1052                         c = UCSERR;
1053                         utf_state = 0;
1054                         break;
1055                     }
1056                     utf_char = (utf_char << 6) | (c & 0x3f);
1057                     if (--utf_state)
1058                         continue;
1059
1060                     c = utf_char;
1061
1062                     /* Is somebody trying to be evil! */
1063                     if (c < 0x80 ||
1064                         (c < 0x800 && utf_size >= 2) ||
1065                         (c < 0x10000 && utf_size >= 3) ||
1066                         (c < 0x200000 && utf_size >= 4) ||
1067                         (c < 0x4000000 && utf_size >= 5))
1068                         c = UCSERR;
1069
1070                     /* Unicode line separator and paragraph separator are CR-LF */
1071                     if (c == 0x2028 || c == 0x2029)
1072                         c = 0x85;
1073
1074                     /* High controls are probably a Baaad idea too. */
1075                     if (c < 0xA0)
1076                         c = 0xFFFD;
1077
1078                     /* The UTF-16 surrogates are not nice either. */
1079                     /*       The standard give the option of decoding these: 
1080                      *       I don't want to! */
1081                     if (c >= 0xD800 && c < 0xE000)
1082                         c = UCSERR;
1083
1084                     /* ISO 10646 characters now limited to UTF-16 range. */
1085                     if (c > 0x10FFFF)
1086                         c = UCSERR;
1087
1088                     /* This is currently a TagPhobic application.. */
1089                     if (c >= 0xE0000 && c <= 0xE007F)
1090                         continue;
1091
1092                     /* U+FEFF is best seen as a null. */
1093                     if (c == 0xFEFF)
1094                         continue;
1095                     /* But U+FFFE is an error. */
1096                     if (c == 0xFFFE || c == 0xFFFF)
1097                         c = UCSERR;
1098
1099                     /* Oops this is a 16bit implementation */
1100                     if (c >= 0x10000)
1101                         c = 0xFFFD;
1102                     break;
1103             }
1104             /* Are we in the nasty ACS mode? Note: no sco in utf mode. */
1105             else if(sco_acs && 
1106                     (c!='\033' && c!='\n' && c!='\r' && c!='\b'))
1107             {
1108                if (sco_acs == 2) c ^= 0x80;
1109                c |= ATTR_SCOACS;
1110             } else {
1111                 switch (cset_attr[cset]) {
1112                     /* 
1113                      * Linedraw characters are different from 'ESC ( B'
1114                      * only for a small range. For ones outside that
1115                      * range, make sure we use the same font as well as
1116                      * the same encoding.
1117                      */
1118                   case ATTR_LINEDRW:
1119                     if (unitab_ctrl[c] != 0xFF)
1120                         c = unitab_ctrl[c];
1121                     else
1122                         c = ((unsigned char) c) | ATTR_LINEDRW;
1123                     break;
1124
1125                   case ATTR_GBCHR:
1126                     /* If UK-ASCII, make the '#' a LineDraw Pound */
1127                     if (c == '#') {
1128                         c = '}' | ATTR_LINEDRW;
1129                         break;
1130                     }
1131                   /*FALLTHROUGH*/ case ATTR_ASCII:
1132                     if (unitab_ctrl[c] != 0xFF)
1133                         c = unitab_ctrl[c];
1134                     else
1135                         c = ((unsigned char) c) | ATTR_ASCII;
1136                     break;
1137                 case ATTR_SCOACS:
1138                     if (c>=' ') c = ((unsigned char)c) | ATTR_SCOACS;
1139                     break;
1140                 }
1141             }
1142         }
1143
1144         /* How about C1 controls ? */
1145         if ((c & -32) == 0x80 && termstate < DO_CTRLS && !vt52_mode &&
1146             has_compat(VT220)) {
1147             termstate = SEEN_ESC;
1148             esc_query = FALSE;
1149             c = '@' + (c & 0x1F);
1150         }
1151
1152         /* Or the GL control. */
1153         if (c == '\177' && termstate < DO_CTRLS && has_compat(OTHER)) {
1154             if (curs.x && !wrapnext)
1155                 curs.x--;
1156             wrapnext = FALSE;
1157             fix_cpos;
1158             *cpos = (' ' | curr_attr | ATTR_ASCII);
1159         } else
1160             /* Or normal C0 controls. */
1161         if ((c & -32) == 0 && termstate < DO_CTRLS) {
1162             switch (c) {
1163               case '\005':             /* terminal type query */
1164                 /* Strictly speaking this is VT100 but a VT100 defaults to
1165                  * no response. Other terminals respond at their option.
1166                  *
1167                  * Don't put a CR in the default string as this tends to
1168                  * upset some weird software.
1169                  *
1170                  * An xterm returns "xterm" (5 characters)
1171                  */
1172                 compatibility(ANSIMIN);
1173                 {
1174                     char abuf[256], *s, *d;
1175                     int state = 0;
1176                     for (s = cfg.answerback, d = abuf; *s; s++) {
1177                         if (state) {
1178                             if (*s >= 'a' && *s <= 'z')
1179                                 *d++ = (*s - ('a' - 1));
1180                             else if ((*s >= '@' && *s <= '_') ||
1181                                      *s == '?' || (*s & 0x80))
1182                                 *d++ = ('@' ^ *s);
1183                             else if (*s == '~')
1184                                 *d++ = '^';
1185                             state = 0;
1186                         } else if (*s == '^') {
1187                             state = 1;
1188                         } else
1189                             *d++ = *s;
1190                     }
1191                     lpage_send(CP_ACP, abuf, d - abuf, 0);
1192                 }
1193                 break;
1194               case '\007':
1195                 {
1196                     struct beeptime *newbeep;
1197                     long ticks;
1198
1199                     ticks = GetTickCount();
1200
1201                     if (!beep_overloaded) {
1202                         newbeep = smalloc(sizeof(struct beeptime));
1203                         newbeep->ticks = ticks;
1204                         newbeep->next = NULL;
1205                         if (!beephead)
1206                             beephead = newbeep;
1207                         else
1208                             beeptail->next = newbeep;
1209                         beeptail = newbeep;
1210                         nbeeps++;
1211                     }
1212
1213                     /*
1214                      * Throw out any beeps that happened more than
1215                      * t seconds ago.
1216                      */
1217                     while (beephead &&
1218                            beephead->ticks < ticks - cfg.bellovl_t) {
1219                         struct beeptime *tmp = beephead;
1220                         beephead = tmp->next;
1221                         sfree(tmp);
1222                         if (!beephead)
1223                             beeptail = NULL;
1224                         nbeeps--;
1225                     }
1226
1227                     if (cfg.bellovl && beep_overloaded &&
1228                         ticks - lastbeep >= cfg.bellovl_s) {
1229                         /*
1230                          * If we're currently overloaded and the
1231                          * last beep was more than s seconds ago,
1232                          * leave overload mode.
1233                          */
1234                         beep_overloaded = FALSE;
1235                     } else if (cfg.bellovl && !beep_overloaded &&
1236                                nbeeps >= cfg.bellovl_n) {
1237                         /*
1238                          * Now, if we have n or more beeps
1239                          * remaining in the queue, go into overload
1240                          * mode.
1241                          */
1242                         beep_overloaded = TRUE;
1243                     }
1244                     lastbeep = ticks;
1245
1246                     /*
1247                      * Perform an actual beep if we're not overloaded.
1248                      */
1249                     if (!cfg.bellovl || !beep_overloaded) {
1250                         beep(cfg.beep);
1251                         if (cfg.beep == BELL_VISUAL) {
1252                             in_vbell = TRUE;
1253                             vbell_timeout = ticks + VBELL_TIMEOUT;
1254                             term_update();
1255                         }
1256                     }
1257                     disptop = 0;
1258                 }
1259                 break;
1260               case '\b':
1261                 if (curs.x == 0 && (curs.y == 0 || wrap == 0));
1262                 else if (curs.x == 0 && curs.y > 0)
1263                     curs.x = cols - 1, curs.y--;
1264                 else if (wrapnext)
1265                     wrapnext = FALSE;
1266                 else
1267                     curs.x--;
1268                 fix_cpos;
1269                 seen_disp_event = TRUE;
1270                 break;
1271               case '\016':
1272                 compatibility(VT100);
1273                 cset = 1;
1274                 break;
1275               case '\017':
1276                 compatibility(VT100);
1277                 cset = 0;
1278                 break;
1279               case '\033':
1280                 if (vt52_mode)
1281                     termstate = VT52_ESC;
1282                 else {
1283                     compatibility(ANSIMIN);
1284                     termstate = SEEN_ESC;
1285                     esc_query = FALSE;
1286                 }
1287                 break;
1288               case '\r':
1289                 curs.x = 0;
1290                 wrapnext = FALSE;
1291                 fix_cpos;
1292                 seen_disp_event = TRUE;
1293                 paste_hold = 0;
1294                 logtraffic((unsigned char) c, LGTYP_ASCII);
1295                 break;
1296               case '\014':
1297                 if (has_compat(SCOANSI)) {
1298                     move(0, 0, 0);
1299                     erase_lots(FALSE, FALSE, TRUE);
1300                     disptop = 0;
1301                     wrapnext = FALSE;
1302                     seen_disp_event = 1;
1303                     break;
1304                 }
1305               case '\013':
1306                 compatibility(VT100);
1307               case '\n':
1308                 if (curs.y == marg_b)
1309                     scroll(marg_t, marg_b, 1, TRUE);
1310                 else if (curs.y < rows - 1)
1311                     curs.y++;
1312                 if (cfg.lfhascr)
1313                     curs.x = 0;
1314                 fix_cpos;
1315                 wrapnext = FALSE;
1316                 seen_disp_event = 1;
1317                 paste_hold = 0;
1318                 logtraffic((unsigned char) c, LGTYP_ASCII);
1319                 break;
1320               case '\t':
1321                 {
1322                     pos old_curs = curs;
1323                     unsigned long *ldata = lineptr(curs.y);
1324
1325                     do {
1326                         curs.x++;
1327                     } while (curs.x < cols - 1 && !tabs[curs.x]);
1328
1329                     if ((ldata[cols] & LATTR_MODE) != LATTR_NORM) {
1330                         if (curs.x >= cols / 2)
1331                             curs.x = cols / 2 - 1;
1332                     } else {
1333                         if (curs.x >= cols)
1334                             curs.x = cols - 1;
1335                     }
1336
1337                     fix_cpos;
1338                     check_selection(old_curs, curs);
1339                 }
1340                 seen_disp_event = TRUE;
1341                 break;
1342             }
1343         } else
1344             switch (termstate) {
1345               case TOPLEVEL:
1346                 /* Only graphic characters get this far, ctrls are stripped above */
1347                 if (wrapnext && wrap) {
1348                     cpos[1] |= LATTR_WRAPPED;
1349                     if (curs.y == marg_b)
1350                         scroll(marg_t, marg_b, 1, TRUE);
1351                     else if (curs.y < rows - 1)
1352                         curs.y++;
1353                     curs.x = 0;
1354                     fix_cpos;
1355                     wrapnext = FALSE;
1356                 }
1357                 if (insert)
1358                     insch(1);
1359                 if (selstate != NO_SELECTION) {
1360                     pos cursplus = curs;
1361                     incpos(cursplus);
1362                     check_selection(curs, cursplus);
1363                 }
1364                 if ((c & CSET_MASK) == ATTR_ASCII || (c & CSET_MASK) == 0)
1365                     logtraffic((unsigned char) c, LGTYP_ASCII);
1366                 {
1367                     extern int wcwidth(wchar_t ucs);
1368                     int width = 0;
1369                     if (DIRECT_CHAR(c))
1370                         width = 1;
1371                     if (!width)
1372                         width = wcwidth((wchar_t) c);
1373                     switch (width) {
1374                       case 2:
1375                         *cpos++ = c | curr_attr;
1376                         if (++curs.x == cols) {
1377                             *cpos |= LATTR_WRAPPED;
1378                             if (curs.y == marg_b)
1379                                 scroll(marg_t, marg_b, 1, TRUE);
1380                             else if (curs.y < rows - 1)
1381                                 curs.y++;
1382                             curs.x = 0;
1383                             fix_cpos;
1384                         }
1385                         *cpos++ = UCSWIDE | curr_attr;
1386                         break;
1387                       case 1:
1388                         *cpos++ = c | curr_attr;
1389                         break;
1390                       default:
1391                         continue;
1392                     }
1393                 }
1394                 curs.x++;
1395                 if (curs.x == cols) {
1396                     cpos--;
1397                     curs.x--;
1398                     wrapnext = TRUE;
1399                     if (wrap && vt52_mode) {
1400                         cpos[1] |= LATTR_WRAPPED;
1401                         if (curs.y == marg_b)
1402                             scroll(marg_t, marg_b, 1, TRUE);
1403                         else if (curs.y < rows - 1)
1404                             curs.y++;
1405                         curs.x = 0;
1406                         fix_cpos;
1407                         wrapnext = FALSE;
1408                     }
1409                 }
1410                 seen_disp_event = 1;
1411                 break;
1412
1413               case OSC_MAYBE_ST:
1414                 /*
1415                  * This state is virtually identical to SEEN_ESC, with the
1416                  * exception that we have an OSC sequence in the pipeline,
1417                  * and _if_ we see a backslash, we process it.
1418                  */
1419                 if (c == '\\') {
1420                     do_osc();
1421                     termstate = TOPLEVEL;
1422                     break;
1423                 }
1424                 /* else fall through */
1425               case SEEN_ESC:
1426                 if (c >= ' ' && c <= '/') {
1427                     if (esc_query)
1428                         esc_query = -1;
1429                     else
1430                         esc_query = c;
1431                     break;
1432                 }
1433                 termstate = TOPLEVEL;
1434                 switch (ANSI(c, esc_query)) {
1435                   case '[':            /* enter CSI mode */
1436                     termstate = SEEN_CSI;
1437                     esc_nargs = 1;
1438                     esc_args[0] = ARG_DEFAULT;
1439                     esc_query = FALSE;
1440                     break;
1441                   case ']':            /* xterm escape sequences */
1442                     /* Compatibility is nasty here, xterm, linux, decterm yuk! */
1443                     compatibility(OTHER);
1444                     termstate = SEEN_OSC;
1445                     esc_args[0] = 0;
1446                     break;
1447                   case '7':            /* save cursor */
1448                     compatibility(VT100);
1449                     save_cursor(TRUE);
1450                     break;
1451                   case '8':            /* restore cursor */
1452                     compatibility(VT100);
1453                     save_cursor(FALSE);
1454                     seen_disp_event = TRUE;
1455                     break;
1456                   case '=':
1457                     compatibility(VT100);
1458                     app_keypad_keys = TRUE;
1459                     break;
1460                   case '>':
1461                     compatibility(VT100);
1462                     app_keypad_keys = FALSE;
1463                     break;
1464                   case 'D':            /* exactly equivalent to LF */
1465                     compatibility(VT100);
1466                     if (curs.y == marg_b)
1467                         scroll(marg_t, marg_b, 1, TRUE);
1468                     else if (curs.y < rows - 1)
1469                         curs.y++;
1470                     fix_cpos;
1471                     wrapnext = FALSE;
1472                     seen_disp_event = TRUE;
1473                     break;
1474                   case 'E':            /* exactly equivalent to CR-LF */
1475                     compatibility(VT100);
1476                     curs.x = 0;
1477                     if (curs.y == marg_b)
1478                         scroll(marg_t, marg_b, 1, TRUE);
1479                     else if (curs.y < rows - 1)
1480                         curs.y++;
1481                     fix_cpos;
1482                     wrapnext = FALSE;
1483                     seen_disp_event = TRUE;
1484                     break;
1485                   case 'M':            /* reverse index - backwards LF */
1486                     compatibility(VT100);
1487                     if (curs.y == marg_t)
1488                         scroll(marg_t, marg_b, -1, TRUE);
1489                     else if (curs.y > 0)
1490                         curs.y--;
1491                     fix_cpos;
1492                     wrapnext = FALSE;
1493                     seen_disp_event = TRUE;
1494                     break;
1495                   case 'Z':            /* terminal type query */
1496                     compatibility(VT100);
1497                     ldisc_send(id_string, strlen(id_string), 0);
1498                     break;
1499                   case 'c':            /* restore power-on settings */
1500                     compatibility(VT100);
1501                     power_on();
1502                     if (reset_132) {
1503                         request_resize(80, rows);
1504                         reset_132 = 0;
1505                     }
1506                     fix_cpos;
1507                     disptop = 0;
1508                     seen_disp_event = TRUE;
1509                     break;
1510                   case 'H':            /* set a tab */
1511                     compatibility(VT100);
1512                     tabs[curs.x] = TRUE;
1513                     break;
1514
1515                   case ANSI('8', '#'):  /* ESC # 8 fills screen with Es :-) */
1516                     compatibility(VT100);
1517                     {
1518                         unsigned long *ldata;
1519                         int i, j;
1520                         pos scrtop, scrbot;
1521
1522                         for (i = 0; i < rows; i++) {
1523                             ldata = lineptr(i);
1524                             for (j = 0; j < cols; j++)
1525                                 ldata[j] = ATTR_DEFAULT | 'E';
1526                             ldata[cols] = 0;
1527                         }
1528                         disptop = 0;
1529                         seen_disp_event = TRUE;
1530                         scrtop.x = scrtop.y = 0;
1531                         scrbot.x = 0;
1532                         scrbot.y = rows;
1533                         check_selection(scrtop, scrbot);
1534                     }
1535                     break;
1536
1537                   case ANSI('3', '#'):
1538                   case ANSI('4', '#'):
1539                   case ANSI('5', '#'):
1540                   case ANSI('6', '#'):
1541                     compatibility(VT100);
1542                     {
1543                         unsigned long nlattr;
1544                         unsigned long *ldata;
1545                         switch (ANSI(c, esc_query)) {
1546                           case ANSI('3', '#'):
1547                             nlattr = LATTR_TOP;
1548                             break;
1549                           case ANSI('4', '#'):
1550                             nlattr = LATTR_BOT;
1551                             break;
1552                           case ANSI('5', '#'):
1553                             nlattr = LATTR_NORM;
1554                             break;
1555                           default: /* spiritually case ANSI('6', '#'): */
1556                             nlattr = LATTR_WIDE;
1557                             break;
1558                         }
1559                         ldata = lineptr(curs.y);
1560                         ldata[cols] &= ~LATTR_MODE;
1561                         ldata[cols] |= nlattr;
1562                     }
1563                     break;
1564
1565                   case ANSI('A', '('):
1566                     compatibility(VT100);
1567                     cset_attr[0] = ATTR_GBCHR;
1568                     break;
1569                   case ANSI('B', '('):
1570                     compatibility(VT100);
1571                     cset_attr[0] = ATTR_ASCII;
1572                     break;
1573                   case ANSI('0', '('):
1574                     compatibility(VT100);
1575                     cset_attr[0] = ATTR_LINEDRW;
1576                     break;
1577                   case ANSI('U', '('): 
1578                     compatibility(OTHER);
1579                     cset_attr[0] = ATTR_SCOACS; 
1580                     break;
1581
1582                   case ANSI('A', ')'):
1583                     compatibility(VT100);
1584                     cset_attr[1] = ATTR_GBCHR;
1585                     break;
1586                   case ANSI('B', ')'):
1587                     compatibility(VT100);
1588                     cset_attr[1] = ATTR_ASCII;
1589                     break;
1590                   case ANSI('0', ')'):
1591                     compatibility(VT100);
1592                     cset_attr[1] = ATTR_LINEDRW;
1593                     break;
1594                   case ANSI('U', ')'): 
1595                     compatibility(OTHER);
1596                     cset_attr[1] = ATTR_SCOACS; 
1597                     break;
1598
1599                   case ANSI('8', '%'):  /* Old Linux code */
1600                   case ANSI('G', '%'):
1601                     compatibility(OTHER);
1602                     utf = 1;
1603                     break;
1604                   case ANSI('@', '%'):
1605                     compatibility(OTHER);
1606                     utf = 0;
1607                     break;
1608                 }
1609                 break;
1610               case SEEN_CSI:
1611                 termstate = TOPLEVEL;  /* default */
1612                 if (isdigit(c)) {
1613                     if (esc_nargs <= ARGS_MAX) {
1614                         if (esc_args[esc_nargs - 1] == ARG_DEFAULT)
1615                             esc_args[esc_nargs - 1] = 0;
1616                         esc_args[esc_nargs - 1] =
1617                             10 * esc_args[esc_nargs - 1] + c - '0';
1618                     }
1619                     termstate = SEEN_CSI;
1620                 } else if (c == ';') {
1621                     if (++esc_nargs <= ARGS_MAX)
1622                         esc_args[esc_nargs - 1] = ARG_DEFAULT;
1623                     termstate = SEEN_CSI;
1624                 } else if (c < '@') {
1625                     if (esc_query)
1626                         esc_query = -1;
1627                     else if (c == '?')
1628                         esc_query = TRUE;
1629                     else
1630                         esc_query = c;
1631                     termstate = SEEN_CSI;
1632                 } else
1633                     switch (ANSI(c, esc_query)) {
1634                       case 'A':       /* move up N lines */
1635                         move(curs.x, curs.y - def(esc_args[0], 1), 1);
1636                         seen_disp_event = TRUE;
1637                         break;
1638                       case 'e':       /* move down N lines */
1639                         compatibility(ANSI);
1640                         /* FALLTHROUGH */
1641                       case 'B':
1642                         move(curs.x, curs.y + def(esc_args[0], 1), 1);
1643                         seen_disp_event = TRUE;
1644                         break;
1645                       case ANSI('c', '>'):      /* report xterm version */
1646                         compatibility(OTHER);
1647                         /* this reports xterm version 136 so that VIM can
1648                            use the drag messages from the mouse reporting */
1649                         ldisc_send("\033[>0;136;0c", 11, 0);
1650                         break;
1651                       case 'a':       /* move right N cols */
1652                         compatibility(ANSI);
1653                         /* FALLTHROUGH */
1654                       case 'C':
1655                         move(curs.x + def(esc_args[0], 1), curs.y, 1);
1656                         seen_disp_event = TRUE;
1657                         break;
1658                       case 'D':       /* move left N cols */
1659                         move(curs.x - def(esc_args[0], 1), curs.y, 1);
1660                         seen_disp_event = TRUE;
1661                         break;
1662                       case 'E':       /* move down N lines and CR */
1663                         compatibility(ANSI);
1664                         move(0, curs.y + def(esc_args[0], 1), 1);
1665                         seen_disp_event = TRUE;
1666                         break;
1667                       case 'F':       /* move up N lines and CR */
1668                         compatibility(ANSI);
1669                         move(0, curs.y - def(esc_args[0], 1), 1);
1670                         seen_disp_event = TRUE;
1671                         break;
1672                       case 'G':
1673                       case '`':       /* set horizontal posn */
1674                         compatibility(ANSI);
1675                         move(def(esc_args[0], 1) - 1, curs.y, 0);
1676                         seen_disp_event = TRUE;
1677                         break;
1678                       case 'd':       /* set vertical posn */
1679                         compatibility(ANSI);
1680                         move(curs.x,
1681                              (dec_om ? marg_t : 0) + def(esc_args[0],
1682                                                          1) - 1,
1683                              (dec_om ? 2 : 0));
1684                         seen_disp_event = TRUE;
1685                         break;
1686                       case 'H':
1687                       case 'f':       /* set horz and vert posns at once */
1688                         if (esc_nargs < 2)
1689                             esc_args[1] = ARG_DEFAULT;
1690                         move(def(esc_args[1], 1) - 1,
1691                              (dec_om ? marg_t : 0) + def(esc_args[0],
1692                                                          1) - 1,
1693                              (dec_om ? 2 : 0));
1694                         seen_disp_event = TRUE;
1695                         break;
1696                       case 'J':       /* erase screen or parts of it */
1697                         {
1698                             unsigned int i = def(esc_args[0], 0) + 1;
1699                             if (i > 3)
1700                                 i = 0;
1701                             erase_lots(FALSE, !!(i & 2), !!(i & 1));
1702                         }
1703                         disptop = 0;
1704                         seen_disp_event = TRUE;
1705                         break;
1706                       case 'K':       /* erase line or parts of it */
1707                         {
1708                             unsigned int i = def(esc_args[0], 0) + 1;
1709                             if (i > 3)
1710                                 i = 0;
1711                             erase_lots(TRUE, !!(i & 2), !!(i & 1));
1712                         }
1713                         seen_disp_event = TRUE;
1714                         break;
1715                       case 'L':       /* insert lines */
1716                         compatibility(VT102);
1717                         if (curs.y <= marg_b)
1718                             scroll(curs.y, marg_b, -def(esc_args[0], 1),
1719                                    FALSE);
1720                         fix_cpos;
1721                         seen_disp_event = TRUE;
1722                         break;
1723                       case 'M':       /* delete lines */
1724                         compatibility(VT102);
1725                         if (curs.y <= marg_b)
1726                             scroll(curs.y, marg_b, def(esc_args[0], 1),
1727                                    TRUE);
1728                         fix_cpos;
1729                         seen_disp_event = TRUE;
1730                         break;
1731                       case '@':       /* insert chars */
1732                         /* XXX VTTEST says this is vt220, vt510 manual says vt102 */
1733                         compatibility(VT102);
1734                         insch(def(esc_args[0], 1));
1735                         seen_disp_event = TRUE;
1736                         break;
1737                       case 'P':       /* delete chars */
1738                         compatibility(VT102);
1739                         insch(-def(esc_args[0], 1));
1740                         seen_disp_event = TRUE;
1741                         break;
1742                       case 'c':       /* terminal type query */
1743                         compatibility(VT100);
1744                         /* This is the response for a VT102 */
1745                         ldisc_send(id_string, strlen(id_string), 0);
1746                         break;
1747                       case 'n':       /* cursor position query */
1748                         if (esc_args[0] == 6) {
1749                             char buf[32];
1750                             sprintf(buf, "\033[%d;%dR", curs.y + 1,
1751                                     curs.x + 1);
1752                             ldisc_send(buf, strlen(buf), 0);
1753                         } else if (esc_args[0] == 5) {
1754                             ldisc_send("\033[0n", 4, 0);
1755                         }
1756                         break;
1757                       case 'h':       /* toggle modes to high */
1758                       case ANSI_QUE('h'):
1759                         compatibility(VT100);
1760                         {
1761                             int i;
1762                             for (i = 0; i < esc_nargs; i++)
1763                                 toggle_mode(esc_args[i], esc_query, TRUE);
1764                         }
1765                         break;
1766                       case 'l':       /* toggle modes to low */
1767                       case ANSI_QUE('l'):
1768                         compatibility(VT100);
1769                         {
1770                             int i;
1771                             for (i = 0; i < esc_nargs; i++)
1772                                 toggle_mode(esc_args[i], esc_query, FALSE);
1773                         }
1774                         break;
1775                       case 'g':       /* clear tabs */
1776                         compatibility(VT100);
1777                         if (esc_nargs == 1) {
1778                             if (esc_args[0] == 0) {
1779                                 tabs[curs.x] = FALSE;
1780                             } else if (esc_args[0] == 3) {
1781                                 int i;
1782                                 for (i = 0; i < cols; i++)
1783                                     tabs[i] = FALSE;
1784                             }
1785                         }
1786                         break;
1787                       case 'r':       /* set scroll margins */
1788                         compatibility(VT100);
1789                         if (esc_nargs <= 2) {
1790                             int top, bot;
1791                             top = def(esc_args[0], 1) - 1;
1792                             bot = (esc_nargs <= 1
1793                                    || esc_args[1] ==
1794                                    0 ? rows : def(esc_args[1], rows)) - 1;
1795                             if (bot >= rows)
1796                                 bot = rows - 1;
1797                             /* VTTEST Bug 9 - if region is less than 2 lines
1798                              * don't change region.
1799                              */
1800                             if (bot - top > 0) {
1801                                 marg_t = top;
1802                                 marg_b = bot;
1803                                 curs.x = 0;
1804                                 /*
1805                                  * I used to think the cursor should be
1806                                  * placed at the top of the newly marginned
1807                                  * area. Apparently not: VMS TPU falls over
1808                                  * if so.
1809                                  *
1810                                  * Well actually it should for Origin mode - RDB
1811                                  */
1812                                 curs.y = (dec_om ? marg_t : 0);
1813                                 fix_cpos;
1814                                 seen_disp_event = TRUE;
1815                             }
1816                         }
1817                         break;
1818                       case 'm':       /* set graphics rendition */
1819                         {
1820                             /* 
1821                              * A VT100 without the AVO only had one attribute, either
1822                              * underline or reverse video depending on the cursor type,
1823                              * this was selected by CSI 7m.
1824                              *
1825                              * case 2:
1826                              *  This is sometimes DIM, eg on the GIGI and Linux
1827                              * case 8:
1828                              *  This is sometimes INVIS various ANSI.
1829                              * case 21:
1830                              *  This like 22 disables BOLD, DIM and INVIS
1831                              *
1832                              * The ANSI colours appear on any terminal that has colour
1833                              * (obviously) but the interaction between sgr0 and the
1834                              * colours varies but is usually related to the background
1835                              * colour erase item.
1836                              * The interaction between colour attributes and the mono
1837                              * ones is also very implementation dependent.
1838                              *
1839                              * The 39 and 49 attributes are likely to be unimplemented.
1840                              */
1841                             int i;
1842                             for (i = 0; i < esc_nargs; i++) {
1843                                 switch (def(esc_args[i], 0)) {
1844                                   case 0:       /* restore defaults */
1845                                     curr_attr = ATTR_DEFAULT;
1846                                     break;
1847                                   case 1:       /* enable bold */
1848                                     compatibility(VT100AVO);
1849                                     curr_attr |= ATTR_BOLD;
1850                                     break;
1851                                   case 21:      /* (enable double underline) */
1852                                     compatibility(OTHER);
1853                                   case 4:       /* enable underline */
1854                                     compatibility(VT100AVO);
1855                                     curr_attr |= ATTR_UNDER;
1856                                     break;
1857                                   case 5:       /* enable blink */
1858                                     compatibility(VT100AVO);
1859                                     curr_attr |= ATTR_BLINK;
1860                                     break;
1861                                   case 7:       /* enable reverse video */
1862                                     curr_attr |= ATTR_REVERSE;
1863                                     break;
1864                                   case 10:      /* SCO acs off */
1865                                     compatibility(SCOANSI);
1866                                     sco_acs = 0; break;
1867                                   case 11:      /* SCO acs on */
1868                                     compatibility(SCOANSI);
1869                                     sco_acs = 1; break;
1870                                   case 12:      /* SCO acs on flipped */
1871                                     compatibility(SCOANSI);
1872                                     sco_acs = 2; break;
1873                                   case 22:      /* disable bold */
1874                                     compatibility2(OTHER, VT220);
1875                                     curr_attr &= ~ATTR_BOLD;
1876                                     break;
1877                                   case 24:      /* disable underline */
1878                                     compatibility2(OTHER, VT220);
1879                                     curr_attr &= ~ATTR_UNDER;
1880                                     break;
1881                                   case 25:      /* disable blink */
1882                                     compatibility2(OTHER, VT220);
1883                                     curr_attr &= ~ATTR_BLINK;
1884                                     break;
1885                                   case 27:      /* disable reverse video */
1886                                     compatibility2(OTHER, VT220);
1887                                     curr_attr &= ~ATTR_REVERSE;
1888                                     break;
1889                                   case 30:
1890                                   case 31:
1891                                   case 32:
1892                                   case 33:
1893                                   case 34:
1894                                   case 35:
1895                                   case 36:
1896                                   case 37:
1897                                     /* foreground */
1898                                     curr_attr &= ~ATTR_FGMASK;
1899                                     curr_attr |=
1900                                         (esc_args[i] - 30) << ATTR_FGSHIFT;
1901                                     break;
1902                                   case 39:      /* default-foreground */
1903                                     curr_attr &= ~ATTR_FGMASK;
1904                                     curr_attr |= ATTR_DEFFG;
1905                                     break;
1906                                   case 40:
1907                                   case 41:
1908                                   case 42:
1909                                   case 43:
1910                                   case 44:
1911                                   case 45:
1912                                   case 46:
1913                                   case 47:
1914                                     /* background */
1915                                     curr_attr &= ~ATTR_BGMASK;
1916                                     curr_attr |=
1917                                         (esc_args[i] - 40) << ATTR_BGSHIFT;
1918                                     break;
1919                                   case 49:      /* default-background */
1920                                     curr_attr &= ~ATTR_BGMASK;
1921                                     curr_attr |= ATTR_DEFBG;
1922                                     break;
1923                                 }
1924                             }
1925                             if (use_bce)
1926                                 erase_char = (' ' | ATTR_ASCII |
1927                                              (curr_attr & 
1928                                               (ATTR_FGMASK | ATTR_BGMASK)));
1929                         }
1930                         break;
1931                       case 's':       /* save cursor */
1932                         save_cursor(TRUE);
1933                         break;
1934                       case 'u':       /* restore cursor */
1935                         save_cursor(FALSE);
1936                         seen_disp_event = TRUE;
1937                         break;
1938                       case 't':       /* set page size - ie window height */
1939                         /*
1940                          * VT340/VT420 sequence DECSLPP, DEC only allows values
1941                          *  24/25/36/48/72/144 other emulators (eg dtterm) use
1942                          * illegal values (eg first arg 1..9) for window changing 
1943                          * and reports.
1944                          */
1945                         compatibility(VT340TEXT);
1946                         if (esc_nargs <= 1
1947                             && (esc_args[0] < 1 || esc_args[0] >= 24)) {
1948                             request_resize(cols, def(esc_args[0], 24));
1949                             deselect();
1950                         }
1951                         break;
1952                       case 'S':
1953                         compatibility(SCOANSI);
1954                         scroll(marg_t, marg_b, def(esc_args[0], 1), TRUE);
1955                         fix_cpos;
1956                         wrapnext = FALSE;
1957                         seen_disp_event = TRUE;
1958                         break;
1959                       case 'T':
1960                         compatibility(SCOANSI);
1961                         scroll(marg_t, marg_b, -def(esc_args[0], 1), TRUE);
1962                         fix_cpos;
1963                         wrapnext = FALSE;
1964                         seen_disp_event = TRUE;
1965                         break;
1966                       case ANSI('|', '*'):
1967                         /* VT420 sequence DECSNLS
1968                          * Set number of lines on screen
1969                          * VT420 uses VGA like hardware and can support any size in
1970                          * reasonable range (24..49 AIUI) with no default specified.
1971                          */
1972                         compatibility(VT420);
1973                         if (esc_nargs == 1 && esc_args[0] > 0) {
1974                             request_resize(cols, def(esc_args[0], cfg.height));
1975                             deselect();
1976                         }
1977                         break;
1978                       case ANSI('|', '$'):
1979                         /* VT340/VT420 sequence DECSCPP
1980                          * Set number of columns per page
1981                          * Docs imply range is only 80 or 132, but I'll allow any.
1982                          */
1983                         compatibility(VT340TEXT);
1984                         if (esc_nargs <= 1) {
1985                             request_resize(def(esc_args[0], cfg.width), rows);
1986                             deselect();
1987                         }
1988                         break;
1989                       case 'X':       /* write N spaces w/o moving cursor */
1990                         /* XXX VTTEST says this is vt220, vt510 manual says vt100 */
1991                         compatibility(ANSIMIN);
1992                         {
1993                             int n = def(esc_args[0], 1);
1994                             pos cursplus;
1995                             unsigned long *p = cpos;
1996                             if (n > cols - curs.x)
1997                                 n = cols - curs.x;
1998                             cursplus = curs;
1999                             cursplus.x += n;
2000                             check_selection(curs, cursplus);
2001                             while (n--)
2002                                 *p++ = erase_char;
2003                             seen_disp_event = TRUE;
2004                         }
2005                         break;
2006                       case 'x':       /* report terminal characteristics */
2007                         compatibility(VT100);
2008                         {
2009                             char buf[32];
2010                             int i = def(esc_args[0], 0);
2011                             if (i == 0 || i == 1) {
2012                                 strcpy(buf, "\033[2;1;1;112;112;1;0x");
2013                                 buf[2] += i;
2014                                 ldisc_send(buf, 20, 0);
2015                             }
2016                         }
2017                         break;
2018                       case 'Z':         /* BackTab for xterm */
2019                         compatibility(OTHER);
2020                         {
2021                             int i = def(esc_args[0], 1);
2022                             pos old_curs = curs;
2023
2024                             for(;i>0 && curs.x>0; i--) {
2025                                 do {
2026                                     curs.x--;
2027                                 } while (curs.x >0 && !tabs[curs.x]);
2028                             }
2029                             fix_cpos;
2030                             check_selection(old_curs, curs);
2031                         }
2032                         break;
2033                       case ANSI('L', '='):
2034                         compatibility(OTHER);
2035                         use_bce = (esc_args[0] <= 0);
2036                         erase_char = ERASE_CHAR;
2037                         if (use_bce)
2038                             erase_char = (' ' | ATTR_ASCII |
2039                                          (curr_attr & 
2040                                           (ATTR_FGMASK | ATTR_BGMASK)));
2041                         break;
2042                       case ANSI('E', '='):
2043                         compatibility(OTHER);
2044                         blink_is_real = (esc_args[0] >= 1);
2045                         break;
2046                       case ANSI('p', '"'):
2047                         /* Allow the host to make this emulator a 'perfect' VT102.
2048                          * This first appeared in the VT220, but we do need to get 
2049                          * back to PuTTY mode so I won't check it.
2050                          *
2051                          * The arg in 40..42,50 are a PuTTY extension.
2052                          * The 2nd arg, 8bit vs 7bit is not checked.
2053                          *
2054                          * Setting VT102 mode should also change the Fkeys to
2055                          * generate PF* codes as a real VT102 has no Fkeys.
2056                          * The VT220 does this, F11..F13 become ESC,BS,LF other Fkeys
2057                          * send nothing.
2058                          *
2059                          * Note ESC c will NOT change this!
2060                          */
2061
2062                         switch (esc_args[0]) {
2063                           case 61:
2064                             compatibility_level &= ~TM_VTXXX;
2065                             compatibility_level |= TM_VT102;
2066                             break;
2067                           case 62:
2068                             compatibility_level &= ~TM_VTXXX;
2069                             compatibility_level |= TM_VT220;
2070                             break;
2071
2072                           default:
2073                             if (esc_args[0] > 60 && esc_args[0] < 70)
2074                                 compatibility_level |= TM_VTXXX;
2075                             break;
2076
2077                           case 40:
2078                             compatibility_level &= TM_VTXXX;
2079                             break;
2080                           case 41:
2081                             compatibility_level = TM_PUTTY;
2082                             break;
2083                           case 42:
2084                             compatibility_level = TM_SCOANSI;
2085                             break;
2086
2087                           case ARG_DEFAULT:
2088                             compatibility_level = TM_PUTTY;
2089                             break;
2090                           case 50:
2091                             break;
2092                         }
2093
2094                         /* Change the response to CSI c */
2095                         if (esc_args[0] == 50) {
2096                             int i;
2097                             char lbuf[64];
2098                             strcpy(id_string, "\033[?");
2099                             for (i = 1; i < esc_nargs; i++) {
2100                                 if (i != 1)
2101                                     strcat(id_string, ";");
2102                                 sprintf(lbuf, "%d", esc_args[i]);
2103                                 strcat(id_string, lbuf);
2104                             }
2105                             strcat(id_string, "c");
2106                         }
2107 #if 0
2108                         /* Is this a good idea ? 
2109                          * Well we should do a soft reset at this point ...
2110                          */
2111                         if (!has_compat(VT420) && has_compat(VT100)) {
2112                             if (reset_132)
2113                                 request_resize(132, 24);
2114                             else
2115                                 request_resize(80, 24);
2116                         }
2117 #endif
2118                         break;
2119                     }
2120                 break;
2121               case SEEN_OSC:
2122                 osc_w = FALSE;
2123                 switch (c) {
2124                   case 'P':            /* Linux palette sequence */
2125                     termstate = SEEN_OSC_P;
2126                     osc_strlen = 0;
2127                     break;
2128                   case 'R':            /* Linux palette reset */
2129                     palette_reset();
2130                     term_invalidate();
2131                     termstate = TOPLEVEL;
2132                     break;
2133                   case 'W':            /* word-set */
2134                     termstate = SEEN_OSC_W;
2135                     osc_w = TRUE;
2136                     break;
2137                   case '0':
2138                   case '1':
2139                   case '2':
2140                   case '3':
2141                   case '4':
2142                   case '5':
2143                   case '6':
2144                   case '7':
2145                   case '8':
2146                   case '9':
2147                     esc_args[0] = 10 * esc_args[0] + c - '0';
2148                     break;
2149                   case 'L':
2150                     /*
2151                      * Grotty hack to support xterm and DECterm title
2152                      * sequences concurrently.
2153                      */
2154                     if (esc_args[0] == 2) {
2155                         esc_args[0] = 1;
2156                         break;
2157                     }
2158                     /* else fall through */
2159                   default:
2160                     termstate = OSC_STRING;
2161                     osc_strlen = 0;
2162                 }
2163                 break;
2164               case OSC_STRING:
2165                 /*
2166                  * This OSC stuff is EVIL. It takes just one character to get into
2167                  * sysline mode and it's not initially obvious how to get out.
2168                  * So I've added CR and LF as string aborts.
2169                  * This shouldn't effect compatibility as I believe embedded 
2170                  * control characters are supposed to be interpreted (maybe?) 
2171                  * and they don't display anything useful anyway.
2172                  *
2173                  * -- RDB
2174                  */
2175                 if (c == '\n' || c == '\r') {
2176                     termstate = TOPLEVEL;
2177                 } else if (c == 0234 || c == '\007') {
2178                     /*
2179                      * These characters terminate the string; ST and BEL
2180                      * terminate the sequence and trigger instant
2181                      * processing of it, whereas ESC goes back to SEEN_ESC
2182                      * mode unless it is followed by \, in which case it is
2183                      * synonymous with ST in the first place.
2184                      */
2185                     do_osc();
2186                     termstate = TOPLEVEL;
2187                 } else if (c == '\033')
2188                     termstate = OSC_MAYBE_ST;
2189                 else if (osc_strlen < OSC_STR_MAX)
2190                     osc_string[osc_strlen++] = c;
2191                 break;
2192               case SEEN_OSC_P:
2193                 {
2194                     int max = (osc_strlen == 0 ? 21 : 16);
2195                     int val;
2196                     if (c >= '0' && c <= '9')
2197                         val = c - '0';
2198                     else if (c >= 'A' && c <= 'A' + max - 10)
2199                         val = c - 'A' + 10;
2200                     else if (c >= 'a' && c <= 'a' + max - 10)
2201                         val = c - 'a' + 10;
2202                     else {
2203                         termstate = TOPLEVEL;
2204                         break;
2205                     }
2206                     osc_string[osc_strlen++] = val;
2207                     if (osc_strlen >= 7) {
2208                         palette_set(osc_string[0],
2209                                     osc_string[1] * 16 + osc_string[2],
2210                                     osc_string[3] * 16 + osc_string[4],
2211                                     osc_string[5] * 16 + osc_string[6]);
2212                         term_invalidate();
2213                         termstate = TOPLEVEL;
2214                     }
2215                 }
2216                 break;
2217               case SEEN_OSC_W:
2218                 switch (c) {
2219                   case '0':
2220                   case '1':
2221                   case '2':
2222                   case '3':
2223                   case '4':
2224                   case '5':
2225                   case '6':
2226                   case '7':
2227                   case '8':
2228                   case '9':
2229                     esc_args[0] = 10 * esc_args[0] + c - '0';
2230                     break;
2231                   default:
2232                     termstate = OSC_STRING;
2233                     osc_strlen = 0;
2234                 }
2235                 break;
2236               case VT52_ESC:
2237                 termstate = TOPLEVEL;
2238                 seen_disp_event = TRUE;
2239                 switch (c) {
2240                   case 'A':
2241                     move(curs.x, curs.y - 1, 1);
2242                     break;
2243                   case 'B':
2244                     move(curs.x, curs.y + 1, 1);
2245                     break;
2246                   case 'C':
2247                     move(curs.x + 1, curs.y, 1);
2248                     break;
2249                   case 'D':
2250                     move(curs.x - 1, curs.y, 1);
2251                     break;
2252                     /*
2253                      * From the VT100 Manual
2254                      * NOTE: The special graphics characters in the VT100
2255                      *       are different from those in the VT52
2256                      *
2257                      * From VT102 manual:
2258                      *       137 _  Blank             - Same
2259                      *       140 `  Reserved          - Humm.
2260                      *       141 a  Solid rectangle   - Similar
2261                      *       142 b  1/                - Top half of fraction for the
2262                      *       143 c  3/                - subscript numbers below.
2263                      *       144 d  5/
2264                      *       145 e  7/
2265                      *       146 f  Degrees           - Same
2266                      *       147 g  Plus or minus     - Same
2267                      *       150 h  Right arrow
2268                      *       151 i  Ellipsis (dots)
2269                      *       152 j  Divide by
2270                      *       153 k  Down arrow
2271                      *       154 l  Bar at scan 0
2272                      *       155 m  Bar at scan 1
2273                      *       156 n  Bar at scan 2
2274                      *       157 o  Bar at scan 3     - Similar
2275                      *       160 p  Bar at scan 4     - Similar
2276                      *       161 q  Bar at scan 5     - Similar
2277                      *       162 r  Bar at scan 6     - Same
2278                      *       163 s  Bar at scan 7     - Similar
2279                      *       164 t  Subscript 0
2280                      *       165 u  Subscript 1
2281                      *       166 v  Subscript 2
2282                      *       167 w  Subscript 3
2283                      *       170 x  Subscript 4
2284                      *       171 y  Subscript 5
2285                      *       172 z  Subscript 6
2286                      *       173 {  Subscript 7
2287                      *       174 |  Subscript 8
2288                      *       175 }  Subscript 9
2289                      *       176 ~  Paragraph
2290                      *
2291                      */
2292                   case 'F':
2293                     cset_attr[cset = 0] = ATTR_LINEDRW;
2294                     break;
2295                   case 'G':
2296                     cset_attr[cset = 0] = ATTR_ASCII;
2297                     break;
2298                   case 'H':
2299                     move(0, 0, 0);
2300                     break;
2301                   case 'I':
2302                     if (curs.y == 0)
2303                         scroll(0, rows - 1, -1, TRUE);
2304                     else if (curs.y > 0)
2305                         curs.y--;
2306                     fix_cpos;
2307                     wrapnext = FALSE;
2308                     break;
2309                   case 'J':
2310                     erase_lots(FALSE, FALSE, TRUE);
2311                     disptop = 0;
2312                     break;
2313                   case 'K':
2314                     erase_lots(TRUE, FALSE, TRUE);
2315                     break;
2316 #if 0
2317                   case 'V':
2318                     /* XXX Print cursor line */
2319                     break;
2320                   case 'W':
2321                     /* XXX Start controller mode */
2322                     break;
2323                   case 'X':
2324                     /* XXX Stop controller mode */
2325                     break;
2326 #endif
2327                   case 'Y':
2328                     termstate = VT52_Y1;
2329                     break;
2330                   case 'Z':
2331                     ldisc_send("\033/Z", 3, 0);
2332                     break;
2333                   case '=':
2334                     app_keypad_keys = TRUE;
2335                     break;
2336                   case '>':
2337                     app_keypad_keys = FALSE;
2338                     break;
2339                   case '<':
2340                     /* XXX This should switch to VT100 mode not current or default
2341                      *     VT mode. But this will only have effect in a VT220+
2342                      *     emulation.
2343                      */
2344                     vt52_mode = FALSE;
2345                     blink_is_real = cfg.blinktext;
2346                     break;
2347 #if 0
2348                   case '^':
2349                     /* XXX Enter auto print mode */
2350                     break;
2351                   case '_':
2352                     /* XXX Exit auto print mode */
2353                     break;
2354                   case ']':
2355                     /* XXX Print screen */
2356                     break;
2357 #endif
2358
2359 #ifdef VT52_PLUS
2360                   case 'E':
2361                     /* compatibility(ATARI) */
2362                     move(0, 0, 0);
2363                     erase_lots(FALSE, FALSE, TRUE);
2364                     disptop = 0;
2365                     break;
2366                   case 'L':
2367                     /* compatibility(ATARI) */
2368                     if (curs.y <= marg_b)
2369                         scroll(curs.y, marg_b, -1, FALSE);
2370                     break;
2371                   case 'M':
2372                     /* compatibility(ATARI) */
2373                     if (curs.y <= marg_b)
2374                         scroll(curs.y, marg_b, 1, TRUE);
2375                     break;
2376                   case 'b':
2377                     /* compatibility(ATARI) */
2378                     termstate = VT52_FG;
2379                     break;
2380                   case 'c':
2381                     /* compatibility(ATARI) */
2382                     termstate = VT52_BG;
2383                     break;
2384                   case 'd':
2385                     /* compatibility(ATARI) */
2386                     erase_lots(FALSE, TRUE, FALSE);
2387                     disptop = 0;
2388                     break;
2389                   case 'e':
2390                     /* compatibility(ATARI) */
2391                     cursor_on = TRUE;
2392                     break;
2393                   case 'f':
2394                     /* compatibility(ATARI) */
2395                     cursor_on = FALSE;
2396                     break;
2397                     /* case 'j': Save cursor position - broken on ST */
2398                     /* case 'k': Restore cursor position */
2399                   case 'l':
2400                     /* compatibility(ATARI) */
2401                     erase_lots(TRUE, TRUE, TRUE);
2402                     curs.x = 0;
2403                     wrapnext = FALSE;
2404                     fix_cpos;
2405                     break;
2406                   case 'o':
2407                     /* compatibility(ATARI) */
2408                     erase_lots(TRUE, TRUE, FALSE);
2409                     break;
2410                   case 'p':
2411                     /* compatibility(ATARI) */
2412                     curr_attr |= ATTR_REVERSE;
2413                     break;
2414                   case 'q':
2415                     /* compatibility(ATARI) */
2416                     curr_attr &= ~ATTR_REVERSE;
2417                     break;
2418                   case 'v':            /* wrap Autowrap on - Wyse style */
2419                     /* compatibility(ATARI) */
2420                     wrap = 1;
2421                     break;
2422                   case 'w':            /* Autowrap off */
2423                     /* compatibility(ATARI) */
2424                     wrap = 0;
2425                     break;
2426
2427                   case 'R':
2428                     /* compatibility(OTHER) */
2429                     vt52_bold = FALSE;
2430                     curr_attr = ATTR_DEFAULT;
2431                     if (use_bce)
2432                         erase_char = (' ' | ATTR_ASCII |
2433                                      (curr_attr & 
2434                                       (ATTR_FGMASK | ATTR_BGMASK)));
2435                     break;
2436                   case 'S':
2437                     /* compatibility(VI50) */
2438                     curr_attr |= ATTR_UNDER;
2439                     break;
2440                   case 'W':
2441                     /* compatibility(VI50) */
2442                     curr_attr &= ~ATTR_UNDER;
2443                     break;
2444                   case 'U':
2445                     /* compatibility(VI50) */
2446                     vt52_bold = TRUE;
2447                     curr_attr |= ATTR_BOLD;
2448                     break;
2449                   case 'T':
2450                     /* compatibility(VI50) */
2451                     vt52_bold = FALSE;
2452                     curr_attr &= ~ATTR_BOLD;
2453                     break;
2454 #endif
2455                 }
2456                 break;
2457               case VT52_Y1:
2458                 termstate = VT52_Y2;
2459                 move(curs.x, c - ' ', 0);
2460                 break;
2461               case VT52_Y2:
2462                 termstate = TOPLEVEL;
2463                 move(c - ' ', curs.y, 0);
2464                 break;
2465
2466 #ifdef VT52_PLUS
2467               case VT52_FG:
2468                 termstate = TOPLEVEL;
2469                 curr_attr &= ~ATTR_FGMASK;
2470                 curr_attr &= ~ATTR_BOLD;
2471                 curr_attr |= (c & 0x7) << ATTR_FGSHIFT;
2472                 if ((c & 0x8) || vt52_bold)
2473                     curr_attr |= ATTR_BOLD;
2474
2475                 if (use_bce)
2476                     erase_char = (' ' | ATTR_ASCII |
2477                                  (curr_attr & (ATTR_FGMASK | ATTR_BGMASK)));
2478                 break;
2479               case VT52_BG:
2480                 termstate = TOPLEVEL;
2481                 curr_attr &= ~ATTR_BGMASK;
2482                 curr_attr &= ~ATTR_BLINK;
2483                 curr_attr |= (c & 0x7) << ATTR_BGSHIFT;
2484
2485                 /* Note: bold background */
2486                 if (c & 0x8)
2487                     curr_attr |= ATTR_BLINK;
2488
2489                 if (use_bce)
2490                     erase_char = (' ' | ATTR_ASCII |
2491                                  (curr_attr & (ATTR_FGMASK | ATTR_BGMASK)));
2492                 break;
2493 #endif
2494               default: break;          /* placate gcc warning about enum use */
2495             }
2496         if (selstate != NO_SELECTION) {
2497             pos cursplus = curs;
2498             incpos(cursplus);
2499             check_selection(curs, cursplus);
2500         }
2501     }
2502 }
2503
2504 #if 0
2505 /*
2506  * Compare two lines to determine whether they are sufficiently
2507  * alike to scroll-optimise one to the other. Return the degree of
2508  * similarity.
2509  */
2510 static int linecmp(unsigned long *a, unsigned long *b)
2511 {
2512     int i, n;
2513
2514     for (i = n = 0; i < cols; i++)
2515         n += (*a++ == *b++);
2516     return n;
2517 }
2518 #endif
2519
2520 /*
2521  * Given a context, update the window. Out of paranoia, we don't
2522  * allow WM_PAINT responses to do scrolling optimisations.
2523  */
2524 static void do_paint(Context ctx, int may_optimise)
2525 {
2526     int i, j, our_curs_y;
2527     unsigned long rv, cursor;
2528     pos scrpos;
2529     char ch[1024];
2530     long cursor_background = ERASE_CHAR;
2531     long ticks;
2532
2533     /*
2534      * Check the visual bell state.
2535      */
2536     if (in_vbell) {
2537         ticks = GetTickCount();
2538         if (ticks - vbell_timeout >= 0)
2539             in_vbell = FALSE;
2540     }
2541
2542     rv = (!rvideo ^ !in_vbell ? ATTR_REVERSE : 0);
2543
2544     /* Depends on:
2545      * screen array, disptop, scrtop,
2546      * selection, rv, 
2547      * cfg.blinkpc, blink_is_real, tblinker, 
2548      * curs.y, curs.x, blinker, cfg.blink_cur, cursor_on, has_focus, wrapnext
2549      */
2550
2551     /* Has the cursor position or type changed ? */
2552     if (cursor_on) {
2553         if (has_focus) {
2554             if (blinker || !cfg.blink_cur)
2555                 cursor = TATTR_ACTCURS;
2556             else
2557                 cursor = 0;
2558         } else
2559             cursor = TATTR_PASCURS;
2560         if (wrapnext)
2561             cursor |= TATTR_RIGHTCURS;
2562     } else
2563         cursor = 0;
2564     our_curs_y = curs.y - disptop;
2565
2566     if (dispcurs && (curstype != cursor ||
2567                      dispcurs !=
2568                      disptext + our_curs_y * (cols + 1) + curs.x)) {
2569         if (dispcurs > disptext && 
2570                 (*dispcurs & (CHAR_MASK | CSET_MASK)) == UCSWIDE)
2571             dispcurs[-1] |= ATTR_INVALID;
2572         if ( (dispcurs[1] & (CHAR_MASK | CSET_MASK)) == UCSWIDE)
2573             dispcurs[1] |= ATTR_INVALID;
2574         *dispcurs |= ATTR_INVALID;
2575         curstype = 0;
2576     }
2577     dispcurs = NULL;
2578
2579     /* The normal screen data */
2580     for (i = 0; i < rows; i++) {
2581         unsigned long *ldata;
2582         int lattr;
2583         int idx, dirty_line, dirty_run;
2584         unsigned long attr = 0;
2585         int updated_line = 0;
2586         int start = 0;
2587         int ccount = 0;
2588         int last_run_dirty = 0;
2589
2590         scrpos.y = i + disptop;
2591         ldata = lineptr(scrpos.y);
2592         lattr = (ldata[cols] & LATTR_MODE);
2593
2594         idx = i * (cols + 1);
2595         dirty_run = dirty_line = (ldata[cols] != disptext[idx + cols]);
2596         disptext[idx + cols] = ldata[cols];
2597
2598         for (j = 0; j < cols; j++, idx++) {
2599             unsigned long tattr, tchar;
2600             unsigned long *d = ldata + j;
2601             int break_run;
2602             scrpos.x = j;
2603
2604             tchar = (*d & (CHAR_MASK | CSET_MASK));
2605             tattr = (*d & (ATTR_MASK ^ CSET_MASK));
2606             switch (tchar & CSET_MASK) {
2607               case ATTR_ASCII:
2608                 tchar = unitab_line[tchar & 0xFF];
2609                 break;
2610               case ATTR_LINEDRW:
2611                 tchar = unitab_xterm[tchar & 0xFF];
2612                 break;
2613               case ATTR_SCOACS:  
2614                 tchar = unitab_scoacs[tchar&0xFF]; 
2615                 break;
2616             }
2617             tattr |= (tchar & CSET_MASK);
2618             tchar &= CHAR_MASK;
2619             if ((d[1] & (CHAR_MASK | CSET_MASK)) == UCSWIDE)
2620                     tattr |= ATTR_WIDE;
2621
2622             /* Video reversing things */
2623             tattr = (tattr ^ rv
2624                      ^ (posle(selstart, scrpos) &&
2625                         poslt(scrpos, selend) ? ATTR_REVERSE : 0));
2626
2627             /* 'Real' blinking ? */
2628             if (blink_is_real && (tattr & ATTR_BLINK)) {
2629                 if (has_focus && tblinker) {
2630                     tchar = ' ';
2631                     tattr &= ~CSET_MASK;
2632                     tattr |= ATTR_ACP;
2633                 }
2634                 tattr &= ~ATTR_BLINK;
2635             }
2636
2637             /*
2638              * Check the font we'll _probably_ be using to see if 
2639              * the character is wide when we don't want it to be.
2640              */
2641             if ((tchar | tattr) != (disptext[idx]& ~ATTR_NARROW)) {
2642                 if ((tattr & ATTR_WIDE) == 0 && 
2643                     CharWidth(ctx, (tchar | tattr) & 0xFFFF) == 2)
2644                     tattr |= ATTR_NARROW;
2645             } else if (disptext[idx]&ATTR_NARROW)
2646                 tattr |= ATTR_NARROW;
2647
2648             /* Cursor here ? Save the 'background' */
2649             if (i == our_curs_y && j == curs.x) {
2650                 cursor_background = tattr | tchar;
2651                 dispcurs = disptext + idx;
2652             }
2653
2654             if ((disptext[idx] ^ tattr) & ATTR_WIDE)
2655                 dirty_line = TRUE;
2656
2657             break_run = (tattr != attr || j - start >= sizeof(ch));
2658
2659             /* Special hack for VT100 Linedraw glyphs */
2660             if ((attr & CSET_MASK) == 0x2300 && tchar >= 0xBA
2661                 && tchar <= 0xBD) break_run = TRUE;
2662
2663             if (!dbcs_screenfont && !dirty_line) {
2664                 if ((tchar | tattr) == disptext[idx])
2665                     break_run = TRUE;
2666                 else if (!dirty_run && ccount == 1)
2667                     break_run = TRUE;
2668             }
2669
2670             if (break_run) {
2671                 if ((dirty_run || last_run_dirty) && ccount > 0) {
2672                     do_text(ctx, start, i, ch, ccount, attr, lattr);
2673                     updated_line = 1;
2674                 }
2675                 start = j;
2676                 ccount = 0;
2677                 attr = tattr;
2678                 if (dbcs_screenfont)
2679                     last_run_dirty = dirty_run;
2680                 dirty_run = dirty_line;
2681             }
2682
2683             if ((tchar | tattr) != disptext[idx])
2684                 dirty_run = TRUE;
2685             ch[ccount++] = (char) tchar;
2686             disptext[idx] = tchar | tattr;
2687
2688             /* If it's a wide char step along to the next one. */
2689             if (tattr & ATTR_WIDE) {
2690                 if (++j < cols) {
2691                     idx++;
2692                     d++;
2693                     /* Cursor is here ? Ouch! */
2694                     if (i == our_curs_y && j == curs.x) {
2695                         cursor_background = *d;
2696                         dispcurs = disptext + idx;
2697                     }
2698                     if (disptext[idx] != *d)
2699                         dirty_run = TRUE;
2700                     disptext[idx] = *d;
2701                 }
2702             }
2703         }
2704         if (dirty_run && ccount > 0) {
2705             do_text(ctx, start, i, ch, ccount, attr, lattr);
2706             updated_line = 1;
2707         }
2708
2709         /* Cursor on this line ? (and changed) */
2710         if (i == our_curs_y && (curstype != cursor || updated_line)) {
2711             ch[0] = (char) (cursor_background & CHAR_MASK);
2712             attr = (cursor_background & ATTR_MASK) | cursor;
2713             do_cursor(ctx, curs.x, i, ch, 1, attr, lattr);
2714             curstype = cursor;
2715         }
2716     }
2717 }
2718
2719 /*
2720  * Flick the switch that says if blinking things should be shown or hidden.
2721  */
2722
2723 void term_blink(int flg)
2724 {
2725     static long last_blink = 0;
2726     static long last_tblink = 0;
2727     long now, blink_diff;
2728
2729     now = GetTickCount();
2730     blink_diff = now - last_tblink;
2731
2732     /* Make sure the text blinks no more than 2Hz */
2733     if (blink_diff < 0 || blink_diff > 450) {
2734         last_tblink = now;
2735         tblinker = !tblinker;
2736     }
2737
2738     if (flg) {
2739         blinker = 1;
2740         last_blink = now;
2741         return;
2742     }
2743
2744     blink_diff = now - last_blink;
2745
2746     /* Make sure the cursor blinks no faster than GetCaretBlinkTime() */
2747     if (blink_diff >= 0 && blink_diff < (long) GetCaretBlinkTime())
2748         return;
2749
2750     last_blink = now;
2751     blinker = !blinker;
2752 }
2753
2754 /*
2755  * Invalidate the whole screen so it will be repainted in full.
2756  */
2757 void term_invalidate(void)
2758 {
2759     int i;
2760
2761     for (i = 0; i < rows * (cols + 1); i++)
2762         disptext[i] = ATTR_INVALID;
2763 }
2764
2765 /*
2766  * Paint the window in response to a WM_PAINT message.
2767  */
2768 void term_paint(Context ctx, int left, int top, int right, int bottom)
2769 {
2770     int i, j;
2771     if (left < 0) left = 0;
2772     if (top < 0) top = 0;
2773     if (right >= cols) right = cols-1;
2774     if (bottom >= rows) bottom = rows-1;
2775
2776     for (i = top; i <= bottom && i < rows; i++) {
2777         if ((disptext[i * (cols + 1) + cols] & LATTR_MODE) == LATTR_NORM)
2778             for (j = left; j <= right && j < cols; j++)
2779                 disptext[i * (cols + 1) + j] = ATTR_INVALID;
2780         else
2781             for (j = left / 2; j <= right / 2 + 1 && j < cols; j++)
2782                 disptext[i * (cols + 1) + j] = ATTR_INVALID;
2783     }
2784
2785     /* This should happen soon enough, also for some reason it sometimes 
2786      * fails to actually do anything when re-sizing ... painting the wrong
2787      * window perhaps ?
2788      */
2789     if (alt_pressed)
2790         do_paint (ctx, FALSE);
2791 }
2792
2793 /*
2794  * Attempt to scroll the scrollback. The second parameter gives the
2795  * position we want to scroll to; the first is +1 to denote that
2796  * this position is relative to the beginning of the scrollback, -1
2797  * to denote it is relative to the end, and 0 to denote that it is
2798  * relative to the current position.
2799  */
2800 void term_scroll(int rel, int where)
2801 {
2802     int sbtop = -count234(scrollback);
2803
2804     disptop = (rel < 0 ? 0 : rel > 0 ? sbtop : disptop) + where;
2805     if (disptop < sbtop)
2806         disptop = sbtop;
2807     if (disptop > 0)
2808         disptop = 0;
2809     update_sbar();
2810     term_update();
2811 }
2812
2813 static void clipme(pos top, pos bottom)
2814 {
2815     wchar_t *workbuf;
2816     wchar_t *wbptr;                    /* where next char goes within workbuf */
2817     int wblen = 0;                     /* workbuf len */
2818     int buflen;                        /* amount of memory allocated to workbuf */
2819
2820     buflen = 5120;                     /* Default size */
2821     workbuf = smalloc(buflen * sizeof(wchar_t));
2822     wbptr = workbuf;                   /* start filling here */
2823
2824     while (poslt(top, bottom)) {
2825         int nl = FALSE;
2826         unsigned long *ldata = lineptr(top.y);
2827         pos nlpos;
2828
2829         nlpos.y = top.y;
2830         nlpos.x = cols;
2831
2832         if (!(ldata[cols] & LATTR_WRAPPED)) {
2833             while (((ldata[nlpos.x - 1] & 0xFF) == 0x20 ||
2834                     (DIRECT_CHAR(ldata[nlpos.x - 1]) &&
2835                      (ldata[nlpos.x - 1] & CHAR_MASK) == 0x20))
2836                    && poslt(top, nlpos))
2837                 decpos(nlpos);
2838             if (poslt(nlpos, bottom))
2839                 nl = TRUE;
2840         }
2841         while (poslt(top, bottom) && poslt(top, nlpos)) {
2842 #if 0
2843             char cbuf[16], *p;
2844             sprintf(cbuf, "<U+%04x>", (ldata[top.x] & 0xFFFF));
2845 #else
2846             wchar_t cbuf[16], *p;
2847             int uc = (ldata[top.x] & 0xFFFF);
2848             int set, c;
2849
2850             if (uc == UCSWIDE) {
2851                 top.x++;
2852                 continue;
2853             }
2854
2855             switch (uc & CSET_MASK) {
2856               case ATTR_LINEDRW:
2857                 if (!cfg.rawcnp) {
2858                     uc = unitab_xterm[uc & 0xFF];
2859                     break;
2860                 }
2861               case ATTR_ASCII:
2862                 uc = unitab_line[uc & 0xFF];
2863                 break;
2864               case ATTR_SCOACS:  
2865                 uc = unitab_scoacs[uc&0xFF]; 
2866                 break;
2867             }
2868             switch (uc & CSET_MASK) {
2869               case ATTR_ACP:
2870                 uc = unitab_font[uc & 0xFF];
2871                 break;
2872               case ATTR_OEMCP:
2873                 uc = unitab_oemcp[uc & 0xFF];
2874                 break;
2875             }
2876
2877             set = (uc & CSET_MASK);
2878             c = (uc & CHAR_MASK);
2879             cbuf[0] = uc;
2880             cbuf[1] = 0;
2881
2882             if (DIRECT_FONT(uc)) {
2883                 if (c >= ' ' && c != 0x7F) {
2884                     unsigned char buf[4];
2885                     WCHAR wbuf[4];
2886                     int rv;
2887                     if (IsDBCSLeadByteEx(font_codepage, (BYTE) c)) {
2888                         buf[0] = c;
2889                         buf[1] = (unsigned char) ldata[top.x + 1];
2890                         rv = MultiByteToWideChar(font_codepage,
2891                                                  0, buf, 2, wbuf, 4);
2892                         top.x++;
2893                     } else {
2894                         buf[0] = c;
2895                         rv = MultiByteToWideChar(font_codepage,
2896                                                  0, buf, 1, wbuf, 4);
2897                     }
2898
2899                     if (rv > 0) {
2900                         memcpy(cbuf, wbuf, rv * sizeof(wchar_t));
2901                         cbuf[rv] = 0;
2902                     }
2903                 }
2904             }
2905 #endif
2906
2907             for (p = cbuf; *p; p++) {
2908                 /* Enough overhead for trailing NL and nul */
2909                 if (wblen >= buflen - 16) {
2910                     workbuf =
2911                         srealloc(workbuf,
2912                                  sizeof(wchar_t) * (buflen += 100));
2913                     wbptr = workbuf + wblen;
2914                 }
2915                 wblen++;
2916                 *wbptr++ = *p;
2917             }
2918             top.x++;
2919         }
2920         if (nl) {
2921             int i;
2922             for (i = 0; i < sel_nl_sz; i++) {
2923                 wblen++;
2924                 *wbptr++ = sel_nl[i];
2925             }
2926         }
2927         top.y++;
2928         top.x = 0;
2929     }
2930     wblen++;
2931     *wbptr++ = 0;
2932     write_clip(workbuf, wblen, FALSE); /* transfer to clipboard */
2933     if (buflen > 0)                    /* indicates we allocated this buffer */
2934         sfree(workbuf);
2935 }
2936
2937 void term_copyall(void)
2938 {
2939     pos top;
2940     top.y = -count234(scrollback);
2941     top.x = 0;
2942     clipme(top, curs);
2943 }
2944
2945 /*
2946  * The wordness array is mainly for deciding the disposition of the US-ASCII 
2947  * characters.
2948  */
2949 static int wordtype(int uc)
2950 {
2951     static struct {
2952         int start, end, ctype;
2953     } *wptr, ucs_words[] = {
2954         {
2955         128, 160, 0}, {
2956         161, 191, 1}, {
2957         215, 215, 1}, {
2958         247, 247, 1}, {
2959         0x037e, 0x037e, 1},            /* Greek question mark */
2960         {
2961         0x0387, 0x0387, 1},            /* Greek ano teleia */
2962         {
2963         0x055a, 0x055f, 1},            /* Armenian punctuation */
2964         {
2965         0x0589, 0x0589, 1},            /* Armenian full stop */
2966         {
2967         0x0700, 0x070d, 1},            /* Syriac punctuation */
2968         {
2969         0x104a, 0x104f, 1},            /* Myanmar punctuation */
2970         {
2971         0x10fb, 0x10fb, 1},            /* Georgian punctuation */
2972         {
2973         0x1361, 0x1368, 1},            /* Ethiopic punctuation */
2974         {
2975         0x166d, 0x166e, 1},            /* Canadian Syl. punctuation */
2976         {
2977         0x17d4, 0x17dc, 1},            /* Khmer punctuation */
2978         {
2979         0x1800, 0x180a, 1},            /* Mongolian punctuation */
2980         {
2981         0x2000, 0x200a, 0},            /* Various spaces */
2982         {
2983         0x2070, 0x207f, 2},            /* superscript */
2984         {
2985         0x2080, 0x208f, 2},            /* subscript */
2986         {
2987         0x200b, 0x27ff, 1},            /* punctuation and symbols */
2988         {
2989         0x3000, 0x3000, 0},            /* ideographic space */
2990         {
2991         0x3001, 0x3020, 1},            /* ideographic punctuation */
2992         {
2993         0x303f, 0x309f, 3},            /* Hiragana */
2994         {
2995         0x30a0, 0x30ff, 3},            /* Katakana */
2996         {
2997         0x3300, 0x9fff, 3},            /* CJK Ideographs */
2998         {
2999         0xac00, 0xd7a3, 3},            /* Hangul Syllables */
3000         {
3001         0xf900, 0xfaff, 3},            /* CJK Ideographs */
3002         {
3003         0xfe30, 0xfe6b, 1},            /* punctuation forms */
3004         {
3005         0xff00, 0xff0f, 1},            /* half/fullwidth ASCII */
3006         {
3007         0xff1a, 0xff20, 1},            /* half/fullwidth ASCII */
3008         {
3009         0xff3b, 0xff40, 1},            /* half/fullwidth ASCII */
3010         {
3011         0xff5b, 0xff64, 1},            /* half/fullwidth ASCII */
3012         {
3013         0xfff0, 0xffff, 0},            /* half/fullwidth ASCII */
3014         {
3015         0, 0, 0}
3016     };
3017
3018     uc &= (CSET_MASK | CHAR_MASK);
3019
3020     switch (uc & CSET_MASK) {
3021       case ATTR_LINEDRW:
3022         uc = unitab_xterm[uc & 0xFF];
3023         break;
3024       case ATTR_ASCII:
3025         uc = unitab_line[uc & 0xFF];
3026         break;
3027       case ATTR_SCOACS:  
3028         uc = unitab_scoacs[uc&0xFF]; 
3029         break;
3030     }
3031     switch (uc & CSET_MASK) {
3032       case ATTR_ACP:
3033         uc = unitab_font[uc & 0xFF];
3034         break;
3035       case ATTR_OEMCP:
3036         uc = unitab_oemcp[uc & 0xFF];
3037         break;
3038     }
3039
3040     /* For DBCS font's I can't do anything usefull. Even this will sometimes
3041      * fail as there's such a thing as a double width space. :-(
3042      */
3043     if (dbcs_screenfont && font_codepage == line_codepage)
3044         return (uc != ' ');
3045
3046     if (uc < 0x80)
3047         return wordness[uc];
3048
3049     for (wptr = ucs_words; wptr->start; wptr++) {
3050         if (uc >= wptr->start && uc <= wptr->end)
3051             return wptr->ctype;
3052     }
3053
3054     return 2;
3055 }
3056
3057 /*
3058  * Spread the selection outwards according to the selection mode.
3059  */
3060 static pos sel_spread_half(pos p, int dir)
3061 {
3062     unsigned long *ldata;
3063     short wvalue;
3064
3065     ldata = lineptr(p.y);
3066
3067     switch (selmode) {
3068       case SM_CHAR:
3069         /*
3070          * In this mode, every character is a separate unit, except
3071          * for runs of spaces at the end of a non-wrapping line.
3072          */
3073         if (!(ldata[cols] & LATTR_WRAPPED)) {
3074             unsigned long *q = ldata + cols;
3075             while (q > ldata && (q[-1] & CHAR_MASK) == 0x20)
3076                 q--;
3077             if (q == ldata + cols)
3078                 q--;
3079             if (p.x >= q - ldata)
3080                 p.x = (dir == -1 ? q - ldata : cols - 1);
3081         }
3082         break;
3083       case SM_WORD:
3084         /*
3085          * In this mode, the units are maximal runs of characters
3086          * whose `wordness' has the same value.
3087          */
3088         wvalue = wordtype(ldata[p.x]);
3089         if (dir == +1) {
3090             while (p.x < cols && wordtype(ldata[p.x + 1]) == wvalue)
3091                 p.x++;
3092         } else {
3093             while (p.x > 0 && wordtype(ldata[p.x - 1]) == wvalue)
3094                 p.x--;
3095         }
3096         break;
3097       case SM_LINE:
3098         /*
3099          * In this mode, every line is a unit.
3100          */
3101         p.x = (dir == -1 ? 0 : cols - 1);
3102         break;
3103     }
3104     return p;
3105 }
3106
3107 static void sel_spread(void)
3108 {
3109     selstart = sel_spread_half(selstart, -1);
3110     decpos(selend);
3111     selend = sel_spread_half(selend, +1);
3112     incpos(selend);
3113 }
3114
3115 void term_do_paste(void)
3116 {
3117     wchar_t *data;
3118     int len;
3119
3120     get_clip(&data, &len);
3121     if (data) {
3122         wchar_t *p, *q;
3123
3124         if (paste_buffer)
3125             sfree(paste_buffer);
3126         paste_pos = paste_hold = paste_len = 0;
3127         paste_buffer = smalloc(len * sizeof(wchar_t));
3128
3129         p = q = data;
3130         while (p < data + len) {
3131             while (p < data + len &&
3132                    !(p <= data + len - sel_nl_sz &&
3133                      !memcmp(p, sel_nl, sizeof(sel_nl))))
3134                 p++;
3135
3136             {
3137                 int i;
3138                 for (i = 0; i < p - q; i++) {
3139                     paste_buffer[paste_len++] = q[i];
3140                 }
3141             }
3142
3143             if (p <= data + len - sel_nl_sz &&
3144                 !memcmp(p, sel_nl, sizeof(sel_nl))) {
3145                 paste_buffer[paste_len++] = '\r';
3146                 p += sel_nl_sz;
3147             }
3148             q = p;
3149         }
3150
3151         /* Assume a small paste will be OK in one go. */
3152         if (paste_len < 256) {
3153             luni_send(paste_buffer, paste_len, 0);
3154             if (paste_buffer)
3155                 sfree(paste_buffer);
3156             paste_buffer = 0;
3157             paste_pos = paste_hold = paste_len = 0;
3158         }
3159     }
3160     get_clip(NULL, NULL);
3161 }
3162
3163 void term_mouse(Mouse_Button b, Mouse_Action a, int x, int y,
3164                 int shift, int ctrl)
3165 {
3166     pos selpoint;
3167     unsigned long *ldata;
3168     int raw_mouse = xterm_mouse && !(cfg.mouse_override && shift);
3169
3170     if (y < 0) {
3171         y = 0;
3172         if (a == MA_DRAG && !raw_mouse)
3173             term_scroll(0, -1);
3174     }
3175     if (y >= rows) {
3176         y = rows - 1;
3177         if (a == MA_DRAG && !raw_mouse)
3178             term_scroll(0, +1);
3179     }
3180     if (x < 0) {
3181         if (y > 0) {
3182             x = cols - 1;
3183             y--;
3184         } else
3185             x = 0;
3186     }
3187     if (x >= cols)
3188         x = cols - 1;
3189
3190     selpoint.y = y + disptop;
3191     selpoint.x = x;
3192     ldata = lineptr(selpoint.y);
3193     if ((ldata[cols] & LATTR_MODE) != LATTR_NORM)
3194         selpoint.x /= 2;
3195
3196     if (raw_mouse) {
3197         int encstate = 0, r, c;
3198         char abuf[16];
3199         static int is_down = 0;
3200
3201         switch (b) {
3202           case MBT_LEFT:
3203             encstate = 0x20;           /* left button down */
3204             break;
3205           case MBT_MIDDLE:
3206             encstate = 0x21;
3207             break;
3208           case MBT_RIGHT:
3209             encstate = 0x22;
3210             break;
3211           case MBT_WHEEL_UP:
3212             encstate = 0x60;
3213             break;
3214           case MBT_WHEEL_DOWN:
3215             encstate = 0x61;
3216             break;
3217           default: break;              /* placate gcc warning about enum use */
3218         }
3219         switch (a) {
3220           case MA_DRAG:
3221             if (xterm_mouse == 1)
3222                 return;
3223             encstate += 0x20;
3224             break;
3225           case MA_RELEASE:
3226             encstate = 0x23;
3227             is_down = 0;
3228             break;
3229           case MA_CLICK:
3230             if (is_down == b)
3231                 return;
3232             is_down = b;
3233             break;
3234           default: break;              /* placate gcc warning about enum use */
3235         }
3236         if (shift)
3237             encstate += 0x04;
3238         if (ctrl)
3239             encstate += 0x10;
3240         r = y + 33;
3241         c = x + 33;
3242
3243         sprintf(abuf, "\033[M%c%c%c", encstate, c, r);
3244         ldisc_send(abuf, 6, 0);
3245         return;
3246     }
3247
3248     b = translate_button(b);
3249
3250     if (b == MBT_SELECT && a == MA_CLICK) {
3251         deselect();
3252         selstate = ABOUT_TO;
3253         selanchor = selpoint;
3254         selmode = SM_CHAR;
3255     } else if (b == MBT_SELECT && (a == MA_2CLK || a == MA_3CLK)) {
3256         deselect();
3257         selmode = (a == MA_2CLK ? SM_WORD : SM_LINE);
3258         selstate = DRAGGING;
3259         selstart = selanchor = selpoint;
3260         selend = selstart;
3261         incpos(selend);
3262         sel_spread();
3263     } else if ((b == MBT_SELECT && a == MA_DRAG) ||
3264                (b == MBT_EXTEND && a != MA_RELEASE)) {
3265         if (selstate == ABOUT_TO && poseq(selanchor, selpoint))
3266             return;
3267         if (b == MBT_EXTEND && a != MA_DRAG && selstate == SELECTED) {
3268             if (posdiff(selpoint, selstart) <
3269                 posdiff(selend, selstart) / 2) {
3270                 selanchor = selend;
3271                 decpos(selanchor);
3272             } else {
3273                 selanchor = selstart;
3274             }
3275             selstate = DRAGGING;
3276         }
3277         if (selstate != ABOUT_TO && selstate != DRAGGING)
3278             selanchor = selpoint;
3279         selstate = DRAGGING;
3280         if (poslt(selpoint, selanchor)) {
3281             selstart = selpoint;
3282             selend = selanchor;
3283             incpos(selend);
3284         } else {
3285             selstart = selanchor;
3286             selend = selpoint;
3287             incpos(selend);
3288         }
3289         sel_spread();
3290     } else if ((b == MBT_SELECT || b == MBT_EXTEND) && a == MA_RELEASE) {
3291         if (selstate == DRAGGING) {
3292             /*
3293              * We've completed a selection. We now transfer the
3294              * data to the clipboard.
3295              */
3296             clipme(selstart, selend);
3297             selstate = SELECTED;
3298         } else
3299             selstate = NO_SELECTION;
3300     } else if (b == MBT_PASTE
3301                && (a == MA_CLICK || a == MA_2CLK || a == MA_3CLK)) {
3302         term_do_paste();
3303     }
3304
3305     term_update();
3306 }
3307
3308 void term_nopaste()
3309 {
3310     if (paste_len == 0)
3311         return;
3312     sfree(paste_buffer);
3313     paste_buffer = 0;
3314     paste_len = 0;
3315 }
3316
3317 void term_paste()
3318 {
3319     static long last_paste = 0;
3320     long now, paste_diff;
3321
3322     if (paste_len == 0)
3323         return;
3324
3325     /* Don't wait forever to paste */
3326     if (paste_hold) {
3327         now = GetTickCount();
3328         paste_diff = now - last_paste;
3329         if (paste_diff >= 0 && paste_diff < 450)
3330             return;
3331     }
3332     paste_hold = 0;
3333
3334     while (paste_pos < paste_len) {
3335         int n = 0;
3336         while (n + paste_pos < paste_len) {
3337             if (paste_buffer[paste_pos + n++] == '\r')
3338                 break;
3339         }
3340         luni_send(paste_buffer + paste_pos, n, 0);
3341         paste_pos += n;
3342
3343         if (paste_pos < paste_len) {
3344             paste_hold = 1;
3345             return;
3346         }
3347     }
3348     sfree(paste_buffer);
3349     paste_buffer = 0;
3350     paste_len = 0;
3351 }
3352
3353 static void deselect(void)
3354 {
3355     selstate = NO_SELECTION;
3356     selstart.x = selstart.y = selend.x = selend.y = 0;
3357 }
3358
3359 void term_deselect(void)
3360 {
3361     deselect();
3362     term_update();
3363 }
3364
3365 int term_ldisc(int option)
3366 {
3367     if (option == LD_ECHO)
3368         return term_echoing;
3369     if (option == LD_EDIT)
3370         return term_editing;
3371     return FALSE;
3372 }
3373
3374 /*
3375  * from_backend(), to get data from the backend for the terminal.
3376  */
3377 int from_backend(int is_stderr, char *data, int len)
3378 {
3379     bufchain_add(&inbuf, data, len);
3380
3381     /*
3382      * term_out() always completely empties inbuf. Therefore,
3383      * there's no reason at all to return anything other than zero
3384      * from this function, because there _can't_ be a question of
3385      * the remote side needing to wait until term_out() has cleared
3386      * a backlog.
3387      *
3388      * This is a slightly suboptimal way to deal with SSH2 - in
3389      * principle, the window mechanism would allow us to continue
3390      * to accept data on forwarded ports and X connections even
3391      * while the terminal processing was going slowly - but we
3392      * can't do the 100% right thing without moving the terminal
3393      * processing into a separate thread, and that might hurt
3394      * portability. So we manage stdout buffering the old SSH1 way:
3395      * if the terminal processing goes slowly, the whole SSH
3396      * connection stops accepting data until it's ready.
3397      *
3398      * In practice, I can't imagine this causing serious trouble.
3399      */
3400     return 0;
3401 }
3402
3403 /*
3404  * Log session traffic.
3405  */
3406 void logtraffic(unsigned char c, int logmode)
3407 {
3408     if (cfg.logtype > 0) {
3409         if (cfg.logtype == logmode) {
3410             /* deferred open file from pgm start? */
3411             if (!lgfp)
3412                 logfopen();
3413             if (lgfp)
3414                 fputc(c, lgfp);
3415         }
3416     }
3417 }
3418
3419 void settimstr(char *ta, int no_sec);
3420 char *subslfcode(char *dest, char *src, char *dstrt);
3421 char *stpncpy(char *dst, const char *src, size_t maxlen);
3422 char timdatbuf[20];
3423 char currlogfilename[FILENAME_MAX];
3424
3425 /* open log file append/overwrite mode */
3426 void logfopen(void)
3427 {
3428     char buf[256];
3429     time_t t;
3430     struct tm tm;
3431     char writemod[4];
3432
3433     if (!cfg.logtype)
3434         return;
3435     sprintf(writemod, "wb");           /* default to rewrite */
3436
3437     time(&t);
3438     tm = *localtime(&t);
3439
3440     /* substitute special codes in file name */
3441     xlatlognam(currlogfilename,cfg.logfilename,cfg.host, &tm);
3442
3443     lgfp = fopen(currlogfilename, "r"); /* file already present? */
3444     if (lgfp) {
3445         int i;
3446         fclose(lgfp);
3447         i = askappend(currlogfilename);
3448         if (i == 1)
3449             writemod[0] = 'a';         /* set append mode */
3450         else if (i == 0) {             /* cancelled */
3451             lgfp = NULL;
3452             cfg.logtype = 0;           /* disable logging */
3453             return;
3454         }
3455     }
3456
3457     lgfp = fopen(currlogfilename, writemod);
3458     if (lgfp) {                        /* enter into event log */
3459         sprintf(buf, "%s session log (%s mode) to file : ",
3460                 (writemod[0] == 'a') ? "Appending" : "Writing new",
3461                 (cfg.logtype == LGTYP_ASCII ? "ASCII" :
3462                  cfg.logtype == LGTYP_DEBUG ? "raw" : "<ukwn>"));
3463         /* Make sure we do not exceed the output buffer size */
3464         strncat(buf, currlogfilename, 128);
3465         buf[strlen(buf)] = '\0';
3466         logevent(buf);
3467
3468         /* --- write header line into log file */
3469         fputs("=~=~=~=~=~=~=~=~=~=~=~= PuTTY log ", lgfp);
3470         strftime(buf, 24, "%Y.%m.%d %H:%M:%S", &tm);
3471         fputs(buf, lgfp);
3472         fputs(" =~=~=~=~=~=~=~=~=~=~=~=\r\n", lgfp);
3473     }
3474 }
3475
3476 void logfclose(void)
3477 {
3478     if (lgfp) {
3479         fclose(lgfp);
3480         lgfp = NULL;
3481     }
3482 }
3483
3484 /*
3485  * translate format codes into time/date strings
3486  * and insert them into log file name
3487  *
3488  * "&Y":YYYY   "&m":MM   "&d":DD   "&T":hhmm   "&h":<hostname>   "&&":&
3489  */
3490 static void xlatlognam(char *d, char *s, char *hostname, struct tm *tm) {
3491     char buf[10], *bufp;
3492     int size;
3493     char *ds = d; /* save start pos. */
3494     int len = FILENAME_MAX-1;
3495
3496     while (*s) {
3497         /* Let (bufp, len) be the string to append. */
3498         bufp = buf;                    /* don't usually override this */
3499         if (*s == '&') {
3500             char c;
3501             s++;
3502             if (*s) switch (c = *s++, tolower(c)) {
3503               case 'y':
3504                 size = strftime(buf, sizeof(buf), "%Y", tm);
3505                 break;
3506               case 'm':
3507                 size = strftime(buf, sizeof(buf), "%m", tm);
3508                 break;
3509               case 'd':
3510                 size = strftime(buf, sizeof(buf), "%d", tm);
3511                 break;
3512               case 't':
3513                 size = strftime(buf, sizeof(buf), "%H%M%S", tm);
3514                 break;
3515               case 'h':
3516                 bufp = hostname;
3517                 size = strlen(bufp);
3518                 break;
3519               default:
3520                 buf[0] = '&';
3521                 size = 1;
3522                 if (c != '&')
3523                     buf[size++] = c;
3524             }
3525         } else {
3526             buf[0] = *s++;
3527             size = 1;
3528         }
3529         if (size > len)
3530             size = len;
3531         memcpy(d, bufp, size);
3532         d += size;
3533         len -= size;
3534     }
3535     *d = '\0';
3536 }