16 #define COMPILE_MULTIMON_STUBS
27 #define PUTTY_DO_GLOBALS /* actually _define_ globals */
34 #define IDM_SHOWLOG 0x0010
35 #define IDM_NEWSESS 0x0020
36 #define IDM_DUPSESS 0x0030
37 #define IDM_RECONF 0x0040
38 #define IDM_CLRSB 0x0050
39 #define IDM_RESET 0x0060
40 #define IDM_TEL_AYT 0x0070
41 #define IDM_TEL_BRK 0x0080
42 #define IDM_TEL_SYNCH 0x0090
43 #define IDM_TEL_EC 0x00a0
44 #define IDM_TEL_EL 0x00b0
45 #define IDM_TEL_GA 0x00c0
46 #define IDM_TEL_NOP 0x00d0
47 #define IDM_TEL_ABORT 0x00e0
48 #define IDM_TEL_AO 0x00f0
49 #define IDM_TEL_IP 0x0100
50 #define IDM_TEL_SUSP 0x0110
51 #define IDM_TEL_EOR 0x0120
52 #define IDM_TEL_EOF 0x0130
53 #define IDM_HELP 0x0140
54 #define IDM_ABOUT 0x0150
55 #define IDM_SAVEDSESS 0x0160
56 #define IDM_COPYALL 0x0170
57 #define IDM_FULLSCREEN 0x0180
59 #define IDM_SESSLGP 0x0250 /* log type printable */
60 #define IDM_SESSLGA 0x0260 /* log type all chars */
61 #define IDM_SESSLGE 0x0270 /* log end */
62 #define IDM_SAVED_MIN 0x1000
63 #define IDM_SAVED_MAX 0x2000
65 #define WM_IGNORE_CLIP (WM_XUSER + 2)
66 #define WM_FULLSCR_ON_MAX (WM_XUSER + 3)
68 /* Needed for Chinese support and apparently not always defined. */
70 #define VK_PROCESSKEY 0xE5
73 /* Mouse wheel support. */
75 #define WM_MOUSEWHEEL 0x020A /* not defined in earlier SDKs */
78 #define WHEEL_DELTA 120
81 static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
82 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
83 unsigned char *output);
84 static void cfgtopalette(void);
85 static void init_palette(void);
86 static void init_fonts(int, int);
87 static void another_font(int);
88 static void deinit_fonts(void);
89 static void set_input_locale(HKL);
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 is_shift_pressed(void);
111 static int get_fullscreen_rect(RECT * ss);
113 static time_t last_movement = 0;
115 static int caret_x = -1, caret_y = -1;
117 static int kbd_codepage;
120 static Backend *back;
121 static void *backhandle;
123 static int session_closed;
125 extern struct sesslist sesslist; /* imported from windlg.c */
127 #define FONT_NORMAL 0
129 #define FONT_UNDERLINE 2
130 #define FONT_BOLDUND 3
131 #define FONT_WIDE 0x04
132 #define FONT_HIGH 0x08
133 #define FONT_NARROW 0x10
135 #define FONT_OEM 0x20
136 #define FONT_OEMBOLD 0x21
137 #define FONT_OEMUND 0x22
138 #define FONT_OEMBOLDUND 0x23
140 #define FONT_MAXNO 0x2F
142 static HFONT fonts[FONT_MAXNO];
143 static LOGFONT lfont;
144 static int fontflag[FONT_MAXNO];
146 BOLD_COLOURS, BOLD_SHADOW, BOLD_FONT
154 static COLORREF colours[NCOLOURS];
156 static LPLOGPALETTE logpal;
157 static RGBTRIPLE defpal[NCOLOURS];
161 static HBITMAP caretbm;
163 static int dbltime, lasttime, lastact;
164 static Mouse_Button lastbtn;
166 /* this allows xterm-style mouse handling. */
167 static int send_raw_mouse = 0;
168 static int wheel_accumulator = 0;
170 static char *window_name, *icon_name;
172 static int compose_state = 0;
174 static int wsa_started = 0;
176 static OSVERSIONINFO osVersion;
178 static UINT wm_mousewheel = WM_MOUSEWHEEL;
180 /* Dummy routine, only required in plink. */
181 void ldisc_update(void *frontend, int echo, int edit)
185 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
187 static char appname[] = "PuTTY";
192 int guess_width, guess_height;
195 flags = FLAG_VERBOSE | FLAG_INTERACTIVE;
197 winsock_ver = MAKEWORD(1, 1);
198 if (WSAStartup(winsock_ver, &wsadata)) {
199 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
200 MB_OK | MB_ICONEXCLAMATION);
203 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
204 MessageBox(NULL, "WinSock version is incompatible with 1.1",
205 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
210 /* WISHLIST: maybe allow config tweaking even if winsock not present? */
213 InitCommonControls();
215 /* Ensure a Maximize setting in Explorer doesn't maximise the
220 ZeroMemory(&osVersion, sizeof(osVersion));
221 osVersion.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
222 if (!GetVersionEx ( (OSVERSIONINFO *) &osVersion)) {
223 MessageBox(NULL, "Windows refuses to report a version",
224 "PuTTY Fatal Error", MB_OK | MB_ICONEXCLAMATION);
230 * If we're running a version of Windows that doesn't support
231 * WM_MOUSEWHEEL, find out what message number we should be
234 if (osVersion.dwMajorVersion < 4 ||
235 (osVersion.dwMajorVersion == 4 &&
236 osVersion.dwPlatformId != VER_PLATFORM_WIN32_NT))
237 wm_mousewheel = RegisterWindowMessage("MSWHEEL_ROLLMSG");
240 * See if we can find our Help file.
243 char b[2048], *p, *q, *r;
245 GetModuleFileName(NULL, b, sizeof(b) - 1);
247 p = strrchr(b, '\\');
248 if (p && p >= r) r = p+1;
250 if (q && q >= r) r = q+1;
251 strcpy(r, "putty.hlp");
252 if ( (fp = fopen(b, "r")) != NULL) {
253 help_path = dupstr(b);
257 strcpy(r, "putty.cnt");
258 if ( (fp = fopen(b, "r")) != NULL) {
259 help_has_contents = TRUE;
262 help_has_contents = FALSE;
266 * Process the command line.
272 default_protocol = DEFAULT_PROTOCOL;
273 default_port = DEFAULT_PORT;
274 cfg.logtype = LGTYP_NONE;
276 do_defaults(NULL, &cfg);
281 * Process a couple of command-line options which are more
282 * easily dealt with before the line is broken up into
283 * words. These are the soon-to-be-defunct @sessionname and
284 * the internal-use-only &sharedmemoryhandle, neither of
285 * which are combined with anything else.
287 while (*p && isspace(*p))
291 while (i > 1 && isspace(p[i - 1]))
294 do_defaults(p + 1, &cfg);
295 if (!*cfg.host && !do_config()) {
299 } else if (*p == '&') {
301 * An initial & means we've been given a command line
302 * containing the hex value of a HANDLE for a file
303 * mapping object, which we must then extract as a
308 if (sscanf(p + 1, "%p", &filemap) == 1 &&
309 (cp = MapViewOfFile(filemap, FILE_MAP_READ,
310 0, 0, sizeof(Config))) != NULL) {
313 CloseHandle(filemap);
314 } else if (!do_config()) {
320 * Otherwise, break up the command line and deal with
326 split_into_argv(cmdline, &argc, &argv, NULL);
328 for (i = 0; i < argc; i++) {
332 ret = cmdline_process_param(p, i+1<argc?argv[i+1]:NULL,
335 cmdline_error("option \"%s\" requires an argument", p);
336 } else if (ret == 2) {
337 i++; /* skip next argument */
338 } else if (ret == 1) {
339 continue; /* nothing further needs doing */
340 } else if (!strcmp(p, "-cleanup")) {
342 * `putty -cleanup'. Remove all registry
343 * entries associated with PuTTY, and also find
344 * and delete the random seed file.
347 "This procedure will remove ALL Registry\n"
348 "entries associated with PuTTY, and will\n"
349 "also remove the PuTTY random seed file.\n"
351 "THIS PROCESS WILL DESTROY YOUR SAVED\n"
352 "SESSIONS. Are you really sure you want\n"
355 MB_YESNO | MB_ICONWARNING) == IDYES) {
359 } else if (*p != '-') {
363 * If we already have a host name, treat
364 * this argument as a port number. NB we
365 * have to treat this as a saved -P
366 * argument, so that it will be deferred
367 * until it's a good moment to run it.
369 int ret = cmdline_process_param("-P", p, 1, &cfg);
371 } else if (!strncmp(q, "telnet:", 7)) {
373 * If the hostname starts with "telnet:",
374 * set the protocol to Telnet and process
375 * the string as a Telnet URL.
380 if (q[0] == '/' && q[1] == '/')
382 cfg.protocol = PROT_TELNET;
384 while (*p && *p != ':' && *p != '/')
393 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
394 cfg.host[sizeof(cfg.host) - 1] = '\0';
398 * Otherwise, treat this argument as a host
401 while (*p && !isspace(*p))
405 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
406 cfg.host[sizeof(cfg.host) - 1] = '\0';
410 cmdline_error("unknown option \"%s\"", p);
415 cmdline_run_saved(&cfg);
417 if (!*cfg.host && !do_config()) {
423 * Trim leading whitespace off the hostname if it's there.
426 int space = strspn(cfg.host, " \t");
427 memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
430 /* See if host is of the form user@host */
431 if (cfg.host[0] != '\0') {
432 char *atsign = strchr(cfg.host, '@');
433 /* Make sure we're not overflowing the user field */
435 if (atsign - cfg.host < sizeof cfg.username) {
436 strncpy(cfg.username, cfg.host, atsign - cfg.host);
437 cfg.username[atsign - cfg.host] = '\0';
439 memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
444 * Trim a colon suffix off the hostname if it's there.
446 cfg.host[strcspn(cfg.host, ":")] = '\0';
449 * Remove any remaining whitespace from the hostname.
453 while (cfg.host[p2] != '\0') {
454 if (cfg.host[p2] != ' ' && cfg.host[p2] != '\t') {
455 cfg.host[p1] = cfg.host[p2];
465 * Select protocol. This is farmed out into a table in a
466 * separate file to enable an ssh-free variant.
471 for (i = 0; backends[i].backend != NULL; i++)
472 if (backends[i].protocol == cfg.protocol) {
473 back = backends[i].backend;
477 MessageBox(NULL, "Unsupported protocol number found",
478 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
484 /* Check for invalid Port number (i.e. zero) */
486 MessageBox(NULL, "Invalid Port Number",
487 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
494 wndclass.lpfnWndProc = WndProc;
495 wndclass.cbClsExtra = 0;
496 wndclass.cbWndExtra = 0;
497 wndclass.hInstance = inst;
498 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
499 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
500 wndclass.hbrBackground = NULL;
501 wndclass.lpszMenuName = NULL;
502 wndclass.lpszClassName = appname;
504 RegisterClass(&wndclass);
509 term = term_init(&cfg, NULL);
510 logctx = log_init(NULL);
511 term_provide_logctx(term, logctx);
516 * Guess some defaults for the window size. This all gets
517 * updated later, so we don't really care too much. However, we
518 * do want the font width/height guesses to correspond to a
519 * large font rather than a small one...
526 term_size(term, cfg.height, cfg.width, cfg.savelines);
527 guess_width = extra_width + font_width * term->cols;
528 guess_height = extra_height + font_height * term->rows;
531 get_fullscreen_rect(&r);
532 if (guess_width > r.right - r.left)
533 guess_width = r.right - r.left;
534 if (guess_height > r.bottom - r.top)
535 guess_height = r.bottom - r.top;
539 int winmode = WS_OVERLAPPEDWINDOW | WS_VSCROLL;
542 winmode &= ~(WS_VSCROLL);
543 if (cfg.resize_action == RESIZE_DISABLED)
544 winmode &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
546 exwinmode |= WS_EX_TOPMOST;
548 exwinmode |= WS_EX_CLIENTEDGE;
549 hwnd = CreateWindowEx(exwinmode, appname, appname,
550 winmode, CW_USEDEFAULT, CW_USEDEFAULT,
551 guess_width, guess_height,
552 NULL, NULL, inst, NULL);
556 * Initialise the fonts, simultaneously correcting the guesses
557 * for font_{width,height}.
562 * Correct the guesses for extra_{width,height}.
566 GetWindowRect(hwnd, &wr);
567 GetClientRect(hwnd, &cr);
568 offset_width = offset_height = cfg.window_border;
569 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
570 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
574 * Resize the window, now we know what size we _really_ want it
577 guess_width = extra_width + font_width * term->cols;
578 guess_height = extra_height + font_height * term->rows;
579 SetWindowPos(hwnd, NULL, 0, 0, guess_width, guess_height,
580 SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
583 * Set up a caret bitmap, with no content.
587 int size = (font_width + 15) / 16 * 2 * font_height;
588 bits = smalloc(size);
589 memset(bits, 0, size);
590 caretbm = CreateBitmap(font_width, font_height, 1, 1, bits);
593 CreateCaret(hwnd, caretbm, font_width, font_height);
596 * Initialise the scroll bar.
601 si.cbSize = sizeof(si);
602 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
604 si.nMax = term->rows - 1;
605 si.nPage = term->rows;
607 SetScrollInfo(hwnd, SB_VERT, &si, FALSE);
611 * Start up the telnet connection.
615 char msg[1024], *title;
618 error = back->init((void *)term, &backhandle,
619 cfg.host, cfg.port, &realhost, cfg.tcp_nodelay);
620 back->provide_logctx(backhandle, logctx);
622 sprintf(msg, "Unable to open connection to\n"
623 "%.800s\n" "%s", cfg.host, error);
624 MessageBox(NULL, msg, "PuTTY Error", MB_ICONERROR | MB_OK);
627 window_name = icon_name = NULL;
629 title = cfg.wintitle;
631 sprintf(msg, "%s - PuTTY", realhost);
635 set_title(NULL, title);
636 set_icon(NULL, title);
640 * Connect the terminal to the backend for resize purposes.
642 term_provide_resize_fn(term, back->size, backhandle);
645 * Set up a line discipline.
647 ldisc = ldisc_create(&cfg, term, back, backhandle, NULL);
649 session_closed = FALSE;
652 * Prepare the mouse handler.
654 lastact = MA_NOTHING;
655 lastbtn = MBT_NOTHING;
656 dbltime = GetDoubleClickTime();
659 * Set up the session-control options on the system menu.
662 HMENU m = GetSystemMenu(hwnd, FALSE);
666 AppendMenu(m, MF_SEPARATOR, 0, 0);
667 if (cfg.protocol == PROT_TELNET) {
669 AppendMenu(p, MF_ENABLED, IDM_TEL_AYT, "Are You There");
670 AppendMenu(p, MF_ENABLED, IDM_TEL_BRK, "Break");
671 AppendMenu(p, MF_ENABLED, IDM_TEL_SYNCH, "Synch");
672 AppendMenu(p, MF_SEPARATOR, 0, 0);
673 AppendMenu(p, MF_ENABLED, IDM_TEL_EC, "Erase Character");
674 AppendMenu(p, MF_ENABLED, IDM_TEL_EL, "Erase Line");
675 AppendMenu(p, MF_ENABLED, IDM_TEL_GA, "Go Ahead");
676 AppendMenu(p, MF_ENABLED, IDM_TEL_NOP, "No Operation");
677 AppendMenu(p, MF_SEPARATOR, 0, 0);
678 AppendMenu(p, MF_ENABLED, IDM_TEL_ABORT, "Abort Process");
679 AppendMenu(p, MF_ENABLED, IDM_TEL_AO, "Abort Output");
680 AppendMenu(p, MF_ENABLED, IDM_TEL_IP, "Interrupt Process");
681 AppendMenu(p, MF_ENABLED, IDM_TEL_SUSP, "Suspend Process");
682 AppendMenu(p, MF_SEPARATOR, 0, 0);
683 AppendMenu(p, MF_ENABLED, IDM_TEL_EOR, "End Of Record");
684 AppendMenu(p, MF_ENABLED, IDM_TEL_EOF, "End Of File");
685 AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) p,
687 AppendMenu(m, MF_SEPARATOR, 0, 0);
689 AppendMenu(m, MF_ENABLED, IDM_SHOWLOG, "&Event Log");
690 AppendMenu(m, MF_SEPARATOR, 0, 0);
691 AppendMenu(m, MF_ENABLED, IDM_NEWSESS, "Ne&w Session...");
692 AppendMenu(m, MF_ENABLED, IDM_DUPSESS, "&Duplicate Session");
694 get_sesslist(&sesslist, TRUE);
696 i < ((sesslist.nsessions < 256) ? sesslist.nsessions : 256);
698 AppendMenu(s, MF_ENABLED, IDM_SAVED_MIN + (16 * i),
699 sesslist.sessions[i]);
700 AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) s, "Sa&ved Sessions");
701 AppendMenu(m, MF_ENABLED, IDM_RECONF, "Chan&ge Settings...");
702 AppendMenu(m, MF_SEPARATOR, 0, 0);
703 AppendMenu(m, MF_ENABLED, IDM_COPYALL, "C&opy All to Clipboard");
704 AppendMenu(m, MF_ENABLED, IDM_CLRSB, "C&lear Scrollback");
705 AppendMenu(m, MF_ENABLED, IDM_RESET, "Rese&t Terminal");
706 AppendMenu(m, MF_SEPARATOR, 0, 0);
707 AppendMenu(m, (cfg.resize_action == RESIZE_DISABLED) ?
708 MF_GRAYED : MF_ENABLED, IDM_FULLSCREEN, "&Full Screen");
709 AppendMenu(m, MF_SEPARATOR, 0, 0);
711 AppendMenu(m, MF_ENABLED, IDM_HELP, "&Help");
712 AppendMenu(m, MF_ENABLED, IDM_ABOUT, "&About PuTTY");
716 * Set up the initial input locale.
718 set_input_locale(GetKeyboardLayout(0));
721 * Open the initial log file if there is one.
726 * Finally show the window!
728 ShowWindow(hwnd, show);
729 SetForegroundWindow(hwnd);
732 * Set the palette up.
738 term->has_focus = (GetForegroundWindow() == hwnd);
741 if (GetMessage(&msg, NULL, 0, 0) == 1) {
742 int timer_id = 0, long_timer = 0;
744 while (msg.message != WM_QUIT) {
745 /* Sometimes DispatchMessage calls routines that use their own
746 * GetMessage loop, setup this timer so we get some control back.
748 * Also call term_update() from the timer so that if the host
749 * is sending data flat out we still do redraws.
751 if (timer_id && long_timer) {
752 KillTimer(hwnd, timer_id);
753 long_timer = timer_id = 0;
756 timer_id = SetTimer(hwnd, 1, 20, NULL);
757 if (!(IsWindow(logbox) && IsDialogMessage(logbox, &msg)))
758 DispatchMessage(&msg);
760 /* Make sure we blink everything that needs it. */
763 /* Send the paste buffer if there's anything to send */
766 /* If there's nothing new in the queue then we can do everything
767 * we've delayed, reading the socket, writing, and repainting
770 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
773 if (pending_netevent) {
774 enact_pending_netevent();
776 /* Force the cursor blink on */
779 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
783 /* Okay there is now nothing to do so we make sure the screen is
784 * completely up to date then tell windows to call us in a little
788 KillTimer(hwnd, timer_id);
792 if (GetCapture() != hwnd ||
794 !(cfg.mouse_override && is_shift_pressed())))
799 flash_window(1); /* maintain */
801 /* The messages seem unreliable; especially if we're being tricky */
802 term->has_focus = (GetForegroundWindow() == hwnd);
805 /* Hmm, term_update didn't want to do an update too soon ... */
806 timer_id = SetTimer(hwnd, 1, 50, NULL);
807 else if (!term->has_focus)
808 timer_id = SetTimer(hwnd, 1, 500, NULL);
810 timer_id = SetTimer(hwnd, 1, 100, NULL);
813 /* There's no point rescanning everything in the message queue
814 * so we do an apparently unnecessary wait here
817 if (GetMessage(&msg, NULL, 0, 0) != 1)
822 cleanup_exit(msg.wParam); /* this doesn't return... */
823 return msg.wParam; /* ... but optimiser doesn't know */
829 void cleanup_exit(int code)
842 if (cfg.protocol == PROT_SSH) {
853 * Set up, or shut down, an AsyncSelect. Called from winnet.c.
855 char *do_select(SOCKET skt, int startup)
860 events = (FD_CONNECT | FD_READ | FD_WRITE |
861 FD_OOB | FD_CLOSE | FD_ACCEPT);
866 return "do_select(): internal error (hwnd==NULL)";
867 if (WSAAsyncSelect(skt, hwnd, msg, events) == SOCKET_ERROR) {
868 switch (WSAGetLastError()) {
870 return "Network is down";
872 return "WSAAsyncSelect(): unknown error";
879 * set or clear the "raw mouse message" mode
881 void set_raw_mouse_mode(void *frontend, int activate)
883 activate = activate && !cfg.no_mouse_rep;
884 send_raw_mouse = activate;
885 SetCursor(LoadCursor(NULL, activate ? IDC_ARROW : IDC_IBEAM));
889 * Print a message box and close the connection.
891 void connection_fatal(void *frontend, char *fmt, ...)
897 vsprintf(stuff, fmt, ap);
899 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
900 if (cfg.close_on_exit == COE_ALWAYS)
903 session_closed = TRUE;
904 SetWindowText(hwnd, "PuTTY (inactive)");
909 * Report an error at the command-line parsing stage.
911 void cmdline_error(char *fmt, ...)
917 vsprintf(stuff, fmt, ap);
919 MessageBox(hwnd, stuff, "PuTTY Command Line Error", MB_ICONERROR | MB_OK);
924 * Actually do the job requested by a WM_NETEVENT
926 static void enact_pending_netevent(void)
928 static int reentering = 0;
929 extern int select_result(WPARAM, LPARAM);
933 return; /* don't unpend the pending */
935 pending_netevent = FALSE;
938 ret = select_result(pend_netevent_wParam, pend_netevent_lParam);
941 if (ret == 0 && !session_closed) {
942 /* Abnormal exits will already have set session_closed and taken
943 * appropriate action. */
944 if (cfg.close_on_exit == COE_ALWAYS ||
945 cfg.close_on_exit == COE_NORMAL) PostQuitMessage(0);
947 session_closed = TRUE;
948 SetWindowText(hwnd, "PuTTY (inactive)");
949 MessageBox(hwnd, "Connection closed by remote host",
950 "PuTTY", MB_OK | MB_ICONINFORMATION);
956 * Copy the colour palette from the configuration data into defpal.
957 * This is non-trivial because the colour indices are different.
959 static void cfgtopalette(void)
962 static const int ww[] = {
963 6, 7, 8, 9, 10, 11, 12, 13,
964 14, 15, 16, 17, 18, 19, 20, 21,
965 0, 1, 2, 3, 4, 4, 5, 5
968 for (i = 0; i < 24; i++) {
970 defpal[i].rgbtRed = cfg.colours[w][0];
971 defpal[i].rgbtGreen = cfg.colours[w][1];
972 defpal[i].rgbtBlue = cfg.colours[w][2];
977 * Set up the colour palette.
979 static void init_palette(void)
982 HDC hdc = GetDC(hwnd);
984 if (cfg.try_palette && GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) {
985 logpal = smalloc(sizeof(*logpal)
986 - sizeof(logpal->palPalEntry)
987 + NCOLOURS * sizeof(PALETTEENTRY));
988 logpal->palVersion = 0x300;
989 logpal->palNumEntries = NCOLOURS;
990 for (i = 0; i < NCOLOURS; i++) {
991 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
992 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
993 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
994 logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
996 pal = CreatePalette(logpal);
998 SelectPalette(hdc, pal, FALSE);
1000 SelectPalette(hdc, GetStockObject(DEFAULT_PALETTE), FALSE);
1003 ReleaseDC(hwnd, hdc);
1006 for (i = 0; i < NCOLOURS; i++)
1007 colours[i] = PALETTERGB(defpal[i].rgbtRed,
1008 defpal[i].rgbtGreen,
1009 defpal[i].rgbtBlue);
1011 for (i = 0; i < NCOLOURS; i++)
1012 colours[i] = RGB(defpal[i].rgbtRed,
1013 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
1017 * Initialise all the fonts we will need initially. There may be as many as
1018 * three or as few as one. The other (poentially) twentyone fonts are done
1019 * if/when they are needed.
1023 * - check the font width and height, correcting our guesses if
1026 * - verify that the bold font is the same width as the ordinary
1027 * one, and engage shadow bolding if not.
1029 * - verify that the underlined font is the same width as the
1030 * ordinary one (manual underlining by means of line drawing can
1031 * be done in a pinch).
1033 static void init_fonts(int pick_width, int pick_height)
1040 int fw_dontcare, fw_bold;
1042 for (i = 0; i < FONT_MAXNO; i++)
1045 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
1046 und_mode = UND_FONT;
1048 if (cfg.fontisbold) {
1049 fw_dontcare = FW_BOLD;
1052 fw_dontcare = FW_DONTCARE;
1059 font_height = pick_height;
1061 font_height = cfg.fontheight;
1062 if (font_height > 0) {
1064 -MulDiv(font_height, GetDeviceCaps(hdc, LOGPIXELSY), 72);
1067 font_width = pick_width;
1069 #define f(i,c,w,u) \
1070 fonts[i] = CreateFont (font_height, font_width, 0, 0, w, FALSE, u, FALSE, \
1071 c, OUT_DEFAULT_PRECIS, \
1072 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, \
1073 FIXED_PITCH | FF_DONTCARE, cfg.font)
1075 f(FONT_NORMAL, cfg.fontcharset, fw_dontcare, FALSE);
1077 lfont.lfHeight = font_height;
1078 lfont.lfWidth = font_width;
1079 lfont.lfEscapement = 0;
1080 lfont.lfOrientation = 0;
1081 lfont.lfWeight = fw_dontcare;
1082 lfont.lfItalic = FALSE;
1083 lfont.lfUnderline = FALSE;
1084 lfont.lfStrikeOut = FALSE;
1085 lfont.lfCharSet = cfg.fontcharset;
1086 lfont.lfOutPrecision = OUT_DEFAULT_PRECIS;
1087 lfont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
1088 lfont.lfQuality = DEFAULT_QUALITY;
1089 lfont.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE;
1090 strncpy(lfont.lfFaceName, cfg.font, LF_FACESIZE);
1092 SelectObject(hdc, fonts[FONT_NORMAL]);
1093 GetTextMetrics(hdc, &tm);
1095 if (pick_width == 0 || pick_height == 0) {
1096 font_height = tm.tmHeight;
1097 font_width = tm.tmAveCharWidth;
1099 font_dualwidth = (tm.tmAveCharWidth != tm.tmMaxCharWidth);
1101 #ifdef RDB_DEBUG_PATCH
1102 debug(23, "Primary font H=%d, AW=%d, MW=%d",
1103 tm.tmHeight, tm.tmAveCharWidth, tm.tmMaxCharWidth);
1108 DWORD cset = tm.tmCharSet;
1109 memset(&info, 0xFF, sizeof(info));
1111 /* !!! Yes the next line is right */
1112 if (cset == OEM_CHARSET)
1113 font_codepage = GetOEMCP();
1115 if (TranslateCharsetInfo
1116 ((DWORD *) cset, &info, TCI_SRCCHARSET)) font_codepage =
1121 GetCPInfo(font_codepage, &cpinfo);
1122 dbcs_screenfont = (cpinfo.MaxCharSize > 1);
1125 f(FONT_UNDERLINE, cfg.fontcharset, fw_dontcare, TRUE);
1128 * Some fonts, e.g. 9-pt Courier, draw their underlines
1129 * outside their character cell. We successfully prevent
1130 * screen corruption by clipping the text output, but then
1131 * we lose the underline completely. Here we try to work
1132 * out whether this is such a font, and if it is, we set a
1133 * flag that causes underlines to be drawn by hand.
1135 * Having tried other more sophisticated approaches (such
1136 * as examining the TEXTMETRIC structure or requesting the
1137 * height of a string), I think we'll do this the brute
1138 * force way: we create a small bitmap, draw an underlined
1139 * space on it, and test to see whether any pixels are
1140 * foreground-coloured. (Since we expect the underline to
1141 * go all the way across the character cell, we only search
1142 * down a single column of the bitmap, half way across.)
1146 HBITMAP und_bm, und_oldbm;
1150 und_dc = CreateCompatibleDC(hdc);
1151 und_bm = CreateCompatibleBitmap(hdc, font_width, font_height);
1152 und_oldbm = SelectObject(und_dc, und_bm);
1153 SelectObject(und_dc, fonts[FONT_UNDERLINE]);
1154 SetTextAlign(und_dc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
1155 SetTextColor(und_dc, RGB(255, 255, 255));
1156 SetBkColor(und_dc, RGB(0, 0, 0));
1157 SetBkMode(und_dc, OPAQUE);
1158 ExtTextOut(und_dc, 0, 0, ETO_OPAQUE, NULL, " ", 1, NULL);
1160 for (i = 0; i < font_height; i++) {
1161 c = GetPixel(und_dc, font_width / 2, i);
1162 if (c != RGB(0, 0, 0))
1165 SelectObject(und_dc, und_oldbm);
1166 DeleteObject(und_bm);
1169 und_mode = UND_LINE;
1170 DeleteObject(fonts[FONT_UNDERLINE]);
1171 fonts[FONT_UNDERLINE] = 0;
1175 if (bold_mode == BOLD_FONT) {
1176 f(FONT_BOLD, cfg.fontcharset, fw_bold, FALSE);
1180 descent = tm.tmAscent + 1;
1181 if (descent >= font_height)
1182 descent = font_height - 1;
1184 for (i = 0; i < 3; i++) {
1186 if (SelectObject(hdc, fonts[i]) && GetTextMetrics(hdc, &tm))
1187 fontsize[i] = tm.tmAveCharWidth + 256 * tm.tmHeight;
1194 ReleaseDC(hwnd, hdc);
1196 if (fontsize[FONT_UNDERLINE] != fontsize[FONT_NORMAL]) {
1197 und_mode = UND_LINE;
1198 DeleteObject(fonts[FONT_UNDERLINE]);
1199 fonts[FONT_UNDERLINE] = 0;
1202 if (bold_mode == BOLD_FONT &&
1203 fontsize[FONT_BOLD] != fontsize[FONT_NORMAL]) {
1204 bold_mode = BOLD_SHADOW;
1205 DeleteObject(fonts[FONT_BOLD]);
1206 fonts[FONT_BOLD] = 0;
1208 fontflag[0] = fontflag[1] = fontflag[2] = 1;
1213 static void another_font(int fontno)
1216 int fw_dontcare, fw_bold;
1220 if (fontno < 0 || fontno >= FONT_MAXNO || fontflag[fontno])
1223 basefont = (fontno & ~(FONT_BOLDUND));
1224 if (basefont != fontno && !fontflag[basefont])
1225 another_font(basefont);
1227 if (cfg.fontisbold) {
1228 fw_dontcare = FW_BOLD;
1231 fw_dontcare = FW_DONTCARE;
1235 c = cfg.fontcharset;
1241 if (fontno & FONT_WIDE)
1243 if (fontno & FONT_NARROW)
1245 if (fontno & FONT_OEM)
1247 if (fontno & FONT_BOLD)
1249 if (fontno & FONT_UNDERLINE)
1253 CreateFont(font_height * (1 + !!(fontno & FONT_HIGH)), x, 0, 0, w,
1254 FALSE, u, FALSE, c, OUT_DEFAULT_PRECIS,
1255 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
1256 FIXED_PITCH | FF_DONTCARE, s);
1258 fontflag[fontno] = 1;
1261 static void deinit_fonts(void)
1264 for (i = 0; i < FONT_MAXNO; i++) {
1266 DeleteObject(fonts[i]);
1272 void request_resize(void *frontend, int w, int h)
1276 /* If the window is maximized supress resizing attempts */
1277 if (IsZoomed(hwnd)) {
1278 if (cfg.resize_action == RESIZE_TERM)
1282 if (cfg.resize_action == RESIZE_DISABLED) return;
1283 if (h == term->rows && w == term->cols) return;
1285 /* Sanity checks ... */
1287 static int first_time = 1;
1290 switch (first_time) {
1292 /* Get the size of the screen */
1293 if (get_fullscreen_rect(&ss))
1294 /* first_time = 0 */ ;
1300 /* Make sure the values are sane */
1301 width = (ss.right - ss.left - extra_width) / 4;
1302 height = (ss.bottom - ss.top - extra_height) / 6;
1304 if (w > width || h > height)
1313 term_size(term, h, w, cfg.savelines);
1315 if (cfg.resize_action != RESIZE_FONT && !IsZoomed(hwnd)) {
1316 width = extra_width + font_width * w;
1317 height = extra_height + font_height * h;
1319 SetWindowPos(hwnd, NULL, 0, 0, width, height,
1320 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1321 SWP_NOMOVE | SWP_NOZORDER);
1325 InvalidateRect(hwnd, NULL, TRUE);
1328 static void reset_window(int reinit) {
1330 * This function decides how to resize or redraw when the
1331 * user changes something.
1333 * This function doesn't like to change the terminal size but if the
1334 * font size is locked that may be it's only soluion.
1336 int win_width, win_height;
1339 #ifdef RDB_DEBUG_PATCH
1340 debug((27, "reset_window()"));
1343 /* Current window sizes ... */
1344 GetWindowRect(hwnd, &wr);
1345 GetClientRect(hwnd, &cr);
1347 win_width = cr.right - cr.left;
1348 win_height = cr.bottom - cr.top;
1350 if (cfg.resize_action == RESIZE_DISABLED) reinit = 2;
1352 /* Are we being forced to reload the fonts ? */
1354 #ifdef RDB_DEBUG_PATCH
1355 debug((27, "reset_window() -- Forced deinit"));
1361 /* Oh, looks like we're minimised */
1362 if (win_width == 0 || win_height == 0)
1365 /* Is the window out of position ? */
1367 (offset_width != (win_width-font_width*term->cols)/2 ||
1368 offset_height != (win_height-font_height*term->rows)/2) ){
1369 offset_width = (win_width-font_width*term->cols)/2;
1370 offset_height = (win_height-font_height*term->rows)/2;
1371 InvalidateRect(hwnd, NULL, TRUE);
1372 #ifdef RDB_DEBUG_PATCH
1373 debug((27, "reset_window() -> Reposition terminal"));
1377 if (IsZoomed(hwnd)) {
1378 /* We're fullscreen, this means we must not change the size of
1379 * the window so it's the font size or the terminal itself.
1382 extra_width = wr.right - wr.left - cr.right + cr.left;
1383 extra_height = wr.bottom - wr.top - cr.bottom + cr.top;
1385 if (cfg.resize_action != RESIZE_TERM) {
1386 if ( font_width != win_width/term->cols ||
1387 font_height != win_height/term->rows) {
1389 init_fonts(win_width/term->cols, win_height/term->rows);
1390 offset_width = (win_width-font_width*term->cols)/2;
1391 offset_height = (win_height-font_height*term->rows)/2;
1392 InvalidateRect(hwnd, NULL, TRUE);
1393 #ifdef RDB_DEBUG_PATCH
1394 debug((25, "reset_window() -> Z font resize to (%d, %d)",
1395 font_width, font_height));
1399 if ( font_width != win_width/term->cols ||
1400 font_height != win_height/term->rows) {
1401 /* Our only choice at this point is to change the
1402 * size of the terminal; Oh well.
1404 term_size(term, win_height/font_height, win_width/font_width,
1406 offset_width = (win_width-font_width*term->cols)/2;
1407 offset_height = (win_height-font_height*term->rows)/2;
1408 InvalidateRect(hwnd, NULL, TRUE);
1409 #ifdef RDB_DEBUG_PATCH
1410 debug((27, "reset_window() -> Zoomed term_size"));
1417 /* Hmm, a force re-init means we should ignore the current window
1418 * so we resize to the default font size.
1421 #ifdef RDB_DEBUG_PATCH
1422 debug((27, "reset_window() -> Forced re-init"));
1425 offset_width = offset_height = cfg.window_border;
1426 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
1427 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
1429 if (win_width != font_width*term->cols + offset_width*2 ||
1430 win_height != font_height*term->rows + offset_height*2) {
1432 /* If this is too large windows will resize it to the maximum
1433 * allowed window size, we will then be back in here and resize
1434 * the font or terminal to fit.
1436 SetWindowPos(hwnd, NULL, 0, 0,
1437 font_width*term->cols + extra_width,
1438 font_height*term->rows + extra_height,
1439 SWP_NOMOVE | SWP_NOZORDER);
1442 InvalidateRect(hwnd, NULL, TRUE);
1446 /* Okay the user doesn't want us to change the font so we try the
1447 * window. But that may be too big for the screen which forces us
1448 * to change the terminal.
1450 if ((cfg.resize_action == RESIZE_TERM && reinit<=0) ||
1451 (cfg.resize_action == RESIZE_EITHER && reinit<0) ||
1453 offset_width = offset_height = cfg.window_border;
1454 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
1455 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
1457 if (win_width != font_width*term->cols + offset_width*2 ||
1458 win_height != font_height*term->rows + offset_height*2) {
1463 get_fullscreen_rect(&ss);
1465 width = (ss.right - ss.left - extra_width) / font_width;
1466 height = (ss.bottom - ss.top - extra_height) / font_height;
1469 if ( term->rows > height || term->cols > width ) {
1470 if (cfg.resize_action == RESIZE_EITHER) {
1471 /* Make the font the biggest we can */
1472 if (term->cols > width)
1473 font_width = (ss.right - ss.left - extra_width)
1475 if (term->rows > height)
1476 font_height = (ss.bottom - ss.top - extra_height)
1480 init_fonts(font_width, font_height);
1482 width = (ss.right - ss.left - extra_width) / font_width;
1483 height = (ss.bottom - ss.top - extra_height) / font_height;
1485 if ( height > term->rows ) height = term->rows;
1486 if ( width > term->cols ) width = term->cols;
1487 term_size(term, height, width, cfg.savelines);
1488 #ifdef RDB_DEBUG_PATCH
1489 debug((27, "reset_window() -> term resize to (%d,%d)",
1495 SetWindowPos(hwnd, NULL, 0, 0,
1496 font_width*term->cols + extra_width,
1497 font_height*term->rows + extra_height,
1498 SWP_NOMOVE | SWP_NOZORDER);
1500 InvalidateRect(hwnd, NULL, TRUE);
1501 #ifdef RDB_DEBUG_PATCH
1502 debug((27, "reset_window() -> window resize to (%d,%d)",
1503 font_width*term->cols + extra_width,
1504 font_height*term->rows + extra_height));
1510 /* We're allowed to or must change the font but do we want to ? */
1512 if (font_width != (win_width-cfg.window_border*2)/term->cols ||
1513 font_height != (win_height-cfg.window_border*2)/term->rows) {
1516 init_fonts((win_width-cfg.window_border*2)/term->cols,
1517 (win_height-cfg.window_border*2)/term->rows);
1518 offset_width = (win_width-font_width*term->cols)/2;
1519 offset_height = (win_height-font_height*term->rows)/2;
1521 extra_width = wr.right - wr.left - cr.right + cr.left +offset_width*2;
1522 extra_height = wr.bottom - wr.top - cr.bottom + cr.top+offset_height*2;
1524 InvalidateRect(hwnd, NULL, TRUE);
1525 #ifdef RDB_DEBUG_PATCH
1526 debug((25, "reset_window() -> font resize to (%d,%d)",
1527 font_width, font_height));
1532 static void set_input_locale(HKL kl)
1536 GetLocaleInfo(LOWORD(kl), LOCALE_IDEFAULTANSICODEPAGE,
1537 lbuf, sizeof(lbuf));
1539 kbd_codepage = atoi(lbuf);
1542 static void click(Mouse_Button b, int x, int y, int shift, int ctrl, int alt)
1544 int thistime = GetMessageTime();
1546 if (send_raw_mouse && !(cfg.mouse_override && shift)) {
1547 lastbtn = MBT_NOTHING;
1548 term_mouse(term, b, MA_CLICK, x, y, shift, ctrl, alt);
1552 if (lastbtn == b && thistime - lasttime < dbltime) {
1553 lastact = (lastact == MA_CLICK ? MA_2CLK :
1554 lastact == MA_2CLK ? MA_3CLK :
1555 lastact == MA_3CLK ? MA_CLICK : MA_NOTHING);
1560 if (lastact != MA_NOTHING)
1561 term_mouse(term, b, lastact, x, y, shift, ctrl, alt);
1562 lasttime = thistime;
1566 * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT)
1567 * into a cooked one (SELECT, EXTEND, PASTE).
1569 Mouse_Button translate_button(void *frontend, Mouse_Button button)
1571 if (button == MBT_LEFT)
1573 if (button == MBT_MIDDLE)
1574 return cfg.mouse_is_xterm ? MBT_PASTE : MBT_EXTEND;
1575 if (button == MBT_RIGHT)
1576 return cfg.mouse_is_xterm ? MBT_EXTEND : MBT_PASTE;
1577 return 0; /* shouldn't happen */
1580 static void show_mouseptr(int show)
1582 static int cursor_visible = 1;
1583 if (!cfg.hide_mouseptr) /* override if this feature disabled */
1585 if (cursor_visible && !show)
1587 else if (!cursor_visible && show)
1589 cursor_visible = show;
1592 static int is_alt_pressed(void)
1595 int r = GetKeyboardState(keystate);
1598 if (keystate[VK_MENU] & 0x80)
1600 if (keystate[VK_RMENU] & 0x80)
1605 static int is_shift_pressed(void)
1608 int r = GetKeyboardState(keystate);
1611 if (keystate[VK_SHIFT] & 0x80)
1616 static int resizing;
1618 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1619 WPARAM wParam, LPARAM lParam)
1622 static int ignore_clip = FALSE;
1623 static int need_backend_resize = FALSE;
1624 static int fullscr_on_max = FALSE;
1628 if (pending_netevent)
1629 enact_pending_netevent();
1630 if (GetCapture() != hwnd ||
1631 (send_raw_mouse && !(cfg.mouse_override && is_shift_pressed())))
1637 if (cfg.ping_interval > 0) {
1640 if (now - last_movement > cfg.ping_interval) {
1641 back->special(backhandle, TS_PING);
1642 last_movement = now;
1645 net_pending_errors();
1651 if (!cfg.warn_on_close || session_closed ||
1653 "Are you sure you want to close this session?",
1654 "PuTTY Exit Confirmation",
1655 MB_ICONWARNING | MB_OKCANCEL) == IDOK)
1656 DestroyWindow(hwnd);
1663 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1675 PROCESS_INFORMATION pi;
1676 HANDLE filemap = NULL;
1678 if (wParam == IDM_DUPSESS) {
1680 * Allocate a file-mapping memory chunk for the
1683 SECURITY_ATTRIBUTES sa;
1686 sa.nLength = sizeof(sa);
1687 sa.lpSecurityDescriptor = NULL;
1688 sa.bInheritHandle = TRUE;
1689 filemap = CreateFileMapping((HANDLE) 0xFFFFFFFF,
1692 0, sizeof(Config), NULL);
1694 p = (Config *) MapViewOfFile(filemap,
1696 0, 0, sizeof(Config));
1698 *p = cfg; /* structure copy */
1702 sprintf(c, "putty &%p", filemap);
1704 } else if (wParam == IDM_SAVEDSESS) {
1705 if ((lParam - IDM_SAVED_MIN) / 16 < sesslist.nsessions) {
1707 sesslist.sessions[(lParam - IDM_SAVED_MIN) / 16];
1708 cl = smalloc(16 + strlen(session));
1709 /* 8, but play safe */
1712 /* not a very important failure mode */
1714 sprintf(cl, "putty @%s", session);
1722 GetModuleFileName(NULL, b, sizeof(b) - 1);
1724 si.lpReserved = NULL;
1725 si.lpDesktop = NULL;
1729 si.lpReserved2 = NULL;
1730 CreateProcess(b, cl, NULL, NULL, TRUE,
1731 NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
1734 CloseHandle(filemap);
1744 GetWindowText(hwnd, cfg.wintitle, sizeof(cfg.wintitle));
1747 if (!do_reconfig(hwnd))
1751 /* Disable full-screen if resizing forbidden */
1752 HMENU m = GetSystemMenu (hwnd, FALSE);
1753 EnableMenuItem(m, IDM_FULLSCREEN, MF_BYCOMMAND |
1754 (cfg.resize_action == RESIZE_DISABLED)
1755 ? MF_GRAYED : MF_ENABLED);
1756 /* Gracefully unzoom if necessary */
1757 if (IsZoomed(hwnd) &&
1758 (cfg.resize_action == RESIZE_DISABLED)) {
1759 ShowWindow(hwnd, SW_RESTORE);
1763 if (strcmp(prev_cfg.logfilename, cfg.logfilename) ||
1764 prev_cfg.logtype != cfg.logtype) {
1765 logfclose(logctx); /* reset logging */
1771 * Flush the line discipline's edit buffer in the
1772 * case where local editing has just been disabled.
1774 ldisc_send(ldisc, NULL, 0, 0);
1782 /* Give terminal a heads-up on miscellaneous stuff */
1783 term_reconfig(term);
1785 /* Screen size changed ? */
1786 if (cfg.height != prev_cfg.height ||
1787 cfg.width != prev_cfg.width ||
1788 cfg.savelines != prev_cfg.savelines ||
1789 cfg.resize_action == RESIZE_FONT ||
1790 (cfg.resize_action == RESIZE_EITHER && IsZoomed(hwnd)) ||
1791 cfg.resize_action == RESIZE_DISABLED)
1792 term_size(term, cfg.height, cfg.width, cfg.savelines);
1794 /* Enable or disable the scroll bar, etc */
1796 LONG nflg, flag = GetWindowLong(hwnd, GWL_STYLE);
1797 LONG nexflag, exflag =
1798 GetWindowLong(hwnd, GWL_EXSTYLE);
1801 if (cfg.alwaysontop != prev_cfg.alwaysontop) {
1802 if (cfg.alwaysontop) {
1803 nexflag |= WS_EX_TOPMOST;
1804 SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
1805 SWP_NOMOVE | SWP_NOSIZE);
1807 nexflag &= ~(WS_EX_TOPMOST);
1808 SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0,
1809 SWP_NOMOVE | SWP_NOSIZE);
1812 if (cfg.sunken_edge)
1813 nexflag |= WS_EX_CLIENTEDGE;
1815 nexflag &= ~(WS_EX_CLIENTEDGE);
1818 if (is_full_screen() ?
1819 cfg.scrollbar_in_fullscreen : cfg.scrollbar)
1822 nflg &= ~WS_VSCROLL;
1824 if (cfg.resize_action == RESIZE_DISABLED ||
1826 nflg &= ~WS_THICKFRAME;
1828 nflg |= WS_THICKFRAME;
1830 if (cfg.resize_action == RESIZE_DISABLED)
1831 nflg &= ~WS_MAXIMIZEBOX;
1833 nflg |= WS_MAXIMIZEBOX;
1835 if (nflg != flag || nexflag != exflag) {
1837 SetWindowLong(hwnd, GWL_STYLE, nflg);
1838 if (nexflag != exflag)
1839 SetWindowLong(hwnd, GWL_EXSTYLE, nexflag);
1841 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
1842 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1843 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
1851 if (cfg.resize_action == RESIZE_DISABLED && IsZoomed(hwnd)) {
1856 set_title(NULL, cfg.wintitle);
1857 if (IsIconic(hwnd)) {
1859 cfg.win_name_always ? window_name :
1863 if (strcmp(cfg.font, prev_cfg.font) != 0 ||
1864 strcmp(cfg.line_codepage, prev_cfg.line_codepage) != 0 ||
1865 cfg.fontisbold != prev_cfg.fontisbold ||
1866 cfg.fontheight != prev_cfg.fontheight ||
1867 cfg.fontcharset != prev_cfg.fontcharset ||
1868 cfg.vtmode != prev_cfg.vtmode ||
1869 cfg.bold_colour != prev_cfg.bold_colour ||
1870 cfg.resize_action == RESIZE_DISABLED ||
1871 cfg.resize_action == RESIZE_EITHER ||
1872 (cfg.resize_action != prev_cfg.resize_action))
1875 InvalidateRect(hwnd, NULL, TRUE);
1876 reset_window(init_lvl);
1877 net_pending_errors();
1888 ldisc_send(ldisc, NULL, 0, 0);
1891 back->special(backhandle, TS_AYT);
1892 net_pending_errors();
1895 back->special(backhandle, TS_BRK);
1896 net_pending_errors();
1899 back->special(backhandle, TS_SYNCH);
1900 net_pending_errors();
1903 back->special(backhandle, TS_EC);
1904 net_pending_errors();
1907 back->special(backhandle, TS_EL);
1908 net_pending_errors();
1911 back->special(backhandle, TS_GA);
1912 net_pending_errors();
1915 back->special(backhandle, TS_NOP);
1916 net_pending_errors();
1919 back->special(backhandle, TS_ABORT);
1920 net_pending_errors();
1923 back->special(backhandle, TS_AO);
1924 net_pending_errors();
1927 back->special(backhandle, TS_IP);
1928 net_pending_errors();
1931 back->special(backhandle, TS_SUSP);
1932 net_pending_errors();
1935 back->special(backhandle, TS_EOR);
1936 net_pending_errors();
1939 back->special(backhandle, TS_EOF);
1940 net_pending_errors();
1946 WinHelp(hwnd, help_path,
1947 help_has_contents ? HELP_FINDER : HELP_CONTENTS, 0);
1951 * We get this if the System menu has been activated
1958 * We get this if the System menu has been activated
1959 * using the keyboard. This might happen from within
1960 * TranslateKey, in which case it really wants to be
1961 * followed by a `space' character to actually _bring
1962 * the menu up_ rather than just sitting there in
1963 * `ready to appear' state.
1965 show_mouseptr(1); /* make sure pointer is visible */
1967 PostMessage(hwnd, WM_CHAR, ' ', 0);
1969 case IDM_FULLSCREEN:
1973 if (wParam >= IDM_SAVED_MIN && wParam <= IDM_SAVED_MAX) {
1974 SendMessage(hwnd, WM_SYSCOMMAND, IDM_SAVEDSESS, wParam);
1979 #define X_POS(l) ((int)(short)LOWORD(l))
1980 #define Y_POS(l) ((int)(short)HIWORD(l))
1982 #define TO_CHR_X(x) ((((x)<0 ? (x)-font_width+1 : (x))-offset_width) / font_width)
1983 #define TO_CHR_Y(y) ((((y)<0 ? (y)-font_height+1: (y))-offset_height) / font_height)
1984 case WM_LBUTTONDOWN:
1985 case WM_MBUTTONDOWN:
1986 case WM_RBUTTONDOWN:
1994 case WM_LBUTTONDOWN:
1998 case WM_MBUTTONDOWN:
1999 button = MBT_MIDDLE;
2002 case WM_RBUTTONDOWN:
2011 button = MBT_MIDDLE;
2019 button = press = 0; /* shouldn't happen */
2023 * Special case: in full-screen mode, if the left
2024 * button is clicked in the very top left corner of the
2025 * window, we put up the System menu instead of doing
2028 if (is_full_screen() && press && button == MBT_LEFT &&
2029 X_POS(lParam) == 0 && Y_POS(lParam) == 0) {
2030 SendMessage(hwnd, WM_SYSCOMMAND, SC_MOUSEMENU, 0);
2035 TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam)),
2036 wParam & MK_SHIFT, wParam & MK_CONTROL,
2040 term_mouse(term, button, MA_RELEASE,
2041 TO_CHR_X(X_POS(lParam)),
2042 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
2043 wParam & MK_CONTROL, is_alt_pressed());
2051 * Add the mouse position and message time to the random
2054 noise_ultralight(lParam);
2056 if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON) &&
2057 GetCapture() == hwnd) {
2059 if (wParam & MK_LBUTTON)
2061 else if (wParam & MK_MBUTTON)
2065 term_mouse(term, b, MA_DRAG, TO_CHR_X(X_POS(lParam)),
2066 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
2067 wParam & MK_CONTROL, is_alt_pressed());
2070 case WM_NCMOUSEMOVE:
2072 noise_ultralight(lParam);
2074 case WM_IGNORE_CLIP:
2075 ignore_clip = wParam; /* don't panic on DESTROYCLIPBOARD */
2077 case WM_DESTROYCLIPBOARD:
2079 term_deselect(term);
2080 ignore_clip = FALSE;
2086 hdc = BeginPaint(hwnd, &p);
2088 SelectPalette(hdc, pal, TRUE);
2089 RealizePalette(hdc);
2091 term_paint(term, hdc,
2092 (p.rcPaint.left-offset_width)/font_width,
2093 (p.rcPaint.top-offset_height)/font_height,
2094 (p.rcPaint.right-offset_width-1)/font_width,
2095 (p.rcPaint.bottom-offset_height-1)/font_height,
2099 p.rcPaint.left < offset_width ||
2100 p.rcPaint.top < offset_height ||
2101 p.rcPaint.right >= offset_width + font_width*term->cols ||
2102 p.rcPaint.bottom>= offset_height + font_height*term->rows)
2104 HBRUSH fillcolour, oldbrush;
2106 fillcolour = CreateSolidBrush (
2107 colours[(ATTR_DEFBG>>ATTR_BGSHIFT)*2]);
2108 oldbrush = SelectObject(hdc, fillcolour);
2109 edge = CreatePen(PS_SOLID, 0,
2110 colours[(ATTR_DEFBG>>ATTR_BGSHIFT)*2]);
2111 oldpen = SelectObject(hdc, edge);
2114 * Jordan Russell reports that this apparently
2115 * ineffectual IntersectClipRect() call masks a
2116 * Windows NT/2K bug causing strange display
2117 * problems when the PuTTY window is taller than
2118 * the primary monitor. It seems harmless enough...
2120 IntersectClipRect(hdc,
2121 p.rcPaint.left, p.rcPaint.top,
2122 p.rcPaint.right, p.rcPaint.bottom);
2124 ExcludeClipRect(hdc,
2125 offset_width, offset_height,
2126 offset_width+font_width*term->cols,
2127 offset_height+font_height*term->rows);
2129 Rectangle(hdc, p.rcPaint.left, p.rcPaint.top,
2130 p.rcPaint.right, p.rcPaint.bottom);
2132 // SelectClipRgn(hdc, NULL);
2134 SelectObject(hdc, oldbrush);
2135 DeleteObject(fillcolour);
2136 SelectObject(hdc, oldpen);
2139 SelectObject(hdc, GetStockObject(SYSTEM_FONT));
2140 SelectObject(hdc, GetStockObject(WHITE_PEN));
2146 /* Notice we can get multiple netevents, FD_READ, FD_WRITE etc
2147 * but the only one that's likely to try to overload us is FD_READ.
2148 * This means buffering just one is fine.
2150 if (pending_netevent)
2151 enact_pending_netevent();
2153 pending_netevent = TRUE;
2154 pend_netevent_wParam = wParam;
2155 pend_netevent_lParam = lParam;
2156 if (WSAGETSELECTEVENT(lParam) != FD_READ)
2157 enact_pending_netevent();
2159 time(&last_movement);
2162 term->has_focus = TRUE;
2163 CreateCaret(hwnd, caretbm, font_width, font_height);
2165 flash_window(0); /* stop */
2172 term->has_focus = FALSE;
2174 caret_x = caret_y = -1; /* ensure caret is replaced next time */
2178 case WM_ENTERSIZEMOVE:
2179 #ifdef RDB_DEBUG_PATCH
2180 debug((27, "WM_ENTERSIZEMOVE"));
2184 need_backend_resize = FALSE;
2186 case WM_EXITSIZEMOVE:
2189 #ifdef RDB_DEBUG_PATCH
2190 debug((27, "WM_EXITSIZEMOVE"));
2192 if (need_backend_resize) {
2193 term_size(term, cfg.height, cfg.width, cfg.savelines);
2194 InvalidateRect(hwnd, NULL, TRUE);
2199 * This does two jobs:
2200 * 1) Keep the sizetip uptodate
2201 * 2) Make sure the window size is _stepped_ in units of the font size.
2203 if (cfg.resize_action != RESIZE_FONT && !is_alt_pressed()) {
2204 int width, height, w, h, ew, eh;
2205 LPRECT r = (LPRECT) lParam;
2207 if ( !need_backend_resize && cfg.resize_action == RESIZE_EITHER &&
2208 (cfg.height != term->rows || cfg.width != term->cols )) {
2210 * Great! It seems that both the terminal size and the
2211 * font size have been changed and the user is now dragging.
2213 * It will now be difficult to get back to the configured
2216 * This would be easier but it seems to be too confusing.
2218 term_size(term, cfg.height, cfg.width, cfg.savelines);
2221 cfg.height=term->rows; cfg.width=term->cols;
2223 InvalidateRect(hwnd, NULL, TRUE);
2224 need_backend_resize = TRUE;
2227 width = r->right - r->left - extra_width;
2228 height = r->bottom - r->top - extra_height;
2229 w = (width + font_width / 2) / font_width;
2232 h = (height + font_height / 2) / font_height;
2235 UpdateSizeTip(hwnd, w, h);
2236 ew = width - w * font_width;
2237 eh = height - h * font_height;
2239 if (wParam == WMSZ_LEFT ||
2240 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
2246 if (wParam == WMSZ_TOP ||
2247 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
2257 int width, height, w, h, rv = 0;
2258 int ex_width = extra_width + (cfg.window_border - offset_width) * 2;
2259 int ex_height = extra_height + (cfg.window_border - offset_height) * 2;
2260 LPRECT r = (LPRECT) lParam;
2262 width = r->right - r->left - ex_width;
2263 height = r->bottom - r->top - ex_height;
2265 w = (width + term->cols/2)/term->cols;
2266 h = (height + term->rows/2)/term->rows;
2267 if ( r->right != r->left + w*term->cols + ex_width)
2270 if (wParam == WMSZ_LEFT ||
2271 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
2272 r->left = r->right - w*term->cols - ex_width;
2274 r->right = r->left + w*term->cols + ex_width;
2276 if (r->bottom != r->top + h*term->rows + ex_height)
2279 if (wParam == WMSZ_TOP ||
2280 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
2281 r->top = r->bottom - h*term->rows - ex_height;
2283 r->bottom = r->top + h*term->rows + ex_height;
2287 /* break; (never reached) */
2288 case WM_FULLSCR_ON_MAX:
2289 fullscr_on_max = TRUE;
2292 sys_cursor_update();
2295 #ifdef RDB_DEBUG_PATCH
2296 debug((27, "WM_SIZE %s (%d,%d)",
2297 (wParam == SIZE_MINIMIZED) ? "SIZE_MINIMIZED":
2298 (wParam == SIZE_MAXIMIZED) ? "SIZE_MAXIMIZED":
2299 (wParam == SIZE_RESTORED && resizing) ? "to":
2300 (wParam == SIZE_RESTORED) ? "SIZE_RESTORED":
2302 LOWORD(lParam), HIWORD(lParam)));
2304 if (wParam == SIZE_MINIMIZED)
2306 cfg.win_name_always ? window_name : icon_name);
2307 if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
2308 SetWindowText(hwnd, window_name);
2309 if (wParam == SIZE_RESTORED)
2310 clear_full_screen();
2311 if (wParam == SIZE_MAXIMIZED && fullscr_on_max) {
2312 fullscr_on_max = FALSE;
2316 if (cfg.resize_action == RESIZE_DISABLED) {
2317 /* A resize, well it better be a minimize. */
2321 int width, height, w, h;
2323 width = LOWORD(lParam);
2324 height = HIWORD(lParam);
2327 if (wParam == SIZE_MAXIMIZED && !was_zoomed) {
2329 prev_rows = term->rows;
2330 prev_cols = term->cols;
2331 if (cfg.resize_action == RESIZE_TERM) {
2332 w = width / font_width;
2334 h = height / font_height;
2337 term_size(term, h, w, cfg.savelines);
2340 } else if (wParam == SIZE_RESTORED && was_zoomed) {
2342 if (cfg.resize_action == RESIZE_TERM)
2343 term_size(term, prev_rows, prev_cols, cfg.savelines);
2344 if (cfg.resize_action != RESIZE_FONT)
2349 /* This is an unexpected resize, these will normally happen
2350 * if the window is too large. Probably either the user
2351 * selected a huge font or the screen size has changed.
2353 * This is also called with minimize.
2355 else reset_window(-1);
2359 * Don't call back->size in mid-resize. (To prevent
2360 * massive numbers of resize events getting sent
2361 * down the connection during an NT opaque drag.)
2364 if (cfg.resize_action != RESIZE_FONT && !is_alt_pressed()) {
2365 need_backend_resize = TRUE;
2366 w = (width-cfg.window_border*2) / font_width;
2368 h = (height-cfg.window_border*2) / font_height;
2377 sys_cursor_update();
2380 switch (LOWORD(wParam)) {
2382 term_scroll(term, -1, 0);
2385 term_scroll(term, +1, 0);
2388 term_scroll(term, 0, +1);
2391 term_scroll(term, 0, -1);
2394 term_scroll(term, 0, +term->rows / 2);
2397 term_scroll(term, 0, -term->rows / 2);
2399 case SB_THUMBPOSITION:
2401 term_scroll(term, 1, HIWORD(wParam));
2405 case WM_PALETTECHANGED:
2406 if ((HWND) wParam != hwnd && pal != NULL) {
2407 HDC hdc = get_ctx(NULL);
2409 if (RealizePalette(hdc) > 0)
2415 case WM_QUERYNEWPALETTE:
2417 HDC hdc = get_ctx(NULL);
2419 if (RealizePalette(hdc) > 0)
2431 * Add the scan code and keypress timing to the random
2434 noise_ultralight(lParam);
2437 * We don't do TranslateMessage since it disassociates the
2438 * resulting CHAR message from the KEYDOWN that sparked it,
2439 * which we occasionally don't want. Instead, we process
2440 * KEYDOWN, and call the Win32 translator functions so that
2441 * we get the translations under _our_ control.
2444 unsigned char buf[20];
2447 if (wParam == VK_PROCESSKEY) {
2450 m.message = WM_KEYDOWN;
2452 m.lParam = lParam & 0xdfff;
2453 TranslateMessage(&m);
2455 len = TranslateKey(message, wParam, lParam, buf);
2457 return DefWindowProc(hwnd, message, wParam, lParam);
2461 * Interrupt an ongoing paste. I'm not sure
2462 * this is sensible, but for the moment it's
2463 * preferable to having to faff about buffering
2469 * We need not bother about stdin backlogs
2470 * here, because in GUI PuTTY we can't do
2471 * anything about it anyway; there's no means
2472 * of asking Windows to hold off on KEYDOWN
2473 * messages. We _have_ to buffer everything
2476 term_seen_key_event(term);
2477 ldisc_send(ldisc, buf, len, 1);
2482 net_pending_errors();
2484 case WM_INPUTLANGCHANGE:
2485 /* wParam == Font number */
2486 /* lParam == Locale */
2487 set_input_locale((HKL)lParam);
2488 sys_cursor_update();
2491 if(wParam == IMN_SETOPENSTATUS) {
2492 HIMC hImc = ImmGetContext(hwnd);
2493 ImmSetCompositionFont(hImc, &lfont);
2494 ImmReleaseContext(hwnd, hImc);
2498 case WM_IME_COMPOSITION:
2504 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ||
2505 osVersion.dwPlatformId == VER_PLATFORM_WIN32s) break; /* no Unicode */
2507 if ((lParam & GCS_RESULTSTR) == 0) /* Composition unfinished. */
2508 break; /* fall back to DefWindowProc */
2510 hIMC = ImmGetContext(hwnd);
2511 n = ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, NULL, 0);
2515 buff = (char*) smalloc(n);
2516 ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, buff, n);
2518 * Jaeyoun Chung reports that Korean character
2519 * input doesn't work correctly if we do a single
2520 * luni_send() covering the whole of buff. So
2521 * instead we luni_send the characters one by one.
2523 term_seen_key_event(term);
2524 for (i = 0; i < n; i += 2) {
2525 luni_send(ldisc, (unsigned short *)(buff+i), 1, 1);
2529 ImmReleaseContext(hwnd, hIMC);
2534 if (wParam & 0xFF00) {
2535 unsigned char buf[2];
2538 buf[0] = wParam >> 8;
2539 term_seen_key_event(term);
2540 lpage_send(ldisc, kbd_codepage, buf, 2, 1);
2542 char c = (unsigned char) wParam;
2543 term_seen_key_event(term);
2544 lpage_send(ldisc, kbd_codepage, &c, 1, 1);
2550 * Nevertheless, we are prepared to deal with WM_CHAR
2551 * messages, should they crop up. So if someone wants to
2552 * post the things to us as part of a macro manoeuvre,
2553 * we're ready to cope.
2556 char c = (unsigned char)wParam;
2557 term_seen_key_event(term);
2558 lpage_send(ldisc, CP_ACP, &c, 1, 1);
2562 if (send_raw_mouse && LOWORD(lParam) == HTCLIENT) {
2563 SetCursor(LoadCursor(NULL, IDC_ARROW));
2567 if (message == wm_mousewheel || message == WM_MOUSEWHEEL) {
2568 int shift_pressed=0, control_pressed=0;
2570 if (message == WM_MOUSEWHEEL) {
2571 wheel_accumulator += (short)HIWORD(wParam);
2572 shift_pressed=LOWORD(wParam) & MK_SHIFT;
2573 control_pressed=LOWORD(wParam) & MK_CONTROL;
2576 wheel_accumulator += (int)wParam;
2577 if (GetKeyboardState(keys)!=0) {
2578 shift_pressed=keys[VK_SHIFT]&0x80;
2579 control_pressed=keys[VK_CONTROL]&0x80;
2583 /* process events when the threshold is reached */
2584 while (abs(wheel_accumulator) >= WHEEL_DELTA) {
2587 /* reduce amount for next time */
2588 if (wheel_accumulator > 0) {
2590 wheel_accumulator -= WHEEL_DELTA;
2591 } else if (wheel_accumulator < 0) {
2593 wheel_accumulator += WHEEL_DELTA;
2597 if (send_raw_mouse &&
2598 !(cfg.mouse_override && shift_pressed)) {
2599 /* send a mouse-down followed by a mouse up */
2602 TO_CHR_X(X_POS(lParam)),
2603 TO_CHR_Y(Y_POS(lParam)), shift_pressed,
2604 control_pressed, is_alt_pressed());
2605 term_mouse(term, b, MA_RELEASE, TO_CHR_X(X_POS(lParam)),
2606 TO_CHR_Y(Y_POS(lParam)), shift_pressed,
2607 control_pressed, is_alt_pressed());
2609 /* trigger a scroll */
2610 term_scroll(term, 0,
2612 -term->rows / 2 : term->rows / 2);
2619 return DefWindowProc(hwnd, message, wParam, lParam);
2623 * Move the system caret. (We maintain one, even though it's
2624 * invisible, for the benefit of blind people: apparently some
2625 * helper software tracks the system caret, so we should arrange to
2628 void sys_cursor(void *frontend, int x, int y)
2632 if (!term->has_focus) return;
2635 * Avoid gratuitously re-updating the cursor position and IMM
2636 * window if there's no actual change required.
2638 cx = x * font_width + offset_width;
2639 cy = y * font_height + offset_height;
2640 if (cx == caret_x && cy == caret_y)
2645 sys_cursor_update();
2648 static void sys_cursor_update(void)
2653 if (!term->has_focus) return;
2655 if (caret_x < 0 || caret_y < 0)
2658 SetCaretPos(caret_x, caret_y);
2660 /* IMM calls on Win98 and beyond only */
2661 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32s) return; /* 3.11 */
2663 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS &&
2664 osVersion.dwMinorVersion == 0) return; /* 95 */
2666 /* we should have the IMM functions */
2667 hIMC = ImmGetContext(hwnd);
2668 cf.dwStyle = CFS_POINT;
2669 cf.ptCurrentPos.x = caret_x;
2670 cf.ptCurrentPos.y = caret_y;
2671 ImmSetCompositionWindow(hIMC, &cf);
2673 ImmReleaseContext(hwnd, hIMC);
2677 * Draw a line of text in the window, at given character
2678 * coordinates, in given attributes.
2680 * We are allowed to fiddle with the contents of `text'.
2682 void do_text(Context ctx, int x, int y, char *text, int len,
2683 unsigned long attr, int lattr)
2686 int nfg, nbg, nfont;
2689 int force_manual_underline = 0;
2690 int fnt_width = font_width * (1 + (lattr != LATTR_NORM));
2691 int char_width = fnt_width;
2692 int text_adjust = 0;
2693 static int *IpDx = 0, IpDxLEN = 0;
2695 if (attr & ATTR_WIDE)
2698 if (len > IpDxLEN || IpDx[0] != char_width) {
2700 if (len > IpDxLEN) {
2702 IpDx = smalloc((len + 16) * sizeof(int));
2703 IpDxLEN = (len + 16);
2705 for (i = 0; i < IpDxLEN; i++)
2706 IpDx[i] = char_width;
2709 /* Only want the left half of double width lines */
2710 if (lattr != LATTR_NORM && x*2 >= term->cols)
2718 if ((attr & TATTR_ACTCURS) && (cfg.cursor_type == 0 || term->big_cursor)) {
2719 attr &= ATTR_CUR_AND | (bold_mode != BOLD_COLOURS ? ATTR_BOLD : 0);
2720 attr ^= ATTR_CUR_XOR;
2724 if (cfg.vtmode == VT_POORMAN && lattr != LATTR_NORM) {
2725 /* Assume a poorman font is borken in other ways too. */
2735 nfont |= FONT_WIDE + FONT_HIGH;
2738 if (attr & ATTR_NARROW)
2739 nfont |= FONT_NARROW;
2741 /* Special hack for the VT100 linedraw glyphs. */
2742 if ((attr & CSET_MASK) == 0x2300) {
2743 if (text[0] >= (char) 0xBA && text[0] <= (char) 0xBD) {
2744 switch ((unsigned char) (text[0])) {
2746 text_adjust = -2 * font_height / 5;
2749 text_adjust = -1 * font_height / 5;
2752 text_adjust = font_height / 5;
2755 text_adjust = 2 * font_height / 5;
2758 if (lattr == LATTR_TOP || lattr == LATTR_BOT)
2761 text[0] = (char) (unitab_xterm['q'] & CHAR_MASK);
2762 attr |= (unitab_xterm['q'] & CSET_MASK);
2763 if (attr & ATTR_UNDER) {
2764 attr &= ~ATTR_UNDER;
2765 force_manual_underline = 1;
2770 /* Anything left as an original character set is unprintable. */
2771 if (DIRECT_CHAR(attr)) {
2774 memset(text, 0xFD, len);
2778 if ((attr & CSET_MASK) == ATTR_OEMCP)
2781 nfg = 2 * ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
2782 nbg = 2 * ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
2783 if (bold_mode == BOLD_FONT && (attr & ATTR_BOLD))
2785 if (und_mode == UND_FONT && (attr & ATTR_UNDER))
2786 nfont |= FONT_UNDERLINE;
2787 another_font(nfont);
2788 if (!fonts[nfont]) {
2789 if (nfont & FONT_UNDERLINE)
2790 force_manual_underline = 1;
2791 /* Don't do the same for manual bold, it could be bad news. */
2793 nfont &= ~(FONT_BOLD | FONT_UNDERLINE);
2795 another_font(nfont);
2797 nfont = FONT_NORMAL;
2798 if (attr & ATTR_REVERSE) {
2803 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BOLD))
2805 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BLINK))
2809 SelectObject(hdc, fonts[nfont]);
2810 SetTextColor(hdc, fg);
2811 SetBkColor(hdc, bg);
2812 SetBkMode(hdc, OPAQUE);
2815 line_box.right = x + char_width * len;
2816 line_box.bottom = y + font_height;
2818 /* Only want the left half of double width lines */
2819 if (line_box.right > font_width*term->cols+offset_width)
2820 line_box.right = font_width*term->cols+offset_width;
2822 /* We're using a private area for direct to font. (512 chars.) */
2823 if (dbcs_screenfont && (attr & CSET_MASK) == ATTR_ACP) {
2824 /* Ho Hum, dbcs fonts are a PITA! */
2825 /* To display on W9x I have to convert to UCS */
2826 static wchar_t *uni_buf = 0;
2827 static int uni_len = 0;
2829 if (len > uni_len) {
2831 uni_buf = smalloc((uni_len = len) * sizeof(wchar_t));
2834 for(nlen = mptr = 0; mptr<len; mptr++) {
2835 uni_buf[nlen] = 0xFFFD;
2836 if (IsDBCSLeadByteEx(font_codepage, (BYTE) text[mptr])) {
2837 IpDx[nlen] += char_width;
2838 MultiByteToWideChar(font_codepage, MB_USEGLYPHCHARS,
2839 text+mptr, 2, uni_buf+nlen, 1);
2844 MultiByteToWideChar(font_codepage, MB_USEGLYPHCHARS,
2845 text+mptr, 1, uni_buf+nlen, 1);
2853 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2854 ETO_CLIPPED | ETO_OPAQUE, &line_box, uni_buf, nlen, IpDx);
2855 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2856 SetBkMode(hdc, TRANSPARENT);
2857 ExtTextOutW(hdc, x - 1,
2858 y - font_height * (lattr ==
2859 LATTR_BOT) + text_adjust,
2860 ETO_CLIPPED, &line_box, uni_buf, nlen, IpDx);
2864 } else if (DIRECT_FONT(attr)) {
2866 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2867 ETO_CLIPPED | ETO_OPAQUE, &line_box, text, len, IpDx);
2868 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2869 SetBkMode(hdc, TRANSPARENT);
2871 /* GRR: This draws the character outside it's box and can leave
2872 * 'droppings' even with the clip box! I suppose I could loop it
2873 * one character at a time ... yuk.
2875 * Or ... I could do a test print with "W", and use +1 or -1 for this
2876 * shift depending on if the leftmost column is blank...
2878 ExtTextOut(hdc, x - 1,
2879 y - font_height * (lattr ==
2880 LATTR_BOT) + text_adjust,
2881 ETO_CLIPPED, &line_box, text, len, IpDx);
2884 /* And 'normal' unicode characters */
2885 static WCHAR *wbuf = NULL;
2886 static int wlen = 0;
2891 wbuf = smalloc(wlen * sizeof(WCHAR));
2893 for (i = 0; i < len; i++)
2894 wbuf[i] = (WCHAR) ((attr & CSET_MASK) + (text[i] & CHAR_MASK));
2897 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2898 ETO_CLIPPED | ETO_OPAQUE, &line_box, wbuf, len, IpDx);
2900 /* And the shadow bold hack. */
2901 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2902 SetBkMode(hdc, TRANSPARENT);
2903 ExtTextOutW(hdc, x - 1,
2904 y - font_height * (lattr ==
2905 LATTR_BOT) + text_adjust,
2906 ETO_CLIPPED, &line_box, wbuf, len, IpDx);
2909 if (lattr != LATTR_TOP && (force_manual_underline ||
2910 (und_mode == UND_LINE
2911 && (attr & ATTR_UNDER)))) {
2914 if (lattr == LATTR_BOT)
2915 dec = dec * 2 - font_height;
2917 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, fg));
2918 MoveToEx(hdc, x, y + dec, NULL);
2919 LineTo(hdc, x + len * char_width, y + dec);
2920 oldpen = SelectObject(hdc, oldpen);
2921 DeleteObject(oldpen);
2925 void do_cursor(Context ctx, int x, int y, char *text, int len,
2926 unsigned long attr, int lattr)
2932 int ctype = cfg.cursor_type;
2934 if ((attr & TATTR_ACTCURS) && (ctype == 0 || term->big_cursor)) {
2935 if (((attr & CSET_MASK) | (unsigned char) *text) != UCSWIDE) {
2936 do_text(ctx, x, y, text, len, attr, lattr);
2940 attr |= TATTR_RIGHTCURS;
2943 fnt_width = char_width = font_width * (1 + (lattr != LATTR_NORM));
2944 if (attr & ATTR_WIDE)
2951 if ((attr & TATTR_PASCURS) && (ctype == 0 || term->big_cursor)) {
2954 pts[0].x = pts[1].x = pts[4].x = x;
2955 pts[2].x = pts[3].x = x + char_width - 1;
2956 pts[0].y = pts[3].y = pts[4].y = y;
2957 pts[1].y = pts[2].y = y + font_height - 1;
2958 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2959 Polyline(hdc, pts, 5);
2960 oldpen = SelectObject(hdc, oldpen);
2961 DeleteObject(oldpen);
2962 } else if ((attr & (TATTR_ACTCURS | TATTR_PASCURS)) && ctype != 0) {
2963 int startx, starty, dx, dy, length, i;
2966 starty = y + descent;
2969 length = char_width;
2972 if (attr & TATTR_RIGHTCURS)
2973 xadjust = char_width - 1;
2974 startx = x + xadjust;
2978 length = font_height;
2980 if (attr & TATTR_ACTCURS) {
2983 SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2984 MoveToEx(hdc, startx, starty, NULL);
2985 LineTo(hdc, startx + dx * length, starty + dy * length);
2986 oldpen = SelectObject(hdc, oldpen);
2987 DeleteObject(oldpen);
2989 for (i = 0; i < length; i++) {
2991 SetPixel(hdc, startx, starty, colours[23]);
3000 /* This function gets the actual width of a character in the normal font.
3002 int char_width(Context ctx, int uc) {
3006 /* If the font max is the same as the font ave width then this
3007 * function is a no-op.
3009 if (!font_dualwidth) return 1;
3011 switch (uc & CSET_MASK) {
3013 uc = unitab_line[uc & 0xFF];
3016 uc = unitab_xterm[uc & 0xFF];
3019 uc = unitab_scoacs[uc & 0xFF];
3022 if (DIRECT_FONT(uc)) {
3023 if (dbcs_screenfont) return 1;
3025 /* Speedup, I know of no font where ascii is the wrong width */
3026 if ((uc&CHAR_MASK) >= ' ' && (uc&CHAR_MASK)<= '~')
3029 if ( (uc & CSET_MASK) == ATTR_ACP ) {
3030 SelectObject(hdc, fonts[FONT_NORMAL]);
3031 } else if ( (uc & CSET_MASK) == ATTR_OEMCP ) {
3032 another_font(FONT_OEM);
3033 if (!fonts[FONT_OEM]) return 0;
3035 SelectObject(hdc, fonts[FONT_OEM]);
3039 if ( GetCharWidth32(hdc, uc&CHAR_MASK, uc&CHAR_MASK, &ibuf) != 1 &&
3040 GetCharWidth(hdc, uc&CHAR_MASK, uc&CHAR_MASK, &ibuf) != 1)
3043 /* Speedup, I know of no font where ascii is the wrong width */
3044 if (uc >= ' ' && uc <= '~') return 1;
3046 SelectObject(hdc, fonts[FONT_NORMAL]);
3047 if ( GetCharWidth32W(hdc, uc, uc, &ibuf) == 1 )
3048 /* Okay that one worked */ ;
3049 else if ( GetCharWidthW(hdc, uc, uc, &ibuf) == 1 )
3050 /* This should work on 9x too, but it's "less accurate" */ ;
3055 ibuf += font_width / 2 -1;
3062 * Translate a WM_(SYS)?KEY(UP|DOWN) message into a string of ASCII
3063 * codes. Returns number of bytes used or zero to drop the message
3064 * or -1 to forward the message to windows.
3066 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
3067 unsigned char *output)
3070 int scan, left_alt = 0, key_down, shift_state;
3072 unsigned char *p = output;
3073 static int alt_sum = 0;
3075 HKL kbd_layout = GetKeyboardLayout(0);
3077 static WORD keys[3];
3078 static int compose_char = 0;
3079 static WPARAM compose_key = 0;
3081 r = GetKeyboardState(keystate);
3083 memset(keystate, 0, sizeof(keystate));
3086 #define SHOW_TOASCII_RESULT
3087 { /* Tell us all about key events */
3088 static BYTE oldstate[256];
3089 static int first = 1;
3093 memcpy(oldstate, keystate, sizeof(oldstate));
3096 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT) {
3098 } else if ((HIWORD(lParam) & KF_UP)
3099 && scan == (HIWORD(lParam) & 0xFF)) {
3103 if (wParam >= VK_F1 && wParam <= VK_F20)
3104 debug(("K_F%d", wParam + 1 - VK_F1));
3117 debug(("VK_%02x", wParam));
3119 if (message == WM_SYSKEYDOWN || message == WM_SYSKEYUP)
3121 debug((", S%02x", scan = (HIWORD(lParam) & 0xFF)));
3123 ch = MapVirtualKeyEx(wParam, 2, kbd_layout);
3124 if (ch >= ' ' && ch <= '~')
3125 debug((", '%c'", ch));
3127 debug((", $%02x", ch));
3130 debug((", KB0=%02x", keys[0]));
3132 debug((", KB1=%02x", keys[1]));
3134 debug((", KB2=%02x", keys[2]));
3136 if ((keystate[VK_SHIFT] & 0x80) != 0)
3138 if ((keystate[VK_CONTROL] & 0x80) != 0)
3140 if ((HIWORD(lParam) & KF_EXTENDED))
3142 if ((HIWORD(lParam) & KF_UP))
3146 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT);
3147 else if ((HIWORD(lParam) & KF_UP))
3148 oldstate[wParam & 0xFF] ^= 0x80;
3150 oldstate[wParam & 0xFF] ^= 0x81;
3152 for (ch = 0; ch < 256; ch++)
3153 if (oldstate[ch] != keystate[ch])
3154 debug((", M%02x=%02x", ch, keystate[ch]));
3156 memcpy(oldstate, keystate, sizeof(oldstate));
3160 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED)) {
3161 keystate[VK_RMENU] = keystate[VK_MENU];
3165 /* Nastyness with NUMLock - Shift-NUMLock is left alone though */
3166 if ((cfg.funky_type == 3 ||
3167 (cfg.funky_type <= 1 && term->app_keypad_keys &&
3169 && wParam == VK_NUMLOCK && !(keystate[VK_SHIFT] & 0x80)) {
3171 wParam = VK_EXECUTE;
3173 /* UnToggle NUMLock */
3174 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0)
3175 keystate[VK_NUMLOCK] ^= 1;
3178 /* And write back the 'adjusted' state */
3179 SetKeyboardState(keystate);
3182 /* Disable Auto repeat if required */
3183 if (term->repeat_off &&
3184 (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT)
3187 if ((HIWORD(lParam) & KF_ALTDOWN) && (keystate[VK_RMENU] & 0x80) == 0)
3190 key_down = ((HIWORD(lParam) & KF_UP) == 0);
3192 /* Make sure Ctrl-ALT is not the same as AltGr for ToAscii unless told. */
3193 if (left_alt && (keystate[VK_CONTROL] & 0x80)) {
3194 if (cfg.ctrlaltkeys)
3195 keystate[VK_MENU] = 0;
3197 keystate[VK_RMENU] = 0x80;
3202 scan = (HIWORD(lParam) & (KF_UP | KF_EXTENDED | 0xFF));
3203 shift_state = ((keystate[VK_SHIFT] & 0x80) != 0)
3204 + ((keystate[VK_CONTROL] & 0x80) != 0) * 2;
3206 /* Note if AltGr was pressed and if it was used as a compose key */
3207 if (!compose_state) {
3208 compose_key = 0x100;
3209 if (cfg.compose_key) {
3210 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED))
3211 compose_key = wParam;
3213 if (wParam == VK_APPS)
3214 compose_key = wParam;
3217 if (wParam == compose_key) {
3218 if (compose_state == 0
3219 && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0) compose_state =
3221 else if (compose_state == 1 && (HIWORD(lParam) & KF_UP))
3225 } else if (compose_state == 1 && wParam != VK_CONTROL)
3228 if (compose_state > 1 && left_alt)
3231 /* Sanitize the number pad if not using a PC NumPad */
3232 if (left_alt || (term->app_keypad_keys && !cfg.no_applic_k
3233 && cfg.funky_type != 2)
3234 || cfg.funky_type == 3 || cfg.nethack_keypad || compose_state) {
3235 if ((HIWORD(lParam) & KF_EXTENDED) == 0) {
3239 nParam = VK_NUMPAD0;
3242 nParam = VK_NUMPAD1;
3245 nParam = VK_NUMPAD2;
3248 nParam = VK_NUMPAD3;
3251 nParam = VK_NUMPAD4;
3254 nParam = VK_NUMPAD5;
3257 nParam = VK_NUMPAD6;
3260 nParam = VK_NUMPAD7;
3263 nParam = VK_NUMPAD8;
3266 nParam = VK_NUMPAD9;
3269 nParam = VK_DECIMAL;
3273 if (keystate[VK_NUMLOCK] & 1)
3280 /* If a key is pressed and AltGr is not active */
3281 if (key_down && (keystate[VK_RMENU] & 0x80) == 0 && !compose_state) {
3282 /* Okay, prepare for most alts then ... */
3286 /* Lets see if it's a pattern we know all about ... */
3287 if (wParam == VK_PRIOR && shift_state == 1) {
3288 SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0);
3291 if (wParam == VK_NEXT && shift_state == 1) {
3292 SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
3295 if (wParam == VK_INSERT && shift_state == 1) {
3296 term_do_paste(term);
3299 if (left_alt && wParam == VK_F4 && cfg.alt_f4) {
3302 if (left_alt && wParam == VK_SPACE && cfg.alt_space) {
3303 SendMessage(hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0);
3306 if (left_alt && wParam == VK_RETURN && cfg.fullscreenonaltenter &&
3307 (cfg.resize_action != RESIZE_DISABLED)) {
3308 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) != KF_REPEAT)
3312 /* Control-Numlock for app-keypad mode switch */
3313 if (wParam == VK_PAUSE && shift_state == 2) {
3314 term->app_keypad_keys ^= 1;
3318 /* Nethack keypad */
3319 if (cfg.nethack_keypad && !left_alt) {
3322 *p++ = shift_state ? 'B' : 'b';
3325 *p++ = shift_state ? 'J' : 'j';
3328 *p++ = shift_state ? 'N' : 'n';
3331 *p++ = shift_state ? 'H' : 'h';
3334 *p++ = shift_state ? '.' : '.';
3337 *p++ = shift_state ? 'L' : 'l';
3340 *p++ = shift_state ? 'Y' : 'y';
3343 *p++ = shift_state ? 'K' : 'k';
3346 *p++ = shift_state ? 'U' : 'u';
3351 /* Application Keypad */
3355 if (cfg.funky_type == 3 ||
3356 (cfg.funky_type <= 1 &&
3357 term->app_keypad_keys && !cfg.no_applic_k)) switch (wParam) {
3371 if (term->app_keypad_keys && !cfg.no_applic_k)
3408 if (cfg.funky_type == 2) {
3413 } else if (shift_state)
3420 if (cfg.funky_type == 2)
3424 if (cfg.funky_type == 2)
3428 if (cfg.funky_type == 2)
3433 if (HIWORD(lParam) & KF_EXTENDED)
3438 if (term->vt52_mode) {
3439 if (xkey >= 'P' && xkey <= 'S')
3440 p += sprintf((char *) p, "\x1B%c", xkey);
3442 p += sprintf((char *) p, "\x1B?%c", xkey);
3444 p += sprintf((char *) p, "\x1BO%c", xkey);
3449 if (wParam == VK_BACK && shift_state == 0) { /* Backspace */
3450 *p++ = (cfg.bksp_is_delete ? 0x7F : 0x08);
3454 if (wParam == VK_BACK && shift_state == 1) { /* Shift Backspace */
3455 /* We do the opposite of what is configured */
3456 *p++ = (cfg.bksp_is_delete ? 0x08 : 0x7F);
3460 if (wParam == VK_TAB && shift_state == 1) { /* Shift tab */
3466 if (wParam == VK_SPACE && shift_state == 2) { /* Ctrl-Space */
3470 if (wParam == VK_SPACE && shift_state == 3) { /* Ctrl-Shift-Space */
3474 if (wParam == VK_CANCEL && shift_state == 2) { /* Ctrl-Break */
3479 if (wParam == VK_PAUSE) { /* Break/Pause */
3484 /* Control-2 to Control-8 are special */
3485 if (shift_state == 2 && wParam >= '2' && wParam <= '8') {
3486 *p++ = "\000\033\034\035\036\037\177"[wParam - '2'];
3489 if (shift_state == 2 && wParam == 0xBD) {
3493 if (shift_state == 2 && wParam == 0xDF) {
3497 if (shift_state == 0 && wParam == VK_RETURN && term->cr_lf_return) {
3504 * Next, all the keys that do tilde codes. (ESC '[' nn '~',
3505 * for integer decimal nn.)
3507 * We also deal with the weird ones here. Linux VCs replace F1
3508 * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
3509 * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
3515 code = (keystate[VK_SHIFT] & 0x80 ? 23 : 11);
3518 code = (keystate[VK_SHIFT] & 0x80 ? 24 : 12);
3521 code = (keystate[VK_SHIFT] & 0x80 ? 25 : 13);
3524 code = (keystate[VK_SHIFT] & 0x80 ? 26 : 14);
3527 code = (keystate[VK_SHIFT] & 0x80 ? 28 : 15);
3530 code = (keystate[VK_SHIFT] & 0x80 ? 29 : 17);
3533 code = (keystate[VK_SHIFT] & 0x80 ? 31 : 18);
3536 code = (keystate[VK_SHIFT] & 0x80 ? 32 : 19);
3539 code = (keystate[VK_SHIFT] & 0x80 ? 33 : 20);
3542 code = (keystate[VK_SHIFT] & 0x80 ? 34 : 21);
3575 if ((shift_state&2) == 0) switch (wParam) {
3595 /* Reorder edit keys to physical order */
3596 if (cfg.funky_type == 3 && code <= 6)
3597 code = "\0\2\1\4\5\3\6"[code];
3599 if (term->vt52_mode && code > 0 && code <= 6) {
3600 p += sprintf((char *) p, "\x1B%c", " HLMEIG"[code]);
3604 if (cfg.funky_type == 5 && /* SCO function keys */
3605 code >= 11 && code <= 34) {
3606 char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
3609 case VK_F1: index = 0; break;
3610 case VK_F2: index = 1; break;
3611 case VK_F3: index = 2; break;
3612 case VK_F4: index = 3; break;
3613 case VK_F5: index = 4; break;
3614 case VK_F6: index = 5; break;
3615 case VK_F7: index = 6; break;
3616 case VK_F8: index = 7; break;
3617 case VK_F9: index = 8; break;
3618 case VK_F10: index = 9; break;
3619 case VK_F11: index = 10; break;
3620 case VK_F12: index = 11; break;
3622 if (keystate[VK_SHIFT] & 0x80) index += 12;
3623 if (keystate[VK_CONTROL] & 0x80) index += 24;
3624 p += sprintf((char *) p, "\x1B[%c", codes[index]);
3627 if (cfg.funky_type == 5 && /* SCO small keypad */
3628 code >= 1 && code <= 6) {
3629 char codes[] = "HL.FIG";
3633 p += sprintf((char *) p, "\x1B[%c", codes[code-1]);
3637 if ((term->vt52_mode || cfg.funky_type == 4) && code >= 11 && code <= 24) {
3643 if (term->vt52_mode)
3644 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11 - offt);
3647 sprintf((char *) p, "\x1BO%c", code + 'P' - 11 - offt);
3650 if (cfg.funky_type == 1 && code >= 11 && code <= 15) {
3651 p += sprintf((char *) p, "\x1B[[%c", code + 'A' - 11);
3654 if (cfg.funky_type == 2 && code >= 11 && code <= 14) {
3655 if (term->vt52_mode)
3656 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11);
3658 p += sprintf((char *) p, "\x1BO%c", code + 'P' - 11);
3661 if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
3662 p += sprintf((char *) p, code == 1 ? "\x1B[H" : "\x1BOw");
3666 p += sprintf((char *) p, "\x1B[%d~", code);
3671 * Now the remaining keys (arrows and Keypad 5. Keypad 5 for
3672 * some reason seems to send VK_CLEAR to Windows...).
3694 if (term->vt52_mode)
3695 p += sprintf((char *) p, "\x1B%c", xkey);
3697 int app_flg = (term->app_cursor_keys && !cfg.no_applic_c);
3700 * RDB: VT100 & VT102 manuals both state the
3701 * app cursor keys only work if the app keypad
3704 * SGT: That may well be true, but xterm
3705 * disagrees and so does at least one
3706 * application, so I've #if'ed this out and the
3707 * behaviour is back to PuTTY's original: app
3708 * cursor and app keypad are independently
3709 * switchable modes. If anyone complains about
3710 * _this_ I'll have to put in a configurable
3713 if (!term->app_keypad_keys)
3716 /* Useful mapping of Ctrl-arrows */
3717 if (shift_state == 2)
3721 p += sprintf((char *) p, "\x1BO%c", xkey);
3723 p += sprintf((char *) p, "\x1B[%c", xkey);
3730 * Finally, deal with Return ourselves. (Win95 seems to
3731 * foul it up when Alt is pressed, for some reason.)
3733 if (wParam == VK_RETURN) { /* Return */
3739 if (left_alt && wParam >= VK_NUMPAD0 && wParam <= VK_NUMPAD9)
3740 alt_sum = alt_sum * 10 + wParam - VK_NUMPAD0;
3745 /* Okay we've done everything interesting; let windows deal with
3746 * the boring stuff */
3750 /* helg: clear CAPS LOCK state if caps lock switches to cyrillic */
3751 if(cfg.xlat_capslockcyr && keystate[VK_CAPITAL] != 0) {
3753 keystate[VK_CAPITAL] = 0;
3756 r = ToAsciiEx(wParam, scan, keystate, keys, 0, kbd_layout);
3757 #ifdef SHOW_TOASCII_RESULT
3758 if (r == 1 && !key_down) {
3760 if (in_utf(term) || dbcs_screenfont)
3761 debug((", (U+%04x)", alt_sum));
3763 debug((", LCH(%d)", alt_sum));
3765 debug((", ACH(%d)", keys[0]));
3770 for (r1 = 0; r1 < r; r1++) {
3771 debug(("%s%d", r1 ? "," : "", keys[r1]));
3780 * Interrupt an ongoing paste. I'm not sure this is
3781 * sensible, but for the moment it's preferable to
3782 * having to faff about buffering things.
3787 for (i = 0; i < r; i++) {
3788 unsigned char ch = (unsigned char) keys[i];
3790 if (compose_state == 2 && (ch & 0x80) == 0 && ch > ' ') {
3795 if (compose_state == 3 && (ch & 0x80) == 0 && ch > ' ') {
3799 if ((nc = check_compose(compose_char, ch)) == -1) {
3800 MessageBeep(MB_ICONHAND);
3804 term_seen_key_event(term);
3805 luni_send(ldisc, &keybuf, 1, 1);
3813 if (in_utf(term) || dbcs_screenfont) {
3815 term_seen_key_event(term);
3816 luni_send(ldisc, &keybuf, 1, 1);
3818 ch = (char) alt_sum;
3820 * We need not bother about stdin
3821 * backlogs here, because in GUI PuTTY
3822 * we can't do anything about it
3823 * anyway; there's no means of asking
3824 * Windows to hold off on KEYDOWN
3825 * messages. We _have_ to buffer
3826 * everything we're sent.
3828 term_seen_key_event(term);
3829 ldisc_send(ldisc, &ch, 1, 1);
3833 term_seen_key_event(term);
3834 lpage_send(ldisc, kbd_codepage, &ch, 1, 1);
3836 if(capsOn && ch < 0x80) {
3839 cbuf[1] = xlat_uskbd2cyrllic(ch);
3840 term_seen_key_event(term);
3841 luni_send(ldisc, cbuf+!left_alt, 1+!!left_alt, 1);
3846 term_seen_key_event(term);
3847 lpage_send(ldisc, kbd_codepage,
3848 cbuf+!left_alt, 1+!!left_alt, 1);
3854 /* This is so the ALT-Numpad and dead keys work correctly. */
3859 /* If we're definitly not building up an ALT-54321 then clear it */
3862 /* If we will be using alt_sum fix the 256s */
3863 else if (keys[0] && (in_utf(term) || dbcs_screenfont))
3868 * ALT alone may or may not want to bring up the System menu.
3869 * If it's not meant to, we return 0 on presses or releases of
3870 * ALT, to show that we've swallowed the keystroke. Otherwise
3871 * we return -1, which means Windows will give the keystroke
3872 * its default handling (i.e. bring up the System menu).
3874 if (wParam == VK_MENU && !cfg.alt_only)
3880 void request_paste(void *frontend)
3883 * In Windows, pasting is synchronous: we can read the
3884 * clipboard with no difficulty, so request_paste() can just go
3887 term_do_paste(term);
3890 void set_title(void *frontend, char *title)
3893 window_name = smalloc(1 + strlen(title));
3894 strcpy(window_name, title);
3895 if (cfg.win_name_always || !IsIconic(hwnd))
3896 SetWindowText(hwnd, title);
3899 void set_icon(void *frontend, char *title)
3902 icon_name = smalloc(1 + strlen(title));
3903 strcpy(icon_name, title);
3904 if (!cfg.win_name_always && IsIconic(hwnd))
3905 SetWindowText(hwnd, title);
3908 void set_sbar(void *frontend, int total, int start, int page)
3912 if (is_full_screen() ? !cfg.scrollbar_in_fullscreen : !cfg.scrollbar)
3915 si.cbSize = sizeof(si);
3916 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
3918 si.nMax = total - 1;
3922 SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
3925 Context get_ctx(void *frontend)
3931 SelectPalette(hdc, pal, FALSE);
3937 void free_ctx(Context ctx)
3939 SelectPalette(ctx, GetStockObject(DEFAULT_PALETTE), FALSE);
3940 ReleaseDC(hwnd, ctx);
3943 static void real_palette_set(int n, int r, int g, int b)
3946 logpal->palPalEntry[n].peRed = r;
3947 logpal->palPalEntry[n].peGreen = g;
3948 logpal->palPalEntry[n].peBlue = b;
3949 logpal->palPalEntry[n].peFlags = PC_NOCOLLAPSE;
3950 colours[n] = PALETTERGB(r, g, b);
3951 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3953 colours[n] = RGB(r, g, b);
3956 void palette_set(void *frontend, int n, int r, int g, int b)
3958 static const int first[21] = {
3959 0, 2, 4, 6, 8, 10, 12, 14,
3960 1, 3, 5, 7, 9, 11, 13, 15,
3963 real_palette_set(first[n], r, g, b);
3965 real_palette_set(first[n] + 1, r, g, b);
3967 HDC hdc = get_ctx(frontend);
3968 UnrealizeObject(pal);
3969 RealizePalette(hdc);
3974 void palette_reset(void *frontend)
3978 for (i = 0; i < NCOLOURS; i++) {
3980 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
3981 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
3982 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
3983 logpal->palPalEntry[i].peFlags = 0;
3984 colours[i] = PALETTERGB(defpal[i].rgbtRed,
3985 defpal[i].rgbtGreen,
3986 defpal[i].rgbtBlue);
3988 colours[i] = RGB(defpal[i].rgbtRed,
3989 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
3994 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3995 hdc = get_ctx(frontend);
3996 RealizePalette(hdc);
4001 void write_aclip(void *frontend, char *data, int len, int must_deselect)
4006 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len + 1);
4009 lock = GlobalLock(clipdata);
4012 memcpy(lock, data, len);
4013 ((unsigned char *) lock)[len] = 0;
4014 GlobalUnlock(clipdata);
4017 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
4019 if (OpenClipboard(hwnd)) {
4021 SetClipboardData(CF_TEXT, clipdata);
4024 GlobalFree(clipdata);
4027 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
4031 * Note: unlike write_aclip() this will not append a nul.
4033 void write_clip(void *frontend, wchar_t * data, int len, int must_deselect)
4035 HGLOBAL clipdata, clipdata2, clipdata3;
4037 void *lock, *lock2, *lock3;
4039 len2 = WideCharToMultiByte(CP_ACP, 0, data, len, 0, 0, NULL, NULL);
4041 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE,
4042 len * sizeof(wchar_t));
4043 clipdata2 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len2);
4045 if (!clipdata || !clipdata2) {
4047 GlobalFree(clipdata);
4049 GlobalFree(clipdata2);
4052 if (!(lock = GlobalLock(clipdata)))
4054 if (!(lock2 = GlobalLock(clipdata2)))
4057 memcpy(lock, data, len * sizeof(wchar_t));
4058 WideCharToMultiByte(CP_ACP, 0, data, len, lock2, len2, NULL, NULL);
4060 if (cfg.rtf_paste) {
4061 wchar_t unitab[256];
4063 unsigned char *tdata = (unsigned char *)lock2;
4064 wchar_t *udata = (wchar_t *)lock;
4065 int rtflen = 0, uindex = 0, tindex = 0;
4067 int multilen, blen, alen, totallen, i;
4068 char before[16], after[4];
4070 get_unitab(CP_ACP, unitab, 0);
4072 rtfsize = 100 + strlen(cfg.font);
4073 rtf = smalloc(rtfsize);
4074 sprintf(rtf, "{\\rtf1\\ansi%d{\\fonttbl\\f0\\fmodern %s;}\\f0",
4075 GetACP(), cfg.font);
4076 rtflen = strlen(rtf);
4079 * We want to construct a piece of RTF that specifies the
4080 * same Unicode text. To do this we will read back in
4081 * parallel from the Unicode data in `udata' and the
4082 * non-Unicode data in `tdata'. For each character in
4083 * `tdata' which becomes the right thing in `udata' when
4084 * looked up in `unitab', we just copy straight over from
4085 * tdata. For each one that doesn't, we must WCToMB it
4086 * individually and produce a \u escape sequence.
4088 * It would probably be more robust to just bite the bullet
4089 * and WCToMB each individual Unicode character one by one,
4090 * then MBToWC each one back to see if it was an accurate
4091 * translation; but that strikes me as a horrifying number
4092 * of Windows API calls so I want to see if this faster way
4093 * will work. If it screws up badly we can always revert to
4094 * the simple and slow way.
4096 while (tindex < len2 && uindex < len &&
4097 tdata[tindex] && udata[uindex]) {
4098 if (tindex + 1 < len2 &&
4099 tdata[tindex] == '\r' &&
4100 tdata[tindex+1] == '\n') {
4104 if (unitab[tdata[tindex]] == udata[uindex]) {
4110 multilen = WideCharToMultiByte(CP_ACP, 0, unitab+uindex, 1,
4111 NULL, 0, NULL, NULL);
4112 if (multilen != 1) {
4113 blen = sprintf(before, "{\\uc%d\\u%d", multilen,
4115 alen = 1; strcpy(after, "}");
4117 blen = sprintf(before, "\\u%d", udata[uindex]);
4118 alen = 0; after[0] = '\0';
4121 assert(tindex + multilen <= len2);
4122 totallen = blen + alen;
4123 for (i = 0; i < multilen; i++) {
4124 if (tdata[tindex+i] == '\\' ||
4125 tdata[tindex+i] == '{' ||
4126 tdata[tindex+i] == '}')
4128 else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A)
4129 totallen += 6; /* \par\r\n */
4130 else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20)
4136 if (rtfsize < rtflen + totallen + 3) {
4137 rtfsize = rtflen + totallen + 512;
4138 rtf = srealloc(rtf, rtfsize);
4141 strcpy(rtf + rtflen, before); rtflen += blen;
4142 for (i = 0; i < multilen; i++) {
4143 if (tdata[tindex+i] == '\\' ||
4144 tdata[tindex+i] == '{' ||
4145 tdata[tindex+i] == '}') {
4146 rtf[rtflen++] = '\\';
4147 rtf[rtflen++] = tdata[tindex+i];
4148 } else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A) {
4149 rtflen += sprintf(rtf+rtflen, "\\par\r\n");
4150 } else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20) {
4151 rtflen += sprintf(rtf+rtflen, "\\'%02x", tdata[tindex+i]);
4153 rtf[rtflen++] = tdata[tindex+i];
4156 strcpy(rtf + rtflen, after); rtflen += alen;
4162 strcpy(rtf + rtflen, "}");
4165 clipdata3 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, rtflen);
4166 if (clipdata3 && (lock3 = GlobalLock(clipdata3)) != NULL) {
4168 GlobalUnlock(clipdata3);
4174 GlobalUnlock(clipdata);
4175 GlobalUnlock(clipdata2);
4178 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
4180 if (OpenClipboard(hwnd)) {
4182 SetClipboardData(CF_UNICODETEXT, clipdata);
4183 SetClipboardData(CF_TEXT, clipdata2);
4185 SetClipboardData(RegisterClipboardFormat(CF_RTF), clipdata3);
4188 GlobalFree(clipdata);
4189 GlobalFree(clipdata2);
4193 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
4196 void get_clip(void *frontend, wchar_t ** p, int *len)
4198 static HGLOBAL clipdata = NULL;
4199 static wchar_t *converted = 0;
4208 GlobalUnlock(clipdata);
4211 } else if (OpenClipboard(NULL)) {
4212 if ((clipdata = GetClipboardData(CF_UNICODETEXT))) {
4214 *p = GlobalLock(clipdata);
4216 for (p2 = *p; *p2; p2++);
4220 } else if ( (clipdata = GetClipboardData(CF_TEXT)) ) {
4224 s = GlobalLock(clipdata);
4225 i = MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, 0, 0);
4226 *p = converted = smalloc(i * sizeof(wchar_t));
4227 MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, converted, i);
4240 * Move `lines' lines from position `from' to position `to' in the
4243 void optimised_move(void *frontend, int to, int from, int lines)
4248 min = (to < from ? to : from);
4249 max = to + from - min;
4251 r.left = offset_width;
4252 r.right = offset_width + term->cols * font_width;
4253 r.top = offset_height + min * font_height;
4254 r.bottom = offset_height + (max + lines) * font_height;
4255 ScrollWindow(hwnd, 0, (to - from) * font_height, &r, &r);
4260 * Print a message box and perform a fatal exit.
4262 void fatalbox(char *fmt, ...)
4268 vsprintf(stuff, fmt, ap);
4270 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
4275 * Print a modal (Really Bad) message box and perform a fatal exit.
4277 void modalfatalbox(char *fmt, ...)
4283 vsprintf(stuff, fmt, ap);
4285 MessageBox(hwnd, stuff, "PuTTY Fatal Error",
4286 MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
4291 * Manage window caption / taskbar flashing, if enabled.
4292 * 0 = stop, 1 = maintain, 2 = start
4294 static void flash_window(int mode)
4296 static long last_flash = 0;
4297 static int flashing = 0;
4298 if ((mode == 0) || (cfg.beep_ind == B_IND_DISABLED)) {
4301 FlashWindow(hwnd, FALSE);
4305 } else if (mode == 2) {
4308 last_flash = GetTickCount();
4310 FlashWindow(hwnd, TRUE);
4313 } else if ((mode == 1) && (cfg.beep_ind == B_IND_FLASH)) {
4316 long now = GetTickCount();
4317 long fdiff = now - last_flash;
4318 if (fdiff < 0 || fdiff > 450) {
4320 FlashWindow(hwnd, TRUE); /* toggle */
4329 void beep(void *frontend, int mode)
4331 if (mode == BELL_DEFAULT) {
4333 * For MessageBeep style bells, we want to be careful of
4334 * timing, because they don't have the nice property of
4335 * PlaySound bells that each one cancels the previous
4336 * active one. So we limit the rate to one per 50ms or so.
4338 static long lastbeep = 0;
4341 beepdiff = GetTickCount() - lastbeep;
4342 if (beepdiff >= 0 && beepdiff < 50)
4346 * The above MessageBeep call takes time, so we record the
4347 * time _after_ it finishes rather than before it starts.
4349 lastbeep = GetTickCount();
4350 } else if (mode == BELL_WAVEFILE) {
4351 if (!PlaySound(cfg.bell_wavefile, NULL, SND_ASYNC | SND_FILENAME)) {
4352 char buf[sizeof(cfg.bell_wavefile) + 80];
4353 sprintf(buf, "Unable to play sound file\n%s\n"
4354 "Using default sound instead", cfg.bell_wavefile);
4355 MessageBox(hwnd, buf, "PuTTY Sound Error",
4356 MB_OK | MB_ICONEXCLAMATION);
4357 cfg.beep = BELL_DEFAULT;
4360 /* Otherwise, either visual bell or disabled; do nothing here */
4361 if (!term->has_focus) {
4362 flash_window(2); /* start */
4367 * Minimise or restore the window in response to a server-side
4370 void set_iconic(void *frontend, int iconic)
4372 if (IsIconic(hwnd)) {
4374 ShowWindow(hwnd, SW_RESTORE);
4377 ShowWindow(hwnd, SW_MINIMIZE);
4382 * Move the window in response to a server-side request.
4384 void move_window(void *frontend, int x, int y)
4386 if (cfg.resize_action == RESIZE_DISABLED ||
4387 cfg.resize_action == RESIZE_FONT ||
4391 SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
4395 * Move the window to the top or bottom of the z-order in response
4396 * to a server-side request.
4398 void set_zorder(void *frontend, int top)
4400 if (cfg.alwaysontop)
4401 return; /* ignore */
4402 SetWindowPos(hwnd, top ? HWND_TOP : HWND_BOTTOM, 0, 0, 0, 0,
4403 SWP_NOMOVE | SWP_NOSIZE);
4407 * Refresh the window in response to a server-side request.
4409 void refresh_window(void *frontend)
4411 InvalidateRect(hwnd, NULL, TRUE);
4415 * Maximise or restore the window in response to a server-side
4418 void set_zoomed(void *frontend, int zoomed)
4420 if (IsZoomed(hwnd)) {
4422 ShowWindow(hwnd, SW_RESTORE);
4425 ShowWindow(hwnd, SW_MAXIMIZE);
4430 * Report whether the window is iconic, for terminal reports.
4432 int is_iconic(void *frontend)
4434 return IsIconic(hwnd);
4438 * Report the window's position, for terminal reports.
4440 void get_window_pos(void *frontend, int *x, int *y)
4443 GetWindowRect(hwnd, &r);
4449 * Report the window's pixel size, for terminal reports.
4451 void get_window_pixels(void *frontend, int *x, int *y)
4454 GetWindowRect(hwnd, &r);
4455 *x = r.right - r.left;
4456 *y = r.bottom - r.top;
4460 * Return the window or icon title.
4462 char *get_window_title(void *frontend, int icon)
4464 return icon ? icon_name : window_name;
4468 * See if we're in full-screen mode.
4470 int is_full_screen()
4472 if (!IsZoomed(hwnd))
4474 if (GetWindowLong(hwnd, GWL_STYLE) & WS_CAPTION)
4479 /* Get the rect/size of a full screen window using the nearest available
4480 * monitor in multimon systems; default to something sensible if only
4481 * one monitor is present. */
4482 static int get_fullscreen_rect(RECT * ss)
4484 #ifdef MONITOR_DEFAULTTONEAREST
4487 mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
4488 mi.cbSize = sizeof(mi);
4489 GetMonitorInfo(mon, &mi);
4491 /* structure copy */
4495 /* could also use code like this:
4496 ss->left = ss->top = 0;
4497 ss->right = GetSystemMetrics(SM_CXSCREEN);
4498 ss->bottom = GetSystemMetrics(SM_CYSCREEN);
4500 return GetClientRect(GetDesktopWindow(), ss);
4506 * Go full-screen. This should only be called when we are already
4509 void make_full_screen()
4514 assert(IsZoomed(hwnd));
4516 if (is_full_screen())
4519 /* Remove the window furniture. */
4520 style = GetWindowLong(hwnd, GWL_STYLE);
4521 style &= ~(WS_CAPTION | WS_BORDER | WS_THICKFRAME);
4522 if (cfg.scrollbar_in_fullscreen)
4523 style |= WS_VSCROLL;
4525 style &= ~WS_VSCROLL;
4526 SetWindowLong(hwnd, GWL_STYLE, style);
4528 /* Resize ourselves to exactly cover the nearest monitor. */
4529 get_fullscreen_rect(&ss);
4530 SetWindowPos(hwnd, HWND_TOP, ss.left, ss.top,
4535 /* Tick the menu item in the System menu. */
4536 CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN,
4541 * Clear the full-screen attributes.
4543 void clear_full_screen()
4545 DWORD oldstyle, style;
4547 /* Reinstate the window furniture. */
4548 style = oldstyle = GetWindowLong(hwnd, GWL_STYLE);
4549 style |= WS_CAPTION | WS_BORDER;
4550 if (cfg.resize_action == RESIZE_DISABLED)
4551 style &= ~WS_THICKFRAME;
4553 style |= WS_THICKFRAME;
4555 style |= WS_VSCROLL;
4557 style &= ~WS_VSCROLL;
4558 if (style != oldstyle) {
4559 SetWindowLong(hwnd, GWL_STYLE, style);
4560 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
4561 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
4565 /* Untick the menu item in the System menu. */
4566 CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN,
4571 * Toggle full-screen mode.
4573 void flip_full_screen()
4575 if (is_full_screen()) {
4576 ShowWindow(hwnd, SW_RESTORE);
4577 } else if (IsZoomed(hwnd)) {
4580 SendMessage(hwnd, WM_FULLSCR_ON_MAX, 0, 0);
4581 ShowWindow(hwnd, SW_MAXIMIZE);
4585 void frontend_keypress(void *handle)
4588 * Keypress termination in non-Close-On-Exit mode is not
4589 * currently supported in PuTTY proper, because the window
4590 * always has a perfectly good Close button anyway. So we do