]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - terminal.c
Tiny \n/\r fix in term_mouse.
[PuTTY.git] / terminal.c
1 #ifndef macintosh
2 #include <windows.h>
3 #endif /* not macintosh */
4
5 #include <string.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8
9 #include "putty.h"
10
11 static unsigned long *text;            /* buffer of text on terminal screen */
12 static unsigned long *scrtop;          /* top of working screen */
13 static unsigned long *disptop;         /* top of displayed screen */
14 static unsigned long *sbtop;           /* top of scrollback */
15 static unsigned long *cpos;            /* cursor position (convenience) */
16 static unsigned long *disptext;        /* buffer of text on real screen */
17 static unsigned long *wanttext;        /* buffer of text we want on screen */
18 static unsigned long *alttext;         /* buffer of text on alt. screen */
19
20 static unsigned char *selspace;        /* buffer for building selections in */
21
22 #define TSIZE (sizeof(*text))
23 #define fix_cpos  do { cpos = scrtop + curs_y * (cols+1) + curs_x; } while(0)
24
25 static unsigned long curr_attr, save_attr;
26
27 static int curs_x, curs_y;             /* cursor */
28 static int save_x, save_y;             /* saved cursor position */
29 static int marg_t, marg_b;             /* scroll margins */
30 static int dec_om;                     /* DEC origin mode flag */
31 static int lfhascr;                    /* Auto-cr mode flag */
32 static int wrap, wrapnext;             /* wrap flags */
33 static int insert;                     /* insert-mode flag */
34 static int cset;                       /* 0 or 1: which char set */
35 static int save_cset, save_csattr;     /* saved with cursor position */
36 static int rvideo;                     /* global reverse video flag */
37
38 static unsigned long cset_attr[2];
39
40 /*
41  * Saved settings on the alternate screen.
42  */
43 static int alt_x, alt_y, alt_om, alt_wrap, alt_wnext, alt_ins, alt_cset;
44 static int alt_t, alt_b;
45 static int alt_which;
46
47 #define ARGS_MAX 32                    /* max # of esc sequence arguments */
48 #define ARG_DEFAULT -1                 /* if an arg isn't specified */
49 #define def(a,d) ( (a) == ARG_DEFAULT ? (d) : (a) )
50 static int esc_args[ARGS_MAX];
51 static int esc_nargs;
52 static int esc_query;
53
54 #define OSC_STR_MAX 2048
55 static int osc_strlen;
56 static char osc_string[OSC_STR_MAX+1];
57 static int osc_w;
58
59 static unsigned char *tabs;
60
61 #define MAXNL 5
62 static int nl_count;
63
64 static enum {
65     TOPLEVEL, IGNORE_NEXT,
66     SEEN_ESC, SEEN_CSI, SEEN_GZD4, SEEN_G1D4,
67     SEEN_OSC, SEEN_OSC_P, SEEN_OSC_W, OSC_STRING, OSC_MAYBE_ST,
68     SEEN_ESCHASH,
69     SEEN_ESC_CONFUSED, SEEN_CSI_CONFUSED,
70 } termstate;
71
72 static enum {
73     NO_SELECTION, ABOUT_TO, DRAGGING, SELECTED
74 } selstate;
75 static enum {
76     SM_CHAR, SM_WORD, SM_LINE
77 } selmode;
78 static unsigned long *selstart, *selend, *selanchor;
79
80 static short wordness[256] = {
81     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 01 */
82     0,1,2,1,1,1,1,1,1,1,1,1,1,2,2,2, 2,2,2,2,2,2,2,2,2,2,1,1,1,1,1,1, /* 23 */
83     1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,2, /* 45 */
84     1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1, /* 67 */
85     1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 89 */
86     1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* AB */
87     2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2, /* CD */
88     2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2, /* EF */
89 };
90
91 static unsigned char sel_nl[] = SEL_NL;
92
93 /*
94  * Internal prototypes.
95  */
96 static void do_paint (Context, int);
97 static void erase_lots (int, int, int);
98 static void swap_screen (int);
99 static void update_sbar (void);
100 static void deselect (void);
101 static void scroll_display(int, int, int);
102
103 /*
104  * Set up power-on settings for the terminal.
105  */
106 static void power_on(void) {
107     curs_x = curs_y = alt_x = alt_y = save_x = save_y = 0;
108     alt_t = marg_t = 0;
109     if (rows != -1)
110         alt_b = marg_b = rows - 1;
111     else
112         alt_b = marg_b = 0;
113     if (cols != -1) {
114         int i;
115         for (i = 0; i < cols; i++)
116             tabs[i] = (i % 8 == 0 ? TRUE : FALSE);
117     }
118     alt_om = dec_om = cfg.dec_om;
119     alt_wnext = wrapnext = alt_ins = insert = FALSE;
120     alt_wrap = wrap = cfg.wrap_mode;
121     alt_cset = cset = 0;
122     cset_attr[0] = cset_attr[1] = ATTR_ASCII;
123     rvideo = 0;
124     lfhascr = cfg.lfhascr;
125     save_attr = curr_attr = ATTR_DEFAULT;
126     app_cursor_keys = cfg.app_cursor;
127     app_keypad_keys = cfg.app_keypad;
128     alt_which = 0;
129     {
130         int i;
131         for (i = 0; i < 256; i++)
132             wordness[i] = cfg.wordness[i];
133     }
134     if (text) {
135         swap_screen (1);
136         erase_lots (FALSE, TRUE, TRUE);
137         swap_screen (0);
138         erase_lots (FALSE, TRUE, TRUE);
139     }
140 }
141
142 /*
143  * Force a screen update.
144  */
145 void term_update(void) {
146     Context ctx;
147     ctx = get_ctx();
148     if (ctx) {
149         do_paint (ctx, TRUE);
150         free_ctx (ctx);
151         nl_count = 0;
152     }
153 }
154
155 /*
156  * Same as power_on(), but an external function.
157  */
158 void term_pwron(void) {
159     power_on();
160     fix_cpos;
161     disptop = scrtop;
162     deselect();
163     term_update();
164 }
165
166 /*
167  * Clear the scrollback.
168  */
169 void term_clrsb(void) {
170     disptop = sbtop = scrtop;
171     update_sbar();
172 }
173
174 /*
175  * Initialise the terminal.
176  */
177 void term_init(void) {
178     text = sbtop = scrtop = disptop = cpos = NULL;
179     disptext = wanttext = NULL;
180     tabs = NULL;
181     selspace = NULL;
182     deselect();
183     rows = cols = -1;
184     nl_count = 0;
185     power_on();
186 }
187
188 /*
189  * Set up the terminal for a given size.
190  */
191 void term_size(int newrows, int newcols, int newsavelines) {
192     unsigned long *newtext, *newdisp, *newwant, *newalt;
193     int i, j, crows, ccols;
194
195     if (newrows == rows && newcols == cols && newsavelines == savelines)
196         return;                        /* nothing to do */
197
198     alt_t = marg_t = 0;
199     alt_b = marg_b = newrows - 1;
200
201     newtext = smalloc ((newrows+newsavelines)*(newcols+1)*TSIZE);
202     disptop = newtext + newsavelines*(newcols+1);
203     for (i=0; i<(newrows+newsavelines)*(newcols+1); i++)
204         newtext[i] = ERASE_CHAR;
205     if (rows != -1) {
206         crows = rows + (scrtop - sbtop) / (cols+1);
207         if (crows > newrows+newsavelines)
208             crows = newrows+newsavelines;
209         ccols = (cols < newcols ? cols : newcols);
210         for (i=0; i<crows; i++) {
211             int oldidx = (rows + savelines - crows + i) * (cols+1);
212             int newidx = (newrows + newsavelines - crows + i) * (newcols+1);
213             for (j=0; j<ccols; j++)
214                 newtext[newidx+j] = text[oldidx+j];
215             newtext[newidx+newcols] =
216                 (cols == newcols ? text[oldidx+cols] : 0);
217         }
218         sbtop = disptop - (crows - newrows) * (newcols+1);
219         if (sbtop > disptop)
220             sbtop = disptop;
221     } else
222         sbtop = disptop;
223     scrtop = disptop;
224     sfree (text);
225     text = newtext;
226
227     newdisp = smalloc (newrows*(newcols+1)*TSIZE);
228     for (i=0; i<newrows*(newcols+1); i++)
229         newdisp[i] = ATTR_INVALID;
230     sfree (disptext);
231     disptext = newdisp;
232
233     newwant = smalloc (newrows*(newcols+1)*TSIZE);
234     for (i=0; i<newrows*(newcols+1); i++)
235         newwant[i] = ATTR_INVALID;
236     sfree (wanttext);
237     wanttext = newwant;
238
239     newalt = smalloc (newrows*(newcols+1)*TSIZE);
240     for (i=0; i<newrows*(newcols+1); i++)
241         newalt[i] = ERASE_CHAR;
242     sfree (alttext);
243     alttext = newalt;
244
245     sfree (selspace);
246     selspace = smalloc ( (newrows+newsavelines) * (newcols+sizeof(sel_nl)) );
247
248     tabs = srealloc (tabs, newcols*sizeof(*tabs));
249     {
250         int i;
251         for (i = (cols > 0 ? cols : 0); i < newcols; i++)
252             tabs[i] = (i % 8 == 0 ? TRUE : FALSE);
253     }
254
255     if (rows > 0)
256         curs_y += newrows - rows;
257     if (curs_y < 0)
258         curs_y = 0;
259     if (curs_y >= newrows)
260         curs_y = newrows-1;
261     if (curs_x >= newcols)
262         curs_x = newcols-1;
263     alt_x = alt_y = 0;
264     wrapnext = alt_wnext = FALSE;
265
266     rows = newrows;
267     cols = newcols;
268     savelines = newsavelines;
269     fix_cpos;
270
271     deselect();
272     update_sbar();
273     term_update();
274 }
275
276 /*
277  * Swap screens.
278  */
279 static void swap_screen (int which) {
280     int t;
281     unsigned long tt;
282
283     if (which == alt_which)
284         return;
285
286     alt_which = which;
287
288     for (t=0; t<rows*(cols+1); t++) {
289         tt = scrtop[t]; scrtop[t] = alttext[t]; alttext[t] = tt;
290     }
291
292     t = curs_x; curs_x = alt_x; alt_x = t;
293     t = curs_y; curs_y = alt_y; alt_y = t;
294     t = marg_t; marg_t = alt_t; alt_t = t;
295     t = marg_b; marg_b = alt_b; alt_b = t;
296     t = dec_om; dec_om = alt_om; alt_om = t;
297     t = wrap; wrap = alt_wrap; alt_wrap = t;
298     t = wrapnext; wrapnext = alt_wnext; alt_wnext = t;
299     t = insert; insert = alt_ins; alt_ins = t;
300     t = cset; cset = alt_cset; alt_cset = t;
301
302     fix_cpos;
303 }
304
305 /*
306  * Retrieve a character from `inbuf'.
307  */
308 static int inbuf_getc(void) {
309     if (inbuf_head == inbuf_reap)
310         return -1;                     /* EOF */
311     else {
312         int n = inbuf_reap;
313         inbuf_reap = (inbuf_reap+1) & INBUF_MASK;
314         return inbuf[n];
315     }
316 }
317
318 /*
319  * Update the scroll bar.
320  */
321 static void update_sbar(void) {
322     int min;
323
324     min = (sbtop - text) / (cols+1);
325     set_sbar ((scrtop - text) / (cols+1) + rows - min,
326               (disptop - text) / (cols+1) - min,
327               rows);
328 }
329
330 /*
331  * Check whether the region bounded by the two pointers intersects
332  * the scroll region, and de-select the on-screen selection if so.
333  */
334 static void check_selection (unsigned long *from, unsigned long *to) {
335     if (from < selend && selstart < to)
336         deselect();
337 }
338
339 /*
340  * Scroll the screen. (`lines' is +ve for scrolling forward, -ve
341  * for backward.) `sb' is TRUE if the scrolling is permitted to
342  * affect the scrollback buffer.
343  */
344 static void scroll (int topline, int botline, int lines, int sb) {
345     unsigned long *scroll_top;
346     int scroll_size, size, i;
347
348     scroll_top = scrtop + topline*(cols+1);
349     size = (lines < 0 ? -lines : lines) * (cols+1);
350     scroll_size = (botline - topline + 1) * (cols+1) - size;
351
352     if (lines > 0 && topline == 0 && botline == (rows-1) && sb) {
353         /*
354          * Since we're going to scroll the whole screen upwards,
355          * let's also affect the scrollback buffer.
356          */
357         sbtop -= lines * (cols+1);
358         if (sbtop < text)
359             sbtop = text;
360         scroll_size += scroll_top - sbtop;
361         scroll_top = sbtop;
362         update_sbar();
363     }
364
365     if (scroll_size < 0) {
366         size += scroll_size;
367         scroll_size = 0;
368     }
369
370     if (lines > 0) {
371         if (scroll_size)
372             memmove (scroll_top, scroll_top + size, scroll_size*TSIZE);
373         for (i = 0; i < size; i++)
374             scroll_top[i+scroll_size] = ERASE_CHAR;
375         if (selstart > scroll_top &&
376             selstart < scroll_top + size + scroll_size) {
377             selstart -= size;
378             if (selstart < scroll_top)
379                 selstart = scroll_top;
380         }
381         if (selend > scroll_top &&
382             selend < scroll_top + size + scroll_size) {
383             selend -= size;
384             if (selend < scroll_top)
385                 selend = scroll_top;
386         }
387     } else {
388         if (scroll_size)
389             memmove (scroll_top + size, scroll_top, scroll_size*TSIZE);
390         for (i = 0; i < size; i++)
391             scroll_top[i] = ERASE_CHAR;
392         if (selstart > scroll_top &&
393             selstart < scroll_top + size + scroll_size) {
394             selstart += size;
395             if (selstart > scroll_top + size + scroll_size)
396                 selstart = scroll_top + size + scroll_size;
397         }
398         if (selend > scroll_top &&
399             selend < scroll_top + size + scroll_size) {
400             selend += size;
401             if (selend > scroll_top + size + scroll_size)
402                 selend = scroll_top + size + scroll_size;
403         }
404     }
405 #ifdef OPTIMISE_SCROLL
406     scroll_display(topline, botline, lines);
407 #endif
408 }
409
410 #ifdef OPTIMISE_SCROLL
411 static void scroll_display(int topline, int botline, int lines) {
412     unsigned long *start, *end;
413     int distance, size, i;
414
415     start = disptext + topline * (cols + 1);
416     end = disptext + (botline + 1) * (cols + 1);
417     distance = (lines > 0 ? lines : -lines) * (cols + 1);
418     size = end - start - distance;
419     if (lines > 0) {
420         memmove(start, start + distance, size * TSIZE);
421         for (i = 0; i < distance; i++)
422             (start + size)[i] |= ATTR_INVALID;
423     } else {
424         memmove(start + distance, start, size * TSIZE);
425         for (i = 0; i < distance; i++)
426             start[i] |= ATTR_INVALID;
427     }
428     do_scroll(topline, botline, lines);
429 }
430 #endif /* OPTIMISE_SCROLL */
431     
432
433 /*
434  * Move the cursor to a given position, clipping at boundaries. We
435  * may or may not want to clip at the scroll margin: marg_clip is 0
436  * not to, 1 to disallow _passing_ the margins, and 2 to disallow
437  * even _being_ outside the margins.
438  */
439 static void move (int x, int y, int marg_clip) {
440     if (x < 0)
441         x = 0;
442     if (x >= cols)
443         x = cols-1;
444     if (marg_clip) {
445         if ((curs_y >= marg_t || marg_clip == 2) && y < marg_t)
446             y = marg_t;
447         if ((curs_y <= marg_b || marg_clip == 2) && y > marg_b)
448             y = marg_b;
449     }
450     if (y < 0)
451         y = 0;
452     if (y >= rows)
453         y = rows-1;
454     curs_x = x;
455     curs_y = y;
456     fix_cpos;
457     wrapnext = FALSE;
458 }
459
460 /*
461  * Save or restore the cursor and SGR mode.
462  */
463 static void save_cursor(int save) {
464     if (save) {
465         save_x = curs_x;
466         save_y = curs_y;
467         save_attr = curr_attr;
468         save_cset = cset;
469         save_csattr = cset_attr[cset];
470     } else {
471         curs_x = save_x;
472         curs_y = save_y;
473         curr_attr = save_attr;
474         cset = save_cset;
475         cset_attr[cset] = save_csattr;
476         fix_cpos;
477     }
478 }
479
480 /*
481  * Erase a large portion of the screen: the whole screen, or the
482  * whole line, or parts thereof.
483  */
484 static void erase_lots (int line_only, int from_begin, int to_end) {
485     unsigned long *startpos, *endpos;
486
487     if (line_only) {
488         startpos = cpos - curs_x;
489         endpos = startpos + cols+1;
490     } else {
491         startpos = scrtop;
492         endpos = startpos + rows * (cols+1);
493     }
494     if (!from_begin)
495         startpos = cpos;
496     if (!to_end)
497         endpos = cpos;
498     check_selection (startpos, endpos);
499     while (startpos < endpos)
500         *startpos++ = ERASE_CHAR;
501 }
502
503 /*
504  * Insert or delete characters within the current line. n is +ve if
505  * insertion is desired, and -ve for deletion.
506  */
507 static void insch (int n) {
508     int dir = (n < 0 ? -1 : +1);
509     int m;
510
511     n = (n < 0 ? -n : n);
512     if (n > cols - curs_x)
513         n = cols - curs_x;
514     m = cols - curs_x - n;
515     check_selection (cpos, cpos+n);
516     if (dir < 0) {
517         memmove (cpos, cpos+n, m*TSIZE);
518         while (n--)
519             cpos[m++] = ERASE_CHAR;
520     } else {
521         memmove (cpos+n, cpos, m*TSIZE);
522         while (n--)
523             cpos[n] = ERASE_CHAR;
524     }
525 }
526
527 /*
528  * Toggle terminal mode `mode' to state `state'. (`query' indicates
529  * whether the mode is a DEC private one or a normal one.)
530  */
531 static void toggle_mode (int mode, int query, int state) {
532     if (query) switch (mode) {
533       case 1:                          /* application cursor keys */
534         app_cursor_keys = state;
535         break;
536       case 3:                          /* 80/132 columns */
537         deselect();
538         request_resize (state ? 132 : 80, rows);
539         break;
540       case 5:                          /* reverse video */
541         rvideo = state;
542         disptop = scrtop;
543         break;
544       case 6:                          /* DEC origin mode */
545         dec_om = state;
546         break;
547       case 7:                          /* auto wrap */
548         wrap = state;
549         break;
550       case 47:                         /* alternate screen */
551         deselect();
552         swap_screen (state);
553         disptop = scrtop;
554         break;
555     } else switch (mode) {
556       case 4:                          /* set insert mode */
557         insert = state;
558         break;
559       case 20:                         /* line feed/new line mode */
560         lfhascr = state;
561         break;
562     }
563 }
564
565 /*
566  * Process an OSC sequence: set window title or icon name.
567  */
568 static void do_osc(void) {
569     if (osc_w) {
570         while (osc_strlen--)
571             wordness[(unsigned char)osc_string[osc_strlen]] = esc_args[0];
572     } else {
573         osc_string[osc_strlen] = '\0';
574         switch (esc_args[0]) {
575           case 0:
576           case 1:
577             set_icon (osc_string);
578             if (esc_args[0] == 1)
579                 break;
580             /* fall through: parameter 0 means set both */
581           case 2:
582           case 21:
583             set_title (osc_string);
584             break;
585         }
586     }
587 }
588
589 enum c0 {
590     NUL, SOH, STX, ETX, EOT, ENQ, ACK, BEL,
591     BS, HT, LF, VT, FF, CR, SO, SI,
592     DLE, DC1, DC2, DC3, DC4, NAK, SYN, ETB,
593     CAN, EM, SUB, ESC, IS1, IS2, IS3, IS4
594 };
595
596 enum c1 {
597     BPH = 0x82, NBH, IND, NEL, SSA, ESA,
598     HTS, HTJ, VTS, PLD, PLU, RI, SS1, SS2,
599     DCS, PU1, PU2, STS, CCH, MW, SPA, EPA,
600     SOS, SCI = 0x9a, CSI, ST, OSC, PM, APC
601 };
602
603 /*
604  * Remove everything currently in `inbuf' and stick it up on the
605  * in-memory display. There's a big state machine in here to
606  * process escape sequences...
607  */
608 void term_out(void) {
609     int c;
610     int must_update = FALSE;
611     int reprocess = FALSE;
612
613     while (reprocess || (c = inbuf_getc()) != -1) {
614 #ifdef LOG
615         if (!reprocess) {
616             static FILE *fp = NULL;
617             if (!fp) fp = fopen("putty.log", "wb");
618             if (fp) fputc (c, fp);
619         }
620 #endif
621         reprocess = FALSE;
622         switch (termstate) {
623           case TOPLEVEL:
624             do_toplevel:
625             switch (c) {
626               case '\005':             /* terminal type query */
627                 back->send ("\033[?1;2c", 7);
628                 break;
629               case '\007':
630                 beep();
631                 disptop = scrtop;
632                 must_update = TRUE;
633                 break;
634               case '\b':
635                 if (curs_x == 0 && curs_y > 0)
636                     curs_x = cols-1, curs_y--;
637                 else if (wrapnext)
638                     wrapnext = FALSE;
639                 else
640                     curs_x--;
641                 fix_cpos;
642                 disptop = scrtop;
643                 must_update = TRUE;
644                 break;
645               case '\015':
646                 curs_x = 0;
647                 wrapnext = FALSE;
648                 fix_cpos;
649                 disptop = scrtop;
650                 must_update = TRUE;
651                 break;
652               case '\013':
653               case '\014':
654               case '\012':
655               case 'IND':
656                 if (curs_y == marg_b)
657                     scroll (marg_t, marg_b, 1, TRUE);
658                 else if (curs_y < rows-1)
659                     curs_y++;
660                 if (lfhascr)
661                     curs_x = 0;
662                 fix_cpos;
663                 wrapnext = FALSE;
664                 disptop = scrtop;
665                 nl_count++;
666                 break;
667               case '\t':
668                 do {
669                     curs_x++;
670                 } while (curs_x < cols-1 && !tabs[curs_x]);
671                 if (curs_x >= cols)
672                     curs_x = cols-1;
673                 {
674                     unsigned long *old_cpos = cpos;
675                     fix_cpos;
676                     check_selection (old_cpos, cpos);
677                 }
678                 disptop = scrtop;
679                 must_update = TRUE;
680                 break;
681               case '\016':
682                 cset = 1;
683                 break;
684               case '\017':
685                 cset = 0;
686                 break;
687               case '\033':
688                 termstate = SEEN_ESC;
689                 break;
690               case NEL:                /* exactly equivalent to CR-LF */
691                 curs_x = 0;
692                 wrapnext = FALSE;
693                 if (curs_y == marg_b)
694                     scroll (marg_t, marg_b, 1, TRUE);
695                 else if (curs_y < rows-1)
696                     curs_y++;
697                 fix_cpos;
698                 wrapnext = FALSE;
699                 nl_count++;
700                 disptop = scrtop;
701                 break;
702               case HTS:                /* set a tab */
703                 tabs[curs_x] = TRUE;
704                 break;
705               case RI:                 /* reverse index - backwards LF */
706                 if (curs_y == marg_t)
707                     scroll (marg_t, marg_b, -1, TRUE);
708                 else if (curs_y > 0)
709                     curs_y--;
710                 fix_cpos;
711                 wrapnext = FALSE;
712                 disptop = scrtop;
713                 must_update = TRUE;
714                 break;
715               case SCI:                /* terminal type query */
716                 /* This sequence is standardised as something else entirely. */
717                 back->send ("\033[?6c", 5);
718                 break;
719               case 0233:
720                 termstate = SEEN_CSI;
721                 esc_nargs = 1;
722                 esc_args[0] = ARG_DEFAULT;
723                 esc_query = FALSE;
724                 break;
725               case 0235:
726                 termstate = SEEN_OSC;
727                 esc_args[0] = 0;
728                 break;
729               default:
730                 if (c >= ' ' && c < 0x7f || c >= 0xa0 ) {
731                     if (wrapnext) {
732                         cpos[1] = ATTR_WRAPPED;
733                         if (curs_y == marg_b)
734                             scroll (marg_t, marg_b, 1, TRUE);
735                         else if (curs_y < rows-1)
736                             curs_y++;
737                         curs_x = 0;
738                         fix_cpos;
739                         wrapnext = FALSE;
740                         nl_count++;
741                     }
742                     if (insert)
743                         insch (1);
744                     check_selection (cpos, cpos+1);
745                     *cpos++ = c | curr_attr | 
746                         (c <= 0x7F ? cset_attr[cset] : ATTR_ASCII);
747                     curs_x++;
748                     if (curs_x == cols) {
749                         cpos--;
750                         curs_x--;
751                         wrapnext = wrap;
752                     }
753                     disptop = scrtop;
754                 }
755                 break;
756             }
757             break;
758           case IGNORE_NEXT:
759             termstate = TOPLEVEL;
760             break;
761           case OSC_MAYBE_ST:
762             /*
763              * This state is virtually identical to SEEN_ESC, with the
764              * exception that we have an OSC sequence in the pipeline,
765              * and _if_ we see a backslash, we process it.
766              */
767             if (c == '\\') {
768                 do_osc();
769                 termstate = TOPLEVEL;
770                 break;
771             }
772             /* else fall through */
773           case SEEN_ESC:
774             /*
775              * According to ECMA-35, an escape sequence consists of
776              * ESC, a sequence (possibly empty) of intermediate bytes
777              * from column 02 (SPACE--/), and a final byte from
778              * columns 03-07 (0--~).
779              */
780             termstate = TOPLEVEL;
781             if (c >= 0x40 && c < 0x60) {
782                 /* Fe sequences -- C1 control as an escape sequence */
783                 c += 0x40;
784                 reprocess = TRUE;
785             } else switch (c) {
786                 /* nF sequences -- with intermediate bytes */
787               case '#':                /* Single control functions */
788                 termstate = SEEN_ESCHASH;
789                 break;
790               case '(':                /* GZD4: should set G0 */
791                 termstate = SEEN_GZD4;
792                 break;
793               case ')':                /* G1D4: should set G1 */
794                 termstate = SEEN_G1D4;
795                 break;
796                 /* Fp sequences -- private control functions */
797               case '7':                /* save cursor */
798                 save_cursor (TRUE);
799                 break;
800               case '8':                /* restore cursor */
801                 save_cursor (FALSE);
802                 disptop = scrtop;
803                 must_update = TRUE;
804                 break;
805               case '=':
806                 app_keypad_keys = TRUE;
807                 break;
808               case '>':
809                 app_keypad_keys = FALSE;
810                 break;
811                 /* Fs sequences -- standardised control functions */
812               case 'c':                /* RIS: restore power-on settings */
813                 power_on();
814                 fix_cpos;
815                 disptop = scrtop;
816                 must_update = TRUE;
817                 break;
818               default:
819                 termstate = SEEN_ESC_CONFUSED;
820                 reprocess = TRUE;
821                 break;
822             }
823             break;
824           case SEEN_ESC_CONFUSED:
825             /*
826              * We're in an escape sequence, but we no longer know what
827              * it means and we just want it to go away
828              */
829             termstate = TOPLEVEL;
830             if (c < 0x20 || c >= 0x7f)
831                 /*
832                  * ECMA-35 says this isn't allowed, so we can do what
833                  * we like.
834                  */
835                 reprocess = TRUE;
836             else if (c <= 0x30)
837                 /* Intermediate byte -- more to come */
838                 termstate = SEEN_ESC_CONFUSED;
839             /* Otherwise, that was a final byte and we're free! */
840             break;
841           case SEEN_CSI:
842             /*
843              * In theory, a control sequence consists of CSI, then a
844              * sequence (possibly empty) of parameter bytes (0--?)
845              * then a sequence (possibly empty) of intermediate bytes
846              * (SPACE--/), then a final byte (@--~).  We're rather
847              * more relaxed, and don't differentiate between parameter
848              * and intermediate bytes.
849              */
850             termstate = TOPLEVEL;      /* default */
851             switch (c) {
852               case '0': case '1': case '2': case '3': case '4':
853               case '5': case '6': case '7': case '8': case '9':
854                 if (esc_nargs <= ARGS_MAX) {
855                     if (esc_args[esc_nargs-1] == ARG_DEFAULT)
856                         esc_args[esc_nargs-1] = 0;
857                     esc_args[esc_nargs-1] =
858                         10 * esc_args[esc_nargs-1] + c - '0';
859                 }
860                 termstate = SEEN_CSI;
861                 break;
862               case ';':
863                 if (++esc_nargs <= ARGS_MAX)
864                     esc_args[esc_nargs-1] = ARG_DEFAULT;
865                 termstate = SEEN_CSI;
866                 break;
867               case '?':
868                 esc_query = TRUE;
869                 termstate = SEEN_CSI;
870                 break;
871               case 'A':                /* move up N lines */
872                 move (curs_x, curs_y - def(esc_args[0], 1), 1);
873                 disptop = scrtop;
874                 must_update = TRUE;
875                 break;
876               case 'B': case 'e':      /* move down N lines */
877                 move (curs_x, curs_y + def(esc_args[0], 1), 1);
878                 disptop = scrtop;
879                 must_update = TRUE;
880                 break;
881               case 'C': case 'a':      /* move right N cols */
882                 move (curs_x + def(esc_args[0], 1), curs_y, 1);
883                 disptop = scrtop;
884                 must_update = TRUE;
885                 break;
886               case 'D':                /* move left N cols */
887                 move (curs_x - def(esc_args[0], 1), curs_y, 1);
888                 disptop = scrtop;
889                 must_update = TRUE;
890                 break;
891               case 'E':                /* move down N lines and CR */
892                 move (0, curs_y + def(esc_args[0], 1), 1);
893                 disptop = scrtop;
894                 must_update = TRUE;
895                 break;
896               case 'F':                /* move up N lines and CR */
897                 move (0, curs_y - def(esc_args[0], 1), 1);
898                 disptop = scrtop;
899                 must_update = TRUE;
900                 break;
901               case 'G': case '`':      /* set horizontal posn */
902                 move (def(esc_args[0], 1) - 1, curs_y, 0);
903                 disptop = scrtop;
904                 must_update = TRUE;
905                 break;
906               case 'd':                /* set vertical posn */
907                 move (curs_x, (dec_om ? marg_t : 0) + def(esc_args[0], 1) - 1,
908                       (dec_om ? 2 : 0));
909                 disptop = scrtop;
910                 must_update = TRUE;
911                 break;
912               case 'H': case 'f':      /* set horz and vert posns at once */
913                 if (esc_nargs < 2)
914                     esc_args[1] = ARG_DEFAULT;
915                 move (def(esc_args[1], 1) - 1,
916                       (dec_om ? marg_t : 0) + def(esc_args[0], 1) - 1,
917                       (dec_om ? 2 : 0));
918                 disptop = scrtop;
919                 must_update = TRUE;
920                 break;
921               case 'J':                /* erase screen or parts of it */
922                 {
923                     unsigned int i = def(esc_args[0], 0) + 1;
924                     if (i > 3)
925                         i = 0;
926                     erase_lots(FALSE, !!(i & 2), !!(i & 1));
927                 }
928                 disptop = scrtop;
929                 must_update = TRUE;
930                 break;
931               case 'K':                /* erase line or parts of it */
932                 {
933                     unsigned int i = def(esc_args[0], 0) + 1;
934                     if (i > 3)
935                         i = 0;
936                     erase_lots(TRUE, !!(i & 2), !!(i & 1));
937                 }
938                 disptop = scrtop;
939                 must_update = TRUE;
940                 break;
941               case 'L':                /* insert lines */
942                 if (curs_y <= marg_b)
943                     scroll (curs_y, marg_b, -def(esc_args[0], 1), FALSE);
944                 disptop = scrtop;
945                 must_update = TRUE;
946                 break;
947               case 'M':                /* delete lines */
948                 if (curs_y <= marg_b)
949                     scroll (curs_y, marg_b, def(esc_args[0], 1), FALSE);
950                 disptop = scrtop;
951                 must_update = TRUE;
952                 break;
953               case '@':                /* insert chars */
954                 insch (def(esc_args[0], 1));
955                 disptop = scrtop;
956                 must_update = TRUE;
957                 break;
958               case 'P':                /* delete chars */
959                 insch (-def(esc_args[0], 1));
960                 disptop = scrtop;
961                 must_update = TRUE;
962                 break;
963               case 'c':                /* terminal type query */
964                 back->send ("\033[?6c", 5);
965                 break;
966               case 'n':                /* cursor position query */
967                 if (esc_args[0] == 6) {
968                     char buf[32];
969                     sprintf (buf, "\033[%d;%dR", curs_y + 1, curs_x + 1);
970                     back->send (buf, strlen(buf));
971                 }
972                 break;
973               case 'h':                /* toggle a mode to high */
974                 toggle_mode (esc_args[0], esc_query, TRUE);
975                 break;
976               case 'l':                /* toggle a mode to low */
977                 toggle_mode (esc_args[0], esc_query, FALSE);
978                 break;
979               case 'g':                /* clear tabs */
980                 if (esc_nargs == 1) {
981                     if (esc_args[0] == 0) {
982                         tabs[curs_x] = FALSE;
983                     } else if (esc_args[0] == 3) {
984                         int i;
985                         for (i = 0; i < cols; i++)
986                             tabs[i] = FALSE;
987                     }
988                 }
989                 break;
990               case 'r':                /* set scroll margins */
991                 if (esc_nargs <= 2) {
992                     int top, bot;
993                     top = def(esc_args[0], 1) - 1;
994                     if (top < 0)
995                         top = 0;
996                     bot = (esc_nargs == 1 ? rows :
997                            def(esc_args[1], rows)) - 1;
998                     if (bot >= rows)
999                         bot = rows-1;
1000                     if (top <= bot) {
1001                         marg_t = top;
1002                         marg_b = bot;
1003                         curs_x = 0;
1004                         /*
1005                          * I used to think the cursor should be
1006                          * placed at the top of the newly marginned
1007                          * area. Apparently not: VMS TPU falls over
1008                          * if so.
1009                          */
1010                         curs_y = 0;
1011                         fix_cpos;
1012                         disptop = scrtop;
1013                         must_update = TRUE;
1014                     }
1015                 }
1016                 break;
1017               case 'm':                /* set graphics rendition */
1018                 {
1019                     int i;
1020                     for (i=0; i<esc_nargs; i++) {
1021                         switch (def(esc_args[i], 0)) {
1022                           case 0:      /* restore defaults */
1023                             curr_attr = ATTR_DEFAULT; break;
1024                           case 1:      /* enable bold */
1025                             curr_attr |= ATTR_BOLD; break;
1026                           case 4:      /* enable underline */
1027                           case 21:     /* (enable double underline) */
1028                             curr_attr |= ATTR_UNDER; break;
1029                           case 7:      /* enable reverse video */
1030                             curr_attr |= ATTR_REVERSE; break;
1031                           case 22:     /* disable bold */
1032                             curr_attr &= ~ATTR_BOLD; break;
1033                           case 24:     /* disable underline */
1034                             curr_attr &= ~ATTR_UNDER; break;
1035                           case 27:     /* disable reverse video */
1036                             curr_attr &= ~ATTR_REVERSE; break;
1037                           case 30: case 31: case 32: case 33:
1038                           case 34: case 35: case 36: case 37:
1039                             /* foreground */
1040                             curr_attr &= ~ATTR_FGMASK;
1041                             curr_attr |= (esc_args[i] - 30) << ATTR_FGSHIFT;
1042                             break;
1043                           case 39:     /* default-foreground */
1044                             curr_attr &= ~ATTR_FGMASK;
1045                             curr_attr |= ATTR_DEFFG;
1046                             break;
1047                           case 40: case 41: case 42: case 43:
1048                           case 44: case 45: case 46: case 47:
1049                             /* background */
1050                             curr_attr &= ~ATTR_BGMASK;
1051                             curr_attr |= (esc_args[i] - 40) << ATTR_BGSHIFT;
1052                             break;
1053                           case 49:     /* default-background */
1054                             curr_attr &= ~ATTR_BGMASK;
1055                             curr_attr |= ATTR_DEFBG;
1056                             break;
1057                         }
1058                     }
1059                 }
1060                 break;
1061               case 's':                /* save cursor */
1062                 save_cursor (TRUE);
1063                 break;
1064               case 'u':                /* restore cursor */
1065                 save_cursor (FALSE);
1066                 disptop = scrtop;
1067                 must_update = TRUE;
1068                 break;
1069               case 't':                /* set page size - ie window height */
1070                 request_resize (cols, def(esc_args[0], 24));
1071                 deselect();
1072                 break;
1073               case 'X':                /* write N spaces w/o moving cursor */
1074                 {
1075                     int n = def(esc_args[0], 1);
1076                     unsigned long *p = cpos;
1077                     if (n > cols - curs_x)
1078                         n = cols - curs_x;
1079                     check_selection (cpos, cpos+n);
1080                     while (n--)
1081                         *p++ = ERASE_CHAR;
1082                     disptop = scrtop;
1083                     must_update = TRUE;
1084                 }
1085                 break;
1086               case 'x':                /* report terminal characteristics */
1087                 {
1088                     char buf[32];
1089                     int i = def(esc_args[0], 0);
1090                     if (i == 0 || i == 1) {
1091                         strcpy (buf, "\033[2;1;1;112;112;1;0x");
1092                         buf[2] += i;
1093                         back->send (buf, 20);
1094                     }
1095                 }
1096                 break;
1097               default:
1098                 termstate = SEEN_CSI_CONFUSED;
1099                 reprocess = TRUE;
1100                 break;
1101             }
1102             break;
1103           case SEEN_CSI_CONFUSED:
1104             termstate = TOPLEVEL;
1105             if (c < 0x20 || c >= 0x7f)
1106                 reprocess = TRUE;
1107             else if (c < 0x40)
1108                 termstate = SEEN_CSI_CONFUSED;
1109           case SEEN_GZD4:
1110           case SEEN_G1D4:
1111             switch (c) {
1112               case 'A':
1113                 cset_attr[termstate == SEEN_GZD4 ? 0 : 1] = ATTR_GBCHR;
1114                 break;
1115               case '0':
1116                 cset_attr[termstate == SEEN_GZD4 ? 0 : 1] = ATTR_LINEDRW;
1117                 break;
1118               default:                 /* specifically, 'B' */
1119                 cset_attr[termstate == SEEN_GZD4 ? 0 : 1] = ATTR_ASCII;
1120                 break;
1121             }
1122             termstate = TOPLEVEL;
1123             break;
1124           case SEEN_OSC:
1125             osc_w = FALSE;
1126             switch (c) {
1127               case '\005': case '\007': case '\b': case '\016': case '\017':
1128               case '\033': case 0233: case 0234: case 0235: case '\015':
1129               case '\013': case '\014': case '\012': case '\t':
1130                 termstate = TOPLEVEL;
1131                 goto do_toplevel;      /* hack... */
1132               case 'P':                /* Linux palette sequence */
1133                 termstate = SEEN_OSC_P;
1134                 osc_strlen = 0;
1135                 break;
1136               case 'R':                /* Linux palette reset */
1137                 palette_reset();
1138                 term_invalidate();
1139                 termstate = TOPLEVEL;
1140                 break;
1141               case 'W':                /* word-set */
1142                 termstate = SEEN_OSC_W;
1143                 osc_w = TRUE;
1144                 break;
1145               case '0': case '1': case '2': case '3': case '4':
1146               case '5': case '6': case '7': case '8': case '9':
1147                 esc_args[0] = 10 * esc_args[0] + c - '0';
1148                 break;
1149               case 'L':
1150                 /*
1151                  * Grotty hack to support xterm and DECterm title
1152                  * sequences concurrently.
1153                  */
1154                 if (esc_args[0] == 2) {
1155                     esc_args[0] = 1;
1156                     break;
1157                 }
1158                 /* else fall through */
1159               default:
1160                 termstate = OSC_STRING;
1161                 osc_strlen = 0;
1162             }
1163             break;
1164           case OSC_STRING:
1165             if (c == 0234 || c == '\007') {
1166                 /*
1167                  * These characters terminate the string; ST and BEL
1168                  * terminate the sequence and trigger instant
1169                  * processing of it, whereas ESC goes back to SEEN_ESC
1170                  * mode unless it is followed by \, in which case it is
1171                  * synonymous with ST in the first place.
1172                  */
1173                 do_osc();
1174                 termstate = TOPLEVEL;
1175             } else if (c == '\033')
1176                     termstate = OSC_MAYBE_ST;
1177             else if (osc_strlen < OSC_STR_MAX)
1178                 osc_string[osc_strlen++] = c;
1179             break;
1180           case SEEN_OSC_P:
1181             {
1182                 int max = (osc_strlen == 0 ? 21 : 16);
1183                 int val;
1184                 if (c >= '0' && c <= '9')
1185                     val = c - '0';
1186                 else if (c >= 'A' && c <= 'A'+max-10)
1187                     val = c - 'A' + 10;
1188                 else if (c >= 'a' && c <= 'a'+max-10)
1189                     val = c - 'a' + 10;
1190                 else
1191                     termstate = TOPLEVEL;
1192                 osc_string[osc_strlen++] = val;
1193                 if (osc_strlen >= 7) {
1194                     palette_set (osc_string[0],
1195                                  osc_string[1] * 16 + osc_string[2],
1196                                  osc_string[3] * 16 + osc_string[4],
1197                                  osc_string[5] * 16 + osc_string[6]);
1198                     term_invalidate();
1199                     termstate = TOPLEVEL;
1200                 }
1201             }
1202             break;
1203           case SEEN_OSC_W:
1204             switch (c) {
1205               case '\005': case '\007': case '\b': case '\016': case '\017':
1206               case '\033': case 0233: case 0234: case 0235: case '\015':
1207               case '\013': case '\014': case '\012': case '\t':
1208                 termstate = TOPLEVEL;
1209                 goto do_toplevel;      /* hack... */
1210               case '0': case '1': case '2': case '3': case '4':
1211               case '5': case '6': case '7': case '8': case '9':
1212                 esc_args[0] = 10 * esc_args[0] + c - '0';
1213                 break;
1214               default:
1215                 termstate = OSC_STRING;
1216                 osc_strlen = 0;
1217             }
1218             break;
1219           case SEEN_ESCHASH:
1220             if (c == '8') {
1221                 unsigned long *p = scrtop;
1222                 int n = rows * (cols+1);
1223                 while (n--)
1224                     *p++ = ATTR_DEFAULT | 'E';
1225                 disptop = scrtop;
1226                 must_update = TRUE;
1227                 check_selection (scrtop, scrtop + rows * (cols+1));
1228             }
1229             termstate = TOPLEVEL;
1230             break;
1231         }
1232         check_selection (cpos, cpos+1);
1233     }
1234         
1235     if (must_update || nl_count > MAXNL)
1236         term_update();
1237 }
1238
1239 /*
1240  * Compare two lines to determine whether they are sufficiently
1241  * alike to scroll-optimise one to the other. Return the degree of
1242  * similarity.
1243  */
1244 static int linecmp (unsigned long *a, unsigned long *b) {
1245     int i, n;
1246
1247     for (i=n=0; i < cols; i++)
1248         n += (*a++ == *b++);
1249     return n;
1250 }
1251
1252 /*
1253  * Given a context, update the window. Out of paranoia, we don't
1254  * allow WM_PAINT responses to do scrolling optimisations.
1255  */
1256 static void do_paint (Context ctx, int may_optimise){ 
1257     int i, j, start, our_curs_y;
1258     unsigned long attr, rv, cursor;
1259     char ch[1024];
1260
1261     cursor = (has_focus ? ATTR_ACTCURS : ATTR_PASCURS);
1262     rv = (rvideo ? ATTR_REVERSE : 0);
1263     our_curs_y = curs_y + (scrtop - disptop) / (cols+1);
1264
1265     for (i=0; i<rows; i++) {
1266         int idx = i*(cols+1);
1267         for (j=0; j<=cols; j++,idx++) {
1268             unsigned long *d = disptop+idx;
1269             wanttext[idx] = ((*d ^ rv
1270                               ^ (selstart <= d && d < selend ?
1271                                  ATTR_REVERSE : 0)) |
1272                              (i==our_curs_y && j==curs_x ? cursor : 0));
1273         }
1274     }
1275
1276     /*
1277      * We would perform scrolling optimisations in here, if they
1278      * didn't have a nasty tendency to cause the whole sodding
1279      * program to hang for a second at speed-critical moments.
1280      * We'll leave it well alone...
1281      */
1282
1283     for (i=0; i<rows; i++) {
1284         int idx = i*(cols+1);
1285         start = -1;
1286         for (j=0; j<=cols; j++,idx++) {
1287             unsigned long t = wanttext[idx];
1288             int needs_update = (j < cols && t != disptext[idx]);
1289             int keep_going = (start != -1 && needs_update &&
1290                               (t & ATTR_MASK) == attr &&
1291                               j-start < sizeof(ch));
1292             if (start != -1 && !keep_going) {
1293                 do_text (ctx, start, i, ch, j-start, attr);
1294                 start = -1;
1295             }
1296             if (needs_update) {
1297                 if (start == -1) {
1298                     start = j;
1299                     attr = t & ATTR_MASK;
1300                 }
1301                 ch[j-start] = (char) (t & CHAR_MASK);
1302             }
1303             disptext[idx] = t;
1304         }
1305     }
1306 }
1307
1308 /*
1309  * Invalidate the whole screen so it will be repainted in full.
1310  */
1311 void term_invalidate(void) {
1312     int i;
1313
1314     for (i=0; i<rows*(cols+1); i++)
1315         disptext[i] = ATTR_INVALID;
1316 }
1317
1318 /*
1319  * Paint the window in response to a WM_PAINT message.
1320  */
1321 void term_paint (Context ctx, int l, int t, int r, int b) {
1322     int i, j, left, top, right, bottom;
1323
1324     left = l / font_width;
1325     right = (r - 1) / font_width;
1326     top = t / font_height;
1327     bottom = (b - 1) / font_height;
1328     for (i = top; i <= bottom && i < rows ; i++)
1329       for (j = left; j <= right && j < cols ; j++)
1330             disptext[i*(cols+1)+j] = ATTR_INVALID;
1331
1332     do_paint (ctx, FALSE);
1333 }
1334
1335 /*
1336  * Attempt to scroll the scrollback. The second parameter gives the
1337  * position we want to scroll to; the first is +1 to denote that
1338  * this position is relative to the beginning of the scrollback, -1
1339  * to denote it is relative to the end, and 0 to denote that it is
1340  * relative to the current position.
1341  */
1342 void term_scroll (int rel, int where) {
1343     int n = where * (cols+1);
1344 #ifdef OPTIMISE_SCROLL
1345     unsigned long *olddisptop = disptop;
1346     int shift;
1347 #endif /* OPTIMISE_SCROLL */
1348
1349     disptop = (rel < 0 ? scrtop :
1350                rel > 0 ? sbtop : disptop) + n;
1351     if (disptop < sbtop)
1352         disptop = sbtop;
1353     if (disptop > scrtop)
1354         disptop = scrtop;
1355     update_sbar();
1356 #ifdef OPTIMISE_SCROLL
1357     shift = (disptop - olddisptop) / (cols + 1);
1358     if (shift < rows && shift > -rows)
1359         scroll_display(0, rows - 1, shift);
1360 #endif /* OPTIMISE_SCROLL */
1361     term_update();
1362 }
1363
1364 /*
1365  * Spread the selection outwards according to the selection mode.
1366  */
1367 static unsigned long *sel_spread_half (unsigned long *p, int dir) {
1368     unsigned long *linestart, *lineend;
1369     int x;
1370     short wvalue;
1371
1372     x = (p - text) % (cols+1);
1373     linestart = p - x;
1374     lineend = linestart + cols;
1375
1376     switch (selmode) {
1377       case SM_CHAR:
1378         /*
1379          * In this mode, every character is a separate unit, except
1380          * for runs of spaces at the end of a non-wrapping line.
1381          */
1382         if (!(linestart[cols] & ATTR_WRAPPED)) {
1383             unsigned long *q = lineend;
1384             while (q > linestart && (q[-1] & CHAR_MASK) == 0x20)
1385                 q--;
1386             if (q == lineend)
1387                 q--;
1388             if (p >= q)
1389                 p = (dir == -1 ? q : lineend - 1);
1390         }
1391         break;
1392       case SM_WORD:
1393         /*
1394          * In this mode, the units are maximal runs of characters
1395          * whose `wordness' has the same value.
1396          */
1397         wvalue = wordness[*p & CHAR_MASK];
1398         if (dir == +1) {
1399             while (p < lineend && wordness[p[1] & CHAR_MASK] == wvalue)
1400                 p++;
1401         } else {
1402             while (p > linestart && wordness[p[-1] & CHAR_MASK] == wvalue)
1403                 p--;
1404         }
1405         break;
1406       case SM_LINE:
1407         /*
1408          * In this mode, every line is a unit.
1409          */
1410         p = (dir == -1 ? linestart : lineend - 1);
1411         break;
1412     }
1413     return p;
1414 }
1415
1416 static void sel_spread (void) {
1417     selstart = sel_spread_half (selstart, -1);
1418     selend = sel_spread_half (selend - 1, +1) + 1;
1419 }
1420
1421 void term_mouse (Mouse_Button b, Mouse_Action a, int x, int y) {
1422     unsigned long *selpoint;
1423     
1424     if (y<0) y = 0;
1425     if (y>=rows) y = rows-1;
1426     if (x<0) {
1427         if (y > 0) {
1428             x = cols-1;
1429             y--;
1430         } else
1431             x = 0;
1432     }
1433     if (x>=cols) x = cols-1;
1434
1435     selpoint = disptop + y * (cols+1) + x;
1436
1437     if (b == MB_SELECT && a == MA_CLICK) {
1438         deselect();
1439         selstate = ABOUT_TO;
1440         selanchor = selpoint;
1441         selmode = SM_CHAR;
1442     } else if (b == MB_SELECT && (a == MA_2CLK || a == MA_3CLK)) {
1443         deselect();
1444         selmode = (a == MA_2CLK ? SM_WORD : SM_LINE);
1445         selstate = DRAGGING;
1446         selstart = selanchor = selpoint;
1447         selend = selstart + 1;
1448         sel_spread();
1449     } else if ((b == MB_SELECT && a == MA_DRAG) ||
1450                (b == MB_EXTEND && a != MA_RELEASE)) {
1451         if (selstate == ABOUT_TO && selanchor == selpoint)
1452             return;
1453         if (b == MB_EXTEND && a != MA_DRAG && selstate == SELECTED) {
1454             if (selpoint-selstart < (selend-selstart)/2)
1455                 selanchor = selend - 1;
1456             else
1457                 selanchor = selstart;
1458             selstate = DRAGGING;
1459         }
1460         if (selstate != ABOUT_TO && selstate != DRAGGING)
1461             selanchor = selpoint;
1462         selstate = DRAGGING;
1463         if (selpoint < selanchor) {
1464             selstart = selpoint;
1465             selend = selanchor + 1;
1466         } else {
1467             selstart = selanchor;
1468             selend = selpoint + 1;
1469         }
1470         sel_spread();
1471     } else if ((b == MB_SELECT || b == MB_EXTEND) && a == MA_RELEASE) {
1472         if (selstate == DRAGGING) {
1473             /*
1474              * We've completed a selection. We now transfer the
1475              * data to the clipboard.
1476              */
1477             unsigned char *p = selspace;
1478             unsigned long *q = selstart;
1479
1480             while (q < selend) {
1481                 int nl = FALSE;
1482                 unsigned long *lineend = q - (q-text) % (cols+1) + cols;
1483                 unsigned long *nlpos = lineend;
1484
1485                 if (!(*nlpos & ATTR_WRAPPED)) {
1486                     while ((nlpos[-1] & CHAR_MASK) == 0x20 && nlpos > q)
1487                         nlpos--;
1488                     if (nlpos < selend)
1489                         nl = TRUE;
1490                 }
1491                 while (q < nlpos && q < selend)
1492                     *p++ = (unsigned char) (*q++ & CHAR_MASK);
1493                 if (nl) {
1494                     int i;
1495                     for (i=0; i<sizeof(sel_nl); i++)
1496                         *p++ = sel_nl[i];
1497                 }
1498                 q = lineend + 1;       /* start of next line */
1499             }
1500             write_clip (selspace, p - selspace);
1501             selstate = SELECTED;
1502         } else
1503             selstate = NO_SELECTION;
1504     } else if (b == MB_PASTE && (a==MA_CLICK || a==MA_2CLK || a==MA_3CLK)) {
1505         char *data;
1506         int len;
1507
1508         get_clip((void **) &data, &len);
1509         if (data) {
1510             char *p, *q;
1511             p = q = data;
1512             while (p < data+len) {
1513                 while (p < data+len &&
1514                        !(p <= data+len-sizeof(sel_nl) &&
1515                          !memcmp(p, sel_nl, sizeof(sel_nl))))
1516                     p++;
1517                 back->send (q, p-q);
1518                 if (p <= data+len-sizeof(sel_nl) &&
1519                     !memcmp(p, sel_nl, sizeof(sel_nl))) {
1520                     back->send ("\n", 1);
1521                     p += sizeof(sel_nl);
1522                 }
1523                 q = p;
1524             }
1525         }
1526         get_clip(NULL, NULL);
1527     }
1528
1529     term_update();
1530 }
1531
1532 static void deselect (void) {
1533     selstate = NO_SELECTION;
1534     selstart = selend = scrtop;
1535 }
1536
1537 void term_deselect (void) {
1538     deselect();
1539     term_update();
1540 }