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 get_fullscreen_rect(RECT * ss);
113 static time_t last_movement = 0;
115 static int caret_x = -1, caret_y = -1;
117 #define FONT_NORMAL 0
119 #define FONT_UNDERLINE 2
120 #define FONT_BOLDUND 3
121 #define FONT_WIDE 0x04
122 #define FONT_HIGH 0x08
123 #define FONT_NARROW 0x10
125 #define FONT_OEM 0x20
126 #define FONT_OEMBOLD 0x21
127 #define FONT_OEMUND 0x22
128 #define FONT_OEMBOLDUND 0x23
130 #define FONT_MAXNO 0x2F
132 static HFONT fonts[FONT_MAXNO];
133 static LOGFONT lfont;
134 static int fontflag[FONT_MAXNO];
136 BOLD_COLOURS, BOLD_SHADOW, BOLD_FONT
144 static COLORREF colours[NCOLOURS];
146 static LPLOGPALETTE logpal;
147 static RGBTRIPLE defpal[NCOLOURS];
151 static HBITMAP caretbm;
153 static int dbltime, lasttime, lastact;
154 static Mouse_Button lastbtn;
156 /* this allows xterm-style mouse handling. */
157 static int send_raw_mouse = 0;
158 static int wheel_accumulator = 0;
160 static char *window_name, *icon_name;
162 static int compose_state = 0;
164 static int wsa_started = 0;
166 static OSVERSIONINFO osVersion;
168 static UINT wm_mousewheel = WM_MOUSEWHEEL;
170 /* Dummy routine, only required in plink. */
171 void ldisc_update(int echo, int edit)
175 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
177 static char appname[] = "PuTTY";
182 int guess_width, guess_height;
185 flags = FLAG_VERBOSE | FLAG_INTERACTIVE;
187 winsock_ver = MAKEWORD(1, 1);
188 if (WSAStartup(winsock_ver, &wsadata)) {
189 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
190 MB_OK | MB_ICONEXCLAMATION);
193 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
194 MessageBox(NULL, "WinSock version is incompatible with 1.1",
195 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
200 /* WISHLIST: maybe allow config tweaking even if winsock not present? */
203 InitCommonControls();
205 /* Ensure a Maximize setting in Explorer doesn't maximise the
210 ZeroMemory(&osVersion, sizeof(osVersion));
211 osVersion.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
212 if (!GetVersionEx ( (OSVERSIONINFO *) &osVersion)) {
213 MessageBox(NULL, "Windows refuses to report a version",
214 "PuTTY Fatal Error", MB_OK | MB_ICONEXCLAMATION);
220 * If we're running a version of Windows that doesn't support
221 * WM_MOUSEWHEEL, find out what message number we should be
224 if (osVersion.dwMajorVersion < 4 ||
225 (osVersion.dwMajorVersion == 4 &&
226 osVersion.dwPlatformId != VER_PLATFORM_WIN32_NT))
227 wm_mousewheel = RegisterWindowMessage("MSWHEEL_ROLLMSG");
230 * See if we can find our Help file.
233 char b[2048], *p, *q, *r;
235 GetModuleFileName(NULL, b, sizeof(b) - 1);
237 p = strrchr(b, '\\');
238 if (p && p >= r) r = p+1;
240 if (q && q >= r) r = q+1;
241 strcpy(r, "putty.hlp");
242 if ( (fp = fopen(b, "r")) != NULL) {
243 help_path = dupstr(b);
247 strcpy(r, "putty.cnt");
248 if ( (fp = fopen(b, "r")) != NULL) {
249 help_has_contents = TRUE;
252 help_has_contents = FALSE;
256 * Process the command line.
262 default_protocol = DEFAULT_PROTOCOL;
263 default_port = DEFAULT_PORT;
264 cfg.logtype = LGTYP_NONE;
266 do_defaults(NULL, &cfg);
271 * Process a couple of command-line options which are more
272 * easily dealt with before the line is broken up into
273 * words. These are the soon-to-be-defunct @sessionname and
274 * the internal-use-only &sharedmemoryhandle, neither of
275 * which are combined with anything else.
277 while (*p && isspace(*p))
281 while (i > 1 && isspace(p[i - 1]))
284 do_defaults(p + 1, &cfg);
285 if (!*cfg.host && !do_config()) {
289 } else if (*p == '&') {
291 * An initial & means we've been given a command line
292 * containing the hex value of a HANDLE for a file
293 * mapping object, which we must then extract as a
298 if (sscanf(p + 1, "%p", &filemap) == 1 &&
299 (cp = MapViewOfFile(filemap, FILE_MAP_READ,
300 0, 0, sizeof(Config))) != NULL) {
303 CloseHandle(filemap);
304 } else if (!do_config()) {
310 * Otherwise, break up the command line and deal with
316 split_into_argv(cmdline, &argc, &argv, NULL);
318 for (i = 0; i < argc; i++) {
322 ret = cmdline_process_param(p, i+1<argc?argv[i+1]:NULL, 1);
324 cmdline_error("option \"%s\" requires an argument", p);
325 } else if (ret == 2) {
326 i++; /* skip next argument */
327 } else if (ret == 1) {
328 continue; /* nothing further needs doing */
329 } else if (!strcmp(p, "-cleanup")) {
331 * `putty -cleanup'. Remove all registry
332 * entries associated with PuTTY, and also find
333 * and delete the random seed file.
336 "This procedure will remove ALL Registry\n"
337 "entries associated with PuTTY, and will\n"
338 "also remove the PuTTY random seed file.\n"
340 "THIS PROCESS WILL DESTROY YOUR SAVED\n"
341 "SESSIONS. Are you really sure you want\n"
344 MB_YESNO | MB_ICONWARNING) == IDYES) {
348 } else if (*p != '-') {
352 * If we already have a host name, treat
353 * this argument as a port number. NB we
354 * have to treat this as a saved -P
355 * argument, so that it will be deferred
356 * until it's a good moment to run it.
358 int ret = cmdline_process_param("-P", p, 1);
360 } else if (!strncmp(q, "telnet:", 7)) {
362 * If the hostname starts with "telnet:",
363 * set the protocol to Telnet and process
364 * the string as a Telnet URL.
369 if (q[0] == '/' && q[1] == '/')
371 cfg.protocol = PROT_TELNET;
373 while (*p && *p != ':' && *p != '/')
382 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
383 cfg.host[sizeof(cfg.host) - 1] = '\0';
387 * Otherwise, treat this argument as a host
390 while (*p && !isspace(*p))
394 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
395 cfg.host[sizeof(cfg.host) - 1] = '\0';
404 if (!*cfg.host && !do_config()) {
410 * Trim leading whitespace off the hostname if it's there.
413 int space = strspn(cfg.host, " \t");
414 memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
417 /* See if host is of the form user@host */
418 if (cfg.host[0] != '\0') {
419 char *atsign = strchr(cfg.host, '@');
420 /* Make sure we're not overflowing the user field */
422 if (atsign - cfg.host < sizeof cfg.username) {
423 strncpy(cfg.username, cfg.host, atsign - cfg.host);
424 cfg.username[atsign - cfg.host] = '\0';
426 memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
431 * Trim a colon suffix off the hostname if it's there.
433 cfg.host[strcspn(cfg.host, ":")] = '\0';
436 * Remove any remaining whitespace from the hostname.
440 while (cfg.host[p2] != '\0') {
441 if (cfg.host[p2] != ' ' && cfg.host[p2] != '\t') {
442 cfg.host[p1] = cfg.host[p2];
452 * Select protocol. This is farmed out into a table in a
453 * separate file to enable an ssh-free variant.
458 for (i = 0; backends[i].backend != NULL; i++)
459 if (backends[i].protocol == cfg.protocol) {
460 back = backends[i].backend;
464 MessageBox(NULL, "Unsupported protocol number found",
465 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
471 /* Check for invalid Port number (i.e. zero) */
473 MessageBox(NULL, "Invalid Port Number",
474 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
481 wndclass.lpfnWndProc = WndProc;
482 wndclass.cbClsExtra = 0;
483 wndclass.cbWndExtra = 0;
484 wndclass.hInstance = inst;
485 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
486 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
487 wndclass.hbrBackground = NULL;
488 wndclass.lpszMenuName = NULL;
489 wndclass.lpszClassName = appname;
491 RegisterClass(&wndclass);
501 * Guess some defaults for the window size. This all gets
502 * updated later, so we don't really care too much. However, we
503 * do want the font width/height guesses to correspond to a
504 * large font rather than a small one...
511 term_size(term, cfg.height, cfg.width, cfg.savelines);
512 guess_width = extra_width + font_width * term->cols;
513 guess_height = extra_height + font_height * term->rows;
516 get_fullscreen_rect(&r);
517 if (guess_width > r.right - r.left)
518 guess_width = r.right - r.left;
519 if (guess_height > r.bottom - r.top)
520 guess_height = r.bottom - r.top;
524 int winmode = WS_OVERLAPPEDWINDOW | WS_VSCROLL;
527 winmode &= ~(WS_VSCROLL);
528 if (cfg.resize_action == RESIZE_DISABLED)
529 winmode &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
531 exwinmode |= WS_EX_TOPMOST;
533 exwinmode |= WS_EX_CLIENTEDGE;
534 hwnd = CreateWindowEx(exwinmode, appname, appname,
535 winmode, CW_USEDEFAULT, CW_USEDEFAULT,
536 guess_width, guess_height,
537 NULL, NULL, inst, NULL);
541 * Initialise the fonts, simultaneously correcting the guesses
542 * for font_{width,height}.
547 * Correct the guesses for extra_{width,height}.
551 GetWindowRect(hwnd, &wr);
552 GetClientRect(hwnd, &cr);
553 offset_width = offset_height = cfg.window_border;
554 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
555 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
559 * Resize the window, now we know what size we _really_ want it
562 guess_width = extra_width + font_width * term->cols;
563 guess_height = extra_height + font_height * term->rows;
564 SetWindowPos(hwnd, NULL, 0, 0, guess_width, guess_height,
565 SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
568 * Set up a caret bitmap, with no content.
572 int size = (font_width + 15) / 16 * 2 * font_height;
573 bits = smalloc(size);
574 memset(bits, 0, size);
575 caretbm = CreateBitmap(font_width, font_height, 1, 1, bits);
578 CreateCaret(hwnd, caretbm, font_width, font_height);
581 * Initialise the scroll bar.
586 si.cbSize = sizeof(si);
587 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
589 si.nMax = term->rows - 1;
590 si.nPage = term->rows;
592 SetScrollInfo(hwnd, SB_VERT, &si, FALSE);
596 * Start up the telnet connection.
600 char msg[1024], *title;
603 error = back->init((void *)term,
604 cfg.host, cfg.port, &realhost, cfg.tcp_nodelay);
606 sprintf(msg, "Unable to open connection to\n"
607 "%.800s\n" "%s", cfg.host, error);
608 MessageBox(NULL, msg, "PuTTY Error", MB_ICONERROR | MB_OK);
611 window_name = icon_name = NULL;
613 title = cfg.wintitle;
615 sprintf(msg, "%s - PuTTY", realhost);
623 session_closed = FALSE;
626 * Prepare the mouse handler.
628 lastact = MA_NOTHING;
629 lastbtn = MBT_NOTHING;
630 dbltime = GetDoubleClickTime();
633 * Set up the session-control options on the system menu.
636 HMENU m = GetSystemMenu(hwnd, FALSE);
640 AppendMenu(m, MF_SEPARATOR, 0, 0);
641 if (cfg.protocol == PROT_TELNET) {
643 AppendMenu(p, MF_ENABLED, IDM_TEL_AYT, "Are You There");
644 AppendMenu(p, MF_ENABLED, IDM_TEL_BRK, "Break");
645 AppendMenu(p, MF_ENABLED, IDM_TEL_SYNCH, "Synch");
646 AppendMenu(p, MF_SEPARATOR, 0, 0);
647 AppendMenu(p, MF_ENABLED, IDM_TEL_EC, "Erase Character");
648 AppendMenu(p, MF_ENABLED, IDM_TEL_EL, "Erase Line");
649 AppendMenu(p, MF_ENABLED, IDM_TEL_GA, "Go Ahead");
650 AppendMenu(p, MF_ENABLED, IDM_TEL_NOP, "No Operation");
651 AppendMenu(p, MF_SEPARATOR, 0, 0);
652 AppendMenu(p, MF_ENABLED, IDM_TEL_ABORT, "Abort Process");
653 AppendMenu(p, MF_ENABLED, IDM_TEL_AO, "Abort Output");
654 AppendMenu(p, MF_ENABLED, IDM_TEL_IP, "Interrupt Process");
655 AppendMenu(p, MF_ENABLED, IDM_TEL_SUSP, "Suspend Process");
656 AppendMenu(p, MF_SEPARATOR, 0, 0);
657 AppendMenu(p, MF_ENABLED, IDM_TEL_EOR, "End Of Record");
658 AppendMenu(p, MF_ENABLED, IDM_TEL_EOF, "End Of File");
659 AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) p,
661 AppendMenu(m, MF_SEPARATOR, 0, 0);
663 AppendMenu(m, MF_ENABLED, IDM_SHOWLOG, "&Event Log");
664 AppendMenu(m, MF_SEPARATOR, 0, 0);
665 AppendMenu(m, MF_ENABLED, IDM_NEWSESS, "Ne&w Session...");
666 AppendMenu(m, MF_ENABLED, IDM_DUPSESS, "&Duplicate Session");
669 for (i = 1; i < ((nsessions < 256) ? nsessions : 256); i++)
670 AppendMenu(s, MF_ENABLED, IDM_SAVED_MIN + (16 * i),
672 AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) s, "Sa&ved Sessions");
673 AppendMenu(m, MF_ENABLED, IDM_RECONF, "Chan&ge Settings...");
674 AppendMenu(m, MF_SEPARATOR, 0, 0);
675 AppendMenu(m, MF_ENABLED, IDM_COPYALL, "C&opy All to Clipboard");
676 AppendMenu(m, MF_ENABLED, IDM_CLRSB, "C&lear Scrollback");
677 AppendMenu(m, MF_ENABLED, IDM_RESET, "Rese&t Terminal");
678 AppendMenu(m, MF_SEPARATOR, 0, 0);
679 AppendMenu(m, (cfg.resize_action == RESIZE_DISABLED) ?
680 MF_GRAYED : MF_ENABLED, IDM_FULLSCREEN, "&Full Screen");
681 AppendMenu(m, MF_SEPARATOR, 0, 0);
683 AppendMenu(m, MF_ENABLED, IDM_HELP, "&Help");
684 AppendMenu(m, MF_ENABLED, IDM_ABOUT, "&About PuTTY");
688 * Set up the initial input locale.
690 set_input_locale(GetKeyboardLayout(0));
693 * Open the initial log file if there is one.
698 * Finally show the window!
700 ShowWindow(hwnd, show);
701 SetForegroundWindow(hwnd);
704 * Set the palette up.
710 term->has_focus = (GetForegroundWindow() == hwnd);
713 if (GetMessage(&msg, NULL, 0, 0) == 1) {
714 int timer_id = 0, long_timer = 0;
716 while (msg.message != WM_QUIT) {
717 /* Sometimes DispatchMessage calls routines that use their own
718 * GetMessage loop, setup this timer so we get some control back.
720 * Also call term_update() from the timer so that if the host
721 * is sending data flat out we still do redraws.
723 if (timer_id && long_timer) {
724 KillTimer(hwnd, timer_id);
725 long_timer = timer_id = 0;
728 timer_id = SetTimer(hwnd, 1, 20, NULL);
729 if (!(IsWindow(logbox) && IsDialogMessage(logbox, &msg)))
730 DispatchMessage(&msg);
732 /* Make sure we blink everything that needs it. */
735 /* Send the paste buffer if there's anything to send */
738 /* If there's nothing new in the queue then we can do everything
739 * we've delayed, reading the socket, writing, and repainting
742 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
745 if (pending_netevent) {
746 enact_pending_netevent();
748 /* Force the cursor blink on */
751 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
755 /* Okay there is now nothing to do so we make sure the screen is
756 * completely up to date then tell windows to call us in a little
760 KillTimer(hwnd, timer_id);
764 if (GetCapture() != hwnd ||
766 !(cfg.mouse_override && is_shift_pressed())))
771 flash_window(1); /* maintain */
773 /* The messages seem unreliable; especially if we're being tricky */
774 term->has_focus = (GetForegroundWindow() == hwnd);
777 /* Hmm, term_update didn't want to do an update too soon ... */
778 timer_id = SetTimer(hwnd, 1, 50, NULL);
779 else if (!term->has_focus)
780 timer_id = SetTimer(hwnd, 1, 500, NULL);
782 timer_id = SetTimer(hwnd, 1, 100, NULL);
785 /* There's no point rescanning everything in the message queue
786 * so we do an apparently unnecessary wait here
789 if (GetMessage(&msg, NULL, 0, 0) != 1)
794 cleanup_exit(msg.wParam); /* this doesn't return... */
795 return msg.wParam; /* ... but optimiser doesn't know */
801 void cleanup_exit(int code)
814 if (cfg.protocol == PROT_SSH) {
825 * Set up, or shut down, an AsyncSelect. Called from winnet.c.
827 char *do_select(SOCKET skt, int startup)
832 events = (FD_CONNECT | FD_READ | FD_WRITE |
833 FD_OOB | FD_CLOSE | FD_ACCEPT);
838 return "do_select(): internal error (hwnd==NULL)";
839 if (WSAAsyncSelect(skt, hwnd, msg, events) == SOCKET_ERROR) {
840 switch (WSAGetLastError()) {
842 return "Network is down";
844 return "WSAAsyncSelect(): unknown error";
851 * set or clear the "raw mouse message" mode
853 void set_raw_mouse_mode(int activate)
855 activate = activate && !cfg.no_mouse_rep;
856 send_raw_mouse = activate;
857 SetCursor(LoadCursor(NULL, activate ? IDC_ARROW : IDC_IBEAM));
861 * Print a message box and close the connection.
863 void connection_fatal(char *fmt, ...)
869 vsprintf(stuff, fmt, ap);
871 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
872 if (cfg.close_on_exit == COE_ALWAYS)
875 session_closed = TRUE;
876 SetWindowText(hwnd, "PuTTY (inactive)");
881 * Report an error at the command-line parsing stage.
883 void cmdline_error(char *fmt, ...)
889 vsprintf(stuff, fmt, ap);
891 MessageBox(hwnd, stuff, "PuTTY Command Line Error", MB_ICONERROR | MB_OK);
896 * Actually do the job requested by a WM_NETEVENT
898 static void enact_pending_netevent(void)
900 static int reentering = 0;
901 extern int select_result(WPARAM, LPARAM);
905 return; /* don't unpend the pending */
907 pending_netevent = FALSE;
910 ret = select_result(pend_netevent_wParam, pend_netevent_lParam);
913 if (ret == 0 && !session_closed) {
914 /* Abnormal exits will already have set session_closed and taken
915 * appropriate action. */
916 if (cfg.close_on_exit == COE_ALWAYS ||
917 cfg.close_on_exit == COE_NORMAL) PostQuitMessage(0);
919 session_closed = TRUE;
920 SetWindowText(hwnd, "PuTTY (inactive)");
921 MessageBox(hwnd, "Connection closed by remote host",
922 "PuTTY", MB_OK | MB_ICONINFORMATION);
928 * Copy the colour palette from the configuration data into defpal.
929 * This is non-trivial because the colour indices are different.
931 static void cfgtopalette(void)
934 static const int ww[] = {
935 6, 7, 8, 9, 10, 11, 12, 13,
936 14, 15, 16, 17, 18, 19, 20, 21,
937 0, 1, 2, 3, 4, 4, 5, 5
940 for (i = 0; i < 24; i++) {
942 defpal[i].rgbtRed = cfg.colours[w][0];
943 defpal[i].rgbtGreen = cfg.colours[w][1];
944 defpal[i].rgbtBlue = cfg.colours[w][2];
949 * Set up the colour palette.
951 static void init_palette(void)
954 HDC hdc = GetDC(hwnd);
956 if (cfg.try_palette && GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) {
957 logpal = smalloc(sizeof(*logpal)
958 - sizeof(logpal->palPalEntry)
959 + NCOLOURS * sizeof(PALETTEENTRY));
960 logpal->palVersion = 0x300;
961 logpal->palNumEntries = NCOLOURS;
962 for (i = 0; i < NCOLOURS; i++) {
963 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
964 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
965 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
966 logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
968 pal = CreatePalette(logpal);
970 SelectPalette(hdc, pal, FALSE);
972 SelectPalette(hdc, GetStockObject(DEFAULT_PALETTE), FALSE);
975 ReleaseDC(hwnd, hdc);
978 for (i = 0; i < NCOLOURS; i++)
979 colours[i] = PALETTERGB(defpal[i].rgbtRed,
983 for (i = 0; i < NCOLOURS; i++)
984 colours[i] = RGB(defpal[i].rgbtRed,
985 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
989 * Initialise all the fonts we will need initially. There may be as many as
990 * three or as few as one. The other (poentially) twentyone fonts are done
991 * if/when they are needed.
995 * - check the font width and height, correcting our guesses if
998 * - verify that the bold font is the same width as the ordinary
999 * one, and engage shadow bolding if not.
1001 * - verify that the underlined font is the same width as the
1002 * ordinary one (manual underlining by means of line drawing can
1003 * be done in a pinch).
1005 static void init_fonts(int pick_width, int pick_height)
1012 int fw_dontcare, fw_bold;
1014 for (i = 0; i < FONT_MAXNO; i++)
1017 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
1018 und_mode = UND_FONT;
1020 if (cfg.fontisbold) {
1021 fw_dontcare = FW_BOLD;
1024 fw_dontcare = FW_DONTCARE;
1031 font_height = pick_height;
1033 font_height = cfg.fontheight;
1034 if (font_height > 0) {
1036 -MulDiv(font_height, GetDeviceCaps(hdc, LOGPIXELSY), 72);
1039 font_width = pick_width;
1041 #define f(i,c,w,u) \
1042 fonts[i] = CreateFont (font_height, font_width, 0, 0, w, FALSE, u, FALSE, \
1043 c, OUT_DEFAULT_PRECIS, \
1044 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, \
1045 FIXED_PITCH | FF_DONTCARE, cfg.font)
1047 f(FONT_NORMAL, cfg.fontcharset, fw_dontcare, FALSE);
1049 lfont.lfHeight = font_height;
1050 lfont.lfWidth = font_width;
1051 lfont.lfEscapement = 0;
1052 lfont.lfOrientation = 0;
1053 lfont.lfWeight = fw_dontcare;
1054 lfont.lfItalic = FALSE;
1055 lfont.lfUnderline = FALSE;
1056 lfont.lfStrikeOut = FALSE;
1057 lfont.lfCharSet = cfg.fontcharset;
1058 lfont.lfOutPrecision = OUT_DEFAULT_PRECIS;
1059 lfont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
1060 lfont.lfQuality = DEFAULT_QUALITY;
1061 lfont.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE;
1062 strncpy(lfont.lfFaceName, cfg.font, LF_FACESIZE);
1064 SelectObject(hdc, fonts[FONT_NORMAL]);
1065 GetTextMetrics(hdc, &tm);
1067 if (pick_width == 0 || pick_height == 0) {
1068 font_height = tm.tmHeight;
1069 font_width = tm.tmAveCharWidth;
1071 font_dualwidth = (tm.tmAveCharWidth != tm.tmMaxCharWidth);
1073 #ifdef RDB_DEBUG_PATCH
1074 debug(23, "Primary font H=%d, AW=%d, MW=%d",
1075 tm.tmHeight, tm.tmAveCharWidth, tm.tmMaxCharWidth);
1080 DWORD cset = tm.tmCharSet;
1081 memset(&info, 0xFF, sizeof(info));
1083 /* !!! Yes the next line is right */
1084 if (cset == OEM_CHARSET)
1085 font_codepage = GetOEMCP();
1087 if (TranslateCharsetInfo
1088 ((DWORD *) cset, &info, TCI_SRCCHARSET)) font_codepage =
1093 GetCPInfo(font_codepage, &cpinfo);
1094 dbcs_screenfont = (cpinfo.MaxCharSize > 1);
1097 f(FONT_UNDERLINE, cfg.fontcharset, fw_dontcare, TRUE);
1100 * Some fonts, e.g. 9-pt Courier, draw their underlines
1101 * outside their character cell. We successfully prevent
1102 * screen corruption by clipping the text output, but then
1103 * we lose the underline completely. Here we try to work
1104 * out whether this is such a font, and if it is, we set a
1105 * flag that causes underlines to be drawn by hand.
1107 * Having tried other more sophisticated approaches (such
1108 * as examining the TEXTMETRIC structure or requesting the
1109 * height of a string), I think we'll do this the brute
1110 * force way: we create a small bitmap, draw an underlined
1111 * space on it, and test to see whether any pixels are
1112 * foreground-coloured. (Since we expect the underline to
1113 * go all the way across the character cell, we only search
1114 * down a single column of the bitmap, half way across.)
1118 HBITMAP und_bm, und_oldbm;
1122 und_dc = CreateCompatibleDC(hdc);
1123 und_bm = CreateCompatibleBitmap(hdc, font_width, font_height);
1124 und_oldbm = SelectObject(und_dc, und_bm);
1125 SelectObject(und_dc, fonts[FONT_UNDERLINE]);
1126 SetTextAlign(und_dc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
1127 SetTextColor(und_dc, RGB(255, 255, 255));
1128 SetBkColor(und_dc, RGB(0, 0, 0));
1129 SetBkMode(und_dc, OPAQUE);
1130 ExtTextOut(und_dc, 0, 0, ETO_OPAQUE, NULL, " ", 1, NULL);
1132 for (i = 0; i < font_height; i++) {
1133 c = GetPixel(und_dc, font_width / 2, i);
1134 if (c != RGB(0, 0, 0))
1137 SelectObject(und_dc, und_oldbm);
1138 DeleteObject(und_bm);
1141 und_mode = UND_LINE;
1142 DeleteObject(fonts[FONT_UNDERLINE]);
1143 fonts[FONT_UNDERLINE] = 0;
1147 if (bold_mode == BOLD_FONT) {
1148 f(FONT_BOLD, cfg.fontcharset, fw_bold, FALSE);
1152 descent = tm.tmAscent + 1;
1153 if (descent >= font_height)
1154 descent = font_height - 1;
1156 for (i = 0; i < 3; i++) {
1158 if (SelectObject(hdc, fonts[i]) && GetTextMetrics(hdc, &tm))
1159 fontsize[i] = tm.tmAveCharWidth + 256 * tm.tmHeight;
1166 ReleaseDC(hwnd, hdc);
1168 if (fontsize[FONT_UNDERLINE] != fontsize[FONT_NORMAL]) {
1169 und_mode = UND_LINE;
1170 DeleteObject(fonts[FONT_UNDERLINE]);
1171 fonts[FONT_UNDERLINE] = 0;
1174 if (bold_mode == BOLD_FONT &&
1175 fontsize[FONT_BOLD] != fontsize[FONT_NORMAL]) {
1176 bold_mode = BOLD_SHADOW;
1177 DeleteObject(fonts[FONT_BOLD]);
1178 fonts[FONT_BOLD] = 0;
1180 fontflag[0] = fontflag[1] = fontflag[2] = 1;
1185 static void another_font(int fontno)
1188 int fw_dontcare, fw_bold;
1192 if (fontno < 0 || fontno >= FONT_MAXNO || fontflag[fontno])
1195 basefont = (fontno & ~(FONT_BOLDUND));
1196 if (basefont != fontno && !fontflag[basefont])
1197 another_font(basefont);
1199 if (cfg.fontisbold) {
1200 fw_dontcare = FW_BOLD;
1203 fw_dontcare = FW_DONTCARE;
1207 c = cfg.fontcharset;
1213 if (fontno & FONT_WIDE)
1215 if (fontno & FONT_NARROW)
1217 if (fontno & FONT_OEM)
1219 if (fontno & FONT_BOLD)
1221 if (fontno & FONT_UNDERLINE)
1225 CreateFont(font_height * (1 + !!(fontno & FONT_HIGH)), x, 0, 0, w,
1226 FALSE, u, FALSE, c, OUT_DEFAULT_PRECIS,
1227 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
1228 FIXED_PITCH | FF_DONTCARE, s);
1230 fontflag[fontno] = 1;
1233 static void deinit_fonts(void)
1236 for (i = 0; i < FONT_MAXNO; i++) {
1238 DeleteObject(fonts[i]);
1244 void request_resize(int w, int h)
1248 /* If the window is maximized supress resizing attempts */
1249 if (IsZoomed(hwnd)) {
1250 if (cfg.resize_action == RESIZE_TERM)
1254 if (cfg.resize_action == RESIZE_DISABLED) return;
1255 if (h == term->rows && w == term->cols) return;
1257 /* Sanity checks ... */
1259 static int first_time = 1;
1262 switch (first_time) {
1264 /* Get the size of the screen */
1265 if (get_fullscreen_rect(&ss))
1266 /* first_time = 0 */ ;
1272 /* Make sure the values are sane */
1273 width = (ss.right - ss.left - extra_width) / 4;
1274 height = (ss.bottom - ss.top - extra_height) / 6;
1276 if (w > width || h > height)
1285 term_size(term, h, w, cfg.savelines);
1287 if (cfg.resize_action != RESIZE_FONT && !IsZoomed(hwnd)) {
1288 width = extra_width + font_width * w;
1289 height = extra_height + font_height * h;
1291 SetWindowPos(hwnd, NULL, 0, 0, width, height,
1292 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1293 SWP_NOMOVE | SWP_NOZORDER);
1297 InvalidateRect(hwnd, NULL, TRUE);
1300 static void reset_window(int reinit) {
1302 * This function decides how to resize or redraw when the
1303 * user changes something.
1305 * This function doesn't like to change the terminal size but if the
1306 * font size is locked that may be it's only soluion.
1308 int win_width, win_height;
1311 #ifdef RDB_DEBUG_PATCH
1312 debug((27, "reset_window()"));
1315 /* Current window sizes ... */
1316 GetWindowRect(hwnd, &wr);
1317 GetClientRect(hwnd, &cr);
1319 win_width = cr.right - cr.left;
1320 win_height = cr.bottom - cr.top;
1322 if (cfg.resize_action == RESIZE_DISABLED) reinit = 2;
1324 /* Are we being forced to reload the fonts ? */
1326 #ifdef RDB_DEBUG_PATCH
1327 debug((27, "reset_window() -- Forced deinit"));
1333 /* Oh, looks like we're minimised */
1334 if (win_width == 0 || win_height == 0)
1337 /* Is the window out of position ? */
1339 (offset_width != (win_width-font_width*term->cols)/2 ||
1340 offset_height != (win_height-font_height*term->rows)/2) ){
1341 offset_width = (win_width-font_width*term->cols)/2;
1342 offset_height = (win_height-font_height*term->rows)/2;
1343 InvalidateRect(hwnd, NULL, TRUE);
1344 #ifdef RDB_DEBUG_PATCH
1345 debug((27, "reset_window() -> Reposition terminal"));
1349 if (IsZoomed(hwnd)) {
1350 /* We're fullscreen, this means we must not change the size of
1351 * the window so it's the font size or the terminal itself.
1354 extra_width = wr.right - wr.left - cr.right + cr.left;
1355 extra_height = wr.bottom - wr.top - cr.bottom + cr.top;
1357 if (cfg.resize_action != RESIZE_TERM) {
1358 if ( font_width != win_width/term->cols ||
1359 font_height != win_height/term->rows) {
1361 init_fonts(win_width/term->cols, win_height/term->rows);
1362 offset_width = (win_width-font_width*term->cols)/2;
1363 offset_height = (win_height-font_height*term->rows)/2;
1364 InvalidateRect(hwnd, NULL, TRUE);
1365 #ifdef RDB_DEBUG_PATCH
1366 debug((25, "reset_window() -> Z font resize to (%d, %d)",
1367 font_width, font_height));
1371 if ( font_width != win_width/term->cols ||
1372 font_height != win_height/term->rows) {
1373 /* Our only choice at this point is to change the
1374 * size of the terminal; Oh well.
1376 term_size(term, win_height/font_height, win_width/font_width,
1378 offset_width = (win_width-font_width*term->cols)/2;
1379 offset_height = (win_height-font_height*term->rows)/2;
1380 InvalidateRect(hwnd, NULL, TRUE);
1381 #ifdef RDB_DEBUG_PATCH
1382 debug((27, "reset_window() -> Zoomed term_size"));
1389 /* Hmm, a force re-init means we should ignore the current window
1390 * so we resize to the default font size.
1393 #ifdef RDB_DEBUG_PATCH
1394 debug((27, "reset_window() -> Forced re-init"));
1397 offset_width = offset_height = cfg.window_border;
1398 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
1399 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
1401 if (win_width != font_width*term->cols + offset_width*2 ||
1402 win_height != font_height*term->rows + offset_height*2) {
1404 /* If this is too large windows will resize it to the maximum
1405 * allowed window size, we will then be back in here and resize
1406 * the font or terminal to fit.
1408 SetWindowPos(hwnd, NULL, 0, 0,
1409 font_width*term->cols + extra_width,
1410 font_height*term->rows + extra_height,
1411 SWP_NOMOVE | SWP_NOZORDER);
1414 InvalidateRect(hwnd, NULL, TRUE);
1418 /* Okay the user doesn't want us to change the font so we try the
1419 * window. But that may be too big for the screen which forces us
1420 * to change the terminal.
1422 if ((cfg.resize_action == RESIZE_TERM && reinit<=0) ||
1423 (cfg.resize_action == RESIZE_EITHER && reinit<0) ||
1425 offset_width = offset_height = cfg.window_border;
1426 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
1427 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
1429 if (win_width != font_width*term->cols + offset_width*2 ||
1430 win_height != font_height*term->rows + offset_height*2) {
1435 get_fullscreen_rect(&ss);
1437 width = (ss.right - ss.left - extra_width) / font_width;
1438 height = (ss.bottom - ss.top - extra_height) / font_height;
1441 if ( term->rows > height || term->cols > width ) {
1442 if (cfg.resize_action == RESIZE_EITHER) {
1443 /* Make the font the biggest we can */
1444 if (term->cols > width)
1445 font_width = (ss.right - ss.left - extra_width)
1447 if (term->rows > height)
1448 font_height = (ss.bottom - ss.top - extra_height)
1452 init_fonts(font_width, font_height);
1454 width = (ss.right - ss.left - extra_width) / font_width;
1455 height = (ss.bottom - ss.top - extra_height) / font_height;
1457 if ( height > term->rows ) height = term->rows;
1458 if ( width > term->cols ) width = term->cols;
1459 term_size(term, height, width, cfg.savelines);
1460 #ifdef RDB_DEBUG_PATCH
1461 debug((27, "reset_window() -> term resize to (%d,%d)",
1467 SetWindowPos(hwnd, NULL, 0, 0,
1468 font_width*term->cols + extra_width,
1469 font_height*term->rows + extra_height,
1470 SWP_NOMOVE | SWP_NOZORDER);
1472 InvalidateRect(hwnd, NULL, TRUE);
1473 #ifdef RDB_DEBUG_PATCH
1474 debug((27, "reset_window() -> window resize to (%d,%d)",
1475 font_width*term->cols + extra_width,
1476 font_height*term->rows + extra_height));
1482 /* We're allowed to or must change the font but do we want to ? */
1484 if (font_width != (win_width-cfg.window_border*2)/term->cols ||
1485 font_height != (win_height-cfg.window_border*2)/term->rows) {
1488 init_fonts((win_width-cfg.window_border*2)/term->cols,
1489 (win_height-cfg.window_border*2)/term->rows);
1490 offset_width = (win_width-font_width*term->cols)/2;
1491 offset_height = (win_height-font_height*term->rows)/2;
1493 extra_width = wr.right - wr.left - cr.right + cr.left +offset_width*2;
1494 extra_height = wr.bottom - wr.top - cr.bottom + cr.top+offset_height*2;
1496 InvalidateRect(hwnd, NULL, TRUE);
1497 #ifdef RDB_DEBUG_PATCH
1498 debug((25, "reset_window() -> font resize to (%d,%d)",
1499 font_width, font_height));
1504 static void set_input_locale(HKL kl)
1508 GetLocaleInfo(LOWORD(kl), LOCALE_IDEFAULTANSICODEPAGE,
1509 lbuf, sizeof(lbuf));
1511 kbd_codepage = atoi(lbuf);
1514 static void click(Mouse_Button b, int x, int y, int shift, int ctrl, int alt)
1516 int thistime = GetMessageTime();
1518 if (send_raw_mouse && !(cfg.mouse_override && shift)) {
1519 lastbtn = MBT_NOTHING;
1520 term_mouse(term, b, MA_CLICK, x, y, shift, ctrl, alt);
1524 if (lastbtn == b && thistime - lasttime < dbltime) {
1525 lastact = (lastact == MA_CLICK ? MA_2CLK :
1526 lastact == MA_2CLK ? MA_3CLK :
1527 lastact == MA_3CLK ? MA_CLICK : MA_NOTHING);
1532 if (lastact != MA_NOTHING)
1533 term_mouse(term, b, lastact, x, y, shift, ctrl, alt);
1534 lasttime = thistime;
1538 * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT)
1539 * into a cooked one (SELECT, EXTEND, PASTE).
1541 Mouse_Button translate_button(Mouse_Button button)
1543 if (button == MBT_LEFT)
1545 if (button == MBT_MIDDLE)
1546 return cfg.mouse_is_xterm ? MBT_PASTE : MBT_EXTEND;
1547 if (button == MBT_RIGHT)
1548 return cfg.mouse_is_xterm ? MBT_EXTEND : MBT_PASTE;
1549 return 0; /* shouldn't happen */
1552 static void show_mouseptr(int show)
1554 static int cursor_visible = 1;
1555 if (!cfg.hide_mouseptr) /* override if this feature disabled */
1557 if (cursor_visible && !show)
1559 else if (!cursor_visible && show)
1561 cursor_visible = show;
1564 static int is_alt_pressed(void)
1567 int r = GetKeyboardState(keystate);
1570 if (keystate[VK_MENU] & 0x80)
1572 if (keystate[VK_RMENU] & 0x80)
1577 static int is_shift_pressed(void)
1580 int r = GetKeyboardState(keystate);
1583 if (keystate[VK_SHIFT] & 0x80)
1588 static int resizing;
1590 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1591 WPARAM wParam, LPARAM lParam)
1594 static int ignore_clip = FALSE;
1595 static int need_backend_resize = FALSE;
1596 static int fullscr_on_max = FALSE;
1600 if (pending_netevent)
1601 enact_pending_netevent();
1602 if (GetCapture() != hwnd ||
1603 (send_raw_mouse && !(cfg.mouse_override && is_shift_pressed())))
1609 if (cfg.ping_interval > 0) {
1612 if (now - last_movement > cfg.ping_interval) {
1613 back->special(TS_PING);
1614 last_movement = now;
1617 net_pending_errors();
1623 if (!cfg.warn_on_close || session_closed ||
1625 "Are you sure you want to close this session?",
1626 "PuTTY Exit Confirmation",
1627 MB_ICONWARNING | MB_OKCANCEL) == IDOK)
1628 DestroyWindow(hwnd);
1635 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1647 PROCESS_INFORMATION pi;
1648 HANDLE filemap = NULL;
1650 if (wParam == IDM_DUPSESS) {
1652 * Allocate a file-mapping memory chunk for the
1655 SECURITY_ATTRIBUTES sa;
1658 sa.nLength = sizeof(sa);
1659 sa.lpSecurityDescriptor = NULL;
1660 sa.bInheritHandle = TRUE;
1661 filemap = CreateFileMapping((HANDLE) 0xFFFFFFFF,
1664 0, sizeof(Config), NULL);
1666 p = (Config *) MapViewOfFile(filemap,
1668 0, 0, sizeof(Config));
1670 *p = cfg; /* structure copy */
1674 sprintf(c, "putty &%p", filemap);
1676 } else if (wParam == IDM_SAVEDSESS) {
1677 if ((lParam - IDM_SAVED_MIN) / 16 < nsessions) {
1679 sessions[(lParam - IDM_SAVED_MIN) / 16];
1680 cl = smalloc(16 + strlen(session));
1681 /* 8, but play safe */
1684 /* not a very important failure mode */
1686 sprintf(cl, "putty @%s", session);
1694 GetModuleFileName(NULL, b, sizeof(b) - 1);
1696 si.lpReserved = NULL;
1697 si.lpDesktop = NULL;
1701 si.lpReserved2 = NULL;
1702 CreateProcess(b, cl, NULL, NULL, TRUE,
1703 NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
1706 CloseHandle(filemap);
1716 GetWindowText(hwnd, cfg.wintitle, sizeof(cfg.wintitle));
1719 if (!do_reconfig(hwnd))
1723 /* Disable full-screen if resizing forbidden */
1724 HMENU m = GetSystemMenu (hwnd, FALSE);
1725 EnableMenuItem(m, IDM_FULLSCREEN, MF_BYCOMMAND |
1726 (cfg.resize_action == RESIZE_DISABLED)
1727 ? MF_GRAYED : MF_ENABLED);
1728 /* Gracefully unzoom if necessary */
1729 if (IsZoomed(hwnd) &&
1730 (cfg.resize_action == RESIZE_DISABLED)) {
1731 ShowWindow(hwnd, SW_RESTORE);
1735 if (strcmp(prev_cfg.logfilename, cfg.logfilename) ||
1736 prev_cfg.logtype != cfg.logtype) {
1737 logfclose(); /* reset logging */
1743 * Flush the line discipline's edit buffer in the
1744 * case where local editing has just been disabled.
1746 ldisc_send(NULL, 0, 0);
1754 /* Give terminal a heads-up on miscellaneous stuff */
1755 term_reconfig(term);
1757 /* Screen size changed ? */
1758 if (cfg.height != prev_cfg.height ||
1759 cfg.width != prev_cfg.width ||
1760 cfg.savelines != prev_cfg.savelines ||
1761 cfg.resize_action == RESIZE_FONT ||
1762 (cfg.resize_action == RESIZE_EITHER && IsZoomed(hwnd)) ||
1763 cfg.resize_action == RESIZE_DISABLED)
1764 term_size(term, cfg.height, cfg.width, cfg.savelines);
1766 /* Enable or disable the scroll bar, etc */
1768 LONG nflg, flag = GetWindowLong(hwnd, GWL_STYLE);
1769 LONG nexflag, exflag =
1770 GetWindowLong(hwnd, GWL_EXSTYLE);
1773 if (cfg.alwaysontop != prev_cfg.alwaysontop) {
1774 if (cfg.alwaysontop) {
1775 nexflag |= WS_EX_TOPMOST;
1776 SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
1777 SWP_NOMOVE | SWP_NOSIZE);
1779 nexflag &= ~(WS_EX_TOPMOST);
1780 SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0,
1781 SWP_NOMOVE | SWP_NOSIZE);
1784 if (cfg.sunken_edge)
1785 nexflag |= WS_EX_CLIENTEDGE;
1787 nexflag &= ~(WS_EX_CLIENTEDGE);
1790 if (is_full_screen() ?
1791 cfg.scrollbar_in_fullscreen : cfg.scrollbar)
1794 nflg &= ~WS_VSCROLL;
1796 if (cfg.resize_action == RESIZE_DISABLED ||
1798 nflg &= ~WS_THICKFRAME;
1800 nflg |= WS_THICKFRAME;
1802 if (cfg.resize_action == RESIZE_DISABLED)
1803 nflg &= ~WS_MAXIMIZEBOX;
1805 nflg |= WS_MAXIMIZEBOX;
1807 if (nflg != flag || nexflag != exflag) {
1809 SetWindowLong(hwnd, GWL_STYLE, nflg);
1810 if (nexflag != exflag)
1811 SetWindowLong(hwnd, GWL_EXSTYLE, nexflag);
1813 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
1814 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1815 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
1823 if (cfg.resize_action == RESIZE_DISABLED && IsZoomed(hwnd)) {
1828 set_title(cfg.wintitle);
1829 if (IsIconic(hwnd)) {
1831 cfg.win_name_always ? window_name :
1835 if (strcmp(cfg.font, prev_cfg.font) != 0 ||
1836 strcmp(cfg.line_codepage, prev_cfg.line_codepage) != 0 ||
1837 cfg.fontisbold != prev_cfg.fontisbold ||
1838 cfg.fontheight != prev_cfg.fontheight ||
1839 cfg.fontcharset != prev_cfg.fontcharset ||
1840 cfg.vtmode != prev_cfg.vtmode ||
1841 cfg.bold_colour != prev_cfg.bold_colour ||
1842 cfg.resize_action == RESIZE_DISABLED ||
1843 cfg.resize_action == RESIZE_EITHER ||
1844 (cfg.resize_action != prev_cfg.resize_action))
1847 InvalidateRect(hwnd, NULL, TRUE);
1848 reset_window(init_lvl);
1849 net_pending_errors();
1860 ldisc_send(NULL, 0, 0);
1863 back->special(TS_AYT);
1864 net_pending_errors();
1867 back->special(TS_BRK);
1868 net_pending_errors();
1871 back->special(TS_SYNCH);
1872 net_pending_errors();
1875 back->special(TS_EC);
1876 net_pending_errors();
1879 back->special(TS_EL);
1880 net_pending_errors();
1883 back->special(TS_GA);
1884 net_pending_errors();
1887 back->special(TS_NOP);
1888 net_pending_errors();
1891 back->special(TS_ABORT);
1892 net_pending_errors();
1895 back->special(TS_AO);
1896 net_pending_errors();
1899 back->special(TS_IP);
1900 net_pending_errors();
1903 back->special(TS_SUSP);
1904 net_pending_errors();
1907 back->special(TS_EOR);
1908 net_pending_errors();
1911 back->special(TS_EOF);
1912 net_pending_errors();
1918 WinHelp(hwnd, help_path,
1919 help_has_contents ? HELP_FINDER : HELP_CONTENTS, 0);
1923 * We get this if the System menu has been activated
1930 * We get this if the System menu has been activated
1931 * using the keyboard. This might happen from within
1932 * TranslateKey, in which case it really wants to be
1933 * followed by a `space' character to actually _bring
1934 * the menu up_ rather than just sitting there in
1935 * `ready to appear' state.
1937 show_mouseptr(1); /* make sure pointer is visible */
1939 PostMessage(hwnd, WM_CHAR, ' ', 0);
1941 case IDM_FULLSCREEN:
1945 if (wParam >= IDM_SAVED_MIN && wParam <= IDM_SAVED_MAX) {
1946 SendMessage(hwnd, WM_SYSCOMMAND, IDM_SAVEDSESS, wParam);
1951 #define X_POS(l) ((int)(short)LOWORD(l))
1952 #define Y_POS(l) ((int)(short)HIWORD(l))
1954 #define TO_CHR_X(x) ((((x)<0 ? (x)-font_width+1 : (x))-offset_width) / font_width)
1955 #define TO_CHR_Y(y) ((((y)<0 ? (y)-font_height+1: (y))-offset_height) / font_height)
1956 case WM_LBUTTONDOWN:
1957 case WM_MBUTTONDOWN:
1958 case WM_RBUTTONDOWN:
1966 case WM_LBUTTONDOWN:
1970 case WM_MBUTTONDOWN:
1971 button = MBT_MIDDLE;
1974 case WM_RBUTTONDOWN:
1983 button = MBT_MIDDLE;
1991 button = press = 0; /* shouldn't happen */
1995 * Special case: in full-screen mode, if the left
1996 * button is clicked in the very top left corner of the
1997 * window, we put up the System menu instead of doing
2000 if (is_full_screen() && press && button == MBT_LEFT &&
2001 X_POS(lParam) == 0 && Y_POS(lParam) == 0) {
2002 SendMessage(hwnd, WM_SYSCOMMAND, SC_MOUSEMENU, 0);
2007 TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam)),
2008 wParam & MK_SHIFT, wParam & MK_CONTROL,
2012 term_mouse(term, button, MA_RELEASE,
2013 TO_CHR_X(X_POS(lParam)),
2014 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
2015 wParam & MK_CONTROL, is_alt_pressed());
2023 * Add the mouse position and message time to the random
2026 noise_ultralight(lParam);
2028 if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON) &&
2029 GetCapture() == hwnd) {
2031 if (wParam & MK_LBUTTON)
2033 else if (wParam & MK_MBUTTON)
2037 term_mouse(term, b, MA_DRAG, TO_CHR_X(X_POS(lParam)),
2038 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
2039 wParam & MK_CONTROL, is_alt_pressed());
2042 case WM_NCMOUSEMOVE:
2044 noise_ultralight(lParam);
2046 case WM_IGNORE_CLIP:
2047 ignore_clip = wParam; /* don't panic on DESTROYCLIPBOARD */
2049 case WM_DESTROYCLIPBOARD:
2051 term_deselect(term);
2052 ignore_clip = FALSE;
2058 hdc = BeginPaint(hwnd, &p);
2060 SelectPalette(hdc, pal, TRUE);
2061 RealizePalette(hdc);
2063 term_paint(term, hdc,
2064 (p.rcPaint.left-offset_width)/font_width,
2065 (p.rcPaint.top-offset_height)/font_height,
2066 (p.rcPaint.right-offset_width-1)/font_width,
2067 (p.rcPaint.bottom-offset_height-1)/font_height);
2070 p.rcPaint.left < offset_width ||
2071 p.rcPaint.top < offset_height ||
2072 p.rcPaint.right >= offset_width + font_width*term->cols ||
2073 p.rcPaint.bottom>= offset_height + font_height*term->rows)
2075 HBRUSH fillcolour, oldbrush;
2077 fillcolour = CreateSolidBrush (
2078 colours[(ATTR_DEFBG>>ATTR_BGSHIFT)*2]);
2079 oldbrush = SelectObject(hdc, fillcolour);
2080 edge = CreatePen(PS_SOLID, 0,
2081 colours[(ATTR_DEFBG>>ATTR_BGSHIFT)*2]);
2082 oldpen = SelectObject(hdc, edge);
2085 * Jordan Russell reports that this apparently
2086 * ineffectual IntersectClipRect() call masks a
2087 * Windows NT/2K bug causing strange display
2088 * problems when the PuTTY window is taller than
2089 * the primary monitor. It seems harmless enough...
2091 IntersectClipRect(hdc,
2092 p.rcPaint.left, p.rcPaint.top,
2093 p.rcPaint.right, p.rcPaint.bottom);
2095 ExcludeClipRect(hdc,
2096 offset_width, offset_height,
2097 offset_width+font_width*term->cols,
2098 offset_height+font_height*term->rows);
2100 Rectangle(hdc, p.rcPaint.left, p.rcPaint.top,
2101 p.rcPaint.right, p.rcPaint.bottom);
2103 // SelectClipRgn(hdc, NULL);
2105 SelectObject(hdc, oldbrush);
2106 DeleteObject(fillcolour);
2107 SelectObject(hdc, oldpen);
2110 SelectObject(hdc, GetStockObject(SYSTEM_FONT));
2111 SelectObject(hdc, GetStockObject(WHITE_PEN));
2117 /* Notice we can get multiple netevents, FD_READ, FD_WRITE etc
2118 * but the only one that's likely to try to overload us is FD_READ.
2119 * This means buffering just one is fine.
2121 if (pending_netevent)
2122 enact_pending_netevent();
2124 pending_netevent = TRUE;
2125 pend_netevent_wParam = wParam;
2126 pend_netevent_lParam = lParam;
2127 if (WSAGETSELECTEVENT(lParam) != FD_READ)
2128 enact_pending_netevent();
2130 time(&last_movement);
2133 term->has_focus = TRUE;
2134 CreateCaret(hwnd, caretbm, font_width, font_height);
2136 flash_window(0); /* stop */
2143 term->has_focus = FALSE;
2145 caret_x = caret_y = -1; /* ensure caret is replaced next time */
2149 case WM_ENTERSIZEMOVE:
2150 #ifdef RDB_DEBUG_PATCH
2151 debug((27, "WM_ENTERSIZEMOVE"));
2155 need_backend_resize = FALSE;
2157 case WM_EXITSIZEMOVE:
2160 #ifdef RDB_DEBUG_PATCH
2161 debug((27, "WM_EXITSIZEMOVE"));
2163 if (need_backend_resize) {
2164 term_size(term, cfg.height, cfg.width, cfg.savelines);
2165 InvalidateRect(hwnd, NULL, TRUE);
2170 * This does two jobs:
2171 * 1) Keep the sizetip uptodate
2172 * 2) Make sure the window size is _stepped_ in units of the font size.
2174 if (cfg.resize_action != RESIZE_FONT && !alt_pressed) {
2175 int width, height, w, h, ew, eh;
2176 LPRECT r = (LPRECT) lParam;
2178 if ( !need_backend_resize && cfg.resize_action == RESIZE_EITHER &&
2179 (cfg.height != term->rows || cfg.width != term->cols )) {
2181 * Great! It seems that both the terminal size and the
2182 * font size have been changed and the user is now dragging.
2184 * It will now be difficult to get back to the configured
2187 * This would be easier but it seems to be too confusing.
2189 term_size(term, cfg.height, cfg.width, cfg.savelines);
2192 cfg.height=term->rows; cfg.width=term->cols;
2194 InvalidateRect(hwnd, NULL, TRUE);
2195 need_backend_resize = TRUE;
2198 width = r->right - r->left - extra_width;
2199 height = r->bottom - r->top - extra_height;
2200 w = (width + font_width / 2) / font_width;
2203 h = (height + font_height / 2) / font_height;
2206 UpdateSizeTip(hwnd, w, h);
2207 ew = width - w * font_width;
2208 eh = height - h * font_height;
2210 if (wParam == WMSZ_LEFT ||
2211 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
2217 if (wParam == WMSZ_TOP ||
2218 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
2228 int width, height, w, h, rv = 0;
2229 int ex_width = extra_width + (cfg.window_border - offset_width) * 2;
2230 int ex_height = extra_height + (cfg.window_border - offset_height) * 2;
2231 LPRECT r = (LPRECT) lParam;
2233 width = r->right - r->left - ex_width;
2234 height = r->bottom - r->top - ex_height;
2236 w = (width + term->cols/2)/term->cols;
2237 h = (height + term->rows/2)/term->rows;
2238 if ( r->right != r->left + w*term->cols + ex_width)
2241 if (wParam == WMSZ_LEFT ||
2242 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
2243 r->left = r->right - w*term->cols - ex_width;
2245 r->right = r->left + w*term->cols + ex_width;
2247 if (r->bottom != r->top + h*term->rows + ex_height)
2250 if (wParam == WMSZ_TOP ||
2251 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
2252 r->top = r->bottom - h*term->rows - ex_height;
2254 r->bottom = r->top + h*term->rows + ex_height;
2258 /* break; (never reached) */
2259 case WM_FULLSCR_ON_MAX:
2260 fullscr_on_max = TRUE;
2263 sys_cursor_update();
2266 #ifdef RDB_DEBUG_PATCH
2267 debug((27, "WM_SIZE %s (%d,%d)",
2268 (wParam == SIZE_MINIMIZED) ? "SIZE_MINIMIZED":
2269 (wParam == SIZE_MAXIMIZED) ? "SIZE_MAXIMIZED":
2270 (wParam == SIZE_RESTORED && resizing) ? "to":
2271 (wParam == SIZE_RESTORED) ? "SIZE_RESTORED":
2273 LOWORD(lParam), HIWORD(lParam)));
2275 if (wParam == SIZE_MINIMIZED)
2277 cfg.win_name_always ? window_name : icon_name);
2278 if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
2279 SetWindowText(hwnd, window_name);
2280 if (wParam == SIZE_RESTORED)
2281 clear_full_screen();
2282 if (wParam == SIZE_MAXIMIZED && fullscr_on_max) {
2283 fullscr_on_max = FALSE;
2287 if (cfg.resize_action == RESIZE_DISABLED) {
2288 /* A resize, well it better be a minimize. */
2292 int width, height, w, h;
2294 width = LOWORD(lParam);
2295 height = HIWORD(lParam);
2298 if (wParam == SIZE_MAXIMIZED && !was_zoomed) {
2300 prev_rows = term->rows;
2301 prev_cols = term->cols;
2302 if (cfg.resize_action == RESIZE_TERM) {
2303 w = width / font_width;
2305 h = height / font_height;
2308 term_size(term, h, w, cfg.savelines);
2311 } else if (wParam == SIZE_RESTORED && was_zoomed) {
2313 if (cfg.resize_action == RESIZE_TERM)
2314 term_size(term, prev_rows, prev_cols, cfg.savelines);
2315 if (cfg.resize_action != RESIZE_FONT)
2320 /* This is an unexpected resize, these will normally happen
2321 * if the window is too large. Probably either the user
2322 * selected a huge font or the screen size has changed.
2324 * This is also called with minimize.
2326 else reset_window(-1);
2330 * Don't call back->size in mid-resize. (To prevent
2331 * massive numbers of resize events getting sent
2332 * down the connection during an NT opaque drag.)
2335 if (cfg.resize_action != RESIZE_FONT && !alt_pressed) {
2336 need_backend_resize = TRUE;
2337 w = (width-cfg.window_border*2) / font_width;
2339 h = (height-cfg.window_border*2) / font_height;
2348 sys_cursor_update();
2351 switch (LOWORD(wParam)) {
2353 term_scroll(term, -1, 0);
2356 term_scroll(term, +1, 0);
2359 term_scroll(term, 0, +1);
2362 term_scroll(term, 0, -1);
2365 term_scroll(term, 0, +term->rows / 2);
2368 term_scroll(term, 0, -term->rows / 2);
2370 case SB_THUMBPOSITION:
2372 term_scroll(term, 1, HIWORD(wParam));
2376 case WM_PALETTECHANGED:
2377 if ((HWND) wParam != hwnd && pal != NULL) {
2378 HDC hdc = get_ctx();
2380 if (RealizePalette(hdc) > 0)
2386 case WM_QUERYNEWPALETTE:
2388 HDC hdc = get_ctx();
2390 if (RealizePalette(hdc) > 0)
2402 * Add the scan code and keypress timing to the random
2405 noise_ultralight(lParam);
2408 * We don't do TranslateMessage since it disassociates the
2409 * resulting CHAR message from the KEYDOWN that sparked it,
2410 * which we occasionally don't want. Instead, we process
2411 * KEYDOWN, and call the Win32 translator functions so that
2412 * we get the translations under _our_ control.
2415 unsigned char buf[20];
2418 if (wParam == VK_PROCESSKEY) {
2421 m.message = WM_KEYDOWN;
2423 m.lParam = lParam & 0xdfff;
2424 TranslateMessage(&m);
2426 len = TranslateKey(message, wParam, lParam, buf);
2428 return DefWindowProc(hwnd, message, wParam, lParam);
2432 * Interrupt an ongoing paste. I'm not sure
2433 * this is sensible, but for the moment it's
2434 * preferable to having to faff about buffering
2440 * We need not bother about stdin backlogs
2441 * here, because in GUI PuTTY we can't do
2442 * anything about it anyway; there's no means
2443 * of asking Windows to hold off on KEYDOWN
2444 * messages. We _have_ to buffer everything
2447 term_seen_key_event(term);
2448 ldisc_send(buf, len, 1);
2453 net_pending_errors();
2455 case WM_INPUTLANGCHANGE:
2456 /* wParam == Font number */
2457 /* lParam == Locale */
2458 set_input_locale((HKL)lParam);
2459 sys_cursor_update();
2462 if(wParam == IMN_SETOPENSTATUS) {
2463 HIMC hImc = ImmGetContext(hwnd);
2464 ImmSetCompositionFont(hImc, &lfont);
2465 ImmReleaseContext(hwnd, hImc);
2469 case WM_IME_COMPOSITION:
2475 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ||
2476 osVersion.dwPlatformId == VER_PLATFORM_WIN32s) break; /* no Unicode */
2478 if ((lParam & GCS_RESULTSTR) == 0) /* Composition unfinished. */
2479 break; /* fall back to DefWindowProc */
2481 hIMC = ImmGetContext(hwnd);
2482 n = ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, NULL, 0);
2486 buff = (char*) smalloc(n);
2487 ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, buff, n);
2489 * Jaeyoun Chung reports that Korean character
2490 * input doesn't work correctly if we do a single
2491 * luni_send() covering the whole of buff. So
2492 * instead we luni_send the characters one by one.
2494 term_seen_key_event(term);
2495 for (i = 0; i < n; i += 2) {
2496 luni_send((unsigned short *)(buff+i), 1, 1);
2500 ImmReleaseContext(hwnd, hIMC);
2505 if (wParam & 0xFF00) {
2506 unsigned char buf[2];
2509 buf[0] = wParam >> 8;
2510 term_seen_key_event(term);
2511 lpage_send(kbd_codepage, buf, 2, 1);
2513 char c = (unsigned char) wParam;
2514 term_seen_key_event(term);
2515 lpage_send(kbd_codepage, &c, 1, 1);
2521 * Nevertheless, we are prepared to deal with WM_CHAR
2522 * messages, should they crop up. So if someone wants to
2523 * post the things to us as part of a macro manoeuvre,
2524 * we're ready to cope.
2527 char c = (unsigned char)wParam;
2528 term_seen_key_event(term);
2529 lpage_send(CP_ACP, &c, 1, 1);
2533 if (send_raw_mouse && LOWORD(lParam) == HTCLIENT) {
2534 SetCursor(LoadCursor(NULL, IDC_ARROW));
2538 if (message == wm_mousewheel || message == WM_MOUSEWHEEL) {
2539 int shift_pressed=0, control_pressed=0, alt_pressed=0;
2541 if (message == WM_MOUSEWHEEL) {
2542 wheel_accumulator += (short)HIWORD(wParam);
2543 shift_pressed=LOWORD(wParam) & MK_SHIFT;
2544 control_pressed=LOWORD(wParam) & MK_CONTROL;
2547 wheel_accumulator += (int)wParam;
2548 if (GetKeyboardState(keys)!=0) {
2549 shift_pressed=keys[VK_SHIFT]&0x80;
2550 control_pressed=keys[VK_CONTROL]&0x80;
2554 /* process events when the threshold is reached */
2555 while (abs(wheel_accumulator) >= WHEEL_DELTA) {
2558 /* reduce amount for next time */
2559 if (wheel_accumulator > 0) {
2561 wheel_accumulator -= WHEEL_DELTA;
2562 } else if (wheel_accumulator < 0) {
2564 wheel_accumulator += WHEEL_DELTA;
2568 if (send_raw_mouse &&
2569 !(cfg.mouse_override && shift_pressed)) {
2570 /* send a mouse-down followed by a mouse up */
2573 TO_CHR_X(X_POS(lParam)),
2574 TO_CHR_Y(Y_POS(lParam)), shift_pressed,
2575 control_pressed, is_alt_pressed());
2576 term_mouse(term, b, MA_RELEASE, TO_CHR_X(X_POS(lParam)),
2577 TO_CHR_Y(Y_POS(lParam)), shift_pressed,
2578 control_pressed, is_alt_pressed());
2580 /* trigger a scroll */
2581 term_scroll(term, 0,
2583 -term->rows / 2 : term->rows / 2);
2590 return DefWindowProc(hwnd, message, wParam, lParam);
2594 * Move the system caret. (We maintain one, even though it's
2595 * invisible, for the benefit of blind people: apparently some
2596 * helper software tracks the system caret, so we should arrange to
2599 void sys_cursor(int x, int y)
2603 if (!term->has_focus) return;
2606 * Avoid gratuitously re-updating the cursor position and IMM
2607 * window if there's no actual change required.
2609 cx = x * font_width + offset_width;
2610 cy = y * font_height + offset_height;
2611 if (cx == caret_x && cy == caret_y)
2616 sys_cursor_update();
2619 static void sys_cursor_update(void)
2624 if (!term->has_focus) return;
2626 if (caret_x < 0 || caret_y < 0)
2629 SetCaretPos(caret_x, caret_y);
2631 /* IMM calls on Win98 and beyond only */
2632 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32s) return; /* 3.11 */
2634 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS &&
2635 osVersion.dwMinorVersion == 0) return; /* 95 */
2637 /* we should have the IMM functions */
2638 hIMC = ImmGetContext(hwnd);
2639 cf.dwStyle = CFS_POINT;
2640 cf.ptCurrentPos.x = caret_x;
2641 cf.ptCurrentPos.y = caret_y;
2642 ImmSetCompositionWindow(hIMC, &cf);
2644 ImmReleaseContext(hwnd, hIMC);
2648 * Draw a line of text in the window, at given character
2649 * coordinates, in given attributes.
2651 * We are allowed to fiddle with the contents of `text'.
2653 void do_text(Context ctx, int x, int y, char *text, int len,
2654 unsigned long attr, int lattr)
2657 int nfg, nbg, nfont;
2660 int force_manual_underline = 0;
2661 int fnt_width = font_width * (1 + (lattr != LATTR_NORM));
2662 int char_width = fnt_width;
2663 int text_adjust = 0;
2664 static int *IpDx = 0, IpDxLEN = 0;
2666 if (attr & ATTR_WIDE)
2669 if (len > IpDxLEN || IpDx[0] != char_width) {
2671 if (len > IpDxLEN) {
2673 IpDx = smalloc((len + 16) * sizeof(int));
2674 IpDxLEN = (len + 16);
2676 for (i = 0; i < IpDxLEN; i++)
2677 IpDx[i] = char_width;
2680 /* Only want the left half of double width lines */
2681 if (lattr != LATTR_NORM && x*2 >= term->cols)
2689 if ((attr & TATTR_ACTCURS) && (cfg.cursor_type == 0 || term->big_cursor)) {
2690 attr &= ATTR_CUR_AND | (bold_mode != BOLD_COLOURS ? ATTR_BOLD : 0);
2691 attr ^= ATTR_CUR_XOR;
2695 if (cfg.vtmode == VT_POORMAN && lattr != LATTR_NORM) {
2696 /* Assume a poorman font is borken in other ways too. */
2706 nfont |= FONT_WIDE + FONT_HIGH;
2709 if (attr & ATTR_NARROW)
2710 nfont |= FONT_NARROW;
2712 /* Special hack for the VT100 linedraw glyphs. */
2713 if ((attr & CSET_MASK) == 0x2300) {
2714 if (text[0] >= (char) 0xBA && text[0] <= (char) 0xBD) {
2715 switch ((unsigned char) (text[0])) {
2717 text_adjust = -2 * font_height / 5;
2720 text_adjust = -1 * font_height / 5;
2723 text_adjust = font_height / 5;
2726 text_adjust = 2 * font_height / 5;
2729 if (lattr == LATTR_TOP || lattr == LATTR_BOT)
2732 text[0] = (char) (unitab_xterm['q'] & CHAR_MASK);
2733 attr |= (unitab_xterm['q'] & CSET_MASK);
2734 if (attr & ATTR_UNDER) {
2735 attr &= ~ATTR_UNDER;
2736 force_manual_underline = 1;
2741 /* Anything left as an original character set is unprintable. */
2742 if (DIRECT_CHAR(attr)) {
2745 memset(text, 0xFD, len);
2749 if ((attr & CSET_MASK) == ATTR_OEMCP)
2752 nfg = 2 * ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
2753 nbg = 2 * ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
2754 if (bold_mode == BOLD_FONT && (attr & ATTR_BOLD))
2756 if (und_mode == UND_FONT && (attr & ATTR_UNDER))
2757 nfont |= FONT_UNDERLINE;
2758 another_font(nfont);
2759 if (!fonts[nfont]) {
2760 if (nfont & FONT_UNDERLINE)
2761 force_manual_underline = 1;
2762 /* Don't do the same for manual bold, it could be bad news. */
2764 nfont &= ~(FONT_BOLD | FONT_UNDERLINE);
2766 another_font(nfont);
2768 nfont = FONT_NORMAL;
2769 if (attr & ATTR_REVERSE) {
2774 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BOLD))
2776 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BLINK))
2780 SelectObject(hdc, fonts[nfont]);
2781 SetTextColor(hdc, fg);
2782 SetBkColor(hdc, bg);
2783 SetBkMode(hdc, OPAQUE);
2786 line_box.right = x + char_width * len;
2787 line_box.bottom = y + font_height;
2789 /* Only want the left half of double width lines */
2790 if (line_box.right > font_width*term->cols+offset_width)
2791 line_box.right = font_width*term->cols+offset_width;
2793 /* We're using a private area for direct to font. (512 chars.) */
2794 if (dbcs_screenfont && (attr & CSET_MASK) == ATTR_ACP) {
2795 /* Ho Hum, dbcs fonts are a PITA! */
2796 /* To display on W9x I have to convert to UCS */
2797 static wchar_t *uni_buf = 0;
2798 static int uni_len = 0;
2800 if (len > uni_len) {
2802 uni_buf = smalloc((uni_len = len) * sizeof(wchar_t));
2805 for(nlen = mptr = 0; mptr<len; mptr++) {
2806 uni_buf[nlen] = 0xFFFD;
2807 if (IsDBCSLeadByteEx(font_codepage, (BYTE) text[mptr])) {
2808 IpDx[nlen] += char_width;
2809 MultiByteToWideChar(font_codepage, MB_USEGLYPHCHARS,
2810 text+mptr, 2, uni_buf+nlen, 1);
2815 MultiByteToWideChar(font_codepage, MB_USEGLYPHCHARS,
2816 text+mptr, 1, uni_buf+nlen, 1);
2824 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2825 ETO_CLIPPED | ETO_OPAQUE, &line_box, uni_buf, nlen, IpDx);
2826 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2827 SetBkMode(hdc, TRANSPARENT);
2828 ExtTextOutW(hdc, x - 1,
2829 y - font_height * (lattr ==
2830 LATTR_BOT) + text_adjust,
2831 ETO_CLIPPED, &line_box, uni_buf, nlen, IpDx);
2835 } else if (DIRECT_FONT(attr)) {
2837 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2838 ETO_CLIPPED | ETO_OPAQUE, &line_box, text, len, IpDx);
2839 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2840 SetBkMode(hdc, TRANSPARENT);
2842 /* GRR: This draws the character outside it's box and can leave
2843 * 'droppings' even with the clip box! I suppose I could loop it
2844 * one character at a time ... yuk.
2846 * Or ... I could do a test print with "W", and use +1 or -1 for this
2847 * shift depending on if the leftmost column is blank...
2849 ExtTextOut(hdc, x - 1,
2850 y - font_height * (lattr ==
2851 LATTR_BOT) + text_adjust,
2852 ETO_CLIPPED, &line_box, text, len, IpDx);
2855 /* And 'normal' unicode characters */
2856 static WCHAR *wbuf = NULL;
2857 static int wlen = 0;
2862 wbuf = smalloc(wlen * sizeof(WCHAR));
2864 for (i = 0; i < len; i++)
2865 wbuf[i] = (WCHAR) ((attr & CSET_MASK) + (text[i] & CHAR_MASK));
2868 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2869 ETO_CLIPPED | ETO_OPAQUE, &line_box, wbuf, len, IpDx);
2871 /* And the shadow bold hack. */
2872 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2873 SetBkMode(hdc, TRANSPARENT);
2874 ExtTextOutW(hdc, x - 1,
2875 y - font_height * (lattr ==
2876 LATTR_BOT) + text_adjust,
2877 ETO_CLIPPED, &line_box, wbuf, len, IpDx);
2880 if (lattr != LATTR_TOP && (force_manual_underline ||
2881 (und_mode == UND_LINE
2882 && (attr & ATTR_UNDER)))) {
2885 if (lattr == LATTR_BOT)
2886 dec = dec * 2 - font_height;
2888 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, fg));
2889 MoveToEx(hdc, x, y + dec, NULL);
2890 LineTo(hdc, x + len * char_width, y + dec);
2891 oldpen = SelectObject(hdc, oldpen);
2892 DeleteObject(oldpen);
2896 void do_cursor(Context ctx, int x, int y, char *text, int len,
2897 unsigned long attr, int lattr)
2903 int ctype = cfg.cursor_type;
2905 if ((attr & TATTR_ACTCURS) && (ctype == 0 || term->big_cursor)) {
2906 if (((attr & CSET_MASK) | (unsigned char) *text) != UCSWIDE) {
2907 do_text(ctx, x, y, text, len, attr, lattr);
2911 attr |= TATTR_RIGHTCURS;
2914 fnt_width = char_width = font_width * (1 + (lattr != LATTR_NORM));
2915 if (attr & ATTR_WIDE)
2922 if ((attr & TATTR_PASCURS) && (ctype == 0 || term->big_cursor)) {
2925 pts[0].x = pts[1].x = pts[4].x = x;
2926 pts[2].x = pts[3].x = x + char_width - 1;
2927 pts[0].y = pts[3].y = pts[4].y = y;
2928 pts[1].y = pts[2].y = y + font_height - 1;
2929 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2930 Polyline(hdc, pts, 5);
2931 oldpen = SelectObject(hdc, oldpen);
2932 DeleteObject(oldpen);
2933 } else if ((attr & (TATTR_ACTCURS | TATTR_PASCURS)) && ctype != 0) {
2934 int startx, starty, dx, dy, length, i;
2937 starty = y + descent;
2940 length = char_width;
2943 if (attr & TATTR_RIGHTCURS)
2944 xadjust = char_width - 1;
2945 startx = x + xadjust;
2949 length = font_height;
2951 if (attr & TATTR_ACTCURS) {
2954 SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2955 MoveToEx(hdc, startx, starty, NULL);
2956 LineTo(hdc, startx + dx * length, starty + dy * length);
2957 oldpen = SelectObject(hdc, oldpen);
2958 DeleteObject(oldpen);
2960 for (i = 0; i < length; i++) {
2962 SetPixel(hdc, startx, starty, colours[23]);
2971 /* This function gets the actual width of a character in the normal font.
2973 int CharWidth(Context ctx, int uc) {
2977 /* If the font max is the same as the font ave width then this
2978 * function is a no-op.
2980 if (!font_dualwidth) return 1;
2982 switch (uc & CSET_MASK) {
2984 uc = unitab_line[uc & 0xFF];
2987 uc = unitab_xterm[uc & 0xFF];
2990 uc = unitab_scoacs[uc & 0xFF];
2993 if (DIRECT_FONT(uc)) {
2994 if (dbcs_screenfont) return 1;
2996 /* Speedup, I know of no font where ascii is the wrong width */
2997 if ((uc&CHAR_MASK) >= ' ' && (uc&CHAR_MASK)<= '~')
3000 if ( (uc & CSET_MASK) == ATTR_ACP ) {
3001 SelectObject(hdc, fonts[FONT_NORMAL]);
3002 } else if ( (uc & CSET_MASK) == ATTR_OEMCP ) {
3003 another_font(FONT_OEM);
3004 if (!fonts[FONT_OEM]) return 0;
3006 SelectObject(hdc, fonts[FONT_OEM]);
3010 if ( GetCharWidth32(hdc, uc&CHAR_MASK, uc&CHAR_MASK, &ibuf) != 1 &&
3011 GetCharWidth(hdc, uc&CHAR_MASK, uc&CHAR_MASK, &ibuf) != 1)
3014 /* Speedup, I know of no font where ascii is the wrong width */
3015 if (uc >= ' ' && uc <= '~') return 1;
3017 SelectObject(hdc, fonts[FONT_NORMAL]);
3018 if ( GetCharWidth32W(hdc, uc, uc, &ibuf) == 1 )
3019 /* Okay that one worked */ ;
3020 else if ( GetCharWidthW(hdc, uc, uc, &ibuf) == 1 )
3021 /* This should work on 9x too, but it's "less accurate" */ ;
3026 ibuf += font_width / 2 -1;
3033 * Translate a WM_(SYS)?KEY(UP|DOWN) message into a string of ASCII
3034 * codes. Returns number of bytes used or zero to drop the message
3035 * or -1 to forward the message to windows.
3037 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
3038 unsigned char *output)
3041 int scan, left_alt = 0, key_down, shift_state;
3043 unsigned char *p = output;
3044 static int alt_sum = 0;
3046 HKL kbd_layout = GetKeyboardLayout(0);
3048 static WORD keys[3];
3049 static int compose_char = 0;
3050 static WPARAM compose_key = 0;
3052 r = GetKeyboardState(keystate);
3054 memset(keystate, 0, sizeof(keystate));
3057 #define SHOW_TOASCII_RESULT
3058 { /* Tell us all about key events */
3059 static BYTE oldstate[256];
3060 static int first = 1;
3064 memcpy(oldstate, keystate, sizeof(oldstate));
3067 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT) {
3069 } else if ((HIWORD(lParam) & KF_UP)
3070 && scan == (HIWORD(lParam) & 0xFF)) {
3074 if (wParam >= VK_F1 && wParam <= VK_F20)
3075 debug(("K_F%d", wParam + 1 - VK_F1));
3088 debug(("VK_%02x", wParam));
3090 if (message == WM_SYSKEYDOWN || message == WM_SYSKEYUP)
3092 debug((", S%02x", scan = (HIWORD(lParam) & 0xFF)));
3094 ch = MapVirtualKeyEx(wParam, 2, kbd_layout);
3095 if (ch >= ' ' && ch <= '~')
3096 debug((", '%c'", ch));
3098 debug((", $%02x", ch));
3101 debug((", KB0=%02x", keys[0]));
3103 debug((", KB1=%02x", keys[1]));
3105 debug((", KB2=%02x", keys[2]));
3107 if ((keystate[VK_SHIFT] & 0x80) != 0)
3109 if ((keystate[VK_CONTROL] & 0x80) != 0)
3111 if ((HIWORD(lParam) & KF_EXTENDED))
3113 if ((HIWORD(lParam) & KF_UP))
3117 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT);
3118 else if ((HIWORD(lParam) & KF_UP))
3119 oldstate[wParam & 0xFF] ^= 0x80;
3121 oldstate[wParam & 0xFF] ^= 0x81;
3123 for (ch = 0; ch < 256; ch++)
3124 if (oldstate[ch] != keystate[ch])
3125 debug((", M%02x=%02x", ch, keystate[ch]));
3127 memcpy(oldstate, keystate, sizeof(oldstate));
3131 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED)) {
3132 keystate[VK_RMENU] = keystate[VK_MENU];
3136 /* Nastyness with NUMLock - Shift-NUMLock is left alone though */
3137 if ((cfg.funky_type == 3 ||
3138 (cfg.funky_type <= 1 && term->app_keypad_keys &&
3140 && wParam == VK_NUMLOCK && !(keystate[VK_SHIFT] & 0x80)) {
3142 wParam = VK_EXECUTE;
3144 /* UnToggle NUMLock */
3145 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0)
3146 keystate[VK_NUMLOCK] ^= 1;
3149 /* And write back the 'adjusted' state */
3150 SetKeyboardState(keystate);
3153 /* Disable Auto repeat if required */
3154 if (term->repeat_off &&
3155 (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT)
3158 if ((HIWORD(lParam) & KF_ALTDOWN) && (keystate[VK_RMENU] & 0x80) == 0)
3161 key_down = ((HIWORD(lParam) & KF_UP) == 0);
3163 /* Make sure Ctrl-ALT is not the same as AltGr for ToAscii unless told. */
3164 if (left_alt && (keystate[VK_CONTROL] & 0x80)) {
3165 if (cfg.ctrlaltkeys)
3166 keystate[VK_MENU] = 0;
3168 keystate[VK_RMENU] = 0x80;
3173 alt_pressed = (left_alt && key_down);
3175 scan = (HIWORD(lParam) & (KF_UP | KF_EXTENDED | 0xFF));
3176 shift_state = ((keystate[VK_SHIFT] & 0x80) != 0)
3177 + ((keystate[VK_CONTROL] & 0x80) != 0) * 2;
3179 /* Note if AltGr was pressed and if it was used as a compose key */
3180 if (!compose_state) {
3181 compose_key = 0x100;
3182 if (cfg.compose_key) {
3183 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED))
3184 compose_key = wParam;
3186 if (wParam == VK_APPS)
3187 compose_key = wParam;
3190 if (wParam == compose_key) {
3191 if (compose_state == 0
3192 && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0) compose_state =
3194 else if (compose_state == 1 && (HIWORD(lParam) & KF_UP))
3198 } else if (compose_state == 1 && wParam != VK_CONTROL)
3201 if (compose_state > 1 && left_alt)
3204 /* Sanitize the number pad if not using a PC NumPad */
3205 if (left_alt || (term->app_keypad_keys && !cfg.no_applic_k
3206 && cfg.funky_type != 2)
3207 || cfg.funky_type == 3 || cfg.nethack_keypad || compose_state) {
3208 if ((HIWORD(lParam) & KF_EXTENDED) == 0) {
3212 nParam = VK_NUMPAD0;
3215 nParam = VK_NUMPAD1;
3218 nParam = VK_NUMPAD2;
3221 nParam = VK_NUMPAD3;
3224 nParam = VK_NUMPAD4;
3227 nParam = VK_NUMPAD5;
3230 nParam = VK_NUMPAD6;
3233 nParam = VK_NUMPAD7;
3236 nParam = VK_NUMPAD8;
3239 nParam = VK_NUMPAD9;
3242 nParam = VK_DECIMAL;
3246 if (keystate[VK_NUMLOCK] & 1)
3253 /* If a key is pressed and AltGr is not active */
3254 if (key_down && (keystate[VK_RMENU] & 0x80) == 0 && !compose_state) {
3255 /* Okay, prepare for most alts then ... */
3259 /* Lets see if it's a pattern we know all about ... */
3260 if (wParam == VK_PRIOR && shift_state == 1) {
3261 SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0);
3264 if (wParam == VK_NEXT && shift_state == 1) {
3265 SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
3268 if (wParam == VK_INSERT && shift_state == 1) {
3269 term_do_paste(term);
3272 if (left_alt && wParam == VK_F4 && cfg.alt_f4) {
3275 if (left_alt && wParam == VK_SPACE && cfg.alt_space) {
3276 SendMessage(hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0);
3279 if (left_alt && wParam == VK_RETURN && cfg.fullscreenonaltenter &&
3280 (cfg.resize_action != RESIZE_DISABLED)) {
3281 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) != KF_REPEAT)
3285 /* Control-Numlock for app-keypad mode switch */
3286 if (wParam == VK_PAUSE && shift_state == 2) {
3287 term->app_keypad_keys ^= 1;
3291 /* Nethack keypad */
3292 if (cfg.nethack_keypad && !left_alt) {
3295 *p++ = shift_state ? 'B' : 'b';
3298 *p++ = shift_state ? 'J' : 'j';
3301 *p++ = shift_state ? 'N' : 'n';
3304 *p++ = shift_state ? 'H' : 'h';
3307 *p++ = shift_state ? '.' : '.';
3310 *p++ = shift_state ? 'L' : 'l';
3313 *p++ = shift_state ? 'Y' : 'y';
3316 *p++ = shift_state ? 'K' : 'k';
3319 *p++ = shift_state ? 'U' : 'u';
3324 /* Application Keypad */
3328 if (cfg.funky_type == 3 ||
3329 (cfg.funky_type <= 1 &&
3330 term->app_keypad_keys && !cfg.no_applic_k)) switch (wParam) {
3344 if (term->app_keypad_keys && !cfg.no_applic_k)
3381 if (cfg.funky_type == 2) {
3386 } else if (shift_state)
3393 if (cfg.funky_type == 2)
3397 if (cfg.funky_type == 2)
3401 if (cfg.funky_type == 2)
3406 if (HIWORD(lParam) & KF_EXTENDED)
3411 if (term->vt52_mode) {
3412 if (xkey >= 'P' && xkey <= 'S')
3413 p += sprintf((char *) p, "\x1B%c", xkey);
3415 p += sprintf((char *) p, "\x1B?%c", xkey);
3417 p += sprintf((char *) p, "\x1BO%c", xkey);
3422 if (wParam == VK_BACK && shift_state == 0) { /* Backspace */
3423 *p++ = (cfg.bksp_is_delete ? 0x7F : 0x08);
3427 if (wParam == VK_BACK && shift_state == 1) { /* Shift Backspace */
3428 /* We do the opposite of what is configured */
3429 *p++ = (cfg.bksp_is_delete ? 0x08 : 0x7F);
3433 if (wParam == VK_TAB && shift_state == 1) { /* Shift tab */
3439 if (wParam == VK_SPACE && shift_state == 2) { /* Ctrl-Space */
3443 if (wParam == VK_SPACE && shift_state == 3) { /* Ctrl-Shift-Space */
3447 if (wParam == VK_CANCEL && shift_state == 2) { /* Ctrl-Break */
3452 if (wParam == VK_PAUSE) { /* Break/Pause */
3457 /* Control-2 to Control-8 are special */
3458 if (shift_state == 2 && wParam >= '2' && wParam <= '8') {
3459 *p++ = "\000\033\034\035\036\037\177"[wParam - '2'];
3462 if (shift_state == 2 && wParam == 0xBD) {
3466 if (shift_state == 2 && wParam == 0xDF) {
3470 if (shift_state == 0 && wParam == VK_RETURN && term->cr_lf_return) {
3477 * Next, all the keys that do tilde codes. (ESC '[' nn '~',
3478 * for integer decimal nn.)
3480 * We also deal with the weird ones here. Linux VCs replace F1
3481 * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
3482 * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
3488 code = (keystate[VK_SHIFT] & 0x80 ? 23 : 11);
3491 code = (keystate[VK_SHIFT] & 0x80 ? 24 : 12);
3494 code = (keystate[VK_SHIFT] & 0x80 ? 25 : 13);
3497 code = (keystate[VK_SHIFT] & 0x80 ? 26 : 14);
3500 code = (keystate[VK_SHIFT] & 0x80 ? 28 : 15);
3503 code = (keystate[VK_SHIFT] & 0x80 ? 29 : 17);
3506 code = (keystate[VK_SHIFT] & 0x80 ? 31 : 18);
3509 code = (keystate[VK_SHIFT] & 0x80 ? 32 : 19);
3512 code = (keystate[VK_SHIFT] & 0x80 ? 33 : 20);
3515 code = (keystate[VK_SHIFT] & 0x80 ? 34 : 21);
3548 if ((shift_state&2) == 0) switch (wParam) {
3568 /* Reorder edit keys to physical order */
3569 if (cfg.funky_type == 3 && code <= 6)
3570 code = "\0\2\1\4\5\3\6"[code];
3572 if (term->vt52_mode && code > 0 && code <= 6) {
3573 p += sprintf((char *) p, "\x1B%c", " HLMEIG"[code]);
3577 if (cfg.funky_type == 5 && /* SCO function keys */
3578 code >= 11 && code <= 34) {
3579 char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
3582 case VK_F1: index = 0; break;
3583 case VK_F2: index = 1; break;
3584 case VK_F3: index = 2; break;
3585 case VK_F4: index = 3; break;
3586 case VK_F5: index = 4; break;
3587 case VK_F6: index = 5; break;
3588 case VK_F7: index = 6; break;
3589 case VK_F8: index = 7; break;
3590 case VK_F9: index = 8; break;
3591 case VK_F10: index = 9; break;
3592 case VK_F11: index = 10; break;
3593 case VK_F12: index = 11; break;
3595 if (keystate[VK_SHIFT] & 0x80) index += 12;
3596 if (keystate[VK_CONTROL] & 0x80) index += 24;
3597 p += sprintf((char *) p, "\x1B[%c", codes[index]);
3600 if (cfg.funky_type == 5 && /* SCO small keypad */
3601 code >= 1 && code <= 6) {
3602 char codes[] = "HL.FIG";
3606 p += sprintf((char *) p, "\x1B[%c", codes[code-1]);
3610 if ((term->vt52_mode || cfg.funky_type == 4) && code >= 11 && code <= 24) {
3616 if (term->vt52_mode)
3617 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11 - offt);
3620 sprintf((char *) p, "\x1BO%c", code + 'P' - 11 - offt);
3623 if (cfg.funky_type == 1 && code >= 11 && code <= 15) {
3624 p += sprintf((char *) p, "\x1B[[%c", code + 'A' - 11);
3627 if (cfg.funky_type == 2 && code >= 11 && code <= 14) {
3628 if (term->vt52_mode)
3629 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11);
3631 p += sprintf((char *) p, "\x1BO%c", code + 'P' - 11);
3634 if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
3635 p += sprintf((char *) p, code == 1 ? "\x1B[H" : "\x1BOw");
3639 p += sprintf((char *) p, "\x1B[%d~", code);
3644 * Now the remaining keys (arrows and Keypad 5. Keypad 5 for
3645 * some reason seems to send VK_CLEAR to Windows...).
3667 if (term->vt52_mode)
3668 p += sprintf((char *) p, "\x1B%c", xkey);
3670 int app_flg = (term->app_cursor_keys && !cfg.no_applic_c);
3673 * RDB: VT100 & VT102 manuals both state the
3674 * app cursor keys only work if the app keypad
3677 * SGT: That may well be true, but xterm
3678 * disagrees and so does at least one
3679 * application, so I've #if'ed this out and the
3680 * behaviour is back to PuTTY's original: app
3681 * cursor and app keypad are independently
3682 * switchable modes. If anyone complains about
3683 * _this_ I'll have to put in a configurable
3686 if (!term->app_keypad_keys)
3689 /* Useful mapping of Ctrl-arrows */
3690 if (shift_state == 2)
3694 p += sprintf((char *) p, "\x1BO%c", xkey);
3696 p += sprintf((char *) p, "\x1B[%c", xkey);
3703 * Finally, deal with Return ourselves. (Win95 seems to
3704 * foul it up when Alt is pressed, for some reason.)
3706 if (wParam == VK_RETURN) { /* Return */
3712 if (left_alt && wParam >= VK_NUMPAD0 && wParam <= VK_NUMPAD9)
3713 alt_sum = alt_sum * 10 + wParam - VK_NUMPAD0;
3718 /* Okay we've done everything interesting; let windows deal with
3719 * the boring stuff */
3723 /* helg: clear CAPS LOCK state if caps lock switches to cyrillic */
3724 if(cfg.xlat_capslockcyr && keystate[VK_CAPITAL] != 0) {
3726 keystate[VK_CAPITAL] = 0;
3729 r = ToAsciiEx(wParam, scan, keystate, keys, 0, kbd_layout);
3730 #ifdef SHOW_TOASCII_RESULT
3731 if (r == 1 && !key_down) {
3733 if (in_utf(term) || dbcs_screenfont)
3734 debug((", (U+%04x)", alt_sum));
3736 debug((", LCH(%d)", alt_sum));
3738 debug((", ACH(%d)", keys[0]));
3743 for (r1 = 0; r1 < r; r1++) {
3744 debug(("%s%d", r1 ? "," : "", keys[r1]));
3753 * Interrupt an ongoing paste. I'm not sure this is
3754 * sensible, but for the moment it's preferable to
3755 * having to faff about buffering things.
3760 for (i = 0; i < r; i++) {
3761 unsigned char ch = (unsigned char) keys[i];
3763 if (compose_state == 2 && (ch & 0x80) == 0 && ch > ' ') {
3768 if (compose_state == 3 && (ch & 0x80) == 0 && ch > ' ') {
3772 if ((nc = check_compose(compose_char, ch)) == -1) {
3773 MessageBeep(MB_ICONHAND);
3777 term_seen_key_event(term);
3778 luni_send(&keybuf, 1, 1);
3786 if (in_utf(term) || dbcs_screenfont) {
3788 term_seen_key_event(term);
3789 luni_send(&keybuf, 1, 1);
3791 ch = (char) alt_sum;
3793 * We need not bother about stdin
3794 * backlogs here, because in GUI PuTTY
3795 * we can't do anything about it
3796 * anyway; there's no means of asking
3797 * Windows to hold off on KEYDOWN
3798 * messages. We _have_ to buffer
3799 * everything we're sent.
3801 term_seen_key_event(term);
3802 ldisc_send(&ch, 1, 1);
3806 term_seen_key_event(term);
3807 lpage_send(kbd_codepage, &ch, 1, 1);
3809 if(capsOn && ch < 0x80) {
3812 cbuf[1] = xlat_uskbd2cyrllic(ch);
3813 term_seen_key_event(term);
3814 luni_send(cbuf+!left_alt, 1+!!left_alt, 1);
3819 term_seen_key_event(term);
3820 lpage_send(kbd_codepage, cbuf+!left_alt, 1+!!left_alt, 1);
3826 /* This is so the ALT-Numpad and dead keys work correctly. */
3831 /* If we're definitly not building up an ALT-54321 then clear it */
3834 /* If we will be using alt_sum fix the 256s */
3835 else if (keys[0] && (in_utf(term) || dbcs_screenfont))
3840 * ALT alone may or may not want to bring up the System menu.
3841 * If it's not meant to, we return 0 on presses or releases of
3842 * ALT, to show that we've swallowed the keystroke. Otherwise
3843 * we return -1, which means Windows will give the keystroke
3844 * its default handling (i.e. bring up the System menu).
3846 if (wParam == VK_MENU && !cfg.alt_only)
3852 void request_paste(void)
3855 * In Windows, pasting is synchronous: we can read the
3856 * clipboard with no difficulty, so request_paste() can just go
3859 term_do_paste(term);
3862 void set_title(char *title)
3865 window_name = smalloc(1 + strlen(title));
3866 strcpy(window_name, title);
3867 if (cfg.win_name_always || !IsIconic(hwnd))
3868 SetWindowText(hwnd, title);
3871 void set_icon(char *title)
3874 icon_name = smalloc(1 + strlen(title));
3875 strcpy(icon_name, title);
3876 if (!cfg.win_name_always && IsIconic(hwnd))
3877 SetWindowText(hwnd, title);
3880 void set_sbar(int total, int start, int page)
3884 if (is_full_screen() ? !cfg.scrollbar_in_fullscreen : !cfg.scrollbar)
3887 si.cbSize = sizeof(si);
3888 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
3890 si.nMax = total - 1;
3894 SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
3897 Context get_ctx(void)
3903 SelectPalette(hdc, pal, FALSE);
3909 void free_ctx(Context ctx)
3911 SelectPalette(ctx, GetStockObject(DEFAULT_PALETTE), FALSE);
3912 ReleaseDC(hwnd, ctx);
3915 static void real_palette_set(int n, int r, int g, int b)
3918 logpal->palPalEntry[n].peRed = r;
3919 logpal->palPalEntry[n].peGreen = g;
3920 logpal->palPalEntry[n].peBlue = b;
3921 logpal->palPalEntry[n].peFlags = PC_NOCOLLAPSE;
3922 colours[n] = PALETTERGB(r, g, b);
3923 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3925 colours[n] = RGB(r, g, b);
3928 void palette_set(int n, int r, int g, int b)
3930 static const int first[21] = {
3931 0, 2, 4, 6, 8, 10, 12, 14,
3932 1, 3, 5, 7, 9, 11, 13, 15,
3935 real_palette_set(first[n], r, g, b);
3937 real_palette_set(first[n] + 1, r, g, b);
3939 HDC hdc = get_ctx();
3940 UnrealizeObject(pal);
3941 RealizePalette(hdc);
3946 void palette_reset(void)
3950 for (i = 0; i < NCOLOURS; i++) {
3952 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
3953 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
3954 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
3955 logpal->palPalEntry[i].peFlags = 0;
3956 colours[i] = PALETTERGB(defpal[i].rgbtRed,
3957 defpal[i].rgbtGreen,
3958 defpal[i].rgbtBlue);
3960 colours[i] = RGB(defpal[i].rgbtRed,
3961 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
3966 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3968 RealizePalette(hdc);
3973 void write_aclip(char *data, int len, int must_deselect)
3978 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len + 1);
3981 lock = GlobalLock(clipdata);
3984 memcpy(lock, data, len);
3985 ((unsigned char *) lock)[len] = 0;
3986 GlobalUnlock(clipdata);
3989 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
3991 if (OpenClipboard(hwnd)) {
3993 SetClipboardData(CF_TEXT, clipdata);
3996 GlobalFree(clipdata);
3999 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
4003 * Note: unlike write_aclip() this will not append a nul.
4005 void write_clip(wchar_t * data, int len, int must_deselect)
4007 HGLOBAL clipdata, clipdata2, clipdata3;
4009 void *lock, *lock2, *lock3;
4011 len2 = WideCharToMultiByte(CP_ACP, 0, data, len, 0, 0, NULL, NULL);
4013 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE,
4014 len * sizeof(wchar_t));
4015 clipdata2 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len2);
4017 if (!clipdata || !clipdata2) {
4019 GlobalFree(clipdata);
4021 GlobalFree(clipdata2);
4024 if (!(lock = GlobalLock(clipdata)))
4026 if (!(lock2 = GlobalLock(clipdata2)))
4029 memcpy(lock, data, len * sizeof(wchar_t));
4030 WideCharToMultiByte(CP_ACP, 0, data, len, lock2, len2, NULL, NULL);
4032 if (cfg.rtf_paste) {
4033 wchar_t unitab[256];
4035 unsigned char *tdata = (unsigned char *)lock2;
4036 wchar_t *udata = (wchar_t *)lock;
4037 int rtflen = 0, uindex = 0, tindex = 0;
4039 int multilen, blen, alen, totallen, i;
4040 char before[16], after[4];
4042 get_unitab(CP_ACP, unitab, 0);
4044 rtfsize = 100 + strlen(cfg.font);
4045 rtf = smalloc(rtfsize);
4046 sprintf(rtf, "{\\rtf1\\ansi%d{\\fonttbl\\f0\\fmodern %s;}\\f0",
4047 GetACP(), cfg.font);
4048 rtflen = strlen(rtf);
4051 * We want to construct a piece of RTF that specifies the
4052 * same Unicode text. To do this we will read back in
4053 * parallel from the Unicode data in `udata' and the
4054 * non-Unicode data in `tdata'. For each character in
4055 * `tdata' which becomes the right thing in `udata' when
4056 * looked up in `unitab', we just copy straight over from
4057 * tdata. For each one that doesn't, we must WCToMB it
4058 * individually and produce a \u escape sequence.
4060 * It would probably be more robust to just bite the bullet
4061 * and WCToMB each individual Unicode character one by one,
4062 * then MBToWC each one back to see if it was an accurate
4063 * translation; but that strikes me as a horrifying number
4064 * of Windows API calls so I want to see if this faster way
4065 * will work. If it screws up badly we can always revert to
4066 * the simple and slow way.
4068 while (tindex < len2 && uindex < len &&
4069 tdata[tindex] && udata[uindex]) {
4070 if (tindex + 1 < len2 &&
4071 tdata[tindex] == '\r' &&
4072 tdata[tindex+1] == '\n') {
4076 if (unitab[tdata[tindex]] == udata[uindex]) {
4082 multilen = WideCharToMultiByte(CP_ACP, 0, unitab+uindex, 1,
4083 NULL, 0, NULL, NULL);
4084 if (multilen != 1) {
4085 blen = sprintf(before, "{\\uc%d\\u%d", multilen,
4087 alen = 1; strcpy(after, "}");
4089 blen = sprintf(before, "\\u%d", udata[uindex]);
4090 alen = 0; after[0] = '\0';
4093 assert(tindex + multilen <= len2);
4094 totallen = blen + alen;
4095 for (i = 0; i < multilen; i++) {
4096 if (tdata[tindex+i] == '\\' ||
4097 tdata[tindex+i] == '{' ||
4098 tdata[tindex+i] == '}')
4100 else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A)
4101 totallen += 6; /* \par\r\n */
4102 else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20)
4108 if (rtfsize < rtflen + totallen + 3) {
4109 rtfsize = rtflen + totallen + 512;
4110 rtf = srealloc(rtf, rtfsize);
4113 strcpy(rtf + rtflen, before); rtflen += blen;
4114 for (i = 0; i < multilen; i++) {
4115 if (tdata[tindex+i] == '\\' ||
4116 tdata[tindex+i] == '{' ||
4117 tdata[tindex+i] == '}') {
4118 rtf[rtflen++] = '\\';
4119 rtf[rtflen++] = tdata[tindex+i];
4120 } else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A) {
4121 rtflen += sprintf(rtf+rtflen, "\\par\r\n");
4122 } else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20) {
4123 rtflen += sprintf(rtf+rtflen, "\\'%02x", tdata[tindex+i]);
4125 rtf[rtflen++] = tdata[tindex+i];
4128 strcpy(rtf + rtflen, after); rtflen += alen;
4134 strcpy(rtf + rtflen, "}");
4137 clipdata3 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, rtflen);
4138 if (clipdata3 && (lock3 = GlobalLock(clipdata3)) != NULL) {
4140 GlobalUnlock(clipdata3);
4146 GlobalUnlock(clipdata);
4147 GlobalUnlock(clipdata2);
4150 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
4152 if (OpenClipboard(hwnd)) {
4154 SetClipboardData(CF_UNICODETEXT, clipdata);
4155 SetClipboardData(CF_TEXT, clipdata2);
4157 SetClipboardData(RegisterClipboardFormat(CF_RTF), clipdata3);
4160 GlobalFree(clipdata);
4161 GlobalFree(clipdata2);
4165 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
4168 void get_clip(wchar_t ** p, int *len)
4170 static HGLOBAL clipdata = NULL;
4171 static wchar_t *converted = 0;
4180 GlobalUnlock(clipdata);
4183 } else if (OpenClipboard(NULL)) {
4184 if ((clipdata = GetClipboardData(CF_UNICODETEXT))) {
4186 *p = GlobalLock(clipdata);
4188 for (p2 = *p; *p2; p2++);
4192 } else if ( (clipdata = GetClipboardData(CF_TEXT)) ) {
4196 s = GlobalLock(clipdata);
4197 i = MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, 0, 0);
4198 *p = converted = smalloc(i * sizeof(wchar_t));
4199 MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, converted, i);
4212 * Move `lines' lines from position `from' to position `to' in the
4215 void optimised_move(int to, int from, int lines)
4220 min = (to < from ? to : from);
4221 max = to + from - min;
4223 r.left = offset_width;
4224 r.right = offset_width + term->cols * font_width;
4225 r.top = offset_height + min * font_height;
4226 r.bottom = offset_height + (max + lines) * font_height;
4227 ScrollWindow(hwnd, 0, (to - from) * font_height, &r, &r);
4232 * Print a message box and perform a fatal exit.
4234 void fatalbox(char *fmt, ...)
4240 vsprintf(stuff, fmt, ap);
4242 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
4247 * Print a modal (Really Bad) message box and perform a fatal exit.
4249 void modalfatalbox(char *fmt, ...)
4255 vsprintf(stuff, fmt, ap);
4257 MessageBox(hwnd, stuff, "PuTTY Fatal Error",
4258 MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
4263 * Manage window caption / taskbar flashing, if enabled.
4264 * 0 = stop, 1 = maintain, 2 = start
4266 static void flash_window(int mode)
4268 static long last_flash = 0;
4269 static int flashing = 0;
4270 if ((mode == 0) || (cfg.beep_ind == B_IND_DISABLED)) {
4273 FlashWindow(hwnd, FALSE);
4277 } else if (mode == 2) {
4280 last_flash = GetTickCount();
4282 FlashWindow(hwnd, TRUE);
4285 } else if ((mode == 1) && (cfg.beep_ind == B_IND_FLASH)) {
4288 long now = GetTickCount();
4289 long fdiff = now - last_flash;
4290 if (fdiff < 0 || fdiff > 450) {
4292 FlashWindow(hwnd, TRUE); /* toggle */
4303 if (mode == BELL_DEFAULT) {
4305 * For MessageBeep style bells, we want to be careful of
4306 * timing, because they don't have the nice property of
4307 * PlaySound bells that each one cancels the previous
4308 * active one. So we limit the rate to one per 50ms or so.
4310 static long lastbeep = 0;
4313 beepdiff = GetTickCount() - lastbeep;
4314 if (beepdiff >= 0 && beepdiff < 50)
4318 * The above MessageBeep call takes time, so we record the
4319 * time _after_ it finishes rather than before it starts.
4321 lastbeep = GetTickCount();
4322 } else if (mode == BELL_WAVEFILE) {
4323 if (!PlaySound(cfg.bell_wavefile, NULL, SND_ASYNC | SND_FILENAME)) {
4324 char buf[sizeof(cfg.bell_wavefile) + 80];
4325 sprintf(buf, "Unable to play sound file\n%s\n"
4326 "Using default sound instead", cfg.bell_wavefile);
4327 MessageBox(hwnd, buf, "PuTTY Sound Error",
4328 MB_OK | MB_ICONEXCLAMATION);
4329 cfg.beep = BELL_DEFAULT;
4332 /* Otherwise, either visual bell or disabled; do nothing here */
4333 if (!term->has_focus) {
4334 flash_window(2); /* start */
4339 * Minimise or restore the window in response to a server-side
4342 void set_iconic(int iconic)
4344 if (IsIconic(hwnd)) {
4346 ShowWindow(hwnd, SW_RESTORE);
4349 ShowWindow(hwnd, SW_MINIMIZE);
4354 * Move the window in response to a server-side request.
4356 void move_window(int x, int y)
4358 if (cfg.resize_action == RESIZE_DISABLED ||
4359 cfg.resize_action == RESIZE_FONT ||
4363 SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
4367 * Move the window to the top or bottom of the z-order in response
4368 * to a server-side request.
4370 void set_zorder(int top)
4372 if (cfg.alwaysontop)
4373 return; /* ignore */
4374 SetWindowPos(hwnd, top ? HWND_TOP : HWND_BOTTOM, 0, 0, 0, 0,
4375 SWP_NOMOVE | SWP_NOSIZE);
4379 * Refresh the window in response to a server-side request.
4381 void refresh_window(void)
4383 InvalidateRect(hwnd, NULL, TRUE);
4387 * Maximise or restore the window in response to a server-side
4390 void set_zoomed(int zoomed)
4392 if (IsZoomed(hwnd)) {
4394 ShowWindow(hwnd, SW_RESTORE);
4397 ShowWindow(hwnd, SW_MAXIMIZE);
4402 * Report whether the window is iconic, for terminal reports.
4406 return IsIconic(hwnd);
4410 * Report the window's position, for terminal reports.
4412 void get_window_pos(int *x, int *y)
4415 GetWindowRect(hwnd, &r);
4421 * Report the window's pixel size, for terminal reports.
4423 void get_window_pixels(int *x, int *y)
4426 GetWindowRect(hwnd, &r);
4427 *x = r.right - r.left;
4428 *y = r.bottom - r.top;
4432 * Return the window or icon title.
4434 char *get_window_title(int icon)
4436 return icon ? icon_name : window_name;
4440 * See if we're in full-screen mode.
4442 int is_full_screen()
4444 if (!IsZoomed(hwnd))
4446 if (GetWindowLong(hwnd, GWL_STYLE) & WS_CAPTION)
4451 /* Get the rect/size of a full screen window using the nearest available
4452 * monitor in multimon systems; default to something sensible if only
4453 * one monitor is present. */
4454 static int get_fullscreen_rect(RECT * ss)
4456 #ifdef MONITOR_DEFAULTTONEAREST
4459 mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
4460 mi.cbSize = sizeof(mi);
4461 GetMonitorInfo(mon, &mi);
4463 /* structure copy */
4467 /* could also use code like this:
4468 ss->left = ss->top = 0;
4469 ss->right = GetSystemMetrics(SM_CXSCREEN);
4470 ss->bottom = GetSystemMetrics(SM_CYSCREEN);
4472 return GetClientRect(GetDesktopWindow(), ss);
4478 * Go full-screen. This should only be called when we are already
4481 void make_full_screen()
4486 assert(IsZoomed(hwnd));
4488 if (is_full_screen())
4491 /* Remove the window furniture. */
4492 style = GetWindowLong(hwnd, GWL_STYLE);
4493 style &= ~(WS_CAPTION | WS_BORDER | WS_THICKFRAME);
4494 if (cfg.scrollbar_in_fullscreen)
4495 style |= WS_VSCROLL;
4497 style &= ~WS_VSCROLL;
4498 SetWindowLong(hwnd, GWL_STYLE, style);
4500 /* Resize ourselves to exactly cover the nearest monitor. */
4501 get_fullscreen_rect(&ss);
4502 SetWindowPos(hwnd, HWND_TOP, ss.left, ss.top,
4507 /* Tick the menu item in the System menu. */
4508 CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN,
4513 * Clear the full-screen attributes.
4515 void clear_full_screen()
4517 DWORD oldstyle, style;
4519 /* Reinstate the window furniture. */
4520 style = oldstyle = GetWindowLong(hwnd, GWL_STYLE);
4521 style |= WS_CAPTION | WS_BORDER;
4522 if (cfg.resize_action == RESIZE_DISABLED)
4523 style &= ~WS_THICKFRAME;
4525 style |= WS_THICKFRAME;
4527 style |= WS_VSCROLL;
4529 style &= ~WS_VSCROLL;
4530 if (style != oldstyle) {
4531 SetWindowLong(hwnd, GWL_STYLE, style);
4532 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
4533 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
4537 /* Untick the menu item in the System menu. */
4538 CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN,
4543 * Toggle full-screen mode.
4545 void flip_full_screen()
4547 if (is_full_screen()) {
4548 ShowWindow(hwnd, SW_RESTORE);
4549 } else if (IsZoomed(hwnd)) {
4552 SendMessage(hwnd, WM_FULLSCR_ON_MAX, 0, 0);
4553 ShowWindow(hwnd, SW_MAXIMIZE);