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