]> asedeno.scripts.mit.edu Git - PuTTY.git/blobdiff - windows/window.c
first pass
[PuTTY.git] / windows / window.c
index 15e77d7236138dee621717ef6f5c8367ab765b3b..004eb4f827eeccaf6ebc3bf1da8bb5f499c94318 100644 (file)
 #include <limits.h>
 #include <assert.h>
 
+#ifdef __WINE__
+#define NO_MULTIMON                    /* winelib doesn't have this */
+#endif
+
 #ifndef NO_MULTIMON
 #define COMPILE_MULTIMON_STUBS
 #endif
@@ -19,6 +23,7 @@
 #include "terminal.h"
 #include "storage.h"
 #include "win_res.h"
+#include "winsecur.h"
 
 #ifndef NO_MULTIMON
 #include <multimon.h>
 #define WHEEL_DELTA 120
 #endif
 
+/* VK_PACKET, used to send Unicode characters in WM_KEYDOWNs */
+#ifndef VK_PACKET
+#define VK_PACKET 0xE7
+#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,
@@ -88,7 +98,7 @@ 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_flashwindow(void);
+static void init_winfuncs(void);
 
 static int is_full_screen(void);
 static void make_full_screen(void);
@@ -104,10 +114,6 @@ static int offset_width, offset_height;
 static int was_zoomed = 0;
 static int prev_rows, prev_cols;
   
-static int pending_netevent = 0;
-static WPARAM pend_netevent_wParam = 0;
-static LPARAM pend_netevent_lParam = 0;
-static void enact_pending_netevent(void);
 static void flash_window(int mode);
 static void sys_cursor_update(void);
 static int get_fullscreen_rect(RECT * ss);
@@ -121,7 +127,7 @@ static Backend *back;
 static void *backhandle;
 
 static struct unicode_data ucsdata;
-static int must_close_session, session_closed;
+static int session_closed;
 static int reconfiguring = FALSE;
 
 static const struct telnet_special *specials = NULL;
@@ -140,6 +146,12 @@ static struct {
 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);
@@ -213,9 +225,17 @@ static UINT wm_mousewheel = WM_MOUSEWHEEL;
     (((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)
+void frontend_echoedit_update(void *frontend, int echo, int edit)
+{
+}
+
+int frontend_is_utf8(void *frontend)
 {
+    return ucsdata.line_codepage == CP_UTF8;
 }
 
 char *get_ttymode(void *frontend, const char *mode)
@@ -289,11 +309,10 @@ static void start_backend(void)
        DeleteMenu(popup_menus[i].menu, IDM_RESTART, MF_BYCOMMAND);
     }
 
-    must_close_session = FALSE;
     session_closed = FALSE;
 }
 
-static void close_session(void)
+static void close_session(void *ignored_context)
 {
     char morestuff[100];
     int i;
@@ -324,24 +343,16 @@ static void close_session(void)
        InsertMenu(popup_menus[i].menu, IDM_DUPSESS, MF_BYCOMMAND | MF_ENABLED,
                   IDM_RESTART, "&Restart Session");
     }
-
-    /*
-     * Unset the 'must_close_session' flag, or else we'll come
-     * straight back here the next time we go round the main message
-     * loop - which, worse still, will be immediately (without
-     * blocking) because we've just triggered a WM_SETTEXT by the
-     * window title change above.
-     */
-    must_close_session = FALSE;
 }
 
 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
 {
-    WNDCLASS wndclass;
     MSG msg;
     HRESULT hr;
     int guess_width, guess_height;
 
+    dll_hijacking_protection();
+
     hinst = inst;
     hwnd = NULL;
     flags = FLAG_VERBOSE | FLAG_INTERACTIVE;
@@ -350,6 +361,11 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
 
     InitCommonControls();
 
+    /* Set Explicit App User Model Id so that jump lists don't cause
+       PuTTY to hang on to removable media. */
+
+    set_explicit_app_user_model_id();
+
     /* Ensure a Maximize setting in Explorer doesn't maximise the
      * config box. */
     defuse_showwindow();
@@ -375,7 +391,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
 
     init_help();
 
-    init_flashwindow();
+    init_winfuncs();
 
     conf = conf_new();
 
@@ -420,11 +436,20 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
         * 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.
+        * the internal-use-only &sharedmemoryhandle, plus the &R
+        * prefix for -restrict-acl, all of which are used by PuTTYs
+        * auto-launching each other via System-menu options.
         */
        while (*p && isspace(*p))
            p++;
+        if (*p == '&' && p[1] == 'R' &&
+            (!p[2] || p[2] == '@' || p[2] == '&')) {
+            /* &R restrict-acl prefix */
+            restrict_process_acl();
+            restricted_acl = TRUE;
+            p += 2;
+        }
+
        if (*p == '@') {
             /*
              * An initial @ means that the whole of the rest of the
@@ -462,7 +487,11 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
                cleanup_exit(0);
            }
            allow_launch = TRUE;
-       } else {
+       } else if (!*p) {
+            /* Do-nothing case for an empty command line - or rather,
+             * for a command line that's empty _after_ we strip off
+             * the &R prefix. */
+        } else {
            /*
             * Otherwise, break up the command line and deal with
             * it sensibly.
@@ -484,39 +513,22 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
                    i++;               /* skip next argument */
                } else if (ret == 1) {
                    continue;          /* nothing further needs doing */
-               } else if (!strcmp(p, "-cleanup") ||
-                          !strcmp(p, "-cleanup-during-uninstall")) {
+               } else if (!strcmp(p, "-cleanup")) {
                    /*
                     * `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);
-                   }
+                   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) {
@@ -553,8 +565,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
                            q += 2;
                        conf_set_int(conf, CONF_protocol, PROT_TELNET);
                        p = q;
-                       while (*p && *p != ':' && *p != '/')
-                           p++;
+                        p += host_strcspn(p, ":/");
                        c = *p;
                        if (*p)
                            *p++ = '\0';
@@ -619,15 +630,15 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
 
             /*
              * Trim a colon suffix off the hostname if it's there. In
-             * order to protect IPv6 address literals against this
-             * treatment, we do not do this if there's _more_ than one
-             * colon.
+             * order to protect unbracketed IPv6 address literals
+             * against this treatment, we do not do this if there's
+             * _more_ than one colon.
              */
             {
-                char *c = strchr(host, ':');
+                char *c = host_strchr(host, ':');
  
                 if (c) {
-                    char *d = strchr(c+1, ':');
+                    char *d = host_strchr(c+1, ':');
                     if (!d)
                         *c = '\0';
                 }
@@ -651,6 +662,8 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
     }
 
     if (!prev) {
+        WNDCLASSW wndclass;
+
        wndclass.style = 0;
        wndclass.lpfnWndProc = WndProc;
        wndclass.cbClsExtra = 0;
@@ -660,9 +673,9 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
        wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
        wndclass.hbrBackground = NULL;
        wndclass.lpszMenuName = NULL;
-       wndclass.lpszClassName = appname;
+       wndclass.lpszClassName = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname);
 
-       RegisterClass(&wndclass);
+       RegisterClassW(&wndclass);
     }
 
     memset(&ucsdata, 0, sizeof(ucsdata));
@@ -696,6 +709,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
     {
        int winmode = WS_OVERLAPPEDWINDOW | WS_VSCROLL;
        int exwinmode = 0;
+        wchar_t *uappname = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname);
        if (!conf_get_int(conf, CONF_scrollbar))
            winmode &= ~(WS_VSCROLL);
        if (conf_get_int(conf, CONF_resize_action) == RESIZE_DISABLED)
@@ -704,10 +718,11 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
            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);
+       hwnd = CreateWindowExW(exwinmode, uappname, uappname,
+                               winmode, CW_USEDEFAULT, CW_USEDEFAULT,
+                               guess_width, guess_height,
+                               NULL, NULL, inst, NULL);
+        sfree(uappname);
     }
 
     /*
@@ -809,7 +824,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
            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,
+           AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT_PTR) savedsess_menu,
                       "Sa&ved Sessions");
            AppendMenu(m, MF_ENABLED, IDM_RECONF, "Chan&ge Settings...");
            AppendMenu(m, MF_SEPARATOR, 0, 0);
@@ -829,6 +844,10 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
        }
     }
 
+    if (restricted_acl) {
+       logevent(NULL, "Running with restricted process ACL");
+    }
+
     start_backend();
 
     /*
@@ -855,44 +874,76 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
     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, INFINITE,
-                                     QS_ALLINPUT);
+       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);
-           if (must_close_session)
-               close_session();
        } else
            sfree(handles);
 
-        run_toplevel_callbacks();
-
-       while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
+       while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) {
            if (msg.message == WM_QUIT)
                goto finished;         /* two-level break */
 
            if (!(IsWindow(logbox) && IsDialogMessage(logbox, &msg)))
-               DispatchMessage(&msg);
-           /* Send the paste buffer if there's anything to send */
-           term_paste(term);
+               DispatchMessageW(&msg);
 
-           if (must_close_session)
-               close_session();
-
-            run_toplevel_callbacks();
+            /*
+             * 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;
        }
 
-       /* The messages seem unreliable; especially if we're being tricky */
-       term_set_focus(term, GetForegroundWindow() == hwnd);
-
-       if (pending_netevent)
-           enact_pending_netevent();
-
-       net_pending_errors();
+        run_toplevel_callbacks();
     }
 
     finished:
@@ -1004,7 +1055,7 @@ void update_specials_menu(void *frontend)
                saved_menu = new_menu; /* XXX lame stacking */
                new_menu = CreatePopupMenu();
                AppendMenu(saved_menu, MF_POPUP | MF_ENABLED,
-                          (UINT) new_menu, specials[i].name);
+                          (UINT_PTR) new_menu, specials[i].name);
                break;
              case TS_EXITMENU:
                nesting--;
@@ -1029,13 +1080,14 @@ void update_specials_menu(void *frontend)
     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, (UINT_PTR)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");
+                      (UINT_PTR) new_menu, "S&pecial Command");
            InsertMenu(popup_menus[j].menu, IDM_SHOWLOG,
                       MF_BYCOMMAND | MF_SEPARATOR, IDM_SPECIALSEP, 0);
        }
@@ -1100,7 +1152,7 @@ void set_raw_mouse_mode(void *frontend, int activate)
 /*
  * Print a message box and close the connection.
  */
-void connection_fatal(void *frontend, char *fmt, ...)
+void connection_fatal(void *frontend, const char *fmt, ...)
 {
     va_list ap;
     char *stuff, morestuff[100];
@@ -1115,14 +1167,14 @@ void connection_fatal(void *frontend, char *fmt, ...)
     if (conf_get_int(conf, CONF_close_on_exit) == FORCE_ON)
        PostQuitMessage(1);
     else {
-       must_close_session = TRUE;
+       queue_toplevel_callback(close_session, NULL);
     }
 }
 
 /*
  * Report an error at the command-line parsing stage.
  */
-void cmdline_error(char *fmt, ...)
+void cmdline_error(const char *fmt, ...)
 {
     va_list ap;
     char *stuff, morestuff[100];
@@ -1139,19 +1191,11 @@ void cmdline_error(char *fmt, ...)
 /*
  * Actually do the job requested by a WM_NETEVENT
  */
-static void enact_pending_netevent(void)
+static void wm_netevent_callback(void *vctx)
 {
-    static int reentering = 0;
-    extern int select_result(WPARAM, LPARAM);
-
-    if (reentering)
-       return;                        /* don't unpend the pending */
-
-    pending_netevent = FALSE;
-
-    reentering = 1;
-    select_result(pend_netevent_wParam, pend_netevent_lParam);
-    reentering = 0;
+    struct wm_netevent_params *params = (struct wm_netevent_params *)vctx;
+    select_result(params->wParam, params->lParam);
+    sfree(vctx);
 }
 
 /*
@@ -1490,7 +1534,8 @@ static void init_fonts(int pick_width, int pick_height)
        if (cset == OEM_CHARSET)
            ucsdata.font_codepage = GetOEMCP();
        else
-           if (TranslateCharsetInfo ((DWORD *) cset, &info, TCI_SRCCHARSET))
+           if (TranslateCharsetInfo ((DWORD *)(ULONG_PTR)cset,
+                                      &info, TCI_SRCCHARSET))
                ucsdata.font_codepage = info.ciACP;
        else
            ucsdata.font_codepage = -1;
@@ -2012,7 +2057,7 @@ void notify_remote_exit(void *fe)
            (close_on_exit == AUTO && exitcode != INT_MAX)) {
            PostQuitMessage(0);
        } else {
-           must_close_session = TRUE;
+            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
@@ -2108,13 +2153,18 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
          case IDM_SAVEDSESS:
            {
                char b[2048];
-               char c[30], *cl;
-               int freecl = FALSE;
+               char *cl;
+                const char *argprefix;
                BOOL inherit_handles;
                STARTUPINFO si;
                PROCESS_INFORMATION pi;
                HANDLE filemap = NULL;
 
+                if (restricted_acl)
+                    argprefix = "&R";
+                else
+                    argprefix = "";
+
                if (wParam == IDM_DUPSESS) {
                    /*
                     * Allocate a file-mapping memory chunk for the
@@ -2141,20 +2191,21 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
                        }
                    }
                    inherit_handles = TRUE;
-                   sprintf(c, "putty &%p:%u", filemap, (unsigned)size);
-                   cl = c;
+                   cl = dupprintf("putty %s&%p:%u", argprefix,
+                                   filemap, (unsigned)size);
                } 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);
+                       const char *session = sesslist.sessions[sessno];
+                       cl = dupprintf("putty %s@%s", argprefix, session);
                        inherit_handles = FALSE;
-                       freecl = TRUE;
                    } else
                        break;
                } else /* IDM_NEWSESS */ {
-                   cl = NULL;
+                    cl = dupprintf("putty%s%s",
+                                   *argprefix ? " " : "",
+                                   argprefix);
                    inherit_handles = FALSE;
                }
 
@@ -2173,8 +2224,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
 
                if (filemap)
                    CloseHandle(filemap);
-               if (freecl)
-                   sfree(cl);
+                sfree(cl);
            }
            break;
          case IDM_RESTART:
@@ -2238,9 +2288,10 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
                 * Flush the line discipline's edit buffer in the
                 * case where local editing has just been disabled.
                 */
-               ldisc_configure(ldisc, conf);
-               if (ldisc)
-                   ldisc_send(ldisc, NULL, 0, 0);
+               if (ldisc) {
+                    ldisc_configure(ldisc, conf);
+                   ldisc_echoedit_update(ldisc);
+                }
                if (pal)
                    DeleteObject(pal);
                logpal = NULL;
@@ -2366,7 +2417,6 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
 
                InvalidateRect(hwnd, NULL, TRUE);
                reset_window(init_lvl);
-               net_pending_errors();
 
                conf_free(prev_conf);
            }
@@ -2383,7 +2433,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
          case IDM_RESET:
            term_pwron(term, TRUE);
            if (ldisc)
-               ldisc_send(ldisc, NULL, 0, 0);
+               ldisc_echoedit_update(ldisc);
            break;
          case IDM_ABOUT:
            showabout(hwnd);
@@ -2429,7 +2479,6 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
                    break;
                if (back)
                    back->special(backhandle, specials[i].code);
-               net_pending_errors();
            }
        }
        break;
@@ -2709,20 +2758,20 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
        }
        return 0;
       case WM_NETEVENT:
-       /* Notice we can get multiple netevents, FD_READ, FD_WRITE etc
-        * but the only one that's likely to try to overload us is FD_READ.
-        * This means buffering just one is fine.
-        */
-       if (pending_netevent)
-           enact_pending_netevent();
-
-       pending_netevent = TRUE;
-       pend_netevent_wParam = wParam;
-       pend_netevent_lParam = lParam;
-       if (WSAGETSELECTEVENT(lParam) != FD_READ)
-           enact_pending_netevent();
-
-       net_pending_errors();
+        {
+            /*
+             * 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);
@@ -3066,7 +3115,8 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
            unsigned char buf[20];
            int len;
 
-           if (wParam == VK_PROCESSKEY) { /* IME PROCESS key */
+           if (wParam == VK_PROCESSKEY || /* IME PROCESS key */
+                wParam == VK_PACKET) {     /* 'this key is a Unicode char' */
                if (message == WM_KEYDOWN) {
                    MSG m;
                    m.hwnd = hwnd;
@@ -3078,17 +3128,9 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
            } else {
                len = TranslateKey(message, wParam, lParam, buf);
                if (len == -1)
-                   return DefWindowProc(hwnd, message, wParam, lParam);
+                   return DefWindowProcW(hwnd, message, wParam, lParam);
 
                if (len != 0) {
-                   /*
-                    * Interrupt an ongoing paste. I'm not sure
-                    * this is sensible, but for the moment it's
-                    * preferable to having to faff about buffering
-                    * things.
-                    */
-                   term_nopaste(term);
-
                    /*
                     * We need not bother about stdin backlogs
                     * here, because in GUI PuTTY we can't do
@@ -3099,12 +3141,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
                     */
                    term_seen_key_event(term);
                    if (ldisc)
-                       ldisc_send(ldisc, buf, len, 1);
+                       ldisc_send(ldisc, (char *)buf, len, 1);
                    show_mouseptr(0);
                }
            }
        }
-       net_pending_errors();
        return 0;
       case WM_INPUTLANGCHANGE:
        /* wParam == Font number */
@@ -3168,7 +3209,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
 
       case WM_IME_CHAR:
        if (wParam & 0xFF00) {
-           unsigned char buf[2];
+           char buf[2];
 
            buf[1] = wParam;
            buf[0] = wParam >> 8;
@@ -3191,10 +3232,21 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
         * 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);
+            static wchar_t pending_surrogate = 0;
+           wchar_t c = wParam;
+
+            if (IS_HIGH_SURROGATE(c)) {
+                pending_surrogate = c;
+            } else if (IS_SURROGATE_PAIR(pending_surrogate, c)) {
+                wchar_t pair[2];
+                pair[0] = pending_surrogate;
+                pair[1] = c;
+                term_seen_key_event(term);
+                luni_send(ldisc, pair, 2, 1);
+            } else if (!IS_SURROGATE(c)) {
+                term_seen_key_event(term);
+                luni_send(ldisc, &c, 1, 1);
+            }
        }
        return 0;
       case WM_SYSCOLORCHANGE:
@@ -3263,10 +3315,6 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
                                   TO_CHR_X(p.x),
                                   TO_CHR_Y(p.y), shift_pressed,
                                   control_pressed, is_alt_pressed());
-                       term_mouse(term, b, translate_button(b),
-                                  MA_RELEASE, 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 */
@@ -3283,7 +3331,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
      * Any messages we don't process completely above are passed through to
      * DefWindowProc() for default processing.
      */
-    return DefWindowProc(hwnd, message, wParam, lParam);
+    return DefWindowProcW(hwnd, message, wParam, lParam);
 }
 
 /*
@@ -3898,6 +3946,17 @@ int char_width(Context ctx, int uc) {
     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,
@@ -4190,7 +4249,7 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
                *p++ = "hH\010\010"[shift_state & 3];
                return p - output;
              case VK_NUMPAD5:
-               *p++ = shift_state ? '.' : '.';
+               *p++ = '.';
                return p - output;
              case VK_NUMPAD6:
                *p++ = "lL\014\014"[shift_state & 3];
@@ -4555,7 +4614,7 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
                break;
            }
            if (xkey) {
-               p += format_arrow_key(p, term, xkey, shift_state);
+               p += format_arrow_key((char *)p, term, xkey, shift_state);
                return p - output;
            }
        }
@@ -4591,9 +4650,9 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
        /* 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) {
-           r = ToUnicodeEx(wParam, scan, keystate, keys_unicode,
-                            lenof(keys_unicode), 0, kbd_layout);
+       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'.
@@ -4639,13 +4698,6 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
        if (r > 0) {
            WCHAR keybuf;
 
-           /*
-            * Interrupt an ongoing paste. I'm not sure this is
-            * sensible, but for the moment it's preferable to
-            * having to faff about buffering things.
-            */
-           term_nopaste(term);
-
            p = output;
            for (i = 0; i < r; i++) {
                wchar_t wch = keys_unicode[i];
@@ -5337,7 +5389,7 @@ void optimised_move(void *frontend, int to, int from, int lines)
 /*
  * Print a message box and perform a fatal exit.
  */
-void fatalbox(char *fmt, ...)
+void fatalbox(const char *fmt, ...)
 {
     va_list ap;
     char *stuff, morestuff[100];
@@ -5354,7 +5406,7 @@ void fatalbox(char *fmt, ...)
 /*
  * Print a modal (Really Bad) message box and perform a fatal exit.
  */
-void modalfatalbox(char *fmt, ...)
+void modalfatalbox(const char *fmt, ...)
 {
     va_list ap;
     char *stuff, morestuff[100];
@@ -5372,7 +5424,7 @@ void modalfatalbox(char *fmt, ...)
 /*
  * Print a message box and don't close the connection.
  */
-void nonfatal(char *fmt, ...)
+void nonfatal(const char *fmt, ...)
 {
     va_list ap;
     char *stuff, morestuff[100];
@@ -5385,14 +5437,6 @@ void nonfatal(char *fmt, ...)
     sfree(stuff);
 }
 
-DECL_WINDOWS_FUNCTION(static, BOOL, FlashWindowEx, (PFLASHWINFO));
-
-static void init_flashwindow(void)
-{
-    HMODULE user32_module = load_system32_dll("user32.dll");
-    GET_WINDOWS_FUNCTION(user32_module, FlashWindowEx);
-}
-
 static BOOL flash_window_ex(DWORD dwFlags, UINT uCount, DWORD dwTimeout)
 {
     if (p_FlashWindowEx) {
@@ -5787,7 +5831,7 @@ 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 get_userpass_input(prompts_t *p, const unsigned char *in, int inlen)
 {
     int ret;
     ret = cmdline_get_passwd_input(p, in, inlen);