14 #define PUTTY_DO_GLOBALS /* actually _define_ globals */
18 #define IDM_SHOWLOG 0x0010
19 #define IDM_NEWSESS 0x0020
20 #define IDM_DUPSESS 0x0030
21 #define IDM_RECONF 0x0040
22 #define IDM_CLRSB 0x0050
23 #define IDM_RESET 0x0060
24 #define IDM_TEL_AYT 0x0070
25 #define IDM_TEL_BRK 0x0080
26 #define IDM_TEL_SYNCH 0x0090
27 #define IDM_TEL_EC 0x00a0
28 #define IDM_TEL_EL 0x00b0
29 #define IDM_TEL_GA 0x00c0
30 #define IDM_TEL_NOP 0x00d0
31 #define IDM_TEL_ABORT 0x00e0
32 #define IDM_TEL_AO 0x00f0
33 #define IDM_TEL_IP 0x0100
34 #define IDM_TEL_SUSP 0x0110
35 #define IDM_TEL_EOR 0x0120
36 #define IDM_TEL_EOF 0x0130
37 #define IDM_ABOUT 0x0140
38 #define IDM_SAVEDSESS 0x0150
40 #define IDM_SAVED_MIN 0x1000
41 #define IDM_SAVED_MAX 0x2000
43 #define WM_IGNORE_SIZE (WM_XUSER + 1)
44 #define WM_IGNORE_CLIP (WM_XUSER + 2)
46 static LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
47 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, unsigned char *output);
48 static void cfgtopalette(void);
49 static void init_palette(void);
50 static void init_fonts(int);
52 static int extra_width, extra_height;
54 static int pending_netevent = 0;
55 static WPARAM pend_netevent_wParam = 0;
56 static LPARAM pend_netevent_lParam = 0;
57 static void enact_pending_netevent(void);
61 #define FONT_UNDERLINE 2
62 #define FONT_BOLDUND 3
64 #define FONT_OEMBOLD 5
65 #define FONT_OEMBOLDUND 6
67 static HFONT fonts[8];
68 static int font_needs_hand_underlining;
70 BOLD_COLOURS, BOLD_SHADOW, BOLD_FONT
78 static COLORREF colours[NCOLOURS];
80 static LPLOGPALETTE logpal;
81 static RGBTRIPLE defpal[NCOLOURS];
85 static int dbltime, lasttime, lastact;
86 static Mouse_Button lastbtn;
88 static char *window_name, *icon_name;
90 static Ldisc *real_ldisc;
92 void begin_session(void) {
96 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) {
97 static char appname[] = "PuTTY";
102 int guess_width, guess_height;
105 flags = FLAG_VERBOSE | FLAG_INTERACTIVE;
107 winsock_ver = MAKEWORD(1, 1);
108 if (WSAStartup(winsock_ver, &wsadata)) {
109 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
110 MB_OK | MB_ICONEXCLAMATION);
113 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
114 MessageBox(NULL, "WinSock version is incompatible with 1.1",
115 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
119 /* WISHLIST: maybe allow config tweaking even if winsock not present? */
121 InitCommonControls();
124 * Process the command line.
129 default_protocol = DEFAULT_PROTOCOL;
130 default_port = DEFAULT_PORT;
135 while (*p && isspace(*p)) p++;
138 * Process command line options first. Yes, this can be
139 * done better, and it will be as soon as I have the
143 char *q = p + strcspn(p, " \t");
146 tolower(p[0]) == 's' &&
147 tolower(p[1]) == 's' &&
148 tolower(p[2]) == 'h') {
149 default_protocol = cfg.protocol = PROT_SSH;
150 default_port = cfg.port = 22;
151 } else if (q == p + 3 &&
152 tolower(p[0]) == 'l' &&
153 tolower(p[1]) == 'o' &&
154 tolower(p[2]) == 'g') {
155 logfile = "putty.log";
156 } else if (q == p + 7 &&
157 tolower(p[0]) == 'c' &&
158 tolower(p[1]) == 'l' &&
159 tolower(p[2]) == 'e' &&
160 tolower(p[3]) == 'a' &&
161 tolower(p[4]) == 'n' &&
162 tolower(p[5]) == 'u' &&
163 tolower(p[6]) == 'p') {
165 * `putty -cleanup'. Remove all registry entries
166 * associated with PuTTY, and also find and delete
167 * the random seed file.
170 "This procedure will remove ALL Registry\n"
171 "entries associated with PuTTY, and will\n"
172 "also remove the PuTTY random seed file.\n"
174 "THIS PROCESS WILL DESTROY YOUR SAVED\n"
175 "SESSIONS. Are you really sure you want\n"
178 MB_YESNO | MB_ICONWARNING) == IDYES) {
179 random_destroy_seed();
184 p = q + strspn(q, " \t");
188 * An initial @ means to activate a saved session.
192 if (!*cfg.host && !do_config()) {
196 } else if (*p == '&') {
198 * An initial & means we've been given a command line
199 * containing the hex value of a HANDLE for a file
200 * mapping object, which we must then extract as a
205 if (sscanf(p+1, "%p", &filemap) == 1 &&
206 (cp = MapViewOfFile(filemap, FILE_MAP_READ,
207 0, 0, sizeof(Config))) != NULL) {
210 CloseHandle(filemap);
211 } else if (!do_config()) {
218 * If the hostname starts with "telnet:", set the
219 * protocol to Telnet and process the string as a
222 if (!strncmp(q, "telnet:", 7)) {
226 if (q[0] == '/' && q[1] == '/')
228 cfg.protocol = PROT_TELNET;
230 while (*p && *p != ':' && *p != '/') p++;
238 strncpy (cfg.host, q, sizeof(cfg.host)-1);
239 cfg.host[sizeof(cfg.host)-1] = '\0';
241 while (*p && !isspace(*p)) p++;
244 strncpy (cfg.host, q, sizeof(cfg.host)-1);
245 cfg.host[sizeof(cfg.host)-1] = '\0';
246 while (*p && isspace(*p)) p++;
259 /* See if host is of the form user@host */
260 if (cfg.host[0] != '\0') {
261 char *atsign = strchr(cfg.host, '@');
262 /* Make sure we're not overflowing the user field */
264 if (atsign-cfg.host < sizeof cfg.username) {
265 strncpy (cfg.username, cfg.host, atsign-cfg.host);
266 cfg.username[atsign-cfg.host] = '\0';
268 memmove(cfg.host, atsign+1, 1+strlen(atsign+1));
274 * Select protocol. This is farmed out into a table in a
275 * separate file to enable an ssh-free variant.
280 for (i = 0; backends[i].backend != NULL; i++)
281 if (backends[i].protocol == cfg.protocol) {
282 back = backends[i].backend;
286 MessageBox(NULL, "Unsupported protocol number found",
287 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
293 real_ldisc = (cfg.ldisc_term ? &ldisc_term : &ldisc_simple);
294 /* To start with, we use the simple line discipline, so we can
295 * type passwords etc without fear of them being echoed... */
296 ldisc = &ldisc_simple;
300 wndclass.lpfnWndProc = WndProc;
301 wndclass.cbClsExtra = 0;
302 wndclass.cbWndExtra = 0;
303 wndclass.hInstance = inst;
304 wndclass.hIcon = LoadIcon (inst,
305 MAKEINTRESOURCE(IDI_MAINICON));
306 wndclass.hCursor = LoadCursor (NULL, IDC_IBEAM);
307 wndclass.hbrBackground = GetStockObject (BLACK_BRUSH);
308 wndclass.lpszMenuName = NULL;
309 wndclass.lpszClassName = appname;
311 RegisterClass (&wndclass);
316 savelines = cfg.savelines;
322 * Guess some defaults for the window size. This all gets
323 * updated later, so we don't really care too much. However, we
324 * do want the font width/height guesses to correspond to a
325 * large font rather than a small one...
332 term_size (cfg.height, cfg.width, cfg.savelines);
333 guess_width = extra_width + font_width * cols;
334 guess_height = extra_height + font_height * rows;
337 HWND w = GetDesktopWindow();
338 GetWindowRect (w, &r);
339 if (guess_width > r.right - r.left)
340 guess_width = r.right - r.left;
341 if (guess_height > r.bottom - r.top)
342 guess_height = r.bottom - r.top;
346 int winmode = WS_OVERLAPPEDWINDOW|WS_VSCROLL;
347 if (!cfg.scrollbar) winmode &= ~(WS_VSCROLL);
348 if (cfg.locksize) winmode &= ~(WS_THICKFRAME|WS_MAXIMIZEBOX);
349 hwnd = CreateWindow (appname, appname,
351 CW_USEDEFAULT, CW_USEDEFAULT,
352 guess_width, guess_height,
353 NULL, NULL, inst, NULL);
357 * Initialise the fonts, simultaneously correcting the guesses
358 * for font_{width,height}.
360 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
365 * Correct the guesses for extra_{width,height}.
369 GetWindowRect (hwnd, &wr);
370 GetClientRect (hwnd, &cr);
371 extra_width = wr.right - wr.left - cr.right + cr.left;
372 extra_height = wr.bottom - wr.top - cr.bottom + cr.top;
376 * Resize the window, now we know what size we _really_ want it
379 guess_width = extra_width + font_width * cols;
380 guess_height = extra_height + font_height * rows;
381 SendMessage (hwnd, WM_IGNORE_SIZE, 0, 0);
382 SetWindowPos (hwnd, NULL, 0, 0, guess_width, guess_height,
383 SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
386 * Initialise the scroll bar.
391 si.cbSize = sizeof(si);
392 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
397 SetScrollInfo (hwnd, SB_VERT, &si, FALSE);
401 * Start up the telnet connection.
408 error = back->init (hwnd, cfg.host, cfg.port, &realhost);
410 sprintf(msg, "Unable to open connection:\n%s", error);
411 MessageBox(NULL, msg, "PuTTY Error", MB_ICONERROR | MB_OK);
414 window_name = icon_name = NULL;
415 sprintf(msg, "%s - PuTTY", realhost);
420 session_closed = FALSE;
423 * Set up the input and output buffers.
426 outbuf_reap = outbuf_head = 0;
429 * Prepare the mouse handler.
431 lastact = MA_NOTHING;
432 lastbtn = MB_NOTHING;
433 dbltime = GetDoubleClickTime();
436 * Set up the session-control options on the system menu.
439 HMENU m = GetSystemMenu (hwnd, FALSE);
443 AppendMenu (m, MF_SEPARATOR, 0, 0);
444 if (cfg.protocol == PROT_TELNET) {
446 AppendMenu (p, MF_ENABLED, IDM_TEL_AYT, "Are You There");
447 AppendMenu (p, MF_ENABLED, IDM_TEL_BRK, "Break");
448 AppendMenu (p, MF_ENABLED, IDM_TEL_SYNCH, "Synch");
449 AppendMenu (p, MF_SEPARATOR, 0, 0);
450 AppendMenu (p, MF_ENABLED, IDM_TEL_EC, "Erase Character");
451 AppendMenu (p, MF_ENABLED, IDM_TEL_EL, "Erase Line");
452 AppendMenu (p, MF_ENABLED, IDM_TEL_GA, "Go Ahead");
453 AppendMenu (p, MF_ENABLED, IDM_TEL_NOP, "No Operation");
454 AppendMenu (p, MF_SEPARATOR, 0, 0);
455 AppendMenu (p, MF_ENABLED, IDM_TEL_ABORT, "Abort Process");
456 AppendMenu (p, MF_ENABLED, IDM_TEL_AO, "Abort Output");
457 AppendMenu (p, MF_ENABLED, IDM_TEL_IP, "Interrupt Process");
458 AppendMenu (p, MF_ENABLED, IDM_TEL_SUSP, "Suspend Process");
459 AppendMenu (p, MF_SEPARATOR, 0, 0);
460 AppendMenu (p, MF_ENABLED, IDM_TEL_EOR, "End Of Record");
461 AppendMenu (p, MF_ENABLED, IDM_TEL_EOF, "End Of File");
462 AppendMenu (m, MF_POPUP | MF_ENABLED, (UINT) p, "Telnet Command");
463 AppendMenu (m, MF_SEPARATOR, 0, 0);
465 AppendMenu (m, MF_ENABLED, IDM_SHOWLOG, "&Event Log");
466 AppendMenu (m, MF_SEPARATOR, 0, 0);
467 AppendMenu (m, MF_ENABLED, IDM_NEWSESS, "Ne&w Session");
468 AppendMenu (m, MF_ENABLED, IDM_DUPSESS, "&Duplicate Session");
471 for (i = 1 ; i < ((nsessions < 256) ? nsessions : 256) ; i++)
472 AppendMenu (s, MF_ENABLED, IDM_SAVED_MIN + (16 * i) , sessions[i]);
473 AppendMenu (m, MF_POPUP | MF_ENABLED, (UINT) s, "Sa&ved Sessions");
474 AppendMenu (m, MF_ENABLED, IDM_RECONF, "Chan&ge Settings");
475 AppendMenu (m, MF_SEPARATOR, 0, 0);
476 AppendMenu (m, MF_ENABLED, IDM_CLRSB, "C&lear Scrollback");
477 AppendMenu (m, MF_ENABLED, IDM_RESET, "Rese&t Terminal");
478 AppendMenu (m, MF_SEPARATOR, 0, 0);
479 AppendMenu (m, MF_ENABLED, IDM_ABOUT, "&About PuTTY");
483 * Finally show the window!
485 ShowWindow (hwnd, show);
488 * Set the palette up.
494 has_focus = (GetForegroundWindow() == hwnd);
498 int timer_id = 0, long_timer = 0;
500 while (GetMessage (&msg, NULL, 0, 0) == 1) {
501 /* Sometimes DispatchMessage calls routines that use their own
502 * GetMessage loop, setup this timer so we get some control back.
504 * Also call term_update() from the timer so that if the host
505 * is sending data flat out we still do redraws.
507 if(timer_id && long_timer) {
508 KillTimer(hwnd, timer_id);
509 long_timer = timer_id = 0;
512 timer_id = SetTimer(hwnd, 1, 20, NULL);
513 DispatchMessage (&msg);
515 /* This is too fast, but I'll leave it for now 'cause it shows
516 * how often term_update is called (far too often at times!)
520 /* Send the paste buffer if there's anything to send */
523 /* If there's nothing new in the queue then we can do everything
524 * we've delayed, reading the socket, writing, and repainting
527 if (!PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE)) {
528 if (pending_netevent) {
529 enact_pending_netevent();
534 if (!PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE)) {
536 KillTimer(hwnd, timer_id);
543 timer_id = SetTimer(hwnd, 1, 2000, NULL);
544 else if (cfg.blinktext)
545 timer_id = SetTimer(hwnd, 1, 250, NULL);
547 timer_id = SetTimer(hwnd, 1, 500, NULL);
560 DeleteObject(fonts[i]);
567 if (cfg.protocol == PROT_SSH) {
578 * Print a message box and close the connection.
580 void connection_fatal(char *fmt, ...) {
585 vsprintf(stuff, fmt, ap);
587 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
588 if (cfg.close_on_exit)
591 session_closed = TRUE;
592 SetWindowText (hwnd, "PuTTY (inactive)");
597 * Actually do the job requested by a WM_NETEVENT
599 static void enact_pending_netevent(void) {
601 static int reentering = 0;
604 return; /* don't unpend the pending */
606 pending_netevent = FALSE;
609 i = back->msg (pend_netevent_wParam, pend_netevent_lParam);
614 switch (WSABASEERR + (-i) % 10000) {
616 sprintf(buf, "Connection reset by peer");
619 sprintf(buf, "Unexpected network error %d", -i);
622 connection_fatal(buf);
625 if (cfg.close_on_exit)
628 session_closed = TRUE;
629 MessageBox(hwnd, "Connection closed by remote host",
630 "PuTTY", MB_OK | MB_ICONINFORMATION);
631 SetWindowText (hwnd, "PuTTY (inactive)");
637 * Copy the colour palette from the configuration data into defpal.
638 * This is non-trivial because the colour indices are different.
640 static void cfgtopalette(void) {
642 static const int ww[] = {
643 6, 7, 8, 9, 10, 11, 12, 13,
644 14, 15, 16, 17, 18, 19, 20, 21,
645 0, 1, 2, 3, 4, 4, 5, 5
648 for (i=0; i<24; i++) {
650 defpal[i].rgbtRed = cfg.colours[w][0];
651 defpal[i].rgbtGreen = cfg.colours[w][1];
652 defpal[i].rgbtBlue = cfg.colours[w][2];
657 * Set up the colour palette.
659 static void init_palette(void) {
661 HDC hdc = GetDC (hwnd);
663 if (cfg.try_palette &&
664 GetDeviceCaps (hdc, RASTERCAPS) & RC_PALETTE) {
665 logpal = smalloc(sizeof(*logpal)
666 - sizeof(logpal->palPalEntry)
667 + NCOLOURS * sizeof(PALETTEENTRY));
668 logpal->palVersion = 0x300;
669 logpal->palNumEntries = NCOLOURS;
670 for (i = 0; i < NCOLOURS; i++) {
671 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
672 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
673 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
674 logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
676 pal = CreatePalette (logpal);
678 SelectPalette (hdc, pal, FALSE);
679 RealizePalette (hdc);
680 SelectPalette (hdc, GetStockObject (DEFAULT_PALETTE),
684 ReleaseDC (hwnd, hdc);
687 for (i=0; i<NCOLOURS; i++)
688 colours[i] = PALETTERGB(defpal[i].rgbtRed,
692 for(i=0; i<NCOLOURS; i++)
693 colours[i] = RGB(defpal[i].rgbtRed,
699 * Initialise all the fonts we will need. There may be as many as
700 * eight or as few as one. We also:
702 * - check the font width and height, correcting our guesses if
705 * - verify that the bold font is the same width as the ordinary
706 * one, and engage shadow bolding if not.
708 * - verify that the underlined font is the same width as the
709 * ordinary one (manual underlining by means of line drawing can
710 * be done in a pinch).
712 static void init_fonts(int pick_width) {
717 int fw_dontcare, fw_bold;
726 if (cfg.fontisbold) {
727 fw_dontcare = FW_BOLD;
730 fw_dontcare = FW_DONTCARE;
736 font_height = cfg.fontheight;
737 font_width = pick_width;
740 fonts[i] = CreateFont (font_height, font_width, 0, 0, w, FALSE, u, FALSE, \
741 c, OUT_DEFAULT_PRECIS, \
742 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, \
743 FIXED_PITCH | FF_DONTCARE, cfg.font)
745 if (cfg.vtmode != VT_OEMONLY) {
746 f(FONT_NORMAL, cfg.fontcharset, fw_dontcare, FALSE);
748 SelectObject (hdc, fonts[FONT_NORMAL]);
749 GetTextMetrics(hdc, &tm);
750 font_height = tm.tmHeight;
751 font_width = tm.tmAveCharWidth;
753 f(FONT_UNDERLINE, cfg.fontcharset, fw_dontcare, TRUE);
756 * Some fonts, e.g. 9-pt Courier, draw their underlines
757 * outside their character cell. We successfully prevent
758 * screen corruption by clipping the text output, but then
759 * we lose the underline completely. Here we try to work
760 * out whether this is such a font, and if it is, we set a
761 * flag that causes underlines to be drawn by hand.
763 * Having tried other more sophisticated approaches (such
764 * as examining the TEXTMETRIC structure or requesting the
765 * height of a string), I think we'll do this the brute
766 * force way: we create a small bitmap, draw an underlined
767 * space on it, and test to see whether any pixels are
768 * foreground-coloured. (Since we expect the underline to
769 * go all the way across the character cell, we only search
770 * down a single column of the bitmap, half way across.)
774 HBITMAP und_bm, und_oldbm;
778 und_dc = CreateCompatibleDC(hdc);
779 und_bm = CreateCompatibleBitmap(hdc, font_width, font_height);
780 und_oldbm = SelectObject(und_dc, und_bm);
781 SelectObject(und_dc, fonts[FONT_UNDERLINE]);
782 SetTextAlign(und_dc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
783 SetTextColor (und_dc, RGB(255,255,255));
784 SetBkColor (und_dc, RGB(0,0,0));
785 SetBkMode (und_dc, OPAQUE);
786 ExtTextOut (und_dc, 0, 0, ETO_OPAQUE, NULL, " ", 1, NULL);
788 for (i = 0; i < font_height; i++) {
789 c = GetPixel(und_dc, font_width/2, i);
793 SelectObject(und_dc, und_oldbm);
794 DeleteObject(und_bm);
796 font_needs_hand_underlining = !gotit;
799 if (bold_mode == BOLD_FONT) {
800 f(FONT_BOLD, cfg.fontcharset, fw_bold, FALSE);
801 f(FONT_BOLDUND, cfg.fontcharset, fw_bold, TRUE);
804 if (cfg.vtmode == VT_OEMANSI) {
805 f(FONT_OEM, OEM_CHARSET, fw_dontcare, FALSE);
806 f(FONT_OEMUND, OEM_CHARSET, fw_dontcare, TRUE);
808 if (bold_mode == BOLD_FONT) {
809 f(FONT_OEMBOLD, OEM_CHARSET, fw_bold, FALSE);
810 f(FONT_OEMBOLDUND, OEM_CHARSET, fw_bold, TRUE);
816 f(FONT_OEM, cfg.fontcharset, fw_dontcare, FALSE);
818 SelectObject (hdc, fonts[FONT_OEM]);
819 GetTextMetrics(hdc, &tm);
820 font_height = tm.tmHeight;
821 font_width = tm.tmAveCharWidth;
823 f(FONT_OEMUND, cfg.fontcharset, fw_dontcare, TRUE);
825 if (bold_mode == BOLD_FONT) {
826 f(FONT_BOLD, cfg.fontcharset, fw_bold, FALSE);
827 f(FONT_BOLDUND, cfg.fontcharset, fw_bold, TRUE);
832 descent = tm.tmAscent + 1;
833 if (descent >= font_height)
834 descent = font_height - 1;
835 firstchar = tm.tmFirstChar;
837 for (i=0; i<8; i++) {
839 if (SelectObject (hdc, fonts[i]) &&
840 GetTextMetrics(hdc, &tm) )
841 fsize[i] = tm.tmAveCharWidth + 256 * tm.tmHeight;
847 ReleaseDC (hwnd, hdc);
849 /* ... This is wrong in OEM only mode */
850 if (fsize[FONT_UNDERLINE] != fsize[FONT_NORMAL] ||
851 (bold_mode == BOLD_FONT &&
852 fsize[FONT_BOLDUND] != fsize[FONT_BOLD])) {
854 DeleteObject (fonts[FONT_UNDERLINE]);
855 if (bold_mode == BOLD_FONT)
856 DeleteObject (fonts[FONT_BOLDUND]);
859 if (bold_mode == BOLD_FONT &&
860 fsize[FONT_BOLD] != fsize[FONT_NORMAL]) {
861 bold_mode = BOLD_SHADOW;
862 DeleteObject (fonts[FONT_BOLD]);
863 if (und_mode == UND_FONT)
864 DeleteObject (fonts[FONT_BOLDUND]);
868 /* With the fascist font painting it doesn't matter if the linedraw font
869 * isn't exactly the right size anymore so we don't have to check this.
871 if (cfg.vtmode == VT_OEMANSI && fsize[FONT_OEM] != fsize[FONT_NORMAL] ) {
872 if( cfg.fontcharset == OEM_CHARSET )
874 MessageBox(NULL, "The OEM and ANSI versions of this font are\n"
875 "different sizes. Using OEM-only mode instead",
876 "Font Size Mismatch", MB_ICONINFORMATION | MB_OK);
877 cfg.vtmode = VT_OEMONLY;
879 else if( firstchar < ' ' )
881 MessageBox(NULL, "The OEM and ANSI versions of this font are\n"
882 "different sizes. Using XTerm mode instead",
883 "Font Size Mismatch", MB_ICONINFORMATION | MB_OK);
884 cfg.vtmode = VT_XWINDOWS;
888 MessageBox(NULL, "The OEM and ANSI versions of this font are\n"
889 "different sizes. Using ISO8859-1 mode instead",
890 "Font Size Mismatch", MB_ICONINFORMATION | MB_OK);
891 cfg.vtmode = VT_POORMAN;
896 DeleteObject (fonts[i]);
902 void request_resize (int w, int h, int refont) {
905 /* If the window is maximized supress resizing attempts */
906 if(IsZoomed(hwnd)) return;
909 /* Don't do this in OEMANSI, you may get disable messages */
910 if (refont && w != cols && (cols==80 || cols==132)
911 && cfg.vtmode != VT_OEMANSI)
913 if (refont && w != cols && (cols==80 || cols==132))
916 /* If font width too big for screen should we shrink the font more ? */
918 font_width = ((font_width*cols+w/2)/w);
925 DeleteObject(fonts[i]);
927 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
929 init_fonts(font_width);
933 static int first_time = 1;
939 /* Get the size of the screen */
940 if (GetClientRect(GetDesktopWindow(),&ss))
941 /* first_time = 0 */;
942 else { first_time = 2; break; }
944 /* Make sure the values are sane */
945 width = (ss.right-ss.left-extra_width ) / font_width;
946 height = (ss.bottom-ss.top-extra_height ) / font_height;
948 if (w>width) w=width;
949 if (h>height) h=height;
955 width = extra_width + font_width * w;
956 height = extra_height + font_height * h;
958 SetWindowPos (hwnd, NULL, 0, 0, width, height,
959 SWP_NOACTIVATE | SWP_NOCOPYBITS |
960 SWP_NOMOVE | SWP_NOZORDER);
963 static void click (Mouse_Button b, int x, int y) {
964 int thistime = GetMessageTime();
966 if (lastbtn == b && thistime - lasttime < dbltime) {
967 lastact = (lastact == MA_CLICK ? MA_2CLK :
968 lastact == MA_2CLK ? MA_3CLK :
969 lastact == MA_3CLK ? MA_CLICK : MA_NOTHING);
974 if (lastact != MA_NOTHING)
975 term_mouse (b, lastact, x, y);
979 static LRESULT CALLBACK WndProc (HWND hwnd, UINT message,
980 WPARAM wParam, LPARAM lParam) {
982 static int ignore_size = FALSE;
983 static int ignore_clip = FALSE;
984 static int just_reconfigged = FALSE;
985 static int resizing = FALSE;
989 if (pending_netevent)
990 enact_pending_netevent();
998 if (!cfg.warn_on_close || session_closed ||
999 MessageBox(hwnd, "Are you sure you want to close this session?",
1000 "PuTTY Exit Confirmation",
1001 MB_ICONWARNING | MB_OKCANCEL) == IDOK)
1002 DestroyWindow(hwnd);
1005 PostQuitMessage (0);
1008 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1020 PROCESS_INFORMATION pi;
1021 HANDLE filemap = NULL;
1023 if (wParam == IDM_DUPSESS) {
1025 * Allocate a file-mapping memory chunk for the
1028 SECURITY_ATTRIBUTES sa;
1031 sa.nLength = sizeof(sa);
1032 sa.lpSecurityDescriptor = NULL;
1033 sa.bInheritHandle = TRUE;
1034 filemap = CreateFileMapping((HANDLE)0xFFFFFFFF,
1041 p = (Config *)MapViewOfFile(filemap,
1043 0, 0, sizeof(Config));
1045 *p = cfg; /* structure copy */
1049 sprintf(c, "putty &%p", filemap);
1051 } else if (wParam == IDM_SAVEDSESS) {
1052 char *session = sessions[(lParam - IDM_SAVED_MIN) / 16];
1053 cl = malloc(16 + strlen(session)); /* 8, but play safe */
1055 cl = NULL; /* not a very important failure mode */
1057 sprintf(cl, "putty @%s", session);
1063 GetModuleFileName (NULL, b, sizeof(b)-1);
1065 si.lpReserved = NULL;
1066 si.lpDesktop = NULL;
1070 si.lpReserved2 = NULL;
1071 CreateProcess (b, cl, NULL, NULL, TRUE,
1072 NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
1075 CloseHandle(filemap);
1081 if (!do_reconfig(hwnd))
1083 just_reconfigged = TRUE;
1088 DeleteObject(fonts[i]);
1090 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
1091 und_mode = UND_FONT;
1094 /* Telnet will change local echo -> remote if the remote asks */
1095 if (cfg.protocol != PROT_TELNET)
1096 ldisc = (cfg.ldisc_term ? &ldisc_term : &ldisc_simple);
1104 /* Enable or disable the scroll bar, etc */
1106 LONG nflg, flag = GetWindowLong(hwnd, GWL_STYLE);
1109 if (cfg.scrollbar) nflg |= WS_VSCROLL;
1110 else nflg &= ~WS_VSCROLL;
1112 nflg &= ~(WS_THICKFRAME|WS_MAXIMIZEBOX);
1114 nflg |= (WS_THICKFRAME|WS_MAXIMIZEBOX);
1120 SetWindowLong(hwnd, GWL_STYLE, nflg);
1121 SendMessage (hwnd, WM_IGNORE_SIZE, 0, 0);
1122 SetWindowPos(hwnd, NULL, 0,0,0,0,
1123 SWP_NOACTIVATE|SWP_NOCOPYBITS|
1124 SWP_NOMOVE|SWP_NOSIZE| SWP_NOZORDER|
1127 GetWindowRect (hwnd, &wr);
1128 GetClientRect (hwnd, &cr);
1129 extra_width = wr.right - wr.left - cr.right + cr.left;
1130 extra_height = wr.bottom - wr.top - cr.bottom + cr.top;
1134 term_size(cfg.height, cfg.width, cfg.savelines);
1135 InvalidateRect(hwnd, NULL, TRUE);
1136 SetWindowPos (hwnd, NULL, 0, 0,
1137 extra_width + font_width * cfg.width,
1138 extra_height + font_height * cfg.height,
1139 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1140 SWP_NOMOVE | SWP_NOZORDER);
1141 if (IsIconic(hwnd)) {
1142 SetWindowText (hwnd,
1143 cfg.win_name_always ? window_name : icon_name);
1152 case IDM_TEL_AYT: back->special (TS_AYT); break;
1153 case IDM_TEL_BRK: back->special (TS_BRK); break;
1154 case IDM_TEL_SYNCH: back->special (TS_SYNCH); break;
1155 case IDM_TEL_EC: back->special (TS_EC); break;
1156 case IDM_TEL_EL: back->special (TS_EL); break;
1157 case IDM_TEL_GA: back->special (TS_GA); break;
1158 case IDM_TEL_NOP: back->special (TS_NOP); break;
1159 case IDM_TEL_ABORT: back->special (TS_ABORT); break;
1160 case IDM_TEL_AO: back->special (TS_AO); break;
1161 case IDM_TEL_IP: back->special (TS_IP); break;
1162 case IDM_TEL_SUSP: back->special (TS_SUSP); break;
1163 case IDM_TEL_EOR: back->special (TS_EOR); break;
1164 case IDM_TEL_EOF: back->special (TS_EOF); break;
1169 if (wParam >= IDM_SAVED_MIN && wParam <= IDM_SAVED_MAX) {
1170 SendMessage(hwnd, WM_SYSCOMMAND, IDM_SAVEDSESS, wParam);
1175 #define X_POS(l) ((int)(short)LOWORD(l))
1176 #define Y_POS(l) ((int)(short)HIWORD(l))
1178 #define TO_CHR_X(x) (((x)<0 ? (x)-font_width+1 : (x)) / font_width)
1179 #define TO_CHR_Y(y) (((y)<0 ? (y)-font_height+1: (y)) / font_height)
1181 case WM_LBUTTONDOWN:
1182 click (MB_SELECT, TO_CHR_X(X_POS(lParam)),
1183 TO_CHR_Y(Y_POS(lParam)));
1187 term_mouse (MB_SELECT, MA_RELEASE, TO_CHR_X(X_POS(lParam)),
1188 TO_CHR_Y(Y_POS(lParam)));
1191 case WM_MBUTTONDOWN:
1193 click (cfg.mouse_is_xterm ? MB_PASTE : MB_EXTEND,
1194 TO_CHR_X(X_POS(lParam)),
1195 TO_CHR_Y(Y_POS(lParam)));
1198 term_mouse (cfg.mouse_is_xterm ? MB_PASTE : MB_EXTEND,
1199 MA_RELEASE, TO_CHR_X(X_POS(lParam)),
1200 TO_CHR_Y(Y_POS(lParam)));
1203 case WM_RBUTTONDOWN:
1205 click (cfg.mouse_is_xterm ? MB_EXTEND : MB_PASTE,
1206 TO_CHR_X(X_POS(lParam)),
1207 TO_CHR_Y(Y_POS(lParam)));
1210 term_mouse (cfg.mouse_is_xterm ? MB_EXTEND : MB_PASTE,
1211 MA_RELEASE, TO_CHR_X(X_POS(lParam)),
1212 TO_CHR_Y(Y_POS(lParam)));
1217 * Add the mouse position and message time to the random
1218 * number noise, if we're using ssh.
1220 if (cfg.protocol == PROT_SSH)
1221 noise_ultralight(lParam);
1223 if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)) {
1225 if (wParam & MK_LBUTTON)
1227 else if (wParam & MK_MBUTTON)
1228 b = cfg.mouse_is_xterm ? MB_PASTE : MB_EXTEND;
1230 b = cfg.mouse_is_xterm ? MB_EXTEND : MB_PASTE;
1231 term_mouse (b, MA_DRAG, TO_CHR_X(X_POS(lParam)),
1232 TO_CHR_Y(Y_POS(lParam)));
1235 case WM_IGNORE_CLIP:
1236 ignore_clip = wParam; /* don't panic on DESTROYCLIPBOARD */
1238 case WM_DESTROYCLIPBOARD:
1241 ignore_clip = FALSE;
1246 hdc = BeginPaint (hwnd, &p);
1248 SelectPalette (hdc, pal, TRUE);
1249 RealizePalette (hdc);
1251 term_paint (hdc, p.rcPaint.left, p.rcPaint.top,
1252 p.rcPaint.right, p.rcPaint.bottom);
1253 SelectObject (hdc, GetStockObject(SYSTEM_FONT));
1254 SelectObject (hdc, GetStockObject(WHITE_PEN));
1255 EndPaint (hwnd, &p);
1259 /* Notice we can get multiple netevents, FD_READ, FD_WRITE etc
1260 * but the only one that's likely to try to overload us is FD_READ.
1261 * This means buffering just one is fine.
1263 if (pending_netevent)
1264 enact_pending_netevent();
1266 pending_netevent = TRUE;
1267 pend_netevent_wParam=wParam;
1268 pend_netevent_lParam=lParam;
1272 CreateCaret(hwnd, NULL, font_width, font_height);
1282 case WM_IGNORE_SIZE:
1283 ignore_size = TRUE; /* don't panic on next WM_SIZE msg */
1285 case WM_ENTERSIZEMOVE:
1289 case WM_EXITSIZEMOVE:
1296 int width, height, w, h, ew, eh;
1297 LPRECT r = (LPRECT)lParam;
1299 width = r->right - r->left - extra_width;
1300 height = r->bottom - r->top - extra_height;
1301 w = (width + font_width/2) / font_width; if (w < 1) w = 1;
1302 h = (height + font_height/2) / font_height; if (h < 1) h = 1;
1303 UpdateSizeTip(hwnd, w, h);
1304 ew = width - w * font_width;
1305 eh = height - h * font_height;
1307 if (wParam == WMSZ_LEFT ||
1308 wParam == WMSZ_BOTTOMLEFT ||
1309 wParam == WMSZ_TOPLEFT)
1315 if (wParam == WMSZ_TOP ||
1316 wParam == WMSZ_TOPRIGHT ||
1317 wParam == WMSZ_TOPLEFT)
1327 /* break; (never reached) */
1329 if (wParam == SIZE_MINIMIZED) {
1330 SetWindowText (hwnd,
1331 cfg.win_name_always ? window_name : icon_name);
1334 if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
1335 SetWindowText (hwnd, window_name);
1337 int width, height, w, h;
1338 #if 0 /* we have fixed this using WM_SIZING now */
1342 width = LOWORD(lParam);
1343 height = HIWORD(lParam);
1344 w = width / font_width; if (w < 1) w = 1;
1345 h = height / font_height; if (h < 1) h = 1;
1346 #if 0 /* we have fixed this using WM_SIZING now */
1347 ew = width - w * font_width;
1348 eh = height - h * font_height;
1349 if (ew != 0 || eh != 0) {
1351 GetWindowRect (hwnd, &r);
1352 SendMessage (hwnd, WM_IGNORE_SIZE, 0, 0);
1353 SetWindowPos (hwnd, NULL, 0, 0,
1354 r.right - r.left - ew, r.bottom - r.top - eh,
1355 SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER);
1358 if (w != cols || h != rows || just_reconfigged) {
1360 term_size (h, w, cfg.savelines);
1362 * Don't call back->size in mid-resize. (To prevent
1363 * massive numbers of resize events getting sent
1364 * down the connection during an NT opaque drag.)
1368 just_reconfigged = FALSE;
1371 ignore_size = FALSE;
1374 switch (LOWORD(wParam)) {
1375 case SB_BOTTOM: term_scroll(-1, 0); break;
1376 case SB_TOP: term_scroll(+1, 0); break;
1377 case SB_LINEDOWN: term_scroll (0, +1); break;
1378 case SB_LINEUP: term_scroll (0, -1); break;
1379 case SB_PAGEDOWN: term_scroll (0, +rows/2); break;
1380 case SB_PAGEUP: term_scroll (0, -rows/2); break;
1381 case SB_THUMBPOSITION: case SB_THUMBTRACK:
1382 term_scroll (1, HIWORD(wParam)); break;
1385 case WM_PALETTECHANGED:
1386 if ((HWND) wParam != hwnd && pal != NULL) {
1387 HDC hdc = get_ctx();
1389 if (RealizePalette (hdc) > 0)
1395 case WM_QUERYNEWPALETTE:
1397 HDC hdc = get_ctx();
1399 if (RealizePalette (hdc) > 0)
1411 * Add the scan code and keypress timing to the random
1412 * number noise, if we're using ssh.
1414 if (cfg.protocol == PROT_SSH)
1415 noise_ultralight(lParam);
1418 * We don't do TranslateMessage since it disassociates the
1419 * resulting CHAR message from the KEYDOWN that sparked it,
1420 * which we occasionally don't want. Instead, we process
1421 * KEYDOWN, and call the Win32 translator functions so that
1422 * we get the translations under _our_ control.
1425 unsigned char buf[20];
1428 len = TranslateKey (message, wParam, lParam, buf);
1430 return DefWindowProc (hwnd, message, wParam, lParam);
1431 ldisc->send (buf, len);
1437 * Nevertheless, we are prepared to deal with WM_CHAR
1438 * messages, should they crop up. So if someone wants to
1439 * post the things to us as part of a macro manoeuvre,
1440 * we're ready to cope.
1443 char c = xlat_kbd2tty((unsigned char)wParam);
1444 ldisc->send (&c, 1);
1449 return DefWindowProc (hwnd, message, wParam, lParam);
1453 * Move the system caret. (We maintain one, even though it's
1454 * invisible, for the benefit of blind people: apparently some
1455 * helper software tracks the system caret, so we should arrange to
1458 void sys_cursor(int x, int y) {
1459 SetCaretPos(x * font_width, y * font_height);
1463 * Draw a line of text in the window, at given character
1464 * coordinates, in given attributes.
1466 * We are allowed to fiddle with the contents of `text'.
1468 void do_text (Context ctx, int x, int y, char *text, int len,
1469 unsigned long attr, int lattr) {
1471 int nfg, nbg, nfont;
1474 int force_manual_underline = 0;
1475 int fnt_width = font_width*(1+(lattr!=LATTR_NORM));
1476 static int *IpDx = 0, IpDxLEN = 0;;
1478 if (len>IpDxLEN || IpDx[0] != fnt_width) {
1482 IpDx = smalloc((len+16)*sizeof(int));
1485 for(i=0; i<IpDxLEN; i++)
1486 IpDx[i] = fnt_width;
1492 if (attr & ATTR_ACTCURS) {
1493 attr &= (bold_mode == BOLD_COLOURS ? 0x300200 : 0x300300);
1494 attr ^= ATTR_CUR_XOR;
1498 if (cfg.vtmode == VT_OEMONLY)
1502 * Map high-half characters in order to approximate ISO using
1503 * OEM character set. No characters are missing if the OEM codepage
1506 if (nfont & FONT_OEM) {
1508 for (i=0; i<len; i++)
1509 if (text[i] >= '\xA0' && text[i] <= '\xFF') {
1511 /* This is CP850 ... perfect translation */
1512 static const char oemhighhalf[] =
1513 "\x20\xAD\xBD\x9C\xCF\xBE\xDD\xF5" /* A0-A7 */
1514 "\xF9\xB8\xA6\xAE\xAA\xF0\xA9\xEE" /* A8-AF */
1515 "\xF8\xF1\xFD\xFC\xEF\xE6\xF4\xFA" /* B0-B7 */
1516 "\xF7\xFB\xA7\xAF\xAC\xAB\xF3\xA8" /* B8-BF */
1517 "\xB7\xB5\xB6\xC7\x8E\x8F\x92\x80" /* C0-C7 */
1518 "\xD4\x90\xD2\xD3\xDE\xD6\xD7\xD8" /* C8-CF */
1519 "\xD1\xA5\xE3\xE0\xE2\xE5\x99\x9E" /* D0-D7 */
1520 "\x9D\xEB\xE9\xEA\x9A\xED\xE8\xE1" /* D8-DF */
1521 "\x85\xA0\x83\xC6\x84\x86\x91\x87" /* E0-E7 */
1522 "\x8A\x82\x88\x89\x8D\xA1\x8C\x8B" /* E8-EF */
1523 "\xD0\xA4\x95\xA2\x93\xE4\x94\xF6" /* F0-F7 */
1524 "\x9B\x97\xA3\x96\x81\xEC\xE7\x98" /* F8-FF */
1527 /* This is CP437 ... junk translation */
1528 static const unsigned char oemhighhalf[] = {
1529 0xff, 0xad, 0x9b, 0x9c, 0x6f, 0x9d, 0x7c, 0x15,
1530 0x22, 0x43, 0xa6, 0xae, 0xaa, 0x2d, 0x52, 0xc4,
1531 0xf8, 0xf1, 0xfd, 0x33, 0x27, 0xe6, 0x14, 0xfa,
1532 0x2c, 0x31, 0xa7, 0xaf, 0xac, 0xab, 0x2f, 0xa8,
1533 0x41, 0x41, 0x41, 0x41, 0x8e, 0x8f, 0x92, 0x80,
1534 0x45, 0x90, 0x45, 0x45, 0x49, 0x49, 0x49, 0x49,
1535 0x44, 0xa5, 0x4f, 0x4f, 0x4f, 0x4f, 0x99, 0x78,
1536 0xed, 0x55, 0x55, 0x55, 0x9a, 0x59, 0x50, 0xe1,
1537 0x85, 0xa0, 0x83, 0x61, 0x84, 0x86, 0x91, 0x87,
1538 0x8a, 0x82, 0x88, 0x89, 0x8d, 0xa1, 0x8c, 0x8b,
1539 0x0b, 0xa4, 0x95, 0xa2, 0x93, 0x6f, 0x94, 0xf6,
1540 0xed, 0x97, 0xa3, 0x96, 0x81, 0x79, 0x70, 0x98
1543 text[i] = oemhighhalf[(unsigned char)text[i] - 0xA0];
1547 if (attr & ATTR_GBCHR) {
1550 * GB mapping: map # to pound, and everything else stays
1553 for (i=0; i<len; i++)
1555 text[i] = cfg.vtmode == VT_OEMONLY ? '\x9C' : '\xA3';
1556 } else if (attr & ATTR_LINEDRW) {
1559 static const char poorman[] =
1560 "*#****\xB0\xB1**+++++-----++++|****\xA3\xB7";
1563 static const char oemmap_437[] =
1564 "\x04\xB1****\xF8\xF1**\xD9\xBF\xDA\xC0\xC5"
1565 "\xC4\xC4\xC4\xC4\xC4\xC3\xB4\xC1\xC2\xB3\xF3\xF2\xE3*\x9C\xFA";
1568 static const char oemmap_850[] =
1569 "\x04\xB1****\xF8\xF1**\xD9\xBF\xDA\xC0\xC5"
1570 "\xC4\xC4\xC4\xC4\xC4\xC3\xB4\xC1\xC2\xB3****\x9C\xFA";
1572 /* Poor windows font ... eg: windows courier */
1573 static const char oemmap[] =
1574 "*\xB1****\xF8\xF1**\xD9\xBF\xDA\xC0\xC5"
1575 "\xC4\xC4\xC4\xC4\xC4\xC3\xB4\xC1\xC2\xB3****\x9C\xFA";
1578 * Line drawing mapping: map ` thru ~ (0x60 thru 0x7E) to
1579 * VT100 line drawing chars; everything else stays normal.
1581 switch (cfg.vtmode) {
1583 for (i=0; i<len; i++)
1584 if (text[i] >= '\x60' && text[i] <= '\x7E')
1585 text[i] += '\x01' - '\x60';
1588 /* Make sure we actually have an OEM font */
1589 if (fonts[nfont|FONT_OEM]) {
1592 for (i=0; i<len; i++)
1593 if (text[i] >= '\x60' && text[i] <= '\x7E')
1594 text[i] = oemmap[(unsigned char)text[i] - 0x60];
1598 for (i=0; i<len; i++)
1599 if (text[i] >= '\x60' && text[i] <= '\x7E')
1600 text[i] = poorman[(unsigned char)text[i] - 0x60];
1605 nfg = 2 * ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
1606 nbg = 2 * ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
1607 if (bold_mode == BOLD_FONT && (attr & ATTR_BOLD))
1609 if (und_mode == UND_FONT && (attr & ATTR_UNDER))
1610 nfont |= FONT_UNDERLINE;
1613 if (nfont&FONT_UNDERLINE)
1614 force_manual_underline = 1;
1615 /* Don't do the same for manual bold, it could be bad news. */
1617 nfont &= ~(FONT_BOLD|FONT_UNDERLINE);
1619 if (font_needs_hand_underlining && (attr & ATTR_UNDER))
1620 force_manual_underline = 1;
1621 if (attr & ATTR_REVERSE) {
1622 t = nfg; nfg = nbg; nbg = t;
1624 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BOLD))
1626 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BLINK))
1630 SelectObject (hdc, fonts[nfont]);
1631 SetTextColor (hdc, fg);
1632 SetBkColor (hdc, bg);
1633 SetBkMode (hdc, OPAQUE);
1636 line_box.right = x+fnt_width*len;
1637 line_box.bottom = y+font_height;
1638 ExtTextOut (hdc, x, y, ETO_CLIPPED|ETO_OPAQUE, &line_box, text, len, IpDx);
1639 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
1640 SetBkMode (hdc, TRANSPARENT);
1642 /* GRR: This draws the character outside it's box and can leave
1643 * 'droppings' even with the clip box! I suppose I could loop it
1644 * one character at a time ... yuk.
1646 * Or ... I could do a test print with "W", and use +1 or -1 for this
1647 * shift depending on if the leftmost column is blank...
1649 ExtTextOut (hdc, x-1, y, ETO_CLIPPED, &line_box, text, len, IpDx);
1651 if (force_manual_underline ||
1652 (und_mode == UND_LINE && (attr & ATTR_UNDER))) {
1654 oldpen = SelectObject (hdc, CreatePen(PS_SOLID, 0, fg));
1655 MoveToEx (hdc, x, y+descent, NULL);
1656 LineTo (hdc, x+len*fnt_width, y+descent);
1657 oldpen = SelectObject (hdc, oldpen);
1658 DeleteObject (oldpen);
1660 if (attr & ATTR_PASCURS) {
1663 pts[0].x = pts[1].x = pts[4].x = x;
1664 pts[2].x = pts[3].x = x+fnt_width-1;
1665 pts[0].y = pts[3].y = pts[4].y = y;
1666 pts[1].y = pts[2].y = y+font_height-1;
1667 oldpen = SelectObject (hdc, CreatePen(PS_SOLID, 0, colours[23]));
1668 Polyline (hdc, pts, 5);
1669 oldpen = SelectObject (hdc, oldpen);
1670 DeleteObject (oldpen);
1674 static int check_compose(int first, int second) {
1676 static char * composetbl[] = {
1677 "++#", "AA@", "(([", "//\\", "))]", "(-{", "-)}", "/^|", "!!¡", "C/¢",
1678 "C|¢", "L-£", "L=£", "XO¤", "X0¤", "Y-¥", "Y=¥", "||¦", "SO§", "S!§",
1679 "S0§", "\"\"¨", "CO©", "C0©", "A_ª", "<<«", ",-¬", "--", "RO®",
1680 "-^¯", "0^°", "+-±", "2^²", "3^³", "''´", "/Uµ", "P!¶", ".^·", ",,¸",
1681 "1^¹", "O_º", ">>»", "14¼", "12½", "34¾", "??¿", "`AÀ", "'AÁ", "^AÂ",
1682 "~AÃ", "\"AÄ", "*AÅ", "AEÆ", ",CÇ", "`EÈ", "'EÉ", "^EÊ", "\"EË",
1683 "`IÌ", "'IÍ", "^IÎ", "\"IÏ", "-DÐ", "~NÑ", "`OÒ", "'OÓ", "^OÔ",
1684 "~OÕ", "\"OÖ", "XX×", "/OØ", "`UÙ", "'UÚ", "^UÛ", "\"UÜ", "'YÝ",
1685 "HTÞ", "ssß", "`aà", "'aá", "^aâ", "~aã", "\"aä", "*aå", "aeæ", ",cç",
1686 "`eè", "'eé", "^eê", "\"eë", "`iì", "'ií", "^iî", "\"iï", "-dð", "~nñ",
1687 "`oò", "'oó", "^oô", "~oõ", "\"oö", ":-÷", "o/ø", "`uù", "'uú", "^uû",
1688 "\"uü", "'yý", "htþ", "\"yÿ",
1692 static int recurse = 0;
1699 sprintf(buf, "cc(%d,%d)", first, second);
1704 for(c=composetbl; *c; c++) {
1705 if( (*c)[0] == first && (*c)[1] == second)
1707 return (*c)[2] & 0xFF;
1714 nc = check_compose(second, first);
1716 nc = check_compose(toupper(first), toupper(second));
1718 nc = check_compose(toupper(second), toupper(first));
1726 * Translate a WM_(SYS)?KEY(UP|DOWN) message into a string of ASCII
1727 * codes. Returns number of bytes used or zero to drop the message
1728 * or -1 to forward the message to windows.
1730 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, unsigned char *output) {
1732 int scan, left_alt = 0, key_down, shift_state;
1734 unsigned char * p = output;
1736 static WORD keys[3];
1737 static int compose_state = 0;
1738 static int compose_char = 0;
1739 static WPARAM compose_key = 0;
1741 r = GetKeyboardState(keystate);
1742 if (!r) memset(keystate, 0, sizeof(keystate));
1745 /* Note if AltGr was pressed and if it was used as a compose key */
1746 if (wParam == VK_MENU && (HIWORD(lParam)&KF_EXTENDED))
1748 keystate[VK_RMENU] = keystate[VK_MENU];
1749 if (!compose_state) compose_key = wParam;
1751 if (wParam == VK_APPS && !compose_state)
1752 compose_key = wParam;
1754 if (wParam == compose_key)
1756 if (compose_state == 0 && (HIWORD(lParam)&(KF_UP|KF_REPEAT))==0)
1758 else if (compose_state == 1 && (HIWORD(lParam)&KF_UP))
1763 else if (compose_state==1 && wParam != VK_CONTROL)
1766 /* Nastyness with NUMLock - Shift-NUMLock is left alone though */
1767 if ( (cfg.funky_type == 0 || (cfg.funky_type == 1 && app_keypad_keys))
1768 && wParam==VK_NUMLOCK && !(keystate[VK_SHIFT]&0x80)) {
1770 wParam = VK_EXECUTE;
1772 /* UnToggle NUMLock */
1773 if ((HIWORD(lParam)&(KF_UP|KF_REPEAT))==0)
1774 keystate[VK_NUMLOCK] ^= 1;
1777 /* And write back the 'adjusted' state */
1778 SetKeyboardState (keystate);
1781 /* Disable Auto repeat if required */
1782 if (repeat_off && (HIWORD(lParam)&(KF_UP|KF_REPEAT))==KF_REPEAT)
1785 if ((HIWORD(lParam)&KF_ALTDOWN) && (keystate[VK_RMENU]&0x80) == 0)
1788 key_down = ((HIWORD(lParam)&KF_UP)==0);
1790 /* Make sure Ctrl-ALT is not the same as AltGr for ToAscii */
1791 if (left_alt && (keystate[VK_CONTROL]&0x80))
1792 keystate[VK_MENU] = 0;
1794 scan = (HIWORD(lParam) & (KF_UP | KF_EXTENDED | 0xFF));
1795 shift_state = ((keystate[VK_SHIFT]&0x80)!=0)
1796 + ((keystate[VK_CONTROL]&0x80)!=0)*2;
1799 * Record that we pressed key so the scroll window can be reset, but
1800 * be careful to avoid Shift-UP/Down
1802 if( wParam != VK_SHIFT && wParam != VK_PRIOR && wParam != VK_NEXT ) {
1806 /* Make sure we're not pasting */
1807 if (key_down) term_nopaste();
1809 if (compose_state>1 && left_alt) compose_state = 0;
1811 /* Sanitize the number pad if not using a PC NumPad */
1812 if( left_alt || (app_keypad_keys && cfg.funky_type != 2)
1813 || cfg.nethack_keypad || compose_state )
1815 if ((HIWORD(lParam)&KF_EXTENDED) == 0)
1820 case VK_INSERT: nParam = VK_NUMPAD0; break;
1821 case VK_END: nParam = VK_NUMPAD1; break;
1822 case VK_DOWN: nParam = VK_NUMPAD2; break;
1823 case VK_NEXT: nParam = VK_NUMPAD3; break;
1824 case VK_LEFT: nParam = VK_NUMPAD4; break;
1825 case VK_CLEAR: nParam = VK_NUMPAD5; break;
1826 case VK_RIGHT: nParam = VK_NUMPAD6; break;
1827 case VK_HOME: nParam = VK_NUMPAD7; break;
1828 case VK_UP: nParam = VK_NUMPAD8; break;
1829 case VK_PRIOR: nParam = VK_NUMPAD9; break;
1830 case VK_DELETE: nParam = VK_DECIMAL; break;
1834 if (keystate[VK_NUMLOCK]&1) shift_state |= 1;
1840 /* If a key is pressed and AltGr is not active */
1841 if (key_down && (keystate[VK_RMENU]&0x80) == 0 && !compose_state)
1843 /* Okay, prepare for most alts then ...*/
1844 if (left_alt) *p++ = '\033';
1846 /* Lets see if it's a pattern we know all about ... */
1847 if (wParam == VK_PRIOR && shift_state == 1) {
1848 SendMessage (hwnd, WM_VSCROLL, SB_PAGEUP, 0);
1851 if (wParam == VK_NEXT && shift_state == 1) {
1852 SendMessage (hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
1855 if (wParam == VK_INSERT && shift_state == 1) {
1856 term_mouse (MB_PASTE, MA_CLICK, 0, 0);
1857 term_mouse (MB_PASTE, MA_RELEASE, 0, 0);
1860 if (left_alt && wParam == VK_F4 && cfg.alt_f4) {
1863 if (left_alt && wParam == VK_SPACE && cfg.alt_space) {
1865 SendMessage (hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0);
1869 /* Nethack keypad */
1870 if (cfg.nethack_keypad && !left_alt) {
1872 case VK_NUMPAD1: *p++ = shift_state ? 'B': 'b'; return p-output;
1873 case VK_NUMPAD2: *p++ = shift_state ? 'J': 'j'; return p-output;
1874 case VK_NUMPAD3: *p++ = shift_state ? 'N': 'n'; return p-output;
1875 case VK_NUMPAD4: *p++ = shift_state ? 'H': 'h'; return p-output;
1876 case VK_NUMPAD5: *p++ = shift_state ? '.': '.'; return p-output;
1877 case VK_NUMPAD6: *p++ = shift_state ? 'L': 'l'; return p-output;
1878 case VK_NUMPAD7: *p++ = shift_state ? 'Y': 'y'; return p-output;
1879 case VK_NUMPAD8: *p++ = shift_state ? 'K': 'k'; return p-output;
1880 case VK_NUMPAD9: *p++ = shift_state ? 'U': 'u'; return p-output;
1884 /* Application Keypad */
1888 if ( cfg.funky_type == 0 ||
1889 ( cfg.funky_type == 1 && app_keypad_keys)) switch(wParam) {
1890 case VK_EXECUTE: if (app_keypad_keys) xkey = 'P'; break;
1891 case VK_DIVIDE: xkey = 'Q'; break;
1892 case VK_MULTIPLY:xkey = 'R'; break;
1893 case VK_SUBTRACT:xkey = 'S'; break;
1895 if(app_keypad_keys) switch(wParam) {
1896 case VK_NUMPAD0: xkey = 'p'; break;
1897 case VK_NUMPAD1: xkey = 'q'; break;
1898 case VK_NUMPAD2: xkey = 'r'; break;
1899 case VK_NUMPAD3: xkey = 's'; break;
1900 case VK_NUMPAD4: xkey = 't'; break;
1901 case VK_NUMPAD5: xkey = 'u'; break;
1902 case VK_NUMPAD6: xkey = 'v'; break;
1903 case VK_NUMPAD7: xkey = 'w'; break;
1904 case VK_NUMPAD8: xkey = 'x'; break;
1905 case VK_NUMPAD9: xkey = 'y'; break;
1907 case VK_DECIMAL: xkey = 'n'; break;
1908 case VK_ADD: if(shift_state) xkey = 'm';
1912 if (HIWORD(lParam)&KF_EXTENDED)
1920 if (xkey>='P' && xkey<='S')
1921 p += sprintf((char *)p, "\x1B%c", xkey);
1923 p += sprintf((char *)p, "\x1B?%c", xkey);
1926 p += sprintf((char *)p, "\x1BO%c", xkey);
1931 if (wParam == VK_BACK && shift_state == 0 ) /* Backspace */
1933 *p++ = (cfg.bksp_is_delete ? 0x7F : 0x08);
1936 if (wParam == VK_TAB && shift_state == 1 ) /* Shift tab */
1938 *p++ = 0x1B; *p++ = '['; *p++ = 'Z'; return p - output;
1940 if (wParam == VK_SPACE && shift_state == 2 ) /* Ctrl-Space */
1942 *p++ = 0; return p - output;
1944 if (wParam == VK_SPACE && shift_state == 3 ) /* Ctrl-Shift-Space */
1946 *p++ = 160; return p - output;
1948 if (wParam == VK_CANCEL && shift_state == 2 ) /* Ctrl-Break */
1950 *p++ = 3; return p - output;
1952 /* Control-2 to Control-8 are special */
1953 if (shift_state == 2 && wParam >= '2' && wParam <= '8')
1955 *p++ = "\000\033\034\035\036\037\177"[wParam-'2'];
1958 if (shift_state == 2 && wParam == 0xBD) {
1962 if (shift_state == 2 && wParam == 0xDF) {
1966 if (shift_state == 0 && wParam == VK_RETURN && cr_lf_return) {
1967 *p++ = '\r'; *p++ = '\n';
1972 * Next, all the keys that do tilde codes. (ESC '[' nn '~',
1973 * for integer decimal nn.)
1975 * We also deal with the weird ones here. Linux VCs replace F1
1976 * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
1977 * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
1982 case VK_F1: code = (keystate[VK_SHIFT] & 0x80 ? 23 : 11); break;
1983 case VK_F2: code = (keystate[VK_SHIFT] & 0x80 ? 24 : 12); break;
1984 case VK_F3: code = (keystate[VK_SHIFT] & 0x80 ? 25 : 13); break;
1985 case VK_F4: code = (keystate[VK_SHIFT] & 0x80 ? 26 : 14); break;
1986 case VK_F5: code = (keystate[VK_SHIFT] & 0x80 ? 28 : 15); break;
1987 case VK_F6: code = (keystate[VK_SHIFT] & 0x80 ? 29 : 17); break;
1988 case VK_F7: code = (keystate[VK_SHIFT] & 0x80 ? 31 : 18); break;
1989 case VK_F8: code = (keystate[VK_SHIFT] & 0x80 ? 32 : 19); break;
1990 case VK_F9: code = (keystate[VK_SHIFT] & 0x80 ? 33 : 20); break;
1991 case VK_F10: code = (keystate[VK_SHIFT] & 0x80 ? 34 : 21); break;
1992 case VK_F11: code = 23; break;
1993 case VK_F12: code = 24; break;
1994 case VK_F13: code = 25; break;
1995 case VK_F14: code = 26; break;
1996 case VK_F15: code = 28; break;
1997 case VK_F16: code = 29; break;
1998 case VK_F17: code = 31; break;
1999 case VK_F18: code = 32; break;
2000 case VK_F19: code = 33; break;
2001 case VK_F20: code = 34; break;
2002 case VK_HOME: code = 1; break;
2003 case VK_INSERT: code = 2; break;
2004 case VK_DELETE: code = 3; break;
2005 case VK_END: code = 4; break;
2006 case VK_PRIOR: code = 5; break;
2007 case VK_NEXT: code = 6; break;
2009 if (cfg.funky_type == 1 && code >= 11 && code <= 15) {
2010 p += sprintf((char *)p, "\x1B[[%c", code + 'A' - 11);
2013 if (cfg.funky_type == 2 && code >= 11 && code <= 14) {
2014 p += sprintf((char *)p, "\x1BO%c", code + 'P' - 11);
2017 if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
2018 p += sprintf((char *)p, code == 1 ? "\x1B[H" : "\x1BOw");
2022 p += sprintf((char *)p, "\x1B[%d~", code);
2027 * Now the remaining keys (arrows and Keypad 5. Keypad 5 for
2028 * some reason seems to send VK_CLEAR to Windows...).
2033 case VK_UP: xkey = 'A'; break;
2034 case VK_DOWN: xkey = 'B'; break;
2035 case VK_RIGHT: xkey = 'C'; break;
2036 case VK_LEFT: xkey = 'D'; break;
2037 case VK_CLEAR: xkey = 'G'; break;
2042 p += sprintf((char *)p, "\x1B%c", xkey);
2043 else if (app_cursor_keys)
2044 p += sprintf((char *)p, "\x1BO%c", xkey);
2046 p += sprintf((char *)p, "\x1B[%c", xkey);
2052 /* Okay we've done everything interesting; let windows deal with
2053 * the boring stuff */
2055 BOOL capsOn=keystate[VK_CAPITAL] !=0;
2057 /* helg: clear CAPS LOCK state if caps lock switches to cyrillic */
2058 if(cfg.xlat_capslockcyr)
2059 keystate[VK_CAPITAL] = 0;
2061 r = ToAscii (wParam, scan, keystate, keys, 0);
2067 unsigned char ch = (unsigned char)keys[i];
2069 if (compose_state==2 && (ch&0x80) == 0 && ch>' ') {
2074 if (compose_state==3 && (ch&0x80) == 0 && ch>' ') {
2078 if ((nc=check_compose(compose_char,ch)) == -1)
2083 *p++ = xlat_kbd2tty((unsigned char)nc);
2089 if( left_alt && key_down ) *p++ = '\033';
2095 ch = xlat_latkbd2win(ch);
2096 *p++ = xlat_kbd2tty(ch);
2100 /* This is so the ALT-Numpad and dead keys work correctly. */
2107 /* This stops ALT press-release doing a 'COMMAND MENU' function */
2108 if (message == WM_SYSKEYUP && wParam == VK_MENU)
2110 keystate[VK_MENU] = 0;
2117 void set_title (char *title) {
2118 sfree (window_name);
2119 window_name = smalloc(1+strlen(title));
2120 strcpy (window_name, title);
2121 if (cfg.win_name_always || !IsIconic(hwnd))
2122 SetWindowText (hwnd, title);
2125 void set_icon (char *title) {
2127 icon_name = smalloc(1+strlen(title));
2128 strcpy (icon_name, title);
2129 if (!cfg.win_name_always && IsIconic(hwnd))
2130 SetWindowText (hwnd, title);
2133 void set_sbar (int total, int start, int page) {
2136 if (!cfg.scrollbar) return;
2138 si.cbSize = sizeof(si);
2139 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
2141 si.nMax = total - 1;
2145 SetScrollInfo (hwnd, SB_VERT, &si, TRUE);
2148 Context get_ctx(void) {
2153 SelectPalette (hdc, pal, FALSE);
2159 void free_ctx (Context ctx) {
2160 SelectPalette (ctx, GetStockObject (DEFAULT_PALETTE), FALSE);
2161 ReleaseDC (hwnd, ctx);
2164 static void real_palette_set (int n, int r, int g, int b) {
2166 logpal->palPalEntry[n].peRed = r;
2167 logpal->palPalEntry[n].peGreen = g;
2168 logpal->palPalEntry[n].peBlue = b;
2169 logpal->palPalEntry[n].peFlags = PC_NOCOLLAPSE;
2170 colours[n] = PALETTERGB(r, g, b);
2171 SetPaletteEntries (pal, 0, NCOLOURS, logpal->palPalEntry);
2173 colours[n] = RGB(r, g, b);
2176 void palette_set (int n, int r, int g, int b) {
2177 static const int first[21] = {
2178 0, 2, 4, 6, 8, 10, 12, 14,
2179 1, 3, 5, 7, 9, 11, 13, 15,
2182 real_palette_set (first[n], r, g, b);
2184 real_palette_set (first[n]+1, r, g, b);
2186 HDC hdc = get_ctx();
2187 UnrealizeObject (pal);
2188 RealizePalette (hdc);
2193 void palette_reset (void) {
2196 for (i = 0; i < NCOLOURS; i++) {
2198 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
2199 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
2200 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
2201 logpal->palPalEntry[i].peFlags = 0;
2202 colours[i] = PALETTERGB(defpal[i].rgbtRed,
2203 defpal[i].rgbtGreen,
2204 defpal[i].rgbtBlue);
2206 colours[i] = RGB(defpal[i].rgbtRed,
2207 defpal[i].rgbtGreen,
2208 defpal[i].rgbtBlue);
2213 SetPaletteEntries (pal, 0, NCOLOURS, logpal->palPalEntry);
2215 RealizePalette (hdc);
2220 void write_clip (void *data, int len) {
2224 clipdata = GlobalAlloc (GMEM_DDESHARE | GMEM_MOVEABLE, len + 1);
2227 lock = GlobalLock (clipdata);
2230 memcpy (lock, data, len);
2231 ((unsigned char *) lock) [len] = 0;
2232 GlobalUnlock (clipdata);
2234 SendMessage (hwnd, WM_IGNORE_CLIP, TRUE, 0);
2235 if (OpenClipboard (hwnd)) {
2237 SetClipboardData (CF_TEXT, clipdata);
2240 GlobalFree (clipdata);
2241 SendMessage (hwnd, WM_IGNORE_CLIP, FALSE, 0);
2244 void get_clip (void **p, int *len) {
2245 static HGLOBAL clipdata = NULL;
2249 GlobalUnlock (clipdata);
2253 if (OpenClipboard (NULL)) {
2254 clipdata = GetClipboardData (CF_TEXT);
2257 *p = GlobalLock (clipdata);
2271 * Move `lines' lines from position `from' to position `to' in the
2274 void optimised_move (int to, int from, int lines) {
2278 min = (to < from ? to : from);
2279 max = to + from - min;
2281 r.left = 0; r.right = cols * font_width;
2282 r.top = min * font_height; r.bottom = (max+lines) * font_height;
2283 ScrollWindow (hwnd, 0, (to - from) * font_height, &r, &r);
2287 * Print a message box and perform a fatal exit.
2289 void fatalbox(char *fmt, ...) {
2294 vsprintf(stuff, fmt, ap);
2296 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
2303 void beep(int errorbeep) {
2304 static long last_beep = 0;
2305 long now, beep_diff;
2307 now = GetTickCount();
2308 beep_diff = now-last_beep;
2310 /* Make sure we only respond to one beep per packet or so */
2311 if (beep_diff>=0 && beep_diff<50)
2315 MessageBeep(MB_ICONHAND);
2319 last_beep = GetTickCount();