16 #define COMPILE_MULTIMON_STUBS
27 #define PUTTY_DO_GLOBALS /* actually _define_ globals */
34 #define IDM_SHOWLOG 0x0010
35 #define IDM_NEWSESS 0x0020
36 #define IDM_DUPSESS 0x0030
37 #define IDM_RECONF 0x0040
38 #define IDM_CLRSB 0x0050
39 #define IDM_RESET 0x0060
40 #define IDM_TEL_AYT 0x0070
41 #define IDM_TEL_BRK 0x0080
42 #define IDM_TEL_SYNCH 0x0090
43 #define IDM_TEL_EC 0x00a0
44 #define IDM_TEL_EL 0x00b0
45 #define IDM_TEL_GA 0x00c0
46 #define IDM_TEL_NOP 0x00d0
47 #define IDM_TEL_ABORT 0x00e0
48 #define IDM_TEL_AO 0x00f0
49 #define IDM_TEL_IP 0x0100
50 #define IDM_TEL_SUSP 0x0110
51 #define IDM_TEL_EOR 0x0120
52 #define IDM_TEL_EOF 0x0130
53 #define IDM_HELP 0x0140
54 #define IDM_ABOUT 0x0150
55 #define IDM_SAVEDSESS 0x0160
56 #define IDM_COPYALL 0x0170
57 #define IDM_FULLSCREEN 0x0180
59 #define IDM_SESSLGP 0x0250 /* log type printable */
60 #define IDM_SESSLGA 0x0260 /* log type all chars */
61 #define IDM_SESSLGE 0x0270 /* log end */
62 #define IDM_SAVED_MIN 0x1000
63 #define IDM_SAVED_MAX 0x2000
65 #define WM_IGNORE_CLIP (WM_XUSER + 2)
66 #define WM_FULLSCR_ON_MAX (WM_XUSER + 3)
68 /* Needed for Chinese support and apparently not always defined. */
70 #define VK_PROCESSKEY 0xE5
73 /* Mouse wheel support. */
75 #define WM_MOUSEWHEEL 0x020A /* not defined in earlier SDKs */
78 #define WHEEL_DELTA 120
81 static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
82 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
83 unsigned char *output);
84 static void cfgtopalette(void);
85 static void init_palette(void);
86 static void init_fonts(int, int);
87 static void another_font(int);
88 static void deinit_fonts(void);
89 static void set_input_locale(HKL);
90 static int do_mouse_wheel_msg(UINT message, WPARAM wParam, LPARAM lParam);
92 static int is_full_screen(void);
93 static void make_full_screen(void);
94 static void clear_full_screen(void);
95 static void flip_full_screen(void);
97 /* Window layout information */
98 static void reset_window(int);
99 static int extra_width, extra_height;
100 static int font_width, font_height, font_dualwidth;
101 static int offset_width, offset_height;
102 static int was_zoomed = 0;
103 static int prev_rows, prev_cols;
105 static int pending_netevent = 0;
106 static WPARAM pend_netevent_wParam = 0;
107 static LPARAM pend_netevent_lParam = 0;
108 static void enact_pending_netevent(void);
109 static void flash_window(int mode);
110 static void sys_cursor_update(void);
111 static int is_shift_pressed(void);
112 static int get_fullscreen_rect(RECT * ss);
114 static time_t last_movement = 0;
116 static int caret_x = -1, caret_y = -1;
120 #define FONT_NORMAL 0
122 #define FONT_UNDERLINE 2
123 #define FONT_BOLDUND 3
124 #define FONT_WIDE 0x04
125 #define FONT_HIGH 0x08
126 #define FONT_NARROW 0x10
128 #define FONT_OEM 0x20
129 #define FONT_OEMBOLD 0x21
130 #define FONT_OEMUND 0x22
131 #define FONT_OEMBOLDUND 0x23
133 #define FONT_MAXNO 0x2F
135 static HFONT fonts[FONT_MAXNO];
136 static LOGFONT lfont;
137 static int fontflag[FONT_MAXNO];
139 BOLD_COLOURS, BOLD_SHADOW, BOLD_FONT
147 static COLORREF colours[NCOLOURS];
149 static LPLOGPALETTE logpal;
150 static RGBTRIPLE defpal[NCOLOURS];
154 static HBITMAP caretbm;
156 static int dbltime, lasttime, lastact;
157 static Mouse_Button lastbtn;
159 /* this allows xterm-style mouse handling. */
160 static int send_raw_mouse = 0;
161 static int wheel_accumulator = 0;
163 static char *window_name, *icon_name;
165 static int compose_state = 0;
167 static int wsa_started = 0;
169 static OSVERSIONINFO osVersion;
171 static UINT wm_mousewheel = WM_MOUSEWHEEL;
173 /* Dummy routine, only required in plink. */
174 void ldisc_update(void *frontend, int echo, int edit)
178 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
180 static char appname[] = "PuTTY";
185 int guess_width, guess_height;
188 flags = FLAG_VERBOSE | FLAG_INTERACTIVE;
190 winsock_ver = MAKEWORD(1, 1);
191 if (WSAStartup(winsock_ver, &wsadata)) {
192 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
193 MB_OK | MB_ICONEXCLAMATION);
196 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
197 MessageBox(NULL, "WinSock version is incompatible with 1.1",
198 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
203 /* WISHLIST: maybe allow config tweaking even if winsock not present? */
206 InitCommonControls();
208 /* Ensure a Maximize setting in Explorer doesn't maximise the
213 ZeroMemory(&osVersion, sizeof(osVersion));
214 osVersion.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
215 if (!GetVersionEx ( (OSVERSIONINFO *) &osVersion)) {
216 MessageBox(NULL, "Windows refuses to report a version",
217 "PuTTY Fatal Error", MB_OK | MB_ICONEXCLAMATION);
223 * If we're running a version of Windows that doesn't support
224 * WM_MOUSEWHEEL, find out what message number we should be
227 if (osVersion.dwMajorVersion < 4 ||
228 (osVersion.dwMajorVersion == 4 &&
229 osVersion.dwPlatformId != VER_PLATFORM_WIN32_NT))
230 wm_mousewheel = RegisterWindowMessage("MSWHEEL_ROLLMSG");
233 * See if we can find our Help file.
236 char b[2048], *p, *q, *r;
238 GetModuleFileName(NULL, b, sizeof(b) - 1);
240 p = strrchr(b, '\\');
241 if (p && p >= r) r = p+1;
243 if (q && q >= r) r = q+1;
244 strcpy(r, "putty.hlp");
245 if ( (fp = fopen(b, "r")) != NULL) {
246 help_path = dupstr(b);
250 strcpy(r, "putty.cnt");
251 if ( (fp = fopen(b, "r")) != NULL) {
252 help_has_contents = TRUE;
255 help_has_contents = FALSE;
259 * Process the command line.
265 default_protocol = DEFAULT_PROTOCOL;
266 default_port = DEFAULT_PORT;
267 cfg.logtype = LGTYP_NONE;
269 do_defaults(NULL, &cfg);
274 * Process a couple of command-line options which are more
275 * easily dealt with before the line is broken up into
276 * words. These are the soon-to-be-defunct @sessionname and
277 * the internal-use-only &sharedmemoryhandle, neither of
278 * which are combined with anything else.
280 while (*p && isspace(*p))
284 while (i > 1 && isspace(p[i - 1]))
287 do_defaults(p + 1, &cfg);
288 if (!*cfg.host && !do_config()) {
292 } else if (*p == '&') {
294 * An initial & means we've been given a command line
295 * containing the hex value of a HANDLE for a file
296 * mapping object, which we must then extract as a
301 if (sscanf(p + 1, "%p", &filemap) == 1 &&
302 (cp = MapViewOfFile(filemap, FILE_MAP_READ,
303 0, 0, sizeof(Config))) != NULL) {
306 CloseHandle(filemap);
307 } else if (!do_config()) {
313 * Otherwise, break up the command line and deal with
319 split_into_argv(cmdline, &argc, &argv, NULL);
321 for (i = 0; i < argc; i++) {
325 ret = cmdline_process_param(p, i+1<argc?argv[i+1]:NULL, 1);
327 cmdline_error("option \"%s\" requires an argument", p);
328 } else if (ret == 2) {
329 i++; /* skip next argument */
330 } else if (ret == 1) {
331 continue; /* nothing further needs doing */
332 } else if (!strcmp(p, "-cleanup")) {
334 * `putty -cleanup'. Remove all registry
335 * entries associated with PuTTY, and also find
336 * and delete the random seed file.
339 "This procedure will remove ALL Registry\n"
340 "entries associated with PuTTY, and will\n"
341 "also remove the PuTTY random seed file.\n"
343 "THIS PROCESS WILL DESTROY YOUR SAVED\n"
344 "SESSIONS. Are you really sure you want\n"
347 MB_YESNO | MB_ICONWARNING) == IDYES) {
351 } else if (*p != '-') {
355 * If we already have a host name, treat
356 * this argument as a port number. NB we
357 * have to treat this as a saved -P
358 * argument, so that it will be deferred
359 * until it's a good moment to run it.
361 int ret = cmdline_process_param("-P", p, 1);
363 } else if (!strncmp(q, "telnet:", 7)) {
365 * If the hostname starts with "telnet:",
366 * set the protocol to Telnet and process
367 * the string as a Telnet URL.
372 if (q[0] == '/' && q[1] == '/')
374 cfg.protocol = PROT_TELNET;
376 while (*p && *p != ':' && *p != '/')
385 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
386 cfg.host[sizeof(cfg.host) - 1] = '\0';
390 * Otherwise, treat this argument as a host
393 while (*p && !isspace(*p))
397 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
398 cfg.host[sizeof(cfg.host) - 1] = '\0';
407 if (!*cfg.host && !do_config()) {
413 * Trim leading whitespace off the hostname if it's there.
416 int space = strspn(cfg.host, " \t");
417 memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
420 /* See if host is of the form user@host */
421 if (cfg.host[0] != '\0') {
422 char *atsign = strchr(cfg.host, '@');
423 /* Make sure we're not overflowing the user field */
425 if (atsign - cfg.host < sizeof cfg.username) {
426 strncpy(cfg.username, cfg.host, atsign - cfg.host);
427 cfg.username[atsign - cfg.host] = '\0';
429 memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
434 * Trim a colon suffix off the hostname if it's there.
436 cfg.host[strcspn(cfg.host, ":")] = '\0';
439 * Remove any remaining whitespace from the hostname.
443 while (cfg.host[p2] != '\0') {
444 if (cfg.host[p2] != ' ' && cfg.host[p2] != '\t') {
445 cfg.host[p1] = cfg.host[p2];
455 * Select protocol. This is farmed out into a table in a
456 * separate file to enable an ssh-free variant.
461 for (i = 0; backends[i].backend != NULL; i++)
462 if (backends[i].protocol == cfg.protocol) {
463 back = backends[i].backend;
467 MessageBox(NULL, "Unsupported protocol number found",
468 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
474 /* Check for invalid Port number (i.e. zero) */
476 MessageBox(NULL, "Invalid Port Number",
477 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
484 wndclass.lpfnWndProc = WndProc;
485 wndclass.cbClsExtra = 0;
486 wndclass.cbWndExtra = 0;
487 wndclass.hInstance = inst;
488 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
489 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
490 wndclass.hbrBackground = NULL;
491 wndclass.lpszMenuName = NULL;
492 wndclass.lpszClassName = appname;
494 RegisterClass(&wndclass);
504 * Guess some defaults for the window size. This all gets
505 * updated later, so we don't really care too much. However, we
506 * do want the font width/height guesses to correspond to a
507 * large font rather than a small one...
514 term_size(term, cfg.height, cfg.width, cfg.savelines);
515 guess_width = extra_width + font_width * term->cols;
516 guess_height = extra_height + font_height * term->rows;
519 get_fullscreen_rect(&r);
520 if (guess_width > r.right - r.left)
521 guess_width = r.right - r.left;
522 if (guess_height > r.bottom - r.top)
523 guess_height = r.bottom - r.top;
527 int winmode = WS_OVERLAPPEDWINDOW | WS_VSCROLL;
530 winmode &= ~(WS_VSCROLL);
531 if (cfg.resize_action == RESIZE_DISABLED)
532 winmode &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
534 exwinmode |= WS_EX_TOPMOST;
536 exwinmode |= WS_EX_CLIENTEDGE;
537 hwnd = CreateWindowEx(exwinmode, appname, appname,
538 winmode, CW_USEDEFAULT, CW_USEDEFAULT,
539 guess_width, guess_height,
540 NULL, NULL, inst, NULL);
544 * Initialise the fonts, simultaneously correcting the guesses
545 * for font_{width,height}.
550 * Correct the guesses for extra_{width,height}.
554 GetWindowRect(hwnd, &wr);
555 GetClientRect(hwnd, &cr);
556 offset_width = offset_height = cfg.window_border;
557 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
558 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
562 * Resize the window, now we know what size we _really_ want it
565 guess_width = extra_width + font_width * term->cols;
566 guess_height = extra_height + font_height * term->rows;
567 SetWindowPos(hwnd, NULL, 0, 0, guess_width, guess_height,
568 SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
571 * Set up a caret bitmap, with no content.
575 int size = (font_width + 15) / 16 * 2 * font_height;
576 bits = smalloc(size);
577 memset(bits, 0, size);
578 caretbm = CreateBitmap(font_width, font_height, 1, 1, bits);
581 CreateCaret(hwnd, caretbm, font_width, font_height);
584 * Initialise the scroll bar.
589 si.cbSize = sizeof(si);
590 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
592 si.nMax = term->rows - 1;
593 si.nPage = term->rows;
595 SetScrollInfo(hwnd, SB_VERT, &si, FALSE);
599 * Start up the telnet connection.
603 char msg[1024], *title;
606 error = back->init((void *)term, &backhandle,
607 cfg.host, cfg.port, &realhost, cfg.tcp_nodelay);
609 sprintf(msg, "Unable to open connection to\n"
610 "%.800s\n" "%s", cfg.host, error);
611 MessageBox(NULL, msg, "PuTTY Error", MB_ICONERROR | MB_OK);
614 window_name = icon_name = NULL;
616 title = cfg.wintitle;
618 sprintf(msg, "%s - PuTTY", realhost);
627 * Connect the terminal to the backend for resize purposes.
629 term_provide_resize_fn(term, back->size, backhandle);
632 * Set up a line discipline.
634 ldisc = ldisc_create(term, back, backhandle, NULL);
636 session_closed = FALSE;
639 * Prepare the mouse handler.
641 lastact = MA_NOTHING;
642 lastbtn = MBT_NOTHING;
643 dbltime = GetDoubleClickTime();
646 * Set up the session-control options on the system menu.
649 HMENU m = GetSystemMenu(hwnd, FALSE);
653 AppendMenu(m, MF_SEPARATOR, 0, 0);
654 if (cfg.protocol == PROT_TELNET) {
656 AppendMenu(p, MF_ENABLED, IDM_TEL_AYT, "Are You There");
657 AppendMenu(p, MF_ENABLED, IDM_TEL_BRK, "Break");
658 AppendMenu(p, MF_ENABLED, IDM_TEL_SYNCH, "Synch");
659 AppendMenu(p, MF_SEPARATOR, 0, 0);
660 AppendMenu(p, MF_ENABLED, IDM_TEL_EC, "Erase Character");
661 AppendMenu(p, MF_ENABLED, IDM_TEL_EL, "Erase Line");
662 AppendMenu(p, MF_ENABLED, IDM_TEL_GA, "Go Ahead");
663 AppendMenu(p, MF_ENABLED, IDM_TEL_NOP, "No Operation");
664 AppendMenu(p, MF_SEPARATOR, 0, 0);
665 AppendMenu(p, MF_ENABLED, IDM_TEL_ABORT, "Abort Process");
666 AppendMenu(p, MF_ENABLED, IDM_TEL_AO, "Abort Output");
667 AppendMenu(p, MF_ENABLED, IDM_TEL_IP, "Interrupt Process");
668 AppendMenu(p, MF_ENABLED, IDM_TEL_SUSP, "Suspend Process");
669 AppendMenu(p, MF_SEPARATOR, 0, 0);
670 AppendMenu(p, MF_ENABLED, IDM_TEL_EOR, "End Of Record");
671 AppendMenu(p, MF_ENABLED, IDM_TEL_EOF, "End Of File");
672 AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) p,
674 AppendMenu(m, MF_SEPARATOR, 0, 0);
676 AppendMenu(m, MF_ENABLED, IDM_SHOWLOG, "&Event Log");
677 AppendMenu(m, MF_SEPARATOR, 0, 0);
678 AppendMenu(m, MF_ENABLED, IDM_NEWSESS, "Ne&w Session...");
679 AppendMenu(m, MF_ENABLED, IDM_DUPSESS, "&Duplicate Session");
682 for (i = 1; i < ((nsessions < 256) ? nsessions : 256); i++)
683 AppendMenu(s, MF_ENABLED, IDM_SAVED_MIN + (16 * i),
685 AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) s, "Sa&ved Sessions");
686 AppendMenu(m, MF_ENABLED, IDM_RECONF, "Chan&ge Settings...");
687 AppendMenu(m, MF_SEPARATOR, 0, 0);
688 AppendMenu(m, MF_ENABLED, IDM_COPYALL, "C&opy All to Clipboard");
689 AppendMenu(m, MF_ENABLED, IDM_CLRSB, "C&lear Scrollback");
690 AppendMenu(m, MF_ENABLED, IDM_RESET, "Rese&t Terminal");
691 AppendMenu(m, MF_SEPARATOR, 0, 0);
692 AppendMenu(m, (cfg.resize_action == RESIZE_DISABLED) ?
693 MF_GRAYED : MF_ENABLED, IDM_FULLSCREEN, "&Full Screen");
694 AppendMenu(m, MF_SEPARATOR, 0, 0);
696 AppendMenu(m, MF_ENABLED, IDM_HELP, "&Help");
697 AppendMenu(m, MF_ENABLED, IDM_ABOUT, "&About PuTTY");
701 * Set up the initial input locale.
703 set_input_locale(GetKeyboardLayout(0));
706 * Open the initial log file if there is one.
711 * Finally show the window!
713 ShowWindow(hwnd, show);
714 SetForegroundWindow(hwnd);
717 * Set the palette up.
723 term->has_focus = (GetForegroundWindow() == hwnd);
726 if (GetMessage(&msg, NULL, 0, 0) == 1) {
727 int timer_id = 0, long_timer = 0;
729 while (msg.message != WM_QUIT) {
730 /* Sometimes DispatchMessage calls routines that use their own
731 * GetMessage loop, setup this timer so we get some control back.
733 * Also call term_update() from the timer so that if the host
734 * is sending data flat out we still do redraws.
736 if (timer_id && long_timer) {
737 KillTimer(hwnd, timer_id);
738 long_timer = timer_id = 0;
741 timer_id = SetTimer(hwnd, 1, 20, NULL);
742 if (!(IsWindow(logbox) && IsDialogMessage(logbox, &msg)))
743 DispatchMessage(&msg);
745 /* Make sure we blink everything that needs it. */
748 /* Send the paste buffer if there's anything to send */
751 /* If there's nothing new in the queue then we can do everything
752 * we've delayed, reading the socket, writing, and repainting
755 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
758 if (pending_netevent) {
759 enact_pending_netevent();
761 /* Force the cursor blink on */
764 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
768 /* Okay there is now nothing to do so we make sure the screen is
769 * completely up to date then tell windows to call us in a little
773 KillTimer(hwnd, timer_id);
777 if (GetCapture() != hwnd ||
779 !(cfg.mouse_override && is_shift_pressed())))
784 flash_window(1); /* maintain */
786 /* The messages seem unreliable; especially if we're being tricky */
787 term->has_focus = (GetForegroundWindow() == hwnd);
790 /* Hmm, term_update didn't want to do an update too soon ... */
791 timer_id = SetTimer(hwnd, 1, 50, NULL);
792 else if (!term->has_focus)
793 timer_id = SetTimer(hwnd, 1, 500, NULL);
795 timer_id = SetTimer(hwnd, 1, 100, NULL);
798 /* There's no point rescanning everything in the message queue
799 * so we do an apparently unnecessary wait here
802 if (GetMessage(&msg, NULL, 0, 0) != 1)
807 cleanup_exit(msg.wParam); /* this doesn't return... */
808 return msg.wParam; /* ... but optimiser doesn't know */
814 void cleanup_exit(int code)
827 if (cfg.protocol == PROT_SSH) {
838 * Set up, or shut down, an AsyncSelect. Called from winnet.c.
840 char *do_select(SOCKET skt, int startup)
845 events = (FD_CONNECT | FD_READ | FD_WRITE |
846 FD_OOB | FD_CLOSE | FD_ACCEPT);
851 return "do_select(): internal error (hwnd==NULL)";
852 if (WSAAsyncSelect(skt, hwnd, msg, events) == SOCKET_ERROR) {
853 switch (WSAGetLastError()) {
855 return "Network is down";
857 return "WSAAsyncSelect(): unknown error";
864 * set or clear the "raw mouse message" mode
866 void set_raw_mouse_mode(int activate)
868 activate = activate && !cfg.no_mouse_rep;
869 send_raw_mouse = activate;
870 SetCursor(LoadCursor(NULL, activate ? IDC_ARROW : IDC_IBEAM));
874 * Print a message box and close the connection.
876 void connection_fatal(char *fmt, ...)
882 vsprintf(stuff, fmt, ap);
884 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
885 if (cfg.close_on_exit == COE_ALWAYS)
888 session_closed = TRUE;
889 SetWindowText(hwnd, "PuTTY (inactive)");
894 * Report an error at the command-line parsing stage.
896 void cmdline_error(char *fmt, ...)
902 vsprintf(stuff, fmt, ap);
904 MessageBox(hwnd, stuff, "PuTTY Command Line Error", MB_ICONERROR | MB_OK);
909 * Actually do the job requested by a WM_NETEVENT
911 static void enact_pending_netevent(void)
913 static int reentering = 0;
914 extern int select_result(WPARAM, LPARAM);
918 return; /* don't unpend the pending */
920 pending_netevent = FALSE;
923 ret = select_result(pend_netevent_wParam, pend_netevent_lParam);
926 if (ret == 0 && !session_closed) {
927 /* Abnormal exits will already have set session_closed and taken
928 * appropriate action. */
929 if (cfg.close_on_exit == COE_ALWAYS ||
930 cfg.close_on_exit == COE_NORMAL) PostQuitMessage(0);
932 session_closed = TRUE;
933 SetWindowText(hwnd, "PuTTY (inactive)");
934 MessageBox(hwnd, "Connection closed by remote host",
935 "PuTTY", MB_OK | MB_ICONINFORMATION);
941 * Copy the colour palette from the configuration data into defpal.
942 * This is non-trivial because the colour indices are different.
944 static void cfgtopalette(void)
947 static const int ww[] = {
948 6, 7, 8, 9, 10, 11, 12, 13,
949 14, 15, 16, 17, 18, 19, 20, 21,
950 0, 1, 2, 3, 4, 4, 5, 5
953 for (i = 0; i < 24; i++) {
955 defpal[i].rgbtRed = cfg.colours[w][0];
956 defpal[i].rgbtGreen = cfg.colours[w][1];
957 defpal[i].rgbtBlue = cfg.colours[w][2];
962 * Set up the colour palette.
964 static void init_palette(void)
967 HDC hdc = GetDC(hwnd);
969 if (cfg.try_palette && GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) {
970 logpal = smalloc(sizeof(*logpal)
971 - sizeof(logpal->palPalEntry)
972 + NCOLOURS * sizeof(PALETTEENTRY));
973 logpal->palVersion = 0x300;
974 logpal->palNumEntries = NCOLOURS;
975 for (i = 0; i < NCOLOURS; i++) {
976 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
977 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
978 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
979 logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
981 pal = CreatePalette(logpal);
983 SelectPalette(hdc, pal, FALSE);
985 SelectPalette(hdc, GetStockObject(DEFAULT_PALETTE), FALSE);
988 ReleaseDC(hwnd, hdc);
991 for (i = 0; i < NCOLOURS; i++)
992 colours[i] = PALETTERGB(defpal[i].rgbtRed,
996 for (i = 0; i < NCOLOURS; i++)
997 colours[i] = RGB(defpal[i].rgbtRed,
998 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
1002 * Initialise all the fonts we will need initially. There may be as many as
1003 * three or as few as one. The other (poentially) twentyone fonts are done
1004 * if/when they are needed.
1008 * - check the font width and height, correcting our guesses if
1011 * - verify that the bold font is the same width as the ordinary
1012 * one, and engage shadow bolding if not.
1014 * - verify that the underlined font is the same width as the
1015 * ordinary one (manual underlining by means of line drawing can
1016 * be done in a pinch).
1018 static void init_fonts(int pick_width, int pick_height)
1025 int fw_dontcare, fw_bold;
1027 for (i = 0; i < FONT_MAXNO; i++)
1030 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
1031 und_mode = UND_FONT;
1033 if (cfg.fontisbold) {
1034 fw_dontcare = FW_BOLD;
1037 fw_dontcare = FW_DONTCARE;
1044 font_height = pick_height;
1046 font_height = cfg.fontheight;
1047 if (font_height > 0) {
1049 -MulDiv(font_height, GetDeviceCaps(hdc, LOGPIXELSY), 72);
1052 font_width = pick_width;
1054 #define f(i,c,w,u) \
1055 fonts[i] = CreateFont (font_height, font_width, 0, 0, w, FALSE, u, FALSE, \
1056 c, OUT_DEFAULT_PRECIS, \
1057 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, \
1058 FIXED_PITCH | FF_DONTCARE, cfg.font)
1060 f(FONT_NORMAL, cfg.fontcharset, fw_dontcare, FALSE);
1062 lfont.lfHeight = font_height;
1063 lfont.lfWidth = font_width;
1064 lfont.lfEscapement = 0;
1065 lfont.lfOrientation = 0;
1066 lfont.lfWeight = fw_dontcare;
1067 lfont.lfItalic = FALSE;
1068 lfont.lfUnderline = FALSE;
1069 lfont.lfStrikeOut = FALSE;
1070 lfont.lfCharSet = cfg.fontcharset;
1071 lfont.lfOutPrecision = OUT_DEFAULT_PRECIS;
1072 lfont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
1073 lfont.lfQuality = DEFAULT_QUALITY;
1074 lfont.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE;
1075 strncpy(lfont.lfFaceName, cfg.font, LF_FACESIZE);
1077 SelectObject(hdc, fonts[FONT_NORMAL]);
1078 GetTextMetrics(hdc, &tm);
1080 if (pick_width == 0 || pick_height == 0) {
1081 font_height = tm.tmHeight;
1082 font_width = tm.tmAveCharWidth;
1084 font_dualwidth = (tm.tmAveCharWidth != tm.tmMaxCharWidth);
1086 #ifdef RDB_DEBUG_PATCH
1087 debug(23, "Primary font H=%d, AW=%d, MW=%d",
1088 tm.tmHeight, tm.tmAveCharWidth, tm.tmMaxCharWidth);
1093 DWORD cset = tm.tmCharSet;
1094 memset(&info, 0xFF, sizeof(info));
1096 /* !!! Yes the next line is right */
1097 if (cset == OEM_CHARSET)
1098 font_codepage = GetOEMCP();
1100 if (TranslateCharsetInfo
1101 ((DWORD *) cset, &info, TCI_SRCCHARSET)) font_codepage =
1106 GetCPInfo(font_codepage, &cpinfo);
1107 dbcs_screenfont = (cpinfo.MaxCharSize > 1);
1110 f(FONT_UNDERLINE, cfg.fontcharset, fw_dontcare, TRUE);
1113 * Some fonts, e.g. 9-pt Courier, draw their underlines
1114 * outside their character cell. We successfully prevent
1115 * screen corruption by clipping the text output, but then
1116 * we lose the underline completely. Here we try to work
1117 * out whether this is such a font, and if it is, we set a
1118 * flag that causes underlines to be drawn by hand.
1120 * Having tried other more sophisticated approaches (such
1121 * as examining the TEXTMETRIC structure or requesting the
1122 * height of a string), I think we'll do this the brute
1123 * force way: we create a small bitmap, draw an underlined
1124 * space on it, and test to see whether any pixels are
1125 * foreground-coloured. (Since we expect the underline to
1126 * go all the way across the character cell, we only search
1127 * down a single column of the bitmap, half way across.)
1131 HBITMAP und_bm, und_oldbm;
1135 und_dc = CreateCompatibleDC(hdc);
1136 und_bm = CreateCompatibleBitmap(hdc, font_width, font_height);
1137 und_oldbm = SelectObject(und_dc, und_bm);
1138 SelectObject(und_dc, fonts[FONT_UNDERLINE]);
1139 SetTextAlign(und_dc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
1140 SetTextColor(und_dc, RGB(255, 255, 255));
1141 SetBkColor(und_dc, RGB(0, 0, 0));
1142 SetBkMode(und_dc, OPAQUE);
1143 ExtTextOut(und_dc, 0, 0, ETO_OPAQUE, NULL, " ", 1, NULL);
1145 for (i = 0; i < font_height; i++) {
1146 c = GetPixel(und_dc, font_width / 2, i);
1147 if (c != RGB(0, 0, 0))
1150 SelectObject(und_dc, und_oldbm);
1151 DeleteObject(und_bm);
1154 und_mode = UND_LINE;
1155 DeleteObject(fonts[FONT_UNDERLINE]);
1156 fonts[FONT_UNDERLINE] = 0;
1160 if (bold_mode == BOLD_FONT) {
1161 f(FONT_BOLD, cfg.fontcharset, fw_bold, FALSE);
1165 descent = tm.tmAscent + 1;
1166 if (descent >= font_height)
1167 descent = font_height - 1;
1169 for (i = 0; i < 3; i++) {
1171 if (SelectObject(hdc, fonts[i]) && GetTextMetrics(hdc, &tm))
1172 fontsize[i] = tm.tmAveCharWidth + 256 * tm.tmHeight;
1179 ReleaseDC(hwnd, hdc);
1181 if (fontsize[FONT_UNDERLINE] != fontsize[FONT_NORMAL]) {
1182 und_mode = UND_LINE;
1183 DeleteObject(fonts[FONT_UNDERLINE]);
1184 fonts[FONT_UNDERLINE] = 0;
1187 if (bold_mode == BOLD_FONT &&
1188 fontsize[FONT_BOLD] != fontsize[FONT_NORMAL]) {
1189 bold_mode = BOLD_SHADOW;
1190 DeleteObject(fonts[FONT_BOLD]);
1191 fonts[FONT_BOLD] = 0;
1193 fontflag[0] = fontflag[1] = fontflag[2] = 1;
1198 static void another_font(int fontno)
1201 int fw_dontcare, fw_bold;
1205 if (fontno < 0 || fontno >= FONT_MAXNO || fontflag[fontno])
1208 basefont = (fontno & ~(FONT_BOLDUND));
1209 if (basefont != fontno && !fontflag[basefont])
1210 another_font(basefont);
1212 if (cfg.fontisbold) {
1213 fw_dontcare = FW_BOLD;
1216 fw_dontcare = FW_DONTCARE;
1220 c = cfg.fontcharset;
1226 if (fontno & FONT_WIDE)
1228 if (fontno & FONT_NARROW)
1230 if (fontno & FONT_OEM)
1232 if (fontno & FONT_BOLD)
1234 if (fontno & FONT_UNDERLINE)
1238 CreateFont(font_height * (1 + !!(fontno & FONT_HIGH)), x, 0, 0, w,
1239 FALSE, u, FALSE, c, OUT_DEFAULT_PRECIS,
1240 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
1241 FIXED_PITCH | FF_DONTCARE, s);
1243 fontflag[fontno] = 1;
1246 static void deinit_fonts(void)
1249 for (i = 0; i < FONT_MAXNO; i++) {
1251 DeleteObject(fonts[i]);
1257 void request_resize(int w, int h)
1261 /* If the window is maximized supress resizing attempts */
1262 if (IsZoomed(hwnd)) {
1263 if (cfg.resize_action == RESIZE_TERM)
1267 if (cfg.resize_action == RESIZE_DISABLED) return;
1268 if (h == term->rows && w == term->cols) return;
1270 /* Sanity checks ... */
1272 static int first_time = 1;
1275 switch (first_time) {
1277 /* Get the size of the screen */
1278 if (get_fullscreen_rect(&ss))
1279 /* first_time = 0 */ ;
1285 /* Make sure the values are sane */
1286 width = (ss.right - ss.left - extra_width) / 4;
1287 height = (ss.bottom - ss.top - extra_height) / 6;
1289 if (w > width || h > height)
1298 term_size(term, h, w, cfg.savelines);
1300 if (cfg.resize_action != RESIZE_FONT && !IsZoomed(hwnd)) {
1301 width = extra_width + font_width * w;
1302 height = extra_height + font_height * h;
1304 SetWindowPos(hwnd, NULL, 0, 0, width, height,
1305 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1306 SWP_NOMOVE | SWP_NOZORDER);
1310 InvalidateRect(hwnd, NULL, TRUE);
1313 static void reset_window(int reinit) {
1315 * This function decides how to resize or redraw when the
1316 * user changes something.
1318 * This function doesn't like to change the terminal size but if the
1319 * font size is locked that may be it's only soluion.
1321 int win_width, win_height;
1324 #ifdef RDB_DEBUG_PATCH
1325 debug((27, "reset_window()"));
1328 /* Current window sizes ... */
1329 GetWindowRect(hwnd, &wr);
1330 GetClientRect(hwnd, &cr);
1332 win_width = cr.right - cr.left;
1333 win_height = cr.bottom - cr.top;
1335 if (cfg.resize_action == RESIZE_DISABLED) reinit = 2;
1337 /* Are we being forced to reload the fonts ? */
1339 #ifdef RDB_DEBUG_PATCH
1340 debug((27, "reset_window() -- Forced deinit"));
1346 /* Oh, looks like we're minimised */
1347 if (win_width == 0 || win_height == 0)
1350 /* Is the window out of position ? */
1352 (offset_width != (win_width-font_width*term->cols)/2 ||
1353 offset_height != (win_height-font_height*term->rows)/2) ){
1354 offset_width = (win_width-font_width*term->cols)/2;
1355 offset_height = (win_height-font_height*term->rows)/2;
1356 InvalidateRect(hwnd, NULL, TRUE);
1357 #ifdef RDB_DEBUG_PATCH
1358 debug((27, "reset_window() -> Reposition terminal"));
1362 if (IsZoomed(hwnd)) {
1363 /* We're fullscreen, this means we must not change the size of
1364 * the window so it's the font size or the terminal itself.
1367 extra_width = wr.right - wr.left - cr.right + cr.left;
1368 extra_height = wr.bottom - wr.top - cr.bottom + cr.top;
1370 if (cfg.resize_action != RESIZE_TERM) {
1371 if ( font_width != win_width/term->cols ||
1372 font_height != win_height/term->rows) {
1374 init_fonts(win_width/term->cols, win_height/term->rows);
1375 offset_width = (win_width-font_width*term->cols)/2;
1376 offset_height = (win_height-font_height*term->rows)/2;
1377 InvalidateRect(hwnd, NULL, TRUE);
1378 #ifdef RDB_DEBUG_PATCH
1379 debug((25, "reset_window() -> Z font resize to (%d, %d)",
1380 font_width, font_height));
1384 if ( font_width != win_width/term->cols ||
1385 font_height != win_height/term->rows) {
1386 /* Our only choice at this point is to change the
1387 * size of the terminal; Oh well.
1389 term_size(term, win_height/font_height, win_width/font_width,
1391 offset_width = (win_width-font_width*term->cols)/2;
1392 offset_height = (win_height-font_height*term->rows)/2;
1393 InvalidateRect(hwnd, NULL, TRUE);
1394 #ifdef RDB_DEBUG_PATCH
1395 debug((27, "reset_window() -> Zoomed term_size"));
1402 /* Hmm, a force re-init means we should ignore the current window
1403 * so we resize to the default font size.
1406 #ifdef RDB_DEBUG_PATCH
1407 debug((27, "reset_window() -> Forced re-init"));
1410 offset_width = offset_height = cfg.window_border;
1411 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
1412 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
1414 if (win_width != font_width*term->cols + offset_width*2 ||
1415 win_height != font_height*term->rows + offset_height*2) {
1417 /* If this is too large windows will resize it to the maximum
1418 * allowed window size, we will then be back in here and resize
1419 * the font or terminal to fit.
1421 SetWindowPos(hwnd, NULL, 0, 0,
1422 font_width*term->cols + extra_width,
1423 font_height*term->rows + extra_height,
1424 SWP_NOMOVE | SWP_NOZORDER);
1427 InvalidateRect(hwnd, NULL, TRUE);
1431 /* Okay the user doesn't want us to change the font so we try the
1432 * window. But that may be too big for the screen which forces us
1433 * to change the terminal.
1435 if ((cfg.resize_action == RESIZE_TERM && reinit<=0) ||
1436 (cfg.resize_action == RESIZE_EITHER && reinit<0) ||
1438 offset_width = offset_height = cfg.window_border;
1439 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
1440 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
1442 if (win_width != font_width*term->cols + offset_width*2 ||
1443 win_height != font_height*term->rows + offset_height*2) {
1448 get_fullscreen_rect(&ss);
1450 width = (ss.right - ss.left - extra_width) / font_width;
1451 height = (ss.bottom - ss.top - extra_height) / font_height;
1454 if ( term->rows > height || term->cols > width ) {
1455 if (cfg.resize_action == RESIZE_EITHER) {
1456 /* Make the font the biggest we can */
1457 if (term->cols > width)
1458 font_width = (ss.right - ss.left - extra_width)
1460 if (term->rows > height)
1461 font_height = (ss.bottom - ss.top - extra_height)
1465 init_fonts(font_width, font_height);
1467 width = (ss.right - ss.left - extra_width) / font_width;
1468 height = (ss.bottom - ss.top - extra_height) / font_height;
1470 if ( height > term->rows ) height = term->rows;
1471 if ( width > term->cols ) width = term->cols;
1472 term_size(term, height, width, cfg.savelines);
1473 #ifdef RDB_DEBUG_PATCH
1474 debug((27, "reset_window() -> term resize to (%d,%d)",
1480 SetWindowPos(hwnd, NULL, 0, 0,
1481 font_width*term->cols + extra_width,
1482 font_height*term->rows + extra_height,
1483 SWP_NOMOVE | SWP_NOZORDER);
1485 InvalidateRect(hwnd, NULL, TRUE);
1486 #ifdef RDB_DEBUG_PATCH
1487 debug((27, "reset_window() -> window resize to (%d,%d)",
1488 font_width*term->cols + extra_width,
1489 font_height*term->rows + extra_height));
1495 /* We're allowed to or must change the font but do we want to ? */
1497 if (font_width != (win_width-cfg.window_border*2)/term->cols ||
1498 font_height != (win_height-cfg.window_border*2)/term->rows) {
1501 init_fonts((win_width-cfg.window_border*2)/term->cols,
1502 (win_height-cfg.window_border*2)/term->rows);
1503 offset_width = (win_width-font_width*term->cols)/2;
1504 offset_height = (win_height-font_height*term->rows)/2;
1506 extra_width = wr.right - wr.left - cr.right + cr.left +offset_width*2;
1507 extra_height = wr.bottom - wr.top - cr.bottom + cr.top+offset_height*2;
1509 InvalidateRect(hwnd, NULL, TRUE);
1510 #ifdef RDB_DEBUG_PATCH
1511 debug((25, "reset_window() -> font resize to (%d,%d)",
1512 font_width, font_height));
1517 static void set_input_locale(HKL kl)
1521 GetLocaleInfo(LOWORD(kl), LOCALE_IDEFAULTANSICODEPAGE,
1522 lbuf, sizeof(lbuf));
1524 kbd_codepage = atoi(lbuf);
1527 static void click(Mouse_Button b, int x, int y, int shift, int ctrl, int alt)
1529 int thistime = GetMessageTime();
1531 if (send_raw_mouse && !(cfg.mouse_override && shift)) {
1532 lastbtn = MBT_NOTHING;
1533 term_mouse(term, b, MA_CLICK, x, y, shift, ctrl, alt);
1537 if (lastbtn == b && thistime - lasttime < dbltime) {
1538 lastact = (lastact == MA_CLICK ? MA_2CLK :
1539 lastact == MA_2CLK ? MA_3CLK :
1540 lastact == MA_3CLK ? MA_CLICK : MA_NOTHING);
1545 if (lastact != MA_NOTHING)
1546 term_mouse(term, b, lastact, x, y, shift, ctrl, alt);
1547 lasttime = thistime;
1551 * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT)
1552 * into a cooked one (SELECT, EXTEND, PASTE).
1554 Mouse_Button translate_button(Mouse_Button button)
1556 if (button == MBT_LEFT)
1558 if (button == MBT_MIDDLE)
1559 return cfg.mouse_is_xterm ? MBT_PASTE : MBT_EXTEND;
1560 if (button == MBT_RIGHT)
1561 return cfg.mouse_is_xterm ? MBT_EXTEND : MBT_PASTE;
1562 return 0; /* shouldn't happen */
1565 static void show_mouseptr(int show)
1567 static int cursor_visible = 1;
1568 if (!cfg.hide_mouseptr) /* override if this feature disabled */
1570 if (cursor_visible && !show)
1572 else if (!cursor_visible && show)
1574 cursor_visible = show;
1577 static int is_alt_pressed(void)
1580 int r = GetKeyboardState(keystate);
1583 if (keystate[VK_MENU] & 0x80)
1585 if (keystate[VK_RMENU] & 0x80)
1590 static int is_shift_pressed(void)
1593 int r = GetKeyboardState(keystate);
1596 if (keystate[VK_SHIFT] & 0x80)
1601 static int resizing;
1603 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1604 WPARAM wParam, LPARAM lParam)
1607 static int ignore_clip = FALSE;
1608 static int need_backend_resize = FALSE;
1609 static int fullscr_on_max = FALSE;
1613 if (pending_netevent)
1614 enact_pending_netevent();
1615 if (GetCapture() != hwnd ||
1616 (send_raw_mouse && !(cfg.mouse_override && is_shift_pressed())))
1622 if (cfg.ping_interval > 0) {
1625 if (now - last_movement > cfg.ping_interval) {
1626 back->special(backhandle, TS_PING);
1627 last_movement = now;
1630 net_pending_errors();
1636 if (!cfg.warn_on_close || session_closed ||
1638 "Are you sure you want to close this session?",
1639 "PuTTY Exit Confirmation",
1640 MB_ICONWARNING | MB_OKCANCEL) == IDOK)
1641 DestroyWindow(hwnd);
1648 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1660 PROCESS_INFORMATION pi;
1661 HANDLE filemap = NULL;
1663 if (wParam == IDM_DUPSESS) {
1665 * Allocate a file-mapping memory chunk for the
1668 SECURITY_ATTRIBUTES sa;
1671 sa.nLength = sizeof(sa);
1672 sa.lpSecurityDescriptor = NULL;
1673 sa.bInheritHandle = TRUE;
1674 filemap = CreateFileMapping((HANDLE) 0xFFFFFFFF,
1677 0, sizeof(Config), NULL);
1679 p = (Config *) MapViewOfFile(filemap,
1681 0, 0, sizeof(Config));
1683 *p = cfg; /* structure copy */
1687 sprintf(c, "putty &%p", filemap);
1689 } else if (wParam == IDM_SAVEDSESS) {
1690 if ((lParam - IDM_SAVED_MIN) / 16 < nsessions) {
1692 sessions[(lParam - IDM_SAVED_MIN) / 16];
1693 cl = smalloc(16 + strlen(session));
1694 /* 8, but play safe */
1697 /* not a very important failure mode */
1699 sprintf(cl, "putty @%s", session);
1707 GetModuleFileName(NULL, b, sizeof(b) - 1);
1709 si.lpReserved = NULL;
1710 si.lpDesktop = NULL;
1714 si.lpReserved2 = NULL;
1715 CreateProcess(b, cl, NULL, NULL, TRUE,
1716 NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
1719 CloseHandle(filemap);
1729 GetWindowText(hwnd, cfg.wintitle, sizeof(cfg.wintitle));
1732 if (!do_reconfig(hwnd))
1736 /* Disable full-screen if resizing forbidden */
1737 HMENU m = GetSystemMenu (hwnd, FALSE);
1738 EnableMenuItem(m, IDM_FULLSCREEN, MF_BYCOMMAND |
1739 (cfg.resize_action == RESIZE_DISABLED)
1740 ? MF_GRAYED : MF_ENABLED);
1741 /* Gracefully unzoom if necessary */
1742 if (IsZoomed(hwnd) &&
1743 (cfg.resize_action == RESIZE_DISABLED)) {
1744 ShowWindow(hwnd, SW_RESTORE);
1748 if (strcmp(prev_cfg.logfilename, cfg.logfilename) ||
1749 prev_cfg.logtype != cfg.logtype) {
1750 logfclose(); /* reset logging */
1756 * Flush the line discipline's edit buffer in the
1757 * case where local editing has just been disabled.
1759 ldisc_send(ldisc, NULL, 0, 0);
1767 /* Give terminal a heads-up on miscellaneous stuff */
1768 term_reconfig(term);
1770 /* Screen size changed ? */
1771 if (cfg.height != prev_cfg.height ||
1772 cfg.width != prev_cfg.width ||
1773 cfg.savelines != prev_cfg.savelines ||
1774 cfg.resize_action == RESIZE_FONT ||
1775 (cfg.resize_action == RESIZE_EITHER && IsZoomed(hwnd)) ||
1776 cfg.resize_action == RESIZE_DISABLED)
1777 term_size(term, cfg.height, cfg.width, cfg.savelines);
1779 /* Enable or disable the scroll bar, etc */
1781 LONG nflg, flag = GetWindowLong(hwnd, GWL_STYLE);
1782 LONG nexflag, exflag =
1783 GetWindowLong(hwnd, GWL_EXSTYLE);
1786 if (cfg.alwaysontop != prev_cfg.alwaysontop) {
1787 if (cfg.alwaysontop) {
1788 nexflag |= WS_EX_TOPMOST;
1789 SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
1790 SWP_NOMOVE | SWP_NOSIZE);
1792 nexflag &= ~(WS_EX_TOPMOST);
1793 SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0,
1794 SWP_NOMOVE | SWP_NOSIZE);
1797 if (cfg.sunken_edge)
1798 nexflag |= WS_EX_CLIENTEDGE;
1800 nexflag &= ~(WS_EX_CLIENTEDGE);
1803 if (is_full_screen() ?
1804 cfg.scrollbar_in_fullscreen : cfg.scrollbar)
1807 nflg &= ~WS_VSCROLL;
1809 if (cfg.resize_action == RESIZE_DISABLED ||
1811 nflg &= ~WS_THICKFRAME;
1813 nflg |= WS_THICKFRAME;
1815 if (cfg.resize_action == RESIZE_DISABLED)
1816 nflg &= ~WS_MAXIMIZEBOX;
1818 nflg |= WS_MAXIMIZEBOX;
1820 if (nflg != flag || nexflag != exflag) {
1822 SetWindowLong(hwnd, GWL_STYLE, nflg);
1823 if (nexflag != exflag)
1824 SetWindowLong(hwnd, GWL_EXSTYLE, nexflag);
1826 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
1827 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1828 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
1836 if (cfg.resize_action == RESIZE_DISABLED && IsZoomed(hwnd)) {
1841 set_title(cfg.wintitle);
1842 if (IsIconic(hwnd)) {
1844 cfg.win_name_always ? window_name :
1848 if (strcmp(cfg.font, prev_cfg.font) != 0 ||
1849 strcmp(cfg.line_codepage, prev_cfg.line_codepage) != 0 ||
1850 cfg.fontisbold != prev_cfg.fontisbold ||
1851 cfg.fontheight != prev_cfg.fontheight ||
1852 cfg.fontcharset != prev_cfg.fontcharset ||
1853 cfg.vtmode != prev_cfg.vtmode ||
1854 cfg.bold_colour != prev_cfg.bold_colour ||
1855 cfg.resize_action == RESIZE_DISABLED ||
1856 cfg.resize_action == RESIZE_EITHER ||
1857 (cfg.resize_action != prev_cfg.resize_action))
1860 InvalidateRect(hwnd, NULL, TRUE);
1861 reset_window(init_lvl);
1862 net_pending_errors();
1873 ldisc_send(ldisc, NULL, 0, 0);
1876 back->special(backhandle, TS_AYT);
1877 net_pending_errors();
1880 back->special(backhandle, TS_BRK);
1881 net_pending_errors();
1884 back->special(backhandle, TS_SYNCH);
1885 net_pending_errors();
1888 back->special(backhandle, TS_EC);
1889 net_pending_errors();
1892 back->special(backhandle, TS_EL);
1893 net_pending_errors();
1896 back->special(backhandle, TS_GA);
1897 net_pending_errors();
1900 back->special(backhandle, TS_NOP);
1901 net_pending_errors();
1904 back->special(backhandle, TS_ABORT);
1905 net_pending_errors();
1908 back->special(backhandle, TS_AO);
1909 net_pending_errors();
1912 back->special(backhandle, TS_IP);
1913 net_pending_errors();
1916 back->special(backhandle, TS_SUSP);
1917 net_pending_errors();
1920 back->special(backhandle, TS_EOR);
1921 net_pending_errors();
1924 back->special(backhandle, TS_EOF);
1925 net_pending_errors();
1931 WinHelp(hwnd, help_path,
1932 help_has_contents ? HELP_FINDER : HELP_CONTENTS, 0);
1936 * We get this if the System menu has been activated
1943 * We get this if the System menu has been activated
1944 * using the keyboard. This might happen from within
1945 * TranslateKey, in which case it really wants to be
1946 * followed by a `space' character to actually _bring
1947 * the menu up_ rather than just sitting there in
1948 * `ready to appear' state.
1950 show_mouseptr(1); /* make sure pointer is visible */
1952 PostMessage(hwnd, WM_CHAR, ' ', 0);
1954 case IDM_FULLSCREEN:
1958 if (wParam >= IDM_SAVED_MIN && wParam <= IDM_SAVED_MAX) {
1959 SendMessage(hwnd, WM_SYSCOMMAND, IDM_SAVEDSESS, wParam);
1964 #define X_POS(l) ((int)(short)LOWORD(l))
1965 #define Y_POS(l) ((int)(short)HIWORD(l))
1967 #define TO_CHR_X(x) ((((x)<0 ? (x)-font_width+1 : (x))-offset_width) / font_width)
1968 #define TO_CHR_Y(y) ((((y)<0 ? (y)-font_height+1: (y))-offset_height) / font_height)
1969 case WM_LBUTTONDOWN:
1970 case WM_MBUTTONDOWN:
1971 case WM_RBUTTONDOWN:
1979 case WM_LBUTTONDOWN:
1983 case WM_MBUTTONDOWN:
1984 button = MBT_MIDDLE;
1987 case WM_RBUTTONDOWN:
1996 button = MBT_MIDDLE;
2004 button = press = 0; /* shouldn't happen */
2008 * Special case: in full-screen mode, if the left
2009 * button is clicked in the very top left corner of the
2010 * window, we put up the System menu instead of doing
2013 if (is_full_screen() && press && button == MBT_LEFT &&
2014 X_POS(lParam) == 0 && Y_POS(lParam) == 0) {
2015 SendMessage(hwnd, WM_SYSCOMMAND, SC_MOUSEMENU, 0);
2020 TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam)),
2021 wParam & MK_SHIFT, wParam & MK_CONTROL,
2025 term_mouse(term, button, MA_RELEASE,
2026 TO_CHR_X(X_POS(lParam)),
2027 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
2028 wParam & MK_CONTROL, is_alt_pressed());
2036 * Add the mouse position and message time to the random
2039 noise_ultralight(lParam);
2041 if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON) &&
2042 GetCapture() == hwnd) {
2044 if (wParam & MK_LBUTTON)
2046 else if (wParam & MK_MBUTTON)
2050 term_mouse(term, b, MA_DRAG, TO_CHR_X(X_POS(lParam)),
2051 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
2052 wParam & MK_CONTROL, is_alt_pressed());
2055 case WM_NCMOUSEMOVE:
2057 noise_ultralight(lParam);
2059 case WM_IGNORE_CLIP:
2060 ignore_clip = wParam; /* don't panic on DESTROYCLIPBOARD */
2062 case WM_DESTROYCLIPBOARD:
2064 term_deselect(term);
2065 ignore_clip = FALSE;
2071 hdc = BeginPaint(hwnd, &p);
2073 SelectPalette(hdc, pal, TRUE);
2074 RealizePalette(hdc);
2076 term_paint(term, hdc,
2077 (p.rcPaint.left-offset_width)/font_width,
2078 (p.rcPaint.top-offset_height)/font_height,
2079 (p.rcPaint.right-offset_width-1)/font_width,
2080 (p.rcPaint.bottom-offset_height-1)/font_height);
2083 p.rcPaint.left < offset_width ||
2084 p.rcPaint.top < offset_height ||
2085 p.rcPaint.right >= offset_width + font_width*term->cols ||
2086 p.rcPaint.bottom>= offset_height + font_height*term->rows)
2088 HBRUSH fillcolour, oldbrush;
2090 fillcolour = CreateSolidBrush (
2091 colours[(ATTR_DEFBG>>ATTR_BGSHIFT)*2]);
2092 oldbrush = SelectObject(hdc, fillcolour);
2093 edge = CreatePen(PS_SOLID, 0,
2094 colours[(ATTR_DEFBG>>ATTR_BGSHIFT)*2]);
2095 oldpen = SelectObject(hdc, edge);
2098 * Jordan Russell reports that this apparently
2099 * ineffectual IntersectClipRect() call masks a
2100 * Windows NT/2K bug causing strange display
2101 * problems when the PuTTY window is taller than
2102 * the primary monitor. It seems harmless enough...
2104 IntersectClipRect(hdc,
2105 p.rcPaint.left, p.rcPaint.top,
2106 p.rcPaint.right, p.rcPaint.bottom);
2108 ExcludeClipRect(hdc,
2109 offset_width, offset_height,
2110 offset_width+font_width*term->cols,
2111 offset_height+font_height*term->rows);
2113 Rectangle(hdc, p.rcPaint.left, p.rcPaint.top,
2114 p.rcPaint.right, p.rcPaint.bottom);
2116 // SelectClipRgn(hdc, NULL);
2118 SelectObject(hdc, oldbrush);
2119 DeleteObject(fillcolour);
2120 SelectObject(hdc, oldpen);
2123 SelectObject(hdc, GetStockObject(SYSTEM_FONT));
2124 SelectObject(hdc, GetStockObject(WHITE_PEN));
2130 /* Notice we can get multiple netevents, FD_READ, FD_WRITE etc
2131 * but the only one that's likely to try to overload us is FD_READ.
2132 * This means buffering just one is fine.
2134 if (pending_netevent)
2135 enact_pending_netevent();
2137 pending_netevent = TRUE;
2138 pend_netevent_wParam = wParam;
2139 pend_netevent_lParam = lParam;
2140 if (WSAGETSELECTEVENT(lParam) != FD_READ)
2141 enact_pending_netevent();
2143 time(&last_movement);
2146 term->has_focus = TRUE;
2147 CreateCaret(hwnd, caretbm, font_width, font_height);
2149 flash_window(0); /* stop */
2156 term->has_focus = FALSE;
2158 caret_x = caret_y = -1; /* ensure caret is replaced next time */
2162 case WM_ENTERSIZEMOVE:
2163 #ifdef RDB_DEBUG_PATCH
2164 debug((27, "WM_ENTERSIZEMOVE"));
2168 need_backend_resize = FALSE;
2170 case WM_EXITSIZEMOVE:
2173 #ifdef RDB_DEBUG_PATCH
2174 debug((27, "WM_EXITSIZEMOVE"));
2176 if (need_backend_resize) {
2177 term_size(term, cfg.height, cfg.width, cfg.savelines);
2178 InvalidateRect(hwnd, NULL, TRUE);
2183 * This does two jobs:
2184 * 1) Keep the sizetip uptodate
2185 * 2) Make sure the window size is _stepped_ in units of the font size.
2187 if (cfg.resize_action != RESIZE_FONT && !alt_pressed) {
2188 int width, height, w, h, ew, eh;
2189 LPRECT r = (LPRECT) lParam;
2191 if ( !need_backend_resize && cfg.resize_action == RESIZE_EITHER &&
2192 (cfg.height != term->rows || cfg.width != term->cols )) {
2194 * Great! It seems that both the terminal size and the
2195 * font size have been changed and the user is now dragging.
2197 * It will now be difficult to get back to the configured
2200 * This would be easier but it seems to be too confusing.
2202 term_size(term, cfg.height, cfg.width, cfg.savelines);
2205 cfg.height=term->rows; cfg.width=term->cols;
2207 InvalidateRect(hwnd, NULL, TRUE);
2208 need_backend_resize = TRUE;
2211 width = r->right - r->left - extra_width;
2212 height = r->bottom - r->top - extra_height;
2213 w = (width + font_width / 2) / font_width;
2216 h = (height + font_height / 2) / font_height;
2219 UpdateSizeTip(hwnd, w, h);
2220 ew = width - w * font_width;
2221 eh = height - h * font_height;
2223 if (wParam == WMSZ_LEFT ||
2224 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
2230 if (wParam == WMSZ_TOP ||
2231 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
2241 int width, height, w, h, rv = 0;
2242 int ex_width = extra_width + (cfg.window_border - offset_width) * 2;
2243 int ex_height = extra_height + (cfg.window_border - offset_height) * 2;
2244 LPRECT r = (LPRECT) lParam;
2246 width = r->right - r->left - ex_width;
2247 height = r->bottom - r->top - ex_height;
2249 w = (width + term->cols/2)/term->cols;
2250 h = (height + term->rows/2)/term->rows;
2251 if ( r->right != r->left + w*term->cols + ex_width)
2254 if (wParam == WMSZ_LEFT ||
2255 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
2256 r->left = r->right - w*term->cols - ex_width;
2258 r->right = r->left + w*term->cols + ex_width;
2260 if (r->bottom != r->top + h*term->rows + ex_height)
2263 if (wParam == WMSZ_TOP ||
2264 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
2265 r->top = r->bottom - h*term->rows - ex_height;
2267 r->bottom = r->top + h*term->rows + ex_height;
2271 /* break; (never reached) */
2272 case WM_FULLSCR_ON_MAX:
2273 fullscr_on_max = TRUE;
2276 sys_cursor_update();
2279 #ifdef RDB_DEBUG_PATCH
2280 debug((27, "WM_SIZE %s (%d,%d)",
2281 (wParam == SIZE_MINIMIZED) ? "SIZE_MINIMIZED":
2282 (wParam == SIZE_MAXIMIZED) ? "SIZE_MAXIMIZED":
2283 (wParam == SIZE_RESTORED && resizing) ? "to":
2284 (wParam == SIZE_RESTORED) ? "SIZE_RESTORED":
2286 LOWORD(lParam), HIWORD(lParam)));
2288 if (wParam == SIZE_MINIMIZED)
2290 cfg.win_name_always ? window_name : icon_name);
2291 if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
2292 SetWindowText(hwnd, window_name);
2293 if (wParam == SIZE_RESTORED)
2294 clear_full_screen();
2295 if (wParam == SIZE_MAXIMIZED && fullscr_on_max) {
2296 fullscr_on_max = FALSE;
2300 if (cfg.resize_action == RESIZE_DISABLED) {
2301 /* A resize, well it better be a minimize. */
2305 int width, height, w, h;
2307 width = LOWORD(lParam);
2308 height = HIWORD(lParam);
2311 if (wParam == SIZE_MAXIMIZED && !was_zoomed) {
2313 prev_rows = term->rows;
2314 prev_cols = term->cols;
2315 if (cfg.resize_action == RESIZE_TERM) {
2316 w = width / font_width;
2318 h = height / font_height;
2321 term_size(term, h, w, cfg.savelines);
2324 } else if (wParam == SIZE_RESTORED && was_zoomed) {
2326 if (cfg.resize_action == RESIZE_TERM)
2327 term_size(term, prev_rows, prev_cols, cfg.savelines);
2328 if (cfg.resize_action != RESIZE_FONT)
2333 /* This is an unexpected resize, these will normally happen
2334 * if the window is too large. Probably either the user
2335 * selected a huge font or the screen size has changed.
2337 * This is also called with minimize.
2339 else reset_window(-1);
2343 * Don't call back->size in mid-resize. (To prevent
2344 * massive numbers of resize events getting sent
2345 * down the connection during an NT opaque drag.)
2348 if (cfg.resize_action != RESIZE_FONT && !alt_pressed) {
2349 need_backend_resize = TRUE;
2350 w = (width-cfg.window_border*2) / font_width;
2352 h = (height-cfg.window_border*2) / font_height;
2361 sys_cursor_update();
2364 switch (LOWORD(wParam)) {
2366 term_scroll(term, -1, 0);
2369 term_scroll(term, +1, 0);
2372 term_scroll(term, 0, +1);
2375 term_scroll(term, 0, -1);
2378 term_scroll(term, 0, +term->rows / 2);
2381 term_scroll(term, 0, -term->rows / 2);
2383 case SB_THUMBPOSITION:
2385 term_scroll(term, 1, HIWORD(wParam));
2389 case WM_PALETTECHANGED:
2390 if ((HWND) wParam != hwnd && pal != NULL) {
2391 HDC hdc = get_ctx();
2393 if (RealizePalette(hdc) > 0)
2399 case WM_QUERYNEWPALETTE:
2401 HDC hdc = get_ctx();
2403 if (RealizePalette(hdc) > 0)
2415 * Add the scan code and keypress timing to the random
2418 noise_ultralight(lParam);
2421 * We don't do TranslateMessage since it disassociates the
2422 * resulting CHAR message from the KEYDOWN that sparked it,
2423 * which we occasionally don't want. Instead, we process
2424 * KEYDOWN, and call the Win32 translator functions so that
2425 * we get the translations under _our_ control.
2428 unsigned char buf[20];
2431 if (wParam == VK_PROCESSKEY) {
2434 m.message = WM_KEYDOWN;
2436 m.lParam = lParam & 0xdfff;
2437 TranslateMessage(&m);
2439 len = TranslateKey(message, wParam, lParam, buf);
2441 return DefWindowProc(hwnd, message, wParam, lParam);
2445 * Interrupt an ongoing paste. I'm not sure
2446 * this is sensible, but for the moment it's
2447 * preferable to having to faff about buffering
2453 * We need not bother about stdin backlogs
2454 * here, because in GUI PuTTY we can't do
2455 * anything about it anyway; there's no means
2456 * of asking Windows to hold off on KEYDOWN
2457 * messages. We _have_ to buffer everything
2460 term_seen_key_event(term);
2461 ldisc_send(ldisc, buf, len, 1);
2466 net_pending_errors();
2468 case WM_INPUTLANGCHANGE:
2469 /* wParam == Font number */
2470 /* lParam == Locale */
2471 set_input_locale((HKL)lParam);
2472 sys_cursor_update();
2475 if(wParam == IMN_SETOPENSTATUS) {
2476 HIMC hImc = ImmGetContext(hwnd);
2477 ImmSetCompositionFont(hImc, &lfont);
2478 ImmReleaseContext(hwnd, hImc);
2482 case WM_IME_COMPOSITION:
2488 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ||
2489 osVersion.dwPlatformId == VER_PLATFORM_WIN32s) break; /* no Unicode */
2491 if ((lParam & GCS_RESULTSTR) == 0) /* Composition unfinished. */
2492 break; /* fall back to DefWindowProc */
2494 hIMC = ImmGetContext(hwnd);
2495 n = ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, NULL, 0);
2499 buff = (char*) smalloc(n);
2500 ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, buff, n);
2502 * Jaeyoun Chung reports that Korean character
2503 * input doesn't work correctly if we do a single
2504 * luni_send() covering the whole of buff. So
2505 * instead we luni_send the characters one by one.
2507 term_seen_key_event(term);
2508 for (i = 0; i < n; i += 2) {
2509 luni_send(ldisc, (unsigned short *)(buff+i), 1, 1);
2513 ImmReleaseContext(hwnd, hIMC);
2518 if (wParam & 0xFF00) {
2519 unsigned char buf[2];
2522 buf[0] = wParam >> 8;
2523 term_seen_key_event(term);
2524 lpage_send(ldisc, kbd_codepage, buf, 2, 1);
2526 char c = (unsigned char) wParam;
2527 term_seen_key_event(term);
2528 lpage_send(ldisc, kbd_codepage, &c, 1, 1);
2534 * Nevertheless, we are prepared to deal with WM_CHAR
2535 * messages, should they crop up. So if someone wants to
2536 * post the things to us as part of a macro manoeuvre,
2537 * we're ready to cope.
2540 char c = (unsigned char)wParam;
2541 term_seen_key_event(term);
2542 lpage_send(ldisc, CP_ACP, &c, 1, 1);
2546 if (send_raw_mouse && LOWORD(lParam) == HTCLIENT) {
2547 SetCursor(LoadCursor(NULL, IDC_ARROW));
2551 if (message == wm_mousewheel || message == WM_MOUSEWHEEL) {
2552 int shift_pressed=0, control_pressed=0, alt_pressed=0;
2554 if (message == WM_MOUSEWHEEL) {
2555 wheel_accumulator += (short)HIWORD(wParam);
2556 shift_pressed=LOWORD(wParam) & MK_SHIFT;
2557 control_pressed=LOWORD(wParam) & MK_CONTROL;
2560 wheel_accumulator += (int)wParam;
2561 if (GetKeyboardState(keys)!=0) {
2562 shift_pressed=keys[VK_SHIFT]&0x80;
2563 control_pressed=keys[VK_CONTROL]&0x80;
2567 /* process events when the threshold is reached */
2568 while (abs(wheel_accumulator) >= WHEEL_DELTA) {
2571 /* reduce amount for next time */
2572 if (wheel_accumulator > 0) {
2574 wheel_accumulator -= WHEEL_DELTA;
2575 } else if (wheel_accumulator < 0) {
2577 wheel_accumulator += WHEEL_DELTA;
2581 if (send_raw_mouse &&
2582 !(cfg.mouse_override && shift_pressed)) {
2583 /* send a mouse-down followed by a mouse up */
2586 TO_CHR_X(X_POS(lParam)),
2587 TO_CHR_Y(Y_POS(lParam)), shift_pressed,
2588 control_pressed, is_alt_pressed());
2589 term_mouse(term, b, MA_RELEASE, TO_CHR_X(X_POS(lParam)),
2590 TO_CHR_Y(Y_POS(lParam)), shift_pressed,
2591 control_pressed, is_alt_pressed());
2593 /* trigger a scroll */
2594 term_scroll(term, 0,
2596 -term->rows / 2 : term->rows / 2);
2603 return DefWindowProc(hwnd, message, wParam, lParam);
2607 * Move the system caret. (We maintain one, even though it's
2608 * invisible, for the benefit of blind people: apparently some
2609 * helper software tracks the system caret, so we should arrange to
2612 void sys_cursor(int x, int y)
2616 if (!term->has_focus) return;
2619 * Avoid gratuitously re-updating the cursor position and IMM
2620 * window if there's no actual change required.
2622 cx = x * font_width + offset_width;
2623 cy = y * font_height + offset_height;
2624 if (cx == caret_x && cy == caret_y)
2629 sys_cursor_update();
2632 static void sys_cursor_update(void)
2637 if (!term->has_focus) return;
2639 if (caret_x < 0 || caret_y < 0)
2642 SetCaretPos(caret_x, caret_y);
2644 /* IMM calls on Win98 and beyond only */
2645 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32s) return; /* 3.11 */
2647 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS &&
2648 osVersion.dwMinorVersion == 0) return; /* 95 */
2650 /* we should have the IMM functions */
2651 hIMC = ImmGetContext(hwnd);
2652 cf.dwStyle = CFS_POINT;
2653 cf.ptCurrentPos.x = caret_x;
2654 cf.ptCurrentPos.y = caret_y;
2655 ImmSetCompositionWindow(hIMC, &cf);
2657 ImmReleaseContext(hwnd, hIMC);
2661 * Draw a line of text in the window, at given character
2662 * coordinates, in given attributes.
2664 * We are allowed to fiddle with the contents of `text'.
2666 void do_text(Context ctx, int x, int y, char *text, int len,
2667 unsigned long attr, int lattr)
2670 int nfg, nbg, nfont;
2673 int force_manual_underline = 0;
2674 int fnt_width = font_width * (1 + (lattr != LATTR_NORM));
2675 int char_width = fnt_width;
2676 int text_adjust = 0;
2677 static int *IpDx = 0, IpDxLEN = 0;
2679 if (attr & ATTR_WIDE)
2682 if (len > IpDxLEN || IpDx[0] != char_width) {
2684 if (len > IpDxLEN) {
2686 IpDx = smalloc((len + 16) * sizeof(int));
2687 IpDxLEN = (len + 16);
2689 for (i = 0; i < IpDxLEN; i++)
2690 IpDx[i] = char_width;
2693 /* Only want the left half of double width lines */
2694 if (lattr != LATTR_NORM && x*2 >= term->cols)
2702 if ((attr & TATTR_ACTCURS) && (cfg.cursor_type == 0 || term->big_cursor)) {
2703 attr &= ATTR_CUR_AND | (bold_mode != BOLD_COLOURS ? ATTR_BOLD : 0);
2704 attr ^= ATTR_CUR_XOR;
2708 if (cfg.vtmode == VT_POORMAN && lattr != LATTR_NORM) {
2709 /* Assume a poorman font is borken in other ways too. */
2719 nfont |= FONT_WIDE + FONT_HIGH;
2722 if (attr & ATTR_NARROW)
2723 nfont |= FONT_NARROW;
2725 /* Special hack for the VT100 linedraw glyphs. */
2726 if ((attr & CSET_MASK) == 0x2300) {
2727 if (text[0] >= (char) 0xBA && text[0] <= (char) 0xBD) {
2728 switch ((unsigned char) (text[0])) {
2730 text_adjust = -2 * font_height / 5;
2733 text_adjust = -1 * font_height / 5;
2736 text_adjust = font_height / 5;
2739 text_adjust = 2 * font_height / 5;
2742 if (lattr == LATTR_TOP || lattr == LATTR_BOT)
2745 text[0] = (char) (unitab_xterm['q'] & CHAR_MASK);
2746 attr |= (unitab_xterm['q'] & CSET_MASK);
2747 if (attr & ATTR_UNDER) {
2748 attr &= ~ATTR_UNDER;
2749 force_manual_underline = 1;
2754 /* Anything left as an original character set is unprintable. */
2755 if (DIRECT_CHAR(attr)) {
2758 memset(text, 0xFD, len);
2762 if ((attr & CSET_MASK) == ATTR_OEMCP)
2765 nfg = 2 * ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
2766 nbg = 2 * ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
2767 if (bold_mode == BOLD_FONT && (attr & ATTR_BOLD))
2769 if (und_mode == UND_FONT && (attr & ATTR_UNDER))
2770 nfont |= FONT_UNDERLINE;
2771 another_font(nfont);
2772 if (!fonts[nfont]) {
2773 if (nfont & FONT_UNDERLINE)
2774 force_manual_underline = 1;
2775 /* Don't do the same for manual bold, it could be bad news. */
2777 nfont &= ~(FONT_BOLD | FONT_UNDERLINE);
2779 another_font(nfont);
2781 nfont = FONT_NORMAL;
2782 if (attr & ATTR_REVERSE) {
2787 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BOLD))
2789 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BLINK))
2793 SelectObject(hdc, fonts[nfont]);
2794 SetTextColor(hdc, fg);
2795 SetBkColor(hdc, bg);
2796 SetBkMode(hdc, OPAQUE);
2799 line_box.right = x + char_width * len;
2800 line_box.bottom = y + font_height;
2802 /* Only want the left half of double width lines */
2803 if (line_box.right > font_width*term->cols+offset_width)
2804 line_box.right = font_width*term->cols+offset_width;
2806 /* We're using a private area for direct to font. (512 chars.) */
2807 if (dbcs_screenfont && (attr & CSET_MASK) == ATTR_ACP) {
2808 /* Ho Hum, dbcs fonts are a PITA! */
2809 /* To display on W9x I have to convert to UCS */
2810 static wchar_t *uni_buf = 0;
2811 static int uni_len = 0;
2813 if (len > uni_len) {
2815 uni_buf = smalloc((uni_len = len) * sizeof(wchar_t));
2818 for(nlen = mptr = 0; mptr<len; mptr++) {
2819 uni_buf[nlen] = 0xFFFD;
2820 if (IsDBCSLeadByteEx(font_codepage, (BYTE) text[mptr])) {
2821 IpDx[nlen] += char_width;
2822 MultiByteToWideChar(font_codepage, MB_USEGLYPHCHARS,
2823 text+mptr, 2, uni_buf+nlen, 1);
2828 MultiByteToWideChar(font_codepage, MB_USEGLYPHCHARS,
2829 text+mptr, 1, uni_buf+nlen, 1);
2837 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2838 ETO_CLIPPED | ETO_OPAQUE, &line_box, uni_buf, nlen, IpDx);
2839 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2840 SetBkMode(hdc, TRANSPARENT);
2841 ExtTextOutW(hdc, x - 1,
2842 y - font_height * (lattr ==
2843 LATTR_BOT) + text_adjust,
2844 ETO_CLIPPED, &line_box, uni_buf, nlen, IpDx);
2848 } else if (DIRECT_FONT(attr)) {
2850 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2851 ETO_CLIPPED | ETO_OPAQUE, &line_box, text, len, IpDx);
2852 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2853 SetBkMode(hdc, TRANSPARENT);
2855 /* GRR: This draws the character outside it's box and can leave
2856 * 'droppings' even with the clip box! I suppose I could loop it
2857 * one character at a time ... yuk.
2859 * Or ... I could do a test print with "W", and use +1 or -1 for this
2860 * shift depending on if the leftmost column is blank...
2862 ExtTextOut(hdc, x - 1,
2863 y - font_height * (lattr ==
2864 LATTR_BOT) + text_adjust,
2865 ETO_CLIPPED, &line_box, text, len, IpDx);
2868 /* And 'normal' unicode characters */
2869 static WCHAR *wbuf = NULL;
2870 static int wlen = 0;
2875 wbuf = smalloc(wlen * sizeof(WCHAR));
2877 for (i = 0; i < len; i++)
2878 wbuf[i] = (WCHAR) ((attr & CSET_MASK) + (text[i] & CHAR_MASK));
2881 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2882 ETO_CLIPPED | ETO_OPAQUE, &line_box, wbuf, len, IpDx);
2884 /* And the shadow bold hack. */
2885 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2886 SetBkMode(hdc, TRANSPARENT);
2887 ExtTextOutW(hdc, x - 1,
2888 y - font_height * (lattr ==
2889 LATTR_BOT) + text_adjust,
2890 ETO_CLIPPED, &line_box, wbuf, len, IpDx);
2893 if (lattr != LATTR_TOP && (force_manual_underline ||
2894 (und_mode == UND_LINE
2895 && (attr & ATTR_UNDER)))) {
2898 if (lattr == LATTR_BOT)
2899 dec = dec * 2 - font_height;
2901 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, fg));
2902 MoveToEx(hdc, x, y + dec, NULL);
2903 LineTo(hdc, x + len * char_width, y + dec);
2904 oldpen = SelectObject(hdc, oldpen);
2905 DeleteObject(oldpen);
2909 void do_cursor(Context ctx, int x, int y, char *text, int len,
2910 unsigned long attr, int lattr)
2916 int ctype = cfg.cursor_type;
2918 if ((attr & TATTR_ACTCURS) && (ctype == 0 || term->big_cursor)) {
2919 if (((attr & CSET_MASK) | (unsigned char) *text) != UCSWIDE) {
2920 do_text(ctx, x, y, text, len, attr, lattr);
2924 attr |= TATTR_RIGHTCURS;
2927 fnt_width = char_width = font_width * (1 + (lattr != LATTR_NORM));
2928 if (attr & ATTR_WIDE)
2935 if ((attr & TATTR_PASCURS) && (ctype == 0 || term->big_cursor)) {
2938 pts[0].x = pts[1].x = pts[4].x = x;
2939 pts[2].x = pts[3].x = x + char_width - 1;
2940 pts[0].y = pts[3].y = pts[4].y = y;
2941 pts[1].y = pts[2].y = y + font_height - 1;
2942 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2943 Polyline(hdc, pts, 5);
2944 oldpen = SelectObject(hdc, oldpen);
2945 DeleteObject(oldpen);
2946 } else if ((attr & (TATTR_ACTCURS | TATTR_PASCURS)) && ctype != 0) {
2947 int startx, starty, dx, dy, length, i;
2950 starty = y + descent;
2953 length = char_width;
2956 if (attr & TATTR_RIGHTCURS)
2957 xadjust = char_width - 1;
2958 startx = x + xadjust;
2962 length = font_height;
2964 if (attr & TATTR_ACTCURS) {
2967 SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2968 MoveToEx(hdc, startx, starty, NULL);
2969 LineTo(hdc, startx + dx * length, starty + dy * length);
2970 oldpen = SelectObject(hdc, oldpen);
2971 DeleteObject(oldpen);
2973 for (i = 0; i < length; i++) {
2975 SetPixel(hdc, startx, starty, colours[23]);
2984 /* This function gets the actual width of a character in the normal font.
2986 int CharWidth(Context ctx, int uc) {
2990 /* If the font max is the same as the font ave width then this
2991 * function is a no-op.
2993 if (!font_dualwidth) return 1;
2995 switch (uc & CSET_MASK) {
2997 uc = unitab_line[uc & 0xFF];
3000 uc = unitab_xterm[uc & 0xFF];
3003 uc = unitab_scoacs[uc & 0xFF];
3006 if (DIRECT_FONT(uc)) {
3007 if (dbcs_screenfont) return 1;
3009 /* Speedup, I know of no font where ascii is the wrong width */
3010 if ((uc&CHAR_MASK) >= ' ' && (uc&CHAR_MASK)<= '~')
3013 if ( (uc & CSET_MASK) == ATTR_ACP ) {
3014 SelectObject(hdc, fonts[FONT_NORMAL]);
3015 } else if ( (uc & CSET_MASK) == ATTR_OEMCP ) {
3016 another_font(FONT_OEM);
3017 if (!fonts[FONT_OEM]) return 0;
3019 SelectObject(hdc, fonts[FONT_OEM]);
3023 if ( GetCharWidth32(hdc, uc&CHAR_MASK, uc&CHAR_MASK, &ibuf) != 1 &&
3024 GetCharWidth(hdc, uc&CHAR_MASK, uc&CHAR_MASK, &ibuf) != 1)
3027 /* Speedup, I know of no font where ascii is the wrong width */
3028 if (uc >= ' ' && uc <= '~') return 1;
3030 SelectObject(hdc, fonts[FONT_NORMAL]);
3031 if ( GetCharWidth32W(hdc, uc, uc, &ibuf) == 1 )
3032 /* Okay that one worked */ ;
3033 else if ( GetCharWidthW(hdc, uc, uc, &ibuf) == 1 )
3034 /* This should work on 9x too, but it's "less accurate" */ ;
3039 ibuf += font_width / 2 -1;
3046 * Translate a WM_(SYS)?KEY(UP|DOWN) message into a string of ASCII
3047 * codes. Returns number of bytes used or zero to drop the message
3048 * or -1 to forward the message to windows.
3050 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
3051 unsigned char *output)
3054 int scan, left_alt = 0, key_down, shift_state;
3056 unsigned char *p = output;
3057 static int alt_sum = 0;
3059 HKL kbd_layout = GetKeyboardLayout(0);
3061 static WORD keys[3];
3062 static int compose_char = 0;
3063 static WPARAM compose_key = 0;
3065 r = GetKeyboardState(keystate);
3067 memset(keystate, 0, sizeof(keystate));
3070 #define SHOW_TOASCII_RESULT
3071 { /* Tell us all about key events */
3072 static BYTE oldstate[256];
3073 static int first = 1;
3077 memcpy(oldstate, keystate, sizeof(oldstate));
3080 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT) {
3082 } else if ((HIWORD(lParam) & KF_UP)
3083 && scan == (HIWORD(lParam) & 0xFF)) {
3087 if (wParam >= VK_F1 && wParam <= VK_F20)
3088 debug(("K_F%d", wParam + 1 - VK_F1));
3101 debug(("VK_%02x", wParam));
3103 if (message == WM_SYSKEYDOWN || message == WM_SYSKEYUP)
3105 debug((", S%02x", scan = (HIWORD(lParam) & 0xFF)));
3107 ch = MapVirtualKeyEx(wParam, 2, kbd_layout);
3108 if (ch >= ' ' && ch <= '~')
3109 debug((", '%c'", ch));
3111 debug((", $%02x", ch));
3114 debug((", KB0=%02x", keys[0]));
3116 debug((", KB1=%02x", keys[1]));
3118 debug((", KB2=%02x", keys[2]));
3120 if ((keystate[VK_SHIFT] & 0x80) != 0)
3122 if ((keystate[VK_CONTROL] & 0x80) != 0)
3124 if ((HIWORD(lParam) & KF_EXTENDED))
3126 if ((HIWORD(lParam) & KF_UP))
3130 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT);
3131 else if ((HIWORD(lParam) & KF_UP))
3132 oldstate[wParam & 0xFF] ^= 0x80;
3134 oldstate[wParam & 0xFF] ^= 0x81;
3136 for (ch = 0; ch < 256; ch++)
3137 if (oldstate[ch] != keystate[ch])
3138 debug((", M%02x=%02x", ch, keystate[ch]));
3140 memcpy(oldstate, keystate, sizeof(oldstate));
3144 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED)) {
3145 keystate[VK_RMENU] = keystate[VK_MENU];
3149 /* Nastyness with NUMLock - Shift-NUMLock is left alone though */
3150 if ((cfg.funky_type == 3 ||
3151 (cfg.funky_type <= 1 && term->app_keypad_keys &&
3153 && wParam == VK_NUMLOCK && !(keystate[VK_SHIFT] & 0x80)) {
3155 wParam = VK_EXECUTE;
3157 /* UnToggle NUMLock */
3158 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0)
3159 keystate[VK_NUMLOCK] ^= 1;
3162 /* And write back the 'adjusted' state */
3163 SetKeyboardState(keystate);
3166 /* Disable Auto repeat if required */
3167 if (term->repeat_off &&
3168 (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT)
3171 if ((HIWORD(lParam) & KF_ALTDOWN) && (keystate[VK_RMENU] & 0x80) == 0)
3174 key_down = ((HIWORD(lParam) & KF_UP) == 0);
3176 /* Make sure Ctrl-ALT is not the same as AltGr for ToAscii unless told. */
3177 if (left_alt && (keystate[VK_CONTROL] & 0x80)) {
3178 if (cfg.ctrlaltkeys)
3179 keystate[VK_MENU] = 0;
3181 keystate[VK_RMENU] = 0x80;
3186 alt_pressed = (left_alt && key_down);
3188 scan = (HIWORD(lParam) & (KF_UP | KF_EXTENDED | 0xFF));
3189 shift_state = ((keystate[VK_SHIFT] & 0x80) != 0)
3190 + ((keystate[VK_CONTROL] & 0x80) != 0) * 2;
3192 /* Note if AltGr was pressed and if it was used as a compose key */
3193 if (!compose_state) {
3194 compose_key = 0x100;
3195 if (cfg.compose_key) {
3196 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED))
3197 compose_key = wParam;
3199 if (wParam == VK_APPS)
3200 compose_key = wParam;
3203 if (wParam == compose_key) {
3204 if (compose_state == 0
3205 && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0) compose_state =
3207 else if (compose_state == 1 && (HIWORD(lParam) & KF_UP))
3211 } else if (compose_state == 1 && wParam != VK_CONTROL)
3214 if (compose_state > 1 && left_alt)
3217 /* Sanitize the number pad if not using a PC NumPad */
3218 if (left_alt || (term->app_keypad_keys && !cfg.no_applic_k
3219 && cfg.funky_type != 2)
3220 || cfg.funky_type == 3 || cfg.nethack_keypad || compose_state) {
3221 if ((HIWORD(lParam) & KF_EXTENDED) == 0) {
3225 nParam = VK_NUMPAD0;
3228 nParam = VK_NUMPAD1;
3231 nParam = VK_NUMPAD2;
3234 nParam = VK_NUMPAD3;
3237 nParam = VK_NUMPAD4;
3240 nParam = VK_NUMPAD5;
3243 nParam = VK_NUMPAD6;
3246 nParam = VK_NUMPAD7;
3249 nParam = VK_NUMPAD8;
3252 nParam = VK_NUMPAD9;
3255 nParam = VK_DECIMAL;
3259 if (keystate[VK_NUMLOCK] & 1)
3266 /* If a key is pressed and AltGr is not active */
3267 if (key_down && (keystate[VK_RMENU] & 0x80) == 0 && !compose_state) {
3268 /* Okay, prepare for most alts then ... */
3272 /* Lets see if it's a pattern we know all about ... */
3273 if (wParam == VK_PRIOR && shift_state == 1) {
3274 SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0);
3277 if (wParam == VK_NEXT && shift_state == 1) {
3278 SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
3281 if (wParam == VK_INSERT && shift_state == 1) {
3282 term_do_paste(term);
3285 if (left_alt && wParam == VK_F4 && cfg.alt_f4) {
3288 if (left_alt && wParam == VK_SPACE && cfg.alt_space) {
3289 SendMessage(hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0);
3292 if (left_alt && wParam == VK_RETURN && cfg.fullscreenonaltenter &&
3293 (cfg.resize_action != RESIZE_DISABLED)) {
3294 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) != KF_REPEAT)
3298 /* Control-Numlock for app-keypad mode switch */
3299 if (wParam == VK_PAUSE && shift_state == 2) {
3300 term->app_keypad_keys ^= 1;
3304 /* Nethack keypad */
3305 if (cfg.nethack_keypad && !left_alt) {
3308 *p++ = shift_state ? 'B' : 'b';
3311 *p++ = shift_state ? 'J' : 'j';
3314 *p++ = shift_state ? 'N' : 'n';
3317 *p++ = shift_state ? 'H' : 'h';
3320 *p++ = shift_state ? '.' : '.';
3323 *p++ = shift_state ? 'L' : 'l';
3326 *p++ = shift_state ? 'Y' : 'y';
3329 *p++ = shift_state ? 'K' : 'k';
3332 *p++ = shift_state ? 'U' : 'u';
3337 /* Application Keypad */
3341 if (cfg.funky_type == 3 ||
3342 (cfg.funky_type <= 1 &&
3343 term->app_keypad_keys && !cfg.no_applic_k)) switch (wParam) {
3357 if (term->app_keypad_keys && !cfg.no_applic_k)
3394 if (cfg.funky_type == 2) {
3399 } else if (shift_state)
3406 if (cfg.funky_type == 2)
3410 if (cfg.funky_type == 2)
3414 if (cfg.funky_type == 2)
3419 if (HIWORD(lParam) & KF_EXTENDED)
3424 if (term->vt52_mode) {
3425 if (xkey >= 'P' && xkey <= 'S')
3426 p += sprintf((char *) p, "\x1B%c", xkey);
3428 p += sprintf((char *) p, "\x1B?%c", xkey);
3430 p += sprintf((char *) p, "\x1BO%c", xkey);
3435 if (wParam == VK_BACK && shift_state == 0) { /* Backspace */
3436 *p++ = (cfg.bksp_is_delete ? 0x7F : 0x08);
3440 if (wParam == VK_BACK && shift_state == 1) { /* Shift Backspace */
3441 /* We do the opposite of what is configured */
3442 *p++ = (cfg.bksp_is_delete ? 0x08 : 0x7F);
3446 if (wParam == VK_TAB && shift_state == 1) { /* Shift tab */
3452 if (wParam == VK_SPACE && shift_state == 2) { /* Ctrl-Space */
3456 if (wParam == VK_SPACE && shift_state == 3) { /* Ctrl-Shift-Space */
3460 if (wParam == VK_CANCEL && shift_state == 2) { /* Ctrl-Break */
3465 if (wParam == VK_PAUSE) { /* Break/Pause */
3470 /* Control-2 to Control-8 are special */
3471 if (shift_state == 2 && wParam >= '2' && wParam <= '8') {
3472 *p++ = "\000\033\034\035\036\037\177"[wParam - '2'];
3475 if (shift_state == 2 && wParam == 0xBD) {
3479 if (shift_state == 2 && wParam == 0xDF) {
3483 if (shift_state == 0 && wParam == VK_RETURN && term->cr_lf_return) {
3490 * Next, all the keys that do tilde codes. (ESC '[' nn '~',
3491 * for integer decimal nn.)
3493 * We also deal with the weird ones here. Linux VCs replace F1
3494 * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
3495 * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
3501 code = (keystate[VK_SHIFT] & 0x80 ? 23 : 11);
3504 code = (keystate[VK_SHIFT] & 0x80 ? 24 : 12);
3507 code = (keystate[VK_SHIFT] & 0x80 ? 25 : 13);
3510 code = (keystate[VK_SHIFT] & 0x80 ? 26 : 14);
3513 code = (keystate[VK_SHIFT] & 0x80 ? 28 : 15);
3516 code = (keystate[VK_SHIFT] & 0x80 ? 29 : 17);
3519 code = (keystate[VK_SHIFT] & 0x80 ? 31 : 18);
3522 code = (keystate[VK_SHIFT] & 0x80 ? 32 : 19);
3525 code = (keystate[VK_SHIFT] & 0x80 ? 33 : 20);
3528 code = (keystate[VK_SHIFT] & 0x80 ? 34 : 21);
3561 if ((shift_state&2) == 0) switch (wParam) {
3581 /* Reorder edit keys to physical order */
3582 if (cfg.funky_type == 3 && code <= 6)
3583 code = "\0\2\1\4\5\3\6"[code];
3585 if (term->vt52_mode && code > 0 && code <= 6) {
3586 p += sprintf((char *) p, "\x1B%c", " HLMEIG"[code]);
3590 if (cfg.funky_type == 5 && /* SCO function keys */
3591 code >= 11 && code <= 34) {
3592 char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
3595 case VK_F1: index = 0; break;
3596 case VK_F2: index = 1; break;
3597 case VK_F3: index = 2; break;
3598 case VK_F4: index = 3; break;
3599 case VK_F5: index = 4; break;
3600 case VK_F6: index = 5; break;
3601 case VK_F7: index = 6; break;
3602 case VK_F8: index = 7; break;
3603 case VK_F9: index = 8; break;
3604 case VK_F10: index = 9; break;
3605 case VK_F11: index = 10; break;
3606 case VK_F12: index = 11; break;
3608 if (keystate[VK_SHIFT] & 0x80) index += 12;
3609 if (keystate[VK_CONTROL] & 0x80) index += 24;
3610 p += sprintf((char *) p, "\x1B[%c", codes[index]);
3613 if (cfg.funky_type == 5 && /* SCO small keypad */
3614 code >= 1 && code <= 6) {
3615 char codes[] = "HL.FIG";
3619 p += sprintf((char *) p, "\x1B[%c", codes[code-1]);
3623 if ((term->vt52_mode || cfg.funky_type == 4) && code >= 11 && code <= 24) {
3629 if (term->vt52_mode)
3630 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11 - offt);
3633 sprintf((char *) p, "\x1BO%c", code + 'P' - 11 - offt);
3636 if (cfg.funky_type == 1 && code >= 11 && code <= 15) {
3637 p += sprintf((char *) p, "\x1B[[%c", code + 'A' - 11);
3640 if (cfg.funky_type == 2 && code >= 11 && code <= 14) {
3641 if (term->vt52_mode)
3642 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11);
3644 p += sprintf((char *) p, "\x1BO%c", code + 'P' - 11);
3647 if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
3648 p += sprintf((char *) p, code == 1 ? "\x1B[H" : "\x1BOw");
3652 p += sprintf((char *) p, "\x1B[%d~", code);
3657 * Now the remaining keys (arrows and Keypad 5. Keypad 5 for
3658 * some reason seems to send VK_CLEAR to Windows...).
3680 if (term->vt52_mode)
3681 p += sprintf((char *) p, "\x1B%c", xkey);
3683 int app_flg = (term->app_cursor_keys && !cfg.no_applic_c);
3686 * RDB: VT100 & VT102 manuals both state the
3687 * app cursor keys only work if the app keypad
3690 * SGT: That may well be true, but xterm
3691 * disagrees and so does at least one
3692 * application, so I've #if'ed this out and the
3693 * behaviour is back to PuTTY's original: app
3694 * cursor and app keypad are independently
3695 * switchable modes. If anyone complains about
3696 * _this_ I'll have to put in a configurable
3699 if (!term->app_keypad_keys)
3702 /* Useful mapping of Ctrl-arrows */
3703 if (shift_state == 2)
3707 p += sprintf((char *) p, "\x1BO%c", xkey);
3709 p += sprintf((char *) p, "\x1B[%c", xkey);
3716 * Finally, deal with Return ourselves. (Win95 seems to
3717 * foul it up when Alt is pressed, for some reason.)
3719 if (wParam == VK_RETURN) { /* Return */
3725 if (left_alt && wParam >= VK_NUMPAD0 && wParam <= VK_NUMPAD9)
3726 alt_sum = alt_sum * 10 + wParam - VK_NUMPAD0;
3731 /* Okay we've done everything interesting; let windows deal with
3732 * the boring stuff */
3736 /* helg: clear CAPS LOCK state if caps lock switches to cyrillic */
3737 if(cfg.xlat_capslockcyr && keystate[VK_CAPITAL] != 0) {
3739 keystate[VK_CAPITAL] = 0;
3742 r = ToAsciiEx(wParam, scan, keystate, keys, 0, kbd_layout);
3743 #ifdef SHOW_TOASCII_RESULT
3744 if (r == 1 && !key_down) {
3746 if (in_utf(term) || dbcs_screenfont)
3747 debug((", (U+%04x)", alt_sum));
3749 debug((", LCH(%d)", alt_sum));
3751 debug((", ACH(%d)", keys[0]));
3756 for (r1 = 0; r1 < r; r1++) {
3757 debug(("%s%d", r1 ? "," : "", keys[r1]));
3766 * Interrupt an ongoing paste. I'm not sure this is
3767 * sensible, but for the moment it's preferable to
3768 * having to faff about buffering things.
3773 for (i = 0; i < r; i++) {
3774 unsigned char ch = (unsigned char) keys[i];
3776 if (compose_state == 2 && (ch & 0x80) == 0 && ch > ' ') {
3781 if (compose_state == 3 && (ch & 0x80) == 0 && ch > ' ') {
3785 if ((nc = check_compose(compose_char, ch)) == -1) {
3786 MessageBeep(MB_ICONHAND);
3790 term_seen_key_event(term);
3791 luni_send(ldisc, &keybuf, 1, 1);
3799 if (in_utf(term) || dbcs_screenfont) {
3801 term_seen_key_event(term);
3802 luni_send(ldisc, &keybuf, 1, 1);
3804 ch = (char) alt_sum;
3806 * We need not bother about stdin
3807 * backlogs here, because in GUI PuTTY
3808 * we can't do anything about it
3809 * anyway; there's no means of asking
3810 * Windows to hold off on KEYDOWN
3811 * messages. We _have_ to buffer
3812 * everything we're sent.
3814 term_seen_key_event(term);
3815 ldisc_send(ldisc, &ch, 1, 1);
3819 term_seen_key_event(term);
3820 lpage_send(ldisc, kbd_codepage, &ch, 1, 1);
3822 if(capsOn && ch < 0x80) {
3825 cbuf[1] = xlat_uskbd2cyrllic(ch);
3826 term_seen_key_event(term);
3827 luni_send(ldisc, cbuf+!left_alt, 1+!!left_alt, 1);
3832 term_seen_key_event(term);
3833 lpage_send(ldisc, kbd_codepage,
3834 cbuf+!left_alt, 1+!!left_alt, 1);
3840 /* This is so the ALT-Numpad and dead keys work correctly. */
3845 /* If we're definitly not building up an ALT-54321 then clear it */
3848 /* If we will be using alt_sum fix the 256s */
3849 else if (keys[0] && (in_utf(term) || dbcs_screenfont))
3854 * ALT alone may or may not want to bring up the System menu.
3855 * If it's not meant to, we return 0 on presses or releases of
3856 * ALT, to show that we've swallowed the keystroke. Otherwise
3857 * we return -1, which means Windows will give the keystroke
3858 * its default handling (i.e. bring up the System menu).
3860 if (wParam == VK_MENU && !cfg.alt_only)
3866 void request_paste(void)
3869 * In Windows, pasting is synchronous: we can read the
3870 * clipboard with no difficulty, so request_paste() can just go
3873 term_do_paste(term);
3876 void set_title(char *title)
3879 window_name = smalloc(1 + strlen(title));
3880 strcpy(window_name, title);
3881 if (cfg.win_name_always || !IsIconic(hwnd))
3882 SetWindowText(hwnd, title);
3885 void set_icon(char *title)
3888 icon_name = smalloc(1 + strlen(title));
3889 strcpy(icon_name, title);
3890 if (!cfg.win_name_always && IsIconic(hwnd))
3891 SetWindowText(hwnd, title);
3894 void set_sbar(int total, int start, int page)
3898 if (is_full_screen() ? !cfg.scrollbar_in_fullscreen : !cfg.scrollbar)
3901 si.cbSize = sizeof(si);
3902 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
3904 si.nMax = total - 1;
3908 SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
3911 Context get_ctx(void)
3917 SelectPalette(hdc, pal, FALSE);
3923 void free_ctx(Context ctx)
3925 SelectPalette(ctx, GetStockObject(DEFAULT_PALETTE), FALSE);
3926 ReleaseDC(hwnd, ctx);
3929 static void real_palette_set(int n, int r, int g, int b)
3932 logpal->palPalEntry[n].peRed = r;
3933 logpal->palPalEntry[n].peGreen = g;
3934 logpal->palPalEntry[n].peBlue = b;
3935 logpal->palPalEntry[n].peFlags = PC_NOCOLLAPSE;
3936 colours[n] = PALETTERGB(r, g, b);
3937 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3939 colours[n] = RGB(r, g, b);
3942 void palette_set(int n, int r, int g, int b)
3944 static const int first[21] = {
3945 0, 2, 4, 6, 8, 10, 12, 14,
3946 1, 3, 5, 7, 9, 11, 13, 15,
3949 real_palette_set(first[n], r, g, b);
3951 real_palette_set(first[n] + 1, r, g, b);
3953 HDC hdc = get_ctx();
3954 UnrealizeObject(pal);
3955 RealizePalette(hdc);
3960 void palette_reset(void)
3964 for (i = 0; i < NCOLOURS; i++) {
3966 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
3967 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
3968 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
3969 logpal->palPalEntry[i].peFlags = 0;
3970 colours[i] = PALETTERGB(defpal[i].rgbtRed,
3971 defpal[i].rgbtGreen,
3972 defpal[i].rgbtBlue);
3974 colours[i] = RGB(defpal[i].rgbtRed,
3975 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
3980 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3982 RealizePalette(hdc);
3987 void write_aclip(char *data, int len, int must_deselect)
3992 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len + 1);
3995 lock = GlobalLock(clipdata);
3998 memcpy(lock, data, len);
3999 ((unsigned char *) lock)[len] = 0;
4000 GlobalUnlock(clipdata);
4003 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
4005 if (OpenClipboard(hwnd)) {
4007 SetClipboardData(CF_TEXT, clipdata);
4010 GlobalFree(clipdata);
4013 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
4017 * Note: unlike write_aclip() this will not append a nul.
4019 void write_clip(wchar_t * data, int len, int must_deselect)
4021 HGLOBAL clipdata, clipdata2, clipdata3;
4023 void *lock, *lock2, *lock3;
4025 len2 = WideCharToMultiByte(CP_ACP, 0, data, len, 0, 0, NULL, NULL);
4027 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE,
4028 len * sizeof(wchar_t));
4029 clipdata2 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len2);
4031 if (!clipdata || !clipdata2) {
4033 GlobalFree(clipdata);
4035 GlobalFree(clipdata2);
4038 if (!(lock = GlobalLock(clipdata)))
4040 if (!(lock2 = GlobalLock(clipdata2)))
4043 memcpy(lock, data, len * sizeof(wchar_t));
4044 WideCharToMultiByte(CP_ACP, 0, data, len, lock2, len2, NULL, NULL);
4046 if (cfg.rtf_paste) {
4047 wchar_t unitab[256];
4049 unsigned char *tdata = (unsigned char *)lock2;
4050 wchar_t *udata = (wchar_t *)lock;
4051 int rtflen = 0, uindex = 0, tindex = 0;
4053 int multilen, blen, alen, totallen, i;
4054 char before[16], after[4];
4056 get_unitab(CP_ACP, unitab, 0);
4058 rtfsize = 100 + strlen(cfg.font);
4059 rtf = smalloc(rtfsize);
4060 sprintf(rtf, "{\\rtf1\\ansi%d{\\fonttbl\\f0\\fmodern %s;}\\f0",
4061 GetACP(), cfg.font);
4062 rtflen = strlen(rtf);
4065 * We want to construct a piece of RTF that specifies the
4066 * same Unicode text. To do this we will read back in
4067 * parallel from the Unicode data in `udata' and the
4068 * non-Unicode data in `tdata'. For each character in
4069 * `tdata' which becomes the right thing in `udata' when
4070 * looked up in `unitab', we just copy straight over from
4071 * tdata. For each one that doesn't, we must WCToMB it
4072 * individually and produce a \u escape sequence.
4074 * It would probably be more robust to just bite the bullet
4075 * and WCToMB each individual Unicode character one by one,
4076 * then MBToWC each one back to see if it was an accurate
4077 * translation; but that strikes me as a horrifying number
4078 * of Windows API calls so I want to see if this faster way
4079 * will work. If it screws up badly we can always revert to
4080 * the simple and slow way.
4082 while (tindex < len2 && uindex < len &&
4083 tdata[tindex] && udata[uindex]) {
4084 if (tindex + 1 < len2 &&
4085 tdata[tindex] == '\r' &&
4086 tdata[tindex+1] == '\n') {
4090 if (unitab[tdata[tindex]] == udata[uindex]) {
4096 multilen = WideCharToMultiByte(CP_ACP, 0, unitab+uindex, 1,
4097 NULL, 0, NULL, NULL);
4098 if (multilen != 1) {
4099 blen = sprintf(before, "{\\uc%d\\u%d", multilen,
4101 alen = 1; strcpy(after, "}");
4103 blen = sprintf(before, "\\u%d", udata[uindex]);
4104 alen = 0; after[0] = '\0';
4107 assert(tindex + multilen <= len2);
4108 totallen = blen + alen;
4109 for (i = 0; i < multilen; i++) {
4110 if (tdata[tindex+i] == '\\' ||
4111 tdata[tindex+i] == '{' ||
4112 tdata[tindex+i] == '}')
4114 else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A)
4115 totallen += 6; /* \par\r\n */
4116 else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20)
4122 if (rtfsize < rtflen + totallen + 3) {
4123 rtfsize = rtflen + totallen + 512;
4124 rtf = srealloc(rtf, rtfsize);
4127 strcpy(rtf + rtflen, before); rtflen += blen;
4128 for (i = 0; i < multilen; i++) {
4129 if (tdata[tindex+i] == '\\' ||
4130 tdata[tindex+i] == '{' ||
4131 tdata[tindex+i] == '}') {
4132 rtf[rtflen++] = '\\';
4133 rtf[rtflen++] = tdata[tindex+i];
4134 } else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A) {
4135 rtflen += sprintf(rtf+rtflen, "\\par\r\n");
4136 } else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20) {
4137 rtflen += sprintf(rtf+rtflen, "\\'%02x", tdata[tindex+i]);
4139 rtf[rtflen++] = tdata[tindex+i];
4142 strcpy(rtf + rtflen, after); rtflen += alen;
4148 strcpy(rtf + rtflen, "}");
4151 clipdata3 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, rtflen);
4152 if (clipdata3 && (lock3 = GlobalLock(clipdata3)) != NULL) {
4154 GlobalUnlock(clipdata3);
4160 GlobalUnlock(clipdata);
4161 GlobalUnlock(clipdata2);
4164 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
4166 if (OpenClipboard(hwnd)) {
4168 SetClipboardData(CF_UNICODETEXT, clipdata);
4169 SetClipboardData(CF_TEXT, clipdata2);
4171 SetClipboardData(RegisterClipboardFormat(CF_RTF), clipdata3);
4174 GlobalFree(clipdata);
4175 GlobalFree(clipdata2);
4179 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
4182 void get_clip(wchar_t ** p, int *len)
4184 static HGLOBAL clipdata = NULL;
4185 static wchar_t *converted = 0;
4194 GlobalUnlock(clipdata);
4197 } else if (OpenClipboard(NULL)) {
4198 if ((clipdata = GetClipboardData(CF_UNICODETEXT))) {
4200 *p = GlobalLock(clipdata);
4202 for (p2 = *p; *p2; p2++);
4206 } else if ( (clipdata = GetClipboardData(CF_TEXT)) ) {
4210 s = GlobalLock(clipdata);
4211 i = MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, 0, 0);
4212 *p = converted = smalloc(i * sizeof(wchar_t));
4213 MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, converted, i);
4226 * Move `lines' lines from position `from' to position `to' in the
4229 void optimised_move(int to, int from, int lines)
4234 min = (to < from ? to : from);
4235 max = to + from - min;
4237 r.left = offset_width;
4238 r.right = offset_width + term->cols * font_width;
4239 r.top = offset_height + min * font_height;
4240 r.bottom = offset_height + (max + lines) * font_height;
4241 ScrollWindow(hwnd, 0, (to - from) * font_height, &r, &r);
4246 * Print a message box and perform a fatal exit.
4248 void fatalbox(char *fmt, ...)
4254 vsprintf(stuff, fmt, ap);
4256 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
4261 * Print a modal (Really Bad) message box and perform a fatal exit.
4263 void modalfatalbox(char *fmt, ...)
4269 vsprintf(stuff, fmt, ap);
4271 MessageBox(hwnd, stuff, "PuTTY Fatal Error",
4272 MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
4277 * Manage window caption / taskbar flashing, if enabled.
4278 * 0 = stop, 1 = maintain, 2 = start
4280 static void flash_window(int mode)
4282 static long last_flash = 0;
4283 static int flashing = 0;
4284 if ((mode == 0) || (cfg.beep_ind == B_IND_DISABLED)) {
4287 FlashWindow(hwnd, FALSE);
4291 } else if (mode == 2) {
4294 last_flash = GetTickCount();
4296 FlashWindow(hwnd, TRUE);
4299 } else if ((mode == 1) && (cfg.beep_ind == B_IND_FLASH)) {
4302 long now = GetTickCount();
4303 long fdiff = now - last_flash;
4304 if (fdiff < 0 || fdiff > 450) {
4306 FlashWindow(hwnd, TRUE); /* toggle */
4317 if (mode == BELL_DEFAULT) {
4319 * For MessageBeep style bells, we want to be careful of
4320 * timing, because they don't have the nice property of
4321 * PlaySound bells that each one cancels the previous
4322 * active one. So we limit the rate to one per 50ms or so.
4324 static long lastbeep = 0;
4327 beepdiff = GetTickCount() - lastbeep;
4328 if (beepdiff >= 0 && beepdiff < 50)
4332 * The above MessageBeep call takes time, so we record the
4333 * time _after_ it finishes rather than before it starts.
4335 lastbeep = GetTickCount();
4336 } else if (mode == BELL_WAVEFILE) {
4337 if (!PlaySound(cfg.bell_wavefile, NULL, SND_ASYNC | SND_FILENAME)) {
4338 char buf[sizeof(cfg.bell_wavefile) + 80];
4339 sprintf(buf, "Unable to play sound file\n%s\n"
4340 "Using default sound instead", cfg.bell_wavefile);
4341 MessageBox(hwnd, buf, "PuTTY Sound Error",
4342 MB_OK | MB_ICONEXCLAMATION);
4343 cfg.beep = BELL_DEFAULT;
4346 /* Otherwise, either visual bell or disabled; do nothing here */
4347 if (!term->has_focus) {
4348 flash_window(2); /* start */
4353 * Minimise or restore the window in response to a server-side
4356 void set_iconic(int iconic)
4358 if (IsIconic(hwnd)) {
4360 ShowWindow(hwnd, SW_RESTORE);
4363 ShowWindow(hwnd, SW_MINIMIZE);
4368 * Move the window in response to a server-side request.
4370 void move_window(int x, int y)
4372 if (cfg.resize_action == RESIZE_DISABLED ||
4373 cfg.resize_action == RESIZE_FONT ||
4377 SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
4381 * Move the window to the top or bottom of the z-order in response
4382 * to a server-side request.
4384 void set_zorder(int top)
4386 if (cfg.alwaysontop)
4387 return; /* ignore */
4388 SetWindowPos(hwnd, top ? HWND_TOP : HWND_BOTTOM, 0, 0, 0, 0,
4389 SWP_NOMOVE | SWP_NOSIZE);
4393 * Refresh the window in response to a server-side request.
4395 void refresh_window(void)
4397 InvalidateRect(hwnd, NULL, TRUE);
4401 * Maximise or restore the window in response to a server-side
4404 void set_zoomed(int zoomed)
4406 if (IsZoomed(hwnd)) {
4408 ShowWindow(hwnd, SW_RESTORE);
4411 ShowWindow(hwnd, SW_MAXIMIZE);
4416 * Report whether the window is iconic, for terminal reports.
4420 return IsIconic(hwnd);
4424 * Report the window's position, for terminal reports.
4426 void get_window_pos(int *x, int *y)
4429 GetWindowRect(hwnd, &r);
4435 * Report the window's pixel size, for terminal reports.
4437 void get_window_pixels(int *x, int *y)
4440 GetWindowRect(hwnd, &r);
4441 *x = r.right - r.left;
4442 *y = r.bottom - r.top;
4446 * Return the window or icon title.
4448 char *get_window_title(int icon)
4450 return icon ? icon_name : window_name;
4454 * See if we're in full-screen mode.
4456 int is_full_screen()
4458 if (!IsZoomed(hwnd))
4460 if (GetWindowLong(hwnd, GWL_STYLE) & WS_CAPTION)
4465 /* Get the rect/size of a full screen window using the nearest available
4466 * monitor in multimon systems; default to something sensible if only
4467 * one monitor is present. */
4468 static int get_fullscreen_rect(RECT * ss)
4470 #ifdef MONITOR_DEFAULTTONEAREST
4473 mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
4474 mi.cbSize = sizeof(mi);
4475 GetMonitorInfo(mon, &mi);
4477 /* structure copy */
4481 /* could also use code like this:
4482 ss->left = ss->top = 0;
4483 ss->right = GetSystemMetrics(SM_CXSCREEN);
4484 ss->bottom = GetSystemMetrics(SM_CYSCREEN);
4486 return GetClientRect(GetDesktopWindow(), ss);
4492 * Go full-screen. This should only be called when we are already
4495 void make_full_screen()
4500 assert(IsZoomed(hwnd));
4502 if (is_full_screen())
4505 /* Remove the window furniture. */
4506 style = GetWindowLong(hwnd, GWL_STYLE);
4507 style &= ~(WS_CAPTION | WS_BORDER | WS_THICKFRAME);
4508 if (cfg.scrollbar_in_fullscreen)
4509 style |= WS_VSCROLL;
4511 style &= ~WS_VSCROLL;
4512 SetWindowLong(hwnd, GWL_STYLE, style);
4514 /* Resize ourselves to exactly cover the nearest monitor. */
4515 get_fullscreen_rect(&ss);
4516 SetWindowPos(hwnd, HWND_TOP, ss.left, ss.top,
4521 /* Tick the menu item in the System menu. */
4522 CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN,
4527 * Clear the full-screen attributes.
4529 void clear_full_screen()
4531 DWORD oldstyle, style;
4533 /* Reinstate the window furniture. */
4534 style = oldstyle = GetWindowLong(hwnd, GWL_STYLE);
4535 style |= WS_CAPTION | WS_BORDER;
4536 if (cfg.resize_action == RESIZE_DISABLED)
4537 style &= ~WS_THICKFRAME;
4539 style |= WS_THICKFRAME;
4541 style |= WS_VSCROLL;
4543 style &= ~WS_VSCROLL;
4544 if (style != oldstyle) {
4545 SetWindowLong(hwnd, GWL_STYLE, style);
4546 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
4547 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
4551 /* Untick the menu item in the System menu. */
4552 CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN,
4557 * Toggle full-screen mode.
4559 void flip_full_screen()
4561 if (is_full_screen()) {
4562 ShowWindow(hwnd, SW_RESTORE);
4563 } else if (IsZoomed(hwnd)) {
4566 SendMessage(hwnd, WM_FULLSCR_ON_MAX, 0, 0);
4567 ShowWindow(hwnd, SW_MAXIMIZE);
4571 void frontend_keypress(void *handle)
4574 * Keypress termination in non-Close-On-Exit mode is not
4575 * currently supported in PuTTY proper, because the window
4576 * always has a perfectly good Close button anyway. So we do