16 #define COMPILE_MULTIMON_STUBS
27 #define PUTTY_DO_GLOBALS /* actually _define_ globals */
33 #define IDM_SHOWLOG 0x0010
34 #define IDM_NEWSESS 0x0020
35 #define IDM_DUPSESS 0x0030
36 #define IDM_RECONF 0x0040
37 #define IDM_CLRSB 0x0050
38 #define IDM_RESET 0x0060
39 #define IDM_TEL_AYT 0x0070
40 #define IDM_TEL_BRK 0x0080
41 #define IDM_TEL_SYNCH 0x0090
42 #define IDM_TEL_EC 0x00a0
43 #define IDM_TEL_EL 0x00b0
44 #define IDM_TEL_GA 0x00c0
45 #define IDM_TEL_NOP 0x00d0
46 #define IDM_TEL_ABORT 0x00e0
47 #define IDM_TEL_AO 0x00f0
48 #define IDM_TEL_IP 0x0100
49 #define IDM_TEL_SUSP 0x0110
50 #define IDM_TEL_EOR 0x0120
51 #define IDM_TEL_EOF 0x0130
52 #define IDM_HELP 0x0140
53 #define IDM_ABOUT 0x0150
54 #define IDM_SAVEDSESS 0x0160
55 #define IDM_COPYALL 0x0170
56 #define IDM_FULLSCREEN 0x0180
58 #define IDM_SESSLGP 0x0250 /* log type printable */
59 #define IDM_SESSLGA 0x0260 /* log type all chars */
60 #define IDM_SESSLGE 0x0270 /* log end */
61 #define IDM_SAVED_MIN 0x1000
62 #define IDM_SAVED_MAX 0x2000
64 #define WM_IGNORE_CLIP (WM_XUSER + 2)
65 #define WM_FULLSCR_ON_MAX (WM_XUSER + 3)
67 /* Needed for Chinese support and apparently not always defined. */
69 #define VK_PROCESSKEY 0xE5
72 /* Mouse wheel support. */
74 #define WM_MOUSEWHEEL 0x020A /* not defined in earlier SDKs */
77 #define WHEEL_DELTA 120
80 static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
81 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
82 unsigned char *output);
83 static void cfgtopalette(void);
84 static void init_palette(void);
85 static void init_fonts(int, int);
86 static void another_font(int);
87 static void deinit_fonts(void);
88 static void set_input_locale(HKL);
89 static int do_mouse_wheel_msg(UINT message, WPARAM wParam, LPARAM lParam);
91 static int is_full_screen(void);
92 static void make_full_screen(void);
93 static void clear_full_screen(void);
94 static void flip_full_screen(void);
96 /* Window layout information */
97 static void reset_window(int);
98 static int extra_width, extra_height;
99 static int font_width, font_height, font_dualwidth;
100 static int offset_width, offset_height;
101 static int was_zoomed = 0;
102 static int prev_rows, prev_cols;
104 static int pending_netevent = 0;
105 static WPARAM pend_netevent_wParam = 0;
106 static LPARAM pend_netevent_lParam = 0;
107 static void enact_pending_netevent(void);
108 static void flash_window(int mode);
109 static void sys_cursor_update(void);
111 static time_t last_movement = 0;
113 static int caret_x = -1, caret_y = -1;
115 #define FONT_NORMAL 0
117 #define FONT_UNDERLINE 2
118 #define FONT_BOLDUND 3
119 #define FONT_WIDE 0x04
120 #define FONT_HIGH 0x08
121 #define FONT_NARROW 0x10
123 #define FONT_OEM 0x20
124 #define FONT_OEMBOLD 0x21
125 #define FONT_OEMUND 0x22
126 #define FONT_OEMBOLDUND 0x23
128 #define FONT_MAXNO 0x2F
130 static HFONT fonts[FONT_MAXNO];
131 static LOGFONT lfont;
132 static int fontflag[FONT_MAXNO];
134 BOLD_COLOURS, BOLD_SHADOW, BOLD_FONT
142 static COLORREF colours[NCOLOURS];
144 static LPLOGPALETTE logpal;
145 static RGBTRIPLE defpal[NCOLOURS];
149 static HBITMAP caretbm;
151 static int dbltime, lasttime, lastact;
152 static Mouse_Button lastbtn;
154 /* this allows xterm-style mouse handling. */
155 static int send_raw_mouse = 0;
156 static int wheel_accumulator = 0;
158 static char *window_name, *icon_name;
160 static int compose_state = 0;
162 static int wsa_started = 0;
164 static OSVERSIONINFO osVersion;
166 static UINT wm_mousewheel = WM_MOUSEWHEEL;
168 /* Dummy routine, only required in plink. */
169 void ldisc_update(int echo, int edit)
173 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
175 static char appname[] = "PuTTY";
180 int guess_width, guess_height;
183 flags = FLAG_VERBOSE | FLAG_INTERACTIVE;
185 winsock_ver = MAKEWORD(1, 1);
186 if (WSAStartup(winsock_ver, &wsadata)) {
187 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
188 MB_OK | MB_ICONEXCLAMATION);
191 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
192 MessageBox(NULL, "WinSock version is incompatible with 1.1",
193 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
198 /* WISHLIST: maybe allow config tweaking even if winsock not present? */
201 InitCommonControls();
203 /* Ensure a Maximize setting in Explorer doesn't maximise the
208 ZeroMemory(&osVersion, sizeof(osVersion));
209 osVersion.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
210 if (!GetVersionEx ( (OSVERSIONINFO *) &osVersion)) {
211 MessageBox(NULL, "Windows refuses to report a version",
212 "PuTTY Fatal Error", MB_OK | MB_ICONEXCLAMATION);
218 * If we're running a version of Windows that doesn't support
219 * WM_MOUSEWHEEL, find out what message number we should be
222 if (osVersion.dwMajorVersion < 4 ||
223 (osVersion.dwMajorVersion == 4 &&
224 osVersion.dwPlatformId != VER_PLATFORM_WIN32_NT))
225 wm_mousewheel = RegisterWindowMessage("MSWHEEL_ROLLMSG");
228 * See if we can find our Help file.
231 char b[2048], *p, *q, *r;
233 GetModuleFileName(NULL, b, sizeof(b) - 1);
235 p = strrchr(b, '\\');
236 if (p && p >= r) r = p+1;
238 if (q && q >= r) r = q+1;
239 strcpy(r, "putty.hlp");
240 if ( (fp = fopen(b, "r")) != NULL) {
241 help_path = dupstr(b);
245 strcpy(r, "putty.cnt");
246 if ( (fp = fopen(b, "r")) != NULL) {
247 help_has_contents = TRUE;
250 help_has_contents = FALSE;
254 * Process the command line.
259 default_protocol = DEFAULT_PROTOCOL;
260 default_port = DEFAULT_PORT;
261 cfg.logtype = LGTYP_NONE;
263 do_defaults(NULL, &cfg);
266 while (*p && isspace(*p))
270 * Process command line options first. Yes, this can be
271 * done better, and it will be as soon as I have the
275 char *q = p + strcspn(p, " \t");
278 tolower(p[0]) == 's' &&
279 tolower(p[1]) == 's' && tolower(p[2]) == 'h') {
280 default_protocol = cfg.protocol = PROT_SSH;
281 default_port = cfg.port = 22;
282 } else if (q == p + 7 &&
283 tolower(p[0]) == 'c' &&
284 tolower(p[1]) == 'l' &&
285 tolower(p[2]) == 'e' &&
286 tolower(p[3]) == 'a' &&
287 tolower(p[4]) == 'n' &&
288 tolower(p[5]) == 'u' && tolower(p[6]) == 'p') {
290 * `putty -cleanup'. Remove all registry entries
291 * associated with PuTTY, and also find and delete
292 * the random seed file.
295 "This procedure will remove ALL Registry\n"
296 "entries associated with PuTTY, and will\n"
297 "also remove the PuTTY random seed file.\n"
299 "THIS PROCESS WILL DESTROY YOUR SAVED\n"
300 "SESSIONS. Are you really sure you want\n"
303 MB_YESNO | MB_ICONWARNING) == IDYES) {
308 p = q + strspn(q, " \t");
312 * An initial @ means to activate a saved session.
316 while (i > 1 && isspace(p[i - 1]))
319 do_defaults(p + 1, &cfg);
320 if (!*cfg.host && !do_config()) {
324 } else if (*p == '&') {
326 * An initial & means we've been given a command line
327 * containing the hex value of a HANDLE for a file
328 * mapping object, which we must then extract as a
333 if (sscanf(p + 1, "%p", &filemap) == 1 &&
334 (cp = MapViewOfFile(filemap, FILE_MAP_READ,
335 0, 0, sizeof(Config))) != NULL) {
338 CloseHandle(filemap);
339 } else if (!do_config()) {
346 * If the hostname starts with "telnet:", set the
347 * protocol to Telnet and process the string as a
350 if (!strncmp(q, "telnet:", 7)) {
354 if (q[0] == '/' && q[1] == '/')
356 cfg.protocol = PROT_TELNET;
358 while (*p && *p != ':' && *p != '/')
367 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
368 cfg.host[sizeof(cfg.host) - 1] = '\0';
370 while (*p && !isspace(*p))
374 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
375 cfg.host[sizeof(cfg.host) - 1] = '\0';
376 while (*p && isspace(*p))
391 * Trim leading whitespace off the hostname if it's there.
394 int space = strspn(cfg.host, " \t");
395 memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
398 /* See if host is of the form user@host */
399 if (cfg.host[0] != '\0') {
400 char *atsign = strchr(cfg.host, '@');
401 /* Make sure we're not overflowing the user field */
403 if (atsign - cfg.host < sizeof cfg.username) {
404 strncpy(cfg.username, cfg.host, atsign - cfg.host);
405 cfg.username[atsign - cfg.host] = '\0';
407 memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
412 * Trim a colon suffix off the hostname if it's there.
414 cfg.host[strcspn(cfg.host, ":")] = '\0';
418 * Select protocol. This is farmed out into a table in a
419 * separate file to enable an ssh-free variant.
424 for (i = 0; backends[i].backend != NULL; i++)
425 if (backends[i].protocol == cfg.protocol) {
426 back = backends[i].backend;
430 MessageBox(NULL, "Unsupported protocol number found",
431 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
437 /* Check for invalid Port number (i.e. zero) */
439 MessageBox(NULL, "Invalid Port Number",
440 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
447 wndclass.lpfnWndProc = WndProc;
448 wndclass.cbClsExtra = 0;
449 wndclass.cbWndExtra = 0;
450 wndclass.hInstance = inst;
451 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
452 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
453 wndclass.hbrBackground = NULL;
454 wndclass.lpszMenuName = NULL;
455 wndclass.lpszClassName = appname;
457 RegisterClass(&wndclass);
462 savelines = cfg.savelines;
468 * Guess some defaults for the window size. This all gets
469 * updated later, so we don't really care too much. However, we
470 * do want the font width/height guesses to correspond to a
471 * large font rather than a small one...
478 term_size(cfg.height, cfg.width, cfg.savelines);
479 guess_width = extra_width + font_width * cols;
480 guess_height = extra_height + font_height * rows;
483 HWND w = GetDesktopWindow();
484 GetWindowRect(w, &r);
485 if (guess_width > r.right - r.left)
486 guess_width = r.right - r.left;
487 if (guess_height > r.bottom - r.top)
488 guess_height = r.bottom - r.top;
492 int winmode = WS_OVERLAPPEDWINDOW | WS_VSCROLL;
495 winmode &= ~(WS_VSCROLL);
496 if (cfg.resize_action == RESIZE_DISABLED)
497 winmode &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
499 exwinmode |= WS_EX_TOPMOST;
501 exwinmode |= WS_EX_CLIENTEDGE;
502 hwnd = CreateWindowEx(exwinmode, appname, appname,
503 winmode, CW_USEDEFAULT, CW_USEDEFAULT,
504 guess_width, guess_height,
505 NULL, NULL, inst, NULL);
509 * Initialise the fonts, simultaneously correcting the guesses
510 * for font_{width,height}.
515 * Correct the guesses for extra_{width,height}.
519 GetWindowRect(hwnd, &wr);
520 GetClientRect(hwnd, &cr);
521 offset_width = offset_height = cfg.window_border;
522 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
523 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
527 * Resize the window, now we know what size we _really_ want it
530 guess_width = extra_width + font_width * cols;
531 guess_height = extra_height + font_height * rows;
532 SetWindowPos(hwnd, NULL, 0, 0, guess_width, guess_height,
533 SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
536 * Set up a caret bitmap, with no content.
540 int size = (font_width + 15) / 16 * 2 * font_height;
541 bits = smalloc(size);
542 memset(bits, 0, size);
543 caretbm = CreateBitmap(font_width, font_height, 1, 1, bits);
546 CreateCaret(hwnd, caretbm, font_width, font_height);
549 * Initialise the scroll bar.
554 si.cbSize = sizeof(si);
555 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
560 SetScrollInfo(hwnd, SB_VERT, &si, FALSE);
564 * Start up the telnet connection.
568 char msg[1024], *title;
571 error = back->init(cfg.host, cfg.port, &realhost, cfg.tcp_nodelay);
573 sprintf(msg, "Unable to open connection to\n"
574 "%.800s\n" "%s", cfg.host, error);
575 MessageBox(NULL, msg, "PuTTY Error", MB_ICONERROR | MB_OK);
578 window_name = icon_name = NULL;
580 title = cfg.wintitle;
582 sprintf(msg, "%s - PuTTY", realhost);
590 session_closed = FALSE;
593 * Prepare the mouse handler.
595 lastact = MA_NOTHING;
596 lastbtn = MBT_NOTHING;
597 dbltime = GetDoubleClickTime();
600 * Set up the session-control options on the system menu.
603 HMENU m = GetSystemMenu(hwnd, FALSE);
607 AppendMenu(m, MF_SEPARATOR, 0, 0);
608 if (cfg.protocol == PROT_TELNET) {
610 AppendMenu(p, MF_ENABLED, IDM_TEL_AYT, "Are You There");
611 AppendMenu(p, MF_ENABLED, IDM_TEL_BRK, "Break");
612 AppendMenu(p, MF_ENABLED, IDM_TEL_SYNCH, "Synch");
613 AppendMenu(p, MF_SEPARATOR, 0, 0);
614 AppendMenu(p, MF_ENABLED, IDM_TEL_EC, "Erase Character");
615 AppendMenu(p, MF_ENABLED, IDM_TEL_EL, "Erase Line");
616 AppendMenu(p, MF_ENABLED, IDM_TEL_GA, "Go Ahead");
617 AppendMenu(p, MF_ENABLED, IDM_TEL_NOP, "No Operation");
618 AppendMenu(p, MF_SEPARATOR, 0, 0);
619 AppendMenu(p, MF_ENABLED, IDM_TEL_ABORT, "Abort Process");
620 AppendMenu(p, MF_ENABLED, IDM_TEL_AO, "Abort Output");
621 AppendMenu(p, MF_ENABLED, IDM_TEL_IP, "Interrupt Process");
622 AppendMenu(p, MF_ENABLED, IDM_TEL_SUSP, "Suspend Process");
623 AppendMenu(p, MF_SEPARATOR, 0, 0);
624 AppendMenu(p, MF_ENABLED, IDM_TEL_EOR, "End Of Record");
625 AppendMenu(p, MF_ENABLED, IDM_TEL_EOF, "End Of File");
626 AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) p,
628 AppendMenu(m, MF_SEPARATOR, 0, 0);
630 AppendMenu(m, MF_ENABLED, IDM_SHOWLOG, "&Event Log");
631 AppendMenu(m, MF_SEPARATOR, 0, 0);
632 AppendMenu(m, MF_ENABLED, IDM_NEWSESS, "Ne&w Session...");
633 AppendMenu(m, MF_ENABLED, IDM_DUPSESS, "&Duplicate Session");
636 for (i = 1; i < ((nsessions < 256) ? nsessions : 256); i++)
637 AppendMenu(s, MF_ENABLED, IDM_SAVED_MIN + (16 * i),
639 AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) s, "Sa&ved Sessions");
640 AppendMenu(m, MF_ENABLED, IDM_RECONF, "Chan&ge Settings...");
641 AppendMenu(m, MF_SEPARATOR, 0, 0);
642 AppendMenu(m, MF_ENABLED, IDM_COPYALL, "C&opy All to Clipboard");
643 AppendMenu(m, MF_ENABLED, IDM_CLRSB, "C&lear Scrollback");
644 AppendMenu(m, MF_ENABLED, IDM_RESET, "Rese&t Terminal");
645 AppendMenu(m, MF_SEPARATOR, 0, 0);
646 AppendMenu(m, (cfg.resize_action == RESIZE_DISABLED) ?
647 MF_GRAYED : MF_ENABLED, IDM_FULLSCREEN, "&Full Screen");
648 AppendMenu(m, MF_SEPARATOR, 0, 0);
650 AppendMenu(m, MF_ENABLED, IDM_HELP, "&Help");
651 AppendMenu(m, MF_ENABLED, IDM_ABOUT, "&About PuTTY");
655 * Set up the initial input locale.
657 set_input_locale(GetKeyboardLayout(0));
660 * Open the initial log file if there is one.
665 * Finally show the window!
667 ShowWindow(hwnd, show);
668 SetForegroundWindow(hwnd);
671 * Set the palette up.
677 has_focus = (GetForegroundWindow() == hwnd);
680 if (GetMessage(&msg, NULL, 0, 0) == 1) {
681 int timer_id = 0, long_timer = 0;
683 while (msg.message != WM_QUIT) {
684 /* Sometimes DispatchMessage calls routines that use their own
685 * GetMessage loop, setup this timer so we get some control back.
687 * Also call term_update() from the timer so that if the host
688 * is sending data flat out we still do redraws.
690 if (timer_id && long_timer) {
691 KillTimer(hwnd, timer_id);
692 long_timer = timer_id = 0;
695 timer_id = SetTimer(hwnd, 1, 20, NULL);
696 if (!(IsWindow(logbox) && IsDialogMessage(logbox, &msg)))
697 DispatchMessage(&msg);
699 /* Make sure we blink everything that needs it. */
702 /* Send the paste buffer if there's anything to send */
705 /* If there's nothing new in the queue then we can do everything
706 * we've delayed, reading the socket, writing, and repainting
709 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
712 if (pending_netevent) {
713 enact_pending_netevent();
715 /* Force the cursor blink on */
718 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
722 /* Okay there is now nothing to do so we make sure the screen is
723 * completely up to date then tell windows to call us in a little
727 KillTimer(hwnd, timer_id);
731 if (GetCapture() != hwnd)
736 flash_window(1); /* maintain */
738 /* The messages seem unreliable; especially if we're being tricky */
739 has_focus = (GetForegroundWindow() == hwnd);
742 /* Hmm, term_update didn't want to do an update too soon ... */
743 timer_id = SetTimer(hwnd, 1, 50, NULL);
745 timer_id = SetTimer(hwnd, 1, 500, NULL);
747 timer_id = SetTimer(hwnd, 1, 100, NULL);
750 /* There's no point rescanning everything in the message queue
751 * so we do an apparently unnecessary wait here
754 if (GetMessage(&msg, NULL, 0, 0) != 1)
759 cleanup_exit(msg.wParam); /* this doesn't return... */
760 return msg.wParam; /* ... but optimiser doesn't know */
766 void cleanup_exit(int code)
779 if (cfg.protocol == PROT_SSH) {
790 * Set up, or shut down, an AsyncSelect. Called from winnet.c.
792 char *do_select(SOCKET skt, int startup)
797 events = (FD_CONNECT | FD_READ | FD_WRITE |
798 FD_OOB | FD_CLOSE | FD_ACCEPT);
803 return "do_select(): internal error (hwnd==NULL)";
804 if (WSAAsyncSelect(skt, hwnd, msg, events) == SOCKET_ERROR) {
805 switch (WSAGetLastError()) {
807 return "Network is down";
809 return "WSAAsyncSelect(): unknown error";
816 * set or clear the "raw mouse message" mode
818 void set_raw_mouse_mode(int activate)
820 activate = activate && !cfg.no_mouse_rep;
821 send_raw_mouse = activate;
822 SetCursor(LoadCursor(NULL, activate ? IDC_ARROW : IDC_IBEAM));
826 * Print a message box and close the connection.
828 void connection_fatal(char *fmt, ...)
834 vsprintf(stuff, fmt, ap);
836 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
837 if (cfg.close_on_exit == COE_ALWAYS)
840 session_closed = TRUE;
841 SetWindowText(hwnd, "PuTTY (inactive)");
846 * Actually do the job requested by a WM_NETEVENT
848 static void enact_pending_netevent(void)
850 static int reentering = 0;
851 extern int select_result(WPARAM, LPARAM);
855 return; /* don't unpend the pending */
857 pending_netevent = FALSE;
860 ret = select_result(pend_netevent_wParam, pend_netevent_lParam);
863 if (ret == 0 && !session_closed) {
864 /* Abnormal exits will already have set session_closed and taken
865 * appropriate action. */
866 if (cfg.close_on_exit == COE_ALWAYS ||
867 cfg.close_on_exit == COE_NORMAL) PostQuitMessage(0);
869 session_closed = TRUE;
870 SetWindowText(hwnd, "PuTTY (inactive)");
871 MessageBox(hwnd, "Connection closed by remote host",
872 "PuTTY", MB_OK | MB_ICONINFORMATION);
878 * Copy the colour palette from the configuration data into defpal.
879 * This is non-trivial because the colour indices are different.
881 static void cfgtopalette(void)
884 static const int ww[] = {
885 6, 7, 8, 9, 10, 11, 12, 13,
886 14, 15, 16, 17, 18, 19, 20, 21,
887 0, 1, 2, 3, 4, 4, 5, 5
890 for (i = 0; i < 24; i++) {
892 defpal[i].rgbtRed = cfg.colours[w][0];
893 defpal[i].rgbtGreen = cfg.colours[w][1];
894 defpal[i].rgbtBlue = cfg.colours[w][2];
899 * Set up the colour palette.
901 static void init_palette(void)
904 HDC hdc = GetDC(hwnd);
906 if (cfg.try_palette && GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) {
907 logpal = smalloc(sizeof(*logpal)
908 - sizeof(logpal->palPalEntry)
909 + NCOLOURS * sizeof(PALETTEENTRY));
910 logpal->palVersion = 0x300;
911 logpal->palNumEntries = NCOLOURS;
912 for (i = 0; i < NCOLOURS; i++) {
913 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
914 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
915 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
916 logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
918 pal = CreatePalette(logpal);
920 SelectPalette(hdc, pal, FALSE);
922 SelectPalette(hdc, GetStockObject(DEFAULT_PALETTE), FALSE);
925 ReleaseDC(hwnd, hdc);
928 for (i = 0; i < NCOLOURS; i++)
929 colours[i] = PALETTERGB(defpal[i].rgbtRed,
933 for (i = 0; i < NCOLOURS; i++)
934 colours[i] = RGB(defpal[i].rgbtRed,
935 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
939 * Initialise all the fonts we will need initially. There may be as many as
940 * three or as few as one. The other (poentially) twentyone fonts are done
941 * if/when they are needed.
945 * - check the font width and height, correcting our guesses if
948 * - verify that the bold font is the same width as the ordinary
949 * one, and engage shadow bolding if not.
951 * - verify that the underlined font is the same width as the
952 * ordinary one (manual underlining by means of line drawing can
953 * be done in a pinch).
955 static void init_fonts(int pick_width, int pick_height)
962 int fw_dontcare, fw_bold;
964 for (i = 0; i < FONT_MAXNO; i++)
967 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
970 if (cfg.fontisbold) {
971 fw_dontcare = FW_BOLD;
974 fw_dontcare = FW_DONTCARE;
981 font_height = pick_height;
983 font_height = cfg.fontheight;
984 if (font_height > 0) {
986 -MulDiv(font_height, GetDeviceCaps(hdc, LOGPIXELSY), 72);
989 font_width = pick_width;
992 fonts[i] = CreateFont (font_height, font_width, 0, 0, w, FALSE, u, FALSE, \
993 c, OUT_DEFAULT_PRECIS, \
994 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, \
995 FIXED_PITCH | FF_DONTCARE, cfg.font)
997 f(FONT_NORMAL, cfg.fontcharset, fw_dontcare, FALSE);
999 lfont.lfHeight = font_height;
1000 lfont.lfWidth = font_width;
1001 lfont.lfEscapement = 0;
1002 lfont.lfOrientation = 0;
1003 lfont.lfWeight = fw_dontcare;
1004 lfont.lfItalic = FALSE;
1005 lfont.lfUnderline = FALSE;
1006 lfont.lfStrikeOut = FALSE;
1007 lfont.lfCharSet = cfg.fontcharset;
1008 lfont.lfOutPrecision = OUT_DEFAULT_PRECIS;
1009 lfont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
1010 lfont.lfQuality = DEFAULT_QUALITY;
1011 lfont.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE;
1012 strncpy(lfont.lfFaceName, cfg.font, LF_FACESIZE);
1014 SelectObject(hdc, fonts[FONT_NORMAL]);
1015 GetTextMetrics(hdc, &tm);
1017 if (pick_width == 0 || pick_height == 0) {
1018 font_height = tm.tmHeight;
1019 font_width = tm.tmAveCharWidth;
1021 font_dualwidth = (tm.tmAveCharWidth != tm.tmMaxCharWidth);
1023 #ifdef RDB_DEBUG_PATCH
1024 debug(23, "Primary font H=%d, AW=%d, MW=%d",
1025 tm.tmHeight, tm.tmAveCharWidth, tm.tmMaxCharWidth);
1030 DWORD cset = tm.tmCharSet;
1031 memset(&info, 0xFF, sizeof(info));
1033 /* !!! Yes the next line is right */
1034 if (cset == OEM_CHARSET)
1035 font_codepage = GetOEMCP();
1037 if (TranslateCharsetInfo
1038 ((DWORD *) cset, &info, TCI_SRCCHARSET)) font_codepage =
1043 GetCPInfo(font_codepage, &cpinfo);
1044 dbcs_screenfont = (cpinfo.MaxCharSize > 1);
1047 f(FONT_UNDERLINE, cfg.fontcharset, fw_dontcare, TRUE);
1050 * Some fonts, e.g. 9-pt Courier, draw their underlines
1051 * outside their character cell. We successfully prevent
1052 * screen corruption by clipping the text output, but then
1053 * we lose the underline completely. Here we try to work
1054 * out whether this is such a font, and if it is, we set a
1055 * flag that causes underlines to be drawn by hand.
1057 * Having tried other more sophisticated approaches (such
1058 * as examining the TEXTMETRIC structure or requesting the
1059 * height of a string), I think we'll do this the brute
1060 * force way: we create a small bitmap, draw an underlined
1061 * space on it, and test to see whether any pixels are
1062 * foreground-coloured. (Since we expect the underline to
1063 * go all the way across the character cell, we only search
1064 * down a single column of the bitmap, half way across.)
1068 HBITMAP und_bm, und_oldbm;
1072 und_dc = CreateCompatibleDC(hdc);
1073 und_bm = CreateCompatibleBitmap(hdc, font_width, font_height);
1074 und_oldbm = SelectObject(und_dc, und_bm);
1075 SelectObject(und_dc, fonts[FONT_UNDERLINE]);
1076 SetTextAlign(und_dc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
1077 SetTextColor(und_dc, RGB(255, 255, 255));
1078 SetBkColor(und_dc, RGB(0, 0, 0));
1079 SetBkMode(und_dc, OPAQUE);
1080 ExtTextOut(und_dc, 0, 0, ETO_OPAQUE, NULL, " ", 1, NULL);
1082 for (i = 0; i < font_height; i++) {
1083 c = GetPixel(und_dc, font_width / 2, i);
1084 if (c != RGB(0, 0, 0))
1087 SelectObject(und_dc, und_oldbm);
1088 DeleteObject(und_bm);
1091 und_mode = UND_LINE;
1092 DeleteObject(fonts[FONT_UNDERLINE]);
1093 fonts[FONT_UNDERLINE] = 0;
1097 if (bold_mode == BOLD_FONT) {
1098 f(FONT_BOLD, cfg.fontcharset, fw_bold, FALSE);
1102 descent = tm.tmAscent + 1;
1103 if (descent >= font_height)
1104 descent = font_height - 1;
1106 for (i = 0; i < 3; i++) {
1108 if (SelectObject(hdc, fonts[i]) && GetTextMetrics(hdc, &tm))
1109 fontsize[i] = tm.tmAveCharWidth + 256 * tm.tmHeight;
1116 ReleaseDC(hwnd, hdc);
1118 if (fontsize[FONT_UNDERLINE] != fontsize[FONT_NORMAL]) {
1119 und_mode = UND_LINE;
1120 DeleteObject(fonts[FONT_UNDERLINE]);
1121 fonts[FONT_UNDERLINE] = 0;
1124 if (bold_mode == BOLD_FONT &&
1125 fontsize[FONT_BOLD] != fontsize[FONT_NORMAL]) {
1126 bold_mode = BOLD_SHADOW;
1127 DeleteObject(fonts[FONT_BOLD]);
1128 fonts[FONT_BOLD] = 0;
1130 fontflag[0] = fontflag[1] = fontflag[2] = 1;
1135 static void another_font(int fontno)
1138 int fw_dontcare, fw_bold;
1142 if (fontno < 0 || fontno >= FONT_MAXNO || fontflag[fontno])
1145 basefont = (fontno & ~(FONT_BOLDUND));
1146 if (basefont != fontno && !fontflag[basefont])
1147 another_font(basefont);
1149 if (cfg.fontisbold) {
1150 fw_dontcare = FW_BOLD;
1153 fw_dontcare = FW_DONTCARE;
1157 c = cfg.fontcharset;
1163 if (fontno & FONT_WIDE)
1165 if (fontno & FONT_NARROW)
1167 if (fontno & FONT_OEM)
1169 if (fontno & FONT_BOLD)
1171 if (fontno & FONT_UNDERLINE)
1175 CreateFont(font_height * (1 + !!(fontno & FONT_HIGH)), x, 0, 0, w,
1176 FALSE, u, FALSE, c, OUT_DEFAULT_PRECIS,
1177 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
1178 FIXED_PITCH | FF_DONTCARE, s);
1180 fontflag[fontno] = 1;
1183 static void deinit_fonts(void)
1186 for (i = 0; i < FONT_MAXNO; i++) {
1188 DeleteObject(fonts[i]);
1194 void request_resize(int w, int h)
1198 /* If the window is maximized supress resizing attempts */
1199 if (IsZoomed(hwnd)) {
1200 if (cfg.resize_action == RESIZE_TERM)
1204 if (cfg.resize_action == RESIZE_DISABLED) return;
1205 if (h == rows && w == cols) return;
1207 /* Sanity checks ... */
1209 static int first_time = 1;
1212 switch (first_time) {
1214 /* Get the size of the screen */
1215 if (GetClientRect(GetDesktopWindow(), &ss))
1216 /* first_time = 0 */ ;
1222 /* Make sure the values are sane */
1223 width = (ss.right - ss.left - extra_width) / 4;
1224 height = (ss.bottom - ss.top - extra_height) / 6;
1226 if (w > width || h > height)
1235 term_size(h, w, cfg.savelines);
1237 if (cfg.resize_action != RESIZE_FONT && !IsZoomed(hwnd)) {
1238 width = extra_width + font_width * w;
1239 height = extra_height + font_height * h;
1241 SetWindowPos(hwnd, NULL, 0, 0, width, height,
1242 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1243 SWP_NOMOVE | SWP_NOZORDER);
1247 InvalidateRect(hwnd, NULL, TRUE);
1250 static void reset_window(int reinit) {
1252 * This function decides how to resize or redraw when the
1253 * user changes something.
1255 * This function doesn't like to change the terminal size but if the
1256 * font size is locked that may be it's only soluion.
1258 int win_width, win_height;
1261 #ifdef RDB_DEBUG_PATCH
1262 debug((27, "reset_window()"));
1265 /* Current window sizes ... */
1266 GetWindowRect(hwnd, &wr);
1267 GetClientRect(hwnd, &cr);
1269 win_width = cr.right - cr.left;
1270 win_height = cr.bottom - cr.top;
1272 if (cfg.resize_action == RESIZE_DISABLED) reinit = 2;
1274 /* Are we being forced to reload the fonts ? */
1276 #ifdef RDB_DEBUG_PATCH
1277 debug((27, "reset_window() -- Forced deinit"));
1283 /* Oh, looks like we're minimised */
1284 if (win_width == 0 || win_height == 0)
1287 /* Is the window out of position ? */
1289 (offset_width != (win_width-font_width*cols)/2 ||
1290 offset_height != (win_height-font_height*rows)/2) ){
1291 offset_width = (win_width-font_width*cols)/2;
1292 offset_height = (win_height-font_height*rows)/2;
1293 InvalidateRect(hwnd, NULL, TRUE);
1294 #ifdef RDB_DEBUG_PATCH
1295 debug((27, "reset_window() -> Reposition terminal"));
1299 if (IsZoomed(hwnd)) {
1300 /* We're fullscreen, this means we must not change the size of
1301 * the window so it's the font size or the terminal itself.
1304 extra_width = wr.right - wr.left - cr.right + cr.left;
1305 extra_height = wr.bottom - wr.top - cr.bottom + cr.top;
1307 if (cfg.resize_action != RESIZE_TERM) {
1308 if ( font_width != win_width/cols ||
1309 font_height != win_height/rows) {
1311 init_fonts(win_width/cols, win_height/rows);
1312 offset_width = (win_width-font_width*cols)/2;
1313 offset_height = (win_height-font_height*rows)/2;
1314 InvalidateRect(hwnd, NULL, TRUE);
1315 #ifdef RDB_DEBUG_PATCH
1316 debug((25, "reset_window() -> Z font resize to (%d, %d)",
1317 font_width, font_height));
1321 if ( font_width != win_width/cols ||
1322 font_height != win_height/rows) {
1323 /* Our only choice at this point is to change the
1324 * size of the terminal; Oh well.
1326 term_size( win_height/font_height, win_width/font_width,
1328 offset_width = (win_width-font_width*cols)/2;
1329 offset_height = (win_height-font_height*rows)/2;
1330 InvalidateRect(hwnd, NULL, TRUE);
1331 #ifdef RDB_DEBUG_PATCH
1332 debug((27, "reset_window() -> Zoomed term_size"));
1339 /* Hmm, a force re-init means we should ignore the current window
1340 * so we resize to the default font size.
1343 #ifdef RDB_DEBUG_PATCH
1344 debug((27, "reset_window() -> Forced re-init"));
1347 offset_width = offset_height = cfg.window_border;
1348 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
1349 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
1351 if (win_width != font_width*cols + offset_width*2 ||
1352 win_height != font_height*rows + offset_height*2) {
1354 /* If this is too large windows will resize it to the maximum
1355 * allowed window size, we will then be back in here and resize
1356 * the font or terminal to fit.
1358 SetWindowPos(hwnd, NULL, 0, 0,
1359 font_width*cols + extra_width,
1360 font_height*rows + extra_height,
1361 SWP_NOMOVE | SWP_NOZORDER);
1364 InvalidateRect(hwnd, NULL, TRUE);
1368 /* Okay the user doesn't want us to change the font so we try the
1369 * window. But that may be too big for the screen which forces us
1370 * to change the terminal.
1372 if ((cfg.resize_action == RESIZE_TERM && reinit<=0) ||
1373 (cfg.resize_action == RESIZE_EITHER && reinit<0) ||
1375 offset_width = offset_height = cfg.window_border;
1376 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
1377 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
1379 if (win_width != font_width*cols + offset_width*2 ||
1380 win_height != font_height*rows + offset_height*2) {
1385 GetClientRect(GetDesktopWindow(), &ss);
1386 width = (ss.right - ss.left - extra_width) / font_width;
1387 height = (ss.bottom - ss.top - extra_height) / font_height;
1390 if ( rows > height || cols > width ) {
1391 if (cfg.resize_action == RESIZE_EITHER) {
1392 /* Make the font the biggest we can */
1394 font_width = (ss.right - ss.left - extra_width)/cols;
1396 font_height = (ss.bottom - ss.top - extra_height)/rows;
1399 init_fonts(font_width, font_height);
1401 width = (ss.right - ss.left - extra_width) / font_width;
1402 height = (ss.bottom - ss.top - extra_height) / font_height;
1404 if ( height > rows ) height = rows;
1405 if ( width > cols ) width = cols;
1406 term_size(height, width, cfg.savelines);
1407 #ifdef RDB_DEBUG_PATCH
1408 debug((27, "reset_window() -> term resize to (%d,%d)",
1414 SetWindowPos(hwnd, NULL, 0, 0,
1415 font_width*cols + extra_width,
1416 font_height*rows + extra_height,
1417 SWP_NOMOVE | SWP_NOZORDER);
1419 InvalidateRect(hwnd, NULL, TRUE);
1420 #ifdef RDB_DEBUG_PATCH
1421 debug((27, "reset_window() -> window resize to (%d,%d)",
1422 font_width*cols + extra_width,
1423 font_height*rows + extra_height));
1429 /* We're allowed to or must change the font but do we want to ? */
1431 if (font_width != (win_width-cfg.window_border*2)/cols ||
1432 font_height != (win_height-cfg.window_border*2)/rows) {
1435 init_fonts((win_width-cfg.window_border*2)/cols,
1436 (win_height-cfg.window_border*2)/rows);
1437 offset_width = (win_width-font_width*cols)/2;
1438 offset_height = (win_height-font_height*rows)/2;
1440 extra_width = wr.right - wr.left - cr.right + cr.left +offset_width*2;
1441 extra_height = wr.bottom - wr.top - cr.bottom + cr.top+offset_height*2;
1443 InvalidateRect(hwnd, NULL, TRUE);
1444 #ifdef RDB_DEBUG_PATCH
1445 debug((25, "reset_window() -> font resize to (%d,%d)",
1446 font_width, font_height));
1451 static void set_input_locale(HKL kl)
1455 GetLocaleInfo(LOWORD(kl), LOCALE_IDEFAULTANSICODEPAGE,
1456 lbuf, sizeof(lbuf));
1458 kbd_codepage = atoi(lbuf);
1461 static void click(Mouse_Button b, int x, int y, int shift, int ctrl, int alt)
1463 int thistime = GetMessageTime();
1465 if (send_raw_mouse && !(cfg.mouse_override && shift)) {
1466 lastbtn = MBT_NOTHING;
1467 term_mouse(b, MA_CLICK, x, y, shift, ctrl, alt);
1471 if (lastbtn == b && thistime - lasttime < dbltime) {
1472 lastact = (lastact == MA_CLICK ? MA_2CLK :
1473 lastact == MA_2CLK ? MA_3CLK :
1474 lastact == MA_3CLK ? MA_CLICK : MA_NOTHING);
1479 if (lastact != MA_NOTHING)
1480 term_mouse(b, lastact, x, y, shift, ctrl, alt);
1481 lasttime = thistime;
1485 * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT)
1486 * into a cooked one (SELECT, EXTEND, PASTE).
1488 Mouse_Button translate_button(Mouse_Button button)
1490 if (button == MBT_LEFT)
1492 if (button == MBT_MIDDLE)
1493 return cfg.mouse_is_xterm ? MBT_PASTE : MBT_EXTEND;
1494 if (button == MBT_RIGHT)
1495 return cfg.mouse_is_xterm ? MBT_EXTEND : MBT_PASTE;
1496 return 0; /* shouldn't happen */
1499 static void show_mouseptr(int show)
1501 static int cursor_visible = 1;
1502 if (!cfg.hide_mouseptr) /* override if this feature disabled */
1504 if (cursor_visible && !show)
1506 else if (!cursor_visible && show)
1508 cursor_visible = show;
1511 static int is_alt_pressed(void)
1514 int r = GetKeyboardState(keystate);
1517 if (keystate[VK_MENU] & 0x80)
1519 if (keystate[VK_RMENU] & 0x80)
1524 static int resizing;
1526 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1527 WPARAM wParam, LPARAM lParam)
1530 static int ignore_clip = FALSE;
1531 static int need_backend_resize = FALSE;
1532 static int fullscr_on_max = FALSE;
1536 if (pending_netevent)
1537 enact_pending_netevent();
1538 if (GetCapture() != hwnd)
1544 if (cfg.ping_interval > 0) {
1547 if (now - last_movement > cfg.ping_interval) {
1548 back->special(TS_PING);
1549 last_movement = now;
1552 net_pending_errors();
1558 if (!cfg.warn_on_close || session_closed ||
1560 "Are you sure you want to close this session?",
1561 "PuTTY Exit Confirmation",
1562 MB_ICONWARNING | MB_OKCANCEL) == IDOK)
1563 DestroyWindow(hwnd);
1570 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1582 PROCESS_INFORMATION pi;
1583 HANDLE filemap = NULL;
1585 if (wParam == IDM_DUPSESS) {
1587 * Allocate a file-mapping memory chunk for the
1590 SECURITY_ATTRIBUTES sa;
1593 sa.nLength = sizeof(sa);
1594 sa.lpSecurityDescriptor = NULL;
1595 sa.bInheritHandle = TRUE;
1596 filemap = CreateFileMapping((HANDLE) 0xFFFFFFFF,
1599 0, sizeof(Config), NULL);
1601 p = (Config *) MapViewOfFile(filemap,
1603 0, 0, sizeof(Config));
1605 *p = cfg; /* structure copy */
1609 sprintf(c, "putty &%p", filemap);
1611 } else if (wParam == IDM_SAVEDSESS) {
1612 if ((lParam - IDM_SAVED_MIN) / 16 < nsessions) {
1614 sessions[(lParam - IDM_SAVED_MIN) / 16];
1615 cl = smalloc(16 + strlen(session));
1616 /* 8, but play safe */
1619 /* not a very important failure mode */
1621 sprintf(cl, "putty @%s", session);
1629 GetModuleFileName(NULL, b, sizeof(b) - 1);
1631 si.lpReserved = NULL;
1632 si.lpDesktop = NULL;
1636 si.lpReserved2 = NULL;
1637 CreateProcess(b, cl, NULL, NULL, TRUE,
1638 NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
1641 CloseHandle(filemap);
1651 GetWindowText(hwnd, cfg.wintitle, sizeof(cfg.wintitle));
1654 if (!do_reconfig(hwnd))
1658 /* Disable full-screen if resizing forbidden */
1659 HMENU m = GetSystemMenu (hwnd, FALSE);
1660 EnableMenuItem(m, IDM_FULLSCREEN, MF_BYCOMMAND |
1661 (cfg.resize_action == RESIZE_DISABLED)
1662 ? MF_GRAYED : MF_ENABLED);
1663 /* Gracefully unzoom if necessary */
1664 if (IsZoomed(hwnd) &&
1665 (cfg.resize_action == RESIZE_DISABLED)) {
1666 ShowWindow(hwnd, SW_RESTORE);
1670 if (strcmp(prev_cfg.logfilename, cfg.logfilename) ||
1671 prev_cfg.logtype != cfg.logtype) {
1672 logfclose(); /* reset logging */
1678 * Flush the line discipline's edit buffer in the
1679 * case where local editing has just been disabled.
1681 ldisc_send(NULL, 0, 0);
1689 /* Give terminal a heads-up on miscellaneous stuff */
1692 /* Screen size changed ? */
1693 if (cfg.height != prev_cfg.height ||
1694 cfg.width != prev_cfg.width ||
1695 cfg.savelines != prev_cfg.savelines ||
1696 cfg.resize_action == RESIZE_FONT ||
1697 (cfg.resize_action == RESIZE_EITHER && IsZoomed(hwnd)) ||
1698 cfg.resize_action == RESIZE_DISABLED)
1699 term_size(cfg.height, cfg.width, cfg.savelines);
1701 /* Enable or disable the scroll bar, etc */
1703 LONG nflg, flag = GetWindowLong(hwnd, GWL_STYLE);
1704 LONG nexflag, exflag =
1705 GetWindowLong(hwnd, GWL_EXSTYLE);
1708 if (cfg.alwaysontop != prev_cfg.alwaysontop) {
1709 if (cfg.alwaysontop) {
1710 nexflag |= WS_EX_TOPMOST;
1711 SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
1712 SWP_NOMOVE | SWP_NOSIZE);
1714 nexflag &= ~(WS_EX_TOPMOST);
1715 SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0,
1716 SWP_NOMOVE | SWP_NOSIZE);
1719 if (cfg.sunken_edge)
1720 nexflag |= WS_EX_CLIENTEDGE;
1722 nexflag &= ~(WS_EX_CLIENTEDGE);
1725 if (is_full_screen() ?
1726 cfg.scrollbar_in_fullscreen : cfg.scrollbar)
1729 nflg &= ~WS_VSCROLL;
1731 if (cfg.resize_action == RESIZE_DISABLED ||
1733 nflg &= ~WS_THICKFRAME;
1735 nflg |= WS_THICKFRAME;
1737 if (cfg.resize_action == RESIZE_DISABLED)
1738 nflg &= ~WS_MAXIMIZEBOX;
1740 nflg |= WS_MAXIMIZEBOX;
1742 if (nflg != flag || nexflag != exflag) {
1744 SetWindowLong(hwnd, GWL_STYLE, nflg);
1745 if (nexflag != exflag)
1746 SetWindowLong(hwnd, GWL_EXSTYLE, nexflag);
1748 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
1749 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1750 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
1758 if (cfg.resize_action == RESIZE_DISABLED && IsZoomed(hwnd)) {
1763 set_title(cfg.wintitle);
1764 if (IsIconic(hwnd)) {
1766 cfg.win_name_always ? window_name :
1770 if (strcmp(cfg.font, prev_cfg.font) != 0 ||
1771 strcmp(cfg.line_codepage, prev_cfg.line_codepage) != 0 ||
1772 cfg.fontisbold != prev_cfg.fontisbold ||
1773 cfg.fontheight != prev_cfg.fontheight ||
1774 cfg.fontcharset != prev_cfg.fontcharset ||
1775 cfg.vtmode != prev_cfg.vtmode ||
1776 cfg.bold_colour != prev_cfg.bold_colour ||
1777 cfg.resize_action == RESIZE_DISABLED ||
1778 cfg.resize_action == RESIZE_EITHER ||
1779 (cfg.resize_action != prev_cfg.resize_action))
1782 InvalidateRect(hwnd, NULL, TRUE);
1783 reset_window(init_lvl);
1784 net_pending_errors();
1797 back->special(TS_AYT);
1798 net_pending_errors();
1801 back->special(TS_BRK);
1802 net_pending_errors();
1805 back->special(TS_SYNCH);
1806 net_pending_errors();
1809 back->special(TS_EC);
1810 net_pending_errors();
1813 back->special(TS_EL);
1814 net_pending_errors();
1817 back->special(TS_GA);
1818 net_pending_errors();
1821 back->special(TS_NOP);
1822 net_pending_errors();
1825 back->special(TS_ABORT);
1826 net_pending_errors();
1829 back->special(TS_AO);
1830 net_pending_errors();
1833 back->special(TS_IP);
1834 net_pending_errors();
1837 back->special(TS_SUSP);
1838 net_pending_errors();
1841 back->special(TS_EOR);
1842 net_pending_errors();
1845 back->special(TS_EOF);
1846 net_pending_errors();
1852 WinHelp(hwnd, help_path,
1853 help_has_contents ? HELP_FINDER : HELP_CONTENTS, 0);
1857 * We get this if the System menu has been activated
1864 * We get this if the System menu has been activated
1865 * using the keyboard. This might happen from within
1866 * TranslateKey, in which case it really wants to be
1867 * followed by a `space' character to actually _bring
1868 * the menu up_ rather than just sitting there in
1869 * `ready to appear' state.
1871 show_mouseptr(1); /* make sure pointer is visible */
1873 PostMessage(hwnd, WM_CHAR, ' ', 0);
1875 case IDM_FULLSCREEN:
1879 if (wParam >= IDM_SAVED_MIN && wParam <= IDM_SAVED_MAX) {
1880 SendMessage(hwnd, WM_SYSCOMMAND, IDM_SAVEDSESS, wParam);
1885 #define X_POS(l) ((int)(short)LOWORD(l))
1886 #define Y_POS(l) ((int)(short)HIWORD(l))
1888 #define TO_CHR_X(x) ((((x)<0 ? (x)-font_width+1 : (x))-offset_width) / font_width)
1889 #define TO_CHR_Y(y) ((((y)<0 ? (y)-font_height+1: (y))-offset_height) / font_height)
1890 case WM_LBUTTONDOWN:
1891 case WM_MBUTTONDOWN:
1892 case WM_RBUTTONDOWN:
1900 case WM_LBUTTONDOWN:
1904 case WM_MBUTTONDOWN:
1905 button = MBT_MIDDLE;
1908 case WM_RBUTTONDOWN:
1917 button = MBT_MIDDLE;
1925 button = press = 0; /* shouldn't happen */
1929 * Special case: in full-screen mode, if the left
1930 * button is clicked in the very top left corner of the
1931 * window, we put up the System menu instead of doing
1934 if (is_full_screen() && press && button == MBT_LEFT &&
1935 X_POS(lParam) == 0 && Y_POS(lParam) == 0) {
1936 SendMessage(hwnd, WM_SYSCOMMAND, SC_MOUSEMENU, 0);
1941 TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam)),
1942 wParam & MK_SHIFT, wParam & MK_CONTROL,
1946 term_mouse(button, MA_RELEASE,
1947 TO_CHR_X(X_POS(lParam)),
1948 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
1949 wParam & MK_CONTROL, is_alt_pressed());
1957 * Add the mouse position and message time to the random
1960 noise_ultralight(lParam);
1962 if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON) &&
1963 GetCapture() == hwnd) {
1965 if (wParam & MK_LBUTTON)
1967 else if (wParam & MK_MBUTTON)
1971 term_mouse(b, MA_DRAG, TO_CHR_X(X_POS(lParam)),
1972 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
1973 wParam & MK_CONTROL, is_alt_pressed());
1976 case WM_NCMOUSEMOVE:
1978 noise_ultralight(lParam);
1980 case WM_IGNORE_CLIP:
1981 ignore_clip = wParam; /* don't panic on DESTROYCLIPBOARD */
1983 case WM_DESTROYCLIPBOARD:
1986 ignore_clip = FALSE;
1992 hdc = BeginPaint(hwnd, &p);
1994 SelectPalette(hdc, pal, TRUE);
1995 RealizePalette(hdc);
1998 (p.rcPaint.left-offset_width)/font_width,
1999 (p.rcPaint.top-offset_height)/font_height,
2000 (p.rcPaint.right-offset_width-1)/font_width,
2001 (p.rcPaint.bottom-offset_height-1)/font_height);
2004 p.rcPaint.left < offset_width ||
2005 p.rcPaint.top < offset_height ||
2006 p.rcPaint.right >= offset_width + font_width*cols ||
2007 p.rcPaint.bottom>= offset_height + font_height*rows)
2009 HBRUSH fillcolour, oldbrush;
2011 fillcolour = CreateSolidBrush (
2012 colours[(ATTR_DEFBG>>ATTR_BGSHIFT)*2]);
2013 oldbrush = SelectObject(hdc, fillcolour);
2014 edge = CreatePen(PS_SOLID, 0,
2015 colours[(ATTR_DEFBG>>ATTR_BGSHIFT)*2]);
2016 oldpen = SelectObject(hdc, edge);
2018 ExcludeClipRect(hdc,
2019 offset_width, offset_height,
2020 offset_width+font_width*cols,
2021 offset_height+font_height*rows);
2023 Rectangle(hdc, p.rcPaint.left, p.rcPaint.top,
2024 p.rcPaint.right, p.rcPaint.bottom);
2026 // SelectClipRgn(hdc, NULL);
2028 SelectObject(hdc, oldbrush);
2029 DeleteObject(fillcolour);
2030 SelectObject(hdc, oldpen);
2033 SelectObject(hdc, GetStockObject(SYSTEM_FONT));
2034 SelectObject(hdc, GetStockObject(WHITE_PEN));
2040 /* Notice we can get multiple netevents, FD_READ, FD_WRITE etc
2041 * but the only one that's likely to try to overload us is FD_READ.
2042 * This means buffering just one is fine.
2044 if (pending_netevent)
2045 enact_pending_netevent();
2047 pending_netevent = TRUE;
2048 pend_netevent_wParam = wParam;
2049 pend_netevent_lParam = lParam;
2050 if (WSAGETSELECTEVENT(lParam) != FD_READ)
2051 enact_pending_netevent();
2053 time(&last_movement);
2057 CreateCaret(hwnd, caretbm, font_width, font_height);
2059 flash_window(0); /* stop */
2068 caret_x = caret_y = -1; /* ensure caret is replaced next time */
2072 case WM_ENTERSIZEMOVE:
2073 #ifdef RDB_DEBUG_PATCH
2074 debug((27, "WM_ENTERSIZEMOVE"));
2078 need_backend_resize = FALSE;
2080 case WM_EXITSIZEMOVE:
2083 #ifdef RDB_DEBUG_PATCH
2084 debug((27, "WM_EXITSIZEMOVE"));
2086 if (need_backend_resize) {
2087 term_size(cfg.height, cfg.width, cfg.savelines);
2088 InvalidateRect(hwnd, NULL, TRUE);
2093 * This does two jobs:
2094 * 1) Keep the sizetip uptodate
2095 * 2) Make sure the window size is _stepped_ in units of the font size.
2097 if (cfg.resize_action != RESIZE_FONT && !alt_pressed) {
2098 int width, height, w, h, ew, eh;
2099 LPRECT r = (LPRECT) lParam;
2101 if ( !need_backend_resize && cfg.resize_action == RESIZE_EITHER &&
2102 (cfg.height != rows || cfg.width != cols )) {
2104 * Great! It seems that both the terminal size and the
2105 * font size have been changed and the user is now dragging.
2107 * It will now be difficult to get back to the configured
2110 * This would be easier but it seems to be too confusing.
2112 term_size(cfg.height, cfg.width, cfg.savelines);
2115 cfg.height=rows; cfg.width=cols;
2117 InvalidateRect(hwnd, NULL, TRUE);
2118 need_backend_resize = TRUE;
2121 width = r->right - r->left - extra_width;
2122 height = r->bottom - r->top - extra_height;
2123 w = (width + font_width / 2) / font_width;
2126 h = (height + font_height / 2) / font_height;
2129 UpdateSizeTip(hwnd, w, h);
2130 ew = width - w * font_width;
2131 eh = height - h * font_height;
2133 if (wParam == WMSZ_LEFT ||
2134 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
2140 if (wParam == WMSZ_TOP ||
2141 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
2151 int width, height, w, h, rv = 0;
2152 int ex_width = extra_width + (cfg.window_border - offset_width) * 2;
2153 int ex_height = extra_height + (cfg.window_border - offset_height) * 2;
2154 LPRECT r = (LPRECT) lParam;
2156 width = r->right - r->left - ex_width;
2157 height = r->bottom - r->top - ex_height;
2159 w = (width + cols/2)/cols;
2160 h = (height + rows/2)/rows;
2161 if ( r->right != r->left + w*cols + ex_width)
2164 if (wParam == WMSZ_LEFT ||
2165 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
2166 r->left = r->right - w*cols - ex_width;
2168 r->right = r->left + w*cols + ex_width;
2170 if (r->bottom != r->top + h*rows + ex_height)
2173 if (wParam == WMSZ_TOP ||
2174 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
2175 r->top = r->bottom - h*rows - ex_height;
2177 r->bottom = r->top + h*rows + ex_height;
2181 /* break; (never reached) */
2182 case WM_FULLSCR_ON_MAX:
2183 fullscr_on_max = TRUE;
2186 sys_cursor_update();
2189 #ifdef RDB_DEBUG_PATCH
2190 debug((27, "WM_SIZE %s (%d,%d)",
2191 (wParam == SIZE_MINIMIZED) ? "SIZE_MINIMIZED":
2192 (wParam == SIZE_MAXIMIZED) ? "SIZE_MAXIMIZED":
2193 (wParam == SIZE_RESTORED && resizing) ? "to":
2194 (wParam == SIZE_RESTORED) ? "SIZE_RESTORED":
2196 LOWORD(lParam), HIWORD(lParam)));
2198 if (wParam == SIZE_MINIMIZED)
2200 cfg.win_name_always ? window_name : icon_name);
2201 if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
2202 SetWindowText(hwnd, window_name);
2203 if (wParam == SIZE_RESTORED)
2204 clear_full_screen();
2205 if (wParam == SIZE_MAXIMIZED && fullscr_on_max) {
2207 fullscr_on_max = FALSE;
2210 if (cfg.resize_action == RESIZE_DISABLED) {
2211 /* A resize, well it better be a minimize. */
2215 int width, height, w, h;
2217 width = LOWORD(lParam);
2218 height = HIWORD(lParam);
2221 if (wParam == SIZE_MAXIMIZED && !was_zoomed) {
2225 if (cfg.resize_action == RESIZE_TERM) {
2226 w = width / font_width;
2228 h = height / font_height;
2231 term_size(h, w, cfg.savelines);
2234 } else if (wParam == SIZE_RESTORED && was_zoomed) {
2236 if (cfg.resize_action == RESIZE_TERM)
2237 term_size(prev_rows, prev_cols, cfg.savelines);
2238 if (cfg.resize_action != RESIZE_FONT)
2243 /* This is an unexpected resize, these will normally happen
2244 * if the window is too large. Probably either the user
2245 * selected a huge font or the screen size has changed.
2247 * This is also called with minimize.
2249 else reset_window(-1);
2253 * Don't call back->size in mid-resize. (To prevent
2254 * massive numbers of resize events getting sent
2255 * down the connection during an NT opaque drag.)
2258 if (cfg.resize_action != RESIZE_FONT && !alt_pressed) {
2259 need_backend_resize = TRUE;
2260 w = (width-cfg.window_border*2) / font_width;
2262 h = (height-cfg.window_border*2) / font_height;
2271 sys_cursor_update();
2274 switch (LOWORD(wParam)) {
2288 term_scroll(0, +rows / 2);
2291 term_scroll(0, -rows / 2);
2293 case SB_THUMBPOSITION:
2295 term_scroll(1, HIWORD(wParam));
2299 case WM_PALETTECHANGED:
2300 if ((HWND) wParam != hwnd && pal != NULL) {
2301 HDC hdc = get_ctx();
2303 if (RealizePalette(hdc) > 0)
2309 case WM_QUERYNEWPALETTE:
2311 HDC hdc = get_ctx();
2313 if (RealizePalette(hdc) > 0)
2325 * Add the scan code and keypress timing to the random
2328 noise_ultralight(lParam);
2331 * We don't do TranslateMessage since it disassociates the
2332 * resulting CHAR message from the KEYDOWN that sparked it,
2333 * which we occasionally don't want. Instead, we process
2334 * KEYDOWN, and call the Win32 translator functions so that
2335 * we get the translations under _our_ control.
2338 unsigned char buf[20];
2341 if (wParam == VK_PROCESSKEY) {
2344 m.message = WM_KEYDOWN;
2346 m.lParam = lParam & 0xdfff;
2347 TranslateMessage(&m);
2349 len = TranslateKey(message, wParam, lParam, buf);
2351 return DefWindowProc(hwnd, message, wParam, lParam);
2355 * Interrupt an ongoing paste. I'm not sure
2356 * this is sensible, but for the moment it's
2357 * preferable to having to faff about buffering
2363 * We need not bother about stdin backlogs
2364 * here, because in GUI PuTTY we can't do
2365 * anything about it anyway; there's no means
2366 * of asking Windows to hold off on KEYDOWN
2367 * messages. We _have_ to buffer everything
2370 ldisc_send(buf, len, 1);
2375 net_pending_errors();
2377 case WM_INPUTLANGCHANGE:
2378 /* wParam == Font number */
2379 /* lParam == Locale */
2380 set_input_locale((HKL)lParam);
2381 sys_cursor_update();
2384 if(wParam == IMN_SETOPENSTATUS) {
2385 HIMC hImc = ImmGetContext(hwnd);
2386 ImmSetCompositionFont(hImc, &lfont);
2387 ImmReleaseContext(hwnd, hImc);
2391 case WM_IME_COMPOSITION:
2397 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ||
2398 osVersion.dwPlatformId == VER_PLATFORM_WIN32s) break; /* no Unicode */
2400 if ((lParam & GCS_RESULTSTR) == 0) /* Composition unfinished. */
2401 break; /* fall back to DefWindowProc */
2403 hIMC = ImmGetContext(hwnd);
2404 n = ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, NULL, 0);
2408 buff = (char*) smalloc(n);
2409 ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, buff, n);
2411 * Jaeyoun Chung reports that Korean character
2412 * input doesn't work correctly if we do a single
2413 * luni_send() covering the whole of buff. So
2414 * instead we luni_send the characters one by one.
2416 for (i = 0; i < n; i += 2)
2417 luni_send((unsigned short *)(buff+i), 1, 1);
2420 ImmReleaseContext(hwnd, hIMC);
2425 if (wParam & 0xFF00) {
2426 unsigned char buf[2];
2429 buf[0] = wParam >> 8;
2430 lpage_send(kbd_codepage, buf, 2, 1);
2432 char c = (unsigned char) wParam;
2433 lpage_send(kbd_codepage, &c, 1, 1);
2439 * Nevertheless, we are prepared to deal with WM_CHAR
2440 * messages, should they crop up. So if someone wants to
2441 * post the things to us as part of a macro manoeuvre,
2442 * we're ready to cope.
2445 char c = (unsigned char)wParam;
2446 lpage_send(CP_ACP, &c, 1, 1);
2450 if (send_raw_mouse && LOWORD(lParam) == HTCLIENT) {
2451 SetCursor(LoadCursor(NULL, IDC_ARROW));
2455 if (message == wm_mousewheel || message == WM_MOUSEWHEEL) {
2456 int shift_pressed=0, control_pressed=0, alt_pressed=0;
2458 if (message == WM_MOUSEWHEEL) {
2459 wheel_accumulator += (short)HIWORD(wParam);
2460 shift_pressed=LOWORD(wParam) & MK_SHIFT;
2461 control_pressed=LOWORD(wParam) & MK_CONTROL;
2464 wheel_accumulator += (int)wParam;
2465 if (GetKeyboardState(keys)!=0) {
2466 shift_pressed=keys[VK_SHIFT]&0x80;
2467 control_pressed=keys[VK_CONTROL]&0x80;
2471 /* process events when the threshold is reached */
2472 while (abs(wheel_accumulator) >= WHEEL_DELTA) {
2475 /* reduce amount for next time */
2476 if (wheel_accumulator > 0) {
2478 wheel_accumulator -= WHEEL_DELTA;
2479 } else if (wheel_accumulator < 0) {
2481 wheel_accumulator += WHEEL_DELTA;
2485 if (send_raw_mouse &&
2486 !(cfg.mouse_override && shift_pressed)) {
2487 /* send a mouse-down followed by a mouse up */
2490 TO_CHR_X(X_POS(lParam)),
2491 TO_CHR_Y(Y_POS(lParam)), shift_pressed,
2492 control_pressed, is_alt_pressed());
2493 term_mouse(b, MA_RELEASE, TO_CHR_X(X_POS(lParam)),
2494 TO_CHR_Y(Y_POS(lParam)), shift_pressed,
2495 control_pressed, is_alt_pressed());
2497 /* trigger a scroll */
2499 b == MBT_WHEEL_UP ? -rows / 2 : rows / 2);
2506 return DefWindowProc(hwnd, message, wParam, lParam);
2510 * Move the system caret. (We maintain one, even though it's
2511 * invisible, for the benefit of blind people: apparently some
2512 * helper software tracks the system caret, so we should arrange to
2515 void sys_cursor(int x, int y)
2519 if (!has_focus) return;
2522 * Avoid gratuitously re-updating the cursor position and IMM
2523 * window if there's no actual change required.
2525 cx = x * font_width + offset_width;
2526 cy = y * font_height + offset_height;
2527 if (cx == caret_x && cy == caret_y)
2532 sys_cursor_update();
2535 static void sys_cursor_update(void)
2540 if (!has_focus) return;
2542 if (caret_x < 0 || caret_y < 0)
2545 SetCaretPos(caret_x, caret_y);
2547 /* IMM calls on Win98 and beyond only */
2548 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32s) return; /* 3.11 */
2550 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS &&
2551 osVersion.dwMinorVersion == 0) return; /* 95 */
2553 /* we should have the IMM functions */
2554 hIMC = ImmGetContext(hwnd);
2555 cf.dwStyle = CFS_POINT;
2556 cf.ptCurrentPos.x = caret_x;
2557 cf.ptCurrentPos.y = caret_y;
2558 ImmSetCompositionWindow(hIMC, &cf);
2560 ImmReleaseContext(hwnd, hIMC);
2564 * Draw a line of text in the window, at given character
2565 * coordinates, in given attributes.
2567 * We are allowed to fiddle with the contents of `text'.
2569 void do_text(Context ctx, int x, int y, char *text, int len,
2570 unsigned long attr, int lattr)
2573 int nfg, nbg, nfont;
2576 int force_manual_underline = 0;
2577 int fnt_width = font_width * (1 + (lattr != LATTR_NORM));
2578 int char_width = fnt_width;
2579 int text_adjust = 0;
2580 static int *IpDx = 0, IpDxLEN = 0;
2582 if (attr & ATTR_WIDE)
2585 if (len > IpDxLEN || IpDx[0] != char_width) {
2587 if (len > IpDxLEN) {
2589 IpDx = smalloc((len + 16) * sizeof(int));
2590 IpDxLEN = (len + 16);
2592 for (i = 0; i < IpDxLEN; i++)
2593 IpDx[i] = char_width;
2596 /* Only want the left half of double width lines */
2597 if (lattr != LATTR_NORM && x*2 >= cols)
2605 if ((attr & TATTR_ACTCURS) && (cfg.cursor_type == 0 || big_cursor)) {
2606 attr &= ATTR_CUR_AND | (bold_mode != BOLD_COLOURS ? ATTR_BOLD : 0);
2607 attr ^= ATTR_CUR_XOR;
2611 if (cfg.vtmode == VT_POORMAN && lattr != LATTR_NORM) {
2612 /* Assume a poorman font is borken in other ways too. */
2622 nfont |= FONT_WIDE + FONT_HIGH;
2625 if (attr & ATTR_NARROW)
2626 nfont |= FONT_NARROW;
2628 /* Special hack for the VT100 linedraw glyphs. */
2629 if ((attr & CSET_MASK) == 0x2300) {
2630 if (text[0] >= (char) 0xBA && text[0] <= (char) 0xBD) {
2631 switch ((unsigned char) (text[0])) {
2633 text_adjust = -2 * font_height / 5;
2636 text_adjust = -1 * font_height / 5;
2639 text_adjust = font_height / 5;
2642 text_adjust = 2 * font_height / 5;
2645 if (lattr == LATTR_TOP || lattr == LATTR_BOT)
2648 text[0] = (char) (unitab_xterm['q'] & CHAR_MASK);
2649 attr |= (unitab_xterm['q'] & CSET_MASK);
2650 if (attr & ATTR_UNDER) {
2651 attr &= ~ATTR_UNDER;
2652 force_manual_underline = 1;
2657 /* Anything left as an original character set is unprintable. */
2658 if (DIRECT_CHAR(attr)) {
2661 memset(text, 0xFD, len);
2665 if ((attr & CSET_MASK) == ATTR_OEMCP)
2668 nfg = 2 * ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
2669 nbg = 2 * ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
2670 if (bold_mode == BOLD_FONT && (attr & ATTR_BOLD))
2672 if (und_mode == UND_FONT && (attr & ATTR_UNDER))
2673 nfont |= FONT_UNDERLINE;
2674 another_font(nfont);
2675 if (!fonts[nfont]) {
2676 if (nfont & FONT_UNDERLINE)
2677 force_manual_underline = 1;
2678 /* Don't do the same for manual bold, it could be bad news. */
2680 nfont &= ~(FONT_BOLD | FONT_UNDERLINE);
2682 another_font(nfont);
2684 nfont = FONT_NORMAL;
2685 if (attr & ATTR_REVERSE) {
2690 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BOLD))
2692 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BLINK))
2696 SelectObject(hdc, fonts[nfont]);
2697 SetTextColor(hdc, fg);
2698 SetBkColor(hdc, bg);
2699 SetBkMode(hdc, OPAQUE);
2702 line_box.right = x + char_width * len;
2703 line_box.bottom = y + font_height;
2705 /* Only want the left half of double width lines */
2706 if (line_box.right > font_width*cols+offset_width)
2707 line_box.right = font_width*cols+offset_width;
2709 /* We're using a private area for direct to font. (512 chars.) */
2710 if (dbcs_screenfont && (attr & CSET_MASK) == ATTR_ACP) {
2711 /* Ho Hum, dbcs fonts are a PITA! */
2712 /* To display on W9x I have to convert to UCS */
2713 static wchar_t *uni_buf = 0;
2714 static int uni_len = 0;
2716 if (len > uni_len) {
2718 uni_buf = smalloc((uni_len = len) * sizeof(wchar_t));
2721 for(nlen = mptr = 0; mptr<len; mptr++) {
2722 uni_buf[nlen] = 0xFFFD;
2723 if (IsDBCSLeadByteEx(font_codepage, (BYTE) text[mptr])) {
2724 IpDx[nlen] += char_width;
2725 MultiByteToWideChar(font_codepage, MB_USEGLYPHCHARS,
2726 text+mptr, 2, uni_buf+nlen, 1);
2731 MultiByteToWideChar(font_codepage, MB_USEGLYPHCHARS,
2732 text+mptr, 1, uni_buf+nlen, 1);
2740 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2741 ETO_CLIPPED | ETO_OPAQUE, &line_box, uni_buf, nlen, IpDx);
2742 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2743 SetBkMode(hdc, TRANSPARENT);
2744 ExtTextOutW(hdc, x - 1,
2745 y - font_height * (lattr ==
2746 LATTR_BOT) + text_adjust,
2747 ETO_CLIPPED, &line_box, uni_buf, nlen, IpDx);
2751 } else if (DIRECT_FONT(attr)) {
2753 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2754 ETO_CLIPPED | ETO_OPAQUE, &line_box, text, len, IpDx);
2755 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2756 SetBkMode(hdc, TRANSPARENT);
2758 /* GRR: This draws the character outside it's box and can leave
2759 * 'droppings' even with the clip box! I suppose I could loop it
2760 * one character at a time ... yuk.
2762 * Or ... I could do a test print with "W", and use +1 or -1 for this
2763 * shift depending on if the leftmost column is blank...
2765 ExtTextOut(hdc, x - 1,
2766 y - font_height * (lattr ==
2767 LATTR_BOT) + text_adjust,
2768 ETO_CLIPPED, &line_box, text, len, IpDx);
2771 /* And 'normal' unicode characters */
2772 static WCHAR *wbuf = NULL;
2773 static int wlen = 0;
2778 wbuf = smalloc(wlen * sizeof(WCHAR));
2780 for (i = 0; i < len; i++)
2781 wbuf[i] = (WCHAR) ((attr & CSET_MASK) + (text[i] & CHAR_MASK));
2784 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2785 ETO_CLIPPED | ETO_OPAQUE, &line_box, wbuf, len, IpDx);
2787 /* And the shadow bold hack. */
2788 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2789 SetBkMode(hdc, TRANSPARENT);
2790 ExtTextOutW(hdc, x - 1,
2791 y - font_height * (lattr ==
2792 LATTR_BOT) + text_adjust,
2793 ETO_CLIPPED, &line_box, wbuf, len, IpDx);
2796 if (lattr != LATTR_TOP && (force_manual_underline ||
2797 (und_mode == UND_LINE
2798 && (attr & ATTR_UNDER)))) {
2801 if (lattr == LATTR_BOT)
2802 dec = dec * 2 - font_height;
2804 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, fg));
2805 MoveToEx(hdc, x, y + dec, NULL);
2806 LineTo(hdc, x + len * char_width, y + dec);
2807 oldpen = SelectObject(hdc, oldpen);
2808 DeleteObject(oldpen);
2812 void do_cursor(Context ctx, int x, int y, char *text, int len,
2813 unsigned long attr, int lattr)
2819 int ctype = cfg.cursor_type;
2821 if ((attr & TATTR_ACTCURS) && (ctype == 0 || big_cursor)) {
2822 if (((attr & CSET_MASK) | (unsigned char) *text) != UCSWIDE) {
2823 do_text(ctx, x, y, text, len, attr, lattr);
2827 attr |= TATTR_RIGHTCURS;
2830 fnt_width = char_width = font_width * (1 + (lattr != LATTR_NORM));
2831 if (attr & ATTR_WIDE)
2838 if ((attr & TATTR_PASCURS) && (ctype == 0 || big_cursor)) {
2841 pts[0].x = pts[1].x = pts[4].x = x;
2842 pts[2].x = pts[3].x = x + char_width - 1;
2843 pts[0].y = pts[3].y = pts[4].y = y;
2844 pts[1].y = pts[2].y = y + font_height - 1;
2845 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2846 Polyline(hdc, pts, 5);
2847 oldpen = SelectObject(hdc, oldpen);
2848 DeleteObject(oldpen);
2849 } else if ((attr & (TATTR_ACTCURS | TATTR_PASCURS)) && ctype != 0) {
2850 int startx, starty, dx, dy, length, i;
2853 starty = y + descent;
2856 length = char_width;
2859 if (attr & TATTR_RIGHTCURS)
2860 xadjust = char_width - 1;
2861 startx = x + xadjust;
2865 length = font_height;
2867 if (attr & TATTR_ACTCURS) {
2870 SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2871 MoveToEx(hdc, startx, starty, NULL);
2872 LineTo(hdc, startx + dx * length, starty + dy * length);
2873 oldpen = SelectObject(hdc, oldpen);
2874 DeleteObject(oldpen);
2876 for (i = 0; i < length; i++) {
2878 SetPixel(hdc, startx, starty, colours[23]);
2887 /* This function gets the actual width of a character in the normal font.
2889 int CharWidth(Context ctx, int uc) {
2893 /* If the font max is the same as the font ave width then this
2894 * function is a no-op.
2896 if (!font_dualwidth) return 1;
2898 switch (uc & CSET_MASK) {
2900 uc = unitab_line[uc & 0xFF];
2903 uc = unitab_xterm[uc & 0xFF];
2906 uc = unitab_scoacs[uc & 0xFF];
2909 if (DIRECT_FONT(uc)) {
2910 if (dbcs_screenfont) return 1;
2912 /* Speedup, I know of no font where ascii is the wrong width */
2913 if ((uc&CHAR_MASK) >= ' ' && (uc&CHAR_MASK)<= '~')
2916 if ( (uc & CSET_MASK) == ATTR_ACP ) {
2917 SelectObject(hdc, fonts[FONT_NORMAL]);
2918 } else if ( (uc & CSET_MASK) == ATTR_OEMCP ) {
2919 another_font(FONT_OEM);
2920 if (!fonts[FONT_OEM]) return 0;
2922 SelectObject(hdc, fonts[FONT_OEM]);
2926 if ( GetCharWidth32(hdc, uc&CHAR_MASK, uc&CHAR_MASK, &ibuf) != 1 &&
2927 GetCharWidth(hdc, uc&CHAR_MASK, uc&CHAR_MASK, &ibuf) != 1)
2930 /* Speedup, I know of no font where ascii is the wrong width */
2931 if (uc >= ' ' && uc <= '~') return 1;
2933 SelectObject(hdc, fonts[FONT_NORMAL]);
2934 if ( GetCharWidth32W(hdc, uc, uc, &ibuf) == 1 )
2935 /* Okay that one worked */ ;
2936 else if ( GetCharWidthW(hdc, uc, uc, &ibuf) == 1 )
2937 /* This should work on 9x too, but it's "less accurate" */ ;
2942 ibuf += font_width / 2 -1;
2949 * Translate a WM_(SYS)?KEY(UP|DOWN) message into a string of ASCII
2950 * codes. Returns number of bytes used or zero to drop the message
2951 * or -1 to forward the message to windows.
2953 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
2954 unsigned char *output)
2957 int scan, left_alt = 0, key_down, shift_state;
2959 unsigned char *p = output;
2960 static int alt_sum = 0;
2962 HKL kbd_layout = GetKeyboardLayout(0);
2964 static WORD keys[3];
2965 static int compose_char = 0;
2966 static WPARAM compose_key = 0;
2968 r = GetKeyboardState(keystate);
2970 memset(keystate, 0, sizeof(keystate));
2973 #define SHOW_TOASCII_RESULT
2974 { /* Tell us all about key events */
2975 static BYTE oldstate[256];
2976 static int first = 1;
2980 memcpy(oldstate, keystate, sizeof(oldstate));
2983 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT) {
2985 } else if ((HIWORD(lParam) & KF_UP)
2986 && scan == (HIWORD(lParam) & 0xFF)) {
2990 if (wParam >= VK_F1 && wParam <= VK_F20)
2991 debug(("K_F%d", wParam + 1 - VK_F1));
3004 debug(("VK_%02x", wParam));
3006 if (message == WM_SYSKEYDOWN || message == WM_SYSKEYUP)
3008 debug((", S%02x", scan = (HIWORD(lParam) & 0xFF)));
3010 ch = MapVirtualKeyEx(wParam, 2, kbd_layout);
3011 if (ch >= ' ' && ch <= '~')
3012 debug((", '%c'", ch));
3014 debug((", $%02x", ch));
3017 debug((", KB0=%02x", keys[0]));
3019 debug((", KB1=%02x", keys[1]));
3021 debug((", KB2=%02x", keys[2]));
3023 if ((keystate[VK_SHIFT] & 0x80) != 0)
3025 if ((keystate[VK_CONTROL] & 0x80) != 0)
3027 if ((HIWORD(lParam) & KF_EXTENDED))
3029 if ((HIWORD(lParam) & KF_UP))
3033 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT);
3034 else if ((HIWORD(lParam) & KF_UP))
3035 oldstate[wParam & 0xFF] ^= 0x80;
3037 oldstate[wParam & 0xFF] ^= 0x81;
3039 for (ch = 0; ch < 256; ch++)
3040 if (oldstate[ch] != keystate[ch])
3041 debug((", M%02x=%02x", ch, keystate[ch]));
3043 memcpy(oldstate, keystate, sizeof(oldstate));
3047 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED)) {
3048 keystate[VK_RMENU] = keystate[VK_MENU];
3052 /* Nastyness with NUMLock - Shift-NUMLock is left alone though */
3053 if ((cfg.funky_type == 3 ||
3054 (cfg.funky_type <= 1 && app_keypad_keys && !cfg.no_applic_k))
3055 && wParam == VK_NUMLOCK && !(keystate[VK_SHIFT] & 0x80)) {
3057 wParam = VK_EXECUTE;
3059 /* UnToggle NUMLock */
3060 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0)
3061 keystate[VK_NUMLOCK] ^= 1;
3064 /* And write back the 'adjusted' state */
3065 SetKeyboardState(keystate);
3068 /* Disable Auto repeat if required */
3069 if (repeat_off && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT)
3072 if ((HIWORD(lParam) & KF_ALTDOWN) && (keystate[VK_RMENU] & 0x80) == 0)
3075 key_down = ((HIWORD(lParam) & KF_UP) == 0);
3077 /* Make sure Ctrl-ALT is not the same as AltGr for ToAscii unless told. */
3078 if (left_alt && (keystate[VK_CONTROL] & 0x80)) {
3079 if (cfg.ctrlaltkeys)
3080 keystate[VK_MENU] = 0;
3082 keystate[VK_RMENU] = 0x80;
3087 alt_pressed = (left_alt && key_down);
3089 scan = (HIWORD(lParam) & (KF_UP | KF_EXTENDED | 0xFF));
3090 shift_state = ((keystate[VK_SHIFT] & 0x80) != 0)
3091 + ((keystate[VK_CONTROL] & 0x80) != 0) * 2;
3093 /* Note if AltGr was pressed and if it was used as a compose key */
3094 if (!compose_state) {
3095 compose_key = 0x100;
3096 if (cfg.compose_key) {
3097 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED))
3098 compose_key = wParam;
3100 if (wParam == VK_APPS)
3101 compose_key = wParam;
3104 if (wParam == compose_key) {
3105 if (compose_state == 0
3106 && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0) compose_state =
3108 else if (compose_state == 1 && (HIWORD(lParam) & KF_UP))
3112 } else if (compose_state == 1 && wParam != VK_CONTROL)
3116 * Record that we pressed key so the scroll window can be reset, but
3117 * be careful to avoid Shift-UP/Down
3119 if (wParam != VK_SHIFT && wParam != VK_PRIOR && wParam != VK_NEXT &&
3120 wParam != VK_MENU && wParam != VK_CONTROL) {
3124 if (compose_state > 1 && left_alt)
3127 /* Sanitize the number pad if not using a PC NumPad */
3128 if (left_alt || (app_keypad_keys && !cfg.no_applic_k
3129 && cfg.funky_type != 2)
3130 || cfg.funky_type == 3 || cfg.nethack_keypad || compose_state) {
3131 if ((HIWORD(lParam) & KF_EXTENDED) == 0) {
3135 nParam = VK_NUMPAD0;
3138 nParam = VK_NUMPAD1;
3141 nParam = VK_NUMPAD2;
3144 nParam = VK_NUMPAD3;
3147 nParam = VK_NUMPAD4;
3150 nParam = VK_NUMPAD5;
3153 nParam = VK_NUMPAD6;
3156 nParam = VK_NUMPAD7;
3159 nParam = VK_NUMPAD8;
3162 nParam = VK_NUMPAD9;
3165 nParam = VK_DECIMAL;
3169 if (keystate[VK_NUMLOCK] & 1)
3176 /* If a key is pressed and AltGr is not active */
3177 if (key_down && (keystate[VK_RMENU] & 0x80) == 0 && !compose_state) {
3178 /* Okay, prepare for most alts then ... */
3182 /* Lets see if it's a pattern we know all about ... */
3183 if (wParam == VK_PRIOR && shift_state == 1) {
3184 SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0);
3187 if (wParam == VK_NEXT && shift_state == 1) {
3188 SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
3191 if (wParam == VK_INSERT && shift_state == 1) {
3195 if (left_alt && wParam == VK_F4 && cfg.alt_f4) {
3198 if (left_alt && wParam == VK_SPACE && cfg.alt_space) {
3199 SendMessage(hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0);
3202 if (left_alt && wParam == VK_RETURN && cfg.fullscreenonaltenter &&
3203 (cfg.resize_action != RESIZE_DISABLED)) {
3204 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) != KF_REPEAT)
3208 /* Control-Numlock for app-keypad mode switch */
3209 if (wParam == VK_PAUSE && shift_state == 2) {
3210 app_keypad_keys ^= 1;
3214 /* Nethack keypad */
3215 if (cfg.nethack_keypad && !left_alt) {
3218 *p++ = shift_state ? 'B' : 'b';
3221 *p++ = shift_state ? 'J' : 'j';
3224 *p++ = shift_state ? 'N' : 'n';
3227 *p++ = shift_state ? 'H' : 'h';
3230 *p++ = shift_state ? '.' : '.';
3233 *p++ = shift_state ? 'L' : 'l';
3236 *p++ = shift_state ? 'Y' : 'y';
3239 *p++ = shift_state ? 'K' : 'k';
3242 *p++ = shift_state ? 'U' : 'u';
3247 /* Application Keypad */
3251 if (cfg.funky_type == 3 ||
3252 (cfg.funky_type <= 1 &&
3253 app_keypad_keys && !cfg.no_applic_k)) switch (wParam) {
3267 if (app_keypad_keys && !cfg.no_applic_k)
3304 if (cfg.funky_type == 2) {
3309 } else if (shift_state)
3316 if (cfg.funky_type == 2)
3320 if (cfg.funky_type == 2)
3324 if (cfg.funky_type == 2)
3329 if (HIWORD(lParam) & KF_EXTENDED)
3335 if (xkey >= 'P' && xkey <= 'S')
3336 p += sprintf((char *) p, "\x1B%c", xkey);
3338 p += sprintf((char *) p, "\x1B?%c", xkey);
3340 p += sprintf((char *) p, "\x1BO%c", xkey);
3345 if (wParam == VK_BACK && shift_state == 0) { /* Backspace */
3346 *p++ = (cfg.bksp_is_delete ? 0x7F : 0x08);
3350 if (wParam == VK_TAB && shift_state == 1) { /* Shift tab */
3356 if (wParam == VK_SPACE && shift_state == 2) { /* Ctrl-Space */
3360 if (wParam == VK_SPACE && shift_state == 3) { /* Ctrl-Shift-Space */
3364 if (wParam == VK_CANCEL && shift_state == 2) { /* Ctrl-Break */
3369 if (wParam == VK_PAUSE) { /* Break/Pause */
3374 /* Control-2 to Control-8 are special */
3375 if (shift_state == 2 && wParam >= '2' && wParam <= '8') {
3376 *p++ = "\000\033\034\035\036\037\177"[wParam - '2'];
3379 if (shift_state == 2 && wParam == 0xBD) {
3383 if (shift_state == 2 && wParam == 0xDF) {
3387 if (shift_state == 0 && wParam == VK_RETURN && cr_lf_return) {
3394 * Next, all the keys that do tilde codes. (ESC '[' nn '~',
3395 * for integer decimal nn.)
3397 * We also deal with the weird ones here. Linux VCs replace F1
3398 * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
3399 * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
3405 code = (keystate[VK_SHIFT] & 0x80 ? 23 : 11);
3408 code = (keystate[VK_SHIFT] & 0x80 ? 24 : 12);
3411 code = (keystate[VK_SHIFT] & 0x80 ? 25 : 13);
3414 code = (keystate[VK_SHIFT] & 0x80 ? 26 : 14);
3417 code = (keystate[VK_SHIFT] & 0x80 ? 28 : 15);
3420 code = (keystate[VK_SHIFT] & 0x80 ? 29 : 17);
3423 code = (keystate[VK_SHIFT] & 0x80 ? 31 : 18);
3426 code = (keystate[VK_SHIFT] & 0x80 ? 32 : 19);
3429 code = (keystate[VK_SHIFT] & 0x80 ? 33 : 20);
3432 code = (keystate[VK_SHIFT] & 0x80 ? 34 : 21);
3465 if ((shift_state&2) == 0) switch (wParam) {
3485 /* Reorder edit keys to physical order */
3486 if (cfg.funky_type == 3 && code <= 6)
3487 code = "\0\2\1\4\5\3\6"[code];
3489 if (vt52_mode && code > 0 && code <= 6) {
3490 p += sprintf((char *) p, "\x1B%c", " HLMEIG"[code]);
3494 if (cfg.funky_type == 5 && /* SCO function keys */
3495 code >= 11 && code <= 34) {
3496 char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
3499 case VK_F1: index = 0; break;
3500 case VK_F2: index = 1; break;
3501 case VK_F3: index = 2; break;
3502 case VK_F4: index = 3; break;
3503 case VK_F5: index = 4; break;
3504 case VK_F6: index = 5; break;
3505 case VK_F7: index = 6; break;
3506 case VK_F8: index = 7; break;
3507 case VK_F9: index = 8; break;
3508 case VK_F10: index = 9; break;
3509 case VK_F11: index = 10; break;
3510 case VK_F12: index = 11; break;
3512 if (keystate[VK_SHIFT] & 0x80) index += 12;
3513 if (keystate[VK_CONTROL] & 0x80) index += 24;
3514 p += sprintf((char *) p, "\x1B[%c", codes[index]);
3517 if (cfg.funky_type == 5 && /* SCO small keypad */
3518 code >= 1 && code <= 6) {
3519 char codes[] = "HL.FIG";
3523 p += sprintf((char *) p, "\x1B[%c", codes[code-1]);
3527 if ((vt52_mode || cfg.funky_type == 4) && code >= 11 && code <= 24) {
3534 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11 - offt);
3537 sprintf((char *) p, "\x1BO%c", code + 'P' - 11 - offt);
3540 if (cfg.funky_type == 1 && code >= 11 && code <= 15) {
3541 p += sprintf((char *) p, "\x1B[[%c", code + 'A' - 11);
3544 if (cfg.funky_type == 2 && code >= 11 && code <= 14) {
3546 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11);
3548 p += sprintf((char *) p, "\x1BO%c", code + 'P' - 11);
3551 if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
3552 p += sprintf((char *) p, code == 1 ? "\x1B[H" : "\x1BOw");
3556 p += sprintf((char *) p, "\x1B[%d~", code);
3561 * Now the remaining keys (arrows and Keypad 5. Keypad 5 for
3562 * some reason seems to send VK_CLEAR to Windows...).
3585 p += sprintf((char *) p, "\x1B%c", xkey);
3587 int app_flg = (app_cursor_keys && !cfg.no_applic_c);
3590 * RDB: VT100 & VT102 manuals both state the
3591 * app cursor keys only work if the app keypad
3594 * SGT: That may well be true, but xterm
3595 * disagrees and so does at least one
3596 * application, so I've #if'ed this out and the
3597 * behaviour is back to PuTTY's original: app
3598 * cursor and app keypad are independently
3599 * switchable modes. If anyone complains about
3600 * _this_ I'll have to put in a configurable
3603 if (!app_keypad_keys)
3606 /* Useful mapping of Ctrl-arrows */
3607 if (shift_state == 2)
3611 p += sprintf((char *) p, "\x1BO%c", xkey);
3613 p += sprintf((char *) p, "\x1B[%c", xkey);
3620 * Finally, deal with Return ourselves. (Win95 seems to
3621 * foul it up when Alt is pressed, for some reason.)
3623 if (wParam == VK_RETURN) { /* Return */
3629 if (left_alt && wParam >= VK_NUMPAD0 && wParam <= VK_NUMPAD9)
3630 alt_sum = alt_sum * 10 + wParam - VK_NUMPAD0;
3635 /* Okay we've done everything interesting; let windows deal with
3636 * the boring stuff */
3640 /* helg: clear CAPS LOCK state if caps lock switches to cyrillic */
3641 if(cfg.xlat_capslockcyr && keystate[VK_CAPITAL] != 0) {
3643 keystate[VK_CAPITAL] = 0;
3646 r = ToAsciiEx(wParam, scan, keystate, keys, 0, kbd_layout);
3647 #ifdef SHOW_TOASCII_RESULT
3648 if (r == 1 && !key_down) {
3650 if (in_utf || dbcs_screenfont)
3651 debug((", (U+%04x)", alt_sum));
3653 debug((", LCH(%d)", alt_sum));
3655 debug((", ACH(%d)", keys[0]));
3660 for (r1 = 0; r1 < r; r1++) {
3661 debug(("%s%d", r1 ? "," : "", keys[r1]));
3670 * Interrupt an ongoing paste. I'm not sure this is
3671 * sensible, but for the moment it's preferable to
3672 * having to faff about buffering things.
3677 for (i = 0; i < r; i++) {
3678 unsigned char ch = (unsigned char) keys[i];
3680 if (compose_state == 2 && (ch & 0x80) == 0 && ch > ' ') {
3685 if (compose_state == 3 && (ch & 0x80) == 0 && ch > ' ') {
3689 if ((nc = check_compose(compose_char, ch)) == -1) {
3690 MessageBeep(MB_ICONHAND);
3694 luni_send(&keybuf, 1, 1);
3702 if (in_utf || dbcs_screenfont) {
3704 luni_send(&keybuf, 1, 1);
3706 ch = (char) alt_sum;
3708 * We need not bother about stdin
3709 * backlogs here, because in GUI PuTTY
3710 * we can't do anything about it
3711 * anyway; there's no means of asking
3712 * Windows to hold off on KEYDOWN
3713 * messages. We _have_ to buffer
3714 * everything we're sent.
3716 ldisc_send(&ch, 1, 1);
3720 lpage_send(kbd_codepage, &ch, 1, 1);
3722 if(capsOn && ch < 0x80) {
3725 cbuf[1] = xlat_uskbd2cyrllic(ch);
3726 luni_send(cbuf+!left_alt, 1+!!left_alt, 1);
3731 lpage_send(kbd_codepage, cbuf+!left_alt, 1+!!left_alt, 1);
3737 /* This is so the ALT-Numpad and dead keys work correctly. */
3742 /* If we're definitly not building up an ALT-54321 then clear it */
3745 /* If we will be using alt_sum fix the 256s */
3746 else if (keys[0] && (in_utf || dbcs_screenfont))
3751 * ALT alone may or may not want to bring up the System menu.
3752 * If it's not meant to, we return 0 on presses or releases of
3753 * ALT, to show that we've swallowed the keystroke. Otherwise
3754 * we return -1, which means Windows will give the keystroke
3755 * its default handling (i.e. bring up the System menu).
3757 if (wParam == VK_MENU && !cfg.alt_only)
3763 void set_title(char *title)
3766 window_name = smalloc(1 + strlen(title));
3767 strcpy(window_name, title);
3768 if (cfg.win_name_always || !IsIconic(hwnd))
3769 SetWindowText(hwnd, title);
3772 void set_icon(char *title)
3775 icon_name = smalloc(1 + strlen(title));
3776 strcpy(icon_name, title);
3777 if (!cfg.win_name_always && IsIconic(hwnd))
3778 SetWindowText(hwnd, title);
3781 void set_sbar(int total, int start, int page)
3785 if (is_full_screen() ? !cfg.scrollbar_in_fullscreen : !cfg.scrollbar)
3788 si.cbSize = sizeof(si);
3789 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
3791 si.nMax = total - 1;
3795 SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
3798 Context get_ctx(void)
3804 SelectPalette(hdc, pal, FALSE);
3810 void free_ctx(Context ctx)
3812 SelectPalette(ctx, GetStockObject(DEFAULT_PALETTE), FALSE);
3813 ReleaseDC(hwnd, ctx);
3816 static void real_palette_set(int n, int r, int g, int b)
3819 logpal->palPalEntry[n].peRed = r;
3820 logpal->palPalEntry[n].peGreen = g;
3821 logpal->palPalEntry[n].peBlue = b;
3822 logpal->palPalEntry[n].peFlags = PC_NOCOLLAPSE;
3823 colours[n] = PALETTERGB(r, g, b);
3824 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3826 colours[n] = RGB(r, g, b);
3829 void palette_set(int n, int r, int g, int b)
3831 static const int first[21] = {
3832 0, 2, 4, 6, 8, 10, 12, 14,
3833 1, 3, 5, 7, 9, 11, 13, 15,
3836 real_palette_set(first[n], r, g, b);
3838 real_palette_set(first[n] + 1, r, g, b);
3840 HDC hdc = get_ctx();
3841 UnrealizeObject(pal);
3842 RealizePalette(hdc);
3847 void palette_reset(void)
3851 for (i = 0; i < NCOLOURS; i++) {
3853 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
3854 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
3855 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
3856 logpal->palPalEntry[i].peFlags = 0;
3857 colours[i] = PALETTERGB(defpal[i].rgbtRed,
3858 defpal[i].rgbtGreen,
3859 defpal[i].rgbtBlue);
3861 colours[i] = RGB(defpal[i].rgbtRed,
3862 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
3867 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3869 RealizePalette(hdc);
3874 void write_aclip(char *data, int len, int must_deselect)
3879 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len + 1);
3882 lock = GlobalLock(clipdata);
3885 memcpy(lock, data, len);
3886 ((unsigned char *) lock)[len] = 0;
3887 GlobalUnlock(clipdata);
3890 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
3892 if (OpenClipboard(hwnd)) {
3894 SetClipboardData(CF_TEXT, clipdata);
3897 GlobalFree(clipdata);
3900 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
3904 * Note: unlike write_aclip() this will not append a nul.
3906 void write_clip(wchar_t * data, int len, int must_deselect)
3908 HGLOBAL clipdata, clipdata2, clipdata3;
3910 void *lock, *lock2, *lock3;
3912 len2 = WideCharToMultiByte(CP_ACP, 0, data, len, 0, 0, NULL, NULL);
3914 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE,
3915 len * sizeof(wchar_t));
3916 clipdata2 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len2);
3918 if (!clipdata || !clipdata2) {
3920 GlobalFree(clipdata);
3922 GlobalFree(clipdata2);
3925 if (!(lock = GlobalLock(clipdata)))
3927 if (!(lock2 = GlobalLock(clipdata2)))
3930 memcpy(lock, data, len * sizeof(wchar_t));
3931 WideCharToMultiByte(CP_ACP, 0, data, len, lock2, len2, NULL, NULL);
3933 if (cfg.rtf_paste) {
3934 wchar_t unitab[256];
3936 unsigned char *tdata = (unsigned char *)lock2;
3937 wchar_t *udata = (wchar_t *)lock;
3938 int rtflen = 0, uindex = 0, tindex = 0;
3940 int multilen, blen, alen, totallen, i;
3941 char before[16], after[4];
3943 get_unitab(CP_ACP, unitab, 0);
3945 rtfsize = 100 + strlen(cfg.font);
3946 rtf = smalloc(rtfsize);
3947 sprintf(rtf, "{\\rtf1\\ansi%d{\\fonttbl\\f0\\fmodern %s;}\\f0",
3948 GetACP(), cfg.font);
3949 rtflen = strlen(rtf);
3952 * We want to construct a piece of RTF that specifies the
3953 * same Unicode text. To do this we will read back in
3954 * parallel from the Unicode data in `udata' and the
3955 * non-Unicode data in `tdata'. For each character in
3956 * `tdata' which becomes the right thing in `udata' when
3957 * looked up in `unitab', we just copy straight over from
3958 * tdata. For each one that doesn't, we must WCToMB it
3959 * individually and produce a \u escape sequence.
3961 * It would probably be more robust to just bite the bullet
3962 * and WCToMB each individual Unicode character one by one,
3963 * then MBToWC each one back to see if it was an accurate
3964 * translation; but that strikes me as a horrifying number
3965 * of Windows API calls so I want to see if this faster way
3966 * will work. If it screws up badly we can always revert to
3967 * the simple and slow way.
3969 while (tindex < len2 && uindex < len &&
3970 tdata[tindex] && udata[uindex]) {
3971 if (tindex + 1 < len2 &&
3972 tdata[tindex] == '\r' &&
3973 tdata[tindex+1] == '\n') {
3977 if (unitab[tdata[tindex]] == udata[uindex]) {
3983 multilen = WideCharToMultiByte(CP_ACP, 0, unitab+uindex, 1,
3984 NULL, 0, NULL, NULL);
3985 if (multilen != 1) {
3986 blen = sprintf(before, "{\\uc%d\\u%d", multilen,
3988 alen = 1; strcpy(after, "}");
3990 blen = sprintf(before, "\\u%d", udata[uindex]);
3991 alen = 0; after[0] = '\0';
3994 assert(tindex + multilen <= len2);
3995 totallen = blen + alen;
3996 for (i = 0; i < multilen; i++) {
3997 if (tdata[tindex+i] == '\\' ||
3998 tdata[tindex+i] == '{' ||
3999 tdata[tindex+i] == '}')
4001 else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A)
4002 totallen += 6; /* \par\r\n */
4003 else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20)
4009 if (rtfsize < rtflen + totallen + 3) {
4010 rtfsize = rtflen + totallen + 512;
4011 rtf = srealloc(rtf, rtfsize);
4014 strcpy(rtf + rtflen, before); rtflen += blen;
4015 for (i = 0; i < multilen; i++) {
4016 if (tdata[tindex+i] == '\\' ||
4017 tdata[tindex+i] == '{' ||
4018 tdata[tindex+i] == '}') {
4019 rtf[rtflen++] = '\\';
4020 rtf[rtflen++] = tdata[tindex+i];
4021 } else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A) {
4022 rtflen += sprintf(rtf+rtflen, "\\par\r\n");
4023 } else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20) {
4024 rtflen += sprintf(rtf+rtflen, "\\'%02x", tdata[tindex+i]);
4026 rtf[rtflen++] = tdata[tindex+i];
4029 strcpy(rtf + rtflen, after); rtflen += alen;
4035 strcpy(rtf + rtflen, "}");
4038 clipdata3 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, rtflen);
4039 if (clipdata3 && (lock3 = GlobalLock(clipdata3)) != NULL) {
4041 GlobalUnlock(clipdata3);
4047 GlobalUnlock(clipdata);
4048 GlobalUnlock(clipdata2);
4051 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
4053 if (OpenClipboard(hwnd)) {
4055 SetClipboardData(CF_UNICODETEXT, clipdata);
4056 SetClipboardData(CF_TEXT, clipdata2);
4058 SetClipboardData(RegisterClipboardFormat(CF_RTF), clipdata3);
4061 GlobalFree(clipdata);
4062 GlobalFree(clipdata2);
4066 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
4069 void get_clip(wchar_t ** p, int *len)
4071 static HGLOBAL clipdata = NULL;
4072 static wchar_t *converted = 0;
4081 GlobalUnlock(clipdata);
4084 } else if (OpenClipboard(NULL)) {
4085 if ((clipdata = GetClipboardData(CF_UNICODETEXT))) {
4087 *p = GlobalLock(clipdata);
4089 for (p2 = *p; *p2; p2++);
4093 } else if ( (clipdata = GetClipboardData(CF_TEXT)) ) {
4097 s = GlobalLock(clipdata);
4098 i = MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, 0, 0);
4099 *p = converted = smalloc(i * sizeof(wchar_t));
4100 MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, converted, i);
4113 * Move `lines' lines from position `from' to position `to' in the
4116 void optimised_move(int to, int from, int lines)
4121 min = (to < from ? to : from);
4122 max = to + from - min;
4124 r.left = offset_width;
4125 r.right = offset_width + cols * font_width;
4126 r.top = offset_height + min * font_height;
4127 r.bottom = offset_height + (max + lines) * font_height;
4128 ScrollWindow(hwnd, 0, (to - from) * font_height, &r, &r);
4133 * Print a message box and perform a fatal exit.
4135 void fatalbox(char *fmt, ...)
4141 vsprintf(stuff, fmt, ap);
4143 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
4148 * Manage window caption / taskbar flashing, if enabled.
4149 * 0 = stop, 1 = maintain, 2 = start
4151 static void flash_window(int mode)
4153 static long last_flash = 0;
4154 static int flashing = 0;
4155 if ((mode == 0) || (cfg.beep_ind == B_IND_DISABLED)) {
4158 FlashWindow(hwnd, FALSE);
4162 } else if (mode == 2) {
4165 last_flash = GetTickCount();
4167 FlashWindow(hwnd, TRUE);
4170 } else if ((mode == 1) && (cfg.beep_ind == B_IND_FLASH)) {
4173 long now = GetTickCount();
4174 long fdiff = now - last_flash;
4175 if (fdiff < 0 || fdiff > 450) {
4177 FlashWindow(hwnd, TRUE); /* toggle */
4188 if (mode == BELL_DEFAULT) {
4190 * For MessageBeep style bells, we want to be careful of
4191 * timing, because they don't have the nice property of
4192 * PlaySound bells that each one cancels the previous
4193 * active one. So we limit the rate to one per 50ms or so.
4195 static long lastbeep = 0;
4198 beepdiff = GetTickCount() - lastbeep;
4199 if (beepdiff >= 0 && beepdiff < 50)
4203 * The above MessageBeep call takes time, so we record the
4204 * time _after_ it finishes rather than before it starts.
4206 lastbeep = GetTickCount();
4207 } else if (mode == BELL_WAVEFILE) {
4208 if (!PlaySound(cfg.bell_wavefile, NULL, SND_ASYNC | SND_FILENAME)) {
4209 char buf[sizeof(cfg.bell_wavefile) + 80];
4210 sprintf(buf, "Unable to play sound file\n%s\n"
4211 "Using default sound instead", cfg.bell_wavefile);
4212 MessageBox(hwnd, buf, "PuTTY Sound Error",
4213 MB_OK | MB_ICONEXCLAMATION);
4214 cfg.beep = BELL_DEFAULT;
4217 /* Otherwise, either visual bell or disabled; do nothing here */
4219 flash_window(2); /* start */
4224 * Minimise or restore the window in response to a server-side
4227 void set_iconic(int iconic)
4229 if (IsIconic(hwnd)) {
4231 ShowWindow(hwnd, SW_RESTORE);
4234 ShowWindow(hwnd, SW_MINIMIZE);
4239 * Move the window in response to a server-side request.
4241 void move_window(int x, int y)
4243 if (cfg.resize_action == RESIZE_DISABLED ||
4244 cfg.resize_action == RESIZE_FONT ||
4248 SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
4252 * Move the window to the top or bottom of the z-order in response
4253 * to a server-side request.
4255 void set_zorder(int top)
4257 if (cfg.alwaysontop)
4258 return; /* ignore */
4259 SetWindowPos(hwnd, top ? HWND_TOP : HWND_BOTTOM, 0, 0, 0, 0,
4260 SWP_NOMOVE | SWP_NOSIZE);
4264 * Refresh the window in response to a server-side request.
4266 void refresh_window(void)
4268 InvalidateRect(hwnd, NULL, TRUE);
4272 * Maximise or restore the window in response to a server-side
4275 void set_zoomed(int zoomed)
4277 if (IsZoomed(hwnd)) {
4279 ShowWindow(hwnd, SW_RESTORE);
4282 ShowWindow(hwnd, SW_MAXIMIZE);
4287 * Report whether the window is iconic, for terminal reports.
4291 return IsIconic(hwnd);
4295 * Report the window's position, for terminal reports.
4297 void get_window_pos(int *x, int *y)
4300 GetWindowRect(hwnd, &r);
4306 * Report the window's pixel size, for terminal reports.
4308 void get_window_pixels(int *x, int *y)
4311 GetWindowRect(hwnd, &r);
4312 *x = r.right - r.left;
4313 *y = r.bottom - r.top;
4317 * Return the window or icon title.
4319 char *get_window_title(int icon)
4321 return icon ? icon_name : window_name;
4325 * See if we're in full-screen mode.
4327 int is_full_screen()
4329 if (!IsZoomed(hwnd))
4331 if (GetWindowLong(hwnd, GWL_STYLE) & WS_CAPTION)
4337 * Go full-screen. This should only be called when we are already
4340 void make_full_screen()
4345 assert(IsZoomed(hwnd));
4347 /* Remove the window furniture. */
4348 style = GetWindowLong(hwnd, GWL_STYLE);
4349 style &= ~(WS_CAPTION | WS_BORDER | WS_THICKFRAME);
4350 if (cfg.scrollbar_in_fullscreen)
4351 style |= WS_VSCROLL;
4353 style &= ~WS_VSCROLL;
4354 SetWindowLong(hwnd, GWL_STYLE, style);
4356 /* Resize ourselves to exactly cover the nearest monitor. */
4357 #ifdef MONITOR_DEFAULTTONEAREST
4361 mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
4362 mi.cbSize = sizeof(mi);
4363 GetMonitorInfo(mon, &mi);
4364 x = mi.rcMonitor.left;
4365 y = mi.rcMonitor.top;
4366 w = mi.rcMonitor.right;
4367 h = mi.rcMonitor.bottom;
4371 w = GetSystemMetrics(SM_CXSCREEN);
4372 h = GetSystemMetrics(SM_CYSCREEN);
4374 SetWindowPos(hwnd, HWND_TOP, x, y, w, h, SWP_FRAMECHANGED);
4376 /* Tick the menu item in the System menu. */
4377 CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN,
4382 * Clear the full-screen attributes.
4384 void clear_full_screen()
4386 DWORD oldstyle, style;
4388 /* Reinstate the window furniture. */
4389 style = oldstyle = GetWindowLong(hwnd, GWL_STYLE);
4390 style |= WS_CAPTION | WS_BORDER;
4391 if (cfg.resize_action == RESIZE_DISABLED)
4392 style &= ~WS_THICKFRAME;
4394 style |= WS_THICKFRAME;
4396 style |= WS_VSCROLL;
4398 style &= ~WS_VSCROLL;
4399 if (style != oldstyle) {
4400 SetWindowLong(hwnd, GWL_STYLE, style);
4401 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
4402 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
4406 /* Untick the menu item in the System menu. */
4407 CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN,
4412 * Toggle full-screen mode.
4414 void flip_full_screen()
4416 if (is_full_screen()) {
4417 ShowWindow(hwnd, SW_RESTORE);
4418 } else if (IsZoomed(hwnd)) {
4421 SendMessage(hwnd, WM_FULLSCR_ON_MAX, 0, 0);
4422 ShowWindow(hwnd, SW_MAXIMIZE);