8 #define PUTTY_DO_GLOBALS /* actually _define_ globals */
12 #define IDM_SHOWLOG 0x0010
13 #define IDM_NEWSESS 0x0020
14 #define IDM_DUPSESS 0x0030
15 #define IDM_RECONF 0x0040
16 #define IDM_CLRSB 0x0050
17 #define IDM_RESET 0x0060
18 #define IDM_TEL_AYT 0x0070
19 #define IDM_TEL_BRK 0x0080
20 #define IDM_TEL_SYNCH 0x0090
21 #define IDM_TEL_EC 0x00a0
22 #define IDM_TEL_EL 0x00b0
23 #define IDM_TEL_GA 0x00c0
24 #define IDM_TEL_NOP 0x00d0
25 #define IDM_TEL_ABORT 0x00e0
26 #define IDM_TEL_AO 0x00f0
27 #define IDM_TEL_IP 0x0100
28 #define IDM_TEL_SUSP 0x0110
29 #define IDM_TEL_EOR 0x0120
30 #define IDM_TEL_EOF 0x0130
31 #define IDM_ABOUT 0x0140
32 #define IDM_SAVEDSESS 0x0150
34 #define IDM_SAVED_MIN 0x1000
35 #define IDM_SAVED_MAX 0x2000
37 #define WM_IGNORE_SIZE (WM_XUSER + 1)
38 #define WM_IGNORE_CLIP (WM_XUSER + 2)
39 #define WM_IGNORE_KEYMENU (WM_XUSER + 3)
41 static LRESULT CALLBACK
WndProc (HWND
, UINT
, WPARAM
, LPARAM
);
42 static int TranslateKey(UINT message
, WPARAM wParam
, LPARAM lParam
, unsigned char *output
);
43 static void cfgtopalette(void);
44 static void init_palette(void);
45 static void init_fonts(int);
47 static int extra_width
, extra_height
;
49 static int pending_netevent
= 0;
50 static WPARAM pend_netevent_wParam
= 0;
51 static LPARAM pend_netevent_lParam
= 0;
52 static void enact_pending_netevent(void);
56 #define FONT_UNDERLINE 2
57 #define FONT_BOLDUND 3
59 #define FONT_OEMBOLD 5
60 #define FONT_OEMBOLDUND 6
62 static HFONT fonts
[8];
64 BOLD_COLOURS
, BOLD_SHADOW
, BOLD_FONT
72 static COLORREF colours
[NCOLOURS
];
74 static LPLOGPALETTE logpal
;
75 static RGBTRIPLE defpal
[NCOLOURS
];
79 static int dbltime
, lasttime
, lastact
;
80 static Mouse_Button lastbtn
;
82 static char *window_name
, *icon_name
;
84 int WINAPI
WinMain(HINSTANCE inst
, HINSTANCE prev
, LPSTR cmdline
, int show
) {
85 static char appname
[] = "PuTTY";
90 int guess_width
, guess_height
;
93 flags
= FLAG_VERBOSE
| FLAG_WINDOWED
| FLAG_CONNECTION
;
95 winsock_ver
= MAKEWORD(1, 1);
96 if (WSAStartup(winsock_ver
, &wsadata
)) {
97 MessageBox(NULL
, "Unable to initialise WinSock", "WinSock Error",
98 MB_OK
| MB_ICONEXCLAMATION
);
101 if (LOBYTE(wsadata
.wVersion
) != 1 || HIBYTE(wsadata
.wVersion
) != 1) {
102 MessageBox(NULL
, "WinSock version is incompatible with 1.1",
103 "WinSock Error", MB_OK
| MB_ICONEXCLAMATION
);
107 /* WISHLIST: maybe allow config tweaking even if winsock not present? */
109 InitCommonControls();
112 * Process the command line.
117 default_protocol
= DEFAULT_PROTOCOL
;
118 default_port
= DEFAULT_PORT
;
123 while (*p
&& isspace(*p
)) p
++;
126 * Process command line options first. Yes, this can be
127 * done better, and it will be as soon as I have the
131 char *q
= p
+ strcspn(p
, " \t");
134 tolower(p
[0]) == 's' &&
135 tolower(p
[1]) == 's' &&
136 tolower(p
[2]) == 'h') {
137 default_protocol
= cfg
.protocol
= PROT_SSH
;
138 default_port
= cfg
.port
= 22;
139 } else if (q
== p
+ 3 &&
140 tolower(p
[0]) == 'l' &&
141 tolower(p
[1]) == 'o' &&
142 tolower(p
[2]) == 'g') {
143 logfile
= "putty.log";
145 p
= q
+ strspn(q
, " \t");
149 * An initial @ means to activate a saved session.
153 if (!*cfg
.host
&& !do_config()) {
157 } else if (*p
== '&') {
159 * An initial & means we've been given a command line
160 * containing the hex value of a HANDLE for a file
161 * mapping object, which we must then extract as a
166 if (sscanf(p
+1, "%p", &filemap
) == 1 &&
167 (cp
= MapViewOfFile(filemap
, FILE_MAP_READ
,
168 0, 0, sizeof(Config
))) != NULL
) {
171 CloseHandle(filemap
);
172 } else if (!do_config()) {
179 * If the hostname starts with "telnet:", set the
180 * protocol to Telnet and process the string as a
183 if (!strncmp(q
, "telnet:", 7)) {
187 if (q
[0] == '/' && q
[1] == '/')
189 cfg
.protocol
= PROT_TELNET
;
191 while (*p
&& *p
!= ':' && *p
!= '/') p
++;
199 strncpy (cfg
.host
, q
, sizeof(cfg
.host
)-1);
200 cfg
.host
[sizeof(cfg
.host
)-1] = '\0';
202 while (*p
&& !isspace(*p
)) p
++;
205 strncpy (cfg
.host
, q
, sizeof(cfg
.host
)-1);
206 cfg
.host
[sizeof(cfg
.host
)-1] = '\0';
207 while (*p
&& isspace(*p
)) p
++;
222 * Select protocol. This is farmed out into a table in a
223 * separate file to enable an ssh-free variant.
228 for (i
= 0; backends
[i
].backend
!= NULL
; i
++)
229 if (backends
[i
].protocol
== cfg
.protocol
) {
230 back
= backends
[i
].backend
;
234 MessageBox(NULL
, "Unsupported protocol number found",
235 "PuTTY Internal Error", MB_OK
| MB_ICONEXCLAMATION
);
241 ldisc
= (cfg
.ldisc_term ?
&ldisc_term
: &ldisc_simple
);
245 wndclass
.lpfnWndProc
= WndProc
;
246 wndclass
.cbClsExtra
= 0;
247 wndclass
.cbWndExtra
= 0;
248 wndclass
.hInstance
= inst
;
249 wndclass
.hIcon
= LoadIcon (inst
,
250 MAKEINTRESOURCE(IDI_MAINICON
));
251 wndclass
.hCursor
= LoadCursor (NULL
, IDC_IBEAM
);
252 wndclass
.hbrBackground
= GetStockObject (BLACK_BRUSH
);
253 wndclass
.lpszMenuName
= NULL
;
254 wndclass
.lpszClassName
= appname
;
256 RegisterClass (&wndclass
);
261 savelines
= cfg
.savelines
;
267 * Guess some defaults for the window size. This all gets
268 * updated later, so we don't really care too much. However, we
269 * do want the font width/height guesses to correspond to a
270 * large font rather than a small one...
277 term_size (cfg
.height
, cfg
.width
, cfg
.savelines
);
278 guess_width
= extra_width
+ font_width
* cols
;
279 guess_height
= extra_height
+ font_height
* rows
;
282 HWND w
= GetDesktopWindow();
283 GetWindowRect (w
, &r
);
284 if (guess_width
> r
.right
- r
.left
)
285 guess_width
= r
.right
- r
.left
;
286 if (guess_height
> r
.bottom
- r
.top
)
287 guess_height
= r
.bottom
- r
.top
;
291 int winmode
= WS_OVERLAPPEDWINDOW
|WS_VSCROLL
;
292 if (!cfg
.scrollbar
) winmode
&= ~(WS_VSCROLL
);
293 if (cfg
.locksize
) winmode
&= ~(WS_THICKFRAME
|WS_MAXIMIZEBOX
);
294 hwnd
= CreateWindow (appname
, appname
,
296 CW_USEDEFAULT
, CW_USEDEFAULT
,
297 guess_width
, guess_height
,
298 NULL
, NULL
, inst
, NULL
);
302 * Initialise the fonts, simultaneously correcting the guesses
303 * for font_{width,height}.
305 bold_mode
= cfg
.bold_colour ? BOLD_COLOURS
: BOLD_FONT
;
310 * Correct the guesses for extra_{width,height}.
314 GetWindowRect (hwnd
, &wr
);
315 GetClientRect (hwnd
, &cr
);
316 extra_width
= wr
.right
- wr
.left
- cr
.right
+ cr
.left
;
317 extra_height
= wr
.bottom
- wr
.top
- cr
.bottom
+ cr
.top
;
321 * Resize the window, now we know what size we _really_ want it
324 guess_width
= extra_width
+ font_width
* cols
;
325 guess_height
= extra_height
+ font_height
* rows
;
326 SendMessage (hwnd
, WM_IGNORE_SIZE
, 0, 0);
327 SetWindowPos (hwnd
, NULL
, 0, 0, guess_width
, guess_height
,
328 SWP_NOMOVE
| SWP_NOREDRAW
| SWP_NOZORDER
);
331 * Initialise the scroll bar.
336 si
.cbSize
= sizeof(si
);
337 si
.fMask
= SIF_ALL
| SIF_DISABLENOSCROLL
;
342 SetScrollInfo (hwnd
, SB_VERT
, &si
, FALSE
);
346 * Start up the telnet connection.
353 error
= back
->init (hwnd
, cfg
.host
, cfg
.port
, &realhost
);
355 sprintf(msg
, "Unable to open connection:\n%s", error
);
356 MessageBox(NULL
, msg
, "PuTTY Error", MB_ICONERROR
| MB_OK
);
359 window_name
= icon_name
= NULL
;
360 sprintf(msg
, "%s - PuTTY", realhost
);
365 session_closed
= FALSE
;
368 * Set up the input and output buffers.
371 outbuf_reap
= outbuf_head
= 0;
374 * Prepare the mouse handler.
376 lastact
= MA_NOTHING
;
377 lastbtn
= MB_NOTHING
;
378 dbltime
= GetDoubleClickTime();
381 * Set up the session-control options on the system menu.
384 HMENU m
= GetSystemMenu (hwnd
, FALSE
);
388 AppendMenu (m
, MF_SEPARATOR
, 0, 0);
389 if (cfg
.protocol
== PROT_TELNET
) {
391 AppendMenu (p
, MF_ENABLED
, IDM_TEL_AYT
, "Are You There");
392 AppendMenu (p
, MF_ENABLED
, IDM_TEL_BRK
, "Break");
393 AppendMenu (p
, MF_ENABLED
, IDM_TEL_SYNCH
, "Synch");
394 AppendMenu (p
, MF_SEPARATOR
, 0, 0);
395 AppendMenu (p
, MF_ENABLED
, IDM_TEL_EC
, "Erase Character");
396 AppendMenu (p
, MF_ENABLED
, IDM_TEL_EL
, "Erase Line");
397 AppendMenu (p
, MF_ENABLED
, IDM_TEL_GA
, "Go Ahead");
398 AppendMenu (p
, MF_ENABLED
, IDM_TEL_NOP
, "No Operation");
399 AppendMenu (p
, MF_SEPARATOR
, 0, 0);
400 AppendMenu (p
, MF_ENABLED
, IDM_TEL_ABORT
, "Abort Process");
401 AppendMenu (p
, MF_ENABLED
, IDM_TEL_AO
, "Abort Output");
402 AppendMenu (p
, MF_ENABLED
, IDM_TEL_IP
, "Interrupt Process");
403 AppendMenu (p
, MF_ENABLED
, IDM_TEL_SUSP
, "Suspend Process");
404 AppendMenu (p
, MF_SEPARATOR
, 0, 0);
405 AppendMenu (p
, MF_ENABLED
, IDM_TEL_EOR
, "End Of Record");
406 AppendMenu (p
, MF_ENABLED
, IDM_TEL_EOF
, "End Of File");
407 AppendMenu (m
, MF_POPUP
| MF_ENABLED
, (UINT
) p
, "Telnet Command");
408 AppendMenu (m
, MF_SEPARATOR
, 0, 0);
410 AppendMenu (m
, MF_ENABLED
, IDM_SHOWLOG
, "&Event Log");
411 AppendMenu (m
, MF_SEPARATOR
, 0, 0);
412 AppendMenu (m
, MF_ENABLED
, IDM_NEWSESS
, "Ne&w Session");
413 AppendMenu (m
, MF_ENABLED
, IDM_DUPSESS
, "&Duplicate Session");
416 for (i
= 1 ; i
< ((nsessions
< 256) ? nsessions
: 256) ; i
++)
417 AppendMenu (s
, MF_ENABLED
, IDM_SAVED_MIN
+ (16 * i
) , sessions
[i
]);
418 AppendMenu (m
, MF_POPUP
| MF_ENABLED
, (UINT
) s
, "Sa&ved Sessions");
419 AppendMenu (m
, MF_ENABLED
, IDM_RECONF
, "Chan&ge Settings");
420 AppendMenu (m
, MF_SEPARATOR
, 0, 0);
421 AppendMenu (m
, MF_ENABLED
, IDM_CLRSB
, "C&lear Scrollback");
422 AppendMenu (m
, MF_ENABLED
, IDM_RESET
, "Rese&t Terminal");
423 AppendMenu (m
, MF_SEPARATOR
, 0, 0);
424 AppendMenu (m
, MF_ENABLED
, IDM_ABOUT
, "&About PuTTY");
428 * Finally show the window!
430 ShowWindow (hwnd
, show
);
433 * Set the palette up.
439 has_focus
= (GetForegroundWindow() == hwnd
);
443 int timer_id
= 0, long_timer
= 0;
445 while (GetMessage (&msg
, NULL
, 0, 0) == 1) {
446 /* Sometimes DispatchMessage calls routines that use their own
447 * GetMessage loop, setup this timer so we get some control back.
449 * Also call term_update() from the timer so that if the host
450 * is sending data flat out we still do redraws.
452 if(timer_id
&& long_timer
) {
453 KillTimer(hwnd
, timer_id
);
454 long_timer
= timer_id
= 0;
457 timer_id
= SetTimer(hwnd
, 1, 20, NULL
);
458 DispatchMessage (&msg
);
460 /* This is too fast, but I'll leave it for now 'cause it shows
461 * how often term_update is called (far too often at times!)
465 /* Send the paste buffer if there's anything to send */
468 /* If there's nothing new in the queue then we can do everything
469 * we've delayed, reading the socket, writing, and repainting
472 if (!PeekMessage (&msg
, NULL
, 0, 0, PM_NOREMOVE
)) {
473 if (pending_netevent
) {
474 enact_pending_netevent();
479 if (!PeekMessage (&msg
, NULL
, 0, 0, PM_NOREMOVE
)) {
481 KillTimer(hwnd
, timer_id
);
488 timer_id
= SetTimer(hwnd
, 1, 2000, NULL
);
489 else if (cfg
.blinktext
)
490 timer_id
= SetTimer(hwnd
, 1, 250, NULL
);
492 timer_id
= SetTimer(hwnd
, 1, 500, NULL
);
505 DeleteObject(fonts
[i
]);
512 if (cfg
.protocol
== PROT_SSH
) {
523 * Actually do the job requested by a WM_NETEVENT
525 static void enact_pending_netevent(void) {
527 pending_netevent
= FALSE
;
528 i
= back
->msg (pend_netevent_wParam
, pend_netevent_lParam
);
532 switch (WSABASEERR
+ (-i
) % 10000) {
534 sprintf(buf
, "Connection reset by peer");
537 sprintf(buf
, "Unexpected network error %d", -i
);
540 MessageBox(hwnd
, buf
, "PuTTY Fatal Error",
541 MB_ICONERROR
| MB_OK
);
544 if (cfg
.close_on_exit
)
547 session_closed
= TRUE
;
548 MessageBox(hwnd
, "Connection closed by remote host",
549 "PuTTY", MB_OK
| MB_ICONINFORMATION
);
550 SetWindowText (hwnd
, "PuTTY (inactive)");
556 * Copy the colour palette from the configuration data into defpal.
557 * This is non-trivial because the colour indices are different.
559 static void cfgtopalette(void) {
561 static const int ww
[] = {
562 6, 7, 8, 9, 10, 11, 12, 13,
563 14, 15, 16, 17, 18, 19, 20, 21,
564 0, 1, 2, 3, 4, 4, 5, 5
567 for (i
=0; i
<24; i
++) {
569 defpal
[i
].rgbtRed
= cfg
.colours
[w
][0];
570 defpal
[i
].rgbtGreen
= cfg
.colours
[w
][1];
571 defpal
[i
].rgbtBlue
= cfg
.colours
[w
][2];
576 * Set up the colour palette.
578 static void init_palette(void) {
580 HDC hdc
= GetDC (hwnd
);
582 if (cfg
.try_palette
&&
583 GetDeviceCaps (hdc
, RASTERCAPS
) & RC_PALETTE
) {
584 logpal
= smalloc(sizeof(*logpal
)
585 - sizeof(logpal
->palPalEntry
)
586 + NCOLOURS
* sizeof(PALETTEENTRY
));
587 logpal
->palVersion
= 0x300;
588 logpal
->palNumEntries
= NCOLOURS
;
589 for (i
= 0; i
< NCOLOURS
; i
++) {
590 logpal
->palPalEntry
[i
].peRed
= defpal
[i
].rgbtRed
;
591 logpal
->palPalEntry
[i
].peGreen
= defpal
[i
].rgbtGreen
;
592 logpal
->palPalEntry
[i
].peBlue
= defpal
[i
].rgbtBlue
;
593 logpal
->palPalEntry
[i
].peFlags
= PC_NOCOLLAPSE
;
595 pal
= CreatePalette (logpal
);
597 SelectPalette (hdc
, pal
, FALSE
);
598 RealizePalette (hdc
);
599 SelectPalette (hdc
, GetStockObject (DEFAULT_PALETTE
),
603 ReleaseDC (hwnd
, hdc
);
606 for (i
=0; i
<NCOLOURS
; i
++)
607 colours
[i
] = PALETTERGB(defpal
[i
].rgbtRed
,
611 for(i
=0; i
<NCOLOURS
; i
++)
612 colours
[i
] = RGB(defpal
[i
].rgbtRed
,
618 * Initialise all the fonts we will need. There may be as many as
619 * eight or as few as one. We also:
621 * - check the font width and height, correcting our guesses if
624 * - verify that the bold font is the same width as the ordinary
625 * one, and engage shadow bolding if not.
627 * - verify that the underlined font is the same width as the
628 * ordinary one (manual underlining by means of line drawing can
629 * be done in a pinch).
631 static void init_fonts(int pick_width
) {
636 int fw_dontcare
, fw_bold
;
645 if (cfg
.fontisbold
) {
646 fw_dontcare
= FW_BOLD
;
649 fw_dontcare
= FW_DONTCARE
;
655 font_height
= cfg
.fontheight
;
656 font_width
= pick_width
;
659 fonts[i] = CreateFont (font_height, font_width, 0, 0, w, FALSE, u, FALSE, \
660 c, OUT_DEFAULT_PRECIS, \
661 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, \
662 FIXED_PITCH | FF_DONTCARE, cfg.font)
664 if (cfg
.vtmode
!= VT_OEMONLY
) {
665 f(FONT_NORMAL
, cfg
.fontcharset
, fw_dontcare
, FALSE
);
667 SelectObject (hdc
, fonts
[FONT_NORMAL
]);
668 GetTextMetrics(hdc
, &tm
);
669 font_height
= tm
.tmHeight
;
670 font_width
= tm
.tmAveCharWidth
;
672 f(FONT_UNDERLINE
, cfg
.fontcharset
, fw_dontcare
, TRUE
);
674 if (bold_mode
== BOLD_FONT
) {
675 f(FONT_BOLD
, cfg
.fontcharset
, fw_bold
, FALSE
);
676 f(FONT_BOLDUND
, cfg
.fontcharset
, fw_bold
, TRUE
);
679 if (cfg
.vtmode
== VT_OEMANSI
) {
680 f(FONT_OEM
, OEM_CHARSET
, fw_dontcare
, FALSE
);
681 f(FONT_OEMUND
, OEM_CHARSET
, fw_dontcare
, TRUE
);
683 if (bold_mode
== BOLD_FONT
) {
684 f(FONT_OEMBOLD
, OEM_CHARSET
, fw_bold
, FALSE
);
685 f(FONT_OEMBOLDUND
, OEM_CHARSET
, fw_bold
, TRUE
);
691 f(FONT_OEM
, cfg
.fontcharset
, fw_dontcare
, FALSE
);
693 SelectObject (hdc
, fonts
[FONT_OEM
]);
694 GetTextMetrics(hdc
, &tm
);
695 font_height
= tm
.tmHeight
;
696 font_width
= tm
.tmAveCharWidth
;
698 f(FONT_OEMUND
, cfg
.fontcharset
, fw_dontcare
, TRUE
);
700 if (bold_mode
== BOLD_FONT
) {
701 f(FONT_BOLD
, cfg
.fontcharset
, fw_bold
, FALSE
);
702 f(FONT_BOLDUND
, cfg
.fontcharset
, fw_bold
, TRUE
);
707 descent
= tm
.tmAscent
+ 1;
708 if (descent
>= font_height
)
709 descent
= font_height
- 1;
710 firstchar
= tm
.tmFirstChar
;
712 for (i
=0; i
<8; i
++) {
714 if (SelectObject (hdc
, fonts
[i
]) &&
715 GetTextMetrics(hdc
, &tm
) )
716 fsize
[i
] = tm
.tmAveCharWidth
+ 256 * tm
.tmHeight
;
722 ReleaseDC (hwnd
, hdc
);
724 /* ... This is wrong in OEM only mode */
725 if (fsize
[FONT_UNDERLINE
] != fsize
[FONT_NORMAL
] ||
726 (bold_mode
== BOLD_FONT
&&
727 fsize
[FONT_BOLDUND
] != fsize
[FONT_BOLD
])) {
729 DeleteObject (fonts
[FONT_UNDERLINE
]);
730 if (bold_mode
== BOLD_FONT
)
731 DeleteObject (fonts
[FONT_BOLDUND
]);
734 if (bold_mode
== BOLD_FONT
&&
735 fsize
[FONT_BOLD
] != fsize
[FONT_NORMAL
]) {
736 bold_mode
= BOLD_SHADOW
;
737 DeleteObject (fonts
[FONT_BOLD
]);
738 if (und_mode
== UND_FONT
)
739 DeleteObject (fonts
[FONT_BOLDUND
]);
743 /* With the fascist font painting it doesn't matter if the linedraw font
744 * isn't exactly the right size anymore so we don't have to check this.
746 if (cfg
.vtmode
== VT_OEMANSI
&& fsize
[FONT_OEM
] != fsize
[FONT_NORMAL
] ) {
747 if( cfg
.fontcharset
== OEM_CHARSET
)
749 MessageBox(NULL
, "The OEM and ANSI versions of this font are\n"
750 "different sizes. Using OEM-only mode instead",
751 "Font Size Mismatch", MB_ICONINFORMATION
| MB_OK
);
752 cfg
.vtmode
= VT_OEMONLY
;
754 else if( firstchar
< ' ' )
756 MessageBox(NULL
, "The OEM and ANSI versions of this font are\n"
757 "different sizes. Using XTerm mode instead",
758 "Font Size Mismatch", MB_ICONINFORMATION
| MB_OK
);
759 cfg
.vtmode
= VT_XWINDOWS
;
763 MessageBox(NULL
, "The OEM and ANSI versions of this font are\n"
764 "different sizes. Using ISO8859-1 mode instead",
765 "Font Size Mismatch", MB_ICONINFORMATION
| MB_OK
);
766 cfg
.vtmode
= VT_POORMAN
;
771 DeleteObject (fonts
[i
]);
777 void request_resize (int w
, int h
, int refont
) {
780 /* If the window is maximized supress resizing attempts */
781 if(IsZoomed(hwnd
)) return;
784 /* Don't do this in OEMANSI, you may get disable messages */
785 if (refont
&& w
!= cols
&& (cols
==80 || cols
==132)
786 && cfg
.vtmode
!= VT_OEMANSI
)
788 if (refont
&& w
!= cols
&& (cols
==80 || cols
==132))
791 /* If font width too big for screen should we shrink the font more ? */
793 font_width
= ((font_width
*cols
+w
/2)/w
);
800 DeleteObject(fonts
[i
]);
802 bold_mode
= cfg
.bold_colour ? BOLD_COLOURS
: BOLD_FONT
;
804 init_fonts(font_width
);
808 static int first_time
= 1;
814 /* Get the size of the screen */
815 if (GetClientRect(GetDesktopWindow(),&ss
))
816 /* first_time = 0 */;
817 else { first_time
= 2; break; }
819 /* Make sure the values are sane */
820 width
= (ss
.right
-ss
.left
-extra_width
) / font_width
;
821 height
= (ss
.bottom
-ss
.top
-extra_height
) / font_height
;
823 if (w
>width
) w
=width
;
824 if (h
>height
) h
=height
;
830 width
= extra_width
+ font_width
* w
;
831 height
= extra_height
+ font_height
* h
;
833 SetWindowPos (hwnd
, NULL
, 0, 0, width
, height
,
834 SWP_NOACTIVATE
| SWP_NOCOPYBITS
|
835 SWP_NOMOVE
| SWP_NOZORDER
);
838 static void click (Mouse_Button b
, int x
, int y
) {
839 int thistime
= GetMessageTime();
841 if (lastbtn
== b
&& thistime
- lasttime
< dbltime
) {
842 lastact
= (lastact
== MA_CLICK ? MA_2CLK
:
843 lastact
== MA_2CLK ? MA_3CLK
:
844 lastact
== MA_3CLK ? MA_CLICK
: MA_NOTHING
);
849 if (lastact
!= MA_NOTHING
)
850 term_mouse (b
, lastact
, x
, y
);
854 static LRESULT CALLBACK
WndProc (HWND hwnd
, UINT message
,
855 WPARAM wParam
, LPARAM lParam
) {
857 static int ignore_size
= FALSE
;
858 static int ignore_clip
= FALSE
;
859 static int ignore_keymenu
= TRUE
;
860 static int just_reconfigged
= FALSE
;
864 if (pending_netevent
)
865 enact_pending_netevent();
873 if (!cfg
.warn_on_close
|| session_closed
||
874 MessageBox(hwnd
, "Are you sure you want to close this session?",
875 "PuTTY Exit Confirmation",
876 MB_ICONWARNING
| MB_OKCANCEL
) == IDOK
)
883 switch (wParam
& ~0xF) { /* low 4 bits reserved to Windows */
886 return 0; /* don't put up system menu on Alt */
899 PROCESS_INFORMATION pi
;
900 HANDLE filemap
= NULL
;
902 if (wParam
== IDM_DUPSESS
) {
904 * Allocate a file-mapping memory chunk for the
907 SECURITY_ATTRIBUTES sa
;
910 sa
.nLength
= sizeof(sa
);
911 sa
.lpSecurityDescriptor
= NULL
;
912 sa
.bInheritHandle
= TRUE
;
913 filemap
= CreateFileMapping((HANDLE
)0xFFFFFFFF,
920 p
= (Config
*)MapViewOfFile(filemap
,
922 0, 0, sizeof(Config
));
924 *p
= cfg
; /* structure copy */
928 sprintf(c
, "putty &%p", filemap
);
930 } else if (wParam
== IDM_SAVEDSESS
) {
931 char *session
= sessions
[(lParam
- IDM_SAVED_MIN
) / 16];
932 cl
= malloc(16 + strlen(session
)); /* 8, but play safe */
934 cl
= NULL
; /* not a very important failure mode */
936 sprintf(cl
, "putty @%s", session
);
942 GetModuleFileName (NULL
, b
, sizeof(b
)-1);
944 si
.lpReserved
= NULL
;
949 si
.lpReserved2
= NULL
;
950 CreateProcess (b
, cl
, NULL
, NULL
, TRUE
,
951 NORMAL_PRIORITY_CLASS
, NULL
, NULL
, &si
, &pi
);
954 CloseHandle(filemap
);
960 if (!do_reconfig(hwnd
))
962 just_reconfigged
= TRUE
;
967 DeleteObject(fonts
[i
]);
969 bold_mode
= cfg
.bold_colour ? BOLD_COLOURS
: BOLD_FONT
;
973 /* Telnet will change local echo -> remote if the remote asks */
974 if (cfg
.protocol
!= PROT_TELNET
)
975 ldisc
= (cfg
.ldisc_term ?
&ldisc_term
: &ldisc_simple
);
983 /* Enable or disable the scroll bar, etc */
985 LONG nflg
, flag
= GetWindowLong(hwnd
, GWL_STYLE
);
988 if (cfg
.scrollbar
) nflg
|= WS_VSCROLL
;
989 else nflg
&= ~WS_VSCROLL
;
991 nflg
&= ~(WS_THICKFRAME
|WS_MAXIMIZEBOX
);
993 nflg
|= (WS_THICKFRAME
|WS_MAXIMIZEBOX
);
999 SetWindowLong(hwnd
, GWL_STYLE
, nflg
);
1000 SendMessage (hwnd
, WM_IGNORE_SIZE
, 0, 0);
1001 SetWindowPos(hwnd
, NULL
, 0,0,0,0,
1002 SWP_NOACTIVATE
|SWP_NOCOPYBITS
|
1003 SWP_NOMOVE
|SWP_NOSIZE
| SWP_NOZORDER
|
1006 GetWindowRect (hwnd
, &wr
);
1007 GetClientRect (hwnd
, &cr
);
1008 extra_width
= wr
.right
- wr
.left
- cr
.right
+ cr
.left
;
1009 extra_height
= wr
.bottom
- wr
.top
- cr
.bottom
+ cr
.top
;
1013 term_size(cfg
.height
, cfg
.width
, cfg
.savelines
);
1014 InvalidateRect(hwnd
, NULL
, TRUE
);
1015 SetWindowPos (hwnd
, NULL
, 0, 0,
1016 extra_width
+ font_width
* cfg
.width
,
1017 extra_height
+ font_height
* cfg
.height
,
1018 SWP_NOACTIVATE
| SWP_NOCOPYBITS
|
1019 SWP_NOMOVE
| SWP_NOZORDER
);
1020 if (IsIconic(hwnd
)) {
1021 SetWindowText (hwnd
,
1022 cfg
.win_name_always ? window_name
: icon_name
);
1031 case IDM_TEL_AYT
: back
->special (TS_AYT
); break;
1032 case IDM_TEL_BRK
: back
->special (TS_BRK
); break;
1033 case IDM_TEL_SYNCH
: back
->special (TS_SYNCH
); break;
1034 case IDM_TEL_EC
: back
->special (TS_EC
); break;
1035 case IDM_TEL_EL
: back
->special (TS_EL
); break;
1036 case IDM_TEL_GA
: back
->special (TS_GA
); break;
1037 case IDM_TEL_NOP
: back
->special (TS_NOP
); break;
1038 case IDM_TEL_ABORT
: back
->special (TS_ABORT
); break;
1039 case IDM_TEL_AO
: back
->special (TS_AO
); break;
1040 case IDM_TEL_IP
: back
->special (TS_IP
); break;
1041 case IDM_TEL_SUSP
: back
->special (TS_SUSP
); break;
1042 case IDM_TEL_EOR
: back
->special (TS_EOR
); break;
1043 case IDM_TEL_EOF
: back
->special (TS_EOF
); break;
1048 if (wParam
>= IDM_SAVED_MIN
&& wParam
<= IDM_SAVED_MAX
) {
1049 SendMessage(hwnd
, WM_SYSCOMMAND
, IDM_SAVEDSESS
, wParam
);
1054 #define X_POS(l) ((int)(short)LOWORD(l))
1055 #define Y_POS(l) ((int)(short)HIWORD(l))
1057 #define TO_CHR_X(x) (((x)<0 ? (x)-font_width+1 : (x)) / font_width)
1058 #define TO_CHR_Y(y) (((y)<0 ? (y)-font_height+1: (y)) / font_height)
1060 case WM_LBUTTONDOWN
:
1061 click (MB_SELECT
, TO_CHR_X(X_POS(lParam
)),
1062 TO_CHR_Y(Y_POS(lParam
)));
1066 term_mouse (MB_SELECT
, MA_RELEASE
, TO_CHR_X(X_POS(lParam
)),
1067 TO_CHR_Y(Y_POS(lParam
)));
1070 case WM_MBUTTONDOWN
:
1072 click (cfg
.mouse_is_xterm ? MB_PASTE
: MB_EXTEND
,
1073 TO_CHR_X(X_POS(lParam
)),
1074 TO_CHR_Y(Y_POS(lParam
)));
1077 term_mouse (cfg
.mouse_is_xterm ? MB_PASTE
: MB_EXTEND
,
1078 MA_RELEASE
, TO_CHR_X(X_POS(lParam
)),
1079 TO_CHR_Y(Y_POS(lParam
)));
1082 case WM_RBUTTONDOWN
:
1084 click (cfg
.mouse_is_xterm ? MB_EXTEND
: MB_PASTE
,
1085 TO_CHR_X(X_POS(lParam
)),
1086 TO_CHR_Y(Y_POS(lParam
)));
1089 term_mouse (cfg
.mouse_is_xterm ? MB_EXTEND
: MB_PASTE
,
1090 MA_RELEASE
, TO_CHR_X(X_POS(lParam
)),
1091 TO_CHR_Y(Y_POS(lParam
)));
1096 * Add the mouse position and message time to the random
1097 * number noise, if we're using ssh.
1099 if (cfg
.protocol
== PROT_SSH
)
1100 noise_ultralight(lParam
);
1102 if (wParam
& (MK_LBUTTON
| MK_MBUTTON
| MK_RBUTTON
)) {
1104 if (wParam
& MK_LBUTTON
)
1106 else if (wParam
& MK_MBUTTON
)
1107 b
= cfg
.mouse_is_xterm ? MB_PASTE
: MB_EXTEND
;
1109 b
= cfg
.mouse_is_xterm ? MB_EXTEND
: MB_PASTE
;
1110 term_mouse (b
, MA_DRAG
, TO_CHR_X(X_POS(lParam
)),
1111 TO_CHR_Y(Y_POS(lParam
)));
1114 case WM_IGNORE_CLIP
:
1115 ignore_clip
= wParam
; /* don't panic on DESTROYCLIPBOARD */
1117 case WM_IGNORE_KEYMENU
:
1118 ignore_keymenu
= wParam
; /* do or don't ignore SC_KEYMENU */
1120 case WM_DESTROYCLIPBOARD
:
1123 ignore_clip
= FALSE
;
1128 hdc
= BeginPaint (hwnd
, &p
);
1130 SelectPalette (hdc
, pal
, TRUE
);
1131 RealizePalette (hdc
);
1133 term_paint (hdc
, p
.rcPaint
.left
, p
.rcPaint
.top
,
1134 p
.rcPaint
.right
, p
.rcPaint
.bottom
);
1135 SelectObject (hdc
, GetStockObject(SYSTEM_FONT
));
1136 SelectObject (hdc
, GetStockObject(WHITE_PEN
));
1137 EndPaint (hwnd
, &p
);
1141 /* Notice we can get multiple netevents, FD_READ, FD_WRITE etc
1142 * but the only one that's likely to try to overload us is FD_READ.
1143 * This means buffering just one is fine.
1145 if (pending_netevent
)
1146 enact_pending_netevent();
1148 pending_netevent
= TRUE
;
1149 pend_netevent_wParam
=wParam
;
1150 pend_netevent_lParam
=lParam
;
1162 case WM_IGNORE_SIZE
:
1163 ignore_size
= TRUE
; /* don't panic on next WM_SIZE msg */
1165 case WM_ENTERSIZEMOVE
:
1168 case WM_EXITSIZEMOVE
:
1173 int width
, height
, w
, h
, ew
, eh
;
1174 LPRECT r
= (LPRECT
)lParam
;
1176 width
= r
->right
- r
->left
- extra_width
;
1177 height
= r
->bottom
- r
->top
- extra_height
;
1178 w
= (width
+ font_width
/2) / font_width
; if (w
< 1) w
= 1;
1179 h
= (height
+ font_height
/2) / font_height
; if (h
< 1) h
= 1;
1180 UpdateSizeTip(hwnd
, w
, h
);
1181 ew
= width
- w
* font_width
;
1182 eh
= height
- h
* font_height
;
1184 if (wParam
== WMSZ_LEFT
||
1185 wParam
== WMSZ_BOTTOMLEFT
||
1186 wParam
== WMSZ_TOPLEFT
)
1192 if (wParam
== WMSZ_TOP
||
1193 wParam
== WMSZ_TOPRIGHT
||
1194 wParam
== WMSZ_TOPLEFT
)
1204 /* break; (never reached) */
1206 if (wParam
== SIZE_MINIMIZED
) {
1207 SetWindowText (hwnd
,
1208 cfg
.win_name_always ? window_name
: icon_name
);
1211 if (wParam
== SIZE_RESTORED
|| wParam
== SIZE_MAXIMIZED
)
1212 SetWindowText (hwnd
, window_name
);
1214 int width
, height
, w
, h
;
1215 #if 0 /* we have fixed this using WM_SIZING now */
1219 width
= LOWORD(lParam
);
1220 height
= HIWORD(lParam
);
1221 w
= width
/ font_width
; if (w
< 1) w
= 1;
1222 h
= height
/ font_height
; if (h
< 1) h
= 1;
1223 #if 0 /* we have fixed this using WM_SIZING now */
1224 ew
= width
- w
* font_width
;
1225 eh
= height
- h
* font_height
;
1226 if (ew
!= 0 || eh
!= 0) {
1228 GetWindowRect (hwnd
, &r
);
1229 SendMessage (hwnd
, WM_IGNORE_SIZE
, 0, 0);
1230 SetWindowPos (hwnd
, NULL
, 0, 0,
1231 r
.right
- r
.left
- ew
, r
.bottom
- r
.top
- eh
,
1232 SWP_NOACTIVATE
| SWP_NOMOVE
| SWP_NOZORDER
);
1235 if (w
!= cols
|| h
!= rows
|| just_reconfigged
) {
1237 term_size (h
, w
, cfg
.savelines
);
1239 just_reconfigged
= FALSE
;
1242 ignore_size
= FALSE
;
1245 switch (LOWORD(wParam
)) {
1246 case SB_BOTTOM
: term_scroll(-1, 0); break;
1247 case SB_TOP
: term_scroll(+1, 0); break;
1248 case SB_LINEDOWN
: term_scroll (0, +1); break;
1249 case SB_LINEUP
: term_scroll (0, -1); break;
1250 case SB_PAGEDOWN
: term_scroll (0, +rows
/2); break;
1251 case SB_PAGEUP
: term_scroll (0, -rows
/2); break;
1252 case SB_THUMBPOSITION
: case SB_THUMBTRACK
:
1253 term_scroll (1, HIWORD(wParam
)); break;
1256 case WM_PALETTECHANGED
:
1257 if ((HWND
) wParam
!= hwnd
&& pal
!= NULL
) {
1258 HDC hdc
= get_ctx();
1260 if (RealizePalette (hdc
) > 0)
1266 case WM_QUERYNEWPALETTE
:
1268 HDC hdc
= get_ctx();
1270 if (RealizePalette (hdc
) > 0)
1282 * Add the scan code and keypress timing to the random
1283 * number noise, if we're using ssh.
1285 if (cfg
.protocol
== PROT_SSH
)
1286 noise_ultralight(lParam
);
1289 * We don't do TranslateMessage since it disassociates the
1290 * resulting CHAR message from the KEYDOWN that sparked it,
1291 * which we occasionally don't want. Instead, we process
1292 * KEYDOWN, and call the Win32 translator functions so that
1293 * we get the translations under _our_ control.
1296 unsigned char buf
[20];
1299 len
= TranslateKey (message
, wParam
, lParam
, buf
);
1301 return DefWindowProc (hwnd
, message
, wParam
, lParam
);
1302 ldisc
->send (buf
, len
);
1308 * Nevertheless, we are prepared to deal with WM_CHAR
1309 * messages, should they crop up. So if someone wants to
1310 * post the things to us as part of a macro manoeuvre,
1311 * we're ready to cope.
1314 char c
= xlat_kbd2tty((unsigned char)wParam
);
1315 ldisc
->send (&c
, 1);
1320 return DefWindowProc (hwnd
, message
, wParam
, lParam
);
1324 * Draw a line of text in the window, at given character
1325 * coordinates, in given attributes.
1327 * We are allowed to fiddle with the contents of `text'.
1329 void do_text (Context ctx
, int x
, int y
, char *text
, int len
,
1330 unsigned long attr
, int lattr
) {
1332 int nfg
, nbg
, nfont
;
1335 int force_manual_underline
= 0;
1336 int fnt_width
= font_width
*(1+(lattr
!=LATTR_NORM
));
1337 static int *IpDx
= 0, IpDxLEN
= 0;;
1339 if (len
>IpDxLEN
|| IpDx
[0] != fnt_width
) {
1343 IpDx
= smalloc((len
+16)*sizeof(int));
1346 for(i
=0; i
<IpDxLEN
; i
++)
1347 IpDx
[i
] = fnt_width
;
1353 if (attr
& ATTR_ACTCURS
) {
1354 attr
&= (bold_mode
== BOLD_COLOURS ?
0x300200 : 0x300300);
1355 attr
^= ATTR_CUR_XOR
;
1359 if (cfg
.vtmode
== VT_OEMONLY
)
1363 * Map high-half characters in order to approximate ISO using
1364 * OEM character set. No characters are missing if the OEM codepage
1367 if (nfont
& FONT_OEM
) {
1369 for (i
=0; i
<len
; i
++)
1370 if (text
[i
] >= '\xA0' && text
[i
] <= '\xFF') {
1372 /* This is CP850 ... perfect translation */
1373 static const char oemhighhalf
[] =
1374 "\x20\xAD\xBD\x9C\xCF\xBE\xDD\xF5" /* A0-A7 */
1375 "\xF9\xB8\xA6\xAE\xAA\xF0\xA9\xEE" /* A8-AF */
1376 "\xF8\xF1\xFD\xFC\xEF\xE6\xF4\xFA" /* B0-B7 */
1377 "\xF7\xFB\xA7\xAF\xAC\xAB\xF3\xA8" /* B8-BF */
1378 "\xB7\xB5\xB6\xC7\x8E\x8F\x92\x80" /* C0-C7 */
1379 "\xD4\x90\xD2\xD3\xDE\xD6\xD7\xD8" /* C8-CF */
1380 "\xD1\xA5\xE3\xE0\xE2\xE5\x99\x9E" /* D0-D7 */
1381 "\x9D\xEB\xE9\xEA\x9A\xED\xE8\xE1" /* D8-DF */
1382 "\x85\xA0\x83\xC6\x84\x86\x91\x87" /* E0-E7 */
1383 "\x8A\x82\x88\x89\x8D\xA1\x8C\x8B" /* E8-EF */
1384 "\xD0\xA4\x95\xA2\x93\xE4\x94\xF6" /* F0-F7 */
1385 "\x9B\x97\xA3\x96\x81\xEC\xE7\x98" /* F8-FF */
1388 /* This is CP437 ... junk translation */
1389 static const unsigned char oemhighhalf
[] = {
1390 0xff, 0xad, 0x9b, 0x9c, 0x6f, 0x9d, 0x7c, 0x15,
1391 0x22, 0x43, 0xa6, 0xae, 0xaa, 0x2d, 0x52, 0xc4,
1392 0xf8, 0xf1, 0xfd, 0x33, 0x27, 0xe6, 0x14, 0xfa,
1393 0x2c, 0x31, 0xa7, 0xaf, 0xac, 0xab, 0x2f, 0xa8,
1394 0x41, 0x41, 0x41, 0x41, 0x8e, 0x8f, 0x92, 0x80,
1395 0x45, 0x90, 0x45, 0x45, 0x49, 0x49, 0x49, 0x49,
1396 0x44, 0xa5, 0x4f, 0x4f, 0x4f, 0x4f, 0x99, 0x78,
1397 0xed, 0x55, 0x55, 0x55, 0x9a, 0x59, 0x50, 0xe1,
1398 0x85, 0xa0, 0x83, 0x61, 0x84, 0x86, 0x91, 0x87,
1399 0x8a, 0x82, 0x88, 0x89, 0x8d, 0xa1, 0x8c, 0x8b,
1400 0x0b, 0xa4, 0x95, 0xa2, 0x93, 0x6f, 0x94, 0xf6,
1401 0xed, 0x97, 0xa3, 0x96, 0x81, 0x79, 0x70, 0x98
1404 text
[i
] = oemhighhalf
[(unsigned char)text
[i
] - 0xA0];
1408 if (attr
& ATTR_GBCHR
) {
1411 * GB mapping: map # to pound, and everything else stays
1414 for (i
=0; i
<len
; i
++)
1416 text
[i
] = cfg
.vtmode
== VT_OEMONLY ?
'\x9C' : '\xA3';
1417 } else if (attr
& ATTR_LINEDRW
) {
1420 static const char poorman
[] =
1421 "*#****\xB0\xB1**+++++-----++++|****\xA3\xB7";
1424 static const char oemmap_437
[] =
1425 "\x04\xB1****\xF8\xF1**\xD9\xBF\xDA\xC0\xC5"
1426 "\xC4\xC4\xC4\xC4\xC4\xC3\xB4\xC1\xC2\xB3\xF3\xF2\xE3*\x9C\xFA";
1429 static const char oemmap_850
[] =
1430 "\x04\xB1****\xF8\xF1**\xD9\xBF\xDA\xC0\xC5"
1431 "\xC4\xC4\xC4\xC4\xC4\xC3\xB4\xC1\xC2\xB3****\x9C\xFA";
1433 /* Poor windows font ... eg: windows courier */
1434 static const char oemmap
[] =
1435 "*\xB1****\xF8\xF1**\xD9\xBF\xDA\xC0\xC5"
1436 "\xC4\xC4\xC4\xC4\xC4\xC3\xB4\xC1\xC2\xB3****\x9C\xFA";
1439 * Line drawing mapping: map ` thru ~ (0x60 thru 0x7E) to
1440 * VT100 line drawing chars; everything else stays normal.
1442 switch (cfg
.vtmode
) {
1444 for (i
=0; i
<len
; i
++)
1445 if (text
[i
] >= '\x60' && text
[i
] <= '\x7E')
1446 text
[i
] += '\x01' - '\x60';
1449 /* Make sure we actually have an OEM font */
1450 if (fonts
[nfont
|FONT_OEM
]) {
1453 for (i
=0; i
<len
; i
++)
1454 if (text
[i
] >= '\x60' && text
[i
] <= '\x7E')
1455 text
[i
] = oemmap
[(unsigned char)text
[i
] - 0x60];
1459 for (i
=0; i
<len
; i
++)
1460 if (text
[i
] >= '\x60' && text
[i
] <= '\x7E')
1461 text
[i
] = poorman
[(unsigned char)text
[i
] - 0x60];
1466 nfg
= 2 * ((attr
& ATTR_FGMASK
) >> ATTR_FGSHIFT
);
1467 nbg
= 2 * ((attr
& ATTR_BGMASK
) >> ATTR_BGSHIFT
);
1468 if (bold_mode
== BOLD_FONT
&& (attr
& ATTR_BOLD
))
1470 if (und_mode
== UND_FONT
&& (attr
& ATTR_UNDER
))
1471 nfont
|= FONT_UNDERLINE
;
1474 if (nfont
&FONT_UNDERLINE
)
1475 force_manual_underline
= 1;
1476 /* Don't do the same for manual bold, it could be bad news. */
1478 nfont
&= ~(FONT_BOLD
|FONT_UNDERLINE
);
1480 if (attr
& ATTR_REVERSE
) {
1481 t
= nfg
; nfg
= nbg
; nbg
= t
;
1483 if (bold_mode
== BOLD_COLOURS
&& (attr
& ATTR_BOLD
))
1485 if (bold_mode
== BOLD_COLOURS
&& (attr
& ATTR_BLINK
))
1489 SelectObject (hdc
, fonts
[nfont
]);
1490 SetTextColor (hdc
, fg
);
1491 SetBkColor (hdc
, bg
);
1492 SetBkMode (hdc
, OPAQUE
);
1495 line_box
.right
= x
+fnt_width
*len
;
1496 line_box
.bottom
= y
+font_height
;
1497 ExtTextOut (hdc
, x
, y
, ETO_CLIPPED
|ETO_OPAQUE
, &line_box
, text
, len
, IpDx
);
1498 if (bold_mode
== BOLD_SHADOW
&& (attr
& ATTR_BOLD
)) {
1499 SetBkMode (hdc
, TRANSPARENT
);
1501 /* GRR: This draws the character outside it's box and can leave
1502 * 'droppings' even with the clip box! I suppose I could loop it
1503 * one character at a time ... yuk.
1505 * Or ... I could do a test print with "W", and use +1 or -1 for this
1506 * shift depending on if the leftmost column is blank...
1508 ExtTextOut (hdc
, x
-1, y
, ETO_CLIPPED
, &line_box
, text
, len
, IpDx
);
1510 if (force_manual_underline
||
1511 (und_mode
== UND_LINE
&& (attr
& ATTR_UNDER
))) {
1513 oldpen
= SelectObject (hdc
, CreatePen(PS_SOLID
, 0, fg
));
1514 MoveToEx (hdc
, x
, y
+descent
, NULL
);
1515 LineTo (hdc
, x
+len
*fnt_width
, y
+descent
);
1516 oldpen
= SelectObject (hdc
, oldpen
);
1517 DeleteObject (oldpen
);
1519 if (attr
& ATTR_PASCURS
) {
1522 pts
[0].x
= pts
[1].x
= pts
[4].x
= x
;
1523 pts
[2].x
= pts
[3].x
= x
+fnt_width
-1;
1524 pts
[0].y
= pts
[3].y
= pts
[4].y
= y
;
1525 pts
[1].y
= pts
[2].y
= y
+font_height
-1;
1526 oldpen
= SelectObject (hdc
, CreatePen(PS_SOLID
, 0, colours
[23]));
1527 Polyline (hdc
, pts
, 5);
1528 oldpen
= SelectObject (hdc
, oldpen
);
1529 DeleteObject (oldpen
);
1533 static int check_compose(int first
, int second
) {
1535 static char * composetbl
[] = {
1536 "++#", "AA@", "(([", "//\\", "))]", "(-{", "-)}", "/^|", "!!¡", "C/¢",
1537 "C|¢", "L-£", "L=£", "XO¤", "X0¤", "Y-¥", "Y=¥", "||¦", "SO§", "S!§",
1538 "S0§", "\"\"¨", "CO©", "C0©", "A_ª", "<<«", ",-¬", "--", "RO®",
1539 "-^¯", "0^°", "+-±", "2^²", "3^³", "''´", "/Uµ", "P!¶", ".^·", ",,¸",
1540 "1^¹", "O_º", ">>»", "14¼", "12½", "34¾", "??¿", "`AÀ", "'AÁ", "^AÂ",
1541 "~AÃ", "\"AÄ", "*AÅ", "AEÆ", ",CÇ", "`EÈ", "'EÉ", "^EÊ", "\"EË",
1542 "`IÌ", "'IÍ", "^IÎ", "\"IÏ", "-DÐ", "~NÑ", "`OÒ", "'OÓ", "^OÔ",
1543 "~OÕ", "\"OÖ", "XX×", "/OØ", "`UÙ", "'UÚ", "^UÛ", "\"UÜ", "'YÝ",
1544 "HTÞ", "ssß", "`aà", "'aá", "^aâ", "~aã", "\"aä", "*aå", "aeæ", ",cç",
1545 "`eè", "'eé", "^eê", "\"eë", "`iì", "'ií", "^iî", "\"iï", "-dð", "~nñ",
1546 "`oò", "'oó", "^oô", "~oõ", "\"oö", ":-÷", "o/ø", "`uù", "'uú", "^uû",
1547 "\"uü", "'yý", "htþ", "\"yÿ",
1551 static int recurse
= 0;
1558 sprintf(buf
, "cc(%d,%d)", first
, second
);
1563 for(c
=composetbl
; *c
; c
++) {
1564 if( (*c
)[0] == first
&& (*c
)[1] == second
)
1566 return (*c
)[2] & 0xFF;
1573 nc
= check_compose(second
, first
);
1575 nc
= check_compose(toupper(first
), toupper(second
));
1577 nc
= check_compose(toupper(second
), toupper(first
));
1585 * Translate a WM_(SYS)?KEY(UP|DOWN) message into a string of ASCII
1586 * codes. Returns number of bytes used or zero to drop the message
1587 * or -1 to forward the message to windows.
1589 static int TranslateKey(UINT message
, WPARAM wParam
, LPARAM lParam
, unsigned char *output
) {
1591 int scan
, left_alt
= 0, key_down
, shift_state
;
1593 unsigned char * p
= output
;
1595 static WORD keys
[3];
1596 static int compose_state
= 0;
1597 static int compose_char
= 0;
1598 static WPARAM compose_key
= 0;
1600 r
= GetKeyboardState(keystate
);
1601 if (!r
) memset(keystate
, 0, sizeof(keystate
));
1604 /* Note if AltGr was pressed and if it was used as a compose key */
1605 if (wParam
== VK_MENU
&& (HIWORD(lParam
)&KF_EXTENDED
))
1607 keystate
[VK_RMENU
] = keystate
[VK_MENU
];
1608 if (!compose_state
) compose_key
= wParam
;
1610 if (wParam
== VK_APPS
&& !compose_state
)
1611 compose_key
= wParam
;
1613 if (wParam
== compose_key
)
1615 if (compose_state
== 0 && (HIWORD(lParam
)&(KF_UP
|KF_REPEAT
))==0)
1617 else if (compose_state
== 1 && (HIWORD(lParam
)&KF_UP
))
1622 else if (compose_state
==1 && wParam
!= VK_CONTROL
)
1625 /* Nastyness with NUMLock - Shift-NUMLock is left alone though */
1626 if ( (cfg
.funky_type
== 0 || (cfg
.funky_type
== 1 && app_keypad_keys
))
1627 && wParam
==VK_NUMLOCK
&& !(keystate
[VK_SHIFT
]&0x80)) {
1629 wParam
= VK_EXECUTE
;
1631 /* UnToggle NUMLock */
1632 if ((HIWORD(lParam
)&(KF_UP
|KF_REPEAT
))==0)
1633 keystate
[VK_NUMLOCK
] ^= 1;
1636 /* And write back the 'adjusted' state */
1637 SetKeyboardState (keystate
);
1640 /* Disable Auto repeat if required */
1641 if (repeat_off
&& (HIWORD(lParam
)&(KF_UP
|KF_REPEAT
))==KF_REPEAT
)
1644 if ((HIWORD(lParam
)&KF_ALTDOWN
) && (keystate
[VK_RMENU
]&0x80) == 0)
1647 key_down
= ((HIWORD(lParam
)&KF_UP
)==0);
1649 /* Make sure Ctrl-ALT is not the same as AltGr for ToAscii */
1650 if (left_alt
&& (keystate
[VK_CONTROL
]&0x80))
1651 keystate
[VK_MENU
] = 0;
1653 scan
= (HIWORD(lParam
) & (KF_UP
| KF_EXTENDED
| 0xFF));
1654 shift_state
= ((keystate
[VK_SHIFT
]&0x80)!=0)
1655 + ((keystate
[VK_CONTROL
]&0x80)!=0)*2;
1658 * Record that we pressed key so the scroll window can be reset, but
1659 * be careful to avoid Shift-UP/Down
1661 if( wParam
!= VK_SHIFT
&& wParam
!= VK_PRIOR
&& wParam
!= VK_NEXT
) {
1665 /* Make sure we're not pasting */
1666 if (key_down
) term_nopaste();
1668 if (compose_state
>1 && left_alt
) compose_state
= 0;
1670 /* Sanitize the number pad if not using a PC NumPad */
1671 if( left_alt
|| (app_keypad_keys
&& cfg
.funky_type
!= 2)
1672 || cfg
.nethack_keypad
|| compose_state
)
1674 if ((HIWORD(lParam
)&KF_EXTENDED
) == 0)
1679 case VK_INSERT
: nParam
= VK_NUMPAD0
; break;
1680 case VK_END
: nParam
= VK_NUMPAD1
; break;
1681 case VK_DOWN
: nParam
= VK_NUMPAD2
; break;
1682 case VK_NEXT
: nParam
= VK_NUMPAD3
; break;
1683 case VK_LEFT
: nParam
= VK_NUMPAD4
; break;
1684 case VK_CLEAR
: nParam
= VK_NUMPAD5
; break;
1685 case VK_RIGHT
: nParam
= VK_NUMPAD6
; break;
1686 case VK_HOME
: nParam
= VK_NUMPAD7
; break;
1687 case VK_UP
: nParam
= VK_NUMPAD8
; break;
1688 case VK_PRIOR
: nParam
= VK_NUMPAD9
; break;
1689 case VK_DELETE
: nParam
= VK_DECIMAL
; break;
1693 if (keystate
[VK_NUMLOCK
]&1) shift_state
|= 1;
1699 /* If a key is pressed and AltGr is not active */
1700 if (key_down
&& (keystate
[VK_RMENU
]&0x80) == 0 && !compose_state
)
1702 /* Okay, prepare for most alts then ...*/
1703 if (left_alt
) *p
++ = '\033';
1705 /* Lets see if it's a pattern we know all about ... */
1706 if (wParam
== VK_PRIOR
&& shift_state
== 1) {
1707 SendMessage (hwnd
, WM_VSCROLL
, SB_PAGEUP
, 0);
1710 if (wParam
== VK_NEXT
&& shift_state
== 1) {
1711 SendMessage (hwnd
, WM_VSCROLL
, SB_PAGEDOWN
, 0);
1714 if (left_alt
&& wParam
== VK_F4
&& cfg
.alt_f4
) {
1717 if (left_alt
&& wParam
== VK_SPACE
&& cfg
.alt_space
) {
1719 SendMessage (hwnd
, WM_IGNORE_KEYMENU
, FALSE
, 0);
1720 SendMessage (hwnd
, WM_SYSCOMMAND
, SC_KEYMENU
, 0);
1721 SendMessage (hwnd
, WM_IGNORE_KEYMENU
, TRUE
, 0);
1725 /* Nethack keypad */
1726 if (cfg
.nethack_keypad
&& !left_alt
) {
1728 case VK_NUMPAD1
: *p
++ = shift_state ?
'B': 'b'; return p
-output
;
1729 case VK_NUMPAD2
: *p
++ = shift_state ?
'J': 'j'; return p
-output
;
1730 case VK_NUMPAD3
: *p
++ = shift_state ?
'N': 'n'; return p
-output
;
1731 case VK_NUMPAD4
: *p
++ = shift_state ?
'H': 'h'; return p
-output
;
1732 case VK_NUMPAD5
: *p
++ = shift_state ?
'.': '.'; return p
-output
;
1733 case VK_NUMPAD6
: *p
++ = shift_state ?
'L': 'l'; return p
-output
;
1734 case VK_NUMPAD7
: *p
++ = shift_state ?
'Y': 'y'; return p
-output
;
1735 case VK_NUMPAD8
: *p
++ = shift_state ?
'K': 'k'; return p
-output
;
1736 case VK_NUMPAD9
: *p
++ = shift_state ?
'U': 'u'; return p
-output
;
1740 /* Application Keypad */
1744 if ( cfg
.funky_type
== 0 ||
1745 ( cfg
.funky_type
== 1 && app_keypad_keys
)) switch(wParam
) {
1746 case VK_EXECUTE
: xkey
= 'P'; break;
1747 case VK_DIVIDE
: xkey
= 'Q'; break;
1748 case VK_MULTIPLY
:xkey
= 'R'; break;
1749 case VK_SUBTRACT
:xkey
= 'S'; break;
1751 if(app_keypad_keys
) switch(wParam
) {
1752 case VK_NUMPAD0
: xkey
= 'p'; break;
1753 case VK_NUMPAD1
: xkey
= 'q'; break;
1754 case VK_NUMPAD2
: xkey
= 'r'; break;
1755 case VK_NUMPAD3
: xkey
= 's'; break;
1756 case VK_NUMPAD4
: xkey
= 't'; break;
1757 case VK_NUMPAD5
: xkey
= 'u'; break;
1758 case VK_NUMPAD6
: xkey
= 'v'; break;
1759 case VK_NUMPAD7
: xkey
= 'w'; break;
1760 case VK_NUMPAD8
: xkey
= 'x'; break;
1761 case VK_NUMPAD9
: xkey
= 'y'; break;
1763 case VK_DECIMAL
: xkey
= 'n'; break;
1764 case VK_ADD
: if(shift_state
) xkey
= 'm';
1768 if (HIWORD(lParam
)&KF_EXTENDED
)
1776 if (xkey
>='P' && xkey
<='S')
1777 p
+= sprintf((char *)p
, "\x1B%c", xkey
);
1779 p
+= sprintf((char *)p
, "\x1B?%c", xkey
);
1782 p
+= sprintf((char *)p
, "\x1BO%c", xkey
);
1787 if (wParam
== VK_BACK
&& shift_state
== 0 ) /* Backspace */
1789 *p
++ = (cfg
.bksp_is_delete ?
0x7F : 0x08);
1792 if (wParam
== VK_TAB
&& shift_state
== 1 ) /* Shift tab */
1794 *p
++ = 0x1B; *p
++ = '['; *p
++ = 'Z'; return p
- output
;
1796 if (wParam
== VK_SPACE
&& shift_state
== 2 ) /* Ctrl-Space */
1798 *p
++ = 0; return p
- output
;
1800 if (wParam
== VK_SPACE
&& shift_state
== 3 ) /* Ctrl-Shift-Space */
1802 *p
++ = 160; return p
- output
;
1804 if (wParam
== VK_CANCEL
&& shift_state
== 2 ) /* Ctrl-Break */
1806 *p
++ = 3; return p
- output
;
1808 /* Control-2 to Control-8 are special */
1809 if (shift_state
== 2 && wParam
>= '2' && wParam
<= '8')
1811 *p
++ = "\000\033\034\035\036\037\177"[wParam
-'2'];
1814 if (shift_state
== 2 && wParam
== 0xBD) {
1818 if (shift_state
== 2 && wParam
== 0xDF) {
1822 if (shift_state
== 0 && wParam
== VK_RETURN
&& cr_lf_return
) {
1823 *p
++ = '\r'; *p
++ = '\n';
1828 * Next, all the keys that do tilde codes. (ESC '[' nn '~',
1829 * for integer decimal nn.)
1831 * We also deal with the weird ones here. Linux VCs replace F1
1832 * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
1833 * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
1838 case VK_F1
: code
= (keystate
[VK_SHIFT
] & 0x80 ?
23 : 11); break;
1839 case VK_F2
: code
= (keystate
[VK_SHIFT
] & 0x80 ?
24 : 12); break;
1840 case VK_F3
: code
= (keystate
[VK_SHIFT
] & 0x80 ?
25 : 13); break;
1841 case VK_F4
: code
= (keystate
[VK_SHIFT
] & 0x80 ?
26 : 14); break;
1842 case VK_F5
: code
= (keystate
[VK_SHIFT
] & 0x80 ?
28 : 15); break;
1843 case VK_F6
: code
= (keystate
[VK_SHIFT
] & 0x80 ?
29 : 17); break;
1844 case VK_F7
: code
= (keystate
[VK_SHIFT
] & 0x80 ?
31 : 18); break;
1845 case VK_F8
: code
= (keystate
[VK_SHIFT
] & 0x80 ?
32 : 19); break;
1846 case VK_F9
: code
= (keystate
[VK_SHIFT
] & 0x80 ?
33 : 20); break;
1847 case VK_F10
: code
= (keystate
[VK_SHIFT
] & 0x80 ?
34 : 21); break;
1848 case VK_F11
: code
= 23; break;
1849 case VK_F12
: code
= 24; break;
1850 case VK_F13
: code
= 25; break;
1851 case VK_F14
: code
= 26; break;
1852 case VK_F15
: code
= 28; break;
1853 case VK_F16
: code
= 29; break;
1854 case VK_F17
: code
= 31; break;
1855 case VK_F18
: code
= 32; break;
1856 case VK_F19
: code
= 33; break;
1857 case VK_F20
: code
= 34; break;
1858 case VK_HOME
: code
= 1; break;
1859 case VK_INSERT
: code
= 2; break;
1860 case VK_DELETE
: code
= 3; break;
1861 case VK_END
: code
= 4; break;
1862 case VK_PRIOR
: code
= 5; break;
1863 case VK_NEXT
: code
= 6; break;
1865 if (cfg
.funky_type
== 1 && code
>= 11 && code
<= 15) {
1866 p
+= sprintf((char *)p
, "\x1B[[%c", code
+ 'A' - 11);
1869 if (cfg
.funky_type
== 2 && code
>= 11 && code
<= 14) {
1870 p
+= sprintf((char *)p
, "\x1BO%c", code
+ 'P' - 11);
1873 if (cfg
.rxvt_homeend
&& (code
== 1 || code
== 4)) {
1874 p
+= sprintf((char *)p
, code
== 1 ?
"\x1B[H" : "\x1BOw");
1878 p
+= sprintf((char *)p
, "\x1B[%d~", code
);
1883 * Now the remaining keys (arrows and Keypad 5. Keypad 5 for
1884 * some reason seems to send VK_CLEAR to Windows...).
1889 case VK_UP
: xkey
= 'A'; break;
1890 case VK_DOWN
: xkey
= 'B'; break;
1891 case VK_RIGHT
: xkey
= 'C'; break;
1892 case VK_LEFT
: xkey
= 'D'; break;
1893 case VK_CLEAR
: xkey
= 'G'; break;
1898 p
+= sprintf((char *)p
, "\x1B%c", xkey
);
1899 else if (app_cursor_keys
)
1900 p
+= sprintf((char *)p
, "\x1BO%c", xkey
);
1902 p
+= sprintf((char *)p
, "\x1B[%c", xkey
);
1908 /* Okay we've done everything interesting; let windows deal with
1909 * the boring stuff */
1911 BOOL capsOn
=keystate
[VK_CAPITAL
] !=0;
1913 /* helg: clear CAPS LOCK state if caps lock switches to cyrillic */
1914 if(cfg
.xlat_capslockcyr
)
1915 keystate
[VK_CAPITAL
] = 0;
1917 r
= ToAscii (wParam
, scan
, keystate
, keys
, 0);
1923 unsigned char ch
= (unsigned char)keys
[i
];
1925 if (compose_state
==2 && (ch
&0x80) == 0 && ch
>' ') {
1930 if (compose_state
==3 && (ch
&0x80) == 0 && ch
>' ') {
1934 if ((nc
=check_compose(compose_char
,ch
)) == -1)
1939 *p
++ = xlat_kbd2tty((unsigned char)nc
);
1945 if( left_alt
&& key_down
) *p
++ = '\033';
1951 ch
= xlat_latkbd2win(ch
);
1952 *p
++ = xlat_kbd2tty(ch
);
1956 /* This is so the ALT-Numpad and dead keys work correctly. */
1963 /* This stops ALT press-release doing a 'COMMAND MENU' function */
1965 if (message
== WM_SYSKEYUP
&& wParam
== VK_MENU
)
1967 keystate
[VK_MENU
] = 0;
1975 void set_title (char *title
) {
1976 sfree (window_name
);
1977 window_name
= smalloc(1+strlen(title
));
1978 strcpy (window_name
, title
);
1979 if (cfg
.win_name_always
|| !IsIconic(hwnd
))
1980 SetWindowText (hwnd
, title
);
1983 void set_icon (char *title
) {
1985 icon_name
= smalloc(1+strlen(title
));
1986 strcpy (icon_name
, title
);
1987 if (!cfg
.win_name_always
&& IsIconic(hwnd
))
1988 SetWindowText (hwnd
, title
);
1991 void set_sbar (int total
, int start
, int page
) {
1994 if (!cfg
.scrollbar
) return;
1996 si
.cbSize
= sizeof(si
);
1997 si
.fMask
= SIF_ALL
| SIF_DISABLENOSCROLL
;
1999 si
.nMax
= total
- 1;
2003 SetScrollInfo (hwnd
, SB_VERT
, &si
, TRUE
);
2006 Context
get_ctx(void) {
2011 SelectPalette (hdc
, pal
, FALSE
);
2017 void free_ctx (Context ctx
) {
2018 SelectPalette (ctx
, GetStockObject (DEFAULT_PALETTE
), FALSE
);
2019 ReleaseDC (hwnd
, ctx
);
2022 static void real_palette_set (int n
, int r
, int g
, int b
) {
2024 logpal
->palPalEntry
[n
].peRed
= r
;
2025 logpal
->palPalEntry
[n
].peGreen
= g
;
2026 logpal
->palPalEntry
[n
].peBlue
= b
;
2027 logpal
->palPalEntry
[n
].peFlags
= PC_NOCOLLAPSE
;
2028 colours
[n
] = PALETTERGB(r
, g
, b
);
2029 SetPaletteEntries (pal
, 0, NCOLOURS
, logpal
->palPalEntry
);
2031 colours
[n
] = RGB(r
, g
, b
);
2034 void palette_set (int n
, int r
, int g
, int b
) {
2035 static const int first
[21] = {
2036 0, 2, 4, 6, 8, 10, 12, 14,
2037 1, 3, 5, 7, 9, 11, 13, 15,
2040 real_palette_set (first
[n
], r
, g
, b
);
2042 real_palette_set (first
[n
]+1, r
, g
, b
);
2044 HDC hdc
= get_ctx();
2045 UnrealizeObject (pal
);
2046 RealizePalette (hdc
);
2051 void palette_reset (void) {
2054 for (i
= 0; i
< NCOLOURS
; i
++) {
2056 logpal
->palPalEntry
[i
].peRed
= defpal
[i
].rgbtRed
;
2057 logpal
->palPalEntry
[i
].peGreen
= defpal
[i
].rgbtGreen
;
2058 logpal
->palPalEntry
[i
].peBlue
= defpal
[i
].rgbtBlue
;
2059 logpal
->palPalEntry
[i
].peFlags
= 0;
2060 colours
[i
] = PALETTERGB(defpal
[i
].rgbtRed
,
2061 defpal
[i
].rgbtGreen
,
2062 defpal
[i
].rgbtBlue
);
2064 colours
[i
] = RGB(defpal
[i
].rgbtRed
,
2065 defpal
[i
].rgbtGreen
,
2066 defpal
[i
].rgbtBlue
);
2071 SetPaletteEntries (pal
, 0, NCOLOURS
, logpal
->palPalEntry
);
2073 RealizePalette (hdc
);
2078 void write_clip (void *data
, int len
) {
2082 clipdata
= GlobalAlloc (GMEM_DDESHARE
| GMEM_MOVEABLE
, len
+ 1);
2085 lock
= GlobalLock (clipdata
);
2088 memcpy (lock
, data
, len
);
2089 ((unsigned char *) lock
) [len
] = 0;
2090 GlobalUnlock (clipdata
);
2092 SendMessage (hwnd
, WM_IGNORE_CLIP
, TRUE
, 0);
2093 if (OpenClipboard (hwnd
)) {
2095 SetClipboardData (CF_TEXT
, clipdata
);
2098 GlobalFree (clipdata
);
2099 SendMessage (hwnd
, WM_IGNORE_CLIP
, FALSE
, 0);
2102 void get_clip (void **p
, int *len
) {
2103 static HGLOBAL clipdata
= NULL
;
2107 GlobalUnlock (clipdata
);
2111 if (OpenClipboard (NULL
)) {
2112 clipdata
= GetClipboardData (CF_TEXT
);
2115 *p
= GlobalLock (clipdata
);
2129 * Move `lines' lines from position `from' to position `to' in the
2132 void optimised_move (int to
, int from
, int lines
) {
2136 min
= (to
< from ? to
: from
);
2137 max
= to
+ from
- min
;
2139 r
.left
= 0; r
.right
= cols
* font_width
;
2140 r
.top
= min
* font_height
; r
.bottom
= (max
+lines
) * font_height
;
2141 ScrollWindow (hwnd
, 0, (to
- from
) * font_height
, &r
, &r
);
2145 * Print a message box and perform a fatal exit.
2147 void fatalbox(char *fmt
, ...) {
2152 vsprintf(stuff
, fmt
, ap
);
2154 MessageBox(hwnd
, stuff
, "PuTTY Fatal Error", MB_ICONERROR
| MB_OK
);
2161 void beep(int errorbeep
) {
2162 static long last_beep
= 0;
2163 long now
, beep_diff
;
2165 now
= GetTickCount();
2166 beep_diff
= now
-last_beep
;
2168 /* Make sure we only respond to one beep per packet or so */
2169 if (beep_diff
>=0 && beep_diff
<50)
2173 MessageBeep(MB_ICONHAND
);
2177 last_beep
= GetTickCount();