From RDB: according to VT manuals, application cursor keys should
[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;
1059 case '\013':
1060 case '\014':
32874aea 1061 compatibility(VT100);
374330e2 1062 case '\n':
4facdf84 1063 if (curs.y == marg_b)
32874aea 1064 scroll(marg_t, marg_b, 1, TRUE);
1065 else if (curs.y < rows - 1)
4facdf84 1066 curs.y++;
cabfd08c 1067 if (cfg.lfhascr)
4facdf84 1068 curs.x = 0;
374330e2 1069 fix_cpos;
1070 wrapnext = FALSE;
cabfd08c 1071 seen_disp_event = 1;
c9def1b8 1072 paste_hold = 0;
32874aea 1073 logtraffic((unsigned char) c, LGTYP_ASCII);
374330e2 1074 break;
1075 case '\t':
374330e2 1076 {
4facdf84 1077 pos old_curs = curs;
1078 unsigned long *ldata = lineptr(curs.y);
c9def1b8 1079
1080 do {
4facdf84 1081 curs.x++;
32874aea 1082 } while (curs.x < cols - 1 && !tabs[curs.x]);
c9def1b8 1083
32874aea 1084 if ((ldata[cols] & LATTR_MODE) != LATTR_NORM) {
1085 if (curs.x >= cols / 2)
1086 curs.x = cols / 2 - 1;
1087 } else {
4facdf84 1088 if (curs.x >= cols)
32874aea 1089 curs.x = cols - 1;
c9def1b8 1090 }
1091
374330e2 1092 fix_cpos;
32874aea 1093 check_selection(old_curs, curs);
374330e2 1094 }
cabfd08c 1095 seen_disp_event = TRUE;
374330e2 1096 break;
32874aea 1097 case '\177': /* Destructive backspace
1098 This does nothing on a real VT100 */
1099 compatibility(OTHER);
1100 if (curs.x && !wrapnext)
1101 curs.x--;
ec55b220 1102 wrapnext = FALSE;
1103 fix_cpos;
1104 *cpos = (' ' | curr_attr | ATTR_ASCII);
1105 break;
cabfd08c 1106 }
32874aea 1107 } else
1108 switch (termstate) {
1109 case TOPLEVEL:
1110 /* Only graphic characters get this far, ctrls are stripped above */
1111 if (wrapnext && wrap) {
1112 cpos[1] |= ATTR_WRAPPED;
1113 if (curs.y == marg_b)
1114 scroll(marg_t, marg_b, 1, TRUE);
1115 else if (curs.y < rows - 1)
1116 curs.y++;
1117 curs.x = 0;
1118 fix_cpos;
1119 wrapnext = FALSE;
374330e2 1120 }
32874aea 1121 if (insert)
1122 insch(1);
1123 if (selstate != NO_SELECTION) {
1124 pos cursplus = curs;
1125 incpos(cursplus);
1126 check_selection(curs, cursplus);
374330e2 1127 }
32874aea 1128 switch (cset_attr[cset]) {
1129 /*
1130 * Linedraw characters are different from 'ESC ( B'
1131 * only for a small range. For ones outside that
1132 * range, make sure we use the same font as well as
1133 * the same encoding.
1134 */
1135 case ATTR_LINEDRW:
1136 if (c < 0x5f || c > 0x7F)
1137 *cpos++ =
1138 xlat_tty2scr((unsigned char) c) | curr_attr |
1139 ATTR_ASCII;
1140 else if (c == 0x5F)
1141 *cpos++ = ' ' | curr_attr | ATTR_ASCII;
1142 else
1143 *cpos++ =
1144 ((unsigned char) c) | curr_attr | ATTR_LINEDRW;
1145 break;
1146 case ATTR_GBCHR:
1147 /* If UK-ASCII, make the '#' a LineDraw Pound */
1148 if (c == '#') {
1149 *cpos++ = '}' | curr_attr | ATTR_LINEDRW;
1150 break;
1151 }
1152 /*FALLTHROUGH*/ default:
1153 *cpos = xlat_tty2scr((unsigned char) c) | curr_attr |
1154 (c <= 0x7F ? cset_attr[cset] : ATTR_ASCII);
1155 logtraffic((unsigned char) c, LGTYP_ASCII);
1156 cpos++;
1157 break;
374330e2 1158 }
32874aea 1159 curs.x++;
1160 if (curs.x == cols) {
1161 cpos--;
1162 curs.x--;
1163 wrapnext = TRUE;
e14a5a13 1164 }
32874aea 1165 seen_disp_event = 1;
374330e2 1166 break;
32874aea 1167
1168 case IGNORE_NEXT:
1169 termstate = TOPLEVEL;
374330e2 1170 break;
32874aea 1171 case OSC_MAYBE_ST:
1172 /*
1173 * This state is virtually identical to SEEN_ESC, with the
1174 * exception that we have an OSC sequence in the pipeline,
1175 * and _if_ we see a backslash, we process it.
1176 */
1177 if (c == '\\') {
1178 do_osc();
1179 termstate = TOPLEVEL;
1180 break;
e14a5a13 1181 }
32874aea 1182 /* else fall through */
1183 case SEEN_ESC:
1184 termstate = TOPLEVEL;
1185 switch (c) {
1186 case ' ': /* some weird sequence? */
1187 compatibility(VT220);
1188 termstate = IGNORE_NEXT;
1189 break;
1190 case '[': /* enter CSI mode */
1191 termstate = SEEN_CSI;
1192 esc_nargs = 1;
1193 esc_args[0] = ARG_DEFAULT;
1194 esc_query = FALSE;
1195 break;
1196 case ']': /* xterm escape sequences */
1197 /* Compatibility is nasty here, xterm, linux, decterm yuk! */
1198 compatibility(OTHER);
1199 termstate = SEEN_OSC;
1200 esc_args[0] = 0;
1201 break;
1202 case '(': /* should set GL */
1203 compatibility(VT100);
1204 termstate = SET_GL;
1205 break;
1206 case ')': /* should set GR */
1207 compatibility(VT100);
1208 termstate = SET_GR;
1209 break;
1210 case '7': /* save cursor */
1211 compatibility(VT100);
1212 save_cursor(TRUE);
1213 break;
1214 case '8': /* restore cursor */
1215 compatibility(VT100);
1216 save_cursor(FALSE);
1217 seen_disp_event = TRUE;
1218 break;
1219 case '=':
1220 compatibility(VT100);
1221 app_keypad_keys = TRUE;
1222 break;
1223 case '>':
1224 compatibility(VT100);
1225 app_keypad_keys = FALSE;
1226 break;
1227 case 'D': /* exactly equivalent to LF */
1228 compatibility(VT100);
1229 if (curs.y == marg_b)
1230 scroll(marg_t, marg_b, 1, TRUE);
1231 else if (curs.y < rows - 1)
1232 curs.y++;
1233 fix_cpos;
1234 wrapnext = FALSE;
1235 seen_disp_event = TRUE;
1236 break;
1237 case 'E': /* exactly equivalent to CR-LF */
1238 compatibility(VT100);
1239 curs.x = 0;
1240 if (curs.y == marg_b)
1241 scroll(marg_t, marg_b, 1, TRUE);
1242 else if (curs.y < rows - 1)
1243 curs.y++;
1244 fix_cpos;
1245 wrapnext = FALSE;
1246 seen_disp_event = TRUE;
1247 break;
1248 case 'M': /* reverse index - backwards LF */
1249 compatibility(VT100);
1250 if (curs.y == marg_t)
1251 scroll(marg_t, marg_b, -1, TRUE);
1252 else if (curs.y > 0)
1253 curs.y--;
1254 fix_cpos;
1255 wrapnext = FALSE;
1256 seen_disp_event = TRUE;
1257 break;
1258 case 'Z': /* terminal type query */
1259 compatibility(VT100);
1260 ldisc_send(id_string, strlen(id_string));
1261 break;
1262 case 'c': /* restore power-on settings */
1263 compatibility(VT100);
1264 power_on();
1265 if (reset_132) {
1266 request_resize(80, rows, 1);
1267 reset_132 = 0;
374330e2 1268 }
32874aea 1269 fix_cpos;
1270 disptop = 0;
1271 seen_disp_event = TRUE;
1272 break;
1273 case '#': /* ESC # 8 fills screen with Es :-) */
1274 compatibility(VT100);
1275 termstate = SEEN_ESCHASH;
1276 break;
1277 case 'H': /* set a tab */
1278 compatibility(VT100);
1279 tabs[curs.x] = TRUE;
1280 break;
374330e2 1281 }
1282 break;
32874aea 1283 case SEEN_CSI:
1284 termstate = TOPLEVEL; /* default */
1285 if (isdigit(c)) {
1286 if (esc_nargs <= ARGS_MAX) {
1287 if (esc_args[esc_nargs - 1] == ARG_DEFAULT)
1288 esc_args[esc_nargs - 1] = 0;
1289 esc_args[esc_nargs - 1] =
1290 10 * esc_args[esc_nargs - 1] + c - '0';
1291 }
1292 termstate = SEEN_CSI;
1293 } else if (c == ';') {
1294 if (++esc_nargs <= ARGS_MAX)
1295 esc_args[esc_nargs - 1] = ARG_DEFAULT;
1296 termstate = SEEN_CSI;
1297 } else if (c < '@') {
1298 if (esc_query)
1299 esc_query = -1;
1300 else if (c == '?')
1301 esc_query = TRUE;
1302 else
1303 esc_query = c;
1304 termstate = SEEN_CSI;
1305 } else
1306 switch (ANSI(c, esc_query)) {
1307 case 'A': /* move up N lines */
1308 move(curs.x, curs.y - def(esc_args[0], 1), 1);
1309 seen_disp_event = TRUE;
1310 break;
1311 case 'e': /* move down N lines */
1312 compatibility(ANSI);
1313 case 'B':
1314 move(curs.x, curs.y + def(esc_args[0], 1), 1);
1315 seen_disp_event = TRUE;
1316 break;
1317 case 'a': /* move right N cols */
1318 compatibility(ANSI);
1319 case ANSI('c', '>'): /* report xterm version */
1320 compatibility(OTHER);
1321 /* this reports xterm version 136 so that VIM can
1322 use the drag messages from the mouse reporting */
1323 ldisc_send("\033[>0;136;0c", 11);
1324 break;
1325 case 'C':
1326 move(curs.x + def(esc_args[0], 1), curs.y, 1);
1327 seen_disp_event = TRUE;
1328 break;
1329 case 'D': /* move left N cols */
1330 move(curs.x - def(esc_args[0], 1), curs.y, 1);
1331 seen_disp_event = TRUE;
1332 break;
1333 case 'E': /* move down N lines and CR */
1334 compatibility(ANSI);
1335 move(0, curs.y + def(esc_args[0], 1), 1);
1336 seen_disp_event = TRUE;
1337 break;
1338 case 'F': /* move up N lines and CR */
1339 compatibility(ANSI);
1340 move(0, curs.y - def(esc_args[0], 1), 1);
1341 seen_disp_event = TRUE;
1342 break;
1343 case 'G':
1344 case '`': /* set horizontal posn */
1345 compatibility(ANSI);
1346 move(def(esc_args[0], 1) - 1, curs.y, 0);
1347 seen_disp_event = TRUE;
1348 break;
1349 case 'd': /* set vertical posn */
1350 compatibility(ANSI);
1351 move(curs.x,
1352 (dec_om ? marg_t : 0) + def(esc_args[0],
1353 1) - 1,
1354 (dec_om ? 2 : 0));
1355 seen_disp_event = TRUE;
1356 break;
1357 case 'H':
1358 case 'f': /* set horz and vert posns at once */
1359 if (esc_nargs < 2)
1360 esc_args[1] = ARG_DEFAULT;
1361 move(def(esc_args[1], 1) - 1,
1362 (dec_om ? marg_t : 0) + def(esc_args[0],
1363 1) - 1,
1364 (dec_om ? 2 : 0));
1365 seen_disp_event = TRUE;
1366 break;
1367 case 'J': /* erase screen or parts of it */
1368 {
1369 unsigned int i = def(esc_args[0], 0) + 1;
1370 if (i > 3)
1371 i = 0;
1372 erase_lots(FALSE, !!(i & 2), !!(i & 1));
1373 }
1374 disptop = 0;
1375 seen_disp_event = TRUE;
1376 break;
1377 case 'K': /* erase line or parts of it */
1378 {
1379 unsigned int i = def(esc_args[0], 0) + 1;
1380 if (i > 3)
1381 i = 0;
1382 erase_lots(TRUE, !!(i & 2), !!(i & 1));
1383 }
1384 seen_disp_event = TRUE;
1385 break;
1386 case 'L': /* insert lines */
1387 compatibility(VT102);
1388 if (curs.y <= marg_b)
1389 scroll(curs.y, marg_b, -def(esc_args[0], 1),
1390 FALSE);
1391 fix_cpos;
1392 seen_disp_event = TRUE;
1393 break;
1394 case 'M': /* delete lines */
1395 compatibility(VT102);
1396 if (curs.y <= marg_b)
1397 scroll(curs.y, marg_b, def(esc_args[0], 1),
1398 TRUE);
1399 fix_cpos;
1400 seen_disp_event = TRUE;
1401 break;
1402 case '@': /* insert chars */
1403 /* XXX VTTEST says this is vt220, vt510 manual says vt102 */
1404 compatibility(VT102);
1405 insch(def(esc_args[0], 1));
1406 seen_disp_event = TRUE;
1407 break;
1408 case 'P': /* delete chars */
1409 compatibility(VT102);
1410 insch(-def(esc_args[0], 1));
1411 seen_disp_event = TRUE;
1412 break;
1413 case 'c': /* terminal type query */
1414 compatibility(VT100);
1415 /* This is the response for a VT102 */
1416 ldisc_send(id_string, strlen(id_string));
1417 break;
1418 case 'n': /* cursor position query */
1419 if (esc_args[0] == 6) {
1420 char buf[32];
1421 sprintf(buf, "\033[%d;%dR", curs.y + 1,
1422 curs.x + 1);
1423 ldisc_send(buf, strlen(buf));
1424 } else if (esc_args[0] == 5) {
1425 ldisc_send("\033[0n", 4);
1426 }
1427 break;
1428 case 'h': /* toggle modes to high */
1429 case ANSI_QUE('h'):
1430 compatibility(VT100);
1431 {
1432 int i;
1433 for (i = 0; i < esc_nargs; i++)
1434 toggle_mode(esc_args[i], esc_query, TRUE);
1435 }
1436 break;
1437 case 'l': /* toggle modes to low */
1438 case ANSI_QUE('l'):
1439 compatibility(VT100);
1440 {
1441 int i;
1442 for (i = 0; i < esc_nargs; i++)
1443 toggle_mode(esc_args[i], esc_query, FALSE);
1444 }
1445 break;
1446 case 'g': /* clear tabs */
1447 compatibility(VT100);
1448 if (esc_nargs == 1) {
1449 if (esc_args[0] == 0) {
1450 tabs[curs.x] = FALSE;
1451 } else if (esc_args[0] == 3) {
1452 int i;
1453 for (i = 0; i < cols; i++)
1454 tabs[i] = FALSE;
1455 }
1456 }
1457 break;
1458 case 'r': /* set scroll margins */
1459 compatibility(VT100);
1460 if (esc_nargs <= 2) {
1461 int top, bot;
1462 top = def(esc_args[0], 1) - 1;
1463 bot = (esc_nargs <= 1
1464 || esc_args[1] ==
1465 0 ? rows : def(esc_args[1], rows)) - 1;
1466 if (bot >= rows)
1467 bot = rows - 1;
1468 /* VTTEST Bug 9 - if region is less than 2 lines
1469 * don't change region.
1470 */
1471 if (bot - top > 0) {
1472 marg_t = top;
1473 marg_b = bot;
1474 curs.x = 0;
1475 /*
1476 * I used to think the cursor should be
1477 * placed at the top of the newly marginned
1478 * area. Apparently not: VMS TPU falls over
1479 * if so.
1480 *
1481 * Well actually it should for Origin mode - RDB
1482 */
1483 curs.y = (dec_om ? marg_t : 0);
1484 fix_cpos;
1485 seen_disp_event = TRUE;
1486 }
1487 }
1488 break;
1489 case 'm': /* set graphics rendition */
1490 {
1491 /*
1492 * A VT100 without the AVO only had one attribute, either
1493 * underline or reverse video depending on the cursor type,
1494 * this was selected by CSI 7m.
1495 *
1496 * case 2:
1497 * This is DIM on the VT100-AVO and VT102
1498 * case 5:
1499 * This is BLINK on the VT100-AVO and VT102+
1500 * case 8:
1501 * This is INVIS on the VT100-AVO and VT102
1502 * case 21:
1503 * This like 22 disables BOLD, DIM and INVIS
1504 *
1505 * The ANSI colours appear on any terminal that has colour
1506 * (obviously) but the interaction between sgr0 and the
1507 * colours varies but is usually related to the background
1508 * colour erase item.
1509 * The interaction between colour attributes and the mono
1510 * ones is also very implementation dependent.
1511 *
1512 * The 39 and 49 attributes are likely to be unimplemented.
1513 */
1514 int i;
1515 for (i = 0; i < esc_nargs; i++) {
1516 switch (def(esc_args[i], 0)) {
1517 case 0: /* restore defaults */
1518 curr_attr = ATTR_DEFAULT;
1519 break;
1520 case 1: /* enable bold */
1521 compatibility(VT100AVO);
1522 curr_attr |= ATTR_BOLD;
1523 break;
1524 case 21: /* (enable double underline) */
1525 compatibility(OTHER);
1526 case 4: /* enable underline */
1527 compatibility(VT100AVO);
1528 curr_attr |= ATTR_UNDER;
1529 break;
1530 case 5: /* enable blink */
1531 compatibility(VT100AVO);
1532 curr_attr |= ATTR_BLINK;
1533 break;
1534 case 7: /* enable reverse video */
1535 curr_attr |= ATTR_REVERSE;
1536 break;
1537 case 22: /* disable bold */
1538 compatibility2(OTHER, VT220);
1539 curr_attr &= ~ATTR_BOLD;
1540 break;
1541 case 24: /* disable underline */
1542 compatibility2(OTHER, VT220);
1543 curr_attr &= ~ATTR_UNDER;
1544 break;
1545 case 25: /* disable blink */
1546 compatibility2(OTHER, VT220);
1547 curr_attr &= ~ATTR_BLINK;
1548 break;
1549 case 27: /* disable reverse video */
1550 compatibility2(OTHER, VT220);
1551 curr_attr &= ~ATTR_REVERSE;
1552 break;
1553 case 30:
1554 case 31:
1555 case 32:
1556 case 33:
1557 case 34:
1558 case 35:
1559 case 36:
1560 case 37:
1561 /* foreground */
1562 curr_attr &= ~ATTR_FGMASK;
1563 curr_attr |=
1564 (esc_args[i] - 30) << ATTR_FGSHIFT;
1565 break;
1566 case 39: /* default-foreground */
1567 curr_attr &= ~ATTR_FGMASK;
1568 curr_attr |= ATTR_DEFFG;
1569 break;
1570 case 40:
1571 case 41:
1572 case 42:
1573 case 43:
1574 case 44:
1575 case 45:
1576 case 46:
1577 case 47:
1578 /* background */
1579 curr_attr &= ~ATTR_BGMASK;
1580 curr_attr |=
1581 (esc_args[i] - 40) << ATTR_BGSHIFT;
1582 break;
1583 case 49: /* default-background */
1584 curr_attr &= ~ATTR_BGMASK;
1585 curr_attr |= ATTR_DEFBG;
1586 break;
1587 }
1588 }
1589 if (use_bce)
1590 erase_char =
1591 (' ' |
1592 (curr_attr &
1593 (ATTR_FGMASK | ATTR_BGMASK |
1594 ATTR_BLINK)));
1595 }
1596 break;
1597 case 's': /* save cursor */
1598 save_cursor(TRUE);
1599 break;
1600 case 'u': /* restore cursor */
1601 save_cursor(FALSE);
1602 seen_disp_event = TRUE;
1603 break;
1604 case 't': /* set page size - ie window height */
374330e2 1605 /*
32874aea 1606 * VT340/VT420 sequence DECSLPP, DEC only allows values
1607 * 24/25/36/48/72/144 other emulators (eg dtterm) use
1608 * illegal values (eg first arg 1..9) for window changing
1609 * and reports.
1610 */
1611 compatibility(VT340TEXT);
1612 if (esc_nargs <= 1
1613 && (esc_args[0] < 1 || esc_args[0] >= 24)) {
1614 request_resize(cols, def(esc_args[0], 24), 0);
1615 deselect();
1616 }
1617 break;
1618 case ANSI('|', '*'):
1619 /* VT420 sequence DECSNLS
1620 * Set number of lines on screen
1621 * VT420 uses VGA like hardware and can support any size in
1622 * reasonable range (24..49 AIUI) with no default specified.
1623 */
1624 compatibility(VT420);
1625 if (esc_nargs == 1 && esc_args[0] > 0) {
1626 request_resize(cols,
1627 def(esc_args[0], cfg.height),
1628 0);
1629 deselect();
1630 }
1631 break;
1632 case ANSI('|', '$'):
1633 /* VT340/VT420 sequence DECSCPP
1634 * Set number of columns per page
1635 * Docs imply range is only 80 or 132, but I'll allow any.
1636 */
1637 compatibility(VT340TEXT);
1638 if (esc_nargs <= 1) {
1639 request_resize(def(esc_args[0], cfg.width),
1640 rows, 0);
1641 deselect();
1642 }
1643 break;
1644 case 'X': /* write N spaces w/o moving cursor */
1645 /* XXX VTTEST says this is vt220, vt510 manual says vt100 */
1646 compatibility(ANSIMIN);
1647 {
1648 int n = def(esc_args[0], 1);
1649 pos cursplus;
1650 unsigned long *p = cpos;
1651 if (n > cols - curs.x)
1652 n = cols - curs.x;
1653 cursplus = curs;
1654 cursplus.x += n;
1655 check_selection(curs, cursplus);
1656 while (n--)
1657 *p++ = erase_char;
1658 seen_disp_event = TRUE;
1659 }
1660 break;
1661 case 'x': /* report terminal characteristics */
1662 compatibility(VT100);
1663 {
1664 char buf[32];
1665 int i = def(esc_args[0], 0);
1666 if (i == 0 || i == 1) {
1667 strcpy(buf, "\033[2;1;1;112;112;1;0x");
1668 buf[2] += i;
1669 ldisc_send(buf, 20);
1670 }
1671 }
1672 break;
1673 case ANSI('L', '='):
1674 compatibility(OTHER);
1675 use_bce = (esc_args[0] <= 0);
1676 erase_char = ERASE_CHAR;
1677 if (use_bce)
1678 erase_char =
1679 (' ' |
1680 (curr_attr &
1681 (ATTR_FGMASK | ATTR_BGMASK)));
1682 break;
1683 case ANSI('E', '='):
1684 compatibility(OTHER);
1685 blink_is_real = (esc_args[0] >= 1);
1686 break;
1687 case ANSI('p', '"'):
1688 /* Allow the host to make this emulator a 'perfect' VT102.
1689 * This first appeared in the VT220, but we do need to get
1690 * back to PuTTY mode so I won't check it.
e14a5a13 1691 *
32874aea 1692 * The arg in 40..42 are a PuTTY extension.
1693 * The 2nd arg, 8bit vs 7bit is not checked.
1694 *
1695 * Setting VT102 mode should also change the Fkeys to
1696 * generate PF* codes as a real VT102 has no Fkeys.
1697 * The VT220 does this, F11..F13 become ESC,BS,LF other Fkeys
1698 * send nothing.
1699 *
1700 * Note ESC c will NOT change this!
374330e2 1701 */
32874aea 1702
1703 switch (esc_args[0]) {
1704 case 61:
1705 compatibility_level &= ~TM_VTXXX;
1706 compatibility_level |= TM_VT102;
374330e2 1707 break;
32874aea 1708 case 62:
1709 compatibility_level &= ~TM_VTXXX;
1710 compatibility_level |= TM_VT220;
1711 break;
1712
1713 default:
1714 if (esc_args[0] > 60 && esc_args[0] < 70)
1715 compatibility_level |= TM_VTXXX;
1716 break;
1717
1718 case 40:
1719 compatibility_level &= TM_VTXXX;
374330e2 1720 break;
32874aea 1721 case 41:
1722 compatibility_level = TM_PUTTY;
374330e2 1723 break;
32874aea 1724 case 42:
1725 compatibility_level = TM_SCOANSI;
374330e2 1726 break;
32874aea 1727
1728 case ARG_DEFAULT:
1729 compatibility_level = TM_PUTTY;
1730 break;
1731 case 50:
1732 break;
1733 }
1734
1735 /* Change the response to CSI c */
1736 if (esc_args[0] == 50) {
1737 int i;
1738 char lbuf[64];
1739 strcpy(id_string, "\033[?");
1740 for (i = 1; i < esc_nargs; i++) {
1741 if (i != 1)
1742 strcat(id_string, ";");
1743 sprintf(lbuf, "%d", esc_args[i]);
1744 strcat(id_string, lbuf);
1745 }
1746 strcat(id_string, "c");
1747 }
1748#if 0
1749 /* Is this a good idea ?
1750 * Well we should do a soft reset at this point ...
1751 */
1752 if (!has_compat(VT420) && has_compat(VT100)) {
1753 if (reset_132)
1754 request_resize(132, 24, 1);
1755 else
1756 request_resize(80, 24, 1);
374330e2 1757 }
32874aea 1758#endif
1759 break;
374330e2 1760 }
374330e2 1761 break;
32874aea 1762 case SET_GL:
1763 case SET_GR:
1764 /* VT100 only here, checked above */
1765 switch (c) {
1766 case 'A':
1767 cset_attr[termstate == SET_GL ? 0 : 1] = ATTR_GBCHR;
1768 break;
1769 case '0':
1770 cset_attr[termstate == SET_GL ? 0 : 1] = ATTR_LINEDRW;
1771 break;
1772 case 'B':
1773 default: /* specifically, 'B' */
1774 cset_attr[termstate == SET_GL ? 0 : 1] = ATTR_ASCII;
1775 break;
e14a5a13 1776 }
32874aea 1777 if (!has_compat(VT220) || c != '%')
1778 termstate = TOPLEVEL;
e14a5a13 1779 break;
32874aea 1780 case SEEN_OSC:
1781 osc_w = FALSE;
1782 switch (c) {
1783 case 'P': /* Linux palette sequence */
1784 termstate = SEEN_OSC_P;
1785 osc_strlen = 0;
1786 break;
1787 case 'R': /* Linux palette reset */
1788 palette_reset();
1789 term_invalidate();
1790 termstate = TOPLEVEL;
1791 break;
1792 case 'W': /* word-set */
1793 termstate = SEEN_OSC_W;
1794 osc_w = TRUE;
1795 break;
1796 case '0':
1797 case '1':
1798 case '2':
1799 case '3':
1800 case '4':
1801 case '5':
1802 case '6':
1803 case '7':
1804 case '8':
1805 case '9':
1806 esc_args[0] = 10 * esc_args[0] + c - '0';
1807 break;
1808 case 'L':
1809 /*
1810 * Grotty hack to support xterm and DECterm title
1811 * sequences concurrently.
1812 */
1813 if (esc_args[0] == 2) {
1814 esc_args[0] = 1;
1815 break;
1816 }
1817 /* else fall through */
1818 default:
1819 termstate = OSC_STRING;
1820 osc_strlen = 0;
e14a5a13 1821 }
1822 break;
32874aea 1823 case OSC_STRING:
1824 /*
1825 * This OSC stuff is EVIL. It takes just one character to get into
1826 * sysline mode and it's not initially obvious how to get out.
1827 * So I've added CR and LF as string aborts.
1828 * This shouldn't effect compatibility as I believe embedded
1829 * control characters are supposed to be interpreted (maybe?)
1830 * and they don't display anything useful anyway.
1831 *
1832 * -- RDB
e14a5a13 1833 */
32874aea 1834 if (c == '\n' || c == '\r') {
1835 termstate = TOPLEVEL;
1836 } else if (c == 0234 || c == '\007') {
1837 /*
1838 * These characters terminate the string; ST and BEL
1839 * terminate the sequence and trigger instant
1840 * processing of it, whereas ESC goes back to SEEN_ESC
1841 * mode unless it is followed by \, in which case it is
1842 * synonymous with ST in the first place.
1843 */
1844 do_osc();
1845 termstate = TOPLEVEL;
1846 } else if (c == '\033')
1847 termstate = OSC_MAYBE_ST;
1848 else if (osc_strlen < OSC_STR_MAX)
1849 osc_string[osc_strlen++] = c;
374330e2 1850 break;
32874aea 1851 case SEEN_OSC_P:
374330e2 1852 {
32874aea 1853 int max = (osc_strlen == 0 ? 21 : 16);
1854 int val;
1855 if (c >= '0' && c <= '9')
1856 val = c - '0';
1857 else if (c >= 'A' && c <= 'A' + max - 10)
1858 val = c - 'A' + 10;
1859 else if (c >= 'a' && c <= 'a' + max - 10)
1860 val = c - 'a' + 10;
1861 else
1862 termstate = TOPLEVEL;
1863 osc_string[osc_strlen++] = val;
1864 if (osc_strlen >= 7) {
1865 palette_set(osc_string[0],
1866 osc_string[1] * 16 + osc_string[2],
1867 osc_string[3] * 16 + osc_string[4],
1868 osc_string[5] * 16 + osc_string[6]);
1869 term_invalidate();
1870 termstate = TOPLEVEL;
374330e2 1871 }
1872 }
1873 break;
32874aea 1874 case SEEN_OSC_W:
1875 switch (c) {
1876 case '0':
1877 case '1':
1878 case '2':
1879 case '3':
1880 case '4':
1881 case '5':
1882 case '6':
1883 case '7':
1884 case '8':
1885 case '9':
1886 esc_args[0] = 10 * esc_args[0] + c - '0';
1887 break;
1888 default:
1889 termstate = OSC_STRING;
1890 osc_strlen = 0;
ec55b220 1891 }
32874aea 1892 break;
1893 case SEEN_ESCHASH:
1894 {
1895 unsigned long nlattr;
1896 unsigned long *ldata;
1897 int i, j;
1898 pos scrtop, scrbot;
1899
1900 switch (c) {
1901 case '8':
1902 for (i = 0; i < rows; i++) {
1903 ldata = lineptr(i);
1904 for (j = 0; j < cols; j++)
1905 ldata[j] = ATTR_DEFAULT | 'E';
1906 ldata[cols] = 0;
1907 }
1908 disptop = 0;
1909 seen_disp_event = TRUE;
1910 scrtop.x = scrtop.y = 0;
1911 scrbot.x = 0;
1912 scrbot.y = rows;
1913 check_selection(scrtop, scrbot);
1914 break;
1915
1916 case '3':
1917 case '4':
1918 case '5':
1919 case '6':
1920 switch (c) {
1921 case '3':
1922 nlattr = LATTR_TOP;
1923 break;
1924 case '4':
1925 nlattr = LATTR_BOT;
1926 break;
1927 case '5':
1928 nlattr = LATTR_NORM;
1929 break;
1930 case '6':
1931 nlattr = LATTR_WIDE;
1932 break;
1933 }
ec55b220 1934
32874aea 1935 ldata = lineptr(curs.y);
1936 ldata[cols] &= ~LATTR_MODE;
1937 ldata[cols] |= nlattr;
1938 }
ec55b220 1939 }
374330e2 1940 termstate = TOPLEVEL;
1941 break;
32874aea 1942 case VT52_ESC:
374330e2 1943 termstate = TOPLEVEL;
32874aea 1944 seen_disp_event = TRUE;
c9def1b8 1945 switch (c) {
32874aea 1946 case 'A':
1947 move(curs.x, curs.y - 1, 1);
1948 break;
1949 case 'B':
1950 move(curs.x, curs.y + 1, 1);
1951 break;
1952 case 'C':
1953 move(curs.x + 1, curs.y, 1);
1954 break;
1955 case 'D':
1956 move(curs.x - 1, curs.y, 1);
1957 break;
1958 case 'F':
1959 cset_attr[cset = 0] = ATTR_LINEDRW;
1960 break;
1961 case 'G':
1962 cset_attr[cset = 0] = ATTR_ASCII;
1963 break;
1964 case 'H':
1965 move(0, 0, 0);
1966 break;
1967 case 'I':
1968 if (curs.y == 0)
1969 scroll(0, rows - 1, -1, TRUE);
1970 else if (curs.y > 0)
1971 curs.y--;
1972 fix_cpos;
1973 wrapnext = FALSE;
1974 break;
1975 case 'J':
1976 erase_lots(FALSE, FALSE, TRUE);
4facdf84 1977 disptop = 0;
c9def1b8 1978 break;
32874aea 1979 case 'K':
1980 erase_lots(TRUE, FALSE, TRUE);
1981 break;
1982 case 'V':
1983 /* XXX Print cursor line */
1984 break;
1985 case 'W':
1986 /* XXX Start controller mode */
1987 break;
1988 case 'X':
1989 /* XXX Stop controller mode */
1990 break;
1991 case 'Y':
1992 termstate = VT52_Y1;
1993 break;
1994 case 'Z':
1995 ldisc_send("\033/Z", 3);
1996 break;
1997 case '=':
1998 app_keypad_keys = TRUE;
1999 break;
2000 case '>':
2001 app_keypad_keys = FALSE;
2002 break;
2003 case '<':
2004 /* XXX This should switch to VT100 mode not current or default
2005 * VT mode. But this will only have effect in a VT220+
2006 * emulation.
2007 */
2008 vt52_mode = FALSE;
2009 break;
2010 case '^':
2011 /* XXX Enter auto print mode */
2012 break;
2013 case '_':
2014 /* XXX Exit auto print mode */
2015 break;
2016 case ']':
2017 /* XXX Print screen */
2018 break;
c9def1b8 2019 }
e14a5a13 2020 break;
32874aea 2021 case VT52_Y1:
2022 termstate = VT52_Y2;
2023 move(curs.x, c - ' ', 0);
e14a5a13 2024 break;
32874aea 2025 case VT52_Y2:
2026 termstate = TOPLEVEL;
2027 move(c - ' ', curs.y, 0);
e14a5a13 2028 break;
2029 }
4facdf84 2030 if (selstate != NO_SELECTION) {
2031 pos cursplus = curs;
2032 incpos(cursplus);
32874aea 2033 check_selection(curs, cursplus);
4facdf84 2034 }
374330e2 2035 }
c9def1b8 2036 inbuf_head = 0;
374330e2 2037}
2038
2039/*
2040 * Compare two lines to determine whether they are sufficiently
2041 * alike to scroll-optimise one to the other. Return the degree of
2042 * similarity.
2043 */
32874aea 2044static int linecmp(unsigned long *a, unsigned long *b)
2045{
374330e2 2046 int i, n;
2047
32874aea 2048 for (i = n = 0; i < cols; i++)
374330e2 2049 n += (*a++ == *b++);
2050 return n;
2051}
2052
2053/*
2054 * Given a context, update the window. Out of paranoia, we don't
2055 * allow WM_PAINT responses to do scrolling optimisations.
2056 */
32874aea 2057static void do_paint(Context ctx, int may_optimise)
2058{
374330e2 2059 int i, j, start, our_curs_y;
2060 unsigned long attr, rv, cursor;
4facdf84 2061 pos scrpos;
374330e2 2062 char ch[1024];
156686ef 2063 long ticks;
374330e2 2064
156686ef 2065 /*
2066 * Check the visual bell state.
2067 */
2068 if (in_vbell) {
2069 ticks = GetTickCount();
2070 if (ticks - vbell_timeout >= 0)
2071 in_vbell = FALSE;
2072 }
2073
2074 /* Depends on:
2075 * screen array, disptop, scrtop,
2076 * selection, rv,
2077 * cfg.blinkpc, blink_is_real, tblinker,
4facdf84 2078 * curs.y, curs.x, blinker, cfg.blink_cur, cursor_on, has_focus
156686ef 2079 */
e14a5a13 2080 if (cursor_on) {
32874aea 2081 if (has_focus) {
217dceef 2082 if (blinker || !cfg.blink_cur)
32874aea 2083 cursor = ATTR_ACTCURS;
2084 else
2085 cursor = 0;
2086 } else
2087 cursor = ATTR_PASCURS;
4e30ff69 2088 if (wrapnext)
2089 cursor |= ATTR_RIGHTCURS;
32874aea 2090 } else
156686ef 2091 cursor = 0;
2092 rv = (!rvideo ^ !in_vbell ? ATTR_REVERSE : 0);
4facdf84 2093 our_curs_y = curs.y - disptop;
374330e2 2094
32874aea 2095 for (i = 0; i < rows; i++) {
4facdf84 2096 unsigned long *ldata;
2097 int lattr;
2098 scrpos.y = i + disptop;
2099 ldata = lineptr(scrpos.y);
2100 lattr = (ldata[cols] & LATTR_MODE);
32874aea 2101 for (j = 0; j <= cols; j++) {
4facdf84 2102 unsigned long d = ldata[j];
32874aea 2103 int idx = i * (cols + 1) + j;
4facdf84 2104 scrpos.x = j;
32874aea 2105
2106 wanttext[idx] = lattr | (((d & ~ATTR_WRAPPED) ^ rv
4facdf84 2107 ^ (posle(selstart, scrpos) &&
2108 poslt(scrpos, selend) ?
2109 ATTR_REVERSE : 0)) |
32874aea 2110 (i == our_curs_y
2111 && j == curs.x ? cursor : 0));
c9def1b8 2112 if (blink_is_real) {
32874aea 2113 if (has_focus && tblinker && (wanttext[idx] & ATTR_BLINK)) {
c9def1b8 2114 wanttext[idx] &= ATTR_MASK;
2115 wanttext[idx] += ' ';
2116 }
2117 wanttext[idx] &= ~ATTR_BLINK;
2118 }
374330e2 2119 }
2120 }
2121
2122 /*
2123 * We would perform scrolling optimisations in here, if they
2124 * didn't have a nasty tendency to cause the whole sodding
2125 * program to hang for a second at speed-critical moments.
2126 * We'll leave it well alone...
2127 */
2128
32874aea 2129 for (i = 0; i < rows; i++) {
2130 int idx = i * (cols + 1);
2131 int lattr = (wanttext[idx + cols] & LATTR_MODE);
374330e2 2132 start = -1;
32874aea 2133 for (j = 0; j <= cols; j++, idx++) {
374330e2 2134 unsigned long t = wanttext[idx];
2135 int needs_update = (j < cols && t != disptext[idx]);
2136 int keep_going = (start != -1 && needs_update &&
2137 (t & ATTR_MASK) == attr &&
32874aea 2138 j - start < sizeof(ch));
374330e2 2139 if (start != -1 && !keep_going) {
32874aea 2140 do_text(ctx, start, i, ch, j - start, attr, lattr);
374330e2 2141 start = -1;
2142 }
2143 if (needs_update) {
2144 if (start == -1) {
2145 start = j;
2146 attr = t & ATTR_MASK;
2147 }
32874aea 2148 ch[j - start] = (char) (t & CHAR_MASK);
374330e2 2149 }
2150 disptext[idx] = t;
2151 }
2152 }
2153}
2154
2155/*
e14a5a13 2156 * Flick the switch that says if blinking things should be shown or hidden.
2157 */
2158
32874aea 2159void term_blink(int flg)
2160{
22dcdc3b 2161 static long last_blink = 0;
2162 static long last_tblink = 0;
e14a5a13 2163 long now, blink_diff;
2164
c9def1b8 2165 now = GetTickCount();
32874aea 2166 blink_diff = now - last_tblink;
c9def1b8 2167
2168 /* Make sure the text blinks no more than 2Hz */
32874aea 2169 if (blink_diff < 0 || blink_diff > 450) {
2170 last_tblink = now;
c9def1b8 2171 tblinker = !tblinker;
2172 }
2173
e14a5a13 2174 if (flg) {
32874aea 2175 blinker = 1;
2176 last_blink = now;
e14a5a13 2177 return;
32874aea 2178 }
e14a5a13 2179
32874aea 2180 blink_diff = now - last_blink;
e14a5a13 2181
22dcdc3b 2182 /* Make sure the cursor blinks no faster than GetCaretBlinkTime() */
32874aea 2183 if (blink_diff >= 0 && blink_diff < (long) GetCaretBlinkTime())
2184 return;
2185
e14a5a13 2186 last_blink = now;
2187 blinker = !blinker;
2188}
2189
2190/*
374330e2 2191 * Invalidate the whole screen so it will be repainted in full.
2192 */
32874aea 2193void term_invalidate(void)
2194{
374330e2 2195 int i;
2196
32874aea 2197 for (i = 0; i < rows * (cols + 1); i++)
374330e2 2198 disptext[i] = ATTR_INVALID;
2199}
2200
2201/*
2202 * Paint the window in response to a WM_PAINT message.
2203 */
32874aea 2204void term_paint(Context ctx, int l, int t, int r, int b)
2205{
374330e2 2206 int i, j, left, top, right, bottom;
2207
2208 left = l / font_width;
2209 right = (r - 1) / font_width;
2210 top = t / font_height;
2211 bottom = (b - 1) / font_height;
32874aea 2212 for (i = top; i <= bottom && i < rows; i++) {
2213 if ((disptext[i * (cols + 1) + cols] & LATTR_MODE) == LATTR_NORM)
2214 for (j = left; j <= right && j < cols; j++)
2215 disptext[i * (cols + 1) + j] = ATTR_INVALID;
c9def1b8 2216 else
32874aea 2217 for (j = left / 2; j <= right / 2 + 1 && j < cols; j++)
2218 disptext[i * (cols + 1) + j] = ATTR_INVALID;
c9def1b8 2219 }
374330e2 2220
e14a5a13 2221 /* This should happen soon enough, also for some reason it sometimes
2222 * fails to actually do anything when re-sizing ... painting the wrong
2223 * window perhaps ?
32874aea 2224 do_paint (ctx, FALSE);
2225 */
374330e2 2226}
2227
2228/*
2229 * Attempt to scroll the scrollback. The second parameter gives the
2230 * position we want to scroll to; the first is +1 to denote that
2231 * this position is relative to the beginning of the scrollback, -1
2232 * to denote it is relative to the end, and 0 to denote that it is
2233 * relative to the current position.
2234 */
32874aea 2235void term_scroll(int rel, int where)
2236{
4facdf84 2237 int sbtop = -count234(scrollback);
374330e2 2238
32874aea 2239 disptop = (rel < 0 ? 0 : rel > 0 ? sbtop : disptop) + where;
374330e2 2240 if (disptop < sbtop)
2241 disptop = sbtop;
4facdf84 2242 if (disptop > 0)
2243 disptop = 0;
374330e2 2244 update_sbar();
2245 term_update();
2246}
2247
32874aea 2248static void clipme(pos top, pos bottom, char *workbuf)
2249{
2250 char *wbptr; /* where next char goes within workbuf */
2251 int wblen = 0; /* workbuf len */
2252 int buflen; /* amount of memory allocated to workbuf */
bc1235d4 2253
32874aea 2254 if (workbuf != NULL) { /* user supplied buffer? */
2255 buflen = -1; /* assume buffer passed in is big enough */
2256 wbptr = workbuf; /* start filling here */
2257 } else
2258 buflen = 0; /* No data is available yet */
bc1235d4 2259
4facdf84 2260 while (poslt(top, bottom)) {
bc1235d4 2261 int nl = FALSE;
4facdf84 2262 unsigned long *ldata = lineptr(top.y);
260f3dec 2263 pos nlpos;
4facdf84 2264
2265 nlpos.y = top.y;
2266 nlpos.x = cols;
bc1235d4 2267
4facdf84 2268 if (!(ldata[cols] & ATTR_WRAPPED)) {
32874aea 2269 while ((ldata[nlpos.x - 1] & CHAR_MASK) == 0x20
2270 && poslt(top, nlpos)) decpos(nlpos);
4facdf84 2271 if (poslt(nlpos, bottom))
bc1235d4 2272 nl = TRUE;
2273 }
4facdf84 2274 while (poslt(top, bottom) && poslt(top, nlpos)) {
2275 int ch = (ldata[top.x] & CHAR_MASK);
2276 int set = (ldata[top.x] & CSET_MASK);
d3a22f79 2277
2278 /* VT Specials -> ISO8859-1 for Cut&Paste */
2279 static const unsigned char poorman2[] =
32874aea 2280 "* # HTFFCRLF\xB0 \xB1 NLVT+ + + + + - - - - - + + + + | <=>=PI!=\xA3 \xB7 ";
bc1235d4 2281
d3a22f79 2282 if (set && !cfg.rawcnp) {
32874aea 2283 if (set == ATTR_LINEDRW && ch >= 0x60 && ch < 0x7F) {
d3a22f79 2284 int x;
32874aea 2285 if ((x = poorman2[2 * (ch - 0x60) + 1]) == ' ')
2286 x = 0;
2287 ch = (x << 8) + poorman2[2 * (ch - 0x60)];
2288 }
d3a22f79 2289 }
bc1235d4 2290
32874aea 2291 while (ch != 0) {
2292 if (cfg.rawcnp || !!(ch & 0xE0)) {
2293 if (wblen == buflen) {
2294 workbuf = srealloc(workbuf, buflen += 100);
2295 wbptr = workbuf + wblen;
d3a22f79 2296 }
2297 wblen++;
2298 *wbptr++ = (unsigned char) ch;
2299 }
32874aea 2300 ch >>= 8;
bc1235d4 2301 }
4facdf84 2302 top.x++;
bc1235d4 2303 }
2304 if (nl) {
2305 int i;
32874aea 2306 for (i = 0; i < sizeof(sel_nl); i++) {
2307 if (wblen == buflen) {
bc1235d4 2308 workbuf = srealloc(workbuf, buflen += 100);
2309 wbptr = workbuf + wblen;
2310 }
32874aea 2311 wblen++;
bc1235d4 2312 *wbptr++ = sel_nl[i];
2313 }
2314 }
4facdf84 2315 top.y++;
2316 top.x = 0;
bc1235d4 2317 }
32874aea 2318 write_clip(workbuf, wblen, FALSE); /* transfer to clipboard */
2319 if (buflen > 0) /* indicates we allocated this buffer */
bc1235d4 2320 sfree(workbuf);
2321
2322}
32874aea 2323void term_copyall(void)
2324{
4facdf84 2325 pos top;
2326 top.y = -count234(scrollback);
2327 top.x = 0;
32874aea 2328 clipme(top, curs, NULL /* dynamic allocation */ );
bc1235d4 2329}
2330
374330e2 2331/*
2332 * Spread the selection outwards according to the selection mode.
2333 */
32874aea 2334static pos sel_spread_half(pos p, int dir)
2335{
4facdf84 2336 unsigned long *ldata;
374330e2 2337 short wvalue;
2338
4facdf84 2339 ldata = lineptr(p.y);
374330e2 2340
2341 switch (selmode) {
2342 case SM_CHAR:
2343 /*
2344 * In this mode, every character is a separate unit, except
2345 * for runs of spaces at the end of a non-wrapping line.
2346 */
4facdf84 2347 if (!(ldata[cols] & ATTR_WRAPPED)) {
32874aea 2348 unsigned long *q = ldata + cols;
4facdf84 2349 while (q > ldata && (q[-1] & CHAR_MASK) == 0x20)
374330e2 2350 q--;
32874aea 2351 if (q == ldata + cols)
374330e2 2352 q--;
32874aea 2353 if (p.x >= q - ldata)
2354 p.x = (dir == -1 ? q - ldata : cols - 1);
374330e2 2355 }
2356 break;
2357 case SM_WORD:
2358 /*
2359 * In this mode, the units are maximal runs of characters
2360 * whose `wordness' has the same value.
2361 */
4facdf84 2362 wvalue = wordness[ldata[p.x] & CHAR_MASK];
374330e2 2363 if (dir == +1) {
32874aea 2364 while (p.x < cols
2365 && wordness[ldata[p.x + 1] & CHAR_MASK] ==
2366 wvalue) p.x++;
374330e2 2367 } else {
32874aea 2368 while (p.x > 0
2369 && wordness[ldata[p.x - 1] & CHAR_MASK] ==
2370 wvalue) p.x--;
374330e2 2371 }
2372 break;
2373 case SM_LINE:
2374 /*
2375 * In this mode, every line is a unit.
2376 */
4facdf84 2377 p.x = (dir == -1 ? 0 : cols - 1);
374330e2 2378 break;
2379 }
2380 return p;
2381}
2382
32874aea 2383static void sel_spread(void)
2384{
2385 selstart = sel_spread_half(selstart, -1);
4facdf84 2386 decpos(selend);
32874aea 2387 selend = sel_spread_half(selend, +1);
4facdf84 2388 incpos(selend);
374330e2 2389}
2390
32874aea 2391void term_mouse(Mouse_Button b, Mouse_Action a, int x, int y,
2392 int shift, int ctrl)
2393{
4facdf84 2394 pos selpoint;
2395 unsigned long *ldata;
32874aea 2396
2397 if (y < 0)
2398 y = 0;
2399 if (y >= rows)
2400 y = rows - 1;
2401 if (x < 0) {
2402 if (y > 0) {
2403 x = cols - 1;
2404 y--;
2405 } else
2406 x = 0;
094ed2a6 2407 }
32874aea 2408 if (x >= cols)
2409 x = cols - 1;
37508af4 2410
4facdf84 2411 selpoint.y = y + disptop;
2412 selpoint.x = x;
2413 ldata = lineptr(selpoint.y);
32874aea 2414 if ((ldata[cols] & LATTR_MODE) != LATTR_NORM)
4facdf84 2415 selpoint.x /= 2;
374330e2 2416
01c034ad 2417 if (xterm_mouse) {
2418 int encstate = 0, r, c;
2419 char abuf[16];
2420 static int is_down = 0;
2421
32874aea 2422 switch (b) {
01c034ad 2423 case MBT_LEFT:
32874aea 2424 encstate = 0x20; /* left button down */
01c034ad 2425 break;
2426 case MBT_MIDDLE:
2427 encstate = 0x21;
2428 break;
2429 case MBT_RIGHT:
2430 encstate = 0x22;
2431 break;
2432 case MBT_WHEEL_UP:
2433 encstate = 0x60;
2434 break;
2435 case MBT_WHEEL_DOWN:
2436 encstate = 0x61;
2437 break;
2438 }
32874aea 2439 switch (a) {
01c034ad 2440 case MA_DRAG:
2441 if (xterm_mouse == 1)
2442 return;
2443 encstate += 0x20;
2444 break;
2445 case MA_RELEASE:
2446 encstate = 0x23;
2447 is_down = 0;
2448 break;
2449 case MA_CLICK:
2450 if (is_down == b)
2451 return;
2452 is_down = b;
2453 break;
2454 }
2455 if (shift)
2456 encstate += 0x04;
2457 if (ctrl)
2458 encstate += 0x10;
2459 r = y + 33;
2460 c = x + 33;
2461
2462 sprintf(abuf, "\033[M%c%c%c", encstate, c, r);
2463 ldisc_send(abuf, 6);
2464 return;
2465 }
2466
2467 b = translate_button(b);
2468
2469 if (b == MBT_SELECT && a == MA_CLICK) {
374330e2 2470 deselect();
2471 selstate = ABOUT_TO;
2472 selanchor = selpoint;
2473 selmode = SM_CHAR;
01c034ad 2474 } else if (b == MBT_SELECT && (a == MA_2CLK || a == MA_3CLK)) {
374330e2 2475 deselect();
2476 selmode = (a == MA_2CLK ? SM_WORD : SM_LINE);
2477 selstate = DRAGGING;
2478 selstart = selanchor = selpoint;
4facdf84 2479 selend = selstart;
2480 incpos(selend);
374330e2 2481 sel_spread();
01c034ad 2482 } else if ((b == MBT_SELECT && a == MA_DRAG) ||
2483 (b == MBT_EXTEND && a != MA_RELEASE)) {
4facdf84 2484 if (selstate == ABOUT_TO && poseq(selanchor, selpoint))
374330e2 2485 return;
01c034ad 2486 if (b == MBT_EXTEND && a != MA_DRAG && selstate == SELECTED) {
32874aea 2487 if (posdiff(selpoint, selstart) <
2488 posdiff(selend, selstart) / 2) {
4facdf84 2489 selanchor = selend;
2490 decpos(selanchor);
2491 } else {
374330e2 2492 selanchor = selstart;
4facdf84 2493 }
374330e2 2494 selstate = DRAGGING;
2495 }
2496 if (selstate != ABOUT_TO && selstate != DRAGGING)
2497 selanchor = selpoint;
2498 selstate = DRAGGING;
4facdf84 2499 if (poslt(selpoint, selanchor)) {
374330e2 2500 selstart = selpoint;
4facdf84 2501 selend = selanchor;
2502 incpos(selend);
374330e2 2503 } else {
2504 selstart = selanchor;
4facdf84 2505 selend = selpoint;
a4450583 2506 incpos(selend);
374330e2 2507 }
2508 sel_spread();
01c034ad 2509 } else if ((b == MBT_SELECT || b == MBT_EXTEND) && a == MA_RELEASE) {
374330e2 2510 if (selstate == DRAGGING) {
2511 /*
2512 * We've completed a selection. We now transfer the
2513 * data to the clipboard.
2514 */
bc1235d4 2515 clipme(selstart, selend, selspace);
374330e2 2516 selstate = SELECTED;
2517 } else
2518 selstate = NO_SELECTION;
32874aea 2519 } else if (b == MBT_PASTE
2520 && (a == MA_CLICK || a == MA_2CLK || a == MA_3CLK)) {
374330e2 2521 char *data;
2522 int len;
2523
2524 get_clip((void **) &data, &len);
2525 if (data) {
2526 char *p, *q;
c9def1b8 2527
32874aea 2528 if (paste_buffer)
2529 sfree(paste_buffer);
c9def1b8 2530 paste_pos = paste_hold = paste_len = 0;
2531 paste_buffer = smalloc(len);
2532
374330e2 2533 p = q = data;
32874aea 2534 while (p < data + len) {
2535 while (p < data + len &&
2536 !(p <= data + len - sizeof(sel_nl) &&
374330e2 2537 !memcmp(p, sel_nl, sizeof(sel_nl))))
2538 p++;
14963b8f 2539
2540 {
2541 int i;
2542 unsigned char c;
32874aea 2543 for (i = 0; i < p - q; i++) {
2544 c = xlat_kbd2tty(q[i]);
c9def1b8 2545 paste_buffer[paste_len++] = c;
14963b8f 2546 }
2547 }
2548
32874aea 2549 if (p <= data + len - sizeof(sel_nl) &&
374330e2 2550 !memcmp(p, sel_nl, sizeof(sel_nl))) {
c9def1b8 2551 paste_buffer[paste_len++] = '\r';
374330e2 2552 p += sizeof(sel_nl);
2553 }
2554 q = p;
2555 }
c9def1b8 2556
2557 /* Assume a small paste will be OK in one go. */
32874aea 2558 if (paste_len < 256) {
2559 ldisc_send(paste_buffer, paste_len);
2560 if (paste_buffer)
2561 sfree(paste_buffer);
c9def1b8 2562 paste_buffer = 0;
32874aea 2563 paste_pos = paste_hold = paste_len = 0;
c9def1b8 2564 }
374330e2 2565 }
2566 get_clip(NULL, NULL);
2567 }
2568
2569 term_update();
2570}
2571
32874aea 2572void term_nopaste()
2573{
2574 if (paste_len == 0)
2575 return;
c9def1b8 2576 sfree(paste_buffer);
2577 paste_buffer = 0;
2578 paste_len = 0;
2579}
2580
32874aea 2581void term_paste()
2582{
8df7a775 2583 static long last_paste = 0;
c9def1b8 2584 long now, paste_diff;
2585
32874aea 2586 if (paste_len == 0)
2587 return;
c9def1b8 2588
2589 /* Don't wait forever to paste */
32874aea 2590 if (paste_hold) {
2591 now = GetTickCount();
2592 paste_diff = now - last_paste;
2593 if (paste_diff >= 0 && paste_diff < 450)
c9def1b8 2594 return;
2595 }
2596 paste_hold = 0;
2597
32874aea 2598 while (paste_pos < paste_len) {
8df7a775 2599 int n = 0;
2600 while (n + paste_pos < paste_len) {
2601 if (paste_buffer[paste_pos + n++] == '\r')
2602 break;
2603 }
32874aea 2604 ldisc_send(paste_buffer + paste_pos, n);
8df7a775 2605 paste_pos += n;
c9def1b8 2606
8df7a775 2607 if (paste_pos < paste_len) {
c9def1b8 2608 paste_hold = 1;
2609 return;
2610 }
2611 }
2612 sfree(paste_buffer);
2613 paste_buffer = 0;
2614 paste_len = 0;
2615}
2616
32874aea 2617static void deselect(void)
2618{
374330e2 2619 selstate = NO_SELECTION;
4facdf84 2620 selstart.x = selstart.y = selend.x = selend.y = 0;
374330e2 2621}
2622
32874aea 2623void term_deselect(void)
2624{
374330e2 2625 deselect();
2626 term_update();
2627}
fe50e814 2628
32874aea 2629int term_ldisc(int option)
2630{
2631 if (option == LD_ECHO)
2632 return term_echoing;
2633 if (option == LD_EDIT)
2634 return term_editing;
0965bee0 2635 return FALSE;
2636}
2637
fe50e814 2638/*
2639 * from_backend(), to get data from the backend for the terminal.
2640 */
32874aea 2641void from_backend(int is_stderr, char *data, int len)
2642{
fe50e814 2643 while (len--) {
2644 if (inbuf_head >= INBUF_SIZE)
2645 term_out();
2646 inbuf[inbuf_head++] = *data++;
2647 }
2648}
e1c8e0ed 2649
2650/*
2651 * Log session traffic.
2652 */
32874aea 2653void logtraffic(unsigned char c, int logmode)
2654{
e1c8e0ed 2655 if (cfg.logtype > 0) {
2656 if (cfg.logtype == logmode) {
2657 /* deferred open file from pgm start? */
32874aea 2658 if (!lgfp)
2659 logfopen();
2660 if (lgfp)
2661 fputc(c, lgfp);
2662 }
e1c8e0ed 2663 }
2664}
2665
2666/* open log file append/overwrite mode */
32874aea 2667void logfopen(void)
2668{
e1c8e0ed 2669 char buf[256];
2670 time_t t;
2671 struct tm *tm;
2672 char writemod[4];
2673
2674 if (!cfg.logtype)
2675 return;
32874aea 2676 sprintf(writemod, "wb"); /* default to rewrite */
2677 lgfp = fopen(cfg.logfilename, "r"); /* file already present? */
e1c8e0ed 2678 if (lgfp) {
2679 int i;
2680 fclose(lgfp);
2681 i = askappend(cfg.logfilename);
2682 if (i == 1)
2683 writemod[0] = 'a'; /* set append mode */
2684 else if (i == 0) { /* cancelled */
2685 lgfp = NULL;
32874aea 2686 cfg.logtype = 0; /* disable logging */
e1c8e0ed 2687 return;
2688 }
2689 }
2690
2691 lgfp = fopen(cfg.logfilename, writemod);
32874aea 2692 if (lgfp) { /* enter into event log */
e1c8e0ed 2693 sprintf(buf, "%s session log (%s mode) to file : ",
2694 (writemod[0] == 'a') ? "Appending" : "Writing new",
2695 (cfg.logtype == LGTYP_ASCII ? "ASCII" :
32874aea 2696 cfg.logtype == LGTYP_DEBUG ? "raw" : "<ukwn>"));
e1c8e0ed 2697 /* Make sure we do not exceed the output buffer size */
32874aea 2698 strncat(buf, cfg.logfilename, 128);
e1c8e0ed 2699 buf[strlen(buf)] = '\0';
2700 logevent(buf);
2701
32874aea 2702 /* --- write header line iinto log file */
2703 fputs("=~=~=~=~=~=~=~=~=~=~=~= PuTTY log ", lgfp);
e1c8e0ed 2704 time(&t);
2705 tm = localtime(&t);
2706 strftime(buf, 24, "%Y.%m.%d %H:%M:%S", tm);
32874aea 2707 fputs(buf, lgfp);
2708 fputs(" =~=~=~=~=~=~=~=~=~=~=~=\r\n", lgfp);
e1c8e0ed 2709 }
2710}
2711
32874aea 2712void logfclose(void)
2713{
2714 if (lgfp) {
2715 fclose(lgfp);
2716 lgfp = NULL;
2717 }
e1c8e0ed 2718}