17 #define PUTTY_DO_GLOBALS /* actually _define_ globals */
23 #define IDM_SHOWLOG 0x0010
24 #define IDM_NEWSESS 0x0020
25 #define IDM_DUPSESS 0x0030
26 #define IDM_RECONF 0x0040
27 #define IDM_CLRSB 0x0050
28 #define IDM_RESET 0x0060
29 #define IDM_TEL_AYT 0x0070
30 #define IDM_TEL_BRK 0x0080
31 #define IDM_TEL_SYNCH 0x0090
32 #define IDM_TEL_EC 0x00a0
33 #define IDM_TEL_EL 0x00b0
34 #define IDM_TEL_GA 0x00c0
35 #define IDM_TEL_NOP 0x00d0
36 #define IDM_TEL_ABORT 0x00e0
37 #define IDM_TEL_AO 0x00f0
38 #define IDM_TEL_IP 0x0100
39 #define IDM_TEL_SUSP 0x0110
40 #define IDM_TEL_EOR 0x0120
41 #define IDM_TEL_EOF 0x0130
42 #define IDM_ABOUT 0x0140
43 #define IDM_SAVEDSESS 0x0150
44 #define IDM_COPYALL 0x0160
46 #define IDM_SESSLGP 0x0250 /* log type printable */
47 #define IDM_SESSLGA 0x0260 /* log type all chars */
48 #define IDM_SESSLGE 0x0270 /* log end */
49 #define IDM_SAVED_MIN 0x1000
50 #define IDM_SAVED_MAX 0x2000
52 #define WM_IGNORE_SIZE (WM_XUSER + 1)
53 #define WM_IGNORE_CLIP (WM_XUSER + 2)
55 /* Needed for Chinese support and apparently not always defined. */
57 #define VK_PROCESSKEY 0xE5
60 /* Needed for mouse wheel support and not defined in earlier SDKs. */
62 #define WM_MOUSEWHEEL 0x020A
65 static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
66 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
67 unsigned char *output);
68 static void cfgtopalette(void);
69 static void init_palette(void);
70 static void init_fonts(int);
71 static void another_font(int);
72 static void deinit_fonts(void);
74 static int extra_width, extra_height;
76 static int pending_netevent = 0;
77 static WPARAM pend_netevent_wParam = 0;
78 static LPARAM pend_netevent_lParam = 0;
79 static void enact_pending_netevent(void);
80 static void flash_window(int mode);
82 static time_t last_movement = 0;
86 #define FONT_UNDERLINE 2
87 #define FONT_BOLDUND 3
88 #define FONT_WIDE 0x04
89 #define FONT_HIGH 0x08
90 #define FONT_NARROW 0x10
92 #define FONT_OEMBOLD 0x21
93 #define FONT_OEMUND 0x22
94 #define FONT_OEMBOLDUND 0x23
95 #define FONT_MSGOTHIC 0x40
96 #define FONT_MINGLIU 0x60
97 #define FONT_GULIMCHE 0x80
98 #define FONT_MAXNO 0x8F
100 static HFONT fonts[FONT_MAXNO];
101 static int fontflag[FONT_MAXNO];
103 BOLD_COLOURS, BOLD_SHADOW, BOLD_FONT
111 static COLORREF colours[NCOLOURS];
113 static LPLOGPALETTE logpal;
114 static RGBTRIPLE defpal[NCOLOURS];
118 static HBITMAP caretbm;
120 static int dbltime, lasttime, lastact;
121 static Mouse_Button lastbtn;
123 /* this allows xterm-style mouse handling. */
124 static int send_raw_mouse = 0;
125 static int wheel_accumulator = 0;
127 static char *window_name, *icon_name;
129 static int compose_state = 0;
131 /* Dummy routine, only required in plink. */
132 void ldisc_update(int echo, int edit)
136 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
138 static char appname[] = "PuTTY";
143 int guess_width, guess_height;
146 flags = FLAG_VERBOSE | FLAG_INTERACTIVE;
148 winsock_ver = MAKEWORD(1, 1);
149 if (WSAStartup(winsock_ver, &wsadata)) {
150 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
151 MB_OK | MB_ICONEXCLAMATION);
154 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
155 MessageBox(NULL, "WinSock version is incompatible with 1.1",
156 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
160 /* WISHLIST: maybe allow config tweaking even if winsock not present? */
163 InitCommonControls();
165 /* Ensure a Maximize setting in Explorer doesn't maximise the
170 * Process the command line.
175 default_protocol = DEFAULT_PROTOCOL;
176 default_port = DEFAULT_PORT;
177 cfg.logtype = LGTYP_NONE;
179 do_defaults(NULL, &cfg);
182 while (*p && isspace(*p))
186 * Process command line options first. Yes, this can be
187 * done better, and it will be as soon as I have the
191 char *q = p + strcspn(p, " \t");
194 tolower(p[0]) == 's' &&
195 tolower(p[1]) == 's' && tolower(p[2]) == 'h') {
196 default_protocol = cfg.protocol = PROT_SSH;
197 default_port = cfg.port = 22;
198 } else if (q == p + 7 &&
199 tolower(p[0]) == 'c' &&
200 tolower(p[1]) == 'l' &&
201 tolower(p[2]) == 'e' &&
202 tolower(p[3]) == 'a' &&
203 tolower(p[4]) == 'n' &&
204 tolower(p[5]) == 'u' && tolower(p[6]) == 'p') {
206 * `putty -cleanup'. Remove all registry entries
207 * associated with PuTTY, and also find and delete
208 * the random seed file.
211 "This procedure will remove ALL Registry\n"
212 "entries associated with PuTTY, and will\n"
213 "also remove the PuTTY random seed file.\n"
215 "THIS PROCESS WILL DESTROY YOUR SAVED\n"
216 "SESSIONS. Are you really sure you want\n"
219 MB_YESNO | MB_ICONWARNING) == IDYES) {
224 p = q + strspn(q, " \t");
228 * An initial @ means to activate a saved session.
232 while (i > 1 && isspace(p[i - 1]))
235 do_defaults(p + 1, &cfg);
236 if (!*cfg.host && !do_config()) {
240 } else if (*p == '&') {
242 * An initial & means we've been given a command line
243 * containing the hex value of a HANDLE for a file
244 * mapping object, which we must then extract as a
249 if (sscanf(p + 1, "%p", &filemap) == 1 &&
250 (cp = MapViewOfFile(filemap, FILE_MAP_READ,
251 0, 0, sizeof(Config))) != NULL) {
254 CloseHandle(filemap);
255 } else if (!do_config()) {
262 * If the hostname starts with "telnet:", set the
263 * protocol to Telnet and process the string as a
266 if (!strncmp(q, "telnet:", 7)) {
270 if (q[0] == '/' && q[1] == '/')
272 cfg.protocol = PROT_TELNET;
274 while (*p && *p != ':' && *p != '/')
283 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
284 cfg.host[sizeof(cfg.host) - 1] = '\0';
286 while (*p && !isspace(*p))
290 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
291 cfg.host[sizeof(cfg.host) - 1] = '\0';
292 while (*p && isspace(*p))
307 * Trim leading whitespace off the hostname if it's there.
310 int space = strspn(cfg.host, " \t");
311 memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
314 /* See if host is of the form user@host */
315 if (cfg.host[0] != '\0') {
316 char *atsign = strchr(cfg.host, '@');
317 /* Make sure we're not overflowing the user field */
319 if (atsign - cfg.host < sizeof cfg.username) {
320 strncpy(cfg.username, cfg.host, atsign - cfg.host);
321 cfg.username[atsign - cfg.host] = '\0';
323 memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
328 * Trim a colon suffix off the hostname if it's there.
330 cfg.host[strcspn(cfg.host, ":")] = '\0';
334 * Select protocol. This is farmed out into a table in a
335 * separate file to enable an ssh-free variant.
340 for (i = 0; backends[i].backend != NULL; i++)
341 if (backends[i].protocol == cfg.protocol) {
342 back = backends[i].backend;
346 MessageBox(NULL, "Unsupported protocol number found",
347 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
353 /* Check for invalid Port number (i.e. zero) */
355 MessageBox(NULL, "Invalid Port Number",
356 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
363 wndclass.lpfnWndProc = WndProc;
364 wndclass.cbClsExtra = 0;
365 wndclass.cbWndExtra = 0;
366 wndclass.hInstance = inst;
367 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
368 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
369 wndclass.hbrBackground = GetStockObject(BLACK_BRUSH);
370 wndclass.lpszMenuName = NULL;
371 wndclass.lpszClassName = appname;
373 RegisterClass(&wndclass);
378 savelines = cfg.savelines;
384 * Guess some defaults for the window size. This all gets
385 * updated later, so we don't really care too much. However, we
386 * do want the font width/height guesses to correspond to a
387 * large font rather than a small one...
394 term_size(cfg.height, cfg.width, cfg.savelines);
395 guess_width = extra_width + font_width * cols;
396 guess_height = extra_height + font_height * rows;
399 HWND w = GetDesktopWindow();
400 GetWindowRect(w, &r);
401 if (guess_width > r.right - r.left)
402 guess_width = r.right - r.left;
403 if (guess_height > r.bottom - r.top)
404 guess_height = r.bottom - r.top;
408 int winmode = WS_OVERLAPPEDWINDOW | WS_VSCROLL;
411 winmode &= ~(WS_VSCROLL);
413 winmode &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
415 exwinmode |= WS_EX_TOPMOST;
417 exwinmode |= WS_EX_CLIENTEDGE;
418 hwnd = CreateWindowEx(exwinmode, appname, appname,
419 winmode, CW_USEDEFAULT, CW_USEDEFAULT,
420 guess_width, guess_height,
421 NULL, NULL, inst, NULL);
425 * Initialise the fonts, simultaneously correcting the guesses
426 * for font_{width,height}.
428 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
433 * Correct the guesses for extra_{width,height}.
437 GetWindowRect(hwnd, &wr);
438 GetClientRect(hwnd, &cr);
439 extra_width = wr.right - wr.left - cr.right + cr.left;
440 extra_height = wr.bottom - wr.top - cr.bottom + cr.top;
444 * Resize the window, now we know what size we _really_ want it
447 guess_width = extra_width + font_width * cols;
448 guess_height = extra_height + font_height * rows;
449 SendMessage(hwnd, WM_IGNORE_SIZE, 0, 0);
450 SetWindowPos(hwnd, NULL, 0, 0, guess_width, guess_height,
451 SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
454 * Set up a caret bitmap, with no content.
458 int size = (font_width + 15) / 16 * 2 * font_height;
459 bits = smalloc(size);
460 memset(bits, 0, size);
461 caretbm = CreateBitmap(font_width, font_height, 1, 1, bits);
464 CreateCaret(hwnd, caretbm, font_width, font_height);
467 * Initialise the scroll bar.
472 si.cbSize = sizeof(si);
473 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
478 SetScrollInfo(hwnd, SB_VERT, &si, FALSE);
482 * Start up the telnet connection.
486 char msg[1024], *title;
489 error = back->init(cfg.host, cfg.port, &realhost);
491 sprintf(msg, "Unable to open connection to\n"
492 "%.800s\n" "%s", cfg.host, error);
493 MessageBox(NULL, msg, "PuTTY Error", MB_ICONERROR | MB_OK);
496 window_name = icon_name = NULL;
498 title = cfg.wintitle;
500 sprintf(msg, "%s - PuTTY", realhost);
508 session_closed = FALSE;
511 * Set up the input and output buffers.
514 outbuf_reap = outbuf_head = 0;
517 * Prepare the mouse handler.
519 lastact = MA_NOTHING;
520 lastbtn = MBT_NOTHING;
521 dbltime = GetDoubleClickTime();
524 * Set up the session-control options on the system menu.
527 HMENU m = GetSystemMenu(hwnd, FALSE);
531 AppendMenu(m, MF_SEPARATOR, 0, 0);
532 if (cfg.protocol == PROT_TELNET) {
534 AppendMenu(p, MF_ENABLED, IDM_TEL_AYT, "Are You There");
535 AppendMenu(p, MF_ENABLED, IDM_TEL_BRK, "Break");
536 AppendMenu(p, MF_ENABLED, IDM_TEL_SYNCH, "Synch");
537 AppendMenu(p, MF_SEPARATOR, 0, 0);
538 AppendMenu(p, MF_ENABLED, IDM_TEL_EC, "Erase Character");
539 AppendMenu(p, MF_ENABLED, IDM_TEL_EL, "Erase Line");
540 AppendMenu(p, MF_ENABLED, IDM_TEL_GA, "Go Ahead");
541 AppendMenu(p, MF_ENABLED, IDM_TEL_NOP, "No Operation");
542 AppendMenu(p, MF_SEPARATOR, 0, 0);
543 AppendMenu(p, MF_ENABLED, IDM_TEL_ABORT, "Abort Process");
544 AppendMenu(p, MF_ENABLED, IDM_TEL_AO, "Abort Output");
545 AppendMenu(p, MF_ENABLED, IDM_TEL_IP, "Interrupt Process");
546 AppendMenu(p, MF_ENABLED, IDM_TEL_SUSP, "Suspend Process");
547 AppendMenu(p, MF_SEPARATOR, 0, 0);
548 AppendMenu(p, MF_ENABLED, IDM_TEL_EOR, "End Of Record");
549 AppendMenu(p, MF_ENABLED, IDM_TEL_EOF, "End Of File");
550 AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) p,
552 AppendMenu(m, MF_SEPARATOR, 0, 0);
554 AppendMenu(m, MF_ENABLED, IDM_SHOWLOG, "&Event Log");
555 AppendMenu(m, MF_SEPARATOR, 0, 0);
556 AppendMenu(m, MF_ENABLED, IDM_NEWSESS, "Ne&w Session...");
557 AppendMenu(m, MF_ENABLED, IDM_DUPSESS, "&Duplicate Session");
560 for (i = 1; i < ((nsessions < 256) ? nsessions : 256); i++)
561 AppendMenu(s, MF_ENABLED, IDM_SAVED_MIN + (16 * i),
563 AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) s, "Sa&ved Sessions");
564 AppendMenu(m, MF_ENABLED, IDM_RECONF, "Chan&ge Settings...");
565 AppendMenu(m, MF_SEPARATOR, 0, 0);
566 AppendMenu(m, MF_ENABLED, IDM_COPYALL, "C&opy All to Clipboard");
567 AppendMenu(m, MF_ENABLED, IDM_CLRSB, "C&lear Scrollback");
568 AppendMenu(m, MF_ENABLED, IDM_RESET, "Rese&t Terminal");
569 AppendMenu(m, MF_SEPARATOR, 0, 0);
570 AppendMenu(m, MF_ENABLED, IDM_ABOUT, "&About PuTTY");
574 * Finally show the window!
576 ShowWindow(hwnd, show);
577 SetForegroundWindow(hwnd);
580 * Open the initial log file if there is one.
585 * Set the palette up.
591 has_focus = (GetForegroundWindow() == hwnd);
594 if (GetMessage(&msg, NULL, 0, 0) == 1) {
595 int timer_id = 0, long_timer = 0;
597 while (msg.message != WM_QUIT) {
598 /* Sometimes DispatchMessage calls routines that use their own
599 * GetMessage loop, setup this timer so we get some control back.
601 * Also call term_update() from the timer so that if the host
602 * is sending data flat out we still do redraws.
604 if (timer_id && long_timer) {
605 KillTimer(hwnd, timer_id);
606 long_timer = timer_id = 0;
609 timer_id = SetTimer(hwnd, 1, 20, NULL);
610 if (!(IsWindow(logbox) && IsDialogMessage(logbox, &msg)))
611 DispatchMessage(&msg);
613 /* Make sure we blink everything that needs it. */
616 /* Send the paste buffer if there's anything to send */
619 /* If there's nothing new in the queue then we can do everything
620 * we've delayed, reading the socket, writing, and repainting
623 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
626 if (pending_netevent) {
627 enact_pending_netevent();
629 /* Force the cursor blink on */
632 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
636 /* Okay there is now nothing to do so we make sure the screen is
637 * completely up to date then tell windows to call us in a little
641 KillTimer(hwnd, timer_id);
650 flash_window(1); /* maintain */
653 /* Hmm, term_update didn't want to do an update too soon ... */
654 timer_id = SetTimer(hwnd, 1, 50, NULL);
656 timer_id = SetTimer(hwnd, 1, 500, NULL);
658 timer_id = SetTimer(hwnd, 1, 100, NULL);
661 /* There's no point rescanning everything in the message queue
662 * so we do an apparently unnecessary wait here
665 if (GetMessage(&msg, NULL, 0, 0) != 1)
679 if (cfg.protocol == PROT_SSH) {
690 * Set up, or shut down, an AsyncSelect. Called from winnet.c.
692 char *do_select(SOCKET skt, int startup)
697 events = FD_READ | FD_WRITE | FD_OOB | FD_CLOSE;
702 return "do_select(): internal error (hwnd==NULL)";
703 if (WSAAsyncSelect(skt, hwnd, msg, events) == SOCKET_ERROR) {
704 switch (WSAGetLastError()) {
706 return "Network is down";
708 return "WSAAsyncSelect(): unknown error";
715 * set or clear the "raw mouse message" mode
717 void set_raw_mouse_mode(int activate)
719 send_raw_mouse = activate;
720 SetCursor(LoadCursor(NULL, activate ? IDC_ARROW : IDC_IBEAM));
724 * Print a message box and close the connection.
726 void connection_fatal(char *fmt, ...)
732 vsprintf(stuff, fmt, ap);
734 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
735 if (cfg.close_on_exit == COE_ALWAYS)
738 session_closed = TRUE;
739 SetWindowText(hwnd, "PuTTY (inactive)");
744 * Actually do the job requested by a WM_NETEVENT
746 static void enact_pending_netevent(void)
748 static int reentering = 0;
749 extern int select_result(WPARAM, LPARAM);
753 return; /* don't unpend the pending */
755 pending_netevent = FALSE;
758 ret = select_result(pend_netevent_wParam, pend_netevent_lParam);
761 if (ret == 0 && !session_closed) {
762 /* Abnormal exits will already have set session_closed and taken
763 * appropriate action. */
764 if (cfg.close_on_exit == COE_ALWAYS ||
765 cfg.close_on_exit == COE_NORMAL) PostQuitMessage(0);
767 session_closed = TRUE;
768 SetWindowText(hwnd, "PuTTY (inactive)");
769 MessageBox(hwnd, "Connection closed by remote host",
770 "PuTTY", MB_OK | MB_ICONINFORMATION);
776 * Copy the colour palette from the configuration data into defpal.
777 * This is non-trivial because the colour indices are different.
779 static void cfgtopalette(void)
782 static const int ww[] = {
783 6, 7, 8, 9, 10, 11, 12, 13,
784 14, 15, 16, 17, 18, 19, 20, 21,
785 0, 1, 2, 3, 4, 4, 5, 5
788 for (i = 0; i < 24; i++) {
790 defpal[i].rgbtRed = cfg.colours[w][0];
791 defpal[i].rgbtGreen = cfg.colours[w][1];
792 defpal[i].rgbtBlue = cfg.colours[w][2];
797 * Set up the colour palette.
799 static void init_palette(void)
802 HDC hdc = GetDC(hwnd);
804 if (cfg.try_palette && GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) {
805 logpal = smalloc(sizeof(*logpal)
806 - sizeof(logpal->palPalEntry)
807 + NCOLOURS * sizeof(PALETTEENTRY));
808 logpal->palVersion = 0x300;
809 logpal->palNumEntries = NCOLOURS;
810 for (i = 0; i < NCOLOURS; i++) {
811 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
812 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
813 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
814 logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
816 pal = CreatePalette(logpal);
818 SelectPalette(hdc, pal, FALSE);
820 SelectPalette(hdc, GetStockObject(DEFAULT_PALETTE), FALSE);
823 ReleaseDC(hwnd, hdc);
826 for (i = 0; i < NCOLOURS; i++)
827 colours[i] = PALETTERGB(defpal[i].rgbtRed,
831 for (i = 0; i < NCOLOURS; i++)
832 colours[i] = RGB(defpal[i].rgbtRed,
833 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
837 * Initialise all the fonts we will need initially. There may be as many as
838 * three or as few as one. The other (poentially) twentyone fonts are done
839 * if/when they are needed.
843 * - check the font width and height, correcting our guesses if
846 * - verify that the bold font is the same width as the ordinary
847 * one, and engage shadow bolding if not.
849 * - verify that the underlined font is the same width as the
850 * ordinary one (manual underlining by means of line drawing can
851 * be done in a pinch).
853 static void init_fonts(int pick_width)
860 int fw_dontcare, fw_bold;
862 for (i = 0; i < FONT_MAXNO; i++)
865 if (cfg.fontisbold) {
866 fw_dontcare = FW_BOLD;
869 fw_dontcare = FW_DONTCARE;
875 font_height = cfg.fontheight;
876 if (font_height > 0) {
878 -MulDiv(font_height, GetDeviceCaps(hdc, LOGPIXELSY), 72);
880 font_width = pick_width;
883 fonts[i] = CreateFont (font_height, font_width, 0, 0, w, FALSE, u, FALSE, \
884 c, OUT_DEFAULT_PRECIS, \
885 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, \
886 FIXED_PITCH | FF_DONTCARE, cfg.font)
888 f(FONT_NORMAL, cfg.fontcharset, fw_dontcare, FALSE);
890 SelectObject(hdc, fonts[FONT_NORMAL]);
891 GetTextMetrics(hdc, &tm);
892 font_height = tm.tmHeight;
893 font_width = tm.tmAveCharWidth;
897 DWORD cset = tm.tmCharSet;
898 memset(&info, 0xFF, sizeof(info));
900 /* !!! Yes the next line is right */
901 if (cset == OEM_CHARSET)
902 font_codepage = GetOEMCP();
904 if (TranslateCharsetInfo
905 ((DWORD *) cset, &info, TCI_SRCCHARSET)) font_codepage =
910 GetCPInfo(font_codepage, &cpinfo);
911 dbcs_screenfont = (cpinfo.MaxCharSize > 1);
914 f(FONT_UNDERLINE, cfg.fontcharset, fw_dontcare, TRUE);
917 * Some fonts, e.g. 9-pt Courier, draw their underlines
918 * outside their character cell. We successfully prevent
919 * screen corruption by clipping the text output, but then
920 * we lose the underline completely. Here we try to work
921 * out whether this is such a font, and if it is, we set a
922 * flag that causes underlines to be drawn by hand.
924 * Having tried other more sophisticated approaches (such
925 * as examining the TEXTMETRIC structure or requesting the
926 * height of a string), I think we'll do this the brute
927 * force way: we create a small bitmap, draw an underlined
928 * space on it, and test to see whether any pixels are
929 * foreground-coloured. (Since we expect the underline to
930 * go all the way across the character cell, we only search
931 * down a single column of the bitmap, half way across.)
935 HBITMAP und_bm, und_oldbm;
939 und_dc = CreateCompatibleDC(hdc);
940 und_bm = CreateCompatibleBitmap(hdc, font_width, font_height);
941 und_oldbm = SelectObject(und_dc, und_bm);
942 SelectObject(und_dc, fonts[FONT_UNDERLINE]);
943 SetTextAlign(und_dc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
944 SetTextColor(und_dc, RGB(255, 255, 255));
945 SetBkColor(und_dc, RGB(0, 0, 0));
946 SetBkMode(und_dc, OPAQUE);
947 ExtTextOut(und_dc, 0, 0, ETO_OPAQUE, NULL, " ", 1, NULL);
949 for (i = 0; i < font_height; i++) {
950 c = GetPixel(und_dc, font_width / 2, i);
951 if (c != RGB(0, 0, 0))
954 SelectObject(und_dc, und_oldbm);
955 DeleteObject(und_bm);
959 DeleteObject(fonts[FONT_UNDERLINE]);
960 fonts[FONT_UNDERLINE] = 0;
964 if (bold_mode == BOLD_FONT) {
965 f(FONT_BOLD, cfg.fontcharset, fw_bold, FALSE);
969 descent = tm.tmAscent + 1;
970 if (descent >= font_height)
971 descent = font_height - 1;
973 for (i = 0; i < 3; i++) {
975 if (SelectObject(hdc, fonts[i]) && GetTextMetrics(hdc, &tm))
976 fontsize[i] = tm.tmAveCharWidth + 256 * tm.tmHeight;
983 ReleaseDC(hwnd, hdc);
985 if (fontsize[FONT_UNDERLINE] != fontsize[FONT_NORMAL]) {
987 DeleteObject(fonts[FONT_UNDERLINE]);
988 fonts[FONT_UNDERLINE] = 0;
991 if (bold_mode == BOLD_FONT &&
992 fontsize[FONT_BOLD] != fontsize[FONT_NORMAL]) {
993 bold_mode = BOLD_SHADOW;
994 DeleteObject(fonts[FONT_BOLD]);
995 fonts[FONT_BOLD] = 0;
997 fontflag[0] = fontflag[1] = fontflag[2] = 1;
1002 static void another_font(int fontno)
1005 int fw_dontcare, fw_bold;
1009 if (fontno < 0 || fontno >= FONT_MAXNO || fontflag[fontno])
1012 basefont = (fontno & ~(FONT_BOLDUND));
1013 if (basefont != fontno && !fontflag[basefont])
1014 another_font(basefont);
1016 if (cfg.fontisbold) {
1017 fw_dontcare = FW_BOLD;
1020 fw_dontcare = FW_DONTCARE;
1024 c = cfg.fontcharset;
1030 if (fontno & FONT_WIDE)
1032 if (fontno & FONT_NARROW)
1034 if (fontno & FONT_OEM)
1036 if (fontno & FONT_BOLD)
1038 if (fontno & FONT_UNDERLINE)
1042 CreateFont(font_height * (1 + !!(fontno & FONT_HIGH)), x, 0, 0, w,
1043 FALSE, u, FALSE, c, OUT_DEFAULT_PRECIS,
1044 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
1045 FIXED_PITCH | FF_DONTCARE, s);
1047 fontflag[fontno] = 1;
1050 static void deinit_fonts(void)
1053 for (i = 0; i < FONT_MAXNO; i++) {
1055 DeleteObject(fonts[i]);
1061 void request_resize(int w, int h, int refont)
1065 /* If the window is maximized supress resizing attempts */
1069 if (refont && w != cols && (cols == 80 || cols == 132)) {
1070 /* If font width too big for screen should we shrink the font more ? */
1072 font_width = ((font_width * cols + w / 2) / w);
1076 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
1077 und_mode = UND_FONT;
1078 init_fonts(font_width);
1080 static int first_time = 1;
1083 switch (first_time) {
1085 /* Get the size of the screen */
1086 if (GetClientRect(GetDesktopWindow(), &ss))
1087 /* first_time = 0 */ ;
1093 /* Make sure the values are sane */
1094 width = (ss.right - ss.left - extra_width) / font_width;
1095 height = (ss.bottom - ss.top - extra_height) / font_height;
1108 width = extra_width + font_width * w;
1109 height = extra_height + font_height * h;
1111 SetWindowPos(hwnd, NULL, 0, 0, width, height,
1112 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1113 SWP_NOMOVE | SWP_NOZORDER);
1116 static void click(Mouse_Button b, int x, int y, int shift, int ctrl)
1118 int thistime = GetMessageTime();
1120 if (send_raw_mouse) {
1121 term_mouse(b, MA_CLICK, x, y, shift, ctrl);
1125 if (lastbtn == b && thistime - lasttime < dbltime) {
1126 lastact = (lastact == MA_CLICK ? MA_2CLK :
1127 lastact == MA_2CLK ? MA_3CLK :
1128 lastact == MA_3CLK ? MA_CLICK : MA_NOTHING);
1133 if (lastact != MA_NOTHING)
1134 term_mouse(b, lastact, x, y, shift, ctrl);
1135 lasttime = thistime;
1139 * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT)
1140 * into a cooked one (SELECT, EXTEND, PASTE).
1142 Mouse_Button translate_button(Mouse_Button button)
1144 if (button == MBT_LEFT)
1146 if (button == MBT_MIDDLE)
1147 return cfg.mouse_is_xterm ? MBT_PASTE : MBT_EXTEND;
1148 if (button == MBT_RIGHT)
1149 return cfg.mouse_is_xterm ? MBT_EXTEND : MBT_PASTE;
1150 return 0; /* shouldn't happen */
1153 static void show_mouseptr(int show)
1155 static int cursor_visible = 1;
1156 if (!cfg.hide_mouseptr) /* override if this feature disabled */
1158 if (cursor_visible && !show)
1160 else if (!cursor_visible && show)
1162 cursor_visible = show;
1165 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1166 WPARAM wParam, LPARAM lParam)
1169 static int ignore_size = FALSE;
1170 static int ignore_clip = FALSE;
1171 static int just_reconfigged = FALSE;
1172 static int resizing = FALSE;
1173 static int need_backend_resize = FALSE;
1174 static int defered_resize = FALSE;
1178 if (pending_netevent)
1179 enact_pending_netevent();
1186 if (cfg.ping_interval > 0) {
1189 if (now - last_movement > cfg.ping_interval) {
1190 back->special(TS_PING);
1191 last_movement = now;
1199 if (!cfg.warn_on_close || session_closed ||
1201 "Are you sure you want to close this session?",
1202 "PuTTY Exit Confirmation",
1203 MB_ICONWARNING | MB_OKCANCEL) == IDOK)
1204 DestroyWindow(hwnd);
1211 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1223 PROCESS_INFORMATION pi;
1224 HANDLE filemap = NULL;
1226 if (wParam == IDM_DUPSESS) {
1228 * Allocate a file-mapping memory chunk for the
1231 SECURITY_ATTRIBUTES sa;
1234 sa.nLength = sizeof(sa);
1235 sa.lpSecurityDescriptor = NULL;
1236 sa.bInheritHandle = TRUE;
1237 filemap = CreateFileMapping((HANDLE) 0xFFFFFFFF,
1240 0, sizeof(Config), NULL);
1242 p = (Config *) MapViewOfFile(filemap,
1244 0, 0, sizeof(Config));
1246 *p = cfg; /* structure copy */
1250 sprintf(c, "putty &%p", filemap);
1252 } else if (wParam == IDM_SAVEDSESS) {
1254 sessions[(lParam - IDM_SAVED_MIN) / 16];
1255 cl = smalloc(16 + strlen(session)); /* 8, but play safe */
1257 cl = NULL; /* not a very important failure mode */
1259 sprintf(cl, "putty @%s", session);
1265 GetModuleFileName(NULL, b, sizeof(b) - 1);
1267 si.lpReserved = NULL;
1268 si.lpDesktop = NULL;
1272 si.lpReserved2 = NULL;
1273 CreateProcess(b, cl, NULL, NULL, TRUE,
1274 NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
1277 CloseHandle(filemap);
1284 int prev_alwaysontop = cfg.alwaysontop;
1285 int prev_sunken_edge = cfg.sunken_edge;
1286 char oldlogfile[FILENAME_MAX];
1288 int need_setwpos = FALSE;
1289 int old_fwidth, old_fheight;
1291 strcpy(oldlogfile, cfg.logfilename);
1292 oldlogtype = cfg.logtype;
1293 old_fwidth = font_width;
1294 old_fheight = font_height;
1295 GetWindowText(hwnd, cfg.wintitle, sizeof(cfg.wintitle));
1297 if (!do_reconfig(hwnd))
1300 if (strcmp(oldlogfile, cfg.logfilename) ||
1301 oldlogtype != cfg.logtype) {
1302 logfclose(); /* reset logging */
1306 just_reconfigged = TRUE;
1308 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
1309 und_mode = UND_FONT;
1313 * Flush the line discipline's edit buffer in the
1314 * case where local editing has just been disabled.
1316 ldisc_send(NULL, 0);
1324 /* Enable or disable the scroll bar, etc */
1326 LONG nflg, flag = GetWindowLong(hwnd, GWL_STYLE);
1327 LONG nexflag, exflag =
1328 GetWindowLong(hwnd, GWL_EXSTYLE);
1331 if (cfg.alwaysontop != prev_alwaysontop) {
1332 if (cfg.alwaysontop) {
1333 nexflag |= WS_EX_TOPMOST;
1334 SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
1335 SWP_NOMOVE | SWP_NOSIZE);
1337 nexflag &= ~(WS_EX_TOPMOST);
1338 SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0,
1339 SWP_NOMOVE | SWP_NOSIZE);
1342 if (cfg.sunken_edge)
1343 nexflag |= WS_EX_CLIENTEDGE;
1345 nexflag &= ~(WS_EX_CLIENTEDGE);
1351 nflg &= ~WS_VSCROLL;
1353 nflg &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
1355 nflg |= (WS_THICKFRAME | WS_MAXIMIZEBOX);
1357 if (nflg != flag || nexflag != exflag) {
1361 SetWindowLong(hwnd, GWL_STYLE, nflg);
1362 if (nexflag != exflag)
1363 SetWindowLong(hwnd, GWL_EXSTYLE, nexflag);
1365 SendMessage(hwnd, WM_IGNORE_SIZE, 0, 0);
1367 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
1368 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1369 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER
1370 | SWP_FRAMECHANGED);
1372 GetWindowRect(hwnd, &wr);
1373 GetClientRect(hwnd, &cr);
1375 wr.right - wr.left - cr.right + cr.left;
1377 wr.bottom - wr.top - cr.bottom + cr.top;
1378 need_setwpos = TRUE;
1382 if (cfg.height != rows ||
1383 cfg.width != cols ||
1384 old_fwidth != font_width ||
1385 old_fheight != font_height ||
1386 cfg.savelines != savelines ||
1387 cfg.sunken_edge != prev_sunken_edge)
1388 need_setwpos = TRUE;
1390 if (IsZoomed(hwnd)) {
1394 defered_resize = TRUE;
1396 GetClientRect(hwnd, &cr);
1397 w = cr.right - cr.left;
1398 h = cr.bottom - cr.top;
1402 h = h / font_height;
1406 term_size(h, w, cfg.savelines);
1407 InvalidateRect(hwnd, NULL, TRUE);
1410 term_size(cfg.height, cfg.width, cfg.savelines);
1411 InvalidateRect(hwnd, NULL, TRUE);
1413 SetWindowPos(hwnd, NULL, 0, 0,
1414 extra_width + font_width * cfg.width,
1416 font_height * cfg.height,
1417 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1418 SWP_NOMOVE | SWP_NOZORDER);
1422 if (cfg.locksize && IsZoomed(hwnd))
1424 set_title(cfg.wintitle);
1425 if (IsIconic(hwnd)) {
1427 cfg.win_name_always ? window_name :
1442 back->special(TS_AYT);
1445 back->special(TS_BRK);
1448 back->special(TS_SYNCH);
1451 back->special(TS_EC);
1454 back->special(TS_EL);
1457 back->special(TS_GA);
1460 back->special(TS_NOP);
1463 back->special(TS_ABORT);
1466 back->special(TS_AO);
1469 back->special(TS_IP);
1472 back->special(TS_SUSP);
1475 back->special(TS_EOR);
1478 back->special(TS_EOF);
1484 if (wParam >= IDM_SAVED_MIN && wParam <= IDM_SAVED_MAX) {
1485 SendMessage(hwnd, WM_SYSCOMMAND, IDM_SAVEDSESS, wParam);
1490 #define X_POS(l) ((int)(short)LOWORD(l))
1491 #define Y_POS(l) ((int)(short)HIWORD(l))
1493 #define TO_CHR_X(x) (((x)<0 ? (x)-font_width+1 : (x)) / font_width)
1494 #define TO_CHR_Y(y) (((y)<0 ? (y)-font_height+1: (y)) / font_height)
1495 #define WHEEL_DELTA 120
1498 wheel_accumulator += (short) HIWORD(wParam);
1499 wParam = LOWORD(wParam);
1501 /* process events when the threshold is reached */
1502 while (abs(wheel_accumulator) >= WHEEL_DELTA) {
1505 /* reduce amount for next time */
1506 if (wheel_accumulator > 0) {
1508 wheel_accumulator -= WHEEL_DELTA;
1509 } else if (wheel_accumulator < 0) {
1511 wheel_accumulator += WHEEL_DELTA;
1515 if (send_raw_mouse) {
1516 /* send a mouse-down followed by a mouse up */
1519 TO_CHR_X(X_POS(lParam)),
1520 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
1521 wParam & MK_CONTROL);
1522 term_mouse(b, MA_RELEASE, TO_CHR_X(X_POS(lParam)),
1523 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
1524 wParam & MK_CONTROL);
1526 /* trigger a scroll */
1528 b == MBT_WHEEL_UP ? -rows / 2 : rows / 2);
1533 case WM_LBUTTONDOWN:
1534 case WM_MBUTTONDOWN:
1535 case WM_RBUTTONDOWN:
1542 case WM_LBUTTONDOWN:
1546 case WM_MBUTTONDOWN:
1547 button = MBT_MIDDLE;
1550 case WM_RBUTTONDOWN:
1559 button = MBT_MIDDLE;
1567 button = press = 0; /* shouldn't happen */
1572 TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam)),
1573 wParam & MK_SHIFT, wParam & MK_CONTROL);
1576 term_mouse(button, MA_RELEASE,
1577 TO_CHR_X(X_POS(lParam)),
1578 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
1579 wParam & MK_CONTROL);
1587 * Add the mouse position and message time to the random
1590 noise_ultralight(lParam);
1592 if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)) {
1594 if (wParam & MK_LBUTTON)
1596 else if (wParam & MK_MBUTTON)
1600 term_mouse(b, MA_DRAG, TO_CHR_X(X_POS(lParam)),
1601 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
1602 wParam & MK_CONTROL);
1605 case WM_NCMOUSEMOVE:
1607 noise_ultralight(lParam);
1609 case WM_IGNORE_CLIP:
1610 ignore_clip = wParam; /* don't panic on DESTROYCLIPBOARD */
1612 case WM_DESTROYCLIPBOARD:
1615 ignore_clip = FALSE;
1621 hdc = BeginPaint(hwnd, &p);
1623 SelectPalette(hdc, pal, TRUE);
1624 RealizePalette(hdc);
1626 term_paint(hdc, p.rcPaint.left, p.rcPaint.top,
1627 p.rcPaint.right, p.rcPaint.bottom);
1628 SelectObject(hdc, GetStockObject(SYSTEM_FONT));
1629 SelectObject(hdc, GetStockObject(WHITE_PEN));
1635 /* Notice we can get multiple netevents, FD_READ, FD_WRITE etc
1636 * but the only one that's likely to try to overload us is FD_READ.
1637 * This means buffering just one is fine.
1639 if (pending_netevent)
1640 enact_pending_netevent();
1642 pending_netevent = TRUE;
1643 pend_netevent_wParam = wParam;
1644 pend_netevent_lParam = lParam;
1645 time(&last_movement);
1649 CreateCaret(hwnd, caretbm, font_width, font_height);
1651 flash_window(0); /* stop */
1663 case WM_IGNORE_SIZE:
1664 ignore_size = TRUE; /* don't panic on next WM_SIZE msg */
1666 case WM_ENTERSIZEMOVE:
1669 need_backend_resize = FALSE;
1671 case WM_EXITSIZEMOVE:
1674 if (need_backend_resize)
1679 int width, height, w, h, ew, eh;
1680 LPRECT r = (LPRECT) lParam;
1682 width = r->right - r->left - extra_width;
1683 height = r->bottom - r->top - extra_height;
1684 w = (width + font_width / 2) / font_width;
1687 h = (height + font_height / 2) / font_height;
1690 UpdateSizeTip(hwnd, w, h);
1691 ew = width - w * font_width;
1692 eh = height - h * font_height;
1694 if (wParam == WMSZ_LEFT ||
1695 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
1701 if (wParam == WMSZ_TOP ||
1702 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
1712 /* break; (never reached) */
1714 if (wParam == SIZE_MINIMIZED) {
1716 cfg.win_name_always ? window_name : icon_name);
1719 if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
1720 SetWindowText(hwnd, window_name);
1722 int width, height, w, h;
1723 #if 0 /* we have fixed this using WM_SIZING now */
1727 width = LOWORD(lParam);
1728 height = HIWORD(lParam);
1729 w = width / font_width;
1732 h = height / font_height;
1735 #if 0 /* we have fixed this using WM_SIZING now */
1736 ew = width - w * font_width;
1737 eh = height - h * font_height;
1738 if (ew != 0 || eh != 0) {
1740 GetWindowRect(hwnd, &r);
1741 SendMessage(hwnd, WM_IGNORE_SIZE, 0, 0);
1742 SetWindowPos(hwnd, NULL, 0, 0,
1743 r.right - r.left - ew, r.bottom - r.top - eh,
1744 SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER);
1747 if (w != cols || h != rows || just_reconfigged) {
1749 term_size(h, w, cfg.savelines);
1751 * Don't call back->size in mid-resize. (To prevent
1752 * massive numbers of resize events getting sent
1753 * down the connection during an NT opaque drag.)
1758 need_backend_resize = TRUE;
1762 just_reconfigged = FALSE;
1765 if (wParam == SIZE_RESTORED && defered_resize) {
1766 defered_resize = FALSE;
1767 SetWindowPos(hwnd, NULL, 0, 0,
1768 extra_width + font_width * cfg.width,
1769 extra_height + font_height * cfg.height,
1770 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1771 SWP_NOMOVE | SWP_NOZORDER);
1773 ignore_size = FALSE;
1776 switch (LOWORD(wParam)) {
1790 term_scroll(0, +rows / 2);
1793 term_scroll(0, -rows / 2);
1795 case SB_THUMBPOSITION:
1797 term_scroll(1, HIWORD(wParam));
1801 case WM_PALETTECHANGED:
1802 if ((HWND) wParam != hwnd && pal != NULL) {
1803 HDC hdc = get_ctx();
1805 if (RealizePalette(hdc) > 0)
1811 case WM_QUERYNEWPALETTE:
1813 HDC hdc = get_ctx();
1815 if (RealizePalette(hdc) > 0)
1827 * Add the scan code and keypress timing to the random
1830 noise_ultralight(lParam);
1833 * We don't do TranslateMessage since it disassociates the
1834 * resulting CHAR message from the KEYDOWN that sparked it,
1835 * which we occasionally don't want. Instead, we process
1836 * KEYDOWN, and call the Win32 translator functions so that
1837 * we get the translations under _our_ control.
1840 unsigned char buf[20];
1843 if (wParam == VK_PROCESSKEY) {
1846 m.message = WM_KEYDOWN;
1848 m.lParam = lParam & 0xdfff;
1849 TranslateMessage(&m);
1851 len = TranslateKey(message, wParam, lParam, buf);
1853 return DefWindowProc(hwnd, message, wParam, lParam);
1854 ldisc_send(buf, len);
1861 case WM_INPUTLANGCHANGE:
1863 /* wParam == Font number */
1864 /* lParam == Locale */
1866 HKL NewInputLocale = (HKL) lParam;
1868 // lParam == GetKeyboardLayout(0);
1870 GetLocaleInfo(LOWORD(NewInputLocale),
1871 LOCALE_IDEFAULTANSICODEPAGE, lbuf, sizeof(lbuf));
1873 kbd_codepage = atoi(lbuf);
1877 if (wParam & 0xFF00) {
1878 unsigned char buf[2];
1881 buf[0] = wParam >> 8;
1882 lpage_send(kbd_codepage, buf, 2);
1884 char c = (unsigned char) wParam;
1885 lpage_send(kbd_codepage, &c, 1);
1891 * Nevertheless, we are prepared to deal with WM_CHAR
1892 * messages, should they crop up. So if someone wants to
1893 * post the things to us as part of a macro manoeuvre,
1894 * we're ready to cope.
1897 char c = (unsigned char)wParam;
1898 lpage_send(CP_ACP, &c, 1);
1902 if (send_raw_mouse) {
1903 SetCursor(LoadCursor(NULL, IDC_ARROW));
1908 return DefWindowProc(hwnd, message, wParam, lParam);
1912 * Move the system caret. (We maintain one, even though it's
1913 * invisible, for the benefit of blind people: apparently some
1914 * helper software tracks the system caret, so we should arrange to
1917 void sys_cursor(int x, int y)
1920 SetCaretPos(x * font_width, y * font_height);
1924 * Draw a line of text in the window, at given character
1925 * coordinates, in given attributes.
1927 * We are allowed to fiddle with the contents of `text'.
1929 void do_text(Context ctx, int x, int y, char *text, int len,
1930 unsigned long attr, int lattr)
1933 int nfg, nbg, nfont;
1936 int force_manual_underline = 0;
1937 int fnt_width = font_width * (1 + (lattr != LATTR_NORM));
1938 int char_width = fnt_width;
1939 int text_adjust = 0;
1940 static int *IpDx = 0, IpDxLEN = 0;
1942 if (attr & ATTR_WIDE)
1945 if (len > IpDxLEN || IpDx[0] != char_width) {
1947 if (len > IpDxLEN) {
1949 IpDx = smalloc((len + 16) * sizeof(int));
1950 IpDxLEN = (len + 16);
1952 for (i = 0; i < IpDxLEN; i++)
1953 IpDx[i] = char_width;
1959 if ((attr & TATTR_ACTCURS) && (cfg.cursor_type == 0 || big_cursor)) {
1960 attr &= ATTR_CUR_AND | (bold_mode != BOLD_COLOURS ? ATTR_BOLD : 0);
1961 attr ^= ATTR_CUR_XOR;
1965 if (cfg.vtmode == VT_POORMAN && lattr != LATTR_NORM) {
1966 /* Assume a poorman font is borken in other ways too. */
1976 nfont |= FONT_WIDE + FONT_HIGH;
1980 /* Special hack for the VT100 linedraw glyphs. */
1981 if ((attr & CSET_MASK) == 0x2300) {
1982 if (!dbcs_screenfont &&
1983 text[0] >= (char) 0xBA && text[0] <= (char) 0xBD) {
1984 switch ((unsigned char) (text[0])) {
1986 text_adjust = -2 * font_height / 5;
1989 text_adjust = -1 * font_height / 5;
1992 text_adjust = font_height / 5;
1995 text_adjust = 2 * font_height / 5;
1998 if (lattr == LATTR_TOP || lattr == LATTR_BOT)
2001 text[0] = (char) (unitab_xterm['q'] & CHAR_MASK);
2002 attr |= (unitab_xterm['q'] & CSET_MASK);
2003 if (attr & ATTR_UNDER) {
2004 attr &= ~ATTR_UNDER;
2005 force_manual_underline = 1;
2010 /* Anything left as an original character set is unprintable. */
2011 if (DIRECT_CHAR(attr)) {
2014 memset(text, 0xFF, len);
2018 if ((attr & CSET_MASK) == ATTR_OEMCP)
2021 nfg = 2 * ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
2022 nbg = 2 * ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
2023 if (bold_mode == BOLD_FONT && (attr & ATTR_BOLD))
2025 if (und_mode == UND_FONT && (attr & ATTR_UNDER))
2026 nfont |= FONT_UNDERLINE;
2027 another_font(nfont);
2028 if (!fonts[nfont]) {
2029 if (nfont & FONT_UNDERLINE)
2030 force_manual_underline = 1;
2031 /* Don't do the same for manual bold, it could be bad news. */
2033 nfont &= ~(FONT_BOLD | FONT_UNDERLINE);
2035 another_font(nfont);
2037 nfont = FONT_NORMAL;
2038 if (attr & ATTR_REVERSE) {
2043 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BOLD))
2045 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BLINK))
2049 SelectObject(hdc, fonts[nfont]);
2050 SetTextColor(hdc, fg);
2051 SetBkColor(hdc, bg);
2052 SetBkMode(hdc, OPAQUE);
2055 line_box.right = x + char_width * len;
2056 line_box.bottom = y + font_height;
2058 /* We're using a private area for direct to font. (512 chars.) */
2059 if (dbcs_screenfont && (attr & CSET_MASK) == ATTR_ACP) {
2060 /* Ho Hum, dbcs fonts are a PITA! */
2061 /* To display on W9x I have to convert to UCS */
2062 static wchar_t *uni_buf = 0;
2063 static int uni_len = 0;
2065 if (len > uni_len) {
2067 uni_buf = smalloc((uni_len = len) * sizeof(wchar_t));
2069 nlen = MultiByteToWideChar(font_codepage, MB_USEGLYPHCHARS,
2070 text, len, uni_buf, uni_len);
2076 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2077 ETO_CLIPPED | ETO_OPAQUE, &line_box, uni_buf, nlen, 0);
2078 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2079 SetBkMode(hdc, TRANSPARENT);
2080 ExtTextOutW(hdc, x - 1,
2081 y - font_height * (lattr ==
2082 LATTR_BOT) + text_adjust,
2083 ETO_CLIPPED, &line_box, uni_buf, nlen, 0);
2085 } else if (DIRECT_FONT(attr)) {
2087 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2088 ETO_CLIPPED | ETO_OPAQUE, &line_box, text, len, IpDx);
2089 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2090 SetBkMode(hdc, TRANSPARENT);
2092 /* GRR: This draws the character outside it's box and can leave
2093 * 'droppings' even with the clip box! I suppose I could loop it
2094 * one character at a time ... yuk.
2096 * Or ... I could do a test print with "W", and use +1 or -1 for this
2097 * shift depending on if the leftmost column is blank...
2099 ExtTextOut(hdc, x - 1,
2100 y - font_height * (lattr ==
2101 LATTR_BOT) + text_adjust,
2102 ETO_CLIPPED, &line_box, text, len, IpDx);
2105 /* And 'normal' unicode characters */
2106 static WCHAR *wbuf = NULL;
2107 static int wlen = 0;
2112 wbuf = smalloc(wlen * sizeof(WCHAR));
2114 for (i = 0; i < len; i++)
2115 wbuf[i] = (WCHAR) ((attr & CSET_MASK) + (text[i] & CHAR_MASK));
2118 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2119 ETO_CLIPPED | ETO_OPAQUE, &line_box, wbuf, len, IpDx);
2121 /* And the shadow bold hack. */
2122 if (bold_mode == BOLD_SHADOW) {
2123 SetBkMode(hdc, TRANSPARENT);
2124 ExtTextOutW(hdc, x - 1,
2125 y - font_height * (lattr ==
2126 LATTR_BOT) + text_adjust,
2127 ETO_CLIPPED, &line_box, wbuf, len, IpDx);
2130 if (lattr != LATTR_TOP && (force_manual_underline ||
2131 (und_mode == UND_LINE
2132 && (attr & ATTR_UNDER)))) {
2135 if (lattr == LATTR_BOT)
2136 dec = dec * 2 - font_height;
2138 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, fg));
2139 MoveToEx(hdc, x, y + dec, NULL);
2140 LineTo(hdc, x + len * char_width, y + dec);
2141 oldpen = SelectObject(hdc, oldpen);
2142 DeleteObject(oldpen);
2146 void do_cursor(Context ctx, int x, int y, char *text, int len,
2147 unsigned long attr, int lattr)
2153 int ctype = cfg.cursor_type;
2155 if ((attr & TATTR_ACTCURS) && (ctype == 0 || big_cursor)) {
2156 if (((attr & CSET_MASK) | (unsigned char) *text) != UCSWIDE) {
2157 do_text(ctx, x, y, text, len, attr, lattr);
2161 attr |= TATTR_RIGHTCURS;
2164 fnt_width = char_width = font_width * (1 + (lattr != LATTR_NORM));
2165 if (attr & ATTR_WIDE)
2170 if ((attr & TATTR_PASCURS) && (ctype == 0 || big_cursor)) {
2173 pts[0].x = pts[1].x = pts[4].x = x;
2174 pts[2].x = pts[3].x = x + char_width - 1;
2175 pts[0].y = pts[3].y = pts[4].y = y;
2176 pts[1].y = pts[2].y = y + font_height - 1;
2177 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2178 Polyline(hdc, pts, 5);
2179 oldpen = SelectObject(hdc, oldpen);
2180 DeleteObject(oldpen);
2181 } else if ((attr & (TATTR_ACTCURS | TATTR_PASCURS)) && ctype != 0) {
2182 int startx, starty, dx, dy, length, i;
2185 starty = y + descent;
2188 length = char_width;
2191 if (attr & TATTR_RIGHTCURS)
2192 xadjust = char_width - 1;
2193 startx = x + xadjust;
2197 length = font_height;
2199 if (attr & TATTR_ACTCURS) {
2202 SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2203 MoveToEx(hdc, startx, starty, NULL);
2204 LineTo(hdc, startx + dx * length, starty + dy * length);
2205 oldpen = SelectObject(hdc, oldpen);
2206 DeleteObject(oldpen);
2208 for (i = 0; i < length; i++) {
2210 SetPixel(hdc, startx, starty, colours[23]);
2220 * Translate a WM_(SYS)?KEY(UP|DOWN) message into a string of ASCII
2221 * codes. Returns number of bytes used or zero to drop the message
2222 * or -1 to forward the message to windows.
2224 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
2225 unsigned char *output)
2228 int scan, left_alt = 0, key_down, shift_state;
2230 unsigned char *p = output;
2231 static int alt_state = 0;
2232 static int alt_sum = 0;
2234 HKL kbd_layout = GetKeyboardLayout(0);
2236 static WORD keys[3];
2237 static int compose_char = 0;
2238 static WPARAM compose_key = 0;
2240 r = GetKeyboardState(keystate);
2242 memset(keystate, 0, sizeof(keystate));
2245 #define SHOW_TOASCII_RESULT
2246 { /* Tell us all about key events */
2247 static BYTE oldstate[256];
2248 static int first = 1;
2252 memcpy(oldstate, keystate, sizeof(oldstate));
2255 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT) {
2257 } else if ((HIWORD(lParam) & KF_UP)
2258 && scan == (HIWORD(lParam) & 0xFF)) {
2262 if (wParam >= VK_F1 && wParam <= VK_F20)
2263 debug(("K_F%d", wParam + 1 - VK_F1));
2276 debug(("VK_%02x", wParam));
2278 if (message == WM_SYSKEYDOWN || message == WM_SYSKEYUP)
2280 debug((", S%02x", scan = (HIWORD(lParam) & 0xFF)));
2282 ch = MapVirtualKeyEx(wParam, 2, kbd_layout);
2283 if (ch >= ' ' && ch <= '~')
2284 debug((", '%c'", ch));
2286 debug((", $%02x", ch));
2289 debug((", KB0=%02x", keys[0]));
2291 debug((", KB1=%02x", keys[1]));
2293 debug((", KB2=%02x", keys[2]));
2295 if ((keystate[VK_SHIFT] & 0x80) != 0)
2297 if ((keystate[VK_CONTROL] & 0x80) != 0)
2299 if ((HIWORD(lParam) & KF_EXTENDED))
2301 if ((HIWORD(lParam) & KF_UP))
2305 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT);
2306 else if ((HIWORD(lParam) & KF_UP))
2307 oldstate[wParam & 0xFF] ^= 0x80;
2309 oldstate[wParam & 0xFF] ^= 0x81;
2311 for (ch = 0; ch < 256; ch++)
2312 if (oldstate[ch] != keystate[ch])
2313 debug((", M%02x=%02x", ch, keystate[ch]));
2315 memcpy(oldstate, keystate, sizeof(oldstate));
2319 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED)) {
2320 keystate[VK_RMENU] = keystate[VK_MENU];
2324 /* Nastyness with NUMLock - Shift-NUMLock is left alone though */
2325 if ((cfg.funky_type == 3 ||
2326 (cfg.funky_type <= 1 && app_keypad_keys && !cfg.no_applic_k))
2327 && wParam == VK_NUMLOCK && !(keystate[VK_SHIFT] & 0x80)) {
2329 wParam = VK_EXECUTE;
2331 /* UnToggle NUMLock */
2332 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0)
2333 keystate[VK_NUMLOCK] ^= 1;
2336 /* And write back the 'adjusted' state */
2337 SetKeyboardState(keystate);
2340 /* Disable Auto repeat if required */
2341 if (repeat_off && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT)
2344 if ((HIWORD(lParam) & KF_ALTDOWN) && (keystate[VK_RMENU] & 0x80) == 0)
2347 key_down = ((HIWORD(lParam) & KF_UP) == 0);
2349 /* Make sure Ctrl-ALT is not the same as AltGr for ToAscii unless told. */
2350 if (left_alt && (keystate[VK_CONTROL] & 0x80)) {
2351 if (cfg.ctrlaltkeys)
2352 keystate[VK_MENU] = 0;
2354 keystate[VK_RMENU] = 0x80;
2359 scan = (HIWORD(lParam) & (KF_UP | KF_EXTENDED | 0xFF));
2360 shift_state = ((keystate[VK_SHIFT] & 0x80) != 0)
2361 + ((keystate[VK_CONTROL] & 0x80) != 0) * 2;
2363 /* Note if AltGr was pressed and if it was used as a compose key */
2364 if (!compose_state) {
2365 compose_key = 0x100;
2366 if (cfg.compose_key) {
2367 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED))
2368 compose_key = wParam;
2370 if (wParam == VK_APPS)
2371 compose_key = wParam;
2374 if (wParam == compose_key) {
2375 if (compose_state == 0
2376 && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0) compose_state =
2378 else if (compose_state == 1 && (HIWORD(lParam) & KF_UP))
2382 } else if (compose_state == 1 && wParam != VK_CONTROL)
2386 * Record that we pressed key so the scroll window can be reset, but
2387 * be careful to avoid Shift-UP/Down
2389 if (wParam != VK_SHIFT && wParam != VK_PRIOR && wParam != VK_NEXT) {
2393 /* Make sure we're not pasting */
2397 if (compose_state > 1 && left_alt)
2400 /* Sanitize the number pad if not using a PC NumPad */
2401 if (left_alt || (app_keypad_keys && !cfg.no_applic_k
2402 && cfg.funky_type != 2)
2403 || cfg.funky_type == 3 || cfg.nethack_keypad || compose_state) {
2404 if ((HIWORD(lParam) & KF_EXTENDED) == 0) {
2408 nParam = VK_NUMPAD0;
2411 nParam = VK_NUMPAD1;
2414 nParam = VK_NUMPAD2;
2417 nParam = VK_NUMPAD3;
2420 nParam = VK_NUMPAD4;
2423 nParam = VK_NUMPAD5;
2426 nParam = VK_NUMPAD6;
2429 nParam = VK_NUMPAD7;
2432 nParam = VK_NUMPAD8;
2435 nParam = VK_NUMPAD9;
2438 nParam = VK_DECIMAL;
2442 if (keystate[VK_NUMLOCK] & 1)
2449 /* If a key is pressed and AltGr is not active */
2450 if (key_down && (keystate[VK_RMENU] & 0x80) == 0 && !compose_state) {
2451 /* Okay, prepare for most alts then ... */
2455 /* Lets see if it's a pattern we know all about ... */
2456 if (wParam == VK_PRIOR && shift_state == 1) {
2457 SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0);
2460 if (wParam == VK_NEXT && shift_state == 1) {
2461 SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
2464 if (wParam == VK_INSERT && shift_state == 1) {
2468 if (left_alt && wParam == VK_F4 && cfg.alt_f4) {
2471 if (left_alt && wParam == VK_SPACE && cfg.alt_space) {
2473 PostMessage(hwnd, WM_CHAR, ' ', 0);
2474 SendMessage(hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0);
2477 /* Control-Numlock for app-keypad mode switch */
2478 if (wParam == VK_PAUSE && shift_state == 2) {
2479 app_keypad_keys ^= 1;
2483 /* Nethack keypad */
2484 if (cfg.nethack_keypad && !left_alt) {
2487 *p++ = shift_state ? 'B' : 'b';
2490 *p++ = shift_state ? 'J' : 'j';
2493 *p++ = shift_state ? 'N' : 'n';
2496 *p++ = shift_state ? 'H' : 'h';
2499 *p++ = shift_state ? '.' : '.';
2502 *p++ = shift_state ? 'L' : 'l';
2505 *p++ = shift_state ? 'Y' : 'y';
2508 *p++ = shift_state ? 'K' : 'k';
2511 *p++ = shift_state ? 'U' : 'u';
2516 /* Application Keypad */
2520 if (cfg.funky_type == 3 ||
2521 (cfg.funky_type <= 1 &&
2522 app_keypad_keys && !cfg.no_applic_k)) switch (wParam) {
2536 if (app_keypad_keys && !cfg.no_applic_k)
2573 if (cfg.funky_type == 2) {
2578 } else if (shift_state)
2585 if (cfg.funky_type == 2)
2589 if (cfg.funky_type == 2)
2593 if (cfg.funky_type == 2)
2598 if (HIWORD(lParam) & KF_EXTENDED)
2604 if (xkey >= 'P' && xkey <= 'S')
2605 p += sprintf((char *) p, "\x1B%c", xkey);
2607 p += sprintf((char *) p, "\x1B?%c", xkey);
2609 p += sprintf((char *) p, "\x1BO%c", xkey);
2614 if (wParam == VK_BACK && shift_state == 0) { /* Backspace */
2615 *p++ = (cfg.bksp_is_delete ? 0x7F : 0x08);
2619 if (wParam == VK_TAB && shift_state == 1) { /* Shift tab */
2625 if (wParam == VK_SPACE && shift_state == 2) { /* Ctrl-Space */
2629 if (wParam == VK_SPACE && shift_state == 3) { /* Ctrl-Shift-Space */
2633 if (wParam == VK_CANCEL && shift_state == 2) { /* Ctrl-Break */
2638 if (wParam == VK_PAUSE) { /* Break/Pause */
2643 /* Control-2 to Control-8 are special */
2644 if (shift_state == 2 && wParam >= '2' && wParam <= '8') {
2645 *p++ = "\000\033\034\035\036\037\177"[wParam - '2'];
2648 if (shift_state == 2 && wParam == 0xBD) {
2652 if (shift_state == 2 && wParam == 0xDF) {
2656 if (shift_state == 0 && wParam == VK_RETURN && cr_lf_return) {
2663 * Next, all the keys that do tilde codes. (ESC '[' nn '~',
2664 * for integer decimal nn.)
2666 * We also deal with the weird ones here. Linux VCs replace F1
2667 * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
2668 * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
2674 code = (keystate[VK_SHIFT] & 0x80 ? 23 : 11);
2677 code = (keystate[VK_SHIFT] & 0x80 ? 24 : 12);
2680 code = (keystate[VK_SHIFT] & 0x80 ? 25 : 13);
2683 code = (keystate[VK_SHIFT] & 0x80 ? 26 : 14);
2686 code = (keystate[VK_SHIFT] & 0x80 ? 28 : 15);
2689 code = (keystate[VK_SHIFT] & 0x80 ? 29 : 17);
2692 code = (keystate[VK_SHIFT] & 0x80 ? 31 : 18);
2695 code = (keystate[VK_SHIFT] & 0x80 ? 32 : 19);
2698 code = (keystate[VK_SHIFT] & 0x80 ? 33 : 20);
2701 code = (keystate[VK_SHIFT] & 0x80 ? 34 : 21);
2752 /* Reorder edit keys to physical order */
2753 if (cfg.funky_type == 3 && code <= 6)
2754 code = "\0\2\1\4\5\3\6"[code];
2756 if (vt52_mode && code > 0 && code <= 6) {
2757 p += sprintf((char *) p, "\x1B%c", " HLMEIG"[code]);
2761 if (cfg.funky_type == 5 && code >= 11 && code <= 34) {
2762 char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
2765 case VK_F1: index = 0; break;
2766 case VK_F2: index = 1; break;
2767 case VK_F3: index = 2; break;
2768 case VK_F4: index = 3; break;
2769 case VK_F5: index = 4; break;
2770 case VK_F6: index = 5; break;
2771 case VK_F7: index = 6; break;
2772 case VK_F8: index = 7; break;
2773 case VK_F9: index = 8; break;
2774 case VK_F10: index = 9; break;
2775 case VK_F11: index = 10; break;
2776 case VK_F12: index = 11; break;
2778 if (keystate[VK_SHIFT] & 0x80) index += 12;
2779 if (keystate[VK_CONTROL] & 0x80) index += 24;
2780 p += sprintf((char *) p, "\x1B[%c", codes[index]);
2783 if ((vt52_mode || cfg.funky_type == 4) && code >= 11 && code <= 24) {
2790 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11 - offt);
2793 sprintf((char *) p, "\x1BO%c", code + 'P' - 11 - offt);
2796 if (cfg.funky_type == 1 && code >= 11 && code <= 15) {
2797 p += sprintf((char *) p, "\x1B[[%c", code + 'A' - 11);
2800 if (cfg.funky_type == 2 && code >= 11 && code <= 14) {
2802 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11);
2804 p += sprintf((char *) p, "\x1BO%c", code + 'P' - 11);
2807 if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
2808 p += sprintf((char *) p, code == 1 ? "\x1B[H" : "\x1BOw");
2812 p += sprintf((char *) p, "\x1B[%d~", code);
2817 * Now the remaining keys (arrows and Keypad 5. Keypad 5 for
2818 * some reason seems to send VK_CLEAR to Windows...).
2841 p += sprintf((char *) p, "\x1B%c", xkey);
2843 int app_flg = (app_cursor_keys && !cfg.no_applic_c);
2844 /* VT100 & VT102 manuals both state the app cursor keys
2845 * only work if the app keypad is on.
2847 if (!app_keypad_keys)
2849 /* Useful mapping of Ctrl-arrows */
2850 if (shift_state == 2)
2854 p += sprintf((char *) p, "\x1BO%c", xkey);
2856 p += sprintf((char *) p, "\x1B[%c", xkey);
2863 * Finally, deal with Return ourselves. (Win95 seems to
2864 * foul it up when Alt is pressed, for some reason.)
2866 if (wParam == VK_RETURN) { /* Return */
2872 if (left_alt && wParam >= VK_NUMPAD0 && wParam <= VK_NUMPAD9)
2873 alt_sum = alt_sum * 10 + wParam - VK_NUMPAD0;
2878 /* Okay we've done everything interesting; let windows deal with
2879 * the boring stuff */
2881 r = ToAsciiEx(wParam, scan, keystate, keys, 0, kbd_layout);
2882 #ifdef SHOW_TOASCII_RESULT
2883 if (r == 1 && !key_down) {
2885 if (in_utf || dbcs_screenfont)
2886 debug((", (U+%04x)", alt_sum));
2888 debug((", LCH(%d)", alt_sum));
2890 debug((", ACH(%d)", keys[0]));
2895 for (r1 = 0; r1 < r; r1++) {
2896 debug(("%s%d", r1 ? "," : "", keys[r1]));
2904 for (i = 0; i < r; i++) {
2905 unsigned char ch = (unsigned char) keys[i];
2907 if (compose_state == 2 && (ch & 0x80) == 0 && ch > ' ') {
2912 if (compose_state == 3 && (ch & 0x80) == 0 && ch > ' ') {
2916 if ((nc = check_compose(compose_char, ch)) == -1) {
2917 MessageBeep(MB_ICONHAND);
2921 luni_send(&keybuf, 1);
2929 if (in_utf || dbcs_screenfont) {
2931 luni_send(&keybuf, 1);
2933 ch = (char) alt_sum;
2938 lpage_send(kbd_codepage, &ch, 1);
2940 static char cbuf[] = "\033 ";
2942 lpage_send(kbd_codepage, cbuf + !left_alt,
2947 /* This is so the ALT-Numpad and dead keys work correctly. */
2952 /* If we're definitly not building up an ALT-54321 then clear it */
2955 /* If we will be using alt_sum fix the 256s */
2956 else if (keys[0] && (in_utf || dbcs_screenfont))
2960 /* ALT alone may or may not want to bring up the System menu */
2961 if (wParam == VK_MENU) {
2963 if (message == WM_SYSKEYDOWN)
2965 else if (message == WM_SYSKEYUP && alt_state)
2966 PostMessage(hwnd, WM_CHAR, ' ', 0);
2967 if (message == WM_SYSKEYUP)
2977 void set_title(char *title)
2980 window_name = smalloc(1 + strlen(title));
2981 strcpy(window_name, title);
2982 if (cfg.win_name_always || !IsIconic(hwnd))
2983 SetWindowText(hwnd, title);
2986 void set_icon(char *title)
2989 icon_name = smalloc(1 + strlen(title));
2990 strcpy(icon_name, title);
2991 if (!cfg.win_name_always && IsIconic(hwnd))
2992 SetWindowText(hwnd, title);
2995 void set_sbar(int total, int start, int page)
3002 si.cbSize = sizeof(si);
3003 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
3005 si.nMax = total - 1;
3009 SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
3012 Context get_ctx(void)
3018 SelectPalette(hdc, pal, FALSE);
3024 void free_ctx(Context ctx)
3026 SelectPalette(ctx, GetStockObject(DEFAULT_PALETTE), FALSE);
3027 ReleaseDC(hwnd, ctx);
3030 static void real_palette_set(int n, int r, int g, int b)
3033 logpal->palPalEntry[n].peRed = r;
3034 logpal->palPalEntry[n].peGreen = g;
3035 logpal->palPalEntry[n].peBlue = b;
3036 logpal->palPalEntry[n].peFlags = PC_NOCOLLAPSE;
3037 colours[n] = PALETTERGB(r, g, b);
3038 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3040 colours[n] = RGB(r, g, b);
3043 void palette_set(int n, int r, int g, int b)
3045 static const int first[21] = {
3046 0, 2, 4, 6, 8, 10, 12, 14,
3047 1, 3, 5, 7, 9, 11, 13, 15,
3050 real_palette_set(first[n], r, g, b);
3052 real_palette_set(first[n] + 1, r, g, b);
3054 HDC hdc = get_ctx();
3055 UnrealizeObject(pal);
3056 RealizePalette(hdc);
3061 void palette_reset(void)
3065 for (i = 0; i < NCOLOURS; i++) {
3067 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
3068 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
3069 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
3070 logpal->palPalEntry[i].peFlags = 0;
3071 colours[i] = PALETTERGB(defpal[i].rgbtRed,
3072 defpal[i].rgbtGreen,
3073 defpal[i].rgbtBlue);
3075 colours[i] = RGB(defpal[i].rgbtRed,
3076 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
3081 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3083 RealizePalette(hdc);
3088 void write_aclip(char *data, int len, int must_deselect)
3093 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len + 1);
3096 lock = GlobalLock(clipdata);
3099 memcpy(lock, data, len);
3100 ((unsigned char *) lock)[len] = 0;
3101 GlobalUnlock(clipdata);
3104 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
3106 if (OpenClipboard(hwnd)) {
3108 SetClipboardData(CF_TEXT, clipdata);
3111 GlobalFree(clipdata);
3114 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
3118 * Note: unlike write_aclip() this will not append a nul.
3120 void write_clip(wchar_t * data, int len, int must_deselect)
3127 len2 = WideCharToMultiByte(CP_ACP, 0, data, len, 0, 0, NULL, NULL);
3129 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE,
3130 len * sizeof(wchar_t));
3131 clipdata2 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len2);
3133 if (!clipdata || !clipdata2) {
3135 GlobalFree(clipdata);
3137 GlobalFree(clipdata2);
3140 if (!(lock = GlobalLock(clipdata)))
3142 if (!(lock2 = GlobalLock(clipdata2)))
3145 memcpy(lock, data, len * sizeof(wchar_t));
3146 WideCharToMultiByte(CP_ACP, 0, data, len, lock2, len2, NULL, NULL);
3148 GlobalUnlock(clipdata);
3149 GlobalUnlock(clipdata2);
3152 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
3154 if (OpenClipboard(hwnd)) {
3156 SetClipboardData(CF_UNICODETEXT, clipdata);
3157 SetClipboardData(CF_TEXT, clipdata2);
3160 GlobalFree(clipdata);
3161 GlobalFree(clipdata2);
3165 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
3168 void get_clip(wchar_t ** p, int *len)
3170 static HGLOBAL clipdata = NULL;
3171 static wchar_t *converted = 0;
3180 GlobalUnlock(clipdata);
3183 } else if (OpenClipboard(NULL)) {
3184 if ((clipdata = GetClipboardData(CF_UNICODETEXT))) {
3186 *p = GlobalLock(clipdata);
3188 for (p2 = *p; *p2; p2++);
3192 } else if ( (clipdata = GetClipboardData(CF_TEXT)) ) {
3196 s = GlobalLock(clipdata);
3197 i = MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, 0, 0);
3198 *p = converted = smalloc(i * sizeof(wchar_t));
3199 MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, converted, i);
3212 * Move `lines' lines from position `from' to position `to' in the
3215 void optimised_move(int to, int from, int lines)
3220 min = (to < from ? to : from);
3221 max = to + from - min;
3224 r.right = cols * font_width;
3225 r.top = min * font_height;
3226 r.bottom = (max + lines) * font_height;
3227 ScrollWindow(hwnd, 0, (to - from) * font_height, &r, &r);
3232 * Print a message box and perform a fatal exit.
3234 void fatalbox(char *fmt, ...)
3240 vsprintf(stuff, fmt, ap);
3242 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
3247 * Manage window caption / taskbar flashing, if enabled.
3248 * 0 = stop, 1 = maintain, 2 = start
3250 static void flash_window(int mode)
3252 static long last_flash = 0;
3253 static int flashing = 0;
3254 if ((mode == 0) || (cfg.beep_ind == B_IND_DISABLED)) {
3257 FlashWindow(hwnd, FALSE);
3261 } else if (mode == 2) {
3264 last_flash = GetTickCount();
3266 FlashWindow(hwnd, TRUE);
3269 } else if ((mode == 1) && (cfg.beep_ind == B_IND_FLASH)) {
3272 long now = GetTickCount();
3273 long fdiff = now - last_flash;
3274 if (fdiff < 0 || fdiff > 450) {
3276 FlashWindow(hwnd, TRUE); /* toggle */
3287 if (mode == BELL_DEFAULT) {
3289 * For MessageBeep style bells, we want to be careful of
3290 * timing, because they don't have the nice property of
3291 * PlaySound bells that each one cancels the previous
3292 * active one. So we limit the rate to one per 50ms or so.
3294 static long lastbeep = 0;
3297 beepdiff = GetTickCount() - lastbeep;
3298 if (beepdiff >= 0 && beepdiff < 50)
3302 * The above MessageBeep call takes time, so we record the
3303 * time _after_ it finishes rather than before it starts.
3305 lastbeep = GetTickCount();
3306 } else if (mode == BELL_WAVEFILE) {
3307 if (!PlaySound(cfg.bell_wavefile, NULL, SND_ASYNC | SND_FILENAME)) {
3308 char buf[sizeof(cfg.bell_wavefile) + 80];
3309 sprintf(buf, "Unable to play sound file\n%s\n"
3310 "Using default sound instead", cfg.bell_wavefile);
3311 MessageBox(hwnd, buf, "PuTTY Sound Error",
3312 MB_OK | MB_ICONEXCLAMATION);
3313 cfg.beep = BELL_DEFAULT;
3316 /* Otherwise, either visual bell or disabled; do nothing here */
3318 flash_window(2); /* start */