14 #define PUTTY_DO_GLOBALS /* actually _define_ globals */
19 #define IDM_SHOWLOG 0x0010
20 #define IDM_NEWSESS 0x0020
21 #define IDM_DUPSESS 0x0030
22 #define IDM_RECONF 0x0040
23 #define IDM_CLRSB 0x0050
24 #define IDM_RESET 0x0060
25 #define IDM_TEL_AYT 0x0070
26 #define IDM_TEL_BRK 0x0080
27 #define IDM_TEL_SYNCH 0x0090
28 #define IDM_TEL_EC 0x00a0
29 #define IDM_TEL_EL 0x00b0
30 #define IDM_TEL_GA 0x00c0
31 #define IDM_TEL_NOP 0x00d0
32 #define IDM_TEL_ABORT 0x00e0
33 #define IDM_TEL_AO 0x00f0
34 #define IDM_TEL_IP 0x0100
35 #define IDM_TEL_SUSP 0x0110
36 #define IDM_TEL_EOR 0x0120
37 #define IDM_TEL_EOF 0x0130
38 #define IDM_ABOUT 0x0140
39 #define IDM_SAVEDSESS 0x0150
41 #define IDM_SAVED_MIN 0x1000
42 #define IDM_SAVED_MAX 0x2000
44 #define WM_IGNORE_SIZE (WM_XUSER + 1)
45 #define WM_IGNORE_CLIP (WM_XUSER + 2)
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 HBITMAP caretbm;
88 static int dbltime, lasttime, lastact;
89 static Mouse_Button lastbtn;
91 static char *window_name, *icon_name;
93 static Ldisc *real_ldisc;
95 void begin_session(void) {
99 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) {
100 static char appname[] = "PuTTY";
105 int guess_width, guess_height;
108 flags = FLAG_VERBOSE | FLAG_INTERACTIVE;
110 winsock_ver = MAKEWORD(1, 1);
111 if (WSAStartup(winsock_ver, &wsadata)) {
112 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
113 MB_OK | MB_ICONEXCLAMATION);
116 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
117 MessageBox(NULL, "WinSock version is incompatible with 1.1",
118 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
122 /* WISHLIST: maybe allow config tweaking even if winsock not present? */
124 InitCommonControls();
127 * Process the command line.
132 default_protocol = DEFAULT_PROTOCOL;
133 default_port = DEFAULT_PORT;
135 do_defaults(NULL, &cfg);
138 while (*p && isspace(*p)) p++;
141 * Process command line options first. Yes, this can be
142 * done better, and it will be as soon as I have the
146 char *q = p + strcspn(p, " \t");
149 tolower(p[0]) == 's' &&
150 tolower(p[1]) == 's' &&
151 tolower(p[2]) == 'h') {
152 default_protocol = cfg.protocol = PROT_SSH;
153 default_port = cfg.port = 22;
154 } else if (q == p + 3 &&
155 tolower(p[0]) == 'l' &&
156 tolower(p[1]) == 'o' &&
157 tolower(p[2]) == 'g') {
158 logfile = "putty.log";
159 } else if (q == p + 7 &&
160 tolower(p[0]) == 'c' &&
161 tolower(p[1]) == 'l' &&
162 tolower(p[2]) == 'e' &&
163 tolower(p[3]) == 'a' &&
164 tolower(p[4]) == 'n' &&
165 tolower(p[5]) == 'u' &&
166 tolower(p[6]) == 'p') {
168 * `putty -cleanup'. Remove all registry entries
169 * associated with PuTTY, and also find and delete
170 * the random seed file.
173 "This procedure will remove ALL Registry\n"
174 "entries associated with PuTTY, and will\n"
175 "also remove the PuTTY random seed file.\n"
177 "THIS PROCESS WILL DESTROY YOUR SAVED\n"
178 "SESSIONS. Are you really sure you want\n"
181 MB_YESNO | MB_ICONWARNING) == IDYES) {
186 p = q + strspn(q, " \t");
190 * An initial @ means to activate a saved session.
193 do_defaults (p+1, &cfg);
194 if (!*cfg.host && !do_config()) {
198 } else if (*p == '&') {
200 * An initial & means we've been given a command line
201 * containing the hex value of a HANDLE for a file
202 * mapping object, which we must then extract as a
207 if (sscanf(p+1, "%p", &filemap) == 1 &&
208 (cp = MapViewOfFile(filemap, FILE_MAP_READ,
209 0, 0, sizeof(Config))) != NULL) {
212 CloseHandle(filemap);
213 } else if (!do_config()) {
220 * If the hostname starts with "telnet:", set the
221 * protocol to Telnet and process the string as a
224 if (!strncmp(q, "telnet:", 7)) {
228 if (q[0] == '/' && q[1] == '/')
230 cfg.protocol = PROT_TELNET;
232 while (*p && *p != ':' && *p != '/') p++;
240 strncpy (cfg.host, q, sizeof(cfg.host)-1);
241 cfg.host[sizeof(cfg.host)-1] = '\0';
243 while (*p && !isspace(*p)) p++;
246 strncpy (cfg.host, q, sizeof(cfg.host)-1);
247 cfg.host[sizeof(cfg.host)-1] = '\0';
248 while (*p && isspace(*p)) p++;
261 /* See if host is of the form user@host */
262 if (cfg.host[0] != '\0') {
263 char *atsign = strchr(cfg.host, '@');
264 /* Make sure we're not overflowing the user field */
266 if (atsign-cfg.host < sizeof cfg.username) {
267 strncpy (cfg.username, cfg.host, atsign-cfg.host);
268 cfg.username[atsign-cfg.host] = '\0';
270 memmove(cfg.host, atsign+1, 1+strlen(atsign+1));
276 * Select protocol. This is farmed out into a table in a
277 * separate file to enable an ssh-free variant.
282 for (i = 0; backends[i].backend != NULL; i++)
283 if (backends[i].protocol == cfg.protocol) {
284 back = backends[i].backend;
288 MessageBox(NULL, "Unsupported protocol number found",
289 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
295 real_ldisc = (cfg.ldisc_term ? &ldisc_term : &ldisc_simple);
296 /* To start with, we use the simple line discipline, so we can
297 * type passwords etc without fear of them being echoed... */
298 ldisc = &ldisc_simple;
302 wndclass.lpfnWndProc = WndProc;
303 wndclass.cbClsExtra = 0;
304 wndclass.cbWndExtra = 0;
305 wndclass.hInstance = inst;
306 wndclass.hIcon = LoadIcon (inst,
307 MAKEINTRESOURCE(IDI_MAINICON));
308 wndclass.hCursor = LoadCursor (NULL, IDC_IBEAM);
309 wndclass.hbrBackground = GetStockObject (BLACK_BRUSH);
310 wndclass.lpszMenuName = NULL;
311 wndclass.lpszClassName = appname;
313 RegisterClass (&wndclass);
318 savelines = cfg.savelines;
324 * Guess some defaults for the window size. This all gets
325 * updated later, so we don't really care too much. However, we
326 * do want the font width/height guesses to correspond to a
327 * large font rather than a small one...
334 term_size (cfg.height, cfg.width, cfg.savelines);
335 guess_width = extra_width + font_width * cols;
336 guess_height = extra_height + font_height * rows;
339 HWND w = GetDesktopWindow();
340 GetWindowRect (w, &r);
341 if (guess_width > r.right - r.left)
342 guess_width = r.right - r.left;
343 if (guess_height > r.bottom - r.top)
344 guess_height = r.bottom - r.top;
348 int winmode = WS_OVERLAPPEDWINDOW|WS_VSCROLL;
349 if (!cfg.scrollbar) winmode &= ~(WS_VSCROLL);
350 if (cfg.locksize) winmode &= ~(WS_THICKFRAME|WS_MAXIMIZEBOX);
351 hwnd = CreateWindow (appname, appname,
353 CW_USEDEFAULT, CW_USEDEFAULT,
354 guess_width, guess_height,
355 NULL, NULL, inst, NULL);
359 * Initialise the fonts, simultaneously correcting the guesses
360 * for font_{width,height}.
362 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
367 * Correct the guesses for extra_{width,height}.
371 GetWindowRect (hwnd, &wr);
372 GetClientRect (hwnd, &cr);
373 extra_width = wr.right - wr.left - cr.right + cr.left;
374 extra_height = wr.bottom - wr.top - cr.bottom + cr.top;
378 * Resize the window, now we know what size we _really_ want it
381 guess_width = extra_width + font_width * cols;
382 guess_height = extra_height + font_height * rows;
383 SendMessage (hwnd, WM_IGNORE_SIZE, 0, 0);
384 SetWindowPos (hwnd, NULL, 0, 0, guess_width, guess_height,
385 SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
388 * Set up a caret bitmap, with no content.
392 int size = (font_width+15)/16 * 2 * font_height;
393 bits = calloc(size, 1);
394 caretbm = CreateBitmap(font_width, font_height, 1, 1, bits);
399 * Initialise the scroll bar.
404 si.cbSize = sizeof(si);
405 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
410 SetScrollInfo (hwnd, SB_VERT, &si, FALSE);
414 * Start up the telnet connection.
418 char msg[1024], *title;
421 error = back->init (hwnd, cfg.host, cfg.port, &realhost);
423 sprintf(msg, "Unable to open connection:\n%s", error);
424 MessageBox(NULL, msg, "PuTTY Error", MB_ICONERROR | MB_OK);
427 window_name = icon_name = NULL;
429 title = cfg.wintitle;
431 sprintf(msg, "%s - PuTTY", realhost);
438 session_closed = FALSE;
441 * Set up the input and output buffers.
444 outbuf_reap = outbuf_head = 0;
447 * Prepare the mouse handler.
449 lastact = MA_NOTHING;
450 lastbtn = MB_NOTHING;
451 dbltime = GetDoubleClickTime();
454 * Set up the session-control options on the system menu.
457 HMENU m = GetSystemMenu (hwnd, FALSE);
461 AppendMenu (m, MF_SEPARATOR, 0, 0);
462 if (cfg.protocol == PROT_TELNET) {
464 AppendMenu (p, MF_ENABLED, IDM_TEL_AYT, "Are You There");
465 AppendMenu (p, MF_ENABLED, IDM_TEL_BRK, "Break");
466 AppendMenu (p, MF_ENABLED, IDM_TEL_SYNCH, "Synch");
467 AppendMenu (p, MF_SEPARATOR, 0, 0);
468 AppendMenu (p, MF_ENABLED, IDM_TEL_EC, "Erase Character");
469 AppendMenu (p, MF_ENABLED, IDM_TEL_EL, "Erase Line");
470 AppendMenu (p, MF_ENABLED, IDM_TEL_GA, "Go Ahead");
471 AppendMenu (p, MF_ENABLED, IDM_TEL_NOP, "No Operation");
472 AppendMenu (p, MF_SEPARATOR, 0, 0);
473 AppendMenu (p, MF_ENABLED, IDM_TEL_ABORT, "Abort Process");
474 AppendMenu (p, MF_ENABLED, IDM_TEL_AO, "Abort Output");
475 AppendMenu (p, MF_ENABLED, IDM_TEL_IP, "Interrupt Process");
476 AppendMenu (p, MF_ENABLED, IDM_TEL_SUSP, "Suspend Process");
477 AppendMenu (p, MF_SEPARATOR, 0, 0);
478 AppendMenu (p, MF_ENABLED, IDM_TEL_EOR, "End Of Record");
479 AppendMenu (p, MF_ENABLED, IDM_TEL_EOF, "End Of File");
480 AppendMenu (m, MF_POPUP | MF_ENABLED, (UINT) p, "Telnet Command");
481 AppendMenu (m, MF_SEPARATOR, 0, 0);
483 AppendMenu (m, MF_ENABLED, IDM_SHOWLOG, "&Event Log");
484 AppendMenu (m, MF_SEPARATOR, 0, 0);
485 AppendMenu (m, MF_ENABLED, IDM_NEWSESS, "Ne&w Session");
486 AppendMenu (m, MF_ENABLED, IDM_DUPSESS, "&Duplicate Session");
489 for (i = 1 ; i < ((nsessions < 256) ? nsessions : 256) ; i++)
490 AppendMenu (s, MF_ENABLED, IDM_SAVED_MIN + (16 * i) , sessions[i]);
491 AppendMenu (m, MF_POPUP | MF_ENABLED, (UINT) s, "Sa&ved Sessions");
492 AppendMenu (m, MF_ENABLED, IDM_RECONF, "Chan&ge Settings");
493 AppendMenu (m, MF_SEPARATOR, 0, 0);
494 AppendMenu (m, MF_ENABLED, IDM_CLRSB, "C&lear Scrollback");
495 AppendMenu (m, MF_ENABLED, IDM_RESET, "Rese&t Terminal");
496 AppendMenu (m, MF_SEPARATOR, 0, 0);
497 AppendMenu (m, MF_ENABLED, IDM_ABOUT, "&About PuTTY");
501 * Finally show the window!
503 ShowWindow (hwnd, show);
506 * Set the palette up.
512 has_focus = (GetForegroundWindow() == hwnd);
516 int timer_id = 0, long_timer = 0;
518 while (GetMessage (&msg, NULL, 0, 0) == 1) {
519 /* Sometimes DispatchMessage calls routines that use their own
520 * GetMessage loop, setup this timer so we get some control back.
522 * Also call term_update() from the timer so that if the host
523 * is sending data flat out we still do redraws.
525 if(timer_id && long_timer) {
526 KillTimer(hwnd, timer_id);
527 long_timer = timer_id = 0;
530 timer_id = SetTimer(hwnd, 1, 20, NULL);
531 DispatchMessage (&msg);
533 /* This is too fast, but I'll leave it for now 'cause it shows
534 * how often term_update is called (far too often at times!)
538 /* Send the paste buffer if there's anything to send */
541 /* If there's nothing new in the queue then we can do everything
542 * we've delayed, reading the socket, writing, and repainting
545 if (!PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE)) {
546 if (pending_netevent) {
547 enact_pending_netevent();
552 if (!PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE)) {
554 KillTimer(hwnd, timer_id);
563 timer_id = SetTimer(hwnd, 1, 2000, NULL);
564 else if (cfg.blinktext)
565 timer_id = SetTimer(hwnd, 1, 250, NULL);
567 timer_id = SetTimer(hwnd, 1, 500, NULL);
580 DeleteObject(fonts[i]);
587 if (cfg.protocol == PROT_SSH) {
598 * Print a message box and close the connection.
600 void connection_fatal(char *fmt, ...) {
605 vsprintf(stuff, fmt, ap);
607 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
608 if (cfg.close_on_exit)
611 session_closed = TRUE;
612 SetWindowText (hwnd, "PuTTY (inactive)");
617 * Actually do the job requested by a WM_NETEVENT
619 static void enact_pending_netevent(void) {
621 static int reentering = 0;
624 return; /* don't unpend the pending */
626 pending_netevent = FALSE;
629 i = back->msg (pend_netevent_wParam, pend_netevent_lParam);
634 switch (WSABASEERR + (-i) % 10000) {
636 sprintf(buf, "Connection reset by peer");
639 sprintf(buf, "Unexpected network error %d", -i);
642 connection_fatal(buf);
645 if (cfg.close_on_exit)
648 session_closed = TRUE;
649 MessageBox(hwnd, "Connection closed by remote host",
650 "PuTTY", MB_OK | MB_ICONINFORMATION);
651 SetWindowText (hwnd, "PuTTY (inactive)");
657 * Copy the colour palette from the configuration data into defpal.
658 * This is non-trivial because the colour indices are different.
660 static void cfgtopalette(void) {
662 static const int ww[] = {
663 6, 7, 8, 9, 10, 11, 12, 13,
664 14, 15, 16, 17, 18, 19, 20, 21,
665 0, 1, 2, 3, 4, 4, 5, 5
668 for (i=0; i<24; i++) {
670 defpal[i].rgbtRed = cfg.colours[w][0];
671 defpal[i].rgbtGreen = cfg.colours[w][1];
672 defpal[i].rgbtBlue = cfg.colours[w][2];
677 * Set up the colour palette.
679 static void init_palette(void) {
681 HDC hdc = GetDC (hwnd);
683 if (cfg.try_palette &&
684 GetDeviceCaps (hdc, RASTERCAPS) & RC_PALETTE) {
685 logpal = smalloc(sizeof(*logpal)
686 - sizeof(logpal->palPalEntry)
687 + NCOLOURS * sizeof(PALETTEENTRY));
688 logpal->palVersion = 0x300;
689 logpal->palNumEntries = NCOLOURS;
690 for (i = 0; i < NCOLOURS; i++) {
691 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
692 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
693 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
694 logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
696 pal = CreatePalette (logpal);
698 SelectPalette (hdc, pal, FALSE);
699 RealizePalette (hdc);
700 SelectPalette (hdc, GetStockObject (DEFAULT_PALETTE),
704 ReleaseDC (hwnd, hdc);
707 for (i=0; i<NCOLOURS; i++)
708 colours[i] = PALETTERGB(defpal[i].rgbtRed,
712 for(i=0; i<NCOLOURS; i++)
713 colours[i] = RGB(defpal[i].rgbtRed,
719 * Initialise all the fonts we will need. There may be as many as
720 * eight or as few as one. We also:
722 * - check the font width and height, correcting our guesses if
725 * - verify that the bold font is the same width as the ordinary
726 * one, and engage shadow bolding if not.
728 * - verify that the underlined font is the same width as the
729 * ordinary one (manual underlining by means of line drawing can
730 * be done in a pinch).
732 static void init_fonts(int pick_width) {
737 int fw_dontcare, fw_bold;
746 if (cfg.fontisbold) {
747 fw_dontcare = FW_BOLD;
750 fw_dontcare = FW_DONTCARE;
756 font_height = cfg.fontheight;
757 font_width = pick_width;
760 fonts[i] = CreateFont (font_height, font_width, 0, 0, w, FALSE, u, FALSE, \
761 c, OUT_DEFAULT_PRECIS, \
762 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, \
763 FIXED_PITCH | FF_DONTCARE, cfg.font)
765 if (cfg.vtmode != VT_OEMONLY) {
766 f(FONT_NORMAL, cfg.fontcharset, fw_dontcare, FALSE);
768 SelectObject (hdc, fonts[FONT_NORMAL]);
769 GetTextMetrics(hdc, &tm);
770 font_height = tm.tmHeight;
771 font_width = tm.tmAveCharWidth;
773 f(FONT_UNDERLINE, cfg.fontcharset, fw_dontcare, TRUE);
776 * Some fonts, e.g. 9-pt Courier, draw their underlines
777 * outside their character cell. We successfully prevent
778 * screen corruption by clipping the text output, but then
779 * we lose the underline completely. Here we try to work
780 * out whether this is such a font, and if it is, we set a
781 * flag that causes underlines to be drawn by hand.
783 * Having tried other more sophisticated approaches (such
784 * as examining the TEXTMETRIC structure or requesting the
785 * height of a string), I think we'll do this the brute
786 * force way: we create a small bitmap, draw an underlined
787 * space on it, and test to see whether any pixels are
788 * foreground-coloured. (Since we expect the underline to
789 * go all the way across the character cell, we only search
790 * down a single column of the bitmap, half way across.)
794 HBITMAP und_bm, und_oldbm;
798 und_dc = CreateCompatibleDC(hdc);
799 und_bm = CreateCompatibleBitmap(hdc, font_width, font_height);
800 und_oldbm = SelectObject(und_dc, und_bm);
801 SelectObject(und_dc, fonts[FONT_UNDERLINE]);
802 SetTextAlign(und_dc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
803 SetTextColor (und_dc, RGB(255,255,255));
804 SetBkColor (und_dc, RGB(0,0,0));
805 SetBkMode (und_dc, OPAQUE);
806 ExtTextOut (und_dc, 0, 0, ETO_OPAQUE, NULL, " ", 1, NULL);
808 for (i = 0; i < font_height; i++) {
809 c = GetPixel(und_dc, font_width/2, i);
813 SelectObject(und_dc, und_oldbm);
814 DeleteObject(und_bm);
816 font_needs_hand_underlining = !gotit;
819 if (bold_mode == BOLD_FONT) {
820 f(FONT_BOLD, cfg.fontcharset, fw_bold, FALSE);
821 f(FONT_BOLDUND, cfg.fontcharset, fw_bold, TRUE);
824 if (cfg.vtmode == VT_OEMANSI) {
825 f(FONT_OEM, OEM_CHARSET, fw_dontcare, FALSE);
826 f(FONT_OEMUND, OEM_CHARSET, fw_dontcare, TRUE);
828 if (bold_mode == BOLD_FONT) {
829 f(FONT_OEMBOLD, OEM_CHARSET, fw_bold, FALSE);
830 f(FONT_OEMBOLDUND, OEM_CHARSET, fw_bold, TRUE);
836 f(FONT_OEM, cfg.fontcharset, fw_dontcare, FALSE);
838 SelectObject (hdc, fonts[FONT_OEM]);
839 GetTextMetrics(hdc, &tm);
840 font_height = tm.tmHeight;
841 font_width = tm.tmAveCharWidth;
843 f(FONT_OEMUND, cfg.fontcharset, fw_dontcare, TRUE);
845 if (bold_mode == BOLD_FONT) {
846 f(FONT_BOLD, cfg.fontcharset, fw_bold, FALSE);
847 f(FONT_BOLDUND, cfg.fontcharset, fw_bold, TRUE);
852 descent = tm.tmAscent + 1;
853 if (descent >= font_height)
854 descent = font_height - 1;
855 firstchar = tm.tmFirstChar;
857 for (i=0; i<8; i++) {
859 if (SelectObject (hdc, fonts[i]) &&
860 GetTextMetrics(hdc, &tm) )
861 fsize[i] = tm.tmAveCharWidth + 256 * tm.tmHeight;
867 ReleaseDC (hwnd, hdc);
869 /* ... This is wrong in OEM only mode */
870 if (fsize[FONT_UNDERLINE] != fsize[FONT_NORMAL] ||
871 (bold_mode == BOLD_FONT &&
872 fsize[FONT_BOLDUND] != fsize[FONT_BOLD])) {
874 DeleteObject (fonts[FONT_UNDERLINE]);
875 if (bold_mode == BOLD_FONT)
876 DeleteObject (fonts[FONT_BOLDUND]);
879 if (bold_mode == BOLD_FONT &&
880 fsize[FONT_BOLD] != fsize[FONT_NORMAL]) {
881 bold_mode = BOLD_SHADOW;
882 DeleteObject (fonts[FONT_BOLD]);
883 if (und_mode == UND_FONT)
884 DeleteObject (fonts[FONT_BOLDUND]);
888 /* With the fascist font painting it doesn't matter if the linedraw font
889 * isn't exactly the right size anymore so we don't have to check this.
891 if (cfg.vtmode == VT_OEMANSI && fsize[FONT_OEM] != fsize[FONT_NORMAL] ) {
892 if( cfg.fontcharset == OEM_CHARSET )
894 MessageBox(NULL, "The OEM and ANSI versions of this font are\n"
895 "different sizes. Using OEM-only mode instead",
896 "Font Size Mismatch", MB_ICONINFORMATION | MB_OK);
897 cfg.vtmode = VT_OEMONLY;
899 else if( firstchar < ' ' )
901 MessageBox(NULL, "The OEM and ANSI versions of this font are\n"
902 "different sizes. Using XTerm mode instead",
903 "Font Size Mismatch", MB_ICONINFORMATION | MB_OK);
904 cfg.vtmode = VT_XWINDOWS;
908 MessageBox(NULL, "The OEM and ANSI versions of this font are\n"
909 "different sizes. Using ISO8859-1 mode instead",
910 "Font Size Mismatch", MB_ICONINFORMATION | MB_OK);
911 cfg.vtmode = VT_POORMAN;
916 DeleteObject (fonts[i]);
922 void request_resize (int w, int h, int refont) {
925 /* If the window is maximized supress resizing attempts */
926 if(IsZoomed(hwnd)) return;
929 /* Don't do this in OEMANSI, you may get disable messages */
930 if (refont && w != cols && (cols==80 || cols==132)
931 && cfg.vtmode != VT_OEMANSI)
933 if (refont && w != cols && (cols==80 || cols==132))
936 /* If font width too big for screen should we shrink the font more ? */
938 font_width = ((font_width*cols+w/2)/w);
945 DeleteObject(fonts[i]);
947 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
949 init_fonts(font_width);
953 static int first_time = 1;
959 /* Get the size of the screen */
960 if (GetClientRect(GetDesktopWindow(),&ss))
961 /* first_time = 0 */;
962 else { first_time = 2; break; }
964 /* Make sure the values are sane */
965 width = (ss.right-ss.left-extra_width ) / font_width;
966 height = (ss.bottom-ss.top-extra_height ) / font_height;
968 if (w>width) w=width;
969 if (h>height) h=height;
975 width = extra_width + font_width * w;
976 height = extra_height + font_height * h;
978 SetWindowPos (hwnd, NULL, 0, 0, width, height,
979 SWP_NOACTIVATE | SWP_NOCOPYBITS |
980 SWP_NOMOVE | SWP_NOZORDER);
983 static void click (Mouse_Button b, int x, int y) {
984 int thistime = GetMessageTime();
986 if (lastbtn == b && thistime - lasttime < dbltime) {
987 lastact = (lastact == MA_CLICK ? MA_2CLK :
988 lastact == MA_2CLK ? MA_3CLK :
989 lastact == MA_3CLK ? MA_CLICK : MA_NOTHING);
994 if (lastact != MA_NOTHING)
995 term_mouse (b, lastact, x, y);
999 static LRESULT CALLBACK WndProc (HWND hwnd, UINT message,
1000 WPARAM wParam, LPARAM lParam) {
1002 static int ignore_size = FALSE;
1003 static int ignore_clip = FALSE;
1004 static int just_reconfigged = FALSE;
1005 static int resizing = FALSE;
1009 if (pending_netevent)
1010 enact_pending_netevent();
1020 if (!cfg.warn_on_close || session_closed ||
1021 MessageBox(hwnd, "Are you sure you want to close this session?",
1022 "PuTTY Exit Confirmation",
1023 MB_ICONWARNING | MB_OKCANCEL) == IDOK)
1024 DestroyWindow(hwnd);
1027 PostQuitMessage (0);
1030 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1042 PROCESS_INFORMATION pi;
1043 HANDLE filemap = NULL;
1045 if (wParam == IDM_DUPSESS) {
1047 * Allocate a file-mapping memory chunk for the
1050 SECURITY_ATTRIBUTES sa;
1053 sa.nLength = sizeof(sa);
1054 sa.lpSecurityDescriptor = NULL;
1055 sa.bInheritHandle = TRUE;
1056 filemap = CreateFileMapping((HANDLE)0xFFFFFFFF,
1063 p = (Config *)MapViewOfFile(filemap,
1065 0, 0, sizeof(Config));
1067 *p = cfg; /* structure copy */
1071 sprintf(c, "putty &%p", filemap);
1073 } else if (wParam == IDM_SAVEDSESS) {
1074 char *session = sessions[(lParam - IDM_SAVED_MIN) / 16];
1075 cl = malloc(16 + strlen(session)); /* 8, but play safe */
1077 cl = NULL; /* not a very important failure mode */
1079 sprintf(cl, "putty @%s", session);
1085 GetModuleFileName (NULL, b, sizeof(b)-1);
1087 si.lpReserved = NULL;
1088 si.lpDesktop = NULL;
1092 si.lpReserved2 = NULL;
1093 CreateProcess (b, cl, NULL, NULL, TRUE,
1094 NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
1097 CloseHandle(filemap);
1103 if (!do_reconfig(hwnd))
1105 just_reconfigged = TRUE;
1110 DeleteObject(fonts[i]);
1112 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
1113 und_mode = UND_FONT;
1116 /* Telnet will change local echo -> remote if the remote asks */
1117 if (cfg.protocol != PROT_TELNET)
1118 ldisc = (cfg.ldisc_term ? &ldisc_term : &ldisc_simple);
1126 /* Enable or disable the scroll bar, etc */
1128 LONG nflg, flag = GetWindowLong(hwnd, GWL_STYLE);
1131 if (cfg.scrollbar) nflg |= WS_VSCROLL;
1132 else nflg &= ~WS_VSCROLL;
1134 nflg &= ~(WS_THICKFRAME|WS_MAXIMIZEBOX);
1136 nflg |= (WS_THICKFRAME|WS_MAXIMIZEBOX);
1142 SetWindowLong(hwnd, GWL_STYLE, nflg);
1143 SendMessage (hwnd, WM_IGNORE_SIZE, 0, 0);
1144 SetWindowPos(hwnd, NULL, 0,0,0,0,
1145 SWP_NOACTIVATE|SWP_NOCOPYBITS|
1146 SWP_NOMOVE|SWP_NOSIZE| SWP_NOZORDER|
1149 GetWindowRect (hwnd, &wr);
1150 GetClientRect (hwnd, &cr);
1151 extra_width = wr.right - wr.left - cr.right + cr.left;
1152 extra_height = wr.bottom - wr.top - cr.bottom + cr.top;
1156 term_size(cfg.height, cfg.width, cfg.savelines);
1157 InvalidateRect(hwnd, NULL, TRUE);
1158 SetWindowPos (hwnd, NULL, 0, 0,
1159 extra_width + font_width * cfg.width,
1160 extra_height + font_height * cfg.height,
1161 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1162 SWP_NOMOVE | SWP_NOZORDER);
1163 if (IsIconic(hwnd)) {
1164 SetWindowText (hwnd,
1165 cfg.win_name_always ? window_name : icon_name);
1174 case IDM_TEL_AYT: back->special (TS_AYT); break;
1175 case IDM_TEL_BRK: back->special (TS_BRK); break;
1176 case IDM_TEL_SYNCH: back->special (TS_SYNCH); break;
1177 case IDM_TEL_EC: back->special (TS_EC); break;
1178 case IDM_TEL_EL: back->special (TS_EL); break;
1179 case IDM_TEL_GA: back->special (TS_GA); break;
1180 case IDM_TEL_NOP: back->special (TS_NOP); break;
1181 case IDM_TEL_ABORT: back->special (TS_ABORT); break;
1182 case IDM_TEL_AO: back->special (TS_AO); break;
1183 case IDM_TEL_IP: back->special (TS_IP); break;
1184 case IDM_TEL_SUSP: back->special (TS_SUSP); break;
1185 case IDM_TEL_EOR: back->special (TS_EOR); break;
1186 case IDM_TEL_EOF: back->special (TS_EOF); break;
1191 if (wParam >= IDM_SAVED_MIN && wParam <= IDM_SAVED_MAX) {
1192 SendMessage(hwnd, WM_SYSCOMMAND, IDM_SAVEDSESS, wParam);
1197 #define X_POS(l) ((int)(short)LOWORD(l))
1198 #define Y_POS(l) ((int)(short)HIWORD(l))
1200 #define TO_CHR_X(x) (((x)<0 ? (x)-font_width+1 : (x)) / font_width)
1201 #define TO_CHR_Y(y) (((y)<0 ? (y)-font_height+1: (y)) / font_height)
1203 case WM_LBUTTONDOWN:
1204 click (MB_SELECT, TO_CHR_X(X_POS(lParam)),
1205 TO_CHR_Y(Y_POS(lParam)));
1209 term_mouse (MB_SELECT, MA_RELEASE, TO_CHR_X(X_POS(lParam)),
1210 TO_CHR_Y(Y_POS(lParam)));
1213 case WM_MBUTTONDOWN:
1215 click (cfg.mouse_is_xterm ? MB_PASTE : MB_EXTEND,
1216 TO_CHR_X(X_POS(lParam)),
1217 TO_CHR_Y(Y_POS(lParam)));
1220 term_mouse (cfg.mouse_is_xterm ? MB_PASTE : MB_EXTEND,
1221 MA_RELEASE, TO_CHR_X(X_POS(lParam)),
1222 TO_CHR_Y(Y_POS(lParam)));
1225 case WM_RBUTTONDOWN:
1227 click (cfg.mouse_is_xterm ? MB_EXTEND : MB_PASTE,
1228 TO_CHR_X(X_POS(lParam)),
1229 TO_CHR_Y(Y_POS(lParam)));
1232 term_mouse (cfg.mouse_is_xterm ? MB_EXTEND : MB_PASTE,
1233 MA_RELEASE, TO_CHR_X(X_POS(lParam)),
1234 TO_CHR_Y(Y_POS(lParam)));
1239 * Add the mouse position and message time to the random
1240 * number noise, if we're using ssh.
1242 if (cfg.protocol == PROT_SSH)
1243 noise_ultralight(lParam);
1245 if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)) {
1247 if (wParam & MK_LBUTTON)
1249 else if (wParam & MK_MBUTTON)
1250 b = cfg.mouse_is_xterm ? MB_PASTE : MB_EXTEND;
1252 b = cfg.mouse_is_xterm ? MB_EXTEND : MB_PASTE;
1253 term_mouse (b, MA_DRAG, TO_CHR_X(X_POS(lParam)),
1254 TO_CHR_Y(Y_POS(lParam)));
1257 case WM_IGNORE_CLIP:
1258 ignore_clip = wParam; /* don't panic on DESTROYCLIPBOARD */
1260 case WM_DESTROYCLIPBOARD:
1263 ignore_clip = FALSE;
1269 hdc = BeginPaint (hwnd, &p);
1271 SelectPalette (hdc, pal, TRUE);
1272 RealizePalette (hdc);
1274 term_paint (hdc, p.rcPaint.left, p.rcPaint.top,
1275 p.rcPaint.right, p.rcPaint.bottom);
1276 SelectObject (hdc, GetStockObject(SYSTEM_FONT));
1277 SelectObject (hdc, GetStockObject(WHITE_PEN));
1278 EndPaint (hwnd, &p);
1283 /* Notice we can get multiple netevents, FD_READ, FD_WRITE etc
1284 * but the only one that's likely to try to overload us is FD_READ.
1285 * This means buffering just one is fine.
1287 if (pending_netevent)
1288 enact_pending_netevent();
1290 pending_netevent = TRUE;
1291 pend_netevent_wParam=wParam;
1292 pend_netevent_lParam=lParam;
1296 CreateCaret(hwnd, caretbm, 0, 0);
1307 case WM_IGNORE_SIZE:
1308 ignore_size = TRUE; /* don't panic on next WM_SIZE msg */
1310 case WM_ENTERSIZEMOVE:
1314 case WM_EXITSIZEMOVE:
1321 int width, height, w, h, ew, eh;
1322 LPRECT r = (LPRECT)lParam;
1324 width = r->right - r->left - extra_width;
1325 height = r->bottom - r->top - extra_height;
1326 w = (width + font_width/2) / font_width; if (w < 1) w = 1;
1327 h = (height + font_height/2) / font_height; if (h < 1) h = 1;
1328 UpdateSizeTip(hwnd, w, h);
1329 ew = width - w * font_width;
1330 eh = height - h * font_height;
1332 if (wParam == WMSZ_LEFT ||
1333 wParam == WMSZ_BOTTOMLEFT ||
1334 wParam == WMSZ_TOPLEFT)
1340 if (wParam == WMSZ_TOP ||
1341 wParam == WMSZ_TOPRIGHT ||
1342 wParam == WMSZ_TOPLEFT)
1352 /* break; (never reached) */
1354 if (wParam == SIZE_MINIMIZED) {
1355 SetWindowText (hwnd,
1356 cfg.win_name_always ? window_name : icon_name);
1359 if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
1360 SetWindowText (hwnd, window_name);
1362 int width, height, w, h;
1363 #if 0 /* we have fixed this using WM_SIZING now */
1367 width = LOWORD(lParam);
1368 height = HIWORD(lParam);
1369 w = width / font_width; if (w < 1) w = 1;
1370 h = height / font_height; if (h < 1) h = 1;
1371 #if 0 /* we have fixed this using WM_SIZING now */
1372 ew = width - w * font_width;
1373 eh = height - h * font_height;
1374 if (ew != 0 || eh != 0) {
1376 GetWindowRect (hwnd, &r);
1377 SendMessage (hwnd, WM_IGNORE_SIZE, 0, 0);
1378 SetWindowPos (hwnd, NULL, 0, 0,
1379 r.right - r.left - ew, r.bottom - r.top - eh,
1380 SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER);
1383 if (w != cols || h != rows || just_reconfigged) {
1385 term_size (h, w, cfg.savelines);
1387 * Don't call back->size in mid-resize. (To prevent
1388 * massive numbers of resize events getting sent
1389 * down the connection during an NT opaque drag.)
1393 just_reconfigged = FALSE;
1396 ignore_size = FALSE;
1399 switch (LOWORD(wParam)) {
1400 case SB_BOTTOM: term_scroll(-1, 0); break;
1401 case SB_TOP: term_scroll(+1, 0); break;
1402 case SB_LINEDOWN: term_scroll (0, +1); break;
1403 case SB_LINEUP: term_scroll (0, -1); break;
1404 case SB_PAGEDOWN: term_scroll (0, +rows/2); break;
1405 case SB_PAGEUP: term_scroll (0, -rows/2); break;
1406 case SB_THUMBPOSITION: case SB_THUMBTRACK:
1407 term_scroll (1, HIWORD(wParam)); break;
1410 case WM_PALETTECHANGED:
1411 if ((HWND) wParam != hwnd && pal != NULL) {
1412 HDC hdc = get_ctx();
1414 if (RealizePalette (hdc) > 0)
1420 case WM_QUERYNEWPALETTE:
1422 HDC hdc = get_ctx();
1424 if (RealizePalette (hdc) > 0)
1436 * Add the scan code and keypress timing to the random
1437 * number noise, if we're using ssh.
1439 if (cfg.protocol == PROT_SSH)
1440 noise_ultralight(lParam);
1443 * We don't do TranslateMessage since it disassociates the
1444 * resulting CHAR message from the KEYDOWN that sparked it,
1445 * which we occasionally don't want. Instead, we process
1446 * KEYDOWN, and call the Win32 translator functions so that
1447 * we get the translations under _our_ control.
1450 unsigned char buf[20];
1453 len = TranslateKey (message, wParam, lParam, buf);
1455 return DefWindowProc (hwnd, message, wParam, lParam);
1456 ldisc->send (buf, len);
1462 * Nevertheless, we are prepared to deal with WM_CHAR
1463 * messages, should they crop up. So if someone wants to
1464 * post the things to us as part of a macro manoeuvre,
1465 * we're ready to cope.
1468 char c = xlat_kbd2tty((unsigned char)wParam);
1469 ldisc->send (&c, 1);
1474 return DefWindowProc (hwnd, message, wParam, lParam);
1478 * Move the system caret. (We maintain one, even though it's
1479 * invisible, for the benefit of blind people: apparently some
1480 * helper software tracks the system caret, so we should arrange to
1483 void sys_cursor(int x, int y) {
1484 SetCaretPos(x * font_width, y * font_height);
1488 * Draw a line of text in the window, at given character
1489 * coordinates, in given attributes.
1491 * We are allowed to fiddle with the contents of `text'.
1493 void do_text (Context ctx, int x, int y, char *text, int len,
1494 unsigned long attr, int lattr) {
1496 int nfg, nbg, nfont;
1499 int force_manual_underline = 0;
1500 int fnt_width = font_width*(1+(lattr!=LATTR_NORM));
1501 static int *IpDx = 0, IpDxLEN = 0;;
1503 if (len>IpDxLEN || IpDx[0] != fnt_width) {
1507 IpDx = smalloc((len+16)*sizeof(int));
1510 for(i=0; i<IpDxLEN; i++)
1511 IpDx[i] = fnt_width;
1517 if (attr & ATTR_ACTCURS) {
1518 attr &= (bold_mode == BOLD_COLOURS ? 0x300200 : 0x300300);
1519 attr ^= ATTR_CUR_XOR;
1523 if (cfg.vtmode == VT_OEMONLY)
1527 * Map high-half characters in order to approximate ISO using
1528 * OEM character set. No characters are missing if the OEM codepage
1531 if (nfont & FONT_OEM) {
1533 for (i=0; i<len; i++)
1534 if (text[i] >= '\xA0' && text[i] <= '\xFF') {
1536 /* This is CP850 ... perfect translation */
1537 static const char oemhighhalf[] =
1538 "\x20\xAD\xBD\x9C\xCF\xBE\xDD\xF5" /* A0-A7 */
1539 "\xF9\xB8\xA6\xAE\xAA\xF0\xA9\xEE" /* A8-AF */
1540 "\xF8\xF1\xFD\xFC\xEF\xE6\xF4\xFA" /* B0-B7 */
1541 "\xF7\xFB\xA7\xAF\xAC\xAB\xF3\xA8" /* B8-BF */
1542 "\xB7\xB5\xB6\xC7\x8E\x8F\x92\x80" /* C0-C7 */
1543 "\xD4\x90\xD2\xD3\xDE\xD6\xD7\xD8" /* C8-CF */
1544 "\xD1\xA5\xE3\xE0\xE2\xE5\x99\x9E" /* D0-D7 */
1545 "\x9D\xEB\xE9\xEA\x9A\xED\xE8\xE1" /* D8-DF */
1546 "\x85\xA0\x83\xC6\x84\x86\x91\x87" /* E0-E7 */
1547 "\x8A\x82\x88\x89\x8D\xA1\x8C\x8B" /* E8-EF */
1548 "\xD0\xA4\x95\xA2\x93\xE4\x94\xF6" /* F0-F7 */
1549 "\x9B\x97\xA3\x96\x81\xEC\xE7\x98" /* F8-FF */
1552 /* This is CP437 ... junk translation */
1553 static const unsigned char oemhighhalf[] = {
1554 0xff, 0xad, 0x9b, 0x9c, 0x6f, 0x9d, 0x7c, 0x15,
1555 0x22, 0x43, 0xa6, 0xae, 0xaa, 0x2d, 0x52, 0xc4,
1556 0xf8, 0xf1, 0xfd, 0x33, 0x27, 0xe6, 0x14, 0xfa,
1557 0x2c, 0x31, 0xa7, 0xaf, 0xac, 0xab, 0x2f, 0xa8,
1558 0x41, 0x41, 0x41, 0x41, 0x8e, 0x8f, 0x92, 0x80,
1559 0x45, 0x90, 0x45, 0x45, 0x49, 0x49, 0x49, 0x49,
1560 0x44, 0xa5, 0x4f, 0x4f, 0x4f, 0x4f, 0x99, 0x78,
1561 0xed, 0x55, 0x55, 0x55, 0x9a, 0x59, 0x50, 0xe1,
1562 0x85, 0xa0, 0x83, 0x61, 0x84, 0x86, 0x91, 0x87,
1563 0x8a, 0x82, 0x88, 0x89, 0x8d, 0xa1, 0x8c, 0x8b,
1564 0x0b, 0xa4, 0x95, 0xa2, 0x93, 0x6f, 0x94, 0xf6,
1565 0xed, 0x97, 0xa3, 0x96, 0x81, 0x79, 0x70, 0x98
1568 text[i] = oemhighhalf[(unsigned char)text[i] - 0xA0];
1572 if (attr & ATTR_GBCHR) {
1575 * GB mapping: map # to pound, and everything else stays
1578 for (i=0; i<len; i++)
1580 text[i] = cfg.vtmode == VT_OEMONLY ? '\x9C' : '\xA3';
1581 } else if (attr & ATTR_LINEDRW) {
1584 static const char poorman[] =
1585 "*#****\xB0\xB1**+++++-----++++|****\xA3\xB7";
1588 static const char oemmap_437[] =
1589 "\x04\xB1****\xF8\xF1**\xD9\xBF\xDA\xC0\xC5"
1590 "\xC4\xC4\xC4\xC4\xC4\xC3\xB4\xC1\xC2\xB3\xF3\xF2\xE3*\x9C\xFA";
1593 static const char oemmap_850[] =
1594 "\x04\xB1****\xF8\xF1**\xD9\xBF\xDA\xC0\xC5"
1595 "\xC4\xC4\xC4\xC4\xC4\xC3\xB4\xC1\xC2\xB3****\x9C\xFA";
1597 /* Poor windows font ... eg: windows courier */
1598 static const char oemmap[] =
1599 "*\xB1****\xF8\xF1**\xD9\xBF\xDA\xC0\xC5"
1600 "\xC4\xC4\xC4\xC4\xC4\xC3\xB4\xC1\xC2\xB3****\x9C\xFA";
1603 * Line drawing mapping: map ` thru ~ (0x60 thru 0x7E) to
1604 * VT100 line drawing chars; everything else stays normal.
1606 switch (cfg.vtmode) {
1608 for (i=0; i<len; i++)
1609 if (text[i] >= '\x60' && text[i] <= '\x7E')
1610 text[i] += '\x01' - '\x60';
1613 /* Make sure we actually have an OEM font */
1614 if (fonts[nfont|FONT_OEM]) {
1617 for (i=0; i<len; i++)
1618 if (text[i] >= '\x60' && text[i] <= '\x7E')
1619 text[i] = oemmap[(unsigned char)text[i] - 0x60];
1623 for (i=0; i<len; i++)
1624 if (text[i] >= '\x60' && text[i] <= '\x7E')
1625 text[i] = poorman[(unsigned char)text[i] - 0x60];
1630 nfg = 2 * ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
1631 nbg = 2 * ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
1632 if (bold_mode == BOLD_FONT && (attr & ATTR_BOLD))
1634 if (und_mode == UND_FONT && (attr & ATTR_UNDER))
1635 nfont |= FONT_UNDERLINE;
1638 if (nfont&FONT_UNDERLINE)
1639 force_manual_underline = 1;
1640 /* Don't do the same for manual bold, it could be bad news. */
1642 nfont &= ~(FONT_BOLD|FONT_UNDERLINE);
1644 if (font_needs_hand_underlining && (attr & ATTR_UNDER))
1645 force_manual_underline = 1;
1646 if (attr & ATTR_REVERSE) {
1647 t = nfg; nfg = nbg; nbg = t;
1649 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BOLD))
1651 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BLINK))
1655 SelectObject (hdc, fonts[nfont]);
1656 SetTextColor (hdc, fg);
1657 SetBkColor (hdc, bg);
1658 SetBkMode (hdc, OPAQUE);
1661 line_box.right = x+fnt_width*len;
1662 line_box.bottom = y+font_height;
1663 ExtTextOut (hdc, x, y, ETO_CLIPPED|ETO_OPAQUE, &line_box, text, len, IpDx);
1664 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
1665 SetBkMode (hdc, TRANSPARENT);
1667 /* GRR: This draws the character outside it's box and can leave
1668 * 'droppings' even with the clip box! I suppose I could loop it
1669 * one character at a time ... yuk.
1671 * Or ... I could do a test print with "W", and use +1 or -1 for this
1672 * shift depending on if the leftmost column is blank...
1674 ExtTextOut (hdc, x-1, y, ETO_CLIPPED, &line_box, text, len, IpDx);
1676 if (force_manual_underline ||
1677 (und_mode == UND_LINE && (attr & ATTR_UNDER))) {
1679 oldpen = SelectObject (hdc, CreatePen(PS_SOLID, 0, fg));
1680 MoveToEx (hdc, x, y+descent, NULL);
1681 LineTo (hdc, x+len*fnt_width, y+descent);
1682 oldpen = SelectObject (hdc, oldpen);
1683 DeleteObject (oldpen);
1685 if (attr & ATTR_PASCURS) {
1688 pts[0].x = pts[1].x = pts[4].x = x;
1689 pts[2].x = pts[3].x = x+fnt_width-1;
1690 pts[0].y = pts[3].y = pts[4].y = y;
1691 pts[1].y = pts[2].y = y+font_height-1;
1692 oldpen = SelectObject (hdc, CreatePen(PS_SOLID, 0, colours[23]));
1693 Polyline (hdc, pts, 5);
1694 oldpen = SelectObject (hdc, oldpen);
1695 DeleteObject (oldpen);
1699 static int check_compose(int first, int second) {
1701 static char * composetbl[] = {
1702 "++#", "AA@", "(([", "//\\", "))]", "(-{", "-)}", "/^|", "!!¡", "C/¢",
1703 "C|¢", "L-£", "L=£", "XO¤", "X0¤", "Y-¥", "Y=¥", "||¦", "SO§", "S!§",
1704 "S0§", "\"\"¨", "CO©", "C0©", "A_ª", "<<«", ",-¬", "--", "RO®",
1705 "-^¯", "0^°", "+-±", "2^²", "3^³", "''´", "/Uµ", "P!¶", ".^·", ",,¸",
1706 "1^¹", "O_º", ">>»", "14¼", "12½", "34¾", "??¿", "`AÀ", "'AÁ", "^AÂ",
1707 "~AÃ", "\"AÄ", "*AÅ", "AEÆ", ",CÇ", "`EÈ", "'EÉ", "^EÊ", "\"EË",
1708 "`IÌ", "'IÍ", "^IÎ", "\"IÏ", "-DÐ", "~NÑ", "`OÒ", "'OÓ", "^OÔ",
1709 "~OÕ", "\"OÖ", "XX×", "/OØ", "`UÙ", "'UÚ", "^UÛ", "\"UÜ", "'YÝ",
1710 "HTÞ", "ssß", "`aà", "'aá", "^aâ", "~aã", "\"aä", "*aå", "aeæ", ",cç",
1711 "`eè", "'eé", "^eê", "\"eë", "`iì", "'ií", "^iî", "\"iï", "-dð", "~nñ",
1712 "`oò", "'oó", "^oô", "~oõ", "\"oö", ":-÷", "o/ø", "`uù", "'uú", "^uû",
1713 "\"uü", "'yý", "htþ", "\"yÿ",
1717 static int recurse = 0;
1724 sprintf(buf, "cc(%d,%d)", first, second);
1729 for(c=composetbl; *c; c++) {
1730 if( (*c)[0] == first && (*c)[1] == second)
1732 return (*c)[2] & 0xFF;
1739 nc = check_compose(second, first);
1741 nc = check_compose(toupper(first), toupper(second));
1743 nc = check_compose(toupper(second), toupper(first));
1751 * Translate a WM_(SYS)?KEY(UP|DOWN) message into a string of ASCII
1752 * codes. Returns number of bytes used or zero to drop the message
1753 * or -1 to forward the message to windows.
1755 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, unsigned char *output) {
1757 int scan, left_alt = 0, key_down, shift_state;
1759 unsigned char * p = output;
1761 static WORD keys[3];
1762 static int compose_state = 0;
1763 static int compose_char = 0;
1764 static WPARAM compose_key = 0;
1766 r = GetKeyboardState(keystate);
1767 if (!r) memset(keystate, 0, sizeof(keystate));
1770 /* Note if AltGr was pressed and if it was used as a compose key */
1771 if (wParam == VK_MENU && (HIWORD(lParam)&KF_EXTENDED))
1773 keystate[VK_RMENU] = keystate[VK_MENU];
1774 if (!compose_state) compose_key = wParam;
1776 if (wParam == VK_APPS && !compose_state)
1777 compose_key = wParam;
1779 if (wParam == compose_key)
1781 if (compose_state == 0 && (HIWORD(lParam)&(KF_UP|KF_REPEAT))==0)
1783 else if (compose_state == 1 && (HIWORD(lParam)&KF_UP))
1788 else if (compose_state==1 && wParam != VK_CONTROL)
1791 /* Nastyness with NUMLock - Shift-NUMLock is left alone though */
1792 if ( (cfg.funky_type == 0 || (cfg.funky_type == 1 && app_keypad_keys))
1793 && wParam==VK_NUMLOCK && !(keystate[VK_SHIFT]&0x80)) {
1795 wParam = VK_EXECUTE;
1797 /* UnToggle NUMLock */
1798 if ((HIWORD(lParam)&(KF_UP|KF_REPEAT))==0)
1799 keystate[VK_NUMLOCK] ^= 1;
1802 /* And write back the 'adjusted' state */
1803 SetKeyboardState (keystate);
1806 /* Disable Auto repeat if required */
1807 if (repeat_off && (HIWORD(lParam)&(KF_UP|KF_REPEAT))==KF_REPEAT)
1810 if ((HIWORD(lParam)&KF_ALTDOWN) && (keystate[VK_RMENU]&0x80) == 0)
1813 key_down = ((HIWORD(lParam)&KF_UP)==0);
1815 /* Make sure Ctrl-ALT is not the same as AltGr for ToAscii */
1816 if (left_alt && (keystate[VK_CONTROL]&0x80))
1817 keystate[VK_MENU] = 0;
1819 scan = (HIWORD(lParam) & (KF_UP | KF_EXTENDED | 0xFF));
1820 shift_state = ((keystate[VK_SHIFT]&0x80)!=0)
1821 + ((keystate[VK_CONTROL]&0x80)!=0)*2;
1824 * Record that we pressed key so the scroll window can be reset, but
1825 * be careful to avoid Shift-UP/Down
1827 if( wParam != VK_SHIFT && wParam != VK_PRIOR && wParam != VK_NEXT ) {
1831 /* Make sure we're not pasting */
1832 if (key_down) term_nopaste();
1834 if (compose_state>1 && left_alt) compose_state = 0;
1836 /* Sanitize the number pad if not using a PC NumPad */
1837 if( left_alt || (app_keypad_keys && cfg.funky_type != 2)
1838 || cfg.nethack_keypad || compose_state )
1840 if ((HIWORD(lParam)&KF_EXTENDED) == 0)
1845 case VK_INSERT: nParam = VK_NUMPAD0; break;
1846 case VK_END: nParam = VK_NUMPAD1; break;
1847 case VK_DOWN: nParam = VK_NUMPAD2; break;
1848 case VK_NEXT: nParam = VK_NUMPAD3; break;
1849 case VK_LEFT: nParam = VK_NUMPAD4; break;
1850 case VK_CLEAR: nParam = VK_NUMPAD5; break;
1851 case VK_RIGHT: nParam = VK_NUMPAD6; break;
1852 case VK_HOME: nParam = VK_NUMPAD7; break;
1853 case VK_UP: nParam = VK_NUMPAD8; break;
1854 case VK_PRIOR: nParam = VK_NUMPAD9; break;
1855 case VK_DELETE: nParam = VK_DECIMAL; break;
1859 if (keystate[VK_NUMLOCK]&1) shift_state |= 1;
1865 /* If a key is pressed and AltGr is not active */
1866 if (key_down && (keystate[VK_RMENU]&0x80) == 0 && !compose_state)
1868 /* Okay, prepare for most alts then ...*/
1869 if (left_alt) *p++ = '\033';
1871 /* Lets see if it's a pattern we know all about ... */
1872 if (wParam == VK_PRIOR && shift_state == 1) {
1873 SendMessage (hwnd, WM_VSCROLL, SB_PAGEUP, 0);
1876 if (wParam == VK_NEXT && shift_state == 1) {
1877 SendMessage (hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
1880 if (wParam == VK_INSERT && shift_state == 1) {
1881 term_mouse (MB_PASTE, MA_CLICK, 0, 0);
1882 term_mouse (MB_PASTE, MA_RELEASE, 0, 0);
1885 if (left_alt && wParam == VK_F4 && cfg.alt_f4) {
1888 if (left_alt && wParam == VK_SPACE && cfg.alt_space) {
1890 SendMessage (hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0);
1894 /* Nethack keypad */
1895 if (cfg.nethack_keypad && !left_alt) {
1897 case VK_NUMPAD1: *p++ = shift_state ? 'B': 'b'; return p-output;
1898 case VK_NUMPAD2: *p++ = shift_state ? 'J': 'j'; return p-output;
1899 case VK_NUMPAD3: *p++ = shift_state ? 'N': 'n'; return p-output;
1900 case VK_NUMPAD4: *p++ = shift_state ? 'H': 'h'; return p-output;
1901 case VK_NUMPAD5: *p++ = shift_state ? '.': '.'; return p-output;
1902 case VK_NUMPAD6: *p++ = shift_state ? 'L': 'l'; return p-output;
1903 case VK_NUMPAD7: *p++ = shift_state ? 'Y': 'y'; return p-output;
1904 case VK_NUMPAD8: *p++ = shift_state ? 'K': 'k'; return p-output;
1905 case VK_NUMPAD9: *p++ = shift_state ? 'U': 'u'; return p-output;
1909 /* Application Keypad */
1913 if ( cfg.funky_type == 0 ||
1914 ( cfg.funky_type == 1 && app_keypad_keys)) switch(wParam) {
1915 case VK_EXECUTE: if (app_keypad_keys) xkey = 'P'; break;
1916 case VK_DIVIDE: xkey = 'Q'; break;
1917 case VK_MULTIPLY:xkey = 'R'; break;
1918 case VK_SUBTRACT:xkey = 'S'; break;
1920 if(app_keypad_keys) switch(wParam) {
1921 case VK_NUMPAD0: xkey = 'p'; break;
1922 case VK_NUMPAD1: xkey = 'q'; break;
1923 case VK_NUMPAD2: xkey = 'r'; break;
1924 case VK_NUMPAD3: xkey = 's'; break;
1925 case VK_NUMPAD4: xkey = 't'; break;
1926 case VK_NUMPAD5: xkey = 'u'; break;
1927 case VK_NUMPAD6: xkey = 'v'; break;
1928 case VK_NUMPAD7: xkey = 'w'; break;
1929 case VK_NUMPAD8: xkey = 'x'; break;
1930 case VK_NUMPAD9: xkey = 'y'; break;
1932 case VK_DECIMAL: xkey = 'n'; break;
1933 case VK_ADD: if(shift_state) xkey = 'm';
1937 if (HIWORD(lParam)&KF_EXTENDED)
1945 if (xkey>='P' && xkey<='S')
1946 p += sprintf((char *)p, "\x1B%c", xkey);
1948 p += sprintf((char *)p, "\x1B?%c", xkey);
1951 p += sprintf((char *)p, "\x1BO%c", xkey);
1956 if (wParam == VK_BACK && shift_state == 0 ) /* Backspace */
1958 *p++ = (cfg.bksp_is_delete ? 0x7F : 0x08);
1961 if (wParam == VK_TAB && shift_state == 1 ) /* Shift tab */
1963 *p++ = 0x1B; *p++ = '['; *p++ = 'Z'; return p - output;
1965 if (wParam == VK_SPACE && shift_state == 2 ) /* Ctrl-Space */
1967 *p++ = 0; return p - output;
1969 if (wParam == VK_SPACE && shift_state == 3 ) /* Ctrl-Shift-Space */
1971 *p++ = 160; return p - output;
1973 if (wParam == VK_CANCEL && shift_state == 2 ) /* Ctrl-Break */
1975 *p++ = 3; return p - output;
1977 /* Control-2 to Control-8 are special */
1978 if (shift_state == 2 && wParam >= '2' && wParam <= '8')
1980 *p++ = "\000\033\034\035\036\037\177"[wParam-'2'];
1983 if (shift_state == 2 && wParam == 0xBD) {
1987 if (shift_state == 2 && wParam == 0xDF) {
1991 if (shift_state == 0 && wParam == VK_RETURN && cr_lf_return) {
1992 *p++ = '\r'; *p++ = '\n';
1997 * Next, all the keys that do tilde codes. (ESC '[' nn '~',
1998 * for integer decimal nn.)
2000 * We also deal with the weird ones here. Linux VCs replace F1
2001 * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
2002 * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
2007 case VK_F1: code = (keystate[VK_SHIFT] & 0x80 ? 23 : 11); break;
2008 case VK_F2: code = (keystate[VK_SHIFT] & 0x80 ? 24 : 12); break;
2009 case VK_F3: code = (keystate[VK_SHIFT] & 0x80 ? 25 : 13); break;
2010 case VK_F4: code = (keystate[VK_SHIFT] & 0x80 ? 26 : 14); break;
2011 case VK_F5: code = (keystate[VK_SHIFT] & 0x80 ? 28 : 15); break;
2012 case VK_F6: code = (keystate[VK_SHIFT] & 0x80 ? 29 : 17); break;
2013 case VK_F7: code = (keystate[VK_SHIFT] & 0x80 ? 31 : 18); break;
2014 case VK_F8: code = (keystate[VK_SHIFT] & 0x80 ? 32 : 19); break;
2015 case VK_F9: code = (keystate[VK_SHIFT] & 0x80 ? 33 : 20); break;
2016 case VK_F10: code = (keystate[VK_SHIFT] & 0x80 ? 34 : 21); break;
2017 case VK_F11: code = 23; break;
2018 case VK_F12: code = 24; break;
2019 case VK_F13: code = 25; break;
2020 case VK_F14: code = 26; break;
2021 case VK_F15: code = 28; break;
2022 case VK_F16: code = 29; break;
2023 case VK_F17: code = 31; break;
2024 case VK_F18: code = 32; break;
2025 case VK_F19: code = 33; break;
2026 case VK_F20: code = 34; break;
2027 case VK_HOME: code = 1; break;
2028 case VK_INSERT: code = 2; break;
2029 case VK_DELETE: code = 3; break;
2030 case VK_END: code = 4; break;
2031 case VK_PRIOR: code = 5; break;
2032 case VK_NEXT: code = 6; break;
2034 if (cfg.funky_type == 1 && code >= 11 && code <= 15) {
2035 p += sprintf((char *)p, "\x1B[[%c", code + 'A' - 11);
2038 if (cfg.funky_type == 2 && code >= 11 && code <= 14) {
2039 p += sprintf((char *)p, "\x1BO%c", code + 'P' - 11);
2042 if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
2043 p += sprintf((char *)p, code == 1 ? "\x1B[H" : "\x1BOw");
2047 p += sprintf((char *)p, "\x1B[%d~", code);
2052 * Now the remaining keys (arrows and Keypad 5. Keypad 5 for
2053 * some reason seems to send VK_CLEAR to Windows...).
2058 case VK_UP: xkey = 'A'; break;
2059 case VK_DOWN: xkey = 'B'; break;
2060 case VK_RIGHT: xkey = 'C'; break;
2061 case VK_LEFT: xkey = 'D'; break;
2062 case VK_CLEAR: xkey = 'G'; break;
2067 p += sprintf((char *)p, "\x1B%c", xkey);
2068 else if (app_cursor_keys)
2069 p += sprintf((char *)p, "\x1BO%c", xkey);
2071 p += sprintf((char *)p, "\x1B[%c", xkey);
2077 * Finally, deal with Return ourselves. (Win95 seems to
2078 * foul it up when Alt is pressed, for some reason.)
2080 if (wParam == VK_RETURN) /* Return */
2087 /* Okay we've done everything interesting; let windows deal with
2088 * the boring stuff */
2090 BOOL capsOn=keystate[VK_CAPITAL] !=0;
2092 /* helg: clear CAPS LOCK state if caps lock switches to cyrillic */
2093 if(cfg.xlat_capslockcyr)
2094 keystate[VK_CAPITAL] = 0;
2096 r = ToAscii (wParam, scan, keystate, keys, 0);
2102 unsigned char ch = (unsigned char)keys[i];
2104 if (compose_state==2 && (ch&0x80) == 0 && ch>' ') {
2109 if (compose_state==3 && (ch&0x80) == 0 && ch>' ') {
2113 if ((nc=check_compose(compose_char,ch)) == -1)
2118 *p++ = xlat_kbd2tty((unsigned char)nc);
2124 if( left_alt && key_down ) *p++ = '\033';
2130 ch = xlat_latkbd2win(ch);
2131 *p++ = xlat_kbd2tty(ch);
2135 /* This is so the ALT-Numpad and dead keys work correctly. */
2142 /* This stops ALT press-release doing a 'COMMAND MENU' function */
2143 if (message == WM_SYSKEYUP && wParam == VK_MENU)
2145 keystate[VK_MENU] = 0;
2152 void set_title (char *title) {
2153 sfree (window_name);
2154 window_name = smalloc(1+strlen(title));
2155 strcpy (window_name, title);
2156 if (cfg.win_name_always || !IsIconic(hwnd))
2157 SetWindowText (hwnd, title);
2160 void set_icon (char *title) {
2162 icon_name = smalloc(1+strlen(title));
2163 strcpy (icon_name, title);
2164 if (!cfg.win_name_always && IsIconic(hwnd))
2165 SetWindowText (hwnd, title);
2168 void set_sbar (int total, int start, int page) {
2171 if (!cfg.scrollbar) return;
2173 si.cbSize = sizeof(si);
2174 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
2176 si.nMax = total - 1;
2180 SetScrollInfo (hwnd, SB_VERT, &si, TRUE);
2183 Context get_ctx(void) {
2188 SelectPalette (hdc, pal, FALSE);
2194 void free_ctx (Context ctx) {
2195 SelectPalette (ctx, GetStockObject (DEFAULT_PALETTE), FALSE);
2196 ReleaseDC (hwnd, ctx);
2199 static void real_palette_set (int n, int r, int g, int b) {
2201 logpal->palPalEntry[n].peRed = r;
2202 logpal->palPalEntry[n].peGreen = g;
2203 logpal->palPalEntry[n].peBlue = b;
2204 logpal->palPalEntry[n].peFlags = PC_NOCOLLAPSE;
2205 colours[n] = PALETTERGB(r, g, b);
2206 SetPaletteEntries (pal, 0, NCOLOURS, logpal->palPalEntry);
2208 colours[n] = RGB(r, g, b);
2211 void palette_set (int n, int r, int g, int b) {
2212 static const int first[21] = {
2213 0, 2, 4, 6, 8, 10, 12, 14,
2214 1, 3, 5, 7, 9, 11, 13, 15,
2217 real_palette_set (first[n], r, g, b);
2219 real_palette_set (first[n]+1, r, g, b);
2221 HDC hdc = get_ctx();
2222 UnrealizeObject (pal);
2223 RealizePalette (hdc);
2228 void palette_reset (void) {
2231 for (i = 0; i < NCOLOURS; i++) {
2233 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
2234 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
2235 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
2236 logpal->palPalEntry[i].peFlags = 0;
2237 colours[i] = PALETTERGB(defpal[i].rgbtRed,
2238 defpal[i].rgbtGreen,
2239 defpal[i].rgbtBlue);
2241 colours[i] = RGB(defpal[i].rgbtRed,
2242 defpal[i].rgbtGreen,
2243 defpal[i].rgbtBlue);
2248 SetPaletteEntries (pal, 0, NCOLOURS, logpal->palPalEntry);
2250 RealizePalette (hdc);
2255 void write_clip (void *data, int len, int must_deselect) {
2259 clipdata = GlobalAlloc (GMEM_DDESHARE | GMEM_MOVEABLE, len + 1);
2262 lock = GlobalLock (clipdata);
2265 memcpy (lock, data, len);
2266 ((unsigned char *) lock) [len] = 0;
2267 GlobalUnlock (clipdata);
2270 SendMessage (hwnd, WM_IGNORE_CLIP, TRUE, 0);
2272 if (OpenClipboard (hwnd)) {
2274 SetClipboardData (CF_TEXT, clipdata);
2277 GlobalFree (clipdata);
2280 SendMessage (hwnd, WM_IGNORE_CLIP, FALSE, 0);
2283 void get_clip (void **p, int *len) {
2284 static HGLOBAL clipdata = NULL;
2288 GlobalUnlock (clipdata);
2292 if (OpenClipboard (NULL)) {
2293 clipdata = GetClipboardData (CF_TEXT);
2296 *p = GlobalLock (clipdata);
2310 * Move `lines' lines from position `from' to position `to' in the
2313 void optimised_move (int to, int from, int lines) {
2317 min = (to < from ? to : from);
2318 max = to + from - min;
2320 r.left = 0; r.right = cols * font_width;
2321 r.top = min * font_height; r.bottom = (max+lines) * font_height;
2322 ScrollWindow (hwnd, 0, (to - from) * font_height, &r, &r);
2326 * Print a message box and perform a fatal exit.
2328 void fatalbox(char *fmt, ...) {
2333 vsprintf(stuff, fmt, ap);
2335 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
2342 void beep(int errorbeep) {
2343 static long last_beep = 0;
2344 long now, beep_diff;
2346 now = GetTickCount();
2347 beep_diff = now-last_beep;
2349 /* Make sure we only respond to one beep per packet or so */
2350 if (beep_diff>=0 && beep_diff<50)
2354 MessageBeep(MB_ICONHAND);
2358 last_beep = GetTickCount();