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);
90 static int do_mouse_wheel_msg(UINT message, WPARAM wParam, LPARAM lParam);
92 static int is_full_screen(void);
93 static void make_full_screen(void);
94 static void clear_full_screen(void);
95 static void flip_full_screen(void);
97 /* Window layout information */
98 static void reset_window(int);
99 static int extra_width, extra_height;
100 static int font_width, font_height, font_dualwidth;
101 static int offset_width, offset_height;
102 static int was_zoomed = 0;
103 static int prev_rows, prev_cols;
105 static int pending_netevent = 0;
106 static WPARAM pend_netevent_wParam = 0;
107 static LPARAM pend_netevent_lParam = 0;
108 static void enact_pending_netevent(void);
109 static void flash_window(int mode);
110 static void sys_cursor_update(void);
111 static int is_shift_pressed(void);
112 static int get_fullscreen_rect(RECT * ss);
114 static time_t last_movement = 0;
116 static int caret_x = -1, caret_y = -1;
118 static int kbd_codepage;
121 static Backend *back;
122 static void *backhandle;
124 static int session_closed;
126 extern struct sesslist sesslist; /* imported from windlg.c */
128 #define FONT_NORMAL 0
130 #define FONT_UNDERLINE 2
131 #define FONT_BOLDUND 3
132 #define FONT_WIDE 0x04
133 #define FONT_HIGH 0x08
134 #define FONT_NARROW 0x10
136 #define FONT_OEM 0x20
137 #define FONT_OEMBOLD 0x21
138 #define FONT_OEMUND 0x22
139 #define FONT_OEMBOLDUND 0x23
141 #define FONT_MAXNO 0x2F
143 static HFONT fonts[FONT_MAXNO];
144 static LOGFONT lfont;
145 static int fontflag[FONT_MAXNO];
147 BOLD_COLOURS, BOLD_SHADOW, BOLD_FONT
155 static COLORREF colours[NCOLOURS];
157 static LPLOGPALETTE logpal;
158 static RGBTRIPLE defpal[NCOLOURS];
162 static HBITMAP caretbm;
164 static int dbltime, lasttime, lastact;
165 static Mouse_Button lastbtn;
167 /* this allows xterm-style mouse handling. */
168 static int send_raw_mouse = 0;
169 static int wheel_accumulator = 0;
171 static char *window_name, *icon_name;
173 static int compose_state = 0;
175 static int wsa_started = 0;
177 static OSVERSIONINFO osVersion;
179 static UINT wm_mousewheel = WM_MOUSEWHEEL;
181 /* Dummy routine, only required in plink. */
182 void ldisc_update(void *frontend, int echo, int edit)
186 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
188 static char appname[] = "PuTTY";
193 int guess_width, guess_height;
196 flags = FLAG_VERBOSE | FLAG_INTERACTIVE;
198 winsock_ver = MAKEWORD(1, 1);
199 if (WSAStartup(winsock_ver, &wsadata)) {
200 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
201 MB_OK | MB_ICONEXCLAMATION);
204 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
205 MessageBox(NULL, "WinSock version is incompatible with 1.1",
206 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
211 /* WISHLIST: maybe allow config tweaking even if winsock not present? */
214 InitCommonControls();
216 /* Ensure a Maximize setting in Explorer doesn't maximise the
221 ZeroMemory(&osVersion, sizeof(osVersion));
222 osVersion.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
223 if (!GetVersionEx ( (OSVERSIONINFO *) &osVersion)) {
224 MessageBox(NULL, "Windows refuses to report a version",
225 "PuTTY Fatal Error", MB_OK | MB_ICONEXCLAMATION);
231 * If we're running a version of Windows that doesn't support
232 * WM_MOUSEWHEEL, find out what message number we should be
235 if (osVersion.dwMajorVersion < 4 ||
236 (osVersion.dwMajorVersion == 4 &&
237 osVersion.dwPlatformId != VER_PLATFORM_WIN32_NT))
238 wm_mousewheel = RegisterWindowMessage("MSWHEEL_ROLLMSG");
241 * See if we can find our Help file.
244 char b[2048], *p, *q, *r;
246 GetModuleFileName(NULL, b, sizeof(b) - 1);
248 p = strrchr(b, '\\');
249 if (p && p >= r) r = p+1;
251 if (q && q >= r) r = q+1;
252 strcpy(r, "putty.hlp");
253 if ( (fp = fopen(b, "r")) != NULL) {
254 help_path = dupstr(b);
258 strcpy(r, "putty.cnt");
259 if ( (fp = fopen(b, "r")) != NULL) {
260 help_has_contents = TRUE;
263 help_has_contents = FALSE;
267 * Process the command line.
273 default_protocol = DEFAULT_PROTOCOL;
274 default_port = DEFAULT_PORT;
275 cfg.logtype = LGTYP_NONE;
277 do_defaults(NULL, &cfg);
282 * Process a couple of command-line options which are more
283 * easily dealt with before the line is broken up into
284 * words. These are the soon-to-be-defunct @sessionname and
285 * the internal-use-only &sharedmemoryhandle, neither of
286 * which are combined with anything else.
288 while (*p && isspace(*p))
292 while (i > 1 && isspace(p[i - 1]))
295 do_defaults(p + 1, &cfg);
296 if (!*cfg.host && !do_config()) {
300 } else if (*p == '&') {
302 * An initial & means we've been given a command line
303 * containing the hex value of a HANDLE for a file
304 * mapping object, which we must then extract as a
309 if (sscanf(p + 1, "%p", &filemap) == 1 &&
310 (cp = MapViewOfFile(filemap, FILE_MAP_READ,
311 0, 0, sizeof(Config))) != NULL) {
314 CloseHandle(filemap);
315 } else if (!do_config()) {
321 * Otherwise, break up the command line and deal with
327 split_into_argv(cmdline, &argc, &argv, NULL);
329 for (i = 0; i < argc; i++) {
333 ret = cmdline_process_param(p, i+1<argc?argv[i+1]:NULL, 1);
335 cmdline_error("option \"%s\" requires an argument", p);
336 } else if (ret == 2) {
337 i++; /* skip next argument */
338 } else if (ret == 1) {
339 continue; /* nothing further needs doing */
340 } else if (!strcmp(p, "-cleanup")) {
342 * `putty -cleanup'. Remove all registry
343 * entries associated with PuTTY, and also find
344 * and delete the random seed file.
347 "This procedure will remove ALL Registry\n"
348 "entries associated with PuTTY, and will\n"
349 "also remove the PuTTY random seed file.\n"
351 "THIS PROCESS WILL DESTROY YOUR SAVED\n"
352 "SESSIONS. Are you really sure you want\n"
355 MB_YESNO | MB_ICONWARNING) == IDYES) {
359 } else if (*p != '-') {
363 * If we already have a host name, treat
364 * this argument as a port number. NB we
365 * have to treat this as a saved -P
366 * argument, so that it will be deferred
367 * until it's a good moment to run it.
369 int ret = cmdline_process_param("-P", p, 1);
371 } else if (!strncmp(q, "telnet:", 7)) {
373 * If the hostname starts with "telnet:",
374 * set the protocol to Telnet and process
375 * the string as a Telnet URL.
380 if (q[0] == '/' && q[1] == '/')
382 cfg.protocol = PROT_TELNET;
384 while (*p && *p != ':' && *p != '/')
393 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
394 cfg.host[sizeof(cfg.host) - 1] = '\0';
398 * Otherwise, treat this argument as a host
401 while (*p && !isspace(*p))
405 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
406 cfg.host[sizeof(cfg.host) - 1] = '\0';
415 if (!*cfg.host && !do_config()) {
421 * Trim leading whitespace off the hostname if it's there.
424 int space = strspn(cfg.host, " \t");
425 memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
428 /* See if host is of the form user@host */
429 if (cfg.host[0] != '\0') {
430 char *atsign = strchr(cfg.host, '@');
431 /* Make sure we're not overflowing the user field */
433 if (atsign - cfg.host < sizeof cfg.username) {
434 strncpy(cfg.username, cfg.host, atsign - cfg.host);
435 cfg.username[atsign - cfg.host] = '\0';
437 memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
442 * Trim a colon suffix off the hostname if it's there.
444 cfg.host[strcspn(cfg.host, ":")] = '\0';
447 * Remove any remaining whitespace from the hostname.
451 while (cfg.host[p2] != '\0') {
452 if (cfg.host[p2] != ' ' && cfg.host[p2] != '\t') {
453 cfg.host[p1] = cfg.host[p2];
463 * Select protocol. This is farmed out into a table in a
464 * separate file to enable an ssh-free variant.
469 for (i = 0; backends[i].backend != NULL; i++)
470 if (backends[i].protocol == cfg.protocol) {
471 back = backends[i].backend;
475 MessageBox(NULL, "Unsupported protocol number found",
476 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
482 /* Check for invalid Port number (i.e. zero) */
484 MessageBox(NULL, "Invalid Port Number",
485 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
492 wndclass.lpfnWndProc = WndProc;
493 wndclass.cbClsExtra = 0;
494 wndclass.cbWndExtra = 0;
495 wndclass.hInstance = inst;
496 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
497 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
498 wndclass.hbrBackground = NULL;
499 wndclass.lpszMenuName = NULL;
500 wndclass.lpszClassName = appname;
502 RegisterClass(&wndclass);
507 term = term_init(NULL);
508 logctx = log_init(NULL);
509 term_provide_logctx(term, logctx);
514 * Guess some defaults for the window size. This all gets
515 * updated later, so we don't really care too much. However, we
516 * do want the font width/height guesses to correspond to a
517 * large font rather than a small one...
524 term_size(term, cfg.height, cfg.width, cfg.savelines);
525 guess_width = extra_width + font_width * term->cols;
526 guess_height = extra_height + font_height * term->rows;
529 get_fullscreen_rect(&r);
530 if (guess_width > r.right - r.left)
531 guess_width = r.right - r.left;
532 if (guess_height > r.bottom - r.top)
533 guess_height = r.bottom - r.top;
537 int winmode = WS_OVERLAPPEDWINDOW | WS_VSCROLL;
540 winmode &= ~(WS_VSCROLL);
541 if (cfg.resize_action == RESIZE_DISABLED)
542 winmode &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
544 exwinmode |= WS_EX_TOPMOST;
546 exwinmode |= WS_EX_CLIENTEDGE;
547 hwnd = CreateWindowEx(exwinmode, appname, appname,
548 winmode, CW_USEDEFAULT, CW_USEDEFAULT,
549 guess_width, guess_height,
550 NULL, NULL, inst, NULL);
554 * Initialise the fonts, simultaneously correcting the guesses
555 * for font_{width,height}.
560 * Correct the guesses for extra_{width,height}.
564 GetWindowRect(hwnd, &wr);
565 GetClientRect(hwnd, &cr);
566 offset_width = offset_height = cfg.window_border;
567 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
568 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
572 * Resize the window, now we know what size we _really_ want it
575 guess_width = extra_width + font_width * term->cols;
576 guess_height = extra_height + font_height * term->rows;
577 SetWindowPos(hwnd, NULL, 0, 0, guess_width, guess_height,
578 SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
581 * Set up a caret bitmap, with no content.
585 int size = (font_width + 15) / 16 * 2 * font_height;
586 bits = smalloc(size);
587 memset(bits, 0, size);
588 caretbm = CreateBitmap(font_width, font_height, 1, 1, bits);
591 CreateCaret(hwnd, caretbm, font_width, font_height);
594 * Initialise the scroll bar.
599 si.cbSize = sizeof(si);
600 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
602 si.nMax = term->rows - 1;
603 si.nPage = term->rows;
605 SetScrollInfo(hwnd, SB_VERT, &si, FALSE);
609 * Start up the telnet connection.
613 char msg[1024], *title;
616 error = back->init((void *)term, &backhandle,
617 cfg.host, cfg.port, &realhost, cfg.tcp_nodelay);
618 back->provide_logctx(backhandle, logctx);
620 sprintf(msg, "Unable to open connection to\n"
621 "%.800s\n" "%s", cfg.host, error);
622 MessageBox(NULL, msg, "PuTTY Error", MB_ICONERROR | MB_OK);
625 window_name = icon_name = NULL;
627 title = cfg.wintitle;
629 sprintf(msg, "%s - PuTTY", realhost);
633 set_title(NULL, title);
634 set_icon(NULL, title);
638 * Connect the terminal to the backend for resize purposes.
640 term_provide_resize_fn(term, back->size, backhandle);
643 * Set up a line discipline.
645 ldisc = ldisc_create(term, back, backhandle, NULL);
647 session_closed = FALSE;
650 * Prepare the mouse handler.
652 lastact = MA_NOTHING;
653 lastbtn = MBT_NOTHING;
654 dbltime = GetDoubleClickTime();
657 * Set up the session-control options on the system menu.
660 HMENU m = GetSystemMenu(hwnd, FALSE);
664 AppendMenu(m, MF_SEPARATOR, 0, 0);
665 if (cfg.protocol == PROT_TELNET) {
667 AppendMenu(p, MF_ENABLED, IDM_TEL_AYT, "Are You There");
668 AppendMenu(p, MF_ENABLED, IDM_TEL_BRK, "Break");
669 AppendMenu(p, MF_ENABLED, IDM_TEL_SYNCH, "Synch");
670 AppendMenu(p, MF_SEPARATOR, 0, 0);
671 AppendMenu(p, MF_ENABLED, IDM_TEL_EC, "Erase Character");
672 AppendMenu(p, MF_ENABLED, IDM_TEL_EL, "Erase Line");
673 AppendMenu(p, MF_ENABLED, IDM_TEL_GA, "Go Ahead");
674 AppendMenu(p, MF_ENABLED, IDM_TEL_NOP, "No Operation");
675 AppendMenu(p, MF_SEPARATOR, 0, 0);
676 AppendMenu(p, MF_ENABLED, IDM_TEL_ABORT, "Abort Process");
677 AppendMenu(p, MF_ENABLED, IDM_TEL_AO, "Abort Output");
678 AppendMenu(p, MF_ENABLED, IDM_TEL_IP, "Interrupt Process");
679 AppendMenu(p, MF_ENABLED, IDM_TEL_SUSP, "Suspend Process");
680 AppendMenu(p, MF_SEPARATOR, 0, 0);
681 AppendMenu(p, MF_ENABLED, IDM_TEL_EOR, "End Of Record");
682 AppendMenu(p, MF_ENABLED, IDM_TEL_EOF, "End Of File");
683 AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) p,
685 AppendMenu(m, MF_SEPARATOR, 0, 0);
687 AppendMenu(m, MF_ENABLED, IDM_SHOWLOG, "&Event Log");
688 AppendMenu(m, MF_SEPARATOR, 0, 0);
689 AppendMenu(m, MF_ENABLED, IDM_NEWSESS, "Ne&w Session...");
690 AppendMenu(m, MF_ENABLED, IDM_DUPSESS, "&Duplicate Session");
692 get_sesslist(&sesslist, TRUE);
694 i < ((sesslist.nsessions < 256) ? sesslist.nsessions : 256);
696 AppendMenu(s, MF_ENABLED, IDM_SAVED_MIN + (16 * i),
697 sesslist.sessions[i]);
698 AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) s, "Sa&ved Sessions");
699 AppendMenu(m, MF_ENABLED, IDM_RECONF, "Chan&ge Settings...");
700 AppendMenu(m, MF_SEPARATOR, 0, 0);
701 AppendMenu(m, MF_ENABLED, IDM_COPYALL, "C&opy All to Clipboard");
702 AppendMenu(m, MF_ENABLED, IDM_CLRSB, "C&lear Scrollback");
703 AppendMenu(m, MF_ENABLED, IDM_RESET, "Rese&t Terminal");
704 AppendMenu(m, MF_SEPARATOR, 0, 0);
705 AppendMenu(m, (cfg.resize_action == RESIZE_DISABLED) ?
706 MF_GRAYED : MF_ENABLED, IDM_FULLSCREEN, "&Full Screen");
707 AppendMenu(m, MF_SEPARATOR, 0, 0);
709 AppendMenu(m, MF_ENABLED, IDM_HELP, "&Help");
710 AppendMenu(m, MF_ENABLED, IDM_ABOUT, "&About PuTTY");
714 * Set up the initial input locale.
716 set_input_locale(GetKeyboardLayout(0));
719 * Open the initial log file if there is one.
724 * Finally show the window!
726 ShowWindow(hwnd, show);
727 SetForegroundWindow(hwnd);
730 * Set the palette up.
736 term->has_focus = (GetForegroundWindow() == hwnd);
739 if (GetMessage(&msg, NULL, 0, 0) == 1) {
740 int timer_id = 0, long_timer = 0;
742 while (msg.message != WM_QUIT) {
743 /* Sometimes DispatchMessage calls routines that use their own
744 * GetMessage loop, setup this timer so we get some control back.
746 * Also call term_update() from the timer so that if the host
747 * is sending data flat out we still do redraws.
749 if (timer_id && long_timer) {
750 KillTimer(hwnd, timer_id);
751 long_timer = timer_id = 0;
754 timer_id = SetTimer(hwnd, 1, 20, NULL);
755 if (!(IsWindow(logbox) && IsDialogMessage(logbox, &msg)))
756 DispatchMessage(&msg);
758 /* Make sure we blink everything that needs it. */
761 /* Send the paste buffer if there's anything to send */
764 /* If there's nothing new in the queue then we can do everything
765 * we've delayed, reading the socket, writing, and repainting
768 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
771 if (pending_netevent) {
772 enact_pending_netevent();
774 /* Force the cursor blink on */
777 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
781 /* Okay there is now nothing to do so we make sure the screen is
782 * completely up to date then tell windows to call us in a little
786 KillTimer(hwnd, timer_id);
790 if (GetCapture() != hwnd ||
792 !(cfg.mouse_override && is_shift_pressed())))
797 flash_window(1); /* maintain */
799 /* The messages seem unreliable; especially if we're being tricky */
800 term->has_focus = (GetForegroundWindow() == hwnd);
803 /* Hmm, term_update didn't want to do an update too soon ... */
804 timer_id = SetTimer(hwnd, 1, 50, NULL);
805 else if (!term->has_focus)
806 timer_id = SetTimer(hwnd, 1, 500, NULL);
808 timer_id = SetTimer(hwnd, 1, 100, NULL);
811 /* There's no point rescanning everything in the message queue
812 * so we do an apparently unnecessary wait here
815 if (GetMessage(&msg, NULL, 0, 0) != 1)
820 cleanup_exit(msg.wParam); /* this doesn't return... */
821 return msg.wParam; /* ... but optimiser doesn't know */
827 void cleanup_exit(int code)
840 if (cfg.protocol == PROT_SSH) {
851 * Set up, or shut down, an AsyncSelect. Called from winnet.c.
853 char *do_select(SOCKET skt, int startup)
858 events = (FD_CONNECT | FD_READ | FD_WRITE |
859 FD_OOB | FD_CLOSE | FD_ACCEPT);
864 return "do_select(): internal error (hwnd==NULL)";
865 if (WSAAsyncSelect(skt, hwnd, msg, events) == SOCKET_ERROR) {
866 switch (WSAGetLastError()) {
868 return "Network is down";
870 return "WSAAsyncSelect(): unknown error";
877 * set or clear the "raw mouse message" mode
879 void set_raw_mouse_mode(void *frontend, int activate)
881 activate = activate && !cfg.no_mouse_rep;
882 send_raw_mouse = activate;
883 SetCursor(LoadCursor(NULL, activate ? IDC_ARROW : IDC_IBEAM));
887 * Print a message box and close the connection.
889 void connection_fatal(void *frontend, char *fmt, ...)
895 vsprintf(stuff, fmt, ap);
897 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
898 if (cfg.close_on_exit == COE_ALWAYS)
901 session_closed = TRUE;
902 SetWindowText(hwnd, "PuTTY (inactive)");
907 * Report an error at the command-line parsing stage.
909 void cmdline_error(char *fmt, ...)
915 vsprintf(stuff, fmt, ap);
917 MessageBox(hwnd, stuff, "PuTTY Command Line Error", MB_ICONERROR | MB_OK);
922 * Actually do the job requested by a WM_NETEVENT
924 static void enact_pending_netevent(void)
926 static int reentering = 0;
927 extern int select_result(WPARAM, LPARAM);
931 return; /* don't unpend the pending */
933 pending_netevent = FALSE;
936 ret = select_result(pend_netevent_wParam, pend_netevent_lParam);
939 if (ret == 0 && !session_closed) {
940 /* Abnormal exits will already have set session_closed and taken
941 * appropriate action. */
942 if (cfg.close_on_exit == COE_ALWAYS ||
943 cfg.close_on_exit == COE_NORMAL) PostQuitMessage(0);
945 session_closed = TRUE;
946 SetWindowText(hwnd, "PuTTY (inactive)");
947 MessageBox(hwnd, "Connection closed by remote host",
948 "PuTTY", MB_OK | MB_ICONINFORMATION);
954 * Copy the colour palette from the configuration data into defpal.
955 * This is non-trivial because the colour indices are different.
957 static void cfgtopalette(void)
960 static const int ww[] = {
961 6, 7, 8, 9, 10, 11, 12, 13,
962 14, 15, 16, 17, 18, 19, 20, 21,
963 0, 1, 2, 3, 4, 4, 5, 5
966 for (i = 0; i < 24; i++) {
968 defpal[i].rgbtRed = cfg.colours[w][0];
969 defpal[i].rgbtGreen = cfg.colours[w][1];
970 defpal[i].rgbtBlue = cfg.colours[w][2];
975 * Set up the colour palette.
977 static void init_palette(void)
980 HDC hdc = GetDC(hwnd);
982 if (cfg.try_palette && GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) {
983 logpal = smalloc(sizeof(*logpal)
984 - sizeof(logpal->palPalEntry)
985 + NCOLOURS * sizeof(PALETTEENTRY));
986 logpal->palVersion = 0x300;
987 logpal->palNumEntries = NCOLOURS;
988 for (i = 0; i < NCOLOURS; i++) {
989 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
990 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
991 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
992 logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
994 pal = CreatePalette(logpal);
996 SelectPalette(hdc, pal, FALSE);
998 SelectPalette(hdc, GetStockObject(DEFAULT_PALETTE), FALSE);
1001 ReleaseDC(hwnd, hdc);
1004 for (i = 0; i < NCOLOURS; i++)
1005 colours[i] = PALETTERGB(defpal[i].rgbtRed,
1006 defpal[i].rgbtGreen,
1007 defpal[i].rgbtBlue);
1009 for (i = 0; i < NCOLOURS; i++)
1010 colours[i] = RGB(defpal[i].rgbtRed,
1011 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
1015 * Initialise all the fonts we will need initially. There may be as many as
1016 * three or as few as one. The other (poentially) twentyone fonts are done
1017 * if/when they are needed.
1021 * - check the font width and height, correcting our guesses if
1024 * - verify that the bold font is the same width as the ordinary
1025 * one, and engage shadow bolding if not.
1027 * - verify that the underlined font is the same width as the
1028 * ordinary one (manual underlining by means of line drawing can
1029 * be done in a pinch).
1031 static void init_fonts(int pick_width, int pick_height)
1038 int fw_dontcare, fw_bold;
1040 for (i = 0; i < FONT_MAXNO; i++)
1043 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
1044 und_mode = UND_FONT;
1046 if (cfg.fontisbold) {
1047 fw_dontcare = FW_BOLD;
1050 fw_dontcare = FW_DONTCARE;
1057 font_height = pick_height;
1059 font_height = cfg.fontheight;
1060 if (font_height > 0) {
1062 -MulDiv(font_height, GetDeviceCaps(hdc, LOGPIXELSY), 72);
1065 font_width = pick_width;
1067 #define f(i,c,w,u) \
1068 fonts[i] = CreateFont (font_height, font_width, 0, 0, w, FALSE, u, FALSE, \
1069 c, OUT_DEFAULT_PRECIS, \
1070 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, \
1071 FIXED_PITCH | FF_DONTCARE, cfg.font)
1073 f(FONT_NORMAL, cfg.fontcharset, fw_dontcare, FALSE);
1075 lfont.lfHeight = font_height;
1076 lfont.lfWidth = font_width;
1077 lfont.lfEscapement = 0;
1078 lfont.lfOrientation = 0;
1079 lfont.lfWeight = fw_dontcare;
1080 lfont.lfItalic = FALSE;
1081 lfont.lfUnderline = FALSE;
1082 lfont.lfStrikeOut = FALSE;
1083 lfont.lfCharSet = cfg.fontcharset;
1084 lfont.lfOutPrecision = OUT_DEFAULT_PRECIS;
1085 lfont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
1086 lfont.lfQuality = DEFAULT_QUALITY;
1087 lfont.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE;
1088 strncpy(lfont.lfFaceName, cfg.font, LF_FACESIZE);
1090 SelectObject(hdc, fonts[FONT_NORMAL]);
1091 GetTextMetrics(hdc, &tm);
1093 if (pick_width == 0 || pick_height == 0) {
1094 font_height = tm.tmHeight;
1095 font_width = tm.tmAveCharWidth;
1097 font_dualwidth = (tm.tmAveCharWidth != tm.tmMaxCharWidth);
1099 #ifdef RDB_DEBUG_PATCH
1100 debug(23, "Primary font H=%d, AW=%d, MW=%d",
1101 tm.tmHeight, tm.tmAveCharWidth, tm.tmMaxCharWidth);
1106 DWORD cset = tm.tmCharSet;
1107 memset(&info, 0xFF, sizeof(info));
1109 /* !!! Yes the next line is right */
1110 if (cset == OEM_CHARSET)
1111 font_codepage = GetOEMCP();
1113 if (TranslateCharsetInfo
1114 ((DWORD *) cset, &info, TCI_SRCCHARSET)) font_codepage =
1119 GetCPInfo(font_codepage, &cpinfo);
1120 dbcs_screenfont = (cpinfo.MaxCharSize > 1);
1123 f(FONT_UNDERLINE, cfg.fontcharset, fw_dontcare, TRUE);
1126 * Some fonts, e.g. 9-pt Courier, draw their underlines
1127 * outside their character cell. We successfully prevent
1128 * screen corruption by clipping the text output, but then
1129 * we lose the underline completely. Here we try to work
1130 * out whether this is such a font, and if it is, we set a
1131 * flag that causes underlines to be drawn by hand.
1133 * Having tried other more sophisticated approaches (such
1134 * as examining the TEXTMETRIC structure or requesting the
1135 * height of a string), I think we'll do this the brute
1136 * force way: we create a small bitmap, draw an underlined
1137 * space on it, and test to see whether any pixels are
1138 * foreground-coloured. (Since we expect the underline to
1139 * go all the way across the character cell, we only search
1140 * down a single column of the bitmap, half way across.)
1144 HBITMAP und_bm, und_oldbm;
1148 und_dc = CreateCompatibleDC(hdc);
1149 und_bm = CreateCompatibleBitmap(hdc, font_width, font_height);
1150 und_oldbm = SelectObject(und_dc, und_bm);
1151 SelectObject(und_dc, fonts[FONT_UNDERLINE]);
1152 SetTextAlign(und_dc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
1153 SetTextColor(und_dc, RGB(255, 255, 255));
1154 SetBkColor(und_dc, RGB(0, 0, 0));
1155 SetBkMode(und_dc, OPAQUE);
1156 ExtTextOut(und_dc, 0, 0, ETO_OPAQUE, NULL, " ", 1, NULL);
1158 for (i = 0; i < font_height; i++) {
1159 c = GetPixel(und_dc, font_width / 2, i);
1160 if (c != RGB(0, 0, 0))
1163 SelectObject(und_dc, und_oldbm);
1164 DeleteObject(und_bm);
1167 und_mode = UND_LINE;
1168 DeleteObject(fonts[FONT_UNDERLINE]);
1169 fonts[FONT_UNDERLINE] = 0;
1173 if (bold_mode == BOLD_FONT) {
1174 f(FONT_BOLD, cfg.fontcharset, fw_bold, FALSE);
1178 descent = tm.tmAscent + 1;
1179 if (descent >= font_height)
1180 descent = font_height - 1;
1182 for (i = 0; i < 3; i++) {
1184 if (SelectObject(hdc, fonts[i]) && GetTextMetrics(hdc, &tm))
1185 fontsize[i] = tm.tmAveCharWidth + 256 * tm.tmHeight;
1192 ReleaseDC(hwnd, hdc);
1194 if (fontsize[FONT_UNDERLINE] != fontsize[FONT_NORMAL]) {
1195 und_mode = UND_LINE;
1196 DeleteObject(fonts[FONT_UNDERLINE]);
1197 fonts[FONT_UNDERLINE] = 0;
1200 if (bold_mode == BOLD_FONT &&
1201 fontsize[FONT_BOLD] != fontsize[FONT_NORMAL]) {
1202 bold_mode = BOLD_SHADOW;
1203 DeleteObject(fonts[FONT_BOLD]);
1204 fonts[FONT_BOLD] = 0;
1206 fontflag[0] = fontflag[1] = fontflag[2] = 1;
1211 static void another_font(int fontno)
1214 int fw_dontcare, fw_bold;
1218 if (fontno < 0 || fontno >= FONT_MAXNO || fontflag[fontno])
1221 basefont = (fontno & ~(FONT_BOLDUND));
1222 if (basefont != fontno && !fontflag[basefont])
1223 another_font(basefont);
1225 if (cfg.fontisbold) {
1226 fw_dontcare = FW_BOLD;
1229 fw_dontcare = FW_DONTCARE;
1233 c = cfg.fontcharset;
1239 if (fontno & FONT_WIDE)
1241 if (fontno & FONT_NARROW)
1243 if (fontno & FONT_OEM)
1245 if (fontno & FONT_BOLD)
1247 if (fontno & FONT_UNDERLINE)
1251 CreateFont(font_height * (1 + !!(fontno & FONT_HIGH)), x, 0, 0, w,
1252 FALSE, u, FALSE, c, OUT_DEFAULT_PRECIS,
1253 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
1254 FIXED_PITCH | FF_DONTCARE, s);
1256 fontflag[fontno] = 1;
1259 static void deinit_fonts(void)
1262 for (i = 0; i < FONT_MAXNO; i++) {
1264 DeleteObject(fonts[i]);
1270 void request_resize(void *frontend, int w, int h)
1274 /* If the window is maximized supress resizing attempts */
1275 if (IsZoomed(hwnd)) {
1276 if (cfg.resize_action == RESIZE_TERM)
1280 if (cfg.resize_action == RESIZE_DISABLED) return;
1281 if (h == term->rows && w == term->cols) return;
1283 /* Sanity checks ... */
1285 static int first_time = 1;
1288 switch (first_time) {
1290 /* Get the size of the screen */
1291 if (get_fullscreen_rect(&ss))
1292 /* first_time = 0 */ ;
1298 /* Make sure the values are sane */
1299 width = (ss.right - ss.left - extra_width) / 4;
1300 height = (ss.bottom - ss.top - extra_height) / 6;
1302 if (w > width || h > height)
1311 term_size(term, h, w, cfg.savelines);
1313 if (cfg.resize_action != RESIZE_FONT && !IsZoomed(hwnd)) {
1314 width = extra_width + font_width * w;
1315 height = extra_height + font_height * h;
1317 SetWindowPos(hwnd, NULL, 0, 0, width, height,
1318 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1319 SWP_NOMOVE | SWP_NOZORDER);
1323 InvalidateRect(hwnd, NULL, TRUE);
1326 static void reset_window(int reinit) {
1328 * This function decides how to resize or redraw when the
1329 * user changes something.
1331 * This function doesn't like to change the terminal size but if the
1332 * font size is locked that may be it's only soluion.
1334 int win_width, win_height;
1337 #ifdef RDB_DEBUG_PATCH
1338 debug((27, "reset_window()"));
1341 /* Current window sizes ... */
1342 GetWindowRect(hwnd, &wr);
1343 GetClientRect(hwnd, &cr);
1345 win_width = cr.right - cr.left;
1346 win_height = cr.bottom - cr.top;
1348 if (cfg.resize_action == RESIZE_DISABLED) reinit = 2;
1350 /* Are we being forced to reload the fonts ? */
1352 #ifdef RDB_DEBUG_PATCH
1353 debug((27, "reset_window() -- Forced deinit"));
1359 /* Oh, looks like we're minimised */
1360 if (win_width == 0 || win_height == 0)
1363 /* Is the window out of position ? */
1365 (offset_width != (win_width-font_width*term->cols)/2 ||
1366 offset_height != (win_height-font_height*term->rows)/2) ){
1367 offset_width = (win_width-font_width*term->cols)/2;
1368 offset_height = (win_height-font_height*term->rows)/2;
1369 InvalidateRect(hwnd, NULL, TRUE);
1370 #ifdef RDB_DEBUG_PATCH
1371 debug((27, "reset_window() -> Reposition terminal"));
1375 if (IsZoomed(hwnd)) {
1376 /* We're fullscreen, this means we must not change the size of
1377 * the window so it's the font size or the terminal itself.
1380 extra_width = wr.right - wr.left - cr.right + cr.left;
1381 extra_height = wr.bottom - wr.top - cr.bottom + cr.top;
1383 if (cfg.resize_action != RESIZE_TERM) {
1384 if ( font_width != win_width/term->cols ||
1385 font_height != win_height/term->rows) {
1387 init_fonts(win_width/term->cols, win_height/term->rows);
1388 offset_width = (win_width-font_width*term->cols)/2;
1389 offset_height = (win_height-font_height*term->rows)/2;
1390 InvalidateRect(hwnd, NULL, TRUE);
1391 #ifdef RDB_DEBUG_PATCH
1392 debug((25, "reset_window() -> Z font resize to (%d, %d)",
1393 font_width, font_height));
1397 if ( font_width != win_width/term->cols ||
1398 font_height != win_height/term->rows) {
1399 /* Our only choice at this point is to change the
1400 * size of the terminal; Oh well.
1402 term_size(term, win_height/font_height, win_width/font_width,
1404 offset_width = (win_width-font_width*term->cols)/2;
1405 offset_height = (win_height-font_height*term->rows)/2;
1406 InvalidateRect(hwnd, NULL, TRUE);
1407 #ifdef RDB_DEBUG_PATCH
1408 debug((27, "reset_window() -> Zoomed term_size"));
1415 /* Hmm, a force re-init means we should ignore the current window
1416 * so we resize to the default font size.
1419 #ifdef RDB_DEBUG_PATCH
1420 debug((27, "reset_window() -> Forced re-init"));
1423 offset_width = offset_height = cfg.window_border;
1424 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
1425 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
1427 if (win_width != font_width*term->cols + offset_width*2 ||
1428 win_height != font_height*term->rows + offset_height*2) {
1430 /* If this is too large windows will resize it to the maximum
1431 * allowed window size, we will then be back in here and resize
1432 * the font or terminal to fit.
1434 SetWindowPos(hwnd, NULL, 0, 0,
1435 font_width*term->cols + extra_width,
1436 font_height*term->rows + extra_height,
1437 SWP_NOMOVE | SWP_NOZORDER);
1440 InvalidateRect(hwnd, NULL, TRUE);
1444 /* Okay the user doesn't want us to change the font so we try the
1445 * window. But that may be too big for the screen which forces us
1446 * to change the terminal.
1448 if ((cfg.resize_action == RESIZE_TERM && reinit<=0) ||
1449 (cfg.resize_action == RESIZE_EITHER && reinit<0) ||
1451 offset_width = offset_height = cfg.window_border;
1452 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
1453 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
1455 if (win_width != font_width*term->cols + offset_width*2 ||
1456 win_height != font_height*term->rows + offset_height*2) {
1461 get_fullscreen_rect(&ss);
1463 width = (ss.right - ss.left - extra_width) / font_width;
1464 height = (ss.bottom - ss.top - extra_height) / font_height;
1467 if ( term->rows > height || term->cols > width ) {
1468 if (cfg.resize_action == RESIZE_EITHER) {
1469 /* Make the font the biggest we can */
1470 if (term->cols > width)
1471 font_width = (ss.right - ss.left - extra_width)
1473 if (term->rows > height)
1474 font_height = (ss.bottom - ss.top - extra_height)
1478 init_fonts(font_width, font_height);
1480 width = (ss.right - ss.left - extra_width) / font_width;
1481 height = (ss.bottom - ss.top - extra_height) / font_height;
1483 if ( height > term->rows ) height = term->rows;
1484 if ( width > term->cols ) width = term->cols;
1485 term_size(term, height, width, cfg.savelines);
1486 #ifdef RDB_DEBUG_PATCH
1487 debug((27, "reset_window() -> term resize to (%d,%d)",
1493 SetWindowPos(hwnd, NULL, 0, 0,
1494 font_width*term->cols + extra_width,
1495 font_height*term->rows + extra_height,
1496 SWP_NOMOVE | SWP_NOZORDER);
1498 InvalidateRect(hwnd, NULL, TRUE);
1499 #ifdef RDB_DEBUG_PATCH
1500 debug((27, "reset_window() -> window resize to (%d,%d)",
1501 font_width*term->cols + extra_width,
1502 font_height*term->rows + extra_height));
1508 /* We're allowed to or must change the font but do we want to ? */
1510 if (font_width != (win_width-cfg.window_border*2)/term->cols ||
1511 font_height != (win_height-cfg.window_border*2)/term->rows) {
1514 init_fonts((win_width-cfg.window_border*2)/term->cols,
1515 (win_height-cfg.window_border*2)/term->rows);
1516 offset_width = (win_width-font_width*term->cols)/2;
1517 offset_height = (win_height-font_height*term->rows)/2;
1519 extra_width = wr.right - wr.left - cr.right + cr.left +offset_width*2;
1520 extra_height = wr.bottom - wr.top - cr.bottom + cr.top+offset_height*2;
1522 InvalidateRect(hwnd, NULL, TRUE);
1523 #ifdef RDB_DEBUG_PATCH
1524 debug((25, "reset_window() -> font resize to (%d,%d)",
1525 font_width, font_height));
1530 static void set_input_locale(HKL kl)
1534 GetLocaleInfo(LOWORD(kl), LOCALE_IDEFAULTANSICODEPAGE,
1535 lbuf, sizeof(lbuf));
1537 kbd_codepage = atoi(lbuf);
1540 static void click(Mouse_Button b, int x, int y, int shift, int ctrl, int alt)
1542 int thistime = GetMessageTime();
1544 if (send_raw_mouse && !(cfg.mouse_override && shift)) {
1545 lastbtn = MBT_NOTHING;
1546 term_mouse(term, b, MA_CLICK, x, y, shift, ctrl, alt);
1550 if (lastbtn == b && thistime - lasttime < dbltime) {
1551 lastact = (lastact == MA_CLICK ? MA_2CLK :
1552 lastact == MA_2CLK ? MA_3CLK :
1553 lastact == MA_3CLK ? MA_CLICK : MA_NOTHING);
1558 if (lastact != MA_NOTHING)
1559 term_mouse(term, b, lastact, x, y, shift, ctrl, alt);
1560 lasttime = thistime;
1564 * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT)
1565 * into a cooked one (SELECT, EXTEND, PASTE).
1567 Mouse_Button translate_button(void *frontend, Mouse_Button button)
1569 if (button == MBT_LEFT)
1571 if (button == MBT_MIDDLE)
1572 return cfg.mouse_is_xterm ? MBT_PASTE : MBT_EXTEND;
1573 if (button == MBT_RIGHT)
1574 return cfg.mouse_is_xterm ? MBT_EXTEND : MBT_PASTE;
1575 return 0; /* shouldn't happen */
1578 static void show_mouseptr(int show)
1580 static int cursor_visible = 1;
1581 if (!cfg.hide_mouseptr) /* override if this feature disabled */
1583 if (cursor_visible && !show)
1585 else if (!cursor_visible && show)
1587 cursor_visible = show;
1590 static int is_alt_pressed(void)
1593 int r = GetKeyboardState(keystate);
1596 if (keystate[VK_MENU] & 0x80)
1598 if (keystate[VK_RMENU] & 0x80)
1603 static int is_shift_pressed(void)
1606 int r = GetKeyboardState(keystate);
1609 if (keystate[VK_SHIFT] & 0x80)
1614 static int resizing;
1616 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1617 WPARAM wParam, LPARAM lParam)
1620 static int ignore_clip = FALSE;
1621 static int need_backend_resize = FALSE;
1622 static int fullscr_on_max = FALSE;
1626 if (pending_netevent)
1627 enact_pending_netevent();
1628 if (GetCapture() != hwnd ||
1629 (send_raw_mouse && !(cfg.mouse_override && is_shift_pressed())))
1635 if (cfg.ping_interval > 0) {
1638 if (now - last_movement > cfg.ping_interval) {
1639 back->special(backhandle, TS_PING);
1640 last_movement = now;
1643 net_pending_errors();
1649 if (!cfg.warn_on_close || session_closed ||
1651 "Are you sure you want to close this session?",
1652 "PuTTY Exit Confirmation",
1653 MB_ICONWARNING | MB_OKCANCEL) == IDOK)
1654 DestroyWindow(hwnd);
1661 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1673 PROCESS_INFORMATION pi;
1674 HANDLE filemap = NULL;
1676 if (wParam == IDM_DUPSESS) {
1678 * Allocate a file-mapping memory chunk for the
1681 SECURITY_ATTRIBUTES sa;
1684 sa.nLength = sizeof(sa);
1685 sa.lpSecurityDescriptor = NULL;
1686 sa.bInheritHandle = TRUE;
1687 filemap = CreateFileMapping((HANDLE) 0xFFFFFFFF,
1690 0, sizeof(Config), NULL);
1692 p = (Config *) MapViewOfFile(filemap,
1694 0, 0, sizeof(Config));
1696 *p = cfg; /* structure copy */
1700 sprintf(c, "putty &%p", filemap);
1702 } else if (wParam == IDM_SAVEDSESS) {
1703 if ((lParam - IDM_SAVED_MIN) / 16 < sesslist.nsessions) {
1705 sesslist.sessions[(lParam - IDM_SAVED_MIN) / 16];
1706 cl = smalloc(16 + strlen(session));
1707 /* 8, but play safe */
1710 /* not a very important failure mode */
1712 sprintf(cl, "putty @%s", session);
1720 GetModuleFileName(NULL, b, sizeof(b) - 1);
1722 si.lpReserved = NULL;
1723 si.lpDesktop = NULL;
1727 si.lpReserved2 = NULL;
1728 CreateProcess(b, cl, NULL, NULL, TRUE,
1729 NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
1732 CloseHandle(filemap);
1742 GetWindowText(hwnd, cfg.wintitle, sizeof(cfg.wintitle));
1745 if (!do_reconfig(hwnd))
1749 /* Disable full-screen if resizing forbidden */
1750 HMENU m = GetSystemMenu (hwnd, FALSE);
1751 EnableMenuItem(m, IDM_FULLSCREEN, MF_BYCOMMAND |
1752 (cfg.resize_action == RESIZE_DISABLED)
1753 ? MF_GRAYED : MF_ENABLED);
1754 /* Gracefully unzoom if necessary */
1755 if (IsZoomed(hwnd) &&
1756 (cfg.resize_action == RESIZE_DISABLED)) {
1757 ShowWindow(hwnd, SW_RESTORE);
1761 if (strcmp(prev_cfg.logfilename, cfg.logfilename) ||
1762 prev_cfg.logtype != cfg.logtype) {
1763 logfclose(logctx); /* reset logging */
1769 * Flush the line discipline's edit buffer in the
1770 * case where local editing has just been disabled.
1772 ldisc_send(ldisc, NULL, 0, 0);
1780 /* Give terminal a heads-up on miscellaneous stuff */
1781 term_reconfig(term);
1783 /* Screen size changed ? */
1784 if (cfg.height != prev_cfg.height ||
1785 cfg.width != prev_cfg.width ||
1786 cfg.savelines != prev_cfg.savelines ||
1787 cfg.resize_action == RESIZE_FONT ||
1788 (cfg.resize_action == RESIZE_EITHER && IsZoomed(hwnd)) ||
1789 cfg.resize_action == RESIZE_DISABLED)
1790 term_size(term, cfg.height, cfg.width, cfg.savelines);
1792 /* Enable or disable the scroll bar, etc */
1794 LONG nflg, flag = GetWindowLong(hwnd, GWL_STYLE);
1795 LONG nexflag, exflag =
1796 GetWindowLong(hwnd, GWL_EXSTYLE);
1799 if (cfg.alwaysontop != prev_cfg.alwaysontop) {
1800 if (cfg.alwaysontop) {
1801 nexflag |= WS_EX_TOPMOST;
1802 SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
1803 SWP_NOMOVE | SWP_NOSIZE);
1805 nexflag &= ~(WS_EX_TOPMOST);
1806 SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0,
1807 SWP_NOMOVE | SWP_NOSIZE);
1810 if (cfg.sunken_edge)
1811 nexflag |= WS_EX_CLIENTEDGE;
1813 nexflag &= ~(WS_EX_CLIENTEDGE);
1816 if (is_full_screen() ?
1817 cfg.scrollbar_in_fullscreen : cfg.scrollbar)
1820 nflg &= ~WS_VSCROLL;
1822 if (cfg.resize_action == RESIZE_DISABLED ||
1824 nflg &= ~WS_THICKFRAME;
1826 nflg |= WS_THICKFRAME;
1828 if (cfg.resize_action == RESIZE_DISABLED)
1829 nflg &= ~WS_MAXIMIZEBOX;
1831 nflg |= WS_MAXIMIZEBOX;
1833 if (nflg != flag || nexflag != exflag) {
1835 SetWindowLong(hwnd, GWL_STYLE, nflg);
1836 if (nexflag != exflag)
1837 SetWindowLong(hwnd, GWL_EXSTYLE, nexflag);
1839 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
1840 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1841 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
1849 if (cfg.resize_action == RESIZE_DISABLED && IsZoomed(hwnd)) {
1854 set_title(NULL, cfg.wintitle);
1855 if (IsIconic(hwnd)) {
1857 cfg.win_name_always ? window_name :
1861 if (strcmp(cfg.font, prev_cfg.font) != 0 ||
1862 strcmp(cfg.line_codepage, prev_cfg.line_codepage) != 0 ||
1863 cfg.fontisbold != prev_cfg.fontisbold ||
1864 cfg.fontheight != prev_cfg.fontheight ||
1865 cfg.fontcharset != prev_cfg.fontcharset ||
1866 cfg.vtmode != prev_cfg.vtmode ||
1867 cfg.bold_colour != prev_cfg.bold_colour ||
1868 cfg.resize_action == RESIZE_DISABLED ||
1869 cfg.resize_action == RESIZE_EITHER ||
1870 (cfg.resize_action != prev_cfg.resize_action))
1873 InvalidateRect(hwnd, NULL, TRUE);
1874 reset_window(init_lvl);
1875 net_pending_errors();
1886 ldisc_send(ldisc, NULL, 0, 0);
1889 back->special(backhandle, TS_AYT);
1890 net_pending_errors();
1893 back->special(backhandle, TS_BRK);
1894 net_pending_errors();
1897 back->special(backhandle, TS_SYNCH);
1898 net_pending_errors();
1901 back->special(backhandle, TS_EC);
1902 net_pending_errors();
1905 back->special(backhandle, TS_EL);
1906 net_pending_errors();
1909 back->special(backhandle, TS_GA);
1910 net_pending_errors();
1913 back->special(backhandle, TS_NOP);
1914 net_pending_errors();
1917 back->special(backhandle, TS_ABORT);
1918 net_pending_errors();
1921 back->special(backhandle, TS_AO);
1922 net_pending_errors();
1925 back->special(backhandle, TS_IP);
1926 net_pending_errors();
1929 back->special(backhandle, TS_SUSP);
1930 net_pending_errors();
1933 back->special(backhandle, TS_EOR);
1934 net_pending_errors();
1937 back->special(backhandle, TS_EOF);
1938 net_pending_errors();
1944 WinHelp(hwnd, help_path,
1945 help_has_contents ? HELP_FINDER : HELP_CONTENTS, 0);
1949 * We get this if the System menu has been activated
1956 * We get this if the System menu has been activated
1957 * using the keyboard. This might happen from within
1958 * TranslateKey, in which case it really wants to be
1959 * followed by a `space' character to actually _bring
1960 * the menu up_ rather than just sitting there in
1961 * `ready to appear' state.
1963 show_mouseptr(1); /* make sure pointer is visible */
1965 PostMessage(hwnd, WM_CHAR, ' ', 0);
1967 case IDM_FULLSCREEN:
1971 if (wParam >= IDM_SAVED_MIN && wParam <= IDM_SAVED_MAX) {
1972 SendMessage(hwnd, WM_SYSCOMMAND, IDM_SAVEDSESS, wParam);
1977 #define X_POS(l) ((int)(short)LOWORD(l))
1978 #define Y_POS(l) ((int)(short)HIWORD(l))
1980 #define TO_CHR_X(x) ((((x)<0 ? (x)-font_width+1 : (x))-offset_width) / font_width)
1981 #define TO_CHR_Y(y) ((((y)<0 ? (y)-font_height+1: (y))-offset_height) / font_height)
1982 case WM_LBUTTONDOWN:
1983 case WM_MBUTTONDOWN:
1984 case WM_RBUTTONDOWN:
1992 case WM_LBUTTONDOWN:
1996 case WM_MBUTTONDOWN:
1997 button = MBT_MIDDLE;
2000 case WM_RBUTTONDOWN:
2009 button = MBT_MIDDLE;
2017 button = press = 0; /* shouldn't happen */
2021 * Special case: in full-screen mode, if the left
2022 * button is clicked in the very top left corner of the
2023 * window, we put up the System menu instead of doing
2026 if (is_full_screen() && press && button == MBT_LEFT &&
2027 X_POS(lParam) == 0 && Y_POS(lParam) == 0) {
2028 SendMessage(hwnd, WM_SYSCOMMAND, SC_MOUSEMENU, 0);
2033 TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam)),
2034 wParam & MK_SHIFT, wParam & MK_CONTROL,
2038 term_mouse(term, button, MA_RELEASE,
2039 TO_CHR_X(X_POS(lParam)),
2040 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
2041 wParam & MK_CONTROL, is_alt_pressed());
2049 * Add the mouse position and message time to the random
2052 noise_ultralight(lParam);
2054 if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON) &&
2055 GetCapture() == hwnd) {
2057 if (wParam & MK_LBUTTON)
2059 else if (wParam & MK_MBUTTON)
2063 term_mouse(term, b, MA_DRAG, TO_CHR_X(X_POS(lParam)),
2064 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
2065 wParam & MK_CONTROL, is_alt_pressed());
2068 case WM_NCMOUSEMOVE:
2070 noise_ultralight(lParam);
2072 case WM_IGNORE_CLIP:
2073 ignore_clip = wParam; /* don't panic on DESTROYCLIPBOARD */
2075 case WM_DESTROYCLIPBOARD:
2077 term_deselect(term);
2078 ignore_clip = FALSE;
2084 hdc = BeginPaint(hwnd, &p);
2086 SelectPalette(hdc, pal, TRUE);
2087 RealizePalette(hdc);
2089 term_paint(term, hdc,
2090 (p.rcPaint.left-offset_width)/font_width,
2091 (p.rcPaint.top-offset_height)/font_height,
2092 (p.rcPaint.right-offset_width-1)/font_width,
2093 (p.rcPaint.bottom-offset_height-1)/font_height,
2097 p.rcPaint.left < offset_width ||
2098 p.rcPaint.top < offset_height ||
2099 p.rcPaint.right >= offset_width + font_width*term->cols ||
2100 p.rcPaint.bottom>= offset_height + font_height*term->rows)
2102 HBRUSH fillcolour, oldbrush;
2104 fillcolour = CreateSolidBrush (
2105 colours[(ATTR_DEFBG>>ATTR_BGSHIFT)*2]);
2106 oldbrush = SelectObject(hdc, fillcolour);
2107 edge = CreatePen(PS_SOLID, 0,
2108 colours[(ATTR_DEFBG>>ATTR_BGSHIFT)*2]);
2109 oldpen = SelectObject(hdc, edge);
2112 * Jordan Russell reports that this apparently
2113 * ineffectual IntersectClipRect() call masks a
2114 * Windows NT/2K bug causing strange display
2115 * problems when the PuTTY window is taller than
2116 * the primary monitor. It seems harmless enough...
2118 IntersectClipRect(hdc,
2119 p.rcPaint.left, p.rcPaint.top,
2120 p.rcPaint.right, p.rcPaint.bottom);
2122 ExcludeClipRect(hdc,
2123 offset_width, offset_height,
2124 offset_width+font_width*term->cols,
2125 offset_height+font_height*term->rows);
2127 Rectangle(hdc, p.rcPaint.left, p.rcPaint.top,
2128 p.rcPaint.right, p.rcPaint.bottom);
2130 // SelectClipRgn(hdc, NULL);
2132 SelectObject(hdc, oldbrush);
2133 DeleteObject(fillcolour);
2134 SelectObject(hdc, oldpen);
2137 SelectObject(hdc, GetStockObject(SYSTEM_FONT));
2138 SelectObject(hdc, GetStockObject(WHITE_PEN));
2144 /* Notice we can get multiple netevents, FD_READ, FD_WRITE etc
2145 * but the only one that's likely to try to overload us is FD_READ.
2146 * This means buffering just one is fine.
2148 if (pending_netevent)
2149 enact_pending_netevent();
2151 pending_netevent = TRUE;
2152 pend_netevent_wParam = wParam;
2153 pend_netevent_lParam = lParam;
2154 if (WSAGETSELECTEVENT(lParam) != FD_READ)
2155 enact_pending_netevent();
2157 time(&last_movement);
2160 term->has_focus = TRUE;
2161 CreateCaret(hwnd, caretbm, font_width, font_height);
2163 flash_window(0); /* stop */
2170 term->has_focus = FALSE;
2172 caret_x = caret_y = -1; /* ensure caret is replaced next time */
2176 case WM_ENTERSIZEMOVE:
2177 #ifdef RDB_DEBUG_PATCH
2178 debug((27, "WM_ENTERSIZEMOVE"));
2182 need_backend_resize = FALSE;
2184 case WM_EXITSIZEMOVE:
2187 #ifdef RDB_DEBUG_PATCH
2188 debug((27, "WM_EXITSIZEMOVE"));
2190 if (need_backend_resize) {
2191 term_size(term, cfg.height, cfg.width, cfg.savelines);
2192 InvalidateRect(hwnd, NULL, TRUE);
2197 * This does two jobs:
2198 * 1) Keep the sizetip uptodate
2199 * 2) Make sure the window size is _stepped_ in units of the font size.
2201 if (cfg.resize_action != RESIZE_FONT && !is_alt_pressed()) {
2202 int width, height, w, h, ew, eh;
2203 LPRECT r = (LPRECT) lParam;
2205 if ( !need_backend_resize && cfg.resize_action == RESIZE_EITHER &&
2206 (cfg.height != term->rows || cfg.width != term->cols )) {
2208 * Great! It seems that both the terminal size and the
2209 * font size have been changed and the user is now dragging.
2211 * It will now be difficult to get back to the configured
2214 * This would be easier but it seems to be too confusing.
2216 term_size(term, cfg.height, cfg.width, cfg.savelines);
2219 cfg.height=term->rows; cfg.width=term->cols;
2221 InvalidateRect(hwnd, NULL, TRUE);
2222 need_backend_resize = TRUE;
2225 width = r->right - r->left - extra_width;
2226 height = r->bottom - r->top - extra_height;
2227 w = (width + font_width / 2) / font_width;
2230 h = (height + font_height / 2) / font_height;
2233 UpdateSizeTip(hwnd, w, h);
2234 ew = width - w * font_width;
2235 eh = height - h * font_height;
2237 if (wParam == WMSZ_LEFT ||
2238 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
2244 if (wParam == WMSZ_TOP ||
2245 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
2255 int width, height, w, h, rv = 0;
2256 int ex_width = extra_width + (cfg.window_border - offset_width) * 2;
2257 int ex_height = extra_height + (cfg.window_border - offset_height) * 2;
2258 LPRECT r = (LPRECT) lParam;
2260 width = r->right - r->left - ex_width;
2261 height = r->bottom - r->top - ex_height;
2263 w = (width + term->cols/2)/term->cols;
2264 h = (height + term->rows/2)/term->rows;
2265 if ( r->right != r->left + w*term->cols + ex_width)
2268 if (wParam == WMSZ_LEFT ||
2269 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
2270 r->left = r->right - w*term->cols - ex_width;
2272 r->right = r->left + w*term->cols + ex_width;
2274 if (r->bottom != r->top + h*term->rows + ex_height)
2277 if (wParam == WMSZ_TOP ||
2278 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
2279 r->top = r->bottom - h*term->rows - ex_height;
2281 r->bottom = r->top + h*term->rows + ex_height;
2285 /* break; (never reached) */
2286 case WM_FULLSCR_ON_MAX:
2287 fullscr_on_max = TRUE;
2290 sys_cursor_update();
2293 #ifdef RDB_DEBUG_PATCH
2294 debug((27, "WM_SIZE %s (%d,%d)",
2295 (wParam == SIZE_MINIMIZED) ? "SIZE_MINIMIZED":
2296 (wParam == SIZE_MAXIMIZED) ? "SIZE_MAXIMIZED":
2297 (wParam == SIZE_RESTORED && resizing) ? "to":
2298 (wParam == SIZE_RESTORED) ? "SIZE_RESTORED":
2300 LOWORD(lParam), HIWORD(lParam)));
2302 if (wParam == SIZE_MINIMIZED)
2304 cfg.win_name_always ? window_name : icon_name);
2305 if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
2306 SetWindowText(hwnd, window_name);
2307 if (wParam == SIZE_RESTORED)
2308 clear_full_screen();
2309 if (wParam == SIZE_MAXIMIZED && fullscr_on_max) {
2310 fullscr_on_max = FALSE;
2314 if (cfg.resize_action == RESIZE_DISABLED) {
2315 /* A resize, well it better be a minimize. */
2319 int width, height, w, h;
2321 width = LOWORD(lParam);
2322 height = HIWORD(lParam);
2325 if (wParam == SIZE_MAXIMIZED && !was_zoomed) {
2327 prev_rows = term->rows;
2328 prev_cols = term->cols;
2329 if (cfg.resize_action == RESIZE_TERM) {
2330 w = width / font_width;
2332 h = height / font_height;
2335 term_size(term, h, w, cfg.savelines);
2338 } else if (wParam == SIZE_RESTORED && was_zoomed) {
2340 if (cfg.resize_action == RESIZE_TERM)
2341 term_size(term, prev_rows, prev_cols, cfg.savelines);
2342 if (cfg.resize_action != RESIZE_FONT)
2347 /* This is an unexpected resize, these will normally happen
2348 * if the window is too large. Probably either the user
2349 * selected a huge font or the screen size has changed.
2351 * This is also called with minimize.
2353 else reset_window(-1);
2357 * Don't call back->size in mid-resize. (To prevent
2358 * massive numbers of resize events getting sent
2359 * down the connection during an NT opaque drag.)
2362 if (cfg.resize_action != RESIZE_FONT && !is_alt_pressed()) {
2363 need_backend_resize = TRUE;
2364 w = (width-cfg.window_border*2) / font_width;
2366 h = (height-cfg.window_border*2) / font_height;
2375 sys_cursor_update();
2378 switch (LOWORD(wParam)) {
2380 term_scroll(term, -1, 0);
2383 term_scroll(term, +1, 0);
2386 term_scroll(term, 0, +1);
2389 term_scroll(term, 0, -1);
2392 term_scroll(term, 0, +term->rows / 2);
2395 term_scroll(term, 0, -term->rows / 2);
2397 case SB_THUMBPOSITION:
2399 term_scroll(term, 1, HIWORD(wParam));
2403 case WM_PALETTECHANGED:
2404 if ((HWND) wParam != hwnd && pal != NULL) {
2405 HDC hdc = get_ctx(NULL);
2407 if (RealizePalette(hdc) > 0)
2413 case WM_QUERYNEWPALETTE:
2415 HDC hdc = get_ctx(NULL);
2417 if (RealizePalette(hdc) > 0)
2429 * Add the scan code and keypress timing to the random
2432 noise_ultralight(lParam);
2435 * We don't do TranslateMessage since it disassociates the
2436 * resulting CHAR message from the KEYDOWN that sparked it,
2437 * which we occasionally don't want. Instead, we process
2438 * KEYDOWN, and call the Win32 translator functions so that
2439 * we get the translations under _our_ control.
2442 unsigned char buf[20];
2445 if (wParam == VK_PROCESSKEY) {
2448 m.message = WM_KEYDOWN;
2450 m.lParam = lParam & 0xdfff;
2451 TranslateMessage(&m);
2453 len = TranslateKey(message, wParam, lParam, buf);
2455 return DefWindowProc(hwnd, message, wParam, lParam);
2459 * Interrupt an ongoing paste. I'm not sure
2460 * this is sensible, but for the moment it's
2461 * preferable to having to faff about buffering
2467 * We need not bother about stdin backlogs
2468 * here, because in GUI PuTTY we can't do
2469 * anything about it anyway; there's no means
2470 * of asking Windows to hold off on KEYDOWN
2471 * messages. We _have_ to buffer everything
2474 term_seen_key_event(term);
2475 ldisc_send(ldisc, buf, len, 1);
2480 net_pending_errors();
2482 case WM_INPUTLANGCHANGE:
2483 /* wParam == Font number */
2484 /* lParam == Locale */
2485 set_input_locale((HKL)lParam);
2486 sys_cursor_update();
2489 if(wParam == IMN_SETOPENSTATUS) {
2490 HIMC hImc = ImmGetContext(hwnd);
2491 ImmSetCompositionFont(hImc, &lfont);
2492 ImmReleaseContext(hwnd, hImc);
2496 case WM_IME_COMPOSITION:
2502 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ||
2503 osVersion.dwPlatformId == VER_PLATFORM_WIN32s) break; /* no Unicode */
2505 if ((lParam & GCS_RESULTSTR) == 0) /* Composition unfinished. */
2506 break; /* fall back to DefWindowProc */
2508 hIMC = ImmGetContext(hwnd);
2509 n = ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, NULL, 0);
2513 buff = (char*) smalloc(n);
2514 ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, buff, n);
2516 * Jaeyoun Chung reports that Korean character
2517 * input doesn't work correctly if we do a single
2518 * luni_send() covering the whole of buff. So
2519 * instead we luni_send the characters one by one.
2521 term_seen_key_event(term);
2522 for (i = 0; i < n; i += 2) {
2523 luni_send(ldisc, (unsigned short *)(buff+i), 1, 1);
2527 ImmReleaseContext(hwnd, hIMC);
2532 if (wParam & 0xFF00) {
2533 unsigned char buf[2];
2536 buf[0] = wParam >> 8;
2537 term_seen_key_event(term);
2538 lpage_send(ldisc, kbd_codepage, buf, 2, 1);
2540 char c = (unsigned char) wParam;
2541 term_seen_key_event(term);
2542 lpage_send(ldisc, kbd_codepage, &c, 1, 1);
2548 * Nevertheless, we are prepared to deal with WM_CHAR
2549 * messages, should they crop up. So if someone wants to
2550 * post the things to us as part of a macro manoeuvre,
2551 * we're ready to cope.
2554 char c = (unsigned char)wParam;
2555 term_seen_key_event(term);
2556 lpage_send(ldisc, CP_ACP, &c, 1, 1);
2560 if (send_raw_mouse && LOWORD(lParam) == HTCLIENT) {
2561 SetCursor(LoadCursor(NULL, IDC_ARROW));
2565 if (message == wm_mousewheel || message == WM_MOUSEWHEEL) {
2566 int shift_pressed=0, control_pressed=0;
2568 if (message == WM_MOUSEWHEEL) {
2569 wheel_accumulator += (short)HIWORD(wParam);
2570 shift_pressed=LOWORD(wParam) & MK_SHIFT;
2571 control_pressed=LOWORD(wParam) & MK_CONTROL;
2574 wheel_accumulator += (int)wParam;
2575 if (GetKeyboardState(keys)!=0) {
2576 shift_pressed=keys[VK_SHIFT]&0x80;
2577 control_pressed=keys[VK_CONTROL]&0x80;
2581 /* process events when the threshold is reached */
2582 while (abs(wheel_accumulator) >= WHEEL_DELTA) {
2585 /* reduce amount for next time */
2586 if (wheel_accumulator > 0) {
2588 wheel_accumulator -= WHEEL_DELTA;
2589 } else if (wheel_accumulator < 0) {
2591 wheel_accumulator += WHEEL_DELTA;
2595 if (send_raw_mouse &&
2596 !(cfg.mouse_override && shift_pressed)) {
2597 /* send a mouse-down followed by a mouse up */
2600 TO_CHR_X(X_POS(lParam)),
2601 TO_CHR_Y(Y_POS(lParam)), shift_pressed,
2602 control_pressed, is_alt_pressed());
2603 term_mouse(term, b, MA_RELEASE, TO_CHR_X(X_POS(lParam)),
2604 TO_CHR_Y(Y_POS(lParam)), shift_pressed,
2605 control_pressed, is_alt_pressed());
2607 /* trigger a scroll */
2608 term_scroll(term, 0,
2610 -term->rows / 2 : term->rows / 2);
2617 return DefWindowProc(hwnd, message, wParam, lParam);
2621 * Move the system caret. (We maintain one, even though it's
2622 * invisible, for the benefit of blind people: apparently some
2623 * helper software tracks the system caret, so we should arrange to
2626 void sys_cursor(void *frontend, int x, int y)
2630 if (!term->has_focus) return;
2633 * Avoid gratuitously re-updating the cursor position and IMM
2634 * window if there's no actual change required.
2636 cx = x * font_width + offset_width;
2637 cy = y * font_height + offset_height;
2638 if (cx == caret_x && cy == caret_y)
2643 sys_cursor_update();
2646 static void sys_cursor_update(void)
2651 if (!term->has_focus) return;
2653 if (caret_x < 0 || caret_y < 0)
2656 SetCaretPos(caret_x, caret_y);
2658 /* IMM calls on Win98 and beyond only */
2659 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32s) return; /* 3.11 */
2661 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS &&
2662 osVersion.dwMinorVersion == 0) return; /* 95 */
2664 /* we should have the IMM functions */
2665 hIMC = ImmGetContext(hwnd);
2666 cf.dwStyle = CFS_POINT;
2667 cf.ptCurrentPos.x = caret_x;
2668 cf.ptCurrentPos.y = caret_y;
2669 ImmSetCompositionWindow(hIMC, &cf);
2671 ImmReleaseContext(hwnd, hIMC);
2675 * Draw a line of text in the window, at given character
2676 * coordinates, in given attributes.
2678 * We are allowed to fiddle with the contents of `text'.
2680 void do_text(Context ctx, int x, int y, char *text, int len,
2681 unsigned long attr, int lattr)
2684 int nfg, nbg, nfont;
2687 int force_manual_underline = 0;
2688 int fnt_width = font_width * (1 + (lattr != LATTR_NORM));
2689 int char_width = fnt_width;
2690 int text_adjust = 0;
2691 static int *IpDx = 0, IpDxLEN = 0;
2693 if (attr & ATTR_WIDE)
2696 if (len > IpDxLEN || IpDx[0] != char_width) {
2698 if (len > IpDxLEN) {
2700 IpDx = smalloc((len + 16) * sizeof(int));
2701 IpDxLEN = (len + 16);
2703 for (i = 0; i < IpDxLEN; i++)
2704 IpDx[i] = char_width;
2707 /* Only want the left half of double width lines */
2708 if (lattr != LATTR_NORM && x*2 >= term->cols)
2716 if ((attr & TATTR_ACTCURS) && (cfg.cursor_type == 0 || term->big_cursor)) {
2717 attr &= ATTR_CUR_AND | (bold_mode != BOLD_COLOURS ? ATTR_BOLD : 0);
2718 attr ^= ATTR_CUR_XOR;
2722 if (cfg.vtmode == VT_POORMAN && lattr != LATTR_NORM) {
2723 /* Assume a poorman font is borken in other ways too. */
2733 nfont |= FONT_WIDE + FONT_HIGH;
2736 if (attr & ATTR_NARROW)
2737 nfont |= FONT_NARROW;
2739 /* Special hack for the VT100 linedraw glyphs. */
2740 if ((attr & CSET_MASK) == 0x2300) {
2741 if (text[0] >= (char) 0xBA && text[0] <= (char) 0xBD) {
2742 switch ((unsigned char) (text[0])) {
2744 text_adjust = -2 * font_height / 5;
2747 text_adjust = -1 * font_height / 5;
2750 text_adjust = font_height / 5;
2753 text_adjust = 2 * font_height / 5;
2756 if (lattr == LATTR_TOP || lattr == LATTR_BOT)
2759 text[0] = (char) (unitab_xterm['q'] & CHAR_MASK);
2760 attr |= (unitab_xterm['q'] & CSET_MASK);
2761 if (attr & ATTR_UNDER) {
2762 attr &= ~ATTR_UNDER;
2763 force_manual_underline = 1;
2768 /* Anything left as an original character set is unprintable. */
2769 if (DIRECT_CHAR(attr)) {
2772 memset(text, 0xFD, len);
2776 if ((attr & CSET_MASK) == ATTR_OEMCP)
2779 nfg = 2 * ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
2780 nbg = 2 * ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
2781 if (bold_mode == BOLD_FONT && (attr & ATTR_BOLD))
2783 if (und_mode == UND_FONT && (attr & ATTR_UNDER))
2784 nfont |= FONT_UNDERLINE;
2785 another_font(nfont);
2786 if (!fonts[nfont]) {
2787 if (nfont & FONT_UNDERLINE)
2788 force_manual_underline = 1;
2789 /* Don't do the same for manual bold, it could be bad news. */
2791 nfont &= ~(FONT_BOLD | FONT_UNDERLINE);
2793 another_font(nfont);
2795 nfont = FONT_NORMAL;
2796 if (attr & ATTR_REVERSE) {
2801 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BOLD))
2803 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BLINK))
2807 SelectObject(hdc, fonts[nfont]);
2808 SetTextColor(hdc, fg);
2809 SetBkColor(hdc, bg);
2810 SetBkMode(hdc, OPAQUE);
2813 line_box.right = x + char_width * len;
2814 line_box.bottom = y + font_height;
2816 /* Only want the left half of double width lines */
2817 if (line_box.right > font_width*term->cols+offset_width)
2818 line_box.right = font_width*term->cols+offset_width;
2820 /* We're using a private area for direct to font. (512 chars.) */
2821 if (dbcs_screenfont && (attr & CSET_MASK) == ATTR_ACP) {
2822 /* Ho Hum, dbcs fonts are a PITA! */
2823 /* To display on W9x I have to convert to UCS */
2824 static wchar_t *uni_buf = 0;
2825 static int uni_len = 0;
2827 if (len > uni_len) {
2829 uni_buf = smalloc((uni_len = len) * sizeof(wchar_t));
2832 for(nlen = mptr = 0; mptr<len; mptr++) {
2833 uni_buf[nlen] = 0xFFFD;
2834 if (IsDBCSLeadByteEx(font_codepage, (BYTE) text[mptr])) {
2835 IpDx[nlen] += char_width;
2836 MultiByteToWideChar(font_codepage, MB_USEGLYPHCHARS,
2837 text+mptr, 2, uni_buf+nlen, 1);
2842 MultiByteToWideChar(font_codepage, MB_USEGLYPHCHARS,
2843 text+mptr, 1, uni_buf+nlen, 1);
2851 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2852 ETO_CLIPPED | ETO_OPAQUE, &line_box, uni_buf, nlen, IpDx);
2853 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2854 SetBkMode(hdc, TRANSPARENT);
2855 ExtTextOutW(hdc, x - 1,
2856 y - font_height * (lattr ==
2857 LATTR_BOT) + text_adjust,
2858 ETO_CLIPPED, &line_box, uni_buf, nlen, IpDx);
2862 } else if (DIRECT_FONT(attr)) {
2864 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2865 ETO_CLIPPED | ETO_OPAQUE, &line_box, text, len, IpDx);
2866 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2867 SetBkMode(hdc, TRANSPARENT);
2869 /* GRR: This draws the character outside it's box and can leave
2870 * 'droppings' even with the clip box! I suppose I could loop it
2871 * one character at a time ... yuk.
2873 * Or ... I could do a test print with "W", and use +1 or -1 for this
2874 * shift depending on if the leftmost column is blank...
2876 ExtTextOut(hdc, x - 1,
2877 y - font_height * (lattr ==
2878 LATTR_BOT) + text_adjust,
2879 ETO_CLIPPED, &line_box, text, len, IpDx);
2882 /* And 'normal' unicode characters */
2883 static WCHAR *wbuf = NULL;
2884 static int wlen = 0;
2889 wbuf = smalloc(wlen * sizeof(WCHAR));
2891 for (i = 0; i < len; i++)
2892 wbuf[i] = (WCHAR) ((attr & CSET_MASK) + (text[i] & CHAR_MASK));
2895 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2896 ETO_CLIPPED | ETO_OPAQUE, &line_box, wbuf, len, IpDx);
2898 /* And the shadow bold hack. */
2899 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2900 SetBkMode(hdc, TRANSPARENT);
2901 ExtTextOutW(hdc, x - 1,
2902 y - font_height * (lattr ==
2903 LATTR_BOT) + text_adjust,
2904 ETO_CLIPPED, &line_box, wbuf, len, IpDx);
2907 if (lattr != LATTR_TOP && (force_manual_underline ||
2908 (und_mode == UND_LINE
2909 && (attr & ATTR_UNDER)))) {
2912 if (lattr == LATTR_BOT)
2913 dec = dec * 2 - font_height;
2915 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, fg));
2916 MoveToEx(hdc, x, y + dec, NULL);
2917 LineTo(hdc, x + len * char_width, y + dec);
2918 oldpen = SelectObject(hdc, oldpen);
2919 DeleteObject(oldpen);
2923 void do_cursor(Context ctx, int x, int y, char *text, int len,
2924 unsigned long attr, int lattr)
2930 int ctype = cfg.cursor_type;
2932 if ((attr & TATTR_ACTCURS) && (ctype == 0 || term->big_cursor)) {
2933 if (((attr & CSET_MASK) | (unsigned char) *text) != UCSWIDE) {
2934 do_text(ctx, x, y, text, len, attr, lattr);
2938 attr |= TATTR_RIGHTCURS;
2941 fnt_width = char_width = font_width * (1 + (lattr != LATTR_NORM));
2942 if (attr & ATTR_WIDE)
2949 if ((attr & TATTR_PASCURS) && (ctype == 0 || term->big_cursor)) {
2952 pts[0].x = pts[1].x = pts[4].x = x;
2953 pts[2].x = pts[3].x = x + char_width - 1;
2954 pts[0].y = pts[3].y = pts[4].y = y;
2955 pts[1].y = pts[2].y = y + font_height - 1;
2956 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2957 Polyline(hdc, pts, 5);
2958 oldpen = SelectObject(hdc, oldpen);
2959 DeleteObject(oldpen);
2960 } else if ((attr & (TATTR_ACTCURS | TATTR_PASCURS)) && ctype != 0) {
2961 int startx, starty, dx, dy, length, i;
2964 starty = y + descent;
2967 length = char_width;
2970 if (attr & TATTR_RIGHTCURS)
2971 xadjust = char_width - 1;
2972 startx = x + xadjust;
2976 length = font_height;
2978 if (attr & TATTR_ACTCURS) {
2981 SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2982 MoveToEx(hdc, startx, starty, NULL);
2983 LineTo(hdc, startx + dx * length, starty + dy * length);
2984 oldpen = SelectObject(hdc, oldpen);
2985 DeleteObject(oldpen);
2987 for (i = 0; i < length; i++) {
2989 SetPixel(hdc, startx, starty, colours[23]);
2998 /* This function gets the actual width of a character in the normal font.
3000 int char_width(Context ctx, int uc) {
3004 /* If the font max is the same as the font ave width then this
3005 * function is a no-op.
3007 if (!font_dualwidth) return 1;
3009 switch (uc & CSET_MASK) {
3011 uc = unitab_line[uc & 0xFF];
3014 uc = unitab_xterm[uc & 0xFF];
3017 uc = unitab_scoacs[uc & 0xFF];
3020 if (DIRECT_FONT(uc)) {
3021 if (dbcs_screenfont) return 1;
3023 /* Speedup, I know of no font where ascii is the wrong width */
3024 if ((uc&CHAR_MASK) >= ' ' && (uc&CHAR_MASK)<= '~')
3027 if ( (uc & CSET_MASK) == ATTR_ACP ) {
3028 SelectObject(hdc, fonts[FONT_NORMAL]);
3029 } else if ( (uc & CSET_MASK) == ATTR_OEMCP ) {
3030 another_font(FONT_OEM);
3031 if (!fonts[FONT_OEM]) return 0;
3033 SelectObject(hdc, fonts[FONT_OEM]);
3037 if ( GetCharWidth32(hdc, uc&CHAR_MASK, uc&CHAR_MASK, &ibuf) != 1 &&
3038 GetCharWidth(hdc, uc&CHAR_MASK, uc&CHAR_MASK, &ibuf) != 1)
3041 /* Speedup, I know of no font where ascii is the wrong width */
3042 if (uc >= ' ' && uc <= '~') return 1;
3044 SelectObject(hdc, fonts[FONT_NORMAL]);
3045 if ( GetCharWidth32W(hdc, uc, uc, &ibuf) == 1 )
3046 /* Okay that one worked */ ;
3047 else if ( GetCharWidthW(hdc, uc, uc, &ibuf) == 1 )
3048 /* This should work on 9x too, but it's "less accurate" */ ;
3053 ibuf += font_width / 2 -1;
3060 * Translate a WM_(SYS)?KEY(UP|DOWN) message into a string of ASCII
3061 * codes. Returns number of bytes used or zero to drop the message
3062 * or -1 to forward the message to windows.
3064 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
3065 unsigned char *output)
3068 int scan, left_alt = 0, key_down, shift_state;
3070 unsigned char *p = output;
3071 static int alt_sum = 0;
3073 HKL kbd_layout = GetKeyboardLayout(0);
3075 static WORD keys[3];
3076 static int compose_char = 0;
3077 static WPARAM compose_key = 0;
3079 r = GetKeyboardState(keystate);
3081 memset(keystate, 0, sizeof(keystate));
3084 #define SHOW_TOASCII_RESULT
3085 { /* Tell us all about key events */
3086 static BYTE oldstate[256];
3087 static int first = 1;
3091 memcpy(oldstate, keystate, sizeof(oldstate));
3094 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT) {
3096 } else if ((HIWORD(lParam) & KF_UP)
3097 && scan == (HIWORD(lParam) & 0xFF)) {
3101 if (wParam >= VK_F1 && wParam <= VK_F20)
3102 debug(("K_F%d", wParam + 1 - VK_F1));
3115 debug(("VK_%02x", wParam));
3117 if (message == WM_SYSKEYDOWN || message == WM_SYSKEYUP)
3119 debug((", S%02x", scan = (HIWORD(lParam) & 0xFF)));
3121 ch = MapVirtualKeyEx(wParam, 2, kbd_layout);
3122 if (ch >= ' ' && ch <= '~')
3123 debug((", '%c'", ch));
3125 debug((", $%02x", ch));
3128 debug((", KB0=%02x", keys[0]));
3130 debug((", KB1=%02x", keys[1]));
3132 debug((", KB2=%02x", keys[2]));
3134 if ((keystate[VK_SHIFT] & 0x80) != 0)
3136 if ((keystate[VK_CONTROL] & 0x80) != 0)
3138 if ((HIWORD(lParam) & KF_EXTENDED))
3140 if ((HIWORD(lParam) & KF_UP))
3144 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT);
3145 else if ((HIWORD(lParam) & KF_UP))
3146 oldstate[wParam & 0xFF] ^= 0x80;
3148 oldstate[wParam & 0xFF] ^= 0x81;
3150 for (ch = 0; ch < 256; ch++)
3151 if (oldstate[ch] != keystate[ch])
3152 debug((", M%02x=%02x", ch, keystate[ch]));
3154 memcpy(oldstate, keystate, sizeof(oldstate));
3158 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED)) {
3159 keystate[VK_RMENU] = keystate[VK_MENU];
3163 /* Nastyness with NUMLock - Shift-NUMLock is left alone though */
3164 if ((cfg.funky_type == 3 ||
3165 (cfg.funky_type <= 1 && term->app_keypad_keys &&
3167 && wParam == VK_NUMLOCK && !(keystate[VK_SHIFT] & 0x80)) {
3169 wParam = VK_EXECUTE;
3171 /* UnToggle NUMLock */
3172 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0)
3173 keystate[VK_NUMLOCK] ^= 1;
3176 /* And write back the 'adjusted' state */
3177 SetKeyboardState(keystate);
3180 /* Disable Auto repeat if required */
3181 if (term->repeat_off &&
3182 (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT)
3185 if ((HIWORD(lParam) & KF_ALTDOWN) && (keystate[VK_RMENU] & 0x80) == 0)
3188 key_down = ((HIWORD(lParam) & KF_UP) == 0);
3190 /* Make sure Ctrl-ALT is not the same as AltGr for ToAscii unless told. */
3191 if (left_alt && (keystate[VK_CONTROL] & 0x80)) {
3192 if (cfg.ctrlaltkeys)
3193 keystate[VK_MENU] = 0;
3195 keystate[VK_RMENU] = 0x80;
3200 scan = (HIWORD(lParam) & (KF_UP | KF_EXTENDED | 0xFF));
3201 shift_state = ((keystate[VK_SHIFT] & 0x80) != 0)
3202 + ((keystate[VK_CONTROL] & 0x80) != 0) * 2;
3204 /* Note if AltGr was pressed and if it was used as a compose key */
3205 if (!compose_state) {
3206 compose_key = 0x100;
3207 if (cfg.compose_key) {
3208 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED))
3209 compose_key = wParam;
3211 if (wParam == VK_APPS)
3212 compose_key = wParam;
3215 if (wParam == compose_key) {
3216 if (compose_state == 0
3217 && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0) compose_state =
3219 else if (compose_state == 1 && (HIWORD(lParam) & KF_UP))
3223 } else if (compose_state == 1 && wParam != VK_CONTROL)
3226 if (compose_state > 1 && left_alt)
3229 /* Sanitize the number pad if not using a PC NumPad */
3230 if (left_alt || (term->app_keypad_keys && !cfg.no_applic_k
3231 && cfg.funky_type != 2)
3232 || cfg.funky_type == 3 || cfg.nethack_keypad || compose_state) {
3233 if ((HIWORD(lParam) & KF_EXTENDED) == 0) {
3237 nParam = VK_NUMPAD0;
3240 nParam = VK_NUMPAD1;
3243 nParam = VK_NUMPAD2;
3246 nParam = VK_NUMPAD3;
3249 nParam = VK_NUMPAD4;
3252 nParam = VK_NUMPAD5;
3255 nParam = VK_NUMPAD6;
3258 nParam = VK_NUMPAD7;
3261 nParam = VK_NUMPAD8;
3264 nParam = VK_NUMPAD9;
3267 nParam = VK_DECIMAL;
3271 if (keystate[VK_NUMLOCK] & 1)
3278 /* If a key is pressed and AltGr is not active */
3279 if (key_down && (keystate[VK_RMENU] & 0x80) == 0 && !compose_state) {
3280 /* Okay, prepare for most alts then ... */
3284 /* Lets see if it's a pattern we know all about ... */
3285 if (wParam == VK_PRIOR && shift_state == 1) {
3286 SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0);
3289 if (wParam == VK_NEXT && shift_state == 1) {
3290 SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
3293 if (wParam == VK_INSERT && shift_state == 1) {
3294 term_do_paste(term);
3297 if (left_alt && wParam == VK_F4 && cfg.alt_f4) {
3300 if (left_alt && wParam == VK_SPACE && cfg.alt_space) {
3301 SendMessage(hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0);
3304 if (left_alt && wParam == VK_RETURN && cfg.fullscreenonaltenter &&
3305 (cfg.resize_action != RESIZE_DISABLED)) {
3306 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) != KF_REPEAT)
3310 /* Control-Numlock for app-keypad mode switch */
3311 if (wParam == VK_PAUSE && shift_state == 2) {
3312 term->app_keypad_keys ^= 1;
3316 /* Nethack keypad */
3317 if (cfg.nethack_keypad && !left_alt) {
3320 *p++ = shift_state ? 'B' : 'b';
3323 *p++ = shift_state ? 'J' : 'j';
3326 *p++ = shift_state ? 'N' : 'n';
3329 *p++ = shift_state ? 'H' : 'h';
3332 *p++ = shift_state ? '.' : '.';
3335 *p++ = shift_state ? 'L' : 'l';
3338 *p++ = shift_state ? 'Y' : 'y';
3341 *p++ = shift_state ? 'K' : 'k';
3344 *p++ = shift_state ? 'U' : 'u';
3349 /* Application Keypad */
3353 if (cfg.funky_type == 3 ||
3354 (cfg.funky_type <= 1 &&
3355 term->app_keypad_keys && !cfg.no_applic_k)) switch (wParam) {
3369 if (term->app_keypad_keys && !cfg.no_applic_k)
3406 if (cfg.funky_type == 2) {
3411 } else if (shift_state)
3418 if (cfg.funky_type == 2)
3422 if (cfg.funky_type == 2)
3426 if (cfg.funky_type == 2)
3431 if (HIWORD(lParam) & KF_EXTENDED)
3436 if (term->vt52_mode) {
3437 if (xkey >= 'P' && xkey <= 'S')
3438 p += sprintf((char *) p, "\x1B%c", xkey);
3440 p += sprintf((char *) p, "\x1B?%c", xkey);
3442 p += sprintf((char *) p, "\x1BO%c", xkey);
3447 if (wParam == VK_BACK && shift_state == 0) { /* Backspace */
3448 *p++ = (cfg.bksp_is_delete ? 0x7F : 0x08);
3452 if (wParam == VK_BACK && shift_state == 1) { /* Shift Backspace */
3453 /* We do the opposite of what is configured */
3454 *p++ = (cfg.bksp_is_delete ? 0x08 : 0x7F);
3458 if (wParam == VK_TAB && shift_state == 1) { /* Shift tab */
3464 if (wParam == VK_SPACE && shift_state == 2) { /* Ctrl-Space */
3468 if (wParam == VK_SPACE && shift_state == 3) { /* Ctrl-Shift-Space */
3472 if (wParam == VK_CANCEL && shift_state == 2) { /* Ctrl-Break */
3477 if (wParam == VK_PAUSE) { /* Break/Pause */
3482 /* Control-2 to Control-8 are special */
3483 if (shift_state == 2 && wParam >= '2' && wParam <= '8') {
3484 *p++ = "\000\033\034\035\036\037\177"[wParam - '2'];
3487 if (shift_state == 2 && wParam == 0xBD) {
3491 if (shift_state == 2 && wParam == 0xDF) {
3495 if (shift_state == 0 && wParam == VK_RETURN && term->cr_lf_return) {
3502 * Next, all the keys that do tilde codes. (ESC '[' nn '~',
3503 * for integer decimal nn.)
3505 * We also deal with the weird ones here. Linux VCs replace F1
3506 * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
3507 * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
3513 code = (keystate[VK_SHIFT] & 0x80 ? 23 : 11);
3516 code = (keystate[VK_SHIFT] & 0x80 ? 24 : 12);
3519 code = (keystate[VK_SHIFT] & 0x80 ? 25 : 13);
3522 code = (keystate[VK_SHIFT] & 0x80 ? 26 : 14);
3525 code = (keystate[VK_SHIFT] & 0x80 ? 28 : 15);
3528 code = (keystate[VK_SHIFT] & 0x80 ? 29 : 17);
3531 code = (keystate[VK_SHIFT] & 0x80 ? 31 : 18);
3534 code = (keystate[VK_SHIFT] & 0x80 ? 32 : 19);
3537 code = (keystate[VK_SHIFT] & 0x80 ? 33 : 20);
3540 code = (keystate[VK_SHIFT] & 0x80 ? 34 : 21);
3573 if ((shift_state&2) == 0) switch (wParam) {
3593 /* Reorder edit keys to physical order */
3594 if (cfg.funky_type == 3 && code <= 6)
3595 code = "\0\2\1\4\5\3\6"[code];
3597 if (term->vt52_mode && code > 0 && code <= 6) {
3598 p += sprintf((char *) p, "\x1B%c", " HLMEIG"[code]);
3602 if (cfg.funky_type == 5 && /* SCO function keys */
3603 code >= 11 && code <= 34) {
3604 char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
3607 case VK_F1: index = 0; break;
3608 case VK_F2: index = 1; break;
3609 case VK_F3: index = 2; break;
3610 case VK_F4: index = 3; break;
3611 case VK_F5: index = 4; break;
3612 case VK_F6: index = 5; break;
3613 case VK_F7: index = 6; break;
3614 case VK_F8: index = 7; break;
3615 case VK_F9: index = 8; break;
3616 case VK_F10: index = 9; break;
3617 case VK_F11: index = 10; break;
3618 case VK_F12: index = 11; break;
3620 if (keystate[VK_SHIFT] & 0x80) index += 12;
3621 if (keystate[VK_CONTROL] & 0x80) index += 24;
3622 p += sprintf((char *) p, "\x1B[%c", codes[index]);
3625 if (cfg.funky_type == 5 && /* SCO small keypad */
3626 code >= 1 && code <= 6) {
3627 char codes[] = "HL.FIG";
3631 p += sprintf((char *) p, "\x1B[%c", codes[code-1]);
3635 if ((term->vt52_mode || cfg.funky_type == 4) && code >= 11 && code <= 24) {
3641 if (term->vt52_mode)
3642 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11 - offt);
3645 sprintf((char *) p, "\x1BO%c", code + 'P' - 11 - offt);
3648 if (cfg.funky_type == 1 && code >= 11 && code <= 15) {
3649 p += sprintf((char *) p, "\x1B[[%c", code + 'A' - 11);
3652 if (cfg.funky_type == 2 && code >= 11 && code <= 14) {
3653 if (term->vt52_mode)
3654 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11);
3656 p += sprintf((char *) p, "\x1BO%c", code + 'P' - 11);
3659 if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
3660 p += sprintf((char *) p, code == 1 ? "\x1B[H" : "\x1BOw");
3664 p += sprintf((char *) p, "\x1B[%d~", code);
3669 * Now the remaining keys (arrows and Keypad 5. Keypad 5 for
3670 * some reason seems to send VK_CLEAR to Windows...).
3692 if (term->vt52_mode)
3693 p += sprintf((char *) p, "\x1B%c", xkey);
3695 int app_flg = (term->app_cursor_keys && !cfg.no_applic_c);
3698 * RDB: VT100 & VT102 manuals both state the
3699 * app cursor keys only work if the app keypad
3702 * SGT: That may well be true, but xterm
3703 * disagrees and so does at least one
3704 * application, so I've #if'ed this out and the
3705 * behaviour is back to PuTTY's original: app
3706 * cursor and app keypad are independently
3707 * switchable modes. If anyone complains about
3708 * _this_ I'll have to put in a configurable
3711 if (!term->app_keypad_keys)
3714 /* Useful mapping of Ctrl-arrows */
3715 if (shift_state == 2)
3719 p += sprintf((char *) p, "\x1BO%c", xkey);
3721 p += sprintf((char *) p, "\x1B[%c", xkey);
3728 * Finally, deal with Return ourselves. (Win95 seems to
3729 * foul it up when Alt is pressed, for some reason.)
3731 if (wParam == VK_RETURN) { /* Return */
3737 if (left_alt && wParam >= VK_NUMPAD0 && wParam <= VK_NUMPAD9)
3738 alt_sum = alt_sum * 10 + wParam - VK_NUMPAD0;
3743 /* Okay we've done everything interesting; let windows deal with
3744 * the boring stuff */
3748 /* helg: clear CAPS LOCK state if caps lock switches to cyrillic */
3749 if(cfg.xlat_capslockcyr && keystate[VK_CAPITAL] != 0) {
3751 keystate[VK_CAPITAL] = 0;
3754 r = ToAsciiEx(wParam, scan, keystate, keys, 0, kbd_layout);
3755 #ifdef SHOW_TOASCII_RESULT
3756 if (r == 1 && !key_down) {
3758 if (in_utf(term) || dbcs_screenfont)
3759 debug((", (U+%04x)", alt_sum));
3761 debug((", LCH(%d)", alt_sum));
3763 debug((", ACH(%d)", keys[0]));
3768 for (r1 = 0; r1 < r; r1++) {
3769 debug(("%s%d", r1 ? "," : "", keys[r1]));
3778 * Interrupt an ongoing paste. I'm not sure this is
3779 * sensible, but for the moment it's preferable to
3780 * having to faff about buffering things.
3785 for (i = 0; i < r; i++) {
3786 unsigned char ch = (unsigned char) keys[i];
3788 if (compose_state == 2 && (ch & 0x80) == 0 && ch > ' ') {
3793 if (compose_state == 3 && (ch & 0x80) == 0 && ch > ' ') {
3797 if ((nc = check_compose(compose_char, ch)) == -1) {
3798 MessageBeep(MB_ICONHAND);
3802 term_seen_key_event(term);
3803 luni_send(ldisc, &keybuf, 1, 1);
3811 if (in_utf(term) || dbcs_screenfont) {
3813 term_seen_key_event(term);
3814 luni_send(ldisc, &keybuf, 1, 1);
3816 ch = (char) alt_sum;
3818 * We need not bother about stdin
3819 * backlogs here, because in GUI PuTTY
3820 * we can't do anything about it
3821 * anyway; there's no means of asking
3822 * Windows to hold off on KEYDOWN
3823 * messages. We _have_ to buffer
3824 * everything we're sent.
3826 term_seen_key_event(term);
3827 ldisc_send(ldisc, &ch, 1, 1);
3831 term_seen_key_event(term);
3832 lpage_send(ldisc, kbd_codepage, &ch, 1, 1);
3834 if(capsOn && ch < 0x80) {
3837 cbuf[1] = xlat_uskbd2cyrllic(ch);
3838 term_seen_key_event(term);
3839 luni_send(ldisc, cbuf+!left_alt, 1+!!left_alt, 1);
3844 term_seen_key_event(term);
3845 lpage_send(ldisc, kbd_codepage,
3846 cbuf+!left_alt, 1+!!left_alt, 1);
3852 /* This is so the ALT-Numpad and dead keys work correctly. */
3857 /* If we're definitly not building up an ALT-54321 then clear it */
3860 /* If we will be using alt_sum fix the 256s */
3861 else if (keys[0] && (in_utf(term) || dbcs_screenfont))
3866 * ALT alone may or may not want to bring up the System menu.
3867 * If it's not meant to, we return 0 on presses or releases of
3868 * ALT, to show that we've swallowed the keystroke. Otherwise
3869 * we return -1, which means Windows will give the keystroke
3870 * its default handling (i.e. bring up the System menu).
3872 if (wParam == VK_MENU && !cfg.alt_only)
3878 void request_paste(void *frontend)
3881 * In Windows, pasting is synchronous: we can read the
3882 * clipboard with no difficulty, so request_paste() can just go
3885 term_do_paste(term);
3888 void set_title(void *frontend, char *title)
3891 window_name = smalloc(1 + strlen(title));
3892 strcpy(window_name, title);
3893 if (cfg.win_name_always || !IsIconic(hwnd))
3894 SetWindowText(hwnd, title);
3897 void set_icon(void *frontend, char *title)
3900 icon_name = smalloc(1 + strlen(title));
3901 strcpy(icon_name, title);
3902 if (!cfg.win_name_always && IsIconic(hwnd))
3903 SetWindowText(hwnd, title);
3906 void set_sbar(void *frontend, int total, int start, int page)
3910 if (is_full_screen() ? !cfg.scrollbar_in_fullscreen : !cfg.scrollbar)
3913 si.cbSize = sizeof(si);
3914 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
3916 si.nMax = total - 1;
3920 SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
3923 Context get_ctx(void *frontend)
3929 SelectPalette(hdc, pal, FALSE);
3935 void free_ctx(Context ctx)
3937 SelectPalette(ctx, GetStockObject(DEFAULT_PALETTE), FALSE);
3938 ReleaseDC(hwnd, ctx);
3941 static void real_palette_set(int n, int r, int g, int b)
3944 logpal->palPalEntry[n].peRed = r;
3945 logpal->palPalEntry[n].peGreen = g;
3946 logpal->palPalEntry[n].peBlue = b;
3947 logpal->palPalEntry[n].peFlags = PC_NOCOLLAPSE;
3948 colours[n] = PALETTERGB(r, g, b);
3949 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3951 colours[n] = RGB(r, g, b);
3954 void palette_set(void *frontend, int n, int r, int g, int b)
3956 static const int first[21] = {
3957 0, 2, 4, 6, 8, 10, 12, 14,
3958 1, 3, 5, 7, 9, 11, 13, 15,
3961 real_palette_set(first[n], r, g, b);
3963 real_palette_set(first[n] + 1, r, g, b);
3965 HDC hdc = get_ctx(frontend);
3966 UnrealizeObject(pal);
3967 RealizePalette(hdc);
3972 void palette_reset(void *frontend)
3976 for (i = 0; i < NCOLOURS; i++) {
3978 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
3979 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
3980 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
3981 logpal->palPalEntry[i].peFlags = 0;
3982 colours[i] = PALETTERGB(defpal[i].rgbtRed,
3983 defpal[i].rgbtGreen,
3984 defpal[i].rgbtBlue);
3986 colours[i] = RGB(defpal[i].rgbtRed,
3987 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
3992 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3993 hdc = get_ctx(frontend);
3994 RealizePalette(hdc);
3999 void write_aclip(void *frontend, char *data, int len, int must_deselect)
4004 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len + 1);
4007 lock = GlobalLock(clipdata);
4010 memcpy(lock, data, len);
4011 ((unsigned char *) lock)[len] = 0;
4012 GlobalUnlock(clipdata);
4015 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
4017 if (OpenClipboard(hwnd)) {
4019 SetClipboardData(CF_TEXT, clipdata);
4022 GlobalFree(clipdata);
4025 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
4029 * Note: unlike write_aclip() this will not append a nul.
4031 void write_clip(void *frontend, wchar_t * data, int len, int must_deselect)
4033 HGLOBAL clipdata, clipdata2, clipdata3;
4035 void *lock, *lock2, *lock3;
4037 len2 = WideCharToMultiByte(CP_ACP, 0, data, len, 0, 0, NULL, NULL);
4039 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE,
4040 len * sizeof(wchar_t));
4041 clipdata2 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len2);
4043 if (!clipdata || !clipdata2) {
4045 GlobalFree(clipdata);
4047 GlobalFree(clipdata2);
4050 if (!(lock = GlobalLock(clipdata)))
4052 if (!(lock2 = GlobalLock(clipdata2)))
4055 memcpy(lock, data, len * sizeof(wchar_t));
4056 WideCharToMultiByte(CP_ACP, 0, data, len, lock2, len2, NULL, NULL);
4058 if (cfg.rtf_paste) {
4059 wchar_t unitab[256];
4061 unsigned char *tdata = (unsigned char *)lock2;
4062 wchar_t *udata = (wchar_t *)lock;
4063 int rtflen = 0, uindex = 0, tindex = 0;
4065 int multilen, blen, alen, totallen, i;
4066 char before[16], after[4];
4068 get_unitab(CP_ACP, unitab, 0);
4070 rtfsize = 100 + strlen(cfg.font);
4071 rtf = smalloc(rtfsize);
4072 sprintf(rtf, "{\\rtf1\\ansi%d{\\fonttbl\\f0\\fmodern %s;}\\f0",
4073 GetACP(), cfg.font);
4074 rtflen = strlen(rtf);
4077 * We want to construct a piece of RTF that specifies the
4078 * same Unicode text. To do this we will read back in
4079 * parallel from the Unicode data in `udata' and the
4080 * non-Unicode data in `tdata'. For each character in
4081 * `tdata' which becomes the right thing in `udata' when
4082 * looked up in `unitab', we just copy straight over from
4083 * tdata. For each one that doesn't, we must WCToMB it
4084 * individually and produce a \u escape sequence.
4086 * It would probably be more robust to just bite the bullet
4087 * and WCToMB each individual Unicode character one by one,
4088 * then MBToWC each one back to see if it was an accurate
4089 * translation; but that strikes me as a horrifying number
4090 * of Windows API calls so I want to see if this faster way
4091 * will work. If it screws up badly we can always revert to
4092 * the simple and slow way.
4094 while (tindex < len2 && uindex < len &&
4095 tdata[tindex] && udata[uindex]) {
4096 if (tindex + 1 < len2 &&
4097 tdata[tindex] == '\r' &&
4098 tdata[tindex+1] == '\n') {
4102 if (unitab[tdata[tindex]] == udata[uindex]) {
4108 multilen = WideCharToMultiByte(CP_ACP, 0, unitab+uindex, 1,
4109 NULL, 0, NULL, NULL);
4110 if (multilen != 1) {
4111 blen = sprintf(before, "{\\uc%d\\u%d", multilen,
4113 alen = 1; strcpy(after, "}");
4115 blen = sprintf(before, "\\u%d", udata[uindex]);
4116 alen = 0; after[0] = '\0';
4119 assert(tindex + multilen <= len2);
4120 totallen = blen + alen;
4121 for (i = 0; i < multilen; i++) {
4122 if (tdata[tindex+i] == '\\' ||
4123 tdata[tindex+i] == '{' ||
4124 tdata[tindex+i] == '}')
4126 else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A)
4127 totallen += 6; /* \par\r\n */
4128 else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20)
4134 if (rtfsize < rtflen + totallen + 3) {
4135 rtfsize = rtflen + totallen + 512;
4136 rtf = srealloc(rtf, rtfsize);
4139 strcpy(rtf + rtflen, before); rtflen += blen;
4140 for (i = 0; i < multilen; i++) {
4141 if (tdata[tindex+i] == '\\' ||
4142 tdata[tindex+i] == '{' ||
4143 tdata[tindex+i] == '}') {
4144 rtf[rtflen++] = '\\';
4145 rtf[rtflen++] = tdata[tindex+i];
4146 } else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A) {
4147 rtflen += sprintf(rtf+rtflen, "\\par\r\n");
4148 } else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20) {
4149 rtflen += sprintf(rtf+rtflen, "\\'%02x", tdata[tindex+i]);
4151 rtf[rtflen++] = tdata[tindex+i];
4154 strcpy(rtf + rtflen, after); rtflen += alen;
4160 strcpy(rtf + rtflen, "}");
4163 clipdata3 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, rtflen);
4164 if (clipdata3 && (lock3 = GlobalLock(clipdata3)) != NULL) {
4166 GlobalUnlock(clipdata3);
4172 GlobalUnlock(clipdata);
4173 GlobalUnlock(clipdata2);
4176 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
4178 if (OpenClipboard(hwnd)) {
4180 SetClipboardData(CF_UNICODETEXT, clipdata);
4181 SetClipboardData(CF_TEXT, clipdata2);
4183 SetClipboardData(RegisterClipboardFormat(CF_RTF), clipdata3);
4186 GlobalFree(clipdata);
4187 GlobalFree(clipdata2);
4191 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
4194 void get_clip(void *frontend, wchar_t ** p, int *len)
4196 static HGLOBAL clipdata = NULL;
4197 static wchar_t *converted = 0;
4206 GlobalUnlock(clipdata);
4209 } else if (OpenClipboard(NULL)) {
4210 if ((clipdata = GetClipboardData(CF_UNICODETEXT))) {
4212 *p = GlobalLock(clipdata);
4214 for (p2 = *p; *p2; p2++);
4218 } else if ( (clipdata = GetClipboardData(CF_TEXT)) ) {
4222 s = GlobalLock(clipdata);
4223 i = MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, 0, 0);
4224 *p = converted = smalloc(i * sizeof(wchar_t));
4225 MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, converted, i);
4238 * Move `lines' lines from position `from' to position `to' in the
4241 void optimised_move(void *frontend, int to, int from, int lines)
4246 min = (to < from ? to : from);
4247 max = to + from - min;
4249 r.left = offset_width;
4250 r.right = offset_width + term->cols * font_width;
4251 r.top = offset_height + min * font_height;
4252 r.bottom = offset_height + (max + lines) * font_height;
4253 ScrollWindow(hwnd, 0, (to - from) * font_height, &r, &r);
4258 * Print a message box and perform a fatal exit.
4260 void fatalbox(char *fmt, ...)
4266 vsprintf(stuff, fmt, ap);
4268 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
4273 * Print a modal (Really Bad) message box and perform a fatal exit.
4275 void modalfatalbox(char *fmt, ...)
4281 vsprintf(stuff, fmt, ap);
4283 MessageBox(hwnd, stuff, "PuTTY Fatal Error",
4284 MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
4289 * Manage window caption / taskbar flashing, if enabled.
4290 * 0 = stop, 1 = maintain, 2 = start
4292 static void flash_window(int mode)
4294 static long last_flash = 0;
4295 static int flashing = 0;
4296 if ((mode == 0) || (cfg.beep_ind == B_IND_DISABLED)) {
4299 FlashWindow(hwnd, FALSE);
4303 } else if (mode == 2) {
4306 last_flash = GetTickCount();
4308 FlashWindow(hwnd, TRUE);
4311 } else if ((mode == 1) && (cfg.beep_ind == B_IND_FLASH)) {
4314 long now = GetTickCount();
4315 long fdiff = now - last_flash;
4316 if (fdiff < 0 || fdiff > 450) {
4318 FlashWindow(hwnd, TRUE); /* toggle */
4327 void beep(void *frontend, int mode)
4329 if (mode == BELL_DEFAULT) {
4331 * For MessageBeep style bells, we want to be careful of
4332 * timing, because they don't have the nice property of
4333 * PlaySound bells that each one cancels the previous
4334 * active one. So we limit the rate to one per 50ms or so.
4336 static long lastbeep = 0;
4339 beepdiff = GetTickCount() - lastbeep;
4340 if (beepdiff >= 0 && beepdiff < 50)
4344 * The above MessageBeep call takes time, so we record the
4345 * time _after_ it finishes rather than before it starts.
4347 lastbeep = GetTickCount();
4348 } else if (mode == BELL_WAVEFILE) {
4349 if (!PlaySound(cfg.bell_wavefile, NULL, SND_ASYNC | SND_FILENAME)) {
4350 char buf[sizeof(cfg.bell_wavefile) + 80];
4351 sprintf(buf, "Unable to play sound file\n%s\n"
4352 "Using default sound instead", cfg.bell_wavefile);
4353 MessageBox(hwnd, buf, "PuTTY Sound Error",
4354 MB_OK | MB_ICONEXCLAMATION);
4355 cfg.beep = BELL_DEFAULT;
4358 /* Otherwise, either visual bell or disabled; do nothing here */
4359 if (!term->has_focus) {
4360 flash_window(2); /* start */
4365 * Minimise or restore the window in response to a server-side
4368 void set_iconic(void *frontend, int iconic)
4370 if (IsIconic(hwnd)) {
4372 ShowWindow(hwnd, SW_RESTORE);
4375 ShowWindow(hwnd, SW_MINIMIZE);
4380 * Move the window in response to a server-side request.
4382 void move_window(void *frontend, int x, int y)
4384 if (cfg.resize_action == RESIZE_DISABLED ||
4385 cfg.resize_action == RESIZE_FONT ||
4389 SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
4393 * Move the window to the top or bottom of the z-order in response
4394 * to a server-side request.
4396 void set_zorder(void *frontend, int top)
4398 if (cfg.alwaysontop)
4399 return; /* ignore */
4400 SetWindowPos(hwnd, top ? HWND_TOP : HWND_BOTTOM, 0, 0, 0, 0,
4401 SWP_NOMOVE | SWP_NOSIZE);
4405 * Refresh the window in response to a server-side request.
4407 void refresh_window(void *frontend)
4409 InvalidateRect(hwnd, NULL, TRUE);
4413 * Maximise or restore the window in response to a server-side
4416 void set_zoomed(void *frontend, int zoomed)
4418 if (IsZoomed(hwnd)) {
4420 ShowWindow(hwnd, SW_RESTORE);
4423 ShowWindow(hwnd, SW_MAXIMIZE);
4428 * Report whether the window is iconic, for terminal reports.
4430 int is_iconic(void *frontend)
4432 return IsIconic(hwnd);
4436 * Report the window's position, for terminal reports.
4438 void get_window_pos(void *frontend, int *x, int *y)
4441 GetWindowRect(hwnd, &r);
4447 * Report the window's pixel size, for terminal reports.
4449 void get_window_pixels(void *frontend, int *x, int *y)
4452 GetWindowRect(hwnd, &r);
4453 *x = r.right - r.left;
4454 *y = r.bottom - r.top;
4458 * Return the window or icon title.
4460 char *get_window_title(void *frontend, int icon)
4462 return icon ? icon_name : window_name;
4466 * See if we're in full-screen mode.
4468 int is_full_screen()
4470 if (!IsZoomed(hwnd))
4472 if (GetWindowLong(hwnd, GWL_STYLE) & WS_CAPTION)
4477 /* Get the rect/size of a full screen window using the nearest available
4478 * monitor in multimon systems; default to something sensible if only
4479 * one monitor is present. */
4480 static int get_fullscreen_rect(RECT * ss)
4482 #ifdef MONITOR_DEFAULTTONEAREST
4485 mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
4486 mi.cbSize = sizeof(mi);
4487 GetMonitorInfo(mon, &mi);
4489 /* structure copy */
4493 /* could also use code like this:
4494 ss->left = ss->top = 0;
4495 ss->right = GetSystemMetrics(SM_CXSCREEN);
4496 ss->bottom = GetSystemMetrics(SM_CYSCREEN);
4498 return GetClientRect(GetDesktopWindow(), ss);
4504 * Go full-screen. This should only be called when we are already
4507 void make_full_screen()
4512 assert(IsZoomed(hwnd));
4514 if (is_full_screen())
4517 /* Remove the window furniture. */
4518 style = GetWindowLong(hwnd, GWL_STYLE);
4519 style &= ~(WS_CAPTION | WS_BORDER | WS_THICKFRAME);
4520 if (cfg.scrollbar_in_fullscreen)
4521 style |= WS_VSCROLL;
4523 style &= ~WS_VSCROLL;
4524 SetWindowLong(hwnd, GWL_STYLE, style);
4526 /* Resize ourselves to exactly cover the nearest monitor. */
4527 get_fullscreen_rect(&ss);
4528 SetWindowPos(hwnd, HWND_TOP, ss.left, ss.top,
4533 /* Tick the menu item in the System menu. */
4534 CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN,
4539 * Clear the full-screen attributes.
4541 void clear_full_screen()
4543 DWORD oldstyle, style;
4545 /* Reinstate the window furniture. */
4546 style = oldstyle = GetWindowLong(hwnd, GWL_STYLE);
4547 style |= WS_CAPTION | WS_BORDER;
4548 if (cfg.resize_action == RESIZE_DISABLED)
4549 style &= ~WS_THICKFRAME;
4551 style |= WS_THICKFRAME;
4553 style |= WS_VSCROLL;
4555 style &= ~WS_VSCROLL;
4556 if (style != oldstyle) {
4557 SetWindowLong(hwnd, GWL_STYLE, style);
4558 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
4559 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
4563 /* Untick the menu item in the System menu. */
4564 CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN,
4569 * Toggle full-screen mode.
4571 void flip_full_screen()
4573 if (is_full_screen()) {
4574 ShowWindow(hwnd, SW_RESTORE);
4575 } else if (IsZoomed(hwnd)) {
4578 SendMessage(hwnd, WM_FULLSCR_ON_MAX, 0, 0);
4579 ShowWindow(hwnd, SW_MAXIMIZE);
4583 void frontend_keypress(void *handle)
4586 * Keypress termination in non-Close-On-Exit mode is not
4587 * currently supported in PuTTY proper, because the window
4588 * always has a perfectly good Close button anyway. So we do