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)
45 #define WM_IGNORE_KEYMENU (WM_XUSER + 3)
47 static LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
48 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, unsigned char *output);
49 static void cfgtopalette(void);
50 static void init_palette(void);
51 static void init_fonts(int);
53 static int extra_width, extra_height;
55 static int pending_netevent = 0;
56 static WPARAM pend_netevent_wParam = 0;
57 static LPARAM pend_netevent_lParam = 0;
58 static void enact_pending_netevent(void);
62 #define FONT_UNDERLINE 2
63 #define FONT_BOLDUND 3
65 #define FONT_OEMBOLD 5
66 #define FONT_OEMBOLDUND 6
68 static HFONT fonts[8];
69 static int font_needs_hand_underlining;
71 BOLD_COLOURS, BOLD_SHADOW, BOLD_FONT
79 static COLORREF colours[NCOLOURS];
81 static LPLOGPALETTE logpal;
82 static RGBTRIPLE defpal[NCOLOURS];
86 static int dbltime, lasttime, lastact;
87 static Mouse_Button lastbtn;
89 static char *window_name, *icon_name;
91 static Ldisc *real_ldisc;
93 void begin_session(void) {
97 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) {
98 static char appname[] = "PuTTY";
103 int guess_width, guess_height;
106 flags = FLAG_VERBOSE | FLAG_INTERACTIVE;
108 winsock_ver = MAKEWORD(1, 1);
109 if (WSAStartup(winsock_ver, &wsadata)) {
110 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
111 MB_OK | MB_ICONEXCLAMATION);
114 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
115 MessageBox(NULL, "WinSock version is incompatible with 1.1",
116 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
120 /* WISHLIST: maybe allow config tweaking even if winsock not present? */
122 InitCommonControls();
125 * Process the command line.
130 default_protocol = DEFAULT_PROTOCOL;
131 default_port = DEFAULT_PORT;
136 while (*p && isspace(*p)) p++;
139 * Process command line options first. Yes, this can be
140 * done better, and it will be as soon as I have the
144 char *q = p + strcspn(p, " \t");
147 tolower(p[0]) == 's' &&
148 tolower(p[1]) == 's' &&
149 tolower(p[2]) == 'h') {
150 default_protocol = cfg.protocol = PROT_SSH;
151 default_port = cfg.port = 22;
152 } else if (q == p + 3 &&
153 tolower(p[0]) == 'l' &&
154 tolower(p[1]) == 'o' &&
155 tolower(p[2]) == 'g') {
156 logfile = "putty.log";
158 p = q + strspn(q, " \t");
162 * An initial @ means to activate a saved session.
166 if (!*cfg.host && !do_config()) {
170 } else if (*p == '&') {
172 * An initial & means we've been given a command line
173 * containing the hex value of a HANDLE for a file
174 * mapping object, which we must then extract as a
179 if (sscanf(p+1, "%p", &filemap) == 1 &&
180 (cp = MapViewOfFile(filemap, FILE_MAP_READ,
181 0, 0, sizeof(Config))) != NULL) {
184 CloseHandle(filemap);
185 } else if (!do_config()) {
192 * If the hostname starts with "telnet:", set the
193 * protocol to Telnet and process the string as a
196 if (!strncmp(q, "telnet:", 7)) {
200 if (q[0] == '/' && q[1] == '/')
202 cfg.protocol = PROT_TELNET;
204 while (*p && *p != ':' && *p != '/') p++;
212 strncpy (cfg.host, q, sizeof(cfg.host)-1);
213 cfg.host[sizeof(cfg.host)-1] = '\0';
215 while (*p && !isspace(*p)) p++;
218 strncpy (cfg.host, q, sizeof(cfg.host)-1);
219 cfg.host[sizeof(cfg.host)-1] = '\0';
220 while (*p && isspace(*p)) p++;
235 * Select protocol. This is farmed out into a table in a
236 * separate file to enable an ssh-free variant.
241 for (i = 0; backends[i].backend != NULL; i++)
242 if (backends[i].protocol == cfg.protocol) {
243 back = backends[i].backend;
247 MessageBox(NULL, "Unsupported protocol number found",
248 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
254 real_ldisc = (cfg.ldisc_term ? &ldisc_term : &ldisc_simple);
255 /* To start with, we use the simple line discipline, so we can
256 * type passwords etc without fear of them being echoed... */
257 ldisc = &ldisc_simple;
261 wndclass.lpfnWndProc = WndProc;
262 wndclass.cbClsExtra = 0;
263 wndclass.cbWndExtra = 0;
264 wndclass.hInstance = inst;
265 wndclass.hIcon = LoadIcon (inst,
266 MAKEINTRESOURCE(IDI_MAINICON));
267 wndclass.hCursor = LoadCursor (NULL, IDC_IBEAM);
268 wndclass.hbrBackground = GetStockObject (BLACK_BRUSH);
269 wndclass.lpszMenuName = NULL;
270 wndclass.lpszClassName = appname;
272 RegisterClass (&wndclass);
277 savelines = cfg.savelines;
283 * Guess some defaults for the window size. This all gets
284 * updated later, so we don't really care too much. However, we
285 * do want the font width/height guesses to correspond to a
286 * large font rather than a small one...
293 term_size (cfg.height, cfg.width, cfg.savelines);
294 guess_width = extra_width + font_width * cols;
295 guess_height = extra_height + font_height * rows;
298 HWND w = GetDesktopWindow();
299 GetWindowRect (w, &r);
300 if (guess_width > r.right - r.left)
301 guess_width = r.right - r.left;
302 if (guess_height > r.bottom - r.top)
303 guess_height = r.bottom - r.top;
307 int winmode = WS_OVERLAPPEDWINDOW|WS_VSCROLL;
308 if (!cfg.scrollbar) winmode &= ~(WS_VSCROLL);
309 if (cfg.locksize) winmode &= ~(WS_THICKFRAME|WS_MAXIMIZEBOX);
310 hwnd = CreateWindow (appname, appname,
312 CW_USEDEFAULT, CW_USEDEFAULT,
313 guess_width, guess_height,
314 NULL, NULL, inst, NULL);
318 * Initialise the fonts, simultaneously correcting the guesses
319 * for font_{width,height}.
321 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
326 * Correct the guesses for extra_{width,height}.
330 GetWindowRect (hwnd, &wr);
331 GetClientRect (hwnd, &cr);
332 extra_width = wr.right - wr.left - cr.right + cr.left;
333 extra_height = wr.bottom - wr.top - cr.bottom + cr.top;
337 * Resize the window, now we know what size we _really_ want it
340 guess_width = extra_width + font_width * cols;
341 guess_height = extra_height + font_height * rows;
342 SendMessage (hwnd, WM_IGNORE_SIZE, 0, 0);
343 SetWindowPos (hwnd, NULL, 0, 0, guess_width, guess_height,
344 SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
347 * Initialise the scroll bar.
352 si.cbSize = sizeof(si);
353 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
358 SetScrollInfo (hwnd, SB_VERT, &si, FALSE);
362 * Start up the telnet connection.
369 error = back->init (hwnd, cfg.host, cfg.port, &realhost);
371 sprintf(msg, "Unable to open connection:\n%s", error);
372 MessageBox(NULL, msg, "PuTTY Error", MB_ICONERROR | MB_OK);
375 window_name = icon_name = NULL;
376 sprintf(msg, "%s - PuTTY", realhost);
381 session_closed = FALSE;
384 * Set up the input and output buffers.
387 outbuf_reap = outbuf_head = 0;
390 * Prepare the mouse handler.
392 lastact = MA_NOTHING;
393 lastbtn = MB_NOTHING;
394 dbltime = GetDoubleClickTime();
397 * Set up the session-control options on the system menu.
400 HMENU m = GetSystemMenu (hwnd, FALSE);
404 AppendMenu (m, MF_SEPARATOR, 0, 0);
405 if (cfg.protocol == PROT_TELNET) {
407 AppendMenu (p, MF_ENABLED, IDM_TEL_AYT, "Are You There");
408 AppendMenu (p, MF_ENABLED, IDM_TEL_BRK, "Break");
409 AppendMenu (p, MF_ENABLED, IDM_TEL_SYNCH, "Synch");
410 AppendMenu (p, MF_SEPARATOR, 0, 0);
411 AppendMenu (p, MF_ENABLED, IDM_TEL_EC, "Erase Character");
412 AppendMenu (p, MF_ENABLED, IDM_TEL_EL, "Erase Line");
413 AppendMenu (p, MF_ENABLED, IDM_TEL_GA, "Go Ahead");
414 AppendMenu (p, MF_ENABLED, IDM_TEL_NOP, "No Operation");
415 AppendMenu (p, MF_SEPARATOR, 0, 0);
416 AppendMenu (p, MF_ENABLED, IDM_TEL_ABORT, "Abort Process");
417 AppendMenu (p, MF_ENABLED, IDM_TEL_AO, "Abort Output");
418 AppendMenu (p, MF_ENABLED, IDM_TEL_IP, "Interrupt Process");
419 AppendMenu (p, MF_ENABLED, IDM_TEL_SUSP, "Suspend Process");
420 AppendMenu (p, MF_SEPARATOR, 0, 0);
421 AppendMenu (p, MF_ENABLED, IDM_TEL_EOR, "End Of Record");
422 AppendMenu (p, MF_ENABLED, IDM_TEL_EOF, "End Of File");
423 AppendMenu (m, MF_POPUP | MF_ENABLED, (UINT) p, "Telnet Command");
424 AppendMenu (m, MF_SEPARATOR, 0, 0);
426 AppendMenu (m, MF_ENABLED, IDM_SHOWLOG, "&Event Log");
427 AppendMenu (m, MF_SEPARATOR, 0, 0);
428 AppendMenu (m, MF_ENABLED, IDM_NEWSESS, "Ne&w Session");
429 AppendMenu (m, MF_ENABLED, IDM_DUPSESS, "&Duplicate Session");
432 for (i = 1 ; i < ((nsessions < 256) ? nsessions : 256) ; i++)
433 AppendMenu (s, MF_ENABLED, IDM_SAVED_MIN + (16 * i) , sessions[i]);
434 AppendMenu (m, MF_POPUP | MF_ENABLED, (UINT) s, "Sa&ved Sessions");
435 AppendMenu (m, MF_ENABLED, IDM_RECONF, "Chan&ge Settings");
436 AppendMenu (m, MF_SEPARATOR, 0, 0);
437 AppendMenu (m, MF_ENABLED, IDM_CLRSB, "C&lear Scrollback");
438 AppendMenu (m, MF_ENABLED, IDM_RESET, "Rese&t Terminal");
439 AppendMenu (m, MF_SEPARATOR, 0, 0);
440 AppendMenu (m, MF_ENABLED, IDM_ABOUT, "&About PuTTY");
444 * Finally show the window!
446 ShowWindow (hwnd, show);
449 * Set the palette up.
455 has_focus = (GetForegroundWindow() == hwnd);
459 int timer_id = 0, long_timer = 0;
461 while (GetMessage (&msg, NULL, 0, 0) == 1) {
462 /* Sometimes DispatchMessage calls routines that use their own
463 * GetMessage loop, setup this timer so we get some control back.
465 * Also call term_update() from the timer so that if the host
466 * is sending data flat out we still do redraws.
468 if(timer_id && long_timer) {
469 KillTimer(hwnd, timer_id);
470 long_timer = timer_id = 0;
473 timer_id = SetTimer(hwnd, 1, 20, NULL);
474 DispatchMessage (&msg);
476 /* This is too fast, but I'll leave it for now 'cause it shows
477 * how often term_update is called (far too often at times!)
481 /* Send the paste buffer if there's anything to send */
484 /* If there's nothing new in the queue then we can do everything
485 * we've delayed, reading the socket, writing, and repainting
488 if (!PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE)) {
489 if (pending_netevent) {
490 enact_pending_netevent();
495 if (!PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE)) {
497 KillTimer(hwnd, timer_id);
504 timer_id = SetTimer(hwnd, 1, 2000, NULL);
505 else if (cfg.blinktext)
506 timer_id = SetTimer(hwnd, 1, 250, NULL);
508 timer_id = SetTimer(hwnd, 1, 500, NULL);
521 DeleteObject(fonts[i]);
528 if (cfg.protocol == PROT_SSH) {
539 * Print a message box and close the connection.
541 void connection_fatal(char *fmt, ...) {
546 vsprintf(stuff, fmt, ap);
548 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
549 if (cfg.close_on_exit)
552 session_closed = TRUE;
553 SetWindowText (hwnd, "PuTTY (inactive)");
558 * Actually do the job requested by a WM_NETEVENT
560 static void enact_pending_netevent(void) {
562 static int reentering = 0;
565 return; /* don't unpend the pending */
567 pending_netevent = FALSE;
570 i = back->msg (pend_netevent_wParam, pend_netevent_lParam);
575 switch (WSABASEERR + (-i) % 10000) {
577 sprintf(buf, "Connection reset by peer");
580 sprintf(buf, "Unexpected network error %d", -i);
583 connection_fatal(buf);
586 if (cfg.close_on_exit)
589 session_closed = TRUE;
590 MessageBox(hwnd, "Connection closed by remote host",
591 "PuTTY", MB_OK | MB_ICONINFORMATION);
592 SetWindowText (hwnd, "PuTTY (inactive)");
598 * Copy the colour palette from the configuration data into defpal.
599 * This is non-trivial because the colour indices are different.
601 static void cfgtopalette(void) {
603 static const int ww[] = {
604 6, 7, 8, 9, 10, 11, 12, 13,
605 14, 15, 16, 17, 18, 19, 20, 21,
606 0, 1, 2, 3, 4, 4, 5, 5
609 for (i=0; i<24; i++) {
611 defpal[i].rgbtRed = cfg.colours[w][0];
612 defpal[i].rgbtGreen = cfg.colours[w][1];
613 defpal[i].rgbtBlue = cfg.colours[w][2];
618 * Set up the colour palette.
620 static void init_palette(void) {
622 HDC hdc = GetDC (hwnd);
624 if (cfg.try_palette &&
625 GetDeviceCaps (hdc, RASTERCAPS) & RC_PALETTE) {
626 logpal = smalloc(sizeof(*logpal)
627 - sizeof(logpal->palPalEntry)
628 + NCOLOURS * sizeof(PALETTEENTRY));
629 logpal->palVersion = 0x300;
630 logpal->palNumEntries = NCOLOURS;
631 for (i = 0; i < NCOLOURS; i++) {
632 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
633 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
634 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
635 logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
637 pal = CreatePalette (logpal);
639 SelectPalette (hdc, pal, FALSE);
640 RealizePalette (hdc);
641 SelectPalette (hdc, GetStockObject (DEFAULT_PALETTE),
645 ReleaseDC (hwnd, hdc);
648 for (i=0; i<NCOLOURS; i++)
649 colours[i] = PALETTERGB(defpal[i].rgbtRed,
653 for(i=0; i<NCOLOURS; i++)
654 colours[i] = RGB(defpal[i].rgbtRed,
660 * Initialise all the fonts we will need. There may be as many as
661 * eight or as few as one. We also:
663 * - check the font width and height, correcting our guesses if
666 * - verify that the bold font is the same width as the ordinary
667 * one, and engage shadow bolding if not.
669 * - verify that the underlined font is the same width as the
670 * ordinary one (manual underlining by means of line drawing can
671 * be done in a pinch).
673 static void init_fonts(int pick_width) {
678 int fw_dontcare, fw_bold;
687 if (cfg.fontisbold) {
688 fw_dontcare = FW_BOLD;
691 fw_dontcare = FW_DONTCARE;
697 font_height = cfg.fontheight;
698 font_width = pick_width;
701 fonts[i] = CreateFont (font_height, font_width, 0, 0, w, FALSE, u, FALSE, \
702 c, OUT_DEFAULT_PRECIS, \
703 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, \
704 FIXED_PITCH | FF_DONTCARE, cfg.font)
706 if (cfg.vtmode != VT_OEMONLY) {
707 f(FONT_NORMAL, cfg.fontcharset, fw_dontcare, FALSE);
709 SelectObject (hdc, fonts[FONT_NORMAL]);
710 GetTextMetrics(hdc, &tm);
711 font_height = tm.tmHeight;
712 font_width = tm.tmAveCharWidth;
714 f(FONT_UNDERLINE, cfg.fontcharset, fw_dontcare, TRUE);
717 * Some fonts, e.g. 9-pt Courier, draw their underlines
718 * outside their character cell. We successfully prevent
719 * screen corruption by clipping the text output, but then
720 * we lose the underline completely. Here we try to work
721 * out whether this is such a font, and if it is, we set a
722 * flag that causes underlines to be drawn by hand.
724 * Having tried other more sophisticated approaches (such
725 * as examining the TEXTMETRIC structure or requesting the
726 * height of a string), I think we'll do this the brute
727 * force way: we create a small bitmap, draw an underlined
728 * space on it, and test to see whether any pixels are
729 * foreground-coloured. (Since we expect the underline to
730 * go all the way across the character cell, we only search
731 * down a single column of the bitmap, half way across.)
735 HBITMAP und_bm, und_oldbm;
739 und_dc = CreateCompatibleDC(hdc);
740 und_bm = CreateCompatibleBitmap(hdc, font_width, font_height);
741 und_oldbm = SelectObject(und_dc, und_bm);
742 SelectObject(und_dc, fonts[FONT_UNDERLINE]);
743 SetTextAlign(und_dc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
744 SetTextColor (und_dc, RGB(255,255,255));
745 SetBkColor (und_dc, RGB(0,0,0));
746 SetBkMode (und_dc, OPAQUE);
747 ExtTextOut (und_dc, 0, 0, ETO_OPAQUE, NULL, " ", 1, NULL);
749 for (i = 0; i < font_height; i++) {
750 c = GetPixel(und_dc, font_width/2, i);
754 SelectObject(und_dc, und_oldbm);
755 DeleteObject(und_bm);
757 font_needs_hand_underlining = !gotit;
760 if (bold_mode == BOLD_FONT) {
761 f(FONT_BOLD, cfg.fontcharset, fw_bold, FALSE);
762 f(FONT_BOLDUND, cfg.fontcharset, fw_bold, TRUE);
765 if (cfg.vtmode == VT_OEMANSI) {
766 f(FONT_OEM, OEM_CHARSET, fw_dontcare, FALSE);
767 f(FONT_OEMUND, OEM_CHARSET, fw_dontcare, TRUE);
769 if (bold_mode == BOLD_FONT) {
770 f(FONT_OEMBOLD, OEM_CHARSET, fw_bold, FALSE);
771 f(FONT_OEMBOLDUND, OEM_CHARSET, fw_bold, TRUE);
777 f(FONT_OEM, cfg.fontcharset, fw_dontcare, FALSE);
779 SelectObject (hdc, fonts[FONT_OEM]);
780 GetTextMetrics(hdc, &tm);
781 font_height = tm.tmHeight;
782 font_width = tm.tmAveCharWidth;
784 f(FONT_OEMUND, cfg.fontcharset, fw_dontcare, TRUE);
786 if (bold_mode == BOLD_FONT) {
787 f(FONT_BOLD, cfg.fontcharset, fw_bold, FALSE);
788 f(FONT_BOLDUND, cfg.fontcharset, fw_bold, TRUE);
793 descent = tm.tmAscent + 1;
794 if (descent >= font_height)
795 descent = font_height - 1;
796 firstchar = tm.tmFirstChar;
798 for (i=0; i<8; i++) {
800 if (SelectObject (hdc, fonts[i]) &&
801 GetTextMetrics(hdc, &tm) )
802 fsize[i] = tm.tmAveCharWidth + 256 * tm.tmHeight;
808 ReleaseDC (hwnd, hdc);
810 /* ... This is wrong in OEM only mode */
811 if (fsize[FONT_UNDERLINE] != fsize[FONT_NORMAL] ||
812 (bold_mode == BOLD_FONT &&
813 fsize[FONT_BOLDUND] != fsize[FONT_BOLD])) {
815 DeleteObject (fonts[FONT_UNDERLINE]);
816 if (bold_mode == BOLD_FONT)
817 DeleteObject (fonts[FONT_BOLDUND]);
820 if (bold_mode == BOLD_FONT &&
821 fsize[FONT_BOLD] != fsize[FONT_NORMAL]) {
822 bold_mode = BOLD_SHADOW;
823 DeleteObject (fonts[FONT_BOLD]);
824 if (und_mode == UND_FONT)
825 DeleteObject (fonts[FONT_BOLDUND]);
829 /* With the fascist font painting it doesn't matter if the linedraw font
830 * isn't exactly the right size anymore so we don't have to check this.
832 if (cfg.vtmode == VT_OEMANSI && fsize[FONT_OEM] != fsize[FONT_NORMAL] ) {
833 if( cfg.fontcharset == OEM_CHARSET )
835 MessageBox(NULL, "The OEM and ANSI versions of this font are\n"
836 "different sizes. Using OEM-only mode instead",
837 "Font Size Mismatch", MB_ICONINFORMATION | MB_OK);
838 cfg.vtmode = VT_OEMONLY;
840 else if( firstchar < ' ' )
842 MessageBox(NULL, "The OEM and ANSI versions of this font are\n"
843 "different sizes. Using XTerm mode instead",
844 "Font Size Mismatch", MB_ICONINFORMATION | MB_OK);
845 cfg.vtmode = VT_XWINDOWS;
849 MessageBox(NULL, "The OEM and ANSI versions of this font are\n"
850 "different sizes. Using ISO8859-1 mode instead",
851 "Font Size Mismatch", MB_ICONINFORMATION | MB_OK);
852 cfg.vtmode = VT_POORMAN;
857 DeleteObject (fonts[i]);
863 void request_resize (int w, int h, int refont) {
866 /* If the window is maximized supress resizing attempts */
867 if(IsZoomed(hwnd)) return;
870 /* Don't do this in OEMANSI, you may get disable messages */
871 if (refont && w != cols && (cols==80 || cols==132)
872 && cfg.vtmode != VT_OEMANSI)
874 if (refont && w != cols && (cols==80 || cols==132))
877 /* If font width too big for screen should we shrink the font more ? */
879 font_width = ((font_width*cols+w/2)/w);
886 DeleteObject(fonts[i]);
888 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
890 init_fonts(font_width);
894 static int first_time = 1;
900 /* Get the size of the screen */
901 if (GetClientRect(GetDesktopWindow(),&ss))
902 /* first_time = 0 */;
903 else { first_time = 2; break; }
905 /* Make sure the values are sane */
906 width = (ss.right-ss.left-extra_width ) / font_width;
907 height = (ss.bottom-ss.top-extra_height ) / font_height;
909 if (w>width) w=width;
910 if (h>height) h=height;
916 width = extra_width + font_width * w;
917 height = extra_height + font_height * h;
919 SetWindowPos (hwnd, NULL, 0, 0, width, height,
920 SWP_NOACTIVATE | SWP_NOCOPYBITS |
921 SWP_NOMOVE | SWP_NOZORDER);
924 static void click (Mouse_Button b, int x, int y) {
925 int thistime = GetMessageTime();
927 if (lastbtn == b && thistime - lasttime < dbltime) {
928 lastact = (lastact == MA_CLICK ? MA_2CLK :
929 lastact == MA_2CLK ? MA_3CLK :
930 lastact == MA_3CLK ? MA_CLICK : MA_NOTHING);
935 if (lastact != MA_NOTHING)
936 term_mouse (b, lastact, x, y);
940 static LRESULT CALLBACK WndProc (HWND hwnd, UINT message,
941 WPARAM wParam, LPARAM lParam) {
943 static int ignore_size = FALSE;
944 static int ignore_clip = FALSE;
945 static int ignore_keymenu = TRUE;
946 static int just_reconfigged = FALSE;
950 if (pending_netevent)
951 enact_pending_netevent();
959 if (!cfg.warn_on_close || session_closed ||
960 MessageBox(hwnd, "Are you sure you want to close this session?",
961 "PuTTY Exit Confirmation",
962 MB_ICONWARNING | MB_OKCANCEL) == IDOK)
969 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
972 return 0; /* don't put up system menu on Alt */
985 PROCESS_INFORMATION pi;
986 HANDLE filemap = NULL;
988 if (wParam == IDM_DUPSESS) {
990 * Allocate a file-mapping memory chunk for the
993 SECURITY_ATTRIBUTES sa;
996 sa.nLength = sizeof(sa);
997 sa.lpSecurityDescriptor = NULL;
998 sa.bInheritHandle = TRUE;
999 filemap = CreateFileMapping((HANDLE)0xFFFFFFFF,
1006 p = (Config *)MapViewOfFile(filemap,
1008 0, 0, sizeof(Config));
1010 *p = cfg; /* structure copy */
1014 sprintf(c, "putty &%p", filemap);
1016 } else if (wParam == IDM_SAVEDSESS) {
1017 char *session = sessions[(lParam - IDM_SAVED_MIN) / 16];
1018 cl = malloc(16 + strlen(session)); /* 8, but play safe */
1020 cl = NULL; /* not a very important failure mode */
1022 sprintf(cl, "putty @%s", session);
1028 GetModuleFileName (NULL, b, sizeof(b)-1);
1030 si.lpReserved = NULL;
1031 si.lpDesktop = NULL;
1035 si.lpReserved2 = NULL;
1036 CreateProcess (b, cl, NULL, NULL, TRUE,
1037 NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
1040 CloseHandle(filemap);
1046 if (!do_reconfig(hwnd))
1048 just_reconfigged = TRUE;
1053 DeleteObject(fonts[i]);
1055 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
1056 und_mode = UND_FONT;
1059 /* Telnet will change local echo -> remote if the remote asks */
1060 if (cfg.protocol != PROT_TELNET)
1061 ldisc = (cfg.ldisc_term ? &ldisc_term : &ldisc_simple);
1069 /* Enable or disable the scroll bar, etc */
1071 LONG nflg, flag = GetWindowLong(hwnd, GWL_STYLE);
1074 if (cfg.scrollbar) nflg |= WS_VSCROLL;
1075 else nflg &= ~WS_VSCROLL;
1077 nflg &= ~(WS_THICKFRAME|WS_MAXIMIZEBOX);
1079 nflg |= (WS_THICKFRAME|WS_MAXIMIZEBOX);
1085 SetWindowLong(hwnd, GWL_STYLE, nflg);
1086 SendMessage (hwnd, WM_IGNORE_SIZE, 0, 0);
1087 SetWindowPos(hwnd, NULL, 0,0,0,0,
1088 SWP_NOACTIVATE|SWP_NOCOPYBITS|
1089 SWP_NOMOVE|SWP_NOSIZE| SWP_NOZORDER|
1092 GetWindowRect (hwnd, &wr);
1093 GetClientRect (hwnd, &cr);
1094 extra_width = wr.right - wr.left - cr.right + cr.left;
1095 extra_height = wr.bottom - wr.top - cr.bottom + cr.top;
1099 term_size(cfg.height, cfg.width, cfg.savelines);
1100 InvalidateRect(hwnd, NULL, TRUE);
1101 SetWindowPos (hwnd, NULL, 0, 0,
1102 extra_width + font_width * cfg.width,
1103 extra_height + font_height * cfg.height,
1104 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1105 SWP_NOMOVE | SWP_NOZORDER);
1106 if (IsIconic(hwnd)) {
1107 SetWindowText (hwnd,
1108 cfg.win_name_always ? window_name : icon_name);
1117 case IDM_TEL_AYT: back->special (TS_AYT); break;
1118 case IDM_TEL_BRK: back->special (TS_BRK); break;
1119 case IDM_TEL_SYNCH: back->special (TS_SYNCH); break;
1120 case IDM_TEL_EC: back->special (TS_EC); break;
1121 case IDM_TEL_EL: back->special (TS_EL); break;
1122 case IDM_TEL_GA: back->special (TS_GA); break;
1123 case IDM_TEL_NOP: back->special (TS_NOP); break;
1124 case IDM_TEL_ABORT: back->special (TS_ABORT); break;
1125 case IDM_TEL_AO: back->special (TS_AO); break;
1126 case IDM_TEL_IP: back->special (TS_IP); break;
1127 case IDM_TEL_SUSP: back->special (TS_SUSP); break;
1128 case IDM_TEL_EOR: back->special (TS_EOR); break;
1129 case IDM_TEL_EOF: back->special (TS_EOF); break;
1134 if (wParam >= IDM_SAVED_MIN && wParam <= IDM_SAVED_MAX) {
1135 SendMessage(hwnd, WM_SYSCOMMAND, IDM_SAVEDSESS, wParam);
1140 #define X_POS(l) ((int)(short)LOWORD(l))
1141 #define Y_POS(l) ((int)(short)HIWORD(l))
1143 #define TO_CHR_X(x) (((x)<0 ? (x)-font_width+1 : (x)) / font_width)
1144 #define TO_CHR_Y(y) (((y)<0 ? (y)-font_height+1: (y)) / font_height)
1146 case WM_LBUTTONDOWN:
1147 click (MB_SELECT, TO_CHR_X(X_POS(lParam)),
1148 TO_CHR_Y(Y_POS(lParam)));
1152 term_mouse (MB_SELECT, MA_RELEASE, TO_CHR_X(X_POS(lParam)),
1153 TO_CHR_Y(Y_POS(lParam)));
1156 case WM_MBUTTONDOWN:
1158 click (cfg.mouse_is_xterm ? MB_PASTE : MB_EXTEND,
1159 TO_CHR_X(X_POS(lParam)),
1160 TO_CHR_Y(Y_POS(lParam)));
1163 term_mouse (cfg.mouse_is_xterm ? MB_PASTE : MB_EXTEND,
1164 MA_RELEASE, TO_CHR_X(X_POS(lParam)),
1165 TO_CHR_Y(Y_POS(lParam)));
1168 case WM_RBUTTONDOWN:
1170 click (cfg.mouse_is_xterm ? MB_EXTEND : MB_PASTE,
1171 TO_CHR_X(X_POS(lParam)),
1172 TO_CHR_Y(Y_POS(lParam)));
1175 term_mouse (cfg.mouse_is_xterm ? MB_EXTEND : MB_PASTE,
1176 MA_RELEASE, TO_CHR_X(X_POS(lParam)),
1177 TO_CHR_Y(Y_POS(lParam)));
1182 * Add the mouse position and message time to the random
1183 * number noise, if we're using ssh.
1185 if (cfg.protocol == PROT_SSH)
1186 noise_ultralight(lParam);
1188 if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)) {
1190 if (wParam & MK_LBUTTON)
1192 else if (wParam & MK_MBUTTON)
1193 b = cfg.mouse_is_xterm ? MB_PASTE : MB_EXTEND;
1195 b = cfg.mouse_is_xterm ? MB_EXTEND : MB_PASTE;
1196 term_mouse (b, MA_DRAG, TO_CHR_X(X_POS(lParam)),
1197 TO_CHR_Y(Y_POS(lParam)));
1200 case WM_IGNORE_CLIP:
1201 ignore_clip = wParam; /* don't panic on DESTROYCLIPBOARD */
1203 case WM_IGNORE_KEYMENU:
1204 ignore_keymenu = wParam; /* do or don't ignore SC_KEYMENU */
1206 case WM_DESTROYCLIPBOARD:
1209 ignore_clip = FALSE;
1214 hdc = BeginPaint (hwnd, &p);
1216 SelectPalette (hdc, pal, TRUE);
1217 RealizePalette (hdc);
1219 term_paint (hdc, p.rcPaint.left, p.rcPaint.top,
1220 p.rcPaint.right, p.rcPaint.bottom);
1221 SelectObject (hdc, GetStockObject(SYSTEM_FONT));
1222 SelectObject (hdc, GetStockObject(WHITE_PEN));
1223 EndPaint (hwnd, &p);
1227 /* Notice we can get multiple netevents, FD_READ, FD_WRITE etc
1228 * but the only one that's likely to try to overload us is FD_READ.
1229 * This means buffering just one is fine.
1231 if (pending_netevent)
1232 enact_pending_netevent();
1234 pending_netevent = TRUE;
1235 pend_netevent_wParam=wParam;
1236 pend_netevent_lParam=lParam;
1248 case WM_IGNORE_SIZE:
1249 ignore_size = TRUE; /* don't panic on next WM_SIZE msg */
1251 case WM_ENTERSIZEMOVE:
1254 case WM_EXITSIZEMOVE:
1259 int width, height, w, h, ew, eh;
1260 LPRECT r = (LPRECT)lParam;
1262 width = r->right - r->left - extra_width;
1263 height = r->bottom - r->top - extra_height;
1264 w = (width + font_width/2) / font_width; if (w < 1) w = 1;
1265 h = (height + font_height/2) / font_height; if (h < 1) h = 1;
1266 UpdateSizeTip(hwnd, w, h);
1267 ew = width - w * font_width;
1268 eh = height - h * font_height;
1270 if (wParam == WMSZ_LEFT ||
1271 wParam == WMSZ_BOTTOMLEFT ||
1272 wParam == WMSZ_TOPLEFT)
1278 if (wParam == WMSZ_TOP ||
1279 wParam == WMSZ_TOPRIGHT ||
1280 wParam == WMSZ_TOPLEFT)
1290 /* break; (never reached) */
1292 if (wParam == SIZE_MINIMIZED) {
1293 SetWindowText (hwnd,
1294 cfg.win_name_always ? window_name : icon_name);
1297 if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
1298 SetWindowText (hwnd, window_name);
1300 int width, height, w, h;
1301 #if 0 /* we have fixed this using WM_SIZING now */
1305 width = LOWORD(lParam);
1306 height = HIWORD(lParam);
1307 w = width / font_width; if (w < 1) w = 1;
1308 h = height / font_height; if (h < 1) h = 1;
1309 #if 0 /* we have fixed this using WM_SIZING now */
1310 ew = width - w * font_width;
1311 eh = height - h * font_height;
1312 if (ew != 0 || eh != 0) {
1314 GetWindowRect (hwnd, &r);
1315 SendMessage (hwnd, WM_IGNORE_SIZE, 0, 0);
1316 SetWindowPos (hwnd, NULL, 0, 0,
1317 r.right - r.left - ew, r.bottom - r.top - eh,
1318 SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER);
1321 if (w != cols || h != rows || just_reconfigged) {
1323 term_size (h, w, cfg.savelines);
1325 just_reconfigged = FALSE;
1328 ignore_size = FALSE;
1331 switch (LOWORD(wParam)) {
1332 case SB_BOTTOM: term_scroll(-1, 0); break;
1333 case SB_TOP: term_scroll(+1, 0); break;
1334 case SB_LINEDOWN: term_scroll (0, +1); break;
1335 case SB_LINEUP: term_scroll (0, -1); break;
1336 case SB_PAGEDOWN: term_scroll (0, +rows/2); break;
1337 case SB_PAGEUP: term_scroll (0, -rows/2); break;
1338 case SB_THUMBPOSITION: case SB_THUMBTRACK:
1339 term_scroll (1, HIWORD(wParam)); break;
1342 case WM_PALETTECHANGED:
1343 if ((HWND) wParam != hwnd && pal != NULL) {
1344 HDC hdc = get_ctx();
1346 if (RealizePalette (hdc) > 0)
1352 case WM_QUERYNEWPALETTE:
1354 HDC hdc = get_ctx();
1356 if (RealizePalette (hdc) > 0)
1368 * Add the scan code and keypress timing to the random
1369 * number noise, if we're using ssh.
1371 if (cfg.protocol == PROT_SSH)
1372 noise_ultralight(lParam);
1375 * We don't do TranslateMessage since it disassociates the
1376 * resulting CHAR message from the KEYDOWN that sparked it,
1377 * which we occasionally don't want. Instead, we process
1378 * KEYDOWN, and call the Win32 translator functions so that
1379 * we get the translations under _our_ control.
1382 unsigned char buf[20];
1385 len = TranslateKey (message, wParam, lParam, buf);
1387 return DefWindowProc (hwnd, message, wParam, lParam);
1388 ldisc->send (buf, len);
1394 * Nevertheless, we are prepared to deal with WM_CHAR
1395 * messages, should they crop up. So if someone wants to
1396 * post the things to us as part of a macro manoeuvre,
1397 * we're ready to cope.
1400 char c = xlat_kbd2tty((unsigned char)wParam);
1401 ldisc->send (&c, 1);
1406 return DefWindowProc (hwnd, message, wParam, lParam);
1410 * Draw a line of text in the window, at given character
1411 * coordinates, in given attributes.
1413 * We are allowed to fiddle with the contents of `text'.
1415 void do_text (Context ctx, int x, int y, char *text, int len,
1416 unsigned long attr, int lattr) {
1418 int nfg, nbg, nfont;
1421 int force_manual_underline = 0;
1422 int fnt_width = font_width*(1+(lattr!=LATTR_NORM));
1423 static int *IpDx = 0, IpDxLEN = 0;;
1425 if (len>IpDxLEN || IpDx[0] != fnt_width) {
1429 IpDx = smalloc((len+16)*sizeof(int));
1432 for(i=0; i<IpDxLEN; i++)
1433 IpDx[i] = fnt_width;
1439 if (attr & ATTR_ACTCURS) {
1440 attr &= (bold_mode == BOLD_COLOURS ? 0x300200 : 0x300300);
1441 attr ^= ATTR_CUR_XOR;
1445 if (cfg.vtmode == VT_OEMONLY)
1449 * Map high-half characters in order to approximate ISO using
1450 * OEM character set. No characters are missing if the OEM codepage
1453 if (nfont & FONT_OEM) {
1455 for (i=0; i<len; i++)
1456 if (text[i] >= '\xA0' && text[i] <= '\xFF') {
1458 /* This is CP850 ... perfect translation */
1459 static const char oemhighhalf[] =
1460 "\x20\xAD\xBD\x9C\xCF\xBE\xDD\xF5" /* A0-A7 */
1461 "\xF9\xB8\xA6\xAE\xAA\xF0\xA9\xEE" /* A8-AF */
1462 "\xF8\xF1\xFD\xFC\xEF\xE6\xF4\xFA" /* B0-B7 */
1463 "\xF7\xFB\xA7\xAF\xAC\xAB\xF3\xA8" /* B8-BF */
1464 "\xB7\xB5\xB6\xC7\x8E\x8F\x92\x80" /* C0-C7 */
1465 "\xD4\x90\xD2\xD3\xDE\xD6\xD7\xD8" /* C8-CF */
1466 "\xD1\xA5\xE3\xE0\xE2\xE5\x99\x9E" /* D0-D7 */
1467 "\x9D\xEB\xE9\xEA\x9A\xED\xE8\xE1" /* D8-DF */
1468 "\x85\xA0\x83\xC6\x84\x86\x91\x87" /* E0-E7 */
1469 "\x8A\x82\x88\x89\x8D\xA1\x8C\x8B" /* E8-EF */
1470 "\xD0\xA4\x95\xA2\x93\xE4\x94\xF6" /* F0-F7 */
1471 "\x9B\x97\xA3\x96\x81\xEC\xE7\x98" /* F8-FF */
1474 /* This is CP437 ... junk translation */
1475 static const unsigned char oemhighhalf[] = {
1476 0xff, 0xad, 0x9b, 0x9c, 0x6f, 0x9d, 0x7c, 0x15,
1477 0x22, 0x43, 0xa6, 0xae, 0xaa, 0x2d, 0x52, 0xc4,
1478 0xf8, 0xf1, 0xfd, 0x33, 0x27, 0xe6, 0x14, 0xfa,
1479 0x2c, 0x31, 0xa7, 0xaf, 0xac, 0xab, 0x2f, 0xa8,
1480 0x41, 0x41, 0x41, 0x41, 0x8e, 0x8f, 0x92, 0x80,
1481 0x45, 0x90, 0x45, 0x45, 0x49, 0x49, 0x49, 0x49,
1482 0x44, 0xa5, 0x4f, 0x4f, 0x4f, 0x4f, 0x99, 0x78,
1483 0xed, 0x55, 0x55, 0x55, 0x9a, 0x59, 0x50, 0xe1,
1484 0x85, 0xa0, 0x83, 0x61, 0x84, 0x86, 0x91, 0x87,
1485 0x8a, 0x82, 0x88, 0x89, 0x8d, 0xa1, 0x8c, 0x8b,
1486 0x0b, 0xa4, 0x95, 0xa2, 0x93, 0x6f, 0x94, 0xf6,
1487 0xed, 0x97, 0xa3, 0x96, 0x81, 0x79, 0x70, 0x98
1490 text[i] = oemhighhalf[(unsigned char)text[i] - 0xA0];
1494 if (attr & ATTR_GBCHR) {
1497 * GB mapping: map # to pound, and everything else stays
1500 for (i=0; i<len; i++)
1502 text[i] = cfg.vtmode == VT_OEMONLY ? '\x9C' : '\xA3';
1503 } else if (attr & ATTR_LINEDRW) {
1506 static const char poorman[] =
1507 "*#****\xB0\xB1**+++++-----++++|****\xA3\xB7";
1510 static const char oemmap_437[] =
1511 "\x04\xB1****\xF8\xF1**\xD9\xBF\xDA\xC0\xC5"
1512 "\xC4\xC4\xC4\xC4\xC4\xC3\xB4\xC1\xC2\xB3\xF3\xF2\xE3*\x9C\xFA";
1515 static const char oemmap_850[] =
1516 "\x04\xB1****\xF8\xF1**\xD9\xBF\xDA\xC0\xC5"
1517 "\xC4\xC4\xC4\xC4\xC4\xC3\xB4\xC1\xC2\xB3****\x9C\xFA";
1519 /* Poor windows font ... eg: windows courier */
1520 static const char oemmap[] =
1521 "*\xB1****\xF8\xF1**\xD9\xBF\xDA\xC0\xC5"
1522 "\xC4\xC4\xC4\xC4\xC4\xC3\xB4\xC1\xC2\xB3****\x9C\xFA";
1525 * Line drawing mapping: map ` thru ~ (0x60 thru 0x7E) to
1526 * VT100 line drawing chars; everything else stays normal.
1528 switch (cfg.vtmode) {
1530 for (i=0; i<len; i++)
1531 if (text[i] >= '\x60' && text[i] <= '\x7E')
1532 text[i] += '\x01' - '\x60';
1535 /* Make sure we actually have an OEM font */
1536 if (fonts[nfont|FONT_OEM]) {
1539 for (i=0; i<len; i++)
1540 if (text[i] >= '\x60' && text[i] <= '\x7E')
1541 text[i] = oemmap[(unsigned char)text[i] - 0x60];
1545 for (i=0; i<len; i++)
1546 if (text[i] >= '\x60' && text[i] <= '\x7E')
1547 text[i] = poorman[(unsigned char)text[i] - 0x60];
1552 nfg = 2 * ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
1553 nbg = 2 * ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
1554 if (bold_mode == BOLD_FONT && (attr & ATTR_BOLD))
1556 if (und_mode == UND_FONT && (attr & ATTR_UNDER))
1557 nfont |= FONT_UNDERLINE;
1560 if (nfont&FONT_UNDERLINE)
1561 force_manual_underline = 1;
1562 /* Don't do the same for manual bold, it could be bad news. */
1564 nfont &= ~(FONT_BOLD|FONT_UNDERLINE);
1566 if (font_needs_hand_underlining && (attr & ATTR_UNDER))
1567 force_manual_underline = 1;
1568 if (attr & ATTR_REVERSE) {
1569 t = nfg; nfg = nbg; nbg = t;
1571 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BOLD))
1573 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BLINK))
1577 SelectObject (hdc, fonts[nfont]);
1578 SetTextColor (hdc, fg);
1579 SetBkColor (hdc, bg);
1580 SetBkMode (hdc, OPAQUE);
1583 line_box.right = x+fnt_width*len;
1584 line_box.bottom = y+font_height;
1585 ExtTextOut (hdc, x, y, ETO_CLIPPED|ETO_OPAQUE, &line_box, text, len, IpDx);
1586 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
1587 SetBkMode (hdc, TRANSPARENT);
1589 /* GRR: This draws the character outside it's box and can leave
1590 * 'droppings' even with the clip box! I suppose I could loop it
1591 * one character at a time ... yuk.
1593 * Or ... I could do a test print with "W", and use +1 or -1 for this
1594 * shift depending on if the leftmost column is blank...
1596 ExtTextOut (hdc, x-1, y, ETO_CLIPPED, &line_box, text, len, IpDx);
1598 if (force_manual_underline ||
1599 (und_mode == UND_LINE && (attr & ATTR_UNDER))) {
1601 oldpen = SelectObject (hdc, CreatePen(PS_SOLID, 0, fg));
1602 MoveToEx (hdc, x, y+descent, NULL);
1603 LineTo (hdc, x+len*fnt_width, y+descent);
1604 oldpen = SelectObject (hdc, oldpen);
1605 DeleteObject (oldpen);
1607 if (attr & ATTR_PASCURS) {
1610 pts[0].x = pts[1].x = pts[4].x = x;
1611 pts[2].x = pts[3].x = x+fnt_width-1;
1612 pts[0].y = pts[3].y = pts[4].y = y;
1613 pts[1].y = pts[2].y = y+font_height-1;
1614 oldpen = SelectObject (hdc, CreatePen(PS_SOLID, 0, colours[23]));
1615 Polyline (hdc, pts, 5);
1616 oldpen = SelectObject (hdc, oldpen);
1617 DeleteObject (oldpen);
1621 static int check_compose(int first, int second) {
1623 static char * composetbl[] = {
1624 "++#", "AA@", "(([", "//\\", "))]", "(-{", "-)}", "/^|", "!!¡", "C/¢",
1625 "C|¢", "L-£", "L=£", "XO¤", "X0¤", "Y-¥", "Y=¥", "||¦", "SO§", "S!§",
1626 "S0§", "\"\"¨", "CO©", "C0©", "A_ª", "<<«", ",-¬", "--", "RO®",
1627 "-^¯", "0^°", "+-±", "2^²", "3^³", "''´", "/Uµ", "P!¶", ".^·", ",,¸",
1628 "1^¹", "O_º", ">>»", "14¼", "12½", "34¾", "??¿", "`AÀ", "'AÁ", "^AÂ",
1629 "~AÃ", "\"AÄ", "*AÅ", "AEÆ", ",CÇ", "`EÈ", "'EÉ", "^EÊ", "\"EË",
1630 "`IÌ", "'IÍ", "^IÎ", "\"IÏ", "-DÐ", "~NÑ", "`OÒ", "'OÓ", "^OÔ",
1631 "~OÕ", "\"OÖ", "XX×", "/OØ", "`UÙ", "'UÚ", "^UÛ", "\"UÜ", "'YÝ",
1632 "HTÞ", "ssß", "`aà", "'aá", "^aâ", "~aã", "\"aä", "*aå", "aeæ", ",cç",
1633 "`eè", "'eé", "^eê", "\"eë", "`iì", "'ií", "^iî", "\"iï", "-dð", "~nñ",
1634 "`oò", "'oó", "^oô", "~oõ", "\"oö", ":-÷", "o/ø", "`uù", "'uú", "^uû",
1635 "\"uü", "'yý", "htþ", "\"yÿ",
1639 static int recurse = 0;
1646 sprintf(buf, "cc(%d,%d)", first, second);
1651 for(c=composetbl; *c; c++) {
1652 if( (*c)[0] == first && (*c)[1] == second)
1654 return (*c)[2] & 0xFF;
1661 nc = check_compose(second, first);
1663 nc = check_compose(toupper(first), toupper(second));
1665 nc = check_compose(toupper(second), toupper(first));
1673 * Translate a WM_(SYS)?KEY(UP|DOWN) message into a string of ASCII
1674 * codes. Returns number of bytes used or zero to drop the message
1675 * or -1 to forward the message to windows.
1677 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, unsigned char *output) {
1679 int scan, left_alt = 0, key_down, shift_state;
1681 unsigned char * p = output;
1683 static WORD keys[3];
1684 static int compose_state = 0;
1685 static int compose_char = 0;
1686 static WPARAM compose_key = 0;
1688 r = GetKeyboardState(keystate);
1689 if (!r) memset(keystate, 0, sizeof(keystate));
1692 /* Note if AltGr was pressed and if it was used as a compose key */
1693 if (wParam == VK_MENU && (HIWORD(lParam)&KF_EXTENDED))
1695 keystate[VK_RMENU] = keystate[VK_MENU];
1696 if (!compose_state) compose_key = wParam;
1698 if (wParam == VK_APPS && !compose_state)
1699 compose_key = wParam;
1701 if (wParam == compose_key)
1703 if (compose_state == 0 && (HIWORD(lParam)&(KF_UP|KF_REPEAT))==0)
1705 else if (compose_state == 1 && (HIWORD(lParam)&KF_UP))
1710 else if (compose_state==1 && wParam != VK_CONTROL)
1713 /* Nastyness with NUMLock - Shift-NUMLock is left alone though */
1714 if ( (cfg.funky_type == 0 || (cfg.funky_type == 1 && app_keypad_keys))
1715 && wParam==VK_NUMLOCK && !(keystate[VK_SHIFT]&0x80)) {
1717 wParam = VK_EXECUTE;
1719 /* UnToggle NUMLock */
1720 if ((HIWORD(lParam)&(KF_UP|KF_REPEAT))==0)
1721 keystate[VK_NUMLOCK] ^= 1;
1724 /* And write back the 'adjusted' state */
1725 SetKeyboardState (keystate);
1728 /* Disable Auto repeat if required */
1729 if (repeat_off && (HIWORD(lParam)&(KF_UP|KF_REPEAT))==KF_REPEAT)
1732 if ((HIWORD(lParam)&KF_ALTDOWN) && (keystate[VK_RMENU]&0x80) == 0)
1735 key_down = ((HIWORD(lParam)&KF_UP)==0);
1737 /* Make sure Ctrl-ALT is not the same as AltGr for ToAscii */
1738 if (left_alt && (keystate[VK_CONTROL]&0x80))
1739 keystate[VK_MENU] = 0;
1741 scan = (HIWORD(lParam) & (KF_UP | KF_EXTENDED | 0xFF));
1742 shift_state = ((keystate[VK_SHIFT]&0x80)!=0)
1743 + ((keystate[VK_CONTROL]&0x80)!=0)*2;
1746 * Record that we pressed key so the scroll window can be reset, but
1747 * be careful to avoid Shift-UP/Down
1749 if( wParam != VK_SHIFT && wParam != VK_PRIOR && wParam != VK_NEXT ) {
1753 /* Make sure we're not pasting */
1754 if (key_down) term_nopaste();
1756 if (compose_state>1 && left_alt) compose_state = 0;
1758 /* Sanitize the number pad if not using a PC NumPad */
1759 if( left_alt || (app_keypad_keys && cfg.funky_type != 2)
1760 || cfg.nethack_keypad || compose_state )
1762 if ((HIWORD(lParam)&KF_EXTENDED) == 0)
1767 case VK_INSERT: nParam = VK_NUMPAD0; break;
1768 case VK_END: nParam = VK_NUMPAD1; break;
1769 case VK_DOWN: nParam = VK_NUMPAD2; break;
1770 case VK_NEXT: nParam = VK_NUMPAD3; break;
1771 case VK_LEFT: nParam = VK_NUMPAD4; break;
1772 case VK_CLEAR: nParam = VK_NUMPAD5; break;
1773 case VK_RIGHT: nParam = VK_NUMPAD6; break;
1774 case VK_HOME: nParam = VK_NUMPAD7; break;
1775 case VK_UP: nParam = VK_NUMPAD8; break;
1776 case VK_PRIOR: nParam = VK_NUMPAD9; break;
1777 case VK_DELETE: nParam = VK_DECIMAL; break;
1781 if (keystate[VK_NUMLOCK]&1) shift_state |= 1;
1787 /* If a key is pressed and AltGr is not active */
1788 if (key_down && (keystate[VK_RMENU]&0x80) == 0 && !compose_state)
1790 /* Okay, prepare for most alts then ...*/
1791 if (left_alt) *p++ = '\033';
1793 /* Lets see if it's a pattern we know all about ... */
1794 if (wParam == VK_PRIOR && shift_state == 1) {
1795 SendMessage (hwnd, WM_VSCROLL, SB_PAGEUP, 0);
1798 if (wParam == VK_NEXT && shift_state == 1) {
1799 SendMessage (hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
1802 if (wParam == VK_INSERT && shift_state == 1) {
1803 term_mouse (MB_PASTE, MA_CLICK, 0, 0);
1804 term_mouse (MB_PASTE, MA_RELEASE, 0, 0);
1807 if (left_alt && wParam == VK_F4 && cfg.alt_f4) {
1810 if (left_alt && wParam == VK_SPACE && cfg.alt_space) {
1812 SendMessage (hwnd, WM_IGNORE_KEYMENU, FALSE, 0);
1813 SendMessage (hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0);
1814 SendMessage (hwnd, WM_IGNORE_KEYMENU, TRUE, 0);
1818 /* Nethack keypad */
1819 if (cfg.nethack_keypad && !left_alt) {
1821 case VK_NUMPAD1: *p++ = shift_state ? 'B': 'b'; return p-output;
1822 case VK_NUMPAD2: *p++ = shift_state ? 'J': 'j'; return p-output;
1823 case VK_NUMPAD3: *p++ = shift_state ? 'N': 'n'; return p-output;
1824 case VK_NUMPAD4: *p++ = shift_state ? 'H': 'h'; return p-output;
1825 case VK_NUMPAD5: *p++ = shift_state ? '.': '.'; return p-output;
1826 case VK_NUMPAD6: *p++ = shift_state ? 'L': 'l'; return p-output;
1827 case VK_NUMPAD7: *p++ = shift_state ? 'Y': 'y'; return p-output;
1828 case VK_NUMPAD8: *p++ = shift_state ? 'K': 'k'; return p-output;
1829 case VK_NUMPAD9: *p++ = shift_state ? 'U': 'u'; return p-output;
1833 /* Application Keypad */
1837 if ( cfg.funky_type == 0 ||
1838 ( cfg.funky_type == 1 && app_keypad_keys)) switch(wParam) {
1839 case VK_EXECUTE: if (app_keypad_keys) xkey = 'P'; break;
1840 case VK_DIVIDE: xkey = 'Q'; break;
1841 case VK_MULTIPLY:xkey = 'R'; break;
1842 case VK_SUBTRACT:xkey = 'S'; break;
1844 if(app_keypad_keys) switch(wParam) {
1845 case VK_NUMPAD0: xkey = 'p'; break;
1846 case VK_NUMPAD1: xkey = 'q'; break;
1847 case VK_NUMPAD2: xkey = 'r'; break;
1848 case VK_NUMPAD3: xkey = 's'; break;
1849 case VK_NUMPAD4: xkey = 't'; break;
1850 case VK_NUMPAD5: xkey = 'u'; break;
1851 case VK_NUMPAD6: xkey = 'v'; break;
1852 case VK_NUMPAD7: xkey = 'w'; break;
1853 case VK_NUMPAD8: xkey = 'x'; break;
1854 case VK_NUMPAD9: xkey = 'y'; break;
1856 case VK_DECIMAL: xkey = 'n'; break;
1857 case VK_ADD: if(shift_state) xkey = 'm';
1861 if (HIWORD(lParam)&KF_EXTENDED)
1869 if (xkey>='P' && xkey<='S')
1870 p += sprintf((char *)p, "\x1B%c", xkey);
1872 p += sprintf((char *)p, "\x1B?%c", xkey);
1875 p += sprintf((char *)p, "\x1BO%c", xkey);
1880 if (wParam == VK_BACK && shift_state == 0 ) /* Backspace */
1882 *p++ = (cfg.bksp_is_delete ? 0x7F : 0x08);
1885 if (wParam == VK_TAB && shift_state == 1 ) /* Shift tab */
1887 *p++ = 0x1B; *p++ = '['; *p++ = 'Z'; return p - output;
1889 if (wParam == VK_SPACE && shift_state == 2 ) /* Ctrl-Space */
1891 *p++ = 0; return p - output;
1893 if (wParam == VK_SPACE && shift_state == 3 ) /* Ctrl-Shift-Space */
1895 *p++ = 160; return p - output;
1897 if (wParam == VK_CANCEL && shift_state == 2 ) /* Ctrl-Break */
1899 *p++ = 3; return p - output;
1901 /* Control-2 to Control-8 are special */
1902 if (shift_state == 2 && wParam >= '2' && wParam <= '8')
1904 *p++ = "\000\033\034\035\036\037\177"[wParam-'2'];
1907 if (shift_state == 2 && wParam == 0xBD) {
1911 if (shift_state == 2 && wParam == 0xDF) {
1915 if (shift_state == 0 && wParam == VK_RETURN && cr_lf_return) {
1916 *p++ = '\r'; *p++ = '\n';
1921 * Next, all the keys that do tilde codes. (ESC '[' nn '~',
1922 * for integer decimal nn.)
1924 * We also deal with the weird ones here. Linux VCs replace F1
1925 * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
1926 * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
1931 case VK_F1: code = (keystate[VK_SHIFT] & 0x80 ? 23 : 11); break;
1932 case VK_F2: code = (keystate[VK_SHIFT] & 0x80 ? 24 : 12); break;
1933 case VK_F3: code = (keystate[VK_SHIFT] & 0x80 ? 25 : 13); break;
1934 case VK_F4: code = (keystate[VK_SHIFT] & 0x80 ? 26 : 14); break;
1935 case VK_F5: code = (keystate[VK_SHIFT] & 0x80 ? 28 : 15); break;
1936 case VK_F6: code = (keystate[VK_SHIFT] & 0x80 ? 29 : 17); break;
1937 case VK_F7: code = (keystate[VK_SHIFT] & 0x80 ? 31 : 18); break;
1938 case VK_F8: code = (keystate[VK_SHIFT] & 0x80 ? 32 : 19); break;
1939 case VK_F9: code = (keystate[VK_SHIFT] & 0x80 ? 33 : 20); break;
1940 case VK_F10: code = (keystate[VK_SHIFT] & 0x80 ? 34 : 21); break;
1941 case VK_F11: code = 23; break;
1942 case VK_F12: code = 24; break;
1943 case VK_F13: code = 25; break;
1944 case VK_F14: code = 26; break;
1945 case VK_F15: code = 28; break;
1946 case VK_F16: code = 29; break;
1947 case VK_F17: code = 31; break;
1948 case VK_F18: code = 32; break;
1949 case VK_F19: code = 33; break;
1950 case VK_F20: code = 34; break;
1951 case VK_HOME: code = 1; break;
1952 case VK_INSERT: code = 2; break;
1953 case VK_DELETE: code = 3; break;
1954 case VK_END: code = 4; break;
1955 case VK_PRIOR: code = 5; break;
1956 case VK_NEXT: code = 6; break;
1958 if (cfg.funky_type == 1 && code >= 11 && code <= 15) {
1959 p += sprintf((char *)p, "\x1B[[%c", code + 'A' - 11);
1962 if (cfg.funky_type == 2 && code >= 11 && code <= 14) {
1963 p += sprintf((char *)p, "\x1BO%c", code + 'P' - 11);
1966 if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
1967 p += sprintf((char *)p, code == 1 ? "\x1B[H" : "\x1BOw");
1971 p += sprintf((char *)p, "\x1B[%d~", code);
1976 * Now the remaining keys (arrows and Keypad 5. Keypad 5 for
1977 * some reason seems to send VK_CLEAR to Windows...).
1982 case VK_UP: xkey = 'A'; break;
1983 case VK_DOWN: xkey = 'B'; break;
1984 case VK_RIGHT: xkey = 'C'; break;
1985 case VK_LEFT: xkey = 'D'; break;
1986 case VK_CLEAR: xkey = 'G'; break;
1991 p += sprintf((char *)p, "\x1B%c", xkey);
1992 else if (app_cursor_keys)
1993 p += sprintf((char *)p, "\x1BO%c", xkey);
1995 p += sprintf((char *)p, "\x1B[%c", xkey);
2001 /* Okay we've done everything interesting; let windows deal with
2002 * the boring stuff */
2004 BOOL capsOn=keystate[VK_CAPITAL] !=0;
2006 /* helg: clear CAPS LOCK state if caps lock switches to cyrillic */
2007 if(cfg.xlat_capslockcyr)
2008 keystate[VK_CAPITAL] = 0;
2010 r = ToAscii (wParam, scan, keystate, keys, 0);
2016 unsigned char ch = (unsigned char)keys[i];
2018 if (compose_state==2 && (ch&0x80) == 0 && ch>' ') {
2023 if (compose_state==3 && (ch&0x80) == 0 && ch>' ') {
2027 if ((nc=check_compose(compose_char,ch)) == -1)
2032 *p++ = xlat_kbd2tty((unsigned char)nc);
2038 if( left_alt && key_down ) *p++ = '\033';
2044 ch = xlat_latkbd2win(ch);
2045 *p++ = xlat_kbd2tty(ch);
2049 /* This is so the ALT-Numpad and dead keys work correctly. */
2056 /* This stops ALT press-release doing a 'COMMAND MENU' function */
2058 if (message == WM_SYSKEYUP && wParam == VK_MENU)
2060 keystate[VK_MENU] = 0;
2068 void set_title (char *title) {
2069 sfree (window_name);
2070 window_name = smalloc(1+strlen(title));
2071 strcpy (window_name, title);
2072 if (cfg.win_name_always || !IsIconic(hwnd))
2073 SetWindowText (hwnd, title);
2076 void set_icon (char *title) {
2078 icon_name = smalloc(1+strlen(title));
2079 strcpy (icon_name, title);
2080 if (!cfg.win_name_always && IsIconic(hwnd))
2081 SetWindowText (hwnd, title);
2084 void set_sbar (int total, int start, int page) {
2087 if (!cfg.scrollbar) return;
2089 si.cbSize = sizeof(si);
2090 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
2092 si.nMax = total - 1;
2096 SetScrollInfo (hwnd, SB_VERT, &si, TRUE);
2099 Context get_ctx(void) {
2104 SelectPalette (hdc, pal, FALSE);
2110 void free_ctx (Context ctx) {
2111 SelectPalette (ctx, GetStockObject (DEFAULT_PALETTE), FALSE);
2112 ReleaseDC (hwnd, ctx);
2115 static void real_palette_set (int n, int r, int g, int b) {
2117 logpal->palPalEntry[n].peRed = r;
2118 logpal->palPalEntry[n].peGreen = g;
2119 logpal->palPalEntry[n].peBlue = b;
2120 logpal->palPalEntry[n].peFlags = PC_NOCOLLAPSE;
2121 colours[n] = PALETTERGB(r, g, b);
2122 SetPaletteEntries (pal, 0, NCOLOURS, logpal->palPalEntry);
2124 colours[n] = RGB(r, g, b);
2127 void palette_set (int n, int r, int g, int b) {
2128 static const int first[21] = {
2129 0, 2, 4, 6, 8, 10, 12, 14,
2130 1, 3, 5, 7, 9, 11, 13, 15,
2133 real_palette_set (first[n], r, g, b);
2135 real_palette_set (first[n]+1, r, g, b);
2137 HDC hdc = get_ctx();
2138 UnrealizeObject (pal);
2139 RealizePalette (hdc);
2144 void palette_reset (void) {
2147 for (i = 0; i < NCOLOURS; i++) {
2149 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
2150 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
2151 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
2152 logpal->palPalEntry[i].peFlags = 0;
2153 colours[i] = PALETTERGB(defpal[i].rgbtRed,
2154 defpal[i].rgbtGreen,
2155 defpal[i].rgbtBlue);
2157 colours[i] = RGB(defpal[i].rgbtRed,
2158 defpal[i].rgbtGreen,
2159 defpal[i].rgbtBlue);
2164 SetPaletteEntries (pal, 0, NCOLOURS, logpal->palPalEntry);
2166 RealizePalette (hdc);
2171 void write_clip (void *data, int len) {
2175 clipdata = GlobalAlloc (GMEM_DDESHARE | GMEM_MOVEABLE, len + 1);
2178 lock = GlobalLock (clipdata);
2181 memcpy (lock, data, len);
2182 ((unsigned char *) lock) [len] = 0;
2183 GlobalUnlock (clipdata);
2185 SendMessage (hwnd, WM_IGNORE_CLIP, TRUE, 0);
2186 if (OpenClipboard (hwnd)) {
2188 SetClipboardData (CF_TEXT, clipdata);
2191 GlobalFree (clipdata);
2192 SendMessage (hwnd, WM_IGNORE_CLIP, FALSE, 0);
2195 void get_clip (void **p, int *len) {
2196 static HGLOBAL clipdata = NULL;
2200 GlobalUnlock (clipdata);
2204 if (OpenClipboard (NULL)) {
2205 clipdata = GetClipboardData (CF_TEXT);
2208 *p = GlobalLock (clipdata);
2222 * Move `lines' lines from position `from' to position `to' in the
2225 void optimised_move (int to, int from, int lines) {
2229 min = (to < from ? to : from);
2230 max = to + from - min;
2232 r.left = 0; r.right = cols * font_width;
2233 r.top = min * font_height; r.bottom = (max+lines) * font_height;
2234 ScrollWindow (hwnd, 0, (to - from) * font_height, &r, &r);
2238 * Print a message box and perform a fatal exit.
2240 void fatalbox(char *fmt, ...) {
2245 vsprintf(stuff, fmt, ap);
2247 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
2254 void beep(int errorbeep) {
2255 static long last_beep = 0;
2256 long now, beep_diff;
2258 now = GetTickCount();
2259 beep_diff = now-last_beep;
2261 /* Make sure we only respond to one beep per packet or so */
2262 if (beep_diff>=0 && beep_diff<50)
2266 MessageBeep(MB_ICONHAND);
2270 last_beep = GetTickCount();