diff -uNr putty-0.65/Recipe putty-url-0.65/Recipe
--- putty-0.65/Recipe	2015-07-25 12:22:48.000000000 +0200
+++ putty-url-0.65/Recipe	2015-09-12 15:35:59.000000000 +0200
@@ -29,6 +29,7 @@
 !srcdir windows/
 !srcdir unix/
 !srcdir macosx/
+!srcdir windows/re_lib/
 
 # Help text added to the top of each Makefile, with /D converted
 # into -D as appropriate for the particular Makefile.
@@ -259,8 +260,8 @@
 # keywords [G] for Windows GUI app, [C] for Console app, [X] for
 # X/GTK Unix app, [U] for command-line Unix app.
 
-putty    : [G] GUITERM NONSSH WINSSH W_BE_ALL WINMISC winx11 putty.res LIBS
-puttytel : [G] GUITERM NONSSH W_BE_NOSSH WINMISC puttytel.res nogss LIBS
+putty    : [G] GUITERM NONSSH WINSSH W_BE_ALL WINMISC winx11 putty.res urlhack regexp LIBS
+puttytel : [G] GUITERM NONSSH W_BE_NOSSH WINMISC puttytel.res nogss urlhack regexp LIBS
 plink    : [C] winplink wincons NONSSH WINSSH W_BE_ALL logging WINMISC
          + winx11 plink.res winnojmp noterm LIBS
 pscp     : [C] pscp winsftp wincons WINSSH BE_SSH SFTP wildcard WINMISC
diff -uNr putty-0.65/mkfiles.pl putty-url-0.65/mkfiles.pl
--- putty-0.65/mkfiles.pl	2015-07-25 12:22:48.000000000 +0200
+++ putty-url-0.65/mkfiles.pl	2015-09-12 15:35:59.000000000 +0200
@@ -453,11 +453,11 @@
     "# You may also need to tell windres where to find include files:\n".
     "# RCINC = --include-dir c:\\cygwin\\include\\\n".
     "\n".
-    &splitline("CFLAGS = -mno-cygwin -Wall -O2 -D_WINDOWS -DDEBUG -DWIN32S_COMPAT".
+    &splitline("CFLAGS = -Wall -O2 -D_WINDOWS -DDEBUG -DWIN32S_COMPAT".
       " -D_NO_OLDNAMES -DNO_MULTIMON -DNO_HTMLHELP -DNO_SECUREZEROMEMORY " .
 	       (join " ", map {"-I$dirpfx$_"} @srcdirs)) .
 	       "\n".
-    "LDFLAGS = -mno-cygwin -s\n".
+    "LDFLAGS = -s\n".
     &splitline("RCFLAGS = \$(RCINC) --define WIN32=1 --define _WIN32=1 ".
       "--define WINVER=0x0400 ".(join " ", map {"-I$dirpfx$_"} @srcdirs))."\n".
     "\n".
diff -uNr putty-0.65/putty.h putty-url-0.65/putty.h
--- putty-0.65/putty.h	2015-07-25 12:22:48.000000000 +0200
+++ putty-url-0.65/putty.h	2015-09-12 15:35:59.000000000 +0200
@@ -137,6 +137,22 @@
 #define ATTR_DEFBG   (258 << ATTR_BGSHIFT)
 #define ATTR_DEFAULT (ATTR_DEFFG | ATTR_DEFBG)
 
+/*
+ * HACK: PuttyTray / Nutty
+ * Hyperlink stuff: define
+ */
+#define CHAR_MASK    0x000000FFUL
+
+/*
+ * HACK: PuttyTray / Nutty
+ * Hyperlink stuff: Underline settings
+ */
+enum {
+	URLHACK_UNDERLINE_ALWAYS,
+	URLHACK_UNDERLINE_HOVER,
+	URLHACK_UNDERLINE_NEVER
+};
+
 struct sesslist {
     int nsessions;
     char **sessions;
@@ -866,6 +882,15 @@
     X(INT, NONE, shadowboldoffset) \
     X(INT, NONE, crhaslf) \
     X(STR, NONE, winclass) \
+    /*                                                                \
+     * HACK: PuTTY-url settings                                       \
+     */ \
+    X(INT, NONE, url_ctrl_click) \
+    X(INT, NONE, url_underline) \
+    X(INT, NONE, url_defbrowser) \
+    X(INT, NONE, url_defregex) \
+    X(FILENAME, NONE, url_browser) \
+    X(STR, NONE, url_regex)
 
 /* Now define the actual enum of option keywords using that macro. */
 #define CONF_ENUM_DEF(valtype, keytype, keyword) CONF_ ## keyword,
@@ -1458,7 +1483,6 @@
                                    IS_LOW_SURROGATE(ls))
 #endif
 
-
 #define IS_SURROGATE(wch) (((wch) >= HIGH_SURROGATE_START) &&   \
                            ((wch) <= LOW_SURROGATE_END))
 #define HIGH_SURROGATE_OF(codept) \
diff -uNr putty-0.65/putty.h.orig putty-url-0.65/putty.h.orig
--- putty-0.65/putty.h.orig	1970-01-01 01:00:00.000000000 +0100
+++ putty-url-0.65/putty.h.orig	2015-07-25 12:22:48.000000000 +0200
@@ -0,0 +1,1471 @@
+#ifndef PUTTY_PUTTY_H
+#define PUTTY_PUTTY_H
+
+#include <stddef.h>		       /* for wchar_t */
+
+/*
+ * Global variables. Most modules declare these `extern', but
+ * window.c will do `#define PUTTY_DO_GLOBALS' before including this
+ * module, and so will get them properly defined.
+ */
+#ifndef GLOBAL
+#ifdef PUTTY_DO_GLOBALS
+#define GLOBAL
+#else
+#define GLOBAL extern
+#endif
+#endif
+
+#ifndef DONE_TYPEDEFS
+#define DONE_TYPEDEFS
+typedef struct conf_tag Conf;
+typedef struct backend_tag Backend;
+typedef struct terminal_tag Terminal;
+#endif
+
+#include "puttyps.h"
+#include "network.h"
+#include "misc.h"
+
+/*
+ * Fingerprints of the PGP master keys that can be used to establish a trust
+ * path between an executable and other files.
+ */
+#define PGP_RSA_MASTER_KEY_FP \
+    "8F 15 97 DA 25 30 AB 0D  88 D1 92 54 11 CF 0C 4C"
+#define PGP_DSA_MASTER_KEY_FP \
+    "313C 3E76 4B74 C2C5 F2AE  83A8 4F5E 6DF5 6A93 B34E"
+
+/* Three attribute types: 
+ * The ATTRs (normal attributes) are stored with the characters in
+ * the main display arrays
+ *
+ * The TATTRs (temporary attributes) are generated on the fly, they
+ * can overlap with characters but not with normal attributes.
+ *
+ * The LATTRs (line attributes) are an entirely disjoint space of
+ * flags.
+ * 
+ * The DATTRs (display attributes) are internal to terminal.c (but
+ * defined here because their values have to match the others
+ * here); they reuse the TATTR_* space but are always masked off
+ * before sending to the front end.
+ *
+ * ATTR_INVALID is an illegal colour combination.
+ */
+
+#define TATTR_ACTCURS 	    0x40000000UL      /* active cursor (block) */
+#define TATTR_PASCURS 	    0x20000000UL      /* passive cursor (box) */
+#define TATTR_RIGHTCURS	    0x10000000UL      /* cursor-on-RHS */
+#define TATTR_COMBINING	    0x80000000UL      /* combining characters */
+
+#define DATTR_STARTRUN      0x80000000UL   /* start of redraw run */
+
+#define TDATTR_MASK         0xF0000000UL
+#define TATTR_MASK (TDATTR_MASK)
+#define DATTR_MASK (TDATTR_MASK)
+
+#define LATTR_NORM   0x00000000UL
+#define LATTR_WIDE   0x00000001UL
+#define LATTR_TOP    0x00000002UL
+#define LATTR_BOT    0x00000003UL
+#define LATTR_MODE   0x00000003UL
+#define LATTR_WRAPPED 0x00000010UL     /* this line wraps to next */
+#define LATTR_WRAPPED2 0x00000020UL    /* with WRAPPED: CJK wide character
+					  wrapped to next line, so last
+					  single-width cell is empty */
+
+#define ATTR_INVALID 0x03FFFFU
+
+/* Like Linux use the F000 page for direct to font. */
+#define CSET_OEMCP   0x0000F000UL      /* OEM Codepage DTF */
+#define CSET_ACP     0x0000F100UL      /* Ansi Codepage DTF */
+
+/* These are internal use overlapping with the UTF-16 surrogates */
+#define CSET_ASCII   0x0000D800UL      /* normal ASCII charset ESC ( B */
+#define CSET_LINEDRW 0x0000D900UL      /* line drawing charset ESC ( 0 */
+#define CSET_SCOACS  0x0000DA00UL      /* SCO Alternate charset */
+#define CSET_GBCHR   0x0000DB00UL      /* UK variant   charset ESC ( A */
+#define CSET_MASK    0xFFFFFF00UL      /* Character set mask */
+
+#define DIRECT_CHAR(c) ((c&0xFFFFFC00)==0xD800)
+#define DIRECT_FONT(c) ((c&0xFFFFFE00)==0xF000)
+
+#define UCSERR	     (CSET_LINEDRW|'a')	/* UCS Format error character. */
+/*
+ * UCSWIDE is a special value used in the terminal data to signify
+ * the character cell containing the right-hand half of a CJK wide
+ * character. We use 0xDFFF because it's part of the surrogate
+ * range and hence won't be used for anything else (it's impossible
+ * to input it via UTF-8 because our UTF-8 decoder correctly
+ * rejects surrogates).
+ */
+#define UCSWIDE	     0xDFFF
+
+#define ATTR_NARROW  0x800000U
+#define ATTR_WIDE    0x400000U
+#define ATTR_BOLD    0x040000U
+#define ATTR_UNDER   0x080000U
+#define ATTR_REVERSE 0x100000U
+#define ATTR_BLINK   0x200000U
+#define ATTR_FGMASK  0x0001FFU
+#define ATTR_BGMASK  0x03FE00U
+#define ATTR_COLOURS 0x03FFFFU
+#define ATTR_FGSHIFT 0
+#define ATTR_BGSHIFT 9
+
+/*
+ * The definitive list of colour numbers stored in terminal
+ * attribute words is kept here. It is:
+ * 
+ *  - 0-7 are ANSI colours (KRGYBMCW).
+ *  - 8-15 are the bold versions of those colours.
+ *  - 16-255 are the remains of the xterm 256-colour mode (a
+ *    216-colour cube with R at most significant and B at least,
+ *    followed by a uniform series of grey shades running between
+ *    black and white but not including either on grounds of
+ *    redundancy).
+ *  - 256 is default foreground
+ *  - 257 is default bold foreground
+ *  - 258 is default background
+ *  - 259 is default bold background
+ *  - 260 is cursor foreground
+ *  - 261 is cursor background
+ */
+
+#define ATTR_DEFFG   (256 << ATTR_FGSHIFT)
+#define ATTR_DEFBG   (258 << ATTR_BGSHIFT)
+#define ATTR_DEFAULT (ATTR_DEFFG | ATTR_DEFBG)
+
+struct sesslist {
+    int nsessions;
+    char **sessions;
+    char *buffer;		       /* so memory can be freed later */
+};
+
+struct unicode_data {
+    char **uni_tbl;
+    int dbcs_screenfont;
+    int font_codepage;
+    int line_codepage;
+    wchar_t unitab_scoacs[256];
+    wchar_t unitab_line[256];
+    wchar_t unitab_font[256];
+    wchar_t unitab_xterm[256];
+    wchar_t unitab_oemcp[256];
+    unsigned char unitab_ctrl[256];
+};
+
+#define LGXF_OVR  1		       /* existing logfile overwrite */
+#define LGXF_APN  0		       /* existing logfile append */
+#define LGXF_ASK -1		       /* existing logfile ask */
+#define LGTYP_NONE  0		       /* logmode: no logging */
+#define LGTYP_ASCII 1		       /* logmode: pure ascii */
+#define LGTYP_DEBUG 2		       /* logmode: all chars of traffic */
+#define LGTYP_PACKETS 3		       /* logmode: SSH data packets */
+#define LGTYP_SSHRAW 4		       /* logmode: SSH raw data */
+
+typedef enum {
+    /* Actual special commands. Originally Telnet, but some codes have
+     * been re-used for similar specials in other protocols. */
+    TS_AYT, TS_BRK, TS_SYNCH, TS_EC, TS_EL, TS_GA, TS_NOP, TS_ABORT,
+    TS_AO, TS_IP, TS_SUSP, TS_EOR, TS_EOF, TS_LECHO, TS_RECHO, TS_PING,
+    TS_EOL,
+    /* Special command for SSH. */
+    TS_REKEY,
+    /* POSIX-style signals. (not Telnet) */
+    TS_SIGABRT, TS_SIGALRM, TS_SIGFPE,  TS_SIGHUP,  TS_SIGILL,
+    TS_SIGINT,  TS_SIGKILL, TS_SIGPIPE, TS_SIGQUIT, TS_SIGSEGV,
+    TS_SIGTERM, TS_SIGUSR1, TS_SIGUSR2,
+    /* Pseudo-specials used for constructing the specials menu. */
+    TS_SEP,	    /* Separator */
+    TS_SUBMENU,	    /* Start a new submenu with specified name */
+    TS_EXITMENU	    /* Exit current submenu or end of specials */
+} Telnet_Special;
+
+struct telnet_special {
+    const char *name;
+    int code;
+};
+
+typedef enum {
+    MBT_NOTHING,
+    MBT_LEFT, MBT_MIDDLE, MBT_RIGHT,   /* `raw' button designations */
+    MBT_SELECT, MBT_EXTEND, MBT_PASTE, /* `cooked' button designations */
+    MBT_WHEEL_UP, MBT_WHEEL_DOWN       /* mouse wheel */
+} Mouse_Button;
+
+typedef enum {
+    MA_NOTHING, MA_CLICK, MA_2CLK, MA_3CLK, MA_DRAG, MA_RELEASE
+} Mouse_Action;
+
+/* Keyboard modifiers -- keys the user is actually holding down */
+
+#define PKM_SHIFT	0x01
+#define PKM_CONTROL	0x02
+#define PKM_META	0x04
+#define PKM_ALT		0x08
+
+/* Keyboard flags that aren't really modifiers */
+#define PKF_CAPSLOCK	0x10
+#define PKF_NUMLOCK	0x20
+#define PKF_REPEAT	0x40
+
+/* Stand-alone keysyms for function keys */
+
+typedef enum {
+    PK_NULL,		/* No symbol for this key */
+    /* Main keypad keys */
+    PK_ESCAPE, PK_TAB, PK_BACKSPACE, PK_RETURN, PK_COMPOSE,
+    /* Editing keys */
+    PK_HOME, PK_INSERT, PK_DELETE, PK_END, PK_PAGEUP, PK_PAGEDOWN,
+    /* Cursor keys */
+    PK_UP, PK_DOWN, PK_RIGHT, PK_LEFT, PK_REST,
+    /* Numeric keypad */			/* Real one looks like: */
+    PK_PF1, PK_PF2, PK_PF3, PK_PF4,		/* PF1 PF2 PF3 PF4 */
+    PK_KPCOMMA, PK_KPMINUS, PK_KPDECIMAL,	/*  7   8   9   -  */
+    PK_KP0, PK_KP1, PK_KP2, PK_KP3, PK_KP4,	/*  4   5   6   ,  */
+    PK_KP5, PK_KP6, PK_KP7, PK_KP8, PK_KP9,	/*  1   2   3  en- */
+    PK_KPBIGPLUS, PK_KPENTER,			/*    0     .  ter */
+    /* Top row */
+    PK_F1,  PK_F2,  PK_F3,  PK_F4,  PK_F5,
+    PK_F6,  PK_F7,  PK_F8,  PK_F9,  PK_F10,
+    PK_F11, PK_F12, PK_F13, PK_F14, PK_F15,
+    PK_F16, PK_F17, PK_F18, PK_F19, PK_F20,
+    PK_PAUSE
+} Key_Sym;
+
+#define PK_ISEDITING(k)	((k) >= PK_HOME && (k) <= PK_PAGEDOWN)
+#define PK_ISCURSOR(k)	((k) >= PK_UP && (k) <= PK_REST)
+#define PK_ISKEYPAD(k)	((k) >= PK_PF1 && (k) <= PK_KPENTER)
+#define PK_ISFKEY(k)	((k) >= PK_F1 && (k) <= PK_F20)
+
+enum {
+    VT_XWINDOWS, VT_OEMANSI, VT_OEMONLY, VT_POORMAN, VT_UNICODE
+};
+
+enum {
+    /*
+     * SSH-2 key exchange algorithms
+     */
+    KEX_WARN,
+    KEX_DHGROUP1,
+    KEX_DHGROUP14,
+    KEX_DHGEX,
+    KEX_RSA,
+    KEX_MAX
+};
+
+enum {
+    /*
+     * SSH ciphers (both SSH-1 and SSH-2)
+     */
+    CIPHER_WARN,		       /* pseudo 'cipher' */
+    CIPHER_3DES,
+    CIPHER_BLOWFISH,
+    CIPHER_AES,			       /* (SSH-2 only) */
+    CIPHER_DES,
+    CIPHER_ARCFOUR,
+    CIPHER_MAX			       /* no. ciphers (inc warn) */
+};
+
+enum {
+    /*
+     * Several different bits of the PuTTY configuration seem to be
+     * three-way settings whose values are `always yes', `always
+     * no', and `decide by some more complex automated means'. This
+     * is true of line discipline options (local echo and line
+     * editing), proxy DNS, Close On Exit, and SSH server bug
+     * workarounds. Accordingly I supply a single enum here to deal
+     * with them all.
+     */
+    FORCE_ON, FORCE_OFF, AUTO
+};
+
+enum {
+    /*
+     * Proxy types.
+     */
+    PROXY_NONE, PROXY_SOCKS4, PROXY_SOCKS5,
+    PROXY_HTTP, PROXY_TELNET, PROXY_CMD
+};
+
+enum {
+    /*
+     * Line discipline options which the backend might try to control.
+     */
+    LD_EDIT,			       /* local line editing */
+    LD_ECHO			       /* local echo */
+};
+
+enum {
+    /* Actions on remote window title query */
+    TITLE_NONE, TITLE_EMPTY, TITLE_REAL
+};
+
+enum {
+    /* Protocol back ends. (CONF_protocol) */
+    PROT_RAW, PROT_TELNET, PROT_RLOGIN, PROT_SSH,
+    /* PROT_SERIAL is supported on a subset of platforms, but it doesn't
+     * hurt to define it globally. */
+    PROT_SERIAL
+};
+
+enum {
+    /* Bell settings (CONF_beep) */
+    BELL_DISABLED, BELL_DEFAULT, BELL_VISUAL, BELL_WAVEFILE, BELL_PCSPEAKER
+};
+
+enum {
+    /* Taskbar flashing indication on bell (CONF_beep_ind) */
+    B_IND_DISABLED, B_IND_FLASH, B_IND_STEADY
+};
+
+enum {
+    /* Resize actions (CONF_resize_action) */
+    RESIZE_TERM, RESIZE_DISABLED, RESIZE_FONT, RESIZE_EITHER
+};
+
+enum {
+    /* Function key types (CONF_funky_type) */
+    FUNKY_TILDE,
+    FUNKY_LINUX,
+    FUNKY_XTERM,
+    FUNKY_VT400,
+    FUNKY_VT100P,
+    FUNKY_SCO
+};
+
+enum {
+    FQ_DEFAULT, FQ_ANTIALIASED, FQ_NONANTIALIASED, FQ_CLEARTYPE
+};
+
+enum {
+    SER_PAR_NONE, SER_PAR_ODD, SER_PAR_EVEN, SER_PAR_MARK, SER_PAR_SPACE
+};
+
+enum {
+    SER_FLOW_NONE, SER_FLOW_XONXOFF, SER_FLOW_RTSCTS, SER_FLOW_DSRDTR
+};
+
+/*
+ * Tables of string <-> enum value mappings used in settings.c.
+ * Defined here so that backends can export their GSS library tables
+ * to the cross-platform settings code.
+ */
+struct keyvalwhere {
+    /*
+     * Two fields which define a string and enum value to be
+     * equivalent to each other.
+     */
+    char *s;
+    int v;
+
+    /*
+     * The next pair of fields are used by gprefs() in settings.c to
+     * arrange that when it reads a list of strings representing a
+     * preference list and translates it into the corresponding list
+     * of integers, strings not appearing in the list are entered in a
+     * configurable position rather than uniformly at the end.
+     */
+
+    /*
+     * 'vrel' indicates which other value in the list to place this
+     * element relative to. It should be a value that has occurred in
+     * a 'v' field of some other element of the array, or -1 to
+     * indicate that we simply place relative to one or other end of
+     * the list.
+     *
+     * gprefs will try to process the elements in an order which makes
+     * this field work (i.e. so that the element referenced has been
+     * added before processing this one).
+     */
+    int vrel;
+
+    /*
+     * 'where' indicates whether to place the new value before or
+     * after the one referred to by vrel. -1 means before; +1 means
+     * after.
+     *
+     * When vrel is -1, this also implicitly indicates which end of
+     * the array to use. So vrel=-1, where=-1 means to place _before_
+     * some end of the list (hence, at the last element); vrel=-1,
+     * where=+1 means to place _after_ an end (hence, at the first).
+     */
+    int where;
+};
+
+#ifndef NO_GSSAPI
+extern const int ngsslibs;
+extern const char *const gsslibnames[]; /* for displaying in configuration */
+extern const struct keyvalwhere gsslibkeywords[]; /* for settings.c */
+#endif
+
+extern const char *const ttymodes[];
+
+enum {
+    /*
+     * Network address types. Used for specifying choice of IPv4/v6
+     * in config; also used in proxy.c to indicate whether a given
+     * host name has already been resolved or will be resolved at
+     * the proxy end.
+     */
+    ADDRTYPE_UNSPEC, ADDRTYPE_IPV4, ADDRTYPE_IPV6, ADDRTYPE_NAME
+};
+
+struct backend_tag {
+    const char *(*init) (void *frontend_handle, void **backend_handle,
+			 Conf *conf, char *host, int port, char **realhost,
+			 int nodelay, int keepalive);
+    void (*free) (void *handle);
+    /* back->reconfig() passes in a replacement configuration. */
+    void (*reconfig) (void *handle, Conf *conf);
+    /* back->send() returns the current amount of buffered data. */
+    int (*send) (void *handle, char *buf, int len);
+    /* back->sendbuffer() does the same thing but without attempting a send */
+    int (*sendbuffer) (void *handle);
+    void (*size) (void *handle, int width, int height);
+    void (*special) (void *handle, Telnet_Special code);
+    const struct telnet_special *(*get_specials) (void *handle);
+    int (*connected) (void *handle);
+    int (*exitcode) (void *handle);
+    /* If back->sendok() returns FALSE, data sent to it from the frontend
+     * may be lost. */
+    int (*sendok) (void *handle);
+    int (*ldisc) (void *handle, int);
+    void (*provide_ldisc) (void *handle, void *ldisc);
+    void (*provide_logctx) (void *handle, void *logctx);
+    /*
+     * back->unthrottle() tells the back end that the front end
+     * buffer is clearing.
+     */
+    void (*unthrottle) (void *handle, int);
+    int (*cfg_info) (void *handle);
+    char *name;
+    int protocol;
+    int default_port;
+};
+
+extern Backend *backends[];
+
+/*
+ * Suggested default protocol provided by the backend link module.
+ * The application is free to ignore this.
+ */
+extern const int be_default_protocol;
+
+/*
+ * Name of this particular application, for use in the config box
+ * and other pieces of text.
+ */
+extern const char *const appname;
+
+/*
+ * Some global flags denoting the type of application.
+ * 
+ * FLAG_VERBOSE is set when the user requests verbose details.
+ * 
+ * FLAG_STDERR is set in command-line applications (which have a
+ * functioning stderr that it makes sense to write to) and not in
+ * GUI applications (which don't).
+ * 
+ * FLAG_INTERACTIVE is set when a full interactive shell session is
+ * being run, _either_ because no remote command has been provided
+ * _or_ because the application is GUI and can't run non-
+ * interactively.
+ * 
+ * These flags describe the type of _application_ - they wouldn't
+ * vary between individual sessions - and so it's OK to have this
+ * variable be GLOBAL.
+ * 
+ * Note that additional flags may be defined in platform-specific
+ * headers. It's probably best if those ones start from 0x1000, to
+ * avoid collision.
+ */
+#define FLAG_VERBOSE     0x0001
+#define FLAG_STDERR      0x0002
+#define FLAG_INTERACTIVE 0x0004
+GLOBAL int flags;
+
+/*
+ * Likewise, these two variables are set up when the application
+ * initialises, and inform all default-settings accesses after
+ * that.
+ */
+GLOBAL int default_protocol;
+GLOBAL int default_port;
+
+/*
+ * This is set TRUE by cmdline.c iff a session is loaded with "-load".
+ */
+GLOBAL int loaded_session;
+/*
+ * This is set to the name of the loaded session.
+ */
+GLOBAL char *cmdline_session_name;
+
+struct RSAKey;			       /* be a little careful of scope */
+
+/*
+ * Mechanism for getting text strings such as usernames and passwords
+ * from the front-end.
+ * The fields are mostly modelled after SSH's keyboard-interactive auth.
+ * FIXME We should probably mandate a character set/encoding (probably UTF-8).
+ *
+ * Since many of the pieces of text involved may be chosen by the server,
+ * the caller must take care to ensure that the server can't spoof locally-
+ * generated prompts such as key passphrase prompts. Some ground rules:
+ *  - If the front-end needs to truncate a string, it should lop off the
+ *    end.
+ *  - The front-end should filter out any dangerous characters and
+ *    generally not trust the strings. (But \n is required to behave
+ *    vaguely sensibly, at least in `instruction', and ideally in
+ *    `prompt[]' too.)
+ */
+typedef struct {
+    char *prompt;
+    int echo;
+    /*
+     * 'result' must be a dynamically allocated array of exactly
+     * 'resultsize' chars. The code for actually reading input may
+     * realloc it bigger (and adjust resultsize accordingly) if it has
+     * to. The caller should free it again when finished with it.
+     *
+     * If resultsize==0, then result may be NULL. When setting up a
+     * prompt_t, it's therefore easiest to initialise them this way,
+     * which means all actual allocation is done by the callee. This
+     * is what add_prompt does.
+     */
+    char *result;
+    size_t resultsize;
+} prompt_t;
+typedef struct {
+    /*
+     * Indicates whether the information entered is to be used locally
+     * (for instance a key passphrase prompt), or is destined for the wire.
+     * This is a hint only; the front-end is at liberty not to use this
+     * information (so the caller should ensure that the supplied text is
+     * sufficient).
+     */
+    int to_server;
+    char *name;		/* Short description, perhaps for dialog box title */
+    int name_reqd;	/* Display of `name' required or optional? */
+    char *instruction;	/* Long description, maybe with embedded newlines */
+    int instr_reqd;	/* Display of `instruction' required or optional? */
+    size_t n_prompts;   /* May be zero (in which case display the foregoing,
+                         * if any, and return success) */
+    prompt_t **prompts;
+    void *frontend;
+    void *data;		/* slot for housekeeping data, managed by
+			 * get_userpass_input(); initially NULL */
+} prompts_t;
+prompts_t *new_prompts(void *frontend);
+void add_prompt(prompts_t *p, char *promptstr, int echo);
+void prompt_set_result(prompt_t *pr, const char *newstr);
+void prompt_ensure_result_size(prompt_t *pr, int len);
+/* Burn the evidence. (Assumes _all_ strings want free()ing.) */
+void free_prompts(prompts_t *p);
+
+/*
+ * Exports from the front end.
+ */
+void request_resize(void *frontend, int, int);
+void do_text(Context, int, int, wchar_t *, int, unsigned long, int);
+void do_cursor(Context, int, int, wchar_t *, int, unsigned long, int);
+int char_width(Context ctx, int uc);
+#ifdef OPTIMISE_SCROLL
+void do_scroll(Context, int, int, int);
+#endif
+void set_title(void *frontend, char *);
+void set_icon(void *frontend, char *);
+void set_sbar(void *frontend, int, int, int);
+Context get_ctx(void *frontend);
+void free_ctx(Context);
+void palette_set(void *frontend, int, int, int, int);
+void palette_reset(void *frontend);
+void write_aclip(void *frontend, char *, int, int);
+void write_clip(void *frontend, wchar_t *, int *, int, int);
+void get_clip(void *frontend, wchar_t **, int *);
+void optimised_move(void *frontend, int, int, int);
+void set_raw_mouse_mode(void *frontend, int);
+void connection_fatal(void *frontend, char *, ...);
+void nonfatal(char *, ...);
+void fatalbox(char *, ...);
+void modalfatalbox(char *, ...);
+#ifdef macintosh
+#pragma noreturn(fatalbox)
+#pragma noreturn(modalfatalbox)
+#endif
+void do_beep(void *frontend, int);
+void begin_session(void *frontend);
+void sys_cursor(void *frontend, int x, int y);
+void request_paste(void *frontend);
+void frontend_keypress(void *frontend);
+void ldisc_update(void *frontend, int echo, int edit);
+/* It's the backend's responsibility to invoke this at the start of a
+ * connection, if necessary; it can also invoke it later if the set of
+ * special commands changes. It does not need to invoke it at session
+ * shutdown. */
+void update_specials_menu(void *frontend);
+int from_backend(void *frontend, int is_stderr, const char *data, int len);
+int from_backend_untrusted(void *frontend, const char *data, int len);
+/* Called when the back end wants to indicate that EOF has arrived on
+ * the server-to-client stream. Returns FALSE to indicate that we
+ * intend to keep the session open in the other direction, or TRUE to
+ * indicate that if they're closing so are we. */
+int from_backend_eof(void *frontend);
+void notify_remote_exit(void *frontend);
+/* Get a sensible value for a tty mode. NULL return = don't set.
+ * Otherwise, returned value should be freed by caller. */
+char *get_ttymode(void *frontend, const char *mode);
+/*
+ * >0 = `got all results, carry on'
+ * 0  = `user cancelled' (FIXME distinguish "give up entirely" and "next auth"?)
+ * <0 = `please call back later with more in/inlen'
+ */
+int get_userpass_input(prompts_t *p, unsigned char *in, int inlen);
+#define OPTIMISE_IS_SCROLL 1
+
+void set_iconic(void *frontend, int iconic);
+void move_window(void *frontend, int x, int y);
+void set_zorder(void *frontend, int top);
+void refresh_window(void *frontend);
+void set_zoomed(void *frontend, int zoomed);
+int is_iconic(void *frontend);
+void get_window_pos(void *frontend, int *x, int *y);
+void get_window_pixels(void *frontend, int *x, int *y);
+char *get_window_title(void *frontend, int icon);
+/* Hint from backend to frontend about time-consuming operations.
+ * Initial state is assumed to be BUSY_NOT. */
+enum {
+    BUSY_NOT,	    /* Not busy, all user interaction OK */
+    BUSY_WAITING,   /* Waiting for something; local event loops still running
+		       so some local interaction (e.g. menus) OK, but network
+		       stuff is suspended */
+    BUSY_CPU	    /* Locally busy (e.g. crypto); user interaction suspended */
+};
+void set_busy_status(void *frontend, int status);
+
+void cleanup_exit(int);
+
+/*
+ * Exports from conf.c, and a big enum (via parametric macro) of
+ * configuration option keys.
+ */
+#define CONFIG_OPTIONS(X) \
+    /* X(value-type, subkey-type, keyword) */ \
+    X(STR, NONE, host) \
+    X(INT, NONE, port) \
+    X(INT, NONE, protocol) \
+    X(INT, NONE, addressfamily) \
+    X(INT, NONE, close_on_exit) \
+    X(INT, NONE, warn_on_close) \
+    X(INT, NONE, ping_interval) /* in seconds */ \
+    X(INT, NONE, tcp_nodelay) \
+    X(INT, NONE, tcp_keepalives) \
+    X(STR, NONE, loghost) /* logical host being contacted, for host key check */ \
+    /* Proxy options */ \
+    X(STR, NONE, proxy_exclude_list) \
+    X(INT, NONE, proxy_dns) \
+    X(INT, NONE, even_proxy_localhost) \
+    X(INT, NONE, proxy_type) \
+    X(STR, NONE, proxy_host) \
+    X(INT, NONE, proxy_port) \
+    X(STR, NONE, proxy_username) \
+    X(STR, NONE, proxy_password) \
+    X(STR, NONE, proxy_telnet_command) \
+    /* SSH options */ \
+    X(STR, NONE, remote_cmd) \
+    X(STR, NONE, remote_cmd2) /* fallback if remote_cmd fails; never loaded or saved */ \
+    X(INT, NONE, nopty) \
+    X(INT, NONE, compression) \
+    X(INT, INT, ssh_kexlist) \
+    X(INT, NONE, ssh_rekey_time) /* in minutes */ \
+    X(STR, NONE, ssh_rekey_data) /* string encoding e.g. "100K", "2M", "1G" */ \
+    X(INT, NONE, tryagent) \
+    X(INT, NONE, agentfwd) \
+    X(INT, NONE, change_username) /* allow username switching in SSH-2 */ \
+    X(INT, INT, ssh_cipherlist) \
+    X(FILENAME, NONE, keyfile) \
+    X(INT, NONE, sshprot) /* use v1 or v2 when both available */ \
+    X(INT, NONE, ssh2_des_cbc) /* "des-cbc" unrecommended SSH-2 cipher */ \
+    X(INT, NONE, ssh_no_userauth) /* bypass "ssh-userauth" (SSH-2 only) */ \
+    X(INT, NONE, ssh_show_banner) /* show USERAUTH_BANNERs (SSH-2 only) */ \
+    X(INT, NONE, try_tis_auth) \
+    X(INT, NONE, try_ki_auth) \
+    X(INT, NONE, try_gssapi_auth) /* attempt gssapi auth */ \
+    X(INT, NONE, gssapifwd) /* forward tgt via gss */ \
+    X(INT, INT, ssh_gsslist) /* preference order for local GSS libs */ \
+    X(FILENAME, NONE, ssh_gss_custom) \
+    X(INT, NONE, ssh_subsys) /* run a subsystem rather than a command */ \
+    X(INT, NONE, ssh_subsys2) /* fallback to go with remote_cmd_ptr2 */ \
+    X(INT, NONE, ssh_no_shell) /* avoid running a shell */ \
+    X(STR, NONE, ssh_nc_host) /* host to connect to in `nc' mode */ \
+    X(INT, NONE, ssh_nc_port) /* port to connect to in `nc' mode */ \
+    /* Telnet options */ \
+    X(STR, NONE, termtype) \
+    X(STR, NONE, termspeed) \
+    X(STR, STR, ttymodes) /* values are "Vvalue" or "A" */ \
+    X(STR, STR, environmt) \
+    X(STR, NONE, username) \
+    X(INT, NONE, username_from_env) \
+    X(STR, NONE, localusername) \
+    X(INT, NONE, rfc_environ) \
+    X(INT, NONE, passive_telnet) \
+    /* Serial port options */ \
+    X(STR, NONE, serline) \
+    X(INT, NONE, serspeed) \
+    X(INT, NONE, serdatabits) \
+    X(INT, NONE, serstopbits) \
+    X(INT, NONE, serparity) \
+    X(INT, NONE, serflow) \
+    /* Keyboard options */ \
+    X(INT, NONE, bksp_is_delete) \
+    X(INT, NONE, rxvt_homeend) \
+    X(INT, NONE, funky_type) \
+    X(INT, NONE, no_applic_c) /* totally disable app cursor keys */ \
+    X(INT, NONE, no_applic_k) /* totally disable app keypad */ \
+    X(INT, NONE, no_mouse_rep) /* totally disable mouse reporting */ \
+    X(INT, NONE, no_remote_resize) /* disable remote resizing */ \
+    X(INT, NONE, no_alt_screen) /* disable alternate screen */ \
+    X(INT, NONE, no_remote_wintitle) /* disable remote retitling */ \
+    X(INT, NONE, no_dbackspace) /* disable destructive backspace */ \
+    X(INT, NONE, no_remote_charset) /* disable remote charset config */ \
+    X(INT, NONE, remote_qtitle_action) /* remote win title query action */ \
+    X(INT, NONE, app_cursor) \
+    X(INT, NONE, app_keypad) \
+    X(INT, NONE, nethack_keypad) \
+    X(INT, NONE, telnet_keyboard) \
+    X(INT, NONE, telnet_newline) \
+    X(INT, NONE, alt_f4) /* is it special? */ \
+    X(INT, NONE, alt_space) /* is it special? */ \
+    X(INT, NONE, alt_only) /* is it special? */ \
+    X(INT, NONE, localecho) \
+    X(INT, NONE, localedit) \
+    X(INT, NONE, alwaysontop) \
+    X(INT, NONE, fullscreenonaltenter) \
+    X(INT, NONE, scroll_on_key) \
+    X(INT, NONE, scroll_on_disp) \
+    X(INT, NONE, erase_to_scrollback) \
+    X(INT, NONE, compose_key) \
+    X(INT, NONE, ctrlaltkeys) \
+    X(STR, NONE, wintitle) /* initial window title */ \
+    /* Terminal options */ \
+    X(INT, NONE, savelines) \
+    X(INT, NONE, dec_om) \
+    X(INT, NONE, wrap_mode) \
+    X(INT, NONE, lfhascr) \
+    X(INT, NONE, cursor_type) /* 0=block 1=underline 2=vertical */ \
+    X(INT, NONE, blink_cur) \
+    X(INT, NONE, beep) \
+    X(INT, NONE, beep_ind) \
+    X(INT, NONE, bellovl) /* bell overload protection active? */ \
+    X(INT, NONE, bellovl_n) /* number of bells to cause overload */ \
+    X(INT, NONE, bellovl_t) /* time interval for overload (seconds) */ \
+    X(INT, NONE, bellovl_s) /* period of silence to re-enable bell (s) */ \
+    X(FILENAME, NONE, bell_wavefile) \
+    X(INT, NONE, scrollbar) \
+    X(INT, NONE, scrollbar_in_fullscreen) \
+    X(INT, NONE, resize_action) \
+    X(INT, NONE, bce) \
+    X(INT, NONE, blinktext) \
+    X(INT, NONE, win_name_always) \
+    X(INT, NONE, width) \
+    X(INT, NONE, height) \
+    X(FONT, NONE, font) \
+    X(INT, NONE, font_quality) \
+    X(FILENAME, NONE, logfilename) \
+    X(INT, NONE, logtype) \
+    X(INT, NONE, logxfovr) \
+    X(INT, NONE, logflush) \
+    X(INT, NONE, logomitpass) \
+    X(INT, NONE, logomitdata) \
+    X(INT, NONE, hide_mouseptr) \
+    X(INT, NONE, sunken_edge) \
+    X(INT, NONE, window_border) \
+    X(STR, NONE, answerback) \
+    X(STR, NONE, printer) \
+    X(INT, NONE, arabicshaping) \
+    X(INT, NONE, bidi) \
+    /* Colour options */ \
+    X(INT, NONE, ansi_colour) \
+    X(INT, NONE, xterm_256_colour) \
+    X(INT, NONE, system_colour) \
+    X(INT, NONE, try_palette) \
+    X(INT, NONE, bold_style) \
+    X(INT, INT, colours) \
+    /* Selection options */ \
+    X(INT, NONE, mouse_is_xterm) \
+    X(INT, NONE, rect_select) \
+    X(INT, NONE, rawcnp) \
+    X(INT, NONE, rtf_paste) \
+    X(INT, NONE, mouse_override) \
+    X(INT, INT, wordness) \
+    /* translations */ \
+    X(INT, NONE, vtmode) \
+    X(STR, NONE, line_codepage) \
+    X(INT, NONE, cjk_ambig_wide) \
+    X(INT, NONE, utf8_override) \
+    X(INT, NONE, xlat_capslockcyr) \
+    /* X11 forwarding */ \
+    X(INT, NONE, x11_forward) \
+    X(STR, NONE, x11_display) \
+    X(INT, NONE, x11_auth) \
+    X(FILENAME, NONE, xauthfile) \
+    /* port forwarding */ \
+    X(INT, NONE, lport_acceptall) /* accept conns from hosts other than localhost */ \
+    X(INT, NONE, rport_acceptall) /* same for remote forwarded ports (SSH-2 only) */ \
+    /*                                                                \
+     * Subkeys for 'portfwd' can have the following forms:            \
+     *                                                                \
+     *   [LR]localport                                                \
+     *   [LR]localaddr:localport                                      \
+     *                                                                \
+     * Dynamic forwardings are indicated by an 'L' key, and the       \
+     * special value "D". For all other forwardings, the value        \
+     * should be of the form 'host:port'.                             \
+     */ \
+    X(STR, STR, portfwd) \
+    /* SSH bug compatibility modes */ \
+    X(INT, NONE, sshbug_ignore1) \
+    X(INT, NONE, sshbug_plainpw1) \
+    X(INT, NONE, sshbug_rsa1) \
+    X(INT, NONE, sshbug_hmac2) \
+    X(INT, NONE, sshbug_derivekey2) \
+    X(INT, NONE, sshbug_rsapad2) \
+    X(INT, NONE, sshbug_pksessid2) \
+    X(INT, NONE, sshbug_rekey2) \
+    X(INT, NONE, sshbug_maxpkt2) \
+    X(INT, NONE, sshbug_ignore2) \
+    X(INT, NONE, sshbug_oldgex2) \
+    X(INT, NONE, sshbug_winadj) \
+    X(INT, NONE, sshbug_chanreq) \
+    /*                                                                \
+     * ssh_simple means that we promise never to open any channel     \
+     * other than the main one, which means it can safely use a very  \
+     * large window in SSH-2.                                         \
+     */ \
+    X(INT, NONE, ssh_simple) \
+    X(INT, NONE, ssh_connection_sharing) \
+    X(INT, NONE, ssh_connection_sharing_upstream) \
+    X(INT, NONE, ssh_connection_sharing_downstream) \
+    /*
+     * ssh_manual_hostkeys is conceptually a set rather than a
+     * dictionary: the string subkeys are the important thing, and the
+     * actual values to which those subkeys map are all "".
+     */ \
+    X(STR, STR, ssh_manual_hostkeys) \
+    /* Options for pterm. Should split out into platform-dependent part. */ \
+    X(INT, NONE, stamp_utmp) \
+    X(INT, NONE, login_shell) \
+    X(INT, NONE, scrollbar_on_left) \
+    X(INT, NONE, shadowbold) \
+    X(FONT, NONE, boldfont) \
+    X(FONT, NONE, widefont) \
+    X(FONT, NONE, wideboldfont) \
+    X(INT, NONE, shadowboldoffset) \
+    X(INT, NONE, crhaslf) \
+    X(STR, NONE, winclass) \
+
+/* Now define the actual enum of option keywords using that macro. */
+#define CONF_ENUM_DEF(valtype, keytype, keyword) CONF_ ## keyword,
+enum config_primary_key { CONFIG_OPTIONS(CONF_ENUM_DEF) N_CONFIG_OPTIONS };
+#undef CONF_ENUM_DEF
+
+#define NCFGCOLOURS 22 /* number of colours in CONF_colours above */
+
+/* Functions handling configuration structures. */
+Conf *conf_new(void);		       /* create an empty configuration */
+void conf_free(Conf *conf);
+Conf *conf_copy(Conf *oldconf);
+void conf_copy_into(Conf *dest, Conf *src);
+/* Mandatory accessor functions: enforce by assertion that keys exist. */
+int conf_get_int(Conf *conf, int key);
+int conf_get_int_int(Conf *conf, int key, int subkey);
+char *conf_get_str(Conf *conf, int key);   /* result still owned by conf */
+char *conf_get_str_str(Conf *conf, int key, const char *subkey);
+Filename *conf_get_filename(Conf *conf, int key);
+FontSpec *conf_get_fontspec(Conf *conf, int key); /* still owned by conf */
+/* Optional accessor function: return NULL if key does not exist. */
+char *conf_get_str_str_opt(Conf *conf, int key, const char *subkey);
+/* Accessor function to step through a string-subkeyed list.
+ * Returns the next subkey after the provided one, or the first if NULL.
+ * Returns NULL if there are none left.
+ * Both the return value and *subkeyout are still owned by conf. */
+char *conf_get_str_strs(Conf *conf, int key, char *subkeyin, char **subkeyout);
+/* Return the nth string subkey in a list. Owned by conf. NULL if beyond end */
+char *conf_get_str_nthstrkey(Conf *conf, int key, int n);
+/* Functions to set entries in configuration. Always copy their inputs. */
+void conf_set_int(Conf *conf, int key, int value);
+void conf_set_int_int(Conf *conf, int key, int subkey, int value);
+void conf_set_str(Conf *conf, int key, const char *value);
+void conf_set_str_str(Conf *conf, int key,
+		      const char *subkey, const char *val);
+void conf_del_str_str(Conf *conf, int key, const char *subkey);
+void conf_set_filename(Conf *conf, int key, const Filename *val);
+void conf_set_fontspec(Conf *conf, int key, const FontSpec *val);
+/* Serialisation functions for Duplicate Session */
+int conf_serialised_size(Conf *conf);
+void conf_serialise(Conf *conf, void *data);
+int conf_deserialise(Conf *conf, void *data, int maxsize);/*returns size used*/
+
+/*
+ * Functions to copy, free, serialise and deserialise FontSpecs.
+ * Provided per-platform, to go with the platform's idea of a
+ * FontSpec's contents.
+ *
+ * fontspec_serialise returns the number of bytes written, and can
+ * handle data==NULL without crashing. So you can call it once to find
+ * out a size, then again once you've allocated a buffer.
+ */
+FontSpec *fontspec_copy(const FontSpec *f);
+void fontspec_free(FontSpec *f);
+int fontspec_serialise(FontSpec *f, void *data);
+FontSpec *fontspec_deserialise(void *data, int maxsize, int *used);
+
+/*
+ * Exports from noise.c.
+ */
+void noise_get_heavy(void (*func) (void *, int));
+void noise_get_light(void (*func) (void *, int));
+void noise_regular(void);
+void noise_ultralight(unsigned long data);
+void random_save_seed(void);
+void random_destroy_seed(void);
+
+/*
+ * Exports from settings.c.
+ */
+Backend *backend_from_name(const char *name);
+Backend *backend_from_proto(int proto);
+char *get_remote_username(Conf *conf); /* dynamically allocated */
+char *save_settings(char *section, Conf *conf);
+void save_open_settings(void *sesskey, Conf *conf);
+void load_settings(char *section, Conf *conf);
+void load_open_settings(void *sesskey, Conf *conf);
+void get_sesslist(struct sesslist *, int allocate);
+void do_defaults(char *, Conf *);
+void registry_cleanup(void);
+
+/*
+ * Functions used by settings.c to provide platform-specific
+ * default settings.
+ * 
+ * (The integer one is expected to return `def' if it has no clear
+ * opinion of its own. This is because there's no integer value
+ * which I can reliably set aside to indicate `nil'. The string
+ * function is perfectly all right returning NULL, of course. The
+ * Filename and FontSpec functions are _not allowed_ to fail to
+ * return, since these defaults _must_ be per-platform.)
+ *
+ * The 'Filename *' returned by platform_default_filename, and the
+ * 'FontSpec *' returned by platform_default_fontspec, have ownership
+ * transferred to the caller, and must be freed.
+ */
+char *platform_default_s(const char *name);
+int platform_default_i(const char *name, int def);
+Filename *platform_default_filename(const char *name);
+FontSpec *platform_default_fontspec(const char *name);
+
+/*
+ * Exports from terminal.c.
+ */
+
+Terminal *term_init(Conf *, struct unicode_data *, void *);
+void term_free(Terminal *);
+void term_size(Terminal *, int, int, int);
+void term_paint(Terminal *, Context, int, int, int, int, int);
+void term_scroll(Terminal *, int, int);
+void term_scroll_to_selection(Terminal *, int);
+void term_pwron(Terminal *, int);
+void term_clrsb(Terminal *);
+void term_mouse(Terminal *, Mouse_Button, Mouse_Button, Mouse_Action,
+		int,int,int,int,int);
+void term_key(Terminal *, Key_Sym, wchar_t *, size_t, unsigned int,
+	      unsigned int);
+void term_deselect(Terminal *);
+void term_update(Terminal *);
+void term_invalidate(Terminal *);
+void term_blink(Terminal *, int set_cursor);
+void term_do_paste(Terminal *);
+void term_nopaste(Terminal *);
+int term_ldisc(Terminal *, int option);
+void term_copyall(Terminal *);
+void term_reconfig(Terminal *, Conf *);
+void term_seen_key_event(Terminal *); 
+int term_data(Terminal *, int is_stderr, const char *data, int len);
+int term_data_untrusted(Terminal *, const char *data, int len);
+void term_provide_resize_fn(Terminal *term,
+			    void (*resize_fn)(void *, int, int),
+			    void *resize_ctx);
+void term_provide_logctx(Terminal *term, void *logctx);
+void term_set_focus(Terminal *term, int has_focus);
+char *term_get_ttymode(Terminal *term, const char *mode);
+int term_get_userpass_input(Terminal *term, prompts_t *p,
+			    unsigned char *in, int inlen);
+
+int format_arrow_key(char *buf, Terminal *term, int xkey, int ctrl);
+
+/*
+ * Exports from logging.c.
+ */
+void *log_init(void *frontend, Conf *conf);
+void log_free(void *logctx);
+void log_reconfig(void *logctx, Conf *conf);
+void logfopen(void *logctx);
+void logfclose(void *logctx);
+void logtraffic(void *logctx, unsigned char c, int logmode);
+void logflush(void *logctx);
+void log_eventlog(void *logctx, const char *string);
+enum { PKT_INCOMING, PKT_OUTGOING };
+enum { PKTLOG_EMIT, PKTLOG_BLANK, PKTLOG_OMIT };
+struct logblank_t {
+    int offset;
+    int len;
+    int type;
+};
+void log_packet(void *logctx, int direction, int type,
+		char *texttype, const void *data, int len,
+		int n_blanks, const struct logblank_t *blanks,
+		const unsigned long *sequence,
+                unsigned downstream_id, const char *additional_log_text);
+
+/*
+ * Exports from testback.c
+ */
+
+extern Backend null_backend;
+extern Backend loop_backend;
+
+/*
+ * Exports from raw.c.
+ */
+
+extern Backend raw_backend;
+
+/*
+ * Exports from rlogin.c.
+ */
+
+extern Backend rlogin_backend;
+
+/*
+ * Exports from telnet.c.
+ */
+
+extern Backend telnet_backend;
+
+/*
+ * Exports from ssh.c.
+ */
+extern Backend ssh_backend;
+
+/*
+ * Exports from ldisc.c.
+ */
+void *ldisc_create(Conf *, Terminal *, Backend *, void *, void *);
+void ldisc_configure(void *, Conf *);
+void ldisc_free(void *);
+void ldisc_send(void *handle, char *buf, int len, int interactive);
+
+/*
+ * Exports from ldiscucs.c.
+ */
+void lpage_send(void *, int codepage, char *buf, int len, int interactive);
+void luni_send(void *, wchar_t * widebuf, int len, int interactive);
+
+/*
+ * Exports from sshrand.c.
+ */
+
+void random_add_noise(void *noise, int length);
+int random_byte(void);
+void random_get_savedata(void **data, int *len);
+extern int random_active;
+/* The random number subsystem is activated if at least one other entity
+ * within the program expresses an interest in it. So each SSH session
+ * calls random_ref on startup and random_unref on shutdown. */
+void random_ref(void);
+void random_unref(void);
+
+/*
+ * Exports from pinger.c.
+ */
+typedef struct pinger_tag *Pinger;
+Pinger pinger_new(Conf *conf, Backend *back, void *backhandle);
+void pinger_reconfig(Pinger, Conf *oldconf, Conf *newconf);
+void pinger_free(Pinger);
+
+/*
+ * Exports from misc.c.
+ */
+
+#include "misc.h"
+int conf_launchable(Conf *conf);
+char const *conf_dest(Conf *conf);
+
+/*
+ * Exports from sercfg.c.
+ */
+void ser_setup_config_box(struct controlbox *b, int midsession,
+			  int parity_mask, int flow_mask);
+
+/*
+ * Exports from version.c.
+ */
+extern char ver[];
+
+/*
+ * Exports from unicode.c.
+ */
+#ifndef CP_UTF8
+#define CP_UTF8 65001
+#endif
+/* void init_ucs(void); -- this is now in platform-specific headers */
+int is_dbcs_leadbyte(int codepage, char byte);
+int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen,
+	     wchar_t *wcstr, int wclen);
+int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen,
+	     char *mbstr, int mblen, char *defchr, int *defused,
+	     struct unicode_data *ucsdata);
+wchar_t xlat_uskbd2cyrllic(int ch);
+int check_compose(int first, int second);
+int decode_codepage(char *cp_name);
+const char *cp_enumerate (int index);
+const char *cp_name(int codepage);
+void get_unitab(int codepage, wchar_t * unitab, int ftype);
+
+/*
+ * Exports from wcwidth.c
+ */
+int mk_wcwidth(unsigned int ucs);
+int mk_wcswidth(const unsigned int *pwcs, size_t n);
+int mk_wcwidth_cjk(unsigned int ucs);
+int mk_wcswidth_cjk(const unsigned int *pwcs, size_t n);
+
+/*
+ * Exports from mscrypto.c
+ */
+#ifdef MSCRYPTOAPI
+int crypto_startup();
+void crypto_wrapup();
+#endif
+
+/*
+ * Exports from pageantc.c.
+ * 
+ * agent_query returns 1 for here's-a-response, and 0 for query-in-
+ * progress. In the latter case there will be a call to `callback'
+ * at some future point, passing callback_ctx as the first
+ * parameter and the actual reply data as the second and third.
+ * 
+ * The response may be a NULL pointer (in either of the synchronous
+ * or asynchronous cases), which indicates failure to receive a
+ * response.
+ */
+int agent_query(void *in, int inlen, void **out, int *outlen,
+		void (*callback)(void *, void *, int), void *callback_ctx);
+int agent_exists(void);
+
+/*
+ * Exports from wildcard.c
+ */
+const char *wc_error(int value);
+int wc_match(const char *wildcard, const char *target);
+int wc_unescape(char *output, const char *wildcard);
+
+/*
+ * Exports from frontend (windlg.c etc)
+ */
+void logevent(void *frontend, const char *);
+void pgp_fingerprints(void);
+/*
+ * verify_ssh_host_key() can return one of three values:
+ * 
+ *  - +1 means `key was OK' (either already known or the user just
+ *    approved it) `so continue with the connection'
+ * 
+ *  - 0 means `key was not OK, abandon the connection'
+ * 
+ *  - -1 means `I've initiated enquiries, please wait to be called
+ *    back via the provided function with a result that's either 0
+ *    or +1'.
+ */
+int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
+                        char *keystr, char *fingerprint,
+                        void (*callback)(void *ctx, int result), void *ctx);
+/*
+ * askalg has the same set of return values as verify_ssh_host_key.
+ */
+int askalg(void *frontend, const char *algtype, const char *algname,
+	   void (*callback)(void *ctx, int result), void *ctx);
+/*
+ * askappend can return four values:
+ * 
+ *  - 2 means overwrite the log file
+ *  - 1 means append to the log file
+ *  - 0 means cancel logging for this session
+ *  - -1 means please wait.
+ */
+int askappend(void *frontend, Filename *filename,
+	      void (*callback)(void *ctx, int result), void *ctx);
+
+/*
+ * Exports from console frontends (wincons.c, uxcons.c)
+ * that aren't equivalents to things in windlg.c et al.
+ */
+extern int console_batch_mode;
+int console_get_userpass_input(prompts_t *p, unsigned char *in, int inlen);
+void console_provide_logctx(void *logctx);
+int is_interactive(void);
+
+/*
+ * Exports from printing.c.
+ */
+typedef struct printer_enum_tag printer_enum;
+typedef struct printer_job_tag printer_job;
+printer_enum *printer_start_enum(int *nprinters);
+char *printer_get_name(printer_enum *, int);
+void printer_finish_enum(printer_enum *);
+printer_job *printer_start_job(char *printer);
+void printer_job_data(printer_job *, void *, int);
+void printer_finish_job(printer_job *);
+
+/*
+ * Exports from cmdline.c (and also cmdline_error(), which is
+ * defined differently in various places and required _by_
+ * cmdline.c).
+ */
+int cmdline_process_param(char *, char *, int, Conf *);
+void cmdline_run_saved(Conf *);
+void cmdline_cleanup(void);
+int cmdline_get_passwd_input(prompts_t *p, unsigned char *in, int inlen);
+#define TOOLTYPE_FILETRANSFER 1
+#define TOOLTYPE_NONNETWORK 2
+extern int cmdline_tooltype;
+
+void cmdline_error(char *, ...);
+
+/*
+ * Exports from config.c.
+ */
+struct controlbox;
+union control;
+void conf_radiobutton_handler(union control *ctrl, void *dlg,
+			      void *data, int event);
+#define CHECKBOX_INVERT (1<<30)
+void conf_checkbox_handler(union control *ctrl, void *dlg,
+			   void *data, int event);
+void conf_editbox_handler(union control *ctrl, void *dlg,
+			  void *data, int event);
+void conf_filesel_handler(union control *ctrl, void *dlg,
+			  void *data, int event);
+void conf_fontsel_handler(union control *ctrl, void *dlg,
+			  void *data, int event);
+void setup_config_box(struct controlbox *b, int midsession,
+		      int protocol, int protcfginfo);
+
+/*
+ * Exports from minibidi.c.
+ */
+typedef struct bidi_char {
+    unsigned int origwc, wc;
+    unsigned short index;
+} bidi_char;
+int do_bidi(bidi_char *line, int count);
+int do_shape(bidi_char *line, bidi_char *to, int count);
+int is_rtl(int c);
+
+/*
+ * X11 auth mechanisms we know about.
+ */
+enum {
+    X11_NO_AUTH,
+    X11_MIT,                           /* MIT-MAGIC-COOKIE-1 */
+    X11_XDM,			       /* XDM-AUTHORIZATION-1 */
+    X11_NAUTHS
+};
+extern const char *const x11_authnames[];  /* declared in x11fwd.c */
+
+/*
+ * Miscellaneous exports from the platform-specific code.
+ *
+ * filename_serialise and filename_deserialise have the same semantics
+ * as fontspec_serialise and fontspec_deserialise above.
+ */
+Filename *filename_from_str(const char *string);
+const char *filename_to_str(const Filename *fn);
+int filename_equal(const Filename *f1, const Filename *f2);
+int filename_is_null(const Filename *fn);
+Filename *filename_copy(const Filename *fn);
+void filename_free(Filename *fn);
+int filename_serialise(const Filename *f, void *data);
+Filename *filename_deserialise(void *data, int maxsize, int *used);
+char *get_username(void);	       /* return value needs freeing */
+char *get_random_data(int bytes);      /* used in cmdgen.c */
+
+/*
+ * Exports and imports from timing.c.
+ *
+ * schedule_timer() asks the front end to schedule a callback to a
+ * timer function in a given number of ticks. The returned value is
+ * the time (in ticks since an arbitrary offset) at which the
+ * callback can be expected. This value will also be passed as the
+ * `now' parameter to the callback function. Hence, you can (for
+ * example) schedule an event at a particular time by calling
+ * schedule_timer() and storing the return value in your context
+ * structure as the time when that event is due. The first time a
+ * callback function gives you that value or more as `now', you do
+ * the thing.
+ * 
+ * expire_timer_context() drops all current timers associated with
+ * a given value of ctx (for when you're about to free ctx).
+ * 
+ * run_timers() is called from the front end when it has reason to
+ * think some timers have reached their moment, or when it simply
+ * needs to know how long to wait next. We pass it the time we
+ * think it is. It returns TRUE and places the time when the next
+ * timer needs to go off in `next', or alternatively it returns
+ * FALSE if there are no timers at all pending.
+ * 
+ * timer_change_notify() must be supplied by the front end; it
+ * notifies the front end that a new timer has been added to the
+ * list which is sooner than any existing ones. It provides the
+ * time when that timer needs to go off.
+ * 
+ * *** FRONT END IMPLEMENTORS NOTE:
+ * 
+ * There's an important subtlety in the front-end implementation of
+ * the timer interface. When a front end is given a `next' value,
+ * either returned from run_timers() or via timer_change_notify(),
+ * it should ensure that it really passes _that value_ as the `now'
+ * parameter to its next run_timers call. It should _not_ simply
+ * call GETTICKCOUNT() to get the `now' parameter when invoking
+ * run_timers().
+ * 
+ * The reason for this is that an OS's system clock might not agree
+ * exactly with the timing mechanisms it supplies to wait for a
+ * given interval. I'll illustrate this by the simple example of
+ * Unix Plink, which uses timeouts to select() in a way which for
+ * these purposes can simply be considered to be a wait() function.
+ * Suppose, for the sake of argument, that this wait() function
+ * tends to return early by 1%. Then a possible sequence of actions
+ * is:
+ * 
+ *  - run_timers() tells the front end that the next timer firing
+ *    is 10000ms from now.
+ *  - Front end calls wait(10000ms), but according to
+ *    GETTICKCOUNT() it has only waited for 9900ms.
+ *  - Front end calls run_timers() again, passing time T-100ms as
+ *    `now'.
+ *  - run_timers() does nothing, and says the next timer firing is
+ *    still 100ms from now.
+ *  - Front end calls wait(100ms), which only waits for 99ms.
+ *  - Front end calls run_timers() yet again, passing time T-1ms.
+ *  - run_timers() says there's still 1ms to wait.
+ *  - Front end calls wait(1ms).
+ * 
+ * If you're _lucky_ at this point, wait(1ms) will actually wait
+ * for 1ms and you'll only have woken the program up three times.
+ * If you're unlucky, wait(1ms) might do nothing at all due to
+ * being below some minimum threshold, and you might find your
+ * program spends the whole of the last millisecond tight-looping
+ * between wait() and run_timers().
+ * 
+ * Instead, what you should do is to _save_ the precise `next'
+ * value provided by run_timers() or via timer_change_notify(), and
+ * use that precise value as the input to the next run_timers()
+ * call. So:
+ * 
+ *  - run_timers() tells the front end that the next timer firing
+ *    is at time T, 10000ms from now.
+ *  - Front end calls wait(10000ms).
+ *  - Front end then immediately calls run_timers() and passes it
+ *    time T, without stopping to check GETTICKCOUNT() at all.
+ * 
+ * This guarantees that the program wakes up only as many times as
+ * there are actual timer actions to be taken, and that the timing
+ * mechanism will never send it into a tight loop.
+ * 
+ * (It does also mean that the timer action in the above example
+ * will occur 100ms early, but this is not generally critical. And
+ * the hypothetical 1% error in wait() will be partially corrected
+ * for anyway when, _after_ run_timers() returns, you call
+ * GETTICKCOUNT() and compare the result with the returned `next'
+ * value to find out how long you have to make your next wait().)
+ */
+typedef void (*timer_fn_t)(void *ctx, unsigned long now);
+unsigned long schedule_timer(int ticks, timer_fn_t fn, void *ctx);
+void expire_timer_context(void *ctx);
+int run_timers(unsigned long now, unsigned long *next);
+void timer_change_notify(unsigned long next);
+
+/*
+ * Exports from callback.c.
+ *
+ * This provides a method of queuing function calls to be run at the
+ * earliest convenience from the top-level event loop. Use it if
+ * you're deep in a nested chain of calls and want to trigger an
+ * action which will probably lead to your function being re-entered
+ * recursively if you just call the initiating function the normal
+ * way.
+ *
+ * Most front ends run the queued callbacks by simply calling
+ * run_toplevel_callbacks() after handling each event in their
+ * top-level event loop. However, if a front end doesn't have control
+ * over its own event loop (e.g. because it's using GTK) then it can
+ * instead request notifications when a callback is available, so that
+ * it knows to ask its delegate event loop to do the same thing. Also,
+ * if a front end needs to know whether a callback is pending without
+ * actually running it (e.g. so as to put a zero timeout on a select()
+ * call) then it can call toplevel_callback_pending(), which will
+ * return true if at least one callback is in the queue.
+ */
+typedef void (*toplevel_callback_fn_t)(void *ctx);
+void queue_toplevel_callback(toplevel_callback_fn_t fn, void *ctx);
+void run_toplevel_callbacks(void);
+int toplevel_callback_pending(void);
+
+typedef void (*toplevel_callback_notify_fn_t)(void *frontend);
+void request_callback_notifications(toplevel_callback_notify_fn_t notify,
+                                    void *frontend);
+
+/*
+ * Define no-op macros for the jump list functions, on platforms that
+ * don't support them. (This is a bit of a hack, and it'd be nicer to
+ * localise even the calls to those functions into the Windows front
+ * end, but it'll do for the moment.)
+ */
+#ifndef JUMPLIST_SUPPORTED
+#define add_session_to_jumplist(x) ((void)0)
+#define remove_session_from_jumplist(x) ((void)0)
+#endif
+
+/* SURROGATE PAIR */
+#define HIGH_SURROGATE_START 0xd800
+#define HIGH_SURROGATE_END 0xdbff
+#define LOW_SURROGATE_START 0xdc00
+#define LOW_SURROGATE_END 0xdfff
+
+/* These macros exist in the Windows API, so the environment may
+ * provide them. If not, define them in terms of the above. */
+#ifndef IS_HIGH_SURROGATE
+#define IS_HIGH_SURROGATE(wch) (((wch) >= HIGH_SURROGATE_START) && \
+                                ((wch) <= HIGH_SURROGATE_END))
+#define IS_LOW_SURROGATE(wch) (((wch) >= LOW_SURROGATE_START) && \
+                               ((wch) <= LOW_SURROGATE_END))
+#define IS_SURROGATE_PAIR(hs, ls) (IS_HIGH_SURROGATE(hs) && \
+                                   IS_LOW_SURROGATE(ls))
+#endif
+
+
+#define IS_SURROGATE(wch) (((wch) >= HIGH_SURROGATE_START) &&   \
+                           ((wch) <= LOW_SURROGATE_END))
+#define HIGH_SURROGATE_OF(codept) \
+    (HIGH_SURROGATE_START + (((codept) - 0x10000) >> 10))
+#define LOW_SURROGATE_OF(codept) \
+    (LOW_SURROGATE_START + (((codept) - 0x10000) & 0x3FF))
+#define FROM_SURROGATES(wch1, wch2) \
+    (0x10000 + (((wch1) & 0x3FF) << 10) + ((wch2) & 0x3FF))
+
+#endif
diff -uNr putty-0.65/settings.c putty-url-0.65/settings.c
--- putty-0.65/settings.c	2015-07-25 12:22:48.000000000 +0200
+++ putty-url-0.65/settings.c	2015-09-12 15:35:59.000000000 +0200
@@ -7,6 +7,7 @@
 #include <stdlib.h>
 #include "putty.h"
 #include "storage.h"
+#include "urlhack.h"
 
 /* The cipher order given here is the default order. */
 static const struct keyvalwhere ciphernames[] = {
@@ -652,6 +653,14 @@
     write_setting_i(sesskey, "ConnectionSharingUpstream", conf_get_int(conf, CONF_ssh_connection_sharing_upstream));
     write_setting_i(sesskey, "ConnectionSharingDownstream", conf_get_int(conf, CONF_ssh_connection_sharing_downstream));
     wmap(sesskey, "SSHManualHostKeys", conf, CONF_ssh_manual_hostkeys, FALSE);
+
+    /* PuTTY-url */
+    write_setting_i(sesskey, "HyperlinkUnderline", conf_get_int(conf, CONF_url_underline));
+    write_setting_i(sesskey, "HyperlinkUseCtrlClick", conf_get_int(conf, CONF_url_ctrl_click));
+    write_setting_i(sesskey, "HyperlinkBrowserUseDefault", conf_get_int(conf, CONF_url_defbrowser));
+    write_setting_filename(sesskey, "HyperlinkBrowser", conf_get_filename(conf, CONF_url_browser));
+    write_setting_i(sesskey, "HyperlinkRegularExpressionUseDefault", conf_get_int(conf, CONF_url_defregex));
+    write_setting_s(sesskey, "HyperlinkRegularExpression", conf_get_str(conf, CONF_url_regex));
 }
 
 void load_settings(char *section, Conf *conf)
@@ -671,6 +680,14 @@
     int i;
     char *prot;
 
+    /*
+     * HACK: PuTTY-url
+     * Set font quality to cleartype on Windows Vista and above
+     */
+    OSVERSIONINFO versioninfo;
+    versioninfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
+    GetVersionEx(&versioninfo);
+
     conf_set_int(conf, CONF_ssh_subsys, 0);   /* FIXME: load this properly */
     conf_set_str(conf, CONF_remote_cmd, "");
     conf_set_str(conf, CONF_remote_cmd2, "");
@@ -865,7 +882,7 @@
 		 / 1000
 #endif
 		 );
-    gppi(sesskey, "ScrollbackLines", 2000, conf, CONF_savelines);
+    gppi(sesskey, "ScrollbackLines", 10000, conf, CONF_savelines); // HACK: PuTTY-url
     gppi(sesskey, "DECOriginMode", 0, conf, CONF_dec_om);
     gppi(sesskey, "AutoWrapMode", 1, conf, CONF_wrap_mode);
     gppi(sesskey, "LFImpliesCR", 0, conf, CONF_lfhascr);
@@ -877,7 +894,17 @@
     gppi(sesskey, "TermWidth", 80, conf, CONF_width);
     gppi(sesskey, "TermHeight", 24, conf, CONF_height);
     gppfont(sesskey, "Font", conf, CONF_font);
-    gppi(sesskey, "FontQuality", FQ_DEFAULT, conf, CONF_font_quality);
+
+    /*
+     * HACK: PuTTY-url
+     * Set font quality to cleartype on Windows Vista and higher
+     */
+    if (versioninfo.dwMajorVersion >= 6) {
+        gppi(sesskey, "FontQuality", FQ_CLEARTYPE, conf, CONF_font_quality);
+    } else {
+        gppi(sesskey, "FontQuality", FQ_DEFAULT, conf, CONF_font_quality);
+    }
+
     gppi(sesskey, "FontVTMode", VT_UNICODE, conf, CONF_vtmode);
     gppi(sesskey, "UseSystemColours", 0, conf, CONF_system_colour);
     gppi(sesskey, "TryPalette", 0, conf, CONF_try_palette);
@@ -1001,6 +1028,14 @@
     gppi(sesskey, "ConnectionSharingUpstream", 1, conf, CONF_ssh_connection_sharing_upstream);
     gppi(sesskey, "ConnectionSharingDownstream", 1, conf, CONF_ssh_connection_sharing_downstream);
     gppmap(sesskey, "SSHManualHostKeys", conf, CONF_ssh_manual_hostkeys);
+
+    /* PuTTY-url */
+    gppi(sesskey, "HyperlinkUnderline", 1, conf, CONF_url_underline);
+    gppi(sesskey, "HyperlinkUseCtrlClick", 0, conf, CONF_url_ctrl_click);
+    gppi(sesskey, "HyperlinkBrowserUseDefault", 1, conf, CONF_url_defbrowser);
+    gppfile(sesskey, "HyperlinkBrowser", conf, CONF_url_browser);
+    gppi(sesskey, "HyperlinkRegularExpressionUseDefault", 1, conf, CONF_url_defregex);
+    gpps(sesskey, "HyperlinkRegularExpression", urlhack_default_regex, conf, CONF_url_regex);
 }
 
 void do_defaults(char *session, Conf *conf)
diff -uNr putty-0.65/settings.c.orig putty-url-0.65/settings.c.orig
--- putty-0.65/settings.c.orig	1970-01-01 01:00:00.000000000 +0100
+++ putty-url-0.65/settings.c.orig	2015-07-25 12:22:48.000000000 +0200
@@ -0,0 +1,1095 @@
+/*
+ * settings.c: read and write saved sessions. (platform-independent)
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "putty.h"
+#include "storage.h"
+
+/* The cipher order given here is the default order. */
+static const struct keyvalwhere ciphernames[] = {
+    { "aes",        CIPHER_AES,             -1, -1 },
+    { "blowfish",   CIPHER_BLOWFISH,        -1, -1 },
+    { "3des",       CIPHER_3DES,            -1, -1 },
+    { "WARN",       CIPHER_WARN,            -1, -1 },
+    { "arcfour",    CIPHER_ARCFOUR,         -1, -1 },
+    { "des",        CIPHER_DES,             -1, -1 }
+};
+
+static const struct keyvalwhere kexnames[] = {
+    { "dh-gex-sha1",        KEX_DHGEX,      -1, -1 },
+    { "dh-group14-sha1",    KEX_DHGROUP14,  -1, -1 },
+    { "dh-group1-sha1",     KEX_DHGROUP1,   -1, -1 },
+    { "rsa",                KEX_RSA,        KEX_WARN, -1 },
+    { "WARN",               KEX_WARN,       -1, -1 }
+};
+
+/*
+ * All the terminal modes that we know about for the "TerminalModes"
+ * setting. (Also used by config.c for the drop-down list.)
+ * This is currently precisely the same as the set in ssh.c, but could
+ * in principle differ if other backends started to support tty modes
+ * (e.g., the pty backend).
+ */
+const char *const ttymodes[] = {
+    "INTR",	"QUIT",     "ERASE",	"KILL",     "EOF",
+    "EOL",	"EOL2",     "START",	"STOP",     "SUSP",
+    "DSUSP",	"REPRINT",  "WERASE",	"LNEXT",    "FLUSH",
+    "SWTCH",	"STATUS",   "DISCARD",	"IGNPAR",   "PARMRK",
+    "INPCK",	"ISTRIP",   "INLCR",	"IGNCR",    "ICRNL",
+    "IUCLC",	"IXON",     "IXANY",	"IXOFF",    "IMAXBEL",
+    "ISIG",	"ICANON",   "XCASE",	"ECHO",     "ECHOE",
+    "ECHOK",	"ECHONL",   "NOFLSH",	"TOSTOP",   "IEXTEN",
+    "ECHOCTL",	"ECHOKE",   "PENDIN",	"OPOST",    "OLCUC",
+    "ONLCR",	"OCRNL",    "ONOCR",	"ONLRET",   "CS7",
+    "CS8",	"PARENB",   "PARODD",	NULL
+};
+
+/*
+ * Convenience functions to access the backends[] array
+ * (which is only present in tools that manage settings).
+ */
+
+Backend *backend_from_name(const char *name)
+{
+    Backend **p;
+    for (p = backends; *p != NULL; p++)
+	if (!strcmp((*p)->name, name))
+	    return *p;
+    return NULL;
+}
+
+Backend *backend_from_proto(int proto)
+{
+    Backend **p;
+    for (p = backends; *p != NULL; p++)
+	if ((*p)->protocol == proto)
+	    return *p;
+    return NULL;
+}
+
+char *get_remote_username(Conf *conf)
+{
+    char *username = conf_get_str(conf, CONF_username);
+    if (*username) {
+	return dupstr(username);
+    } else if (conf_get_int(conf, CONF_username_from_env)) {
+	/* Use local username. */
+	return get_username();     /* might still be NULL */
+    } else {
+	return NULL;
+    }
+}
+
+static char *gpps_raw(void *handle, const char *name, const char *def)
+{
+    char *ret = read_setting_s(handle, name);
+    if (!ret)
+	ret = platform_default_s(name);
+    if (!ret)
+	ret = def ? dupstr(def) : NULL;   /* permit NULL as final fallback */
+    return ret;
+}
+
+static void gpps(void *handle, const char *name, const char *def,
+		 Conf *conf, int primary)
+{
+    char *val = gpps_raw(handle, name, def);
+    conf_set_str(conf, primary, val);
+    sfree(val);
+}
+
+/*
+ * gppfont and gppfile cannot have local defaults, since the very
+ * format of a Filename or FontSpec is platform-dependent. So the
+ * platform-dependent functions MUST return some sort of value.
+ */
+static void gppfont(void *handle, const char *name, Conf *conf, int primary)
+{
+    FontSpec *result = read_setting_fontspec(handle, name);
+    if (!result)
+        result = platform_default_fontspec(name);
+    conf_set_fontspec(conf, primary, result);
+    fontspec_free(result);
+}
+static void gppfile(void *handle, const char *name, Conf *conf, int primary)
+{
+    Filename *result = read_setting_filename(handle, name);
+    if (!result)
+	result = platform_default_filename(name);
+    conf_set_filename(conf, primary, result);
+    filename_free(result);
+}
+
+static int gppi_raw(void *handle, char *name, int def)
+{
+    def = platform_default_i(name, def);
+    return read_setting_i(handle, name, def);
+}
+
+static void gppi(void *handle, char *name, int def, Conf *conf, int primary)
+{
+    conf_set_int(conf, primary, gppi_raw(handle, name, def));
+}
+
+/*
+ * Read a set of name-value pairs in the format we occasionally use:
+ *   NAME\tVALUE\0NAME\tVALUE\0\0 in memory
+ *   NAME=VALUE,NAME=VALUE, in storage
+ * If there's no "=VALUE" (e.g. just NAME,NAME,NAME) then those keys
+ * are mapped to the empty string.
+ */
+static int gppmap(void *handle, char *name, Conf *conf, int primary)
+{
+    char *buf, *p, *q, *key, *val;
+
+    /*
+     * Start by clearing any existing subkeys of this key from conf.
+     */
+    while ((key = conf_get_str_nthstrkey(conf, primary, 0)) != NULL)
+        conf_del_str_str(conf, primary, key);
+
+    /*
+     * Now read a serialised list from the settings and unmarshal it
+     * into its components.
+     */
+    buf = gpps_raw(handle, name, NULL);
+    if (!buf)
+	return FALSE;
+
+    p = buf;
+    while (*p) {
+	q = buf;
+	val = NULL;
+	while (*p && *p != ',') {
+	    int c = *p++;
+	    if (c == '=')
+		c = '\0';
+	    if (c == '\\')
+		c = *p++;
+	    *q++ = c;
+	    if (!c)
+		val = q;
+	}
+	if (*p == ',')
+	    p++;
+	if (!val)
+	    val = q;
+	*q = '\0';
+
+        if (primary == CONF_portfwd && strchr(buf, 'D') != NULL) {
+            /*
+             * Backwards-compatibility hack: dynamic forwardings are
+             * indexed in the data store as a third type letter in the
+             * key, 'D' alongside 'L' and 'R' - but really, they
+             * should be filed under 'L' with a special _value_,
+             * because local and dynamic forwardings both involve
+             * _listening_ on a local port, and are hence mutually
+             * exclusive on the same port number. So here we translate
+             * the legacy storage format into the sensible internal
+             * form, by finding the D and turning it into a L.
+             */
+            char *newkey = dupstr(buf);
+            *strchr(newkey, 'D') = 'L';
+            conf_set_str_str(conf, primary, newkey, "D");
+            sfree(newkey);
+        } else {
+            conf_set_str_str(conf, primary, buf, val);
+        }
+    }
+    sfree(buf);
+
+    return TRUE;
+}
+
+/*
+ * Write a set of name/value pairs in the above format, or just the
+ * names if include_values is FALSE.
+ */
+static void wmap(void *handle, char const *outkey, Conf *conf, int primary,
+                 int include_values)
+{
+    char *buf, *p, *q, *key, *realkey, *val;
+    int len;
+
+    len = 1;			       /* allow for NUL */
+
+    for (val = conf_get_str_strs(conf, primary, NULL, &key);
+	 val != NULL;
+	 val = conf_get_str_strs(conf, primary, key, &key))
+	len += 2 + 2 * (strlen(key) + strlen(val));   /* allow for escaping */
+
+    buf = snewn(len, char);
+    p = buf;
+
+    for (val = conf_get_str_strs(conf, primary, NULL, &key);
+	 val != NULL;
+	 val = conf_get_str_strs(conf, primary, key, &key)) {
+
+        if (primary == CONF_portfwd && !strcmp(val, "D")) {
+            /*
+             * Backwards-compatibility hack, as above: translate from
+             * the sensible internal representation of dynamic
+             * forwardings (key "L<port>", value "D") to the
+             * conceptually incoherent legacy storage format (key
+             * "D<port>", value empty).
+             */
+            char *L;
+
+            realkey = key;             /* restore it at end of loop */
+            val = "";
+            key = dupstr(key);
+            L = strchr(key, 'L');
+            if (L) *L = 'D';
+        } else {
+            realkey = NULL;
+        }
+
+	if (p != buf)
+	    *p++ = ',';
+	for (q = key; *q; q++) {
+	    if (*q == '=' || *q == ',' || *q == '\\')
+		*p++ = '\\';
+	    *p++ = *q;
+	}
+        if (include_values) {
+            *p++ = '=';
+            for (q = val; *q; q++) {
+                if (*q == '=' || *q == ',' || *q == '\\')
+                    *p++ = '\\';
+                *p++ = *q;
+            }
+        }
+
+        if (realkey) {
+            free(key);
+            key = realkey;
+        }
+    }
+    *p = '\0';
+    write_setting_s(handle, outkey, buf);
+    sfree(buf);
+}
+
+static int key2val(const struct keyvalwhere *mapping,
+                   int nmaps, char *key)
+{
+    int i;
+    for (i = 0; i < nmaps; i++)
+	if (!strcmp(mapping[i].s, key)) return mapping[i].v;
+    return -1;
+}
+
+static const char *val2key(const struct keyvalwhere *mapping,
+                           int nmaps, int val)
+{
+    int i;
+    for (i = 0; i < nmaps; i++)
+	if (mapping[i].v == val) return mapping[i].s;
+    return NULL;
+}
+
+/*
+ * Helper function to parse a comma-separated list of strings into
+ * a preference list array of values. Any missing values are added
+ * to the end and duplicates are weeded.
+ * XXX: assumes vals in 'mapping' are small +ve integers
+ */
+static void gprefs(void *sesskey, char *name, char *def,
+		   const struct keyvalwhere *mapping, int nvals,
+		   Conf *conf, int primary)
+{
+    char *commalist;
+    char *p, *q;
+    int i, j, n, v, pos;
+    unsigned long seen = 0;	       /* bitmap for weeding dups etc */
+
+    /*
+     * Fetch the string which we'll parse as a comma-separated list.
+     */
+    commalist = gpps_raw(sesskey, name, def);
+
+    /*
+     * Go through that list and convert it into values.
+     */
+    n = 0;
+    p = commalist;
+    while (1) {
+        while (*p && *p == ',') p++;
+        if (!*p)
+            break;                     /* no more words */
+
+        q = p;
+        while (*p && *p != ',') p++;
+        if (*p) *p++ = '\0';
+
+        v = key2val(mapping, nvals, q);
+        if (v != -1 && !(seen & (1 << v))) {
+	    seen |= (1 << v);
+            conf_set_int_int(conf, primary, n, v);
+            n++;
+	}
+    }
+
+    sfree(commalist);
+
+    /*
+     * Now go through 'mapping' and add values that weren't mentioned
+     * in the list we fetched. We may have to loop over it multiple
+     * times so that we add values before other values whose default
+     * positions depend on them.
+     */
+    while (n < nvals) {
+        for (i = 0; i < nvals; i++) {
+	    assert(mapping[i].v < 32);
+
+	    if (!(seen & (1 << mapping[i].v))) {
+                /*
+                 * This element needs adding. But can we add it yet?
+                 */
+                if (mapping[i].vrel != -1 && !(seen & (1 << mapping[i].vrel)))
+                    continue;          /* nope */
+
+                /*
+                 * OK, we can work out where to add this element, so
+                 * do so.
+                 */
+                if (mapping[i].vrel == -1) {
+                    pos = (mapping[i].where < 0 ? n : 0);
+                } else {
+                    for (j = 0; j < n; j++)
+                        if (conf_get_int_int(conf, primary, j) ==
+                            mapping[i].vrel)
+                            break;
+                    assert(j < n);     /* implied by (seen & (1<<vrel)) */
+                    pos = (mapping[i].where < 0 ? j : j+1);
+                }
+
+                /*
+                 * And add it.
+                 */
+                for (j = n-1; j >= pos; j--)
+                    conf_set_int_int(conf, primary, j+1,
+                                     conf_get_int_int(conf, primary, j));
+                conf_set_int_int(conf, primary, pos, mapping[i].v);
+                n++;
+            }
+        }
+    }
+}
+
+/* 
+ * Write out a preference list.
+ */
+static void wprefs(void *sesskey, char *name,
+		   const struct keyvalwhere *mapping, int nvals,
+		   Conf *conf, int primary)
+{
+    char *buf, *p;
+    int i, maxlen;
+
+    for (maxlen = i = 0; i < nvals; i++) {
+	const char *s = val2key(mapping, nvals,
+                                conf_get_int_int(conf, primary, i));
+	if (s) {
+            maxlen += (maxlen > 0 ? 1 : 0) + strlen(s);
+        }
+    }
+
+    buf = snewn(maxlen + 1, char);
+    p = buf;
+
+    for (i = 0; i < nvals; i++) {
+	const char *s = val2key(mapping, nvals,
+                                conf_get_int_int(conf, primary, i));
+	if (s) {
+            p += sprintf(p, "%s%s", (p > buf ? "," : ""), s);
+	}
+    }
+
+    assert(p - buf == maxlen);
+    *p = '\0';
+
+    write_setting_s(sesskey, name, buf);
+
+    sfree(buf);
+}
+
+char *save_settings(char *section, Conf *conf)
+{
+    void *sesskey;
+    char *errmsg;
+
+    sesskey = open_settings_w(section, &errmsg);
+    if (!sesskey)
+	return errmsg;
+    save_open_settings(sesskey, conf);
+    close_settings_w(sesskey);
+    return NULL;
+}
+
+void save_open_settings(void *sesskey, Conf *conf)
+{
+    int i;
+    char *p;
+
+    write_setting_i(sesskey, "Present", 1);
+    write_setting_s(sesskey, "HostName", conf_get_str(conf, CONF_host));
+    write_setting_filename(sesskey, "LogFileName", conf_get_filename(conf, CONF_logfilename));
+    write_setting_i(sesskey, "LogType", conf_get_int(conf, CONF_logtype));
+    write_setting_i(sesskey, "LogFileClash", conf_get_int(conf, CONF_logxfovr));
+    write_setting_i(sesskey, "LogFlush", conf_get_int(conf, CONF_logflush));
+    write_setting_i(sesskey, "SSHLogOmitPasswords", conf_get_int(conf, CONF_logomitpass));
+    write_setting_i(sesskey, "SSHLogOmitData", conf_get_int(conf, CONF_logomitdata));
+    p = "raw";
+    {
+	const Backend *b = backend_from_proto(conf_get_int(conf, CONF_protocol));
+	if (b)
+	    p = b->name;
+    }
+    write_setting_s(sesskey, "Protocol", p);
+    write_setting_i(sesskey, "PortNumber", conf_get_int(conf, CONF_port));
+    /* The CloseOnExit numbers are arranged in a different order from
+     * the standard FORCE_ON / FORCE_OFF / AUTO. */
+    write_setting_i(sesskey, "CloseOnExit", (conf_get_int(conf, CONF_close_on_exit)+2)%3);
+    write_setting_i(sesskey, "WarnOnClose", !!conf_get_int(conf, CONF_warn_on_close));
+    write_setting_i(sesskey, "PingInterval", conf_get_int(conf, CONF_ping_interval) / 60);	/* minutes */
+    write_setting_i(sesskey, "PingIntervalSecs", conf_get_int(conf, CONF_ping_interval) % 60);	/* seconds */
+    write_setting_i(sesskey, "TCPNoDelay", conf_get_int(conf, CONF_tcp_nodelay));
+    write_setting_i(sesskey, "TCPKeepalives", conf_get_int(conf, CONF_tcp_keepalives));
+    write_setting_s(sesskey, "TerminalType", conf_get_str(conf, CONF_termtype));
+    write_setting_s(sesskey, "TerminalSpeed", conf_get_str(conf, CONF_termspeed));
+    wmap(sesskey, "TerminalModes", conf, CONF_ttymodes, TRUE);
+
+    /* Address family selection */
+    write_setting_i(sesskey, "AddressFamily", conf_get_int(conf, CONF_addressfamily));
+
+    /* proxy settings */
+    write_setting_s(sesskey, "ProxyExcludeList", conf_get_str(conf, CONF_proxy_exclude_list));
+    write_setting_i(sesskey, "ProxyDNS", (conf_get_int(conf, CONF_proxy_dns)+2)%3);
+    write_setting_i(sesskey, "ProxyLocalhost", conf_get_int(conf, CONF_even_proxy_localhost));
+    write_setting_i(sesskey, "ProxyMethod", conf_get_int(conf, CONF_proxy_type));
+    write_setting_s(sesskey, "ProxyHost", conf_get_str(conf, CONF_proxy_host));
+    write_setting_i(sesskey, "ProxyPort", conf_get_int(conf, CONF_proxy_port));
+    write_setting_s(sesskey, "ProxyUsername", conf_get_str(conf, CONF_proxy_username));
+    write_setting_s(sesskey, "ProxyPassword", conf_get_str(conf, CONF_proxy_password));
+    write_setting_s(sesskey, "ProxyTelnetCommand", conf_get_str(conf, CONF_proxy_telnet_command));
+    wmap(sesskey, "Environment", conf, CONF_environmt, TRUE);
+    write_setting_s(sesskey, "UserName", conf_get_str(conf, CONF_username));
+    write_setting_i(sesskey, "UserNameFromEnvironment", conf_get_int(conf, CONF_username_from_env));
+    write_setting_s(sesskey, "LocalUserName", conf_get_str(conf, CONF_localusername));
+    write_setting_i(sesskey, "NoPTY", conf_get_int(conf, CONF_nopty));
+    write_setting_i(sesskey, "Compression", conf_get_int(conf, CONF_compression));
+    write_setting_i(sesskey, "TryAgent", conf_get_int(conf, CONF_tryagent));
+    write_setting_i(sesskey, "AgentFwd", conf_get_int(conf, CONF_agentfwd));
+    write_setting_i(sesskey, "GssapiFwd", conf_get_int(conf, CONF_gssapifwd));
+    write_setting_i(sesskey, "ChangeUsername", conf_get_int(conf, CONF_change_username));
+    wprefs(sesskey, "Cipher", ciphernames, CIPHER_MAX, conf, CONF_ssh_cipherlist);
+    wprefs(sesskey, "KEX", kexnames, KEX_MAX, conf, CONF_ssh_kexlist);
+    write_setting_i(sesskey, "RekeyTime", conf_get_int(conf, CONF_ssh_rekey_time));
+    write_setting_s(sesskey, "RekeyBytes", conf_get_str(conf, CONF_ssh_rekey_data));
+    write_setting_i(sesskey, "SshNoAuth", conf_get_int(conf, CONF_ssh_no_userauth));
+    write_setting_i(sesskey, "SshBanner", conf_get_int(conf, CONF_ssh_show_banner));
+    write_setting_i(sesskey, "AuthTIS", conf_get_int(conf, CONF_try_tis_auth));
+    write_setting_i(sesskey, "AuthKI", conf_get_int(conf, CONF_try_ki_auth));
+    write_setting_i(sesskey, "AuthGSSAPI", conf_get_int(conf, CONF_try_gssapi_auth));
+#ifndef NO_GSSAPI
+    wprefs(sesskey, "GSSLibs", gsslibkeywords, ngsslibs, conf, CONF_ssh_gsslist);
+    write_setting_filename(sesskey, "GSSCustom", conf_get_filename(conf, CONF_ssh_gss_custom));
+#endif
+    write_setting_i(sesskey, "SshNoShell", conf_get_int(conf, CONF_ssh_no_shell));
+    write_setting_i(sesskey, "SshProt", conf_get_int(conf, CONF_sshprot));
+    write_setting_s(sesskey, "LogHost", conf_get_str(conf, CONF_loghost));
+    write_setting_i(sesskey, "SSH2DES", conf_get_int(conf, CONF_ssh2_des_cbc));
+    write_setting_filename(sesskey, "PublicKeyFile", conf_get_filename(conf, CONF_keyfile));
+    write_setting_s(sesskey, "RemoteCommand", conf_get_str(conf, CONF_remote_cmd));
+    write_setting_i(sesskey, "RFCEnviron", conf_get_int(conf, CONF_rfc_environ));
+    write_setting_i(sesskey, "PassiveTelnet", conf_get_int(conf, CONF_passive_telnet));
+    write_setting_i(sesskey, "BackspaceIsDelete", conf_get_int(conf, CONF_bksp_is_delete));
+    write_setting_i(sesskey, "RXVTHomeEnd", conf_get_int(conf, CONF_rxvt_homeend));
+    write_setting_i(sesskey, "LinuxFunctionKeys", conf_get_int(conf, CONF_funky_type));
+    write_setting_i(sesskey, "NoApplicationKeys", conf_get_int(conf, CONF_no_applic_k));
+    write_setting_i(sesskey, "NoApplicationCursors", conf_get_int(conf, CONF_no_applic_c));
+    write_setting_i(sesskey, "NoMouseReporting", conf_get_int(conf, CONF_no_mouse_rep));
+    write_setting_i(sesskey, "NoRemoteResize", conf_get_int(conf, CONF_no_remote_resize));
+    write_setting_i(sesskey, "NoAltScreen", conf_get_int(conf, CONF_no_alt_screen));
+    write_setting_i(sesskey, "NoRemoteWinTitle", conf_get_int(conf, CONF_no_remote_wintitle));
+    write_setting_i(sesskey, "RemoteQTitleAction", conf_get_int(conf, CONF_remote_qtitle_action));
+    write_setting_i(sesskey, "NoDBackspace", conf_get_int(conf, CONF_no_dbackspace));
+    write_setting_i(sesskey, "NoRemoteCharset", conf_get_int(conf, CONF_no_remote_charset));
+    write_setting_i(sesskey, "ApplicationCursorKeys", conf_get_int(conf, CONF_app_cursor));
+    write_setting_i(sesskey, "ApplicationKeypad", conf_get_int(conf, CONF_app_keypad));
+    write_setting_i(sesskey, "NetHackKeypad", conf_get_int(conf, CONF_nethack_keypad));
+    write_setting_i(sesskey, "AltF4", conf_get_int(conf, CONF_alt_f4));
+    write_setting_i(sesskey, "AltSpace", conf_get_int(conf, CONF_alt_space));
+    write_setting_i(sesskey, "AltOnly", conf_get_int(conf, CONF_alt_only));
+    write_setting_i(sesskey, "ComposeKey", conf_get_int(conf, CONF_compose_key));
+    write_setting_i(sesskey, "CtrlAltKeys", conf_get_int(conf, CONF_ctrlaltkeys));
+    write_setting_i(sesskey, "TelnetKey", conf_get_int(conf, CONF_telnet_keyboard));
+    write_setting_i(sesskey, "TelnetRet", conf_get_int(conf, CONF_telnet_newline));
+    write_setting_i(sesskey, "LocalEcho", conf_get_int(conf, CONF_localecho));
+    write_setting_i(sesskey, "LocalEdit", conf_get_int(conf, CONF_localedit));
+    write_setting_s(sesskey, "Answerback", conf_get_str(conf, CONF_answerback));
+    write_setting_i(sesskey, "AlwaysOnTop", conf_get_int(conf, CONF_alwaysontop));
+    write_setting_i(sesskey, "FullScreenOnAltEnter", conf_get_int(conf, CONF_fullscreenonaltenter));
+    write_setting_i(sesskey, "HideMousePtr", conf_get_int(conf, CONF_hide_mouseptr));
+    write_setting_i(sesskey, "SunkenEdge", conf_get_int(conf, CONF_sunken_edge));
+    write_setting_i(sesskey, "WindowBorder", conf_get_int(conf, CONF_window_border));
+    write_setting_i(sesskey, "CurType", conf_get_int(conf, CONF_cursor_type));
+    write_setting_i(sesskey, "BlinkCur", conf_get_int(conf, CONF_blink_cur));
+    write_setting_i(sesskey, "Beep", conf_get_int(conf, CONF_beep));
+    write_setting_i(sesskey, "BeepInd", conf_get_int(conf, CONF_beep_ind));
+    write_setting_filename(sesskey, "BellWaveFile", conf_get_filename(conf, CONF_bell_wavefile));
+    write_setting_i(sesskey, "BellOverload", conf_get_int(conf, CONF_bellovl));
+    write_setting_i(sesskey, "BellOverloadN", conf_get_int(conf, CONF_bellovl_n));
+    write_setting_i(sesskey, "BellOverloadT", conf_get_int(conf, CONF_bellovl_t)
+#ifdef PUTTY_UNIX_H
+		    * 1000
+#endif
+		    );
+    write_setting_i(sesskey, "BellOverloadS", conf_get_int(conf, CONF_bellovl_s)
+#ifdef PUTTY_UNIX_H
+		    * 1000
+#endif
+		    );
+    write_setting_i(sesskey, "ScrollbackLines", conf_get_int(conf, CONF_savelines));
+    write_setting_i(sesskey, "DECOriginMode", conf_get_int(conf, CONF_dec_om));
+    write_setting_i(sesskey, "AutoWrapMode", conf_get_int(conf, CONF_wrap_mode));
+    write_setting_i(sesskey, "LFImpliesCR", conf_get_int(conf, CONF_lfhascr));
+    write_setting_i(sesskey, "CRImpliesLF", conf_get_int(conf, CONF_crhaslf));
+    write_setting_i(sesskey, "DisableArabicShaping", conf_get_int(conf, CONF_arabicshaping));
+    write_setting_i(sesskey, "DisableBidi", conf_get_int(conf, CONF_bidi));
+    write_setting_i(sesskey, "WinNameAlways", conf_get_int(conf, CONF_win_name_always));
+    write_setting_s(sesskey, "WinTitle", conf_get_str(conf, CONF_wintitle));
+    write_setting_i(sesskey, "TermWidth", conf_get_int(conf, CONF_width));
+    write_setting_i(sesskey, "TermHeight", conf_get_int(conf, CONF_height));
+    write_setting_fontspec(sesskey, "Font", conf_get_fontspec(conf, CONF_font));
+    write_setting_i(sesskey, "FontQuality", conf_get_int(conf, CONF_font_quality));
+    write_setting_i(sesskey, "FontVTMode", conf_get_int(conf, CONF_vtmode));
+    write_setting_i(sesskey, "UseSystemColours", conf_get_int(conf, CONF_system_colour));
+    write_setting_i(sesskey, "TryPalette", conf_get_int(conf, CONF_try_palette));
+    write_setting_i(sesskey, "ANSIColour", conf_get_int(conf, CONF_ansi_colour));
+    write_setting_i(sesskey, "Xterm256Colour", conf_get_int(conf, CONF_xterm_256_colour));
+    write_setting_i(sesskey, "BoldAsColour", conf_get_int(conf, CONF_bold_style)-1);
+
+    for (i = 0; i < 22; i++) {
+	char buf[20], buf2[30];
+	sprintf(buf, "Colour%d", i);
+	sprintf(buf2, "%d,%d,%d",
+		conf_get_int_int(conf, CONF_colours, i*3+0),
+		conf_get_int_int(conf, CONF_colours, i*3+1),
+		conf_get_int_int(conf, CONF_colours, i*3+2));
+	write_setting_s(sesskey, buf, buf2);
+    }
+    write_setting_i(sesskey, "RawCNP", conf_get_int(conf, CONF_rawcnp));
+    write_setting_i(sesskey, "PasteRTF", conf_get_int(conf, CONF_rtf_paste));
+    write_setting_i(sesskey, "MouseIsXterm", conf_get_int(conf, CONF_mouse_is_xterm));
+    write_setting_i(sesskey, "RectSelect", conf_get_int(conf, CONF_rect_select));
+    write_setting_i(sesskey, "MouseOverride", conf_get_int(conf, CONF_mouse_override));
+    for (i = 0; i < 256; i += 32) {
+	char buf[20], buf2[256];
+	int j;
+	sprintf(buf, "Wordness%d", i);
+	*buf2 = '\0';
+	for (j = i; j < i + 32; j++) {
+	    sprintf(buf2 + strlen(buf2), "%s%d",
+		    (*buf2 ? "," : ""),
+		    conf_get_int_int(conf, CONF_wordness, j));
+	}
+	write_setting_s(sesskey, buf, buf2);
+    }
+    write_setting_s(sesskey, "LineCodePage", conf_get_str(conf, CONF_line_codepage));
+    write_setting_i(sesskey, "CJKAmbigWide", conf_get_int(conf, CONF_cjk_ambig_wide));
+    write_setting_i(sesskey, "UTF8Override", conf_get_int(conf, CONF_utf8_override));
+    write_setting_s(sesskey, "Printer", conf_get_str(conf, CONF_printer));
+    write_setting_i(sesskey, "CapsLockCyr", conf_get_int(conf, CONF_xlat_capslockcyr));
+    write_setting_i(sesskey, "ScrollBar", conf_get_int(conf, CONF_scrollbar));
+    write_setting_i(sesskey, "ScrollBarFullScreen", conf_get_int(conf, CONF_scrollbar_in_fullscreen));
+    write_setting_i(sesskey, "ScrollOnKey", conf_get_int(conf, CONF_scroll_on_key));
+    write_setting_i(sesskey, "ScrollOnDisp", conf_get_int(conf, CONF_scroll_on_disp));
+    write_setting_i(sesskey, "EraseToScrollback", conf_get_int(conf, CONF_erase_to_scrollback));
+    write_setting_i(sesskey, "LockSize", conf_get_int(conf, CONF_resize_action));
+    write_setting_i(sesskey, "BCE", conf_get_int(conf, CONF_bce));
+    write_setting_i(sesskey, "BlinkText", conf_get_int(conf, CONF_blinktext));
+    write_setting_i(sesskey, "X11Forward", conf_get_int(conf, CONF_x11_forward));
+    write_setting_s(sesskey, "X11Display", conf_get_str(conf, CONF_x11_display));
+    write_setting_i(sesskey, "X11AuthType", conf_get_int(conf, CONF_x11_auth));
+    write_setting_filename(sesskey, "X11AuthFile", conf_get_filename(conf, CONF_xauthfile));
+    write_setting_i(sesskey, "LocalPortAcceptAll", conf_get_int(conf, CONF_lport_acceptall));
+    write_setting_i(sesskey, "RemotePortAcceptAll", conf_get_int(conf, CONF_rport_acceptall));
+    wmap(sesskey, "PortForwardings", conf, CONF_portfwd, TRUE);
+    write_setting_i(sesskey, "BugIgnore1", 2-conf_get_int(conf, CONF_sshbug_ignore1));
+    write_setting_i(sesskey, "BugPlainPW1", 2-conf_get_int(conf, CONF_sshbug_plainpw1));
+    write_setting_i(sesskey, "BugRSA1", 2-conf_get_int(conf, CONF_sshbug_rsa1));
+    write_setting_i(sesskey, "BugIgnore2", 2-conf_get_int(conf, CONF_sshbug_ignore2));
+    write_setting_i(sesskey, "BugHMAC2", 2-conf_get_int(conf, CONF_sshbug_hmac2));
+    write_setting_i(sesskey, "BugDeriveKey2", 2-conf_get_int(conf, CONF_sshbug_derivekey2));
+    write_setting_i(sesskey, "BugRSAPad2", 2-conf_get_int(conf, CONF_sshbug_rsapad2));
+    write_setting_i(sesskey, "BugPKSessID2", 2-conf_get_int(conf, CONF_sshbug_pksessid2));
+    write_setting_i(sesskey, "BugRekey2", 2-conf_get_int(conf, CONF_sshbug_rekey2));
+    write_setting_i(sesskey, "BugMaxPkt2", 2-conf_get_int(conf, CONF_sshbug_maxpkt2));
+    write_setting_i(sesskey, "BugOldGex2", 2-conf_get_int(conf, CONF_sshbug_oldgex2));
+    write_setting_i(sesskey, "BugWinadj", 2-conf_get_int(conf, CONF_sshbug_winadj));
+    write_setting_i(sesskey, "BugChanReq", 2-conf_get_int(conf, CONF_sshbug_chanreq));
+    write_setting_i(sesskey, "StampUtmp", conf_get_int(conf, CONF_stamp_utmp));
+    write_setting_i(sesskey, "LoginShell", conf_get_int(conf, CONF_login_shell));
+    write_setting_i(sesskey, "ScrollbarOnLeft", conf_get_int(conf, CONF_scrollbar_on_left));
+    write_setting_fontspec(sesskey, "BoldFont", conf_get_fontspec(conf, CONF_boldfont));
+    write_setting_fontspec(sesskey, "WideFont", conf_get_fontspec(conf, CONF_widefont));
+    write_setting_fontspec(sesskey, "WideBoldFont", conf_get_fontspec(conf, CONF_wideboldfont));
+    write_setting_i(sesskey, "ShadowBold", conf_get_int(conf, CONF_shadowbold));
+    write_setting_i(sesskey, "ShadowBoldOffset", conf_get_int(conf, CONF_shadowboldoffset));
+    write_setting_s(sesskey, "SerialLine", conf_get_str(conf, CONF_serline));
+    write_setting_i(sesskey, "SerialSpeed", conf_get_int(conf, CONF_serspeed));
+    write_setting_i(sesskey, "SerialDataBits", conf_get_int(conf, CONF_serdatabits));
+    write_setting_i(sesskey, "SerialStopHalfbits", conf_get_int(conf, CONF_serstopbits));
+    write_setting_i(sesskey, "SerialParity", conf_get_int(conf, CONF_serparity));
+    write_setting_i(sesskey, "SerialFlowControl", conf_get_int(conf, CONF_serflow));
+    write_setting_s(sesskey, "WindowClass", conf_get_str(conf, CONF_winclass));
+    write_setting_i(sesskey, "ConnectionSharing", conf_get_int(conf, CONF_ssh_connection_sharing));
+    write_setting_i(sesskey, "ConnectionSharingUpstream", conf_get_int(conf, CONF_ssh_connection_sharing_upstream));
+    write_setting_i(sesskey, "ConnectionSharingDownstream", conf_get_int(conf, CONF_ssh_connection_sharing_downstream));
+    wmap(sesskey, "SSHManualHostKeys", conf, CONF_ssh_manual_hostkeys, FALSE);
+}
+
+void load_settings(char *section, Conf *conf)
+{
+    void *sesskey;
+
+    sesskey = open_settings_r(section);
+    load_open_settings(sesskey, conf);
+    close_settings_r(sesskey);
+
+    if (conf_launchable(conf))
+        add_session_to_jumplist(section);
+}
+
+void load_open_settings(void *sesskey, Conf *conf)
+{
+    int i;
+    char *prot;
+
+    conf_set_int(conf, CONF_ssh_subsys, 0);   /* FIXME: load this properly */
+    conf_set_str(conf, CONF_remote_cmd, "");
+    conf_set_str(conf, CONF_remote_cmd2, "");
+    conf_set_str(conf, CONF_ssh_nc_host, "");
+
+    gpps(sesskey, "HostName", "", conf, CONF_host);
+    gppfile(sesskey, "LogFileName", conf, CONF_logfilename);
+    gppi(sesskey, "LogType", 0, conf, CONF_logtype);
+    gppi(sesskey, "LogFileClash", LGXF_ASK, conf, CONF_logxfovr);
+    gppi(sesskey, "LogFlush", 1, conf, CONF_logflush);
+    gppi(sesskey, "SSHLogOmitPasswords", 1, conf, CONF_logomitpass);
+    gppi(sesskey, "SSHLogOmitData", 0, conf, CONF_logomitdata);
+
+    prot = gpps_raw(sesskey, "Protocol", "default");
+    conf_set_int(conf, CONF_protocol, default_protocol);
+    conf_set_int(conf, CONF_port, default_port);
+    {
+	const Backend *b = backend_from_name(prot);
+	if (b) {
+	    conf_set_int(conf, CONF_protocol, b->protocol);
+	    gppi(sesskey, "PortNumber", default_port, conf, CONF_port);
+	}
+    }
+    sfree(prot);
+
+    /* Address family selection */
+    gppi(sesskey, "AddressFamily", ADDRTYPE_UNSPEC, conf, CONF_addressfamily);
+
+    /* The CloseOnExit numbers are arranged in a different order from
+     * the standard FORCE_ON / FORCE_OFF / AUTO. */
+    i = gppi_raw(sesskey, "CloseOnExit", 1); conf_set_int(conf, CONF_close_on_exit, (i+1)%3);
+    gppi(sesskey, "WarnOnClose", 1, conf, CONF_warn_on_close);
+    {
+	/* This is two values for backward compatibility with 0.50/0.51 */
+	int pingmin, pingsec;
+	pingmin = gppi_raw(sesskey, "PingInterval", 0);
+	pingsec = gppi_raw(sesskey, "PingIntervalSecs", 0);
+	conf_set_int(conf, CONF_ping_interval, pingmin * 60 + pingsec);
+    }
+    gppi(sesskey, "TCPNoDelay", 1, conf, CONF_tcp_nodelay);
+    gppi(sesskey, "TCPKeepalives", 0, conf, CONF_tcp_keepalives);
+    gpps(sesskey, "TerminalType", "xterm", conf, CONF_termtype);
+    gpps(sesskey, "TerminalSpeed", "38400,38400", conf, CONF_termspeed);
+    if (!gppmap(sesskey, "TerminalModes", conf, CONF_ttymodes)) {
+	/* This hardcodes a big set of defaults in any new saved
+	 * sessions. Let's hope we don't change our mind. */
+	for (i = 0; ttymodes[i]; i++)
+	    conf_set_str_str(conf, CONF_ttymodes, ttymodes[i], "A");
+    }
+
+    /* proxy settings */
+    gpps(sesskey, "ProxyExcludeList", "", conf, CONF_proxy_exclude_list);
+    i = gppi_raw(sesskey, "ProxyDNS", 1); conf_set_int(conf, CONF_proxy_dns, (i+1)%3);
+    gppi(sesskey, "ProxyLocalhost", 0, conf, CONF_even_proxy_localhost);
+    gppi(sesskey, "ProxyMethod", -1, conf, CONF_proxy_type);
+    if (conf_get_int(conf, CONF_proxy_type) == -1) {
+        int i;
+        i = gppi_raw(sesskey, "ProxyType", 0);
+        if (i == 0)
+            conf_set_int(conf, CONF_proxy_type, PROXY_NONE);
+        else if (i == 1)
+            conf_set_int(conf, CONF_proxy_type, PROXY_HTTP);
+        else if (i == 3)
+            conf_set_int(conf, CONF_proxy_type, PROXY_TELNET);
+        else if (i == 4)
+            conf_set_int(conf, CONF_proxy_type, PROXY_CMD);
+        else {
+            i = gppi_raw(sesskey, "ProxySOCKSVersion", 5);
+            if (i == 5)
+                conf_set_int(conf, CONF_proxy_type, PROXY_SOCKS5);
+            else
+                conf_set_int(conf, CONF_proxy_type, PROXY_SOCKS4);
+        }
+    }
+    gpps(sesskey, "ProxyHost", "proxy", conf, CONF_proxy_host);
+    gppi(sesskey, "ProxyPort", 80, conf, CONF_proxy_port);
+    gpps(sesskey, "ProxyUsername", "", conf, CONF_proxy_username);
+    gpps(sesskey, "ProxyPassword", "", conf, CONF_proxy_password);
+    gpps(sesskey, "ProxyTelnetCommand", "connect %host %port\\n",
+	 conf, CONF_proxy_telnet_command);
+    gppmap(sesskey, "Environment", conf, CONF_environmt);
+    gpps(sesskey, "UserName", "", conf, CONF_username);
+    gppi(sesskey, "UserNameFromEnvironment", 0, conf, CONF_username_from_env);
+    gpps(sesskey, "LocalUserName", "", conf, CONF_localusername);
+    gppi(sesskey, "NoPTY", 0, conf, CONF_nopty);
+    gppi(sesskey, "Compression", 0, conf, CONF_compression);
+    gppi(sesskey, "TryAgent", 1, conf, CONF_tryagent);
+    gppi(sesskey, "AgentFwd", 0, conf, CONF_agentfwd);
+    gppi(sesskey, "ChangeUsername", 0, conf, CONF_change_username);
+    gppi(sesskey, "GssapiFwd", 0, conf, CONF_gssapifwd);
+    gprefs(sesskey, "Cipher", "\0",
+	   ciphernames, CIPHER_MAX, conf, CONF_ssh_cipherlist);
+    {
+	/* Backward-compatibility: we used to have an option to
+	 * disable gex under the "bugs" panel after one report of
+	 * a server which offered it then choked, but we never got
+	 * a server version string or any other reports. */
+	char *default_kexes;
+	i = 2 - gppi_raw(sesskey, "BugDHGEx2", 0);
+	if (i == FORCE_ON)
+	    default_kexes = "dh-group14-sha1,dh-group1-sha1,rsa,WARN,dh-gex-sha1";
+	else
+	    default_kexes = "dh-gex-sha1,dh-group14-sha1,dh-group1-sha1,rsa,WARN";
+	gprefs(sesskey, "KEX", default_kexes,
+	       kexnames, KEX_MAX, conf, CONF_ssh_kexlist);
+    }
+    gppi(sesskey, "RekeyTime", 60, conf, CONF_ssh_rekey_time);
+    gpps(sesskey, "RekeyBytes", "1G", conf, CONF_ssh_rekey_data);
+    /* SSH-2 only by default */
+    gppi(sesskey, "SshProt", 3, conf, CONF_sshprot);
+    gpps(sesskey, "LogHost", "", conf, CONF_loghost);
+    gppi(sesskey, "SSH2DES", 0, conf, CONF_ssh2_des_cbc);
+    gppi(sesskey, "SshNoAuth", 0, conf, CONF_ssh_no_userauth);
+    gppi(sesskey, "SshBanner", 1, conf, CONF_ssh_show_banner);
+    gppi(sesskey, "AuthTIS", 0, conf, CONF_try_tis_auth);
+    gppi(sesskey, "AuthKI", 1, conf, CONF_try_ki_auth);
+    gppi(sesskey, "AuthGSSAPI", 1, conf, CONF_try_gssapi_auth);
+#ifndef NO_GSSAPI
+    gprefs(sesskey, "GSSLibs", "\0",
+	   gsslibkeywords, ngsslibs, conf, CONF_ssh_gsslist);
+    gppfile(sesskey, "GSSCustom", conf, CONF_ssh_gss_custom);
+#endif
+    gppi(sesskey, "SshNoShell", 0, conf, CONF_ssh_no_shell);
+    gppfile(sesskey, "PublicKeyFile", conf, CONF_keyfile);
+    gpps(sesskey, "RemoteCommand", "", conf, CONF_remote_cmd);
+    gppi(sesskey, "RFCEnviron", 0, conf, CONF_rfc_environ);
+    gppi(sesskey, "PassiveTelnet", 0, conf, CONF_passive_telnet);
+    gppi(sesskey, "BackspaceIsDelete", 1, conf, CONF_bksp_is_delete);
+    gppi(sesskey, "RXVTHomeEnd", 0, conf, CONF_rxvt_homeend);
+    gppi(sesskey, "LinuxFunctionKeys", 0, conf, CONF_funky_type);
+    gppi(sesskey, "NoApplicationKeys", 0, conf, CONF_no_applic_k);
+    gppi(sesskey, "NoApplicationCursors", 0, conf, CONF_no_applic_c);
+    gppi(sesskey, "NoMouseReporting", 0, conf, CONF_no_mouse_rep);
+    gppi(sesskey, "NoRemoteResize", 0, conf, CONF_no_remote_resize);
+    gppi(sesskey, "NoAltScreen", 0, conf, CONF_no_alt_screen);
+    gppi(sesskey, "NoRemoteWinTitle", 0, conf, CONF_no_remote_wintitle);
+    {
+	/* Backward compatibility */
+	int no_remote_qtitle = gppi_raw(sesskey, "NoRemoteQTitle", 1);
+	/* We deliberately interpret the old setting of "no response" as
+	 * "empty string". This changes the behaviour, but hopefully for
+	 * the better; the user can always recover the old behaviour. */
+	gppi(sesskey, "RemoteQTitleAction",
+	     no_remote_qtitle ? TITLE_EMPTY : TITLE_REAL,
+	     conf, CONF_remote_qtitle_action);
+    }
+    gppi(sesskey, "NoDBackspace", 0, conf, CONF_no_dbackspace);
+    gppi(sesskey, "NoRemoteCharset", 0, conf, CONF_no_remote_charset);
+    gppi(sesskey, "ApplicationCursorKeys", 0, conf, CONF_app_cursor);
+    gppi(sesskey, "ApplicationKeypad", 0, conf, CONF_app_keypad);
+    gppi(sesskey, "NetHackKeypad", 0, conf, CONF_nethack_keypad);
+    gppi(sesskey, "AltF4", 1, conf, CONF_alt_f4);
+    gppi(sesskey, "AltSpace", 0, conf, CONF_alt_space);
+    gppi(sesskey, "AltOnly", 0, conf, CONF_alt_only);
+    gppi(sesskey, "ComposeKey", 0, conf, CONF_compose_key);
+    gppi(sesskey, "CtrlAltKeys", 1, conf, CONF_ctrlaltkeys);
+    gppi(sesskey, "TelnetKey", 0, conf, CONF_telnet_keyboard);
+    gppi(sesskey, "TelnetRet", 1, conf, CONF_telnet_newline);
+    gppi(sesskey, "LocalEcho", AUTO, conf, CONF_localecho);
+    gppi(sesskey, "LocalEdit", AUTO, conf, CONF_localedit);
+    gpps(sesskey, "Answerback", "PuTTY", conf, CONF_answerback);
+    gppi(sesskey, "AlwaysOnTop", 0, conf, CONF_alwaysontop);
+    gppi(sesskey, "FullScreenOnAltEnter", 0, conf, CONF_fullscreenonaltenter);
+    gppi(sesskey, "HideMousePtr", 0, conf, CONF_hide_mouseptr);
+    gppi(sesskey, "SunkenEdge", 0, conf, CONF_sunken_edge);
+    gppi(sesskey, "WindowBorder", 1, conf, CONF_window_border);
+    gppi(sesskey, "CurType", 0, conf, CONF_cursor_type);
+    gppi(sesskey, "BlinkCur", 0, conf, CONF_blink_cur);
+    /* pedantic compiler tells me I can't use conf, CONF_beep as an int * :-) */
+    gppi(sesskey, "Beep", 1, conf, CONF_beep);
+    gppi(sesskey, "BeepInd", 0, conf, CONF_beep_ind);
+    gppfile(sesskey, "BellWaveFile", conf, CONF_bell_wavefile);
+    gppi(sesskey, "BellOverload", 1, conf, CONF_bellovl);
+    gppi(sesskey, "BellOverloadN", 5, conf, CONF_bellovl_n);
+    i = gppi_raw(sesskey, "BellOverloadT", 2*TICKSPERSEC
+#ifdef PUTTY_UNIX_H
+				   *1000
+#endif
+				   );
+    conf_set_int(conf, CONF_bellovl_t, i
+#ifdef PUTTY_UNIX_H
+		 / 1000
+#endif
+		 );
+    i = gppi_raw(sesskey, "BellOverloadS", 5*TICKSPERSEC
+#ifdef PUTTY_UNIX_H
+				   *1000
+#endif
+				   );
+    conf_set_int(conf, CONF_bellovl_s, i
+#ifdef PUTTY_UNIX_H
+		 / 1000
+#endif
+		 );
+    gppi(sesskey, "ScrollbackLines", 2000, conf, CONF_savelines);
+    gppi(sesskey, "DECOriginMode", 0, conf, CONF_dec_om);
+    gppi(sesskey, "AutoWrapMode", 1, conf, CONF_wrap_mode);
+    gppi(sesskey, "LFImpliesCR", 0, conf, CONF_lfhascr);
+    gppi(sesskey, "CRImpliesLF", 0, conf, CONF_crhaslf);
+    gppi(sesskey, "DisableArabicShaping", 0, conf, CONF_arabicshaping);
+    gppi(sesskey, "DisableBidi", 0, conf, CONF_bidi);
+    gppi(sesskey, "WinNameAlways", 1, conf, CONF_win_name_always);
+    gpps(sesskey, "WinTitle", "", conf, CONF_wintitle);
+    gppi(sesskey, "TermWidth", 80, conf, CONF_width);
+    gppi(sesskey, "TermHeight", 24, conf, CONF_height);
+    gppfont(sesskey, "Font", conf, CONF_font);
+    gppi(sesskey, "FontQuality", FQ_DEFAULT, conf, CONF_font_quality);
+    gppi(sesskey, "FontVTMode", VT_UNICODE, conf, CONF_vtmode);
+    gppi(sesskey, "UseSystemColours", 0, conf, CONF_system_colour);
+    gppi(sesskey, "TryPalette", 0, conf, CONF_try_palette);
+    gppi(sesskey, "ANSIColour", 1, conf, CONF_ansi_colour);
+    gppi(sesskey, "Xterm256Colour", 1, conf, CONF_xterm_256_colour);
+    i = gppi_raw(sesskey, "BoldAsColour", 1); conf_set_int(conf, CONF_bold_style, i+1);
+
+    for (i = 0; i < 22; i++) {
+	static const char *const defaults[] = {
+	    "187,187,187", "255,255,255", "0,0,0", "85,85,85", "0,0,0",
+	    "0,255,0", "0,0,0", "85,85,85", "187,0,0", "255,85,85",
+	    "0,187,0", "85,255,85", "187,187,0", "255,255,85", "0,0,187",
+	    "85,85,255", "187,0,187", "255,85,255", "0,187,187",
+	    "85,255,255", "187,187,187", "255,255,255"
+	};
+	char buf[20], *buf2;
+	int c0, c1, c2;
+	sprintf(buf, "Colour%d", i);
+	buf2 = gpps_raw(sesskey, buf, defaults[i]);
+	if (sscanf(buf2, "%d,%d,%d", &c0, &c1, &c2) == 3) {
+	    conf_set_int_int(conf, CONF_colours, i*3+0, c0);
+	    conf_set_int_int(conf, CONF_colours, i*3+1, c1);
+	    conf_set_int_int(conf, CONF_colours, i*3+2, c2);
+	}
+	sfree(buf2);
+    }
+    gppi(sesskey, "RawCNP", 0, conf, CONF_rawcnp);
+    gppi(sesskey, "PasteRTF", 0, conf, CONF_rtf_paste);
+    gppi(sesskey, "MouseIsXterm", 0, conf, CONF_mouse_is_xterm);
+    gppi(sesskey, "RectSelect", 0, conf, CONF_rect_select);
+    gppi(sesskey, "MouseOverride", 1, conf, CONF_mouse_override);
+    for (i = 0; i < 256; i += 32) {
+	static const char *const defaults[] = {
+	    "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",
+	    "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",
+	    "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",
+	    "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",
+	    "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",
+	    "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",
+	    "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",
+	    "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"
+	};
+	char buf[20], *buf2, *p;
+	int j;
+	sprintf(buf, "Wordness%d", i);
+	buf2 = gpps_raw(sesskey, buf, defaults[i / 32]);
+	p = buf2;
+	for (j = i; j < i + 32; j++) {
+	    char *q = p;
+	    while (*p && *p != ',')
+		p++;
+	    if (*p == ',')
+		*p++ = '\0';
+	    conf_set_int_int(conf, CONF_wordness, j, atoi(q));
+	}
+	sfree(buf2);
+    }
+    /*
+     * The empty default for LineCodePage will be converted later
+     * into a plausible default for the locale.
+     */
+    gpps(sesskey, "LineCodePage", "", conf, CONF_line_codepage);
+    gppi(sesskey, "CJKAmbigWide", 0, conf, CONF_cjk_ambig_wide);
+    gppi(sesskey, "UTF8Override", 1, conf, CONF_utf8_override);
+    gpps(sesskey, "Printer", "", conf, CONF_printer);
+    gppi(sesskey, "CapsLockCyr", 0, conf, CONF_xlat_capslockcyr);
+    gppi(sesskey, "ScrollBar", 1, conf, CONF_scrollbar);
+    gppi(sesskey, "ScrollBarFullScreen", 0, conf, CONF_scrollbar_in_fullscreen);
+    gppi(sesskey, "ScrollOnKey", 0, conf, CONF_scroll_on_key);
+    gppi(sesskey, "ScrollOnDisp", 1, conf, CONF_scroll_on_disp);
+    gppi(sesskey, "EraseToScrollback", 1, conf, CONF_erase_to_scrollback);
+    gppi(sesskey, "LockSize", 0, conf, CONF_resize_action);
+    gppi(sesskey, "BCE", 1, conf, CONF_bce);
+    gppi(sesskey, "BlinkText", 0, conf, CONF_blinktext);
+    gppi(sesskey, "X11Forward", 0, conf, CONF_x11_forward);
+    gpps(sesskey, "X11Display", "", conf, CONF_x11_display);
+    gppi(sesskey, "X11AuthType", X11_MIT, conf, CONF_x11_auth);
+    gppfile(sesskey, "X11AuthFile", conf, CONF_xauthfile);
+
+    gppi(sesskey, "LocalPortAcceptAll", 0, conf, CONF_lport_acceptall);
+    gppi(sesskey, "RemotePortAcceptAll", 0, conf, CONF_rport_acceptall);
+    gppmap(sesskey, "PortForwardings", conf, CONF_portfwd);
+    i = gppi_raw(sesskey, "BugIgnore1", 0); conf_set_int(conf, CONF_sshbug_ignore1, 2-i);
+    i = gppi_raw(sesskey, "BugPlainPW1", 0); conf_set_int(conf, CONF_sshbug_plainpw1, 2-i);
+    i = gppi_raw(sesskey, "BugRSA1", 0); conf_set_int(conf, CONF_sshbug_rsa1, 2-i);
+    i = gppi_raw(sesskey, "BugIgnore2", 0); conf_set_int(conf, CONF_sshbug_ignore2, 2-i);
+    {
+	int i;
+	i = gppi_raw(sesskey, "BugHMAC2", 0); conf_set_int(conf, CONF_sshbug_hmac2, 2-i);
+	if (2-i == AUTO) {
+	    i = gppi_raw(sesskey, "BuggyMAC", 0);
+	    if (i == 1)
+		conf_set_int(conf, CONF_sshbug_hmac2, FORCE_ON);
+	}
+    }
+    i = gppi_raw(sesskey, "BugDeriveKey2", 0); conf_set_int(conf, CONF_sshbug_derivekey2, 2-i);
+    i = gppi_raw(sesskey, "BugRSAPad2", 0); conf_set_int(conf, CONF_sshbug_rsapad2, 2-i);
+    i = gppi_raw(sesskey, "BugPKSessID2", 0); conf_set_int(conf, CONF_sshbug_pksessid2, 2-i);
+    i = gppi_raw(sesskey, "BugRekey2", 0); conf_set_int(conf, CONF_sshbug_rekey2, 2-i);
+    i = gppi_raw(sesskey, "BugMaxPkt2", 0); conf_set_int(conf, CONF_sshbug_maxpkt2, 2-i);
+    i = gppi_raw(sesskey, "BugOldGex2", 0); conf_set_int(conf, CONF_sshbug_oldgex2, 2-i);
+    i = gppi_raw(sesskey, "BugWinadj", 0); conf_set_int(conf, CONF_sshbug_winadj, 2-i);
+    i = gppi_raw(sesskey, "BugChanReq", 0); conf_set_int(conf, CONF_sshbug_chanreq, 2-i);
+    conf_set_int(conf, CONF_ssh_simple, FALSE);
+    gppi(sesskey, "StampUtmp", 1, conf, CONF_stamp_utmp);
+    gppi(sesskey, "LoginShell", 1, conf, CONF_login_shell);
+    gppi(sesskey, "ScrollbarOnLeft", 0, conf, CONF_scrollbar_on_left);
+    gppi(sesskey, "ShadowBold", 0, conf, CONF_shadowbold);
+    gppfont(sesskey, "BoldFont", conf, CONF_boldfont);
+    gppfont(sesskey, "WideFont", conf, CONF_widefont);
+    gppfont(sesskey, "WideBoldFont", conf, CONF_wideboldfont);
+    gppi(sesskey, "ShadowBoldOffset", 1, conf, CONF_shadowboldoffset);
+    gpps(sesskey, "SerialLine", "", conf, CONF_serline);
+    gppi(sesskey, "SerialSpeed", 9600, conf, CONF_serspeed);
+    gppi(sesskey, "SerialDataBits", 8, conf, CONF_serdatabits);
+    gppi(sesskey, "SerialStopHalfbits", 2, conf, CONF_serstopbits);
+    gppi(sesskey, "SerialParity", SER_PAR_NONE, conf, CONF_serparity);
+    gppi(sesskey, "SerialFlowControl", SER_FLOW_XONXOFF, conf, CONF_serflow);
+    gpps(sesskey, "WindowClass", "", conf, CONF_winclass);
+    gppi(sesskey, "ConnectionSharing", 0, conf, CONF_ssh_connection_sharing);
+    gppi(sesskey, "ConnectionSharingUpstream", 1, conf, CONF_ssh_connection_sharing_upstream);
+    gppi(sesskey, "ConnectionSharingDownstream", 1, conf, CONF_ssh_connection_sharing_downstream);
+    gppmap(sesskey, "SSHManualHostKeys", conf, CONF_ssh_manual_hostkeys);
+}
+
+void do_defaults(char *session, Conf *conf)
+{
+    load_settings(session, conf);
+}
+
+static int sessioncmp(const void *av, const void *bv)
+{
+    const char *a = *(const char *const *) av;
+    const char *b = *(const char *const *) bv;
+
+    /*
+     * Alphabetical order, except that "Default Settings" is a
+     * special case and comes first.
+     */
+    if (!strcmp(a, "Default Settings"))
+	return -1;		       /* a comes first */
+    if (!strcmp(b, "Default Settings"))
+	return +1;		       /* b comes first */
+    /*
+     * FIXME: perhaps we should ignore the first & in determining
+     * sort order.
+     */
+    return strcmp(a, b);	       /* otherwise, compare normally */
+}
+
+void get_sesslist(struct sesslist *list, int allocate)
+{
+    char otherbuf[2048];
+    int buflen, bufsize, i;
+    char *p, *ret;
+    void *handle;
+
+    if (allocate) {
+
+	buflen = bufsize = 0;
+	list->buffer = NULL;
+	if ((handle = enum_settings_start()) != NULL) {
+	    do {
+		ret = enum_settings_next(handle, otherbuf, sizeof(otherbuf));
+		if (ret) {
+		    int len = strlen(otherbuf) + 1;
+		    if (bufsize < buflen + len) {
+			bufsize = buflen + len + 2048;
+			list->buffer = sresize(list->buffer, bufsize, char);
+		    }
+		    strcpy(list->buffer + buflen, otherbuf);
+		    buflen += strlen(list->buffer + buflen) + 1;
+		}
+	    } while (ret);
+	    enum_settings_finish(handle);
+	}
+	list->buffer = sresize(list->buffer, buflen + 1, char);
+	list->buffer[buflen] = '\0';
+
+	/*
+	 * Now set up the list of sessions. Note that "Default
+	 * Settings" must always be claimed to exist, even if it
+	 * doesn't really.
+	 */
+
+	p = list->buffer;
+	list->nsessions = 1;	       /* "Default Settings" counts as one */
+	while (*p) {
+	    if (strcmp(p, "Default Settings"))
+		list->nsessions++;
+	    while (*p)
+		p++;
+	    p++;
+	}
+
+	list->sessions = snewn(list->nsessions + 1, char *);
+	list->sessions[0] = "Default Settings";
+	p = list->buffer;
+	i = 1;
+	while (*p) {
+	    if (strcmp(p, "Default Settings"))
+		list->sessions[i++] = p;
+	    while (*p)
+		p++;
+	    p++;
+	}
+
+	qsort(list->sessions, i, sizeof(char *), sessioncmp);
+    } else {
+	sfree(list->buffer);
+	sfree(list->sessions);
+	list->buffer = NULL;
+	list->sessions = NULL;
+    }
+}
diff -uNr putty-0.65/terminal.c putty-url-0.65/terminal.c
--- putty-0.65/terminal.c	2015-07-25 12:22:48.000000000 +0200
+++ putty-url-0.65/terminal.c	2015-09-12 15:35:59.000000000 +0200
@@ -11,6 +11,11 @@
 #include "putty.h"
 #include "terminal.h"
 
+/*
+ * HACK: PuttyTray / Nutty
+ */ 
+#include "urlhack.h"
+
 #define poslt(p1,p2) ( (p1).y < (p2).y || ( (p1).y == (p2).y && (p1).x < (p2).x ) )
 #define posle(p1,p2) ( (p1).y < (p2).y || ( (p1).y == (p2).y && (p1).x <= (p2).x ) )
 #define poseq(p1,p2) ( (p1).y == (p2).y && (p1).x == (p2).x )
@@ -1640,6 +1645,11 @@
     term->bidi_cache_size = 0;
     term->pre_bidi_cache = term->post_bidi_cache = NULL;
 
+	/*
+	 * HACK: PuttyTray / Nutty
+	 */
+	term->url_update = TRUE;
+
     /* FULL-TERMCHAR */
     term->basic_erase_char.chr = CSET_ASCII | ' ';
     term->basic_erase_char.attr = ATTR_DEFAULT;
@@ -4893,6 +4903,49 @@
 #endif /* OPTIMISE_SCROLL */
     termchar *newline;
 
+    /*
+     * HACK: PuttyTray / Nutty
+     * Hyperlink stuff: Find visible hyperlinks
+     *
+     * TODO: We should find out somehow that the stuff on screen has changed since last
+     *       paint. How to do it?
+     */
+    int urlhack_underline_always = conf_get_int(term->conf, CONF_url_underline) == URLHACK_UNDERLINE_ALWAYS;
+
+    int urlhack_underline =
+        conf_get_int(term->conf, CONF_url_underline) == URLHACK_UNDERLINE_ALWAYS ||
+        (conf_get_int(term->conf, CONF_url_underline) == URLHACK_UNDERLINE_HOVER && (!conf_get_int(term->conf, CONF_url_ctrl_click) || urlhack_is_ctrl_pressed())) ? 1 : 0;
+
+    int urlhack_is_link = 0, urlhack_hover_current = 0;
+    int urlhack_toggle_x = term->cols, urlhack_toggle_y = term->rows;
+    int urlhack_region_index = 0;
+    text_region urlhack_region;
+
+    if (term->url_update) {
+        urlhack_reset();
+
+        for (i = 0; i < term->rows; i++) {
+            termline *lp = lineptr(term->disptop + i);
+
+            for (j = 0; j < term->cols; j++) {
+                urlhack_putchar((char)(lp->chars[j].chr & CHAR_MASK));
+            }
+
+            unlineptr(lp);
+        }
+
+        urlhack_go_find_me_some_hyperlinks(term->cols);
+    }
+    urlhack_region = urlhack_get_link_region(urlhack_region_index);
+    urlhack_toggle_x = urlhack_region.x0;
+    urlhack_toggle_y = urlhack_region.y0;
+
+    if (urlhack_underline_always)
+        urlhack_hover_current = 1;
+    else
+        urlhack_hover_current = urlhack_is_in_this_link_region(urlhack_region, urlhack_mouse_old_x, urlhack_mouse_old_y);
+    /* HACK: PuttyTray / Nutty : END */
+
     chlen = 1024;
     ch = snewn(chlen, wchar_t);
 
@@ -5046,6 +5099,47 @@
 	    if (j < term->cols-1 && d[1].chr == UCSWIDE)
 		tattr |= ATTR_WIDE;
 
+		/*
+		 * HACK: PuttyTray / Nutty
+		 * Hyperlink stuff: Underline link regions if user has configured us so
+		 */
+		if (urlhack_underline) {
+			if (j == urlhack_toggle_x && i == urlhack_toggle_y) {
+				urlhack_is_link = urlhack_is_link == 1 ? 0 : 1;
+
+				// Find next bound for the toggle
+				
+				if (urlhack_is_link == 1) {
+					urlhack_toggle_x = urlhack_region.x1;
+					urlhack_toggle_y = urlhack_region.y1;
+
+					if (urlhack_toggle_x == term->cols - 1) {
+						// Handle special case where link ends at the last char of the row
+						urlhack_toggle_y++;
+						urlhack_toggle_x = 0;
+					}
+				}
+				else {
+					urlhack_region = urlhack_get_link_region(++urlhack_region_index);
+
+					if (urlhack_underline_always)
+						urlhack_hover_current = 1;
+					else
+						urlhack_hover_current = urlhack_is_in_this_link_region(urlhack_region, urlhack_mouse_old_x, urlhack_mouse_old_y);
+
+					urlhack_toggle_x = urlhack_region.x0;
+					urlhack_toggle_y = urlhack_region.y0;
+				}
+			}
+
+			if (urlhack_is_link == 1 && urlhack_hover_current == 1) {	
+				tattr |= ATTR_UNDER;
+			}
+
+			term->url_update = 0;
+		}
+		/* HACK: PuttyTray / Nutty : END */
+
 	    /* Video reversing things */
 	    if (term->selstate == DRAGGING || term->selstate == SELECTED) {
 		if (term->seltype == LEXICOGRAPHIC)
@@ -5353,6 +5447,11 @@
     int shift;
 #endif /* OPTIMISE_SCROLL */
 
+	/*
+	 * HACK: PuttyTray / Nutty
+	 */
+	term->url_update = TRUE;
+
     term->disptop = (rel < 0 ? 0 : rel > 0 ? sbtop : term->disptop) + where;
     if (term->disptop < sbtop)
 	term->disptop = sbtop;
@@ -5958,7 +6057,7 @@
     }
 
     selpoint.x = x;
-    unlineptr(ldata);
+    //unlineptr(ldata); // HACK: REMOVED FOR HYPERLINK STUFF
 
     /*
      * If we're in the middle of a selection operation, we ignore raw
@@ -6009,8 +6108,10 @@
                     return;
             } else switch (a) {
 	      case MA_DRAG:
-		if (term->xterm_mouse == 1)
-		    return;
+			if (term->xterm_mouse == 1) {// HACK: ADDED FOR hyperlink stuff
+				unlineptr(ldata); 
+				return;
+			}
 		encstate += 0x20;
 		break;
 	      case MA_RELEASE:
@@ -6020,8 +6121,10 @@
 		term->mouse_is_down = 0;
 		break;
 	      case MA_CLICK:
-		if (term->mouse_is_down == braw)
-		    return;
+			  if (term->mouse_is_down == braw) {// HACK: ADDED FOR hyperlink stuff
+				  unlineptr(ldata); 
+				  return;
+			  }
 		term->mouse_is_down = braw;
 		break;
               default:
@@ -6044,6 +6147,7 @@
 	    }
 	    ldisc_send(term->ldisc, abuf, len, 0);
 	}
+	unlineptr(ldata); // HACK: ADDED FOR hyperlink stuff
 	return;
     }
 
@@ -6066,6 +6170,60 @@
 	term->seltype = default_seltype;
 	term->selanchor = selpoint;
 	term->selmode = SM_CHAR;
+
+	/*
+	 * HACK: PuttyTray / Nutty
+	 * Hyperlink stuff: Check whether the click coordinates are inside link
+	 * region, if so -> copy url to temporary buffer and launch it. Delete
+	 * the temporary buffer.
+	 */
+	} else if (bcooked == MBT_SELECT && a == MA_RELEASE && term->selstate == ABOUT_TO) {
+	deselect(term);
+	term->selstate = NO_SELECTION;
+
+	if ((!conf_get_int(term->conf, CONF_url_ctrl_click) || (conf_get_int(term->conf, CONF_url_ctrl_click) && urlhack_is_ctrl_pressed())) && urlhack_is_in_link_region(x, y)) {
+		int i;
+		char *linkbuf = NULL;
+		text_region region = urlhack_get_link_bounds(x, y);
+
+		if (region.y0 == region.y1) {
+			linkbuf = snewn(region.x1 - region.x0 + 2, char);
+			
+			for (i = region.x0; i < region.x1; i++) {
+				linkbuf[i - region.x0] = (char)(ldata->chars[i].chr);
+			}
+
+			linkbuf[i - region.x0] = '\0';
+		}
+		else {
+			termline *urldata = lineptr(region.y0 + term->disptop);
+			int linklen, pos = region.x0, row = region.y0 + term->disptop;
+
+			linklen = (term->cols - region.x0) +
+				((region.y1 - region.y0 - 1) * term->cols) + region.x1 + 1;
+
+			linkbuf = snewn(linklen, char);
+
+			for (i = region.x0; i < linklen + region.x0; i++) {
+				linkbuf[i - region.x0] = (char)(urldata->chars[i % term->cols].chr);
+				
+				// Jump to next line?
+				if (((i + 1) % term->cols) == 0) {
+					row++;
+					urldata = lineptr(row);
+				}
+			}
+
+			linkbuf[linklen - 1] = '\0';
+			unlineptr(urldata);
+		}
+		
+		urlhack_launch_url(!conf_get_int(term->conf, CONF_url_defbrowser) ? conf_get_filename(term->conf, CONF_url_browser)->path : NULL, linkbuf);
+		
+		sfree(linkbuf);
+	}
+	/* HACK: PuttyTray / Nutty : END */
+
     } else if (bcooked == MBT_SELECT && (a == MA_2CLK || a == MA_3CLK)) {
 	deselect(term);
 	term->selmode = (a == MA_2CLK ? SM_WORD : SM_LINE);
@@ -6076,8 +6234,10 @@
 	sel_spread(term);
     } else if ((bcooked == MBT_SELECT && a == MA_DRAG) ||
 	       (bcooked == MBT_EXTEND && a != MA_RELEASE)) {
-	if (term->selstate == ABOUT_TO && poseq(term->selanchor, selpoint))
-	    return;
+	if (term->selstate == ABOUT_TO && poseq(term->selanchor, selpoint)) { // HACK: ADDED FOR HYPERLINK STUFF
+		unlineptr(ldata);
+		return;
+	}
 	if (bcooked == MBT_EXTEND && a != MA_DRAG &&
 	    term->selstate == SELECTED) {
 	    if (term->seltype == LEXICOGRAPHIC) {
@@ -6260,6 +6420,11 @@
 	if (term->selstate != DRAGGING)
 	    term_out(term);
 	term->in_term_out = FALSE;
+
+	/*
+	 * HACK: PuttyTray / Nutty
+	 */
+	term->url_update = TRUE;
     }
 
     /*
diff -uNr putty-0.65/terminal.c.orig putty-url-0.65/terminal.c.orig
--- putty-0.65/terminal.c.orig	1970-01-01 01:00:00.000000000 +0100
+++ putty-url-0.65/terminal.c.orig	2015-07-25 12:22:48.000000000 +0200
@@ -0,0 +1,6455 @@
+/*
+ * Terminal emulator.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include <time.h>
+#include <assert.h>
+#include "putty.h"
+#include "terminal.h"
+
+#define poslt(p1,p2) ( (p1).y < (p2).y || ( (p1).y == (p2).y && (p1).x < (p2).x ) )
+#define posle(p1,p2) ( (p1).y < (p2).y || ( (p1).y == (p2).y && (p1).x <= (p2).x ) )
+#define poseq(p1,p2) ( (p1).y == (p2).y && (p1).x == (p2).x )
+#define posdiff(p1,p2) ( ((p1).y - (p2).y) * (term->cols+1) + (p1).x - (p2).x )
+
+/* Product-order comparisons for rectangular block selection. */
+#define posPlt(p1,p2) ( (p1).y <= (p2).y && (p1).x < (p2).x )
+#define posPle(p1,p2) ( (p1).y <= (p2).y && (p1).x <= (p2).x )
+
+#define incpos(p) ( (p).x == term->cols ? ((p).x = 0, (p).y++, 1) : ((p).x++, 0) )
+#define decpos(p) ( (p).x == 0 ? ((p).x = term->cols, (p).y--, 1) : ((p).x--, 0) )
+
+#define VT52_PLUS
+
+#define CL_ANSIMIN	0x0001	       /* Codes in all ANSI like terminals. */
+#define CL_VT100	0x0002	       /* VT100 */
+#define CL_VT100AVO	0x0004	       /* VT100 +AVO; 132x24 (not 132x14) & attrs */
+#define CL_VT102	0x0008	       /* VT102 */
+#define CL_VT220	0x0010	       /* VT220 */
+#define CL_VT320	0x0020	       /* VT320 */
+#define CL_VT420	0x0040	       /* VT420 */
+#define CL_VT510	0x0080	       /* VT510, NB VT510 includes ANSI */
+#define CL_VT340TEXT	0x0100	       /* VT340 extensions that appear in the VT420 */
+#define CL_SCOANSI	0x1000	       /* SCOANSI not in ANSIMIN. */
+#define CL_ANSI		0x2000	       /* ANSI ECMA-48 not in the VT100..VT420 */
+#define CL_OTHER	0x4000	       /* Others, Xterm, linux, putty, dunno, etc */
+
+#define TM_VT100	(CL_ANSIMIN|CL_VT100)
+#define TM_VT100AVO	(TM_VT100|CL_VT100AVO)
+#define TM_VT102	(TM_VT100AVO|CL_VT102)
+#define TM_VT220	(TM_VT102|CL_VT220)
+#define TM_VTXXX	(TM_VT220|CL_VT340TEXT|CL_VT510|CL_VT420|CL_VT320)
+#define TM_SCOANSI	(CL_ANSIMIN|CL_SCOANSI)
+
+#define TM_PUTTY	(0xFFFF)
+
+#define UPDATE_DELAY    ((TICKSPERSEC+49)/50)/* ticks to defer window update */
+#define TBLINK_DELAY    ((TICKSPERSEC*9+19)/20)/* ticks between text blinks*/
+#define CBLINK_DELAY    (CURSORBLINK) /* ticks between cursor blinks */
+#define VBELL_DELAY     (VBELL_TIMEOUT) /* visual bell timeout in ticks */
+
+#define compatibility(x) \
+    if ( ((CL_##x)&term->compatibility_level) == 0 ) { 	\
+       term->termstate=TOPLEVEL;			\
+       break;						\
+    }
+#define compatibility2(x,y) \
+    if ( ((CL_##x|CL_##y)&term->compatibility_level) == 0 ) { \
+       term->termstate=TOPLEVEL;			\
+       break;						\
+    }
+
+#define has_compat(x) ( ((CL_##x)&term->compatibility_level) != 0 )
+
+char *EMPTY_WINDOW_TITLE = "";
+
+const char sco2ansicolour[] = { 0, 4, 2, 6, 1, 5, 3, 7 };
+
+#define sel_nl_sz  (sizeof(sel_nl)/sizeof(wchar_t))
+const wchar_t sel_nl[] = SEL_NL;
+
+/*
+ * Fetch the character at a particular position in a line array,
+ * for purposes of `wordtype'. The reason this isn't just a simple
+ * array reference is that if the character we find is UCSWIDE,
+ * then we must look one space further to the left.
+ */
+#define UCSGET(a, x) \
+    ( (x)>0 && (a)[(x)].chr == UCSWIDE ? (a)[(x)-1].chr : (a)[(x)].chr )
+
+/*
+ * Detect the various aliases of U+0020 SPACE.
+ */
+#define IS_SPACE_CHR(chr) \
+	((chr) == 0x20 || (DIRECT_CHAR(chr) && ((chr) & 0xFF) == 0x20))
+
+/*
+ * Spot magic CSETs.
+ */
+#define CSET_OF(chr) (DIRECT_CHAR(chr)||DIRECT_FONT(chr) ? (chr)&CSET_MASK : 0)
+
+/*
+ * Internal prototypes.
+ */
+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 *);
+static void swap_screen(Terminal *, int, int, int);
+static void update_sbar(Terminal *);
+static void deselect(Terminal *);
+static void term_print_finish(Terminal *);
+static void scroll(Terminal *, int, int, int, int);
+#ifdef OPTIMISE_SCROLL
+static void scroll_display(Terminal *, int, int, int);
+#endif /* OPTIMISE_SCROLL */
+
+static termline *newline(Terminal *term, int cols, int bce)
+{
+    termline *line;
+    int j;
+
+    line = snew(termline);
+    line->chars = snewn(cols, termchar);
+    for (j = 0; j < cols; j++)
+	line->chars[j] = (bce ? term->erase_char : term->basic_erase_char);
+    line->cols = line->size = cols;
+    line->lattr = LATTR_NORM;
+    line->temporary = FALSE;
+    line->cc_free = 0;
+
+    return line;
+}
+
+static void freeline(termline *line)
+{
+    if (line) {
+	sfree(line->chars);
+	sfree(line);
+    }
+}
+
+static void unlineptr(termline *line)
+{
+    if (line->temporary)
+	freeline(line);
+}
+
+#ifdef TERM_CC_DIAGS
+/*
+ * Diagnostic function: verify that a termline has a correct
+ * combining character structure.
+ * 
+ * This is a performance-intensive check, so it's no longer enabled
+ * by default.
+ */
+static void cc_check(termline *line)
+{
+    unsigned char *flags;
+    int i, j;
+
+    assert(line->size >= line->cols);
+
+    flags = snewn(line->size, unsigned char);
+
+    for (i = 0; i < line->size; i++)
+	flags[i] = (i < line->cols);
+
+    for (i = 0; i < line->cols; i++) {
+	j = i;
+	while (line->chars[j].cc_next) {
+	    j += line->chars[j].cc_next;
+	    assert(j >= line->cols && j < line->size);
+	    assert(!flags[j]);
+	    flags[j] = TRUE;
+	}
+    }
+
+    j = line->cc_free;
+    if (j) {
+	while (1) {
+	    assert(j >= line->cols && j < line->size);
+	    assert(!flags[j]);
+	    flags[j] = TRUE;
+	    if (line->chars[j].cc_next)
+		j += line->chars[j].cc_next;
+	    else
+		break;
+	}
+    }
+
+    j = 0;
+    for (i = 0; i < line->size; i++)
+	j += (flags[i] != 0);
+
+    assert(j == line->size);
+
+    sfree(flags);
+}
+#endif
+
+/*
+ * Add a combining character to a character cell.
+ */
+static void add_cc(termline *line, int col, unsigned long chr)
+{
+    int newcc;
+
+    assert(col >= 0 && col < line->cols);
+
+    /*
+     * Start by extending the cols array if the free list is empty.
+     */
+    if (!line->cc_free) {
+	int n = line->size;
+	line->size += 16 + (line->size - line->cols) / 2;
+	line->chars = sresize(line->chars, line->size, termchar);
+	line->cc_free = n;
+	while (n < line->size) {
+	    if (n+1 < line->size)
+		line->chars[n].cc_next = 1;
+	    else
+		line->chars[n].cc_next = 0;
+	    n++;
+	}
+    }
+
+    /*
+     * Now walk the cc list of the cell in question.
+     */
+    while (line->chars[col].cc_next)
+	col += line->chars[col].cc_next;
+
+    /*
+     * `col' now points at the last cc currently in this cell; so
+     * we simply add another one.
+     */
+    newcc = line->cc_free;
+    if (line->chars[newcc].cc_next)
+	line->cc_free = newcc + line->chars[newcc].cc_next;
+    else
+	line->cc_free = 0;
+    line->chars[newcc].cc_next = 0;
+    line->chars[newcc].chr = chr;
+    line->chars[col].cc_next = newcc - col;
+
+#ifdef TERM_CC_DIAGS
+    cc_check(line);
+#endif
+}
+
+/*
+ * Clear the combining character list in a character cell.
+ */
+static void clear_cc(termline *line, int col)
+{
+    int oldfree, origcol = col;
+
+    assert(col >= 0 && col < line->cols);
+
+    if (!line->chars[col].cc_next)
+	return;			       /* nothing needs doing */
+
+    oldfree = line->cc_free;
+    line->cc_free = col + line->chars[col].cc_next;
+    while (line->chars[col].cc_next)
+	col += line->chars[col].cc_next;
+    if (oldfree)
+	line->chars[col].cc_next = oldfree - col;
+    else
+	line->chars[col].cc_next = 0;
+
+    line->chars[origcol].cc_next = 0;
+
+#ifdef TERM_CC_DIAGS
+    cc_check(line);
+#endif
+}
+
+/*
+ * Compare two character cells for equality. Special case required
+ * in do_paint() where we override what we expect the chr and attr
+ * fields to be.
+ */
+static int termchars_equal_override(termchar *a, termchar *b,
+				    unsigned long bchr, unsigned long battr)
+{
+    /* FULL-TERMCHAR */
+    if (a->chr != bchr)
+	return FALSE;
+    if ((a->attr &~ DATTR_MASK) != (battr &~ DATTR_MASK))
+	return FALSE;
+    while (a->cc_next || b->cc_next) {
+	if (!a->cc_next || !b->cc_next)
+	    return FALSE;	       /* one cc-list ends, other does not */
+	a += a->cc_next;
+	b += b->cc_next;
+	if (a->chr != b->chr)
+	    return FALSE;
+    }
+    return TRUE;
+}
+
+static int termchars_equal(termchar *a, termchar *b)
+{
+    return termchars_equal_override(a, b, b->chr, b->attr);
+}
+
+/*
+ * Copy a character cell. (Requires a pointer to the destination
+ * termline, so as to access its free list.)
+ */
+static void copy_termchar(termline *destline, int x, termchar *src)
+{
+    clear_cc(destline, x);
+
+    destline->chars[x] = *src;	       /* copy everything except cc-list */
+    destline->chars[x].cc_next = 0;    /* and make sure this is zero */
+
+    while (src->cc_next) {
+	src += src->cc_next;
+	add_cc(destline, x, src->chr);
+    }
+
+#ifdef TERM_CC_DIAGS
+    cc_check(destline);
+#endif
+}
+
+/*
+ * Move a character cell within its termline.
+ */
+static void move_termchar(termline *line, termchar *dest, termchar *src)
+{
+    /* First clear the cc list from the original char, just in case. */
+    clear_cc(line, dest - line->chars);
+
+    /* Move the character cell and adjust its cc_next. */
+    *dest = *src;		       /* copy everything except cc-list */
+    if (src->cc_next)
+	dest->cc_next = src->cc_next - (dest-src);
+
+    /* Ensure the original cell doesn't have a cc list. */
+    src->cc_next = 0;
+
+#ifdef TERM_CC_DIAGS
+    cc_check(line);
+#endif
+}
+
+/*
+ * Compress and decompress a termline into an RLE-based format for
+ * storing in scrollback. (Since scrollback almost never needs to
+ * be modified and exists in huge quantities, this is a sensible
+ * tradeoff, particularly since it allows us to continue adding
+ * features to the main termchar structure without proportionally
+ * bloating the terminal emulator's memory footprint unless those
+ * features are in constant use.)
+ */
+struct buf {
+    unsigned char *data;
+    int len, size;
+};
+static void add(struct buf *b, unsigned char c)
+{
+    if (b->len >= b->size) {
+	b->size = (b->len * 3 / 2) + 512;
+	b->data = sresize(b->data, b->size, unsigned char);
+    }
+    b->data[b->len++] = c;
+}
+static int get(struct buf *b)
+{
+    return b->data[b->len++];
+}
+static void makerle(struct buf *b, termline *ldata,
+		    void (*makeliteral)(struct buf *b, termchar *c,
+					unsigned long *state))
+{
+    int hdrpos, hdrsize, n, prevlen, prevpos, thislen, thispos, prev2;
+    termchar *c = ldata->chars;
+    unsigned long state = 0, oldstate;
+
+    n = ldata->cols;
+
+    hdrpos = b->len;
+    hdrsize = 0;
+    add(b, 0);
+    prevlen = prevpos = 0;
+    prev2 = FALSE;
+
+    while (n-- > 0) {
+	thispos = b->len;
+	makeliteral(b, c++, &state);
+	thislen = b->len - thispos;
+	if (thislen == prevlen &&
+	    !memcmp(b->data + prevpos, b->data + thispos, thislen)) {
+	    /*
+	     * This literal precisely matches the previous one.
+	     * Turn it into a run if it's worthwhile.
+	     * 
+	     * With one-byte literals, it costs us two bytes to
+	     * encode a run, plus another byte to write the header
+	     * to resume normal output; so a three-element run is
+	     * neutral, and anything beyond that is unconditionally
+	     * worthwhile. With two-byte literals or more, even a
+	     * 2-run is a win.
+	     */
+	    if (thislen > 1 || prev2) {
+		int runpos, runlen;
+
+		/*
+		 * It's worth encoding a run. Start at prevpos,
+		 * unless hdrsize==0 in which case we can back up
+		 * another one and start by overwriting hdrpos.
+		 */
+
+		hdrsize--;	       /* remove the literal at prevpos */
+		if (prev2) {
+		    assert(hdrsize > 0);
+		    hdrsize--;
+		    prevpos -= prevlen;/* and possibly another one */
+		}
+
+		if (hdrsize == 0) {
+		    assert(prevpos == hdrpos + 1);
+		    runpos = hdrpos;
+		    b->len = prevpos+prevlen;
+		} else {
+		    memmove(b->data + prevpos+1, b->data + prevpos, prevlen);
+		    runpos = prevpos;
+		    b->len = prevpos+prevlen+1;
+		    /*
+		     * Terminate the previous run of ordinary
+		     * literals.
+		     */
+		    assert(hdrsize >= 1 && hdrsize <= 128);
+		    b->data[hdrpos] = hdrsize - 1;
+		}
+
+		runlen = prev2 ? 3 : 2;
+
+		while (n > 0 && runlen < 129) {
+		    int tmppos, tmplen;
+		    tmppos = b->len;
+		    oldstate = state;
+		    makeliteral(b, c, &state);
+		    tmplen = b->len - tmppos;
+		    b->len = tmppos;
+		    if (tmplen != thislen ||
+			memcmp(b->data + runpos+1, b->data + tmppos, tmplen)) {
+			state = oldstate;
+			break;	       /* run over */
+		    }
+		    n--, c++, runlen++;
+		}
+
+		assert(runlen >= 2 && runlen <= 129);
+		b->data[runpos] = runlen + 0x80 - 2;
+
+		hdrpos = b->len;
+		hdrsize = 0;
+		add(b, 0);
+		/* And ensure this run doesn't interfere with the next. */
+		prevlen = prevpos = 0;
+		prev2 = FALSE;
+
+		continue;
+	    } else {
+		/*
+		 * Just flag that the previous two literals were
+		 * identical, in case we find a third identical one
+		 * we want to turn into a run.
+		 */
+		prev2 = TRUE;
+		prevlen = thislen;
+		prevpos = thispos;
+	    }
+	} else {
+	    prev2 = FALSE;
+	    prevlen = thislen;
+	    prevpos = thispos;
+	}
+
+	/*
+	 * This character isn't (yet) part of a run. Add it to
+	 * hdrsize.
+	 */
+	hdrsize++;
+	if (hdrsize == 128) {
+	    b->data[hdrpos] = hdrsize - 1;
+	    hdrpos = b->len;
+	    hdrsize = 0;
+	    add(b, 0);
+	    prevlen = prevpos = 0;
+	    prev2 = FALSE;
+	}
+    }
+
+    /*
+     * Clean up.
+     */
+    if (hdrsize > 0) {
+	assert(hdrsize <= 128);
+	b->data[hdrpos] = hdrsize - 1;
+    } else {
+	b->len = hdrpos;
+    }
+}
+static void makeliteral_chr(struct buf *b, termchar *c, unsigned long *state)
+{
+    /*
+     * My encoding for characters is UTF-8-like, in that it stores
+     * 7-bit ASCII in one byte and uses high-bit-set bytes as
+     * introducers to indicate a longer sequence. However, it's
+     * unlike UTF-8 in that it doesn't need to be able to
+     * resynchronise, and therefore I don't want to waste two bits
+     * per byte on having recognisable continuation characters.
+     * Also I don't want to rule out the possibility that I may one
+     * day use values 0x80000000-0xFFFFFFFF for interesting
+     * purposes, so unlike UTF-8 I need a full 32-bit range.
+     * Accordingly, here is my encoding:
+     * 
+     * 00000000-0000007F: 0xxxxxxx (but see below)
+     * 00000080-00003FFF: 10xxxxxx xxxxxxxx
+     * 00004000-001FFFFF: 110xxxxx xxxxxxxx xxxxxxxx
+     * 00200000-0FFFFFFF: 1110xxxx xxxxxxxx xxxxxxxx xxxxxxxx
+     * 10000000-FFFFFFFF: 11110ZZZ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
+     * 
+     * (`Z' is like `x' but is always going to be zero since the
+     * values I'm encoding don't go above 2^32. In principle the
+     * five-byte form of the encoding could extend to 2^35, and
+     * there could be six-, seven-, eight- and nine-byte forms as
+     * well to allow up to 64-bit values to be encoded. But that's
+     * completely unnecessary for these purposes!)
+     * 
+     * The encoding as written above would be very simple, except
+     * that 7-bit ASCII can occur in several different ways in the
+     * terminal data; sometimes it crops up in the D800 page
+     * (CSET_ASCII) but at other times it's in the 0000 page (real
+     * Unicode). Therefore, this encoding is actually _stateful_:
+     * the one-byte encoding of 00-7F actually indicates `reuse the
+     * upper three bytes of the last character', and to encode an
+     * absolute value of 00-7F you need to use the two-byte form
+     * instead.
+     */
+    if ((c->chr & ~0x7F) == *state) {
+	add(b, (unsigned char)(c->chr & 0x7F));
+    } else if (c->chr < 0x4000) {
+	add(b, (unsigned char)(((c->chr >> 8) & 0x3F) | 0x80));
+	add(b, (unsigned char)(c->chr & 0xFF));
+    } else if (c->chr < 0x200000) {
+	add(b, (unsigned char)(((c->chr >> 16) & 0x1F) | 0xC0));
+	add(b, (unsigned char)((c->chr >> 8) & 0xFF));
+	add(b, (unsigned char)(c->chr & 0xFF));
+    } else if (c->chr < 0x10000000) {
+	add(b, (unsigned char)(((c->chr >> 24) & 0x0F) | 0xE0));
+	add(b, (unsigned char)((c->chr >> 16) & 0xFF));
+	add(b, (unsigned char)((c->chr >> 8) & 0xFF));
+	add(b, (unsigned char)(c->chr & 0xFF));
+    } else {
+	add(b, 0xF0);
+	add(b, (unsigned char)((c->chr >> 24) & 0xFF));
+	add(b, (unsigned char)((c->chr >> 16) & 0xFF));
+	add(b, (unsigned char)((c->chr >> 8) & 0xFF));
+	add(b, (unsigned char)(c->chr & 0xFF));
+    }
+    *state = c->chr & ~0xFF;
+}
+static void makeliteral_attr(struct buf *b, termchar *c, unsigned long *state)
+{
+    /*
+     * My encoding for attributes is 16-bit-granular and assumes
+     * that the top bit of the word is never required. I either
+     * store a two-byte value with the top bit clear (indicating
+     * just that value), or a four-byte value with the top bit set
+     * (indicating the same value with its top bit clear).
+     * 
+     * However, first I permute the bits of the attribute value, so
+     * that the eight bits of colour (four in each of fg and bg)
+     * which are never non-zero unless xterm 256-colour mode is in
+     * use are placed higher up the word than everything else. This
+     * ensures that attribute values remain 16-bit _unless_ the
+     * user uses extended colour.
+     */
+    unsigned attr, colourbits;
+
+    attr = c->attr;
+
+    assert(ATTR_BGSHIFT > ATTR_FGSHIFT);
+
+    colourbits = (attr >> (ATTR_BGSHIFT + 4)) & 0xF;
+    colourbits <<= 4;
+    colourbits |= (attr >> (ATTR_FGSHIFT + 4)) & 0xF;
+
+    attr = (((attr >> (ATTR_BGSHIFT + 8)) << (ATTR_BGSHIFT + 4)) |
+	    (attr & ((1 << (ATTR_BGSHIFT + 4))-1)));
+    attr = (((attr >> (ATTR_FGSHIFT + 8)) << (ATTR_FGSHIFT + 4)) |
+	    (attr & ((1 << (ATTR_FGSHIFT + 4))-1)));
+
+    attr |= (colourbits << (32-9));
+
+    if (attr < 0x8000) {
+	add(b, (unsigned char)((attr >> 8) & 0xFF));
+	add(b, (unsigned char)(attr & 0xFF));
+    } else {
+	add(b, (unsigned char)(((attr >> 24) & 0x7F) | 0x80));
+	add(b, (unsigned char)((attr >> 16) & 0xFF));
+	add(b, (unsigned char)((attr >> 8) & 0xFF));
+	add(b, (unsigned char)(attr & 0xFF));
+    }
+}
+static void makeliteral_cc(struct buf *b, termchar *c, unsigned long *state)
+{
+    /*
+     * For combining characters, I just encode a bunch of ordinary
+     * chars using makeliteral_chr, and terminate with a \0
+     * character (which I know won't come up as a combining char
+     * itself).
+     * 
+     * I don't use the stateful encoding in makeliteral_chr.
+     */
+    unsigned long zstate;
+    termchar z;
+
+    while (c->cc_next) {
+	c += c->cc_next;
+
+	assert(c->chr != 0);
+
+	zstate = 0;
+	makeliteral_chr(b, c, &zstate);
+    }
+
+    z.chr = 0;
+    zstate = 0;
+    makeliteral_chr(b, &z, &zstate);
+}
+
+static termline *decompressline(unsigned char *data, int *bytes_used);
+
+static unsigned char *compressline(termline *ldata)
+{
+    struct buf buffer = { NULL, 0, 0 }, *b = &buffer;
+
+    /*
+     * First, store the column count, 7 bits at a time, least
+     * significant `digit' first, with the high bit set on all but
+     * the last.
+     */
+    {
+	int n = ldata->cols;
+	while (n >= 128) {
+	    add(b, (unsigned char)((n & 0x7F) | 0x80));
+	    n >>= 7;
+	}
+	add(b, (unsigned char)(n));
+    }
+
+    /*
+     * Next store the lattrs; same principle.
+     */
+    {
+	int n = ldata->lattr;
+	while (n >= 128) {
+	    add(b, (unsigned char)((n & 0x7F) | 0x80));
+	    n >>= 7;
+	}
+	add(b, (unsigned char)(n));
+    }
+
+    /*
+     * Now we store a sequence of separate run-length encoded
+     * fragments, each containing exactly as many symbols as there
+     * are columns in the ldata.
+     * 
+     * All of these have a common basic format:
+     * 
+     *  - a byte 00-7F indicates that X+1 literals follow it
+     * 	- a byte 80-FF indicates that a single literal follows it
+     * 	  and expects to be repeated (X-0x80)+2 times.
+     * 
+     * The format of the `literals' varies between the fragments.
+     */
+    makerle(b, ldata, makeliteral_chr);
+    makerle(b, ldata, makeliteral_attr);
+    makerle(b, ldata, makeliteral_cc);
+
+    /*
+     * Diagnostics: ensure that the compressed data really does
+     * decompress to the right thing.
+     * 
+     * This is a bit performance-heavy for production code.
+     */
+#ifdef TERM_CC_DIAGS
+#ifndef CHECK_SB_COMPRESSION
+    {
+	int dused;
+	termline *dcl;
+	int i;
+
+#ifdef DIAGNOSTIC_SB_COMPRESSION
+	for (i = 0; i < b->len; i++) {
+	    printf(" %02x ", b->data[i]);
+	}
+	printf("\n");
+#endif
+
+	dcl = decompressline(b->data, &dused);
+	assert(b->len == dused);
+	assert(ldata->cols == dcl->cols);
+	assert(ldata->lattr == dcl->lattr);
+	for (i = 0; i < ldata->cols; i++)
+	    assert(termchars_equal(&ldata->chars[i], &dcl->chars[i]));
+
+#ifdef DIAGNOSTIC_SB_COMPRESSION
+	printf("%d cols (%d bytes) -> %d bytes (factor of %g)\n",
+	       ldata->cols, 4 * ldata->cols, dused,
+	       (double)dused / (4 * ldata->cols));
+#endif
+
+	freeline(dcl);
+    }
+#endif
+#endif /* TERM_CC_DIAGS */
+
+    /*
+     * Trim the allocated memory so we don't waste any, and return.
+     */
+    return sresize(b->data, b->len, unsigned char);
+}
+
+static void readrle(struct buf *b, termline *ldata,
+		    void (*readliteral)(struct buf *b, termchar *c,
+					termline *ldata, unsigned long *state))
+{
+    int n = 0;
+    unsigned long state = 0;
+
+    while (n < ldata->cols) {
+	int hdr = get(b);
+
+	if (hdr >= 0x80) {
+	    /* A run. */
+
+	    int pos = b->len, count = hdr + 2 - 0x80;
+	    while (count--) {
+		assert(n < ldata->cols);
+		b->len = pos;
+		readliteral(b, ldata->chars + n, ldata, &state);
+		n++;
+	    }
+	} else {
+	    /* Just a sequence of consecutive literals. */
+
+	    int count = hdr + 1;
+	    while (count--) {
+		assert(n < ldata->cols);
+		readliteral(b, ldata->chars + n, ldata, &state);
+		n++;
+	    }
+	}
+    }
+
+    assert(n == ldata->cols);
+}
+static void readliteral_chr(struct buf *b, termchar *c, termline *ldata,
+			    unsigned long *state)
+{
+    int byte;
+
+    /*
+     * 00000000-0000007F: 0xxxxxxx
+     * 00000080-00003FFF: 10xxxxxx xxxxxxxx
+     * 00004000-001FFFFF: 110xxxxx xxxxxxxx xxxxxxxx
+     * 00200000-0FFFFFFF: 1110xxxx xxxxxxxx xxxxxxxx xxxxxxxx
+     * 10000000-FFFFFFFF: 11110ZZZ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
+     */
+
+    byte = get(b);
+    if (byte < 0x80) {
+	c->chr = byte | *state;
+    } else if (byte < 0xC0) {
+	c->chr = (byte &~ 0xC0) << 8;
+	c->chr |= get(b);
+    } else if (byte < 0xE0) {
+	c->chr = (byte &~ 0xE0) << 16;
+	c->chr |= get(b) << 8;
+	c->chr |= get(b);
+    } else if (byte < 0xF0) {
+	c->chr = (byte &~ 0xF0) << 24;
+	c->chr |= get(b) << 16;
+	c->chr |= get(b) << 8;
+	c->chr |= get(b);
+    } else {
+	assert(byte == 0xF0);
+	c->chr = get(b) << 24;
+	c->chr |= get(b) << 16;
+	c->chr |= get(b) << 8;
+	c->chr |= get(b);
+    }
+    *state = c->chr & ~0xFF;
+}
+static void readliteral_attr(struct buf *b, termchar *c, termline *ldata,
+			     unsigned long *state)
+{
+    unsigned val, attr, colourbits;
+
+    val = get(b) << 8;
+    val |= get(b);
+
+    if (val >= 0x8000) {
+	val &= ~0x8000;
+	val <<= 16;
+	val |= get(b) << 8;
+	val |= get(b);
+    }
+
+    colourbits = (val >> (32-9)) & 0xFF;
+    attr = (val & ((1<<(32-9))-1));
+
+    attr = (((attr >> (ATTR_FGSHIFT + 4)) << (ATTR_FGSHIFT + 8)) |
+	    (attr & ((1 << (ATTR_FGSHIFT + 4))-1)));
+    attr = (((attr >> (ATTR_BGSHIFT + 4)) << (ATTR_BGSHIFT + 8)) |
+	    (attr & ((1 << (ATTR_BGSHIFT + 4))-1)));
+
+    attr |= (colourbits >> 4) << (ATTR_BGSHIFT + 4);
+    attr |= (colourbits & 0xF) << (ATTR_FGSHIFT + 4);
+
+    c->attr = attr;
+}
+static void readliteral_cc(struct buf *b, termchar *c, termline *ldata,
+			   unsigned long *state)
+{
+    termchar n;
+    unsigned long zstate;
+    int x = c - ldata->chars;
+
+    c->cc_next = 0;
+
+    while (1) {
+	zstate = 0;
+	readliteral_chr(b, &n, ldata, &zstate);
+	if (!n.chr)
+	    break;
+	add_cc(ldata, x, n.chr);
+    }
+}
+
+static termline *decompressline(unsigned char *data, int *bytes_used)
+{
+    int ncols, byte, shift;
+    struct buf buffer, *b = &buffer;
+    termline *ldata;
+
+    b->data = data;
+    b->len = 0;
+
+    /*
+     * First read in the column count.
+     */
+    ncols = shift = 0;
+    do {
+	byte = get(b);
+	ncols |= (byte & 0x7F) << shift;
+	shift += 7;
+    } while (byte & 0x80);
+
+    /*
+     * Now create the output termline.
+     */
+    ldata = snew(termline);
+    ldata->chars = snewn(ncols, termchar);
+    ldata->cols = ldata->size = ncols;
+    ldata->temporary = TRUE;
+    ldata->cc_free = 0;
+
+    /*
+     * We must set all the cc pointers in ldata->chars to 0 right
+     * now, so that cc diagnostics that verify the integrity of the
+     * whole line will make sense while we're in the middle of
+     * building it up.
+     */
+    {
+	int i;
+	for (i = 0; i < ldata->cols; i++)
+	    ldata->chars[i].cc_next = 0;
+    }
+
+    /*
+     * Now read in the lattr.
+     */
+    ldata->lattr = shift = 0;
+    do {
+	byte = get(b);
+	ldata->lattr |= (byte & 0x7F) << shift;
+	shift += 7;
+    } while (byte & 0x80);
+
+    /*
+     * Now we read in each of the RLE streams in turn.
+     */
+    readrle(b, ldata, readliteral_chr);
+    readrle(b, ldata, readliteral_attr);
+    readrle(b, ldata, readliteral_cc);
+
+    /* Return the number of bytes read, for diagnostic purposes. */
+    if (bytes_used)
+	*bytes_used = b->len;
+
+    return ldata;
+}
+
+/*
+ * Resize a line to make it `cols' columns wide.
+ */
+static void resizeline(Terminal *term, termline *line, int cols)
+{
+    int i, oldcols;
+
+    if (line->cols != cols) {
+
+	oldcols = line->cols;
+
+	/*
+	 * This line is the wrong length, which probably means it
+	 * hasn't been accessed since a resize. Resize it now.
+	 * 
+	 * First, go through all the characters that will be thrown
+	 * out in the resize (if we're shrinking the line) and
+	 * return their cc lists to the cc free list.
+	 */
+	for (i = cols; i < oldcols; i++)
+	    clear_cc(line, i);
+
+	/*
+	 * If we're shrinking the line, we now bodily move the
+	 * entire cc section from where it started to where it now
+	 * needs to be. (We have to do this before the resize, so
+	 * that the data we're copying is still there. However, if
+	 * we're expanding, we have to wait until _after_ the
+	 * resize so that the space we're copying into is there.)
+	 */
+	if (cols < oldcols)
+	    memmove(line->chars + cols, line->chars + oldcols,
+		    (line->size - line->cols) * TSIZE);
+
+	/*
+	 * Now do the actual resize, leaving the _same_ amount of
+	 * cc space as there was to begin with.
+	 */
+	line->size += cols - oldcols;
+	line->chars = sresize(line->chars, line->size, TTYPE);
+	line->cols = cols;
+
+	/*
+	 * If we're expanding the line, _now_ we move the cc
+	 * section.
+	 */
+	if (cols > oldcols)
+	    memmove(line->chars + cols, line->chars + oldcols,
+		    (line->size - line->cols) * TSIZE);
+
+	/*
+	 * Go through what's left of the original line, and adjust
+	 * the first cc_next pointer in each list. (All the
+	 * subsequent ones are still valid because they are
+	 * relative offsets within the cc block.) Also do the same
+	 * to the head of the cc_free list.
+	 */
+	for (i = 0; i < oldcols && i < cols; i++)
+	    if (line->chars[i].cc_next)
+		line->chars[i].cc_next += cols - oldcols;
+	if (line->cc_free)
+	    line->cc_free += cols - oldcols;
+
+	/*
+	 * And finally fill in the new space with erase chars. (We
+	 * don't have to worry about cc lists here, because we
+	 * _know_ the erase char doesn't have one.)
+	 */
+	for (i = oldcols; i < cols; i++)
+	    line->chars[i] = term->basic_erase_char;
+
+#ifdef TERM_CC_DIAGS
+	cc_check(line);
+#endif
+    }
+}
+
+/*
+ * Get the number of lines in the scrollback.
+ */
+static int sblines(Terminal *term)
+{
+    int sblines = count234(term->scrollback);
+    if (term->erase_to_scrollback &&
+	term->alt_which && term->alt_screen) {
+	    sblines += term->alt_sblines;
+    }
+    return sblines;
+}
+
+/*
+ * Retrieve a line of the screen or of the scrollback, according to
+ * whether the y coordinate is non-negative or negative
+ * (respectively).
+ */
+static termline *lineptr(Terminal *term, int y, int lineno, int screen)
+{
+    termline *line;
+    tree234 *whichtree;
+    int treeindex;
+
+    if (y >= 0) {
+	whichtree = term->screen;
+	treeindex = y;
+    } else {
+	int altlines = 0;
+
+	assert(!screen);
+
+	if (term->erase_to_scrollback &&
+	    term->alt_which && term->alt_screen) {
+	    altlines = term->alt_sblines;
+	}
+	if (y < -altlines) {
+	    whichtree = term->scrollback;
+	    treeindex = y + altlines + count234(term->scrollback);
+	} else {
+	    whichtree = term->alt_screen;
+	    treeindex = y + term->alt_sblines;
+	    /* treeindex = y + count234(term->alt_screen); */
+	}
+    }
+    if (whichtree == term->scrollback) {
+	unsigned char *cline = index234(whichtree, treeindex);
+	line = decompressline(cline, NULL);
+    } else {
+	line = index234(whichtree, treeindex);
+    }
+
+    /* We assume that we don't screw up and retrieve something out of range. */
+    if (line == NULL) {
+	fatalbox("line==NULL in terminal.c\n"
+		 "lineno=%d y=%d w=%d h=%d\n"
+		 "count(scrollback=%p)=%d\n"
+		 "count(screen=%p)=%d\n"
+		 "count(alt=%p)=%d alt_sblines=%d\n"
+		 "whichtree=%p treeindex=%d\n\n"
+		 "Please contact <putty@projects.tartarus.org> "
+		 "and pass on the above information.",
+		 lineno, y, term->cols, term->rows,
+		 term->scrollback, count234(term->scrollback),
+		 term->screen, count234(term->screen),
+		 term->alt_screen, count234(term->alt_screen), term->alt_sblines,
+		 whichtree, treeindex);
+    }
+    assert(line != NULL);
+
+    /*
+     * 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;
+}
+
+#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, unsigned long now)
+{
+    Terminal *term = (Terminal *)ctx;
+    int update = FALSE;
+
+    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) {
+	term->cblinker = !term->cblinker;
+	term->cblink_pending = FALSE;
+	term_schedule_cblink(term);
+	update = TRUE;
+    }
+
+    if (term->in_vbell && now == term->vbell_end) {
+	term->in_vbell = FALSE;
+	update = TRUE;
+    }
+
+    if (update ||
+	(term->window_update_pending && now == term->next_update))
+	term_update(term);
+}
+
+static void term_schedule_update(Terminal *term)
+{
+    if (!term->window_update_pending) {
+	term->window_update_pending = TRUE;
+	term->next_update = schedule_timer(UPDATE_DELAY, term_timer, term);
+    }
+}
+
+/*
+ * Call this whenever the terminal window state changes, to queue
+ * an update.
+ */
+static void seen_disp_event(Terminal *term)
+{
+    term->seen_disp_event = TRUE;      /* for scrollback-reset-on-activity */
+    term_schedule_update(term);
+}
+
+/*
+ * Call when the terminal's blinking-text settings change, or when
+ * a text blink has just occurred.
+ */
+static void term_schedule_tblink(Terminal *term)
+{
+    if (term->blink_is_real) {
+	if (!term->tblink_pending)
+	    term->next_tblink = schedule_timer(TBLINK_DELAY, term_timer, term);
+	term->tblink_pending = TRUE;
+    } else {
+	term->tblinker = 1;	       /* reset when not in use */
+	term->tblink_pending = FALSE;
+    }
+}
+
+/*
+ * Likewise with cursor blinks.
+ */
+static void term_schedule_cblink(Terminal *term)
+{
+    if (term->blink_cur && term->has_focus) {
+	if (!term->cblink_pending)
+	    term->next_cblink = schedule_timer(CBLINK_DELAY, term_timer, term);
+	term->cblink_pending = TRUE;
+    } else {
+	term->cblinker = 1;	       /* reset when not in use */
+	term->cblink_pending = FALSE;
+    }
+}
+
+/*
+ * Call to reset cursor blinking on new output.
+ */
+static void term_reset_cblink(Terminal *term)
+{
+    seen_disp_event(term);
+    term->cblinker = 1;
+    term->cblink_pending = FALSE;
+    term_schedule_cblink(term);
+}
+
+/*
+ * Call to begin a visual bell.
+ */
+static void term_schedule_vbell(Terminal *term, int already_started,
+				long startpoint)
+{
+    long ticks_already_gone;
+
+    if (already_started)
+	ticks_already_gone = GETTICKCOUNT() - startpoint;
+    else
+	ticks_already_gone = 0;
+
+    if (ticks_already_gone < VBELL_DELAY) {
+	term->in_vbell = TRUE;
+	term->vbell_end = schedule_timer(VBELL_DELAY - ticks_already_gone,
+					 term_timer, term);
+    } else {
+	term->in_vbell = FALSE;
+    }
+}
+
+/*
+ * Set up power-on settings for the terminal.
+ * If 'clear' is false, don't actually clear the primary screen, and
+ * position the cursor below the last non-blank line (scrolling if
+ * necessary).
+ */
+static void power_on(Terminal *term, int clear)
+{
+    term->alt_x = term->alt_y = 0;
+    term->savecurs.x = term->savecurs.y = 0;
+    term->alt_savecurs.x = term->alt_savecurs.y = 0;
+    term->alt_t = term->marg_t = 0;
+    if (term->rows != -1)
+	term->alt_b = term->marg_b = term->rows - 1;
+    else
+	term->alt_b = term->marg_b = 0;
+    if (term->cols != -1) {
+	int i;
+	for (i = 0; i < term->cols; i++)
+	    term->tabs[i] = (i % 8 == 0 ? TRUE : FALSE);
+    }
+    term->alt_om = term->dec_om = conf_get_int(term->conf, CONF_dec_om);
+    term->alt_ins = term->insert = FALSE;
+    term->alt_wnext = term->wrapnext =
+        term->save_wnext = term->alt_save_wnext = FALSE;
+    term->alt_wrap = term->wrap = conf_get_int(term->conf, CONF_wrap_mode);
+    term->alt_cset = term->cset = term->save_cset = term->alt_save_cset = 0;
+    term->alt_utf = term->utf = term->save_utf = term->alt_save_utf = 0;
+    term->utf_state = 0;
+    term->alt_sco_acs = term->sco_acs =
+        term->save_sco_acs = term->alt_save_sco_acs = 0;
+    term->cset_attr[0] = term->cset_attr[1] =
+        term->save_csattr = term->alt_save_csattr = CSET_ASCII;
+    term->rvideo = 0;
+    term->in_vbell = FALSE;
+    term->cursor_on = 1;
+    term->big_cursor = 0;
+    term->default_attr = term->save_attr =
+	term->alt_save_attr = term->curr_attr = ATTR_DEFAULT;
+    term->term_editing = term->term_echoing = FALSE;
+    term->app_cursor_keys = conf_get_int(term->conf, CONF_app_cursor);
+    term->app_keypad_keys = conf_get_int(term->conf, CONF_app_keypad);
+    term->use_bce = conf_get_int(term->conf, CONF_bce);
+    term->blink_is_real = conf_get_int(term->conf, CONF_blinktext);
+    term->erase_char = term->basic_erase_char;
+    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++)
+	    term->wordness[i] = conf_get_int_int(term->conf, CONF_wordness, i);
+    }
+    if (term->screen) {
+	swap_screen(term, 1, FALSE, FALSE);
+	erase_lots(term, FALSE, TRUE, TRUE);
+	swap_screen(term, 0, FALSE, FALSE);
+	if (clear)
+	    erase_lots(term, FALSE, TRUE, TRUE);
+	term->curs.y = find_last_nonempty_line(term, term->screen) + 1;
+	if (term->curs.y == term->rows) {
+	    term->curs.y--;
+	    scroll(term, 0, term->rows - 1, 1, TRUE);
+	}
+    } else {
+	term->curs.y = 0;
+    }
+    term->curs.x = 0;
+    term_schedule_tblink(term);
+    term_schedule_cblink(term);
+}
+
+/*
+ * Force a screen update.
+ */
+void term_update(Terminal *term)
+{
+    Context ctx;
+
+    term->window_update_pending = FALSE;
+
+    ctx = get_ctx(term->frontend);
+    if (ctx) {
+	int need_sbar_update = term->seen_disp_event;
+	if (term->seen_disp_event && term->scroll_on_disp) {
+	    term->disptop = 0;	       /* return to main screen */
+	    term->seen_disp_event = 0;
+	    need_sbar_update = TRUE;
+	}
+
+	if (need_sbar_update)
+	    update_sbar(term);
+	do_paint(term, ctx, TRUE);
+	sys_cursor(term->frontend, term->curs.x, term->curs.y - term->disptop);
+	free_ctx(ctx);
+    }
+}
+
+/*
+ * Called from front end when a keypress occurs, to trigger
+ * anything magical that needs to happen in that situation.
+ */
+void term_seen_key_event(Terminal *term)
+{
+    /*
+     * On any keypress, clear the bell overload mechanism
+     * completely, on the grounds that large numbers of
+     * beeps coming from deliberate key action are likely
+     * to be intended (e.g. beeps from filename completion
+     * blocking repeatedly).
+     */
+    term->beep_overloaded = FALSE;
+    while (term->beephead) {
+	struct beeptime *tmp = term->beephead;
+	term->beephead = tmp->next;
+	sfree(tmp);
+    }
+    term->beeptail = NULL;
+    term->nbeeps = 0;
+
+    /*
+     * Reset the scrollback on keypress, if we're doing that.
+     */
+    if (term->scroll_on_key) {
+	term->disptop = 0;	       /* return to main screen */
+	seen_disp_event(term);
+    }
+}
+
+/*
+ * Same as power_on(), but an external function.
+ */
+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);
+    term->disptop = 0;
+    deselect(term);
+    term_update(term);
+}
+
+static void set_erase_char(Terminal *term)
+{
+    term->erase_char = term->basic_erase_char;
+    if (term->use_bce)
+	term->erase_char.attr = (term->curr_attr &
+				 (ATTR_FGMASK | ATTR_BGMASK));
+}
+
+/*
+ * We copy a bunch of stuff out of the Conf structure into local
+ * fields in the Terminal structure, to avoid the repeated tree234
+ * lookups which would be involved in fetching them from the former
+ * every time.
+ */
+void term_copy_stuff_from_conf(Terminal *term)
+{
+    term->ansi_colour = conf_get_int(term->conf, CONF_ansi_colour);
+    term->arabicshaping = conf_get_int(term->conf, CONF_arabicshaping);
+    term->beep = conf_get_int(term->conf, CONF_beep);
+    term->bellovl = conf_get_int(term->conf, CONF_bellovl);
+    term->bellovl_n = conf_get_int(term->conf, CONF_bellovl_n);
+    term->bellovl_s = conf_get_int(term->conf, CONF_bellovl_s);
+    term->bellovl_t = conf_get_int(term->conf, CONF_bellovl_t);
+    term->bidi = conf_get_int(term->conf, CONF_bidi);
+    term->bksp_is_delete = conf_get_int(term->conf, CONF_bksp_is_delete);
+    term->blink_cur = conf_get_int(term->conf, CONF_blink_cur);
+    term->blinktext = conf_get_int(term->conf, CONF_blinktext);
+    term->cjk_ambig_wide = conf_get_int(term->conf, CONF_cjk_ambig_wide);
+    term->conf_height = conf_get_int(term->conf, CONF_height);
+    term->conf_width = conf_get_int(term->conf, CONF_width);
+    term->crhaslf = conf_get_int(term->conf, CONF_crhaslf);
+    term->erase_to_scrollback = conf_get_int(term->conf, CONF_erase_to_scrollback);
+    term->funky_type = conf_get_int(term->conf, CONF_funky_type);
+    term->lfhascr = conf_get_int(term->conf, CONF_lfhascr);
+    term->logflush = conf_get_int(term->conf, CONF_logflush);
+    term->logtype = conf_get_int(term->conf, CONF_logtype);
+    term->mouse_override = conf_get_int(term->conf, CONF_mouse_override);
+    term->nethack_keypad = conf_get_int(term->conf, CONF_nethack_keypad);
+    term->no_alt_screen = conf_get_int(term->conf, CONF_no_alt_screen);
+    term->no_applic_c = conf_get_int(term->conf, CONF_no_applic_c);
+    term->no_applic_k = conf_get_int(term->conf, CONF_no_applic_k);
+    term->no_dbackspace = conf_get_int(term->conf, CONF_no_dbackspace);
+    term->no_mouse_rep = conf_get_int(term->conf, CONF_no_mouse_rep);
+    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->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);
+    term->rxvt_homeend = conf_get_int(term->conf, CONF_rxvt_homeend);
+    term->scroll_on_disp = conf_get_int(term->conf, CONF_scroll_on_disp);
+    term->scroll_on_key = conf_get_int(term->conf, CONF_scroll_on_key);
+    term->xterm_256_colour = conf_get_int(term->conf, CONF_xterm_256_colour);
+
+    /*
+     * Parse the control-character escapes in the configured
+     * answerback string.
+     */
+    {
+	char *answerback = conf_get_str(term->conf, CONF_answerback);
+	int maxlen = strlen(answerback);
+
+	term->answerback = snewn(maxlen, char);
+	term->answerbacklen = 0;
+
+	while (*answerback) {
+	    char *n;
+	    char c = ctrlparse(answerback, &n);
+	    if (n) {
+		term->answerback[term->answerbacklen++] = c;
+		answerback = n;
+	    } else {
+		term->answerback[term->answerbacklen++] = *answerback++;
+	    }
+	}
+    }
+}
+
+/*
+ * When the user reconfigures us, we need to check the forbidden-
+ * alternate-screen config option, disable raw mouse mode if the
+ * user has disabled mouse reporting, and abandon a print job if
+ * the user has disabled printing.
+ */
+void term_reconfig(Terminal *term, Conf *conf)
+{
+    /*
+     * Before adopting the new config, check all those terminal
+     * settings which control power-on defaults; and if they've
+     * changed, we will modify the current state as well as the
+     * default one. The full list is: Auto wrap mode, DEC Origin
+     * Mode, BCE, blinking text, character classes.
+     */
+    int reset_wrap, reset_decom, reset_bce, reset_tblink, reset_charclass;
+    int i;
+
+    reset_wrap = (conf_get_int(term->conf, CONF_wrap_mode) !=
+		  conf_get_int(conf, CONF_wrap_mode));
+    reset_decom = (conf_get_int(term->conf, CONF_dec_om) !=
+		   conf_get_int(conf, CONF_dec_om));
+    reset_bce = (conf_get_int(term->conf, CONF_bce) !=
+		 conf_get_int(conf, CONF_bce));
+    reset_tblink = (conf_get_int(term->conf, CONF_blinktext) !=
+		    conf_get_int(conf, CONF_blinktext));
+    reset_charclass = 0;
+    for (i = 0; i < 256; i++)
+	if (conf_get_int_int(term->conf, CONF_wordness, i) !=
+	    conf_get_int_int(conf, CONF_wordness, i))
+	    reset_charclass = 1;
+
+    /*
+     * If the bidi or shaping settings have changed, flush the bidi
+     * cache completely.
+     */
+    if (conf_get_int(term->conf, CONF_arabicshaping) !=
+	conf_get_int(conf, CONF_arabicshaping) ||
+	conf_get_int(term->conf, CONF_bidi) !=
+	conf_get_int(conf, CONF_bidi)) {
+	for (i = 0; i < term->bidi_cache_size; i++) {
+	    sfree(term->pre_bidi_cache[i].chars);
+	    sfree(term->post_bidi_cache[i].chars);
+	    term->pre_bidi_cache[i].width = -1;
+	    term->pre_bidi_cache[i].chars = NULL;
+	    term->post_bidi_cache[i].width = -1;
+	    term->post_bidi_cache[i].chars = NULL;
+	}
+    }
+
+    conf_free(term->conf);
+    term->conf = conf_copy(conf);
+
+    if (reset_wrap)
+	term->alt_wrap = term->wrap = conf_get_int(term->conf, CONF_wrap_mode);
+    if (reset_decom)
+	term->alt_om = term->dec_om = conf_get_int(term->conf, CONF_dec_om);
+    if (reset_bce) {
+	term->use_bce = conf_get_int(term->conf, CONF_bce);
+	set_erase_char(term);
+    }
+    if (reset_tblink) {
+	term->blink_is_real = conf_get_int(term->conf, CONF_blinktext);
+    }
+    if (reset_charclass)
+	for (i = 0; i < 256; i++)
+	    term->wordness[i] = conf_get_int_int(term->conf, CONF_wordness, i);
+
+    if (conf_get_int(term->conf, CONF_no_alt_screen))
+	swap_screen(term, 0, FALSE, FALSE);
+    if (conf_get_int(term->conf, CONF_no_mouse_rep)) {
+	term->xterm_mouse = 0;
+	set_raw_mouse_mode(term->frontend, 0);
+    }
+    if (conf_get_int(term->conf, CONF_no_remote_charset)) {
+	term->cset_attr[0] = term->cset_attr[1] = CSET_ASCII;
+	term->sco_acs = term->alt_sco_acs = 0;
+	term->utf = 0;
+    }
+    if (!conf_get_str(term->conf, CONF_printer)) {
+	term_print_finish(term);
+    }
+    term_schedule_tblink(term);
+    term_schedule_cblink(term);
+    term_copy_stuff_from_conf(term);
+}
+
+/*
+ * Clear the scrollback.
+ */
+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);
+}
+
+/*
+ * Initialise the terminal.
+ */
+Terminal *term_init(Conf *myconf, struct unicode_data *ucsdata,
+		    void *frontend)
+{
+    Terminal *term;
+
+    /*
+     * Allocate a new Terminal structure and initialise the fields
+     * that need it.
+     */
+    term = snew(Terminal);
+    term->frontend = frontend;
+    term->ucsdata = ucsdata;
+    term->conf = conf_copy(myconf);
+    term->logctx = NULL;
+    term->compatibility_level = TM_PUTTY;
+    strcpy(term->id_string, "\033[?6c");
+    term->cblink_pending = term->tblink_pending = FALSE;
+    term->paste_buffer = NULL;
+    term->paste_len = 0;
+    bufchain_init(&term->inbuf);
+    bufchain_init(&term->printer_buf);
+    term->printing = term->only_printing = FALSE;
+    term->print_job = NULL;
+    term->vt52_mode = FALSE;
+    term->cr_lf_return = FALSE;
+    term->seen_disp_event = FALSE;
+    term->mouse_is_down = FALSE;
+    term->reset_132 = FALSE;
+    term->cblinker = term->tblinker = 0;
+    term->has_focus = 1;
+    term->repeat_off = FALSE;
+    term->termstate = TOPLEVEL;
+    term->selstate = NO_SELECTION;
+    term->curstype = 0;
+
+    term_copy_stuff_from_conf(term);
+
+    term->screen = term->alt_screen = term->scrollback = NULL;
+    term->tempsblines = 0;
+    term->alt_sblines = 0;
+    term->disptop = 0;
+    term->disptext = NULL;
+    term->dispcursx = term->dispcursy = -1;
+    term->tabs = NULL;
+    deselect(term);
+    term->rows = term->cols = -1;
+    power_on(term, TRUE);
+    term->beephead = term->beeptail = NULL;
+#ifdef OPTIMISE_SCROLL
+    term->scrollhead = term->scrolltail = NULL;
+#endif /* OPTIMISE_SCROLL */
+    term->nbeeps = 0;
+    term->lastbeep = FALSE;
+    term->beep_overloaded = FALSE;
+    term->attr_mask = 0xffffffff;
+    term->resize_fn = NULL;
+    term->resize_ctx = NULL;
+    term->in_term_out = FALSE;
+    term->ltemp = NULL;
+    term->ltemp_size = 0;
+    term->wcFrom = NULL;
+    term->wcTo = NULL;
+    term->wcFromTo_size = 0;
+
+    term->window_update_pending = FALSE;
+
+    term->bidi_cache_size = 0;
+    term->pre_bidi_cache = term->post_bidi_cache = NULL;
+
+    /* FULL-TERMCHAR */
+    term->basic_erase_char.chr = CSET_ASCII | ' ';
+    term->basic_erase_char.attr = ATTR_DEFAULT;
+    term->basic_erase_char.cc_next = 0;
+    term->erase_char = term->basic_erase_char;
+
+    return term;
+}
+
+void term_free(Terminal *term)
+{
+    termline *line;
+    struct beeptime *beep;
+    int i;
+
+    while ((line = delpos234(term->scrollback, 0)) != NULL)
+	sfree(line);		       /* compressed data, not a termline */
+    freetree234(term->scrollback);
+    while ((line = delpos234(term->screen, 0)) != NULL)
+	freeline(line);
+    freetree234(term->screen);
+    while ((line = delpos234(term->alt_screen, 0)) != NULL)
+	freeline(line);
+    freetree234(term->alt_screen);
+    if (term->disptext) {
+	for (i = 0; i < term->rows; i++)
+	    freeline(term->disptext[i]);
+    }
+    sfree(term->disptext);
+    while (term->beephead) {
+	beep = term->beephead;
+	term->beephead = beep->next;
+	sfree(beep);
+    }
+    bufchain_clear(&term->inbuf);
+    if(term->print_job)
+	printer_finish_job(term->print_job);
+    bufchain_clear(&term->printer_buf);
+    sfree(term->paste_buffer);
+    sfree(term->ltemp);
+    sfree(term->wcFrom);
+    sfree(term->wcTo);
+
+    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);
+
+    sfree(term);
+}
+
+/*
+ * Set up the terminal for a given size.
+ */
+void term_size(Terminal *term, int newrows, int newcols, int newsavelines)
+{
+    tree234 *newalt;
+    termline **newdisp, *line;
+    int i, j, oldrows = term->rows;
+    int sblen;
+    int save_alt_which = term->alt_which;
+
+    if (newrows == term->rows && newcols == term->cols &&
+	newsavelines == term->savelines)
+	return;			       /* nothing to do */
+
+    /* Behave sensibly if we're given zero (or negative) rows/cols */
+
+    if (newrows < 1) newrows = 1;
+    if (newcols < 1) newcols = 1;
+
+    deselect(term);
+    swap_screen(term, 0, FALSE, FALSE);
+
+    term->alt_t = term->marg_t = 0;
+    term->alt_b = term->marg_b = newrows - 1;
+
+    if (term->rows == -1) {
+	term->scrollback = newtree234(NULL);
+	term->screen = newtree234(NULL);
+	term->tempsblines = 0;
+	term->rows = 0;
+    }
+
+    /*
+     * Resize the screen and scrollback. We only need to shift
+     * lines around within our data structures, because lineptr()
+     * will take care of resizing each individual line if
+     * necessary. So:
+     * 
+     *  - If the new screen is longer, we shunt lines in from temporary
+     *    scrollback if possible, otherwise we add new blank lines at
+     *    the bottom.
+     *
+     *  - If the new screen is shorter, we remove any blank lines at
+     *    the bottom if possible, otherwise shunt lines above the cursor
+     *    to scrollback if possible, otherwise delete lines below the
+     *    cursor.
+     * 
+     *  - Then, if the new scrollback length is less than the
+     *    amount of scrollback we actually have, we must throw some
+     *    away.
+     */
+    sblen = count234(term->scrollback);
+    /* Do this loop to expand the screen if newrows > rows */
+    assert(term->rows == count234(term->screen));
+    while (term->rows < newrows) {
+	if (term->tempsblines > 0) {
+	    unsigned char *cline;
+	    /* Insert a line from the scrollback at the top of the screen. */
+	    assert(sblen >= term->tempsblines);
+	    cline = delpos234(term->scrollback, --sblen);
+	    line = decompressline(cline, NULL);
+	    sfree(cline);
+	    line->temporary = FALSE;   /* reconstituted line is now real */
+	    term->tempsblines -= 1;
+	    addpos234(term->screen, line, 0);
+	    term->curs.y += 1;
+	    term->savecurs.y += 1;
+	    term->alt_y += 1;
+	    term->alt_savecurs.y += 1;
+	} else {
+	    /* Add a new blank line at the bottom of the screen. */
+	    line = newline(term, newcols, FALSE);
+	    addpos234(term->screen, line, count234(term->screen));
+	}
+	term->rows += 1;
+    }
+    /* Do this loop to shrink the screen if newrows < rows */
+    while (term->rows > newrows) {
+	if (term->curs.y < term->rows - 1) {
+	    /* delete bottom row, unless it contains the cursor */
+            line = delpos234(term->screen, term->rows - 1);
+            freeline(line);
+	} else {
+	    /* push top row to scrollback */
+	    line = delpos234(term->screen, 0);
+	    addpos234(term->scrollback, compressline(line), sblen++);
+	    freeline(line);
+	    term->tempsblines += 1;
+	    term->curs.y -= 1;
+	    term->savecurs.y -= 1;
+	    term->alt_y -= 1;
+	    term->alt_savecurs.y -= 1;
+	}
+	term->rows -= 1;
+    }
+    assert(term->rows == newrows);
+    assert(count234(term->screen) == newrows);
+
+    /* Delete any excess lines from the scrollback. */
+    while (sblen > newsavelines) {
+	line = delpos234(term->scrollback, 0);
+	sfree(line);
+	sblen--;
+    }
+    if (sblen < term->tempsblines)
+	term->tempsblines = sblen;
+    assert(count234(term->scrollback) <= newsavelines);
+    assert(count234(term->scrollback) >= term->tempsblines);
+    term->disptop = 0;
+
+    /* Make a new displayed text buffer. */
+    newdisp = snewn(newrows, termline *);
+    for (i = 0; i < newrows; i++) {
+	newdisp[i] = newline(term, newcols, FALSE);
+	for (j = 0; j < newcols; j++)
+	    newdisp[i]->chars[j].attr = ATTR_INVALID;
+    }
+    if (term->disptext) {
+	for (i = 0; i < oldrows; i++)
+	    freeline(term->disptext[i]);
+    }
+    sfree(term->disptext);
+    term->disptext = newdisp;
+    term->dispcursx = term->dispcursy = -1;
+
+    /* Make a new alternate screen. */
+    newalt = newtree234(NULL);
+    for (i = 0; i < newrows; i++) {
+	line = newline(term, newcols, TRUE);
+	addpos234(newalt, line, i);
+    }
+    if (term->alt_screen) {
+	while (NULL != (line = delpos234(term->alt_screen, 0)))
+	    freeline(line);
+	freetree234(term->alt_screen);
+    }
+    term->alt_screen = newalt;
+    term->alt_sblines = 0;
+
+    term->tabs = sresize(term->tabs, newcols, unsigned char);
+    {
+	int i;
+	for (i = (term->cols > 0 ? term->cols : 0); i < newcols; i++)
+	    term->tabs[i] = (i % 8 == 0 ? TRUE : FALSE);
+    }
+
+    /* Check that the cursor positions are still valid. */
+    if (term->savecurs.y < 0)
+	term->savecurs.y = 0;
+    if (term->savecurs.y >= newrows)
+	term->savecurs.y = newrows - 1;
+    if (term->savecurs.x >= newcols)
+	term->savecurs.x = newcols - 1;
+    if (term->alt_savecurs.y < 0)
+	term->alt_savecurs.y = 0;
+    if (term->alt_savecurs.y >= newrows)
+	term->alt_savecurs.y = newrows - 1;
+    if (term->alt_savecurs.x >= newcols)
+	term->alt_savecurs.x = newcols - 1;
+    if (term->curs.y < 0)
+	term->curs.y = 0;
+    if (term->curs.y >= newrows)
+	term->curs.y = newrows - 1;
+    if (term->curs.x >= newcols)
+	term->curs.x = newcols - 1;
+    if (term->alt_y < 0)
+	term->alt_y = 0;
+    if (term->alt_y >= newrows)
+	term->alt_y = newrows - 1;
+    if (term->alt_x >= newcols)
+	term->alt_x = newcols - 1;
+    term->alt_x = term->alt_y = 0;
+    term->wrapnext = term->alt_wnext = FALSE;
+
+    term->rows = newrows;
+    term->cols = newcols;
+    term->savelines = newsavelines;
+
+    swap_screen(term, save_alt_which, FALSE, FALSE);
+
+    update_sbar(term);
+    term_update(term);
+    if (term->resize_fn)
+	term->resize_fn(term->resize_ctx, term->cols, term->rows);
+}
+
+/*
+ * Hand a function and context pointer to the terminal which it can
+ * use to notify a back end of resizes.
+ */
+void term_provide_resize_fn(Terminal *term,
+			    void (*resize_fn)(void *, int, int),
+			    void *resize_ctx)
+{
+    term->resize_fn = resize_fn;
+    term->resize_ctx = resize_ctx;
+    if (resize_fn && term->cols > 0 && term->rows > 0)
+	resize_fn(resize_ctx, term->cols, term->rows);
+}
+
+/* Find the bottom line on the screen that has any content.
+ * If only the top line has content, returns 0.
+ * If no lines have content, return -1.
+ */ 
+static int find_last_nonempty_line(Terminal * term, tree234 * screen)
+{
+    int i;
+    for (i = count234(screen) - 1; i >= 0; i--) {
+	termline *line = index234(screen, i);
+	int j;
+	for (j = 0; j < line->cols; j++)
+	    if (!termchars_equal(&line->chars[j], &term->erase_char))
+		break;
+	if (j != line->cols) break;
+    }
+    return i;
+}
+
+/*
+ * Swap screens. If `reset' is TRUE and we have been asked to
+ * switch to the alternate screen, we must bring most of its
+ * configuration from the main screen and erase the contents of the
+ * alternate screen completely. (This is even true if we're already
+ * on it! Blame xterm.)
+ */
+static void swap_screen(Terminal *term, int which, int reset, int keep_cur_pos)
+{
+    int t;
+    pos tp;
+    tree234 *ttr;
+
+    if (!which)
+	reset = FALSE;		       /* do no weird resetting if which==0 */
+
+    if (which != term->alt_which) {
+	term->alt_which = which;
+
+	ttr = term->alt_screen;
+	term->alt_screen = term->screen;
+	term->screen = ttr;
+	term->alt_sblines = find_last_nonempty_line(term, term->alt_screen) + 1;
+	t = term->curs.x;
+	if (!reset && !keep_cur_pos)
+	    term->curs.x = term->alt_x;
+	term->alt_x = t;
+	t = term->curs.y;
+	if (!reset && !keep_cur_pos)
+	    term->curs.y = term->alt_y;
+	term->alt_y = t;
+	t = term->marg_t;
+	if (!reset) term->marg_t = term->alt_t;
+	term->alt_t = t;
+	t = term->marg_b;
+	if (!reset) term->marg_b = term->alt_b;
+	term->alt_b = t;
+	t = term->dec_om;
+	if (!reset) term->dec_om = term->alt_om;
+	term->alt_om = t;
+	t = term->wrap;
+	if (!reset) term->wrap = term->alt_wrap;
+	term->alt_wrap = t;
+	t = term->wrapnext;
+	if (!reset) term->wrapnext = term->alt_wnext;
+	term->alt_wnext = t;
+	t = term->insert;
+	if (!reset) term->insert = term->alt_ins;
+	term->alt_ins = t;
+	t = term->cset;
+	if (!reset) term->cset = term->alt_cset;
+	term->alt_cset = t;
+	t = term->utf;
+	if (!reset) term->utf = term->alt_utf;
+	term->alt_utf = t;
+	t = term->sco_acs;
+	if (!reset) term->sco_acs = term->alt_sco_acs;
+	term->alt_sco_acs = t;
+
+	tp = term->savecurs;
+	if (!reset && !keep_cur_pos)
+	    term->savecurs = term->alt_savecurs;
+	term->alt_savecurs = tp;
+        t = term->save_cset;
+        if (!reset && !keep_cur_pos)
+            term->save_cset = term->alt_save_cset;
+        term->alt_save_cset = t;
+        t = term->save_csattr;
+        if (!reset && !keep_cur_pos)
+            term->save_csattr = term->alt_save_csattr;
+        term->alt_save_csattr = t;
+        t = term->save_attr;
+        if (!reset && !keep_cur_pos)
+            term->save_attr = term->alt_save_attr;
+        term->alt_save_attr = t;
+        t = term->save_utf;
+        if (!reset && !keep_cur_pos)
+            term->save_utf = term->alt_save_utf;
+        term->alt_save_utf = t;
+        t = term->save_wnext;
+        if (!reset && !keep_cur_pos)
+            term->save_wnext = term->alt_save_wnext;
+        term->alt_save_wnext = t;
+        t = term->save_sco_acs;
+        if (!reset && !keep_cur_pos)
+            term->save_sco_acs = term->alt_save_sco_acs;
+        term->alt_save_sco_acs = t;
+    }
+
+    if (reset && term->screen) {
+	/*
+	 * Yes, this _is_ supposed to honour background-colour-erase.
+	 */
+	erase_lots(term, FALSE, TRUE, TRUE);
+    }
+}
+
+/*
+ * Update the scroll bar.
+ */
+static void update_sbar(Terminal *term)
+{
+    int nscroll = sblines(term);
+    set_sbar(term->frontend, nscroll + term->rows,
+	     nscroll + term->disptop, term->rows);
+}
+
+/*
+ * Check whether the region bounded by the two pointers intersects
+ * the scroll region, and de-select the on-screen selection if so.
+ */
+static void check_selection(Terminal *term, pos from, pos to)
+{
+    if (poslt(from, term->selend) && poslt(term->selstart, to))
+	deselect(term);
+}
+
+/*
+ * Scroll the screen. (`lines' is +ve for scrolling forward, -ve
+ * for backward.) `sb' is TRUE if the scrolling is permitted to
+ * affect the scrollback buffer.
+ */
+static void scroll(Terminal *term, int topline, int botline, int lines, int sb)
+{
+    termline *line;
+    int i, seltop, scrollwinsize;
+#ifdef OPTIMISE_SCROLL
+    int olddisptop, shift;
+#endif /* OPTIMISE_SCROLL */
+
+    if (topline != 0 || term->alt_which != 0)
+	sb = FALSE;
+
+#ifdef OPTIMISE_SCROLL
+    olddisptop = term->disptop;
+    shift = lines;
+#endif /* OPTIMISE_SCROLL */
+
+    scrollwinsize = botline - topline + 1;
+
+    if (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++)
+		copy_termchar(line, i, &term->erase_char);
+	    line->lattr = LATTR_NORM;
+	    addpos234(term->screen, line, topline);
+
+	    if (term->selstart.y >= topline && term->selstart.y <= botline) {
+		term->selstart.y++;
+		if (term->selstart.y > botline) {
+		    term->selstart.y = botline + 1;
+		    term->selstart.x = 0;
+		}
+	    }
+	    if (term->selend.y >= topline && term->selend.y <= botline) {
+		term->selend.y++;
+		if (term->selend.y > botline) {
+		    term->selend.y = botline + 1;
+		    term->selend.x = 0;
+		}
+	    }
+	}
+    } else {
+        if (lines > scrollwinsize)
+            lines = scrollwinsize;
+	while (lines-- > 0) {
+	    line = delpos234(term->screen, topline);
+#ifdef TERM_CC_DIAGS
+	    cc_check(line);
+#endif
+	    if (sb && term->savelines > 0) {
+		int sblen = count234(term->scrollback);
+		/*
+		 * We must add this line to the scrollback. We'll
+		 * remove a line from the top of the scrollback if
+		 * the scrollback is full.
+		 */
+		if (sblen == term->savelines) {
+		    unsigned char *cline;
+
+		    sblen--;
+		    cline = delpos234(term->scrollback, 0);
+		    sfree(cline);
+		} else
+		    term->tempsblines += 1;
+
+		addpos234(term->scrollback, compressline(line), sblen);
+
+		/* now `line' itself can be reused as the bottom line */
+
+		/*
+		 * If the user is currently looking at part of the
+		 * scrollback, and they haven't enabled any options
+		 * that are going to reset the scrollback as a
+		 * result of this movement, then the chances are
+		 * they'd like to keep looking at the same line. So
+		 * we move their viewpoint at the same rate as the
+		 * scroll, at least until their viewpoint hits the
+		 * top end of the scrollback buffer, at which point
+		 * we don't have the choice any more.
+		 * 
+		 * Thanks to Jan Holmen Holsten for the idea and
+		 * initial implementation.
+		 */
+		if (term->disptop > -term->savelines && term->disptop < 0)
+		    term->disptop--;
+	    }
+            resizeline(term, line, term->cols);
+	    for (i = 0; i < term->cols; i++)
+		copy_termchar(line, i, &term->erase_char);
+	    line->lattr = LATTR_NORM;
+	    addpos234(term->screen, line, botline);
+
+	    /*
+	     * If the selection endpoints move into the scrollback,
+	     * we keep them moving until they hit the top. However,
+	     * of course, if the line _hasn't_ moved into the
+	     * scrollback then we don't do this, and cut them off
+	     * at the top of the scroll region.
+	     * 
+	     * This applies to selstart and selend (for an existing
+	     * selection), and also selanchor (for one being
+	     * selected as we speak).
+	     */
+	    seltop = sb ? -term->savelines : topline;
+
+	    if (term->selstate != NO_SELECTION) {
+		if (term->selstart.y >= seltop &&
+		    term->selstart.y <= botline) {
+		    term->selstart.y--;
+		    if (term->selstart.y < seltop) {
+			term->selstart.y = seltop;
+			term->selstart.x = 0;
+		    }
+		}
+		if (term->selend.y >= seltop && term->selend.y <= botline) {
+		    term->selend.y--;
+		    if (term->selend.y < seltop) {
+			term->selend.y = seltop;
+			term->selend.x = 0;
+		    }
+		}
+		if (term->selanchor.y >= seltop &&
+		    term->selanchor.y <= botline) {
+		    term->selanchor.y--;
+		    if (term->selanchor.y < seltop) {
+			term->selanchor.y = seltop;
+			term->selanchor.x = 0;
+		    }
+		}
+	    }
+	}
+    }
+#ifdef OPTIMISE_SCROLL
+    shift += term->disptop - olddisptop;
+    if (shift < term->rows && shift > -term->rows && shift != 0)
+	scroll_display(term, topline, botline, shift);
+#endif /* OPTIMISE_SCROLL */
+}
+
+#ifdef OPTIMISE_SCROLL
+/*
+ * Add a scroll of a region on the screen into the pending scroll list.
+ * `lines' is +ve for scrolling forward, -ve for backward.
+ *
+ * If the scroll is on the same area as the last scroll in the list,
+ * merge them.
+ */
+static void save_scroll(Terminal *term, int topline, int botline, int lines)
+{
+    struct scrollregion *newscroll;
+    if (term->scrolltail &&
+	term->scrolltail->topline == topline && 
+	term->scrolltail->botline == botline) {
+	term->scrolltail->lines += lines;
+    } else {
+	newscroll = snew(struct scrollregion);
+	newscroll->topline = topline;
+	newscroll->botline = botline;
+	newscroll->lines = lines;
+	newscroll->next = NULL;
+
+	if (!term->scrollhead)
+	    term->scrollhead = newscroll;
+	else
+	    term->scrolltail->next = newscroll;
+	term->scrolltail = newscroll;
+    }
+}
+
+/*
+ * Scroll the physical display, and our conception of it in disptext.
+ */
+static void scroll_display(Terminal *term, int topline, int botline, int lines)
+{
+    int distance, nlines, i, j;
+
+    distance = lines > 0 ? lines : -lines;
+    nlines = botline - topline + 1 - distance;
+    if (lines > 0) {
+	for (i = 0; i < nlines; i++)
+	    for (j = 0; j < term->cols; j++)
+		copy_termchar(term->disptext[i], j,
+			      term->disptext[i+distance]->chars+j);
+	if (term->dispcursy >= 0 &&
+	    term->dispcursy >= topline + distance &&
+	    term->dispcursy < topline + distance + nlines)
+	    term->dispcursy -= distance;
+	for (i = 0; i < distance; i++)
+	    for (j = 0; j < term->cols; j++)
+		term->disptext[nlines+i]->chars[j].attr |= ATTR_INVALID;
+    } else {
+	for (i = nlines; i-- ;)
+	    for (j = 0; j < term->cols; j++)
+		copy_termchar(term->disptext[i+distance], j,
+			      term->disptext[i]->chars+j);
+	if (term->dispcursy >= 0 &&
+	    term->dispcursy >= topline &&
+	    term->dispcursy < topline + nlines)
+	    term->dispcursy += distance;
+	for (i = 0; i < distance; i++)
+	    for (j = 0; j < term->cols; j++)
+		term->disptext[i]->chars[j].attr |= ATTR_INVALID;
+    }
+    save_scroll(term, topline, botline, lines);
+}
+#endif /* OPTIMISE_SCROLL */
+
+/*
+ * Move the cursor to a given position, clipping at boundaries. We
+ * may or may not want to clip at the scroll margin: marg_clip is 0
+ * not to, 1 to disallow _passing_ the margins, and 2 to disallow
+ * even _being_ outside the margins.
+ */
+static void move(Terminal *term, int x, int y, int marg_clip)
+{
+    if (x < 0)
+	x = 0;
+    if (x >= term->cols)
+	x = term->cols - 1;
+    if (marg_clip) {
+	if ((term->curs.y >= term->marg_t || marg_clip == 2) &&
+	    y < term->marg_t)
+	    y = term->marg_t;
+	if ((term->curs.y <= term->marg_b || marg_clip == 2) &&
+	    y > term->marg_b)
+	    y = term->marg_b;
+    }
+    if (y < 0)
+	y = 0;
+    if (y >= term->rows)
+	y = term->rows - 1;
+    term->curs.x = x;
+    term->curs.y = y;
+    term->wrapnext = FALSE;
+}
+
+/*
+ * Save or restore the cursor and SGR mode.
+ */
+static void save_cursor(Terminal *term, int save)
+{
+    if (save) {
+	term->savecurs = term->curs;
+	term->save_attr = term->curr_attr;
+	term->save_cset = term->cset;
+	term->save_utf = term->utf;
+	term->save_wnext = term->wrapnext;
+	term->save_csattr = term->cset_attr[term->cset];
+	term->save_sco_acs = term->sco_acs;
+    } else {
+	term->curs = term->savecurs;
+	/* Make sure the window hasn't shrunk since the save */
+	if (term->curs.x >= term->cols)
+	    term->curs.x = term->cols - 1;
+	if (term->curs.y >= term->rows)
+	    term->curs.y = term->rows - 1;
+
+	term->curr_attr = term->save_attr;
+	term->cset = term->save_cset;
+	term->utf = term->save_utf;
+	term->wrapnext = term->save_wnext;
+	/*
+	 * wrapnext might reset to False if the x position is no
+	 * longer at the rightmost edge.
+	 */
+	if (term->wrapnext && term->curs.x < term->cols-1)
+	    term->wrapnext = FALSE;
+	term->cset_attr[term->cset] = term->save_csattr;
+	term->sco_acs = term->save_sco_acs;
+	set_erase_char(term);
+    }
+}
+
+/*
+ * This function is called before doing _anything_ which affects
+ * only part of a line of text. It is used to mark the boundary
+ * between two character positions, and it indicates that some sort
+ * of effect is going to happen on only one side of that boundary.
+ * 
+ * The effect of this function is to check whether a CJK
+ * double-width character is straddling the boundary, and to remove
+ * it and replace it with two spaces if so. (Of course, one or
+ * other of those spaces is then likely to be replaced with
+ * something else again, as a result of whatever happens next.)
+ * 
+ * Also, if the boundary is at the right-hand _edge_ of the screen,
+ * it implies something deliberate is being done to the rightmost
+ * column position; hence we must clear LATTR_WRAPPED2.
+ * 
+ * The input to the function is the coordinates of the _second_
+ * character of the pair.
+ */
+static void check_boundary(Terminal *term, int x, int y)
+{
+    termline *ldata;
+
+    /* Validate input coordinates, just in case. */
+    if (x == 0 || x > term->cols)
+	return;
+
+    ldata = scrlineptr(y);
+    check_line_size(term, ldata);
+    if (x == term->cols) {
+	ldata->lattr &= ~LATTR_WRAPPED2;
+    } else {
+	if (ldata->chars[x].chr == UCSWIDE) {
+	    clear_cc(ldata, x-1);
+	    clear_cc(ldata, x);
+	    ldata->chars[x-1].chr = ' ' | CSET_ASCII;
+	    ldata->chars[x] = ldata->chars[x-1];
+	}
+    }
+}
+
+/*
+ * Erase a large portion of the screen: the whole screen, or the
+ * whole line, or parts thereof.
+ */
+static void erase_lots(Terminal *term,
+		       int line_only, int from_begin, int to_end)
+{
+    pos start, end;
+    int erase_lattr;
+    int erasing_lines_from_top = 0;
+
+    if (line_only) {
+	start.y = term->curs.y;
+	start.x = 0;
+	end.y = term->curs.y + 1;
+	end.x = 0;
+	erase_lattr = FALSE;
+    } else {
+	start.y = 0;
+	start.x = 0;
+	end.y = term->rows;
+	end.x = 0;
+	erase_lattr = TRUE;
+    }
+    if (!from_begin) {
+	start = term->curs;
+    }
+    if (!to_end) {
+	end = term->curs;
+	incpos(end);
+    }
+    if (!from_begin || !to_end)
+	check_boundary(term, term->curs.x, term->curs.y);
+    check_selection(term, start, end);
+
+    /* Clear screen also forces a full window redraw, just in case. */
+    if (start.y == 0 && start.x == 0 && end.y == term->rows)
+	term_invalidate(term);
+
+    /* Lines scrolled away shouldn't be brought back on if the terminal
+     * resizes. */
+    if (start.y == 0 && start.x == 0 && end.x == 0 && erase_lattr)
+	erasing_lines_from_top = 1;
+
+    if (term->erase_to_scrollback && erasing_lines_from_top) {
+	/* If it's a whole number of lines, starting at the top, and
+	 * we're fully erasing them, erase by scrolling and keep the
+	 * lines in the scrollback. */
+	int scrolllines = end.y;
+	if (end.y == term->rows) {
+	    /* Shrink until we find a non-empty row.*/
+	    scrolllines = find_last_nonempty_line(term, term->screen) + 1;
+	}
+	if (scrolllines > 0)
+	    scroll(term, 0, scrolllines - 1, scrolllines, TRUE);
+    } 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);
+		else
+		    ldata->lattr = LATTR_NORM;
+	    } else {
+		copy_termchar(ldata, start.x, &term->erase_char);
+	    }
+	    if (incpos(start) && start.y < term->rows) {
+		ldata = scrlineptr(start.y);
+	    }
+	}
+    }
+
+    /* After an erase of lines from the top of the screen, we shouldn't
+     * bring the lines back again if the terminal enlarges (since the user or
+     * application has explictly thrown them away). */
+    if (erasing_lines_from_top && !(term->alt_which))
+	term->tempsblines = 0;
+}
+
+/*
+ * Insert or delete characters within the current line. n is +ve if
+ * insertion is desired, and -ve for deletion.
+ */
+static void insch(Terminal *term, int n)
+{
+    int dir = (n < 0 ? -1 : +1);
+    int m, j;
+    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;
+
+    /*
+     * 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);
+    ldata = scrlineptr(term->curs.y);
+    if (dir < 0) {
+	for (j = 0; j < m; j++)
+	    move_termchar(ldata,
+			  ldata->chars + term->curs.x + j,
+			  ldata->chars + term->curs.x + j + n);
+	while (n--)
+	    copy_termchar(ldata, term->curs.x + m++, &term->erase_char);
+    } else {
+	for (j = m; j-- ;)
+	    move_termchar(ldata,
+			  ldata->chars + term->curs.x + j + n,
+			  ldata->chars + term->curs.x + j);
+	while (n--)
+	    copy_termchar(ldata, term->curs.x + n, &term->erase_char);
+    }
+}
+
+/*
+ * Toggle terminal mode `mode' to state `state'. (`query' indicates
+ * whether the mode is a DEC private one or a normal one.)
+ */
+static void toggle_mode(Terminal *term, int mode, int query, int state)
+{
+    if (query)
+	switch (mode) {
+	  case 1:		       /* DECCKM: application cursor keys */
+	    term->app_cursor_keys = state;
+	    break;
+	  case 2:		       /* DECANM: VT52 mode */
+	    term->vt52_mode = !state;
+	    if (term->vt52_mode) {
+		term->blink_is_real = FALSE;
+		term->vt52_bold = FALSE;
+	    } else {
+		term->blink_is_real = term->blinktext;
+	    }
+	    term_schedule_tblink(term);
+	    break;
+	  case 3:		       /* DECCOLM: 80/132 columns */
+	    deselect(term);
+	    if (!term->no_remote_resize)
+		request_resize(term->frontend, state ? 132 : 80, term->rows);
+	    term->reset_132 = state;
+	    term->alt_t = term->marg_t = 0;
+	    term->alt_b = term->marg_b = term->rows - 1;
+	    move(term, 0, 0, 0);
+	    erase_lots(term, FALSE, TRUE, TRUE);
+	    break;
+	  case 5:		       /* DECSCNM: reverse video */
+	    /*
+	     * Toggle reverse video. If we receive an OFF within the
+	     * visual bell timeout period after an ON, we trigger an
+	     * effective visual bell, so that ESC[?5hESC[?5l will
+	     * always be an actually _visible_ visual bell.
+	     */
+	    if (term->rvideo && !state) {
+		/* This is an OFF, so set up a vbell */
+		term_schedule_vbell(term, TRUE, term->rvbell_startpoint);
+	    } else if (!term->rvideo && state) {
+		/* This is an ON, so we notice the time and save it. */
+		term->rvbell_startpoint = GETTICKCOUNT();
+	    }
+	    term->rvideo = state;
+	    seen_disp_event(term);
+	    break;
+	  case 6:		       /* DECOM: DEC origin mode */
+	    term->dec_om = state;
+	    break;
+	  case 7:		       /* DECAWM: auto wrap */
+	    term->wrap = state;
+	    break;
+	  case 8:		       /* DECARM: auto key repeat */
+	    term->repeat_off = !state;
+	    break;
+	  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);
+	    break;
+	  case 25:		       /* DECTCEM: enable/disable cursor */
+	    compatibility2(OTHER, VT220);
+	    term->cursor_on = state;
+	    seen_disp_event(term);
+	    break;
+	  case 47:		       /* alternate screen */
+	    compatibility(OTHER);
+	    deselect(term);
+	    swap_screen(term, term->no_alt_screen ? 0 : state, FALSE, FALSE);
+            if (term->scroll_on_disp)
+                term->disptop = 0;
+	    break;
+	  case 1000:		       /* xterm mouse 1 (normal) */
+	    term->xterm_mouse = state ? 1 : 0;
+	    set_raw_mouse_mode(term->frontend, state);
+	    break;
+	  case 1002:		       /* xterm mouse 2 (inc. button drags) */
+	    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);
+            if (term->scroll_on_disp)
+                term->disptop = 0;
+	    break;
+	  case 1048:                   /* save/restore cursor */
+	    if (!term->no_alt_screen)
+                save_cursor(term, state);
+	    if (!state) seen_disp_event(term);
+	    break;
+	  case 1049:                   /* cursor & alternate screen */
+	    if (state && !term->no_alt_screen)
+		save_cursor(term, state);
+	    if (!state) seen_disp_event(term);
+	    compatibility(OTHER);
+	    deselect(term);
+	    swap_screen(term, term->no_alt_screen ? 0 : state, TRUE, FALSE);
+	    if (!state && !term->no_alt_screen)
+		save_cursor(term, state);
+            if (term->scroll_on_disp)
+                term->disptop = 0;
+	    break;
+	  case 2004:		       /* xterm bracketed paste */
+	    term->bracketed_paste = state ? TRUE : FALSE;
+	    break;
+    } else
+	switch (mode) {
+	  case 4:		       /* IRM: set insert mode */
+	    compatibility(VT102);
+	    term->insert = state;
+	    break;
+	  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);
+	    break;
+	  case 20:		       /* LNM: Return sends ... */
+	    term->cr_lf_return = state;
+	    break;
+	  case 34:		       /* WYULCURM: Make cursor BIG */
+	    compatibility2(OTHER, VT220);
+	    term->big_cursor = !state;
+	}
+}
+
+/*
+ * Process an OSC sequence: set window title or icon name.
+ */
+static void do_osc(Terminal *term)
+{
+    if (term->osc_w) {
+	while (term->osc_strlen--)
+	    term->wordness[(unsigned char)
+		term->osc_string[term->osc_strlen]] = term->esc_args[0];
+    } else {
+	term->osc_string[term->osc_strlen] = '\0';
+	switch (term->esc_args[0]) {
+	  case 0:
+	  case 1:
+	    if (!term->no_remote_wintitle)
+		set_icon(term->frontend, term->osc_string);
+	    if (term->esc_args[0] == 1)
+		break;
+	    /* fall through: parameter 0 means set both */
+	  case 2:
+	  case 21:
+	    if (!term->no_remote_wintitle)
+		set_title(term->frontend, term->osc_string);
+	    break;
+	}
+    }
+}
+
+/*
+ * ANSI printing routines.
+ */
+static void term_print_setup(Terminal *term, char *printer)
+{
+    bufchain_clear(&term->printer_buf);
+    term->print_job = printer_start_job(printer);
+}
+static void term_print_flush(Terminal *term)
+{
+    void *data;
+    int len;
+    int size;
+    while ((size = bufchain_size(&term->printer_buf)) > 5) {
+	bufchain_prefix(&term->printer_buf, &data, &len);
+	if (len > size-5)
+	    len = size-5;
+	printer_job_data(term->print_job, data, len);
+	bufchain_consume(&term->printer_buf, len);
+    }
+}
+static void term_print_finish(Terminal *term)
+{
+    void *data;
+    int len, size;
+    char c;
+
+    if (!term->printing && !term->only_printing)
+	return;			       /* we need do nothing */
+
+    term_print_flush(term);
+    while ((size = bufchain_size(&term->printer_buf)) > 0) {
+	bufchain_prefix(&term->printer_buf, &data, &len);
+	c = *(char *)data;
+	if (c == '\033' || c == '\233') {
+	    bufchain_consume(&term->printer_buf, size);
+	    break;
+	} else {
+	    printer_job_data(term->print_job, &c, 1);
+	    bufchain_consume(&term->printer_buf, 1);
+	}
+    }
+    printer_finish_job(term->print_job);
+    term->print_job = NULL;
+    term->printing = term->only_printing = FALSE;
+}
+
+/*
+ * Remove everything currently in `inbuf' and stick it up on the
+ * in-memory display. There's a big state machine in here to
+ * process escape sequences...
+ */
+static void term_out(Terminal *term)
+{
+    unsigned long c;
+    int unget;
+    unsigned char localbuf[256], *chars;
+    int nchars = 0;
+
+    unget = -1;
+
+    chars = NULL;		       /* placate compiler warnings */
+    while (nchars > 0 || unget != -1 || bufchain_size(&term->inbuf) > 0) {
+	if (unget == -1) {
+	    if (nchars == 0) {
+		void *ret;
+		bufchain_prefix(&term->inbuf, &ret, &nchars);
+		if (nchars > sizeof(localbuf))
+		    nchars = sizeof(localbuf);
+		memcpy(localbuf, ret, nchars);
+		bufchain_consume(&term->inbuf, nchars);
+		chars = localbuf;
+		assert(chars != NULL);
+	    }
+	    c = *chars++;
+	    nchars--;
+
+	    /*
+	     * Optionally log the session traffic to a file. Useful for
+	     * debugging and possibly also useful for actual logging.
+	     */
+	    if (term->logtype == LGTYP_DEBUG && term->logctx)
+		logtraffic(term->logctx, (unsigned char) c, LGTYP_DEBUG);
+	} else {
+	    c = unget;
+	    unget = -1;
+	}
+
+	/* Note only VT220+ are 8-bit VT102 is seven bit, it shouldn't even
+	 * be able to display 8-bit characters, but I'll let that go 'cause
+	 * of i18n.
+	 */
+
+	/*
+	 * If we're printing, add the character to the printer
+	 * buffer.
+	 */
+	if (term->printing) {
+	    bufchain_add(&term->printer_buf, &c, 1);
+
+	    /*
+	     * If we're in print-only mode, we use a much simpler
+	     * state machine designed only to recognise the ESC[4i
+	     * termination sequence.
+	     */
+	    if (term->only_printing) {
+		if (c == '\033')
+		    term->print_state = 1;
+		else if (c == (unsigned char)'\233')
+		    term->print_state = 2;
+		else if (c == '[' && term->print_state == 1)
+		    term->print_state = 2;
+		else if (c == '4' && term->print_state == 2)
+		    term->print_state = 3;
+		else if (c == 'i' && term->print_state == 3)
+		    term->print_state = 4;
+		else
+		    term->print_state = 0;
+		if (term->print_state == 4) {
+		    term_print_finish(term);
+		}
+		continue;
+	    }
+	}
+
+	/* First see about all those translations. */
+	if (term->termstate == TOPLEVEL) {
+	    if (in_utf(term))
+		switch (term->utf_state) {
+		  case 0:
+		    if (c < 0x80) {
+			/* UTF-8 must be stateless so we ignore iso2022. */
+			if (term->ucsdata->unitab_ctrl[c] != 0xFF) 
+			     c = term->ucsdata->unitab_ctrl[c];
+			else c = ((unsigned char)c) | CSET_ASCII;
+			break;
+		    } else if ((c & 0xe0) == 0xc0) {
+			term->utf_size = term->utf_state = 1;
+			term->utf_char = (c & 0x1f);
+		    } else if ((c & 0xf0) == 0xe0) {
+			term->utf_size = term->utf_state = 2;
+			term->utf_char = (c & 0x0f);
+		    } else if ((c & 0xf8) == 0xf0) {
+			term->utf_size = term->utf_state = 3;
+			term->utf_char = (c & 0x07);
+		    } else if ((c & 0xfc) == 0xf8) {
+			term->utf_size = term->utf_state = 4;
+			term->utf_char = (c & 0x03);
+		    } else if ((c & 0xfe) == 0xfc) {
+			term->utf_size = term->utf_state = 5;
+			term->utf_char = (c & 0x01);
+		    } else {
+			c = UCSERR;
+			break;
+		    }
+		    continue;
+		  case 1:
+		  case 2:
+		  case 3:
+		  case 4:
+		  case 5:
+		    if ((c & 0xC0) != 0x80) {
+			unget = c;
+			c = UCSERR;
+			term->utf_state = 0;
+			break;
+		    }
+		    term->utf_char = (term->utf_char << 6) | (c & 0x3f);
+		    if (--term->utf_state)
+			continue;
+
+		    c = term->utf_char;
+
+		    /* Is somebody trying to be evil! */
+		    if (c < 0x80 ||
+			(c < 0x800 && term->utf_size >= 2) ||
+			(c < 0x10000 && term->utf_size >= 3) ||
+			(c < 0x200000 && term->utf_size >= 4) ||
+			(c < 0x4000000 && term->utf_size >= 5))
+			c = UCSERR;
+
+		    /* Unicode line separator and paragraph separator are CR-LF */
+		    if (c == 0x2028 || c == 0x2029)
+			c = 0x85;
+
+		    /* High controls are probably a Baaad idea too. */
+		    if (c < 0xA0)
+			c = 0xFFFD;
+
+		    /* The UTF-16 surrogates are not nice either. */
+		    /*       The standard give the option of decoding these: 
+		     *       I don't want to! */
+		    if (c >= 0xD800 && c < 0xE000)
+			c = UCSERR;
+
+		    /* ISO 10646 characters now limited to UTF-16 range. */
+		    if (c > 0x10FFFF)
+			c = UCSERR;
+
+		    /* This is currently a TagPhobic application.. */
+		    if (c >= 0xE0000 && c <= 0xE007F)
+			continue;
+
+		    /* U+FEFF is best seen as a null. */
+		    if (c == 0xFEFF)
+			continue;
+		    /* But U+FFFE is an error. */
+		    if (c == 0xFFFE || c == 0xFFFF)
+			c = UCSERR;
+
+		    break;
+	    }
+	    /* Are we in the nasty ACS mode? Note: no sco in utf mode. */
+	    else if(term->sco_acs && 
+		    (c!='\033' && c!='\012' && c!='\015' && c!='\b'))
+	    {
+	       if (term->sco_acs == 2) c |= 0x80;
+	       c |= CSET_SCOACS;
+	    } else {
+		switch (term->cset_attr[term->cset]) {
+		    /* 
+		     * Linedraw characters are different from 'ESC ( B'
+		     * only for a small range. For ones outside that
+		     * range, make sure we use the same font as well as
+		     * the same encoding.
+		     */
+		  case CSET_LINEDRW:
+		    if (term->ucsdata->unitab_ctrl[c] != 0xFF)
+			c = term->ucsdata->unitab_ctrl[c];
+		    else
+			c = ((unsigned char) c) | CSET_LINEDRW;
+		    break;
+
+		  case CSET_GBCHR:
+		    /* If UK-ASCII, make the '#' a LineDraw Pound */
+		    if (c == '#') {
+			c = '}' | CSET_LINEDRW;
+			break;
+		    }
+		  /*FALLTHROUGH*/ case CSET_ASCII:
+		    if (term->ucsdata->unitab_ctrl[c] != 0xFF)
+			c = term->ucsdata->unitab_ctrl[c];
+		    else
+			c = ((unsigned char) c) | CSET_ASCII;
+		    break;
+		case CSET_SCOACS:
+		    if (c>=' ') c = ((unsigned char)c) | CSET_SCOACS;
+		    break;
+		}
+	    }
+	}
+
+	/*
+	 * How about C1 controls? 
+	 * Explicitly ignore SCI (0x9a), which we don't translate to DECID.
+	 */
+	if ((c & -32) == 0x80 && term->termstate < DO_CTRLS &&
+	    !term->vt52_mode && has_compat(VT220)) {
+	    if (c == 0x9a)
+		c = 0;
+	    else {
+		term->termstate = SEEN_ESC;
+		term->esc_query = FALSE;
+		c = '@' + (c & 0x1F);
+	    }
+	}
+
+	/* Or the GL control. */
+	if (c == '\177' && term->termstate < DO_CTRLS && has_compat(OTHER)) {
+	    if (term->curs.x && !term->wrapnext)
+		term->curs.x--;
+	    term->wrapnext = FALSE;
+	    /* destructive backspace might be disabled */
+	    if (!term->no_dbackspace) {
+		check_boundary(term, term->curs.x, term->curs.y);
+		check_boundary(term, term->curs.x+1, term->curs.y);
+		copy_termchar(scrlineptr(term->curs.y),
+			      term->curs.x, &term->erase_char);
+	    }
+	} else
+	    /* Or normal C0 controls. */
+	if ((c & ~0x1F) == 0 && term->termstate < DO_CTRLS) {
+	    switch (c) {
+	      case '\005':	       /* ENQ: terminal type query */
+		/* 
+		 * Strictly speaking this is VT100 but a VT100 defaults to
+		 * no response. Other terminals respond at their option.
+		 *
+		 * Don't put a CR in the default string as this tends to
+		 * upset some weird software.
+		 */
+		compatibility(ANSIMIN);
+		if (term->ldisc) {
+		    lpage_send(term->ldisc, DEFAULT_CODEPAGE,
+			       term->answerback, term->answerbacklen, 0);
+		}
+		break;
+	      case '\007':	      /* BEL: Bell */
+		{
+		    struct beeptime *newbeep;
+		    unsigned long ticks;
+
+		    ticks = GETTICKCOUNT();
+
+		    if (!term->beep_overloaded) {
+			newbeep = snew(struct beeptime);
+			newbeep->ticks = ticks;
+			newbeep->next = NULL;
+			if (!term->beephead)
+			    term->beephead = newbeep;
+			else
+			    term->beeptail->next = newbeep;
+			term->beeptail = newbeep;
+			term->nbeeps++;
+		    }
+
+		    /*
+		     * Throw out any beeps that happened more than
+		     * t seconds ago.
+		     */
+		    while (term->beephead &&
+			   term->beephead->ticks < ticks - term->bellovl_t) {
+			struct beeptime *tmp = term->beephead;
+			term->beephead = tmp->next;
+			sfree(tmp);
+			if (!term->beephead)
+			    term->beeptail = NULL;
+			term->nbeeps--;
+		    }
+
+		    if (term->bellovl && term->beep_overloaded &&
+			ticks - term->lastbeep >= (unsigned)term->bellovl_s) {
+			/*
+			 * If we're currently overloaded and the
+			 * last beep was more than s seconds ago,
+			 * leave overload mode.
+			 */
+			term->beep_overloaded = FALSE;
+		    } else if (term->bellovl && !term->beep_overloaded &&
+			       term->nbeeps >= term->bellovl_n) {
+			/*
+			 * Now, if we have n or more beeps
+			 * remaining in the queue, go into overload
+			 * mode.
+			 */
+			term->beep_overloaded = TRUE;
+		    }
+		    term->lastbeep = ticks;
+
+		    /*
+		     * Perform an actual beep if we're not overloaded.
+		     */
+		    if (!term->bellovl || !term->beep_overloaded) {
+			do_beep(term->frontend, term->beep);
+
+			if (term->beep == BELL_VISUAL) {
+			    term_schedule_vbell(term, FALSE, 0);
+			}
+		    }
+		    seen_disp_event(term);
+		}
+		break;
+	      case '\b':	      /* BS: Back space */
+		if (term->curs.x == 0 &&
+		    (term->curs.y == 0 || term->wrap == 0))
+		    /* do nothing */ ;
+		else if (term->curs.x == 0 && term->curs.y > 0)
+		    term->curs.x = term->cols - 1, term->curs.y--;
+		else if (term->wrapnext)
+		    term->wrapnext = FALSE;
+		else
+		    term->curs.x--;
+		seen_disp_event(term);
+		break;
+	      case '\016':	      /* LS1: Locking-shift one */
+		compatibility(VT100);
+		term->cset = 1;
+		break;
+	      case '\017':	      /* LS0: Locking-shift zero */
+		compatibility(VT100);
+		term->cset = 0;
+		break;
+	      case '\033':	      /* ESC: Escape */
+		if (term->vt52_mode)
+		    term->termstate = VT52_ESC;
+		else {
+		    compatibility(ANSIMIN);
+		    term->termstate = SEEN_ESC;
+		    term->esc_query = FALSE;
+		}
+		break;
+	      case '\015':	      /* CR: Carriage return */
+		term->curs.x = 0;
+		term->wrapnext = FALSE;
+		seen_disp_event(term);
+
+		if (term->crhaslf) {
+		    if (term->curs.y == term->marg_b)
+			scroll(term, term->marg_t, term->marg_b, 1, TRUE);
+		    else if (term->curs.y < term->rows - 1)
+			term->curs.y++;
+		}
+		if (term->logctx)
+		    logtraffic(term->logctx, (unsigned char) c, LGTYP_ASCII);
+		break;
+	      case '\014':	      /* FF: Form feed */
+		if (has_compat(SCOANSI)) {
+		    move(term, 0, 0, 0);
+		    erase_lots(term, FALSE, FALSE, TRUE);
+                    if (term->scroll_on_disp)
+                        term->disptop = 0;
+		    term->wrapnext = FALSE;
+		    seen_disp_event(term);
+		    break;
+		}
+	      case '\013':	      /* VT: Line tabulation */
+		compatibility(VT100);
+	      case '\012':	      /* LF: Line feed */
+		if (term->curs.y == term->marg_b)
+		    scroll(term, term->marg_t, term->marg_b, 1, TRUE);
+		else if (term->curs.y < term->rows - 1)
+		    term->curs.y++;
+		if (term->lfhascr)
+		    term->curs.x = 0;
+		term->wrapnext = FALSE;
+		seen_disp_event(term);
+		if (term->logctx)
+		    logtraffic(term->logctx, (unsigned char) c, LGTYP_ASCII);
+		break;
+	      case '\t':	      /* HT: Character tabulation */
+		{
+		    pos old_curs = term->curs;
+		    termline *ldata = scrlineptr(term->curs.y);
+
+		    do {
+			term->curs.x++;
+		    } while (term->curs.x < term->cols - 1 &&
+			     !term->tabs[term->curs.x]);
+
+		    if ((ldata->lattr & LATTR_MODE) != LATTR_NORM) {
+			if (term->curs.x >= term->cols / 2)
+			    term->curs.x = term->cols / 2 - 1;
+		    } else {
+			if (term->curs.x >= term->cols)
+			    term->curs.x = term->cols - 1;
+		    }
+
+		    check_selection(term, old_curs, term->curs);
+		}
+		seen_disp_event(term);
+		break;
+	    }
+	} else
+	    switch (term->termstate) {
+	      case TOPLEVEL:
+		/* Only graphic characters get this far;
+		 * ctrls are stripped above */
+		{
+		    termline *cline = scrlineptr(term->curs.y);
+		    int width = 0;
+		    if (DIRECT_CHAR(c))
+			width = 1;
+		    if (!width)
+			width = (term->cjk_ambig_wide ?
+				 mk_wcwidth_cjk((unsigned int) c) :
+				 mk_wcwidth((unsigned int) c));
+
+		    if (term->wrapnext && term->wrap && width > 0) {
+			cline->lattr |= LATTR_WRAPPED;
+			if (term->curs.y == term->marg_b)
+			    scroll(term, term->marg_t, term->marg_b, 1, TRUE);
+			else if (term->curs.y < term->rows - 1)
+			    term->curs.y++;
+			term->curs.x = 0;
+			term->wrapnext = FALSE;
+			cline = scrlineptr(term->curs.y);
+		    }
+		    if (term->insert && width > 0)
+			insch(term, width);
+		    if (term->selstate != NO_SELECTION) {
+			pos cursplus = term->curs;
+			incpos(cursplus);
+			check_selection(term, term->curs, cursplus);
+		    }
+		    if (((c & CSET_MASK) == CSET_ASCII ||
+			 (c & CSET_MASK) == 0) &&
+			term->logctx)
+			logtraffic(term->logctx, (unsigned char) c,
+				   LGTYP_ASCII);
+
+		    switch (width) {
+		      case 2:
+			/*
+			 * If we're about to display a double-width
+			 * character starting in the rightmost
+			 * column, then we do something special
+			 * instead. We must print a space in the
+			 * last column of the screen, then wrap;
+			 * and we also set LATTR_WRAPPED2 which
+			 * instructs subsequent cut-and-pasting not
+			 * only to splice this line to the one
+			 * after it, but to ignore the space in the
+			 * last character position as well.
+			 * (Because what was actually output to the
+			 * terminal was presumably just a sequence
+			 * of CJK characters, and we don't want a
+			 * space to be pasted in the middle of
+			 * those just because they had the
+			 * misfortune to start in the wrong parity
+			 * column. xterm concurs.)
+			 */
+			check_boundary(term, term->curs.x, term->curs.y);
+			check_boundary(term, term->curs.x+2, term->curs.y);
+			if (term->curs.x == term->cols-1) {
+			    copy_termchar(cline, term->curs.x,
+					  &term->erase_char);
+			    cline->lattr |= LATTR_WRAPPED | LATTR_WRAPPED2;
+			    if (term->curs.y == term->marg_b)
+				scroll(term, term->marg_t, term->marg_b,
+				       1, TRUE);
+			    else if (term->curs.y < term->rows - 1)
+				term->curs.y++;
+			    term->curs.x = 0;
+			    cline = scrlineptr(term->curs.y);
+			    /* Now we must check_boundary again, of course. */
+			    check_boundary(term, term->curs.x, term->curs.y);
+			    check_boundary(term, term->curs.x+2, term->curs.y);
+			}
+
+			/* FULL-TERMCHAR */
+			clear_cc(cline, term->curs.x);
+			cline->chars[term->curs.x].chr = c;
+			cline->chars[term->curs.x].attr = term->curr_attr;
+
+			term->curs.x++;
+
+			/* FULL-TERMCHAR */
+			clear_cc(cline, term->curs.x);
+			cline->chars[term->curs.x].chr = UCSWIDE;
+			cline->chars[term->curs.x].attr = term->curr_attr;
+
+			break;
+		      case 1:
+			check_boundary(term, term->curs.x, term->curs.y);
+			check_boundary(term, term->curs.x+1, term->curs.y);
+
+			/* FULL-TERMCHAR */
+			clear_cc(cline, term->curs.x);
+			cline->chars[term->curs.x].chr = c;
+			cline->chars[term->curs.x].attr = term->curr_attr;
+
+			break;
+		      case 0:
+			if (term->curs.x > 0) {
+			    int x = term->curs.x - 1;
+
+			    /* If we're in wrapnext state, the character
+			     * to combine with is _here_, not to our left. */
+			    if (term->wrapnext)
+				x++;
+
+			    /*
+			     * If the previous character is
+			     * UCSWIDE, back up another one.
+			     */
+			    if (cline->chars[x].chr == UCSWIDE) {
+				assert(x > 0);
+				x--;
+			    }
+
+			    add_cc(cline, x, c);
+			    seen_disp_event(term);
+			}
+			continue;
+		      default:
+			continue;
+		    }
+		    term->curs.x++;
+		    if (term->curs.x == term->cols) {
+			term->curs.x--;
+			term->wrapnext = TRUE;
+			if (term->wrap && term->vt52_mode) {
+			    cline->lattr |= LATTR_WRAPPED;
+			    if (term->curs.y == term->marg_b)
+				scroll(term, term->marg_t, term->marg_b, 1, TRUE);
+			    else if (term->curs.y < term->rows - 1)
+				term->curs.y++;
+			    term->curs.x = 0;
+			    term->wrapnext = FALSE;
+			}
+		    }
+		    seen_disp_event(term);
+		}
+		break;
+
+	      case OSC_MAYBE_ST:
+		/*
+		 * This state is virtually identical to SEEN_ESC, with the
+		 * exception that we have an OSC sequence in the pipeline,
+		 * and _if_ we see a backslash, we process it.
+		 */
+		if (c == '\\') {
+		    do_osc(term);
+		    term->termstate = TOPLEVEL;
+		    break;
+		}
+		/* else fall through */
+	      case SEEN_ESC:
+		if (c >= ' ' && c <= '/') {
+		    if (term->esc_query)
+			term->esc_query = -1;
+		    else
+			term->esc_query = c;
+		    break;
+		}
+		term->termstate = TOPLEVEL;
+		switch (ANSI(c, term->esc_query)) {
+		  case '[':		/* enter CSI mode */
+		    term->termstate = SEEN_CSI;
+		    term->esc_nargs = 1;
+		    term->esc_args[0] = ARG_DEFAULT;
+		    term->esc_query = FALSE;
+		    break;
+		  case ']':		/* OSC: xterm escape sequences */
+		    /* Compatibility is nasty here, xterm, linux, decterm yuk! */
+		    compatibility(OTHER);
+		    term->termstate = SEEN_OSC;
+		    term->esc_args[0] = 0;
+		    break;
+		  case '7':		/* DECSC: save cursor */
+		    compatibility(VT100);
+		    save_cursor(term, TRUE);
+		    break;
+		  case '8':	 	/* DECRC: restore cursor */
+		    compatibility(VT100);
+		    save_cursor(term, FALSE);
+		    seen_disp_event(term);
+		    break;
+		  case '=':		/* DECKPAM: Keypad application mode */
+		    compatibility(VT100);
+		    term->app_keypad_keys = TRUE;
+		    break;
+		  case '>':		/* DECKPNM: Keypad numeric mode */
+		    compatibility(VT100);
+		    term->app_keypad_keys = FALSE;
+		    break;
+		  case 'D':	       /* IND: exactly equivalent to LF */
+		    compatibility(VT100);
+		    if (term->curs.y == term->marg_b)
+			scroll(term, term->marg_t, term->marg_b, 1, TRUE);
+		    else if (term->curs.y < term->rows - 1)
+			term->curs.y++;
+		    term->wrapnext = FALSE;
+		    seen_disp_event(term);
+		    break;
+		  case 'E':	       /* NEL: exactly equivalent to CR-LF */
+		    compatibility(VT100);
+		    term->curs.x = 0;
+		    if (term->curs.y == term->marg_b)
+			scroll(term, term->marg_t, term->marg_b, 1, TRUE);
+		    else if (term->curs.y < term->rows - 1)
+			term->curs.y++;
+		    term->wrapnext = FALSE;
+		    seen_disp_event(term);
+		    break;
+		  case 'M':	       /* RI: reverse index - backwards LF */
+		    compatibility(VT100);
+		    if (term->curs.y == term->marg_t)
+			scroll(term, term->marg_t, term->marg_b, -1, TRUE);
+		    else if (term->curs.y > 0)
+			term->curs.y--;
+		    term->wrapnext = FALSE;
+		    seen_disp_event(term);
+		    break;
+		  case 'Z':	       /* DECID: terminal type query */
+		    compatibility(VT100);
+		    if (term->ldisc)
+			ldisc_send(term->ldisc, term->id_string,
+				   strlen(term->id_string), 0);
+		    break;
+		  case 'c':	       /* RIS: restore power-on settings */
+		    compatibility(VT100);
+		    power_on(term, TRUE);
+		    if (term->ldisc)   /* cause ldisc to notice changes */
+			ldisc_send(term->ldisc, NULL, 0, 0);
+		    if (term->reset_132) {
+			if (!term->no_remote_resize)
+			    request_resize(term->frontend, 80, term->rows);
+			term->reset_132 = 0;
+		    }
+                    if (term->scroll_on_disp)
+                        term->disptop = 0;
+		    seen_disp_event(term);
+		    break;
+		  case 'H':	       /* HTS: set a tab */
+		    compatibility(VT100);
+		    term->tabs[term->curs.x] = TRUE;
+		    break;
+
+		  case ANSI('8', '#'):	/* DECALN: fills screen with Es :-) */
+		    compatibility(VT100);
+		    {
+			termline *ldata;
+			int i, j;
+			pos scrtop, scrbot;
+
+			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);
+				ldata->chars[j].chr = 'E';
+			    }
+			    ldata->lattr = LATTR_NORM;
+			}
+                        if (term->scroll_on_disp)
+                            term->disptop = 0;
+			seen_disp_event(term);
+			scrtop.x = scrtop.y = 0;
+			scrbot.x = 0;
+			scrbot.y = term->rows;
+			check_selection(term, scrtop, scrbot);
+		    }
+		    break;
+
+		  case ANSI('3', '#'):
+		  case ANSI('4', '#'):
+		  case ANSI('5', '#'):
+		  case ANSI('6', '#'):
+		    compatibility(VT100);
+		    {
+			int nlattr;
+			termline *ldata;
+
+			switch (ANSI(c, term->esc_query)) {
+			  case ANSI('3', '#'): /* DECDHL: 2*height, top */
+			    nlattr = LATTR_TOP;
+			    break;
+			  case ANSI('4', '#'): /* DECDHL: 2*height, bottom */
+			    nlattr = LATTR_BOT;
+			    break;
+			  case ANSI('5', '#'): /* DECSWL: normal */
+			    nlattr = LATTR_NORM;
+			    break;
+			  default: /* case ANSI('6', '#'): DECDWL: 2*width */
+			    nlattr = LATTR_WIDE;
+			    break;
+			}
+			ldata = scrlineptr(term->curs.y);
+                        check_line_size(term, ldata);
+                        ldata->lattr = nlattr;
+		    }
+		    break;
+		  /* GZD4: G0 designate 94-set */
+		  case ANSI('A', '('):
+		    compatibility(VT100);
+		    if (!term->no_remote_charset)
+			term->cset_attr[0] = CSET_GBCHR;
+		    break;
+		  case ANSI('B', '('):
+		    compatibility(VT100);
+		    if (!term->no_remote_charset)
+			term->cset_attr[0] = CSET_ASCII;
+		    break;
+		  case ANSI('0', '('):
+		    compatibility(VT100);
+		    if (!term->no_remote_charset)
+			term->cset_attr[0] = CSET_LINEDRW;
+		    break;
+		  case ANSI('U', '('): 
+		    compatibility(OTHER);
+		    if (!term->no_remote_charset)
+			term->cset_attr[0] = CSET_SCOACS; 
+		    break;
+		  /* G1D4: G1-designate 94-set */
+		  case ANSI('A', ')'):
+		    compatibility(VT100);
+		    if (!term->no_remote_charset)
+			term->cset_attr[1] = CSET_GBCHR;
+		    break;
+		  case ANSI('B', ')'):
+		    compatibility(VT100);
+		    if (!term->no_remote_charset)
+			term->cset_attr[1] = CSET_ASCII;
+		    break;
+		  case ANSI('0', ')'):
+		    compatibility(VT100);
+		    if (!term->no_remote_charset)
+			term->cset_attr[1] = CSET_LINEDRW;
+		    break;
+		  case ANSI('U', ')'): 
+		    compatibility(OTHER);
+		    if (!term->no_remote_charset)
+			term->cset_attr[1] = CSET_SCOACS; 
+		    break;
+		  /* DOCS: Designate other coding system */
+		  case ANSI('8', '%'):	/* Old Linux code */
+		  case ANSI('G', '%'):
+		    compatibility(OTHER);
+		    if (!term->no_remote_charset)
+			term->utf = 1;
+		    break;
+		  case ANSI('@', '%'):
+		    compatibility(OTHER);
+		    if (!term->no_remote_charset)
+			term->utf = 0;
+		    break;
+		}
+		break;
+	      case SEEN_CSI:
+		term->termstate = TOPLEVEL;  /* default */
+		if (isdigit(c)) {
+		    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';
+		    }
+		    term->termstate = SEEN_CSI;
+		} else if (c == ';') {
+		    if (term->esc_nargs < ARGS_MAX)
+			term->esc_args[term->esc_nargs++] = ARG_DEFAULT;
+		    term->termstate = SEEN_CSI;
+		} else if (c < '@') {
+		    if (term->esc_query)
+			term->esc_query = -1;
+		    else if (c == '?')
+			term->esc_query = TRUE;
+		    else
+			term->esc_query = c;
+		    term->termstate = SEEN_CSI;
+		} else
+		    switch (ANSI(c, term->esc_query)) {
+		      case 'A':       /* CUU: move up N lines */
+			move(term, term->curs.x,
+			     term->curs.y - def(term->esc_args[0], 1), 1);
+			seen_disp_event(term);
+			break;
+		      case 'e':		/* VPR: move down N lines */
+			compatibility(ANSI);
+			/* FALLTHROUGH */
+		      case 'B':		/* CUD: Cursor down */
+			move(term, term->curs.x,
+			     term->curs.y + def(term->esc_args[0], 1), 1);
+			seen_disp_event(term);
+			break;
+		      case ANSI('c', '>'):	/* DA: report xterm version */
+			compatibility(OTHER);
+			/* this reports xterm version 136 so that VIM can
+			   use the drag messages from the mouse reporting */
+			if (term->ldisc)
+			    ldisc_send(term->ldisc, "\033[>0;136;0c", 11, 0);
+			break;
+		      case 'a':		/* HPR: move right N cols */
+			compatibility(ANSI);
+			/* FALLTHROUGH */
+		      case 'C':		/* CUF: Cursor right */ 
+			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 */
+			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);
+			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);
+			move(term, 0,
+			     term->curs.y - def(term->esc_args[0], 1), 1);
+			seen_disp_event(term);
+			break;
+		      case 'G':	      /* CHA */
+		      case '`':       /* HPA: set horizontal posn */
+			compatibility(ANSI);
+			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);
+			move(term, term->curs.x,
+			     ((term->dec_om ? term->marg_t : 0) +
+			      def(term->esc_args[0], 1) - 1),
+			     (term->dec_om ? 2 : 0));
+			seen_disp_event(term);
+			break;
+		      case 'H':	     /* CUP */
+		      case 'f':      /* HVP: set horz and vert posns at once */
+			if (term->esc_nargs < 2)
+			    term->esc_args[1] = ARG_DEFAULT;
+			move(term, def(term->esc_args[1], 1) - 1,
+			     ((term->dec_om ? term->marg_t : 0) +
+			      def(term->esc_args[0], 1) - 1),
+			     (term->dec_om ? 2 : 0));
+			seen_disp_event(term);
+			break;
+		      case 'J':       /* ED: erase screen or parts of it */
+			{
+			    unsigned int i = def(term->esc_args[0], 0);
+			    if (i == 3) {
+				/* Erase Saved Lines (xterm)
+				 * This follows Thomas Dickey's xterm. */
+				term_clrsb(term);
+			    } else {
+				i++;
+				if (i > 3)
+				    i = 0;
+				erase_lots(term, FALSE, !!(i & 2), !!(i & 1));
+			    }
+			}
+			if (term->scroll_on_disp)
+                            term->disptop = 0;
+			seen_disp_event(term);
+			break;
+		      case 'K':       /* EL: erase line or parts of it */
+			{
+			    unsigned int i = def(term->esc_args[0], 0) + 1;
+			    if (i > 3)
+				i = 0;
+			    erase_lots(term, TRUE, !!(i & 2), !!(i & 1));
+			}
+			seen_disp_event(term);
+			break;
+		      case 'L':       /* IL: insert lines */
+			compatibility(VT102);
+			if (term->curs.y <= term->marg_b)
+			    scroll(term, term->curs.y, term->marg_b,
+				   -def(term->esc_args[0], 1), FALSE);
+			seen_disp_event(term);
+			break;
+		      case 'M':       /* DL: delete lines */
+			compatibility(VT102);
+			if (term->curs.y <= term->marg_b)
+			    scroll(term, term->curs.y, term->marg_b,
+				   def(term->esc_args[0], 1),
+				   TRUE);
+			seen_disp_event(term);
+			break;
+		      case '@':       /* ICH: insert chars */
+			/* XXX VTTEST says this is vt220, vt510 manual says vt102 */
+			compatibility(VT102);
+			insch(term, def(term->esc_args[0], 1));
+			seen_disp_event(term);
+			break;
+		      case 'P':       /* DCH: delete chars */
+			compatibility(VT102);
+			insch(term, -def(term->esc_args[0], 1));
+			seen_disp_event(term);
+			break;
+		      case 'c':       /* DA: terminal type query */
+			compatibility(VT100);
+			/* This is the response for a VT102 */
+			if (term->ldisc)
+			    ldisc_send(term->ldisc, term->id_string,
+ 				       strlen(term->id_string), 0);
+			break;
+		      case 'n':       /* DSR: cursor position query */
+			if (term->ldisc) {
+			    if (term->esc_args[0] == 6) {
+				char buf[32];
+				sprintf(buf, "\033[%d;%dR", term->curs.y + 1,
+					term->curs.x + 1);
+				ldisc_send(term->ldisc, buf, strlen(buf), 0);
+			    } else if (term->esc_args[0] == 5) {
+				ldisc_send(term->ldisc, "\033[0n", 4, 0);
+			    }
+			}
+			break;
+		      case 'h':       /* SM: toggle modes to high */
+		      case ANSI_QUE('h'):
+			compatibility(VT100);
+			{
+			    int i;
+			    for (i = 0; i < term->esc_nargs; i++)
+				toggle_mode(term, term->esc_args[i],
+					    term->esc_query, TRUE);
+			}
+			break;
+		      case 'i':		/* MC: Media copy */
+		      case ANSI_QUE('i'):
+			compatibility(VT100);
+			{
+			    char *printer;
+			    if (term->esc_nargs != 1) break;
+			    if (term->esc_args[0] == 5 && 
+				(printer = conf_get_str(term->conf,
+							CONF_printer))[0]) {
+				term->printing = TRUE;
+				term->only_printing = !term->esc_query;
+				term->print_state = 0;
+				term_print_setup(term, printer);
+			    } else if (term->esc_args[0] == 4 &&
+				       term->printing) {
+				term_print_finish(term);
+			    }
+			}
+			break;			
+		      case 'l':       /* RM: toggle modes to low */
+		      case ANSI_QUE('l'):
+			compatibility(VT100);
+			{
+			    int i;
+			    for (i = 0; i < term->esc_nargs; i++)
+				toggle_mode(term, term->esc_args[i],
+					    term->esc_query, FALSE);
+			}
+			break;
+		      case 'g':       /* TBC: clear tabs */
+			compatibility(VT100);
+			if (term->esc_nargs == 1) {
+			    if (term->esc_args[0] == 0) {
+				term->tabs[term->curs.x] = FALSE;
+			    } else if (term->esc_args[0] == 3) {
+				int i;
+				for (i = 0; i < term->cols; i++)
+				    term->tabs[i] = FALSE;
+			    }
+			}
+			break;
+		      case 'r':       /* DECSTBM: set scroll margins */
+			compatibility(VT100);
+			if (term->esc_nargs <= 2) {
+			    int top, bot;
+			    top = def(term->esc_args[0], 1) - 1;
+			    bot = (term->esc_nargs <= 1
+				   || term->esc_args[1] == 0 ?
+				   term->rows :
+				   def(term->esc_args[1], term->rows)) - 1;
+			    if (bot >= term->rows)
+				bot = term->rows - 1;
+			    /* VTTEST Bug 9 - if region is less than 2 lines
+			     * don't change region.
+			     */
+			    if (bot - top > 0) {
+				term->marg_t = top;
+				term->marg_b = bot;
+				term->curs.x = 0;
+				/*
+				 * I used to think the cursor should be
+				 * placed at the top of the newly marginned
+				 * area. Apparently not: VMS TPU falls over
+				 * if so.
+				 *
+				 * Well actually it should for
+				 * Origin mode - RDB
+				 */
+				term->curs.y = (term->dec_om ?
+						term->marg_t : 0);
+				seen_disp_event(term);
+			    }
+			}
+			break;
+		      case 'm':       /* SGR: set graphics rendition */
+			{
+			    /* 
+			     * A VT100 without the AVO only had one
+			     * attribute, either underline or
+			     * reverse video depending on the
+			     * cursor type, this was selected by
+			     * CSI 7m.
+			     *
+			     * case 2:
+			     *  This is sometimes DIM, eg on the
+			     *  GIGI and Linux
+			     * case 8:
+			     *  This is sometimes INVIS various ANSI.
+			     * case 21:
+			     *  This like 22 disables BOLD, DIM and INVIS
+			     *
+			     * The ANSI colours appear on any
+			     * terminal that has colour (obviously)
+			     * but the interaction between sgr0 and
+			     * the colours varies but is usually
+			     * related to the background colour
+			     * erase item. The interaction between
+			     * colour attributes and the mono ones
+			     * is also very implementation
+			     * dependent.
+			     *
+			     * The 39 and 49 attributes are likely
+			     * to be unimplemented.
+			     */
+			    int i;
+			    for (i = 0; i < term->esc_nargs; i++) {
+				switch (def(term->esc_args[i], 0)) {
+				  case 0:	/* restore defaults */
+				    term->curr_attr = term->default_attr;
+				    break;
+				  case 1:	/* enable bold */
+				    compatibility(VT100AVO);
+				    term->curr_attr |= ATTR_BOLD;
+				    break;
+				  case 21:	/* (enable double underline) */
+				    compatibility(OTHER);
+				  case 4:	/* enable underline */
+				    compatibility(VT100AVO);
+				    term->curr_attr |= ATTR_UNDER;
+				    break;
+				  case 5:	/* enable blink */
+				    compatibility(VT100AVO);
+				    term->curr_attr |= ATTR_BLINK;
+				    break;
+				  case 6:	/* SCO light bkgrd */
+				    compatibility(SCOANSI);
+				    term->blink_is_real = FALSE;
+				    term->curr_attr |= ATTR_BLINK;
+				    term_schedule_tblink(term);
+				    break;
+				  case 7:	/* enable reverse video */
+				    term->curr_attr |= ATTR_REVERSE;
+				    break;
+				  case 10:      /* SCO acs off */
+				    compatibility(SCOANSI);
+				    if (term->no_remote_charset) break;
+				    term->sco_acs = 0; break;
+				  case 11:      /* SCO acs on */
+				    compatibility(SCOANSI);
+				    if (term->no_remote_charset) break;
+				    term->sco_acs = 1; break;
+				  case 12:      /* SCO acs on, |0x80 */
+				    compatibility(SCOANSI);
+				    if (term->no_remote_charset) break;
+				    term->sco_acs = 2; break;
+				  case 22:	/* disable bold */
+				    compatibility2(OTHER, VT220);
+				    term->curr_attr &= ~ATTR_BOLD;
+				    break;
+				  case 24:	/* disable underline */
+				    compatibility2(OTHER, VT220);
+				    term->curr_attr &= ~ATTR_UNDER;
+				    break;
+				  case 25:	/* disable blink */
+				    compatibility2(OTHER, VT220);
+				    term->curr_attr &= ~ATTR_BLINK;
+				    break;
+				  case 27:	/* disable reverse video */
+				    compatibility2(OTHER, VT220);
+				    term->curr_attr &= ~ATTR_REVERSE;
+				    break;
+				  case 30:
+				  case 31:
+				  case 32:
+				  case 33:
+				  case 34:
+				  case 35:
+				  case 36:
+				  case 37:
+				    /* foreground */
+				    term->curr_attr &= ~ATTR_FGMASK;
+				    term->curr_attr |=
+					(term->esc_args[i] - 30)<<ATTR_FGSHIFT;
+				    break;
+				  case 90:
+				  case 91:
+				  case 92:
+				  case 93:
+				  case 94:
+				  case 95:
+				  case 96:
+				  case 97:
+				    /* aixterm-style bright foreground */
+				    term->curr_attr &= ~ATTR_FGMASK;
+				    term->curr_attr |=
+					((term->esc_args[i] - 90 + 8)
+                                         << ATTR_FGSHIFT);
+				    break;
+				  case 39:	/* default-foreground */
+				    term->curr_attr &= ~ATTR_FGMASK;
+				    term->curr_attr |= ATTR_DEFFG;
+				    break;
+				  case 40:
+				  case 41:
+				  case 42:
+				  case 43:
+				  case 44:
+				  case 45:
+				  case 46:
+				  case 47:
+				    /* background */
+				    term->curr_attr &= ~ATTR_BGMASK;
+				    term->curr_attr |=
+					(term->esc_args[i] - 40)<<ATTR_BGSHIFT;
+				    break;
+				  case 100:
+				  case 101:
+				  case 102:
+				  case 103:
+				  case 104:
+				  case 105:
+				  case 106:
+				  case 107:
+				    /* aixterm-style bright background */
+				    term->curr_attr &= ~ATTR_BGMASK;
+				    term->curr_attr |=
+					((term->esc_args[i] - 100 + 8)
+                                         << ATTR_BGSHIFT);
+				    break;
+				  case 49:	/* default-background */
+				    term->curr_attr &= ~ATTR_BGMASK;
+				    term->curr_attr |= ATTR_DEFBG;
+				    break;
+				  case 38:   /* xterm 256-colour mode */
+				    if (i+2 < term->esc_nargs &&
+					term->esc_args[i+1] == 5) {
+					term->curr_attr &= ~ATTR_FGMASK;
+					term->curr_attr |=
+					    ((term->esc_args[i+2] & 0xFF)
+					     << ATTR_FGSHIFT);
+					i += 2;
+				    }
+				    break;
+				  case 48:   /* xterm 256-colour mode */
+				    if (i+2 < term->esc_nargs &&
+					term->esc_args[i+1] == 5) {
+					term->curr_attr &= ~ATTR_BGMASK;
+					term->curr_attr |=
+					    ((term->esc_args[i+2] & 0xFF)
+					     << ATTR_BGSHIFT);
+					i += 2;
+				    }
+				    break;
+				}
+			    }
+			    set_erase_char(term);
+			}
+			break;
+		      case 's':       /* save cursor */
+			save_cursor(term, TRUE);
+			break;
+		      case 'u':       /* restore cursor */
+			save_cursor(term, FALSE);
+			seen_disp_event(term);
+			break;
+		      case 't': /* DECSLPP: set page size - ie window height */
+			/*
+			 * VT340/VT420 sequence DECSLPP, DEC only allows values
+			 *  24/25/36/48/72/144 other emulators (eg dtterm) use
+			 * illegal values (eg first arg 1..9) for window changing 
+			 * and reports.
+			 */
+			if (term->esc_nargs <= 1
+			    && (term->esc_args[0] < 1 ||
+				term->esc_args[0] >= 24)) {
+			    compatibility(VT340TEXT);
+			    if (!term->no_remote_resize)
+				request_resize(term->frontend, term->cols,
+					       def(term->esc_args[0], 24));
+			    deselect(term);
+			} else if (term->esc_nargs >= 1 &&
+				   term->esc_args[0] >= 1 &&
+				   term->esc_args[0] < 24) {
+			    compatibility(OTHER);
+
+			    switch (term->esc_args[0]) {
+				int x, y, len;
+				char buf[80], *p;
+			      case 1:
+				set_iconic(term->frontend, FALSE);
+				break;
+			      case 2:
+				set_iconic(term->frontend, TRUE);
+				break;
+			      case 3:
+				if (term->esc_nargs >= 3) {
+				    if (!term->no_remote_resize)
+					move_window(term->frontend,
+						    def(term->esc_args[1], 0),
+						    def(term->esc_args[2], 0));
+				}
+				break;
+			      case 4:
+				/* We should resize the window to a given
+				 * size in pixels here, but currently our
+				 * resizing code isn't healthy enough to
+				 * manage it. */
+				break;
+			      case 5:
+				/* move to top */
+				set_zorder(term->frontend, TRUE);
+				break;
+			      case 6:
+				/* move to bottom */
+				set_zorder(term->frontend, FALSE);
+				break;
+			      case 7:
+				refresh_window(term->frontend);
+				break;
+			      case 8:
+				if (term->esc_nargs >= 3) {
+				    if (!term->no_remote_resize)
+					request_resize(term->frontend,
+						       def(term->esc_args[2], term->conf_width),
+						       def(term->esc_args[1], term->conf_height));
+				}
+				break;
+			      case 9:
+				if (term->esc_nargs >= 2)
+				    set_zoomed(term->frontend,
+					       term->esc_args[1] ?
+					       TRUE : FALSE);
+				break;
+			      case 11:
+				if (term->ldisc)
+				    ldisc_send(term->ldisc,
+					       is_iconic(term->frontend) ?
+					       "\033[2t" : "\033[1t", 4, 0);
+				break;
+			      case 13:
+				if (term->ldisc) {
+				    get_window_pos(term->frontend, &x, &y);
+				    len = sprintf(buf, "\033[3;%u;%ut",
+                                                  (unsigned)x,
+                                                  (unsigned)y);
+				    ldisc_send(term->ldisc, buf, len, 0);
+				}
+				break;
+			      case 14:
+				if (term->ldisc) {
+				    get_window_pixels(term->frontend, &x, &y);
+				    len = sprintf(buf, "\033[4;%d;%dt", y, x);
+				    ldisc_send(term->ldisc, buf, len, 0);
+				}
+				break;
+			      case 18:
+				if (term->ldisc) {
+				    len = sprintf(buf, "\033[8;%d;%dt",
+						  term->rows, term->cols);
+				    ldisc_send(term->ldisc, buf, len, 0);
+				}
+				break;
+			      case 19:
+				/*
+				 * Hmmm. Strictly speaking we
+				 * should return `the size of the
+				 * screen in characters', but
+				 * that's not easy: (a) window
+				 * furniture being what it is it's
+				 * hard to compute, and (b) in
+				 * resize-font mode maximising the
+				 * window wouldn't change the
+				 * number of characters. *shrug*. I
+				 * think we'll ignore it for the
+				 * moment and see if anyone
+				 * complains, and then ask them
+				 * what they would like it to do.
+				 */
+				break;
+			      case 20:
+				if (term->ldisc &&
+				    term->remote_qtitle_action != TITLE_NONE) {
+				    if(term->remote_qtitle_action == TITLE_REAL)
+					p = get_window_title(term->frontend, TRUE);
+				    else
+					p = EMPTY_WINDOW_TITLE;
+				    len = strlen(p);
+				    ldisc_send(term->ldisc, "\033]L", 3, 0);
+				    ldisc_send(term->ldisc, p, len, 0);
+				    ldisc_send(term->ldisc, "\033\\", 2, 0);
+				}
+				break;
+			      case 21:
+				if (term->ldisc &&
+				    term->remote_qtitle_action != TITLE_NONE) {
+				    if(term->remote_qtitle_action == TITLE_REAL)
+					p = get_window_title(term->frontend, FALSE);
+				    else
+					p = EMPTY_WINDOW_TITLE;
+				    len = strlen(p);
+				    ldisc_send(term->ldisc, "\033]l", 3, 0);
+				    ldisc_send(term->ldisc, p, len, 0);
+				    ldisc_send(term->ldisc, "\033\\", 2, 0);
+				}
+				break;
+			    }
+			}
+			break;
+		      case 'S':		/* SU: Scroll up */
+			compatibility(SCOANSI);
+			scroll(term, term->marg_t, term->marg_b,
+			       def(term->esc_args[0], 1), TRUE);
+			term->wrapnext = FALSE;
+			seen_disp_event(term);
+			break;
+		      case 'T':		/* SD: Scroll down */
+			compatibility(SCOANSI);
+			scroll(term, term->marg_t, term->marg_b,
+			       -def(term->esc_args[0], 1), TRUE);
+			term->wrapnext = FALSE;
+			seen_disp_event(term);
+			break;
+		      case ANSI('|', '*'): /* DECSNLS */
+			/* 
+			 * Set number of lines on screen
+			 * VT420 uses VGA like hardware and can
+			 * support any size in reasonable range
+			 * (24..49 AIUI) with no default specified.
+			 */
+			compatibility(VT420);
+			if (term->esc_nargs == 1 && term->esc_args[0] > 0) {
+			    if (!term->no_remote_resize)
+				request_resize(term->frontend, term->cols,
+					       def(term->esc_args[0],
+						   term->conf_height));
+			    deselect(term);
+			}
+			break;
+		      case ANSI('|', '$'): /* DECSCPP */
+			/*
+			 * Set number of columns per page
+			 * Docs imply range is only 80 or 132, but
+			 * I'll allow any.
+			 */
+			compatibility(VT340TEXT);
+			if (term->esc_nargs <= 1) {
+			    if (!term->no_remote_resize)
+				request_resize(term->frontend,
+					       def(term->esc_args[0],
+						   term->conf_width),
+					       term->rows);
+			    deselect(term);
+			}
+			break;
+		      case 'X':     /* ECH: write N spaces w/o moving cursor */
+			/* XXX VTTEST says this is vt220, vt510 manual
+			 * says vt100 */
+			compatibility(ANSIMIN);
+			{
+			    int n = def(term->esc_args[0], 1);
+			    pos cursplus;
+			    int p = term->curs.x;
+			    termline *cline = scrlineptr(term->curs.y);
+
+			    if (n > term->cols - term->curs.x)
+				n = term->cols - term->curs.x;
+			    cursplus = term->curs;
+			    cursplus.x += n;
+			    check_boundary(term, term->curs.x, term->curs.y);
+			    check_boundary(term, term->curs.x+n, term->curs.y);
+			    check_selection(term, term->curs, cursplus);
+			    while (n--)
+				copy_termchar(cline, p++,
+					      &term->erase_char);
+			    seen_disp_event(term);
+			}
+			break;
+		      case 'x':       /* DECREQTPARM: report terminal characteristics */
+			compatibility(VT100);
+			if (term->ldisc) {
+			    char buf[32];
+			    int i = def(term->esc_args[0], 0);
+			    if (i == 0 || i == 1) {
+				strcpy(buf, "\033[2;1;1;112;112;1;0x");
+				buf[2] += i;
+				ldisc_send(term->ldisc, buf, 20, 0);
+			    }
+			}
+			break;
+		      case 'Z':		/* CBT */
+			compatibility(OTHER);
+			{
+			    int i = def(term->esc_args[0], 1);
+			    pos old_curs = term->curs;
+
+			    for(;i>0 && term->curs.x>0; i--) {
+				do {
+				    term->curs.x--;
+				} while (term->curs.x >0 &&
+					 !term->tabs[term->curs.x]);
+			    }
+			    check_selection(term, old_curs, term->curs);
+			}
+			break;
+		      case ANSI('c', '='):      /* Hide or Show Cursor */
+			compatibility(SCOANSI);
+			switch(term->esc_args[0]) {
+			  case 0:  /* hide cursor */
+			    term->cursor_on = FALSE;
+			    break;
+			  case 1:  /* restore cursor */
+			    term->big_cursor = FALSE;
+			    term->cursor_on = TRUE;
+			    break;
+			  case 2:  /* block cursor */
+			    term->big_cursor = TRUE;
+			    term->cursor_on = TRUE;
+			    break;
+			}
+			break;
+		      case ANSI('C', '='):
+			/*
+			 * set cursor start on scanline esc_args[0] and
+			 * end on scanline esc_args[1].If you set
+			 * the bottom scan line to a value less than
+			 * the top scan line, the cursor will disappear.
+			 */
+			compatibility(SCOANSI);
+			if (term->esc_nargs >= 2) {
+			    if (term->esc_args[0] > term->esc_args[1])
+				term->cursor_on = FALSE;
+			    else
+				term->cursor_on = TRUE;
+			}
+			break;
+		      case ANSI('D', '='):
+			compatibility(SCOANSI);
+			term->blink_is_real = FALSE;
+			term_schedule_tblink(term);
+			if (term->esc_args[0]>=1)
+			    term->curr_attr |= ATTR_BLINK;
+			else
+			    term->curr_attr &= ~ATTR_BLINK;
+			break;
+		      case ANSI('E', '='):
+			compatibility(SCOANSI);
+			term->blink_is_real = (term->esc_args[0] >= 1);
+			term_schedule_tblink(term);
+			break;
+		      case ANSI('F', '='):      /* set normal foreground */
+			compatibility(SCOANSI);
+			if (term->esc_args[0] >= 0 && term->esc_args[0] < 16) {
+			    long colour =
+ 				(sco2ansicolour[term->esc_args[0] & 0x7] |
+				 (term->esc_args[0] & 0x8)) <<
+				ATTR_FGSHIFT;
+			    term->curr_attr &= ~ATTR_FGMASK;
+			    term->curr_attr |= colour;
+			    term->default_attr &= ~ATTR_FGMASK;
+			    term->default_attr |= colour;
+			    set_erase_char(term);
+			}
+			break;
+		      case ANSI('G', '='):      /* set normal background */
+			compatibility(SCOANSI);
+			if (term->esc_args[0] >= 0 && term->esc_args[0] < 16) {
+			    long colour =
+ 				(sco2ansicolour[term->esc_args[0] & 0x7] |
+				 (term->esc_args[0] & 0x8)) <<
+				ATTR_BGSHIFT;
+			    term->curr_attr &= ~ATTR_BGMASK;
+			    term->curr_attr |= colour;
+			    term->default_attr &= ~ATTR_BGMASK;
+			    term->default_attr |= colour;
+			    set_erase_char(term);
+			}
+			break;
+		      case ANSI('L', '='):
+			compatibility(SCOANSI);
+			term->use_bce = (term->esc_args[0] <= 0);
+			set_erase_char(term);
+			break;
+		      case ANSI('p', '"'): /* DECSCL: set compat level */
+			/*
+			 * Allow the host to make this emulator a
+			 * 'perfect' VT102. This first appeared in
+			 * the VT220, but we do need to get back to
+			 * PuTTY mode so I won't check it.
+			 *
+			 * The arg in 40..42,50 are a PuTTY extension.
+			 * The 2nd arg, 8bit vs 7bit is not checked.
+			 *
+			 * Setting VT102 mode should also change
+			 * the Fkeys to generate PF* codes as a
+			 * real VT102 has no Fkeys. The VT220 does
+			 * this, F11..F13 become ESC,BS,LF other
+			 * Fkeys send nothing.
+			 *
+			 * Note ESC c will NOT change this!
+			 */
+
+			switch (term->esc_args[0]) {
+			  case 61:
+			    term->compatibility_level &= ~TM_VTXXX;
+			    term->compatibility_level |= TM_VT102;
+			    break;
+			  case 62:
+			    term->compatibility_level &= ~TM_VTXXX;
+			    term->compatibility_level |= TM_VT220;
+			    break;
+
+			  default:
+			    if (term->esc_args[0] > 60 &&
+				term->esc_args[0] < 70)
+				term->compatibility_level |= TM_VTXXX;
+			    break;
+
+			  case 40:
+			    term->compatibility_level &= TM_VTXXX;
+			    break;
+			  case 41:
+			    term->compatibility_level = TM_PUTTY;
+			    break;
+			  case 42:
+			    term->compatibility_level = TM_SCOANSI;
+			    break;
+
+			  case ARG_DEFAULT:
+			    term->compatibility_level = TM_PUTTY;
+			    break;
+			  case 50:
+			    break;
+			}
+
+			/* Change the response to CSI c */
+			if (term->esc_args[0] == 50) {
+			    int i;
+			    char lbuf[64];
+			    strcpy(term->id_string, "\033[?");
+			    for (i = 1; i < term->esc_nargs; i++) {
+				if (i != 1)
+				    strcat(term->id_string, ";");
+				sprintf(lbuf, "%d", term->esc_args[i]);
+				strcat(term->id_string, lbuf);
+			    }
+			    strcat(term->id_string, "c");
+			}
+#if 0
+			/* Is this a good idea ? 
+			 * Well we should do a soft reset at this point ...
+			 */
+			if (!has_compat(VT420) && has_compat(VT100)) {
+			    if (!term->no_remote_resize) {
+				if (term->reset_132)
+				    request_resize(132, 24);
+				else
+				    request_resize(80, 24);
+			    }
+			}
+#endif
+			break;
+		    }
+		break;
+	      case SEEN_OSC:
+		term->osc_w = FALSE;
+		switch (c) {
+		  case 'P':	       /* Linux palette sequence */
+		    term->termstate = SEEN_OSC_P;
+		    term->osc_strlen = 0;
+		    break;
+		  case 'R':	       /* Linux palette reset */
+		    palette_reset(term->frontend);
+		    term_invalidate(term);
+		    term->termstate = TOPLEVEL;
+		    break;
+		  case 'W':	       /* word-set */
+		    term->termstate = SEEN_OSC_W;
+		    term->osc_w = TRUE;
+		    break;
+		  case '0':
+		  case '1':
+		  case '2':
+		  case '3':
+		  case '4':
+		  case '5':
+		  case '6':
+		  case '7':
+		  case '8':
+		  case '9':
+		    term->esc_args[0] = 10 * term->esc_args[0] + c - '0';
+		    break;
+		  case 'L':
+		    /*
+		     * Grotty hack to support xterm and DECterm title
+		     * sequences concurrently.
+		     */
+		    if (term->esc_args[0] == 2) {
+			term->esc_args[0] = 1;
+			break;
+		    }
+		    /* else fall through */
+		  default:
+		    term->termstate = OSC_STRING;
+		    term->osc_strlen = 0;
+		}
+		break;
+	      case OSC_STRING:
+		/*
+		 * This OSC stuff is EVIL. It takes just one character to get into
+		 * sysline mode and it's not initially obvious how to get out.
+		 * So I've added CR and LF as string aborts.
+		 * This shouldn't effect compatibility as I believe embedded 
+		 * control characters are supposed to be interpreted (maybe?) 
+		 * and they don't display anything useful anyway.
+		 *
+		 * -- RDB
+		 */
+		if (c == '\012' || c == '\015') {
+		    term->termstate = TOPLEVEL;
+		} else if (c == 0234 || c == '\007') {
+		    /*
+		     * These characters terminate the string; ST and BEL
+		     * terminate the sequence and trigger instant
+		     * processing of it, whereas ESC goes back to SEEN_ESC
+		     * mode unless it is followed by \, in which case it is
+		     * synonymous with ST in the first place.
+		     */
+		    do_osc(term);
+		    term->termstate = TOPLEVEL;
+		} else if (c == '\033')
+		    term->termstate = OSC_MAYBE_ST;
+		else if (term->osc_strlen < OSC_STR_MAX)
+		    term->osc_string[term->osc_strlen++] = (char)c;
+		break;
+	      case SEEN_OSC_P:
+		{
+		    int max = (term->osc_strlen == 0 ? 21 : 15);
+		    int val;
+		    if ((int)c >= '0' && (int)c <= '9')
+			val = c - '0';
+		    else if ((int)c >= 'A' && (int)c <= 'A' + max - 10)
+			val = c - 'A' + 10;
+		    else if ((int)c >= 'a' && (int)c <= 'a' + max - 10)
+			val = c - 'a' + 10;
+		    else {
+			term->termstate = TOPLEVEL;
+			break;
+		    }
+		    term->osc_string[term->osc_strlen++] = val;
+		    if (term->osc_strlen >= 7) {
+			palette_set(term->frontend, term->osc_string[0],
+				    term->osc_string[1] * 16 + term->osc_string[2],
+				    term->osc_string[3] * 16 + term->osc_string[4],
+				    term->osc_string[5] * 16 + term->osc_string[6]);
+			term_invalidate(term);
+			term->termstate = TOPLEVEL;
+		    }
+		}
+		break;
+	      case SEEN_OSC_W:
+		switch (c) {
+		  case '0':
+		  case '1':
+		  case '2':
+		  case '3':
+		  case '4':
+		  case '5':
+		  case '6':
+		  case '7':
+		  case '8':
+		  case '9':
+		    term->esc_args[0] = 10 * term->esc_args[0] + c - '0';
+		    break;
+		  default:
+		    term->termstate = OSC_STRING;
+		    term->osc_strlen = 0;
+		}
+		break;
+	      case VT52_ESC:
+		term->termstate = TOPLEVEL;
+		seen_disp_event(term);
+		switch (c) {
+		  case 'A':
+		    move(term, term->curs.x, term->curs.y - 1, 1);
+		    break;
+		  case 'B':
+		    move(term, term->curs.x, term->curs.y + 1, 1);
+		    break;
+		  case 'C':
+		    move(term, term->curs.x + 1, term->curs.y, 1);
+		    break;
+		  case 'D':
+		    move(term, term->curs.x - 1, term->curs.y, 1);
+		    break;
+		    /*
+		     * From the VT100 Manual
+		     * NOTE: The special graphics characters in the VT100
+		     *       are different from those in the VT52
+		     *
+		     * From VT102 manual:
+		     *       137 _  Blank             - Same
+		     *       140 `  Reserved          - Humm.
+		     *       141 a  Solid rectangle   - Similar
+		     *       142 b  1/                - Top half of fraction for the
+		     *       143 c  3/                - subscript numbers below.
+		     *       144 d  5/
+		     *       145 e  7/
+		     *       146 f  Degrees           - Same
+		     *       147 g  Plus or minus     - Same
+		     *       150 h  Right arrow
+		     *       151 i  Ellipsis (dots)
+		     *       152 j  Divide by
+		     *       153 k  Down arrow
+		     *       154 l  Bar at scan 0
+		     *       155 m  Bar at scan 1
+		     *       156 n  Bar at scan 2
+		     *       157 o  Bar at scan 3     - Similar
+		     *       160 p  Bar at scan 4     - Similar
+		     *       161 q  Bar at scan 5     - Similar
+		     *       162 r  Bar at scan 6     - Same
+		     *       163 s  Bar at scan 7     - Similar
+		     *       164 t  Subscript 0
+		     *       165 u  Subscript 1
+		     *       166 v  Subscript 2
+		     *       167 w  Subscript 3
+		     *       170 x  Subscript 4
+		     *       171 y  Subscript 5
+		     *       172 z  Subscript 6
+		     *       173 {  Subscript 7
+		     *       174 |  Subscript 8
+		     *       175 }  Subscript 9
+		     *       176 ~  Paragraph
+		     *
+		     */
+		  case 'F':
+		    term->cset_attr[term->cset = 0] = CSET_LINEDRW;
+		    break;
+		  case 'G':
+		    term->cset_attr[term->cset = 0] = CSET_ASCII;
+		    break;
+		  case 'H':
+		    move(term, 0, 0, 0);
+		    break;
+		  case 'I':
+		    if (term->curs.y == 0)
+			scroll(term, 0, term->rows - 1, -1, TRUE);
+		    else if (term->curs.y > 0)
+			term->curs.y--;
+		    term->wrapnext = FALSE;
+		    break;
+		  case 'J':
+		    erase_lots(term, FALSE, FALSE, TRUE);
+                    if (term->scroll_on_disp)
+                        term->disptop = 0;
+		    break;
+		  case 'K':
+		    erase_lots(term, TRUE, FALSE, TRUE);
+		    break;
+#if 0
+		  case 'V':
+		    /* XXX Print cursor line */
+		    break;
+		  case 'W':
+		    /* XXX Start controller mode */
+		    break;
+		  case 'X':
+		    /* XXX Stop controller mode */
+		    break;
+#endif
+		  case 'Y':
+		    term->termstate = VT52_Y1;
+		    break;
+		  case 'Z':
+		    if (term->ldisc)
+			ldisc_send(term->ldisc, "\033/Z", 3, 0);
+		    break;
+		  case '=':
+		    term->app_keypad_keys = TRUE;
+		    break;
+		  case '>':
+		    term->app_keypad_keys = FALSE;
+		    break;
+		  case '<':
+		    /* XXX This should switch to VT100 mode not current or default
+		     *     VT mode. But this will only have effect in a VT220+
+		     *     emulation.
+		     */
+		    term->vt52_mode = FALSE;
+		    term->blink_is_real = term->blinktext;
+		    term_schedule_tblink(term);
+		    break;
+#if 0
+		  case '^':
+		    /* XXX Enter auto print mode */
+		    break;
+		  case '_':
+		    /* XXX Exit auto print mode */
+		    break;
+		  case ']':
+		    /* XXX Print screen */
+		    break;
+#endif
+
+#ifdef VT52_PLUS
+		  case 'E':
+		    /* compatibility(ATARI) */
+		    move(term, 0, 0, 0);
+		    erase_lots(term, FALSE, FALSE, TRUE);
+                    if (term->scroll_on_disp)
+                        term->disptop = 0;
+		    break;
+		  case 'L':
+		    /* compatibility(ATARI) */
+		    if (term->curs.y <= term->marg_b)
+			scroll(term, term->curs.y, term->marg_b, -1, FALSE);
+		    break;
+		  case 'M':
+		    /* compatibility(ATARI) */
+		    if (term->curs.y <= term->marg_b)
+			scroll(term, term->curs.y, term->marg_b, 1, TRUE);
+		    break;
+		  case 'b':
+		    /* compatibility(ATARI) */
+		    term->termstate = VT52_FG;
+		    break;
+		  case 'c':
+		    /* compatibility(ATARI) */
+		    term->termstate = VT52_BG;
+		    break;
+		  case 'd':
+		    /* compatibility(ATARI) */
+		    erase_lots(term, FALSE, TRUE, FALSE);
+                    if (term->scroll_on_disp)
+                        term->disptop = 0;
+		    break;
+		  case 'e':
+		    /* compatibility(ATARI) */
+		    term->cursor_on = TRUE;
+		    break;
+		  case 'f':
+		    /* compatibility(ATARI) */
+		    term->cursor_on = FALSE;
+		    break;
+		    /* case 'j': Save cursor position - broken on ST */
+		    /* case 'k': Restore cursor position */
+		  case 'l':
+		    /* compatibility(ATARI) */
+		    erase_lots(term, TRUE, TRUE, TRUE);
+		    term->curs.x = 0;
+		    term->wrapnext = FALSE;
+		    break;
+		  case 'o':
+		    /* compatibility(ATARI) */
+		    erase_lots(term, TRUE, TRUE, FALSE);
+		    break;
+		  case 'p':
+		    /* compatibility(ATARI) */
+		    term->curr_attr |= ATTR_REVERSE;
+		    break;
+		  case 'q':
+		    /* compatibility(ATARI) */
+		    term->curr_attr &= ~ATTR_REVERSE;
+		    break;
+		  case 'v':	       /* wrap Autowrap on - Wyse style */
+		    /* compatibility(ATARI) */
+		    term->wrap = 1;
+		    break;
+		  case 'w':	       /* Autowrap off */
+		    /* compatibility(ATARI) */
+		    term->wrap = 0;
+		    break;
+
+		  case 'R':
+		    /* compatibility(OTHER) */
+		    term->vt52_bold = FALSE;
+		    term->curr_attr = ATTR_DEFAULT;
+		    set_erase_char(term);
+		    break;
+		  case 'S':
+		    /* compatibility(VI50) */
+		    term->curr_attr |= ATTR_UNDER;
+		    break;
+		  case 'W':
+		    /* compatibility(VI50) */
+		    term->curr_attr &= ~ATTR_UNDER;
+		    break;
+		  case 'U':
+		    /* compatibility(VI50) */
+		    term->vt52_bold = TRUE;
+		    term->curr_attr |= ATTR_BOLD;
+		    break;
+		  case 'T':
+		    /* compatibility(VI50) */
+		    term->vt52_bold = FALSE;
+		    term->curr_attr &= ~ATTR_BOLD;
+		    break;
+#endif
+		}
+		break;
+	      case VT52_Y1:
+		term->termstate = VT52_Y2;
+		move(term, term->curs.x, c - ' ', 0);
+		break;
+	      case VT52_Y2:
+		term->termstate = TOPLEVEL;
+		move(term, c - ' ', term->curs.y, 0);
+		break;
+
+#ifdef VT52_PLUS
+	      case VT52_FG:
+		term->termstate = TOPLEVEL;
+		term->curr_attr &= ~ATTR_FGMASK;
+		term->curr_attr &= ~ATTR_BOLD;
+		term->curr_attr |= (c & 0xF) << ATTR_FGSHIFT;
+		set_erase_char(term);
+		break;
+	      case VT52_BG:
+		term->termstate = TOPLEVEL;
+		term->curr_attr &= ~ATTR_BGMASK;
+		term->curr_attr &= ~ATTR_BLINK;
+		term->curr_attr |= (c & 0xF) << ATTR_BGSHIFT;
+		set_erase_char(term);
+		break;
+#endif
+	      default: break;	       /* placate gcc warning about enum use */
+	    }
+	if (term->selstate != NO_SELECTION) {
+	    pos cursplus = term->curs;
+	    incpos(cursplus);
+	    check_selection(term, term->curs, cursplus);
+	}
+    }
+
+    term_print_flush(term);
+    if (term->logflush)
+	logflush(term->logctx);
+}
+
+/*
+ * To prevent having to run the reasonably tricky bidi algorithm
+ * too many times, we maintain a cache of the last lineful of data
+ * fed to the algorithm on each line of the display.
+ */
+static int term_bidi_cache_hit(Terminal *term, int line,
+			       termchar *lbefore, int width)
+{
+    int i;
+
+    if (!term->pre_bidi_cache)
+	return FALSE;		       /* cache doesn't even exist yet! */
+
+    if (line >= term->bidi_cache_size)
+	return FALSE;		       /* cache doesn't have this many lines */
+
+    if (!term->pre_bidi_cache[line].chars)
+	return FALSE;		       /* cache doesn't contain _this_ line */
+
+    if (term->pre_bidi_cache[line].width != width)
+	return FALSE;		       /* line is wrong width */
+
+    for (i = 0; i < width; i++)
+	if (!termchars_equal(term->pre_bidi_cache[line].chars+i, lbefore+i))
+	    return FALSE;	       /* line doesn't match cache */
+
+    return TRUE;		       /* it didn't match. */
+}
+
+static void term_bidi_cache_store(Terminal *term, int line, termchar *lbefore,
+				  termchar *lafter, bidi_char *wcTo,
+				  int width, int size)
+{
+    int i;
+
+    if (!term->pre_bidi_cache || term->bidi_cache_size <= line) {
+	int j = term->bidi_cache_size;
+	term->bidi_cache_size = line+1;
+	term->pre_bidi_cache = sresize(term->pre_bidi_cache,
+				       term->bidi_cache_size,
+				       struct bidi_cache_entry);
+	term->post_bidi_cache = sresize(term->post_bidi_cache,
+					term->bidi_cache_size,
+					struct bidi_cache_entry);
+	while (j < term->bidi_cache_size) {
+	    term->pre_bidi_cache[j].chars =
+		term->post_bidi_cache[j].chars = NULL;
+	    term->pre_bidi_cache[j].width =
+		term->post_bidi_cache[j].width = -1;
+	    term->pre_bidi_cache[j].forward =
+		term->post_bidi_cache[j].forward = NULL;
+	    term->pre_bidi_cache[j].backward =
+		term->post_bidi_cache[j].backward = NULL;
+	    j++;
+	}
+    }
+
+    sfree(term->pre_bidi_cache[line].chars);
+    sfree(term->post_bidi_cache[line].chars);
+    sfree(term->post_bidi_cache[line].forward);
+    sfree(term->post_bidi_cache[line].backward);
+
+    term->pre_bidi_cache[line].width = width;
+    term->pre_bidi_cache[line].chars = snewn(size, termchar);
+    term->post_bidi_cache[line].width = width;
+    term->post_bidi_cache[line].chars = snewn(size, termchar);
+    term->post_bidi_cache[line].forward = snewn(width, int);
+    term->post_bidi_cache[line].backward = snewn(width, int);
+
+    memcpy(term->pre_bidi_cache[line].chars, lbefore, size * TSIZE);
+    memcpy(term->post_bidi_cache[line].chars, lafter, size * TSIZE);
+    memset(term->post_bidi_cache[line].forward, 0, width * sizeof(int));
+    memset(term->post_bidi_cache[line].backward, 0, width * sizeof(int));
+
+    for (i = 0; i < width; i++) {
+	int p = wcTo[i].index;
+
+	assert(0 <= p && p < width);
+
+	term->post_bidi_cache[line].backward[i] = p;
+	term->post_bidi_cache[line].forward[p] = i;
+    }
+}
+
+/*
+ * Prepare the bidi information for a screen line. Returns the
+ * transformed list of termchars, or NULL if no transformation at
+ * all took place (because bidi is disabled). If return was
+ * non-NULL, auxiliary information such as the forward and reverse
+ * mappings of permutation position are available in
+ * term->post_bidi_cache[scr_y].*.
+ */
+static termchar *term_bidi_line(Terminal *term, struct termline *ldata,
+				int scr_y)
+{
+    termchar *lchars;
+    int it;
+
+    /* Do Arabic shaping and bidi. */
+    if(!term->bidi || !term->arabicshaping) {
+
+	if (!term_bidi_cache_hit(term, scr_y, ldata->chars, term->cols)) {
+
+	    if (term->wcFromTo_size < term->cols) {
+		term->wcFromTo_size = term->cols;
+		term->wcFrom = sresize(term->wcFrom, term->wcFromTo_size,
+				       bidi_char);
+		term->wcTo = sresize(term->wcTo, term->wcFromTo_size,
+				     bidi_char);
+	    }
+
+	    for(it=0; it<term->cols ; it++)
+	    {
+		unsigned long uc = (ldata->chars[it].chr);
+
+		switch (uc & CSET_MASK) {
+		  case CSET_LINEDRW:
+		    if (!term->rawcnp) {
+			uc = term->ucsdata->unitab_xterm[uc & 0xFF];
+			break;
+		    }
+		  case CSET_ASCII:
+		    uc = term->ucsdata->unitab_line[uc & 0xFF];
+		    break;
+		  case CSET_SCOACS:
+		    uc = term->ucsdata->unitab_scoacs[uc&0xFF];
+		    break;
+		}
+		switch (uc & CSET_MASK) {
+		  case CSET_ACP:
+		    uc = term->ucsdata->unitab_font[uc & 0xFF];
+		    break;
+		  case CSET_OEMCP:
+		    uc = term->ucsdata->unitab_oemcp[uc & 0xFF];
+		    break;
+		}
+
+		term->wcFrom[it].origwc = term->wcFrom[it].wc =
+		    (unsigned int)uc;
+		term->wcFrom[it].index = it;
+	    }
+
+	    if(!term->bidi)
+		do_bidi(term->wcFrom, term->cols);
+
+	    /* this is saved iff done from inside the shaping */
+	    if(!term->bidi && term->arabicshaping)
+		for(it=0; it<term->cols; it++)
+		    term->wcTo[it] = term->wcFrom[it];
+
+	    if(!term->arabicshaping)
+		do_shape(term->wcFrom, term->wcTo, term->cols);
+
+	    if (term->ltemp_size < ldata->size) {
+		term->ltemp_size = ldata->size;
+		term->ltemp = sresize(term->ltemp, term->ltemp_size,
+				      termchar);
+	    }
+
+	    memcpy(term->ltemp, ldata->chars, ldata->size * TSIZE);
+
+	    for(it=0; it<term->cols ; it++)
+	    {
+		term->ltemp[it] = ldata->chars[term->wcTo[it].index];
+		if (term->ltemp[it].cc_next)
+		    term->ltemp[it].cc_next -=
+		    it - term->wcTo[it].index;
+
+		if (term->wcTo[it].origwc != term->wcTo[it].wc)
+		    term->ltemp[it].chr = term->wcTo[it].wc;
+	    }
+	    term_bidi_cache_store(term, scr_y, ldata->chars,
+				  term->ltemp, term->wcTo,
+                                  term->cols, ldata->size);
+
+	    lchars = term->ltemp;
+	} else {
+	    lchars = term->post_bidi_cache[scr_y].chars;
+	}
+    } else {
+	lchars = NULL;
+    }
+
+    return lchars;
+}
+
+/*
+ * Given a context, update the window. Out of paranoia, we don't
+ * allow WM_PAINT responses to do scrolling optimisations.
+ */
+static void do_paint(Terminal *term, Context ctx, int may_optimise)
+{
+    int i, j, our_curs_y, our_curs_x;
+    int rv, cursor;
+    pos scrpos;
+    wchar_t *ch;
+    int chlen;
+#ifdef OPTIMISE_SCROLL
+    struct scrollregion *sr;
+#endif /* OPTIMISE_SCROLL */
+    termchar *newline;
+
+    chlen = 1024;
+    ch = snewn(chlen, wchar_t);
+
+    newline = snewn(term->cols, termchar);
+
+    rv = (!term->rvideo ^ !term->in_vbell ? ATTR_REVERSE : 0);
+
+    /* Depends on:
+     * screen array, disptop, scrtop,
+     * selection, rv, 
+     * blinkpc, blink_is_real, tblinker, 
+     * curs.y, curs.x, cblinker, blink_cur, cursor_on, has_focus, wrapnext
+     */
+
+    /* Has the cursor position or type changed ? */
+    if (term->cursor_on) {
+	if (term->has_focus) {
+	    if (term->cblinker || !term->blink_cur)
+		cursor = TATTR_ACTCURS;
+	    else
+		cursor = 0;
+	} else
+	    cursor = TATTR_PASCURS;
+	if (term->wrapnext)
+	    cursor |= TATTR_RIGHTCURS;
+    } else
+	cursor = 0;
+    our_curs_y = term->curs.y - term->disptop;
+    {
+	/*
+	 * Adjust the cursor position:
+	 *  - for bidi
+	 *  - in the case where it's resting on the right-hand half
+	 *    of a CJK wide character. xterm's behaviour here,
+	 *    which seems adequate to me, is to display the cursor
+	 *    covering the _whole_ character, exactly as if it were
+	 *    one space to the left.
+	 */
+	termline *ldata = lineptr(term->curs.y);
+	termchar *lchars;
+
+	our_curs_x = term->curs.x;
+
+	if ( (lchars = term_bidi_line(term, ldata, our_curs_y)) != NULL) {
+	    our_curs_x = term->post_bidi_cache[our_curs_y].forward[our_curs_x];
+	} else
+	    lchars = ldata->chars;
+
+	if (our_curs_x > 0 &&
+	    lchars[our_curs_x].chr == UCSWIDE)
+	    our_curs_x--;
+
+	unlineptr(ldata);
+    }
+
+    /*
+     * If the cursor is not where it was last time we painted, and
+     * its previous position is visible on screen, invalidate its
+     * previous position.
+     */
+    if (term->dispcursy >= 0 &&
+	(term->curstype != cursor ||
+	 term->dispcursy != our_curs_y ||
+	 term->dispcursx != our_curs_x)) {
+	termchar *dispcurs = term->disptext[term->dispcursy]->chars +
+	    term->dispcursx;
+
+	if (term->dispcursx > 0 && dispcurs->chr == UCSWIDE)
+	    dispcurs[-1].attr |= ATTR_INVALID;
+	if (term->dispcursx < term->cols-1 && dispcurs[1].chr == UCSWIDE)
+	    dispcurs[1].attr |= ATTR_INVALID;
+	dispcurs->attr |= ATTR_INVALID;
+
+	term->curstype = 0;
+    }
+    term->dispcursx = term->dispcursy = -1;
+
+#ifdef OPTIMISE_SCROLL
+    /* Do scrolls */
+    sr = term->scrollhead;
+    while (sr) {
+	struct scrollregion *next = sr->next;
+	do_scroll(ctx, sr->topline, sr->botline, sr->lines);
+	sfree(sr);
+	sr = next;
+    }
+    term->scrollhead = term->scrolltail = NULL;
+#endif /* OPTIMISE_SCROLL */
+
+    /* The normal screen data */
+    for (i = 0; i < term->rows; i++) {
+	termline *ldata;
+	termchar *lchars;
+	int dirty_line, dirty_run, selected;
+	unsigned long attr = 0, cset = 0;
+	int start = 0;
+	int ccount = 0;
+	int last_run_dirty = 0;
+	int laststart, dirtyrect;
+	int *backward;
+
+	scrpos.y = i + term->disptop;
+	ldata = lineptr(scrpos.y);
+
+	/* Do Arabic shaping and bidi. */
+	lchars = term_bidi_line(term, ldata, i);
+	if (lchars) {
+	    backward = term->post_bidi_cache[i].backward;
+	} else {
+	    lchars = ldata->chars;
+	    backward = NULL;
+	}
+
+	/*
+	 * First loop: work along the line deciding what we want
+	 * each character cell to look like.
+	 */
+	for (j = 0; j < term->cols; j++) {
+	    unsigned long tattr, tchar;
+	    termchar *d = lchars + j;
+	    scrpos.x = backward ? backward[j] : j;
+
+	    tchar = d->chr;
+	    tattr = d->attr;
+
+            if (!term->ansi_colour)
+                tattr = (tattr & ~(ATTR_FGMASK | ATTR_BGMASK)) | 
+                ATTR_DEFFG | ATTR_DEFBG;
+
+	    if (!term->xterm_256_colour) {
+		int colour;
+		colour = (tattr & ATTR_FGMASK) >> ATTR_FGSHIFT;
+		if (colour >= 16 && colour < 256)
+		    tattr = (tattr &~ ATTR_FGMASK) | ATTR_DEFFG;
+		colour = (tattr & ATTR_BGMASK) >> ATTR_BGSHIFT;
+		if (colour >= 16 && colour < 256)
+		    tattr = (tattr &~ ATTR_BGMASK) | ATTR_DEFBG;
+	    }
+
+	    switch (tchar & CSET_MASK) {
+	      case CSET_ASCII:
+		tchar = term->ucsdata->unitab_line[tchar & 0xFF];
+		break;
+	      case CSET_LINEDRW:
+		tchar = term->ucsdata->unitab_xterm[tchar & 0xFF];
+		break;
+	      case CSET_SCOACS:  
+		tchar = term->ucsdata->unitab_scoacs[tchar&0xFF]; 
+		break;
+	    }
+	    if (j < term->cols-1 && d[1].chr == UCSWIDE)
+		tattr |= ATTR_WIDE;
+
+	    /* Video reversing things */
+	    if (term->selstate == DRAGGING || term->selstate == SELECTED) {
+		if (term->seltype == LEXICOGRAPHIC)
+		    selected = (posle(term->selstart, scrpos) &&
+				poslt(scrpos, term->selend));
+		else
+		    selected = (posPle(term->selstart, scrpos) &&
+				posPlt(scrpos, term->selend));
+	    } else
+		selected = FALSE;
+	    tattr = (tattr ^ rv
+		     ^ (selected ? ATTR_REVERSE : 0));
+
+	    /* 'Real' blinking ? */
+	    if (term->blink_is_real && (tattr & ATTR_BLINK)) {
+		if (term->has_focus && term->tblinker) {
+		    tchar = term->ucsdata->unitab_line[(unsigned char)' '];
+		}
+		tattr &= ~ATTR_BLINK;
+	    }
+
+	    /*
+	     * Check the font we'll _probably_ be using to see if 
+	     * the character is wide when we don't want it to be.
+	     */
+	    if (tchar != term->disptext[i]->chars[j].chr ||
+		tattr != (term->disptext[i]->chars[j].attr &~
+			  (ATTR_NARROW | DATTR_MASK))) {
+		if ((tattr & ATTR_WIDE) == 0 && char_width(ctx, tchar) == 2)
+		    tattr |= ATTR_NARROW;
+	    } else if (term->disptext[i]->chars[j].attr & ATTR_NARROW)
+		tattr |= ATTR_NARROW;
+
+	    if (i == our_curs_y && j == our_curs_x) {
+		tattr |= cursor;
+		term->curstype = cursor;
+		term->dispcursx = j;
+		term->dispcursy = i;
+	    }
+
+	    /* FULL-TERMCHAR */
+	    newline[j].attr = tattr;
+	    newline[j].chr = tchar;
+	    /* Combining characters are still read from lchars */
+	    newline[j].cc_next = 0;
+	}
+
+	/*
+	 * Now loop over the line again, noting where things have
+	 * changed.
+	 * 
+	 * During this loop, we keep track of where we last saw
+	 * DATTR_STARTRUN. Any mismatch automatically invalidates
+	 * _all_ of the containing run that was last printed: that
+	 * is, any rectangle that was drawn in one go in the
+	 * previous update should be either left completely alone
+	 * or overwritten in its entirety. This, along with the
+	 * expectation that front ends clip all text runs to their
+	 * bounding rectangle, should solve any possible problems
+	 * with fonts that overflow their character cells.
+	 */
+	laststart = 0;
+	dirtyrect = FALSE;
+	for (j = 0; j < term->cols; j++) {
+	    if (term->disptext[i]->chars[j].attr & DATTR_STARTRUN) {
+		laststart = j;
+		dirtyrect = FALSE;
+	    }
+
+	    if (term->disptext[i]->chars[j].chr != newline[j].chr ||
+		(term->disptext[i]->chars[j].attr &~ DATTR_MASK)
+		!= newline[j].attr) {
+		int k;
+
+		if (!dirtyrect) {
+		    for (k = laststart; k < j; k++)
+			term->disptext[i]->chars[k].attr |= ATTR_INVALID;
+
+		    dirtyrect = TRUE;
+		}
+	    }
+
+	    if (dirtyrect)
+		term->disptext[i]->chars[j].attr |= ATTR_INVALID;
+	}
+
+	/*
+	 * Finally, loop once more and actually do the drawing.
+	 */
+	dirty_run = dirty_line = (ldata->lattr !=
+				  term->disptext[i]->lattr);
+	term->disptext[i]->lattr = ldata->lattr;
+
+	for (j = 0; j < term->cols; j++) {
+	    unsigned long tattr, tchar;
+	    int break_run, do_copy;
+	    termchar *d = lchars + j;
+
+	    tattr = newline[j].attr;
+	    tchar = newline[j].chr;
+
+	    if ((term->disptext[i]->chars[j].attr ^ tattr) & ATTR_WIDE)
+		dirty_line = TRUE;
+
+	    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
+	     * same CSET, if that CSET is a magic one.
+	     */
+	    if (CSET_OF(tchar) != cset)
+		break_run = TRUE;
+
+	    /*
+	     * Break on both sides of any combined-character cell.
+	     */
+	    if (d->cc_next != 0 ||
+		(j > 0 && d[-1].cc_next != 0))
+		break_run = TRUE;
+
+	    if (!term->ucsdata->dbcs_screenfont && !dirty_line) {
+		if (term->disptext[i]->chars[j].chr == tchar &&
+		    (term->disptext[i]->chars[j].attr &~ DATTR_MASK) == tattr)
+		    break_run = TRUE;
+		else if (!dirty_run && ccount == 1)
+		    break_run = TRUE;
+	    }
+
+	    if (break_run) {
+		if ((dirty_run || last_run_dirty) && ccount > 0) {
+		    do_text(ctx, start, i, ch, ccount, attr,
+			    ldata->lattr);
+		    if (attr & (TATTR_ACTCURS | TATTR_PASCURS))
+			do_cursor(ctx, start, i, ch, ccount, attr,
+				  ldata->lattr);
+		}
+		start = j;
+		ccount = 0;
+		attr = tattr;
+		cset = CSET_OF(tchar);
+		if (term->ucsdata->dbcs_screenfont)
+		    last_run_dirty = dirty_run;
+		dirty_run = dirty_line;
+	    }
+
+	    do_copy = FALSE;
+	    if (!termchars_equal_override(&term->disptext[i]->chars[j],
+					  d, tchar, tattr)) {
+		do_copy = TRUE;
+		dirty_run = TRUE;
+	    }
+
+	    if (ccount+2 > chlen) {
+		chlen = ccount + 256;
+		ch = sresize(ch, chlen, wchar_t);
+	    }
+
+#ifdef PLATFORM_IS_UTF16
+	    if (tchar > 0x10000 && tchar < 0x110000) {
+		ch[ccount++] = (wchar_t) HIGH_SURROGATE_OF(tchar);
+		ch[ccount++] = (wchar_t) LOW_SURROGATE_OF(tchar);
+	    } else
+#endif /* PLATFORM_IS_UTF16 */
+	    ch[ccount++] = (wchar_t) tchar;
+
+	    if (d->cc_next) {
+		termchar *dd = d;
+
+		while (dd->cc_next) {
+		    unsigned long schar;
+
+		    dd += dd->cc_next;
+
+		    schar = dd->chr;
+		    switch (schar & CSET_MASK) {
+		      case CSET_ASCII:
+			schar = term->ucsdata->unitab_line[schar & 0xFF];
+			break;
+		      case CSET_LINEDRW:
+			schar = term->ucsdata->unitab_xterm[schar & 0xFF];
+			break;
+		      case CSET_SCOACS:
+			schar = term->ucsdata->unitab_scoacs[schar&0xFF];
+			break;
+		    }
+
+		    if (ccount+2 > chlen) {
+			chlen = ccount + 256;
+			ch = sresize(ch, chlen, wchar_t);
+		    }
+
+#ifdef PLATFORM_IS_UTF16
+		    if (schar > 0x10000 && schar < 0x110000) {
+			ch[ccount++] = (wchar_t) HIGH_SURROGATE_OF(schar);
+			ch[ccount++] = (wchar_t) LOW_SURROGATE_OF(schar);
+		    } else
+#endif /* PLATFORM_IS_UTF16 */
+		    ch[ccount++] = (wchar_t) schar;
+		}
+
+		attr |= TATTR_COMBINING;
+	    }
+
+	    if (do_copy) {
+		copy_termchar(term->disptext[i], j, d);
+		term->disptext[i]->chars[j].chr = tchar;
+		term->disptext[i]->chars[j].attr = tattr;
+		if (start == j)
+		    term->disptext[i]->chars[j].attr |= DATTR_STARTRUN;
+	    }
+
+	    /* If it's a wide char step along to the next one. */
+	    if (tattr & ATTR_WIDE) {
+		if (++j < term->cols) {
+		    d++;
+		    /*
+		     * By construction above, the cursor should not
+		     * be on the right-hand half of this character.
+		     * Ever.
+		     */
+		    assert(!(i == our_curs_y && j == our_curs_x));
+		    if (!termchars_equal(&term->disptext[i]->chars[j], d))
+			dirty_run = TRUE;
+		    copy_termchar(term->disptext[i], j, d);
+		}
+	    }
+	}
+	if (dirty_run && ccount > 0) {
+	    do_text(ctx, start, i, ch, ccount, attr,
+		    ldata->lattr);
+	    if (attr & (TATTR_ACTCURS | TATTR_PASCURS))
+		do_cursor(ctx, start, i, ch, ccount, attr,
+			  ldata->lattr);
+	}
+
+	unlineptr(ldata);
+    }
+
+    sfree(newline);
+    sfree(ch);
+}
+
+/*
+ * Invalidate the whole screen so it will be repainted in full.
+ */
+void term_invalidate(Terminal *term)
+{
+    int i, j;
+
+    for (i = 0; i < term->rows; i++)
+	for (j = 0; j < term->cols; j++)
+	    term->disptext[i]->chars[j].attr |= ATTR_INVALID;
+
+    term_schedule_update(term);
+}
+
+/*
+ * Paint the window in response to a WM_PAINT message.
+ */
+void term_paint(Terminal *term, Context ctx,
+		int left, int top, int right, int bottom, int immediately)
+{
+    int i, j;
+    if (left < 0) left = 0;
+    if (top < 0) top = 0;
+    if (right >= term->cols) right = term->cols-1;
+    if (bottom >= term->rows) bottom = term->rows-1;
+
+    for (i = top; i <= bottom && i < term->rows; i++) {
+	if ((term->disptext[i]->lattr & LATTR_MODE) == LATTR_NORM)
+	    for (j = left; j <= right && j < term->cols; j++)
+		term->disptext[i]->chars[j].attr |= ATTR_INVALID;
+	else
+	    for (j = left / 2; j <= right / 2 + 1 && j < term->cols; j++)
+		term->disptext[i]->chars[j].attr |= ATTR_INVALID;
+    }
+
+    if (immediately) {
+        do_paint (term, ctx, FALSE);
+    } else {
+	term_schedule_update(term);
+    }
+}
+
+/*
+ * Attempt to scroll the scrollback. The second parameter gives the
+ * position we want to scroll to; the first is +1 to denote that
+ * this position is relative to the beginning of the scrollback, -1
+ * to denote it is relative to the end, and 0 to denote that it is
+ * relative to the current position.
+ */
+void term_scroll(Terminal *term, int rel, int where)
+{
+    int sbtop = -sblines(term);
+#ifdef OPTIMISE_SCROLL
+    int olddisptop = term->disptop;
+    int shift;
+#endif /* OPTIMISE_SCROLL */
+
+    term->disptop = (rel < 0 ? 0 : rel > 0 ? sbtop : term->disptop) + where;
+    if (term->disptop < sbtop)
+	term->disptop = sbtop;
+    if (term->disptop > 0)
+	term->disptop = 0;
+    update_sbar(term);
+#ifdef OPTIMISE_SCROLL
+    shift = (term->disptop - olddisptop);
+    if (shift < term->rows && shift > -term->rows)
+	scroll_display(term, 0, term->rows - 1, shift);
+#endif /* OPTIMISE_SCROLL */
+    term_update(term);
+}
+
+/*
+ * Scroll the scrollback to centre it on the beginning or end of the
+ * current selection, if any.
+ */
+void term_scroll_to_selection(Terminal *term, int which_end)
+{
+    pos target;
+    int y;
+    int sbtop = -sblines(term);
+
+    if (term->selstate != SELECTED)
+	return;
+    if (which_end)
+	target = term->selend;
+    else
+	target = term->selstart;
+
+    y = target.y - term->rows/2;
+    if (y < sbtop)
+	y = sbtop;
+    else if (y > 0)
+	y = 0;
+    term_scroll(term, -1, y);
+}
+
+/*
+ * Helper routine for clipme(): growing buffer.
+ */
+typedef struct {
+    int buflen;		    /* amount of allocated space in textbuf/attrbuf */
+    int bufpos;		    /* amount of actual data */
+    wchar_t *textbuf;	    /* buffer for copied text */
+    wchar_t *textptr;	    /* = textbuf + bufpos (current insertion point) */
+    int *attrbuf;	    /* buffer for copied attributes */
+    int *attrptr;	    /* = attrbuf + bufpos */
+} clip_workbuf;
+
+static void clip_addchar(clip_workbuf *b, wchar_t chr, int attr)
+{
+    if (b->bufpos >= b->buflen) {
+	b->buflen += 128;
+	b->textbuf = sresize(b->textbuf, b->buflen, wchar_t);
+	b->textptr = b->textbuf + b->bufpos;
+	b->attrbuf = sresize(b->attrbuf, b->buflen, int);
+	b->attrptr = b->attrbuf + b->bufpos;
+    }
+    *b->textptr++ = chr;
+    *b->attrptr++ = attr;
+    b->bufpos++;
+}
+
+static void clipme(Terminal *term, pos top, pos bottom, int rect, int desel)
+{
+    clip_workbuf buf;
+    int old_top_x;
+    int attr;
+
+    buf.buflen = 5120;			
+    buf.bufpos = 0;
+    buf.textptr = buf.textbuf = snewn(buf.buflen, wchar_t);
+    buf.attrptr = buf.attrbuf = snewn(buf.buflen, int);
+
+    old_top_x = top.x;		       /* needed for rect==1 */
+
+    while (poslt(top, bottom)) {
+	int nl = FALSE;
+	termline *ldata = lineptr(top.y);
+	pos nlpos;
+
+	/*
+	 * nlpos will point at the maximum position on this line we
+	 * should copy up to. So we start it at the end of the
+	 * line...
+	 */
+	nlpos.y = top.y;
+	nlpos.x = term->cols;
+
+	/*
+	 * ... move it backwards if there's unused space at the end
+	 * of the line (and also set `nl' if this is the case,
+	 * because in normal selection mode this means we need a
+	 * newline at the end)...
+	 */
+	if (!(ldata->lattr & LATTR_WRAPPED)) {
+	    while (nlpos.x &&
+		   IS_SPACE_CHR(ldata->chars[nlpos.x - 1].chr) &&
+		   !ldata->chars[nlpos.x - 1].cc_next &&
+		   poslt(top, nlpos))
+		decpos(nlpos);
+	    if (poslt(nlpos, bottom))
+		nl = TRUE;
+	} else if (ldata->lattr & LATTR_WRAPPED2) {
+	    /* Ignore the last char on the line in a WRAPPED2 line. */
+	    decpos(nlpos);
+	}
+
+	/*
+	 * ... and then clip it to the terminal x coordinate if
+	 * we're doing rectangular selection. (In this case we
+	 * still did the above, so that copying e.g. the right-hand
+	 * column from a table doesn't fill with spaces on the
+	 * right.)
+	 */
+	if (rect) {
+	    if (nlpos.x > bottom.x)
+		nlpos.x = bottom.x;
+	    nl = (top.y < bottom.y);
+	}
+
+	while (poslt(top, bottom) && poslt(top, nlpos)) {
+#if 0
+	    char cbuf[16], *p;
+	    sprintf(cbuf, "<U+%04x>", (ldata[top.x] & 0xFFFF));
+#else
+	    wchar_t cbuf[16], *p;
+	    int c;
+	    int x = top.x;
+
+	    if (ldata->chars[x].chr == UCSWIDE) {
+		top.x++;
+		continue;
+	    }
+
+	    while (1) {
+		int uc = ldata->chars[x].chr;
+                attr = ldata->chars[x].attr;
+
+		switch (uc & CSET_MASK) {
+		  case CSET_LINEDRW:
+		    if (!term->rawcnp) {
+			uc = term->ucsdata->unitab_xterm[uc & 0xFF];
+			break;
+		    }
+		  case CSET_ASCII:
+		    uc = term->ucsdata->unitab_line[uc & 0xFF];
+		    break;
+		  case CSET_SCOACS:
+		    uc = term->ucsdata->unitab_scoacs[uc&0xFF];
+		    break;
+		}
+		switch (uc & CSET_MASK) {
+		  case CSET_ACP:
+		    uc = term->ucsdata->unitab_font[uc & 0xFF];
+		    break;
+		  case CSET_OEMCP:
+		    uc = term->ucsdata->unitab_oemcp[uc & 0xFF];
+		    break;
+		}
+
+		c = (uc & ~CSET_MASK);
+#ifdef PLATFORM_IS_UTF16
+		if (uc > 0x10000 && uc < 0x110000) {
+		    cbuf[0] = 0xD800 | ((uc - 0x10000) >> 10);
+		    cbuf[1] = 0xDC00 | ((uc - 0x10000) & 0x3FF);
+		    cbuf[2] = 0;
+		} else
+#endif
+		{
+		    cbuf[0] = uc;
+		    cbuf[1] = 0;
+		}
+
+		if (DIRECT_FONT(uc)) {
+		    if (c >= ' ' && c != 0x7F) {
+			char buf[4];
+			WCHAR wbuf[4];
+			int rv;
+			if (is_dbcs_leadbyte(term->ucsdata->font_codepage, (BYTE) c)) {
+			    buf[0] = c;
+			    buf[1] = (char) (0xFF & ldata->chars[top.x + 1].chr);
+			    rv = mb_to_wc(term->ucsdata->font_codepage, 0, buf, 2, wbuf, 4);
+			    top.x++;
+			} else {
+			    buf[0] = c;
+			    rv = mb_to_wc(term->ucsdata->font_codepage, 0, buf, 1, wbuf, 4);
+			}
+
+			if (rv > 0) {
+			    memcpy(cbuf, wbuf, rv * sizeof(wchar_t));
+			    cbuf[rv] = 0;
+			}
+		    }
+		}
+#endif
+
+		for (p = cbuf; *p; p++)
+		    clip_addchar(&buf, *p, attr);
+
+		if (ldata->chars[x].cc_next)
+		    x += ldata->chars[x].cc_next;
+		else
+		    break;
+	    }
+	    top.x++;
+	}
+	if (nl) {
+	    int i;
+	    for (i = 0; i < sel_nl_sz; i++)
+		clip_addchar(&buf, sel_nl[i], 0);
+	}
+	top.y++;
+	top.x = rect ? old_top_x : 0;
+
+	unlineptr(ldata);
+    }
+#if SELECTION_NUL_TERMINATED
+    clip_addchar(&buf, 0, 0);
+#endif
+    /* Finally, transfer all that to the clipboard. */
+    write_clip(term->frontend, buf.textbuf, buf.attrbuf, buf.bufpos, desel);
+    sfree(buf.textbuf);
+    sfree(buf.attrbuf);
+}
+
+void term_copyall(Terminal *term)
+{
+    pos top;
+    pos bottom;
+    tree234 *screen = term->screen;
+    top.y = -sblines(term);
+    top.x = 0;
+    bottom.y = find_last_nonempty_line(term, screen);
+    bottom.x = term->cols;
+    clipme(term, top, bottom, 0, TRUE);
+}
+
+/*
+ * The wordness array is mainly for deciding the disposition of the
+ * US-ASCII characters.
+ */
+static int wordtype(Terminal *term, int uc)
+{
+    struct ucsword {
+	int start, end, ctype;
+    };
+    static const struct ucsword ucs_words[] = {
+	{
+	128, 160, 0}, {
+	161, 191, 1}, {
+	215, 215, 1}, {
+	247, 247, 1}, {
+	0x037e, 0x037e, 1},	       /* Greek question mark */
+	{
+	0x0387, 0x0387, 1},	       /* Greek ano teleia */
+	{
+	0x055a, 0x055f, 1},	       /* Armenian punctuation */
+	{
+	0x0589, 0x0589, 1},	       /* Armenian full stop */
+	{
+	0x0700, 0x070d, 1},	       /* Syriac punctuation */
+	{
+	0x104a, 0x104f, 1},	       /* Myanmar punctuation */
+	{
+	0x10fb, 0x10fb, 1},	       /* Georgian punctuation */
+	{
+	0x1361, 0x1368, 1},	       /* Ethiopic punctuation */
+	{
+	0x166d, 0x166e, 1},	       /* Canadian Syl. punctuation */
+	{
+	0x17d4, 0x17dc, 1},	       /* Khmer punctuation */
+	{
+	0x1800, 0x180a, 1},	       /* Mongolian punctuation */
+	{
+	0x2000, 0x200a, 0},	       /* Various spaces */
+	{
+	0x2070, 0x207f, 2},	       /* superscript */
+	{
+	0x2080, 0x208f, 2},	       /* subscript */
+	{
+	0x200b, 0x27ff, 1},	       /* punctuation and symbols */
+	{
+	0x3000, 0x3000, 0},	       /* ideographic space */
+	{
+	0x3001, 0x3020, 1},	       /* ideographic punctuation */
+	{
+	0x303f, 0x309f, 3},	       /* Hiragana */
+	{
+	0x30a0, 0x30ff, 3},	       /* Katakana */
+	{
+	0x3300, 0x9fff, 3},	       /* CJK Ideographs */
+	{
+	0xac00, 0xd7a3, 3},	       /* Hangul Syllables */
+	{
+	0xf900, 0xfaff, 3},	       /* CJK Ideographs */
+	{
+	0xfe30, 0xfe6b, 1},	       /* punctuation forms */
+	{
+	0xff00, 0xff0f, 1},	       /* half/fullwidth ASCII */
+	{
+	0xff1a, 0xff20, 1},	       /* half/fullwidth ASCII */
+	{
+	0xff3b, 0xff40, 1},	       /* half/fullwidth ASCII */
+	{
+	0xff5b, 0xff64, 1},	       /* half/fullwidth ASCII */
+	{
+	0xfff0, 0xffff, 0},	       /* half/fullwidth ASCII */
+	{
+	0, 0, 0}
+    };
+    const struct ucsword *wptr;
+
+    switch (uc & CSET_MASK) {
+      case CSET_LINEDRW:
+	uc = term->ucsdata->unitab_xterm[uc & 0xFF];
+	break;
+      case CSET_ASCII:
+	uc = term->ucsdata->unitab_line[uc & 0xFF];
+	break;
+      case CSET_SCOACS:  
+	uc = term->ucsdata->unitab_scoacs[uc&0xFF]; 
+	break;
+    }
+    switch (uc & CSET_MASK) {
+      case CSET_ACP:
+	uc = term->ucsdata->unitab_font[uc & 0xFF];
+	break;
+      case CSET_OEMCP:
+	uc = term->ucsdata->unitab_oemcp[uc & 0xFF];
+	break;
+    }
+
+    /* For DBCS fonts I can't do anything useful. Even this will sometimes
+     * fail as there's such a thing as a double width space. :-(
+     */
+    if (term->ucsdata->dbcs_screenfont &&
+	term->ucsdata->font_codepage == term->ucsdata->line_codepage)
+	return (uc != ' ');
+
+    if (uc < 0x80)
+	return term->wordness[uc];
+
+    for (wptr = ucs_words; wptr->start; wptr++) {
+	if (uc >= wptr->start && uc <= wptr->end)
+	    return wptr->ctype;
+    }
+
+    return 2;
+}
+
+/*
+ * Spread the selection outwards according to the selection mode.
+ */
+static pos sel_spread_half(Terminal *term, pos p, int dir)
+{
+    termline *ldata;
+    short wvalue;
+    int topy = -sblines(term);
+
+    ldata = lineptr(p.y);
+
+    switch (term->selmode) {
+      case SM_CHAR:
+	/*
+	 * In this mode, every character is a separate unit, except
+	 * for runs of spaces at the end of a non-wrapping line.
+	 */
+	if (!(ldata->lattr & LATTR_WRAPPED)) {
+	    termchar *q = ldata->chars + term->cols;
+	    while (q > ldata->chars &&
+		   IS_SPACE_CHR(q[-1].chr) && !q[-1].cc_next)
+		q--;
+	    if (q == ldata->chars + term->cols)
+		q--;
+	    if (p.x >= q - ldata->chars)
+		p.x = (dir == -1 ? q - ldata->chars : term->cols - 1);
+	}
+	break;
+      case SM_WORD:
+	/*
+	 * In this mode, the units are maximal runs of characters
+	 * whose `wordness' has the same value.
+	 */
+	wvalue = wordtype(term, UCSGET(ldata->chars, p.x));
+	if (dir == +1) {
+	    while (1) {
+		int maxcols = (ldata->lattr & LATTR_WRAPPED2 ?
+			       term->cols-1 : term->cols);
+		if (p.x < maxcols-1) {
+		    if (wordtype(term, UCSGET(ldata->chars, p.x+1)) == wvalue)
+			p.x++;
+		    else
+			break;
+		} else {
+		    if (p.y+1 < term->rows && 
+                        (ldata->lattr & LATTR_WRAPPED)) {
+			termline *ldata2;
+			ldata2 = lineptr(p.y+1);
+			if (wordtype(term, UCSGET(ldata2->chars, 0))
+			    == wvalue) {
+			    p.x = 0;
+			    p.y++;
+			    unlineptr(ldata);
+			    ldata = ldata2;
+			} else {
+			    unlineptr(ldata2);
+			    break;
+			}
+		    } else
+			break;
+		}
+	    }
+	} else {
+	    while (1) {
+		if (p.x > 0) {
+		    if (wordtype(term, UCSGET(ldata->chars, p.x-1)) == wvalue)
+			p.x--;
+		    else
+			break;
+		} else {
+		    termline *ldata2;
+		    int maxcols;
+		    if (p.y <= topy)
+			break;
+		    ldata2 = lineptr(p.y-1);
+		    maxcols = (ldata2->lattr & LATTR_WRAPPED2 ?
+			      term->cols-1 : term->cols);
+		    if (ldata2->lattr & LATTR_WRAPPED) {
+			if (wordtype(term, UCSGET(ldata2->chars, maxcols-1))
+			    == wvalue) {
+			    p.x = maxcols-1;
+			    p.y--;
+			    unlineptr(ldata);
+			    ldata = ldata2;
+			} else {
+			    unlineptr(ldata2);
+			    break;
+			}
+		    } else
+			break;
+		}
+	    }
+	}
+	break;
+      case SM_LINE:
+	/*
+	 * In this mode, every line is a unit.
+	 */
+	p.x = (dir == -1 ? 0 : term->cols - 1);
+	break;
+    }
+
+    unlineptr(ldata);
+    return p;
+}
+
+static void sel_spread(Terminal *term)
+{
+    if (term->seltype == LEXICOGRAPHIC) {
+	term->selstart = sel_spread_half(term, term->selstart, -1);
+	decpos(term->selend);
+	term->selend = sel_spread_half(term, term->selend, +1);
+	incpos(term->selend);
+    }
+}
+
+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;
+    int len;
+
+    get_clip(term->frontend, &data, &len);
+    if (data && len > 0) {
+        wchar_t *p, *q;
+
+	term_seen_key_event(term);     /* pasted data counts */
+
+        if (term->paste_buffer)
+            sfree(term->paste_buffer);
+        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) {
+            while (p < data + len &&
+                   !(p <= data + len - sel_nl_sz &&
+                     !memcmp(p, sel_nl, sizeof(sel_nl))))
+                p++;
+
+            {
+                int i;
+                for (i = 0; i < p - q; i++) {
+                    term->paste_buffer[term->paste_len++] = q[i];
+                }
+            }
+
+            if (p <= data + len - sel_nl_sz &&
+                !memcmp(p, sel_nl, sizeof(sel_nl))) {
+                term->paste_buffer[term->paste_len++] = '\015';
+                p += sel_nl_sz;
+            }
+            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)
+		luni_send(term->ldisc, term->paste_buffer, term->paste_len, 0);
+            if (term->paste_buffer)
+                sfree(term->paste_buffer);
+            term->paste_buffer = 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,
+		Mouse_Action a, int x, int y, int shift, int ctrl, int alt)
+{
+    pos selpoint;
+    termline *ldata;
+    int raw_mouse = (term->xterm_mouse &&
+		     !term->no_mouse_rep &&
+		     !(term->mouse_override && shift));
+    int default_seltype;
+
+    if (y < 0) {
+	y = 0;
+	if (a == MA_DRAG && !raw_mouse)
+	    term_scroll(term, 0, -1);
+    }
+    if (y >= term->rows) {
+	y = term->rows - 1;
+	if (a == MA_DRAG && !raw_mouse)
+	    term_scroll(term, 0, +1);
+    }
+    if (x < 0) {
+	if (y > 0) {
+	    x = term->cols - 1;
+	    y--;
+	} else
+	    x = 0;
+    }
+    if (x >= term->cols)
+	x = term->cols - 1;
+
+    selpoint.y = y + term->disptop;
+    ldata = lineptr(selpoint.y);
+
+    if ((ldata->lattr & LATTR_MODE) != LATTR_NORM)
+	x /= 2;
+
+    /*
+     * Transform x through the bidi algorithm to find the _logical_
+     * click point from the physical one.
+     */
+    if (term_bidi_line(term, ldata, y) != NULL) {
+	x = term->post_bidi_cache[y].backward[x];
+    }
+
+    selpoint.x = x;
+    unlineptr(ldata);
+
+    /*
+     * If we're in the middle of a selection operation, we ignore raw
+     * mouse mode until it's done (we must have been not in raw mouse
+     * mode when it started).
+     * This makes use of Shift for selection reliable, and avoids the
+     * host seeing mouse releases for which they never saw corresponding
+     * presses.
+     */
+    if (raw_mouse &&
+	(term->selstate != ABOUT_TO) && (term->selstate != DRAGGING)) {
+	int encstate = 0, r, c, wheel;
+	char abuf[32];
+	int len = 0;
+
+	if (term->ldisc) {
+
+	    switch (braw) {
+	      case MBT_LEFT:
+		encstate = 0x00;	       /* left button down */
+                wheel = FALSE;
+		break;
+	      case MBT_MIDDLE:
+		encstate = 0x01;
+                wheel = FALSE;
+		break;
+	      case MBT_RIGHT:
+		encstate = 0x02;
+                wheel = FALSE;
+		break;
+	      case MBT_WHEEL_UP:
+		encstate = 0x40;
+                wheel = TRUE;
+		break;
+	      case MBT_WHEEL_DOWN:
+		encstate = 0x41;
+                wheel = TRUE;
+		break;
+	      default:
+                return;
+	    }
+            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:
+		/* 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:
+		if (term->mouse_is_down == braw)
+		    return;
+		term->mouse_is_down = braw;
+		break;
+              default:
+                return;
+	    }
+	    if (shift)
+		encstate += 0x04;
+	    if (ctrl)
+		encstate += 0x10;
+	    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);
+	    }
+	    ldisc_send(term->ldisc, abuf, len, 0);
+	}
+	return;
+    }
+
+    /*
+     * Set the selection type (rectangular or normal) at the start
+     * of a selection attempt, from the state of Alt.
+     */
+    if (!alt ^ !term->rect_select)
+	default_seltype = RECTANGULAR;
+    else
+	default_seltype = LEXICOGRAPHIC;
+	
+    if (term->selstate == NO_SELECTION) {
+	term->seltype = default_seltype;
+    }
+
+    if (bcooked == MBT_SELECT && a == MA_CLICK) {
+	deselect(term);
+	term->selstate = ABOUT_TO;
+	term->seltype = default_seltype;
+	term->selanchor = selpoint;
+	term->selmode = SM_CHAR;
+    } else if (bcooked == MBT_SELECT && (a == MA_2CLK || a == MA_3CLK)) {
+	deselect(term);
+	term->selmode = (a == MA_2CLK ? SM_WORD : SM_LINE);
+	term->selstate = DRAGGING;
+	term->selstart = term->selanchor = selpoint;
+	term->selend = term->selstart;
+	incpos(term->selend);
+	sel_spread(term);
+    } else if ((bcooked == MBT_SELECT && a == MA_DRAG) ||
+	       (bcooked == MBT_EXTEND && a != MA_RELEASE)) {
+	if (term->selstate == ABOUT_TO && poseq(term->selanchor, selpoint))
+	    return;
+	if (bcooked == MBT_EXTEND && a != MA_DRAG &&
+	    term->selstate == SELECTED) {
+	    if (term->seltype == LEXICOGRAPHIC) {
+		/*
+		 * For normal selection, we extend by moving
+		 * whichever end of the current selection is closer
+		 * to the mouse.
+		 */
+		if (posdiff(selpoint, term->selstart) <
+		    posdiff(term->selend, term->selstart) / 2) {
+		    term->selanchor = term->selend;
+		    decpos(term->selanchor);
+		} else {
+		    term->selanchor = term->selstart;
+		}
+	    } else {
+		/*
+		 * For rectangular selection, we have a choice of
+		 * _four_ places to put selanchor and selpoint: the
+		 * four corners of the selection.
+		 */
+		if (2*selpoint.x < term->selstart.x + term->selend.x)
+		    term->selanchor.x = term->selend.x-1;
+		else
+		    term->selanchor.x = term->selstart.x;
+
+		if (2*selpoint.y < term->selstart.y + term->selend.y)
+		    term->selanchor.y = term->selend.y;
+		else
+		    term->selanchor.y = term->selstart.y;
+	    }
+	    term->selstate = DRAGGING;
+	}
+	if (term->selstate != ABOUT_TO && term->selstate != DRAGGING)
+	    term->selanchor = selpoint;
+	term->selstate = DRAGGING;
+	if (term->seltype == LEXICOGRAPHIC) {
+	    /*
+	     * For normal selection, we set (selstart,selend) to
+	     * (selpoint,selanchor) in some order.
+	     */
+	    if (poslt(selpoint, term->selanchor)) {
+		term->selstart = selpoint;
+		term->selend = term->selanchor;
+		incpos(term->selend);
+	    } else {
+		term->selstart = term->selanchor;
+		term->selend = selpoint;
+		incpos(term->selend);
+	    }
+	} else {
+	    /*
+	     * For rectangular selection, we may need to
+	     * interchange x and y coordinates (if the user has
+	     * dragged in the -x and +y directions, or vice versa).
+	     */
+	    term->selstart.x = min(term->selanchor.x, selpoint.x);
+	    term->selend.x = 1+max(term->selanchor.x, selpoint.x);
+	    term->selstart.y = min(term->selanchor.y, selpoint.y);
+	    term->selend.y =   max(term->selanchor.y, selpoint.y);
+	}
+	sel_spread(term);
+    } else if ((bcooked == MBT_SELECT || bcooked == MBT_EXTEND) &&
+	       a == MA_RELEASE) {
+	if (term->selstate == DRAGGING) {
+	    /*
+	     * We've completed a selection. We now transfer the
+	     * data to the clipboard.
+	     */
+	    clipme(term, term->selstart, term->selend,
+		   (term->seltype == RECTANGULAR), FALSE);
+	    term->selstate = SELECTED;
+	} else
+	    term->selstate = NO_SELECTION;
+    } else if (bcooked == MBT_PASTE
+	       && (a == MA_CLICK
+#if MULTICLICK_ONLY_EVENT
+		   || a == MA_2CLK || a == MA_3CLK
+#endif
+		   )) {
+	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);
+}
+
+int format_arrow_key(char *buf, Terminal *term, int xkey, int ctrl)
+{
+    char *p = buf;
+
+    if (term->vt52_mode)
+	p += sprintf((char *) p, "\x1B%c", xkey);
+    else {
+	int app_flg = (term->app_cursor_keys && !term->no_applic_c);
+#if 0
+	/*
+	 * RDB: VT100 & VT102 manuals both state the app cursor
+	 * keys only work if the app keypad is on.
+	 *
+	 * SGT: That may well be true, but xterm disagrees and so
+	 * does at least one application, so I've #if'ed this out
+	 * and the behaviour is back to PuTTY's original: app
+	 * cursor and app keypad are independently switchable
+	 * modes. If anyone complains about _this_ I'll have to
+	 * put in a configurable option.
+	 */
+	if (!term->app_keypad_keys)
+	    app_flg = 0;
+#endif
+	/* Useful mapping of Ctrl-arrows */
+	if (ctrl)
+	    app_flg = !app_flg;
+
+	if (app_flg)
+	    p += sprintf((char *) p, "\x1BO%c", xkey);
+	else
+	    p += sprintf((char *) p, "\x1B[%c", xkey);
+    }
+
+    return p - buf;
+}
+
+void term_nopaste(Terminal *term)
+{
+    if (term->paste_len == 0)
+	return;
+    sfree(term->paste_buffer);
+    term->paste_buffer = NULL;
+    term->paste_len = 0;
+}
+
+static void deselect(Terminal *term)
+{
+    term->selstate = NO_SELECTION;
+    term->selstart.x = term->selstart.y = term->selend.x = term->selend.y = 0;
+}
+
+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)
+{
+    if (option == LD_ECHO)
+	return term->term_echoing;
+    if (option == LD_EDIT)
+	return term->term_editing;
+    return FALSE;
+}
+
+int term_data(Terminal *term, int is_stderr, const char *data, int len)
+{
+    bufchain_add(&term->inbuf, data, len);
+
+    if (!term->in_term_out) {
+	term->in_term_out = TRUE;
+	term_reset_cblink(term);
+	/*
+	 * During drag-selects, we do not process terminal input,
+	 * because the user will want the screen to hold still to
+	 * be selected.
+	 */
+	if (term->selstate != DRAGGING)
+	    term_out(term);
+	term->in_term_out = FALSE;
+    }
+
+    /*
+     * term_out() always completely empties inbuf. Therefore,
+     * there's no reason at all to return anything other than zero
+     * from this function, because there _can't_ be a question of
+     * the remote side needing to wait until term_out() has cleared
+     * a backlog.
+     *
+     * This is a slightly suboptimal way to deal with SSH-2 - in
+     * principle, the window mechanism would allow us to continue
+     * to accept data on forwarded ports and X connections even
+     * while the terminal processing was going slowly - but we
+     * can't do the 100% right thing without moving the terminal
+     * processing into a separate thread, and that might hurt
+     * portability. So we manage stdout buffering the old SSH-1 way:
+     * if the terminal processing goes slowly, the whole SSH
+     * connection stops accepting data until it's ready.
+     *
+     * In practice, I can't imagine this causing serious trouble.
+     */
+    return 0;
+}
+
+/*
+ * Write untrusted data to the terminal.
+ * The only control character that should be honoured is \n (which
+ * will behave as a CRLF).
+ */
+int term_data_untrusted(Terminal *term, const char *data, int len)
+{
+    int i;
+    /* FIXME: more sophisticated checking? */
+    for (i = 0; i < len; i++) {
+	if (data[i] == '\n')
+	    term_data(term, 1, "\r\n", 2);
+	else if (data[i] & 0x60)
+	    term_data(term, 1, data + i, 1);
+    }
+    return 0; /* assumes that term_data() always returns 0 */
+}
+
+void term_provide_logctx(Terminal *term, void *logctx)
+{
+    term->logctx = logctx;
+}
+
+void term_set_focus(Terminal *term, int has_focus)
+{
+    term->has_focus = has_focus;
+    term_schedule_cblink(term);
+}
+
+/*
+ * Provide "auto" settings for remote tty modes, suitable for an
+ * application with a terminal window.
+ */
+char *term_get_ttymode(Terminal *term, const char *mode)
+{
+    char *val = NULL;
+    if (strcmp(mode, "ERASE") == 0) {
+	val = term->bksp_is_delete ? "^?" : "^H";
+    }
+    /* FIXME: perhaps we should set ONLCR based on lfhascr as well? */
+    /* FIXME: or ECHO and friends based on local echo state? */
+    return dupstr(val);
+}
+
+struct term_userpass_state {
+    size_t curr_prompt;
+    int done_prompt;	/* printed out prompt yet? */
+    size_t pos;		/* cursor position */
+};
+
+/*
+ * Process some terminal data in the course of username/password
+ * input.
+ */
+int term_get_userpass_input(Terminal *term, prompts_t *p,
+			    unsigned char *in, int inlen)
+{
+    struct term_userpass_state *s = (struct term_userpass_state *)p->data;
+    if (!s) {
+	/*
+	 * First call. Set some stuff up.
+	 */
+	p->data = s = snew(struct term_userpass_state);
+	s->curr_prompt = 0;
+	s->done_prompt = 0;
+	/* We only print the `name' caption if we have to... */
+	if (p->name_reqd && p->name) {
+	    size_t l = strlen(p->name);
+	    term_data_untrusted(term, p->name, l);
+	    if (p->name[l-1] != '\n')
+		term_data_untrusted(term, "\n", 1);
+	}
+	/* ...but we always print any `instruction'. */
+	if (p->instruction) {
+	    size_t l = strlen(p->instruction);
+	    term_data_untrusted(term, p->instruction, l);
+	    if (p->instruction[l-1] != '\n')
+		term_data_untrusted(term, "\n", 1);
+	}
+	/*
+	 * Zero all the results, in case we abort half-way through.
+	 */
+	{
+	    int i;
+	    for (i = 0; i < (int)p->n_prompts; i++)
+                prompt_set_result(p->prompts[i], "");
+	}
+    }
+
+    while (s->curr_prompt < p->n_prompts) {
+
+	prompt_t *pr = p->prompts[s->curr_prompt];
+	int finished_prompt = 0;
+
+	if (!s->done_prompt) {
+	    term_data_untrusted(term, pr->prompt, strlen(pr->prompt));
+	    s->done_prompt = 1;
+	    s->pos = 0;
+	}
+
+	/* Breaking out here ensures that the prompt is printed even
+	 * if we're now waiting for user data. */
+	if (!in || !inlen) break;
+
+	/* FIXME: should we be using local-line-editing code instead? */
+	while (!finished_prompt && inlen) {
+	    char c = *in++;
+	    inlen--;
+	    switch (c) {
+	      case 10:
+	      case 13:
+		term_data(term, 0, "\r\n", 2);
+                prompt_ensure_result_size(pr, s->pos + 1);
+		pr->result[s->pos] = '\0';
+		/* go to next prompt, if any */
+		s->curr_prompt++;
+		s->done_prompt = 0;
+		finished_prompt = 1; /* break out */
+		break;
+	      case 8:
+	      case 127:
+		if (s->pos > 0) {
+		    if (pr->echo)
+			term_data(term, 0, "\b \b", 3);
+		    s->pos--;
+		}
+		break;
+	      case 21:
+	      case 27:
+		while (s->pos > 0) {
+		    if (pr->echo)
+			term_data(term, 0, "\b \b", 3);
+		    s->pos--;
+		}
+		break;
+	      case 3:
+	      case 4:
+		/* Immediate abort. */
+		term_data(term, 0, "\r\n", 2);
+		sfree(s);
+		p->data = NULL;
+		return 0; /* user abort */
+	      default:
+		/*
+		 * This simplistic check for printability is disabled
+		 * when we're doing password input, because some people
+		 * have control characters in their passwords.
+		 */
+		if (!pr->echo || (c >= ' ' && c <= '~') ||
+		     ((unsigned char) c >= 160)) {
+                    prompt_ensure_result_size(pr, s->pos + 1);
+		    pr->result[s->pos++] = c;
+		    if (pr->echo)
+			term_data(term, 0, &c, 1);
+		}
+		break;
+	    }
+	}
+	
+    }
+
+    if (s->curr_prompt < p->n_prompts) {
+	return -1; /* more data required */
+    } else {
+	sfree(s);
+	p->data = NULL;
+	return +1; /* all done */
+    }
+}
diff -uNr putty-0.65/terminal.h putty-url-0.65/terminal.h
--- putty-0.65/terminal.h	2015-07-25 12:22:48.000000000 +0200
+++ putty-url-0.65/terminal.h	2015-09-12 15:35:59.000000000 +0200
@@ -322,6 +322,11 @@
     int scroll_on_disp;
     int scroll_on_key;
     int xterm_256_colour;
+
+    /*
+     * HACK: PuTTY-url
+     */
+    int url_update;
 };
 
 #define in_utf(term) ((term)->utf || (term)->ucsdata->line_codepage==CP_UTF8)
diff -uNr putty-0.65/version.h putty-url-0.65/version.h
--- putty-0.65/version.h	2015-07-25 12:22:48.000000000 +0200
+++ putty-url-0.65/version.h	2015-09-12 15:37:21.000000000 +0200
@@ -1,5 +1,5 @@
 /* Generated by automated build script */
 #define RELEASE 0.65
-#define TEXTVER "Release 0.65"
-#define SSHVER "PuTTY-Release-0.65"
+#define TEXTVER "Release 0.65 (url)"
+#define SSHVER "PuTTY-Release-0.65-url"
 #define BINARY_VERSION 0,65,0,0
diff -uNr putty-0.65/windows/re_lib/regexp.c putty-url-0.65/windows/re_lib/regexp.c
--- putty-0.65/windows/re_lib/regexp.c	1970-01-01 01:00:00.000000000 +0100
+++ putty-url-0.65/windows/re_lib/regexp.c	2015-09-12 15:35:59.000000000 +0200
@@ -0,0 +1,1212 @@
+/*
+ * regcomp and regexec -- regsub and regerror are elsewhere
+ * @(#)regexp.c	1.3 of 18 April 87
+ *
+ *	Copyright (c) 1986 by University of Toronto.
+ *	Written by Henry Spencer.  Not derived from licensed software.
+ *
+ *	Permission is granted to anyone to use this software for any
+ *	purpose on any computer system, and to redistribute it freely,
+ *	subject to the following restrictions:
+ *
+ *	1. The author is not responsible for the consequences of use of
+ *		this software, no matter how awful, even if they arise
+ *		from defects in it.
+ *
+ *	2. The origin of this software must not be misrepresented, either
+ *		by explicit claim or by omission.
+ *
+ *	3. Altered versions must be plainly marked as such, and must not
+ *		be misrepresented as being the original software.
+ *
+ * Beware that some of this code is subtly aware of the way operator
+ * precedence is structured in regular expressions.  Serious changes in
+ * regular-expression syntax might require a total rethink.
+ */
+#include <stdio.h>
+#include <malloc.h>
+#include <string.h>
+
+#include "regexp.h"
+#include "regmagic.h"
+
+/*
+ * The "internal use only" fields in regexp.h are present to pass info from
+ * compile to execute that permits the execute phase to run lots faster on
+ * simple cases.  They are:
+ *
+ * regstart	char that must begin a match; '\0' if none obvious
+ * reganch	is the match anchored (at beginning-of-line only)?
+ * regmust	string (pointer into program) that match must include, or NULL
+ * regmlen	length of regmust string
+ *
+ * Regstart and reganch permit very fast decisions on suitable starting points
+ * for a match, cutting down the work a lot.  Regmust permits fast rejection
+ * of lines that cannot possibly match.  The regmust tests are costly enough
+ * that regcomp() supplies a regmust only if the r.e. contains something
+ * potentially expensive (at present, the only such thing detected is * or +
+ * at the start of the r.e., which can involve a lot of backup).  Regmlen is
+ * supplied because the test in regexec() needs it and regcomp() is computing
+ * it anyway.
+ */
+
+/*
+ * Structure for regexp "program".  This is essentially a linear encoding
+ * of a nondeterministic finite-state machine (aka syntax charts or
+ * "railroad normal form" in parsing technology).  Each node is an opcode
+ * plus a "next" pointer, possibly plus an operand.  "Next" pointers of
+ * all nodes except BRANCH implement concatenation; a "next" pointer with
+ * a BRANCH on both ends of it is connecting two alternatives.  (Here we
+ * have one of the subtle syntax dependencies:  an individual BRANCH (as
+ * opposed to a collection of them) is never concatenated with anything
+ * because of operator precedence.)  The operand of some types of node is
+ * a literal string; for others, it is a node leading into a sub-FSM.  In
+ * particular, the operand of a BRANCH node is the first node of the branch.
+ * (NB this is *not* a tree structure:  the tail of the branch connects
+ * to the thing following the set of BRANCHes.)  The opcodes are:
+ */
+
+/* definition	number	opnd?	meaning */
+#define	END	0	/* no	End of program. */
+#define	BOL	1	/* no	Match "" at beginning of line. */
+#define	EOL	2	/* no	Match "" at end of line. */
+#define	ANY	3	/* no	Match any one character. */
+#define	ANYOF	4	/* str	Match any character in this string. */
+#define	ANYBUT	5	/* str	Match any character not in this string. */
+#define	BRANCH	6	/* node	Match this alternative, or the next... */
+#define	BACK	7	/* no	Match "", "next" ptr points backward. */
+#define	EXACTLY	8	/* str	Match this string. */
+#define	NOTHING	9	/* no	Match empty string. */
+#define	STAR	10	/* node	Match this (simple) thing 0 or more times. */
+#define	PLUS	11	/* node	Match this (simple) thing 1 or more times. */
+#define	OPEN	20	/* no	Mark this point in input as start of #n. */
+			/*	OPEN+1 is number 1, etc. */
+#define	CLOSE	OPEN + NSUBEXP	/* no	Analogous to OPEN. */
+
+/*
+ * Opcode notes:
+ *
+ * BRANCH	The set of branches constituting a single choice are hooked
+ *		together with their "next" pointers, since precedence prevents
+ *		anything being concatenated to any individual branch.  The
+ *		"next" pointer of the last BRANCH in a choice points to the
+ *		thing following the whole choice.  This is also where the
+ *		final "next" pointer of each individual branch points; each
+ *		branch starts with the operand node of a BRANCH node.
+ *
+ * BACK		Normal "next" pointers all implicitly point forward; BACK
+ *		exists to make loop structures possible.
+ *
+ * STAR,PLUS	'?', and complex '*' and '+', are implemented as circular
+ *		BRANCH structures using BACK.  Simple cases (one character
+ *		per match) are implemented with STAR and PLUS for speed
+ *		and to minimize recursive plunges.
+ *
+ * OPEN,CLOSE	...are numbered at compile time.
+ */
+
+/*
+ * A node is one char of opcode followed by two chars of "next" pointer.
+ * "Next" pointers are stored as two 8-bit pieces, high order first.  The
+ * value is a positive offset from the opcode of the node containing it.
+ * An operand, if any, simply follows the node.  (Note that much of the
+ * code generation knows about this implicit relationship.)
+ *
+ * Using two bytes for the "next" pointer is vast overkill for most things,
+ * but allows patterns to get big without disasters.
+ */
+#define	OP(p)	(*(p))
+#define	NEXT(p)	(((*((p)+1)&0377)<<8) + (*((p)+2)&0377))
+#define	OPERAND(p)	((p) + 3)
+
+/*
+ * See regmagic.h for one further detail of program structure.
+ */
+
+
+/*
+ * Utility definitions.
+ */
+#ifndef CHARBITS
+#define	UCHARAT(p)	((int)*(unsigned char *)(p))
+#else
+#define	UCHARAT(p)	((int)*(p)&CHARBITS)
+#endif
+
+#define	FAIL(m)	{ regerror(m); return(NULL); }
+#define	ISMULT(c)	((c) == '*' || (c) == '+' || (c) == '?')
+#define	META	"^$.[()|?+*\\"
+
+/*
+ * Flags to be passed up and down.
+ */
+#define	HASWIDTH	01	/* Known never to match null string. */
+#define	SIMPLE		02	/* Simple enough to be STAR/PLUS operand. */
+#define	SPSTART		04	/* Starts with * or +. */
+#define	WORST		0	/* Worst case. */
+
+/*
+ * Global work variables for regcomp().
+ */
+static char *regparse;		/* Input-scan pointer. */
+static int regnpar;		/* () count. */
+static char regdummy;
+static char *regcode;		/* Code-emit pointer; &regdummy = don't. */
+static long regsize;		/* Code size. */
+
+/*
+ * Forward declarations for regcomp()'s friends.
+ */
+#ifndef STATIC
+#define	STATIC	static
+#endif
+STATIC char* reg( int paren, int* flagp);
+STATIC char* regbranch( int* flagp);
+STATIC char* regpiece( int* flagp);
+STATIC char* regatom( int* flagp);
+STATIC char* regnode( char op);
+STATIC char* regnext( char* p);
+STATIC void regc( char c);
+STATIC void reginsert( char op, char* opnd);
+STATIC void regtail( char* p, char* val);
+STATIC void regoptail( char* p,  char* val);
+#ifdef STRCSPN
+STATIC int strcspn();
+#endif
+
+static void (*regerror_func)( char* s) = 0;
+
+void regerror( char* s)
+{
+	if( regerror_func)
+		(*regerror_func)( s);
+}
+
+void set_regerror_func( void (*func)( char*))
+{
+	regerror_func = func;
+}
+
+/*
+ - regcomp - compile a regular expression into internal code
+ *
+ * We can't allocate space until we know how big the compiled form will be,
+ * but we can't compile it (and thus know how big it is) until we've got a
+ * place to put the code.  So we cheat:  we compile it twice, once with code
+ * generation turned off and size counting turned on, and once "for real".
+ * This also means that we don't allocate space until we are sure that the
+ * thing really will compile successfully, and we never have to move the
+ * code and thus invalidate pointers into it.  (Note that it has to be in
+ * one piece because free() must be able to free it all.)
+ *
+ * Beware that the optimization-preparation code in here knows about some
+ * of the structure of the compiled regexp.
+ */
+regexp *
+regcomp( char* exp)
+{
+	register regexp *r;
+	register char *scan;
+	register char *longest;
+	register int len;
+	int flags;
+
+	if (exp == NULL)
+		FAIL("NULL argument");
+
+	/* First pass: determine size, legality. */
+	regparse = exp;
+	regnpar = 1;
+	regsize = 0L;
+	regcode = &regdummy;
+	regc(MAGIC);
+	if (reg(0, &flags) == NULL)
+		return(NULL);
+
+	/* Small enough for pointer-storage convention? */
+	if (regsize >= 32767L)		/* Probably could be 65535L. */
+		FAIL("regexp too big");
+
+	/* Allocate space. */
+	r = (regexp *)malloc(sizeof(regexp) + (unsigned)regsize);
+	if (r == NULL)
+		FAIL("out of space");
+
+	/* Second pass: emit code. */
+	regparse = exp;
+	regnpar = 1;
+	regcode = r->program;
+	regc(MAGIC);
+	if (reg(0, &flags) == NULL)
+		return(NULL);
+
+	/* Dig out information for optimizations. */
+	r->regstart = '\0';	/* Worst-case defaults. */
+	r->reganch = 0;
+	r->regmust = NULL;
+	r->regmlen = 0;
+	scan = r->program+1;			/* First BRANCH. */
+	if (OP(regnext(scan)) == END) {		/* Only one top-level choice. */
+		scan = OPERAND(scan);
+
+		/* Starting-point info. */
+		if (OP(scan) == EXACTLY)
+			r->regstart = *OPERAND(scan);
+		else if (OP(scan) == BOL)
+			r->reganch++;
+
+		/*
+		 * If there's something expensive in the r.e., find the
+		 * longest literal string that must appear and make it the
+		 * regmust.  Resolve ties in favor of later strings, since
+		 * the regstart check works with the beginning of the r.e.
+		 * and avoiding duplication strengthens checking.  Not a
+		 * strong reason, but sufficient in the absence of others.
+		 */
+		if (flags&SPSTART) {
+			longest = NULL;
+			len = 0;
+			for (; scan != NULL; scan = regnext(scan))
+				if (OP(scan) == EXACTLY && strlen(OPERAND(scan)) >= len) {
+					longest = OPERAND(scan);
+					len = strlen(OPERAND(scan));
+				}
+			r->regmust = longest;
+			r->regmlen = len;
+		}
+	}
+
+	return(r);
+}
+
+/*
+ - reg - regular expression, i.e. main body or parenthesized thing
+ *
+ * Caller must absorb opening parenthesis.
+ *
+ * Combining parenthesis handling with the base level of regular expression
+ * is a trifle forced, but the need to tie the tails of the branches to what
+ * follows makes it hard to avoid.
+ */
+static char *
+reg( int paren, int* flagp)	/* paren - Parenthesized? */
+{
+	register char *ret;
+	register char *br;
+	register char *ender;
+	register int parno;
+	int flags;
+
+	*flagp = HASWIDTH;	/* Tentatively. */
+
+	/* Make an OPEN node, if parenthesized. */
+	if (paren) {
+		if (regnpar >= NSUBEXP)
+			FAIL("too many ()");
+		parno = regnpar;
+		regnpar++;
+		ret = regnode(OPEN+parno);
+	} else
+		ret = NULL;
+
+	/* Pick up the branches, linking them together. */
+	br = regbranch(&flags);
+	if (br == NULL)
+		return(NULL);
+	if (ret != NULL)
+		regtail(ret, br);	/* OPEN -> first. */
+	else
+		ret = br;
+	if (!(flags&HASWIDTH))
+		*flagp &= ~HASWIDTH;
+	*flagp |= flags&SPSTART;
+	while (*regparse == '|') {
+		regparse++;
+		br = regbranch(&flags);
+		if (br == NULL)
+			return(NULL);
+		regtail(ret, br);	/* BRANCH -> BRANCH. */
+		if (!(flags&HASWIDTH))
+			*flagp &= ~HASWIDTH;
+		*flagp |= flags&SPSTART;
+	}
+
+	/* Make a closing node, and hook it on the end. */
+	ender = regnode((paren) ? CLOSE+parno : END);	
+	regtail(ret, ender);
+
+	/* Hook the tails of the branches to the closing node. */
+	for (br = ret; br != NULL; br = regnext(br))
+		regoptail(br, ender);
+
+	/* Check for proper termination. */
+	if (paren && *regparse++ != ')') {
+		FAIL("unmatched ()");
+	} else if (!paren && *regparse != '\0') {
+		if (*regparse == ')') {
+			FAIL("unmatched ()");
+		} else
+			FAIL("junk on end");	/* "Can't happen". */
+		/* NOTREACHED */
+	}
+
+	return(ret);
+}
+
+/*
+ - regbranch - one alternative of an | operator
+ *
+ * Implements the concatenation operator.
+ */
+static char *
+regbranch( int* flagp)
+{
+	register char *ret;
+	register char *chain;
+	register char *latest;
+	int flags;
+
+	*flagp = WORST;		/* Tentatively. */
+
+	ret = regnode(BRANCH);
+	chain = NULL;
+	while (*regparse != '\0' && *regparse != '|' && *regparse != ')') {
+		latest = regpiece(&flags);
+		if (latest == NULL)
+			return(NULL);
+		*flagp |= flags&HASWIDTH;
+		if (chain == NULL)	/* First piece. */
+			*flagp |= flags&SPSTART;
+		else
+			regtail(chain, latest);
+		chain = latest;
+	}
+	if (chain == NULL)	/* Loop ran zero times. */
+		(void) regnode(NOTHING);
+
+	return(ret);
+}
+
+/*
+ - regpiece - something followed by possible [*+?]
+ *
+ * Note that the branching code sequences used for ? and the general cases
+ * of * and + are somewhat optimized:  they use the same NOTHING node as
+ * both the endmarker for their branch list and the body of the last branch.
+ * It might seem that this node could be dispensed with entirely, but the
+ * endmarker role is not redundant.
+ */
+static char *
+regpiece( int* flagp)
+{
+	register char *ret;
+	register char op;
+	register char *next;
+	int flags;
+
+	ret = regatom(&flags);
+	if (ret == NULL)
+		return(NULL);
+
+	op = *regparse;
+	if (!ISMULT(op)) {
+		*flagp = flags;
+		return(ret);
+	}
+
+	if (!(flags&HASWIDTH) && op != '?')
+		FAIL("*+ operand could be empty");
+	*flagp = (op != '+') ? (WORST|SPSTART) : (WORST|HASWIDTH);
+
+	if (op == '*' && (flags&SIMPLE))
+		reginsert(STAR, ret);
+	else if (op == '*') {
+		/* Emit x* as (x&|), where & means "self". */
+		reginsert(BRANCH, ret);			/* Either x */
+		regoptail(ret, regnode(BACK));		/* and loop */
+		regoptail(ret, ret);			/* back */
+		regtail(ret, regnode(BRANCH));		/* or */
+		regtail(ret, regnode(NOTHING));		/* null. */
+	} else if (op == '+' && (flags&SIMPLE))
+		reginsert(PLUS, ret);
+	else if (op == '+') {
+		/* Emit x+ as x(&|), where & means "self". */
+		next = regnode(BRANCH);			/* Either */
+		regtail(ret, next);
+		regtail(regnode(BACK), ret);		/* loop back */
+		regtail(next, regnode(BRANCH));		/* or */
+		regtail(ret, regnode(NOTHING));		/* null. */
+	} else if (op == '?') {
+		/* Emit x? as (x|) */
+		reginsert(BRANCH, ret);			/* Either x */
+		regtail(ret, regnode(BRANCH));		/* or */
+		next = regnode(NOTHING);		/* null. */
+		regtail(ret, next);
+		regoptail(ret, next);
+	}
+	regparse++;
+	if (ISMULT(*regparse))
+		FAIL("nested *?+");
+
+	return(ret);
+}
+
+/*
+ - regatom - the lowest level
+ *
+ * Optimization:  gobbles an entire sequence of ordinary characters so that
+ * it can turn them into a single node, which is smaller to store and
+ * faster to run.  Backslashed characters are exceptions, each becoming a
+ * separate node; the code is simpler that way and it's not worth fixing.
+ */
+static char *
+regatom( int* flagp)
+{
+	register char *ret;
+	int flags;
+
+	*flagp = WORST;		/* Tentatively. */
+
+	switch (*regparse++) {
+	case '^':
+		ret = regnode(BOL);
+		break;
+	case '$':
+		ret = regnode(EOL);
+		break;
+	case '.':
+		ret = regnode(ANY);
+		*flagp |= HASWIDTH|SIMPLE;
+		break;
+	case '[': {
+			int rclass;
+			int classend;
+
+			if (*regparse == '^') {	/* Complement of range. */
+				ret = regnode(ANYBUT);
+				regparse++;
+			} else
+				ret = regnode(ANYOF);
+			if (*regparse == ']' || *regparse == '-')
+				regc(*regparse++);
+			while (*regparse != '\0' && *regparse != ']') {
+				if (*regparse == '-') {
+					regparse++;
+					if (*regparse == ']' || *regparse == '\0')
+						regc('-');
+					else {
+						rclass = UCHARAT(regparse-2)+1;
+						classend = UCHARAT(regparse);
+						if (rclass > classend+1)
+							FAIL("invalid [] range");
+						for (; rclass <= classend; rclass++)
+							regc(rclass);
+						regparse++;
+					}
+				} else
+					regc(*regparse++);
+			}
+			regc('\0');
+			if (*regparse != ']')
+				FAIL("unmatched []");
+			regparse++;
+			*flagp |= HASWIDTH|SIMPLE;
+		}
+		break;
+	case '(':
+		ret = reg(1, &flags);
+		if (ret == NULL)
+			return(NULL);
+		*flagp |= flags&(HASWIDTH|SPSTART);
+		break;
+	case '\0':
+	case '|':
+	case ')':
+		FAIL("internal urp");	/* Supposed to be caught earlier. */
+		break;
+	case '?':
+	case '+':
+	case '*':
+		FAIL("?+* follows nothing");
+		break;
+	case '\\':
+		if (*regparse == '\0')
+			FAIL("trailing \\");
+		ret = regnode(EXACTLY);
+		regc(*regparse++);
+		regc('\0');
+		*flagp |= HASWIDTH|SIMPLE;
+		break;
+	default: {
+			register int len;
+			register char ender;
+
+			regparse--;
+			len = strcspn(regparse, META);
+			if (len <= 0)
+				FAIL("internal disaster");
+			ender = *(regparse+len);
+			if (len > 1 && ISMULT(ender))
+				len--;		/* Back off clear of ?+* operand. */
+			*flagp |= HASWIDTH;
+			if (len == 1)
+				*flagp |= SIMPLE;
+			ret = regnode(EXACTLY);
+			while (len > 0) {
+				regc(*regparse++);
+				len--;
+			}
+			regc('\0');
+		}
+		break;
+	}
+
+	return(ret);
+}
+
+/*
+ - regnode - emit a node
+ */
+static char *			/* Location. */
+regnode( char op)
+{
+	register char *ret;
+	register char *ptr;
+
+	ret = regcode;
+	if (ret == &regdummy) {
+		regsize += 3;
+		return(ret);
+	}
+
+	ptr = ret;
+	*ptr++ = op;
+	*ptr++ = '\0';		/* Null "next" pointer. */
+	*ptr++ = '\0';
+	regcode = ptr;
+
+	return(ret);
+}
+
+/*
+ - regc - emit (if appropriate) a byte of code
+ */
+static void
+regc( char b)
+{
+	if (regcode != &regdummy)
+		*regcode++ = b;
+	else
+		regsize++;
+}
+
+/*
+ - reginsert - insert an operator in front of already-emitted operand
+ *
+ * Means relocating the operand.
+ */
+static void
+reginsert( char op, char* opnd)
+{
+	register char *src;
+	register char *dst;
+	register char *place;
+
+	if (regcode == &regdummy) {
+		regsize += 3;
+		return;
+	}
+
+	src = regcode;
+	regcode += 3;
+	dst = regcode;
+	while (src > opnd)
+		*--dst = *--src;
+
+	place = opnd;		/* Op node, where operand used to be. */
+	*place++ = op;
+	*place++ = '\0';
+	*place++ = '\0';
+}
+
+/*
+ - regtail - set the next-pointer at the end of a node chain
+ */
+static void
+regtail( char* p, char* val)
+{
+	register char *scan;
+	register char *temp;
+	register int offset;
+
+	if (p == &regdummy)
+		return;
+
+	/* Find last node. */
+	scan = p;
+	for (;;) {
+		temp = regnext(scan);
+		if (temp == NULL)
+			break;
+		scan = temp;
+	}
+
+	if (OP(scan) == BACK)
+		offset = scan - val;
+	else
+		offset = val - scan;
+	*(scan+1) = (offset>>8)&0377;
+	*(scan+2) = offset&0377;
+}
+
+/*
+ - regoptail - regtail on operand of first argument; nop if operandless
+ */
+static void
+regoptail( char* p,  char* val)
+{
+	/* "Operandless" and "op != BRANCH" are synonymous in practice. */
+	if (p == NULL || p == &regdummy || OP(p) != BRANCH)
+		return;
+	regtail(OPERAND(p), val);
+}
+
+/*
+ * regexec and friends
+ */
+
+/*
+ * Global work variables for regexec().
+ */
+static char *reginput;		/* String-input pointer. */
+static char *regbol;		/* Beginning of input, for ^ check. */
+static char **regstartp;	/* Pointer to startp array. */
+static char **regendp;		/* Ditto for endp. */
+
+/*
+ * Forwards.
+ */
+STATIC int regtry( regexp* prog, char* string);
+STATIC int regmatch( char* prog);
+STATIC int regrepeat( char* p);
+
+#ifdef DEBUG
+int regnarrate = 0;
+void regdump( regexp* r);
+STATIC char* regprop( char* op);
+#endif
+
+/*
+ - regexec - match a regexp against a string
+ */
+int
+regexec( regexp* prog, char* string)
+{
+	register char *s;
+
+	/* Be paranoid... */
+	if (prog == NULL || string == NULL) {
+		regerror("NULL parameter");
+		return(0);
+	}
+
+	/* Check validity of program. */
+	if (UCHARAT(prog->program) != MAGIC) {
+		regerror("corrupted program");
+		return(0);
+	}
+
+	/* If there is a "must appear" string, look for it. */
+	if (prog->regmust != NULL) {
+		s = string;
+		while ((s = strchr(s, prog->regmust[0])) != NULL) {
+			if (strncmp(s, prog->regmust, prog->regmlen) == 0)
+				break;	/* Found it. */
+			s++;
+		}
+		if (s == NULL)	/* Not present. */
+			return(0);
+	}
+
+	/* Mark beginning of line for ^ . */
+	regbol = string;
+
+	/* Simplest case:  anchored match need be tried only once. */
+	if (prog->reganch)
+		return(regtry(prog, string));
+
+	/* Messy cases:  unanchored match. */
+	s = string;
+	if (prog->regstart != '\0')
+		/* We know what char it must start with. */
+		while ((s = strchr(s, prog->regstart)) != NULL) {
+			if (regtry(prog, s))
+				return(1);
+			s++;
+		}
+	else
+		/* We don't -- general case. */
+		do {
+			if (regtry(prog, s))
+				return(1);
+		} while (*s++ != '\0');
+
+	/* Failure. */
+	return(0);
+}
+
+/*
+ - regtry - try match at specific point
+ */
+static int			/* 0 failure, 1 success */
+regtry( regexp* prog, char* string)
+{
+	register int i;
+	register char **sp;
+	register char **ep;
+
+	reginput = string;
+	regstartp = prog->startp;
+	regendp = prog->endp;
+
+	sp = prog->startp;
+	ep = prog->endp;
+	for (i = NSUBEXP; i > 0; i--) {
+		*sp++ = NULL;
+		*ep++ = NULL;
+	}
+	if (regmatch(prog->program + 1)) {
+		prog->startp[0] = string;
+		prog->endp[0] = reginput;
+		return(1);
+	} else
+		return(0);
+}
+
+/*
+ - regmatch - main matching routine
+ *
+ * Conceptually the strategy is simple:  check to see whether the current
+ * node matches, call self recursively to see whether the rest matches,
+ * and then act accordingly.  In practice we make some effort to avoid
+ * recursion, in particular by going through "ordinary" nodes (that don't
+ * need to know whether the rest of the match failed) by a loop instead of
+ * by recursion.
+ */
+static int			/* 0 failure, 1 success */
+regmatch( char* prog)
+{
+	register char *scan;	/* Current node. */
+	char *next;		/* Next node. */
+
+	scan = prog;
+#ifdef DEBUG
+	if (scan != NULL && regnarrate)
+		fprintf(stderr, "%s(\n", regprop(scan));
+#endif
+	while (scan != NULL) {
+#ifdef DEBUG
+		if (regnarrate)
+			fprintf(stderr, "%s...\n", regprop(scan));
+#endif
+		next = regnext(scan);
+
+		switch (OP(scan)) {
+		case BOL:
+			if (reginput != regbol)
+				return(0);
+			break;
+		case EOL:
+			if (*reginput != '\0')
+				return(0);
+			break;
+		case ANY:
+			if (*reginput == '\0')
+				return(0);
+			reginput++;
+			break;
+		case EXACTLY: {
+				register int len;
+				register char *opnd;
+
+				opnd = OPERAND(scan);
+				/* Inline the first character, for speed. */
+				if (*opnd != *reginput)
+					return(0);
+				len = strlen(opnd);
+				if (len > 1 && strncmp(opnd, reginput, len) != 0)
+					return(0);
+				reginput += len;
+			}
+			break;
+		case ANYOF:
+			if (*reginput == '\0' || strchr(OPERAND(scan), *reginput) == NULL)
+				return(0);
+			reginput++;
+			break;
+		case ANYBUT:
+			if (*reginput == '\0' || strchr(OPERAND(scan), *reginput) != NULL)
+				return(0);
+			reginput++;
+			break;
+		case NOTHING:
+			break;
+		case BACK:
+			break;
+		case OPEN+1:
+		case OPEN+2:
+		case OPEN+3:
+		case OPEN+4:
+		case OPEN+5:
+		case OPEN+6:
+		case OPEN+7:
+		case OPEN+8:
+		case OPEN+9:
+		case OPEN+10:
+		case OPEN+11:
+		case OPEN+12:
+		case OPEN+13:
+		case OPEN+14:
+		case OPEN+15: {
+				register int no;
+				register char *save;
+
+				no = OP(scan) - OPEN;
+				save = reginput;
+
+				if (regmatch(next)) {
+					/*
+					 * Don't set startp if some later
+					 * invocation of the same parentheses
+					 * already has.
+					 */
+					if (regstartp[no] == NULL)
+						regstartp[no] = save;
+					return(1);
+				} else
+					return(0);
+			}
+			break;
+		case CLOSE+1:
+		case CLOSE+2:
+		case CLOSE+3:
+		case CLOSE+4:
+		case CLOSE+5:
+		case CLOSE+6:
+		case CLOSE+7:
+		case CLOSE+8:
+		case CLOSE+9:
+		case CLOSE+10:
+		case CLOSE+11:
+		case CLOSE+12:
+		case CLOSE+13:
+		case CLOSE+14:
+		case CLOSE+15: {
+				register int no;
+				register char *save;
+
+				no = OP(scan) - CLOSE;
+				save = reginput;
+
+				if (regmatch(next)) {
+					/*
+					 * Don't set endp if some later
+					 * invocation of the same parentheses
+					 * already has.
+					 */
+					if (regendp[no] == NULL)
+						regendp[no] = save;
+					return(1);
+				} else
+					return(0);
+			}
+			break;
+		case BRANCH: {
+				register char *save;
+
+				if (OP(next) != BRANCH)		/* No choice. */
+					next = OPERAND(scan);	/* Avoid recursion. */
+				else {
+					do {
+						save = reginput;
+						if (regmatch(OPERAND(scan)))
+							return(1);
+						reginput = save;
+						scan = regnext(scan);
+					} while (scan != NULL && OP(scan) == BRANCH);
+					return(0);
+					/* NOTREACHED */
+				}
+			}
+			break;
+		case STAR:
+		case PLUS: {
+				register char nextch;
+				register int no;
+				register char *save;
+				register int min;
+
+				/*
+				 * Lookahead to avoid useless match attempts
+				 * when we know what character comes next.
+				 */
+				nextch = '\0';
+				if (OP(next) == EXACTLY)
+					nextch = *OPERAND(next);
+				min = (OP(scan) == STAR) ? 0 : 1;
+				save = reginput;
+				no = regrepeat(OPERAND(scan));
+				while (no >= min) {
+					/* If it could work, try it. */
+					if (nextch == '\0' || *reginput == nextch)
+						if (regmatch(next))
+							return(1);
+					/* Couldn't or didn't -- back up. */
+					no--;
+					reginput = save + no;
+				}
+				return(0);
+			}
+			break;
+		case END:
+			return(1);	/* Success! */
+			break;
+		default:
+			regerror("memory corruption");
+			return(0);
+			break;
+		}
+
+		scan = next;
+	}
+
+	/*
+	 * We get here only if there's trouble -- normally "case END" is
+	 * the terminating point.
+	 */
+	regerror("corrupted pointers");
+	return(0);
+}
+
+/*
+ - regrepeat - repeatedly match something simple, report how many
+ */
+static int
+regrepeat( char* p)
+{
+	register int count = 0;
+	register char *scan;
+	register char *opnd;
+
+	scan = reginput;
+	opnd = OPERAND(p);
+	switch (OP(p)) {
+	case ANY:
+		count = strlen(scan);
+		scan += count;
+		break;
+	case EXACTLY:
+		while (*opnd == *scan) {
+			count++;
+			scan++;
+		}
+		break;
+	case ANYOF:
+		while (*scan != '\0' && strchr(opnd, *scan) != NULL) {
+			count++;
+			scan++;
+		}
+		break;
+	case ANYBUT:
+		while (*scan != '\0' && strchr(opnd, *scan) == NULL) {
+			count++;
+			scan++;
+		}
+		break;
+	default:		/* Oh dear.  Called inappropriately. */
+		regerror("internal foulup");
+		count = 0;	/* Best compromise. */
+		break;
+	}
+	reginput = scan;
+
+	return(count);
+}
+
+/*
+ - regnext - dig the "next" pointer out of a node
+ */
+static char *
+regnext( char* p)
+{
+	register int offset;
+
+	if (p == &regdummy)
+		return(NULL);
+
+	offset = NEXT(p);
+	if (offset == 0)
+		return(NULL);
+
+	if (OP(p) == BACK)
+		return(p-offset);
+	else
+		return(p+offset);
+}
+
+#ifdef DEBUG
+
+STATIC char *regprop();
+
+/*
+ - regdump - dump a regexp onto stdout in vaguely comprehensible form
+ */
+void
+regdump( regexp* r)
+{
+	register char *s;
+	register char op = EXACTLY;	/* Arbitrary non-END op. */
+	register char *next;
+
+	s = r->program + 1;
+	while (op != END) {	/* While that wasn't END last time... */
+		op = OP(s);
+		printf("%2d%s", s-r->program, regprop(s));	/* Where, what. */
+		next = regnext(s);
+		if (next == NULL)		/* Next ptr. */
+			printf("(0)");
+		else 
+			printf("(%d)", (s-r->program)+(next-s));
+		s += 3;
+		if (op == ANYOF || op == ANYBUT || op == EXACTLY) {
+			/* Literal string, where present. */
+			while (*s != '\0') {
+				putchar(*s);
+				s++;
+			}
+			s++;
+		}
+		putchar('\n');
+	}
+
+	/* Header fields of interest. */
+	if (r->regstart != '\0')
+		printf("start `%c' ", r->regstart);
+	if (r->reganch)
+		printf("anchored ");
+	if (r->regmust != NULL)
+		printf("must have \"%s\"", r->regmust);
+	printf("\n");
+}
+
+/*
+ - regprop - printable representation of opcode
+ */
+static char *
+regprop( char* op)
+{
+	register char *p;
+	static char buf[50];
+
+	(void) strcpy(buf, ":");
+
+	switch (OP(op)) {
+	case BOL:
+		p = "BOL";
+		break;
+	case EOL:
+		p = "EOL";
+		break;
+	case ANY:
+		p = "ANY";
+		break;
+	case ANYOF:
+		p = "ANYOF";
+		break;
+	case ANYBUT:
+		p = "ANYBUT";
+		break;
+	case BRANCH:
+		p = "BRANCH";
+		break;
+	case EXACTLY:
+		p = "EXACTLY";
+		break;
+	case NOTHING:
+		p = "NOTHING";
+		break;
+	case BACK:
+		p = "BACK";
+		break;
+	case END:
+		p = "END";
+		break;
+	case OPEN+1:
+	case OPEN+2:
+	case OPEN+3:
+	case OPEN+4:
+	case OPEN+5:
+	case OPEN+6:
+	case OPEN+7:
+	case OPEN+8:
+	case OPEN+9:
+		sprintf(buf+strlen(buf), "OPEN%d", OP(op)-OPEN);
+		p = NULL;
+		break;
+	case CLOSE+1:
+	case CLOSE+2:
+	case CLOSE+3:
+	case CLOSE+4:
+	case CLOSE+5:
+	case CLOSE+6:
+	case CLOSE+7:
+	case CLOSE+8:
+	case CLOSE+9:
+		sprintf(buf+strlen(buf), "CLOSE%d", OP(op)-CLOSE);
+		p = NULL;
+		break;
+	case STAR:
+		p = "STAR";
+		break;
+	case PLUS:
+		p = "PLUS";
+		break;
+	default:
+		regerror("corrupted opcode");
+		break;
+	}
+	if (p != NULL)
+		(void) strcat(buf, p);
+	return(buf);
+}
+#endif
+
+/*
+ * The following is provided for those people who do not have strcspn() in
+ * their C libraries.  They should get off their butts and do something
+ * about it; at least one public-domain implementation of those (highly
+ * useful) string routines has been published on Usenet.
+ */
+#ifdef STRCSPN
+/*
+ * strcspn - find length of initial segment of s1 consisting entirely
+ * of characters not from s2
+ */
+
+static int
+strcspn( char* s1, char* s2)
+{
+	register char *scan1;
+	register char *scan2;
+	register int count;
+
+	count = 0;
+	for (scan1 = s1; *scan1 != '\0'; scan1++) {
+		for (scan2 = s2; *scan2 != '\0';)	/* ++ moved down. */
+			if (*scan1 == *scan2++)
+				return(count);
+		count++;
+	}
+	return(count);
+}
+#endif
diff -uNr putty-0.65/windows/re_lib/regexp.h putty-url-0.65/windows/re_lib/regexp.h
--- putty-0.65/windows/re_lib/regexp.h	1970-01-01 01:00:00.000000000 +0100
+++ putty-url-0.65/windows/re_lib/regexp.h	2015-09-12 15:35:59.000000000 +0200
@@ -0,0 +1,22 @@
+/*
+ * Definitions etc. for regexp(3) routines.
+ *
+ * Caveat:  this is V8 regexp(3) [actually, a reimplementation thereof],
+ * not the System V one.
+ */
+#define NSUBEXP  16
+typedef struct regexp {
+	char *startp[NSUBEXP];
+	char *endp[NSUBEXP];
+	char regstart;		/* Internal use only. */
+	char reganch;		/* Internal use only. */
+	char *regmust;		/* Internal use only. */
+	int regmlen;		/* Internal use only. */
+	char program[1];	/* Unwarranted chumminess with compiler. */
+} regexp;
+
+regexp *regcomp( char* re);
+int regexec( regexp* r, char* str);
+void regsub( regexp* r, char* str, char* substr);
+void regerror( char* s);	/* for internal use only */
+void set_regerror_func( void (*func)( char*));
diff -uNr putty-0.65/windows/re_lib/regmagic.h putty-url-0.65/windows/re_lib/regmagic.h
--- putty-0.65/windows/re_lib/regmagic.h	1970-01-01 01:00:00.000000000 +0100
+++ putty-url-0.65/windows/re_lib/regmagic.h	2015-09-12 15:35:59.000000000 +0200
@@ -0,0 +1,5 @@
+/*
+ * The first byte of the regexp internal "program" is actually this magic
+ * number; the start node begins in the second byte.
+ */
+#define	MAGIC	0234
diff -uNr putty-0.65/windows/urlhack.c putty-url-0.65/windows/urlhack.c
--- putty-0.65/windows/urlhack.c	1970-01-01 01:00:00.000000000 +0100
+++ putty-url-0.65/windows/urlhack.c	2015-09-12 15:35:59.000000000 +0200
@@ -0,0 +1,233 @@
+/*
+ * HACK: PuttyTray / Nutty
+ * Hyperlink stuff: CORE FILE! Don't forget to COPY IT TO THE NEXT VERSION
+ */
+#include <windows.h>
+#include <string.h>
+#include "urlhack.h"
+#include "misc.h"
+#include "puttymem.h"
+
+
+extern int urlhack_mouse_old_x = -1, urlhack_mouse_old_y = -1, urlhack_current_region = -1;
+
+static text_region **link_regions;
+static unsigned int link_regions_len;
+static unsigned int link_regions_current_pos;
+
+extern const char* urlhack_default_regex = "(((https?|ftp):\\/\\/)|www\\.)(([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)|localhost|([a-zA-Z0-9\\-]+\\.)*[a-zA-Z0-9\\-]+\\.(com|net|org|info|biz|gov|name|edu|[a-zA-Z][a-zA-Z]))(:[0-9]+)?((\\/|\\?)[^ \"]*[^ ,;\\.:\">)])?";
+
+int urlhack_is_in_link_region(int x, int y)
+{
+    unsigned int i = 0;
+
+    while (i != link_regions_current_pos) {
+        text_region r = *link_regions[i];
+
+        if ((r.y0 == r.y1 && y == r.y0 && y == r.y1 && x >= r.x0 && x < r.x1) ||
+            (r.y0 != r.y1 && ((y == r.y0 && x >= r.x0) || (y == r.y1 && x < r.x1) || (y > r.y0 && y < r.y1))))
+            return i+1;
+        i++;
+    }
+    
+    return 0;
+}
+
+int urlhack_is_in_this_link_region(text_region r, int x, int y)
+{
+    if ((r.y0 == r.y1 && y == r.y0 && y == r.y1 && x >= r.x0 && x < r.x1) || 
+        (r.y0 != r.y1 && ((y == r.y0 && x >= r.x0) || (y == r.y1 && x < r.x1) || (y > r.y0 && y < r.y1)))) {
+        return 1;
+    }
+    
+    return 0;
+}
+
+text_region urlhack_get_link_bounds(int x, int y)
+{
+    unsigned int i = 0;
+    text_region region;
+
+    while (i != link_regions_current_pos) {
+        text_region r = *link_regions[i];
+
+        if ((r.y0 == r.y1 && y == r.y0 && y == r.y1 && x >= r.x0 && x < r.x1) ||
+            (r.y0 != r.y1 && ((y == r.y0 && x >= r.x0) || (y == r.y1 && x < r.x1) || (y > r.y0 && y < r.y1)))) {
+            return *link_regions[i];
+        }
+
+        i++;
+    }
+
+    region.x0 = region.y0 = region.x1 = region.y1 = -1;
+    return region;
+}
+
+text_region urlhack_get_link_region(int index)
+{
+    text_region region;
+
+    if (index < 0 || index >= link_regions_current_pos) {
+        region.x0 = region.y0 = region.x1 = region.y1 = -1;
+        return region;
+    }
+    else {
+        return *link_regions[index];
+    }
+}
+
+void urlhack_add_link_region(int x0, int y0, int x1, int y1)
+{
+    if (link_regions_current_pos >= link_regions_len) {
+        unsigned int i;
+        link_regions_len *= 2;
+        link_regions = sresize(link_regions, link_regions_len, text_region*);
+        for (i = link_regions_current_pos; i < link_regions_len; ++i) {
+            link_regions[i] = NULL;
+        }
+    }
+
+    link_regions[link_regions_current_pos] = snew(text_region);
+    link_regions[link_regions_current_pos]->x0 = x0;
+    link_regions[link_regions_current_pos]->y0 = y0;
+    link_regions[link_regions_current_pos]->x1 = x1;
+    link_regions[link_regions_current_pos]->y1 = y1;
+
+    link_regions_current_pos++;
+}
+
+void urlhack_launch_url(const char* app, const char *url)
+{
+    if (app) {
+        ShellExecute(NULL, NULL, app, url, NULL, SW_SHOWNORMAL);
+    } else {
+        ShellExecute(NULL, "open", url, NULL, NULL, SW_SHOWNORMAL);
+    }
+}
+
+int urlhack_is_ctrl_pressed()
+{
+    return HIWORD(GetAsyncKeyState(VK_CONTROL));
+}
+
+void urlhack_link_regions_clear()
+{
+    unsigned int i;
+    for (i = 0; i < link_regions_len; ++i) {
+        if (link_regions[i] != NULL) {
+            sfree(link_regions[i]);
+            link_regions[i] = NULL;
+        }
+    }
+    link_regions_current_pos = 0;
+}
+
+// Regular expression stuff
+
+static int urlhack_disabled = 0;
+static int is_regexp_compiled = 0;
+static regexp* urlhack_rx;
+
+static char *window_text;
+static int window_text_len;
+static int window_text_current_pos;
+
+void urlhack_init()
+{
+    unsigned int i;
+
+    /* 32 links seems like a sane base value */
+    link_regions_current_pos = 0;
+    link_regions_len = 32;
+    link_regions = snewn(link_regions_len, text_region*);
+
+    for (i = 0; i < link_regions_len; ++i) {
+        link_regions[i] = NULL;
+    }
+
+    /* Start with default terminal size */
+    window_text_len = 80*24+1;
+    window_text = snewn(window_text_len, char);
+    urlhack_reset();
+}
+
+void urlhack_cleanup()
+{
+    urlhack_link_regions_clear();
+    sfree(link_regions);
+    sfree(window_text);
+}
+
+void urlhack_putchar(char ch)
+{
+    if (window_text_current_pos >= window_text_len) {
+        window_text = sresize(window_text, 2 * window_text_len, char);
+        memset(window_text + window_text_current_pos, '\0', window_text_len - window_text_current_pos);
+        window_text_len *= 2;
+    }
+    window_text[window_text_current_pos++] = ch;
+}
+
+void urlhack_reset()
+{
+    memset(window_text, '\0', window_text_len);
+    window_text_current_pos = 0;
+}
+
+static void rtfm(char *error)
+{
+    char std_msg[] = "The following error occured when compiling the regular expression\n" \
+        "for the hyperlink support. Hyperlink detection is disabled during\n" \
+        "this session (restart PuTTY Tray to try again).\n\n";
+
+    char *full_msg = dupprintf("%s%s", std_msg, error);
+
+    MessageBox(0, full_msg, "PuTTY Tray Error", MB_OK);
+    free(full_msg);
+}
+
+void urlhack_set_regular_expression(const char* expression)
+{
+    is_regexp_compiled = 0;
+    urlhack_disabled = 0;
+
+    set_regerror_func(rtfm);
+    urlhack_rx = regcomp((char*)(expression));
+
+    if (urlhack_rx == 0) {
+        urlhack_disabled = 1;
+    }
+
+    is_regexp_compiled = 1;
+}
+
+void urlhack_go_find_me_some_hyperlinks(int screen_width)
+{
+    char* text_pos;
+
+    if (urlhack_disabled != 0) return;
+
+    if (is_regexp_compiled == 0) {
+        urlhack_set_regular_expression(urlhack_default_regex);
+    }
+
+    urlhack_link_regions_clear();
+
+    text_pos = window_text;
+
+    while (regexec(urlhack_rx, text_pos) == 1) {
+        char* start_pos = *urlhack_rx->startp[0] == ' ' ? urlhack_rx->startp[0] + 1: urlhack_rx->startp[0];
+
+        int x0 = (start_pos - window_text) % screen_width;
+        int y0 = (start_pos - window_text) / screen_width;
+        int x1 = (urlhack_rx->endp[0] - window_text) % screen_width;
+        int y1 = (urlhack_rx->endp[0] - window_text) / screen_width;
+
+        if (x0 >= screen_width) x0 = screen_width - 1;
+        if (x1 >= screen_width) x1 = screen_width - 1;
+
+        urlhack_add_link_region(x0, y0, x1, y1);
+
+        text_pos = urlhack_rx->endp[0] + 1;
+    }
+}
diff -uNr putty-0.65/windows/urlhack.h putty-url-0.65/windows/urlhack.h
--- putty-0.65/windows/urlhack.h	1970-01-01 01:00:00.000000000 +0100
+++ putty-url-0.65/windows/urlhack.h	2015-09-12 15:35:59.000000000 +0200
@@ -0,0 +1,31 @@
+/*
+ * HACK: PuttyTray / Nutty
+ * Hyperlink stuff: CORE FILE! Don't forget to COPY IT TO THE NEXT VERSION
+ */
+#ifndef _URLHACK_H
+#define _URLHACK_H
+
+#include "re_lib/regexp.h"
+
+typedef struct { int x0, y0, x1, y1; } text_region;
+
+const char* urlhack_default_regex;
+int urlhack_mouse_old_x, urlhack_mouse_old_y, urlhack_current_region;
+
+void urlhack_reset();
+void urlhack_go_find_me_some_hyperlinks(int screen_width);
+void urlhack_putchar(char ch);
+text_region urlhack_get_link_region(int index);
+
+int urlhack_is_in_link_region(int x, int y);
+int urlhack_is_in_this_link_region(text_region r, int x, int y);
+text_region urlhack_get_link_bounds(int x, int y);
+void urlhack_add_link_region(int x0, int y0, int x1, int y1);
+void urlhack_launch_url(const char* app, const char *url);
+int urlhack_is_ctrl_pressed();
+void urlhack_set_regular_expression(const char* expression);
+
+void urlhack_init();
+void urlhack_cleanup();
+
+#endif // _URLHACK_H
diff -uNr putty-0.65/windows/wincfg.c putty-url-0.65/windows/wincfg.c
--- putty-0.65/windows/wincfg.c	2015-07-25 12:22:48.000000000 +0200
+++ putty-url-0.65/windows/wincfg.c	2015-09-12 15:35:59.000000000 +0200
@@ -346,6 +346,51 @@
 		  conf_checkbox_handler,
 		  I(CONF_fullscreenonaltenter));
 
+	/*
+	 * HACK: PuttyTray / Nutty
+	 * Hyperlink stuff: The Window/Hyperlinks panel.
+	 */
+	ctrl_settitle(b, "Window/Hyperlinks", "Options controlling behaviour of hyperlinks");
+	s = ctrl_getset(b, "Window/Hyperlinks", "general", "General options for hyperlinks");
+
+	ctrl_radiobuttons(s, "Underline hyperlinks:", 'u', 1,
+			  HELPCTX(no_help),
+			  conf_radiobutton_handler,
+			  I(CONF_url_underline),
+			  "Always", I(URLHACK_UNDERLINE_ALWAYS),
+			  "When hovered upon", I(URLHACK_UNDERLINE_HOVER),
+			  "Never", I(URLHACK_UNDERLINE_NEVER),
+			  NULL);
+
+	ctrl_checkbox(s, "Use ctrl+click to launch hyperlinks", 'l',
+		  HELPCTX(no_help),
+		  conf_checkbox_handler, I(CONF_url_ctrl_click));
+
+	s = ctrl_getset(b, "Window/Hyperlinks", "browser", "Browser application");
+
+	ctrl_checkbox(s, "Use the default browser", 'b',
+		  HELPCTX(no_help),
+		  conf_checkbox_handler, I(CONF_url_defbrowser));
+
+	ctrl_filesel(s, "or specify an application to open hyperlinks with:", 's',
+		"Application (*.exe)\0*.exe\0All files (*.*)\0*.*\0\0", TRUE,
+		"Select executable to open hyperlinks with", HELPCTX(no_help),
+		 conf_filesel_handler, I(CONF_url_browser));
+
+	s = ctrl_getset(b, "Window/Hyperlinks", "regexp", "Regular expression");
+
+	ctrl_checkbox(s, "Use the default regular expression", 'r',
+		  HELPCTX(no_help),
+		  conf_checkbox_handler, I(CONF_url_defregex));
+
+	ctrl_editbox(s, "or specify your own:", NO_SHORTCUT, 100,
+		 HELPCTX(no_help),
+		 conf_editbox_handler, I(CONF_url_regex),
+		 I(1));
+
+	ctrl_text(s, "The single white space will be cropped in front of the link, if exists.",
+		  HELPCTX(no_help));
+
     /*
      * Windows supports a local-command proxy. This also means we
      * must adjust the text on the `Telnet command' control.
diff -uNr putty-0.65/windows/windefs.c putty-url-0.65/windows/windefs.c
--- putty-0.65/windows/windefs.c	2015-07-25 12:22:48.000000000 +0200
+++ putty-url-0.65/windows/windefs.c	2015-09-12 15:35:59.000000000 +0200
@@ -8,10 +8,21 @@
 
 FontSpec *platform_default_fontspec(const char *name)
 {
-    if (!strcmp(name, "Font"))
+    /*
+     * HACK: PuTTY-url
+     * Set font to Consolas on Windows Vista and above
+     */
+    OSVERSIONINFO versioninfo;
+    versioninfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
+    GetVersionEx(&versioninfo);
+
+    if (!strcmp(name, "Font")) {
+        if (versioninfo.dwMajorVersion >= 6) {
+            return fontspec_new("Consolas", 0, 10, ANSI_CHARSET);
+        }
         return fontspec_new("Courier New", 0, 10, ANSI_CHARSET);
-    else
-        return fontspec_new("", 0, 0, 0);
+    }
+    return fontspec_new("", 0, 0, 0);
 }
 
 Filename *platform_default_filename(const char *name)
diff -uNr putty-0.65/windows/window.c putty-url-0.65/windows/window.c
--- putty-0.65/windows/window.c	2015-07-25 12:22:48.000000000 +0200
+++ putty-url-0.65/windows/window.c	2015-09-12 15:35:59.000000000 +0200
@@ -10,6 +10,11 @@
 #include <limits.h>
 #include <assert.h>
 
+/*
+ * HACK: PuttyTray / Nutty
+ */ 
+#include "urlhack.h"
+
 #ifndef NO_MULTIMON
 #define COMPILE_MULTIMON_STUBS
 #endif
@@ -218,6 +223,11 @@
 const int share_can_be_downstream = TRUE;
 const int share_can_be_upstream = TRUE;
 
+/*
+ * HACK: PuttyTray / Nutty
+ */ 
+static int urlhack_cursor_is_hand = 0;
+
 /* Dummy routine, only required in plink. */
 void ldisc_update(void *frontend, int echo, int edit)
 {
@@ -374,6 +384,8 @@
 
     conf = conf_new();
 
+    urlhack_init();
+
     /*
      * Initialize COM.
      */
@@ -825,6 +837,14 @@
 
     start_backend();
 
+	/*
+	 * HACK: PuttyTray / Nutty
+	 * Hyperlink stuff: Set the regular expression
+	 */
+	if (conf_get_int(term->conf, CONF_url_defregex) == 0) {
+		urlhack_set_regular_expression(conf_get_str(term->conf, CONF_url_regex));
+	}
+
     /*
      * Set up the initial input locale.
      */
@@ -934,6 +954,7 @@
     /*
      * Clean up.
      */
+    urlhack_cleanup();
     deinit_fonts();
     sfree(logpal);
     if (pal)
@@ -2073,6 +2094,11 @@
     static UINT last_mousemove = 0;
     int resize_action;
 
+	/*
+	 * HACK: PuttyTray / Nutty
+	 */ 
+	POINT cursor_pt;
+
     switch (message) {
       case WM_TIMER:
 	if ((UINT_PTR)wParam == TIMING_TIMER_ID) {
@@ -2274,6 +2300,16 @@
 		if (back)
 		    back->reconfig(backhandle, conf);
 
+		/*
+		 * HACK: PuttyTray / Nutty
+		 * Reconfigure
+		 */
+		if (conf_get_int(conf, CONF_url_defregex) == 0) {
+			urlhack_set_regular_expression(conf_get_str(conf, CONF_url_regex));
+		}
+		term->url_update = TRUE;
+		term_update(term);
+
 		/* Screen size changed ? */
 		if (conf_get_int(conf, CONF_height) !=
 		    conf_get_int(prev_conf, CONF_height) ||
@@ -2592,6 +2628,37 @@
 	 */
 	noise_ultralight(lParam);
 
+	/*
+	 * HACK: PuttyTray / Nutty
+	 * Hyperlink stuff: Change cursor type if hovering over link
+	 */ 
+	if (urlhack_mouse_old_x != TO_CHR_X(X_POS(lParam)) || urlhack_mouse_old_y != TO_CHR_Y(Y_POS(lParam))) {
+		urlhack_mouse_old_x = TO_CHR_X(X_POS(lParam));
+		urlhack_mouse_old_y = TO_CHR_Y(Y_POS(lParam));
+
+		if ((!conf_get_int(term->conf, CONF_url_ctrl_click) || urlhack_is_ctrl_pressed()) &&
+			urlhack_is_in_link_region(urlhack_mouse_old_x, urlhack_mouse_old_y)) {
+				if (urlhack_cursor_is_hand == 0) {
+					SetClassLong(hwnd, GCL_HCURSOR, LoadCursor(NULL, IDC_HAND));
+					urlhack_cursor_is_hand = 1;
+					term_update(term); // Force the terminal to update, otherwise the underline will not show (bug somewhere, this is an ugly fix)
+				}
+		}
+		else if (urlhack_cursor_is_hand == 1) {
+			SetClassLong(hwnd, GCL_HCURSOR, LoadCursor(NULL, IDC_IBEAM));
+			urlhack_cursor_is_hand = 0;
+			term_update(term); // Force the terminal to update, see above
+		}
+
+		// If mouse jumps from one link directly into another, we need a forced terminal update too
+		if (urlhack_is_in_link_region(urlhack_mouse_old_x, urlhack_mouse_old_y) != urlhack_current_region) {
+			urlhack_current_region = urlhack_is_in_link_region(urlhack_mouse_old_x, urlhack_mouse_old_y);
+			term_update(term);
+		}
+
+	}
+	/* HACK: PuttyTray / Nutty : END */
+
 	if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON) &&
 	    GetCapture() == hwnd) {
 	    Mouse_Button b;
@@ -3062,10 +3129,39 @@
 	    }
 	}
 	return FALSE;
-      case WM_KEYDOWN:
-      case WM_SYSKEYDOWN:
-      case WM_KEYUP:
-      case WM_SYSKEYUP:
+
+	/*
+	 * HACK: PuttyTray / Nutty
+	 * Hyperlink stuff: Change cursor if we are in ctrl+click link mode
+	 *
+	 * WARNING: Spans over multiple CASEs
+	 */
+	case WM_KEYDOWN:
+		if (wParam == VK_CONTROL && conf_get_int(term->conf, CONF_url_ctrl_click)) {
+			GetCursorPos(&cursor_pt);
+			ScreenToClient(hwnd, &cursor_pt);
+
+			if (urlhack_is_in_link_region(TO_CHR_X(cursor_pt.x), TO_CHR_Y(cursor_pt.y))) {
+				SetCursor(LoadCursor(NULL, IDC_HAND));
+				term_update(term);
+			}
+		
+			goto KEY_END;
+		}	
+
+	case WM_KEYUP:
+		if (wParam == VK_CONTROL && conf_get_int(term->conf, CONF_url_ctrl_click)) {
+			SetCursor(LoadCursor(NULL, IDC_IBEAM));
+			term_update(term);
+		
+			goto KEY_END;
+		}
+	KEY_END:
+
+	case WM_SYSKEYDOWN:
+	case WM_SYSKEYUP:
+	/* HACK: PuttyTray / Nutty : END */
+
 	/*
 	 * Add the scan code and keypress timing to the random
 	 * number noise.
diff -uNr putty-0.65/windows/window.c.orig putty-url-0.65/windows/window.c.orig
--- putty-0.65/windows/window.c.orig	1970-01-01 01:00:00.000000000 +0100
+++ putty-url-0.65/windows/window.c.orig	2015-07-25 12:22:48.000000000 +0200
@@ -0,0 +1,5808 @@
+/*
+ * window.c - the PuTTY(tel) main program, which runs a PuTTY terminal
+ * emulator and backend in a window.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <time.h>
+#include <limits.h>
+#include <assert.h>
+
+#ifndef NO_MULTIMON
+#define COMPILE_MULTIMON_STUBS
+#endif
+
+#define PUTTY_DO_GLOBALS	       /* actually _define_ globals */
+#include "putty.h"
+#include "terminal.h"
+#include "storage.h"
+#include "win_res.h"
+
+#ifndef NO_MULTIMON
+#include <multimon.h>
+#endif
+
+#include <imm.h>
+#include <commctrl.h>
+#include <richedit.h>
+#include <mmsystem.h>
+
+/* From MSDN: In the WM_SYSCOMMAND message, the four low-order bits of
+ * wParam are used by Windows, and should be masked off, so we shouldn't
+ * attempt to store information in them. Hence all these identifiers have
+ * the low 4 bits clear. Also, identifiers should < 0xF000. */
+
+#define IDM_SHOWLOG   0x0010
+#define IDM_NEWSESS   0x0020
+#define IDM_DUPSESS   0x0030
+#define IDM_RESTART   0x0040
+#define IDM_RECONF    0x0050
+#define IDM_CLRSB     0x0060
+#define IDM_RESET     0x0070
+#define IDM_HELP      0x0140
+#define IDM_ABOUT     0x0150
+#define IDM_SAVEDSESS 0x0160
+#define IDM_COPYALL   0x0170
+#define IDM_FULLSCREEN	0x0180
+#define IDM_PASTE     0x0190
+#define IDM_SPECIALSEP 0x0200
+
+#define IDM_SPECIAL_MIN 0x0400
+#define IDM_SPECIAL_MAX 0x0800
+
+#define IDM_SAVED_MIN 0x1000
+#define IDM_SAVED_MAX 0x5000
+#define MENU_SAVED_STEP 16
+/* Maximum number of sessions on saved-session submenu */
+#define MENU_SAVED_MAX ((IDM_SAVED_MAX-IDM_SAVED_MIN) / MENU_SAVED_STEP)
+
+#define WM_IGNORE_CLIP (WM_APP + 2)
+#define WM_FULLSCR_ON_MAX (WM_APP + 3)
+#define WM_AGENT_CALLBACK (WM_APP + 4)
+#define WM_GOT_CLIPDATA (WM_APP + 6)
+
+/* Needed for Chinese support and apparently not always defined. */
+#ifndef VK_PROCESSKEY
+#define VK_PROCESSKEY 0xE5
+#endif
+
+/* Mouse wheel support. */
+#ifndef WM_MOUSEWHEEL
+#define WM_MOUSEWHEEL 0x020A	       /* not defined in earlier SDKs */
+#endif
+#ifndef WHEEL_DELTA
+#define WHEEL_DELTA 120
+#endif
+
+static Mouse_Button translate_button(Mouse_Button button);
+static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
+static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
+			unsigned char *output);
+static void conftopalette(void);
+static void systopalette(void);
+static void init_palette(void);
+static void init_fonts(int, int);
+static void another_font(int);
+static void deinit_fonts(void);
+static void set_input_locale(HKL);
+static void update_savedsess_menu(void);
+static void init_winfuncs(void);
+
+static int is_full_screen(void);
+static void make_full_screen(void);
+static void clear_full_screen(void);
+static void flip_full_screen(void);
+static int process_clipdata(HGLOBAL clipdata, int unicode);
+
+/* Window layout information */
+static void reset_window(int);
+static int extra_width, extra_height;
+static int font_width, font_height, font_dualwidth, font_varpitch;
+static int offset_width, offset_height;
+static int was_zoomed = 0;
+static int prev_rows, prev_cols;
+  
+static void flash_window(int mode);
+static void sys_cursor_update(void);
+static int get_fullscreen_rect(RECT * ss);
+
+static int caret_x = -1, caret_y = -1;
+
+static int kbd_codepage;
+
+static void *ldisc;
+static Backend *back;
+static void *backhandle;
+
+static struct unicode_data ucsdata;
+static int session_closed;
+static int reconfiguring = FALSE;
+
+static const struct telnet_special *specials = NULL;
+static HMENU specials_menu = NULL;
+static int n_specials = 0;
+
+static wchar_t *clipboard_contents;
+static size_t clipboard_length;
+
+#define TIMING_TIMER_ID 1234
+static long timing_next_time;
+
+static struct {
+    HMENU menu;
+} popup_menus[2];
+enum { SYSMENU, CTXMENU };
+static HMENU savedsess_menu;
+
+struct wm_netevent_params {
+    /* Used to pass data to wm_netevent_callback */
+    WPARAM wParam;
+    LPARAM lParam;
+};
+
+Conf *conf;			       /* exported to windlg.c */
+
+static void conf_cache_data(void);
+int cursor_type;
+int vtmode;
+
+static struct sesslist sesslist;       /* for saved-session menu */
+
+struct agent_callback {
+    void (*callback)(void *, void *, int);
+    void *callback_ctx;
+    void *data;
+    int len;
+};
+
+#define FONT_NORMAL 0
+#define FONT_BOLD 1
+#define FONT_UNDERLINE 2
+#define FONT_BOLDUND 3
+#define FONT_WIDE	0x04
+#define FONT_HIGH	0x08
+#define FONT_NARROW	0x10
+
+#define FONT_OEM 	0x20
+#define FONT_OEMBOLD 	0x21
+#define FONT_OEMUND 	0x22
+#define FONT_OEMBOLDUND 0x23
+
+#define FONT_MAXNO 	0x40
+#define FONT_SHIFT	5
+static HFONT fonts[FONT_MAXNO];
+static LOGFONT lfont;
+static int fontflag[FONT_MAXNO];
+static enum {
+    BOLD_NONE, BOLD_SHADOW, BOLD_FONT
+} bold_font_mode;
+static int bold_colours;
+static enum {
+    UND_LINE, UND_FONT
+} und_mode;
+static int descent;
+
+#define NCFGCOLOURS 22
+#define NEXTCOLOURS 240
+#define NALLCOLOURS (NCFGCOLOURS + NEXTCOLOURS)
+static COLORREF colours[NALLCOLOURS];
+static HPALETTE pal;
+static LPLOGPALETTE logpal;
+static RGBTRIPLE defpal[NALLCOLOURS];
+
+static HBITMAP caretbm;
+
+static int dbltime, lasttime, lastact;
+static Mouse_Button lastbtn;
+
+/* this allows xterm-style mouse handling. */
+static int send_raw_mouse = 0;
+static int wheel_accumulator = 0;
+
+static int busy_status = BUSY_NOT;
+
+static char *window_name, *icon_name;
+
+static int compose_state = 0;
+
+static UINT wm_mousewheel = WM_MOUSEWHEEL;
+
+#define IS_HIGH_VARSEL(wch1, wch2) \
+    ((wch1) == 0xDB40 && ((wch2) >= 0xDD00 && (wch2) <= 0xDDEF))
+#define IS_LOW_VARSEL(wch) \
+    (((wch) >= 0x180B && (wch) <= 0x180D) || /* MONGOLIAN FREE VARIATION SELECTOR */ \
+     ((wch) >= 0xFE00 && (wch) <= 0xFE0F)) /* VARIATION SELECTOR 1-16 */
+
+const int share_can_be_downstream = TRUE;
+const int share_can_be_upstream = TRUE;
+
+/* Dummy routine, only required in plink. */
+void ldisc_update(void *frontend, int echo, int edit)
+{
+}
+
+char *get_ttymode(void *frontend, const char *mode)
+{
+    return term_get_ttymode(term, mode);
+}
+
+static void start_backend(void)
+{
+    const char *error;
+    char msg[1024], *title;
+    char *realhost;
+    int i;
+
+    /*
+     * Select protocol. This is farmed out into a table in a
+     * separate file to enable an ssh-free variant.
+     */
+    back = backend_from_proto(conf_get_int(conf, CONF_protocol));
+    if (back == NULL) {
+	char *str = dupprintf("%s Internal Error", appname);
+	MessageBox(NULL, "Unsupported protocol number found",
+		   str, MB_OK | MB_ICONEXCLAMATION);
+	sfree(str);
+	cleanup_exit(1);
+    }
+
+    error = back->init(NULL, &backhandle, conf,
+		       conf_get_str(conf, CONF_host),
+		       conf_get_int(conf, CONF_port),
+		       &realhost,
+		       conf_get_int(conf, CONF_tcp_nodelay),
+		       conf_get_int(conf, CONF_tcp_keepalives));
+    back->provide_logctx(backhandle, logctx);
+    if (error) {
+	char *str = dupprintf("%s Error", appname);
+	sprintf(msg, "Unable to open connection to\n"
+		"%.800s\n" "%s", conf_dest(conf), error);
+	MessageBox(NULL, msg, str, MB_ICONERROR | MB_OK);
+	sfree(str);
+	exit(0);
+    }
+    window_name = icon_name = NULL;
+    title = conf_get_str(conf, CONF_wintitle);
+    if (!*title) {
+	sprintf(msg, "%s - %s", realhost, appname);
+	title = msg;
+    }
+    sfree(realhost);
+    set_title(NULL, title);
+    set_icon(NULL, title);
+
+    /*
+     * Connect the terminal to the backend for resize purposes.
+     */
+    term_provide_resize_fn(term, back->size, backhandle);
+
+    /*
+     * Set up a line discipline.
+     */
+    ldisc = ldisc_create(conf, term, back, backhandle, NULL);
+
+    /*
+     * Destroy the Restart Session menu item. (This will return
+     * failure if it's already absent, as it will be the very first
+     * time we call this function. We ignore that, because as long
+     * as the menu item ends up not being there, we don't care
+     * whether it was us who removed it or not!)
+     */
+    for (i = 0; i < lenof(popup_menus); i++) {
+	DeleteMenu(popup_menus[i].menu, IDM_RESTART, MF_BYCOMMAND);
+    }
+
+    session_closed = FALSE;
+}
+
+static void close_session(void *ignored_context)
+{
+    char morestuff[100];
+    int i;
+
+    session_closed = TRUE;
+    sprintf(morestuff, "%.70s (inactive)", appname);
+    set_icon(NULL, morestuff);
+    set_title(NULL, morestuff);
+
+    if (ldisc) {
+	ldisc_free(ldisc);
+	ldisc = NULL;
+    }
+    if (back) {
+	back->free(backhandle);
+	backhandle = NULL;
+	back = NULL;
+        term_provide_resize_fn(term, NULL, NULL);
+	update_specials_menu(NULL);
+    }
+
+    /*
+     * Show the Restart Session menu item. Do a precautionary
+     * delete first to ensure we never end up with more than one.
+     */
+    for (i = 0; i < lenof(popup_menus); i++) {
+	DeleteMenu(popup_menus[i].menu, IDM_RESTART, MF_BYCOMMAND);
+	InsertMenu(popup_menus[i].menu, IDM_DUPSESS, MF_BYCOMMAND | MF_ENABLED,
+		   IDM_RESTART, "&Restart Session");
+    }
+}
+
+int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
+{
+    WNDCLASS wndclass;
+    MSG msg;
+    HRESULT hr;
+    int guess_width, guess_height;
+
+    hinst = inst;
+    hwnd = NULL;
+    flags = FLAG_VERBOSE | FLAG_INTERACTIVE;
+
+    sk_init();
+
+    InitCommonControls();
+
+    /* Ensure a Maximize setting in Explorer doesn't maximise the
+     * config box. */
+    defuse_showwindow();
+
+    if (!init_winver())
+    {
+	char *str = dupprintf("%s Fatal Error", appname);
+	MessageBox(NULL, "Windows refuses to report a version",
+		   str, MB_OK | MB_ICONEXCLAMATION);
+	sfree(str);
+	return 1;
+    }
+
+    /*
+     * If we're running a version of Windows that doesn't support
+     * WM_MOUSEWHEEL, find out what message number we should be
+     * using instead.
+     */
+    if (osVersion.dwMajorVersion < 4 ||
+	(osVersion.dwMajorVersion == 4 && 
+	 osVersion.dwPlatformId != VER_PLATFORM_WIN32_NT))
+	wm_mousewheel = RegisterWindowMessage("MSWHEEL_ROLLMSG");
+
+    init_help();
+
+    init_winfuncs();
+
+    conf = conf_new();
+
+    /*
+     * Initialize COM.
+     */
+    hr = CoInitialize(NULL);
+    if (hr != S_OK && hr != S_FALSE) {
+        char *str = dupprintf("%s Fatal Error", appname);
+	MessageBox(NULL, "Failed to initialize COM subsystem",
+		   str, MB_OK | MB_ICONEXCLAMATION);
+	sfree(str);
+	return 1;
+    }
+
+    /*
+     * Process the command line.
+     */
+    {
+	char *p;
+	int got_host = 0;
+	/* By default, we bring up the config dialog, rather than launching
+	 * a session. This gets set to TRUE if something happens to change
+	 * that (e.g., a hostname is specified on the command-line). */
+	int allow_launch = FALSE;
+
+	default_protocol = be_default_protocol;
+	/* Find the appropriate default port. */
+	{
+	    Backend *b = backend_from_proto(default_protocol);
+	    default_port = 0; /* illegal */
+	    if (b)
+		default_port = b->default_port;
+	}
+	conf_set_int(conf, CONF_logtype, LGTYP_NONE);
+
+	do_defaults(NULL, conf);
+
+	p = cmdline;
+
+	/*
+	 * Process a couple of command-line options which are more
+	 * easily dealt with before the line is broken up into words.
+	 * These are the old-fashioned but convenient @sessionname and
+	 * the internal-use-only &sharedmemoryhandle, neither of which
+	 * are combined with anything else.
+	 */
+	while (*p && isspace(*p))
+	    p++;
+	if (*p == '@') {
+            /*
+             * An initial @ means that the whole of the rest of the
+             * command line should be treated as the name of a saved
+             * session, with _no quoting or escaping_. This makes it a
+             * very convenient means of automated saved-session
+             * launching, via IDM_SAVEDSESS or Windows 7 jump lists.
+             */
+	    int i = strlen(p);
+	    while (i > 1 && isspace(p[i - 1]))
+		i--;
+	    p[i] = '\0';
+	    do_defaults(p + 1, conf);
+	    if (!conf_launchable(conf) && !do_config()) {
+		cleanup_exit(0);
+	    }
+	    allow_launch = TRUE;    /* allow it to be launched directly */
+	} else if (*p == '&') {
+	    /*
+	     * An initial & means we've been given a command line
+	     * containing the hex value of a HANDLE for a file
+	     * mapping object, which we must then interpret as a
+	     * serialised Conf.
+	     */
+	    HANDLE filemap;
+	    void *cp;
+	    unsigned cpsize;
+	    if (sscanf(p + 1, "%p:%u", &filemap, &cpsize) == 2 &&
+		(cp = MapViewOfFile(filemap, FILE_MAP_READ,
+				    0, 0, cpsize)) != NULL) {
+		conf_deserialise(conf, cp, cpsize);
+		UnmapViewOfFile(cp);
+		CloseHandle(filemap);
+	    } else if (!do_config()) {
+		cleanup_exit(0);
+	    }
+	    allow_launch = TRUE;
+	} else {
+	    /*
+	     * Otherwise, break up the command line and deal with
+	     * it sensibly.
+	     */
+	    int argc, i;
+	    char **argv;
+	    
+	    split_into_argv(cmdline, &argc, &argv, NULL);
+
+	    for (i = 0; i < argc; i++) {
+		char *p = argv[i];
+		int ret;
+
+		ret = cmdline_process_param(p, i+1<argc?argv[i+1]:NULL,
+					    1, conf);
+		if (ret == -2) {
+		    cmdline_error("option \"%s\" requires an argument", p);
+		} else if (ret == 2) {
+		    i++;	       /* skip next argument */
+		} else if (ret == 1) {
+		    continue;	       /* nothing further needs doing */
+		} else if (!strcmp(p, "-cleanup") ||
+			   !strcmp(p, "-cleanup-during-uninstall")) {
+		    /*
+		     * `putty -cleanup'. Remove all registry
+		     * entries associated with PuTTY, and also find
+		     * and delete the random seed file.
+		     */
+		    char *s1, *s2;
+		    /* Are we being invoked from an uninstaller? */
+		    if (!strcmp(p, "-cleanup-during-uninstall")) {
+			s1 = dupprintf("Remove saved sessions and random seed file?\n"
+				       "\n"
+				       "If you hit Yes, ALL Registry entries associated\n"
+				       "with %s will be removed, as well as the\n"
+				       "random seed file. THIS PROCESS WILL\n"
+				       "DESTROY YOUR SAVED SESSIONS.\n"
+				       "(This only affects the currently logged-in user.)\n"
+				       "\n"
+				       "If you hit No, uninstallation will proceed, but\n"
+				       "saved sessions etc will be left on the machine.",
+				       appname);
+			s2 = dupprintf("%s Uninstallation", appname);
+		    } else {
+			s1 = dupprintf("This procedure will remove ALL Registry entries\n"
+				       "associated with %s, and will also remove\n"
+				       "the random seed file. (This only affects the\n"
+				       "currently logged-in user.)\n"
+				       "\n"
+				       "THIS PROCESS WILL DESTROY YOUR SAVED SESSIONS.\n"
+				       "Are you really sure you want to continue?",
+				       appname);
+			s2 = dupprintf("%s Warning", appname);
+		    }
+		    if (message_box(s1, s2,
+				    MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2,
+				    HELPCTXID(option_cleanup)) == IDYES) {
+			cleanup_all();
+		    }
+		    sfree(s1);
+		    sfree(s2);
+		    exit(0);
+		} else if (!strcmp(p, "-pgpfp")) {
+		    pgp_fingerprints();
+		    exit(1);
+		} else if (*p != '-') {
+		    char *q = p;
+		    if (got_host) {
+			/*
+			 * If we already have a host name, treat
+			 * this argument as a port number. NB we
+			 * have to treat this as a saved -P
+			 * argument, so that it will be deferred
+			 * until it's a good moment to run it.
+			 */
+			int ret = cmdline_process_param("-P", p, 1, conf);
+			assert(ret == 2);
+		    } else if (!strncmp(q, "telnet:", 7)) {
+			/*
+			 * If the hostname starts with "telnet:",
+			 * set the protocol to Telnet and process
+			 * the string as a Telnet URL.
+			 */
+			char c;
+
+			q += 7;
+			if (q[0] == '/' && q[1] == '/')
+			    q += 2;
+			conf_set_int(conf, CONF_protocol, PROT_TELNET);
+			p = q;
+                        p += host_strcspn(p, ":/");
+			c = *p;
+			if (*p)
+			    *p++ = '\0';
+			if (c == ':')
+			    conf_set_int(conf, CONF_port, atoi(p));
+			else
+			    conf_set_int(conf, CONF_port, -1);
+			conf_set_str(conf, CONF_host, q);
+			got_host = 1;
+		    } else {
+			/*
+			 * Otherwise, treat this argument as a host
+			 * name.
+			 */
+			while (*p && !isspace(*p))
+			    p++;
+			if (*p)
+			    *p++ = '\0';
+			conf_set_str(conf, CONF_host, q);
+			got_host = 1;
+		    }
+		} else {
+		    cmdline_error("unknown option \"%s\"", p);
+		}
+	    }
+	}
+
+	cmdline_run_saved(conf);
+
+	if (loaded_session || got_host)
+	    allow_launch = TRUE;
+
+	if ((!allow_launch || !conf_launchable(conf)) && !do_config()) {
+	    cleanup_exit(0);
+	}
+
+	/*
+	 * Muck about with the hostname in various ways.
+	 */
+	{
+	    char *hostbuf = dupstr(conf_get_str(conf, CONF_host));
+	    char *host = hostbuf;
+	    char *p, *q;
+
+	    /*
+	     * Trim leading whitespace.
+	     */
+	    host += strspn(host, " \t");
+
+	    /*
+	     * See if host is of the form user@host, and separate
+	     * out the username if so.
+	     */
+	    if (host[0] != '\0') {
+		char *atsign = strrchr(host, '@');
+		if (atsign) {
+		    *atsign = '\0';
+		    conf_set_str(conf, CONF_username, host);
+		    host = atsign + 1;
+		}
+	    }
+
+            /*
+             * Trim a colon suffix off the hostname if it's there. In
+             * order to protect unbracketed IPv6 address literals
+             * against this treatment, we do not do this if there's
+             * _more_ than one colon.
+             */
+            {
+                char *c = host_strchr(host, ':');
+ 
+                if (c) {
+                    char *d = host_strchr(c+1, ':');
+                    if (!d)
+                        *c = '\0';
+                }
+            }
+
+	    /*
+	     * Remove any remaining whitespace.
+	     */
+	    p = hostbuf;
+	    q = host;
+	    while (*q) {
+		if (*q != ' ' && *q != '\t')
+		    *p++ = *q;
+		q++;
+	    }
+	    *p = '\0';
+
+	    conf_set_str(conf, CONF_host, hostbuf);
+	    sfree(hostbuf);
+	}
+    }
+
+    if (!prev) {
+	wndclass.style = 0;
+	wndclass.lpfnWndProc = WndProc;
+	wndclass.cbClsExtra = 0;
+	wndclass.cbWndExtra = 0;
+	wndclass.hInstance = inst;
+	wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
+	wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
+	wndclass.hbrBackground = NULL;
+	wndclass.lpszMenuName = NULL;
+	wndclass.lpszClassName = appname;
+
+	RegisterClass(&wndclass);
+    }
+
+    memset(&ucsdata, 0, sizeof(ucsdata));
+
+    conf_cache_data();
+
+    conftopalette();
+
+    /*
+     * Guess some defaults for the window size. This all gets
+     * updated later, so we don't really care too much. However, we
+     * do want the font width/height guesses to correspond to a
+     * large font rather than a small one...
+     */
+
+    font_width = 10;
+    font_height = 20;
+    extra_width = 25;
+    extra_height = 28;
+    guess_width = extra_width + font_width * conf_get_int(conf, CONF_width);
+    guess_height = extra_height + font_height*conf_get_int(conf, CONF_height);
+    {
+	RECT r;
+	get_fullscreen_rect(&r);
+	if (guess_width > r.right - r.left)
+	    guess_width = r.right - r.left;
+	if (guess_height > r.bottom - r.top)
+	    guess_height = r.bottom - r.top;
+    }
+
+    {
+	int winmode = WS_OVERLAPPEDWINDOW | WS_VSCROLL;
+	int exwinmode = 0;
+	if (!conf_get_int(conf, CONF_scrollbar))
+	    winmode &= ~(WS_VSCROLL);
+	if (conf_get_int(conf, CONF_resize_action) == RESIZE_DISABLED)
+	    winmode &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
+	if (conf_get_int(conf, CONF_alwaysontop))
+	    exwinmode |= WS_EX_TOPMOST;
+	if (conf_get_int(conf, CONF_sunken_edge))
+	    exwinmode |= WS_EX_CLIENTEDGE;
+	hwnd = CreateWindowEx(exwinmode, appname, appname,
+			      winmode, CW_USEDEFAULT, CW_USEDEFAULT,
+			      guess_width, guess_height,
+			      NULL, NULL, inst, NULL);
+    }
+
+    /*
+     * Initialise the fonts, simultaneously correcting the guesses
+     * for font_{width,height}.
+     */
+    init_fonts(0,0);
+
+    /*
+     * Initialise the terminal. (We have to do this _after_
+     * creating the window, since the terminal is the first thing
+     * which will call schedule_timer(), which will in turn call
+     * timer_change_notify() which will expect hwnd to exist.)
+     */
+    term = term_init(conf, &ucsdata, NULL);
+    logctx = log_init(NULL, conf);
+    term_provide_logctx(term, logctx);
+    term_size(term, conf_get_int(conf, CONF_height),
+	      conf_get_int(conf, CONF_width),
+	      conf_get_int(conf, CONF_savelines));
+
+    /*
+     * Correct the guesses for extra_{width,height}.
+     */
+    {
+	RECT cr, wr;
+	GetWindowRect(hwnd, &wr);
+	GetClientRect(hwnd, &cr);
+	offset_width = offset_height = conf_get_int(conf, CONF_window_border);
+	extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
+	extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
+    }
+
+    /*
+     * Resize the window, now we know what size we _really_ want it
+     * to be.
+     */
+    guess_width = extra_width + font_width * term->cols;
+    guess_height = extra_height + font_height * term->rows;
+    SetWindowPos(hwnd, NULL, 0, 0, guess_width, guess_height,
+		 SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
+
+    /*
+     * Set up a caret bitmap, with no content.
+     */
+    {
+	char *bits;
+	int size = (font_width + 15) / 16 * 2 * font_height;
+	bits = snewn(size, char);
+	memset(bits, 0, size);
+	caretbm = CreateBitmap(font_width, font_height, 1, 1, bits);
+	sfree(bits);
+    }
+    CreateCaret(hwnd, caretbm, font_width, font_height);
+
+    /*
+     * Initialise the scroll bar.
+     */
+    {
+	SCROLLINFO si;
+
+	si.cbSize = sizeof(si);
+	si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
+	si.nMin = 0;
+	si.nMax = term->rows - 1;
+	si.nPage = term->rows;
+	si.nPos = 0;
+	SetScrollInfo(hwnd, SB_VERT, &si, FALSE);
+    }
+
+    /*
+     * Prepare the mouse handler.
+     */
+    lastact = MA_NOTHING;
+    lastbtn = MBT_NOTHING;
+    dbltime = GetDoubleClickTime();
+
+    /*
+     * Set up the session-control options on the system menu.
+     */
+    {
+	HMENU m;
+	int j;
+	char *str;
+
+	popup_menus[SYSMENU].menu = GetSystemMenu(hwnd, FALSE);
+	popup_menus[CTXMENU].menu = CreatePopupMenu();
+	AppendMenu(popup_menus[CTXMENU].menu, MF_ENABLED, IDM_PASTE, "&Paste");
+
+	savedsess_menu = CreateMenu();
+	get_sesslist(&sesslist, TRUE);
+	update_savedsess_menu();
+
+	for (j = 0; j < lenof(popup_menus); j++) {
+	    m = popup_menus[j].menu;
+
+	    AppendMenu(m, MF_SEPARATOR, 0, 0);
+	    AppendMenu(m, MF_ENABLED, IDM_SHOWLOG, "&Event Log");
+	    AppendMenu(m, MF_SEPARATOR, 0, 0);
+	    AppendMenu(m, MF_ENABLED, IDM_NEWSESS, "Ne&w Session...");
+	    AppendMenu(m, MF_ENABLED, IDM_DUPSESS, "&Duplicate Session");
+	    AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) savedsess_menu,
+		       "Sa&ved Sessions");
+	    AppendMenu(m, MF_ENABLED, IDM_RECONF, "Chan&ge Settings...");
+	    AppendMenu(m, MF_SEPARATOR, 0, 0);
+	    AppendMenu(m, MF_ENABLED, IDM_COPYALL, "C&opy All to Clipboard");
+	    AppendMenu(m, MF_ENABLED, IDM_CLRSB, "C&lear Scrollback");
+	    AppendMenu(m, MF_ENABLED, IDM_RESET, "Rese&t Terminal");
+	    AppendMenu(m, MF_SEPARATOR, 0, 0);
+	    AppendMenu(m, (conf_get_int(conf, CONF_resize_action)
+			   == RESIZE_DISABLED) ? MF_GRAYED : MF_ENABLED,
+		       IDM_FULLSCREEN, "&Full Screen");
+	    AppendMenu(m, MF_SEPARATOR, 0, 0);
+	    if (has_help())
+		AppendMenu(m, MF_ENABLED, IDM_HELP, "&Help");
+	    str = dupprintf("&About %s", appname);
+	    AppendMenu(m, MF_ENABLED, IDM_ABOUT, str);
+	    sfree(str);
+	}
+    }
+
+    start_backend();
+
+    /*
+     * Set up the initial input locale.
+     */
+    set_input_locale(GetKeyboardLayout(0));
+
+    /*
+     * Finally show the window!
+     */
+    ShowWindow(hwnd, show);
+    SetForegroundWindow(hwnd);
+
+    /*
+     * Set the palette up.
+     */
+    pal = NULL;
+    logpal = NULL;
+    init_palette();
+
+    term_set_focus(term, GetForegroundWindow() == hwnd);
+    UpdateWindow(hwnd);
+
+    while (1) {
+	HANDLE *handles;
+	int nhandles, n;
+        DWORD timeout;
+
+        if (toplevel_callback_pending() ||
+            PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {
+            /*
+             * If we have anything we'd like to do immediately, set
+             * the timeout for MsgWaitForMultipleObjects to zero so
+             * that we'll only do a quick check of our handles and
+             * then get on with whatever that was.
+             *
+             * One such option is a pending toplevel callback. The
+             * other is a non-empty Windows message queue, which you'd
+             * think we could leave to MsgWaitForMultipleObjects to
+             * check for us along with all the handles, but in fact we
+             * can't because once PeekMessage in one iteration of this
+             * loop has removed a message from the queue, the whole
+             * queue is considered uninteresting by the next
+             * invocation of MWFMO. So we check ourselves whether the
+             * message queue is non-empty, and if so, set this timeout
+             * to zero to ensure MWFMO doesn't block.
+             */
+            timeout = 0;
+        } else {
+            timeout = INFINITE;
+            /* The messages seem unreliable; especially if we're being tricky */
+            term_set_focus(term, GetForegroundWindow() == hwnd);
+        }
+
+	handles = handle_get_events(&nhandles);
+
+	n = MsgWaitForMultipleObjects(nhandles, handles, FALSE,
+                                      timeout, QS_ALLINPUT);
+
+	if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) {
+	    handle_got_event(handles[n - WAIT_OBJECT_0]);
+	    sfree(handles);
+	} else
+	    sfree(handles);
+
+	while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
+	    if (msg.message == WM_QUIT)
+		goto finished;	       /* two-level break */
+
+	    if (!(IsWindow(logbox) && IsDialogMessage(logbox, &msg)))
+		DispatchMessage(&msg);
+
+            /*
+             * WM_NETEVENT messages seem to jump ahead of others in
+             * the message queue. I'm not sure why; the docs for
+             * PeekMessage mention that messages are prioritised in
+             * some way, but I'm unclear on which priorities go where.
+             *
+             * Anyway, in practice I observe that WM_NETEVENT seems to
+             * jump to the head of the queue, which means that if we
+             * were to only process one message every time round this
+             * loop, we'd get nothing but NETEVENTs if the server
+             * flooded us with data, and stop responding to any other
+             * kind of window message. So instead, we keep on round
+             * this loop until we've consumed at least one message
+             * that _isn't_ a NETEVENT, or run out of messages
+             * completely (whichever comes first). And we don't go to
+             * run_toplevel_callbacks (which is where the netevents
+             * are actually processed, causing fresh NETEVENT messages
+             * to appear) until we've done this.
+             */
+            if (msg.message != WM_NETEVENT)
+                break;
+	}
+
+        run_toplevel_callbacks();
+    }
+
+    finished:
+    cleanup_exit(msg.wParam);	       /* this doesn't return... */
+    return msg.wParam;		       /* ... but optimiser doesn't know */
+}
+
+/*
+ * Clean up and exit.
+ */
+void cleanup_exit(int code)
+{
+    /*
+     * Clean up.
+     */
+    deinit_fonts();
+    sfree(logpal);
+    if (pal)
+	DeleteObject(pal);
+    sk_cleanup();
+
+    if (conf_get_int(conf, CONF_protocol) == PROT_SSH) {
+	random_save_seed();
+#ifdef MSCRYPTOAPI
+	crypto_wrapup();
+#endif
+    }
+    shutdown_help();
+
+    /* Clean up COM. */
+    CoUninitialize();
+
+    exit(code);
+}
+
+/*
+ * Set up, or shut down, an AsyncSelect. Called from winnet.c.
+ */
+char *do_select(SOCKET skt, int startup)
+{
+    int msg, events;
+    if (startup) {
+	msg = WM_NETEVENT;
+	events = (FD_CONNECT | FD_READ | FD_WRITE |
+		  FD_OOB | FD_CLOSE | FD_ACCEPT);
+    } else {
+	msg = events = 0;
+    }
+    if (!hwnd)
+	return "do_select(): internal error (hwnd==NULL)";
+    if (p_WSAAsyncSelect(skt, hwnd, msg, events) == SOCKET_ERROR) {
+	switch (p_WSAGetLastError()) {
+	  case WSAENETDOWN:
+	    return "Network is down";
+	  default:
+	    return "WSAAsyncSelect(): unknown error";
+	}
+    }
+    return NULL;
+}
+
+/*
+ * Refresh the saved-session submenu from `sesslist'.
+ */
+static void update_savedsess_menu(void)
+{
+    int i;
+    while (DeleteMenu(savedsess_menu, 0, MF_BYPOSITION)) ;
+    /* skip sesslist.sessions[0] == Default Settings */
+    for (i = 1;
+	 i < ((sesslist.nsessions <= MENU_SAVED_MAX+1) ? sesslist.nsessions
+						       : MENU_SAVED_MAX+1);
+	 i++)
+	AppendMenu(savedsess_menu, MF_ENABLED,
+		   IDM_SAVED_MIN + (i-1)*MENU_SAVED_STEP,
+		   sesslist.sessions[i]);
+    if (sesslist.nsessions <= 1)
+	AppendMenu(savedsess_menu, MF_GRAYED, IDM_SAVED_MIN, "(No sessions)");
+}
+
+/*
+ * Update the Special Commands submenu.
+ */
+void update_specials_menu(void *frontend)
+{
+    HMENU new_menu;
+    int i, j;
+
+    if (back)
+	specials = back->get_specials(backhandle);
+    else
+	specials = NULL;
+
+    if (specials) {
+	/* We can't use Windows to provide a stack for submenus, so
+	 * here's a lame "stack" that will do for now. */
+	HMENU saved_menu = NULL;
+	int nesting = 1;
+	new_menu = CreatePopupMenu();
+	for (i = 0; nesting > 0; i++) {
+	    assert(IDM_SPECIAL_MIN + 0x10 * i < IDM_SPECIAL_MAX);
+	    switch (specials[i].code) {
+	      case TS_SEP:
+		AppendMenu(new_menu, MF_SEPARATOR, 0, 0);
+		break;
+	      case TS_SUBMENU:
+		assert(nesting < 2);
+		nesting++;
+		saved_menu = new_menu; /* XXX lame stacking */
+		new_menu = CreatePopupMenu();
+		AppendMenu(saved_menu, MF_POPUP | MF_ENABLED,
+			   (UINT) new_menu, specials[i].name);
+		break;
+	      case TS_EXITMENU:
+		nesting--;
+		if (nesting) {
+		    new_menu = saved_menu; /* XXX lame stacking */
+		    saved_menu = NULL;
+		}
+		break;
+	      default:
+		AppendMenu(new_menu, MF_ENABLED, IDM_SPECIAL_MIN + 0x10 * i,
+			   specials[i].name);
+		break;
+	    }
+	}
+	/* Squirrel the highest special. */
+	n_specials = i - 1;
+    } else {
+	new_menu = NULL;
+	n_specials = 0;
+    }
+
+    for (j = 0; j < lenof(popup_menus); j++) {
+	if (specials_menu) {
+	    /* XXX does this free up all submenus? */
+	    DeleteMenu(popup_menus[j].menu, (UINT)specials_menu, MF_BYCOMMAND);
+	    DeleteMenu(popup_menus[j].menu, IDM_SPECIALSEP, MF_BYCOMMAND);
+	}
+	if (new_menu) {
+	    InsertMenu(popup_menus[j].menu, IDM_SHOWLOG,
+		       MF_BYCOMMAND | MF_POPUP | MF_ENABLED,
+		       (UINT) new_menu, "S&pecial Command");
+	    InsertMenu(popup_menus[j].menu, IDM_SHOWLOG,
+		       MF_BYCOMMAND | MF_SEPARATOR, IDM_SPECIALSEP, 0);
+	}
+    }
+    specials_menu = new_menu;
+}
+
+static void update_mouse_pointer(void)
+{
+    LPTSTR curstype;
+    int force_visible = FALSE;
+    static int forced_visible = FALSE;
+    switch (busy_status) {
+      case BUSY_NOT:
+	if (send_raw_mouse)
+	    curstype = IDC_ARROW;
+	else
+	    curstype = IDC_IBEAM;
+	break;
+      case BUSY_WAITING:
+	curstype = IDC_APPSTARTING; /* this may be an abuse */
+	force_visible = TRUE;
+	break;
+      case BUSY_CPU:
+	curstype = IDC_WAIT;
+	force_visible = TRUE;
+	break;
+      default:
+	assert(0);
+    }
+    {
+	HCURSOR cursor = LoadCursor(NULL, curstype);
+	SetClassLongPtr(hwnd, GCLP_HCURSOR, (LONG_PTR)cursor);
+	SetCursor(cursor); /* force redraw of cursor at current posn */
+    }
+    if (force_visible != forced_visible) {
+	/* We want some cursor shapes to be visible always.
+	 * Along with show_mouseptr(), this manages the ShowCursor()
+	 * counter such that if we switch back to a non-force_visible
+	 * cursor, the previous visibility state is restored. */
+	ShowCursor(force_visible);
+	forced_visible = force_visible;
+    }
+}
+
+void set_busy_status(void *frontend, int status)
+{
+    busy_status = status;
+    update_mouse_pointer();
+}
+
+/*
+ * set or clear the "raw mouse message" mode
+ */
+void set_raw_mouse_mode(void *frontend, int activate)
+{
+    activate = activate && !conf_get_int(conf, CONF_no_mouse_rep);
+    send_raw_mouse = activate;
+    update_mouse_pointer();
+}
+
+/*
+ * Print a message box and close the connection.
+ */
+void connection_fatal(void *frontend, char *fmt, ...)
+{
+    va_list ap;
+    char *stuff, morestuff[100];
+
+    va_start(ap, fmt);
+    stuff = dupvprintf(fmt, ap);
+    va_end(ap);
+    sprintf(morestuff, "%.70s Fatal Error", appname);
+    MessageBox(hwnd, stuff, morestuff, MB_ICONERROR | MB_OK);
+    sfree(stuff);
+
+    if (conf_get_int(conf, CONF_close_on_exit) == FORCE_ON)
+	PostQuitMessage(1);
+    else {
+	queue_toplevel_callback(close_session, NULL);
+    }
+}
+
+/*
+ * Report an error at the command-line parsing stage.
+ */
+void cmdline_error(char *fmt, ...)
+{
+    va_list ap;
+    char *stuff, morestuff[100];
+
+    va_start(ap, fmt);
+    stuff = dupvprintf(fmt, ap);
+    va_end(ap);
+    sprintf(morestuff, "%.70s Command Line Error", appname);
+    MessageBox(hwnd, stuff, morestuff, MB_ICONERROR | MB_OK);
+    sfree(stuff);
+    exit(1);
+}
+
+/*
+ * Actually do the job requested by a WM_NETEVENT
+ */
+static void wm_netevent_callback(void *vctx)
+{
+    struct wm_netevent_params *params = (struct wm_netevent_params *)vctx;
+    select_result(params->wParam, params->lParam);
+    sfree(vctx);
+}
+
+/*
+ * Copy the colour palette from the configuration data into defpal.
+ * This is non-trivial because the colour indices are different.
+ */
+static void conftopalette(void)
+{
+    int i;
+    static const int ww[] = {
+	256, 257, 258, 259, 260, 261,
+	0, 8, 1, 9, 2, 10, 3, 11,
+	4, 12, 5, 13, 6, 14, 7, 15
+    };
+
+    for (i = 0; i < 22; i++) {
+	int w = ww[i];
+	defpal[w].rgbtRed = conf_get_int_int(conf, CONF_colours, i*3+0);
+	defpal[w].rgbtGreen = conf_get_int_int(conf, CONF_colours, i*3+1);
+	defpal[w].rgbtBlue = conf_get_int_int(conf, CONF_colours, i*3+2);
+    }
+    for (i = 0; i < NEXTCOLOURS; i++) {
+	if (i < 216) {
+	    int r = i / 36, g = (i / 6) % 6, b = i % 6;
+	    defpal[i+16].rgbtRed = r ? r * 40 + 55 : 0;
+	    defpal[i+16].rgbtGreen = g ? g * 40 + 55 : 0;
+	    defpal[i+16].rgbtBlue = b ? b * 40 + 55 : 0;
+	} else {
+	    int shade = i - 216;
+	    shade = shade * 10 + 8;
+	    defpal[i+16].rgbtRed = defpal[i+16].rgbtGreen =
+		defpal[i+16].rgbtBlue = shade;
+	}
+    }
+
+    /* Override with system colours if appropriate */
+    if (conf_get_int(conf, CONF_system_colour))
+        systopalette();
+}
+
+/*
+ * Override bit of defpal with colours from the system.
+ * (NB that this takes a copy the system colours at the time this is called,
+ * so subsequent colour scheme changes don't take effect. To fix that we'd
+ * probably want to be using GetSysColorBrush() and the like.)
+ */
+static void systopalette(void)
+{
+    int i;
+    static const struct { int nIndex; int norm; int bold; } or[] =
+    {
+	{ COLOR_WINDOWTEXT,	256, 257 }, /* Default Foreground */
+	{ COLOR_WINDOW,		258, 259 }, /* Default Background */
+	{ COLOR_HIGHLIGHTTEXT,	260, 260 }, /* Cursor Text */
+	{ COLOR_HIGHLIGHT,	261, 261 }, /* Cursor Colour */
+    };
+
+    for (i = 0; i < (sizeof(or)/sizeof(or[0])); i++) {
+	COLORREF colour = GetSysColor(or[i].nIndex);
+	defpal[or[i].norm].rgbtRed =
+	   defpal[or[i].bold].rgbtRed = GetRValue(colour);
+	defpal[or[i].norm].rgbtGreen =
+	   defpal[or[i].bold].rgbtGreen = GetGValue(colour);
+	defpal[or[i].norm].rgbtBlue =
+	   defpal[or[i].bold].rgbtBlue = GetBValue(colour);
+    }
+}
+
+/*
+ * Set up the colour palette.
+ */
+static void init_palette(void)
+{
+    int i;
+    HDC hdc = GetDC(hwnd);
+    if (hdc) {
+	if (conf_get_int(conf, CONF_try_palette) &&
+	    GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) {
+	    /*
+	     * This is a genuine case where we must use smalloc
+	     * because the snew macros can't cope.
+	     */
+	    logpal = smalloc(sizeof(*logpal)
+			     - sizeof(logpal->palPalEntry)
+			     + NALLCOLOURS * sizeof(PALETTEENTRY));
+	    logpal->palVersion = 0x300;
+	    logpal->palNumEntries = NALLCOLOURS;
+	    for (i = 0; i < NALLCOLOURS; i++) {
+		logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
+		logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
+		logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
+		logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
+	    }
+	    pal = CreatePalette(logpal);
+	    if (pal) {
+		SelectPalette(hdc, pal, FALSE);
+		RealizePalette(hdc);
+		SelectPalette(hdc, GetStockObject(DEFAULT_PALETTE), FALSE);
+	    }
+	}
+	ReleaseDC(hwnd, hdc);
+    }
+    if (pal)
+	for (i = 0; i < NALLCOLOURS; i++)
+	    colours[i] = PALETTERGB(defpal[i].rgbtRed,
+				    defpal[i].rgbtGreen,
+				    defpal[i].rgbtBlue);
+    else
+	for (i = 0; i < NALLCOLOURS; i++)
+	    colours[i] = RGB(defpal[i].rgbtRed,
+			     defpal[i].rgbtGreen, defpal[i].rgbtBlue);
+}
+
+/*
+ * This is a wrapper to ExtTextOut() to force Windows to display
+ * the precise glyphs we give it. Otherwise it would do its own
+ * bidi and Arabic shaping, and we would end up uncertain which
+ * characters it had put where.
+ */
+static void exact_textout(HDC hdc, int x, int y, CONST RECT *lprc,
+			  unsigned short *lpString, UINT cbCount,
+			  CONST INT *lpDx, int opaque)
+{
+#ifdef __LCC__
+    /*
+     * The LCC include files apparently don't supply the
+     * GCP_RESULTSW type, but we can make do with GCP_RESULTS
+     * proper: the differences aren't important to us (the only
+     * variable-width string parameter is one we don't use anyway).
+     */
+    GCP_RESULTS gcpr;
+#else
+    GCP_RESULTSW gcpr;
+#endif
+    char *buffer = snewn(cbCount*2+2, char);
+    char *classbuffer = snewn(cbCount, char);
+    memset(&gcpr, 0, sizeof(gcpr));
+    memset(buffer, 0, cbCount*2+2);
+    memset(classbuffer, GCPCLASS_NEUTRAL, cbCount);
+
+    gcpr.lStructSize = sizeof(gcpr);
+    gcpr.lpGlyphs = (void *)buffer;
+    gcpr.lpClass = (void *)classbuffer;
+    gcpr.nGlyphs = cbCount;
+    GetCharacterPlacementW(hdc, lpString, cbCount, 0, &gcpr,
+			   FLI_MASK | GCP_CLASSIN | GCP_DIACRITIC);
+
+    ExtTextOut(hdc, x, y,
+	       ETO_GLYPH_INDEX | ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0),
+	       lprc, buffer, cbCount, lpDx);
+}
+
+/*
+ * The exact_textout() wrapper, unfortunately, destroys the useful
+ * Windows `font linking' behaviour: automatic handling of Unicode
+ * code points not supported in this font by falling back to a font
+ * which does contain them. Therefore, we adopt a multi-layered
+ * approach: for any potentially-bidi text, we use exact_textout(),
+ * and for everything else we use a simple ExtTextOut as we did
+ * before exact_textout() was introduced.
+ */
+static void general_textout(HDC hdc, int x, int y, CONST RECT *lprc,
+			    unsigned short *lpString, UINT cbCount,
+			    CONST INT *lpDx, int opaque)
+{
+    int i, j, xp, xn;
+    int bkmode = 0, got_bkmode = FALSE;
+
+    xp = xn = x;
+
+    for (i = 0; i < (int)cbCount ;) {
+	int rtl = is_rtl(lpString[i]);
+
+	xn += lpDx[i];
+
+	for (j = i+1; j < (int)cbCount; j++) {
+	    if (rtl != is_rtl(lpString[j]))
+		break;
+	    xn += lpDx[j];
+	}
+
+	/*
+	 * Now [i,j) indicates a maximal substring of lpString
+	 * which should be displayed using the same textout
+	 * function.
+	 */
+	if (rtl) {
+	    exact_textout(hdc, xp, y, lprc, lpString+i, j-i,
+                          font_varpitch ? NULL : lpDx+i, opaque);
+	} else {
+	    ExtTextOutW(hdc, xp, y, ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0),
+			lprc, lpString+i, j-i,
+                        font_varpitch ? NULL : lpDx+i);
+	}
+
+	i = j;
+	xp = xn;
+
+        bkmode = GetBkMode(hdc);
+        got_bkmode = TRUE;
+        SetBkMode(hdc, TRANSPARENT);
+        opaque = FALSE;
+    }
+
+    if (got_bkmode)
+        SetBkMode(hdc, bkmode);
+}
+
+static int get_font_width(HDC hdc, const TEXTMETRIC *tm)
+{
+    int ret;
+    /* Note that the TMPF_FIXED_PITCH bit is defined upside down :-( */
+    if (!(tm->tmPitchAndFamily & TMPF_FIXED_PITCH)) {
+        ret = tm->tmAveCharWidth;
+    } else {
+#define FIRST '0'
+#define LAST '9'
+        ABCFLOAT widths[LAST-FIRST + 1];
+        int j;
+
+        font_varpitch = TRUE;
+        font_dualwidth = TRUE;
+        if (GetCharABCWidthsFloat(hdc, FIRST, LAST, widths)) {
+            ret = 0;
+            for (j = 0; j < lenof(widths); j++) {
+                int width = (int)(0.5 + widths[j].abcfA +
+                                  widths[j].abcfB + widths[j].abcfC);
+                if (ret < width)
+                    ret = width;
+            }
+        } else {
+            ret = tm->tmMaxCharWidth;
+        }
+#undef FIRST
+#undef LAST
+    }
+    return ret;
+}
+
+/*
+ * Initialise all the fonts we will need initially. There may be as many as
+ * three or as few as one.  The other (potentially) twenty-one fonts are done
+ * if/when they are needed.
+ *
+ * We also:
+ *
+ * - check the font width and height, correcting our guesses if
+ *   necessary.
+ *
+ * - verify that the bold font is the same width as the ordinary
+ *   one, and engage shadow bolding if not.
+ * 
+ * - verify that the underlined font is the same width as the
+ *   ordinary one (manual underlining by means of line drawing can
+ *   be done in a pinch).
+ */
+static void init_fonts(int pick_width, int pick_height)
+{
+    TEXTMETRIC tm;
+    CPINFO cpinfo;
+    FontSpec *font;
+    int fontsize[3];
+    int i;
+    int quality;
+    HDC hdc;
+    int fw_dontcare, fw_bold;
+
+    for (i = 0; i < FONT_MAXNO; i++)
+	fonts[i] = NULL;
+
+    bold_font_mode = conf_get_int(conf, CONF_bold_style) & 1 ?
+	BOLD_FONT : BOLD_NONE;
+    bold_colours = conf_get_int(conf, CONF_bold_style) & 2 ? TRUE : FALSE;
+    und_mode = UND_FONT;
+
+    font = conf_get_fontspec(conf, CONF_font);
+    if (font->isbold) {
+	fw_dontcare = FW_BOLD;
+	fw_bold = FW_HEAVY;
+    } else {
+	fw_dontcare = FW_DONTCARE;
+	fw_bold = FW_BOLD;
+    }
+
+    hdc = GetDC(hwnd);
+
+    if (pick_height)
+	font_height = pick_height;
+    else {
+	font_height = font->height;
+	if (font_height > 0) {
+	    font_height =
+		-MulDiv(font_height, GetDeviceCaps(hdc, LOGPIXELSY), 72);
+	}
+    }
+    font_width = pick_width;
+
+    quality = conf_get_int(conf, CONF_font_quality);
+#define f(i,c,w,u) \
+    fonts[i] = CreateFont (font_height, font_width, 0, 0, w, FALSE, u, FALSE, \
+			   c, OUT_DEFAULT_PRECIS, \
+		           CLIP_DEFAULT_PRECIS, FONT_QUALITY(quality), \
+			   FIXED_PITCH | FF_DONTCARE, font->name)
+
+    f(FONT_NORMAL, font->charset, fw_dontcare, FALSE);
+
+    SelectObject(hdc, fonts[FONT_NORMAL]);
+    GetTextMetrics(hdc, &tm);
+
+    GetObject(fonts[FONT_NORMAL], sizeof(LOGFONT), &lfont);
+
+    /* Note that the TMPF_FIXED_PITCH bit is defined upside down :-( */
+    if (!(tm.tmPitchAndFamily & TMPF_FIXED_PITCH)) {
+        font_varpitch = FALSE;
+        font_dualwidth = (tm.tmAveCharWidth != tm.tmMaxCharWidth);
+    } else {
+        font_varpitch = TRUE;
+        font_dualwidth = TRUE;
+    }
+    if (pick_width == 0 || pick_height == 0) {
+	font_height = tm.tmHeight;
+        font_width = get_font_width(hdc, &tm);
+    }
+
+#ifdef RDB_DEBUG_PATCH
+    debug(23, "Primary font H=%d, AW=%d, MW=%d",
+	    tm.tmHeight, tm.tmAveCharWidth, tm.tmMaxCharWidth);
+#endif
+
+    {
+	CHARSETINFO info;
+	DWORD cset = tm.tmCharSet;
+	memset(&info, 0xFF, sizeof(info));
+
+	/* !!! Yes the next line is right */
+	if (cset == OEM_CHARSET)
+	    ucsdata.font_codepage = GetOEMCP();
+	else
+	    if (TranslateCharsetInfo ((DWORD *) cset, &info, TCI_SRCCHARSET))
+		ucsdata.font_codepage = info.ciACP;
+	else
+	    ucsdata.font_codepage = -1;
+
+	GetCPInfo(ucsdata.font_codepage, &cpinfo);
+	ucsdata.dbcs_screenfont = (cpinfo.MaxCharSize > 1);
+    }
+
+    f(FONT_UNDERLINE, font->charset, fw_dontcare, TRUE);
+
+    /*
+     * Some fonts, e.g. 9-pt Courier, draw their underlines
+     * outside their character cell. We successfully prevent
+     * screen corruption by clipping the text output, but then
+     * we lose the underline completely. Here we try to work
+     * out whether this is such a font, and if it is, we set a
+     * flag that causes underlines to be drawn by hand.
+     *
+     * Having tried other more sophisticated approaches (such
+     * as examining the TEXTMETRIC structure or requesting the
+     * height of a string), I think we'll do this the brute
+     * force way: we create a small bitmap, draw an underlined
+     * space on it, and test to see whether any pixels are
+     * foreground-coloured. (Since we expect the underline to
+     * go all the way across the character cell, we only search
+     * down a single column of the bitmap, half way across.)
+     */
+    {
+	HDC und_dc;
+	HBITMAP und_bm, und_oldbm;
+	int i, gotit;
+	COLORREF c;
+
+	und_dc = CreateCompatibleDC(hdc);
+	und_bm = CreateCompatibleBitmap(hdc, font_width, font_height);
+	und_oldbm = SelectObject(und_dc, und_bm);
+	SelectObject(und_dc, fonts[FONT_UNDERLINE]);
+	SetTextAlign(und_dc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
+	SetTextColor(und_dc, RGB(255, 255, 255));
+	SetBkColor(und_dc, RGB(0, 0, 0));
+	SetBkMode(und_dc, OPAQUE);
+	ExtTextOut(und_dc, 0, 0, ETO_OPAQUE, NULL, " ", 1, NULL);
+	gotit = FALSE;
+	for (i = 0; i < font_height; i++) {
+	    c = GetPixel(und_dc, font_width / 2, i);
+	    if (c != RGB(0, 0, 0))
+		gotit = TRUE;
+	}
+	SelectObject(und_dc, und_oldbm);
+	DeleteObject(und_bm);
+	DeleteDC(und_dc);
+	if (!gotit) {
+	    und_mode = UND_LINE;
+	    DeleteObject(fonts[FONT_UNDERLINE]);
+	    fonts[FONT_UNDERLINE] = 0;
+	}
+    }
+
+    if (bold_font_mode == BOLD_FONT) {
+	f(FONT_BOLD, font->charset, fw_bold, FALSE);
+    }
+#undef f
+
+    descent = tm.tmAscent + 1;
+    if (descent >= font_height)
+	descent = font_height - 1;
+
+    for (i = 0; i < 3; i++) {
+	if (fonts[i]) {
+	    if (SelectObject(hdc, fonts[i]) && GetTextMetrics(hdc, &tm))
+		fontsize[i] = get_font_width(hdc, &tm) + 256 * tm.tmHeight;
+	    else
+		fontsize[i] = -i;
+	} else
+	    fontsize[i] = -i;
+    }
+
+    ReleaseDC(hwnd, hdc);
+
+    if (fontsize[FONT_UNDERLINE] != fontsize[FONT_NORMAL]) {
+	und_mode = UND_LINE;
+	DeleteObject(fonts[FONT_UNDERLINE]);
+	fonts[FONT_UNDERLINE] = 0;
+    }
+
+    if (bold_font_mode == BOLD_FONT &&
+	fontsize[FONT_BOLD] != fontsize[FONT_NORMAL]) {
+	bold_font_mode = BOLD_SHADOW;
+	DeleteObject(fonts[FONT_BOLD]);
+	fonts[FONT_BOLD] = 0;
+    }
+    fontflag[0] = fontflag[1] = fontflag[2] = 1;
+
+    init_ucs(conf, &ucsdata);
+}
+
+static void another_font(int fontno)
+{
+    int basefont;
+    int fw_dontcare, fw_bold, quality;
+    int c, u, w, x;
+    char *s;
+    FontSpec *font;
+
+    if (fontno < 0 || fontno >= FONT_MAXNO || fontflag[fontno])
+	return;
+
+    basefont = (fontno & ~(FONT_BOLDUND));
+    if (basefont != fontno && !fontflag[basefont])
+	another_font(basefont);
+
+    font = conf_get_fontspec(conf, CONF_font);
+
+    if (font->isbold) {
+	fw_dontcare = FW_BOLD;
+	fw_bold = FW_HEAVY;
+    } else {
+	fw_dontcare = FW_DONTCARE;
+	fw_bold = FW_BOLD;
+    }
+
+    c = font->charset;
+    w = fw_dontcare;
+    u = FALSE;
+    s = font->name;
+    x = font_width;
+
+    if (fontno & FONT_WIDE)
+	x *= 2;
+    if (fontno & FONT_NARROW)
+	x = (x+1)/2;
+    if (fontno & FONT_OEM)
+	c = OEM_CHARSET;
+    if (fontno & FONT_BOLD)
+	w = fw_bold;
+    if (fontno & FONT_UNDERLINE)
+	u = TRUE;
+
+    quality = conf_get_int(conf, CONF_font_quality);
+
+    fonts[fontno] =
+	CreateFont(font_height * (1 + !!(fontno & FONT_HIGH)), x, 0, 0, w,
+		   FALSE, u, FALSE, c, OUT_DEFAULT_PRECIS,
+		   CLIP_DEFAULT_PRECIS, FONT_QUALITY(quality),
+		   DEFAULT_PITCH | FF_DONTCARE, s);
+
+    fontflag[fontno] = 1;
+}
+
+static void deinit_fonts(void)
+{
+    int i;
+    for (i = 0; i < FONT_MAXNO; i++) {
+	if (fonts[i])
+	    DeleteObject(fonts[i]);
+	fonts[i] = 0;
+	fontflag[i] = 0;
+    }
+}
+
+void request_resize(void *frontend, int w, int h)
+{
+    int width, height;
+
+    /* If the window is maximized supress resizing attempts */
+    if (IsZoomed(hwnd)) {
+	if (conf_get_int(conf, CONF_resize_action) == RESIZE_TERM)
+	    return;
+    }
+
+    if (conf_get_int(conf, CONF_resize_action) == RESIZE_DISABLED) return;
+    if (h == term->rows && w == term->cols) return;
+
+    /* Sanity checks ... */
+    {
+	static int first_time = 1;
+	static RECT ss;
+
+	switch (first_time) {
+	  case 1:
+	    /* Get the size of the screen */
+	    if (get_fullscreen_rect(&ss))
+		/* first_time = 0 */ ;
+	    else {
+		first_time = 2;
+		break;
+	    }
+	  case 0:
+	    /* Make sure the values are sane */
+	    width = (ss.right - ss.left - extra_width) / 4;
+	    height = (ss.bottom - ss.top - extra_height) / 6;
+
+	    if (w > width || h > height)
+		return;
+	    if (w < 15)
+		w = 15;
+	    if (h < 1)
+		h = 1;
+	}
+    }
+
+    term_size(term, h, w, conf_get_int(conf, CONF_savelines));
+
+    if (conf_get_int(conf, CONF_resize_action) != RESIZE_FONT &&
+	!IsZoomed(hwnd)) {
+	width = extra_width + font_width * w;
+	height = extra_height + font_height * h;
+
+	SetWindowPos(hwnd, NULL, 0, 0, width, height,
+	    SWP_NOACTIVATE | SWP_NOCOPYBITS |
+	    SWP_NOMOVE | SWP_NOZORDER);
+    } else
+	reset_window(0);
+
+    InvalidateRect(hwnd, NULL, TRUE);
+}
+
+static void reset_window(int reinit) {
+    /*
+     * This function decides how to resize or redraw when the 
+     * user changes something. 
+     *
+     * This function doesn't like to change the terminal size but if the
+     * font size is locked that may be it's only soluion.
+     */
+    int win_width, win_height, resize_action, window_border;
+    RECT cr, wr;
+
+#ifdef RDB_DEBUG_PATCH
+    debug((27, "reset_window()"));
+#endif
+
+    /* Current window sizes ... */
+    GetWindowRect(hwnd, &wr);
+    GetClientRect(hwnd, &cr);
+
+    win_width  = cr.right - cr.left;
+    win_height = cr.bottom - cr.top;
+
+    resize_action = conf_get_int(conf, CONF_resize_action);
+    window_border = conf_get_int(conf, CONF_window_border);
+
+    if (resize_action == RESIZE_DISABLED)
+	reinit = 2;
+
+    /* Are we being forced to reload the fonts ? */
+    if (reinit>1) {
+#ifdef RDB_DEBUG_PATCH
+	debug((27, "reset_window() -- Forced deinit"));
+#endif
+	deinit_fonts();
+	init_fonts(0,0);
+    }
+
+    /* Oh, looks like we're minimised */
+    if (win_width == 0 || win_height == 0)
+	return;
+
+    /* Is the window out of position ? */
+    if ( !reinit && 
+	    (offset_width != (win_width-font_width*term->cols)/2 ||
+	     offset_height != (win_height-font_height*term->rows)/2) ){
+	offset_width = (win_width-font_width*term->cols)/2;
+	offset_height = (win_height-font_height*term->rows)/2;
+	InvalidateRect(hwnd, NULL, TRUE);
+#ifdef RDB_DEBUG_PATCH
+	debug((27, "reset_window() -> Reposition terminal"));
+#endif
+    }
+
+    if (IsZoomed(hwnd)) {
+	/* We're fullscreen, this means we must not change the size of
+	 * the window so it's the font size or the terminal itself.
+	 */
+
+	extra_width = wr.right - wr.left - cr.right + cr.left;
+	extra_height = wr.bottom - wr.top - cr.bottom + cr.top;
+
+	if (resize_action != RESIZE_TERM) {
+	    if (font_width != win_width/term->cols || 
+		font_height != win_height/term->rows) {
+		deinit_fonts();
+		init_fonts(win_width/term->cols, win_height/term->rows);
+		offset_width = (win_width-font_width*term->cols)/2;
+		offset_height = (win_height-font_height*term->rows)/2;
+		InvalidateRect(hwnd, NULL, TRUE);
+#ifdef RDB_DEBUG_PATCH
+		debug((25, "reset_window() -> Z font resize to (%d, %d)",
+			font_width, font_height));
+#endif
+	    }
+	} else {
+	    if (font_width * term->cols != win_width || 
+		font_height * term->rows != win_height) {
+		/* Our only choice at this point is to change the 
+		 * size of the terminal; Oh well.
+		 */
+		term_size(term, win_height/font_height, win_width/font_width,
+			  conf_get_int(conf, CONF_savelines));
+		offset_width = (win_width-font_width*term->cols)/2;
+		offset_height = (win_height-font_height*term->rows)/2;
+		InvalidateRect(hwnd, NULL, TRUE);
+#ifdef RDB_DEBUG_PATCH
+		debug((27, "reset_window() -> Zoomed term_size"));
+#endif
+	    }
+	}
+	return;
+    }
+
+    /* Hmm, a force re-init means we should ignore the current window
+     * so we resize to the default font size.
+     */
+    if (reinit>0) {
+#ifdef RDB_DEBUG_PATCH
+	debug((27, "reset_window() -> Forced re-init"));
+#endif
+
+	offset_width = offset_height = window_border;
+	extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
+	extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
+
+	if (win_width != font_width*term->cols + offset_width*2 ||
+	    win_height != font_height*term->rows + offset_height*2) {
+
+	    /* If this is too large windows will resize it to the maximum
+	     * allowed window size, we will then be back in here and resize
+	     * the font or terminal to fit.
+	     */
+	    SetWindowPos(hwnd, NULL, 0, 0, 
+		         font_width*term->cols + extra_width, 
+			 font_height*term->rows + extra_height,
+			 SWP_NOMOVE | SWP_NOZORDER);
+	}
+
+	InvalidateRect(hwnd, NULL, TRUE);
+	return;
+    }
+
+    /* Okay the user doesn't want us to change the font so we try the 
+     * window. But that may be too big for the screen which forces us
+     * to change the terminal.
+     */
+    if ((resize_action == RESIZE_TERM && reinit<=0) ||
+        (resize_action == RESIZE_EITHER && reinit<0) ||
+	    reinit>0) {
+	offset_width = offset_height = window_border;
+	extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
+	extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
+
+	if (win_width != font_width*term->cols + offset_width*2 ||
+	    win_height != font_height*term->rows + offset_height*2) {
+
+	    static RECT ss;
+	    int width, height;
+		
+		get_fullscreen_rect(&ss);
+
+	    width = (ss.right - ss.left - extra_width) / font_width;
+	    height = (ss.bottom - ss.top - extra_height) / font_height;
+
+	    /* Grrr too big */
+	    if ( term->rows > height || term->cols > width ) {
+		if (resize_action == RESIZE_EITHER) {
+		    /* Make the font the biggest we can */
+		    if (term->cols > width)
+			font_width = (ss.right - ss.left - extra_width)
+			    / term->cols;
+		    if (term->rows > height)
+			font_height = (ss.bottom - ss.top - extra_height)
+			    / term->rows;
+
+		    deinit_fonts();
+		    init_fonts(font_width, font_height);
+
+		    width = (ss.right - ss.left - extra_width) / font_width;
+		    height = (ss.bottom - ss.top - extra_height) / font_height;
+		} else {
+		    if ( height > term->rows ) height = term->rows;
+		    if ( width > term->cols )  width = term->cols;
+		    term_size(term, height, width,
+			      conf_get_int(conf, CONF_savelines));
+#ifdef RDB_DEBUG_PATCH
+		    debug((27, "reset_window() -> term resize to (%d,%d)",
+			       height, width));
+#endif
+		}
+	    }
+	    
+	    SetWindowPos(hwnd, NULL, 0, 0, 
+		         font_width*term->cols + extra_width, 
+			 font_height*term->rows + extra_height,
+			 SWP_NOMOVE | SWP_NOZORDER);
+
+	    InvalidateRect(hwnd, NULL, TRUE);
+#ifdef RDB_DEBUG_PATCH
+	    debug((27, "reset_window() -> window resize to (%d,%d)",
+			font_width*term->cols + extra_width,
+			font_height*term->rows + extra_height));
+#endif
+	}
+	return;
+    }
+
+    /* We're allowed to or must change the font but do we want to ?  */
+
+    if (font_width != (win_width-window_border*2)/term->cols || 
+	font_height != (win_height-window_border*2)/term->rows) {
+
+	deinit_fonts();
+	init_fonts((win_width-window_border*2)/term->cols, 
+		   (win_height-window_border*2)/term->rows);
+	offset_width = (win_width-font_width*term->cols)/2;
+	offset_height = (win_height-font_height*term->rows)/2;
+
+	extra_width = wr.right - wr.left - cr.right + cr.left +offset_width*2;
+	extra_height = wr.bottom - wr.top - cr.bottom + cr.top+offset_height*2;
+
+	InvalidateRect(hwnd, NULL, TRUE);
+#ifdef RDB_DEBUG_PATCH
+	debug((25, "reset_window() -> font resize to (%d,%d)", 
+		   font_width, font_height));
+#endif
+    }
+}
+
+static void set_input_locale(HKL kl)
+{
+    char lbuf[20];
+
+    GetLocaleInfo(LOWORD(kl), LOCALE_IDEFAULTANSICODEPAGE,
+		  lbuf, sizeof(lbuf));
+
+    kbd_codepage = atoi(lbuf);
+}
+
+static void click(Mouse_Button b, int x, int y, int shift, int ctrl, int alt)
+{
+    int thistime = GetMessageTime();
+
+    if (send_raw_mouse &&
+	!(shift && conf_get_int(conf, CONF_mouse_override))) {
+	lastbtn = MBT_NOTHING;
+	term_mouse(term, b, translate_button(b), MA_CLICK,
+		   x, y, shift, ctrl, alt);
+	return;
+    }
+
+    if (lastbtn == b && thistime - lasttime < dbltime) {
+	lastact = (lastact == MA_CLICK ? MA_2CLK :
+		   lastact == MA_2CLK ? MA_3CLK :
+		   lastact == MA_3CLK ? MA_CLICK : MA_NOTHING);
+    } else {
+	lastbtn = b;
+	lastact = MA_CLICK;
+    }
+    if (lastact != MA_NOTHING)
+	term_mouse(term, b, translate_button(b), lastact,
+		   x, y, shift, ctrl, alt);
+    lasttime = thistime;
+}
+
+/*
+ * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT)
+ * into a cooked one (SELECT, EXTEND, PASTE).
+ */
+static Mouse_Button translate_button(Mouse_Button button)
+{
+    if (button == MBT_LEFT)
+	return MBT_SELECT;
+    if (button == MBT_MIDDLE)
+	return conf_get_int(conf, CONF_mouse_is_xterm) == 1 ?
+	MBT_PASTE : MBT_EXTEND;
+    if (button == MBT_RIGHT)
+	return conf_get_int(conf, CONF_mouse_is_xterm) == 1 ?
+	MBT_EXTEND : MBT_PASTE;
+    return 0;			       /* shouldn't happen */
+}
+
+static void show_mouseptr(int show)
+{
+    /* NB that the counter in ShowCursor() is also frobbed by
+     * update_mouse_pointer() */
+    static int cursor_visible = 1;
+    if (!conf_get_int(conf, CONF_hide_mouseptr))
+	show = 1;		       /* override if this feature disabled */
+    if (cursor_visible && !show)
+	ShowCursor(FALSE);
+    else if (!cursor_visible && show)
+	ShowCursor(TRUE);
+    cursor_visible = show;
+}
+
+static int is_alt_pressed(void)
+{
+    BYTE keystate[256];
+    int r = GetKeyboardState(keystate);
+    if (!r)
+	return FALSE;
+    if (keystate[VK_MENU] & 0x80)
+	return TRUE;
+    if (keystate[VK_RMENU] & 0x80)
+	return TRUE;
+    return FALSE;
+}
+
+static int resizing;
+
+void notify_remote_exit(void *fe)
+{
+    int exitcode, close_on_exit;
+
+    if (!session_closed &&
+        (exitcode = back->exitcode(backhandle)) >= 0) {
+	close_on_exit = conf_get_int(conf, CONF_close_on_exit);
+	/* Abnormal exits will already have set session_closed and taken
+	 * appropriate action. */
+	if (close_on_exit == FORCE_ON ||
+	    (close_on_exit == AUTO && exitcode != INT_MAX)) {
+	    PostQuitMessage(0);
+	} else {
+            queue_toplevel_callback(close_session, NULL);
+	    session_closed = TRUE;
+	    /* exitcode == INT_MAX indicates that the connection was closed
+	     * by a fatal error, so an error box will be coming our way and
+	     * we should not generate this informational one. */
+	    if (exitcode != INT_MAX)
+		MessageBox(hwnd, "Connection closed by remote host",
+			   appname, MB_OK | MB_ICONINFORMATION);
+	}
+    }
+}
+
+void timer_change_notify(unsigned long next)
+{
+    unsigned long now = GETTICKCOUNT();
+    long ticks;
+    if (now - next < INT_MAX)
+	ticks = 0;
+    else
+	ticks = next - now;
+    KillTimer(hwnd, TIMING_TIMER_ID);
+    SetTimer(hwnd, TIMING_TIMER_ID, ticks, NULL);
+    timing_next_time = next;
+}
+
+static void conf_cache_data(void)
+{
+    /* Cache some items from conf to speed lookups in very hot code */
+    cursor_type = conf_get_int(conf, CONF_cursor_type);
+    vtmode = conf_get_int(conf, CONF_vtmode);
+}
+
+static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
+				WPARAM wParam, LPARAM lParam)
+{
+    HDC hdc;
+    static int ignore_clip = FALSE;
+    static int need_backend_resize = FALSE;
+    static int fullscr_on_max = FALSE;
+    static int processed_resize = FALSE;
+    static UINT last_mousemove = 0;
+    int resize_action;
+
+    switch (message) {
+      case WM_TIMER:
+	if ((UINT_PTR)wParam == TIMING_TIMER_ID) {
+	    unsigned long next;
+
+	    KillTimer(hwnd, TIMING_TIMER_ID);
+	    if (run_timers(timing_next_time, &next)) {
+		timer_change_notify(next);
+	    } else {
+	    }
+	}
+	return 0;
+      case WM_CREATE:
+	break;
+      case WM_CLOSE:
+	{
+	    char *str;
+	    show_mouseptr(1);
+	    str = dupprintf("%s Exit Confirmation", appname);
+	    if (session_closed || !conf_get_int(conf, CONF_warn_on_close) ||
+		MessageBox(hwnd,
+			   "Are you sure you want to close this session?",
+			   str, MB_ICONWARNING | MB_OKCANCEL | MB_DEFBUTTON1)
+		== IDOK)
+		DestroyWindow(hwnd);
+	    sfree(str);
+	}
+	return 0;
+      case WM_DESTROY:
+	show_mouseptr(1);
+	PostQuitMessage(0);
+	return 0;
+      case WM_INITMENUPOPUP:
+	if ((HMENU)wParam == savedsess_menu) {
+	    /* About to pop up Saved Sessions sub-menu.
+	     * Refresh the session list. */
+	    get_sesslist(&sesslist, FALSE); /* free */
+	    get_sesslist(&sesslist, TRUE);
+	    update_savedsess_menu();
+	    return 0;
+	}
+	break;
+      case WM_COMMAND:
+      case WM_SYSCOMMAND:
+	switch (wParam & ~0xF) {       /* low 4 bits reserved to Windows */
+	  case IDM_SHOWLOG:
+	    showeventlog(hwnd);
+	    break;
+	  case IDM_NEWSESS:
+	  case IDM_DUPSESS:
+	  case IDM_SAVEDSESS:
+	    {
+		char b[2048];
+		char c[30], *cl;
+		int freecl = FALSE;
+		BOOL inherit_handles;
+		STARTUPINFO si;
+		PROCESS_INFORMATION pi;
+		HANDLE filemap = NULL;
+
+		if (wParam == IDM_DUPSESS) {
+		    /*
+		     * Allocate a file-mapping memory chunk for the
+		     * config structure.
+		     */
+		    SECURITY_ATTRIBUTES sa;
+		    void *p;
+		    int size;
+
+		    size = conf_serialised_size(conf);
+
+		    sa.nLength = sizeof(sa);
+		    sa.lpSecurityDescriptor = NULL;
+		    sa.bInheritHandle = TRUE;
+		    filemap = CreateFileMapping(INVALID_HANDLE_VALUE,
+						&sa,
+						PAGE_READWRITE,
+						0, size, NULL);
+		    if (filemap && filemap != INVALID_HANDLE_VALUE) {
+			p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, size);
+			if (p) {
+			    conf_serialise(conf, p);
+			    UnmapViewOfFile(p);
+			}
+		    }
+		    inherit_handles = TRUE;
+		    sprintf(c, "putty &%p:%u", filemap, (unsigned)size);
+		    cl = c;
+		} else if (wParam == IDM_SAVEDSESS) {
+		    unsigned int sessno = ((lParam - IDM_SAVED_MIN)
+					   / MENU_SAVED_STEP) + 1;
+		    if (sessno < (unsigned)sesslist.nsessions) {
+			char *session = sesslist.sessions[sessno];
+			cl = dupprintf("putty @%s", session);
+			inherit_handles = FALSE;
+			freecl = TRUE;
+		    } else
+			break;
+		} else /* IDM_NEWSESS */ {
+		    cl = NULL;
+		    inherit_handles = FALSE;
+		}
+
+		GetModuleFileName(NULL, b, sizeof(b) - 1);
+		si.cb = sizeof(si);
+		si.lpReserved = NULL;
+		si.lpDesktop = NULL;
+		si.lpTitle = NULL;
+		si.dwFlags = 0;
+		si.cbReserved2 = 0;
+		si.lpReserved2 = NULL;
+		CreateProcess(b, cl, NULL, NULL, inherit_handles,
+			      NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
+                CloseHandle(pi.hProcess);
+                CloseHandle(pi.hThread);
+
+		if (filemap)
+		    CloseHandle(filemap);
+		if (freecl)
+		    sfree(cl);
+	    }
+	    break;
+	  case IDM_RESTART:
+	    if (!back) {
+		logevent(NULL, "----- Session restarted -----");
+		term_pwron(term, FALSE);
+		start_backend();
+	    }
+
+	    break;
+	  case IDM_RECONF:
+	    {
+		Conf *prev_conf;
+		int init_lvl = 1;
+		int reconfig_result;
+
+		if (reconfiguring)
+		    break;
+		else
+		    reconfiguring = TRUE;
+
+		/*
+		 * Copy the current window title into the stored
+		 * previous configuration, so that doing nothing to
+		 * the window title field in the config box doesn't
+		 * reset the title to its startup state.
+		 */
+		conf_set_str(conf, CONF_wintitle, window_name);
+
+		prev_conf = conf_copy(conf);
+
+		reconfig_result =
+		    do_reconfig(hwnd, back ? back->cfg_info(backhandle) : 0);
+		reconfiguring = FALSE;
+		if (!reconfig_result) {
+                    conf_free(prev_conf);
+		    break;
+                }
+
+		conf_cache_data();
+
+		resize_action = conf_get_int(conf, CONF_resize_action);
+		{
+		    /* Disable full-screen if resizing forbidden */
+		    int i;
+		    for (i = 0; i < lenof(popup_menus); i++)
+			EnableMenuItem(popup_menus[i].menu, IDM_FULLSCREEN,
+				       MF_BYCOMMAND | 
+				       (resize_action == RESIZE_DISABLED)
+				       ? MF_GRAYED : MF_ENABLED);
+		    /* Gracefully unzoom if necessary */
+		    if (IsZoomed(hwnd) && (resize_action == RESIZE_DISABLED))
+			ShowWindow(hwnd, SW_RESTORE);
+		}
+
+		/* Pass new config data to the logging module */
+		log_reconfig(logctx, conf);
+
+		sfree(logpal);
+		/*
+		 * Flush the line discipline's edit buffer in the
+		 * case where local editing has just been disabled.
+		 */
+		if (ldisc) {
+                    ldisc_configure(ldisc, conf);
+		    ldisc_send(ldisc, NULL, 0, 0);
+                }
+		if (pal)
+		    DeleteObject(pal);
+		logpal = NULL;
+		pal = NULL;
+		conftopalette();
+		init_palette();
+
+		/* Pass new config data to the terminal */
+		term_reconfig(term, conf);
+
+		/* Pass new config data to the back end */
+		if (back)
+		    back->reconfig(backhandle, conf);
+
+		/* Screen size changed ? */
+		if (conf_get_int(conf, CONF_height) !=
+		    conf_get_int(prev_conf, CONF_height) ||
+		    conf_get_int(conf, CONF_width) !=
+		    conf_get_int(prev_conf, CONF_width) ||
+		    conf_get_int(conf, CONF_savelines) !=
+		    conf_get_int(prev_conf, CONF_savelines) ||
+		    resize_action == RESIZE_FONT ||
+		    (resize_action == RESIZE_EITHER && IsZoomed(hwnd)) ||
+		    resize_action == RESIZE_DISABLED)
+		    term_size(term, conf_get_int(conf, CONF_height),
+			      conf_get_int(conf, CONF_width),
+			      conf_get_int(conf, CONF_savelines));
+
+		/* Enable or disable the scroll bar, etc */
+		{
+		    LONG nflg, flag = GetWindowLongPtr(hwnd, GWL_STYLE);
+		    LONG nexflag, exflag =
+			GetWindowLongPtr(hwnd, GWL_EXSTYLE);
+
+		    nexflag = exflag;
+		    if (conf_get_int(conf, CONF_alwaysontop) !=
+			conf_get_int(prev_conf, CONF_alwaysontop)) {
+			if (conf_get_int(conf, CONF_alwaysontop)) {
+			    nexflag |= WS_EX_TOPMOST;
+			    SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
+					 SWP_NOMOVE | SWP_NOSIZE);
+			} else {
+			    nexflag &= ~(WS_EX_TOPMOST);
+			    SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0,
+					 SWP_NOMOVE | SWP_NOSIZE);
+			}
+		    }
+		    if (conf_get_int(conf, CONF_sunken_edge))
+			nexflag |= WS_EX_CLIENTEDGE;
+		    else
+			nexflag &= ~(WS_EX_CLIENTEDGE);
+
+		    nflg = flag;
+		    if (conf_get_int(conf, is_full_screen() ?
+				     CONF_scrollbar_in_fullscreen :
+				     CONF_scrollbar))
+			nflg |= WS_VSCROLL;
+		    else
+			nflg &= ~WS_VSCROLL;
+
+		    if (resize_action == RESIZE_DISABLED ||
+                        is_full_screen())
+			nflg &= ~WS_THICKFRAME;
+		    else
+			nflg |= WS_THICKFRAME;
+
+		    if (resize_action == RESIZE_DISABLED)
+			nflg &= ~WS_MAXIMIZEBOX;
+		    else
+			nflg |= WS_MAXIMIZEBOX;
+
+		    if (nflg != flag || nexflag != exflag) {
+			if (nflg != flag)
+			    SetWindowLongPtr(hwnd, GWL_STYLE, nflg);
+			if (nexflag != exflag)
+			    SetWindowLongPtr(hwnd, GWL_EXSTYLE, nexflag);
+
+			SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
+				     SWP_NOACTIVATE | SWP_NOCOPYBITS |
+				     SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
+				     SWP_FRAMECHANGED);
+
+			init_lvl = 2;
+		    }
+		}
+
+		/* Oops */
+		if (resize_action == RESIZE_DISABLED && IsZoomed(hwnd)) {
+		    force_normal(hwnd);
+		    init_lvl = 2;
+		}
+
+		set_title(NULL, conf_get_str(conf, CONF_wintitle));
+		if (IsIconic(hwnd)) {
+		    SetWindowText(hwnd,
+				  conf_get_int(conf, CONF_win_name_always) ?
+				  window_name : icon_name);
+		}
+
+		{
+		    FontSpec *font = conf_get_fontspec(conf, CONF_font);
+		    FontSpec *prev_font = conf_get_fontspec(prev_conf,
+                                                             CONF_font);
+
+		    if (!strcmp(font->name, prev_font->name) ||
+			!strcmp(conf_get_str(conf, CONF_line_codepage),
+				conf_get_str(prev_conf, CONF_line_codepage)) ||
+			font->isbold != prev_font->isbold ||
+			font->height != prev_font->height ||
+			font->charset != prev_font->charset ||
+			conf_get_int(conf, CONF_font_quality) !=
+			conf_get_int(prev_conf, CONF_font_quality) ||
+			conf_get_int(conf, CONF_vtmode) !=
+			conf_get_int(prev_conf, CONF_vtmode) ||
+			conf_get_int(conf, CONF_bold_style) !=
+			conf_get_int(prev_conf, CONF_bold_style) ||
+			resize_action == RESIZE_DISABLED ||
+			resize_action == RESIZE_EITHER ||
+			resize_action != conf_get_int(prev_conf,
+						      CONF_resize_action))
+			init_lvl = 2;
+		}
+
+		InvalidateRect(hwnd, NULL, TRUE);
+		reset_window(init_lvl);
+
+		conf_free(prev_conf);
+	    }
+	    break;
+	  case IDM_COPYALL:
+	    term_copyall(term);
+	    break;
+	  case IDM_PASTE:
+	    request_paste(NULL);
+	    break;
+	  case IDM_CLRSB:
+	    term_clrsb(term);
+	    break;
+	  case IDM_RESET:
+	    term_pwron(term, TRUE);
+	    if (ldisc)
+		ldisc_send(ldisc, NULL, 0, 0);
+	    break;
+	  case IDM_ABOUT:
+	    showabout(hwnd);
+	    break;
+	  case IDM_HELP:
+	    launch_help(hwnd, NULL);
+	    break;
+	  case SC_MOUSEMENU:
+	    /*
+	     * We get this if the System menu has been activated
+	     * using the mouse.
+	     */
+	    show_mouseptr(1);
+	    break;
+          case SC_KEYMENU:
+	    /*
+	     * We get this if the System menu has been activated
+	     * using the keyboard. This might happen from within
+	     * TranslateKey, in which case it really wants to be
+	     * followed by a `space' character to actually _bring
+	     * the menu up_ rather than just sitting there in
+	     * `ready to appear' state.
+	     */
+	    show_mouseptr(1);	       /* make sure pointer is visible */
+	    if( lParam == 0 )
+		PostMessage(hwnd, WM_CHAR, ' ', 0);
+	    break;
+	  case IDM_FULLSCREEN:
+	    flip_full_screen();
+	    break;
+	  default:
+	    if (wParam >= IDM_SAVED_MIN && wParam < IDM_SAVED_MAX) {
+		SendMessage(hwnd, WM_SYSCOMMAND, IDM_SAVEDSESS, wParam);
+	    }
+	    if (wParam >= IDM_SPECIAL_MIN && wParam <= IDM_SPECIAL_MAX) {
+		int i = (wParam - IDM_SPECIAL_MIN) / 0x10;
+		/*
+		 * Ensure we haven't been sent a bogus SYSCOMMAND
+		 * which would cause us to reference invalid memory
+		 * and crash. Perhaps I'm just too paranoid here.
+		 */
+		if (i >= n_specials)
+		    break;
+		if (back)
+		    back->special(backhandle, specials[i].code);
+	    }
+	}
+	break;
+
+#define X_POS(l) ((int)(short)LOWORD(l))
+#define Y_POS(l) ((int)(short)HIWORD(l))
+
+#define TO_CHR_X(x) ((((x)<0 ? (x)-font_width+1 : (x))-offset_width) / font_width)
+#define TO_CHR_Y(y) ((((y)<0 ? (y)-font_height+1: (y))-offset_height) / font_height)
+      case WM_LBUTTONDOWN:
+      case WM_MBUTTONDOWN:
+      case WM_RBUTTONDOWN:
+      case WM_LBUTTONUP:
+      case WM_MBUTTONUP:
+      case WM_RBUTTONUP:
+	if (message == WM_RBUTTONDOWN &&
+	    ((wParam & MK_CONTROL) ||
+	     (conf_get_int(conf, CONF_mouse_is_xterm) == 2))) {
+	    POINT cursorpos;
+
+	    show_mouseptr(1);	       /* make sure pointer is visible */
+	    GetCursorPos(&cursorpos);
+	    TrackPopupMenu(popup_menus[CTXMENU].menu,
+			   TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RIGHTBUTTON,
+			   cursorpos.x, cursorpos.y,
+			   0, hwnd, NULL);
+	    break;
+	}
+	{
+	    int button, press;
+
+	    switch (message) {
+	      case WM_LBUTTONDOWN:
+		button = MBT_LEFT;
+		wParam |= MK_LBUTTON;
+		press = 1;
+		break;
+	      case WM_MBUTTONDOWN:
+		button = MBT_MIDDLE;
+		wParam |= MK_MBUTTON;
+		press = 1;
+		break;
+	      case WM_RBUTTONDOWN:
+		button = MBT_RIGHT;
+		wParam |= MK_RBUTTON;
+		press = 1;
+		break;
+	      case WM_LBUTTONUP:
+		button = MBT_LEFT;
+		wParam &= ~MK_LBUTTON;
+		press = 0;
+		break;
+	      case WM_MBUTTONUP:
+		button = MBT_MIDDLE;
+		wParam &= ~MK_MBUTTON;
+		press = 0;
+		break;
+	      case WM_RBUTTONUP:
+		button = MBT_RIGHT;
+		wParam &= ~MK_RBUTTON;
+		press = 0;
+		break;
+	      default:
+		button = press = 0;    /* shouldn't happen */
+	    }
+	    show_mouseptr(1);
+	    /*
+	     * Special case: in full-screen mode, if the left
+	     * button is clicked in the very top left corner of the
+	     * window, we put up the System menu instead of doing
+	     * selection.
+	     */
+	    {
+		char mouse_on_hotspot = 0;
+		POINT pt;
+
+		GetCursorPos(&pt);
+#ifndef NO_MULTIMON
+		{
+		    HMONITOR mon;
+		    MONITORINFO mi;
+
+		    mon = MonitorFromPoint(pt, MONITOR_DEFAULTTONULL);
+
+		    if (mon != NULL) {
+			mi.cbSize = sizeof(MONITORINFO);
+			GetMonitorInfo(mon, &mi);
+
+			if (mi.rcMonitor.left == pt.x &&
+			    mi.rcMonitor.top == pt.y) {
+			    mouse_on_hotspot = 1;
+			}
+		    }
+		}
+#else
+		if (pt.x == 0 && pt.y == 0) {
+		    mouse_on_hotspot = 1;
+		}
+#endif
+		if (is_full_screen() && press &&
+		    button == MBT_LEFT && mouse_on_hotspot) {
+		    SendMessage(hwnd, WM_SYSCOMMAND, SC_MOUSEMENU,
+				MAKELPARAM(pt.x, pt.y));
+		    return 0;
+		}
+	    }
+
+	    if (press) {
+		click(button,
+		      TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam)),
+		      wParam & MK_SHIFT, wParam & MK_CONTROL,
+		      is_alt_pressed());
+		SetCapture(hwnd);
+	    } else {
+		term_mouse(term, button, translate_button(button), MA_RELEASE,
+			   TO_CHR_X(X_POS(lParam)),
+			   TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
+			   wParam & MK_CONTROL, is_alt_pressed());
+		if (!(wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)))
+		    ReleaseCapture();
+	    }
+	}
+	return 0;
+      case WM_MOUSEMOVE:
+	{
+	    /*
+	     * Windows seems to like to occasionally send MOUSEMOVE
+	     * events even if the mouse hasn't moved. Don't unhide
+	     * the mouse pointer in this case.
+	     */
+	    static WPARAM wp = 0;
+	    static LPARAM lp = 0;
+	    if (wParam != wp || lParam != lp ||
+		last_mousemove != WM_MOUSEMOVE) {
+		show_mouseptr(1);
+		wp = wParam; lp = lParam;
+		last_mousemove = WM_MOUSEMOVE;
+	    }
+	}
+	/*
+	 * Add the mouse position and message time to the random
+	 * number noise.
+	 */
+	noise_ultralight(lParam);
+
+	if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON) &&
+	    GetCapture() == hwnd) {
+	    Mouse_Button b;
+	    if (wParam & MK_LBUTTON)
+		b = MBT_LEFT;
+	    else if (wParam & MK_MBUTTON)
+		b = MBT_MIDDLE;
+	    else
+		b = MBT_RIGHT;
+	    term_mouse(term, b, translate_button(b), MA_DRAG,
+		       TO_CHR_X(X_POS(lParam)),
+		       TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
+		       wParam & MK_CONTROL, is_alt_pressed());
+	}
+	return 0;
+      case WM_NCMOUSEMOVE:
+	{
+	    static WPARAM wp = 0;
+	    static LPARAM lp = 0;
+	    if (wParam != wp || lParam != lp ||
+		last_mousemove != WM_NCMOUSEMOVE) {
+		show_mouseptr(1);
+		wp = wParam; lp = lParam;
+		last_mousemove = WM_NCMOUSEMOVE;
+	    }
+	}
+	noise_ultralight(lParam);
+	break;
+      case WM_IGNORE_CLIP:
+	ignore_clip = wParam;	       /* don't panic on DESTROYCLIPBOARD */
+	break;
+      case WM_DESTROYCLIPBOARD:
+	if (!ignore_clip)
+	    term_deselect(term);
+	ignore_clip = FALSE;
+	return 0;
+      case WM_PAINT:
+	{
+	    PAINTSTRUCT p;
+
+	    HideCaret(hwnd);
+	    hdc = BeginPaint(hwnd, &p);
+	    if (pal) {
+		SelectPalette(hdc, pal, TRUE);
+		RealizePalette(hdc);
+	    }
+
+	    /*
+	     * We have to be careful about term_paint(). It will
+	     * set a bunch of character cells to INVALID and then
+	     * call do_paint(), which will redraw those cells and
+	     * _then mark them as done_. This may not be accurate:
+	     * when painting in WM_PAINT context we are restricted
+	     * to the rectangle which has just been exposed - so if
+	     * that only covers _part_ of a character cell and the
+	     * rest of it was already visible, that remainder will
+	     * not be redrawn at all. Accordingly, we must not
+	     * paint any character cell in a WM_PAINT context which
+	     * already has a pending update due to terminal output.
+	     * The simplest solution to this - and many, many
+	     * thanks to Hung-Te Lin for working all this out - is
+	     * not to do any actual painting at _all_ if there's a
+	     * pending terminal update: just mark the relevant
+	     * character cells as INVALID and wait for the
+	     * scheduled full update to sort it out.
+	     * 
+	     * I have a suspicion this isn't the _right_ solution.
+	     * An alternative approach would be to have terminal.c
+	     * separately track what _should_ be on the terminal
+	     * screen and what _is_ on the terminal screen, and
+	     * have two completely different types of redraw (one
+	     * for full updates, which syncs the former with the
+	     * terminal itself, and one for WM_PAINT which syncs
+	     * the latter with the former); yet another possibility
+	     * would be to have the Windows front end do what the
+	     * GTK one already does, and maintain a bitmap of the
+	     * current terminal appearance so that WM_PAINT becomes
+	     * completely trivial. However, this should do for now.
+	     */
+	    term_paint(term, hdc, 
+		       (p.rcPaint.left-offset_width)/font_width,
+		       (p.rcPaint.top-offset_height)/font_height,
+		       (p.rcPaint.right-offset_width-1)/font_width,
+		       (p.rcPaint.bottom-offset_height-1)/font_height,
+		       !term->window_update_pending);
+
+	    if (p.fErase ||
+	        p.rcPaint.left  < offset_width  ||
+		p.rcPaint.top   < offset_height ||
+		p.rcPaint.right >= offset_width + font_width*term->cols ||
+		p.rcPaint.bottom>= offset_height + font_height*term->rows)
+	    {
+		HBRUSH fillcolour, oldbrush;
+		HPEN   edge, oldpen;
+		fillcolour = CreateSolidBrush (
+				    colours[ATTR_DEFBG>>ATTR_BGSHIFT]);
+		oldbrush = SelectObject(hdc, fillcolour);
+		edge = CreatePen(PS_SOLID, 0, 
+				    colours[ATTR_DEFBG>>ATTR_BGSHIFT]);
+		oldpen = SelectObject(hdc, edge);
+
+		/*
+		 * Jordan Russell reports that this apparently
+		 * ineffectual IntersectClipRect() call masks a
+		 * Windows NT/2K bug causing strange display
+		 * problems when the PuTTY window is taller than
+		 * the primary monitor. It seems harmless enough...
+		 */
+		IntersectClipRect(hdc,
+			p.rcPaint.left, p.rcPaint.top,
+			p.rcPaint.right, p.rcPaint.bottom);
+
+		ExcludeClipRect(hdc, 
+			offset_width, offset_height,
+			offset_width+font_width*term->cols,
+			offset_height+font_height*term->rows);
+
+		Rectangle(hdc, p.rcPaint.left, p.rcPaint.top, 
+			  p.rcPaint.right, p.rcPaint.bottom);
+
+		/* SelectClipRgn(hdc, NULL); */
+
+		SelectObject(hdc, oldbrush);
+		DeleteObject(fillcolour);
+		SelectObject(hdc, oldpen);
+		DeleteObject(edge);
+	    }
+	    SelectObject(hdc, GetStockObject(SYSTEM_FONT));
+	    SelectObject(hdc, GetStockObject(WHITE_PEN));
+	    EndPaint(hwnd, &p);
+	    ShowCaret(hwnd);
+	}
+	return 0;
+      case WM_NETEVENT:
+        {
+            /*
+             * To protect against re-entrancy when Windows's recv()
+             * immediately triggers a new WSAAsyncSelect window
+             * message, we don't call select_result directly from this
+             * handler but instead wait until we're back out at the
+             * top level of the message loop.
+             */
+            struct wm_netevent_params *params =
+                snew(struct wm_netevent_params);
+            params->wParam = wParam;
+            params->lParam = lParam;
+            queue_toplevel_callback(wm_netevent_callback, params);
+        }
+	return 0;
+      case WM_SETFOCUS:
+	term_set_focus(term, TRUE);
+	CreateCaret(hwnd, caretbm, font_width, font_height);
+	ShowCaret(hwnd);
+	flash_window(0);	       /* stop */
+	compose_state = 0;
+	term_update(term);
+	break;
+      case WM_KILLFOCUS:
+	show_mouseptr(1);
+	term_set_focus(term, FALSE);
+	DestroyCaret();
+	caret_x = caret_y = -1;	       /* ensure caret is replaced next time */
+	term_update(term);
+	break;
+      case WM_ENTERSIZEMOVE:
+#ifdef RDB_DEBUG_PATCH
+	debug((27, "WM_ENTERSIZEMOVE"));
+#endif
+	EnableSizeTip(1);
+	resizing = TRUE;
+	need_backend_resize = FALSE;
+	break;
+      case WM_EXITSIZEMOVE:
+	EnableSizeTip(0);
+	resizing = FALSE;
+#ifdef RDB_DEBUG_PATCH
+	debug((27, "WM_EXITSIZEMOVE"));
+#endif
+	if (need_backend_resize) {
+	    term_size(term, conf_get_int(conf, CONF_height),
+		      conf_get_int(conf, CONF_width),
+		      conf_get_int(conf, CONF_savelines));
+	    InvalidateRect(hwnd, NULL, TRUE);
+	}
+	break;
+      case WM_SIZING:
+	/*
+	 * This does two jobs:
+	 * 1) Keep the sizetip uptodate
+	 * 2) Make sure the window size is _stepped_ in units of the font size.
+	 */
+	resize_action = conf_get_int(conf, CONF_resize_action);
+	if (resize_action == RESIZE_TERM ||
+            (resize_action == RESIZE_EITHER && !is_alt_pressed())) {
+	    int width, height, w, h, ew, eh;
+	    LPRECT r = (LPRECT) lParam;
+
+	    if (!need_backend_resize && resize_action == RESIZE_EITHER &&
+		(conf_get_int(conf, CONF_height) != term->rows ||
+		 conf_get_int(conf, CONF_width) != term->cols)) {
+		/* 
+		 * Great! It seems that both the terminal size and the
+		 * font size have been changed and the user is now dragging.
+		 * 
+		 * It will now be difficult to get back to the configured
+		 * font size!
+		 *
+		 * This would be easier but it seems to be too confusing.
+		 */
+	        conf_set_int(conf, CONF_height, term->rows);
+	        conf_set_int(conf, CONF_width, term->cols);
+
+		InvalidateRect(hwnd, NULL, TRUE);
+		need_backend_resize = TRUE;
+	    }
+
+	    width = r->right - r->left - extra_width;
+	    height = r->bottom - r->top - extra_height;
+	    w = (width + font_width / 2) / font_width;
+	    if (w < 1)
+		w = 1;
+	    h = (height + font_height / 2) / font_height;
+	    if (h < 1)
+		h = 1;
+	    UpdateSizeTip(hwnd, w, h);
+	    ew = width - w * font_width;
+	    eh = height - h * font_height;
+	    if (ew != 0) {
+		if (wParam == WMSZ_LEFT ||
+		    wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
+		    r->left += ew;
+		else
+		    r->right -= ew;
+	    }
+	    if (eh != 0) {
+		if (wParam == WMSZ_TOP ||
+		    wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
+		    r->top += eh;
+		else
+		    r->bottom -= eh;
+	    }
+	    if (ew || eh)
+		return 1;
+	    else
+		return 0;
+	} else {
+	    int width, height, w, h, rv = 0;
+	    int window_border = conf_get_int(conf, CONF_window_border);
+	    int ex_width = extra_width + (window_border - offset_width) * 2;
+	    int ex_height = extra_height + (window_border - offset_height) * 2;
+	    LPRECT r = (LPRECT) lParam;
+
+	    width = r->right - r->left - ex_width;
+	    height = r->bottom - r->top - ex_height;
+
+	    w = (width + term->cols/2)/term->cols;
+	    h = (height + term->rows/2)/term->rows;
+	    if ( r->right != r->left + w*term->cols + ex_width)
+		rv = 1;
+
+	    if (wParam == WMSZ_LEFT ||
+		wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
+		r->left = r->right - w*term->cols - ex_width;
+	    else
+		r->right = r->left + w*term->cols + ex_width;
+
+	    if (r->bottom != r->top + h*term->rows + ex_height)
+		rv = 1;
+
+	    if (wParam == WMSZ_TOP ||
+		wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
+		r->top = r->bottom - h*term->rows - ex_height;
+	    else
+		r->bottom = r->top + h*term->rows + ex_height;
+
+	    return rv;
+	}
+	/* break;  (never reached) */
+      case WM_FULLSCR_ON_MAX:
+	fullscr_on_max = TRUE;
+	break;
+      case WM_MOVE:
+	sys_cursor_update();
+	break;
+      case WM_SIZE:
+	resize_action = conf_get_int(conf, CONF_resize_action);
+#ifdef RDB_DEBUG_PATCH
+	debug((27, "WM_SIZE %s (%d,%d)",
+		(wParam == SIZE_MINIMIZED) ? "SIZE_MINIMIZED":
+		(wParam == SIZE_MAXIMIZED) ? "SIZE_MAXIMIZED":
+		(wParam == SIZE_RESTORED && resizing) ? "to":
+		(wParam == SIZE_RESTORED) ? "SIZE_RESTORED":
+		"...",
+	    LOWORD(lParam), HIWORD(lParam)));
+#endif
+	if (wParam == SIZE_MINIMIZED)
+	    SetWindowText(hwnd,
+			  conf_get_int(conf, CONF_win_name_always) ?
+			  window_name : icon_name);
+	if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
+	    SetWindowText(hwnd, window_name);
+        if (wParam == SIZE_RESTORED) {
+            processed_resize = FALSE;
+            clear_full_screen();
+            if (processed_resize) {
+                /*
+                 * Inhibit normal processing of this WM_SIZE; a
+                 * secondary one was triggered just now by
+                 * clear_full_screen which contained the correct
+                 * client area size.
+                 */
+                return 0;
+            }
+        }
+        if (wParam == SIZE_MAXIMIZED && fullscr_on_max) {
+            fullscr_on_max = FALSE;
+            processed_resize = FALSE;
+            make_full_screen();
+            if (processed_resize) {
+                /*
+                 * Inhibit normal processing of this WM_SIZE; a
+                 * secondary one was triggered just now by
+                 * make_full_screen which contained the correct client
+                 * area size.
+                 */
+                return 0;
+            }
+        }
+
+        processed_resize = TRUE;
+
+	if (resize_action == RESIZE_DISABLED) {
+	    /* A resize, well it better be a minimize. */
+	    reset_window(-1);
+	} else {
+
+	    int width, height, w, h;
+            int window_border = conf_get_int(conf, CONF_window_border);
+
+	    width = LOWORD(lParam);
+	    height = HIWORD(lParam);
+
+            if (wParam == SIZE_MAXIMIZED && !was_zoomed) {
+                was_zoomed = 1;
+                prev_rows = term->rows;
+                prev_cols = term->cols;
+                if (resize_action == RESIZE_TERM) {
+                    w = width / font_width;
+                    if (w < 1) w = 1;
+                    h = height / font_height;
+                    if (h < 1) h = 1;
+
+                    if (resizing) {
+                        /*
+                         * As below, if we're in the middle of an
+                         * interactive resize we don't call
+                         * back->size. In Windows 7, this case can
+                         * arise in maximisation as well via the Aero
+                         * snap UI.
+                         */
+                        need_backend_resize = TRUE;
+                        conf_set_int(conf, CONF_height, h);
+                        conf_set_int(conf, CONF_width, w);
+                    } else {
+                        term_size(term, h, w,
+                                  conf_get_int(conf, CONF_savelines));
+                    }
+                }
+                reset_window(0);
+            } else if (wParam == SIZE_RESTORED && was_zoomed) {
+                was_zoomed = 0;
+                if (resize_action == RESIZE_TERM) {
+                    w = (width-window_border*2) / font_width;
+                    if (w < 1) w = 1;
+                    h = (height-window_border*2) / font_height;
+                    if (h < 1) h = 1;
+                    term_size(term, h, w, conf_get_int(conf, CONF_savelines));
+                    reset_window(2);
+                } else if (resize_action != RESIZE_FONT)
+                    reset_window(2);
+                else
+                    reset_window(0);
+            } else if (wParam == SIZE_MINIMIZED) {
+                /* do nothing */
+	    } else if (resize_action == RESIZE_TERM ||
+                       (resize_action == RESIZE_EITHER &&
+                        !is_alt_pressed())) {
+                w = (width-window_border*2) / font_width;
+                if (w < 1) w = 1;
+                h = (height-window_border*2) / font_height;
+                if (h < 1) h = 1;
+
+                if (resizing) {
+                    /*
+                     * Don't call back->size in mid-resize. (To
+                     * prevent massive numbers of resize events
+                     * getting sent down the connection during an NT
+                     * opaque drag.)
+                     */
+		    need_backend_resize = TRUE;
+		    conf_set_int(conf, CONF_height, h);
+		    conf_set_int(conf, CONF_width, w);
+                } else {
+                    term_size(term, h, w, conf_get_int(conf, CONF_savelines));
+                }
+            } else {
+                reset_window(0);
+	    }
+	}
+	sys_cursor_update();
+	return 0;
+      case WM_VSCROLL:
+	switch (LOWORD(wParam)) {
+	  case SB_BOTTOM:
+	    term_scroll(term, -1, 0);
+	    break;
+	  case SB_TOP:
+	    term_scroll(term, +1, 0);
+	    break;
+	  case SB_LINEDOWN:
+	    term_scroll(term, 0, +1);
+	    break;
+	  case SB_LINEUP:
+	    term_scroll(term, 0, -1);
+	    break;
+	  case SB_PAGEDOWN:
+	    term_scroll(term, 0, +term->rows / 2);
+	    break;
+	  case SB_PAGEUP:
+	    term_scroll(term, 0, -term->rows / 2);
+	    break;
+	  case SB_THUMBPOSITION:
+	  case SB_THUMBTRACK:
+	    /*
+	     * Use GetScrollInfo instead of HIWORD(wParam) to get
+	     * 32-bit scroll position.
+	     */
+	    {
+		SCROLLINFO si;
+
+		si.cbSize = sizeof(si);
+		si.fMask = SIF_TRACKPOS;
+		if (GetScrollInfo(hwnd, SB_VERT, &si) == 0)
+		    si.nTrackPos = HIWORD(wParam);
+		term_scroll(term, 1, si.nTrackPos);
+	    }
+	    break;
+	}
+	break;
+      case WM_PALETTECHANGED:
+	if ((HWND) wParam != hwnd && pal != NULL) {
+	    HDC hdc = get_ctx(NULL);
+	    if (hdc) {
+		if (RealizePalette(hdc) > 0)
+		    UpdateColors(hdc);
+		free_ctx(hdc);
+	    }
+	}
+	break;
+      case WM_QUERYNEWPALETTE:
+	if (pal != NULL) {
+	    HDC hdc = get_ctx(NULL);
+	    if (hdc) {
+		if (RealizePalette(hdc) > 0)
+		    UpdateColors(hdc);
+		free_ctx(hdc);
+		return TRUE;
+	    }
+	}
+	return FALSE;
+      case WM_KEYDOWN:
+      case WM_SYSKEYDOWN:
+      case WM_KEYUP:
+      case WM_SYSKEYUP:
+	/*
+	 * Add the scan code and keypress timing to the random
+	 * number noise.
+	 */
+	noise_ultralight(lParam);
+
+	/*
+	 * We don't do TranslateMessage since it disassociates the
+	 * resulting CHAR message from the KEYDOWN that sparked it,
+	 * which we occasionally don't want. Instead, we process
+	 * KEYDOWN, and call the Win32 translator functions so that
+	 * we get the translations under _our_ control.
+	 */
+	{
+	    unsigned char buf[20];
+	    int len;
+
+	    if (wParam == VK_PROCESSKEY) { /* IME PROCESS key */
+		if (message == WM_KEYDOWN) {
+		    MSG m;
+		    m.hwnd = hwnd;
+		    m.message = WM_KEYDOWN;
+		    m.wParam = wParam;
+		    m.lParam = lParam & 0xdfff;
+		    TranslateMessage(&m);
+		} else break; /* pass to Windows for default processing */
+	    } else {
+		len = TranslateKey(message, wParam, lParam, buf);
+		if (len == -1)
+		    return DefWindowProc(hwnd, message, wParam, lParam);
+
+		if (len != 0) {
+		    /*
+		     * 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 (ldisc)
+			ldisc_send(ldisc, buf, len, 1);
+		    show_mouseptr(0);
+		}
+	    }
+	}
+	return 0;
+      case WM_INPUTLANGCHANGE:
+	/* wParam == Font number */
+	/* lParam == Locale */
+	set_input_locale((HKL)lParam);
+	sys_cursor_update();
+	break;
+      case WM_IME_STARTCOMPOSITION:
+	{
+	    HIMC hImc = ImmGetContext(hwnd);
+	    ImmSetCompositionFont(hImc, &lfont);
+	    ImmReleaseContext(hwnd, hImc);
+	}
+	break;
+      case WM_IME_COMPOSITION:
+	{
+	    HIMC hIMC;
+	    int n;
+	    char *buff;
+
+	    if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS || 
+	        osVersion.dwPlatformId == VER_PLATFORM_WIN32s) break; /* no Unicode */
+
+	    if ((lParam & GCS_RESULTSTR) == 0) /* Composition unfinished. */
+		break; /* fall back to DefWindowProc */
+
+	    hIMC = ImmGetContext(hwnd);
+	    n = ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, NULL, 0);
+
+	    if (n > 0) {
+		int i;
+		buff = snewn(n, char);
+		ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, buff, n);
+		/*
+		 * Jaeyoun Chung reports that Korean character
+		 * input doesn't work correctly if we do a single
+		 * luni_send() covering the whole of buff. So
+		 * instead we luni_send the characters one by one.
+		 */
+		term_seen_key_event(term);
+		/* don't divide SURROGATE PAIR */
+		if (ldisc) {
+                    for (i = 0; i < n; i += 2) {
+			WCHAR hs = *(unsigned short *)(buff+i);
+			if (IS_HIGH_SURROGATE(hs) && i+2 < n) {
+			    WCHAR ls = *(unsigned short *)(buff+i+2);
+			    if (IS_LOW_SURROGATE(ls)) {
+				luni_send(ldisc, (unsigned short *)(buff+i), 2, 1);
+				i += 2;
+				continue;
+			    }
+			}
+			luni_send(ldisc, (unsigned short *)(buff+i), 1, 1);
+                    }
+		}
+		free(buff);
+	    }
+	    ImmReleaseContext(hwnd, hIMC);
+	    return 1;
+	}
+
+      case WM_IME_CHAR:
+	if (wParam & 0xFF00) {
+	    unsigned char buf[2];
+
+	    buf[1] = wParam;
+	    buf[0] = wParam >> 8;
+	    term_seen_key_event(term);
+	    if (ldisc)
+		lpage_send(ldisc, kbd_codepage, buf, 2, 1);
+	} else {
+	    char c = (unsigned char) wParam;
+	    term_seen_key_event(term);
+	    if (ldisc)
+		lpage_send(ldisc, kbd_codepage, &c, 1, 1);
+	}
+	return (0);
+      case WM_CHAR:
+      case WM_SYSCHAR:
+	/*
+	 * Nevertheless, we are prepared to deal with WM_CHAR
+	 * messages, should they crop up. So if someone wants to
+	 * post the things to us as part of a macro manoeuvre,
+	 * we're ready to cope.
+	 */
+	{
+	    char c = (unsigned char)wParam;
+	    term_seen_key_event(term);
+	    if (ldisc)
+		lpage_send(ldisc, CP_ACP, &c, 1, 1);
+	}
+	return 0;
+      case WM_SYSCOLORCHANGE:
+	if (conf_get_int(conf, CONF_system_colour)) {
+	    /* Refresh palette from system colours. */
+	    /* XXX actually this zaps the entire palette. */
+	    systopalette();
+	    init_palette();
+	    /* Force a repaint of the terminal window. */
+	    term_invalidate(term);
+	}
+	break;
+      case WM_AGENT_CALLBACK:
+	{
+	    struct agent_callback *c = (struct agent_callback *)lParam;
+	    c->callback(c->callback_ctx, c->data, c->len);
+	    sfree(c);
+	}
+	return 0;
+      case WM_GOT_CLIPDATA:
+	if (process_clipdata((HGLOBAL)lParam, wParam))
+	    term_do_paste(term);
+	return 0;
+      default:
+	if (message == wm_mousewheel || message == WM_MOUSEWHEEL) {
+	    int shift_pressed=0, control_pressed=0;
+
+	    if (message == WM_MOUSEWHEEL) {
+		wheel_accumulator += (short)HIWORD(wParam);
+		shift_pressed=LOWORD(wParam) & MK_SHIFT;
+		control_pressed=LOWORD(wParam) & MK_CONTROL;
+	    } else {
+		BYTE keys[256];
+		wheel_accumulator += (int)wParam;
+		if (GetKeyboardState(keys)!=0) {
+		    shift_pressed=keys[VK_SHIFT]&0x80;
+		    control_pressed=keys[VK_CONTROL]&0x80;
+		}
+	    }
+
+	    /* process events when the threshold is reached */
+	    while (abs(wheel_accumulator) >= WHEEL_DELTA) {
+		int b;
+
+		/* reduce amount for next time */
+		if (wheel_accumulator > 0) {
+		    b = MBT_WHEEL_UP;
+		    wheel_accumulator -= WHEEL_DELTA;
+		} else if (wheel_accumulator < 0) {
+		    b = MBT_WHEEL_DOWN;
+		    wheel_accumulator += WHEEL_DELTA;
+		} else
+		    break;
+
+		if (send_raw_mouse &&
+		    !(conf_get_int(conf, CONF_mouse_override) &&
+                      shift_pressed)) {
+		    /* Mouse wheel position is in screen coordinates for
+		     * some reason */
+		    POINT p;
+		    p.x = X_POS(lParam); p.y = Y_POS(lParam);
+		    if (ScreenToClient(hwnd, &p)) {
+			/* send a mouse-down followed by a mouse up */
+			term_mouse(term, b, translate_button(b),
+				   MA_CLICK,
+				   TO_CHR_X(p.x),
+				   TO_CHR_Y(p.y), shift_pressed,
+				   control_pressed, is_alt_pressed());
+		    } /* else: not sure when this can fail */
+		} else {
+		    /* trigger a scroll */
+		    term_scroll(term, 0,
+				b == MBT_WHEEL_UP ?
+				-term->rows / 2 : term->rows / 2);
+		}
+	    }
+	    return 0;
+	}
+    }
+
+    /*
+     * Any messages we don't process completely above are passed through to
+     * DefWindowProc() for default processing.
+     */
+    return DefWindowProc(hwnd, message, wParam, lParam);
+}
+
+/*
+ * Move the system caret. (We maintain one, even though it's
+ * invisible, for the benefit of blind people: apparently some
+ * helper software tracks the system caret, so we should arrange to
+ * have one.)
+ */
+void sys_cursor(void *frontend, int x, int y)
+{
+    int cx, cy;
+
+    if (!term->has_focus) return;
+
+    /*
+     * Avoid gratuitously re-updating the cursor position and IMM
+     * window if there's no actual change required.
+     */
+    cx = x * font_width + offset_width;
+    cy = y * font_height + offset_height;
+    if (cx == caret_x && cy == caret_y)
+	return;
+    caret_x = cx;
+    caret_y = cy;
+
+    sys_cursor_update();
+}
+
+static void sys_cursor_update(void)
+{
+    COMPOSITIONFORM cf;
+    HIMC hIMC;
+
+    if (!term->has_focus) return;
+
+    if (caret_x < 0 || caret_y < 0)
+	return;
+
+    SetCaretPos(caret_x, caret_y);
+
+    /* IMM calls on Win98 and beyond only */
+    if(osVersion.dwPlatformId == VER_PLATFORM_WIN32s) return; /* 3.11 */
+    
+    if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS &&
+	    osVersion.dwMinorVersion == 0) return; /* 95 */
+
+    /* we should have the IMM functions */
+    hIMC = ImmGetContext(hwnd);
+    cf.dwStyle = CFS_POINT;
+    cf.ptCurrentPos.x = caret_x;
+    cf.ptCurrentPos.y = caret_y;
+    ImmSetCompositionWindow(hIMC, &cf);
+
+    ImmReleaseContext(hwnd, hIMC);
+}
+
+/*
+ * Draw a line of text in the window, at given character
+ * coordinates, in given attributes.
+ *
+ * We are allowed to fiddle with the contents of `text'.
+ */
+void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len,
+		      unsigned long attr, int lattr)
+{
+    COLORREF fg, bg, t;
+    int nfg, nbg, nfont;
+    HDC hdc = ctx;
+    RECT line_box;
+    int force_manual_underline = 0;
+    int fnt_width, char_width;
+    int text_adjust = 0;
+    int xoffset = 0;
+    int maxlen, remaining, opaque;
+    int is_cursor = FALSE;
+    static int *lpDx = NULL;
+    static int lpDx_len = 0;
+    int *lpDx_maybe;
+    int len2; /* for SURROGATE PAIR */
+
+    lattr &= LATTR_MODE;
+
+    char_width = fnt_width = font_width * (1 + (lattr != LATTR_NORM));
+
+    if (attr & ATTR_WIDE)
+	char_width *= 2;
+
+    /* Only want the left half of double width lines */
+    if (lattr != LATTR_NORM && x*2 >= term->cols)
+	return;
+
+    x *= fnt_width;
+    y *= font_height;
+    x += offset_width;
+    y += offset_height;
+
+    if ((attr & TATTR_ACTCURS) && (cursor_type == 0 || term->big_cursor)) {
+	attr &= ~(ATTR_REVERSE|ATTR_BLINK|ATTR_COLOURS);
+	/* cursor fg and bg */
+	attr |= (260 << ATTR_FGSHIFT) | (261 << ATTR_BGSHIFT);
+        is_cursor = TRUE;
+    }
+
+    nfont = 0;
+    if (vtmode == VT_POORMAN && lattr != LATTR_NORM) {
+	/* Assume a poorman font is borken in other ways too. */
+	lattr = LATTR_WIDE;
+    } else
+	switch (lattr) {
+	  case LATTR_NORM:
+	    break;
+	  case LATTR_WIDE:
+	    nfont |= FONT_WIDE;
+	    break;
+	  default:
+	    nfont |= FONT_WIDE + FONT_HIGH;
+	    break;
+	}
+    if (attr & ATTR_NARROW)
+	nfont |= FONT_NARROW;
+
+#ifdef USES_VTLINE_HACK
+    /* Special hack for the VT100 linedraw glyphs. */
+    if (text[0] >= 0x23BA && text[0] <= 0x23BD) {
+	switch ((unsigned char) (text[0])) {
+	  case 0xBA:
+	    text_adjust = -2 * font_height / 5;
+	    break;
+	  case 0xBB:
+	    text_adjust = -1 * font_height / 5;
+	    break;
+	  case 0xBC:
+	    text_adjust = font_height / 5;
+	    break;
+	  case 0xBD:
+	    text_adjust = 2 * font_height / 5;
+	    break;
+	}
+	if (lattr == LATTR_TOP || lattr == LATTR_BOT)
+	    text_adjust *= 2;
+	text[0] = ucsdata.unitab_xterm['q'];
+	if (attr & ATTR_UNDER) {
+	    attr &= ~ATTR_UNDER;
+	    force_manual_underline = 1;
+	}
+    }
+#endif
+
+    /* Anything left as an original character set is unprintable. */
+    if (DIRECT_CHAR(text[0]) &&
+        (len < 2 || !IS_SURROGATE_PAIR(text[0], text[1]))) {
+	int i;
+	for (i = 0; i < len; i++)
+	    text[i] = 0xFFFD;
+    }
+
+    /* OEM CP */
+    if ((text[0] & CSET_MASK) == CSET_OEMCP)
+	nfont |= FONT_OEM;
+
+    nfg = ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
+    nbg = ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
+    if (bold_font_mode == BOLD_FONT && (attr & ATTR_BOLD))
+	nfont |= FONT_BOLD;
+    if (und_mode == UND_FONT && (attr & ATTR_UNDER))
+	nfont |= FONT_UNDERLINE;
+    another_font(nfont);
+    if (!fonts[nfont]) {
+	if (nfont & FONT_UNDERLINE)
+	    force_manual_underline = 1;
+	/* Don't do the same for manual bold, it could be bad news. */
+
+	nfont &= ~(FONT_BOLD | FONT_UNDERLINE);
+    }
+    another_font(nfont);
+    if (!fonts[nfont])
+	nfont = FONT_NORMAL;
+    if (attr & ATTR_REVERSE) {
+	t = nfg;
+	nfg = nbg;
+	nbg = t;
+    }
+    if (bold_colours && (attr & ATTR_BOLD) && !is_cursor) {
+	if (nfg < 16) nfg |= 8;
+	else if (nfg >= 256) nfg |= 1;
+    }
+    if (bold_colours && (attr & ATTR_BLINK)) {
+	if (nbg < 16) nbg |= 8;
+	else if (nbg >= 256) nbg |= 1;
+    }
+    fg = colours[nfg];
+    bg = colours[nbg];
+    SelectObject(hdc, fonts[nfont]);
+    SetTextColor(hdc, fg);
+    SetBkColor(hdc, bg);
+    if (attr & TATTR_COMBINING)
+	SetBkMode(hdc, TRANSPARENT);
+    else
+	SetBkMode(hdc, OPAQUE);
+    line_box.left = x;
+    line_box.top = y;
+    line_box.right = x + char_width * len;
+    line_box.bottom = y + font_height;
+    /* adjust line_box.right for SURROGATE PAIR & VARIATION SELECTOR */
+    {
+	int i;
+	int rc_width = 0;
+	for (i = 0; i < len ; i++) {
+	    if (i+1 < len && IS_HIGH_VARSEL(text[i], text[i+1])) {
+		i++;
+	    } else if (i+1 < len && IS_SURROGATE_PAIR(text[i], text[i+1])) {
+		rc_width += char_width;
+		i++;
+	    } else if (IS_LOW_VARSEL(text[i])) {
+		/* do nothing */
+            } else {
+		rc_width += char_width;
+            }
+	}
+	line_box.right = line_box.left + rc_width;
+    }
+
+    /* Only want the left half of double width lines */
+    if (line_box.right > font_width*term->cols+offset_width)
+	line_box.right = font_width*term->cols+offset_width;
+
+    if (font_varpitch) {
+        /*
+         * If we're using a variable-pitch font, we unconditionally
+         * draw the glyphs one at a time and centre them in their
+         * character cells (which means in particular that we must
+         * disable the lpDx mechanism). This gives slightly odd but
+         * generally reasonable results.
+         */
+        xoffset = char_width / 2;
+        SetTextAlign(hdc, TA_TOP | TA_CENTER | TA_NOUPDATECP);
+        lpDx_maybe = NULL;
+        maxlen = 1;
+    } else {
+        /*
+         * In a fixed-pitch font, we draw the whole string in one go
+         * in the normal way.
+         */
+        xoffset = 0;
+        SetTextAlign(hdc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
+        lpDx_maybe = lpDx;
+        maxlen = len;
+    }
+
+    opaque = TRUE;                     /* start by erasing the rectangle */
+    for (remaining = len; remaining > 0;
+         text += len, remaining -= len, x += char_width * len2) {
+        len = (maxlen < remaining ? maxlen : remaining);
+        /* don't divide SURROGATE PAIR and VARIATION SELECTOR */
+        len2 = len;
+        if (maxlen == 1) {
+            if (remaining >= 1 && IS_SURROGATE_PAIR(text[0], text[1]))
+                len++;
+            if (remaining-len >= 1 && IS_LOW_VARSEL(text[len]))
+                len++;
+            else if (remaining-len >= 2 &&
+                     IS_HIGH_VARSEL(text[len], text[len+1]))
+                len += 2;
+        }
+
+	if (len > lpDx_len) {
+	    lpDx_len = len * 9 / 8 + 16;
+	    lpDx = sresize(lpDx, lpDx_len, int);
+
+	    if (lpDx_maybe) lpDx_maybe = lpDx;
+	}
+
+        {
+            int i;
+            /* only last char has dx width in SURROGATE PAIR and
+             * VARIATION sequence */
+            for (i = 0; i < len; i++) {
+                lpDx[i] = char_width;
+                if (i+1 < len && IS_HIGH_VARSEL(text[i], text[i+1])) {
+                    if (i > 0) lpDx[i-1] = 0;
+                    lpDx[i] = 0;
+                    i++;
+                    lpDx[i] = char_width;
+                } else if (i+1 < len && IS_SURROGATE_PAIR(text[i],text[i+1])) {
+                    lpDx[i] = 0;
+                    i++;
+                    lpDx[i] = char_width;
+                } else if (IS_LOW_VARSEL(text[i])) {
+                    if (i > 0) lpDx[i-1] = 0;
+                    lpDx[i] = char_width;
+                }
+            }
+        }
+
+        /* We're using a private area for direct to font. (512 chars.) */
+        if (ucsdata.dbcs_screenfont && (text[0] & CSET_MASK) == CSET_ACP) {
+            /* Ho Hum, dbcs fonts are a PITA! */
+            /* To display on W9x I have to convert to UCS */
+            static wchar_t *uni_buf = 0;
+            static int uni_len = 0;
+            int nlen, mptr;
+            if (len > uni_len) {
+                sfree(uni_buf);
+                uni_len = len;
+                uni_buf = snewn(uni_len, wchar_t);
+            }
+
+            for(nlen = mptr = 0; mptr<len; mptr++) {
+                uni_buf[nlen] = 0xFFFD;
+                if (IsDBCSLeadByteEx(ucsdata.font_codepage,
+                                     (BYTE) text[mptr])) {
+                    char dbcstext[2];
+                    dbcstext[0] = text[mptr] & 0xFF;
+                    dbcstext[1] = text[mptr+1] & 0xFF;
+                    lpDx[nlen] += char_width;
+                    MultiByteToWideChar(ucsdata.font_codepage, MB_USEGLYPHCHARS,
+                                        dbcstext, 2, uni_buf+nlen, 1);
+                    mptr++;
+                }
+                else
+                {
+                    char dbcstext[1];
+                    dbcstext[0] = text[mptr] & 0xFF;
+                    MultiByteToWideChar(ucsdata.font_codepage, MB_USEGLYPHCHARS,
+                                        dbcstext, 1, uni_buf+nlen, 1);
+                }
+                nlen++;
+            }
+            if (nlen <= 0)
+                return;		       /* Eeek! */
+
+            ExtTextOutW(hdc, x + xoffset,
+                        y - font_height * (lattr == LATTR_BOT) + text_adjust,
+                        ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0),
+                        &line_box, uni_buf, nlen,
+                        lpDx_maybe);
+            if (bold_font_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
+                SetBkMode(hdc, TRANSPARENT);
+                ExtTextOutW(hdc, x + xoffset - 1,
+                            y - font_height * (lattr ==
+                                               LATTR_BOT) + text_adjust,
+                            ETO_CLIPPED, &line_box, uni_buf, nlen, lpDx_maybe);
+            }
+
+            lpDx[0] = -1;
+        } else if (DIRECT_FONT(text[0])) {
+            static char *directbuf = NULL;
+            static int directlen = 0;
+            int i;
+            if (len > directlen) {
+                directlen = len;
+                directbuf = sresize(directbuf, directlen, char);
+            }
+
+            for (i = 0; i < len; i++)
+                directbuf[i] = text[i] & 0xFF;
+
+            ExtTextOut(hdc, x + xoffset,
+                       y - font_height * (lattr == LATTR_BOT) + text_adjust,
+                       ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0),
+                       &line_box, directbuf, len, lpDx_maybe);
+            if (bold_font_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
+                SetBkMode(hdc, TRANSPARENT);
+
+                /* GRR: This draws the character outside its box and
+                 * can leave 'droppings' even with the clip box! I
+                 * suppose I could loop it one character at a time ...
+                 * yuk.
+                 * 
+                 * Or ... I could do a test print with "W", and use +1
+                 * or -1 for this shift depending on if the leftmost
+                 * column is blank...
+                 */
+                ExtTextOut(hdc, x + xoffset - 1,
+                           y - font_height * (lattr ==
+                                              LATTR_BOT) + text_adjust,
+                           ETO_CLIPPED, &line_box, directbuf, len, lpDx_maybe);
+            }
+        } else {
+            /* And 'normal' unicode characters */
+            static WCHAR *wbuf = NULL;
+            static int wlen = 0;
+            int i;
+
+            if (wlen < len) {
+                sfree(wbuf);
+                wlen = len;
+                wbuf = snewn(wlen, WCHAR);
+            }
+
+            for (i = 0; i < len; i++)
+                wbuf[i] = text[i];
+
+            /* print Glyphs as they are, without Windows' Shaping*/
+            general_textout(hdc, x + xoffset,
+                            y - font_height * (lattr==LATTR_BOT) + text_adjust,
+                            &line_box, wbuf, len, lpDx,
+                            opaque && !(attr & TATTR_COMBINING));
+
+            /* And the shadow bold hack. */
+            if (bold_font_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
+                SetBkMode(hdc, TRANSPARENT);
+                ExtTextOutW(hdc, x + xoffset - 1,
+                            y - font_height * (lattr ==
+                                               LATTR_BOT) + text_adjust,
+                            ETO_CLIPPED, &line_box, wbuf, len, lpDx_maybe);
+            }
+        }
+
+        /*
+         * If we're looping round again, stop erasing the background
+         * rectangle.
+         */
+        SetBkMode(hdc, TRANSPARENT);
+        opaque = FALSE;
+    }
+    if (lattr != LATTR_TOP && (force_manual_underline ||
+			       (und_mode == UND_LINE
+				&& (attr & ATTR_UNDER)))) {
+	HPEN oldpen;
+	int dec = descent;
+	if (lattr == LATTR_BOT)
+	    dec = dec * 2 - font_height;
+
+	oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, fg));
+	MoveToEx(hdc, line_box.left, line_box.top + dec, NULL);
+	LineTo(hdc, line_box.right, line_box.top + dec);
+	oldpen = SelectObject(hdc, oldpen);
+	DeleteObject(oldpen);
+    }
+}
+
+/*
+ * Wrapper that handles combining characters.
+ */
+void do_text(Context ctx, int x, int y, wchar_t *text, int len,
+	     unsigned long attr, int lattr)
+{
+    if (attr & TATTR_COMBINING) {
+	unsigned long a = 0;
+	int len0 = 1;
+        /* don't divide SURROGATE PAIR and VARIATION SELECTOR */
+	if (len >= 2 && IS_SURROGATE_PAIR(text[0], text[1]))
+	    len0 = 2;
+	if (len-len0 >= 1 && IS_LOW_VARSEL(text[len0])) {
+	    attr &= ~TATTR_COMBINING;
+	    do_text_internal(ctx, x, y, text, len0+1, attr, lattr);
+	    text += len0+1;
+	    len -= len0+1;
+	    a = TATTR_COMBINING;
+	} else if (len-len0 >= 2 && IS_HIGH_VARSEL(text[len0], text[len0+1])) {
+	    attr &= ~TATTR_COMBINING;
+	    do_text_internal(ctx, x, y, text, len0+2, attr, lattr);
+	    text += len0+2;
+	    len -= len0+2;
+	    a = TATTR_COMBINING;
+	} else {
+            attr &= ~TATTR_COMBINING;
+        }
+
+	while (len--) {
+	    if (len >= 1 && IS_SURROGATE_PAIR(text[0], text[1])) {
+		do_text_internal(ctx, x, y, text, 2, attr | a, lattr);
+		len--;
+		text++;
+	    } else {
+                do_text_internal(ctx, x, y, text, 1, attr | a, lattr);
+            }
+
+	    text++;
+	    a = TATTR_COMBINING;
+	}
+    } else
+	do_text_internal(ctx, x, y, text, len, attr, lattr);
+}
+
+void do_cursor(Context ctx, int x, int y, wchar_t *text, int len,
+	       unsigned long attr, int lattr)
+{
+
+    int fnt_width;
+    int char_width;
+    HDC hdc = ctx;
+    int ctype = cursor_type;
+
+    lattr &= LATTR_MODE;
+
+    if ((attr & TATTR_ACTCURS) && (ctype == 0 || term->big_cursor)) {
+	if (*text != UCSWIDE) {
+	    do_text(ctx, x, y, text, len, attr, lattr);
+	    return;
+	}
+	ctype = 2;
+	attr |= TATTR_RIGHTCURS;
+    }
+
+    fnt_width = char_width = font_width * (1 + (lattr != LATTR_NORM));
+    if (attr & ATTR_WIDE)
+	char_width *= 2;
+    x *= fnt_width;
+    y *= font_height;
+    x += offset_width;
+    y += offset_height;
+
+    if ((attr & TATTR_PASCURS) && (ctype == 0 || term->big_cursor)) {
+	POINT pts[5];
+	HPEN oldpen;
+	pts[0].x = pts[1].x = pts[4].x = x;
+	pts[2].x = pts[3].x = x + char_width - 1;
+	pts[0].y = pts[3].y = pts[4].y = y;
+	pts[1].y = pts[2].y = y + font_height - 1;
+	oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[261]));
+	Polyline(hdc, pts, 5);
+	oldpen = SelectObject(hdc, oldpen);
+	DeleteObject(oldpen);
+    } else if ((attr & (TATTR_ACTCURS | TATTR_PASCURS)) && ctype != 0) {
+	int startx, starty, dx, dy, length, i;
+	if (ctype == 1) {
+	    startx = x;
+	    starty = y + descent;
+	    dx = 1;
+	    dy = 0;
+	    length = char_width;
+	} else {
+	    int xadjust = 0;
+	    if (attr & TATTR_RIGHTCURS)
+		xadjust = char_width - 1;
+	    startx = x + xadjust;
+	    starty = y;
+	    dx = 0;
+	    dy = 1;
+	    length = font_height;
+	}
+	if (attr & TATTR_ACTCURS) {
+	    HPEN oldpen;
+	    oldpen =
+		SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[261]));
+	    MoveToEx(hdc, startx, starty, NULL);
+	    LineTo(hdc, startx + dx * length, starty + dy * length);
+	    oldpen = SelectObject(hdc, oldpen);
+	    DeleteObject(oldpen);
+	} else {
+	    for (i = 0; i < length; i++) {
+		if (i % 2 == 0) {
+		    SetPixel(hdc, startx, starty, colours[261]);
+		}
+		startx += dx;
+		starty += dy;
+	    }
+	}
+    }
+}
+
+/* This function gets the actual width of a character in the normal font.
+ */
+int char_width(Context ctx, int uc) {
+    HDC hdc = ctx;
+    int ibuf = 0;
+
+    /* If the font max is the same as the font ave width then this
+     * function is a no-op.
+     */
+    if (!font_dualwidth) return 1;
+
+    switch (uc & CSET_MASK) {
+      case CSET_ASCII:
+	uc = ucsdata.unitab_line[uc & 0xFF];
+	break;
+      case CSET_LINEDRW:
+	uc = ucsdata.unitab_xterm[uc & 0xFF];
+	break;
+      case CSET_SCOACS:
+	uc = ucsdata.unitab_scoacs[uc & 0xFF];
+	break;
+    }
+    if (DIRECT_FONT(uc)) {
+	if (ucsdata.dbcs_screenfont) return 1;
+
+	/* Speedup, I know of no font where ascii is the wrong width */
+	if ((uc&~CSET_MASK) >= ' ' && (uc&~CSET_MASK)<= '~')
+	    return 1;
+
+	if ( (uc & CSET_MASK) == CSET_ACP ) {
+	    SelectObject(hdc, fonts[FONT_NORMAL]);
+	} else if ( (uc & CSET_MASK) == CSET_OEMCP ) {
+	    another_font(FONT_OEM);
+	    if (!fonts[FONT_OEM]) return 0;
+
+	    SelectObject(hdc, fonts[FONT_OEM]);
+	} else
+	    return 0;
+
+	if ( GetCharWidth32(hdc, uc&~CSET_MASK, uc&~CSET_MASK, &ibuf) != 1 &&
+	     GetCharWidth(hdc, uc&~CSET_MASK, uc&~CSET_MASK, &ibuf) != 1)
+	    return 0;
+    } else {
+	/* Speedup, I know of no font where ascii is the wrong width */
+	if (uc >= ' ' && uc <= '~') return 1;
+
+	SelectObject(hdc, fonts[FONT_NORMAL]);
+	if ( GetCharWidth32W(hdc, uc, uc, &ibuf) == 1 )
+	    /* Okay that one worked */ ;
+	else if ( GetCharWidthW(hdc, uc, uc, &ibuf) == 1 )
+	    /* This should work on 9x too, but it's "less accurate" */ ;
+	else
+	    return 0;
+    }
+
+    ibuf += font_width / 2 -1;
+    ibuf /= font_width;
+
+    return ibuf;
+}
+
+DECL_WINDOWS_FUNCTION(static, BOOL, FlashWindowEx, (PFLASHWINFO));
+DECL_WINDOWS_FUNCTION(static, BOOL, ToUnicodeEx,
+                      (UINT, UINT, const BYTE *, LPWSTR, int, UINT, HKL));
+
+static void init_winfuncs(void)
+{
+    HMODULE user32_module = load_system32_dll("user32.dll");
+    GET_WINDOWS_FUNCTION(user32_module, FlashWindowEx);
+    GET_WINDOWS_FUNCTION(user32_module, ToUnicodeEx);
+}
+
+/*
+ * Translate a WM_(SYS)?KEY(UP|DOWN) message into a string of ASCII
+ * codes. Returns number of bytes used, zero to drop the message,
+ * -1 to forward the message to Windows, or another negative number
+ * to indicate a NUL-terminated "special" string.
+ */
+static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
+			unsigned char *output)
+{
+    BYTE keystate[256];
+    int scan, left_alt = 0, key_down, shift_state;
+    int r, i, code;
+    unsigned char *p = output;
+    static int alt_sum = 0;
+    int funky_type = conf_get_int(conf, CONF_funky_type);
+    int no_applic_k = conf_get_int(conf, CONF_no_applic_k);
+    int ctrlaltkeys = conf_get_int(conf, CONF_ctrlaltkeys);
+    int nethack_keypad = conf_get_int(conf, CONF_nethack_keypad);
+
+    HKL kbd_layout = GetKeyboardLayout(0);
+
+    static wchar_t keys_unicode[3];
+    static int compose_char = 0;
+    static WPARAM compose_keycode = 0;
+
+    r = GetKeyboardState(keystate);
+    if (!r)
+	memset(keystate, 0, sizeof(keystate));
+    else {
+#if 0
+#define SHOW_TOASCII_RESULT
+	{			       /* Tell us all about key events */
+	    static BYTE oldstate[256];
+	    static int first = 1;
+	    static int scan;
+	    int ch;
+	    if (first)
+		memcpy(oldstate, keystate, sizeof(oldstate));
+	    first = 0;
+
+	    if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT) {
+		debug(("+"));
+	    } else if ((HIWORD(lParam) & KF_UP)
+		       && scan == (HIWORD(lParam) & 0xFF)) {
+		debug((". U"));
+	    } else {
+		debug((".\n"));
+		if (wParam >= VK_F1 && wParam <= VK_F20)
+		    debug(("K_F%d", wParam + 1 - VK_F1));
+		else
+		    switch (wParam) {
+		      case VK_SHIFT:
+			debug(("SHIFT"));
+			break;
+		      case VK_CONTROL:
+			debug(("CTRL"));
+			break;
+		      case VK_MENU:
+			debug(("ALT"));
+			break;
+		      default:
+			debug(("VK_%02x", wParam));
+		    }
+		if (message == WM_SYSKEYDOWN || message == WM_SYSKEYUP)
+		    debug(("*"));
+		debug((", S%02x", scan = (HIWORD(lParam) & 0xFF)));
+
+		ch = MapVirtualKeyEx(wParam, 2, kbd_layout);
+		if (ch >= ' ' && ch <= '~')
+		    debug((", '%c'", ch));
+		else if (ch)
+		    debug((", $%02x", ch));
+
+		if (keys_unicode[0])
+		    debug((", KB0=%04x", keys_unicode[0]));
+		if (keys_unicode[1])
+		    debug((", KB1=%04x", keys_unicode[1]));
+		if (keys_unicode[2])
+		    debug((", KB2=%04x", keys_unicode[2]));
+
+		if ((keystate[VK_SHIFT] & 0x80) != 0)
+		    debug((", S"));
+		if ((keystate[VK_CONTROL] & 0x80) != 0)
+		    debug((", C"));
+		if ((HIWORD(lParam) & KF_EXTENDED))
+		    debug((", E"));
+		if ((HIWORD(lParam) & KF_UP))
+		    debug((", U"));
+	    }
+
+	    if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT);
+	    else if ((HIWORD(lParam) & KF_UP))
+		oldstate[wParam & 0xFF] ^= 0x80;
+	    else
+		oldstate[wParam & 0xFF] ^= 0x81;
+
+	    for (ch = 0; ch < 256; ch++)
+		if (oldstate[ch] != keystate[ch])
+		    debug((", M%02x=%02x", ch, keystate[ch]));
+
+	    memcpy(oldstate, keystate, sizeof(oldstate));
+	}
+#endif
+
+	if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED)) {
+	    keystate[VK_RMENU] = keystate[VK_MENU];
+	}
+
+
+	/* Nastyness with NUMLock - Shift-NUMLock is left alone though */
+	if ((funky_type == FUNKY_VT400 ||
+	     (funky_type <= FUNKY_LINUX && term->app_keypad_keys &&
+	      !no_applic_k))
+	    && wParam == VK_NUMLOCK && !(keystate[VK_SHIFT] & 0x80)) {
+
+	    wParam = VK_EXECUTE;
+
+	    /* UnToggle NUMLock */
+	    if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0)
+		keystate[VK_NUMLOCK] ^= 1;
+	}
+
+	/* And write back the 'adjusted' state */
+	SetKeyboardState(keystate);
+    }
+
+    /* Disable Auto repeat if required */
+    if (term->repeat_off &&
+	(HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT)
+	return 0;
+
+    if ((HIWORD(lParam) & KF_ALTDOWN) && (keystate[VK_RMENU] & 0x80) == 0)
+	left_alt = 1;
+
+    key_down = ((HIWORD(lParam) & KF_UP) == 0);
+
+    /* Make sure Ctrl-ALT is not the same as AltGr for ToAscii unless told. */
+    if (left_alt && (keystate[VK_CONTROL] & 0x80)) {
+	if (ctrlaltkeys)
+	    keystate[VK_MENU] = 0;
+	else {
+	    keystate[VK_RMENU] = 0x80;
+	    left_alt = 0;
+	}
+    }
+
+    scan = (HIWORD(lParam) & (KF_UP | KF_EXTENDED | 0xFF));
+    shift_state = ((keystate[VK_SHIFT] & 0x80) != 0)
+	+ ((keystate[VK_CONTROL] & 0x80) != 0) * 2;
+
+    /* Note if AltGr was pressed and if it was used as a compose key */
+    if (!compose_state) {
+	compose_keycode = 0x100;
+	if (conf_get_int(conf, CONF_compose_key)) {
+	    if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED))
+		compose_keycode = wParam;
+	}
+	if (wParam == VK_APPS)
+	    compose_keycode = wParam;
+    }
+
+    if (wParam == compose_keycode) {
+	if (compose_state == 0
+	    && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0) compose_state =
+		1;
+	else if (compose_state == 1 && (HIWORD(lParam) & KF_UP))
+	    compose_state = 2;
+	else
+	    compose_state = 0;
+    } else if (compose_state == 1 && wParam != VK_CONTROL)
+	compose_state = 0;
+
+    if (compose_state > 1 && left_alt)
+	compose_state = 0;
+
+    /* Sanitize the number pad if not using a PC NumPad */
+    if (left_alt || (term->app_keypad_keys && !no_applic_k
+		     && funky_type != FUNKY_XTERM)
+	|| funky_type == FUNKY_VT400 || nethack_keypad || compose_state) {
+	if ((HIWORD(lParam) & KF_EXTENDED) == 0) {
+	    int nParam = 0;
+	    switch (wParam) {
+	      case VK_INSERT:
+		nParam = VK_NUMPAD0;
+		break;
+	      case VK_END:
+		nParam = VK_NUMPAD1;
+		break;
+	      case VK_DOWN:
+		nParam = VK_NUMPAD2;
+		break;
+	      case VK_NEXT:
+		nParam = VK_NUMPAD3;
+		break;
+	      case VK_LEFT:
+		nParam = VK_NUMPAD4;
+		break;
+	      case VK_CLEAR:
+		nParam = VK_NUMPAD5;
+		break;
+	      case VK_RIGHT:
+		nParam = VK_NUMPAD6;
+		break;
+	      case VK_HOME:
+		nParam = VK_NUMPAD7;
+		break;
+	      case VK_UP:
+		nParam = VK_NUMPAD8;
+		break;
+	      case VK_PRIOR:
+		nParam = VK_NUMPAD9;
+		break;
+	      case VK_DELETE:
+		nParam = VK_DECIMAL;
+		break;
+	    }
+	    if (nParam) {
+		if (keystate[VK_NUMLOCK] & 1)
+		    shift_state |= 1;
+		wParam = nParam;
+	    }
+	}
+    }
+
+    /* If a key is pressed and AltGr is not active */
+    if (key_down && (keystate[VK_RMENU] & 0x80) == 0 && !compose_state) {
+	/* Okay, prepare for most alts then ... */
+	if (left_alt)
+	    *p++ = '\033';
+
+	/* Lets see if it's a pattern we know all about ... */
+	if (wParam == VK_PRIOR && shift_state == 1) {
+	    SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0);
+	    return 0;
+	}
+	if (wParam == VK_PRIOR && shift_state == 2) {
+	    SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0);
+	    return 0;
+	}
+	if (wParam == VK_NEXT && shift_state == 1) {
+	    SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
+	    return 0;
+	}
+	if (wParam == VK_NEXT && shift_state == 2) {
+	    SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0);
+	    return 0;
+	}
+	if ((wParam == VK_PRIOR || wParam == VK_NEXT) && shift_state == 3) {
+	    term_scroll_to_selection(term, (wParam == VK_PRIOR ? 0 : 1));
+	    return 0;
+	}
+	if (wParam == VK_INSERT && shift_state == 1) {
+	    request_paste(NULL);
+	    return 0;
+	}
+	if (left_alt && wParam == VK_F4 && conf_get_int(conf, CONF_alt_f4)) {
+	    return -1;
+	}
+	if (left_alt && wParam == VK_SPACE && conf_get_int(conf,
+							   CONF_alt_space)) {
+	    SendMessage(hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0);
+	    return -1;
+	}
+	if (left_alt && wParam == VK_RETURN &&
+	    conf_get_int(conf, CONF_fullscreenonaltenter) &&
+	    (conf_get_int(conf, CONF_resize_action) != RESIZE_DISABLED)) {
+ 	    if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) != KF_REPEAT)
+ 		flip_full_screen();
+	    return -1;
+	}
+	/* Control-Numlock for app-keypad mode switch */
+	if (wParam == VK_PAUSE && shift_state == 2) {
+	    term->app_keypad_keys ^= 1;
+	    return 0;
+	}
+
+	/* Nethack keypad */
+	if (nethack_keypad && !left_alt) {
+	    switch (wParam) {
+	      case VK_NUMPAD1:
+		*p++ = "bB\002\002"[shift_state & 3];
+		return p - output;
+	      case VK_NUMPAD2:
+		*p++ = "jJ\012\012"[shift_state & 3];
+		return p - output;
+	      case VK_NUMPAD3:
+		*p++ = "nN\016\016"[shift_state & 3];
+		return p - output;
+	      case VK_NUMPAD4:
+		*p++ = "hH\010\010"[shift_state & 3];
+		return p - output;
+	      case VK_NUMPAD5:
+		*p++ = shift_state ? '.' : '.';
+		return p - output;
+	      case VK_NUMPAD6:
+		*p++ = "lL\014\014"[shift_state & 3];
+		return p - output;
+	      case VK_NUMPAD7:
+		*p++ = "yY\031\031"[shift_state & 3];
+		return p - output;
+	      case VK_NUMPAD8:
+		*p++ = "kK\013\013"[shift_state & 3];
+		return p - output;
+	      case VK_NUMPAD9:
+		*p++ = "uU\025\025"[shift_state & 3];
+		return p - output;
+	    }
+	}
+
+	/* Application Keypad */
+	if (!left_alt) {
+	    int xkey = 0;
+
+	    if (funky_type == FUNKY_VT400 ||
+		(funky_type <= FUNKY_LINUX &&
+		 term->app_keypad_keys && !no_applic_k)) switch (wParam) {
+		  case VK_EXECUTE:
+		    xkey = 'P';
+		    break;
+		  case VK_DIVIDE:
+		    xkey = 'Q';
+		    break;
+		  case VK_MULTIPLY:
+		    xkey = 'R';
+		    break;
+		  case VK_SUBTRACT:
+		    xkey = 'S';
+		    break;
+		}
+	    if (term->app_keypad_keys && !no_applic_k)
+		switch (wParam) {
+		  case VK_NUMPAD0:
+		    xkey = 'p';
+		    break;
+		  case VK_NUMPAD1:
+		    xkey = 'q';
+		    break;
+		  case VK_NUMPAD2:
+		    xkey = 'r';
+		    break;
+		  case VK_NUMPAD3:
+		    xkey = 's';
+		    break;
+		  case VK_NUMPAD4:
+		    xkey = 't';
+		    break;
+		  case VK_NUMPAD5:
+		    xkey = 'u';
+		    break;
+		  case VK_NUMPAD6:
+		    xkey = 'v';
+		    break;
+		  case VK_NUMPAD7:
+		    xkey = 'w';
+		    break;
+		  case VK_NUMPAD8:
+		    xkey = 'x';
+		    break;
+		  case VK_NUMPAD9:
+		    xkey = 'y';
+		    break;
+
+		  case VK_DECIMAL:
+		    xkey = 'n';
+		    break;
+		  case VK_ADD:
+		    if (funky_type == FUNKY_XTERM) {
+			if (shift_state)
+			    xkey = 'l';
+			else
+			    xkey = 'k';
+		    } else if (shift_state)
+			xkey = 'm';
+		    else
+			xkey = 'l';
+		    break;
+
+		  case VK_DIVIDE:
+		    if (funky_type == FUNKY_XTERM)
+			xkey = 'o';
+		    break;
+		  case VK_MULTIPLY:
+		    if (funky_type == FUNKY_XTERM)
+			xkey = 'j';
+		    break;
+		  case VK_SUBTRACT:
+		    if (funky_type == FUNKY_XTERM)
+			xkey = 'm';
+		    break;
+
+		  case VK_RETURN:
+		    if (HIWORD(lParam) & KF_EXTENDED)
+			xkey = 'M';
+		    break;
+		}
+	    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);
+		return p - output;
+	    }
+	}
+
+	if (wParam == VK_BACK && shift_state == 0) {	/* Backspace */
+	    *p++ = (conf_get_int(conf, CONF_bksp_is_delete) ? 0x7F : 0x08);
+	    *p++ = 0;
+	    return -2;
+	}
+	if (wParam == VK_BACK && shift_state == 1) {	/* Shift Backspace */
+	    /* We do the opposite of what is configured */
+	    *p++ = (conf_get_int(conf, CONF_bksp_is_delete) ? 0x08 : 0x7F);
+	    *p++ = 0;
+	    return -2;
+	}
+	if (wParam == VK_TAB && shift_state == 1) {	/* Shift tab */
+	    *p++ = 0x1B;
+	    *p++ = '[';
+	    *p++ = 'Z';
+	    return p - output;
+	}
+	if (wParam == VK_SPACE && shift_state == 2) {	/* Ctrl-Space */
+	    *p++ = 0;
+	    return p - output;
+	}
+	if (wParam == VK_SPACE && shift_state == 3) {	/* Ctrl-Shift-Space */
+	    *p++ = 160;
+	    return p - output;
+	}
+	if (wParam == VK_CANCEL && shift_state == 2) {	/* Ctrl-Break */
+	    if (back)
+		back->special(backhandle, TS_BRK);
+	    return 0;
+	}
+	if (wParam == VK_PAUSE) {      /* Break/Pause */
+	    *p++ = 26;
+	    *p++ = 0;
+	    return -2;
+	}
+	/* Control-2 to Control-8 are special */
+	if (shift_state == 2 && wParam >= '2' && wParam <= '8') {
+	    *p++ = "\000\033\034\035\036\037\177"[wParam - '2'];
+	    return p - output;
+	}
+	if (shift_state == 2 && (wParam == 0xBD || wParam == 0xBF)) {
+	    *p++ = 0x1F;
+	    return p - output;
+	}
+	if (shift_state == 2 && (wParam == 0xDF || wParam == 0xDC)) {
+	    *p++ = 0x1C;
+	    return p - output;
+	}
+	if (shift_state == 3 && wParam == 0xDE) {
+	    *p++ = 0x1E;	       /* Ctrl-~ == Ctrl-^ in xterm at least */
+	    return p - output;
+	}
+	if (shift_state == 0 && wParam == VK_RETURN && term->cr_lf_return) {
+	    *p++ = '\r';
+	    *p++ = '\n';
+	    return p - output;
+	}
+
+	/*
+	 * Next, all the keys that do tilde codes. (ESC '[' nn '~',
+	 * for integer decimal nn.)
+	 *
+	 * We also deal with the weird ones here. Linux VCs replace F1
+	 * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
+	 * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
+	 * respectively.
+	 */
+	code = 0;
+	switch (wParam) {
+	  case VK_F1:
+	    code = (keystate[VK_SHIFT] & 0x80 ? 23 : 11);
+	    break;
+	  case VK_F2:
+	    code = (keystate[VK_SHIFT] & 0x80 ? 24 : 12);
+	    break;
+	  case VK_F3:
+	    code = (keystate[VK_SHIFT] & 0x80 ? 25 : 13);
+	    break;
+	  case VK_F4:
+	    code = (keystate[VK_SHIFT] & 0x80 ? 26 : 14);
+	    break;
+	  case VK_F5:
+	    code = (keystate[VK_SHIFT] & 0x80 ? 28 : 15);
+	    break;
+	  case VK_F6:
+	    code = (keystate[VK_SHIFT] & 0x80 ? 29 : 17);
+	    break;
+	  case VK_F7:
+	    code = (keystate[VK_SHIFT] & 0x80 ? 31 : 18);
+	    break;
+	  case VK_F8:
+	    code = (keystate[VK_SHIFT] & 0x80 ? 32 : 19);
+	    break;
+	  case VK_F9:
+	    code = (keystate[VK_SHIFT] & 0x80 ? 33 : 20);
+	    break;
+	  case VK_F10:
+	    code = (keystate[VK_SHIFT] & 0x80 ? 34 : 21);
+	    break;
+	  case VK_F11:
+	    code = 23;
+	    break;
+	  case VK_F12:
+	    code = 24;
+	    break;
+	  case VK_F13:
+	    code = 25;
+	    break;
+	  case VK_F14:
+	    code = 26;
+	    break;
+	  case VK_F15:
+	    code = 28;
+	    break;
+	  case VK_F16:
+	    code = 29;
+	    break;
+	  case VK_F17:
+	    code = 31;
+	    break;
+	  case VK_F18:
+	    code = 32;
+	    break;
+	  case VK_F19:
+	    code = 33;
+	    break;
+	  case VK_F20:
+	    code = 34;
+	    break;
+	}
+	if ((shift_state&2) == 0) switch (wParam) {
+	  case VK_HOME:
+	    code = 1;
+	    break;
+	  case VK_INSERT:
+	    code = 2;
+	    break;
+	  case VK_DELETE:
+	    code = 3;
+	    break;
+	  case VK_END:
+	    code = 4;
+	    break;
+	  case VK_PRIOR:
+	    code = 5;
+	    break;
+	  case VK_NEXT:
+	    code = 6;
+	    break;
+	}
+	/* Reorder edit keys to physical order */
+	if (funky_type == FUNKY_VT400 && code <= 6)
+	    code = "\0\2\1\4\5\3\6"[code];
+
+	if (term->vt52_mode && code > 0 && code <= 6) {
+	    p += sprintf((char *) p, "\x1B%c", " HLMEIG"[code]);
+	    return p - output;
+	}
+
+	if (funky_type == FUNKY_SCO && code >= 11 && code <= 34) {
+	    /* SCO function keys */
+	    char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
+	    int index = 0;
+	    switch (wParam) {
+	      case VK_F1: index = 0; break;
+	      case VK_F2: index = 1; break;
+	      case VK_F3: index = 2; break;
+	      case VK_F4: index = 3; break;
+	      case VK_F5: index = 4; break;
+	      case VK_F6: index = 5; break;
+	      case VK_F7: index = 6; break;
+	      case VK_F8: index = 7; break;
+	      case VK_F9: index = 8; break;
+	      case VK_F10: index = 9; break;
+	      case VK_F11: index = 10; break;
+	      case VK_F12: index = 11; break;
+	    }
+	    if (keystate[VK_SHIFT] & 0x80) index += 12;
+	    if (keystate[VK_CONTROL] & 0x80) index += 24;
+	    p += sprintf((char *) p, "\x1B[%c", codes[index]);
+	    return p - output;
+	}
+	if (funky_type == FUNKY_SCO &&     /* SCO small keypad */
+	    code >= 1 && code <= 6) {
+	    char codes[] = "HL.FIG";
+	    if (code == 3) {
+		*p++ = '\x7F';
+	    } else {
+		p += sprintf((char *) p, "\x1B[%c", codes[code-1]);
+	    }
+	    return p - output;
+	}
+	if ((term->vt52_mode || funky_type == FUNKY_VT100P) && code >= 11 && code <= 24) {
+	    int offt = 0;
+	    if (code > 15)
+		offt++;
+	    if (code > 21)
+		offt++;
+	    if (term->vt52_mode)
+		p += sprintf((char *) p, "\x1B%c", code + 'P' - 11 - offt);
+	    else
+		p +=
+		    sprintf((char *) p, "\x1BO%c", code + 'P' - 11 - offt);
+	    return p - output;
+	}
+	if (funky_type == FUNKY_LINUX && code >= 11 && code <= 15) {
+	    p += sprintf((char *) p, "\x1B[[%c", code + 'A' - 11);
+	    return p - output;
+	}
+	if (funky_type == FUNKY_XTERM && code >= 11 && code <= 14) {
+	    if (term->vt52_mode)
+		p += sprintf((char *) p, "\x1B%c", code + 'P' - 11);
+	    else
+		p += sprintf((char *) p, "\x1BO%c", code + 'P' - 11);
+	    return p - output;
+	}
+	if ((code == 1 || code == 4) &&
+	    conf_get_int(conf, CONF_rxvt_homeend)) {
+	    p += sprintf((char *) p, code == 1 ? "\x1B[H" : "\x1BOw");
+	    return p - output;
+	}
+	if (code) {
+	    p += sprintf((char *) p, "\x1B[%d~", code);
+	    return p - output;
+	}
+
+	/*
+	 * Now the remaining keys (arrows and Keypad 5. Keypad 5 for
+	 * some reason seems to send VK_CLEAR to Windows...).
+	 */
+	{
+	    char xkey = 0;
+	    switch (wParam) {
+	      case VK_UP:
+		xkey = 'A';
+		break;
+	      case VK_DOWN:
+		xkey = 'B';
+		break;
+	      case VK_RIGHT:
+		xkey = 'C';
+		break;
+	      case VK_LEFT:
+		xkey = 'D';
+		break;
+	      case VK_CLEAR:
+		xkey = 'G';
+		break;
+	    }
+	    if (xkey) {
+		p += format_arrow_key(p, term, xkey, shift_state);
+		return p - output;
+	    }
+	}
+
+	/*
+	 * Finally, deal with Return ourselves. (Win95 seems to
+	 * foul it up when Alt is pressed, for some reason.)
+	 */
+	if (wParam == VK_RETURN) {     /* Return */
+	    *p++ = 0x0D;
+	    *p++ = 0;
+	    return -2;
+	}
+
+	if (left_alt && wParam >= VK_NUMPAD0 && wParam <= VK_NUMPAD9)
+	    alt_sum = alt_sum * 10 + wParam - VK_NUMPAD0;
+	else
+	    alt_sum = 0;
+    }
+
+    /* Okay we've done everything interesting; let windows deal with 
+     * the boring stuff */
+    {
+	BOOL capsOn=0;
+
+	/* helg: clear CAPS LOCK state if caps lock switches to cyrillic */
+	if(keystate[VK_CAPITAL] != 0 &&
+	   conf_get_int(conf, CONF_xlat_capslockcyr)) {
+	    capsOn= !left_alt;
+	    keystate[VK_CAPITAL] = 0;
+	}
+
+	/* XXX how do we know what the max size of the keys array should
+	 * be is? There's indication on MS' website of an Inquire/InquireEx
+	 * functioning returning a KBINFO structure which tells us. */
+	if (osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT && p_ToUnicodeEx) {
+	    r = p_ToUnicodeEx(wParam, scan, keystate, keys_unicode,
+                              lenof(keys_unicode), 0, kbd_layout);
+	} else {
+	    /* XXX 'keys' parameter is declared in MSDN documentation as
+	     * 'LPWORD lpChar'.
+	     * The experience of a French user indicates that on
+	     * Win98, WORD[] should be passed in, but on Win2K, it should
+	     * be BYTE[]. German WinXP and my Win2K with "US International"
+	     * driver corroborate this.
+	     * Experimentally I've conditionalised the behaviour on the
+	     * Win9x/NT split, but I suspect it's worse than that.
+	     * See wishlist item `win-dead-keys' for more horrible detail
+	     * and speculations. */
+	    int i;
+	    static WORD keys[3];
+	    static BYTE keysb[3];
+	    r = ToAsciiEx(wParam, scan, keystate, keys, 0, kbd_layout);
+	    if (r > 0) {
+	        for (i = 0; i < r; i++) {
+	            keysb[i] = (BYTE)keys[i];
+	        }
+	        MultiByteToWideChar(CP_ACP, 0, (LPCSTR)keysb, r,
+                                    keys_unicode, lenof(keys_unicode));
+	    }
+	}
+#ifdef SHOW_TOASCII_RESULT
+	if (r == 1 && !key_down) {
+	    if (alt_sum) {
+		if (in_utf(term) || ucsdata.dbcs_screenfont)
+		    debug((", (U+%04x)", alt_sum));
+		else
+		    debug((", LCH(%d)", alt_sum));
+	    } else {
+		debug((", ACH(%d)", keys_unicode[0]));
+	    }
+	} else if (r > 0) {
+	    int r1;
+	    debug((", ASC("));
+	    for (r1 = 0; r1 < r; r1++) {
+		debug(("%s%d", r1 ? "," : "", keys_unicode[r1]));
+	    }
+	    debug((")"));
+	}
+#endif
+	if (r > 0) {
+	    WCHAR keybuf;
+
+	    p = output;
+	    for (i = 0; i < r; i++) {
+		wchar_t wch = keys_unicode[i];
+
+		if (compose_state == 2 && wch >= ' ' && wch < 0x80) {
+		    compose_char = wch;
+		    compose_state++;
+		    continue;
+		}
+		if (compose_state == 3 && wch >= ' ' && wch < 0x80) {
+		    int nc;
+		    compose_state = 0;
+
+		    if ((nc = check_compose(compose_char, wch)) == -1) {
+			MessageBeep(MB_ICONHAND);
+			return 0;
+		    }
+		    keybuf = nc;
+		    term_seen_key_event(term);
+		    if (ldisc)
+			luni_send(ldisc, &keybuf, 1, 1);
+		    continue;
+		}
+
+		compose_state = 0;
+
+		if (!key_down) {
+		    if (alt_sum) {
+			if (in_utf(term) || ucsdata.dbcs_screenfont) {
+			    keybuf = alt_sum;
+			    term_seen_key_event(term);
+			    if (ldisc)
+				luni_send(ldisc, &keybuf, 1, 1);
+			} else {
+			    char ch = (char) alt_sum;
+			    /*
+			     * 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 (ldisc)
+				ldisc_send(ldisc, &ch, 1, 1);
+			}
+			alt_sum = 0;
+		    } else {
+			term_seen_key_event(term);
+			if (ldisc)
+			    luni_send(ldisc, &wch, 1, 1);
+		    }
+		} else {
+		    if(capsOn && wch < 0x80) {
+			WCHAR cbuf[2];
+			cbuf[0] = 27;
+			cbuf[1] = xlat_uskbd2cyrllic(wch);
+			term_seen_key_event(term);
+			if (ldisc)
+			    luni_send(ldisc, cbuf+!left_alt, 1+!!left_alt, 1);
+		    } else {
+			WCHAR cbuf[2];
+			cbuf[0] = '\033';
+			cbuf[1] = wch;
+			term_seen_key_event(term);
+			if (ldisc)
+			    luni_send(ldisc, cbuf +!left_alt, 1+!!left_alt, 1);
+		    }
+		}
+		show_mouseptr(0);
+	    }
+
+	    /* This is so the ALT-Numpad and dead keys work correctly. */
+	    keys_unicode[0] = 0;
+
+	    return p - output;
+	}
+	/* If we're definitly not building up an ALT-54321 then clear it */
+	if (!left_alt)
+	    keys_unicode[0] = 0;
+	/* If we will be using alt_sum fix the 256s */
+	else if (keys_unicode[0] && (in_utf(term) || ucsdata.dbcs_screenfont))
+	    keys_unicode[0] = 10;
+    }
+
+    /*
+     * ALT alone may or may not want to bring up the System menu.
+     * If it's not meant to, we return 0 on presses or releases of
+     * ALT, to show that we've swallowed the keystroke. Otherwise
+     * we return -1, which means Windows will give the keystroke
+     * its default handling (i.e. bring up the System menu).
+     */
+    if (wParam == VK_MENU && !conf_get_int(conf, CONF_alt_only))
+	return 0;
+
+    return -1;
+}
+
+void set_title(void *frontend, char *title)
+{
+    sfree(window_name);
+    window_name = snewn(1 + strlen(title), char);
+    strcpy(window_name, title);
+    if (conf_get_int(conf, CONF_win_name_always) || !IsIconic(hwnd))
+	SetWindowText(hwnd, title);
+}
+
+void set_icon(void *frontend, char *title)
+{
+    sfree(icon_name);
+    icon_name = snewn(1 + strlen(title), char);
+    strcpy(icon_name, title);
+    if (!conf_get_int(conf, CONF_win_name_always) && IsIconic(hwnd))
+	SetWindowText(hwnd, title);
+}
+
+void set_sbar(void *frontend, int total, int start, int page)
+{
+    SCROLLINFO si;
+
+    if (!conf_get_int(conf, is_full_screen() ?
+		      CONF_scrollbar_in_fullscreen : CONF_scrollbar))
+	return;
+
+    si.cbSize = sizeof(si);
+    si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
+    si.nMin = 0;
+    si.nMax = total - 1;
+    si.nPage = page;
+    si.nPos = start;
+    if (hwnd)
+	SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
+}
+
+Context get_ctx(void *frontend)
+{
+    HDC hdc;
+    if (hwnd) {
+	hdc = GetDC(hwnd);
+	if (hdc && pal)
+	    SelectPalette(hdc, pal, FALSE);
+	return hdc;
+    } else
+	return NULL;
+}
+
+void free_ctx(Context ctx)
+{
+    SelectPalette(ctx, GetStockObject(DEFAULT_PALETTE), FALSE);
+    ReleaseDC(hwnd, ctx);
+}
+
+static void real_palette_set(int n, int r, int g, int b)
+{
+    if (pal) {
+	logpal->palPalEntry[n].peRed = r;
+	logpal->palPalEntry[n].peGreen = g;
+	logpal->palPalEntry[n].peBlue = b;
+	logpal->palPalEntry[n].peFlags = PC_NOCOLLAPSE;
+	colours[n] = PALETTERGB(r, g, b);
+	SetPaletteEntries(pal, 0, NALLCOLOURS, logpal->palPalEntry);
+    } else
+	colours[n] = RGB(r, g, b);
+}
+
+void palette_set(void *frontend, int n, int r, int g, int b)
+{
+    if (n >= 16)
+	n += 256 - 16;
+    if (n >= NALLCOLOURS)
+	return;
+    real_palette_set(n, r, g, b);
+    if (pal) {
+	HDC hdc = get_ctx(frontend);
+	UnrealizeObject(pal);
+	RealizePalette(hdc);
+	free_ctx(hdc);
+    } else {
+	if (n == (ATTR_DEFBG>>ATTR_BGSHIFT))
+	    /* If Default Background changes, we need to ensure any
+	     * space between the text area and the window border is
+	     * redrawn. */
+	    InvalidateRect(hwnd, NULL, TRUE);
+    }
+}
+
+void palette_reset(void *frontend)
+{
+    int i;
+
+    /* And this */
+    for (i = 0; i < NALLCOLOURS; i++) {
+	if (pal) {
+	    logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
+	    logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
+	    logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
+	    logpal->palPalEntry[i].peFlags = 0;
+	    colours[i] = PALETTERGB(defpal[i].rgbtRed,
+				    defpal[i].rgbtGreen,
+				    defpal[i].rgbtBlue);
+	} else
+	    colours[i] = RGB(defpal[i].rgbtRed,
+			     defpal[i].rgbtGreen, defpal[i].rgbtBlue);
+    }
+
+    if (pal) {
+	HDC hdc;
+	SetPaletteEntries(pal, 0, NALLCOLOURS, logpal->palPalEntry);
+	hdc = get_ctx(frontend);
+	RealizePalette(hdc);
+	free_ctx(hdc);
+    } else {
+	/* Default Background may have changed. Ensure any space between
+	 * text area and window border is redrawn. */
+	InvalidateRect(hwnd, NULL, TRUE);
+    }
+}
+
+void write_aclip(void *frontend, char *data, int len, int must_deselect)
+{
+    HGLOBAL clipdata;
+    void *lock;
+
+    clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len + 1);
+    if (!clipdata)
+	return;
+    lock = GlobalLock(clipdata);
+    if (!lock)
+	return;
+    memcpy(lock, data, len);
+    ((unsigned char *) lock)[len] = 0;
+    GlobalUnlock(clipdata);
+
+    if (!must_deselect)
+	SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
+
+    if (OpenClipboard(hwnd)) {
+	EmptyClipboard();
+	SetClipboardData(CF_TEXT, clipdata);
+	CloseClipboard();
+    } else
+	GlobalFree(clipdata);
+
+    if (!must_deselect)
+	SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
+}
+
+/*
+ * Note: unlike write_aclip() this will not append a nul.
+ */
+void write_clip(void *frontend, wchar_t * data, int *attr, int len, int must_deselect)
+{
+    HGLOBAL clipdata, clipdata2, clipdata3;
+    int len2;
+    void *lock, *lock2, *lock3;
+
+    len2 = WideCharToMultiByte(CP_ACP, 0, data, len, 0, 0, NULL, NULL);
+
+    clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE,
+			   len * sizeof(wchar_t));
+    clipdata2 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len2);
+
+    if (!clipdata || !clipdata2) {
+	if (clipdata)
+	    GlobalFree(clipdata);
+	if (clipdata2)
+	    GlobalFree(clipdata2);
+	return;
+    }
+    if (!(lock = GlobalLock(clipdata))) {
+        GlobalFree(clipdata);
+        GlobalFree(clipdata2);
+	return;
+    }
+    if (!(lock2 = GlobalLock(clipdata2))) {
+        GlobalUnlock(clipdata);
+        GlobalFree(clipdata);
+        GlobalFree(clipdata2);
+	return;
+    }
+
+    memcpy(lock, data, len * sizeof(wchar_t));
+    WideCharToMultiByte(CP_ACP, 0, data, len, lock2, len2, NULL, NULL);
+
+    if (conf_get_int(conf, CONF_rtf_paste)) {
+	wchar_t unitab[256];
+	char *rtf = NULL;
+	unsigned char *tdata = (unsigned char *)lock2;
+	wchar_t *udata = (wchar_t *)lock;
+	int rtflen = 0, uindex = 0, tindex = 0;
+	int rtfsize = 0;
+	int multilen, blen, alen, totallen, i;
+	char before[16], after[4];
+	int fgcolour,  lastfgcolour  = 0;
+	int bgcolour,  lastbgcolour  = 0;
+	int attrBold,  lastAttrBold  = 0;
+	int attrUnder, lastAttrUnder = 0;
+	int palette[NALLCOLOURS];
+	int numcolours;
+	FontSpec *font = conf_get_fontspec(conf, CONF_font);
+
+	get_unitab(CP_ACP, unitab, 0);
+
+	rtfsize = 100 + strlen(font->name);
+	rtf = snewn(rtfsize, char);
+	rtflen = sprintf(rtf, "{\\rtf1\\ansi\\deff0{\\fonttbl\\f0\\fmodern %s;}\\f0\\fs%d",
+			 font->name, font->height*2);
+
+	/*
+	 * Add colour palette
+	 * {\colortbl ;\red255\green0\blue0;\red0\green0\blue128;}
+	 */
+
+	/*
+	 * First - Determine all colours in use
+	 *    o  Foregound and background colours share the same palette
+	 */
+	if (attr) {
+	    memset(palette, 0, sizeof(palette));
+	    for (i = 0; i < (len-1); i++) {
+		fgcolour = ((attr[i] & ATTR_FGMASK) >> ATTR_FGSHIFT);
+		bgcolour = ((attr[i] & ATTR_BGMASK) >> ATTR_BGSHIFT);
+
+		if (attr[i] & ATTR_REVERSE) {
+		    int tmpcolour = fgcolour;	/* Swap foreground and background */
+		    fgcolour = bgcolour;
+		    bgcolour = tmpcolour;
+		}
+
+		if (bold_colours && (attr[i] & ATTR_BOLD)) {
+		    if (fgcolour  <   8)	/* ANSI colours */
+			fgcolour +=   8;
+		    else if (fgcolour >= 256)	/* Default colours */
+			fgcolour ++;
+		}
+
+		if (attr[i] & ATTR_BLINK) {
+		    if (bgcolour  <   8)	/* ANSI colours */
+			bgcolour +=   8;
+    		    else if (bgcolour >= 256)	/* Default colours */
+			bgcolour ++;
+		}
+
+		palette[fgcolour]++;
+		palette[bgcolour]++;
+	    }
+
+	    /*
+	     * Next - Create a reduced palette
+	     */
+	    numcolours = 0;
+	    for (i = 0; i < NALLCOLOURS; i++) {
+		if (palette[i] != 0)
+		    palette[i]  = ++numcolours;
+	    }
+
+	    /*
+	     * Finally - Write the colour table
+	     */
+	    rtf = sresize(rtf, rtfsize + (numcolours * 25), char);
+	    strcat(rtf, "{\\colortbl ;");
+	    rtflen = strlen(rtf);
+
+	    for (i = 0; i < NALLCOLOURS; i++) {
+		if (palette[i] != 0) {
+		    rtflen += sprintf(&rtf[rtflen], "\\red%d\\green%d\\blue%d;", defpal[i].rgbtRed, defpal[i].rgbtGreen, defpal[i].rgbtBlue);
+		}
+	    }
+	    strcpy(&rtf[rtflen], "}");
+	    rtflen ++;
+	}
+
+	/*
+	 * We want to construct a piece of RTF that specifies the
+	 * same Unicode text. To do this we will read back in
+	 * parallel from the Unicode data in `udata' and the
+	 * non-Unicode data in `tdata'. For each character in
+	 * `tdata' which becomes the right thing in `udata' when
+	 * looked up in `unitab', we just copy straight over from
+	 * tdata. For each one that doesn't, we must WCToMB it
+	 * individually and produce a \u escape sequence.
+	 * 
+	 * It would probably be more robust to just bite the bullet
+	 * and WCToMB each individual Unicode character one by one,
+	 * then MBToWC each one back to see if it was an accurate
+	 * translation; but that strikes me as a horrifying number
+	 * of Windows API calls so I want to see if this faster way
+	 * will work. If it screws up badly we can always revert to
+	 * the simple and slow way.
+	 */
+	while (tindex < len2 && uindex < len &&
+	       tdata[tindex] && udata[uindex]) {
+	    if (tindex + 1 < len2 &&
+		tdata[tindex] == '\r' &&
+		tdata[tindex+1] == '\n') {
+		tindex++;
+		uindex++;
+            }
+
+            /*
+             * Set text attributes
+             */
+            if (attr) {
+                if (rtfsize < rtflen + 64) {
+		    rtfsize = rtflen + 512;
+		    rtf = sresize(rtf, rtfsize, char);
+                }
+
+                /*
+                 * Determine foreground and background colours
+                 */
+                fgcolour = ((attr[tindex] & ATTR_FGMASK) >> ATTR_FGSHIFT);
+                bgcolour = ((attr[tindex] & ATTR_BGMASK) >> ATTR_BGSHIFT);
+
+		if (attr[tindex] & ATTR_REVERSE) {
+		    int tmpcolour = fgcolour;	    /* Swap foreground and background */
+		    fgcolour = bgcolour;
+		    bgcolour = tmpcolour;
+		}
+
+		if (bold_colours && (attr[tindex] & ATTR_BOLD)) {
+		    if (fgcolour  <   8)	    /* ANSI colours */
+			fgcolour +=   8;
+		    else if (fgcolour >= 256)	    /* Default colours */
+			fgcolour ++;
+                }
+
+		if (attr[tindex] & ATTR_BLINK) {
+		    if (bgcolour  <   8)	    /* ANSI colours */
+			bgcolour +=   8;
+		    else if (bgcolour >= 256)	    /* Default colours */
+			bgcolour ++;
+                }
+
+                /*
+                 * Collect other attributes
+                 */
+		if (bold_font_mode != BOLD_NONE)
+		    attrBold  = attr[tindex] & ATTR_BOLD;
+		else
+		    attrBold  = 0;
+                
+		attrUnder = attr[tindex] & ATTR_UNDER;
+
+                /*
+                 * Reverse video
+		 *   o  If video isn't reversed, ignore colour attributes for default foregound
+	         *	or background.
+		 *   o  Special case where bolded text is displayed using the default foregound
+		 *      and background colours - force to bolded RTF.
+                 */
+		if (!(attr[tindex] & ATTR_REVERSE)) {
+		    if (bgcolour >= 256)	    /* Default color */
+			bgcolour  = -1;		    /* No coloring */
+
+		    if (fgcolour >= 256) {	    /* Default colour */
+			if (bold_colours && (fgcolour & 1) && bgcolour == -1)
+			    attrBold = ATTR_BOLD;   /* Emphasize text with bold attribute */
+
+			fgcolour  = -1;		    /* No coloring */
+		    }
+		}
+
+                /*
+                 * Write RTF text attributes
+                 */
+		if (lastfgcolour != fgcolour) {
+                    lastfgcolour  = fgcolour;
+		    rtflen       += sprintf(&rtf[rtflen], "\\cf%d ", (fgcolour >= 0) ? palette[fgcolour] : 0);
+                }
+
+                if (lastbgcolour != bgcolour) {
+                    lastbgcolour  = bgcolour;
+                    rtflen       += sprintf(&rtf[rtflen], "\\highlight%d ", (bgcolour >= 0) ? palette[bgcolour] : 0);
+                }
+
+		if (lastAttrBold != attrBold) {
+		    lastAttrBold  = attrBold;
+		    rtflen       += sprintf(&rtf[rtflen], "%s", attrBold ? "\\b " : "\\b0 ");
+		}
+
+                if (lastAttrUnder != attrUnder) {
+                    lastAttrUnder  = attrUnder;
+                    rtflen        += sprintf(&rtf[rtflen], "%s", attrUnder ? "\\ul " : "\\ulnone ");
+                }
+	    }
+
+	    if (unitab[tdata[tindex]] == udata[uindex]) {
+		multilen = 1;
+		before[0] = '\0';
+		after[0] = '\0';
+		blen = alen = 0;
+	    } else {
+		multilen = WideCharToMultiByte(CP_ACP, 0, unitab+uindex, 1,
+					       NULL, 0, NULL, NULL);
+		if (multilen != 1) {
+		    blen = sprintf(before, "{\\uc%d\\u%d", multilen,
+				   udata[uindex]);
+		    alen = 1; strcpy(after, "}");
+		} else {
+		    blen = sprintf(before, "\\u%d", udata[uindex]);
+		    alen = 0; after[0] = '\0';
+		}
+	    }
+	    assert(tindex + multilen <= len2);
+	    totallen = blen + alen;
+	    for (i = 0; i < multilen; i++) {
+		if (tdata[tindex+i] == '\\' ||
+		    tdata[tindex+i] == '{' ||
+		    tdata[tindex+i] == '}')
+		    totallen += 2;
+		else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A)
+		    totallen += 6;     /* \par\r\n */
+		else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20)
+		    totallen += 4;
+		else
+		    totallen++;
+	    }
+
+	    if (rtfsize < rtflen + totallen + 3) {
+		rtfsize = rtflen + totallen + 512;
+		rtf = sresize(rtf, rtfsize, char);
+	    }
+
+	    strcpy(rtf + rtflen, before); rtflen += blen;
+	    for (i = 0; i < multilen; i++) {
+		if (tdata[tindex+i] == '\\' ||
+		    tdata[tindex+i] == '{' ||
+		    tdata[tindex+i] == '}') {
+		    rtf[rtflen++] = '\\';
+		    rtf[rtflen++] = tdata[tindex+i];
+		} else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A) {
+		    rtflen += sprintf(rtf+rtflen, "\\par\r\n");
+		} else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20) {
+		    rtflen += sprintf(rtf+rtflen, "\\'%02x", tdata[tindex+i]);
+		} else {
+		    rtf[rtflen++] = tdata[tindex+i];
+		}
+	    }
+	    strcpy(rtf + rtflen, after); rtflen += alen;
+
+	    tindex += multilen;
+	    uindex++;
+	}
+
+        rtf[rtflen++] = '}';	       /* Terminate RTF stream */
+        rtf[rtflen++] = '\0';
+        rtf[rtflen++] = '\0';
+
+	clipdata3 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, rtflen);
+	if (clipdata3 && (lock3 = GlobalLock(clipdata3)) != NULL) {
+	    memcpy(lock3, rtf, rtflen);
+	    GlobalUnlock(clipdata3);
+	}
+	sfree(rtf);
+    } else
+	clipdata3 = NULL;
+
+    GlobalUnlock(clipdata);
+    GlobalUnlock(clipdata2);
+
+    if (!must_deselect)
+	SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
+
+    if (OpenClipboard(hwnd)) {
+	EmptyClipboard();
+	SetClipboardData(CF_UNICODETEXT, clipdata);
+	SetClipboardData(CF_TEXT, clipdata2);
+	if (clipdata3)
+	    SetClipboardData(RegisterClipboardFormat(CF_RTF), clipdata3);
+	CloseClipboard();
+    } else {
+	GlobalFree(clipdata);
+	GlobalFree(clipdata2);
+    }
+
+    if (!must_deselect)
+	SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
+}
+
+static DWORD WINAPI clipboard_read_threadfunc(void *param)
+{
+    HWND hwnd = (HWND)param;
+    HGLOBAL clipdata;
+
+    if (OpenClipboard(NULL)) {
+	if ((clipdata = GetClipboardData(CF_UNICODETEXT))) {
+	    SendMessage(hwnd, WM_GOT_CLIPDATA, (WPARAM)1, (LPARAM)clipdata);
+	} else if ((clipdata = GetClipboardData(CF_TEXT))) {
+	    SendMessage(hwnd, WM_GOT_CLIPDATA, (WPARAM)0, (LPARAM)clipdata);
+	}
+	CloseClipboard();
+    }
+
+    return 0;
+}
+
+static int process_clipdata(HGLOBAL clipdata, int unicode)
+{
+    sfree(clipboard_contents);
+    clipboard_contents = NULL;
+    clipboard_length = 0;
+
+    if (unicode) {
+	wchar_t *p = GlobalLock(clipdata);
+	wchar_t *p2;
+
+	if (p) {
+	    /* Unwilling to rely on Windows having wcslen() */
+	    for (p2 = p; *p2; p2++);
+	    clipboard_length = p2 - p;
+	    clipboard_contents = snewn(clipboard_length + 1, wchar_t);
+	    memcpy(clipboard_contents, p, clipboard_length * sizeof(wchar_t));
+	    clipboard_contents[clipboard_length] = L'\0';
+	    return TRUE;
+	}
+    } else {
+	char *s = GlobalLock(clipdata);
+	int i;
+
+	if (s) {
+	    i = MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, 0, 0);
+	    clipboard_contents = snewn(i, wchar_t);
+	    MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1,
+				clipboard_contents, i);
+	    clipboard_length = i - 1;
+	    clipboard_contents[clipboard_length] = L'\0';
+	    return TRUE;
+	}
+    }
+
+    return FALSE;
+}
+
+void request_paste(void *frontend)
+{
+    /*
+     * I always thought pasting was synchronous in Windows; the
+     * clipboard access functions certainly _look_ synchronous,
+     * unlike the X ones. But in fact it seems that in some
+     * situations the contents of the clipboard might not be
+     * immediately available, and the clipboard-reading functions
+     * may block. This leads to trouble if the application
+     * delivering the clipboard data has to get hold of it by -
+     * for example - talking over a network connection which is
+     * forwarded through this very PuTTY.
+     *
+     * Hence, we spawn a subthread to read the clipboard, and do
+     * our paste when it's finished. The thread will send a
+     * message back to our main window when it terminates, and
+     * that tells us it's OK to paste.
+     */
+    DWORD in_threadid; /* required for Win9x */
+    CreateThread(NULL, 0, clipboard_read_threadfunc,
+		 hwnd, 0, &in_threadid);
+}
+
+void get_clip(void *frontend, wchar_t **p, int *len)
+{
+    if (p) {
+	*p = clipboard_contents;
+	*len = clipboard_length;
+    }
+}
+
+#if 0
+/*
+ * Move `lines' lines from position `from' to position `to' in the
+ * window.
+ */
+void optimised_move(void *frontend, int to, int from, int lines)
+{
+    RECT r;
+    int min, max;
+
+    min = (to < from ? to : from);
+    max = to + from - min;
+
+    r.left = offset_width;
+    r.right = offset_width + term->cols * font_width;
+    r.top = offset_height + min * font_height;
+    r.bottom = offset_height + (max + lines) * font_height;
+    ScrollWindow(hwnd, 0, (to - from) * font_height, &r, &r);
+}
+#endif
+
+/*
+ * Print a message box and perform a fatal exit.
+ */
+void fatalbox(char *fmt, ...)
+{
+    va_list ap;
+    char *stuff, morestuff[100];
+
+    va_start(ap, fmt);
+    stuff = dupvprintf(fmt, ap);
+    va_end(ap);
+    sprintf(morestuff, "%.70s Fatal Error", appname);
+    MessageBox(hwnd, stuff, morestuff, MB_ICONERROR | MB_OK);
+    sfree(stuff);
+    cleanup_exit(1);
+}
+
+/*
+ * Print a modal (Really Bad) message box and perform a fatal exit.
+ */
+void modalfatalbox(char *fmt, ...)
+{
+    va_list ap;
+    char *stuff, morestuff[100];
+
+    va_start(ap, fmt);
+    stuff = dupvprintf(fmt, ap);
+    va_end(ap);
+    sprintf(morestuff, "%.70s Fatal Error", appname);
+    MessageBox(hwnd, stuff, morestuff,
+	       MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
+    sfree(stuff);
+    cleanup_exit(1);
+}
+
+/*
+ * Print a message box and don't close the connection.
+ */
+void nonfatal(char *fmt, ...)
+{
+    va_list ap;
+    char *stuff, morestuff[100];
+
+    va_start(ap, fmt);
+    stuff = dupvprintf(fmt, ap);
+    va_end(ap);
+    sprintf(morestuff, "%.70s Error", appname);
+    MessageBox(hwnd, stuff, morestuff, MB_ICONERROR | MB_OK);
+    sfree(stuff);
+}
+
+static BOOL flash_window_ex(DWORD dwFlags, UINT uCount, DWORD dwTimeout)
+{
+    if (p_FlashWindowEx) {
+	FLASHWINFO fi;
+	fi.cbSize = sizeof(fi);
+	fi.hwnd = hwnd;
+	fi.dwFlags = dwFlags;
+	fi.uCount = uCount;
+	fi.dwTimeout = dwTimeout;
+	return (*p_FlashWindowEx)(&fi);
+    }
+    else
+	return FALSE; /* shrug */
+}
+
+static void flash_window(int mode);
+static long next_flash;
+static int flashing = 0;
+
+/*
+ * Timer for platforms where we must maintain window flashing manually
+ * (e.g., Win95).
+ */
+static void flash_window_timer(void *ctx, unsigned long now)
+{
+    if (flashing && now == next_flash) {
+	flash_window(1);
+    }
+}
+
+/*
+ * Manage window caption / taskbar flashing, if enabled.
+ * 0 = stop, 1 = maintain, 2 = start
+ */
+static void flash_window(int mode)
+{
+    int beep_ind = conf_get_int(conf, CONF_beep_ind);
+    if ((mode == 0) || (beep_ind == B_IND_DISABLED)) {
+	/* stop */
+	if (flashing) {
+	    flashing = 0;
+	    if (p_FlashWindowEx)
+		flash_window_ex(FLASHW_STOP, 0, 0);
+	    else
+		FlashWindow(hwnd, FALSE);
+	}
+
+    } else if (mode == 2) {
+	/* start */
+	if (!flashing) {
+	    flashing = 1;
+	    if (p_FlashWindowEx) {
+		/* For so-called "steady" mode, we use uCount=2, which
+		 * seems to be the traditional number of flashes used
+		 * by user notifications (e.g., by Explorer).
+		 * uCount=0 appears to enable continuous flashing, per
+		 * "flashing" mode, although I haven't seen this
+		 * documented. */
+		flash_window_ex(FLASHW_ALL | FLASHW_TIMER,
+				(beep_ind == B_IND_FLASH ? 0 : 2),
+				0 /* system cursor blink rate */);
+		/* No need to schedule timer */
+	    } else {
+		FlashWindow(hwnd, TRUE);
+		next_flash = schedule_timer(450, flash_window_timer, hwnd);
+	    }
+	}
+
+    } else if ((mode == 1) && (beep_ind == B_IND_FLASH)) {
+	/* maintain */
+	if (flashing && !p_FlashWindowEx) {
+	    FlashWindow(hwnd, TRUE);	/* toggle */
+	    next_flash = schedule_timer(450, flash_window_timer, hwnd);
+	}
+    }
+}
+
+/*
+ * Beep.
+ */
+void do_beep(void *frontend, int mode)
+{
+    if (mode == BELL_DEFAULT) {
+	/*
+	 * For MessageBeep style bells, we want to be careful of
+	 * timing, because they don't have the nice property of
+	 * PlaySound bells that each one cancels the previous
+	 * active one. So we limit the rate to one per 50ms or so.
+	 */
+	static long lastbeep = 0;
+	long beepdiff;
+
+	beepdiff = GetTickCount() - lastbeep;
+	if (beepdiff >= 0 && beepdiff < 50)
+	    return;
+	MessageBeep(MB_OK);
+	/*
+	 * The above MessageBeep call takes time, so we record the
+	 * time _after_ it finishes rather than before it starts.
+	 */
+	lastbeep = GetTickCount();
+    } else if (mode == BELL_WAVEFILE) {
+	Filename *bell_wavefile = conf_get_filename(conf, CONF_bell_wavefile);
+	if (!PlaySound(bell_wavefile->path, NULL,
+		       SND_ASYNC | SND_FILENAME)) {
+	    char buf[sizeof(bell_wavefile->path) + 80];
+	    char otherbuf[100];
+	    sprintf(buf, "Unable to play sound file\n%s\n"
+		    "Using default sound instead", bell_wavefile->path);
+	    sprintf(otherbuf, "%.70s Sound Error", appname);
+	    MessageBox(hwnd, buf, otherbuf,
+		       MB_OK | MB_ICONEXCLAMATION);
+	    conf_set_int(conf, CONF_beep, BELL_DEFAULT);
+	}
+    } else if (mode == BELL_PCSPEAKER) {
+	static long lastbeep = 0;
+	long beepdiff;
+
+	beepdiff = GetTickCount() - lastbeep;
+	if (beepdiff >= 0 && beepdiff < 50)
+	    return;
+
+	/*
+	 * We must beep in different ways depending on whether this
+	 * is a 95-series or NT-series OS.
+	 */
+	if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT)
+	    Beep(800, 100);
+	else
+	    MessageBeep(-1);
+	lastbeep = GetTickCount();
+    }
+    /* Otherwise, either visual bell or disabled; do nothing here */
+    if (!term->has_focus) {
+	flash_window(2);	       /* start */
+    }
+}
+
+/*
+ * Minimise or restore the window in response to a server-side
+ * request.
+ */
+void set_iconic(void *frontend, int iconic)
+{
+    if (IsIconic(hwnd)) {
+	if (!iconic)
+	    ShowWindow(hwnd, SW_RESTORE);
+    } else {
+	if (iconic)
+	    ShowWindow(hwnd, SW_MINIMIZE);
+    }
+}
+
+/*
+ * Move the window in response to a server-side request.
+ */
+void move_window(void *frontend, int x, int y)
+{
+    int resize_action = conf_get_int(conf, CONF_resize_action);
+    if (resize_action == RESIZE_DISABLED || 
+	resize_action == RESIZE_FONT ||
+	IsZoomed(hwnd))
+       return;
+
+    SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
+}
+
+/*
+ * Move the window to the top or bottom of the z-order in response
+ * to a server-side request.
+ */
+void set_zorder(void *frontend, int top)
+{
+    if (conf_get_int(conf, CONF_alwaysontop))
+	return;			       /* ignore */
+    SetWindowPos(hwnd, top ? HWND_TOP : HWND_BOTTOM, 0, 0, 0, 0,
+		 SWP_NOMOVE | SWP_NOSIZE);
+}
+
+/*
+ * Refresh the window in response to a server-side request.
+ */
+void refresh_window(void *frontend)
+{
+    InvalidateRect(hwnd, NULL, TRUE);
+}
+
+/*
+ * Maximise or restore the window in response to a server-side
+ * request.
+ */
+void set_zoomed(void *frontend, int zoomed)
+{
+    if (IsZoomed(hwnd)) {
+        if (!zoomed)
+	    ShowWindow(hwnd, SW_RESTORE);
+    } else {
+	if (zoomed)
+	    ShowWindow(hwnd, SW_MAXIMIZE);
+    }
+}
+
+/*
+ * Report whether the window is iconic, for terminal reports.
+ */
+int is_iconic(void *frontend)
+{
+    return IsIconic(hwnd);
+}
+
+/*
+ * Report the window's position, for terminal reports.
+ */
+void get_window_pos(void *frontend, int *x, int *y)
+{
+    RECT r;
+    GetWindowRect(hwnd, &r);
+    *x = r.left;
+    *y = r.top;
+}
+
+/*
+ * Report the window's pixel size, for terminal reports.
+ */
+void get_window_pixels(void *frontend, int *x, int *y)
+{
+    RECT r;
+    GetWindowRect(hwnd, &r);
+    *x = r.right - r.left;
+    *y = r.bottom - r.top;
+}
+
+/*
+ * Return the window or icon title.
+ */
+char *get_window_title(void *frontend, int icon)
+{
+    return icon ? icon_name : window_name;
+}
+
+/*
+ * See if we're in full-screen mode.
+ */
+static int is_full_screen()
+{
+    if (!IsZoomed(hwnd))
+	return FALSE;
+    if (GetWindowLongPtr(hwnd, GWL_STYLE) & WS_CAPTION)
+	return FALSE;
+    return TRUE;
+}
+
+/* Get the rect/size of a full screen window using the nearest available
+ * monitor in multimon systems; default to something sensible if only
+ * one monitor is present. */
+static int get_fullscreen_rect(RECT * ss)
+{
+#if defined(MONITOR_DEFAULTTONEAREST) && !defined(NO_MULTIMON)
+	HMONITOR mon;
+	MONITORINFO mi;
+	mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
+	mi.cbSize = sizeof(mi);
+	GetMonitorInfo(mon, &mi);
+
+	/* structure copy */
+	*ss = mi.rcMonitor;
+	return TRUE;
+#else
+/* could also use code like this:
+	ss->left = ss->top = 0;
+	ss->right = GetSystemMetrics(SM_CXSCREEN);
+	ss->bottom = GetSystemMetrics(SM_CYSCREEN);
+*/ 
+	return GetClientRect(GetDesktopWindow(), ss);
+#endif
+}
+
+
+/*
+ * Go full-screen. This should only be called when we are already
+ * maximised.
+ */
+static void make_full_screen()
+{
+    DWORD style;
+	RECT ss;
+
+    assert(IsZoomed(hwnd));
+
+	if (is_full_screen())
+		return;
+	
+    /* Remove the window furniture. */
+    style = GetWindowLongPtr(hwnd, GWL_STYLE);
+    style &= ~(WS_CAPTION | WS_BORDER | WS_THICKFRAME);
+    if (conf_get_int(conf, CONF_scrollbar_in_fullscreen))
+	style |= WS_VSCROLL;
+    else
+	style &= ~WS_VSCROLL;
+    SetWindowLongPtr(hwnd, GWL_STYLE, style);
+
+    /* Resize ourselves to exactly cover the nearest monitor. */
+	get_fullscreen_rect(&ss);
+    SetWindowPos(hwnd, HWND_TOP, ss.left, ss.top,
+			ss.right - ss.left,
+			ss.bottom - ss.top,
+			SWP_FRAMECHANGED);
+
+    /* We may have changed size as a result */
+
+    reset_window(0);
+
+    /* Tick the menu item in the System and context menus. */
+    {
+	int i;
+	for (i = 0; i < lenof(popup_menus); i++)
+	    CheckMenuItem(popup_menus[i].menu, IDM_FULLSCREEN, MF_CHECKED);
+    }
+}
+
+/*
+ * Clear the full-screen attributes.
+ */
+static void clear_full_screen()
+{
+    DWORD oldstyle, style;
+
+    /* Reinstate the window furniture. */
+    style = oldstyle = GetWindowLongPtr(hwnd, GWL_STYLE);
+    style |= WS_CAPTION | WS_BORDER;
+    if (conf_get_int(conf, CONF_resize_action) == RESIZE_DISABLED)
+        style &= ~WS_THICKFRAME;
+    else
+        style |= WS_THICKFRAME;
+    if (conf_get_int(conf, CONF_scrollbar))
+	style |= WS_VSCROLL;
+    else
+	style &= ~WS_VSCROLL;
+    if (style != oldstyle) {
+	SetWindowLongPtr(hwnd, GWL_STYLE, style);
+	SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
+		     SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
+		     SWP_FRAMECHANGED);
+    }
+
+    /* Untick the menu item in the System and context menus. */
+    {
+	int i;
+	for (i = 0; i < lenof(popup_menus); i++)
+	    CheckMenuItem(popup_menus[i].menu, IDM_FULLSCREEN, MF_UNCHECKED);
+    }
+}
+
+/*
+ * Toggle full-screen mode.
+ */
+static void flip_full_screen()
+{
+    if (is_full_screen()) {
+	ShowWindow(hwnd, SW_RESTORE);
+    } else if (IsZoomed(hwnd)) {
+	make_full_screen();
+    } else {
+	SendMessage(hwnd, WM_FULLSCR_ON_MAX, 0, 0);
+	ShowWindow(hwnd, SW_MAXIMIZE);
+    }
+}
+
+void frontend_keypress(void *handle)
+{
+    /*
+     * Keypress termination in non-Close-On-Exit mode is not
+     * currently supported in PuTTY proper, because the window
+     * always has a perfectly good Close button anyway. So we do
+     * nothing here.
+     */
+    return;
+}
+
+int from_backend(void *frontend, int is_stderr, const char *data, int len)
+{
+    return term_data(term, is_stderr, data, len);
+}
+
+int from_backend_untrusted(void *frontend, const char *data, int len)
+{
+    return term_data_untrusted(term, data, len);
+}
+
+int from_backend_eof(void *frontend)
+{
+    return TRUE;   /* do respond to incoming EOF with outgoing */
+}
+
+int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
+{
+    int ret;
+    ret = cmdline_get_passwd_input(p, in, inlen);
+    if (ret == -1)
+	ret = term_get_userpass_input(term, p, in, inlen);
+    return ret;
+}
+
+void agent_schedule_callback(void (*callback)(void *, void *, int),
+			     void *callback_ctx, void *data, int len)
+{
+    struct agent_callback *c = snew(struct agent_callback);
+    c->callback = callback;
+    c->callback_ctx = callback_ctx;
+    c->data = data;
+    c->len = len;
+    PostMessage(hwnd, WM_AGENT_CALLBACK, 0, (LPARAM)c);
+}
diff -uNr putty-0.65/windows/winshare.c putty-url-0.65/windows/winshare.c
--- putty-0.65/windows/winshare.c	2015-07-25 12:22:48.000000000 +0200
+++ putty-url-0.65/windows/winshare.c	2015-09-12 15:35:59.000000000 +0200
@@ -32,6 +32,17 @@
 #endif
 #endif
 
+/*
+ * PuTTY-url: These are not (yet) available in wincrypt.h from mingw as of now
+ * (2015-06-23, w32api 3.17.2).
+ */
+#ifndef CRYPTPROTECTMEMORY_BLOCK_SIZE
+#define CRYPTPROTECTMEMORY_BLOCK_SIZE 16
+#endif
+#ifndef CRYPTPROTECTMEMORY_CROSS_PROCESS
+#define CRYPTPROTECTMEMORY_CROSS_PROCESS 1
+#endif
+
 #define CONNSHARE_PIPE_PREFIX "\\\\.\\pipe\\putty-connshare"
 #define CONNSHARE_MUTEX_PREFIX "Local\\putty-connshare-mutex"
 
