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 OSVERSIONINFO osVersion;
164 static UINT wm_mousewheel = WM_MOUSEWHEEL;
166 /* Dummy routine, only required in plink. */
167 void ldisc_update(int echo, int edit)
171 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
173 static char appname[] = "PuTTY";
178 int guess_width, guess_height;
181 flags = FLAG_VERBOSE | FLAG_INTERACTIVE;
183 winsock_ver = MAKEWORD(1, 1);
184 if (WSAStartup(winsock_ver, &wsadata)) {
185 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
186 MB_OK | MB_ICONEXCLAMATION);
189 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
190 MessageBox(NULL, "WinSock version is incompatible with 1.1",
191 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
195 /* WISHLIST: maybe allow config tweaking even if winsock not present? */
198 InitCommonControls();
200 /* Ensure a Maximize setting in Explorer doesn't maximise the
205 ZeroMemory(&osVersion, sizeof(osVersion));
206 osVersion.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
207 if (!GetVersionEx ( (OSVERSIONINFO *) &osVersion)) {
208 MessageBox(NULL, "Windows refuses to report a version",
209 "PuTTY Fatal Error", MB_OK | MB_ICONEXCLAMATION);
215 * If we're running a version of Windows that doesn't support
216 * WM_MOUSEWHEEL, find out what message number we should be
219 if (osVersion.dwMajorVersion < 4 ||
220 (osVersion.dwMajorVersion == 4 &&
221 osVersion.dwPlatformId != VER_PLATFORM_WIN32_NT))
222 wm_mousewheel = RegisterWindowMessage("MSWHEEL_ROLLMSG");
225 * See if we can find our Help file.
228 char b[2048], *p, *q, *r;
230 GetModuleFileName(NULL, b, sizeof(b) - 1);
232 p = strrchr(b, '\\');
233 if (p && p >= r) r = p+1;
235 if (q && q >= r) r = q+1;
236 strcpy(r, "putty.hlp");
237 if ( (fp = fopen(b, "r")) != NULL) {
238 help_path = dupstr(b);
242 strcpy(r, "putty.cnt");
243 if ( (fp = fopen(b, "r")) != NULL) {
244 help_has_contents = TRUE;
247 help_has_contents = FALSE;
251 * Process the command line.
256 default_protocol = DEFAULT_PROTOCOL;
257 default_port = DEFAULT_PORT;
258 cfg.logtype = LGTYP_NONE;
260 do_defaults(NULL, &cfg);
263 while (*p && isspace(*p))
267 * Process command line options first. Yes, this can be
268 * done better, and it will be as soon as I have the
272 char *q = p + strcspn(p, " \t");
275 tolower(p[0]) == 's' &&
276 tolower(p[1]) == 's' && tolower(p[2]) == 'h') {
277 default_protocol = cfg.protocol = PROT_SSH;
278 default_port = cfg.port = 22;
279 } else if (q == p + 7 &&
280 tolower(p[0]) == 'c' &&
281 tolower(p[1]) == 'l' &&
282 tolower(p[2]) == 'e' &&
283 tolower(p[3]) == 'a' &&
284 tolower(p[4]) == 'n' &&
285 tolower(p[5]) == 'u' && tolower(p[6]) == 'p') {
287 * `putty -cleanup'. Remove all registry entries
288 * associated with PuTTY, and also find and delete
289 * the random seed file.
292 "This procedure will remove ALL Registry\n"
293 "entries associated with PuTTY, and will\n"
294 "also remove the PuTTY random seed file.\n"
296 "THIS PROCESS WILL DESTROY YOUR SAVED\n"
297 "SESSIONS. Are you really sure you want\n"
300 MB_YESNO | MB_ICONWARNING) == IDYES) {
305 p = q + strspn(q, " \t");
309 * An initial @ means to activate a saved session.
313 while (i > 1 && isspace(p[i - 1]))
316 do_defaults(p + 1, &cfg);
317 if (!*cfg.host && !do_config()) {
321 } else if (*p == '&') {
323 * An initial & means we've been given a command line
324 * containing the hex value of a HANDLE for a file
325 * mapping object, which we must then extract as a
330 if (sscanf(p + 1, "%p", &filemap) == 1 &&
331 (cp = MapViewOfFile(filemap, FILE_MAP_READ,
332 0, 0, sizeof(Config))) != NULL) {
335 CloseHandle(filemap);
336 } else if (!do_config()) {
343 * If the hostname starts with "telnet:", set the
344 * protocol to Telnet and process the string as a
347 if (!strncmp(q, "telnet:", 7)) {
351 if (q[0] == '/' && q[1] == '/')
353 cfg.protocol = PROT_TELNET;
355 while (*p && *p != ':' && *p != '/')
364 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
365 cfg.host[sizeof(cfg.host) - 1] = '\0';
367 while (*p && !isspace(*p))
371 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
372 cfg.host[sizeof(cfg.host) - 1] = '\0';
373 while (*p && isspace(*p))
388 * Trim leading whitespace off the hostname if it's there.
391 int space = strspn(cfg.host, " \t");
392 memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
395 /* See if host is of the form user@host */
396 if (cfg.host[0] != '\0') {
397 char *atsign = strchr(cfg.host, '@');
398 /* Make sure we're not overflowing the user field */
400 if (atsign - cfg.host < sizeof cfg.username) {
401 strncpy(cfg.username, cfg.host, atsign - cfg.host);
402 cfg.username[atsign - cfg.host] = '\0';
404 memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
409 * Trim a colon suffix off the hostname if it's there.
411 cfg.host[strcspn(cfg.host, ":")] = '\0';
415 * Select protocol. This is farmed out into a table in a
416 * separate file to enable an ssh-free variant.
421 for (i = 0; backends[i].backend != NULL; i++)
422 if (backends[i].protocol == cfg.protocol) {
423 back = backends[i].backend;
427 MessageBox(NULL, "Unsupported protocol number found",
428 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
434 /* Check for invalid Port number (i.e. zero) */
436 MessageBox(NULL, "Invalid Port Number",
437 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
444 wndclass.lpfnWndProc = WndProc;
445 wndclass.cbClsExtra = 0;
446 wndclass.cbWndExtra = 0;
447 wndclass.hInstance = inst;
448 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
449 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
450 wndclass.hbrBackground = NULL;
451 wndclass.lpszMenuName = NULL;
452 wndclass.lpszClassName = appname;
454 RegisterClass(&wndclass);
459 savelines = cfg.savelines;
465 * Guess some defaults for the window size. This all gets
466 * updated later, so we don't really care too much. However, we
467 * do want the font width/height guesses to correspond to a
468 * large font rather than a small one...
475 term_size(cfg.height, cfg.width, cfg.savelines);
476 guess_width = extra_width + font_width * cols;
477 guess_height = extra_height + font_height * rows;
480 HWND w = GetDesktopWindow();
481 GetWindowRect(w, &r);
482 if (guess_width > r.right - r.left)
483 guess_width = r.right - r.left;
484 if (guess_height > r.bottom - r.top)
485 guess_height = r.bottom - r.top;
489 int winmode = WS_OVERLAPPEDWINDOW | WS_VSCROLL;
492 winmode &= ~(WS_VSCROLL);
493 if (cfg.resize_action == RESIZE_DISABLED)
494 winmode &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
496 exwinmode |= WS_EX_TOPMOST;
498 exwinmode |= WS_EX_CLIENTEDGE;
499 hwnd = CreateWindowEx(exwinmode, appname, appname,
500 winmode, CW_USEDEFAULT, CW_USEDEFAULT,
501 guess_width, guess_height,
502 NULL, NULL, inst, NULL);
506 * Initialise the fonts, simultaneously correcting the guesses
507 * for font_{width,height}.
512 * Correct the guesses for extra_{width,height}.
516 GetWindowRect(hwnd, &wr);
517 GetClientRect(hwnd, &cr);
518 offset_width = offset_height = cfg.window_border;
519 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
520 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
524 * Resize the window, now we know what size we _really_ want it
527 guess_width = extra_width + font_width * cols;
528 guess_height = extra_height + font_height * rows;
529 SetWindowPos(hwnd, NULL, 0, 0, guess_width, guess_height,
530 SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
533 * Set up a caret bitmap, with no content.
537 int size = (font_width + 15) / 16 * 2 * font_height;
538 bits = smalloc(size);
539 memset(bits, 0, size);
540 caretbm = CreateBitmap(font_width, font_height, 1, 1, bits);
543 CreateCaret(hwnd, caretbm, font_width, font_height);
546 * Initialise the scroll bar.
551 si.cbSize = sizeof(si);
552 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
557 SetScrollInfo(hwnd, SB_VERT, &si, FALSE);
561 * Start up the telnet connection.
565 char msg[1024], *title;
568 error = back->init(cfg.host, cfg.port, &realhost, cfg.tcp_nodelay);
570 sprintf(msg, "Unable to open connection to\n"
571 "%.800s\n" "%s", cfg.host, error);
572 MessageBox(NULL, msg, "PuTTY Error", MB_ICONERROR | MB_OK);
575 window_name = icon_name = NULL;
577 title = cfg.wintitle;
579 sprintf(msg, "%s - PuTTY", realhost);
587 session_closed = FALSE;
590 * Prepare the mouse handler.
592 lastact = MA_NOTHING;
593 lastbtn = MBT_NOTHING;
594 dbltime = GetDoubleClickTime();
597 * Set up the session-control options on the system menu.
600 HMENU m = GetSystemMenu(hwnd, FALSE);
604 AppendMenu(m, MF_SEPARATOR, 0, 0);
605 if (cfg.protocol == PROT_TELNET) {
607 AppendMenu(p, MF_ENABLED, IDM_TEL_AYT, "Are You There");
608 AppendMenu(p, MF_ENABLED, IDM_TEL_BRK, "Break");
609 AppendMenu(p, MF_ENABLED, IDM_TEL_SYNCH, "Synch");
610 AppendMenu(p, MF_SEPARATOR, 0, 0);
611 AppendMenu(p, MF_ENABLED, IDM_TEL_EC, "Erase Character");
612 AppendMenu(p, MF_ENABLED, IDM_TEL_EL, "Erase Line");
613 AppendMenu(p, MF_ENABLED, IDM_TEL_GA, "Go Ahead");
614 AppendMenu(p, MF_ENABLED, IDM_TEL_NOP, "No Operation");
615 AppendMenu(p, MF_SEPARATOR, 0, 0);
616 AppendMenu(p, MF_ENABLED, IDM_TEL_ABORT, "Abort Process");
617 AppendMenu(p, MF_ENABLED, IDM_TEL_AO, "Abort Output");
618 AppendMenu(p, MF_ENABLED, IDM_TEL_IP, "Interrupt Process");
619 AppendMenu(p, MF_ENABLED, IDM_TEL_SUSP, "Suspend Process");
620 AppendMenu(p, MF_SEPARATOR, 0, 0);
621 AppendMenu(p, MF_ENABLED, IDM_TEL_EOR, "End Of Record");
622 AppendMenu(p, MF_ENABLED, IDM_TEL_EOF, "End Of File");
623 AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) p,
625 AppendMenu(m, MF_SEPARATOR, 0, 0);
627 AppendMenu(m, MF_ENABLED, IDM_SHOWLOG, "&Event Log");
628 AppendMenu(m, MF_SEPARATOR, 0, 0);
629 AppendMenu(m, MF_ENABLED, IDM_NEWSESS, "Ne&w Session...");
630 AppendMenu(m, MF_ENABLED, IDM_DUPSESS, "&Duplicate Session");
633 for (i = 1; i < ((nsessions < 256) ? nsessions : 256); i++)
634 AppendMenu(s, MF_ENABLED, IDM_SAVED_MIN + (16 * i),
636 AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) s, "Sa&ved Sessions");
637 AppendMenu(m, MF_ENABLED, IDM_RECONF, "Chan&ge Settings...");
638 AppendMenu(m, MF_SEPARATOR, 0, 0);
639 AppendMenu(m, MF_ENABLED, IDM_COPYALL, "C&opy All to Clipboard");
640 AppendMenu(m, MF_ENABLED, IDM_CLRSB, "C&lear Scrollback");
641 AppendMenu(m, MF_ENABLED, IDM_RESET, "Rese&t Terminal");
642 AppendMenu(m, MF_SEPARATOR, 0, 0);
643 AppendMenu(m, (cfg.resize_action == RESIZE_DISABLED) ?
644 MF_GRAYED : MF_ENABLED, IDM_FULLSCREEN, "&Full Screen");
645 AppendMenu(m, MF_SEPARATOR, 0, 0);
647 AppendMenu(m, MF_ENABLED, IDM_HELP, "&Help");
648 AppendMenu(m, MF_ENABLED, IDM_ABOUT, "&About PuTTY");
652 * Set up the initial input locale.
654 set_input_locale(GetKeyboardLayout(0));
657 * Open the initial log file if there is one.
662 * Finally show the window!
664 ShowWindow(hwnd, show);
665 SetForegroundWindow(hwnd);
668 * Set the palette up.
674 has_focus = (GetForegroundWindow() == hwnd);
677 if (GetMessage(&msg, NULL, 0, 0) == 1) {
678 int timer_id = 0, long_timer = 0;
680 while (msg.message != WM_QUIT) {
681 /* Sometimes DispatchMessage calls routines that use their own
682 * GetMessage loop, setup this timer so we get some control back.
684 * Also call term_update() from the timer so that if the host
685 * is sending data flat out we still do redraws.
687 if (timer_id && long_timer) {
688 KillTimer(hwnd, timer_id);
689 long_timer = timer_id = 0;
692 timer_id = SetTimer(hwnd, 1, 20, NULL);
693 if (!(IsWindow(logbox) && IsDialogMessage(logbox, &msg)))
694 DispatchMessage(&msg);
696 /* Make sure we blink everything that needs it. */
699 /* Send the paste buffer if there's anything to send */
702 /* If there's nothing new in the queue then we can do everything
703 * we've delayed, reading the socket, writing, and repainting
706 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
709 if (pending_netevent) {
710 enact_pending_netevent();
712 /* Force the cursor blink on */
715 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
719 /* Okay there is now nothing to do so we make sure the screen is
720 * completely up to date then tell windows to call us in a little
724 KillTimer(hwnd, timer_id);
728 if (GetCapture() != hwnd)
733 flash_window(1); /* maintain */
735 /* The messages seem unreliable; especially if we're being tricky */
736 has_focus = (GetForegroundWindow() == hwnd);
739 /* Hmm, term_update didn't want to do an update too soon ... */
740 timer_id = SetTimer(hwnd, 1, 50, NULL);
742 timer_id = SetTimer(hwnd, 1, 500, NULL);
744 timer_id = SetTimer(hwnd, 1, 100, NULL);
747 /* There's no point rescanning everything in the message queue
748 * so we do an apparently unnecessary wait here
751 if (GetMessage(&msg, NULL, 0, 0) != 1)
765 if (cfg.protocol == PROT_SSH) {
776 * Set up, or shut down, an AsyncSelect. Called from winnet.c.
778 char *do_select(SOCKET skt, int startup)
783 events = (FD_CONNECT | FD_READ | FD_WRITE |
784 FD_OOB | FD_CLOSE | FD_ACCEPT);
789 return "do_select(): internal error (hwnd==NULL)";
790 if (WSAAsyncSelect(skt, hwnd, msg, events) == SOCKET_ERROR) {
791 switch (WSAGetLastError()) {
793 return "Network is down";
795 return "WSAAsyncSelect(): unknown error";
802 * set or clear the "raw mouse message" mode
804 void set_raw_mouse_mode(int activate)
806 send_raw_mouse = activate;
807 SetCursor(LoadCursor(NULL, activate ? IDC_ARROW : IDC_IBEAM));
811 * Print a message box and close the connection.
813 void connection_fatal(char *fmt, ...)
819 vsprintf(stuff, fmt, ap);
821 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
822 if (cfg.close_on_exit == COE_ALWAYS)
825 session_closed = TRUE;
826 SetWindowText(hwnd, "PuTTY (inactive)");
831 * Actually do the job requested by a WM_NETEVENT
833 static void enact_pending_netevent(void)
835 static int reentering = 0;
836 extern int select_result(WPARAM, LPARAM);
840 return; /* don't unpend the pending */
842 pending_netevent = FALSE;
845 ret = select_result(pend_netevent_wParam, pend_netevent_lParam);
848 if (ret == 0 && !session_closed) {
849 /* Abnormal exits will already have set session_closed and taken
850 * appropriate action. */
851 if (cfg.close_on_exit == COE_ALWAYS ||
852 cfg.close_on_exit == COE_NORMAL) PostQuitMessage(0);
854 session_closed = TRUE;
855 SetWindowText(hwnd, "PuTTY (inactive)");
856 MessageBox(hwnd, "Connection closed by remote host",
857 "PuTTY", MB_OK | MB_ICONINFORMATION);
863 * Copy the colour palette from the configuration data into defpal.
864 * This is non-trivial because the colour indices are different.
866 static void cfgtopalette(void)
869 static const int ww[] = {
870 6, 7, 8, 9, 10, 11, 12, 13,
871 14, 15, 16, 17, 18, 19, 20, 21,
872 0, 1, 2, 3, 4, 4, 5, 5
875 for (i = 0; i < 24; i++) {
877 defpal[i].rgbtRed = cfg.colours[w][0];
878 defpal[i].rgbtGreen = cfg.colours[w][1];
879 defpal[i].rgbtBlue = cfg.colours[w][2];
884 * Set up the colour palette.
886 static void init_palette(void)
889 HDC hdc = GetDC(hwnd);
891 if (cfg.try_palette && GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) {
892 logpal = smalloc(sizeof(*logpal)
893 - sizeof(logpal->palPalEntry)
894 + NCOLOURS * sizeof(PALETTEENTRY));
895 logpal->palVersion = 0x300;
896 logpal->palNumEntries = NCOLOURS;
897 for (i = 0; i < NCOLOURS; i++) {
898 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
899 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
900 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
901 logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
903 pal = CreatePalette(logpal);
905 SelectPalette(hdc, pal, FALSE);
907 SelectPalette(hdc, GetStockObject(DEFAULT_PALETTE), FALSE);
910 ReleaseDC(hwnd, hdc);
913 for (i = 0; i < NCOLOURS; i++)
914 colours[i] = PALETTERGB(defpal[i].rgbtRed,
918 for (i = 0; i < NCOLOURS; i++)
919 colours[i] = RGB(defpal[i].rgbtRed,
920 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
924 * Initialise all the fonts we will need initially. There may be as many as
925 * three or as few as one. The other (poentially) twentyone fonts are done
926 * if/when they are needed.
930 * - check the font width and height, correcting our guesses if
933 * - verify that the bold font is the same width as the ordinary
934 * one, and engage shadow bolding if not.
936 * - verify that the underlined font is the same width as the
937 * ordinary one (manual underlining by means of line drawing can
938 * be done in a pinch).
940 static void init_fonts(int pick_width, int pick_height)
947 int fw_dontcare, fw_bold;
949 for (i = 0; i < FONT_MAXNO; i++)
952 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
955 if (cfg.fontisbold) {
956 fw_dontcare = FW_BOLD;
959 fw_dontcare = FW_DONTCARE;
966 font_height = pick_height;
968 font_height = cfg.fontheight;
969 if (font_height > 0) {
971 -MulDiv(font_height, GetDeviceCaps(hdc, LOGPIXELSY), 72);
974 font_width = pick_width;
977 fonts[i] = CreateFont (font_height, font_width, 0, 0, w, FALSE, u, FALSE, \
978 c, OUT_DEFAULT_PRECIS, \
979 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, \
980 FIXED_PITCH | FF_DONTCARE, cfg.font)
982 f(FONT_NORMAL, cfg.fontcharset, fw_dontcare, FALSE);
984 lfont.lfHeight = font_height;
985 lfont.lfWidth = font_width;
986 lfont.lfEscapement = 0;
987 lfont.lfOrientation = 0;
988 lfont.lfWeight = fw_dontcare;
989 lfont.lfItalic = FALSE;
990 lfont.lfUnderline = FALSE;
991 lfont.lfStrikeOut = FALSE;
992 lfont.lfCharSet = cfg.fontcharset;
993 lfont.lfOutPrecision = OUT_DEFAULT_PRECIS;
994 lfont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
995 lfont.lfQuality = DEFAULT_QUALITY;
996 lfont.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE;
997 strncpy(lfont.lfFaceName, cfg.font, LF_FACESIZE);
999 SelectObject(hdc, fonts[FONT_NORMAL]);
1000 GetTextMetrics(hdc, &tm);
1002 if (pick_width == 0 || pick_height == 0) {
1003 font_height = tm.tmHeight;
1004 font_width = tm.tmAveCharWidth;
1006 font_dualwidth = (tm.tmAveCharWidth != tm.tmMaxCharWidth);
1008 #ifdef RDB_DEBUG_PATCH
1009 debug(23, "Primary font H=%d, AW=%d, MW=%d",
1010 tm.tmHeight, tm.tmAveCharWidth, tm.tmMaxCharWidth);
1015 DWORD cset = tm.tmCharSet;
1016 memset(&info, 0xFF, sizeof(info));
1018 /* !!! Yes the next line is right */
1019 if (cset == OEM_CHARSET)
1020 font_codepage = GetOEMCP();
1022 if (TranslateCharsetInfo
1023 ((DWORD *) cset, &info, TCI_SRCCHARSET)) font_codepage =
1028 GetCPInfo(font_codepage, &cpinfo);
1029 dbcs_screenfont = (cpinfo.MaxCharSize > 1);
1032 f(FONT_UNDERLINE, cfg.fontcharset, fw_dontcare, TRUE);
1035 * Some fonts, e.g. 9-pt Courier, draw their underlines
1036 * outside their character cell. We successfully prevent
1037 * screen corruption by clipping the text output, but then
1038 * we lose the underline completely. Here we try to work
1039 * out whether this is such a font, and if it is, we set a
1040 * flag that causes underlines to be drawn by hand.
1042 * Having tried other more sophisticated approaches (such
1043 * as examining the TEXTMETRIC structure or requesting the
1044 * height of a string), I think we'll do this the brute
1045 * force way: we create a small bitmap, draw an underlined
1046 * space on it, and test to see whether any pixels are
1047 * foreground-coloured. (Since we expect the underline to
1048 * go all the way across the character cell, we only search
1049 * down a single column of the bitmap, half way across.)
1053 HBITMAP und_bm, und_oldbm;
1057 und_dc = CreateCompatibleDC(hdc);
1058 und_bm = CreateCompatibleBitmap(hdc, font_width, font_height);
1059 und_oldbm = SelectObject(und_dc, und_bm);
1060 SelectObject(und_dc, fonts[FONT_UNDERLINE]);
1061 SetTextAlign(und_dc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
1062 SetTextColor(und_dc, RGB(255, 255, 255));
1063 SetBkColor(und_dc, RGB(0, 0, 0));
1064 SetBkMode(und_dc, OPAQUE);
1065 ExtTextOut(und_dc, 0, 0, ETO_OPAQUE, NULL, " ", 1, NULL);
1067 for (i = 0; i < font_height; i++) {
1068 c = GetPixel(und_dc, font_width / 2, i);
1069 if (c != RGB(0, 0, 0))
1072 SelectObject(und_dc, und_oldbm);
1073 DeleteObject(und_bm);
1076 und_mode = UND_LINE;
1077 DeleteObject(fonts[FONT_UNDERLINE]);
1078 fonts[FONT_UNDERLINE] = 0;
1082 if (bold_mode == BOLD_FONT) {
1083 f(FONT_BOLD, cfg.fontcharset, fw_bold, FALSE);
1087 descent = tm.tmAscent + 1;
1088 if (descent >= font_height)
1089 descent = font_height - 1;
1091 for (i = 0; i < 3; i++) {
1093 if (SelectObject(hdc, fonts[i]) && GetTextMetrics(hdc, &tm))
1094 fontsize[i] = tm.tmAveCharWidth + 256 * tm.tmHeight;
1101 ReleaseDC(hwnd, hdc);
1103 if (fontsize[FONT_UNDERLINE] != fontsize[FONT_NORMAL]) {
1104 und_mode = UND_LINE;
1105 DeleteObject(fonts[FONT_UNDERLINE]);
1106 fonts[FONT_UNDERLINE] = 0;
1109 if (bold_mode == BOLD_FONT &&
1110 fontsize[FONT_BOLD] != fontsize[FONT_NORMAL]) {
1111 bold_mode = BOLD_SHADOW;
1112 DeleteObject(fonts[FONT_BOLD]);
1113 fonts[FONT_BOLD] = 0;
1115 fontflag[0] = fontflag[1] = fontflag[2] = 1;
1120 static void another_font(int fontno)
1123 int fw_dontcare, fw_bold;
1127 if (fontno < 0 || fontno >= FONT_MAXNO || fontflag[fontno])
1130 basefont = (fontno & ~(FONT_BOLDUND));
1131 if (basefont != fontno && !fontflag[basefont])
1132 another_font(basefont);
1134 if (cfg.fontisbold) {
1135 fw_dontcare = FW_BOLD;
1138 fw_dontcare = FW_DONTCARE;
1142 c = cfg.fontcharset;
1148 if (fontno & FONT_WIDE)
1150 if (fontno & FONT_NARROW)
1152 if (fontno & FONT_OEM)
1154 if (fontno & FONT_BOLD)
1156 if (fontno & FONT_UNDERLINE)
1160 CreateFont(font_height * (1 + !!(fontno & FONT_HIGH)), x, 0, 0, w,
1161 FALSE, u, FALSE, c, OUT_DEFAULT_PRECIS,
1162 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
1163 FIXED_PITCH | FF_DONTCARE, s);
1165 fontflag[fontno] = 1;
1168 static void deinit_fonts(void)
1171 for (i = 0; i < FONT_MAXNO; i++) {
1173 DeleteObject(fonts[i]);
1179 void request_resize(int w, int h)
1183 /* If the window is maximized supress resizing attempts */
1184 if (IsZoomed(hwnd)) {
1185 if (cfg.resize_action == RESIZE_TERM)
1189 if (cfg.resize_action == RESIZE_DISABLED) return;
1190 if (h == rows && w == cols) return;
1192 /* Sanity checks ... */
1194 static int first_time = 1;
1197 switch (first_time) {
1199 /* Get the size of the screen */
1200 if (GetClientRect(GetDesktopWindow(), &ss))
1201 /* first_time = 0 */ ;
1207 /* Make sure the values are sane */
1208 width = (ss.right - ss.left - extra_width) / 4;
1209 height = (ss.bottom - ss.top - extra_height) / 6;
1211 if (w > width || h > height)
1220 term_size(h, w, cfg.savelines);
1222 if (cfg.resize_action != RESIZE_FONT && !IsZoomed(hwnd)) {
1223 width = extra_width + font_width * w;
1224 height = extra_height + font_height * h;
1226 SetWindowPos(hwnd, NULL, 0, 0, width, height,
1227 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1228 SWP_NOMOVE | SWP_NOZORDER);
1232 InvalidateRect(hwnd, NULL, TRUE);
1235 static void reset_window(int reinit) {
1237 * This function decides how to resize or redraw when the
1238 * user changes something.
1240 * This function doesn't like to change the terminal size but if the
1241 * font size is locked that may be it's only soluion.
1243 int win_width, win_height;
1246 #ifdef RDB_DEBUG_PATCH
1247 debug((27, "reset_window()"));
1250 /* Current window sizes ... */
1251 GetWindowRect(hwnd, &wr);
1252 GetClientRect(hwnd, &cr);
1254 win_width = cr.right - cr.left;
1255 win_height = cr.bottom - cr.top;
1257 if (cfg.resize_action == RESIZE_DISABLED) reinit = 2;
1259 /* Are we being forced to reload the fonts ? */
1261 #ifdef RDB_DEBUG_PATCH
1262 debug((27, "reset_window() -- Forced deinit"));
1268 /* Oh, looks like we're minimised */
1269 if (win_width == 0 || win_height == 0)
1272 /* Is the window out of position ? */
1274 (offset_width != (win_width-font_width*cols)/2 ||
1275 offset_height != (win_height-font_height*rows)/2) ){
1276 offset_width = (win_width-font_width*cols)/2;
1277 offset_height = (win_height-font_height*rows)/2;
1278 InvalidateRect(hwnd, NULL, TRUE);
1279 #ifdef RDB_DEBUG_PATCH
1280 debug((27, "reset_window() -> Reposition terminal"));
1284 if (IsZoomed(hwnd)) {
1285 /* We're fullscreen, this means we must not change the size of
1286 * the window so it's the font size or the terminal itself.
1289 extra_width = wr.right - wr.left - cr.right + cr.left;
1290 extra_height = wr.bottom - wr.top - cr.bottom + cr.top;
1292 if (cfg.resize_action != RESIZE_TERM) {
1293 if ( font_width != win_width/cols ||
1294 font_height != win_height/rows) {
1296 init_fonts(win_width/cols, win_height/rows);
1297 offset_width = (win_width-font_width*cols)/2;
1298 offset_height = (win_height-font_height*rows)/2;
1299 InvalidateRect(hwnd, NULL, TRUE);
1300 #ifdef RDB_DEBUG_PATCH
1301 debug((25, "reset_window() -> Z font resize to (%d, %d)",
1302 font_width, font_height));
1306 if ( font_width != win_width/cols ||
1307 font_height != win_height/rows) {
1308 /* Our only choice at this point is to change the
1309 * size of the terminal; Oh well.
1311 term_size( win_height/font_height, win_width/font_width,
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((27, "reset_window() -> Zoomed term_size"));
1324 /* Hmm, a force re-init means we should ignore the current window
1325 * so we resize to the default font size.
1328 #ifdef RDB_DEBUG_PATCH
1329 debug((27, "reset_window() -> Forced re-init"));
1332 offset_width = offset_height = cfg.window_border;
1333 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
1334 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
1336 if (win_width != font_width*cols + offset_width*2 ||
1337 win_height != font_height*rows + offset_height*2) {
1339 /* If this is too large windows will resize it to the maximum
1340 * allowed window size, we will then be back in here and resize
1341 * the font or terminal to fit.
1343 SetWindowPos(hwnd, NULL, 0, 0,
1344 font_width*cols + extra_width,
1345 font_height*rows + extra_height,
1346 SWP_NOMOVE | SWP_NOZORDER);
1349 InvalidateRect(hwnd, NULL, TRUE);
1353 /* Okay the user doesn't want us to change the font so we try the
1354 * window. But that may be too big for the screen which forces us
1355 * to change the terminal.
1357 if ((cfg.resize_action == RESIZE_TERM && reinit<=0) ||
1358 (cfg.resize_action == RESIZE_EITHER && reinit<0) ||
1360 offset_width = offset_height = cfg.window_border;
1361 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
1362 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
1364 if (win_width != font_width*cols + offset_width*2 ||
1365 win_height != font_height*rows + offset_height*2) {
1370 GetClientRect(GetDesktopWindow(), &ss);
1371 width = (ss.right - ss.left - extra_width) / font_width;
1372 height = (ss.bottom - ss.top - extra_height) / font_height;
1375 if ( rows > height || cols > width ) {
1376 if (cfg.resize_action == RESIZE_EITHER) {
1377 /* Make the font the biggest we can */
1379 font_width = (ss.right - ss.left - extra_width)/cols;
1381 font_height = (ss.bottom - ss.top - extra_height)/rows;
1384 init_fonts(font_width, font_height);
1386 width = (ss.right - ss.left - extra_width) / font_width;
1387 height = (ss.bottom - ss.top - extra_height) / font_height;
1389 if ( height > rows ) height = rows;
1390 if ( width > cols ) width = cols;
1391 term_size(height, width, cfg.savelines);
1392 #ifdef RDB_DEBUG_PATCH
1393 debug((27, "reset_window() -> term resize to (%d,%d)",
1399 SetWindowPos(hwnd, NULL, 0, 0,
1400 font_width*cols + extra_width,
1401 font_height*rows + extra_height,
1402 SWP_NOMOVE | SWP_NOZORDER);
1404 InvalidateRect(hwnd, NULL, TRUE);
1405 #ifdef RDB_DEBUG_PATCH
1406 debug((27, "reset_window() -> window resize to (%d,%d)",
1407 font_width*cols + extra_width,
1408 font_height*rows + extra_height));
1414 /* We're allowed to or must change the font but do we want to ? */
1416 if (font_width != (win_width-cfg.window_border*2)/cols ||
1417 font_height != (win_height-cfg.window_border*2)/rows) {
1420 init_fonts((win_width-cfg.window_border*2)/cols,
1421 (win_height-cfg.window_border*2)/rows);
1422 offset_width = (win_width-font_width*cols)/2;
1423 offset_height = (win_height-font_height*rows)/2;
1425 extra_width = wr.right - wr.left - cr.right + cr.left +offset_width*2;
1426 extra_height = wr.bottom - wr.top - cr.bottom + cr.top+offset_height*2;
1428 InvalidateRect(hwnd, NULL, TRUE);
1429 #ifdef RDB_DEBUG_PATCH
1430 debug((25, "reset_window() -> font resize to (%d,%d)",
1431 font_width, font_height));
1436 static void set_input_locale(HKL kl)
1440 GetLocaleInfo(LOWORD(kl), LOCALE_IDEFAULTANSICODEPAGE,
1441 lbuf, sizeof(lbuf));
1443 kbd_codepage = atoi(lbuf);
1446 static void click(Mouse_Button b, int x, int y, int shift, int ctrl, int alt)
1448 int thistime = GetMessageTime();
1450 if (send_raw_mouse && !(cfg.mouse_override && shift)) {
1451 lastbtn = MBT_NOTHING;
1452 term_mouse(b, MA_CLICK, x, y, shift, ctrl, alt);
1456 if (lastbtn == b && thistime - lasttime < dbltime) {
1457 lastact = (lastact == MA_CLICK ? MA_2CLK :
1458 lastact == MA_2CLK ? MA_3CLK :
1459 lastact == MA_3CLK ? MA_CLICK : MA_NOTHING);
1464 if (lastact != MA_NOTHING)
1465 term_mouse(b, lastact, x, y, shift, ctrl, alt);
1466 lasttime = thistime;
1470 * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT)
1471 * into a cooked one (SELECT, EXTEND, PASTE).
1473 Mouse_Button translate_button(Mouse_Button button)
1475 if (button == MBT_LEFT)
1477 if (button == MBT_MIDDLE)
1478 return cfg.mouse_is_xterm ? MBT_PASTE : MBT_EXTEND;
1479 if (button == MBT_RIGHT)
1480 return cfg.mouse_is_xterm ? MBT_EXTEND : MBT_PASTE;
1481 return 0; /* shouldn't happen */
1484 static void show_mouseptr(int show)
1486 static int cursor_visible = 1;
1487 if (!cfg.hide_mouseptr) /* override if this feature disabled */
1489 if (cursor_visible && !show)
1491 else if (!cursor_visible && show)
1493 cursor_visible = show;
1496 static int is_alt_pressed(void)
1499 int r = GetKeyboardState(keystate);
1502 if (keystate[VK_MENU] & 0x80)
1504 if (keystate[VK_RMENU] & 0x80)
1509 static int resizing;
1511 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1512 WPARAM wParam, LPARAM lParam)
1515 static int ignore_clip = FALSE;
1516 static int need_backend_resize = FALSE;
1517 static int fullscr_on_max = FALSE;
1521 if (pending_netevent)
1522 enact_pending_netevent();
1523 if (GetCapture() != hwnd)
1529 if (cfg.ping_interval > 0) {
1532 if (now - last_movement > cfg.ping_interval) {
1533 back->special(TS_PING);
1534 last_movement = now;
1537 net_pending_errors();
1543 if (!cfg.warn_on_close || session_closed ||
1545 "Are you sure you want to close this session?",
1546 "PuTTY Exit Confirmation",
1547 MB_ICONWARNING | MB_OKCANCEL) == IDOK)
1548 DestroyWindow(hwnd);
1555 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1567 PROCESS_INFORMATION pi;
1568 HANDLE filemap = NULL;
1570 if (wParam == IDM_DUPSESS) {
1572 * Allocate a file-mapping memory chunk for the
1575 SECURITY_ATTRIBUTES sa;
1578 sa.nLength = sizeof(sa);
1579 sa.lpSecurityDescriptor = NULL;
1580 sa.bInheritHandle = TRUE;
1581 filemap = CreateFileMapping((HANDLE) 0xFFFFFFFF,
1584 0, sizeof(Config), NULL);
1586 p = (Config *) MapViewOfFile(filemap,
1588 0, 0, sizeof(Config));
1590 *p = cfg; /* structure copy */
1594 sprintf(c, "putty &%p", filemap);
1596 } else if (wParam == IDM_SAVEDSESS) {
1597 if ((lParam - IDM_SAVED_MIN) / 16 < nsessions) {
1599 sessions[(lParam - IDM_SAVED_MIN) / 16];
1600 cl = smalloc(16 + strlen(session));
1601 /* 8, but play safe */
1604 /* not a very important failure mode */
1606 sprintf(cl, "putty @%s", session);
1614 GetModuleFileName(NULL, b, sizeof(b) - 1);
1616 si.lpReserved = NULL;
1617 si.lpDesktop = NULL;
1621 si.lpReserved2 = NULL;
1622 CreateProcess(b, cl, NULL, NULL, TRUE,
1623 NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
1626 CloseHandle(filemap);
1636 GetWindowText(hwnd, cfg.wintitle, sizeof(cfg.wintitle));
1639 if (!do_reconfig(hwnd))
1643 /* Disable full-screen if resizing forbidden */
1644 HMENU m = GetSystemMenu (hwnd, FALSE);
1645 EnableMenuItem(m, IDM_FULLSCREEN, MF_BYCOMMAND |
1646 (cfg.resize_action == RESIZE_DISABLED)
1647 ? MF_GRAYED : MF_ENABLED);
1648 /* Gracefully unzoom if necessary */
1649 if (IsZoomed(hwnd) &&
1650 (cfg.resize_action == RESIZE_DISABLED)) {
1651 ShowWindow(hwnd, SW_RESTORE);
1655 if (strcmp(prev_cfg.logfilename, cfg.logfilename) ||
1656 prev_cfg.logtype != cfg.logtype) {
1657 logfclose(); /* reset logging */
1663 * Flush the line discipline's edit buffer in the
1664 * case where local editing has just been disabled.
1666 ldisc_send(NULL, 0, 0);
1674 /* Screen size changed ? */
1675 if (cfg.height != prev_cfg.height ||
1676 cfg.width != prev_cfg.width ||
1677 cfg.savelines != prev_cfg.savelines ||
1678 cfg.resize_action == RESIZE_FONT ||
1679 (cfg.resize_action == RESIZE_EITHER && IsZoomed(hwnd)) ||
1680 cfg.resize_action == RESIZE_DISABLED)
1681 term_size(cfg.height, cfg.width, cfg.savelines);
1683 /* Enable or disable the scroll bar, etc */
1685 LONG nflg, flag = GetWindowLong(hwnd, GWL_STYLE);
1686 LONG nexflag, exflag =
1687 GetWindowLong(hwnd, GWL_EXSTYLE);
1690 if (cfg.alwaysontop != prev_cfg.alwaysontop) {
1691 if (cfg.alwaysontop) {
1692 nexflag |= WS_EX_TOPMOST;
1693 SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
1694 SWP_NOMOVE | SWP_NOSIZE);
1696 nexflag &= ~(WS_EX_TOPMOST);
1697 SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0,
1698 SWP_NOMOVE | SWP_NOSIZE);
1701 if (cfg.sunken_edge)
1702 nexflag |= WS_EX_CLIENTEDGE;
1704 nexflag &= ~(WS_EX_CLIENTEDGE);
1707 if (is_full_screen() ?
1708 cfg.scrollbar_in_fullscreen : cfg.scrollbar)
1711 nflg &= ~WS_VSCROLL;
1713 if (cfg.resize_action == RESIZE_DISABLED ||
1715 nflg &= ~WS_THICKFRAME;
1717 nflg |= WS_THICKFRAME;
1719 if (cfg.resize_action == RESIZE_DISABLED)
1720 nflg &= ~WS_MAXIMIZEBOX;
1722 nflg |= WS_MAXIMIZEBOX;
1724 if (nflg != flag || nexflag != exflag) {
1726 SetWindowLong(hwnd, GWL_STYLE, nflg);
1727 if (nexflag != exflag)
1728 SetWindowLong(hwnd, GWL_EXSTYLE, nexflag);
1730 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
1731 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1732 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
1740 if (cfg.resize_action == RESIZE_DISABLED && IsZoomed(hwnd)) {
1745 set_title(cfg.wintitle);
1746 if (IsIconic(hwnd)) {
1748 cfg.win_name_always ? window_name :
1752 if (strcmp(cfg.font, prev_cfg.font) != 0 ||
1753 strcmp(cfg.line_codepage, prev_cfg.line_codepage) != 0 ||
1754 cfg.fontisbold != prev_cfg.fontisbold ||
1755 cfg.fontheight != prev_cfg.fontheight ||
1756 cfg.fontcharset != prev_cfg.fontcharset ||
1757 cfg.vtmode != prev_cfg.vtmode ||
1758 cfg.bold_colour != prev_cfg.bold_colour ||
1759 cfg.resize_action == RESIZE_DISABLED ||
1760 cfg.resize_action == RESIZE_EITHER ||
1761 (cfg.resize_action != prev_cfg.resize_action))
1764 InvalidateRect(hwnd, NULL, TRUE);
1765 reset_window(init_lvl);
1766 net_pending_errors();
1779 back->special(TS_AYT);
1780 net_pending_errors();
1783 back->special(TS_BRK);
1784 net_pending_errors();
1787 back->special(TS_SYNCH);
1788 net_pending_errors();
1791 back->special(TS_EC);
1792 net_pending_errors();
1795 back->special(TS_EL);
1796 net_pending_errors();
1799 back->special(TS_GA);
1800 net_pending_errors();
1803 back->special(TS_NOP);
1804 net_pending_errors();
1807 back->special(TS_ABORT);
1808 net_pending_errors();
1811 back->special(TS_AO);
1812 net_pending_errors();
1815 back->special(TS_IP);
1816 net_pending_errors();
1819 back->special(TS_SUSP);
1820 net_pending_errors();
1823 back->special(TS_EOR);
1824 net_pending_errors();
1827 back->special(TS_EOF);
1828 net_pending_errors();
1834 WinHelp(hwnd, help_path,
1835 help_has_contents ? HELP_FINDER : HELP_CONTENTS, 0);
1839 * We get this if the System menu has been activated
1846 * We get this if the System menu has been activated
1847 * using the keyboard. This might happen from within
1848 * TranslateKey, in which case it really wants to be
1849 * followed by a `space' character to actually _bring
1850 * the menu up_ rather than just sitting there in
1851 * `ready to appear' state.
1853 show_mouseptr(1); /* make sure pointer is visible */
1855 PostMessage(hwnd, WM_CHAR, ' ', 0);
1857 case IDM_FULLSCREEN:
1861 if (wParam >= IDM_SAVED_MIN && wParam <= IDM_SAVED_MAX) {
1862 SendMessage(hwnd, WM_SYSCOMMAND, IDM_SAVEDSESS, wParam);
1867 #define X_POS(l) ((int)(short)LOWORD(l))
1868 #define Y_POS(l) ((int)(short)HIWORD(l))
1870 #define TO_CHR_X(x) ((((x)<0 ? (x)-font_width+1 : (x))-offset_width) / font_width)
1871 #define TO_CHR_Y(y) ((((y)<0 ? (y)-font_height+1: (y))-offset_height) / font_height)
1872 case WM_LBUTTONDOWN:
1873 case WM_MBUTTONDOWN:
1874 case WM_RBUTTONDOWN:
1882 case WM_LBUTTONDOWN:
1886 case WM_MBUTTONDOWN:
1887 button = MBT_MIDDLE;
1890 case WM_RBUTTONDOWN:
1899 button = MBT_MIDDLE;
1907 button = press = 0; /* shouldn't happen */
1911 * Special case: in full-screen mode, if the left
1912 * button is clicked in the very top left corner of the
1913 * window, we put up the System menu instead of doing
1916 if (is_full_screen() && press && button == MBT_LEFT &&
1917 X_POS(lParam) == 0 && Y_POS(lParam) == 0) {
1918 SendMessage(hwnd, WM_SYSCOMMAND, SC_MOUSEMENU, 0);
1923 TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam)),
1924 wParam & MK_SHIFT, wParam & MK_CONTROL,
1928 term_mouse(button, MA_RELEASE,
1929 TO_CHR_X(X_POS(lParam)),
1930 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
1931 wParam & MK_CONTROL, is_alt_pressed());
1939 * Add the mouse position and message time to the random
1942 noise_ultralight(lParam);
1944 if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON) &&
1945 GetCapture() == hwnd) {
1947 if (wParam & MK_LBUTTON)
1949 else if (wParam & MK_MBUTTON)
1953 term_mouse(b, MA_DRAG, TO_CHR_X(X_POS(lParam)),
1954 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
1955 wParam & MK_CONTROL, is_alt_pressed());
1958 case WM_NCMOUSEMOVE:
1960 noise_ultralight(lParam);
1962 case WM_IGNORE_CLIP:
1963 ignore_clip = wParam; /* don't panic on DESTROYCLIPBOARD */
1965 case WM_DESTROYCLIPBOARD:
1968 ignore_clip = FALSE;
1974 hdc = BeginPaint(hwnd, &p);
1976 SelectPalette(hdc, pal, TRUE);
1977 RealizePalette(hdc);
1980 (p.rcPaint.left-offset_width)/font_width,
1981 (p.rcPaint.top-offset_height)/font_height,
1982 (p.rcPaint.right-offset_width-1)/font_width,
1983 (p.rcPaint.bottom-offset_height-1)/font_height);
1986 p.rcPaint.left < offset_width ||
1987 p.rcPaint.top < offset_height ||
1988 p.rcPaint.right >= offset_width + font_width*cols ||
1989 p.rcPaint.bottom>= offset_height + font_height*rows)
1991 HBRUSH fillcolour, oldbrush;
1993 fillcolour = CreateSolidBrush (
1994 colours[(ATTR_DEFBG>>ATTR_BGSHIFT)*2]);
1995 oldbrush = SelectObject(hdc, fillcolour);
1996 edge = CreatePen(PS_SOLID, 0,
1997 colours[(ATTR_DEFBG>>ATTR_BGSHIFT)*2]);
1998 oldpen = SelectObject(hdc, edge);
2000 ExcludeClipRect(hdc,
2001 offset_width, offset_height,
2002 offset_width+font_width*cols,
2003 offset_height+font_height*rows);
2005 Rectangle(hdc, p.rcPaint.left, p.rcPaint.top,
2006 p.rcPaint.right, p.rcPaint.bottom);
2008 // SelectClipRgn(hdc, NULL);
2010 SelectObject(hdc, oldbrush);
2011 DeleteObject(fillcolour);
2012 SelectObject(hdc, oldpen);
2015 SelectObject(hdc, GetStockObject(SYSTEM_FONT));
2016 SelectObject(hdc, GetStockObject(WHITE_PEN));
2022 /* Notice we can get multiple netevents, FD_READ, FD_WRITE etc
2023 * but the only one that's likely to try to overload us is FD_READ.
2024 * This means buffering just one is fine.
2026 if (pending_netevent)
2027 enact_pending_netevent();
2029 pending_netevent = TRUE;
2030 pend_netevent_wParam = wParam;
2031 pend_netevent_lParam = lParam;
2032 if (WSAGETSELECTEVENT(lParam) != FD_READ)
2033 enact_pending_netevent();
2035 time(&last_movement);
2039 CreateCaret(hwnd, caretbm, font_width, font_height);
2041 flash_window(0); /* stop */
2050 caret_x = caret_y = -1; /* ensure caret is replaced next time */
2054 case WM_ENTERSIZEMOVE:
2055 #ifdef RDB_DEBUG_PATCH
2056 debug((27, "WM_ENTERSIZEMOVE"));
2060 need_backend_resize = FALSE;
2062 case WM_EXITSIZEMOVE:
2065 #ifdef RDB_DEBUG_PATCH
2066 debug((27, "WM_EXITSIZEMOVE"));
2068 if (need_backend_resize) {
2069 term_size(cfg.height, cfg.width, cfg.savelines);
2070 InvalidateRect(hwnd, NULL, TRUE);
2075 * This does two jobs:
2076 * 1) Keep the sizetip uptodate
2077 * 2) Make sure the window size is _stepped_ in units of the font size.
2079 if (cfg.resize_action != RESIZE_FONT && !alt_pressed) {
2080 int width, height, w, h, ew, eh;
2081 LPRECT r = (LPRECT) lParam;
2083 if ( !need_backend_resize && cfg.resize_action == RESIZE_EITHER &&
2084 (cfg.height != rows || cfg.width != cols )) {
2086 * Great! It seems that both the terminal size and the
2087 * font size have been changed and the user is now dragging.
2089 * It will now be difficult to get back to the configured
2092 * This would be easier but it seems to be too confusing.
2094 term_size(cfg.height, cfg.width, cfg.savelines);
2097 cfg.height=rows; cfg.width=cols;
2099 InvalidateRect(hwnd, NULL, TRUE);
2100 need_backend_resize = TRUE;
2103 width = r->right - r->left - extra_width;
2104 height = r->bottom - r->top - extra_height;
2105 w = (width + font_width / 2) / font_width;
2108 h = (height + font_height / 2) / font_height;
2111 UpdateSizeTip(hwnd, w, h);
2112 ew = width - w * font_width;
2113 eh = height - h * font_height;
2115 if (wParam == WMSZ_LEFT ||
2116 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
2122 if (wParam == WMSZ_TOP ||
2123 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
2133 int width, height, w, h, rv = 0;
2134 int ex_width = extra_width + (cfg.window_border - offset_width) * 2;
2135 int ex_height = extra_height + (cfg.window_border - offset_height) * 2;
2136 LPRECT r = (LPRECT) lParam;
2138 width = r->right - r->left - ex_width;
2139 height = r->bottom - r->top - ex_height;
2141 w = (width + cols/2)/cols;
2142 h = (height + rows/2)/rows;
2143 if ( r->right != r->left + w*cols + ex_width)
2146 if (wParam == WMSZ_LEFT ||
2147 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
2148 r->left = r->right - w*cols - ex_width;
2150 r->right = r->left + w*cols + ex_width;
2152 if (r->bottom != r->top + h*rows + ex_height)
2155 if (wParam == WMSZ_TOP ||
2156 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
2157 r->top = r->bottom - h*rows - ex_height;
2159 r->bottom = r->top + h*rows + ex_height;
2163 /* break; (never reached) */
2164 case WM_FULLSCR_ON_MAX:
2165 fullscr_on_max = TRUE;
2168 sys_cursor_update();
2171 #ifdef RDB_DEBUG_PATCH
2172 debug((27, "WM_SIZE %s (%d,%d)",
2173 (wParam == SIZE_MINIMIZED) ? "SIZE_MINIMIZED":
2174 (wParam == SIZE_MAXIMIZED) ? "SIZE_MAXIMIZED":
2175 (wParam == SIZE_RESTORED && resizing) ? "to":
2176 (wParam == SIZE_RESTORED) ? "SIZE_RESTORED":
2178 LOWORD(lParam), HIWORD(lParam)));
2180 if (wParam == SIZE_MINIMIZED)
2182 cfg.win_name_always ? window_name : icon_name);
2183 if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
2184 SetWindowText(hwnd, window_name);
2185 if (wParam == SIZE_RESTORED)
2186 clear_full_screen();
2187 if (wParam == SIZE_MAXIMIZED && fullscr_on_max) {
2189 fullscr_on_max = FALSE;
2192 if (cfg.resize_action == RESIZE_DISABLED) {
2193 /* A resize, well it better be a minimize. */
2197 int width, height, w, h;
2199 width = LOWORD(lParam);
2200 height = HIWORD(lParam);
2203 if (wParam == SIZE_MAXIMIZED && !was_zoomed) {
2207 if (cfg.resize_action == RESIZE_TERM) {
2208 w = width / font_width;
2210 h = height / font_height;
2213 term_size(h, w, cfg.savelines);
2216 } else if (wParam == SIZE_RESTORED && was_zoomed) {
2218 if (cfg.resize_action == RESIZE_TERM)
2219 term_size(prev_rows, prev_cols, cfg.savelines);
2220 if (cfg.resize_action != RESIZE_FONT)
2225 /* This is an unexpected resize, these will normally happen
2226 * if the window is too large. Probably either the user
2227 * selected a huge font or the screen size has changed.
2229 * This is also called with minimize.
2231 else reset_window(-1);
2235 * Don't call back->size in mid-resize. (To prevent
2236 * massive numbers of resize events getting sent
2237 * down the connection during an NT opaque drag.)
2240 if (cfg.resize_action != RESIZE_FONT && !alt_pressed) {
2241 need_backend_resize = TRUE;
2242 w = (width-cfg.window_border*2) / font_width;
2244 h = (height-cfg.window_border*2) / font_height;
2253 sys_cursor_update();
2256 switch (LOWORD(wParam)) {
2270 term_scroll(0, +rows / 2);
2273 term_scroll(0, -rows / 2);
2275 case SB_THUMBPOSITION:
2277 term_scroll(1, HIWORD(wParam));
2281 case WM_PALETTECHANGED:
2282 if ((HWND) wParam != hwnd && pal != NULL) {
2283 HDC hdc = get_ctx();
2285 if (RealizePalette(hdc) > 0)
2291 case WM_QUERYNEWPALETTE:
2293 HDC hdc = get_ctx();
2295 if (RealizePalette(hdc) > 0)
2307 * Add the scan code and keypress timing to the random
2310 noise_ultralight(lParam);
2313 * We don't do TranslateMessage since it disassociates the
2314 * resulting CHAR message from the KEYDOWN that sparked it,
2315 * which we occasionally don't want. Instead, we process
2316 * KEYDOWN, and call the Win32 translator functions so that
2317 * we get the translations under _our_ control.
2320 unsigned char buf[20];
2323 if (wParam == VK_PROCESSKEY) {
2326 m.message = WM_KEYDOWN;
2328 m.lParam = lParam & 0xdfff;
2329 TranslateMessage(&m);
2331 len = TranslateKey(message, wParam, lParam, buf);
2333 return DefWindowProc(hwnd, message, wParam, lParam);
2337 * Interrupt an ongoing paste. I'm not sure
2338 * this is sensible, but for the moment it's
2339 * preferable to having to faff about buffering
2345 * We need not bother about stdin backlogs
2346 * here, because in GUI PuTTY we can't do
2347 * anything about it anyway; there's no means
2348 * of asking Windows to hold off on KEYDOWN
2349 * messages. We _have_ to buffer everything
2352 ldisc_send(buf, len, 1);
2357 net_pending_errors();
2359 case WM_INPUTLANGCHANGE:
2360 /* wParam == Font number */
2361 /* lParam == Locale */
2362 set_input_locale((HKL)lParam);
2363 sys_cursor_update();
2366 if(wParam == IMN_SETOPENSTATUS) {
2367 HIMC hImc = ImmGetContext(hwnd);
2368 ImmSetCompositionFont(hImc, &lfont);
2369 ImmReleaseContext(hwnd, hImc);
2373 case WM_IME_COMPOSITION:
2379 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ||
2380 osVersion.dwPlatformId == VER_PLATFORM_WIN32s) break; /* no Unicode */
2382 if ((lParam & GCS_RESULTSTR) == 0) /* Composition unfinished. */
2383 break; /* fall back to DefWindowProc */
2385 hIMC = ImmGetContext(hwnd);
2386 n = ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, NULL, 0);
2390 buff = (char*) smalloc(n);
2391 ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, buff, n);
2393 * Jaeyoun Chung reports that Korean character
2394 * input doesn't work correctly if we do a single
2395 * luni_send() covering the whole of buff. So
2396 * instead we luni_send the characters one by one.
2398 for (i = 0; i < n; i += 2)
2399 luni_send((unsigned short *)(buff+i), 1, 1);
2402 ImmReleaseContext(hwnd, hIMC);
2407 if (wParam & 0xFF00) {
2408 unsigned char buf[2];
2411 buf[0] = wParam >> 8;
2412 lpage_send(kbd_codepage, buf, 2, 1);
2414 char c = (unsigned char) wParam;
2415 lpage_send(kbd_codepage, &c, 1, 1);
2421 * Nevertheless, we are prepared to deal with WM_CHAR
2422 * messages, should they crop up. So if someone wants to
2423 * post the things to us as part of a macro manoeuvre,
2424 * we're ready to cope.
2427 char c = (unsigned char)wParam;
2428 lpage_send(CP_ACP, &c, 1, 1);
2432 if (send_raw_mouse && LOWORD(lParam) == HTCLIENT) {
2433 SetCursor(LoadCursor(NULL, IDC_ARROW));
2437 if (message == wm_mousewheel) {
2438 int shift_pressed=0, control_pressed=0, alt_pressed=0;
2440 if (message == WM_MOUSEWHEEL) {
2441 wheel_accumulator += (short)HIWORD(wParam);
2442 shift_pressed=LOWORD(wParam) & MK_SHIFT;
2443 control_pressed=LOWORD(wParam) & MK_CONTROL;
2446 wheel_accumulator += (int)wParam;
2447 if (GetKeyboardState(keys)!=0) {
2448 shift_pressed=keys[VK_SHIFT]&0x80;
2449 control_pressed=keys[VK_CONTROL]&0x80;
2453 /* process events when the threshold is reached */
2454 while (abs(wheel_accumulator) >= WHEEL_DELTA) {
2457 /* reduce amount for next time */
2458 if (wheel_accumulator > 0) {
2460 wheel_accumulator -= WHEEL_DELTA;
2461 } else if (wheel_accumulator < 0) {
2463 wheel_accumulator += WHEEL_DELTA;
2467 if (send_raw_mouse &&
2468 !(cfg.mouse_override && shift_pressed)) {
2469 /* send a mouse-down followed by a mouse up */
2472 TO_CHR_X(X_POS(lParam)),
2473 TO_CHR_Y(Y_POS(lParam)), shift_pressed,
2474 control_pressed, is_alt_pressed());
2475 term_mouse(b, MA_RELEASE, TO_CHR_X(X_POS(lParam)),
2476 TO_CHR_Y(Y_POS(lParam)), shift_pressed,
2477 control_pressed, is_alt_pressed());
2479 /* trigger a scroll */
2481 b == MBT_WHEEL_UP ? -rows / 2 : rows / 2);
2488 return DefWindowProc(hwnd, message, wParam, lParam);
2492 * Move the system caret. (We maintain one, even though it's
2493 * invisible, for the benefit of blind people: apparently some
2494 * helper software tracks the system caret, so we should arrange to
2497 void sys_cursor(int x, int y)
2501 if (!has_focus) return;
2504 * Avoid gratuitously re-updating the cursor position and IMM
2505 * window if there's no actual change required.
2507 cx = x * font_width + offset_width;
2508 cy = y * font_height + offset_height;
2509 if (cx == caret_x && cy == caret_y)
2514 sys_cursor_update();
2517 static void sys_cursor_update(void)
2522 if (!has_focus) return;
2524 if (caret_x < 0 || caret_y < 0)
2527 SetCaretPos(caret_x, caret_y);
2529 /* IMM calls on Win98 and beyond only */
2530 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32s) return; /* 3.11 */
2532 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS &&
2533 osVersion.dwMinorVersion == 0) return; /* 95 */
2535 /* we should have the IMM functions */
2536 hIMC = ImmGetContext(hwnd);
2537 cf.dwStyle = CFS_POINT;
2538 cf.ptCurrentPos.x = caret_x;
2539 cf.ptCurrentPos.y = caret_y;
2540 ImmSetCompositionWindow(hIMC, &cf);
2542 ImmReleaseContext(hwnd, hIMC);
2546 * Draw a line of text in the window, at given character
2547 * coordinates, in given attributes.
2549 * We are allowed to fiddle with the contents of `text'.
2551 void do_text(Context ctx, int x, int y, char *text, int len,
2552 unsigned long attr, int lattr)
2555 int nfg, nbg, nfont;
2558 int force_manual_underline = 0;
2559 int fnt_width = font_width * (1 + (lattr != LATTR_NORM));
2560 int char_width = fnt_width;
2561 int text_adjust = 0;
2562 static int *IpDx = 0, IpDxLEN = 0;
2564 if (attr & ATTR_WIDE)
2567 if (len > IpDxLEN || IpDx[0] != char_width) {
2569 if (len > IpDxLEN) {
2571 IpDx = smalloc((len + 16) * sizeof(int));
2572 IpDxLEN = (len + 16);
2574 for (i = 0; i < IpDxLEN; i++)
2575 IpDx[i] = char_width;
2578 /* Only want the left half of double width lines */
2579 if (lattr != LATTR_NORM && x*2 >= cols)
2587 if ((attr & TATTR_ACTCURS) && (cfg.cursor_type == 0 || big_cursor)) {
2588 attr &= ATTR_CUR_AND | (bold_mode != BOLD_COLOURS ? ATTR_BOLD : 0);
2589 attr ^= ATTR_CUR_XOR;
2593 if (cfg.vtmode == VT_POORMAN && lattr != LATTR_NORM) {
2594 /* Assume a poorman font is borken in other ways too. */
2604 nfont |= FONT_WIDE + FONT_HIGH;
2607 if (attr & ATTR_NARROW)
2608 nfont |= FONT_NARROW;
2610 /* Special hack for the VT100 linedraw glyphs. */
2611 if ((attr & CSET_MASK) == 0x2300) {
2612 if (text[0] >= (char) 0xBA && text[0] <= (char) 0xBD) {
2613 switch ((unsigned char) (text[0])) {
2615 text_adjust = -2 * font_height / 5;
2618 text_adjust = -1 * font_height / 5;
2621 text_adjust = font_height / 5;
2624 text_adjust = 2 * font_height / 5;
2627 if (lattr == LATTR_TOP || lattr == LATTR_BOT)
2630 text[0] = (char) (unitab_xterm['q'] & CHAR_MASK);
2631 attr |= (unitab_xterm['q'] & CSET_MASK);
2632 if (attr & ATTR_UNDER) {
2633 attr &= ~ATTR_UNDER;
2634 force_manual_underline = 1;
2639 /* Anything left as an original character set is unprintable. */
2640 if (DIRECT_CHAR(attr)) {
2643 memset(text, 0xFD, len);
2647 if ((attr & CSET_MASK) == ATTR_OEMCP)
2650 nfg = 2 * ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
2651 nbg = 2 * ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
2652 if (bold_mode == BOLD_FONT && (attr & ATTR_BOLD))
2654 if (und_mode == UND_FONT && (attr & ATTR_UNDER))
2655 nfont |= FONT_UNDERLINE;
2656 another_font(nfont);
2657 if (!fonts[nfont]) {
2658 if (nfont & FONT_UNDERLINE)
2659 force_manual_underline = 1;
2660 /* Don't do the same for manual bold, it could be bad news. */
2662 nfont &= ~(FONT_BOLD | FONT_UNDERLINE);
2664 another_font(nfont);
2666 nfont = FONT_NORMAL;
2667 if (attr & ATTR_REVERSE) {
2672 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BOLD))
2674 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BLINK))
2678 SelectObject(hdc, fonts[nfont]);
2679 SetTextColor(hdc, fg);
2680 SetBkColor(hdc, bg);
2681 SetBkMode(hdc, OPAQUE);
2684 line_box.right = x + char_width * len;
2685 line_box.bottom = y + font_height;
2687 /* Only want the left half of double width lines */
2688 if (line_box.right > font_width*cols+offset_width)
2689 line_box.right = font_width*cols+offset_width;
2691 /* We're using a private area for direct to font. (512 chars.) */
2692 if (dbcs_screenfont && (attr & CSET_MASK) == ATTR_ACP) {
2693 /* Ho Hum, dbcs fonts are a PITA! */
2694 /* To display on W9x I have to convert to UCS */
2695 static wchar_t *uni_buf = 0;
2696 static int uni_len = 0;
2698 if (len > uni_len) {
2700 uni_buf = smalloc((uni_len = len) * sizeof(wchar_t));
2703 for(nlen = mptr = 0; mptr<len; mptr++) {
2704 uni_buf[nlen] = 0xFFFD;
2705 if (IsDBCSLeadByteEx(font_codepage, (BYTE) text[mptr])) {
2706 IpDx[nlen] += char_width;
2707 MultiByteToWideChar(font_codepage, MB_USEGLYPHCHARS,
2708 text+mptr, 2, uni_buf+nlen, 1);
2713 MultiByteToWideChar(font_codepage, MB_USEGLYPHCHARS,
2714 text+mptr, 1, uni_buf+nlen, 1);
2722 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2723 ETO_CLIPPED | ETO_OPAQUE, &line_box, uni_buf, nlen, IpDx);
2724 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2725 SetBkMode(hdc, TRANSPARENT);
2726 ExtTextOutW(hdc, x - 1,
2727 y - font_height * (lattr ==
2728 LATTR_BOT) + text_adjust,
2729 ETO_CLIPPED, &line_box, uni_buf, nlen, IpDx);
2733 } else if (DIRECT_FONT(attr)) {
2735 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2736 ETO_CLIPPED | ETO_OPAQUE, &line_box, text, len, IpDx);
2737 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2738 SetBkMode(hdc, TRANSPARENT);
2740 /* GRR: This draws the character outside it's box and can leave
2741 * 'droppings' even with the clip box! I suppose I could loop it
2742 * one character at a time ... yuk.
2744 * Or ... I could do a test print with "W", and use +1 or -1 for this
2745 * shift depending on if the leftmost column is blank...
2747 ExtTextOut(hdc, x - 1,
2748 y - font_height * (lattr ==
2749 LATTR_BOT) + text_adjust,
2750 ETO_CLIPPED, &line_box, text, len, IpDx);
2753 /* And 'normal' unicode characters */
2754 static WCHAR *wbuf = NULL;
2755 static int wlen = 0;
2760 wbuf = smalloc(wlen * sizeof(WCHAR));
2762 for (i = 0; i < len; i++)
2763 wbuf[i] = (WCHAR) ((attr & CSET_MASK) + (text[i] & CHAR_MASK));
2766 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2767 ETO_CLIPPED | ETO_OPAQUE, &line_box, wbuf, len, IpDx);
2769 /* And the shadow bold hack. */
2770 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2771 SetBkMode(hdc, TRANSPARENT);
2772 ExtTextOutW(hdc, x - 1,
2773 y - font_height * (lattr ==
2774 LATTR_BOT) + text_adjust,
2775 ETO_CLIPPED, &line_box, wbuf, len, IpDx);
2778 if (lattr != LATTR_TOP && (force_manual_underline ||
2779 (und_mode == UND_LINE
2780 && (attr & ATTR_UNDER)))) {
2783 if (lattr == LATTR_BOT)
2784 dec = dec * 2 - font_height;
2786 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, fg));
2787 MoveToEx(hdc, x, y + dec, NULL);
2788 LineTo(hdc, x + len * char_width, y + dec);
2789 oldpen = SelectObject(hdc, oldpen);
2790 DeleteObject(oldpen);
2794 void do_cursor(Context ctx, int x, int y, char *text, int len,
2795 unsigned long attr, int lattr)
2801 int ctype = cfg.cursor_type;
2803 if ((attr & TATTR_ACTCURS) && (ctype == 0 || big_cursor)) {
2804 if (((attr & CSET_MASK) | (unsigned char) *text) != UCSWIDE) {
2805 do_text(ctx, x, y, text, len, attr, lattr);
2809 attr |= TATTR_RIGHTCURS;
2812 fnt_width = char_width = font_width * (1 + (lattr != LATTR_NORM));
2813 if (attr & ATTR_WIDE)
2820 if ((attr & TATTR_PASCURS) && (ctype == 0 || big_cursor)) {
2823 pts[0].x = pts[1].x = pts[4].x = x;
2824 pts[2].x = pts[3].x = x + char_width - 1;
2825 pts[0].y = pts[3].y = pts[4].y = y;
2826 pts[1].y = pts[2].y = y + font_height - 1;
2827 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2828 Polyline(hdc, pts, 5);
2829 oldpen = SelectObject(hdc, oldpen);
2830 DeleteObject(oldpen);
2831 } else if ((attr & (TATTR_ACTCURS | TATTR_PASCURS)) && ctype != 0) {
2832 int startx, starty, dx, dy, length, i;
2835 starty = y + descent;
2838 length = char_width;
2841 if (attr & TATTR_RIGHTCURS)
2842 xadjust = char_width - 1;
2843 startx = x + xadjust;
2847 length = font_height;
2849 if (attr & TATTR_ACTCURS) {
2852 SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2853 MoveToEx(hdc, startx, starty, NULL);
2854 LineTo(hdc, startx + dx * length, starty + dy * length);
2855 oldpen = SelectObject(hdc, oldpen);
2856 DeleteObject(oldpen);
2858 for (i = 0; i < length; i++) {
2860 SetPixel(hdc, startx, starty, colours[23]);
2869 /* This function gets the actual width of a character in the normal font.
2871 int CharWidth(Context ctx, int uc) {
2875 /* If the font max is the same as the font ave width then this
2876 * function is a no-op.
2878 if (!font_dualwidth) return 1;
2880 switch (uc & CSET_MASK) {
2882 uc = unitab_line[uc & 0xFF];
2885 uc = unitab_xterm[uc & 0xFF];
2888 uc = unitab_scoacs[uc & 0xFF];
2891 if (DIRECT_FONT(uc)) {
2892 if (dbcs_screenfont) return 1;
2894 /* Speedup, I know of no font where ascii is the wrong width */
2895 if ((uc&CHAR_MASK) >= ' ' && (uc&CHAR_MASK)<= '~')
2898 if ( (uc & CSET_MASK) == ATTR_ACP ) {
2899 SelectObject(hdc, fonts[FONT_NORMAL]);
2900 } else if ( (uc & CSET_MASK) == ATTR_OEMCP ) {
2901 another_font(FONT_OEM);
2902 if (!fonts[FONT_OEM]) return 0;
2904 SelectObject(hdc, fonts[FONT_OEM]);
2908 if ( GetCharWidth32(hdc, uc&CHAR_MASK, uc&CHAR_MASK, &ibuf) != 1 &&
2909 GetCharWidth(hdc, uc&CHAR_MASK, uc&CHAR_MASK, &ibuf) != 1)
2912 /* Speedup, I know of no font where ascii is the wrong width */
2913 if (uc >= ' ' && uc <= '~') return 1;
2915 SelectObject(hdc, fonts[FONT_NORMAL]);
2916 if ( GetCharWidth32W(hdc, uc, uc, &ibuf) == 1 )
2917 /* Okay that one worked */ ;
2918 else if ( GetCharWidthW(hdc, uc, uc, &ibuf) == 1 )
2919 /* This should work on 9x too, but it's "less accurate" */ ;
2924 ibuf += font_width / 2 -1;
2931 * Translate a WM_(SYS)?KEY(UP|DOWN) message into a string of ASCII
2932 * codes. Returns number of bytes used or zero to drop the message
2933 * or -1 to forward the message to windows.
2935 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
2936 unsigned char *output)
2939 int scan, left_alt = 0, key_down, shift_state;
2941 unsigned char *p = output;
2942 static int alt_sum = 0;
2944 HKL kbd_layout = GetKeyboardLayout(0);
2946 static WORD keys[3];
2947 static int compose_char = 0;
2948 static WPARAM compose_key = 0;
2950 r = GetKeyboardState(keystate);
2952 memset(keystate, 0, sizeof(keystate));
2955 #define SHOW_TOASCII_RESULT
2956 { /* Tell us all about key events */
2957 static BYTE oldstate[256];
2958 static int first = 1;
2962 memcpy(oldstate, keystate, sizeof(oldstate));
2965 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT) {
2967 } else if ((HIWORD(lParam) & KF_UP)
2968 && scan == (HIWORD(lParam) & 0xFF)) {
2972 if (wParam >= VK_F1 && wParam <= VK_F20)
2973 debug(("K_F%d", wParam + 1 - VK_F1));
2986 debug(("VK_%02x", wParam));
2988 if (message == WM_SYSKEYDOWN || message == WM_SYSKEYUP)
2990 debug((", S%02x", scan = (HIWORD(lParam) & 0xFF)));
2992 ch = MapVirtualKeyEx(wParam, 2, kbd_layout);
2993 if (ch >= ' ' && ch <= '~')
2994 debug((", '%c'", ch));
2996 debug((", $%02x", ch));
2999 debug((", KB0=%02x", keys[0]));
3001 debug((", KB1=%02x", keys[1]));
3003 debug((", KB2=%02x", keys[2]));
3005 if ((keystate[VK_SHIFT] & 0x80) != 0)
3007 if ((keystate[VK_CONTROL] & 0x80) != 0)
3009 if ((HIWORD(lParam) & KF_EXTENDED))
3011 if ((HIWORD(lParam) & KF_UP))
3015 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT);
3016 else if ((HIWORD(lParam) & KF_UP))
3017 oldstate[wParam & 0xFF] ^= 0x80;
3019 oldstate[wParam & 0xFF] ^= 0x81;
3021 for (ch = 0; ch < 256; ch++)
3022 if (oldstate[ch] != keystate[ch])
3023 debug((", M%02x=%02x", ch, keystate[ch]));
3025 memcpy(oldstate, keystate, sizeof(oldstate));
3029 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED)) {
3030 keystate[VK_RMENU] = keystate[VK_MENU];
3034 /* Nastyness with NUMLock - Shift-NUMLock is left alone though */
3035 if ((cfg.funky_type == 3 ||
3036 (cfg.funky_type <= 1 && app_keypad_keys && !cfg.no_applic_k))
3037 && wParam == VK_NUMLOCK && !(keystate[VK_SHIFT] & 0x80)) {
3039 wParam = VK_EXECUTE;
3041 /* UnToggle NUMLock */
3042 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0)
3043 keystate[VK_NUMLOCK] ^= 1;
3046 /* And write back the 'adjusted' state */
3047 SetKeyboardState(keystate);
3050 /* Disable Auto repeat if required */
3051 if (repeat_off && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT)
3054 if ((HIWORD(lParam) & KF_ALTDOWN) && (keystate[VK_RMENU] & 0x80) == 0)
3057 key_down = ((HIWORD(lParam) & KF_UP) == 0);
3059 /* Make sure Ctrl-ALT is not the same as AltGr for ToAscii unless told. */
3060 if (left_alt && (keystate[VK_CONTROL] & 0x80)) {
3061 if (cfg.ctrlaltkeys)
3062 keystate[VK_MENU] = 0;
3064 keystate[VK_RMENU] = 0x80;
3069 alt_pressed = (left_alt && key_down);
3071 scan = (HIWORD(lParam) & (KF_UP | KF_EXTENDED | 0xFF));
3072 shift_state = ((keystate[VK_SHIFT] & 0x80) != 0)
3073 + ((keystate[VK_CONTROL] & 0x80) != 0) * 2;
3075 /* Note if AltGr was pressed and if it was used as a compose key */
3076 if (!compose_state) {
3077 compose_key = 0x100;
3078 if (cfg.compose_key) {
3079 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED))
3080 compose_key = wParam;
3082 if (wParam == VK_APPS)
3083 compose_key = wParam;
3086 if (wParam == compose_key) {
3087 if (compose_state == 0
3088 && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0) compose_state =
3090 else if (compose_state == 1 && (HIWORD(lParam) & KF_UP))
3094 } else if (compose_state == 1 && wParam != VK_CONTROL)
3098 * Record that we pressed key so the scroll window can be reset, but
3099 * be careful to avoid Shift-UP/Down
3101 if (wParam != VK_SHIFT && wParam != VK_PRIOR && wParam != VK_NEXT &&
3102 wParam != VK_MENU && wParam != VK_CONTROL) {
3106 if (compose_state > 1 && left_alt)
3109 /* Sanitize the number pad if not using a PC NumPad */
3110 if (left_alt || (app_keypad_keys && !cfg.no_applic_k
3111 && cfg.funky_type != 2)
3112 || cfg.funky_type == 3 || cfg.nethack_keypad || compose_state) {
3113 if ((HIWORD(lParam) & KF_EXTENDED) == 0) {
3117 nParam = VK_NUMPAD0;
3120 nParam = VK_NUMPAD1;
3123 nParam = VK_NUMPAD2;
3126 nParam = VK_NUMPAD3;
3129 nParam = VK_NUMPAD4;
3132 nParam = VK_NUMPAD5;
3135 nParam = VK_NUMPAD6;
3138 nParam = VK_NUMPAD7;
3141 nParam = VK_NUMPAD8;
3144 nParam = VK_NUMPAD9;
3147 nParam = VK_DECIMAL;
3151 if (keystate[VK_NUMLOCK] & 1)
3158 /* If a key is pressed and AltGr is not active */
3159 if (key_down && (keystate[VK_RMENU] & 0x80) == 0 && !compose_state) {
3160 /* Okay, prepare for most alts then ... */
3164 /* Lets see if it's a pattern we know all about ... */
3165 if (wParam == VK_PRIOR && shift_state == 1) {
3166 SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0);
3169 if (wParam == VK_NEXT && shift_state == 1) {
3170 SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
3173 if (wParam == VK_INSERT && shift_state == 1) {
3177 if (left_alt && wParam == VK_F4 && cfg.alt_f4) {
3180 if (left_alt && wParam == VK_SPACE && cfg.alt_space) {
3181 SendMessage(hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0);
3184 if (left_alt && wParam == VK_RETURN && cfg.fullscreenonaltenter &&
3185 (cfg.resize_action != RESIZE_DISABLED)) {
3186 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) != KF_REPEAT)
3190 /* Control-Numlock for app-keypad mode switch */
3191 if (wParam == VK_PAUSE && shift_state == 2) {
3192 app_keypad_keys ^= 1;
3196 /* Nethack keypad */
3197 if (cfg.nethack_keypad && !left_alt) {
3200 *p++ = shift_state ? 'B' : 'b';
3203 *p++ = shift_state ? 'J' : 'j';
3206 *p++ = shift_state ? 'N' : 'n';
3209 *p++ = shift_state ? 'H' : 'h';
3212 *p++ = shift_state ? '.' : '.';
3215 *p++ = shift_state ? 'L' : 'l';
3218 *p++ = shift_state ? 'Y' : 'y';
3221 *p++ = shift_state ? 'K' : 'k';
3224 *p++ = shift_state ? 'U' : 'u';
3229 /* Application Keypad */
3233 if (cfg.funky_type == 3 ||
3234 (cfg.funky_type <= 1 &&
3235 app_keypad_keys && !cfg.no_applic_k)) switch (wParam) {
3249 if (app_keypad_keys && !cfg.no_applic_k)
3286 if (cfg.funky_type == 2) {
3291 } else if (shift_state)
3298 if (cfg.funky_type == 2)
3302 if (cfg.funky_type == 2)
3306 if (cfg.funky_type == 2)
3311 if (HIWORD(lParam) & KF_EXTENDED)
3317 if (xkey >= 'P' && xkey <= 'S')
3318 p += sprintf((char *) p, "\x1B%c", xkey);
3320 p += sprintf((char *) p, "\x1B?%c", xkey);
3322 p += sprintf((char *) p, "\x1BO%c", xkey);
3327 if (wParam == VK_BACK && shift_state == 0) { /* Backspace */
3328 *p++ = (cfg.bksp_is_delete ? 0x7F : 0x08);
3332 if (wParam == VK_TAB && shift_state == 1) { /* Shift tab */
3338 if (wParam == VK_SPACE && shift_state == 2) { /* Ctrl-Space */
3342 if (wParam == VK_SPACE && shift_state == 3) { /* Ctrl-Shift-Space */
3346 if (wParam == VK_CANCEL && shift_state == 2) { /* Ctrl-Break */
3351 if (wParam == VK_PAUSE) { /* Break/Pause */
3356 /* Control-2 to Control-8 are special */
3357 if (shift_state == 2 && wParam >= '2' && wParam <= '8') {
3358 *p++ = "\000\033\034\035\036\037\177"[wParam - '2'];
3361 if (shift_state == 2 && wParam == 0xBD) {
3365 if (shift_state == 2 && wParam == 0xDF) {
3369 if (shift_state == 0 && wParam == VK_RETURN && cr_lf_return) {
3376 * Next, all the keys that do tilde codes. (ESC '[' nn '~',
3377 * for integer decimal nn.)
3379 * We also deal with the weird ones here. Linux VCs replace F1
3380 * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
3381 * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
3387 code = (keystate[VK_SHIFT] & 0x80 ? 23 : 11);
3390 code = (keystate[VK_SHIFT] & 0x80 ? 24 : 12);
3393 code = (keystate[VK_SHIFT] & 0x80 ? 25 : 13);
3396 code = (keystate[VK_SHIFT] & 0x80 ? 26 : 14);
3399 code = (keystate[VK_SHIFT] & 0x80 ? 28 : 15);
3402 code = (keystate[VK_SHIFT] & 0x80 ? 29 : 17);
3405 code = (keystate[VK_SHIFT] & 0x80 ? 31 : 18);
3408 code = (keystate[VK_SHIFT] & 0x80 ? 32 : 19);
3411 code = (keystate[VK_SHIFT] & 0x80 ? 33 : 20);
3414 code = (keystate[VK_SHIFT] & 0x80 ? 34 : 21);
3447 if ((shift_state&2) == 0) switch (wParam) {
3467 /* Reorder edit keys to physical order */
3468 if (cfg.funky_type == 3 && code <= 6)
3469 code = "\0\2\1\4\5\3\6"[code];
3471 if (vt52_mode && code > 0 && code <= 6) {
3472 p += sprintf((char *) p, "\x1B%c", " HLMEIG"[code]);
3476 if (cfg.funky_type == 5 && /* SCO function keys */
3477 code >= 11 && code <= 34) {
3478 char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
3481 case VK_F1: index = 0; break;
3482 case VK_F2: index = 1; break;
3483 case VK_F3: index = 2; break;
3484 case VK_F4: index = 3; break;
3485 case VK_F5: index = 4; break;
3486 case VK_F6: index = 5; break;
3487 case VK_F7: index = 6; break;
3488 case VK_F8: index = 7; break;
3489 case VK_F9: index = 8; break;
3490 case VK_F10: index = 9; break;
3491 case VK_F11: index = 10; break;
3492 case VK_F12: index = 11; break;
3494 if (keystate[VK_SHIFT] & 0x80) index += 12;
3495 if (keystate[VK_CONTROL] & 0x80) index += 24;
3496 p += sprintf((char *) p, "\x1B[%c", codes[index]);
3499 if (cfg.funky_type == 5 && /* SCO small keypad */
3500 code >= 1 && code <= 6) {
3501 char codes[] = "HL.FIG";
3505 p += sprintf((char *) p, "\x1B[%c", codes[code-1]);
3509 if ((vt52_mode || cfg.funky_type == 4) && code >= 11 && code <= 24) {
3516 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11 - offt);
3519 sprintf((char *) p, "\x1BO%c", code + 'P' - 11 - offt);
3522 if (cfg.funky_type == 1 && code >= 11 && code <= 15) {
3523 p += sprintf((char *) p, "\x1B[[%c", code + 'A' - 11);
3526 if (cfg.funky_type == 2 && code >= 11 && code <= 14) {
3528 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11);
3530 p += sprintf((char *) p, "\x1BO%c", code + 'P' - 11);
3533 if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
3534 p += sprintf((char *) p, code == 1 ? "\x1B[H" : "\x1BOw");
3538 p += sprintf((char *) p, "\x1B[%d~", code);
3543 * Now the remaining keys (arrows and Keypad 5. Keypad 5 for
3544 * some reason seems to send VK_CLEAR to Windows...).
3567 p += sprintf((char *) p, "\x1B%c", xkey);
3569 int app_flg = (app_cursor_keys && !cfg.no_applic_c);
3572 * RDB: VT100 & VT102 manuals both state the
3573 * app cursor keys only work if the app keypad
3576 * SGT: That may well be true, but xterm
3577 * disagrees and so does at least one
3578 * application, so I've #if'ed this out and the
3579 * behaviour is back to PuTTY's original: app
3580 * cursor and app keypad are independently
3581 * switchable modes. If anyone complains about
3582 * _this_ I'll have to put in a configurable
3585 if (!app_keypad_keys)
3588 /* Useful mapping of Ctrl-arrows */
3589 if (shift_state == 2)
3593 p += sprintf((char *) p, "\x1BO%c", xkey);
3595 p += sprintf((char *) p, "\x1B[%c", xkey);
3602 * Finally, deal with Return ourselves. (Win95 seems to
3603 * foul it up when Alt is pressed, for some reason.)
3605 if (wParam == VK_RETURN) { /* Return */
3611 if (left_alt && wParam >= VK_NUMPAD0 && wParam <= VK_NUMPAD9)
3612 alt_sum = alt_sum * 10 + wParam - VK_NUMPAD0;
3617 /* Okay we've done everything interesting; let windows deal with
3618 * the boring stuff */
3622 /* helg: clear CAPS LOCK state if caps lock switches to cyrillic */
3623 if(cfg.xlat_capslockcyr && keystate[VK_CAPITAL] != 0) {
3625 keystate[VK_CAPITAL] = 0;
3628 r = ToAsciiEx(wParam, scan, keystate, keys, 0, kbd_layout);
3629 #ifdef SHOW_TOASCII_RESULT
3630 if (r == 1 && !key_down) {
3632 if (in_utf || dbcs_screenfont)
3633 debug((", (U+%04x)", alt_sum));
3635 debug((", LCH(%d)", alt_sum));
3637 debug((", ACH(%d)", keys[0]));
3642 for (r1 = 0; r1 < r; r1++) {
3643 debug(("%s%d", r1 ? "," : "", keys[r1]));
3652 * Interrupt an ongoing paste. I'm not sure this is
3653 * sensible, but for the moment it's preferable to
3654 * having to faff about buffering things.
3659 for (i = 0; i < r; i++) {
3660 unsigned char ch = (unsigned char) keys[i];
3662 if (compose_state == 2 && (ch & 0x80) == 0 && ch > ' ') {
3667 if (compose_state == 3 && (ch & 0x80) == 0 && ch > ' ') {
3671 if ((nc = check_compose(compose_char, ch)) == -1) {
3672 MessageBeep(MB_ICONHAND);
3676 luni_send(&keybuf, 1, 1);
3684 if (in_utf || dbcs_screenfont) {
3686 luni_send(&keybuf, 1, 1);
3688 ch = (char) alt_sum;
3690 * We need not bother about stdin
3691 * backlogs here, because in GUI PuTTY
3692 * we can't do anything about it
3693 * anyway; there's no means of asking
3694 * Windows to hold off on KEYDOWN
3695 * messages. We _have_ to buffer
3696 * everything we're sent.
3698 ldisc_send(&ch, 1, 1);
3702 lpage_send(kbd_codepage, &ch, 1, 1);
3704 if(capsOn && ch < 0x80) {
3707 cbuf[1] = xlat_uskbd2cyrllic(ch);
3708 luni_send(cbuf+!left_alt, 1+!!left_alt, 1);
3713 lpage_send(kbd_codepage, cbuf+!left_alt, 1+!!left_alt, 1);
3719 /* This is so the ALT-Numpad and dead keys work correctly. */
3724 /* If we're definitly not building up an ALT-54321 then clear it */
3727 /* If we will be using alt_sum fix the 256s */
3728 else if (keys[0] && (in_utf || dbcs_screenfont))
3733 * ALT alone may or may not want to bring up the System menu.
3734 * If it's not meant to, we return 0 on presses or releases of
3735 * ALT, to show that we've swallowed the keystroke. Otherwise
3736 * we return -1, which means Windows will give the keystroke
3737 * its default handling (i.e. bring up the System menu).
3739 if (wParam == VK_MENU && !cfg.alt_only)
3745 void set_title(char *title)
3748 window_name = smalloc(1 + strlen(title));
3749 strcpy(window_name, title);
3750 if (cfg.win_name_always || !IsIconic(hwnd))
3751 SetWindowText(hwnd, title);
3754 void set_icon(char *title)
3757 icon_name = smalloc(1 + strlen(title));
3758 strcpy(icon_name, title);
3759 if (!cfg.win_name_always && IsIconic(hwnd))
3760 SetWindowText(hwnd, title);
3763 void set_sbar(int total, int start, int page)
3767 if (is_full_screen() ? !cfg.scrollbar_in_fullscreen : !cfg.scrollbar)
3770 si.cbSize = sizeof(si);
3771 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
3773 si.nMax = total - 1;
3777 SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
3780 Context get_ctx(void)
3786 SelectPalette(hdc, pal, FALSE);
3792 void free_ctx(Context ctx)
3794 SelectPalette(ctx, GetStockObject(DEFAULT_PALETTE), FALSE);
3795 ReleaseDC(hwnd, ctx);
3798 static void real_palette_set(int n, int r, int g, int b)
3801 logpal->palPalEntry[n].peRed = r;
3802 logpal->palPalEntry[n].peGreen = g;
3803 logpal->palPalEntry[n].peBlue = b;
3804 logpal->palPalEntry[n].peFlags = PC_NOCOLLAPSE;
3805 colours[n] = PALETTERGB(r, g, b);
3806 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3808 colours[n] = RGB(r, g, b);
3811 void palette_set(int n, int r, int g, int b)
3813 static const int first[21] = {
3814 0, 2, 4, 6, 8, 10, 12, 14,
3815 1, 3, 5, 7, 9, 11, 13, 15,
3818 real_palette_set(first[n], r, g, b);
3820 real_palette_set(first[n] + 1, r, g, b);
3822 HDC hdc = get_ctx();
3823 UnrealizeObject(pal);
3824 RealizePalette(hdc);
3829 void palette_reset(void)
3833 for (i = 0; i < NCOLOURS; i++) {
3835 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
3836 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
3837 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
3838 logpal->palPalEntry[i].peFlags = 0;
3839 colours[i] = PALETTERGB(defpal[i].rgbtRed,
3840 defpal[i].rgbtGreen,
3841 defpal[i].rgbtBlue);
3843 colours[i] = RGB(defpal[i].rgbtRed,
3844 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
3849 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3851 RealizePalette(hdc);
3856 void write_aclip(char *data, int len, int must_deselect)
3861 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len + 1);
3864 lock = GlobalLock(clipdata);
3867 memcpy(lock, data, len);
3868 ((unsigned char *) lock)[len] = 0;
3869 GlobalUnlock(clipdata);
3872 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
3874 if (OpenClipboard(hwnd)) {
3876 SetClipboardData(CF_TEXT, clipdata);
3879 GlobalFree(clipdata);
3882 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
3886 * Note: unlike write_aclip() this will not append a nul.
3888 void write_clip(wchar_t * data, int len, int must_deselect)
3890 HGLOBAL clipdata, clipdata2, clipdata3;
3892 void *lock, *lock2, *lock3;
3894 len2 = WideCharToMultiByte(CP_ACP, 0, data, len, 0, 0, NULL, NULL);
3896 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE,
3897 len * sizeof(wchar_t));
3898 clipdata2 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len2);
3900 if (!clipdata || !clipdata2) {
3902 GlobalFree(clipdata);
3904 GlobalFree(clipdata2);
3907 if (!(lock = GlobalLock(clipdata)))
3909 if (!(lock2 = GlobalLock(clipdata2)))
3912 memcpy(lock, data, len * sizeof(wchar_t));
3913 WideCharToMultiByte(CP_ACP, 0, data, len, lock2, len2, NULL, NULL);
3915 if (cfg.rtf_paste) {
3916 wchar_t unitab[256];
3918 unsigned char *tdata = (unsigned char *)lock2;
3919 wchar_t *udata = (wchar_t *)lock;
3920 int rtflen = 0, uindex = 0, tindex = 0;
3922 int multilen, blen, alen, totallen, i;
3923 char before[16], after[4];
3925 get_unitab(CP_ACP, unitab, 0);
3927 rtfsize = 100 + strlen(cfg.font);
3928 rtf = smalloc(rtfsize);
3929 sprintf(rtf, "{\\rtf1\\ansi%d{\\fonttbl\\f0\\fmodern %s;}\\f0",
3930 GetACP(), cfg.font);
3931 rtflen = strlen(rtf);
3934 * We want to construct a piece of RTF that specifies the
3935 * same Unicode text. To do this we will read back in
3936 * parallel from the Unicode data in `udata' and the
3937 * non-Unicode data in `tdata'. For each character in
3938 * `tdata' which becomes the right thing in `udata' when
3939 * looked up in `unitab', we just copy straight over from
3940 * tdata. For each one that doesn't, we must WCToMB it
3941 * individually and produce a \u escape sequence.
3943 * It would probably be more robust to just bite the bullet
3944 * and WCToMB each individual Unicode character one by one,
3945 * then MBToWC each one back to see if it was an accurate
3946 * translation; but that strikes me as a horrifying number
3947 * of Windows API calls so I want to see if this faster way
3948 * will work. If it screws up badly we can always revert to
3949 * the simple and slow way.
3951 while (tindex < len2 && uindex < len &&
3952 tdata[tindex] && udata[uindex]) {
3953 if (tindex + 1 < len2 &&
3954 tdata[tindex] == '\r' &&
3955 tdata[tindex+1] == '\n') {
3959 if (unitab[tdata[tindex]] == udata[uindex]) {
3965 multilen = WideCharToMultiByte(CP_ACP, 0, unitab+uindex, 1,
3966 NULL, 0, NULL, NULL);
3967 if (multilen != 1) {
3968 blen = sprintf(before, "{\\uc%d\\u%d", multilen,
3970 alen = 1; strcpy(after, "}");
3972 blen = sprintf(before, "\\u%d", udata[uindex]);
3973 alen = 0; after[0] = '\0';
3976 assert(tindex + multilen <= len2);
3977 totallen = blen + alen;
3978 for (i = 0; i < multilen; i++) {
3979 if (tdata[tindex+i] == '\\' ||
3980 tdata[tindex+i] == '{' ||
3981 tdata[tindex+i] == '}')
3983 else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A)
3984 totallen += 6; /* \par\r\n */
3985 else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20)
3991 if (rtfsize < rtflen + totallen + 3) {
3992 rtfsize = rtflen + totallen + 512;
3993 rtf = srealloc(rtf, rtfsize);
3996 strcpy(rtf + rtflen, before); rtflen += blen;
3997 for (i = 0; i < multilen; i++) {
3998 if (tdata[tindex+i] == '\\' ||
3999 tdata[tindex+i] == '{' ||
4000 tdata[tindex+i] == '}') {
4001 rtf[rtflen++] = '\\';
4002 rtf[rtflen++] = tdata[tindex+i];
4003 } else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A) {
4004 rtflen += sprintf(rtf+rtflen, "\\par\r\n");
4005 } else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20) {
4006 rtflen += sprintf(rtf+rtflen, "\\'%02x", tdata[tindex+i]);
4008 rtf[rtflen++] = tdata[tindex+i];
4011 strcpy(rtf + rtflen, after); rtflen += alen;
4017 strcpy(rtf + rtflen, "}");
4020 clipdata3 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, rtflen);
4021 if (clipdata3 && (lock3 = GlobalLock(clipdata3)) != NULL) {
4023 GlobalUnlock(clipdata3);
4029 GlobalUnlock(clipdata);
4030 GlobalUnlock(clipdata2);
4033 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
4035 if (OpenClipboard(hwnd)) {
4037 SetClipboardData(CF_UNICODETEXT, clipdata);
4038 SetClipboardData(CF_TEXT, clipdata2);
4040 SetClipboardData(RegisterClipboardFormat(CF_RTF), clipdata3);
4043 GlobalFree(clipdata);
4044 GlobalFree(clipdata2);
4048 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
4051 void get_clip(wchar_t ** p, int *len)
4053 static HGLOBAL clipdata = NULL;
4054 static wchar_t *converted = 0;
4063 GlobalUnlock(clipdata);
4066 } else if (OpenClipboard(NULL)) {
4067 if ((clipdata = GetClipboardData(CF_UNICODETEXT))) {
4069 *p = GlobalLock(clipdata);
4071 for (p2 = *p; *p2; p2++);
4075 } else if ( (clipdata = GetClipboardData(CF_TEXT)) ) {
4079 s = GlobalLock(clipdata);
4080 i = MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, 0, 0);
4081 *p = converted = smalloc(i * sizeof(wchar_t));
4082 MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, converted, i);
4095 * Move `lines' lines from position `from' to position `to' in the
4098 void optimised_move(int to, int from, int lines)
4103 min = (to < from ? to : from);
4104 max = to + from - min;
4106 r.left = offset_width;
4107 r.right = offset_width + cols * font_width;
4108 r.top = offset_height + min * font_height;
4109 r.bottom = offset_height + (max + lines) * font_height;
4110 ScrollWindow(hwnd, 0, (to - from) * font_height, &r, &r);
4115 * Print a message box and perform a fatal exit.
4117 void fatalbox(char *fmt, ...)
4123 vsprintf(stuff, fmt, ap);
4125 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
4130 * Manage window caption / taskbar flashing, if enabled.
4131 * 0 = stop, 1 = maintain, 2 = start
4133 static void flash_window(int mode)
4135 static long last_flash = 0;
4136 static int flashing = 0;
4137 if ((mode == 0) || (cfg.beep_ind == B_IND_DISABLED)) {
4140 FlashWindow(hwnd, FALSE);
4144 } else if (mode == 2) {
4147 last_flash = GetTickCount();
4149 FlashWindow(hwnd, TRUE);
4152 } else if ((mode == 1) && (cfg.beep_ind == B_IND_FLASH)) {
4155 long now = GetTickCount();
4156 long fdiff = now - last_flash;
4157 if (fdiff < 0 || fdiff > 450) {
4159 FlashWindow(hwnd, TRUE); /* toggle */
4170 if (mode == BELL_DEFAULT) {
4172 * For MessageBeep style bells, we want to be careful of
4173 * timing, because they don't have the nice property of
4174 * PlaySound bells that each one cancels the previous
4175 * active one. So we limit the rate to one per 50ms or so.
4177 static long lastbeep = 0;
4180 beepdiff = GetTickCount() - lastbeep;
4181 if (beepdiff >= 0 && beepdiff < 50)
4185 * The above MessageBeep call takes time, so we record the
4186 * time _after_ it finishes rather than before it starts.
4188 lastbeep = GetTickCount();
4189 } else if (mode == BELL_WAVEFILE) {
4190 if (!PlaySound(cfg.bell_wavefile, NULL, SND_ASYNC | SND_FILENAME)) {
4191 char buf[sizeof(cfg.bell_wavefile) + 80];
4192 sprintf(buf, "Unable to play sound file\n%s\n"
4193 "Using default sound instead", cfg.bell_wavefile);
4194 MessageBox(hwnd, buf, "PuTTY Sound Error",
4195 MB_OK | MB_ICONEXCLAMATION);
4196 cfg.beep = BELL_DEFAULT;
4199 /* Otherwise, either visual bell or disabled; do nothing here */
4201 flash_window(2); /* start */
4206 * Minimise or restore the window in response to a server-side
4209 void set_iconic(int iconic)
4211 if (IsIconic(hwnd)) {
4213 ShowWindow(hwnd, SW_RESTORE);
4216 ShowWindow(hwnd, SW_MINIMIZE);
4221 * Move the window in response to a server-side request.
4223 void move_window(int x, int y)
4225 SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
4229 * Move the window to the top or bottom of the z-order in response
4230 * to a server-side request.
4232 void set_zorder(int top)
4234 if (cfg.alwaysontop)
4235 return; /* ignore */
4236 SetWindowPos(hwnd, top ? HWND_TOP : HWND_BOTTOM, 0, 0, 0, 0,
4237 SWP_NOMOVE | SWP_NOSIZE);
4241 * Refresh the window in response to a server-side request.
4243 void refresh_window(void)
4245 InvalidateRect(hwnd, NULL, TRUE);
4249 * Maximise or restore the window in response to a server-side
4252 void set_zoomed(int zoomed)
4254 if (IsZoomed(hwnd)) {
4256 ShowWindow(hwnd, SW_RESTORE);
4259 ShowWindow(hwnd, SW_MAXIMIZE);
4264 * Report whether the window is iconic, for terminal reports.
4268 return IsIconic(hwnd);
4272 * Report the window's position, for terminal reports.
4274 void get_window_pos(int *x, int *y)
4277 GetWindowRect(hwnd, &r);
4283 * Report the window's pixel size, for terminal reports.
4285 void get_window_pixels(int *x, int *y)
4288 GetWindowRect(hwnd, &r);
4289 *x = r.right - r.left;
4290 *y = r.bottom - r.top;
4294 * Return the window or icon title.
4296 char *get_window_title(int icon)
4298 return icon ? icon_name : window_name;
4302 * See if we're in full-screen mode.
4304 int is_full_screen()
4306 if (!IsZoomed(hwnd))
4308 if (GetWindowLong(hwnd, GWL_STYLE) & WS_CAPTION)
4314 * Go full-screen. This should only be called when we are already
4317 void make_full_screen()
4322 assert(IsZoomed(hwnd));
4324 /* Remove the window furniture. */
4325 style = GetWindowLong(hwnd, GWL_STYLE);
4326 style &= ~(WS_CAPTION | WS_BORDER | WS_THICKFRAME);
4327 if (cfg.scrollbar_in_fullscreen)
4328 style |= WS_VSCROLL;
4330 style &= ~WS_VSCROLL;
4331 SetWindowLong(hwnd, GWL_STYLE, style);
4333 /* Resize ourselves to exactly cover the nearest monitor. */
4334 #ifdef MONITOR_DEFAULTTONEAREST
4338 mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
4339 mi.cbSize = sizeof(mi);
4340 GetMonitorInfo(mon, &mi);
4341 x = mi.rcMonitor.left;
4342 y = mi.rcMonitor.top;
4343 w = mi.rcMonitor.right;
4344 h = mi.rcMonitor.bottom;
4348 w = GetSystemMetrics(SM_CXSCREEN);
4349 h = GetSystemMetrics(SM_CYSCREEN);
4351 SetWindowPos(hwnd, HWND_TOP, x, y, w, h, SWP_FRAMECHANGED);
4353 /* Tick the menu item in the System menu. */
4354 CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN,
4359 * Clear the full-screen attributes.
4361 void clear_full_screen()
4363 DWORD oldstyle, style;
4365 /* Reinstate the window furniture. */
4366 style = oldstyle = GetWindowLong(hwnd, GWL_STYLE);
4367 style |= WS_CAPTION | WS_BORDER;
4368 if (cfg.resize_action == RESIZE_DISABLED)
4369 style &= ~WS_THICKFRAME;
4371 style |= WS_THICKFRAME;
4373 style |= WS_VSCROLL;
4375 style &= ~WS_VSCROLL;
4376 if (style != oldstyle) {
4377 SetWindowLong(hwnd, GWL_STYLE, style);
4378 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
4379 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
4383 /* Untick the menu item in the System menu. */
4384 CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN,
4389 * Toggle full-screen mode.
4391 void flip_full_screen()
4393 if (is_full_screen()) {
4394 ShowWindow(hwnd, SW_RESTORE);
4395 } else if (IsZoomed(hwnd)) {
4398 SendMessage(hwnd, WM_FULLSCR_ON_MAX, 0, 0);
4399 ShowWindow(hwnd, SW_MAXIMIZE);