16 #define COMPILE_MULTIMON_STUBS
27 #define PUTTY_DO_GLOBALS /* actually _define_ globals */
34 #define IDM_SHOWLOG 0x0010
35 #define IDM_NEWSESS 0x0020
36 #define IDM_DUPSESS 0x0030
37 #define IDM_RECONF 0x0040
38 #define IDM_CLRSB 0x0050
39 #define IDM_RESET 0x0060
40 #define IDM_TEL_AYT 0x0070
41 #define IDM_TEL_BRK 0x0080
42 #define IDM_TEL_SYNCH 0x0090
43 #define IDM_TEL_EC 0x00a0
44 #define IDM_TEL_EL 0x00b0
45 #define IDM_TEL_GA 0x00c0
46 #define IDM_TEL_NOP 0x00d0
47 #define IDM_TEL_ABORT 0x00e0
48 #define IDM_TEL_AO 0x00f0
49 #define IDM_TEL_IP 0x0100
50 #define IDM_TEL_SUSP 0x0110
51 #define IDM_TEL_EOR 0x0120
52 #define IDM_TEL_EOF 0x0130
53 #define IDM_HELP 0x0140
54 #define IDM_ABOUT 0x0150
55 #define IDM_SAVEDSESS 0x0160
56 #define IDM_COPYALL 0x0170
57 #define IDM_FULLSCREEN 0x0180
59 #define IDM_SESSLGP 0x0250 /* log type printable */
60 #define IDM_SESSLGA 0x0260 /* log type all chars */
61 #define IDM_SESSLGE 0x0270 /* log end */
62 #define IDM_SAVED_MIN 0x1000
63 #define IDM_SAVED_MAX 0x2000
65 #define WM_IGNORE_CLIP (WM_XUSER + 2)
66 #define WM_FULLSCR_ON_MAX (WM_XUSER + 3)
68 /* Needed for Chinese support and apparently not always defined. */
70 #define VK_PROCESSKEY 0xE5
73 /* Mouse wheel support. */
75 #define WM_MOUSEWHEEL 0x020A /* not defined in earlier SDKs */
78 #define WHEEL_DELTA 120
81 static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
82 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
83 unsigned char *output);
84 static void cfgtopalette(void);
85 static void init_palette(void);
86 static void init_fonts(int, int);
87 static void another_font(int);
88 static void deinit_fonts(void);
89 static void set_input_locale(HKL);
90 static int do_mouse_wheel_msg(UINT message, WPARAM wParam, LPARAM lParam);
92 static int is_full_screen(void);
93 static void make_full_screen(void);
94 static void clear_full_screen(void);
95 static void flip_full_screen(void);
97 /* Window layout information */
98 static void reset_window(int);
99 static int extra_width, extra_height;
100 static int font_width, font_height, font_dualwidth;
101 static int offset_width, offset_height;
102 static int was_zoomed = 0;
103 static int prev_rows, prev_cols;
105 static int pending_netevent = 0;
106 static WPARAM pend_netevent_wParam = 0;
107 static LPARAM pend_netevent_lParam = 0;
108 static void enact_pending_netevent(void);
109 static void flash_window(int mode);
110 static void sys_cursor_update(void);
111 static int is_shift_pressed(void);
112 static int get_fullscreen_rect(RECT * ss);
114 static time_t last_movement = 0;
116 static int caret_x = -1, caret_y = -1;
119 static Backend *back;
120 static void *backhandle;
122 #define FONT_NORMAL 0
124 #define FONT_UNDERLINE 2
125 #define FONT_BOLDUND 3
126 #define FONT_WIDE 0x04
127 #define FONT_HIGH 0x08
128 #define FONT_NARROW 0x10
130 #define FONT_OEM 0x20
131 #define FONT_OEMBOLD 0x21
132 #define FONT_OEMUND 0x22
133 #define FONT_OEMBOLDUND 0x23
135 #define FONT_MAXNO 0x2F
137 static HFONT fonts[FONT_MAXNO];
138 static LOGFONT lfont;
139 static int fontflag[FONT_MAXNO];
141 BOLD_COLOURS, BOLD_SHADOW, BOLD_FONT
149 static COLORREF colours[NCOLOURS];
151 static LPLOGPALETTE logpal;
152 static RGBTRIPLE defpal[NCOLOURS];
156 static HBITMAP caretbm;
158 static int dbltime, lasttime, lastact;
159 static Mouse_Button lastbtn;
161 /* this allows xterm-style mouse handling. */
162 static int send_raw_mouse = 0;
163 static int wheel_accumulator = 0;
165 static char *window_name, *icon_name;
167 static int compose_state = 0;
169 static int wsa_started = 0;
171 static OSVERSIONINFO osVersion;
173 static UINT wm_mousewheel = WM_MOUSEWHEEL;
175 /* Dummy routine, only required in plink. */
176 void ldisc_update(void *frontend, int echo, int edit)
180 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
182 static char appname[] = "PuTTY";
187 int guess_width, guess_height;
190 flags = FLAG_VERBOSE | FLAG_INTERACTIVE;
192 winsock_ver = MAKEWORD(1, 1);
193 if (WSAStartup(winsock_ver, &wsadata)) {
194 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
195 MB_OK | MB_ICONEXCLAMATION);
198 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
199 MessageBox(NULL, "WinSock version is incompatible with 1.1",
200 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
205 /* WISHLIST: maybe allow config tweaking even if winsock not present? */
208 InitCommonControls();
210 /* Ensure a Maximize setting in Explorer doesn't maximise the
215 ZeroMemory(&osVersion, sizeof(osVersion));
216 osVersion.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
217 if (!GetVersionEx ( (OSVERSIONINFO *) &osVersion)) {
218 MessageBox(NULL, "Windows refuses to report a version",
219 "PuTTY Fatal Error", MB_OK | MB_ICONEXCLAMATION);
225 * If we're running a version of Windows that doesn't support
226 * WM_MOUSEWHEEL, find out what message number we should be
229 if (osVersion.dwMajorVersion < 4 ||
230 (osVersion.dwMajorVersion == 4 &&
231 osVersion.dwPlatformId != VER_PLATFORM_WIN32_NT))
232 wm_mousewheel = RegisterWindowMessage("MSWHEEL_ROLLMSG");
235 * See if we can find our Help file.
238 char b[2048], *p, *q, *r;
240 GetModuleFileName(NULL, b, sizeof(b) - 1);
242 p = strrchr(b, '\\');
243 if (p && p >= r) r = p+1;
245 if (q && q >= r) r = q+1;
246 strcpy(r, "putty.hlp");
247 if ( (fp = fopen(b, "r")) != NULL) {
248 help_path = dupstr(b);
252 strcpy(r, "putty.cnt");
253 if ( (fp = fopen(b, "r")) != NULL) {
254 help_has_contents = TRUE;
257 help_has_contents = FALSE;
261 * Process the command line.
267 default_protocol = DEFAULT_PROTOCOL;
268 default_port = DEFAULT_PORT;
269 cfg.logtype = LGTYP_NONE;
271 do_defaults(NULL, &cfg);
276 * Process a couple of command-line options which are more
277 * easily dealt with before the line is broken up into
278 * words. These are the soon-to-be-defunct @sessionname and
279 * the internal-use-only &sharedmemoryhandle, neither of
280 * which are combined with anything else.
282 while (*p && isspace(*p))
286 while (i > 1 && isspace(p[i - 1]))
289 do_defaults(p + 1, &cfg);
290 if (!*cfg.host && !do_config()) {
294 } else if (*p == '&') {
296 * An initial & means we've been given a command line
297 * containing the hex value of a HANDLE for a file
298 * mapping object, which we must then extract as a
303 if (sscanf(p + 1, "%p", &filemap) == 1 &&
304 (cp = MapViewOfFile(filemap, FILE_MAP_READ,
305 0, 0, sizeof(Config))) != NULL) {
308 CloseHandle(filemap);
309 } else if (!do_config()) {
315 * Otherwise, break up the command line and deal with
321 split_into_argv(cmdline, &argc, &argv, NULL);
323 for (i = 0; i < argc; i++) {
327 ret = cmdline_process_param(p, i+1<argc?argv[i+1]:NULL, 1);
329 cmdline_error("option \"%s\" requires an argument", p);
330 } else if (ret == 2) {
331 i++; /* skip next argument */
332 } else if (ret == 1) {
333 continue; /* nothing further needs doing */
334 } else if (!strcmp(p, "-cleanup")) {
336 * `putty -cleanup'. Remove all registry
337 * entries associated with PuTTY, and also find
338 * and delete the random seed file.
341 "This procedure will remove ALL Registry\n"
342 "entries associated with PuTTY, and will\n"
343 "also remove the PuTTY random seed file.\n"
345 "THIS PROCESS WILL DESTROY YOUR SAVED\n"
346 "SESSIONS. Are you really sure you want\n"
349 MB_YESNO | MB_ICONWARNING) == IDYES) {
353 } else if (*p != '-') {
357 * If we already have a host name, treat
358 * this argument as a port number. NB we
359 * have to treat this as a saved -P
360 * argument, so that it will be deferred
361 * until it's a good moment to run it.
363 int ret = cmdline_process_param("-P", p, 1);
365 } else if (!strncmp(q, "telnet:", 7)) {
367 * If the hostname starts with "telnet:",
368 * set the protocol to Telnet and process
369 * the string as a Telnet URL.
374 if (q[0] == '/' && q[1] == '/')
376 cfg.protocol = PROT_TELNET;
378 while (*p && *p != ':' && *p != '/')
387 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
388 cfg.host[sizeof(cfg.host) - 1] = '\0';
392 * Otherwise, treat this argument as a host
395 while (*p && !isspace(*p))
399 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
400 cfg.host[sizeof(cfg.host) - 1] = '\0';
409 if (!*cfg.host && !do_config()) {
415 * Trim leading whitespace off the hostname if it's there.
418 int space = strspn(cfg.host, " \t");
419 memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
422 /* See if host is of the form user@host */
423 if (cfg.host[0] != '\0') {
424 char *atsign = strchr(cfg.host, '@');
425 /* Make sure we're not overflowing the user field */
427 if (atsign - cfg.host < sizeof cfg.username) {
428 strncpy(cfg.username, cfg.host, atsign - cfg.host);
429 cfg.username[atsign - cfg.host] = '\0';
431 memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
436 * Trim a colon suffix off the hostname if it's there.
438 cfg.host[strcspn(cfg.host, ":")] = '\0';
441 * Remove any remaining whitespace from the hostname.
445 while (cfg.host[p2] != '\0') {
446 if (cfg.host[p2] != ' ' && cfg.host[p2] != '\t') {
447 cfg.host[p1] = cfg.host[p2];
457 * Select protocol. This is farmed out into a table in a
458 * separate file to enable an ssh-free variant.
463 for (i = 0; backends[i].backend != NULL; i++)
464 if (backends[i].protocol == cfg.protocol) {
465 back = backends[i].backend;
469 MessageBox(NULL, "Unsupported protocol number found",
470 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
476 /* Check for invalid Port number (i.e. zero) */
478 MessageBox(NULL, "Invalid Port Number",
479 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
486 wndclass.lpfnWndProc = WndProc;
487 wndclass.cbClsExtra = 0;
488 wndclass.cbWndExtra = 0;
489 wndclass.hInstance = inst;
490 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
491 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
492 wndclass.hbrBackground = NULL;
493 wndclass.lpszMenuName = NULL;
494 wndclass.lpszClassName = appname;
496 RegisterClass(&wndclass);
501 term = term_init(NULL);
502 logctx = log_init(NULL);
503 term_provide_logctx(term, logctx);
508 * Guess some defaults for the window size. This all gets
509 * updated later, so we don't really care too much. However, we
510 * do want the font width/height guesses to correspond to a
511 * large font rather than a small one...
518 term_size(term, cfg.height, cfg.width, cfg.savelines);
519 guess_width = extra_width + font_width * term->cols;
520 guess_height = extra_height + font_height * term->rows;
523 get_fullscreen_rect(&r);
524 if (guess_width > r.right - r.left)
525 guess_width = r.right - r.left;
526 if (guess_height > r.bottom - r.top)
527 guess_height = r.bottom - r.top;
531 int winmode = WS_OVERLAPPEDWINDOW | WS_VSCROLL;
534 winmode &= ~(WS_VSCROLL);
535 if (cfg.resize_action == RESIZE_DISABLED)
536 winmode &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
538 exwinmode |= WS_EX_TOPMOST;
540 exwinmode |= WS_EX_CLIENTEDGE;
541 hwnd = CreateWindowEx(exwinmode, appname, appname,
542 winmode, CW_USEDEFAULT, CW_USEDEFAULT,
543 guess_width, guess_height,
544 NULL, NULL, inst, NULL);
548 * Initialise the fonts, simultaneously correcting the guesses
549 * for font_{width,height}.
554 * Correct the guesses for extra_{width,height}.
558 GetWindowRect(hwnd, &wr);
559 GetClientRect(hwnd, &cr);
560 offset_width = offset_height = cfg.window_border;
561 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
562 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
566 * Resize the window, now we know what size we _really_ want it
569 guess_width = extra_width + font_width * term->cols;
570 guess_height = extra_height + font_height * term->rows;
571 SetWindowPos(hwnd, NULL, 0, 0, guess_width, guess_height,
572 SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
575 * Set up a caret bitmap, with no content.
579 int size = (font_width + 15) / 16 * 2 * font_height;
580 bits = smalloc(size);
581 memset(bits, 0, size);
582 caretbm = CreateBitmap(font_width, font_height, 1, 1, bits);
585 CreateCaret(hwnd, caretbm, font_width, font_height);
588 * Initialise the scroll bar.
593 si.cbSize = sizeof(si);
594 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
596 si.nMax = term->rows - 1;
597 si.nPage = term->rows;
599 SetScrollInfo(hwnd, SB_VERT, &si, FALSE);
603 * Start up the telnet connection.
607 char msg[1024], *title;
610 error = back->init((void *)term, &backhandle,
611 cfg.host, cfg.port, &realhost, cfg.tcp_nodelay);
612 back->provide_logctx(backhandle, logctx);
614 sprintf(msg, "Unable to open connection to\n"
615 "%.800s\n" "%s", cfg.host, error);
616 MessageBox(NULL, msg, "PuTTY Error", MB_ICONERROR | MB_OK);
619 window_name = icon_name = NULL;
621 title = cfg.wintitle;
623 sprintf(msg, "%s - PuTTY", realhost);
627 set_title(NULL, title);
628 set_icon(NULL, title);
632 * Connect the terminal to the backend for resize purposes.
634 term_provide_resize_fn(term, back->size, backhandle);
637 * Set up a line discipline.
639 ldisc = ldisc_create(term, back, backhandle, NULL);
641 session_closed = FALSE;
644 * Prepare the mouse handler.
646 lastact = MA_NOTHING;
647 lastbtn = MBT_NOTHING;
648 dbltime = GetDoubleClickTime();
651 * Set up the session-control options on the system menu.
654 HMENU m = GetSystemMenu(hwnd, FALSE);
658 AppendMenu(m, MF_SEPARATOR, 0, 0);
659 if (cfg.protocol == PROT_TELNET) {
661 AppendMenu(p, MF_ENABLED, IDM_TEL_AYT, "Are You There");
662 AppendMenu(p, MF_ENABLED, IDM_TEL_BRK, "Break");
663 AppendMenu(p, MF_ENABLED, IDM_TEL_SYNCH, "Synch");
664 AppendMenu(p, MF_SEPARATOR, 0, 0);
665 AppendMenu(p, MF_ENABLED, IDM_TEL_EC, "Erase Character");
666 AppendMenu(p, MF_ENABLED, IDM_TEL_EL, "Erase Line");
667 AppendMenu(p, MF_ENABLED, IDM_TEL_GA, "Go Ahead");
668 AppendMenu(p, MF_ENABLED, IDM_TEL_NOP, "No Operation");
669 AppendMenu(p, MF_SEPARATOR, 0, 0);
670 AppendMenu(p, MF_ENABLED, IDM_TEL_ABORT, "Abort Process");
671 AppendMenu(p, MF_ENABLED, IDM_TEL_AO, "Abort Output");
672 AppendMenu(p, MF_ENABLED, IDM_TEL_IP, "Interrupt Process");
673 AppendMenu(p, MF_ENABLED, IDM_TEL_SUSP, "Suspend Process");
674 AppendMenu(p, MF_SEPARATOR, 0, 0);
675 AppendMenu(p, MF_ENABLED, IDM_TEL_EOR, "End Of Record");
676 AppendMenu(p, MF_ENABLED, IDM_TEL_EOF, "End Of File");
677 AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) p,
679 AppendMenu(m, MF_SEPARATOR, 0, 0);
681 AppendMenu(m, MF_ENABLED, IDM_SHOWLOG, "&Event Log");
682 AppendMenu(m, MF_SEPARATOR, 0, 0);
683 AppendMenu(m, MF_ENABLED, IDM_NEWSESS, "Ne&w Session...");
684 AppendMenu(m, MF_ENABLED, IDM_DUPSESS, "&Duplicate Session");
687 for (i = 1; i < ((nsessions < 256) ? nsessions : 256); i++)
688 AppendMenu(s, MF_ENABLED, IDM_SAVED_MIN + (16 * i),
690 AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) s, "Sa&ved Sessions");
691 AppendMenu(m, MF_ENABLED, IDM_RECONF, "Chan&ge Settings...");
692 AppendMenu(m, MF_SEPARATOR, 0, 0);
693 AppendMenu(m, MF_ENABLED, IDM_COPYALL, "C&opy All to Clipboard");
694 AppendMenu(m, MF_ENABLED, IDM_CLRSB, "C&lear Scrollback");
695 AppendMenu(m, MF_ENABLED, IDM_RESET, "Rese&t Terminal");
696 AppendMenu(m, MF_SEPARATOR, 0, 0);
697 AppendMenu(m, (cfg.resize_action == RESIZE_DISABLED) ?
698 MF_GRAYED : MF_ENABLED, IDM_FULLSCREEN, "&Full Screen");
699 AppendMenu(m, MF_SEPARATOR, 0, 0);
701 AppendMenu(m, MF_ENABLED, IDM_HELP, "&Help");
702 AppendMenu(m, MF_ENABLED, IDM_ABOUT, "&About PuTTY");
706 * Set up the initial input locale.
708 set_input_locale(GetKeyboardLayout(0));
711 * Open the initial log file if there is one.
716 * Finally show the window!
718 ShowWindow(hwnd, show);
719 SetForegroundWindow(hwnd);
722 * Set the palette up.
728 term->has_focus = (GetForegroundWindow() == hwnd);
731 if (GetMessage(&msg, NULL, 0, 0) == 1) {
732 int timer_id = 0, long_timer = 0;
734 while (msg.message != WM_QUIT) {
735 /* Sometimes DispatchMessage calls routines that use their own
736 * GetMessage loop, setup this timer so we get some control back.
738 * Also call term_update() from the timer so that if the host
739 * is sending data flat out we still do redraws.
741 if (timer_id && long_timer) {
742 KillTimer(hwnd, timer_id);
743 long_timer = timer_id = 0;
746 timer_id = SetTimer(hwnd, 1, 20, NULL);
747 if (!(IsWindow(logbox) && IsDialogMessage(logbox, &msg)))
748 DispatchMessage(&msg);
750 /* Make sure we blink everything that needs it. */
753 /* Send the paste buffer if there's anything to send */
756 /* If there's nothing new in the queue then we can do everything
757 * we've delayed, reading the socket, writing, and repainting
760 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
763 if (pending_netevent) {
764 enact_pending_netevent();
766 /* Force the cursor blink on */
769 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
773 /* Okay there is now nothing to do so we make sure the screen is
774 * completely up to date then tell windows to call us in a little
778 KillTimer(hwnd, timer_id);
782 if (GetCapture() != hwnd ||
784 !(cfg.mouse_override && is_shift_pressed())))
789 flash_window(1); /* maintain */
791 /* The messages seem unreliable; especially if we're being tricky */
792 term->has_focus = (GetForegroundWindow() == hwnd);
795 /* Hmm, term_update didn't want to do an update too soon ... */
796 timer_id = SetTimer(hwnd, 1, 50, NULL);
797 else if (!term->has_focus)
798 timer_id = SetTimer(hwnd, 1, 500, NULL);
800 timer_id = SetTimer(hwnd, 1, 100, NULL);
803 /* There's no point rescanning everything in the message queue
804 * so we do an apparently unnecessary wait here
807 if (GetMessage(&msg, NULL, 0, 0) != 1)
812 cleanup_exit(msg.wParam); /* this doesn't return... */
813 return msg.wParam; /* ... but optimiser doesn't know */
819 void cleanup_exit(int code)
832 if (cfg.protocol == PROT_SSH) {
843 * Set up, or shut down, an AsyncSelect. Called from winnet.c.
845 char *do_select(SOCKET skt, int startup)
850 events = (FD_CONNECT | FD_READ | FD_WRITE |
851 FD_OOB | FD_CLOSE | FD_ACCEPT);
856 return "do_select(): internal error (hwnd==NULL)";
857 if (WSAAsyncSelect(skt, hwnd, msg, events) == SOCKET_ERROR) {
858 switch (WSAGetLastError()) {
860 return "Network is down";
862 return "WSAAsyncSelect(): unknown error";
869 * set or clear the "raw mouse message" mode
871 void set_raw_mouse_mode(void *frontend, int activate)
873 activate = activate && !cfg.no_mouse_rep;
874 send_raw_mouse = activate;
875 SetCursor(LoadCursor(NULL, activate ? IDC_ARROW : IDC_IBEAM));
879 * Print a message box and close the connection.
881 void connection_fatal(void *frontend, char *fmt, ...)
887 vsprintf(stuff, fmt, ap);
889 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
890 if (cfg.close_on_exit == COE_ALWAYS)
893 session_closed = TRUE;
894 SetWindowText(hwnd, "PuTTY (inactive)");
899 * Report an error at the command-line parsing stage.
901 void cmdline_error(char *fmt, ...)
907 vsprintf(stuff, fmt, ap);
909 MessageBox(hwnd, stuff, "PuTTY Command Line Error", MB_ICONERROR | MB_OK);
914 * Actually do the job requested by a WM_NETEVENT
916 static void enact_pending_netevent(void)
918 static int reentering = 0;
919 extern int select_result(WPARAM, LPARAM);
923 return; /* don't unpend the pending */
925 pending_netevent = FALSE;
928 ret = select_result(pend_netevent_wParam, pend_netevent_lParam);
931 if (ret == 0 && !session_closed) {
932 /* Abnormal exits will already have set session_closed and taken
933 * appropriate action. */
934 if (cfg.close_on_exit == COE_ALWAYS ||
935 cfg.close_on_exit == COE_NORMAL) PostQuitMessage(0);
937 session_closed = TRUE;
938 SetWindowText(hwnd, "PuTTY (inactive)");
939 MessageBox(hwnd, "Connection closed by remote host",
940 "PuTTY", MB_OK | MB_ICONINFORMATION);
946 * Copy the colour palette from the configuration data into defpal.
947 * This is non-trivial because the colour indices are different.
949 static void cfgtopalette(void)
952 static const int ww[] = {
953 6, 7, 8, 9, 10, 11, 12, 13,
954 14, 15, 16, 17, 18, 19, 20, 21,
955 0, 1, 2, 3, 4, 4, 5, 5
958 for (i = 0; i < 24; i++) {
960 defpal[i].rgbtRed = cfg.colours[w][0];
961 defpal[i].rgbtGreen = cfg.colours[w][1];
962 defpal[i].rgbtBlue = cfg.colours[w][2];
967 * Set up the colour palette.
969 static void init_palette(void)
972 HDC hdc = GetDC(hwnd);
974 if (cfg.try_palette && GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) {
975 logpal = smalloc(sizeof(*logpal)
976 - sizeof(logpal->palPalEntry)
977 + NCOLOURS * sizeof(PALETTEENTRY));
978 logpal->palVersion = 0x300;
979 logpal->palNumEntries = NCOLOURS;
980 for (i = 0; i < NCOLOURS; i++) {
981 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
982 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
983 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
984 logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
986 pal = CreatePalette(logpal);
988 SelectPalette(hdc, pal, FALSE);
990 SelectPalette(hdc, GetStockObject(DEFAULT_PALETTE), FALSE);
993 ReleaseDC(hwnd, hdc);
996 for (i = 0; i < NCOLOURS; i++)
997 colours[i] = PALETTERGB(defpal[i].rgbtRed,
1001 for (i = 0; i < NCOLOURS; i++)
1002 colours[i] = RGB(defpal[i].rgbtRed,
1003 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
1007 * Initialise all the fonts we will need initially. There may be as many as
1008 * three or as few as one. The other (poentially) twentyone fonts are done
1009 * if/when they are needed.
1013 * - check the font width and height, correcting our guesses if
1016 * - verify that the bold font is the same width as the ordinary
1017 * one, and engage shadow bolding if not.
1019 * - verify that the underlined font is the same width as the
1020 * ordinary one (manual underlining by means of line drawing can
1021 * be done in a pinch).
1023 static void init_fonts(int pick_width, int pick_height)
1030 int fw_dontcare, fw_bold;
1032 for (i = 0; i < FONT_MAXNO; i++)
1035 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
1036 und_mode = UND_FONT;
1038 if (cfg.fontisbold) {
1039 fw_dontcare = FW_BOLD;
1042 fw_dontcare = FW_DONTCARE;
1049 font_height = pick_height;
1051 font_height = cfg.fontheight;
1052 if (font_height > 0) {
1054 -MulDiv(font_height, GetDeviceCaps(hdc, LOGPIXELSY), 72);
1057 font_width = pick_width;
1059 #define f(i,c,w,u) \
1060 fonts[i] = CreateFont (font_height, font_width, 0, 0, w, FALSE, u, FALSE, \
1061 c, OUT_DEFAULT_PRECIS, \
1062 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, \
1063 FIXED_PITCH | FF_DONTCARE, cfg.font)
1065 f(FONT_NORMAL, cfg.fontcharset, fw_dontcare, FALSE);
1067 lfont.lfHeight = font_height;
1068 lfont.lfWidth = font_width;
1069 lfont.lfEscapement = 0;
1070 lfont.lfOrientation = 0;
1071 lfont.lfWeight = fw_dontcare;
1072 lfont.lfItalic = FALSE;
1073 lfont.lfUnderline = FALSE;
1074 lfont.lfStrikeOut = FALSE;
1075 lfont.lfCharSet = cfg.fontcharset;
1076 lfont.lfOutPrecision = OUT_DEFAULT_PRECIS;
1077 lfont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
1078 lfont.lfQuality = DEFAULT_QUALITY;
1079 lfont.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE;
1080 strncpy(lfont.lfFaceName, cfg.font, LF_FACESIZE);
1082 SelectObject(hdc, fonts[FONT_NORMAL]);
1083 GetTextMetrics(hdc, &tm);
1085 if (pick_width == 0 || pick_height == 0) {
1086 font_height = tm.tmHeight;
1087 font_width = tm.tmAveCharWidth;
1089 font_dualwidth = (tm.tmAveCharWidth != tm.tmMaxCharWidth);
1091 #ifdef RDB_DEBUG_PATCH
1092 debug(23, "Primary font H=%d, AW=%d, MW=%d",
1093 tm.tmHeight, tm.tmAveCharWidth, tm.tmMaxCharWidth);
1098 DWORD cset = tm.tmCharSet;
1099 memset(&info, 0xFF, sizeof(info));
1101 /* !!! Yes the next line is right */
1102 if (cset == OEM_CHARSET)
1103 font_codepage = GetOEMCP();
1105 if (TranslateCharsetInfo
1106 ((DWORD *) cset, &info, TCI_SRCCHARSET)) font_codepage =
1111 GetCPInfo(font_codepage, &cpinfo);
1112 dbcs_screenfont = (cpinfo.MaxCharSize > 1);
1115 f(FONT_UNDERLINE, cfg.fontcharset, fw_dontcare, TRUE);
1118 * Some fonts, e.g. 9-pt Courier, draw their underlines
1119 * outside their character cell. We successfully prevent
1120 * screen corruption by clipping the text output, but then
1121 * we lose the underline completely. Here we try to work
1122 * out whether this is such a font, and if it is, we set a
1123 * flag that causes underlines to be drawn by hand.
1125 * Having tried other more sophisticated approaches (such
1126 * as examining the TEXTMETRIC structure or requesting the
1127 * height of a string), I think we'll do this the brute
1128 * force way: we create a small bitmap, draw an underlined
1129 * space on it, and test to see whether any pixels are
1130 * foreground-coloured. (Since we expect the underline to
1131 * go all the way across the character cell, we only search
1132 * down a single column of the bitmap, half way across.)
1136 HBITMAP und_bm, und_oldbm;
1140 und_dc = CreateCompatibleDC(hdc);
1141 und_bm = CreateCompatibleBitmap(hdc, font_width, font_height);
1142 und_oldbm = SelectObject(und_dc, und_bm);
1143 SelectObject(und_dc, fonts[FONT_UNDERLINE]);
1144 SetTextAlign(und_dc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
1145 SetTextColor(und_dc, RGB(255, 255, 255));
1146 SetBkColor(und_dc, RGB(0, 0, 0));
1147 SetBkMode(und_dc, OPAQUE);
1148 ExtTextOut(und_dc, 0, 0, ETO_OPAQUE, NULL, " ", 1, NULL);
1150 for (i = 0; i < font_height; i++) {
1151 c = GetPixel(und_dc, font_width / 2, i);
1152 if (c != RGB(0, 0, 0))
1155 SelectObject(und_dc, und_oldbm);
1156 DeleteObject(und_bm);
1159 und_mode = UND_LINE;
1160 DeleteObject(fonts[FONT_UNDERLINE]);
1161 fonts[FONT_UNDERLINE] = 0;
1165 if (bold_mode == BOLD_FONT) {
1166 f(FONT_BOLD, cfg.fontcharset, fw_bold, FALSE);
1170 descent = tm.tmAscent + 1;
1171 if (descent >= font_height)
1172 descent = font_height - 1;
1174 for (i = 0; i < 3; i++) {
1176 if (SelectObject(hdc, fonts[i]) && GetTextMetrics(hdc, &tm))
1177 fontsize[i] = tm.tmAveCharWidth + 256 * tm.tmHeight;
1184 ReleaseDC(hwnd, hdc);
1186 if (fontsize[FONT_UNDERLINE] != fontsize[FONT_NORMAL]) {
1187 und_mode = UND_LINE;
1188 DeleteObject(fonts[FONT_UNDERLINE]);
1189 fonts[FONT_UNDERLINE] = 0;
1192 if (bold_mode == BOLD_FONT &&
1193 fontsize[FONT_BOLD] != fontsize[FONT_NORMAL]) {
1194 bold_mode = BOLD_SHADOW;
1195 DeleteObject(fonts[FONT_BOLD]);
1196 fonts[FONT_BOLD] = 0;
1198 fontflag[0] = fontflag[1] = fontflag[2] = 1;
1203 static void another_font(int fontno)
1206 int fw_dontcare, fw_bold;
1210 if (fontno < 0 || fontno >= FONT_MAXNO || fontflag[fontno])
1213 basefont = (fontno & ~(FONT_BOLDUND));
1214 if (basefont != fontno && !fontflag[basefont])
1215 another_font(basefont);
1217 if (cfg.fontisbold) {
1218 fw_dontcare = FW_BOLD;
1221 fw_dontcare = FW_DONTCARE;
1225 c = cfg.fontcharset;
1231 if (fontno & FONT_WIDE)
1233 if (fontno & FONT_NARROW)
1235 if (fontno & FONT_OEM)
1237 if (fontno & FONT_BOLD)
1239 if (fontno & FONT_UNDERLINE)
1243 CreateFont(font_height * (1 + !!(fontno & FONT_HIGH)), x, 0, 0, w,
1244 FALSE, u, FALSE, c, OUT_DEFAULT_PRECIS,
1245 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
1246 FIXED_PITCH | FF_DONTCARE, s);
1248 fontflag[fontno] = 1;
1251 static void deinit_fonts(void)
1254 for (i = 0; i < FONT_MAXNO; i++) {
1256 DeleteObject(fonts[i]);
1262 void request_resize(void *frontend, int w, int h)
1266 /* If the window is maximized supress resizing attempts */
1267 if (IsZoomed(hwnd)) {
1268 if (cfg.resize_action == RESIZE_TERM)
1272 if (cfg.resize_action == RESIZE_DISABLED) return;
1273 if (h == term->rows && w == term->cols) return;
1275 /* Sanity checks ... */
1277 static int first_time = 1;
1280 switch (first_time) {
1282 /* Get the size of the screen */
1283 if (get_fullscreen_rect(&ss))
1284 /* first_time = 0 */ ;
1290 /* Make sure the values are sane */
1291 width = (ss.right - ss.left - extra_width) / 4;
1292 height = (ss.bottom - ss.top - extra_height) / 6;
1294 if (w > width || h > height)
1303 term_size(term, h, w, cfg.savelines);
1305 if (cfg.resize_action != RESIZE_FONT && !IsZoomed(hwnd)) {
1306 width = extra_width + font_width * w;
1307 height = extra_height + font_height * h;
1309 SetWindowPos(hwnd, NULL, 0, 0, width, height,
1310 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1311 SWP_NOMOVE | SWP_NOZORDER);
1315 InvalidateRect(hwnd, NULL, TRUE);
1318 static void reset_window(int reinit) {
1320 * This function decides how to resize or redraw when the
1321 * user changes something.
1323 * This function doesn't like to change the terminal size but if the
1324 * font size is locked that may be it's only soluion.
1326 int win_width, win_height;
1329 #ifdef RDB_DEBUG_PATCH
1330 debug((27, "reset_window()"));
1333 /* Current window sizes ... */
1334 GetWindowRect(hwnd, &wr);
1335 GetClientRect(hwnd, &cr);
1337 win_width = cr.right - cr.left;
1338 win_height = cr.bottom - cr.top;
1340 if (cfg.resize_action == RESIZE_DISABLED) reinit = 2;
1342 /* Are we being forced to reload the fonts ? */
1344 #ifdef RDB_DEBUG_PATCH
1345 debug((27, "reset_window() -- Forced deinit"));
1351 /* Oh, looks like we're minimised */
1352 if (win_width == 0 || win_height == 0)
1355 /* Is the window out of position ? */
1357 (offset_width != (win_width-font_width*term->cols)/2 ||
1358 offset_height != (win_height-font_height*term->rows)/2) ){
1359 offset_width = (win_width-font_width*term->cols)/2;
1360 offset_height = (win_height-font_height*term->rows)/2;
1361 InvalidateRect(hwnd, NULL, TRUE);
1362 #ifdef RDB_DEBUG_PATCH
1363 debug((27, "reset_window() -> Reposition terminal"));
1367 if (IsZoomed(hwnd)) {
1368 /* We're fullscreen, this means we must not change the size of
1369 * the window so it's the font size or the terminal itself.
1372 extra_width = wr.right - wr.left - cr.right + cr.left;
1373 extra_height = wr.bottom - wr.top - cr.bottom + cr.top;
1375 if (cfg.resize_action != RESIZE_TERM) {
1376 if ( font_width != win_width/term->cols ||
1377 font_height != win_height/term->rows) {
1379 init_fonts(win_width/term->cols, win_height/term->rows);
1380 offset_width = (win_width-font_width*term->cols)/2;
1381 offset_height = (win_height-font_height*term->rows)/2;
1382 InvalidateRect(hwnd, NULL, TRUE);
1383 #ifdef RDB_DEBUG_PATCH
1384 debug((25, "reset_window() -> Z font resize to (%d, %d)",
1385 font_width, font_height));
1389 if ( font_width != win_width/term->cols ||
1390 font_height != win_height/term->rows) {
1391 /* Our only choice at this point is to change the
1392 * size of the terminal; Oh well.
1394 term_size(term, win_height/font_height, win_width/font_width,
1396 offset_width = (win_width-font_width*term->cols)/2;
1397 offset_height = (win_height-font_height*term->rows)/2;
1398 InvalidateRect(hwnd, NULL, TRUE);
1399 #ifdef RDB_DEBUG_PATCH
1400 debug((27, "reset_window() -> Zoomed term_size"));
1407 /* Hmm, a force re-init means we should ignore the current window
1408 * so we resize to the default font size.
1411 #ifdef RDB_DEBUG_PATCH
1412 debug((27, "reset_window() -> Forced re-init"));
1415 offset_width = offset_height = cfg.window_border;
1416 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
1417 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
1419 if (win_width != font_width*term->cols + offset_width*2 ||
1420 win_height != font_height*term->rows + offset_height*2) {
1422 /* If this is too large windows will resize it to the maximum
1423 * allowed window size, we will then be back in here and resize
1424 * the font or terminal to fit.
1426 SetWindowPos(hwnd, NULL, 0, 0,
1427 font_width*term->cols + extra_width,
1428 font_height*term->rows + extra_height,
1429 SWP_NOMOVE | SWP_NOZORDER);
1432 InvalidateRect(hwnd, NULL, TRUE);
1436 /* Okay the user doesn't want us to change the font so we try the
1437 * window. But that may be too big for the screen which forces us
1438 * to change the terminal.
1440 if ((cfg.resize_action == RESIZE_TERM && reinit<=0) ||
1441 (cfg.resize_action == RESIZE_EITHER && reinit<0) ||
1443 offset_width = offset_height = cfg.window_border;
1444 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
1445 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
1447 if (win_width != font_width*term->cols + offset_width*2 ||
1448 win_height != font_height*term->rows + offset_height*2) {
1453 get_fullscreen_rect(&ss);
1455 width = (ss.right - ss.left - extra_width) / font_width;
1456 height = (ss.bottom - ss.top - extra_height) / font_height;
1459 if ( term->rows > height || term->cols > width ) {
1460 if (cfg.resize_action == RESIZE_EITHER) {
1461 /* Make the font the biggest we can */
1462 if (term->cols > width)
1463 font_width = (ss.right - ss.left - extra_width)
1465 if (term->rows > height)
1466 font_height = (ss.bottom - ss.top - extra_height)
1470 init_fonts(font_width, font_height);
1472 width = (ss.right - ss.left - extra_width) / font_width;
1473 height = (ss.bottom - ss.top - extra_height) / font_height;
1475 if ( height > term->rows ) height = term->rows;
1476 if ( width > term->cols ) width = term->cols;
1477 term_size(term, height, width, cfg.savelines);
1478 #ifdef RDB_DEBUG_PATCH
1479 debug((27, "reset_window() -> term resize to (%d,%d)",
1485 SetWindowPos(hwnd, NULL, 0, 0,
1486 font_width*term->cols + extra_width,
1487 font_height*term->rows + extra_height,
1488 SWP_NOMOVE | SWP_NOZORDER);
1490 InvalidateRect(hwnd, NULL, TRUE);
1491 #ifdef RDB_DEBUG_PATCH
1492 debug((27, "reset_window() -> window resize to (%d,%d)",
1493 font_width*term->cols + extra_width,
1494 font_height*term->rows + extra_height));
1500 /* We're allowed to or must change the font but do we want to ? */
1502 if (font_width != (win_width-cfg.window_border*2)/term->cols ||
1503 font_height != (win_height-cfg.window_border*2)/term->rows) {
1506 init_fonts((win_width-cfg.window_border*2)/term->cols,
1507 (win_height-cfg.window_border*2)/term->rows);
1508 offset_width = (win_width-font_width*term->cols)/2;
1509 offset_height = (win_height-font_height*term->rows)/2;
1511 extra_width = wr.right - wr.left - cr.right + cr.left +offset_width*2;
1512 extra_height = wr.bottom - wr.top - cr.bottom + cr.top+offset_height*2;
1514 InvalidateRect(hwnd, NULL, TRUE);
1515 #ifdef RDB_DEBUG_PATCH
1516 debug((25, "reset_window() -> font resize to (%d,%d)",
1517 font_width, font_height));
1522 static void set_input_locale(HKL kl)
1526 GetLocaleInfo(LOWORD(kl), LOCALE_IDEFAULTANSICODEPAGE,
1527 lbuf, sizeof(lbuf));
1529 kbd_codepage = atoi(lbuf);
1532 static void click(Mouse_Button b, int x, int y, int shift, int ctrl, int alt)
1534 int thistime = GetMessageTime();
1536 if (send_raw_mouse && !(cfg.mouse_override && shift)) {
1537 lastbtn = MBT_NOTHING;
1538 term_mouse(term, b, MA_CLICK, x, y, shift, ctrl, alt);
1542 if (lastbtn == b && thistime - lasttime < dbltime) {
1543 lastact = (lastact == MA_CLICK ? MA_2CLK :
1544 lastact == MA_2CLK ? MA_3CLK :
1545 lastact == MA_3CLK ? MA_CLICK : MA_NOTHING);
1550 if (lastact != MA_NOTHING)
1551 term_mouse(term, b, lastact, x, y, shift, ctrl, alt);
1552 lasttime = thistime;
1556 * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT)
1557 * into a cooked one (SELECT, EXTEND, PASTE).
1559 Mouse_Button translate_button(void *frontend, Mouse_Button button)
1561 if (button == MBT_LEFT)
1563 if (button == MBT_MIDDLE)
1564 return cfg.mouse_is_xterm ? MBT_PASTE : MBT_EXTEND;
1565 if (button == MBT_RIGHT)
1566 return cfg.mouse_is_xterm ? MBT_EXTEND : MBT_PASTE;
1567 return 0; /* shouldn't happen */
1570 static void show_mouseptr(int show)
1572 static int cursor_visible = 1;
1573 if (!cfg.hide_mouseptr) /* override if this feature disabled */
1575 if (cursor_visible && !show)
1577 else if (!cursor_visible && show)
1579 cursor_visible = show;
1582 static int is_alt_pressed(void)
1585 int r = GetKeyboardState(keystate);
1588 if (keystate[VK_MENU] & 0x80)
1590 if (keystate[VK_RMENU] & 0x80)
1595 static int is_shift_pressed(void)
1598 int r = GetKeyboardState(keystate);
1601 if (keystate[VK_SHIFT] & 0x80)
1606 static int resizing;
1608 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1609 WPARAM wParam, LPARAM lParam)
1612 static int ignore_clip = FALSE;
1613 static int need_backend_resize = FALSE;
1614 static int fullscr_on_max = FALSE;
1618 if (pending_netevent)
1619 enact_pending_netevent();
1620 if (GetCapture() != hwnd ||
1621 (send_raw_mouse && !(cfg.mouse_override && is_shift_pressed())))
1627 if (cfg.ping_interval > 0) {
1630 if (now - last_movement > cfg.ping_interval) {
1631 back->special(backhandle, TS_PING);
1632 last_movement = now;
1635 net_pending_errors();
1641 if (!cfg.warn_on_close || session_closed ||
1643 "Are you sure you want to close this session?",
1644 "PuTTY Exit Confirmation",
1645 MB_ICONWARNING | MB_OKCANCEL) == IDOK)
1646 DestroyWindow(hwnd);
1653 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1665 PROCESS_INFORMATION pi;
1666 HANDLE filemap = NULL;
1668 if (wParam == IDM_DUPSESS) {
1670 * Allocate a file-mapping memory chunk for the
1673 SECURITY_ATTRIBUTES sa;
1676 sa.nLength = sizeof(sa);
1677 sa.lpSecurityDescriptor = NULL;
1678 sa.bInheritHandle = TRUE;
1679 filemap = CreateFileMapping((HANDLE) 0xFFFFFFFF,
1682 0, sizeof(Config), NULL);
1684 p = (Config *) MapViewOfFile(filemap,
1686 0, 0, sizeof(Config));
1688 *p = cfg; /* structure copy */
1692 sprintf(c, "putty &%p", filemap);
1694 } else if (wParam == IDM_SAVEDSESS) {
1695 if ((lParam - IDM_SAVED_MIN) / 16 < nsessions) {
1697 sessions[(lParam - IDM_SAVED_MIN) / 16];
1698 cl = smalloc(16 + strlen(session));
1699 /* 8, but play safe */
1702 /* not a very important failure mode */
1704 sprintf(cl, "putty @%s", session);
1712 GetModuleFileName(NULL, b, sizeof(b) - 1);
1714 si.lpReserved = NULL;
1715 si.lpDesktop = NULL;
1719 si.lpReserved2 = NULL;
1720 CreateProcess(b, cl, NULL, NULL, TRUE,
1721 NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
1724 CloseHandle(filemap);
1734 GetWindowText(hwnd, cfg.wintitle, sizeof(cfg.wintitle));
1737 if (!do_reconfig(hwnd))
1741 /* Disable full-screen if resizing forbidden */
1742 HMENU m = GetSystemMenu (hwnd, FALSE);
1743 EnableMenuItem(m, IDM_FULLSCREEN, MF_BYCOMMAND |
1744 (cfg.resize_action == RESIZE_DISABLED)
1745 ? MF_GRAYED : MF_ENABLED);
1746 /* Gracefully unzoom if necessary */
1747 if (IsZoomed(hwnd) &&
1748 (cfg.resize_action == RESIZE_DISABLED)) {
1749 ShowWindow(hwnd, SW_RESTORE);
1753 if (strcmp(prev_cfg.logfilename, cfg.logfilename) ||
1754 prev_cfg.logtype != cfg.logtype) {
1755 logfclose(logctx); /* reset logging */
1761 * Flush the line discipline's edit buffer in the
1762 * case where local editing has just been disabled.
1764 ldisc_send(ldisc, NULL, 0, 0);
1772 /* Give terminal a heads-up on miscellaneous stuff */
1773 term_reconfig(term);
1775 /* Screen size changed ? */
1776 if (cfg.height != prev_cfg.height ||
1777 cfg.width != prev_cfg.width ||
1778 cfg.savelines != prev_cfg.savelines ||
1779 cfg.resize_action == RESIZE_FONT ||
1780 (cfg.resize_action == RESIZE_EITHER && IsZoomed(hwnd)) ||
1781 cfg.resize_action == RESIZE_DISABLED)
1782 term_size(term, cfg.height, cfg.width, cfg.savelines);
1784 /* Enable or disable the scroll bar, etc */
1786 LONG nflg, flag = GetWindowLong(hwnd, GWL_STYLE);
1787 LONG nexflag, exflag =
1788 GetWindowLong(hwnd, GWL_EXSTYLE);
1791 if (cfg.alwaysontop != prev_cfg.alwaysontop) {
1792 if (cfg.alwaysontop) {
1793 nexflag |= WS_EX_TOPMOST;
1794 SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
1795 SWP_NOMOVE | SWP_NOSIZE);
1797 nexflag &= ~(WS_EX_TOPMOST);
1798 SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0,
1799 SWP_NOMOVE | SWP_NOSIZE);
1802 if (cfg.sunken_edge)
1803 nexflag |= WS_EX_CLIENTEDGE;
1805 nexflag &= ~(WS_EX_CLIENTEDGE);
1808 if (is_full_screen() ?
1809 cfg.scrollbar_in_fullscreen : cfg.scrollbar)
1812 nflg &= ~WS_VSCROLL;
1814 if (cfg.resize_action == RESIZE_DISABLED ||
1816 nflg &= ~WS_THICKFRAME;
1818 nflg |= WS_THICKFRAME;
1820 if (cfg.resize_action == RESIZE_DISABLED)
1821 nflg &= ~WS_MAXIMIZEBOX;
1823 nflg |= WS_MAXIMIZEBOX;
1825 if (nflg != flag || nexflag != exflag) {
1827 SetWindowLong(hwnd, GWL_STYLE, nflg);
1828 if (nexflag != exflag)
1829 SetWindowLong(hwnd, GWL_EXSTYLE, nexflag);
1831 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
1832 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1833 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
1841 if (cfg.resize_action == RESIZE_DISABLED && IsZoomed(hwnd)) {
1846 set_title(NULL, cfg.wintitle);
1847 if (IsIconic(hwnd)) {
1849 cfg.win_name_always ? window_name :
1853 if (strcmp(cfg.font, prev_cfg.font) != 0 ||
1854 strcmp(cfg.line_codepage, prev_cfg.line_codepage) != 0 ||
1855 cfg.fontisbold != prev_cfg.fontisbold ||
1856 cfg.fontheight != prev_cfg.fontheight ||
1857 cfg.fontcharset != prev_cfg.fontcharset ||
1858 cfg.vtmode != prev_cfg.vtmode ||
1859 cfg.bold_colour != prev_cfg.bold_colour ||
1860 cfg.resize_action == RESIZE_DISABLED ||
1861 cfg.resize_action == RESIZE_EITHER ||
1862 (cfg.resize_action != prev_cfg.resize_action))
1865 InvalidateRect(hwnd, NULL, TRUE);
1866 reset_window(init_lvl);
1867 net_pending_errors();
1878 ldisc_send(ldisc, NULL, 0, 0);
1881 back->special(backhandle, TS_AYT);
1882 net_pending_errors();
1885 back->special(backhandle, TS_BRK);
1886 net_pending_errors();
1889 back->special(backhandle, TS_SYNCH);
1890 net_pending_errors();
1893 back->special(backhandle, TS_EC);
1894 net_pending_errors();
1897 back->special(backhandle, TS_EL);
1898 net_pending_errors();
1901 back->special(backhandle, TS_GA);
1902 net_pending_errors();
1905 back->special(backhandle, TS_NOP);
1906 net_pending_errors();
1909 back->special(backhandle, TS_ABORT);
1910 net_pending_errors();
1913 back->special(backhandle, TS_AO);
1914 net_pending_errors();
1917 back->special(backhandle, TS_IP);
1918 net_pending_errors();
1921 back->special(backhandle, TS_SUSP);
1922 net_pending_errors();
1925 back->special(backhandle, TS_EOR);
1926 net_pending_errors();
1929 back->special(backhandle, TS_EOF);
1930 net_pending_errors();
1936 WinHelp(hwnd, help_path,
1937 help_has_contents ? HELP_FINDER : HELP_CONTENTS, 0);
1941 * We get this if the System menu has been activated
1948 * We get this if the System menu has been activated
1949 * using the keyboard. This might happen from within
1950 * TranslateKey, in which case it really wants to be
1951 * followed by a `space' character to actually _bring
1952 * the menu up_ rather than just sitting there in
1953 * `ready to appear' state.
1955 show_mouseptr(1); /* make sure pointer is visible */
1957 PostMessage(hwnd, WM_CHAR, ' ', 0);
1959 case IDM_FULLSCREEN:
1963 if (wParam >= IDM_SAVED_MIN && wParam <= IDM_SAVED_MAX) {
1964 SendMessage(hwnd, WM_SYSCOMMAND, IDM_SAVEDSESS, wParam);
1969 #define X_POS(l) ((int)(short)LOWORD(l))
1970 #define Y_POS(l) ((int)(short)HIWORD(l))
1972 #define TO_CHR_X(x) ((((x)<0 ? (x)-font_width+1 : (x))-offset_width) / font_width)
1973 #define TO_CHR_Y(y) ((((y)<0 ? (y)-font_height+1: (y))-offset_height) / font_height)
1974 case WM_LBUTTONDOWN:
1975 case WM_MBUTTONDOWN:
1976 case WM_RBUTTONDOWN:
1984 case WM_LBUTTONDOWN:
1988 case WM_MBUTTONDOWN:
1989 button = MBT_MIDDLE;
1992 case WM_RBUTTONDOWN:
2001 button = MBT_MIDDLE;
2009 button = press = 0; /* shouldn't happen */
2013 * Special case: in full-screen mode, if the left
2014 * button is clicked in the very top left corner of the
2015 * window, we put up the System menu instead of doing
2018 if (is_full_screen() && press && button == MBT_LEFT &&
2019 X_POS(lParam) == 0 && Y_POS(lParam) == 0) {
2020 SendMessage(hwnd, WM_SYSCOMMAND, SC_MOUSEMENU, 0);
2025 TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam)),
2026 wParam & MK_SHIFT, wParam & MK_CONTROL,
2030 term_mouse(term, button, MA_RELEASE,
2031 TO_CHR_X(X_POS(lParam)),
2032 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
2033 wParam & MK_CONTROL, is_alt_pressed());
2041 * Add the mouse position and message time to the random
2044 noise_ultralight(lParam);
2046 if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON) &&
2047 GetCapture() == hwnd) {
2049 if (wParam & MK_LBUTTON)
2051 else if (wParam & MK_MBUTTON)
2055 term_mouse(term, b, MA_DRAG, TO_CHR_X(X_POS(lParam)),
2056 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
2057 wParam & MK_CONTROL, is_alt_pressed());
2060 case WM_NCMOUSEMOVE:
2062 noise_ultralight(lParam);
2064 case WM_IGNORE_CLIP:
2065 ignore_clip = wParam; /* don't panic on DESTROYCLIPBOARD */
2067 case WM_DESTROYCLIPBOARD:
2069 term_deselect(term);
2070 ignore_clip = FALSE;
2076 hdc = BeginPaint(hwnd, &p);
2078 SelectPalette(hdc, pal, TRUE);
2079 RealizePalette(hdc);
2081 term_paint(term, hdc,
2082 (p.rcPaint.left-offset_width)/font_width,
2083 (p.rcPaint.top-offset_height)/font_height,
2084 (p.rcPaint.right-offset_width-1)/font_width,
2085 (p.rcPaint.bottom-offset_height-1)/font_height);
2088 p.rcPaint.left < offset_width ||
2089 p.rcPaint.top < offset_height ||
2090 p.rcPaint.right >= offset_width + font_width*term->cols ||
2091 p.rcPaint.bottom>= offset_height + font_height*term->rows)
2093 HBRUSH fillcolour, oldbrush;
2095 fillcolour = CreateSolidBrush (
2096 colours[(ATTR_DEFBG>>ATTR_BGSHIFT)*2]);
2097 oldbrush = SelectObject(hdc, fillcolour);
2098 edge = CreatePen(PS_SOLID, 0,
2099 colours[(ATTR_DEFBG>>ATTR_BGSHIFT)*2]);
2100 oldpen = SelectObject(hdc, edge);
2103 * Jordan Russell reports that this apparently
2104 * ineffectual IntersectClipRect() call masks a
2105 * Windows NT/2K bug causing strange display
2106 * problems when the PuTTY window is taller than
2107 * the primary monitor. It seems harmless enough...
2109 IntersectClipRect(hdc,
2110 p.rcPaint.left, p.rcPaint.top,
2111 p.rcPaint.right, p.rcPaint.bottom);
2113 ExcludeClipRect(hdc,
2114 offset_width, offset_height,
2115 offset_width+font_width*term->cols,
2116 offset_height+font_height*term->rows);
2118 Rectangle(hdc, p.rcPaint.left, p.rcPaint.top,
2119 p.rcPaint.right, p.rcPaint.bottom);
2121 // SelectClipRgn(hdc, NULL);
2123 SelectObject(hdc, oldbrush);
2124 DeleteObject(fillcolour);
2125 SelectObject(hdc, oldpen);
2128 SelectObject(hdc, GetStockObject(SYSTEM_FONT));
2129 SelectObject(hdc, GetStockObject(WHITE_PEN));
2135 /* Notice we can get multiple netevents, FD_READ, FD_WRITE etc
2136 * but the only one that's likely to try to overload us is FD_READ.
2137 * This means buffering just one is fine.
2139 if (pending_netevent)
2140 enact_pending_netevent();
2142 pending_netevent = TRUE;
2143 pend_netevent_wParam = wParam;
2144 pend_netevent_lParam = lParam;
2145 if (WSAGETSELECTEVENT(lParam) != FD_READ)
2146 enact_pending_netevent();
2148 time(&last_movement);
2151 term->has_focus = TRUE;
2152 CreateCaret(hwnd, caretbm, font_width, font_height);
2154 flash_window(0); /* stop */
2161 term->has_focus = FALSE;
2163 caret_x = caret_y = -1; /* ensure caret is replaced next time */
2167 case WM_ENTERSIZEMOVE:
2168 #ifdef RDB_DEBUG_PATCH
2169 debug((27, "WM_ENTERSIZEMOVE"));
2173 need_backend_resize = FALSE;
2175 case WM_EXITSIZEMOVE:
2178 #ifdef RDB_DEBUG_PATCH
2179 debug((27, "WM_EXITSIZEMOVE"));
2181 if (need_backend_resize) {
2182 term_size(term, cfg.height, cfg.width, cfg.savelines);
2183 InvalidateRect(hwnd, NULL, TRUE);
2188 * This does two jobs:
2189 * 1) Keep the sizetip uptodate
2190 * 2) Make sure the window size is _stepped_ in units of the font size.
2192 if (cfg.resize_action != RESIZE_FONT && !alt_pressed) {
2193 int width, height, w, h, ew, eh;
2194 LPRECT r = (LPRECT) lParam;
2196 if ( !need_backend_resize && cfg.resize_action == RESIZE_EITHER &&
2197 (cfg.height != term->rows || cfg.width != term->cols )) {
2199 * Great! It seems that both the terminal size and the
2200 * font size have been changed and the user is now dragging.
2202 * It will now be difficult to get back to the configured
2205 * This would be easier but it seems to be too confusing.
2207 term_size(term, cfg.height, cfg.width, cfg.savelines);
2210 cfg.height=term->rows; cfg.width=term->cols;
2212 InvalidateRect(hwnd, NULL, TRUE);
2213 need_backend_resize = TRUE;
2216 width = r->right - r->left - extra_width;
2217 height = r->bottom - r->top - extra_height;
2218 w = (width + font_width / 2) / font_width;
2221 h = (height + font_height / 2) / font_height;
2224 UpdateSizeTip(hwnd, w, h);
2225 ew = width - w * font_width;
2226 eh = height - h * font_height;
2228 if (wParam == WMSZ_LEFT ||
2229 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
2235 if (wParam == WMSZ_TOP ||
2236 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
2246 int width, height, w, h, rv = 0;
2247 int ex_width = extra_width + (cfg.window_border - offset_width) * 2;
2248 int ex_height = extra_height + (cfg.window_border - offset_height) * 2;
2249 LPRECT r = (LPRECT) lParam;
2251 width = r->right - r->left - ex_width;
2252 height = r->bottom - r->top - ex_height;
2254 w = (width + term->cols/2)/term->cols;
2255 h = (height + term->rows/2)/term->rows;
2256 if ( r->right != r->left + w*term->cols + ex_width)
2259 if (wParam == WMSZ_LEFT ||
2260 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
2261 r->left = r->right - w*term->cols - ex_width;
2263 r->right = r->left + w*term->cols + ex_width;
2265 if (r->bottom != r->top + h*term->rows + ex_height)
2268 if (wParam == WMSZ_TOP ||
2269 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
2270 r->top = r->bottom - h*term->rows - ex_height;
2272 r->bottom = r->top + h*term->rows + ex_height;
2276 /* break; (never reached) */
2277 case WM_FULLSCR_ON_MAX:
2278 fullscr_on_max = TRUE;
2281 sys_cursor_update();
2284 #ifdef RDB_DEBUG_PATCH
2285 debug((27, "WM_SIZE %s (%d,%d)",
2286 (wParam == SIZE_MINIMIZED) ? "SIZE_MINIMIZED":
2287 (wParam == SIZE_MAXIMIZED) ? "SIZE_MAXIMIZED":
2288 (wParam == SIZE_RESTORED && resizing) ? "to":
2289 (wParam == SIZE_RESTORED) ? "SIZE_RESTORED":
2291 LOWORD(lParam), HIWORD(lParam)));
2293 if (wParam == SIZE_MINIMIZED)
2295 cfg.win_name_always ? window_name : icon_name);
2296 if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
2297 SetWindowText(hwnd, window_name);
2298 if (wParam == SIZE_RESTORED)
2299 clear_full_screen();
2300 if (wParam == SIZE_MAXIMIZED && fullscr_on_max) {
2301 fullscr_on_max = FALSE;
2305 if (cfg.resize_action == RESIZE_DISABLED) {
2306 /* A resize, well it better be a minimize. */
2310 int width, height, w, h;
2312 width = LOWORD(lParam);
2313 height = HIWORD(lParam);
2316 if (wParam == SIZE_MAXIMIZED && !was_zoomed) {
2318 prev_rows = term->rows;
2319 prev_cols = term->cols;
2320 if (cfg.resize_action == RESIZE_TERM) {
2321 w = width / font_width;
2323 h = height / font_height;
2326 term_size(term, h, w, cfg.savelines);
2329 } else if (wParam == SIZE_RESTORED && was_zoomed) {
2331 if (cfg.resize_action == RESIZE_TERM)
2332 term_size(term, prev_rows, prev_cols, cfg.savelines);
2333 if (cfg.resize_action != RESIZE_FONT)
2338 /* This is an unexpected resize, these will normally happen
2339 * if the window is too large. Probably either the user
2340 * selected a huge font or the screen size has changed.
2342 * This is also called with minimize.
2344 else reset_window(-1);
2348 * Don't call back->size in mid-resize. (To prevent
2349 * massive numbers of resize events getting sent
2350 * down the connection during an NT opaque drag.)
2353 if (cfg.resize_action != RESIZE_FONT && !alt_pressed) {
2354 need_backend_resize = TRUE;
2355 w = (width-cfg.window_border*2) / font_width;
2357 h = (height-cfg.window_border*2) / font_height;
2366 sys_cursor_update();
2369 switch (LOWORD(wParam)) {
2371 term_scroll(term, -1, 0);
2374 term_scroll(term, +1, 0);
2377 term_scroll(term, 0, +1);
2380 term_scroll(term, 0, -1);
2383 term_scroll(term, 0, +term->rows / 2);
2386 term_scroll(term, 0, -term->rows / 2);
2388 case SB_THUMBPOSITION:
2390 term_scroll(term, 1, HIWORD(wParam));
2394 case WM_PALETTECHANGED:
2395 if ((HWND) wParam != hwnd && pal != NULL) {
2396 HDC hdc = get_ctx(NULL);
2398 if (RealizePalette(hdc) > 0)
2404 case WM_QUERYNEWPALETTE:
2406 HDC hdc = get_ctx(NULL);
2408 if (RealizePalette(hdc) > 0)
2420 * Add the scan code and keypress timing to the random
2423 noise_ultralight(lParam);
2426 * We don't do TranslateMessage since it disassociates the
2427 * resulting CHAR message from the KEYDOWN that sparked it,
2428 * which we occasionally don't want. Instead, we process
2429 * KEYDOWN, and call the Win32 translator functions so that
2430 * we get the translations under _our_ control.
2433 unsigned char buf[20];
2436 if (wParam == VK_PROCESSKEY) {
2439 m.message = WM_KEYDOWN;
2441 m.lParam = lParam & 0xdfff;
2442 TranslateMessage(&m);
2444 len = TranslateKey(message, wParam, lParam, buf);
2446 return DefWindowProc(hwnd, message, wParam, lParam);
2450 * Interrupt an ongoing paste. I'm not sure
2451 * this is sensible, but for the moment it's
2452 * preferable to having to faff about buffering
2458 * We need not bother about stdin backlogs
2459 * here, because in GUI PuTTY we can't do
2460 * anything about it anyway; there's no means
2461 * of asking Windows to hold off on KEYDOWN
2462 * messages. We _have_ to buffer everything
2465 term_seen_key_event(term);
2466 ldisc_send(ldisc, buf, len, 1);
2471 net_pending_errors();
2473 case WM_INPUTLANGCHANGE:
2474 /* wParam == Font number */
2475 /* lParam == Locale */
2476 set_input_locale((HKL)lParam);
2477 sys_cursor_update();
2480 if(wParam == IMN_SETOPENSTATUS) {
2481 HIMC hImc = ImmGetContext(hwnd);
2482 ImmSetCompositionFont(hImc, &lfont);
2483 ImmReleaseContext(hwnd, hImc);
2487 case WM_IME_COMPOSITION:
2493 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ||
2494 osVersion.dwPlatformId == VER_PLATFORM_WIN32s) break; /* no Unicode */
2496 if ((lParam & GCS_RESULTSTR) == 0) /* Composition unfinished. */
2497 break; /* fall back to DefWindowProc */
2499 hIMC = ImmGetContext(hwnd);
2500 n = ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, NULL, 0);
2504 buff = (char*) smalloc(n);
2505 ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, buff, n);
2507 * Jaeyoun Chung reports that Korean character
2508 * input doesn't work correctly if we do a single
2509 * luni_send() covering the whole of buff. So
2510 * instead we luni_send the characters one by one.
2512 term_seen_key_event(term);
2513 for (i = 0; i < n; i += 2) {
2514 luni_send(ldisc, (unsigned short *)(buff+i), 1, 1);
2518 ImmReleaseContext(hwnd, hIMC);
2523 if (wParam & 0xFF00) {
2524 unsigned char buf[2];
2527 buf[0] = wParam >> 8;
2528 term_seen_key_event(term);
2529 lpage_send(ldisc, kbd_codepage, buf, 2, 1);
2531 char c = (unsigned char) wParam;
2532 term_seen_key_event(term);
2533 lpage_send(ldisc, kbd_codepage, &c, 1, 1);
2539 * Nevertheless, we are prepared to deal with WM_CHAR
2540 * messages, should they crop up. So if someone wants to
2541 * post the things to us as part of a macro manoeuvre,
2542 * we're ready to cope.
2545 char c = (unsigned char)wParam;
2546 term_seen_key_event(term);
2547 lpage_send(ldisc, CP_ACP, &c, 1, 1);
2551 if (send_raw_mouse && LOWORD(lParam) == HTCLIENT) {
2552 SetCursor(LoadCursor(NULL, IDC_ARROW));
2556 if (message == wm_mousewheel || message == WM_MOUSEWHEEL) {
2557 int shift_pressed=0, control_pressed=0, alt_pressed=0;
2559 if (message == WM_MOUSEWHEEL) {
2560 wheel_accumulator += (short)HIWORD(wParam);
2561 shift_pressed=LOWORD(wParam) & MK_SHIFT;
2562 control_pressed=LOWORD(wParam) & MK_CONTROL;
2565 wheel_accumulator += (int)wParam;
2566 if (GetKeyboardState(keys)!=0) {
2567 shift_pressed=keys[VK_SHIFT]&0x80;
2568 control_pressed=keys[VK_CONTROL]&0x80;
2572 /* process events when the threshold is reached */
2573 while (abs(wheel_accumulator) >= WHEEL_DELTA) {
2576 /* reduce amount for next time */
2577 if (wheel_accumulator > 0) {
2579 wheel_accumulator -= WHEEL_DELTA;
2580 } else if (wheel_accumulator < 0) {
2582 wheel_accumulator += WHEEL_DELTA;
2586 if (send_raw_mouse &&
2587 !(cfg.mouse_override && shift_pressed)) {
2588 /* send a mouse-down followed by a mouse up */
2591 TO_CHR_X(X_POS(lParam)),
2592 TO_CHR_Y(Y_POS(lParam)), shift_pressed,
2593 control_pressed, is_alt_pressed());
2594 term_mouse(term, b, MA_RELEASE, TO_CHR_X(X_POS(lParam)),
2595 TO_CHR_Y(Y_POS(lParam)), shift_pressed,
2596 control_pressed, is_alt_pressed());
2598 /* trigger a scroll */
2599 term_scroll(term, 0,
2601 -term->rows / 2 : term->rows / 2);
2608 return DefWindowProc(hwnd, message, wParam, lParam);
2612 * Move the system caret. (We maintain one, even though it's
2613 * invisible, for the benefit of blind people: apparently some
2614 * helper software tracks the system caret, so we should arrange to
2617 void sys_cursor(void *frontend, int x, int y)
2621 if (!term->has_focus) return;
2624 * Avoid gratuitously re-updating the cursor position and IMM
2625 * window if there's no actual change required.
2627 cx = x * font_width + offset_width;
2628 cy = y * font_height + offset_height;
2629 if (cx == caret_x && cy == caret_y)
2634 sys_cursor_update();
2637 static void sys_cursor_update(void)
2642 if (!term->has_focus) return;
2644 if (caret_x < 0 || caret_y < 0)
2647 SetCaretPos(caret_x, caret_y);
2649 /* IMM calls on Win98 and beyond only */
2650 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32s) return; /* 3.11 */
2652 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS &&
2653 osVersion.dwMinorVersion == 0) return; /* 95 */
2655 /* we should have the IMM functions */
2656 hIMC = ImmGetContext(hwnd);
2657 cf.dwStyle = CFS_POINT;
2658 cf.ptCurrentPos.x = caret_x;
2659 cf.ptCurrentPos.y = caret_y;
2660 ImmSetCompositionWindow(hIMC, &cf);
2662 ImmReleaseContext(hwnd, hIMC);
2666 * Draw a line of text in the window, at given character
2667 * coordinates, in given attributes.
2669 * We are allowed to fiddle with the contents of `text'.
2671 void do_text(Context ctx, int x, int y, char *text, int len,
2672 unsigned long attr, int lattr)
2675 int nfg, nbg, nfont;
2678 int force_manual_underline = 0;
2679 int fnt_width = font_width * (1 + (lattr != LATTR_NORM));
2680 int char_width = fnt_width;
2681 int text_adjust = 0;
2682 static int *IpDx = 0, IpDxLEN = 0;
2684 if (attr & ATTR_WIDE)
2687 if (len > IpDxLEN || IpDx[0] != char_width) {
2689 if (len > IpDxLEN) {
2691 IpDx = smalloc((len + 16) * sizeof(int));
2692 IpDxLEN = (len + 16);
2694 for (i = 0; i < IpDxLEN; i++)
2695 IpDx[i] = char_width;
2698 /* Only want the left half of double width lines */
2699 if (lattr != LATTR_NORM && x*2 >= term->cols)
2707 if ((attr & TATTR_ACTCURS) && (cfg.cursor_type == 0 || term->big_cursor)) {
2708 attr &= ATTR_CUR_AND | (bold_mode != BOLD_COLOURS ? ATTR_BOLD : 0);
2709 attr ^= ATTR_CUR_XOR;
2713 if (cfg.vtmode == VT_POORMAN && lattr != LATTR_NORM) {
2714 /* Assume a poorman font is borken in other ways too. */
2724 nfont |= FONT_WIDE + FONT_HIGH;
2727 if (attr & ATTR_NARROW)
2728 nfont |= FONT_NARROW;
2730 /* Special hack for the VT100 linedraw glyphs. */
2731 if ((attr & CSET_MASK) == 0x2300) {
2732 if (text[0] >= (char) 0xBA && text[0] <= (char) 0xBD) {
2733 switch ((unsigned char) (text[0])) {
2735 text_adjust = -2 * font_height / 5;
2738 text_adjust = -1 * font_height / 5;
2741 text_adjust = font_height / 5;
2744 text_adjust = 2 * font_height / 5;
2747 if (lattr == LATTR_TOP || lattr == LATTR_BOT)
2750 text[0] = (char) (unitab_xterm['q'] & CHAR_MASK);
2751 attr |= (unitab_xterm['q'] & CSET_MASK);
2752 if (attr & ATTR_UNDER) {
2753 attr &= ~ATTR_UNDER;
2754 force_manual_underline = 1;
2759 /* Anything left as an original character set is unprintable. */
2760 if (DIRECT_CHAR(attr)) {
2763 memset(text, 0xFD, len);
2767 if ((attr & CSET_MASK) == ATTR_OEMCP)
2770 nfg = 2 * ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
2771 nbg = 2 * ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
2772 if (bold_mode == BOLD_FONT && (attr & ATTR_BOLD))
2774 if (und_mode == UND_FONT && (attr & ATTR_UNDER))
2775 nfont |= FONT_UNDERLINE;
2776 another_font(nfont);
2777 if (!fonts[nfont]) {
2778 if (nfont & FONT_UNDERLINE)
2779 force_manual_underline = 1;
2780 /* Don't do the same for manual bold, it could be bad news. */
2782 nfont &= ~(FONT_BOLD | FONT_UNDERLINE);
2784 another_font(nfont);
2786 nfont = FONT_NORMAL;
2787 if (attr & ATTR_REVERSE) {
2792 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BOLD))
2794 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BLINK))
2798 SelectObject(hdc, fonts[nfont]);
2799 SetTextColor(hdc, fg);
2800 SetBkColor(hdc, bg);
2801 SetBkMode(hdc, OPAQUE);
2804 line_box.right = x + char_width * len;
2805 line_box.bottom = y + font_height;
2807 /* Only want the left half of double width lines */
2808 if (line_box.right > font_width*term->cols+offset_width)
2809 line_box.right = font_width*term->cols+offset_width;
2811 /* We're using a private area for direct to font. (512 chars.) */
2812 if (dbcs_screenfont && (attr & CSET_MASK) == ATTR_ACP) {
2813 /* Ho Hum, dbcs fonts are a PITA! */
2814 /* To display on W9x I have to convert to UCS */
2815 static wchar_t *uni_buf = 0;
2816 static int uni_len = 0;
2818 if (len > uni_len) {
2820 uni_buf = smalloc((uni_len = len) * sizeof(wchar_t));
2823 for(nlen = mptr = 0; mptr<len; mptr++) {
2824 uni_buf[nlen] = 0xFFFD;
2825 if (IsDBCSLeadByteEx(font_codepage, (BYTE) text[mptr])) {
2826 IpDx[nlen] += char_width;
2827 MultiByteToWideChar(font_codepage, MB_USEGLYPHCHARS,
2828 text+mptr, 2, uni_buf+nlen, 1);
2833 MultiByteToWideChar(font_codepage, MB_USEGLYPHCHARS,
2834 text+mptr, 1, uni_buf+nlen, 1);
2842 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2843 ETO_CLIPPED | ETO_OPAQUE, &line_box, uni_buf, nlen, IpDx);
2844 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2845 SetBkMode(hdc, TRANSPARENT);
2846 ExtTextOutW(hdc, x - 1,
2847 y - font_height * (lattr ==
2848 LATTR_BOT) + text_adjust,
2849 ETO_CLIPPED, &line_box, uni_buf, nlen, IpDx);
2853 } else if (DIRECT_FONT(attr)) {
2855 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2856 ETO_CLIPPED | ETO_OPAQUE, &line_box, text, len, IpDx);
2857 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2858 SetBkMode(hdc, TRANSPARENT);
2860 /* GRR: This draws the character outside it's box and can leave
2861 * 'droppings' even with the clip box! I suppose I could loop it
2862 * one character at a time ... yuk.
2864 * Or ... I could do a test print with "W", and use +1 or -1 for this
2865 * shift depending on if the leftmost column is blank...
2867 ExtTextOut(hdc, x - 1,
2868 y - font_height * (lattr ==
2869 LATTR_BOT) + text_adjust,
2870 ETO_CLIPPED, &line_box, text, len, IpDx);
2873 /* And 'normal' unicode characters */
2874 static WCHAR *wbuf = NULL;
2875 static int wlen = 0;
2880 wbuf = smalloc(wlen * sizeof(WCHAR));
2882 for (i = 0; i < len; i++)
2883 wbuf[i] = (WCHAR) ((attr & CSET_MASK) + (text[i] & CHAR_MASK));
2886 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2887 ETO_CLIPPED | ETO_OPAQUE, &line_box, wbuf, len, IpDx);
2889 /* And the shadow bold hack. */
2890 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2891 SetBkMode(hdc, TRANSPARENT);
2892 ExtTextOutW(hdc, x - 1,
2893 y - font_height * (lattr ==
2894 LATTR_BOT) + text_adjust,
2895 ETO_CLIPPED, &line_box, wbuf, len, IpDx);
2898 if (lattr != LATTR_TOP && (force_manual_underline ||
2899 (und_mode == UND_LINE
2900 && (attr & ATTR_UNDER)))) {
2903 if (lattr == LATTR_BOT)
2904 dec = dec * 2 - font_height;
2906 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, fg));
2907 MoveToEx(hdc, x, y + dec, NULL);
2908 LineTo(hdc, x + len * char_width, y + dec);
2909 oldpen = SelectObject(hdc, oldpen);
2910 DeleteObject(oldpen);
2914 void do_cursor(Context ctx, int x, int y, char *text, int len,
2915 unsigned long attr, int lattr)
2921 int ctype = cfg.cursor_type;
2923 if ((attr & TATTR_ACTCURS) && (ctype == 0 || term->big_cursor)) {
2924 if (((attr & CSET_MASK) | (unsigned char) *text) != UCSWIDE) {
2925 do_text(ctx, x, y, text, len, attr, lattr);
2929 attr |= TATTR_RIGHTCURS;
2932 fnt_width = char_width = font_width * (1 + (lattr != LATTR_NORM));
2933 if (attr & ATTR_WIDE)
2940 if ((attr & TATTR_PASCURS) && (ctype == 0 || term->big_cursor)) {
2943 pts[0].x = pts[1].x = pts[4].x = x;
2944 pts[2].x = pts[3].x = x + char_width - 1;
2945 pts[0].y = pts[3].y = pts[4].y = y;
2946 pts[1].y = pts[2].y = y + font_height - 1;
2947 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2948 Polyline(hdc, pts, 5);
2949 oldpen = SelectObject(hdc, oldpen);
2950 DeleteObject(oldpen);
2951 } else if ((attr & (TATTR_ACTCURS | TATTR_PASCURS)) && ctype != 0) {
2952 int startx, starty, dx, dy, length, i;
2955 starty = y + descent;
2958 length = char_width;
2961 if (attr & TATTR_RIGHTCURS)
2962 xadjust = char_width - 1;
2963 startx = x + xadjust;
2967 length = font_height;
2969 if (attr & TATTR_ACTCURS) {
2972 SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2973 MoveToEx(hdc, startx, starty, NULL);
2974 LineTo(hdc, startx + dx * length, starty + dy * length);
2975 oldpen = SelectObject(hdc, oldpen);
2976 DeleteObject(oldpen);
2978 for (i = 0; i < length; i++) {
2980 SetPixel(hdc, startx, starty, colours[23]);
2989 /* This function gets the actual width of a character in the normal font.
2991 int CharWidth(Context ctx, int uc) {
2995 /* If the font max is the same as the font ave width then this
2996 * function is a no-op.
2998 if (!font_dualwidth) return 1;
3000 switch (uc & CSET_MASK) {
3002 uc = unitab_line[uc & 0xFF];
3005 uc = unitab_xterm[uc & 0xFF];
3008 uc = unitab_scoacs[uc & 0xFF];
3011 if (DIRECT_FONT(uc)) {
3012 if (dbcs_screenfont) return 1;
3014 /* Speedup, I know of no font where ascii is the wrong width */
3015 if ((uc&CHAR_MASK) >= ' ' && (uc&CHAR_MASK)<= '~')
3018 if ( (uc & CSET_MASK) == ATTR_ACP ) {
3019 SelectObject(hdc, fonts[FONT_NORMAL]);
3020 } else if ( (uc & CSET_MASK) == ATTR_OEMCP ) {
3021 another_font(FONT_OEM);
3022 if (!fonts[FONT_OEM]) return 0;
3024 SelectObject(hdc, fonts[FONT_OEM]);
3028 if ( GetCharWidth32(hdc, uc&CHAR_MASK, uc&CHAR_MASK, &ibuf) != 1 &&
3029 GetCharWidth(hdc, uc&CHAR_MASK, uc&CHAR_MASK, &ibuf) != 1)
3032 /* Speedup, I know of no font where ascii is the wrong width */
3033 if (uc >= ' ' && uc <= '~') return 1;
3035 SelectObject(hdc, fonts[FONT_NORMAL]);
3036 if ( GetCharWidth32W(hdc, uc, uc, &ibuf) == 1 )
3037 /* Okay that one worked */ ;
3038 else if ( GetCharWidthW(hdc, uc, uc, &ibuf) == 1 )
3039 /* This should work on 9x too, but it's "less accurate" */ ;
3044 ibuf += font_width / 2 -1;
3051 * Translate a WM_(SYS)?KEY(UP|DOWN) message into a string of ASCII
3052 * codes. Returns number of bytes used or zero to drop the message
3053 * or -1 to forward the message to windows.
3055 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
3056 unsigned char *output)
3059 int scan, left_alt = 0, key_down, shift_state;
3061 unsigned char *p = output;
3062 static int alt_sum = 0;
3064 HKL kbd_layout = GetKeyboardLayout(0);
3066 static WORD keys[3];
3067 static int compose_char = 0;
3068 static WPARAM compose_key = 0;
3070 r = GetKeyboardState(keystate);
3072 memset(keystate, 0, sizeof(keystate));
3075 #define SHOW_TOASCII_RESULT
3076 { /* Tell us all about key events */
3077 static BYTE oldstate[256];
3078 static int first = 1;
3082 memcpy(oldstate, keystate, sizeof(oldstate));
3085 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT) {
3087 } else if ((HIWORD(lParam) & KF_UP)
3088 && scan == (HIWORD(lParam) & 0xFF)) {
3092 if (wParam >= VK_F1 && wParam <= VK_F20)
3093 debug(("K_F%d", wParam + 1 - VK_F1));
3106 debug(("VK_%02x", wParam));
3108 if (message == WM_SYSKEYDOWN || message == WM_SYSKEYUP)
3110 debug((", S%02x", scan = (HIWORD(lParam) & 0xFF)));
3112 ch = MapVirtualKeyEx(wParam, 2, kbd_layout);
3113 if (ch >= ' ' && ch <= '~')
3114 debug((", '%c'", ch));
3116 debug((", $%02x", ch));
3119 debug((", KB0=%02x", keys[0]));
3121 debug((", KB1=%02x", keys[1]));
3123 debug((", KB2=%02x", keys[2]));
3125 if ((keystate[VK_SHIFT] & 0x80) != 0)
3127 if ((keystate[VK_CONTROL] & 0x80) != 0)
3129 if ((HIWORD(lParam) & KF_EXTENDED))
3131 if ((HIWORD(lParam) & KF_UP))
3135 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT);
3136 else if ((HIWORD(lParam) & KF_UP))
3137 oldstate[wParam & 0xFF] ^= 0x80;
3139 oldstate[wParam & 0xFF] ^= 0x81;
3141 for (ch = 0; ch < 256; ch++)
3142 if (oldstate[ch] != keystate[ch])
3143 debug((", M%02x=%02x", ch, keystate[ch]));
3145 memcpy(oldstate, keystate, sizeof(oldstate));
3149 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED)) {
3150 keystate[VK_RMENU] = keystate[VK_MENU];
3154 /* Nastyness with NUMLock - Shift-NUMLock is left alone though */
3155 if ((cfg.funky_type == 3 ||
3156 (cfg.funky_type <= 1 && term->app_keypad_keys &&
3158 && wParam == VK_NUMLOCK && !(keystate[VK_SHIFT] & 0x80)) {
3160 wParam = VK_EXECUTE;
3162 /* UnToggle NUMLock */
3163 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0)
3164 keystate[VK_NUMLOCK] ^= 1;
3167 /* And write back the 'adjusted' state */
3168 SetKeyboardState(keystate);
3171 /* Disable Auto repeat if required */
3172 if (term->repeat_off &&
3173 (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT)
3176 if ((HIWORD(lParam) & KF_ALTDOWN) && (keystate[VK_RMENU] & 0x80) == 0)
3179 key_down = ((HIWORD(lParam) & KF_UP) == 0);
3181 /* Make sure Ctrl-ALT is not the same as AltGr for ToAscii unless told. */
3182 if (left_alt && (keystate[VK_CONTROL] & 0x80)) {
3183 if (cfg.ctrlaltkeys)
3184 keystate[VK_MENU] = 0;
3186 keystate[VK_RMENU] = 0x80;
3191 alt_pressed = (left_alt && key_down);
3193 scan = (HIWORD(lParam) & (KF_UP | KF_EXTENDED | 0xFF));
3194 shift_state = ((keystate[VK_SHIFT] & 0x80) != 0)
3195 + ((keystate[VK_CONTROL] & 0x80) != 0) * 2;
3197 /* Note if AltGr was pressed and if it was used as a compose key */
3198 if (!compose_state) {
3199 compose_key = 0x100;
3200 if (cfg.compose_key) {
3201 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED))
3202 compose_key = wParam;
3204 if (wParam == VK_APPS)
3205 compose_key = wParam;
3208 if (wParam == compose_key) {
3209 if (compose_state == 0
3210 && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0) compose_state =
3212 else if (compose_state == 1 && (HIWORD(lParam) & KF_UP))
3216 } else if (compose_state == 1 && wParam != VK_CONTROL)
3219 if (compose_state > 1 && left_alt)
3222 /* Sanitize the number pad if not using a PC NumPad */
3223 if (left_alt || (term->app_keypad_keys && !cfg.no_applic_k
3224 && cfg.funky_type != 2)
3225 || cfg.funky_type == 3 || cfg.nethack_keypad || compose_state) {
3226 if ((HIWORD(lParam) & KF_EXTENDED) == 0) {
3230 nParam = VK_NUMPAD0;
3233 nParam = VK_NUMPAD1;
3236 nParam = VK_NUMPAD2;
3239 nParam = VK_NUMPAD3;
3242 nParam = VK_NUMPAD4;
3245 nParam = VK_NUMPAD5;
3248 nParam = VK_NUMPAD6;
3251 nParam = VK_NUMPAD7;
3254 nParam = VK_NUMPAD8;
3257 nParam = VK_NUMPAD9;
3260 nParam = VK_DECIMAL;
3264 if (keystate[VK_NUMLOCK] & 1)
3271 /* If a key is pressed and AltGr is not active */
3272 if (key_down && (keystate[VK_RMENU] & 0x80) == 0 && !compose_state) {
3273 /* Okay, prepare for most alts then ... */
3277 /* Lets see if it's a pattern we know all about ... */
3278 if (wParam == VK_PRIOR && shift_state == 1) {
3279 SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0);
3282 if (wParam == VK_NEXT && shift_state == 1) {
3283 SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
3286 if (wParam == VK_INSERT && shift_state == 1) {
3287 term_do_paste(term);
3290 if (left_alt && wParam == VK_F4 && cfg.alt_f4) {
3293 if (left_alt && wParam == VK_SPACE && cfg.alt_space) {
3294 SendMessage(hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0);
3297 if (left_alt && wParam == VK_RETURN && cfg.fullscreenonaltenter &&
3298 (cfg.resize_action != RESIZE_DISABLED)) {
3299 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) != KF_REPEAT)
3303 /* Control-Numlock for app-keypad mode switch */
3304 if (wParam == VK_PAUSE && shift_state == 2) {
3305 term->app_keypad_keys ^= 1;
3309 /* Nethack keypad */
3310 if (cfg.nethack_keypad && !left_alt) {
3313 *p++ = shift_state ? 'B' : 'b';
3316 *p++ = shift_state ? 'J' : 'j';
3319 *p++ = shift_state ? 'N' : 'n';
3322 *p++ = shift_state ? 'H' : 'h';
3325 *p++ = shift_state ? '.' : '.';
3328 *p++ = shift_state ? 'L' : 'l';
3331 *p++ = shift_state ? 'Y' : 'y';
3334 *p++ = shift_state ? 'K' : 'k';
3337 *p++ = shift_state ? 'U' : 'u';
3342 /* Application Keypad */
3346 if (cfg.funky_type == 3 ||
3347 (cfg.funky_type <= 1 &&
3348 term->app_keypad_keys && !cfg.no_applic_k)) switch (wParam) {
3362 if (term->app_keypad_keys && !cfg.no_applic_k)
3399 if (cfg.funky_type == 2) {
3404 } else if (shift_state)
3411 if (cfg.funky_type == 2)
3415 if (cfg.funky_type == 2)
3419 if (cfg.funky_type == 2)
3424 if (HIWORD(lParam) & KF_EXTENDED)
3429 if (term->vt52_mode) {
3430 if (xkey >= 'P' && xkey <= 'S')
3431 p += sprintf((char *) p, "\x1B%c", xkey);
3433 p += sprintf((char *) p, "\x1B?%c", xkey);
3435 p += sprintf((char *) p, "\x1BO%c", xkey);
3440 if (wParam == VK_BACK && shift_state == 0) { /* Backspace */
3441 *p++ = (cfg.bksp_is_delete ? 0x7F : 0x08);
3445 if (wParam == VK_BACK && shift_state == 1) { /* Shift Backspace */
3446 /* We do the opposite of what is configured */
3447 *p++ = (cfg.bksp_is_delete ? 0x08 : 0x7F);
3451 if (wParam == VK_TAB && shift_state == 1) { /* Shift tab */
3457 if (wParam == VK_SPACE && shift_state == 2) { /* Ctrl-Space */
3461 if (wParam == VK_SPACE && shift_state == 3) { /* Ctrl-Shift-Space */
3465 if (wParam == VK_CANCEL && shift_state == 2) { /* Ctrl-Break */
3470 if (wParam == VK_PAUSE) { /* Break/Pause */
3475 /* Control-2 to Control-8 are special */
3476 if (shift_state == 2 && wParam >= '2' && wParam <= '8') {
3477 *p++ = "\000\033\034\035\036\037\177"[wParam - '2'];
3480 if (shift_state == 2 && wParam == 0xBD) {
3484 if (shift_state == 2 && wParam == 0xDF) {
3488 if (shift_state == 0 && wParam == VK_RETURN && term->cr_lf_return) {
3495 * Next, all the keys that do tilde codes. (ESC '[' nn '~',
3496 * for integer decimal nn.)
3498 * We also deal with the weird ones here. Linux VCs replace F1
3499 * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
3500 * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
3506 code = (keystate[VK_SHIFT] & 0x80 ? 23 : 11);
3509 code = (keystate[VK_SHIFT] & 0x80 ? 24 : 12);
3512 code = (keystate[VK_SHIFT] & 0x80 ? 25 : 13);
3515 code = (keystate[VK_SHIFT] & 0x80 ? 26 : 14);
3518 code = (keystate[VK_SHIFT] & 0x80 ? 28 : 15);
3521 code = (keystate[VK_SHIFT] & 0x80 ? 29 : 17);
3524 code = (keystate[VK_SHIFT] & 0x80 ? 31 : 18);
3527 code = (keystate[VK_SHIFT] & 0x80 ? 32 : 19);
3530 code = (keystate[VK_SHIFT] & 0x80 ? 33 : 20);
3533 code = (keystate[VK_SHIFT] & 0x80 ? 34 : 21);
3566 if ((shift_state&2) == 0) switch (wParam) {
3586 /* Reorder edit keys to physical order */
3587 if (cfg.funky_type == 3 && code <= 6)
3588 code = "\0\2\1\4\5\3\6"[code];
3590 if (term->vt52_mode && code > 0 && code <= 6) {
3591 p += sprintf((char *) p, "\x1B%c", " HLMEIG"[code]);
3595 if (cfg.funky_type == 5 && /* SCO function keys */
3596 code >= 11 && code <= 34) {
3597 char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
3600 case VK_F1: index = 0; break;
3601 case VK_F2: index = 1; break;
3602 case VK_F3: index = 2; break;
3603 case VK_F4: index = 3; break;
3604 case VK_F5: index = 4; break;
3605 case VK_F6: index = 5; break;
3606 case VK_F7: index = 6; break;
3607 case VK_F8: index = 7; break;
3608 case VK_F9: index = 8; break;
3609 case VK_F10: index = 9; break;
3610 case VK_F11: index = 10; break;
3611 case VK_F12: index = 11; break;
3613 if (keystate[VK_SHIFT] & 0x80) index += 12;
3614 if (keystate[VK_CONTROL] & 0x80) index += 24;
3615 p += sprintf((char *) p, "\x1B[%c", codes[index]);
3618 if (cfg.funky_type == 5 && /* SCO small keypad */
3619 code >= 1 && code <= 6) {
3620 char codes[] = "HL.FIG";
3624 p += sprintf((char *) p, "\x1B[%c", codes[code-1]);
3628 if ((term->vt52_mode || cfg.funky_type == 4) && code >= 11 && code <= 24) {
3634 if (term->vt52_mode)
3635 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11 - offt);
3638 sprintf((char *) p, "\x1BO%c", code + 'P' - 11 - offt);
3641 if (cfg.funky_type == 1 && code >= 11 && code <= 15) {
3642 p += sprintf((char *) p, "\x1B[[%c", code + 'A' - 11);
3645 if (cfg.funky_type == 2 && code >= 11 && code <= 14) {
3646 if (term->vt52_mode)
3647 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11);
3649 p += sprintf((char *) p, "\x1BO%c", code + 'P' - 11);
3652 if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
3653 p += sprintf((char *) p, code == 1 ? "\x1B[H" : "\x1BOw");
3657 p += sprintf((char *) p, "\x1B[%d~", code);
3662 * Now the remaining keys (arrows and Keypad 5. Keypad 5 for
3663 * some reason seems to send VK_CLEAR to Windows...).
3685 if (term->vt52_mode)
3686 p += sprintf((char *) p, "\x1B%c", xkey);
3688 int app_flg = (term->app_cursor_keys && !cfg.no_applic_c);
3691 * RDB: VT100 & VT102 manuals both state the
3692 * app cursor keys only work if the app keypad
3695 * SGT: That may well be true, but xterm
3696 * disagrees and so does at least one
3697 * application, so I've #if'ed this out and the
3698 * behaviour is back to PuTTY's original: app
3699 * cursor and app keypad are independently
3700 * switchable modes. If anyone complains about
3701 * _this_ I'll have to put in a configurable
3704 if (!term->app_keypad_keys)
3707 /* Useful mapping of Ctrl-arrows */
3708 if (shift_state == 2)
3712 p += sprintf((char *) p, "\x1BO%c", xkey);
3714 p += sprintf((char *) p, "\x1B[%c", xkey);
3721 * Finally, deal with Return ourselves. (Win95 seems to
3722 * foul it up when Alt is pressed, for some reason.)
3724 if (wParam == VK_RETURN) { /* Return */
3730 if (left_alt && wParam >= VK_NUMPAD0 && wParam <= VK_NUMPAD9)
3731 alt_sum = alt_sum * 10 + wParam - VK_NUMPAD0;
3736 /* Okay we've done everything interesting; let windows deal with
3737 * the boring stuff */
3741 /* helg: clear CAPS LOCK state if caps lock switches to cyrillic */
3742 if(cfg.xlat_capslockcyr && keystate[VK_CAPITAL] != 0) {
3744 keystate[VK_CAPITAL] = 0;
3747 r = ToAsciiEx(wParam, scan, keystate, keys, 0, kbd_layout);
3748 #ifdef SHOW_TOASCII_RESULT
3749 if (r == 1 && !key_down) {
3751 if (in_utf(term) || dbcs_screenfont)
3752 debug((", (U+%04x)", alt_sum));
3754 debug((", LCH(%d)", alt_sum));
3756 debug((", ACH(%d)", keys[0]));
3761 for (r1 = 0; r1 < r; r1++) {
3762 debug(("%s%d", r1 ? "," : "", keys[r1]));
3771 * Interrupt an ongoing paste. I'm not sure this is
3772 * sensible, but for the moment it's preferable to
3773 * having to faff about buffering things.
3778 for (i = 0; i < r; i++) {
3779 unsigned char ch = (unsigned char) keys[i];
3781 if (compose_state == 2 && (ch & 0x80) == 0 && ch > ' ') {
3786 if (compose_state == 3 && (ch & 0x80) == 0 && ch > ' ') {
3790 if ((nc = check_compose(compose_char, ch)) == -1) {
3791 MessageBeep(MB_ICONHAND);
3795 term_seen_key_event(term);
3796 luni_send(ldisc, &keybuf, 1, 1);
3804 if (in_utf(term) || dbcs_screenfont) {
3806 term_seen_key_event(term);
3807 luni_send(ldisc, &keybuf, 1, 1);
3809 ch = (char) alt_sum;
3811 * We need not bother about stdin
3812 * backlogs here, because in GUI PuTTY
3813 * we can't do anything about it
3814 * anyway; there's no means of asking
3815 * Windows to hold off on KEYDOWN
3816 * messages. We _have_ to buffer
3817 * everything we're sent.
3819 term_seen_key_event(term);
3820 ldisc_send(ldisc, &ch, 1, 1);
3824 term_seen_key_event(term);
3825 lpage_send(ldisc, kbd_codepage, &ch, 1, 1);
3827 if(capsOn && ch < 0x80) {
3830 cbuf[1] = xlat_uskbd2cyrllic(ch);
3831 term_seen_key_event(term);
3832 luni_send(ldisc, cbuf+!left_alt, 1+!!left_alt, 1);
3837 term_seen_key_event(term);
3838 lpage_send(ldisc, kbd_codepage,
3839 cbuf+!left_alt, 1+!!left_alt, 1);
3845 /* This is so the ALT-Numpad and dead keys work correctly. */
3850 /* If we're definitly not building up an ALT-54321 then clear it */
3853 /* If we will be using alt_sum fix the 256s */
3854 else if (keys[0] && (in_utf(term) || dbcs_screenfont))
3859 * ALT alone may or may not want to bring up the System menu.
3860 * If it's not meant to, we return 0 on presses or releases of
3861 * ALT, to show that we've swallowed the keystroke. Otherwise
3862 * we return -1, which means Windows will give the keystroke
3863 * its default handling (i.e. bring up the System menu).
3865 if (wParam == VK_MENU && !cfg.alt_only)
3871 void request_paste(void *frontend)
3874 * In Windows, pasting is synchronous: we can read the
3875 * clipboard with no difficulty, so request_paste() can just go
3878 term_do_paste(term);
3881 void set_title(void *frontend, char *title)
3884 window_name = smalloc(1 + strlen(title));
3885 strcpy(window_name, title);
3886 if (cfg.win_name_always || !IsIconic(hwnd))
3887 SetWindowText(hwnd, title);
3890 void set_icon(void *frontend, char *title)
3893 icon_name = smalloc(1 + strlen(title));
3894 strcpy(icon_name, title);
3895 if (!cfg.win_name_always && IsIconic(hwnd))
3896 SetWindowText(hwnd, title);
3899 void set_sbar(void *frontend, int total, int start, int page)
3903 if (is_full_screen() ? !cfg.scrollbar_in_fullscreen : !cfg.scrollbar)
3906 si.cbSize = sizeof(si);
3907 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
3909 si.nMax = total - 1;
3913 SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
3916 Context get_ctx(void *frontend)
3922 SelectPalette(hdc, pal, FALSE);
3928 void free_ctx(Context ctx)
3930 SelectPalette(ctx, GetStockObject(DEFAULT_PALETTE), FALSE);
3931 ReleaseDC(hwnd, ctx);
3934 static void real_palette_set(int n, int r, int g, int b)
3937 logpal->palPalEntry[n].peRed = r;
3938 logpal->palPalEntry[n].peGreen = g;
3939 logpal->palPalEntry[n].peBlue = b;
3940 logpal->palPalEntry[n].peFlags = PC_NOCOLLAPSE;
3941 colours[n] = PALETTERGB(r, g, b);
3942 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3944 colours[n] = RGB(r, g, b);
3947 void palette_set(void *frontend, int n, int r, int g, int b)
3949 static const int first[21] = {
3950 0, 2, 4, 6, 8, 10, 12, 14,
3951 1, 3, 5, 7, 9, 11, 13, 15,
3954 real_palette_set(first[n], r, g, b);
3956 real_palette_set(first[n] + 1, r, g, b);
3958 HDC hdc = get_ctx(frontend);
3959 UnrealizeObject(pal);
3960 RealizePalette(hdc);
3965 void palette_reset(void *frontend)
3969 for (i = 0; i < NCOLOURS; i++) {
3971 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
3972 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
3973 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
3974 logpal->palPalEntry[i].peFlags = 0;
3975 colours[i] = PALETTERGB(defpal[i].rgbtRed,
3976 defpal[i].rgbtGreen,
3977 defpal[i].rgbtBlue);
3979 colours[i] = RGB(defpal[i].rgbtRed,
3980 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
3985 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3986 hdc = get_ctx(frontend);
3987 RealizePalette(hdc);
3992 void write_aclip(void *frontend, char *data, int len, int must_deselect)
3997 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len + 1);
4000 lock = GlobalLock(clipdata);
4003 memcpy(lock, data, len);
4004 ((unsigned char *) lock)[len] = 0;
4005 GlobalUnlock(clipdata);
4008 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
4010 if (OpenClipboard(hwnd)) {
4012 SetClipboardData(CF_TEXT, clipdata);
4015 GlobalFree(clipdata);
4018 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
4022 * Note: unlike write_aclip() this will not append a nul.
4024 void write_clip(void *frontend, wchar_t * data, int len, int must_deselect)
4026 HGLOBAL clipdata, clipdata2, clipdata3;
4028 void *lock, *lock2, *lock3;
4030 len2 = WideCharToMultiByte(CP_ACP, 0, data, len, 0, 0, NULL, NULL);
4032 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE,
4033 len * sizeof(wchar_t));
4034 clipdata2 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len2);
4036 if (!clipdata || !clipdata2) {
4038 GlobalFree(clipdata);
4040 GlobalFree(clipdata2);
4043 if (!(lock = GlobalLock(clipdata)))
4045 if (!(lock2 = GlobalLock(clipdata2)))
4048 memcpy(lock, data, len * sizeof(wchar_t));
4049 WideCharToMultiByte(CP_ACP, 0, data, len, lock2, len2, NULL, NULL);
4051 if (cfg.rtf_paste) {
4052 wchar_t unitab[256];
4054 unsigned char *tdata = (unsigned char *)lock2;
4055 wchar_t *udata = (wchar_t *)lock;
4056 int rtflen = 0, uindex = 0, tindex = 0;
4058 int multilen, blen, alen, totallen, i;
4059 char before[16], after[4];
4061 get_unitab(CP_ACP, unitab, 0);
4063 rtfsize = 100 + strlen(cfg.font);
4064 rtf = smalloc(rtfsize);
4065 sprintf(rtf, "{\\rtf1\\ansi%d{\\fonttbl\\f0\\fmodern %s;}\\f0",
4066 GetACP(), cfg.font);
4067 rtflen = strlen(rtf);
4070 * We want to construct a piece of RTF that specifies the
4071 * same Unicode text. To do this we will read back in
4072 * parallel from the Unicode data in `udata' and the
4073 * non-Unicode data in `tdata'. For each character in
4074 * `tdata' which becomes the right thing in `udata' when
4075 * looked up in `unitab', we just copy straight over from
4076 * tdata. For each one that doesn't, we must WCToMB it
4077 * individually and produce a \u escape sequence.
4079 * It would probably be more robust to just bite the bullet
4080 * and WCToMB each individual Unicode character one by one,
4081 * then MBToWC each one back to see if it was an accurate
4082 * translation; but that strikes me as a horrifying number
4083 * of Windows API calls so I want to see if this faster way
4084 * will work. If it screws up badly we can always revert to
4085 * the simple and slow way.
4087 while (tindex < len2 && uindex < len &&
4088 tdata[tindex] && udata[uindex]) {
4089 if (tindex + 1 < len2 &&
4090 tdata[tindex] == '\r' &&
4091 tdata[tindex+1] == '\n') {
4095 if (unitab[tdata[tindex]] == udata[uindex]) {
4101 multilen = WideCharToMultiByte(CP_ACP, 0, unitab+uindex, 1,
4102 NULL, 0, NULL, NULL);
4103 if (multilen != 1) {
4104 blen = sprintf(before, "{\\uc%d\\u%d", multilen,
4106 alen = 1; strcpy(after, "}");
4108 blen = sprintf(before, "\\u%d", udata[uindex]);
4109 alen = 0; after[0] = '\0';
4112 assert(tindex + multilen <= len2);
4113 totallen = blen + alen;
4114 for (i = 0; i < multilen; i++) {
4115 if (tdata[tindex+i] == '\\' ||
4116 tdata[tindex+i] == '{' ||
4117 tdata[tindex+i] == '}')
4119 else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A)
4120 totallen += 6; /* \par\r\n */
4121 else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20)
4127 if (rtfsize < rtflen + totallen + 3) {
4128 rtfsize = rtflen + totallen + 512;
4129 rtf = srealloc(rtf, rtfsize);
4132 strcpy(rtf + rtflen, before); rtflen += blen;
4133 for (i = 0; i < multilen; i++) {
4134 if (tdata[tindex+i] == '\\' ||
4135 tdata[tindex+i] == '{' ||
4136 tdata[tindex+i] == '}') {
4137 rtf[rtflen++] = '\\';
4138 rtf[rtflen++] = tdata[tindex+i];
4139 } else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A) {
4140 rtflen += sprintf(rtf+rtflen, "\\par\r\n");
4141 } else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20) {
4142 rtflen += sprintf(rtf+rtflen, "\\'%02x", tdata[tindex+i]);
4144 rtf[rtflen++] = tdata[tindex+i];
4147 strcpy(rtf + rtflen, after); rtflen += alen;
4153 strcpy(rtf + rtflen, "}");
4156 clipdata3 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, rtflen);
4157 if (clipdata3 && (lock3 = GlobalLock(clipdata3)) != NULL) {
4159 GlobalUnlock(clipdata3);
4165 GlobalUnlock(clipdata);
4166 GlobalUnlock(clipdata2);
4169 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
4171 if (OpenClipboard(hwnd)) {
4173 SetClipboardData(CF_UNICODETEXT, clipdata);
4174 SetClipboardData(CF_TEXT, clipdata2);
4176 SetClipboardData(RegisterClipboardFormat(CF_RTF), clipdata3);
4179 GlobalFree(clipdata);
4180 GlobalFree(clipdata2);
4184 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
4187 void get_clip(void *frontend, wchar_t ** p, int *len)
4189 static HGLOBAL clipdata = NULL;
4190 static wchar_t *converted = 0;
4199 GlobalUnlock(clipdata);
4202 } else if (OpenClipboard(NULL)) {
4203 if ((clipdata = GetClipboardData(CF_UNICODETEXT))) {
4205 *p = GlobalLock(clipdata);
4207 for (p2 = *p; *p2; p2++);
4211 } else if ( (clipdata = GetClipboardData(CF_TEXT)) ) {
4215 s = GlobalLock(clipdata);
4216 i = MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, 0, 0);
4217 *p = converted = smalloc(i * sizeof(wchar_t));
4218 MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, converted, i);
4231 * Move `lines' lines from position `from' to position `to' in the
4234 void optimised_move(void *frontend, int to, int from, int lines)
4239 min = (to < from ? to : from);
4240 max = to + from - min;
4242 r.left = offset_width;
4243 r.right = offset_width + term->cols * font_width;
4244 r.top = offset_height + min * font_height;
4245 r.bottom = offset_height + (max + lines) * font_height;
4246 ScrollWindow(hwnd, 0, (to - from) * font_height, &r, &r);
4251 * Print a message box and perform a fatal exit.
4253 void fatalbox(char *fmt, ...)
4259 vsprintf(stuff, fmt, ap);
4261 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
4266 * Print a modal (Really Bad) message box and perform a fatal exit.
4268 void modalfatalbox(char *fmt, ...)
4274 vsprintf(stuff, fmt, ap);
4276 MessageBox(hwnd, stuff, "PuTTY Fatal Error",
4277 MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
4282 * Manage window caption / taskbar flashing, if enabled.
4283 * 0 = stop, 1 = maintain, 2 = start
4285 static void flash_window(int mode)
4287 static long last_flash = 0;
4288 static int flashing = 0;
4289 if ((mode == 0) || (cfg.beep_ind == B_IND_DISABLED)) {
4292 FlashWindow(hwnd, FALSE);
4296 } else if (mode == 2) {
4299 last_flash = GetTickCount();
4301 FlashWindow(hwnd, TRUE);
4304 } else if ((mode == 1) && (cfg.beep_ind == B_IND_FLASH)) {
4307 long now = GetTickCount();
4308 long fdiff = now - last_flash;
4309 if (fdiff < 0 || fdiff > 450) {
4311 FlashWindow(hwnd, TRUE); /* toggle */
4320 void beep(void *frontend, int mode)
4322 if (mode == BELL_DEFAULT) {
4324 * For MessageBeep style bells, we want to be careful of
4325 * timing, because they don't have the nice property of
4326 * PlaySound bells that each one cancels the previous
4327 * active one. So we limit the rate to one per 50ms or so.
4329 static long lastbeep = 0;
4332 beepdiff = GetTickCount() - lastbeep;
4333 if (beepdiff >= 0 && beepdiff < 50)
4337 * The above MessageBeep call takes time, so we record the
4338 * time _after_ it finishes rather than before it starts.
4340 lastbeep = GetTickCount();
4341 } else if (mode == BELL_WAVEFILE) {
4342 if (!PlaySound(cfg.bell_wavefile, NULL, SND_ASYNC | SND_FILENAME)) {
4343 char buf[sizeof(cfg.bell_wavefile) + 80];
4344 sprintf(buf, "Unable to play sound file\n%s\n"
4345 "Using default sound instead", cfg.bell_wavefile);
4346 MessageBox(hwnd, buf, "PuTTY Sound Error",
4347 MB_OK | MB_ICONEXCLAMATION);
4348 cfg.beep = BELL_DEFAULT;
4351 /* Otherwise, either visual bell or disabled; do nothing here */
4352 if (!term->has_focus) {
4353 flash_window(2); /* start */
4358 * Minimise or restore the window in response to a server-side
4361 void set_iconic(void *frontend, int iconic)
4363 if (IsIconic(hwnd)) {
4365 ShowWindow(hwnd, SW_RESTORE);
4368 ShowWindow(hwnd, SW_MINIMIZE);
4373 * Move the window in response to a server-side request.
4375 void move_window(void *frontend, int x, int y)
4377 if (cfg.resize_action == RESIZE_DISABLED ||
4378 cfg.resize_action == RESIZE_FONT ||
4382 SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
4386 * Move the window to the top or bottom of the z-order in response
4387 * to a server-side request.
4389 void set_zorder(void *frontend, int top)
4391 if (cfg.alwaysontop)
4392 return; /* ignore */
4393 SetWindowPos(hwnd, top ? HWND_TOP : HWND_BOTTOM, 0, 0, 0, 0,
4394 SWP_NOMOVE | SWP_NOSIZE);
4398 * Refresh the window in response to a server-side request.
4400 void refresh_window(void *frontend)
4402 InvalidateRect(hwnd, NULL, TRUE);
4406 * Maximise or restore the window in response to a server-side
4409 void set_zoomed(void *frontend, int zoomed)
4411 if (IsZoomed(hwnd)) {
4413 ShowWindow(hwnd, SW_RESTORE);
4416 ShowWindow(hwnd, SW_MAXIMIZE);
4421 * Report whether the window is iconic, for terminal reports.
4423 int is_iconic(void *frontend)
4425 return IsIconic(hwnd);
4429 * Report the window's position, for terminal reports.
4431 void get_window_pos(void *frontend, int *x, int *y)
4434 GetWindowRect(hwnd, &r);
4440 * Report the window's pixel size, for terminal reports.
4442 void get_window_pixels(void *frontend, int *x, int *y)
4445 GetWindowRect(hwnd, &r);
4446 *x = r.right - r.left;
4447 *y = r.bottom - r.top;
4451 * Return the window or icon title.
4453 char *get_window_title(void *frontend, int icon)
4455 return icon ? icon_name : window_name;
4459 * See if we're in full-screen mode.
4461 int is_full_screen()
4463 if (!IsZoomed(hwnd))
4465 if (GetWindowLong(hwnd, GWL_STYLE) & WS_CAPTION)
4470 /* Get the rect/size of a full screen window using the nearest available
4471 * monitor in multimon systems; default to something sensible if only
4472 * one monitor is present. */
4473 static int get_fullscreen_rect(RECT * ss)
4475 #ifdef MONITOR_DEFAULTTONEAREST
4478 mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
4479 mi.cbSize = sizeof(mi);
4480 GetMonitorInfo(mon, &mi);
4482 /* structure copy */
4486 /* could also use code like this:
4487 ss->left = ss->top = 0;
4488 ss->right = GetSystemMetrics(SM_CXSCREEN);
4489 ss->bottom = GetSystemMetrics(SM_CYSCREEN);
4491 return GetClientRect(GetDesktopWindow(), ss);
4497 * Go full-screen. This should only be called when we are already
4500 void make_full_screen()
4505 assert(IsZoomed(hwnd));
4507 if (is_full_screen())
4510 /* Remove the window furniture. */
4511 style = GetWindowLong(hwnd, GWL_STYLE);
4512 style &= ~(WS_CAPTION | WS_BORDER | WS_THICKFRAME);
4513 if (cfg.scrollbar_in_fullscreen)
4514 style |= WS_VSCROLL;
4516 style &= ~WS_VSCROLL;
4517 SetWindowLong(hwnd, GWL_STYLE, style);
4519 /* Resize ourselves to exactly cover the nearest monitor. */
4520 get_fullscreen_rect(&ss);
4521 SetWindowPos(hwnd, HWND_TOP, ss.left, ss.top,
4526 /* Tick the menu item in the System menu. */
4527 CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN,
4532 * Clear the full-screen attributes.
4534 void clear_full_screen()
4536 DWORD oldstyle, style;
4538 /* Reinstate the window furniture. */
4539 style = oldstyle = GetWindowLong(hwnd, GWL_STYLE);
4540 style |= WS_CAPTION | WS_BORDER;
4541 if (cfg.resize_action == RESIZE_DISABLED)
4542 style &= ~WS_THICKFRAME;
4544 style |= WS_THICKFRAME;
4546 style |= WS_VSCROLL;
4548 style &= ~WS_VSCROLL;
4549 if (style != oldstyle) {
4550 SetWindowLong(hwnd, GWL_STYLE, style);
4551 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
4552 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
4556 /* Untick the menu item in the System menu. */
4557 CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN,
4562 * Toggle full-screen mode.
4564 void flip_full_screen()
4566 if (is_full_screen()) {
4567 ShowWindow(hwnd, SW_RESTORE);
4568 } else if (IsZoomed(hwnd)) {
4571 SendMessage(hwnd, WM_FULLSCR_ON_MAX, 0, 0);
4572 ShowWindow(hwnd, SW_MAXIMIZE);
4576 void frontend_keypress(void *handle)
4579 * Keypress termination in non-Close-On-Exit mode is not
4580 * currently supported in PuTTY proper, because the window
4581 * always has a perfectly good Close button anyway. So we do