Add a configuration option for TCP keepalives (SO_KEEPALIVE), default off.
[u/mdw/putty] / window.c
index c9d8df5..e720ed3 100644 (file)
--- a/window.c
+++ b/window.c
@@ -1,23 +1,3 @@
-#include <windows.h>
-#include <imm.h>
-#include <commctrl.h>
-#include <richedit.h>
-#include <mmsystem.h>
-#ifndef AUTO_WINSOCK
-#ifdef WINSOCK_TWO
-#include <winsock2.h>
-#else
-#include <winsock.h>
-#endif
-#endif
-
-#ifndef NO_MULTIMON
-#if WINVER < 0x0500
-#define COMPILE_MULTIMON_STUBS
-#include <multimon.h>
-#endif
-#endif
-
 #include <stdio.h>
 #include <stdlib.h>
 #include <ctype.h>
 #define PUTTY_DO_GLOBALS              /* actually _define_ globals */
 #include "putty.h"
 #include "terminal.h"
-#include "winstuff.h"
 #include "storage.h"
 #include "win_res.h"
 
+#ifndef NO_MULTIMON
+#if WINVER < 0x0500
+#define COMPILE_MULTIMON_STUBS
+#include <multimon.h>
+#endif
+#endif
+
+#include <imm.h>
+#include <commctrl.h>
+#include <richedit.h>
+#include <mmsystem.h>
+
 #define IDM_SHOWLOG   0x0010
 #define IDM_NEWSESS   0x0020
 #define IDM_DUPSESS   0x0030
@@ -42,6 +33,7 @@
 #define IDM_SAVEDSESS 0x0160
 #define IDM_COPYALL   0x0170
 #define IDM_FULLSCREEN 0x0180
+#define IDM_PASTE     0x0190
 
 #define IDM_SESSLGP   0x0250          /* log type printable */
 #define IDM_SESSLGA   0x0260          /* log type all chars */
@@ -75,6 +67,7 @@ static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
                        unsigned char *output);
 static void cfgtopalette(void);
+static void systopalette(void);
 static void init_palette(void);
 static void init_fonts(int, int);
 static void another_font(int);
@@ -117,7 +110,12 @@ static struct unicode_data ucsdata;
 static int session_closed;
 
 static const struct telnet_special *specials;
-static int specials_menu_position;
+
+static struct {
+    HMENU menu;
+    int specials_submenu_pos;
+} popup_menus[2];
+enum { SYSMENU, CTXMENU };
 
 Config cfg;                           /* exported to windlg.c */
 
@@ -177,10 +175,6 @@ static char *window_name, *icon_name;
 
 static int compose_state = 0;
 
-static int wsa_started = 0;
-
-static OSVERSIONINFO osVersion;
-
 static UINT wm_mousewheel = WM_MOUSEWHEEL;
 
 /* Dummy routine, only required in plink. */
@@ -190,8 +184,6 @@ void ldisc_update(void *frontend, int echo, int edit)
 
 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
 {
-    WORD winsock_ver;
-    WSADATA wsadata;
     WNDCLASS wndclass;
     MSG msg;
     int guess_width, guess_height;
@@ -199,20 +191,6 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
     hinst = inst;
     flags = FLAG_VERBOSE | FLAG_INTERACTIVE;
 
-    winsock_ver = MAKEWORD(1, 1);
-    if (WSAStartup(winsock_ver, &wsadata)) {
-       MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
-                  MB_OK | MB_ICONEXCLAMATION);
-       return 1;
-    }
-    if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
-       MessageBox(NULL, "WinSock version is incompatible with 1.1",
-                  "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
-       WSACleanup();
-       return 1;
-    }
-    wsa_started = 1;
-    /* WISHLIST: maybe allow config tweaking even if winsock not present? */
     sk_init();
 
     InitCommonControls();
@@ -221,16 +199,13 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
      * config box. */
     defuse_showwindow();
 
+    if (!init_winver())
     {
-       ZeroMemory(&osVersion, sizeof(osVersion));
-       osVersion.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
-       if (!GetVersionEx ( (OSVERSIONINFO *) &osVersion)) {
-           char *str = dupprintf("%s Fatal Error", appname);
-            MessageBox(NULL, "Windows refuses to report a version",
-                       str, MB_OK | MB_ICONEXCLAMATION);
-           sfree(str);
-           return 1;
-        }
+       char *str = dupprintf("%s Fatal Error", appname);
+       MessageBox(NULL, "Windows refuses to report a version",
+                  str, MB_OK | MB_ICONEXCLAMATION);
+       sfree(str);
+       return 1;
     }
 
     /*
@@ -309,8 +284,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
            p[i] = '\0';
            do_defaults(p + 1, &cfg);
            if (!*cfg.host && !do_config()) {
-               WSACleanup();
-               return 0;
+               cleanup_exit(0);
            }
        } else if (*p == '&') {
            /*
@@ -328,8 +302,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
                UnmapViewOfFile(cp);
                CloseHandle(filemap);
            } else if (!do_config()) {
-               WSACleanup();
-               return 0;
+               cleanup_exit(0);
            }
        } else {
            /*
@@ -434,8 +407,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
        cmdline_run_saved(&cfg);
 
        if (!*cfg.host && !do_config()) {
-           WSACleanup();
-           return 0;
+           cleanup_exit(0);
        }
 
        /*
@@ -497,8 +469,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
            MessageBox(NULL, "Unsupported protocol number found",
                       str, MB_OK | MB_ICONEXCLAMATION);
            sfree(str);
-           WSACleanup();
-           return 1;
+           cleanup_exit(1);
        }
     }
 
@@ -508,8 +479,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
        MessageBox(NULL, "Invalid Port Number",
                   str, MB_OK | MB_ICONEXCLAMATION);
        sfree(str);
-       WSACleanup();
-       return 1;
+       cleanup_exit(1);
     }
 
     if (!prev) {
@@ -641,7 +611,8 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
        char *realhost;
 
        error = back->init(NULL, &backhandle, &cfg,
-                          cfg.host, cfg.port, &realhost, cfg.tcp_nodelay);
+                          cfg.host, cfg.port, &realhost, cfg.tcp_nodelay,
+                          cfg.tcp_keepalives);
        back->provide_logctx(backhandle, logctx);
        if (error) {
            char *str = dupprintf("%s Error", appname);
@@ -686,17 +657,14 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
      * Set up the session-control options on the system menu.
      */
     {
-       HMENU m = GetSystemMenu(hwnd, FALSE);
-       HMENU s;
-       int i;
+       HMENU s, m;
+       int i, j;
        char *str;
 
-       AppendMenu(m, MF_SEPARATOR, 0, 0);
-       specials_menu_position = GetMenuItemCount(m);
-       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");
+       popup_menus[SYSMENU].menu = GetSystemMenu(hwnd, FALSE);
+       popup_menus[CTXMENU].menu = CreatePopupMenu();
+       AppendMenu(popup_menus[CTXMENU].menu, MF_ENABLED, IDM_PASTE, "&Paste");
+
        s = CreateMenu();
        get_sesslist(&sesslist, TRUE);
        for (i = 1;
@@ -704,21 +672,32 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
             i++)
            AppendMenu(s, MF_ENABLED, IDM_SAVED_MIN + (16 * i),
                       sesslist.sessions[i]);
-       AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) s, "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, (cfg.resize_action == RESIZE_DISABLED) ?
-                  MF_GRAYED : MF_ENABLED, IDM_FULLSCREEN, "&Full Screen");
-       AppendMenu(m, MF_SEPARATOR, 0, 0);
-        if (help_path)
-            AppendMenu(m, MF_ENABLED, IDM_HELP, "&Help");
-       str = dupprintf("&About %s", appname);
-       AppendMenu(m, MF_ENABLED, IDM_ABOUT, str);
-       sfree(str);
+
+       for (j = 0; j < lenof(popup_menus); j++) {
+           m = popup_menus[j].menu;
+
+           AppendMenu(m, MF_SEPARATOR, 0, 0);
+           popup_menus[j].specials_submenu_pos = GetMenuItemCount(m);
+           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) s, "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, (cfg.resize_action == RESIZE_DISABLED) ?
+                      MF_GRAYED : MF_ENABLED, IDM_FULLSCREEN, "&Full Screen");
+           AppendMenu(m, MF_SEPARATOR, 0, 0);
+           if (help_path)
+               AppendMenu(m, MF_ENABLED, IDM_HELP, "&Help");
+           str = dupprintf("&About %s", appname);
+           AppendMenu(m, MF_ENABLED, IDM_ABOUT, str);
+           sfree(str);
+       }
     }
 
     update_specials_menu(NULL);
@@ -844,8 +823,6 @@ void cleanup_exit(int code)
     if (pal)
        DeleteObject(pal);
     sk_cleanup();
-    if (wsa_started)
-       WSACleanup();
 
     if (cfg.protocol == PROT_SSH) {
        random_save_seed();
@@ -872,8 +849,8 @@ char *do_select(SOCKET skt, int startup)
     }
     if (!hwnd)
        return "do_select(): internal error (hwnd==NULL)";
-    if (WSAAsyncSelect(skt, hwnd, msg, events) == SOCKET_ERROR) {
-       switch (WSAGetLastError()) {
+    if (p_WSAAsyncSelect(skt, hwnd, msg, events) == SOCKET_ERROR) {
+       switch (p_WSAGetLastError()) {
          case WSAENETDOWN:
            return "Network is down";
          default:
@@ -890,7 +867,7 @@ void update_specials_menu(void *frontend)
 {
     HMENU m = GetSystemMenu(hwnd, FALSE);
     int menu_already_exists = (specials != NULL);
-    int i;
+    int i, j;
 
     specials = back->get_specials(backhandle);
     if (specials) {
@@ -903,14 +880,20 @@ void update_specials_menu(void *frontend)
            else
                AppendMenu(p, MF_SEPARATOR, 0, 0);
        }
-       if (menu_already_exists)
-           DeleteMenu(m, specials_menu_position, MF_BYPOSITION);
-       else
-           InsertMenu(m, specials_menu_position,
-                      MF_BYPOSITION | MF_SEPARATOR, 0, 0);
-       InsertMenu(m, specials_menu_position,
-                  MF_BYPOSITION | MF_POPUP | MF_ENABLED,
-                  (UINT) p, "Special Command");
+       for (j = 0; j < lenof(popup_menus); j++) {
+           if (menu_already_exists)
+               DeleteMenu(popup_menus[j].menu,
+                          popup_menus[j].specials_submenu_pos,
+                          MF_BYPOSITION);
+           else
+               InsertMenu(popup_menus[j].menu,
+                          popup_menus[j].specials_submenu_pos,
+                          MF_BYPOSITION | MF_SEPARATOR, 0, 0);
+           InsertMenu(popup_menus[j].menu,
+                      popup_menus[j].specials_submenu_pos,
+                      MF_BYPOSITION | MF_POPUP | MF_ENABLED,
+                      (UINT) p, "Special Command");
+       }
     }
 }
 
@@ -1020,6 +1003,38 @@ static void cfgtopalette(void)
        defpal[i].rgbtGreen = cfg.colours[w][1];
        defpal[i].rgbtBlue = cfg.colours[w][2];
     }
+
+    /* Override with system colours if appropriate */
+    if (cfg.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,     16, 17 }, /* Default Foreground */
+       { COLOR_WINDOW,         18, 19 }, /* Default Background */
+       { COLOR_HIGHLIGHTTEXT,  20, 21 }, /* Cursor Text */
+       { COLOR_HIGHLIGHT,      22, 23 }, /* 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);
+    }
 }
 
 /*
@@ -1067,6 +1082,36 @@ static void init_palette(void)
 }
 
 /*
+ * 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)
+{
+
+    GCP_RESULTSW gcpr;
+    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 = classbuffer;
+    gcpr.nGlyphs = cbCount;
+
+    GetCharacterPlacementW(hdc, lpString, cbCount, 0, &gcpr,
+                          FLI_MASK | GCP_CLASSIN);
+
+    ExtTextOut(hdc, x, y, ETO_GLYPH_INDEX | ETO_CLIPPED | ETO_OPAQUE, lprc,
+              buffer, cbCount, lpDx);
+}
+
+/*
  * Initialise all the fonts we will need initially. There may be as many as
  * three or as few as one.  The other (poentially) twentyone fonts are done
  * if/when they are needed.
@@ -1625,9 +1670,9 @@ static Mouse_Button translate_button(Mouse_Button button)
     if (button == MBT_LEFT)
        return MBT_SELECT;
     if (button == MBT_MIDDLE)
-       return cfg.mouse_is_xterm ? MBT_PASTE : MBT_EXTEND;
+       return cfg.mouse_is_xterm == 1 ? MBT_PASTE : MBT_EXTEND;
     if (button == MBT_RIGHT)
-       return cfg.mouse_is_xterm ? MBT_EXTEND : MBT_PASTE;
+       return cfg.mouse_is_xterm == 1 ? MBT_EXTEND : MBT_PASTE;
     return 0;                         /* shouldn't happen */
 }
 
@@ -1717,6 +1762,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
        show_mouseptr(1);
        PostQuitMessage(0);
        return 0;
+      case WM_COMMAND:
       case WM_SYSCOMMAND:
        switch (wParam & ~0xF) {       /* low 4 bits reserved to Windows */
          case IDM_SHOWLOG:
@@ -1938,6 +1984,9 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
          case IDM_COPYALL:
            term_copyall(term);
            break;
+         case IDM_PASTE:
+           term_do_paste(term);
+           break;
          case IDM_CLRSB:
            term_clrsb(term);
            break;
@@ -2009,6 +2058,18 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
       case WM_LBUTTONUP:
       case WM_MBUTTONUP:
       case WM_RBUTTONUP:
+       if (message == WM_RBUTTONDOWN &&
+           ((wParam & MK_CONTROL) || (cfg.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;
 
@@ -2047,11 +2108,42 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
             * window, we put up the System menu instead of doing
             * selection.
             */
-           if (is_full_screen() && press && button == MBT_LEFT &&
-               X_POS(lParam) == 0 && Y_POS(lParam) == 0) {
-               SendMessage(hwnd, WM_SYSCOMMAND, SC_MOUSEMENU, 0);
-               return 0;
+           {
+               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;
+                       }
+                       CloseHandle(mon);
+                   }
+               }
+#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)),
@@ -2093,7 +2185,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
       case WM_NCMOUSEMOVE:
        show_mouseptr(1);
        noise_ultralight(lParam);
-       return 0;
+       break;
       case WM_IGNORE_CLIP:
        ignore_clip = wParam;          /* don't panic on DESTROYCLIPBOARD */
        break;
@@ -2928,9 +3020,13 @@ void do_text(Context ctx, int x, int y, char *text, int len,
        for (i = 0; i < len; i++)
            wbuf[i] = (WCHAR) ((attr & CSET_MASK) + (text[i] & CHAR_MASK));
 
-       ExtTextOutW(hdc, x,
+       /* print Glyphs as they are, without Windows' Shaping*/
+       exact_textout(hdc, x, y - font_height * (lattr == LATTR_BOT) + text_adjust,
+                     &line_box, wbuf, len, IpDx);
+/*     ExtTextOutW(hdc, x,
                    y - font_height * (lattr == LATTR_BOT) + text_adjust,
                    ETO_CLIPPED | ETO_OPAQUE, &line_box, wbuf, len, IpDx);
+ */
 
        /* And the shadow bold hack. */
        if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
@@ -3109,6 +3205,7 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
 
     HKL kbd_layout = GetKeyboardLayout(0);
 
+    /* keys is for ToAsciiEx. There's some ick here, see below. */
     static WORD keys[3];
     static int compose_char = 0;
     static WPARAM compose_key = 0;
@@ -3537,6 +3634,10 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
            *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';
@@ -3796,7 +3897,27 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
            keystate[VK_CAPITAL] = 0;
        }
 
-       r = ToAsciiEx(wParam, scan, keystate, keys, 0, kbd_layout);
+       /* 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) {
+           /* 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. */
+           BYTE keybs[3];
+           int i;
+           r = ToAsciiEx(wParam, scan, keystate, (LPWORD)keybs, 0, kbd_layout);
+           for (i=0; i<3; i++) keys[i] = keybs[i];
+       } else {
+           r = ToAsciiEx(wParam, scan, keystate, keys, 0, kbd_layout);
+       }
 #ifdef SHOW_TOASCII_RESULT
        if (r == 1 && !key_down) {
            if (alt_sum) {
@@ -3872,9 +3993,10 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
                            ldisc_send(ldisc, &ch, 1, 1);
                        }
                        alt_sum = 0;
-                   } else
+                   } else {
                        term_seen_key_event(term);
                        lpage_send(ldisc, kbd_codepage, &ch, 1, 1);
+                   }
                } else {
                    if(capsOn && ch < 0x80) {
                        WCHAR cbuf[2];
@@ -4397,15 +4519,32 @@ void beep(void *frontend, int mode)
     } else if (mode == BELL_WAVEFILE) {
        if (!PlaySound(cfg.bell_wavefile.path, NULL,
                       SND_ASYNC | SND_FILENAME)) {
-           char buf[sizeof(cfg.bell_wavefile) + 80];
+           char buf[sizeof(cfg.bell_wavefile.path) + 80];
            char otherbuf[100];
            sprintf(buf, "Unable to play sound file\n%s\n"
-                   "Using default sound instead", cfg.bell_wavefile);
+                   "Using default sound instead", cfg.bell_wavefile.path);
            sprintf(otherbuf, "%.70s Sound Error", appname);
            MessageBox(hwnd, buf, otherbuf,
                       MB_OK | MB_ICONEXCLAMATION);
            cfg.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) {
@@ -4517,7 +4656,7 @@ char *get_window_title(void *frontend, int icon)
 /*
  * See if we're in full-screen mode.
  */
-int is_full_screen()
+static int is_full_screen()
 {
     if (!IsZoomed(hwnd))
        return FALSE;
@@ -4531,7 +4670,7 @@ int is_full_screen()
  * one monitor is present. */
 static int get_fullscreen_rect(RECT * ss)
 {
-#ifdef MONITOR_DEFAULTTONEAREST
+#if defined(MONITOR_DEFAULTTONEAREST) && !defined(NO_MULTIMON)
        HMONITOR mon;
        MONITORINFO mi;
        mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
@@ -4556,7 +4695,7 @@ static int get_fullscreen_rect(RECT * ss)
  * Go full-screen. This should only be called when we are already
  * maximised.
  */
-void make_full_screen()
+static void make_full_screen()
 {
     DWORD style;
        RECT ss;
@@ -4590,7 +4729,7 @@ void make_full_screen()
 /*
  * Clear the full-screen attributes.
  */
-void clear_full_screen()
+static void clear_full_screen()
 {
     DWORD oldstyle, style;
 
@@ -4620,7 +4759,7 @@ void clear_full_screen()
 /*
  * Toggle full-screen mode.
  */
-void flip_full_screen()
+static void flip_full_screen()
 {
     if (is_full_screen()) {
        ShowWindow(hwnd, SW_RESTORE);