15 #define COMPILE_MULTIMON_STUBS
25 #define PUTTY_DO_GLOBALS /* actually _define_ globals */
31 #define IDM_SHOWLOG 0x0010
32 #define IDM_NEWSESS 0x0020
33 #define IDM_DUPSESS 0x0030
34 #define IDM_RECONF 0x0040
35 #define IDM_CLRSB 0x0050
36 #define IDM_RESET 0x0060
37 #define IDM_TEL_AYT 0x0070
38 #define IDM_TEL_BRK 0x0080
39 #define IDM_TEL_SYNCH 0x0090
40 #define IDM_TEL_EC 0x00a0
41 #define IDM_TEL_EL 0x00b0
42 #define IDM_TEL_GA 0x00c0
43 #define IDM_TEL_NOP 0x00d0
44 #define IDM_TEL_ABORT 0x00e0
45 #define IDM_TEL_AO 0x00f0
46 #define IDM_TEL_IP 0x0100
47 #define IDM_TEL_SUSP 0x0110
48 #define IDM_TEL_EOR 0x0120
49 #define IDM_TEL_EOF 0x0130
50 #define IDM_HELP 0x0140
51 #define IDM_ABOUT 0x0150
52 #define IDM_SAVEDSESS 0x0160
53 #define IDM_COPYALL 0x0170
54 #define IDM_FULLSCREEN 0x0180
56 #define IDM_SESSLGP 0x0250 /* log type printable */
57 #define IDM_SESSLGA 0x0260 /* log type all chars */
58 #define IDM_SESSLGE 0x0270 /* log end */
59 #define IDM_SAVED_MIN 0x1000
60 #define IDM_SAVED_MAX 0x2000
62 #define WM_IGNORE_CLIP (WM_XUSER + 2)
63 #define WM_FULLSCR_ON_MAX (WM_XUSER + 3)
65 /* Needed for Chinese support and apparently not always defined. */
67 #define VK_PROCESSKEY 0xE5
70 /* Mouse wheel support. */
72 #define WM_MOUSEWHEEL 0x020A /* not defined in earlier SDKs */
75 #define WHEEL_DELTA 120
78 static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
79 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
80 unsigned char *output);
81 static void cfgtopalette(void);
82 static void init_palette(void);
83 static void init_fonts(int, int);
84 static void another_font(int);
85 static void deinit_fonts(void);
86 static void set_input_locale(HKL);
87 static int do_mouse_wheel_msg(UINT message, WPARAM wParam, LPARAM lParam);
89 static int is_full_screen(void);
90 static void make_full_screen(void);
91 static void clear_full_screen(void);
92 static void flip_full_screen(void);
94 /* Window layout information */
95 static void reset_window(int);
96 static int extra_width, extra_height;
97 static int font_width, font_height, font_dualwidth;
98 static int offset_width, offset_height;
99 static int was_zoomed = 0;
100 static int prev_rows, prev_cols;
102 static int pending_netevent = 0;
103 static WPARAM pend_netevent_wParam = 0;
104 static LPARAM pend_netevent_lParam = 0;
105 static void enact_pending_netevent(void);
106 static void flash_window(int mode);
108 static time_t last_movement = 0;
110 #define FONT_NORMAL 0
112 #define FONT_UNDERLINE 2
113 #define FONT_BOLDUND 3
114 #define FONT_WIDE 0x04
115 #define FONT_HIGH 0x08
116 #define FONT_NARROW 0x10
118 #define FONT_OEM 0x20
119 #define FONT_OEMBOLD 0x21
120 #define FONT_OEMUND 0x22
121 #define FONT_OEMBOLDUND 0x23
123 #define FONT_MAXNO 0x2F
125 static HFONT fonts[FONT_MAXNO];
126 static LOGFONT lfont;
127 static int fontflag[FONT_MAXNO];
129 BOLD_COLOURS, BOLD_SHADOW, BOLD_FONT
137 static COLORREF colours[NCOLOURS];
139 static LPLOGPALETTE logpal;
140 static RGBTRIPLE defpal[NCOLOURS];
144 static HBITMAP caretbm;
146 static int dbltime, lasttime, lastact;
147 static Mouse_Button lastbtn;
149 /* this allows xterm-style mouse handling. */
150 static int send_raw_mouse = 0;
151 static int wheel_accumulator = 0;
153 static char *window_name, *icon_name;
155 static int compose_state = 0;
157 static OSVERSIONINFO osVersion;
159 static UINT wm_mousewheel = WM_MOUSEWHEEL;
161 /* Dummy routine, only required in plink. */
162 void ldisc_update(int echo, int edit)
166 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
168 static char appname[] = "PuTTY";
173 int guess_width, guess_height;
176 flags = FLAG_VERBOSE | FLAG_INTERACTIVE;
178 winsock_ver = MAKEWORD(1, 1);
179 if (WSAStartup(winsock_ver, &wsadata)) {
180 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
181 MB_OK | MB_ICONEXCLAMATION);
184 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
185 MessageBox(NULL, "WinSock version is incompatible with 1.1",
186 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
190 /* WISHLIST: maybe allow config tweaking even if winsock not present? */
193 InitCommonControls();
195 /* Ensure a Maximize setting in Explorer doesn't maximise the
200 ZeroMemory(&osVersion, sizeof(osVersion));
201 osVersion.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
202 if (!GetVersionEx ( (OSVERSIONINFO *) &osVersion)) {
203 MessageBox(NULL, "Windows refuses to report a version",
204 "PuTTY Fatal Error", MB_OK | MB_ICONEXCLAMATION);
210 * If we're running a version of Windows that doesn't support
211 * WM_MOUSEWHEEL, find out what message number we should be
214 if (osVersion.dwMajorVersion < 4 ||
215 (osVersion.dwMajorVersion == 4 &&
216 osVersion.dwPlatformId != VER_PLATFORM_WIN32_NT))
217 wm_mousewheel = RegisterWindowMessage("MSWHEEL_ROLLMSG");
220 * See if we can find our Help file.
223 char b[2048], *p, *q, *r;
225 GetModuleFileName(NULL, b, sizeof(b) - 1);
227 p = strrchr(b, '\\');
228 if (p && p >= r) r = p+1;
230 if (q && q >= r) r = q+1;
231 strcpy(r, "putty.hlp");
232 if ( (fp = fopen(b, "r")) != NULL) {
233 help_path = dupstr(b);
237 strcpy(r, "putty.cnt");
238 if ( (fp = fopen(b, "r")) != NULL) {
239 help_has_contents = TRUE;
242 help_has_contents = FALSE;
246 * Process the command line.
251 default_protocol = DEFAULT_PROTOCOL;
252 default_port = DEFAULT_PORT;
253 cfg.logtype = LGTYP_NONE;
255 do_defaults(NULL, &cfg);
258 while (*p && isspace(*p))
262 * Process command line options first. Yes, this can be
263 * done better, and it will be as soon as I have the
267 char *q = p + strcspn(p, " \t");
270 tolower(p[0]) == 's' &&
271 tolower(p[1]) == 's' && tolower(p[2]) == 'h') {
272 default_protocol = cfg.protocol = PROT_SSH;
273 default_port = cfg.port = 22;
274 } else if (q == p + 7 &&
275 tolower(p[0]) == 'c' &&
276 tolower(p[1]) == 'l' &&
277 tolower(p[2]) == 'e' &&
278 tolower(p[3]) == 'a' &&
279 tolower(p[4]) == 'n' &&
280 tolower(p[5]) == 'u' && tolower(p[6]) == 'p') {
282 * `putty -cleanup'. Remove all registry entries
283 * associated with PuTTY, and also find and delete
284 * the random seed file.
287 "This procedure will remove ALL Registry\n"
288 "entries associated with PuTTY, and will\n"
289 "also remove the PuTTY random seed file.\n"
291 "THIS PROCESS WILL DESTROY YOUR SAVED\n"
292 "SESSIONS. Are you really sure you want\n"
295 MB_YESNO | MB_ICONWARNING) == IDYES) {
300 p = q + strspn(q, " \t");
304 * An initial @ means to activate a saved session.
308 while (i > 1 && isspace(p[i - 1]))
311 do_defaults(p + 1, &cfg);
312 if (!*cfg.host && !do_config()) {
316 } else if (*p == '&') {
318 * An initial & means we've been given a command line
319 * containing the hex value of a HANDLE for a file
320 * mapping object, which we must then extract as a
325 if (sscanf(p + 1, "%p", &filemap) == 1 &&
326 (cp = MapViewOfFile(filemap, FILE_MAP_READ,
327 0, 0, sizeof(Config))) != NULL) {
330 CloseHandle(filemap);
331 } else if (!do_config()) {
338 * If the hostname starts with "telnet:", set the
339 * protocol to Telnet and process the string as a
342 if (!strncmp(q, "telnet:", 7)) {
346 if (q[0] == '/' && q[1] == '/')
348 cfg.protocol = PROT_TELNET;
350 while (*p && *p != ':' && *p != '/')
359 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
360 cfg.host[sizeof(cfg.host) - 1] = '\0';
362 while (*p && !isspace(*p))
366 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
367 cfg.host[sizeof(cfg.host) - 1] = '\0';
368 while (*p && isspace(*p))
383 * Trim leading whitespace off the hostname if it's there.
386 int space = strspn(cfg.host, " \t");
387 memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
390 /* See if host is of the form user@host */
391 if (cfg.host[0] != '\0') {
392 char *atsign = strchr(cfg.host, '@');
393 /* Make sure we're not overflowing the user field */
395 if (atsign - cfg.host < sizeof cfg.username) {
396 strncpy(cfg.username, cfg.host, atsign - cfg.host);
397 cfg.username[atsign - cfg.host] = '\0';
399 memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
404 * Trim a colon suffix off the hostname if it's there.
406 cfg.host[strcspn(cfg.host, ":")] = '\0';
410 * Select protocol. This is farmed out into a table in a
411 * separate file to enable an ssh-free variant.
416 for (i = 0; backends[i].backend != NULL; i++)
417 if (backends[i].protocol == cfg.protocol) {
418 back = backends[i].backend;
422 MessageBox(NULL, "Unsupported protocol number found",
423 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
429 /* Check for invalid Port number (i.e. zero) */
431 MessageBox(NULL, "Invalid Port Number",
432 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
439 wndclass.lpfnWndProc = WndProc;
440 wndclass.cbClsExtra = 0;
441 wndclass.cbWndExtra = 0;
442 wndclass.hInstance = inst;
443 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
444 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
445 wndclass.hbrBackground = NULL;
446 wndclass.lpszMenuName = NULL;
447 wndclass.lpszClassName = appname;
449 RegisterClass(&wndclass);
454 savelines = cfg.savelines;
460 * Guess some defaults for the window size. This all gets
461 * updated later, so we don't really care too much. However, we
462 * do want the font width/height guesses to correspond to a
463 * large font rather than a small one...
470 term_size(cfg.height, cfg.width, cfg.savelines);
471 guess_width = extra_width + font_width * cols;
472 guess_height = extra_height + font_height * rows;
475 HWND w = GetDesktopWindow();
476 GetWindowRect(w, &r);
477 if (guess_width > r.right - r.left)
478 guess_width = r.right - r.left;
479 if (guess_height > r.bottom - r.top)
480 guess_height = r.bottom - r.top;
484 int winmode = WS_OVERLAPPEDWINDOW | WS_VSCROLL;
487 winmode &= ~(WS_VSCROLL);
488 if (cfg.resize_action == RESIZE_DISABLED)
489 winmode &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
491 exwinmode |= WS_EX_TOPMOST;
493 exwinmode |= WS_EX_CLIENTEDGE;
494 hwnd = CreateWindowEx(exwinmode, appname, appname,
495 winmode, CW_USEDEFAULT, CW_USEDEFAULT,
496 guess_width, guess_height,
497 NULL, NULL, inst, NULL);
501 * Initialise the fonts, simultaneously correcting the guesses
502 * for font_{width,height}.
507 * Correct the guesses for extra_{width,height}.
511 GetWindowRect(hwnd, &wr);
512 GetClientRect(hwnd, &cr);
513 offset_width = offset_height = cfg.window_border;
514 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
515 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
519 * Resize the window, now we know what size we _really_ want it
522 guess_width = extra_width + font_width * cols;
523 guess_height = extra_height + font_height * rows;
524 SetWindowPos(hwnd, NULL, 0, 0, guess_width, guess_height,
525 SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
528 * Set up a caret bitmap, with no content.
532 int size = (font_width + 15) / 16 * 2 * font_height;
533 bits = smalloc(size);
534 memset(bits, 0, size);
535 caretbm = CreateBitmap(font_width, font_height, 1, 1, bits);
538 CreateCaret(hwnd, caretbm, font_width, font_height);
541 * Initialise the scroll bar.
546 si.cbSize = sizeof(si);
547 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
552 SetScrollInfo(hwnd, SB_VERT, &si, FALSE);
556 * Start up the telnet connection.
560 char msg[1024], *title;
563 error = back->init(cfg.host, cfg.port, &realhost, cfg.tcp_nodelay);
565 sprintf(msg, "Unable to open connection to\n"
566 "%.800s\n" "%s", cfg.host, error);
567 MessageBox(NULL, msg, "PuTTY Error", MB_ICONERROR | MB_OK);
570 window_name = icon_name = NULL;
572 title = cfg.wintitle;
574 sprintf(msg, "%s - PuTTY", realhost);
582 session_closed = FALSE;
585 * Prepare the mouse handler.
587 lastact = MA_NOTHING;
588 lastbtn = MBT_NOTHING;
589 dbltime = GetDoubleClickTime();
592 * Set up the session-control options on the system menu.
595 HMENU m = GetSystemMenu(hwnd, FALSE);
599 AppendMenu(m, MF_SEPARATOR, 0, 0);
600 if (cfg.protocol == PROT_TELNET) {
602 AppendMenu(p, MF_ENABLED, IDM_TEL_AYT, "Are You There");
603 AppendMenu(p, MF_ENABLED, IDM_TEL_BRK, "Break");
604 AppendMenu(p, MF_ENABLED, IDM_TEL_SYNCH, "Synch");
605 AppendMenu(p, MF_SEPARATOR, 0, 0);
606 AppendMenu(p, MF_ENABLED, IDM_TEL_EC, "Erase Character");
607 AppendMenu(p, MF_ENABLED, IDM_TEL_EL, "Erase Line");
608 AppendMenu(p, MF_ENABLED, IDM_TEL_GA, "Go Ahead");
609 AppendMenu(p, MF_ENABLED, IDM_TEL_NOP, "No Operation");
610 AppendMenu(p, MF_SEPARATOR, 0, 0);
611 AppendMenu(p, MF_ENABLED, IDM_TEL_ABORT, "Abort Process");
612 AppendMenu(p, MF_ENABLED, IDM_TEL_AO, "Abort Output");
613 AppendMenu(p, MF_ENABLED, IDM_TEL_IP, "Interrupt Process");
614 AppendMenu(p, MF_ENABLED, IDM_TEL_SUSP, "Suspend Process");
615 AppendMenu(p, MF_SEPARATOR, 0, 0);
616 AppendMenu(p, MF_ENABLED, IDM_TEL_EOR, "End Of Record");
617 AppendMenu(p, MF_ENABLED, IDM_TEL_EOF, "End Of File");
618 AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) p,
620 AppendMenu(m, MF_SEPARATOR, 0, 0);
622 AppendMenu(m, MF_ENABLED, IDM_SHOWLOG, "&Event Log");
623 AppendMenu(m, MF_SEPARATOR, 0, 0);
624 AppendMenu(m, MF_ENABLED, IDM_NEWSESS, "Ne&w Session...");
625 AppendMenu(m, MF_ENABLED, IDM_DUPSESS, "&Duplicate Session");
628 for (i = 1; i < ((nsessions < 256) ? nsessions : 256); i++)
629 AppendMenu(s, MF_ENABLED, IDM_SAVED_MIN + (16 * i),
631 AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) s, "Sa&ved Sessions");
632 AppendMenu(m, MF_ENABLED, IDM_RECONF, "Chan&ge Settings...");
633 AppendMenu(m, MF_SEPARATOR, 0, 0);
634 AppendMenu(m, MF_ENABLED, IDM_COPYALL, "C&opy All to Clipboard");
635 AppendMenu(m, MF_ENABLED, IDM_CLRSB, "C&lear Scrollback");
636 AppendMenu(m, MF_ENABLED, IDM_RESET, "Rese&t Terminal");
637 AppendMenu(m, MF_SEPARATOR, 0, 0);
638 AppendMenu(m, (cfg.resize_action == RESIZE_DISABLED) ?
639 MF_GRAYED : MF_ENABLED, IDM_FULLSCREEN, "&Full Screen");
640 AppendMenu(m, MF_SEPARATOR, 0, 0);
642 AppendMenu(m, MF_ENABLED, IDM_HELP, "&Help");
643 AppendMenu(m, MF_ENABLED, IDM_ABOUT, "&About PuTTY");
647 * Set up the initial input locale.
649 set_input_locale(GetKeyboardLayout(0));
652 * Open the initial log file if there is one.
657 * Finally show the window!
659 ShowWindow(hwnd, show);
660 SetForegroundWindow(hwnd);
663 * Set the palette up.
669 has_focus = (GetForegroundWindow() == hwnd);
672 if (GetMessage(&msg, NULL, 0, 0) == 1) {
673 int timer_id = 0, long_timer = 0;
675 while (msg.message != WM_QUIT) {
676 /* Sometimes DispatchMessage calls routines that use their own
677 * GetMessage loop, setup this timer so we get some control back.
679 * Also call term_update() from the timer so that if the host
680 * is sending data flat out we still do redraws.
682 if (timer_id && long_timer) {
683 KillTimer(hwnd, timer_id);
684 long_timer = timer_id = 0;
687 timer_id = SetTimer(hwnd, 1, 20, NULL);
688 if (!(IsWindow(logbox) && IsDialogMessage(logbox, &msg)))
689 DispatchMessage(&msg);
691 /* Make sure we blink everything that needs it. */
694 /* Send the paste buffer if there's anything to send */
697 /* If there's nothing new in the queue then we can do everything
698 * we've delayed, reading the socket, writing, and repainting
701 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
704 if (pending_netevent) {
705 enact_pending_netevent();
707 /* Force the cursor blink on */
710 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
714 /* Okay there is now nothing to do so we make sure the screen is
715 * completely up to date then tell windows to call us in a little
719 KillTimer(hwnd, timer_id);
727 flash_window(1); /* maintain */
729 /* The messages seem unreliable; especially if we're being tricky */
730 has_focus = (GetForegroundWindow() == hwnd);
733 /* Hmm, term_update didn't want to do an update too soon ... */
734 timer_id = SetTimer(hwnd, 1, 50, NULL);
736 timer_id = SetTimer(hwnd, 1, 500, NULL);
738 timer_id = SetTimer(hwnd, 1, 100, NULL);
741 /* There's no point rescanning everything in the message queue
742 * so we do an apparently unnecessary wait here
745 if (GetMessage(&msg, NULL, 0, 0) != 1)
759 if (cfg.protocol == PROT_SSH) {
770 * Set up, or shut down, an AsyncSelect. Called from winnet.c.
772 char *do_select(SOCKET skt, int startup)
777 events = (FD_CONNECT | FD_READ | FD_WRITE |
778 FD_OOB | FD_CLOSE | FD_ACCEPT);
783 return "do_select(): internal error (hwnd==NULL)";
784 if (WSAAsyncSelect(skt, hwnd, msg, events) == SOCKET_ERROR) {
785 switch (WSAGetLastError()) {
787 return "Network is down";
789 return "WSAAsyncSelect(): unknown error";
796 * set or clear the "raw mouse message" mode
798 void set_raw_mouse_mode(int activate)
800 send_raw_mouse = activate;
801 SetCursor(LoadCursor(NULL, activate ? IDC_ARROW : IDC_IBEAM));
805 * Print a message box and close the connection.
807 void connection_fatal(char *fmt, ...)
813 vsprintf(stuff, fmt, ap);
815 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
816 if (cfg.close_on_exit == COE_ALWAYS)
819 session_closed = TRUE;
820 SetWindowText(hwnd, "PuTTY (inactive)");
825 * Actually do the job requested by a WM_NETEVENT
827 static void enact_pending_netevent(void)
829 static int reentering = 0;
830 extern int select_result(WPARAM, LPARAM);
834 return; /* don't unpend the pending */
836 pending_netevent = FALSE;
839 ret = select_result(pend_netevent_wParam, pend_netevent_lParam);
842 if (ret == 0 && !session_closed) {
843 /* Abnormal exits will already have set session_closed and taken
844 * appropriate action. */
845 if (cfg.close_on_exit == COE_ALWAYS ||
846 cfg.close_on_exit == COE_NORMAL) PostQuitMessage(0);
848 session_closed = TRUE;
849 SetWindowText(hwnd, "PuTTY (inactive)");
850 MessageBox(hwnd, "Connection closed by remote host",
851 "PuTTY", MB_OK | MB_ICONINFORMATION);
857 * Copy the colour palette from the configuration data into defpal.
858 * This is non-trivial because the colour indices are different.
860 static void cfgtopalette(void)
863 static const int ww[] = {
864 6, 7, 8, 9, 10, 11, 12, 13,
865 14, 15, 16, 17, 18, 19, 20, 21,
866 0, 1, 2, 3, 4, 4, 5, 5
869 for (i = 0; i < 24; i++) {
871 defpal[i].rgbtRed = cfg.colours[w][0];
872 defpal[i].rgbtGreen = cfg.colours[w][1];
873 defpal[i].rgbtBlue = cfg.colours[w][2];
878 * Set up the colour palette.
880 static void init_palette(void)
883 HDC hdc = GetDC(hwnd);
885 if (cfg.try_palette && GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) {
886 logpal = smalloc(sizeof(*logpal)
887 - sizeof(logpal->palPalEntry)
888 + NCOLOURS * sizeof(PALETTEENTRY));
889 logpal->palVersion = 0x300;
890 logpal->palNumEntries = NCOLOURS;
891 for (i = 0; i < NCOLOURS; i++) {
892 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
893 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
894 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
895 logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
897 pal = CreatePalette(logpal);
899 SelectPalette(hdc, pal, FALSE);
901 SelectPalette(hdc, GetStockObject(DEFAULT_PALETTE), FALSE);
904 ReleaseDC(hwnd, hdc);
907 for (i = 0; i < NCOLOURS; i++)
908 colours[i] = PALETTERGB(defpal[i].rgbtRed,
912 for (i = 0; i < NCOLOURS; i++)
913 colours[i] = RGB(defpal[i].rgbtRed,
914 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
918 * Initialise all the fonts we will need initially. There may be as many as
919 * three or as few as one. The other (poentially) twentyone fonts are done
920 * if/when they are needed.
924 * - check the font width and height, correcting our guesses if
927 * - verify that the bold font is the same width as the ordinary
928 * one, and engage shadow bolding if not.
930 * - verify that the underlined font is the same width as the
931 * ordinary one (manual underlining by means of line drawing can
932 * be done in a pinch).
934 static void init_fonts(int pick_width, int pick_height)
941 int fw_dontcare, fw_bold;
943 for (i = 0; i < FONT_MAXNO; i++)
946 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
949 if (cfg.fontisbold) {
950 fw_dontcare = FW_BOLD;
953 fw_dontcare = FW_DONTCARE;
960 font_height = pick_height;
962 font_height = cfg.fontheight;
963 if (font_height > 0) {
965 -MulDiv(font_height, GetDeviceCaps(hdc, LOGPIXELSY), 72);
968 font_width = pick_width;
971 fonts[i] = CreateFont (font_height, font_width, 0, 0, w, FALSE, u, FALSE, \
972 c, OUT_DEFAULT_PRECIS, \
973 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, \
974 FIXED_PITCH | FF_DONTCARE, cfg.font)
976 f(FONT_NORMAL, cfg.fontcharset, fw_dontcare, FALSE);
978 lfont.lfHeight = font_height;
979 lfont.lfWidth = font_width;
980 lfont.lfEscapement = 0;
981 lfont.lfOrientation = 0;
982 lfont.lfWeight = fw_dontcare;
983 lfont.lfItalic = FALSE;
984 lfont.lfUnderline = FALSE;
985 lfont.lfStrikeOut = FALSE;
986 lfont.lfCharSet = cfg.fontcharset;
987 lfont.lfOutPrecision = OUT_DEFAULT_PRECIS;
988 lfont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
989 lfont.lfQuality = DEFAULT_QUALITY;
990 lfont.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE;
991 strncpy(lfont.lfFaceName, cfg.font, LF_FACESIZE);
993 SelectObject(hdc, fonts[FONT_NORMAL]);
994 GetTextMetrics(hdc, &tm);
996 if (pick_width == 0 || pick_height == 0) {
997 font_height = tm.tmHeight;
998 font_width = tm.tmAveCharWidth;
1000 font_dualwidth = (tm.tmAveCharWidth != tm.tmMaxCharWidth);
1002 #ifdef RDB_DEBUG_PATCH
1003 debug(23, "Primary font H=%d, AW=%d, MW=%d",
1004 tm.tmHeight, tm.tmAveCharWidth, tm.tmMaxCharWidth);
1009 DWORD cset = tm.tmCharSet;
1010 memset(&info, 0xFF, sizeof(info));
1012 /* !!! Yes the next line is right */
1013 if (cset == OEM_CHARSET)
1014 font_codepage = GetOEMCP();
1016 if (TranslateCharsetInfo
1017 ((DWORD *) cset, &info, TCI_SRCCHARSET)) font_codepage =
1022 GetCPInfo(font_codepage, &cpinfo);
1023 dbcs_screenfont = (cpinfo.MaxCharSize > 1);
1026 f(FONT_UNDERLINE, cfg.fontcharset, fw_dontcare, TRUE);
1029 * Some fonts, e.g. 9-pt Courier, draw their underlines
1030 * outside their character cell. We successfully prevent
1031 * screen corruption by clipping the text output, but then
1032 * we lose the underline completely. Here we try to work
1033 * out whether this is such a font, and if it is, we set a
1034 * flag that causes underlines to be drawn by hand.
1036 * Having tried other more sophisticated approaches (such
1037 * as examining the TEXTMETRIC structure or requesting the
1038 * height of a string), I think we'll do this the brute
1039 * force way: we create a small bitmap, draw an underlined
1040 * space on it, and test to see whether any pixels are
1041 * foreground-coloured. (Since we expect the underline to
1042 * go all the way across the character cell, we only search
1043 * down a single column of the bitmap, half way across.)
1047 HBITMAP und_bm, und_oldbm;
1051 und_dc = CreateCompatibleDC(hdc);
1052 und_bm = CreateCompatibleBitmap(hdc, font_width, font_height);
1053 und_oldbm = SelectObject(und_dc, und_bm);
1054 SelectObject(und_dc, fonts[FONT_UNDERLINE]);
1055 SetTextAlign(und_dc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
1056 SetTextColor(und_dc, RGB(255, 255, 255));
1057 SetBkColor(und_dc, RGB(0, 0, 0));
1058 SetBkMode(und_dc, OPAQUE);
1059 ExtTextOut(und_dc, 0, 0, ETO_OPAQUE, NULL, " ", 1, NULL);
1061 for (i = 0; i < font_height; i++) {
1062 c = GetPixel(und_dc, font_width / 2, i);
1063 if (c != RGB(0, 0, 0))
1066 SelectObject(und_dc, und_oldbm);
1067 DeleteObject(und_bm);
1070 und_mode = UND_LINE;
1071 DeleteObject(fonts[FONT_UNDERLINE]);
1072 fonts[FONT_UNDERLINE] = 0;
1076 if (bold_mode == BOLD_FONT) {
1077 f(FONT_BOLD, cfg.fontcharset, fw_bold, FALSE);
1081 descent = tm.tmAscent + 1;
1082 if (descent >= font_height)
1083 descent = font_height - 1;
1085 for (i = 0; i < 3; i++) {
1087 if (SelectObject(hdc, fonts[i]) && GetTextMetrics(hdc, &tm))
1088 fontsize[i] = tm.tmAveCharWidth + 256 * tm.tmHeight;
1095 ReleaseDC(hwnd, hdc);
1097 if (fontsize[FONT_UNDERLINE] != fontsize[FONT_NORMAL]) {
1098 und_mode = UND_LINE;
1099 DeleteObject(fonts[FONT_UNDERLINE]);
1100 fonts[FONT_UNDERLINE] = 0;
1103 if (bold_mode == BOLD_FONT &&
1104 fontsize[FONT_BOLD] != fontsize[FONT_NORMAL]) {
1105 bold_mode = BOLD_SHADOW;
1106 DeleteObject(fonts[FONT_BOLD]);
1107 fonts[FONT_BOLD] = 0;
1109 fontflag[0] = fontflag[1] = fontflag[2] = 1;
1114 static void another_font(int fontno)
1117 int fw_dontcare, fw_bold;
1121 if (fontno < 0 || fontno >= FONT_MAXNO || fontflag[fontno])
1124 basefont = (fontno & ~(FONT_BOLDUND));
1125 if (basefont != fontno && !fontflag[basefont])
1126 another_font(basefont);
1128 if (cfg.fontisbold) {
1129 fw_dontcare = FW_BOLD;
1132 fw_dontcare = FW_DONTCARE;
1136 c = cfg.fontcharset;
1142 if (fontno & FONT_WIDE)
1144 if (fontno & FONT_NARROW)
1146 if (fontno & FONT_OEM)
1148 if (fontno & FONT_BOLD)
1150 if (fontno & FONT_UNDERLINE)
1154 CreateFont(font_height * (1 + !!(fontno & FONT_HIGH)), x, 0, 0, w,
1155 FALSE, u, FALSE, c, OUT_DEFAULT_PRECIS,
1156 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
1157 FIXED_PITCH | FF_DONTCARE, s);
1159 fontflag[fontno] = 1;
1162 static void deinit_fonts(void)
1165 for (i = 0; i < FONT_MAXNO; i++) {
1167 DeleteObject(fonts[i]);
1173 void request_resize(int w, int h)
1177 /* If the window is maximized supress resizing attempts */
1178 if (IsZoomed(hwnd)) {
1179 if (cfg.resize_action == RESIZE_TERM)
1183 if (cfg.resize_action == RESIZE_DISABLED) return;
1184 if (h == rows && w == cols) return;
1186 /* Sanity checks ... */
1188 static int first_time = 1;
1191 switch (first_time) {
1193 /* Get the size of the screen */
1194 if (GetClientRect(GetDesktopWindow(), &ss))
1195 /* first_time = 0 */ ;
1201 /* Make sure the values are sane */
1202 width = (ss.right - ss.left - extra_width) / 4;
1203 height = (ss.bottom - ss.top - extra_height) / 6;
1205 if (w > width || h > height)
1214 term_size(h, w, cfg.savelines);
1216 if (cfg.resize_action != RESIZE_FONT && !IsZoomed(hwnd)) {
1217 width = extra_width + font_width * w;
1218 height = extra_height + font_height * h;
1220 SetWindowPos(hwnd, NULL, 0, 0, width, height,
1221 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1222 SWP_NOMOVE | SWP_NOZORDER);
1226 InvalidateRect(hwnd, NULL, TRUE);
1229 static void reset_window(int reinit) {
1231 * This function decides how to resize or redraw when the
1232 * user changes something.
1234 * This function doesn't like to change the terminal size but if the
1235 * font size is locked that may be it's only soluion.
1237 int win_width, win_height;
1240 #ifdef RDB_DEBUG_PATCH
1241 debug((27, "reset_window()"));
1244 /* Current window sizes ... */
1245 GetWindowRect(hwnd, &wr);
1246 GetClientRect(hwnd, &cr);
1248 win_width = cr.right - cr.left;
1249 win_height = cr.bottom - cr.top;
1251 if (cfg.resize_action == RESIZE_DISABLED) reinit = 2;
1253 /* Are we being forced to reload the fonts ? */
1255 #ifdef RDB_DEBUG_PATCH
1256 debug((27, "reset_window() -- Forced deinit"));
1262 /* Oh, looks like we're minimised */
1263 if (win_width == 0 || win_height == 0)
1266 /* Is the window out of position ? */
1268 (offset_width != (win_width-font_width*cols)/2 ||
1269 offset_height != (win_height-font_height*rows)/2) ){
1270 offset_width = (win_width-font_width*cols)/2;
1271 offset_height = (win_height-font_height*rows)/2;
1272 InvalidateRect(hwnd, NULL, TRUE);
1273 #ifdef RDB_DEBUG_PATCH
1274 debug((27, "reset_window() -> Reposition terminal"));
1278 if (IsZoomed(hwnd)) {
1279 /* We're fullscreen, this means we must not change the size of
1280 * the window so it's the font size or the terminal itself.
1283 extra_width = wr.right - wr.left - cr.right + cr.left;
1284 extra_height = wr.bottom - wr.top - cr.bottom + cr.top;
1286 if (cfg.resize_action != RESIZE_TERM) {
1287 if ( font_width != win_width/cols ||
1288 font_height != win_height/rows) {
1290 init_fonts(win_width/cols, win_height/rows);
1291 offset_width = (win_width-font_width*cols)/2;
1292 offset_height = (win_height-font_height*rows)/2;
1293 InvalidateRect(hwnd, NULL, TRUE);
1294 #ifdef RDB_DEBUG_PATCH
1295 debug((25, "reset_window() -> Z font resize to (%d, %d)",
1296 font_width, font_height));
1300 if ( font_width != win_width/cols ||
1301 font_height != win_height/rows) {
1302 /* Our only choice at this point is to change the
1303 * size of the terminal; Oh well.
1305 term_size( win_height/font_height, win_width/font_width,
1307 offset_width = (win_width-font_width*cols)/2;
1308 offset_height = (win_height-font_height*rows)/2;
1309 InvalidateRect(hwnd, NULL, TRUE);
1310 #ifdef RDB_DEBUG_PATCH
1311 debug((27, "reset_window() -> Zoomed term_size"));
1318 /* Hmm, a force re-init means we should ignore the current window
1319 * so we resize to the default font size.
1322 #ifdef RDB_DEBUG_PATCH
1323 debug((27, "reset_window() -> Forced re-init"));
1326 offset_width = offset_height = cfg.window_border;
1327 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
1328 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
1330 if (win_width != font_width*cols + offset_width*2 ||
1331 win_height != font_height*rows + offset_height*2) {
1333 /* If this is too large windows will resize it to the maximum
1334 * allowed window size, we will then be back in here and resize
1335 * the font or terminal to fit.
1337 SetWindowPos(hwnd, NULL, 0, 0,
1338 font_width*cols + extra_width,
1339 font_height*rows + extra_height,
1340 SWP_NOMOVE | SWP_NOZORDER);
1343 InvalidateRect(hwnd, NULL, TRUE);
1347 /* Okay the user doesn't want us to change the font so we try the
1348 * window. But that may be too big for the screen which forces us
1349 * to change the terminal.
1351 if ((cfg.resize_action == RESIZE_TERM && reinit<=0) ||
1352 (cfg.resize_action == RESIZE_EITHER && reinit<0) ||
1354 offset_width = offset_height = cfg.window_border;
1355 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
1356 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
1358 if (win_width != font_width*cols + offset_width*2 ||
1359 win_height != font_height*rows + offset_height*2) {
1364 GetClientRect(GetDesktopWindow(), &ss);
1365 width = (ss.right - ss.left - extra_width) / font_width;
1366 height = (ss.bottom - ss.top - extra_height) / font_height;
1369 if ( rows > height || cols > width ) {
1370 if (cfg.resize_action == RESIZE_EITHER) {
1371 /* Make the font the biggest we can */
1373 font_width = (ss.right - ss.left - extra_width)/cols;
1375 font_height = (ss.bottom - ss.top - extra_height)/rows;
1378 init_fonts(font_width, font_height);
1380 width = (ss.right - ss.left - extra_width) / font_width;
1381 height = (ss.bottom - ss.top - extra_height) / font_height;
1383 if ( height > rows ) height = rows;
1384 if ( width > cols ) width = cols;
1385 term_size(height, width, cfg.savelines);
1386 #ifdef RDB_DEBUG_PATCH
1387 debug((27, "reset_window() -> term resize to (%d,%d)",
1393 SetWindowPos(hwnd, NULL, 0, 0,
1394 font_width*cols + extra_width,
1395 font_height*rows + extra_height,
1396 SWP_NOMOVE | SWP_NOZORDER);
1398 InvalidateRect(hwnd, NULL, TRUE);
1399 #ifdef RDB_DEBUG_PATCH
1400 debug((27, "reset_window() -> window resize to (%d,%d)",
1401 font_width*cols + extra_width,
1402 font_height*rows + extra_height));
1408 /* We're allowed to or must change the font but do we want to ? */
1410 if (font_width != (win_width-cfg.window_border*2)/cols ||
1411 font_height != (win_height-cfg.window_border*2)/rows) {
1414 init_fonts((win_width-cfg.window_border*2)/cols,
1415 (win_height-cfg.window_border*2)/rows);
1416 offset_width = (win_width-font_width*cols)/2;
1417 offset_height = (win_height-font_height*rows)/2;
1419 extra_width = wr.right - wr.left - cr.right + cr.left +offset_width*2;
1420 extra_height = wr.bottom - wr.top - cr.bottom + cr.top+offset_height*2;
1422 InvalidateRect(hwnd, NULL, TRUE);
1423 #ifdef RDB_DEBUG_PATCH
1424 debug((25, "reset_window() -> font resize to (%d,%d)",
1425 font_width, font_height));
1430 static void set_input_locale(HKL kl)
1434 GetLocaleInfo(LOWORD(kl), LOCALE_IDEFAULTANSICODEPAGE,
1435 lbuf, sizeof(lbuf));
1437 kbd_codepage = atoi(lbuf);
1440 static void click(Mouse_Button b, int x, int y, int shift, int ctrl, int alt)
1442 int thistime = GetMessageTime();
1444 if (send_raw_mouse && !(cfg.mouse_override && shift)) {
1445 lastbtn = MBT_NOTHING;
1446 term_mouse(b, MA_CLICK, x, y, shift, ctrl, alt);
1450 if (lastbtn == b && thistime - lasttime < dbltime) {
1451 lastact = (lastact == MA_CLICK ? MA_2CLK :
1452 lastact == MA_2CLK ? MA_3CLK :
1453 lastact == MA_3CLK ? MA_CLICK : MA_NOTHING);
1458 if (lastact != MA_NOTHING)
1459 term_mouse(b, lastact, x, y, shift, ctrl, alt);
1460 lasttime = thistime;
1464 * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT)
1465 * into a cooked one (SELECT, EXTEND, PASTE).
1467 Mouse_Button translate_button(Mouse_Button button)
1469 if (button == MBT_LEFT)
1471 if (button == MBT_MIDDLE)
1472 return cfg.mouse_is_xterm ? MBT_PASTE : MBT_EXTEND;
1473 if (button == MBT_RIGHT)
1474 return cfg.mouse_is_xterm ? MBT_EXTEND : MBT_PASTE;
1475 return 0; /* shouldn't happen */
1478 static void show_mouseptr(int show)
1480 static int cursor_visible = 1;
1481 if (!cfg.hide_mouseptr) /* override if this feature disabled */
1483 if (cursor_visible && !show)
1485 else if (!cursor_visible && show)
1487 cursor_visible = show;
1490 static int is_alt_pressed(void)
1493 int r = GetKeyboardState(keystate);
1496 if (keystate[VK_MENU] & 0x80)
1498 if (keystate[VK_RMENU] & 0x80)
1503 static int resizing;
1505 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1506 WPARAM wParam, LPARAM lParam)
1509 static int ignore_clip = FALSE;
1510 static int need_backend_resize = FALSE;
1511 static int fullscr_on_max = FALSE;
1515 if (pending_netevent)
1516 enact_pending_netevent();
1522 if (cfg.ping_interval > 0) {
1525 if (now - last_movement > cfg.ping_interval) {
1526 back->special(TS_PING);
1527 last_movement = now;
1530 net_pending_errors();
1536 if (!cfg.warn_on_close || session_closed ||
1538 "Are you sure you want to close this session?",
1539 "PuTTY Exit Confirmation",
1540 MB_ICONWARNING | MB_OKCANCEL) == IDOK)
1541 DestroyWindow(hwnd);
1548 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1560 PROCESS_INFORMATION pi;
1561 HANDLE filemap = NULL;
1563 if (wParam == IDM_DUPSESS) {
1565 * Allocate a file-mapping memory chunk for the
1568 SECURITY_ATTRIBUTES sa;
1571 sa.nLength = sizeof(sa);
1572 sa.lpSecurityDescriptor = NULL;
1573 sa.bInheritHandle = TRUE;
1574 filemap = CreateFileMapping((HANDLE) 0xFFFFFFFF,
1577 0, sizeof(Config), NULL);
1579 p = (Config *) MapViewOfFile(filemap,
1581 0, 0, sizeof(Config));
1583 *p = cfg; /* structure copy */
1587 sprintf(c, "putty &%p", filemap);
1589 } else if (wParam == IDM_SAVEDSESS) {
1590 if ((lParam - IDM_SAVED_MIN) / 16 < nsessions) {
1592 sessions[(lParam - IDM_SAVED_MIN) / 16];
1593 cl = smalloc(16 + strlen(session));
1594 /* 8, but play safe */
1597 /* not a very important failure mode */
1599 sprintf(cl, "putty @%s", session);
1607 GetModuleFileName(NULL, b, sizeof(b) - 1);
1609 si.lpReserved = NULL;
1610 si.lpDesktop = NULL;
1614 si.lpReserved2 = NULL;
1615 CreateProcess(b, cl, NULL, NULL, TRUE,
1616 NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
1619 CloseHandle(filemap);
1629 GetWindowText(hwnd, cfg.wintitle, sizeof(cfg.wintitle));
1632 if (!do_reconfig(hwnd))
1636 /* Disable full-screen if resizing forbidden */
1637 HMENU m = GetSystemMenu (hwnd, FALSE);
1638 EnableMenuItem(m, IDM_FULLSCREEN, MF_BYCOMMAND |
1639 (cfg.resize_action == RESIZE_DISABLED)
1640 ? MF_GRAYED : MF_ENABLED);
1641 /* Gracefully unzoom if necessary */
1642 if (IsZoomed(hwnd) &&
1643 (cfg.resize_action == RESIZE_DISABLED)) {
1644 ShowWindow(hwnd, SW_RESTORE);
1648 if (strcmp(prev_cfg.logfilename, cfg.logfilename) ||
1649 prev_cfg.logtype != cfg.logtype) {
1650 logfclose(); /* reset logging */
1656 * Flush the line discipline's edit buffer in the
1657 * case where local editing has just been disabled.
1659 ldisc_send(NULL, 0, 0);
1667 /* Screen size changed ? */
1668 if (cfg.height != prev_cfg.height ||
1669 cfg.width != prev_cfg.width ||
1670 cfg.savelines != prev_cfg.savelines ||
1671 cfg.resize_action == RESIZE_FONT ||
1672 (cfg.resize_action == RESIZE_EITHER && IsZoomed(hwnd)) ||
1673 cfg.resize_action == RESIZE_DISABLED)
1674 term_size(cfg.height, cfg.width, cfg.savelines);
1676 /* Enable or disable the scroll bar, etc */
1678 LONG nflg, flag = GetWindowLong(hwnd, GWL_STYLE);
1679 LONG nexflag, exflag =
1680 GetWindowLong(hwnd, GWL_EXSTYLE);
1683 if (cfg.alwaysontop != prev_cfg.alwaysontop) {
1684 if (cfg.alwaysontop) {
1685 nexflag |= WS_EX_TOPMOST;
1686 SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
1687 SWP_NOMOVE | SWP_NOSIZE);
1689 nexflag &= ~(WS_EX_TOPMOST);
1690 SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0,
1691 SWP_NOMOVE | SWP_NOSIZE);
1694 if (cfg.sunken_edge)
1695 nexflag |= WS_EX_CLIENTEDGE;
1697 nexflag &= ~(WS_EX_CLIENTEDGE);
1700 if (is_full_screen() ?
1701 cfg.scrollbar_in_fullscreen : cfg.scrollbar)
1704 nflg &= ~WS_VSCROLL;
1706 if (cfg.resize_action == RESIZE_DISABLED ||
1708 nflg &= ~WS_THICKFRAME;
1710 nflg |= WS_THICKFRAME;
1712 if (cfg.resize_action == RESIZE_DISABLED)
1713 nflg &= ~WS_MAXIMIZEBOX;
1715 nflg |= WS_MAXIMIZEBOX;
1717 if (nflg != flag || nexflag != exflag) {
1719 SetWindowLong(hwnd, GWL_STYLE, nflg);
1720 if (nexflag != exflag)
1721 SetWindowLong(hwnd, GWL_EXSTYLE, nexflag);
1723 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
1724 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1725 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
1733 if (cfg.resize_action == RESIZE_DISABLED && IsZoomed(hwnd)) {
1738 set_title(cfg.wintitle);
1739 if (IsIconic(hwnd)) {
1741 cfg.win_name_always ? window_name :
1745 if (strcmp(cfg.font, prev_cfg.font) != 0 ||
1746 strcmp(cfg.line_codepage, prev_cfg.line_codepage) != 0 ||
1747 cfg.fontisbold != prev_cfg.fontisbold ||
1748 cfg.fontheight != prev_cfg.fontheight ||
1749 cfg.fontcharset != prev_cfg.fontcharset ||
1750 cfg.vtmode != prev_cfg.vtmode ||
1751 cfg.bold_colour != prev_cfg.bold_colour ||
1752 cfg.resize_action == RESIZE_DISABLED ||
1753 cfg.resize_action == RESIZE_EITHER ||
1754 (cfg.resize_action != prev_cfg.resize_action))
1757 InvalidateRect(hwnd, NULL, TRUE);
1758 reset_window(init_lvl);
1759 net_pending_errors();
1772 back->special(TS_AYT);
1773 net_pending_errors();
1776 back->special(TS_BRK);
1777 net_pending_errors();
1780 back->special(TS_SYNCH);
1781 net_pending_errors();
1784 back->special(TS_EC);
1785 net_pending_errors();
1788 back->special(TS_EL);
1789 net_pending_errors();
1792 back->special(TS_GA);
1793 net_pending_errors();
1796 back->special(TS_NOP);
1797 net_pending_errors();
1800 back->special(TS_ABORT);
1801 net_pending_errors();
1804 back->special(TS_AO);
1805 net_pending_errors();
1808 back->special(TS_IP);
1809 net_pending_errors();
1812 back->special(TS_SUSP);
1813 net_pending_errors();
1816 back->special(TS_EOR);
1817 net_pending_errors();
1820 back->special(TS_EOF);
1821 net_pending_errors();
1827 WinHelp(hwnd, help_path,
1828 help_has_contents ? HELP_FINDER : HELP_CONTENTS, 0);
1832 * We get this if the System menu has been activated
1839 * We get this if the System menu has been activated
1840 * using the keyboard. This might happen from within
1841 * TranslateKey, in which case it really wants to be
1842 * followed by a `space' character to actually _bring
1843 * the menu up_ rather than just sitting there in
1844 * `ready to appear' state.
1846 show_mouseptr(1); /* make sure pointer is visible */
1848 PostMessage(hwnd, WM_CHAR, ' ', 0);
1850 case IDM_FULLSCREEN:
1854 if (wParam >= IDM_SAVED_MIN && wParam <= IDM_SAVED_MAX) {
1855 SendMessage(hwnd, WM_SYSCOMMAND, IDM_SAVEDSESS, wParam);
1860 #define X_POS(l) ((int)(short)LOWORD(l))
1861 #define Y_POS(l) ((int)(short)HIWORD(l))
1863 #define TO_CHR_X(x) ((((x)<0 ? (x)-font_width+1 : (x))-offset_width) / font_width)
1864 #define TO_CHR_Y(y) ((((y)<0 ? (y)-font_height+1: (y))-offset_height) / font_height)
1865 case WM_LBUTTONDOWN:
1866 case WM_MBUTTONDOWN:
1867 case WM_RBUTTONDOWN:
1875 case WM_LBUTTONDOWN:
1879 case WM_MBUTTONDOWN:
1880 button = MBT_MIDDLE;
1883 case WM_RBUTTONDOWN:
1892 button = MBT_MIDDLE;
1900 button = press = 0; /* shouldn't happen */
1904 * Special case: in full-screen mode, if the left
1905 * button is clicked in the very top left corner of the
1906 * window, we put up the System menu instead of doing
1909 if (is_full_screen() && press && button == MBT_LEFT &&
1910 X_POS(lParam) == 0 && Y_POS(lParam) == 0) {
1911 SendMessage(hwnd, WM_SYSCOMMAND, SC_MOUSEMENU, 0);
1916 TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam)),
1917 wParam & MK_SHIFT, wParam & MK_CONTROL,
1921 term_mouse(button, MA_RELEASE,
1922 TO_CHR_X(X_POS(lParam)),
1923 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
1924 wParam & MK_CONTROL, is_alt_pressed());
1932 * Add the mouse position and message time to the random
1935 noise_ultralight(lParam);
1937 if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)) {
1939 if (wParam & MK_LBUTTON)
1941 else if (wParam & MK_MBUTTON)
1945 term_mouse(b, MA_DRAG, TO_CHR_X(X_POS(lParam)),
1946 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
1947 wParam & MK_CONTROL, is_alt_pressed());
1950 case WM_NCMOUSEMOVE:
1952 noise_ultralight(lParam);
1954 case WM_IGNORE_CLIP:
1955 ignore_clip = wParam; /* don't panic on DESTROYCLIPBOARD */
1957 case WM_DESTROYCLIPBOARD:
1960 ignore_clip = FALSE;
1966 hdc = BeginPaint(hwnd, &p);
1968 SelectPalette(hdc, pal, TRUE);
1969 RealizePalette(hdc);
1972 (p.rcPaint.left-offset_width)/font_width,
1973 (p.rcPaint.top-offset_height)/font_height,
1974 (p.rcPaint.right-offset_width-1)/font_width,
1975 (p.rcPaint.bottom-offset_height-1)/font_height);
1978 p.rcPaint.left < offset_width ||
1979 p.rcPaint.top < offset_height ||
1980 p.rcPaint.right >= offset_width + font_width*cols ||
1981 p.rcPaint.bottom>= offset_height + font_height*rows)
1983 HBRUSH fillcolour, oldbrush;
1985 fillcolour = CreateSolidBrush (
1986 colours[(ATTR_DEFBG>>ATTR_BGSHIFT)*2]);
1987 oldbrush = SelectObject(hdc, fillcolour);
1988 edge = CreatePen(PS_SOLID, 0,
1989 colours[(ATTR_DEFBG>>ATTR_BGSHIFT)*2]);
1990 oldpen = SelectObject(hdc, edge);
1992 ExcludeClipRect(hdc,
1993 offset_width, offset_height,
1994 offset_width+font_width*cols,
1995 offset_height+font_height*rows);
1997 Rectangle(hdc, p.rcPaint.left, p.rcPaint.top,
1998 p.rcPaint.right, p.rcPaint.bottom);
2000 // SelectClipRgn(hdc, NULL);
2002 SelectObject(hdc, oldbrush);
2003 DeleteObject(fillcolour);
2004 SelectObject(hdc, oldpen);
2007 SelectObject(hdc, GetStockObject(SYSTEM_FONT));
2008 SelectObject(hdc, GetStockObject(WHITE_PEN));
2014 /* Notice we can get multiple netevents, FD_READ, FD_WRITE etc
2015 * but the only one that's likely to try to overload us is FD_READ.
2016 * This means buffering just one is fine.
2018 if (pending_netevent)
2019 enact_pending_netevent();
2021 pending_netevent = TRUE;
2022 pend_netevent_wParam = wParam;
2023 pend_netevent_lParam = lParam;
2024 if (WSAGETSELECTEVENT(lParam) != FD_READ)
2025 enact_pending_netevent();
2027 time(&last_movement);
2031 CreateCaret(hwnd, caretbm, font_width, font_height);
2033 flash_window(0); /* stop */
2045 case WM_ENTERSIZEMOVE:
2046 #ifdef RDB_DEBUG_PATCH
2047 debug((27, "WM_ENTERSIZEMOVE"));
2051 need_backend_resize = FALSE;
2053 case WM_EXITSIZEMOVE:
2056 #ifdef RDB_DEBUG_PATCH
2057 debug((27, "WM_EXITSIZEMOVE"));
2059 if (need_backend_resize) {
2060 term_size(cfg.height, cfg.width, cfg.savelines);
2061 InvalidateRect(hwnd, NULL, TRUE);
2066 * This does two jobs:
2067 * 1) Keep the sizetip uptodate
2068 * 2) Make sure the window size is _stepped_ in units of the font size.
2070 if (cfg.resize_action != RESIZE_FONT && !alt_pressed) {
2071 int width, height, w, h, ew, eh;
2072 LPRECT r = (LPRECT) lParam;
2074 if ( !need_backend_resize && cfg.resize_action == RESIZE_EITHER &&
2075 (cfg.height != rows || cfg.width != cols )) {
2077 * Great! It seems that both the terminal size and the
2078 * font size have been changed and the user is now dragging.
2080 * It will now be difficult to get back to the configured
2083 * This would be easier but it seems to be too confusing.
2085 term_size(cfg.height, cfg.width, cfg.savelines);
2088 cfg.height=rows; cfg.width=cols;
2090 InvalidateRect(hwnd, NULL, TRUE);
2091 need_backend_resize = TRUE;
2094 width = r->right - r->left - extra_width;
2095 height = r->bottom - r->top - extra_height;
2096 w = (width + font_width / 2) / font_width;
2099 h = (height + font_height / 2) / font_height;
2102 UpdateSizeTip(hwnd, w, h);
2103 ew = width - w * font_width;
2104 eh = height - h * font_height;
2106 if (wParam == WMSZ_LEFT ||
2107 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
2113 if (wParam == WMSZ_TOP ||
2114 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
2124 int width, height, w, h, rv = 0;
2125 int ex_width = extra_width + (cfg.window_border - offset_width) * 2;
2126 int ex_height = extra_height + (cfg.window_border - offset_height) * 2;
2127 LPRECT r = (LPRECT) lParam;
2129 width = r->right - r->left - ex_width;
2130 height = r->bottom - r->top - ex_height;
2132 w = (width + cols/2)/cols;
2133 h = (height + rows/2)/rows;
2134 if ( r->right != r->left + w*cols + ex_width)
2137 if (wParam == WMSZ_LEFT ||
2138 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
2139 r->left = r->right - w*cols - ex_width;
2141 r->right = r->left + w*cols + ex_width;
2143 if (r->bottom != r->top + h*rows + ex_height)
2146 if (wParam == WMSZ_TOP ||
2147 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
2148 r->top = r->bottom - h*rows - ex_height;
2150 r->bottom = r->top + h*rows + ex_height;
2154 /* break; (never reached) */
2155 case WM_FULLSCR_ON_MAX:
2156 fullscr_on_max = TRUE;
2159 #ifdef RDB_DEBUG_PATCH
2160 debug((27, "WM_SIZE %s (%d,%d)",
2161 (wParam == SIZE_MINIMIZED) ? "SIZE_MINIMIZED":
2162 (wParam == SIZE_MAXIMIZED) ? "SIZE_MAXIMIZED":
2163 (wParam == SIZE_RESTORED && resizing) ? "to":
2164 (wParam == SIZE_RESTORED) ? "SIZE_RESTORED":
2166 LOWORD(lParam), HIWORD(lParam)));
2168 if (wParam == SIZE_MINIMIZED)
2170 cfg.win_name_always ? window_name : icon_name);
2171 if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
2172 SetWindowText(hwnd, window_name);
2173 if (wParam == SIZE_RESTORED)
2174 clear_full_screen();
2175 if (wParam == SIZE_MAXIMIZED && fullscr_on_max) {
2177 fullscr_on_max = FALSE;
2180 if (cfg.resize_action == RESIZE_DISABLED) {
2181 /* A resize, well it better be a minimize. */
2185 int width, height, w, h;
2187 width = LOWORD(lParam);
2188 height = HIWORD(lParam);
2191 if (wParam == SIZE_MAXIMIZED && !was_zoomed) {
2195 if (cfg.resize_action == RESIZE_TERM) {
2196 w = width / font_width;
2198 h = height / font_height;
2201 term_size(h, w, cfg.savelines);
2204 } else if (wParam == SIZE_RESTORED && was_zoomed) {
2206 if (cfg.resize_action == RESIZE_TERM)
2207 term_size(prev_rows, prev_cols, cfg.savelines);
2208 if (cfg.resize_action != RESIZE_FONT)
2213 /* This is an unexpected resize, these will normally happen
2214 * if the window is too large. Probably either the user
2215 * selected a huge font or the screen size has changed.
2217 * This is also called with minimize.
2219 else reset_window(-1);
2223 * Don't call back->size in mid-resize. (To prevent
2224 * massive numbers of resize events getting sent
2225 * down the connection during an NT opaque drag.)
2228 if (cfg.resize_action != RESIZE_FONT && !alt_pressed) {
2229 need_backend_resize = TRUE;
2230 w = (width-cfg.window_border*2) / font_width;
2232 h = (height-cfg.window_border*2) / font_height;
2243 switch (LOWORD(wParam)) {
2257 term_scroll(0, +rows / 2);
2260 term_scroll(0, -rows / 2);
2262 case SB_THUMBPOSITION:
2264 term_scroll(1, HIWORD(wParam));
2268 case WM_PALETTECHANGED:
2269 if ((HWND) wParam != hwnd && pal != NULL) {
2270 HDC hdc = get_ctx();
2272 if (RealizePalette(hdc) > 0)
2278 case WM_QUERYNEWPALETTE:
2280 HDC hdc = get_ctx();
2282 if (RealizePalette(hdc) > 0)
2294 * Add the scan code and keypress timing to the random
2297 noise_ultralight(lParam);
2300 * We don't do TranslateMessage since it disassociates the
2301 * resulting CHAR message from the KEYDOWN that sparked it,
2302 * which we occasionally don't want. Instead, we process
2303 * KEYDOWN, and call the Win32 translator functions so that
2304 * we get the translations under _our_ control.
2307 unsigned char buf[20];
2310 if (wParam == VK_PROCESSKEY) {
2313 m.message = WM_KEYDOWN;
2315 m.lParam = lParam & 0xdfff;
2316 TranslateMessage(&m);
2318 len = TranslateKey(message, wParam, lParam, buf);
2320 return DefWindowProc(hwnd, message, wParam, lParam);
2324 * Interrupt an ongoing paste. I'm not sure
2325 * this is sensible, but for the moment it's
2326 * preferable to having to faff about buffering
2332 * We need not bother about stdin backlogs
2333 * here, because in GUI PuTTY we can't do
2334 * anything about it anyway; there's no means
2335 * of asking Windows to hold off on KEYDOWN
2336 * messages. We _have_ to buffer everything
2339 ldisc_send(buf, len, 1);
2344 net_pending_errors();
2346 case WM_INPUTLANGCHANGE:
2347 /* wParam == Font number */
2348 /* lParam == Locale */
2349 set_input_locale((HKL)lParam);
2352 if(wParam == IMN_SETOPENSTATUS) {
2353 HIMC hImc = ImmGetContext(hwnd);
2354 ImmSetCompositionFont(hImc, &lfont);
2355 ImmReleaseContext(hwnd, hImc);
2359 case WM_IME_COMPOSITION:
2365 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ||
2366 osVersion.dwPlatformId == VER_PLATFORM_WIN32s) break; /* no Unicode */
2368 if ((lParam & GCS_RESULTSTR) == 0) /* Composition unfinished. */
2369 break; /* fall back to DefWindowProc */
2371 hIMC = ImmGetContext(hwnd);
2372 n = ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, NULL, 0);
2376 buff = (char*) smalloc(n);
2377 ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, buff, n);
2379 * Jaeyoun Chung reports that Korean character
2380 * input doesn't work correctly if we do a single
2381 * luni_send() covering the whole of buff. So
2382 * instead we luni_send the characters one by one.
2384 for (i = 0; i < n; i += 2)
2385 luni_send((unsigned short *)(buff+i), 1, 1);
2388 ImmReleaseContext(hwnd, hIMC);
2393 if (wParam & 0xFF00) {
2394 unsigned char buf[2];
2397 buf[0] = wParam >> 8;
2398 lpage_send(kbd_codepage, buf, 2, 1);
2400 char c = (unsigned char) wParam;
2401 lpage_send(kbd_codepage, &c, 1, 1);
2407 * Nevertheless, we are prepared to deal with WM_CHAR
2408 * messages, should they crop up. So if someone wants to
2409 * post the things to us as part of a macro manoeuvre,
2410 * we're ready to cope.
2413 char c = (unsigned char)wParam;
2414 lpage_send(CP_ACP, &c, 1, 1);
2418 if (send_raw_mouse && LOWORD(lParam) == HTCLIENT) {
2419 SetCursor(LoadCursor(NULL, IDC_ARROW));
2423 if (message == wm_mousewheel) {
2424 int shift_pressed=0, control_pressed=0, alt_pressed=0;
2426 if (message == WM_MOUSEWHEEL) {
2427 wheel_accumulator += (short)HIWORD(wParam);
2428 shift_pressed=LOWORD(wParam) & MK_SHIFT;
2429 control_pressed=LOWORD(wParam) & MK_CONTROL;
2432 wheel_accumulator += (int)wParam;
2433 if (GetKeyboardState(keys)!=0) {
2434 shift_pressed=keys[VK_SHIFT]&0x80;
2435 control_pressed=keys[VK_CONTROL]&0x80;
2439 /* process events when the threshold is reached */
2440 while (abs(wheel_accumulator) >= WHEEL_DELTA) {
2443 /* reduce amount for next time */
2444 if (wheel_accumulator > 0) {
2446 wheel_accumulator -= WHEEL_DELTA;
2447 } else if (wheel_accumulator < 0) {
2449 wheel_accumulator += WHEEL_DELTA;
2453 if (send_raw_mouse &&
2454 !(cfg.mouse_override && shift_pressed)) {
2455 /* send a mouse-down followed by a mouse up */
2458 TO_CHR_X(X_POS(lParam)),
2459 TO_CHR_Y(Y_POS(lParam)), shift_pressed,
2460 control_pressed, is_alt_pressed());
2461 term_mouse(b, MA_RELEASE, TO_CHR_X(X_POS(lParam)),
2462 TO_CHR_Y(Y_POS(lParam)), shift_pressed,
2463 control_pressed, is_alt_pressed());
2465 /* trigger a scroll */
2467 b == MBT_WHEEL_UP ? -rows / 2 : rows / 2);
2474 return DefWindowProc(hwnd, message, wParam, lParam);
2478 * Move the system caret. (We maintain one, even though it's
2479 * invisible, for the benefit of blind people: apparently some
2480 * helper software tracks the system caret, so we should arrange to
2483 void sys_cursor(int x, int y)
2488 if (!has_focus) return;
2490 SetCaretPos(x * font_width + offset_width,
2491 y * font_height + offset_height);
2493 /* IMM calls on Win98 and beyond only */
2494 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32s) return; /* 3.11 */
2496 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS &&
2497 osVersion.dwMinorVersion == 0) return; /* 95 */
2499 /* we should have the IMM functions */
2500 hIMC = ImmGetContext(hwnd);
2501 cf.dwStyle = CFS_POINT;
2502 cf.ptCurrentPos.x = x * font_width + offset_width;
2503 cf.ptCurrentPos.y = y * font_height + offset_height;
2504 ImmSetCompositionWindow(hIMC, &cf);
2506 ImmReleaseContext(hwnd, hIMC);
2510 * Draw a line of text in the window, at given character
2511 * coordinates, in given attributes.
2513 * We are allowed to fiddle with the contents of `text'.
2515 void do_text(Context ctx, int x, int y, char *text, int len,
2516 unsigned long attr, int lattr)
2519 int nfg, nbg, nfont;
2522 int force_manual_underline = 0;
2523 int fnt_width = font_width * (1 + (lattr != LATTR_NORM));
2524 int char_width = fnt_width;
2525 int text_adjust = 0;
2526 static int *IpDx = 0, IpDxLEN = 0;
2528 if (attr & ATTR_WIDE)
2531 if (len > IpDxLEN || IpDx[0] != char_width) {
2533 if (len > IpDxLEN) {
2535 IpDx = smalloc((len + 16) * sizeof(int));
2536 IpDxLEN = (len + 16);
2538 for (i = 0; i < IpDxLEN; i++)
2539 IpDx[i] = char_width;
2542 /* Only want the left half of double width lines */
2543 if (lattr != LATTR_NORM && x*2 >= cols)
2551 if ((attr & TATTR_ACTCURS) && (cfg.cursor_type == 0 || big_cursor)) {
2552 attr &= ATTR_CUR_AND | (bold_mode != BOLD_COLOURS ? ATTR_BOLD : 0);
2553 attr ^= ATTR_CUR_XOR;
2557 if (cfg.vtmode == VT_POORMAN && lattr != LATTR_NORM) {
2558 /* Assume a poorman font is borken in other ways too. */
2568 nfont |= FONT_WIDE + FONT_HIGH;
2571 if (attr & ATTR_NARROW)
2572 nfont |= FONT_NARROW;
2574 /* Special hack for the VT100 linedraw glyphs. */
2575 if ((attr & CSET_MASK) == 0x2300) {
2576 if (text[0] >= (char) 0xBA && text[0] <= (char) 0xBD) {
2577 switch ((unsigned char) (text[0])) {
2579 text_adjust = -2 * font_height / 5;
2582 text_adjust = -1 * font_height / 5;
2585 text_adjust = font_height / 5;
2588 text_adjust = 2 * font_height / 5;
2591 if (lattr == LATTR_TOP || lattr == LATTR_BOT)
2594 text[0] = (char) (unitab_xterm['q'] & CHAR_MASK);
2595 attr |= (unitab_xterm['q'] & CSET_MASK);
2596 if (attr & ATTR_UNDER) {
2597 attr &= ~ATTR_UNDER;
2598 force_manual_underline = 1;
2603 /* Anything left as an original character set is unprintable. */
2604 if (DIRECT_CHAR(attr)) {
2607 memset(text, 0xFD, len);
2611 if ((attr & CSET_MASK) == ATTR_OEMCP)
2614 nfg = 2 * ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
2615 nbg = 2 * ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
2616 if (bold_mode == BOLD_FONT && (attr & ATTR_BOLD))
2618 if (und_mode == UND_FONT && (attr & ATTR_UNDER))
2619 nfont |= FONT_UNDERLINE;
2620 another_font(nfont);
2621 if (!fonts[nfont]) {
2622 if (nfont & FONT_UNDERLINE)
2623 force_manual_underline = 1;
2624 /* Don't do the same for manual bold, it could be bad news. */
2626 nfont &= ~(FONT_BOLD | FONT_UNDERLINE);
2628 another_font(nfont);
2630 nfont = FONT_NORMAL;
2631 if (attr & ATTR_REVERSE) {
2636 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BOLD))
2638 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BLINK))
2642 SelectObject(hdc, fonts[nfont]);
2643 SetTextColor(hdc, fg);
2644 SetBkColor(hdc, bg);
2645 SetBkMode(hdc, OPAQUE);
2648 line_box.right = x + char_width * len;
2649 line_box.bottom = y + font_height;
2651 /* Only want the left half of double width lines */
2652 if (line_box.right > font_width*cols+offset_width)
2653 line_box.right = font_width*cols+offset_width;
2655 /* We're using a private area for direct to font. (512 chars.) */
2656 if (dbcs_screenfont && (attr & CSET_MASK) == ATTR_ACP) {
2657 /* Ho Hum, dbcs fonts are a PITA! */
2658 /* To display on W9x I have to convert to UCS */
2659 static wchar_t *uni_buf = 0;
2660 static int uni_len = 0;
2662 if (len > uni_len) {
2664 uni_buf = smalloc((uni_len = len) * sizeof(wchar_t));
2667 for(nlen = mptr = 0; mptr<len; mptr++) {
2668 uni_buf[nlen] = 0xFFFD;
2669 if (IsDBCSLeadByteEx(font_codepage, (BYTE) text[mptr])) {
2670 IpDx[nlen] += char_width;
2671 MultiByteToWideChar(font_codepage, MB_USEGLYPHCHARS,
2672 text+mptr, 2, uni_buf+nlen, 1);
2677 MultiByteToWideChar(font_codepage, MB_USEGLYPHCHARS,
2678 text+mptr, 1, uni_buf+nlen, 1);
2686 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2687 ETO_CLIPPED | ETO_OPAQUE, &line_box, uni_buf, nlen, IpDx);
2688 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2689 SetBkMode(hdc, TRANSPARENT);
2690 ExtTextOutW(hdc, x - 1,
2691 y - font_height * (lattr ==
2692 LATTR_BOT) + text_adjust,
2693 ETO_CLIPPED, &line_box, uni_buf, nlen, IpDx);
2697 } else if (DIRECT_FONT(attr)) {
2699 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2700 ETO_CLIPPED | ETO_OPAQUE, &line_box, text, len, IpDx);
2701 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2702 SetBkMode(hdc, TRANSPARENT);
2704 /* GRR: This draws the character outside it's box and can leave
2705 * 'droppings' even with the clip box! I suppose I could loop it
2706 * one character at a time ... yuk.
2708 * Or ... I could do a test print with "W", and use +1 or -1 for this
2709 * shift depending on if the leftmost column is blank...
2711 ExtTextOut(hdc, x - 1,
2712 y - font_height * (lattr ==
2713 LATTR_BOT) + text_adjust,
2714 ETO_CLIPPED, &line_box, text, len, IpDx);
2717 /* And 'normal' unicode characters */
2718 static WCHAR *wbuf = NULL;
2719 static int wlen = 0;
2724 wbuf = smalloc(wlen * sizeof(WCHAR));
2726 for (i = 0; i < len; i++)
2727 wbuf[i] = (WCHAR) ((attr & CSET_MASK) + (text[i] & CHAR_MASK));
2730 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2731 ETO_CLIPPED | ETO_OPAQUE, &line_box, wbuf, len, IpDx);
2733 /* And the shadow bold hack. */
2734 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2735 SetBkMode(hdc, TRANSPARENT);
2736 ExtTextOutW(hdc, x - 1,
2737 y - font_height * (lattr ==
2738 LATTR_BOT) + text_adjust,
2739 ETO_CLIPPED, &line_box, wbuf, len, IpDx);
2742 if (lattr != LATTR_TOP && (force_manual_underline ||
2743 (und_mode == UND_LINE
2744 && (attr & ATTR_UNDER)))) {
2747 if (lattr == LATTR_BOT)
2748 dec = dec * 2 - font_height;
2750 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, fg));
2751 MoveToEx(hdc, x, y + dec, NULL);
2752 LineTo(hdc, x + len * char_width, y + dec);
2753 oldpen = SelectObject(hdc, oldpen);
2754 DeleteObject(oldpen);
2758 void do_cursor(Context ctx, int x, int y, char *text, int len,
2759 unsigned long attr, int lattr)
2765 int ctype = cfg.cursor_type;
2767 if ((attr & TATTR_ACTCURS) && (ctype == 0 || big_cursor)) {
2768 if (((attr & CSET_MASK) | (unsigned char) *text) != UCSWIDE) {
2769 do_text(ctx, x, y, text, len, attr, lattr);
2773 attr |= TATTR_RIGHTCURS;
2776 fnt_width = char_width = font_width * (1 + (lattr != LATTR_NORM));
2777 if (attr & ATTR_WIDE)
2784 if ((attr & TATTR_PASCURS) && (ctype == 0 || big_cursor)) {
2787 pts[0].x = pts[1].x = pts[4].x = x;
2788 pts[2].x = pts[3].x = x + char_width - 1;
2789 pts[0].y = pts[3].y = pts[4].y = y;
2790 pts[1].y = pts[2].y = y + font_height - 1;
2791 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2792 Polyline(hdc, pts, 5);
2793 oldpen = SelectObject(hdc, oldpen);
2794 DeleteObject(oldpen);
2795 } else if ((attr & (TATTR_ACTCURS | TATTR_PASCURS)) && ctype != 0) {
2796 int startx, starty, dx, dy, length, i;
2799 starty = y + descent;
2802 length = char_width;
2805 if (attr & TATTR_RIGHTCURS)
2806 xadjust = char_width - 1;
2807 startx = x + xadjust;
2811 length = font_height;
2813 if (attr & TATTR_ACTCURS) {
2816 SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2817 MoveToEx(hdc, startx, starty, NULL);
2818 LineTo(hdc, startx + dx * length, starty + dy * length);
2819 oldpen = SelectObject(hdc, oldpen);
2820 DeleteObject(oldpen);
2822 for (i = 0; i < length; i++) {
2824 SetPixel(hdc, startx, starty, colours[23]);
2833 /* This function gets the actual width of a character in the normal font.
2835 int CharWidth(Context ctx, int uc) {
2839 /* If the font max is the same as the font ave width then this
2840 * function is a no-op.
2842 if (!font_dualwidth) return 1;
2844 switch (uc & CSET_MASK) {
2846 uc = unitab_line[uc & 0xFF];
2849 uc = unitab_xterm[uc & 0xFF];
2852 uc = unitab_scoacs[uc & 0xFF];
2855 if (DIRECT_FONT(uc)) {
2856 if (dbcs_screenfont) return 1;
2858 /* Speedup, I know of no font where ascii is the wrong width */
2859 if ((uc&CHAR_MASK) >= ' ' && (uc&CHAR_MASK)<= '~')
2862 if ( (uc & CSET_MASK) == ATTR_ACP ) {
2863 SelectObject(hdc, fonts[FONT_NORMAL]);
2864 } else if ( (uc & CSET_MASK) == ATTR_OEMCP ) {
2865 another_font(FONT_OEM);
2866 if (!fonts[FONT_OEM]) return 0;
2868 SelectObject(hdc, fonts[FONT_OEM]);
2872 if ( GetCharWidth32(hdc, uc&CHAR_MASK, uc&CHAR_MASK, &ibuf) != 1 &&
2873 GetCharWidth(hdc, uc&CHAR_MASK, uc&CHAR_MASK, &ibuf) != 1)
2876 /* Speedup, I know of no font where ascii is the wrong width */
2877 if (uc >= ' ' && uc <= '~') return 1;
2879 SelectObject(hdc, fonts[FONT_NORMAL]);
2880 if ( GetCharWidth32W(hdc, uc, uc, &ibuf) == 1 )
2881 /* Okay that one worked */ ;
2882 else if ( GetCharWidthW(hdc, uc, uc, &ibuf) == 1 )
2883 /* This should work on 9x too, but it's "less accurate" */ ;
2888 ibuf += font_width / 2 -1;
2895 * Translate a WM_(SYS)?KEY(UP|DOWN) message into a string of ASCII
2896 * codes. Returns number of bytes used or zero to drop the message
2897 * or -1 to forward the message to windows.
2899 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
2900 unsigned char *output)
2903 int scan, left_alt = 0, key_down, shift_state;
2905 unsigned char *p = output;
2906 static int alt_sum = 0;
2908 HKL kbd_layout = GetKeyboardLayout(0);
2910 static WORD keys[3];
2911 static int compose_char = 0;
2912 static WPARAM compose_key = 0;
2914 r = GetKeyboardState(keystate);
2916 memset(keystate, 0, sizeof(keystate));
2919 #define SHOW_TOASCII_RESULT
2920 { /* Tell us all about key events */
2921 static BYTE oldstate[256];
2922 static int first = 1;
2926 memcpy(oldstate, keystate, sizeof(oldstate));
2929 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT) {
2931 } else if ((HIWORD(lParam) & KF_UP)
2932 && scan == (HIWORD(lParam) & 0xFF)) {
2936 if (wParam >= VK_F1 && wParam <= VK_F20)
2937 debug(("K_F%d", wParam + 1 - VK_F1));
2950 debug(("VK_%02x", wParam));
2952 if (message == WM_SYSKEYDOWN || message == WM_SYSKEYUP)
2954 debug((", S%02x", scan = (HIWORD(lParam) & 0xFF)));
2956 ch = MapVirtualKeyEx(wParam, 2, kbd_layout);
2957 if (ch >= ' ' && ch <= '~')
2958 debug((", '%c'", ch));
2960 debug((", $%02x", ch));
2963 debug((", KB0=%02x", keys[0]));
2965 debug((", KB1=%02x", keys[1]));
2967 debug((", KB2=%02x", keys[2]));
2969 if ((keystate[VK_SHIFT] & 0x80) != 0)
2971 if ((keystate[VK_CONTROL] & 0x80) != 0)
2973 if ((HIWORD(lParam) & KF_EXTENDED))
2975 if ((HIWORD(lParam) & KF_UP))
2979 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT);
2980 else if ((HIWORD(lParam) & KF_UP))
2981 oldstate[wParam & 0xFF] ^= 0x80;
2983 oldstate[wParam & 0xFF] ^= 0x81;
2985 for (ch = 0; ch < 256; ch++)
2986 if (oldstate[ch] != keystate[ch])
2987 debug((", M%02x=%02x", ch, keystate[ch]));
2989 memcpy(oldstate, keystate, sizeof(oldstate));
2993 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED)) {
2994 keystate[VK_RMENU] = keystate[VK_MENU];
2998 /* Nastyness with NUMLock - Shift-NUMLock is left alone though */
2999 if ((cfg.funky_type == 3 ||
3000 (cfg.funky_type <= 1 && app_keypad_keys && !cfg.no_applic_k))
3001 && wParam == VK_NUMLOCK && !(keystate[VK_SHIFT] & 0x80)) {
3003 wParam = VK_EXECUTE;
3005 /* UnToggle NUMLock */
3006 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0)
3007 keystate[VK_NUMLOCK] ^= 1;
3010 /* And write back the 'adjusted' state */
3011 SetKeyboardState(keystate);
3014 /* Disable Auto repeat if required */
3015 if (repeat_off && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT)
3018 if ((HIWORD(lParam) & KF_ALTDOWN) && (keystate[VK_RMENU] & 0x80) == 0)
3021 key_down = ((HIWORD(lParam) & KF_UP) == 0);
3023 /* Make sure Ctrl-ALT is not the same as AltGr for ToAscii unless told. */
3024 if (left_alt && (keystate[VK_CONTROL] & 0x80)) {
3025 if (cfg.ctrlaltkeys)
3026 keystate[VK_MENU] = 0;
3028 keystate[VK_RMENU] = 0x80;
3033 alt_pressed = (left_alt && key_down);
3035 scan = (HIWORD(lParam) & (KF_UP | KF_EXTENDED | 0xFF));
3036 shift_state = ((keystate[VK_SHIFT] & 0x80) != 0)
3037 + ((keystate[VK_CONTROL] & 0x80) != 0) * 2;
3039 /* Note if AltGr was pressed and if it was used as a compose key */
3040 if (!compose_state) {
3041 compose_key = 0x100;
3042 if (cfg.compose_key) {
3043 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED))
3044 compose_key = wParam;
3046 if (wParam == VK_APPS)
3047 compose_key = wParam;
3050 if (wParam == compose_key) {
3051 if (compose_state == 0
3052 && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0) compose_state =
3054 else if (compose_state == 1 && (HIWORD(lParam) & KF_UP))
3058 } else if (compose_state == 1 && wParam != VK_CONTROL)
3062 * Record that we pressed key so the scroll window can be reset, but
3063 * be careful to avoid Shift-UP/Down
3065 if (wParam != VK_SHIFT && wParam != VK_PRIOR && wParam != VK_NEXT &&
3066 wParam != VK_MENU && wParam != VK_CONTROL) {
3070 if (compose_state > 1 && left_alt)
3073 /* Sanitize the number pad if not using a PC NumPad */
3074 if (left_alt || (app_keypad_keys && !cfg.no_applic_k
3075 && cfg.funky_type != 2)
3076 || cfg.funky_type == 3 || cfg.nethack_keypad || compose_state) {
3077 if ((HIWORD(lParam) & KF_EXTENDED) == 0) {
3081 nParam = VK_NUMPAD0;
3084 nParam = VK_NUMPAD1;
3087 nParam = VK_NUMPAD2;
3090 nParam = VK_NUMPAD3;
3093 nParam = VK_NUMPAD4;
3096 nParam = VK_NUMPAD5;
3099 nParam = VK_NUMPAD6;
3102 nParam = VK_NUMPAD7;
3105 nParam = VK_NUMPAD8;
3108 nParam = VK_NUMPAD9;
3111 nParam = VK_DECIMAL;
3115 if (keystate[VK_NUMLOCK] & 1)
3122 /* If a key is pressed and AltGr is not active */
3123 if (key_down && (keystate[VK_RMENU] & 0x80) == 0 && !compose_state) {
3124 /* Okay, prepare for most alts then ... */
3128 /* Lets see if it's a pattern we know all about ... */
3129 if (wParam == VK_PRIOR && shift_state == 1) {
3130 SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0);
3133 if (wParam == VK_NEXT && shift_state == 1) {
3134 SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
3137 if (wParam == VK_INSERT && shift_state == 1) {
3141 if (left_alt && wParam == VK_F4 && cfg.alt_f4) {
3144 if (left_alt && wParam == VK_SPACE && cfg.alt_space) {
3145 SendMessage(hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0);
3148 if (left_alt && wParam == VK_RETURN && cfg.fullscreenonaltenter &&
3149 (cfg.resize_action != RESIZE_DISABLED)) {
3150 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) != KF_REPEAT)
3154 /* Control-Numlock for app-keypad mode switch */
3155 if (wParam == VK_PAUSE && shift_state == 2) {
3156 app_keypad_keys ^= 1;
3160 /* Nethack keypad */
3161 if (cfg.nethack_keypad && !left_alt) {
3164 *p++ = shift_state ? 'B' : 'b';
3167 *p++ = shift_state ? 'J' : 'j';
3170 *p++ = shift_state ? 'N' : 'n';
3173 *p++ = shift_state ? 'H' : 'h';
3176 *p++ = shift_state ? '.' : '.';
3179 *p++ = shift_state ? 'L' : 'l';
3182 *p++ = shift_state ? 'Y' : 'y';
3185 *p++ = shift_state ? 'K' : 'k';
3188 *p++ = shift_state ? 'U' : 'u';
3193 /* Application Keypad */
3197 if (cfg.funky_type == 3 ||
3198 (cfg.funky_type <= 1 &&
3199 app_keypad_keys && !cfg.no_applic_k)) switch (wParam) {
3213 if (app_keypad_keys && !cfg.no_applic_k)
3250 if (cfg.funky_type == 2) {
3255 } else if (shift_state)
3262 if (cfg.funky_type == 2)
3266 if (cfg.funky_type == 2)
3270 if (cfg.funky_type == 2)
3275 if (HIWORD(lParam) & KF_EXTENDED)
3281 if (xkey >= 'P' && xkey <= 'S')
3282 p += sprintf((char *) p, "\x1B%c", xkey);
3284 p += sprintf((char *) p, "\x1B?%c", xkey);
3286 p += sprintf((char *) p, "\x1BO%c", xkey);
3291 if (wParam == VK_BACK && shift_state == 0) { /* Backspace */
3292 *p++ = (cfg.bksp_is_delete ? 0x7F : 0x08);
3296 if (wParam == VK_TAB && shift_state == 1) { /* Shift tab */
3302 if (wParam == VK_SPACE && shift_state == 2) { /* Ctrl-Space */
3306 if (wParam == VK_SPACE && shift_state == 3) { /* Ctrl-Shift-Space */
3310 if (wParam == VK_CANCEL && shift_state == 2) { /* Ctrl-Break */
3315 if (wParam == VK_PAUSE) { /* Break/Pause */
3320 /* Control-2 to Control-8 are special */
3321 if (shift_state == 2 && wParam >= '2' && wParam <= '8') {
3322 *p++ = "\000\033\034\035\036\037\177"[wParam - '2'];
3325 if (shift_state == 2 && wParam == 0xBD) {
3329 if (shift_state == 2 && wParam == 0xDF) {
3333 if (shift_state == 0 && wParam == VK_RETURN && cr_lf_return) {
3340 * Next, all the keys that do tilde codes. (ESC '[' nn '~',
3341 * for integer decimal nn.)
3343 * We also deal with the weird ones here. Linux VCs replace F1
3344 * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
3345 * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
3351 code = (keystate[VK_SHIFT] & 0x80 ? 23 : 11);
3354 code = (keystate[VK_SHIFT] & 0x80 ? 24 : 12);
3357 code = (keystate[VK_SHIFT] & 0x80 ? 25 : 13);
3360 code = (keystate[VK_SHIFT] & 0x80 ? 26 : 14);
3363 code = (keystate[VK_SHIFT] & 0x80 ? 28 : 15);
3366 code = (keystate[VK_SHIFT] & 0x80 ? 29 : 17);
3369 code = (keystate[VK_SHIFT] & 0x80 ? 31 : 18);
3372 code = (keystate[VK_SHIFT] & 0x80 ? 32 : 19);
3375 code = (keystate[VK_SHIFT] & 0x80 ? 33 : 20);
3378 code = (keystate[VK_SHIFT] & 0x80 ? 34 : 21);
3411 if ((shift_state&2) == 0) switch (wParam) {
3431 /* Reorder edit keys to physical order */
3432 if (cfg.funky_type == 3 && code <= 6)
3433 code = "\0\2\1\4\5\3\6"[code];
3435 if (vt52_mode && code > 0 && code <= 6) {
3436 p += sprintf((char *) p, "\x1B%c", " HLMEIG"[code]);
3440 if (cfg.funky_type == 5 && /* SCO function keys */
3441 code >= 11 && code <= 34) {
3442 char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
3445 case VK_F1: index = 0; break;
3446 case VK_F2: index = 1; break;
3447 case VK_F3: index = 2; break;
3448 case VK_F4: index = 3; break;
3449 case VK_F5: index = 4; break;
3450 case VK_F6: index = 5; break;
3451 case VK_F7: index = 6; break;
3452 case VK_F8: index = 7; break;
3453 case VK_F9: index = 8; break;
3454 case VK_F10: index = 9; break;
3455 case VK_F11: index = 10; break;
3456 case VK_F12: index = 11; break;
3458 if (keystate[VK_SHIFT] & 0x80) index += 12;
3459 if (keystate[VK_CONTROL] & 0x80) index += 24;
3460 p += sprintf((char *) p, "\x1B[%c", codes[index]);
3463 if (cfg.funky_type == 5 && /* SCO small keypad */
3464 code >= 1 && code <= 6) {
3465 char codes[] = "HL.FIG";
3469 p += sprintf((char *) p, "\x1B[%c", codes[code-1]);
3473 if ((vt52_mode || cfg.funky_type == 4) && code >= 11 && code <= 24) {
3480 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11 - offt);
3483 sprintf((char *) p, "\x1BO%c", code + 'P' - 11 - offt);
3486 if (cfg.funky_type == 1 && code >= 11 && code <= 15) {
3487 p += sprintf((char *) p, "\x1B[[%c", code + 'A' - 11);
3490 if (cfg.funky_type == 2 && code >= 11 && code <= 14) {
3492 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11);
3494 p += sprintf((char *) p, "\x1BO%c", code + 'P' - 11);
3497 if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
3498 p += sprintf((char *) p, code == 1 ? "\x1B[H" : "\x1BOw");
3502 p += sprintf((char *) p, "\x1B[%d~", code);
3507 * Now the remaining keys (arrows and Keypad 5. Keypad 5 for
3508 * some reason seems to send VK_CLEAR to Windows...).
3531 p += sprintf((char *) p, "\x1B%c", xkey);
3533 int app_flg = (app_cursor_keys && !cfg.no_applic_c);
3536 * RDB: VT100 & VT102 manuals both state the
3537 * app cursor keys only work if the app keypad
3540 * SGT: That may well be true, but xterm
3541 * disagrees and so does at least one
3542 * application, so I've #if'ed this out and the
3543 * behaviour is back to PuTTY's original: app
3544 * cursor and app keypad are independently
3545 * switchable modes. If anyone complains about
3546 * _this_ I'll have to put in a configurable
3549 if (!app_keypad_keys)
3552 /* Useful mapping of Ctrl-arrows */
3553 if (shift_state == 2)
3557 p += sprintf((char *) p, "\x1BO%c", xkey);
3559 p += sprintf((char *) p, "\x1B[%c", xkey);
3566 * Finally, deal with Return ourselves. (Win95 seems to
3567 * foul it up when Alt is pressed, for some reason.)
3569 if (wParam == VK_RETURN) { /* Return */
3575 if (left_alt && wParam >= VK_NUMPAD0 && wParam <= VK_NUMPAD9)
3576 alt_sum = alt_sum * 10 + wParam - VK_NUMPAD0;
3581 /* Okay we've done everything interesting; let windows deal with
3582 * the boring stuff */
3586 /* helg: clear CAPS LOCK state if caps lock switches to cyrillic */
3587 if(cfg.xlat_capslockcyr && keystate[VK_CAPITAL] != 0) {
3589 keystate[VK_CAPITAL] = 0;
3592 r = ToAsciiEx(wParam, scan, keystate, keys, 0, kbd_layout);
3593 #ifdef SHOW_TOASCII_RESULT
3594 if (r == 1 && !key_down) {
3596 if (in_utf || dbcs_screenfont)
3597 debug((", (U+%04x)", alt_sum));
3599 debug((", LCH(%d)", alt_sum));
3601 debug((", ACH(%d)", keys[0]));
3606 for (r1 = 0; r1 < r; r1++) {
3607 debug(("%s%d", r1 ? "," : "", keys[r1]));
3616 * Interrupt an ongoing paste. I'm not sure this is
3617 * sensible, but for the moment it's preferable to
3618 * having to faff about buffering things.
3623 for (i = 0; i < r; i++) {
3624 unsigned char ch = (unsigned char) keys[i];
3626 if (compose_state == 2 && (ch & 0x80) == 0 && ch > ' ') {
3631 if (compose_state == 3 && (ch & 0x80) == 0 && ch > ' ') {
3635 if ((nc = check_compose(compose_char, ch)) == -1) {
3636 MessageBeep(MB_ICONHAND);
3640 luni_send(&keybuf, 1, 1);
3648 if (in_utf || dbcs_screenfont) {
3650 luni_send(&keybuf, 1, 1);
3652 ch = (char) alt_sum;
3654 * We need not bother about stdin
3655 * backlogs here, because in GUI PuTTY
3656 * we can't do anything about it
3657 * anyway; there's no means of asking
3658 * Windows to hold off on KEYDOWN
3659 * messages. We _have_ to buffer
3660 * everything we're sent.
3662 ldisc_send(&ch, 1, 1);
3666 lpage_send(kbd_codepage, &ch, 1, 1);
3668 if(capsOn && ch < 0x80) {
3671 cbuf[1] = xlat_uskbd2cyrllic(ch);
3672 luni_send(cbuf+!left_alt, 1+!!left_alt, 1);
3677 lpage_send(kbd_codepage, cbuf+!left_alt, 1+!!left_alt, 1);
3683 /* This is so the ALT-Numpad and dead keys work correctly. */
3688 /* If we're definitly not building up an ALT-54321 then clear it */
3691 /* If we will be using alt_sum fix the 256s */
3692 else if (keys[0] && (in_utf || dbcs_screenfont))
3697 * ALT alone may or may not want to bring up the System menu.
3698 * If it's not meant to, we return 0 on presses or releases of
3699 * ALT, to show that we've swallowed the keystroke. Otherwise
3700 * we return -1, which means Windows will give the keystroke
3701 * its default handling (i.e. bring up the System menu).
3703 if (wParam == VK_MENU && !cfg.alt_only)
3709 void set_title(char *title)
3712 window_name = smalloc(1 + strlen(title));
3713 strcpy(window_name, title);
3714 if (cfg.win_name_always || !IsIconic(hwnd))
3715 SetWindowText(hwnd, title);
3718 void set_icon(char *title)
3721 icon_name = smalloc(1 + strlen(title));
3722 strcpy(icon_name, title);
3723 if (!cfg.win_name_always && IsIconic(hwnd))
3724 SetWindowText(hwnd, title);
3727 void set_sbar(int total, int start, int page)
3731 if (is_full_screen() ? !cfg.scrollbar_in_fullscreen : !cfg.scrollbar)
3734 si.cbSize = sizeof(si);
3735 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
3737 si.nMax = total - 1;
3741 SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
3744 Context get_ctx(void)
3750 SelectPalette(hdc, pal, FALSE);
3756 void free_ctx(Context ctx)
3758 SelectPalette(ctx, GetStockObject(DEFAULT_PALETTE), FALSE);
3759 ReleaseDC(hwnd, ctx);
3762 static void real_palette_set(int n, int r, int g, int b)
3765 logpal->palPalEntry[n].peRed = r;
3766 logpal->palPalEntry[n].peGreen = g;
3767 logpal->palPalEntry[n].peBlue = b;
3768 logpal->palPalEntry[n].peFlags = PC_NOCOLLAPSE;
3769 colours[n] = PALETTERGB(r, g, b);
3770 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3772 colours[n] = RGB(r, g, b);
3775 void palette_set(int n, int r, int g, int b)
3777 static const int first[21] = {
3778 0, 2, 4, 6, 8, 10, 12, 14,
3779 1, 3, 5, 7, 9, 11, 13, 15,
3782 real_palette_set(first[n], r, g, b);
3784 real_palette_set(first[n] + 1, r, g, b);
3786 HDC hdc = get_ctx();
3787 UnrealizeObject(pal);
3788 RealizePalette(hdc);
3793 void palette_reset(void)
3797 for (i = 0; i < NCOLOURS; i++) {
3799 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
3800 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
3801 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
3802 logpal->palPalEntry[i].peFlags = 0;
3803 colours[i] = PALETTERGB(defpal[i].rgbtRed,
3804 defpal[i].rgbtGreen,
3805 defpal[i].rgbtBlue);
3807 colours[i] = RGB(defpal[i].rgbtRed,
3808 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
3813 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3815 RealizePalette(hdc);
3820 void write_aclip(char *data, int len, int must_deselect)
3825 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len + 1);
3828 lock = GlobalLock(clipdata);
3831 memcpy(lock, data, len);
3832 ((unsigned char *) lock)[len] = 0;
3833 GlobalUnlock(clipdata);
3836 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
3838 if (OpenClipboard(hwnd)) {
3840 SetClipboardData(CF_TEXT, clipdata);
3843 GlobalFree(clipdata);
3846 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
3850 * Note: unlike write_aclip() this will not append a nul.
3852 void write_clip(wchar_t * data, int len, int must_deselect)
3854 HGLOBAL clipdata, clipdata2, clipdata3;
3856 void *lock, *lock2, *lock3;
3858 len2 = WideCharToMultiByte(CP_ACP, 0, data, len, 0, 0, NULL, NULL);
3860 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE,
3861 len * sizeof(wchar_t));
3862 clipdata2 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len2);
3864 if (!clipdata || !clipdata2) {
3866 GlobalFree(clipdata);
3868 GlobalFree(clipdata2);
3871 if (!(lock = GlobalLock(clipdata)))
3873 if (!(lock2 = GlobalLock(clipdata2)))
3876 memcpy(lock, data, len * sizeof(wchar_t));
3877 WideCharToMultiByte(CP_ACP, 0, data, len, lock2, len2, NULL, NULL);
3879 if (cfg.rtf_paste) {
3880 wchar_t unitab[256];
3882 unsigned char *tdata = (unsigned char *)lock2;
3883 wchar_t *udata = (wchar_t *)lock;
3884 int rtflen = 0, uindex = 0, tindex = 0;
3886 int multilen, blen, alen, totallen, i;
3887 char before[16], after[4];
3889 get_unitab(CP_ACP, unitab, 0);
3891 rtfsize = 100 + strlen(cfg.font);
3892 rtf = smalloc(rtfsize);
3893 sprintf(rtf, "{\\rtf1\\ansi%d{\\fonttbl\\f0\\fmodern %s;}\\f0",
3894 GetACP(), cfg.font);
3895 rtflen = strlen(rtf);
3898 * We want to construct a piece of RTF that specifies the
3899 * same Unicode text. To do this we will read back in
3900 * parallel from the Unicode data in `udata' and the
3901 * non-Unicode data in `tdata'. For each character in
3902 * `tdata' which becomes the right thing in `udata' when
3903 * looked up in `unitab', we just copy straight over from
3904 * tdata. For each one that doesn't, we must WCToMB it
3905 * individually and produce a \u escape sequence.
3907 * It would probably be more robust to just bite the bullet
3908 * and WCToMB each individual Unicode character one by one,
3909 * then MBToWC each one back to see if it was an accurate
3910 * translation; but that strikes me as a horrifying number
3911 * of Windows API calls so I want to see if this faster way
3912 * will work. If it screws up badly we can always revert to
3913 * the simple and slow way.
3915 while (tindex < len2 && uindex < len &&
3916 tdata[tindex] && udata[uindex]) {
3917 if (tindex + 1 < len2 &&
3918 tdata[tindex] == '\r' &&
3919 tdata[tindex+1] == '\n') {
3923 if (unitab[tdata[tindex]] == udata[uindex]) {
3929 multilen = WideCharToMultiByte(CP_ACP, 0, unitab+uindex, 1,
3930 NULL, 0, NULL, NULL);
3931 if (multilen != 1) {
3932 blen = sprintf(before, "{\\uc%d\\u%d", multilen,
3934 alen = 1; strcpy(after, "}");
3936 blen = sprintf(before, "\\u%d", udata[uindex]);
3937 alen = 0; after[0] = '\0';
3940 assert(tindex + multilen <= len2);
3941 totallen = blen + alen;
3942 for (i = 0; i < multilen; i++) {
3943 if (tdata[tindex+i] == '\\' ||
3944 tdata[tindex+i] == '{' ||
3945 tdata[tindex+i] == '}')
3947 else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A)
3948 totallen += 6; /* \par\r\n */
3949 else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20)
3955 if (rtfsize < rtflen + totallen + 3) {
3956 rtfsize = rtflen + totallen + 512;
3957 rtf = srealloc(rtf, rtfsize);
3960 strcpy(rtf + rtflen, before); rtflen += blen;
3961 for (i = 0; i < multilen; i++) {
3962 if (tdata[tindex+i] == '\\' ||
3963 tdata[tindex+i] == '{' ||
3964 tdata[tindex+i] == '}') {
3965 rtf[rtflen++] = '\\';
3966 rtf[rtflen++] = tdata[tindex+i];
3967 } else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A) {
3968 rtflen += sprintf(rtf+rtflen, "\\par\r\n");
3969 } else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20) {
3970 rtflen += sprintf(rtf+rtflen, "\\'%02x", tdata[tindex+i]);
3972 rtf[rtflen++] = tdata[tindex+i];
3975 strcpy(rtf + rtflen, after); rtflen += alen;
3981 strcpy(rtf + rtflen, "}");
3984 clipdata3 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, rtflen);
3985 if (clipdata3 && (lock3 = GlobalLock(clipdata3)) != NULL) {
3987 GlobalUnlock(clipdata3);
3993 GlobalUnlock(clipdata);
3994 GlobalUnlock(clipdata2);
3997 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
3999 if (OpenClipboard(hwnd)) {
4001 SetClipboardData(CF_UNICODETEXT, clipdata);
4002 SetClipboardData(CF_TEXT, clipdata2);
4004 SetClipboardData(RegisterClipboardFormat(CF_RTF), clipdata3);
4007 GlobalFree(clipdata);
4008 GlobalFree(clipdata2);
4012 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
4015 void get_clip(wchar_t ** p, int *len)
4017 static HGLOBAL clipdata = NULL;
4018 static wchar_t *converted = 0;
4027 GlobalUnlock(clipdata);
4030 } else if (OpenClipboard(NULL)) {
4031 if ((clipdata = GetClipboardData(CF_UNICODETEXT))) {
4033 *p = GlobalLock(clipdata);
4035 for (p2 = *p; *p2; p2++);
4039 } else if ( (clipdata = GetClipboardData(CF_TEXT)) ) {
4043 s = GlobalLock(clipdata);
4044 i = MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, 0, 0);
4045 *p = converted = smalloc(i * sizeof(wchar_t));
4046 MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, converted, i);
4059 * Move `lines' lines from position `from' to position `to' in the
4062 void optimised_move(int to, int from, int lines)
4067 min = (to < from ? to : from);
4068 max = to + from - min;
4070 r.left = offset_width;
4071 r.right = offset_width + cols * font_width;
4072 r.top = offset_height + min * font_height;
4073 r.bottom = offset_height + (max + lines) * font_height;
4074 ScrollWindow(hwnd, 0, (to - from) * font_height, &r, &r);
4079 * Print a message box and perform a fatal exit.
4081 void fatalbox(char *fmt, ...)
4087 vsprintf(stuff, fmt, ap);
4089 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
4094 * Manage window caption / taskbar flashing, if enabled.
4095 * 0 = stop, 1 = maintain, 2 = start
4097 static void flash_window(int mode)
4099 static long last_flash = 0;
4100 static int flashing = 0;
4101 if ((mode == 0) || (cfg.beep_ind == B_IND_DISABLED)) {
4104 FlashWindow(hwnd, FALSE);
4108 } else if (mode == 2) {
4111 last_flash = GetTickCount();
4113 FlashWindow(hwnd, TRUE);
4116 } else if ((mode == 1) && (cfg.beep_ind == B_IND_FLASH)) {
4119 long now = GetTickCount();
4120 long fdiff = now - last_flash;
4121 if (fdiff < 0 || fdiff > 450) {
4123 FlashWindow(hwnd, TRUE); /* toggle */
4134 if (mode == BELL_DEFAULT) {
4136 * For MessageBeep style bells, we want to be careful of
4137 * timing, because they don't have the nice property of
4138 * PlaySound bells that each one cancels the previous
4139 * active one. So we limit the rate to one per 50ms or so.
4141 static long lastbeep = 0;
4144 beepdiff = GetTickCount() - lastbeep;
4145 if (beepdiff >= 0 && beepdiff < 50)
4149 * The above MessageBeep call takes time, so we record the
4150 * time _after_ it finishes rather than before it starts.
4152 lastbeep = GetTickCount();
4153 } else if (mode == BELL_WAVEFILE) {
4154 if (!PlaySound(cfg.bell_wavefile, NULL, SND_ASYNC | SND_FILENAME)) {
4155 char buf[sizeof(cfg.bell_wavefile) + 80];
4156 sprintf(buf, "Unable to play sound file\n%s\n"
4157 "Using default sound instead", cfg.bell_wavefile);
4158 MessageBox(hwnd, buf, "PuTTY Sound Error",
4159 MB_OK | MB_ICONEXCLAMATION);
4160 cfg.beep = BELL_DEFAULT;
4163 /* Otherwise, either visual bell or disabled; do nothing here */
4165 flash_window(2); /* start */
4170 * Minimise or restore the window in response to a server-side
4173 void set_iconic(int iconic)
4175 if (IsIconic(hwnd)) {
4177 ShowWindow(hwnd, SW_RESTORE);
4180 ShowWindow(hwnd, SW_MINIMIZE);
4185 * Move the window in response to a server-side request.
4187 void move_window(int x, int y)
4189 SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
4193 * Move the window to the top or bottom of the z-order in response
4194 * to a server-side request.
4196 void set_zorder(int top)
4198 if (cfg.alwaysontop)
4199 return; /* ignore */
4200 SetWindowPos(hwnd, top ? HWND_TOP : HWND_BOTTOM, 0, 0, 0, 0,
4201 SWP_NOMOVE | SWP_NOSIZE);
4205 * Refresh the window in response to a server-side request.
4207 void refresh_window(void)
4209 InvalidateRect(hwnd, NULL, TRUE);
4213 * Maximise or restore the window in response to a server-side
4216 void set_zoomed(int zoomed)
4218 if (IsZoomed(hwnd)) {
4220 ShowWindow(hwnd, SW_RESTORE);
4223 ShowWindow(hwnd, SW_MAXIMIZE);
4228 * Report whether the window is iconic, for terminal reports.
4232 return IsIconic(hwnd);
4236 * Report the window's position, for terminal reports.
4238 void get_window_pos(int *x, int *y)
4241 GetWindowRect(hwnd, &r);
4247 * Report the window's pixel size, for terminal reports.
4249 void get_window_pixels(int *x, int *y)
4252 GetWindowRect(hwnd, &r);
4253 *x = r.right - r.left;
4254 *y = r.bottom - r.top;
4258 * Return the window or icon title.
4260 char *get_window_title(int icon)
4262 return icon ? icon_name : window_name;
4266 * See if we're in full-screen mode.
4268 int is_full_screen()
4270 if (!IsZoomed(hwnd))
4272 if (GetWindowLong(hwnd, GWL_STYLE) & WS_CAPTION)
4278 * Go full-screen. This should only be called when we are already
4281 void make_full_screen()
4286 assert(IsZoomed(hwnd));
4288 /* Remove the window furniture. */
4289 style = GetWindowLong(hwnd, GWL_STYLE);
4290 style &= ~(WS_CAPTION | WS_BORDER | WS_THICKFRAME);
4291 if (cfg.scrollbar_in_fullscreen)
4292 style |= WS_VSCROLL;
4294 style &= ~WS_VSCROLL;
4295 SetWindowLong(hwnd, GWL_STYLE, style);
4297 /* Resize ourselves to exactly cover the nearest monitor. */
4298 #ifdef MONITOR_DEFAULTTONEAREST
4302 mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
4303 mi.cbSize = sizeof(mi);
4304 GetMonitorInfo(mon, &mi);
4305 x = mi.rcMonitor.left;
4306 y = mi.rcMonitor.top;
4307 w = mi.rcMonitor.right;
4308 h = mi.rcMonitor.bottom;
4312 w = GetSystemMetrics(SM_CXSCREEN);
4313 h = GetSystemMetrics(SM_CYSCREEN);
4315 SetWindowPos(hwnd, HWND_TOP, x, y, w, h, SWP_FRAMECHANGED);
4317 /* Tick the menu item in the System menu. */
4318 CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN,
4323 * Clear the full-screen attributes.
4325 void clear_full_screen()
4327 DWORD oldstyle, style;
4329 /* Reinstate the window furniture. */
4330 style = oldstyle = GetWindowLong(hwnd, GWL_STYLE);
4331 style |= WS_CAPTION | WS_BORDER;
4332 if (cfg.resize_action == RESIZE_DISABLED)
4333 style &= ~WS_THICKFRAME;
4335 style |= WS_THICKFRAME;
4337 style |= WS_VSCROLL;
4339 style &= ~WS_VSCROLL;
4340 if (style != oldstyle) {
4341 SetWindowLong(hwnd, GWL_STYLE, style);
4342 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
4343 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
4347 /* Untick the menu item in the System menu. */
4348 CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN,
4353 * Toggle full-screen mode.
4355 void flip_full_screen()
4357 if (is_full_screen()) {
4358 ShowWindow(hwnd, SW_RESTORE);
4359 } else if (IsZoomed(hwnd)) {
4362 SendMessage(hwnd, WM_FULLSCR_ON_MAX, 0, 0);
4363 ShowWindow(hwnd, SW_MAXIMIZE);