Fix due to Robert de Bath: backspace should not cross line
[sgt/putty] / window.c
1 #include <windows.h>
2 #include <imm.h>
3 #include <commctrl.h>
4 #include <mmsystem.h>
5 #ifndef AUTO_WINSOCK
6 #ifdef WINSOCK_TWO
7 #include <winsock2.h>
8 #else
9 #include <winsock.h>
10 #endif
11 #endif
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <ctype.h>
15 #include <time.h>
16
17 #define PUTTY_DO_GLOBALS /* actually _define_ globals */
18 #include "putty.h"
19 #include "winstuff.h"
20 #include "storage.h"
21 #include "win_res.h"
22
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
45
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
51
52 #define WM_IGNORE_SIZE (WM_XUSER + 1)
53 #define WM_IGNORE_CLIP (WM_XUSER + 2)
54
55 /* Needed for Chinese support and apparently not always defined. */
56 #ifndef VK_PROCESSKEY
57 #define VK_PROCESSKEY 0xE5
58 #endif
59
60 static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
61 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
62 unsigned char *output);
63 static void cfgtopalette(void);
64 static void init_palette(void);
65 static void init_fonts(int);
66
67 static int extra_width, extra_height;
68
69 static int pending_netevent = 0;
70 static WPARAM pend_netevent_wParam = 0;
71 static LPARAM pend_netevent_lParam = 0;
72 static void enact_pending_netevent(void);
73
74 static time_t last_movement = 0;
75
76 #define FONT_NORMAL 0
77 #define FONT_BOLD 1
78 #define FONT_UNDERLINE 2
79 #define FONT_BOLDUND 3
80 #define FONT_OEM 4
81 #define FONT_OEMBOLD 5
82 #define FONT_OEMBOLDUND 6
83 #define FONT_OEMUND 7
84 static HFONT fonts[8];
85 static int font_needs_hand_underlining;
86 static enum {
87 BOLD_COLOURS, BOLD_SHADOW, BOLD_FONT
88 } bold_mode;
89 static enum {
90 UND_LINE, UND_FONT
91 } und_mode;
92 static int descent;
93
94 #define NCOLOURS 24
95 static COLORREF colours[NCOLOURS];
96 static HPALETTE pal;
97 static LPLOGPALETTE logpal;
98 static RGBTRIPLE defpal[NCOLOURS];
99
100 static HWND hwnd;
101
102 static HBITMAP caretbm;
103
104 static int dbltime, lasttime, lastact;
105 static Mouse_Button lastbtn;
106
107 /* this allows xterm-style mouse handling. */
108 static int send_raw_mouse = 0;
109 static int wheel_accumulator = 0;
110
111 static char *window_name, *icon_name;
112
113 static int compose_state = 0;
114
115 /* Dummy routine, only required in plink. */
116 void ldisc_update(int echo, int edit)
117 {
118 }
119
120 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
121 {
122 static char appname[] = "PuTTY";
123 WORD winsock_ver;
124 WSADATA wsadata;
125 WNDCLASS wndclass;
126 MSG msg;
127 int guess_width, guess_height;
128
129 hinst = inst;
130 flags = FLAG_VERBOSE | FLAG_INTERACTIVE;
131
132 winsock_ver = MAKEWORD(1, 1);
133 if (WSAStartup(winsock_ver, &wsadata)) {
134 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
135 MB_OK | MB_ICONEXCLAMATION);
136 return 1;
137 }
138 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
139 MessageBox(NULL, "WinSock version is incompatible with 1.1",
140 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
141 WSACleanup();
142 return 1;
143 }
144 /* WISHLIST: maybe allow config tweaking even if winsock not present? */
145 sk_init();
146
147 InitCommonControls();
148
149 /* Ensure a Maximize setting in Explorer doesn't maximise the
150 * config box. */
151 defuse_showwindow();
152
153 /*
154 * Process the command line.
155 */
156 {
157 char *p;
158
159 default_protocol = DEFAULT_PROTOCOL;
160 default_port = DEFAULT_PORT;
161 cfg.logtype = LGTYP_NONE;
162
163 do_defaults(NULL, &cfg);
164
165 p = cmdline;
166 while (*p && isspace(*p))
167 p++;
168
169 /*
170 * Process command line options first. Yes, this can be
171 * done better, and it will be as soon as I have the
172 * energy...
173 */
174 while (*p == '-') {
175 char *q = p + strcspn(p, " \t");
176 p++;
177 if (q == p + 3 &&
178 tolower(p[0]) == 's' &&
179 tolower(p[1]) == 's' && tolower(p[2]) == 'h') {
180 default_protocol = cfg.protocol = PROT_SSH;
181 default_port = cfg.port = 22;
182 } else if (q == p + 7 &&
183 tolower(p[0]) == 'c' &&
184 tolower(p[1]) == 'l' &&
185 tolower(p[2]) == 'e' &&
186 tolower(p[3]) == 'a' &&
187 tolower(p[4]) == 'n' &&
188 tolower(p[5]) == 'u' && tolower(p[6]) == 'p') {
189 /*
190 * `putty -cleanup'. Remove all registry entries
191 * associated with PuTTY, and also find and delete
192 * the random seed file.
193 */
194 if (MessageBox(NULL,
195 "This procedure will remove ALL Registry\n"
196 "entries associated with PuTTY, and will\n"
197 "also remove the PuTTY random seed file.\n"
198 "\n"
199 "THIS PROCESS WILL DESTROY YOUR SAVED\n"
200 "SESSIONS. Are you really sure you want\n"
201 "to continue?",
202 "PuTTY Warning",
203 MB_YESNO | MB_ICONWARNING) == IDYES) {
204 cleanup_all();
205 }
206 exit(0);
207 }
208 p = q + strspn(q, " \t");
209 }
210
211 /*
212 * An initial @ means to activate a saved session.
213 */
214 if (*p == '@') {
215 int i = strlen(p);
216 while (i > 1 && isspace(p[i - 1]))
217 i--;
218 p[i] = '\0';
219 do_defaults(p + 1, &cfg);
220 if (!*cfg.host && !do_config()) {
221 WSACleanup();
222 return 0;
223 }
224 } else if (*p == '&') {
225 /*
226 * An initial & means we've been given a command line
227 * containing the hex value of a HANDLE for a file
228 * mapping object, which we must then extract as a
229 * config.
230 */
231 HANDLE filemap;
232 Config *cp;
233 if (sscanf(p + 1, "%p", &filemap) == 1 &&
234 (cp = MapViewOfFile(filemap, FILE_MAP_READ,
235 0, 0, sizeof(Config))) != NULL) {
236 cfg = *cp;
237 UnmapViewOfFile(cp);
238 CloseHandle(filemap);
239 } else if (!do_config()) {
240 WSACleanup();
241 return 0;
242 }
243 } else if (*p) {
244 char *q = p;
245 /*
246 * If the hostname starts with "telnet:", set the
247 * protocol to Telnet and process the string as a
248 * Telnet URL.
249 */
250 if (!strncmp(q, "telnet:", 7)) {
251 char c;
252
253 q += 7;
254 if (q[0] == '/' && q[1] == '/')
255 q += 2;
256 cfg.protocol = PROT_TELNET;
257 p = q;
258 while (*p && *p != ':' && *p != '/')
259 p++;
260 c = *p;
261 if (*p)
262 *p++ = '\0';
263 if (c == ':')
264 cfg.port = atoi(p);
265 else
266 cfg.port = -1;
267 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
268 cfg.host[sizeof(cfg.host) - 1] = '\0';
269 } else {
270 while (*p && !isspace(*p))
271 p++;
272 if (*p)
273 *p++ = '\0';
274 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
275 cfg.host[sizeof(cfg.host) - 1] = '\0';
276 while (*p && isspace(*p))
277 p++;
278 if (*p)
279 cfg.port = atoi(p);
280 else
281 cfg.port = -1;
282 }
283 } else {
284 if (!do_config()) {
285 WSACleanup();
286 return 0;
287 }
288 }
289
290 /* See if host is of the form user@host */
291 if (cfg.host[0] != '\0') {
292 char *atsign = strchr(cfg.host, '@');
293 /* Make sure we're not overflowing the user field */
294 if (atsign) {
295 if (atsign - cfg.host < sizeof cfg.username) {
296 strncpy(cfg.username, cfg.host, atsign - cfg.host);
297 cfg.username[atsign - cfg.host] = '\0';
298 }
299 memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
300 }
301 }
302
303 /*
304 * Trim a colon suffix off the hostname if it's there.
305 */
306 cfg.host[strcspn(cfg.host, ":")] = '\0';
307 }
308
309 /*
310 * Select protocol. This is farmed out into a table in a
311 * separate file to enable an ssh-free variant.
312 */
313 {
314 int i;
315 back = NULL;
316 for (i = 0; backends[i].backend != NULL; i++)
317 if (backends[i].protocol == cfg.protocol) {
318 back = backends[i].backend;
319 break;
320 }
321 if (back == NULL) {
322 MessageBox(NULL, "Unsupported protocol number found",
323 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
324 WSACleanup();
325 return 1;
326 }
327 }
328
329 /* Check for invalid Port number (i.e. zero) */
330 if (cfg.port == 0) {
331 MessageBox(NULL, "Invalid Port Number",
332 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
333 WSACleanup();
334 return 1;
335 }
336
337 if (!prev) {
338 wndclass.style = 0;
339 wndclass.lpfnWndProc = WndProc;
340 wndclass.cbClsExtra = 0;
341 wndclass.cbWndExtra = 0;
342 wndclass.hInstance = inst;
343 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
344 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
345 wndclass.hbrBackground = GetStockObject(BLACK_BRUSH);
346 wndclass.lpszMenuName = NULL;
347 wndclass.lpszClassName = appname;
348
349 RegisterClass(&wndclass);
350 }
351
352 hwnd = NULL;
353
354 savelines = cfg.savelines;
355 term_init();
356
357 cfgtopalette();
358
359 /*
360 * Guess some defaults for the window size. This all gets
361 * updated later, so we don't really care too much. However, we
362 * do want the font width/height guesses to correspond to a
363 * large font rather than a small one...
364 */
365
366 font_width = 10;
367 font_height = 20;
368 extra_width = 25;
369 extra_height = 28;
370 term_size(cfg.height, cfg.width, cfg.savelines);
371 guess_width = extra_width + font_width * cols;
372 guess_height = extra_height + font_height * rows;
373 {
374 RECT r;
375 HWND w = GetDesktopWindow();
376 GetWindowRect(w, &r);
377 if (guess_width > r.right - r.left)
378 guess_width = r.right - r.left;
379 if (guess_height > r.bottom - r.top)
380 guess_height = r.bottom - r.top;
381 }
382
383 {
384 int winmode = WS_OVERLAPPEDWINDOW | WS_VSCROLL;
385 int exwinmode = 0;
386 if (!cfg.scrollbar)
387 winmode &= ~(WS_VSCROLL);
388 if (cfg.locksize)
389 winmode &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
390 if (cfg.alwaysontop)
391 exwinmode |= WS_EX_TOPMOST;
392 if (cfg.sunken_edge)
393 exwinmode |= WS_EX_CLIENTEDGE;
394 hwnd = CreateWindowEx(exwinmode, appname, appname,
395 winmode, CW_USEDEFAULT, CW_USEDEFAULT,
396 guess_width, guess_height,
397 NULL, NULL, inst, NULL);
398 }
399
400 /*
401 * Initialise the fonts, simultaneously correcting the guesses
402 * for font_{width,height}.
403 */
404 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
405 und_mode = UND_FONT;
406 init_fonts(0);
407
408 /*
409 * Correct the guesses for extra_{width,height}.
410 */
411 {
412 RECT cr, wr;
413 GetWindowRect(hwnd, &wr);
414 GetClientRect(hwnd, &cr);
415 extra_width = wr.right - wr.left - cr.right + cr.left;
416 extra_height = wr.bottom - wr.top - cr.bottom + cr.top;
417 }
418
419 /*
420 * Resize the window, now we know what size we _really_ want it
421 * to be.
422 */
423 guess_width = extra_width + font_width * cols;
424 guess_height = extra_height + font_height * rows;
425 SendMessage(hwnd, WM_IGNORE_SIZE, 0, 0);
426 SetWindowPos(hwnd, NULL, 0, 0, guess_width, guess_height,
427 SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
428
429 /*
430 * Set up a caret bitmap, with no content.
431 */
432 {
433 char *bits;
434 int size = (font_width + 15) / 16 * 2 * font_height;
435 bits = smalloc(size);
436 memset(bits, 0, size);
437 caretbm = CreateBitmap(font_width, font_height, 1, 1, bits);
438 sfree(bits);
439 }
440 CreateCaret(hwnd, caretbm, font_width, font_height);
441
442 /*
443 * Initialise the scroll bar.
444 */
445 {
446 SCROLLINFO si;
447
448 si.cbSize = sizeof(si);
449 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
450 si.nMin = 0;
451 si.nMax = rows - 1;
452 si.nPage = rows;
453 si.nPos = 0;
454 SetScrollInfo(hwnd, SB_VERT, &si, FALSE);
455 }
456
457 /*
458 * Start up the telnet connection.
459 */
460 {
461 char *error;
462 char msg[1024], *title;
463 char *realhost;
464
465 error = back->init(cfg.host, cfg.port, &realhost);
466 if (error) {
467 sprintf(msg, "Unable to open connection to\n"
468 "%.800s\n" "%s", cfg.host, error);
469 MessageBox(NULL, msg, "PuTTY Error", MB_ICONERROR | MB_OK);
470 return 0;
471 }
472 window_name = icon_name = NULL;
473 if (*cfg.wintitle) {
474 title = cfg.wintitle;
475 } else {
476 sprintf(msg, "%s - PuTTY", realhost);
477 title = msg;
478 }
479 set_title(title);
480 set_icon(title);
481 }
482
483 session_closed = FALSE;
484
485 /*
486 * Set up the input and output buffers.
487 */
488 inbuf_head = 0;
489 outbuf_reap = outbuf_head = 0;
490
491 /*
492 * Prepare the mouse handler.
493 */
494 lastact = MA_NOTHING;
495 lastbtn = MBT_NOTHING;
496 dbltime = GetDoubleClickTime();
497
498 /*
499 * Set up the session-control options on the system menu.
500 */
501 {
502 HMENU m = GetSystemMenu(hwnd, FALSE);
503 HMENU p, s;
504 int i;
505
506 AppendMenu(m, MF_SEPARATOR, 0, 0);
507 if (cfg.protocol == PROT_TELNET) {
508 p = CreateMenu();
509 AppendMenu(p, MF_ENABLED, IDM_TEL_AYT, "Are You There");
510 AppendMenu(p, MF_ENABLED, IDM_TEL_BRK, "Break");
511 AppendMenu(p, MF_ENABLED, IDM_TEL_SYNCH, "Synch");
512 AppendMenu(p, MF_SEPARATOR, 0, 0);
513 AppendMenu(p, MF_ENABLED, IDM_TEL_EC, "Erase Character");
514 AppendMenu(p, MF_ENABLED, IDM_TEL_EL, "Erase Line");
515 AppendMenu(p, MF_ENABLED, IDM_TEL_GA, "Go Ahead");
516 AppendMenu(p, MF_ENABLED, IDM_TEL_NOP, "No Operation");
517 AppendMenu(p, MF_SEPARATOR, 0, 0);
518 AppendMenu(p, MF_ENABLED, IDM_TEL_ABORT, "Abort Process");
519 AppendMenu(p, MF_ENABLED, IDM_TEL_AO, "Abort Output");
520 AppendMenu(p, MF_ENABLED, IDM_TEL_IP, "Interrupt Process");
521 AppendMenu(p, MF_ENABLED, IDM_TEL_SUSP, "Suspend Process");
522 AppendMenu(p, MF_SEPARATOR, 0, 0);
523 AppendMenu(p, MF_ENABLED, IDM_TEL_EOR, "End Of Record");
524 AppendMenu(p, MF_ENABLED, IDM_TEL_EOF, "End Of File");
525 AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) p,
526 "Telnet Command");
527 AppendMenu(m, MF_SEPARATOR, 0, 0);
528 }
529 AppendMenu(m, MF_ENABLED, IDM_SHOWLOG, "&Event Log");
530 AppendMenu(m, MF_SEPARATOR, 0, 0);
531 AppendMenu(m, MF_ENABLED, IDM_NEWSESS, "Ne&w Session...");
532 AppendMenu(m, MF_ENABLED, IDM_DUPSESS, "&Duplicate Session");
533 s = CreateMenu();
534 get_sesslist(TRUE);
535 for (i = 1; i < ((nsessions < 256) ? nsessions : 256); i++)
536 AppendMenu(s, MF_ENABLED, IDM_SAVED_MIN + (16 * i),
537 sessions[i]);
538 AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) s, "Sa&ved Sessions");
539 AppendMenu(m, MF_ENABLED, IDM_RECONF, "Chan&ge Settings...");
540 AppendMenu(m, MF_SEPARATOR, 0, 0);
541 AppendMenu(m, MF_ENABLED, IDM_COPYALL, "C&opy All to Clipboard");
542 AppendMenu(m, MF_ENABLED, IDM_CLRSB, "C&lear Scrollback");
543 AppendMenu(m, MF_ENABLED, IDM_RESET, "Rese&t Terminal");
544 AppendMenu(m, MF_SEPARATOR, 0, 0);
545 AppendMenu(m, MF_ENABLED, IDM_ABOUT, "&About PuTTY");
546 }
547
548 /*
549 * Finally show the window!
550 */
551 ShowWindow(hwnd, show);
552
553 /*
554 * Open the initial log file if there is one.
555 */
556 logfopen();
557
558 /*
559 * Set the palette up.
560 */
561 pal = NULL;
562 logpal = NULL;
563 init_palette();
564
565 has_focus = (GetForegroundWindow() == hwnd);
566 UpdateWindow(hwnd);
567
568 if (GetMessage(&msg, NULL, 0, 0) == 1) {
569 int timer_id = 0, long_timer = 0;
570
571 while (msg.message != WM_QUIT) {
572 /* Sometimes DispatchMessage calls routines that use their own
573 * GetMessage loop, setup this timer so we get some control back.
574 *
575 * Also call term_update() from the timer so that if the host
576 * is sending data flat out we still do redraws.
577 */
578 if (timer_id && long_timer) {
579 KillTimer(hwnd, timer_id);
580 long_timer = timer_id = 0;
581 }
582 if (!timer_id)
583 timer_id = SetTimer(hwnd, 1, 20, NULL);
584 if (!(IsWindow(logbox) && IsDialogMessage(logbox, &msg)))
585 DispatchMessage(&msg);
586
587 /* Make sure we blink everything that needs it. */
588 term_blink(0);
589
590 /* Send the paste buffer if there's anything to send */
591 term_paste();
592
593 /* If there's nothing new in the queue then we can do everything
594 * we've delayed, reading the socket, writing, and repainting
595 * the window.
596 */
597 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
598 continue;
599
600 if (pending_netevent) {
601 enact_pending_netevent();
602
603 /* Force the cursor blink on */
604 term_blink(1);
605
606 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
607 continue;
608 }
609
610 /* Okay there is now nothing to do so we make sure the screen is
611 * completely up to date then tell windows to call us in a little
612 * while.
613 */
614 if (timer_id) {
615 KillTimer(hwnd, timer_id);
616 timer_id = 0;
617 }
618 HideCaret(hwnd);
619 if (inbuf_head)
620 term_out();
621 term_update();
622 ShowCaret(hwnd);
623 if (in_vbell)
624 /* Hmm, term_update didn't want to do an update too soon ... */
625 timer_id = SetTimer(hwnd, 1, 50, NULL);
626 else if (!has_focus)
627 timer_id = SetTimer(hwnd, 1, 2000, NULL);
628 else
629 timer_id = SetTimer(hwnd, 1, 100, NULL);
630 long_timer = 1;
631
632 /* There's no point rescanning everything in the message queue
633 * so we do an apparently unnecessary wait here
634 */
635 WaitMessage();
636 if (GetMessage(&msg, NULL, 0, 0) != 1)
637 break;
638 }
639 }
640
641 /*
642 * Clean up.
643 */
644 {
645 int i;
646 for (i = 0; i < 8; i++)
647 if (fonts[i])
648 DeleteObject(fonts[i]);
649 }
650 sfree(logpal);
651 if (pal)
652 DeleteObject(pal);
653 WSACleanup();
654
655 if (cfg.protocol == PROT_SSH) {
656 random_save_seed();
657 #ifdef MSCRYPTOAPI
658 crypto_wrapup();
659 #endif
660 }
661
662 return msg.wParam;
663 }
664
665 /*
666 * Set up, or shut down, an AsyncSelect. Called from winnet.c.
667 */
668 char *do_select(SOCKET skt, int startup)
669 {
670 int msg, events;
671 if (startup) {
672 msg = WM_NETEVENT;
673 events = FD_READ | FD_WRITE | FD_OOB | FD_CLOSE;
674 } else {
675 msg = events = 0;
676 }
677 if (!hwnd)
678 return "do_select(): internal error (hwnd==NULL)";
679 if (WSAAsyncSelect(skt, hwnd, msg, events) == SOCKET_ERROR) {
680 switch (WSAGetLastError()) {
681 case WSAENETDOWN:
682 return "Network is down";
683 default:
684 return "WSAAsyncSelect(): unknown error";
685 }
686 }
687 return NULL;
688 }
689
690 /*
691 * set or clear the "raw mouse message" mode
692 */
693 void set_raw_mouse_mode(int activate)
694 {
695 send_raw_mouse = activate;
696 SetCursor(LoadCursor(NULL, activate ? IDC_ARROW : IDC_IBEAM));
697 }
698
699 /*
700 * Print a message box and close the connection.
701 */
702 void connection_fatal(char *fmt, ...)
703 {
704 va_list ap;
705 char stuff[200];
706
707 va_start(ap, fmt);
708 vsprintf(stuff, fmt, ap);
709 va_end(ap);
710 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
711 if (cfg.close_on_exit == COE_ALWAYS)
712 PostQuitMessage(1);
713 else {
714 session_closed = TRUE;
715 SetWindowText(hwnd, "PuTTY (inactive)");
716 }
717 }
718
719 /*
720 * Actually do the job requested by a WM_NETEVENT
721 */
722 static void enact_pending_netevent(void)
723 {
724 static int reentering = 0;
725 extern int select_result(WPARAM, LPARAM);
726 int ret;
727
728 if (reentering)
729 return; /* don't unpend the pending */
730
731 pending_netevent = FALSE;
732
733 reentering = 1;
734 ret = select_result(pend_netevent_wParam, pend_netevent_lParam);
735 reentering = 0;
736
737 if (ret == 0 && !session_closed) {
738 /* Abnormal exits will already have set session_closed and taken
739 * appropriate action. */
740 if (cfg.close_on_exit == COE_ALWAYS ||
741 cfg.close_on_exit == COE_NORMAL) PostQuitMessage(0);
742 else {
743 session_closed = TRUE;
744 SetWindowText(hwnd, "PuTTY (inactive)");
745 MessageBox(hwnd, "Connection closed by remote host",
746 "PuTTY", MB_OK | MB_ICONINFORMATION);
747 }
748 }
749 }
750
751 /*
752 * Copy the colour palette from the configuration data into defpal.
753 * This is non-trivial because the colour indices are different.
754 */
755 static void cfgtopalette(void)
756 {
757 int i;
758 static const int ww[] = {
759 6, 7, 8, 9, 10, 11, 12, 13,
760 14, 15, 16, 17, 18, 19, 20, 21,
761 0, 1, 2, 3, 4, 4, 5, 5
762 };
763
764 for (i = 0; i < 24; i++) {
765 int w = ww[i];
766 defpal[i].rgbtRed = cfg.colours[w][0];
767 defpal[i].rgbtGreen = cfg.colours[w][1];
768 defpal[i].rgbtBlue = cfg.colours[w][2];
769 }
770 }
771
772 /*
773 * Set up the colour palette.
774 */
775 static void init_palette(void)
776 {
777 int i;
778 HDC hdc = GetDC(hwnd);
779 if (hdc) {
780 if (cfg.try_palette && GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) {
781 logpal = smalloc(sizeof(*logpal)
782 - sizeof(logpal->palPalEntry)
783 + NCOLOURS * sizeof(PALETTEENTRY));
784 logpal->palVersion = 0x300;
785 logpal->palNumEntries = NCOLOURS;
786 for (i = 0; i < NCOLOURS; i++) {
787 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
788 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
789 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
790 logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
791 }
792 pal = CreatePalette(logpal);
793 if (pal) {
794 SelectPalette(hdc, pal, FALSE);
795 RealizePalette(hdc);
796 SelectPalette(hdc, GetStockObject(DEFAULT_PALETTE), FALSE);
797 }
798 }
799 ReleaseDC(hwnd, hdc);
800 }
801 if (pal)
802 for (i = 0; i < NCOLOURS; i++)
803 colours[i] = PALETTERGB(defpal[i].rgbtRed,
804 defpal[i].rgbtGreen,
805 defpal[i].rgbtBlue);
806 else
807 for (i = 0; i < NCOLOURS; i++)
808 colours[i] = RGB(defpal[i].rgbtRed,
809 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
810 }
811
812 /*
813 * Initialise all the fonts we will need. There may be as many as
814 * eight or as few as one. We also:
815 *
816 * - check the font width and height, correcting our guesses if
817 * necessary.
818 *
819 * - verify that the bold font is the same width as the ordinary
820 * one, and engage shadow bolding if not.
821 *
822 * - verify that the underlined font is the same width as the
823 * ordinary one (manual underlining by means of line drawing can
824 * be done in a pinch).
825 */
826 static void init_fonts(int pick_width)
827 {
828 TEXTMETRIC tm;
829 int i;
830 int fsize[8];
831 HDC hdc;
832 int fw_dontcare, fw_bold;
833 int firstchar = ' ';
834
835 #ifdef CHECKOEMFONT
836 font_messup:
837 #endif
838 for (i = 0; i < 8; i++)
839 fonts[i] = NULL;
840
841 if (cfg.fontisbold) {
842 fw_dontcare = FW_BOLD;
843 fw_bold = FW_HEAVY;
844 } else {
845 fw_dontcare = FW_DONTCARE;
846 fw_bold = FW_BOLD;
847 }
848
849 hdc = GetDC(hwnd);
850
851 font_height = cfg.fontheight;
852 if (font_height > 0) {
853 font_height =
854 -MulDiv(font_height, GetDeviceCaps(hdc, LOGPIXELSY), 72);
855 }
856 font_width = pick_width;
857
858 #define f(i,c,w,u) \
859 fonts[i] = CreateFont (font_height, font_width, 0, 0, w, FALSE, u, FALSE, \
860 c, OUT_DEFAULT_PRECIS, \
861 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, \
862 FIXED_PITCH | FF_DONTCARE, cfg.font)
863
864 if (cfg.vtmode != VT_OEMONLY) {
865 f(FONT_NORMAL, cfg.fontcharset, fw_dontcare, FALSE);
866
867 SelectObject(hdc, fonts[FONT_NORMAL]);
868 GetTextMetrics(hdc, &tm);
869 font_height = tm.tmHeight;
870 font_width = tm.tmAveCharWidth;
871
872 f(FONT_UNDERLINE, cfg.fontcharset, fw_dontcare, TRUE);
873
874 /*
875 * Some fonts, e.g. 9-pt Courier, draw their underlines
876 * outside their character cell. We successfully prevent
877 * screen corruption by clipping the text output, but then
878 * we lose the underline completely. Here we try to work
879 * out whether this is such a font, and if it is, we set a
880 * flag that causes underlines to be drawn by hand.
881 *
882 * Having tried other more sophisticated approaches (such
883 * as examining the TEXTMETRIC structure or requesting the
884 * height of a string), I think we'll do this the brute
885 * force way: we create a small bitmap, draw an underlined
886 * space on it, and test to see whether any pixels are
887 * foreground-coloured. (Since we expect the underline to
888 * go all the way across the character cell, we only search
889 * down a single column of the bitmap, half way across.)
890 */
891 {
892 HDC und_dc;
893 HBITMAP und_bm, und_oldbm;
894 int i, gotit;
895 COLORREF c;
896
897 und_dc = CreateCompatibleDC(hdc);
898 und_bm = CreateCompatibleBitmap(hdc, font_width, font_height);
899 und_oldbm = SelectObject(und_dc, und_bm);
900 SelectObject(und_dc, fonts[FONT_UNDERLINE]);
901 SetTextAlign(und_dc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
902 SetTextColor(und_dc, RGB(255, 255, 255));
903 SetBkColor(und_dc, RGB(0, 0, 0));
904 SetBkMode(und_dc, OPAQUE);
905 ExtTextOut(und_dc, 0, 0, ETO_OPAQUE, NULL, " ", 1, NULL);
906 gotit = FALSE;
907 for (i = 0; i < font_height; i++) {
908 c = GetPixel(und_dc, font_width / 2, i);
909 if (c != RGB(0, 0, 0))
910 gotit = TRUE;
911 }
912 SelectObject(und_dc, und_oldbm);
913 DeleteObject(und_bm);
914 DeleteDC(und_dc);
915 font_needs_hand_underlining = !gotit;
916 }
917
918 if (bold_mode == BOLD_FONT) {
919 f(FONT_BOLD, cfg.fontcharset, fw_bold, FALSE);
920 f(FONT_BOLDUND, cfg.fontcharset, fw_bold, TRUE);
921 }
922
923 if (cfg.vtmode == VT_OEMANSI) {
924 f(FONT_OEM, OEM_CHARSET, fw_dontcare, FALSE);
925 f(FONT_OEMUND, OEM_CHARSET, fw_dontcare, TRUE);
926
927 if (bold_mode == BOLD_FONT) {
928 f(FONT_OEMBOLD, OEM_CHARSET, fw_bold, FALSE);
929 f(FONT_OEMBOLDUND, OEM_CHARSET, fw_bold, TRUE);
930 }
931 }
932 } else {
933 f(FONT_OEM, cfg.fontcharset, fw_dontcare, FALSE);
934
935 SelectObject(hdc, fonts[FONT_OEM]);
936 GetTextMetrics(hdc, &tm);
937 font_height = tm.tmHeight;
938 font_width = tm.tmAveCharWidth;
939
940 f(FONT_OEMUND, cfg.fontcharset, fw_dontcare, TRUE);
941
942 if (bold_mode == BOLD_FONT) {
943 f(FONT_BOLD, cfg.fontcharset, fw_bold, FALSE);
944 f(FONT_BOLDUND, cfg.fontcharset, fw_bold, TRUE);
945 }
946 }
947 #undef f
948
949 descent = tm.tmAscent + 1;
950 if (descent >= font_height)
951 descent = font_height - 1;
952 firstchar = tm.tmFirstChar;
953
954 for (i = 0; i < 8; i++) {
955 if (fonts[i]) {
956 if (SelectObject(hdc, fonts[i]) && GetTextMetrics(hdc, &tm))
957 fsize[i] = tm.tmAveCharWidth + 256 * tm.tmHeight;
958 else
959 fsize[i] = -i;
960 } else
961 fsize[i] = -i;
962 }
963
964 ReleaseDC(hwnd, hdc);
965
966 /* ... This is wrong in OEM only mode */
967 if (fsize[FONT_UNDERLINE] != fsize[FONT_NORMAL] ||
968 (bold_mode == BOLD_FONT &&
969 fsize[FONT_BOLDUND] != fsize[FONT_BOLD])) {
970 und_mode = UND_LINE;
971 DeleteObject(fonts[FONT_UNDERLINE]);
972 if (bold_mode == BOLD_FONT)
973 DeleteObject(fonts[FONT_BOLDUND]);
974 }
975
976 if (bold_mode == BOLD_FONT && fsize[FONT_BOLD] != fsize[FONT_NORMAL]) {
977 bold_mode = BOLD_SHADOW;
978 DeleteObject(fonts[FONT_BOLD]);
979 if (und_mode == UND_FONT)
980 DeleteObject(fonts[FONT_BOLDUND]);
981 }
982 #ifdef CHECKOEMFONT
983 /* With the fascist font painting it doesn't matter if the linedraw font
984 * isn't exactly the right size anymore so we don't have to check this.
985 */
986 if (cfg.vtmode == VT_OEMANSI && fsize[FONT_OEM] != fsize[FONT_NORMAL]) {
987 if (cfg.fontcharset == OEM_CHARSET) {
988 MessageBox(NULL, "The OEM and ANSI versions of this font are\n"
989 "different sizes. Using OEM-only mode instead",
990 "Font Size Mismatch", MB_ICONINFORMATION | MB_OK);
991 cfg.vtmode = VT_OEMONLY;
992 } else if (firstchar < ' ') {
993 MessageBox(NULL, "The OEM and ANSI versions of this font are\n"
994 "different sizes. Using XTerm mode instead",
995 "Font Size Mismatch", MB_ICONINFORMATION | MB_OK);
996 cfg.vtmode = VT_XWINDOWS;
997 } else {
998 MessageBox(NULL, "The OEM and ANSI versions of this font are\n"
999 "different sizes. Using ISO8859-1 mode instead",
1000 "Font Size Mismatch", MB_ICONINFORMATION | MB_OK);
1001 cfg.vtmode = VT_POORMAN;
1002 }
1003
1004 for (i = 0; i < 8; i++)
1005 if (fonts[i])
1006 DeleteObject(fonts[i]);
1007 goto font_messup;
1008 }
1009 #endif
1010 }
1011
1012 void request_resize(int w, int h, int refont)
1013 {
1014 int width, height;
1015
1016 /* If the window is maximized supress resizing attempts */
1017 if (IsZoomed(hwnd))
1018 return;
1019
1020 #ifdef CHECKOEMFONT
1021 /* Don't do this in OEMANSI, you may get disable messages */
1022 if (refont && w != cols && (cols == 80 || cols == 132)
1023 && cfg.vtmode != VT_OEMANSI)
1024 #else
1025 if (refont && w != cols && (cols == 80 || cols == 132))
1026 #endif
1027 {
1028 /* If font width too big for screen should we shrink the font more ? */
1029 if (w == 132)
1030 font_width = ((font_width * cols + w / 2) / w);
1031 else
1032 font_width = 0;
1033 {
1034 int i;
1035 for (i = 0; i < 8; i++)
1036 if (fonts[i])
1037 DeleteObject(fonts[i]);
1038 }
1039 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
1040 und_mode = UND_FONT;
1041 init_fonts(font_width);
1042 } else {
1043 static int first_time = 1;
1044 static RECT ss;
1045
1046 switch (first_time) {
1047 case 1:
1048 /* Get the size of the screen */
1049 if (GetClientRect(GetDesktopWindow(), &ss))
1050 /* first_time = 0 */ ;
1051 else {
1052 first_time = 2;
1053 break;
1054 }
1055 case 0:
1056 /* Make sure the values are sane */
1057 width = (ss.right - ss.left - extra_width) / font_width;
1058 height = (ss.bottom - ss.top - extra_height) / font_height;
1059
1060 if (w > width)
1061 w = width;
1062 if (h > height)
1063 h = height;
1064 if (w < 15)
1065 w = 15;
1066 if (h < 1)
1067 w = 1;
1068 }
1069 }
1070
1071 width = extra_width + font_width * w;
1072 height = extra_height + font_height * h;
1073
1074 SetWindowPos(hwnd, NULL, 0, 0, width, height,
1075 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1076 SWP_NOMOVE | SWP_NOZORDER);
1077 }
1078
1079 static void click(Mouse_Button b, int x, int y, int shift, int ctrl)
1080 {
1081 int thistime = GetMessageTime();
1082
1083 if (send_raw_mouse) {
1084 term_mouse(b, MA_CLICK, x, y, shift, ctrl);
1085 return;
1086 }
1087
1088 if (lastbtn == b && thistime - lasttime < dbltime) {
1089 lastact = (lastact == MA_CLICK ? MA_2CLK :
1090 lastact == MA_2CLK ? MA_3CLK :
1091 lastact == MA_3CLK ? MA_CLICK : MA_NOTHING);
1092 } else {
1093 lastbtn = b;
1094 lastact = MA_CLICK;
1095 }
1096 if (lastact != MA_NOTHING)
1097 term_mouse(b, lastact, x, y, shift, ctrl);
1098 lasttime = thistime;
1099 }
1100
1101 /*
1102 * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT)
1103 * into a cooked one (SELECT, EXTEND, PASTE).
1104 */
1105 Mouse_Button translate_button(Mouse_Button button)
1106 {
1107 if (button == MBT_LEFT)
1108 return MBT_SELECT;
1109 if (button == MBT_MIDDLE)
1110 return cfg.mouse_is_xterm ? MBT_PASTE : MBT_EXTEND;
1111 if (button == MBT_RIGHT)
1112 return cfg.mouse_is_xterm ? MBT_EXTEND : MBT_PASTE;
1113 }
1114
1115 static void show_mouseptr(int show)
1116 {
1117 static int cursor_visible = 1;
1118 if (!cfg.hide_mouseptr) /* override if this feature disabled */
1119 show = 1;
1120 if (cursor_visible && !show)
1121 ShowCursor(FALSE);
1122 else if (!cursor_visible && show)
1123 ShowCursor(TRUE);
1124 cursor_visible = show;
1125 }
1126
1127 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1128 WPARAM wParam, LPARAM lParam)
1129 {
1130 HDC hdc;
1131 static int ignore_size = FALSE;
1132 static int ignore_clip = FALSE;
1133 static int just_reconfigged = FALSE;
1134 static int resizing = FALSE;
1135 static int need_backend_resize = FALSE;
1136
1137 switch (message) {
1138 case WM_TIMER:
1139 if (pending_netevent)
1140 enact_pending_netevent();
1141 if (inbuf_head)
1142 term_out();
1143 noise_regular();
1144 HideCaret(hwnd);
1145 term_update();
1146 ShowCaret(hwnd);
1147 if (cfg.ping_interval > 0) {
1148 time_t now;
1149 time(&now);
1150 if (now - last_movement > cfg.ping_interval) {
1151 back->special(TS_PING);
1152 last_movement = now;
1153 }
1154 }
1155 return 0;
1156 case WM_CREATE:
1157 break;
1158 case WM_CLOSE:
1159 show_mouseptr(1);
1160 if (!cfg.warn_on_close || session_closed ||
1161 MessageBox(hwnd,
1162 "Are you sure you want to close this session?",
1163 "PuTTY Exit Confirmation",
1164 MB_ICONWARNING | MB_OKCANCEL) == IDOK)
1165 DestroyWindow(hwnd);
1166 return 0;
1167 case WM_DESTROY:
1168 show_mouseptr(1);
1169 PostQuitMessage(0);
1170 return 0;
1171 case WM_SYSCOMMAND:
1172 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1173 case IDM_SHOWLOG:
1174 showeventlog(hwnd);
1175 break;
1176 case IDM_NEWSESS:
1177 case IDM_DUPSESS:
1178 case IDM_SAVEDSESS:
1179 {
1180 char b[2048];
1181 char c[30], *cl;
1182 int freecl = FALSE;
1183 STARTUPINFO si;
1184 PROCESS_INFORMATION pi;
1185 HANDLE filemap = NULL;
1186
1187 if (wParam == IDM_DUPSESS) {
1188 /*
1189 * Allocate a file-mapping memory chunk for the
1190 * config structure.
1191 */
1192 SECURITY_ATTRIBUTES sa;
1193 Config *p;
1194
1195 sa.nLength = sizeof(sa);
1196 sa.lpSecurityDescriptor = NULL;
1197 sa.bInheritHandle = TRUE;
1198 filemap = CreateFileMapping((HANDLE) 0xFFFFFFFF,
1199 &sa,
1200 PAGE_READWRITE,
1201 0, sizeof(Config), NULL);
1202 if (filemap) {
1203 p = (Config *) MapViewOfFile(filemap,
1204 FILE_MAP_WRITE,
1205 0, 0, sizeof(Config));
1206 if (p) {
1207 *p = cfg; /* structure copy */
1208 UnmapViewOfFile(p);
1209 }
1210 }
1211 sprintf(c, "putty &%p", filemap);
1212 cl = c;
1213 } else if (wParam == IDM_SAVEDSESS) {
1214 char *session =
1215 sessions[(lParam - IDM_SAVED_MIN) / 16];
1216 cl = smalloc(16 + strlen(session)); /* 8, but play safe */
1217 if (!cl)
1218 cl = NULL; /* not a very important failure mode */
1219 else {
1220 sprintf(cl, "putty @%s", session);
1221 freecl = TRUE;
1222 }
1223 } else
1224 cl = NULL;
1225
1226 GetModuleFileName(NULL, b, sizeof(b) - 1);
1227 si.cb = sizeof(si);
1228 si.lpReserved = NULL;
1229 si.lpDesktop = NULL;
1230 si.lpTitle = NULL;
1231 si.dwFlags = 0;
1232 si.cbReserved2 = 0;
1233 si.lpReserved2 = NULL;
1234 CreateProcess(b, cl, NULL, NULL, TRUE,
1235 NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
1236
1237 if (filemap)
1238 CloseHandle(filemap);
1239 if (freecl)
1240 sfree(cl);
1241 }
1242 break;
1243 case IDM_RECONF:
1244 {
1245 int prev_alwaysontop = cfg.alwaysontop;
1246 int prev_sunken_edge = cfg.sunken_edge;
1247 char oldlogfile[FILENAME_MAX];
1248 int oldlogtype;
1249 int need_setwpos = FALSE;
1250 int old_fwidth, old_fheight;
1251
1252 strcpy(oldlogfile, cfg.logfilename);
1253 oldlogtype = cfg.logtype;
1254 cfg.width = cols;
1255 cfg.height = rows;
1256 old_fwidth = font_width;
1257 old_fheight = font_height;
1258 GetWindowText(hwnd, cfg.wintitle, sizeof(cfg.wintitle));
1259
1260 if (!do_reconfig(hwnd))
1261 break;
1262
1263 if (strcmp(oldlogfile, cfg.logfilename) ||
1264 oldlogtype != cfg.logtype) {
1265 logfclose(); /* reset logging */
1266 logfopen();
1267 }
1268
1269 just_reconfigged = TRUE;
1270 {
1271 int i;
1272 for (i = 0; i < 8; i++)
1273 if (fonts[i])
1274 DeleteObject(fonts[i]);
1275 }
1276 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
1277 und_mode = UND_FONT;
1278 init_fonts(0);
1279 sfree(logpal);
1280 /*
1281 * Flush the line discipline's edit buffer in the
1282 * case where local editing has just been disabled.
1283 */
1284 ldisc_send(NULL, 0);
1285 if (pal)
1286 DeleteObject(pal);
1287 logpal = NULL;
1288 pal = NULL;
1289 cfgtopalette();
1290 init_palette();
1291
1292 /* Enable or disable the scroll bar, etc */
1293 {
1294 LONG nflg, flag = GetWindowLong(hwnd, GWL_STYLE);
1295 LONG nexflag, exflag =
1296 GetWindowLong(hwnd, GWL_EXSTYLE);
1297
1298 nexflag = exflag;
1299 if (cfg.alwaysontop != prev_alwaysontop) {
1300 if (cfg.alwaysontop) {
1301 nexflag |= WS_EX_TOPMOST;
1302 SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
1303 SWP_NOMOVE | SWP_NOSIZE);
1304 } else {
1305 nexflag &= ~(WS_EX_TOPMOST);
1306 SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0,
1307 SWP_NOMOVE | SWP_NOSIZE);
1308 }
1309 }
1310 if (cfg.sunken_edge)
1311 nexflag |= WS_EX_CLIENTEDGE;
1312 else
1313 nexflag &= ~(WS_EX_CLIENTEDGE);
1314
1315 nflg = flag;
1316 if (cfg.scrollbar)
1317 nflg |= WS_VSCROLL;
1318 else
1319 nflg &= ~WS_VSCROLL;
1320 if (cfg.locksize)
1321 nflg &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
1322 else
1323 nflg |= (WS_THICKFRAME | WS_MAXIMIZEBOX);
1324
1325 if (nflg != flag || nexflag != exflag) {
1326 RECT cr, wr;
1327
1328 if (nflg != flag)
1329 SetWindowLong(hwnd, GWL_STYLE, nflg);
1330 if (nexflag != exflag)
1331 SetWindowLong(hwnd, GWL_EXSTYLE, nexflag);
1332
1333 SendMessage(hwnd, WM_IGNORE_SIZE, 0, 0);
1334
1335 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
1336 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1337 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER
1338 | SWP_FRAMECHANGED);
1339
1340 GetWindowRect(hwnd, &wr);
1341 GetClientRect(hwnd, &cr);
1342 extra_width =
1343 wr.right - wr.left - cr.right + cr.left;
1344 extra_height =
1345 wr.bottom - wr.top - cr.bottom + cr.top;
1346 }
1347 }
1348
1349 if (cfg.height != rows ||
1350 cfg.width != cols ||
1351 old_fwidth != font_width ||
1352 old_fheight != font_height ||
1353 cfg.savelines != savelines ||
1354 cfg.sunken_edge != prev_sunken_edge)
1355 need_setwpos = TRUE;
1356 term_size(cfg.height, cfg.width, cfg.savelines);
1357 InvalidateRect(hwnd, NULL, TRUE);
1358 if (need_setwpos) {
1359 force_normal(hwnd);
1360 SetWindowPos(hwnd, NULL, 0, 0,
1361 extra_width + font_width * cfg.width,
1362 extra_height + font_height * cfg.height,
1363 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1364 SWP_NOMOVE | SWP_NOZORDER);
1365 }
1366 set_title(cfg.wintitle);
1367 if (IsIconic(hwnd)) {
1368 SetWindowText(hwnd,
1369 cfg.win_name_always ? window_name :
1370 icon_name);
1371 }
1372 }
1373 break;
1374 case IDM_COPYALL:
1375 term_copyall();
1376 break;
1377 case IDM_CLRSB:
1378 term_clrsb();
1379 break;
1380 case IDM_RESET:
1381 term_pwron();
1382 break;
1383 case IDM_TEL_AYT:
1384 back->special(TS_AYT);
1385 break;
1386 case IDM_TEL_BRK:
1387 back->special(TS_BRK);
1388 break;
1389 case IDM_TEL_SYNCH:
1390 back->special(TS_SYNCH);
1391 break;
1392 case IDM_TEL_EC:
1393 back->special(TS_EC);
1394 break;
1395 case IDM_TEL_EL:
1396 back->special(TS_EL);
1397 break;
1398 case IDM_TEL_GA:
1399 back->special(TS_GA);
1400 break;
1401 case IDM_TEL_NOP:
1402 back->special(TS_NOP);
1403 break;
1404 case IDM_TEL_ABORT:
1405 back->special(TS_ABORT);
1406 break;
1407 case IDM_TEL_AO:
1408 back->special(TS_AO);
1409 break;
1410 case IDM_TEL_IP:
1411 back->special(TS_IP);
1412 break;
1413 case IDM_TEL_SUSP:
1414 back->special(TS_SUSP);
1415 break;
1416 case IDM_TEL_EOR:
1417 back->special(TS_EOR);
1418 break;
1419 case IDM_TEL_EOF:
1420 back->special(TS_EOF);
1421 break;
1422 case IDM_ABOUT:
1423 showabout(hwnd);
1424 break;
1425 default:
1426 if (wParam >= IDM_SAVED_MIN && wParam <= IDM_SAVED_MAX) {
1427 SendMessage(hwnd, WM_SYSCOMMAND, IDM_SAVEDSESS, wParam);
1428 }
1429 }
1430 break;
1431
1432 #define X_POS(l) ((int)(short)LOWORD(l))
1433 #define Y_POS(l) ((int)(short)HIWORD(l))
1434
1435 #define TO_CHR_X(x) (((x)<0 ? (x)-font_width+1 : (x)) / font_width)
1436 #define TO_CHR_Y(y) (((y)<0 ? (y)-font_height+1: (y)) / font_height)
1437 #define WHEEL_DELTA 120
1438 case WM_MOUSEWHEEL:
1439 {
1440 wheel_accumulator += (short) HIWORD(wParam);
1441 wParam = LOWORD(wParam);
1442
1443 /* process events when the threshold is reached */
1444 while (abs(wheel_accumulator) >= WHEEL_DELTA) {
1445 int b;
1446
1447 /* reduce amount for next time */
1448 if (wheel_accumulator > 0) {
1449 b = MBT_WHEEL_UP;
1450 wheel_accumulator -= WHEEL_DELTA;
1451 } else if (wheel_accumulator < 0) {
1452 b = MBT_WHEEL_DOWN;
1453 wheel_accumulator += WHEEL_DELTA;
1454 } else
1455 break;
1456
1457 if (send_raw_mouse) {
1458 /* send a mouse-down followed by a mouse up */
1459 term_mouse(b,
1460 MA_CLICK,
1461 TO_CHR_X(X_POS(lParam)),
1462 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
1463 wParam & MK_CONTROL);
1464 term_mouse(b, MA_RELEASE, TO_CHR_X(X_POS(lParam)),
1465 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
1466 wParam & MK_CONTROL);
1467 } else {
1468 /* trigger a scroll */
1469 term_scroll(0,
1470 b == MBT_WHEEL_UP ? -rows / 2 : rows / 2);
1471 }
1472 }
1473 return 0;
1474 }
1475 case WM_LBUTTONDOWN:
1476 case WM_MBUTTONDOWN:
1477 case WM_RBUTTONDOWN:
1478 case WM_LBUTTONUP:
1479 case WM_MBUTTONUP:
1480 case WM_RBUTTONUP:
1481 {
1482 int button, press;
1483 switch (message) {
1484 case WM_LBUTTONDOWN:
1485 button = MBT_LEFT;
1486 press = 1;
1487 break;
1488 case WM_MBUTTONDOWN:
1489 button = MBT_MIDDLE;
1490 press = 1;
1491 break;
1492 case WM_RBUTTONDOWN:
1493 button = MBT_RIGHT;
1494 press = 1;
1495 break;
1496 case WM_LBUTTONUP:
1497 button = MBT_LEFT;
1498 press = 0;
1499 break;
1500 case WM_MBUTTONUP:
1501 button = MBT_MIDDLE;
1502 press = 0;
1503 break;
1504 case WM_RBUTTONUP:
1505 button = MBT_RIGHT;
1506 press = 0;
1507 break;
1508 }
1509 show_mouseptr(1);
1510 if (press) {
1511 click(button,
1512 TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam)),
1513 wParam & MK_SHIFT, wParam & MK_CONTROL);
1514 SetCapture(hwnd);
1515 } else {
1516 term_mouse(button, MA_RELEASE,
1517 TO_CHR_X(X_POS(lParam)),
1518 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
1519 wParam & MK_CONTROL);
1520 ReleaseCapture();
1521 }
1522 }
1523 return 0;
1524 case WM_MOUSEMOVE:
1525 show_mouseptr(1);
1526 /*
1527 * Add the mouse position and message time to the random
1528 * number noise.
1529 */
1530 noise_ultralight(lParam);
1531
1532 if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)) {
1533 Mouse_Button b;
1534 if (wParam & MK_LBUTTON)
1535 b = MBT_SELECT;
1536 else if (wParam & MK_MBUTTON)
1537 b = cfg.mouse_is_xterm ? MBT_PASTE : MBT_EXTEND;
1538 else
1539 b = cfg.mouse_is_xterm ? MBT_EXTEND : MBT_PASTE;
1540 term_mouse(b, MA_DRAG, TO_CHR_X(X_POS(lParam)),
1541 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
1542 wParam & MK_CONTROL);
1543 }
1544 return 0;
1545 case WM_NCMOUSEMOVE:
1546 show_mouseptr(1);
1547 noise_ultralight(lParam);
1548 return 0;
1549 case WM_IGNORE_CLIP:
1550 ignore_clip = wParam; /* don't panic on DESTROYCLIPBOARD */
1551 break;
1552 case WM_DESTROYCLIPBOARD:
1553 if (!ignore_clip)
1554 term_deselect();
1555 ignore_clip = FALSE;
1556 return 0;
1557 case WM_PAINT:
1558 {
1559 PAINTSTRUCT p;
1560 HideCaret(hwnd);
1561 hdc = BeginPaint(hwnd, &p);
1562 if (pal) {
1563 SelectPalette(hdc, pal, TRUE);
1564 RealizePalette(hdc);
1565 }
1566 term_paint(hdc, p.rcPaint.left, p.rcPaint.top,
1567 p.rcPaint.right, p.rcPaint.bottom);
1568 SelectObject(hdc, GetStockObject(SYSTEM_FONT));
1569 SelectObject(hdc, GetStockObject(WHITE_PEN));
1570 EndPaint(hwnd, &p);
1571 ShowCaret(hwnd);
1572 }
1573 return 0;
1574 case WM_NETEVENT:
1575 /* Notice we can get multiple netevents, FD_READ, FD_WRITE etc
1576 * but the only one that's likely to try to overload us is FD_READ.
1577 * This means buffering just one is fine.
1578 */
1579 if (pending_netevent)
1580 enact_pending_netevent();
1581
1582 pending_netevent = TRUE;
1583 pend_netevent_wParam = wParam;
1584 pend_netevent_lParam = lParam;
1585 time(&last_movement);
1586 return 0;
1587 case WM_SETFOCUS:
1588 has_focus = TRUE;
1589 CreateCaret(hwnd, caretbm, font_width, font_height);
1590 ShowCaret(hwnd);
1591 compose_state = 0;
1592 term_out();
1593 term_update();
1594 break;
1595 case WM_KILLFOCUS:
1596 show_mouseptr(1);
1597 has_focus = FALSE;
1598 DestroyCaret();
1599 term_out();
1600 term_update();
1601 break;
1602 case WM_IGNORE_SIZE:
1603 ignore_size = TRUE; /* don't panic on next WM_SIZE msg */
1604 break;
1605 case WM_ENTERSIZEMOVE:
1606 EnableSizeTip(1);
1607 resizing = TRUE;
1608 need_backend_resize = FALSE;
1609 break;
1610 case WM_EXITSIZEMOVE:
1611 EnableSizeTip(0);
1612 resizing = FALSE;
1613 if (need_backend_resize)
1614 back->size();
1615 break;
1616 case WM_SIZING:
1617 {
1618 int width, height, w, h, ew, eh;
1619 LPRECT r = (LPRECT) lParam;
1620
1621 width = r->right - r->left - extra_width;
1622 height = r->bottom - r->top - extra_height;
1623 w = (width + font_width / 2) / font_width;
1624 if (w < 1)
1625 w = 1;
1626 h = (height + font_height / 2) / font_height;
1627 if (h < 1)
1628 h = 1;
1629 UpdateSizeTip(hwnd, w, h);
1630 ew = width - w * font_width;
1631 eh = height - h * font_height;
1632 if (ew != 0) {
1633 if (wParam == WMSZ_LEFT ||
1634 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
1635 r->left += ew;
1636 else
1637 r->right -= ew;
1638 }
1639 if (eh != 0) {
1640 if (wParam == WMSZ_TOP ||
1641 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
1642 r->top += eh;
1643 else
1644 r->bottom -= eh;
1645 }
1646 if (ew || eh)
1647 return 1;
1648 else
1649 return 0;
1650 }
1651 /* break; (never reached) */
1652 case WM_SIZE:
1653 if (wParam == SIZE_MINIMIZED) {
1654 SetWindowText(hwnd,
1655 cfg.win_name_always ? window_name : icon_name);
1656 break;
1657 }
1658 if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
1659 SetWindowText(hwnd, window_name);
1660 if (!ignore_size) {
1661 int width, height, w, h;
1662 #if 0 /* we have fixed this using WM_SIZING now */
1663 int ew, eh;
1664 #endif
1665
1666 width = LOWORD(lParam);
1667 height = HIWORD(lParam);
1668 w = width / font_width;
1669 if (w < 1)
1670 w = 1;
1671 h = height / font_height;
1672 if (h < 1)
1673 h = 1;
1674 #if 0 /* we have fixed this using WM_SIZING now */
1675 ew = width - w * font_width;
1676 eh = height - h * font_height;
1677 if (ew != 0 || eh != 0) {
1678 RECT r;
1679 GetWindowRect(hwnd, &r);
1680 SendMessage(hwnd, WM_IGNORE_SIZE, 0, 0);
1681 SetWindowPos(hwnd, NULL, 0, 0,
1682 r.right - r.left - ew, r.bottom - r.top - eh,
1683 SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER);
1684 }
1685 #endif
1686 if (w != cols || h != rows || just_reconfigged) {
1687 term_invalidate();
1688 term_size(h, w, cfg.savelines);
1689 /*
1690 * Don't call back->size in mid-resize. (To prevent
1691 * massive numbers of resize events getting sent
1692 * down the connection during an NT opaque drag.)
1693 */
1694 if (!resizing)
1695 back->size();
1696 else
1697 need_backend_resize = TRUE;
1698 just_reconfigged = FALSE;
1699 }
1700 }
1701 ignore_size = FALSE;
1702 return 0;
1703 case WM_VSCROLL:
1704 switch (LOWORD(wParam)) {
1705 case SB_BOTTOM:
1706 term_scroll(-1, 0);
1707 break;
1708 case SB_TOP:
1709 term_scroll(+1, 0);
1710 break;
1711 case SB_LINEDOWN:
1712 term_scroll(0, +1);
1713 break;
1714 case SB_LINEUP:
1715 term_scroll(0, -1);
1716 break;
1717 case SB_PAGEDOWN:
1718 term_scroll(0, +rows / 2);
1719 break;
1720 case SB_PAGEUP:
1721 term_scroll(0, -rows / 2);
1722 break;
1723 case SB_THUMBPOSITION:
1724 case SB_THUMBTRACK:
1725 term_scroll(1, HIWORD(wParam));
1726 break;
1727 }
1728 break;
1729 case WM_PALETTECHANGED:
1730 if ((HWND) wParam != hwnd && pal != NULL) {
1731 HDC hdc = get_ctx();
1732 if (hdc) {
1733 if (RealizePalette(hdc) > 0)
1734 UpdateColors(hdc);
1735 free_ctx(hdc);
1736 }
1737 }
1738 break;
1739 case WM_QUERYNEWPALETTE:
1740 if (pal != NULL) {
1741 HDC hdc = get_ctx();
1742 if (hdc) {
1743 if (RealizePalette(hdc) > 0)
1744 UpdateColors(hdc);
1745 free_ctx(hdc);
1746 return TRUE;
1747 }
1748 }
1749 return FALSE;
1750 case WM_KEYDOWN:
1751 case WM_SYSKEYDOWN:
1752 case WM_KEYUP:
1753 case WM_SYSKEYUP:
1754 /*
1755 * Add the scan code and keypress timing to the random
1756 * number noise.
1757 */
1758 noise_ultralight(lParam);
1759
1760 /*
1761 * We don't do TranslateMessage since it disassociates the
1762 * resulting CHAR message from the KEYDOWN that sparked it,
1763 * which we occasionally don't want. Instead, we process
1764 * KEYDOWN, and call the Win32 translator functions so that
1765 * we get the translations under _our_ control.
1766 */
1767 {
1768 unsigned char buf[20];
1769 int len;
1770
1771 if (wParam == VK_PROCESSKEY) {
1772 MSG m;
1773 m.hwnd = hwnd;
1774 m.message = WM_KEYDOWN;
1775 m.wParam = wParam;
1776 m.lParam = lParam & 0xdfff;
1777 TranslateMessage(&m);
1778 } else {
1779 len = TranslateKey(message, wParam, lParam, buf);
1780 if (len == -1)
1781 return DefWindowProc(hwnd, message, wParam, lParam);
1782 ldisc_send(buf, len);
1783
1784 if (len > 0)
1785 show_mouseptr(0);
1786 }
1787 }
1788 return 0;
1789 case WM_IME_CHAR:
1790 {
1791 unsigned char buf[2];
1792
1793 buf[1] = wParam;
1794 buf[0] = wParam >> 8;
1795 ldisc_send(buf, 2);
1796 }
1797 case WM_CHAR:
1798 case WM_SYSCHAR:
1799 /*
1800 * Nevertheless, we are prepared to deal with WM_CHAR
1801 * messages, should they crop up. So if someone wants to
1802 * post the things to us as part of a macro manoeuvre,
1803 * we're ready to cope.
1804 */
1805 {
1806 char c = xlat_kbd2tty((unsigned char) wParam);
1807 ldisc_send(&c, 1);
1808 }
1809 return 0;
1810 case WM_SETCURSOR:
1811 if (send_raw_mouse) {
1812 SetCursor(LoadCursor(NULL, IDC_ARROW));
1813 return TRUE;
1814 }
1815 }
1816
1817 return DefWindowProc(hwnd, message, wParam, lParam);
1818 }
1819
1820 /*
1821 * Move the system caret. (We maintain one, even though it's
1822 * invisible, for the benefit of blind people: apparently some
1823 * helper software tracks the system caret, so we should arrange to
1824 * have one.)
1825 */
1826 void sys_cursor(int x, int y)
1827 {
1828 if (has_focus)
1829 SetCaretPos(x * font_width, y * font_height);
1830 }
1831
1832 /*
1833 * Draw a line of text in the window, at given character
1834 * coordinates, in given attributes.
1835 *
1836 * We are allowed to fiddle with the contents of `text'.
1837 */
1838 void do_text(Context ctx, int x, int y, char *text, int len,
1839 unsigned long attr, int lattr)
1840 {
1841 COLORREF fg, bg, t;
1842 int nfg, nbg, nfont;
1843 HDC hdc = ctx;
1844 RECT line_box;
1845 int force_manual_underline = 0;
1846 int fnt_width = font_width * (1 + (lattr != LATTR_NORM));
1847 static int *IpDx = 0, IpDxLEN = 0;;
1848
1849 if (len > IpDxLEN || IpDx[0] != fnt_width) {
1850 int i;
1851 if (len > IpDxLEN) {
1852 sfree(IpDx);
1853 IpDx = smalloc((len + 16) * sizeof(int));
1854 IpDxLEN = (len + 16);
1855 }
1856 for (i = 0; i < IpDxLEN; i++)
1857 IpDx[i] = fnt_width;
1858 }
1859
1860 x *= fnt_width;
1861 y *= font_height;
1862
1863 if ((attr & ATTR_ACTCURS) && cfg.cursor_type == 0) {
1864 attr &= (bold_mode == BOLD_COLOURS ? 0x300200 : 0x300300);
1865 attr ^= ATTR_CUR_XOR;
1866 }
1867
1868 nfont = 0;
1869 if (cfg.vtmode == VT_OEMONLY)
1870 nfont |= FONT_OEM;
1871
1872 /*
1873 * Map high-half characters in order to approximate ISO using
1874 * OEM character set. No characters are missing if the OEM codepage
1875 * is CP850.
1876 */
1877 if (nfont & FONT_OEM) {
1878 int i;
1879 for (i = 0; i < len; i++)
1880 if (text[i] >= '\xA0' && text[i] <= '\xFF') {
1881 #if 0
1882 /* This is CP850 ... perfect translation */
1883 static const char oemhighhalf[] = "\x20\xAD\xBD\x9C\xCF\xBE\xDD\xF5" /* A0-A7 */
1884 "\xF9\xB8\xA6\xAE\xAA\xF0\xA9\xEE" /* A8-AF */
1885 "\xF8\xF1\xFD\xFC\xEF\xE6\xF4\xFA" /* B0-B7 */
1886 "\xF7\xFB\xA7\xAF\xAC\xAB\xF3\xA8" /* B8-BF */
1887 "\xB7\xB5\xB6\xC7\x8E\x8F\x92\x80" /* C0-C7 */
1888 "\xD4\x90\xD2\xD3\xDE\xD6\xD7\xD8" /* C8-CF */
1889 "\xD1\xA5\xE3\xE0\xE2\xE5\x99\x9E" /* D0-D7 */
1890 "\x9D\xEB\xE9\xEA\x9A\xED\xE8\xE1" /* D8-DF */
1891 "\x85\xA0\x83\xC6\x84\x86\x91\x87" /* E0-E7 */
1892 "\x8A\x82\x88\x89\x8D\xA1\x8C\x8B" /* E8-EF */
1893 "\xD0\xA4\x95\xA2\x93\xE4\x94\xF6" /* F0-F7 */
1894 "\x9B\x97\xA3\x96\x81\xEC\xE7\x98" /* F8-FF */
1895 ;
1896 #endif
1897 /* This is CP437 ... junk translation */
1898 static const unsigned char oemhighhalf[] = {
1899 0x20, 0xad, 0x9b, 0x9c, 0x6f, 0x9d, 0x7c, 0x15,
1900 0x22, 0x43, 0xa6, 0xae, 0xaa, 0x2d, 0x52, 0xc4,
1901 0xf8, 0xf1, 0xfd, 0x33, 0x27, 0xe6, 0x14, 0xfa,
1902 0x2c, 0x31, 0xa7, 0xaf, 0xac, 0xab, 0x2f, 0xa8,
1903 0x41, 0x41, 0x41, 0x41, 0x8e, 0x8f, 0x92, 0x80,
1904 0x45, 0x90, 0x45, 0x45, 0x49, 0x49, 0x49, 0x49,
1905 0x44, 0xa5, 0x4f, 0x4f, 0x4f, 0x4f, 0x99, 0x78,
1906 0xed, 0x55, 0x55, 0x55, 0x9a, 0x59, 0x50, 0xe1,
1907 0x85, 0xa0, 0x83, 0x61, 0x84, 0x86, 0x91, 0x87,
1908 0x8a, 0x82, 0x88, 0x89, 0x8d, 0xa1, 0x8c, 0x8b,
1909 0x0b, 0xa4, 0x95, 0xa2, 0x93, 0x6f, 0x94, 0xf6,
1910 0xed, 0x97, 0xa3, 0x96, 0x81, 0x79, 0x70, 0x98
1911 };
1912
1913 text[i] = oemhighhalf[(unsigned char) text[i] - 0xA0];
1914 }
1915 }
1916
1917 if (attr & ATTR_LINEDRW) {
1918 int i;
1919 /* ISO 8859-1 */
1920 static const char poorman[] =
1921 "*#****\xB0\xB1**+++++-----++++|****\xA3\xB7";
1922
1923 /* CP437 */
1924 static const char oemmap_437[] =
1925 "\x04\xB1****\xF8\xF1**\xD9\xBF\xDA\xC0\xC5"
1926 "\xC4\xC4\xC4\xC4\xC4\xC3\xB4\xC1\xC2\xB3\xF3\xF2\xE3*\x9C\xFA";
1927
1928 /* CP850 */
1929 static const char oemmap_850[] =
1930 "\x04\xB1****\xF8\xF1**\xD9\xBF\xDA\xC0\xC5"
1931 "\xC4\xC4\xC4\xC4\xC4\xC3\xB4\xC1\xC2\xB3****\x9C\xFA";
1932
1933 /* Poor windows font ... eg: windows courier */
1934 static const char oemmap[] =
1935 "*\xB1****\xF8\xF1**\xD9\xBF\xDA\xC0\xC5"
1936 "\xC4\xC4\xC4\xC4\xC4\xC3\xB4\xC1\xC2\xB3****\x9C\xFA";
1937
1938 /*
1939 * Line drawing mapping: map ` thru ~ (0x60 thru 0x7E) to
1940 * VT100 line drawing chars; everything else stays normal.
1941 *
1942 * Actually '_' maps to space too, but that's done before.
1943 */
1944 switch (cfg.vtmode) {
1945 case VT_XWINDOWS:
1946 for (i = 0; i < len; i++)
1947 if (text[i] >= '\x60' && text[i] <= '\x7E')
1948 text[i] += '\x01' - '\x60';
1949 break;
1950 case VT_OEMANSI:
1951 /* Make sure we actually have an OEM font */
1952 if (fonts[nfont | FONT_OEM]) {
1953 case VT_OEMONLY:
1954 nfont |= FONT_OEM;
1955 for (i = 0; i < len; i++)
1956 if (text[i] >= '\x60' && text[i] <= '\x7E')
1957 text[i] = oemmap[(unsigned char) text[i] - 0x60];
1958 break;
1959 }
1960 case VT_POORMAN:
1961 for (i = 0; i < len; i++)
1962 if (text[i] >= '\x60' && text[i] <= '\x7E')
1963 text[i] = poorman[(unsigned char) text[i] - 0x60];
1964 break;
1965 }
1966 }
1967
1968 nfg = 2 * ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
1969 nbg = 2 * ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
1970 if (bold_mode == BOLD_FONT && (attr & ATTR_BOLD))
1971 nfont |= FONT_BOLD;
1972 if (und_mode == UND_FONT && (attr & ATTR_UNDER))
1973 nfont |= FONT_UNDERLINE;
1974 if (!fonts[nfont]) {
1975 if (nfont & FONT_UNDERLINE)
1976 force_manual_underline = 1;
1977 /* Don't do the same for manual bold, it could be bad news. */
1978
1979 nfont &= ~(FONT_BOLD | FONT_UNDERLINE);
1980 }
1981 if (font_needs_hand_underlining && (attr & ATTR_UNDER))
1982 force_manual_underline = 1;
1983 if (attr & ATTR_REVERSE) {
1984 t = nfg;
1985 nfg = nbg;
1986 nbg = t;
1987 }
1988 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BOLD))
1989 nfg++;
1990 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BLINK))
1991 nbg++;
1992 fg = colours[nfg];
1993 bg = colours[nbg];
1994 SelectObject(hdc, fonts[nfont]);
1995 SetTextColor(hdc, fg);
1996 SetBkColor(hdc, bg);
1997 SetBkMode(hdc, OPAQUE);
1998 line_box.left = x;
1999 line_box.top = y;
2000 line_box.right = x + fnt_width * len;
2001 line_box.bottom = y + font_height;
2002 ExtTextOut(hdc, x, y, ETO_CLIPPED | ETO_OPAQUE, &line_box, text, len,
2003 IpDx);
2004 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2005 SetBkMode(hdc, TRANSPARENT);
2006
2007 /* GRR: This draws the character outside it's box and can leave
2008 * 'droppings' even with the clip box! I suppose I could loop it
2009 * one character at a time ... yuk.
2010 *
2011 * Or ... I could do a test print with "W", and use +1 or -1 for this
2012 * shift depending on if the leftmost column is blank...
2013 */
2014 ExtTextOut(hdc, x - 1, y, ETO_CLIPPED, &line_box, text, len, IpDx);
2015 }
2016 if (force_manual_underline ||
2017 (und_mode == UND_LINE && (attr & ATTR_UNDER))) {
2018 HPEN oldpen;
2019 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, fg));
2020 MoveToEx(hdc, x, y + descent, NULL);
2021 LineTo(hdc, x + len * fnt_width, y + descent);
2022 oldpen = SelectObject(hdc, oldpen);
2023 DeleteObject(oldpen);
2024 }
2025 if ((attr & ATTR_PASCURS) && cfg.cursor_type == 0) {
2026 POINT pts[5];
2027 HPEN oldpen;
2028 pts[0].x = pts[1].x = pts[4].x = x;
2029 pts[2].x = pts[3].x = x + fnt_width - 1;
2030 pts[0].y = pts[3].y = pts[4].y = y;
2031 pts[1].y = pts[2].y = y + font_height - 1;
2032 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2033 Polyline(hdc, pts, 5);
2034 oldpen = SelectObject(hdc, oldpen);
2035 DeleteObject(oldpen);
2036 }
2037 if ((attr & (ATTR_ACTCURS | ATTR_PASCURS)) && cfg.cursor_type != 0) {
2038 int startx, starty, dx, dy, length, i;
2039 if (cfg.cursor_type == 1) {
2040 startx = x;
2041 starty = y + descent;
2042 dx = 1;
2043 dy = 0;
2044 length = fnt_width;
2045 } else {
2046 int xadjust = 0;
2047 if (attr & ATTR_RIGHTCURS)
2048 xadjust = fnt_width - 1;
2049 startx = x + xadjust;
2050 starty = y;
2051 dx = 0;
2052 dy = 1;
2053 length = font_height;
2054 }
2055 if (attr & ATTR_ACTCURS) {
2056 HPEN oldpen;
2057 oldpen =
2058 SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2059 MoveToEx(hdc, startx, starty, NULL);
2060 LineTo(hdc, startx + dx * length, starty + dy * length);
2061 oldpen = SelectObject(hdc, oldpen);
2062 DeleteObject(oldpen);
2063 } else {
2064 for (i = 0; i < length; i++) {
2065 if (i % 2 == 0) {
2066 SetPixel(hdc, startx, starty, colours[23]);
2067 }
2068 startx += dx;
2069 starty += dy;
2070 }
2071 }
2072 }
2073 }
2074
2075 static int check_compose(int first, int second)
2076 {
2077
2078 static char *composetbl[] = {
2079 "++#", "AA@", "(([", "//\\", "))]", "(-{", "-)}", "/^|", "!!¡",
2080 "C/¢",
2081 "C|¢", "L-£", "L=£", "XO¤", "X0¤", "Y-¥", "Y=¥", "||¦", "SO§",
2082 "S!§",
2083 "S0§", "\"\"¨", "CO©", "C0©", "A_ª", "<<«", ",-¬", "--­", "RO®",
2084 "-^¯", "0^°", "+-±", "2^²", "3^³", "''´", "/Uµ", "P!¶", ".^·",
2085 ",,¸",
2086 "1^¹", "O_º", ">>»", "14¼", "12½", "34¾", "??¿", "`AÀ", "'AÁ",
2087 "^AÂ",
2088 "~AÃ", "\"AÄ", "*AÅ", "AEÆ", ",CÇ", "`EÈ", "'EÉ", "^EÊ", "\"EË",
2089 "`IÌ", "'IÍ", "^IÎ", "\"IÏ", "-DÐ", "~NÑ", "`OÒ", "'OÓ", "^OÔ",
2090 "~OÕ", "\"OÖ", "XX×", "/OØ", "`UÙ", "'UÚ", "^UÛ", "\"UÜ", "'YÝ",
2091 "HTÞ", "ssß", "`aà", "'aá", "^aâ", "~aã", "\"aä", "*aå", "aeæ",
2092 ",cç",
2093 "`eè", "'eé", "^eê", "\"eë", "`iì", "'ií", "^iî", "\"iï", "-dð",
2094 "~nñ",
2095 "`oò", "'oó", "^oô", "~oõ", "\"oö", ":-÷", "o/ø", "`uù", "'uú",
2096 "^uû",
2097 "\"uü", "'yý", "htþ", "\"yÿ",
2098 0
2099 };
2100
2101 char **c;
2102 static int recurse = 0;
2103 int nc = -1;
2104
2105 for (c = composetbl; *c; c++) {
2106 if ((*c)[0] == first && (*c)[1] == second) {
2107 return (*c)[2] & 0xFF;
2108 }
2109 }
2110
2111 if (recurse == 0) {
2112 recurse = 1;
2113 nc = check_compose(second, first);
2114 if (nc == -1)
2115 nc = check_compose(toupper(first), toupper(second));
2116 if (nc == -1)
2117 nc = check_compose(toupper(second), toupper(first));
2118 recurse = 0;
2119 }
2120 return nc;
2121 }
2122
2123
2124 /*
2125 * Translate a WM_(SYS)?KEY(UP|DOWN) message into a string of ASCII
2126 * codes. Returns number of bytes used or zero to drop the message
2127 * or -1 to forward the message to windows.
2128 */
2129 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
2130 unsigned char *output)
2131 {
2132 BYTE keystate[256];
2133 int scan, left_alt = 0, key_down, shift_state;
2134 int r, i, code;
2135 unsigned char *p = output;
2136 static int alt_state = 0;
2137
2138 HKL kbd_layout = GetKeyboardLayout(0);
2139
2140 static WORD keys[3];
2141 static int compose_char = 0;
2142 static WPARAM compose_key = 0;
2143
2144 r = GetKeyboardState(keystate);
2145 if (!r)
2146 memset(keystate, 0, sizeof(keystate));
2147 else {
2148 #if 0
2149 { /* Tell us all about key events */
2150 static BYTE oldstate[256];
2151 static int first = 1;
2152 static int scan;
2153 int ch;
2154 if (first)
2155 memcpy(oldstate, keystate, sizeof(oldstate));
2156 first = 0;
2157
2158 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT) {
2159 debug(("+"));
2160 } else if ((HIWORD(lParam) & KF_UP)
2161 && scan == (HIWORD(lParam) & 0xFF)) {
2162 debug((". U"));
2163 } else {
2164 debug((".\n"));
2165 if (wParam >= VK_F1 && wParam <= VK_F20)
2166 debug(("K_F%d", wParam + 1 - VK_F1));
2167 else
2168 switch (wParam) {
2169 case VK_SHIFT:
2170 debug(("SHIFT"));
2171 break;
2172 case VK_CONTROL:
2173 debug(("CTRL"));
2174 break;
2175 case VK_MENU:
2176 debug(("ALT"));
2177 break;
2178 default:
2179 debug(("VK_%02x", wParam));
2180 }
2181 if (message == WM_SYSKEYDOWN || message == WM_SYSKEYUP)
2182 debug(("*"));
2183 debug((", S%02x", scan = (HIWORD(lParam) & 0xFF)));
2184
2185 ch = MapVirtualKeyEx(wParam, 2, kbd_layout);
2186 if (ch >= ' ' && ch <= '~')
2187 debug((", '%c'", ch));
2188 else if (ch)
2189 debug((", $%02x", ch));
2190
2191 if (keys[0])
2192 debug((", KB0=%02x", keys[0]));
2193 if (keys[1])
2194 debug((", KB1=%02x", keys[1]));
2195 if (keys[2])
2196 debug((", KB2=%02x", keys[2]));
2197
2198 if ((keystate[VK_SHIFT] & 0x80) != 0)
2199 debug((", S"));
2200 if ((keystate[VK_CONTROL] & 0x80) != 0)
2201 debug((", C"));
2202 if ((HIWORD(lParam) & KF_EXTENDED))
2203 debug((", E"));
2204 if ((HIWORD(lParam) & KF_UP))
2205 debug((", U"));
2206 }
2207
2208 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT);
2209 else if ((HIWORD(lParam) & KF_UP))
2210 oldstate[wParam & 0xFF] ^= 0x80;
2211 else
2212 oldstate[wParam & 0xFF] ^= 0x81;
2213
2214 for (ch = 0; ch < 256; ch++)
2215 if (oldstate[ch] != keystate[ch])
2216 debug((", M%02x=%02x", ch, keystate[ch]));
2217
2218 memcpy(oldstate, keystate, sizeof(oldstate));
2219 }
2220 #endif
2221
2222 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED)) {
2223 keystate[VK_RMENU] = keystate[VK_MENU];
2224 }
2225
2226
2227 /* Nastyness with NUMLock - Shift-NUMLock is left alone though */
2228 if ((cfg.funky_type == 3 ||
2229 (cfg.funky_type <= 1 && app_keypad_keys && !cfg.no_applic_k))
2230 && wParam == VK_NUMLOCK && !(keystate[VK_SHIFT] & 0x80)) {
2231
2232 wParam = VK_EXECUTE;
2233
2234 /* UnToggle NUMLock */
2235 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0)
2236 keystate[VK_NUMLOCK] ^= 1;
2237 }
2238
2239 /* And write back the 'adjusted' state */
2240 SetKeyboardState(keystate);
2241 }
2242
2243 /* Disable Auto repeat if required */
2244 if (repeat_off && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT)
2245 return 0;
2246
2247 if ((HIWORD(lParam) & KF_ALTDOWN) && (keystate[VK_RMENU] & 0x80) == 0)
2248 left_alt = 1;
2249
2250 key_down = ((HIWORD(lParam) & KF_UP) == 0);
2251
2252 /* Make sure Ctrl-ALT is not the same as AltGr for ToAscii unless told. */
2253 if (left_alt && (keystate[VK_CONTROL] & 0x80)) {
2254 if (cfg.ctrlaltkeys)
2255 keystate[VK_MENU] = 0;
2256 else {
2257 keystate[VK_RMENU] = 0x80;
2258 left_alt = 0;
2259 }
2260 }
2261
2262 scan = (HIWORD(lParam) & (KF_UP | KF_EXTENDED | 0xFF));
2263 shift_state = ((keystate[VK_SHIFT] & 0x80) != 0)
2264 + ((keystate[VK_CONTROL] & 0x80) != 0) * 2;
2265
2266 /* Note if AltGr was pressed and if it was used as a compose key */
2267 if (!compose_state) {
2268 compose_key = 0x100;
2269 if (cfg.compose_key) {
2270 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED))
2271 compose_key = wParam;
2272 }
2273 if (wParam == VK_APPS)
2274 compose_key = wParam;
2275 }
2276
2277 if (wParam == compose_key) {
2278 if (compose_state == 0
2279 && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0) compose_state =
2280 1;
2281 else if (compose_state == 1 && (HIWORD(lParam) & KF_UP))
2282 compose_state = 2;
2283 else
2284 compose_state = 0;
2285 } else if (compose_state == 1 && wParam != VK_CONTROL)
2286 compose_state = 0;
2287
2288 /*
2289 * Record that we pressed key so the scroll window can be reset, but
2290 * be careful to avoid Shift-UP/Down
2291 */
2292 if (wParam != VK_SHIFT && wParam != VK_PRIOR && wParam != VK_NEXT) {
2293 seen_key_event = 1;
2294 }
2295
2296 /* Make sure we're not pasting */
2297 if (key_down)
2298 term_nopaste();
2299
2300 if (compose_state > 1 && left_alt)
2301 compose_state = 0;
2302
2303 /* Sanitize the number pad if not using a PC NumPad */
2304 if (left_alt || (app_keypad_keys && !cfg.no_applic_k
2305 && cfg.funky_type != 2)
2306 || cfg.funky_type == 3 || cfg.nethack_keypad || compose_state) {
2307 if ((HIWORD(lParam) & KF_EXTENDED) == 0) {
2308 int nParam = 0;
2309 switch (wParam) {
2310 case VK_INSERT:
2311 nParam = VK_NUMPAD0;
2312 break;
2313 case VK_END:
2314 nParam = VK_NUMPAD1;
2315 break;
2316 case VK_DOWN:
2317 nParam = VK_NUMPAD2;
2318 break;
2319 case VK_NEXT:
2320 nParam = VK_NUMPAD3;
2321 break;
2322 case VK_LEFT:
2323 nParam = VK_NUMPAD4;
2324 break;
2325 case VK_CLEAR:
2326 nParam = VK_NUMPAD5;
2327 break;
2328 case VK_RIGHT:
2329 nParam = VK_NUMPAD6;
2330 break;
2331 case VK_HOME:
2332 nParam = VK_NUMPAD7;
2333 break;
2334 case VK_UP:
2335 nParam = VK_NUMPAD8;
2336 break;
2337 case VK_PRIOR:
2338 nParam = VK_NUMPAD9;
2339 break;
2340 case VK_DELETE:
2341 nParam = VK_DECIMAL;
2342 break;
2343 }
2344 if (nParam) {
2345 if (keystate[VK_NUMLOCK] & 1)
2346 shift_state |= 1;
2347 wParam = nParam;
2348 }
2349 }
2350 }
2351
2352 /* If a key is pressed and AltGr is not active */
2353 if (key_down && (keystate[VK_RMENU] & 0x80) == 0 && !compose_state) {
2354 /* Okay, prepare for most alts then ... */
2355 if (left_alt)
2356 *p++ = '\033';
2357
2358 /* Lets see if it's a pattern we know all about ... */
2359 if (wParam == VK_PRIOR && shift_state == 1) {
2360 SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0);
2361 return 0;
2362 }
2363 if (wParam == VK_NEXT && shift_state == 1) {
2364 SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
2365 return 0;
2366 }
2367 if (wParam == VK_INSERT && shift_state == 1) {
2368 term_mouse(MBT_PASTE, MA_CLICK, 0, 0, 0, 0);
2369 term_mouse(MBT_PASTE, MA_RELEASE, 0, 0, 0, 0);
2370 return 0;
2371 }
2372 if (left_alt && wParam == VK_F4 && cfg.alt_f4) {
2373 return -1;
2374 }
2375 if (left_alt && wParam == VK_SPACE && cfg.alt_space) {
2376 alt_state = 0;
2377 PostMessage(hwnd, WM_CHAR, ' ', 0);
2378 SendMessage(hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0);
2379 return -1;
2380 }
2381 /* Control-Numlock for app-keypad mode switch */
2382 if (wParam == VK_PAUSE && shift_state == 2) {
2383 app_keypad_keys ^= 1;
2384 return 0;
2385 }
2386
2387 /* Nethack keypad */
2388 if (cfg.nethack_keypad && !left_alt) {
2389 switch (wParam) {
2390 case VK_NUMPAD1:
2391 *p++ = shift_state ? 'B' : 'b';
2392 return p - output;
2393 case VK_NUMPAD2:
2394 *p++ = shift_state ? 'J' : 'j';
2395 return p - output;
2396 case VK_NUMPAD3:
2397 *p++ = shift_state ? 'N' : 'n';
2398 return p - output;
2399 case VK_NUMPAD4:
2400 *p++ = shift_state ? 'H' : 'h';
2401 return p - output;
2402 case VK_NUMPAD5:
2403 *p++ = shift_state ? '.' : '.';
2404 return p - output;
2405 case VK_NUMPAD6:
2406 *p++ = shift_state ? 'L' : 'l';
2407 return p - output;
2408 case VK_NUMPAD7:
2409 *p++ = shift_state ? 'Y' : 'y';
2410 return p - output;
2411 case VK_NUMPAD8:
2412 *p++ = shift_state ? 'K' : 'k';
2413 return p - output;
2414 case VK_NUMPAD9:
2415 *p++ = shift_state ? 'U' : 'u';
2416 return p - output;
2417 }
2418 }
2419
2420 /* Application Keypad */
2421 if (!left_alt) {
2422 int xkey = 0;
2423
2424 if (cfg.funky_type == 3 ||
2425 (cfg.funky_type <= 1 &&
2426 app_keypad_keys && !cfg.no_applic_k)) switch (wParam) {
2427 case VK_EXECUTE:
2428 xkey = 'P';
2429 break;
2430 case VK_DIVIDE:
2431 xkey = 'Q';
2432 break;
2433 case VK_MULTIPLY:
2434 xkey = 'R';
2435 break;
2436 case VK_SUBTRACT:
2437 xkey = 'S';
2438 break;
2439 }
2440 if (app_keypad_keys && !cfg.no_applic_k)
2441 switch (wParam) {
2442 case VK_NUMPAD0:
2443 xkey = 'p';
2444 break;
2445 case VK_NUMPAD1:
2446 xkey = 'q';
2447 break;
2448 case VK_NUMPAD2:
2449 xkey = 'r';
2450 break;
2451 case VK_NUMPAD3:
2452 xkey = 's';
2453 break;
2454 case VK_NUMPAD4:
2455 xkey = 't';
2456 break;
2457 case VK_NUMPAD5:
2458 xkey = 'u';
2459 break;
2460 case VK_NUMPAD6:
2461 xkey = 'v';
2462 break;
2463 case VK_NUMPAD7:
2464 xkey = 'w';
2465 break;
2466 case VK_NUMPAD8:
2467 xkey = 'x';
2468 break;
2469 case VK_NUMPAD9:
2470 xkey = 'y';
2471 break;
2472
2473 case VK_DECIMAL:
2474 xkey = 'n';
2475 break;
2476 case VK_ADD:
2477 if (cfg.funky_type == 2) {
2478 if (shift_state)
2479 xkey = 'l';
2480 else
2481 xkey = 'k';
2482 } else if (shift_state)
2483 xkey = 'm';
2484 else
2485 xkey = 'l';
2486 break;
2487
2488 case VK_DIVIDE:
2489 if (cfg.funky_type == 2)
2490 xkey = 'o';
2491 break;
2492 case VK_MULTIPLY:
2493 if (cfg.funky_type == 2)
2494 xkey = 'j';
2495 break;
2496 case VK_SUBTRACT:
2497 if (cfg.funky_type == 2)
2498 xkey = 'm';
2499 break;
2500
2501 case VK_RETURN:
2502 if (HIWORD(lParam) & KF_EXTENDED)
2503 xkey = 'M';
2504 break;
2505 }
2506 if (xkey) {
2507 if (vt52_mode) {
2508 if (xkey >= 'P' && xkey <= 'S')
2509 p += sprintf((char *) p, "\x1B%c", xkey);
2510 else
2511 p += sprintf((char *) p, "\x1B?%c", xkey);
2512 } else
2513 p += sprintf((char *) p, "\x1BO%c", xkey);
2514 return p - output;
2515 }
2516 }
2517
2518 if (wParam == VK_BACK && shift_state == 0) { /* Backspace */
2519 *p++ = (cfg.bksp_is_delete ? 0x7F : 0x08);
2520 return p - output;
2521 }
2522 if (wParam == VK_TAB && shift_state == 1) { /* Shift tab */
2523 *p++ = 0x1B;
2524 *p++ = '[';
2525 *p++ = 'Z';
2526 return p - output;
2527 }
2528 if (wParam == VK_SPACE && shift_state == 2) { /* Ctrl-Space */
2529 *p++ = 0;
2530 return p - output;
2531 }
2532 if (wParam == VK_SPACE && shift_state == 3) { /* Ctrl-Shift-Space */
2533 *p++ = 160;
2534 return p - output;
2535 }
2536 if (wParam == VK_CANCEL && shift_state == 2) { /* Ctrl-Break */
2537 *p++ = 3;
2538 return p - output;
2539 }
2540 if (wParam == VK_PAUSE) { /* Break/Pause */
2541 *p++ = 26;
2542 *p++ = 0;
2543 return -2;
2544 }
2545 /* Control-2 to Control-8 are special */
2546 if (shift_state == 2 && wParam >= '2' && wParam <= '8') {
2547 *p++ = "\000\033\034\035\036\037\177"[wParam - '2'];
2548 return p - output;
2549 }
2550 if (shift_state == 2 && wParam == 0xBD) {
2551 *p++ = 0x1F;
2552 return p - output;
2553 }
2554 if (shift_state == 2 && wParam == 0xDF) {
2555 *p++ = 0x1C;
2556 return p - output;
2557 }
2558 if (shift_state == 0 && wParam == VK_RETURN && cr_lf_return) {
2559 *p++ = '\r';
2560 *p++ = '\n';
2561 return p - output;
2562 }
2563
2564 /*
2565 * Next, all the keys that do tilde codes. (ESC '[' nn '~',
2566 * for integer decimal nn.)
2567 *
2568 * We also deal with the weird ones here. Linux VCs replace F1
2569 * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
2570 * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
2571 * respectively.
2572 */
2573 code = 0;
2574 switch (wParam) {
2575 case VK_F1:
2576 code = (keystate[VK_SHIFT] & 0x80 ? 23 : 11);
2577 break;
2578 case VK_F2:
2579 code = (keystate[VK_SHIFT] & 0x80 ? 24 : 12);
2580 break;
2581 case VK_F3:
2582 code = (keystate[VK_SHIFT] & 0x80 ? 25 : 13);
2583 break;
2584 case VK_F4:
2585 code = (keystate[VK_SHIFT] & 0x80 ? 26 : 14);
2586 break;
2587 case VK_F5:
2588 code = (keystate[VK_SHIFT] & 0x80 ? 28 : 15);
2589 break;
2590 case VK_F6:
2591 code = (keystate[VK_SHIFT] & 0x80 ? 29 : 17);
2592 break;
2593 case VK_F7:
2594 code = (keystate[VK_SHIFT] & 0x80 ? 31 : 18);
2595 break;
2596 case VK_F8:
2597 code = (keystate[VK_SHIFT] & 0x80 ? 32 : 19);
2598 break;
2599 case VK_F9:
2600 code = (keystate[VK_SHIFT] & 0x80 ? 33 : 20);
2601 break;
2602 case VK_F10:
2603 code = (keystate[VK_SHIFT] & 0x80 ? 34 : 21);
2604 break;
2605 case VK_F11:
2606 code = 23;
2607 break;
2608 case VK_F12:
2609 code = 24;
2610 break;
2611 case VK_F13:
2612 code = 25;
2613 break;
2614 case VK_F14:
2615 code = 26;
2616 break;
2617 case VK_F15:
2618 code = 28;
2619 break;
2620 case VK_F16:
2621 code = 29;
2622 break;
2623 case VK_F17:
2624 code = 31;
2625 break;
2626 case VK_F18:
2627 code = 32;
2628 break;
2629 case VK_F19:
2630 code = 33;
2631 break;
2632 case VK_F20:
2633 code = 34;
2634 break;
2635 case VK_HOME:
2636 code = 1;
2637 break;
2638 case VK_INSERT:
2639 code = 2;
2640 break;
2641 case VK_DELETE:
2642 code = 3;
2643 break;
2644 case VK_END:
2645 code = 4;
2646 break;
2647 case VK_PRIOR:
2648 code = 5;
2649 break;
2650 case VK_NEXT:
2651 code = 6;
2652 break;
2653 }
2654 /* Reorder edit keys to physical order */
2655 if (cfg.funky_type == 3 && code <= 6)
2656 code = "\0\2\1\4\5\3\6"[code];
2657
2658 if (vt52_mode && code > 0 && code <= 6) {
2659 p += sprintf((char *) p, "\x1B%c", " HLMEIG"[code]);
2660 return p - output;
2661 }
2662
2663 if (cfg.funky_type == 5 && code >= 11 && code <= 24) {
2664 p += sprintf((char *) p, "\x1B[%c", code + 'M' - 11);
2665 return p - output;
2666 }
2667 if ((vt52_mode || cfg.funky_type == 4) && code >= 11 && code <= 24) {
2668 int offt = 0;
2669 if (code > 15)
2670 offt++;
2671 if (code > 21)
2672 offt++;
2673 if (vt52_mode)
2674 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11 - offt);
2675 else
2676 p +=
2677 sprintf((char *) p, "\x1BO%c", code + 'P' - 11 - offt);
2678 return p - output;
2679 }
2680 if (cfg.funky_type == 1 && code >= 11 && code <= 15) {
2681 p += sprintf((char *) p, "\x1B[[%c", code + 'A' - 11);
2682 return p - output;
2683 }
2684 if (cfg.funky_type == 2 && code >= 11 && code <= 14) {
2685 if (vt52_mode)
2686 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11);
2687 else
2688 p += sprintf((char *) p, "\x1BO%c", code + 'P' - 11);
2689 return p - output;
2690 }
2691 if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
2692 p += sprintf((char *) p, code == 1 ? "\x1B[H" : "\x1BOw");
2693 return p - output;
2694 }
2695 if (code) {
2696 p += sprintf((char *) p, "\x1B[%d~", code);
2697 return p - output;
2698 }
2699
2700 /*
2701 * Now the remaining keys (arrows and Keypad 5. Keypad 5 for
2702 * some reason seems to send VK_CLEAR to Windows...).
2703 */
2704 {
2705 char xkey = 0;
2706 switch (wParam) {
2707 case VK_UP:
2708 xkey = 'A';
2709 break;
2710 case VK_DOWN:
2711 xkey = 'B';
2712 break;
2713 case VK_RIGHT:
2714 xkey = 'C';
2715 break;
2716 case VK_LEFT:
2717 xkey = 'D';
2718 break;
2719 case VK_CLEAR:
2720 xkey = 'G';
2721 break;
2722 }
2723 if (xkey) {
2724 if (vt52_mode)
2725 p += sprintf((char *) p, "\x1B%c", xkey);
2726 else if (app_cursor_keys && !cfg.no_applic_c)
2727 p += sprintf((char *) p, "\x1BO%c", xkey);
2728 else
2729 p += sprintf((char *) p, "\x1B[%c", xkey);
2730 return p - output;
2731 }
2732 }
2733
2734 /*
2735 * Finally, deal with Return ourselves. (Win95 seems to
2736 * foul it up when Alt is pressed, for some reason.)
2737 */
2738 if (wParam == VK_RETURN) { /* Return */
2739 *p++ = 0x0D;
2740 return p - output;
2741 }
2742 }
2743
2744 /* Okay we've done everything interesting; let windows deal with
2745 * the boring stuff */
2746 {
2747 BOOL capsOn = keystate[VK_CAPITAL] != 0;
2748
2749 /* helg: clear CAPS LOCK state if caps lock switches to cyrillic */
2750 if (cfg.xlat_capslockcyr)
2751 keystate[VK_CAPITAL] = 0;
2752
2753 r = ToAsciiEx(wParam, scan, keystate, keys, 0, kbd_layout);
2754 if (r > 0) {
2755 p = output;
2756 for (i = 0; i < r; i++) {
2757 unsigned char ch = (unsigned char) keys[i];
2758
2759 if (compose_state == 2 && (ch & 0x80) == 0 && ch > ' ') {
2760 compose_char = ch;
2761 compose_state++;
2762 continue;
2763 }
2764 if (compose_state == 3 && (ch & 0x80) == 0 && ch > ' ') {
2765 int nc;
2766 compose_state = 0;
2767
2768 if ((nc = check_compose(compose_char, ch)) == -1) {
2769 MessageBeep(MB_ICONHAND);
2770 return 0;
2771 }
2772 *p++ = xlat_kbd2tty((unsigned char) nc);
2773 return p - output;
2774 }
2775
2776 compose_state = 0;
2777
2778 if (left_alt && key_down)
2779 *p++ = '\033';
2780 if (!key_down)
2781 *p++ = ch;
2782 else {
2783 if (capsOn)
2784 ch = xlat_latkbd2win(ch);
2785 *p++ = xlat_kbd2tty(ch);
2786 }
2787 }
2788
2789 /* This is so the ALT-Numpad and dead keys work correctly. */
2790 keys[0] = 0;
2791
2792 return p - output;
2793 }
2794 /* If we're definitly not building up an ALT-54321 then clear it */
2795 if (!left_alt)
2796 keys[0] = 0;
2797 }
2798
2799 /* ALT alone may or may not want to bring up the System menu */
2800 if (wParam == VK_MENU) {
2801 if (cfg.alt_only) {
2802 if (message == WM_SYSKEYDOWN)
2803 alt_state = 1;
2804 else if (message == WM_SYSKEYUP && alt_state)
2805 PostMessage(hwnd, WM_CHAR, ' ', 0);
2806 if (message == WM_SYSKEYUP)
2807 alt_state = 0;
2808 } else
2809 return 0;
2810 } else
2811 alt_state = 0;
2812
2813 return -1;
2814 }
2815
2816 void set_title(char *title)
2817 {
2818 sfree(window_name);
2819 window_name = smalloc(1 + strlen(title));
2820 strcpy(window_name, title);
2821 if (cfg.win_name_always || !IsIconic(hwnd))
2822 SetWindowText(hwnd, title);
2823 }
2824
2825 void set_icon(char *title)
2826 {
2827 sfree(icon_name);
2828 icon_name = smalloc(1 + strlen(title));
2829 strcpy(icon_name, title);
2830 if (!cfg.win_name_always && IsIconic(hwnd))
2831 SetWindowText(hwnd, title);
2832 }
2833
2834 void set_sbar(int total, int start, int page)
2835 {
2836 SCROLLINFO si;
2837
2838 if (!cfg.scrollbar)
2839 return;
2840
2841 si.cbSize = sizeof(si);
2842 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
2843 si.nMin = 0;
2844 si.nMax = total - 1;
2845 si.nPage = page;
2846 si.nPos = start;
2847 if (hwnd)
2848 SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
2849 }
2850
2851 Context get_ctx(void)
2852 {
2853 HDC hdc;
2854 if (hwnd) {
2855 hdc = GetDC(hwnd);
2856 if (hdc && pal)
2857 SelectPalette(hdc, pal, FALSE);
2858 return hdc;
2859 } else
2860 return NULL;
2861 }
2862
2863 void free_ctx(Context ctx)
2864 {
2865 SelectPalette(ctx, GetStockObject(DEFAULT_PALETTE), FALSE);
2866 ReleaseDC(hwnd, ctx);
2867 }
2868
2869 static void real_palette_set(int n, int r, int g, int b)
2870 {
2871 if (pal) {
2872 logpal->palPalEntry[n].peRed = r;
2873 logpal->palPalEntry[n].peGreen = g;
2874 logpal->palPalEntry[n].peBlue = b;
2875 logpal->palPalEntry[n].peFlags = PC_NOCOLLAPSE;
2876 colours[n] = PALETTERGB(r, g, b);
2877 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
2878 } else
2879 colours[n] = RGB(r, g, b);
2880 }
2881
2882 void palette_set(int n, int r, int g, int b)
2883 {
2884 static const int first[21] = {
2885 0, 2, 4, 6, 8, 10, 12, 14,
2886 1, 3, 5, 7, 9, 11, 13, 15,
2887 16, 17, 18, 20, 22
2888 };
2889 real_palette_set(first[n], r, g, b);
2890 if (first[n] >= 18)
2891 real_palette_set(first[n] + 1, r, g, b);
2892 if (pal) {
2893 HDC hdc = get_ctx();
2894 UnrealizeObject(pal);
2895 RealizePalette(hdc);
2896 free_ctx(hdc);
2897 }
2898 }
2899
2900 void palette_reset(void)
2901 {
2902 int i;
2903
2904 for (i = 0; i < NCOLOURS; i++) {
2905 if (pal) {
2906 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
2907 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
2908 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
2909 logpal->palPalEntry[i].peFlags = 0;
2910 colours[i] = PALETTERGB(defpal[i].rgbtRed,
2911 defpal[i].rgbtGreen,
2912 defpal[i].rgbtBlue);
2913 } else
2914 colours[i] = RGB(defpal[i].rgbtRed,
2915 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
2916 }
2917
2918 if (pal) {
2919 HDC hdc;
2920 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
2921 hdc = get_ctx();
2922 RealizePalette(hdc);
2923 free_ctx(hdc);
2924 }
2925 }
2926
2927 void write_clip(void *data, int len, int must_deselect)
2928 {
2929 HGLOBAL clipdata;
2930 void *lock;
2931
2932 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len + 1);
2933 if (!clipdata)
2934 return;
2935 lock = GlobalLock(clipdata);
2936 if (!lock)
2937 return;
2938 memcpy(lock, data, len);
2939 ((unsigned char *) lock)[len] = 0;
2940 GlobalUnlock(clipdata);
2941
2942 if (!must_deselect)
2943 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
2944
2945 if (OpenClipboard(hwnd)) {
2946 EmptyClipboard();
2947 SetClipboardData(CF_TEXT, clipdata);
2948 CloseClipboard();
2949 } else
2950 GlobalFree(clipdata);
2951
2952 if (!must_deselect)
2953 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
2954 }
2955
2956 void get_clip(void **p, int *len)
2957 {
2958 static HGLOBAL clipdata = NULL;
2959
2960 if (!p) {
2961 if (clipdata)
2962 GlobalUnlock(clipdata);
2963 clipdata = NULL;
2964 return;
2965 } else {
2966 if (OpenClipboard(NULL)) {
2967 clipdata = GetClipboardData(CF_TEXT);
2968 CloseClipboard();
2969 if (clipdata) {
2970 *p = GlobalLock(clipdata);
2971 if (*p) {
2972 *len = strlen(*p);
2973 return;
2974 }
2975 }
2976 }
2977 }
2978
2979 *p = NULL;
2980 *len = 0;
2981 }
2982
2983 /*
2984 * Move `lines' lines from position `from' to position `to' in the
2985 * window.
2986 */
2987 void optimised_move(int to, int from, int lines)
2988 {
2989 RECT r;
2990 int min, max;
2991
2992 min = (to < from ? to : from);
2993 max = to + from - min;
2994
2995 r.left = 0;
2996 r.right = cols * font_width;
2997 r.top = min * font_height;
2998 r.bottom = (max + lines) * font_height;
2999 ScrollWindow(hwnd, 0, (to - from) * font_height, &r, &r);
3000 }
3001
3002 /*
3003 * Print a message box and perform a fatal exit.
3004 */
3005 void fatalbox(char *fmt, ...)
3006 {
3007 va_list ap;
3008 char stuff[200];
3009
3010 va_start(ap, fmt);
3011 vsprintf(stuff, fmt, ap);
3012 va_end(ap);
3013 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
3014 exit(1);
3015 }
3016
3017 /*
3018 * Beep.
3019 */
3020 void beep(int mode)
3021 {
3022 if (mode == BELL_DEFAULT) {
3023 /*
3024 * For MessageBeep style bells, we want to be careful of
3025 * timing, because they don't have the nice property of
3026 * PlaySound bells that each one cancels the previous
3027 * active one. So we limit the rate to one per 50ms or so.
3028 */
3029 static long lastbeep = 0;
3030 long beepdiff;
3031
3032 beepdiff = GetTickCount() - lastbeep;
3033 if (beepdiff >= 0 && beepdiff < 50)
3034 return;
3035 MessageBeep(MB_OK);
3036 /*
3037 * The above MessageBeep call takes time, so we record the
3038 * time _after_ it finishes rather than before it starts.
3039 */
3040 lastbeep = GetTickCount();
3041 } else if (mode == BELL_WAVEFILE) {
3042 if (!PlaySound(cfg.bell_wavefile, NULL, SND_ASYNC | SND_FILENAME)) {
3043 char buf[sizeof(cfg.bell_wavefile) + 80];
3044 sprintf(buf, "Unable to play sound file\n%s\n"
3045 "Using default sound instead", cfg.bell_wavefile);
3046 MessageBox(hwnd, buf, "PuTTY Sound Error",
3047 MB_OK | MB_ICONEXCLAMATION);
3048 cfg.beep = BELL_DEFAULT;
3049 }
3050 }
3051 }