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.
261 default_protocol = DEFAULT_PROTOCOL;
262 default_port = DEFAULT_PORT;
263 cfg.logtype = LGTYP_NONE;
265 do_defaults(NULL, &cfg);
270 * Process a couple of command-line options which are more
271 * easily dealt with before the line is broken up into
272 * words. These are the soon-to-be-defunct @sessionname and
273 * the internal-use-only &sharedmemoryhandle, neither of
274 * which are combined with anything else.
276 while (*p && isspace(*p))
280 while (i > 1 && isspace(p[i - 1]))
283 do_defaults(p + 1, &cfg);
284 if (!*cfg.host && !do_config()) {
288 } else if (*p == '&') {
290 * An initial & means we've been given a command line
291 * containing the hex value of a HANDLE for a file
292 * mapping object, which we must then extract as a
297 if (sscanf(p + 1, "%p", &filemap) == 1 &&
298 (cp = MapViewOfFile(filemap, FILE_MAP_READ,
299 0, 0, sizeof(Config))) != NULL) {
302 CloseHandle(filemap);
303 } else if (!do_config()) {
309 * Otherwise, break up the command line and deal with
315 split_into_argv(cmdline, &argc, &argv, NULL);
317 for (i = 0; i < argc; i++) {
321 ret = cmdline_process_param(p, i+1<argc?argv[i+1]:NULL, 1);
323 cmdline_error("option \"%s\" requires an argument", p);
324 } else if (ret == 2) {
325 i++; /* skip next argument */
326 } else if (ret == 1) {
327 continue; /* nothing further needs doing */
328 } else if (!strcmp(p, "-cleanup")) {
330 * `putty -cleanup'. Remove all registry
331 * entries associated with PuTTY, and also find
332 * and delete the random seed file.
335 "This procedure will remove ALL Registry\n"
336 "entries associated with PuTTY, and will\n"
337 "also remove the PuTTY random seed file.\n"
339 "THIS PROCESS WILL DESTROY YOUR SAVED\n"
340 "SESSIONS. Are you really sure you want\n"
343 MB_YESNO | MB_ICONWARNING) == IDYES) {
347 } else if (*p != '-') {
351 * If we already have a host name, treat
352 * this argument as a port number. NB we
353 * have to treat this as a saved -P
354 * argument, so that it will be deferred
355 * until it's a good moment to run it.
357 int ret = cmdline_process_param("-P", p, 1);
359 } else if (!strncmp(q, "telnet:", 7)) {
361 * If the hostname starts with "telnet:",
362 * set the protocol to Telnet and process
363 * the string as a Telnet URL.
368 if (q[0] == '/' && q[1] == '/')
370 cfg.protocol = PROT_TELNET;
372 while (*p && *p != ':' && *p != '/')
381 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
382 cfg.host[sizeof(cfg.host) - 1] = '\0';
386 * Otherwise, treat this argument as a host
389 while (*p && !isspace(*p))
393 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
394 cfg.host[sizeof(cfg.host) - 1] = '\0';
403 if (!*cfg.host && !do_config()) {
409 * Trim leading whitespace off the hostname if it's there.
412 int space = strspn(cfg.host, " \t");
413 memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
416 /* See if host is of the form user@host */
417 if (cfg.host[0] != '\0') {
418 char *atsign = strchr(cfg.host, '@');
419 /* Make sure we're not overflowing the user field */
421 if (atsign - cfg.host < sizeof cfg.username) {
422 strncpy(cfg.username, cfg.host, atsign - cfg.host);
423 cfg.username[atsign - cfg.host] = '\0';
425 memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
430 * Trim a colon suffix off the hostname if it's there.
432 cfg.host[strcspn(cfg.host, ":")] = '\0';
435 * Remove any remaining whitespace from the hostname.
439 while (cfg.host[p2] != '\0') {
440 if (cfg.host[p2] != ' ' && cfg.host[p2] != '\t') {
441 cfg.host[p1] = cfg.host[p2];
451 * Select protocol. This is farmed out into a table in a
452 * separate file to enable an ssh-free variant.
457 for (i = 0; backends[i].backend != NULL; i++)
458 if (backends[i].protocol == cfg.protocol) {
459 back = backends[i].backend;
463 MessageBox(NULL, "Unsupported protocol number found",
464 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
470 /* Check for invalid Port number (i.e. zero) */
472 MessageBox(NULL, "Invalid Port Number",
473 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
480 wndclass.lpfnWndProc = WndProc;
481 wndclass.cbClsExtra = 0;
482 wndclass.cbWndExtra = 0;
483 wndclass.hInstance = inst;
484 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
485 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
486 wndclass.hbrBackground = NULL;
487 wndclass.lpszMenuName = NULL;
488 wndclass.lpszClassName = appname;
490 RegisterClass(&wndclass);
495 savelines = cfg.savelines;
501 * Guess some defaults for the window size. This all gets
502 * updated later, so we don't really care too much. However, we
503 * do want the font width/height guesses to correspond to a
504 * large font rather than a small one...
511 term_size(cfg.height, cfg.width, cfg.savelines);
512 guess_width = extra_width + font_width * cols;
513 guess_height = extra_height + font_height * rows;
516 get_fullscreen_rect(&r);
517 if (guess_width > r.right - r.left)
518 guess_width = r.right - r.left;
519 if (guess_height > r.bottom - r.top)
520 guess_height = r.bottom - r.top;
524 int winmode = WS_OVERLAPPEDWINDOW | WS_VSCROLL;
527 winmode &= ~(WS_VSCROLL);
528 if (cfg.resize_action == RESIZE_DISABLED)
529 winmode &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
531 exwinmode |= WS_EX_TOPMOST;
533 exwinmode |= WS_EX_CLIENTEDGE;
534 hwnd = CreateWindowEx(exwinmode, appname, appname,
535 winmode, CW_USEDEFAULT, CW_USEDEFAULT,
536 guess_width, guess_height,
537 NULL, NULL, inst, NULL);
541 * Initialise the fonts, simultaneously correcting the guesses
542 * for font_{width,height}.
547 * Correct the guesses for extra_{width,height}.
551 GetWindowRect(hwnd, &wr);
552 GetClientRect(hwnd, &cr);
553 offset_width = offset_height = cfg.window_border;
554 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
555 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
559 * Resize the window, now we know what size we _really_ want it
562 guess_width = extra_width + font_width * cols;
563 guess_height = extra_height + font_height * rows;
564 SetWindowPos(hwnd, NULL, 0, 0, guess_width, guess_height,
565 SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
568 * Set up a caret bitmap, with no content.
572 int size = (font_width + 15) / 16 * 2 * font_height;
573 bits = smalloc(size);
574 memset(bits, 0, size);
575 caretbm = CreateBitmap(font_width, font_height, 1, 1, bits);
578 CreateCaret(hwnd, caretbm, font_width, font_height);
581 * Initialise the scroll bar.
586 si.cbSize = sizeof(si);
587 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
592 SetScrollInfo(hwnd, SB_VERT, &si, FALSE);
596 * Start up the telnet connection.
600 char msg[1024], *title;
603 error = back->init(cfg.host, cfg.port, &realhost, cfg.tcp_nodelay);
605 sprintf(msg, "Unable to open connection to\n"
606 "%.800s\n" "%s", cfg.host, error);
607 MessageBox(NULL, msg, "PuTTY Error", MB_ICONERROR | MB_OK);
610 window_name = icon_name = NULL;
612 title = cfg.wintitle;
614 sprintf(msg, "%s - PuTTY", realhost);
622 session_closed = FALSE;
625 * Prepare the mouse handler.
627 lastact = MA_NOTHING;
628 lastbtn = MBT_NOTHING;
629 dbltime = GetDoubleClickTime();
632 * Set up the session-control options on the system menu.
635 HMENU m = GetSystemMenu(hwnd, FALSE);
639 AppendMenu(m, MF_SEPARATOR, 0, 0);
640 if (cfg.protocol == PROT_TELNET) {
642 AppendMenu(p, MF_ENABLED, IDM_TEL_AYT, "Are You There");
643 AppendMenu(p, MF_ENABLED, IDM_TEL_BRK, "Break");
644 AppendMenu(p, MF_ENABLED, IDM_TEL_SYNCH, "Synch");
645 AppendMenu(p, MF_SEPARATOR, 0, 0);
646 AppendMenu(p, MF_ENABLED, IDM_TEL_EC, "Erase Character");
647 AppendMenu(p, MF_ENABLED, IDM_TEL_EL, "Erase Line");
648 AppendMenu(p, MF_ENABLED, IDM_TEL_GA, "Go Ahead");
649 AppendMenu(p, MF_ENABLED, IDM_TEL_NOP, "No Operation");
650 AppendMenu(p, MF_SEPARATOR, 0, 0);
651 AppendMenu(p, MF_ENABLED, IDM_TEL_ABORT, "Abort Process");
652 AppendMenu(p, MF_ENABLED, IDM_TEL_AO, "Abort Output");
653 AppendMenu(p, MF_ENABLED, IDM_TEL_IP, "Interrupt Process");
654 AppendMenu(p, MF_ENABLED, IDM_TEL_SUSP, "Suspend Process");
655 AppendMenu(p, MF_SEPARATOR, 0, 0);
656 AppendMenu(p, MF_ENABLED, IDM_TEL_EOR, "End Of Record");
657 AppendMenu(p, MF_ENABLED, IDM_TEL_EOF, "End Of File");
658 AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) p,
660 AppendMenu(m, MF_SEPARATOR, 0, 0);
662 AppendMenu(m, MF_ENABLED, IDM_SHOWLOG, "&Event Log");
663 AppendMenu(m, MF_SEPARATOR, 0, 0);
664 AppendMenu(m, MF_ENABLED, IDM_NEWSESS, "Ne&w Session...");
665 AppendMenu(m, MF_ENABLED, IDM_DUPSESS, "&Duplicate Session");
668 for (i = 1; i < ((nsessions < 256) ? nsessions : 256); i++)
669 AppendMenu(s, MF_ENABLED, IDM_SAVED_MIN + (16 * i),
671 AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) s, "Sa&ved Sessions");
672 AppendMenu(m, MF_ENABLED, IDM_RECONF, "Chan&ge Settings...");
673 AppendMenu(m, MF_SEPARATOR, 0, 0);
674 AppendMenu(m, MF_ENABLED, IDM_COPYALL, "C&opy All to Clipboard");
675 AppendMenu(m, MF_ENABLED, IDM_CLRSB, "C&lear Scrollback");
676 AppendMenu(m, MF_ENABLED, IDM_RESET, "Rese&t Terminal");
677 AppendMenu(m, MF_SEPARATOR, 0, 0);
678 AppendMenu(m, (cfg.resize_action == RESIZE_DISABLED) ?
679 MF_GRAYED : MF_ENABLED, IDM_FULLSCREEN, "&Full Screen");
680 AppendMenu(m, MF_SEPARATOR, 0, 0);
682 AppendMenu(m, MF_ENABLED, IDM_HELP, "&Help");
683 AppendMenu(m, MF_ENABLED, IDM_ABOUT, "&About PuTTY");
687 * Set up the initial input locale.
689 set_input_locale(GetKeyboardLayout(0));
692 * Open the initial log file if there is one.
697 * Finally show the window!
699 ShowWindow(hwnd, show);
700 SetForegroundWindow(hwnd);
703 * Set the palette up.
709 has_focus = (GetForegroundWindow() == hwnd);
712 if (GetMessage(&msg, NULL, 0, 0) == 1) {
713 int timer_id = 0, long_timer = 0;
715 while (msg.message != WM_QUIT) {
716 /* Sometimes DispatchMessage calls routines that use their own
717 * GetMessage loop, setup this timer so we get some control back.
719 * Also call term_update() from the timer so that if the host
720 * is sending data flat out we still do redraws.
722 if (timer_id && long_timer) {
723 KillTimer(hwnd, timer_id);
724 long_timer = timer_id = 0;
727 timer_id = SetTimer(hwnd, 1, 20, NULL);
728 if (!(IsWindow(logbox) && IsDialogMessage(logbox, &msg)))
729 DispatchMessage(&msg);
731 /* Make sure we blink everything that needs it. */
734 /* Send the paste buffer if there's anything to send */
737 /* If there's nothing new in the queue then we can do everything
738 * we've delayed, reading the socket, writing, and repainting
741 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
744 if (pending_netevent) {
745 enact_pending_netevent();
747 /* Force the cursor blink on */
750 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
754 /* Okay there is now nothing to do so we make sure the screen is
755 * completely up to date then tell windows to call us in a little
759 KillTimer(hwnd, timer_id);
763 if (GetCapture() != hwnd ||
764 (send_raw_mouse && !(cfg.mouse_override && is_shift_pressed())))
769 flash_window(1); /* maintain */
771 /* The messages seem unreliable; especially if we're being tricky */
772 has_focus = (GetForegroundWindow() == hwnd);
775 /* Hmm, term_update didn't want to do an update too soon ... */
776 timer_id = SetTimer(hwnd, 1, 50, NULL);
778 timer_id = SetTimer(hwnd, 1, 500, NULL);
780 timer_id = SetTimer(hwnd, 1, 100, NULL);
783 /* There's no point rescanning everything in the message queue
784 * so we do an apparently unnecessary wait here
787 if (GetMessage(&msg, NULL, 0, 0) != 1)
792 cleanup_exit(msg.wParam); /* this doesn't return... */
793 return msg.wParam; /* ... but optimiser doesn't know */
799 void cleanup_exit(int code)
812 if (cfg.protocol == PROT_SSH) {
823 * Set up, or shut down, an AsyncSelect. Called from winnet.c.
825 char *do_select(SOCKET skt, int startup)
830 events = (FD_CONNECT | FD_READ | FD_WRITE |
831 FD_OOB | FD_CLOSE | FD_ACCEPT);
836 return "do_select(): internal error (hwnd==NULL)";
837 if (WSAAsyncSelect(skt, hwnd, msg, events) == SOCKET_ERROR) {
838 switch (WSAGetLastError()) {
840 return "Network is down";
842 return "WSAAsyncSelect(): unknown error";
849 * set or clear the "raw mouse message" mode
851 void set_raw_mouse_mode(int activate)
853 activate = activate && !cfg.no_mouse_rep;
854 send_raw_mouse = activate;
855 SetCursor(LoadCursor(NULL, activate ? IDC_ARROW : IDC_IBEAM));
859 * Print a message box and close the connection.
861 void connection_fatal(char *fmt, ...)
867 vsprintf(stuff, fmt, ap);
869 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
870 if (cfg.close_on_exit == COE_ALWAYS)
873 session_closed = TRUE;
874 SetWindowText(hwnd, "PuTTY (inactive)");
879 * Report an error at the command-line parsing stage.
881 void cmdline_error(char *fmt, ...)
887 vsprintf(stuff, fmt, ap);
889 MessageBox(hwnd, stuff, "PuTTY Command Line Error", MB_ICONERROR | MB_OK);
894 * Actually do the job requested by a WM_NETEVENT
896 static void enact_pending_netevent(void)
898 static int reentering = 0;
899 extern int select_result(WPARAM, LPARAM);
903 return; /* don't unpend the pending */
905 pending_netevent = FALSE;
908 ret = select_result(pend_netevent_wParam, pend_netevent_lParam);
911 if (ret == 0 && !session_closed) {
912 /* Abnormal exits will already have set session_closed and taken
913 * appropriate action. */
914 if (cfg.close_on_exit == COE_ALWAYS ||
915 cfg.close_on_exit == COE_NORMAL) PostQuitMessage(0);
917 session_closed = TRUE;
918 SetWindowText(hwnd, "PuTTY (inactive)");
919 MessageBox(hwnd, "Connection closed by remote host",
920 "PuTTY", MB_OK | MB_ICONINFORMATION);
926 * Copy the colour palette from the configuration data into defpal.
927 * This is non-trivial because the colour indices are different.
929 static void cfgtopalette(void)
932 static const int ww[] = {
933 6, 7, 8, 9, 10, 11, 12, 13,
934 14, 15, 16, 17, 18, 19, 20, 21,
935 0, 1, 2, 3, 4, 4, 5, 5
938 for (i = 0; i < 24; i++) {
940 defpal[i].rgbtRed = cfg.colours[w][0];
941 defpal[i].rgbtGreen = cfg.colours[w][1];
942 defpal[i].rgbtBlue = cfg.colours[w][2];
947 * Set up the colour palette.
949 static void init_palette(void)
952 HDC hdc = GetDC(hwnd);
954 if (cfg.try_palette && GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) {
955 logpal = smalloc(sizeof(*logpal)
956 - sizeof(logpal->palPalEntry)
957 + NCOLOURS * sizeof(PALETTEENTRY));
958 logpal->palVersion = 0x300;
959 logpal->palNumEntries = NCOLOURS;
960 for (i = 0; i < NCOLOURS; i++) {
961 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
962 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
963 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
964 logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
966 pal = CreatePalette(logpal);
968 SelectPalette(hdc, pal, FALSE);
970 SelectPalette(hdc, GetStockObject(DEFAULT_PALETTE), FALSE);
973 ReleaseDC(hwnd, hdc);
976 for (i = 0; i < NCOLOURS; i++)
977 colours[i] = PALETTERGB(defpal[i].rgbtRed,
981 for (i = 0; i < NCOLOURS; i++)
982 colours[i] = RGB(defpal[i].rgbtRed,
983 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
987 * Initialise all the fonts we will need initially. There may be as many as
988 * three or as few as one. The other (poentially) twentyone fonts are done
989 * if/when they are needed.
993 * - check the font width and height, correcting our guesses if
996 * - verify that the bold font is the same width as the ordinary
997 * one, and engage shadow bolding if not.
999 * - verify that the underlined font is the same width as the
1000 * ordinary one (manual underlining by means of line drawing can
1001 * be done in a pinch).
1003 static void init_fonts(int pick_width, int pick_height)
1010 int fw_dontcare, fw_bold;
1012 for (i = 0; i < FONT_MAXNO; i++)
1015 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
1016 und_mode = UND_FONT;
1018 if (cfg.fontisbold) {
1019 fw_dontcare = FW_BOLD;
1022 fw_dontcare = FW_DONTCARE;
1029 font_height = pick_height;
1031 font_height = cfg.fontheight;
1032 if (font_height > 0) {
1034 -MulDiv(font_height, GetDeviceCaps(hdc, LOGPIXELSY), 72);
1037 font_width = pick_width;
1039 #define f(i,c,w,u) \
1040 fonts[i] = CreateFont (font_height, font_width, 0, 0, w, FALSE, u, FALSE, \
1041 c, OUT_DEFAULT_PRECIS, \
1042 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, \
1043 FIXED_PITCH | FF_DONTCARE, cfg.font)
1045 f(FONT_NORMAL, cfg.fontcharset, fw_dontcare, FALSE);
1047 lfont.lfHeight = font_height;
1048 lfont.lfWidth = font_width;
1049 lfont.lfEscapement = 0;
1050 lfont.lfOrientation = 0;
1051 lfont.lfWeight = fw_dontcare;
1052 lfont.lfItalic = FALSE;
1053 lfont.lfUnderline = FALSE;
1054 lfont.lfStrikeOut = FALSE;
1055 lfont.lfCharSet = cfg.fontcharset;
1056 lfont.lfOutPrecision = OUT_DEFAULT_PRECIS;
1057 lfont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
1058 lfont.lfQuality = DEFAULT_QUALITY;
1059 lfont.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE;
1060 strncpy(lfont.lfFaceName, cfg.font, LF_FACESIZE);
1062 SelectObject(hdc, fonts[FONT_NORMAL]);
1063 GetTextMetrics(hdc, &tm);
1065 if (pick_width == 0 || pick_height == 0) {
1066 font_height = tm.tmHeight;
1067 font_width = tm.tmAveCharWidth;
1069 font_dualwidth = (tm.tmAveCharWidth != tm.tmMaxCharWidth);
1071 #ifdef RDB_DEBUG_PATCH
1072 debug(23, "Primary font H=%d, AW=%d, MW=%d",
1073 tm.tmHeight, tm.tmAveCharWidth, tm.tmMaxCharWidth);
1078 DWORD cset = tm.tmCharSet;
1079 memset(&info, 0xFF, sizeof(info));
1081 /* !!! Yes the next line is right */
1082 if (cset == OEM_CHARSET)
1083 font_codepage = GetOEMCP();
1085 if (TranslateCharsetInfo
1086 ((DWORD *) cset, &info, TCI_SRCCHARSET)) font_codepage =
1091 GetCPInfo(font_codepage, &cpinfo);
1092 dbcs_screenfont = (cpinfo.MaxCharSize > 1);
1095 f(FONT_UNDERLINE, cfg.fontcharset, fw_dontcare, TRUE);
1098 * Some fonts, e.g. 9-pt Courier, draw their underlines
1099 * outside their character cell. We successfully prevent
1100 * screen corruption by clipping the text output, but then
1101 * we lose the underline completely. Here we try to work
1102 * out whether this is such a font, and if it is, we set a
1103 * flag that causes underlines to be drawn by hand.
1105 * Having tried other more sophisticated approaches (such
1106 * as examining the TEXTMETRIC structure or requesting the
1107 * height of a string), I think we'll do this the brute
1108 * force way: we create a small bitmap, draw an underlined
1109 * space on it, and test to see whether any pixels are
1110 * foreground-coloured. (Since we expect the underline to
1111 * go all the way across the character cell, we only search
1112 * down a single column of the bitmap, half way across.)
1116 HBITMAP und_bm, und_oldbm;
1120 und_dc = CreateCompatibleDC(hdc);
1121 und_bm = CreateCompatibleBitmap(hdc, font_width, font_height);
1122 und_oldbm = SelectObject(und_dc, und_bm);
1123 SelectObject(und_dc, fonts[FONT_UNDERLINE]);
1124 SetTextAlign(und_dc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
1125 SetTextColor(und_dc, RGB(255, 255, 255));
1126 SetBkColor(und_dc, RGB(0, 0, 0));
1127 SetBkMode(und_dc, OPAQUE);
1128 ExtTextOut(und_dc, 0, 0, ETO_OPAQUE, NULL, " ", 1, NULL);
1130 for (i = 0; i < font_height; i++) {
1131 c = GetPixel(und_dc, font_width / 2, i);
1132 if (c != RGB(0, 0, 0))
1135 SelectObject(und_dc, und_oldbm);
1136 DeleteObject(und_bm);
1139 und_mode = UND_LINE;
1140 DeleteObject(fonts[FONT_UNDERLINE]);
1141 fonts[FONT_UNDERLINE] = 0;
1145 if (bold_mode == BOLD_FONT) {
1146 f(FONT_BOLD, cfg.fontcharset, fw_bold, FALSE);
1150 descent = tm.tmAscent + 1;
1151 if (descent >= font_height)
1152 descent = font_height - 1;
1154 for (i = 0; i < 3; i++) {
1156 if (SelectObject(hdc, fonts[i]) && GetTextMetrics(hdc, &tm))
1157 fontsize[i] = tm.tmAveCharWidth + 256 * tm.tmHeight;
1164 ReleaseDC(hwnd, hdc);
1166 if (fontsize[FONT_UNDERLINE] != fontsize[FONT_NORMAL]) {
1167 und_mode = UND_LINE;
1168 DeleteObject(fonts[FONT_UNDERLINE]);
1169 fonts[FONT_UNDERLINE] = 0;
1172 if (bold_mode == BOLD_FONT &&
1173 fontsize[FONT_BOLD] != fontsize[FONT_NORMAL]) {
1174 bold_mode = BOLD_SHADOW;
1175 DeleteObject(fonts[FONT_BOLD]);
1176 fonts[FONT_BOLD] = 0;
1178 fontflag[0] = fontflag[1] = fontflag[2] = 1;
1183 static void another_font(int fontno)
1186 int fw_dontcare, fw_bold;
1190 if (fontno < 0 || fontno >= FONT_MAXNO || fontflag[fontno])
1193 basefont = (fontno & ~(FONT_BOLDUND));
1194 if (basefont != fontno && !fontflag[basefont])
1195 another_font(basefont);
1197 if (cfg.fontisbold) {
1198 fw_dontcare = FW_BOLD;
1201 fw_dontcare = FW_DONTCARE;
1205 c = cfg.fontcharset;
1211 if (fontno & FONT_WIDE)
1213 if (fontno & FONT_NARROW)
1215 if (fontno & FONT_OEM)
1217 if (fontno & FONT_BOLD)
1219 if (fontno & FONT_UNDERLINE)
1223 CreateFont(font_height * (1 + !!(fontno & FONT_HIGH)), x, 0, 0, w,
1224 FALSE, u, FALSE, c, OUT_DEFAULT_PRECIS,
1225 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
1226 FIXED_PITCH | FF_DONTCARE, s);
1228 fontflag[fontno] = 1;
1231 static void deinit_fonts(void)
1234 for (i = 0; i < FONT_MAXNO; i++) {
1236 DeleteObject(fonts[i]);
1242 void request_resize(int w, int h)
1246 /* If the window is maximized supress resizing attempts */
1247 if (IsZoomed(hwnd)) {
1248 if (cfg.resize_action == RESIZE_TERM)
1252 if (cfg.resize_action == RESIZE_DISABLED) return;
1253 if (h == rows && w == cols) return;
1255 /* Sanity checks ... */
1257 static int first_time = 1;
1260 switch (first_time) {
1262 /* Get the size of the screen */
1263 if (get_fullscreen_rect(&ss))
1264 /* first_time = 0 */ ;
1270 /* Make sure the values are sane */
1271 width = (ss.right - ss.left - extra_width) / 4;
1272 height = (ss.bottom - ss.top - extra_height) / 6;
1274 if (w > width || h > height)
1283 term_size(h, w, cfg.savelines);
1285 if (cfg.resize_action != RESIZE_FONT && !IsZoomed(hwnd)) {
1286 width = extra_width + font_width * w;
1287 height = extra_height + font_height * h;
1289 SetWindowPos(hwnd, NULL, 0, 0, width, height,
1290 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1291 SWP_NOMOVE | SWP_NOZORDER);
1295 InvalidateRect(hwnd, NULL, TRUE);
1298 static void reset_window(int reinit) {
1300 * This function decides how to resize or redraw when the
1301 * user changes something.
1303 * This function doesn't like to change the terminal size but if the
1304 * font size is locked that may be it's only soluion.
1306 int win_width, win_height;
1309 #ifdef RDB_DEBUG_PATCH
1310 debug((27, "reset_window()"));
1313 /* Current window sizes ... */
1314 GetWindowRect(hwnd, &wr);
1315 GetClientRect(hwnd, &cr);
1317 win_width = cr.right - cr.left;
1318 win_height = cr.bottom - cr.top;
1320 if (cfg.resize_action == RESIZE_DISABLED) reinit = 2;
1322 /* Are we being forced to reload the fonts ? */
1324 #ifdef RDB_DEBUG_PATCH
1325 debug((27, "reset_window() -- Forced deinit"));
1331 /* Oh, looks like we're minimised */
1332 if (win_width == 0 || win_height == 0)
1335 /* Is the window out of position ? */
1337 (offset_width != (win_width-font_width*cols)/2 ||
1338 offset_height != (win_height-font_height*rows)/2) ){
1339 offset_width = (win_width-font_width*cols)/2;
1340 offset_height = (win_height-font_height*rows)/2;
1341 InvalidateRect(hwnd, NULL, TRUE);
1342 #ifdef RDB_DEBUG_PATCH
1343 debug((27, "reset_window() -> Reposition terminal"));
1347 if (IsZoomed(hwnd)) {
1348 /* We're fullscreen, this means we must not change the size of
1349 * the window so it's the font size or the terminal itself.
1352 extra_width = wr.right - wr.left - cr.right + cr.left;
1353 extra_height = wr.bottom - wr.top - cr.bottom + cr.top;
1355 if (cfg.resize_action != RESIZE_TERM) {
1356 if ( font_width != win_width/cols ||
1357 font_height != win_height/rows) {
1359 init_fonts(win_width/cols, win_height/rows);
1360 offset_width = (win_width-font_width*cols)/2;
1361 offset_height = (win_height-font_height*rows)/2;
1362 InvalidateRect(hwnd, NULL, TRUE);
1363 #ifdef RDB_DEBUG_PATCH
1364 debug((25, "reset_window() -> Z font resize to (%d, %d)",
1365 font_width, font_height));
1369 if ( font_width != win_width/cols ||
1370 font_height != win_height/rows) {
1371 /* Our only choice at this point is to change the
1372 * size of the terminal; Oh well.
1374 term_size( win_height/font_height, win_width/font_width,
1376 offset_width = (win_width-font_width*cols)/2;
1377 offset_height = (win_height-font_height*rows)/2;
1378 InvalidateRect(hwnd, NULL, TRUE);
1379 #ifdef RDB_DEBUG_PATCH
1380 debug((27, "reset_window() -> Zoomed term_size"));
1387 /* Hmm, a force re-init means we should ignore the current window
1388 * so we resize to the default font size.
1391 #ifdef RDB_DEBUG_PATCH
1392 debug((27, "reset_window() -> Forced re-init"));
1395 offset_width = offset_height = cfg.window_border;
1396 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
1397 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
1399 if (win_width != font_width*cols + offset_width*2 ||
1400 win_height != font_height*rows + offset_height*2) {
1402 /* If this is too large windows will resize it to the maximum
1403 * allowed window size, we will then be back in here and resize
1404 * the font or terminal to fit.
1406 SetWindowPos(hwnd, NULL, 0, 0,
1407 font_width*cols + extra_width,
1408 font_height*rows + extra_height,
1409 SWP_NOMOVE | SWP_NOZORDER);
1412 InvalidateRect(hwnd, NULL, TRUE);
1416 /* Okay the user doesn't want us to change the font so we try the
1417 * window. But that may be too big for the screen which forces us
1418 * to change the terminal.
1420 if ((cfg.resize_action == RESIZE_TERM && reinit<=0) ||
1421 (cfg.resize_action == RESIZE_EITHER && reinit<0) ||
1423 offset_width = offset_height = cfg.window_border;
1424 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
1425 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
1427 if (win_width != font_width*cols + offset_width*2 ||
1428 win_height != font_height*rows + offset_height*2) {
1433 get_fullscreen_rect(&ss);
1435 width = (ss.right - ss.left - extra_width) / font_width;
1436 height = (ss.bottom - ss.top - extra_height) / font_height;
1439 if ( rows > height || cols > width ) {
1440 if (cfg.resize_action == RESIZE_EITHER) {
1441 /* Make the font the biggest we can */
1443 font_width = (ss.right - ss.left - extra_width)/cols;
1445 font_height = (ss.bottom - ss.top - extra_height)/rows;
1448 init_fonts(font_width, font_height);
1450 width = (ss.right - ss.left - extra_width) / font_width;
1451 height = (ss.bottom - ss.top - extra_height) / font_height;
1453 if ( height > rows ) height = rows;
1454 if ( width > cols ) width = cols;
1455 term_size(height, width, cfg.savelines);
1456 #ifdef RDB_DEBUG_PATCH
1457 debug((27, "reset_window() -> term resize to (%d,%d)",
1463 SetWindowPos(hwnd, NULL, 0, 0,
1464 font_width*cols + extra_width,
1465 font_height*rows + extra_height,
1466 SWP_NOMOVE | SWP_NOZORDER);
1468 InvalidateRect(hwnd, NULL, TRUE);
1469 #ifdef RDB_DEBUG_PATCH
1470 debug((27, "reset_window() -> window resize to (%d,%d)",
1471 font_width*cols + extra_width,
1472 font_height*rows + extra_height));
1478 /* We're allowed to or must change the font but do we want to ? */
1480 if (font_width != (win_width-cfg.window_border*2)/cols ||
1481 font_height != (win_height-cfg.window_border*2)/rows) {
1484 init_fonts((win_width-cfg.window_border*2)/cols,
1485 (win_height-cfg.window_border*2)/rows);
1486 offset_width = (win_width-font_width*cols)/2;
1487 offset_height = (win_height-font_height*rows)/2;
1489 extra_width = wr.right - wr.left - cr.right + cr.left +offset_width*2;
1490 extra_height = wr.bottom - wr.top - cr.bottom + cr.top+offset_height*2;
1492 InvalidateRect(hwnd, NULL, TRUE);
1493 #ifdef RDB_DEBUG_PATCH
1494 debug((25, "reset_window() -> font resize to (%d,%d)",
1495 font_width, font_height));
1500 static void set_input_locale(HKL kl)
1504 GetLocaleInfo(LOWORD(kl), LOCALE_IDEFAULTANSICODEPAGE,
1505 lbuf, sizeof(lbuf));
1507 kbd_codepage = atoi(lbuf);
1510 static void click(Mouse_Button b, int x, int y, int shift, int ctrl, int alt)
1512 int thistime = GetMessageTime();
1514 if (send_raw_mouse && !(cfg.mouse_override && shift)) {
1515 lastbtn = MBT_NOTHING;
1516 term_mouse(b, MA_CLICK, x, y, shift, ctrl, alt);
1520 if (lastbtn == b && thistime - lasttime < dbltime) {
1521 lastact = (lastact == MA_CLICK ? MA_2CLK :
1522 lastact == MA_2CLK ? MA_3CLK :
1523 lastact == MA_3CLK ? MA_CLICK : MA_NOTHING);
1528 if (lastact != MA_NOTHING)
1529 term_mouse(b, lastact, x, y, shift, ctrl, alt);
1530 lasttime = thistime;
1534 * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT)
1535 * into a cooked one (SELECT, EXTEND, PASTE).
1537 Mouse_Button translate_button(Mouse_Button button)
1539 if (button == MBT_LEFT)
1541 if (button == MBT_MIDDLE)
1542 return cfg.mouse_is_xterm ? MBT_PASTE : MBT_EXTEND;
1543 if (button == MBT_RIGHT)
1544 return cfg.mouse_is_xterm ? MBT_EXTEND : MBT_PASTE;
1545 return 0; /* shouldn't happen */
1548 static void show_mouseptr(int show)
1550 static int cursor_visible = 1;
1551 if (!cfg.hide_mouseptr) /* override if this feature disabled */
1553 if (cursor_visible && !show)
1555 else if (!cursor_visible && show)
1557 cursor_visible = show;
1560 static int is_alt_pressed(void)
1563 int r = GetKeyboardState(keystate);
1566 if (keystate[VK_MENU] & 0x80)
1568 if (keystate[VK_RMENU] & 0x80)
1573 static int is_shift_pressed(void)
1576 int r = GetKeyboardState(keystate);
1579 if (keystate[VK_SHIFT] & 0x80)
1584 static int resizing;
1586 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1587 WPARAM wParam, LPARAM lParam)
1590 static int ignore_clip = FALSE;
1591 static int need_backend_resize = FALSE;
1592 static int fullscr_on_max = FALSE;
1596 if (pending_netevent)
1597 enact_pending_netevent();
1598 if (GetCapture() != hwnd ||
1599 (send_raw_mouse && !(cfg.mouse_override && is_shift_pressed())))
1605 if (cfg.ping_interval > 0) {
1608 if (now - last_movement > cfg.ping_interval) {
1609 back->special(TS_PING);
1610 last_movement = now;
1613 net_pending_errors();
1619 if (!cfg.warn_on_close || session_closed ||
1621 "Are you sure you want to close this session?",
1622 "PuTTY Exit Confirmation",
1623 MB_ICONWARNING | MB_OKCANCEL) == IDOK)
1624 DestroyWindow(hwnd);
1631 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1643 PROCESS_INFORMATION pi;
1644 HANDLE filemap = NULL;
1646 if (wParam == IDM_DUPSESS) {
1648 * Allocate a file-mapping memory chunk for the
1651 SECURITY_ATTRIBUTES sa;
1654 sa.nLength = sizeof(sa);
1655 sa.lpSecurityDescriptor = NULL;
1656 sa.bInheritHandle = TRUE;
1657 filemap = CreateFileMapping((HANDLE) 0xFFFFFFFF,
1660 0, sizeof(Config), NULL);
1662 p = (Config *) MapViewOfFile(filemap,
1664 0, 0, sizeof(Config));
1666 *p = cfg; /* structure copy */
1670 sprintf(c, "putty &%p", filemap);
1672 } else if (wParam == IDM_SAVEDSESS) {
1673 if ((lParam - IDM_SAVED_MIN) / 16 < nsessions) {
1675 sessions[(lParam - IDM_SAVED_MIN) / 16];
1676 cl = smalloc(16 + strlen(session));
1677 /* 8, but play safe */
1680 /* not a very important failure mode */
1682 sprintf(cl, "putty @%s", session);
1690 GetModuleFileName(NULL, b, sizeof(b) - 1);
1692 si.lpReserved = NULL;
1693 si.lpDesktop = NULL;
1697 si.lpReserved2 = NULL;
1698 CreateProcess(b, cl, NULL, NULL, TRUE,
1699 NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
1702 CloseHandle(filemap);
1712 GetWindowText(hwnd, cfg.wintitle, sizeof(cfg.wintitle));
1715 if (!do_reconfig(hwnd))
1719 /* Disable full-screen if resizing forbidden */
1720 HMENU m = GetSystemMenu (hwnd, FALSE);
1721 EnableMenuItem(m, IDM_FULLSCREEN, MF_BYCOMMAND |
1722 (cfg.resize_action == RESIZE_DISABLED)
1723 ? MF_GRAYED : MF_ENABLED);
1724 /* Gracefully unzoom if necessary */
1725 if (IsZoomed(hwnd) &&
1726 (cfg.resize_action == RESIZE_DISABLED)) {
1727 ShowWindow(hwnd, SW_RESTORE);
1731 if (strcmp(prev_cfg.logfilename, cfg.logfilename) ||
1732 prev_cfg.logtype != cfg.logtype) {
1733 logfclose(); /* reset logging */
1739 * Flush the line discipline's edit buffer in the
1740 * case where local editing has just been disabled.
1742 ldisc_send(NULL, 0, 0);
1750 /* Give terminal a heads-up on miscellaneous stuff */
1753 /* Screen size changed ? */
1754 if (cfg.height != prev_cfg.height ||
1755 cfg.width != prev_cfg.width ||
1756 cfg.savelines != prev_cfg.savelines ||
1757 cfg.resize_action == RESIZE_FONT ||
1758 (cfg.resize_action == RESIZE_EITHER && IsZoomed(hwnd)) ||
1759 cfg.resize_action == RESIZE_DISABLED)
1760 term_size(cfg.height, cfg.width, cfg.savelines);
1762 /* Enable or disable the scroll bar, etc */
1764 LONG nflg, flag = GetWindowLong(hwnd, GWL_STYLE);
1765 LONG nexflag, exflag =
1766 GetWindowLong(hwnd, GWL_EXSTYLE);
1769 if (cfg.alwaysontop != prev_cfg.alwaysontop) {
1770 if (cfg.alwaysontop) {
1771 nexflag |= WS_EX_TOPMOST;
1772 SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
1773 SWP_NOMOVE | SWP_NOSIZE);
1775 nexflag &= ~(WS_EX_TOPMOST);
1776 SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0,
1777 SWP_NOMOVE | SWP_NOSIZE);
1780 if (cfg.sunken_edge)
1781 nexflag |= WS_EX_CLIENTEDGE;
1783 nexflag &= ~(WS_EX_CLIENTEDGE);
1786 if (is_full_screen() ?
1787 cfg.scrollbar_in_fullscreen : cfg.scrollbar)
1790 nflg &= ~WS_VSCROLL;
1792 if (cfg.resize_action == RESIZE_DISABLED ||
1794 nflg &= ~WS_THICKFRAME;
1796 nflg |= WS_THICKFRAME;
1798 if (cfg.resize_action == RESIZE_DISABLED)
1799 nflg &= ~WS_MAXIMIZEBOX;
1801 nflg |= WS_MAXIMIZEBOX;
1803 if (nflg != flag || nexflag != exflag) {
1805 SetWindowLong(hwnd, GWL_STYLE, nflg);
1806 if (nexflag != exflag)
1807 SetWindowLong(hwnd, GWL_EXSTYLE, nexflag);
1809 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
1810 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1811 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
1819 if (cfg.resize_action == RESIZE_DISABLED && IsZoomed(hwnd)) {
1824 set_title(cfg.wintitle);
1825 if (IsIconic(hwnd)) {
1827 cfg.win_name_always ? window_name :
1831 if (strcmp(cfg.font, prev_cfg.font) != 0 ||
1832 strcmp(cfg.line_codepage, prev_cfg.line_codepage) != 0 ||
1833 cfg.fontisbold != prev_cfg.fontisbold ||
1834 cfg.fontheight != prev_cfg.fontheight ||
1835 cfg.fontcharset != prev_cfg.fontcharset ||
1836 cfg.vtmode != prev_cfg.vtmode ||
1837 cfg.bold_colour != prev_cfg.bold_colour ||
1838 cfg.resize_action == RESIZE_DISABLED ||
1839 cfg.resize_action == RESIZE_EITHER ||
1840 (cfg.resize_action != prev_cfg.resize_action))
1843 InvalidateRect(hwnd, NULL, TRUE);
1844 reset_window(init_lvl);
1845 net_pending_errors();
1858 back->special(TS_AYT);
1859 net_pending_errors();
1862 back->special(TS_BRK);
1863 net_pending_errors();
1866 back->special(TS_SYNCH);
1867 net_pending_errors();
1870 back->special(TS_EC);
1871 net_pending_errors();
1874 back->special(TS_EL);
1875 net_pending_errors();
1878 back->special(TS_GA);
1879 net_pending_errors();
1882 back->special(TS_NOP);
1883 net_pending_errors();
1886 back->special(TS_ABORT);
1887 net_pending_errors();
1890 back->special(TS_AO);
1891 net_pending_errors();
1894 back->special(TS_IP);
1895 net_pending_errors();
1898 back->special(TS_SUSP);
1899 net_pending_errors();
1902 back->special(TS_EOR);
1903 net_pending_errors();
1906 back->special(TS_EOF);
1907 net_pending_errors();
1913 WinHelp(hwnd, help_path,
1914 help_has_contents ? HELP_FINDER : HELP_CONTENTS, 0);
1918 * We get this if the System menu has been activated
1925 * We get this if the System menu has been activated
1926 * using the keyboard. This might happen from within
1927 * TranslateKey, in which case it really wants to be
1928 * followed by a `space' character to actually _bring
1929 * the menu up_ rather than just sitting there in
1930 * `ready to appear' state.
1932 show_mouseptr(1); /* make sure pointer is visible */
1934 PostMessage(hwnd, WM_CHAR, ' ', 0);
1936 case IDM_FULLSCREEN:
1940 if (wParam >= IDM_SAVED_MIN && wParam <= IDM_SAVED_MAX) {
1941 SendMessage(hwnd, WM_SYSCOMMAND, IDM_SAVEDSESS, wParam);
1946 #define X_POS(l) ((int)(short)LOWORD(l))
1947 #define Y_POS(l) ((int)(short)HIWORD(l))
1949 #define TO_CHR_X(x) ((((x)<0 ? (x)-font_width+1 : (x))-offset_width) / font_width)
1950 #define TO_CHR_Y(y) ((((y)<0 ? (y)-font_height+1: (y))-offset_height) / font_height)
1951 case WM_LBUTTONDOWN:
1952 case WM_MBUTTONDOWN:
1953 case WM_RBUTTONDOWN:
1961 case WM_LBUTTONDOWN:
1965 case WM_MBUTTONDOWN:
1966 button = MBT_MIDDLE;
1969 case WM_RBUTTONDOWN:
1978 button = MBT_MIDDLE;
1986 button = press = 0; /* shouldn't happen */
1990 * Special case: in full-screen mode, if the left
1991 * button is clicked in the very top left corner of the
1992 * window, we put up the System menu instead of doing
1995 if (is_full_screen() && press && button == MBT_LEFT &&
1996 X_POS(lParam) == 0 && Y_POS(lParam) == 0) {
1997 SendMessage(hwnd, WM_SYSCOMMAND, SC_MOUSEMENU, 0);
2002 TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam)),
2003 wParam & MK_SHIFT, wParam & MK_CONTROL,
2007 term_mouse(button, MA_RELEASE,
2008 TO_CHR_X(X_POS(lParam)),
2009 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
2010 wParam & MK_CONTROL, is_alt_pressed());
2018 * Add the mouse position and message time to the random
2021 noise_ultralight(lParam);
2023 if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON) &&
2024 GetCapture() == hwnd) {
2026 if (wParam & MK_LBUTTON)
2028 else if (wParam & MK_MBUTTON)
2032 term_mouse(b, MA_DRAG, TO_CHR_X(X_POS(lParam)),
2033 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
2034 wParam & MK_CONTROL, is_alt_pressed());
2037 case WM_NCMOUSEMOVE:
2039 noise_ultralight(lParam);
2041 case WM_IGNORE_CLIP:
2042 ignore_clip = wParam; /* don't panic on DESTROYCLIPBOARD */
2044 case WM_DESTROYCLIPBOARD:
2047 ignore_clip = FALSE;
2053 hdc = BeginPaint(hwnd, &p);
2055 SelectPalette(hdc, pal, TRUE);
2056 RealizePalette(hdc);
2059 (p.rcPaint.left-offset_width)/font_width,
2060 (p.rcPaint.top-offset_height)/font_height,
2061 (p.rcPaint.right-offset_width-1)/font_width,
2062 (p.rcPaint.bottom-offset_height-1)/font_height);
2065 p.rcPaint.left < offset_width ||
2066 p.rcPaint.top < offset_height ||
2067 p.rcPaint.right >= offset_width + font_width*cols ||
2068 p.rcPaint.bottom>= offset_height + font_height*rows)
2070 HBRUSH fillcolour, oldbrush;
2072 fillcolour = CreateSolidBrush (
2073 colours[(ATTR_DEFBG>>ATTR_BGSHIFT)*2]);
2074 oldbrush = SelectObject(hdc, fillcolour);
2075 edge = CreatePen(PS_SOLID, 0,
2076 colours[(ATTR_DEFBG>>ATTR_BGSHIFT)*2]);
2077 oldpen = SelectObject(hdc, edge);
2080 * Jordan Russell reports that this apparently
2081 * ineffectual IntersectClipRect() call masks a
2082 * Windows NT/2K bug causing strange display
2083 * problems when the PuTTY window is taller than
2084 * the primary monitor. It seems harmless enough...
2086 IntersectClipRect(hdc,
2087 p.rcPaint.left, p.rcPaint.top,
2088 p.rcPaint.right, p.rcPaint.bottom);
2090 ExcludeClipRect(hdc,
2091 offset_width, offset_height,
2092 offset_width+font_width*cols,
2093 offset_height+font_height*rows);
2095 Rectangle(hdc, p.rcPaint.left, p.rcPaint.top,
2096 p.rcPaint.right, p.rcPaint.bottom);
2098 // SelectClipRgn(hdc, NULL);
2100 SelectObject(hdc, oldbrush);
2101 DeleteObject(fillcolour);
2102 SelectObject(hdc, oldpen);
2105 SelectObject(hdc, GetStockObject(SYSTEM_FONT));
2106 SelectObject(hdc, GetStockObject(WHITE_PEN));
2112 /* Notice we can get multiple netevents, FD_READ, FD_WRITE etc
2113 * but the only one that's likely to try to overload us is FD_READ.
2114 * This means buffering just one is fine.
2116 if (pending_netevent)
2117 enact_pending_netevent();
2119 pending_netevent = TRUE;
2120 pend_netevent_wParam = wParam;
2121 pend_netevent_lParam = lParam;
2122 if (WSAGETSELECTEVENT(lParam) != FD_READ)
2123 enact_pending_netevent();
2125 time(&last_movement);
2129 CreateCaret(hwnd, caretbm, font_width, font_height);
2131 flash_window(0); /* stop */
2140 caret_x = caret_y = -1; /* ensure caret is replaced next time */
2144 case WM_ENTERSIZEMOVE:
2145 #ifdef RDB_DEBUG_PATCH
2146 debug((27, "WM_ENTERSIZEMOVE"));
2150 need_backend_resize = FALSE;
2152 case WM_EXITSIZEMOVE:
2155 #ifdef RDB_DEBUG_PATCH
2156 debug((27, "WM_EXITSIZEMOVE"));
2158 if (need_backend_resize) {
2159 term_size(cfg.height, cfg.width, cfg.savelines);
2160 InvalidateRect(hwnd, NULL, TRUE);
2165 * This does two jobs:
2166 * 1) Keep the sizetip uptodate
2167 * 2) Make sure the window size is _stepped_ in units of the font size.
2169 if (cfg.resize_action != RESIZE_FONT && !alt_pressed) {
2170 int width, height, w, h, ew, eh;
2171 LPRECT r = (LPRECT) lParam;
2173 if ( !need_backend_resize && cfg.resize_action == RESIZE_EITHER &&
2174 (cfg.height != rows || cfg.width != cols )) {
2176 * Great! It seems that both the terminal size and the
2177 * font size have been changed and the user is now dragging.
2179 * It will now be difficult to get back to the configured
2182 * This would be easier but it seems to be too confusing.
2184 term_size(cfg.height, cfg.width, cfg.savelines);
2187 cfg.height=rows; cfg.width=cols;
2189 InvalidateRect(hwnd, NULL, TRUE);
2190 need_backend_resize = TRUE;
2193 width = r->right - r->left - extra_width;
2194 height = r->bottom - r->top - extra_height;
2195 w = (width + font_width / 2) / font_width;
2198 h = (height + font_height / 2) / font_height;
2201 UpdateSizeTip(hwnd, w, h);
2202 ew = width - w * font_width;
2203 eh = height - h * font_height;
2205 if (wParam == WMSZ_LEFT ||
2206 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
2212 if (wParam == WMSZ_TOP ||
2213 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
2223 int width, height, w, h, rv = 0;
2224 int ex_width = extra_width + (cfg.window_border - offset_width) * 2;
2225 int ex_height = extra_height + (cfg.window_border - offset_height) * 2;
2226 LPRECT r = (LPRECT) lParam;
2228 width = r->right - r->left - ex_width;
2229 height = r->bottom - r->top - ex_height;
2231 w = (width + cols/2)/cols;
2232 h = (height + rows/2)/rows;
2233 if ( r->right != r->left + w*cols + ex_width)
2236 if (wParam == WMSZ_LEFT ||
2237 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
2238 r->left = r->right - w*cols - ex_width;
2240 r->right = r->left + w*cols + ex_width;
2242 if (r->bottom != r->top + h*rows + ex_height)
2245 if (wParam == WMSZ_TOP ||
2246 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
2247 r->top = r->bottom - h*rows - ex_height;
2249 r->bottom = r->top + h*rows + ex_height;
2253 /* break; (never reached) */
2254 case WM_FULLSCR_ON_MAX:
2255 fullscr_on_max = TRUE;
2258 sys_cursor_update();
2261 #ifdef RDB_DEBUG_PATCH
2262 debug((27, "WM_SIZE %s (%d,%d)",
2263 (wParam == SIZE_MINIMIZED) ? "SIZE_MINIMIZED":
2264 (wParam == SIZE_MAXIMIZED) ? "SIZE_MAXIMIZED":
2265 (wParam == SIZE_RESTORED && resizing) ? "to":
2266 (wParam == SIZE_RESTORED) ? "SIZE_RESTORED":
2268 LOWORD(lParam), HIWORD(lParam)));
2270 if (wParam == SIZE_MINIMIZED)
2272 cfg.win_name_always ? window_name : icon_name);
2273 if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
2274 SetWindowText(hwnd, window_name);
2275 if (wParam == SIZE_RESTORED)
2276 clear_full_screen();
2277 if (wParam == SIZE_MAXIMIZED && fullscr_on_max) {
2278 fullscr_on_max = FALSE;
2282 if (cfg.resize_action == RESIZE_DISABLED) {
2283 /* A resize, well it better be a minimize. */
2287 int width, height, w, h;
2289 width = LOWORD(lParam);
2290 height = HIWORD(lParam);
2293 if (wParam == SIZE_MAXIMIZED && !was_zoomed) {
2297 if (cfg.resize_action == RESIZE_TERM) {
2298 w = width / font_width;
2300 h = height / font_height;
2303 term_size(h, w, cfg.savelines);
2306 } else if (wParam == SIZE_RESTORED && was_zoomed) {
2308 if (cfg.resize_action == RESIZE_TERM)
2309 term_size(prev_rows, prev_cols, cfg.savelines);
2310 if (cfg.resize_action != RESIZE_FONT)
2315 /* This is an unexpected resize, these will normally happen
2316 * if the window is too large. Probably either the user
2317 * selected a huge font or the screen size has changed.
2319 * This is also called with minimize.
2321 else reset_window(-1);
2325 * Don't call back->size in mid-resize. (To prevent
2326 * massive numbers of resize events getting sent
2327 * down the connection during an NT opaque drag.)
2330 if (cfg.resize_action != RESIZE_FONT && !alt_pressed) {
2331 need_backend_resize = TRUE;
2332 w = (width-cfg.window_border*2) / font_width;
2334 h = (height-cfg.window_border*2) / font_height;
2343 sys_cursor_update();
2346 switch (LOWORD(wParam)) {
2360 term_scroll(0, +rows / 2);
2363 term_scroll(0, -rows / 2);
2365 case SB_THUMBPOSITION:
2367 term_scroll(1, HIWORD(wParam));
2371 case WM_PALETTECHANGED:
2372 if ((HWND) wParam != hwnd && pal != NULL) {
2373 HDC hdc = get_ctx();
2375 if (RealizePalette(hdc) > 0)
2381 case WM_QUERYNEWPALETTE:
2383 HDC hdc = get_ctx();
2385 if (RealizePalette(hdc) > 0)
2397 * Add the scan code and keypress timing to the random
2400 noise_ultralight(lParam);
2403 * We don't do TranslateMessage since it disassociates the
2404 * resulting CHAR message from the KEYDOWN that sparked it,
2405 * which we occasionally don't want. Instead, we process
2406 * KEYDOWN, and call the Win32 translator functions so that
2407 * we get the translations under _our_ control.
2410 unsigned char buf[20];
2413 if (wParam == VK_PROCESSKEY) {
2416 m.message = WM_KEYDOWN;
2418 m.lParam = lParam & 0xdfff;
2419 TranslateMessage(&m);
2421 len = TranslateKey(message, wParam, lParam, buf);
2423 return DefWindowProc(hwnd, message, wParam, lParam);
2427 * Interrupt an ongoing paste. I'm not sure
2428 * this is sensible, but for the moment it's
2429 * preferable to having to faff about buffering
2435 * We need not bother about stdin backlogs
2436 * here, because in GUI PuTTY we can't do
2437 * anything about it anyway; there's no means
2438 * of asking Windows to hold off on KEYDOWN
2439 * messages. We _have_ to buffer everything
2442 term_seen_key_event();
2443 ldisc_send(buf, len, 1);
2448 net_pending_errors();
2450 case WM_INPUTLANGCHANGE:
2451 /* wParam == Font number */
2452 /* lParam == Locale */
2453 set_input_locale((HKL)lParam);
2454 sys_cursor_update();
2457 if(wParam == IMN_SETOPENSTATUS) {
2458 HIMC hImc = ImmGetContext(hwnd);
2459 ImmSetCompositionFont(hImc, &lfont);
2460 ImmReleaseContext(hwnd, hImc);
2464 case WM_IME_COMPOSITION:
2470 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ||
2471 osVersion.dwPlatformId == VER_PLATFORM_WIN32s) break; /* no Unicode */
2473 if ((lParam & GCS_RESULTSTR) == 0) /* Composition unfinished. */
2474 break; /* fall back to DefWindowProc */
2476 hIMC = ImmGetContext(hwnd);
2477 n = ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, NULL, 0);
2481 buff = (char*) smalloc(n);
2482 ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, buff, n);
2484 * Jaeyoun Chung reports that Korean character
2485 * input doesn't work correctly if we do a single
2486 * luni_send() covering the whole of buff. So
2487 * instead we luni_send the characters one by one.
2489 term_seen_key_event();
2490 for (i = 0; i < n; i += 2) {
2491 luni_send((unsigned short *)(buff+i), 1, 1);
2495 ImmReleaseContext(hwnd, hIMC);
2500 if (wParam & 0xFF00) {
2501 unsigned char buf[2];
2504 buf[0] = wParam >> 8;
2505 term_seen_key_event();
2506 lpage_send(kbd_codepage, buf, 2, 1);
2508 char c = (unsigned char) wParam;
2509 term_seen_key_event();
2510 lpage_send(kbd_codepage, &c, 1, 1);
2516 * Nevertheless, we are prepared to deal with WM_CHAR
2517 * messages, should they crop up. So if someone wants to
2518 * post the things to us as part of a macro manoeuvre,
2519 * we're ready to cope.
2522 char c = (unsigned char)wParam;
2523 term_seen_key_event();
2524 lpage_send(CP_ACP, &c, 1, 1);
2528 if (send_raw_mouse && LOWORD(lParam) == HTCLIENT) {
2529 SetCursor(LoadCursor(NULL, IDC_ARROW));
2533 if (message == wm_mousewheel || message == WM_MOUSEWHEEL) {
2534 int shift_pressed=0, control_pressed=0, alt_pressed=0;
2536 if (message == WM_MOUSEWHEEL) {
2537 wheel_accumulator += (short)HIWORD(wParam);
2538 shift_pressed=LOWORD(wParam) & MK_SHIFT;
2539 control_pressed=LOWORD(wParam) & MK_CONTROL;
2542 wheel_accumulator += (int)wParam;
2543 if (GetKeyboardState(keys)!=0) {
2544 shift_pressed=keys[VK_SHIFT]&0x80;
2545 control_pressed=keys[VK_CONTROL]&0x80;
2549 /* process events when the threshold is reached */
2550 while (abs(wheel_accumulator) >= WHEEL_DELTA) {
2553 /* reduce amount for next time */
2554 if (wheel_accumulator > 0) {
2556 wheel_accumulator -= WHEEL_DELTA;
2557 } else if (wheel_accumulator < 0) {
2559 wheel_accumulator += WHEEL_DELTA;
2563 if (send_raw_mouse &&
2564 !(cfg.mouse_override && shift_pressed)) {
2565 /* send a mouse-down followed by a mouse up */
2568 TO_CHR_X(X_POS(lParam)),
2569 TO_CHR_Y(Y_POS(lParam)), shift_pressed,
2570 control_pressed, is_alt_pressed());
2571 term_mouse(b, MA_RELEASE, TO_CHR_X(X_POS(lParam)),
2572 TO_CHR_Y(Y_POS(lParam)), shift_pressed,
2573 control_pressed, is_alt_pressed());
2575 /* trigger a scroll */
2577 b == MBT_WHEEL_UP ? -rows / 2 : rows / 2);
2584 return DefWindowProc(hwnd, message, wParam, lParam);
2588 * Move the system caret. (We maintain one, even though it's
2589 * invisible, for the benefit of blind people: apparently some
2590 * helper software tracks the system caret, so we should arrange to
2593 void sys_cursor(int x, int y)
2597 if (!has_focus) return;
2600 * Avoid gratuitously re-updating the cursor position and IMM
2601 * window if there's no actual change required.
2603 cx = x * font_width + offset_width;
2604 cy = y * font_height + offset_height;
2605 if (cx == caret_x && cy == caret_y)
2610 sys_cursor_update();
2613 static void sys_cursor_update(void)
2618 if (!has_focus) return;
2620 if (caret_x < 0 || caret_y < 0)
2623 SetCaretPos(caret_x, caret_y);
2625 /* IMM calls on Win98 and beyond only */
2626 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32s) return; /* 3.11 */
2628 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS &&
2629 osVersion.dwMinorVersion == 0) return; /* 95 */
2631 /* we should have the IMM functions */
2632 hIMC = ImmGetContext(hwnd);
2633 cf.dwStyle = CFS_POINT;
2634 cf.ptCurrentPos.x = caret_x;
2635 cf.ptCurrentPos.y = caret_y;
2636 ImmSetCompositionWindow(hIMC, &cf);
2638 ImmReleaseContext(hwnd, hIMC);
2642 * Draw a line of text in the window, at given character
2643 * coordinates, in given attributes.
2645 * We are allowed to fiddle with the contents of `text'.
2647 void do_text(Context ctx, int x, int y, char *text, int len,
2648 unsigned long attr, int lattr)
2651 int nfg, nbg, nfont;
2654 int force_manual_underline = 0;
2655 int fnt_width = font_width * (1 + (lattr != LATTR_NORM));
2656 int char_width = fnt_width;
2657 int text_adjust = 0;
2658 static int *IpDx = 0, IpDxLEN = 0;
2660 if (attr & ATTR_WIDE)
2663 if (len > IpDxLEN || IpDx[0] != char_width) {
2665 if (len > IpDxLEN) {
2667 IpDx = smalloc((len + 16) * sizeof(int));
2668 IpDxLEN = (len + 16);
2670 for (i = 0; i < IpDxLEN; i++)
2671 IpDx[i] = char_width;
2674 /* Only want the left half of double width lines */
2675 if (lattr != LATTR_NORM && x*2 >= cols)
2683 if ((attr & TATTR_ACTCURS) && (cfg.cursor_type == 0 || big_cursor)) {
2684 attr &= ATTR_CUR_AND | (bold_mode != BOLD_COLOURS ? ATTR_BOLD : 0);
2685 attr ^= ATTR_CUR_XOR;
2689 if (cfg.vtmode == VT_POORMAN && lattr != LATTR_NORM) {
2690 /* Assume a poorman font is borken in other ways too. */
2700 nfont |= FONT_WIDE + FONT_HIGH;
2703 if (attr & ATTR_NARROW)
2704 nfont |= FONT_NARROW;
2706 /* Special hack for the VT100 linedraw glyphs. */
2707 if ((attr & CSET_MASK) == 0x2300) {
2708 if (text[0] >= (char) 0xBA && text[0] <= (char) 0xBD) {
2709 switch ((unsigned char) (text[0])) {
2711 text_adjust = -2 * font_height / 5;
2714 text_adjust = -1 * font_height / 5;
2717 text_adjust = font_height / 5;
2720 text_adjust = 2 * font_height / 5;
2723 if (lattr == LATTR_TOP || lattr == LATTR_BOT)
2726 text[0] = (char) (unitab_xterm['q'] & CHAR_MASK);
2727 attr |= (unitab_xterm['q'] & CSET_MASK);
2728 if (attr & ATTR_UNDER) {
2729 attr &= ~ATTR_UNDER;
2730 force_manual_underline = 1;
2735 /* Anything left as an original character set is unprintable. */
2736 if (DIRECT_CHAR(attr)) {
2739 memset(text, 0xFD, len);
2743 if ((attr & CSET_MASK) == ATTR_OEMCP)
2746 nfg = 2 * ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
2747 nbg = 2 * ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
2748 if (bold_mode == BOLD_FONT && (attr & ATTR_BOLD))
2750 if (und_mode == UND_FONT && (attr & ATTR_UNDER))
2751 nfont |= FONT_UNDERLINE;
2752 another_font(nfont);
2753 if (!fonts[nfont]) {
2754 if (nfont & FONT_UNDERLINE)
2755 force_manual_underline = 1;
2756 /* Don't do the same for manual bold, it could be bad news. */
2758 nfont &= ~(FONT_BOLD | FONT_UNDERLINE);
2760 another_font(nfont);
2762 nfont = FONT_NORMAL;
2763 if (attr & ATTR_REVERSE) {
2768 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BOLD))
2770 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BLINK))
2774 SelectObject(hdc, fonts[nfont]);
2775 SetTextColor(hdc, fg);
2776 SetBkColor(hdc, bg);
2777 SetBkMode(hdc, OPAQUE);
2780 line_box.right = x + char_width * len;
2781 line_box.bottom = y + font_height;
2783 /* Only want the left half of double width lines */
2784 if (line_box.right > font_width*cols+offset_width)
2785 line_box.right = font_width*cols+offset_width;
2787 /* We're using a private area for direct to font. (512 chars.) */
2788 if (dbcs_screenfont && (attr & CSET_MASK) == ATTR_ACP) {
2789 /* Ho Hum, dbcs fonts are a PITA! */
2790 /* To display on W9x I have to convert to UCS */
2791 static wchar_t *uni_buf = 0;
2792 static int uni_len = 0;
2794 if (len > uni_len) {
2796 uni_buf = smalloc((uni_len = len) * sizeof(wchar_t));
2799 for(nlen = mptr = 0; mptr<len; mptr++) {
2800 uni_buf[nlen] = 0xFFFD;
2801 if (IsDBCSLeadByteEx(font_codepage, (BYTE) text[mptr])) {
2802 IpDx[nlen] += char_width;
2803 MultiByteToWideChar(font_codepage, MB_USEGLYPHCHARS,
2804 text+mptr, 2, uni_buf+nlen, 1);
2809 MultiByteToWideChar(font_codepage, MB_USEGLYPHCHARS,
2810 text+mptr, 1, uni_buf+nlen, 1);
2818 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2819 ETO_CLIPPED | ETO_OPAQUE, &line_box, uni_buf, nlen, IpDx);
2820 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2821 SetBkMode(hdc, TRANSPARENT);
2822 ExtTextOutW(hdc, x - 1,
2823 y - font_height * (lattr ==
2824 LATTR_BOT) + text_adjust,
2825 ETO_CLIPPED, &line_box, uni_buf, nlen, IpDx);
2829 } else if (DIRECT_FONT(attr)) {
2831 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2832 ETO_CLIPPED | ETO_OPAQUE, &line_box, text, len, IpDx);
2833 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2834 SetBkMode(hdc, TRANSPARENT);
2836 /* GRR: This draws the character outside it's box and can leave
2837 * 'droppings' even with the clip box! I suppose I could loop it
2838 * one character at a time ... yuk.
2840 * Or ... I could do a test print with "W", and use +1 or -1 for this
2841 * shift depending on if the leftmost column is blank...
2843 ExtTextOut(hdc, x - 1,
2844 y - font_height * (lattr ==
2845 LATTR_BOT) + text_adjust,
2846 ETO_CLIPPED, &line_box, text, len, IpDx);
2849 /* And 'normal' unicode characters */
2850 static WCHAR *wbuf = NULL;
2851 static int wlen = 0;
2856 wbuf = smalloc(wlen * sizeof(WCHAR));
2858 for (i = 0; i < len; i++)
2859 wbuf[i] = (WCHAR) ((attr & CSET_MASK) + (text[i] & CHAR_MASK));
2862 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2863 ETO_CLIPPED | ETO_OPAQUE, &line_box, wbuf, len, IpDx);
2865 /* And the shadow bold hack. */
2866 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2867 SetBkMode(hdc, TRANSPARENT);
2868 ExtTextOutW(hdc, x - 1,
2869 y - font_height * (lattr ==
2870 LATTR_BOT) + text_adjust,
2871 ETO_CLIPPED, &line_box, wbuf, len, IpDx);
2874 if (lattr != LATTR_TOP && (force_manual_underline ||
2875 (und_mode == UND_LINE
2876 && (attr & ATTR_UNDER)))) {
2879 if (lattr == LATTR_BOT)
2880 dec = dec * 2 - font_height;
2882 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, fg));
2883 MoveToEx(hdc, x, y + dec, NULL);
2884 LineTo(hdc, x + len * char_width, y + dec);
2885 oldpen = SelectObject(hdc, oldpen);
2886 DeleteObject(oldpen);
2890 void do_cursor(Context ctx, int x, int y, char *text, int len,
2891 unsigned long attr, int lattr)
2897 int ctype = cfg.cursor_type;
2899 if ((attr & TATTR_ACTCURS) && (ctype == 0 || big_cursor)) {
2900 if (((attr & CSET_MASK) | (unsigned char) *text) != UCSWIDE) {
2901 do_text(ctx, x, y, text, len, attr, lattr);
2905 attr |= TATTR_RIGHTCURS;
2908 fnt_width = char_width = font_width * (1 + (lattr != LATTR_NORM));
2909 if (attr & ATTR_WIDE)
2916 if ((attr & TATTR_PASCURS) && (ctype == 0 || big_cursor)) {
2919 pts[0].x = pts[1].x = pts[4].x = x;
2920 pts[2].x = pts[3].x = x + char_width - 1;
2921 pts[0].y = pts[3].y = pts[4].y = y;
2922 pts[1].y = pts[2].y = y + font_height - 1;
2923 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2924 Polyline(hdc, pts, 5);
2925 oldpen = SelectObject(hdc, oldpen);
2926 DeleteObject(oldpen);
2927 } else if ((attr & (TATTR_ACTCURS | TATTR_PASCURS)) && ctype != 0) {
2928 int startx, starty, dx, dy, length, i;
2931 starty = y + descent;
2934 length = char_width;
2937 if (attr & TATTR_RIGHTCURS)
2938 xadjust = char_width - 1;
2939 startx = x + xadjust;
2943 length = font_height;
2945 if (attr & TATTR_ACTCURS) {
2948 SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2949 MoveToEx(hdc, startx, starty, NULL);
2950 LineTo(hdc, startx + dx * length, starty + dy * length);
2951 oldpen = SelectObject(hdc, oldpen);
2952 DeleteObject(oldpen);
2954 for (i = 0; i < length; i++) {
2956 SetPixel(hdc, startx, starty, colours[23]);
2965 /* This function gets the actual width of a character in the normal font.
2967 int CharWidth(Context ctx, int uc) {
2971 /* If the font max is the same as the font ave width then this
2972 * function is a no-op.
2974 if (!font_dualwidth) return 1;
2976 switch (uc & CSET_MASK) {
2978 uc = unitab_line[uc & 0xFF];
2981 uc = unitab_xterm[uc & 0xFF];
2984 uc = unitab_scoacs[uc & 0xFF];
2987 if (DIRECT_FONT(uc)) {
2988 if (dbcs_screenfont) return 1;
2990 /* Speedup, I know of no font where ascii is the wrong width */
2991 if ((uc&CHAR_MASK) >= ' ' && (uc&CHAR_MASK)<= '~')
2994 if ( (uc & CSET_MASK) == ATTR_ACP ) {
2995 SelectObject(hdc, fonts[FONT_NORMAL]);
2996 } else if ( (uc & CSET_MASK) == ATTR_OEMCP ) {
2997 another_font(FONT_OEM);
2998 if (!fonts[FONT_OEM]) return 0;
3000 SelectObject(hdc, fonts[FONT_OEM]);
3004 if ( GetCharWidth32(hdc, uc&CHAR_MASK, uc&CHAR_MASK, &ibuf) != 1 &&
3005 GetCharWidth(hdc, uc&CHAR_MASK, uc&CHAR_MASK, &ibuf) != 1)
3008 /* Speedup, I know of no font where ascii is the wrong width */
3009 if (uc >= ' ' && uc <= '~') return 1;
3011 SelectObject(hdc, fonts[FONT_NORMAL]);
3012 if ( GetCharWidth32W(hdc, uc, uc, &ibuf) == 1 )
3013 /* Okay that one worked */ ;
3014 else if ( GetCharWidthW(hdc, uc, uc, &ibuf) == 1 )
3015 /* This should work on 9x too, but it's "less accurate" */ ;
3020 ibuf += font_width / 2 -1;
3027 * Translate a WM_(SYS)?KEY(UP|DOWN) message into a string of ASCII
3028 * codes. Returns number of bytes used or zero to drop the message
3029 * or -1 to forward the message to windows.
3031 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
3032 unsigned char *output)
3035 int scan, left_alt = 0, key_down, shift_state;
3037 unsigned char *p = output;
3038 static int alt_sum = 0;
3040 HKL kbd_layout = GetKeyboardLayout(0);
3042 static WORD keys[3];
3043 static int compose_char = 0;
3044 static WPARAM compose_key = 0;
3046 r = GetKeyboardState(keystate);
3048 memset(keystate, 0, sizeof(keystate));
3051 #define SHOW_TOASCII_RESULT
3052 { /* Tell us all about key events */
3053 static BYTE oldstate[256];
3054 static int first = 1;
3058 memcpy(oldstate, keystate, sizeof(oldstate));
3061 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT) {
3063 } else if ((HIWORD(lParam) & KF_UP)
3064 && scan == (HIWORD(lParam) & 0xFF)) {
3068 if (wParam >= VK_F1 && wParam <= VK_F20)
3069 debug(("K_F%d", wParam + 1 - VK_F1));
3082 debug(("VK_%02x", wParam));
3084 if (message == WM_SYSKEYDOWN || message == WM_SYSKEYUP)
3086 debug((", S%02x", scan = (HIWORD(lParam) & 0xFF)));
3088 ch = MapVirtualKeyEx(wParam, 2, kbd_layout);
3089 if (ch >= ' ' && ch <= '~')
3090 debug((", '%c'", ch));
3092 debug((", $%02x", ch));
3095 debug((", KB0=%02x", keys[0]));
3097 debug((", KB1=%02x", keys[1]));
3099 debug((", KB2=%02x", keys[2]));
3101 if ((keystate[VK_SHIFT] & 0x80) != 0)
3103 if ((keystate[VK_CONTROL] & 0x80) != 0)
3105 if ((HIWORD(lParam) & KF_EXTENDED))
3107 if ((HIWORD(lParam) & KF_UP))
3111 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT);
3112 else if ((HIWORD(lParam) & KF_UP))
3113 oldstate[wParam & 0xFF] ^= 0x80;
3115 oldstate[wParam & 0xFF] ^= 0x81;
3117 for (ch = 0; ch < 256; ch++)
3118 if (oldstate[ch] != keystate[ch])
3119 debug((", M%02x=%02x", ch, keystate[ch]));
3121 memcpy(oldstate, keystate, sizeof(oldstate));
3125 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED)) {
3126 keystate[VK_RMENU] = keystate[VK_MENU];
3130 /* Nastyness with NUMLock - Shift-NUMLock is left alone though */
3131 if ((cfg.funky_type == 3 ||
3132 (cfg.funky_type <= 1 && app_keypad_keys && !cfg.no_applic_k))
3133 && wParam == VK_NUMLOCK && !(keystate[VK_SHIFT] & 0x80)) {
3135 wParam = VK_EXECUTE;
3137 /* UnToggle NUMLock */
3138 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0)
3139 keystate[VK_NUMLOCK] ^= 1;
3142 /* And write back the 'adjusted' state */
3143 SetKeyboardState(keystate);
3146 /* Disable Auto repeat if required */
3147 if (repeat_off && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT)
3150 if ((HIWORD(lParam) & KF_ALTDOWN) && (keystate[VK_RMENU] & 0x80) == 0)
3153 key_down = ((HIWORD(lParam) & KF_UP) == 0);
3155 /* Make sure Ctrl-ALT is not the same as AltGr for ToAscii unless told. */
3156 if (left_alt && (keystate[VK_CONTROL] & 0x80)) {
3157 if (cfg.ctrlaltkeys)
3158 keystate[VK_MENU] = 0;
3160 keystate[VK_RMENU] = 0x80;
3165 alt_pressed = (left_alt && key_down);
3167 scan = (HIWORD(lParam) & (KF_UP | KF_EXTENDED | 0xFF));
3168 shift_state = ((keystate[VK_SHIFT] & 0x80) != 0)
3169 + ((keystate[VK_CONTROL] & 0x80) != 0) * 2;
3171 /* Note if AltGr was pressed and if it was used as a compose key */
3172 if (!compose_state) {
3173 compose_key = 0x100;
3174 if (cfg.compose_key) {
3175 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED))
3176 compose_key = wParam;
3178 if (wParam == VK_APPS)
3179 compose_key = wParam;
3182 if (wParam == compose_key) {
3183 if (compose_state == 0
3184 && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0) compose_state =
3186 else if (compose_state == 1 && (HIWORD(lParam) & KF_UP))
3190 } else if (compose_state == 1 && wParam != VK_CONTROL)
3193 if (compose_state > 1 && left_alt)
3196 /* Sanitize the number pad if not using a PC NumPad */
3197 if (left_alt || (app_keypad_keys && !cfg.no_applic_k
3198 && cfg.funky_type != 2)
3199 || cfg.funky_type == 3 || cfg.nethack_keypad || compose_state) {
3200 if ((HIWORD(lParam) & KF_EXTENDED) == 0) {
3204 nParam = VK_NUMPAD0;
3207 nParam = VK_NUMPAD1;
3210 nParam = VK_NUMPAD2;
3213 nParam = VK_NUMPAD3;
3216 nParam = VK_NUMPAD4;
3219 nParam = VK_NUMPAD5;
3222 nParam = VK_NUMPAD6;
3225 nParam = VK_NUMPAD7;
3228 nParam = VK_NUMPAD8;
3231 nParam = VK_NUMPAD9;
3234 nParam = VK_DECIMAL;
3238 if (keystate[VK_NUMLOCK] & 1)
3245 /* If a key is pressed and AltGr is not active */
3246 if (key_down && (keystate[VK_RMENU] & 0x80) == 0 && !compose_state) {
3247 /* Okay, prepare for most alts then ... */
3251 /* Lets see if it's a pattern we know all about ... */
3252 if (wParam == VK_PRIOR && shift_state == 1) {
3253 SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0);
3256 if (wParam == VK_NEXT && shift_state == 1) {
3257 SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
3260 if (wParam == VK_INSERT && shift_state == 1) {
3264 if (left_alt && wParam == VK_F4 && cfg.alt_f4) {
3267 if (left_alt && wParam == VK_SPACE && cfg.alt_space) {
3268 SendMessage(hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0);
3271 if (left_alt && wParam == VK_RETURN && cfg.fullscreenonaltenter &&
3272 (cfg.resize_action != RESIZE_DISABLED)) {
3273 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) != KF_REPEAT)
3277 /* Control-Numlock for app-keypad mode switch */
3278 if (wParam == VK_PAUSE && shift_state == 2) {
3279 app_keypad_keys ^= 1;
3283 /* Nethack keypad */
3284 if (cfg.nethack_keypad && !left_alt) {
3287 *p++ = shift_state ? 'B' : 'b';
3290 *p++ = shift_state ? 'J' : 'j';
3293 *p++ = shift_state ? 'N' : 'n';
3296 *p++ = shift_state ? 'H' : 'h';
3299 *p++ = shift_state ? '.' : '.';
3302 *p++ = shift_state ? 'L' : 'l';
3305 *p++ = shift_state ? 'Y' : 'y';
3308 *p++ = shift_state ? 'K' : 'k';
3311 *p++ = shift_state ? 'U' : 'u';
3316 /* Application Keypad */
3320 if (cfg.funky_type == 3 ||
3321 (cfg.funky_type <= 1 &&
3322 app_keypad_keys && !cfg.no_applic_k)) switch (wParam) {
3336 if (app_keypad_keys && !cfg.no_applic_k)
3373 if (cfg.funky_type == 2) {
3378 } else if (shift_state)
3385 if (cfg.funky_type == 2)
3389 if (cfg.funky_type == 2)
3393 if (cfg.funky_type == 2)
3398 if (HIWORD(lParam) & KF_EXTENDED)
3404 if (xkey >= 'P' && xkey <= 'S')
3405 p += sprintf((char *) p, "\x1B%c", xkey);
3407 p += sprintf((char *) p, "\x1B?%c", xkey);
3409 p += sprintf((char *) p, "\x1BO%c", xkey);
3414 if (wParam == VK_BACK && shift_state == 0) { /* Backspace */
3415 *p++ = (cfg.bksp_is_delete ? 0x7F : 0x08);
3419 if (wParam == VK_BACK && shift_state == 1) { /* Shift Backspace */
3420 /* We do the opposite of what is configured */
3421 *p++ = (cfg.bksp_is_delete ? 0x08 : 0x7F);
3425 if (wParam == VK_TAB && shift_state == 1) { /* Shift tab */
3431 if (wParam == VK_SPACE && shift_state == 2) { /* Ctrl-Space */
3435 if (wParam == VK_SPACE && shift_state == 3) { /* Ctrl-Shift-Space */
3439 if (wParam == VK_CANCEL && shift_state == 2) { /* Ctrl-Break */
3444 if (wParam == VK_PAUSE) { /* Break/Pause */
3449 /* Control-2 to Control-8 are special */
3450 if (shift_state == 2 && wParam >= '2' && wParam <= '8') {
3451 *p++ = "\000\033\034\035\036\037\177"[wParam - '2'];
3454 if (shift_state == 2 && wParam == 0xBD) {
3458 if (shift_state == 2 && wParam == 0xDF) {
3462 if (shift_state == 0 && wParam == VK_RETURN && cr_lf_return) {
3469 * Next, all the keys that do tilde codes. (ESC '[' nn '~',
3470 * for integer decimal nn.)
3472 * We also deal with the weird ones here. Linux VCs replace F1
3473 * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
3474 * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
3480 code = (keystate[VK_SHIFT] & 0x80 ? 23 : 11);
3483 code = (keystate[VK_SHIFT] & 0x80 ? 24 : 12);
3486 code = (keystate[VK_SHIFT] & 0x80 ? 25 : 13);
3489 code = (keystate[VK_SHIFT] & 0x80 ? 26 : 14);
3492 code = (keystate[VK_SHIFT] & 0x80 ? 28 : 15);
3495 code = (keystate[VK_SHIFT] & 0x80 ? 29 : 17);
3498 code = (keystate[VK_SHIFT] & 0x80 ? 31 : 18);
3501 code = (keystate[VK_SHIFT] & 0x80 ? 32 : 19);
3504 code = (keystate[VK_SHIFT] & 0x80 ? 33 : 20);
3507 code = (keystate[VK_SHIFT] & 0x80 ? 34 : 21);
3540 if ((shift_state&2) == 0) switch (wParam) {
3560 /* Reorder edit keys to physical order */
3561 if (cfg.funky_type == 3 && code <= 6)
3562 code = "\0\2\1\4\5\3\6"[code];
3564 if (vt52_mode && code > 0 && code <= 6) {
3565 p += sprintf((char *) p, "\x1B%c", " HLMEIG"[code]);
3569 if (cfg.funky_type == 5 && /* SCO function keys */
3570 code >= 11 && code <= 34) {
3571 char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
3574 case VK_F1: index = 0; break;
3575 case VK_F2: index = 1; break;
3576 case VK_F3: index = 2; break;
3577 case VK_F4: index = 3; break;
3578 case VK_F5: index = 4; break;
3579 case VK_F6: index = 5; break;
3580 case VK_F7: index = 6; break;
3581 case VK_F8: index = 7; break;
3582 case VK_F9: index = 8; break;
3583 case VK_F10: index = 9; break;
3584 case VK_F11: index = 10; break;
3585 case VK_F12: index = 11; break;
3587 if (keystate[VK_SHIFT] & 0x80) index += 12;
3588 if (keystate[VK_CONTROL] & 0x80) index += 24;
3589 p += sprintf((char *) p, "\x1B[%c", codes[index]);
3592 if (cfg.funky_type == 5 && /* SCO small keypad */
3593 code >= 1 && code <= 6) {
3594 char codes[] = "HL.FIG";
3598 p += sprintf((char *) p, "\x1B[%c", codes[code-1]);
3602 if ((vt52_mode || cfg.funky_type == 4) && code >= 11 && code <= 24) {
3609 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11 - offt);
3612 sprintf((char *) p, "\x1BO%c", code + 'P' - 11 - offt);
3615 if (cfg.funky_type == 1 && code >= 11 && code <= 15) {
3616 p += sprintf((char *) p, "\x1B[[%c", code + 'A' - 11);
3619 if (cfg.funky_type == 2 && code >= 11 && code <= 14) {
3621 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11);
3623 p += sprintf((char *) p, "\x1BO%c", code + 'P' - 11);
3626 if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
3627 p += sprintf((char *) p, code == 1 ? "\x1B[H" : "\x1BOw");
3631 p += sprintf((char *) p, "\x1B[%d~", code);
3636 * Now the remaining keys (arrows and Keypad 5. Keypad 5 for
3637 * some reason seems to send VK_CLEAR to Windows...).
3660 p += sprintf((char *) p, "\x1B%c", xkey);
3662 int app_flg = (app_cursor_keys && !cfg.no_applic_c);
3665 * RDB: VT100 & VT102 manuals both state the
3666 * app cursor keys only work if the app keypad
3669 * SGT: That may well be true, but xterm
3670 * disagrees and so does at least one
3671 * application, so I've #if'ed this out and the
3672 * behaviour is back to PuTTY's original: app
3673 * cursor and app keypad are independently
3674 * switchable modes. If anyone complains about
3675 * _this_ I'll have to put in a configurable
3678 if (!app_keypad_keys)
3681 /* Useful mapping of Ctrl-arrows */
3682 if (shift_state == 2)
3686 p += sprintf((char *) p, "\x1BO%c", xkey);
3688 p += sprintf((char *) p, "\x1B[%c", xkey);
3695 * Finally, deal with Return ourselves. (Win95 seems to
3696 * foul it up when Alt is pressed, for some reason.)
3698 if (wParam == VK_RETURN) { /* Return */
3704 if (left_alt && wParam >= VK_NUMPAD0 && wParam <= VK_NUMPAD9)
3705 alt_sum = alt_sum * 10 + wParam - VK_NUMPAD0;
3710 /* Okay we've done everything interesting; let windows deal with
3711 * the boring stuff */
3715 /* helg: clear CAPS LOCK state if caps lock switches to cyrillic */
3716 if(cfg.xlat_capslockcyr && keystate[VK_CAPITAL] != 0) {
3718 keystate[VK_CAPITAL] = 0;
3721 r = ToAsciiEx(wParam, scan, keystate, keys, 0, kbd_layout);
3722 #ifdef SHOW_TOASCII_RESULT
3723 if (r == 1 && !key_down) {
3725 if (in_utf || dbcs_screenfont)
3726 debug((", (U+%04x)", alt_sum));
3728 debug((", LCH(%d)", alt_sum));
3730 debug((", ACH(%d)", keys[0]));
3735 for (r1 = 0; r1 < r; r1++) {
3736 debug(("%s%d", r1 ? "," : "", keys[r1]));
3745 * Interrupt an ongoing paste. I'm not sure this is
3746 * sensible, but for the moment it's preferable to
3747 * having to faff about buffering things.
3752 for (i = 0; i < r; i++) {
3753 unsigned char ch = (unsigned char) keys[i];
3755 if (compose_state == 2 && (ch & 0x80) == 0 && ch > ' ') {
3760 if (compose_state == 3 && (ch & 0x80) == 0 && ch > ' ') {
3764 if ((nc = check_compose(compose_char, ch)) == -1) {
3765 MessageBeep(MB_ICONHAND);
3769 term_seen_key_event();
3770 luni_send(&keybuf, 1, 1);
3778 if (in_utf || dbcs_screenfont) {
3780 term_seen_key_event();
3781 luni_send(&keybuf, 1, 1);
3783 ch = (char) alt_sum;
3785 * We need not bother about stdin
3786 * backlogs here, because in GUI PuTTY
3787 * we can't do anything about it
3788 * anyway; there's no means of asking
3789 * Windows to hold off on KEYDOWN
3790 * messages. We _have_ to buffer
3791 * everything we're sent.
3793 term_seen_key_event();
3794 ldisc_send(&ch, 1, 1);
3798 term_seen_key_event();
3799 lpage_send(kbd_codepage, &ch, 1, 1);
3801 if(capsOn && ch < 0x80) {
3804 cbuf[1] = xlat_uskbd2cyrllic(ch);
3805 term_seen_key_event();
3806 luni_send(cbuf+!left_alt, 1+!!left_alt, 1);
3811 term_seen_key_event();
3812 lpage_send(kbd_codepage, cbuf+!left_alt, 1+!!left_alt, 1);
3818 /* This is so the ALT-Numpad and dead keys work correctly. */
3823 /* If we're definitly not building up an ALT-54321 then clear it */
3826 /* If we will be using alt_sum fix the 256s */
3827 else if (keys[0] && (in_utf || dbcs_screenfont))
3832 * ALT alone may or may not want to bring up the System menu.
3833 * If it's not meant to, we return 0 on presses or releases of
3834 * ALT, to show that we've swallowed the keystroke. Otherwise
3835 * we return -1, which means Windows will give the keystroke
3836 * its default handling (i.e. bring up the System menu).
3838 if (wParam == VK_MENU && !cfg.alt_only)
3844 void request_paste(void)
3847 * In Windows, pasting is synchronous: we can read the
3848 * clipboard with no difficulty, so request_paste() can just go
3854 void set_title(char *title)
3857 window_name = smalloc(1 + strlen(title));
3858 strcpy(window_name, title);
3859 if (cfg.win_name_always || !IsIconic(hwnd))
3860 SetWindowText(hwnd, title);
3863 void set_icon(char *title)
3866 icon_name = smalloc(1 + strlen(title));
3867 strcpy(icon_name, title);
3868 if (!cfg.win_name_always && IsIconic(hwnd))
3869 SetWindowText(hwnd, title);
3872 void set_sbar(int total, int start, int page)
3876 if (is_full_screen() ? !cfg.scrollbar_in_fullscreen : !cfg.scrollbar)
3879 si.cbSize = sizeof(si);
3880 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
3882 si.nMax = total - 1;
3886 SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
3889 Context get_ctx(void)
3895 SelectPalette(hdc, pal, FALSE);
3901 void free_ctx(Context ctx)
3903 SelectPalette(ctx, GetStockObject(DEFAULT_PALETTE), FALSE);
3904 ReleaseDC(hwnd, ctx);
3907 static void real_palette_set(int n, int r, int g, int b)
3910 logpal->palPalEntry[n].peRed = r;
3911 logpal->palPalEntry[n].peGreen = g;
3912 logpal->palPalEntry[n].peBlue = b;
3913 logpal->palPalEntry[n].peFlags = PC_NOCOLLAPSE;
3914 colours[n] = PALETTERGB(r, g, b);
3915 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3917 colours[n] = RGB(r, g, b);
3920 void palette_set(int n, int r, int g, int b)
3922 static const int first[21] = {
3923 0, 2, 4, 6, 8, 10, 12, 14,
3924 1, 3, 5, 7, 9, 11, 13, 15,
3927 real_palette_set(first[n], r, g, b);
3929 real_palette_set(first[n] + 1, r, g, b);
3931 HDC hdc = get_ctx();
3932 UnrealizeObject(pal);
3933 RealizePalette(hdc);
3938 void palette_reset(void)
3942 for (i = 0; i < NCOLOURS; i++) {
3944 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
3945 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
3946 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
3947 logpal->palPalEntry[i].peFlags = 0;
3948 colours[i] = PALETTERGB(defpal[i].rgbtRed,
3949 defpal[i].rgbtGreen,
3950 defpal[i].rgbtBlue);
3952 colours[i] = RGB(defpal[i].rgbtRed,
3953 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
3958 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3960 RealizePalette(hdc);
3965 void write_aclip(char *data, int len, int must_deselect)
3970 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len + 1);
3973 lock = GlobalLock(clipdata);
3976 memcpy(lock, data, len);
3977 ((unsigned char *) lock)[len] = 0;
3978 GlobalUnlock(clipdata);
3981 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
3983 if (OpenClipboard(hwnd)) {
3985 SetClipboardData(CF_TEXT, clipdata);
3988 GlobalFree(clipdata);
3991 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
3995 * Note: unlike write_aclip() this will not append a nul.
3997 void write_clip(wchar_t * data, int len, int must_deselect)
3999 HGLOBAL clipdata, clipdata2, clipdata3;
4001 void *lock, *lock2, *lock3;
4003 len2 = WideCharToMultiByte(CP_ACP, 0, data, len, 0, 0, NULL, NULL);
4005 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE,
4006 len * sizeof(wchar_t));
4007 clipdata2 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len2);
4009 if (!clipdata || !clipdata2) {
4011 GlobalFree(clipdata);
4013 GlobalFree(clipdata2);
4016 if (!(lock = GlobalLock(clipdata)))
4018 if (!(lock2 = GlobalLock(clipdata2)))
4021 memcpy(lock, data, len * sizeof(wchar_t));
4022 WideCharToMultiByte(CP_ACP, 0, data, len, lock2, len2, NULL, NULL);
4024 if (cfg.rtf_paste) {
4025 wchar_t unitab[256];
4027 unsigned char *tdata = (unsigned char *)lock2;
4028 wchar_t *udata = (wchar_t *)lock;
4029 int rtflen = 0, uindex = 0, tindex = 0;
4031 int multilen, blen, alen, totallen, i;
4032 char before[16], after[4];
4034 get_unitab(CP_ACP, unitab, 0);
4036 rtfsize = 100 + strlen(cfg.font);
4037 rtf = smalloc(rtfsize);
4038 sprintf(rtf, "{\\rtf1\\ansi%d{\\fonttbl\\f0\\fmodern %s;}\\f0",
4039 GetACP(), cfg.font);
4040 rtflen = strlen(rtf);
4043 * We want to construct a piece of RTF that specifies the
4044 * same Unicode text. To do this we will read back in
4045 * parallel from the Unicode data in `udata' and the
4046 * non-Unicode data in `tdata'. For each character in
4047 * `tdata' which becomes the right thing in `udata' when
4048 * looked up in `unitab', we just copy straight over from
4049 * tdata. For each one that doesn't, we must WCToMB it
4050 * individually and produce a \u escape sequence.
4052 * It would probably be more robust to just bite the bullet
4053 * and WCToMB each individual Unicode character one by one,
4054 * then MBToWC each one back to see if it was an accurate
4055 * translation; but that strikes me as a horrifying number
4056 * of Windows API calls so I want to see if this faster way
4057 * will work. If it screws up badly we can always revert to
4058 * the simple and slow way.
4060 while (tindex < len2 && uindex < len &&
4061 tdata[tindex] && udata[uindex]) {
4062 if (tindex + 1 < len2 &&
4063 tdata[tindex] == '\r' &&
4064 tdata[tindex+1] == '\n') {
4068 if (unitab[tdata[tindex]] == udata[uindex]) {
4074 multilen = WideCharToMultiByte(CP_ACP, 0, unitab+uindex, 1,
4075 NULL, 0, NULL, NULL);
4076 if (multilen != 1) {
4077 blen = sprintf(before, "{\\uc%d\\u%d", multilen,
4079 alen = 1; strcpy(after, "}");
4081 blen = sprintf(before, "\\u%d", udata[uindex]);
4082 alen = 0; after[0] = '\0';
4085 assert(tindex + multilen <= len2);
4086 totallen = blen + alen;
4087 for (i = 0; i < multilen; i++) {
4088 if (tdata[tindex+i] == '\\' ||
4089 tdata[tindex+i] == '{' ||
4090 tdata[tindex+i] == '}')
4092 else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A)
4093 totallen += 6; /* \par\r\n */
4094 else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20)
4100 if (rtfsize < rtflen + totallen + 3) {
4101 rtfsize = rtflen + totallen + 512;
4102 rtf = srealloc(rtf, rtfsize);
4105 strcpy(rtf + rtflen, before); rtflen += blen;
4106 for (i = 0; i < multilen; i++) {
4107 if (tdata[tindex+i] == '\\' ||
4108 tdata[tindex+i] == '{' ||
4109 tdata[tindex+i] == '}') {
4110 rtf[rtflen++] = '\\';
4111 rtf[rtflen++] = tdata[tindex+i];
4112 } else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A) {
4113 rtflen += sprintf(rtf+rtflen, "\\par\r\n");
4114 } else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20) {
4115 rtflen += sprintf(rtf+rtflen, "\\'%02x", tdata[tindex+i]);
4117 rtf[rtflen++] = tdata[tindex+i];
4120 strcpy(rtf + rtflen, after); rtflen += alen;
4126 strcpy(rtf + rtflen, "}");
4129 clipdata3 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, rtflen);
4130 if (clipdata3 && (lock3 = GlobalLock(clipdata3)) != NULL) {
4132 GlobalUnlock(clipdata3);
4138 GlobalUnlock(clipdata);
4139 GlobalUnlock(clipdata2);
4142 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
4144 if (OpenClipboard(hwnd)) {
4146 SetClipboardData(CF_UNICODETEXT, clipdata);
4147 SetClipboardData(CF_TEXT, clipdata2);
4149 SetClipboardData(RegisterClipboardFormat(CF_RTF), clipdata3);
4152 GlobalFree(clipdata);
4153 GlobalFree(clipdata2);
4157 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
4160 void get_clip(wchar_t ** p, int *len)
4162 static HGLOBAL clipdata = NULL;
4163 static wchar_t *converted = 0;
4172 GlobalUnlock(clipdata);
4175 } else if (OpenClipboard(NULL)) {
4176 if ((clipdata = GetClipboardData(CF_UNICODETEXT))) {
4178 *p = GlobalLock(clipdata);
4180 for (p2 = *p; *p2; p2++);
4184 } else if ( (clipdata = GetClipboardData(CF_TEXT)) ) {
4188 s = GlobalLock(clipdata);
4189 i = MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, 0, 0);
4190 *p = converted = smalloc(i * sizeof(wchar_t));
4191 MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, converted, i);
4204 * Move `lines' lines from position `from' to position `to' in the
4207 void optimised_move(int to, int from, int lines)
4212 min = (to < from ? to : from);
4213 max = to + from - min;
4215 r.left = offset_width;
4216 r.right = offset_width + cols * font_width;
4217 r.top = offset_height + min * font_height;
4218 r.bottom = offset_height + (max + lines) * font_height;
4219 ScrollWindow(hwnd, 0, (to - from) * font_height, &r, &r);
4224 * Print a message box and perform a fatal exit.
4226 void fatalbox(char *fmt, ...)
4232 vsprintf(stuff, fmt, ap);
4234 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
4239 * Print a modal (Really Bad) message box and perform a fatal exit.
4241 void modalfatalbox(char *fmt, ...)
4247 vsprintf(stuff, fmt, ap);
4249 MessageBox(hwnd, stuff, "PuTTY Fatal Error",
4250 MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
4255 * Manage window caption / taskbar flashing, if enabled.
4256 * 0 = stop, 1 = maintain, 2 = start
4258 static void flash_window(int mode)
4260 static long last_flash = 0;
4261 static int flashing = 0;
4262 if ((mode == 0) || (cfg.beep_ind == B_IND_DISABLED)) {
4265 FlashWindow(hwnd, FALSE);
4269 } else if (mode == 2) {
4272 last_flash = GetTickCount();
4274 FlashWindow(hwnd, TRUE);
4277 } else if ((mode == 1) && (cfg.beep_ind == B_IND_FLASH)) {
4280 long now = GetTickCount();
4281 long fdiff = now - last_flash;
4282 if (fdiff < 0 || fdiff > 450) {
4284 FlashWindow(hwnd, TRUE); /* toggle */
4295 if (mode == BELL_DEFAULT) {
4297 * For MessageBeep style bells, we want to be careful of
4298 * timing, because they don't have the nice property of
4299 * PlaySound bells that each one cancels the previous
4300 * active one. So we limit the rate to one per 50ms or so.
4302 static long lastbeep = 0;
4305 beepdiff = GetTickCount() - lastbeep;
4306 if (beepdiff >= 0 && beepdiff < 50)
4310 * The above MessageBeep call takes time, so we record the
4311 * time _after_ it finishes rather than before it starts.
4313 lastbeep = GetTickCount();
4314 } else if (mode == BELL_WAVEFILE) {
4315 if (!PlaySound(cfg.bell_wavefile, NULL, SND_ASYNC | SND_FILENAME)) {
4316 char buf[sizeof(cfg.bell_wavefile) + 80];
4317 sprintf(buf, "Unable to play sound file\n%s\n"
4318 "Using default sound instead", cfg.bell_wavefile);
4319 MessageBox(hwnd, buf, "PuTTY Sound Error",
4320 MB_OK | MB_ICONEXCLAMATION);
4321 cfg.beep = BELL_DEFAULT;
4324 /* Otherwise, either visual bell or disabled; do nothing here */
4326 flash_window(2); /* start */
4331 * Minimise or restore the window in response to a server-side
4334 void set_iconic(int iconic)
4336 if (IsIconic(hwnd)) {
4338 ShowWindow(hwnd, SW_RESTORE);
4341 ShowWindow(hwnd, SW_MINIMIZE);
4346 * Move the window in response to a server-side request.
4348 void move_window(int x, int y)
4350 if (cfg.resize_action == RESIZE_DISABLED ||
4351 cfg.resize_action == RESIZE_FONT ||
4355 SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
4359 * Move the window to the top or bottom of the z-order in response
4360 * to a server-side request.
4362 void set_zorder(int top)
4364 if (cfg.alwaysontop)
4365 return; /* ignore */
4366 SetWindowPos(hwnd, top ? HWND_TOP : HWND_BOTTOM, 0, 0, 0, 0,
4367 SWP_NOMOVE | SWP_NOSIZE);
4371 * Refresh the window in response to a server-side request.
4373 void refresh_window(void)
4375 InvalidateRect(hwnd, NULL, TRUE);
4379 * Maximise or restore the window in response to a server-side
4382 void set_zoomed(int zoomed)
4384 if (IsZoomed(hwnd)) {
4386 ShowWindow(hwnd, SW_RESTORE);
4389 ShowWindow(hwnd, SW_MAXIMIZE);
4394 * Report whether the window is iconic, for terminal reports.
4398 return IsIconic(hwnd);
4402 * Report the window's position, for terminal reports.
4404 void get_window_pos(int *x, int *y)
4407 GetWindowRect(hwnd, &r);
4413 * Report the window's pixel size, for terminal reports.
4415 void get_window_pixels(int *x, int *y)
4418 GetWindowRect(hwnd, &r);
4419 *x = r.right - r.left;
4420 *y = r.bottom - r.top;
4424 * Return the window or icon title.
4426 char *get_window_title(int icon)
4428 return icon ? icon_name : window_name;
4432 * See if we're in full-screen mode.
4434 int is_full_screen()
4436 if (!IsZoomed(hwnd))
4438 if (GetWindowLong(hwnd, GWL_STYLE) & WS_CAPTION)
4443 /* Get the rect/size of a full screen window using the nearest available
4444 * monitor in multimon systems; default to something sensible if only
4445 * one monitor is present. */
4446 static int get_fullscreen_rect(RECT * ss)
4448 #ifdef MONITOR_DEFAULTTONEAREST
4451 mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
4452 mi.cbSize = sizeof(mi);
4453 GetMonitorInfo(mon, &mi);
4455 /* structure copy */
4459 /* could also use code like this:
4460 ss->left = ss->top = 0;
4461 ss->right = GetSystemMetrics(SM_CXSCREEN);
4462 ss->bottom = GetSystemMetrics(SM_CYSCREEN);
4464 return GetClientRect(GetDesktopWindow(), ss);
4470 * Go full-screen. This should only be called when we are already
4473 void make_full_screen()
4478 assert(IsZoomed(hwnd));
4480 if (is_full_screen())
4483 /* Remove the window furniture. */
4484 style = GetWindowLong(hwnd, GWL_STYLE);
4485 style &= ~(WS_CAPTION | WS_BORDER | WS_THICKFRAME);
4486 if (cfg.scrollbar_in_fullscreen)
4487 style |= WS_VSCROLL;
4489 style &= ~WS_VSCROLL;
4490 SetWindowLong(hwnd, GWL_STYLE, style);
4492 /* Resize ourselves to exactly cover the nearest monitor. */
4493 get_fullscreen_rect(&ss);
4494 SetWindowPos(hwnd, HWND_TOP, ss.left, ss.top,
4499 /* Tick the menu item in the System menu. */
4500 CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN,
4505 * Clear the full-screen attributes.
4507 void clear_full_screen()
4509 DWORD oldstyle, style;
4511 /* Reinstate the window furniture. */
4512 style = oldstyle = GetWindowLong(hwnd, GWL_STYLE);
4513 style |= WS_CAPTION | WS_BORDER;
4514 if (cfg.resize_action == RESIZE_DISABLED)
4515 style &= ~WS_THICKFRAME;
4517 style |= WS_THICKFRAME;
4519 style |= WS_VSCROLL;
4521 style &= ~WS_VSCROLL;
4522 if (style != oldstyle) {
4523 SetWindowLong(hwnd, GWL_STYLE, style);
4524 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
4525 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
4529 /* Untick the menu item in the System menu. */
4530 CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN,
4535 * Toggle full-screen mode.
4537 void flip_full_screen()
4539 if (is_full_screen()) {
4540 ShowWindow(hwnd, SW_RESTORE);
4541 } else if (IsZoomed(hwnd)) {
4544 SendMessage(hwnd, WM_FULLSCR_ON_MAX, 0, 0);
4545 ShowWindow(hwnd, SW_MAXIMIZE);