]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - window.c
Add ISO8859-2 / CP852 translation courtesy of Jaromir Filsak
[PuTTY.git] / window.c
1 #include <windows.h>
2 #include <commctrl.h>
3 #ifndef AUTO_WINSOCK
4 #ifdef WINSOCK_TWO
5 #include <winsock2.h>
6 #else
7 #include <winsock.h>
8 #endif
9 #endif
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <ctype.h>
13 #include <time.h>
14
15 #define PUTTY_DO_GLOBALS                       /* actually _define_ globals */
16 #include "putty.h"
17 #include "storage.h"
18 #include "win_res.h"
19
20 #define IDM_SHOWLOG   0x0010
21 #define IDM_NEWSESS   0x0020
22 #define IDM_DUPSESS   0x0030
23 #define IDM_RECONF    0x0040
24 #define IDM_CLRSB     0x0050
25 #define IDM_RESET     0x0060
26 #define IDM_TEL_AYT   0x0070
27 #define IDM_TEL_BRK   0x0080
28 #define IDM_TEL_SYNCH 0x0090
29 #define IDM_TEL_EC    0x00a0
30 #define IDM_TEL_EL    0x00b0
31 #define IDM_TEL_GA    0x00c0
32 #define IDM_TEL_NOP   0x00d0
33 #define IDM_TEL_ABORT 0x00e0
34 #define IDM_TEL_AO    0x00f0
35 #define IDM_TEL_IP    0x0100
36 #define IDM_TEL_SUSP  0x0110
37 #define IDM_TEL_EOR   0x0120
38 #define IDM_TEL_EOF   0x0130
39 #define IDM_ABOUT     0x0140
40 #define IDM_SAVEDSESS 0x0150
41
42 #define IDM_SAVED_MIN 0x1000
43 #define IDM_SAVED_MAX 0x2000
44
45 #define WM_IGNORE_SIZE (WM_XUSER + 1)
46 #define WM_IGNORE_CLIP (WM_XUSER + 2)
47
48 static LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
49 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, unsigned char *output);
50 static void cfgtopalette(void);
51 static void init_palette(void);
52 static void init_fonts(int);
53
54 static int extra_width, extra_height;
55
56 static int pending_netevent = 0;
57 static WPARAM pend_netevent_wParam = 0;
58 static LPARAM pend_netevent_lParam = 0;
59 static void enact_pending_netevent(void);
60
61 static time_t last_movement = 0;
62
63 #define FONT_NORMAL 0
64 #define FONT_BOLD 1
65 #define FONT_UNDERLINE 2
66 #define FONT_BOLDUND 3
67 #define FONT_OEM 4
68 #define FONT_OEMBOLD 5
69 #define FONT_OEMBOLDUND 6
70 #define FONT_OEMUND 7
71 static HFONT fonts[8];
72 static int font_needs_hand_underlining;
73 static enum {
74     BOLD_COLOURS, BOLD_SHADOW, BOLD_FONT
75 } bold_mode;
76 static enum {
77     UND_LINE, UND_FONT
78 } und_mode;
79 static int descent;
80
81 #define NCOLOURS 24
82 static COLORREF colours[NCOLOURS];
83 static HPALETTE pal;
84 static LPLOGPALETTE logpal;
85 static RGBTRIPLE defpal[NCOLOURS];
86
87 static HWND hwnd;
88
89 static HBITMAP caretbm;
90
91 static int dbltime, lasttime, lastact;
92 static Mouse_Button lastbtn;
93
94 static char *window_name, *icon_name;
95
96 static Ldisc *real_ldisc;
97
98 void begin_session(void) {
99     ldisc = real_ldisc;
100 }
101
102 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) {
103     static char appname[] = "PuTTY";
104     WORD winsock_ver;
105     WSADATA wsadata;
106     WNDCLASS wndclass;
107     MSG msg;
108     int guess_width, guess_height;
109
110     putty_inst = inst;
111     flags = FLAG_VERBOSE | FLAG_INTERACTIVE;
112
113     winsock_ver = MAKEWORD(1, 1);
114     if (WSAStartup(winsock_ver, &wsadata)) {
115         MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
116                    MB_OK | MB_ICONEXCLAMATION);
117         return 1;
118     }
119     if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
120         MessageBox(NULL, "WinSock version is incompatible with 1.1",
121                    "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
122         WSACleanup();
123         return 1;
124     }
125     /* WISHLIST: maybe allow config tweaking even if winsock not present? */
126
127     InitCommonControls();
128
129     /*
130      * Process the command line.
131      */
132     {
133         char *p;
134
135         default_protocol = DEFAULT_PROTOCOL;
136         default_port = DEFAULT_PORT;
137
138         do_defaults(NULL, &cfg);
139
140         p = cmdline;
141         while (*p && isspace(*p)) p++;
142
143         /*
144          * Process command line options first. Yes, this can be
145          * done better, and it will be as soon as I have the
146          * energy...
147          */
148         while (*p == '-') {
149             char *q = p + strcspn(p, " \t");
150             p++;
151             if (q == p + 3 &&
152                 tolower(p[0]) == 's' &&
153                 tolower(p[1]) == 's' &&
154                 tolower(p[2]) == 'h') {
155                 default_protocol = cfg.protocol = PROT_SSH;
156                 default_port = cfg.port = 22;
157             } else if (q == p + 3 &&
158                 tolower(p[0]) == 'l' &&
159                 tolower(p[1]) == 'o' &&
160                 tolower(p[2]) == 'g') {
161                 logfile = "putty.log";
162             } else if (q == p + 7 &&
163                 tolower(p[0]) == 'c' &&
164                 tolower(p[1]) == 'l' &&
165                 tolower(p[2]) == 'e' &&
166                 tolower(p[3]) == 'a' &&
167                 tolower(p[4]) == 'n' &&
168                 tolower(p[5]) == 'u' &&
169                 tolower(p[6]) == 'p') {
170                 /*
171                  * `putty -cleanup'. Remove all registry entries
172                  * associated with PuTTY, and also find and delete
173                  * the random seed file.
174                  */
175                 if (MessageBox(NULL,
176                                "This procedure will remove ALL Registry\n"
177                                "entries associated with PuTTY, and will\n"
178                                "also remove the PuTTY random seed file.\n"
179                                "\n"
180                                "THIS PROCESS WILL DESTROY YOUR SAVED\n"
181                                "SESSIONS. Are you really sure you want\n"
182                                "to continue?",
183                                "PuTTY Warning",
184                                MB_YESNO | MB_ICONWARNING) == IDYES) {
185                     cleanup_all();
186                 }
187                 exit(0);
188             }
189             p = q + strspn(q, " \t");
190         }
191
192         /*
193          * An initial @ means to activate a saved session.
194          */
195         if (*p == '@') {
196             do_defaults (p+1, &cfg);
197             if (!*cfg.host && !do_config()) {
198                 WSACleanup();
199                 return 0;
200             }
201         } else if (*p == '&') {
202             /*
203              * An initial & means we've been given a command line
204              * containing the hex value of a HANDLE for a file
205              * mapping object, which we must then extract as a
206              * config.
207              */
208             HANDLE filemap;
209             Config *cp;
210             if (sscanf(p+1, "%p", &filemap) == 1 &&
211                 (cp = MapViewOfFile(filemap, FILE_MAP_READ,
212                                     0, 0, sizeof(Config))) != NULL) {
213                 cfg = *cp;
214                 UnmapViewOfFile(cp);
215                 CloseHandle(filemap);
216             } else if (!do_config()) {
217                 WSACleanup();
218                 return 0;
219             }
220         } else if (*p) {
221             char *q = p;
222             /*
223              * If the hostname starts with "telnet:", set the
224              * protocol to Telnet and process the string as a
225              * Telnet URL.
226              */
227             if (!strncmp(q, "telnet:", 7)) {
228                 char c;
229
230                 q += 7;
231                 if (q[0] == '/' && q[1] == '/')
232                     q += 2;
233                 cfg.protocol = PROT_TELNET;
234                 p = q;
235                 while (*p && *p != ':' && *p != '/') p++;
236                 c = *p;
237                 if (*p)
238                     *p++ = '\0';
239                 if (c == ':')
240                     cfg.port = atoi(p);
241                 else
242                     cfg.port = -1;
243                 strncpy (cfg.host, q, sizeof(cfg.host)-1);
244                 cfg.host[sizeof(cfg.host)-1] = '\0';
245             } else {
246                 while (*p && !isspace(*p)) p++;
247                 if (*p)
248                     *p++ = '\0';
249                 strncpy (cfg.host, q, sizeof(cfg.host)-1);
250                 cfg.host[sizeof(cfg.host)-1] = '\0';
251                 while (*p && isspace(*p)) p++;
252                 if (*p)
253                     cfg.port = atoi(p);
254                 else
255                     cfg.port = -1;
256             }
257         } else {
258             if (!do_config()) {
259                 WSACleanup();
260                 return 0;
261             }
262         }
263
264         /* See if host is of the form user@host */
265         if (cfg.host[0] != '\0') {
266             char *atsign = strchr(cfg.host, '@');
267             /* Make sure we're not overflowing the user field */
268             if (atsign) {
269                 if (atsign-cfg.host < sizeof cfg.username) {
270                     strncpy (cfg.username, cfg.host, atsign-cfg.host);
271                     cfg.username[atsign-cfg.host] = '\0';
272                 }
273                 memmove(cfg.host, atsign+1, 1+strlen(atsign+1));
274             }
275         }
276     }
277
278     /*
279      * Select protocol. This is farmed out into a table in a
280      * separate file to enable an ssh-free variant.
281      */
282     {
283         int i;
284         back = NULL;
285         for (i = 0; backends[i].backend != NULL; i++)
286             if (backends[i].protocol == cfg.protocol) {
287                 back = backends[i].backend;
288                 break;
289             }
290         if (back == NULL) {
291             MessageBox(NULL, "Unsupported protocol number found",
292                        "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
293             WSACleanup();
294             return 1;
295         }
296     }
297
298     real_ldisc = (cfg.ldisc_term ? &ldisc_term : &ldisc_simple);
299     /* To start with, we use the simple line discipline, so we can
300      * type passwords etc without fear of them being echoed... */
301     ldisc = &ldisc_simple;
302
303     if (!prev) {
304         wndclass.style         = 0;
305         wndclass.lpfnWndProc   = WndProc;
306         wndclass.cbClsExtra    = 0;
307         wndclass.cbWndExtra    = 0;
308         wndclass.hInstance     = inst;
309         wndclass.hIcon         = LoadIcon (inst,
310                                            MAKEINTRESOURCE(IDI_MAINICON));
311         wndclass.hCursor       = LoadCursor (NULL, IDC_IBEAM);
312         wndclass.hbrBackground = GetStockObject (BLACK_BRUSH);
313         wndclass.lpszMenuName  = NULL;
314         wndclass.lpszClassName = appname;
315
316         RegisterClass (&wndclass);
317     }
318
319     hwnd = NULL;
320
321     savelines = cfg.savelines;
322     term_init();
323
324     cfgtopalette();
325
326     /*
327      * Guess some defaults for the window size. This all gets
328      * updated later, so we don't really care too much. However, we
329      * do want the font width/height guesses to correspond to a
330      * large font rather than a small one...
331      */
332     
333     font_width = 10;
334     font_height = 20;
335     extra_width = 25;
336     extra_height = 28;
337     term_size (cfg.height, cfg.width, cfg.savelines);
338     guess_width = extra_width + font_width * cols;
339     guess_height = extra_height + font_height * rows;
340     {
341         RECT r;
342         HWND w = GetDesktopWindow();
343         GetWindowRect (w, &r);
344         if (guess_width > r.right - r.left)
345             guess_width = r.right - r.left;
346         if (guess_height > r.bottom - r.top)
347             guess_height = r.bottom - r.top;
348     }
349
350     {
351        int winmode = WS_OVERLAPPEDWINDOW|WS_VSCROLL;
352        if (!cfg.scrollbar) winmode &= ~(WS_VSCROLL);
353        if (cfg.locksize)   winmode &= ~(WS_THICKFRAME|WS_MAXIMIZEBOX);
354        hwnd = CreateWindow (appname, appname,
355                             winmode,
356                             CW_USEDEFAULT, CW_USEDEFAULT,
357                             guess_width, guess_height,
358                             NULL, NULL, inst, NULL);
359     }
360
361     /*
362      * Initialise the fonts, simultaneously correcting the guesses
363      * for font_{width,height}.
364      */
365     bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
366     und_mode = UND_FONT;
367     init_fonts(0);
368
369     /*
370      * Correct the guesses for extra_{width,height}.
371      */
372     {
373         RECT cr, wr;
374         GetWindowRect (hwnd, &wr);
375         GetClientRect (hwnd, &cr);
376         extra_width = wr.right - wr.left - cr.right + cr.left;
377         extra_height = wr.bottom - wr.top - cr.bottom + cr.top;
378     }
379
380     /*
381      * Resize the window, now we know what size we _really_ want it
382      * to be.
383      */
384     guess_width = extra_width + font_width * cols;
385     guess_height = extra_height + font_height * rows;
386     SendMessage (hwnd, WM_IGNORE_SIZE, 0, 0);
387     SetWindowPos (hwnd, NULL, 0, 0, guess_width, guess_height,
388                   SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
389
390     /*
391      * Set up a caret bitmap, with no content.
392      */
393     {
394         char *bits;
395         int size = (font_width+15)/16 * 2 * font_height; 
396         bits = calloc(size, 1);
397         caretbm = CreateBitmap(font_width, font_height, 1, 1, bits);
398         free(bits);
399     }
400
401     /*
402      * Initialise the scroll bar.
403      */
404     {
405         SCROLLINFO si;
406
407         si.cbSize = sizeof(si);
408         si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
409         si.nMin = 0;
410         si.nMax = rows-1;
411         si.nPage = rows;
412         si.nPos = 0;
413         SetScrollInfo (hwnd, SB_VERT, &si, FALSE);
414     }
415
416     /*
417      * Start up the telnet connection.
418      */
419     {
420         char *error;
421         char msg[1024], *title;
422         char *realhost;
423
424         error = back->init (hwnd, cfg.host, cfg.port, &realhost);
425         if (error) {
426             sprintf(msg, "Unable to open connection:\n%s", error);
427             MessageBox(NULL, msg, "PuTTY Error", MB_ICONERROR | MB_OK);
428             return 0;
429         }
430         window_name = icon_name = NULL;
431         if (*cfg.wintitle) {
432             title = cfg.wintitle;
433         } else {
434             sprintf(msg, "%s - PuTTY", realhost);
435             title = msg;
436         }
437         set_title (title);
438         set_icon (title);
439     }
440
441     session_closed = FALSE;
442
443     /*
444      * Set up the input and output buffers.
445      */
446     inbuf_head = 0;
447     outbuf_reap = outbuf_head = 0;
448
449     /*
450      * Prepare the mouse handler.
451      */
452     lastact = MA_NOTHING;
453     lastbtn = MB_NOTHING;
454     dbltime = GetDoubleClickTime();
455
456     /*
457      * Set up the session-control options on the system menu.
458      */
459     {
460         HMENU m = GetSystemMenu (hwnd, FALSE);
461         HMENU p,s;
462         int i;
463
464         AppendMenu (m, MF_SEPARATOR, 0, 0);
465         if (cfg.protocol == PROT_TELNET) {
466             p = CreateMenu();
467             AppendMenu (p, MF_ENABLED, IDM_TEL_AYT, "Are You There");
468             AppendMenu (p, MF_ENABLED, IDM_TEL_BRK, "Break");
469             AppendMenu (p, MF_ENABLED, IDM_TEL_SYNCH, "Synch");
470             AppendMenu (p, MF_SEPARATOR, 0, 0);
471             AppendMenu (p, MF_ENABLED, IDM_TEL_EC, "Erase Character");
472             AppendMenu (p, MF_ENABLED, IDM_TEL_EL, "Erase Line");
473             AppendMenu (p, MF_ENABLED, IDM_TEL_GA, "Go Ahead");
474             AppendMenu (p, MF_ENABLED, IDM_TEL_NOP, "No Operation");
475             AppendMenu (p, MF_SEPARATOR, 0, 0);
476             AppendMenu (p, MF_ENABLED, IDM_TEL_ABORT, "Abort Process");
477             AppendMenu (p, MF_ENABLED, IDM_TEL_AO, "Abort Output");
478             AppendMenu (p, MF_ENABLED, IDM_TEL_IP, "Interrupt Process");
479             AppendMenu (p, MF_ENABLED, IDM_TEL_SUSP, "Suspend Process");
480             AppendMenu (p, MF_SEPARATOR, 0, 0);
481             AppendMenu (p, MF_ENABLED, IDM_TEL_EOR, "End Of Record");
482             AppendMenu (p, MF_ENABLED, IDM_TEL_EOF, "End Of File");
483             AppendMenu (m, MF_POPUP | MF_ENABLED, (UINT) p, "Telnet Command");
484             AppendMenu (m, MF_SEPARATOR, 0, 0);
485         }
486         AppendMenu (m, MF_ENABLED, IDM_SHOWLOG, "&Event Log");
487         AppendMenu (m, MF_SEPARATOR, 0, 0);
488         AppendMenu (m, MF_ENABLED, IDM_NEWSESS, "Ne&w Session");
489         AppendMenu (m, MF_ENABLED, IDM_DUPSESS, "&Duplicate Session");
490         s = CreateMenu();
491         get_sesslist(TRUE);
492         for (i = 1 ; i < ((nsessions < 256) ? nsessions : 256) ; i++)
493           AppendMenu (s, MF_ENABLED, IDM_SAVED_MIN + (16 * i) , sessions[i]);
494         AppendMenu (m, MF_POPUP | MF_ENABLED, (UINT) s, "Sa&ved Sessions");
495         AppendMenu (m, MF_ENABLED, IDM_RECONF, "Chan&ge Settings");
496         AppendMenu (m, MF_SEPARATOR, 0, 0);
497         AppendMenu (m, MF_ENABLED, IDM_CLRSB, "C&lear Scrollback");
498         AppendMenu (m, MF_ENABLED, IDM_RESET, "Rese&t Terminal");
499         AppendMenu (m, MF_SEPARATOR, 0, 0);
500         AppendMenu (m, MF_ENABLED, IDM_ABOUT, "&About PuTTY");
501     }
502
503     /*
504      * Finally show the window!
505      */
506     ShowWindow (hwnd, show);
507
508     /*
509      * Set the palette up.
510      */
511     pal = NULL;
512     logpal = NULL;
513     init_palette();
514
515     has_focus = (GetForegroundWindow() == hwnd);
516     UpdateWindow (hwnd);
517
518     if (GetMessage (&msg, NULL, 0, 0) == 1)
519     {
520         int timer_id = 0, long_timer = 0;
521
522         while (msg.message != WM_QUIT) {
523             /* Sometimes DispatchMessage calls routines that use their own
524              * GetMessage loop, setup this timer so we get some control back.
525              *
526              * Also call term_update() from the timer so that if the host
527              * is sending data flat out we still do redraws.
528              */
529             if(timer_id && long_timer) {
530                 KillTimer(hwnd, timer_id);
531                 long_timer = timer_id = 0;
532             }
533             if(!timer_id)
534                 timer_id = SetTimer(hwnd, 1, 20, NULL);
535             DispatchMessage (&msg);
536
537             /* Make sure we blink everything that needs it. */
538             term_blink(0);
539
540             /* Send the paste buffer if there's anything to send */
541             term_paste();
542
543             /* If there's nothing new in the queue then we can do everything
544              * we've delayed, reading the socket, writing, and repainting
545              * the window.
546              */
547             if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
548                 continue;
549
550             if (pending_netevent) {
551                 enact_pending_netevent();
552
553                 /* Force the cursor blink on */
554                 term_blink(1);
555
556                 if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
557                     continue;
558             }
559
560             /* Okay there is now nothing to do so we make sure the screen is
561              * completely up to date then tell windows to call us in a little 
562              * while.
563              */
564             if (timer_id) {
565                 KillTimer(hwnd, timer_id);
566                 timer_id = 0;
567             }
568             HideCaret(hwnd);
569             if (inbuf_head)
570                 term_out();
571             term_update();
572             ShowCaret(hwnd);
573             if (!has_focus)
574                timer_id = SetTimer(hwnd, 1, 59500, NULL);
575             else
576                timer_id = SetTimer(hwnd, 1, 250, NULL);
577             long_timer = 1;
578         
579             /* There's no point rescanning everything in the message queue
580              * so we do an apperently unneccesary wait here 
581              */
582             WaitMessage();
583             if (GetMessage (&msg, NULL, 0, 0) != 1)
584                 break;
585         }
586     }
587
588     /*
589      * Clean up.
590      */
591     {
592         int i;
593         for (i=0; i<8; i++)
594             if (fonts[i])
595                 DeleteObject(fonts[i]);
596     }
597     sfree(logpal);
598     if (pal)
599         DeleteObject(pal);
600     WSACleanup();
601
602     if (cfg.protocol == PROT_SSH) {
603         random_save_seed();
604 #ifdef MSCRYPTOAPI
605         crypto_wrapup();
606 #endif
607     }
608
609     return msg.wParam;
610 }
611
612 /*
613  * Print a message box and close the connection.
614  */
615 void connection_fatal(char *fmt, ...) {
616     va_list ap;
617     char stuff[200];
618
619     va_start(ap, fmt);
620     vsprintf(stuff, fmt, ap);
621     va_end(ap);
622     MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
623     if (cfg.close_on_exit)
624         PostQuitMessage(1);
625     else {
626         session_closed = TRUE;
627         SetWindowText (hwnd, "PuTTY (inactive)");
628     }
629 }
630
631 /*
632  * Actually do the job requested by a WM_NETEVENT
633  */
634 static void enact_pending_netevent(void) {
635     int i;
636     static int reentering = 0;
637
638     if (reentering)
639         return;                        /* don't unpend the pending */
640
641     pending_netevent = FALSE;
642
643     reentering = 1;
644     i = back->msg (pend_netevent_wParam, pend_netevent_lParam);
645     reentering = 0;
646
647     if (i < 0) {
648         char buf[1024];
649         switch (WSABASEERR + (-i) % 10000) {
650           case WSAECONNRESET:
651             sprintf(buf, "Connection reset by peer");
652             break;
653           default:
654             sprintf(buf, "Unexpected network error %d", -i);
655             break;
656         }
657         connection_fatal(buf);
658     }
659     if (i <= 0) {
660         if (cfg.close_on_exit)
661             PostQuitMessage(0);
662         else {
663             session_closed = TRUE;
664             MessageBox(hwnd, "Connection closed by remote host",
665                        "PuTTY", MB_OK | MB_ICONINFORMATION);
666             SetWindowText (hwnd, "PuTTY (inactive)");
667         }
668     }
669 }
670
671 /*
672  * Copy the colour palette from the configuration data into defpal.
673  * This is non-trivial because the colour indices are different.
674  */
675 static void cfgtopalette(void) {
676     int i;
677     static const int ww[] = {
678         6, 7, 8, 9, 10, 11, 12, 13,
679         14, 15, 16, 17, 18, 19, 20, 21,
680         0, 1, 2, 3, 4, 4, 5, 5
681     };
682
683     for (i=0; i<24; i++) {
684         int w = ww[i];
685         defpal[i].rgbtRed = cfg.colours[w][0];
686         defpal[i].rgbtGreen = cfg.colours[w][1];
687         defpal[i].rgbtBlue = cfg.colours[w][2];
688     }
689 }
690
691 /*
692  * Set up the colour palette.
693  */
694 static void init_palette(void) {
695     int i;
696     HDC hdc = GetDC (hwnd);
697     if (hdc) {
698         if (cfg.try_palette &&
699             GetDeviceCaps (hdc, RASTERCAPS) & RC_PALETTE) {
700             logpal = smalloc(sizeof(*logpal)
701                              - sizeof(logpal->palPalEntry)
702                              + NCOLOURS * sizeof(PALETTEENTRY));
703             logpal->palVersion = 0x300;
704             logpal->palNumEntries = NCOLOURS;
705             for (i = 0; i < NCOLOURS; i++) {
706                 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
707                 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
708                 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
709                 logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
710             }
711             pal = CreatePalette (logpal);
712             if (pal) {
713                 SelectPalette (hdc, pal, FALSE);
714                 RealizePalette (hdc);
715                 SelectPalette (hdc, GetStockObject (DEFAULT_PALETTE),
716                                FALSE);
717             }
718         }
719         ReleaseDC (hwnd, hdc);
720     }
721     if (pal)
722         for (i=0; i<NCOLOURS; i++)
723             colours[i] = PALETTERGB(defpal[i].rgbtRed,
724                                     defpal[i].rgbtGreen,
725                                     defpal[i].rgbtBlue);
726     else
727         for(i=0; i<NCOLOURS; i++)
728             colours[i] = RGB(defpal[i].rgbtRed,
729                              defpal[i].rgbtGreen,
730                              defpal[i].rgbtBlue);
731 }
732
733 /*
734  * Initialise all the fonts we will need. There may be as many as
735  * eight or as few as one. We also:
736  *
737  * - check the font width and height, correcting our guesses if
738  *   necessary.
739  *
740  * - verify that the bold font is the same width as the ordinary
741  *   one, and engage shadow bolding if not.
742  * 
743  * - verify that the underlined font is the same width as the
744  *   ordinary one (manual underlining by means of line drawing can
745  *   be done in a pinch).
746  */
747 static void init_fonts(int pick_width) {
748     TEXTMETRIC tm;
749     int i;
750     int fsize[8];
751     HDC hdc;
752     int fw_dontcare, fw_bold;
753     int firstchar = ' ';
754
755 #ifdef CHECKOEMFONT
756 font_messup:
757 #endif
758     for (i=0; i<8; i++)
759         fonts[i] = NULL;
760
761     if (cfg.fontisbold) {
762         fw_dontcare = FW_BOLD;
763         fw_bold = FW_HEAVY;
764    } else {
765         fw_dontcare = FW_DONTCARE;
766         fw_bold = FW_BOLD;
767     }
768
769     hdc = GetDC(hwnd);
770
771     font_height = cfg.fontheight;
772     font_width = pick_width;
773
774 #define f(i,c,w,u) \
775     fonts[i] = CreateFont (font_height, font_width, 0, 0, w, FALSE, u, FALSE, \
776                            c, OUT_DEFAULT_PRECIS, \
777                            CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, \
778                            FIXED_PITCH | FF_DONTCARE, cfg.font)
779
780     if (cfg.vtmode != VT_OEMONLY) {
781         f(FONT_NORMAL, cfg.fontcharset, fw_dontcare, FALSE);
782
783         SelectObject (hdc, fonts[FONT_NORMAL]);
784         GetTextMetrics(hdc, &tm); 
785         font_height = tm.tmHeight;
786         font_width = tm.tmAveCharWidth;
787
788         f(FONT_UNDERLINE, cfg.fontcharset, fw_dontcare, TRUE);
789
790         /*
791          * Some fonts, e.g. 9-pt Courier, draw their underlines
792          * outside their character cell. We successfully prevent
793          * screen corruption by clipping the text output, but then
794          * we lose the underline completely. Here we try to work
795          * out whether this is such a font, and if it is, we set a
796          * flag that causes underlines to be drawn by hand.
797          *
798          * Having tried other more sophisticated approaches (such
799          * as examining the TEXTMETRIC structure or requesting the
800          * height of a string), I think we'll do this the brute
801          * force way: we create a small bitmap, draw an underlined
802          * space on it, and test to see whether any pixels are
803          * foreground-coloured. (Since we expect the underline to
804          * go all the way across the character cell, we only search
805          * down a single column of the bitmap, half way across.)
806          */
807         {
808             HDC und_dc;
809             HBITMAP und_bm, und_oldbm;
810             int i, gotit;
811             COLORREF c;
812
813             und_dc = CreateCompatibleDC(hdc);
814             und_bm = CreateCompatibleBitmap(hdc, font_width, font_height);
815             und_oldbm = SelectObject(und_dc, und_bm);
816             SelectObject(und_dc, fonts[FONT_UNDERLINE]);
817             SetTextAlign(und_dc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
818             SetTextColor (und_dc, RGB(255,255,255));
819             SetBkColor (und_dc, RGB(0,0,0));
820             SetBkMode (und_dc, OPAQUE);
821             ExtTextOut (und_dc, 0, 0, ETO_OPAQUE, NULL, " ", 1, NULL);
822             gotit = FALSE;
823             for (i = 0; i < font_height; i++) {
824                 c = GetPixel(und_dc, font_width/2, i);
825                 if (c != RGB(0,0,0))
826                     gotit = TRUE;
827             }
828             SelectObject(und_dc, und_oldbm);
829             DeleteObject(und_bm);
830             DeleteDC(und_dc);
831             font_needs_hand_underlining = !gotit;
832         }
833
834         if (bold_mode == BOLD_FONT) {
835             f(FONT_BOLD, cfg.fontcharset, fw_bold, FALSE);
836             f(FONT_BOLDUND, cfg.fontcharset, fw_bold, TRUE);
837         }
838
839         if (cfg.vtmode == VT_OEMANSI) {
840             f(FONT_OEM, OEM_CHARSET, fw_dontcare, FALSE);
841             f(FONT_OEMUND, OEM_CHARSET, fw_dontcare, TRUE);
842
843             if (bold_mode == BOLD_FONT) {
844                 f(FONT_OEMBOLD, OEM_CHARSET, fw_bold, FALSE);
845                 f(FONT_OEMBOLDUND, OEM_CHARSET, fw_bold, TRUE);
846             }
847         }
848     }
849     else
850     {
851         f(FONT_OEM, cfg.fontcharset, fw_dontcare, FALSE);
852
853         SelectObject (hdc, fonts[FONT_OEM]);
854         GetTextMetrics(hdc, &tm); 
855         font_height = tm.tmHeight;
856         font_width = tm.tmAveCharWidth;
857
858         f(FONT_OEMUND, cfg.fontcharset, fw_dontcare, TRUE);
859
860         if (bold_mode == BOLD_FONT) {
861             f(FONT_BOLD, cfg.fontcharset, fw_bold, FALSE);
862             f(FONT_BOLDUND, cfg.fontcharset, fw_bold, TRUE);
863         }
864     }
865 #undef f
866
867     descent = tm.tmAscent + 1;
868     if (descent >= font_height)
869         descent = font_height - 1;
870     firstchar = tm.tmFirstChar;
871
872     for (i=0; i<8; i++) {
873         if (fonts[i]) {
874             if (SelectObject (hdc, fonts[i]) &&
875                 GetTextMetrics(hdc, &tm) )
876                  fsize[i] = tm.tmAveCharWidth + 256 * tm.tmHeight;
877             else fsize[i] = -i;
878         }
879         else fsize[i] = -i;
880     }
881
882     ReleaseDC (hwnd, hdc);
883
884     /* ... This is wrong in OEM only mode */
885     if (fsize[FONT_UNDERLINE] != fsize[FONT_NORMAL] ||
886         (bold_mode == BOLD_FONT &&
887          fsize[FONT_BOLDUND] != fsize[FONT_BOLD])) {
888         und_mode = UND_LINE;
889         DeleteObject (fonts[FONT_UNDERLINE]);
890         if (bold_mode == BOLD_FONT)
891             DeleteObject (fonts[FONT_BOLDUND]);
892     }
893
894     if (bold_mode == BOLD_FONT &&
895         fsize[FONT_BOLD] != fsize[FONT_NORMAL]) {
896         bold_mode = BOLD_SHADOW;
897         DeleteObject (fonts[FONT_BOLD]);
898         if (und_mode == UND_FONT)
899             DeleteObject (fonts[FONT_BOLDUND]);
900     }
901
902 #ifdef CHECKOEMFONT
903     /* With the fascist font painting it doesn't matter if the linedraw font
904      * isn't exactly the right size anymore so we don't have to check this.
905      */
906     if (cfg.vtmode == VT_OEMANSI && fsize[FONT_OEM] != fsize[FONT_NORMAL] ) {
907         if( cfg.fontcharset == OEM_CHARSET )
908         {
909             MessageBox(NULL, "The OEM and ANSI versions of this font are\n"
910                    "different sizes. Using OEM-only mode instead",
911                    "Font Size Mismatch", MB_ICONINFORMATION | MB_OK);
912             cfg.vtmode = VT_OEMONLY;
913         }
914         else if( firstchar < ' ' )
915         {
916             MessageBox(NULL, "The OEM and ANSI versions of this font are\n"
917                    "different sizes. Using XTerm mode instead",
918                    "Font Size Mismatch", MB_ICONINFORMATION | MB_OK);
919             cfg.vtmode = VT_XWINDOWS;
920         }
921         else
922         {
923             MessageBox(NULL, "The OEM and ANSI versions of this font are\n"
924                    "different sizes. Using ISO8859-1 mode instead",
925                    "Font Size Mismatch", MB_ICONINFORMATION | MB_OK);
926             cfg.vtmode = VT_POORMAN;
927         }
928
929         for (i=0; i<8; i++)
930             if (fonts[i])
931                 DeleteObject (fonts[i]);
932         goto font_messup;
933     }
934 #endif
935 }
936
937 void request_resize (int w, int h, int refont) {
938     int width, height;
939
940     /* If the window is maximized supress resizing attempts */
941     if(IsZoomed(hwnd)) return;
942     
943 #ifdef CHECKOEMFONT
944     /* Don't do this in OEMANSI, you may get disable messages */
945     if (refont && w != cols && (cols==80 || cols==132)
946           && cfg.vtmode != VT_OEMANSI)
947 #else
948     if (refont && w != cols && (cols==80 || cols==132))
949 #endif
950     {
951        /* If font width too big for screen should we shrink the font more ? */
952         if (w==132)
953             font_width = ((font_width*cols+w/2)/w);
954         else
955             font_width = 0;
956         {
957             int i;
958             for (i=0; i<8; i++)
959                 if (fonts[i])
960                     DeleteObject(fonts[i]);
961         }
962         bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
963         und_mode = UND_FONT;
964         init_fonts(font_width);
965     }
966     else
967     {
968        static int first_time = 1;
969        static RECT ss;
970
971        switch(first_time)
972        {
973        case 1:
974              /* Get the size of the screen */
975              if (GetClientRect(GetDesktopWindow(),&ss))
976                 /* first_time = 0 */;
977              else { first_time = 2; break; }
978        case 0:
979              /* Make sure the values are sane */
980              width  = (ss.right-ss.left-extra_width  ) / font_width;
981              height = (ss.bottom-ss.top-extra_height ) / font_height;
982
983              if (w>width)  w=width;
984              if (h>height) h=height;
985              if (w<15) w = 15;
986              if (h<1) w = 1;
987        }
988     }
989
990     width = extra_width + font_width * w;
991     height = extra_height + font_height * h;
992
993     SetWindowPos (hwnd, NULL, 0, 0, width, height,
994                   SWP_NOACTIVATE | SWP_NOCOPYBITS |
995                   SWP_NOMOVE | SWP_NOZORDER);
996 }
997
998 static void click (Mouse_Button b, int x, int y) {
999     int thistime = GetMessageTime();
1000
1001     if (lastbtn == b && thistime - lasttime < dbltime) {
1002         lastact = (lastact == MA_CLICK ? MA_2CLK :
1003                    lastact == MA_2CLK ? MA_3CLK :
1004                    lastact == MA_3CLK ? MA_CLICK : MA_NOTHING);
1005     } else {
1006         lastbtn = b;
1007         lastact = MA_CLICK;
1008     }
1009     if (lastact != MA_NOTHING)
1010         term_mouse (b, lastact, x, y);
1011     lasttime = thistime;
1012 }
1013
1014 static LRESULT CALLBACK WndProc (HWND hwnd, UINT message,
1015                                  WPARAM wParam, LPARAM lParam) {
1016     HDC hdc;
1017     static int ignore_size = FALSE;
1018     static int ignore_clip = FALSE;
1019     static int just_reconfigged = FALSE;
1020     static int resizing = FALSE;
1021
1022     switch (message) {
1023       case WM_TIMER:
1024         if (pending_netevent)
1025             enact_pending_netevent();
1026         if (inbuf_head)
1027             term_out();
1028         HideCaret(hwnd);
1029         term_update();
1030         ShowCaret(hwnd);
1031         if (cfg.ping_interval > 0)
1032         {
1033            time_t now;
1034            time(&now);
1035            if (now-last_movement > cfg.ping_interval * 60 - 10)
1036            {
1037               back->special(TS_PING);
1038               last_movement = now;
1039            }
1040         }
1041         return 0;
1042       case WM_CREATE:
1043         break;
1044       case WM_CLOSE:
1045         if (!cfg.warn_on_close || session_closed ||
1046             MessageBox(hwnd, "Are you sure you want to close this session?",
1047                        "PuTTY Exit Confirmation",
1048                        MB_ICONWARNING | MB_OKCANCEL) == IDOK)
1049             DestroyWindow(hwnd);
1050         return 0;
1051       case WM_DESTROY:
1052         PostQuitMessage (0);
1053         return 0;
1054       case WM_SYSCOMMAND:
1055         switch (wParam & ~0xF) {       /* low 4 bits reserved to Windows */
1056           case IDM_SHOWLOG:
1057             showeventlog(hwnd);
1058             break;
1059           case IDM_NEWSESS:
1060           case IDM_DUPSESS:
1061           case IDM_SAVEDSESS:
1062             {
1063                 char b[2048];
1064                 char c[30], *cl;
1065                 int freecl = FALSE;
1066                 STARTUPINFO si;
1067                 PROCESS_INFORMATION pi;
1068                 HANDLE filemap = NULL;
1069
1070                 if (wParam == IDM_DUPSESS) {
1071                     /*
1072                      * Allocate a file-mapping memory chunk for the
1073                      * config structure.
1074                      */
1075                     SECURITY_ATTRIBUTES sa;
1076                     Config *p;
1077
1078                     sa.nLength = sizeof(sa);
1079                     sa.lpSecurityDescriptor = NULL;
1080                     sa.bInheritHandle = TRUE;
1081                     filemap = CreateFileMapping((HANDLE)0xFFFFFFFF,
1082                                                 &sa,
1083                                                 PAGE_READWRITE,
1084                                                 0,
1085                                                 sizeof(Config),
1086                                                 NULL);
1087                     if (filemap) {
1088                         p = (Config *)MapViewOfFile(filemap,
1089                                                     FILE_MAP_WRITE,
1090                                                     0, 0, sizeof(Config));
1091                         if (p) {
1092                             *p = cfg;  /* structure copy */
1093                             UnmapViewOfFile(p);
1094                         }
1095                     }
1096                     sprintf(c, "putty &%p", filemap);
1097                     cl = c;
1098                 } else if (wParam == IDM_SAVEDSESS) {
1099                     char *session = sessions[(lParam - IDM_SAVED_MIN) / 16];
1100                     cl = malloc(16 + strlen(session)); /* 8, but play safe */
1101                     if (!cl)
1102                         cl = NULL;     /* not a very important failure mode */
1103                     else {
1104                         sprintf(cl, "putty @%s", session);
1105                         freecl = TRUE;
1106                     }
1107                 } else
1108                     cl = NULL;
1109
1110                 GetModuleFileName (NULL, b, sizeof(b)-1);
1111                 si.cb = sizeof(si);
1112                 si.lpReserved = NULL;
1113                 si.lpDesktop = NULL;
1114                 si.lpTitle = NULL;
1115                 si.dwFlags = 0;
1116                 si.cbReserved2 = 0;
1117                 si.lpReserved2 = NULL;
1118                 CreateProcess (b, cl, NULL, NULL, TRUE,
1119                                NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
1120
1121                 if (filemap)
1122                     CloseHandle(filemap);
1123                 if (freecl)
1124                     free(cl);
1125             }
1126             break;
1127           case IDM_RECONF:
1128             if (!do_reconfig(hwnd))
1129                 break;
1130             just_reconfigged = TRUE;
1131             {
1132                 int i;
1133                 for (i=0; i<8; i++)
1134                     if (fonts[i])
1135                         DeleteObject(fonts[i]);
1136             }
1137             bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
1138             und_mode = UND_FONT;
1139             init_fonts(0);
1140             sfree(logpal);
1141             /* Telnet will change local echo -> remote if the remote asks */
1142             if (cfg.protocol != PROT_TELNET)
1143                 ldisc = (cfg.ldisc_term ? &ldisc_term : &ldisc_simple);
1144             if (pal)
1145                 DeleteObject(pal);
1146             logpal = NULL;
1147             pal = NULL;
1148             cfgtopalette();
1149             init_palette();
1150
1151             /* Enable or disable the scroll bar, etc */
1152             {
1153                 LONG nflg, flag = GetWindowLong(hwnd, GWL_STYLE);
1154
1155                 nflg = flag;
1156                 if (cfg.scrollbar) nflg |=  WS_VSCROLL;
1157                 else               nflg &= ~WS_VSCROLL;
1158                 if (cfg.locksize) 
1159                    nflg &= ~(WS_THICKFRAME|WS_MAXIMIZEBOX);
1160                 else              
1161                    nflg |= (WS_THICKFRAME|WS_MAXIMIZEBOX);
1162
1163                 if (nflg != flag)
1164                 {
1165                     RECT cr, wr;
1166
1167                     SetWindowLong(hwnd, GWL_STYLE, nflg);
1168                     SendMessage (hwnd, WM_IGNORE_SIZE, 0, 0);
1169                     SetWindowPos(hwnd, NULL, 0,0,0,0,
1170                          SWP_NOACTIVATE|SWP_NOCOPYBITS|
1171                          SWP_NOMOVE|SWP_NOSIZE| SWP_NOZORDER|
1172                          SWP_FRAMECHANGED);
1173
1174                     GetWindowRect (hwnd, &wr);
1175                     GetClientRect (hwnd, &cr);
1176                     extra_width = wr.right - wr.left - cr.right + cr.left;
1177                     extra_height = wr.bottom - wr.top - cr.bottom + cr.top;
1178                 }
1179             }
1180
1181             term_size(cfg.height, cfg.width, cfg.savelines);
1182             InvalidateRect(hwnd, NULL, TRUE);
1183             SetWindowPos (hwnd, NULL, 0, 0,
1184                           extra_width + font_width * cfg.width,
1185                           extra_height + font_height * cfg.height,
1186                           SWP_NOACTIVATE | SWP_NOCOPYBITS |
1187                           SWP_NOMOVE | SWP_NOZORDER);
1188             if (IsIconic(hwnd)) {
1189                 SetWindowText (hwnd,
1190                                cfg.win_name_always ? window_name : icon_name);
1191             }
1192             break;
1193           case IDM_CLRSB:
1194             term_clrsb();
1195             break;
1196           case IDM_RESET:
1197             term_pwron();
1198             break;
1199           case IDM_TEL_AYT: back->special (TS_AYT); break;
1200           case IDM_TEL_BRK: back->special (TS_BRK); break;
1201           case IDM_TEL_SYNCH: back->special (TS_SYNCH); break;
1202           case IDM_TEL_EC: back->special (TS_EC); break;
1203           case IDM_TEL_EL: back->special (TS_EL); break;
1204           case IDM_TEL_GA: back->special (TS_GA); break;
1205           case IDM_TEL_NOP: back->special (TS_NOP); break;
1206           case IDM_TEL_ABORT: back->special (TS_ABORT); break;
1207           case IDM_TEL_AO: back->special (TS_AO); break;
1208           case IDM_TEL_IP: back->special (TS_IP); break;
1209           case IDM_TEL_SUSP: back->special (TS_SUSP); break;
1210           case IDM_TEL_EOR: back->special (TS_EOR); break;
1211           case IDM_TEL_EOF: back->special (TS_EOF); break;
1212           case IDM_ABOUT:
1213             showabout (hwnd);
1214             break;
1215         default:
1216           if (wParam >= IDM_SAVED_MIN && wParam <= IDM_SAVED_MAX) {
1217             SendMessage(hwnd, WM_SYSCOMMAND, IDM_SAVEDSESS, wParam);
1218           }
1219         }
1220         break;
1221
1222 #define X_POS(l) ((int)(short)LOWORD(l))
1223 #define Y_POS(l) ((int)(short)HIWORD(l))
1224
1225 #define TO_CHR_X(x) (((x)<0 ? (x)-font_width+1 : (x)) / font_width)
1226 #define TO_CHR_Y(y) (((y)<0 ? (y)-font_height+1: (y)) / font_height)
1227
1228       case WM_LBUTTONDOWN:
1229         click (MB_SELECT, TO_CHR_X(X_POS(lParam)),
1230                TO_CHR_Y(Y_POS(lParam)));
1231         SetCapture(hwnd);
1232         return 0;
1233       case WM_LBUTTONUP:
1234         term_mouse (MB_SELECT, MA_RELEASE, TO_CHR_X(X_POS(lParam)),
1235                     TO_CHR_Y(Y_POS(lParam)));
1236         ReleaseCapture();
1237         return 0;
1238       case WM_MBUTTONDOWN:
1239         SetCapture(hwnd);
1240         click (cfg.mouse_is_xterm ? MB_PASTE : MB_EXTEND,
1241                TO_CHR_X(X_POS(lParam)),
1242                TO_CHR_Y(Y_POS(lParam)));
1243         return 0;
1244       case WM_MBUTTONUP:
1245         term_mouse (cfg.mouse_is_xterm ? MB_PASTE : MB_EXTEND,
1246                     MA_RELEASE, TO_CHR_X(X_POS(lParam)),
1247                     TO_CHR_Y(Y_POS(lParam)));
1248         ReleaseCapture();
1249         return 0;
1250       case WM_RBUTTONDOWN:
1251         SetCapture(hwnd);
1252         click (cfg.mouse_is_xterm ? MB_EXTEND : MB_PASTE,
1253                TO_CHR_X(X_POS(lParam)),
1254                TO_CHR_Y(Y_POS(lParam)));
1255         return 0;
1256       case WM_RBUTTONUP:
1257         term_mouse (cfg.mouse_is_xterm ? MB_EXTEND : MB_PASTE,
1258                     MA_RELEASE, TO_CHR_X(X_POS(lParam)),
1259                     TO_CHR_Y(Y_POS(lParam)));
1260         ReleaseCapture();
1261         return 0;
1262       case WM_MOUSEMOVE:
1263         /*
1264          * Add the mouse position and message time to the random
1265          * number noise, if we're using ssh.
1266          */
1267         if (cfg.protocol == PROT_SSH)
1268             noise_ultralight(lParam);
1269
1270         if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)) {
1271             Mouse_Button b;
1272             if (wParam & MK_LBUTTON)
1273                 b = MB_SELECT;
1274             else if (wParam & MK_MBUTTON)
1275                 b = cfg.mouse_is_xterm ? MB_PASTE : MB_EXTEND;
1276             else
1277                 b = cfg.mouse_is_xterm ? MB_EXTEND : MB_PASTE;
1278             term_mouse (b, MA_DRAG, TO_CHR_X(X_POS(lParam)),
1279                         TO_CHR_Y(Y_POS(lParam)));
1280         }
1281         return 0;
1282       case WM_IGNORE_CLIP:
1283         ignore_clip = wParam;          /* don't panic on DESTROYCLIPBOARD */
1284         break;
1285       case WM_DESTROYCLIPBOARD:
1286         if (!ignore_clip)
1287             term_deselect();
1288         ignore_clip = FALSE;
1289         return 0;
1290       case WM_PAINT:
1291         {
1292             PAINTSTRUCT p;
1293             HideCaret(hwnd);
1294             hdc = BeginPaint (hwnd, &p);
1295             if (pal) {
1296                 SelectPalette (hdc, pal, TRUE);
1297                 RealizePalette (hdc);
1298             }
1299             term_paint (hdc, p.rcPaint.left, p.rcPaint.top,
1300                         p.rcPaint.right, p.rcPaint.bottom);
1301             SelectObject (hdc, GetStockObject(SYSTEM_FONT));
1302             SelectObject (hdc, GetStockObject(WHITE_PEN));
1303             EndPaint (hwnd, &p);
1304             ShowCaret(hwnd);
1305         }
1306         return 0;
1307       case WM_NETEVENT:
1308         /* Notice we can get multiple netevents, FD_READ, FD_WRITE etc
1309          * but the only one that's likely to try to overload us is FD_READ.
1310          * This means buffering just one is fine.
1311          */
1312         if (pending_netevent)
1313             enact_pending_netevent();
1314
1315         pending_netevent = TRUE;
1316         pend_netevent_wParam=wParam;
1317         pend_netevent_lParam=lParam;
1318         time(&last_movement);
1319         return 0;
1320       case WM_SETFOCUS:
1321         has_focus = TRUE;
1322         CreateCaret(hwnd, caretbm, 0, 0);
1323         ShowCaret(hwnd);
1324         term_out();
1325         term_update();
1326         break;
1327       case WM_KILLFOCUS:
1328         has_focus = FALSE;
1329         DestroyCaret();
1330         term_out();
1331         term_update();
1332         break;
1333       case WM_IGNORE_SIZE:
1334         ignore_size = TRUE;            /* don't panic on next WM_SIZE msg */
1335         break;
1336       case WM_ENTERSIZEMOVE:
1337         EnableSizeTip(1);
1338         resizing = TRUE;
1339         break;
1340       case WM_EXITSIZEMOVE:
1341         EnableSizeTip(0);
1342         resizing = FALSE;
1343         back->size();
1344         break;
1345       case WM_SIZING:
1346         {
1347             int width, height, w, h, ew, eh;
1348             LPRECT r = (LPRECT)lParam;
1349
1350             width = r->right - r->left - extra_width;
1351             height = r->bottom - r->top - extra_height;
1352             w = (width + font_width/2) / font_width; if (w < 1) w = 1;
1353             h = (height + font_height/2) / font_height; if (h < 1) h = 1;
1354             UpdateSizeTip(hwnd, w, h);
1355             ew = width - w * font_width;
1356             eh = height - h * font_height;
1357             if (ew != 0) {
1358                 if (wParam == WMSZ_LEFT ||
1359                     wParam == WMSZ_BOTTOMLEFT ||
1360                     wParam == WMSZ_TOPLEFT)
1361                     r->left += ew;
1362                 else
1363                     r->right -= ew;
1364             }
1365             if (eh != 0) {
1366                 if (wParam == WMSZ_TOP ||
1367                     wParam == WMSZ_TOPRIGHT ||
1368                     wParam == WMSZ_TOPLEFT)
1369                     r->top += eh;
1370                 else
1371                     r->bottom -= eh;
1372             }
1373             if (ew || eh)
1374                 return 1;
1375             else
1376                 return 0;
1377         }
1378         /* break;  (never reached) */
1379       case WM_SIZE:
1380         if (wParam == SIZE_MINIMIZED) {
1381             SetWindowText (hwnd,
1382                            cfg.win_name_always ? window_name : icon_name);
1383             break;
1384         }
1385         if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
1386             SetWindowText (hwnd, window_name);
1387         if (!ignore_size) {
1388             int width, height, w, h;
1389 #if 0 /* we have fixed this using WM_SIZING now */
1390             int ew, eh;
1391 #endif
1392
1393             width = LOWORD(lParam);
1394             height = HIWORD(lParam);
1395             w = width / font_width; if (w < 1) w = 1;
1396             h = height / font_height; if (h < 1) h = 1;
1397 #if 0 /* we have fixed this using WM_SIZING now */
1398             ew = width - w * font_width;
1399             eh = height - h * font_height;
1400             if (ew != 0 || eh != 0) {
1401                 RECT r;
1402                 GetWindowRect (hwnd, &r);
1403                 SendMessage (hwnd, WM_IGNORE_SIZE, 0, 0);
1404                 SetWindowPos (hwnd, NULL, 0, 0,
1405                               r.right - r.left - ew, r.bottom - r.top - eh,
1406                               SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER);
1407             }
1408 #endif
1409             if (w != cols || h != rows || just_reconfigged) {
1410                 term_invalidate();
1411                 term_size (h, w, cfg.savelines);
1412                 /*
1413                  * Don't call back->size in mid-resize. (To prevent
1414                  * massive numbers of resize events getting sent
1415                  * down the connection during an NT opaque drag.)
1416                  */
1417                 if (!resizing)
1418                     back->size();
1419                 just_reconfigged = FALSE;
1420             }
1421         }
1422         ignore_size = FALSE;
1423         return 0;
1424       case WM_VSCROLL:
1425         switch (LOWORD(wParam)) {
1426           case SB_BOTTOM: term_scroll(-1, 0); break;
1427           case SB_TOP: term_scroll(+1, 0); break;
1428           case SB_LINEDOWN: term_scroll (0, +1); break;
1429           case SB_LINEUP: term_scroll (0, -1); break;
1430           case SB_PAGEDOWN: term_scroll (0, +rows/2); break;
1431           case SB_PAGEUP: term_scroll (0, -rows/2); break;
1432           case SB_THUMBPOSITION: case SB_THUMBTRACK:
1433             term_scroll (1, HIWORD(wParam)); break;
1434         }
1435         break; 
1436      case WM_PALETTECHANGED:
1437         if ((HWND) wParam != hwnd && pal != NULL) {
1438             HDC hdc = get_ctx();
1439             if (hdc) {
1440                 if (RealizePalette (hdc) > 0)
1441                     UpdateColors (hdc);
1442                 free_ctx (hdc);
1443             }
1444         }
1445         break;
1446       case WM_QUERYNEWPALETTE:
1447         if (pal != NULL) {
1448             HDC hdc = get_ctx();
1449             if (hdc) {
1450                 if (RealizePalette (hdc) > 0)
1451                     UpdateColors (hdc);
1452                 free_ctx (hdc);
1453                 return TRUE;
1454             }
1455         }
1456         return FALSE;
1457       case WM_KEYDOWN:
1458       case WM_SYSKEYDOWN:
1459       case WM_KEYUP:
1460       case WM_SYSKEYUP:
1461         /*
1462          * Add the scan code and keypress timing to the random
1463          * number noise, if we're using ssh.
1464          */
1465         if (cfg.protocol == PROT_SSH)
1466             noise_ultralight(lParam);
1467
1468         /*
1469          * We don't do TranslateMessage since it disassociates the
1470          * resulting CHAR message from the KEYDOWN that sparked it,
1471          * which we occasionally don't want. Instead, we process
1472          * KEYDOWN, and call the Win32 translator functions so that
1473          * we get the translations under _our_ control.
1474          */
1475         {
1476             unsigned char buf[20];
1477             int len;
1478
1479             len = TranslateKey (message, wParam, lParam, buf);
1480             if (len == -1)
1481                 return DefWindowProc (hwnd, message, wParam, lParam);
1482             ldisc->send (buf, len);
1483         }
1484         return 0;
1485       case WM_CHAR:
1486       case WM_SYSCHAR:
1487         /*
1488          * Nevertheless, we are prepared to deal with WM_CHAR
1489          * messages, should they crop up. So if someone wants to
1490          * post the things to us as part of a macro manoeuvre,
1491          * we're ready to cope.
1492          */
1493         {
1494             char c = xlat_kbd2tty((unsigned char)wParam);
1495             ldisc->send (&c, 1);
1496         }
1497         return 0;
1498     }
1499
1500     return DefWindowProc (hwnd, message, wParam, lParam);
1501 }
1502
1503 /*
1504  * Move the system caret. (We maintain one, even though it's
1505  * invisible, for the benefit of blind people: apparently some
1506  * helper software tracks the system caret, so we should arrange to
1507  * have one.)
1508  */
1509 void sys_cursor(int x, int y) {
1510     SetCaretPos(x * font_width, y * font_height);
1511 }
1512
1513 /*
1514  * Draw a line of text in the window, at given character
1515  * coordinates, in given attributes.
1516  *
1517  * We are allowed to fiddle with the contents of `text'.
1518  */
1519 void do_text (Context ctx, int x, int y, char *text, int len,
1520               unsigned long attr, int lattr) {
1521     COLORREF fg, bg, t;
1522     int nfg, nbg, nfont;
1523     HDC hdc = ctx;
1524     RECT line_box;
1525     int force_manual_underline = 0;
1526     int fnt_width = font_width*(1+(lattr!=LATTR_NORM));
1527     static int *IpDx = 0, IpDxLEN = 0;;
1528
1529     if (len>IpDxLEN || IpDx[0] != fnt_width) {
1530         int i;
1531         if (len>IpDxLEN) {
1532             sfree(IpDx);
1533             IpDx = smalloc((len+16)*sizeof(int));
1534             IpDxLEN = (len+16);
1535         }
1536         for(i=0; i<IpDxLEN; i++)
1537             IpDx[i] = fnt_width;
1538     }
1539
1540     x *= fnt_width;
1541     y *= font_height;
1542
1543     if (attr & ATTR_ACTCURS) {
1544         attr &= (bold_mode == BOLD_COLOURS ? 0x300200 : 0x300300);
1545         attr ^= ATTR_CUR_XOR;
1546     }
1547
1548     nfont = 0;
1549     if (cfg.vtmode == VT_OEMONLY)
1550         nfont |= FONT_OEM;
1551
1552     /*
1553      * Map high-half characters in order to approximate ISO using
1554      * OEM character set. No characters are missing if the OEM codepage
1555      * is CP850.
1556      */
1557     if (nfont & FONT_OEM) {
1558         int i;
1559         for (i=0; i<len; i++)
1560             if (text[i] >= '\xA0' && text[i] <= '\xFF') {
1561 #if 0
1562                 /* This is CP850 ... perfect translation */
1563                 static const char oemhighhalf[] =
1564                     "\x20\xAD\xBD\x9C\xCF\xBE\xDD\xF5" /* A0-A7 */
1565                     "\xF9\xB8\xA6\xAE\xAA\xF0\xA9\xEE" /* A8-AF */
1566                     "\xF8\xF1\xFD\xFC\xEF\xE6\xF4\xFA" /* B0-B7 */
1567                     "\xF7\xFB\xA7\xAF\xAC\xAB\xF3\xA8" /* B8-BF */
1568                     "\xB7\xB5\xB6\xC7\x8E\x8F\x92\x80" /* C0-C7 */
1569                     "\xD4\x90\xD2\xD3\xDE\xD6\xD7\xD8" /* C8-CF */
1570                     "\xD1\xA5\xE3\xE0\xE2\xE5\x99\x9E" /* D0-D7 */
1571                     "\x9D\xEB\xE9\xEA\x9A\xED\xE8\xE1" /* D8-DF */
1572                     "\x85\xA0\x83\xC6\x84\x86\x91\x87" /* E0-E7 */
1573                     "\x8A\x82\x88\x89\x8D\xA1\x8C\x8B" /* E8-EF */
1574                     "\xD0\xA4\x95\xA2\x93\xE4\x94\xF6" /* F0-F7 */
1575                     "\x9B\x97\xA3\x96\x81\xEC\xE7\x98" /* F8-FF */
1576                     ;
1577 #endif
1578                 /* This is CP437 ... junk translation */
1579                 static const unsigned char oemhighhalf[] = {
1580                     0xff, 0xad, 0x9b, 0x9c, 0x6f, 0x9d, 0x7c, 0x15,
1581                     0x22, 0x43, 0xa6, 0xae, 0xaa, 0x2d, 0x52, 0xc4,
1582                     0xf8, 0xf1, 0xfd, 0x33, 0x27, 0xe6, 0x14, 0xfa,
1583                     0x2c, 0x31, 0xa7, 0xaf, 0xac, 0xab, 0x2f, 0xa8,
1584                     0x41, 0x41, 0x41, 0x41, 0x8e, 0x8f, 0x92, 0x80,
1585                     0x45, 0x90, 0x45, 0x45, 0x49, 0x49, 0x49, 0x49,
1586                     0x44, 0xa5, 0x4f, 0x4f, 0x4f, 0x4f, 0x99, 0x78,
1587                     0xed, 0x55, 0x55, 0x55, 0x9a, 0x59, 0x50, 0xe1,
1588                     0x85, 0xa0, 0x83, 0x61, 0x84, 0x86, 0x91, 0x87,
1589                     0x8a, 0x82, 0x88, 0x89, 0x8d, 0xa1, 0x8c, 0x8b,
1590                     0x0b, 0xa4, 0x95, 0xa2, 0x93, 0x6f, 0x94, 0xf6,
1591                     0xed, 0x97, 0xa3, 0x96, 0x81, 0x79, 0x70, 0x98
1592                 };
1593
1594                 text[i] = oemhighhalf[(unsigned char)text[i] - 0xA0];
1595             }
1596     }
1597
1598     if (attr & ATTR_LINEDRW) {
1599         int i;
1600         /* ISO 8859-1 */
1601         static const char poorman[] =
1602             "*#****\xB0\xB1**+++++-----++++|****\xA3\xB7";
1603
1604         /* CP437 */
1605         static const char oemmap_437[] =
1606             "\x04\xB1****\xF8\xF1**\xD9\xBF\xDA\xC0\xC5"
1607             "\xC4\xC4\xC4\xC4\xC4\xC3\xB4\xC1\xC2\xB3\xF3\xF2\xE3*\x9C\xFA";
1608
1609         /* CP850 */
1610         static const char oemmap_850[] =
1611             "\x04\xB1****\xF8\xF1**\xD9\xBF\xDA\xC0\xC5"
1612             "\xC4\xC4\xC4\xC4\xC4\xC3\xB4\xC1\xC2\xB3****\x9C\xFA";
1613
1614         /* Poor windows font ... eg: windows courier */
1615         static const char oemmap[] =
1616             "*\xB1****\xF8\xF1**\xD9\xBF\xDA\xC0\xC5"
1617             "\xC4\xC4\xC4\xC4\xC4\xC3\xB4\xC1\xC2\xB3****\x9C\xFA";
1618
1619         /*
1620          * Line drawing mapping: map ` thru ~ (0x60 thru 0x7E) to
1621          * VT100 line drawing chars; everything else stays normal.
1622          *
1623          * Actually '_' maps to space too, but that's done before.
1624          */
1625         switch (cfg.vtmode) {
1626           case VT_XWINDOWS:
1627             for (i=0; i<len; i++)
1628                 if (text[i] >= '\x60' && text[i] <= '\x7E')
1629                     text[i] += '\x01' - '\x60';
1630             break;
1631           case VT_OEMANSI:
1632             /* Make sure we actually have an OEM font */
1633             if (fonts[nfont|FONT_OEM]) { 
1634           case VT_OEMONLY:
1635                 nfont |= FONT_OEM;
1636                 for (i=0; i<len; i++)
1637                     if (text[i] >= '\x60' && text[i] <= '\x7E')
1638                         text[i] = oemmap[(unsigned char)text[i] - 0x60];
1639                 break;
1640             }
1641           case VT_POORMAN:
1642             for (i=0; i<len; i++)
1643                 if (text[i] >= '\x60' && text[i] <= '\x7E')
1644                     text[i] = poorman[(unsigned char)text[i] - 0x60];
1645             break;
1646         }
1647     }
1648
1649     nfg = 2 * ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
1650     nbg = 2 * ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
1651     if (bold_mode == BOLD_FONT && (attr & ATTR_BOLD))
1652         nfont |= FONT_BOLD;
1653     if (und_mode == UND_FONT && (attr & ATTR_UNDER))
1654         nfont |= FONT_UNDERLINE;
1655     if (!fonts[nfont]) 
1656     {
1657         if (nfont&FONT_UNDERLINE)
1658             force_manual_underline = 1;
1659         /* Don't do the same for manual bold, it could be bad news. */
1660
1661         nfont &= ~(FONT_BOLD|FONT_UNDERLINE);
1662     }
1663     if (font_needs_hand_underlining && (attr & ATTR_UNDER))
1664         force_manual_underline = 1;
1665     if (attr & ATTR_REVERSE) {
1666         t = nfg; nfg = nbg; nbg = t;
1667     }
1668     if (bold_mode == BOLD_COLOURS && (attr & ATTR_BOLD))
1669         nfg++;
1670     if (bold_mode == BOLD_COLOURS && (attr & ATTR_BLINK))
1671         nbg++;
1672     fg = colours[nfg];
1673     bg = colours[nbg];
1674     SelectObject (hdc, fonts[nfont]);
1675     SetTextColor (hdc, fg);
1676     SetBkColor (hdc, bg);
1677     SetBkMode (hdc, OPAQUE);
1678     line_box.left   = x;
1679     line_box.top    = y;
1680     line_box.right  = x+fnt_width*len;
1681     line_box.bottom = y+font_height;
1682     ExtTextOut (hdc, x, y, ETO_CLIPPED|ETO_OPAQUE, &line_box, text, len, IpDx);
1683     if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
1684         SetBkMode (hdc, TRANSPARENT);
1685
1686        /* GRR: This draws the character outside it's box and can leave
1687         * 'droppings' even with the clip box! I suppose I could loop it
1688         * one character at a time ... yuk. 
1689         * 
1690         * Or ... I could do a test print with "W", and use +1 or -1 for this
1691         * shift depending on if the leftmost column is blank...
1692         */
1693         ExtTextOut (hdc, x-1, y, ETO_CLIPPED, &line_box, text, len, IpDx);
1694     }
1695     if (force_manual_underline || 
1696             (und_mode == UND_LINE && (attr & ATTR_UNDER))) {
1697         HPEN oldpen;
1698         oldpen = SelectObject (hdc, CreatePen(PS_SOLID, 0, fg));
1699         MoveToEx (hdc, x, y+descent, NULL);
1700         LineTo (hdc, x+len*fnt_width, y+descent);
1701         oldpen = SelectObject (hdc, oldpen);
1702         DeleteObject (oldpen);
1703     }
1704     if (attr & ATTR_PASCURS) {
1705         POINT pts[5];
1706         HPEN oldpen;
1707         pts[0].x = pts[1].x = pts[4].x = x;
1708         pts[2].x = pts[3].x = x+fnt_width-1;
1709         pts[0].y = pts[3].y = pts[4].y = y;
1710         pts[1].y = pts[2].y = y+font_height-1;
1711         oldpen = SelectObject (hdc, CreatePen(PS_SOLID, 0, colours[23]));
1712         Polyline (hdc, pts, 5);
1713         oldpen = SelectObject (hdc, oldpen);
1714         DeleteObject (oldpen);
1715     }
1716 }
1717
1718 static int check_compose(int first, int second) {
1719
1720     static char * composetbl[] = {
1721        "++#", "AA@", "(([", "//\\", "))]", "(-{", "-)}", "/^|", "!!¡", "C/¢",
1722        "C|¢", "L-£", "L=£", "XO¤", "X0¤", "Y-¥", "Y=¥", "||¦", "SO§", "S!§",
1723        "S0§", "\"\"¨", "CO©", "C0©", "A_ª", "<<«", ",-¬", "--­", "RO®",
1724        "-^¯", "0^°", "+-±", "2^²", "3^³", "''´", "/Uµ", "P!¶", ".^·", ",,¸",
1725        "1^¹", "O_º", ">>»", "14¼", "12½", "34¾", "??¿", "`AÀ", "'AÁ", "^AÂ",
1726        "~AÃ", "\"AÄ", "*AÅ", "AEÆ", ",CÇ", "`EÈ", "'EÉ", "^EÊ", "\"EË",
1727        "`IÌ", "'IÍ", "^IÎ", "\"IÏ", "-DÐ", "~NÑ", "`OÒ", "'OÓ", "^OÔ",
1728        "~OÕ", "\"OÖ", "XX×", "/OØ", "`UÙ", "'UÚ", "^UÛ", "\"UÜ", "'YÝ",
1729        "HTÞ", "ssß", "`aà", "'aá", "^aâ", "~aã", "\"aä", "*aå", "aeæ", ",cç",
1730        "`eè", "'eé", "^eê", "\"eë", "`iì", "'ií", "^iî", "\"iï", "-dð", "~nñ",
1731        "`oò", "'oó", "^oô", "~oõ", "\"oö", ":-÷", "o/ø", "`uù", "'uú", "^uû",
1732        "\"uü", "'yý", "htþ", "\"yÿ",
1733     0};
1734
1735     char ** c;
1736     static int recurse = 0;
1737     int nc = -1;
1738
1739     if(0)
1740     {
1741         char buf[256];
1742         char * p;
1743         sprintf(buf, "cc(%d,%d)", first, second);
1744         for(p=buf; *p; p++)
1745             c_write1(*p);
1746     }
1747
1748     for(c=composetbl; *c; c++) {
1749         if( (*c)[0] == first && (*c)[1] == second)
1750         {
1751             return (*c)[2] & 0xFF;
1752         }
1753     }
1754
1755     if(recurse==0)
1756     {
1757         recurse=1;
1758         nc = check_compose(second, first);
1759         if(nc == -1)
1760             nc = check_compose(toupper(first), toupper(second));
1761         if(nc == -1)
1762             nc = check_compose(toupper(second), toupper(first));
1763         recurse=0;
1764     }
1765     return nc;
1766 }
1767
1768
1769 /*
1770  * Translate a WM_(SYS)?KEY(UP|DOWN) message into a string of ASCII
1771  * codes. Returns number of bytes used or zero to drop the message
1772  * or -1 to forward the message to windows.
1773  */
1774 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, unsigned char *output) {
1775     BYTE keystate[256];
1776     int  scan, left_alt = 0, key_down, shift_state;
1777     int  r, i, code;
1778     unsigned char * p = output;
1779
1780     static WORD keys[3];
1781     static int compose_state = 0;
1782     static int compose_char = 0;
1783     static WPARAM compose_key = 0;
1784
1785     r = GetKeyboardState(keystate);
1786     if (!r) memset(keystate, 0, sizeof(keystate));
1787     else
1788     {
1789 #if 0
1790        {  /* Tell us all about key events */
1791           static BYTE oldstate[256];
1792           static int first = 1;
1793           static int scan;
1794           int ch;
1795           if(first) memcpy(oldstate, keystate, sizeof(oldstate));
1796           first=0;
1797
1798           if ((HIWORD(lParam)&(KF_UP|KF_REPEAT))==KF_REPEAT) {
1799              debug(("+"));
1800           } else if ((HIWORD(lParam)&KF_UP) && scan==(HIWORD(lParam) & 0xFF) ) {
1801              debug((". U"));
1802           } else {
1803              debug((".\n"));
1804              if (wParam >= VK_F1 && wParam <= VK_F20 )
1805                 debug(("K_F%d", wParam+1-VK_F1));
1806              else switch(wParam)
1807              {
1808              case VK_SHIFT:   debug(("SHIFT")); break;
1809              case VK_CONTROL: debug(("CTRL")); break;
1810              case VK_MENU:    debug(("ALT")); break;
1811              default:         debug(("VK_%02x", wParam));
1812              }
1813              if(message == WM_SYSKEYDOWN || message == WM_SYSKEYUP)
1814                 debug(("*"));
1815              debug((", S%02x", scan=(HIWORD(lParam) & 0xFF) ));
1816
1817              ch = MapVirtualKey(wParam, 2);
1818              if (ch>=' ' && ch<='~') debug((", '%c'", ch));
1819              else if (ch)            debug((", $%02x", ch));
1820
1821              if (keys[0]) debug((", KB0=%02x", keys[0]));
1822              if (keys[1]) debug((", KB1=%02x", keys[1]));
1823              if (keys[2]) debug((", KB2=%02x", keys[2]));
1824
1825              if ( (keystate[VK_SHIFT]&0x80)!=0)     debug((", S"));
1826              if ( (keystate[VK_CONTROL]&0x80)!=0)   debug((", C"));
1827              if ( (HIWORD(lParam)&KF_EXTENDED) )    debug((", E"));
1828              if ( (HIWORD(lParam)&KF_UP) )          debug((", U"));
1829           }
1830
1831           if ((HIWORD(lParam)&(KF_UP|KF_REPEAT))==KF_REPEAT)
1832              ;
1833           else if ( (HIWORD(lParam)&KF_UP) ) 
1834              oldstate[wParam&0xFF] ^= 0x80;
1835           else
1836              oldstate[wParam&0xFF] ^= 0x81;
1837
1838           for(ch=0; ch<256; ch++)
1839              if (oldstate[ch] != keystate[ch])
1840                 debug((", M%02x=%02x", ch, keystate[ch]));
1841
1842           memcpy(oldstate, keystate, sizeof(oldstate));
1843        }
1844 #endif
1845
1846         /* Note if AltGr was pressed and if it was used as a compose key */
1847         if (wParam == VK_MENU && (HIWORD(lParam)&KF_EXTENDED))
1848         {
1849             keystate[VK_RMENU] = keystate[VK_MENU];
1850             if (!compose_state) compose_key = wParam;
1851         }
1852         if (wParam == VK_APPS && !compose_state)
1853             compose_key = wParam;
1854
1855         if (wParam == compose_key)
1856         {
1857             if (compose_state == 0 && (HIWORD(lParam)&(KF_UP|KF_REPEAT))==0)
1858                compose_state = 1;
1859             else if (compose_state == 1 && (HIWORD(lParam)&KF_UP))
1860                compose_state = 2;
1861             else
1862                compose_state = 0;
1863         }
1864         else if (compose_state==1 && wParam != VK_CONTROL)
1865            compose_state = 0;
1866
1867         /* Nastyness with NUMLock - Shift-NUMLock is left alone though */
1868         if ( (cfg.funky_type == 3 || (cfg.funky_type <= 1 && app_keypad_keys))
1869               && wParam==VK_NUMLOCK && !(keystate[VK_SHIFT]&0x80)) {
1870
1871             wParam = VK_EXECUTE;
1872
1873             /* UnToggle NUMLock */
1874             if ((HIWORD(lParam)&(KF_UP|KF_REPEAT))==0)
1875                 keystate[VK_NUMLOCK] ^= 1;
1876         }
1877
1878         /* And write back the 'adjusted' state */
1879         SetKeyboardState (keystate);
1880     }
1881
1882     /* Disable Auto repeat if required */
1883     if (repeat_off && (HIWORD(lParam)&(KF_UP|KF_REPEAT))==KF_REPEAT)
1884        return 0;
1885
1886     if ((HIWORD(lParam)&KF_ALTDOWN) && (keystate[VK_RMENU]&0x80) == 0)
1887         left_alt = 1;
1888
1889     key_down = ((HIWORD(lParam)&KF_UP)==0);
1890
1891     /* Make sure Ctrl-ALT is not the same as AltGr for ToAscii */
1892     if (left_alt && (keystate[VK_CONTROL]&0x80))
1893         keystate[VK_MENU] = 0;
1894
1895     scan = (HIWORD(lParam) & (KF_UP | KF_EXTENDED | 0xFF));
1896     shift_state = ((keystate[VK_SHIFT]&0x80)!=0)
1897                 + ((keystate[VK_CONTROL]&0x80)!=0)*2;
1898
1899     /* 
1900      * Record that we pressed key so the scroll window can be reset, but
1901      * be careful to avoid Shift-UP/Down
1902      */
1903     if( wParam != VK_SHIFT && wParam != VK_PRIOR && wParam != VK_NEXT ) {
1904         seen_key_event = 1; 
1905     }
1906
1907     /* Make sure we're not pasting */
1908     if (key_down) term_nopaste();
1909
1910     if (compose_state>1 && left_alt) compose_state = 0;
1911
1912     /* Sanitize the number pad if not using a PC NumPad */
1913     if( left_alt || (app_keypad_keys && cfg.funky_type != 2)
1914         || cfg.funky_type == 3 || cfg.nethack_keypad || compose_state )
1915     {
1916         if ((HIWORD(lParam)&KF_EXTENDED) == 0)
1917         {
1918             int nParam = 0;
1919             switch(wParam)
1920             {
1921             case VK_INSERT:     nParam = VK_NUMPAD0; break;
1922             case VK_END:        nParam = VK_NUMPAD1; break;
1923             case VK_DOWN:       nParam = VK_NUMPAD2; break;
1924             case VK_NEXT:       nParam = VK_NUMPAD3; break;
1925             case VK_LEFT:       nParam = VK_NUMPAD4; break;
1926             case VK_CLEAR:      nParam = VK_NUMPAD5; break;
1927             case VK_RIGHT:      nParam = VK_NUMPAD6; break;
1928             case VK_HOME:       nParam = VK_NUMPAD7; break;
1929             case VK_UP:         nParam = VK_NUMPAD8; break;
1930             case VK_PRIOR:      nParam = VK_NUMPAD9; break;
1931             case VK_DELETE:     nParam = VK_DECIMAL; break;
1932             }
1933             if (nParam)
1934             {
1935                 if (keystate[VK_NUMLOCK]&1) shift_state |= 1;
1936                 wParam = nParam;
1937             }
1938         }
1939     }
1940
1941     /* If a key is pressed and AltGr is not active */
1942     if (key_down && (keystate[VK_RMENU]&0x80) == 0 && !compose_state)
1943     {
1944         /* Okay, prepare for most alts then ...*/
1945         if (left_alt) *p++ = '\033';
1946
1947         /* Lets see if it's a pattern we know all about ... */
1948         if (wParam == VK_PRIOR && shift_state == 1) {
1949             SendMessage (hwnd, WM_VSCROLL, SB_PAGEUP, 0);
1950             return 0;
1951         }
1952         if (wParam == VK_NEXT && shift_state == 1) {
1953             SendMessage (hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
1954             return 0;
1955         }
1956         if (wParam == VK_INSERT && shift_state == 1) {
1957             term_mouse (MB_PASTE, MA_CLICK, 0, 0);
1958             term_mouse (MB_PASTE, MA_RELEASE, 0, 0);
1959             return 0;
1960         }
1961         if (left_alt && wParam == VK_F4 && cfg.alt_f4) {
1962             return -1;
1963         }
1964         if (left_alt && wParam == VK_SPACE && cfg.alt_space) {
1965             
1966             SendMessage (hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0);
1967             return -1;
1968         }
1969         /* Control-Numlock for app-keypad mode switch */
1970         if (wParam == VK_PAUSE && shift_state == 2) {
1971             app_keypad_keys ^= 1;
1972             return 0;
1973         }
1974
1975         /* Nethack keypad */
1976         if (cfg.nethack_keypad && !left_alt) {
1977            switch(wParam) {
1978                case VK_NUMPAD1: *p++ = shift_state ? 'B': 'b'; return p-output;
1979                case VK_NUMPAD2: *p++ = shift_state ? 'J': 'j'; return p-output;
1980                case VK_NUMPAD3: *p++ = shift_state ? 'N': 'n'; return p-output;
1981                case VK_NUMPAD4: *p++ = shift_state ? 'H': 'h'; return p-output;
1982                case VK_NUMPAD5: *p++ = shift_state ? '.': '.'; return p-output;
1983                case VK_NUMPAD6: *p++ = shift_state ? 'L': 'l'; return p-output;
1984                case VK_NUMPAD7: *p++ = shift_state ? 'Y': 'y'; return p-output;
1985                case VK_NUMPAD8: *p++ = shift_state ? 'K': 'k'; return p-output;
1986                case VK_NUMPAD9: *p++ = shift_state ? 'U': 'u'; return p-output;
1987            }
1988         }
1989
1990         /* Application Keypad */
1991         if (!left_alt) {
1992            int xkey = 0;
1993
1994            if ( cfg.funky_type == 3 ||
1995               ( cfg.funky_type <= 1 && app_keypad_keys)) switch(wParam) {
1996                case VK_EXECUTE: xkey = 'P'; break;
1997                case VK_DIVIDE:  xkey = 'Q'; break;
1998                case VK_MULTIPLY:xkey = 'R'; break;
1999                case VK_SUBTRACT:xkey = 'S'; break;
2000            }
2001            if(app_keypad_keys) switch(wParam) {
2002                case VK_NUMPAD0: xkey = 'p'; break;
2003                case VK_NUMPAD1: xkey = 'q'; break;
2004                case VK_NUMPAD2: xkey = 'r'; break;
2005                case VK_NUMPAD3: xkey = 's'; break;
2006                case VK_NUMPAD4: xkey = 't'; break;
2007                case VK_NUMPAD5: xkey = 'u'; break;
2008                case VK_NUMPAD6: xkey = 'v'; break;
2009                case VK_NUMPAD7: xkey = 'w'; break;
2010                case VK_NUMPAD8: xkey = 'x'; break;
2011                case VK_NUMPAD9: xkey = 'y'; break;
2012
2013                case VK_DECIMAL: xkey = 'n'; break;
2014                case VK_ADD:     if(cfg.funky_type==2) { 
2015                                     if(shift_state) xkey = 'l';
2016                                     else            xkey = 'k';
2017                                 } else if(shift_state)  xkey = 'm'; 
2018                                   else                  xkey = 'l';
2019                                 break;
2020
2021                case VK_DIVIDE:  if(cfg.funky_type==2) xkey = 'o'; break;
2022                case VK_MULTIPLY:if(cfg.funky_type==2) xkey = 'j'; break;
2023                case VK_SUBTRACT:if(cfg.funky_type==2) xkey = 'm'; break;
2024
2025                case VK_RETURN:
2026                                 if (HIWORD(lParam)&KF_EXTENDED)
2027                                     xkey = 'M';
2028                                 break;
2029             }
2030             if(xkey)
2031             {
2032                 if (vt52_mode)
2033                 {
2034                     if (xkey>='P' && xkey<='S')
2035                         p += sprintf((char *)p, "\x1B%c", xkey); 
2036                     else
2037                         p += sprintf((char *)p, "\x1B?%c", xkey); 
2038                 }
2039                 else 
2040                     p += sprintf((char *)p, "\x1BO%c", xkey); 
2041                 return p - output;
2042             }
2043         }
2044
2045         if (wParam == VK_BACK && shift_state == 0 )     /* Backspace */
2046         {
2047             *p++ = (cfg.bksp_is_delete ? 0x7F : 0x08);
2048             return p-output;
2049         }
2050         if (wParam == VK_TAB && shift_state == 1 )      /* Shift tab */
2051         {
2052             *p++ = 0x1B; *p++ = '['; *p++ = 'Z'; return p - output;
2053         }
2054         if (wParam == VK_SPACE && shift_state == 2 )    /* Ctrl-Space */
2055         {
2056             *p++ = 0; return p - output;
2057         }
2058         if (wParam == VK_SPACE && shift_state == 3 )    /* Ctrl-Shift-Space */
2059         {
2060             *p++ = 160; return p - output;
2061         }
2062         if (wParam == VK_CANCEL && shift_state == 2 )   /* Ctrl-Break */
2063         {
2064             *p++ = 3; return p - output;
2065         }
2066         /* Control-2 to Control-8 are special */
2067         if (shift_state == 2 && wParam >= '2' && wParam <= '8')
2068         {
2069             *p++ = "\000\033\034\035\036\037\177"[wParam-'2'];
2070             return p - output;
2071         }
2072         if (shift_state == 2 && wParam == 0xBD) {
2073             *p++ = 0x1F;
2074             return p - output;
2075         }
2076         if (shift_state == 2 && wParam == 0xDF) {
2077             *p++ = 0x1C;
2078             return p - output;
2079         }
2080         if (shift_state == 0 && wParam == VK_RETURN && cr_lf_return) {
2081             *p++ = '\r'; *p++ = '\n';
2082             return p - output;
2083         }
2084
2085         /*
2086          * Next, all the keys that do tilde codes. (ESC '[' nn '~',
2087          * for integer decimal nn.)
2088          *
2089          * We also deal with the weird ones here. Linux VCs replace F1
2090          * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
2091          * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
2092          * respectively.
2093          */
2094         code = 0;
2095         switch (wParam) {
2096           case VK_F1: code = (keystate[VK_SHIFT] & 0x80 ? 23 : 11); break;
2097           case VK_F2: code = (keystate[VK_SHIFT] & 0x80 ? 24 : 12); break;
2098           case VK_F3: code = (keystate[VK_SHIFT] & 0x80 ? 25 : 13); break;
2099           case VK_F4: code = (keystate[VK_SHIFT] & 0x80 ? 26 : 14); break;
2100           case VK_F5: code = (keystate[VK_SHIFT] & 0x80 ? 28 : 15); break;
2101           case VK_F6: code = (keystate[VK_SHIFT] & 0x80 ? 29 : 17); break;
2102           case VK_F7: code = (keystate[VK_SHIFT] & 0x80 ? 31 : 18); break;
2103           case VK_F8: code = (keystate[VK_SHIFT] & 0x80 ? 32 : 19); break;
2104           case VK_F9: code = (keystate[VK_SHIFT] & 0x80 ? 33 : 20); break;
2105           case VK_F10: code = (keystate[VK_SHIFT] & 0x80 ? 34 : 21); break;
2106           case VK_F11: code = 23; break;
2107           case VK_F12: code = 24; break;
2108           case VK_F13: code = 25; break;
2109           case VK_F14: code = 26; break;
2110           case VK_F15: code = 28; break;
2111           case VK_F16: code = 29; break;
2112           case VK_F17: code = 31; break;
2113           case VK_F18: code = 32; break;
2114           case VK_F19: code = 33; break;
2115           case VK_F20: code = 34; break;
2116           case VK_HOME: code = 1; break;
2117           case VK_INSERT: code = 2; break;
2118           case VK_DELETE: code = 3; break;
2119           case VK_END: code = 4; break;
2120           case VK_PRIOR: code = 5; break;
2121           case VK_NEXT: code = 6; break;
2122         }
2123         /* Reorder edit keys to physical order */
2124         if (cfg.funky_type == 3 && code <= 6 ) code = "\0\2\1\4\5\3\6"[code];
2125
2126         if (cfg.funky_type == 1 && code >= 11 && code <= 15) {
2127             p += sprintf((char *)p, "\x1B[[%c", code + 'A' - 11);
2128             return p - output;
2129         }
2130         if (cfg.funky_type == 2 && code >= 11 && code <= 14) {
2131             if (vt52_mode)
2132                 p += sprintf((char *)p, "\x1B%c", code + 'P' - 11);
2133             else
2134                 p += sprintf((char *)p, "\x1BO%c", code + 'P' - 11);
2135             return p - output;
2136         }
2137         if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
2138             p += sprintf((char *)p, code == 1 ? "\x1B[H" : "\x1BOw");
2139             return p - output;
2140         }
2141         if (code) {
2142             p += sprintf((char *)p, "\x1B[%d~", code);
2143             return p - output;
2144         }
2145
2146         /*
2147          * Now the remaining keys (arrows and Keypad 5. Keypad 5 for
2148          * some reason seems to send VK_CLEAR to Windows...).
2149          */
2150         {
2151             char xkey = 0;
2152             switch (wParam) {
2153                 case VK_UP:     xkey = 'A'; break;
2154                 case VK_DOWN:   xkey = 'B'; break;
2155                 case VK_RIGHT:  xkey = 'C'; break;
2156                 case VK_LEFT:   xkey = 'D'; break;
2157                 case VK_CLEAR:  xkey = 'G'; break;
2158             }
2159             if (xkey)
2160             {
2161                 if (vt52_mode)
2162                     p += sprintf((char *)p, "\x1B%c", xkey);
2163                 else if (app_cursor_keys)
2164                     p += sprintf((char *)p, "\x1BO%c", xkey);
2165                 else
2166                     p += sprintf((char *)p, "\x1B[%c", xkey);
2167                 return p - output;
2168             }
2169         }
2170
2171         /*
2172          * Finally, deal with Return ourselves. (Win95 seems to
2173          * foul it up when Alt is pressed, for some reason.)
2174          */
2175         if (wParam == VK_RETURN)       /* Return */
2176         {
2177             *p++ = 0x0D;
2178             return p-output;
2179         }
2180     }
2181
2182     /* Okay we've done everything interesting; let windows deal with 
2183      * the boring stuff */
2184     {
2185         BOOL capsOn=keystate[VK_CAPITAL] !=0;
2186
2187         /* helg: clear CAPS LOCK state if caps lock switches to cyrillic */
2188         if(cfg.xlat_capslockcyr)
2189             keystate[VK_CAPITAL] = 0;
2190
2191         r = ToAscii (wParam, scan, keystate, keys, 0);
2192         if(r>0)
2193         {
2194             p = output;
2195             for(i=0; i<r; i++)
2196             {
2197                 unsigned char ch = (unsigned char)keys[i];
2198
2199                 if (compose_state==2 && (ch&0x80) == 0 && ch>' ') {
2200                     compose_char = ch;
2201                     compose_state ++;
2202                     continue;
2203                 }
2204                 if (compose_state==3 && (ch&0x80) == 0 && ch>' ') {
2205                     int nc;
2206                     compose_state = 0;
2207
2208                     if ((nc=check_compose(compose_char,ch)) == -1)
2209                     {
2210                         c_write1('\007');
2211                         return 0;
2212                     }
2213                     *p++ = xlat_kbd2tty((unsigned char)nc);
2214                     return p-output;
2215                 }
2216
2217                 compose_state = 0;
2218
2219                 if( left_alt && key_down ) *p++ = '\033';
2220                 if (!key_down)
2221                     *p++ = ch;
2222                 else
2223                 {
2224                     if(capsOn)
2225                         ch = xlat_latkbd2win(ch);
2226                     *p++ = xlat_kbd2tty(ch);
2227                 }
2228             }
2229
2230             /* This is so the ALT-Numpad and dead keys work correctly. */
2231             keys[0] = 0;
2232
2233             return p-output;
2234         }
2235     }
2236
2237     /* This stops ALT press-release doing a 'COMMAND MENU' function */
2238     if (message == WM_SYSKEYUP && wParam == VK_MENU) 
2239         return 0;
2240
2241     return -1;
2242 }
2243
2244 void set_title (char *title) {
2245     sfree (window_name);
2246     window_name = smalloc(1+strlen(title));
2247     strcpy (window_name, title);
2248     if (cfg.win_name_always || !IsIconic(hwnd))
2249         SetWindowText (hwnd, title);
2250 }
2251
2252 void set_icon (char *title) {
2253     sfree (icon_name);
2254     icon_name = smalloc(1+strlen(title));
2255     strcpy (icon_name, title);
2256     if (!cfg.win_name_always && IsIconic(hwnd))
2257         SetWindowText (hwnd, title);
2258 }
2259
2260 void set_sbar (int total, int start, int page) {
2261     SCROLLINFO si;
2262
2263     if (!cfg.scrollbar) return;
2264
2265     si.cbSize = sizeof(si);
2266     si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
2267     si.nMin = 0;
2268     si.nMax = total - 1;
2269     si.nPage = page;
2270     si.nPos = start;
2271     if (hwnd)
2272         SetScrollInfo (hwnd, SB_VERT, &si, TRUE);
2273 }
2274
2275 Context get_ctx(void) {
2276     HDC hdc;
2277     if (hwnd) {
2278         hdc = GetDC (hwnd);
2279         if (hdc && pal)
2280             SelectPalette (hdc, pal, FALSE);
2281         return hdc;
2282     } else
2283         return NULL;
2284 }
2285
2286 void free_ctx (Context ctx) {
2287     SelectPalette (ctx, GetStockObject (DEFAULT_PALETTE), FALSE);
2288     ReleaseDC (hwnd, ctx);
2289 }
2290
2291 static void real_palette_set (int n, int r, int g, int b) {
2292     if (pal) {
2293         logpal->palPalEntry[n].peRed = r;
2294         logpal->palPalEntry[n].peGreen = g;
2295         logpal->palPalEntry[n].peBlue = b;
2296         logpal->palPalEntry[n].peFlags = PC_NOCOLLAPSE;
2297         colours[n] = PALETTERGB(r, g, b);
2298         SetPaletteEntries (pal, 0, NCOLOURS, logpal->palPalEntry);
2299     } else
2300         colours[n] = RGB(r, g, b);
2301 }
2302
2303 void palette_set (int n, int r, int g, int b) {
2304     static const int first[21] = {
2305         0, 2, 4, 6, 8, 10, 12, 14,
2306         1, 3, 5, 7, 9, 11, 13, 15,
2307         16, 17, 18, 20, 22
2308     };
2309     real_palette_set (first[n], r, g, b);
2310     if (first[n] >= 18)
2311         real_palette_set (first[n]+1, r, g, b);
2312     if (pal) {
2313         HDC hdc = get_ctx();
2314         UnrealizeObject (pal);
2315         RealizePalette (hdc);
2316         free_ctx (hdc);
2317     }
2318 }
2319
2320 void palette_reset (void) {
2321     int i;
2322
2323     for (i = 0; i < NCOLOURS; i++) {
2324         if (pal) {
2325             logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
2326             logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
2327             logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
2328             logpal->palPalEntry[i].peFlags = 0;
2329             colours[i] = PALETTERGB(defpal[i].rgbtRed,
2330                                     defpal[i].rgbtGreen,
2331                                     defpal[i].rgbtBlue);
2332         } else
2333             colours[i] = RGB(defpal[i].rgbtRed,
2334                              defpal[i].rgbtGreen,
2335                              defpal[i].rgbtBlue);
2336     }
2337
2338     if (pal) {
2339         HDC hdc;
2340         SetPaletteEntries (pal, 0, NCOLOURS, logpal->palPalEntry);
2341         hdc = get_ctx();
2342         RealizePalette (hdc);
2343         free_ctx (hdc);
2344     }
2345 }
2346
2347 void write_clip (void *data, int len, int must_deselect) {
2348     HGLOBAL clipdata;
2349     void *lock;
2350
2351     clipdata = GlobalAlloc (GMEM_DDESHARE | GMEM_MOVEABLE, len + 1);
2352     if (!clipdata)
2353         return;
2354     lock = GlobalLock (clipdata);
2355     if (!lock)
2356         return;
2357     memcpy (lock, data, len);
2358     ((unsigned char *) lock) [len] = 0;
2359     GlobalUnlock (clipdata);
2360
2361     if (!must_deselect)
2362         SendMessage (hwnd, WM_IGNORE_CLIP, TRUE, 0);
2363
2364     if (OpenClipboard (hwnd)) {
2365         EmptyClipboard();
2366         SetClipboardData (CF_TEXT, clipdata);
2367         CloseClipboard();
2368     } else
2369         GlobalFree (clipdata);
2370
2371     if (!must_deselect)
2372         SendMessage (hwnd, WM_IGNORE_CLIP, FALSE, 0);
2373 }
2374
2375 void get_clip (void **p, int *len) {
2376     static HGLOBAL clipdata = NULL;
2377
2378     if (!p) {
2379         if (clipdata)
2380             GlobalUnlock (clipdata);
2381         clipdata = NULL;
2382         return;
2383     } else {
2384         if (OpenClipboard (NULL)) {
2385             clipdata = GetClipboardData (CF_TEXT);
2386             CloseClipboard();
2387             if (clipdata) {
2388                 *p = GlobalLock (clipdata);
2389                 if (*p) {
2390                     *len = strlen(*p);
2391                     return;
2392                 }
2393             }
2394         }
2395     }
2396
2397     *p = NULL;
2398     *len = 0;
2399 }
2400
2401 /*
2402  * Move `lines' lines from position `from' to position `to' in the
2403  * window.
2404  */
2405 void optimised_move (int to, int from, int lines) {
2406     RECT r;
2407     int min, max;
2408
2409     min = (to < from ? to : from);
2410     max = to + from - min;
2411
2412     r.left = 0; r.right = cols * font_width;
2413     r.top = min * font_height; r.bottom = (max+lines) * font_height;
2414     ScrollWindow (hwnd, 0, (to - from) * font_height, &r, &r);
2415 }
2416
2417 /*
2418  * Print a message box and perform a fatal exit.
2419  */
2420 void fatalbox(char *fmt, ...) {
2421     va_list ap;
2422     char stuff[200];
2423
2424     va_start(ap, fmt);
2425     vsprintf(stuff, fmt, ap);
2426     va_end(ap);
2427     MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
2428     exit(1);
2429 }
2430
2431 /*
2432  * Beep.
2433  */
2434 void beep(int errorbeep) {
2435     static long last_beep = 0;
2436     long now, beep_diff;
2437
2438     now = GetTickCount();
2439     beep_diff = now-last_beep;
2440
2441     /* Make sure we only respond to one beep per packet or so */
2442     if (beep_diff>=0 && beep_diff<50)
2443         return;
2444
2445     if(errorbeep)
2446        MessageBeep(MB_ICONHAND);
2447     else
2448        MessageBeep(MB_OK);
2449
2450     last_beep = GetTickCount();
2451 }