]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - window.c
pterm now uses the new uxsel module, so it's one step closer to
[PuTTY.git] / window.c
1 #include <windows.h>
2 #include <imm.h>
3 #include <commctrl.h>
4 #include <richedit.h>
5 #include <mmsystem.h>
6 #ifndef AUTO_WINSOCK
7 #ifdef WINSOCK_TWO
8 #include <winsock2.h>
9 #else
10 #include <winsock.h>
11 #endif
12 #endif
13
14 #ifndef NO_MULTIMON
15 #if WINVER < 0x0500
16 #define COMPILE_MULTIMON_STUBS
17 #include <multimon.h>
18 #endif
19 #endif
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <ctype.h>
24 #include <time.h>
25 #include <assert.h>
26
27 #define PUTTY_DO_GLOBALS               /* actually _define_ globals */
28 #include "putty.h"
29 #include "terminal.h"
30 #include "winstuff.h"
31 #include "storage.h"
32 #include "win_res.h"
33
34 #define IDM_SHOWLOG   0x0010
35 #define IDM_NEWSESS   0x0020
36 #define IDM_DUPSESS   0x0030
37 #define IDM_RECONF    0x0040
38 #define IDM_CLRSB     0x0050
39 #define IDM_RESET     0x0060
40 #define IDM_TEL_AYT   0x0070
41 #define IDM_TEL_BRK   0x0080
42 #define IDM_TEL_SYNCH 0x0090
43 #define IDM_TEL_EC    0x00a0
44 #define IDM_TEL_EL    0x00b0
45 #define IDM_TEL_GA    0x00c0
46 #define IDM_TEL_NOP   0x00d0
47 #define IDM_TEL_ABORT 0x00e0
48 #define IDM_TEL_AO    0x00f0
49 #define IDM_TEL_IP    0x0100
50 #define IDM_TEL_SUSP  0x0110
51 #define IDM_TEL_EOR   0x0120
52 #define IDM_TEL_EOF   0x0130
53 #define IDM_HELP      0x0140
54 #define IDM_ABOUT     0x0150
55 #define IDM_SAVEDSESS 0x0160
56 #define IDM_COPYALL   0x0170
57 #define IDM_FULLSCREEN  0x0180
58
59 #define IDM_SESSLGP   0x0250           /* log type printable */
60 #define IDM_SESSLGA   0x0260           /* log type all chars */
61 #define IDM_SESSLGE   0x0270           /* log end */
62 #define IDM_SAVED_MIN 0x1000
63 #define IDM_SAVED_MAX 0x2000
64
65 #define WM_IGNORE_CLIP (WM_XUSER + 2)
66 #define WM_FULLSCR_ON_MAX (WM_XUSER + 3)
67
68 /* Needed for Chinese support and apparently not always defined. */
69 #ifndef VK_PROCESSKEY
70 #define VK_PROCESSKEY 0xE5
71 #endif
72
73 /* Mouse wheel support. */
74 #ifndef WM_MOUSEWHEEL
75 #define WM_MOUSEWHEEL 0x020A           /* not defined in earlier SDKs */
76 #endif
77 #ifndef WHEEL_DELTA
78 #define WHEEL_DELTA 120
79 #endif
80
81 static Mouse_Button translate_button(Mouse_Button button);
82 static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
83 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
84                         unsigned char *output);
85 static void cfgtopalette(void);
86 static void init_palette(void);
87 static void init_fonts(int, int);
88 static void another_font(int);
89 static void deinit_fonts(void);
90 static void set_input_locale(HKL);
91
92 static int is_full_screen(void);
93 static void make_full_screen(void);
94 static void clear_full_screen(void);
95 static void flip_full_screen(void);
96
97 /* Window layout information */
98 static void reset_window(int);
99 static int extra_width, extra_height;
100 static int font_width, font_height, font_dualwidth;
101 static int offset_width, offset_height;
102 static int was_zoomed = 0;
103 static int prev_rows, prev_cols;
104   
105 static int pending_netevent = 0;
106 static WPARAM pend_netevent_wParam = 0;
107 static LPARAM pend_netevent_lParam = 0;
108 static void enact_pending_netevent(void);
109 static void flash_window(int mode);
110 static void sys_cursor_update(void);
111 static int is_shift_pressed(void);
112 static int get_fullscreen_rect(RECT * ss);
113
114 static time_t last_movement = 0;
115
116 static int caret_x = -1, caret_y = -1;
117
118 static int kbd_codepage;
119
120 static void *ldisc;
121 static Backend *back;
122 static void *backhandle;
123
124 static struct unicode_data ucsdata;
125 static int session_closed;
126
127 Config cfg;                            /* exported to windlg.c */
128
129 extern struct sesslist sesslist;       /* imported from windlg.c */
130
131 #define FONT_NORMAL 0
132 #define FONT_BOLD 1
133 #define FONT_UNDERLINE 2
134 #define FONT_BOLDUND 3
135 #define FONT_WIDE       0x04
136 #define FONT_HIGH       0x08
137 #define FONT_NARROW     0x10
138
139 #define FONT_OEM        0x20
140 #define FONT_OEMBOLD    0x21
141 #define FONT_OEMUND     0x22
142 #define FONT_OEMBOLDUND 0x23
143
144 #define FONT_MAXNO      0x2F
145 #define FONT_SHIFT      5
146 static HFONT fonts[FONT_MAXNO];
147 static LOGFONT lfont;
148 static int fontflag[FONT_MAXNO];
149 static enum {
150     BOLD_COLOURS, BOLD_SHADOW, BOLD_FONT
151 } bold_mode;
152 static enum {
153     UND_LINE, UND_FONT
154 } und_mode;
155 static int descent;
156
157 #define NCOLOURS 24
158 static COLORREF colours[NCOLOURS];
159 static HPALETTE pal;
160 static LPLOGPALETTE logpal;
161 static RGBTRIPLE defpal[NCOLOURS];
162
163 static HWND hwnd;
164
165 static HBITMAP caretbm;
166
167 static int dbltime, lasttime, lastact;
168 static Mouse_Button lastbtn;
169
170 /* this allows xterm-style mouse handling. */
171 static int send_raw_mouse = 0;
172 static int wheel_accumulator = 0;
173
174 static char *window_name, *icon_name;
175
176 static int compose_state = 0;
177
178 static int wsa_started = 0;
179
180 static OSVERSIONINFO osVersion;
181
182 static UINT wm_mousewheel = WM_MOUSEWHEEL;
183
184 /* Dummy routine, only required in plink. */
185 void ldisc_update(void *frontend, int echo, int edit)
186 {
187 }
188
189 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
190 {
191     static char appname[] = "PuTTY";
192     WORD winsock_ver;
193     WSADATA wsadata;
194     WNDCLASS wndclass;
195     MSG msg;
196     int guess_width, guess_height;
197
198     hinst = inst;
199     flags = FLAG_VERBOSE | FLAG_INTERACTIVE;
200
201     winsock_ver = MAKEWORD(1, 1);
202     if (WSAStartup(winsock_ver, &wsadata)) {
203         MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
204                    MB_OK | MB_ICONEXCLAMATION);
205         return 1;
206     }
207     if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
208         MessageBox(NULL, "WinSock version is incompatible with 1.1",
209                    "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
210         WSACleanup();
211         return 1;
212     }
213     wsa_started = 1;
214     /* WISHLIST: maybe allow config tweaking even if winsock not present? */
215     sk_init();
216
217     InitCommonControls();
218
219     /* Ensure a Maximize setting in Explorer doesn't maximise the
220      * config box. */
221     defuse_showwindow();
222
223     {
224         ZeroMemory(&osVersion, sizeof(osVersion));
225         osVersion.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
226         if (!GetVersionEx ( (OSVERSIONINFO *) &osVersion)) {
227             MessageBox(NULL, "Windows refuses to report a version",
228                        "PuTTY Fatal Error", MB_OK | MB_ICONEXCLAMATION);
229             return 1;
230         }
231     }
232
233     /*
234      * If we're running a version of Windows that doesn't support
235      * WM_MOUSEWHEEL, find out what message number we should be
236      * using instead.
237      */
238     if (osVersion.dwMajorVersion < 4 ||
239         (osVersion.dwMajorVersion == 4 && 
240          osVersion.dwPlatformId != VER_PLATFORM_WIN32_NT))
241         wm_mousewheel = RegisterWindowMessage("MSWHEEL_ROLLMSG");
242
243     /*
244      * See if we can find our Help file.
245      */
246     {
247         char b[2048], *p, *q, *r;
248         FILE *fp;
249         GetModuleFileName(NULL, b, sizeof(b) - 1);
250         r = b;
251         p = strrchr(b, '\\');
252         if (p && p >= r) r = p+1;
253         q = strrchr(b, ':');
254         if (q && q >= r) r = q+1;
255         strcpy(r, "putty.hlp");
256         if ( (fp = fopen(b, "r")) != NULL) {
257             help_path = dupstr(b);
258             fclose(fp);
259         } else
260             help_path = NULL;
261         strcpy(r, "putty.cnt");
262         if ( (fp = fopen(b, "r")) != NULL) {
263             help_has_contents = TRUE;
264             fclose(fp);
265         } else
266             help_has_contents = FALSE;
267     }
268
269     /*
270      * Process the command line.
271      */
272     {
273         char *p;
274         int got_host = 0;
275
276         default_protocol = be_default_protocol;
277         /* Find the appropriate default port. */
278         {
279             int i;
280             default_port = 0; /* illegal */
281             for (i = 0; backends[i].backend != NULL; i++)
282                 if (backends[i].protocol == default_protocol) {
283                     default_port = backends[i].backend->default_port;
284                     break;
285                 }
286         }
287         cfg.logtype = LGTYP_NONE;
288
289         do_defaults(NULL, &cfg);
290
291         p = cmdline;
292
293         /*
294          * Process a couple of command-line options which are more
295          * easily dealt with before the line is broken up into
296          * words. These are the soon-to-be-defunct @sessionname and
297          * the internal-use-only &sharedmemoryhandle, neither of
298          * which are combined with anything else.
299          */
300         while (*p && isspace(*p))
301             p++;
302         if (*p == '@') {
303             int i = strlen(p);
304             while (i > 1 && isspace(p[i - 1]))
305                 i--;
306             p[i] = '\0';
307             do_defaults(p + 1, &cfg);
308             if (!*cfg.host && !do_config()) {
309                 WSACleanup();
310                 return 0;
311             }
312         } else if (*p == '&') {
313             /*
314              * An initial & means we've been given a command line
315              * containing the hex value of a HANDLE for a file
316              * mapping object, which we must then extract as a
317              * config.
318              */
319             HANDLE filemap;
320             Config *cp;
321             if (sscanf(p + 1, "%p", &filemap) == 1 &&
322                 (cp = MapViewOfFile(filemap, FILE_MAP_READ,
323                                     0, 0, sizeof(Config))) != NULL) {
324                 cfg = *cp;
325                 UnmapViewOfFile(cp);
326                 CloseHandle(filemap);
327             } else if (!do_config()) {
328                 WSACleanup();
329                 return 0;
330             }
331         } else {
332             /*
333              * Otherwise, break up the command line and deal with
334              * it sensibly.
335              */
336             int argc, i;
337             char **argv;
338             
339             split_into_argv(cmdline, &argc, &argv, NULL);
340
341             for (i = 0; i < argc; i++) {
342                 char *p = argv[i];
343                 int ret;
344
345                 ret = cmdline_process_param(p, i+1<argc?argv[i+1]:NULL,
346                                             1, &cfg);
347                 if (ret == -2) {
348                     cmdline_error("option \"%s\" requires an argument", p);
349                 } else if (ret == 2) {
350                     i++;               /* skip next argument */
351                 } else if (ret == 1) {
352                     continue;          /* nothing further needs doing */
353                 } else if (!strcmp(p, "-cleanup")) {
354                     /*
355                      * `putty -cleanup'. Remove all registry
356                      * entries associated with PuTTY, and also find
357                      * and delete the random seed file.
358                      */
359                     if (MessageBox(NULL,
360                                    "This procedure will remove ALL Registry\n"
361                                    "entries associated with PuTTY, and will\n"
362                                    "also remove the PuTTY random seed file.\n"
363                                    "\n"
364                                    "THIS PROCESS WILL DESTROY YOUR SAVED\n"
365                                    "SESSIONS. Are you really sure you want\n"
366                                    "to continue?",
367                                    "PuTTY Warning",
368                                    MB_YESNO | MB_ICONWARNING) == IDYES) {
369                         cleanup_all();
370                     }
371                     exit(0);
372                 } else if (*p != '-') {
373                     char *q = p;
374                     if (got_host) {
375                         /*
376                          * If we already have a host name, treat
377                          * this argument as a port number. NB we
378                          * have to treat this as a saved -P
379                          * argument, so that it will be deferred
380                          * until it's a good moment to run it.
381                          */
382                         int ret = cmdline_process_param("-P", p, 1, &cfg);
383                         assert(ret == 2);
384                     } else if (!strncmp(q, "telnet:", 7)) {
385                         /*
386                          * If the hostname starts with "telnet:",
387                          * set the protocol to Telnet and process
388                          * the string as a Telnet URL.
389                          */
390                         char c;
391
392                         q += 7;
393                         if (q[0] == '/' && q[1] == '/')
394                             q += 2;
395                         cfg.protocol = PROT_TELNET;
396                         p = q;
397                         while (*p && *p != ':' && *p != '/')
398                             p++;
399                         c = *p;
400                         if (*p)
401                             *p++ = '\0';
402                         if (c == ':')
403                             cfg.port = atoi(p);
404                         else
405                             cfg.port = -1;
406                         strncpy(cfg.host, q, sizeof(cfg.host) - 1);
407                         cfg.host[sizeof(cfg.host) - 1] = '\0';
408                         got_host = 1;
409                     } else {
410                         /*
411                          * Otherwise, treat this argument as a host
412                          * name.
413                          */
414                         while (*p && !isspace(*p))
415                             p++;
416                         if (*p)
417                             *p++ = '\0';
418                         strncpy(cfg.host, q, sizeof(cfg.host) - 1);
419                         cfg.host[sizeof(cfg.host) - 1] = '\0';
420                         got_host = 1;
421                     }
422                 } else {
423                     cmdline_error("unknown option \"%s\"", p);
424                 }
425             }
426         }
427
428         cmdline_run_saved(&cfg);
429
430         if (!*cfg.host && !do_config()) {
431             WSACleanup();
432             return 0;
433         }
434
435         /*
436          * Trim leading whitespace off the hostname if it's there.
437          */
438         {
439             int space = strspn(cfg.host, " \t");
440             memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
441         }
442
443         /* See if host is of the form user@host */
444         if (cfg.host[0] != '\0') {
445             char *atsign = strchr(cfg.host, '@');
446             /* Make sure we're not overflowing the user field */
447             if (atsign) {
448                 if (atsign - cfg.host < sizeof cfg.username) {
449                     strncpy(cfg.username, cfg.host, atsign - cfg.host);
450                     cfg.username[atsign - cfg.host] = '\0';
451                 }
452                 memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
453             }
454         }
455
456         /*
457          * Trim a colon suffix off the hostname if it's there.
458          */
459         cfg.host[strcspn(cfg.host, ":")] = '\0';
460
461         /*
462          * Remove any remaining whitespace from the hostname.
463          */
464         {
465             int p1 = 0, p2 = 0;
466             while (cfg.host[p2] != '\0') {
467                 if (cfg.host[p2] != ' ' && cfg.host[p2] != '\t') {
468                     cfg.host[p1] = cfg.host[p2];
469                     p1++;
470                 }
471                 p2++;
472             }
473             cfg.host[p1] = '\0';
474         }
475     }
476
477     /*
478      * Select protocol. This is farmed out into a table in a
479      * separate file to enable an ssh-free variant.
480      */
481     {
482         int i;
483         back = NULL;
484         for (i = 0; backends[i].backend != NULL; i++)
485             if (backends[i].protocol == cfg.protocol) {
486                 back = backends[i].backend;
487                 break;
488             }
489         if (back == NULL) {
490             MessageBox(NULL, "Unsupported protocol number found",
491                        "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
492             WSACleanup();
493             return 1;
494         }
495     }
496
497     /* Check for invalid Port number (i.e. zero) */
498     if (cfg.port == 0) {
499         MessageBox(NULL, "Invalid Port Number",
500                    "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
501         WSACleanup();
502         return 1;
503     }
504
505     if (!prev) {
506         wndclass.style = 0;
507         wndclass.lpfnWndProc = WndProc;
508         wndclass.cbClsExtra = 0;
509         wndclass.cbWndExtra = 0;
510         wndclass.hInstance = inst;
511         wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
512         wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
513         wndclass.hbrBackground = NULL;
514         wndclass.lpszMenuName = NULL;
515         wndclass.lpszClassName = appname;
516
517         RegisterClass(&wndclass);
518     }
519
520     hwnd = NULL;
521
522     memset(&ucsdata, 0, sizeof(ucsdata));
523
524     term = term_init(&cfg, &ucsdata, NULL);
525     logctx = log_init(NULL, &cfg);
526     term_provide_logctx(term, logctx);
527
528     cfgtopalette();
529
530     /*
531      * Guess some defaults for the window size. This all gets
532      * updated later, so we don't really care too much. However, we
533      * do want the font width/height guesses to correspond to a
534      * large font rather than a small one...
535      */
536
537     font_width = 10;
538     font_height = 20;
539     extra_width = 25;
540     extra_height = 28;
541     term_size(term, cfg.height, cfg.width, cfg.savelines);
542     guess_width = extra_width + font_width * term->cols;
543     guess_height = extra_height + font_height * term->rows;
544     {
545         RECT r;
546                 get_fullscreen_rect(&r);
547         if (guess_width > r.right - r.left)
548             guess_width = r.right - r.left;
549         if (guess_height > r.bottom - r.top)
550             guess_height = r.bottom - r.top;
551     }
552
553     {
554         int winmode = WS_OVERLAPPEDWINDOW | WS_VSCROLL;
555         int exwinmode = 0;
556         if (!cfg.scrollbar)
557             winmode &= ~(WS_VSCROLL);
558         if (cfg.resize_action == RESIZE_DISABLED)
559             winmode &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
560         if (cfg.alwaysontop)
561             exwinmode |= WS_EX_TOPMOST;
562         if (cfg.sunken_edge)
563             exwinmode |= WS_EX_CLIENTEDGE;
564         hwnd = CreateWindowEx(exwinmode, appname, appname,
565                               winmode, CW_USEDEFAULT, CW_USEDEFAULT,
566                               guess_width, guess_height,
567                               NULL, NULL, inst, NULL);
568     }
569
570     /*
571      * Initialise the fonts, simultaneously correcting the guesses
572      * for font_{width,height}.
573      */
574     init_fonts(0,0);
575
576     /*
577      * Correct the guesses for extra_{width,height}.
578      */
579     {
580         RECT cr, wr;
581         GetWindowRect(hwnd, &wr);
582         GetClientRect(hwnd, &cr);
583         offset_width = offset_height = cfg.window_border;
584         extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
585         extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
586     }
587
588     /*
589      * Resize the window, now we know what size we _really_ want it
590      * to be.
591      */
592     guess_width = extra_width + font_width * term->cols;
593     guess_height = extra_height + font_height * term->rows;
594     SetWindowPos(hwnd, NULL, 0, 0, guess_width, guess_height,
595                  SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
596
597     /*
598      * Set up a caret bitmap, with no content.
599      */
600     {
601         char *bits;
602         int size = (font_width + 15) / 16 * 2 * font_height;
603         bits = snewn(size, char);
604         memset(bits, 0, size);
605         caretbm = CreateBitmap(font_width, font_height, 1, 1, bits);
606         sfree(bits);
607     }
608     CreateCaret(hwnd, caretbm, font_width, font_height);
609
610     /*
611      * Initialise the scroll bar.
612      */
613     {
614         SCROLLINFO si;
615
616         si.cbSize = sizeof(si);
617         si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
618         si.nMin = 0;
619         si.nMax = term->rows - 1;
620         si.nPage = term->rows;
621         si.nPos = 0;
622         SetScrollInfo(hwnd, SB_VERT, &si, FALSE);
623     }
624
625     /*
626      * Start up the telnet connection.
627      */
628     {
629         char *error;
630         char msg[1024], *title;
631         char *realhost;
632
633         error = back->init((void *)term, &backhandle, &cfg,
634                            cfg.host, cfg.port, &realhost, cfg.tcp_nodelay);
635         back->provide_logctx(backhandle, logctx);
636         if (error) {
637             sprintf(msg, "Unable to open connection to\n"
638                     "%.800s\n" "%s", cfg.host, error);
639             MessageBox(NULL, msg, "PuTTY Error", MB_ICONERROR | MB_OK);
640             return 0;
641         }
642         window_name = icon_name = NULL;
643         if (*cfg.wintitle) {
644             title = cfg.wintitle;
645         } else {
646             sprintf(msg, "%s - PuTTY", realhost);
647             title = msg;
648         }
649         sfree(realhost);
650         set_title(NULL, title);
651         set_icon(NULL, title);
652     }
653
654     /*
655      * Connect the terminal to the backend for resize purposes.
656      */
657     term_provide_resize_fn(term, back->size, backhandle);
658
659     /*
660      * Set up a line discipline.
661      */
662     ldisc = ldisc_create(&cfg, term, back, backhandle, NULL);
663
664     session_closed = FALSE;
665
666     /*
667      * Prepare the mouse handler.
668      */
669     lastact = MA_NOTHING;
670     lastbtn = MBT_NOTHING;
671     dbltime = GetDoubleClickTime();
672
673     /*
674      * Set up the session-control options on the system menu.
675      */
676     {
677         HMENU m = GetSystemMenu(hwnd, FALSE);
678         HMENU p, s;
679         int i;
680
681         AppendMenu(m, MF_SEPARATOR, 0, 0);
682         if (cfg.protocol == PROT_TELNET) {
683             p = CreateMenu();
684             AppendMenu(p, MF_ENABLED, IDM_TEL_AYT, "Are You There");
685             AppendMenu(p, MF_ENABLED, IDM_TEL_BRK, "Break");
686             AppendMenu(p, MF_ENABLED, IDM_TEL_SYNCH, "Synch");
687             AppendMenu(p, MF_SEPARATOR, 0, 0);
688             AppendMenu(p, MF_ENABLED, IDM_TEL_EC, "Erase Character");
689             AppendMenu(p, MF_ENABLED, IDM_TEL_EL, "Erase Line");
690             AppendMenu(p, MF_ENABLED, IDM_TEL_GA, "Go Ahead");
691             AppendMenu(p, MF_ENABLED, IDM_TEL_NOP, "No Operation");
692             AppendMenu(p, MF_SEPARATOR, 0, 0);
693             AppendMenu(p, MF_ENABLED, IDM_TEL_ABORT, "Abort Process");
694             AppendMenu(p, MF_ENABLED, IDM_TEL_AO, "Abort Output");
695             AppendMenu(p, MF_ENABLED, IDM_TEL_IP, "Interrupt Process");
696             AppendMenu(p, MF_ENABLED, IDM_TEL_SUSP, "Suspend Process");
697             AppendMenu(p, MF_SEPARATOR, 0, 0);
698             AppendMenu(p, MF_ENABLED, IDM_TEL_EOR, "End Of Record");
699             AppendMenu(p, MF_ENABLED, IDM_TEL_EOF, "End Of File");
700             AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) p,
701                        "Telnet Command");
702             AppendMenu(m, MF_SEPARATOR, 0, 0);
703         }
704         AppendMenu(m, MF_ENABLED, IDM_SHOWLOG, "&Event Log");
705         AppendMenu(m, MF_SEPARATOR, 0, 0);
706         AppendMenu(m, MF_ENABLED, IDM_NEWSESS, "Ne&w Session...");
707         AppendMenu(m, MF_ENABLED, IDM_DUPSESS, "&Duplicate Session");
708         s = CreateMenu();
709         get_sesslist(&sesslist, TRUE);
710         for (i = 1;
711              i < ((sesslist.nsessions < 256) ? sesslist.nsessions : 256);
712              i++)
713             AppendMenu(s, MF_ENABLED, IDM_SAVED_MIN + (16 * i),
714                        sesslist.sessions[i]);
715         AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) s, "Sa&ved Sessions");
716         AppendMenu(m, MF_ENABLED, IDM_RECONF, "Chan&ge Settings...");
717         AppendMenu(m, MF_SEPARATOR, 0, 0);
718         AppendMenu(m, MF_ENABLED, IDM_COPYALL, "C&opy All to Clipboard");
719         AppendMenu(m, MF_ENABLED, IDM_CLRSB, "C&lear Scrollback");
720         AppendMenu(m, MF_ENABLED, IDM_RESET, "Rese&t Terminal");
721         AppendMenu(m, MF_SEPARATOR, 0, 0);
722         AppendMenu(m, (cfg.resize_action == RESIZE_DISABLED) ?
723                    MF_GRAYED : MF_ENABLED, IDM_FULLSCREEN, "&Full Screen");
724         AppendMenu(m, MF_SEPARATOR, 0, 0);
725         if (help_path)
726             AppendMenu(m, MF_ENABLED, IDM_HELP, "&Help");
727         AppendMenu(m, MF_ENABLED, IDM_ABOUT, "&About PuTTY");
728     }
729
730     /*
731      * Set up the initial input locale.
732      */
733     set_input_locale(GetKeyboardLayout(0));
734
735     /*
736      * Open the initial log file if there is one.
737      */
738     logfopen(logctx);
739
740     /*
741      * Finally show the window!
742      */
743     ShowWindow(hwnd, show);
744     SetForegroundWindow(hwnd);
745
746     /*
747      * Set the palette up.
748      */
749     pal = NULL;
750     logpal = NULL;
751     init_palette();
752
753     term->has_focus = (GetForegroundWindow() == hwnd);
754     UpdateWindow(hwnd);
755
756     if (GetMessage(&msg, NULL, 0, 0) == 1) {
757         int timer_id = 0, long_timer = 0;
758
759         while (msg.message != WM_QUIT) {
760             /* Sometimes DispatchMessage calls routines that use their own
761              * GetMessage loop, setup this timer so we get some control back.
762              *
763              * Also call term_update() from the timer so that if the host
764              * is sending data flat out we still do redraws.
765              */
766             if (timer_id && long_timer) {
767                 KillTimer(hwnd, timer_id);
768                 long_timer = timer_id = 0;
769             }
770             if (!timer_id)
771                 timer_id = SetTimer(hwnd, 1, 20, NULL);
772             if (!(IsWindow(logbox) && IsDialogMessage(logbox, &msg)))
773                 DispatchMessage(&msg);
774
775             /* Make sure we blink everything that needs it. */
776             term_blink(term, 0);
777
778             /* Send the paste buffer if there's anything to send */
779             term_paste(term);
780
781             /* If there's nothing new in the queue then we can do everything
782              * we've delayed, reading the socket, writing, and repainting
783              * the window.
784              */
785             if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
786                 continue;
787
788             if (pending_netevent) {
789                 enact_pending_netevent();
790
791                 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
792                     continue;
793             }
794
795             /* Okay there is now nothing to do so we make sure the screen is
796              * completely up to date then tell windows to call us in a little 
797              * while.
798              */
799             if (timer_id) {
800                 KillTimer(hwnd, timer_id);
801                 timer_id = 0;
802             }
803             HideCaret(hwnd);
804             if (GetCapture() != hwnd || 
805                 (send_raw_mouse &&
806                  !(cfg.mouse_override && is_shift_pressed())))
807                 term_out(term);
808             term_update(term);
809             ShowCaret(hwnd);
810
811             flash_window(1);           /* maintain */
812
813             /* The messages seem unreliable; especially if we're being tricky */
814             term->has_focus = (GetForegroundWindow() == hwnd);
815
816             if (term->in_vbell)
817                 /* Hmm, term_update didn't want to do an update too soon ... */
818                 timer_id = SetTimer(hwnd, 1, 50, NULL);
819             else if (!term->has_focus)
820                 timer_id = SetTimer(hwnd, 1, 500, NULL);
821             else
822                 timer_id = SetTimer(hwnd, 1, 100, NULL);
823             long_timer = 1;
824
825             /* There's no point rescanning everything in the message queue
826              * so we do an apparently unnecessary wait here
827              */
828             WaitMessage();
829             if (GetMessage(&msg, NULL, 0, 0) != 1)
830                 break;
831         }
832     }
833
834     cleanup_exit(msg.wParam);          /* this doesn't return... */
835     return msg.wParam;                 /* ... but optimiser doesn't know */
836 }
837
838 /*
839  * Clean up and exit.
840  */
841 void cleanup_exit(int code)
842 {
843     /*
844      * Clean up.
845      */
846     deinit_fonts();
847     sfree(logpal);
848     if (pal)
849         DeleteObject(pal);
850     sk_cleanup();
851     if (wsa_started)
852         WSACleanup();
853
854     if (cfg.protocol == PROT_SSH) {
855         random_save_seed();
856 #ifdef MSCRYPTOAPI
857         crypto_wrapup();
858 #endif
859     }
860
861     exit(code);
862 }
863
864 /*
865  * Set up, or shut down, an AsyncSelect. Called from winnet.c.
866  */
867 char *do_select(SOCKET skt, int startup)
868 {
869     int msg, events;
870     if (startup) {
871         msg = WM_NETEVENT;
872         events = (FD_CONNECT | FD_READ | FD_WRITE |
873                   FD_OOB | FD_CLOSE | FD_ACCEPT);
874     } else {
875         msg = events = 0;
876     }
877     if (!hwnd)
878         return "do_select(): internal error (hwnd==NULL)";
879     if (WSAAsyncSelect(skt, hwnd, msg, events) == SOCKET_ERROR) {
880         switch (WSAGetLastError()) {
881           case WSAENETDOWN:
882             return "Network is down";
883           default:
884             return "WSAAsyncSelect(): unknown error";
885         }
886     }
887     return NULL;
888 }
889
890 /*
891  * set or clear the "raw mouse message" mode
892  */
893 void set_raw_mouse_mode(void *frontend, int activate)
894 {
895     activate = activate && !cfg.no_mouse_rep;
896     send_raw_mouse = activate;
897     SetCursor(LoadCursor(NULL, activate ? IDC_ARROW : IDC_IBEAM));
898 }
899
900 /*
901  * Print a message box and close the connection.
902  */
903 void connection_fatal(void *frontend, char *fmt, ...)
904 {
905     va_list ap;
906     char stuff[200];
907
908     va_start(ap, fmt);
909     vsprintf(stuff, fmt, ap);
910     va_end(ap);
911     MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
912     if (cfg.close_on_exit == FORCE_ON)
913         PostQuitMessage(1);
914     else {
915         session_closed = TRUE;
916         set_icon(NULL, "PuTTY (inactive)");
917         set_title(NULL, "PuTTY (inactive)");
918     }
919 }
920
921 /*
922  * Report an error at the command-line parsing stage.
923  */
924 void cmdline_error(char *fmt, ...)
925 {
926     va_list ap;
927     char stuff[200];
928
929     va_start(ap, fmt);
930     vsprintf(stuff, fmt, ap);
931     va_end(ap);
932     MessageBox(hwnd, stuff, "PuTTY Command Line Error", MB_ICONERROR | MB_OK);
933     exit(1);
934 }
935
936 /*
937  * Actually do the job requested by a WM_NETEVENT
938  */
939 static void enact_pending_netevent(void)
940 {
941     static int reentering = 0;
942     extern int select_result(WPARAM, LPARAM);
943     int ret;
944
945     if (reentering)
946         return;                        /* don't unpend the pending */
947
948     pending_netevent = FALSE;
949
950     reentering = 1;
951     ret = select_result(pend_netevent_wParam, pend_netevent_lParam);
952     reentering = 0;
953
954     if (ret == 0 && !session_closed) {
955         /* Abnormal exits will already have set session_closed and taken
956          * appropriate action. */
957         if (cfg.close_on_exit == FORCE_ON ||
958             cfg.close_on_exit == AUTO) PostQuitMessage(0);
959         else {
960             session_closed = TRUE;
961             set_icon(NULL, "PuTTY (inactive)");
962             set_title(NULL, "PuTTY (inactive)");
963             MessageBox(hwnd, "Connection closed by remote host",
964                        "PuTTY", MB_OK | MB_ICONINFORMATION);
965         }
966     }
967 }
968
969 /*
970  * Copy the colour palette from the configuration data into defpal.
971  * This is non-trivial because the colour indices are different.
972  */
973 static void cfgtopalette(void)
974 {
975     int i;
976     static const int ww[] = {
977         6, 7, 8, 9, 10, 11, 12, 13,
978         14, 15, 16, 17, 18, 19, 20, 21,
979         0, 1, 2, 3, 4, 4, 5, 5
980     };
981
982     for (i = 0; i < 24; i++) {
983         int w = ww[i];
984         defpal[i].rgbtRed = cfg.colours[w][0];
985         defpal[i].rgbtGreen = cfg.colours[w][1];
986         defpal[i].rgbtBlue = cfg.colours[w][2];
987     }
988 }
989
990 /*
991  * Set up the colour palette.
992  */
993 static void init_palette(void)
994 {
995     int i;
996     HDC hdc = GetDC(hwnd);
997     if (hdc) {
998         if (cfg.try_palette && GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) {
999             /*
1000              * This is a genuine case where we must use smalloc
1001              * because the snew macros can't cope.
1002              */
1003             logpal = smalloc(sizeof(*logpal)
1004                              - sizeof(logpal->palPalEntry)
1005                              + NCOLOURS * sizeof(PALETTEENTRY));
1006             logpal->palVersion = 0x300;
1007             logpal->palNumEntries = NCOLOURS;
1008             for (i = 0; i < NCOLOURS; i++) {
1009                 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
1010                 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
1011                 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
1012                 logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
1013             }
1014             pal = CreatePalette(logpal);
1015             if (pal) {
1016                 SelectPalette(hdc, pal, FALSE);
1017                 RealizePalette(hdc);
1018                 SelectPalette(hdc, GetStockObject(DEFAULT_PALETTE), FALSE);
1019             }
1020         }
1021         ReleaseDC(hwnd, hdc);
1022     }
1023     if (pal)
1024         for (i = 0; i < NCOLOURS; i++)
1025             colours[i] = PALETTERGB(defpal[i].rgbtRed,
1026                                     defpal[i].rgbtGreen,
1027                                     defpal[i].rgbtBlue);
1028     else
1029         for (i = 0; i < NCOLOURS; i++)
1030             colours[i] = RGB(defpal[i].rgbtRed,
1031                              defpal[i].rgbtGreen, defpal[i].rgbtBlue);
1032 }
1033
1034 /*
1035  * Initialise all the fonts we will need initially. There may be as many as
1036  * three or as few as one.  The other (poentially) twentyone fonts are done
1037  * if/when they are needed.
1038  *
1039  * We also:
1040  *
1041  * - check the font width and height, correcting our guesses if
1042  *   necessary.
1043  *
1044  * - verify that the bold font is the same width as the ordinary
1045  *   one, and engage shadow bolding if not.
1046  * 
1047  * - verify that the underlined font is the same width as the
1048  *   ordinary one (manual underlining by means of line drawing can
1049  *   be done in a pinch).
1050  */
1051 static void init_fonts(int pick_width, int pick_height)
1052 {
1053     TEXTMETRIC tm;
1054     CPINFO cpinfo;
1055     int fontsize[3];
1056     int i;
1057     HDC hdc;
1058     int fw_dontcare, fw_bold;
1059
1060     for (i = 0; i < FONT_MAXNO; i++)
1061         fonts[i] = NULL;
1062
1063     bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
1064     und_mode = UND_FONT;
1065
1066     if (cfg.font.isbold) {
1067         fw_dontcare = FW_BOLD;
1068         fw_bold = FW_HEAVY;
1069     } else {
1070         fw_dontcare = FW_DONTCARE;
1071         fw_bold = FW_BOLD;
1072     }
1073
1074     hdc = GetDC(hwnd);
1075
1076     if (pick_height)
1077         font_height = pick_height;
1078     else {
1079         font_height = cfg.font.height;
1080         if (font_height > 0) {
1081             font_height =
1082                 -MulDiv(font_height, GetDeviceCaps(hdc, LOGPIXELSY), 72);
1083         }
1084     }
1085     font_width = pick_width;
1086
1087 #define f(i,c,w,u) \
1088     fonts[i] = CreateFont (font_height, font_width, 0, 0, w, FALSE, u, FALSE, \
1089                            c, OUT_DEFAULT_PRECIS, \
1090                            CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, \
1091                            FIXED_PITCH | FF_DONTCARE, cfg.font.name)
1092
1093     f(FONT_NORMAL, cfg.font.charset, fw_dontcare, FALSE);
1094
1095     lfont.lfHeight = font_height;
1096     lfont.lfWidth = font_width;
1097     lfont.lfEscapement = 0;
1098     lfont.lfOrientation  = 0;
1099     lfont.lfWeight  = fw_dontcare;
1100     lfont.lfItalic = FALSE;
1101     lfont.lfUnderline = FALSE;
1102     lfont.lfStrikeOut = FALSE;
1103     lfont.lfCharSet = cfg.font.charset;
1104     lfont.lfOutPrecision = OUT_DEFAULT_PRECIS;
1105     lfont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
1106     lfont.lfQuality = DEFAULT_QUALITY;
1107     lfont.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE;
1108     strncpy(lfont.lfFaceName, cfg.font.name, LF_FACESIZE);
1109
1110     SelectObject(hdc, fonts[FONT_NORMAL]);
1111     GetTextMetrics(hdc, &tm);
1112
1113     if (pick_width == 0 || pick_height == 0) {
1114         font_height = tm.tmHeight;
1115         font_width = tm.tmAveCharWidth;
1116     }
1117     font_dualwidth = (tm.tmAveCharWidth != tm.tmMaxCharWidth);
1118
1119 #ifdef RDB_DEBUG_PATCH
1120     debug(23, "Primary font H=%d, AW=%d, MW=%d",
1121             tm.tmHeight, tm.tmAveCharWidth, tm.tmMaxCharWidth);
1122 #endif
1123
1124     {
1125         CHARSETINFO info;
1126         DWORD cset = tm.tmCharSet;
1127         memset(&info, 0xFF, sizeof(info));
1128
1129         /* !!! Yes the next line is right */
1130         if (cset == OEM_CHARSET)
1131             ucsdata.font_codepage = GetOEMCP();
1132         else
1133             if (TranslateCharsetInfo ((DWORD *) cset, &info, TCI_SRCCHARSET))
1134                 ucsdata.font_codepage = info.ciACP;
1135         else
1136             ucsdata.font_codepage = -1;
1137
1138         GetCPInfo(ucsdata.font_codepage, &cpinfo);
1139         ucsdata.dbcs_screenfont = (cpinfo.MaxCharSize > 1);
1140     }
1141
1142     f(FONT_UNDERLINE, cfg.font.charset, fw_dontcare, TRUE);
1143
1144     /*
1145      * Some fonts, e.g. 9-pt Courier, draw their underlines
1146      * outside their character cell. We successfully prevent
1147      * screen corruption by clipping the text output, but then
1148      * we lose the underline completely. Here we try to work
1149      * out whether this is such a font, and if it is, we set a
1150      * flag that causes underlines to be drawn by hand.
1151      *
1152      * Having tried other more sophisticated approaches (such
1153      * as examining the TEXTMETRIC structure or requesting the
1154      * height of a string), I think we'll do this the brute
1155      * force way: we create a small bitmap, draw an underlined
1156      * space on it, and test to see whether any pixels are
1157      * foreground-coloured. (Since we expect the underline to
1158      * go all the way across the character cell, we only search
1159      * down a single column of the bitmap, half way across.)
1160      */
1161     {
1162         HDC und_dc;
1163         HBITMAP und_bm, und_oldbm;
1164         int i, gotit;
1165         COLORREF c;
1166
1167         und_dc = CreateCompatibleDC(hdc);
1168         und_bm = CreateCompatibleBitmap(hdc, font_width, font_height);
1169         und_oldbm = SelectObject(und_dc, und_bm);
1170         SelectObject(und_dc, fonts[FONT_UNDERLINE]);
1171         SetTextAlign(und_dc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
1172         SetTextColor(und_dc, RGB(255, 255, 255));
1173         SetBkColor(und_dc, RGB(0, 0, 0));
1174         SetBkMode(und_dc, OPAQUE);
1175         ExtTextOut(und_dc, 0, 0, ETO_OPAQUE, NULL, " ", 1, NULL);
1176         gotit = FALSE;
1177         for (i = 0; i < font_height; i++) {
1178             c = GetPixel(und_dc, font_width / 2, i);
1179             if (c != RGB(0, 0, 0))
1180                 gotit = TRUE;
1181         }
1182         SelectObject(und_dc, und_oldbm);
1183         DeleteObject(und_bm);
1184         DeleteDC(und_dc);
1185         if (!gotit) {
1186             und_mode = UND_LINE;
1187             DeleteObject(fonts[FONT_UNDERLINE]);
1188             fonts[FONT_UNDERLINE] = 0;
1189         }
1190     }
1191
1192     if (bold_mode == BOLD_FONT) {
1193         f(FONT_BOLD, cfg.font.charset, fw_bold, FALSE);
1194     }
1195 #undef f
1196
1197     descent = tm.tmAscent + 1;
1198     if (descent >= font_height)
1199         descent = font_height - 1;
1200
1201     for (i = 0; i < 3; i++) {
1202         if (fonts[i]) {
1203             if (SelectObject(hdc, fonts[i]) && GetTextMetrics(hdc, &tm))
1204                 fontsize[i] = tm.tmAveCharWidth + 256 * tm.tmHeight;
1205             else
1206                 fontsize[i] = -i;
1207         } else
1208             fontsize[i] = -i;
1209     }
1210
1211     ReleaseDC(hwnd, hdc);
1212
1213     if (fontsize[FONT_UNDERLINE] != fontsize[FONT_NORMAL]) {
1214         und_mode = UND_LINE;
1215         DeleteObject(fonts[FONT_UNDERLINE]);
1216         fonts[FONT_UNDERLINE] = 0;
1217     }
1218
1219     if (bold_mode == BOLD_FONT &&
1220         fontsize[FONT_BOLD] != fontsize[FONT_NORMAL]) {
1221         bold_mode = BOLD_SHADOW;
1222         DeleteObject(fonts[FONT_BOLD]);
1223         fonts[FONT_BOLD] = 0;
1224     }
1225     fontflag[0] = fontflag[1] = fontflag[2] = 1;
1226
1227     init_ucs(&cfg, &ucsdata);
1228 }
1229
1230 static void another_font(int fontno)
1231 {
1232     int basefont;
1233     int fw_dontcare, fw_bold;
1234     int c, u, w, x;
1235     char *s;
1236
1237     if (fontno < 0 || fontno >= FONT_MAXNO || fontflag[fontno])
1238         return;
1239
1240     basefont = (fontno & ~(FONT_BOLDUND));
1241     if (basefont != fontno && !fontflag[basefont])
1242         another_font(basefont);
1243
1244     if (cfg.font.isbold) {
1245         fw_dontcare = FW_BOLD;
1246         fw_bold = FW_HEAVY;
1247     } else {
1248         fw_dontcare = FW_DONTCARE;
1249         fw_bold = FW_BOLD;
1250     }
1251
1252     c = cfg.font.charset;
1253     w = fw_dontcare;
1254     u = FALSE;
1255     s = cfg.font.name;
1256     x = font_width;
1257
1258     if (fontno & FONT_WIDE)
1259         x *= 2;
1260     if (fontno & FONT_NARROW)
1261         x = (x+1)/2;
1262     if (fontno & FONT_OEM)
1263         c = OEM_CHARSET;
1264     if (fontno & FONT_BOLD)
1265         w = fw_bold;
1266     if (fontno & FONT_UNDERLINE)
1267         u = TRUE;
1268
1269     fonts[fontno] =
1270         CreateFont(font_height * (1 + !!(fontno & FONT_HIGH)), x, 0, 0, w,
1271                    FALSE, u, FALSE, c, OUT_DEFAULT_PRECIS,
1272                    CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
1273                    FIXED_PITCH | FF_DONTCARE, s);
1274
1275     fontflag[fontno] = 1;
1276 }
1277
1278 static void deinit_fonts(void)
1279 {
1280     int i;
1281     for (i = 0; i < FONT_MAXNO; i++) {
1282         if (fonts[i])
1283             DeleteObject(fonts[i]);
1284         fonts[i] = 0;
1285         fontflag[i] = 0;
1286     }
1287 }
1288
1289 void request_resize(void *frontend, int w, int h)
1290 {
1291     int width, height;
1292
1293     /* If the window is maximized supress resizing attempts */
1294     if (IsZoomed(hwnd)) {
1295         if (cfg.resize_action == RESIZE_TERM)
1296             return;
1297     }
1298
1299     if (cfg.resize_action == RESIZE_DISABLED) return;
1300     if (h == term->rows && w == term->cols) return;
1301
1302     /* Sanity checks ... */
1303     {
1304         static int first_time = 1;
1305         static RECT ss;
1306
1307         switch (first_time) {
1308           case 1:
1309             /* Get the size of the screen */
1310             if (get_fullscreen_rect(&ss))
1311                 /* first_time = 0 */ ;
1312             else {
1313                 first_time = 2;
1314                 break;
1315             }
1316           case 0:
1317             /* Make sure the values are sane */
1318             width = (ss.right - ss.left - extra_width) / 4;
1319             height = (ss.bottom - ss.top - extra_height) / 6;
1320
1321             if (w > width || h > height)
1322                 return;
1323             if (w < 15)
1324                 w = 15;
1325             if (h < 1)
1326                 h = 1;
1327         }
1328     }
1329
1330     term_size(term, h, w, cfg.savelines);
1331
1332     if (cfg.resize_action != RESIZE_FONT && !IsZoomed(hwnd)) {
1333         width = extra_width + font_width * w;
1334         height = extra_height + font_height * h;
1335
1336         SetWindowPos(hwnd, NULL, 0, 0, width, height,
1337             SWP_NOACTIVATE | SWP_NOCOPYBITS |
1338             SWP_NOMOVE | SWP_NOZORDER);
1339     } else
1340         reset_window(0);
1341
1342     InvalidateRect(hwnd, NULL, TRUE);
1343 }
1344
1345 static void reset_window(int reinit) {
1346     /*
1347      * This function decides how to resize or redraw when the 
1348      * user changes something. 
1349      *
1350      * This function doesn't like to change the terminal size but if the
1351      * font size is locked that may be it's only soluion.
1352      */
1353     int win_width, win_height;
1354     RECT cr, wr;
1355
1356 #ifdef RDB_DEBUG_PATCH
1357     debug((27, "reset_window()"));
1358 #endif
1359
1360     /* Current window sizes ... */
1361     GetWindowRect(hwnd, &wr);
1362     GetClientRect(hwnd, &cr);
1363
1364     win_width  = cr.right - cr.left;
1365     win_height = cr.bottom - cr.top;
1366
1367     if (cfg.resize_action == RESIZE_DISABLED) reinit = 2;
1368
1369     /* Are we being forced to reload the fonts ? */
1370     if (reinit>1) {
1371 #ifdef RDB_DEBUG_PATCH
1372         debug((27, "reset_window() -- Forced deinit"));
1373 #endif
1374         deinit_fonts();
1375         init_fonts(0,0);
1376     }
1377
1378     /* Oh, looks like we're minimised */
1379     if (win_width == 0 || win_height == 0)
1380         return;
1381
1382     /* Is the window out of position ? */
1383     if ( !reinit && 
1384             (offset_width != (win_width-font_width*term->cols)/2 ||
1385              offset_height != (win_height-font_height*term->rows)/2) ){
1386         offset_width = (win_width-font_width*term->cols)/2;
1387         offset_height = (win_height-font_height*term->rows)/2;
1388         InvalidateRect(hwnd, NULL, TRUE);
1389 #ifdef RDB_DEBUG_PATCH
1390         debug((27, "reset_window() -> Reposition terminal"));
1391 #endif
1392     }
1393
1394     if (IsZoomed(hwnd)) {
1395         /* We're fullscreen, this means we must not change the size of
1396          * the window so it's the font size or the terminal itself.
1397          */
1398
1399         extra_width = wr.right - wr.left - cr.right + cr.left;
1400         extra_height = wr.bottom - wr.top - cr.bottom + cr.top;
1401
1402         if (cfg.resize_action != RESIZE_TERM) {
1403             if (  font_width != win_width/term->cols || 
1404                   font_height != win_height/term->rows) {
1405                 deinit_fonts();
1406                 init_fonts(win_width/term->cols, win_height/term->rows);
1407                 offset_width = (win_width-font_width*term->cols)/2;
1408                 offset_height = (win_height-font_height*term->rows)/2;
1409                 InvalidateRect(hwnd, NULL, TRUE);
1410 #ifdef RDB_DEBUG_PATCH
1411                 debug((25, "reset_window() -> Z font resize to (%d, %d)",
1412                         font_width, font_height));
1413 #endif
1414             }
1415         } else {
1416             if (  font_width != win_width/term->cols || 
1417                   font_height != win_height/term->rows) {
1418                 /* Our only choice at this point is to change the 
1419                  * size of the terminal; Oh well.
1420                  */
1421                 term_size(term, win_height/font_height, win_width/font_width,
1422                           cfg.savelines);
1423                 offset_width = (win_width-font_width*term->cols)/2;
1424                 offset_height = (win_height-font_height*term->rows)/2;
1425                 InvalidateRect(hwnd, NULL, TRUE);
1426 #ifdef RDB_DEBUG_PATCH
1427                 debug((27, "reset_window() -> Zoomed term_size"));
1428 #endif
1429             }
1430         }
1431         return;
1432     }
1433
1434     /* Hmm, a force re-init means we should ignore the current window
1435      * so we resize to the default font size.
1436      */
1437     if (reinit>0) {
1438 #ifdef RDB_DEBUG_PATCH
1439         debug((27, "reset_window() -> Forced re-init"));
1440 #endif
1441
1442         offset_width = offset_height = cfg.window_border;
1443         extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
1444         extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
1445
1446         if (win_width != font_width*term->cols + offset_width*2 ||
1447             win_height != font_height*term->rows + offset_height*2) {
1448
1449             /* If this is too large windows will resize it to the maximum
1450              * allowed window size, we will then be back in here and resize
1451              * the font or terminal to fit.
1452              */
1453             SetWindowPos(hwnd, NULL, 0, 0, 
1454                          font_width*term->cols + extra_width, 
1455                          font_height*term->rows + extra_height,
1456                          SWP_NOMOVE | SWP_NOZORDER);
1457         }
1458
1459         InvalidateRect(hwnd, NULL, TRUE);
1460         return;
1461     }
1462
1463     /* Okay the user doesn't want us to change the font so we try the 
1464      * window. But that may be too big for the screen which forces us
1465      * to change the terminal.
1466      */
1467     if ((cfg.resize_action == RESIZE_TERM && reinit<=0) ||
1468         (cfg.resize_action == RESIZE_EITHER && reinit<0) ||
1469             reinit>0) {
1470         offset_width = offset_height = cfg.window_border;
1471         extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
1472         extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
1473
1474         if (win_width != font_width*term->cols + offset_width*2 ||
1475             win_height != font_height*term->rows + offset_height*2) {
1476
1477             static RECT ss;
1478             int width, height;
1479                 
1480                 get_fullscreen_rect(&ss);
1481
1482             width = (ss.right - ss.left - extra_width) / font_width;
1483             height = (ss.bottom - ss.top - extra_height) / font_height;
1484
1485             /* Grrr too big */
1486             if ( term->rows > height || term->cols > width ) {
1487                 if (cfg.resize_action == RESIZE_EITHER) {
1488                     /* Make the font the biggest we can */
1489                     if (term->cols > width)
1490                         font_width = (ss.right - ss.left - extra_width)
1491                             / term->cols;
1492                     if (term->rows > height)
1493                         font_height = (ss.bottom - ss.top - extra_height)
1494                             / term->rows;
1495
1496                     deinit_fonts();
1497                     init_fonts(font_width, font_height);
1498
1499                     width = (ss.right - ss.left - extra_width) / font_width;
1500                     height = (ss.bottom - ss.top - extra_height) / font_height;
1501                 } else {
1502                     if ( height > term->rows ) height = term->rows;
1503                     if ( width > term->cols )  width = term->cols;
1504                     term_size(term, height, width, cfg.savelines);
1505 #ifdef RDB_DEBUG_PATCH
1506                     debug((27, "reset_window() -> term resize to (%d,%d)",
1507                                height, width));
1508 #endif
1509                 }
1510             }
1511             
1512             SetWindowPos(hwnd, NULL, 0, 0, 
1513                          font_width*term->cols + extra_width, 
1514                          font_height*term->rows + extra_height,
1515                          SWP_NOMOVE | SWP_NOZORDER);
1516
1517             InvalidateRect(hwnd, NULL, TRUE);
1518 #ifdef RDB_DEBUG_PATCH
1519             debug((27, "reset_window() -> window resize to (%d,%d)",
1520                         font_width*term->cols + extra_width,
1521                         font_height*term->rows + extra_height));
1522 #endif
1523         }
1524         return;
1525     }
1526
1527     /* We're allowed to or must change the font but do we want to ?  */
1528
1529     if (font_width != (win_width-cfg.window_border*2)/term->cols || 
1530         font_height != (win_height-cfg.window_border*2)/term->rows) {
1531
1532         deinit_fonts();
1533         init_fonts((win_width-cfg.window_border*2)/term->cols, 
1534                    (win_height-cfg.window_border*2)/term->rows);
1535         offset_width = (win_width-font_width*term->cols)/2;
1536         offset_height = (win_height-font_height*term->rows)/2;
1537
1538         extra_width = wr.right - wr.left - cr.right + cr.left +offset_width*2;
1539         extra_height = wr.bottom - wr.top - cr.bottom + cr.top+offset_height*2;
1540
1541         InvalidateRect(hwnd, NULL, TRUE);
1542 #ifdef RDB_DEBUG_PATCH
1543         debug((25, "reset_window() -> font resize to (%d,%d)", 
1544                    font_width, font_height));
1545 #endif
1546     }
1547 }
1548
1549 static void set_input_locale(HKL kl)
1550 {
1551     char lbuf[20];
1552
1553     GetLocaleInfo(LOWORD(kl), LOCALE_IDEFAULTANSICODEPAGE,
1554                   lbuf, sizeof(lbuf));
1555
1556     kbd_codepage = atoi(lbuf);
1557 }
1558
1559 static void click(Mouse_Button b, int x, int y, int shift, int ctrl, int alt)
1560 {
1561     int thistime = GetMessageTime();
1562
1563     if (send_raw_mouse && !(cfg.mouse_override && shift)) {
1564         lastbtn = MBT_NOTHING;
1565         term_mouse(term, b, translate_button(b), MA_CLICK,
1566                    x, y, shift, ctrl, alt);
1567         return;
1568     }
1569
1570     if (lastbtn == b && thistime - lasttime < dbltime) {
1571         lastact = (lastact == MA_CLICK ? MA_2CLK :
1572                    lastact == MA_2CLK ? MA_3CLK :
1573                    lastact == MA_3CLK ? MA_CLICK : MA_NOTHING);
1574     } else {
1575         lastbtn = b;
1576         lastact = MA_CLICK;
1577     }
1578     if (lastact != MA_NOTHING)
1579         term_mouse(term, b, translate_button(b), lastact,
1580                    x, y, shift, ctrl, alt);
1581     lasttime = thistime;
1582 }
1583
1584 /*
1585  * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT)
1586  * into a cooked one (SELECT, EXTEND, PASTE).
1587  */
1588 static Mouse_Button translate_button(Mouse_Button button)
1589 {
1590     if (button == MBT_LEFT)
1591         return MBT_SELECT;
1592     if (button == MBT_MIDDLE)
1593         return cfg.mouse_is_xterm ? MBT_PASTE : MBT_EXTEND;
1594     if (button == MBT_RIGHT)
1595         return cfg.mouse_is_xterm ? MBT_EXTEND : MBT_PASTE;
1596     return 0;                          /* shouldn't happen */
1597 }
1598
1599 static void show_mouseptr(int show)
1600 {
1601     static int cursor_visible = 1;
1602     if (!cfg.hide_mouseptr)            /* override if this feature disabled */
1603         show = 1;
1604     if (cursor_visible && !show)
1605         ShowCursor(FALSE);
1606     else if (!cursor_visible && show)
1607         ShowCursor(TRUE);
1608     cursor_visible = show;
1609 }
1610
1611 static int is_alt_pressed(void)
1612 {
1613     BYTE keystate[256];
1614     int r = GetKeyboardState(keystate);
1615     if (!r)
1616         return FALSE;
1617     if (keystate[VK_MENU] & 0x80)
1618         return TRUE;
1619     if (keystate[VK_RMENU] & 0x80)
1620         return TRUE;
1621     return FALSE;
1622 }
1623
1624 static int is_shift_pressed(void)
1625 {
1626     BYTE keystate[256];
1627     int r = GetKeyboardState(keystate);
1628     if (!r)
1629         return FALSE;
1630     if (keystate[VK_SHIFT] & 0x80)
1631         return TRUE;
1632     return FALSE;
1633 }
1634
1635 static int resizing;
1636
1637 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1638                                 WPARAM wParam, LPARAM lParam)
1639 {
1640     HDC hdc;
1641     static int ignore_clip = FALSE;
1642     static int need_backend_resize = FALSE;
1643     static int fullscr_on_max = FALSE;
1644
1645     switch (message) {
1646       case WM_TIMER:
1647         if (pending_netevent)
1648             enact_pending_netevent();
1649         if (GetCapture() != hwnd || 
1650             (send_raw_mouse && !(cfg.mouse_override && is_shift_pressed())))
1651             term_out(term);
1652         noise_regular();
1653         HideCaret(hwnd);
1654         term_update(term);
1655         ShowCaret(hwnd);
1656         if (cfg.ping_interval > 0) {
1657             time_t now;
1658             time(&now);
1659             if (now - last_movement > cfg.ping_interval) {
1660                 back->special(backhandle, TS_PING);
1661                 last_movement = now;
1662             }
1663         }
1664         net_pending_errors();
1665         return 0;
1666       case WM_CREATE:
1667         break;
1668       case WM_CLOSE:
1669         show_mouseptr(1);
1670         if (!cfg.warn_on_close || session_closed ||
1671             MessageBox(hwnd,
1672                        "Are you sure you want to close this session?",
1673                        "PuTTY Exit Confirmation",
1674                        MB_ICONWARNING | MB_OKCANCEL) == IDOK)
1675             DestroyWindow(hwnd);
1676         return 0;
1677       case WM_DESTROY:
1678         show_mouseptr(1);
1679         PostQuitMessage(0);
1680         return 0;
1681       case WM_SYSCOMMAND:
1682         switch (wParam & ~0xF) {       /* low 4 bits reserved to Windows */
1683           case IDM_SHOWLOG:
1684             showeventlog(hwnd);
1685             break;
1686           case IDM_NEWSESS:
1687           case IDM_DUPSESS:
1688           case IDM_SAVEDSESS:
1689             {
1690                 char b[2048];
1691                 char c[30], *cl;
1692                 int freecl = FALSE;
1693                 STARTUPINFO si;
1694                 PROCESS_INFORMATION pi;
1695                 HANDLE filemap = NULL;
1696
1697                 if (wParam == IDM_DUPSESS) {
1698                     /*
1699                      * Allocate a file-mapping memory chunk for the
1700                      * config structure.
1701                      */
1702                     SECURITY_ATTRIBUTES sa;
1703                     Config *p;
1704
1705                     sa.nLength = sizeof(sa);
1706                     sa.lpSecurityDescriptor = NULL;
1707                     sa.bInheritHandle = TRUE;
1708                     filemap = CreateFileMapping((HANDLE) 0xFFFFFFFF,
1709                                                 &sa,
1710                                                 PAGE_READWRITE,
1711                                                 0, sizeof(Config), NULL);
1712                     if (filemap) {
1713                         p = (Config *) MapViewOfFile(filemap,
1714                                                      FILE_MAP_WRITE,
1715                                                      0, 0, sizeof(Config));
1716                         if (p) {
1717                             *p = cfg;  /* structure copy */
1718                             UnmapViewOfFile(p);
1719                         }
1720                     }
1721                     sprintf(c, "putty &%p", filemap);
1722                     cl = c;
1723                 } else if (wParam == IDM_SAVEDSESS) {
1724                     if ((lParam - IDM_SAVED_MIN) / 16 < sesslist.nsessions) {
1725                         char *session =
1726                             sesslist.sessions[(lParam - IDM_SAVED_MIN) / 16];
1727                         cl = snewn(16 + strlen(session), char);
1728                                        /* 8, but play safe */
1729                         if (!cl)
1730                             cl = NULL;    
1731                                        /* not a very important failure mode */
1732                         else {
1733                             sprintf(cl, "putty @%s", session);
1734                             freecl = TRUE;
1735                         }
1736                     } else
1737                         break;
1738                 } else
1739                     cl = NULL;
1740
1741                 GetModuleFileName(NULL, b, sizeof(b) - 1);
1742                 si.cb = sizeof(si);
1743                 si.lpReserved = NULL;
1744                 si.lpDesktop = NULL;
1745                 si.lpTitle = NULL;
1746                 si.dwFlags = 0;
1747                 si.cbReserved2 = 0;
1748                 si.lpReserved2 = NULL;
1749                 CreateProcess(b, cl, NULL, NULL, TRUE,
1750                               NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
1751
1752                 if (filemap)
1753                     CloseHandle(filemap);
1754                 if (freecl)
1755                     sfree(cl);
1756             }
1757             break;
1758           case IDM_RECONF:
1759             {
1760                 Config prev_cfg;
1761                 int init_lvl = 1;
1762
1763                 GetWindowText(hwnd, cfg.wintitle, sizeof(cfg.wintitle));
1764                 prev_cfg = cfg;
1765
1766                 if (!do_reconfig(hwnd))
1767                     break;
1768
1769                 {
1770                     /* Disable full-screen if resizing forbidden */
1771                     HMENU m = GetSystemMenu (hwnd, FALSE);
1772                     EnableMenuItem(m, IDM_FULLSCREEN, MF_BYCOMMAND | 
1773                                    (cfg.resize_action == RESIZE_DISABLED)
1774                                    ? MF_GRAYED : MF_ENABLED);
1775                     /* Gracefully unzoom if necessary */
1776                     if (IsZoomed(hwnd) &&
1777                         (cfg.resize_action == RESIZE_DISABLED)) {
1778                         ShowWindow(hwnd, SW_RESTORE);
1779                     }
1780                 }
1781
1782                 /* Pass new config data to the logging module */
1783                 log_reconfig(logctx, &cfg);
1784
1785                 sfree(logpal);
1786                 /*
1787                  * Flush the line discipline's edit buffer in the
1788                  * case where local editing has just been disabled.
1789                  */
1790                 ldisc_send(ldisc, NULL, 0, 0);
1791                 if (pal)
1792                     DeleteObject(pal);
1793                 logpal = NULL;
1794                 pal = NULL;
1795                 cfgtopalette();
1796                 init_palette();
1797
1798                 /* Pass new config data to the terminal */
1799                 term_reconfig(term, &cfg);
1800
1801                 /* Pass new config data to the back end */
1802                 back->reconfig(backhandle, &cfg);
1803
1804                 /* Screen size changed ? */
1805                 if (cfg.height != prev_cfg.height ||
1806                     cfg.width != prev_cfg.width ||
1807                     cfg.savelines != prev_cfg.savelines ||
1808                     cfg.resize_action == RESIZE_FONT ||
1809                     (cfg.resize_action == RESIZE_EITHER && IsZoomed(hwnd)) ||
1810                     cfg.resize_action == RESIZE_DISABLED)
1811                     term_size(term, cfg.height, cfg.width, cfg.savelines);
1812
1813                 /* Enable or disable the scroll bar, etc */
1814                 {
1815                     LONG nflg, flag = GetWindowLong(hwnd, GWL_STYLE);
1816                     LONG nexflag, exflag =
1817                         GetWindowLong(hwnd, GWL_EXSTYLE);
1818
1819                     nexflag = exflag;
1820                     if (cfg.alwaysontop != prev_cfg.alwaysontop) {
1821                         if (cfg.alwaysontop) {
1822                             nexflag |= WS_EX_TOPMOST;
1823                             SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
1824                                          SWP_NOMOVE | SWP_NOSIZE);
1825                         } else {
1826                             nexflag &= ~(WS_EX_TOPMOST);
1827                             SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0,
1828                                          SWP_NOMOVE | SWP_NOSIZE);
1829                         }
1830                     }
1831                     if (cfg.sunken_edge)
1832                         nexflag |= WS_EX_CLIENTEDGE;
1833                     else
1834                         nexflag &= ~(WS_EX_CLIENTEDGE);
1835
1836                     nflg = flag;
1837                     if (is_full_screen() ?
1838                         cfg.scrollbar_in_fullscreen : cfg.scrollbar)
1839                         nflg |= WS_VSCROLL;
1840                     else
1841                         nflg &= ~WS_VSCROLL;
1842
1843                     if (cfg.resize_action == RESIZE_DISABLED ||
1844                         is_full_screen())
1845                         nflg &= ~WS_THICKFRAME;
1846                     else
1847                         nflg |= WS_THICKFRAME;
1848
1849                     if (cfg.resize_action == RESIZE_DISABLED)
1850                         nflg &= ~WS_MAXIMIZEBOX;
1851                     else
1852                         nflg |= WS_MAXIMIZEBOX;
1853
1854                     if (nflg != flag || nexflag != exflag) {
1855                         if (nflg != flag)
1856                             SetWindowLong(hwnd, GWL_STYLE, nflg);
1857                         if (nexflag != exflag)
1858                             SetWindowLong(hwnd, GWL_EXSTYLE, nexflag);
1859
1860                         SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
1861                                      SWP_NOACTIVATE | SWP_NOCOPYBITS |
1862                                      SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
1863                                      SWP_FRAMECHANGED);
1864
1865                         init_lvl = 2;
1866                     }
1867                 }
1868
1869                 /* Oops */
1870                 if (cfg.resize_action == RESIZE_DISABLED && IsZoomed(hwnd)) {
1871                     force_normal(hwnd);
1872                     init_lvl = 2;
1873                 }
1874
1875                 set_title(NULL, cfg.wintitle);
1876                 if (IsIconic(hwnd)) {
1877                     SetWindowText(hwnd,
1878                                   cfg.win_name_always ? window_name :
1879                                   icon_name);
1880                 }
1881
1882                 if (strcmp(cfg.font.name, prev_cfg.font.name) != 0 ||
1883                     strcmp(cfg.line_codepage, prev_cfg.line_codepage) != 0 ||
1884                     cfg.font.isbold != prev_cfg.font.isbold ||
1885                     cfg.font.height != prev_cfg.font.height ||
1886                     cfg.font.charset != prev_cfg.font.charset ||
1887                     cfg.vtmode != prev_cfg.vtmode ||
1888                     cfg.bold_colour != prev_cfg.bold_colour ||
1889                     cfg.resize_action == RESIZE_DISABLED ||
1890                     cfg.resize_action == RESIZE_EITHER ||
1891                     (cfg.resize_action != prev_cfg.resize_action))
1892                     init_lvl = 2;
1893
1894                 InvalidateRect(hwnd, NULL, TRUE);
1895                 reset_window(init_lvl);
1896                 net_pending_errors();
1897             }
1898             break;
1899           case IDM_COPYALL:
1900             term_copyall(term);
1901             break;
1902           case IDM_CLRSB:
1903             term_clrsb(term);
1904             break;
1905           case IDM_RESET:
1906             term_pwron(term);
1907             ldisc_send(ldisc, NULL, 0, 0);
1908             break;
1909           case IDM_TEL_AYT:
1910             back->special(backhandle, TS_AYT);
1911             net_pending_errors();
1912             break;
1913           case IDM_TEL_BRK:
1914             back->special(backhandle, TS_BRK);
1915             net_pending_errors();
1916             break;
1917           case IDM_TEL_SYNCH:
1918             back->special(backhandle, TS_SYNCH);
1919             net_pending_errors();
1920             break;
1921           case IDM_TEL_EC:
1922             back->special(backhandle, TS_EC);
1923             net_pending_errors();
1924             break;
1925           case IDM_TEL_EL:
1926             back->special(backhandle, TS_EL);
1927             net_pending_errors();
1928             break;
1929           case IDM_TEL_GA:
1930             back->special(backhandle, TS_GA);
1931             net_pending_errors();
1932             break;
1933           case IDM_TEL_NOP:
1934             back->special(backhandle, TS_NOP);
1935             net_pending_errors();
1936             break;
1937           case IDM_TEL_ABORT:
1938             back->special(backhandle, TS_ABORT);
1939             net_pending_errors();
1940             break;
1941           case IDM_TEL_AO:
1942             back->special(backhandle, TS_AO);
1943             net_pending_errors();
1944             break;
1945           case IDM_TEL_IP:
1946             back->special(backhandle, TS_IP);
1947             net_pending_errors();
1948             break;
1949           case IDM_TEL_SUSP:
1950             back->special(backhandle, TS_SUSP);
1951             net_pending_errors();
1952             break;
1953           case IDM_TEL_EOR:
1954             back->special(backhandle, TS_EOR);
1955             net_pending_errors();
1956             break;
1957           case IDM_TEL_EOF:
1958             back->special(backhandle, TS_EOF);
1959             net_pending_errors();
1960             break;
1961           case IDM_ABOUT:
1962             showabout(hwnd);
1963             break;
1964           case IDM_HELP:
1965             WinHelp(hwnd, help_path,
1966                     help_has_contents ? HELP_FINDER : HELP_CONTENTS, 0);
1967             break;
1968           case SC_MOUSEMENU:
1969             /*
1970              * We get this if the System menu has been activated
1971              * using the mouse.
1972              */
1973             show_mouseptr(1);
1974             break;
1975           case SC_KEYMENU:
1976             /*
1977              * We get this if the System menu has been activated
1978              * using the keyboard. This might happen from within
1979              * TranslateKey, in which case it really wants to be
1980              * followed by a `space' character to actually _bring
1981              * the menu up_ rather than just sitting there in
1982              * `ready to appear' state.
1983              */
1984             show_mouseptr(1);          /* make sure pointer is visible */
1985             if( lParam == 0 )
1986                 PostMessage(hwnd, WM_CHAR, ' ', 0);
1987             break;
1988           case IDM_FULLSCREEN:
1989             flip_full_screen();
1990             break;
1991           default:
1992             if (wParam >= IDM_SAVED_MIN && wParam <= IDM_SAVED_MAX) {
1993                 SendMessage(hwnd, WM_SYSCOMMAND, IDM_SAVEDSESS, wParam);
1994             }
1995         }
1996         break;
1997
1998 #define X_POS(l) ((int)(short)LOWORD(l))
1999 #define Y_POS(l) ((int)(short)HIWORD(l))
2000
2001 #define TO_CHR_X(x) ((((x)<0 ? (x)-font_width+1 : (x))-offset_width) / font_width)
2002 #define TO_CHR_Y(y) ((((y)<0 ? (y)-font_height+1: (y))-offset_height) / font_height)
2003       case WM_LBUTTONDOWN:
2004       case WM_MBUTTONDOWN:
2005       case WM_RBUTTONDOWN:
2006       case WM_LBUTTONUP:
2007       case WM_MBUTTONUP:
2008       case WM_RBUTTONUP:
2009         {
2010             int button, press;
2011
2012             switch (message) {
2013               case WM_LBUTTONDOWN:
2014                 button = MBT_LEFT;
2015                 press = 1;
2016                 break;
2017               case WM_MBUTTONDOWN:
2018                 button = MBT_MIDDLE;
2019                 press = 1;
2020                 break;
2021               case WM_RBUTTONDOWN:
2022                 button = MBT_RIGHT;
2023                 press = 1;
2024                 break;
2025               case WM_LBUTTONUP:
2026                 button = MBT_LEFT;
2027                 press = 0;
2028                 break;
2029               case WM_MBUTTONUP:
2030                 button = MBT_MIDDLE;
2031                 press = 0;
2032                 break;
2033               case WM_RBUTTONUP:
2034                 button = MBT_RIGHT;
2035                 press = 0;
2036                 break;
2037               default:
2038                 button = press = 0;    /* shouldn't happen */
2039             }
2040             show_mouseptr(1);
2041             /*
2042              * Special case: in full-screen mode, if the left
2043              * button is clicked in the very top left corner of the
2044              * window, we put up the System menu instead of doing
2045              * selection.
2046              */
2047             if (is_full_screen() && press && button == MBT_LEFT &&
2048                 X_POS(lParam) == 0 && Y_POS(lParam) == 0) {
2049                 SendMessage(hwnd, WM_SYSCOMMAND, SC_MOUSEMENU, 0);
2050                 return 0;
2051             }
2052             if (press) {
2053                 click(button,
2054                       TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam)),
2055                       wParam & MK_SHIFT, wParam & MK_CONTROL,
2056                       is_alt_pressed());
2057                 SetCapture(hwnd);
2058             } else {
2059                 term_mouse(term, button, translate_button(button), MA_RELEASE,
2060                            TO_CHR_X(X_POS(lParam)),
2061                            TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
2062                            wParam & MK_CONTROL, is_alt_pressed());
2063                 ReleaseCapture();
2064             }
2065         }
2066         return 0;
2067       case WM_MOUSEMOVE:
2068         show_mouseptr(1);
2069         /*
2070          * Add the mouse position and message time to the random
2071          * number noise.
2072          */
2073         noise_ultralight(lParam);
2074
2075         if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON) &&
2076             GetCapture() == hwnd) {
2077             Mouse_Button b;
2078             if (wParam & MK_LBUTTON)
2079                 b = MBT_LEFT;
2080             else if (wParam & MK_MBUTTON)
2081                 b = MBT_MIDDLE;
2082             else
2083                 b = MBT_RIGHT;
2084             term_mouse(term, b, translate_button(b), MA_DRAG,
2085                        TO_CHR_X(X_POS(lParam)),
2086                        TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
2087                        wParam & MK_CONTROL, is_alt_pressed());
2088         }
2089         return 0;
2090       case WM_NCMOUSEMOVE:
2091         show_mouseptr(1);
2092         noise_ultralight(lParam);
2093         return 0;
2094       case WM_IGNORE_CLIP:
2095         ignore_clip = wParam;          /* don't panic on DESTROYCLIPBOARD */
2096         break;
2097       case WM_DESTROYCLIPBOARD:
2098         if (!ignore_clip)
2099             term_deselect(term);
2100         ignore_clip = FALSE;
2101         return 0;
2102       case WM_PAINT:
2103         {
2104             PAINTSTRUCT p;
2105             HideCaret(hwnd);
2106             hdc = BeginPaint(hwnd, &p);
2107             if (pal) {
2108                 SelectPalette(hdc, pal, TRUE);
2109                 RealizePalette(hdc);
2110             }
2111             term_paint(term, hdc, 
2112                        (p.rcPaint.left-offset_width)/font_width,
2113                        (p.rcPaint.top-offset_height)/font_height,
2114                        (p.rcPaint.right-offset_width-1)/font_width,
2115                        (p.rcPaint.bottom-offset_height-1)/font_height,
2116                        is_alt_pressed());
2117
2118             if (p.fErase ||
2119                 p.rcPaint.left  < offset_width  ||
2120                 p.rcPaint.top   < offset_height ||
2121                 p.rcPaint.right >= offset_width + font_width*term->cols ||
2122                 p.rcPaint.bottom>= offset_height + font_height*term->rows)
2123             {
2124                 HBRUSH fillcolour, oldbrush;
2125                 HPEN   edge, oldpen;
2126                 fillcolour = CreateSolidBrush (
2127                                     colours[(ATTR_DEFBG>>ATTR_BGSHIFT)*2]);
2128                 oldbrush = SelectObject(hdc, fillcolour);
2129                 edge = CreatePen(PS_SOLID, 0, 
2130                                     colours[(ATTR_DEFBG>>ATTR_BGSHIFT)*2]);
2131                 oldpen = SelectObject(hdc, edge);
2132
2133                 /*
2134                  * Jordan Russell reports that this apparently
2135                  * ineffectual IntersectClipRect() call masks a
2136                  * Windows NT/2K bug causing strange display
2137                  * problems when the PuTTY window is taller than
2138                  * the primary monitor. It seems harmless enough...
2139                  */
2140                 IntersectClipRect(hdc,
2141                         p.rcPaint.left, p.rcPaint.top,
2142                         p.rcPaint.right, p.rcPaint.bottom);
2143
2144                 ExcludeClipRect(hdc, 
2145                         offset_width, offset_height,
2146                         offset_width+font_width*term->cols,
2147                         offset_height+font_height*term->rows);
2148
2149                 Rectangle(hdc, p.rcPaint.left, p.rcPaint.top, 
2150                           p.rcPaint.right, p.rcPaint.bottom);
2151
2152                 // SelectClipRgn(hdc, NULL);
2153
2154                 SelectObject(hdc, oldbrush);
2155                 DeleteObject(fillcolour);
2156                 SelectObject(hdc, oldpen);
2157                 DeleteObject(edge);
2158             }
2159             SelectObject(hdc, GetStockObject(SYSTEM_FONT));
2160             SelectObject(hdc, GetStockObject(WHITE_PEN));
2161             EndPaint(hwnd, &p);
2162             ShowCaret(hwnd);
2163         }
2164         return 0;
2165       case WM_NETEVENT:
2166         /* Notice we can get multiple netevents, FD_READ, FD_WRITE etc
2167          * but the only one that's likely to try to overload us is FD_READ.
2168          * This means buffering just one is fine.
2169          */
2170         if (pending_netevent)
2171             enact_pending_netevent();
2172
2173         pending_netevent = TRUE;
2174         pend_netevent_wParam = wParam;
2175         pend_netevent_lParam = lParam;
2176         if (WSAGETSELECTEVENT(lParam) != FD_READ)
2177             enact_pending_netevent();
2178
2179         time(&last_movement);
2180         return 0;
2181       case WM_SETFOCUS:
2182         term->has_focus = TRUE;
2183         CreateCaret(hwnd, caretbm, font_width, font_height);
2184         ShowCaret(hwnd);
2185         flash_window(0);               /* stop */
2186         compose_state = 0;
2187         term_out(term);
2188         term_update(term);
2189         break;
2190       case WM_KILLFOCUS:
2191         show_mouseptr(1);
2192         term->has_focus = FALSE;
2193         DestroyCaret();
2194         caret_x = caret_y = -1;        /* ensure caret is replaced next time */
2195         term_out(term);
2196         term_update(term);
2197         break;
2198       case WM_ENTERSIZEMOVE:
2199 #ifdef RDB_DEBUG_PATCH
2200         debug((27, "WM_ENTERSIZEMOVE"));
2201 #endif
2202         EnableSizeTip(1);
2203         resizing = TRUE;
2204         need_backend_resize = FALSE;
2205         break;
2206       case WM_EXITSIZEMOVE:
2207         EnableSizeTip(0);
2208         resizing = FALSE;
2209 #ifdef RDB_DEBUG_PATCH
2210         debug((27, "WM_EXITSIZEMOVE"));
2211 #endif
2212         if (need_backend_resize) {
2213             term_size(term, cfg.height, cfg.width, cfg.savelines);
2214             InvalidateRect(hwnd, NULL, TRUE);
2215         }
2216         break;
2217       case WM_SIZING:
2218         /*
2219          * This does two jobs:
2220          * 1) Keep the sizetip uptodate
2221          * 2) Make sure the window size is _stepped_ in units of the font size.
2222          */
2223         if (cfg.resize_action != RESIZE_FONT && !is_alt_pressed()) {
2224             int width, height, w, h, ew, eh;
2225             LPRECT r = (LPRECT) lParam;
2226
2227             if ( !need_backend_resize && cfg.resize_action == RESIZE_EITHER &&
2228                     (cfg.height != term->rows || cfg.width != term->cols )) {
2229                 /* 
2230                  * Great! It seems that both the terminal size and the
2231                  * font size have been changed and the user is now dragging.
2232                  * 
2233                  * It will now be difficult to get back to the configured
2234                  * font size!
2235                  *
2236                  * This would be easier but it seems to be too confusing.
2237
2238                 term_size(term, cfg.height, cfg.width, cfg.savelines);
2239                 reset_window(2);
2240                  */
2241                 cfg.height=term->rows; cfg.width=term->cols;
2242
2243                 InvalidateRect(hwnd, NULL, TRUE);
2244                 need_backend_resize = TRUE;
2245             }
2246
2247             width = r->right - r->left - extra_width;
2248             height = r->bottom - r->top - extra_height;
2249             w = (width + font_width / 2) / font_width;
2250             if (w < 1)
2251                 w = 1;
2252             h = (height + font_height / 2) / font_height;
2253             if (h < 1)
2254                 h = 1;
2255             UpdateSizeTip(hwnd, w, h);
2256             ew = width - w * font_width;
2257             eh = height - h * font_height;
2258             if (ew != 0) {
2259                 if (wParam == WMSZ_LEFT ||
2260                     wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
2261                     r->left += ew;
2262                 else
2263                     r->right -= ew;
2264             }
2265             if (eh != 0) {
2266                 if (wParam == WMSZ_TOP ||
2267                     wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
2268                     r->top += eh;
2269                 else
2270                     r->bottom -= eh;
2271             }
2272             if (ew || eh)
2273                 return 1;
2274             else
2275                 return 0;
2276         } else {
2277             int width, height, w, h, rv = 0;
2278             int ex_width = extra_width + (cfg.window_border - offset_width) * 2;
2279             int ex_height = extra_height + (cfg.window_border - offset_height) * 2;
2280             LPRECT r = (LPRECT) lParam;
2281
2282             width = r->right - r->left - ex_width;
2283             height = r->bottom - r->top - ex_height;
2284
2285             w = (width + term->cols/2)/term->cols;
2286             h = (height + term->rows/2)/term->rows;
2287             if ( r->right != r->left + w*term->cols + ex_width)
2288                 rv = 1;
2289
2290             if (wParam == WMSZ_LEFT ||
2291                 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
2292                 r->left = r->right - w*term->cols - ex_width;
2293             else
2294                 r->right = r->left + w*term->cols + ex_width;
2295
2296             if (r->bottom != r->top + h*term->rows + ex_height)
2297                 rv = 1;
2298
2299             if (wParam == WMSZ_TOP ||
2300                 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
2301                 r->top = r->bottom - h*term->rows - ex_height;
2302             else
2303                 r->bottom = r->top + h*term->rows + ex_height;
2304
2305             return rv;
2306         }
2307         /* break;  (never reached) */
2308       case WM_FULLSCR_ON_MAX:
2309         fullscr_on_max = TRUE;
2310         break;
2311       case WM_MOVE:
2312         sys_cursor_update();
2313         break;
2314       case WM_SIZE:
2315 #ifdef RDB_DEBUG_PATCH
2316         debug((27, "WM_SIZE %s (%d,%d)",
2317                 (wParam == SIZE_MINIMIZED) ? "SIZE_MINIMIZED":
2318                 (wParam == SIZE_MAXIMIZED) ? "SIZE_MAXIMIZED":
2319                 (wParam == SIZE_RESTORED && resizing) ? "to":
2320                 (wParam == SIZE_RESTORED) ? "SIZE_RESTORED":
2321                 "...",
2322             LOWORD(lParam), HIWORD(lParam)));
2323 #endif
2324         if (wParam == SIZE_MINIMIZED)
2325             SetWindowText(hwnd,
2326                           cfg.win_name_always ? window_name : icon_name);
2327         if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
2328             SetWindowText(hwnd, window_name);
2329         if (wParam == SIZE_RESTORED)
2330             clear_full_screen();
2331         if (wParam == SIZE_MAXIMIZED && fullscr_on_max) {
2332             fullscr_on_max = FALSE;
2333             make_full_screen();
2334         }
2335
2336         if (cfg.resize_action == RESIZE_DISABLED) {
2337             /* A resize, well it better be a minimize. */
2338             reset_window(-1);
2339         } else {
2340
2341             int width, height, w, h;
2342
2343             width = LOWORD(lParam);
2344             height = HIWORD(lParam);
2345
2346             if (!resizing) {
2347                 if (wParam == SIZE_MAXIMIZED && !was_zoomed) {
2348                     was_zoomed = 1;
2349                     prev_rows = term->rows;
2350                     prev_cols = term->cols;
2351                     if (cfg.resize_action == RESIZE_TERM) {
2352                         w = width / font_width;
2353                         if (w < 1) w = 1;
2354                         h = height / font_height;
2355                         if (h < 1) h = 1;
2356
2357                         term_size(term, h, w, cfg.savelines);
2358                     }
2359                     reset_window(0);
2360                 } else if (wParam == SIZE_RESTORED && was_zoomed) {
2361                     was_zoomed = 0;
2362                     if (cfg.resize_action == RESIZE_TERM)
2363                         term_size(term, prev_rows, prev_cols, cfg.savelines);
2364                     if (cfg.resize_action != RESIZE_FONT)
2365                         reset_window(2);
2366                     else
2367                         reset_window(0);
2368                 }
2369                 /* This is an unexpected resize, these will normally happen
2370                  * if the window is too large. Probably either the user
2371                  * selected a huge font or the screen size has changed.
2372                  *
2373                  * This is also called with minimize.
2374                  */
2375                 else reset_window(-1);
2376             }
2377
2378             /*
2379              * Don't call back->size in mid-resize. (To prevent
2380              * massive numbers of resize events getting sent
2381              * down the connection during an NT opaque drag.)
2382              */
2383             if (resizing) {
2384                 if (cfg.resize_action != RESIZE_FONT && !is_alt_pressed()) {
2385                     need_backend_resize = TRUE;
2386                     w = (width-cfg.window_border*2) / font_width;
2387                     if (w < 1) w = 1;
2388                     h = (height-cfg.window_border*2) / font_height;
2389                     if (h < 1) h = 1;
2390
2391                     cfg.height = h;
2392                     cfg.width = w;
2393                 } else 
2394                     reset_window(0);
2395             }
2396         }
2397         sys_cursor_update();
2398         return 0;
2399       case WM_VSCROLL:
2400         switch (LOWORD(wParam)) {
2401           case SB_BOTTOM:
2402             term_scroll(term, -1, 0);
2403             break;
2404           case SB_TOP:
2405             term_scroll(term, +1, 0);
2406             break;
2407           case SB_LINEDOWN:
2408             term_scroll(term, 0, +1);
2409             break;
2410           case SB_LINEUP:
2411             term_scroll(term, 0, -1);
2412             break;
2413           case SB_PAGEDOWN:
2414             term_scroll(term, 0, +term->rows / 2);
2415             break;
2416           case SB_PAGEUP:
2417             term_scroll(term, 0, -term->rows / 2);
2418             break;
2419           case SB_THUMBPOSITION:
2420           case SB_THUMBTRACK:
2421             term_scroll(term, 1, HIWORD(wParam));
2422             break;
2423         }
2424         break;
2425       case WM_PALETTECHANGED:
2426         if ((HWND) wParam != hwnd && pal != NULL) {
2427             HDC hdc = get_ctx(NULL);
2428             if (hdc) {
2429                 if (RealizePalette(hdc) > 0)
2430                     UpdateColors(hdc);
2431                 free_ctx(hdc);
2432             }
2433         }
2434         break;
2435       case WM_QUERYNEWPALETTE:
2436         if (pal != NULL) {
2437             HDC hdc = get_ctx(NULL);
2438             if (hdc) {
2439                 if (RealizePalette(hdc) > 0)
2440                     UpdateColors(hdc);
2441                 free_ctx(hdc);
2442                 return TRUE;
2443             }
2444         }
2445         return FALSE;
2446       case WM_KEYDOWN:
2447       case WM_SYSKEYDOWN:
2448       case WM_KEYUP:
2449       case WM_SYSKEYUP:
2450         /*
2451          * Add the scan code and keypress timing to the random
2452          * number noise.
2453          */
2454         noise_ultralight(lParam);
2455
2456         /*
2457          * We don't do TranslateMessage since it disassociates the
2458          * resulting CHAR message from the KEYDOWN that sparked it,
2459          * which we occasionally don't want. Instead, we process
2460          * KEYDOWN, and call the Win32 translator functions so that
2461          * we get the translations under _our_ control.
2462          */
2463         {
2464             unsigned char buf[20];
2465             int len;
2466
2467             if (wParam == VK_PROCESSKEY) {
2468                 MSG m;
2469                 m.hwnd = hwnd;
2470                 m.message = WM_KEYDOWN;
2471                 m.wParam = wParam;
2472                 m.lParam = lParam & 0xdfff;
2473                 TranslateMessage(&m);
2474             } else {
2475                 len = TranslateKey(message, wParam, lParam, buf);
2476                 if (len == -1)
2477                     return DefWindowProc(hwnd, message, wParam, lParam);
2478
2479                 if (len != 0) {
2480                     /*
2481                      * Interrupt an ongoing paste. I'm not sure
2482                      * this is sensible, but for the moment it's
2483                      * preferable to having to faff about buffering
2484                      * things.
2485                      */
2486                     term_nopaste(term);
2487
2488                     /*
2489                      * We need not bother about stdin backlogs
2490                      * here, because in GUI PuTTY we can't do
2491                      * anything about it anyway; there's no means
2492                      * of asking Windows to hold off on KEYDOWN
2493                      * messages. We _have_ to buffer everything
2494                      * we're sent.
2495                      */
2496                     term_seen_key_event(term);
2497                     ldisc_send(ldisc, buf, len, 1);
2498                     show_mouseptr(0);
2499                 }
2500             }
2501         }
2502         net_pending_errors();
2503         return 0;
2504       case WM_INPUTLANGCHANGE:
2505         /* wParam == Font number */
2506         /* lParam == Locale */
2507         set_input_locale((HKL)lParam);
2508         sys_cursor_update();
2509         break;
2510       case WM_IME_NOTIFY:
2511         if(wParam == IMN_SETOPENSTATUS) {
2512             HIMC hImc = ImmGetContext(hwnd);
2513             ImmSetCompositionFont(hImc, &lfont);
2514             ImmReleaseContext(hwnd, hImc);
2515             return 0;
2516         }
2517         break;
2518       case WM_IME_COMPOSITION:
2519         {
2520             HIMC hIMC;
2521             int n;
2522             char *buff;
2523
2524             if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS || 
2525                 osVersion.dwPlatformId == VER_PLATFORM_WIN32s) break; /* no Unicode */
2526
2527             if ((lParam & GCS_RESULTSTR) == 0) /* Composition unfinished. */
2528                 break; /* fall back to DefWindowProc */
2529
2530             hIMC = ImmGetContext(hwnd);
2531             n = ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, NULL, 0);
2532
2533             if (n > 0) {
2534                 int i;
2535                 buff = snewn(n, char);
2536                 ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, buff, n);
2537                 /*
2538                  * Jaeyoun Chung reports that Korean character
2539                  * input doesn't work correctly if we do a single
2540                  * luni_send() covering the whole of buff. So
2541                  * instead we luni_send the characters one by one.
2542                  */
2543                 term_seen_key_event(term);
2544                 for (i = 0; i < n; i += 2) {
2545                     luni_send(ldisc, (unsigned short *)(buff+i), 1, 1);
2546                 }
2547                 free(buff);
2548             }
2549             ImmReleaseContext(hwnd, hIMC);
2550             return 1;
2551         }
2552
2553       case WM_IME_CHAR:
2554         if (wParam & 0xFF00) {
2555             unsigned char buf[2];
2556
2557             buf[1] = wParam;
2558             buf[0] = wParam >> 8;
2559             term_seen_key_event(term);
2560             lpage_send(ldisc, kbd_codepage, buf, 2, 1);
2561         } else {
2562             char c = (unsigned char) wParam;
2563             term_seen_key_event(term);
2564             lpage_send(ldisc, kbd_codepage, &c, 1, 1);
2565         }
2566         return (0);
2567       case WM_CHAR:
2568       case WM_SYSCHAR:
2569         /*
2570          * Nevertheless, we are prepared to deal with WM_CHAR
2571          * messages, should they crop up. So if someone wants to
2572          * post the things to us as part of a macro manoeuvre,
2573          * we're ready to cope.
2574          */
2575         {
2576             char c = (unsigned char)wParam;
2577             term_seen_key_event(term);
2578             lpage_send(ldisc, CP_ACP, &c, 1, 1);
2579         }
2580         return 0;
2581       case WM_SETCURSOR:
2582         if (send_raw_mouse && LOWORD(lParam) == HTCLIENT) {
2583             SetCursor(LoadCursor(NULL, IDC_ARROW));
2584             return TRUE;
2585         }
2586       default:
2587         if (message == wm_mousewheel || message == WM_MOUSEWHEEL) {
2588             int shift_pressed=0, control_pressed=0;
2589
2590             if (message == WM_MOUSEWHEEL) {
2591                 wheel_accumulator += (short)HIWORD(wParam);
2592                 shift_pressed=LOWORD(wParam) & MK_SHIFT;
2593                 control_pressed=LOWORD(wParam) & MK_CONTROL;
2594             } else {
2595                 BYTE keys[256];
2596                 wheel_accumulator += (int)wParam;
2597                 if (GetKeyboardState(keys)!=0) {
2598                     shift_pressed=keys[VK_SHIFT]&0x80;
2599                     control_pressed=keys[VK_CONTROL]&0x80;
2600                 }
2601             }
2602
2603             /* process events when the threshold is reached */
2604             while (abs(wheel_accumulator) >= WHEEL_DELTA) {
2605                 int b;
2606
2607                 /* reduce amount for next time */
2608                 if (wheel_accumulator > 0) {
2609                     b = MBT_WHEEL_UP;
2610                     wheel_accumulator -= WHEEL_DELTA;
2611                 } else if (wheel_accumulator < 0) {
2612                     b = MBT_WHEEL_DOWN;
2613                     wheel_accumulator += WHEEL_DELTA;
2614                 } else
2615                     break;
2616
2617                 if (send_raw_mouse &&
2618                     !(cfg.mouse_override && shift_pressed)) {
2619                     /* send a mouse-down followed by a mouse up */
2620                     term_mouse(term, b, translate_button(b),
2621                                MA_CLICK,
2622                                TO_CHR_X(X_POS(lParam)),
2623                                TO_CHR_Y(Y_POS(lParam)), shift_pressed,
2624                                control_pressed, is_alt_pressed());
2625                     term_mouse(term, b, translate_button(b),
2626                                MA_RELEASE, TO_CHR_X(X_POS(lParam)),
2627                                TO_CHR_Y(Y_POS(lParam)), shift_pressed,
2628                                control_pressed, is_alt_pressed());
2629                 } else {
2630                     /* trigger a scroll */
2631                     term_scroll(term, 0,
2632                                 b == MBT_WHEEL_UP ?
2633                                 -term->rows / 2 : term->rows / 2);
2634                 }
2635             }
2636             return 0;
2637         }
2638     }
2639
2640     return DefWindowProc(hwnd, message, wParam, lParam);
2641 }
2642
2643 /*
2644  * Move the system caret. (We maintain one, even though it's
2645  * invisible, for the benefit of blind people: apparently some
2646  * helper software tracks the system caret, so we should arrange to
2647  * have one.)
2648  */
2649 void sys_cursor(void *frontend, int x, int y)
2650 {
2651     int cx, cy;
2652
2653     if (!term->has_focus) return;
2654
2655     /*
2656      * Avoid gratuitously re-updating the cursor position and IMM
2657      * window if there's no actual change required.
2658      */
2659     cx = x * font_width + offset_width;
2660     cy = y * font_height + offset_height;
2661     if (cx == caret_x && cy == caret_y)
2662         return;
2663     caret_x = cx;
2664     caret_y = cy;
2665
2666     sys_cursor_update();
2667 }
2668
2669 static void sys_cursor_update(void)
2670 {
2671     COMPOSITIONFORM cf;
2672     HIMC hIMC;
2673
2674     if (!term->has_focus) return;
2675
2676     if (caret_x < 0 || caret_y < 0)
2677         return;
2678
2679     SetCaretPos(caret_x, caret_y);
2680
2681     /* IMM calls on Win98 and beyond only */
2682     if(osVersion.dwPlatformId == VER_PLATFORM_WIN32s) return; /* 3.11 */
2683     
2684     if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS &&
2685             osVersion.dwMinorVersion == 0) return; /* 95 */
2686
2687     /* we should have the IMM functions */
2688     hIMC = ImmGetContext(hwnd);
2689     cf.dwStyle = CFS_POINT;
2690     cf.ptCurrentPos.x = caret_x;
2691     cf.ptCurrentPos.y = caret_y;
2692     ImmSetCompositionWindow(hIMC, &cf);
2693
2694     ImmReleaseContext(hwnd, hIMC);
2695 }
2696
2697 /*
2698  * Draw a line of text in the window, at given character
2699  * coordinates, in given attributes.
2700  *
2701  * We are allowed to fiddle with the contents of `text'.
2702  */
2703 void do_text(Context ctx, int x, int y, char *text, int len,
2704              unsigned long attr, int lattr)
2705 {
2706     COLORREF fg, bg, t;
2707     int nfg, nbg, nfont;
2708     HDC hdc = ctx;
2709     RECT line_box;
2710     int force_manual_underline = 0;
2711     int fnt_width = font_width * (1 + (lattr != LATTR_NORM));
2712     int char_width = fnt_width;
2713     int text_adjust = 0;
2714     static int *IpDx = 0, IpDxLEN = 0;
2715
2716     if (attr & ATTR_WIDE)
2717         char_width *= 2;
2718
2719     if (len > IpDxLEN || IpDx[0] != char_width) {
2720         int i;
2721         if (len > IpDxLEN) {
2722             sfree(IpDx);
2723             IpDx = snewn(len + 16, int);
2724             IpDxLEN = (len + 16);
2725         }
2726         for (i = 0; i < IpDxLEN; i++)
2727             IpDx[i] = char_width;
2728     }
2729
2730     /* Only want the left half of double width lines */
2731     if (lattr != LATTR_NORM && x*2 >= term->cols)
2732         return;
2733
2734     x *= fnt_width;
2735     y *= font_height;
2736     x += offset_width;
2737     y += offset_height;
2738
2739     if ((attr & TATTR_ACTCURS) && (cfg.cursor_type == 0 || term->big_cursor)) {
2740         attr &= ATTR_CUR_AND | (bold_mode != BOLD_COLOURS ? ATTR_BOLD : 0);
2741         attr ^= ATTR_CUR_XOR;
2742     }
2743
2744     nfont = 0;
2745     if (cfg.vtmode == VT_POORMAN && lattr != LATTR_NORM) {
2746         /* Assume a poorman font is borken in other ways too. */
2747         lattr = LATTR_WIDE;
2748     } else
2749         switch (lattr) {
2750           case LATTR_NORM:
2751             break;
2752           case LATTR_WIDE:
2753             nfont |= FONT_WIDE;
2754             break;
2755           default:
2756             nfont |= FONT_WIDE + FONT_HIGH;
2757             break;
2758         }
2759     if (attr & ATTR_NARROW)
2760         nfont |= FONT_NARROW;
2761
2762     /* Special hack for the VT100 linedraw glyphs. */
2763     if ((attr & CSET_MASK) == 0x2300) {
2764         if (text[0] >= (char) 0xBA && text[0] <= (char) 0xBD) {
2765             switch ((unsigned char) (text[0])) {
2766               case 0xBA:
2767                 text_adjust = -2 * font_height / 5;
2768                 break;
2769               case 0xBB:
2770                 text_adjust = -1 * font_height / 5;
2771                 break;
2772               case 0xBC:
2773                 text_adjust = font_height / 5;
2774                 break;
2775               case 0xBD:
2776                 text_adjust = 2 * font_height / 5;
2777                 break;
2778             }
2779             if (lattr == LATTR_TOP || lattr == LATTR_BOT)
2780                 text_adjust *= 2;
2781             attr &= ~CSET_MASK;
2782             text[0] = (char) (ucsdata.unitab_xterm['q'] & CHAR_MASK);
2783             attr |= (ucsdata.unitab_xterm['q'] & CSET_MASK);
2784             if (attr & ATTR_UNDER) {
2785                 attr &= ~ATTR_UNDER;
2786                 force_manual_underline = 1;
2787             }
2788         }
2789     }
2790
2791     /* Anything left as an original character set is unprintable. */
2792     if (DIRECT_CHAR(attr)) {
2793         attr &= ~CSET_MASK;
2794         attr |= 0xFF00;
2795         memset(text, 0xFD, len);
2796     }
2797
2798     /* OEM CP */
2799     if ((attr & CSET_MASK) == ATTR_OEMCP)
2800         nfont |= FONT_OEM;
2801
2802     nfg = ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
2803     nfg = 2 * (nfg & 0xF) + (nfg & 0x10 ? 1 : 0);
2804     nbg = ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
2805     nbg = 2 * (nbg & 0xF) + (nbg & 0x10 ? 1 : 0);
2806     if (bold_mode == BOLD_FONT && (attr & ATTR_BOLD))
2807         nfont |= FONT_BOLD;
2808     if (und_mode == UND_FONT && (attr & ATTR_UNDER))
2809         nfont |= FONT_UNDERLINE;
2810     another_font(nfont);
2811     if (!fonts[nfont]) {
2812         if (nfont & FONT_UNDERLINE)
2813             force_manual_underline = 1;
2814         /* Don't do the same for manual bold, it could be bad news. */
2815
2816         nfont &= ~(FONT_BOLD | FONT_UNDERLINE);
2817     }
2818     another_font(nfont);
2819     if (!fonts[nfont])
2820         nfont = FONT_NORMAL;
2821     if (attr & ATTR_REVERSE) {
2822         t = nfg;
2823         nfg = nbg;
2824         nbg = t;
2825     }
2826     if (bold_mode == BOLD_COLOURS && (attr & ATTR_BOLD))
2827         nfg |= 1;
2828     if (bold_mode == BOLD_COLOURS && (attr & ATTR_BLINK))
2829         nbg |= 1;
2830     fg = colours[nfg];
2831     bg = colours[nbg];
2832     SelectObject(hdc, fonts[nfont]);
2833     SetTextColor(hdc, fg);
2834     SetBkColor(hdc, bg);
2835     SetBkMode(hdc, OPAQUE);
2836     line_box.left = x;
2837     line_box.top = y;
2838     line_box.right = x + char_width * len;
2839     line_box.bottom = y + font_height;
2840
2841     /* Only want the left half of double width lines */
2842     if (line_box.right > font_width*term->cols+offset_width)
2843         line_box.right = font_width*term->cols+offset_width;
2844
2845     /* We're using a private area for direct to font. (512 chars.) */
2846     if (ucsdata.dbcs_screenfont && (attr & CSET_MASK) == ATTR_ACP) {
2847         /* Ho Hum, dbcs fonts are a PITA! */
2848         /* To display on W9x I have to convert to UCS */
2849         static wchar_t *uni_buf = 0;
2850         static int uni_len = 0;
2851         int nlen, mptr;
2852         if (len > uni_len) {
2853             sfree(uni_buf);
2854             uni_len = len;
2855             uni_buf = snewn(uni_len, wchar_t);
2856         }
2857
2858         for(nlen = mptr = 0; mptr<len; mptr++) {
2859             uni_buf[nlen] = 0xFFFD;
2860             if (IsDBCSLeadByteEx(ucsdata.font_codepage, (BYTE) text[mptr])) {
2861                 IpDx[nlen] += char_width;
2862                 MultiByteToWideChar(ucsdata.font_codepage, MB_USEGLYPHCHARS,
2863                                    text+mptr, 2, uni_buf+nlen, 1);
2864                 mptr++;
2865             }
2866             else
2867             {
2868                 MultiByteToWideChar(ucsdata.font_codepage, MB_USEGLYPHCHARS,
2869                                    text+mptr, 1, uni_buf+nlen, 1);
2870             }
2871             nlen++;
2872         }
2873         if (nlen <= 0)
2874             return;                    /* Eeek! */
2875
2876         ExtTextOutW(hdc, x,
2877                     y - font_height * (lattr == LATTR_BOT) + text_adjust,
2878                     ETO_CLIPPED | ETO_OPAQUE, &line_box, uni_buf, nlen, IpDx);
2879         if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2880             SetBkMode(hdc, TRANSPARENT);
2881             ExtTextOutW(hdc, x - 1,
2882                         y - font_height * (lattr ==
2883                                            LATTR_BOT) + text_adjust,
2884                         ETO_CLIPPED, &line_box, uni_buf, nlen, IpDx);
2885         }
2886
2887         IpDx[0] = -1;
2888     } else if (DIRECT_FONT(attr)) {
2889         ExtTextOut(hdc, x,
2890                    y - font_height * (lattr == LATTR_BOT) + text_adjust,
2891                    ETO_CLIPPED | ETO_OPAQUE, &line_box, text, len, IpDx);
2892         if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2893             SetBkMode(hdc, TRANSPARENT);
2894
2895             /* GRR: This draws the character outside it's box and can leave
2896              * 'droppings' even with the clip box! I suppose I could loop it
2897              * one character at a time ... yuk. 
2898              * 
2899              * Or ... I could do a test print with "W", and use +1 or -1 for this
2900              * shift depending on if the leftmost column is blank...
2901              */
2902             ExtTextOut(hdc, x - 1,
2903                        y - font_height * (lattr ==
2904                                           LATTR_BOT) + text_adjust,
2905                        ETO_CLIPPED, &line_box, text, len, IpDx);
2906         }
2907     } else {
2908         /* And 'normal' unicode characters */
2909         static WCHAR *wbuf = NULL;
2910         static int wlen = 0;
2911         int i;
2912         if (wlen < len) {
2913             sfree(wbuf);
2914             wlen = len;
2915             wbuf = snewn(wlen, WCHAR);
2916         }
2917         for (i = 0; i < len; i++)
2918             wbuf[i] = (WCHAR) ((attr & CSET_MASK) + (text[i] & CHAR_MASK));
2919
2920         ExtTextOutW(hdc, x,
2921                     y - font_height * (lattr == LATTR_BOT) + text_adjust,
2922                     ETO_CLIPPED | ETO_OPAQUE, &line_box, wbuf, len, IpDx);
2923
2924         /* And the shadow bold hack. */
2925         if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2926             SetBkMode(hdc, TRANSPARENT);
2927             ExtTextOutW(hdc, x - 1,
2928                         y - font_height * (lattr ==
2929                                            LATTR_BOT) + text_adjust,
2930                         ETO_CLIPPED, &line_box, wbuf, len, IpDx);
2931         }
2932     }
2933     if (lattr != LATTR_TOP && (force_manual_underline ||
2934                                (und_mode == UND_LINE
2935                                 && (attr & ATTR_UNDER)))) {
2936         HPEN oldpen;
2937         int dec = descent;
2938         if (lattr == LATTR_BOT)
2939             dec = dec * 2 - font_height;
2940
2941         oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, fg));
2942         MoveToEx(hdc, x, y + dec, NULL);
2943         LineTo(hdc, x + len * char_width, y + dec);
2944         oldpen = SelectObject(hdc, oldpen);
2945         DeleteObject(oldpen);
2946     }
2947 }
2948
2949 void do_cursor(Context ctx, int x, int y, char *text, int len,
2950                unsigned long attr, int lattr)
2951 {
2952
2953     int fnt_width;
2954     int char_width;
2955     HDC hdc = ctx;
2956     int ctype = cfg.cursor_type;
2957
2958     if ((attr & TATTR_ACTCURS) && (ctype == 0 || term->big_cursor)) {
2959         if (((attr & CSET_MASK) | (unsigned char) *text) != UCSWIDE) {
2960             do_text(ctx, x, y, text, len, attr, lattr);
2961             return;
2962         }
2963         ctype = 2;
2964         attr |= TATTR_RIGHTCURS;
2965     }
2966
2967     fnt_width = char_width = font_width * (1 + (lattr != LATTR_NORM));
2968     if (attr & ATTR_WIDE)
2969         char_width *= 2;
2970     x *= fnt_width;
2971     y *= font_height;
2972     x += offset_width;
2973     y += offset_height;
2974
2975     if ((attr & TATTR_PASCURS) && (ctype == 0 || term->big_cursor)) {
2976         POINT pts[5];
2977         HPEN oldpen;
2978         pts[0].x = pts[1].x = pts[4].x = x;
2979         pts[2].x = pts[3].x = x + char_width - 1;
2980         pts[0].y = pts[3].y = pts[4].y = y;
2981         pts[1].y = pts[2].y = y + font_height - 1;
2982         oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2983         Polyline(hdc, pts, 5);
2984         oldpen = SelectObject(hdc, oldpen);
2985         DeleteObject(oldpen);
2986     } else if ((attr & (TATTR_ACTCURS | TATTR_PASCURS)) && ctype != 0) {
2987         int startx, starty, dx, dy, length, i;
2988         if (ctype == 1) {
2989             startx = x;
2990             starty = y + descent;
2991             dx = 1;
2992             dy = 0;
2993             length = char_width;
2994         } else {
2995             int xadjust = 0;
2996             if (attr & TATTR_RIGHTCURS)
2997                 xadjust = char_width - 1;
2998             startx = x + xadjust;
2999             starty = y;
3000             dx = 0;
3001             dy = 1;
3002             length = font_height;
3003         }
3004         if (attr & TATTR_ACTCURS) {
3005             HPEN oldpen;
3006             oldpen =
3007                 SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
3008             MoveToEx(hdc, startx, starty, NULL);
3009             LineTo(hdc, startx + dx * length, starty + dy * length);
3010             oldpen = SelectObject(hdc, oldpen);
3011             DeleteObject(oldpen);
3012         } else {
3013             for (i = 0; i < length; i++) {
3014                 if (i % 2 == 0) {
3015                     SetPixel(hdc, startx, starty, colours[23]);
3016                 }
3017                 startx += dx;
3018                 starty += dy;
3019             }
3020         }
3021     }
3022 }
3023
3024 /* This function gets the actual width of a character in the normal font.
3025  */
3026 int char_width(Context ctx, int uc) {
3027     HDC hdc = ctx;
3028     int ibuf = 0;
3029
3030     /* If the font max is the same as the font ave width then this
3031      * function is a no-op.
3032      */
3033     if (!font_dualwidth) return 1;
3034
3035     switch (uc & CSET_MASK) {
3036       case ATTR_ASCII:
3037         uc = ucsdata.unitab_line[uc & 0xFF];
3038         break;
3039       case ATTR_LINEDRW:
3040         uc = ucsdata.unitab_xterm[uc & 0xFF];
3041         break;
3042       case ATTR_SCOACS:
3043         uc = ucsdata.unitab_scoacs[uc & 0xFF];
3044         break;
3045     }
3046     if (DIRECT_FONT(uc)) {
3047         if (ucsdata.dbcs_screenfont) return 1;
3048
3049         /* Speedup, I know of no font where ascii is the wrong width */
3050         if ((uc&CHAR_MASK) >= ' ' && (uc&CHAR_MASK)<= '~') 
3051             return 1;
3052
3053         if ( (uc & CSET_MASK) == ATTR_ACP ) {
3054             SelectObject(hdc, fonts[FONT_NORMAL]);
3055         } else if ( (uc & CSET_MASK) == ATTR_OEMCP ) {
3056             another_font(FONT_OEM);
3057             if (!fonts[FONT_OEM]) return 0;
3058
3059             SelectObject(hdc, fonts[FONT_OEM]);
3060         } else
3061             return 0;
3062
3063         if ( GetCharWidth32(hdc, uc&CHAR_MASK, uc&CHAR_MASK, &ibuf) != 1 && 
3064              GetCharWidth(hdc, uc&CHAR_MASK, uc&CHAR_MASK, &ibuf) != 1)
3065             return 0;
3066     } else {
3067         /* Speedup, I know of no font where ascii is the wrong width */
3068         if (uc >= ' ' && uc <= '~') return 1;
3069
3070         SelectObject(hdc, fonts[FONT_NORMAL]);
3071         if ( GetCharWidth32W(hdc, uc, uc, &ibuf) == 1 )
3072             /* Okay that one worked */ ;
3073         else if ( GetCharWidthW(hdc, uc, uc, &ibuf) == 1 )
3074             /* This should work on 9x too, but it's "less accurate" */ ;
3075         else
3076             return 0;
3077     }
3078
3079     ibuf += font_width / 2 -1;
3080     ibuf /= font_width;
3081
3082     return ibuf;
3083 }
3084
3085 /*
3086  * Translate a WM_(SYS)?KEY(UP|DOWN) message into a string of ASCII
3087  * codes. Returns number of bytes used or zero to drop the message
3088  * or -1 to forward the message to windows.
3089  */
3090 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
3091                         unsigned char *output)
3092 {
3093     BYTE keystate[256];
3094     int scan, left_alt = 0, key_down, shift_state;
3095     int r, i, code;
3096     unsigned char *p = output;
3097     static int alt_sum = 0;
3098
3099     HKL kbd_layout = GetKeyboardLayout(0);
3100
3101     static WORD keys[3];
3102     static int compose_char = 0;
3103     static WPARAM compose_key = 0;
3104
3105     r = GetKeyboardState(keystate);
3106     if (!r)
3107         memset(keystate, 0, sizeof(keystate));
3108     else {
3109 #if 0
3110 #define SHOW_TOASCII_RESULT
3111         {                              /* Tell us all about key events */
3112             static BYTE oldstate[256];
3113             static int first = 1;
3114             static int scan;
3115             int ch;
3116             if (first)
3117                 memcpy(oldstate, keystate, sizeof(oldstate));
3118             first = 0;
3119
3120             if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT) {
3121                 debug(("+"));
3122             } else if ((HIWORD(lParam) & KF_UP)
3123                        && scan == (HIWORD(lParam) & 0xFF)) {
3124                 debug((". U"));
3125             } else {
3126                 debug((".\n"));
3127                 if (wParam >= VK_F1 && wParam <= VK_F20)
3128                     debug(("K_F%d", wParam + 1 - VK_F1));
3129                 else
3130                     switch (wParam) {
3131                       case VK_SHIFT:
3132                         debug(("SHIFT"));
3133                         break;
3134                       case VK_CONTROL:
3135                         debug(("CTRL"));
3136                         break;
3137                       case VK_MENU:
3138                         debug(("ALT"));
3139                         break;
3140                       default:
3141                         debug(("VK_%02x", wParam));
3142                     }
3143                 if (message == WM_SYSKEYDOWN || message == WM_SYSKEYUP)
3144                     debug(("*"));
3145                 debug((", S%02x", scan = (HIWORD(lParam) & 0xFF)));
3146
3147                 ch = MapVirtualKeyEx(wParam, 2, kbd_layout);
3148                 if (ch >= ' ' && ch <= '~')
3149                     debug((", '%c'", ch));
3150                 else if (ch)
3151                     debug((", $%02x", ch));
3152
3153                 if (keys[0])
3154                     debug((", KB0=%02x", keys[0]));
3155                 if (keys[1])
3156                     debug((", KB1=%02x", keys[1]));
3157                 if (keys[2])
3158                     debug((", KB2=%02x", keys[2]));
3159
3160                 if ((keystate[VK_SHIFT] & 0x80) != 0)
3161                     debug((", S"));
3162                 if ((keystate[VK_CONTROL] & 0x80) != 0)
3163                     debug((", C"));
3164                 if ((HIWORD(lParam) & KF_EXTENDED))
3165                     debug((", E"));
3166                 if ((HIWORD(lParam) & KF_UP))
3167                     debug((", U"));
3168             }
3169
3170             if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT);
3171             else if ((HIWORD(lParam) & KF_UP))
3172                 oldstate[wParam & 0xFF] ^= 0x80;
3173             else
3174                 oldstate[wParam & 0xFF] ^= 0x81;
3175
3176             for (ch = 0; ch < 256; ch++)
3177                 if (oldstate[ch] != keystate[ch])
3178                     debug((", M%02x=%02x", ch, keystate[ch]));
3179
3180             memcpy(oldstate, keystate, sizeof(oldstate));
3181         }
3182 #endif
3183
3184         if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED)) {
3185             keystate[VK_RMENU] = keystate[VK_MENU];
3186         }
3187
3188
3189         /* Nastyness with NUMLock - Shift-NUMLock is left alone though */
3190         if ((cfg.funky_type == 3 ||
3191              (cfg.funky_type <= 1 && term->app_keypad_keys &&
3192               !cfg.no_applic_k))
3193             && wParam == VK_NUMLOCK && !(keystate[VK_SHIFT] & 0x80)) {
3194
3195             wParam = VK_EXECUTE;
3196
3197             /* UnToggle NUMLock */
3198             if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0)
3199                 keystate[VK_NUMLOCK] ^= 1;
3200         }
3201
3202         /* And write back the 'adjusted' state */
3203         SetKeyboardState(keystate);
3204     }
3205
3206     /* Disable Auto repeat if required */
3207     if (term->repeat_off &&
3208         (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT)
3209         return 0;
3210
3211     if ((HIWORD(lParam) & KF_ALTDOWN) && (keystate[VK_RMENU] & 0x80) == 0)
3212         left_alt = 1;
3213
3214     key_down = ((HIWORD(lParam) & KF_UP) == 0);
3215
3216     /* Make sure Ctrl-ALT is not the same as AltGr for ToAscii unless told. */
3217     if (left_alt && (keystate[VK_CONTROL] & 0x80)) {
3218         if (cfg.ctrlaltkeys)
3219             keystate[VK_MENU] = 0;
3220         else {
3221             keystate[VK_RMENU] = 0x80;
3222             left_alt = 0;
3223         }
3224     }
3225
3226     scan = (HIWORD(lParam) & (KF_UP | KF_EXTENDED | 0xFF));
3227     shift_state = ((keystate[VK_SHIFT] & 0x80) != 0)
3228         + ((keystate[VK_CONTROL] & 0x80) != 0) * 2;
3229
3230     /* Note if AltGr was pressed and if it was used as a compose key */
3231     if (!compose_state) {
3232         compose_key = 0x100;
3233         if (cfg.compose_key) {
3234             if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED))
3235                 compose_key = wParam;
3236         }
3237         if (wParam == VK_APPS)
3238             compose_key = wParam;
3239     }
3240
3241     if (wParam == compose_key) {
3242         if (compose_state == 0
3243             && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0) compose_state =
3244                 1;
3245         else if (compose_state == 1 && (HIWORD(lParam) & KF_UP))
3246             compose_state = 2;
3247         else
3248             compose_state = 0;
3249     } else if (compose_state == 1 && wParam != VK_CONTROL)
3250         compose_state = 0;
3251
3252     if (compose_state > 1 && left_alt)
3253         compose_state = 0;
3254
3255     /* Sanitize the number pad if not using a PC NumPad */
3256     if (left_alt || (term->app_keypad_keys && !cfg.no_applic_k
3257                      && cfg.funky_type != 2)
3258         || cfg.funky_type == 3 || cfg.nethack_keypad || compose_state) {
3259         if ((HIWORD(lParam) & KF_EXTENDED) == 0) {
3260             int nParam = 0;
3261             switch (wParam) {
3262               case VK_INSERT:
3263                 nParam = VK_NUMPAD0;
3264                 break;
3265               case VK_END:
3266                 nParam = VK_NUMPAD1;
3267                 break;
3268               case VK_DOWN:
3269                 nParam = VK_NUMPAD2;
3270                 break;
3271               case VK_NEXT:
3272                 nParam = VK_NUMPAD3;
3273                 break;
3274               case VK_LEFT:
3275                 nParam = VK_NUMPAD4;
3276                 break;
3277               case VK_CLEAR:
3278                 nParam = VK_NUMPAD5;
3279                 break;
3280               case VK_RIGHT:
3281                 nParam = VK_NUMPAD6;
3282                 break;
3283               case VK_HOME:
3284                 nParam = VK_NUMPAD7;
3285                 break;
3286               case VK_UP:
3287                 nParam = VK_NUMPAD8;
3288                 break;
3289               case VK_PRIOR:
3290                 nParam = VK_NUMPAD9;
3291                 break;
3292               case VK_DELETE:
3293                 nParam = VK_DECIMAL;
3294                 break;
3295             }
3296             if (nParam) {
3297                 if (keystate[VK_NUMLOCK] & 1)
3298                     shift_state |= 1;
3299                 wParam = nParam;
3300             }
3301         }
3302     }
3303
3304     /* If a key is pressed and AltGr is not active */
3305     if (key_down && (keystate[VK_RMENU] & 0x80) == 0 && !compose_state) {
3306         /* Okay, prepare for most alts then ... */
3307         if (left_alt)
3308             *p++ = '\033';
3309
3310         /* Lets see if it's a pattern we know all about ... */
3311         if (wParam == VK_PRIOR && shift_state == 1) {
3312             SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0);
3313             return 0;
3314         }
3315         if (wParam == VK_PRIOR && shift_state == 2) {
3316             SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0);
3317             return 0;
3318         }
3319         if (wParam == VK_NEXT && shift_state == 1) {
3320             SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
3321             return 0;
3322         }
3323         if (wParam == VK_NEXT && shift_state == 2) {
3324             SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0);
3325             return 0;
3326         }
3327         if (wParam == VK_INSERT && shift_state == 1) {
3328             term_do_paste(term);
3329             return 0;
3330         }
3331         if (left_alt && wParam == VK_F4 && cfg.alt_f4) {
3332             return -1;
3333         }
3334         if (left_alt && wParam == VK_SPACE && cfg.alt_space) {
3335             SendMessage(hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0);
3336             return -1;
3337         }
3338         if (left_alt && wParam == VK_RETURN && cfg.fullscreenonaltenter &&
3339             (cfg.resize_action != RESIZE_DISABLED)) {
3340             if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) != KF_REPEAT)
3341                 flip_full_screen();
3342             return -1;
3343         }
3344         /* Control-Numlock for app-keypad mode switch */
3345         if (wParam == VK_PAUSE && shift_state == 2) {
3346             term->app_keypad_keys ^= 1;
3347             return 0;
3348         }
3349
3350         /* Nethack keypad */
3351         if (cfg.nethack_keypad && !left_alt) {
3352             switch (wParam) {
3353               case VK_NUMPAD1:
3354                 *p++ = shift_state ? 'B' : 'b';
3355                 return p - output;
3356               case VK_NUMPAD2:
3357                 *p++ = shift_state ? 'J' : 'j';
3358                 return p - output;
3359               case VK_NUMPAD3:
3360                 *p++ = shift_state ? 'N' : 'n';
3361                 return p - output;
3362               case VK_NUMPAD4:
3363                 *p++ = shift_state ? 'H' : 'h';
3364                 return p - output;
3365               case VK_NUMPAD5:
3366                 *p++ = shift_state ? '.' : '.';
3367                 return p - output;
3368               case VK_NUMPAD6:
3369                 *p++ = shift_state ? 'L' : 'l';
3370                 return p - output;
3371               case VK_NUMPAD7:
3372                 *p++ = shift_state ? 'Y' : 'y';
3373                 return p - output;
3374               case VK_NUMPAD8:
3375                 *p++ = shift_state ? 'K' : 'k';
3376                 return p - output;
3377               case VK_NUMPAD9:
3378                 *p++ = shift_state ? 'U' : 'u';
3379                 return p - output;
3380             }
3381         }
3382
3383         /* Application Keypad */
3384         if (!left_alt) {
3385             int xkey = 0;
3386
3387             if (cfg.funky_type == 3 ||
3388                 (cfg.funky_type <= 1 &&
3389                  term->app_keypad_keys && !cfg.no_applic_k)) switch (wParam) {
3390                   case VK_EXECUTE:
3391                     xkey = 'P';
3392                     break;
3393                   case VK_DIVIDE:
3394                     xkey = 'Q';
3395                     break;
3396                   case VK_MULTIPLY:
3397                     xkey = 'R';
3398                     break;
3399                   case VK_SUBTRACT:
3400                     xkey = 'S';
3401                     break;
3402                 }
3403             if (term->app_keypad_keys && !cfg.no_applic_k)
3404                 switch (wParam) {
3405                   case VK_NUMPAD0:
3406                     xkey = 'p';
3407                     break;
3408                   case VK_NUMPAD1:
3409                     xkey = 'q';
3410                     break;
3411                   case VK_NUMPAD2:
3412                     xkey = 'r';
3413                     break;
3414                   case VK_NUMPAD3:
3415                     xkey = 's';
3416                     break;
3417                   case VK_NUMPAD4:
3418                     xkey = 't';
3419                     break;
3420                   case VK_NUMPAD5:
3421                     xkey = 'u';
3422                     break;
3423                   case VK_NUMPAD6:
3424                     xkey = 'v';
3425                     break;
3426                   case VK_NUMPAD7:
3427                     xkey = 'w';
3428                     break;
3429                   case VK_NUMPAD8:
3430                     xkey = 'x';
3431                     break;
3432                   case VK_NUMPAD9:
3433                     xkey = 'y';
3434                     break;
3435
3436                   case VK_DECIMAL:
3437                     xkey = 'n';
3438                     break;
3439                   case VK_ADD:
3440                     if (cfg.funky_type == 2) {
3441                         if (shift_state)
3442                             xkey = 'l';
3443                         else
3444                             xkey = 'k';
3445                     } else if (shift_state)
3446                         xkey = 'm';
3447                     else
3448                         xkey = 'l';
3449                     break;
3450
3451                   case VK_DIVIDE:
3452                     if (cfg.funky_type == 2)
3453                         xkey = 'o';
3454                     break;
3455                   case VK_MULTIPLY:
3456                     if (cfg.funky_type == 2)
3457                         xkey = 'j';
3458                     break;
3459                   case VK_SUBTRACT:
3460                     if (cfg.funky_type == 2)
3461                         xkey = 'm';
3462                     break;
3463
3464                   case VK_RETURN:
3465                     if (HIWORD(lParam) & KF_EXTENDED)
3466                         xkey = 'M';
3467                     break;
3468                 }
3469             if (xkey) {
3470                 if (term->vt52_mode) {
3471                     if (xkey >= 'P' && xkey <= 'S')
3472                         p += sprintf((char *) p, "\x1B%c", xkey);
3473                     else
3474                         p += sprintf((char *) p, "\x1B?%c", xkey);
3475                 } else
3476                     p += sprintf((char *) p, "\x1BO%c", xkey);
3477                 return p - output;
3478             }
3479         }
3480
3481         if (wParam == VK_BACK && shift_state == 0) {    /* Backspace */
3482             *p++ = (cfg.bksp_is_delete ? 0x7F : 0x08);
3483             *p++ = 0;
3484             return -2;
3485         }
3486         if (wParam == VK_BACK && shift_state == 1) {    /* Shift Backspace */
3487             /* We do the opposite of what is configured */
3488             *p++ = (cfg.bksp_is_delete ? 0x08 : 0x7F);
3489             *p++ = 0;
3490             return -2;
3491         }
3492         if (wParam == VK_TAB && shift_state == 1) {     /* Shift tab */
3493             *p++ = 0x1B;
3494             *p++ = '[';
3495             *p++ = 'Z';
3496             return p - output;
3497         }
3498         if (wParam == VK_SPACE && shift_state == 2) {   /* Ctrl-Space */
3499             *p++ = 0;
3500             return p - output;
3501         }
3502         if (wParam == VK_SPACE && shift_state == 3) {   /* Ctrl-Shift-Space */
3503             *p++ = 160;
3504             return p - output;
3505         }
3506         if (wParam == VK_CANCEL && shift_state == 2) {  /* Ctrl-Break */
3507             *p++ = 3;
3508             *p++ = 0;
3509             return -2;
3510         }
3511         if (wParam == VK_PAUSE) {      /* Break/Pause */
3512             *p++ = 26;
3513             *p++ = 0;
3514             return -2;
3515         }
3516         /* Control-2 to Control-8 are special */
3517         if (shift_state == 2 && wParam >= '2' && wParam <= '8') {
3518             *p++ = "\000\033\034\035\036\037\177"[wParam - '2'];
3519             return p - output;
3520         }
3521         if (shift_state == 2 && (wParam == 0xBD || wParam == 0xBF)) {
3522             *p++ = 0x1F;
3523             return p - output;
3524         }
3525         if (shift_state == 2 && wParam == 0xDF) {
3526             *p++ = 0x1C;
3527             return p - output;
3528         }
3529         if (shift_state == 0 && wParam == VK_RETURN && term->cr_lf_return) {
3530             *p++ = '\r';
3531             *p++ = '\n';
3532             return p - output;
3533         }
3534
3535         /*
3536          * Next, all the keys that do tilde codes. (ESC '[' nn '~',
3537          * for integer decimal nn.)
3538          *
3539          * We also deal with the weird ones here. Linux VCs replace F1
3540          * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
3541          * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
3542          * respectively.
3543          */
3544         code = 0;
3545         switch (wParam) {
3546           case VK_F1:
3547             code = (keystate[VK_SHIFT] & 0x80 ? 23 : 11);
3548             break;
3549           case VK_F2:
3550             code = (keystate[VK_SHIFT] & 0x80 ? 24 : 12);
3551             break;
3552           case VK_F3:
3553             code = (keystate[VK_SHIFT] & 0x80 ? 25 : 13);
3554             break;
3555           case VK_F4:
3556             code = (keystate[VK_SHIFT] & 0x80 ? 26 : 14);
3557             break;
3558           case VK_F5:
3559             code = (keystate[VK_SHIFT] & 0x80 ? 28 : 15);
3560             break;
3561           case VK_F6:
3562             code = (keystate[VK_SHIFT] & 0x80 ? 29 : 17);
3563             break;
3564           case VK_F7:
3565             code = (keystate[VK_SHIFT] & 0x80 ? 31 : 18);
3566             break;
3567           case VK_F8:
3568             code = (keystate[VK_SHIFT] & 0x80 ? 32 : 19);
3569             break;
3570           case VK_F9:
3571             code = (keystate[VK_SHIFT] & 0x80 ? 33 : 20);
3572             break;
3573           case VK_F10:
3574             code = (keystate[VK_SHIFT] & 0x80 ? 34 : 21);
3575             break;
3576           case VK_F11:
3577             code = 23;
3578             break;
3579           case VK_F12:
3580             code = 24;
3581             break;
3582           case VK_F13:
3583             code = 25;
3584             break;
3585           case VK_F14:
3586             code = 26;
3587             break;
3588           case VK_F15:
3589             code = 28;
3590             break;
3591           case VK_F16:
3592             code = 29;
3593             break;
3594           case VK_F17:
3595             code = 31;
3596             break;
3597           case VK_F18:
3598             code = 32;
3599             break;
3600           case VK_F19:
3601             code = 33;
3602             break;
3603           case VK_F20:
3604             code = 34;
3605             break;
3606         }
3607         if ((shift_state&2) == 0) switch (wParam) {
3608           case VK_HOME:
3609             code = 1;
3610             break;
3611           case VK_INSERT:
3612             code = 2;
3613             break;
3614           case VK_DELETE:
3615             code = 3;
3616             break;
3617           case VK_END:
3618             code = 4;
3619             break;
3620           case VK_PRIOR:
3621             code = 5;
3622             break;
3623           case VK_NEXT:
3624             code = 6;
3625             break;
3626         }
3627         /* Reorder edit keys to physical order */
3628         if (cfg.funky_type == 3 && code <= 6)
3629             code = "\0\2\1\4\5\3\6"[code];
3630
3631         if (term->vt52_mode && code > 0 && code <= 6) {
3632             p += sprintf((char *) p, "\x1B%c", " HLMEIG"[code]);
3633             return p - output;
3634         }
3635
3636         if (cfg.funky_type == 5 &&     /* SCO function keys */
3637             code >= 11 && code <= 34) {
3638             char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
3639             int index = 0;
3640             switch (wParam) {
3641               case VK_F1: index = 0; break;
3642               case VK_F2: index = 1; break;
3643               case VK_F3: index = 2; break;
3644               case VK_F4: index = 3; break;
3645               case VK_F5: index = 4; break;
3646               case VK_F6: index = 5; break;
3647               case VK_F7: index = 6; break;
3648               case VK_F8: index = 7; break;
3649               case VK_F9: index = 8; break;
3650               case VK_F10: index = 9; break;
3651               case VK_F11: index = 10; break;
3652               case VK_F12: index = 11; break;
3653             }
3654             if (keystate[VK_SHIFT] & 0x80) index += 12;
3655             if (keystate[VK_CONTROL] & 0x80) index += 24;
3656             p += sprintf((char *) p, "\x1B[%c", codes[index]);
3657             return p - output;
3658         }
3659         if (cfg.funky_type == 5 &&     /* SCO small keypad */
3660             code >= 1 && code <= 6) {
3661             char codes[] = "HL.FIG";
3662             if (code == 3) {
3663                 *p++ = '\x7F';
3664             } else {
3665                 p += sprintf((char *) p, "\x1B[%c", codes[code-1]);
3666             }
3667             return p - output;
3668         }
3669         if ((term->vt52_mode || cfg.funky_type == 4) && code >= 11 && code <= 24) {
3670             int offt = 0;
3671             if (code > 15)
3672                 offt++;
3673             if (code > 21)
3674                 offt++;
3675             if (term->vt52_mode)
3676                 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11 - offt);
3677             else
3678                 p +=
3679                     sprintf((char *) p, "\x1BO%c", code + 'P' - 11 - offt);
3680             return p - output;
3681         }
3682         if (cfg.funky_type == 1 && code >= 11 && code <= 15) {
3683             p += sprintf((char *) p, "\x1B[[%c", code + 'A' - 11);
3684             return p - output;
3685         }
3686         if (cfg.funky_type == 2 && code >= 11 && code <= 14) {
3687             if (term->vt52_mode)
3688                 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11);
3689             else
3690                 p += sprintf((char *) p, "\x1BO%c", code + 'P' - 11);
3691             return p - output;
3692         }
3693         if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
3694             p += sprintf((char *) p, code == 1 ? "\x1B[H" : "\x1BOw");
3695             return p - output;
3696         }
3697         if (code) {
3698             p += sprintf((char *) p, "\x1B[%d~", code);
3699             return p - output;
3700         }
3701
3702         /*
3703          * Now the remaining keys (arrows and Keypad 5. Keypad 5 for
3704          * some reason seems to send VK_CLEAR to Windows...).
3705          */
3706         {
3707             char xkey = 0;
3708             switch (wParam) {
3709               case VK_UP:
3710                 xkey = 'A';
3711                 break;
3712               case VK_DOWN:
3713                 xkey = 'B';
3714                 break;
3715               case VK_RIGHT:
3716                 xkey = 'C';
3717                 break;
3718               case VK_LEFT:
3719                 xkey = 'D';
3720                 break;
3721               case VK_CLEAR:
3722                 xkey = 'G';
3723                 break;
3724             }
3725             if (xkey) {
3726                 if (term->vt52_mode)
3727                     p += sprintf((char *) p, "\x1B%c", xkey);
3728                 else {
3729                     int app_flg = (term->app_cursor_keys && !cfg.no_applic_c);
3730 #if 0
3731                     /*
3732                      * RDB: VT100 & VT102 manuals both state the
3733                      * app cursor keys only work if the app keypad
3734                      * is on.
3735                      * 
3736                      * SGT: That may well be true, but xterm
3737                      * disagrees and so does at least one
3738                      * application, so I've #if'ed this out and the
3739                      * behaviour is back to PuTTY's original: app
3740                      * cursor and app keypad are independently
3741                      * switchable modes. If anyone complains about
3742                      * _this_ I'll have to put in a configurable
3743                      * option.
3744                      */
3745                     if (!term->app_keypad_keys)
3746                         app_flg = 0;
3747 #endif
3748                     /* Useful mapping of Ctrl-arrows */
3749                     if (shift_state == 2)
3750                         app_flg = !app_flg;
3751
3752                     if (app_flg)
3753                         p += sprintf((char *) p, "\x1BO%c", xkey);
3754                     else
3755                         p += sprintf((char *) p, "\x1B[%c", xkey);
3756                 }
3757                 return p - output;
3758             }
3759         }
3760
3761         /*
3762          * Finally, deal with Return ourselves. (Win95 seems to
3763          * foul it up when Alt is pressed, for some reason.)
3764          */
3765         if (wParam == VK_RETURN) {     /* Return */
3766             *p++ = 0x0D;
3767             *p++ = 0;
3768             return -2;
3769         }
3770
3771         if (left_alt && wParam >= VK_NUMPAD0 && wParam <= VK_NUMPAD9)
3772             alt_sum = alt_sum * 10 + wParam - VK_NUMPAD0;
3773         else
3774             alt_sum = 0;
3775     }
3776
3777     /* Okay we've done everything interesting; let windows deal with 
3778      * the boring stuff */
3779     {
3780         BOOL capsOn=0;
3781
3782         /* helg: clear CAPS LOCK state if caps lock switches to cyrillic */
3783         if(cfg.xlat_capslockcyr && keystate[VK_CAPITAL] != 0) {
3784             capsOn= !left_alt;
3785             keystate[VK_CAPITAL] = 0;
3786         }
3787
3788         r = ToAsciiEx(wParam, scan, keystate, keys, 0, kbd_layout);
3789 #ifdef SHOW_TOASCII_RESULT
3790         if (r == 1 && !key_down) {
3791             if (alt_sum) {
3792                 if (in_utf(term) || ucsdata.dbcs_screenfont)
3793                     debug((", (U+%04x)", alt_sum));
3794                 else
3795                     debug((", LCH(%d)", alt_sum));
3796             } else {
3797                 debug((", ACH(%d)", keys[0]));
3798             }
3799         } else if (r > 0) {
3800             int r1;
3801             debug((", ASC("));
3802             for (r1 = 0; r1 < r; r1++) {
3803                 debug(("%s%d", r1 ? "," : "", keys[r1]));
3804             }
3805             debug((")"));
3806         }
3807 #endif
3808         if (r > 0) {
3809             WCHAR keybuf;
3810
3811             /*
3812              * Interrupt an ongoing paste. I'm not sure this is
3813              * sensible, but for the moment it's preferable to
3814              * having to faff about buffering things.
3815              */
3816             term_nopaste(term);
3817
3818             p = output;
3819             for (i = 0; i < r; i++) {
3820                 unsigned char ch = (unsigned char) keys[i];
3821
3822                 if (compose_state == 2 && (ch & 0x80) == 0 && ch > ' ') {
3823                     compose_char = ch;
3824                     compose_state++;
3825                     continue;
3826                 }
3827                 if (compose_state == 3 && (ch & 0x80) == 0 && ch > ' ') {
3828                     int nc;
3829                     compose_state = 0;
3830
3831                     if ((nc = check_compose(compose_char, ch)) == -1) {
3832                         MessageBeep(MB_ICONHAND);
3833                         return 0;
3834                     }
3835                     keybuf = nc;
3836                     term_seen_key_event(term);
3837                     luni_send(ldisc, &keybuf, 1, 1);
3838                     continue;
3839                 }
3840
3841                 compose_state = 0;
3842
3843                 if (!key_down) {
3844                     if (alt_sum) {
3845                         if (in_utf(term) || ucsdata.dbcs_screenfont) {
3846                             keybuf = alt_sum;
3847                             term_seen_key_event(term);
3848                             luni_send(ldisc, &keybuf, 1, 1);
3849                         } else {
3850                             ch = (char) alt_sum;
3851                             /*
3852                              * We need not bother about stdin
3853                              * backlogs here, because in GUI PuTTY
3854                              * we can't do anything about it
3855                              * anyway; there's no means of asking
3856                              * Windows to hold off on KEYDOWN
3857                              * messages. We _have_ to buffer
3858                              * everything we're sent.
3859                              */
3860                             term_seen_key_event(term);
3861                             ldisc_send(ldisc, &ch, 1, 1);
3862                         }
3863                         alt_sum = 0;
3864                     } else
3865                         term_seen_key_event(term);
3866                         lpage_send(ldisc, kbd_codepage, &ch, 1, 1);
3867                 } else {
3868                     if(capsOn && ch < 0x80) {
3869                         WCHAR cbuf[2];
3870                         cbuf[0] = 27;
3871                         cbuf[1] = xlat_uskbd2cyrllic(ch);
3872                         term_seen_key_event(term);
3873                         luni_send(ldisc, cbuf+!left_alt, 1+!!left_alt, 1);
3874                     } else {
3875                         char cbuf[2];
3876                         cbuf[0] = '\033';
3877                         cbuf[1] = ch;
3878                         term_seen_key_event(term);
3879                         lpage_send(ldisc, kbd_codepage,
3880                                    cbuf+!left_alt, 1+!!left_alt, 1);
3881                     }
3882                 }
3883                 show_mouseptr(0);
3884             }
3885
3886             /* This is so the ALT-Numpad and dead keys work correctly. */
3887             keys[0] = 0;
3888
3889             return p - output;
3890         }
3891         /* If we're definitly not building up an ALT-54321 then clear it */
3892         if (!left_alt)
3893             keys[0] = 0;
3894         /* If we will be using alt_sum fix the 256s */
3895         else if (keys[0] && (in_utf(term) || ucsdata.dbcs_screenfont))
3896             keys[0] = 10;
3897     }
3898
3899     /*
3900      * ALT alone may or may not want to bring up the System menu.
3901      * If it's not meant to, we return 0 on presses or releases of
3902      * ALT, to show that we've swallowed the keystroke. Otherwise
3903      * we return -1, which means Windows will give the keystroke
3904      * its default handling (i.e. bring up the System menu).
3905      */
3906     if (wParam == VK_MENU && !cfg.alt_only)
3907         return 0;
3908
3909     return -1;
3910 }
3911
3912 void request_paste(void *frontend)
3913 {
3914     /*
3915      * In Windows, pasting is synchronous: we can read the
3916      * clipboard with no difficulty, so request_paste() can just go
3917      * ahead and paste.
3918      */
3919     term_do_paste(term);
3920 }
3921
3922 void set_title(void *frontend, char *title)
3923 {
3924     sfree(window_name);
3925     window_name = snewn(1 + strlen(title), char);
3926     strcpy(window_name, title);
3927     if (cfg.win_name_always || !IsIconic(hwnd))
3928         SetWindowText(hwnd, title);
3929 }
3930
3931 void set_icon(void *frontend, char *title)
3932 {
3933     sfree(icon_name);
3934     icon_name = snewn(1 + strlen(title), char);
3935     strcpy(icon_name, title);
3936     if (!cfg.win_name_always && IsIconic(hwnd))
3937         SetWindowText(hwnd, title);
3938 }
3939
3940 void set_sbar(void *frontend, int total, int start, int page)
3941 {
3942     SCROLLINFO si;
3943
3944     if (is_full_screen() ? !cfg.scrollbar_in_fullscreen : !cfg.scrollbar)
3945         return;
3946
3947     si.cbSize = sizeof(si);
3948     si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
3949     si.nMin = 0;
3950     si.nMax = total - 1;
3951     si.nPage = page;
3952     si.nPos = start;
3953     if (hwnd)
3954         SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
3955 }
3956
3957 Context get_ctx(void *frontend)
3958 {
3959     HDC hdc;
3960     if (hwnd) {
3961         hdc = GetDC(hwnd);
3962         if (hdc && pal)
3963             SelectPalette(hdc, pal, FALSE);
3964         return hdc;
3965     } else
3966         return NULL;
3967 }
3968
3969 void free_ctx(Context ctx)
3970 {
3971     SelectPalette(ctx, GetStockObject(DEFAULT_PALETTE), FALSE);
3972     ReleaseDC(hwnd, ctx);
3973 }
3974
3975 static void real_palette_set(int n, int r, int g, int b)
3976 {
3977     if (pal) {
3978         logpal->palPalEntry[n].peRed = r;
3979         logpal->palPalEntry[n].peGreen = g;
3980         logpal->palPalEntry[n].peBlue = b;
3981         logpal->palPalEntry[n].peFlags = PC_NOCOLLAPSE;
3982         colours[n] = PALETTERGB(r, g, b);
3983         SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3984     } else
3985         colours[n] = RGB(r, g, b);
3986 }
3987
3988 void palette_set(void *frontend, int n, int r, int g, int b)
3989 {
3990     static const int first[21] = {
3991         0, 2, 4, 6, 8, 10, 12, 14,
3992         1, 3, 5, 7, 9, 11, 13, 15,
3993         16, 17, 18, 20, 22
3994     };
3995     real_palette_set(first[n], r, g, b);
3996     if (first[n] >= 18)
3997         real_palette_set(first[n] + 1, r, g, b);
3998     if (pal) {
3999         HDC hdc = get_ctx(frontend);
4000         UnrealizeObject(pal);
4001         RealizePalette(hdc);
4002         free_ctx(hdc);
4003     }
4004 }
4005
4006 void palette_reset(void *frontend)
4007 {
4008     int i;
4009
4010     for (i = 0; i < NCOLOURS; i++) {
4011         if (pal) {
4012             logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
4013             logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
4014             logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
4015             logpal->palPalEntry[i].peFlags = 0;
4016             colours[i] = PALETTERGB(defpal[i].rgbtRed,
4017                                     defpal[i].rgbtGreen,
4018                                     defpal[i].rgbtBlue);
4019         } else
4020             colours[i] = RGB(defpal[i].rgbtRed,
4021                              defpal[i].rgbtGreen, defpal[i].rgbtBlue);
4022     }
4023
4024     if (pal) {
4025         HDC hdc;
4026         SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
4027         hdc = get_ctx(frontend);
4028         RealizePalette(hdc);
4029         free_ctx(hdc);
4030     }
4031 }
4032
4033 void write_aclip(void *frontend, char *data, int len, int must_deselect)
4034 {
4035     HGLOBAL clipdata;
4036     void *lock;
4037
4038     clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len + 1);
4039     if (!clipdata)
4040         return;
4041     lock = GlobalLock(clipdata);
4042     if (!lock)
4043         return;
4044     memcpy(lock, data, len);
4045     ((unsigned char *) lock)[len] = 0;
4046     GlobalUnlock(clipdata);
4047
4048     if (!must_deselect)
4049         SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
4050
4051     if (OpenClipboard(hwnd)) {
4052         EmptyClipboard();
4053         SetClipboardData(CF_TEXT, clipdata);
4054         CloseClipboard();
4055     } else
4056         GlobalFree(clipdata);
4057
4058     if (!must_deselect)
4059         SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
4060 }
4061
4062 /*
4063  * Note: unlike write_aclip() this will not append a nul.
4064  */
4065 void write_clip(void *frontend, wchar_t * data, int len, int must_deselect)
4066 {
4067     HGLOBAL clipdata, clipdata2, clipdata3;
4068     int len2;
4069     void *lock, *lock2, *lock3;
4070
4071     len2 = WideCharToMultiByte(CP_ACP, 0, data, len, 0, 0, NULL, NULL);
4072
4073     clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE,
4074                            len * sizeof(wchar_t));
4075     clipdata2 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len2);
4076
4077     if (!clipdata || !clipdata2) {
4078         if (clipdata)
4079             GlobalFree(clipdata);
4080         if (clipdata2)
4081             GlobalFree(clipdata2);
4082         return;
4083     }
4084     if (!(lock = GlobalLock(clipdata)))
4085         return;
4086     if (!(lock2 = GlobalLock(clipdata2)))
4087         return;
4088
4089     memcpy(lock, data, len * sizeof(wchar_t));
4090     WideCharToMultiByte(CP_ACP, 0, data, len, lock2, len2, NULL, NULL);
4091
4092     if (cfg.rtf_paste) {
4093         wchar_t unitab[256];
4094         char *rtf = NULL;
4095         unsigned char *tdata = (unsigned char *)lock2;
4096         wchar_t *udata = (wchar_t *)lock;
4097         int rtflen = 0, uindex = 0, tindex = 0;
4098         int rtfsize = 0;
4099         int multilen, blen, alen, totallen, i;
4100         char before[16], after[4];
4101
4102         get_unitab(CP_ACP, unitab, 0);
4103
4104         rtfsize = 100 + strlen(cfg.font.name);
4105         rtf = snewn(rtfsize, char);
4106         sprintf(rtf, "{\\rtf1\\ansi%d{\\fonttbl\\f0\\fmodern %s;}\\f0",
4107                 GetACP(), cfg.font.name);
4108         rtflen = strlen(rtf);
4109
4110         /*
4111          * We want to construct a piece of RTF that specifies the
4112          * same Unicode text. To do this we will read back in
4113          * parallel from the Unicode data in `udata' and the
4114          * non-Unicode data in `tdata'. For each character in
4115          * `tdata' which becomes the right thing in `udata' when
4116          * looked up in `unitab', we just copy straight over from
4117          * tdata. For each one that doesn't, we must WCToMB it
4118          * individually and produce a \u escape sequence.
4119          * 
4120          * It would probably be more robust to just bite the bullet
4121          * and WCToMB each individual Unicode character one by one,
4122          * then MBToWC each one back to see if it was an accurate
4123          * translation; but that strikes me as a horrifying number
4124          * of Windows API calls so I want to see if this faster way
4125          * will work. If it screws up badly we can always revert to
4126          * the simple and slow way.
4127          */
4128         while (tindex < len2 && uindex < len &&
4129                tdata[tindex] && udata[uindex]) {
4130             if (tindex + 1 < len2 &&
4131                 tdata[tindex] == '\r' &&
4132                 tdata[tindex+1] == '\n') {
4133                 tindex++;
4134                 uindex++;
4135             }
4136             if (unitab[tdata[tindex]] == udata[uindex]) {
4137                 multilen = 1;
4138                 before[0] = '\0';
4139                 after[0] = '\0';
4140                 blen = alen = 0;
4141             } else {
4142                 multilen = WideCharToMultiByte(CP_ACP, 0, unitab+uindex, 1,
4143                                                NULL, 0, NULL, NULL);
4144                 if (multilen != 1) {
4145                     blen = sprintf(before, "{\\uc%d\\u%d", multilen,
4146                                    udata[uindex]);
4147                     alen = 1; strcpy(after, "}");
4148                 } else {
4149                     blen = sprintf(before, "\\u%d", udata[uindex]);
4150                     alen = 0; after[0] = '\0';
4151                 }
4152             }
4153             assert(tindex + multilen <= len2);
4154             totallen = blen + alen;
4155             for (i = 0; i < multilen; i++) {
4156                 if (tdata[tindex+i] == '\\' ||
4157                     tdata[tindex+i] == '{' ||
4158                     tdata[tindex+i] == '}')
4159                     totallen += 2;
4160                 else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A)
4161                     totallen += 6;     /* \par\r\n */
4162                 else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20)
4163                     totallen += 4;
4164                 else
4165                     totallen++;
4166             }
4167
4168             if (rtfsize < rtflen + totallen + 3) {
4169                 rtfsize = rtflen + totallen + 512;
4170                 rtf = sresize(rtf, rtfsize, char);
4171             }
4172
4173             strcpy(rtf + rtflen, before); rtflen += blen;
4174             for (i = 0; i < multilen; i++) {
4175                 if (tdata[tindex+i] == '\\' ||
4176                     tdata[tindex+i] == '{' ||
4177                     tdata[tindex+i] == '}') {
4178                     rtf[rtflen++] = '\\';
4179                     rtf[rtflen++] = tdata[tindex+i];
4180                 } else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A) {
4181                     rtflen += sprintf(rtf+rtflen, "\\par\r\n");
4182                 } else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20) {
4183                     rtflen += sprintf(rtf+rtflen, "\\'%02x", tdata[tindex+i]);
4184                 } else {
4185                     rtf[rtflen++] = tdata[tindex+i];
4186                 }
4187             }
4188             strcpy(rtf + rtflen, after); rtflen += alen;
4189
4190             tindex += multilen;
4191             uindex++;
4192         }
4193
4194         strcpy(rtf + rtflen, "}");
4195         rtflen += 2;
4196
4197         clipdata3 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, rtflen);
4198         if (clipdata3 && (lock3 = GlobalLock(clipdata3)) != NULL) {
4199             strcpy(lock3, rtf);
4200             GlobalUnlock(clipdata3);
4201         }
4202         sfree(rtf);
4203     } else
4204         clipdata3 = NULL;
4205
4206     GlobalUnlock(clipdata);
4207     GlobalUnlock(clipdata2);
4208
4209     if (!must_deselect)
4210         SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
4211
4212     if (OpenClipboard(hwnd)) {
4213         EmptyClipboard();
4214         SetClipboardData(CF_UNICODETEXT, clipdata);
4215         SetClipboardData(CF_TEXT, clipdata2);
4216         if (clipdata3)
4217             SetClipboardData(RegisterClipboardFormat(CF_RTF), clipdata3);
4218         CloseClipboard();
4219     } else {
4220         GlobalFree(clipdata);
4221         GlobalFree(clipdata2);
4222     }
4223
4224     if (!must_deselect)
4225         SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
4226 }
4227
4228 void get_clip(void *frontend, wchar_t ** p, int *len)
4229 {
4230     static HGLOBAL clipdata = NULL;
4231     static wchar_t *converted = 0;
4232     wchar_t *p2;
4233
4234     if (converted) {
4235         sfree(converted);
4236         converted = 0;
4237     }
4238     if (!p) {
4239         if (clipdata)
4240             GlobalUnlock(clipdata);
4241         clipdata = NULL;
4242         return;
4243     } else if (OpenClipboard(NULL)) {
4244         if ((clipdata = GetClipboardData(CF_UNICODETEXT))) {
4245             CloseClipboard();
4246             *p = GlobalLock(clipdata);
4247             if (*p) {
4248                 for (p2 = *p; *p2; p2++);
4249                 *len = p2 - *p;
4250                 return;
4251             }
4252         } else if ( (clipdata = GetClipboardData(CF_TEXT)) ) {
4253             char *s;
4254             int i;
4255             CloseClipboard();
4256             s = GlobalLock(clipdata);
4257             i = MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, 0, 0);
4258             *p = converted = snewn(i, wchar_t);
4259             MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, converted, i);
4260             *len = i - 1;
4261             return;
4262         } else
4263             CloseClipboard();
4264     }
4265
4266     *p = NULL;
4267     *len = 0;
4268 }
4269
4270 #if 0
4271 /*
4272  * Move `lines' lines from position `from' to position `to' in the
4273  * window.
4274  */
4275 void optimised_move(void *frontend, int to, int from, int lines)
4276 {
4277     RECT r;
4278     int min, max;
4279
4280     min = (to < from ? to : from);
4281     max = to + from - min;
4282
4283     r.left = offset_width;
4284     r.right = offset_width + term->cols * font_width;
4285     r.top = offset_height + min * font_height;
4286     r.bottom = offset_height + (max + lines) * font_height;
4287     ScrollWindow(hwnd, 0, (to - from) * font_height, &r, &r);
4288 }
4289 #endif
4290
4291 /*
4292  * Print a message box and perform a fatal exit.
4293  */
4294 void fatalbox(char *fmt, ...)
4295 {
4296     va_list ap;
4297     char stuff[200];
4298
4299     va_start(ap, fmt);
4300     vsprintf(stuff, fmt, ap);
4301     va_end(ap);
4302     MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
4303     cleanup_exit(1);
4304 }
4305
4306 /*
4307  * Print a modal (Really Bad) message box and perform a fatal exit.
4308  */
4309 void modalfatalbox(char *fmt, ...)
4310 {
4311     va_list ap;
4312     char stuff[200];
4313
4314     va_start(ap, fmt);
4315     vsprintf(stuff, fmt, ap);
4316     va_end(ap);
4317     MessageBox(hwnd, stuff, "PuTTY Fatal Error",
4318                MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
4319     cleanup_exit(1);
4320 }
4321
4322 /*
4323  * Manage window caption / taskbar flashing, if enabled.
4324  * 0 = stop, 1 = maintain, 2 = start
4325  */
4326 static void flash_window(int mode)
4327 {
4328     static long last_flash = 0;
4329     static int flashing = 0;
4330     if ((mode == 0) || (cfg.beep_ind == B_IND_DISABLED)) {
4331         /* stop */
4332         if (flashing) {
4333             FlashWindow(hwnd, FALSE);
4334             flashing = 0;
4335         }
4336
4337     } else if (mode == 2) {
4338         /* start */
4339         if (!flashing) {
4340             last_flash = GetTickCount();
4341             flashing = 1;
4342             FlashWindow(hwnd, TRUE);
4343         }
4344
4345     } else if ((mode == 1) && (cfg.beep_ind == B_IND_FLASH)) {
4346         /* maintain */
4347         if (flashing) {
4348             long now = GetTickCount();
4349             long fdiff = now - last_flash;
4350             if (fdiff < 0 || fdiff > 450) {
4351                 last_flash = now;
4352                 FlashWindow(hwnd, TRUE);        /* toggle */
4353             }
4354         }
4355     }
4356 }
4357
4358 /*
4359  * Beep.
4360  */
4361 void beep(void *frontend, int mode)
4362 {
4363     if (mode == BELL_DEFAULT) {
4364         /*
4365          * For MessageBeep style bells, we want to be careful of
4366          * timing, because they don't have the nice property of
4367          * PlaySound bells that each one cancels the previous
4368          * active one. So we limit the rate to one per 50ms or so.
4369          */
4370         static long lastbeep = 0;
4371         long beepdiff;
4372
4373         beepdiff = GetTickCount() - lastbeep;
4374         if (beepdiff >= 0 && beepdiff < 50)
4375             return;
4376         MessageBeep(MB_OK);
4377         /*
4378          * The above MessageBeep call takes time, so we record the
4379          * time _after_ it finishes rather than before it starts.
4380          */
4381         lastbeep = GetTickCount();
4382     } else if (mode == BELL_WAVEFILE) {
4383         if (!PlaySound(cfg.bell_wavefile.path, NULL,
4384                        SND_ASYNC | SND_FILENAME)) {
4385             char buf[sizeof(cfg.bell_wavefile) + 80];
4386             sprintf(buf, "Unable to play sound file\n%s\n"
4387                     "Using default sound instead", cfg.bell_wavefile);
4388             MessageBox(hwnd, buf, "PuTTY Sound Error",
4389                        MB_OK | MB_ICONEXCLAMATION);
4390             cfg.beep = BELL_DEFAULT;
4391         }
4392     }
4393     /* Otherwise, either visual bell or disabled; do nothing here */
4394     if (!term->has_focus) {
4395         flash_window(2);               /* start */
4396     }
4397 }
4398
4399 /*
4400  * Minimise or restore the window in response to a server-side
4401  * request.
4402  */
4403 void set_iconic(void *frontend, int iconic)
4404 {
4405     if (IsIconic(hwnd)) {
4406         if (!iconic)
4407             ShowWindow(hwnd, SW_RESTORE);
4408     } else {
4409         if (iconic)
4410             ShowWindow(hwnd, SW_MINIMIZE);
4411     }
4412 }
4413
4414 /*
4415  * Move the window in response to a server-side request.
4416  */
4417 void move_window(void *frontend, int x, int y)
4418 {
4419     if (cfg.resize_action == RESIZE_DISABLED || 
4420         cfg.resize_action == RESIZE_FONT ||
4421         IsZoomed(hwnd))
4422        return;
4423
4424     SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
4425 }
4426
4427 /*
4428  * Move the window to the top or bottom of the z-order in response
4429  * to a server-side request.
4430  */
4431 void set_zorder(void *frontend, int top)
4432 {
4433     if (cfg.alwaysontop)
4434         return;                        /* ignore */
4435     SetWindowPos(hwnd, top ? HWND_TOP : HWND_BOTTOM, 0, 0, 0, 0,
4436                  SWP_NOMOVE | SWP_NOSIZE);
4437 }
4438
4439 /*
4440  * Refresh the window in response to a server-side request.
4441  */
4442 void refresh_window(void *frontend)
4443 {
4444     InvalidateRect(hwnd, NULL, TRUE);
4445 }
4446
4447 /*
4448  * Maximise or restore the window in response to a server-side
4449  * request.
4450  */
4451 void set_zoomed(void *frontend, int zoomed)
4452 {
4453     if (IsZoomed(hwnd)) {
4454         if (!zoomed)
4455             ShowWindow(hwnd, SW_RESTORE);
4456     } else {
4457         if (zoomed)
4458             ShowWindow(hwnd, SW_MAXIMIZE);
4459     }
4460 }
4461
4462 /*
4463  * Report whether the window is iconic, for terminal reports.
4464  */
4465 int is_iconic(void *frontend)
4466 {
4467     return IsIconic(hwnd);
4468 }
4469
4470 /*
4471  * Report the window's position, for terminal reports.
4472  */
4473 void get_window_pos(void *frontend, int *x, int *y)
4474 {
4475     RECT r;
4476     GetWindowRect(hwnd, &r);
4477     *x = r.left;
4478     *y = r.top;
4479 }
4480
4481 /*
4482  * Report the window's pixel size, for terminal reports.
4483  */
4484 void get_window_pixels(void *frontend, int *x, int *y)
4485 {
4486     RECT r;
4487     GetWindowRect(hwnd, &r);
4488     *x = r.right - r.left;
4489     *y = r.bottom - r.top;
4490 }
4491
4492 /*
4493  * Return the window or icon title.
4494  */
4495 char *get_window_title(void *frontend, int icon)
4496 {
4497     return icon ? icon_name : window_name;
4498 }
4499
4500 /*
4501  * See if we're in full-screen mode.
4502  */
4503 int is_full_screen()
4504 {
4505     if (!IsZoomed(hwnd))
4506         return FALSE;
4507     if (GetWindowLong(hwnd, GWL_STYLE) & WS_CAPTION)
4508         return FALSE;
4509     return TRUE;
4510 }
4511
4512 /* Get the rect/size of a full screen window using the nearest available
4513  * monitor in multimon systems; default to something sensible if only
4514  * one monitor is present. */
4515 static int get_fullscreen_rect(RECT * ss)
4516 {
4517 #ifdef MONITOR_DEFAULTTONEAREST
4518         HMONITOR mon;
4519         MONITORINFO mi;
4520         mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
4521         mi.cbSize = sizeof(mi);
4522         GetMonitorInfo(mon, &mi);
4523
4524         /* structure copy */
4525         *ss = mi.rcMonitor;
4526         return TRUE;
4527 #else
4528 /* could also use code like this:
4529         ss->left = ss->top = 0;
4530         ss->right = GetSystemMetrics(SM_CXSCREEN);
4531         ss->bottom = GetSystemMetrics(SM_CYSCREEN);
4532 */ 
4533         return GetClientRect(GetDesktopWindow(), ss);
4534 #endif
4535 }
4536
4537
4538 /*
4539  * Go full-screen. This should only be called when we are already
4540  * maximised.
4541  */
4542 void make_full_screen()
4543 {
4544     DWORD style;
4545         RECT ss;
4546
4547     assert(IsZoomed(hwnd));
4548
4549         if (is_full_screen())
4550                 return;
4551         
4552     /* Remove the window furniture. */
4553     style = GetWindowLong(hwnd, GWL_STYLE);
4554     style &= ~(WS_CAPTION | WS_BORDER | WS_THICKFRAME);
4555     if (cfg.scrollbar_in_fullscreen)
4556         style |= WS_VSCROLL;
4557     else
4558         style &= ~WS_VSCROLL;
4559     SetWindowLong(hwnd, GWL_STYLE, style);
4560
4561     /* Resize ourselves to exactly cover the nearest monitor. */
4562         get_fullscreen_rect(&ss);
4563     SetWindowPos(hwnd, HWND_TOP, ss.left, ss.top,
4564                         ss.right - ss.left,
4565                         ss.bottom - ss.top,
4566                         SWP_FRAMECHANGED);
4567
4568     /* Tick the menu item in the System menu. */
4569     CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN,
4570                   MF_CHECKED);
4571 }
4572
4573 /*
4574  * Clear the full-screen attributes.
4575  */
4576 void clear_full_screen()
4577 {
4578     DWORD oldstyle, style;
4579
4580     /* Reinstate the window furniture. */
4581     style = oldstyle = GetWindowLong(hwnd, GWL_STYLE);
4582     style |= WS_CAPTION | WS_BORDER;
4583     if (cfg.resize_action == RESIZE_DISABLED)
4584         style &= ~WS_THICKFRAME;
4585     else
4586         style |= WS_THICKFRAME;
4587     if (cfg.scrollbar)
4588         style |= WS_VSCROLL;
4589     else
4590         style &= ~WS_VSCROLL;
4591     if (style != oldstyle) {
4592         SetWindowLong(hwnd, GWL_STYLE, style);
4593         SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
4594                      SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
4595                      SWP_FRAMECHANGED);
4596     }
4597
4598     /* Untick the menu item in the System menu. */
4599     CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN,
4600                   MF_UNCHECKED);
4601 }
4602
4603 /*
4604  * Toggle full-screen mode.
4605  */
4606 void flip_full_screen()
4607 {
4608     if (is_full_screen()) {
4609         ShowWindow(hwnd, SW_RESTORE);
4610     } else if (IsZoomed(hwnd)) {
4611         make_full_screen();
4612     } else {
4613         SendMessage(hwnd, WM_FULLSCR_ON_MAX, 0, 0);
4614         ShowWindow(hwnd, SW_MAXIMIZE);
4615     }
4616 }
4617
4618 void frontend_keypress(void *handle)
4619 {
4620     /*
4621      * Keypress termination in non-Close-On-Exit mode is not
4622      * currently supported in PuTTY proper, because the window
4623      * always has a perfectly good Close button anyway. So we do
4624      * nothing here.
4625      */
4626     return;
4627 }