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 && !(cfg.mouse_override && shift)) {
1133 lastbtn = MBT_NOTHING;
1134 term_mouse(b, MA_CLICK, x, y, shift, ctrl);
1138 if (lastbtn == b && thistime - lasttime < dbltime) {
1139 lastact = (lastact == MA_CLICK ? MA_2CLK :
1140 lastact == MA_2CLK ? MA_3CLK :
1141 lastact == MA_3CLK ? MA_CLICK : MA_NOTHING);
1146 if (lastact != MA_NOTHING)
1147 term_mouse(b, lastact, x, y, shift, ctrl);
1148 lasttime = thistime;
1152 * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT)
1153 * into a cooked one (SELECT, EXTEND, PASTE).
1155 Mouse_Button translate_button(Mouse_Button button)
1157 if (button == MBT_LEFT)
1159 if (button == MBT_MIDDLE)
1160 return cfg.mouse_is_xterm ? MBT_PASTE : MBT_EXTEND;
1161 if (button == MBT_RIGHT)
1162 return cfg.mouse_is_xterm ? MBT_EXTEND : MBT_PASTE;
1163 return 0; /* shouldn't happen */
1166 static void show_mouseptr(int show)
1168 static int cursor_visible = 1;
1169 if (!cfg.hide_mouseptr) /* override if this feature disabled */
1171 if (cursor_visible && !show)
1173 else if (!cursor_visible && show)
1175 cursor_visible = show;
1178 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1179 WPARAM wParam, LPARAM lParam)
1182 static int ignore_size = FALSE;
1183 static int ignore_clip = FALSE;
1184 static int just_reconfigged = FALSE;
1185 static int resizing = FALSE;
1186 static int need_backend_resize = FALSE;
1187 static int defered_resize = FALSE;
1191 if (pending_netevent)
1192 enact_pending_netevent();
1199 if (cfg.ping_interval > 0) {
1202 if (now - last_movement > cfg.ping_interval) {
1203 back->special(TS_PING);
1204 last_movement = now;
1212 if (!cfg.warn_on_close || session_closed ||
1214 "Are you sure you want to close this session?",
1215 "PuTTY Exit Confirmation",
1216 MB_ICONWARNING | MB_OKCANCEL) == IDOK)
1217 DestroyWindow(hwnd);
1224 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1236 PROCESS_INFORMATION pi;
1237 HANDLE filemap = NULL;
1239 if (wParam == IDM_DUPSESS) {
1241 * Allocate a file-mapping memory chunk for the
1244 SECURITY_ATTRIBUTES sa;
1247 sa.nLength = sizeof(sa);
1248 sa.lpSecurityDescriptor = NULL;
1249 sa.bInheritHandle = TRUE;
1250 filemap = CreateFileMapping((HANDLE) 0xFFFFFFFF,
1253 0, sizeof(Config), NULL);
1255 p = (Config *) MapViewOfFile(filemap,
1257 0, 0, sizeof(Config));
1259 *p = cfg; /* structure copy */
1263 sprintf(c, "putty &%p", filemap);
1265 } else if (wParam == IDM_SAVEDSESS) {
1267 sessions[(lParam - IDM_SAVED_MIN) / 16];
1268 cl = smalloc(16 + strlen(session)); /* 8, but play safe */
1270 cl = NULL; /* not a very important failure mode */
1272 sprintf(cl, "putty @%s", session);
1278 GetModuleFileName(NULL, b, sizeof(b) - 1);
1280 si.lpReserved = NULL;
1281 si.lpDesktop = NULL;
1285 si.lpReserved2 = NULL;
1286 CreateProcess(b, cl, NULL, NULL, TRUE,
1287 NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
1290 CloseHandle(filemap);
1297 int prev_alwaysontop = cfg.alwaysontop;
1298 int prev_sunken_edge = cfg.sunken_edge;
1299 char oldlogfile[FILENAME_MAX];
1301 int need_setwpos = FALSE;
1302 int old_fwidth, old_fheight;
1304 strcpy(oldlogfile, cfg.logfilename);
1305 oldlogtype = cfg.logtype;
1306 old_fwidth = font_width;
1307 old_fheight = font_height;
1308 GetWindowText(hwnd, cfg.wintitle, sizeof(cfg.wintitle));
1310 if (!do_reconfig(hwnd))
1313 if (strcmp(oldlogfile, cfg.logfilename) ||
1314 oldlogtype != cfg.logtype) {
1315 logfclose(); /* reset logging */
1319 just_reconfigged = TRUE;
1321 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
1322 und_mode = UND_FONT;
1326 * Flush the line discipline's edit buffer in the
1327 * case where local editing has just been disabled.
1329 ldisc_send(NULL, 0);
1337 /* Enable or disable the scroll bar, etc */
1339 LONG nflg, flag = GetWindowLong(hwnd, GWL_STYLE);
1340 LONG nexflag, exflag =
1341 GetWindowLong(hwnd, GWL_EXSTYLE);
1344 if (cfg.alwaysontop != prev_alwaysontop) {
1345 if (cfg.alwaysontop) {
1346 nexflag |= WS_EX_TOPMOST;
1347 SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
1348 SWP_NOMOVE | SWP_NOSIZE);
1350 nexflag &= ~(WS_EX_TOPMOST);
1351 SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0,
1352 SWP_NOMOVE | SWP_NOSIZE);
1355 if (cfg.sunken_edge)
1356 nexflag |= WS_EX_CLIENTEDGE;
1358 nexflag &= ~(WS_EX_CLIENTEDGE);
1364 nflg &= ~WS_VSCROLL;
1366 nflg &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
1368 nflg |= (WS_THICKFRAME | WS_MAXIMIZEBOX);
1370 if (nflg != flag || nexflag != exflag) {
1374 SetWindowLong(hwnd, GWL_STYLE, nflg);
1375 if (nexflag != exflag)
1376 SetWindowLong(hwnd, GWL_EXSTYLE, nexflag);
1378 SendMessage(hwnd, WM_IGNORE_SIZE, 0, 0);
1380 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
1381 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1382 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER
1383 | SWP_FRAMECHANGED);
1385 GetWindowRect(hwnd, &wr);
1386 GetClientRect(hwnd, &cr);
1388 wr.right - wr.left - cr.right + cr.left;
1390 wr.bottom - wr.top - cr.bottom + cr.top;
1391 need_setwpos = TRUE;
1395 if (cfg.height != rows ||
1396 cfg.width != cols ||
1397 old_fwidth != font_width ||
1398 old_fheight != font_height ||
1399 cfg.savelines != savelines ||
1400 cfg.sunken_edge != prev_sunken_edge)
1401 need_setwpos = TRUE;
1403 if (IsZoomed(hwnd)) {
1407 defered_resize = TRUE;
1409 GetClientRect(hwnd, &cr);
1410 w = cr.right - cr.left;
1411 h = cr.bottom - cr.top;
1415 h = h / font_height;
1419 term_size(h, w, cfg.savelines);
1420 InvalidateRect(hwnd, NULL, TRUE);
1423 term_size(cfg.height, cfg.width, cfg.savelines);
1424 InvalidateRect(hwnd, NULL, TRUE);
1426 SetWindowPos(hwnd, NULL, 0, 0,
1427 extra_width + font_width * cfg.width,
1429 font_height * cfg.height,
1430 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1431 SWP_NOMOVE | SWP_NOZORDER);
1435 if (cfg.locksize && IsZoomed(hwnd))
1437 set_title(cfg.wintitle);
1438 if (IsIconic(hwnd)) {
1440 cfg.win_name_always ? window_name :
1455 back->special(TS_AYT);
1458 back->special(TS_BRK);
1461 back->special(TS_SYNCH);
1464 back->special(TS_EC);
1467 back->special(TS_EL);
1470 back->special(TS_GA);
1473 back->special(TS_NOP);
1476 back->special(TS_ABORT);
1479 back->special(TS_AO);
1482 back->special(TS_IP);
1485 back->special(TS_SUSP);
1488 back->special(TS_EOR);
1491 back->special(TS_EOF);
1498 * We get this if the System menu has been activated.
1499 * This might happen from within TranslateKey, in which
1500 * case it really wants to be followed by a `space'
1501 * character to actually _bring the menu up_ rather
1502 * than just sitting there in `ready to appear' state.
1505 PostMessage(hwnd, WM_CHAR, ' ', 0);
1508 if (wParam >= IDM_SAVED_MIN && wParam <= IDM_SAVED_MAX) {
1509 SendMessage(hwnd, WM_SYSCOMMAND, IDM_SAVEDSESS, wParam);
1514 #define X_POS(l) ((int)(short)LOWORD(l))
1515 #define Y_POS(l) ((int)(short)HIWORD(l))
1517 #define TO_CHR_X(x) (((x)<0 ? (x)-font_width+1 : (x)) / font_width)
1518 #define TO_CHR_Y(y) (((y)<0 ? (y)-font_height+1: (y)) / font_height)
1519 #define WHEEL_DELTA 120
1522 wheel_accumulator += (short) HIWORD(wParam);
1523 wParam = LOWORD(wParam);
1525 /* process events when the threshold is reached */
1526 while (abs(wheel_accumulator) >= WHEEL_DELTA) {
1529 /* reduce amount for next time */
1530 if (wheel_accumulator > 0) {
1532 wheel_accumulator -= WHEEL_DELTA;
1533 } else if (wheel_accumulator < 0) {
1535 wheel_accumulator += WHEEL_DELTA;
1539 if (send_raw_mouse) {
1540 /* send a mouse-down followed by a mouse up */
1543 TO_CHR_X(X_POS(lParam)),
1544 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
1545 wParam & MK_CONTROL);
1546 term_mouse(b, MA_RELEASE, TO_CHR_X(X_POS(lParam)),
1547 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
1548 wParam & MK_CONTROL);
1550 /* trigger a scroll */
1552 b == MBT_WHEEL_UP ? -rows / 2 : rows / 2);
1557 case WM_LBUTTONDOWN:
1558 case WM_MBUTTONDOWN:
1559 case WM_RBUTTONDOWN:
1566 case WM_LBUTTONDOWN:
1570 case WM_MBUTTONDOWN:
1571 button = MBT_MIDDLE;
1574 case WM_RBUTTONDOWN:
1583 button = MBT_MIDDLE;
1591 button = press = 0; /* shouldn't happen */
1596 TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam)),
1597 wParam & MK_SHIFT, wParam & MK_CONTROL);
1600 term_mouse(button, MA_RELEASE,
1601 TO_CHR_X(X_POS(lParam)),
1602 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
1603 wParam & MK_CONTROL);
1611 * Add the mouse position and message time to the random
1614 noise_ultralight(lParam);
1616 if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)) {
1618 if (wParam & MK_LBUTTON)
1620 else if (wParam & MK_MBUTTON)
1624 term_mouse(b, MA_DRAG, TO_CHR_X(X_POS(lParam)),
1625 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
1626 wParam & MK_CONTROL);
1629 case WM_NCMOUSEMOVE:
1631 noise_ultralight(lParam);
1633 case WM_IGNORE_CLIP:
1634 ignore_clip = wParam; /* don't panic on DESTROYCLIPBOARD */
1636 case WM_DESTROYCLIPBOARD:
1639 ignore_clip = FALSE;
1645 hdc = BeginPaint(hwnd, &p);
1647 SelectPalette(hdc, pal, TRUE);
1648 RealizePalette(hdc);
1650 term_paint(hdc, p.rcPaint.left, p.rcPaint.top,
1651 p.rcPaint.right, p.rcPaint.bottom);
1652 SelectObject(hdc, GetStockObject(SYSTEM_FONT));
1653 SelectObject(hdc, GetStockObject(WHITE_PEN));
1659 /* Notice we can get multiple netevents, FD_READ, FD_WRITE etc
1660 * but the only one that's likely to try to overload us is FD_READ.
1661 * This means buffering just one is fine.
1663 if (pending_netevent)
1664 enact_pending_netevent();
1666 pending_netevent = TRUE;
1667 pend_netevent_wParam = wParam;
1668 pend_netevent_lParam = lParam;
1669 time(&last_movement);
1673 CreateCaret(hwnd, caretbm, font_width, font_height);
1675 flash_window(0); /* stop */
1687 case WM_IGNORE_SIZE:
1688 ignore_size = TRUE; /* don't panic on next WM_SIZE msg */
1690 case WM_ENTERSIZEMOVE:
1693 need_backend_resize = FALSE;
1695 case WM_EXITSIZEMOVE:
1698 if (need_backend_resize)
1703 int width, height, w, h, ew, eh;
1704 LPRECT r = (LPRECT) lParam;
1706 width = r->right - r->left - extra_width;
1707 height = r->bottom - r->top - extra_height;
1708 w = (width + font_width / 2) / font_width;
1711 h = (height + font_height / 2) / font_height;
1714 UpdateSizeTip(hwnd, w, h);
1715 ew = width - w * font_width;
1716 eh = height - h * font_height;
1718 if (wParam == WMSZ_LEFT ||
1719 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
1725 if (wParam == WMSZ_TOP ||
1726 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
1736 /* break; (never reached) */
1738 if (wParam == SIZE_MINIMIZED) {
1740 cfg.win_name_always ? window_name : icon_name);
1743 if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
1744 SetWindowText(hwnd, window_name);
1746 int width, height, w, h;
1747 #if 0 /* we have fixed this using WM_SIZING now */
1751 width = LOWORD(lParam);
1752 height = HIWORD(lParam);
1753 w = width / font_width;
1756 h = height / font_height;
1759 #if 0 /* we have fixed this using WM_SIZING now */
1760 ew = width - w * font_width;
1761 eh = height - h * font_height;
1762 if (ew != 0 || eh != 0) {
1764 GetWindowRect(hwnd, &r);
1765 SendMessage(hwnd, WM_IGNORE_SIZE, 0, 0);
1766 SetWindowPos(hwnd, NULL, 0, 0,
1767 r.right - r.left - ew, r.bottom - r.top - eh,
1768 SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER);
1771 if (w != cols || h != rows || just_reconfigged) {
1773 term_size(h, w, cfg.savelines);
1775 * Don't call back->size in mid-resize. (To prevent
1776 * massive numbers of resize events getting sent
1777 * down the connection during an NT opaque drag.)
1782 need_backend_resize = TRUE;
1786 just_reconfigged = FALSE;
1789 if (wParam == SIZE_RESTORED && defered_resize) {
1790 defered_resize = FALSE;
1791 SetWindowPos(hwnd, NULL, 0, 0,
1792 extra_width + font_width * cfg.width,
1793 extra_height + font_height * cfg.height,
1794 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1795 SWP_NOMOVE | SWP_NOZORDER);
1797 ignore_size = FALSE;
1800 switch (LOWORD(wParam)) {
1814 term_scroll(0, +rows / 2);
1817 term_scroll(0, -rows / 2);
1819 case SB_THUMBPOSITION:
1821 term_scroll(1, HIWORD(wParam));
1825 case WM_PALETTECHANGED:
1826 if ((HWND) wParam != hwnd && pal != NULL) {
1827 HDC hdc = get_ctx();
1829 if (RealizePalette(hdc) > 0)
1835 case WM_QUERYNEWPALETTE:
1837 HDC hdc = get_ctx();
1839 if (RealizePalette(hdc) > 0)
1851 * Add the scan code and keypress timing to the random
1854 noise_ultralight(lParam);
1857 * We don't do TranslateMessage since it disassociates the
1858 * resulting CHAR message from the KEYDOWN that sparked it,
1859 * which we occasionally don't want. Instead, we process
1860 * KEYDOWN, and call the Win32 translator functions so that
1861 * we get the translations under _our_ control.
1864 unsigned char buf[20];
1867 if (wParam == VK_PROCESSKEY) {
1870 m.message = WM_KEYDOWN;
1872 m.lParam = lParam & 0xdfff;
1873 TranslateMessage(&m);
1875 len = TranslateKey(message, wParam, lParam, buf);
1877 return DefWindowProc(hwnd, message, wParam, lParam);
1881 * We need not bother about stdin backlogs
1882 * here, because in GUI PuTTY we can't do
1883 * anything about it anyway; there's no means
1884 * of asking Windows to hold off on KEYDOWN
1885 * messages. We _have_ to buffer everything
1888 ldisc_send(buf, len);
1894 case WM_INPUTLANGCHANGE:
1896 /* wParam == Font number */
1897 /* lParam == Locale */
1899 HKL NewInputLocale = (HKL) lParam;
1901 // lParam == GetKeyboardLayout(0);
1903 GetLocaleInfo(LOWORD(NewInputLocale),
1904 LOCALE_IDEFAULTANSICODEPAGE, lbuf, sizeof(lbuf));
1906 kbd_codepage = atoi(lbuf);
1909 case WM_IME_COMPOSITION:
1915 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ||
1916 osVersion.dwPlatformId == VER_PLATFORM_WIN32s) break; /* no Unicode */
1918 if ((lParam & GCS_RESULTSTR) == 0) /* Composition unfinished. */
1919 break; /* fall back to DefWindowProc */
1921 hIMC = ImmGetContext(hwnd);
1922 n = ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, NULL, 0);
1925 buff = (char*) smalloc(n);
1926 ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, buff, n);
1927 luni_send((unsigned short *)buff, n / 2);
1930 ImmReleaseContext(hwnd, hIMC);
1935 if (wParam & 0xFF00) {
1936 unsigned char buf[2];
1939 buf[0] = wParam >> 8;
1940 lpage_send(kbd_codepage, buf, 2);
1942 char c = (unsigned char) wParam;
1943 lpage_send(kbd_codepage, &c, 1);
1949 * Nevertheless, we are prepared to deal with WM_CHAR
1950 * messages, should they crop up. So if someone wants to
1951 * post the things to us as part of a macro manoeuvre,
1952 * we're ready to cope.
1955 char c = (unsigned char)wParam;
1956 lpage_send(CP_ACP, &c, 1);
1960 if (send_raw_mouse) {
1961 SetCursor(LoadCursor(NULL, IDC_ARROW));
1966 return DefWindowProc(hwnd, message, wParam, lParam);
1970 * Move the system caret. (We maintain one, even though it's
1971 * invisible, for the benefit of blind people: apparently some
1972 * helper software tracks the system caret, so we should arrange to
1975 void sys_cursor(int x, int y)
1980 if (!has_focus) return;
1982 SetCaretPos(x * font_width, y * font_height);
1984 /* IMM calls on Win98 and beyond only */
1985 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32s) return; /* 3.11 */
1987 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS &&
1988 osVersion.dwMinorVersion == 0) return; /* 95 */
1990 /* we should have the IMM functions */
1991 hIMC = ImmGetContext(hwnd);
1992 cf.dwStyle = CFS_POINT;
1993 cf.ptCurrentPos.x = x * font_width;
1994 cf.ptCurrentPos.y = y * font_height;
1995 ImmSetCompositionWindow(hIMC, &cf);
1997 ImmReleaseContext(hwnd, hIMC);
2001 * Draw a line of text in the window, at given character
2002 * coordinates, in given attributes.
2004 * We are allowed to fiddle with the contents of `text'.
2006 void do_text(Context ctx, int x, int y, char *text, int len,
2007 unsigned long attr, int lattr)
2010 int nfg, nbg, nfont;
2013 int force_manual_underline = 0;
2014 int fnt_width = font_width * (1 + (lattr != LATTR_NORM));
2015 int char_width = fnt_width;
2016 int text_adjust = 0;
2017 static int *IpDx = 0, IpDxLEN = 0;
2019 if (attr & ATTR_WIDE)
2022 if (len > IpDxLEN || IpDx[0] != char_width) {
2024 if (len > IpDxLEN) {
2026 IpDx = smalloc((len + 16) * sizeof(int));
2027 IpDxLEN = (len + 16);
2029 for (i = 0; i < IpDxLEN; i++)
2030 IpDx[i] = char_width;
2036 if ((attr & TATTR_ACTCURS) && (cfg.cursor_type == 0 || big_cursor)) {
2037 attr &= ATTR_CUR_AND | (bold_mode != BOLD_COLOURS ? ATTR_BOLD : 0);
2038 attr ^= ATTR_CUR_XOR;
2042 if (cfg.vtmode == VT_POORMAN && lattr != LATTR_NORM) {
2043 /* Assume a poorman font is borken in other ways too. */
2053 nfont |= FONT_WIDE + FONT_HIGH;
2057 /* Special hack for the VT100 linedraw glyphs. */
2058 if ((attr & CSET_MASK) == 0x2300) {
2059 if (!dbcs_screenfont &&
2060 text[0] >= (char) 0xBA && text[0] <= (char) 0xBD) {
2061 switch ((unsigned char) (text[0])) {
2063 text_adjust = -2 * font_height / 5;
2066 text_adjust = -1 * font_height / 5;
2069 text_adjust = font_height / 5;
2072 text_adjust = 2 * font_height / 5;
2075 if (lattr == LATTR_TOP || lattr == LATTR_BOT)
2078 text[0] = (char) (unitab_xterm['q'] & CHAR_MASK);
2079 attr |= (unitab_xterm['q'] & CSET_MASK);
2080 if (attr & ATTR_UNDER) {
2081 attr &= ~ATTR_UNDER;
2082 force_manual_underline = 1;
2087 /* Anything left as an original character set is unprintable. */
2088 if (DIRECT_CHAR(attr)) {
2091 memset(text, 0xFF, len);
2095 if ((attr & CSET_MASK) == ATTR_OEMCP)
2098 nfg = 2 * ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
2099 nbg = 2 * ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
2100 if (bold_mode == BOLD_FONT && (attr & ATTR_BOLD))
2102 if (und_mode == UND_FONT && (attr & ATTR_UNDER))
2103 nfont |= FONT_UNDERLINE;
2104 another_font(nfont);
2105 if (!fonts[nfont]) {
2106 if (nfont & FONT_UNDERLINE)
2107 force_manual_underline = 1;
2108 /* Don't do the same for manual bold, it could be bad news. */
2110 nfont &= ~(FONT_BOLD | FONT_UNDERLINE);
2112 another_font(nfont);
2114 nfont = FONT_NORMAL;
2115 if (attr & ATTR_REVERSE) {
2120 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BOLD))
2122 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BLINK))
2126 SelectObject(hdc, fonts[nfont]);
2127 SetTextColor(hdc, fg);
2128 SetBkColor(hdc, bg);
2129 SetBkMode(hdc, OPAQUE);
2132 line_box.right = x + char_width * len;
2133 line_box.bottom = y + font_height;
2135 /* We're using a private area for direct to font. (512 chars.) */
2136 if (dbcs_screenfont && (attr & CSET_MASK) == ATTR_ACP) {
2137 /* Ho Hum, dbcs fonts are a PITA! */
2138 /* To display on W9x I have to convert to UCS */
2139 static wchar_t *uni_buf = 0;
2140 static int uni_len = 0;
2142 if (len > uni_len) {
2144 uni_buf = smalloc((uni_len = len) * sizeof(wchar_t));
2146 nlen = MultiByteToWideChar(font_codepage, MB_USEGLYPHCHARS,
2147 text, len, uni_buf, uni_len);
2153 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2154 ETO_CLIPPED | ETO_OPAQUE, &line_box, uni_buf, nlen, 0);
2155 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2156 SetBkMode(hdc, TRANSPARENT);
2157 ExtTextOutW(hdc, x - 1,
2158 y - font_height * (lattr ==
2159 LATTR_BOT) + text_adjust,
2160 ETO_CLIPPED, &line_box, uni_buf, nlen, 0);
2162 } else if (DIRECT_FONT(attr)) {
2164 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2165 ETO_CLIPPED | ETO_OPAQUE, &line_box, text, len, IpDx);
2166 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2167 SetBkMode(hdc, TRANSPARENT);
2169 /* GRR: This draws the character outside it's box and can leave
2170 * 'droppings' even with the clip box! I suppose I could loop it
2171 * one character at a time ... yuk.
2173 * Or ... I could do a test print with "W", and use +1 or -1 for this
2174 * shift depending on if the leftmost column is blank...
2176 ExtTextOut(hdc, x - 1,
2177 y - font_height * (lattr ==
2178 LATTR_BOT) + text_adjust,
2179 ETO_CLIPPED, &line_box, text, len, IpDx);
2182 /* And 'normal' unicode characters */
2183 static WCHAR *wbuf = NULL;
2184 static int wlen = 0;
2189 wbuf = smalloc(wlen * sizeof(WCHAR));
2191 for (i = 0; i < len; i++)
2192 wbuf[i] = (WCHAR) ((attr & CSET_MASK) + (text[i] & CHAR_MASK));
2195 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2196 ETO_CLIPPED | ETO_OPAQUE, &line_box, wbuf, len, IpDx);
2198 /* And the shadow bold hack. */
2199 if (bold_mode == BOLD_SHADOW) {
2200 SetBkMode(hdc, TRANSPARENT);
2201 ExtTextOutW(hdc, x - 1,
2202 y - font_height * (lattr ==
2203 LATTR_BOT) + text_adjust,
2204 ETO_CLIPPED, &line_box, wbuf, len, IpDx);
2207 if (lattr != LATTR_TOP && (force_manual_underline ||
2208 (und_mode == UND_LINE
2209 && (attr & ATTR_UNDER)))) {
2212 if (lattr == LATTR_BOT)
2213 dec = dec * 2 - font_height;
2215 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, fg));
2216 MoveToEx(hdc, x, y + dec, NULL);
2217 LineTo(hdc, x + len * char_width, y + dec);
2218 oldpen = SelectObject(hdc, oldpen);
2219 DeleteObject(oldpen);
2223 void do_cursor(Context ctx, int x, int y, char *text, int len,
2224 unsigned long attr, int lattr)
2230 int ctype = cfg.cursor_type;
2232 if ((attr & TATTR_ACTCURS) && (ctype == 0 || big_cursor)) {
2233 if (((attr & CSET_MASK) | (unsigned char) *text) != UCSWIDE) {
2234 do_text(ctx, x, y, text, len, attr, lattr);
2238 attr |= TATTR_RIGHTCURS;
2241 fnt_width = char_width = font_width * (1 + (lattr != LATTR_NORM));
2242 if (attr & ATTR_WIDE)
2247 if ((attr & TATTR_PASCURS) && (ctype == 0 || big_cursor)) {
2250 pts[0].x = pts[1].x = pts[4].x = x;
2251 pts[2].x = pts[3].x = x + char_width - 1;
2252 pts[0].y = pts[3].y = pts[4].y = y;
2253 pts[1].y = pts[2].y = y + font_height - 1;
2254 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2255 Polyline(hdc, pts, 5);
2256 oldpen = SelectObject(hdc, oldpen);
2257 DeleteObject(oldpen);
2258 } else if ((attr & (TATTR_ACTCURS | TATTR_PASCURS)) && ctype != 0) {
2259 int startx, starty, dx, dy, length, i;
2262 starty = y + descent;
2265 length = char_width;
2268 if (attr & TATTR_RIGHTCURS)
2269 xadjust = char_width - 1;
2270 startx = x + xadjust;
2274 length = font_height;
2276 if (attr & TATTR_ACTCURS) {
2279 SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2280 MoveToEx(hdc, startx, starty, NULL);
2281 LineTo(hdc, startx + dx * length, starty + dy * length);
2282 oldpen = SelectObject(hdc, oldpen);
2283 DeleteObject(oldpen);
2285 for (i = 0; i < length; i++) {
2287 SetPixel(hdc, startx, starty, colours[23]);
2297 * Translate a WM_(SYS)?KEY(UP|DOWN) message into a string of ASCII
2298 * codes. Returns number of bytes used or zero to drop the message
2299 * or -1 to forward the message to windows.
2301 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
2302 unsigned char *output)
2305 int scan, left_alt = 0, key_down, shift_state;
2307 unsigned char *p = output;
2308 static int alt_sum = 0;
2310 HKL kbd_layout = GetKeyboardLayout(0);
2312 static WORD keys[3];
2313 static int compose_char = 0;
2314 static WPARAM compose_key = 0;
2316 r = GetKeyboardState(keystate);
2318 memset(keystate, 0, sizeof(keystate));
2321 #define SHOW_TOASCII_RESULT
2322 { /* Tell us all about key events */
2323 static BYTE oldstate[256];
2324 static int first = 1;
2328 memcpy(oldstate, keystate, sizeof(oldstate));
2331 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT) {
2333 } else if ((HIWORD(lParam) & KF_UP)
2334 && scan == (HIWORD(lParam) & 0xFF)) {
2338 if (wParam >= VK_F1 && wParam <= VK_F20)
2339 debug(("K_F%d", wParam + 1 - VK_F1));
2352 debug(("VK_%02x", wParam));
2354 if (message == WM_SYSKEYDOWN || message == WM_SYSKEYUP)
2356 debug((", S%02x", scan = (HIWORD(lParam) & 0xFF)));
2358 ch = MapVirtualKeyEx(wParam, 2, kbd_layout);
2359 if (ch >= ' ' && ch <= '~')
2360 debug((", '%c'", ch));
2362 debug((", $%02x", ch));
2365 debug((", KB0=%02x", keys[0]));
2367 debug((", KB1=%02x", keys[1]));
2369 debug((", KB2=%02x", keys[2]));
2371 if ((keystate[VK_SHIFT] & 0x80) != 0)
2373 if ((keystate[VK_CONTROL] & 0x80) != 0)
2375 if ((HIWORD(lParam) & KF_EXTENDED))
2377 if ((HIWORD(lParam) & KF_UP))
2381 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT);
2382 else if ((HIWORD(lParam) & KF_UP))
2383 oldstate[wParam & 0xFF] ^= 0x80;
2385 oldstate[wParam & 0xFF] ^= 0x81;
2387 for (ch = 0; ch < 256; ch++)
2388 if (oldstate[ch] != keystate[ch])
2389 debug((", M%02x=%02x", ch, keystate[ch]));
2391 memcpy(oldstate, keystate, sizeof(oldstate));
2395 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED)) {
2396 keystate[VK_RMENU] = keystate[VK_MENU];
2400 /* Nastyness with NUMLock - Shift-NUMLock is left alone though */
2401 if ((cfg.funky_type == 3 ||
2402 (cfg.funky_type <= 1 && app_keypad_keys && !cfg.no_applic_k))
2403 && wParam == VK_NUMLOCK && !(keystate[VK_SHIFT] & 0x80)) {
2405 wParam = VK_EXECUTE;
2407 /* UnToggle NUMLock */
2408 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0)
2409 keystate[VK_NUMLOCK] ^= 1;
2412 /* And write back the 'adjusted' state */
2413 SetKeyboardState(keystate);
2416 /* Disable Auto repeat if required */
2417 if (repeat_off && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT)
2420 if ((HIWORD(lParam) & KF_ALTDOWN) && (keystate[VK_RMENU] & 0x80) == 0)
2423 key_down = ((HIWORD(lParam) & KF_UP) == 0);
2425 /* Make sure Ctrl-ALT is not the same as AltGr for ToAscii unless told. */
2426 if (left_alt && (keystate[VK_CONTROL] & 0x80)) {
2427 if (cfg.ctrlaltkeys)
2428 keystate[VK_MENU] = 0;
2430 keystate[VK_RMENU] = 0x80;
2435 scan = (HIWORD(lParam) & (KF_UP | KF_EXTENDED | 0xFF));
2436 shift_state = ((keystate[VK_SHIFT] & 0x80) != 0)
2437 + ((keystate[VK_CONTROL] & 0x80) != 0) * 2;
2439 /* Note if AltGr was pressed and if it was used as a compose key */
2440 if (!compose_state) {
2441 compose_key = 0x100;
2442 if (cfg.compose_key) {
2443 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED))
2444 compose_key = wParam;
2446 if (wParam == VK_APPS)
2447 compose_key = wParam;
2450 if (wParam == compose_key) {
2451 if (compose_state == 0
2452 && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0) compose_state =
2454 else if (compose_state == 1 && (HIWORD(lParam) & KF_UP))
2458 } else if (compose_state == 1 && wParam != VK_CONTROL)
2462 * Record that we pressed key so the scroll window can be reset, but
2463 * be careful to avoid Shift-UP/Down
2465 if (wParam != VK_SHIFT && wParam != VK_PRIOR && wParam != VK_NEXT) {
2469 /* Make sure we're not pasting */
2473 if (compose_state > 1 && left_alt)
2476 /* Sanitize the number pad if not using a PC NumPad */
2477 if (left_alt || (app_keypad_keys && !cfg.no_applic_k
2478 && cfg.funky_type != 2)
2479 || cfg.funky_type == 3 || cfg.nethack_keypad || compose_state) {
2480 if ((HIWORD(lParam) & KF_EXTENDED) == 0) {
2484 nParam = VK_NUMPAD0;
2487 nParam = VK_NUMPAD1;
2490 nParam = VK_NUMPAD2;
2493 nParam = VK_NUMPAD3;
2496 nParam = VK_NUMPAD4;
2499 nParam = VK_NUMPAD5;
2502 nParam = VK_NUMPAD6;
2505 nParam = VK_NUMPAD7;
2508 nParam = VK_NUMPAD8;
2511 nParam = VK_NUMPAD9;
2514 nParam = VK_DECIMAL;
2518 if (keystate[VK_NUMLOCK] & 1)
2525 /* If a key is pressed and AltGr is not active */
2526 if (key_down && (keystate[VK_RMENU] & 0x80) == 0 && !compose_state) {
2527 /* Okay, prepare for most alts then ... */
2531 /* Lets see if it's a pattern we know all about ... */
2532 if (wParam == VK_PRIOR && shift_state == 1) {
2533 SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0);
2536 if (wParam == VK_NEXT && shift_state == 1) {
2537 SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
2540 if (wParam == VK_INSERT && shift_state == 1) {
2544 if (left_alt && wParam == VK_F4 && cfg.alt_f4) {
2547 if (left_alt && wParam == VK_SPACE && cfg.alt_space) {
2548 SendMessage(hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0);
2551 /* Control-Numlock for app-keypad mode switch */
2552 if (wParam == VK_PAUSE && shift_state == 2) {
2553 app_keypad_keys ^= 1;
2557 /* Nethack keypad */
2558 if (cfg.nethack_keypad && !left_alt) {
2561 *p++ = shift_state ? 'B' : 'b';
2564 *p++ = shift_state ? 'J' : 'j';
2567 *p++ = shift_state ? 'N' : 'n';
2570 *p++ = shift_state ? 'H' : 'h';
2573 *p++ = shift_state ? '.' : '.';
2576 *p++ = shift_state ? 'L' : 'l';
2579 *p++ = shift_state ? 'Y' : 'y';
2582 *p++ = shift_state ? 'K' : 'k';
2585 *p++ = shift_state ? 'U' : 'u';
2590 /* Application Keypad */
2594 if (cfg.funky_type == 3 ||
2595 (cfg.funky_type <= 1 &&
2596 app_keypad_keys && !cfg.no_applic_k)) switch (wParam) {
2610 if (app_keypad_keys && !cfg.no_applic_k)
2647 if (cfg.funky_type == 2) {
2652 } else if (shift_state)
2659 if (cfg.funky_type == 2)
2663 if (cfg.funky_type == 2)
2667 if (cfg.funky_type == 2)
2672 if (HIWORD(lParam) & KF_EXTENDED)
2678 if (xkey >= 'P' && xkey <= 'S')
2679 p += sprintf((char *) p, "\x1B%c", xkey);
2681 p += sprintf((char *) p, "\x1B?%c", xkey);
2683 p += sprintf((char *) p, "\x1BO%c", xkey);
2688 if (wParam == VK_BACK && shift_state == 0) { /* Backspace */
2689 *p++ = (cfg.bksp_is_delete ? 0x7F : 0x08);
2693 if (wParam == VK_TAB && shift_state == 1) { /* Shift tab */
2699 if (wParam == VK_SPACE && shift_state == 2) { /* Ctrl-Space */
2703 if (wParam == VK_SPACE && shift_state == 3) { /* Ctrl-Shift-Space */
2707 if (wParam == VK_CANCEL && shift_state == 2) { /* Ctrl-Break */
2712 if (wParam == VK_PAUSE) { /* Break/Pause */
2717 /* Control-2 to Control-8 are special */
2718 if (shift_state == 2 && wParam >= '2' && wParam <= '8') {
2719 *p++ = "\000\033\034\035\036\037\177"[wParam - '2'];
2722 if (shift_state == 2 && wParam == 0xBD) {
2726 if (shift_state == 2 && wParam == 0xDF) {
2730 if (shift_state == 0 && wParam == VK_RETURN && cr_lf_return) {
2737 * Next, all the keys that do tilde codes. (ESC '[' nn '~',
2738 * for integer decimal nn.)
2740 * We also deal with the weird ones here. Linux VCs replace F1
2741 * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
2742 * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
2748 code = (keystate[VK_SHIFT] & 0x80 ? 23 : 11);
2751 code = (keystate[VK_SHIFT] & 0x80 ? 24 : 12);
2754 code = (keystate[VK_SHIFT] & 0x80 ? 25 : 13);
2757 code = (keystate[VK_SHIFT] & 0x80 ? 26 : 14);
2760 code = (keystate[VK_SHIFT] & 0x80 ? 28 : 15);
2763 code = (keystate[VK_SHIFT] & 0x80 ? 29 : 17);
2766 code = (keystate[VK_SHIFT] & 0x80 ? 31 : 18);
2769 code = (keystate[VK_SHIFT] & 0x80 ? 32 : 19);
2772 code = (keystate[VK_SHIFT] & 0x80 ? 33 : 20);
2775 code = (keystate[VK_SHIFT] & 0x80 ? 34 : 21);
2808 if ((shift_state&2) == 0) switch (wParam) {
2828 /* Reorder edit keys to physical order */
2829 if (cfg.funky_type == 3 && code <= 6)
2830 code = "\0\2\1\4\5\3\6"[code];
2832 if (vt52_mode && code > 0 && code <= 6) {
2833 p += sprintf((char *) p, "\x1B%c", " HLMEIG"[code]);
2837 if (cfg.funky_type == 5 && /* SCO function keys */
2838 code >= 11 && code <= 34) {
2839 char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
2842 case VK_F1: index = 0; break;
2843 case VK_F2: index = 1; break;
2844 case VK_F3: index = 2; break;
2845 case VK_F4: index = 3; break;
2846 case VK_F5: index = 4; break;
2847 case VK_F6: index = 5; break;
2848 case VK_F7: index = 6; break;
2849 case VK_F8: index = 7; break;
2850 case VK_F9: index = 8; break;
2851 case VK_F10: index = 9; break;
2852 case VK_F11: index = 10; break;
2853 case VK_F12: index = 11; break;
2855 if (keystate[VK_SHIFT] & 0x80) index += 12;
2856 if (keystate[VK_CONTROL] & 0x80) index += 24;
2857 p += sprintf((char *) p, "\x1B[%c", codes[index]);
2860 if (cfg.funky_type == 5 && /* SCO small keypad */
2861 code >= 1 && code <= 6) {
2862 char codes[] = "HL.FIG";
2866 p += sprintf((char *) p, "\x1B[%c", codes[code-1]);
2870 if ((vt52_mode || cfg.funky_type == 4) && code >= 11 && code <= 24) {
2877 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11 - offt);
2880 sprintf((char *) p, "\x1BO%c", code + 'P' - 11 - offt);
2883 if (cfg.funky_type == 1 && code >= 11 && code <= 15) {
2884 p += sprintf((char *) p, "\x1B[[%c", code + 'A' - 11);
2887 if (cfg.funky_type == 2 && code >= 11 && code <= 14) {
2889 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11);
2891 p += sprintf((char *) p, "\x1BO%c", code + 'P' - 11);
2894 if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
2895 p += sprintf((char *) p, code == 1 ? "\x1B[H" : "\x1BOw");
2899 p += sprintf((char *) p, "\x1B[%d~", code);
2904 * Now the remaining keys (arrows and Keypad 5. Keypad 5 for
2905 * some reason seems to send VK_CLEAR to Windows...).
2928 p += sprintf((char *) p, "\x1B%c", xkey);
2930 int app_flg = (app_cursor_keys && !cfg.no_applic_c);
2931 /* VT100 & VT102 manuals both state the app cursor keys
2932 * only work if the app keypad is on.
2934 if (!app_keypad_keys)
2936 /* Useful mapping of Ctrl-arrows */
2937 if (shift_state == 2)
2941 p += sprintf((char *) p, "\x1BO%c", xkey);
2943 p += sprintf((char *) p, "\x1B[%c", xkey);
2950 * Finally, deal with Return ourselves. (Win95 seems to
2951 * foul it up when Alt is pressed, for some reason.)
2953 if (wParam == VK_RETURN) { /* Return */
2959 if (left_alt && wParam >= VK_NUMPAD0 && wParam <= VK_NUMPAD9)
2960 alt_sum = alt_sum * 10 + wParam - VK_NUMPAD0;
2965 /* Okay we've done everything interesting; let windows deal with
2966 * the boring stuff */
2968 r = ToAsciiEx(wParam, scan, keystate, keys, 0, kbd_layout);
2969 #ifdef SHOW_TOASCII_RESULT
2970 if (r == 1 && !key_down) {
2972 if (in_utf || dbcs_screenfont)
2973 debug((", (U+%04x)", alt_sum));
2975 debug((", LCH(%d)", alt_sum));
2977 debug((", ACH(%d)", keys[0]));
2982 for (r1 = 0; r1 < r; r1++) {
2983 debug(("%s%d", r1 ? "," : "", keys[r1]));
2991 for (i = 0; i < r; i++) {
2992 unsigned char ch = (unsigned char) keys[i];
2994 if (compose_state == 2 && (ch & 0x80) == 0 && ch > ' ') {
2999 if (compose_state == 3 && (ch & 0x80) == 0 && ch > ' ') {
3003 if ((nc = check_compose(compose_char, ch)) == -1) {
3004 MessageBeep(MB_ICONHAND);
3008 luni_send(&keybuf, 1);
3016 if (in_utf || dbcs_screenfont) {
3018 luni_send(&keybuf, 1);
3020 ch = (char) alt_sum;
3022 * We need not bother about stdin
3023 * backlogs here, because in GUI PuTTY
3024 * we can't do anything about it
3025 * anyway; there's no means of asking
3026 * Windows to hold off on KEYDOWN
3027 * messages. We _have_ to buffer
3028 * everything we're sent.
3034 lpage_send(kbd_codepage, &ch, 1);
3036 static char cbuf[] = "\033 ";
3038 lpage_send(kbd_codepage, cbuf + !left_alt,
3044 /* This is so the ALT-Numpad and dead keys work correctly. */
3049 /* If we're definitly not building up an ALT-54321 then clear it */
3052 /* If we will be using alt_sum fix the 256s */
3053 else if (keys[0] && (in_utf || dbcs_screenfont))
3058 * ALT alone may or may not want to bring up the System menu.
3059 * If it's not meant to, we return 0 on presses or releases of
3060 * ALT, to show that we've swallowed the keystroke. Otherwise
3061 * we return -1, which means Windows will give the keystroke
3062 * its default handling (i.e. bring up the System menu).
3064 if (wParam == VK_MENU && !cfg.alt_only)
3070 void set_title(char *title)
3073 window_name = smalloc(1 + strlen(title));
3074 strcpy(window_name, title);
3075 if (cfg.win_name_always || !IsIconic(hwnd))
3076 SetWindowText(hwnd, title);
3079 void set_icon(char *title)
3082 icon_name = smalloc(1 + strlen(title));
3083 strcpy(icon_name, title);
3084 if (!cfg.win_name_always && IsIconic(hwnd))
3085 SetWindowText(hwnd, title);
3088 void set_sbar(int total, int start, int page)
3095 si.cbSize = sizeof(si);
3096 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
3098 si.nMax = total - 1;
3102 SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
3105 Context get_ctx(void)
3111 SelectPalette(hdc, pal, FALSE);
3117 void free_ctx(Context ctx)
3119 SelectPalette(ctx, GetStockObject(DEFAULT_PALETTE), FALSE);
3120 ReleaseDC(hwnd, ctx);
3123 static void real_palette_set(int n, int r, int g, int b)
3126 logpal->palPalEntry[n].peRed = r;
3127 logpal->palPalEntry[n].peGreen = g;
3128 logpal->palPalEntry[n].peBlue = b;
3129 logpal->palPalEntry[n].peFlags = PC_NOCOLLAPSE;
3130 colours[n] = PALETTERGB(r, g, b);
3131 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3133 colours[n] = RGB(r, g, b);
3136 void palette_set(int n, int r, int g, int b)
3138 static const int first[21] = {
3139 0, 2, 4, 6, 8, 10, 12, 14,
3140 1, 3, 5, 7, 9, 11, 13, 15,
3143 real_palette_set(first[n], r, g, b);
3145 real_palette_set(first[n] + 1, r, g, b);
3147 HDC hdc = get_ctx();
3148 UnrealizeObject(pal);
3149 RealizePalette(hdc);
3154 void palette_reset(void)
3158 for (i = 0; i < NCOLOURS; i++) {
3160 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
3161 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
3162 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
3163 logpal->palPalEntry[i].peFlags = 0;
3164 colours[i] = PALETTERGB(defpal[i].rgbtRed,
3165 defpal[i].rgbtGreen,
3166 defpal[i].rgbtBlue);
3168 colours[i] = RGB(defpal[i].rgbtRed,
3169 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
3174 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3176 RealizePalette(hdc);
3181 void write_aclip(char *data, int len, int must_deselect)
3186 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len + 1);
3189 lock = GlobalLock(clipdata);
3192 memcpy(lock, data, len);
3193 ((unsigned char *) lock)[len] = 0;
3194 GlobalUnlock(clipdata);
3197 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
3199 if (OpenClipboard(hwnd)) {
3201 SetClipboardData(CF_TEXT, clipdata);
3204 GlobalFree(clipdata);
3207 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
3211 * Note: unlike write_aclip() this will not append a nul.
3213 void write_clip(wchar_t * data, int len, int must_deselect)
3220 len2 = WideCharToMultiByte(CP_ACP, 0, data, len, 0, 0, NULL, NULL);
3222 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE,
3223 len * sizeof(wchar_t));
3224 clipdata2 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len2);
3226 if (!clipdata || !clipdata2) {
3228 GlobalFree(clipdata);
3230 GlobalFree(clipdata2);
3233 if (!(lock = GlobalLock(clipdata)))
3235 if (!(lock2 = GlobalLock(clipdata2)))
3238 memcpy(lock, data, len * sizeof(wchar_t));
3239 WideCharToMultiByte(CP_ACP, 0, data, len, lock2, len2, NULL, NULL);
3241 GlobalUnlock(clipdata);
3242 GlobalUnlock(clipdata2);
3245 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
3247 if (OpenClipboard(hwnd)) {
3249 SetClipboardData(CF_UNICODETEXT, clipdata);
3250 SetClipboardData(CF_TEXT, clipdata2);
3253 GlobalFree(clipdata);
3254 GlobalFree(clipdata2);
3258 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
3261 void get_clip(wchar_t ** p, int *len)
3263 static HGLOBAL clipdata = NULL;
3264 static wchar_t *converted = 0;
3273 GlobalUnlock(clipdata);
3276 } else if (OpenClipboard(NULL)) {
3277 if ((clipdata = GetClipboardData(CF_UNICODETEXT))) {
3279 *p = GlobalLock(clipdata);
3281 for (p2 = *p; *p2; p2++);
3285 } else if ( (clipdata = GetClipboardData(CF_TEXT)) ) {
3289 s = GlobalLock(clipdata);
3290 i = MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, 0, 0);
3291 *p = converted = smalloc(i * sizeof(wchar_t));
3292 MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, converted, i);
3305 * Move `lines' lines from position `from' to position `to' in the
3308 void optimised_move(int to, int from, int lines)
3313 min = (to < from ? to : from);
3314 max = to + from - min;
3317 r.right = cols * font_width;
3318 r.top = min * font_height;
3319 r.bottom = (max + lines) * font_height;
3320 ScrollWindow(hwnd, 0, (to - from) * font_height, &r, &r);
3325 * Print a message box and perform a fatal exit.
3327 void fatalbox(char *fmt, ...)
3333 vsprintf(stuff, fmt, ap);
3335 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
3340 * Manage window caption / taskbar flashing, if enabled.
3341 * 0 = stop, 1 = maintain, 2 = start
3343 static void flash_window(int mode)
3345 static long last_flash = 0;
3346 static int flashing = 0;
3347 if ((mode == 0) || (cfg.beep_ind == B_IND_DISABLED)) {
3350 FlashWindow(hwnd, FALSE);
3354 } else if (mode == 2) {
3357 last_flash = GetTickCount();
3359 FlashWindow(hwnd, TRUE);
3362 } else if ((mode == 1) && (cfg.beep_ind == B_IND_FLASH)) {
3365 long now = GetTickCount();
3366 long fdiff = now - last_flash;
3367 if (fdiff < 0 || fdiff > 450) {
3369 FlashWindow(hwnd, TRUE); /* toggle */
3380 if (mode == BELL_DEFAULT) {
3382 * For MessageBeep style bells, we want to be careful of
3383 * timing, because they don't have the nice property of
3384 * PlaySound bells that each one cancels the previous
3385 * active one. So we limit the rate to one per 50ms or so.
3387 static long lastbeep = 0;
3390 beepdiff = GetTickCount() - lastbeep;
3391 if (beepdiff >= 0 && beepdiff < 50)
3395 * The above MessageBeep call takes time, so we record the
3396 * time _after_ it finishes rather than before it starts.
3398 lastbeep = GetTickCount();
3399 } else if (mode == BELL_WAVEFILE) {
3400 if (!PlaySound(cfg.bell_wavefile, NULL, SND_ASYNC | SND_FILENAME)) {
3401 char buf[sizeof(cfg.bell_wavefile) + 80];
3402 sprintf(buf, "Unable to play sound file\n%s\n"
3403 "Using default sound instead", cfg.bell_wavefile);
3404 MessageBox(hwnd, buf, "PuTTY Sound Error",
3405 MB_OK | MB_ICONEXCLAMATION);
3406 cfg.beep = BELL_DEFAULT;
3409 /* Otherwise, either visual bell or disabled; do nothing here */
3411 flash_window(2); /* start */