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);
110 static int get_fullscreen_rect(RECT * ss);
112 static time_t last_movement = 0;
114 static int caret_x = -1, caret_y = -1;
116 #define FONT_NORMAL 0
118 #define FONT_UNDERLINE 2
119 #define FONT_BOLDUND 3
120 #define FONT_WIDE 0x04
121 #define FONT_HIGH 0x08
122 #define FONT_NARROW 0x10
124 #define FONT_OEM 0x20
125 #define FONT_OEMBOLD 0x21
126 #define FONT_OEMUND 0x22
127 #define FONT_OEMBOLDUND 0x23
129 #define FONT_MAXNO 0x2F
131 static HFONT fonts[FONT_MAXNO];
132 static LOGFONT lfont;
133 static int fontflag[FONT_MAXNO];
135 BOLD_COLOURS, BOLD_SHADOW, BOLD_FONT
143 static COLORREF colours[NCOLOURS];
145 static LPLOGPALETTE logpal;
146 static RGBTRIPLE defpal[NCOLOURS];
150 static HBITMAP caretbm;
152 static int dbltime, lasttime, lastact;
153 static Mouse_Button lastbtn;
155 /* this allows xterm-style mouse handling. */
156 static int send_raw_mouse = 0;
157 static int wheel_accumulator = 0;
159 static char *window_name, *icon_name;
161 static int compose_state = 0;
163 static int wsa_started = 0;
165 static OSVERSIONINFO osVersion;
167 static UINT wm_mousewheel = WM_MOUSEWHEEL;
169 /* Dummy routine, only required in plink. */
170 void ldisc_update(int echo, int edit)
174 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
176 static char appname[] = "PuTTY";
181 int guess_width, guess_height;
184 flags = FLAG_VERBOSE | FLAG_INTERACTIVE;
186 winsock_ver = MAKEWORD(1, 1);
187 if (WSAStartup(winsock_ver, &wsadata)) {
188 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
189 MB_OK | MB_ICONEXCLAMATION);
192 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
193 MessageBox(NULL, "WinSock version is incompatible with 1.1",
194 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
199 /* WISHLIST: maybe allow config tweaking even if winsock not present? */
202 InitCommonControls();
204 /* Ensure a Maximize setting in Explorer doesn't maximise the
209 ZeroMemory(&osVersion, sizeof(osVersion));
210 osVersion.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
211 if (!GetVersionEx ( (OSVERSIONINFO *) &osVersion)) {
212 MessageBox(NULL, "Windows refuses to report a version",
213 "PuTTY Fatal Error", MB_OK | MB_ICONEXCLAMATION);
219 * If we're running a version of Windows that doesn't support
220 * WM_MOUSEWHEEL, find out what message number we should be
223 if (osVersion.dwMajorVersion < 4 ||
224 (osVersion.dwMajorVersion == 4 &&
225 osVersion.dwPlatformId != VER_PLATFORM_WIN32_NT))
226 wm_mousewheel = RegisterWindowMessage("MSWHEEL_ROLLMSG");
229 * See if we can find our Help file.
232 char b[2048], *p, *q, *r;
234 GetModuleFileName(NULL, b, sizeof(b) - 1);
236 p = strrchr(b, '\\');
237 if (p && p >= r) r = p+1;
239 if (q && q >= r) r = q+1;
240 strcpy(r, "putty.hlp");
241 if ( (fp = fopen(b, "r")) != NULL) {
242 help_path = dupstr(b);
246 strcpy(r, "putty.cnt");
247 if ( (fp = fopen(b, "r")) != NULL) {
248 help_has_contents = TRUE;
251 help_has_contents = FALSE;
255 * Process the command line.
260 default_protocol = DEFAULT_PROTOCOL;
261 default_port = DEFAULT_PORT;
262 cfg.logtype = LGTYP_NONE;
264 do_defaults(NULL, &cfg);
267 while (*p && isspace(*p))
271 * Process command line options first. Yes, this can be
272 * done better, and it will be as soon as I have the
276 char *q = p + strcspn(p, " \t");
279 tolower(p[0]) == 's' &&
280 tolower(p[1]) == 's' && tolower(p[2]) == 'h') {
281 default_protocol = cfg.protocol = PROT_SSH;
282 default_port = cfg.port = 22;
283 } else if (q == p + 7 &&
284 tolower(p[0]) == 'c' &&
285 tolower(p[1]) == 'l' &&
286 tolower(p[2]) == 'e' &&
287 tolower(p[3]) == 'a' &&
288 tolower(p[4]) == 'n' &&
289 tolower(p[5]) == 'u' && tolower(p[6]) == 'p') {
291 * `putty -cleanup'. Remove all registry entries
292 * associated with PuTTY, and also find and delete
293 * the random seed file.
296 "This procedure will remove ALL Registry\n"
297 "entries associated with PuTTY, and will\n"
298 "also remove the PuTTY random seed file.\n"
300 "THIS PROCESS WILL DESTROY YOUR SAVED\n"
301 "SESSIONS. Are you really sure you want\n"
304 MB_YESNO | MB_ICONWARNING) == IDYES) {
309 p = q + strspn(q, " \t");
313 * An initial @ means to activate a saved session.
317 while (i > 1 && isspace(p[i - 1]))
320 do_defaults(p + 1, &cfg);
321 if (!*cfg.host && !do_config()) {
325 } else if (*p == '&') {
327 * An initial & means we've been given a command line
328 * containing the hex value of a HANDLE for a file
329 * mapping object, which we must then extract as a
334 if (sscanf(p + 1, "%p", &filemap) == 1 &&
335 (cp = MapViewOfFile(filemap, FILE_MAP_READ,
336 0, 0, sizeof(Config))) != NULL) {
339 CloseHandle(filemap);
340 } else if (!do_config()) {
347 * If the hostname starts with "telnet:", set the
348 * protocol to Telnet and process the string as a
351 if (!strncmp(q, "telnet:", 7)) {
355 if (q[0] == '/' && q[1] == '/')
357 cfg.protocol = PROT_TELNET;
359 while (*p && *p != ':' && *p != '/')
368 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
369 cfg.host[sizeof(cfg.host) - 1] = '\0';
371 while (*p && !isspace(*p))
375 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
376 cfg.host[sizeof(cfg.host) - 1] = '\0';
377 while (*p && isspace(*p))
392 * Trim leading whitespace off the hostname if it's there.
395 int space = strspn(cfg.host, " \t");
396 memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
399 /* See if host is of the form user@host */
400 if (cfg.host[0] != '\0') {
401 char *atsign = strchr(cfg.host, '@');
402 /* Make sure we're not overflowing the user field */
404 if (atsign - cfg.host < sizeof cfg.username) {
405 strncpy(cfg.username, cfg.host, atsign - cfg.host);
406 cfg.username[atsign - cfg.host] = '\0';
408 memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
413 * Trim a colon suffix off the hostname if it's there.
415 cfg.host[strcspn(cfg.host, ":")] = '\0';
419 * Select protocol. This is farmed out into a table in a
420 * separate file to enable an ssh-free variant.
425 for (i = 0; backends[i].backend != NULL; i++)
426 if (backends[i].protocol == cfg.protocol) {
427 back = backends[i].backend;
431 MessageBox(NULL, "Unsupported protocol number found",
432 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
438 /* Check for invalid Port number (i.e. zero) */
440 MessageBox(NULL, "Invalid Port Number",
441 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
448 wndclass.lpfnWndProc = WndProc;
449 wndclass.cbClsExtra = 0;
450 wndclass.cbWndExtra = 0;
451 wndclass.hInstance = inst;
452 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
453 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
454 wndclass.hbrBackground = NULL;
455 wndclass.lpszMenuName = NULL;
456 wndclass.lpszClassName = appname;
458 RegisterClass(&wndclass);
463 savelines = cfg.savelines;
469 * Guess some defaults for the window size. This all gets
470 * updated later, so we don't really care too much. However, we
471 * do want the font width/height guesses to correspond to a
472 * large font rather than a small one...
479 term_size(cfg.height, cfg.width, cfg.savelines);
480 guess_width = extra_width + font_width * cols;
481 guess_height = extra_height + font_height * rows;
484 get_fullscreen_rect(&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 ||
732 (send_raw_mouse && !(cfg.mouse_override && is_shift_pressed())))
737 flash_window(1); /* maintain */
739 /* The messages seem unreliable; especially if we're being tricky */
740 has_focus = (GetForegroundWindow() == hwnd);
743 /* Hmm, term_update didn't want to do an update too soon ... */
744 timer_id = SetTimer(hwnd, 1, 50, NULL);
746 timer_id = SetTimer(hwnd, 1, 500, NULL);
748 timer_id = SetTimer(hwnd, 1, 100, NULL);
751 /* There's no point rescanning everything in the message queue
752 * so we do an apparently unnecessary wait here
755 if (GetMessage(&msg, NULL, 0, 0) != 1)
760 cleanup_exit(msg.wParam); /* this doesn't return... */
761 return msg.wParam; /* ... but optimiser doesn't know */
767 void cleanup_exit(int code)
780 if (cfg.protocol == PROT_SSH) {
791 * Set up, or shut down, an AsyncSelect. Called from winnet.c.
793 char *do_select(SOCKET skt, int startup)
798 events = (FD_CONNECT | FD_READ | FD_WRITE |
799 FD_OOB | FD_CLOSE | FD_ACCEPT);
804 return "do_select(): internal error (hwnd==NULL)";
805 if (WSAAsyncSelect(skt, hwnd, msg, events) == SOCKET_ERROR) {
806 switch (WSAGetLastError()) {
808 return "Network is down";
810 return "WSAAsyncSelect(): unknown error";
817 * set or clear the "raw mouse message" mode
819 void set_raw_mouse_mode(int activate)
821 activate = activate && !cfg.no_mouse_rep;
822 send_raw_mouse = activate;
823 SetCursor(LoadCursor(NULL, activate ? IDC_ARROW : IDC_IBEAM));
827 * Print a message box and close the connection.
829 void connection_fatal(char *fmt, ...)
835 vsprintf(stuff, fmt, ap);
837 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
838 if (cfg.close_on_exit == COE_ALWAYS)
841 session_closed = TRUE;
842 SetWindowText(hwnd, "PuTTY (inactive)");
847 * Actually do the job requested by a WM_NETEVENT
849 static void enact_pending_netevent(void)
851 static int reentering = 0;
852 extern int select_result(WPARAM, LPARAM);
856 return; /* don't unpend the pending */
858 pending_netevent = FALSE;
861 ret = select_result(pend_netevent_wParam, pend_netevent_lParam);
864 if (ret == 0 && !session_closed) {
865 /* Abnormal exits will already have set session_closed and taken
866 * appropriate action. */
867 if (cfg.close_on_exit == COE_ALWAYS ||
868 cfg.close_on_exit == COE_NORMAL) PostQuitMessage(0);
870 session_closed = TRUE;
871 SetWindowText(hwnd, "PuTTY (inactive)");
872 MessageBox(hwnd, "Connection closed by remote host",
873 "PuTTY", MB_OK | MB_ICONINFORMATION);
879 * Copy the colour palette from the configuration data into defpal.
880 * This is non-trivial because the colour indices are different.
882 static void cfgtopalette(void)
885 static const int ww[] = {
886 6, 7, 8, 9, 10, 11, 12, 13,
887 14, 15, 16, 17, 18, 19, 20, 21,
888 0, 1, 2, 3, 4, 4, 5, 5
891 for (i = 0; i < 24; i++) {
893 defpal[i].rgbtRed = cfg.colours[w][0];
894 defpal[i].rgbtGreen = cfg.colours[w][1];
895 defpal[i].rgbtBlue = cfg.colours[w][2];
900 * Set up the colour palette.
902 static void init_palette(void)
905 HDC hdc = GetDC(hwnd);
907 if (cfg.try_palette && GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) {
908 logpal = smalloc(sizeof(*logpal)
909 - sizeof(logpal->palPalEntry)
910 + NCOLOURS * sizeof(PALETTEENTRY));
911 logpal->palVersion = 0x300;
912 logpal->palNumEntries = NCOLOURS;
913 for (i = 0; i < NCOLOURS; i++) {
914 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
915 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
916 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
917 logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
919 pal = CreatePalette(logpal);
921 SelectPalette(hdc, pal, FALSE);
923 SelectPalette(hdc, GetStockObject(DEFAULT_PALETTE), FALSE);
926 ReleaseDC(hwnd, hdc);
929 for (i = 0; i < NCOLOURS; i++)
930 colours[i] = PALETTERGB(defpal[i].rgbtRed,
934 for (i = 0; i < NCOLOURS; i++)
935 colours[i] = RGB(defpal[i].rgbtRed,
936 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
940 * Initialise all the fonts we will need initially. There may be as many as
941 * three or as few as one. The other (poentially) twentyone fonts are done
942 * if/when they are needed.
946 * - check the font width and height, correcting our guesses if
949 * - verify that the bold font is the same width as the ordinary
950 * one, and engage shadow bolding if not.
952 * - verify that the underlined font is the same width as the
953 * ordinary one (manual underlining by means of line drawing can
954 * be done in a pinch).
956 static void init_fonts(int pick_width, int pick_height)
963 int fw_dontcare, fw_bold;
965 for (i = 0; i < FONT_MAXNO; i++)
968 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
971 if (cfg.fontisbold) {
972 fw_dontcare = FW_BOLD;
975 fw_dontcare = FW_DONTCARE;
982 font_height = pick_height;
984 font_height = cfg.fontheight;
985 if (font_height > 0) {
987 -MulDiv(font_height, GetDeviceCaps(hdc, LOGPIXELSY), 72);
990 font_width = pick_width;
993 fonts[i] = CreateFont (font_height, font_width, 0, 0, w, FALSE, u, FALSE, \
994 c, OUT_DEFAULT_PRECIS, \
995 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, \
996 FIXED_PITCH | FF_DONTCARE, cfg.font)
998 f(FONT_NORMAL, cfg.fontcharset, fw_dontcare, FALSE);
1000 lfont.lfHeight = font_height;
1001 lfont.lfWidth = font_width;
1002 lfont.lfEscapement = 0;
1003 lfont.lfOrientation = 0;
1004 lfont.lfWeight = fw_dontcare;
1005 lfont.lfItalic = FALSE;
1006 lfont.lfUnderline = FALSE;
1007 lfont.lfStrikeOut = FALSE;
1008 lfont.lfCharSet = cfg.fontcharset;
1009 lfont.lfOutPrecision = OUT_DEFAULT_PRECIS;
1010 lfont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
1011 lfont.lfQuality = DEFAULT_QUALITY;
1012 lfont.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE;
1013 strncpy(lfont.lfFaceName, cfg.font, LF_FACESIZE);
1015 SelectObject(hdc, fonts[FONT_NORMAL]);
1016 GetTextMetrics(hdc, &tm);
1018 if (pick_width == 0 || pick_height == 0) {
1019 font_height = tm.tmHeight;
1020 font_width = tm.tmAveCharWidth;
1022 font_dualwidth = (tm.tmAveCharWidth != tm.tmMaxCharWidth);
1024 #ifdef RDB_DEBUG_PATCH
1025 debug(23, "Primary font H=%d, AW=%d, MW=%d",
1026 tm.tmHeight, tm.tmAveCharWidth, tm.tmMaxCharWidth);
1031 DWORD cset = tm.tmCharSet;
1032 memset(&info, 0xFF, sizeof(info));
1034 /* !!! Yes the next line is right */
1035 if (cset == OEM_CHARSET)
1036 font_codepage = GetOEMCP();
1038 if (TranslateCharsetInfo
1039 ((DWORD *) cset, &info, TCI_SRCCHARSET)) font_codepage =
1044 GetCPInfo(font_codepage, &cpinfo);
1045 dbcs_screenfont = (cpinfo.MaxCharSize > 1);
1048 f(FONT_UNDERLINE, cfg.fontcharset, fw_dontcare, TRUE);
1051 * Some fonts, e.g. 9-pt Courier, draw their underlines
1052 * outside their character cell. We successfully prevent
1053 * screen corruption by clipping the text output, but then
1054 * we lose the underline completely. Here we try to work
1055 * out whether this is such a font, and if it is, we set a
1056 * flag that causes underlines to be drawn by hand.
1058 * Having tried other more sophisticated approaches (such
1059 * as examining the TEXTMETRIC structure or requesting the
1060 * height of a string), I think we'll do this the brute
1061 * force way: we create a small bitmap, draw an underlined
1062 * space on it, and test to see whether any pixels are
1063 * foreground-coloured. (Since we expect the underline to
1064 * go all the way across the character cell, we only search
1065 * down a single column of the bitmap, half way across.)
1069 HBITMAP und_bm, und_oldbm;
1073 und_dc = CreateCompatibleDC(hdc);
1074 und_bm = CreateCompatibleBitmap(hdc, font_width, font_height);
1075 und_oldbm = SelectObject(und_dc, und_bm);
1076 SelectObject(und_dc, fonts[FONT_UNDERLINE]);
1077 SetTextAlign(und_dc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
1078 SetTextColor(und_dc, RGB(255, 255, 255));
1079 SetBkColor(und_dc, RGB(0, 0, 0));
1080 SetBkMode(und_dc, OPAQUE);
1081 ExtTextOut(und_dc, 0, 0, ETO_OPAQUE, NULL, " ", 1, NULL);
1083 for (i = 0; i < font_height; i++) {
1084 c = GetPixel(und_dc, font_width / 2, i);
1085 if (c != RGB(0, 0, 0))
1088 SelectObject(und_dc, und_oldbm);
1089 DeleteObject(und_bm);
1092 und_mode = UND_LINE;
1093 DeleteObject(fonts[FONT_UNDERLINE]);
1094 fonts[FONT_UNDERLINE] = 0;
1098 if (bold_mode == BOLD_FONT) {
1099 f(FONT_BOLD, cfg.fontcharset, fw_bold, FALSE);
1103 descent = tm.tmAscent + 1;
1104 if (descent >= font_height)
1105 descent = font_height - 1;
1107 for (i = 0; i < 3; i++) {
1109 if (SelectObject(hdc, fonts[i]) && GetTextMetrics(hdc, &tm))
1110 fontsize[i] = tm.tmAveCharWidth + 256 * tm.tmHeight;
1117 ReleaseDC(hwnd, hdc);
1119 if (fontsize[FONT_UNDERLINE] != fontsize[FONT_NORMAL]) {
1120 und_mode = UND_LINE;
1121 DeleteObject(fonts[FONT_UNDERLINE]);
1122 fonts[FONT_UNDERLINE] = 0;
1125 if (bold_mode == BOLD_FONT &&
1126 fontsize[FONT_BOLD] != fontsize[FONT_NORMAL]) {
1127 bold_mode = BOLD_SHADOW;
1128 DeleteObject(fonts[FONT_BOLD]);
1129 fonts[FONT_BOLD] = 0;
1131 fontflag[0] = fontflag[1] = fontflag[2] = 1;
1136 static void another_font(int fontno)
1139 int fw_dontcare, fw_bold;
1143 if (fontno < 0 || fontno >= FONT_MAXNO || fontflag[fontno])
1146 basefont = (fontno & ~(FONT_BOLDUND));
1147 if (basefont != fontno && !fontflag[basefont])
1148 another_font(basefont);
1150 if (cfg.fontisbold) {
1151 fw_dontcare = FW_BOLD;
1154 fw_dontcare = FW_DONTCARE;
1158 c = cfg.fontcharset;
1164 if (fontno & FONT_WIDE)
1166 if (fontno & FONT_NARROW)
1168 if (fontno & FONT_OEM)
1170 if (fontno & FONT_BOLD)
1172 if (fontno & FONT_UNDERLINE)
1176 CreateFont(font_height * (1 + !!(fontno & FONT_HIGH)), x, 0, 0, w,
1177 FALSE, u, FALSE, c, OUT_DEFAULT_PRECIS,
1178 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
1179 FIXED_PITCH | FF_DONTCARE, s);
1181 fontflag[fontno] = 1;
1184 static void deinit_fonts(void)
1187 for (i = 0; i < FONT_MAXNO; i++) {
1189 DeleteObject(fonts[i]);
1195 void request_resize(int w, int h)
1199 /* If the window is maximized supress resizing attempts */
1200 if (IsZoomed(hwnd)) {
1201 if (cfg.resize_action == RESIZE_TERM)
1205 if (cfg.resize_action == RESIZE_DISABLED) return;
1206 if (h == rows && w == cols) return;
1208 /* Sanity checks ... */
1210 static int first_time = 1;
1213 switch (first_time) {
1215 /* Get the size of the screen */
1216 if (get_fullscreen_rect(&ss))
1217 /* first_time = 0 */ ;
1223 /* Make sure the values are sane */
1224 width = (ss.right - ss.left - extra_width) / 4;
1225 height = (ss.bottom - ss.top - extra_height) / 6;
1227 if (w > width || h > height)
1236 term_size(h, w, cfg.savelines);
1238 if (cfg.resize_action != RESIZE_FONT && !IsZoomed(hwnd)) {
1239 width = extra_width + font_width * w;
1240 height = extra_height + font_height * h;
1242 SetWindowPos(hwnd, NULL, 0, 0, width, height,
1243 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1244 SWP_NOMOVE | SWP_NOZORDER);
1248 InvalidateRect(hwnd, NULL, TRUE);
1251 static void reset_window(int reinit) {
1253 * This function decides how to resize or redraw when the
1254 * user changes something.
1256 * This function doesn't like to change the terminal size but if the
1257 * font size is locked that may be it's only soluion.
1259 int win_width, win_height;
1262 #ifdef RDB_DEBUG_PATCH
1263 debug((27, "reset_window()"));
1266 /* Current window sizes ... */
1267 GetWindowRect(hwnd, &wr);
1268 GetClientRect(hwnd, &cr);
1270 win_width = cr.right - cr.left;
1271 win_height = cr.bottom - cr.top;
1273 if (cfg.resize_action == RESIZE_DISABLED) reinit = 2;
1275 /* Are we being forced to reload the fonts ? */
1277 #ifdef RDB_DEBUG_PATCH
1278 debug((27, "reset_window() -- Forced deinit"));
1284 /* Oh, looks like we're minimised */
1285 if (win_width == 0 || win_height == 0)
1288 /* Is the window out of position ? */
1290 (offset_width != (win_width-font_width*cols)/2 ||
1291 offset_height != (win_height-font_height*rows)/2) ){
1292 offset_width = (win_width-font_width*cols)/2;
1293 offset_height = (win_height-font_height*rows)/2;
1294 InvalidateRect(hwnd, NULL, TRUE);
1295 #ifdef RDB_DEBUG_PATCH
1296 debug((27, "reset_window() -> Reposition terminal"));
1300 if (IsZoomed(hwnd)) {
1301 /* We're fullscreen, this means we must not change the size of
1302 * the window so it's the font size or the terminal itself.
1305 extra_width = wr.right - wr.left - cr.right + cr.left;
1306 extra_height = wr.bottom - wr.top - cr.bottom + cr.top;
1308 if (cfg.resize_action != RESIZE_TERM) {
1309 if ( font_width != win_width/cols ||
1310 font_height != win_height/rows) {
1312 init_fonts(win_width/cols, win_height/rows);
1313 offset_width = (win_width-font_width*cols)/2;
1314 offset_height = (win_height-font_height*rows)/2;
1315 InvalidateRect(hwnd, NULL, TRUE);
1316 #ifdef RDB_DEBUG_PATCH
1317 debug((25, "reset_window() -> Z font resize to (%d, %d)",
1318 font_width, font_height));
1322 if ( font_width != win_width/cols ||
1323 font_height != win_height/rows) {
1324 /* Our only choice at this point is to change the
1325 * size of the terminal; Oh well.
1327 term_size( win_height/font_height, win_width/font_width,
1329 offset_width = (win_width-font_width*cols)/2;
1330 offset_height = (win_height-font_height*rows)/2;
1331 InvalidateRect(hwnd, NULL, TRUE);
1332 #ifdef RDB_DEBUG_PATCH
1333 debug((27, "reset_window() -> Zoomed term_size"));
1340 /* Hmm, a force re-init means we should ignore the current window
1341 * so we resize to the default font size.
1344 #ifdef RDB_DEBUG_PATCH
1345 debug((27, "reset_window() -> Forced re-init"));
1348 offset_width = offset_height = cfg.window_border;
1349 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
1350 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
1352 if (win_width != font_width*cols + offset_width*2 ||
1353 win_height != font_height*rows + offset_height*2) {
1355 /* If this is too large windows will resize it to the maximum
1356 * allowed window size, we will then be back in here and resize
1357 * the font or terminal to fit.
1359 SetWindowPos(hwnd, NULL, 0, 0,
1360 font_width*cols + extra_width,
1361 font_height*rows + extra_height,
1362 SWP_NOMOVE | SWP_NOZORDER);
1365 InvalidateRect(hwnd, NULL, TRUE);
1369 /* Okay the user doesn't want us to change the font so we try the
1370 * window. But that may be too big for the screen which forces us
1371 * to change the terminal.
1373 if ((cfg.resize_action == RESIZE_TERM && reinit<=0) ||
1374 (cfg.resize_action == RESIZE_EITHER && reinit<0) ||
1376 offset_width = offset_height = cfg.window_border;
1377 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
1378 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
1380 if (win_width != font_width*cols + offset_width*2 ||
1381 win_height != font_height*rows + offset_height*2) {
1386 get_fullscreen_rect(&ss);
1388 width = (ss.right - ss.left - extra_width) / font_width;
1389 height = (ss.bottom - ss.top - extra_height) / font_height;
1392 if ( rows > height || cols > width ) {
1393 if (cfg.resize_action == RESIZE_EITHER) {
1394 /* Make the font the biggest we can */
1396 font_width = (ss.right - ss.left - extra_width)/cols;
1398 font_height = (ss.bottom - ss.top - extra_height)/rows;
1401 init_fonts(font_width, font_height);
1403 width = (ss.right - ss.left - extra_width) / font_width;
1404 height = (ss.bottom - ss.top - extra_height) / font_height;
1406 if ( height > rows ) height = rows;
1407 if ( width > cols ) width = cols;
1408 term_size(height, width, cfg.savelines);
1409 #ifdef RDB_DEBUG_PATCH
1410 debug((27, "reset_window() -> term resize to (%d,%d)",
1416 SetWindowPos(hwnd, NULL, 0, 0,
1417 font_width*cols + extra_width,
1418 font_height*rows + extra_height,
1419 SWP_NOMOVE | SWP_NOZORDER);
1421 InvalidateRect(hwnd, NULL, TRUE);
1422 #ifdef RDB_DEBUG_PATCH
1423 debug((27, "reset_window() -> window resize to (%d,%d)",
1424 font_width*cols + extra_width,
1425 font_height*rows + extra_height));
1431 /* We're allowed to or must change the font but do we want to ? */
1433 if (font_width != (win_width-cfg.window_border*2)/cols ||
1434 font_height != (win_height-cfg.window_border*2)/rows) {
1437 init_fonts((win_width-cfg.window_border*2)/cols,
1438 (win_height-cfg.window_border*2)/rows);
1439 offset_width = (win_width-font_width*cols)/2;
1440 offset_height = (win_height-font_height*rows)/2;
1442 extra_width = wr.right - wr.left - cr.right + cr.left +offset_width*2;
1443 extra_height = wr.bottom - wr.top - cr.bottom + cr.top+offset_height*2;
1445 InvalidateRect(hwnd, NULL, TRUE);
1446 #ifdef RDB_DEBUG_PATCH
1447 debug((25, "reset_window() -> font resize to (%d,%d)",
1448 font_width, font_height));
1453 static void set_input_locale(HKL kl)
1457 GetLocaleInfo(LOWORD(kl), LOCALE_IDEFAULTANSICODEPAGE,
1458 lbuf, sizeof(lbuf));
1460 kbd_codepage = atoi(lbuf);
1463 static void click(Mouse_Button b, int x, int y, int shift, int ctrl, int alt)
1465 int thistime = GetMessageTime();
1467 if (send_raw_mouse && !(cfg.mouse_override && shift)) {
1468 lastbtn = MBT_NOTHING;
1469 term_mouse(b, MA_CLICK, x, y, shift, ctrl, alt);
1473 if (lastbtn == b && thistime - lasttime < dbltime) {
1474 lastact = (lastact == MA_CLICK ? MA_2CLK :
1475 lastact == MA_2CLK ? MA_3CLK :
1476 lastact == MA_3CLK ? MA_CLICK : MA_NOTHING);
1481 if (lastact != MA_NOTHING)
1482 term_mouse(b, lastact, x, y, shift, ctrl, alt);
1483 lasttime = thistime;
1487 * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT)
1488 * into a cooked one (SELECT, EXTEND, PASTE).
1490 Mouse_Button translate_button(Mouse_Button button)
1492 if (button == MBT_LEFT)
1494 if (button == MBT_MIDDLE)
1495 return cfg.mouse_is_xterm ? MBT_PASTE : MBT_EXTEND;
1496 if (button == MBT_RIGHT)
1497 return cfg.mouse_is_xterm ? MBT_EXTEND : MBT_PASTE;
1498 return 0; /* shouldn't happen */
1501 static void show_mouseptr(int show)
1503 static int cursor_visible = 1;
1504 if (!cfg.hide_mouseptr) /* override if this feature disabled */
1506 if (cursor_visible && !show)
1508 else if (!cursor_visible && show)
1510 cursor_visible = show;
1513 static int is_alt_pressed(void)
1516 int r = GetKeyboardState(keystate);
1519 if (keystate[VK_MENU] & 0x80)
1521 if (keystate[VK_RMENU] & 0x80)
1526 static int is_shift_pressed(void)
1529 int r = GetKeyboardState(keystate);
1532 if (keystate[VK_SHIFT] & 0x80)
1537 static int resizing;
1539 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1540 WPARAM wParam, LPARAM lParam)
1543 static int ignore_clip = FALSE;
1544 static int need_backend_resize = FALSE;
1545 static int fullscr_on_max = FALSE;
1549 if (pending_netevent)
1550 enact_pending_netevent();
1551 if (GetCapture() != hwnd ||
1552 (send_raw_mouse && !(cfg.mouse_override && is_shift_pressed())))
1558 if (cfg.ping_interval > 0) {
1561 if (now - last_movement > cfg.ping_interval) {
1562 back->special(TS_PING);
1563 last_movement = now;
1566 net_pending_errors();
1572 if (!cfg.warn_on_close || session_closed ||
1574 "Are you sure you want to close this session?",
1575 "PuTTY Exit Confirmation",
1576 MB_ICONWARNING | MB_OKCANCEL) == IDOK)
1577 DestroyWindow(hwnd);
1584 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1596 PROCESS_INFORMATION pi;
1597 HANDLE filemap = NULL;
1599 if (wParam == IDM_DUPSESS) {
1601 * Allocate a file-mapping memory chunk for the
1604 SECURITY_ATTRIBUTES sa;
1607 sa.nLength = sizeof(sa);
1608 sa.lpSecurityDescriptor = NULL;
1609 sa.bInheritHandle = TRUE;
1610 filemap = CreateFileMapping((HANDLE) 0xFFFFFFFF,
1613 0, sizeof(Config), NULL);
1615 p = (Config *) MapViewOfFile(filemap,
1617 0, 0, sizeof(Config));
1619 *p = cfg; /* structure copy */
1623 sprintf(c, "putty &%p", filemap);
1625 } else if (wParam == IDM_SAVEDSESS) {
1626 if ((lParam - IDM_SAVED_MIN) / 16 < nsessions) {
1628 sessions[(lParam - IDM_SAVED_MIN) / 16];
1629 cl = smalloc(16 + strlen(session));
1630 /* 8, but play safe */
1633 /* not a very important failure mode */
1635 sprintf(cl, "putty @%s", session);
1643 GetModuleFileName(NULL, b, sizeof(b) - 1);
1645 si.lpReserved = NULL;
1646 si.lpDesktop = NULL;
1650 si.lpReserved2 = NULL;
1651 CreateProcess(b, cl, NULL, NULL, TRUE,
1652 NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
1655 CloseHandle(filemap);
1665 GetWindowText(hwnd, cfg.wintitle, sizeof(cfg.wintitle));
1668 if (!do_reconfig(hwnd))
1672 /* Disable full-screen if resizing forbidden */
1673 HMENU m = GetSystemMenu (hwnd, FALSE);
1674 EnableMenuItem(m, IDM_FULLSCREEN, MF_BYCOMMAND |
1675 (cfg.resize_action == RESIZE_DISABLED)
1676 ? MF_GRAYED : MF_ENABLED);
1677 /* Gracefully unzoom if necessary */
1678 if (IsZoomed(hwnd) &&
1679 (cfg.resize_action == RESIZE_DISABLED)) {
1680 ShowWindow(hwnd, SW_RESTORE);
1684 if (strcmp(prev_cfg.logfilename, cfg.logfilename) ||
1685 prev_cfg.logtype != cfg.logtype) {
1686 logfclose(); /* reset logging */
1692 * Flush the line discipline's edit buffer in the
1693 * case where local editing has just been disabled.
1695 ldisc_send(NULL, 0, 0);
1703 /* Give terminal a heads-up on miscellaneous stuff */
1706 /* Screen size changed ? */
1707 if (cfg.height != prev_cfg.height ||
1708 cfg.width != prev_cfg.width ||
1709 cfg.savelines != prev_cfg.savelines ||
1710 cfg.resize_action == RESIZE_FONT ||
1711 (cfg.resize_action == RESIZE_EITHER && IsZoomed(hwnd)) ||
1712 cfg.resize_action == RESIZE_DISABLED)
1713 term_size(cfg.height, cfg.width, cfg.savelines);
1715 /* Enable or disable the scroll bar, etc */
1717 LONG nflg, flag = GetWindowLong(hwnd, GWL_STYLE);
1718 LONG nexflag, exflag =
1719 GetWindowLong(hwnd, GWL_EXSTYLE);
1722 if (cfg.alwaysontop != prev_cfg.alwaysontop) {
1723 if (cfg.alwaysontop) {
1724 nexflag |= WS_EX_TOPMOST;
1725 SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
1726 SWP_NOMOVE | SWP_NOSIZE);
1728 nexflag &= ~(WS_EX_TOPMOST);
1729 SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0,
1730 SWP_NOMOVE | SWP_NOSIZE);
1733 if (cfg.sunken_edge)
1734 nexflag |= WS_EX_CLIENTEDGE;
1736 nexflag &= ~(WS_EX_CLIENTEDGE);
1739 if (is_full_screen() ?
1740 cfg.scrollbar_in_fullscreen : cfg.scrollbar)
1743 nflg &= ~WS_VSCROLL;
1745 if (cfg.resize_action == RESIZE_DISABLED ||
1747 nflg &= ~WS_THICKFRAME;
1749 nflg |= WS_THICKFRAME;
1751 if (cfg.resize_action == RESIZE_DISABLED)
1752 nflg &= ~WS_MAXIMIZEBOX;
1754 nflg |= WS_MAXIMIZEBOX;
1756 if (nflg != flag || nexflag != exflag) {
1758 SetWindowLong(hwnd, GWL_STYLE, nflg);
1759 if (nexflag != exflag)
1760 SetWindowLong(hwnd, GWL_EXSTYLE, nexflag);
1762 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
1763 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1764 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
1772 if (cfg.resize_action == RESIZE_DISABLED && IsZoomed(hwnd)) {
1777 set_title(cfg.wintitle);
1778 if (IsIconic(hwnd)) {
1780 cfg.win_name_always ? window_name :
1784 if (strcmp(cfg.font, prev_cfg.font) != 0 ||
1785 strcmp(cfg.line_codepage, prev_cfg.line_codepage) != 0 ||
1786 cfg.fontisbold != prev_cfg.fontisbold ||
1787 cfg.fontheight != prev_cfg.fontheight ||
1788 cfg.fontcharset != prev_cfg.fontcharset ||
1789 cfg.vtmode != prev_cfg.vtmode ||
1790 cfg.bold_colour != prev_cfg.bold_colour ||
1791 cfg.resize_action == RESIZE_DISABLED ||
1792 cfg.resize_action == RESIZE_EITHER ||
1793 (cfg.resize_action != prev_cfg.resize_action))
1796 InvalidateRect(hwnd, NULL, TRUE);
1797 reset_window(init_lvl);
1798 net_pending_errors();
1811 back->special(TS_AYT);
1812 net_pending_errors();
1815 back->special(TS_BRK);
1816 net_pending_errors();
1819 back->special(TS_SYNCH);
1820 net_pending_errors();
1823 back->special(TS_EC);
1824 net_pending_errors();
1827 back->special(TS_EL);
1828 net_pending_errors();
1831 back->special(TS_GA);
1832 net_pending_errors();
1835 back->special(TS_NOP);
1836 net_pending_errors();
1839 back->special(TS_ABORT);
1840 net_pending_errors();
1843 back->special(TS_AO);
1844 net_pending_errors();
1847 back->special(TS_IP);
1848 net_pending_errors();
1851 back->special(TS_SUSP);
1852 net_pending_errors();
1855 back->special(TS_EOR);
1856 net_pending_errors();
1859 back->special(TS_EOF);
1860 net_pending_errors();
1866 WinHelp(hwnd, help_path,
1867 help_has_contents ? HELP_FINDER : HELP_CONTENTS, 0);
1871 * We get this if the System menu has been activated
1878 * We get this if the System menu has been activated
1879 * using the keyboard. This might happen from within
1880 * TranslateKey, in which case it really wants to be
1881 * followed by a `space' character to actually _bring
1882 * the menu up_ rather than just sitting there in
1883 * `ready to appear' state.
1885 show_mouseptr(1); /* make sure pointer is visible */
1887 PostMessage(hwnd, WM_CHAR, ' ', 0);
1889 case IDM_FULLSCREEN:
1893 if (wParam >= IDM_SAVED_MIN && wParam <= IDM_SAVED_MAX) {
1894 SendMessage(hwnd, WM_SYSCOMMAND, IDM_SAVEDSESS, wParam);
1899 #define X_POS(l) ((int)(short)LOWORD(l))
1900 #define Y_POS(l) ((int)(short)HIWORD(l))
1902 #define TO_CHR_X(x) ((((x)<0 ? (x)-font_width+1 : (x))-offset_width) / font_width)
1903 #define TO_CHR_Y(y) ((((y)<0 ? (y)-font_height+1: (y))-offset_height) / font_height)
1904 case WM_LBUTTONDOWN:
1905 case WM_MBUTTONDOWN:
1906 case WM_RBUTTONDOWN:
1914 case WM_LBUTTONDOWN:
1918 case WM_MBUTTONDOWN:
1919 button = MBT_MIDDLE;
1922 case WM_RBUTTONDOWN:
1931 button = MBT_MIDDLE;
1939 button = press = 0; /* shouldn't happen */
1943 * Special case: in full-screen mode, if the left
1944 * button is clicked in the very top left corner of the
1945 * window, we put up the System menu instead of doing
1948 if (is_full_screen() && press && button == MBT_LEFT &&
1949 X_POS(lParam) == 0 && Y_POS(lParam) == 0) {
1950 SendMessage(hwnd, WM_SYSCOMMAND, SC_MOUSEMENU, 0);
1955 TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam)),
1956 wParam & MK_SHIFT, wParam & MK_CONTROL,
1960 term_mouse(button, MA_RELEASE,
1961 TO_CHR_X(X_POS(lParam)),
1962 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
1963 wParam & MK_CONTROL, is_alt_pressed());
1971 * Add the mouse position and message time to the random
1974 noise_ultralight(lParam);
1976 if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON) &&
1977 GetCapture() == hwnd) {
1979 if (wParam & MK_LBUTTON)
1981 else if (wParam & MK_MBUTTON)
1985 term_mouse(b, MA_DRAG, TO_CHR_X(X_POS(lParam)),
1986 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
1987 wParam & MK_CONTROL, is_alt_pressed());
1990 case WM_NCMOUSEMOVE:
1992 noise_ultralight(lParam);
1994 case WM_IGNORE_CLIP:
1995 ignore_clip = wParam; /* don't panic on DESTROYCLIPBOARD */
1997 case WM_DESTROYCLIPBOARD:
2000 ignore_clip = FALSE;
2006 hdc = BeginPaint(hwnd, &p);
2008 SelectPalette(hdc, pal, TRUE);
2009 RealizePalette(hdc);
2012 (p.rcPaint.left-offset_width)/font_width,
2013 (p.rcPaint.top-offset_height)/font_height,
2014 (p.rcPaint.right-offset_width-1)/font_width,
2015 (p.rcPaint.bottom-offset_height-1)/font_height);
2018 p.rcPaint.left < offset_width ||
2019 p.rcPaint.top < offset_height ||
2020 p.rcPaint.right >= offset_width + font_width*cols ||
2021 p.rcPaint.bottom>= offset_height + font_height*rows)
2023 HBRUSH fillcolour, oldbrush;
2025 fillcolour = CreateSolidBrush (
2026 colours[(ATTR_DEFBG>>ATTR_BGSHIFT)*2]);
2027 oldbrush = SelectObject(hdc, fillcolour);
2028 edge = CreatePen(PS_SOLID, 0,
2029 colours[(ATTR_DEFBG>>ATTR_BGSHIFT)*2]);
2030 oldpen = SelectObject(hdc, edge);
2032 ExcludeClipRect(hdc,
2033 offset_width, offset_height,
2034 offset_width+font_width*cols,
2035 offset_height+font_height*rows);
2037 Rectangle(hdc, p.rcPaint.left, p.rcPaint.top,
2038 p.rcPaint.right, p.rcPaint.bottom);
2040 // SelectClipRgn(hdc, NULL);
2042 SelectObject(hdc, oldbrush);
2043 DeleteObject(fillcolour);
2044 SelectObject(hdc, oldpen);
2047 SelectObject(hdc, GetStockObject(SYSTEM_FONT));
2048 SelectObject(hdc, GetStockObject(WHITE_PEN));
2054 /* Notice we can get multiple netevents, FD_READ, FD_WRITE etc
2055 * but the only one that's likely to try to overload us is FD_READ.
2056 * This means buffering just one is fine.
2058 if (pending_netevent)
2059 enact_pending_netevent();
2061 pending_netevent = TRUE;
2062 pend_netevent_wParam = wParam;
2063 pend_netevent_lParam = lParam;
2064 if (WSAGETSELECTEVENT(lParam) != FD_READ)
2065 enact_pending_netevent();
2067 time(&last_movement);
2071 CreateCaret(hwnd, caretbm, font_width, font_height);
2073 flash_window(0); /* stop */
2082 caret_x = caret_y = -1; /* ensure caret is replaced next time */
2086 case WM_ENTERSIZEMOVE:
2087 #ifdef RDB_DEBUG_PATCH
2088 debug((27, "WM_ENTERSIZEMOVE"));
2092 need_backend_resize = FALSE;
2094 case WM_EXITSIZEMOVE:
2097 #ifdef RDB_DEBUG_PATCH
2098 debug((27, "WM_EXITSIZEMOVE"));
2100 if (need_backend_resize) {
2101 term_size(cfg.height, cfg.width, cfg.savelines);
2102 InvalidateRect(hwnd, NULL, TRUE);
2107 * This does two jobs:
2108 * 1) Keep the sizetip uptodate
2109 * 2) Make sure the window size is _stepped_ in units of the font size.
2111 if (cfg.resize_action != RESIZE_FONT && !alt_pressed) {
2112 int width, height, w, h, ew, eh;
2113 LPRECT r = (LPRECT) lParam;
2115 if ( !need_backend_resize && cfg.resize_action == RESIZE_EITHER &&
2116 (cfg.height != rows || cfg.width != cols )) {
2118 * Great! It seems that both the terminal size and the
2119 * font size have been changed and the user is now dragging.
2121 * It will now be difficult to get back to the configured
2124 * This would be easier but it seems to be too confusing.
2126 term_size(cfg.height, cfg.width, cfg.savelines);
2129 cfg.height=rows; cfg.width=cols;
2131 InvalidateRect(hwnd, NULL, TRUE);
2132 need_backend_resize = TRUE;
2135 width = r->right - r->left - extra_width;
2136 height = r->bottom - r->top - extra_height;
2137 w = (width + font_width / 2) / font_width;
2140 h = (height + font_height / 2) / font_height;
2143 UpdateSizeTip(hwnd, w, h);
2144 ew = width - w * font_width;
2145 eh = height - h * font_height;
2147 if (wParam == WMSZ_LEFT ||
2148 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
2154 if (wParam == WMSZ_TOP ||
2155 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
2165 int width, height, w, h, rv = 0;
2166 int ex_width = extra_width + (cfg.window_border - offset_width) * 2;
2167 int ex_height = extra_height + (cfg.window_border - offset_height) * 2;
2168 LPRECT r = (LPRECT) lParam;
2170 width = r->right - r->left - ex_width;
2171 height = r->bottom - r->top - ex_height;
2173 w = (width + cols/2)/cols;
2174 h = (height + rows/2)/rows;
2175 if ( r->right != r->left + w*cols + ex_width)
2178 if (wParam == WMSZ_LEFT ||
2179 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
2180 r->left = r->right - w*cols - ex_width;
2182 r->right = r->left + w*cols + ex_width;
2184 if (r->bottom != r->top + h*rows + ex_height)
2187 if (wParam == WMSZ_TOP ||
2188 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
2189 r->top = r->bottom - h*rows - ex_height;
2191 r->bottom = r->top + h*rows + ex_height;
2195 /* break; (never reached) */
2196 case WM_FULLSCR_ON_MAX:
2197 fullscr_on_max = TRUE;
2200 sys_cursor_update();
2203 #ifdef RDB_DEBUG_PATCH
2204 debug((27, "WM_SIZE %s (%d,%d)",
2205 (wParam == SIZE_MINIMIZED) ? "SIZE_MINIMIZED":
2206 (wParam == SIZE_MAXIMIZED) ? "SIZE_MAXIMIZED":
2207 (wParam == SIZE_RESTORED && resizing) ? "to":
2208 (wParam == SIZE_RESTORED) ? "SIZE_RESTORED":
2210 LOWORD(lParam), HIWORD(lParam)));
2212 if (wParam == SIZE_MINIMIZED)
2214 cfg.win_name_always ? window_name : icon_name);
2215 if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
2216 SetWindowText(hwnd, window_name);
2217 if (wParam == SIZE_RESTORED)
2218 clear_full_screen();
2219 if (wParam == SIZE_MAXIMIZED && fullscr_on_max) {
2220 fullscr_on_max = FALSE;
2224 if (cfg.resize_action == RESIZE_DISABLED) {
2225 /* A resize, well it better be a minimize. */
2229 int width, height, w, h;
2231 width = LOWORD(lParam);
2232 height = HIWORD(lParam);
2235 if (wParam == SIZE_MAXIMIZED && !was_zoomed) {
2239 if (cfg.resize_action == RESIZE_TERM) {
2240 w = width / font_width;
2242 h = height / font_height;
2245 term_size(h, w, cfg.savelines);
2248 } else if (wParam == SIZE_RESTORED && was_zoomed) {
2250 if (cfg.resize_action == RESIZE_TERM)
2251 term_size(prev_rows, prev_cols, cfg.savelines);
2252 if (cfg.resize_action != RESIZE_FONT)
2257 /* This is an unexpected resize, these will normally happen
2258 * if the window is too large. Probably either the user
2259 * selected a huge font or the screen size has changed.
2261 * This is also called with minimize.
2263 else reset_window(-1);
2267 * Don't call back->size in mid-resize. (To prevent
2268 * massive numbers of resize events getting sent
2269 * down the connection during an NT opaque drag.)
2272 if (cfg.resize_action != RESIZE_FONT && !alt_pressed) {
2273 need_backend_resize = TRUE;
2274 w = (width-cfg.window_border*2) / font_width;
2276 h = (height-cfg.window_border*2) / font_height;
2285 sys_cursor_update();
2288 switch (LOWORD(wParam)) {
2302 term_scroll(0, +rows / 2);
2305 term_scroll(0, -rows / 2);
2307 case SB_THUMBPOSITION:
2309 term_scroll(1, HIWORD(wParam));
2313 case WM_PALETTECHANGED:
2314 if ((HWND) wParam != hwnd && pal != NULL) {
2315 HDC hdc = get_ctx();
2317 if (RealizePalette(hdc) > 0)
2323 case WM_QUERYNEWPALETTE:
2325 HDC hdc = get_ctx();
2327 if (RealizePalette(hdc) > 0)
2339 * Add the scan code and keypress timing to the random
2342 noise_ultralight(lParam);
2345 * We don't do TranslateMessage since it disassociates the
2346 * resulting CHAR message from the KEYDOWN that sparked it,
2347 * which we occasionally don't want. Instead, we process
2348 * KEYDOWN, and call the Win32 translator functions so that
2349 * we get the translations under _our_ control.
2352 unsigned char buf[20];
2355 if (wParam == VK_PROCESSKEY) {
2358 m.message = WM_KEYDOWN;
2360 m.lParam = lParam & 0xdfff;
2361 TranslateMessage(&m);
2363 len = TranslateKey(message, wParam, lParam, buf);
2365 return DefWindowProc(hwnd, message, wParam, lParam);
2369 * Interrupt an ongoing paste. I'm not sure
2370 * this is sensible, but for the moment it's
2371 * preferable to having to faff about buffering
2377 * We need not bother about stdin backlogs
2378 * here, because in GUI PuTTY we can't do
2379 * anything about it anyway; there's no means
2380 * of asking Windows to hold off on KEYDOWN
2381 * messages. We _have_ to buffer everything
2384 ldisc_send(buf, len, 1);
2389 net_pending_errors();
2391 case WM_INPUTLANGCHANGE:
2392 /* wParam == Font number */
2393 /* lParam == Locale */
2394 set_input_locale((HKL)lParam);
2395 sys_cursor_update();
2398 if(wParam == IMN_SETOPENSTATUS) {
2399 HIMC hImc = ImmGetContext(hwnd);
2400 ImmSetCompositionFont(hImc, &lfont);
2401 ImmReleaseContext(hwnd, hImc);
2405 case WM_IME_COMPOSITION:
2411 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ||
2412 osVersion.dwPlatformId == VER_PLATFORM_WIN32s) break; /* no Unicode */
2414 if ((lParam & GCS_RESULTSTR) == 0) /* Composition unfinished. */
2415 break; /* fall back to DefWindowProc */
2417 hIMC = ImmGetContext(hwnd);
2418 n = ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, NULL, 0);
2422 buff = (char*) smalloc(n);
2423 ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, buff, n);
2425 * Jaeyoun Chung reports that Korean character
2426 * input doesn't work correctly if we do a single
2427 * luni_send() covering the whole of buff. So
2428 * instead we luni_send the characters one by one.
2430 for (i = 0; i < n; i += 2)
2431 luni_send((unsigned short *)(buff+i), 1, 1);
2434 ImmReleaseContext(hwnd, hIMC);
2439 if (wParam & 0xFF00) {
2440 unsigned char buf[2];
2443 buf[0] = wParam >> 8;
2444 lpage_send(kbd_codepage, buf, 2, 1);
2446 char c = (unsigned char) wParam;
2447 lpage_send(kbd_codepage, &c, 1, 1);
2453 * Nevertheless, we are prepared to deal with WM_CHAR
2454 * messages, should they crop up. So if someone wants to
2455 * post the things to us as part of a macro manoeuvre,
2456 * we're ready to cope.
2459 char c = (unsigned char)wParam;
2460 lpage_send(CP_ACP, &c, 1, 1);
2464 if (send_raw_mouse && LOWORD(lParam) == HTCLIENT) {
2465 SetCursor(LoadCursor(NULL, IDC_ARROW));
2469 if (message == wm_mousewheel || message == WM_MOUSEWHEEL) {
2470 int shift_pressed=0, control_pressed=0, alt_pressed=0;
2472 if (message == WM_MOUSEWHEEL) {
2473 wheel_accumulator += (short)HIWORD(wParam);
2474 shift_pressed=LOWORD(wParam) & MK_SHIFT;
2475 control_pressed=LOWORD(wParam) & MK_CONTROL;
2478 wheel_accumulator += (int)wParam;
2479 if (GetKeyboardState(keys)!=0) {
2480 shift_pressed=keys[VK_SHIFT]&0x80;
2481 control_pressed=keys[VK_CONTROL]&0x80;
2485 /* process events when the threshold is reached */
2486 while (abs(wheel_accumulator) >= WHEEL_DELTA) {
2489 /* reduce amount for next time */
2490 if (wheel_accumulator > 0) {
2492 wheel_accumulator -= WHEEL_DELTA;
2493 } else if (wheel_accumulator < 0) {
2495 wheel_accumulator += WHEEL_DELTA;
2499 if (send_raw_mouse &&
2500 !(cfg.mouse_override && shift_pressed)) {
2501 /* send a mouse-down followed by a mouse up */
2504 TO_CHR_X(X_POS(lParam)),
2505 TO_CHR_Y(Y_POS(lParam)), shift_pressed,
2506 control_pressed, is_alt_pressed());
2507 term_mouse(b, MA_RELEASE, TO_CHR_X(X_POS(lParam)),
2508 TO_CHR_Y(Y_POS(lParam)), shift_pressed,
2509 control_pressed, is_alt_pressed());
2511 /* trigger a scroll */
2513 b == MBT_WHEEL_UP ? -rows / 2 : rows / 2);
2520 return DefWindowProc(hwnd, message, wParam, lParam);
2524 * Move the system caret. (We maintain one, even though it's
2525 * invisible, for the benefit of blind people: apparently some
2526 * helper software tracks the system caret, so we should arrange to
2529 void sys_cursor(int x, int y)
2533 if (!has_focus) return;
2536 * Avoid gratuitously re-updating the cursor position and IMM
2537 * window if there's no actual change required.
2539 cx = x * font_width + offset_width;
2540 cy = y * font_height + offset_height;
2541 if (cx == caret_x && cy == caret_y)
2546 sys_cursor_update();
2549 static void sys_cursor_update(void)
2554 if (!has_focus) return;
2556 if (caret_x < 0 || caret_y < 0)
2559 SetCaretPos(caret_x, caret_y);
2561 /* IMM calls on Win98 and beyond only */
2562 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32s) return; /* 3.11 */
2564 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS &&
2565 osVersion.dwMinorVersion == 0) return; /* 95 */
2567 /* we should have the IMM functions */
2568 hIMC = ImmGetContext(hwnd);
2569 cf.dwStyle = CFS_POINT;
2570 cf.ptCurrentPos.x = caret_x;
2571 cf.ptCurrentPos.y = caret_y;
2572 ImmSetCompositionWindow(hIMC, &cf);
2574 ImmReleaseContext(hwnd, hIMC);
2578 * Draw a line of text in the window, at given character
2579 * coordinates, in given attributes.
2581 * We are allowed to fiddle with the contents of `text'.
2583 void do_text(Context ctx, int x, int y, char *text, int len,
2584 unsigned long attr, int lattr)
2587 int nfg, nbg, nfont;
2590 int force_manual_underline = 0;
2591 int fnt_width = font_width * (1 + (lattr != LATTR_NORM));
2592 int char_width = fnt_width;
2593 int text_adjust = 0;
2594 static int *IpDx = 0, IpDxLEN = 0;
2596 if (attr & ATTR_WIDE)
2599 if (len > IpDxLEN || IpDx[0] != char_width) {
2601 if (len > IpDxLEN) {
2603 IpDx = smalloc((len + 16) * sizeof(int));
2604 IpDxLEN = (len + 16);
2606 for (i = 0; i < IpDxLEN; i++)
2607 IpDx[i] = char_width;
2610 /* Only want the left half of double width lines */
2611 if (lattr != LATTR_NORM && x*2 >= cols)
2619 if ((attr & TATTR_ACTCURS) && (cfg.cursor_type == 0 || big_cursor)) {
2620 attr &= ATTR_CUR_AND | (bold_mode != BOLD_COLOURS ? ATTR_BOLD : 0);
2621 attr ^= ATTR_CUR_XOR;
2625 if (cfg.vtmode == VT_POORMAN && lattr != LATTR_NORM) {
2626 /* Assume a poorman font is borken in other ways too. */
2636 nfont |= FONT_WIDE + FONT_HIGH;
2639 if (attr & ATTR_NARROW)
2640 nfont |= FONT_NARROW;
2642 /* Special hack for the VT100 linedraw glyphs. */
2643 if ((attr & CSET_MASK) == 0x2300) {
2644 if (text[0] >= (char) 0xBA && text[0] <= (char) 0xBD) {
2645 switch ((unsigned char) (text[0])) {
2647 text_adjust = -2 * font_height / 5;
2650 text_adjust = -1 * font_height / 5;
2653 text_adjust = font_height / 5;
2656 text_adjust = 2 * font_height / 5;
2659 if (lattr == LATTR_TOP || lattr == LATTR_BOT)
2662 text[0] = (char) (unitab_xterm['q'] & CHAR_MASK);
2663 attr |= (unitab_xterm['q'] & CSET_MASK);
2664 if (attr & ATTR_UNDER) {
2665 attr &= ~ATTR_UNDER;
2666 force_manual_underline = 1;
2671 /* Anything left as an original character set is unprintable. */
2672 if (DIRECT_CHAR(attr)) {
2675 memset(text, 0xFD, len);
2679 if ((attr & CSET_MASK) == ATTR_OEMCP)
2682 nfg = 2 * ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
2683 nbg = 2 * ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
2684 if (bold_mode == BOLD_FONT && (attr & ATTR_BOLD))
2686 if (und_mode == UND_FONT && (attr & ATTR_UNDER))
2687 nfont |= FONT_UNDERLINE;
2688 another_font(nfont);
2689 if (!fonts[nfont]) {
2690 if (nfont & FONT_UNDERLINE)
2691 force_manual_underline = 1;
2692 /* Don't do the same for manual bold, it could be bad news. */
2694 nfont &= ~(FONT_BOLD | FONT_UNDERLINE);
2696 another_font(nfont);
2698 nfont = FONT_NORMAL;
2699 if (attr & ATTR_REVERSE) {
2704 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BOLD))
2706 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BLINK))
2710 SelectObject(hdc, fonts[nfont]);
2711 SetTextColor(hdc, fg);
2712 SetBkColor(hdc, bg);
2713 SetBkMode(hdc, OPAQUE);
2716 line_box.right = x + char_width * len;
2717 line_box.bottom = y + font_height;
2719 /* Only want the left half of double width lines */
2720 if (line_box.right > font_width*cols+offset_width)
2721 line_box.right = font_width*cols+offset_width;
2723 /* We're using a private area for direct to font. (512 chars.) */
2724 if (dbcs_screenfont && (attr & CSET_MASK) == ATTR_ACP) {
2725 /* Ho Hum, dbcs fonts are a PITA! */
2726 /* To display on W9x I have to convert to UCS */
2727 static wchar_t *uni_buf = 0;
2728 static int uni_len = 0;
2730 if (len > uni_len) {
2732 uni_buf = smalloc((uni_len = len) * sizeof(wchar_t));
2735 for(nlen = mptr = 0; mptr<len; mptr++) {
2736 uni_buf[nlen] = 0xFFFD;
2737 if (IsDBCSLeadByteEx(font_codepage, (BYTE) text[mptr])) {
2738 IpDx[nlen] += char_width;
2739 MultiByteToWideChar(font_codepage, MB_USEGLYPHCHARS,
2740 text+mptr, 2, uni_buf+nlen, 1);
2745 MultiByteToWideChar(font_codepage, MB_USEGLYPHCHARS,
2746 text+mptr, 1, uni_buf+nlen, 1);
2754 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2755 ETO_CLIPPED | ETO_OPAQUE, &line_box, uni_buf, nlen, IpDx);
2756 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2757 SetBkMode(hdc, TRANSPARENT);
2758 ExtTextOutW(hdc, x - 1,
2759 y - font_height * (lattr ==
2760 LATTR_BOT) + text_adjust,
2761 ETO_CLIPPED, &line_box, uni_buf, nlen, IpDx);
2765 } else if (DIRECT_FONT(attr)) {
2767 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2768 ETO_CLIPPED | ETO_OPAQUE, &line_box, text, len, IpDx);
2769 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2770 SetBkMode(hdc, TRANSPARENT);
2772 /* GRR: This draws the character outside it's box and can leave
2773 * 'droppings' even with the clip box! I suppose I could loop it
2774 * one character at a time ... yuk.
2776 * Or ... I could do a test print with "W", and use +1 or -1 for this
2777 * shift depending on if the leftmost column is blank...
2779 ExtTextOut(hdc, x - 1,
2780 y - font_height * (lattr ==
2781 LATTR_BOT) + text_adjust,
2782 ETO_CLIPPED, &line_box, text, len, IpDx);
2785 /* And 'normal' unicode characters */
2786 static WCHAR *wbuf = NULL;
2787 static int wlen = 0;
2792 wbuf = smalloc(wlen * sizeof(WCHAR));
2794 for (i = 0; i < len; i++)
2795 wbuf[i] = (WCHAR) ((attr & CSET_MASK) + (text[i] & CHAR_MASK));
2798 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2799 ETO_CLIPPED | ETO_OPAQUE, &line_box, wbuf, len, IpDx);
2801 /* And the shadow bold hack. */
2802 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2803 SetBkMode(hdc, TRANSPARENT);
2804 ExtTextOutW(hdc, x - 1,
2805 y - font_height * (lattr ==
2806 LATTR_BOT) + text_adjust,
2807 ETO_CLIPPED, &line_box, wbuf, len, IpDx);
2810 if (lattr != LATTR_TOP && (force_manual_underline ||
2811 (und_mode == UND_LINE
2812 && (attr & ATTR_UNDER)))) {
2815 if (lattr == LATTR_BOT)
2816 dec = dec * 2 - font_height;
2818 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, fg));
2819 MoveToEx(hdc, x, y + dec, NULL);
2820 LineTo(hdc, x + len * char_width, y + dec);
2821 oldpen = SelectObject(hdc, oldpen);
2822 DeleteObject(oldpen);
2826 void do_cursor(Context ctx, int x, int y, char *text, int len,
2827 unsigned long attr, int lattr)
2833 int ctype = cfg.cursor_type;
2835 if ((attr & TATTR_ACTCURS) && (ctype == 0 || big_cursor)) {
2836 if (((attr & CSET_MASK) | (unsigned char) *text) != UCSWIDE) {
2837 do_text(ctx, x, y, text, len, attr, lattr);
2841 attr |= TATTR_RIGHTCURS;
2844 fnt_width = char_width = font_width * (1 + (lattr != LATTR_NORM));
2845 if (attr & ATTR_WIDE)
2852 if ((attr & TATTR_PASCURS) && (ctype == 0 || big_cursor)) {
2855 pts[0].x = pts[1].x = pts[4].x = x;
2856 pts[2].x = pts[3].x = x + char_width - 1;
2857 pts[0].y = pts[3].y = pts[4].y = y;
2858 pts[1].y = pts[2].y = y + font_height - 1;
2859 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2860 Polyline(hdc, pts, 5);
2861 oldpen = SelectObject(hdc, oldpen);
2862 DeleteObject(oldpen);
2863 } else if ((attr & (TATTR_ACTCURS | TATTR_PASCURS)) && ctype != 0) {
2864 int startx, starty, dx, dy, length, i;
2867 starty = y + descent;
2870 length = char_width;
2873 if (attr & TATTR_RIGHTCURS)
2874 xadjust = char_width - 1;
2875 startx = x + xadjust;
2879 length = font_height;
2881 if (attr & TATTR_ACTCURS) {
2884 SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2885 MoveToEx(hdc, startx, starty, NULL);
2886 LineTo(hdc, startx + dx * length, starty + dy * length);
2887 oldpen = SelectObject(hdc, oldpen);
2888 DeleteObject(oldpen);
2890 for (i = 0; i < length; i++) {
2892 SetPixel(hdc, startx, starty, colours[23]);
2901 /* This function gets the actual width of a character in the normal font.
2903 int CharWidth(Context ctx, int uc) {
2907 /* If the font max is the same as the font ave width then this
2908 * function is a no-op.
2910 if (!font_dualwidth) return 1;
2912 switch (uc & CSET_MASK) {
2914 uc = unitab_line[uc & 0xFF];
2917 uc = unitab_xterm[uc & 0xFF];
2920 uc = unitab_scoacs[uc & 0xFF];
2923 if (DIRECT_FONT(uc)) {
2924 if (dbcs_screenfont) return 1;
2926 /* Speedup, I know of no font where ascii is the wrong width */
2927 if ((uc&CHAR_MASK) >= ' ' && (uc&CHAR_MASK)<= '~')
2930 if ( (uc & CSET_MASK) == ATTR_ACP ) {
2931 SelectObject(hdc, fonts[FONT_NORMAL]);
2932 } else if ( (uc & CSET_MASK) == ATTR_OEMCP ) {
2933 another_font(FONT_OEM);
2934 if (!fonts[FONT_OEM]) return 0;
2936 SelectObject(hdc, fonts[FONT_OEM]);
2940 if ( GetCharWidth32(hdc, uc&CHAR_MASK, uc&CHAR_MASK, &ibuf) != 1 &&
2941 GetCharWidth(hdc, uc&CHAR_MASK, uc&CHAR_MASK, &ibuf) != 1)
2944 /* Speedup, I know of no font where ascii is the wrong width */
2945 if (uc >= ' ' && uc <= '~') return 1;
2947 SelectObject(hdc, fonts[FONT_NORMAL]);
2948 if ( GetCharWidth32W(hdc, uc, uc, &ibuf) == 1 )
2949 /* Okay that one worked */ ;
2950 else if ( GetCharWidthW(hdc, uc, uc, &ibuf) == 1 )
2951 /* This should work on 9x too, but it's "less accurate" */ ;
2956 ibuf += font_width / 2 -1;
2963 * Translate a WM_(SYS)?KEY(UP|DOWN) message into a string of ASCII
2964 * codes. Returns number of bytes used or zero to drop the message
2965 * or -1 to forward the message to windows.
2967 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
2968 unsigned char *output)
2971 int scan, left_alt = 0, key_down, shift_state;
2973 unsigned char *p = output;
2974 static int alt_sum = 0;
2976 HKL kbd_layout = GetKeyboardLayout(0);
2978 static WORD keys[3];
2979 static int compose_char = 0;
2980 static WPARAM compose_key = 0;
2982 r = GetKeyboardState(keystate);
2984 memset(keystate, 0, sizeof(keystate));
2987 #define SHOW_TOASCII_RESULT
2988 { /* Tell us all about key events */
2989 static BYTE oldstate[256];
2990 static int first = 1;
2994 memcpy(oldstate, keystate, sizeof(oldstate));
2997 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT) {
2999 } else if ((HIWORD(lParam) & KF_UP)
3000 && scan == (HIWORD(lParam) & 0xFF)) {
3004 if (wParam >= VK_F1 && wParam <= VK_F20)
3005 debug(("K_F%d", wParam + 1 - VK_F1));
3018 debug(("VK_%02x", wParam));
3020 if (message == WM_SYSKEYDOWN || message == WM_SYSKEYUP)
3022 debug((", S%02x", scan = (HIWORD(lParam) & 0xFF)));
3024 ch = MapVirtualKeyEx(wParam, 2, kbd_layout);
3025 if (ch >= ' ' && ch <= '~')
3026 debug((", '%c'", ch));
3028 debug((", $%02x", ch));
3031 debug((", KB0=%02x", keys[0]));
3033 debug((", KB1=%02x", keys[1]));
3035 debug((", KB2=%02x", keys[2]));
3037 if ((keystate[VK_SHIFT] & 0x80) != 0)
3039 if ((keystate[VK_CONTROL] & 0x80) != 0)
3041 if ((HIWORD(lParam) & KF_EXTENDED))
3043 if ((HIWORD(lParam) & KF_UP))
3047 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT);
3048 else if ((HIWORD(lParam) & KF_UP))
3049 oldstate[wParam & 0xFF] ^= 0x80;
3051 oldstate[wParam & 0xFF] ^= 0x81;
3053 for (ch = 0; ch < 256; ch++)
3054 if (oldstate[ch] != keystate[ch])
3055 debug((", M%02x=%02x", ch, keystate[ch]));
3057 memcpy(oldstate, keystate, sizeof(oldstate));
3061 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED)) {
3062 keystate[VK_RMENU] = keystate[VK_MENU];
3066 /* Nastyness with NUMLock - Shift-NUMLock is left alone though */
3067 if ((cfg.funky_type == 3 ||
3068 (cfg.funky_type <= 1 && app_keypad_keys && !cfg.no_applic_k))
3069 && wParam == VK_NUMLOCK && !(keystate[VK_SHIFT] & 0x80)) {
3071 wParam = VK_EXECUTE;
3073 /* UnToggle NUMLock */
3074 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0)
3075 keystate[VK_NUMLOCK] ^= 1;
3078 /* And write back the 'adjusted' state */
3079 SetKeyboardState(keystate);
3082 /* Disable Auto repeat if required */
3083 if (repeat_off && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT)
3086 if ((HIWORD(lParam) & KF_ALTDOWN) && (keystate[VK_RMENU] & 0x80) == 0)
3089 key_down = ((HIWORD(lParam) & KF_UP) == 0);
3091 /* Make sure Ctrl-ALT is not the same as AltGr for ToAscii unless told. */
3092 if (left_alt && (keystate[VK_CONTROL] & 0x80)) {
3093 if (cfg.ctrlaltkeys)
3094 keystate[VK_MENU] = 0;
3096 keystate[VK_RMENU] = 0x80;
3101 alt_pressed = (left_alt && key_down);
3103 scan = (HIWORD(lParam) & (KF_UP | KF_EXTENDED | 0xFF));
3104 shift_state = ((keystate[VK_SHIFT] & 0x80) != 0)
3105 + ((keystate[VK_CONTROL] & 0x80) != 0) * 2;
3107 /* Note if AltGr was pressed and if it was used as a compose key */
3108 if (!compose_state) {
3109 compose_key = 0x100;
3110 if (cfg.compose_key) {
3111 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED))
3112 compose_key = wParam;
3114 if (wParam == VK_APPS)
3115 compose_key = wParam;
3118 if (wParam == compose_key) {
3119 if (compose_state == 0
3120 && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0) compose_state =
3122 else if (compose_state == 1 && (HIWORD(lParam) & KF_UP))
3126 } else if (compose_state == 1 && wParam != VK_CONTROL)
3130 * Record that we pressed key so the scroll window can be reset, but
3131 * be careful to avoid Shift-UP/Down
3133 if (wParam != VK_SHIFT && wParam != VK_PRIOR && wParam != VK_NEXT &&
3134 wParam != VK_MENU && wParam != VK_CONTROL) {
3138 if (compose_state > 1 && left_alt)
3141 /* Sanitize the number pad if not using a PC NumPad */
3142 if (left_alt || (app_keypad_keys && !cfg.no_applic_k
3143 && cfg.funky_type != 2)
3144 || cfg.funky_type == 3 || cfg.nethack_keypad || compose_state) {
3145 if ((HIWORD(lParam) & KF_EXTENDED) == 0) {
3149 nParam = VK_NUMPAD0;
3152 nParam = VK_NUMPAD1;
3155 nParam = VK_NUMPAD2;
3158 nParam = VK_NUMPAD3;
3161 nParam = VK_NUMPAD4;
3164 nParam = VK_NUMPAD5;
3167 nParam = VK_NUMPAD6;
3170 nParam = VK_NUMPAD7;
3173 nParam = VK_NUMPAD8;
3176 nParam = VK_NUMPAD9;
3179 nParam = VK_DECIMAL;
3183 if (keystate[VK_NUMLOCK] & 1)
3190 /* If a key is pressed and AltGr is not active */
3191 if (key_down && (keystate[VK_RMENU] & 0x80) == 0 && !compose_state) {
3192 /* Okay, prepare for most alts then ... */
3196 /* Lets see if it's a pattern we know all about ... */
3197 if (wParam == VK_PRIOR && shift_state == 1) {
3198 SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0);
3201 if (wParam == VK_NEXT && shift_state == 1) {
3202 SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
3205 if (wParam == VK_INSERT && shift_state == 1) {
3209 if (left_alt && wParam == VK_F4 && cfg.alt_f4) {
3212 if (left_alt && wParam == VK_SPACE && cfg.alt_space) {
3213 SendMessage(hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0);
3216 if (left_alt && wParam == VK_RETURN && cfg.fullscreenonaltenter &&
3217 (cfg.resize_action != RESIZE_DISABLED)) {
3218 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) != KF_REPEAT)
3222 /* Control-Numlock for app-keypad mode switch */
3223 if (wParam == VK_PAUSE && shift_state == 2) {
3224 app_keypad_keys ^= 1;
3228 /* Nethack keypad */
3229 if (cfg.nethack_keypad && !left_alt) {
3232 *p++ = shift_state ? 'B' : 'b';
3235 *p++ = shift_state ? 'J' : 'j';
3238 *p++ = shift_state ? 'N' : 'n';
3241 *p++ = shift_state ? 'H' : 'h';
3244 *p++ = shift_state ? '.' : '.';
3247 *p++ = shift_state ? 'L' : 'l';
3250 *p++ = shift_state ? 'Y' : 'y';
3253 *p++ = shift_state ? 'K' : 'k';
3256 *p++ = shift_state ? 'U' : 'u';
3261 /* Application Keypad */
3265 if (cfg.funky_type == 3 ||
3266 (cfg.funky_type <= 1 &&
3267 app_keypad_keys && !cfg.no_applic_k)) switch (wParam) {
3281 if (app_keypad_keys && !cfg.no_applic_k)
3318 if (cfg.funky_type == 2) {
3323 } else if (shift_state)
3330 if (cfg.funky_type == 2)
3334 if (cfg.funky_type == 2)
3338 if (cfg.funky_type == 2)
3343 if (HIWORD(lParam) & KF_EXTENDED)
3349 if (xkey >= 'P' && xkey <= 'S')
3350 p += sprintf((char *) p, "\x1B%c", xkey);
3352 p += sprintf((char *) p, "\x1B?%c", xkey);
3354 p += sprintf((char *) p, "\x1BO%c", xkey);
3359 if (wParam == VK_BACK && shift_state == 0) { /* Backspace */
3360 *p++ = (cfg.bksp_is_delete ? 0x7F : 0x08);
3364 if (wParam == VK_TAB && shift_state == 1) { /* Shift tab */
3370 if (wParam == VK_SPACE && shift_state == 2) { /* Ctrl-Space */
3374 if (wParam == VK_SPACE && shift_state == 3) { /* Ctrl-Shift-Space */
3378 if (wParam == VK_CANCEL && shift_state == 2) { /* Ctrl-Break */
3383 if (wParam == VK_PAUSE) { /* Break/Pause */
3388 /* Control-2 to Control-8 are special */
3389 if (shift_state == 2 && wParam >= '2' && wParam <= '8') {
3390 *p++ = "\000\033\034\035\036\037\177"[wParam - '2'];
3393 if (shift_state == 2 && wParam == 0xBD) {
3397 if (shift_state == 2 && wParam == 0xDF) {
3401 if (shift_state == 0 && wParam == VK_RETURN && cr_lf_return) {
3408 * Next, all the keys that do tilde codes. (ESC '[' nn '~',
3409 * for integer decimal nn.)
3411 * We also deal with the weird ones here. Linux VCs replace F1
3412 * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
3413 * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
3419 code = (keystate[VK_SHIFT] & 0x80 ? 23 : 11);
3422 code = (keystate[VK_SHIFT] & 0x80 ? 24 : 12);
3425 code = (keystate[VK_SHIFT] & 0x80 ? 25 : 13);
3428 code = (keystate[VK_SHIFT] & 0x80 ? 26 : 14);
3431 code = (keystate[VK_SHIFT] & 0x80 ? 28 : 15);
3434 code = (keystate[VK_SHIFT] & 0x80 ? 29 : 17);
3437 code = (keystate[VK_SHIFT] & 0x80 ? 31 : 18);
3440 code = (keystate[VK_SHIFT] & 0x80 ? 32 : 19);
3443 code = (keystate[VK_SHIFT] & 0x80 ? 33 : 20);
3446 code = (keystate[VK_SHIFT] & 0x80 ? 34 : 21);
3479 if ((shift_state&2) == 0) switch (wParam) {
3499 /* Reorder edit keys to physical order */
3500 if (cfg.funky_type == 3 && code <= 6)
3501 code = "\0\2\1\4\5\3\6"[code];
3503 if (vt52_mode && code > 0 && code <= 6) {
3504 p += sprintf((char *) p, "\x1B%c", " HLMEIG"[code]);
3508 if (cfg.funky_type == 5 && /* SCO function keys */
3509 code >= 11 && code <= 34) {
3510 char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
3513 case VK_F1: index = 0; break;
3514 case VK_F2: index = 1; break;
3515 case VK_F3: index = 2; break;
3516 case VK_F4: index = 3; break;
3517 case VK_F5: index = 4; break;
3518 case VK_F6: index = 5; break;
3519 case VK_F7: index = 6; break;
3520 case VK_F8: index = 7; break;
3521 case VK_F9: index = 8; break;
3522 case VK_F10: index = 9; break;
3523 case VK_F11: index = 10; break;
3524 case VK_F12: index = 11; break;
3526 if (keystate[VK_SHIFT] & 0x80) index += 12;
3527 if (keystate[VK_CONTROL] & 0x80) index += 24;
3528 p += sprintf((char *) p, "\x1B[%c", codes[index]);
3531 if (cfg.funky_type == 5 && /* SCO small keypad */
3532 code >= 1 && code <= 6) {
3533 char codes[] = "HL.FIG";
3537 p += sprintf((char *) p, "\x1B[%c", codes[code-1]);
3541 if ((vt52_mode || cfg.funky_type == 4) && code >= 11 && code <= 24) {
3548 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11 - offt);
3551 sprintf((char *) p, "\x1BO%c", code + 'P' - 11 - offt);
3554 if (cfg.funky_type == 1 && code >= 11 && code <= 15) {
3555 p += sprintf((char *) p, "\x1B[[%c", code + 'A' - 11);
3558 if (cfg.funky_type == 2 && code >= 11 && code <= 14) {
3560 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11);
3562 p += sprintf((char *) p, "\x1BO%c", code + 'P' - 11);
3565 if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
3566 p += sprintf((char *) p, code == 1 ? "\x1B[H" : "\x1BOw");
3570 p += sprintf((char *) p, "\x1B[%d~", code);
3575 * Now the remaining keys (arrows and Keypad 5. Keypad 5 for
3576 * some reason seems to send VK_CLEAR to Windows...).
3599 p += sprintf((char *) p, "\x1B%c", xkey);
3601 int app_flg = (app_cursor_keys && !cfg.no_applic_c);
3604 * RDB: VT100 & VT102 manuals both state the
3605 * app cursor keys only work if the app keypad
3608 * SGT: That may well be true, but xterm
3609 * disagrees and so does at least one
3610 * application, so I've #if'ed this out and the
3611 * behaviour is back to PuTTY's original: app
3612 * cursor and app keypad are independently
3613 * switchable modes. If anyone complains about
3614 * _this_ I'll have to put in a configurable
3617 if (!app_keypad_keys)
3620 /* Useful mapping of Ctrl-arrows */
3621 if (shift_state == 2)
3625 p += sprintf((char *) p, "\x1BO%c", xkey);
3627 p += sprintf((char *) p, "\x1B[%c", xkey);
3634 * Finally, deal with Return ourselves. (Win95 seems to
3635 * foul it up when Alt is pressed, for some reason.)
3637 if (wParam == VK_RETURN) { /* Return */
3643 if (left_alt && wParam >= VK_NUMPAD0 && wParam <= VK_NUMPAD9)
3644 alt_sum = alt_sum * 10 + wParam - VK_NUMPAD0;
3649 /* Okay we've done everything interesting; let windows deal with
3650 * the boring stuff */
3654 /* helg: clear CAPS LOCK state if caps lock switches to cyrillic */
3655 if(cfg.xlat_capslockcyr && keystate[VK_CAPITAL] != 0) {
3657 keystate[VK_CAPITAL] = 0;
3660 r = ToAsciiEx(wParam, scan, keystate, keys, 0, kbd_layout);
3661 #ifdef SHOW_TOASCII_RESULT
3662 if (r == 1 && !key_down) {
3664 if (in_utf || dbcs_screenfont)
3665 debug((", (U+%04x)", alt_sum));
3667 debug((", LCH(%d)", alt_sum));
3669 debug((", ACH(%d)", keys[0]));
3674 for (r1 = 0; r1 < r; r1++) {
3675 debug(("%s%d", r1 ? "," : "", keys[r1]));
3684 * Interrupt an ongoing paste. I'm not sure this is
3685 * sensible, but for the moment it's preferable to
3686 * having to faff about buffering things.
3691 for (i = 0; i < r; i++) {
3692 unsigned char ch = (unsigned char) keys[i];
3694 if (compose_state == 2 && (ch & 0x80) == 0 && ch > ' ') {
3699 if (compose_state == 3 && (ch & 0x80) == 0 && ch > ' ') {
3703 if ((nc = check_compose(compose_char, ch)) == -1) {
3704 MessageBeep(MB_ICONHAND);
3708 luni_send(&keybuf, 1, 1);
3716 if (in_utf || dbcs_screenfont) {
3718 luni_send(&keybuf, 1, 1);
3720 ch = (char) alt_sum;
3722 * We need not bother about stdin
3723 * backlogs here, because in GUI PuTTY
3724 * we can't do anything about it
3725 * anyway; there's no means of asking
3726 * Windows to hold off on KEYDOWN
3727 * messages. We _have_ to buffer
3728 * everything we're sent.
3730 ldisc_send(&ch, 1, 1);
3734 lpage_send(kbd_codepage, &ch, 1, 1);
3736 if(capsOn && ch < 0x80) {
3739 cbuf[1] = xlat_uskbd2cyrllic(ch);
3740 luni_send(cbuf+!left_alt, 1+!!left_alt, 1);
3745 lpage_send(kbd_codepage, cbuf+!left_alt, 1+!!left_alt, 1);
3751 /* This is so the ALT-Numpad and dead keys work correctly. */
3756 /* If we're definitly not building up an ALT-54321 then clear it */
3759 /* If we will be using alt_sum fix the 256s */
3760 else if (keys[0] && (in_utf || dbcs_screenfont))
3765 * ALT alone may or may not want to bring up the System menu.
3766 * If it's not meant to, we return 0 on presses or releases of
3767 * ALT, to show that we've swallowed the keystroke. Otherwise
3768 * we return -1, which means Windows will give the keystroke
3769 * its default handling (i.e. bring up the System menu).
3771 if (wParam == VK_MENU && !cfg.alt_only)
3777 void set_title(char *title)
3780 window_name = smalloc(1 + strlen(title));
3781 strcpy(window_name, title);
3782 if (cfg.win_name_always || !IsIconic(hwnd))
3783 SetWindowText(hwnd, title);
3786 void set_icon(char *title)
3789 icon_name = smalloc(1 + strlen(title));
3790 strcpy(icon_name, title);
3791 if (!cfg.win_name_always && IsIconic(hwnd))
3792 SetWindowText(hwnd, title);
3795 void set_sbar(int total, int start, int page)
3799 if (is_full_screen() ? !cfg.scrollbar_in_fullscreen : !cfg.scrollbar)
3802 si.cbSize = sizeof(si);
3803 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
3805 si.nMax = total - 1;
3809 SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
3812 Context get_ctx(void)
3818 SelectPalette(hdc, pal, FALSE);
3824 void free_ctx(Context ctx)
3826 SelectPalette(ctx, GetStockObject(DEFAULT_PALETTE), FALSE);
3827 ReleaseDC(hwnd, ctx);
3830 static void real_palette_set(int n, int r, int g, int b)
3833 logpal->palPalEntry[n].peRed = r;
3834 logpal->palPalEntry[n].peGreen = g;
3835 logpal->palPalEntry[n].peBlue = b;
3836 logpal->palPalEntry[n].peFlags = PC_NOCOLLAPSE;
3837 colours[n] = PALETTERGB(r, g, b);
3838 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3840 colours[n] = RGB(r, g, b);
3843 void palette_set(int n, int r, int g, int b)
3845 static const int first[21] = {
3846 0, 2, 4, 6, 8, 10, 12, 14,
3847 1, 3, 5, 7, 9, 11, 13, 15,
3850 real_palette_set(first[n], r, g, b);
3852 real_palette_set(first[n] + 1, r, g, b);
3854 HDC hdc = get_ctx();
3855 UnrealizeObject(pal);
3856 RealizePalette(hdc);
3861 void palette_reset(void)
3865 for (i = 0; i < NCOLOURS; i++) {
3867 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
3868 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
3869 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
3870 logpal->palPalEntry[i].peFlags = 0;
3871 colours[i] = PALETTERGB(defpal[i].rgbtRed,
3872 defpal[i].rgbtGreen,
3873 defpal[i].rgbtBlue);
3875 colours[i] = RGB(defpal[i].rgbtRed,
3876 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
3881 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3883 RealizePalette(hdc);
3888 void write_aclip(char *data, int len, int must_deselect)
3893 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len + 1);
3896 lock = GlobalLock(clipdata);
3899 memcpy(lock, data, len);
3900 ((unsigned char *) lock)[len] = 0;
3901 GlobalUnlock(clipdata);
3904 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
3906 if (OpenClipboard(hwnd)) {
3908 SetClipboardData(CF_TEXT, clipdata);
3911 GlobalFree(clipdata);
3914 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
3918 * Note: unlike write_aclip() this will not append a nul.
3920 void write_clip(wchar_t * data, int len, int must_deselect)
3922 HGLOBAL clipdata, clipdata2, clipdata3;
3924 void *lock, *lock2, *lock3;
3926 len2 = WideCharToMultiByte(CP_ACP, 0, data, len, 0, 0, NULL, NULL);
3928 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE,
3929 len * sizeof(wchar_t));
3930 clipdata2 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len2);
3932 if (!clipdata || !clipdata2) {
3934 GlobalFree(clipdata);
3936 GlobalFree(clipdata2);
3939 if (!(lock = GlobalLock(clipdata)))
3941 if (!(lock2 = GlobalLock(clipdata2)))
3944 memcpy(lock, data, len * sizeof(wchar_t));
3945 WideCharToMultiByte(CP_ACP, 0, data, len, lock2, len2, NULL, NULL);
3947 if (cfg.rtf_paste) {
3948 wchar_t unitab[256];
3950 unsigned char *tdata = (unsigned char *)lock2;
3951 wchar_t *udata = (wchar_t *)lock;
3952 int rtflen = 0, uindex = 0, tindex = 0;
3954 int multilen, blen, alen, totallen, i;
3955 char before[16], after[4];
3957 get_unitab(CP_ACP, unitab, 0);
3959 rtfsize = 100 + strlen(cfg.font);
3960 rtf = smalloc(rtfsize);
3961 sprintf(rtf, "{\\rtf1\\ansi%d{\\fonttbl\\f0\\fmodern %s;}\\f0",
3962 GetACP(), cfg.font);
3963 rtflen = strlen(rtf);
3966 * We want to construct a piece of RTF that specifies the
3967 * same Unicode text. To do this we will read back in
3968 * parallel from the Unicode data in `udata' and the
3969 * non-Unicode data in `tdata'. For each character in
3970 * `tdata' which becomes the right thing in `udata' when
3971 * looked up in `unitab', we just copy straight over from
3972 * tdata. For each one that doesn't, we must WCToMB it
3973 * individually and produce a \u escape sequence.
3975 * It would probably be more robust to just bite the bullet
3976 * and WCToMB each individual Unicode character one by one,
3977 * then MBToWC each one back to see if it was an accurate
3978 * translation; but that strikes me as a horrifying number
3979 * of Windows API calls so I want to see if this faster way
3980 * will work. If it screws up badly we can always revert to
3981 * the simple and slow way.
3983 while (tindex < len2 && uindex < len &&
3984 tdata[tindex] && udata[uindex]) {
3985 if (tindex + 1 < len2 &&
3986 tdata[tindex] == '\r' &&
3987 tdata[tindex+1] == '\n') {
3991 if (unitab[tdata[tindex]] == udata[uindex]) {
3997 multilen = WideCharToMultiByte(CP_ACP, 0, unitab+uindex, 1,
3998 NULL, 0, NULL, NULL);
3999 if (multilen != 1) {
4000 blen = sprintf(before, "{\\uc%d\\u%d", multilen,
4002 alen = 1; strcpy(after, "}");
4004 blen = sprintf(before, "\\u%d", udata[uindex]);
4005 alen = 0; after[0] = '\0';
4008 assert(tindex + multilen <= len2);
4009 totallen = blen + alen;
4010 for (i = 0; i < multilen; i++) {
4011 if (tdata[tindex+i] == '\\' ||
4012 tdata[tindex+i] == '{' ||
4013 tdata[tindex+i] == '}')
4015 else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A)
4016 totallen += 6; /* \par\r\n */
4017 else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20)
4023 if (rtfsize < rtflen + totallen + 3) {
4024 rtfsize = rtflen + totallen + 512;
4025 rtf = srealloc(rtf, rtfsize);
4028 strcpy(rtf + rtflen, before); rtflen += blen;
4029 for (i = 0; i < multilen; i++) {
4030 if (tdata[tindex+i] == '\\' ||
4031 tdata[tindex+i] == '{' ||
4032 tdata[tindex+i] == '}') {
4033 rtf[rtflen++] = '\\';
4034 rtf[rtflen++] = tdata[tindex+i];
4035 } else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A) {
4036 rtflen += sprintf(rtf+rtflen, "\\par\r\n");
4037 } else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20) {
4038 rtflen += sprintf(rtf+rtflen, "\\'%02x", tdata[tindex+i]);
4040 rtf[rtflen++] = tdata[tindex+i];
4043 strcpy(rtf + rtflen, after); rtflen += alen;
4049 strcpy(rtf + rtflen, "}");
4052 clipdata3 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, rtflen);
4053 if (clipdata3 && (lock3 = GlobalLock(clipdata3)) != NULL) {
4055 GlobalUnlock(clipdata3);
4061 GlobalUnlock(clipdata);
4062 GlobalUnlock(clipdata2);
4065 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
4067 if (OpenClipboard(hwnd)) {
4069 SetClipboardData(CF_UNICODETEXT, clipdata);
4070 SetClipboardData(CF_TEXT, clipdata2);
4072 SetClipboardData(RegisterClipboardFormat(CF_RTF), clipdata3);
4075 GlobalFree(clipdata);
4076 GlobalFree(clipdata2);
4080 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
4083 void get_clip(wchar_t ** p, int *len)
4085 static HGLOBAL clipdata = NULL;
4086 static wchar_t *converted = 0;
4095 GlobalUnlock(clipdata);
4098 } else if (OpenClipboard(NULL)) {
4099 if ((clipdata = GetClipboardData(CF_UNICODETEXT))) {
4101 *p = GlobalLock(clipdata);
4103 for (p2 = *p; *p2; p2++);
4107 } else if ( (clipdata = GetClipboardData(CF_TEXT)) ) {
4111 s = GlobalLock(clipdata);
4112 i = MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, 0, 0);
4113 *p = converted = smalloc(i * sizeof(wchar_t));
4114 MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, converted, i);
4127 * Move `lines' lines from position `from' to position `to' in the
4130 void optimised_move(int to, int from, int lines)
4135 min = (to < from ? to : from);
4136 max = to + from - min;
4138 r.left = offset_width;
4139 r.right = offset_width + cols * font_width;
4140 r.top = offset_height + min * font_height;
4141 r.bottom = offset_height + (max + lines) * font_height;
4142 ScrollWindow(hwnd, 0, (to - from) * font_height, &r, &r);
4147 * Print a message box and perform a fatal exit.
4149 void fatalbox(char *fmt, ...)
4155 vsprintf(stuff, fmt, ap);
4157 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
4162 * Manage window caption / taskbar flashing, if enabled.
4163 * 0 = stop, 1 = maintain, 2 = start
4165 static void flash_window(int mode)
4167 static long last_flash = 0;
4168 static int flashing = 0;
4169 if ((mode == 0) || (cfg.beep_ind == B_IND_DISABLED)) {
4172 FlashWindow(hwnd, FALSE);
4176 } else if (mode == 2) {
4179 last_flash = GetTickCount();
4181 FlashWindow(hwnd, TRUE);
4184 } else if ((mode == 1) && (cfg.beep_ind == B_IND_FLASH)) {
4187 long now = GetTickCount();
4188 long fdiff = now - last_flash;
4189 if (fdiff < 0 || fdiff > 450) {
4191 FlashWindow(hwnd, TRUE); /* toggle */
4202 if (mode == BELL_DEFAULT) {
4204 * For MessageBeep style bells, we want to be careful of
4205 * timing, because they don't have the nice property of
4206 * PlaySound bells that each one cancels the previous
4207 * active one. So we limit the rate to one per 50ms or so.
4209 static long lastbeep = 0;
4212 beepdiff = GetTickCount() - lastbeep;
4213 if (beepdiff >= 0 && beepdiff < 50)
4217 * The above MessageBeep call takes time, so we record the
4218 * time _after_ it finishes rather than before it starts.
4220 lastbeep = GetTickCount();
4221 } else if (mode == BELL_WAVEFILE) {
4222 if (!PlaySound(cfg.bell_wavefile, NULL, SND_ASYNC | SND_FILENAME)) {
4223 char buf[sizeof(cfg.bell_wavefile) + 80];
4224 sprintf(buf, "Unable to play sound file\n%s\n"
4225 "Using default sound instead", cfg.bell_wavefile);
4226 MessageBox(hwnd, buf, "PuTTY Sound Error",
4227 MB_OK | MB_ICONEXCLAMATION);
4228 cfg.beep = BELL_DEFAULT;
4231 /* Otherwise, either visual bell or disabled; do nothing here */
4233 flash_window(2); /* start */
4238 * Minimise or restore the window in response to a server-side
4241 void set_iconic(int iconic)
4243 if (IsIconic(hwnd)) {
4245 ShowWindow(hwnd, SW_RESTORE);
4248 ShowWindow(hwnd, SW_MINIMIZE);
4253 * Move the window in response to a server-side request.
4255 void move_window(int x, int y)
4257 if (cfg.resize_action == RESIZE_DISABLED ||
4258 cfg.resize_action == RESIZE_FONT ||
4262 SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
4266 * Move the window to the top or bottom of the z-order in response
4267 * to a server-side request.
4269 void set_zorder(int top)
4271 if (cfg.alwaysontop)
4272 return; /* ignore */
4273 SetWindowPos(hwnd, top ? HWND_TOP : HWND_BOTTOM, 0, 0, 0, 0,
4274 SWP_NOMOVE | SWP_NOSIZE);
4278 * Refresh the window in response to a server-side request.
4280 void refresh_window(void)
4282 InvalidateRect(hwnd, NULL, TRUE);
4286 * Maximise or restore the window in response to a server-side
4289 void set_zoomed(int zoomed)
4291 if (IsZoomed(hwnd)) {
4293 ShowWindow(hwnd, SW_RESTORE);
4296 ShowWindow(hwnd, SW_MAXIMIZE);
4301 * Report whether the window is iconic, for terminal reports.
4305 return IsIconic(hwnd);
4309 * Report the window's position, for terminal reports.
4311 void get_window_pos(int *x, int *y)
4314 GetWindowRect(hwnd, &r);
4320 * Report the window's pixel size, for terminal reports.
4322 void get_window_pixels(int *x, int *y)
4325 GetWindowRect(hwnd, &r);
4326 *x = r.right - r.left;
4327 *y = r.bottom - r.top;
4331 * Return the window or icon title.
4333 char *get_window_title(int icon)
4335 return icon ? icon_name : window_name;
4339 * See if we're in full-screen mode.
4341 int is_full_screen()
4343 if (!IsZoomed(hwnd))
4345 if (GetWindowLong(hwnd, GWL_STYLE) & WS_CAPTION)
4350 /* Get the rect/size of a full screen window using the nearest available
4351 * monitor in multimon systems; default to something sensible if only
4352 * one monitor is present. */
4353 static int get_fullscreen_rect(RECT * ss)
4355 #ifdef MONITOR_DEFAULTTONEAREST
4358 mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
4359 mi.cbSize = sizeof(mi);
4360 GetMonitorInfo(mon, &mi);
4362 /* structure copy */
4366 /* could also use code like this:
4367 ss->left = ss->top = 0;
4368 ss->right = GetSystemMetrics(SM_CXSCREEN);
4369 ss->bottom = GetSystemMetrics(SM_CYSCREEN);
4371 return GetClientRect(GetDesktopWindow(), ss);
4377 * Go full-screen. This should only be called when we are already
4380 void make_full_screen()
4385 assert(IsZoomed(hwnd));
4387 if (is_full_screen())
4390 /* Remove the window furniture. */
4391 style = GetWindowLong(hwnd, GWL_STYLE);
4392 style &= ~(WS_CAPTION | WS_BORDER | WS_THICKFRAME);
4393 if (cfg.scrollbar_in_fullscreen)
4394 style |= WS_VSCROLL;
4396 style &= ~WS_VSCROLL;
4397 SetWindowLong(hwnd, GWL_STYLE, style);
4399 /* Resize ourselves to exactly cover the nearest monitor. */
4400 get_fullscreen_rect(&ss);
4401 SetWindowPos(hwnd, HWND_TOP, ss.left, ss.top,
4406 /* Tick the menu item in the System menu. */
4407 CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN,
4412 * Clear the full-screen attributes.
4414 void clear_full_screen()
4416 DWORD oldstyle, style;
4418 /* Reinstate the window furniture. */
4419 style = oldstyle = GetWindowLong(hwnd, GWL_STYLE);
4420 style |= WS_CAPTION | WS_BORDER;
4421 if (cfg.resize_action == RESIZE_DISABLED)
4422 style &= ~WS_THICKFRAME;
4424 style |= WS_THICKFRAME;
4426 style |= WS_VSCROLL;
4428 style &= ~WS_VSCROLL;
4429 if (style != oldstyle) {
4430 SetWindowLong(hwnd, GWL_STYLE, style);
4431 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
4432 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
4436 /* Untick the menu item in the System menu. */
4437 CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN,
4442 * Toggle full-screen mode.
4444 void flip_full_screen()
4446 if (is_full_screen()) {
4447 ShowWindow(hwnd, SW_RESTORE);
4448 } else if (IsZoomed(hwnd)) {
4451 SendMessage(hwnd, WM_FULLSCR_ON_MAX, 0, 0);
4452 ShowWindow(hwnd, SW_MAXIMIZE);