From RDB: a patch to allow special keys (^C, ^Z, Delete, Return) to
[u/mdw/putty] / terminal.c
CommitLineData
374330e2 1#include <windows.h>
2
3#include <stdio.h>
4#include <stdlib.h>
49bad831 5#include <ctype.h>
374330e2 6
e1c8e0ed 7#include <time.h>
c83de303 8#include <assert.h>
374330e2 9#include "putty.h"
4facdf84 10#include "tree234.h"
374330e2 11
32874aea 12#define CL_ANSIMIN 0x0001 /* Codes in all ANSI like terminals. */
13#define CL_VT100 0x0002 /* VT100 */
14#define CL_VT100AVO 0x0004 /* VT100 +AVO; 132x24 (not 132x14) & attrs */
15#define CL_VT102 0x0008 /* VT102 */
16#define CL_VT220 0x0010 /* VT220 */
17#define CL_VT320 0x0020 /* VT320 */
18#define CL_VT420 0x0040 /* VT420 */
19#define CL_VT510 0x0080 /* VT510, NB VT510 includes ANSI */
20#define CL_VT340TEXT 0x0100 /* VT340 extensions that appear in the VT420 */
21#define CL_SCOANSI 0x1000 /* SCOANSI not in ANSIMIN. */
22#define CL_ANSI 0x2000 /* ANSI ECMA-48 not in the VT100..VT420 */
23#define CL_OTHER 0x4000 /* Others, Xterm, linux, putty, dunno, etc */
e14a5a13 24
c9def1b8 25#define TM_VT100 (CL_ANSIMIN|CL_VT100)
26#define TM_VT100AVO (TM_VT100|CL_VT100AVO)
27#define TM_VT102 (TM_VT100AVO|CL_VT102)
28#define TM_VT220 (TM_VT102|CL_VT220)
29#define TM_VTXXX (TM_VT220|CL_VT340TEXT|CL_VT510|CL_VT420|CL_VT320)
30#define TM_SCOANSI (CL_ANSIMIN|CL_SCOANSI)
31
32#define TM_PUTTY (0xFFFF)
e14a5a13 33
34#define compatibility(x) \
35 if ( ((CL_##x)&compatibility_level) == 0 ) { \
36 termstate=TOPLEVEL; \
37 break; \
38 }
c9def1b8 39#define compatibility2(x,y) \
40 if ( ((CL_##x|CL_##y)&compatibility_level) == 0 ) { \
41 termstate=TOPLEVEL; \
42 break; \
43 }
e14a5a13 44
45#define has_compat(x) ( ((CL_##x)&compatibility_level) != 0 )
46
47static int compatibility_level = TM_PUTTY;
48
4facdf84 49static tree234 *scrollback; /* lines scrolled off top of screen */
50static tree234 *screen; /* lines on primary screen */
51static tree234 *alt_screen; /* lines on alternate screen */
c83de303 52static int disptop; /* distance scrolled back (0 or -ve) */
e14a5a13 53
374330e2 54static unsigned long *cpos; /* cursor position (convenience) */
4facdf84 55
374330e2 56static unsigned long *disptext; /* buffer of text on real screen */
57static unsigned long *wanttext; /* buffer of text we want on screen */
374330e2 58
c83de303 59#define VBELL_TIMEOUT 100 /* millisecond len of visual bell */
156686ef 60
61struct beeptime {
62 struct beeptime *next;
63 long ticks;
64};
65static struct beeptime *beephead, *beeptail;
66int nbeeps;
67int beep_overloaded;
68long lastbeep;
69
374330e2 70static unsigned char *selspace; /* buffer for building selections in */
71
4facdf84 72#define TSIZE (sizeof(unsigned long))
c83de303 73#define fix_cpos do { cpos = lineptr(curs.y) + curs.x; } while(0)
374330e2 74
75static unsigned long curr_attr, save_attr;
e14a5a13 76static unsigned long erase_char = ERASE_CHAR;
374330e2 77
4facdf84 78typedef struct {
79 int y, x;
80} pos;
81#define poslt(p1,p2) ( (p1).y < (p2).y || ( (p1).y == (p2).y && (p1).x < (p2).x ) )
82#define posle(p1,p2) ( (p1).y < (p2).y || ( (p1).y == (p2).y && (p1).x <= (p2).x ) )
83#define poseq(p1,p2) ( (p1).y == (p2).y && (p1).x == (p2).x )
84#define posdiff(p1,p2) ( ((p2).y - (p1).y) * (cols+1) + (p2).x - (p1).x )
85#define incpos(p) ( (p).x == cols ? ((p).x = 0, (p).y++, 1) : ((p).x++, 0) )
86#define decpos(p) ( (p).x == 0 ? ((p).x = cols, (p).y--, 1) : ((p).x--, 0) )
87
88static pos curs; /* cursor */
89static pos savecurs; /* saved cursor position */
374330e2 90static int marg_t, marg_b; /* scroll margins */
91static int dec_om; /* DEC origin mode flag */
92static int wrap, wrapnext; /* wrap flags */
93static int insert; /* insert-mode flag */
94static int cset; /* 0 or 1: which char set */
95static int save_cset, save_csattr; /* saved with cursor position */
96static int rvideo; /* global reverse video flag */
c83de303 97static int rvbell_timeout; /* for ESC[?5hESC[?5l vbell */
e14a5a13 98static int cursor_on; /* cursor enabled flag */
99static int reset_132; /* Flag ESC c resets to 80 cols */
100static int use_bce; /* Use Background coloured erase */
101static int blinker; /* When blinking is the cursor on ? */
c9def1b8 102static int tblinker; /* When the blinking text is on */
103static int blink_is_real; /* Actually blink blinking text */
32874aea 104static int term_echoing; /* Does terminal want local echo? */
105static int term_editing; /* Does terminal want local edit? */
374330e2 106
32874aea 107static int xterm_mouse; /* send mouse messages to app */
01c034ad 108
374330e2 109static unsigned long cset_attr[2];
110
111/*
112 * Saved settings on the alternate screen.
113 */
114static int alt_x, alt_y, alt_om, alt_wrap, alt_wnext, alt_ins, alt_cset;
115static int alt_t, alt_b;
116static int alt_which;
117
118#define ARGS_MAX 32 /* max # of esc sequence arguments */
064916ea 119#define ARG_DEFAULT 0 /* if an arg isn't specified */
374330e2 120#define def(a,d) ( (a) == ARG_DEFAULT ? (d) : (a) )
121static int esc_args[ARGS_MAX];
122static int esc_nargs;
123static int esc_query;
8b811b12 124#define ANSI(x,y) ((x)+((y)<<8))
125#define ANSI_QUE(x) ANSI(x,TRUE)
374330e2 126
127#define OSC_STR_MAX 2048
128static int osc_strlen;
32874aea 129static char osc_string[OSC_STR_MAX + 1];
374330e2 130static int osc_w;
131
c9def1b8 132static char id_string[1024] = "\033[?6c";
133
374330e2 134static unsigned char *tabs;
135
374330e2 136static enum {
cabfd08c 137 TOPLEVEL,
138 SEEN_ESC,
139 SEEN_CSI,
140 SEEN_OSC,
141 SEEN_OSC_W,
142
143 DO_CTRLS,
144
145 IGNORE_NEXT,
146 SET_GL, SET_GR,
147 SEEN_OSC_P,
148 OSC_STRING, OSC_MAYBE_ST,
e14a5a13 149 SEEN_ESCHASH,
150 VT52_ESC,
151 VT52_Y1,
152 VT52_Y2
374330e2 153} termstate;
154
155static enum {
156 NO_SELECTION, ABOUT_TO, DRAGGING, SELECTED
157} selstate;
158static enum {
159 SM_CHAR, SM_WORD, SM_LINE
160} selmode;
4facdf84 161static pos selstart, selend, selanchor;
374330e2 162
163static short wordness[256] = {
32874aea 164 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
165 0, 0, 0, 0, 0, 0, 0, 0, /* 01 */
166 0, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
167 2, 2, 1, 1, 1, 1, 1, 1, /* 23 */
168 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
169 2, 2, 2, 1, 1, 1, 1, 2, /* 45 */
170 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
171 2, 2, 2, 1, 1, 1, 1, 1, /* 67 */
172 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
173 1, 1, 1, 1, 1, 1, 1, 1, /* 89 */
174 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
175 1, 1, 1, 1, 1, 1, 1, 1, /* AB */
176 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1,
177 2, 2, 2, 2, 2, 2, 2, 2, /* CD */
178 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1,
179 2, 2, 2, 2, 2, 2, 2, 2, /* EF */
374330e2 180};
181
182static unsigned char sel_nl[] = SEL_NL;
32874aea 183static char *paste_buffer = 0;
c9def1b8 184static int paste_len, paste_pos, paste_hold;
374330e2 185
186/*
187 * Internal prototypes.
188 */
32874aea 189static void do_paint(Context, int);
190static void erase_lots(int, int, int);
191static void swap_screen(int);
192static void update_sbar(void);
193static void deselect(void);
e1c8e0ed 194/* log session to file stuff ... */
195static FILE *lgfp = NULL;
196static void logtraffic(unsigned char c, int logmode);
374330e2 197
198/*
c83de303 199 * Retrieve a line of the screen or of the scrollback, according to
200 * whether the y coordinate is non-negative or negative
201 * (respectively).
202 */
32874aea 203unsigned long *lineptr(int y, int lineno)
204{
c83de303 205 unsigned long *line, lineattrs;
206 tree234 *whichtree;
207 int i, treeindex, oldlen;
208
209 if (y >= 0) {
210 whichtree = screen;
211 treeindex = y;
212 } else {
213 whichtree = scrollback;
214 treeindex = y + count234(scrollback);
215 }
216 line = index234(whichtree, treeindex);
217
218 /* We assume that we don't screw up and retrieve something out of range. */
c83de303 219 assert(line != NULL);
c83de303 220
221 if (line[0] != cols) {
222 /*
223 * This line is the wrong length, which probably means it
224 * hasn't been accessed since a resize. Resize it now.
225 */
226 oldlen = line[0];
32874aea 227 lineattrs = line[oldlen + 1];
c83de303 228 delpos234(whichtree, treeindex);
32874aea 229 line = srealloc(line, TSIZE * (2 + cols));
c83de303 230 line[0] = cols;
231 for (i = oldlen; i < cols; i++)
32874aea 232 line[i + 1] = ERASE_CHAR;
233 line[cols + 1] = lineattrs & LATTR_MODE;
c83de303 234 addpos234(whichtree, line, treeindex);
235 }
236
32874aea 237 return line + 1;
c83de303 238}
32874aea 239
c83de303 240#define lineptr(x) lineptr(x,__LINE__)
241/*
374330e2 242 * Set up power-on settings for the terminal.
243 */
32874aea 244static void power_on(void)
245{
4facdf84 246 curs.x = curs.y = alt_x = alt_y = savecurs.x = savecurs.y = 0;
374330e2 247 alt_t = marg_t = 0;
248 if (rows != -1)
249 alt_b = marg_b = rows - 1;
250 else
251 alt_b = marg_b = 0;
252 if (cols != -1) {
253 int i;
254 for (i = 0; i < cols; i++)
255 tabs[i] = (i % 8 == 0 ? TRUE : FALSE);
256 }
257 alt_om = dec_om = cfg.dec_om;
258 alt_wnext = wrapnext = alt_ins = insert = FALSE;
259 alt_wrap = wrap = cfg.wrap_mode;
260 alt_cset = cset = 0;
261 cset_attr[0] = cset_attr[1] = ATTR_ASCII;
262 rvideo = 0;
156686ef 263 in_vbell = FALSE;
e14a5a13 264 cursor_on = 1;
374330e2 265 save_attr = curr_attr = ATTR_DEFAULT;
0965bee0 266 term_editing = term_echoing = FALSE;
32874aea 267 ldisc_send(NULL, 0); /* cause ldisc to notice changes */
374330e2 268 app_cursor_keys = cfg.app_cursor;
269 app_keypad_keys = cfg.app_keypad;
c9def1b8 270 use_bce = cfg.bce;
271 blink_is_real = cfg.blinktext;
e14a5a13 272 erase_char = ERASE_CHAR;
374330e2 273 alt_which = 0;
274 {
275 int i;
276 for (i = 0; i < 256; i++)
277 wordness[i] = cfg.wordness[i];
278 }
4facdf84 279 if (screen) {
32874aea 280 swap_screen(1);
281 erase_lots(FALSE, TRUE, TRUE);
282 swap_screen(0);
283 erase_lots(FALSE, TRUE, TRUE);
374330e2 284 }
285}
286
287/*
288 * Force a screen update.
289 */
32874aea 290void term_update(void)
291{
374330e2 292 Context ctx;
293 ctx = get_ctx();
294 if (ctx) {
32874aea 295 if ((seen_key_event && (cfg.scroll_on_key)) ||
296 (seen_disp_event && (cfg.scroll_on_disp))) {
4facdf84 297 disptop = 0; /* return to main screen */
cabfd08c 298 seen_disp_event = seen_key_event = 0;
8ac3e544 299 update_sbar();
cabfd08c 300 }
32874aea 301 do_paint(ctx, TRUE);
302 sys_cursor(curs.x, curs.y - disptop);
303 free_ctx(ctx);
374330e2 304 }
305}
306
307/*
308 * Same as power_on(), but an external function.
309 */
32874aea 310void term_pwron(void)
311{
374330e2 312 power_on();
313 fix_cpos;
4facdf84 314 disptop = 0;
374330e2 315 deselect();
316 term_update();
317}
318
319/*
320 * Clear the scrollback.
321 */
32874aea 322void term_clrsb(void)
323{
4facdf84 324 unsigned long *line;
325 disptop = 0;
326 while ((line = delpos234(scrollback, 0)) != NULL) {
327 sfree(line);
328 }
374330e2 329 update_sbar();
330}
331
332/*
333 * Initialise the terminal.
334 */
32874aea 335void term_init(void)
336{
4facdf84 337 screen = alt_screen = scrollback = NULL;
338 disptop = 0;
374330e2 339 disptext = wanttext = NULL;
340 tabs = NULL;
341 selspace = NULL;
342 deselect();
343 rows = cols = -1;
374330e2 344 power_on();
156686ef 345 beephead = beeptail = NULL;
346 nbeeps = 0;
347 lastbeep = FALSE;
348 beep_overloaded = FALSE;
374330e2 349}
350
351/*
352 * Set up the terminal for a given size.
353 */
32874aea 354void term_size(int newrows, int newcols, int newsavelines)
355{
4facdf84 356 tree234 *newsb, *newscreen, *newalt;
357 unsigned long *newdisp, *newwant, *oldline, *line;
260f3dec 358 int i, j, ccols;
c83de303 359 int sblen;
d6832394 360 int save_alt_which = alt_which;
361
374330e2 362 if (newrows == rows && newcols == cols && newsavelines == savelines)
363 return; /* nothing to do */
364
d6832394 365 deselect();
366 swap_screen(0);
367
374330e2 368 alt_t = marg_t = 0;
369 alt_b = marg_b = newrows - 1;
370
c83de303 371 if (rows == -1) {
372 scrollback = newtree234(NULL);
373 screen = newtree234(NULL);
374 rows = 0;
375 }
376
377 /*
378 * Resize the screen and scrollback. We only need to shift
379 * lines around within our data structures, because lineptr()
380 * will take care of resizing each individual line if
381 * necessary. So:
382 *
383 * - If the new screen and the old screen differ in length, we
384 * must shunt some lines in from the scrollback or out to
385 * the scrollback.
386 *
387 * - If doing that fails to provide us with enough material to
388 * fill the new screen (i.e. the number of rows needed in
389 * the new screen exceeds the total number in the previous
390 * screen+scrollback), we must invent some blank lines to
391 * cover the gap.
392 *
393 * - Then, if the new scrollback length is less than the
394 * amount of scrollback we actually have, we must throw some
395 * away.
396 */
397 sblen = count234(scrollback);
cdd6c586 398 /* Do this loop to expand the screen if newrows > rows */
399 for (i = rows; i < newrows; i++) {
32874aea 400 if (sblen > 0) {
401 line = delpos234(scrollback, --sblen);
402 } else {
403 line = smalloc(TSIZE * (newcols + 2));
404 line[0] = newcols;
405 for (j = 0; j <= newcols; j++)
406 line[j + 1] = ERASE_CHAR;
407 }
408 addpos234(screen, line, 0);
cdd6c586 409 }
410 /* Do this loop to shrink the screen if newrows < rows */
411 for (i = newrows; i < rows; i++) {
32874aea 412 line = delpos234(screen, 0);
413 addpos234(scrollback, line, sblen++);
c83de303 414 }
415 assert(count234(screen) == newrows);
416 while (sblen > newsavelines) {
417 line = delpos234(scrollback, 0);
418 sfree(line);
419 sblen--;
c9def1b8 420 }
c83de303 421 assert(count234(scrollback) <= newsavelines);
4facdf84 422 disptop = 0;
374330e2 423
32874aea 424 newdisp = smalloc(newrows * (newcols + 1) * TSIZE);
425 for (i = 0; i < newrows * (newcols + 1); i++)
374330e2 426 newdisp[i] = ATTR_INVALID;
32874aea 427 sfree(disptext);
374330e2 428 disptext = newdisp;
429
32874aea 430 newwant = smalloc(newrows * (newcols + 1) * TSIZE);
431 for (i = 0; i < newrows * (newcols + 1); i++)
374330e2 432 newwant[i] = ATTR_INVALID;
32874aea 433 sfree(wanttext);
374330e2 434 wanttext = newwant;
435
4facdf84 436 newalt = newtree234(NULL);
32874aea 437 for (i = 0; i < newrows; i++) {
438 line = smalloc(TSIZE * (newcols + 2));
c83de303 439 line[0] = newcols;
4facdf84 440 for (j = 0; j <= newcols; j++)
32874aea 441 line[j + 1] = erase_char;
4facdf84 442 addpos234(newalt, line, i);
443 }
444 if (alt_screen) {
445 while (NULL != (line = delpos234(alt_screen, 0)))
446 sfree(line);
447 freetree234(alt_screen);
448 }
449 alt_screen = newalt;
374330e2 450
32874aea 451 sfree(selspace);
452 selspace =
453 smalloc((newrows + newsavelines) * (newcols + sizeof(sel_nl)));
374330e2 454
32874aea 455 tabs = srealloc(tabs, newcols * sizeof(*tabs));
374330e2 456 {
457 int i;
458 for (i = (cols > 0 ? cols : 0); i < newcols; i++)
459 tabs[i] = (i % 8 == 0 ? TRUE : FALSE);
460 }
461
462 if (rows > 0)
4facdf84 463 curs.y += newrows - rows;
464 if (curs.y < 0)
465 curs.y = 0;
466 if (curs.y >= newrows)
32874aea 467 curs.y = newrows - 1;
4facdf84 468 if (curs.x >= newcols)
32874aea 469 curs.x = newcols - 1;
374330e2 470 alt_x = alt_y = 0;
471 wrapnext = alt_wnext = FALSE;
472
473 rows = newrows;
474 cols = newcols;
475 savelines = newsavelines;
476 fix_cpos;
477
d6832394 478 swap_screen(save_alt_which);
479
374330e2 480 update_sbar();
481 term_update();
482}
483
484/*
485 * Swap screens.
486 */
32874aea 487static void swap_screen(int which)
488{
374330e2 489 int t;
4facdf84 490 tree234 *ttr;
374330e2 491
492 if (which == alt_which)
493 return;
494
495 alt_which = which;
496
32874aea 497 ttr = alt_screen;
498 alt_screen = screen;
499 screen = ttr;
500 t = curs.x;
501 curs.x = alt_x;
502 alt_x = t;
503 t = curs.y;
504 curs.y = alt_y;
505 alt_y = t;
506 t = marg_t;
507 marg_t = alt_t;
508 alt_t = t;
509 t = marg_b;
510 marg_b = alt_b;
511 alt_b = t;
512 t = dec_om;
513 dec_om = alt_om;
514 alt_om = t;
515 t = wrap;
516 wrap = alt_wrap;
517 alt_wrap = t;
518 t = wrapnext;
519 wrapnext = alt_wnext;
520 alt_wnext = t;
521 t = insert;
522 insert = alt_ins;
523 alt_ins = t;
524 t = cset;
525 cset = alt_cset;
526 alt_cset = t;
374330e2 527
528 fix_cpos;
529}
530
531/*
374330e2 532 * Update the scroll bar.
533 */
32874aea 534static void update_sbar(void)
535{
260f3dec 536 int nscroll;
374330e2 537
4facdf84 538 nscroll = count234(scrollback);
539
32874aea 540 set_sbar(nscroll + rows, nscroll + disptop, rows);
374330e2 541}
542
543/*
544 * Check whether the region bounded by the two pointers intersects
545 * the scroll region, and de-select the on-screen selection if so.
546 */
32874aea 547static void check_selection(pos from, pos to)
548{
4facdf84 549 if (poslt(from, selend) && poslt(selstart, to))
374330e2 550 deselect();
551}
552
553/*
554 * Scroll the screen. (`lines' is +ve for scrolling forward, -ve
555 * for backward.) `sb' is TRUE if the scrolling is permitted to
556 * affect the scrollback buffer.
a4450583 557 *
558 * NB this function invalidates all pointers into lines of the
559 * screen data structures. In particular, you MUST call fix_cpos
560 * after calling scroll() and before doing anything else that
561 * uses the cpos shortcut pointer.
374330e2 562 */
32874aea 563static void scroll(int topline, int botline, int lines, int sb)
564{
4facdf84 565 unsigned long *line, *line2;
566 int i;
c9def1b8 567
7af9fa7f 568 if (topline != 0 || alt_which != 0)
4facdf84 569 sb = FALSE;
570
571 if (lines < 0) {
572 while (lines < 0) {
573 line = delpos234(screen, botline);
574 for (i = 0; i < cols; i++)
32874aea 575 line[i + 1] = erase_char;
576 line[cols + 1] = 0;
4facdf84 577 addpos234(screen, line, topline);
578
579 if (selstart.y >= topline && selstart.y <= botline) {
580 selstart.y++;
581 if (selstart.y > botline) {
582 selstart.y = botline;
583 selstart.x = 0;
c9def1b8 584 }
c9def1b8 585 }
4facdf84 586 if (selend.y >= topline && selend.y <= botline) {
587 selend.y++;
588 if (selend.y > botline) {
589 selend.y = botline;
590 selend.x = 0;
c9def1b8 591 }
c9def1b8 592 }
c9def1b8 593
4facdf84 594 lines++;
c9def1b8 595 }
374330e2 596 } else {
4facdf84 597 while (lines > 0) {
598 line = delpos234(screen, topline);
c83de303 599 if (sb && savelines > 0) {
4facdf84 600 int sblen = count234(scrollback);
601 /*
602 * We must add this line to the scrollback. We'll
603 * remove a line from the top of the scrollback to
604 * replace it, or allocate a new one if the
605 * scrollback isn't full.
606 */
c83de303 607 if (sblen == savelines) {
4facdf84 608 sblen--, line2 = delpos234(scrollback, 0);
c83de303 609 } else {
32874aea 610 line2 = smalloc(TSIZE * (cols + 2));
c83de303 611 line2[0] = cols;
612 }
4facdf84 613 addpos234(scrollback, line, sblen);
614 line = line2;
615 }
616 for (i = 0; i < cols; i++)
32874aea 617 line[i + 1] = erase_char;
618 line[cols + 1] = 0;
4facdf84 619 addpos234(screen, line, botline);
620
621 if (selstart.y >= topline && selstart.y <= botline) {
622 selstart.y--;
623 if (selstart.y < topline) {
624 selstart.y = topline;
625 selstart.x = 0;
626 }
627 }
628 if (selend.y >= topline && selend.y <= botline) {
629 selend.y--;
630 if (selend.y < topline) {
631 selend.y = topline;
632 selend.x = 0;
633 }
634 }
635
636 lines--;
c9def1b8 637 }
374330e2 638 }
374330e2 639}
640
641/*
642 * Move the cursor to a given position, clipping at boundaries. We
643 * may or may not want to clip at the scroll margin: marg_clip is 0
644 * not to, 1 to disallow _passing_ the margins, and 2 to disallow
645 * even _being_ outside the margins.
646 */
32874aea 647static void move(int x, int y, int marg_clip)
648{
374330e2 649 if (x < 0)
650 x = 0;
651 if (x >= cols)
32874aea 652 x = cols - 1;
374330e2 653 if (marg_clip) {
4facdf84 654 if ((curs.y >= marg_t || marg_clip == 2) && y < marg_t)
374330e2 655 y = marg_t;
4facdf84 656 if ((curs.y <= marg_b || marg_clip == 2) && y > marg_b)
374330e2 657 y = marg_b;
658 }
659 if (y < 0)
660 y = 0;
661 if (y >= rows)
32874aea 662 y = rows - 1;
4facdf84 663 curs.x = x;
664 curs.y = y;
374330e2 665 fix_cpos;
666 wrapnext = FALSE;
667}
668
669/*
670 * Save or restore the cursor and SGR mode.
671 */
32874aea 672static void save_cursor(int save)
673{
374330e2 674 if (save) {
4facdf84 675 savecurs = curs;
374330e2 676 save_attr = curr_attr;
677 save_cset = cset;
678 save_csattr = cset_attr[cset];
679 } else {
4facdf84 680 curs = savecurs;
c9def1b8 681 /* Make sure the window hasn't shrunk since the save */
32874aea 682 if (curs.x >= cols)
683 curs.x = cols - 1;
684 if (curs.y >= rows)
685 curs.y = rows - 1;
c9def1b8 686
374330e2 687 curr_attr = save_attr;
688 cset = save_cset;
689 cset_attr[cset] = save_csattr;
690 fix_cpos;
32874aea 691 if (use_bce)
692 erase_char = (' ' | (curr_attr & (ATTR_FGMASK | ATTR_BGMASK)));
374330e2 693 }
694}
695
696/*
697 * Erase a large portion of the screen: the whole screen, or the
698 * whole line, or parts thereof.
699 */
32874aea 700static void erase_lots(int line_only, int from_begin, int to_end)
701{
260f3dec 702 pos start, end;
4facdf84 703 int erase_lattr;
704 unsigned long *ldata;
374330e2 705
706 if (line_only) {
4facdf84 707 start.y = curs.y;
708 start.x = 0;
709 end.y = curs.y + 1;
710 end.x = 0;
711 erase_lattr = FALSE;
374330e2 712 } else {
4facdf84 713 start.y = 0;
714 start.x = 0;
715 end.y = rows;
716 end.x = 0;
717 erase_lattr = TRUE;
718 }
719 if (!from_begin) {
720 start = curs;
374330e2 721 }
4facdf84 722 if (!to_end) {
723 end = curs;
724 }
32874aea 725 check_selection(start, end);
e14a5a13 726
727 /* Clear screen also forces a full window redraw, just in case. */
4facdf84 728 if (start.y == 0 && start.x == 0 && end.y == rows)
32874aea 729 term_invalidate();
e14a5a13 730
4facdf84 731 ldata = lineptr(start.y);
732 while (poslt(start, end)) {
733 if (start.y == cols && !erase_lattr)
734 ldata[start.x] &= ~ATTR_WRAPPED;
735 else
736 ldata[start.x] = erase_char;
c83de303 737 if (incpos(start) && start.y < rows)
4facdf84 738 ldata = lineptr(start.y);
739 }
374330e2 740}
741
742/*
743 * Insert or delete characters within the current line. n is +ve if
744 * insertion is desired, and -ve for deletion.
745 */
32874aea 746static void insch(int n)
747{
374330e2 748 int dir = (n < 0 ? -1 : +1);
749 int m;
4facdf84 750 pos cursplus;
751 unsigned long *ldata;
374330e2 752
753 n = (n < 0 ? -n : n);
4facdf84 754 if (n > cols - curs.x)
755 n = cols - curs.x;
756 m = cols - curs.x - n;
757 cursplus.y = curs.y;
758 cursplus.x = curs.x + n;
32874aea 759 check_selection(curs, cursplus);
4facdf84 760 ldata = lineptr(curs.y);
374330e2 761 if (dir < 0) {
32874aea 762 memmove(ldata + curs.x, ldata + curs.x + n, m * TSIZE);
374330e2 763 while (n--)
4facdf84 764 ldata[curs.x + m++] = erase_char;
374330e2 765 } else {
32874aea 766 memmove(ldata + curs.x + n, ldata + curs.x, m * TSIZE);
374330e2 767 while (n--)
4facdf84 768 ldata[curs.x + n] = erase_char;
374330e2 769 }
770}
771
772/*
773 * Toggle terminal mode `mode' to state `state'. (`query' indicates
774 * whether the mode is a DEC private one or a normal one.)
775 */
32874aea 776static void toggle_mode(int mode, int query, int state)
777{
7594731b 778 long ticks;
01c034ad 779
32874aea 780 if (query)
781 switch (mode) {
782 case 1: /* application cursor keys */
783 app_cursor_keys = state;
784 break;
785 case 2: /* VT52 mode */
786 vt52_mode = !state;
787 break;
788 case 3: /* 80/132 columns */
789 deselect();
790 request_resize(state ? 132 : 80, rows, 1);
791 reset_132 = state;
792 break;
793 case 5: /* reverse video */
794 /*
795 * Toggle reverse video. If we receive an OFF within the
796 * visual bell timeout period after an ON, we trigger an
797 * effective visual bell, so that ESC[?5hESC[?5l will
798 * always be an actually _visible_ visual bell.
799 */
800 ticks = GetTickCount();
801 if (rvideo && !state && /* we're turning it off */
802 ticks < rvbell_timeout) { /* and it's not long since it was turned on */
803 in_vbell = TRUE; /* we may clear rvideo but we set in_vbell */
804 if (vbell_timeout < rvbell_timeout) /* don't move vbell end forward */
805 vbell_timeout = rvbell_timeout; /* vbell end is at least then */
806 } else if (!rvideo && state) {
807 /* This is an ON, so we notice the time and save it. */
808 rvbell_timeout = ticks + VBELL_TIMEOUT;
809 }
810 rvideo = state;
811 seen_disp_event = TRUE;
812 if (state)
813 term_update();
814 break;
815 case 6: /* DEC origin mode */
816 dec_om = state;
817 break;
818 case 7: /* auto wrap */
819 wrap = state;
820 break;
821 case 8: /* auto key repeat */
822 repeat_off = !state;
823 break;
824 case 10: /* set local edit mode */
825 term_editing = state;
826 ldisc_send(NULL, 0); /* cause ldisc to notice changes */
827 break;
828 case 25: /* enable/disable cursor */
829 compatibility2(OTHER, VT220);
830 cursor_on = state;
831 seen_disp_event = TRUE;
832 break;
833 case 47: /* alternate screen */
834 compatibility(OTHER);
835 deselect();
836 swap_screen(state);
837 disptop = 0;
838 break;
839 case 1000: /* xterm mouse 1 */
840 xterm_mouse = state ? 1 : 0;
841 set_raw_mouse_mode(state);
842 break;
843 case 1002: /* xterm mouse 2 */
844 xterm_mouse = state ? 2 : 0;
845 set_raw_mouse_mode(state);
846 break;
847 } else
848 switch (mode) {
849 case 4: /* set insert mode */
850 compatibility(VT102);
851 insert = state;
852 break;
853 case 12: /* set echo mode */
854 term_echoing = !state;
855 ldisc_send(NULL, 0); /* cause ldisc to notice changes */
856 break;
857 case 20: /* Return sends ... */
858 cr_lf_return = state;
859 break;
7594731b 860 }
374330e2 861}
862
863/*
864 * Process an OSC sequence: set window title or icon name.
865 */
32874aea 866static void do_osc(void)
867{
374330e2 868 if (osc_w) {
869 while (osc_strlen--)
32874aea 870 wordness[(unsigned char) osc_string[osc_strlen]] = esc_args[0];
374330e2 871 } else {
872 osc_string[osc_strlen] = '\0';
873 switch (esc_args[0]) {
874 case 0:
875 case 1:
32874aea 876 set_icon(osc_string);
374330e2 877 if (esc_args[0] == 1)
878 break;
879 /* fall through: parameter 0 means set both */
880 case 2:
881 case 21:
32874aea 882 set_title(osc_string);
374330e2 883 break;
884 }
885 }
886}
887
888/*
889 * Remove everything currently in `inbuf' and stick it up on the
890 * in-memory display. There's a big state machine in here to
891 * process escape sequences...
892 */
32874aea 893void term_out(void)
894{
c9def1b8 895 int c, inbuf_reap;
896
32874aea 897 for (inbuf_reap = 0; inbuf_reap < inbuf_head; inbuf_reap++) {
898 c = inbuf[inbuf_reap];
374330e2 899
c9def1b8 900 /*
32874aea 901 * Optionally log the session traffic to a file. Useful for
902 * debugging and possibly also useful for actual logging.
903 */
904 logtraffic((unsigned char) c, LGTYP_DEBUG);
e1c8e0ed 905
e14a5a13 906 /* Note only VT220+ are 8-bit VT102 is seven bit, it shouldn't even
907 * be able to display 8-bit characters, but I'll let that go 'cause
908 * of i18n.
909 */
32874aea 910 if (((c & 0x60) == 0 || c == '\177') &&
911 termstate < DO_CTRLS && ((c & 0x80) == 0 || has_compat(VT220))) {
374330e2 912 switch (c) {
913 case '\005': /* terminal type query */
e14a5a13 914 /* Strictly speaking this is VT100 but a VT100 defaults to
915 * no response. Other terminals respond at their option.
916 *
917 * Don't put a CR in the default string as this tends to
918 * upset some weird software.
919 *
920 * An xterm returns "xterm" (5 characters)
921 */
32874aea 922 compatibility(ANSIMIN);
e7fbcdd8 923 {
924 char abuf[256], *s, *d;
32874aea 925 int state = 0;
926 for (s = cfg.answerback, d = abuf; *s; s++) {
927 if (state) {
928 if (*s >= 'a' && *s <= 'z')
929 *d++ = (*s - ('a' - 1));
930 else if ((*s >= '@' && *s <= '_') ||
931 *s == '?' || (*s & 0x80))
932 *d++ = ('@' ^ *s);
933 else if (*s == '~')
934 *d++ = '^';
935 state = 0;
936 } else if (*s == '^') {
937 state = 1;
938 } else
939 *d++ = xlat_kbd2tty((unsigned char) *s);
e7fbcdd8 940 }
32874aea 941 ldisc_send(abuf, d - abuf);
e7fbcdd8 942 }
374330e2 943 break;
944 case '\007':
156686ef 945 {
946 struct beeptime *newbeep;
947 long ticks;
948
949 ticks = GetTickCount();
156686ef 950
951 if (!beep_overloaded) {
952 newbeep = smalloc(sizeof(struct beeptime));
953 newbeep->ticks = ticks;
954 newbeep->next = NULL;
955 if (!beephead)
956 beephead = newbeep;
957 else
958 beeptail->next = newbeep;
959 beeptail = newbeep;
960 nbeeps++;
961 }
962
963 /*
964 * Throw out any beeps that happened more than
965 * t seconds ago.
966 */
967 while (beephead &&
7f4968e6 968 beephead->ticks < ticks - cfg.bellovl_t) {
156686ef 969 struct beeptime *tmp = beephead;
970 beephead = tmp->next;
156686ef 971 sfree(tmp);
972 if (!beephead)
973 beeptail = NULL;
974 nbeeps--;
975 }
976
977 if (cfg.bellovl && beep_overloaded &&
32874aea 978 ticks - lastbeep >= cfg.bellovl_s) {
156686ef 979 /*
980 * If we're currently overloaded and the
981 * last beep was more than s seconds ago,
982 * leave overload mode.
983 */
156686ef 984 beep_overloaded = FALSE;
985 } else if (cfg.bellovl && !beep_overloaded &&
986 nbeeps >= cfg.bellovl_n) {
987 /*
988 * Now, if we have n or more beeps
989 * remaining in the queue, go into overload
990 * mode.
991 */
156686ef 992 beep_overloaded = TRUE;
993 }
994 lastbeep = ticks;
995
996 /*
997 * Perform an actual beep if we're not overloaded.
998 */
32874aea 999 if ((!cfg.bellovl || !beep_overloaded)
1000 && cfg.beep != 0) {
156686ef 1001 if (cfg.beep != 2)
1002 beep(cfg.beep);
32874aea 1003 else if (cfg.beep == 2) {
156686ef 1004 in_vbell = TRUE;
1005 vbell_timeout = ticks + VBELL_TIMEOUT;
1006 term_update();
1007 }
1008 }
4facdf84 1009 disptop = 0;
156686ef 1010 }
374330e2 1011 break;
1012 case '\b':
967a161e 1013 if (curs.x == 0 && (curs.y == 0 || wrap == 0));
4facdf84 1014 else if (curs.x == 0 && curs.y > 0)
32874aea 1015 curs.x = cols - 1, curs.y--;
374330e2 1016 else if (wrapnext)
1017 wrapnext = FALSE;
1018 else
4facdf84 1019 curs.x--;
374330e2 1020 fix_cpos;
cabfd08c 1021 seen_disp_event = TRUE;
374330e2 1022 break;
1023 case '\016':
32874aea 1024 compatibility(VT100);
374330e2 1025 cset = 1;
1026 break;
1027 case '\017':
32874aea 1028 compatibility(VT100);
374330e2 1029 cset = 0;
1030 break;
1031 case '\033':
32874aea 1032 if (vt52_mode)
1033 termstate = VT52_ESC;
e14a5a13 1034 else {
1035 compatibility(ANSIMIN);
1036 termstate = SEEN_ESC;
1037 }
374330e2 1038 break;
1039 case 0233:
32874aea 1040 compatibility(VT220);
374330e2 1041 termstate = SEEN_CSI;
1042 esc_nargs = 1;
1043 esc_args[0] = ARG_DEFAULT;
1044 esc_query = FALSE;
1045 break;
1046 case 0235:
32874aea 1047 compatibility(VT220);
374330e2 1048 termstate = SEEN_OSC;
1049 esc_args[0] = 0;
1050 break;
1051 case '\r':
4facdf84 1052 curs.x = 0;
374330e2 1053 wrapnext = FALSE;
1054 fix_cpos;
cabfd08c 1055 seen_disp_event = TRUE;
c9def1b8 1056 paste_hold = 0;
32874aea 1057 logtraffic((unsigned char) c, LGTYP_ASCII);
374330e2 1058 break;
374330e2 1059 case '\014':
1a837633 1060 if (has_compat(SCOANSI)) {
1061 move(0, 0, 0);
1062 erase_lots(FALSE, FALSE, TRUE);
1063 disptop = 0;
1064 wrapnext = FALSE;
1065 seen_disp_event = 1;
1066 break;
1067 }
1068 case '\013':
32874aea 1069 compatibility(VT100);
374330e2 1070 case '\n':
4facdf84 1071 if (curs.y == marg_b)
32874aea 1072 scroll(marg_t, marg_b, 1, TRUE);
1073 else if (curs.y < rows - 1)
4facdf84 1074 curs.y++;
cabfd08c 1075 if (cfg.lfhascr)
4facdf84 1076 curs.x = 0;
374330e2 1077 fix_cpos;
1078 wrapnext = FALSE;
cabfd08c 1079 seen_disp_event = 1;
c9def1b8 1080 paste_hold = 0;
32874aea 1081 logtraffic((unsigned char) c, LGTYP_ASCII);
374330e2 1082 break;
1083 case '\t':
374330e2 1084 {
4facdf84 1085 pos old_curs = curs;
1086 unsigned long *ldata = lineptr(curs.y);
c9def1b8 1087
1088 do {
4facdf84 1089 curs.x++;
32874aea 1090 } while (curs.x < cols - 1 && !tabs[curs.x]);
c9def1b8 1091
32874aea 1092 if ((ldata[cols] & LATTR_MODE) != LATTR_NORM) {
1093 if (curs.x >= cols / 2)
1094 curs.x = cols / 2 - 1;
1095 } else {
4facdf84 1096 if (curs.x >= cols)
32874aea 1097 curs.x = cols - 1;
c9def1b8 1098 }
1099
374330e2 1100 fix_cpos;
32874aea 1101 check_selection(old_curs, curs);
374330e2 1102 }
cabfd08c 1103 seen_disp_event = TRUE;
374330e2 1104 break;
32874aea 1105 case '\177': /* Destructive backspace
1106 This does nothing on a real VT100 */
1107 compatibility(OTHER);
1108 if (curs.x && !wrapnext)
1109 curs.x--;
ec55b220 1110 wrapnext = FALSE;
1111 fix_cpos;
1112 *cpos = (' ' | curr_attr | ATTR_ASCII);
1113 break;
cabfd08c 1114 }
32874aea 1115 } else
1116 switch (termstate) {
1117 case TOPLEVEL:
1118 /* Only graphic characters get this far, ctrls are stripped above */
1119 if (wrapnext && wrap) {
1120 cpos[1] |= ATTR_WRAPPED;
1121 if (curs.y == marg_b)
1122 scroll(marg_t, marg_b, 1, TRUE);
1123 else if (curs.y < rows - 1)
1124 curs.y++;
1125 curs.x = 0;
1126 fix_cpos;
1127 wrapnext = FALSE;
374330e2 1128 }
32874aea 1129 if (insert)
1130 insch(1);
1131 if (selstate != NO_SELECTION) {
1132 pos cursplus = curs;
1133 incpos(cursplus);
1134 check_selection(curs, cursplus);
374330e2 1135 }
32874aea 1136 switch (cset_attr[cset]) {
1137 /*
1138 * Linedraw characters are different from 'ESC ( B'
1139 * only for a small range. For ones outside that
1140 * range, make sure we use the same font as well as
1141 * the same encoding.
1142 */
1143 case ATTR_LINEDRW:
1144 if (c < 0x5f || c > 0x7F)
1145 *cpos++ =
1146 xlat_tty2scr((unsigned char) c) | curr_attr |
1147 ATTR_ASCII;
1148 else if (c == 0x5F)
1149 *cpos++ = ' ' | curr_attr | ATTR_ASCII;
1150 else
1151 *cpos++ =
1152 ((unsigned char) c) | curr_attr | ATTR_LINEDRW;
1153 break;
1154 case ATTR_GBCHR:
1155 /* If UK-ASCII, make the '#' a LineDraw Pound */
1156 if (c == '#') {
1157 *cpos++ = '}' | curr_attr | ATTR_LINEDRW;
1158 break;
1159 }
1160 /*FALLTHROUGH*/ default:
1161 *cpos = xlat_tty2scr((unsigned char) c) | curr_attr |
1162 (c <= 0x7F ? cset_attr[cset] : ATTR_ASCII);
1163 logtraffic((unsigned char) c, LGTYP_ASCII);
1164 cpos++;
1165 break;
374330e2 1166 }
32874aea 1167 curs.x++;
1168 if (curs.x == cols) {
1169 cpos--;
1170 curs.x--;
1171 wrapnext = TRUE;
e14a5a13 1172 }
32874aea 1173 seen_disp_event = 1;
374330e2 1174 break;
32874aea 1175
1176 case IGNORE_NEXT:
1177 termstate = TOPLEVEL;
374330e2 1178 break;
32874aea 1179 case OSC_MAYBE_ST:
1180 /*
1181 * This state is virtually identical to SEEN_ESC, with the
1182 * exception that we have an OSC sequence in the pipeline,
1183 * and _if_ we see a backslash, we process it.
1184 */
1185 if (c == '\\') {
1186 do_osc();
1187 termstate = TOPLEVEL;
1188 break;
e14a5a13 1189 }
32874aea 1190 /* else fall through */
1191 case SEEN_ESC:
1192 termstate = TOPLEVEL;
1193 switch (c) {
1194 case ' ': /* some weird sequence? */
1195 compatibility(VT220);
1196 termstate = IGNORE_NEXT;
1197 break;
1198 case '[': /* enter CSI mode */
1199 termstate = SEEN_CSI;
1200 esc_nargs = 1;
1201 esc_args[0] = ARG_DEFAULT;
1202 esc_query = FALSE;
1203 break;
1204 case ']': /* xterm escape sequences */
1205 /* Compatibility is nasty here, xterm, linux, decterm yuk! */
1206 compatibility(OTHER);
1207 termstate = SEEN_OSC;
1208 esc_args[0] = 0;
1209 break;
1210 case '(': /* should set GL */
1211 compatibility(VT100);
1212 termstate = SET_GL;
1213 break;
1214 case ')': /* should set GR */
1215 compatibility(VT100);
1216 termstate = SET_GR;
1217 break;
1218 case '7': /* save cursor */
1219 compatibility(VT100);
1220 save_cursor(TRUE);
1221 break;
1222 case '8': /* restore cursor */
1223 compatibility(VT100);
1224 save_cursor(FALSE);
1225 seen_disp_event = TRUE;
1226 break;
1227 case '=':
1228 compatibility(VT100);
1229 app_keypad_keys = TRUE;
1230 break;
1231 case '>':
1232 compatibility(VT100);
1233 app_keypad_keys = FALSE;
1234 break;
1235 case 'D': /* exactly equivalent to LF */
1236 compatibility(VT100);
1237 if (curs.y == marg_b)
1238 scroll(marg_t, marg_b, 1, TRUE);
1239 else if (curs.y < rows - 1)
1240 curs.y++;
1241 fix_cpos;
1242 wrapnext = FALSE;
1243 seen_disp_event = TRUE;
1244 break;
1245 case 'E': /* exactly equivalent to CR-LF */
1246 compatibility(VT100);
1247 curs.x = 0;
1248 if (curs.y == marg_b)
1249 scroll(marg_t, marg_b, 1, TRUE);
1250 else if (curs.y < rows - 1)
1251 curs.y++;
1252 fix_cpos;
1253 wrapnext = FALSE;
1254 seen_disp_event = TRUE;
1255 break;
1256 case 'M': /* reverse index - backwards LF */
1257 compatibility(VT100);
1258 if (curs.y == marg_t)
1259 scroll(marg_t, marg_b, -1, TRUE);
1260 else if (curs.y > 0)
1261 curs.y--;
1262 fix_cpos;
1263 wrapnext = FALSE;
1264 seen_disp_event = TRUE;
1265 break;
1266 case 'Z': /* terminal type query */
1267 compatibility(VT100);
1268 ldisc_send(id_string, strlen(id_string));
1269 break;
1270 case 'c': /* restore power-on settings */
1271 compatibility(VT100);
1272 power_on();
1273 if (reset_132) {
1274 request_resize(80, rows, 1);
1275 reset_132 = 0;
374330e2 1276 }
32874aea 1277 fix_cpos;
1278 disptop = 0;
1279 seen_disp_event = TRUE;
1280 break;
1281 case '#': /* ESC # 8 fills screen with Es :-) */
1282 compatibility(VT100);
1283 termstate = SEEN_ESCHASH;
1284 break;
1285 case 'H': /* set a tab */
1286 compatibility(VT100);
1287 tabs[curs.x] = TRUE;
1288 break;
374330e2 1289 }
1290 break;
32874aea 1291 case SEEN_CSI:
1292 termstate = TOPLEVEL; /* default */
1293 if (isdigit(c)) {
1294 if (esc_nargs <= ARGS_MAX) {
1295 if (esc_args[esc_nargs - 1] == ARG_DEFAULT)
1296 esc_args[esc_nargs - 1] = 0;
1297 esc_args[esc_nargs - 1] =
1298 10 * esc_args[esc_nargs - 1] + c - '0';
1299 }
1300 termstate = SEEN_CSI;
1301 } else if (c == ';') {
1302 if (++esc_nargs <= ARGS_MAX)
1303 esc_args[esc_nargs - 1] = ARG_DEFAULT;
1304 termstate = SEEN_CSI;
1305 } else if (c < '@') {
1306 if (esc_query)
1307 esc_query = -1;
1308 else if (c == '?')
1309 esc_query = TRUE;
1310 else
1311 esc_query = c;
1312 termstate = SEEN_CSI;
1313 } else
1314 switch (ANSI(c, esc_query)) {
1315 case 'A': /* move up N lines */
1316 move(curs.x, curs.y - def(esc_args[0], 1), 1);
1317 seen_disp_event = TRUE;
1318 break;
1319 case 'e': /* move down N lines */
1320 compatibility(ANSI);
1321 case 'B':
1322 move(curs.x, curs.y + def(esc_args[0], 1), 1);
1323 seen_disp_event = TRUE;
1324 break;
1325 case 'a': /* move right N cols */
1326 compatibility(ANSI);
1327 case ANSI('c', '>'): /* report xterm version */
1328 compatibility(OTHER);
1329 /* this reports xterm version 136 so that VIM can
1330 use the drag messages from the mouse reporting */
1331 ldisc_send("\033[>0;136;0c", 11);
1332 break;
1333 case 'C':
1334 move(curs.x + def(esc_args[0], 1), curs.y, 1);
1335 seen_disp_event = TRUE;
1336 break;
1337 case 'D': /* move left N cols */
1338 move(curs.x - def(esc_args[0], 1), curs.y, 1);
1339 seen_disp_event = TRUE;
1340 break;
1341 case 'E': /* move down N lines and CR */
1342 compatibility(ANSI);
1343 move(0, curs.y + def(esc_args[0], 1), 1);
1344 seen_disp_event = TRUE;
1345 break;
1346 case 'F': /* move up N lines and CR */
1347 compatibility(ANSI);
1348 move(0, curs.y - def(esc_args[0], 1), 1);
1349 seen_disp_event = TRUE;
1350 break;
1351 case 'G':
1352 case '`': /* set horizontal posn */
1353 compatibility(ANSI);
1354 move(def(esc_args[0], 1) - 1, curs.y, 0);
1355 seen_disp_event = TRUE;
1356 break;
1357 case 'd': /* set vertical posn */
1358 compatibility(ANSI);
1359 move(curs.x,
1360 (dec_om ? marg_t : 0) + def(esc_args[0],
1361 1) - 1,
1362 (dec_om ? 2 : 0));
1363 seen_disp_event = TRUE;
1364 break;
1365 case 'H':
1366 case 'f': /* set horz and vert posns at once */
1367 if (esc_nargs < 2)
1368 esc_args[1] = ARG_DEFAULT;
1369 move(def(esc_args[1], 1) - 1,
1370 (dec_om ? marg_t : 0) + def(esc_args[0],
1371 1) - 1,
1372 (dec_om ? 2 : 0));
1373 seen_disp_event = TRUE;
1374 break;
1375 case 'J': /* erase screen or parts of it */
1376 {
1377 unsigned int i = def(esc_args[0], 0) + 1;
1378 if (i > 3)
1379 i = 0;
1380 erase_lots(FALSE, !!(i & 2), !!(i & 1));
1381 }
1382 disptop = 0;
1383 seen_disp_event = TRUE;
1384 break;
1385 case 'K': /* erase line or parts of it */
1386 {
1387 unsigned int i = def(esc_args[0], 0) + 1;
1388 if (i > 3)
1389 i = 0;
1390 erase_lots(TRUE, !!(i & 2), !!(i & 1));
1391 }
1392 seen_disp_event = TRUE;
1393 break;
1394 case 'L': /* insert lines */
1395 compatibility(VT102);
1396 if (curs.y <= marg_b)
1397 scroll(curs.y, marg_b, -def(esc_args[0], 1),
1398 FALSE);
1399 fix_cpos;
1400 seen_disp_event = TRUE;
1401 break;
1402 case 'M': /* delete lines */
1403 compatibility(VT102);
1404 if (curs.y <= marg_b)
1405 scroll(curs.y, marg_b, def(esc_args[0], 1),
1406 TRUE);
1407 fix_cpos;
1408 seen_disp_event = TRUE;
1409 break;
1410 case '@': /* insert chars */
1411 /* XXX VTTEST says this is vt220, vt510 manual says vt102 */
1412 compatibility(VT102);
1413 insch(def(esc_args[0], 1));
1414 seen_disp_event = TRUE;
1415 break;
1416 case 'P': /* delete chars */
1417 compatibility(VT102);
1418 insch(-def(esc_args[0], 1));
1419 seen_disp_event = TRUE;
1420 break;
1421 case 'c': /* terminal type query */
1422 compatibility(VT100);
1423 /* This is the response for a VT102 */
1424 ldisc_send(id_string, strlen(id_string));
1425 break;
1426 case 'n': /* cursor position query */
1427 if (esc_args[0] == 6) {
1428 char buf[32];
1429 sprintf(buf, "\033[%d;%dR", curs.y + 1,
1430 curs.x + 1);
1431 ldisc_send(buf, strlen(buf));
1432 } else if (esc_args[0] == 5) {
1433 ldisc_send("\033[0n", 4);
1434 }
1435 break;
1436 case 'h': /* toggle modes to high */
1437 case ANSI_QUE('h'):
1438 compatibility(VT100);
1439 {
1440 int i;
1441 for (i = 0; i < esc_nargs; i++)
1442 toggle_mode(esc_args[i], esc_query, TRUE);
1443 }
1444 break;
1445 case 'l': /* toggle modes to low */
1446 case ANSI_QUE('l'):
1447 compatibility(VT100);
1448 {
1449 int i;
1450 for (i = 0; i < esc_nargs; i++)
1451 toggle_mode(esc_args[i], esc_query, FALSE);
1452 }
1453 break;
1454 case 'g': /* clear tabs */
1455 compatibility(VT100);
1456 if (esc_nargs == 1) {
1457 if (esc_args[0] == 0) {
1458 tabs[curs.x] = FALSE;
1459 } else if (esc_args[0] == 3) {
1460 int i;
1461 for (i = 0; i < cols; i++)
1462 tabs[i] = FALSE;
1463 }
1464 }
1465 break;
1466 case 'r': /* set scroll margins */
1467 compatibility(VT100);
1468 if (esc_nargs <= 2) {
1469 int top, bot;
1470 top = def(esc_args[0], 1) - 1;
1471 bot = (esc_nargs <= 1
1472 || esc_args[1] ==
1473 0 ? rows : def(esc_args[1], rows)) - 1;
1474 if (bot >= rows)
1475 bot = rows - 1;
1476 /* VTTEST Bug 9 - if region is less than 2 lines
1477 * don't change region.
1478 */
1479 if (bot - top > 0) {
1480 marg_t = top;
1481 marg_b = bot;
1482 curs.x = 0;
1483 /*
1484 * I used to think the cursor should be
1485 * placed at the top of the newly marginned
1486 * area. Apparently not: VMS TPU falls over
1487 * if so.
1488 *
1489 * Well actually it should for Origin mode - RDB
1490 */
1491 curs.y = (dec_om ? marg_t : 0);
1492 fix_cpos;
1493 seen_disp_event = TRUE;
1494 }
1495 }
1496 break;
1497 case 'm': /* set graphics rendition */
1498 {
1499 /*
1500 * A VT100 without the AVO only had one attribute, either
1501 * underline or reverse video depending on the cursor type,
1502 * this was selected by CSI 7m.
1503 *
1504 * case 2:
1505 * This is DIM on the VT100-AVO and VT102
1506 * case 5:
1507 * This is BLINK on the VT100-AVO and VT102+
1508 * case 8:
1509 * This is INVIS on the VT100-AVO and VT102
1510 * case 21:
1511 * This like 22 disables BOLD, DIM and INVIS
1512 *
1513 * The ANSI colours appear on any terminal that has colour
1514 * (obviously) but the interaction between sgr0 and the
1515 * colours varies but is usually related to the background
1516 * colour erase item.
1517 * The interaction between colour attributes and the mono
1518 * ones is also very implementation dependent.
1519 *
1520 * The 39 and 49 attributes are likely to be unimplemented.
1521 */
1522 int i;
1523 for (i = 0; i < esc_nargs; i++) {
1524 switch (def(esc_args[i], 0)) {
1525 case 0: /* restore defaults */
1526 curr_attr = ATTR_DEFAULT;
1527 break;
1528 case 1: /* enable bold */
1529 compatibility(VT100AVO);
1530 curr_attr |= ATTR_BOLD;
1531 break;
1532 case 21: /* (enable double underline) */
1533 compatibility(OTHER);
1534 case 4: /* enable underline */
1535 compatibility(VT100AVO);
1536 curr_attr |= ATTR_UNDER;
1537 break;
1538 case 5: /* enable blink */
1539 compatibility(VT100AVO);
1540 curr_attr |= ATTR_BLINK;
1541 break;
1542 case 7: /* enable reverse video */
1543 curr_attr |= ATTR_REVERSE;
1544 break;
1545 case 22: /* disable bold */
1546 compatibility2(OTHER, VT220);
1547 curr_attr &= ~ATTR_BOLD;
1548 break;
1549 case 24: /* disable underline */
1550 compatibility2(OTHER, VT220);
1551 curr_attr &= ~ATTR_UNDER;
1552 break;
1553 case 25: /* disable blink */
1554 compatibility2(OTHER, VT220);
1555 curr_attr &= ~ATTR_BLINK;
1556 break;
1557 case 27: /* disable reverse video */
1558 compatibility2(OTHER, VT220);
1559 curr_attr &= ~ATTR_REVERSE;
1560 break;
1561 case 30:
1562 case 31:
1563 case 32:
1564 case 33:
1565 case 34:
1566 case 35:
1567 case 36:
1568 case 37:
1569 /* foreground */
1570 curr_attr &= ~ATTR_FGMASK;
1571 curr_attr |=
1572 (esc_args[i] - 30) << ATTR_FGSHIFT;
1573 break;
1574 case 39: /* default-foreground */
1575 curr_attr &= ~ATTR_FGMASK;
1576 curr_attr |= ATTR_DEFFG;
1577 break;
1578 case 40:
1579 case 41:
1580 case 42:
1581 case 43:
1582 case 44:
1583 case 45:
1584 case 46:
1585 case 47:
1586 /* background */
1587 curr_attr &= ~ATTR_BGMASK;
1588 curr_attr |=
1589 (esc_args[i] - 40) << ATTR_BGSHIFT;
1590 break;
1591 case 49: /* default-background */
1592 curr_attr &= ~ATTR_BGMASK;
1593 curr_attr |= ATTR_DEFBG;
1594 break;
1595 }
1596 }
1597 if (use_bce)
1598 erase_char =
1599 (' ' |
1600 (curr_attr &
1601 (ATTR_FGMASK | ATTR_BGMASK |
1602 ATTR_BLINK)));
1603 }
1604 break;
1605 case 's': /* save cursor */
1606 save_cursor(TRUE);
1607 break;
1608 case 'u': /* restore cursor */
1609 save_cursor(FALSE);
1610 seen_disp_event = TRUE;
1611 break;
1612 case 't': /* set page size - ie window height */
374330e2 1613 /*
32874aea 1614 * VT340/VT420 sequence DECSLPP, DEC only allows values
1615 * 24/25/36/48/72/144 other emulators (eg dtterm) use
1616 * illegal values (eg first arg 1..9) for window changing
1617 * and reports.
1618 */
1619 compatibility(VT340TEXT);
1620 if (esc_nargs <= 1
1621 && (esc_args[0] < 1 || esc_args[0] >= 24)) {
1622 request_resize(cols, def(esc_args[0], 24), 0);
1623 deselect();
1624 }
1625 break;
1a837633 1626 case 'S':
1627 compatibility(SCOANSI);
1628 scroll(marg_t, marg_b, def(esc_args[0], 1), TRUE);
1629 fix_cpos;
1630 wrapnext = FALSE;
1631 seen_disp_event = TRUE;
1632 break;
1633 case 'T':
1634 compatibility(SCOANSI);
1635 scroll(marg_t, marg_b, -def(esc_args[0], 1), TRUE);
1636 fix_cpos;
1637 wrapnext = FALSE;
1638 seen_disp_event = TRUE;
1639 break;
32874aea 1640 case ANSI('|', '*'):
1641 /* VT420 sequence DECSNLS
1642 * Set number of lines on screen
1643 * VT420 uses VGA like hardware and can support any size in
1644 * reasonable range (24..49 AIUI) with no default specified.
1645 */
1646 compatibility(VT420);
1647 if (esc_nargs == 1 && esc_args[0] > 0) {
1648 request_resize(cols,
1649 def(esc_args[0], cfg.height),
1650 0);
1651 deselect();
1652 }
1653 break;
1654 case ANSI('|', '$'):
1655 /* VT340/VT420 sequence DECSCPP
1656 * Set number of columns per page
1657 * Docs imply range is only 80 or 132, but I'll allow any.
1658 */
1659 compatibility(VT340TEXT);
1660 if (esc_nargs <= 1) {
1661 request_resize(def(esc_args[0], cfg.width),
1662 rows, 0);
1663 deselect();
1664 }
1665 break;
1666 case 'X': /* write N spaces w/o moving cursor */
1667 /* XXX VTTEST says this is vt220, vt510 manual says vt100 */
1668 compatibility(ANSIMIN);
1669 {
1670 int n = def(esc_args[0], 1);
1671 pos cursplus;
1672 unsigned long *p = cpos;
1673 if (n > cols - curs.x)
1674 n = cols - curs.x;
1675 cursplus = curs;
1676 cursplus.x += n;
1677 check_selection(curs, cursplus);
1678 while (n--)
1679 *p++ = erase_char;
1680 seen_disp_event = TRUE;
1681 }
1682 break;
1683 case 'x': /* report terminal characteristics */
1684 compatibility(VT100);
1685 {
1686 char buf[32];
1687 int i = def(esc_args[0], 0);
1688 if (i == 0 || i == 1) {
1689 strcpy(buf, "\033[2;1;1;112;112;1;0x");
1690 buf[2] += i;
1691 ldisc_send(buf, 20);
1692 }
1693 }
1694 break;
1695 case ANSI('L', '='):
1696 compatibility(OTHER);
1697 use_bce = (esc_args[0] <= 0);
1698 erase_char = ERASE_CHAR;
1699 if (use_bce)
1700 erase_char =
1701 (' ' |
1702 (curr_attr &
1703 (ATTR_FGMASK | ATTR_BGMASK)));
1704 break;
1705 case ANSI('E', '='):
1706 compatibility(OTHER);
1707 blink_is_real = (esc_args[0] >= 1);
1708 break;
1709 case ANSI('p', '"'):
1710 /* Allow the host to make this emulator a 'perfect' VT102.
1711 * This first appeared in the VT220, but we do need to get
1712 * back to PuTTY mode so I won't check it.
e14a5a13 1713 *
32874aea 1714 * The arg in 40..42 are a PuTTY extension.
1715 * The 2nd arg, 8bit vs 7bit is not checked.
1716 *
1717 * Setting VT102 mode should also change the Fkeys to
1718 * generate PF* codes as a real VT102 has no Fkeys.
1719 * The VT220 does this, F11..F13 become ESC,BS,LF other Fkeys
1720 * send nothing.
1721 *
1722 * Note ESC c will NOT change this!
374330e2 1723 */
32874aea 1724
1725 switch (esc_args[0]) {
1726 case 61:
1727 compatibility_level &= ~TM_VTXXX;
1728 compatibility_level |= TM_VT102;
374330e2 1729 break;
32874aea 1730 case 62:
1731 compatibility_level &= ~TM_VTXXX;
1732 compatibility_level |= TM_VT220;
1733 break;
1734
1735 default:
1736 if (esc_args[0] > 60 && esc_args[0] < 70)
1737 compatibility_level |= TM_VTXXX;
1738 break;
1739
1740 case 40:
1741 compatibility_level &= TM_VTXXX;
374330e2 1742 break;
32874aea 1743 case 41:
1744 compatibility_level = TM_PUTTY;
374330e2 1745 break;
32874aea 1746 case 42:
1747 compatibility_level = TM_SCOANSI;
374330e2 1748 break;
32874aea 1749
1750 case ARG_DEFAULT:
1751 compatibility_level = TM_PUTTY;
1752 break;
1753 case 50:
1754 break;
1755 }
1756
1757 /* Change the response to CSI c */
1758 if (esc_args[0] == 50) {
1759 int i;
1760 char lbuf[64];
1761 strcpy(id_string, "\033[?");
1762 for (i = 1; i < esc_nargs; i++) {
1763 if (i != 1)
1764 strcat(id_string, ";");
1765 sprintf(lbuf, "%d", esc_args[i]);
1766 strcat(id_string, lbuf);
1767 }
1768 strcat(id_string, "c");
1769 }
1770#if 0
1771 /* Is this a good idea ?
1772 * Well we should do a soft reset at this point ...
1773 */
1774 if (!has_compat(VT420) && has_compat(VT100)) {
1775 if (reset_132)
1776 request_resize(132, 24, 1);
1777 else
1778 request_resize(80, 24, 1);
374330e2 1779 }
32874aea 1780#endif
1781 break;
374330e2 1782 }
374330e2 1783 break;
32874aea 1784 case SET_GL:
1785 case SET_GR:
1786 /* VT100 only here, checked above */
1787 switch (c) {
1788 case 'A':
1789 cset_attr[termstate == SET_GL ? 0 : 1] = ATTR_GBCHR;
1790 break;
1791 case '0':
1792 cset_attr[termstate == SET_GL ? 0 : 1] = ATTR_LINEDRW;
1793 break;
1794 case 'B':
1795 default: /* specifically, 'B' */
1796 cset_attr[termstate == SET_GL ? 0 : 1] = ATTR_ASCII;
1797 break;
e14a5a13 1798 }
32874aea 1799 if (!has_compat(VT220) || c != '%')
1800 termstate = TOPLEVEL;
e14a5a13 1801 break;
32874aea 1802 case SEEN_OSC:
1803 osc_w = FALSE;
1804 switch (c) {
1805 case 'P': /* Linux palette sequence */
1806 termstate = SEEN_OSC_P;
1807 osc_strlen = 0;
1808 break;
1809 case 'R': /* Linux palette reset */
1810 palette_reset();
1811 term_invalidate();
1812 termstate = TOPLEVEL;
1813 break;
1814 case 'W': /* word-set */
1815 termstate = SEEN_OSC_W;
1816 osc_w = TRUE;
1817 break;
1818 case '0':
1819 case '1':
1820 case '2':
1821 case '3':
1822 case '4':
1823 case '5':
1824 case '6':
1825 case '7':
1826 case '8':
1827 case '9':
1828 esc_args[0] = 10 * esc_args[0] + c - '0';
1829 break;
1830 case 'L':
1831 /*
1832 * Grotty hack to support xterm and DECterm title
1833 * sequences concurrently.
1834 */
1835 if (esc_args[0] == 2) {
1836 esc_args[0] = 1;
1837 break;
1838 }
1839 /* else fall through */
1840 default:
1841 termstate = OSC_STRING;
1842 osc_strlen = 0;
e14a5a13 1843 }
1844 break;
32874aea 1845 case OSC_STRING:
1846 /*
1847 * This OSC stuff is EVIL. It takes just one character to get into
1848 * sysline mode and it's not initially obvious how to get out.
1849 * So I've added CR and LF as string aborts.
1850 * This shouldn't effect compatibility as I believe embedded
1851 * control characters are supposed to be interpreted (maybe?)
1852 * and they don't display anything useful anyway.
1853 *
1854 * -- RDB
e14a5a13 1855 */
32874aea 1856 if (c == '\n' || c == '\r') {
1857 termstate = TOPLEVEL;
1858 } else if (c == 0234 || c == '\007') {
1859 /*
1860 * These characters terminate the string; ST and BEL
1861 * terminate the sequence and trigger instant
1862 * processing of it, whereas ESC goes back to SEEN_ESC
1863 * mode unless it is followed by \, in which case it is
1864 * synonymous with ST in the first place.
1865 */
1866 do_osc();
1867 termstate = TOPLEVEL;
1868 } else if (c == '\033')
1869 termstate = OSC_MAYBE_ST;
1870 else if (osc_strlen < OSC_STR_MAX)
1871 osc_string[osc_strlen++] = c;
374330e2 1872 break;
32874aea 1873 case SEEN_OSC_P:
374330e2 1874 {
32874aea 1875 int max = (osc_strlen == 0 ? 21 : 16);
1876 int val;
1877 if (c >= '0' && c <= '9')
1878 val = c - '0';
1879 else if (c >= 'A' && c <= 'A' + max - 10)
1880 val = c - 'A' + 10;
1881 else if (c >= 'a' && c <= 'a' + max - 10)
1882 val = c - 'a' + 10;
1883 else
1884 termstate = TOPLEVEL;
1885 osc_string[osc_strlen++] = val;
1886 if (osc_strlen >= 7) {
1887 palette_set(osc_string[0],
1888 osc_string[1] * 16 + osc_string[2],
1889 osc_string[3] * 16 + osc_string[4],
1890 osc_string[5] * 16 + osc_string[6]);
1891 term_invalidate();
1892 termstate = TOPLEVEL;
374330e2 1893 }
1894 }
1895 break;
32874aea 1896 case SEEN_OSC_W:
1897 switch (c) {
1898 case '0':
1899 case '1':
1900 case '2':
1901 case '3':
1902 case '4':
1903 case '5':
1904 case '6':
1905 case '7':
1906 case '8':
1907 case '9':
1908 esc_args[0] = 10 * esc_args[0] + c - '0';
1909 break;
1910 default:
1911 termstate = OSC_STRING;
1912 osc_strlen = 0;
ec55b220 1913 }
32874aea 1914 break;
1915 case SEEN_ESCHASH:
1916 {
1917 unsigned long nlattr;
1918 unsigned long *ldata;
1919 int i, j;
1920 pos scrtop, scrbot;
1921
1922 switch (c) {
1923 case '8':
1924 for (i = 0; i < rows; i++) {
1925 ldata = lineptr(i);
1926 for (j = 0; j < cols; j++)
1927 ldata[j] = ATTR_DEFAULT | 'E';
1928 ldata[cols] = 0;
1929 }
1930 disptop = 0;
1931 seen_disp_event = TRUE;
1932 scrtop.x = scrtop.y = 0;
1933 scrbot.x = 0;
1934 scrbot.y = rows;
1935 check_selection(scrtop, scrbot);
1936 break;
1937
1938 case '3':
1939 case '4':
1940 case '5':
1941 case '6':
1942 switch (c) {
1943 case '3':
1944 nlattr = LATTR_TOP;
1945 break;
1946 case '4':
1947 nlattr = LATTR_BOT;
1948 break;
1949 case '5':
1950 nlattr = LATTR_NORM;
1951 break;
1952 case '6':
1953 nlattr = LATTR_WIDE;
1954 break;
1955 }
ec55b220 1956
32874aea 1957 ldata = lineptr(curs.y);
1958 ldata[cols] &= ~LATTR_MODE;
1959 ldata[cols] |= nlattr;
1960 }
ec55b220 1961 }
374330e2 1962 termstate = TOPLEVEL;
1963 break;
32874aea 1964 case VT52_ESC:
374330e2 1965 termstate = TOPLEVEL;
32874aea 1966 seen_disp_event = TRUE;
c9def1b8 1967 switch (c) {
32874aea 1968 case 'A':
1969 move(curs.x, curs.y - 1, 1);
1970 break;
1971 case 'B':
1972 move(curs.x, curs.y + 1, 1);
1973 break;
1974 case 'C':
1975 move(curs.x + 1, curs.y, 1);
1976 break;
1977 case 'D':
1978 move(curs.x - 1, curs.y, 1);
1979 break;
1980 case 'F':
1981 cset_attr[cset = 0] = ATTR_LINEDRW;
1982 break;
1983 case 'G':
1984 cset_attr[cset = 0] = ATTR_ASCII;
1985 break;
1986 case 'H':
1987 move(0, 0, 0);
1988 break;
1989 case 'I':
1990 if (curs.y == 0)
1991 scroll(0, rows - 1, -1, TRUE);
1992 else if (curs.y > 0)
1993 curs.y--;
1994 fix_cpos;
1995 wrapnext = FALSE;
1996 break;
1997 case 'J':
1998 erase_lots(FALSE, FALSE, TRUE);
4facdf84 1999 disptop = 0;
c9def1b8 2000 break;
32874aea 2001 case 'K':
2002 erase_lots(TRUE, FALSE, TRUE);
2003 break;
2004 case 'V':
2005 /* XXX Print cursor line */
2006 break;
2007 case 'W':
2008 /* XXX Start controller mode */
2009 break;
2010 case 'X':
2011 /* XXX Stop controller mode */
2012 break;
2013 case 'Y':
2014 termstate = VT52_Y1;
2015 break;
2016 case 'Z':
2017 ldisc_send("\033/Z", 3);
2018 break;
2019 case '=':
2020 app_keypad_keys = TRUE;
2021 break;
2022 case '>':
2023 app_keypad_keys = FALSE;
2024 break;
2025 case '<':
2026 /* XXX This should switch to VT100 mode not current or default
2027 * VT mode. But this will only have effect in a VT220+
2028 * emulation.
2029 */
2030 vt52_mode = FALSE;
2031 break;
2032 case '^':
2033 /* XXX Enter auto print mode */
2034 break;
2035 case '_':
2036 /* XXX Exit auto print mode */
2037 break;
2038 case ']':
2039 /* XXX Print screen */
2040 break;
c9def1b8 2041 }
e14a5a13 2042 break;
32874aea 2043 case VT52_Y1:
2044 termstate = VT52_Y2;
2045 move(curs.x, c - ' ', 0);
e14a5a13 2046 break;
32874aea 2047 case VT52_Y2:
2048 termstate = TOPLEVEL;
2049 move(c - ' ', curs.y, 0);
e14a5a13 2050 break;
2051 }
4facdf84 2052 if (selstate != NO_SELECTION) {
2053 pos cursplus = curs;
2054 incpos(cursplus);
32874aea 2055 check_selection(curs, cursplus);
4facdf84 2056 }
374330e2 2057 }
c9def1b8 2058 inbuf_head = 0;
374330e2 2059}
2060
2061/*
2062 * Compare two lines to determine whether they are sufficiently
2063 * alike to scroll-optimise one to the other. Return the degree of
2064 * similarity.
2065 */
32874aea 2066static int linecmp(unsigned long *a, unsigned long *b)
2067{
374330e2 2068 int i, n;
2069
32874aea 2070 for (i = n = 0; i < cols; i++)
374330e2 2071 n += (*a++ == *b++);
2072 return n;
2073}
2074
2075/*
2076 * Given a context, update the window. Out of paranoia, we don't
2077 * allow WM_PAINT responses to do scrolling optimisations.
2078 */
32874aea 2079static void do_paint(Context ctx, int may_optimise)
2080{
374330e2 2081 int i, j, start, our_curs_y;
2082 unsigned long attr, rv, cursor;
4facdf84 2083 pos scrpos;
374330e2 2084 char ch[1024];
156686ef 2085 long ticks;
374330e2 2086
156686ef 2087 /*
2088 * Check the visual bell state.
2089 */
2090 if (in_vbell) {
2091 ticks = GetTickCount();
2092 if (ticks - vbell_timeout >= 0)
2093 in_vbell = FALSE;
2094 }
2095
2096 /* Depends on:
2097 * screen array, disptop, scrtop,
2098 * selection, rv,
2099 * cfg.blinkpc, blink_is_real, tblinker,
4facdf84 2100 * curs.y, curs.x, blinker, cfg.blink_cur, cursor_on, has_focus
156686ef 2101 */
e14a5a13 2102 if (cursor_on) {
32874aea 2103 if (has_focus) {
217dceef 2104 if (blinker || !cfg.blink_cur)
32874aea 2105 cursor = ATTR_ACTCURS;
2106 else
2107 cursor = 0;
2108 } else
2109 cursor = ATTR_PASCURS;
4e30ff69 2110 if (wrapnext)
2111 cursor |= ATTR_RIGHTCURS;
32874aea 2112 } else
156686ef 2113 cursor = 0;
2114 rv = (!rvideo ^ !in_vbell ? ATTR_REVERSE : 0);
4facdf84 2115 our_curs_y = curs.y - disptop;
374330e2 2116
32874aea 2117 for (i = 0; i < rows; i++) {
4facdf84 2118 unsigned long *ldata;
2119 int lattr;
2120 scrpos.y = i + disptop;
2121 ldata = lineptr(scrpos.y);
2122 lattr = (ldata[cols] & LATTR_MODE);
32874aea 2123 for (j = 0; j <= cols; j++) {
4facdf84 2124 unsigned long d = ldata[j];
32874aea 2125 int idx = i * (cols + 1) + j;
4facdf84 2126 scrpos.x = j;
32874aea 2127
2128 wanttext[idx] = lattr | (((d & ~ATTR_WRAPPED) ^ rv
4facdf84 2129 ^ (posle(selstart, scrpos) &&
2130 poslt(scrpos, selend) ?
2131 ATTR_REVERSE : 0)) |
32874aea 2132 (i == our_curs_y
2133 && j == curs.x ? cursor : 0));
c9def1b8 2134 if (blink_is_real) {
32874aea 2135 if (has_focus && tblinker && (wanttext[idx] & ATTR_BLINK)) {
c9def1b8 2136 wanttext[idx] &= ATTR_MASK;
2137 wanttext[idx] += ' ';
2138 }
2139 wanttext[idx] &= ~ATTR_BLINK;
2140 }
374330e2 2141 }
2142 }
2143
2144 /*
2145 * We would perform scrolling optimisations in here, if they
2146 * didn't have a nasty tendency to cause the whole sodding
2147 * program to hang for a second at speed-critical moments.
2148 * We'll leave it well alone...
2149 */
2150
32874aea 2151 for (i = 0; i < rows; i++) {
2152 int idx = i * (cols + 1);
2153 int lattr = (wanttext[idx + cols] & LATTR_MODE);
374330e2 2154 start = -1;
32874aea 2155 for (j = 0; j <= cols; j++, idx++) {
374330e2 2156 unsigned long t = wanttext[idx];
2157 int needs_update = (j < cols && t != disptext[idx]);
2158 int keep_going = (start != -1 && needs_update &&
2159 (t & ATTR_MASK) == attr &&
32874aea 2160 j - start < sizeof(ch));
374330e2 2161 if (start != -1 && !keep_going) {
32874aea 2162 do_text(ctx, start, i, ch, j - start, attr, lattr);
374330e2 2163 start = -1;
2164 }
2165 if (needs_update) {
2166 if (start == -1) {
2167 start = j;
2168 attr = t & ATTR_MASK;
2169 }
32874aea 2170 ch[j - start] = (char) (t & CHAR_MASK);
374330e2 2171 }
2172 disptext[idx] = t;
2173 }
2174 }
2175}
2176
2177/*
e14a5a13 2178 * Flick the switch that says if blinking things should be shown or hidden.
2179 */
2180
32874aea 2181void term_blink(int flg)
2182{
22dcdc3b 2183 static long last_blink = 0;
2184 static long last_tblink = 0;
e14a5a13 2185 long now, blink_diff;
2186
c9def1b8 2187 now = GetTickCount();
32874aea 2188 blink_diff = now - last_tblink;
c9def1b8 2189
2190 /* Make sure the text blinks no more than 2Hz */
32874aea 2191 if (blink_diff < 0 || blink_diff > 450) {
2192 last_tblink = now;
c9def1b8 2193 tblinker = !tblinker;
2194 }
2195
e14a5a13 2196 if (flg) {
32874aea 2197 blinker = 1;
2198 last_blink = now;
e14a5a13 2199 return;
32874aea 2200 }
e14a5a13 2201
32874aea 2202 blink_diff = now - last_blink;
e14a5a13 2203
22dcdc3b 2204 /* Make sure the cursor blinks no faster than GetCaretBlinkTime() */
32874aea 2205 if (blink_diff >= 0 && blink_diff < (long) GetCaretBlinkTime())
2206 return;
2207
e14a5a13 2208 last_blink = now;
2209 blinker = !blinker;
2210}
2211
2212/*
374330e2 2213 * Invalidate the whole screen so it will be repainted in full.
2214 */
32874aea 2215void term_invalidate(void)
2216{
374330e2 2217 int i;
2218
32874aea 2219 for (i = 0; i < rows * (cols + 1); i++)
374330e2 2220 disptext[i] = ATTR_INVALID;
2221}
2222
2223/*
2224 * Paint the window in response to a WM_PAINT message.
2225 */
32874aea 2226void term_paint(Context ctx, int l, int t, int r, int b)
2227{
374330e2 2228 int i, j, left, top, right, bottom;
2229
2230 left = l / font_width;
2231 right = (r - 1) / font_width;
2232 top = t / font_height;
2233 bottom = (b - 1) / font_height;
32874aea 2234 for (i = top; i <= bottom && i < rows; i++) {
2235 if ((disptext[i * (cols + 1) + cols] & LATTR_MODE) == LATTR_NORM)
2236 for (j = left; j <= right && j < cols; j++)
2237 disptext[i * (cols + 1) + j] = ATTR_INVALID;
c9def1b8 2238 else
32874aea 2239 for (j = left / 2; j <= right / 2 + 1 && j < cols; j++)
2240 disptext[i * (cols + 1) + j] = ATTR_INVALID;
c9def1b8 2241 }
374330e2 2242
e14a5a13 2243 /* This should happen soon enough, also for some reason it sometimes
2244 * fails to actually do anything when re-sizing ... painting the wrong
2245 * window perhaps ?
32874aea 2246 do_paint (ctx, FALSE);
2247 */
374330e2 2248}
2249
2250/*
2251 * Attempt to scroll the scrollback. The second parameter gives the
2252 * position we want to scroll to; the first is +1 to denote that
2253 * this position is relative to the beginning of the scrollback, -1
2254 * to denote it is relative to the end, and 0 to denote that it is
2255 * relative to the current position.
2256 */
32874aea 2257void term_scroll(int rel, int where)
2258{
4facdf84 2259 int sbtop = -count234(scrollback);
374330e2 2260
32874aea 2261 disptop = (rel < 0 ? 0 : rel > 0 ? sbtop : disptop) + where;
374330e2 2262 if (disptop < sbtop)
2263 disptop = sbtop;
4facdf84 2264 if (disptop > 0)
2265 disptop = 0;
374330e2 2266 update_sbar();
2267 term_update();
2268}
2269
32874aea 2270static void clipme(pos top, pos bottom, char *workbuf)
2271{
2272 char *wbptr; /* where next char goes within workbuf */
2273 int wblen = 0; /* workbuf len */
2274 int buflen; /* amount of memory allocated to workbuf */
bc1235d4 2275
32874aea 2276 if (workbuf != NULL) { /* user supplied buffer? */
2277 buflen = -1; /* assume buffer passed in is big enough */
2278 wbptr = workbuf; /* start filling here */
2279 } else
2280 buflen = 0; /* No data is available yet */
bc1235d4 2281
4facdf84 2282 while (poslt(top, bottom)) {
bc1235d4 2283 int nl = FALSE;
4facdf84 2284 unsigned long *ldata = lineptr(top.y);
260f3dec 2285 pos nlpos;
4facdf84 2286
2287 nlpos.y = top.y;
2288 nlpos.x = cols;
bc1235d4 2289
4facdf84 2290 if (!(ldata[cols] & ATTR_WRAPPED)) {
32874aea 2291 while ((ldata[nlpos.x - 1] & CHAR_MASK) == 0x20
2292 && poslt(top, nlpos)) decpos(nlpos);
4facdf84 2293 if (poslt(nlpos, bottom))
bc1235d4 2294 nl = TRUE;
2295 }
4facdf84 2296 while (poslt(top, bottom) && poslt(top, nlpos)) {
2297 int ch = (ldata[top.x] & CHAR_MASK);
2298 int set = (ldata[top.x] & CSET_MASK);
d3a22f79 2299
2300 /* VT Specials -> ISO8859-1 for Cut&Paste */
2301 static const unsigned char poorman2[] =
32874aea 2302 "* # HTFFCRLF\xB0 \xB1 NLVT+ + + + + - - - - - + + + + | <=>=PI!=\xA3 \xB7 ";
bc1235d4 2303
d3a22f79 2304 if (set && !cfg.rawcnp) {
32874aea 2305 if (set == ATTR_LINEDRW && ch >= 0x60 && ch < 0x7F) {
d3a22f79 2306 int x;
32874aea 2307 if ((x = poorman2[2 * (ch - 0x60) + 1]) == ' ')
2308 x = 0;
2309 ch = (x << 8) + poorman2[2 * (ch - 0x60)];
2310 }
d3a22f79 2311 }
bc1235d4 2312
32874aea 2313 while (ch != 0) {
2314 if (cfg.rawcnp || !!(ch & 0xE0)) {
2315 if (wblen == buflen) {
2316 workbuf = srealloc(workbuf, buflen += 100);
2317 wbptr = workbuf + wblen;
d3a22f79 2318 }
2319 wblen++;
2320 *wbptr++ = (unsigned char) ch;
2321 }
32874aea 2322 ch >>= 8;
bc1235d4 2323 }
4facdf84 2324 top.x++;
bc1235d4 2325 }
2326 if (nl) {
2327 int i;
32874aea 2328 for (i = 0; i < sizeof(sel_nl); i++) {
2329 if (wblen == buflen) {
bc1235d4 2330 workbuf = srealloc(workbuf, buflen += 100);
2331 wbptr = workbuf + wblen;
2332 }
32874aea 2333 wblen++;
bc1235d4 2334 *wbptr++ = sel_nl[i];
2335 }
2336 }
4facdf84 2337 top.y++;
2338 top.x = 0;
bc1235d4 2339 }
32874aea 2340 write_clip(workbuf, wblen, FALSE); /* transfer to clipboard */
2341 if (buflen > 0) /* indicates we allocated this buffer */
bc1235d4 2342 sfree(workbuf);
2343
2344}
32874aea 2345void term_copyall(void)
2346{
4facdf84 2347 pos top;
2348 top.y = -count234(scrollback);
2349 top.x = 0;
32874aea 2350 clipme(top, curs, NULL /* dynamic allocation */ );
bc1235d4 2351}
2352
374330e2 2353/*
2354 * Spread the selection outwards according to the selection mode.
2355 */
32874aea 2356static pos sel_spread_half(pos p, int dir)
2357{
4facdf84 2358 unsigned long *ldata;
374330e2 2359 short wvalue;
2360
4facdf84 2361 ldata = lineptr(p.y);
374330e2 2362
2363 switch (selmode) {
2364 case SM_CHAR:
2365 /*
2366 * In this mode, every character is a separate unit, except
2367 * for runs of spaces at the end of a non-wrapping line.
2368 */
4facdf84 2369 if (!(ldata[cols] & ATTR_WRAPPED)) {
32874aea 2370 unsigned long *q = ldata + cols;
4facdf84 2371 while (q > ldata && (q[-1] & CHAR_MASK) == 0x20)
374330e2 2372 q--;
32874aea 2373 if (q == ldata + cols)
374330e2 2374 q--;
32874aea 2375 if (p.x >= q - ldata)
2376 p.x = (dir == -1 ? q - ldata : cols - 1);
374330e2 2377 }
2378 break;
2379 case SM_WORD:
2380 /*
2381 * In this mode, the units are maximal runs of characters
2382 * whose `wordness' has the same value.
2383 */
4facdf84 2384 wvalue = wordness[ldata[p.x] & CHAR_MASK];
374330e2 2385 if (dir == +1) {
32874aea 2386 while (p.x < cols
2387 && wordness[ldata[p.x + 1] & CHAR_MASK] ==
2388 wvalue) p.x++;
374330e2 2389 } else {
32874aea 2390 while (p.x > 0
2391 && wordness[ldata[p.x - 1] & CHAR_MASK] ==
2392 wvalue) p.x--;
374330e2 2393 }
2394 break;
2395 case SM_LINE:
2396 /*
2397 * In this mode, every line is a unit.
2398 */
4facdf84 2399 p.x = (dir == -1 ? 0 : cols - 1);
374330e2 2400 break;
2401 }
2402 return p;
2403}
2404
32874aea 2405static void sel_spread(void)
2406{
2407 selstart = sel_spread_half(selstart, -1);
4facdf84 2408 decpos(selend);
32874aea 2409 selend = sel_spread_half(selend, +1);
4facdf84 2410 incpos(selend);
374330e2 2411}
2412
32874aea 2413void term_mouse(Mouse_Button b, Mouse_Action a, int x, int y,
2414 int shift, int ctrl)
2415{
4facdf84 2416 pos selpoint;
2417 unsigned long *ldata;
32874aea 2418
2419 if (y < 0)
2420 y = 0;
2421 if (y >= rows)
2422 y = rows - 1;
2423 if (x < 0) {
2424 if (y > 0) {
2425 x = cols - 1;
2426 y--;
2427 } else
2428 x = 0;
094ed2a6 2429 }
32874aea 2430 if (x >= cols)
2431 x = cols - 1;
37508af4 2432
4facdf84 2433 selpoint.y = y + disptop;
2434 selpoint.x = x;
2435 ldata = lineptr(selpoint.y);
32874aea 2436 if ((ldata[cols] & LATTR_MODE) != LATTR_NORM)
4facdf84 2437 selpoint.x /= 2;
374330e2 2438
01c034ad 2439 if (xterm_mouse) {
2440 int encstate = 0, r, c;
2441 char abuf[16];
2442 static int is_down = 0;
2443
32874aea 2444 switch (b) {
01c034ad 2445 case MBT_LEFT:
32874aea 2446 encstate = 0x20; /* left button down */
01c034ad 2447 break;
2448 case MBT_MIDDLE:
2449 encstate = 0x21;
2450 break;
2451 case MBT_RIGHT:
2452 encstate = 0x22;
2453 break;
2454 case MBT_WHEEL_UP:
2455 encstate = 0x60;
2456 break;
2457 case MBT_WHEEL_DOWN:
2458 encstate = 0x61;
2459 break;
2460 }
32874aea 2461 switch (a) {
01c034ad 2462 case MA_DRAG:
2463 if (xterm_mouse == 1)
2464 return;
2465 encstate += 0x20;
2466 break;
2467 case MA_RELEASE:
2468 encstate = 0x23;
2469 is_down = 0;
2470 break;
2471 case MA_CLICK:
2472 if (is_down == b)
2473 return;
2474 is_down = b;
2475 break;
2476 }
2477 if (shift)
2478 encstate += 0x04;
2479 if (ctrl)
2480 encstate += 0x10;
2481 r = y + 33;
2482 c = x + 33;
2483
2484 sprintf(abuf, "\033[M%c%c%c", encstate, c, r);
2485 ldisc_send(abuf, 6);
2486 return;
2487 }
2488
2489 b = translate_button(b);
2490
2491 if (b == MBT_SELECT && a == MA_CLICK) {
374330e2 2492 deselect();
2493 selstate = ABOUT_TO;
2494 selanchor = selpoint;
2495 selmode = SM_CHAR;
01c034ad 2496 } else if (b == MBT_SELECT && (a == MA_2CLK || a == MA_3CLK)) {
374330e2 2497 deselect();
2498 selmode = (a == MA_2CLK ? SM_WORD : SM_LINE);
2499 selstate = DRAGGING;
2500 selstart = selanchor = selpoint;
4facdf84 2501 selend = selstart;
2502 incpos(selend);
374330e2 2503 sel_spread();
01c034ad 2504 } else if ((b == MBT_SELECT && a == MA_DRAG) ||
2505 (b == MBT_EXTEND && a != MA_RELEASE)) {
4facdf84 2506 if (selstate == ABOUT_TO && poseq(selanchor, selpoint))
374330e2 2507 return;
01c034ad 2508 if (b == MBT_EXTEND && a != MA_DRAG && selstate == SELECTED) {
32874aea 2509 if (posdiff(selpoint, selstart) <
2510 posdiff(selend, selstart) / 2) {
4facdf84 2511 selanchor = selend;
2512 decpos(selanchor);
2513 } else {
374330e2 2514 selanchor = selstart;
4facdf84 2515 }
374330e2 2516 selstate = DRAGGING;
2517 }
2518 if (selstate != ABOUT_TO && selstate != DRAGGING)
2519 selanchor = selpoint;
2520 selstate = DRAGGING;
4facdf84 2521 if (poslt(selpoint, selanchor)) {
374330e2 2522 selstart = selpoint;
4facdf84 2523 selend = selanchor;
2524 incpos(selend);
374330e2 2525 } else {
2526 selstart = selanchor;
4facdf84 2527 selend = selpoint;
a4450583 2528 incpos(selend);
374330e2 2529 }
2530 sel_spread();
01c034ad 2531 } else if ((b == MBT_SELECT || b == MBT_EXTEND) && a == MA_RELEASE) {
374330e2 2532 if (selstate == DRAGGING) {
2533 /*
2534 * We've completed a selection. We now transfer the
2535 * data to the clipboard.
2536 */
bc1235d4 2537 clipme(selstart, selend, selspace);
374330e2 2538 selstate = SELECTED;
2539 } else
2540 selstate = NO_SELECTION;
32874aea 2541 } else if (b == MBT_PASTE
2542 && (a == MA_CLICK || a == MA_2CLK || a == MA_3CLK)) {
374330e2 2543 char *data;
2544 int len;
2545
2546 get_clip((void **) &data, &len);
2547 if (data) {
2548 char *p, *q;
c9def1b8 2549
32874aea 2550 if (paste_buffer)
2551 sfree(paste_buffer);
c9def1b8 2552 paste_pos = paste_hold = paste_len = 0;
2553 paste_buffer = smalloc(len);
2554
374330e2 2555 p = q = data;
32874aea 2556 while (p < data + len) {
2557 while (p < data + len &&
2558 !(p <= data + len - sizeof(sel_nl) &&
374330e2 2559 !memcmp(p, sel_nl, sizeof(sel_nl))))
2560 p++;
14963b8f 2561
2562 {
2563 int i;
2564 unsigned char c;
32874aea 2565 for (i = 0; i < p - q; i++) {
2566 c = xlat_kbd2tty(q[i]);
c9def1b8 2567 paste_buffer[paste_len++] = c;
14963b8f 2568 }
2569 }
2570
32874aea 2571 if (p <= data + len - sizeof(sel_nl) &&
374330e2 2572 !memcmp(p, sel_nl, sizeof(sel_nl))) {
c9def1b8 2573 paste_buffer[paste_len++] = '\r';
374330e2 2574 p += sizeof(sel_nl);
2575 }
2576 q = p;
2577 }
c9def1b8 2578
2579 /* Assume a small paste will be OK in one go. */
32874aea 2580 if (paste_len < 256) {
2581 ldisc_send(paste_buffer, paste_len);
2582 if (paste_buffer)
2583 sfree(paste_buffer);
c9def1b8 2584 paste_buffer = 0;
32874aea 2585 paste_pos = paste_hold = paste_len = 0;
c9def1b8 2586 }
374330e2 2587 }
2588 get_clip(NULL, NULL);
2589 }
2590
2591 term_update();
2592}
2593
32874aea 2594void term_nopaste()
2595{
2596 if (paste_len == 0)
2597 return;
c9def1b8 2598 sfree(paste_buffer);
2599 paste_buffer = 0;
2600 paste_len = 0;
2601}
2602
32874aea 2603void term_paste()
2604{
8df7a775 2605 static long last_paste = 0;
c9def1b8 2606 long now, paste_diff;
2607
32874aea 2608 if (paste_len == 0)
2609 return;
c9def1b8 2610
2611 /* Don't wait forever to paste */
32874aea 2612 if (paste_hold) {
2613 now = GetTickCount();
2614 paste_diff = now - last_paste;
2615 if (paste_diff >= 0 && paste_diff < 450)
c9def1b8 2616 return;
2617 }
2618 paste_hold = 0;
2619
32874aea 2620 while (paste_pos < paste_len) {
8df7a775 2621 int n = 0;
2622 while (n + paste_pos < paste_len) {
2623 if (paste_buffer[paste_pos + n++] == '\r')
2624 break;
2625 }
32874aea 2626 ldisc_send(paste_buffer + paste_pos, n);
8df7a775 2627 paste_pos += n;
c9def1b8 2628
8df7a775 2629 if (paste_pos < paste_len) {
c9def1b8 2630 paste_hold = 1;
2631 return;
2632 }
2633 }
2634 sfree(paste_buffer);
2635 paste_buffer = 0;
2636 paste_len = 0;
2637}
2638
32874aea 2639static void deselect(void)
2640{
374330e2 2641 selstate = NO_SELECTION;
4facdf84 2642 selstart.x = selstart.y = selend.x = selend.y = 0;
374330e2 2643}
2644
32874aea 2645void term_deselect(void)
2646{
374330e2 2647 deselect();
2648 term_update();
2649}
fe50e814 2650
32874aea 2651int term_ldisc(int option)
2652{
2653 if (option == LD_ECHO)
2654 return term_echoing;
2655 if (option == LD_EDIT)
2656 return term_editing;
0965bee0 2657 return FALSE;
2658}
2659
fe50e814 2660/*
2661 * from_backend(), to get data from the backend for the terminal.
2662 */
32874aea 2663void from_backend(int is_stderr, char *data, int len)
2664{
fe50e814 2665 while (len--) {
2666 if (inbuf_head >= INBUF_SIZE)
2667 term_out();
2668 inbuf[inbuf_head++] = *data++;
2669 }
2670}
e1c8e0ed 2671
2672/*
2673 * Log session traffic.
2674 */
32874aea 2675void logtraffic(unsigned char c, int logmode)
2676{
e1c8e0ed 2677 if (cfg.logtype > 0) {
2678 if (cfg.logtype == logmode) {
2679 /* deferred open file from pgm start? */
32874aea 2680 if (!lgfp)
2681 logfopen();
2682 if (lgfp)
2683 fputc(c, lgfp);
2684 }
e1c8e0ed 2685 }
2686}
2687
2688/* open log file append/overwrite mode */
32874aea 2689void logfopen(void)
2690{
e1c8e0ed 2691 char buf[256];
2692 time_t t;
2693 struct tm *tm;
2694 char writemod[4];
2695
2696 if (!cfg.logtype)
2697 return;
32874aea 2698 sprintf(writemod, "wb"); /* default to rewrite */
2699 lgfp = fopen(cfg.logfilename, "r"); /* file already present? */
e1c8e0ed 2700 if (lgfp) {
2701 int i;
2702 fclose(lgfp);
2703 i = askappend(cfg.logfilename);
2704 if (i == 1)
2705 writemod[0] = 'a'; /* set append mode */
2706 else if (i == 0) { /* cancelled */
2707 lgfp = NULL;
32874aea 2708 cfg.logtype = 0; /* disable logging */
e1c8e0ed 2709 return;
2710 }
2711 }
2712
2713 lgfp = fopen(cfg.logfilename, writemod);
32874aea 2714 if (lgfp) { /* enter into event log */
e1c8e0ed 2715 sprintf(buf, "%s session log (%s mode) to file : ",
2716 (writemod[0] == 'a') ? "Appending" : "Writing new",
2717 (cfg.logtype == LGTYP_ASCII ? "ASCII" :
32874aea 2718 cfg.logtype == LGTYP_DEBUG ? "raw" : "<ukwn>"));
e1c8e0ed 2719 /* Make sure we do not exceed the output buffer size */
32874aea 2720 strncat(buf, cfg.logfilename, 128);
e1c8e0ed 2721 buf[strlen(buf)] = '\0';
2722 logevent(buf);
2723
32874aea 2724 /* --- write header line iinto log file */
2725 fputs("=~=~=~=~=~=~=~=~=~=~=~= PuTTY log ", lgfp);
e1c8e0ed 2726 time(&t);
2727 tm = localtime(&t);
2728 strftime(buf, 24, "%Y.%m.%d %H:%M:%S", tm);
32874aea 2729 fputs(buf, lgfp);
2730 fputs(" =~=~=~=~=~=~=~=~=~=~=~=\r\n", lgfp);
e1c8e0ed 2731 }
2732}
2733
32874aea 2734void logfclose(void)
2735{
2736 if (lgfp) {
2737 fclose(lgfp);
2738 lgfp = NULL;
2739 }
e1c8e0ed 2740}