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, 1);
334 cmdline_error("option \"%s\" requires an argument", p);
335 } else if (ret == 2) {
336 i++; /* skip next argument */
337 } else if (ret == 1) {
338 continue; /* nothing further needs doing */
339 } else if (!strcmp(p, "-cleanup")) {
341 * `putty -cleanup'. Remove all registry
342 * entries associated with PuTTY, and also find
343 * and delete the random seed file.
346 "This procedure will remove ALL Registry\n"
347 "entries associated with PuTTY, and will\n"
348 "also remove the PuTTY random seed file.\n"
350 "THIS PROCESS WILL DESTROY YOUR SAVED\n"
351 "SESSIONS. Are you really sure you want\n"
354 MB_YESNO | MB_ICONWARNING) == IDYES) {
358 } else if (*p != '-') {
362 * If we already have a host name, treat
363 * this argument as a port number. NB we
364 * have to treat this as a saved -P
365 * argument, so that it will be deferred
366 * until it's a good moment to run it.
368 int ret = cmdline_process_param("-P", p, 1);
370 } else if (!strncmp(q, "telnet:", 7)) {
372 * If the hostname starts with "telnet:",
373 * set the protocol to Telnet and process
374 * the string as a Telnet URL.
379 if (q[0] == '/' && q[1] == '/')
381 cfg.protocol = PROT_TELNET;
383 while (*p && *p != ':' && *p != '/')
392 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
393 cfg.host[sizeof(cfg.host) - 1] = '\0';
397 * Otherwise, treat this argument as a host
400 while (*p && !isspace(*p))
404 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
405 cfg.host[sizeof(cfg.host) - 1] = '\0';
409 cmdline_error("unknown option \"%s\"", p);
416 if (!*cfg.host && !do_config()) {
422 * Trim leading whitespace off the hostname if it's there.
425 int space = strspn(cfg.host, " \t");
426 memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
429 /* See if host is of the form user@host */
430 if (cfg.host[0] != '\0') {
431 char *atsign = strchr(cfg.host, '@');
432 /* Make sure we're not overflowing the user field */
434 if (atsign - cfg.host < sizeof cfg.username) {
435 strncpy(cfg.username, cfg.host, atsign - cfg.host);
436 cfg.username[atsign - cfg.host] = '\0';
438 memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
443 * Trim a colon suffix off the hostname if it's there.
445 cfg.host[strcspn(cfg.host, ":")] = '\0';
448 * Remove any remaining whitespace from the hostname.
452 while (cfg.host[p2] != '\0') {
453 if (cfg.host[p2] != ' ' && cfg.host[p2] != '\t') {
454 cfg.host[p1] = cfg.host[p2];
464 * Select protocol. This is farmed out into a table in a
465 * separate file to enable an ssh-free variant.
470 for (i = 0; backends[i].backend != NULL; i++)
471 if (backends[i].protocol == cfg.protocol) {
472 back = backends[i].backend;
476 MessageBox(NULL, "Unsupported protocol number found",
477 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
483 /* Check for invalid Port number (i.e. zero) */
485 MessageBox(NULL, "Invalid Port Number",
486 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
493 wndclass.lpfnWndProc = WndProc;
494 wndclass.cbClsExtra = 0;
495 wndclass.cbWndExtra = 0;
496 wndclass.hInstance = inst;
497 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
498 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
499 wndclass.hbrBackground = NULL;
500 wndclass.lpszMenuName = NULL;
501 wndclass.lpszClassName = appname;
503 RegisterClass(&wndclass);
508 term = term_init(&cfg, NULL);
509 logctx = log_init(NULL);
510 term_provide_logctx(term, logctx);
515 * Guess some defaults for the window size. This all gets
516 * updated later, so we don't really care too much. However, we
517 * do want the font width/height guesses to correspond to a
518 * large font rather than a small one...
525 term_size(term, cfg.height, cfg.width, cfg.savelines);
526 guess_width = extra_width + font_width * term->cols;
527 guess_height = extra_height + font_height * term->rows;
530 get_fullscreen_rect(&r);
531 if (guess_width > r.right - r.left)
532 guess_width = r.right - r.left;
533 if (guess_height > r.bottom - r.top)
534 guess_height = r.bottom - r.top;
538 int winmode = WS_OVERLAPPEDWINDOW | WS_VSCROLL;
541 winmode &= ~(WS_VSCROLL);
542 if (cfg.resize_action == RESIZE_DISABLED)
543 winmode &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
545 exwinmode |= WS_EX_TOPMOST;
547 exwinmode |= WS_EX_CLIENTEDGE;
548 hwnd = CreateWindowEx(exwinmode, appname, appname,
549 winmode, CW_USEDEFAULT, CW_USEDEFAULT,
550 guess_width, guess_height,
551 NULL, NULL, inst, NULL);
555 * Initialise the fonts, simultaneously correcting the guesses
556 * for font_{width,height}.
561 * Correct the guesses for extra_{width,height}.
565 GetWindowRect(hwnd, &wr);
566 GetClientRect(hwnd, &cr);
567 offset_width = offset_height = cfg.window_border;
568 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
569 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
573 * Resize the window, now we know what size we _really_ want it
576 guess_width = extra_width + font_width * term->cols;
577 guess_height = extra_height + font_height * term->rows;
578 SetWindowPos(hwnd, NULL, 0, 0, guess_width, guess_height,
579 SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
582 * Set up a caret bitmap, with no content.
586 int size = (font_width + 15) / 16 * 2 * font_height;
587 bits = smalloc(size);
588 memset(bits, 0, size);
589 caretbm = CreateBitmap(font_width, font_height, 1, 1, bits);
592 CreateCaret(hwnd, caretbm, font_width, font_height);
595 * Initialise the scroll bar.
600 si.cbSize = sizeof(si);
601 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
603 si.nMax = term->rows - 1;
604 si.nPage = term->rows;
606 SetScrollInfo(hwnd, SB_VERT, &si, FALSE);
610 * Start up the telnet connection.
614 char msg[1024], *title;
617 error = back->init((void *)term, &backhandle,
618 cfg.host, cfg.port, &realhost, cfg.tcp_nodelay);
619 back->provide_logctx(backhandle, logctx);
621 sprintf(msg, "Unable to open connection to\n"
622 "%.800s\n" "%s", cfg.host, error);
623 MessageBox(NULL, msg, "PuTTY Error", MB_ICONERROR | MB_OK);
626 window_name = icon_name = NULL;
628 title = cfg.wintitle;
630 sprintf(msg, "%s - PuTTY", realhost);
634 set_title(NULL, title);
635 set_icon(NULL, title);
639 * Connect the terminal to the backend for resize purposes.
641 term_provide_resize_fn(term, back->size, backhandle);
644 * Set up a line discipline.
646 ldisc = ldisc_create(&cfg, term, back, backhandle, NULL);
648 session_closed = FALSE;
651 * Prepare the mouse handler.
653 lastact = MA_NOTHING;
654 lastbtn = MBT_NOTHING;
655 dbltime = GetDoubleClickTime();
658 * Set up the session-control options on the system menu.
661 HMENU m = GetSystemMenu(hwnd, FALSE);
665 AppendMenu(m, MF_SEPARATOR, 0, 0);
666 if (cfg.protocol == PROT_TELNET) {
668 AppendMenu(p, MF_ENABLED, IDM_TEL_AYT, "Are You There");
669 AppendMenu(p, MF_ENABLED, IDM_TEL_BRK, "Break");
670 AppendMenu(p, MF_ENABLED, IDM_TEL_SYNCH, "Synch");
671 AppendMenu(p, MF_SEPARATOR, 0, 0);
672 AppendMenu(p, MF_ENABLED, IDM_TEL_EC, "Erase Character");
673 AppendMenu(p, MF_ENABLED, IDM_TEL_EL, "Erase Line");
674 AppendMenu(p, MF_ENABLED, IDM_TEL_GA, "Go Ahead");
675 AppendMenu(p, MF_ENABLED, IDM_TEL_NOP, "No Operation");
676 AppendMenu(p, MF_SEPARATOR, 0, 0);
677 AppendMenu(p, MF_ENABLED, IDM_TEL_ABORT, "Abort Process");
678 AppendMenu(p, MF_ENABLED, IDM_TEL_AO, "Abort Output");
679 AppendMenu(p, MF_ENABLED, IDM_TEL_IP, "Interrupt Process");
680 AppendMenu(p, MF_ENABLED, IDM_TEL_SUSP, "Suspend Process");
681 AppendMenu(p, MF_SEPARATOR, 0, 0);
682 AppendMenu(p, MF_ENABLED, IDM_TEL_EOR, "End Of Record");
683 AppendMenu(p, MF_ENABLED, IDM_TEL_EOF, "End Of File");
684 AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) p,
686 AppendMenu(m, MF_SEPARATOR, 0, 0);
688 AppendMenu(m, MF_ENABLED, IDM_SHOWLOG, "&Event Log");
689 AppendMenu(m, MF_SEPARATOR, 0, 0);
690 AppendMenu(m, MF_ENABLED, IDM_NEWSESS, "Ne&w Session...");
691 AppendMenu(m, MF_ENABLED, IDM_DUPSESS, "&Duplicate Session");
693 get_sesslist(&sesslist, TRUE);
695 i < ((sesslist.nsessions < 256) ? sesslist.nsessions : 256);
697 AppendMenu(s, MF_ENABLED, IDM_SAVED_MIN + (16 * i),
698 sesslist.sessions[i]);
699 AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) s, "Sa&ved Sessions");
700 AppendMenu(m, MF_ENABLED, IDM_RECONF, "Chan&ge Settings...");
701 AppendMenu(m, MF_SEPARATOR, 0, 0);
702 AppendMenu(m, MF_ENABLED, IDM_COPYALL, "C&opy All to Clipboard");
703 AppendMenu(m, MF_ENABLED, IDM_CLRSB, "C&lear Scrollback");
704 AppendMenu(m, MF_ENABLED, IDM_RESET, "Rese&t Terminal");
705 AppendMenu(m, MF_SEPARATOR, 0, 0);
706 AppendMenu(m, (cfg.resize_action == RESIZE_DISABLED) ?
707 MF_GRAYED : MF_ENABLED, IDM_FULLSCREEN, "&Full Screen");
708 AppendMenu(m, MF_SEPARATOR, 0, 0);
710 AppendMenu(m, MF_ENABLED, IDM_HELP, "&Help");
711 AppendMenu(m, MF_ENABLED, IDM_ABOUT, "&About PuTTY");
715 * Set up the initial input locale.
717 set_input_locale(GetKeyboardLayout(0));
720 * Open the initial log file if there is one.
725 * Finally show the window!
727 ShowWindow(hwnd, show);
728 SetForegroundWindow(hwnd);
731 * Set the palette up.
737 term->has_focus = (GetForegroundWindow() == hwnd);
740 if (GetMessage(&msg, NULL, 0, 0) == 1) {
741 int timer_id = 0, long_timer = 0;
743 while (msg.message != WM_QUIT) {
744 /* Sometimes DispatchMessage calls routines that use their own
745 * GetMessage loop, setup this timer so we get some control back.
747 * Also call term_update() from the timer so that if the host
748 * is sending data flat out we still do redraws.
750 if (timer_id && long_timer) {
751 KillTimer(hwnd, timer_id);
752 long_timer = timer_id = 0;
755 timer_id = SetTimer(hwnd, 1, 20, NULL);
756 if (!(IsWindow(logbox) && IsDialogMessage(logbox, &msg)))
757 DispatchMessage(&msg);
759 /* Make sure we blink everything that needs it. */
762 /* Send the paste buffer if there's anything to send */
765 /* If there's nothing new in the queue then we can do everything
766 * we've delayed, reading the socket, writing, and repainting
769 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
772 if (pending_netevent) {
773 enact_pending_netevent();
775 /* Force the cursor blink on */
778 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
782 /* Okay there is now nothing to do so we make sure the screen is
783 * completely up to date then tell windows to call us in a little
787 KillTimer(hwnd, timer_id);
791 if (GetCapture() != hwnd ||
793 !(cfg.mouse_override && is_shift_pressed())))
798 flash_window(1); /* maintain */
800 /* The messages seem unreliable; especially if we're being tricky */
801 term->has_focus = (GetForegroundWindow() == hwnd);
804 /* Hmm, term_update didn't want to do an update too soon ... */
805 timer_id = SetTimer(hwnd, 1, 50, NULL);
806 else if (!term->has_focus)
807 timer_id = SetTimer(hwnd, 1, 500, NULL);
809 timer_id = SetTimer(hwnd, 1, 100, NULL);
812 /* There's no point rescanning everything in the message queue
813 * so we do an apparently unnecessary wait here
816 if (GetMessage(&msg, NULL, 0, 0) != 1)
821 cleanup_exit(msg.wParam); /* this doesn't return... */
822 return msg.wParam; /* ... but optimiser doesn't know */
828 void cleanup_exit(int code)
841 if (cfg.protocol == PROT_SSH) {
852 * Set up, or shut down, an AsyncSelect. Called from winnet.c.
854 char *do_select(SOCKET skt, int startup)
859 events = (FD_CONNECT | FD_READ | FD_WRITE |
860 FD_OOB | FD_CLOSE | FD_ACCEPT);
865 return "do_select(): internal error (hwnd==NULL)";
866 if (WSAAsyncSelect(skt, hwnd, msg, events) == SOCKET_ERROR) {
867 switch (WSAGetLastError()) {
869 return "Network is down";
871 return "WSAAsyncSelect(): unknown error";
878 * set or clear the "raw mouse message" mode
880 void set_raw_mouse_mode(void *frontend, int activate)
882 activate = activate && !cfg.no_mouse_rep;
883 send_raw_mouse = activate;
884 SetCursor(LoadCursor(NULL, activate ? IDC_ARROW : IDC_IBEAM));
888 * Print a message box and close the connection.
890 void connection_fatal(void *frontend, char *fmt, ...)
896 vsprintf(stuff, fmt, ap);
898 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
899 if (cfg.close_on_exit == COE_ALWAYS)
902 session_closed = TRUE;
903 SetWindowText(hwnd, "PuTTY (inactive)");
908 * Report an error at the command-line parsing stage.
910 void cmdline_error(char *fmt, ...)
916 vsprintf(stuff, fmt, ap);
918 MessageBox(hwnd, stuff, "PuTTY Command Line Error", MB_ICONERROR | MB_OK);
923 * Actually do the job requested by a WM_NETEVENT
925 static void enact_pending_netevent(void)
927 static int reentering = 0;
928 extern int select_result(WPARAM, LPARAM);
932 return; /* don't unpend the pending */
934 pending_netevent = FALSE;
937 ret = select_result(pend_netevent_wParam, pend_netevent_lParam);
940 if (ret == 0 && !session_closed) {
941 /* Abnormal exits will already have set session_closed and taken
942 * appropriate action. */
943 if (cfg.close_on_exit == COE_ALWAYS ||
944 cfg.close_on_exit == COE_NORMAL) PostQuitMessage(0);
946 session_closed = TRUE;
947 SetWindowText(hwnd, "PuTTY (inactive)");
948 MessageBox(hwnd, "Connection closed by remote host",
949 "PuTTY", MB_OK | MB_ICONINFORMATION);
955 * Copy the colour palette from the configuration data into defpal.
956 * This is non-trivial because the colour indices are different.
958 static void cfgtopalette(void)
961 static const int ww[] = {
962 6, 7, 8, 9, 10, 11, 12, 13,
963 14, 15, 16, 17, 18, 19, 20, 21,
964 0, 1, 2, 3, 4, 4, 5, 5
967 for (i = 0; i < 24; i++) {
969 defpal[i].rgbtRed = cfg.colours[w][0];
970 defpal[i].rgbtGreen = cfg.colours[w][1];
971 defpal[i].rgbtBlue = cfg.colours[w][2];
976 * Set up the colour palette.
978 static void init_palette(void)
981 HDC hdc = GetDC(hwnd);
983 if (cfg.try_palette && GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) {
984 logpal = smalloc(sizeof(*logpal)
985 - sizeof(logpal->palPalEntry)
986 + NCOLOURS * sizeof(PALETTEENTRY));
987 logpal->palVersion = 0x300;
988 logpal->palNumEntries = NCOLOURS;
989 for (i = 0; i < NCOLOURS; i++) {
990 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
991 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
992 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
993 logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
995 pal = CreatePalette(logpal);
997 SelectPalette(hdc, pal, FALSE);
999 SelectPalette(hdc, GetStockObject(DEFAULT_PALETTE), FALSE);
1002 ReleaseDC(hwnd, hdc);
1005 for (i = 0; i < NCOLOURS; i++)
1006 colours[i] = PALETTERGB(defpal[i].rgbtRed,
1007 defpal[i].rgbtGreen,
1008 defpal[i].rgbtBlue);
1010 for (i = 0; i < NCOLOURS; i++)
1011 colours[i] = RGB(defpal[i].rgbtRed,
1012 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
1016 * Initialise all the fonts we will need initially. There may be as many as
1017 * three or as few as one. The other (poentially) twentyone fonts are done
1018 * if/when they are needed.
1022 * - check the font width and height, correcting our guesses if
1025 * - verify that the bold font is the same width as the ordinary
1026 * one, and engage shadow bolding if not.
1028 * - verify that the underlined font is the same width as the
1029 * ordinary one (manual underlining by means of line drawing can
1030 * be done in a pinch).
1032 static void init_fonts(int pick_width, int pick_height)
1039 int fw_dontcare, fw_bold;
1041 for (i = 0; i < FONT_MAXNO; i++)
1044 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
1045 und_mode = UND_FONT;
1047 if (cfg.fontisbold) {
1048 fw_dontcare = FW_BOLD;
1051 fw_dontcare = FW_DONTCARE;
1058 font_height = pick_height;
1060 font_height = cfg.fontheight;
1061 if (font_height > 0) {
1063 -MulDiv(font_height, GetDeviceCaps(hdc, LOGPIXELSY), 72);
1066 font_width = pick_width;
1068 #define f(i,c,w,u) \
1069 fonts[i] = CreateFont (font_height, font_width, 0, 0, w, FALSE, u, FALSE, \
1070 c, OUT_DEFAULT_PRECIS, \
1071 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, \
1072 FIXED_PITCH | FF_DONTCARE, cfg.font)
1074 f(FONT_NORMAL, cfg.fontcharset, fw_dontcare, FALSE);
1076 lfont.lfHeight = font_height;
1077 lfont.lfWidth = font_width;
1078 lfont.lfEscapement = 0;
1079 lfont.lfOrientation = 0;
1080 lfont.lfWeight = fw_dontcare;
1081 lfont.lfItalic = FALSE;
1082 lfont.lfUnderline = FALSE;
1083 lfont.lfStrikeOut = FALSE;
1084 lfont.lfCharSet = cfg.fontcharset;
1085 lfont.lfOutPrecision = OUT_DEFAULT_PRECIS;
1086 lfont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
1087 lfont.lfQuality = DEFAULT_QUALITY;
1088 lfont.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE;
1089 strncpy(lfont.lfFaceName, cfg.font, LF_FACESIZE);
1091 SelectObject(hdc, fonts[FONT_NORMAL]);
1092 GetTextMetrics(hdc, &tm);
1094 if (pick_width == 0 || pick_height == 0) {
1095 font_height = tm.tmHeight;
1096 font_width = tm.tmAveCharWidth;
1098 font_dualwidth = (tm.tmAveCharWidth != tm.tmMaxCharWidth);
1100 #ifdef RDB_DEBUG_PATCH
1101 debug(23, "Primary font H=%d, AW=%d, MW=%d",
1102 tm.tmHeight, tm.tmAveCharWidth, tm.tmMaxCharWidth);
1107 DWORD cset = tm.tmCharSet;
1108 memset(&info, 0xFF, sizeof(info));
1110 /* !!! Yes the next line is right */
1111 if (cset == OEM_CHARSET)
1112 font_codepage = GetOEMCP();
1114 if (TranslateCharsetInfo
1115 ((DWORD *) cset, &info, TCI_SRCCHARSET)) font_codepage =
1120 GetCPInfo(font_codepage, &cpinfo);
1121 dbcs_screenfont = (cpinfo.MaxCharSize > 1);
1124 f(FONT_UNDERLINE, cfg.fontcharset, fw_dontcare, TRUE);
1127 * Some fonts, e.g. 9-pt Courier, draw their underlines
1128 * outside their character cell. We successfully prevent
1129 * screen corruption by clipping the text output, but then
1130 * we lose the underline completely. Here we try to work
1131 * out whether this is such a font, and if it is, we set a
1132 * flag that causes underlines to be drawn by hand.
1134 * Having tried other more sophisticated approaches (such
1135 * as examining the TEXTMETRIC structure or requesting the
1136 * height of a string), I think we'll do this the brute
1137 * force way: we create a small bitmap, draw an underlined
1138 * space on it, and test to see whether any pixels are
1139 * foreground-coloured. (Since we expect the underline to
1140 * go all the way across the character cell, we only search
1141 * down a single column of the bitmap, half way across.)
1145 HBITMAP und_bm, und_oldbm;
1149 und_dc = CreateCompatibleDC(hdc);
1150 und_bm = CreateCompatibleBitmap(hdc, font_width, font_height);
1151 und_oldbm = SelectObject(und_dc, und_bm);
1152 SelectObject(und_dc, fonts[FONT_UNDERLINE]);
1153 SetTextAlign(und_dc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
1154 SetTextColor(und_dc, RGB(255, 255, 255));
1155 SetBkColor(und_dc, RGB(0, 0, 0));
1156 SetBkMode(und_dc, OPAQUE);
1157 ExtTextOut(und_dc, 0, 0, ETO_OPAQUE, NULL, " ", 1, NULL);
1159 for (i = 0; i < font_height; i++) {
1160 c = GetPixel(und_dc, font_width / 2, i);
1161 if (c != RGB(0, 0, 0))
1164 SelectObject(und_dc, und_oldbm);
1165 DeleteObject(und_bm);
1168 und_mode = UND_LINE;
1169 DeleteObject(fonts[FONT_UNDERLINE]);
1170 fonts[FONT_UNDERLINE] = 0;
1174 if (bold_mode == BOLD_FONT) {
1175 f(FONT_BOLD, cfg.fontcharset, fw_bold, FALSE);
1179 descent = tm.tmAscent + 1;
1180 if (descent >= font_height)
1181 descent = font_height - 1;
1183 for (i = 0; i < 3; i++) {
1185 if (SelectObject(hdc, fonts[i]) && GetTextMetrics(hdc, &tm))
1186 fontsize[i] = tm.tmAveCharWidth + 256 * tm.tmHeight;
1193 ReleaseDC(hwnd, hdc);
1195 if (fontsize[FONT_UNDERLINE] != fontsize[FONT_NORMAL]) {
1196 und_mode = UND_LINE;
1197 DeleteObject(fonts[FONT_UNDERLINE]);
1198 fonts[FONT_UNDERLINE] = 0;
1201 if (bold_mode == BOLD_FONT &&
1202 fontsize[FONT_BOLD] != fontsize[FONT_NORMAL]) {
1203 bold_mode = BOLD_SHADOW;
1204 DeleteObject(fonts[FONT_BOLD]);
1205 fonts[FONT_BOLD] = 0;
1207 fontflag[0] = fontflag[1] = fontflag[2] = 1;
1212 static void another_font(int fontno)
1215 int fw_dontcare, fw_bold;
1219 if (fontno < 0 || fontno >= FONT_MAXNO || fontflag[fontno])
1222 basefont = (fontno & ~(FONT_BOLDUND));
1223 if (basefont != fontno && !fontflag[basefont])
1224 another_font(basefont);
1226 if (cfg.fontisbold) {
1227 fw_dontcare = FW_BOLD;
1230 fw_dontcare = FW_DONTCARE;
1234 c = cfg.fontcharset;
1240 if (fontno & FONT_WIDE)
1242 if (fontno & FONT_NARROW)
1244 if (fontno & FONT_OEM)
1246 if (fontno & FONT_BOLD)
1248 if (fontno & FONT_UNDERLINE)
1252 CreateFont(font_height * (1 + !!(fontno & FONT_HIGH)), x, 0, 0, w,
1253 FALSE, u, FALSE, c, OUT_DEFAULT_PRECIS,
1254 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
1255 FIXED_PITCH | FF_DONTCARE, s);
1257 fontflag[fontno] = 1;
1260 static void deinit_fonts(void)
1263 for (i = 0; i < FONT_MAXNO; i++) {
1265 DeleteObject(fonts[i]);
1271 void request_resize(void *frontend, int w, int h)
1275 /* If the window is maximized supress resizing attempts */
1276 if (IsZoomed(hwnd)) {
1277 if (cfg.resize_action == RESIZE_TERM)
1281 if (cfg.resize_action == RESIZE_DISABLED) return;
1282 if (h == term->rows && w == term->cols) return;
1284 /* Sanity checks ... */
1286 static int first_time = 1;
1289 switch (first_time) {
1291 /* Get the size of the screen */
1292 if (get_fullscreen_rect(&ss))
1293 /* first_time = 0 */ ;
1299 /* Make sure the values are sane */
1300 width = (ss.right - ss.left - extra_width) / 4;
1301 height = (ss.bottom - ss.top - extra_height) / 6;
1303 if (w > width || h > height)
1312 term_size(term, h, w, cfg.savelines);
1314 if (cfg.resize_action != RESIZE_FONT && !IsZoomed(hwnd)) {
1315 width = extra_width + font_width * w;
1316 height = extra_height + font_height * h;
1318 SetWindowPos(hwnd, NULL, 0, 0, width, height,
1319 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1320 SWP_NOMOVE | SWP_NOZORDER);
1324 InvalidateRect(hwnd, NULL, TRUE);
1327 static void reset_window(int reinit) {
1329 * This function decides how to resize or redraw when the
1330 * user changes something.
1332 * This function doesn't like to change the terminal size but if the
1333 * font size is locked that may be it's only soluion.
1335 int win_width, win_height;
1338 #ifdef RDB_DEBUG_PATCH
1339 debug((27, "reset_window()"));
1342 /* Current window sizes ... */
1343 GetWindowRect(hwnd, &wr);
1344 GetClientRect(hwnd, &cr);
1346 win_width = cr.right - cr.left;
1347 win_height = cr.bottom - cr.top;
1349 if (cfg.resize_action == RESIZE_DISABLED) reinit = 2;
1351 /* Are we being forced to reload the fonts ? */
1353 #ifdef RDB_DEBUG_PATCH
1354 debug((27, "reset_window() -- Forced deinit"));
1360 /* Oh, looks like we're minimised */
1361 if (win_width == 0 || win_height == 0)
1364 /* Is the window out of position ? */
1366 (offset_width != (win_width-font_width*term->cols)/2 ||
1367 offset_height != (win_height-font_height*term->rows)/2) ){
1368 offset_width = (win_width-font_width*term->cols)/2;
1369 offset_height = (win_height-font_height*term->rows)/2;
1370 InvalidateRect(hwnd, NULL, TRUE);
1371 #ifdef RDB_DEBUG_PATCH
1372 debug((27, "reset_window() -> Reposition terminal"));
1376 if (IsZoomed(hwnd)) {
1377 /* We're fullscreen, this means we must not change the size of
1378 * the window so it's the font size or the terminal itself.
1381 extra_width = wr.right - wr.left - cr.right + cr.left;
1382 extra_height = wr.bottom - wr.top - cr.bottom + cr.top;
1384 if (cfg.resize_action != RESIZE_TERM) {
1385 if ( font_width != win_width/term->cols ||
1386 font_height != win_height/term->rows) {
1388 init_fonts(win_width/term->cols, win_height/term->rows);
1389 offset_width = (win_width-font_width*term->cols)/2;
1390 offset_height = (win_height-font_height*term->rows)/2;
1391 InvalidateRect(hwnd, NULL, TRUE);
1392 #ifdef RDB_DEBUG_PATCH
1393 debug((25, "reset_window() -> Z font resize to (%d, %d)",
1394 font_width, font_height));
1398 if ( font_width != win_width/term->cols ||
1399 font_height != win_height/term->rows) {
1400 /* Our only choice at this point is to change the
1401 * size of the terminal; Oh well.
1403 term_size(term, win_height/font_height, win_width/font_width,
1405 offset_width = (win_width-font_width*term->cols)/2;
1406 offset_height = (win_height-font_height*term->rows)/2;
1407 InvalidateRect(hwnd, NULL, TRUE);
1408 #ifdef RDB_DEBUG_PATCH
1409 debug((27, "reset_window() -> Zoomed term_size"));
1416 /* Hmm, a force re-init means we should ignore the current window
1417 * so we resize to the default font size.
1420 #ifdef RDB_DEBUG_PATCH
1421 debug((27, "reset_window() -> Forced re-init"));
1424 offset_width = offset_height = cfg.window_border;
1425 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
1426 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
1428 if (win_width != font_width*term->cols + offset_width*2 ||
1429 win_height != font_height*term->rows + offset_height*2) {
1431 /* If this is too large windows will resize it to the maximum
1432 * allowed window size, we will then be back in here and resize
1433 * the font or terminal to fit.
1435 SetWindowPos(hwnd, NULL, 0, 0,
1436 font_width*term->cols + extra_width,
1437 font_height*term->rows + extra_height,
1438 SWP_NOMOVE | SWP_NOZORDER);
1441 InvalidateRect(hwnd, NULL, TRUE);
1445 /* Okay the user doesn't want us to change the font so we try the
1446 * window. But that may be too big for the screen which forces us
1447 * to change the terminal.
1449 if ((cfg.resize_action == RESIZE_TERM && reinit<=0) ||
1450 (cfg.resize_action == RESIZE_EITHER && reinit<0) ||
1452 offset_width = offset_height = cfg.window_border;
1453 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
1454 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
1456 if (win_width != font_width*term->cols + offset_width*2 ||
1457 win_height != font_height*term->rows + offset_height*2) {
1462 get_fullscreen_rect(&ss);
1464 width = (ss.right - ss.left - extra_width) / font_width;
1465 height = (ss.bottom - ss.top - extra_height) / font_height;
1468 if ( term->rows > height || term->cols > width ) {
1469 if (cfg.resize_action == RESIZE_EITHER) {
1470 /* Make the font the biggest we can */
1471 if (term->cols > width)
1472 font_width = (ss.right - ss.left - extra_width)
1474 if (term->rows > height)
1475 font_height = (ss.bottom - ss.top - extra_height)
1479 init_fonts(font_width, font_height);
1481 width = (ss.right - ss.left - extra_width) / font_width;
1482 height = (ss.bottom - ss.top - extra_height) / font_height;
1484 if ( height > term->rows ) height = term->rows;
1485 if ( width > term->cols ) width = term->cols;
1486 term_size(term, height, width, cfg.savelines);
1487 #ifdef RDB_DEBUG_PATCH
1488 debug((27, "reset_window() -> term resize to (%d,%d)",
1494 SetWindowPos(hwnd, NULL, 0, 0,
1495 font_width*term->cols + extra_width,
1496 font_height*term->rows + extra_height,
1497 SWP_NOMOVE | SWP_NOZORDER);
1499 InvalidateRect(hwnd, NULL, TRUE);
1500 #ifdef RDB_DEBUG_PATCH
1501 debug((27, "reset_window() -> window resize to (%d,%d)",
1502 font_width*term->cols + extra_width,
1503 font_height*term->rows + extra_height));
1509 /* We're allowed to or must change the font but do we want to ? */
1511 if (font_width != (win_width-cfg.window_border*2)/term->cols ||
1512 font_height != (win_height-cfg.window_border*2)/term->rows) {
1515 init_fonts((win_width-cfg.window_border*2)/term->cols,
1516 (win_height-cfg.window_border*2)/term->rows);
1517 offset_width = (win_width-font_width*term->cols)/2;
1518 offset_height = (win_height-font_height*term->rows)/2;
1520 extra_width = wr.right - wr.left - cr.right + cr.left +offset_width*2;
1521 extra_height = wr.bottom - wr.top - cr.bottom + cr.top+offset_height*2;
1523 InvalidateRect(hwnd, NULL, TRUE);
1524 #ifdef RDB_DEBUG_PATCH
1525 debug((25, "reset_window() -> font resize to (%d,%d)",
1526 font_width, font_height));
1531 static void set_input_locale(HKL kl)
1535 GetLocaleInfo(LOWORD(kl), LOCALE_IDEFAULTANSICODEPAGE,
1536 lbuf, sizeof(lbuf));
1538 kbd_codepage = atoi(lbuf);
1541 static void click(Mouse_Button b, int x, int y, int shift, int ctrl, int alt)
1543 int thistime = GetMessageTime();
1545 if (send_raw_mouse && !(cfg.mouse_override && shift)) {
1546 lastbtn = MBT_NOTHING;
1547 term_mouse(term, b, MA_CLICK, x, y, shift, ctrl, alt);
1551 if (lastbtn == b && thistime - lasttime < dbltime) {
1552 lastact = (lastact == MA_CLICK ? MA_2CLK :
1553 lastact == MA_2CLK ? MA_3CLK :
1554 lastact == MA_3CLK ? MA_CLICK : MA_NOTHING);
1559 if (lastact != MA_NOTHING)
1560 term_mouse(term, b, lastact, x, y, shift, ctrl, alt);
1561 lasttime = thistime;
1565 * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT)
1566 * into a cooked one (SELECT, EXTEND, PASTE).
1568 Mouse_Button translate_button(void *frontend, Mouse_Button button)
1570 if (button == MBT_LEFT)
1572 if (button == MBT_MIDDLE)
1573 return cfg.mouse_is_xterm ? MBT_PASTE : MBT_EXTEND;
1574 if (button == MBT_RIGHT)
1575 return cfg.mouse_is_xterm ? MBT_EXTEND : MBT_PASTE;
1576 return 0; /* shouldn't happen */
1579 static void show_mouseptr(int show)
1581 static int cursor_visible = 1;
1582 if (!cfg.hide_mouseptr) /* override if this feature disabled */
1584 if (cursor_visible && !show)
1586 else if (!cursor_visible && show)
1588 cursor_visible = show;
1591 static int is_alt_pressed(void)
1594 int r = GetKeyboardState(keystate);
1597 if (keystate[VK_MENU] & 0x80)
1599 if (keystate[VK_RMENU] & 0x80)
1604 static int is_shift_pressed(void)
1607 int r = GetKeyboardState(keystate);
1610 if (keystate[VK_SHIFT] & 0x80)
1615 static int resizing;
1617 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1618 WPARAM wParam, LPARAM lParam)
1621 static int ignore_clip = FALSE;
1622 static int need_backend_resize = FALSE;
1623 static int fullscr_on_max = FALSE;
1627 if (pending_netevent)
1628 enact_pending_netevent();
1629 if (GetCapture() != hwnd ||
1630 (send_raw_mouse && !(cfg.mouse_override && is_shift_pressed())))
1636 if (cfg.ping_interval > 0) {
1639 if (now - last_movement > cfg.ping_interval) {
1640 back->special(backhandle, TS_PING);
1641 last_movement = now;
1644 net_pending_errors();
1650 if (!cfg.warn_on_close || session_closed ||
1652 "Are you sure you want to close this session?",
1653 "PuTTY Exit Confirmation",
1654 MB_ICONWARNING | MB_OKCANCEL) == IDOK)
1655 DestroyWindow(hwnd);
1662 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1674 PROCESS_INFORMATION pi;
1675 HANDLE filemap = NULL;
1677 if (wParam == IDM_DUPSESS) {
1679 * Allocate a file-mapping memory chunk for the
1682 SECURITY_ATTRIBUTES sa;
1685 sa.nLength = sizeof(sa);
1686 sa.lpSecurityDescriptor = NULL;
1687 sa.bInheritHandle = TRUE;
1688 filemap = CreateFileMapping((HANDLE) 0xFFFFFFFF,
1691 0, sizeof(Config), NULL);
1693 p = (Config *) MapViewOfFile(filemap,
1695 0, 0, sizeof(Config));
1697 *p = cfg; /* structure copy */
1701 sprintf(c, "putty &%p", filemap);
1703 } else if (wParam == IDM_SAVEDSESS) {
1704 if ((lParam - IDM_SAVED_MIN) / 16 < sesslist.nsessions) {
1706 sesslist.sessions[(lParam - IDM_SAVED_MIN) / 16];
1707 cl = smalloc(16 + strlen(session));
1708 /* 8, but play safe */
1711 /* not a very important failure mode */
1713 sprintf(cl, "putty @%s", session);
1721 GetModuleFileName(NULL, b, sizeof(b) - 1);
1723 si.lpReserved = NULL;
1724 si.lpDesktop = NULL;
1728 si.lpReserved2 = NULL;
1729 CreateProcess(b, cl, NULL, NULL, TRUE,
1730 NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
1733 CloseHandle(filemap);
1743 GetWindowText(hwnd, cfg.wintitle, sizeof(cfg.wintitle));
1746 if (!do_reconfig(hwnd))
1750 /* Disable full-screen if resizing forbidden */
1751 HMENU m = GetSystemMenu (hwnd, FALSE);
1752 EnableMenuItem(m, IDM_FULLSCREEN, MF_BYCOMMAND |
1753 (cfg.resize_action == RESIZE_DISABLED)
1754 ? MF_GRAYED : MF_ENABLED);
1755 /* Gracefully unzoom if necessary */
1756 if (IsZoomed(hwnd) &&
1757 (cfg.resize_action == RESIZE_DISABLED)) {
1758 ShowWindow(hwnd, SW_RESTORE);
1762 if (strcmp(prev_cfg.logfilename, cfg.logfilename) ||
1763 prev_cfg.logtype != cfg.logtype) {
1764 logfclose(logctx); /* reset logging */
1770 * Flush the line discipline's edit buffer in the
1771 * case where local editing has just been disabled.
1773 ldisc_send(ldisc, NULL, 0, 0);
1781 /* Give terminal a heads-up on miscellaneous stuff */
1782 term_reconfig(term);
1784 /* Screen size changed ? */
1785 if (cfg.height != prev_cfg.height ||
1786 cfg.width != prev_cfg.width ||
1787 cfg.savelines != prev_cfg.savelines ||
1788 cfg.resize_action == RESIZE_FONT ||
1789 (cfg.resize_action == RESIZE_EITHER && IsZoomed(hwnd)) ||
1790 cfg.resize_action == RESIZE_DISABLED)
1791 term_size(term, cfg.height, cfg.width, cfg.savelines);
1793 /* Enable or disable the scroll bar, etc */
1795 LONG nflg, flag = GetWindowLong(hwnd, GWL_STYLE);
1796 LONG nexflag, exflag =
1797 GetWindowLong(hwnd, GWL_EXSTYLE);
1800 if (cfg.alwaysontop != prev_cfg.alwaysontop) {
1801 if (cfg.alwaysontop) {
1802 nexflag |= WS_EX_TOPMOST;
1803 SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
1804 SWP_NOMOVE | SWP_NOSIZE);
1806 nexflag &= ~(WS_EX_TOPMOST);
1807 SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0,
1808 SWP_NOMOVE | SWP_NOSIZE);
1811 if (cfg.sunken_edge)
1812 nexflag |= WS_EX_CLIENTEDGE;
1814 nexflag &= ~(WS_EX_CLIENTEDGE);
1817 if (is_full_screen() ?
1818 cfg.scrollbar_in_fullscreen : cfg.scrollbar)
1821 nflg &= ~WS_VSCROLL;
1823 if (cfg.resize_action == RESIZE_DISABLED ||
1825 nflg &= ~WS_THICKFRAME;
1827 nflg |= WS_THICKFRAME;
1829 if (cfg.resize_action == RESIZE_DISABLED)
1830 nflg &= ~WS_MAXIMIZEBOX;
1832 nflg |= WS_MAXIMIZEBOX;
1834 if (nflg != flag || nexflag != exflag) {
1836 SetWindowLong(hwnd, GWL_STYLE, nflg);
1837 if (nexflag != exflag)
1838 SetWindowLong(hwnd, GWL_EXSTYLE, nexflag);
1840 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
1841 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1842 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
1850 if (cfg.resize_action == RESIZE_DISABLED && IsZoomed(hwnd)) {
1855 set_title(NULL, cfg.wintitle);
1856 if (IsIconic(hwnd)) {
1858 cfg.win_name_always ? window_name :
1862 if (strcmp(cfg.font, prev_cfg.font) != 0 ||
1863 strcmp(cfg.line_codepage, prev_cfg.line_codepage) != 0 ||
1864 cfg.fontisbold != prev_cfg.fontisbold ||
1865 cfg.fontheight != prev_cfg.fontheight ||
1866 cfg.fontcharset != prev_cfg.fontcharset ||
1867 cfg.vtmode != prev_cfg.vtmode ||
1868 cfg.bold_colour != prev_cfg.bold_colour ||
1869 cfg.resize_action == RESIZE_DISABLED ||
1870 cfg.resize_action == RESIZE_EITHER ||
1871 (cfg.resize_action != prev_cfg.resize_action))
1874 InvalidateRect(hwnd, NULL, TRUE);
1875 reset_window(init_lvl);
1876 net_pending_errors();
1887 ldisc_send(ldisc, NULL, 0, 0);
1890 back->special(backhandle, TS_AYT);
1891 net_pending_errors();
1894 back->special(backhandle, TS_BRK);
1895 net_pending_errors();
1898 back->special(backhandle, TS_SYNCH);
1899 net_pending_errors();
1902 back->special(backhandle, TS_EC);
1903 net_pending_errors();
1906 back->special(backhandle, TS_EL);
1907 net_pending_errors();
1910 back->special(backhandle, TS_GA);
1911 net_pending_errors();
1914 back->special(backhandle, TS_NOP);
1915 net_pending_errors();
1918 back->special(backhandle, TS_ABORT);
1919 net_pending_errors();
1922 back->special(backhandle, TS_AO);
1923 net_pending_errors();
1926 back->special(backhandle, TS_IP);
1927 net_pending_errors();
1930 back->special(backhandle, TS_SUSP);
1931 net_pending_errors();
1934 back->special(backhandle, TS_EOR);
1935 net_pending_errors();
1938 back->special(backhandle, TS_EOF);
1939 net_pending_errors();
1945 WinHelp(hwnd, help_path,
1946 help_has_contents ? HELP_FINDER : HELP_CONTENTS, 0);
1950 * We get this if the System menu has been activated
1957 * We get this if the System menu has been activated
1958 * using the keyboard. This might happen from within
1959 * TranslateKey, in which case it really wants to be
1960 * followed by a `space' character to actually _bring
1961 * the menu up_ rather than just sitting there in
1962 * `ready to appear' state.
1964 show_mouseptr(1); /* make sure pointer is visible */
1966 PostMessage(hwnd, WM_CHAR, ' ', 0);
1968 case IDM_FULLSCREEN:
1972 if (wParam >= IDM_SAVED_MIN && wParam <= IDM_SAVED_MAX) {
1973 SendMessage(hwnd, WM_SYSCOMMAND, IDM_SAVEDSESS, wParam);
1978 #define X_POS(l) ((int)(short)LOWORD(l))
1979 #define Y_POS(l) ((int)(short)HIWORD(l))
1981 #define TO_CHR_X(x) ((((x)<0 ? (x)-font_width+1 : (x))-offset_width) / font_width)
1982 #define TO_CHR_Y(y) ((((y)<0 ? (y)-font_height+1: (y))-offset_height) / font_height)
1983 case WM_LBUTTONDOWN:
1984 case WM_MBUTTONDOWN:
1985 case WM_RBUTTONDOWN:
1993 case WM_LBUTTONDOWN:
1997 case WM_MBUTTONDOWN:
1998 button = MBT_MIDDLE;
2001 case WM_RBUTTONDOWN:
2010 button = MBT_MIDDLE;
2018 button = press = 0; /* shouldn't happen */
2022 * Special case: in full-screen mode, if the left
2023 * button is clicked in the very top left corner of the
2024 * window, we put up the System menu instead of doing
2027 if (is_full_screen() && press && button == MBT_LEFT &&
2028 X_POS(lParam) == 0 && Y_POS(lParam) == 0) {
2029 SendMessage(hwnd, WM_SYSCOMMAND, SC_MOUSEMENU, 0);
2034 TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam)),
2035 wParam & MK_SHIFT, wParam & MK_CONTROL,
2039 term_mouse(term, button, MA_RELEASE,
2040 TO_CHR_X(X_POS(lParam)),
2041 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
2042 wParam & MK_CONTROL, is_alt_pressed());
2050 * Add the mouse position and message time to the random
2053 noise_ultralight(lParam);
2055 if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON) &&
2056 GetCapture() == hwnd) {
2058 if (wParam & MK_LBUTTON)
2060 else if (wParam & MK_MBUTTON)
2064 term_mouse(term, b, MA_DRAG, TO_CHR_X(X_POS(lParam)),
2065 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
2066 wParam & MK_CONTROL, is_alt_pressed());
2069 case WM_NCMOUSEMOVE:
2071 noise_ultralight(lParam);
2073 case WM_IGNORE_CLIP:
2074 ignore_clip = wParam; /* don't panic on DESTROYCLIPBOARD */
2076 case WM_DESTROYCLIPBOARD:
2078 term_deselect(term);
2079 ignore_clip = FALSE;
2085 hdc = BeginPaint(hwnd, &p);
2087 SelectPalette(hdc, pal, TRUE);
2088 RealizePalette(hdc);
2090 term_paint(term, hdc,
2091 (p.rcPaint.left-offset_width)/font_width,
2092 (p.rcPaint.top-offset_height)/font_height,
2093 (p.rcPaint.right-offset_width-1)/font_width,
2094 (p.rcPaint.bottom-offset_height-1)/font_height,
2098 p.rcPaint.left < offset_width ||
2099 p.rcPaint.top < offset_height ||
2100 p.rcPaint.right >= offset_width + font_width*term->cols ||
2101 p.rcPaint.bottom>= offset_height + font_height*term->rows)
2103 HBRUSH fillcolour, oldbrush;
2105 fillcolour = CreateSolidBrush (
2106 colours[(ATTR_DEFBG>>ATTR_BGSHIFT)*2]);
2107 oldbrush = SelectObject(hdc, fillcolour);
2108 edge = CreatePen(PS_SOLID, 0,
2109 colours[(ATTR_DEFBG>>ATTR_BGSHIFT)*2]);
2110 oldpen = SelectObject(hdc, edge);
2113 * Jordan Russell reports that this apparently
2114 * ineffectual IntersectClipRect() call masks a
2115 * Windows NT/2K bug causing strange display
2116 * problems when the PuTTY window is taller than
2117 * the primary monitor. It seems harmless enough...
2119 IntersectClipRect(hdc,
2120 p.rcPaint.left, p.rcPaint.top,
2121 p.rcPaint.right, p.rcPaint.bottom);
2123 ExcludeClipRect(hdc,
2124 offset_width, offset_height,
2125 offset_width+font_width*term->cols,
2126 offset_height+font_height*term->rows);
2128 Rectangle(hdc, p.rcPaint.left, p.rcPaint.top,
2129 p.rcPaint.right, p.rcPaint.bottom);
2131 // SelectClipRgn(hdc, NULL);
2133 SelectObject(hdc, oldbrush);
2134 DeleteObject(fillcolour);
2135 SelectObject(hdc, oldpen);
2138 SelectObject(hdc, GetStockObject(SYSTEM_FONT));
2139 SelectObject(hdc, GetStockObject(WHITE_PEN));
2145 /* Notice we can get multiple netevents, FD_READ, FD_WRITE etc
2146 * but the only one that's likely to try to overload us is FD_READ.
2147 * This means buffering just one is fine.
2149 if (pending_netevent)
2150 enact_pending_netevent();
2152 pending_netevent = TRUE;
2153 pend_netevent_wParam = wParam;
2154 pend_netevent_lParam = lParam;
2155 if (WSAGETSELECTEVENT(lParam) != FD_READ)
2156 enact_pending_netevent();
2158 time(&last_movement);
2161 term->has_focus = TRUE;
2162 CreateCaret(hwnd, caretbm, font_width, font_height);
2164 flash_window(0); /* stop */
2171 term->has_focus = FALSE;
2173 caret_x = caret_y = -1; /* ensure caret is replaced next time */
2177 case WM_ENTERSIZEMOVE:
2178 #ifdef RDB_DEBUG_PATCH
2179 debug((27, "WM_ENTERSIZEMOVE"));
2183 need_backend_resize = FALSE;
2185 case WM_EXITSIZEMOVE:
2188 #ifdef RDB_DEBUG_PATCH
2189 debug((27, "WM_EXITSIZEMOVE"));
2191 if (need_backend_resize) {
2192 term_size(term, cfg.height, cfg.width, cfg.savelines);
2193 InvalidateRect(hwnd, NULL, TRUE);
2198 * This does two jobs:
2199 * 1) Keep the sizetip uptodate
2200 * 2) Make sure the window size is _stepped_ in units of the font size.
2202 if (cfg.resize_action != RESIZE_FONT && !is_alt_pressed()) {
2203 int width, height, w, h, ew, eh;
2204 LPRECT r = (LPRECT) lParam;
2206 if ( !need_backend_resize && cfg.resize_action == RESIZE_EITHER &&
2207 (cfg.height != term->rows || cfg.width != term->cols )) {
2209 * Great! It seems that both the terminal size and the
2210 * font size have been changed and the user is now dragging.
2212 * It will now be difficult to get back to the configured
2215 * This would be easier but it seems to be too confusing.
2217 term_size(term, cfg.height, cfg.width, cfg.savelines);
2220 cfg.height=term->rows; cfg.width=term->cols;
2222 InvalidateRect(hwnd, NULL, TRUE);
2223 need_backend_resize = TRUE;
2226 width = r->right - r->left - extra_width;
2227 height = r->bottom - r->top - extra_height;
2228 w = (width + font_width / 2) / font_width;
2231 h = (height + font_height / 2) / font_height;
2234 UpdateSizeTip(hwnd, w, h);
2235 ew = width - w * font_width;
2236 eh = height - h * font_height;
2238 if (wParam == WMSZ_LEFT ||
2239 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
2245 if (wParam == WMSZ_TOP ||
2246 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
2256 int width, height, w, h, rv = 0;
2257 int ex_width = extra_width + (cfg.window_border - offset_width) * 2;
2258 int ex_height = extra_height + (cfg.window_border - offset_height) * 2;
2259 LPRECT r = (LPRECT) lParam;
2261 width = r->right - r->left - ex_width;
2262 height = r->bottom - r->top - ex_height;
2264 w = (width + term->cols/2)/term->cols;
2265 h = (height + term->rows/2)/term->rows;
2266 if ( r->right != r->left + w*term->cols + ex_width)
2269 if (wParam == WMSZ_LEFT ||
2270 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
2271 r->left = r->right - w*term->cols - ex_width;
2273 r->right = r->left + w*term->cols + ex_width;
2275 if (r->bottom != r->top + h*term->rows + ex_height)
2278 if (wParam == WMSZ_TOP ||
2279 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
2280 r->top = r->bottom - h*term->rows - ex_height;
2282 r->bottom = r->top + h*term->rows + ex_height;
2286 /* break; (never reached) */
2287 case WM_FULLSCR_ON_MAX:
2288 fullscr_on_max = TRUE;
2291 sys_cursor_update();
2294 #ifdef RDB_DEBUG_PATCH
2295 debug((27, "WM_SIZE %s (%d,%d)",
2296 (wParam == SIZE_MINIMIZED) ? "SIZE_MINIMIZED":
2297 (wParam == SIZE_MAXIMIZED) ? "SIZE_MAXIMIZED":
2298 (wParam == SIZE_RESTORED && resizing) ? "to":
2299 (wParam == SIZE_RESTORED) ? "SIZE_RESTORED":
2301 LOWORD(lParam), HIWORD(lParam)));
2303 if (wParam == SIZE_MINIMIZED)
2305 cfg.win_name_always ? window_name : icon_name);
2306 if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
2307 SetWindowText(hwnd, window_name);
2308 if (wParam == SIZE_RESTORED)
2309 clear_full_screen();
2310 if (wParam == SIZE_MAXIMIZED && fullscr_on_max) {
2311 fullscr_on_max = FALSE;
2315 if (cfg.resize_action == RESIZE_DISABLED) {
2316 /* A resize, well it better be a minimize. */
2320 int width, height, w, h;
2322 width = LOWORD(lParam);
2323 height = HIWORD(lParam);
2326 if (wParam == SIZE_MAXIMIZED && !was_zoomed) {
2328 prev_rows = term->rows;
2329 prev_cols = term->cols;
2330 if (cfg.resize_action == RESIZE_TERM) {
2331 w = width / font_width;
2333 h = height / font_height;
2336 term_size(term, h, w, cfg.savelines);
2339 } else if (wParam == SIZE_RESTORED && was_zoomed) {
2341 if (cfg.resize_action == RESIZE_TERM)
2342 term_size(term, prev_rows, prev_cols, cfg.savelines);
2343 if (cfg.resize_action != RESIZE_FONT)
2348 /* This is an unexpected resize, these will normally happen
2349 * if the window is too large. Probably either the user
2350 * selected a huge font or the screen size has changed.
2352 * This is also called with minimize.
2354 else reset_window(-1);
2358 * Don't call back->size in mid-resize. (To prevent
2359 * massive numbers of resize events getting sent
2360 * down the connection during an NT opaque drag.)
2363 if (cfg.resize_action != RESIZE_FONT && !is_alt_pressed()) {
2364 need_backend_resize = TRUE;
2365 w = (width-cfg.window_border*2) / font_width;
2367 h = (height-cfg.window_border*2) / font_height;
2376 sys_cursor_update();
2379 switch (LOWORD(wParam)) {
2381 term_scroll(term, -1, 0);
2384 term_scroll(term, +1, 0);
2387 term_scroll(term, 0, +1);
2390 term_scroll(term, 0, -1);
2393 term_scroll(term, 0, +term->rows / 2);
2396 term_scroll(term, 0, -term->rows / 2);
2398 case SB_THUMBPOSITION:
2400 term_scroll(term, 1, HIWORD(wParam));
2404 case WM_PALETTECHANGED:
2405 if ((HWND) wParam != hwnd && pal != NULL) {
2406 HDC hdc = get_ctx(NULL);
2408 if (RealizePalette(hdc) > 0)
2414 case WM_QUERYNEWPALETTE:
2416 HDC hdc = get_ctx(NULL);
2418 if (RealizePalette(hdc) > 0)
2430 * Add the scan code and keypress timing to the random
2433 noise_ultralight(lParam);
2436 * We don't do TranslateMessage since it disassociates the
2437 * resulting CHAR message from the KEYDOWN that sparked it,
2438 * which we occasionally don't want. Instead, we process
2439 * KEYDOWN, and call the Win32 translator functions so that
2440 * we get the translations under _our_ control.
2443 unsigned char buf[20];
2446 if (wParam == VK_PROCESSKEY) {
2449 m.message = WM_KEYDOWN;
2451 m.lParam = lParam & 0xdfff;
2452 TranslateMessage(&m);
2454 len = TranslateKey(message, wParam, lParam, buf);
2456 return DefWindowProc(hwnd, message, wParam, lParam);
2460 * Interrupt an ongoing paste. I'm not sure
2461 * this is sensible, but for the moment it's
2462 * preferable to having to faff about buffering
2468 * We need not bother about stdin backlogs
2469 * here, because in GUI PuTTY we can't do
2470 * anything about it anyway; there's no means
2471 * of asking Windows to hold off on KEYDOWN
2472 * messages. We _have_ to buffer everything
2475 term_seen_key_event(term);
2476 ldisc_send(ldisc, buf, len, 1);
2481 net_pending_errors();
2483 case WM_INPUTLANGCHANGE:
2484 /* wParam == Font number */
2485 /* lParam == Locale */
2486 set_input_locale((HKL)lParam);
2487 sys_cursor_update();
2490 if(wParam == IMN_SETOPENSTATUS) {
2491 HIMC hImc = ImmGetContext(hwnd);
2492 ImmSetCompositionFont(hImc, &lfont);
2493 ImmReleaseContext(hwnd, hImc);
2497 case WM_IME_COMPOSITION:
2503 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ||
2504 osVersion.dwPlatformId == VER_PLATFORM_WIN32s) break; /* no Unicode */
2506 if ((lParam & GCS_RESULTSTR) == 0) /* Composition unfinished. */
2507 break; /* fall back to DefWindowProc */
2509 hIMC = ImmGetContext(hwnd);
2510 n = ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, NULL, 0);
2514 buff = (char*) smalloc(n);
2515 ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, buff, n);
2517 * Jaeyoun Chung reports that Korean character
2518 * input doesn't work correctly if we do a single
2519 * luni_send() covering the whole of buff. So
2520 * instead we luni_send the characters one by one.
2522 term_seen_key_event(term);
2523 for (i = 0; i < n; i += 2) {
2524 luni_send(ldisc, (unsigned short *)(buff+i), 1, 1);
2528 ImmReleaseContext(hwnd, hIMC);
2533 if (wParam & 0xFF00) {
2534 unsigned char buf[2];
2537 buf[0] = wParam >> 8;
2538 term_seen_key_event(term);
2539 lpage_send(ldisc, kbd_codepage, buf, 2, 1);
2541 char c = (unsigned char) wParam;
2542 term_seen_key_event(term);
2543 lpage_send(ldisc, kbd_codepage, &c, 1, 1);
2549 * Nevertheless, we are prepared to deal with WM_CHAR
2550 * messages, should they crop up. So if someone wants to
2551 * post the things to us as part of a macro manoeuvre,
2552 * we're ready to cope.
2555 char c = (unsigned char)wParam;
2556 term_seen_key_event(term);
2557 lpage_send(ldisc, CP_ACP, &c, 1, 1);
2561 if (send_raw_mouse && LOWORD(lParam) == HTCLIENT) {
2562 SetCursor(LoadCursor(NULL, IDC_ARROW));
2566 if (message == wm_mousewheel || message == WM_MOUSEWHEEL) {
2567 int shift_pressed=0, control_pressed=0;
2569 if (message == WM_MOUSEWHEEL) {
2570 wheel_accumulator += (short)HIWORD(wParam);
2571 shift_pressed=LOWORD(wParam) & MK_SHIFT;
2572 control_pressed=LOWORD(wParam) & MK_CONTROL;
2575 wheel_accumulator += (int)wParam;
2576 if (GetKeyboardState(keys)!=0) {
2577 shift_pressed=keys[VK_SHIFT]&0x80;
2578 control_pressed=keys[VK_CONTROL]&0x80;
2582 /* process events when the threshold is reached */
2583 while (abs(wheel_accumulator) >= WHEEL_DELTA) {
2586 /* reduce amount for next time */
2587 if (wheel_accumulator > 0) {
2589 wheel_accumulator -= WHEEL_DELTA;
2590 } else if (wheel_accumulator < 0) {
2592 wheel_accumulator += WHEEL_DELTA;
2596 if (send_raw_mouse &&
2597 !(cfg.mouse_override && shift_pressed)) {
2598 /* send a mouse-down followed by a mouse up */
2601 TO_CHR_X(X_POS(lParam)),
2602 TO_CHR_Y(Y_POS(lParam)), shift_pressed,
2603 control_pressed, is_alt_pressed());
2604 term_mouse(term, b, MA_RELEASE, TO_CHR_X(X_POS(lParam)),
2605 TO_CHR_Y(Y_POS(lParam)), shift_pressed,
2606 control_pressed, is_alt_pressed());
2608 /* trigger a scroll */
2609 term_scroll(term, 0,
2611 -term->rows / 2 : term->rows / 2);
2618 return DefWindowProc(hwnd, message, wParam, lParam);
2622 * Move the system caret. (We maintain one, even though it's
2623 * invisible, for the benefit of blind people: apparently some
2624 * helper software tracks the system caret, so we should arrange to
2627 void sys_cursor(void *frontend, int x, int y)
2631 if (!term->has_focus) return;
2634 * Avoid gratuitously re-updating the cursor position and IMM
2635 * window if there's no actual change required.
2637 cx = x * font_width + offset_width;
2638 cy = y * font_height + offset_height;
2639 if (cx == caret_x && cy == caret_y)
2644 sys_cursor_update();
2647 static void sys_cursor_update(void)
2652 if (!term->has_focus) return;
2654 if (caret_x < 0 || caret_y < 0)
2657 SetCaretPos(caret_x, caret_y);
2659 /* IMM calls on Win98 and beyond only */
2660 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32s) return; /* 3.11 */
2662 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS &&
2663 osVersion.dwMinorVersion == 0) return; /* 95 */
2665 /* we should have the IMM functions */
2666 hIMC = ImmGetContext(hwnd);
2667 cf.dwStyle = CFS_POINT;
2668 cf.ptCurrentPos.x = caret_x;
2669 cf.ptCurrentPos.y = caret_y;
2670 ImmSetCompositionWindow(hIMC, &cf);
2672 ImmReleaseContext(hwnd, hIMC);
2676 * Draw a line of text in the window, at given character
2677 * coordinates, in given attributes.
2679 * We are allowed to fiddle with the contents of `text'.
2681 void do_text(Context ctx, int x, int y, char *text, int len,
2682 unsigned long attr, int lattr)
2685 int nfg, nbg, nfont;
2688 int force_manual_underline = 0;
2689 int fnt_width = font_width * (1 + (lattr != LATTR_NORM));
2690 int char_width = fnt_width;
2691 int text_adjust = 0;
2692 static int *IpDx = 0, IpDxLEN = 0;
2694 if (attr & ATTR_WIDE)
2697 if (len > IpDxLEN || IpDx[0] != char_width) {
2699 if (len > IpDxLEN) {
2701 IpDx = smalloc((len + 16) * sizeof(int));
2702 IpDxLEN = (len + 16);
2704 for (i = 0; i < IpDxLEN; i++)
2705 IpDx[i] = char_width;
2708 /* Only want the left half of double width lines */
2709 if (lattr != LATTR_NORM && x*2 >= term->cols)
2717 if ((attr & TATTR_ACTCURS) && (cfg.cursor_type == 0 || term->big_cursor)) {
2718 attr &= ATTR_CUR_AND | (bold_mode != BOLD_COLOURS ? ATTR_BOLD : 0);
2719 attr ^= ATTR_CUR_XOR;
2723 if (cfg.vtmode == VT_POORMAN && lattr != LATTR_NORM) {
2724 /* Assume a poorman font is borken in other ways too. */
2734 nfont |= FONT_WIDE + FONT_HIGH;
2737 if (attr & ATTR_NARROW)
2738 nfont |= FONT_NARROW;
2740 /* Special hack for the VT100 linedraw glyphs. */
2741 if ((attr & CSET_MASK) == 0x2300) {
2742 if (text[0] >= (char) 0xBA && text[0] <= (char) 0xBD) {
2743 switch ((unsigned char) (text[0])) {
2745 text_adjust = -2 * font_height / 5;
2748 text_adjust = -1 * font_height / 5;
2751 text_adjust = font_height / 5;
2754 text_adjust = 2 * font_height / 5;
2757 if (lattr == LATTR_TOP || lattr == LATTR_BOT)
2760 text[0] = (char) (unitab_xterm['q'] & CHAR_MASK);
2761 attr |= (unitab_xterm['q'] & CSET_MASK);
2762 if (attr & ATTR_UNDER) {
2763 attr &= ~ATTR_UNDER;
2764 force_manual_underline = 1;
2769 /* Anything left as an original character set is unprintable. */
2770 if (DIRECT_CHAR(attr)) {
2773 memset(text, 0xFD, len);
2777 if ((attr & CSET_MASK) == ATTR_OEMCP)
2780 nfg = 2 * ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
2781 nbg = 2 * ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
2782 if (bold_mode == BOLD_FONT && (attr & ATTR_BOLD))
2784 if (und_mode == UND_FONT && (attr & ATTR_UNDER))
2785 nfont |= FONT_UNDERLINE;
2786 another_font(nfont);
2787 if (!fonts[nfont]) {
2788 if (nfont & FONT_UNDERLINE)
2789 force_manual_underline = 1;
2790 /* Don't do the same for manual bold, it could be bad news. */
2792 nfont &= ~(FONT_BOLD | FONT_UNDERLINE);
2794 another_font(nfont);
2796 nfont = FONT_NORMAL;
2797 if (attr & ATTR_REVERSE) {
2802 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BOLD))
2804 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BLINK))
2808 SelectObject(hdc, fonts[nfont]);
2809 SetTextColor(hdc, fg);
2810 SetBkColor(hdc, bg);
2811 SetBkMode(hdc, OPAQUE);
2814 line_box.right = x + char_width * len;
2815 line_box.bottom = y + font_height;
2817 /* Only want the left half of double width lines */
2818 if (line_box.right > font_width*term->cols+offset_width)
2819 line_box.right = font_width*term->cols+offset_width;
2821 /* We're using a private area for direct to font. (512 chars.) */
2822 if (dbcs_screenfont && (attr & CSET_MASK) == ATTR_ACP) {
2823 /* Ho Hum, dbcs fonts are a PITA! */
2824 /* To display on W9x I have to convert to UCS */
2825 static wchar_t *uni_buf = 0;
2826 static int uni_len = 0;
2828 if (len > uni_len) {
2830 uni_buf = smalloc((uni_len = len) * sizeof(wchar_t));
2833 for(nlen = mptr = 0; mptr<len; mptr++) {
2834 uni_buf[nlen] = 0xFFFD;
2835 if (IsDBCSLeadByteEx(font_codepage, (BYTE) text[mptr])) {
2836 IpDx[nlen] += char_width;
2837 MultiByteToWideChar(font_codepage, MB_USEGLYPHCHARS,
2838 text+mptr, 2, uni_buf+nlen, 1);
2843 MultiByteToWideChar(font_codepage, MB_USEGLYPHCHARS,
2844 text+mptr, 1, uni_buf+nlen, 1);
2852 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2853 ETO_CLIPPED | ETO_OPAQUE, &line_box, uni_buf, nlen, IpDx);
2854 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2855 SetBkMode(hdc, TRANSPARENT);
2856 ExtTextOutW(hdc, x - 1,
2857 y - font_height * (lattr ==
2858 LATTR_BOT) + text_adjust,
2859 ETO_CLIPPED, &line_box, uni_buf, nlen, IpDx);
2863 } else if (DIRECT_FONT(attr)) {
2865 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2866 ETO_CLIPPED | ETO_OPAQUE, &line_box, text, len, IpDx);
2867 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2868 SetBkMode(hdc, TRANSPARENT);
2870 /* GRR: This draws the character outside it's box and can leave
2871 * 'droppings' even with the clip box! I suppose I could loop it
2872 * one character at a time ... yuk.
2874 * Or ... I could do a test print with "W", and use +1 or -1 for this
2875 * shift depending on if the leftmost column is blank...
2877 ExtTextOut(hdc, x - 1,
2878 y - font_height * (lattr ==
2879 LATTR_BOT) + text_adjust,
2880 ETO_CLIPPED, &line_box, text, len, IpDx);
2883 /* And 'normal' unicode characters */
2884 static WCHAR *wbuf = NULL;
2885 static int wlen = 0;
2890 wbuf = smalloc(wlen * sizeof(WCHAR));
2892 for (i = 0; i < len; i++)
2893 wbuf[i] = (WCHAR) ((attr & CSET_MASK) + (text[i] & CHAR_MASK));
2896 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2897 ETO_CLIPPED | ETO_OPAQUE, &line_box, wbuf, len, IpDx);
2899 /* And the shadow bold hack. */
2900 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2901 SetBkMode(hdc, TRANSPARENT);
2902 ExtTextOutW(hdc, x - 1,
2903 y - font_height * (lattr ==
2904 LATTR_BOT) + text_adjust,
2905 ETO_CLIPPED, &line_box, wbuf, len, IpDx);
2908 if (lattr != LATTR_TOP && (force_manual_underline ||
2909 (und_mode == UND_LINE
2910 && (attr & ATTR_UNDER)))) {
2913 if (lattr == LATTR_BOT)
2914 dec = dec * 2 - font_height;
2916 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, fg));
2917 MoveToEx(hdc, x, y + dec, NULL);
2918 LineTo(hdc, x + len * char_width, y + dec);
2919 oldpen = SelectObject(hdc, oldpen);
2920 DeleteObject(oldpen);
2924 void do_cursor(Context ctx, int x, int y, char *text, int len,
2925 unsigned long attr, int lattr)
2931 int ctype = cfg.cursor_type;
2933 if ((attr & TATTR_ACTCURS) && (ctype == 0 || term->big_cursor)) {
2934 if (((attr & CSET_MASK) | (unsigned char) *text) != UCSWIDE) {
2935 do_text(ctx, x, y, text, len, attr, lattr);
2939 attr |= TATTR_RIGHTCURS;
2942 fnt_width = char_width = font_width * (1 + (lattr != LATTR_NORM));
2943 if (attr & ATTR_WIDE)
2950 if ((attr & TATTR_PASCURS) && (ctype == 0 || term->big_cursor)) {
2953 pts[0].x = pts[1].x = pts[4].x = x;
2954 pts[2].x = pts[3].x = x + char_width - 1;
2955 pts[0].y = pts[3].y = pts[4].y = y;
2956 pts[1].y = pts[2].y = y + font_height - 1;
2957 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2958 Polyline(hdc, pts, 5);
2959 oldpen = SelectObject(hdc, oldpen);
2960 DeleteObject(oldpen);
2961 } else if ((attr & (TATTR_ACTCURS | TATTR_PASCURS)) && ctype != 0) {
2962 int startx, starty, dx, dy, length, i;
2965 starty = y + descent;
2968 length = char_width;
2971 if (attr & TATTR_RIGHTCURS)
2972 xadjust = char_width - 1;
2973 startx = x + xadjust;
2977 length = font_height;
2979 if (attr & TATTR_ACTCURS) {
2982 SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2983 MoveToEx(hdc, startx, starty, NULL);
2984 LineTo(hdc, startx + dx * length, starty + dy * length);
2985 oldpen = SelectObject(hdc, oldpen);
2986 DeleteObject(oldpen);
2988 for (i = 0; i < length; i++) {
2990 SetPixel(hdc, startx, starty, colours[23]);
2999 /* This function gets the actual width of a character in the normal font.
3001 int char_width(Context ctx, int uc) {
3005 /* If the font max is the same as the font ave width then this
3006 * function is a no-op.
3008 if (!font_dualwidth) return 1;
3010 switch (uc & CSET_MASK) {
3012 uc = unitab_line[uc & 0xFF];
3015 uc = unitab_xterm[uc & 0xFF];
3018 uc = unitab_scoacs[uc & 0xFF];
3021 if (DIRECT_FONT(uc)) {
3022 if (dbcs_screenfont) return 1;
3024 /* Speedup, I know of no font where ascii is the wrong width */
3025 if ((uc&CHAR_MASK) >= ' ' && (uc&CHAR_MASK)<= '~')
3028 if ( (uc & CSET_MASK) == ATTR_ACP ) {
3029 SelectObject(hdc, fonts[FONT_NORMAL]);
3030 } else if ( (uc & CSET_MASK) == ATTR_OEMCP ) {
3031 another_font(FONT_OEM);
3032 if (!fonts[FONT_OEM]) return 0;
3034 SelectObject(hdc, fonts[FONT_OEM]);
3038 if ( GetCharWidth32(hdc, uc&CHAR_MASK, uc&CHAR_MASK, &ibuf) != 1 &&
3039 GetCharWidth(hdc, uc&CHAR_MASK, uc&CHAR_MASK, &ibuf) != 1)
3042 /* Speedup, I know of no font where ascii is the wrong width */
3043 if (uc >= ' ' && uc <= '~') return 1;
3045 SelectObject(hdc, fonts[FONT_NORMAL]);
3046 if ( GetCharWidth32W(hdc, uc, uc, &ibuf) == 1 )
3047 /* Okay that one worked */ ;
3048 else if ( GetCharWidthW(hdc, uc, uc, &ibuf) == 1 )
3049 /* This should work on 9x too, but it's "less accurate" */ ;
3054 ibuf += font_width / 2 -1;
3061 * Translate a WM_(SYS)?KEY(UP|DOWN) message into a string of ASCII
3062 * codes. Returns number of bytes used or zero to drop the message
3063 * or -1 to forward the message to windows.
3065 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
3066 unsigned char *output)
3069 int scan, left_alt = 0, key_down, shift_state;
3071 unsigned char *p = output;
3072 static int alt_sum = 0;
3074 HKL kbd_layout = GetKeyboardLayout(0);
3076 static WORD keys[3];
3077 static int compose_char = 0;
3078 static WPARAM compose_key = 0;
3080 r = GetKeyboardState(keystate);
3082 memset(keystate, 0, sizeof(keystate));
3085 #define SHOW_TOASCII_RESULT
3086 { /* Tell us all about key events */
3087 static BYTE oldstate[256];
3088 static int first = 1;
3092 memcpy(oldstate, keystate, sizeof(oldstate));
3095 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT) {
3097 } else if ((HIWORD(lParam) & KF_UP)
3098 && scan == (HIWORD(lParam) & 0xFF)) {
3102 if (wParam >= VK_F1 && wParam <= VK_F20)
3103 debug(("K_F%d", wParam + 1 - VK_F1));
3116 debug(("VK_%02x", wParam));
3118 if (message == WM_SYSKEYDOWN || message == WM_SYSKEYUP)
3120 debug((", S%02x", scan = (HIWORD(lParam) & 0xFF)));
3122 ch = MapVirtualKeyEx(wParam, 2, kbd_layout);
3123 if (ch >= ' ' && ch <= '~')
3124 debug((", '%c'", ch));
3126 debug((", $%02x", ch));
3129 debug((", KB0=%02x", keys[0]));
3131 debug((", KB1=%02x", keys[1]));
3133 debug((", KB2=%02x", keys[2]));
3135 if ((keystate[VK_SHIFT] & 0x80) != 0)
3137 if ((keystate[VK_CONTROL] & 0x80) != 0)
3139 if ((HIWORD(lParam) & KF_EXTENDED))
3141 if ((HIWORD(lParam) & KF_UP))
3145 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT);
3146 else if ((HIWORD(lParam) & KF_UP))
3147 oldstate[wParam & 0xFF] ^= 0x80;
3149 oldstate[wParam & 0xFF] ^= 0x81;
3151 for (ch = 0; ch < 256; ch++)
3152 if (oldstate[ch] != keystate[ch])
3153 debug((", M%02x=%02x", ch, keystate[ch]));
3155 memcpy(oldstate, keystate, sizeof(oldstate));
3159 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED)) {
3160 keystate[VK_RMENU] = keystate[VK_MENU];
3164 /* Nastyness with NUMLock - Shift-NUMLock is left alone though */
3165 if ((cfg.funky_type == 3 ||
3166 (cfg.funky_type <= 1 && term->app_keypad_keys &&
3168 && wParam == VK_NUMLOCK && !(keystate[VK_SHIFT] & 0x80)) {
3170 wParam = VK_EXECUTE;
3172 /* UnToggle NUMLock */
3173 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0)
3174 keystate[VK_NUMLOCK] ^= 1;
3177 /* And write back the 'adjusted' state */
3178 SetKeyboardState(keystate);
3181 /* Disable Auto repeat if required */
3182 if (term->repeat_off &&
3183 (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT)
3186 if ((HIWORD(lParam) & KF_ALTDOWN) && (keystate[VK_RMENU] & 0x80) == 0)
3189 key_down = ((HIWORD(lParam) & KF_UP) == 0);
3191 /* Make sure Ctrl-ALT is not the same as AltGr for ToAscii unless told. */
3192 if (left_alt && (keystate[VK_CONTROL] & 0x80)) {
3193 if (cfg.ctrlaltkeys)
3194 keystate[VK_MENU] = 0;
3196 keystate[VK_RMENU] = 0x80;
3201 scan = (HIWORD(lParam) & (KF_UP | KF_EXTENDED | 0xFF));
3202 shift_state = ((keystate[VK_SHIFT] & 0x80) != 0)
3203 + ((keystate[VK_CONTROL] & 0x80) != 0) * 2;
3205 /* Note if AltGr was pressed and if it was used as a compose key */
3206 if (!compose_state) {
3207 compose_key = 0x100;
3208 if (cfg.compose_key) {
3209 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED))
3210 compose_key = wParam;
3212 if (wParam == VK_APPS)
3213 compose_key = wParam;
3216 if (wParam == compose_key) {
3217 if (compose_state == 0
3218 && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0) compose_state =
3220 else if (compose_state == 1 && (HIWORD(lParam) & KF_UP))
3224 } else if (compose_state == 1 && wParam != VK_CONTROL)
3227 if (compose_state > 1 && left_alt)
3230 /* Sanitize the number pad if not using a PC NumPad */
3231 if (left_alt || (term->app_keypad_keys && !cfg.no_applic_k
3232 && cfg.funky_type != 2)
3233 || cfg.funky_type == 3 || cfg.nethack_keypad || compose_state) {
3234 if ((HIWORD(lParam) & KF_EXTENDED) == 0) {
3238 nParam = VK_NUMPAD0;
3241 nParam = VK_NUMPAD1;
3244 nParam = VK_NUMPAD2;
3247 nParam = VK_NUMPAD3;
3250 nParam = VK_NUMPAD4;
3253 nParam = VK_NUMPAD5;
3256 nParam = VK_NUMPAD6;
3259 nParam = VK_NUMPAD7;
3262 nParam = VK_NUMPAD8;
3265 nParam = VK_NUMPAD9;
3268 nParam = VK_DECIMAL;
3272 if (keystate[VK_NUMLOCK] & 1)
3279 /* If a key is pressed and AltGr is not active */
3280 if (key_down && (keystate[VK_RMENU] & 0x80) == 0 && !compose_state) {
3281 /* Okay, prepare for most alts then ... */
3285 /* Lets see if it's a pattern we know all about ... */
3286 if (wParam == VK_PRIOR && shift_state == 1) {
3287 SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0);
3290 if (wParam == VK_NEXT && shift_state == 1) {
3291 SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
3294 if (wParam == VK_INSERT && shift_state == 1) {
3295 term_do_paste(term);
3298 if (left_alt && wParam == VK_F4 && cfg.alt_f4) {
3301 if (left_alt && wParam == VK_SPACE && cfg.alt_space) {
3302 SendMessage(hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0);
3305 if (left_alt && wParam == VK_RETURN && cfg.fullscreenonaltenter &&
3306 (cfg.resize_action != RESIZE_DISABLED)) {
3307 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) != KF_REPEAT)
3311 /* Control-Numlock for app-keypad mode switch */
3312 if (wParam == VK_PAUSE && shift_state == 2) {
3313 term->app_keypad_keys ^= 1;
3317 /* Nethack keypad */
3318 if (cfg.nethack_keypad && !left_alt) {
3321 *p++ = shift_state ? 'B' : 'b';
3324 *p++ = shift_state ? 'J' : 'j';
3327 *p++ = shift_state ? 'N' : 'n';
3330 *p++ = shift_state ? 'H' : 'h';
3333 *p++ = shift_state ? '.' : '.';
3336 *p++ = shift_state ? 'L' : 'l';
3339 *p++ = shift_state ? 'Y' : 'y';
3342 *p++ = shift_state ? 'K' : 'k';
3345 *p++ = shift_state ? 'U' : 'u';
3350 /* Application Keypad */
3354 if (cfg.funky_type == 3 ||
3355 (cfg.funky_type <= 1 &&
3356 term->app_keypad_keys && !cfg.no_applic_k)) switch (wParam) {
3370 if (term->app_keypad_keys && !cfg.no_applic_k)
3407 if (cfg.funky_type == 2) {
3412 } else if (shift_state)
3419 if (cfg.funky_type == 2)
3423 if (cfg.funky_type == 2)
3427 if (cfg.funky_type == 2)
3432 if (HIWORD(lParam) & KF_EXTENDED)
3437 if (term->vt52_mode) {
3438 if (xkey >= 'P' && xkey <= 'S')
3439 p += sprintf((char *) p, "\x1B%c", xkey);
3441 p += sprintf((char *) p, "\x1B?%c", xkey);
3443 p += sprintf((char *) p, "\x1BO%c", xkey);
3448 if (wParam == VK_BACK && shift_state == 0) { /* Backspace */
3449 *p++ = (cfg.bksp_is_delete ? 0x7F : 0x08);
3453 if (wParam == VK_BACK && shift_state == 1) { /* Shift Backspace */
3454 /* We do the opposite of what is configured */
3455 *p++ = (cfg.bksp_is_delete ? 0x08 : 0x7F);
3459 if (wParam == VK_TAB && shift_state == 1) { /* Shift tab */
3465 if (wParam == VK_SPACE && shift_state == 2) { /* Ctrl-Space */
3469 if (wParam == VK_SPACE && shift_state == 3) { /* Ctrl-Shift-Space */
3473 if (wParam == VK_CANCEL && shift_state == 2) { /* Ctrl-Break */
3478 if (wParam == VK_PAUSE) { /* Break/Pause */
3483 /* Control-2 to Control-8 are special */
3484 if (shift_state == 2 && wParam >= '2' && wParam <= '8') {
3485 *p++ = "\000\033\034\035\036\037\177"[wParam - '2'];
3488 if (shift_state == 2 && wParam == 0xBD) {
3492 if (shift_state == 2 && wParam == 0xDF) {
3496 if (shift_state == 0 && wParam == VK_RETURN && term->cr_lf_return) {
3503 * Next, all the keys that do tilde codes. (ESC '[' nn '~',
3504 * for integer decimal nn.)
3506 * We also deal with the weird ones here. Linux VCs replace F1
3507 * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
3508 * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
3514 code = (keystate[VK_SHIFT] & 0x80 ? 23 : 11);
3517 code = (keystate[VK_SHIFT] & 0x80 ? 24 : 12);
3520 code = (keystate[VK_SHIFT] & 0x80 ? 25 : 13);
3523 code = (keystate[VK_SHIFT] & 0x80 ? 26 : 14);
3526 code = (keystate[VK_SHIFT] & 0x80 ? 28 : 15);
3529 code = (keystate[VK_SHIFT] & 0x80 ? 29 : 17);
3532 code = (keystate[VK_SHIFT] & 0x80 ? 31 : 18);
3535 code = (keystate[VK_SHIFT] & 0x80 ? 32 : 19);
3538 code = (keystate[VK_SHIFT] & 0x80 ? 33 : 20);
3541 code = (keystate[VK_SHIFT] & 0x80 ? 34 : 21);
3574 if ((shift_state&2) == 0) switch (wParam) {
3594 /* Reorder edit keys to physical order */
3595 if (cfg.funky_type == 3 && code <= 6)
3596 code = "\0\2\1\4\5\3\6"[code];
3598 if (term->vt52_mode && code > 0 && code <= 6) {
3599 p += sprintf((char *) p, "\x1B%c", " HLMEIG"[code]);
3603 if (cfg.funky_type == 5 && /* SCO function keys */
3604 code >= 11 && code <= 34) {
3605 char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
3608 case VK_F1: index = 0; break;
3609 case VK_F2: index = 1; break;
3610 case VK_F3: index = 2; break;
3611 case VK_F4: index = 3; break;
3612 case VK_F5: index = 4; break;
3613 case VK_F6: index = 5; break;
3614 case VK_F7: index = 6; break;
3615 case VK_F8: index = 7; break;
3616 case VK_F9: index = 8; break;
3617 case VK_F10: index = 9; break;
3618 case VK_F11: index = 10; break;
3619 case VK_F12: index = 11; break;
3621 if (keystate[VK_SHIFT] & 0x80) index += 12;
3622 if (keystate[VK_CONTROL] & 0x80) index += 24;
3623 p += sprintf((char *) p, "\x1B[%c", codes[index]);
3626 if (cfg.funky_type == 5 && /* SCO small keypad */
3627 code >= 1 && code <= 6) {
3628 char codes[] = "HL.FIG";
3632 p += sprintf((char *) p, "\x1B[%c", codes[code-1]);
3636 if ((term->vt52_mode || cfg.funky_type == 4) && code >= 11 && code <= 24) {
3642 if (term->vt52_mode)
3643 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11 - offt);
3646 sprintf((char *) p, "\x1BO%c", code + 'P' - 11 - offt);
3649 if (cfg.funky_type == 1 && code >= 11 && code <= 15) {
3650 p += sprintf((char *) p, "\x1B[[%c", code + 'A' - 11);
3653 if (cfg.funky_type == 2 && code >= 11 && code <= 14) {
3654 if (term->vt52_mode)
3655 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11);
3657 p += sprintf((char *) p, "\x1BO%c", code + 'P' - 11);
3660 if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
3661 p += sprintf((char *) p, code == 1 ? "\x1B[H" : "\x1BOw");
3665 p += sprintf((char *) p, "\x1B[%d~", code);
3670 * Now the remaining keys (arrows and Keypad 5. Keypad 5 for
3671 * some reason seems to send VK_CLEAR to Windows...).
3693 if (term->vt52_mode)
3694 p += sprintf((char *) p, "\x1B%c", xkey);
3696 int app_flg = (term->app_cursor_keys && !cfg.no_applic_c);
3699 * RDB: VT100 & VT102 manuals both state the
3700 * app cursor keys only work if the app keypad
3703 * SGT: That may well be true, but xterm
3704 * disagrees and so does at least one
3705 * application, so I've #if'ed this out and the
3706 * behaviour is back to PuTTY's original: app
3707 * cursor and app keypad are independently
3708 * switchable modes. If anyone complains about
3709 * _this_ I'll have to put in a configurable
3712 if (!term->app_keypad_keys)
3715 /* Useful mapping of Ctrl-arrows */
3716 if (shift_state == 2)
3720 p += sprintf((char *) p, "\x1BO%c", xkey);
3722 p += sprintf((char *) p, "\x1B[%c", xkey);
3729 * Finally, deal with Return ourselves. (Win95 seems to
3730 * foul it up when Alt is pressed, for some reason.)
3732 if (wParam == VK_RETURN) { /* Return */
3738 if (left_alt && wParam >= VK_NUMPAD0 && wParam <= VK_NUMPAD9)
3739 alt_sum = alt_sum * 10 + wParam - VK_NUMPAD0;
3744 /* Okay we've done everything interesting; let windows deal with
3745 * the boring stuff */
3749 /* helg: clear CAPS LOCK state if caps lock switches to cyrillic */
3750 if(cfg.xlat_capslockcyr && keystate[VK_CAPITAL] != 0) {
3752 keystate[VK_CAPITAL] = 0;
3755 r = ToAsciiEx(wParam, scan, keystate, keys, 0, kbd_layout);
3756 #ifdef SHOW_TOASCII_RESULT
3757 if (r == 1 && !key_down) {
3759 if (in_utf(term) || dbcs_screenfont)
3760 debug((", (U+%04x)", alt_sum));
3762 debug((", LCH(%d)", alt_sum));
3764 debug((", ACH(%d)", keys[0]));
3769 for (r1 = 0; r1 < r; r1++) {
3770 debug(("%s%d", r1 ? "," : "", keys[r1]));
3779 * Interrupt an ongoing paste. I'm not sure this is
3780 * sensible, but for the moment it's preferable to
3781 * having to faff about buffering things.
3786 for (i = 0; i < r; i++) {
3787 unsigned char ch = (unsigned char) keys[i];
3789 if (compose_state == 2 && (ch & 0x80) == 0 && ch > ' ') {
3794 if (compose_state == 3 && (ch & 0x80) == 0 && ch > ' ') {
3798 if ((nc = check_compose(compose_char, ch)) == -1) {
3799 MessageBeep(MB_ICONHAND);
3803 term_seen_key_event(term);
3804 luni_send(ldisc, &keybuf, 1, 1);
3812 if (in_utf(term) || dbcs_screenfont) {
3814 term_seen_key_event(term);
3815 luni_send(ldisc, &keybuf, 1, 1);
3817 ch = (char) alt_sum;
3819 * We need not bother about stdin
3820 * backlogs here, because in GUI PuTTY
3821 * we can't do anything about it
3822 * anyway; there's no means of asking
3823 * Windows to hold off on KEYDOWN
3824 * messages. We _have_ to buffer
3825 * everything we're sent.
3827 term_seen_key_event(term);
3828 ldisc_send(ldisc, &ch, 1, 1);
3832 term_seen_key_event(term);
3833 lpage_send(ldisc, kbd_codepage, &ch, 1, 1);
3835 if(capsOn && ch < 0x80) {
3838 cbuf[1] = xlat_uskbd2cyrllic(ch);
3839 term_seen_key_event(term);
3840 luni_send(ldisc, cbuf+!left_alt, 1+!!left_alt, 1);
3845 term_seen_key_event(term);
3846 lpage_send(ldisc, kbd_codepage,
3847 cbuf+!left_alt, 1+!!left_alt, 1);
3853 /* This is so the ALT-Numpad and dead keys work correctly. */
3858 /* If we're definitly not building up an ALT-54321 then clear it */
3861 /* If we will be using alt_sum fix the 256s */
3862 else if (keys[0] && (in_utf(term) || dbcs_screenfont))
3867 * ALT alone may or may not want to bring up the System menu.
3868 * If it's not meant to, we return 0 on presses or releases of
3869 * ALT, to show that we've swallowed the keystroke. Otherwise
3870 * we return -1, which means Windows will give the keystroke
3871 * its default handling (i.e. bring up the System menu).
3873 if (wParam == VK_MENU && !cfg.alt_only)
3879 void request_paste(void *frontend)
3882 * In Windows, pasting is synchronous: we can read the
3883 * clipboard with no difficulty, so request_paste() can just go
3886 term_do_paste(term);
3889 void set_title(void *frontend, char *title)
3892 window_name = smalloc(1 + strlen(title));
3893 strcpy(window_name, title);
3894 if (cfg.win_name_always || !IsIconic(hwnd))
3895 SetWindowText(hwnd, title);
3898 void set_icon(void *frontend, char *title)
3901 icon_name = smalloc(1 + strlen(title));
3902 strcpy(icon_name, title);
3903 if (!cfg.win_name_always && IsIconic(hwnd))
3904 SetWindowText(hwnd, title);
3907 void set_sbar(void *frontend, int total, int start, int page)
3911 if (is_full_screen() ? !cfg.scrollbar_in_fullscreen : !cfg.scrollbar)
3914 si.cbSize = sizeof(si);
3915 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
3917 si.nMax = total - 1;
3921 SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
3924 Context get_ctx(void *frontend)
3930 SelectPalette(hdc, pal, FALSE);
3936 void free_ctx(Context ctx)
3938 SelectPalette(ctx, GetStockObject(DEFAULT_PALETTE), FALSE);
3939 ReleaseDC(hwnd, ctx);
3942 static void real_palette_set(int n, int r, int g, int b)
3945 logpal->palPalEntry[n].peRed = r;
3946 logpal->palPalEntry[n].peGreen = g;
3947 logpal->palPalEntry[n].peBlue = b;
3948 logpal->palPalEntry[n].peFlags = PC_NOCOLLAPSE;
3949 colours[n] = PALETTERGB(r, g, b);
3950 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3952 colours[n] = RGB(r, g, b);
3955 void palette_set(void *frontend, int n, int r, int g, int b)
3957 static const int first[21] = {
3958 0, 2, 4, 6, 8, 10, 12, 14,
3959 1, 3, 5, 7, 9, 11, 13, 15,
3962 real_palette_set(first[n], r, g, b);
3964 real_palette_set(first[n] + 1, r, g, b);
3966 HDC hdc = get_ctx(frontend);
3967 UnrealizeObject(pal);
3968 RealizePalette(hdc);
3973 void palette_reset(void *frontend)
3977 for (i = 0; i < NCOLOURS; i++) {
3979 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
3980 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
3981 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
3982 logpal->palPalEntry[i].peFlags = 0;
3983 colours[i] = PALETTERGB(defpal[i].rgbtRed,
3984 defpal[i].rgbtGreen,
3985 defpal[i].rgbtBlue);
3987 colours[i] = RGB(defpal[i].rgbtRed,
3988 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
3993 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3994 hdc = get_ctx(frontend);
3995 RealizePalette(hdc);
4000 void write_aclip(void *frontend, char *data, int len, int must_deselect)
4005 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len + 1);
4008 lock = GlobalLock(clipdata);
4011 memcpy(lock, data, len);
4012 ((unsigned char *) lock)[len] = 0;
4013 GlobalUnlock(clipdata);
4016 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
4018 if (OpenClipboard(hwnd)) {
4020 SetClipboardData(CF_TEXT, clipdata);
4023 GlobalFree(clipdata);
4026 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
4030 * Note: unlike write_aclip() this will not append a nul.
4032 void write_clip(void *frontend, wchar_t * data, int len, int must_deselect)
4034 HGLOBAL clipdata, clipdata2, clipdata3;
4036 void *lock, *lock2, *lock3;
4038 len2 = WideCharToMultiByte(CP_ACP, 0, data, len, 0, 0, NULL, NULL);
4040 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE,
4041 len * sizeof(wchar_t));
4042 clipdata2 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len2);
4044 if (!clipdata || !clipdata2) {
4046 GlobalFree(clipdata);
4048 GlobalFree(clipdata2);
4051 if (!(lock = GlobalLock(clipdata)))
4053 if (!(lock2 = GlobalLock(clipdata2)))
4056 memcpy(lock, data, len * sizeof(wchar_t));
4057 WideCharToMultiByte(CP_ACP, 0, data, len, lock2, len2, NULL, NULL);
4059 if (cfg.rtf_paste) {
4060 wchar_t unitab[256];
4062 unsigned char *tdata = (unsigned char *)lock2;
4063 wchar_t *udata = (wchar_t *)lock;
4064 int rtflen = 0, uindex = 0, tindex = 0;
4066 int multilen, blen, alen, totallen, i;
4067 char before[16], after[4];
4069 get_unitab(CP_ACP, unitab, 0);
4071 rtfsize = 100 + strlen(cfg.font);
4072 rtf = smalloc(rtfsize);
4073 sprintf(rtf, "{\\rtf1\\ansi%d{\\fonttbl\\f0\\fmodern %s;}\\f0",
4074 GetACP(), cfg.font);
4075 rtflen = strlen(rtf);
4078 * We want to construct a piece of RTF that specifies the
4079 * same Unicode text. To do this we will read back in
4080 * parallel from the Unicode data in `udata' and the
4081 * non-Unicode data in `tdata'. For each character in
4082 * `tdata' which becomes the right thing in `udata' when
4083 * looked up in `unitab', we just copy straight over from
4084 * tdata. For each one that doesn't, we must WCToMB it
4085 * individually and produce a \u escape sequence.
4087 * It would probably be more robust to just bite the bullet
4088 * and WCToMB each individual Unicode character one by one,
4089 * then MBToWC each one back to see if it was an accurate
4090 * translation; but that strikes me as a horrifying number
4091 * of Windows API calls so I want to see if this faster way
4092 * will work. If it screws up badly we can always revert to
4093 * the simple and slow way.
4095 while (tindex < len2 && uindex < len &&
4096 tdata[tindex] && udata[uindex]) {
4097 if (tindex + 1 < len2 &&
4098 tdata[tindex] == '\r' &&
4099 tdata[tindex+1] == '\n') {
4103 if (unitab[tdata[tindex]] == udata[uindex]) {
4109 multilen = WideCharToMultiByte(CP_ACP, 0, unitab+uindex, 1,
4110 NULL, 0, NULL, NULL);
4111 if (multilen != 1) {
4112 blen = sprintf(before, "{\\uc%d\\u%d", multilen,
4114 alen = 1; strcpy(after, "}");
4116 blen = sprintf(before, "\\u%d", udata[uindex]);
4117 alen = 0; after[0] = '\0';
4120 assert(tindex + multilen <= len2);
4121 totallen = blen + alen;
4122 for (i = 0; i < multilen; i++) {
4123 if (tdata[tindex+i] == '\\' ||
4124 tdata[tindex+i] == '{' ||
4125 tdata[tindex+i] == '}')
4127 else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A)
4128 totallen += 6; /* \par\r\n */
4129 else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20)
4135 if (rtfsize < rtflen + totallen + 3) {
4136 rtfsize = rtflen + totallen + 512;
4137 rtf = srealloc(rtf, rtfsize);
4140 strcpy(rtf + rtflen, before); rtflen += blen;
4141 for (i = 0; i < multilen; i++) {
4142 if (tdata[tindex+i] == '\\' ||
4143 tdata[tindex+i] == '{' ||
4144 tdata[tindex+i] == '}') {
4145 rtf[rtflen++] = '\\';
4146 rtf[rtflen++] = tdata[tindex+i];
4147 } else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A) {
4148 rtflen += sprintf(rtf+rtflen, "\\par\r\n");
4149 } else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20) {
4150 rtflen += sprintf(rtf+rtflen, "\\'%02x", tdata[tindex+i]);
4152 rtf[rtflen++] = tdata[tindex+i];
4155 strcpy(rtf + rtflen, after); rtflen += alen;
4161 strcpy(rtf + rtflen, "}");
4164 clipdata3 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, rtflen);
4165 if (clipdata3 && (lock3 = GlobalLock(clipdata3)) != NULL) {
4167 GlobalUnlock(clipdata3);
4173 GlobalUnlock(clipdata);
4174 GlobalUnlock(clipdata2);
4177 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
4179 if (OpenClipboard(hwnd)) {
4181 SetClipboardData(CF_UNICODETEXT, clipdata);
4182 SetClipboardData(CF_TEXT, clipdata2);
4184 SetClipboardData(RegisterClipboardFormat(CF_RTF), clipdata3);
4187 GlobalFree(clipdata);
4188 GlobalFree(clipdata2);
4192 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
4195 void get_clip(void *frontend, wchar_t ** p, int *len)
4197 static HGLOBAL clipdata = NULL;
4198 static wchar_t *converted = 0;
4207 GlobalUnlock(clipdata);
4210 } else if (OpenClipboard(NULL)) {
4211 if ((clipdata = GetClipboardData(CF_UNICODETEXT))) {
4213 *p = GlobalLock(clipdata);
4215 for (p2 = *p; *p2; p2++);
4219 } else if ( (clipdata = GetClipboardData(CF_TEXT)) ) {
4223 s = GlobalLock(clipdata);
4224 i = MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, 0, 0);
4225 *p = converted = smalloc(i * sizeof(wchar_t));
4226 MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, converted, i);
4239 * Move `lines' lines from position `from' to position `to' in the
4242 void optimised_move(void *frontend, int to, int from, int lines)
4247 min = (to < from ? to : from);
4248 max = to + from - min;
4250 r.left = offset_width;
4251 r.right = offset_width + term->cols * font_width;
4252 r.top = offset_height + min * font_height;
4253 r.bottom = offset_height + (max + lines) * font_height;
4254 ScrollWindow(hwnd, 0, (to - from) * font_height, &r, &r);
4259 * Print a message box and perform a fatal exit.
4261 void fatalbox(char *fmt, ...)
4267 vsprintf(stuff, fmt, ap);
4269 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
4274 * Print a modal (Really Bad) message box and perform a fatal exit.
4276 void modalfatalbox(char *fmt, ...)
4282 vsprintf(stuff, fmt, ap);
4284 MessageBox(hwnd, stuff, "PuTTY Fatal Error",
4285 MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
4290 * Manage window caption / taskbar flashing, if enabled.
4291 * 0 = stop, 1 = maintain, 2 = start
4293 static void flash_window(int mode)
4295 static long last_flash = 0;
4296 static int flashing = 0;
4297 if ((mode == 0) || (cfg.beep_ind == B_IND_DISABLED)) {
4300 FlashWindow(hwnd, FALSE);
4304 } else if (mode == 2) {
4307 last_flash = GetTickCount();
4309 FlashWindow(hwnd, TRUE);
4312 } else if ((mode == 1) && (cfg.beep_ind == B_IND_FLASH)) {
4315 long now = GetTickCount();
4316 long fdiff = now - last_flash;
4317 if (fdiff < 0 || fdiff > 450) {
4319 FlashWindow(hwnd, TRUE); /* toggle */
4328 void beep(void *frontend, int mode)
4330 if (mode == BELL_DEFAULT) {
4332 * For MessageBeep style bells, we want to be careful of
4333 * timing, because they don't have the nice property of
4334 * PlaySound bells that each one cancels the previous
4335 * active one. So we limit the rate to one per 50ms or so.
4337 static long lastbeep = 0;
4340 beepdiff = GetTickCount() - lastbeep;
4341 if (beepdiff >= 0 && beepdiff < 50)
4345 * The above MessageBeep call takes time, so we record the
4346 * time _after_ it finishes rather than before it starts.
4348 lastbeep = GetTickCount();
4349 } else if (mode == BELL_WAVEFILE) {
4350 if (!PlaySound(cfg.bell_wavefile, NULL, SND_ASYNC | SND_FILENAME)) {
4351 char buf[sizeof(cfg.bell_wavefile) + 80];
4352 sprintf(buf, "Unable to play sound file\n%s\n"
4353 "Using default sound instead", cfg.bell_wavefile);
4354 MessageBox(hwnd, buf, "PuTTY Sound Error",
4355 MB_OK | MB_ICONEXCLAMATION);
4356 cfg.beep = BELL_DEFAULT;
4359 /* Otherwise, either visual bell or disabled; do nothing here */
4360 if (!term->has_focus) {
4361 flash_window(2); /* start */
4366 * Minimise or restore the window in response to a server-side
4369 void set_iconic(void *frontend, int iconic)
4371 if (IsIconic(hwnd)) {
4373 ShowWindow(hwnd, SW_RESTORE);
4376 ShowWindow(hwnd, SW_MINIMIZE);
4381 * Move the window in response to a server-side request.
4383 void move_window(void *frontend, int x, int y)
4385 if (cfg.resize_action == RESIZE_DISABLED ||
4386 cfg.resize_action == RESIZE_FONT ||
4390 SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
4394 * Move the window to the top or bottom of the z-order in response
4395 * to a server-side request.
4397 void set_zorder(void *frontend, int top)
4399 if (cfg.alwaysontop)
4400 return; /* ignore */
4401 SetWindowPos(hwnd, top ? HWND_TOP : HWND_BOTTOM, 0, 0, 0, 0,
4402 SWP_NOMOVE | SWP_NOSIZE);
4406 * Refresh the window in response to a server-side request.
4408 void refresh_window(void *frontend)
4410 InvalidateRect(hwnd, NULL, TRUE);
4414 * Maximise or restore the window in response to a server-side
4417 void set_zoomed(void *frontend, int zoomed)
4419 if (IsZoomed(hwnd)) {
4421 ShowWindow(hwnd, SW_RESTORE);
4424 ShowWindow(hwnd, SW_MAXIMIZE);
4429 * Report whether the window is iconic, for terminal reports.
4431 int is_iconic(void *frontend)
4433 return IsIconic(hwnd);
4437 * Report the window's position, for terminal reports.
4439 void get_window_pos(void *frontend, int *x, int *y)
4442 GetWindowRect(hwnd, &r);
4448 * Report the window's pixel size, for terminal reports.
4450 void get_window_pixels(void *frontend, int *x, int *y)
4453 GetWindowRect(hwnd, &r);
4454 *x = r.right - r.left;
4455 *y = r.bottom - r.top;
4459 * Return the window or icon title.
4461 char *get_window_title(void *frontend, int icon)
4463 return icon ? icon_name : window_name;
4467 * See if we're in full-screen mode.
4469 int is_full_screen()
4471 if (!IsZoomed(hwnd))
4473 if (GetWindowLong(hwnd, GWL_STYLE) & WS_CAPTION)
4478 /* Get the rect/size of a full screen window using the nearest available
4479 * monitor in multimon systems; default to something sensible if only
4480 * one monitor is present. */
4481 static int get_fullscreen_rect(RECT * ss)
4483 #ifdef MONITOR_DEFAULTTONEAREST
4486 mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
4487 mi.cbSize = sizeof(mi);
4488 GetMonitorInfo(mon, &mi);
4490 /* structure copy */
4494 /* could also use code like this:
4495 ss->left = ss->top = 0;
4496 ss->right = GetSystemMetrics(SM_CXSCREEN);
4497 ss->bottom = GetSystemMetrics(SM_CYSCREEN);
4499 return GetClientRect(GetDesktopWindow(), ss);
4505 * Go full-screen. This should only be called when we are already
4508 void make_full_screen()
4513 assert(IsZoomed(hwnd));
4515 if (is_full_screen())
4518 /* Remove the window furniture. */
4519 style = GetWindowLong(hwnd, GWL_STYLE);
4520 style &= ~(WS_CAPTION | WS_BORDER | WS_THICKFRAME);
4521 if (cfg.scrollbar_in_fullscreen)
4522 style |= WS_VSCROLL;
4524 style &= ~WS_VSCROLL;
4525 SetWindowLong(hwnd, GWL_STYLE, style);
4527 /* Resize ourselves to exactly cover the nearest monitor. */
4528 get_fullscreen_rect(&ss);
4529 SetWindowPos(hwnd, HWND_TOP, ss.left, ss.top,
4534 /* Tick the menu item in the System menu. */
4535 CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN,
4540 * Clear the full-screen attributes.
4542 void clear_full_screen()
4544 DWORD oldstyle, style;
4546 /* Reinstate the window furniture. */
4547 style = oldstyle = GetWindowLong(hwnd, GWL_STYLE);
4548 style |= WS_CAPTION | WS_BORDER;
4549 if (cfg.resize_action == RESIZE_DISABLED)
4550 style &= ~WS_THICKFRAME;
4552 style |= WS_THICKFRAME;
4554 style |= WS_VSCROLL;
4556 style &= ~WS_VSCROLL;
4557 if (style != oldstyle) {
4558 SetWindowLong(hwnd, GWL_STYLE, style);
4559 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
4560 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
4564 /* Untick the menu item in the System menu. */
4565 CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN,
4570 * Toggle full-screen mode.
4572 void flip_full_screen()
4574 if (is_full_screen()) {
4575 ShowWindow(hwnd, SW_RESTORE);
4576 } else if (IsZoomed(hwnd)) {
4579 SendMessage(hwnd, WM_FULLSCR_ON_MAX, 0, 0);
4580 ShowWindow(hwnd, SW_MAXIMIZE);
4584 void frontend_keypress(void *handle)
4587 * Keypress termination in non-Close-On-Exit mode is not
4588 * currently supported in PuTTY proper, because the window
4589 * always has a perfectly good Close button anyway. So we do