16 #define COMPILE_MULTIMON_STUBS
27 #define PUTTY_DO_GLOBALS /* actually _define_ globals */
33 #define IDM_SHOWLOG 0x0010
34 #define IDM_NEWSESS 0x0020
35 #define IDM_DUPSESS 0x0030
36 #define IDM_RECONF 0x0040
37 #define IDM_CLRSB 0x0050
38 #define IDM_RESET 0x0060
39 #define IDM_TEL_AYT 0x0070
40 #define IDM_TEL_BRK 0x0080
41 #define IDM_TEL_SYNCH 0x0090
42 #define IDM_TEL_EC 0x00a0
43 #define IDM_TEL_EL 0x00b0
44 #define IDM_TEL_GA 0x00c0
45 #define IDM_TEL_NOP 0x00d0
46 #define IDM_TEL_ABORT 0x00e0
47 #define IDM_TEL_AO 0x00f0
48 #define IDM_TEL_IP 0x0100
49 #define IDM_TEL_SUSP 0x0110
50 #define IDM_TEL_EOR 0x0120
51 #define IDM_TEL_EOF 0x0130
52 #define IDM_HELP 0x0140
53 #define IDM_ABOUT 0x0150
54 #define IDM_SAVEDSESS 0x0160
55 #define IDM_COPYALL 0x0170
56 #define IDM_FULLSCREEN 0x0180
58 #define IDM_SESSLGP 0x0250 /* log type printable */
59 #define IDM_SESSLGA 0x0260 /* log type all chars */
60 #define IDM_SESSLGE 0x0270 /* log end */
61 #define IDM_SAVED_MIN 0x1000
62 #define IDM_SAVED_MAX 0x2000
64 #define WM_IGNORE_CLIP (WM_XUSER + 2)
65 #define WM_FULLSCR_ON_MAX (WM_XUSER + 3)
67 /* Needed for Chinese support and apparently not always defined. */
69 #define VK_PROCESSKEY 0xE5
72 /* Mouse wheel support. */
74 #define WM_MOUSEWHEEL 0x020A /* not defined in earlier SDKs */
77 #define WHEEL_DELTA 120
80 static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
81 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
82 unsigned char *output);
83 static void cfgtopalette(void);
84 static void init_palette(void);
85 static void init_fonts(int, int);
86 static void another_font(int);
87 static void deinit_fonts(void);
88 static void set_input_locale(HKL);
89 static int do_mouse_wheel_msg(UINT message, WPARAM wParam, LPARAM lParam);
91 static int is_full_screen(void);
92 static void make_full_screen(void);
93 static void clear_full_screen(void);
94 static void flip_full_screen(void);
96 /* Window layout information */
97 static void reset_window(int);
98 static int extra_width, extra_height;
99 static int font_width, font_height, font_dualwidth;
100 static int offset_width, offset_height;
101 static int was_zoomed = 0;
102 static int prev_rows, prev_cols;
104 static int pending_netevent = 0;
105 static WPARAM pend_netevent_wParam = 0;
106 static LPARAM pend_netevent_lParam = 0;
107 static void enact_pending_netevent(void);
108 static void flash_window(int mode);
109 static void sys_cursor_update(void);
110 static int get_fullscreen_rect(RECT * ss);
112 static time_t last_movement = 0;
114 static int caret_x = -1, caret_y = -1;
116 #define FONT_NORMAL 0
118 #define FONT_UNDERLINE 2
119 #define FONT_BOLDUND 3
120 #define FONT_WIDE 0x04
121 #define FONT_HIGH 0x08
122 #define FONT_NARROW 0x10
124 #define FONT_OEM 0x20
125 #define FONT_OEMBOLD 0x21
126 #define FONT_OEMUND 0x22
127 #define FONT_OEMBOLDUND 0x23
129 #define FONT_MAXNO 0x2F
131 static HFONT fonts[FONT_MAXNO];
132 static LOGFONT lfont;
133 static int fontflag[FONT_MAXNO];
135 BOLD_COLOURS, BOLD_SHADOW, BOLD_FONT
143 static COLORREF colours[NCOLOURS];
145 static LPLOGPALETTE logpal;
146 static RGBTRIPLE defpal[NCOLOURS];
150 static HBITMAP caretbm;
152 static int dbltime, lasttime, lastact;
153 static Mouse_Button lastbtn;
155 /* this allows xterm-style mouse handling. */
156 static int send_raw_mouse = 0;
157 static int wheel_accumulator = 0;
159 static char *window_name, *icon_name;
161 static int compose_state = 0;
163 static int wsa_started = 0;
165 static OSVERSIONINFO osVersion;
167 static UINT wm_mousewheel = WM_MOUSEWHEEL;
169 /* Dummy routine, only required in plink. */
170 void ldisc_update(int echo, int edit)
174 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
176 static char appname[] = "PuTTY";
181 int guess_width, guess_height;
184 flags = FLAG_VERBOSE | FLAG_INTERACTIVE;
186 winsock_ver = MAKEWORD(1, 1);
187 if (WSAStartup(winsock_ver, &wsadata)) {
188 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
189 MB_OK | MB_ICONEXCLAMATION);
192 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
193 MessageBox(NULL, "WinSock version is incompatible with 1.1",
194 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
199 /* WISHLIST: maybe allow config tweaking even if winsock not present? */
202 InitCommonControls();
204 /* Ensure a Maximize setting in Explorer doesn't maximise the
209 ZeroMemory(&osVersion, sizeof(osVersion));
210 osVersion.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
211 if (!GetVersionEx ( (OSVERSIONINFO *) &osVersion)) {
212 MessageBox(NULL, "Windows refuses to report a version",
213 "PuTTY Fatal Error", MB_OK | MB_ICONEXCLAMATION);
219 * If we're running a version of Windows that doesn't support
220 * WM_MOUSEWHEEL, find out what message number we should be
223 if (osVersion.dwMajorVersion < 4 ||
224 (osVersion.dwMajorVersion == 4 &&
225 osVersion.dwPlatformId != VER_PLATFORM_WIN32_NT))
226 wm_mousewheel = RegisterWindowMessage("MSWHEEL_ROLLMSG");
229 * See if we can find our Help file.
232 char b[2048], *p, *q, *r;
234 GetModuleFileName(NULL, b, sizeof(b) - 1);
236 p = strrchr(b, '\\');
237 if (p && p >= r) r = p+1;
239 if (q && q >= r) r = q+1;
240 strcpy(r, "putty.hlp");
241 if ( (fp = fopen(b, "r")) != NULL) {
242 help_path = dupstr(b);
246 strcpy(r, "putty.cnt");
247 if ( (fp = fopen(b, "r")) != NULL) {
248 help_has_contents = TRUE;
251 help_has_contents = FALSE;
255 * Process the command line.
261 default_protocol = DEFAULT_PROTOCOL;
262 default_port = DEFAULT_PORT;
263 cfg.logtype = LGTYP_NONE;
265 do_defaults(NULL, &cfg);
270 * Process a couple of command-line options which are more
271 * easily dealt with before the line is broken up into
272 * words. These are the soon-to-be-defunct @sessionname and
273 * the internal-use-only &sharedmemoryhandle, neither of
274 * which are combined with anything else.
276 while (*p && isspace(*p))
280 while (i > 1 && isspace(p[i - 1]))
283 do_defaults(p + 1, &cfg);
284 if (!*cfg.host && !do_config()) {
288 } else if (*p == '&') {
290 * An initial & means we've been given a command line
291 * containing the hex value of a HANDLE for a file
292 * mapping object, which we must then extract as a
297 if (sscanf(p + 1, "%p", &filemap) == 1 &&
298 (cp = MapViewOfFile(filemap, FILE_MAP_READ,
299 0, 0, sizeof(Config))) != NULL) {
302 CloseHandle(filemap);
303 } else if (!do_config()) {
309 * Otherwise, break up the command line and deal with
315 split_into_argv(cmdline, &argc, &argv, NULL);
317 for (i = 0; i < argc; i++) {
321 ret = cmdline_process_param(p, i+1<argc?argv[i+1]:NULL, 1);
323 cmdline_error("option \"%s\" requires an argument", p);
324 } else if (ret == 2) {
325 i++; /* skip next argument */
326 } else if (ret == 1) {
327 continue; /* nothing further needs doing */
328 } else if (!strcmp(p, "-cleanup")) {
330 * `putty -cleanup'. Remove all registry
331 * entries associated with PuTTY, and also find
332 * and delete the random seed file.
335 "This procedure will remove ALL Registry\n"
336 "entries associated with PuTTY, and will\n"
337 "also remove the PuTTY random seed file.\n"
339 "THIS PROCESS WILL DESTROY YOUR SAVED\n"
340 "SESSIONS. Are you really sure you want\n"
343 MB_YESNO | MB_ICONWARNING) == IDYES) {
347 } else if (*p != '-') {
351 * If we already have a host name, treat
352 * this argument as a port number. NB we
353 * have to treat this as a saved -P
354 * argument, so that it will be deferred
355 * until it's a good moment to run it.
357 int ret = cmdline_process_param("-P", p, 1);
359 } else if (!strncmp(q, "telnet:", 7)) {
361 * If the hostname starts with "telnet:",
362 * set the protocol to Telnet and process
363 * the string as a Telnet URL.
368 if (q[0] == '/' && q[1] == '/')
370 cfg.protocol = PROT_TELNET;
372 while (*p && *p != ':' && *p != '/')
381 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
382 cfg.host[sizeof(cfg.host) - 1] = '\0';
386 * Otherwise, treat this argument as a host
389 while (*p && !isspace(*p))
393 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
394 cfg.host[sizeof(cfg.host) - 1] = '\0';
403 if (!*cfg.host && !do_config()) {
409 * Trim leading whitespace off the hostname if it's there.
412 int space = strspn(cfg.host, " \t");
413 memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
416 /* See if host is of the form user@host */
417 if (cfg.host[0] != '\0') {
418 char *atsign = strchr(cfg.host, '@');
419 /* Make sure we're not overflowing the user field */
421 if (atsign - cfg.host < sizeof cfg.username) {
422 strncpy(cfg.username, cfg.host, atsign - cfg.host);
423 cfg.username[atsign - cfg.host] = '\0';
425 memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
430 * Trim a colon suffix off the hostname if it's there.
432 cfg.host[strcspn(cfg.host, ":")] = '\0';
436 * Select protocol. This is farmed out into a table in a
437 * separate file to enable an ssh-free variant.
442 for (i = 0; backends[i].backend != NULL; i++)
443 if (backends[i].protocol == cfg.protocol) {
444 back = backends[i].backend;
448 MessageBox(NULL, "Unsupported protocol number found",
449 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
455 /* Check for invalid Port number (i.e. zero) */
457 MessageBox(NULL, "Invalid Port Number",
458 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
465 wndclass.lpfnWndProc = WndProc;
466 wndclass.cbClsExtra = 0;
467 wndclass.cbWndExtra = 0;
468 wndclass.hInstance = inst;
469 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
470 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
471 wndclass.hbrBackground = NULL;
472 wndclass.lpszMenuName = NULL;
473 wndclass.lpszClassName = appname;
475 RegisterClass(&wndclass);
480 savelines = cfg.savelines;
486 * Guess some defaults for the window size. This all gets
487 * updated later, so we don't really care too much. However, we
488 * do want the font width/height guesses to correspond to a
489 * large font rather than a small one...
496 term_size(cfg.height, cfg.width, cfg.savelines);
497 guess_width = extra_width + font_width * cols;
498 guess_height = extra_height + font_height * rows;
501 get_fullscreen_rect(&r);
502 if (guess_width > r.right - r.left)
503 guess_width = r.right - r.left;
504 if (guess_height > r.bottom - r.top)
505 guess_height = r.bottom - r.top;
509 int winmode = WS_OVERLAPPEDWINDOW | WS_VSCROLL;
512 winmode &= ~(WS_VSCROLL);
513 if (cfg.resize_action == RESIZE_DISABLED)
514 winmode &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
516 exwinmode |= WS_EX_TOPMOST;
518 exwinmode |= WS_EX_CLIENTEDGE;
519 hwnd = CreateWindowEx(exwinmode, appname, appname,
520 winmode, CW_USEDEFAULT, CW_USEDEFAULT,
521 guess_width, guess_height,
522 NULL, NULL, inst, NULL);
526 * Initialise the fonts, simultaneously correcting the guesses
527 * for font_{width,height}.
532 * Correct the guesses for extra_{width,height}.
536 GetWindowRect(hwnd, &wr);
537 GetClientRect(hwnd, &cr);
538 offset_width = offset_height = cfg.window_border;
539 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
540 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
544 * Resize the window, now we know what size we _really_ want it
547 guess_width = extra_width + font_width * cols;
548 guess_height = extra_height + font_height * rows;
549 SetWindowPos(hwnd, NULL, 0, 0, guess_width, guess_height,
550 SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
553 * Set up a caret bitmap, with no content.
557 int size = (font_width + 15) / 16 * 2 * font_height;
558 bits = smalloc(size);
559 memset(bits, 0, size);
560 caretbm = CreateBitmap(font_width, font_height, 1, 1, bits);
563 CreateCaret(hwnd, caretbm, font_width, font_height);
566 * Initialise the scroll bar.
571 si.cbSize = sizeof(si);
572 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
577 SetScrollInfo(hwnd, SB_VERT, &si, FALSE);
581 * Start up the telnet connection.
585 char msg[1024], *title;
588 error = back->init(cfg.host, cfg.port, &realhost, cfg.tcp_nodelay);
590 sprintf(msg, "Unable to open connection to\n"
591 "%.800s\n" "%s", cfg.host, error);
592 MessageBox(NULL, msg, "PuTTY Error", MB_ICONERROR | MB_OK);
595 window_name = icon_name = NULL;
597 title = cfg.wintitle;
599 sprintf(msg, "%s - PuTTY", realhost);
607 session_closed = FALSE;
610 * Prepare the mouse handler.
612 lastact = MA_NOTHING;
613 lastbtn = MBT_NOTHING;
614 dbltime = GetDoubleClickTime();
617 * Set up the session-control options on the system menu.
620 HMENU m = GetSystemMenu(hwnd, FALSE);
624 AppendMenu(m, MF_SEPARATOR, 0, 0);
625 if (cfg.protocol == PROT_TELNET) {
627 AppendMenu(p, MF_ENABLED, IDM_TEL_AYT, "Are You There");
628 AppendMenu(p, MF_ENABLED, IDM_TEL_BRK, "Break");
629 AppendMenu(p, MF_ENABLED, IDM_TEL_SYNCH, "Synch");
630 AppendMenu(p, MF_SEPARATOR, 0, 0);
631 AppendMenu(p, MF_ENABLED, IDM_TEL_EC, "Erase Character");
632 AppendMenu(p, MF_ENABLED, IDM_TEL_EL, "Erase Line");
633 AppendMenu(p, MF_ENABLED, IDM_TEL_GA, "Go Ahead");
634 AppendMenu(p, MF_ENABLED, IDM_TEL_NOP, "No Operation");
635 AppendMenu(p, MF_SEPARATOR, 0, 0);
636 AppendMenu(p, MF_ENABLED, IDM_TEL_ABORT, "Abort Process");
637 AppendMenu(p, MF_ENABLED, IDM_TEL_AO, "Abort Output");
638 AppendMenu(p, MF_ENABLED, IDM_TEL_IP, "Interrupt Process");
639 AppendMenu(p, MF_ENABLED, IDM_TEL_SUSP, "Suspend Process");
640 AppendMenu(p, MF_SEPARATOR, 0, 0);
641 AppendMenu(p, MF_ENABLED, IDM_TEL_EOR, "End Of Record");
642 AppendMenu(p, MF_ENABLED, IDM_TEL_EOF, "End Of File");
643 AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) p,
645 AppendMenu(m, MF_SEPARATOR, 0, 0);
647 AppendMenu(m, MF_ENABLED, IDM_SHOWLOG, "&Event Log");
648 AppendMenu(m, MF_SEPARATOR, 0, 0);
649 AppendMenu(m, MF_ENABLED, IDM_NEWSESS, "Ne&w Session...");
650 AppendMenu(m, MF_ENABLED, IDM_DUPSESS, "&Duplicate Session");
653 for (i = 1; i < ((nsessions < 256) ? nsessions : 256); i++)
654 AppendMenu(s, MF_ENABLED, IDM_SAVED_MIN + (16 * i),
656 AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) s, "Sa&ved Sessions");
657 AppendMenu(m, MF_ENABLED, IDM_RECONF, "Chan&ge Settings...");
658 AppendMenu(m, MF_SEPARATOR, 0, 0);
659 AppendMenu(m, MF_ENABLED, IDM_COPYALL, "C&opy All to Clipboard");
660 AppendMenu(m, MF_ENABLED, IDM_CLRSB, "C&lear Scrollback");
661 AppendMenu(m, MF_ENABLED, IDM_RESET, "Rese&t Terminal");
662 AppendMenu(m, MF_SEPARATOR, 0, 0);
663 AppendMenu(m, (cfg.resize_action == RESIZE_DISABLED) ?
664 MF_GRAYED : MF_ENABLED, IDM_FULLSCREEN, "&Full Screen");
665 AppendMenu(m, MF_SEPARATOR, 0, 0);
667 AppendMenu(m, MF_ENABLED, IDM_HELP, "&Help");
668 AppendMenu(m, MF_ENABLED, IDM_ABOUT, "&About PuTTY");
672 * Set up the initial input locale.
674 set_input_locale(GetKeyboardLayout(0));
677 * Open the initial log file if there is one.
682 * Finally show the window!
684 ShowWindow(hwnd, show);
685 SetForegroundWindow(hwnd);
688 * Set the palette up.
694 has_focus = (GetForegroundWindow() == hwnd);
697 if (GetMessage(&msg, NULL, 0, 0) == 1) {
698 int timer_id = 0, long_timer = 0;
700 while (msg.message != WM_QUIT) {
701 /* Sometimes DispatchMessage calls routines that use their own
702 * GetMessage loop, setup this timer so we get some control back.
704 * Also call term_update() from the timer so that if the host
705 * is sending data flat out we still do redraws.
707 if (timer_id && long_timer) {
708 KillTimer(hwnd, timer_id);
709 long_timer = timer_id = 0;
712 timer_id = SetTimer(hwnd, 1, 20, NULL);
713 if (!(IsWindow(logbox) && IsDialogMessage(logbox, &msg)))
714 DispatchMessage(&msg);
716 /* Make sure we blink everything that needs it. */
719 /* Send the paste buffer if there's anything to send */
722 /* If there's nothing new in the queue then we can do everything
723 * we've delayed, reading the socket, writing, and repainting
726 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
729 if (pending_netevent) {
730 enact_pending_netevent();
732 /* Force the cursor blink on */
735 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
739 /* Okay there is now nothing to do so we make sure the screen is
740 * completely up to date then tell windows to call us in a little
744 KillTimer(hwnd, timer_id);
748 if (GetCapture() != hwnd ||
749 (send_raw_mouse && !(cfg.mouse_override && is_shift_pressed())))
754 flash_window(1); /* maintain */
756 /* The messages seem unreliable; especially if we're being tricky */
757 has_focus = (GetForegroundWindow() == hwnd);
760 /* Hmm, term_update didn't want to do an update too soon ... */
761 timer_id = SetTimer(hwnd, 1, 50, NULL);
763 timer_id = SetTimer(hwnd, 1, 500, NULL);
765 timer_id = SetTimer(hwnd, 1, 100, NULL);
768 /* There's no point rescanning everything in the message queue
769 * so we do an apparently unnecessary wait here
772 if (GetMessage(&msg, NULL, 0, 0) != 1)
777 cleanup_exit(msg.wParam); /* this doesn't return... */
778 return msg.wParam; /* ... but optimiser doesn't know */
784 void cleanup_exit(int code)
797 if (cfg.protocol == PROT_SSH) {
808 * Set up, or shut down, an AsyncSelect. Called from winnet.c.
810 char *do_select(SOCKET skt, int startup)
815 events = (FD_CONNECT | FD_READ | FD_WRITE |
816 FD_OOB | FD_CLOSE | FD_ACCEPT);
821 return "do_select(): internal error (hwnd==NULL)";
822 if (WSAAsyncSelect(skt, hwnd, msg, events) == SOCKET_ERROR) {
823 switch (WSAGetLastError()) {
825 return "Network is down";
827 return "WSAAsyncSelect(): unknown error";
834 * set or clear the "raw mouse message" mode
836 void set_raw_mouse_mode(int activate)
838 activate = activate && !cfg.no_mouse_rep;
839 send_raw_mouse = activate;
840 SetCursor(LoadCursor(NULL, activate ? IDC_ARROW : IDC_IBEAM));
844 * Print a message box and close the connection.
846 void connection_fatal(char *fmt, ...)
852 vsprintf(stuff, fmt, ap);
854 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
855 if (cfg.close_on_exit == COE_ALWAYS)
858 session_closed = TRUE;
859 SetWindowText(hwnd, "PuTTY (inactive)");
864 * Report an error at the command-line parsing stage.
866 void cmdline_error(char *fmt, ...)
872 vsprintf(stuff, fmt, ap);
874 MessageBox(hwnd, stuff, "PuTTY Command Line Error", MB_ICONERROR | MB_OK);
879 * Actually do the job requested by a WM_NETEVENT
881 static void enact_pending_netevent(void)
883 static int reentering = 0;
884 extern int select_result(WPARAM, LPARAM);
888 return; /* don't unpend the pending */
890 pending_netevent = FALSE;
893 ret = select_result(pend_netevent_wParam, pend_netevent_lParam);
896 if (ret == 0 && !session_closed) {
897 /* Abnormal exits will already have set session_closed and taken
898 * appropriate action. */
899 if (cfg.close_on_exit == COE_ALWAYS ||
900 cfg.close_on_exit == COE_NORMAL) PostQuitMessage(0);
902 session_closed = TRUE;
903 SetWindowText(hwnd, "PuTTY (inactive)");
904 MessageBox(hwnd, "Connection closed by remote host",
905 "PuTTY", MB_OK | MB_ICONINFORMATION);
911 * Copy the colour palette from the configuration data into defpal.
912 * This is non-trivial because the colour indices are different.
914 static void cfgtopalette(void)
917 static const int ww[] = {
918 6, 7, 8, 9, 10, 11, 12, 13,
919 14, 15, 16, 17, 18, 19, 20, 21,
920 0, 1, 2, 3, 4, 4, 5, 5
923 for (i = 0; i < 24; i++) {
925 defpal[i].rgbtRed = cfg.colours[w][0];
926 defpal[i].rgbtGreen = cfg.colours[w][1];
927 defpal[i].rgbtBlue = cfg.colours[w][2];
932 * Set up the colour palette.
934 static void init_palette(void)
937 HDC hdc = GetDC(hwnd);
939 if (cfg.try_palette && GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) {
940 logpal = smalloc(sizeof(*logpal)
941 - sizeof(logpal->palPalEntry)
942 + NCOLOURS * sizeof(PALETTEENTRY));
943 logpal->palVersion = 0x300;
944 logpal->palNumEntries = NCOLOURS;
945 for (i = 0; i < NCOLOURS; i++) {
946 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
947 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
948 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
949 logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
951 pal = CreatePalette(logpal);
953 SelectPalette(hdc, pal, FALSE);
955 SelectPalette(hdc, GetStockObject(DEFAULT_PALETTE), FALSE);
958 ReleaseDC(hwnd, hdc);
961 for (i = 0; i < NCOLOURS; i++)
962 colours[i] = PALETTERGB(defpal[i].rgbtRed,
966 for (i = 0; i < NCOLOURS; i++)
967 colours[i] = RGB(defpal[i].rgbtRed,
968 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
972 * Initialise all the fonts we will need initially. There may be as many as
973 * three or as few as one. The other (poentially) twentyone fonts are done
974 * if/when they are needed.
978 * - check the font width and height, correcting our guesses if
981 * - verify that the bold font is the same width as the ordinary
982 * one, and engage shadow bolding if not.
984 * - verify that the underlined font is the same width as the
985 * ordinary one (manual underlining by means of line drawing can
986 * be done in a pinch).
988 static void init_fonts(int pick_width, int pick_height)
995 int fw_dontcare, fw_bold;
997 for (i = 0; i < FONT_MAXNO; i++)
1000 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
1001 und_mode = UND_FONT;
1003 if (cfg.fontisbold) {
1004 fw_dontcare = FW_BOLD;
1007 fw_dontcare = FW_DONTCARE;
1014 font_height = pick_height;
1016 font_height = cfg.fontheight;
1017 if (font_height > 0) {
1019 -MulDiv(font_height, GetDeviceCaps(hdc, LOGPIXELSY), 72);
1022 font_width = pick_width;
1024 #define f(i,c,w,u) \
1025 fonts[i] = CreateFont (font_height, font_width, 0, 0, w, FALSE, u, FALSE, \
1026 c, OUT_DEFAULT_PRECIS, \
1027 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, \
1028 FIXED_PITCH | FF_DONTCARE, cfg.font)
1030 f(FONT_NORMAL, cfg.fontcharset, fw_dontcare, FALSE);
1032 lfont.lfHeight = font_height;
1033 lfont.lfWidth = font_width;
1034 lfont.lfEscapement = 0;
1035 lfont.lfOrientation = 0;
1036 lfont.lfWeight = fw_dontcare;
1037 lfont.lfItalic = FALSE;
1038 lfont.lfUnderline = FALSE;
1039 lfont.lfStrikeOut = FALSE;
1040 lfont.lfCharSet = cfg.fontcharset;
1041 lfont.lfOutPrecision = OUT_DEFAULT_PRECIS;
1042 lfont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
1043 lfont.lfQuality = DEFAULT_QUALITY;
1044 lfont.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE;
1045 strncpy(lfont.lfFaceName, cfg.font, LF_FACESIZE);
1047 SelectObject(hdc, fonts[FONT_NORMAL]);
1048 GetTextMetrics(hdc, &tm);
1050 if (pick_width == 0 || pick_height == 0) {
1051 font_height = tm.tmHeight;
1052 font_width = tm.tmAveCharWidth;
1054 font_dualwidth = (tm.tmAveCharWidth != tm.tmMaxCharWidth);
1056 #ifdef RDB_DEBUG_PATCH
1057 debug(23, "Primary font H=%d, AW=%d, MW=%d",
1058 tm.tmHeight, tm.tmAveCharWidth, tm.tmMaxCharWidth);
1063 DWORD cset = tm.tmCharSet;
1064 memset(&info, 0xFF, sizeof(info));
1066 /* !!! Yes the next line is right */
1067 if (cset == OEM_CHARSET)
1068 font_codepage = GetOEMCP();
1070 if (TranslateCharsetInfo
1071 ((DWORD *) cset, &info, TCI_SRCCHARSET)) font_codepage =
1076 GetCPInfo(font_codepage, &cpinfo);
1077 dbcs_screenfont = (cpinfo.MaxCharSize > 1);
1080 f(FONT_UNDERLINE, cfg.fontcharset, fw_dontcare, TRUE);
1083 * Some fonts, e.g. 9-pt Courier, draw their underlines
1084 * outside their character cell. We successfully prevent
1085 * screen corruption by clipping the text output, but then
1086 * we lose the underline completely. Here we try to work
1087 * out whether this is such a font, and if it is, we set a
1088 * flag that causes underlines to be drawn by hand.
1090 * Having tried other more sophisticated approaches (such
1091 * as examining the TEXTMETRIC structure or requesting the
1092 * height of a string), I think we'll do this the brute
1093 * force way: we create a small bitmap, draw an underlined
1094 * space on it, and test to see whether any pixels are
1095 * foreground-coloured. (Since we expect the underline to
1096 * go all the way across the character cell, we only search
1097 * down a single column of the bitmap, half way across.)
1101 HBITMAP und_bm, und_oldbm;
1105 und_dc = CreateCompatibleDC(hdc);
1106 und_bm = CreateCompatibleBitmap(hdc, font_width, font_height);
1107 und_oldbm = SelectObject(und_dc, und_bm);
1108 SelectObject(und_dc, fonts[FONT_UNDERLINE]);
1109 SetTextAlign(und_dc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
1110 SetTextColor(und_dc, RGB(255, 255, 255));
1111 SetBkColor(und_dc, RGB(0, 0, 0));
1112 SetBkMode(und_dc, OPAQUE);
1113 ExtTextOut(und_dc, 0, 0, ETO_OPAQUE, NULL, " ", 1, NULL);
1115 for (i = 0; i < font_height; i++) {
1116 c = GetPixel(und_dc, font_width / 2, i);
1117 if (c != RGB(0, 0, 0))
1120 SelectObject(und_dc, und_oldbm);
1121 DeleteObject(und_bm);
1124 und_mode = UND_LINE;
1125 DeleteObject(fonts[FONT_UNDERLINE]);
1126 fonts[FONT_UNDERLINE] = 0;
1130 if (bold_mode == BOLD_FONT) {
1131 f(FONT_BOLD, cfg.fontcharset, fw_bold, FALSE);
1135 descent = tm.tmAscent + 1;
1136 if (descent >= font_height)
1137 descent = font_height - 1;
1139 for (i = 0; i < 3; i++) {
1141 if (SelectObject(hdc, fonts[i]) && GetTextMetrics(hdc, &tm))
1142 fontsize[i] = tm.tmAveCharWidth + 256 * tm.tmHeight;
1149 ReleaseDC(hwnd, hdc);
1151 if (fontsize[FONT_UNDERLINE] != fontsize[FONT_NORMAL]) {
1152 und_mode = UND_LINE;
1153 DeleteObject(fonts[FONT_UNDERLINE]);
1154 fonts[FONT_UNDERLINE] = 0;
1157 if (bold_mode == BOLD_FONT &&
1158 fontsize[FONT_BOLD] != fontsize[FONT_NORMAL]) {
1159 bold_mode = BOLD_SHADOW;
1160 DeleteObject(fonts[FONT_BOLD]);
1161 fonts[FONT_BOLD] = 0;
1163 fontflag[0] = fontflag[1] = fontflag[2] = 1;
1168 static void another_font(int fontno)
1171 int fw_dontcare, fw_bold;
1175 if (fontno < 0 || fontno >= FONT_MAXNO || fontflag[fontno])
1178 basefont = (fontno & ~(FONT_BOLDUND));
1179 if (basefont != fontno && !fontflag[basefont])
1180 another_font(basefont);
1182 if (cfg.fontisbold) {
1183 fw_dontcare = FW_BOLD;
1186 fw_dontcare = FW_DONTCARE;
1190 c = cfg.fontcharset;
1196 if (fontno & FONT_WIDE)
1198 if (fontno & FONT_NARROW)
1200 if (fontno & FONT_OEM)
1202 if (fontno & FONT_BOLD)
1204 if (fontno & FONT_UNDERLINE)
1208 CreateFont(font_height * (1 + !!(fontno & FONT_HIGH)), x, 0, 0, w,
1209 FALSE, u, FALSE, c, OUT_DEFAULT_PRECIS,
1210 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
1211 FIXED_PITCH | FF_DONTCARE, s);
1213 fontflag[fontno] = 1;
1216 static void deinit_fonts(void)
1219 for (i = 0; i < FONT_MAXNO; i++) {
1221 DeleteObject(fonts[i]);
1227 void request_resize(int w, int h)
1231 /* If the window is maximized supress resizing attempts */
1232 if (IsZoomed(hwnd)) {
1233 if (cfg.resize_action == RESIZE_TERM)
1237 if (cfg.resize_action == RESIZE_DISABLED) return;
1238 if (h == rows && w == cols) return;
1240 /* Sanity checks ... */
1242 static int first_time = 1;
1245 switch (first_time) {
1247 /* Get the size of the screen */
1248 if (get_fullscreen_rect(&ss))
1249 /* first_time = 0 */ ;
1255 /* Make sure the values are sane */
1256 width = (ss.right - ss.left - extra_width) / 4;
1257 height = (ss.bottom - ss.top - extra_height) / 6;
1259 if (w > width || h > height)
1268 term_size(h, w, cfg.savelines);
1270 if (cfg.resize_action != RESIZE_FONT && !IsZoomed(hwnd)) {
1271 width = extra_width + font_width * w;
1272 height = extra_height + font_height * h;
1274 SetWindowPos(hwnd, NULL, 0, 0, width, height,
1275 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1276 SWP_NOMOVE | SWP_NOZORDER);
1280 InvalidateRect(hwnd, NULL, TRUE);
1283 static void reset_window(int reinit) {
1285 * This function decides how to resize or redraw when the
1286 * user changes something.
1288 * This function doesn't like to change the terminal size but if the
1289 * font size is locked that may be it's only soluion.
1291 int win_width, win_height;
1294 #ifdef RDB_DEBUG_PATCH
1295 debug((27, "reset_window()"));
1298 /* Current window sizes ... */
1299 GetWindowRect(hwnd, &wr);
1300 GetClientRect(hwnd, &cr);
1302 win_width = cr.right - cr.left;
1303 win_height = cr.bottom - cr.top;
1305 if (cfg.resize_action == RESIZE_DISABLED) reinit = 2;
1307 /* Are we being forced to reload the fonts ? */
1309 #ifdef RDB_DEBUG_PATCH
1310 debug((27, "reset_window() -- Forced deinit"));
1316 /* Oh, looks like we're minimised */
1317 if (win_width == 0 || win_height == 0)
1320 /* Is the window out of position ? */
1322 (offset_width != (win_width-font_width*cols)/2 ||
1323 offset_height != (win_height-font_height*rows)/2) ){
1324 offset_width = (win_width-font_width*cols)/2;
1325 offset_height = (win_height-font_height*rows)/2;
1326 InvalidateRect(hwnd, NULL, TRUE);
1327 #ifdef RDB_DEBUG_PATCH
1328 debug((27, "reset_window() -> Reposition terminal"));
1332 if (IsZoomed(hwnd)) {
1333 /* We're fullscreen, this means we must not change the size of
1334 * the window so it's the font size or the terminal itself.
1337 extra_width = wr.right - wr.left - cr.right + cr.left;
1338 extra_height = wr.bottom - wr.top - cr.bottom + cr.top;
1340 if (cfg.resize_action != RESIZE_TERM) {
1341 if ( font_width != win_width/cols ||
1342 font_height != win_height/rows) {
1344 init_fonts(win_width/cols, win_height/rows);
1345 offset_width = (win_width-font_width*cols)/2;
1346 offset_height = (win_height-font_height*rows)/2;
1347 InvalidateRect(hwnd, NULL, TRUE);
1348 #ifdef RDB_DEBUG_PATCH
1349 debug((25, "reset_window() -> Z font resize to (%d, %d)",
1350 font_width, font_height));
1354 if ( font_width != win_width/cols ||
1355 font_height != win_height/rows) {
1356 /* Our only choice at this point is to change the
1357 * size of the terminal; Oh well.
1359 term_size( win_height/font_height, win_width/font_width,
1361 offset_width = (win_width-font_width*cols)/2;
1362 offset_height = (win_height-font_height*rows)/2;
1363 InvalidateRect(hwnd, NULL, TRUE);
1364 #ifdef RDB_DEBUG_PATCH
1365 debug((27, "reset_window() -> Zoomed term_size"));
1372 /* Hmm, a force re-init means we should ignore the current window
1373 * so we resize to the default font size.
1376 #ifdef RDB_DEBUG_PATCH
1377 debug((27, "reset_window() -> Forced re-init"));
1380 offset_width = offset_height = cfg.window_border;
1381 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
1382 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
1384 if (win_width != font_width*cols + offset_width*2 ||
1385 win_height != font_height*rows + offset_height*2) {
1387 /* If this is too large windows will resize it to the maximum
1388 * allowed window size, we will then be back in here and resize
1389 * the font or terminal to fit.
1391 SetWindowPos(hwnd, NULL, 0, 0,
1392 font_width*cols + extra_width,
1393 font_height*rows + extra_height,
1394 SWP_NOMOVE | SWP_NOZORDER);
1397 InvalidateRect(hwnd, NULL, TRUE);
1401 /* Okay the user doesn't want us to change the font so we try the
1402 * window. But that may be too big for the screen which forces us
1403 * to change the terminal.
1405 if ((cfg.resize_action == RESIZE_TERM && reinit<=0) ||
1406 (cfg.resize_action == RESIZE_EITHER && reinit<0) ||
1408 offset_width = offset_height = cfg.window_border;
1409 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
1410 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
1412 if (win_width != font_width*cols + offset_width*2 ||
1413 win_height != font_height*rows + offset_height*2) {
1418 get_fullscreen_rect(&ss);
1420 width = (ss.right - ss.left - extra_width) / font_width;
1421 height = (ss.bottom - ss.top - extra_height) / font_height;
1424 if ( rows > height || cols > width ) {
1425 if (cfg.resize_action == RESIZE_EITHER) {
1426 /* Make the font the biggest we can */
1428 font_width = (ss.right - ss.left - extra_width)/cols;
1430 font_height = (ss.bottom - ss.top - extra_height)/rows;
1433 init_fonts(font_width, font_height);
1435 width = (ss.right - ss.left - extra_width) / font_width;
1436 height = (ss.bottom - ss.top - extra_height) / font_height;
1438 if ( height > rows ) height = rows;
1439 if ( width > cols ) width = cols;
1440 term_size(height, width, cfg.savelines);
1441 #ifdef RDB_DEBUG_PATCH
1442 debug((27, "reset_window() -> term resize to (%d,%d)",
1448 SetWindowPos(hwnd, NULL, 0, 0,
1449 font_width*cols + extra_width,
1450 font_height*rows + extra_height,
1451 SWP_NOMOVE | SWP_NOZORDER);
1453 InvalidateRect(hwnd, NULL, TRUE);
1454 #ifdef RDB_DEBUG_PATCH
1455 debug((27, "reset_window() -> window resize to (%d,%d)",
1456 font_width*cols + extra_width,
1457 font_height*rows + extra_height));
1463 /* We're allowed to or must change the font but do we want to ? */
1465 if (font_width != (win_width-cfg.window_border*2)/cols ||
1466 font_height != (win_height-cfg.window_border*2)/rows) {
1469 init_fonts((win_width-cfg.window_border*2)/cols,
1470 (win_height-cfg.window_border*2)/rows);
1471 offset_width = (win_width-font_width*cols)/2;
1472 offset_height = (win_height-font_height*rows)/2;
1474 extra_width = wr.right - wr.left - cr.right + cr.left +offset_width*2;
1475 extra_height = wr.bottom - wr.top - cr.bottom + cr.top+offset_height*2;
1477 InvalidateRect(hwnd, NULL, TRUE);
1478 #ifdef RDB_DEBUG_PATCH
1479 debug((25, "reset_window() -> font resize to (%d,%d)",
1480 font_width, font_height));
1485 static void set_input_locale(HKL kl)
1489 GetLocaleInfo(LOWORD(kl), LOCALE_IDEFAULTANSICODEPAGE,
1490 lbuf, sizeof(lbuf));
1492 kbd_codepage = atoi(lbuf);
1495 static void click(Mouse_Button b, int x, int y, int shift, int ctrl, int alt)
1497 int thistime = GetMessageTime();
1499 if (send_raw_mouse && !(cfg.mouse_override && shift)) {
1500 lastbtn = MBT_NOTHING;
1501 term_mouse(b, MA_CLICK, x, y, shift, ctrl, alt);
1505 if (lastbtn == b && thistime - lasttime < dbltime) {
1506 lastact = (lastact == MA_CLICK ? MA_2CLK :
1507 lastact == MA_2CLK ? MA_3CLK :
1508 lastact == MA_3CLK ? MA_CLICK : MA_NOTHING);
1513 if (lastact != MA_NOTHING)
1514 term_mouse(b, lastact, x, y, shift, ctrl, alt);
1515 lasttime = thistime;
1519 * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT)
1520 * into a cooked one (SELECT, EXTEND, PASTE).
1522 Mouse_Button translate_button(Mouse_Button button)
1524 if (button == MBT_LEFT)
1526 if (button == MBT_MIDDLE)
1527 return cfg.mouse_is_xterm ? MBT_PASTE : MBT_EXTEND;
1528 if (button == MBT_RIGHT)
1529 return cfg.mouse_is_xterm ? MBT_EXTEND : MBT_PASTE;
1530 return 0; /* shouldn't happen */
1533 static void show_mouseptr(int show)
1535 static int cursor_visible = 1;
1536 if (!cfg.hide_mouseptr) /* override if this feature disabled */
1538 if (cursor_visible && !show)
1540 else if (!cursor_visible && show)
1542 cursor_visible = show;
1545 static int is_alt_pressed(void)
1548 int r = GetKeyboardState(keystate);
1551 if (keystate[VK_MENU] & 0x80)
1553 if (keystate[VK_RMENU] & 0x80)
1558 static int is_shift_pressed(void)
1561 int r = GetKeyboardState(keystate);
1564 if (keystate[VK_SHIFT] & 0x80)
1569 static int resizing;
1571 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1572 WPARAM wParam, LPARAM lParam)
1575 static int ignore_clip = FALSE;
1576 static int need_backend_resize = FALSE;
1577 static int fullscr_on_max = FALSE;
1581 if (pending_netevent)
1582 enact_pending_netevent();
1583 if (GetCapture() != hwnd ||
1584 (send_raw_mouse && !(cfg.mouse_override && is_shift_pressed())))
1590 if (cfg.ping_interval > 0) {
1593 if (now - last_movement > cfg.ping_interval) {
1594 back->special(TS_PING);
1595 last_movement = now;
1598 net_pending_errors();
1604 if (!cfg.warn_on_close || session_closed ||
1606 "Are you sure you want to close this session?",
1607 "PuTTY Exit Confirmation",
1608 MB_ICONWARNING | MB_OKCANCEL) == IDOK)
1609 DestroyWindow(hwnd);
1616 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1628 PROCESS_INFORMATION pi;
1629 HANDLE filemap = NULL;
1631 if (wParam == IDM_DUPSESS) {
1633 * Allocate a file-mapping memory chunk for the
1636 SECURITY_ATTRIBUTES sa;
1639 sa.nLength = sizeof(sa);
1640 sa.lpSecurityDescriptor = NULL;
1641 sa.bInheritHandle = TRUE;
1642 filemap = CreateFileMapping((HANDLE) 0xFFFFFFFF,
1645 0, sizeof(Config), NULL);
1647 p = (Config *) MapViewOfFile(filemap,
1649 0, 0, sizeof(Config));
1651 *p = cfg; /* structure copy */
1655 sprintf(c, "putty &%p", filemap);
1657 } else if (wParam == IDM_SAVEDSESS) {
1658 if ((lParam - IDM_SAVED_MIN) / 16 < nsessions) {
1660 sessions[(lParam - IDM_SAVED_MIN) / 16];
1661 cl = smalloc(16 + strlen(session));
1662 /* 8, but play safe */
1665 /* not a very important failure mode */
1667 sprintf(cl, "putty @%s", session);
1675 GetModuleFileName(NULL, b, sizeof(b) - 1);
1677 si.lpReserved = NULL;
1678 si.lpDesktop = NULL;
1682 si.lpReserved2 = NULL;
1683 CreateProcess(b, cl, NULL, NULL, TRUE,
1684 NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
1687 CloseHandle(filemap);
1697 GetWindowText(hwnd, cfg.wintitle, sizeof(cfg.wintitle));
1700 if (!do_reconfig(hwnd))
1704 /* Disable full-screen if resizing forbidden */
1705 HMENU m = GetSystemMenu (hwnd, FALSE);
1706 EnableMenuItem(m, IDM_FULLSCREEN, MF_BYCOMMAND |
1707 (cfg.resize_action == RESIZE_DISABLED)
1708 ? MF_GRAYED : MF_ENABLED);
1709 /* Gracefully unzoom if necessary */
1710 if (IsZoomed(hwnd) &&
1711 (cfg.resize_action == RESIZE_DISABLED)) {
1712 ShowWindow(hwnd, SW_RESTORE);
1716 if (strcmp(prev_cfg.logfilename, cfg.logfilename) ||
1717 prev_cfg.logtype != cfg.logtype) {
1718 logfclose(); /* reset logging */
1724 * Flush the line discipline's edit buffer in the
1725 * case where local editing has just been disabled.
1727 ldisc_send(NULL, 0, 0);
1735 /* Give terminal a heads-up on miscellaneous stuff */
1738 /* Screen size changed ? */
1739 if (cfg.height != prev_cfg.height ||
1740 cfg.width != prev_cfg.width ||
1741 cfg.savelines != prev_cfg.savelines ||
1742 cfg.resize_action == RESIZE_FONT ||
1743 (cfg.resize_action == RESIZE_EITHER && IsZoomed(hwnd)) ||
1744 cfg.resize_action == RESIZE_DISABLED)
1745 term_size(cfg.height, cfg.width, cfg.savelines);
1747 /* Enable or disable the scroll bar, etc */
1749 LONG nflg, flag = GetWindowLong(hwnd, GWL_STYLE);
1750 LONG nexflag, exflag =
1751 GetWindowLong(hwnd, GWL_EXSTYLE);
1754 if (cfg.alwaysontop != prev_cfg.alwaysontop) {
1755 if (cfg.alwaysontop) {
1756 nexflag |= WS_EX_TOPMOST;
1757 SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
1758 SWP_NOMOVE | SWP_NOSIZE);
1760 nexflag &= ~(WS_EX_TOPMOST);
1761 SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0,
1762 SWP_NOMOVE | SWP_NOSIZE);
1765 if (cfg.sunken_edge)
1766 nexflag |= WS_EX_CLIENTEDGE;
1768 nexflag &= ~(WS_EX_CLIENTEDGE);
1771 if (is_full_screen() ?
1772 cfg.scrollbar_in_fullscreen : cfg.scrollbar)
1775 nflg &= ~WS_VSCROLL;
1777 if (cfg.resize_action == RESIZE_DISABLED ||
1779 nflg &= ~WS_THICKFRAME;
1781 nflg |= WS_THICKFRAME;
1783 if (cfg.resize_action == RESIZE_DISABLED)
1784 nflg &= ~WS_MAXIMIZEBOX;
1786 nflg |= WS_MAXIMIZEBOX;
1788 if (nflg != flag || nexflag != exflag) {
1790 SetWindowLong(hwnd, GWL_STYLE, nflg);
1791 if (nexflag != exflag)
1792 SetWindowLong(hwnd, GWL_EXSTYLE, nexflag);
1794 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
1795 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1796 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
1804 if (cfg.resize_action == RESIZE_DISABLED && IsZoomed(hwnd)) {
1809 set_title(cfg.wintitle);
1810 if (IsIconic(hwnd)) {
1812 cfg.win_name_always ? window_name :
1816 if (strcmp(cfg.font, prev_cfg.font) != 0 ||
1817 strcmp(cfg.line_codepage, prev_cfg.line_codepage) != 0 ||
1818 cfg.fontisbold != prev_cfg.fontisbold ||
1819 cfg.fontheight != prev_cfg.fontheight ||
1820 cfg.fontcharset != prev_cfg.fontcharset ||
1821 cfg.vtmode != prev_cfg.vtmode ||
1822 cfg.bold_colour != prev_cfg.bold_colour ||
1823 cfg.resize_action == RESIZE_DISABLED ||
1824 cfg.resize_action == RESIZE_EITHER ||
1825 (cfg.resize_action != prev_cfg.resize_action))
1828 InvalidateRect(hwnd, NULL, TRUE);
1829 reset_window(init_lvl);
1830 net_pending_errors();
1843 back->special(TS_AYT);
1844 net_pending_errors();
1847 back->special(TS_BRK);
1848 net_pending_errors();
1851 back->special(TS_SYNCH);
1852 net_pending_errors();
1855 back->special(TS_EC);
1856 net_pending_errors();
1859 back->special(TS_EL);
1860 net_pending_errors();
1863 back->special(TS_GA);
1864 net_pending_errors();
1867 back->special(TS_NOP);
1868 net_pending_errors();
1871 back->special(TS_ABORT);
1872 net_pending_errors();
1875 back->special(TS_AO);
1876 net_pending_errors();
1879 back->special(TS_IP);
1880 net_pending_errors();
1883 back->special(TS_SUSP);
1884 net_pending_errors();
1887 back->special(TS_EOR);
1888 net_pending_errors();
1891 back->special(TS_EOF);
1892 net_pending_errors();
1898 WinHelp(hwnd, help_path,
1899 help_has_contents ? HELP_FINDER : HELP_CONTENTS, 0);
1903 * We get this if the System menu has been activated
1910 * We get this if the System menu has been activated
1911 * using the keyboard. This might happen from within
1912 * TranslateKey, in which case it really wants to be
1913 * followed by a `space' character to actually _bring
1914 * the menu up_ rather than just sitting there in
1915 * `ready to appear' state.
1917 show_mouseptr(1); /* make sure pointer is visible */
1919 PostMessage(hwnd, WM_CHAR, ' ', 0);
1921 case IDM_FULLSCREEN:
1925 if (wParam >= IDM_SAVED_MIN && wParam <= IDM_SAVED_MAX) {
1926 SendMessage(hwnd, WM_SYSCOMMAND, IDM_SAVEDSESS, wParam);
1931 #define X_POS(l) ((int)(short)LOWORD(l))
1932 #define Y_POS(l) ((int)(short)HIWORD(l))
1934 #define TO_CHR_X(x) ((((x)<0 ? (x)-font_width+1 : (x))-offset_width) / font_width)
1935 #define TO_CHR_Y(y) ((((y)<0 ? (y)-font_height+1: (y))-offset_height) / font_height)
1936 case WM_LBUTTONDOWN:
1937 case WM_MBUTTONDOWN:
1938 case WM_RBUTTONDOWN:
1946 case WM_LBUTTONDOWN:
1950 case WM_MBUTTONDOWN:
1951 button = MBT_MIDDLE;
1954 case WM_RBUTTONDOWN:
1963 button = MBT_MIDDLE;
1971 button = press = 0; /* shouldn't happen */
1975 * Special case: in full-screen mode, if the left
1976 * button is clicked in the very top left corner of the
1977 * window, we put up the System menu instead of doing
1980 if (is_full_screen() && press && button == MBT_LEFT &&
1981 X_POS(lParam) == 0 && Y_POS(lParam) == 0) {
1982 SendMessage(hwnd, WM_SYSCOMMAND, SC_MOUSEMENU, 0);
1987 TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam)),
1988 wParam & MK_SHIFT, wParam & MK_CONTROL,
1992 term_mouse(button, MA_RELEASE,
1993 TO_CHR_X(X_POS(lParam)),
1994 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
1995 wParam & MK_CONTROL, is_alt_pressed());
2003 * Add the mouse position and message time to the random
2006 noise_ultralight(lParam);
2008 if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON) &&
2009 GetCapture() == hwnd) {
2011 if (wParam & MK_LBUTTON)
2013 else if (wParam & MK_MBUTTON)
2017 term_mouse(b, MA_DRAG, TO_CHR_X(X_POS(lParam)),
2018 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
2019 wParam & MK_CONTROL, is_alt_pressed());
2022 case WM_NCMOUSEMOVE:
2024 noise_ultralight(lParam);
2026 case WM_IGNORE_CLIP:
2027 ignore_clip = wParam; /* don't panic on DESTROYCLIPBOARD */
2029 case WM_DESTROYCLIPBOARD:
2032 ignore_clip = FALSE;
2038 hdc = BeginPaint(hwnd, &p);
2040 SelectPalette(hdc, pal, TRUE);
2041 RealizePalette(hdc);
2044 (p.rcPaint.left-offset_width)/font_width,
2045 (p.rcPaint.top-offset_height)/font_height,
2046 (p.rcPaint.right-offset_width-1)/font_width,
2047 (p.rcPaint.bottom-offset_height-1)/font_height);
2050 p.rcPaint.left < offset_width ||
2051 p.rcPaint.top < offset_height ||
2052 p.rcPaint.right >= offset_width + font_width*cols ||
2053 p.rcPaint.bottom>= offset_height + font_height*rows)
2055 HBRUSH fillcolour, oldbrush;
2057 fillcolour = CreateSolidBrush (
2058 colours[(ATTR_DEFBG>>ATTR_BGSHIFT)*2]);
2059 oldbrush = SelectObject(hdc, fillcolour);
2060 edge = CreatePen(PS_SOLID, 0,
2061 colours[(ATTR_DEFBG>>ATTR_BGSHIFT)*2]);
2062 oldpen = SelectObject(hdc, edge);
2065 * Jordan Russell reports that this apparently
2066 * ineffectual IntersectClipRect() call masks a
2067 * Windows NT/2K bug causing strange display
2068 * problems when the PuTTY window is taller than
2069 * the primary monitor. It seems harmless enough...
2071 IntersectClipRect(hdc,
2072 p.rcPaint.left, p.rcPaint.top,
2073 p.rcPaint.right, p.rcPaint.bottom);
2075 ExcludeClipRect(hdc,
2076 offset_width, offset_height,
2077 offset_width+font_width*cols,
2078 offset_height+font_height*rows);
2080 Rectangle(hdc, p.rcPaint.left, p.rcPaint.top,
2081 p.rcPaint.right, p.rcPaint.bottom);
2083 // SelectClipRgn(hdc, NULL);
2085 SelectObject(hdc, oldbrush);
2086 DeleteObject(fillcolour);
2087 SelectObject(hdc, oldpen);
2090 SelectObject(hdc, GetStockObject(SYSTEM_FONT));
2091 SelectObject(hdc, GetStockObject(WHITE_PEN));
2097 /* Notice we can get multiple netevents, FD_READ, FD_WRITE etc
2098 * but the only one that's likely to try to overload us is FD_READ.
2099 * This means buffering just one is fine.
2101 if (pending_netevent)
2102 enact_pending_netevent();
2104 pending_netevent = TRUE;
2105 pend_netevent_wParam = wParam;
2106 pend_netevent_lParam = lParam;
2107 if (WSAGETSELECTEVENT(lParam) != FD_READ)
2108 enact_pending_netevent();
2110 time(&last_movement);
2114 CreateCaret(hwnd, caretbm, font_width, font_height);
2116 flash_window(0); /* stop */
2125 caret_x = caret_y = -1; /* ensure caret is replaced next time */
2129 case WM_ENTERSIZEMOVE:
2130 #ifdef RDB_DEBUG_PATCH
2131 debug((27, "WM_ENTERSIZEMOVE"));
2135 need_backend_resize = FALSE;
2137 case WM_EXITSIZEMOVE:
2140 #ifdef RDB_DEBUG_PATCH
2141 debug((27, "WM_EXITSIZEMOVE"));
2143 if (need_backend_resize) {
2144 term_size(cfg.height, cfg.width, cfg.savelines);
2145 InvalidateRect(hwnd, NULL, TRUE);
2150 * This does two jobs:
2151 * 1) Keep the sizetip uptodate
2152 * 2) Make sure the window size is _stepped_ in units of the font size.
2154 if (cfg.resize_action != RESIZE_FONT && !alt_pressed) {
2155 int width, height, w, h, ew, eh;
2156 LPRECT r = (LPRECT) lParam;
2158 if ( !need_backend_resize && cfg.resize_action == RESIZE_EITHER &&
2159 (cfg.height != rows || cfg.width != cols )) {
2161 * Great! It seems that both the terminal size and the
2162 * font size have been changed and the user is now dragging.
2164 * It will now be difficult to get back to the configured
2167 * This would be easier but it seems to be too confusing.
2169 term_size(cfg.height, cfg.width, cfg.savelines);
2172 cfg.height=rows; cfg.width=cols;
2174 InvalidateRect(hwnd, NULL, TRUE);
2175 need_backend_resize = TRUE;
2178 width = r->right - r->left - extra_width;
2179 height = r->bottom - r->top - extra_height;
2180 w = (width + font_width / 2) / font_width;
2183 h = (height + font_height / 2) / font_height;
2186 UpdateSizeTip(hwnd, w, h);
2187 ew = width - w * font_width;
2188 eh = height - h * font_height;
2190 if (wParam == WMSZ_LEFT ||
2191 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
2197 if (wParam == WMSZ_TOP ||
2198 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
2208 int width, height, w, h, rv = 0;
2209 int ex_width = extra_width + (cfg.window_border - offset_width) * 2;
2210 int ex_height = extra_height + (cfg.window_border - offset_height) * 2;
2211 LPRECT r = (LPRECT) lParam;
2213 width = r->right - r->left - ex_width;
2214 height = r->bottom - r->top - ex_height;
2216 w = (width + cols/2)/cols;
2217 h = (height + rows/2)/rows;
2218 if ( r->right != r->left + w*cols + ex_width)
2221 if (wParam == WMSZ_LEFT ||
2222 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
2223 r->left = r->right - w*cols - ex_width;
2225 r->right = r->left + w*cols + ex_width;
2227 if (r->bottom != r->top + h*rows + ex_height)
2230 if (wParam == WMSZ_TOP ||
2231 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
2232 r->top = r->bottom - h*rows - ex_height;
2234 r->bottom = r->top + h*rows + ex_height;
2238 /* break; (never reached) */
2239 case WM_FULLSCR_ON_MAX:
2240 fullscr_on_max = TRUE;
2243 sys_cursor_update();
2246 #ifdef RDB_DEBUG_PATCH
2247 debug((27, "WM_SIZE %s (%d,%d)",
2248 (wParam == SIZE_MINIMIZED) ? "SIZE_MINIMIZED":
2249 (wParam == SIZE_MAXIMIZED) ? "SIZE_MAXIMIZED":
2250 (wParam == SIZE_RESTORED && resizing) ? "to":
2251 (wParam == SIZE_RESTORED) ? "SIZE_RESTORED":
2253 LOWORD(lParam), HIWORD(lParam)));
2255 if (wParam == SIZE_MINIMIZED)
2257 cfg.win_name_always ? window_name : icon_name);
2258 if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
2259 SetWindowText(hwnd, window_name);
2260 if (wParam == SIZE_RESTORED)
2261 clear_full_screen();
2262 if (wParam == SIZE_MAXIMIZED && fullscr_on_max) {
2263 fullscr_on_max = FALSE;
2267 if (cfg.resize_action == RESIZE_DISABLED) {
2268 /* A resize, well it better be a minimize. */
2272 int width, height, w, h;
2274 width = LOWORD(lParam);
2275 height = HIWORD(lParam);
2278 if (wParam == SIZE_MAXIMIZED && !was_zoomed) {
2282 if (cfg.resize_action == RESIZE_TERM) {
2283 w = width / font_width;
2285 h = height / font_height;
2288 term_size(h, w, cfg.savelines);
2291 } else if (wParam == SIZE_RESTORED && was_zoomed) {
2293 if (cfg.resize_action == RESIZE_TERM)
2294 term_size(prev_rows, prev_cols, cfg.savelines);
2295 if (cfg.resize_action != RESIZE_FONT)
2300 /* This is an unexpected resize, these will normally happen
2301 * if the window is too large. Probably either the user
2302 * selected a huge font or the screen size has changed.
2304 * This is also called with minimize.
2306 else reset_window(-1);
2310 * Don't call back->size in mid-resize. (To prevent
2311 * massive numbers of resize events getting sent
2312 * down the connection during an NT opaque drag.)
2315 if (cfg.resize_action != RESIZE_FONT && !alt_pressed) {
2316 need_backend_resize = TRUE;
2317 w = (width-cfg.window_border*2) / font_width;
2319 h = (height-cfg.window_border*2) / font_height;
2328 sys_cursor_update();
2331 switch (LOWORD(wParam)) {
2345 term_scroll(0, +rows / 2);
2348 term_scroll(0, -rows / 2);
2350 case SB_THUMBPOSITION:
2352 term_scroll(1, HIWORD(wParam));
2356 case WM_PALETTECHANGED:
2357 if ((HWND) wParam != hwnd && pal != NULL) {
2358 HDC hdc = get_ctx();
2360 if (RealizePalette(hdc) > 0)
2366 case WM_QUERYNEWPALETTE:
2368 HDC hdc = get_ctx();
2370 if (RealizePalette(hdc) > 0)
2382 * Add the scan code and keypress timing to the random
2385 noise_ultralight(lParam);
2388 * We don't do TranslateMessage since it disassociates the
2389 * resulting CHAR message from the KEYDOWN that sparked it,
2390 * which we occasionally don't want. Instead, we process
2391 * KEYDOWN, and call the Win32 translator functions so that
2392 * we get the translations under _our_ control.
2395 unsigned char buf[20];
2398 if (wParam == VK_PROCESSKEY) {
2401 m.message = WM_KEYDOWN;
2403 m.lParam = lParam & 0xdfff;
2404 TranslateMessage(&m);
2406 len = TranslateKey(message, wParam, lParam, buf);
2408 return DefWindowProc(hwnd, message, wParam, lParam);
2412 * Interrupt an ongoing paste. I'm not sure
2413 * this is sensible, but for the moment it's
2414 * preferable to having to faff about buffering
2420 * We need not bother about stdin backlogs
2421 * here, because in GUI PuTTY we can't do
2422 * anything about it anyway; there's no means
2423 * of asking Windows to hold off on KEYDOWN
2424 * messages. We _have_ to buffer everything
2427 ldisc_send(buf, len, 1);
2432 net_pending_errors();
2434 case WM_INPUTLANGCHANGE:
2435 /* wParam == Font number */
2436 /* lParam == Locale */
2437 set_input_locale((HKL)lParam);
2438 sys_cursor_update();
2441 if(wParam == IMN_SETOPENSTATUS) {
2442 HIMC hImc = ImmGetContext(hwnd);
2443 ImmSetCompositionFont(hImc, &lfont);
2444 ImmReleaseContext(hwnd, hImc);
2448 case WM_IME_COMPOSITION:
2454 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ||
2455 osVersion.dwPlatformId == VER_PLATFORM_WIN32s) break; /* no Unicode */
2457 if ((lParam & GCS_RESULTSTR) == 0) /* Composition unfinished. */
2458 break; /* fall back to DefWindowProc */
2460 hIMC = ImmGetContext(hwnd);
2461 n = ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, NULL, 0);
2465 buff = (char*) smalloc(n);
2466 ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, buff, n);
2468 * Jaeyoun Chung reports that Korean character
2469 * input doesn't work correctly if we do a single
2470 * luni_send() covering the whole of buff. So
2471 * instead we luni_send the characters one by one.
2473 for (i = 0; i < n; i += 2)
2474 luni_send((unsigned short *)(buff+i), 1, 1);
2477 ImmReleaseContext(hwnd, hIMC);
2482 if (wParam & 0xFF00) {
2483 unsigned char buf[2];
2486 buf[0] = wParam >> 8;
2487 lpage_send(kbd_codepage, buf, 2, 1);
2489 char c = (unsigned char) wParam;
2490 lpage_send(kbd_codepage, &c, 1, 1);
2496 * Nevertheless, we are prepared to deal with WM_CHAR
2497 * messages, should they crop up. So if someone wants to
2498 * post the things to us as part of a macro manoeuvre,
2499 * we're ready to cope.
2502 char c = (unsigned char)wParam;
2503 lpage_send(CP_ACP, &c, 1, 1);
2507 if (send_raw_mouse && LOWORD(lParam) == HTCLIENT) {
2508 SetCursor(LoadCursor(NULL, IDC_ARROW));
2512 if (message == wm_mousewheel || message == WM_MOUSEWHEEL) {
2513 int shift_pressed=0, control_pressed=0, alt_pressed=0;
2515 if (message == WM_MOUSEWHEEL) {
2516 wheel_accumulator += (short)HIWORD(wParam);
2517 shift_pressed=LOWORD(wParam) & MK_SHIFT;
2518 control_pressed=LOWORD(wParam) & MK_CONTROL;
2521 wheel_accumulator += (int)wParam;
2522 if (GetKeyboardState(keys)!=0) {
2523 shift_pressed=keys[VK_SHIFT]&0x80;
2524 control_pressed=keys[VK_CONTROL]&0x80;
2528 /* process events when the threshold is reached */
2529 while (abs(wheel_accumulator) >= WHEEL_DELTA) {
2532 /* reduce amount for next time */
2533 if (wheel_accumulator > 0) {
2535 wheel_accumulator -= WHEEL_DELTA;
2536 } else if (wheel_accumulator < 0) {
2538 wheel_accumulator += WHEEL_DELTA;
2542 if (send_raw_mouse &&
2543 !(cfg.mouse_override && shift_pressed)) {
2544 /* send a mouse-down followed by a mouse up */
2547 TO_CHR_X(X_POS(lParam)),
2548 TO_CHR_Y(Y_POS(lParam)), shift_pressed,
2549 control_pressed, is_alt_pressed());
2550 term_mouse(b, MA_RELEASE, TO_CHR_X(X_POS(lParam)),
2551 TO_CHR_Y(Y_POS(lParam)), shift_pressed,
2552 control_pressed, is_alt_pressed());
2554 /* trigger a scroll */
2556 b == MBT_WHEEL_UP ? -rows / 2 : rows / 2);
2563 return DefWindowProc(hwnd, message, wParam, lParam);
2567 * Move the system caret. (We maintain one, even though it's
2568 * invisible, for the benefit of blind people: apparently some
2569 * helper software tracks the system caret, so we should arrange to
2572 void sys_cursor(int x, int y)
2576 if (!has_focus) return;
2579 * Avoid gratuitously re-updating the cursor position and IMM
2580 * window if there's no actual change required.
2582 cx = x * font_width + offset_width;
2583 cy = y * font_height + offset_height;
2584 if (cx == caret_x && cy == caret_y)
2589 sys_cursor_update();
2592 static void sys_cursor_update(void)
2597 if (!has_focus) return;
2599 if (caret_x < 0 || caret_y < 0)
2602 SetCaretPos(caret_x, caret_y);
2604 /* IMM calls on Win98 and beyond only */
2605 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32s) return; /* 3.11 */
2607 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS &&
2608 osVersion.dwMinorVersion == 0) return; /* 95 */
2610 /* we should have the IMM functions */
2611 hIMC = ImmGetContext(hwnd);
2612 cf.dwStyle = CFS_POINT;
2613 cf.ptCurrentPos.x = caret_x;
2614 cf.ptCurrentPos.y = caret_y;
2615 ImmSetCompositionWindow(hIMC, &cf);
2617 ImmReleaseContext(hwnd, hIMC);
2621 * Draw a line of text in the window, at given character
2622 * coordinates, in given attributes.
2624 * We are allowed to fiddle with the contents of `text'.
2626 void do_text(Context ctx, int x, int y, char *text, int len,
2627 unsigned long attr, int lattr)
2630 int nfg, nbg, nfont;
2633 int force_manual_underline = 0;
2634 int fnt_width = font_width * (1 + (lattr != LATTR_NORM));
2635 int char_width = fnt_width;
2636 int text_adjust = 0;
2637 static int *IpDx = 0, IpDxLEN = 0;
2639 if (attr & ATTR_WIDE)
2642 if (len > IpDxLEN || IpDx[0] != char_width) {
2644 if (len > IpDxLEN) {
2646 IpDx = smalloc((len + 16) * sizeof(int));
2647 IpDxLEN = (len + 16);
2649 for (i = 0; i < IpDxLEN; i++)
2650 IpDx[i] = char_width;
2653 /* Only want the left half of double width lines */
2654 if (lattr != LATTR_NORM && x*2 >= cols)
2662 if ((attr & TATTR_ACTCURS) && (cfg.cursor_type == 0 || big_cursor)) {
2663 attr &= ATTR_CUR_AND | (bold_mode != BOLD_COLOURS ? ATTR_BOLD : 0);
2664 attr ^= ATTR_CUR_XOR;
2668 if (cfg.vtmode == VT_POORMAN && lattr != LATTR_NORM) {
2669 /* Assume a poorman font is borken in other ways too. */
2679 nfont |= FONT_WIDE + FONT_HIGH;
2682 if (attr & ATTR_NARROW)
2683 nfont |= FONT_NARROW;
2685 /* Special hack for the VT100 linedraw glyphs. */
2686 if ((attr & CSET_MASK) == 0x2300) {
2687 if (text[0] >= (char) 0xBA && text[0] <= (char) 0xBD) {
2688 switch ((unsigned char) (text[0])) {
2690 text_adjust = -2 * font_height / 5;
2693 text_adjust = -1 * font_height / 5;
2696 text_adjust = font_height / 5;
2699 text_adjust = 2 * font_height / 5;
2702 if (lattr == LATTR_TOP || lattr == LATTR_BOT)
2705 text[0] = (char) (unitab_xterm['q'] & CHAR_MASK);
2706 attr |= (unitab_xterm['q'] & CSET_MASK);
2707 if (attr & ATTR_UNDER) {
2708 attr &= ~ATTR_UNDER;
2709 force_manual_underline = 1;
2714 /* Anything left as an original character set is unprintable. */
2715 if (DIRECT_CHAR(attr)) {
2718 memset(text, 0xFD, len);
2722 if ((attr & CSET_MASK) == ATTR_OEMCP)
2725 nfg = 2 * ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
2726 nbg = 2 * ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
2727 if (bold_mode == BOLD_FONT && (attr & ATTR_BOLD))
2729 if (und_mode == UND_FONT && (attr & ATTR_UNDER))
2730 nfont |= FONT_UNDERLINE;
2731 another_font(nfont);
2732 if (!fonts[nfont]) {
2733 if (nfont & FONT_UNDERLINE)
2734 force_manual_underline = 1;
2735 /* Don't do the same for manual bold, it could be bad news. */
2737 nfont &= ~(FONT_BOLD | FONT_UNDERLINE);
2739 another_font(nfont);
2741 nfont = FONT_NORMAL;
2742 if (attr & ATTR_REVERSE) {
2747 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BOLD))
2749 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BLINK))
2753 SelectObject(hdc, fonts[nfont]);
2754 SetTextColor(hdc, fg);
2755 SetBkColor(hdc, bg);
2756 SetBkMode(hdc, OPAQUE);
2759 line_box.right = x + char_width * len;
2760 line_box.bottom = y + font_height;
2762 /* Only want the left half of double width lines */
2763 if (line_box.right > font_width*cols+offset_width)
2764 line_box.right = font_width*cols+offset_width;
2766 /* We're using a private area for direct to font. (512 chars.) */
2767 if (dbcs_screenfont && (attr & CSET_MASK) == ATTR_ACP) {
2768 /* Ho Hum, dbcs fonts are a PITA! */
2769 /* To display on W9x I have to convert to UCS */
2770 static wchar_t *uni_buf = 0;
2771 static int uni_len = 0;
2773 if (len > uni_len) {
2775 uni_buf = smalloc((uni_len = len) * sizeof(wchar_t));
2778 for(nlen = mptr = 0; mptr<len; mptr++) {
2779 uni_buf[nlen] = 0xFFFD;
2780 if (IsDBCSLeadByteEx(font_codepage, (BYTE) text[mptr])) {
2781 IpDx[nlen] += char_width;
2782 MultiByteToWideChar(font_codepage, MB_USEGLYPHCHARS,
2783 text+mptr, 2, uni_buf+nlen, 1);
2788 MultiByteToWideChar(font_codepage, MB_USEGLYPHCHARS,
2789 text+mptr, 1, uni_buf+nlen, 1);
2797 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2798 ETO_CLIPPED | ETO_OPAQUE, &line_box, uni_buf, nlen, IpDx);
2799 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2800 SetBkMode(hdc, TRANSPARENT);
2801 ExtTextOutW(hdc, x - 1,
2802 y - font_height * (lattr ==
2803 LATTR_BOT) + text_adjust,
2804 ETO_CLIPPED, &line_box, uni_buf, nlen, IpDx);
2808 } else if (DIRECT_FONT(attr)) {
2810 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2811 ETO_CLIPPED | ETO_OPAQUE, &line_box, text, len, IpDx);
2812 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2813 SetBkMode(hdc, TRANSPARENT);
2815 /* GRR: This draws the character outside it's box and can leave
2816 * 'droppings' even with the clip box! I suppose I could loop it
2817 * one character at a time ... yuk.
2819 * Or ... I could do a test print with "W", and use +1 or -1 for this
2820 * shift depending on if the leftmost column is blank...
2822 ExtTextOut(hdc, x - 1,
2823 y - font_height * (lattr ==
2824 LATTR_BOT) + text_adjust,
2825 ETO_CLIPPED, &line_box, text, len, IpDx);
2828 /* And 'normal' unicode characters */
2829 static WCHAR *wbuf = NULL;
2830 static int wlen = 0;
2835 wbuf = smalloc(wlen * sizeof(WCHAR));
2837 for (i = 0; i < len; i++)
2838 wbuf[i] = (WCHAR) ((attr & CSET_MASK) + (text[i] & CHAR_MASK));
2841 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2842 ETO_CLIPPED | ETO_OPAQUE, &line_box, wbuf, len, IpDx);
2844 /* And the shadow bold hack. */
2845 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2846 SetBkMode(hdc, TRANSPARENT);
2847 ExtTextOutW(hdc, x - 1,
2848 y - font_height * (lattr ==
2849 LATTR_BOT) + text_adjust,
2850 ETO_CLIPPED, &line_box, wbuf, len, IpDx);
2853 if (lattr != LATTR_TOP && (force_manual_underline ||
2854 (und_mode == UND_LINE
2855 && (attr & ATTR_UNDER)))) {
2858 if (lattr == LATTR_BOT)
2859 dec = dec * 2 - font_height;
2861 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, fg));
2862 MoveToEx(hdc, x, y + dec, NULL);
2863 LineTo(hdc, x + len * char_width, y + dec);
2864 oldpen = SelectObject(hdc, oldpen);
2865 DeleteObject(oldpen);
2869 void do_cursor(Context ctx, int x, int y, char *text, int len,
2870 unsigned long attr, int lattr)
2876 int ctype = cfg.cursor_type;
2878 if ((attr & TATTR_ACTCURS) && (ctype == 0 || big_cursor)) {
2879 if (((attr & CSET_MASK) | (unsigned char) *text) != UCSWIDE) {
2880 do_text(ctx, x, y, text, len, attr, lattr);
2884 attr |= TATTR_RIGHTCURS;
2887 fnt_width = char_width = font_width * (1 + (lattr != LATTR_NORM));
2888 if (attr & ATTR_WIDE)
2895 if ((attr & TATTR_PASCURS) && (ctype == 0 || big_cursor)) {
2898 pts[0].x = pts[1].x = pts[4].x = x;
2899 pts[2].x = pts[3].x = x + char_width - 1;
2900 pts[0].y = pts[3].y = pts[4].y = y;
2901 pts[1].y = pts[2].y = y + font_height - 1;
2902 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2903 Polyline(hdc, pts, 5);
2904 oldpen = SelectObject(hdc, oldpen);
2905 DeleteObject(oldpen);
2906 } else if ((attr & (TATTR_ACTCURS | TATTR_PASCURS)) && ctype != 0) {
2907 int startx, starty, dx, dy, length, i;
2910 starty = y + descent;
2913 length = char_width;
2916 if (attr & TATTR_RIGHTCURS)
2917 xadjust = char_width - 1;
2918 startx = x + xadjust;
2922 length = font_height;
2924 if (attr & TATTR_ACTCURS) {
2927 SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2928 MoveToEx(hdc, startx, starty, NULL);
2929 LineTo(hdc, startx + dx * length, starty + dy * length);
2930 oldpen = SelectObject(hdc, oldpen);
2931 DeleteObject(oldpen);
2933 for (i = 0; i < length; i++) {
2935 SetPixel(hdc, startx, starty, colours[23]);
2944 /* This function gets the actual width of a character in the normal font.
2946 int CharWidth(Context ctx, int uc) {
2950 /* If the font max is the same as the font ave width then this
2951 * function is a no-op.
2953 if (!font_dualwidth) return 1;
2955 switch (uc & CSET_MASK) {
2957 uc = unitab_line[uc & 0xFF];
2960 uc = unitab_xterm[uc & 0xFF];
2963 uc = unitab_scoacs[uc & 0xFF];
2966 if (DIRECT_FONT(uc)) {
2967 if (dbcs_screenfont) return 1;
2969 /* Speedup, I know of no font where ascii is the wrong width */
2970 if ((uc&CHAR_MASK) >= ' ' && (uc&CHAR_MASK)<= '~')
2973 if ( (uc & CSET_MASK) == ATTR_ACP ) {
2974 SelectObject(hdc, fonts[FONT_NORMAL]);
2975 } else if ( (uc & CSET_MASK) == ATTR_OEMCP ) {
2976 another_font(FONT_OEM);
2977 if (!fonts[FONT_OEM]) return 0;
2979 SelectObject(hdc, fonts[FONT_OEM]);
2983 if ( GetCharWidth32(hdc, uc&CHAR_MASK, uc&CHAR_MASK, &ibuf) != 1 &&
2984 GetCharWidth(hdc, uc&CHAR_MASK, uc&CHAR_MASK, &ibuf) != 1)
2987 /* Speedup, I know of no font where ascii is the wrong width */
2988 if (uc >= ' ' && uc <= '~') return 1;
2990 SelectObject(hdc, fonts[FONT_NORMAL]);
2991 if ( GetCharWidth32W(hdc, uc, uc, &ibuf) == 1 )
2992 /* Okay that one worked */ ;
2993 else if ( GetCharWidthW(hdc, uc, uc, &ibuf) == 1 )
2994 /* This should work on 9x too, but it's "less accurate" */ ;
2999 ibuf += font_width / 2 -1;
3006 * Translate a WM_(SYS)?KEY(UP|DOWN) message into a string of ASCII
3007 * codes. Returns number of bytes used or zero to drop the message
3008 * or -1 to forward the message to windows.
3010 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
3011 unsigned char *output)
3014 int scan, left_alt = 0, key_down, shift_state;
3016 unsigned char *p = output;
3017 static int alt_sum = 0;
3019 HKL kbd_layout = GetKeyboardLayout(0);
3021 static WORD keys[3];
3022 static int compose_char = 0;
3023 static WPARAM compose_key = 0;
3025 r = GetKeyboardState(keystate);
3027 memset(keystate, 0, sizeof(keystate));
3030 #define SHOW_TOASCII_RESULT
3031 { /* Tell us all about key events */
3032 static BYTE oldstate[256];
3033 static int first = 1;
3037 memcpy(oldstate, keystate, sizeof(oldstate));
3040 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT) {
3042 } else if ((HIWORD(lParam) & KF_UP)
3043 && scan == (HIWORD(lParam) & 0xFF)) {
3047 if (wParam >= VK_F1 && wParam <= VK_F20)
3048 debug(("K_F%d", wParam + 1 - VK_F1));
3061 debug(("VK_%02x", wParam));
3063 if (message == WM_SYSKEYDOWN || message == WM_SYSKEYUP)
3065 debug((", S%02x", scan = (HIWORD(lParam) & 0xFF)));
3067 ch = MapVirtualKeyEx(wParam, 2, kbd_layout);
3068 if (ch >= ' ' && ch <= '~')
3069 debug((", '%c'", ch));
3071 debug((", $%02x", ch));
3074 debug((", KB0=%02x", keys[0]));
3076 debug((", KB1=%02x", keys[1]));
3078 debug((", KB2=%02x", keys[2]));
3080 if ((keystate[VK_SHIFT] & 0x80) != 0)
3082 if ((keystate[VK_CONTROL] & 0x80) != 0)
3084 if ((HIWORD(lParam) & KF_EXTENDED))
3086 if ((HIWORD(lParam) & KF_UP))
3090 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT);
3091 else if ((HIWORD(lParam) & KF_UP))
3092 oldstate[wParam & 0xFF] ^= 0x80;
3094 oldstate[wParam & 0xFF] ^= 0x81;
3096 for (ch = 0; ch < 256; ch++)
3097 if (oldstate[ch] != keystate[ch])
3098 debug((", M%02x=%02x", ch, keystate[ch]));
3100 memcpy(oldstate, keystate, sizeof(oldstate));
3104 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED)) {
3105 keystate[VK_RMENU] = keystate[VK_MENU];
3109 /* Nastyness with NUMLock - Shift-NUMLock is left alone though */
3110 if ((cfg.funky_type == 3 ||
3111 (cfg.funky_type <= 1 && app_keypad_keys && !cfg.no_applic_k))
3112 && wParam == VK_NUMLOCK && !(keystate[VK_SHIFT] & 0x80)) {
3114 wParam = VK_EXECUTE;
3116 /* UnToggle NUMLock */
3117 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0)
3118 keystate[VK_NUMLOCK] ^= 1;
3121 /* And write back the 'adjusted' state */
3122 SetKeyboardState(keystate);
3125 /* Disable Auto repeat if required */
3126 if (repeat_off && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT)
3129 if ((HIWORD(lParam) & KF_ALTDOWN) && (keystate[VK_RMENU] & 0x80) == 0)
3132 key_down = ((HIWORD(lParam) & KF_UP) == 0);
3134 /* Make sure Ctrl-ALT is not the same as AltGr for ToAscii unless told. */
3135 if (left_alt && (keystate[VK_CONTROL] & 0x80)) {
3136 if (cfg.ctrlaltkeys)
3137 keystate[VK_MENU] = 0;
3139 keystate[VK_RMENU] = 0x80;
3144 alt_pressed = (left_alt && key_down);
3146 scan = (HIWORD(lParam) & (KF_UP | KF_EXTENDED | 0xFF));
3147 shift_state = ((keystate[VK_SHIFT] & 0x80) != 0)
3148 + ((keystate[VK_CONTROL] & 0x80) != 0) * 2;
3150 /* Note if AltGr was pressed and if it was used as a compose key */
3151 if (!compose_state) {
3152 compose_key = 0x100;
3153 if (cfg.compose_key) {
3154 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED))
3155 compose_key = wParam;
3157 if (wParam == VK_APPS)
3158 compose_key = wParam;
3161 if (wParam == compose_key) {
3162 if (compose_state == 0
3163 && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0) compose_state =
3165 else if (compose_state == 1 && (HIWORD(lParam) & KF_UP))
3169 } else if (compose_state == 1 && wParam != VK_CONTROL)
3173 * Record that we pressed key so the scroll window can be reset, but
3174 * be careful to avoid Shift-UP/Down
3176 if (wParam != VK_SHIFT && wParam != VK_PRIOR && wParam != VK_NEXT &&
3177 wParam != VK_MENU && wParam != VK_CONTROL) {
3181 if (compose_state > 1 && left_alt)
3184 /* Sanitize the number pad if not using a PC NumPad */
3185 if (left_alt || (app_keypad_keys && !cfg.no_applic_k
3186 && cfg.funky_type != 2)
3187 || cfg.funky_type == 3 || cfg.nethack_keypad || compose_state) {
3188 if ((HIWORD(lParam) & KF_EXTENDED) == 0) {
3192 nParam = VK_NUMPAD0;
3195 nParam = VK_NUMPAD1;
3198 nParam = VK_NUMPAD2;
3201 nParam = VK_NUMPAD3;
3204 nParam = VK_NUMPAD4;
3207 nParam = VK_NUMPAD5;
3210 nParam = VK_NUMPAD6;
3213 nParam = VK_NUMPAD7;
3216 nParam = VK_NUMPAD8;
3219 nParam = VK_NUMPAD9;
3222 nParam = VK_DECIMAL;
3226 if (keystate[VK_NUMLOCK] & 1)
3233 /* If a key is pressed and AltGr is not active */
3234 if (key_down && (keystate[VK_RMENU] & 0x80) == 0 && !compose_state) {
3235 /* Okay, prepare for most alts then ... */
3239 /* Lets see if it's a pattern we know all about ... */
3240 if (wParam == VK_PRIOR && shift_state == 1) {
3241 SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0);
3244 if (wParam == VK_NEXT && shift_state == 1) {
3245 SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
3248 if (wParam == VK_INSERT && shift_state == 1) {
3252 if (left_alt && wParam == VK_F4 && cfg.alt_f4) {
3255 if (left_alt && wParam == VK_SPACE && cfg.alt_space) {
3256 SendMessage(hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0);
3259 if (left_alt && wParam == VK_RETURN && cfg.fullscreenonaltenter &&
3260 (cfg.resize_action != RESIZE_DISABLED)) {
3261 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) != KF_REPEAT)
3265 /* Control-Numlock for app-keypad mode switch */
3266 if (wParam == VK_PAUSE && shift_state == 2) {
3267 app_keypad_keys ^= 1;
3271 /* Nethack keypad */
3272 if (cfg.nethack_keypad && !left_alt) {
3275 *p++ = shift_state ? 'B' : 'b';
3278 *p++ = shift_state ? 'J' : 'j';
3281 *p++ = shift_state ? 'N' : 'n';
3284 *p++ = shift_state ? 'H' : 'h';
3287 *p++ = shift_state ? '.' : '.';
3290 *p++ = shift_state ? 'L' : 'l';
3293 *p++ = shift_state ? 'Y' : 'y';
3296 *p++ = shift_state ? 'K' : 'k';
3299 *p++ = shift_state ? 'U' : 'u';
3304 /* Application Keypad */
3308 if (cfg.funky_type == 3 ||
3309 (cfg.funky_type <= 1 &&
3310 app_keypad_keys && !cfg.no_applic_k)) switch (wParam) {
3324 if (app_keypad_keys && !cfg.no_applic_k)
3361 if (cfg.funky_type == 2) {
3366 } else if (shift_state)
3373 if (cfg.funky_type == 2)
3377 if (cfg.funky_type == 2)
3381 if (cfg.funky_type == 2)
3386 if (HIWORD(lParam) & KF_EXTENDED)
3392 if (xkey >= 'P' && xkey <= 'S')
3393 p += sprintf((char *) p, "\x1B%c", xkey);
3395 p += sprintf((char *) p, "\x1B?%c", xkey);
3397 p += sprintf((char *) p, "\x1BO%c", xkey);
3402 if (wParam == VK_BACK && shift_state == 0) { /* Backspace */
3403 *p++ = (cfg.bksp_is_delete ? 0x7F : 0x08);
3407 if (wParam == VK_TAB && shift_state == 1) { /* Shift tab */
3413 if (wParam == VK_SPACE && shift_state == 2) { /* Ctrl-Space */
3417 if (wParam == VK_SPACE && shift_state == 3) { /* Ctrl-Shift-Space */
3421 if (wParam == VK_CANCEL && shift_state == 2) { /* Ctrl-Break */
3426 if (wParam == VK_PAUSE) { /* Break/Pause */
3431 /* Control-2 to Control-8 are special */
3432 if (shift_state == 2 && wParam >= '2' && wParam <= '8') {
3433 *p++ = "\000\033\034\035\036\037\177"[wParam - '2'];
3436 if (shift_state == 2 && wParam == 0xBD) {
3440 if (shift_state == 2 && wParam == 0xDF) {
3444 if (shift_state == 0 && wParam == VK_RETURN && cr_lf_return) {
3451 * Next, all the keys that do tilde codes. (ESC '[' nn '~',
3452 * for integer decimal nn.)
3454 * We also deal with the weird ones here. Linux VCs replace F1
3455 * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
3456 * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
3462 code = (keystate[VK_SHIFT] & 0x80 ? 23 : 11);
3465 code = (keystate[VK_SHIFT] & 0x80 ? 24 : 12);
3468 code = (keystate[VK_SHIFT] & 0x80 ? 25 : 13);
3471 code = (keystate[VK_SHIFT] & 0x80 ? 26 : 14);
3474 code = (keystate[VK_SHIFT] & 0x80 ? 28 : 15);
3477 code = (keystate[VK_SHIFT] & 0x80 ? 29 : 17);
3480 code = (keystate[VK_SHIFT] & 0x80 ? 31 : 18);
3483 code = (keystate[VK_SHIFT] & 0x80 ? 32 : 19);
3486 code = (keystate[VK_SHIFT] & 0x80 ? 33 : 20);
3489 code = (keystate[VK_SHIFT] & 0x80 ? 34 : 21);
3522 if ((shift_state&2) == 0) switch (wParam) {
3542 /* Reorder edit keys to physical order */
3543 if (cfg.funky_type == 3 && code <= 6)
3544 code = "\0\2\1\4\5\3\6"[code];
3546 if (vt52_mode && code > 0 && code <= 6) {
3547 p += sprintf((char *) p, "\x1B%c", " HLMEIG"[code]);
3551 if (cfg.funky_type == 5 && /* SCO function keys */
3552 code >= 11 && code <= 34) {
3553 char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
3556 case VK_F1: index = 0; break;
3557 case VK_F2: index = 1; break;
3558 case VK_F3: index = 2; break;
3559 case VK_F4: index = 3; break;
3560 case VK_F5: index = 4; break;
3561 case VK_F6: index = 5; break;
3562 case VK_F7: index = 6; break;
3563 case VK_F8: index = 7; break;
3564 case VK_F9: index = 8; break;
3565 case VK_F10: index = 9; break;
3566 case VK_F11: index = 10; break;
3567 case VK_F12: index = 11; break;
3569 if (keystate[VK_SHIFT] & 0x80) index += 12;
3570 if (keystate[VK_CONTROL] & 0x80) index += 24;
3571 p += sprintf((char *) p, "\x1B[%c", codes[index]);
3574 if (cfg.funky_type == 5 && /* SCO small keypad */
3575 code >= 1 && code <= 6) {
3576 char codes[] = "HL.FIG";
3580 p += sprintf((char *) p, "\x1B[%c", codes[code-1]);
3584 if ((vt52_mode || cfg.funky_type == 4) && code >= 11 && code <= 24) {
3591 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11 - offt);
3594 sprintf((char *) p, "\x1BO%c", code + 'P' - 11 - offt);
3597 if (cfg.funky_type == 1 && code >= 11 && code <= 15) {
3598 p += sprintf((char *) p, "\x1B[[%c", code + 'A' - 11);
3601 if (cfg.funky_type == 2 && code >= 11 && code <= 14) {
3603 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11);
3605 p += sprintf((char *) p, "\x1BO%c", code + 'P' - 11);
3608 if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
3609 p += sprintf((char *) p, code == 1 ? "\x1B[H" : "\x1BOw");
3613 p += sprintf((char *) p, "\x1B[%d~", code);
3618 * Now the remaining keys (arrows and Keypad 5. Keypad 5 for
3619 * some reason seems to send VK_CLEAR to Windows...).
3642 p += sprintf((char *) p, "\x1B%c", xkey);
3644 int app_flg = (app_cursor_keys && !cfg.no_applic_c);
3647 * RDB: VT100 & VT102 manuals both state the
3648 * app cursor keys only work if the app keypad
3651 * SGT: That may well be true, but xterm
3652 * disagrees and so does at least one
3653 * application, so I've #if'ed this out and the
3654 * behaviour is back to PuTTY's original: app
3655 * cursor and app keypad are independently
3656 * switchable modes. If anyone complains about
3657 * _this_ I'll have to put in a configurable
3660 if (!app_keypad_keys)
3663 /* Useful mapping of Ctrl-arrows */
3664 if (shift_state == 2)
3668 p += sprintf((char *) p, "\x1BO%c", xkey);
3670 p += sprintf((char *) p, "\x1B[%c", xkey);
3677 * Finally, deal with Return ourselves. (Win95 seems to
3678 * foul it up when Alt is pressed, for some reason.)
3680 if (wParam == VK_RETURN) { /* Return */
3686 if (left_alt && wParam >= VK_NUMPAD0 && wParam <= VK_NUMPAD9)
3687 alt_sum = alt_sum * 10 + wParam - VK_NUMPAD0;
3692 /* Okay we've done everything interesting; let windows deal with
3693 * the boring stuff */
3697 /* helg: clear CAPS LOCK state if caps lock switches to cyrillic */
3698 if(cfg.xlat_capslockcyr && keystate[VK_CAPITAL] != 0) {
3700 keystate[VK_CAPITAL] = 0;
3703 r = ToAsciiEx(wParam, scan, keystate, keys, 0, kbd_layout);
3704 #ifdef SHOW_TOASCII_RESULT
3705 if (r == 1 && !key_down) {
3707 if (in_utf || dbcs_screenfont)
3708 debug((", (U+%04x)", alt_sum));
3710 debug((", LCH(%d)", alt_sum));
3712 debug((", ACH(%d)", keys[0]));
3717 for (r1 = 0; r1 < r; r1++) {
3718 debug(("%s%d", r1 ? "," : "", keys[r1]));
3727 * Interrupt an ongoing paste. I'm not sure this is
3728 * sensible, but for the moment it's preferable to
3729 * having to faff about buffering things.
3734 for (i = 0; i < r; i++) {
3735 unsigned char ch = (unsigned char) keys[i];
3737 if (compose_state == 2 && (ch & 0x80) == 0 && ch > ' ') {
3742 if (compose_state == 3 && (ch & 0x80) == 0 && ch > ' ') {
3746 if ((nc = check_compose(compose_char, ch)) == -1) {
3747 MessageBeep(MB_ICONHAND);
3751 luni_send(&keybuf, 1, 1);
3759 if (in_utf || dbcs_screenfont) {
3761 luni_send(&keybuf, 1, 1);
3763 ch = (char) alt_sum;
3765 * We need not bother about stdin
3766 * backlogs here, because in GUI PuTTY
3767 * we can't do anything about it
3768 * anyway; there's no means of asking
3769 * Windows to hold off on KEYDOWN
3770 * messages. We _have_ to buffer
3771 * everything we're sent.
3773 ldisc_send(&ch, 1, 1);
3777 lpage_send(kbd_codepage, &ch, 1, 1);
3779 if(capsOn && ch < 0x80) {
3782 cbuf[1] = xlat_uskbd2cyrllic(ch);
3783 luni_send(cbuf+!left_alt, 1+!!left_alt, 1);
3788 lpage_send(kbd_codepage, cbuf+!left_alt, 1+!!left_alt, 1);
3794 /* This is so the ALT-Numpad and dead keys work correctly. */
3799 /* If we're definitly not building up an ALT-54321 then clear it */
3802 /* If we will be using alt_sum fix the 256s */
3803 else if (keys[0] && (in_utf || dbcs_screenfont))
3808 * ALT alone may or may not want to bring up the System menu.
3809 * If it's not meant to, we return 0 on presses or releases of
3810 * ALT, to show that we've swallowed the keystroke. Otherwise
3811 * we return -1, which means Windows will give the keystroke
3812 * its default handling (i.e. bring up the System menu).
3814 if (wParam == VK_MENU && !cfg.alt_only)
3820 void set_title(char *title)
3823 window_name = smalloc(1 + strlen(title));
3824 strcpy(window_name, title);
3825 if (cfg.win_name_always || !IsIconic(hwnd))
3826 SetWindowText(hwnd, title);
3829 void set_icon(char *title)
3832 icon_name = smalloc(1 + strlen(title));
3833 strcpy(icon_name, title);
3834 if (!cfg.win_name_always && IsIconic(hwnd))
3835 SetWindowText(hwnd, title);
3838 void set_sbar(int total, int start, int page)
3842 if (is_full_screen() ? !cfg.scrollbar_in_fullscreen : !cfg.scrollbar)
3845 si.cbSize = sizeof(si);
3846 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
3848 si.nMax = total - 1;
3852 SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
3855 Context get_ctx(void)
3861 SelectPalette(hdc, pal, FALSE);
3867 void free_ctx(Context ctx)
3869 SelectPalette(ctx, GetStockObject(DEFAULT_PALETTE), FALSE);
3870 ReleaseDC(hwnd, ctx);
3873 static void real_palette_set(int n, int r, int g, int b)
3876 logpal->palPalEntry[n].peRed = r;
3877 logpal->palPalEntry[n].peGreen = g;
3878 logpal->palPalEntry[n].peBlue = b;
3879 logpal->palPalEntry[n].peFlags = PC_NOCOLLAPSE;
3880 colours[n] = PALETTERGB(r, g, b);
3881 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3883 colours[n] = RGB(r, g, b);
3886 void palette_set(int n, int r, int g, int b)
3888 static const int first[21] = {
3889 0, 2, 4, 6, 8, 10, 12, 14,
3890 1, 3, 5, 7, 9, 11, 13, 15,
3893 real_palette_set(first[n], r, g, b);
3895 real_palette_set(first[n] + 1, r, g, b);
3897 HDC hdc = get_ctx();
3898 UnrealizeObject(pal);
3899 RealizePalette(hdc);
3904 void palette_reset(void)
3908 for (i = 0; i < NCOLOURS; i++) {
3910 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
3911 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
3912 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
3913 logpal->palPalEntry[i].peFlags = 0;
3914 colours[i] = PALETTERGB(defpal[i].rgbtRed,
3915 defpal[i].rgbtGreen,
3916 defpal[i].rgbtBlue);
3918 colours[i] = RGB(defpal[i].rgbtRed,
3919 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
3924 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3926 RealizePalette(hdc);
3931 void write_aclip(char *data, int len, int must_deselect)
3936 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len + 1);
3939 lock = GlobalLock(clipdata);
3942 memcpy(lock, data, len);
3943 ((unsigned char *) lock)[len] = 0;
3944 GlobalUnlock(clipdata);
3947 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
3949 if (OpenClipboard(hwnd)) {
3951 SetClipboardData(CF_TEXT, clipdata);
3954 GlobalFree(clipdata);
3957 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
3961 * Note: unlike write_aclip() this will not append a nul.
3963 void write_clip(wchar_t * data, int len, int must_deselect)
3965 HGLOBAL clipdata, clipdata2, clipdata3;
3967 void *lock, *lock2, *lock3;
3969 len2 = WideCharToMultiByte(CP_ACP, 0, data, len, 0, 0, NULL, NULL);
3971 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE,
3972 len * sizeof(wchar_t));
3973 clipdata2 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len2);
3975 if (!clipdata || !clipdata2) {
3977 GlobalFree(clipdata);
3979 GlobalFree(clipdata2);
3982 if (!(lock = GlobalLock(clipdata)))
3984 if (!(lock2 = GlobalLock(clipdata2)))
3987 memcpy(lock, data, len * sizeof(wchar_t));
3988 WideCharToMultiByte(CP_ACP, 0, data, len, lock2, len2, NULL, NULL);
3990 if (cfg.rtf_paste) {
3991 wchar_t unitab[256];
3993 unsigned char *tdata = (unsigned char *)lock2;
3994 wchar_t *udata = (wchar_t *)lock;
3995 int rtflen = 0, uindex = 0, tindex = 0;
3997 int multilen, blen, alen, totallen, i;
3998 char before[16], after[4];
4000 get_unitab(CP_ACP, unitab, 0);
4002 rtfsize = 100 + strlen(cfg.font);
4003 rtf = smalloc(rtfsize);
4004 sprintf(rtf, "{\\rtf1\\ansi%d{\\fonttbl\\f0\\fmodern %s;}\\f0",
4005 GetACP(), cfg.font);
4006 rtflen = strlen(rtf);
4009 * We want to construct a piece of RTF that specifies the
4010 * same Unicode text. To do this we will read back in
4011 * parallel from the Unicode data in `udata' and the
4012 * non-Unicode data in `tdata'. For each character in
4013 * `tdata' which becomes the right thing in `udata' when
4014 * looked up in `unitab', we just copy straight over from
4015 * tdata. For each one that doesn't, we must WCToMB it
4016 * individually and produce a \u escape sequence.
4018 * It would probably be more robust to just bite the bullet
4019 * and WCToMB each individual Unicode character one by one,
4020 * then MBToWC each one back to see if it was an accurate
4021 * translation; but that strikes me as a horrifying number
4022 * of Windows API calls so I want to see if this faster way
4023 * will work. If it screws up badly we can always revert to
4024 * the simple and slow way.
4026 while (tindex < len2 && uindex < len &&
4027 tdata[tindex] && udata[uindex]) {
4028 if (tindex + 1 < len2 &&
4029 tdata[tindex] == '\r' &&
4030 tdata[tindex+1] == '\n') {
4034 if (unitab[tdata[tindex]] == udata[uindex]) {
4040 multilen = WideCharToMultiByte(CP_ACP, 0, unitab+uindex, 1,
4041 NULL, 0, NULL, NULL);
4042 if (multilen != 1) {
4043 blen = sprintf(before, "{\\uc%d\\u%d", multilen,
4045 alen = 1; strcpy(after, "}");
4047 blen = sprintf(before, "\\u%d", udata[uindex]);
4048 alen = 0; after[0] = '\0';
4051 assert(tindex + multilen <= len2);
4052 totallen = blen + alen;
4053 for (i = 0; i < multilen; i++) {
4054 if (tdata[tindex+i] == '\\' ||
4055 tdata[tindex+i] == '{' ||
4056 tdata[tindex+i] == '}')
4058 else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A)
4059 totallen += 6; /* \par\r\n */
4060 else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20)
4066 if (rtfsize < rtflen + totallen + 3) {
4067 rtfsize = rtflen + totallen + 512;
4068 rtf = srealloc(rtf, rtfsize);
4071 strcpy(rtf + rtflen, before); rtflen += blen;
4072 for (i = 0; i < multilen; i++) {
4073 if (tdata[tindex+i] == '\\' ||
4074 tdata[tindex+i] == '{' ||
4075 tdata[tindex+i] == '}') {
4076 rtf[rtflen++] = '\\';
4077 rtf[rtflen++] = tdata[tindex+i];
4078 } else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A) {
4079 rtflen += sprintf(rtf+rtflen, "\\par\r\n");
4080 } else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20) {
4081 rtflen += sprintf(rtf+rtflen, "\\'%02x", tdata[tindex+i]);
4083 rtf[rtflen++] = tdata[tindex+i];
4086 strcpy(rtf + rtflen, after); rtflen += alen;
4092 strcpy(rtf + rtflen, "}");
4095 clipdata3 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, rtflen);
4096 if (clipdata3 && (lock3 = GlobalLock(clipdata3)) != NULL) {
4098 GlobalUnlock(clipdata3);
4104 GlobalUnlock(clipdata);
4105 GlobalUnlock(clipdata2);
4108 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
4110 if (OpenClipboard(hwnd)) {
4112 SetClipboardData(CF_UNICODETEXT, clipdata);
4113 SetClipboardData(CF_TEXT, clipdata2);
4115 SetClipboardData(RegisterClipboardFormat(CF_RTF), clipdata3);
4118 GlobalFree(clipdata);
4119 GlobalFree(clipdata2);
4123 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
4126 void get_clip(wchar_t ** p, int *len)
4128 static HGLOBAL clipdata = NULL;
4129 static wchar_t *converted = 0;
4138 GlobalUnlock(clipdata);
4141 } else if (OpenClipboard(NULL)) {
4142 if ((clipdata = GetClipboardData(CF_UNICODETEXT))) {
4144 *p = GlobalLock(clipdata);
4146 for (p2 = *p; *p2; p2++);
4150 } else if ( (clipdata = GetClipboardData(CF_TEXT)) ) {
4154 s = GlobalLock(clipdata);
4155 i = MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, 0, 0);
4156 *p = converted = smalloc(i * sizeof(wchar_t));
4157 MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, converted, i);
4170 * Move `lines' lines from position `from' to position `to' in the
4173 void optimised_move(int to, int from, int lines)
4178 min = (to < from ? to : from);
4179 max = to + from - min;
4181 r.left = offset_width;
4182 r.right = offset_width + cols * font_width;
4183 r.top = offset_height + min * font_height;
4184 r.bottom = offset_height + (max + lines) * font_height;
4185 ScrollWindow(hwnd, 0, (to - from) * font_height, &r, &r);
4190 * Print a message box and perform a fatal exit.
4192 void fatalbox(char *fmt, ...)
4198 vsprintf(stuff, fmt, ap);
4200 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
4205 * Manage window caption / taskbar flashing, if enabled.
4206 * 0 = stop, 1 = maintain, 2 = start
4208 static void flash_window(int mode)
4210 static long last_flash = 0;
4211 static int flashing = 0;
4212 if ((mode == 0) || (cfg.beep_ind == B_IND_DISABLED)) {
4215 FlashWindow(hwnd, FALSE);
4219 } else if (mode == 2) {
4222 last_flash = GetTickCount();
4224 FlashWindow(hwnd, TRUE);
4227 } else if ((mode == 1) && (cfg.beep_ind == B_IND_FLASH)) {
4230 long now = GetTickCount();
4231 long fdiff = now - last_flash;
4232 if (fdiff < 0 || fdiff > 450) {
4234 FlashWindow(hwnd, TRUE); /* toggle */
4245 if (mode == BELL_DEFAULT) {
4247 * For MessageBeep style bells, we want to be careful of
4248 * timing, because they don't have the nice property of
4249 * PlaySound bells that each one cancels the previous
4250 * active one. So we limit the rate to one per 50ms or so.
4252 static long lastbeep = 0;
4255 beepdiff = GetTickCount() - lastbeep;
4256 if (beepdiff >= 0 && beepdiff < 50)
4260 * The above MessageBeep call takes time, so we record the
4261 * time _after_ it finishes rather than before it starts.
4263 lastbeep = GetTickCount();
4264 } else if (mode == BELL_WAVEFILE) {
4265 if (!PlaySound(cfg.bell_wavefile, NULL, SND_ASYNC | SND_FILENAME)) {
4266 char buf[sizeof(cfg.bell_wavefile) + 80];
4267 sprintf(buf, "Unable to play sound file\n%s\n"
4268 "Using default sound instead", cfg.bell_wavefile);
4269 MessageBox(hwnd, buf, "PuTTY Sound Error",
4270 MB_OK | MB_ICONEXCLAMATION);
4271 cfg.beep = BELL_DEFAULT;
4274 /* Otherwise, either visual bell or disabled; do nothing here */
4276 flash_window(2); /* start */
4281 * Minimise or restore the window in response to a server-side
4284 void set_iconic(int iconic)
4286 if (IsIconic(hwnd)) {
4288 ShowWindow(hwnd, SW_RESTORE);
4291 ShowWindow(hwnd, SW_MINIMIZE);
4296 * Move the window in response to a server-side request.
4298 void move_window(int x, int y)
4300 if (cfg.resize_action == RESIZE_DISABLED ||
4301 cfg.resize_action == RESIZE_FONT ||
4305 SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
4309 * Move the window to the top or bottom of the z-order in response
4310 * to a server-side request.
4312 void set_zorder(int top)
4314 if (cfg.alwaysontop)
4315 return; /* ignore */
4316 SetWindowPos(hwnd, top ? HWND_TOP : HWND_BOTTOM, 0, 0, 0, 0,
4317 SWP_NOMOVE | SWP_NOSIZE);
4321 * Refresh the window in response to a server-side request.
4323 void refresh_window(void)
4325 InvalidateRect(hwnd, NULL, TRUE);
4329 * Maximise or restore the window in response to a server-side
4332 void set_zoomed(int zoomed)
4334 if (IsZoomed(hwnd)) {
4336 ShowWindow(hwnd, SW_RESTORE);
4339 ShowWindow(hwnd, SW_MAXIMIZE);
4344 * Report whether the window is iconic, for terminal reports.
4348 return IsIconic(hwnd);
4352 * Report the window's position, for terminal reports.
4354 void get_window_pos(int *x, int *y)
4357 GetWindowRect(hwnd, &r);
4363 * Report the window's pixel size, for terminal reports.
4365 void get_window_pixels(int *x, int *y)
4368 GetWindowRect(hwnd, &r);
4369 *x = r.right - r.left;
4370 *y = r.bottom - r.top;
4374 * Return the window or icon title.
4376 char *get_window_title(int icon)
4378 return icon ? icon_name : window_name;
4382 * See if we're in full-screen mode.
4384 int is_full_screen()
4386 if (!IsZoomed(hwnd))
4388 if (GetWindowLong(hwnd, GWL_STYLE) & WS_CAPTION)
4393 /* Get the rect/size of a full screen window using the nearest available
4394 * monitor in multimon systems; default to something sensible if only
4395 * one monitor is present. */
4396 static int get_fullscreen_rect(RECT * ss)
4398 #ifdef MONITOR_DEFAULTTONEAREST
4401 mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
4402 mi.cbSize = sizeof(mi);
4403 GetMonitorInfo(mon, &mi);
4405 /* structure copy */
4409 /* could also use code like this:
4410 ss->left = ss->top = 0;
4411 ss->right = GetSystemMetrics(SM_CXSCREEN);
4412 ss->bottom = GetSystemMetrics(SM_CYSCREEN);
4414 return GetClientRect(GetDesktopWindow(), ss);
4420 * Go full-screen. This should only be called when we are already
4423 void make_full_screen()
4428 assert(IsZoomed(hwnd));
4430 if (is_full_screen())
4433 /* Remove the window furniture. */
4434 style = GetWindowLong(hwnd, GWL_STYLE);
4435 style &= ~(WS_CAPTION | WS_BORDER | WS_THICKFRAME);
4436 if (cfg.scrollbar_in_fullscreen)
4437 style |= WS_VSCROLL;
4439 style &= ~WS_VSCROLL;
4440 SetWindowLong(hwnd, GWL_STYLE, style);
4442 /* Resize ourselves to exactly cover the nearest monitor. */
4443 get_fullscreen_rect(&ss);
4444 SetWindowPos(hwnd, HWND_TOP, ss.left, ss.top,
4449 /* Tick the menu item in the System menu. */
4450 CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN,
4455 * Clear the full-screen attributes.
4457 void clear_full_screen()
4459 DWORD oldstyle, style;
4461 /* Reinstate the window furniture. */
4462 style = oldstyle = GetWindowLong(hwnd, GWL_STYLE);
4463 style |= WS_CAPTION | WS_BORDER;
4464 if (cfg.resize_action == RESIZE_DISABLED)
4465 style &= ~WS_THICKFRAME;
4467 style |= WS_THICKFRAME;
4469 style |= WS_VSCROLL;
4471 style &= ~WS_VSCROLL;
4472 if (style != oldstyle) {
4473 SetWindowLong(hwnd, GWL_STYLE, style);
4474 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
4475 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
4479 /* Untick the menu item in the System menu. */
4480 CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN,
4485 * Toggle full-screen mode.
4487 void flip_full_screen()
4489 if (is_full_screen()) {
4490 ShowWindow(hwnd, SW_RESTORE);
4491 } else if (IsZoomed(hwnd)) {
4494 SendMessage(hwnd, WM_FULLSCR_ON_MAX, 0, 0);
4495 ShowWindow(hwnd, SW_MAXIMIZE);