]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - window.c
83e709e5b199f0d5ee92a1f8e6528cf0b0fd2854
[PuTTY.git] / window.c
1 #include <windows.h>
2 #include <commctrl.h>
3 #include <winsock.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <ctype.h>
7
8 #define PUTTY_DO_GLOBALS                       /* actually _define_ globals */
9 #include "putty.h"
10 #include "win_res.h"
11
12 #define IDM_SHOWLOG   0x0010
13 #define IDM_NEWSESS   0x0020
14 #define IDM_DUPSESS   0x0030
15 #define IDM_RECONF    0x0040
16 #define IDM_CLRSB     0x0050
17 #define IDM_RESET     0x0060
18 #define IDM_TEL_AYT   0x0070
19 #define IDM_TEL_BRK   0x0080
20 #define IDM_TEL_SYNCH 0x0090
21 #define IDM_TEL_EC    0x00a0
22 #define IDM_TEL_EL    0x00b0
23 #define IDM_TEL_GA    0x00c0
24 #define IDM_TEL_NOP   0x00d0
25 #define IDM_TEL_ABORT 0x00e0
26 #define IDM_TEL_AO    0x00f0
27 #define IDM_TEL_IP    0x0100
28 #define IDM_TEL_SUSP  0x0110
29 #define IDM_TEL_EOR   0x0120
30 #define IDM_TEL_EOF   0x0130
31 #define IDM_ABOUT     0x0140
32 #define IDM_SAVEDSESS 0x0150
33
34 #define IDM_SAVED_MIN 0x1000
35 #define IDM_SAVED_MAX 0x2000
36
37 #define WM_IGNORE_SIZE (WM_XUSER + 1)
38 #define WM_IGNORE_CLIP (WM_XUSER + 2)
39
40 static LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
41 static int TranslateKey(WPARAM wParam, LPARAM lParam, unsigned char *output);
42 static void cfgtopalette(void);
43 static void init_palette(void);
44 static void init_fonts(int);
45
46 static int extra_width, extra_height;
47
48 static int pending_netevent = 0;
49 static WPARAM pend_netevent_wParam = 0;
50 static LPARAM pend_netevent_lParam = 0;
51 static void enact_pending_netevent(void);
52
53 #define FONT_NORMAL 0
54 #define FONT_BOLD 1
55 #define FONT_UNDERLINE 2
56 #define FONT_BOLDUND 3
57 #define FONT_OEM 4
58 #define FONT_OEMBOLD 5
59 #define FONT_OEMBOLDUND 6
60 #define FONT_OEMUND 7
61 static HFONT fonts[8];
62 static enum {
63     BOLD_COLOURS, BOLD_SHADOW, BOLD_FONT
64 } bold_mode;
65 static enum {
66     UND_LINE, UND_FONT
67 } und_mode;
68 static int descent;
69
70 #define NCOLOURS 24
71 static COLORREF colours[NCOLOURS];
72 static HPALETTE pal;
73 static LPLOGPALETTE logpal;
74 static RGBTRIPLE defpal[NCOLOURS];
75
76 static HWND hwnd;
77
78 static int dbltime, lasttime, lastact;
79 static Mouse_Button lastbtn;
80
81 static char *window_name, *icon_name;
82
83 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) {
84     static char appname[] = "PuTTY";
85     WORD winsock_ver;
86     WSADATA wsadata;
87     WNDCLASS wndclass;
88     MSG msg;
89     int guess_width, guess_height;
90
91     putty_inst = inst;
92
93     winsock_ver = MAKEWORD(1, 1);
94     if (WSAStartup(winsock_ver, &wsadata)) {
95         MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
96                    MB_OK | MB_ICONEXCLAMATION);
97         return 1;
98     }
99     if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
100         MessageBox(NULL, "WinSock version is incompatible with 1.1",
101                    "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
102         WSACleanup();
103         return 1;
104     }
105     /* WISHLIST: maybe allow config tweaking even if winsock not present? */
106
107     InitCommonControls();
108
109     /*
110      * Process the command line.
111      */
112     {
113         char *p;
114
115         default_protocol = DEFAULT_PROTOCOL;
116         default_port = DEFAULT_PORT;
117
118         do_defaults(NULL);
119
120         p = cmdline;
121         while (*p && isspace(*p)) p++;
122
123         /*
124          * Process command line options first. Yes, this can be
125          * done better, and it will be as soon as I have the
126          * energy...
127          */
128         while (*p == '-') {
129             char *q = p + strcspn(p, " \t");
130             p++;
131             if (q == p + 3 &&
132                 tolower(p[0]) == 's' &&
133                 tolower(p[1]) == 's' &&
134                 tolower(p[2]) == 'h') {
135                 default_protocol = cfg.protocol = PROT_SSH;
136                 default_port = cfg.port = 22;
137             } else if (q == p + 3 &&
138                 tolower(p[0]) == 'l' &&
139                 tolower(p[1]) == 'o' &&
140                 tolower(p[2]) == 'g') {
141                 logfile = "putty.log";
142             }
143             p = q + strspn(q, " \t");
144         }
145
146         /*
147          * An initial @ means to activate a saved session.
148          */
149         if (*p == '@') {
150             do_defaults (p+1);
151             if (!*cfg.host && !do_config()) {
152                 WSACleanup();
153                 return 0;
154             }
155         } else if (*p == '&') {
156             /*
157              * An initial & means we've been given a command line
158              * containing the hex value of a HANDLE for a file
159              * mapping object, which we must then extract as a
160              * config.
161              */
162             HANDLE filemap;
163             Config *cp;
164             if (sscanf(p+1, "%p", &filemap) == 1 &&
165                 (cp = MapViewOfFile(filemap, FILE_MAP_READ,
166                                     0, 0, sizeof(Config))) != NULL) {
167                 cfg = *cp;
168                 UnmapViewOfFile(cp);
169                 CloseHandle(filemap);
170             } else if (!do_config()) {
171                 WSACleanup();
172                 return 0;
173             }
174         } else if (*p) {
175             char *q = p;
176             while (*p && !isspace(*p)) p++;
177             if (*p)
178                 *p++ = '\0';
179             strncpy (cfg.host, q, sizeof(cfg.host)-1);
180             cfg.host[sizeof(cfg.host)-1] = '\0';
181             while (*p && isspace(*p)) p++;
182             if (*p)
183                 cfg.port = atoi(p);
184             else
185                 cfg.port = -1;
186         } else {
187             if (!do_config()) {
188                 WSACleanup();
189                 return 0;
190             }
191         }
192     }
193
194     /*
195      * Select protocol. This is farmed out into a table in a
196      * separate file to enable an ssh-free variant.
197      */
198     {
199         int i;
200         back = NULL;
201         for (i = 0; backends[i].backend != NULL; i++)
202             if (backends[i].protocol == cfg.protocol) {
203                 back = backends[i].backend;
204                 break;
205             }
206         if (back == NULL) {
207             MessageBox(NULL, "Unsupported protocol number found",
208                        "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
209             WSACleanup();
210             return 1;
211         }
212     }
213
214     ldisc = (cfg.ldisc_term ? &ldisc_term : &ldisc_simple);
215
216     if (!prev) {
217         wndclass.style         = 0;
218         wndclass.lpfnWndProc   = WndProc;
219         wndclass.cbClsExtra    = 0;
220         wndclass.cbWndExtra    = 0;
221         wndclass.hInstance     = inst;
222         wndclass.hIcon         = LoadIcon (inst,
223                                            MAKEINTRESOURCE(IDI_MAINICON));
224         wndclass.hCursor       = LoadCursor (NULL, IDC_IBEAM);
225         wndclass.hbrBackground = GetStockObject (BLACK_BRUSH);
226         wndclass.lpszMenuName  = NULL;
227         wndclass.lpszClassName = appname;
228
229         RegisterClass (&wndclass);
230     }
231
232     hwnd = NULL;
233
234     savelines = cfg.savelines;
235     term_init();
236
237     cfgtopalette();
238
239     /*
240      * Guess some defaults for the window size. This all gets
241      * updated later, so we don't really care too much. However, we
242      * do want the font width/height guesses to correspond to a
243      * large font rather than a small one...
244      */
245     
246     font_width = 10;
247     font_height = 20;
248     extra_width = 25;
249     extra_height = 28;
250     term_size (cfg.height, cfg.width, cfg.savelines);
251     guess_width = extra_width + font_width * cols;
252     guess_height = extra_height + font_height * rows;
253     {
254         RECT r;
255         HWND w = GetDesktopWindow();
256         GetWindowRect (w, &r);
257         if (guess_width > r.right - r.left)
258             guess_width = r.right - r.left;
259         if (guess_height > r.bottom - r.top)
260             guess_height = r.bottom - r.top;
261     }
262
263     hwnd = CreateWindow (appname, appname,
264                          WS_OVERLAPPEDWINDOW | WS_VSCROLL,
265                          CW_USEDEFAULT, CW_USEDEFAULT,
266                          guess_width, guess_height,
267                          NULL, NULL, inst, NULL);
268
269     /*
270      * Initialise the fonts, simultaneously correcting the guesses
271      * for font_{width,height}.
272      */
273     bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
274     und_mode = UND_FONT;
275     init_fonts(0);
276
277     /*
278      * Correct the guesses for extra_{width,height}.
279      */
280     {
281         RECT cr, wr;
282         GetWindowRect (hwnd, &wr);
283         GetClientRect (hwnd, &cr);
284         extra_width = wr.right - wr.left - cr.right + cr.left;
285         extra_height = wr.bottom - wr.top - cr.bottom + cr.top;
286     }
287
288     /*
289      * Resize the window, now we know what size we _really_ want it
290      * to be.
291      */
292     guess_width = extra_width + font_width * cols;
293     guess_height = extra_height + font_height * rows;
294     SendMessage (hwnd, WM_IGNORE_SIZE, 0, 0);
295     SetWindowPos (hwnd, NULL, 0, 0, guess_width, guess_height,
296                   SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
297
298     /*
299      * Initialise the scroll bar.
300      */
301     {
302         SCROLLINFO si;
303
304         si.cbSize = sizeof(si);
305         si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS | SIF_DISABLENOSCROLL;
306         si.nMin = 0;
307         si.nMax = rows-1;
308         si.nPage = rows;
309         si.nPos = 0;
310         SetScrollInfo (hwnd, SB_VERT, &si, FALSE);
311     }
312
313     /*
314      * Start up the telnet connection.
315      */
316     {
317         char *error;
318         char msg[1024];
319         char *realhost;
320
321         error = back->init (hwnd, cfg.host, cfg.port, &realhost);
322         if (error) {
323             sprintf(msg, "Unable to open connection:\n%s", error);
324             MessageBox(NULL, msg, "PuTTY Error", MB_ICONERROR | MB_OK);
325             return 0;
326         }
327         window_name = icon_name = NULL;
328         sprintf(msg, "%s - PuTTY", realhost);
329         set_title (msg);
330         set_icon (msg);
331     }
332
333     session_closed = FALSE;
334
335     /*
336      * Set up the input and output buffers.
337      */
338     inbuf_reap = inbuf_head = 0;
339     outbuf_reap = outbuf_head = 0;
340
341     /* 
342      * Choose unscroll method
343      */
344     unscroll_event = US_DISP;
345
346     /*
347      * Prepare the mouse handler.
348      */
349     lastact = MA_NOTHING;
350     lastbtn = MB_NOTHING;
351     dbltime = GetDoubleClickTime();
352
353     /*
354      * Set up the session-control options on the system menu.
355      */
356     {
357         HMENU m = GetSystemMenu (hwnd, FALSE);
358         HMENU p,s;
359         int i;
360
361         AppendMenu (m, MF_SEPARATOR, 0, 0);
362         if (cfg.protocol == PROT_TELNET) {
363             p = CreateMenu();
364             AppendMenu (p, MF_ENABLED, IDM_TEL_AYT, "Are You There");
365             AppendMenu (p, MF_ENABLED, IDM_TEL_BRK, "Break");
366             AppendMenu (p, MF_ENABLED, IDM_TEL_SYNCH, "Synch");
367             AppendMenu (p, MF_SEPARATOR, 0, 0);
368             AppendMenu (p, MF_ENABLED, IDM_TEL_EC, "Erase Character");
369             AppendMenu (p, MF_ENABLED, IDM_TEL_EL, "Erase Line");
370             AppendMenu (p, MF_ENABLED, IDM_TEL_GA, "Go Ahead");
371             AppendMenu (p, MF_ENABLED, IDM_TEL_NOP, "No Operation");
372             AppendMenu (p, MF_SEPARATOR, 0, 0);
373             AppendMenu (p, MF_ENABLED, IDM_TEL_ABORT, "Abort Process");
374             AppendMenu (p, MF_ENABLED, IDM_TEL_AO, "Abort Output");
375             AppendMenu (p, MF_ENABLED, IDM_TEL_IP, "Interrupt Process");
376             AppendMenu (p, MF_ENABLED, IDM_TEL_SUSP, "Suspend Process");
377             AppendMenu (p, MF_SEPARATOR, 0, 0);
378             AppendMenu (p, MF_ENABLED, IDM_TEL_EOR, "End Of Record");
379             AppendMenu (p, MF_ENABLED, IDM_TEL_EOF, "End Of File");
380             AppendMenu (m, MF_POPUP | MF_ENABLED, (UINT) p, "Telnet Command");
381             AppendMenu (m, MF_SEPARATOR, 0, 0);
382         }
383         AppendMenu (m, MF_ENABLED, IDM_SHOWLOG, "&Event Log");
384         AppendMenu (m, MF_SEPARATOR, 0, 0);
385         AppendMenu (m, MF_ENABLED, IDM_NEWSESS, "Ne&w Session");
386         AppendMenu (m, MF_ENABLED, IDM_DUPSESS, "&Duplicate Session");
387         s = CreateMenu();
388         get_sesslist(TRUE);
389         for (i = 1 ; i < ((nsessions < 256) ? nsessions : 256) ; i++)
390           AppendMenu (s, MF_ENABLED, IDM_SAVED_MIN + (16 * i) , sessions[i]);
391         AppendMenu (m, MF_POPUP | MF_ENABLED, (UINT) s, "Sa&ved Sessions");
392         AppendMenu (m, MF_ENABLED, IDM_RECONF, "Chan&ge Settings");
393         AppendMenu (m, MF_SEPARATOR, 0, 0);
394         AppendMenu (m, MF_ENABLED, IDM_CLRSB, "C&lear Scrollback");
395         AppendMenu (m, MF_ENABLED, IDM_RESET, "Rese&t Terminal");
396         AppendMenu (m, MF_SEPARATOR, 0, 0);
397         AppendMenu (m, MF_ENABLED, IDM_ABOUT, "&About PuTTY");
398     }
399
400     /*
401      * Finally show the window!
402      */
403     ShowWindow (hwnd, show);
404
405     /*
406      * Set the palette up.
407      */
408     pal = NULL;
409     logpal = NULL;
410     init_palette();
411
412     has_focus = (GetForegroundWindow() == hwnd);
413     UpdateWindow (hwnd);
414
415     {
416         int timer_id = 0, long_timer = 0;
417
418         while (GetMessage (&msg, NULL, 0, 0) == 1) {
419             /* Sometimes DispatchMessage calls routines that use their own
420              * GetMessage loop, setup this timer so we get some control back.
421              *
422              * Also call term_update() from the timer so that if the host
423              * is sending data flat out we still do redraws.
424              */
425             if(timer_id && long_timer) {
426                 KillTimer(hwnd, timer_id);
427                 long_timer = timer_id = 0;
428             }
429             if(!timer_id)
430                 timer_id = SetTimer(hwnd, 1, 20, NULL);
431             DispatchMessage (&msg);
432
433             /* This is too fast, but I'll leave it for now 'cause it shows
434              * how often term_update is called (far too often at times!)
435              */
436             term_blink(0);
437
438             /* If there's nothing new in the queue then we can do everything
439              * we've delayed, reading the socket, writing, and repainting
440              * the window.
441              */
442             if (!PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE)) {
443                 if (pending_netevent) {
444                     enact_pending_netevent();
445
446                     term_blink(1);
447                 }
448             } else continue;
449             if (!PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE)) {
450                 if (timer_id) {
451                     KillTimer(hwnd, timer_id);
452                     timer_id = 0;
453                 }
454                 if (inbuf_reap != inbuf_head)
455                     term_out();
456                 term_update();
457                 timer_id = SetTimer(hwnd, 1, 500, NULL);
458                 long_timer = 1;
459             }
460         }
461     }
462
463     /*
464      * Clean up.
465      */
466     {
467         int i;
468         for (i=0; i<8; i++)
469             if (fonts[i])
470                 DeleteObject(fonts[i]);
471     }
472     sfree(logpal);
473     if (pal)
474         DeleteObject(pal);
475     WSACleanup();
476
477     if (cfg.protocol == PROT_SSH)
478         random_save_seed();
479
480     return msg.wParam;
481 }
482
483 /*
484  * Actually do the job requested by a WM_NETEVENT
485  */
486 static void enact_pending_netevent(void) {
487     int i;
488     pending_netevent = FALSE;
489     i = back->msg (pend_netevent_wParam, pend_netevent_lParam);
490
491     if (i < 0) {
492         char buf[1024];
493         switch (WSABASEERR + (-i) % 10000) {
494           case WSAECONNRESET:
495             sprintf(buf, "Connection reset by peer");
496             break;
497           default:
498             sprintf(buf, "Unexpected network error %d", -i);
499             break;
500         }
501         MessageBox(hwnd, buf, "PuTTY Fatal Error",
502                    MB_ICONERROR | MB_OK);
503         PostQuitMessage(1);
504     } else if (i == 0) {
505         if (cfg.close_on_exit)
506             PostQuitMessage(0);
507         else {
508             session_closed = TRUE;
509             MessageBox(hwnd, "Connection closed by remote host",
510                        "PuTTY", MB_OK | MB_ICONINFORMATION);
511             SetWindowText (hwnd, "PuTTY (inactive)");
512         }
513     }
514 }
515
516 /*
517  * Copy the colour palette from the configuration data into defpal.
518  * This is non-trivial because the colour indices are different.
519  */
520 static void cfgtopalette(void) {
521     int i;
522     static const int ww[] = {
523         6, 7, 8, 9, 10, 11, 12, 13,
524         14, 15, 16, 17, 18, 19, 20, 21,
525         0, 1, 2, 3, 4, 4, 5, 5
526     };
527
528     for (i=0; i<24; i++) {
529         int w = ww[i];
530         defpal[i].rgbtRed = cfg.colours[w][0];
531         defpal[i].rgbtGreen = cfg.colours[w][1];
532         defpal[i].rgbtBlue = cfg.colours[w][2];
533     }
534 }
535
536 /*
537  * Set up the colour palette.
538  */
539 static void init_palette(void) {
540     int i;
541     HDC hdc = GetDC (hwnd);
542     if (hdc) {
543         if (cfg.try_palette &&
544             GetDeviceCaps (hdc, RASTERCAPS) & RC_PALETTE) {
545             logpal = smalloc(sizeof(*logpal)
546                              - sizeof(logpal->palPalEntry)
547                              + NCOLOURS * sizeof(PALETTEENTRY));
548             logpal->palVersion = 0x300;
549             logpal->palNumEntries = NCOLOURS;
550             for (i = 0; i < NCOLOURS; i++) {
551                 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
552                 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
553                 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
554                 logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
555             }
556             pal = CreatePalette (logpal);
557             if (pal) {
558                 SelectPalette (hdc, pal, FALSE);
559                 RealizePalette (hdc);
560                 SelectPalette (hdc, GetStockObject (DEFAULT_PALETTE),
561                                FALSE);
562             }
563         }
564         ReleaseDC (hwnd, hdc);
565     }
566     if (pal)
567         for (i=0; i<NCOLOURS; i++)
568             colours[i] = PALETTERGB(defpal[i].rgbtRed,
569                                     defpal[i].rgbtGreen,
570                                     defpal[i].rgbtBlue);
571     else
572         for(i=0; i<NCOLOURS; i++)
573             colours[i] = RGB(defpal[i].rgbtRed,
574                              defpal[i].rgbtGreen,
575                              defpal[i].rgbtBlue);
576 }
577
578 /*
579  * Initialise all the fonts we will need. There may be as many as
580  * eight or as few as one. We also:
581  *
582  * - check the font width and height, correcting our guesses if
583  *   necessary.
584  *
585  * - verify that the bold font is the same width as the ordinary
586  *   one, and engage shadow bolding if not.
587  * 
588  * - verify that the underlined font is the same width as the
589  *   ordinary one (manual underlining by means of line drawing can
590  *   be done in a pinch).
591  */
592 static void init_fonts(int pick_width) {
593     TEXTMETRIC tm;
594     int i;
595     int fsize[8];
596     HDC hdc;
597     int fw_dontcare, fw_bold;
598     int firstchar = ' ';
599
600 #ifdef CHECKOEMFONT
601 font_messup:
602 #endif
603     for (i=0; i<8; i++)
604         fonts[i] = NULL;
605
606     if (cfg.fontisbold) {
607         fw_dontcare = FW_BOLD;
608         fw_bold = FW_BLACK;
609    } else {
610         fw_dontcare = FW_DONTCARE;
611         fw_bold = FW_BOLD;
612     }
613
614     hdc = GetDC(hwnd);
615
616     font_height = cfg.fontheight;
617     font_width = pick_width;
618
619 #define f(i,c,w,u) \
620     fonts[i] = CreateFont (font_height, font_width, 0, 0, w, FALSE, u, FALSE, \
621                            c, OUT_DEFAULT_PRECIS, \
622                            CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, \
623                            FIXED_PITCH | FF_DONTCARE, cfg.font)
624
625     if (cfg.vtmode != VT_OEMONLY) {
626         f(FONT_NORMAL, cfg.fontcharset, fw_dontcare, FALSE);
627
628         SelectObject (hdc, fonts[FONT_NORMAL]);
629         GetTextMetrics(hdc, &tm); 
630         font_height = tm.tmHeight;
631         font_width = tm.tmAveCharWidth;
632
633         f(FONT_UNDERLINE, cfg.fontcharset, fw_dontcare, TRUE);
634
635         if (bold_mode == BOLD_FONT) {
636             f(FONT_BOLD, cfg.fontcharset, fw_bold, FALSE);
637             f(FONT_BOLDUND, cfg.fontcharset, fw_bold, TRUE);
638         }
639
640         if (cfg.vtmode == VT_OEMANSI) {
641             f(FONT_OEM, OEM_CHARSET, fw_dontcare, FALSE);
642             f(FONT_OEMUND, OEM_CHARSET, fw_dontcare, TRUE);
643
644             if (bold_mode == BOLD_FONT) {
645                 f(FONT_OEMBOLD, OEM_CHARSET, fw_bold, FALSE);
646                 f(FONT_OEMBOLDUND, OEM_CHARSET, fw_bold, TRUE);
647             }
648         }
649     }
650     else
651     {
652         f(FONT_OEM, cfg.fontcharset, fw_dontcare, FALSE);
653
654         SelectObject (hdc, fonts[FONT_OEM]);
655         GetTextMetrics(hdc, &tm); 
656         font_height = tm.tmHeight;
657         font_width = tm.tmAveCharWidth;
658
659         f(FONT_OEMUND, cfg.fontcharset, fw_dontcare, TRUE);
660
661         if (bold_mode == BOLD_FONT) {
662             f(FONT_BOLD, cfg.fontcharset, fw_bold, FALSE);
663             f(FONT_BOLDUND, cfg.fontcharset, fw_bold, TRUE);
664         }
665     }
666 #undef f
667
668     descent = tm.tmAscent + 1;
669     if (descent >= font_height)
670         descent = font_height - 1;
671     firstchar = tm.tmFirstChar;
672
673     for (i=0; i<8; i++) {
674         if (fonts[i]) {
675             if (SelectObject (hdc, fonts[i]) &&
676                 GetTextMetrics(hdc, &tm) )
677                  fsize[i] = tm.tmAveCharWidth + 256 * tm.tmHeight;
678             else fsize[i] = -i;
679         }
680         else fsize[i] = -i;
681     }
682
683     ReleaseDC (hwnd, hdc);
684
685     /* ... This is wrong in OEM only mode */
686     if (fsize[FONT_UNDERLINE] != fsize[FONT_NORMAL] ||
687         (bold_mode == BOLD_FONT &&
688          fsize[FONT_BOLDUND] != fsize[FONT_BOLD])) {
689         und_mode = UND_LINE;
690         DeleteObject (fonts[FONT_UNDERLINE]);
691         if (bold_mode == BOLD_FONT)
692             DeleteObject (fonts[FONT_BOLDUND]);
693     }
694
695     if (bold_mode == BOLD_FONT &&
696         fsize[FONT_BOLD] != fsize[FONT_NORMAL]) {
697         bold_mode = BOLD_SHADOW;
698         DeleteObject (fonts[FONT_BOLD]);
699         if (und_mode == UND_FONT)
700             DeleteObject (fonts[FONT_BOLDUND]);
701     }
702
703 #ifdef CHECKOEMFONT
704     /* With the fascist font painting it doesn't matter if the linedraw font
705      * isn't exactly the right size anymore so we don't have to check this.
706      */
707     if (cfg.vtmode == VT_OEMANSI && fsize[FONT_OEM] != fsize[FONT_NORMAL] ) {
708         if( cfg.fontcharset == OEM_CHARSET )
709         {
710             MessageBox(NULL, "The OEM and ANSI versions of this font are\n"
711                    "different sizes. Using OEM-only mode instead",
712                    "Font Size Mismatch", MB_ICONINFORMATION | MB_OK);
713             cfg.vtmode = VT_OEMONLY;
714         }
715         else if( firstchar < ' ' )
716         {
717             MessageBox(NULL, "The OEM and ANSI versions of this font are\n"
718                    "different sizes. Using XTerm mode instead",
719                    "Font Size Mismatch", MB_ICONINFORMATION | MB_OK);
720             cfg.vtmode = VT_XWINDOWS;
721         }
722         else
723         {
724             MessageBox(NULL, "The OEM and ANSI versions of this font are\n"
725                    "different sizes. Using ISO8859-1 mode instead",
726                    "Font Size Mismatch", MB_ICONINFORMATION | MB_OK);
727             cfg.vtmode = VT_POORMAN;
728         }
729
730         for (i=0; i<8; i++)
731             if (fonts[i])
732                 DeleteObject (fonts[i]);
733         goto font_messup;
734     }
735 #endif
736 }
737
738 void request_resize (int w, int h, int refont) {
739     int width, height;
740     
741 #ifdef CHECKOEMFONT
742     /* Don't do this in OEMANSI, you may get disable messages */
743     if (refont && w != cols && (cols==80 || cols==132)
744           && cfg.vtmode != VT_OEMANSI)
745 #else
746     if (refont && w != cols && (cols==80 || cols==132))
747 #endif
748     {
749        /* If font width too big for screen should we shrink the font more ? */
750         if (w==132)
751             font_width = ((font_width*cols+w/2)/w);
752         else
753             font_width = 0;
754         {
755             int i;
756             for (i=0; i<8; i++)
757                 if (fonts[i])
758                     DeleteObject(fonts[i]);
759         }
760         bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
761         und_mode = UND_FONT;
762         init_fonts(font_width);
763     }
764
765     width = extra_width + font_width * w;
766     height = extra_height + font_height * h;
767
768     SetWindowPos (hwnd, NULL, 0, 0, width, height,
769                   SWP_NOACTIVATE | SWP_NOCOPYBITS |
770                   SWP_NOMOVE | SWP_NOZORDER);
771 }
772
773 static void click (Mouse_Button b, int x, int y) {
774     int thistime = GetMessageTime();
775
776     if (lastbtn == b && thistime - lasttime < dbltime) {
777         lastact = (lastact == MA_CLICK ? MA_2CLK :
778                    lastact == MA_2CLK ? MA_3CLK :
779                    lastact == MA_3CLK ? MA_CLICK : MA_NOTHING);
780     } else {
781         lastbtn = b;
782         lastact = MA_CLICK;
783     }
784     if (lastact != MA_NOTHING)
785         term_mouse (b, lastact, x, y);
786     lasttime = thistime;
787 }
788
789 static LRESULT CALLBACK WndProc (HWND hwnd, UINT message,
790                                  WPARAM wParam, LPARAM lParam) {
791     HDC hdc;
792     static int ignore_size = FALSE;
793     static int ignore_clip = FALSE;
794     static int just_reconfigged = FALSE;
795
796     switch (message) {
797       case WM_TIMER:
798         if (pending_netevent)
799             enact_pending_netevent();
800         if (inbuf_reap != inbuf_head)
801             term_out();
802         term_update();
803         return 0;
804       case WM_CREATE:
805         break;
806       case WM_CLOSE:
807         if (!cfg.warn_on_close || session_closed ||
808             MessageBox(hwnd, "Are you sure you want to close this session?",
809                        "PuTTY Exit Confirmation",
810                        MB_ICONWARNING | MB_OKCANCEL) == IDOK)
811             DestroyWindow(hwnd);
812         return 0;
813       case WM_DESTROY:
814         PostQuitMessage (0);
815         return 0;
816       case WM_SYSCOMMAND:
817         switch (wParam & ~0xF) {       /* low 4 bits reserved to Windows */
818           case IDM_SHOWLOG:
819             showeventlog(hwnd);
820             break;
821           case IDM_NEWSESS:
822           case IDM_DUPSESS:
823           case IDM_SAVEDSESS:
824             {
825                 char b[2048];
826                 char c[30], *cl;
827                 int freecl = FALSE;
828                 STARTUPINFO si;
829                 PROCESS_INFORMATION pi;
830                 HANDLE filemap = NULL;
831
832                 if (wParam == IDM_DUPSESS) {
833                     /*
834                      * Allocate a file-mapping memory chunk for the
835                      * config structure.
836                      */
837                     SECURITY_ATTRIBUTES sa;
838                     Config *p;
839
840                     sa.nLength = sizeof(sa);
841                     sa.lpSecurityDescriptor = NULL;
842                     sa.bInheritHandle = TRUE;
843                     filemap = CreateFileMapping((HANDLE)0xFFFFFFFF,
844                                                 &sa,
845                                                 PAGE_READWRITE,
846                                                 0,
847                                                 sizeof(Config),
848                                                 NULL);
849                     if (filemap) {
850                         p = (Config *)MapViewOfFile(filemap,
851                                                     FILE_MAP_WRITE,
852                                                     0, 0, sizeof(Config));
853                         if (p) {
854                             *p = cfg;  /* structure copy */
855                             UnmapViewOfFile(p);
856                         }
857                     }
858                     sprintf(c, "putty &%p", filemap);
859                     cl = c;
860                 } else if (wParam == IDM_SAVEDSESS) {
861                     char *session = sessions[(lParam - IDM_SAVED_MIN) / 16];
862                     cl = malloc(16 + strlen(session)); /* 8, but play safe */
863                     if (!cl)
864                         cl = NULL;     /* not a very important failure mode */
865                     else {
866                         sprintf(cl, "putty @%s", session);
867                         freecl = TRUE;
868                     }
869                 } else
870                     cl = NULL;
871
872                 GetModuleFileName (NULL, b, sizeof(b)-1);
873                 si.cb = sizeof(si);
874                 si.lpReserved = NULL;
875                 si.lpDesktop = NULL;
876                 si.lpTitle = NULL;
877                 si.dwFlags = 0;
878                 si.cbReserved2 = 0;
879                 si.lpReserved2 = NULL;
880                 CreateProcess (b, cl, NULL, NULL, TRUE,
881                                NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
882
883                 if (filemap)
884                     CloseHandle(filemap);
885                 if (freecl)
886                     free(cl);
887             }
888             break;
889           case IDM_RECONF:
890             if (!do_reconfig(hwnd))
891                 break;
892             just_reconfigged = TRUE;
893             {
894                 int i;
895                 for (i=0; i<8; i++)
896                     if (fonts[i])
897                         DeleteObject(fonts[i]);
898             }
899             bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
900             und_mode = UND_FONT;
901             init_fonts(0);
902             sfree(logpal);
903             /* Telnet will change local echo -> remote if the remote asks */
904             if (cfg.protocol != PROT_TELNET)
905                 ldisc = (cfg.ldisc_term ? &ldisc_term : &ldisc_simple);
906             if (pal)
907                 DeleteObject(pal);
908             logpal = NULL;
909             pal = NULL;
910             cfgtopalette();
911             init_palette();
912             term_size(cfg.height, cfg.width, cfg.savelines);
913             InvalidateRect(hwnd, NULL, TRUE);
914             SetWindowPos (hwnd, NULL, 0, 0,
915                           extra_width + font_width * cfg.width,
916                           extra_height + font_height * cfg.height,
917                           SWP_NOACTIVATE | SWP_NOCOPYBITS |
918                           SWP_NOMOVE | SWP_NOZORDER);
919             if (IsIconic(hwnd)) {
920                 SetWindowText (hwnd,
921                                cfg.win_name_always ? window_name : icon_name);
922             }
923             break;
924           case IDM_CLRSB:
925             term_clrsb();
926             break;
927           case IDM_RESET:
928             term_pwron();
929             break;
930           case IDM_TEL_AYT: back->special (TS_AYT); break;
931           case IDM_TEL_BRK: back->special (TS_BRK); break;
932           case IDM_TEL_SYNCH: back->special (TS_SYNCH); break;
933           case IDM_TEL_EC: back->special (TS_EC); break;
934           case IDM_TEL_EL: back->special (TS_EL); break;
935           case IDM_TEL_GA: back->special (TS_GA); break;
936           case IDM_TEL_NOP: back->special (TS_NOP); break;
937           case IDM_TEL_ABORT: back->special (TS_ABORT); break;
938           case IDM_TEL_AO: back->special (TS_AO); break;
939           case IDM_TEL_IP: back->special (TS_IP); break;
940           case IDM_TEL_SUSP: back->special (TS_SUSP); break;
941           case IDM_TEL_EOR: back->special (TS_EOR); break;
942           case IDM_TEL_EOF: back->special (TS_EOF); break;
943           case IDM_ABOUT:
944             showabout (hwnd);
945             break;
946         default:
947           if (wParam >= IDM_SAVED_MIN && wParam <= IDM_SAVED_MAX) {
948             SendMessage(hwnd, WM_SYSCOMMAND, IDM_SAVEDSESS, wParam);
949           }
950         }
951         break;
952
953 #define X_POS(l) ((int)(short)LOWORD(l))
954 #define Y_POS(l) ((int)(short)HIWORD(l))
955
956 #define TO_CHR_X(x) (((x)<0 ? (x)-font_width+1 : (x)) / font_width)
957 #define TO_CHR_Y(y) (((y)<0 ? (y)-font_height+1: (y)) / font_height)
958
959       case WM_LBUTTONDOWN:
960         click (MB_SELECT, TO_CHR_X(X_POS(lParam)),
961                TO_CHR_Y(Y_POS(lParam)));
962         SetCapture(hwnd);
963         return 0;
964       case WM_LBUTTONUP:
965         term_mouse (MB_SELECT, MA_RELEASE, TO_CHR_X(X_POS(lParam)),
966                     TO_CHR_Y(Y_POS(lParam)));
967         ReleaseCapture();
968         return 0;
969       case WM_MBUTTONDOWN:
970         SetCapture(hwnd);
971         click (cfg.mouse_is_xterm ? MB_PASTE : MB_EXTEND,
972                TO_CHR_X(X_POS(lParam)),
973                TO_CHR_Y(Y_POS(lParam)));
974         return 0;
975       case WM_MBUTTONUP:
976         term_mouse (cfg.mouse_is_xterm ? MB_PASTE : MB_EXTEND,
977                     MA_RELEASE, TO_CHR_X(X_POS(lParam)),
978                     TO_CHR_Y(Y_POS(lParam)));
979         ReleaseCapture();
980         return 0;
981       case WM_RBUTTONDOWN:
982         SetCapture(hwnd);
983         click (cfg.mouse_is_xterm ? MB_EXTEND : MB_PASTE,
984                TO_CHR_X(X_POS(lParam)),
985                TO_CHR_Y(Y_POS(lParam)));
986         return 0;
987       case WM_RBUTTONUP:
988         term_mouse (cfg.mouse_is_xterm ? MB_EXTEND : MB_PASTE,
989                     MA_RELEASE, TO_CHR_X(X_POS(lParam)),
990                     TO_CHR_Y(Y_POS(lParam)));
991         ReleaseCapture();
992         return 0;
993       case WM_MOUSEMOVE:
994         /*
995          * Add the mouse position and message time to the random
996          * number noise, if we're using ssh.
997          */
998         if (cfg.protocol == PROT_SSH)
999             noise_ultralight(lParam);
1000
1001         if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)) {
1002             Mouse_Button b;
1003             if (wParam & MK_LBUTTON)
1004                 b = MB_SELECT;
1005             else if (wParam & MK_MBUTTON)
1006                 b = cfg.mouse_is_xterm ? MB_PASTE : MB_EXTEND;
1007             else
1008                 b = cfg.mouse_is_xterm ? MB_EXTEND : MB_PASTE;
1009             term_mouse (b, MA_DRAG, TO_CHR_X(X_POS(lParam)),
1010                         TO_CHR_Y(Y_POS(lParam)));
1011         }
1012         return 0;
1013       case WM_IGNORE_CLIP:
1014         ignore_clip = wParam;          /* don't panic on DESTROYCLIPBOARD */
1015         break;
1016       case WM_DESTROYCLIPBOARD:
1017         if (!ignore_clip)
1018             term_deselect();
1019         ignore_clip = FALSE;
1020         return 0;
1021       case WM_PAINT:
1022         {
1023             PAINTSTRUCT p;
1024             hdc = BeginPaint (hwnd, &p);
1025             if (pal) {
1026                 SelectPalette (hdc, pal, TRUE);
1027                 RealizePalette (hdc);
1028             }
1029             term_paint (hdc, p.rcPaint.left, p.rcPaint.top,
1030                         p.rcPaint.right, p.rcPaint.bottom);
1031             SelectObject (hdc, GetStockObject(SYSTEM_FONT));
1032             SelectObject (hdc, GetStockObject(WHITE_PEN));
1033             EndPaint (hwnd, &p);
1034         }
1035         return 0;
1036       case WM_NETEVENT:
1037         /* Notice we can get multiple netevents, FD_READ, FD_WRITE etc
1038          * but the only one that's likely to try to overload us is FD_READ.
1039          * This means buffering just one is fine.
1040          */
1041         if (pending_netevent)
1042             enact_pending_netevent();
1043
1044         pending_netevent = TRUE;
1045         pend_netevent_wParam=wParam;
1046         pend_netevent_lParam=lParam;
1047         return 0;
1048       case WM_SETFOCUS:
1049         has_focus = TRUE;
1050         term_out();
1051         term_update();
1052         break;
1053       case WM_KILLFOCUS:
1054         has_focus = FALSE;
1055         term_out();
1056         term_update();
1057         break;
1058       case WM_IGNORE_SIZE:
1059         ignore_size = TRUE;            /* don't panic on next WM_SIZE msg */
1060         break;
1061       case WM_ENTERSIZEMOVE:
1062         EnableSizeTip(1);
1063         break;
1064       case WM_EXITSIZEMOVE:
1065         EnableSizeTip(0);
1066         break;
1067       case WM_SIZING:
1068         {
1069             int width, height, w, h, ew, eh;
1070             LPRECT r = (LPRECT)lParam;
1071
1072             width = r->right - r->left - extra_width;
1073             height = r->bottom - r->top - extra_height;
1074             w = (width + font_width/2) / font_width; if (w < 1) w = 1;
1075             h = (height + font_height/2) / font_height; if (h < 1) h = 1;
1076             UpdateSizeTip(hwnd, w, h);
1077             ew = width - w * font_width;
1078             eh = height - h * font_height;
1079             if (ew != 0) {
1080                 if (wParam == WMSZ_LEFT ||
1081                     wParam == WMSZ_BOTTOMLEFT ||
1082                     wParam == WMSZ_TOPLEFT)
1083                     r->left += ew;
1084                 else
1085                     r->right -= ew;
1086             }
1087             if (eh != 0) {
1088                 if (wParam == WMSZ_TOP ||
1089                     wParam == WMSZ_TOPRIGHT ||
1090                     wParam == WMSZ_TOPLEFT)
1091                     r->top += eh;
1092                 else
1093                     r->bottom -= eh;
1094             }
1095             if (ew || eh)
1096                 return 1;
1097             else
1098                 return 0;
1099         }
1100         /* break;  (never reached) */
1101       case WM_SIZE:
1102         if (wParam == SIZE_MINIMIZED) {
1103             SetWindowText (hwnd,
1104                            cfg.win_name_always ? window_name : icon_name);
1105             break;
1106         }
1107         if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
1108             SetWindowText (hwnd, window_name);
1109         if (!ignore_size) {
1110             int width, height, w, h;
1111 #if 0 /* we have fixed this using WM_SIZING now */
1112             int ew, eh;
1113 #endif
1114
1115             width = LOWORD(lParam);
1116             height = HIWORD(lParam);
1117             w = width / font_width; if (w < 1) w = 1;
1118             h = height / font_height; if (h < 1) h = 1;
1119 #if 0 /* we have fixed this using WM_SIZING now */
1120             ew = width - w * font_width;
1121             eh = height - h * font_height;
1122             if (ew != 0 || eh != 0) {
1123                 RECT r;
1124                 GetWindowRect (hwnd, &r);
1125                 SendMessage (hwnd, WM_IGNORE_SIZE, 0, 0);
1126                 SetWindowPos (hwnd, NULL, 0, 0,
1127                               r.right - r.left - ew, r.bottom - r.top - eh,
1128                               SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER);
1129             }
1130 #endif
1131             if (w != cols || h != rows || just_reconfigged) {
1132                 term_invalidate();
1133                 term_size (h, w, cfg.savelines);
1134                 back->size();
1135                 just_reconfigged = FALSE;
1136             }
1137         }
1138         ignore_size = FALSE;
1139         return 0;
1140       case WM_VSCROLL:
1141         switch (LOWORD(wParam)) {
1142           case SB_BOTTOM: term_scroll(-1, 0); break;
1143           case SB_TOP: term_scroll(+1, 0); break;
1144           case SB_LINEDOWN: term_scroll (0, +1); break;
1145           case SB_LINEUP: term_scroll (0, -1); break;
1146           case SB_PAGEDOWN: term_scroll (0, +rows/2); break;
1147           case SB_PAGEUP: term_scroll (0, -rows/2); break;
1148           case SB_THUMBPOSITION: case SB_THUMBTRACK:
1149             term_scroll (1, HIWORD(wParam)); break;
1150         }
1151         break; 
1152      case WM_PALETTECHANGED:
1153         if ((HWND) wParam != hwnd && pal != NULL) {
1154             HDC hdc = get_ctx();
1155             if (hdc) {
1156                 if (RealizePalette (hdc) > 0)
1157                     UpdateColors (hdc);
1158                 free_ctx (hdc);
1159             }
1160         }
1161         break;
1162       case WM_QUERYNEWPALETTE:
1163         if (pal != NULL) {
1164             HDC hdc = get_ctx();
1165             if (hdc) {
1166                 if (RealizePalette (hdc) > 0)
1167                     UpdateColors (hdc);
1168                 free_ctx (hdc);
1169                 return TRUE;
1170             }
1171         }
1172         return FALSE;
1173       case WM_KEYDOWN:
1174       case WM_SYSKEYDOWN:
1175         /*
1176          * Add the scan code and keypress timing to the random
1177          * number noise, if we're using ssh.
1178          */
1179         if (cfg.protocol == PROT_SSH)
1180             noise_ultralight(lParam);
1181
1182         /*
1183          * We don't do TranslateMessage since it disassociates the
1184          * resulting CHAR message from the KEYDOWN that sparked it,
1185          * which we occasionally don't want. Instead, we process
1186          * KEYDOWN, and call the Win32 translator functions so that
1187          * we get the translations under _our_ control.
1188          */
1189         {
1190             unsigned char buf[20];
1191             int len;
1192
1193             len = TranslateKey (wParam, lParam, buf);
1194             if (len == -1)
1195                 return DefWindowProc (hwnd, message, wParam, lParam);
1196             ldisc->send (buf, len);
1197         }
1198         return 0;
1199       case WM_KEYUP:
1200       case WM_SYSKEYUP:
1201         /*
1202          * We handle KEYUP ourselves in order to distinghish left
1203          * and right Alt or Control keys, which Windows won't do
1204          * right if left to itself. See also the special processing
1205          * at the top of TranslateKey.
1206          */
1207         {
1208             BYTE keystate[256];
1209             int ret = GetKeyboardState(keystate);
1210             if (ret && wParam == VK_MENU) {
1211                 if (lParam & 0x1000000) keystate[VK_RMENU] = 0;
1212                 else keystate[VK_LMENU] = 0;
1213                 SetKeyboardState (keystate);
1214             }
1215             if (ret && wParam == VK_CONTROL) {
1216                 if (lParam & 0x1000000) keystate[VK_RCONTROL] = 0;
1217                 else keystate[VK_LCONTROL] = 0;
1218                 SetKeyboardState (keystate);
1219             }
1220         }
1221         /*
1222          * We don't return here, in order to allow Windows to do
1223          * its own KEYUP processing as well.
1224          */
1225         break;
1226       case WM_CHAR:
1227       case WM_SYSCHAR:
1228         /*
1229          * Nevertheless, we are prepared to deal with WM_CHAR
1230          * messages, should they crop up. So if someone wants to
1231          * post the things to us as part of a macro manoeuvre,
1232          * we're ready to cope.
1233          */
1234         {
1235             char c = xlat_kbd2tty((unsigned char)wParam);
1236             ldisc->send (&c, 1);
1237         }
1238         return 0;
1239     }
1240
1241     return DefWindowProc (hwnd, message, wParam, lParam);
1242 }
1243
1244 /*
1245  * Draw a line of text in the window, at given character
1246  * coordinates, in given attributes.
1247  *
1248  * We are allowed to fiddle with the contents of `text'.
1249  */
1250 void do_text (Context ctx, int x, int y, char *text, int len,
1251               unsigned long attr) {
1252     int lattr = 0;      /* Will be arg later for line attribute type */
1253     COLORREF fg, bg, t;
1254     int nfg, nbg, nfont;
1255     HDC hdc = ctx;
1256     RECT line_box;
1257     int force_manual_underline = 0;
1258 static int *IpDx = 0, IpDxLEN = 0;;
1259
1260     if (len>IpDxLEN || IpDx[0] != font_width*(1+!lattr)) {
1261         int i;
1262         if (len>IpDxLEN) {
1263             sfree(IpDx);
1264             IpDx = smalloc((len+16)*sizeof(int));
1265             IpDxLEN = (len+16);
1266         }
1267         for(i=0; i<len; i++)
1268             IpDx[i] = font_width;
1269     }
1270
1271     x *= font_width;
1272     y *= font_height;
1273
1274     if (lattr) x *= 2;
1275
1276     if (attr & ATTR_ACTCURS) {
1277         attr &= (bold_mode == BOLD_COLOURS ? 0x200 : 0x300);
1278         attr ^= ATTR_CUR_XOR;
1279     }
1280
1281     nfont = 0;
1282     if (cfg.vtmode == VT_OEMONLY)
1283         nfont |= FONT_OEM;
1284
1285     /*
1286      * Map high-half characters in order to approximate ISO using
1287      * OEM character set. No characters are missing if the OEM codepage
1288      * is CP850.
1289      */
1290     if (nfont & FONT_OEM) {
1291         int i;
1292         for (i=0; i<len; i++)
1293             if (text[i] >= '\xA0' && text[i] <= '\xFF') {
1294 #if 0
1295                 /* This is CP850 ... perfect translation */
1296                 static const char oemhighhalf[] =
1297                     "\x20\xAD\xBD\x9C\xCF\xBE\xDD\xF5" /* A0-A7 */
1298                     "\xF9\xB8\xA6\xAE\xAA\xF0\xA9\xEE" /* A8-AF */
1299                     "\xF8\xF1\xFD\xFC\xEF\xE6\xF4\xFA" /* B0-B7 */
1300                     "\xF7\xFB\xA7\xAF\xAC\xAB\xF3\xA8" /* B8-BF */
1301                     "\xB7\xB5\xB6\xC7\x8E\x8F\x92\x80" /* C0-C7 */
1302                     "\xD4\x90\xD2\xD3\xDE\xD6\xD7\xD8" /* C8-CF */
1303                     "\xD1\xA5\xE3\xE0\xE2\xE5\x99\x9E" /* D0-D7 */
1304                     "\x9D\xEB\xE9\xEA\x9A\xED\xE8\xE1" /* D8-DF */
1305                     "\x85\xA0\x83\xC6\x84\x86\x91\x87" /* E0-E7 */
1306                     "\x8A\x82\x88\x89\x8D\xA1\x8C\x8B" /* E8-EF */
1307                     "\xD0\xA4\x95\xA2\x93\xE4\x94\xF6" /* F0-F7 */
1308                     "\x9B\x97\xA3\x96\x81\xEC\xE7\x98" /* F8-FF */
1309                     ;
1310 #endif
1311                 /* This is CP437 ... junk translation */
1312                 static const unsigned char oemhighhalf[] = {
1313                     0xff, 0xad, 0x9b, 0x9c, 0x6f, 0x9d, 0x7c, 0x15,
1314                     0x22, 0x43, 0xa6, 0xae, 0xaa, 0x2d, 0x52, 0xc4,
1315                     0xf8, 0xf1, 0xfd, 0x33, 0x27, 0xe6, 0x14, 0xfa,
1316                     0x2c, 0x31, 0xa7, 0xaf, 0xac, 0xab, 0x2f, 0xa8,
1317                     0x41, 0x41, 0x41, 0x41, 0x8e, 0x8f, 0x92, 0x80,
1318                     0x45, 0x90, 0x45, 0x45, 0x49, 0x49, 0x49, 0x49,
1319                     0x44, 0xa5, 0x4f, 0x4f, 0x4f, 0x4f, 0x99, 0x78,
1320                     0xed, 0x55, 0x55, 0x55, 0x9a, 0x59, 0x50, 0xe1,
1321                     0x85, 0xa0, 0x83, 0x61, 0x84, 0x86, 0x91, 0x87,
1322                     0x8a, 0x82, 0x88, 0x89, 0x8d, 0xa1, 0x8c, 0x8b,
1323                     0x0b, 0xa4, 0x95, 0xa2, 0x93, 0x6f, 0x94, 0xf6,
1324                     0xed, 0x97, 0xa3, 0x96, 0x81, 0x79, 0x70, 0x98
1325                 };
1326
1327                 text[i] = oemhighhalf[(unsigned char)text[i] - 0xA0];
1328             }
1329     }
1330
1331     if (attr & ATTR_GBCHR) {
1332         int i;
1333         /*
1334          * GB mapping: map # to pound, and everything else stays
1335          * normal.
1336          */
1337         for (i=0; i<len; i++)
1338             if (text[i] == '#')
1339                 text[i] = cfg.vtmode == VT_OEMONLY ? '\x9C' : '\xA3';
1340     } else if (attr & ATTR_LINEDRW) {
1341         int i;
1342         /* ISO 8859-1 */
1343         static const char poorman[] =
1344             "*#****\xB0\xB1**+++++-----++++|****\xA3\xB7";
1345
1346         /* CP437 */
1347         static const char oemmap_437[] =
1348             "\x04\xB1****\xF8\xF1**\xD9\xBF\xDA\xC0\xC5"
1349             "\xC4\xC4\xC4\xC4\xC4\xC3\xB4\xC1\xC2\xB3\xF3\xF2\xE3*\x9C\xFA";
1350
1351         /* CP850 */
1352         static const char oemmap_850[] =
1353             "\x04\xB1****\xF8\xF1**\xD9\xBF\xDA\xC0\xC5"
1354             "\xC4\xC4\xC4\xC4\xC4\xC3\xB4\xC1\xC2\xB3****\x9C\xFA";
1355
1356         /* Poor windows font ... eg: windows courier */
1357         static const char oemmap[] =
1358             "*\xB1****\xF8\xF1**\xD9\xBF\xDA\xC0\xC5"
1359             "\xC4\xC4\xC4\xC4\xC4\xC3\xB4\xC1\xC2\xB3****\x9C\xFA";
1360
1361         /*
1362          * Line drawing mapping: map ` thru ~ (0x60 thru 0x7E) to
1363          * VT100 line drawing chars; everything else stays normal.
1364          */
1365         switch (cfg.vtmode) {
1366           case VT_XWINDOWS:
1367             for (i=0; i<len; i++)
1368                 if (text[i] >= '\x60' && text[i] <= '\x7E')
1369                     text[i] += '\x01' - '\x60';
1370             break;
1371           case VT_OEMANSI:
1372             /* Make sure we actually have an OEM font */
1373             if (fonts[nfont|FONT_OEM]) { 
1374           case VT_OEMONLY:
1375                 nfont |= FONT_OEM;
1376                 for (i=0; i<len; i++)
1377                     if (text[i] >= '\x60' && text[i] <= '\x7E')
1378                         text[i] = oemmap[(unsigned char)text[i] - 0x60];
1379                 break;
1380             }
1381           case VT_POORMAN:
1382             for (i=0; i<len; i++)
1383                 if (text[i] >= '\x60' && text[i] <= '\x7E')
1384                     text[i] = poorman[(unsigned char)text[i] - 0x60];
1385             break;
1386         }
1387     }
1388
1389     nfg = 2 * ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
1390     nbg = 2 * ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
1391     if (bold_mode == BOLD_FONT && (attr & ATTR_BOLD))
1392         nfont |= FONT_BOLD;
1393     if (und_mode == UND_FONT && (attr & ATTR_UNDER))
1394         nfont |= FONT_UNDERLINE;
1395     if (!fonts[nfont]) 
1396     {
1397         if (nfont&FONT_UNDERLINE)
1398             force_manual_underline = 1;
1399         /* Don't do the same for manual bold, it could be bad news. */
1400
1401         nfont &= ~(FONT_BOLD|FONT_UNDERLINE);
1402     }
1403     if (attr & ATTR_REVERSE) {
1404         t = nfg; nfg = nbg; nbg = t;
1405     }
1406     if (bold_mode == BOLD_COLOURS && (attr & ATTR_BOLD))
1407         nfg++;
1408     if (bold_mode == BOLD_COLOURS && (attr & ATTR_BLINK))
1409         nbg++;
1410     fg = colours[nfg];
1411     bg = colours[nbg];
1412     SelectObject (hdc, fonts[nfont]);
1413     SetTextColor (hdc, fg);
1414     SetBkColor (hdc, bg);
1415     SetBkMode (hdc, OPAQUE);
1416     line_box.left   = x;
1417     line_box.top    = y;
1418     line_box.right  = x+font_width*len;
1419     line_box.bottom = y+font_height;
1420     ExtTextOut (hdc, x, y, ETO_CLIPPED|ETO_OPAQUE, &line_box, text, len, IpDx);
1421     if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
1422         SetBkMode (hdc, TRANSPARENT);
1423
1424        /* GRR: This draws the character outside it's box and can leave
1425         * 'droppings' even with the clip box! I suppose I could loop it
1426         * one character at a time ... yuk. */
1427         ExtTextOut (hdc, x-1, y, ETO_CLIPPED, &line_box, text, len, IpDx);
1428     }
1429     if (force_manual_underline || 
1430             (und_mode == UND_LINE && (attr & ATTR_UNDER))) {
1431         HPEN oldpen;
1432         oldpen = SelectObject (hdc, CreatePen(PS_SOLID, 0, fg));
1433         MoveToEx (hdc, x, y+descent, NULL);
1434         LineTo (hdc, x+len*font_width, y+descent);
1435         oldpen = SelectObject (hdc, oldpen);
1436         DeleteObject (oldpen);
1437     }
1438     if (attr & ATTR_PASCURS) {
1439         POINT pts[5];
1440         HPEN oldpen;
1441         pts[0].x = pts[1].x = pts[4].x = x;
1442         pts[2].x = pts[3].x = x+font_width-1;
1443         pts[0].y = pts[3].y = pts[4].y = y;
1444         pts[1].y = pts[2].y = y+font_height-1;
1445         oldpen = SelectObject (hdc, CreatePen(PS_SOLID, 0, colours[23]));
1446         Polyline (hdc, pts, 5);
1447         oldpen = SelectObject (hdc, oldpen);
1448         DeleteObject (oldpen);
1449     }
1450 }
1451
1452 /*
1453  * Translate a WM_(SYS)?KEYDOWN message into a string of ASCII
1454  * codes. Returns number of bytes used.
1455  */
1456 static int TranslateKey(WPARAM wParam, LPARAM lParam, unsigned char *output) {
1457     unsigned char *p = output;
1458     BYTE keystate[256];
1459     int ret, code;
1460     int cancel_alt = FALSE;
1461
1462     /*
1463      * Get hold of the keyboard state, because we'll need it a few
1464      * times shortly.
1465      */
1466     ret = GetKeyboardState(keystate);
1467
1468     /* 
1469      * Record that we pressed key so the scroll window can be reset, but
1470      * be careful to avoid Shift-UP/Down
1471      */
1472     if( wParam != VK_SHIFT && wParam != VK_PRIOR && wParam != VK_NEXT ) {
1473         seen_key_event = 1; 
1474     }
1475
1476     /* 
1477      * Windows does not always want to distinguish left and right
1478      * Alt or Control keys. Thus we keep track of them ourselves.
1479      * See also the WM_KEYUP handler.
1480      */
1481     if (wParam == VK_MENU) {
1482         if (lParam & 0x1000000) keystate[VK_RMENU] = 0x80;
1483         else keystate[VK_LMENU] = 0x80;
1484         SetKeyboardState (keystate);
1485         return 0;
1486     }
1487     if (wParam == VK_CONTROL) {
1488         if (lParam & 0x1000000) keystate[VK_RCONTROL] = 0x80;
1489         else keystate[VK_LCONTROL] = 0x80;
1490         SetKeyboardState (keystate);
1491         return 0;
1492     }
1493
1494     /*
1495      * Prepend ESC, and cancel ALT, if ALT was pressed at the time
1496      * and it wasn't AltGr.
1497      */
1498     if (lParam & 0x20000000 && (keystate[VK_LMENU] & 0x80)) {
1499         *p++ = 0x1B;
1500         cancel_alt = TRUE;
1501     }
1502
1503     /*
1504      * NetHack keypad mode. This may conflict with Shift-PgUp/PgDn,
1505      * so we do it first.
1506      */
1507     if (cfg.nethack_keypad) {
1508         int shift = keystate[VK_SHIFT] & 0x80;
1509         /*
1510          * NB the shifted versions only work with numlock off.
1511          */
1512         switch ( (lParam >> 16) & 0x1FF ) {
1513           case 0x047: *p++ = shift ? 'Y' : 'y'; return p - output;
1514           case 0x048: *p++ = shift ? 'K' : 'k'; return p - output;
1515           case 0x049: *p++ = shift ? 'U' : 'u'; return p - output;
1516           case 0x04B: *p++ = shift ? 'H' : 'h'; return p - output;
1517           case 0x04C: *p++ = '.'; return p - output;
1518           case 0x04D: *p++ = shift ? 'L' : 'l'; return p - output;
1519           case 0x04F: *p++ = shift ? 'B' : 'b'; return p - output;
1520           case 0x050: *p++ = shift ? 'J' : 'j'; return p - output;
1521           case 0x051: *p++ = shift ? 'N' : 'n'; return p - output;
1522           case 0x053: *p++ = '.'; return p - output;
1523         }
1524     }
1525
1526     /*
1527      * Shift-PgUp, Shift-PgDn, and Alt-F4 all produce window
1528      * events: we'll deal with those now.
1529      */
1530     if (ret && (keystate[VK_SHIFT] & 0x80) && wParam == VK_PRIOR) {
1531         SendMessage (hwnd, WM_VSCROLL, SB_PAGEUP, 0);
1532         return 0;
1533     }
1534     if (ret && (keystate[VK_SHIFT] & 0x80) && wParam == VK_NEXT) {
1535         SendMessage (hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
1536         return 0;
1537     }
1538     if ((lParam & 0x20000000) && wParam == VK_F4 && cfg.alt_f4) {
1539         return -1;
1540     }
1541     if ((lParam & 0x20000000) && wParam == VK_SPACE && cfg.alt_space) {
1542         SendMessage (hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0);
1543         return -1;
1544     }
1545
1546     /*
1547      * In general, the strategy is to see what the Windows keymap
1548      * translation has to say for itself, and then process function
1549      * keys and suchlike ourselves if that fails. But first we must
1550      * deal with the small number of special cases which the
1551      * Windows keymap translator thinks it can do but gets wrong.
1552      *
1553      * First special case: we might want the Backspace key to send
1554      * 0x7F not 0x08.
1555      */
1556     if (wParam == VK_BACK) {
1557         *p++ = (cfg.bksp_is_delete ? 0x7F : 0x08);
1558         return p - output;
1559     }
1560
1561     /*
1562      * Control-Space should send ^@ (0x00), not Space.
1563      */
1564     if (ret && (keystate[VK_CONTROL] & 0x80) && wParam == VK_SPACE) {
1565         *p++ = 0x00;
1566         return p - output;
1567     }
1568
1569     if (app_keypad_keys) {
1570         /*
1571          * If we're in applications keypad mode, we have to process it
1572          * before char-map translation, because it will pre-empt lots
1573          * of stuff, even if NumLock is off.
1574          */
1575         if (ret) {
1576             /*
1577              * Hack to ensure NumLock doesn't interfere with
1578              * perception of Shift for Keypad Plus. I don't pretend
1579              * to understand this, but it seems to work as is.
1580              * Leave it alone, or die.
1581              */
1582             keystate[VK_NUMLOCK] = 0;
1583             SetKeyboardState (keystate);
1584             GetKeyboardState (keystate);
1585         }
1586         switch ( (lParam >> 16) & 0x1FF ) {
1587           case 0x145: p += sprintf((char *)p, "\x1BOP"); return p - output;
1588           case 0x135: p += sprintf((char *)p, "\x1BOQ"); return p - output;
1589           case 0x037: p += sprintf((char *)p, "\x1BOR"); return p - output;
1590           case 0x047: p += sprintf((char *)p, "\x1BOw"); return p - output;
1591           case 0x048: p += sprintf((char *)p, "\x1BOx"); return p - output;
1592           case 0x049: p += sprintf((char *)p, "\x1BOy"); return p - output;
1593           case 0x04A: p += sprintf((char *)p, "\x1BOS"); return p - output;
1594           case 0x04B: p += sprintf((char *)p, "\x1BOt"); return p - output;
1595           case 0x04C: p += sprintf((char *)p, "\x1BOu"); return p - output;
1596           case 0x04D: p += sprintf((char *)p, "\x1BOv"); return p - output;
1597           case 0x04E: /* keypad + is ^[Ol, but ^[Om with Shift */
1598             p += sprintf((char *)p,
1599                          (ret && (keystate[VK_SHIFT] & 0x80)) ?
1600                          "\x1BOm" : "\x1BOl");
1601             return p - output;
1602           case 0x04F: p += sprintf((char *)p, "\x1BOq"); return p - output;
1603           case 0x050: p += sprintf((char *)p, "\x1BOr"); return p - output;
1604           case 0x051: p += sprintf((char *)p, "\x1BOs"); return p - output;
1605           case 0x052: p += sprintf((char *)p, "\x1BOp"); return p - output;
1606           case 0x053: p += sprintf((char *)p, "\x1BOn"); return p - output;
1607           case 0x11C: p += sprintf((char *)p, "\x1BOM"); return p - output;
1608         }
1609     }
1610
1611     /*
1612      * Shift-Tab should send ESC [ Z.
1613      */
1614     if (ret && (keystate[VK_SHIFT] & 0x80) && wParam == VK_TAB) {
1615         *p++ = 0x1B;                   /* ESC */
1616         *p++ = '[';
1617         *p++ = 'Z';
1618         return p - output;
1619     }
1620
1621     /*
1622      * Before doing Windows charmap translation, remove LeftALT
1623      * from the keymap, since its sole effect should be to prepend
1624      * ESC, which we've already done. Note that removal of LeftALT
1625      * has to happen _after_ the above call to SetKeyboardState, or
1626      * dire things will befall.
1627      */
1628     if (cancel_alt) {
1629         keystate[VK_MENU] = keystate[VK_RMENU];
1630         keystate[VK_LMENU] = 0;
1631     }
1632
1633     /*
1634      * Attempt the Windows char-map translation.
1635      */
1636     if (ret) {
1637         WORD chr;
1638         int r;
1639         BOOL capsOn=keystate[VK_CAPITAL] !=0;
1640
1641         /* helg: clear CAPS LOCK state if caps lock switches to cyrillic */
1642         if(cfg.xlat_capslockcyr)
1643             keystate[VK_CAPITAL] = 0;
1644
1645         r = ToAscii (wParam, (lParam >> 16) & 0xFF,
1646                      keystate, &chr, 0);
1647
1648         if(capsOn)
1649             chr = xlat_latkbd2win((unsigned char)(chr & 0xFF));
1650         if (r == 1) {
1651             *p++ = xlat_kbd2tty((unsigned char)(chr & 0xFF));
1652             return p - output;
1653         }
1654     }
1655
1656     /*
1657      * OK, we haven't had a key code from the keymap translation.
1658      * We'll try our various special cases and function keys, and
1659      * then give up. (There's nothing wrong with giving up:
1660      * Scrollock, Pause/Break, and of course the various buckybit
1661      * keys all produce KEYDOWN events that we really _do_ want to
1662      * ignore.)
1663      */
1664
1665     /*
1666      * Control-2 should return ^@ (0x00), Control-6 should return
1667      * ^^ (0x1E), and Control-Minus should return ^_ (0x1F). Since
1668      * the DOS keyboard handling did it, and we have nothing better
1669      * to do with the key combo in question, we'll also map
1670      * Control-Backquote to ^\ (0x1C).
1671      *
1672      * In addition a real VT100 maps Ctrl-3/4/5/7 and 8.
1673      */
1674     if (ret && (keystate[VK_CONTROL] & 0x80) && wParam == '2') {
1675         *p++ = 0x00;
1676         return p - output;
1677     }
1678     if (ret && (keystate[VK_CONTROL] & 0x80) && wParam == '3') {
1679         *p++ = 0x1B;
1680         return p - output;
1681     }
1682     if (ret && (keystate[VK_CONTROL] & 0x80) && wParam == '4') {
1683         *p++ = 0x1C;
1684         return p - output;
1685     }
1686     if (ret && (keystate[VK_CONTROL] & 0x80) && wParam == '5') {
1687         *p++ = 0x1D;
1688         return p - output;
1689     }
1690     if (ret && (keystate[VK_CONTROL] & 0x80) && wParam == '6') {
1691         *p++ = 0x1E;
1692         return p - output;
1693     }
1694     if (ret && (keystate[VK_CONTROL] & 0x80) && wParam == '7') {
1695         *p++ = 0x1F;
1696         return p - output;
1697     }
1698     if (ret && (keystate[VK_CONTROL] & 0x80) && wParam == '8') {
1699         *p++ = 0x7F;
1700         return p - output;
1701     }
1702     if (ret && (keystate[VK_CONTROL] & 0x80) && wParam == 0xBD) {
1703         *p++ = 0x1F;
1704         return p - output;
1705     }
1706     if (ret && (keystate[VK_CONTROL] & 0x80) && wParam == 0xDF) {
1707         *p++ = 0x1C;
1708         return p - output;
1709     }
1710
1711     /*
1712      * First, all the keys that do tilde codes. (ESC '[' nn '~',
1713      * for integer decimal nn.)
1714      *
1715      * We also deal with the weird ones here. Linux VCs replace F1
1716      * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
1717      * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
1718      * respectively.
1719      */
1720     code = 0;
1721     switch (wParam) {
1722       case VK_F1: code = (keystate[VK_SHIFT] & 0x80 ? 23 : 11); break;
1723       case VK_F2: code = (keystate[VK_SHIFT] & 0x80 ? 24 : 12); break;
1724       case VK_F3: code = (keystate[VK_SHIFT] & 0x80 ? 25 : 13); break;
1725       case VK_F4: code = (keystate[VK_SHIFT] & 0x80 ? 26 : 14); break;
1726       case VK_F5: code = (keystate[VK_SHIFT] & 0x80 ? 28 : 15); break;
1727       case VK_F6: code = (keystate[VK_SHIFT] & 0x80 ? 29 : 17); break;
1728       case VK_F7: code = (keystate[VK_SHIFT] & 0x80 ? 31 : 18); break;
1729       case VK_F8: code = (keystate[VK_SHIFT] & 0x80 ? 32 : 19); break;
1730       case VK_F9: code = (keystate[VK_SHIFT] & 0x80 ? 33 : 20); break;
1731       case VK_F10: code = (keystate[VK_SHIFT] & 0x80 ? 34 : 21); break;
1732       case VK_F11: code = 23; break;
1733       case VK_F12: code = 24; break;
1734       case VK_HOME: code = 1; break;
1735       case VK_INSERT: code = 2; break;
1736       case VK_DELETE: code = 3; break;
1737       case VK_END: code = 4; break;
1738       case VK_PRIOR: code = 5; break;
1739       case VK_NEXT: code = 6; break;
1740     }
1741     if (cfg.linux_funkeys && code >= 11 && code <= 15) {
1742         p += sprintf((char *)p, "\x1B[[%c", code + 'A' - 11);
1743         return p - output;
1744     }
1745     if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
1746         p += sprintf((char *)p, code == 1 ? "\x1B[H" : "\x1BOw");
1747         return p - output;
1748     }
1749     if (code) {
1750         p += sprintf((char *)p, "\x1B[%d~", code);
1751         return p - output;
1752     }
1753
1754     /*
1755      * Now the remaining keys (arrows and Keypad 5. Keypad 5 for
1756      * some reason seems to send VK_CLEAR to Windows...).
1757      */
1758     switch (wParam) {
1759       case VK_UP:
1760         p += sprintf((char *)p, app_cursor_keys ? "\x1BOA" : "\x1B[A");
1761         return p - output;
1762       case VK_DOWN:
1763         p += sprintf((char *)p, app_cursor_keys ? "\x1BOB" : "\x1B[B");
1764         return p - output;
1765       case VK_RIGHT:
1766         p += sprintf((char *)p, app_cursor_keys ? "\x1BOC" : "\x1B[C");
1767         return p - output;
1768       case VK_LEFT:
1769         p += sprintf((char *)p, app_cursor_keys ? "\x1BOD" : "\x1B[D");
1770         return p - output;
1771       case VK_CLEAR: p += sprintf((char *)p, "\x1B[G"); return p - output;
1772     }
1773
1774     return 0;
1775 }
1776
1777 void set_title (char *title) {
1778     sfree (window_name);
1779     window_name = smalloc(1+strlen(title));
1780     strcpy (window_name, title);
1781     if (cfg.win_name_always || !IsIconic(hwnd))
1782         SetWindowText (hwnd, title);
1783 }
1784
1785 void set_icon (char *title) {
1786     sfree (icon_name);
1787     icon_name = smalloc(1+strlen(title));
1788     strcpy (icon_name, title);
1789     if (!cfg.win_name_always && IsIconic(hwnd))
1790         SetWindowText (hwnd, title);
1791 }
1792
1793 void set_sbar (int total, int start, int page) {
1794     SCROLLINFO si;
1795     si.cbSize = sizeof(si);
1796     si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS | SIF_DISABLENOSCROLL;
1797     si.nMin = 0;
1798     si.nMax = total - 1;
1799     si.nPage = page;
1800     si.nPos = start;
1801     if (hwnd)
1802         SetScrollInfo (hwnd, SB_VERT, &si, TRUE);
1803 }
1804
1805 Context get_ctx(void) {
1806     HDC hdc;
1807     if (hwnd) {
1808         hdc = GetDC (hwnd);
1809         if (hdc && pal)
1810             SelectPalette (hdc, pal, FALSE);
1811         return hdc;
1812     } else
1813         return NULL;
1814 }
1815
1816 void free_ctx (Context ctx) {
1817     SelectPalette (ctx, GetStockObject (DEFAULT_PALETTE), FALSE);
1818     ReleaseDC (hwnd, ctx);
1819 }
1820
1821 static void real_palette_set (int n, int r, int g, int b) {
1822     if (pal) {
1823         logpal->palPalEntry[n].peRed = r;
1824         logpal->palPalEntry[n].peGreen = g;
1825         logpal->palPalEntry[n].peBlue = b;
1826         logpal->palPalEntry[n].peFlags = PC_NOCOLLAPSE;
1827         colours[n] = PALETTERGB(r, g, b);
1828         SetPaletteEntries (pal, 0, NCOLOURS, logpal->palPalEntry);
1829     } else
1830         colours[n] = RGB(r, g, b);
1831 }
1832
1833 void palette_set (int n, int r, int g, int b) {
1834     static const int first[21] = {
1835         0, 2, 4, 6, 8, 10, 12, 14,
1836         1, 3, 5, 7, 9, 11, 13, 15,
1837         16, 17, 18, 20, 22
1838     };
1839     real_palette_set (first[n], r, g, b);
1840     if (first[n] >= 18)
1841         real_palette_set (first[n]+1, r, g, b);
1842     if (pal) {
1843         HDC hdc = get_ctx();
1844         UnrealizeObject (pal);
1845         RealizePalette (hdc);
1846         free_ctx (hdc);
1847     }
1848 }
1849
1850 void palette_reset (void) {
1851     int i;
1852
1853     for (i = 0; i < NCOLOURS; i++) {
1854         if (pal) {
1855             logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
1856             logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
1857             logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
1858             logpal->palPalEntry[i].peFlags = 0;
1859             colours[i] = PALETTERGB(defpal[i].rgbtRed,
1860                                     defpal[i].rgbtGreen,
1861                                     defpal[i].rgbtBlue);
1862         } else
1863             colours[i] = RGB(defpal[i].rgbtRed,
1864                              defpal[i].rgbtGreen,
1865                              defpal[i].rgbtBlue);
1866     }
1867
1868     if (pal) {
1869         HDC hdc;
1870         SetPaletteEntries (pal, 0, NCOLOURS, logpal->palPalEntry);
1871         hdc = get_ctx();
1872         RealizePalette (hdc);
1873         free_ctx (hdc);
1874     }
1875 }
1876
1877 void write_clip (void *data, int len) {
1878     HGLOBAL clipdata;
1879     void *lock;
1880
1881     clipdata = GlobalAlloc (GMEM_DDESHARE | GMEM_MOVEABLE, len + 1);
1882     if (!clipdata)
1883         return;
1884     lock = GlobalLock (clipdata);
1885     if (!lock)
1886         return;
1887     memcpy (lock, data, len);
1888     ((unsigned char *) lock) [len] = 0;
1889     GlobalUnlock (clipdata);
1890
1891     SendMessage (hwnd, WM_IGNORE_CLIP, TRUE, 0);
1892     if (OpenClipboard (hwnd)) {
1893         EmptyClipboard();
1894         SetClipboardData (CF_TEXT, clipdata);
1895         CloseClipboard();
1896     } else
1897         GlobalFree (clipdata);
1898     SendMessage (hwnd, WM_IGNORE_CLIP, FALSE, 0);
1899 }
1900
1901 void get_clip (void **p, int *len) {
1902     static HGLOBAL clipdata = NULL;
1903
1904     if (!p) {
1905         if (clipdata)
1906             GlobalUnlock (clipdata);
1907         clipdata = NULL;
1908         return;
1909     } else {
1910         if (OpenClipboard (NULL)) {
1911             clipdata = GetClipboardData (CF_TEXT);
1912             CloseClipboard();
1913             if (clipdata) {
1914                 *p = GlobalLock (clipdata);
1915                 if (*p) {
1916                     *len = strlen(*p);
1917                     return;
1918                 }
1919             }
1920         }
1921     }
1922
1923     *p = NULL;
1924     *len = 0;
1925 }
1926
1927 /*
1928  * Move `lines' lines from position `from' to position `to' in the
1929  * window.
1930  */
1931 void optimised_move (int to, int from, int lines) {
1932     RECT r;
1933     int min, max;
1934
1935     min = (to < from ? to : from);
1936     max = to + from - min;
1937
1938     r.left = 0; r.right = cols * font_width;
1939     r.top = min * font_height; r.bottom = (max+lines) * font_height;
1940     ScrollWindow (hwnd, 0, (to - from) * font_height, &r, &r);
1941 }
1942
1943 /*
1944  * Print a message box and perform a fatal exit.
1945  */
1946 void fatalbox(char *fmt, ...) {
1947     va_list ap;
1948     char stuff[200];
1949
1950     va_start(ap, fmt);
1951     vsprintf(stuff, fmt, ap);
1952     va_end(ap);
1953     MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
1954     exit(1);
1955 }
1956
1957 /*
1958  * Beep.
1959  */
1960 void beep(void) {
1961     MessageBeep(MB_OK);
1962 }