17 #define PUTTY_DO_GLOBALS /* actually _define_ globals */
23 #define IDM_SHOWLOG 0x0010
24 #define IDM_NEWSESS 0x0020
25 #define IDM_DUPSESS 0x0030
26 #define IDM_RECONF 0x0040
27 #define IDM_CLRSB 0x0050
28 #define IDM_RESET 0x0060
29 #define IDM_TEL_AYT 0x0070
30 #define IDM_TEL_BRK 0x0080
31 #define IDM_TEL_SYNCH 0x0090
32 #define IDM_TEL_EC 0x00a0
33 #define IDM_TEL_EL 0x00b0
34 #define IDM_TEL_GA 0x00c0
35 #define IDM_TEL_NOP 0x00d0
36 #define IDM_TEL_ABORT 0x00e0
37 #define IDM_TEL_AO 0x00f0
38 #define IDM_TEL_IP 0x0100
39 #define IDM_TEL_SUSP 0x0110
40 #define IDM_TEL_EOR 0x0120
41 #define IDM_TEL_EOF 0x0130
42 #define IDM_ABOUT 0x0140
43 #define IDM_SAVEDSESS 0x0150
44 #define IDM_COPYALL 0x0160
46 #define IDM_SESSLGP 0x0250 /* log type printable */
47 #define IDM_SESSLGA 0x0260 /* log type all chars */
48 #define IDM_SESSLGE 0x0270 /* log end */
49 #define IDM_SAVED_MIN 0x1000
50 #define IDM_SAVED_MAX 0x2000
52 #define WM_IGNORE_SIZE (WM_XUSER + 1)
53 #define WM_IGNORE_CLIP (WM_XUSER + 2)
55 /* Needed for Chinese support and apparently not always defined. */
57 #define VK_PROCESSKEY 0xE5
60 /* Needed for mouse wheel support and not defined in earlier SDKs. */
62 #define WM_MOUSEWHEEL 0x020A
65 static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
66 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
67 unsigned char *output);
68 static void cfgtopalette(void);
69 static void init_palette(void);
70 static void init_fonts(int);
71 static void another_font(int);
72 static void deinit_fonts(void);
74 static int extra_width, extra_height;
76 static int pending_netevent = 0;
77 static WPARAM pend_netevent_wParam = 0;
78 static LPARAM pend_netevent_lParam = 0;
79 static void enact_pending_netevent(void);
80 static void flash_window(int mode);
82 static time_t last_movement = 0;
86 #define FONT_UNDERLINE 2
87 #define FONT_BOLDUND 3
88 #define FONT_WIDE 0x04
89 #define FONT_HIGH 0x08
90 #define FONT_NARROW 0x10
92 #define FONT_OEMBOLD 0x21
93 #define FONT_OEMUND 0x22
94 #define FONT_OEMBOLDUND 0x23
95 #define FONT_MSGOTHIC 0x40
96 #define FONT_MINGLIU 0x60
97 #define FONT_GULIMCHE 0x80
98 #define FONT_MAXNO 0x8F
100 static HFONT fonts[FONT_MAXNO];
101 static int fontflag[FONT_MAXNO];
103 BOLD_COLOURS, BOLD_SHADOW, BOLD_FONT
111 static COLORREF colours[NCOLOURS];
113 static LPLOGPALETTE logpal;
114 static RGBTRIPLE defpal[NCOLOURS];
118 static HBITMAP caretbm;
120 static int dbltime, lasttime, lastact;
121 static Mouse_Button lastbtn;
123 /* this allows xterm-style mouse handling. */
124 static int send_raw_mouse = 0;
125 static int wheel_accumulator = 0;
127 static char *window_name, *icon_name;
129 static int compose_state = 0;
131 static OSVERSIONINFO osVersion;
133 /* Dummy routine, only required in plink. */
134 void ldisc_update(int echo, int edit)
138 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
140 static char appname[] = "PuTTY";
145 int guess_width, guess_height;
148 flags = FLAG_VERBOSE | FLAG_INTERACTIVE;
150 winsock_ver = MAKEWORD(1, 1);
151 if (WSAStartup(winsock_ver, &wsadata)) {
152 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
153 MB_OK | MB_ICONEXCLAMATION);
156 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
157 MessageBox(NULL, "WinSock version is incompatible with 1.1",
158 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
162 /* WISHLIST: maybe allow config tweaking even if winsock not present? */
165 InitCommonControls();
167 /* Ensure a Maximize setting in Explorer doesn't maximise the
172 ZeroMemory(&osVersion, sizeof(osVersion));
173 osVersion.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
174 if (!GetVersionEx ( (OSVERSIONINFO *) &osVersion)) {
175 MessageBox(NULL, "Windows refuses to report a version",
176 "PuTTY Fatal Error", MB_OK | MB_ICONEXCLAMATION);
182 * Process the command line.
187 default_protocol = DEFAULT_PROTOCOL;
188 default_port = DEFAULT_PORT;
189 cfg.logtype = LGTYP_NONE;
191 do_defaults(NULL, &cfg);
194 while (*p && isspace(*p))
198 * Process command line options first. Yes, this can be
199 * done better, and it will be as soon as I have the
203 char *q = p + strcspn(p, " \t");
206 tolower(p[0]) == 's' &&
207 tolower(p[1]) == 's' && tolower(p[2]) == 'h') {
208 default_protocol = cfg.protocol = PROT_SSH;
209 default_port = cfg.port = 22;
210 } else if (q == p + 7 &&
211 tolower(p[0]) == 'c' &&
212 tolower(p[1]) == 'l' &&
213 tolower(p[2]) == 'e' &&
214 tolower(p[3]) == 'a' &&
215 tolower(p[4]) == 'n' &&
216 tolower(p[5]) == 'u' && tolower(p[6]) == 'p') {
218 * `putty -cleanup'. Remove all registry entries
219 * associated with PuTTY, and also find and delete
220 * the random seed file.
223 "This procedure will remove ALL Registry\n"
224 "entries associated with PuTTY, and will\n"
225 "also remove the PuTTY random seed file.\n"
227 "THIS PROCESS WILL DESTROY YOUR SAVED\n"
228 "SESSIONS. Are you really sure you want\n"
231 MB_YESNO | MB_ICONWARNING) == IDYES) {
236 p = q + strspn(q, " \t");
240 * An initial @ means to activate a saved session.
244 while (i > 1 && isspace(p[i - 1]))
247 do_defaults(p + 1, &cfg);
248 if (!*cfg.host && !do_config()) {
252 } else if (*p == '&') {
254 * An initial & means we've been given a command line
255 * containing the hex value of a HANDLE for a file
256 * mapping object, which we must then extract as a
261 if (sscanf(p + 1, "%p", &filemap) == 1 &&
262 (cp = MapViewOfFile(filemap, FILE_MAP_READ,
263 0, 0, sizeof(Config))) != NULL) {
266 CloseHandle(filemap);
267 } else if (!do_config()) {
274 * If the hostname starts with "telnet:", set the
275 * protocol to Telnet and process the string as a
278 if (!strncmp(q, "telnet:", 7)) {
282 if (q[0] == '/' && q[1] == '/')
284 cfg.protocol = PROT_TELNET;
286 while (*p && *p != ':' && *p != '/')
295 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
296 cfg.host[sizeof(cfg.host) - 1] = '\0';
298 while (*p && !isspace(*p))
302 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
303 cfg.host[sizeof(cfg.host) - 1] = '\0';
304 while (*p && isspace(*p))
319 * Trim leading whitespace off the hostname if it's there.
322 int space = strspn(cfg.host, " \t");
323 memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
326 /* See if host is of the form user@host */
327 if (cfg.host[0] != '\0') {
328 char *atsign = strchr(cfg.host, '@');
329 /* Make sure we're not overflowing the user field */
331 if (atsign - cfg.host < sizeof cfg.username) {
332 strncpy(cfg.username, cfg.host, atsign - cfg.host);
333 cfg.username[atsign - cfg.host] = '\0';
335 memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
340 * Trim a colon suffix off the hostname if it's there.
342 cfg.host[strcspn(cfg.host, ":")] = '\0';
346 * Select protocol. This is farmed out into a table in a
347 * separate file to enable an ssh-free variant.
352 for (i = 0; backends[i].backend != NULL; i++)
353 if (backends[i].protocol == cfg.protocol) {
354 back = backends[i].backend;
358 MessageBox(NULL, "Unsupported protocol number found",
359 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
365 /* Check for invalid Port number (i.e. zero) */
367 MessageBox(NULL, "Invalid Port Number",
368 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
375 wndclass.lpfnWndProc = WndProc;
376 wndclass.cbClsExtra = 0;
377 wndclass.cbWndExtra = 0;
378 wndclass.hInstance = inst;
379 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
380 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
381 wndclass.hbrBackground = GetStockObject(BLACK_BRUSH);
382 wndclass.lpszMenuName = NULL;
383 wndclass.lpszClassName = appname;
385 RegisterClass(&wndclass);
390 savelines = cfg.savelines;
396 * Guess some defaults for the window size. This all gets
397 * updated later, so we don't really care too much. However, we
398 * do want the font width/height guesses to correspond to a
399 * large font rather than a small one...
406 term_size(cfg.height, cfg.width, cfg.savelines);
407 guess_width = extra_width + font_width * cols;
408 guess_height = extra_height + font_height * rows;
411 HWND w = GetDesktopWindow();
412 GetWindowRect(w, &r);
413 if (guess_width > r.right - r.left)
414 guess_width = r.right - r.left;
415 if (guess_height > r.bottom - r.top)
416 guess_height = r.bottom - r.top;
420 int winmode = WS_OVERLAPPEDWINDOW | WS_VSCROLL;
423 winmode &= ~(WS_VSCROLL);
425 winmode &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
427 exwinmode |= WS_EX_TOPMOST;
429 exwinmode |= WS_EX_CLIENTEDGE;
430 hwnd = CreateWindowEx(exwinmode, appname, appname,
431 winmode, CW_USEDEFAULT, CW_USEDEFAULT,
432 guess_width, guess_height,
433 NULL, NULL, inst, NULL);
437 * Initialise the fonts, simultaneously correcting the guesses
438 * for font_{width,height}.
440 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
445 * Correct the guesses for extra_{width,height}.
449 GetWindowRect(hwnd, &wr);
450 GetClientRect(hwnd, &cr);
451 extra_width = wr.right - wr.left - cr.right + cr.left;
452 extra_height = wr.bottom - wr.top - cr.bottom + cr.top;
456 * Resize the window, now we know what size we _really_ want it
459 guess_width = extra_width + font_width * cols;
460 guess_height = extra_height + font_height * rows;
461 SendMessage(hwnd, WM_IGNORE_SIZE, 0, 0);
462 SetWindowPos(hwnd, NULL, 0, 0, guess_width, guess_height,
463 SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
466 * Set up a caret bitmap, with no content.
470 int size = (font_width + 15) / 16 * 2 * font_height;
471 bits = smalloc(size);
472 memset(bits, 0, size);
473 caretbm = CreateBitmap(font_width, font_height, 1, 1, bits);
476 CreateCaret(hwnd, caretbm, font_width, font_height);
479 * Initialise the scroll bar.
484 si.cbSize = sizeof(si);
485 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
490 SetScrollInfo(hwnd, SB_VERT, &si, FALSE);
494 * Start up the telnet connection.
498 char msg[1024], *title;
501 error = back->init(cfg.host, cfg.port, &realhost);
503 sprintf(msg, "Unable to open connection to\n"
504 "%.800s\n" "%s", cfg.host, error);
505 MessageBox(NULL, msg, "PuTTY Error", MB_ICONERROR | MB_OK);
508 window_name = icon_name = NULL;
510 title = cfg.wintitle;
512 sprintf(msg, "%s - PuTTY", realhost);
520 session_closed = FALSE;
523 * Set up the input and output buffers.
526 outbuf_reap = outbuf_head = 0;
529 * Prepare the mouse handler.
531 lastact = MA_NOTHING;
532 lastbtn = MBT_NOTHING;
533 dbltime = GetDoubleClickTime();
536 * Set up the session-control options on the system menu.
539 HMENU m = GetSystemMenu(hwnd, FALSE);
543 AppendMenu(m, MF_SEPARATOR, 0, 0);
544 if (cfg.protocol == PROT_TELNET) {
546 AppendMenu(p, MF_ENABLED, IDM_TEL_AYT, "Are You There");
547 AppendMenu(p, MF_ENABLED, IDM_TEL_BRK, "Break");
548 AppendMenu(p, MF_ENABLED, IDM_TEL_SYNCH, "Synch");
549 AppendMenu(p, MF_SEPARATOR, 0, 0);
550 AppendMenu(p, MF_ENABLED, IDM_TEL_EC, "Erase Character");
551 AppendMenu(p, MF_ENABLED, IDM_TEL_EL, "Erase Line");
552 AppendMenu(p, MF_ENABLED, IDM_TEL_GA, "Go Ahead");
553 AppendMenu(p, MF_ENABLED, IDM_TEL_NOP, "No Operation");
554 AppendMenu(p, MF_SEPARATOR, 0, 0);
555 AppendMenu(p, MF_ENABLED, IDM_TEL_ABORT, "Abort Process");
556 AppendMenu(p, MF_ENABLED, IDM_TEL_AO, "Abort Output");
557 AppendMenu(p, MF_ENABLED, IDM_TEL_IP, "Interrupt Process");
558 AppendMenu(p, MF_ENABLED, IDM_TEL_SUSP, "Suspend Process");
559 AppendMenu(p, MF_SEPARATOR, 0, 0);
560 AppendMenu(p, MF_ENABLED, IDM_TEL_EOR, "End Of Record");
561 AppendMenu(p, MF_ENABLED, IDM_TEL_EOF, "End Of File");
562 AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) p,
564 AppendMenu(m, MF_SEPARATOR, 0, 0);
566 AppendMenu(m, MF_ENABLED, IDM_SHOWLOG, "&Event Log");
567 AppendMenu(m, MF_SEPARATOR, 0, 0);
568 AppendMenu(m, MF_ENABLED, IDM_NEWSESS, "Ne&w Session...");
569 AppendMenu(m, MF_ENABLED, IDM_DUPSESS, "&Duplicate Session");
572 for (i = 1; i < ((nsessions < 256) ? nsessions : 256); i++)
573 AppendMenu(s, MF_ENABLED, IDM_SAVED_MIN + (16 * i),
575 AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) s, "Sa&ved Sessions");
576 AppendMenu(m, MF_ENABLED, IDM_RECONF, "Chan&ge Settings...");
577 AppendMenu(m, MF_SEPARATOR, 0, 0);
578 AppendMenu(m, MF_ENABLED, IDM_COPYALL, "C&opy All to Clipboard");
579 AppendMenu(m, MF_ENABLED, IDM_CLRSB, "C&lear Scrollback");
580 AppendMenu(m, MF_ENABLED, IDM_RESET, "Rese&t Terminal");
581 AppendMenu(m, MF_SEPARATOR, 0, 0);
582 AppendMenu(m, MF_ENABLED, IDM_ABOUT, "&About PuTTY");
586 * Finally show the window!
588 ShowWindow(hwnd, show);
589 SetForegroundWindow(hwnd);
592 * Open the initial log file if there is one.
597 * Set the palette up.
603 has_focus = (GetForegroundWindow() == hwnd);
606 if (GetMessage(&msg, NULL, 0, 0) == 1) {
607 int timer_id = 0, long_timer = 0;
609 while (msg.message != WM_QUIT) {
610 /* Sometimes DispatchMessage calls routines that use their own
611 * GetMessage loop, setup this timer so we get some control back.
613 * Also call term_update() from the timer so that if the host
614 * is sending data flat out we still do redraws.
616 if (timer_id && long_timer) {
617 KillTimer(hwnd, timer_id);
618 long_timer = timer_id = 0;
621 timer_id = SetTimer(hwnd, 1, 20, NULL);
622 if (!(IsWindow(logbox) && IsDialogMessage(logbox, &msg)))
623 DispatchMessage(&msg);
625 /* Make sure we blink everything that needs it. */
628 /* Send the paste buffer if there's anything to send */
631 /* If there's nothing new in the queue then we can do everything
632 * we've delayed, reading the socket, writing, and repainting
635 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
638 if (pending_netevent) {
639 enact_pending_netevent();
641 /* Force the cursor blink on */
644 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
648 /* Okay there is now nothing to do so we make sure the screen is
649 * completely up to date then tell windows to call us in a little
653 KillTimer(hwnd, timer_id);
662 flash_window(1); /* maintain */
665 /* Hmm, term_update didn't want to do an update too soon ... */
666 timer_id = SetTimer(hwnd, 1, 50, NULL);
668 timer_id = SetTimer(hwnd, 1, 500, NULL);
670 timer_id = SetTimer(hwnd, 1, 100, NULL);
673 /* There's no point rescanning everything in the message queue
674 * so we do an apparently unnecessary wait here
677 if (GetMessage(&msg, NULL, 0, 0) != 1)
691 if (cfg.protocol == PROT_SSH) {
702 * Set up, or shut down, an AsyncSelect. Called from winnet.c.
704 char *do_select(SOCKET skt, int startup)
709 events = FD_READ | FD_WRITE | FD_OOB | FD_CLOSE | FD_ACCEPT;
714 return "do_select(): internal error (hwnd==NULL)";
715 if (WSAAsyncSelect(skt, hwnd, msg, events) == SOCKET_ERROR) {
716 switch (WSAGetLastError()) {
718 return "Network is down";
720 return "WSAAsyncSelect(): unknown error";
727 * set or clear the "raw mouse message" mode
729 void set_raw_mouse_mode(int activate)
731 send_raw_mouse = activate;
732 SetCursor(LoadCursor(NULL, activate ? IDC_ARROW : IDC_IBEAM));
736 * Print a message box and close the connection.
738 void connection_fatal(char *fmt, ...)
744 vsprintf(stuff, fmt, ap);
746 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
747 if (cfg.close_on_exit == COE_ALWAYS)
750 session_closed = TRUE;
751 SetWindowText(hwnd, "PuTTY (inactive)");
756 * Actually do the job requested by a WM_NETEVENT
758 static void enact_pending_netevent(void)
760 static int reentering = 0;
761 extern int select_result(WPARAM, LPARAM);
765 return; /* don't unpend the pending */
767 pending_netevent = FALSE;
770 ret = select_result(pend_netevent_wParam, pend_netevent_lParam);
773 if (ret == 0 && !session_closed) {
774 /* Abnormal exits will already have set session_closed and taken
775 * appropriate action. */
776 if (cfg.close_on_exit == COE_ALWAYS ||
777 cfg.close_on_exit == COE_NORMAL) PostQuitMessage(0);
779 session_closed = TRUE;
780 SetWindowText(hwnd, "PuTTY (inactive)");
781 MessageBox(hwnd, "Connection closed by remote host",
782 "PuTTY", MB_OK | MB_ICONINFORMATION);
788 * Copy the colour palette from the configuration data into defpal.
789 * This is non-trivial because the colour indices are different.
791 static void cfgtopalette(void)
794 static const int ww[] = {
795 6, 7, 8, 9, 10, 11, 12, 13,
796 14, 15, 16, 17, 18, 19, 20, 21,
797 0, 1, 2, 3, 4, 4, 5, 5
800 for (i = 0; i < 24; i++) {
802 defpal[i].rgbtRed = cfg.colours[w][0];
803 defpal[i].rgbtGreen = cfg.colours[w][1];
804 defpal[i].rgbtBlue = cfg.colours[w][2];
809 * Set up the colour palette.
811 static void init_palette(void)
814 HDC hdc = GetDC(hwnd);
816 if (cfg.try_palette && GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) {
817 logpal = smalloc(sizeof(*logpal)
818 - sizeof(logpal->palPalEntry)
819 + NCOLOURS * sizeof(PALETTEENTRY));
820 logpal->palVersion = 0x300;
821 logpal->palNumEntries = NCOLOURS;
822 for (i = 0; i < NCOLOURS; i++) {
823 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
824 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
825 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
826 logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
828 pal = CreatePalette(logpal);
830 SelectPalette(hdc, pal, FALSE);
832 SelectPalette(hdc, GetStockObject(DEFAULT_PALETTE), FALSE);
835 ReleaseDC(hwnd, hdc);
838 for (i = 0; i < NCOLOURS; i++)
839 colours[i] = PALETTERGB(defpal[i].rgbtRed,
843 for (i = 0; i < NCOLOURS; i++)
844 colours[i] = RGB(defpal[i].rgbtRed,
845 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
849 * Initialise all the fonts we will need initially. There may be as many as
850 * three or as few as one. The other (poentially) twentyone fonts are done
851 * if/when they are needed.
855 * - check the font width and height, correcting our guesses if
858 * - verify that the bold font is the same width as the ordinary
859 * one, and engage shadow bolding if not.
861 * - verify that the underlined font is the same width as the
862 * ordinary one (manual underlining by means of line drawing can
863 * be done in a pinch).
865 static void init_fonts(int pick_width)
872 int fw_dontcare, fw_bold;
874 for (i = 0; i < FONT_MAXNO; i++)
877 if (cfg.fontisbold) {
878 fw_dontcare = FW_BOLD;
881 fw_dontcare = FW_DONTCARE;
887 font_height = cfg.fontheight;
888 if (font_height > 0) {
890 -MulDiv(font_height, GetDeviceCaps(hdc, LOGPIXELSY), 72);
892 font_width = pick_width;
895 fonts[i] = CreateFont (font_height, font_width, 0, 0, w, FALSE, u, FALSE, \
896 c, OUT_DEFAULT_PRECIS, \
897 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, \
898 FIXED_PITCH | FF_DONTCARE, cfg.font)
900 f(FONT_NORMAL, cfg.fontcharset, fw_dontcare, FALSE);
902 SelectObject(hdc, fonts[FONT_NORMAL]);
903 GetTextMetrics(hdc, &tm);
904 font_height = tm.tmHeight;
905 font_width = tm.tmAveCharWidth;
909 DWORD cset = tm.tmCharSet;
910 memset(&info, 0xFF, sizeof(info));
912 /* !!! Yes the next line is right */
913 if (cset == OEM_CHARSET)
914 font_codepage = GetOEMCP();
916 if (TranslateCharsetInfo
917 ((DWORD *) cset, &info, TCI_SRCCHARSET)) font_codepage =
922 GetCPInfo(font_codepage, &cpinfo);
923 dbcs_screenfont = (cpinfo.MaxCharSize > 1);
926 f(FONT_UNDERLINE, cfg.fontcharset, fw_dontcare, TRUE);
929 * Some fonts, e.g. 9-pt Courier, draw their underlines
930 * outside their character cell. We successfully prevent
931 * screen corruption by clipping the text output, but then
932 * we lose the underline completely. Here we try to work
933 * out whether this is such a font, and if it is, we set a
934 * flag that causes underlines to be drawn by hand.
936 * Having tried other more sophisticated approaches (such
937 * as examining the TEXTMETRIC structure or requesting the
938 * height of a string), I think we'll do this the brute
939 * force way: we create a small bitmap, draw an underlined
940 * space on it, and test to see whether any pixels are
941 * foreground-coloured. (Since we expect the underline to
942 * go all the way across the character cell, we only search
943 * down a single column of the bitmap, half way across.)
947 HBITMAP und_bm, und_oldbm;
951 und_dc = CreateCompatibleDC(hdc);
952 und_bm = CreateCompatibleBitmap(hdc, font_width, font_height);
953 und_oldbm = SelectObject(und_dc, und_bm);
954 SelectObject(und_dc, fonts[FONT_UNDERLINE]);
955 SetTextAlign(und_dc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
956 SetTextColor(und_dc, RGB(255, 255, 255));
957 SetBkColor(und_dc, RGB(0, 0, 0));
958 SetBkMode(und_dc, OPAQUE);
959 ExtTextOut(und_dc, 0, 0, ETO_OPAQUE, NULL, " ", 1, NULL);
961 for (i = 0; i < font_height; i++) {
962 c = GetPixel(und_dc, font_width / 2, i);
963 if (c != RGB(0, 0, 0))
966 SelectObject(und_dc, und_oldbm);
967 DeleteObject(und_bm);
971 DeleteObject(fonts[FONT_UNDERLINE]);
972 fonts[FONT_UNDERLINE] = 0;
976 if (bold_mode == BOLD_FONT) {
977 f(FONT_BOLD, cfg.fontcharset, fw_bold, FALSE);
981 descent = tm.tmAscent + 1;
982 if (descent >= font_height)
983 descent = font_height - 1;
985 for (i = 0; i < 3; i++) {
987 if (SelectObject(hdc, fonts[i]) && GetTextMetrics(hdc, &tm))
988 fontsize[i] = tm.tmAveCharWidth + 256 * tm.tmHeight;
995 ReleaseDC(hwnd, hdc);
997 if (fontsize[FONT_UNDERLINE] != fontsize[FONT_NORMAL]) {
999 DeleteObject(fonts[FONT_UNDERLINE]);
1000 fonts[FONT_UNDERLINE] = 0;
1003 if (bold_mode == BOLD_FONT &&
1004 fontsize[FONT_BOLD] != fontsize[FONT_NORMAL]) {
1005 bold_mode = BOLD_SHADOW;
1006 DeleteObject(fonts[FONT_BOLD]);
1007 fonts[FONT_BOLD] = 0;
1009 fontflag[0] = fontflag[1] = fontflag[2] = 1;
1014 static void another_font(int fontno)
1017 int fw_dontcare, fw_bold;
1021 if (fontno < 0 || fontno >= FONT_MAXNO || fontflag[fontno])
1024 basefont = (fontno & ~(FONT_BOLDUND));
1025 if (basefont != fontno && !fontflag[basefont])
1026 another_font(basefont);
1028 if (cfg.fontisbold) {
1029 fw_dontcare = FW_BOLD;
1032 fw_dontcare = FW_DONTCARE;
1036 c = cfg.fontcharset;
1042 if (fontno & FONT_WIDE)
1044 if (fontno & FONT_NARROW)
1046 if (fontno & FONT_OEM)
1048 if (fontno & FONT_BOLD)
1050 if (fontno & FONT_UNDERLINE)
1054 CreateFont(font_height * (1 + !!(fontno & FONT_HIGH)), x, 0, 0, w,
1055 FALSE, u, FALSE, c, OUT_DEFAULT_PRECIS,
1056 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
1057 FIXED_PITCH | FF_DONTCARE, s);
1059 fontflag[fontno] = 1;
1062 static void deinit_fonts(void)
1065 for (i = 0; i < FONT_MAXNO; i++) {
1067 DeleteObject(fonts[i]);
1073 void request_resize(int w, int h, int refont)
1077 /* If the window is maximized supress resizing attempts */
1081 if (refont && w != cols && (cols == 80 || cols == 132)) {
1082 /* If font width too big for screen should we shrink the font more ? */
1084 font_width = ((font_width * cols + w / 2) / w);
1088 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
1089 und_mode = UND_FONT;
1090 init_fonts(font_width);
1092 static int first_time = 1;
1095 switch (first_time) {
1097 /* Get the size of the screen */
1098 if (GetClientRect(GetDesktopWindow(), &ss))
1099 /* first_time = 0 */ ;
1105 /* Make sure the values are sane */
1106 width = (ss.right - ss.left - extra_width) / font_width;
1107 height = (ss.bottom - ss.top - extra_height) / font_height;
1120 width = extra_width + font_width * w;
1121 height = extra_height + font_height * h;
1123 SetWindowPos(hwnd, NULL, 0, 0, width, height,
1124 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1125 SWP_NOMOVE | SWP_NOZORDER);
1128 static void click(Mouse_Button b, int x, int y, int shift, int ctrl)
1130 int thistime = GetMessageTime();
1132 if (send_raw_mouse) {
1133 term_mouse(b, MA_CLICK, x, y, shift, ctrl);
1137 if (lastbtn == b && thistime - lasttime < dbltime) {
1138 lastact = (lastact == MA_CLICK ? MA_2CLK :
1139 lastact == MA_2CLK ? MA_3CLK :
1140 lastact == MA_3CLK ? MA_CLICK : MA_NOTHING);
1145 if (lastact != MA_NOTHING)
1146 term_mouse(b, lastact, x, y, shift, ctrl);
1147 lasttime = thistime;
1151 * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT)
1152 * into a cooked one (SELECT, EXTEND, PASTE).
1154 Mouse_Button translate_button(Mouse_Button button)
1156 if (button == MBT_LEFT)
1158 if (button == MBT_MIDDLE)
1159 return cfg.mouse_is_xterm ? MBT_PASTE : MBT_EXTEND;
1160 if (button == MBT_RIGHT)
1161 return cfg.mouse_is_xterm ? MBT_EXTEND : MBT_PASTE;
1162 return 0; /* shouldn't happen */
1165 static void show_mouseptr(int show)
1167 static int cursor_visible = 1;
1168 if (!cfg.hide_mouseptr) /* override if this feature disabled */
1170 if (cursor_visible && !show)
1172 else if (!cursor_visible && show)
1174 cursor_visible = show;
1177 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1178 WPARAM wParam, LPARAM lParam)
1181 static int ignore_size = FALSE;
1182 static int ignore_clip = FALSE;
1183 static int just_reconfigged = FALSE;
1184 static int resizing = FALSE;
1185 static int need_backend_resize = FALSE;
1186 static int defered_resize = FALSE;
1190 if (pending_netevent)
1191 enact_pending_netevent();
1198 if (cfg.ping_interval > 0) {
1201 if (now - last_movement > cfg.ping_interval) {
1202 back->special(TS_PING);
1203 last_movement = now;
1211 if (!cfg.warn_on_close || session_closed ||
1213 "Are you sure you want to close this session?",
1214 "PuTTY Exit Confirmation",
1215 MB_ICONWARNING | MB_OKCANCEL) == IDOK)
1216 DestroyWindow(hwnd);
1223 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1235 PROCESS_INFORMATION pi;
1236 HANDLE filemap = NULL;
1238 if (wParam == IDM_DUPSESS) {
1240 * Allocate a file-mapping memory chunk for the
1243 SECURITY_ATTRIBUTES sa;
1246 sa.nLength = sizeof(sa);
1247 sa.lpSecurityDescriptor = NULL;
1248 sa.bInheritHandle = TRUE;
1249 filemap = CreateFileMapping((HANDLE) 0xFFFFFFFF,
1252 0, sizeof(Config), NULL);
1254 p = (Config *) MapViewOfFile(filemap,
1256 0, 0, sizeof(Config));
1258 *p = cfg; /* structure copy */
1262 sprintf(c, "putty &%p", filemap);
1264 } else if (wParam == IDM_SAVEDSESS) {
1266 sessions[(lParam - IDM_SAVED_MIN) / 16];
1267 cl = smalloc(16 + strlen(session)); /* 8, but play safe */
1269 cl = NULL; /* not a very important failure mode */
1271 sprintf(cl, "putty @%s", session);
1277 GetModuleFileName(NULL, b, sizeof(b) - 1);
1279 si.lpReserved = NULL;
1280 si.lpDesktop = NULL;
1284 si.lpReserved2 = NULL;
1285 CreateProcess(b, cl, NULL, NULL, TRUE,
1286 NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
1289 CloseHandle(filemap);
1296 int prev_alwaysontop = cfg.alwaysontop;
1297 int prev_sunken_edge = cfg.sunken_edge;
1298 char oldlogfile[FILENAME_MAX];
1300 int need_setwpos = FALSE;
1301 int old_fwidth, old_fheight;
1303 strcpy(oldlogfile, cfg.logfilename);
1304 oldlogtype = cfg.logtype;
1305 old_fwidth = font_width;
1306 old_fheight = font_height;
1307 GetWindowText(hwnd, cfg.wintitle, sizeof(cfg.wintitle));
1309 if (!do_reconfig(hwnd))
1312 if (strcmp(oldlogfile, cfg.logfilename) ||
1313 oldlogtype != cfg.logtype) {
1314 logfclose(); /* reset logging */
1318 just_reconfigged = TRUE;
1320 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
1321 und_mode = UND_FONT;
1325 * Flush the line discipline's edit buffer in the
1326 * case where local editing has just been disabled.
1328 ldisc_send(NULL, 0);
1336 /* Enable or disable the scroll bar, etc */
1338 LONG nflg, flag = GetWindowLong(hwnd, GWL_STYLE);
1339 LONG nexflag, exflag =
1340 GetWindowLong(hwnd, GWL_EXSTYLE);
1343 if (cfg.alwaysontop != prev_alwaysontop) {
1344 if (cfg.alwaysontop) {
1345 nexflag |= WS_EX_TOPMOST;
1346 SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
1347 SWP_NOMOVE | SWP_NOSIZE);
1349 nexflag &= ~(WS_EX_TOPMOST);
1350 SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0,
1351 SWP_NOMOVE | SWP_NOSIZE);
1354 if (cfg.sunken_edge)
1355 nexflag |= WS_EX_CLIENTEDGE;
1357 nexflag &= ~(WS_EX_CLIENTEDGE);
1363 nflg &= ~WS_VSCROLL;
1365 nflg &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
1367 nflg |= (WS_THICKFRAME | WS_MAXIMIZEBOX);
1369 if (nflg != flag || nexflag != exflag) {
1373 SetWindowLong(hwnd, GWL_STYLE, nflg);
1374 if (nexflag != exflag)
1375 SetWindowLong(hwnd, GWL_EXSTYLE, nexflag);
1377 SendMessage(hwnd, WM_IGNORE_SIZE, 0, 0);
1379 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
1380 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1381 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER
1382 | SWP_FRAMECHANGED);
1384 GetWindowRect(hwnd, &wr);
1385 GetClientRect(hwnd, &cr);
1387 wr.right - wr.left - cr.right + cr.left;
1389 wr.bottom - wr.top - cr.bottom + cr.top;
1390 need_setwpos = TRUE;
1394 if (cfg.height != rows ||
1395 cfg.width != cols ||
1396 old_fwidth != font_width ||
1397 old_fheight != font_height ||
1398 cfg.savelines != savelines ||
1399 cfg.sunken_edge != prev_sunken_edge)
1400 need_setwpos = TRUE;
1402 if (IsZoomed(hwnd)) {
1406 defered_resize = TRUE;
1408 GetClientRect(hwnd, &cr);
1409 w = cr.right - cr.left;
1410 h = cr.bottom - cr.top;
1414 h = h / font_height;
1418 term_size(h, w, cfg.savelines);
1419 InvalidateRect(hwnd, NULL, TRUE);
1422 term_size(cfg.height, cfg.width, cfg.savelines);
1423 InvalidateRect(hwnd, NULL, TRUE);
1425 SetWindowPos(hwnd, NULL, 0, 0,
1426 extra_width + font_width * cfg.width,
1428 font_height * cfg.height,
1429 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1430 SWP_NOMOVE | SWP_NOZORDER);
1434 if (cfg.locksize && IsZoomed(hwnd))
1436 set_title(cfg.wintitle);
1437 if (IsIconic(hwnd)) {
1439 cfg.win_name_always ? window_name :
1454 back->special(TS_AYT);
1457 back->special(TS_BRK);
1460 back->special(TS_SYNCH);
1463 back->special(TS_EC);
1466 back->special(TS_EL);
1469 back->special(TS_GA);
1472 back->special(TS_NOP);
1475 back->special(TS_ABORT);
1478 back->special(TS_AO);
1481 back->special(TS_IP);
1484 back->special(TS_SUSP);
1487 back->special(TS_EOR);
1490 back->special(TS_EOF);
1496 if (wParam >= IDM_SAVED_MIN && wParam <= IDM_SAVED_MAX) {
1497 SendMessage(hwnd, WM_SYSCOMMAND, IDM_SAVEDSESS, wParam);
1502 #define X_POS(l) ((int)(short)LOWORD(l))
1503 #define Y_POS(l) ((int)(short)HIWORD(l))
1505 #define TO_CHR_X(x) (((x)<0 ? (x)-font_width+1 : (x)) / font_width)
1506 #define TO_CHR_Y(y) (((y)<0 ? (y)-font_height+1: (y)) / font_height)
1507 #define WHEEL_DELTA 120
1510 wheel_accumulator += (short) HIWORD(wParam);
1511 wParam = LOWORD(wParam);
1513 /* process events when the threshold is reached */
1514 while (abs(wheel_accumulator) >= WHEEL_DELTA) {
1517 /* reduce amount for next time */
1518 if (wheel_accumulator > 0) {
1520 wheel_accumulator -= WHEEL_DELTA;
1521 } else if (wheel_accumulator < 0) {
1523 wheel_accumulator += WHEEL_DELTA;
1527 if (send_raw_mouse) {
1528 /* send a mouse-down followed by a mouse up */
1531 TO_CHR_X(X_POS(lParam)),
1532 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
1533 wParam & MK_CONTROL);
1534 term_mouse(b, MA_RELEASE, TO_CHR_X(X_POS(lParam)),
1535 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
1536 wParam & MK_CONTROL);
1538 /* trigger a scroll */
1540 b == MBT_WHEEL_UP ? -rows / 2 : rows / 2);
1545 case WM_LBUTTONDOWN:
1546 case WM_MBUTTONDOWN:
1547 case WM_RBUTTONDOWN:
1554 case WM_LBUTTONDOWN:
1558 case WM_MBUTTONDOWN:
1559 button = MBT_MIDDLE;
1562 case WM_RBUTTONDOWN:
1571 button = MBT_MIDDLE;
1579 button = press = 0; /* shouldn't happen */
1584 TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam)),
1585 wParam & MK_SHIFT, wParam & MK_CONTROL);
1588 term_mouse(button, MA_RELEASE,
1589 TO_CHR_X(X_POS(lParam)),
1590 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
1591 wParam & MK_CONTROL);
1599 * Add the mouse position and message time to the random
1602 noise_ultralight(lParam);
1604 if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)) {
1606 if (wParam & MK_LBUTTON)
1608 else if (wParam & MK_MBUTTON)
1612 term_mouse(b, MA_DRAG, TO_CHR_X(X_POS(lParam)),
1613 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
1614 wParam & MK_CONTROL);
1617 case WM_NCMOUSEMOVE:
1619 noise_ultralight(lParam);
1621 case WM_IGNORE_CLIP:
1622 ignore_clip = wParam; /* don't panic on DESTROYCLIPBOARD */
1624 case WM_DESTROYCLIPBOARD:
1627 ignore_clip = FALSE;
1633 hdc = BeginPaint(hwnd, &p);
1635 SelectPalette(hdc, pal, TRUE);
1636 RealizePalette(hdc);
1638 term_paint(hdc, p.rcPaint.left, p.rcPaint.top,
1639 p.rcPaint.right, p.rcPaint.bottom);
1640 SelectObject(hdc, GetStockObject(SYSTEM_FONT));
1641 SelectObject(hdc, GetStockObject(WHITE_PEN));
1647 /* Notice we can get multiple netevents, FD_READ, FD_WRITE etc
1648 * but the only one that's likely to try to overload us is FD_READ.
1649 * This means buffering just one is fine.
1651 if (pending_netevent)
1652 enact_pending_netevent();
1654 pending_netevent = TRUE;
1655 pend_netevent_wParam = wParam;
1656 pend_netevent_lParam = lParam;
1657 time(&last_movement);
1661 CreateCaret(hwnd, caretbm, font_width, font_height);
1663 flash_window(0); /* stop */
1675 case WM_IGNORE_SIZE:
1676 ignore_size = TRUE; /* don't panic on next WM_SIZE msg */
1678 case WM_ENTERSIZEMOVE:
1681 need_backend_resize = FALSE;
1683 case WM_EXITSIZEMOVE:
1686 if (need_backend_resize)
1691 int width, height, w, h, ew, eh;
1692 LPRECT r = (LPRECT) lParam;
1694 width = r->right - r->left - extra_width;
1695 height = r->bottom - r->top - extra_height;
1696 w = (width + font_width / 2) / font_width;
1699 h = (height + font_height / 2) / font_height;
1702 UpdateSizeTip(hwnd, w, h);
1703 ew = width - w * font_width;
1704 eh = height - h * font_height;
1706 if (wParam == WMSZ_LEFT ||
1707 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
1713 if (wParam == WMSZ_TOP ||
1714 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
1724 /* break; (never reached) */
1726 if (wParam == SIZE_MINIMIZED) {
1728 cfg.win_name_always ? window_name : icon_name);
1731 if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
1732 SetWindowText(hwnd, window_name);
1734 int width, height, w, h;
1735 #if 0 /* we have fixed this using WM_SIZING now */
1739 width = LOWORD(lParam);
1740 height = HIWORD(lParam);
1741 w = width / font_width;
1744 h = height / font_height;
1747 #if 0 /* we have fixed this using WM_SIZING now */
1748 ew = width - w * font_width;
1749 eh = height - h * font_height;
1750 if (ew != 0 || eh != 0) {
1752 GetWindowRect(hwnd, &r);
1753 SendMessage(hwnd, WM_IGNORE_SIZE, 0, 0);
1754 SetWindowPos(hwnd, NULL, 0, 0,
1755 r.right - r.left - ew, r.bottom - r.top - eh,
1756 SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER);
1759 if (w != cols || h != rows || just_reconfigged) {
1761 term_size(h, w, cfg.savelines);
1763 * Don't call back->size in mid-resize. (To prevent
1764 * massive numbers of resize events getting sent
1765 * down the connection during an NT opaque drag.)
1770 need_backend_resize = TRUE;
1774 just_reconfigged = FALSE;
1777 if (wParam == SIZE_RESTORED && defered_resize) {
1778 defered_resize = FALSE;
1779 SetWindowPos(hwnd, NULL, 0, 0,
1780 extra_width + font_width * cfg.width,
1781 extra_height + font_height * cfg.height,
1782 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1783 SWP_NOMOVE | SWP_NOZORDER);
1785 ignore_size = FALSE;
1788 switch (LOWORD(wParam)) {
1802 term_scroll(0, +rows / 2);
1805 term_scroll(0, -rows / 2);
1807 case SB_THUMBPOSITION:
1809 term_scroll(1, HIWORD(wParam));
1813 case WM_PALETTECHANGED:
1814 if ((HWND) wParam != hwnd && pal != NULL) {
1815 HDC hdc = get_ctx();
1817 if (RealizePalette(hdc) > 0)
1823 case WM_QUERYNEWPALETTE:
1825 HDC hdc = get_ctx();
1827 if (RealizePalette(hdc) > 0)
1839 * Add the scan code and keypress timing to the random
1842 noise_ultralight(lParam);
1845 * We don't do TranslateMessage since it disassociates the
1846 * resulting CHAR message from the KEYDOWN that sparked it,
1847 * which we occasionally don't want. Instead, we process
1848 * KEYDOWN, and call the Win32 translator functions so that
1849 * we get the translations under _our_ control.
1852 unsigned char buf[20];
1855 if (wParam == VK_PROCESSKEY) {
1858 m.message = WM_KEYDOWN;
1860 m.lParam = lParam & 0xdfff;
1861 TranslateMessage(&m);
1863 len = TranslateKey(message, wParam, lParam, buf);
1865 return DefWindowProc(hwnd, message, wParam, lParam);
1868 * We need not bother about stdin backlogs here,
1869 * because in GUI PuTTY we can't do anything about
1870 * it anyway; there's no means of asking Windows to
1871 * hold off on KEYDOWN messages. We _have_ to
1872 * buffer everything we're sent.
1874 ldisc_send(buf, len);
1881 case WM_INPUTLANGCHANGE:
1883 /* wParam == Font number */
1884 /* lParam == Locale */
1886 HKL NewInputLocale = (HKL) lParam;
1888 // lParam == GetKeyboardLayout(0);
1890 GetLocaleInfo(LOWORD(NewInputLocale),
1891 LOCALE_IDEFAULTANSICODEPAGE, lbuf, sizeof(lbuf));
1893 kbd_codepage = atoi(lbuf);
1896 case WM_IME_COMPOSITION:
1902 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ||
1903 osVersion.dwPlatformId == VER_PLATFORM_WIN32s) break; /* no Unicode */
1905 if ((lParam & GCS_RESULTSTR) == 0) /* Composition unfinished. */
1906 break; /* fall back to DefWindowProc */
1908 hIMC = ImmGetContext(hwnd);
1909 n = ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, NULL, 0);
1912 buff = (char*) smalloc(n);
1913 ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, buff, n);
1914 luni_send((unsigned short *)buff, n / 2);
1917 ImmReleaseContext(hwnd, hIMC);
1922 if (wParam & 0xFF00) {
1923 unsigned char buf[2];
1926 buf[0] = wParam >> 8;
1927 lpage_send(kbd_codepage, buf, 2);
1929 char c = (unsigned char) wParam;
1930 lpage_send(kbd_codepage, &c, 1);
1936 * Nevertheless, we are prepared to deal with WM_CHAR
1937 * messages, should they crop up. So if someone wants to
1938 * post the things to us as part of a macro manoeuvre,
1939 * we're ready to cope.
1942 char c = (unsigned char)wParam;
1943 lpage_send(CP_ACP, &c, 1);
1947 if (send_raw_mouse) {
1948 SetCursor(LoadCursor(NULL, IDC_ARROW));
1953 return DefWindowProc(hwnd, message, wParam, lParam);
1957 * Move the system caret. (We maintain one, even though it's
1958 * invisible, for the benefit of blind people: apparently some
1959 * helper software tracks the system caret, so we should arrange to
1962 void sys_cursor(int x, int y)
1967 if (!has_focus) return;
1969 SetCaretPos(x * font_width, y * font_height);
1971 /* IMM calls on Win98 and beyond only */
1972 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32s) return; /* 3.11 */
1974 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS &&
1975 osVersion.dwMinorVersion == 0) return; /* 95 */
1977 /* we should have the IMM functions */
1978 hIMC = ImmGetContext(hwnd);
1979 cf.dwStyle = CFS_POINT;
1980 cf.ptCurrentPos.x = x * font_width;
1981 cf.ptCurrentPos.y = y * font_height;
1982 ImmSetCompositionWindow(hIMC, &cf);
1984 ImmReleaseContext(hwnd, hIMC);
1988 * Draw a line of text in the window, at given character
1989 * coordinates, in given attributes.
1991 * We are allowed to fiddle with the contents of `text'.
1993 void do_text(Context ctx, int x, int y, char *text, int len,
1994 unsigned long attr, int lattr)
1997 int nfg, nbg, nfont;
2000 int force_manual_underline = 0;
2001 int fnt_width = font_width * (1 + (lattr != LATTR_NORM));
2002 int char_width = fnt_width;
2003 int text_adjust = 0;
2004 static int *IpDx = 0, IpDxLEN = 0;
2006 if (attr & ATTR_WIDE)
2009 if (len > IpDxLEN || IpDx[0] != char_width) {
2011 if (len > IpDxLEN) {
2013 IpDx = smalloc((len + 16) * sizeof(int));
2014 IpDxLEN = (len + 16);
2016 for (i = 0; i < IpDxLEN; i++)
2017 IpDx[i] = char_width;
2023 if ((attr & TATTR_ACTCURS) && (cfg.cursor_type == 0 || big_cursor)) {
2024 attr &= ATTR_CUR_AND | (bold_mode != BOLD_COLOURS ? ATTR_BOLD : 0);
2025 attr ^= ATTR_CUR_XOR;
2029 if (cfg.vtmode == VT_POORMAN && lattr != LATTR_NORM) {
2030 /* Assume a poorman font is borken in other ways too. */
2040 nfont |= FONT_WIDE + FONT_HIGH;
2044 /* Special hack for the VT100 linedraw glyphs. */
2045 if ((attr & CSET_MASK) == 0x2300) {
2046 if (!dbcs_screenfont &&
2047 text[0] >= (char) 0xBA && text[0] <= (char) 0xBD) {
2048 switch ((unsigned char) (text[0])) {
2050 text_adjust = -2 * font_height / 5;
2053 text_adjust = -1 * font_height / 5;
2056 text_adjust = font_height / 5;
2059 text_adjust = 2 * font_height / 5;
2062 if (lattr == LATTR_TOP || lattr == LATTR_BOT)
2065 text[0] = (char) (unitab_xterm['q'] & CHAR_MASK);
2066 attr |= (unitab_xterm['q'] & CSET_MASK);
2067 if (attr & ATTR_UNDER) {
2068 attr &= ~ATTR_UNDER;
2069 force_manual_underline = 1;
2074 /* Anything left as an original character set is unprintable. */
2075 if (DIRECT_CHAR(attr)) {
2078 memset(text, 0xFF, len);
2082 if ((attr & CSET_MASK) == ATTR_OEMCP)
2085 nfg = 2 * ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
2086 nbg = 2 * ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
2087 if (bold_mode == BOLD_FONT && (attr & ATTR_BOLD))
2089 if (und_mode == UND_FONT && (attr & ATTR_UNDER))
2090 nfont |= FONT_UNDERLINE;
2091 another_font(nfont);
2092 if (!fonts[nfont]) {
2093 if (nfont & FONT_UNDERLINE)
2094 force_manual_underline = 1;
2095 /* Don't do the same for manual bold, it could be bad news. */
2097 nfont &= ~(FONT_BOLD | FONT_UNDERLINE);
2099 another_font(nfont);
2101 nfont = FONT_NORMAL;
2102 if (attr & ATTR_REVERSE) {
2107 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BOLD))
2109 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BLINK))
2113 SelectObject(hdc, fonts[nfont]);
2114 SetTextColor(hdc, fg);
2115 SetBkColor(hdc, bg);
2116 SetBkMode(hdc, OPAQUE);
2119 line_box.right = x + char_width * len;
2120 line_box.bottom = y + font_height;
2122 /* We're using a private area for direct to font. (512 chars.) */
2123 if (dbcs_screenfont && (attr & CSET_MASK) == ATTR_ACP) {
2124 /* Ho Hum, dbcs fonts are a PITA! */
2125 /* To display on W9x I have to convert to UCS */
2126 static wchar_t *uni_buf = 0;
2127 static int uni_len = 0;
2129 if (len > uni_len) {
2131 uni_buf = smalloc((uni_len = len) * sizeof(wchar_t));
2133 nlen = MultiByteToWideChar(font_codepage, MB_USEGLYPHCHARS,
2134 text, len, uni_buf, uni_len);
2140 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2141 ETO_CLIPPED | ETO_OPAQUE, &line_box, uni_buf, nlen, 0);
2142 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2143 SetBkMode(hdc, TRANSPARENT);
2144 ExtTextOutW(hdc, x - 1,
2145 y - font_height * (lattr ==
2146 LATTR_BOT) + text_adjust,
2147 ETO_CLIPPED, &line_box, uni_buf, nlen, 0);
2149 } else if (DIRECT_FONT(attr)) {
2151 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2152 ETO_CLIPPED | ETO_OPAQUE, &line_box, text, len, IpDx);
2153 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2154 SetBkMode(hdc, TRANSPARENT);
2156 /* GRR: This draws the character outside it's box and can leave
2157 * 'droppings' even with the clip box! I suppose I could loop it
2158 * one character at a time ... yuk.
2160 * Or ... I could do a test print with "W", and use +1 or -1 for this
2161 * shift depending on if the leftmost column is blank...
2163 ExtTextOut(hdc, x - 1,
2164 y - font_height * (lattr ==
2165 LATTR_BOT) + text_adjust,
2166 ETO_CLIPPED, &line_box, text, len, IpDx);
2169 /* And 'normal' unicode characters */
2170 static WCHAR *wbuf = NULL;
2171 static int wlen = 0;
2176 wbuf = smalloc(wlen * sizeof(WCHAR));
2178 for (i = 0; i < len; i++)
2179 wbuf[i] = (WCHAR) ((attr & CSET_MASK) + (text[i] & CHAR_MASK));
2182 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2183 ETO_CLIPPED | ETO_OPAQUE, &line_box, wbuf, len, IpDx);
2185 /* And the shadow bold hack. */
2186 if (bold_mode == BOLD_SHADOW) {
2187 SetBkMode(hdc, TRANSPARENT);
2188 ExtTextOutW(hdc, x - 1,
2189 y - font_height * (lattr ==
2190 LATTR_BOT) + text_adjust,
2191 ETO_CLIPPED, &line_box, wbuf, len, IpDx);
2194 if (lattr != LATTR_TOP && (force_manual_underline ||
2195 (und_mode == UND_LINE
2196 && (attr & ATTR_UNDER)))) {
2199 if (lattr == LATTR_BOT)
2200 dec = dec * 2 - font_height;
2202 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, fg));
2203 MoveToEx(hdc, x, y + dec, NULL);
2204 LineTo(hdc, x + len * char_width, y + dec);
2205 oldpen = SelectObject(hdc, oldpen);
2206 DeleteObject(oldpen);
2210 void do_cursor(Context ctx, int x, int y, char *text, int len,
2211 unsigned long attr, int lattr)
2217 int ctype = cfg.cursor_type;
2219 if ((attr & TATTR_ACTCURS) && (ctype == 0 || big_cursor)) {
2220 if (((attr & CSET_MASK) | (unsigned char) *text) != UCSWIDE) {
2221 do_text(ctx, x, y, text, len, attr, lattr);
2225 attr |= TATTR_RIGHTCURS;
2228 fnt_width = char_width = font_width * (1 + (lattr != LATTR_NORM));
2229 if (attr & ATTR_WIDE)
2234 if ((attr & TATTR_PASCURS) && (ctype == 0 || big_cursor)) {
2237 pts[0].x = pts[1].x = pts[4].x = x;
2238 pts[2].x = pts[3].x = x + char_width - 1;
2239 pts[0].y = pts[3].y = pts[4].y = y;
2240 pts[1].y = pts[2].y = y + font_height - 1;
2241 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2242 Polyline(hdc, pts, 5);
2243 oldpen = SelectObject(hdc, oldpen);
2244 DeleteObject(oldpen);
2245 } else if ((attr & (TATTR_ACTCURS | TATTR_PASCURS)) && ctype != 0) {
2246 int startx, starty, dx, dy, length, i;
2249 starty = y + descent;
2252 length = char_width;
2255 if (attr & TATTR_RIGHTCURS)
2256 xadjust = char_width - 1;
2257 startx = x + xadjust;
2261 length = font_height;
2263 if (attr & TATTR_ACTCURS) {
2266 SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2267 MoveToEx(hdc, startx, starty, NULL);
2268 LineTo(hdc, startx + dx * length, starty + dy * length);
2269 oldpen = SelectObject(hdc, oldpen);
2270 DeleteObject(oldpen);
2272 for (i = 0; i < length; i++) {
2274 SetPixel(hdc, startx, starty, colours[23]);
2284 * Translate a WM_(SYS)?KEY(UP|DOWN) message into a string of ASCII
2285 * codes. Returns number of bytes used or zero to drop the message
2286 * or -1 to forward the message to windows.
2288 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
2289 unsigned char *output)
2292 int scan, left_alt = 0, key_down, shift_state;
2294 unsigned char *p = output;
2295 static int alt_state = 0;
2296 static int alt_sum = 0;
2298 HKL kbd_layout = GetKeyboardLayout(0);
2300 static WORD keys[3];
2301 static int compose_char = 0;
2302 static WPARAM compose_key = 0;
2304 r = GetKeyboardState(keystate);
2306 memset(keystate, 0, sizeof(keystate));
2309 #define SHOW_TOASCII_RESULT
2310 { /* Tell us all about key events */
2311 static BYTE oldstate[256];
2312 static int first = 1;
2316 memcpy(oldstate, keystate, sizeof(oldstate));
2319 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT) {
2321 } else if ((HIWORD(lParam) & KF_UP)
2322 && scan == (HIWORD(lParam) & 0xFF)) {
2326 if (wParam >= VK_F1 && wParam <= VK_F20)
2327 debug(("K_F%d", wParam + 1 - VK_F1));
2340 debug(("VK_%02x", wParam));
2342 if (message == WM_SYSKEYDOWN || message == WM_SYSKEYUP)
2344 debug((", S%02x", scan = (HIWORD(lParam) & 0xFF)));
2346 ch = MapVirtualKeyEx(wParam, 2, kbd_layout);
2347 if (ch >= ' ' && ch <= '~')
2348 debug((", '%c'", ch));
2350 debug((", $%02x", ch));
2353 debug((", KB0=%02x", keys[0]));
2355 debug((", KB1=%02x", keys[1]));
2357 debug((", KB2=%02x", keys[2]));
2359 if ((keystate[VK_SHIFT] & 0x80) != 0)
2361 if ((keystate[VK_CONTROL] & 0x80) != 0)
2363 if ((HIWORD(lParam) & KF_EXTENDED))
2365 if ((HIWORD(lParam) & KF_UP))
2369 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT);
2370 else if ((HIWORD(lParam) & KF_UP))
2371 oldstate[wParam & 0xFF] ^= 0x80;
2373 oldstate[wParam & 0xFF] ^= 0x81;
2375 for (ch = 0; ch < 256; ch++)
2376 if (oldstate[ch] != keystate[ch])
2377 debug((", M%02x=%02x", ch, keystate[ch]));
2379 memcpy(oldstate, keystate, sizeof(oldstate));
2383 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED)) {
2384 keystate[VK_RMENU] = keystate[VK_MENU];
2388 /* Nastyness with NUMLock - Shift-NUMLock is left alone though */
2389 if ((cfg.funky_type == 3 ||
2390 (cfg.funky_type <= 1 && app_keypad_keys && !cfg.no_applic_k))
2391 && wParam == VK_NUMLOCK && !(keystate[VK_SHIFT] & 0x80)) {
2393 wParam = VK_EXECUTE;
2395 /* UnToggle NUMLock */
2396 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0)
2397 keystate[VK_NUMLOCK] ^= 1;
2400 /* And write back the 'adjusted' state */
2401 SetKeyboardState(keystate);
2404 /* Disable Auto repeat if required */
2405 if (repeat_off && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT)
2408 if ((HIWORD(lParam) & KF_ALTDOWN) && (keystate[VK_RMENU] & 0x80) == 0)
2411 key_down = ((HIWORD(lParam) & KF_UP) == 0);
2413 /* Make sure Ctrl-ALT is not the same as AltGr for ToAscii unless told. */
2414 if (left_alt && (keystate[VK_CONTROL] & 0x80)) {
2415 if (cfg.ctrlaltkeys)
2416 keystate[VK_MENU] = 0;
2418 keystate[VK_RMENU] = 0x80;
2423 scan = (HIWORD(lParam) & (KF_UP | KF_EXTENDED | 0xFF));
2424 shift_state = ((keystate[VK_SHIFT] & 0x80) != 0)
2425 + ((keystate[VK_CONTROL] & 0x80) != 0) * 2;
2427 /* Note if AltGr was pressed and if it was used as a compose key */
2428 if (!compose_state) {
2429 compose_key = 0x100;
2430 if (cfg.compose_key) {
2431 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED))
2432 compose_key = wParam;
2434 if (wParam == VK_APPS)
2435 compose_key = wParam;
2438 if (wParam == compose_key) {
2439 if (compose_state == 0
2440 && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0) compose_state =
2442 else if (compose_state == 1 && (HIWORD(lParam) & KF_UP))
2446 } else if (compose_state == 1 && wParam != VK_CONTROL)
2450 * Record that we pressed key so the scroll window can be reset, but
2451 * be careful to avoid Shift-UP/Down
2453 if (wParam != VK_SHIFT && wParam != VK_PRIOR && wParam != VK_NEXT) {
2457 /* Make sure we're not pasting */
2461 if (compose_state > 1 && left_alt)
2464 /* Sanitize the number pad if not using a PC NumPad */
2465 if (left_alt || (app_keypad_keys && !cfg.no_applic_k
2466 && cfg.funky_type != 2)
2467 || cfg.funky_type == 3 || cfg.nethack_keypad || compose_state) {
2468 if ((HIWORD(lParam) & KF_EXTENDED) == 0) {
2472 nParam = VK_NUMPAD0;
2475 nParam = VK_NUMPAD1;
2478 nParam = VK_NUMPAD2;
2481 nParam = VK_NUMPAD3;
2484 nParam = VK_NUMPAD4;
2487 nParam = VK_NUMPAD5;
2490 nParam = VK_NUMPAD6;
2493 nParam = VK_NUMPAD7;
2496 nParam = VK_NUMPAD8;
2499 nParam = VK_NUMPAD9;
2502 nParam = VK_DECIMAL;
2506 if (keystate[VK_NUMLOCK] & 1)
2513 /* If a key is pressed and AltGr is not active */
2514 if (key_down && (keystate[VK_RMENU] & 0x80) == 0 && !compose_state) {
2515 /* Okay, prepare for most alts then ... */
2519 /* Lets see if it's a pattern we know all about ... */
2520 if (wParam == VK_PRIOR && shift_state == 1) {
2521 SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0);
2524 if (wParam == VK_NEXT && shift_state == 1) {
2525 SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
2528 if (wParam == VK_INSERT && shift_state == 1) {
2532 if (left_alt && wParam == VK_F4 && cfg.alt_f4) {
2535 if (left_alt && wParam == VK_SPACE && cfg.alt_space) {
2537 PostMessage(hwnd, WM_CHAR, ' ', 0);
2538 SendMessage(hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0);
2541 /* Control-Numlock for app-keypad mode switch */
2542 if (wParam == VK_PAUSE && shift_state == 2) {
2543 app_keypad_keys ^= 1;
2547 /* Nethack keypad */
2548 if (cfg.nethack_keypad && !left_alt) {
2551 *p++ = shift_state ? 'B' : 'b';
2554 *p++ = shift_state ? 'J' : 'j';
2557 *p++ = shift_state ? 'N' : 'n';
2560 *p++ = shift_state ? 'H' : 'h';
2563 *p++ = shift_state ? '.' : '.';
2566 *p++ = shift_state ? 'L' : 'l';
2569 *p++ = shift_state ? 'Y' : 'y';
2572 *p++ = shift_state ? 'K' : 'k';
2575 *p++ = shift_state ? 'U' : 'u';
2580 /* Application Keypad */
2584 if (cfg.funky_type == 3 ||
2585 (cfg.funky_type <= 1 &&
2586 app_keypad_keys && !cfg.no_applic_k)) switch (wParam) {
2600 if (app_keypad_keys && !cfg.no_applic_k)
2637 if (cfg.funky_type == 2) {
2642 } else if (shift_state)
2649 if (cfg.funky_type == 2)
2653 if (cfg.funky_type == 2)
2657 if (cfg.funky_type == 2)
2662 if (HIWORD(lParam) & KF_EXTENDED)
2668 if (xkey >= 'P' && xkey <= 'S')
2669 p += sprintf((char *) p, "\x1B%c", xkey);
2671 p += sprintf((char *) p, "\x1B?%c", xkey);
2673 p += sprintf((char *) p, "\x1BO%c", xkey);
2678 if (wParam == VK_BACK && shift_state == 0) { /* Backspace */
2679 *p++ = (cfg.bksp_is_delete ? 0x7F : 0x08);
2683 if (wParam == VK_TAB && shift_state == 1) { /* Shift tab */
2689 if (wParam == VK_SPACE && shift_state == 2) { /* Ctrl-Space */
2693 if (wParam == VK_SPACE && shift_state == 3) { /* Ctrl-Shift-Space */
2697 if (wParam == VK_CANCEL && shift_state == 2) { /* Ctrl-Break */
2702 if (wParam == VK_PAUSE) { /* Break/Pause */
2707 /* Control-2 to Control-8 are special */
2708 if (shift_state == 2 && wParam >= '2' && wParam <= '8') {
2709 *p++ = "\000\033\034\035\036\037\177"[wParam - '2'];
2712 if (shift_state == 2 && wParam == 0xBD) {
2716 if (shift_state == 2 && wParam == 0xDF) {
2720 if (shift_state == 0 && wParam == VK_RETURN && cr_lf_return) {
2727 * Next, all the keys that do tilde codes. (ESC '[' nn '~',
2728 * for integer decimal nn.)
2730 * We also deal with the weird ones here. Linux VCs replace F1
2731 * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
2732 * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
2738 code = (keystate[VK_SHIFT] & 0x80 ? 23 : 11);
2741 code = (keystate[VK_SHIFT] & 0x80 ? 24 : 12);
2744 code = (keystate[VK_SHIFT] & 0x80 ? 25 : 13);
2747 code = (keystate[VK_SHIFT] & 0x80 ? 26 : 14);
2750 code = (keystate[VK_SHIFT] & 0x80 ? 28 : 15);
2753 code = (keystate[VK_SHIFT] & 0x80 ? 29 : 17);
2756 code = (keystate[VK_SHIFT] & 0x80 ? 31 : 18);
2759 code = (keystate[VK_SHIFT] & 0x80 ? 32 : 19);
2762 code = (keystate[VK_SHIFT] & 0x80 ? 33 : 20);
2765 code = (keystate[VK_SHIFT] & 0x80 ? 34 : 21);
2816 /* Reorder edit keys to physical order */
2817 if (cfg.funky_type == 3 && code <= 6)
2818 code = "\0\2\1\4\5\3\6"[code];
2820 if (vt52_mode && code > 0 && code <= 6) {
2821 p += sprintf((char *) p, "\x1B%c", " HLMEIG"[code]);
2825 if (cfg.funky_type == 5 && /* SCO function keys */
2826 code >= 11 && code <= 34) {
2827 char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
2830 case VK_F1: index = 0; break;
2831 case VK_F2: index = 1; break;
2832 case VK_F3: index = 2; break;
2833 case VK_F4: index = 3; break;
2834 case VK_F5: index = 4; break;
2835 case VK_F6: index = 5; break;
2836 case VK_F7: index = 6; break;
2837 case VK_F8: index = 7; break;
2838 case VK_F9: index = 8; break;
2839 case VK_F10: index = 9; break;
2840 case VK_F11: index = 10; break;
2841 case VK_F12: index = 11; break;
2843 if (keystate[VK_SHIFT] & 0x80) index += 12;
2844 if (keystate[VK_CONTROL] & 0x80) index += 24;
2845 p += sprintf((char *) p, "\x1B[%c", codes[index]);
2848 if (cfg.funky_type == 5 && /* SCO small keypad */
2849 code >= 1 && code <= 6) {
2850 char codes[] = "HL.FIG";
2854 p += sprintf((char *) p, "\x1B[%c", codes[code-1]);
2858 if ((vt52_mode || cfg.funky_type == 4) && code >= 11 && code <= 24) {
2865 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11 - offt);
2868 sprintf((char *) p, "\x1BO%c", code + 'P' - 11 - offt);
2871 if (cfg.funky_type == 1 && code >= 11 && code <= 15) {
2872 p += sprintf((char *) p, "\x1B[[%c", code + 'A' - 11);
2875 if (cfg.funky_type == 2 && code >= 11 && code <= 14) {
2877 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11);
2879 p += sprintf((char *) p, "\x1BO%c", code + 'P' - 11);
2882 if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
2883 p += sprintf((char *) p, code == 1 ? "\x1B[H" : "\x1BOw");
2887 p += sprintf((char *) p, "\x1B[%d~", code);
2892 * Now the remaining keys (arrows and Keypad 5. Keypad 5 for
2893 * some reason seems to send VK_CLEAR to Windows...).
2916 p += sprintf((char *) p, "\x1B%c", xkey);
2918 int app_flg = (app_cursor_keys && !cfg.no_applic_c);
2919 /* VT100 & VT102 manuals both state the app cursor keys
2920 * only work if the app keypad is on.
2922 if (!app_keypad_keys)
2924 /* Useful mapping of Ctrl-arrows */
2925 if (shift_state == 2)
2929 p += sprintf((char *) p, "\x1BO%c", xkey);
2931 p += sprintf((char *) p, "\x1B[%c", xkey);
2938 * Finally, deal with Return ourselves. (Win95 seems to
2939 * foul it up when Alt is pressed, for some reason.)
2941 if (wParam == VK_RETURN) { /* Return */
2947 if (left_alt && wParam >= VK_NUMPAD0 && wParam <= VK_NUMPAD9)
2948 alt_sum = alt_sum * 10 + wParam - VK_NUMPAD0;
2953 /* Okay we've done everything interesting; let windows deal with
2954 * the boring stuff */
2956 r = ToAsciiEx(wParam, scan, keystate, keys, 0, kbd_layout);
2957 #ifdef SHOW_TOASCII_RESULT
2958 if (r == 1 && !key_down) {
2960 if (in_utf || dbcs_screenfont)
2961 debug((", (U+%04x)", alt_sum));
2963 debug((", LCH(%d)", alt_sum));
2965 debug((", ACH(%d)", keys[0]));
2970 for (r1 = 0; r1 < r; r1++) {
2971 debug(("%s%d", r1 ? "," : "", keys[r1]));
2979 for (i = 0; i < r; i++) {
2980 unsigned char ch = (unsigned char) keys[i];
2982 if (compose_state == 2 && (ch & 0x80) == 0 && ch > ' ') {
2987 if (compose_state == 3 && (ch & 0x80) == 0 && ch > ' ') {
2991 if ((nc = check_compose(compose_char, ch)) == -1) {
2992 MessageBeep(MB_ICONHAND);
2996 luni_send(&keybuf, 1);
3004 if (in_utf || dbcs_screenfont) {
3006 luni_send(&keybuf, 1);
3008 ch = (char) alt_sum;
3010 * We need not bother about stdin
3011 * backlogs here, because in GUI PuTTY
3012 * we can't do anything about it
3013 * anyway; there's no means of asking
3014 * Windows to hold off on KEYDOWN
3015 * messages. We _have_ to buffer
3016 * everything we're sent.
3022 lpage_send(kbd_codepage, &ch, 1);
3024 static char cbuf[] = "\033 ";
3026 lpage_send(kbd_codepage, cbuf + !left_alt,
3031 /* This is so the ALT-Numpad and dead keys work correctly. */
3036 /* If we're definitly not building up an ALT-54321 then clear it */
3039 /* If we will be using alt_sum fix the 256s */
3040 else if (keys[0] && (in_utf || dbcs_screenfont))
3044 /* ALT alone may or may not want to bring up the System menu */
3045 if (wParam == VK_MENU) {
3047 if (message == WM_SYSKEYDOWN)
3049 else if (message == WM_SYSKEYUP && alt_state)
3050 PostMessage(hwnd, WM_CHAR, ' ', 0);
3051 if (message == WM_SYSKEYUP)
3061 void set_title(char *title)
3064 window_name = smalloc(1 + strlen(title));
3065 strcpy(window_name, title);
3066 if (cfg.win_name_always || !IsIconic(hwnd))
3067 SetWindowText(hwnd, title);
3070 void set_icon(char *title)
3073 icon_name = smalloc(1 + strlen(title));
3074 strcpy(icon_name, title);
3075 if (!cfg.win_name_always && IsIconic(hwnd))
3076 SetWindowText(hwnd, title);
3079 void set_sbar(int total, int start, int page)
3086 si.cbSize = sizeof(si);
3087 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
3089 si.nMax = total - 1;
3093 SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
3096 Context get_ctx(void)
3102 SelectPalette(hdc, pal, FALSE);
3108 void free_ctx(Context ctx)
3110 SelectPalette(ctx, GetStockObject(DEFAULT_PALETTE), FALSE);
3111 ReleaseDC(hwnd, ctx);
3114 static void real_palette_set(int n, int r, int g, int b)
3117 logpal->palPalEntry[n].peRed = r;
3118 logpal->palPalEntry[n].peGreen = g;
3119 logpal->palPalEntry[n].peBlue = b;
3120 logpal->palPalEntry[n].peFlags = PC_NOCOLLAPSE;
3121 colours[n] = PALETTERGB(r, g, b);
3122 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3124 colours[n] = RGB(r, g, b);
3127 void palette_set(int n, int r, int g, int b)
3129 static const int first[21] = {
3130 0, 2, 4, 6, 8, 10, 12, 14,
3131 1, 3, 5, 7, 9, 11, 13, 15,
3134 real_palette_set(first[n], r, g, b);
3136 real_palette_set(first[n] + 1, r, g, b);
3138 HDC hdc = get_ctx();
3139 UnrealizeObject(pal);
3140 RealizePalette(hdc);
3145 void palette_reset(void)
3149 for (i = 0; i < NCOLOURS; i++) {
3151 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
3152 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
3153 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
3154 logpal->palPalEntry[i].peFlags = 0;
3155 colours[i] = PALETTERGB(defpal[i].rgbtRed,
3156 defpal[i].rgbtGreen,
3157 defpal[i].rgbtBlue);
3159 colours[i] = RGB(defpal[i].rgbtRed,
3160 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
3165 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3167 RealizePalette(hdc);
3172 void write_aclip(char *data, int len, int must_deselect)
3177 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len + 1);
3180 lock = GlobalLock(clipdata);
3183 memcpy(lock, data, len);
3184 ((unsigned char *) lock)[len] = 0;
3185 GlobalUnlock(clipdata);
3188 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
3190 if (OpenClipboard(hwnd)) {
3192 SetClipboardData(CF_TEXT, clipdata);
3195 GlobalFree(clipdata);
3198 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
3202 * Note: unlike write_aclip() this will not append a nul.
3204 void write_clip(wchar_t * data, int len, int must_deselect)
3211 len2 = WideCharToMultiByte(CP_ACP, 0, data, len, 0, 0, NULL, NULL);
3213 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE,
3214 len * sizeof(wchar_t));
3215 clipdata2 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len2);
3217 if (!clipdata || !clipdata2) {
3219 GlobalFree(clipdata);
3221 GlobalFree(clipdata2);
3224 if (!(lock = GlobalLock(clipdata)))
3226 if (!(lock2 = GlobalLock(clipdata2)))
3229 memcpy(lock, data, len * sizeof(wchar_t));
3230 WideCharToMultiByte(CP_ACP, 0, data, len, lock2, len2, NULL, NULL);
3232 GlobalUnlock(clipdata);
3233 GlobalUnlock(clipdata2);
3236 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
3238 if (OpenClipboard(hwnd)) {
3240 SetClipboardData(CF_UNICODETEXT, clipdata);
3241 SetClipboardData(CF_TEXT, clipdata2);
3244 GlobalFree(clipdata);
3245 GlobalFree(clipdata2);
3249 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
3252 void get_clip(wchar_t ** p, int *len)
3254 static HGLOBAL clipdata = NULL;
3255 static wchar_t *converted = 0;
3264 GlobalUnlock(clipdata);
3267 } else if (OpenClipboard(NULL)) {
3268 if ((clipdata = GetClipboardData(CF_UNICODETEXT))) {
3270 *p = GlobalLock(clipdata);
3272 for (p2 = *p; *p2; p2++);
3276 } else if ( (clipdata = GetClipboardData(CF_TEXT)) ) {
3280 s = GlobalLock(clipdata);
3281 i = MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, 0, 0);
3282 *p = converted = smalloc(i * sizeof(wchar_t));
3283 MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, converted, i);
3296 * Move `lines' lines from position `from' to position `to' in the
3299 void optimised_move(int to, int from, int lines)
3304 min = (to < from ? to : from);
3305 max = to + from - min;
3308 r.right = cols * font_width;
3309 r.top = min * font_height;
3310 r.bottom = (max + lines) * font_height;
3311 ScrollWindow(hwnd, 0, (to - from) * font_height, &r, &r);
3316 * Print a message box and perform a fatal exit.
3318 void fatalbox(char *fmt, ...)
3324 vsprintf(stuff, fmt, ap);
3326 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
3331 * Manage window caption / taskbar flashing, if enabled.
3332 * 0 = stop, 1 = maintain, 2 = start
3334 static void flash_window(int mode)
3336 static long last_flash = 0;
3337 static int flashing = 0;
3338 if ((mode == 0) || (cfg.beep_ind == B_IND_DISABLED)) {
3341 FlashWindow(hwnd, FALSE);
3345 } else if (mode == 2) {
3348 last_flash = GetTickCount();
3350 FlashWindow(hwnd, TRUE);
3353 } else if ((mode == 1) && (cfg.beep_ind == B_IND_FLASH)) {
3356 long now = GetTickCount();
3357 long fdiff = now - last_flash;
3358 if (fdiff < 0 || fdiff > 450) {
3360 FlashWindow(hwnd, TRUE); /* toggle */
3371 if (mode == BELL_DEFAULT) {
3373 * For MessageBeep style bells, we want to be careful of
3374 * timing, because they don't have the nice property of
3375 * PlaySound bells that each one cancels the previous
3376 * active one. So we limit the rate to one per 50ms or so.
3378 static long lastbeep = 0;
3381 beepdiff = GetTickCount() - lastbeep;
3382 if (beepdiff >= 0 && beepdiff < 50)
3386 * The above MessageBeep call takes time, so we record the
3387 * time _after_ it finishes rather than before it starts.
3389 lastbeep = GetTickCount();
3390 } else if (mode == BELL_WAVEFILE) {
3391 if (!PlaySound(cfg.bell_wavefile, NULL, SND_ASYNC | SND_FILENAME)) {
3392 char buf[sizeof(cfg.bell_wavefile) + 80];
3393 sprintf(buf, "Unable to play sound file\n%s\n"
3394 "Using default sound instead", cfg.bell_wavefile);
3395 MessageBox(hwnd, buf, "PuTTY Sound Error",
3396 MB_OK | MB_ICONEXCLAMATION);
3397 cfg.beep = BELL_DEFAULT;
3400 /* Otherwise, either visual bell or disabled; do nothing here */
3402 flash_window(2); /* start */