]> asedeno.scripts.mit.edu Git - PuTTY.git/blobdiff - terminal.c
first pass
[PuTTY.git] / terminal.c
index d23cbe91a9786f40e7b2d32fcbcda3b2269aa865..c79944cda889cb8822be342b4557bebc38de85a9 100644 (file)
@@ -5,6 +5,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <ctype.h>
+#include <limits.h>
 
 #include <time.h>
 #include <assert.h>
@@ -65,7 +66,7 @@
 
 #define has_compat(x) ( ((CL_##x)&term->compatibility_level) != 0 )
 
-char *EMPTY_WINDOW_TITLE = "";
+const char *EMPTY_WINDOW_TITLE = "";
 
 const char sco2ansicolour[] = { 0, 4, 2, 6, 1, 5, 3, 7 };
 
@@ -98,6 +99,7 @@ const wchar_t sel_nl[] = SEL_NL;
 static void resizeline(Terminal *, termline *, int);
 static termline *lineptr(Terminal *, int, int, int);
 static void unlineptr(termline *);
+static void check_line_size(Terminal *, termline *);
 static void do_paint(Terminal *, Context, int);
 static void erase_lots(Terminal *, int, int, int);
 static int find_last_nonempty_line(Terminal *, tree234 *);
@@ -1053,8 +1055,25 @@ static termline *lineptr(Terminal *term, int y, int lineno, int screen)
     }
     assert(line != NULL);
 
-    resizeline(term, line, term->cols);
-    /* FIXME: should we sort the compressed scrollback out here? */
+    /*
+     * Here we resize lines to _at least_ the right length, but we
+     * don't truncate them. Truncation is done as a side effect of
+     * modifying the line.
+     *
+     * The point of this policy is to try to arrange that resizing the
+     * terminal window repeatedly - e.g. successive steps in an X11
+     * opaque window-resize drag, or resizing as a side effect of
+     * retiling by tiling WMs such as xmonad - does not throw away
+     * data gratuitously. Specifically, we want a sequence of resize
+     * operations with no terminal output between them to have the
+     * same effect as a single resize to the ultimate terminal size,
+     * and also (for the case in which xmonad narrows a window that's
+     * scrolling things) we want scrolling up new text at the bottom
+     * of a narrowed window to avoid truncating lines further up when
+     * the window is re-widened.
+     */
+    if (term->cols > line->cols)
+        resizeline(term, line, term->cols);
 
     return line;
 }
@@ -1062,35 +1081,51 @@ static termline *lineptr(Terminal *term, int y, int lineno, int screen)
 #define lineptr(x) (lineptr)(term,x,__LINE__,FALSE)
 #define scrlineptr(x) (lineptr)(term,x,__LINE__,TRUE)
 
+/*
+ * Coerce a termline to the terminal's current width. Unlike the
+ * optional resize in lineptr() above, this is potentially destructive
+ * of text, since it can shrink as well as grow the line.
+ *
+ * We call this whenever a termline is actually going to be modified.
+ * Helpfully, putting a single call to this function in check_boundary
+ * deals with _nearly_ all such cases, leaving only a few things like
+ * bulk erase and ESC#8 to handle separately.
+ */
+static void check_line_size(Terminal *term, termline *line)
+{
+    if (term->cols != line->cols)      /* trivial optimisation */
+        resizeline(term, line, term->cols);
+}
+
 static void term_schedule_tblink(Terminal *term);
 static void term_schedule_cblink(Terminal *term);
 
-static void term_timer(void *ctx, long now)
+static void term_timer(void *ctx, unsigned long now)
 {
     Terminal *term = (Terminal *)ctx;
     int update = FALSE;
 
-    if (term->tblink_pending && now - term->next_tblink >= 0) {
+    if (term->tblink_pending && now == term->next_tblink) {
        term->tblinker = !term->tblinker;
        term->tblink_pending = FALSE;
        term_schedule_tblink(term);
        update = TRUE;
     }
 
-    if (term->cblink_pending && now - term->next_cblink >= 0) {
+    if (term->cblink_pending && now == term->next_cblink) {
        term->cblinker = !term->cblinker;
        term->cblink_pending = FALSE;
        term_schedule_cblink(term);
        update = TRUE;
     }
 
-    if (term->in_vbell && now - term->vbell_end >= 0) {
+    if (term->in_vbell && now == term->vbell_end) {
        term->in_vbell = FALSE;
        update = TRUE;
     }
 
     if (update ||
-       (term->window_update_pending && now - term->next_update >= 0))
+       (term->window_update_pending && now == term->next_update))
        term_update(term);
 }
 
@@ -1224,7 +1259,10 @@ static void power_on(Terminal *term, int clear)
     term->alt_which = 0;
     term_print_finish(term);
     term->xterm_mouse = 0;
+    term->xterm_extended_mouse = 0;
+    term->urxvt_extended_mouse = 0;
     set_raw_mouse_mode(term->frontend, FALSE);
+    term->bracketed_paste = FALSE;
     {
        int i;
        for (i = 0; i < 256; i++)
@@ -1313,7 +1351,7 @@ void term_pwron(Terminal *term, int clear)
 {
     power_on(term, clear);
     if (term->ldisc)                  /* cause ldisc to notice changes */
-       ldisc_send(term->ldisc, NULL, 0, 0);
+       ldisc_echoedit_update(term->ldisc);
     term->disptop = 0;
     deselect(term);
     term_update(term);
@@ -1365,6 +1403,7 @@ void term_copy_stuff_from_conf(Terminal *term)
     term->no_remote_charset = conf_get_int(term->conf, CONF_no_remote_charset);
     term->no_remote_resize = conf_get_int(term->conf, CONF_no_remote_resize);
     term->no_remote_wintitle = conf_get_int(term->conf, CONF_no_remote_wintitle);
+    term->no_remote_clearscroll = conf_get_int(term->conf, CONF_no_remote_clearscroll);
     term->rawcnp = conf_get_int(term->conf, CONF_rawcnp);
     term->rect_select = conf_get_int(term->conf, CONF_rect_select);
     term->remote_qtitle_action = conf_get_int(term->conf, CONF_remote_qtitle_action);
@@ -1490,12 +1529,44 @@ void term_reconfig(Terminal *term, Conf *conf)
 void term_clrsb(Terminal *term)
 {
     unsigned char *line;
+    int i;
+
+    /*
+     * Scroll forward to the current screen, if we were back in the
+     * scrollback somewhere until now.
+     */
     term->disptop = 0;
+
+    /*
+     * Clear the actual scrollback.
+     */
     while ((line = delpos234(term->scrollback, 0)) != NULL) {
        sfree(line);            /* this is compressed data, not a termline */
     }
+
+    /*
+     * When clearing the scrollback, we also truncate any termlines on
+     * the current screen which have remembered data from a previous
+     * larger window size. Rationale: clearing the scrollback is
+     * sometimes done to protect privacy, so the user intention is
+     * specifically that we should not retain evidence of what
+     * previously happened in the terminal, and that ought to include
+     * evidence to the right as well as evidence above.
+     */
+    for (i = 0; i < term->rows; i++)
+        check_line_size(term, scrlineptr(i));
+
+    /*
+     * There are now no lines of real scrollback which can be pulled
+     * back into the screen by a resize, and no lines of the alternate
+     * screen which should be displayed as if part of the scrollback.
+     */
     term->tempsblines = 0;
     term->alt_sblines = 0;
+
+    /*
+     * Update the scrollbar to reflect the new state of the world.
+     */
     update_sbar(term);
 }
 
@@ -1521,7 +1592,6 @@ Terminal *term_init(Conf *myconf, struct unicode_data *ucsdata,
     term->cblink_pending = term->tblink_pending = FALSE;
     term->paste_buffer = NULL;
     term->paste_len = 0;
-    term->last_paste = 0;
     bufchain_init(&term->inbuf);
     bufchain_init(&term->printer_buf);
     term->printing = term->only_printing = FALSE;
@@ -1618,10 +1688,14 @@ void term_free(Terminal *term)
     for (i = 0; i < term->bidi_cache_size; i++) {
        sfree(term->pre_bidi_cache[i].chars);
        sfree(term->post_bidi_cache[i].chars);
+        sfree(term->post_bidi_cache[i].forward);
+        sfree(term->post_bidi_cache[i].backward);
     }
     sfree(term->pre_bidi_cache);
     sfree(term->post_bidi_cache);
 
+    sfree(term->tabs);
+
     expire_timer_context(term);
 
     conf_free(term->conf);
@@ -1710,7 +1784,8 @@ void term_size(Terminal *term, int newrows, int newcols, int newsavelines)
     while (term->rows > newrows) {
        if (term->curs.y < term->rows - 1) {
            /* delete bottom row, unless it contains the cursor */
-           sfree(delpos234(term->screen, term->rows - 1));
+            line = delpos234(term->screen, term->rows - 1);
+            freeline(line);
        } else {
            /* push top row to scrollback */
            line = delpos234(term->screen, 0);
@@ -1972,7 +2047,7 @@ static void check_selection(Terminal *term, pos from, pos to)
 static void scroll(Terminal *term, int topline, int botline, int lines, int sb)
 {
     termline *line;
-    int i, seltop;
+    int i, seltop, scrollwinsize;
 #ifdef OPTIMISE_SCROLL
     int olddisptop, shift;
 #endif /* OPTIMISE_SCROLL */
@@ -1984,8 +2059,14 @@ static void scroll(Terminal *term, int topline, int botline, int lines, int sb)
     olddisptop = term->disptop;
     shift = lines;
 #endif /* OPTIMISE_SCROLL */
+
+    scrollwinsize = botline - topline + 1;
+
     if (lines < 0) {
-       while (lines < 0) {
+        lines = -lines;
+        if (lines > scrollwinsize)
+            lines = scrollwinsize;
+       while (lines-- > 0) {
            line = delpos234(term->screen, botline);
             resizeline(term, line, term->cols);
            for (i = 0; i < term->cols; i++)
@@ -2007,11 +2088,11 @@ static void scroll(Terminal *term, int topline, int botline, int lines, int sb)
                    term->selend.x = 0;
                }
            }
-
-           lines++;
        }
     } else {
-       while (lines > 0) {
+        if (lines > scrollwinsize)
+            lines = scrollwinsize;
+       while (lines-- > 0) {
            line = delpos234(term->screen, topline);
 #ifdef TERM_CC_DIAGS
            cc_check(line);
@@ -2097,8 +2178,6 @@ static void scroll(Terminal *term, int topline, int botline, int lines, int sb)
                    }
                }
            }
-
-           lines--;
        }
     }
 #ifdef OPTIMISE_SCROLL
@@ -2266,10 +2345,11 @@ static void check_boundary(Terminal *term, int x, int y)
     termline *ldata;
 
     /* Validate input coordinates, just in case. */
-    if (x == 0 || x > term->cols)
+    if (x <= 0 || x > term->cols)
        return;
 
     ldata = scrlineptr(y);
+    check_line_size(term, ldata);
     if (x == term->cols) {
        ldata->lattr &= ~LATTR_WRAPPED2;
     } else {
@@ -2340,6 +2420,7 @@ static void erase_lots(Terminal *term,
     } else {
        termline *ldata = scrlineptr(start.y);
        while (poslt(start, end)) {
+            check_line_size(term, ldata);
            if (start.x == term->cols) {
                if (!erase_lattr)
                    ldata->lattr &= ~(LATTR_WRAPPED | LATTR_WRAPPED2);
@@ -2369,16 +2450,51 @@ static void insch(Terminal *term, int n)
 {
     int dir = (n < 0 ? -1 : +1);
     int m, j;
-    pos cursplus;
+    pos eol;
     termline *ldata;
 
     n = (n < 0 ? -n : n);
     if (n > term->cols - term->curs.x)
        n = term->cols - term->curs.x;
     m = term->cols - term->curs.x - n;
-    cursplus.y = term->curs.y;
-    cursplus.x = term->curs.x + n;
-    check_selection(term, term->curs, cursplus);
+
+    /*
+     * We must de-highlight the selection if it overlaps any part of
+     * the region affected by this operation, i.e. the region from the
+     * current cursor position to end-of-line, _unless_ the entirety
+     * of the selection is going to be moved to the left or right by
+     * this operation but otherwise unchanged, in which case we can
+     * simply move the highlight with the text.
+     */
+    eol.y = term->curs.y;
+    eol.x = term->cols;
+    if (poslt(term->curs, term->selend) && poslt(term->selstart, eol)) {
+        pos okstart = term->curs;
+        pos okend = eol;
+        if (dir > 0) {
+            /* Insertion: n characters at EOL will be splatted. */
+            okend.x -= n;
+        } else {
+            /* Deletion: n characters at cursor position will be splatted. */
+            okstart.x += n;
+        }
+        if (posle(okstart, term->selstart) && posle(term->selend, okend)) {
+            /* Selection is contained entirely in the interval
+             * [okstart,okend), so we need only adjust the selection
+             * bounds. */
+            term->selstart.x += dir * n;
+            term->selend.x += dir * n;
+            assert(term->selstart.x >= term->curs.x);
+            assert(term->selstart.x < term->cols);
+            assert(term->selend.x > term->curs.x);
+            assert(term->selend.x <= term->cols);
+        } else {
+            /* Selection is not wholly contained in that interval, so
+             * we must unhighlight it. */
+            deselect(term);
+        }
+    }
+
     check_boundary(term, term->curs.x, term->curs.y);
     if (dir < 0)
        check_boundary(term, term->curs.x + n, term->curs.y);
@@ -2460,7 +2576,7 @@ static void toggle_mode(Terminal *term, int mode, int query, int state)
          case 10:                     /* DECEDM: set local edit mode */
            term->term_editing = state;
            if (term->ldisc)           /* cause ldisc to notice changes */
-               ldisc_send(term->ldisc, NULL, 0, 0);
+               ldisc_echoedit_update(term->ldisc);
            break;
          case 25:                     /* DECTCEM: enable/disable cursor */
            compatibility2(OTHER, VT220);
@@ -2471,7 +2587,8 @@ static void toggle_mode(Terminal *term, int mode, int query, int state)
            compatibility(OTHER);
            deselect(term);
            swap_screen(term, term->no_alt_screen ? 0 : state, FALSE, FALSE);
-           term->disptop = 0;
+            if (term->scroll_on_disp)
+                term->disptop = 0;
            break;
          case 1000:                   /* xterm mouse 1 (normal) */
            term->xterm_mouse = state ? 1 : 0;
@@ -2481,11 +2598,18 @@ static void toggle_mode(Terminal *term, int mode, int query, int state)
            term->xterm_mouse = state ? 2 : 0;
            set_raw_mouse_mode(term->frontend, state);
            break;
+         case 1006:                   /* xterm extended mouse */
+           term->xterm_extended_mouse = state ? 1 : 0;
+           break;
+         case 1015:                   /* urxvt extended mouse */
+           term->urxvt_extended_mouse = state ? 1 : 0;
+           break;
          case 1047:                   /* alternate screen */
            compatibility(OTHER);
            deselect(term);
            swap_screen(term, term->no_alt_screen ? 0 : state, TRUE, TRUE);
-           term->disptop = 0;
+            if (term->scroll_on_disp)
+                term->disptop = 0;
            break;
          case 1048:                   /* save/restore cursor */
            if (!term->no_alt_screen)
@@ -2501,7 +2625,11 @@ static void toggle_mode(Terminal *term, int mode, int query, int state)
            swap_screen(term, term->no_alt_screen ? 0 : state, TRUE, FALSE);
            if (!state && !term->no_alt_screen)
                save_cursor(term, state);
-           term->disptop = 0;
+            if (term->scroll_on_disp)
+                term->disptop = 0;
+           break;
+         case 2004:                   /* xterm bracketed paste */
+           term->bracketed_paste = state ? TRUE : FALSE;
            break;
     } else
        switch (mode) {
@@ -2512,7 +2640,7 @@ static void toggle_mode(Terminal *term, int mode, int query, int state)
          case 12:                     /* SRM: set echo mode */
            term->term_echoing = !state;
            if (term->ldisc)           /* cause ldisc to notice changes */
-               ldisc_send(term->ldisc, NULL, 0, 0);
+               ldisc_echoedit_update(term->ldisc);
            break;
          case 20:                     /* LNM: Return sends ... */
            term->cr_lf_return = state;
@@ -2946,7 +3074,6 @@ static void term_out(Terminal *term)
                term->curs.x = 0;
                term->wrapnext = FALSE;
                seen_disp_event(term);
-               term->paste_hold = 0;
 
                if (term->crhaslf) {
                    if (term->curs.y == term->marg_b)
@@ -2961,7 +3088,8 @@ static void term_out(Terminal *term)
                if (has_compat(SCOANSI)) {
                    move(term, 0, 0, 0);
                    erase_lots(term, FALSE, FALSE, TRUE);
-                   term->disptop = 0;
+                    if (term->scroll_on_disp)
+                        term->disptop = 0;
                    term->wrapnext = FALSE;
                    seen_disp_event(term);
                    break;
@@ -2977,7 +3105,6 @@ static void term_out(Terminal *term)
                    term->curs.x = 0;
                term->wrapnext = FALSE;
                seen_disp_event(term);
-               term->paste_hold = 0;
                if (term->logctx)
                    logtraffic(term->logctx, (unsigned char) c, LGTYP_ASCII);
                break;
@@ -3236,13 +3363,14 @@ static void term_out(Terminal *term)
                    compatibility(VT100);
                    power_on(term, TRUE);
                    if (term->ldisc)   /* cause ldisc to notice changes */
-                       ldisc_send(term->ldisc, NULL, 0, 0);
+                       ldisc_echoedit_update(term->ldisc);
                    if (term->reset_132) {
                        if (!term->no_remote_resize)
                            request_resize(term->frontend, 80, term->rows);
                        term->reset_132 = 0;
                    }
-                   term->disptop = 0;
+                    if (term->scroll_on_disp)
+                        term->disptop = 0;
                    seen_disp_event(term);
                    break;
                  case 'H':            /* HTS: set a tab */
@@ -3259,6 +3387,7 @@ static void term_out(Terminal *term)
 
                        for (i = 0; i < term->rows; i++) {
                            ldata = scrlineptr(i);
+                            check_line_size(term, ldata);
                            for (j = 0; j < term->cols; j++) {
                                copy_termchar(ldata, j,
                                              &term->basic_erase_char);
@@ -3266,7 +3395,8 @@ static void term_out(Terminal *term)
                            }
                            ldata->lattr = LATTR_NORM;
                        }
-                       term->disptop = 0;
+                        if (term->scroll_on_disp)
+                            term->disptop = 0;
                        seen_disp_event(term);
                        scrtop.x = scrtop.y = 0;
                        scrbot.x = 0;
@@ -3282,6 +3412,7 @@ static void term_out(Terminal *term)
                    compatibility(VT100);
                    {
                        int nlattr;
+                       termline *ldata;
 
                        switch (ANSI(c, term->esc_query)) {
                          case ANSI('3', '#'): /* DECDHL: 2*height, top */
@@ -3297,7 +3428,9 @@ static void term_out(Terminal *term)
                            nlattr = LATTR_WIDE;
                            break;
                        }
-                       scrlineptr(term->curs.y)->lattr = nlattr;
+                       ldata = scrlineptr(term->curs.y);
+                        check_line_size(term, ldata);
+                        ldata->lattr = nlattr;
                    }
                    break;
                  /* GZD4: G0 designate 94-set */
@@ -3362,8 +3495,15 @@ static void term_out(Terminal *term)
                    if (term->esc_nargs <= ARGS_MAX) {
                        if (term->esc_args[term->esc_nargs - 1] == ARG_DEFAULT)
                            term->esc_args[term->esc_nargs - 1] = 0;
-                       term->esc_args[term->esc_nargs - 1] =
-                           10 * term->esc_args[term->esc_nargs - 1] + c - '0';
+                       if (term->esc_args[term->esc_nargs - 1] <=
+                           UINT_MAX / 10 &&
+                           term->esc_args[term->esc_nargs - 1] * 10 <=
+                           UINT_MAX - c - '0')
+                           term->esc_args[term->esc_nargs - 1] =
+                               10 * term->esc_args[term->esc_nargs - 1] +
+                               c - '0';
+                       else
+                           term->esc_args[term->esc_nargs - 1] = UINT_MAX;
                    }
                    term->termstate = SEEN_CSI;
                } else if (c == ';') {
@@ -3379,8 +3519,10 @@ static void term_out(Terminal *term)
                        term->esc_query = c;
                    term->termstate = SEEN_CSI;
                } else
+#define CLAMP(arg, lim) ((arg) = ((arg) > (lim)) ? (lim) : (arg))
                    switch (ANSI(c, term->esc_query)) {
                      case 'A':       /* CUU: move up N lines */
+                       CLAMP(term->esc_args[0], term->rows);
                        move(term, term->curs.x,
                             term->curs.y - def(term->esc_args[0], 1), 1);
                        seen_disp_event(term);
@@ -3389,6 +3531,7 @@ static void term_out(Terminal *term)
                        compatibility(ANSI);
                        /* FALLTHROUGH */
                      case 'B':         /* CUD: Cursor down */
+                       CLAMP(term->esc_args[0], term->rows);
                        move(term, term->curs.x,
                             term->curs.y + def(term->esc_args[0], 1), 1);
                        seen_disp_event(term);
@@ -3404,23 +3547,27 @@ static void term_out(Terminal *term)
                        compatibility(ANSI);
                        /* FALLTHROUGH */
                      case 'C':         /* CUF: Cursor right */ 
+                       CLAMP(term->esc_args[0], term->cols);
                        move(term, term->curs.x + def(term->esc_args[0], 1),
                             term->curs.y, 1);
                        seen_disp_event(term);
                        break;
                      case 'D':       /* CUB: move left N cols */
+                       CLAMP(term->esc_args[0], term->cols);
                        move(term, term->curs.x - def(term->esc_args[0], 1),
                             term->curs.y, 1);
                        seen_disp_event(term);
                        break;
                      case 'E':       /* CNL: move down N lines and CR */
                        compatibility(ANSI);
+                       CLAMP(term->esc_args[0], term->rows);
                        move(term, 0,
                             term->curs.y + def(term->esc_args[0], 1), 1);
                        seen_disp_event(term);
                        break;
                      case 'F':       /* CPL: move up N lines and CR */
                        compatibility(ANSI);
+                       CLAMP(term->esc_args[0], term->rows);
                        move(term, 0,
                             term->curs.y - def(term->esc_args[0], 1), 1);
                        seen_disp_event(term);
@@ -3428,12 +3575,14 @@ static void term_out(Terminal *term)
                      case 'G':       /* CHA */
                      case '`':       /* HPA: set horizontal posn */
                        compatibility(ANSI);
+                       CLAMP(term->esc_args[0], term->cols);
                        move(term, def(term->esc_args[0], 1) - 1,
                             term->curs.y, 0);
                        seen_disp_event(term);
                        break;
                      case 'd':       /* VPA: set vertical posn */
                        compatibility(ANSI);
+                       CLAMP(term->esc_args[0], term->rows);
                        move(term, term->curs.x,
                             ((term->dec_om ? term->marg_t : 0) +
                              def(term->esc_args[0], 1) - 1),
@@ -3444,6 +3593,8 @@ static void term_out(Terminal *term)
                      case 'f':      /* HVP: set horz and vert posns at once */
                        if (term->esc_nargs < 2)
                            term->esc_args[1] = ARG_DEFAULT;
+                       CLAMP(term->esc_args[0], term->rows);
+                       CLAMP(term->esc_args[1], term->cols);
                        move(term, def(term->esc_args[1], 1) - 1,
                             ((term->dec_om ? term->marg_t : 0) +
                              def(term->esc_args[0], 1) - 1),
@@ -3456,7 +3607,8 @@ static void term_out(Terminal *term)
                            if (i == 3) {
                                /* Erase Saved Lines (xterm)
                                 * This follows Thomas Dickey's xterm. */
-                               term_clrsb(term);
+                                if (!term->no_remote_clearscroll)
+                                    term_clrsb(term);
                            } else {
                                i++;
                                if (i > 3)
@@ -3464,7 +3616,8 @@ static void term_out(Terminal *term)
                                erase_lots(term, FALSE, !!(i & 2), !!(i & 1));
                            }
                        }
-                       term->disptop = 0;
+                       if (term->scroll_on_disp)
+                            term->disptop = 0;
                        seen_disp_event(term);
                        break;
                      case 'K':       /* EL: erase line or parts of it */
@@ -3478,6 +3631,7 @@ static void term_out(Terminal *term)
                        break;
                      case 'L':       /* IL: insert lines */
                        compatibility(VT102);
+                       CLAMP(term->esc_args[0], term->rows);
                        if (term->curs.y <= term->marg_b)
                            scroll(term, term->curs.y, term->marg_b,
                                   -def(term->esc_args[0], 1), FALSE);
@@ -3485,6 +3639,7 @@ static void term_out(Terminal *term)
                        break;
                      case 'M':       /* DL: delete lines */
                        compatibility(VT102);
+                       CLAMP(term->esc_args[0], term->rows);
                        if (term->curs.y <= term->marg_b)
                            scroll(term, term->curs.y, term->marg_b,
                                   def(term->esc_args[0], 1),
@@ -3494,11 +3649,13 @@ static void term_out(Terminal *term)
                      case '@':       /* ICH: insert chars */
                        /* XXX VTTEST says this is vt220, vt510 manual says vt102 */
                        compatibility(VT102);
+                       CLAMP(term->esc_args[0], term->cols);
                        insch(term, def(term->esc_args[0], 1));
                        seen_disp_event(term);
                        break;
                      case 'P':       /* DCH: delete chars */
                        compatibility(VT102);
+                       CLAMP(term->esc_args[0], term->cols);
                        insch(term, -def(term->esc_args[0], 1));
                        seen_disp_event(term);
                        break;
@@ -3576,6 +3733,8 @@ static void term_out(Terminal *term)
                        compatibility(VT100);
                        if (term->esc_nargs <= 2) {
                            int top, bot;
+                           CLAMP(term->esc_args[0], term->rows);
+                           CLAMP(term->esc_args[1], term->rows);
                            top = def(term->esc_args[0], 1) - 1;
                            bot = (term->esc_nargs <= 1
                                   || term->esc_args[1] == 0 ?
@@ -3808,7 +3967,8 @@ static void term_out(Terminal *term)
 
                            switch (term->esc_args[0]) {
                                int x, y, len;
-                               char buf[80], *p;
+                               char buf[80];
+                                const char *p;
                              case 1:
                                set_iconic(term->frontend, FALSE);
                                break;
@@ -3863,7 +4023,9 @@ static void term_out(Terminal *term)
                              case 13:
                                if (term->ldisc) {
                                    get_window_pos(term->frontend, &x, &y);
-                                   len = sprintf(buf, "\033[3;%d;%dt", x, y);
+                                   len = sprintf(buf, "\033[3;%u;%ut",
+                                                  (unsigned)x,
+                                                  (unsigned)y);
                                    ldisc_send(term->ldisc, buf, len, 0);
                                }
                                break;
@@ -3928,6 +4090,7 @@ static void term_out(Terminal *term)
                        }
                        break;
                      case 'S':         /* SU: Scroll up */
+                       CLAMP(term->esc_args[0], term->rows);
                        compatibility(SCOANSI);
                        scroll(term, term->marg_t, term->marg_b,
                               def(term->esc_args[0], 1), TRUE);
@@ -3935,6 +4098,7 @@ static void term_out(Terminal *term)
                        seen_disp_event(term);
                        break;
                      case 'T':         /* SD: Scroll down */
+                       CLAMP(term->esc_args[0], term->rows);
                        compatibility(SCOANSI);
                        scroll(term, term->marg_t, term->marg_b,
                               -def(term->esc_args[0], 1), TRUE);
@@ -3977,6 +4141,7 @@ static void term_out(Terminal *term)
                        /* XXX VTTEST says this is vt220, vt510 manual
                         * says vt100 */
                        compatibility(ANSIMIN);
+                       CLAMP(term->esc_args[0], term->cols);
                        {
                            int n = def(term->esc_args[0], 1);
                            pos cursplus;
@@ -4010,6 +4175,7 @@ static void term_out(Terminal *term)
                        break;
                      case 'Z':         /* CBT */
                        compatibility(OTHER);
+                       CLAMP(term->esc_args[0], term->cols);
                        {
                            int i = def(term->esc_args[0], 1);
                            pos old_curs = term->curs;
@@ -4070,7 +4236,7 @@ static void term_out(Terminal *term)
                        break;
                      case ANSI('F', '='):      /* set normal foreground */
                        compatibility(SCOANSI);
-                       if (term->esc_args[0] >= 0 && term->esc_args[0] < 16) {
+                       if (term->esc_args[0] < 16) {
                            long colour =
                                (sco2ansicolour[term->esc_args[0] & 0x7] |
                                 (term->esc_args[0] & 0x8)) <<
@@ -4084,7 +4250,7 @@ static void term_out(Terminal *term)
                        break;
                      case ANSI('G', '='):      /* set normal background */
                        compatibility(SCOANSI);
-                       if (term->esc_args[0] >= 0 && term->esc_args[0] < 16) {
+                       if (term->esc_args[0] < 16) {
                            long colour =
                                (sco2ansicolour[term->esc_args[0] & 0x7] |
                                 (term->esc_args[0] & 0x8)) <<
@@ -4208,7 +4374,11 @@ static void term_out(Terminal *term)
                  case '7':
                  case '8':
                  case '9':
-                   term->esc_args[0] = 10 * term->esc_args[0] + c - '0';
+                   if (term->esc_args[0] <= UINT_MAX / 10 &&
+                       term->esc_args[0] * 10 <= UINT_MAX - c - '0')
+                       term->esc_args[0] = 10 * term->esc_args[0] + c - '0';
+                   else
+                       term->esc_args[0] = UINT_MAX;
                    break;
                  case 'L':
                    /*
@@ -4290,7 +4460,11 @@ static void term_out(Terminal *term)
                  case '7':
                  case '8':
                  case '9':
-                   term->esc_args[0] = 10 * term->esc_args[0] + c - '0';
+                   if (term->esc_args[0] <= UINT_MAX / 10 &&
+                       term->esc_args[0] * 10 <= UINT_MAX - c - '0')
+                       term->esc_args[0] = 10 * term->esc_args[0] + c - '0';
+                   else
+                       term->esc_args[0] = UINT_MAX;
                    break;
                  default:
                    term->termstate = OSC_STRING;
@@ -4371,7 +4545,8 @@ static void term_out(Terminal *term)
                    break;
                  case 'J':
                    erase_lots(term, FALSE, FALSE, TRUE);
-                   term->disptop = 0;
+                    if (term->scroll_on_disp)
+                        term->disptop = 0;
                    break;
                  case 'K':
                    erase_lots(term, TRUE, FALSE, TRUE);
@@ -4426,7 +4601,8 @@ static void term_out(Terminal *term)
                    /* compatibility(ATARI) */
                    move(term, 0, 0, 0);
                    erase_lots(term, FALSE, FALSE, TRUE);
-                   term->disptop = 0;
+                    if (term->scroll_on_disp)
+                        term->disptop = 0;
                    break;
                  case 'L':
                    /* compatibility(ATARI) */
@@ -4449,7 +4625,8 @@ static void term_out(Terminal *term)
                  case 'd':
                    /* compatibility(ATARI) */
                    erase_lots(term, FALSE, TRUE, FALSE);
-                   term->disptop = 0;
+                    if (term->scroll_on_disp)
+                        term->disptop = 0;
                    break;
                  case 'e':
                    /* compatibility(ATARI) */
@@ -4550,7 +4727,7 @@ static void term_out(Terminal *term)
     }
 
     term_print_flush(term);
-    if (term->logflush)
+    if (term->logflush && term->logctx)
        logflush(term->logctx);
 }
 
@@ -5015,11 +5192,13 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise)
 
            break_run = ((tattr ^ attr) & term->attr_mask) != 0;
 
+#ifdef USES_VTLINE_HACK
            /* Special hack for VT100 Linedraw glyphs */
            if ((tchar >= 0x23BA && tchar <= 0x23BD) ||
                 (j > 0 && (newline[j-1].chr >= 0x23BA &&
                            newline[j-1].chr <= 0x23BD)))
                break_run = TRUE;
+#endif
 
            /*
             * Separate out sequences of characters that have the
@@ -5268,7 +5447,7 @@ typedef struct {
 static void clip_addchar(clip_workbuf *b, wchar_t chr, int attr)
 {
     if (b->bufpos >= b->buflen) {
-       b->buflen += 128;
+       b->buflen *= 2;
        b->textbuf = sresize(b->textbuf, b->buflen, wchar_t);
        b->textptr = b->textbuf + b->bufpos;
        b->attrbuf = sresize(b->attrbuf, b->buflen, int);
@@ -5611,7 +5790,8 @@ static pos sel_spread_half(Terminal *term, pos p, int dir)
                    else
                        break;
                } else {
-                   if (ldata->lattr & LATTR_WRAPPED) {
+                   if (p.y+1 < term->rows && 
+                        (ldata->lattr & LATTR_WRAPPED)) {
                        termline *ldata2;
                        ldata2 = lineptr(p.y+1);
                        if (wordtype(term, UCSGET(ldata2->chars, 0))
@@ -5682,6 +5862,33 @@ static void sel_spread(Terminal *term)
     }
 }
 
+static void term_paste_callback(void *vterm)
+{
+    Terminal *term = (Terminal *)vterm;
+
+    if (term->paste_len == 0)
+       return;
+
+    while (term->paste_pos < term->paste_len) {
+       int n = 0;
+       while (n + term->paste_pos < term->paste_len) {
+           if (term->paste_buffer[term->paste_pos + n++] == '\015')
+               break;
+       }
+       if (term->ldisc)
+           luni_send(term->ldisc, term->paste_buffer + term->paste_pos, n, 0);
+       term->paste_pos += n;
+
+       if (term->paste_pos < term->paste_len) {
+            queue_toplevel_callback(term_paste_callback, term);
+           return;
+       }
+    }
+    sfree(term->paste_buffer);
+    term->paste_buffer = NULL;
+    term->paste_len = 0;
+}
+
 void term_do_paste(Terminal *term)
 {
     wchar_t *data;
@@ -5695,8 +5902,13 @@ void term_do_paste(Terminal *term)
 
         if (term->paste_buffer)
             sfree(term->paste_buffer);
-        term->paste_pos = term->paste_hold = term->paste_len = 0;
-        term->paste_buffer = snewn(len, wchar_t);
+        term->paste_pos = term->paste_len = 0;
+        term->paste_buffer = snewn(len + 12, wchar_t);
+
+        if (term->bracketed_paste) {
+            memcpy(term->paste_buffer, L"\033[200~", 6 * sizeof(wchar_t));
+            term->paste_len += 6;
+        }
 
         p = q = data;
         while (p < data + len) {
@@ -5720,6 +5932,12 @@ void term_do_paste(Terminal *term)
             q = p;
         }
 
+        if (term->bracketed_paste) {
+            memcpy(term->paste_buffer + term->paste_len,
+                   L"\033[201~", 6 * sizeof(wchar_t));
+            term->paste_len += 6;
+        }
+
         /* Assume a small paste will be OK in one go. */
         if (term->paste_len < 256) {
             if (term->ldisc)
@@ -5727,10 +5945,12 @@ void term_do_paste(Terminal *term)
             if (term->paste_buffer)
                 sfree(term->paste_buffer);
             term->paste_buffer = 0;
-            term->paste_pos = term->paste_hold = term->paste_len = 0;
+            term->paste_pos = term->paste_len = 0;
         }
     }
     get_clip(term->frontend, NULL, NULL);
+
+    queue_toplevel_callback(term_paste_callback, term);
 }
 
 void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked,
@@ -5790,37 +6010,53 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked,
      */
     if (raw_mouse &&
        (term->selstate != ABOUT_TO) && (term->selstate != DRAGGING)) {
-       int encstate = 0, r, c;
-       char abuf[16];
+       int encstate = 0, r, c, wheel;
+       char abuf[32];
+       int len = 0;
 
        if (term->ldisc) {
 
            switch (braw) {
              case MBT_LEFT:
-               encstate = 0x20;               /* left button down */
+               encstate = 0x00;               /* left button down */
+                wheel = FALSE;
                break;
              case MBT_MIDDLE:
-               encstate = 0x21;
+               encstate = 0x01;
+                wheel = FALSE;
                break;
              case MBT_RIGHT:
-               encstate = 0x22;
+               encstate = 0x02;
+                wheel = FALSE;
                break;
              case MBT_WHEEL_UP:
-               encstate = 0x60;
+               encstate = 0x40;
+                wheel = TRUE;
                break;
              case MBT_WHEEL_DOWN:
-               encstate = 0x61;
+               encstate = 0x41;
+                wheel = TRUE;
                break;
-             default: break;          /* placate gcc warning about enum use */
+             default:
+                return;
            }
-           switch (a) {
+            if (wheel) {
+                /* For mouse wheel buttons, we only ever expect to see
+                 * MA_CLICK actions, and we don't try to keep track of
+                 * the buttons being 'pressed' (since without matching
+                 * click/release pairs that's pointless). */
+                if (a != MA_CLICK)
+                    return;
+            } else switch (a) {
              case MA_DRAG:
                if (term->xterm_mouse == 1)
                    return;
                encstate += 0x20;
                break;
              case MA_RELEASE:
-               encstate = 0x23;
+               /* If multiple extensions are enabled, the xterm 1006 is used, so it's okay to check for only that */
+               if (!term->xterm_extended_mouse)
+                   encstate = 0x03;
                term->mouse_is_down = 0;
                break;
              case MA_CLICK:
@@ -5828,17 +6064,26 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked,
                    return;
                term->mouse_is_down = braw;
                break;
-             default: break;          /* placate gcc warning about enum use */
+              default:
+                return;
            }
            if (shift)
                encstate += 0x04;
            if (ctrl)
                encstate += 0x10;
-           r = y + 33;
-           c = x + 33;
-
-           sprintf(abuf, "\033[M%c%c%c", encstate, c, r);
-           ldisc_send(term->ldisc, abuf, 6, 0);
+           r = y + 1;
+           c = x + 1;
+
+           /* Check the extensions in decreasing order of preference. Encoding the release event above assumes that 1006 comes first. */
+           if (term->xterm_extended_mouse) {
+               len = sprintf(abuf, "\033[<%d;%d;%d%c", encstate, c, r, a == MA_RELEASE ? 'm' : 'M');
+           } else if (term->urxvt_extended_mouse) {
+               len = sprintf(abuf, "\033[%d;%d;%dM", encstate + 32, c, r);
+           } else if (c <= 223 && r <= 223) {
+               len = sprintf(abuf, "\033[M%c%c%c", encstate + 32, c + 32, r + 32);
+           }
+            if (len > 0)
+                ldisc_send(term->ldisc, abuf, len, 0);
        }
        return;
     }
@@ -5872,6 +6117,19 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked,
        sel_spread(term);
     } else if ((bcooked == MBT_SELECT && a == MA_DRAG) ||
               (bcooked == MBT_EXTEND && a != MA_RELEASE)) {
+        if (a == MA_DRAG &&
+            (term->selstate == NO_SELECTION || term->selstate == SELECTED)) {
+            /*
+             * This can happen if a front end has passed us a MA_DRAG
+             * without a prior MA_CLICK. OS X GTK does so, for
+             * example, if the initial button press was eaten by the
+             * WM when it activated the window in the first place. The
+             * nicest thing to do in this situation is to ignore
+             * further drags, and wait for the user to click in the
+             * window again properly if they want to select.
+             */
+            return;
+        }
        if (term->selstate == ABOUT_TO && poseq(term->selanchor, selpoint))
            return;
        if (bcooked == MBT_EXTEND && a != MA_DRAG &&
@@ -5957,6 +6215,13 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked,
        request_paste(term->frontend);
     }
 
+    /*
+     * Since terminal output is suppressed during drag-selects, we
+     * should make sure to write any pending output if one has just
+     * finished.
+     */
+    if (term->selstate != DRAGGING)
+        term_out(term);
     term_update(term);
 }
 
@@ -5996,433 +6261,6 @@ int format_arrow_key(char *buf, Terminal *term, int xkey, int ctrl)
     return p - buf;
 }
 
-void term_key(Terminal *term, Key_Sym keysym, wchar_t *text, size_t tlen,
-             unsigned int modifiers, unsigned int flags)
-{
-    char output[10];
-    char *p = output;
-    int prependesc = FALSE;
-#if 0
-    int i;
-
-    fprintf(stderr, "keysym = %d, %d chars:", keysym, tlen);
-    for (i = 0; i < tlen; i++)
-       fprintf(stderr, " %04x", (unsigned)text[i]);
-    fprintf(stderr, "\n");
-#endif
-
-    /* XXX Num Lock */
-    if ((flags & PKF_REPEAT) && term->repeat_off)
-       return;
-
-    /* Currently, Meta always just prefixes everything with ESC. */
-    if (modifiers & PKM_META)
-       prependesc = TRUE;
-    modifiers &= ~PKM_META;
-
-    /*
-     * Alt is only used for Alt+keypad, which isn't supported yet, so
-     * ignore it.
-     */
-    modifiers &= ~PKM_ALT;
-
-    /* Standard local function keys */
-    switch (modifiers & (PKM_SHIFT | PKM_CONTROL)) {
-      case PKM_SHIFT:
-       if (keysym == PK_PAGEUP)
-           /* scroll up one page */;
-       if (keysym == PK_PAGEDOWN)
-           /* scroll down on page */;
-       if (keysym == PK_INSERT)
-           term_do_paste(term);
-       break;
-      case PKM_CONTROL:
-       if (keysym == PK_PAGEUP)
-           /* scroll up one line */;
-       if (keysym == PK_PAGEDOWN)
-           /* scroll down one line */;
-       /* Control-Numlock for app-keypad mode switch */
-       if (keysym == PK_PF1)
-           term->app_keypad_keys ^= 1;
-       break;
-    }
-
-    if (modifiers & PKM_ALT) {
-       /* Alt+F4 (close) */
-       /* Alt+Return (full screen) */
-       /* Alt+Space (system menu) */
-    }
-
-    if (keysym == PK_NULL && (modifiers & PKM_CONTROL) && tlen == 1 &&
-       text[0] >= 0x20 && text[0] <= 0x7e) {
-       /* ASCII chars + Control */
-       if ((text[0] >= 0x40 && text[0] <= 0x5f) ||
-           (text[0] >= 0x61 && text[0] <= 0x7a))
-           text[0] &= 0x1f;
-       else {
-           /*
-            * Control-2 should return ^@ (0x00), Control-6 should return
-            * ^^ (0x1E), and Control-Minus should return ^_ (0x1F). Since
-            * the DOS keyboard handling did it, and we have nothing better
-            * to do with the key combo in question, we'll also map
-            * Control-Backquote to ^\ (0x1C).
-            */
-           switch (text[0]) {
-             case ' ': text[0] = 0x00; break;
-             case '-': text[0] = 0x1f; break;
-             case '/': text[0] = 0x1f; break;
-             case '2': text[0] = 0x00; break;
-             case '3': text[0] = 0x1b; break;
-             case '4': text[0] = 0x1c; break;
-             case '5': text[0] = 0x1d; break;
-             case '6': text[0] = 0x1e; break;
-             case '7': text[0] = 0x1f; break;
-             case '8': text[0] = 0x7f; break;
-             case '`': text[0] = 0x1c; break;
-           }
-       }
-    }
-
-    /* Nethack keypad */
-    if (term->nethack_keypad) {
-       char c = 0;
-       switch (keysym) {
-         case PK_KP1: c = 'b'; break;
-         case PK_KP2: c = 'j'; break;
-         case PK_KP3: c = 'n'; break;
-         case PK_KP4: c = 'h'; break;
-         case PK_KP5: c = '.'; break;
-         case PK_KP6: c = 'l'; break;
-         case PK_KP7: c = 'y'; break;
-         case PK_KP8: c = 'k'; break;
-         case PK_KP9: c = 'u'; break;
-         default: break; /* else gcc warns `enum value not used' */
-       }
-       if (c != 0) {
-           if (c != '.') {
-               if (modifiers & PKM_CONTROL)
-                   c &= 0x1f;
-               else if (modifiers & PKM_SHIFT)
-                       c = toupper((unsigned char)c);
-           }
-           *p++ = c;
-           goto done;
-       }
-    }
-
-    /* Numeric Keypad */
-    if (PK_ISKEYPAD(keysym)) {
-       int xkey = 0;
-
-       /*
-        * In VT400 mode, PFn always emits an escape sequence.  In
-        * Linux and tilde modes, this only happens in app keypad mode.
-        */
-       if (term->funky_type == FUNKY_VT400 ||
-           ((term->funky_type == FUNKY_LINUX ||
-             term->funky_type == FUNKY_TILDE) &&
-            term->app_keypad_keys && !term->no_applic_k)) {
-           switch (keysym) {
-             case PK_PF1: xkey = 'P'; break;
-             case PK_PF2: xkey = 'Q'; break;
-             case PK_PF3: xkey = 'R'; break;
-             case PK_PF4: xkey = 'S'; break;
-             default: break; /* else gcc warns `enum value not used' */
-           }
-       }
-       if (term->app_keypad_keys && !term->no_applic_k) {
-           switch (keysym) {
-             case PK_KP0: xkey = 'p'; break;
-             case PK_KP1: xkey = 'q'; break;
-             case PK_KP2: xkey = 'r'; break;
-             case PK_KP3: xkey = 's'; break;
-             case PK_KP4: xkey = 't'; break;
-             case PK_KP5: xkey = 'u'; break;
-             case PK_KP6: xkey = 'v'; break;
-             case PK_KP7: xkey = 'w'; break;
-             case PK_KP8: xkey = 'x'; break;
-             case PK_KP9: xkey = 'y'; break;
-             case PK_KPDECIMAL: xkey = 'n'; break;
-             case PK_KPENTER: xkey = 'M'; break;
-             default: break; /* else gcc warns `enum value not used' */
-           }
-           if (term->funky_type == FUNKY_XTERM && tlen > 0) {
-               /*
-                * xterm can't see the layout of the keypad, so it has
-                * to rely on the X keysyms returned by the keys.
-                * Hence, we look at the strings here, not the PuTTY
-                * keysyms (which describe the layout).
-                */
-               switch (text[0]) {
-                 case '+':
-                   if (modifiers & PKM_SHIFT)
-                       xkey = 'l';
-                   else
-                       xkey = 'k';
-                   break;
-                 case '/': xkey = 'o'; break;
-                 case '*': xkey = 'j'; break;
-                 case '-': xkey = 'm'; break;
-               }
-           } else {
-               /*
-                * In all other modes, we try to retain the layout of
-                * the DEC keypad in application mode.
-                */
-               switch (keysym) {
-                 case PK_KPBIGPLUS:
-                   /* This key covers the '-' and ',' keys on a VT220 */
-                   if (modifiers & PKM_SHIFT)
-                       xkey = 'm'; /* VT220 '-' */
-                   else
-                       xkey = 'l'; /* VT220 ',' */
-                   break;
-                 case PK_KPMINUS: xkey = 'm'; break;
-                 case PK_KPCOMMA: xkey = 'l'; break;
-                 default: break; /* else gcc warns `enum value not used' */
-               }
-           }
-       }
-       if (xkey) {
-           if (term->vt52_mode) {
-               if (xkey >= 'P' && xkey <= 'S')
-                   p += sprintf((char *) p, "\x1B%c", xkey);
-               else
-                   p += sprintf((char *) p, "\x1B?%c", xkey);
-           } else
-               p += sprintf((char *) p, "\x1BO%c", xkey);
-           goto done;
-       }
-       /* Not in application mode -- treat the number pad as arrow keys? */
-       if ((flags & PKF_NUMLOCK) == 0) {
-           switch (keysym) {
-             case PK_KP0: keysym = PK_INSERT; break;
-             case PK_KP1: keysym = PK_END; break;
-             case PK_KP2: keysym = PK_DOWN; break;
-             case PK_KP3: keysym = PK_PAGEDOWN; break;
-             case PK_KP4: keysym = PK_LEFT; break;
-             case PK_KP5: keysym = PK_REST; break;
-             case PK_KP6: keysym = PK_RIGHT; break;
-             case PK_KP7: keysym = PK_HOME; break;
-             case PK_KP8: keysym = PK_UP; break;
-             case PK_KP9: keysym = PK_PAGEUP; break;
-             default: break; /* else gcc warns `enum value not used' */
-           }
-       }
-    }
-
-    /* Miscellaneous keys */
-    switch (keysym) {
-      case PK_ESCAPE:
-       *p++ = 0x1b;
-       goto done;
-      case PK_BACKSPACE:
-           if (modifiers == 0)
-               *p++ = (term->bksp_is_delete ? 0x7F : 0x08);
-           else if (modifiers == PKM_SHIFT)
-               /* We do the opposite of what is configured */
-               *p++ = (term->bksp_is_delete ? 0x08 : 0x7F);
-           else break;
-           goto done;
-      case PK_TAB:
-       if (modifiers == 0)
-           *p++ = 0x09;
-       else if (modifiers == PKM_SHIFT)
-           *p++ = 0x1B, *p++ = '[', *p++ = 'Z';
-       else break;
-       goto done;
-       /* XXX window.c has ctrl+shift+space sending 0xa0 */
-      case PK_PAUSE:
-       if (modifiers == PKM_CONTROL)
-           *p++ = 26;
-       else break;
-       goto done;
-      case PK_RETURN:
-      case PK_KPENTER: /* Odd keypad modes handled above */
-       if (modifiers == 0) {
-           *p++ = 0x0d;
-           if (term->cr_lf_return)
-               *p++ = 0x0a;
-           goto done;
-       }
-      default: break; /* else gcc warns `enum value not used' */
-    }
-
-    /* SCO function keys and editing keys */
-    if (term->funky_type == FUNKY_SCO) {
-       if (PK_ISFKEY(keysym) && keysym <= PK_F12) {
-           static char const codes[] =
-               "MNOPQRSTUVWX" "YZabcdefghij" "klmnopqrstuv" "wxyz@[\\]^_`{";
-           int index = keysym - PK_F1;
-
-           if (modifiers & PKM_SHIFT) index += 12;
-           if (modifiers & PKM_CONTROL) index += 24;
-           p += sprintf((char *) p, "\x1B[%c", codes[index]);
-           goto done;
-       }
-       if (PK_ISEDITING(keysym)) {
-           int xkey = 0;
-
-           switch (keysym) {
-             case PK_DELETE:   *p++ = 0x7f; goto done;
-             case PK_HOME:     xkey = 'H'; break;
-             case PK_INSERT:   xkey = 'L'; break;
-             case PK_END:      xkey = 'F'; break;
-             case PK_PAGEUP:   xkey = 'I'; break;
-             case PK_PAGEDOWN: xkey = 'G'; break;
-             default: break; /* else gcc warns `enum value not used' */
-           }
-           p += sprintf((char *) p, "\x1B[%c", xkey);
-       }
-    }
-
-    if (PK_ISEDITING(keysym) && (modifiers & PKM_SHIFT) == 0) {
-       int code;
-
-       if (term->funky_type == FUNKY_XTERM) {
-           /* Xterm shuffles these keys, apparently. */
-           switch (keysym) {
-             case PK_HOME:     keysym = PK_INSERT;   break;
-             case PK_INSERT:   keysym = PK_HOME;     break;
-             case PK_DELETE:   keysym = PK_END;      break;
-             case PK_END:      keysym = PK_PAGEUP;   break;
-             case PK_PAGEUP:   keysym = PK_DELETE;   break;
-             case PK_PAGEDOWN: keysym = PK_PAGEDOWN; break;
-             default: break; /* else gcc warns `enum value not used' */
-           }
-       }
-
-       /* RXVT Home/End */
-       if (term->rxvt_homeend &&
-           (keysym == PK_HOME || keysym == PK_END)) {
-           p += sprintf((char *) p, keysym == PK_HOME ? "\x1B[H" : "\x1BOw");
-           goto done;
-       }
-
-       if (term->vt52_mode) {
-           int xkey;
-
-           /*
-            * A real VT52 doesn't have these, and a VT220 doesn't
-            * send anything for them in VT52 mode.
-            */
-           switch (keysym) {
-             case PK_HOME:     xkey = 'H'; break;
-             case PK_INSERT:   xkey = 'L'; break;
-             case PK_DELETE:   xkey = 'M'; break;
-             case PK_END:      xkey = 'E'; break;
-             case PK_PAGEUP:   xkey = 'I'; break;
-             case PK_PAGEDOWN: xkey = 'G'; break;
-             default: xkey=0; break; /* else gcc warns `enum value not used'*/
-           }
-           p += sprintf((char *) p, "\x1B%c", xkey);
-           goto done;
-       }
-
-       switch (keysym) {
-         case PK_HOME:     code = 1; break;
-         case PK_INSERT:   code = 2; break;
-         case PK_DELETE:   code = 3; break;
-         case PK_END:      code = 4; break;
-         case PK_PAGEUP:   code = 5; break;
-         case PK_PAGEDOWN: code = 6; break;
-         default: code = 0; break; /* else gcc warns `enum value not used' */
-       }
-       p += sprintf((char *) p, "\x1B[%d~", code);
-       goto done;
-    }
-
-    if (PK_ISFKEY(keysym)) {
-       /* Map Shift+F1-F10 to F11-F20 */
-       if (keysym >= PK_F1 && keysym <= PK_F10 && (modifiers & PKM_SHIFT))
-           keysym += 10;
-       if ((term->vt52_mode || term->funky_type == FUNKY_VT100P) &&
-           keysym <= PK_F14) {
-           /* XXX This overrides the XTERM/VT52 mode below */
-           int offt = 0;
-           if (keysym >= PK_F6)  offt++;
-           if (keysym >= PK_F12) offt++;
-           p += sprintf((char *) p, term->vt52_mode ? "\x1B%c" : "\x1BO%c",
-                        'P' + keysym - PK_F1 - offt);
-           goto done;
-       }
-       if (term->funky_type == FUNKY_LINUX && keysym <= PK_F5) {
-           p += sprintf((char *) p, "\x1B[[%c", 'A' + keysym - PK_F1);
-           goto done;
-       }
-       if (term->funky_type == FUNKY_XTERM && keysym <= PK_F4) {
-           if (term->vt52_mode)
-               p += sprintf((char *) p, "\x1B%c", 'P' + keysym - PK_F1);
-           else
-               p += sprintf((char *) p, "\x1BO%c", 'P' + keysym - PK_F1);
-           goto done;
-       }
-       p += sprintf((char *) p, "\x1B[%d~", 11 + keysym - PK_F1);
-       goto done;
-    }
-
-    if (PK_ISCURSOR(keysym)) {
-       int xkey;
-
-       switch (keysym) {
-         case PK_UP:    xkey = 'A'; break;
-         case PK_DOWN:  xkey = 'B'; break;
-         case PK_RIGHT: xkey = 'C'; break;
-         case PK_LEFT:  xkey = 'D'; break;
-         case PK_REST:  xkey = 'G'; break; /* centre key on number pad */
-         default: xkey = 0; break; /* else gcc warns `enum value not used' */
-       }
-       p += format_arrow_key(p, term, xkey, modifiers == PKM_CONTROL);
-       goto done;
-    }
-
-  done:
-    if (p > output || tlen > 0) {
-       /*
-        * Interrupt an ongoing paste. I'm not sure
-        * this is sensible, but for the moment it's
-        * preferable to having to faff about buffering
-        * things.
-        */
-       term_nopaste(term);
-
-       /*
-        * We need not bother about stdin backlogs
-        * here, because in GUI PuTTY we can't do
-        * anything about it anyway; there's no means
-        * of asking Windows to hold off on KEYDOWN
-        * messages. We _have_ to buffer everything
-        * we're sent.
-        */
-       term_seen_key_event(term);
-
-       if (prependesc) {
-#if 0
-           fprintf(stderr, "sending ESC\n");
-#endif
-           ldisc_send(term->ldisc, "\x1b", 1, 1);
-       }
-
-       if (p > output) {
-#if 0
-           fprintf(stderr, "sending %d bytes:", p - output);
-           for (i = 0; i < p - output; i++)
-               fprintf(stderr, " %02x", output[i]);
-           fprintf(stderr, "\n");
-#endif
-           ldisc_send(term->ldisc, output, p - output, 1);
-       } else if (tlen > 0) {
-#if 0
-           fprintf(stderr, "sending %d unichars:", tlen);
-           for (i = 0; i < tlen; i++)
-               fprintf(stderr, " %04x", (unsigned) text[i]);
-           fprintf(stderr, "\n");
-#endif
-           luni_send(term->ldisc, text, tlen, 1);
-       }
-    }
-}
-
 void term_nopaste(Terminal *term)
 {
     if (term->paste_len == 0)
@@ -6432,47 +6270,6 @@ void term_nopaste(Terminal *term)
     term->paste_len = 0;
 }
 
-int term_paste_pending(Terminal *term)
-{
-    return term->paste_len != 0;
-}
-
-void term_paste(Terminal *term)
-{
-    long now, paste_diff;
-
-    if (term->paste_len == 0)
-       return;
-
-    /* Don't wait forever to paste */
-    if (term->paste_hold) {
-       now = GETTICKCOUNT();
-       paste_diff = now - term->last_paste;
-       if (paste_diff >= 0 && paste_diff < 450)
-           return;
-    }
-    term->paste_hold = 0;
-
-    while (term->paste_pos < term->paste_len) {
-       int n = 0;
-       while (n + term->paste_pos < term->paste_len) {
-           if (term->paste_buffer[term->paste_pos + n++] == '\015')
-               break;
-       }
-       if (term->ldisc)
-           luni_send(term->ldisc, term->paste_buffer + term->paste_pos, n, 0);
-       term->paste_pos += n;
-
-       if (term->paste_pos < term->paste_len) {
-           term->paste_hold = 1;
-           return;
-       }
-    }
-    sfree(term->paste_buffer);
-    term->paste_buffer = NULL;
-    term->paste_len = 0;
-}
-
 static void deselect(Terminal *term)
 {
     term->selstate = NO_SELECTION;
@@ -6483,6 +6280,14 @@ void term_deselect(Terminal *term)
 {
     deselect(term);
     term_update(term);
+
+    /*
+     * Since terminal output is suppressed during drag-selects, we
+     * should make sure to write any pending output if one has just
+     * finished.
+     */
+    if (term->selstate != DRAGGING)
+        term_out(term);
 }
 
 int term_ldisc(Terminal *term, int option)
@@ -6568,9 +6373,11 @@ void term_set_focus(Terminal *term, int has_focus)
  */
 char *term_get_ttymode(Terminal *term, const char *mode)
 {
-    char *val = NULL;
+    const char *val = NULL;
     if (strcmp(mode, "ERASE") == 0) {
        val = term->bksp_is_delete ? "^?" : "^H";
+    } else if (strcmp(mode, "IUTF8") == 0) {
+       val = frontend_is_utf8(term->frontend) ? "yes" : "no";
     }
     /* FIXME: perhaps we should set ONLCR based on lfhascr as well? */
     /* FIXME: or ECHO and friends based on local echo state? */
@@ -6588,7 +6395,7 @@ struct term_userpass_state {
  * input.
  */
 int term_get_userpass_input(Terminal *term, prompts_t *p,
-                           unsigned char *in, int inlen)
+                           const unsigned char *in, int inlen)
 {
     struct term_userpass_state *s = (struct term_userpass_state *)p->data;
     if (!s) {