Fix for `hostname-whitespace'; thanks to Justin Bradford.
[u/mdw/putty] / window.c
1 #include <windows.h>
2 #include <imm.h>
3 #include <commctrl.h>
4 #include <richedit.h>
5 #include <mmsystem.h>
6 #ifndef AUTO_WINSOCK
7 #ifdef WINSOCK_TWO
8 #include <winsock2.h>
9 #else
10 #include <winsock.h>
11 #endif
12 #endif
13
14 #ifndef NO_MULTIMON
15 #if WINVER < 0x0500
16 #define COMPILE_MULTIMON_STUBS
17 #include <multimon.h>
18 #endif
19 #endif
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <ctype.h>
24 #include <time.h>
25 #include <assert.h>
26
27 #define PUTTY_DO_GLOBALS /* actually _define_ globals */
28 #include "putty.h"
29 #include "winstuff.h"
30 #include "storage.h"
31 #include "win_res.h"
32
33 #define IDM_SHOWLOG 0x0010
34 #define IDM_NEWSESS 0x0020
35 #define IDM_DUPSESS 0x0030
36 #define IDM_RECONF 0x0040
37 #define IDM_CLRSB 0x0050
38 #define IDM_RESET 0x0060
39 #define IDM_TEL_AYT 0x0070
40 #define IDM_TEL_BRK 0x0080
41 #define IDM_TEL_SYNCH 0x0090
42 #define IDM_TEL_EC 0x00a0
43 #define IDM_TEL_EL 0x00b0
44 #define IDM_TEL_GA 0x00c0
45 #define IDM_TEL_NOP 0x00d0
46 #define IDM_TEL_ABORT 0x00e0
47 #define IDM_TEL_AO 0x00f0
48 #define IDM_TEL_IP 0x0100
49 #define IDM_TEL_SUSP 0x0110
50 #define IDM_TEL_EOR 0x0120
51 #define IDM_TEL_EOF 0x0130
52 #define IDM_HELP 0x0140
53 #define IDM_ABOUT 0x0150
54 #define IDM_SAVEDSESS 0x0160
55 #define IDM_COPYALL 0x0170
56 #define IDM_FULLSCREEN 0x0180
57
58 #define IDM_SESSLGP 0x0250 /* log type printable */
59 #define IDM_SESSLGA 0x0260 /* log type all chars */
60 #define IDM_SESSLGE 0x0270 /* log end */
61 #define IDM_SAVED_MIN 0x1000
62 #define IDM_SAVED_MAX 0x2000
63
64 #define WM_IGNORE_CLIP (WM_XUSER + 2)
65 #define WM_FULLSCR_ON_MAX (WM_XUSER + 3)
66
67 /* Needed for Chinese support and apparently not always defined. */
68 #ifndef VK_PROCESSKEY
69 #define VK_PROCESSKEY 0xE5
70 #endif
71
72 /* Mouse wheel support. */
73 #ifndef WM_MOUSEWHEEL
74 #define WM_MOUSEWHEEL 0x020A /* not defined in earlier SDKs */
75 #endif
76 #ifndef WHEEL_DELTA
77 #define WHEEL_DELTA 120
78 #endif
79
80 static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
81 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
82 unsigned char *output);
83 static void cfgtopalette(void);
84 static void init_palette(void);
85 static void init_fonts(int, int);
86 static void another_font(int);
87 static void deinit_fonts(void);
88 static void set_input_locale(HKL);
89 static int do_mouse_wheel_msg(UINT message, WPARAM wParam, LPARAM lParam);
90
91 static int is_full_screen(void);
92 static void make_full_screen(void);
93 static void clear_full_screen(void);
94 static void flip_full_screen(void);
95
96 /* Window layout information */
97 static void reset_window(int);
98 static int extra_width, extra_height;
99 static int font_width, font_height, font_dualwidth;
100 static int offset_width, offset_height;
101 static int was_zoomed = 0;
102 static int prev_rows, prev_cols;
103
104 static int pending_netevent = 0;
105 static WPARAM pend_netevent_wParam = 0;
106 static LPARAM pend_netevent_lParam = 0;
107 static void enact_pending_netevent(void);
108 static void flash_window(int mode);
109 static void sys_cursor_update(void);
110 static int get_fullscreen_rect(RECT * ss);
111
112 static time_t last_movement = 0;
113
114 static int caret_x = -1, caret_y = -1;
115
116 #define FONT_NORMAL 0
117 #define FONT_BOLD 1
118 #define FONT_UNDERLINE 2
119 #define FONT_BOLDUND 3
120 #define FONT_WIDE 0x04
121 #define FONT_HIGH 0x08
122 #define FONT_NARROW 0x10
123
124 #define FONT_OEM 0x20
125 #define FONT_OEMBOLD 0x21
126 #define FONT_OEMUND 0x22
127 #define FONT_OEMBOLDUND 0x23
128
129 #define FONT_MAXNO 0x2F
130 #define FONT_SHIFT 5
131 static HFONT fonts[FONT_MAXNO];
132 static LOGFONT lfont;
133 static int fontflag[FONT_MAXNO];
134 static enum {
135 BOLD_COLOURS, BOLD_SHADOW, BOLD_FONT
136 } bold_mode;
137 static enum {
138 UND_LINE, UND_FONT
139 } und_mode;
140 static int descent;
141
142 #define NCOLOURS 24
143 static COLORREF colours[NCOLOURS];
144 static HPALETTE pal;
145 static LPLOGPALETTE logpal;
146 static RGBTRIPLE defpal[NCOLOURS];
147
148 static HWND hwnd;
149
150 static HBITMAP caretbm;
151
152 static int dbltime, lasttime, lastact;
153 static Mouse_Button lastbtn;
154
155 /* this allows xterm-style mouse handling. */
156 static int send_raw_mouse = 0;
157 static int wheel_accumulator = 0;
158
159 static char *window_name, *icon_name;
160
161 static int compose_state = 0;
162
163 static int wsa_started = 0;
164
165 static OSVERSIONINFO osVersion;
166
167 static UINT wm_mousewheel = WM_MOUSEWHEEL;
168
169 /* Dummy routine, only required in plink. */
170 void ldisc_update(int echo, int edit)
171 {
172 }
173
174 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
175 {
176 static char appname[] = "PuTTY";
177 WORD winsock_ver;
178 WSADATA wsadata;
179 WNDCLASS wndclass;
180 MSG msg;
181 int guess_width, guess_height;
182
183 hinst = inst;
184 flags = FLAG_VERBOSE | FLAG_INTERACTIVE;
185
186 winsock_ver = MAKEWORD(1, 1);
187 if (WSAStartup(winsock_ver, &wsadata)) {
188 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
189 MB_OK | MB_ICONEXCLAMATION);
190 return 1;
191 }
192 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
193 MessageBox(NULL, "WinSock version is incompatible with 1.1",
194 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
195 WSACleanup();
196 return 1;
197 }
198 wsa_started = 1;
199 /* WISHLIST: maybe allow config tweaking even if winsock not present? */
200 sk_init();
201
202 InitCommonControls();
203
204 /* Ensure a Maximize setting in Explorer doesn't maximise the
205 * config box. */
206 defuse_showwindow();
207
208 {
209 ZeroMemory(&osVersion, sizeof(osVersion));
210 osVersion.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
211 if (!GetVersionEx ( (OSVERSIONINFO *) &osVersion)) {
212 MessageBox(NULL, "Windows refuses to report a version",
213 "PuTTY Fatal Error", MB_OK | MB_ICONEXCLAMATION);
214 return 1;
215 }
216 }
217
218 /*
219 * If we're running a version of Windows that doesn't support
220 * WM_MOUSEWHEEL, find out what message number we should be
221 * using instead.
222 */
223 if (osVersion.dwMajorVersion < 4 ||
224 (osVersion.dwMajorVersion == 4 &&
225 osVersion.dwPlatformId != VER_PLATFORM_WIN32_NT))
226 wm_mousewheel = RegisterWindowMessage("MSWHEEL_ROLLMSG");
227
228 /*
229 * See if we can find our Help file.
230 */
231 {
232 char b[2048], *p, *q, *r;
233 FILE *fp;
234 GetModuleFileName(NULL, b, sizeof(b) - 1);
235 r = b;
236 p = strrchr(b, '\\');
237 if (p && p >= r) r = p+1;
238 q = strrchr(b, ':');
239 if (q && q >= r) r = q+1;
240 strcpy(r, "putty.hlp");
241 if ( (fp = fopen(b, "r")) != NULL) {
242 help_path = dupstr(b);
243 fclose(fp);
244 } else
245 help_path = NULL;
246 strcpy(r, "putty.cnt");
247 if ( (fp = fopen(b, "r")) != NULL) {
248 help_has_contents = TRUE;
249 fclose(fp);
250 } else
251 help_has_contents = FALSE;
252 }
253
254 /*
255 * Process the command line.
256 */
257 {
258 char *p;
259 int got_host = 0;
260
261 default_protocol = DEFAULT_PROTOCOL;
262 default_port = DEFAULT_PORT;
263 cfg.logtype = LGTYP_NONE;
264
265 do_defaults(NULL, &cfg);
266
267 p = cmdline;
268
269 /*
270 * Process a couple of command-line options which are more
271 * easily dealt with before the line is broken up into
272 * words. These are the soon-to-be-defunct @sessionname and
273 * the internal-use-only &sharedmemoryhandle, neither of
274 * which are combined with anything else.
275 */
276 while (*p && isspace(*p))
277 p++;
278 if (*p == '@') {
279 int i = strlen(p);
280 while (i > 1 && isspace(p[i - 1]))
281 i--;
282 p[i] = '\0';
283 do_defaults(p + 1, &cfg);
284 if (!*cfg.host && !do_config()) {
285 WSACleanup();
286 return 0;
287 }
288 } else if (*p == '&') {
289 /*
290 * An initial & means we've been given a command line
291 * containing the hex value of a HANDLE for a file
292 * mapping object, which we must then extract as a
293 * config.
294 */
295 HANDLE filemap;
296 Config *cp;
297 if (sscanf(p + 1, "%p", &filemap) == 1 &&
298 (cp = MapViewOfFile(filemap, FILE_MAP_READ,
299 0, 0, sizeof(Config))) != NULL) {
300 cfg = *cp;
301 UnmapViewOfFile(cp);
302 CloseHandle(filemap);
303 } else if (!do_config()) {
304 WSACleanup();
305 return 0;
306 }
307 } else {
308 /*
309 * Otherwise, break up the command line and deal with
310 * it sensibly.
311 */
312 int argc, i;
313 char **argv;
314
315 split_into_argv(cmdline, &argc, &argv, NULL);
316
317 for (i = 0; i < argc; i++) {
318 char *p = argv[i];
319 int ret;
320
321 ret = cmdline_process_param(p, i+1<argc?argv[i+1]:NULL, 1);
322 if (ret == -2) {
323 cmdline_error("option \"%s\" requires an argument", p);
324 } else if (ret == 2) {
325 i++; /* skip next argument */
326 } else if (ret == 1) {
327 continue; /* nothing further needs doing */
328 } else if (!strcmp(p, "-cleanup")) {
329 /*
330 * `putty -cleanup'. Remove all registry
331 * entries associated with PuTTY, and also find
332 * and delete the random seed file.
333 */
334 if (MessageBox(NULL,
335 "This procedure will remove ALL Registry\n"
336 "entries associated with PuTTY, and will\n"
337 "also remove the PuTTY random seed file.\n"
338 "\n"
339 "THIS PROCESS WILL DESTROY YOUR SAVED\n"
340 "SESSIONS. Are you really sure you want\n"
341 "to continue?",
342 "PuTTY Warning",
343 MB_YESNO | MB_ICONWARNING) == IDYES) {
344 cleanup_all();
345 }
346 exit(0);
347 } else if (*p != '-') {
348 char *q = p;
349 if (got_host) {
350 /*
351 * If we already have a host name, treat
352 * this argument as a port number. NB we
353 * have to treat this as a saved -P
354 * argument, so that it will be deferred
355 * until it's a good moment to run it.
356 */
357 int ret = cmdline_process_param("-P", p, 1);
358 assert(ret == 2);
359 } else if (!strncmp(q, "telnet:", 7)) {
360 /*
361 * If the hostname starts with "telnet:",
362 * set the protocol to Telnet and process
363 * the string as a Telnet URL.
364 */
365 char c;
366
367 q += 7;
368 if (q[0] == '/' && q[1] == '/')
369 q += 2;
370 cfg.protocol = PROT_TELNET;
371 p = q;
372 while (*p && *p != ':' && *p != '/')
373 p++;
374 c = *p;
375 if (*p)
376 *p++ = '\0';
377 if (c == ':')
378 cfg.port = atoi(p);
379 else
380 cfg.port = -1;
381 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
382 cfg.host[sizeof(cfg.host) - 1] = '\0';
383 got_host = 1;
384 } else {
385 /*
386 * Otherwise, treat this argument as a host
387 * name.
388 */
389 while (*p && !isspace(*p))
390 p++;
391 if (*p)
392 *p++ = '\0';
393 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
394 cfg.host[sizeof(cfg.host) - 1] = '\0';
395 got_host = 1;
396 }
397 }
398 }
399 }
400
401 cmdline_run_saved();
402
403 if (!*cfg.host && !do_config()) {
404 WSACleanup();
405 return 0;
406 }
407
408 /*
409 * Trim leading whitespace off the hostname if it's there.
410 */
411 {
412 int space = strspn(cfg.host, " \t");
413 memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
414 }
415
416 /* See if host is of the form user@host */
417 if (cfg.host[0] != '\0') {
418 char *atsign = strchr(cfg.host, '@');
419 /* Make sure we're not overflowing the user field */
420 if (atsign) {
421 if (atsign - cfg.host < sizeof cfg.username) {
422 strncpy(cfg.username, cfg.host, atsign - cfg.host);
423 cfg.username[atsign - cfg.host] = '\0';
424 }
425 memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
426 }
427 }
428
429 /*
430 * Trim a colon suffix off the hostname if it's there.
431 */
432 cfg.host[strcspn(cfg.host, ":")] = '\0';
433
434 /*
435 * Remove any remaining whitespace from the hostname.
436 */
437 {
438 int p1 = 0, p2 = 0;
439 while (cfg.host[p2] != '\0') {
440 if (cfg.host[p2] != ' ' && cfg.host[p2] != '\t') {
441 cfg.host[p1] = cfg.host[p2];
442 p1++;
443 }
444 p2++;
445 }
446 cfg.host[p1] = '\0';
447 }
448 }
449
450 /*
451 * Select protocol. This is farmed out into a table in a
452 * separate file to enable an ssh-free variant.
453 */
454 {
455 int i;
456 back = NULL;
457 for (i = 0; backends[i].backend != NULL; i++)
458 if (backends[i].protocol == cfg.protocol) {
459 back = backends[i].backend;
460 break;
461 }
462 if (back == NULL) {
463 MessageBox(NULL, "Unsupported protocol number found",
464 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
465 WSACleanup();
466 return 1;
467 }
468 }
469
470 /* Check for invalid Port number (i.e. zero) */
471 if (cfg.port == 0) {
472 MessageBox(NULL, "Invalid Port Number",
473 "PuTTY Internal Error", MB_OK | MB_ICONEXCLAMATION);
474 WSACleanup();
475 return 1;
476 }
477
478 if (!prev) {
479 wndclass.style = 0;
480 wndclass.lpfnWndProc = WndProc;
481 wndclass.cbClsExtra = 0;
482 wndclass.cbWndExtra = 0;
483 wndclass.hInstance = inst;
484 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
485 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
486 wndclass.hbrBackground = NULL;
487 wndclass.lpszMenuName = NULL;
488 wndclass.lpszClassName = appname;
489
490 RegisterClass(&wndclass);
491 }
492
493 hwnd = NULL;
494
495 savelines = cfg.savelines;
496 term_init();
497
498 cfgtopalette();
499
500 /*
501 * Guess some defaults for the window size. This all gets
502 * updated later, so we don't really care too much. However, we
503 * do want the font width/height guesses to correspond to a
504 * large font rather than a small one...
505 */
506
507 font_width = 10;
508 font_height = 20;
509 extra_width = 25;
510 extra_height = 28;
511 term_size(cfg.height, cfg.width, cfg.savelines);
512 guess_width = extra_width + font_width * cols;
513 guess_height = extra_height + font_height * rows;
514 {
515 RECT r;
516 get_fullscreen_rect(&r);
517 if (guess_width > r.right - r.left)
518 guess_width = r.right - r.left;
519 if (guess_height > r.bottom - r.top)
520 guess_height = r.bottom - r.top;
521 }
522
523 {
524 int winmode = WS_OVERLAPPEDWINDOW | WS_VSCROLL;
525 int exwinmode = 0;
526 if (!cfg.scrollbar)
527 winmode &= ~(WS_VSCROLL);
528 if (cfg.resize_action == RESIZE_DISABLED)
529 winmode &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
530 if (cfg.alwaysontop)
531 exwinmode |= WS_EX_TOPMOST;
532 if (cfg.sunken_edge)
533 exwinmode |= WS_EX_CLIENTEDGE;
534 hwnd = CreateWindowEx(exwinmode, appname, appname,
535 winmode, CW_USEDEFAULT, CW_USEDEFAULT,
536 guess_width, guess_height,
537 NULL, NULL, inst, NULL);
538 }
539
540 /*
541 * Initialise the fonts, simultaneously correcting the guesses
542 * for font_{width,height}.
543 */
544 init_fonts(0,0);
545
546 /*
547 * Correct the guesses for extra_{width,height}.
548 */
549 {
550 RECT cr, wr;
551 GetWindowRect(hwnd, &wr);
552 GetClientRect(hwnd, &cr);
553 offset_width = offset_height = cfg.window_border;
554 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
555 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
556 }
557
558 /*
559 * Resize the window, now we know what size we _really_ want it
560 * to be.
561 */
562 guess_width = extra_width + font_width * cols;
563 guess_height = extra_height + font_height * rows;
564 SetWindowPos(hwnd, NULL, 0, 0, guess_width, guess_height,
565 SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
566
567 /*
568 * Set up a caret bitmap, with no content.
569 */
570 {
571 char *bits;
572 int size = (font_width + 15) / 16 * 2 * font_height;
573 bits = smalloc(size);
574 memset(bits, 0, size);
575 caretbm = CreateBitmap(font_width, font_height, 1, 1, bits);
576 sfree(bits);
577 }
578 CreateCaret(hwnd, caretbm, font_width, font_height);
579
580 /*
581 * Initialise the scroll bar.
582 */
583 {
584 SCROLLINFO si;
585
586 si.cbSize = sizeof(si);
587 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
588 si.nMin = 0;
589 si.nMax = rows - 1;
590 si.nPage = rows;
591 si.nPos = 0;
592 SetScrollInfo(hwnd, SB_VERT, &si, FALSE);
593 }
594
595 /*
596 * Start up the telnet connection.
597 */
598 {
599 char *error;
600 char msg[1024], *title;
601 char *realhost;
602
603 error = back->init(cfg.host, cfg.port, &realhost, cfg.tcp_nodelay);
604 if (error) {
605 sprintf(msg, "Unable to open connection to\n"
606 "%.800s\n" "%s", cfg.host, error);
607 MessageBox(NULL, msg, "PuTTY Error", MB_ICONERROR | MB_OK);
608 return 0;
609 }
610 window_name = icon_name = NULL;
611 if (*cfg.wintitle) {
612 title = cfg.wintitle;
613 } else {
614 sprintf(msg, "%s - PuTTY", realhost);
615 title = msg;
616 }
617 sfree(realhost);
618 set_title(title);
619 set_icon(title);
620 }
621
622 session_closed = FALSE;
623
624 /*
625 * Prepare the mouse handler.
626 */
627 lastact = MA_NOTHING;
628 lastbtn = MBT_NOTHING;
629 dbltime = GetDoubleClickTime();
630
631 /*
632 * Set up the session-control options on the system menu.
633 */
634 {
635 HMENU m = GetSystemMenu(hwnd, FALSE);
636 HMENU p, s;
637 int i;
638
639 AppendMenu(m, MF_SEPARATOR, 0, 0);
640 if (cfg.protocol == PROT_TELNET) {
641 p = CreateMenu();
642 AppendMenu(p, MF_ENABLED, IDM_TEL_AYT, "Are You There");
643 AppendMenu(p, MF_ENABLED, IDM_TEL_BRK, "Break");
644 AppendMenu(p, MF_ENABLED, IDM_TEL_SYNCH, "Synch");
645 AppendMenu(p, MF_SEPARATOR, 0, 0);
646 AppendMenu(p, MF_ENABLED, IDM_TEL_EC, "Erase Character");
647 AppendMenu(p, MF_ENABLED, IDM_TEL_EL, "Erase Line");
648 AppendMenu(p, MF_ENABLED, IDM_TEL_GA, "Go Ahead");
649 AppendMenu(p, MF_ENABLED, IDM_TEL_NOP, "No Operation");
650 AppendMenu(p, MF_SEPARATOR, 0, 0);
651 AppendMenu(p, MF_ENABLED, IDM_TEL_ABORT, "Abort Process");
652 AppendMenu(p, MF_ENABLED, IDM_TEL_AO, "Abort Output");
653 AppendMenu(p, MF_ENABLED, IDM_TEL_IP, "Interrupt Process");
654 AppendMenu(p, MF_ENABLED, IDM_TEL_SUSP, "Suspend Process");
655 AppendMenu(p, MF_SEPARATOR, 0, 0);
656 AppendMenu(p, MF_ENABLED, IDM_TEL_EOR, "End Of Record");
657 AppendMenu(p, MF_ENABLED, IDM_TEL_EOF, "End Of File");
658 AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) p,
659 "Telnet Command");
660 AppendMenu(m, MF_SEPARATOR, 0, 0);
661 }
662 AppendMenu(m, MF_ENABLED, IDM_SHOWLOG, "&Event Log");
663 AppendMenu(m, MF_SEPARATOR, 0, 0);
664 AppendMenu(m, MF_ENABLED, IDM_NEWSESS, "Ne&w Session...");
665 AppendMenu(m, MF_ENABLED, IDM_DUPSESS, "&Duplicate Session");
666 s = CreateMenu();
667 get_sesslist(TRUE);
668 for (i = 1; i < ((nsessions < 256) ? nsessions : 256); i++)
669 AppendMenu(s, MF_ENABLED, IDM_SAVED_MIN + (16 * i),
670 sessions[i]);
671 AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) s, "Sa&ved Sessions");
672 AppendMenu(m, MF_ENABLED, IDM_RECONF, "Chan&ge Settings...");
673 AppendMenu(m, MF_SEPARATOR, 0, 0);
674 AppendMenu(m, MF_ENABLED, IDM_COPYALL, "C&opy All to Clipboard");
675 AppendMenu(m, MF_ENABLED, IDM_CLRSB, "C&lear Scrollback");
676 AppendMenu(m, MF_ENABLED, IDM_RESET, "Rese&t Terminal");
677 AppendMenu(m, MF_SEPARATOR, 0, 0);
678 AppendMenu(m, (cfg.resize_action == RESIZE_DISABLED) ?
679 MF_GRAYED : MF_ENABLED, IDM_FULLSCREEN, "&Full Screen");
680 AppendMenu(m, MF_SEPARATOR, 0, 0);
681 if (help_path)
682 AppendMenu(m, MF_ENABLED, IDM_HELP, "&Help");
683 AppendMenu(m, MF_ENABLED, IDM_ABOUT, "&About PuTTY");
684 }
685
686 /*
687 * Set up the initial input locale.
688 */
689 set_input_locale(GetKeyboardLayout(0));
690
691 /*
692 * Open the initial log file if there is one.
693 */
694 logfopen();
695
696 /*
697 * Finally show the window!
698 */
699 ShowWindow(hwnd, show);
700 SetForegroundWindow(hwnd);
701
702 /*
703 * Set the palette up.
704 */
705 pal = NULL;
706 logpal = NULL;
707 init_palette();
708
709 has_focus = (GetForegroundWindow() == hwnd);
710 UpdateWindow(hwnd);
711
712 if (GetMessage(&msg, NULL, 0, 0) == 1) {
713 int timer_id = 0, long_timer = 0;
714
715 while (msg.message != WM_QUIT) {
716 /* Sometimes DispatchMessage calls routines that use their own
717 * GetMessage loop, setup this timer so we get some control back.
718 *
719 * Also call term_update() from the timer so that if the host
720 * is sending data flat out we still do redraws.
721 */
722 if (timer_id && long_timer) {
723 KillTimer(hwnd, timer_id);
724 long_timer = timer_id = 0;
725 }
726 if (!timer_id)
727 timer_id = SetTimer(hwnd, 1, 20, NULL);
728 if (!(IsWindow(logbox) && IsDialogMessage(logbox, &msg)))
729 DispatchMessage(&msg);
730
731 /* Make sure we blink everything that needs it. */
732 term_blink(0);
733
734 /* Send the paste buffer if there's anything to send */
735 term_paste();
736
737 /* If there's nothing new in the queue then we can do everything
738 * we've delayed, reading the socket, writing, and repainting
739 * the window.
740 */
741 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
742 continue;
743
744 if (pending_netevent) {
745 enact_pending_netevent();
746
747 /* Force the cursor blink on */
748 term_blink(1);
749
750 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
751 continue;
752 }
753
754 /* Okay there is now nothing to do so we make sure the screen is
755 * completely up to date then tell windows to call us in a little
756 * while.
757 */
758 if (timer_id) {
759 KillTimer(hwnd, timer_id);
760 timer_id = 0;
761 }
762 HideCaret(hwnd);
763 if (GetCapture() != hwnd ||
764 (send_raw_mouse && !(cfg.mouse_override && is_shift_pressed())))
765 term_out();
766 term_update();
767 ShowCaret(hwnd);
768
769 flash_window(1); /* maintain */
770
771 /* The messages seem unreliable; especially if we're being tricky */
772 has_focus = (GetForegroundWindow() == hwnd);
773
774 if (in_vbell)
775 /* Hmm, term_update didn't want to do an update too soon ... */
776 timer_id = SetTimer(hwnd, 1, 50, NULL);
777 else if (!has_focus)
778 timer_id = SetTimer(hwnd, 1, 500, NULL);
779 else
780 timer_id = SetTimer(hwnd, 1, 100, NULL);
781 long_timer = 1;
782
783 /* There's no point rescanning everything in the message queue
784 * so we do an apparently unnecessary wait here
785 */
786 WaitMessage();
787 if (GetMessage(&msg, NULL, 0, 0) != 1)
788 break;
789 }
790 }
791
792 cleanup_exit(msg.wParam); /* this doesn't return... */
793 return msg.wParam; /* ... but optimiser doesn't know */
794 }
795
796 /*
797 * Clean up and exit.
798 */
799 void cleanup_exit(int code)
800 {
801 /*
802 * Clean up.
803 */
804 deinit_fonts();
805 sfree(logpal);
806 if (pal)
807 DeleteObject(pal);
808 sk_cleanup();
809 if (wsa_started)
810 WSACleanup();
811
812 if (cfg.protocol == PROT_SSH) {
813 random_save_seed();
814 #ifdef MSCRYPTOAPI
815 crypto_wrapup();
816 #endif
817 }
818
819 exit(code);
820 }
821
822 /*
823 * Set up, or shut down, an AsyncSelect. Called from winnet.c.
824 */
825 char *do_select(SOCKET skt, int startup)
826 {
827 int msg, events;
828 if (startup) {
829 msg = WM_NETEVENT;
830 events = (FD_CONNECT | FD_READ | FD_WRITE |
831 FD_OOB | FD_CLOSE | FD_ACCEPT);
832 } else {
833 msg = events = 0;
834 }
835 if (!hwnd)
836 return "do_select(): internal error (hwnd==NULL)";
837 if (WSAAsyncSelect(skt, hwnd, msg, events) == SOCKET_ERROR) {
838 switch (WSAGetLastError()) {
839 case WSAENETDOWN:
840 return "Network is down";
841 default:
842 return "WSAAsyncSelect(): unknown error";
843 }
844 }
845 return NULL;
846 }
847
848 /*
849 * set or clear the "raw mouse message" mode
850 */
851 void set_raw_mouse_mode(int activate)
852 {
853 activate = activate && !cfg.no_mouse_rep;
854 send_raw_mouse = activate;
855 SetCursor(LoadCursor(NULL, activate ? IDC_ARROW : IDC_IBEAM));
856 }
857
858 /*
859 * Print a message box and close the connection.
860 */
861 void connection_fatal(char *fmt, ...)
862 {
863 va_list ap;
864 char stuff[200];
865
866 va_start(ap, fmt);
867 vsprintf(stuff, fmt, ap);
868 va_end(ap);
869 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
870 if (cfg.close_on_exit == COE_ALWAYS)
871 PostQuitMessage(1);
872 else {
873 session_closed = TRUE;
874 SetWindowText(hwnd, "PuTTY (inactive)");
875 }
876 }
877
878 /*
879 * Report an error at the command-line parsing stage.
880 */
881 void cmdline_error(char *fmt, ...)
882 {
883 va_list ap;
884 char stuff[200];
885
886 va_start(ap, fmt);
887 vsprintf(stuff, fmt, ap);
888 va_end(ap);
889 MessageBox(hwnd, stuff, "PuTTY Command Line Error", MB_ICONERROR | MB_OK);
890 exit(1);
891 }
892
893 /*
894 * Actually do the job requested by a WM_NETEVENT
895 */
896 static void enact_pending_netevent(void)
897 {
898 static int reentering = 0;
899 extern int select_result(WPARAM, LPARAM);
900 int ret;
901
902 if (reentering)
903 return; /* don't unpend the pending */
904
905 pending_netevent = FALSE;
906
907 reentering = 1;
908 ret = select_result(pend_netevent_wParam, pend_netevent_lParam);
909 reentering = 0;
910
911 if (ret == 0 && !session_closed) {
912 /* Abnormal exits will already have set session_closed and taken
913 * appropriate action. */
914 if (cfg.close_on_exit == COE_ALWAYS ||
915 cfg.close_on_exit == COE_NORMAL) PostQuitMessage(0);
916 else {
917 session_closed = TRUE;
918 SetWindowText(hwnd, "PuTTY (inactive)");
919 MessageBox(hwnd, "Connection closed by remote host",
920 "PuTTY", MB_OK | MB_ICONINFORMATION);
921 }
922 }
923 }
924
925 /*
926 * Copy the colour palette from the configuration data into defpal.
927 * This is non-trivial because the colour indices are different.
928 */
929 static void cfgtopalette(void)
930 {
931 int i;
932 static const int ww[] = {
933 6, 7, 8, 9, 10, 11, 12, 13,
934 14, 15, 16, 17, 18, 19, 20, 21,
935 0, 1, 2, 3, 4, 4, 5, 5
936 };
937
938 for (i = 0; i < 24; i++) {
939 int w = ww[i];
940 defpal[i].rgbtRed = cfg.colours[w][0];
941 defpal[i].rgbtGreen = cfg.colours[w][1];
942 defpal[i].rgbtBlue = cfg.colours[w][2];
943 }
944 }
945
946 /*
947 * Set up the colour palette.
948 */
949 static void init_palette(void)
950 {
951 int i;
952 HDC hdc = GetDC(hwnd);
953 if (hdc) {
954 if (cfg.try_palette && GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) {
955 logpal = smalloc(sizeof(*logpal)
956 - sizeof(logpal->palPalEntry)
957 + NCOLOURS * sizeof(PALETTEENTRY));
958 logpal->palVersion = 0x300;
959 logpal->palNumEntries = NCOLOURS;
960 for (i = 0; i < NCOLOURS; i++) {
961 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
962 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
963 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
964 logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
965 }
966 pal = CreatePalette(logpal);
967 if (pal) {
968 SelectPalette(hdc, pal, FALSE);
969 RealizePalette(hdc);
970 SelectPalette(hdc, GetStockObject(DEFAULT_PALETTE), FALSE);
971 }
972 }
973 ReleaseDC(hwnd, hdc);
974 }
975 if (pal)
976 for (i = 0; i < NCOLOURS; i++)
977 colours[i] = PALETTERGB(defpal[i].rgbtRed,
978 defpal[i].rgbtGreen,
979 defpal[i].rgbtBlue);
980 else
981 for (i = 0; i < NCOLOURS; i++)
982 colours[i] = RGB(defpal[i].rgbtRed,
983 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
984 }
985
986 /*
987 * Initialise all the fonts we will need initially. There may be as many as
988 * three or as few as one. The other (poentially) twentyone fonts are done
989 * if/when they are needed.
990 *
991 * We also:
992 *
993 * - check the font width and height, correcting our guesses if
994 * necessary.
995 *
996 * - verify that the bold font is the same width as the ordinary
997 * one, and engage shadow bolding if not.
998 *
999 * - verify that the underlined font is the same width as the
1000 * ordinary one (manual underlining by means of line drawing can
1001 * be done in a pinch).
1002 */
1003 static void init_fonts(int pick_width, int pick_height)
1004 {
1005 TEXTMETRIC tm;
1006 CPINFO cpinfo;
1007 int fontsize[3];
1008 int i;
1009 HDC hdc;
1010 int fw_dontcare, fw_bold;
1011
1012 for (i = 0; i < FONT_MAXNO; i++)
1013 fonts[i] = NULL;
1014
1015 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
1016 und_mode = UND_FONT;
1017
1018 if (cfg.fontisbold) {
1019 fw_dontcare = FW_BOLD;
1020 fw_bold = FW_HEAVY;
1021 } else {
1022 fw_dontcare = FW_DONTCARE;
1023 fw_bold = FW_BOLD;
1024 }
1025
1026 hdc = GetDC(hwnd);
1027
1028 if (pick_height)
1029 font_height = pick_height;
1030 else {
1031 font_height = cfg.fontheight;
1032 if (font_height > 0) {
1033 font_height =
1034 -MulDiv(font_height, GetDeviceCaps(hdc, LOGPIXELSY), 72);
1035 }
1036 }
1037 font_width = pick_width;
1038
1039 #define f(i,c,w,u) \
1040 fonts[i] = CreateFont (font_height, font_width, 0, 0, w, FALSE, u, FALSE, \
1041 c, OUT_DEFAULT_PRECIS, \
1042 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, \
1043 FIXED_PITCH | FF_DONTCARE, cfg.font)
1044
1045 f(FONT_NORMAL, cfg.fontcharset, fw_dontcare, FALSE);
1046
1047 lfont.lfHeight = font_height;
1048 lfont.lfWidth = font_width;
1049 lfont.lfEscapement = 0;
1050 lfont.lfOrientation = 0;
1051 lfont.lfWeight = fw_dontcare;
1052 lfont.lfItalic = FALSE;
1053 lfont.lfUnderline = FALSE;
1054 lfont.lfStrikeOut = FALSE;
1055 lfont.lfCharSet = cfg.fontcharset;
1056 lfont.lfOutPrecision = OUT_DEFAULT_PRECIS;
1057 lfont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
1058 lfont.lfQuality = DEFAULT_QUALITY;
1059 lfont.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE;
1060 strncpy(lfont.lfFaceName, cfg.font, LF_FACESIZE);
1061
1062 SelectObject(hdc, fonts[FONT_NORMAL]);
1063 GetTextMetrics(hdc, &tm);
1064
1065 if (pick_width == 0 || pick_height == 0) {
1066 font_height = tm.tmHeight;
1067 font_width = tm.tmAveCharWidth;
1068 }
1069 font_dualwidth = (tm.tmAveCharWidth != tm.tmMaxCharWidth);
1070
1071 #ifdef RDB_DEBUG_PATCH
1072 debug(23, "Primary font H=%d, AW=%d, MW=%d",
1073 tm.tmHeight, tm.tmAveCharWidth, tm.tmMaxCharWidth);
1074 #endif
1075
1076 {
1077 CHARSETINFO info;
1078 DWORD cset = tm.tmCharSet;
1079 memset(&info, 0xFF, sizeof(info));
1080
1081 /* !!! Yes the next line is right */
1082 if (cset == OEM_CHARSET)
1083 font_codepage = GetOEMCP();
1084 else
1085 if (TranslateCharsetInfo
1086 ((DWORD *) cset, &info, TCI_SRCCHARSET)) font_codepage =
1087 info.ciACP;
1088 else
1089 font_codepage = -1;
1090
1091 GetCPInfo(font_codepage, &cpinfo);
1092 dbcs_screenfont = (cpinfo.MaxCharSize > 1);
1093 }
1094
1095 f(FONT_UNDERLINE, cfg.fontcharset, fw_dontcare, TRUE);
1096
1097 /*
1098 * Some fonts, e.g. 9-pt Courier, draw their underlines
1099 * outside their character cell. We successfully prevent
1100 * screen corruption by clipping the text output, but then
1101 * we lose the underline completely. Here we try to work
1102 * out whether this is such a font, and if it is, we set a
1103 * flag that causes underlines to be drawn by hand.
1104 *
1105 * Having tried other more sophisticated approaches (such
1106 * as examining the TEXTMETRIC structure or requesting the
1107 * height of a string), I think we'll do this the brute
1108 * force way: we create a small bitmap, draw an underlined
1109 * space on it, and test to see whether any pixels are
1110 * foreground-coloured. (Since we expect the underline to
1111 * go all the way across the character cell, we only search
1112 * down a single column of the bitmap, half way across.)
1113 */
1114 {
1115 HDC und_dc;
1116 HBITMAP und_bm, und_oldbm;
1117 int i, gotit;
1118 COLORREF c;
1119
1120 und_dc = CreateCompatibleDC(hdc);
1121 und_bm = CreateCompatibleBitmap(hdc, font_width, font_height);
1122 und_oldbm = SelectObject(und_dc, und_bm);
1123 SelectObject(und_dc, fonts[FONT_UNDERLINE]);
1124 SetTextAlign(und_dc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
1125 SetTextColor(und_dc, RGB(255, 255, 255));
1126 SetBkColor(und_dc, RGB(0, 0, 0));
1127 SetBkMode(und_dc, OPAQUE);
1128 ExtTextOut(und_dc, 0, 0, ETO_OPAQUE, NULL, " ", 1, NULL);
1129 gotit = FALSE;
1130 for (i = 0; i < font_height; i++) {
1131 c = GetPixel(und_dc, font_width / 2, i);
1132 if (c != RGB(0, 0, 0))
1133 gotit = TRUE;
1134 }
1135 SelectObject(und_dc, und_oldbm);
1136 DeleteObject(und_bm);
1137 DeleteDC(und_dc);
1138 if (!gotit) {
1139 und_mode = UND_LINE;
1140 DeleteObject(fonts[FONT_UNDERLINE]);
1141 fonts[FONT_UNDERLINE] = 0;
1142 }
1143 }
1144
1145 if (bold_mode == BOLD_FONT) {
1146 f(FONT_BOLD, cfg.fontcharset, fw_bold, FALSE);
1147 }
1148 #undef f
1149
1150 descent = tm.tmAscent + 1;
1151 if (descent >= font_height)
1152 descent = font_height - 1;
1153
1154 for (i = 0; i < 3; i++) {
1155 if (fonts[i]) {
1156 if (SelectObject(hdc, fonts[i]) && GetTextMetrics(hdc, &tm))
1157 fontsize[i] = tm.tmAveCharWidth + 256 * tm.tmHeight;
1158 else
1159 fontsize[i] = -i;
1160 } else
1161 fontsize[i] = -i;
1162 }
1163
1164 ReleaseDC(hwnd, hdc);
1165
1166 if (fontsize[FONT_UNDERLINE] != fontsize[FONT_NORMAL]) {
1167 und_mode = UND_LINE;
1168 DeleteObject(fonts[FONT_UNDERLINE]);
1169 fonts[FONT_UNDERLINE] = 0;
1170 }
1171
1172 if (bold_mode == BOLD_FONT &&
1173 fontsize[FONT_BOLD] != fontsize[FONT_NORMAL]) {
1174 bold_mode = BOLD_SHADOW;
1175 DeleteObject(fonts[FONT_BOLD]);
1176 fonts[FONT_BOLD] = 0;
1177 }
1178 fontflag[0] = fontflag[1] = fontflag[2] = 1;
1179
1180 init_ucs_tables();
1181 }
1182
1183 static void another_font(int fontno)
1184 {
1185 int basefont;
1186 int fw_dontcare, fw_bold;
1187 int c, u, w, x;
1188 char *s;
1189
1190 if (fontno < 0 || fontno >= FONT_MAXNO || fontflag[fontno])
1191 return;
1192
1193 basefont = (fontno & ~(FONT_BOLDUND));
1194 if (basefont != fontno && !fontflag[basefont])
1195 another_font(basefont);
1196
1197 if (cfg.fontisbold) {
1198 fw_dontcare = FW_BOLD;
1199 fw_bold = FW_HEAVY;
1200 } else {
1201 fw_dontcare = FW_DONTCARE;
1202 fw_bold = FW_BOLD;
1203 }
1204
1205 c = cfg.fontcharset;
1206 w = fw_dontcare;
1207 u = FALSE;
1208 s = cfg.font;
1209 x = font_width;
1210
1211 if (fontno & FONT_WIDE)
1212 x *= 2;
1213 if (fontno & FONT_NARROW)
1214 x = (x+1)/2;
1215 if (fontno & FONT_OEM)
1216 c = OEM_CHARSET;
1217 if (fontno & FONT_BOLD)
1218 w = fw_bold;
1219 if (fontno & FONT_UNDERLINE)
1220 u = TRUE;
1221
1222 fonts[fontno] =
1223 CreateFont(font_height * (1 + !!(fontno & FONT_HIGH)), x, 0, 0, w,
1224 FALSE, u, FALSE, c, OUT_DEFAULT_PRECIS,
1225 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
1226 FIXED_PITCH | FF_DONTCARE, s);
1227
1228 fontflag[fontno] = 1;
1229 }
1230
1231 static void deinit_fonts(void)
1232 {
1233 int i;
1234 for (i = 0; i < FONT_MAXNO; i++) {
1235 if (fonts[i])
1236 DeleteObject(fonts[i]);
1237 fonts[i] = 0;
1238 fontflag[i] = 0;
1239 }
1240 }
1241
1242 void request_resize(int w, int h)
1243 {
1244 int width, height;
1245
1246 /* If the window is maximized supress resizing attempts */
1247 if (IsZoomed(hwnd)) {
1248 if (cfg.resize_action == RESIZE_TERM)
1249 return;
1250 }
1251
1252 if (cfg.resize_action == RESIZE_DISABLED) return;
1253 if (h == rows && w == cols) return;
1254
1255 /* Sanity checks ... */
1256 {
1257 static int first_time = 1;
1258 static RECT ss;
1259
1260 switch (first_time) {
1261 case 1:
1262 /* Get the size of the screen */
1263 if (get_fullscreen_rect(&ss))
1264 /* first_time = 0 */ ;
1265 else {
1266 first_time = 2;
1267 break;
1268 }
1269 case 0:
1270 /* Make sure the values are sane */
1271 width = (ss.right - ss.left - extra_width) / 4;
1272 height = (ss.bottom - ss.top - extra_height) / 6;
1273
1274 if (w > width || h > height)
1275 return;
1276 if (w < 15)
1277 w = 15;
1278 if (h < 1)
1279 h = 1;
1280 }
1281 }
1282
1283 term_size(h, w, cfg.savelines);
1284
1285 if (cfg.resize_action != RESIZE_FONT && !IsZoomed(hwnd)) {
1286 width = extra_width + font_width * w;
1287 height = extra_height + font_height * h;
1288
1289 SetWindowPos(hwnd, NULL, 0, 0, width, height,
1290 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1291 SWP_NOMOVE | SWP_NOZORDER);
1292 } else
1293 reset_window(0);
1294
1295 InvalidateRect(hwnd, NULL, TRUE);
1296 }
1297
1298 static void reset_window(int reinit) {
1299 /*
1300 * This function decides how to resize or redraw when the
1301 * user changes something.
1302 *
1303 * This function doesn't like to change the terminal size but if the
1304 * font size is locked that may be it's only soluion.
1305 */
1306 int win_width, win_height;
1307 RECT cr, wr;
1308
1309 #ifdef RDB_DEBUG_PATCH
1310 debug((27, "reset_window()"));
1311 #endif
1312
1313 /* Current window sizes ... */
1314 GetWindowRect(hwnd, &wr);
1315 GetClientRect(hwnd, &cr);
1316
1317 win_width = cr.right - cr.left;
1318 win_height = cr.bottom - cr.top;
1319
1320 if (cfg.resize_action == RESIZE_DISABLED) reinit = 2;
1321
1322 /* Are we being forced to reload the fonts ? */
1323 if (reinit>1) {
1324 #ifdef RDB_DEBUG_PATCH
1325 debug((27, "reset_window() -- Forced deinit"));
1326 #endif
1327 deinit_fonts();
1328 init_fonts(0,0);
1329 }
1330
1331 /* Oh, looks like we're minimised */
1332 if (win_width == 0 || win_height == 0)
1333 return;
1334
1335 /* Is the window out of position ? */
1336 if ( !reinit &&
1337 (offset_width != (win_width-font_width*cols)/2 ||
1338 offset_height != (win_height-font_height*rows)/2) ){
1339 offset_width = (win_width-font_width*cols)/2;
1340 offset_height = (win_height-font_height*rows)/2;
1341 InvalidateRect(hwnd, NULL, TRUE);
1342 #ifdef RDB_DEBUG_PATCH
1343 debug((27, "reset_window() -> Reposition terminal"));
1344 #endif
1345 }
1346
1347 if (IsZoomed(hwnd)) {
1348 /* We're fullscreen, this means we must not change the size of
1349 * the window so it's the font size or the terminal itself.
1350 */
1351
1352 extra_width = wr.right - wr.left - cr.right + cr.left;
1353 extra_height = wr.bottom - wr.top - cr.bottom + cr.top;
1354
1355 if (cfg.resize_action != RESIZE_TERM) {
1356 if ( font_width != win_width/cols ||
1357 font_height != win_height/rows) {
1358 deinit_fonts();
1359 init_fonts(win_width/cols, win_height/rows);
1360 offset_width = (win_width-font_width*cols)/2;
1361 offset_height = (win_height-font_height*rows)/2;
1362 InvalidateRect(hwnd, NULL, TRUE);
1363 #ifdef RDB_DEBUG_PATCH
1364 debug((25, "reset_window() -> Z font resize to (%d, %d)",
1365 font_width, font_height));
1366 #endif
1367 }
1368 } else {
1369 if ( font_width != win_width/cols ||
1370 font_height != win_height/rows) {
1371 /* Our only choice at this point is to change the
1372 * size of the terminal; Oh well.
1373 */
1374 term_size( win_height/font_height, win_width/font_width,
1375 cfg.savelines);
1376 offset_width = (win_width-font_width*cols)/2;
1377 offset_height = (win_height-font_height*rows)/2;
1378 InvalidateRect(hwnd, NULL, TRUE);
1379 #ifdef RDB_DEBUG_PATCH
1380 debug((27, "reset_window() -> Zoomed term_size"));
1381 #endif
1382 }
1383 }
1384 return;
1385 }
1386
1387 /* Hmm, a force re-init means we should ignore the current window
1388 * so we resize to the default font size.
1389 */
1390 if (reinit>0) {
1391 #ifdef RDB_DEBUG_PATCH
1392 debug((27, "reset_window() -> Forced re-init"));
1393 #endif
1394
1395 offset_width = offset_height = cfg.window_border;
1396 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
1397 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
1398
1399 if (win_width != font_width*cols + offset_width*2 ||
1400 win_height != font_height*rows + offset_height*2) {
1401
1402 /* If this is too large windows will resize it to the maximum
1403 * allowed window size, we will then be back in here and resize
1404 * the font or terminal to fit.
1405 */
1406 SetWindowPos(hwnd, NULL, 0, 0,
1407 font_width*cols + extra_width,
1408 font_height*rows + extra_height,
1409 SWP_NOMOVE | SWP_NOZORDER);
1410 }
1411
1412 InvalidateRect(hwnd, NULL, TRUE);
1413 return;
1414 }
1415
1416 /* Okay the user doesn't want us to change the font so we try the
1417 * window. But that may be too big for the screen which forces us
1418 * to change the terminal.
1419 */
1420 if ((cfg.resize_action == RESIZE_TERM && reinit<=0) ||
1421 (cfg.resize_action == RESIZE_EITHER && reinit<0) ||
1422 reinit>0) {
1423 offset_width = offset_height = cfg.window_border;
1424 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
1425 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
1426
1427 if (win_width != font_width*cols + offset_width*2 ||
1428 win_height != font_height*rows + offset_height*2) {
1429
1430 static RECT ss;
1431 int width, height;
1432
1433 get_fullscreen_rect(&ss);
1434
1435 width = (ss.right - ss.left - extra_width) / font_width;
1436 height = (ss.bottom - ss.top - extra_height) / font_height;
1437
1438 /* Grrr too big */
1439 if ( rows > height || cols > width ) {
1440 if (cfg.resize_action == RESIZE_EITHER) {
1441 /* Make the font the biggest we can */
1442 if (cols > width)
1443 font_width = (ss.right - ss.left - extra_width)/cols;
1444 if (rows > height)
1445 font_height = (ss.bottom - ss.top - extra_height)/rows;
1446
1447 deinit_fonts();
1448 init_fonts(font_width, font_height);
1449
1450 width = (ss.right - ss.left - extra_width) / font_width;
1451 height = (ss.bottom - ss.top - extra_height) / font_height;
1452 } else {
1453 if ( height > rows ) height = rows;
1454 if ( width > cols ) width = cols;
1455 term_size(height, width, cfg.savelines);
1456 #ifdef RDB_DEBUG_PATCH
1457 debug((27, "reset_window() -> term resize to (%d,%d)",
1458 height, width));
1459 #endif
1460 }
1461 }
1462
1463 SetWindowPos(hwnd, NULL, 0, 0,
1464 font_width*cols + extra_width,
1465 font_height*rows + extra_height,
1466 SWP_NOMOVE | SWP_NOZORDER);
1467
1468 InvalidateRect(hwnd, NULL, TRUE);
1469 #ifdef RDB_DEBUG_PATCH
1470 debug((27, "reset_window() -> window resize to (%d,%d)",
1471 font_width*cols + extra_width,
1472 font_height*rows + extra_height));
1473 #endif
1474 }
1475 return;
1476 }
1477
1478 /* We're allowed to or must change the font but do we want to ? */
1479
1480 if (font_width != (win_width-cfg.window_border*2)/cols ||
1481 font_height != (win_height-cfg.window_border*2)/rows) {
1482
1483 deinit_fonts();
1484 init_fonts((win_width-cfg.window_border*2)/cols,
1485 (win_height-cfg.window_border*2)/rows);
1486 offset_width = (win_width-font_width*cols)/2;
1487 offset_height = (win_height-font_height*rows)/2;
1488
1489 extra_width = wr.right - wr.left - cr.right + cr.left +offset_width*2;
1490 extra_height = wr.bottom - wr.top - cr.bottom + cr.top+offset_height*2;
1491
1492 InvalidateRect(hwnd, NULL, TRUE);
1493 #ifdef RDB_DEBUG_PATCH
1494 debug((25, "reset_window() -> font resize to (%d,%d)",
1495 font_width, font_height));
1496 #endif
1497 }
1498 }
1499
1500 static void set_input_locale(HKL kl)
1501 {
1502 char lbuf[20];
1503
1504 GetLocaleInfo(LOWORD(kl), LOCALE_IDEFAULTANSICODEPAGE,
1505 lbuf, sizeof(lbuf));
1506
1507 kbd_codepage = atoi(lbuf);
1508 }
1509
1510 static void click(Mouse_Button b, int x, int y, int shift, int ctrl, int alt)
1511 {
1512 int thistime = GetMessageTime();
1513
1514 if (send_raw_mouse && !(cfg.mouse_override && shift)) {
1515 lastbtn = MBT_NOTHING;
1516 term_mouse(b, MA_CLICK, x, y, shift, ctrl, alt);
1517 return;
1518 }
1519
1520 if (lastbtn == b && thistime - lasttime < dbltime) {
1521 lastact = (lastact == MA_CLICK ? MA_2CLK :
1522 lastact == MA_2CLK ? MA_3CLK :
1523 lastact == MA_3CLK ? MA_CLICK : MA_NOTHING);
1524 } else {
1525 lastbtn = b;
1526 lastact = MA_CLICK;
1527 }
1528 if (lastact != MA_NOTHING)
1529 term_mouse(b, lastact, x, y, shift, ctrl, alt);
1530 lasttime = thistime;
1531 }
1532
1533 /*
1534 * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT)
1535 * into a cooked one (SELECT, EXTEND, PASTE).
1536 */
1537 Mouse_Button translate_button(Mouse_Button button)
1538 {
1539 if (button == MBT_LEFT)
1540 return MBT_SELECT;
1541 if (button == MBT_MIDDLE)
1542 return cfg.mouse_is_xterm ? MBT_PASTE : MBT_EXTEND;
1543 if (button == MBT_RIGHT)
1544 return cfg.mouse_is_xterm ? MBT_EXTEND : MBT_PASTE;
1545 return 0; /* shouldn't happen */
1546 }
1547
1548 static void show_mouseptr(int show)
1549 {
1550 static int cursor_visible = 1;
1551 if (!cfg.hide_mouseptr) /* override if this feature disabled */
1552 show = 1;
1553 if (cursor_visible && !show)
1554 ShowCursor(FALSE);
1555 else if (!cursor_visible && show)
1556 ShowCursor(TRUE);
1557 cursor_visible = show;
1558 }
1559
1560 static int is_alt_pressed(void)
1561 {
1562 BYTE keystate[256];
1563 int r = GetKeyboardState(keystate);
1564 if (!r)
1565 return FALSE;
1566 if (keystate[VK_MENU] & 0x80)
1567 return TRUE;
1568 if (keystate[VK_RMENU] & 0x80)
1569 return TRUE;
1570 return FALSE;
1571 }
1572
1573 static int is_shift_pressed(void)
1574 {
1575 BYTE keystate[256];
1576 int r = GetKeyboardState(keystate);
1577 if (!r)
1578 return FALSE;
1579 if (keystate[VK_SHIFT] & 0x80)
1580 return TRUE;
1581 return FALSE;
1582 }
1583
1584 static int resizing;
1585
1586 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1587 WPARAM wParam, LPARAM lParam)
1588 {
1589 HDC hdc;
1590 static int ignore_clip = FALSE;
1591 static int need_backend_resize = FALSE;
1592 static int fullscr_on_max = FALSE;
1593
1594 switch (message) {
1595 case WM_TIMER:
1596 if (pending_netevent)
1597 enact_pending_netevent();
1598 if (GetCapture() != hwnd ||
1599 (send_raw_mouse && !(cfg.mouse_override && is_shift_pressed())))
1600 term_out();
1601 noise_regular();
1602 HideCaret(hwnd);
1603 term_update();
1604 ShowCaret(hwnd);
1605 if (cfg.ping_interval > 0) {
1606 time_t now;
1607 time(&now);
1608 if (now - last_movement > cfg.ping_interval) {
1609 back->special(TS_PING);
1610 last_movement = now;
1611 }
1612 }
1613 net_pending_errors();
1614 return 0;
1615 case WM_CREATE:
1616 break;
1617 case WM_CLOSE:
1618 show_mouseptr(1);
1619 if (!cfg.warn_on_close || session_closed ||
1620 MessageBox(hwnd,
1621 "Are you sure you want to close this session?",
1622 "PuTTY Exit Confirmation",
1623 MB_ICONWARNING | MB_OKCANCEL) == IDOK)
1624 DestroyWindow(hwnd);
1625 return 0;
1626 case WM_DESTROY:
1627 show_mouseptr(1);
1628 PostQuitMessage(0);
1629 return 0;
1630 case WM_SYSCOMMAND:
1631 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1632 case IDM_SHOWLOG:
1633 showeventlog(hwnd);
1634 break;
1635 case IDM_NEWSESS:
1636 case IDM_DUPSESS:
1637 case IDM_SAVEDSESS:
1638 {
1639 char b[2048];
1640 char c[30], *cl;
1641 int freecl = FALSE;
1642 STARTUPINFO si;
1643 PROCESS_INFORMATION pi;
1644 HANDLE filemap = NULL;
1645
1646 if (wParam == IDM_DUPSESS) {
1647 /*
1648 * Allocate a file-mapping memory chunk for the
1649 * config structure.
1650 */
1651 SECURITY_ATTRIBUTES sa;
1652 Config *p;
1653
1654 sa.nLength = sizeof(sa);
1655 sa.lpSecurityDescriptor = NULL;
1656 sa.bInheritHandle = TRUE;
1657 filemap = CreateFileMapping((HANDLE) 0xFFFFFFFF,
1658 &sa,
1659 PAGE_READWRITE,
1660 0, sizeof(Config), NULL);
1661 if (filemap) {
1662 p = (Config *) MapViewOfFile(filemap,
1663 FILE_MAP_WRITE,
1664 0, 0, sizeof(Config));
1665 if (p) {
1666 *p = cfg; /* structure copy */
1667 UnmapViewOfFile(p);
1668 }
1669 }
1670 sprintf(c, "putty &%p", filemap);
1671 cl = c;
1672 } else if (wParam == IDM_SAVEDSESS) {
1673 if ((lParam - IDM_SAVED_MIN) / 16 < nsessions) {
1674 char *session =
1675 sessions[(lParam - IDM_SAVED_MIN) / 16];
1676 cl = smalloc(16 + strlen(session));
1677 /* 8, but play safe */
1678 if (!cl)
1679 cl = NULL;
1680 /* not a very important failure mode */
1681 else {
1682 sprintf(cl, "putty @%s", session);
1683 freecl = TRUE;
1684 }
1685 } else
1686 break;
1687 } else
1688 cl = NULL;
1689
1690 GetModuleFileName(NULL, b, sizeof(b) - 1);
1691 si.cb = sizeof(si);
1692 si.lpReserved = NULL;
1693 si.lpDesktop = NULL;
1694 si.lpTitle = NULL;
1695 si.dwFlags = 0;
1696 si.cbReserved2 = 0;
1697 si.lpReserved2 = NULL;
1698 CreateProcess(b, cl, NULL, NULL, TRUE,
1699 NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
1700
1701 if (filemap)
1702 CloseHandle(filemap);
1703 if (freecl)
1704 sfree(cl);
1705 }
1706 break;
1707 case IDM_RECONF:
1708 {
1709 Config prev_cfg;
1710 int init_lvl = 1;
1711
1712 GetWindowText(hwnd, cfg.wintitle, sizeof(cfg.wintitle));
1713 prev_cfg = cfg;
1714
1715 if (!do_reconfig(hwnd))
1716 break;
1717
1718 {
1719 /* Disable full-screen if resizing forbidden */
1720 HMENU m = GetSystemMenu (hwnd, FALSE);
1721 EnableMenuItem(m, IDM_FULLSCREEN, MF_BYCOMMAND |
1722 (cfg.resize_action == RESIZE_DISABLED)
1723 ? MF_GRAYED : MF_ENABLED);
1724 /* Gracefully unzoom if necessary */
1725 if (IsZoomed(hwnd) &&
1726 (cfg.resize_action == RESIZE_DISABLED)) {
1727 ShowWindow(hwnd, SW_RESTORE);
1728 }
1729 }
1730
1731 if (strcmp(prev_cfg.logfilename, cfg.logfilename) ||
1732 prev_cfg.logtype != cfg.logtype) {
1733 logfclose(); /* reset logging */
1734 logfopen();
1735 }
1736
1737 sfree(logpal);
1738 /*
1739 * Flush the line discipline's edit buffer in the
1740 * case where local editing has just been disabled.
1741 */
1742 ldisc_send(NULL, 0, 0);
1743 if (pal)
1744 DeleteObject(pal);
1745 logpal = NULL;
1746 pal = NULL;
1747 cfgtopalette();
1748 init_palette();
1749
1750 /* Give terminal a heads-up on miscellaneous stuff */
1751 term_reconfig();
1752
1753 /* Screen size changed ? */
1754 if (cfg.height != prev_cfg.height ||
1755 cfg.width != prev_cfg.width ||
1756 cfg.savelines != prev_cfg.savelines ||
1757 cfg.resize_action == RESIZE_FONT ||
1758 (cfg.resize_action == RESIZE_EITHER && IsZoomed(hwnd)) ||
1759 cfg.resize_action == RESIZE_DISABLED)
1760 term_size(cfg.height, cfg.width, cfg.savelines);
1761
1762 /* Enable or disable the scroll bar, etc */
1763 {
1764 LONG nflg, flag = GetWindowLong(hwnd, GWL_STYLE);
1765 LONG nexflag, exflag =
1766 GetWindowLong(hwnd, GWL_EXSTYLE);
1767
1768 nexflag = exflag;
1769 if (cfg.alwaysontop != prev_cfg.alwaysontop) {
1770 if (cfg.alwaysontop) {
1771 nexflag |= WS_EX_TOPMOST;
1772 SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
1773 SWP_NOMOVE | SWP_NOSIZE);
1774 } else {
1775 nexflag &= ~(WS_EX_TOPMOST);
1776 SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0,
1777 SWP_NOMOVE | SWP_NOSIZE);
1778 }
1779 }
1780 if (cfg.sunken_edge)
1781 nexflag |= WS_EX_CLIENTEDGE;
1782 else
1783 nexflag &= ~(WS_EX_CLIENTEDGE);
1784
1785 nflg = flag;
1786 if (is_full_screen() ?
1787 cfg.scrollbar_in_fullscreen : cfg.scrollbar)
1788 nflg |= WS_VSCROLL;
1789 else
1790 nflg &= ~WS_VSCROLL;
1791
1792 if (cfg.resize_action == RESIZE_DISABLED ||
1793 is_full_screen())
1794 nflg &= ~WS_THICKFRAME;
1795 else
1796 nflg |= WS_THICKFRAME;
1797
1798 if (cfg.resize_action == RESIZE_DISABLED)
1799 nflg &= ~WS_MAXIMIZEBOX;
1800 else
1801 nflg |= WS_MAXIMIZEBOX;
1802
1803 if (nflg != flag || nexflag != exflag) {
1804 if (nflg != flag)
1805 SetWindowLong(hwnd, GWL_STYLE, nflg);
1806 if (nexflag != exflag)
1807 SetWindowLong(hwnd, GWL_EXSTYLE, nexflag);
1808
1809 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
1810 SWP_NOACTIVATE | SWP_NOCOPYBITS |
1811 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
1812 SWP_FRAMECHANGED);
1813
1814 init_lvl = 2;
1815 }
1816 }
1817
1818 /* Oops */
1819 if (cfg.resize_action == RESIZE_DISABLED && IsZoomed(hwnd)) {
1820 force_normal(hwnd);
1821 init_lvl = 2;
1822 }
1823
1824 set_title(cfg.wintitle);
1825 if (IsIconic(hwnd)) {
1826 SetWindowText(hwnd,
1827 cfg.win_name_always ? window_name :
1828 icon_name);
1829 }
1830
1831 if (strcmp(cfg.font, prev_cfg.font) != 0 ||
1832 strcmp(cfg.line_codepage, prev_cfg.line_codepage) != 0 ||
1833 cfg.fontisbold != prev_cfg.fontisbold ||
1834 cfg.fontheight != prev_cfg.fontheight ||
1835 cfg.fontcharset != prev_cfg.fontcharset ||
1836 cfg.vtmode != prev_cfg.vtmode ||
1837 cfg.bold_colour != prev_cfg.bold_colour ||
1838 cfg.resize_action == RESIZE_DISABLED ||
1839 cfg.resize_action == RESIZE_EITHER ||
1840 (cfg.resize_action != prev_cfg.resize_action))
1841 init_lvl = 2;
1842
1843 InvalidateRect(hwnd, NULL, TRUE);
1844 reset_window(init_lvl);
1845 net_pending_errors();
1846 }
1847 break;
1848 case IDM_COPYALL:
1849 term_copyall();
1850 break;
1851 case IDM_CLRSB:
1852 term_clrsb();
1853 break;
1854 case IDM_RESET:
1855 term_pwron();
1856 break;
1857 case IDM_TEL_AYT:
1858 back->special(TS_AYT);
1859 net_pending_errors();
1860 break;
1861 case IDM_TEL_BRK:
1862 back->special(TS_BRK);
1863 net_pending_errors();
1864 break;
1865 case IDM_TEL_SYNCH:
1866 back->special(TS_SYNCH);
1867 net_pending_errors();
1868 break;
1869 case IDM_TEL_EC:
1870 back->special(TS_EC);
1871 net_pending_errors();
1872 break;
1873 case IDM_TEL_EL:
1874 back->special(TS_EL);
1875 net_pending_errors();
1876 break;
1877 case IDM_TEL_GA:
1878 back->special(TS_GA);
1879 net_pending_errors();
1880 break;
1881 case IDM_TEL_NOP:
1882 back->special(TS_NOP);
1883 net_pending_errors();
1884 break;
1885 case IDM_TEL_ABORT:
1886 back->special(TS_ABORT);
1887 net_pending_errors();
1888 break;
1889 case IDM_TEL_AO:
1890 back->special(TS_AO);
1891 net_pending_errors();
1892 break;
1893 case IDM_TEL_IP:
1894 back->special(TS_IP);
1895 net_pending_errors();
1896 break;
1897 case IDM_TEL_SUSP:
1898 back->special(TS_SUSP);
1899 net_pending_errors();
1900 break;
1901 case IDM_TEL_EOR:
1902 back->special(TS_EOR);
1903 net_pending_errors();
1904 break;
1905 case IDM_TEL_EOF:
1906 back->special(TS_EOF);
1907 net_pending_errors();
1908 break;
1909 case IDM_ABOUT:
1910 showabout(hwnd);
1911 break;
1912 case IDM_HELP:
1913 WinHelp(hwnd, help_path,
1914 help_has_contents ? HELP_FINDER : HELP_CONTENTS, 0);
1915 break;
1916 case SC_MOUSEMENU:
1917 /*
1918 * We get this if the System menu has been activated
1919 * using the mouse.
1920 */
1921 show_mouseptr(1);
1922 break;
1923 case SC_KEYMENU:
1924 /*
1925 * We get this if the System menu has been activated
1926 * using the keyboard. This might happen from within
1927 * TranslateKey, in which case it really wants to be
1928 * followed by a `space' character to actually _bring
1929 * the menu up_ rather than just sitting there in
1930 * `ready to appear' state.
1931 */
1932 show_mouseptr(1); /* make sure pointer is visible */
1933 if( lParam == 0 )
1934 PostMessage(hwnd, WM_CHAR, ' ', 0);
1935 break;
1936 case IDM_FULLSCREEN:
1937 flip_full_screen();
1938 break;
1939 default:
1940 if (wParam >= IDM_SAVED_MIN && wParam <= IDM_SAVED_MAX) {
1941 SendMessage(hwnd, WM_SYSCOMMAND, IDM_SAVEDSESS, wParam);
1942 }
1943 }
1944 break;
1945
1946 #define X_POS(l) ((int)(short)LOWORD(l))
1947 #define Y_POS(l) ((int)(short)HIWORD(l))
1948
1949 #define TO_CHR_X(x) ((((x)<0 ? (x)-font_width+1 : (x))-offset_width) / font_width)
1950 #define TO_CHR_Y(y) ((((y)<0 ? (y)-font_height+1: (y))-offset_height) / font_height)
1951 case WM_LBUTTONDOWN:
1952 case WM_MBUTTONDOWN:
1953 case WM_RBUTTONDOWN:
1954 case WM_LBUTTONUP:
1955 case WM_MBUTTONUP:
1956 case WM_RBUTTONUP:
1957 {
1958 int button, press;
1959
1960 switch (message) {
1961 case WM_LBUTTONDOWN:
1962 button = MBT_LEFT;
1963 press = 1;
1964 break;
1965 case WM_MBUTTONDOWN:
1966 button = MBT_MIDDLE;
1967 press = 1;
1968 break;
1969 case WM_RBUTTONDOWN:
1970 button = MBT_RIGHT;
1971 press = 1;
1972 break;
1973 case WM_LBUTTONUP:
1974 button = MBT_LEFT;
1975 press = 0;
1976 break;
1977 case WM_MBUTTONUP:
1978 button = MBT_MIDDLE;
1979 press = 0;
1980 break;
1981 case WM_RBUTTONUP:
1982 button = MBT_RIGHT;
1983 press = 0;
1984 break;
1985 default:
1986 button = press = 0; /* shouldn't happen */
1987 }
1988 show_mouseptr(1);
1989 /*
1990 * Special case: in full-screen mode, if the left
1991 * button is clicked in the very top left corner of the
1992 * window, we put up the System menu instead of doing
1993 * selection.
1994 */
1995 if (is_full_screen() && press && button == MBT_LEFT &&
1996 X_POS(lParam) == 0 && Y_POS(lParam) == 0) {
1997 SendMessage(hwnd, WM_SYSCOMMAND, SC_MOUSEMENU, 0);
1998 return 0;
1999 }
2000 if (press) {
2001 click(button,
2002 TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam)),
2003 wParam & MK_SHIFT, wParam & MK_CONTROL,
2004 is_alt_pressed());
2005 SetCapture(hwnd);
2006 } else {
2007 term_mouse(button, MA_RELEASE,
2008 TO_CHR_X(X_POS(lParam)),
2009 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
2010 wParam & MK_CONTROL, is_alt_pressed());
2011 ReleaseCapture();
2012 }
2013 }
2014 return 0;
2015 case WM_MOUSEMOVE:
2016 show_mouseptr(1);
2017 /*
2018 * Add the mouse position and message time to the random
2019 * number noise.
2020 */
2021 noise_ultralight(lParam);
2022
2023 if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON) &&
2024 GetCapture() == hwnd) {
2025 Mouse_Button b;
2026 if (wParam & MK_LBUTTON)
2027 b = MBT_LEFT;
2028 else if (wParam & MK_MBUTTON)
2029 b = MBT_MIDDLE;
2030 else
2031 b = MBT_RIGHT;
2032 term_mouse(b, MA_DRAG, TO_CHR_X(X_POS(lParam)),
2033 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
2034 wParam & MK_CONTROL, is_alt_pressed());
2035 }
2036 return 0;
2037 case WM_NCMOUSEMOVE:
2038 show_mouseptr(1);
2039 noise_ultralight(lParam);
2040 return 0;
2041 case WM_IGNORE_CLIP:
2042 ignore_clip = wParam; /* don't panic on DESTROYCLIPBOARD */
2043 break;
2044 case WM_DESTROYCLIPBOARD:
2045 if (!ignore_clip)
2046 term_deselect();
2047 ignore_clip = FALSE;
2048 return 0;
2049 case WM_PAINT:
2050 {
2051 PAINTSTRUCT p;
2052 HideCaret(hwnd);
2053 hdc = BeginPaint(hwnd, &p);
2054 if (pal) {
2055 SelectPalette(hdc, pal, TRUE);
2056 RealizePalette(hdc);
2057 }
2058 term_paint(hdc,
2059 (p.rcPaint.left-offset_width)/font_width,
2060 (p.rcPaint.top-offset_height)/font_height,
2061 (p.rcPaint.right-offset_width-1)/font_width,
2062 (p.rcPaint.bottom-offset_height-1)/font_height);
2063
2064 if (p.fErase ||
2065 p.rcPaint.left < offset_width ||
2066 p.rcPaint.top < offset_height ||
2067 p.rcPaint.right >= offset_width + font_width*cols ||
2068 p.rcPaint.bottom>= offset_height + font_height*rows)
2069 {
2070 HBRUSH fillcolour, oldbrush;
2071 HPEN edge, oldpen;
2072 fillcolour = CreateSolidBrush (
2073 colours[(ATTR_DEFBG>>ATTR_BGSHIFT)*2]);
2074 oldbrush = SelectObject(hdc, fillcolour);
2075 edge = CreatePen(PS_SOLID, 0,
2076 colours[(ATTR_DEFBG>>ATTR_BGSHIFT)*2]);
2077 oldpen = SelectObject(hdc, edge);
2078
2079 /*
2080 * Jordan Russell reports that this apparently
2081 * ineffectual IntersectClipRect() call masks a
2082 * Windows NT/2K bug causing strange display
2083 * problems when the PuTTY window is taller than
2084 * the primary monitor. It seems harmless enough...
2085 */
2086 IntersectClipRect(hdc,
2087 p.rcPaint.left, p.rcPaint.top,
2088 p.rcPaint.right, p.rcPaint.bottom);
2089
2090 ExcludeClipRect(hdc,
2091 offset_width, offset_height,
2092 offset_width+font_width*cols,
2093 offset_height+font_height*rows);
2094
2095 Rectangle(hdc, p.rcPaint.left, p.rcPaint.top,
2096 p.rcPaint.right, p.rcPaint.bottom);
2097
2098 // SelectClipRgn(hdc, NULL);
2099
2100 SelectObject(hdc, oldbrush);
2101 DeleteObject(fillcolour);
2102 SelectObject(hdc, oldpen);
2103 DeleteObject(edge);
2104 }
2105 SelectObject(hdc, GetStockObject(SYSTEM_FONT));
2106 SelectObject(hdc, GetStockObject(WHITE_PEN));
2107 EndPaint(hwnd, &p);
2108 ShowCaret(hwnd);
2109 }
2110 return 0;
2111 case WM_NETEVENT:
2112 /* Notice we can get multiple netevents, FD_READ, FD_WRITE etc
2113 * but the only one that's likely to try to overload us is FD_READ.
2114 * This means buffering just one is fine.
2115 */
2116 if (pending_netevent)
2117 enact_pending_netevent();
2118
2119 pending_netevent = TRUE;
2120 pend_netevent_wParam = wParam;
2121 pend_netevent_lParam = lParam;
2122 if (WSAGETSELECTEVENT(lParam) != FD_READ)
2123 enact_pending_netevent();
2124
2125 time(&last_movement);
2126 return 0;
2127 case WM_SETFOCUS:
2128 has_focus = TRUE;
2129 CreateCaret(hwnd, caretbm, font_width, font_height);
2130 ShowCaret(hwnd);
2131 flash_window(0); /* stop */
2132 compose_state = 0;
2133 term_out();
2134 term_update();
2135 break;
2136 case WM_KILLFOCUS:
2137 show_mouseptr(1);
2138 has_focus = FALSE;
2139 DestroyCaret();
2140 caret_x = caret_y = -1; /* ensure caret is replaced next time */
2141 term_out();
2142 term_update();
2143 break;
2144 case WM_ENTERSIZEMOVE:
2145 #ifdef RDB_DEBUG_PATCH
2146 debug((27, "WM_ENTERSIZEMOVE"));
2147 #endif
2148 EnableSizeTip(1);
2149 resizing = TRUE;
2150 need_backend_resize = FALSE;
2151 break;
2152 case WM_EXITSIZEMOVE:
2153 EnableSizeTip(0);
2154 resizing = FALSE;
2155 #ifdef RDB_DEBUG_PATCH
2156 debug((27, "WM_EXITSIZEMOVE"));
2157 #endif
2158 if (need_backend_resize) {
2159 term_size(cfg.height, cfg.width, cfg.savelines);
2160 InvalidateRect(hwnd, NULL, TRUE);
2161 }
2162 break;
2163 case WM_SIZING:
2164 /*
2165 * This does two jobs:
2166 * 1) Keep the sizetip uptodate
2167 * 2) Make sure the window size is _stepped_ in units of the font size.
2168 */
2169 if (cfg.resize_action != RESIZE_FONT && !alt_pressed) {
2170 int width, height, w, h, ew, eh;
2171 LPRECT r = (LPRECT) lParam;
2172
2173 if ( !need_backend_resize && cfg.resize_action == RESIZE_EITHER &&
2174 (cfg.height != rows || cfg.width != cols )) {
2175 /*
2176 * Great! It seems that both the terminal size and the
2177 * font size have been changed and the user is now dragging.
2178 *
2179 * It will now be difficult to get back to the configured
2180 * font size!
2181 *
2182 * This would be easier but it seems to be too confusing.
2183
2184 term_size(cfg.height, cfg.width, cfg.savelines);
2185 reset_window(2);
2186 */
2187 cfg.height=rows; cfg.width=cols;
2188
2189 InvalidateRect(hwnd, NULL, TRUE);
2190 need_backend_resize = TRUE;
2191 }
2192
2193 width = r->right - r->left - extra_width;
2194 height = r->bottom - r->top - extra_height;
2195 w = (width + font_width / 2) / font_width;
2196 if (w < 1)
2197 w = 1;
2198 h = (height + font_height / 2) / font_height;
2199 if (h < 1)
2200 h = 1;
2201 UpdateSizeTip(hwnd, w, h);
2202 ew = width - w * font_width;
2203 eh = height - h * font_height;
2204 if (ew != 0) {
2205 if (wParam == WMSZ_LEFT ||
2206 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
2207 r->left += ew;
2208 else
2209 r->right -= ew;
2210 }
2211 if (eh != 0) {
2212 if (wParam == WMSZ_TOP ||
2213 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
2214 r->top += eh;
2215 else
2216 r->bottom -= eh;
2217 }
2218 if (ew || eh)
2219 return 1;
2220 else
2221 return 0;
2222 } else {
2223 int width, height, w, h, rv = 0;
2224 int ex_width = extra_width + (cfg.window_border - offset_width) * 2;
2225 int ex_height = extra_height + (cfg.window_border - offset_height) * 2;
2226 LPRECT r = (LPRECT) lParam;
2227
2228 width = r->right - r->left - ex_width;
2229 height = r->bottom - r->top - ex_height;
2230
2231 w = (width + cols/2)/cols;
2232 h = (height + rows/2)/rows;
2233 if ( r->right != r->left + w*cols + ex_width)
2234 rv = 1;
2235
2236 if (wParam == WMSZ_LEFT ||
2237 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
2238 r->left = r->right - w*cols - ex_width;
2239 else
2240 r->right = r->left + w*cols + ex_width;
2241
2242 if (r->bottom != r->top + h*rows + ex_height)
2243 rv = 1;
2244
2245 if (wParam == WMSZ_TOP ||
2246 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
2247 r->top = r->bottom - h*rows - ex_height;
2248 else
2249 r->bottom = r->top + h*rows + ex_height;
2250
2251 return rv;
2252 }
2253 /* break; (never reached) */
2254 case WM_FULLSCR_ON_MAX:
2255 fullscr_on_max = TRUE;
2256 break;
2257 case WM_MOVE:
2258 sys_cursor_update();
2259 break;
2260 case WM_SIZE:
2261 #ifdef RDB_DEBUG_PATCH
2262 debug((27, "WM_SIZE %s (%d,%d)",
2263 (wParam == SIZE_MINIMIZED) ? "SIZE_MINIMIZED":
2264 (wParam == SIZE_MAXIMIZED) ? "SIZE_MAXIMIZED":
2265 (wParam == SIZE_RESTORED && resizing) ? "to":
2266 (wParam == SIZE_RESTORED) ? "SIZE_RESTORED":
2267 "...",
2268 LOWORD(lParam), HIWORD(lParam)));
2269 #endif
2270 if (wParam == SIZE_MINIMIZED)
2271 SetWindowText(hwnd,
2272 cfg.win_name_always ? window_name : icon_name);
2273 if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
2274 SetWindowText(hwnd, window_name);
2275 if (wParam == SIZE_RESTORED)
2276 clear_full_screen();
2277 if (wParam == SIZE_MAXIMIZED && fullscr_on_max) {
2278 fullscr_on_max = FALSE;
2279 make_full_screen();
2280 }
2281
2282 if (cfg.resize_action == RESIZE_DISABLED) {
2283 /* A resize, well it better be a minimize. */
2284 reset_window(-1);
2285 } else {
2286
2287 int width, height, w, h;
2288
2289 width = LOWORD(lParam);
2290 height = HIWORD(lParam);
2291
2292 if (!resizing) {
2293 if (wParam == SIZE_MAXIMIZED && !was_zoomed) {
2294 was_zoomed = 1;
2295 prev_rows = rows;
2296 prev_cols = cols;
2297 if (cfg.resize_action == RESIZE_TERM) {
2298 w = width / font_width;
2299 if (w < 1) w = 1;
2300 h = height / font_height;
2301 if (h < 1) h = 1;
2302
2303 term_size(h, w, cfg.savelines);
2304 }
2305 reset_window(0);
2306 } else if (wParam == SIZE_RESTORED && was_zoomed) {
2307 was_zoomed = 0;
2308 if (cfg.resize_action == RESIZE_TERM)
2309 term_size(prev_rows, prev_cols, cfg.savelines);
2310 if (cfg.resize_action != RESIZE_FONT)
2311 reset_window(2);
2312 else
2313 reset_window(0);
2314 }
2315 /* This is an unexpected resize, these will normally happen
2316 * if the window is too large. Probably either the user
2317 * selected a huge font or the screen size has changed.
2318 *
2319 * This is also called with minimize.
2320 */
2321 else reset_window(-1);
2322 }
2323
2324 /*
2325 * Don't call back->size in mid-resize. (To prevent
2326 * massive numbers of resize events getting sent
2327 * down the connection during an NT opaque drag.)
2328 */
2329 if (resizing) {
2330 if (cfg.resize_action != RESIZE_FONT && !alt_pressed) {
2331 need_backend_resize = TRUE;
2332 w = (width-cfg.window_border*2) / font_width;
2333 if (w < 1) w = 1;
2334 h = (height-cfg.window_border*2) / font_height;
2335 if (h < 1) h = 1;
2336
2337 cfg.height = h;
2338 cfg.width = w;
2339 } else
2340 reset_window(0);
2341 }
2342 }
2343 sys_cursor_update();
2344 return 0;
2345 case WM_VSCROLL:
2346 switch (LOWORD(wParam)) {
2347 case SB_BOTTOM:
2348 term_scroll(-1, 0);
2349 break;
2350 case SB_TOP:
2351 term_scroll(+1, 0);
2352 break;
2353 case SB_LINEDOWN:
2354 term_scroll(0, +1);
2355 break;
2356 case SB_LINEUP:
2357 term_scroll(0, -1);
2358 break;
2359 case SB_PAGEDOWN:
2360 term_scroll(0, +rows / 2);
2361 break;
2362 case SB_PAGEUP:
2363 term_scroll(0, -rows / 2);
2364 break;
2365 case SB_THUMBPOSITION:
2366 case SB_THUMBTRACK:
2367 term_scroll(1, HIWORD(wParam));
2368 break;
2369 }
2370 break;
2371 case WM_PALETTECHANGED:
2372 if ((HWND) wParam != hwnd && pal != NULL) {
2373 HDC hdc = get_ctx();
2374 if (hdc) {
2375 if (RealizePalette(hdc) > 0)
2376 UpdateColors(hdc);
2377 free_ctx(hdc);
2378 }
2379 }
2380 break;
2381 case WM_QUERYNEWPALETTE:
2382 if (pal != NULL) {
2383 HDC hdc = get_ctx();
2384 if (hdc) {
2385 if (RealizePalette(hdc) > 0)
2386 UpdateColors(hdc);
2387 free_ctx(hdc);
2388 return TRUE;
2389 }
2390 }
2391 return FALSE;
2392 case WM_KEYDOWN:
2393 case WM_SYSKEYDOWN:
2394 case WM_KEYUP:
2395 case WM_SYSKEYUP:
2396 /*
2397 * Add the scan code and keypress timing to the random
2398 * number noise.
2399 */
2400 noise_ultralight(lParam);
2401
2402 /*
2403 * We don't do TranslateMessage since it disassociates the
2404 * resulting CHAR message from the KEYDOWN that sparked it,
2405 * which we occasionally don't want. Instead, we process
2406 * KEYDOWN, and call the Win32 translator functions so that
2407 * we get the translations under _our_ control.
2408 */
2409 {
2410 unsigned char buf[20];
2411 int len;
2412
2413 if (wParam == VK_PROCESSKEY) {
2414 MSG m;
2415 m.hwnd = hwnd;
2416 m.message = WM_KEYDOWN;
2417 m.wParam = wParam;
2418 m.lParam = lParam & 0xdfff;
2419 TranslateMessage(&m);
2420 } else {
2421 len = TranslateKey(message, wParam, lParam, buf);
2422 if (len == -1)
2423 return DefWindowProc(hwnd, message, wParam, lParam);
2424
2425 if (len != 0) {
2426 /*
2427 * Interrupt an ongoing paste. I'm not sure
2428 * this is sensible, but for the moment it's
2429 * preferable to having to faff about buffering
2430 * things.
2431 */
2432 term_nopaste();
2433
2434 /*
2435 * We need not bother about stdin backlogs
2436 * here, because in GUI PuTTY we can't do
2437 * anything about it anyway; there's no means
2438 * of asking Windows to hold off on KEYDOWN
2439 * messages. We _have_ to buffer everything
2440 * we're sent.
2441 */
2442 ldisc_send(buf, len, 1);
2443 show_mouseptr(0);
2444 }
2445 }
2446 }
2447 net_pending_errors();
2448 return 0;
2449 case WM_INPUTLANGCHANGE:
2450 /* wParam == Font number */
2451 /* lParam == Locale */
2452 set_input_locale((HKL)lParam);
2453 sys_cursor_update();
2454 break;
2455 case WM_IME_NOTIFY:
2456 if(wParam == IMN_SETOPENSTATUS) {
2457 HIMC hImc = ImmGetContext(hwnd);
2458 ImmSetCompositionFont(hImc, &lfont);
2459 ImmReleaseContext(hwnd, hImc);
2460 return 0;
2461 }
2462 break;
2463 case WM_IME_COMPOSITION:
2464 {
2465 HIMC hIMC;
2466 int n;
2467 char *buff;
2468
2469 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ||
2470 osVersion.dwPlatformId == VER_PLATFORM_WIN32s) break; /* no Unicode */
2471
2472 if ((lParam & GCS_RESULTSTR) == 0) /* Composition unfinished. */
2473 break; /* fall back to DefWindowProc */
2474
2475 hIMC = ImmGetContext(hwnd);
2476 n = ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, NULL, 0);
2477
2478 if (n > 0) {
2479 int i;
2480 buff = (char*) smalloc(n);
2481 ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, buff, n);
2482 /*
2483 * Jaeyoun Chung reports that Korean character
2484 * input doesn't work correctly if we do a single
2485 * luni_send() covering the whole of buff. So
2486 * instead we luni_send the characters one by one.
2487 */
2488 for (i = 0; i < n; i += 2)
2489 luni_send((unsigned short *)(buff+i), 1, 1);
2490 free(buff);
2491 }
2492 ImmReleaseContext(hwnd, hIMC);
2493 return 1;
2494 }
2495
2496 case WM_IME_CHAR:
2497 if (wParam & 0xFF00) {
2498 unsigned char buf[2];
2499
2500 buf[1] = wParam;
2501 buf[0] = wParam >> 8;
2502 lpage_send(kbd_codepage, buf, 2, 1);
2503 } else {
2504 char c = (unsigned char) wParam;
2505 lpage_send(kbd_codepage, &c, 1, 1);
2506 }
2507 return (0);
2508 case WM_CHAR:
2509 case WM_SYSCHAR:
2510 /*
2511 * Nevertheless, we are prepared to deal with WM_CHAR
2512 * messages, should they crop up. So if someone wants to
2513 * post the things to us as part of a macro manoeuvre,
2514 * we're ready to cope.
2515 */
2516 {
2517 char c = (unsigned char)wParam;
2518 lpage_send(CP_ACP, &c, 1, 1);
2519 }
2520 return 0;
2521 case WM_SETCURSOR:
2522 if (send_raw_mouse && LOWORD(lParam) == HTCLIENT) {
2523 SetCursor(LoadCursor(NULL, IDC_ARROW));
2524 return TRUE;
2525 }
2526 default:
2527 if (message == wm_mousewheel || message == WM_MOUSEWHEEL) {
2528 int shift_pressed=0, control_pressed=0, alt_pressed=0;
2529
2530 if (message == WM_MOUSEWHEEL) {
2531 wheel_accumulator += (short)HIWORD(wParam);
2532 shift_pressed=LOWORD(wParam) & MK_SHIFT;
2533 control_pressed=LOWORD(wParam) & MK_CONTROL;
2534 } else {
2535 BYTE keys[256];
2536 wheel_accumulator += (int)wParam;
2537 if (GetKeyboardState(keys)!=0) {
2538 shift_pressed=keys[VK_SHIFT]&0x80;
2539 control_pressed=keys[VK_CONTROL]&0x80;
2540 }
2541 }
2542
2543 /* process events when the threshold is reached */
2544 while (abs(wheel_accumulator) >= WHEEL_DELTA) {
2545 int b;
2546
2547 /* reduce amount for next time */
2548 if (wheel_accumulator > 0) {
2549 b = MBT_WHEEL_UP;
2550 wheel_accumulator -= WHEEL_DELTA;
2551 } else if (wheel_accumulator < 0) {
2552 b = MBT_WHEEL_DOWN;
2553 wheel_accumulator += WHEEL_DELTA;
2554 } else
2555 break;
2556
2557 if (send_raw_mouse &&
2558 !(cfg.mouse_override && shift_pressed)) {
2559 /* send a mouse-down followed by a mouse up */
2560 term_mouse(b,
2561 MA_CLICK,
2562 TO_CHR_X(X_POS(lParam)),
2563 TO_CHR_Y(Y_POS(lParam)), shift_pressed,
2564 control_pressed, is_alt_pressed());
2565 term_mouse(b, MA_RELEASE, TO_CHR_X(X_POS(lParam)),
2566 TO_CHR_Y(Y_POS(lParam)), shift_pressed,
2567 control_pressed, is_alt_pressed());
2568 } else {
2569 /* trigger a scroll */
2570 term_scroll(0,
2571 b == MBT_WHEEL_UP ? -rows / 2 : rows / 2);
2572 }
2573 }
2574 return 0;
2575 }
2576 }
2577
2578 return DefWindowProc(hwnd, message, wParam, lParam);
2579 }
2580
2581 /*
2582 * Move the system caret. (We maintain one, even though it's
2583 * invisible, for the benefit of blind people: apparently some
2584 * helper software tracks the system caret, so we should arrange to
2585 * have one.)
2586 */
2587 void sys_cursor(int x, int y)
2588 {
2589 int cx, cy;
2590
2591 if (!has_focus) return;
2592
2593 /*
2594 * Avoid gratuitously re-updating the cursor position and IMM
2595 * window if there's no actual change required.
2596 */
2597 cx = x * font_width + offset_width;
2598 cy = y * font_height + offset_height;
2599 if (cx == caret_x && cy == caret_y)
2600 return;
2601 caret_x = cx;
2602 caret_y = cy;
2603
2604 sys_cursor_update();
2605 }
2606
2607 static void sys_cursor_update(void)
2608 {
2609 COMPOSITIONFORM cf;
2610 HIMC hIMC;
2611
2612 if (!has_focus) return;
2613
2614 if (caret_x < 0 || caret_y < 0)
2615 return;
2616
2617 SetCaretPos(caret_x, caret_y);
2618
2619 /* IMM calls on Win98 and beyond only */
2620 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32s) return; /* 3.11 */
2621
2622 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS &&
2623 osVersion.dwMinorVersion == 0) return; /* 95 */
2624
2625 /* we should have the IMM functions */
2626 hIMC = ImmGetContext(hwnd);
2627 cf.dwStyle = CFS_POINT;
2628 cf.ptCurrentPos.x = caret_x;
2629 cf.ptCurrentPos.y = caret_y;
2630 ImmSetCompositionWindow(hIMC, &cf);
2631
2632 ImmReleaseContext(hwnd, hIMC);
2633 }
2634
2635 /*
2636 * Draw a line of text in the window, at given character
2637 * coordinates, in given attributes.
2638 *
2639 * We are allowed to fiddle with the contents of `text'.
2640 */
2641 void do_text(Context ctx, int x, int y, char *text, int len,
2642 unsigned long attr, int lattr)
2643 {
2644 COLORREF fg, bg, t;
2645 int nfg, nbg, nfont;
2646 HDC hdc = ctx;
2647 RECT line_box;
2648 int force_manual_underline = 0;
2649 int fnt_width = font_width * (1 + (lattr != LATTR_NORM));
2650 int char_width = fnt_width;
2651 int text_adjust = 0;
2652 static int *IpDx = 0, IpDxLEN = 0;
2653
2654 if (attr & ATTR_WIDE)
2655 char_width *= 2;
2656
2657 if (len > IpDxLEN || IpDx[0] != char_width) {
2658 int i;
2659 if (len > IpDxLEN) {
2660 sfree(IpDx);
2661 IpDx = smalloc((len + 16) * sizeof(int));
2662 IpDxLEN = (len + 16);
2663 }
2664 for (i = 0; i < IpDxLEN; i++)
2665 IpDx[i] = char_width;
2666 }
2667
2668 /* Only want the left half of double width lines */
2669 if (lattr != LATTR_NORM && x*2 >= cols)
2670 return;
2671
2672 x *= fnt_width;
2673 y *= font_height;
2674 x += offset_width;
2675 y += offset_height;
2676
2677 if ((attr & TATTR_ACTCURS) && (cfg.cursor_type == 0 || big_cursor)) {
2678 attr &= ATTR_CUR_AND | (bold_mode != BOLD_COLOURS ? ATTR_BOLD : 0);
2679 attr ^= ATTR_CUR_XOR;
2680 }
2681
2682 nfont = 0;
2683 if (cfg.vtmode == VT_POORMAN && lattr != LATTR_NORM) {
2684 /* Assume a poorman font is borken in other ways too. */
2685 lattr = LATTR_WIDE;
2686 } else
2687 switch (lattr) {
2688 case LATTR_NORM:
2689 break;
2690 case LATTR_WIDE:
2691 nfont |= FONT_WIDE;
2692 break;
2693 default:
2694 nfont |= FONT_WIDE + FONT_HIGH;
2695 break;
2696 }
2697 if (attr & ATTR_NARROW)
2698 nfont |= FONT_NARROW;
2699
2700 /* Special hack for the VT100 linedraw glyphs. */
2701 if ((attr & CSET_MASK) == 0x2300) {
2702 if (text[0] >= (char) 0xBA && text[0] <= (char) 0xBD) {
2703 switch ((unsigned char) (text[0])) {
2704 case 0xBA:
2705 text_adjust = -2 * font_height / 5;
2706 break;
2707 case 0xBB:
2708 text_adjust = -1 * font_height / 5;
2709 break;
2710 case 0xBC:
2711 text_adjust = font_height / 5;
2712 break;
2713 case 0xBD:
2714 text_adjust = 2 * font_height / 5;
2715 break;
2716 }
2717 if (lattr == LATTR_TOP || lattr == LATTR_BOT)
2718 text_adjust *= 2;
2719 attr &= ~CSET_MASK;
2720 text[0] = (char) (unitab_xterm['q'] & CHAR_MASK);
2721 attr |= (unitab_xterm['q'] & CSET_MASK);
2722 if (attr & ATTR_UNDER) {
2723 attr &= ~ATTR_UNDER;
2724 force_manual_underline = 1;
2725 }
2726 }
2727 }
2728
2729 /* Anything left as an original character set is unprintable. */
2730 if (DIRECT_CHAR(attr)) {
2731 attr &= ~CSET_MASK;
2732 attr |= 0xFF00;
2733 memset(text, 0xFD, len);
2734 }
2735
2736 /* OEM CP */
2737 if ((attr & CSET_MASK) == ATTR_OEMCP)
2738 nfont |= FONT_OEM;
2739
2740 nfg = 2 * ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
2741 nbg = 2 * ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
2742 if (bold_mode == BOLD_FONT && (attr & ATTR_BOLD))
2743 nfont |= FONT_BOLD;
2744 if (und_mode == UND_FONT && (attr & ATTR_UNDER))
2745 nfont |= FONT_UNDERLINE;
2746 another_font(nfont);
2747 if (!fonts[nfont]) {
2748 if (nfont & FONT_UNDERLINE)
2749 force_manual_underline = 1;
2750 /* Don't do the same for manual bold, it could be bad news. */
2751
2752 nfont &= ~(FONT_BOLD | FONT_UNDERLINE);
2753 }
2754 another_font(nfont);
2755 if (!fonts[nfont])
2756 nfont = FONT_NORMAL;
2757 if (attr & ATTR_REVERSE) {
2758 t = nfg;
2759 nfg = nbg;
2760 nbg = t;
2761 }
2762 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BOLD))
2763 nfg++;
2764 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BLINK))
2765 nbg++;
2766 fg = colours[nfg];
2767 bg = colours[nbg];
2768 SelectObject(hdc, fonts[nfont]);
2769 SetTextColor(hdc, fg);
2770 SetBkColor(hdc, bg);
2771 SetBkMode(hdc, OPAQUE);
2772 line_box.left = x;
2773 line_box.top = y;
2774 line_box.right = x + char_width * len;
2775 line_box.bottom = y + font_height;
2776
2777 /* Only want the left half of double width lines */
2778 if (line_box.right > font_width*cols+offset_width)
2779 line_box.right = font_width*cols+offset_width;
2780
2781 /* We're using a private area for direct to font. (512 chars.) */
2782 if (dbcs_screenfont && (attr & CSET_MASK) == ATTR_ACP) {
2783 /* Ho Hum, dbcs fonts are a PITA! */
2784 /* To display on W9x I have to convert to UCS */
2785 static wchar_t *uni_buf = 0;
2786 static int uni_len = 0;
2787 int nlen, mptr;
2788 if (len > uni_len) {
2789 sfree(uni_buf);
2790 uni_buf = smalloc((uni_len = len) * sizeof(wchar_t));
2791 }
2792
2793 for(nlen = mptr = 0; mptr<len; mptr++) {
2794 uni_buf[nlen] = 0xFFFD;
2795 if (IsDBCSLeadByteEx(font_codepage, (BYTE) text[mptr])) {
2796 IpDx[nlen] += char_width;
2797 MultiByteToWideChar(font_codepage, MB_USEGLYPHCHARS,
2798 text+mptr, 2, uni_buf+nlen, 1);
2799 mptr++;
2800 }
2801 else
2802 {
2803 MultiByteToWideChar(font_codepage, MB_USEGLYPHCHARS,
2804 text+mptr, 1, uni_buf+nlen, 1);
2805 }
2806 nlen++;
2807 }
2808 if (nlen <= 0)
2809 return; /* Eeek! */
2810
2811 ExtTextOutW(hdc, x,
2812 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2813 ETO_CLIPPED | ETO_OPAQUE, &line_box, uni_buf, nlen, IpDx);
2814 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2815 SetBkMode(hdc, TRANSPARENT);
2816 ExtTextOutW(hdc, x - 1,
2817 y - font_height * (lattr ==
2818 LATTR_BOT) + text_adjust,
2819 ETO_CLIPPED, &line_box, uni_buf, nlen, IpDx);
2820 }
2821
2822 IpDx[0] = -1;
2823 } else if (DIRECT_FONT(attr)) {
2824 ExtTextOut(hdc, x,
2825 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2826 ETO_CLIPPED | ETO_OPAQUE, &line_box, text, len, IpDx);
2827 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2828 SetBkMode(hdc, TRANSPARENT);
2829
2830 /* GRR: This draws the character outside it's box and can leave
2831 * 'droppings' even with the clip box! I suppose I could loop it
2832 * one character at a time ... yuk.
2833 *
2834 * Or ... I could do a test print with "W", and use +1 or -1 for this
2835 * shift depending on if the leftmost column is blank...
2836 */
2837 ExtTextOut(hdc, x - 1,
2838 y - font_height * (lattr ==
2839 LATTR_BOT) + text_adjust,
2840 ETO_CLIPPED, &line_box, text, len, IpDx);
2841 }
2842 } else {
2843 /* And 'normal' unicode characters */
2844 static WCHAR *wbuf = NULL;
2845 static int wlen = 0;
2846 int i;
2847 if (wlen < len) {
2848 sfree(wbuf);
2849 wlen = len;
2850 wbuf = smalloc(wlen * sizeof(WCHAR));
2851 }
2852 for (i = 0; i < len; i++)
2853 wbuf[i] = (WCHAR) ((attr & CSET_MASK) + (text[i] & CHAR_MASK));
2854
2855 ExtTextOutW(hdc, x,
2856 y - font_height * (lattr == LATTR_BOT) + text_adjust,
2857 ETO_CLIPPED | ETO_OPAQUE, &line_box, wbuf, len, IpDx);
2858
2859 /* And the shadow bold hack. */
2860 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
2861 SetBkMode(hdc, TRANSPARENT);
2862 ExtTextOutW(hdc, x - 1,
2863 y - font_height * (lattr ==
2864 LATTR_BOT) + text_adjust,
2865 ETO_CLIPPED, &line_box, wbuf, len, IpDx);
2866 }
2867 }
2868 if (lattr != LATTR_TOP && (force_manual_underline ||
2869 (und_mode == UND_LINE
2870 && (attr & ATTR_UNDER)))) {
2871 HPEN oldpen;
2872 int dec = descent;
2873 if (lattr == LATTR_BOT)
2874 dec = dec * 2 - font_height;
2875
2876 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, fg));
2877 MoveToEx(hdc, x, y + dec, NULL);
2878 LineTo(hdc, x + len * char_width, y + dec);
2879 oldpen = SelectObject(hdc, oldpen);
2880 DeleteObject(oldpen);
2881 }
2882 }
2883
2884 void do_cursor(Context ctx, int x, int y, char *text, int len,
2885 unsigned long attr, int lattr)
2886 {
2887
2888 int fnt_width;
2889 int char_width;
2890 HDC hdc = ctx;
2891 int ctype = cfg.cursor_type;
2892
2893 if ((attr & TATTR_ACTCURS) && (ctype == 0 || big_cursor)) {
2894 if (((attr & CSET_MASK) | (unsigned char) *text) != UCSWIDE) {
2895 do_text(ctx, x, y, text, len, attr, lattr);
2896 return;
2897 }
2898 ctype = 2;
2899 attr |= TATTR_RIGHTCURS;
2900 }
2901
2902 fnt_width = char_width = font_width * (1 + (lattr != LATTR_NORM));
2903 if (attr & ATTR_WIDE)
2904 char_width *= 2;
2905 x *= fnt_width;
2906 y *= font_height;
2907 x += offset_width;
2908 y += offset_height;
2909
2910 if ((attr & TATTR_PASCURS) && (ctype == 0 || big_cursor)) {
2911 POINT pts[5];
2912 HPEN oldpen;
2913 pts[0].x = pts[1].x = pts[4].x = x;
2914 pts[2].x = pts[3].x = x + char_width - 1;
2915 pts[0].y = pts[3].y = pts[4].y = y;
2916 pts[1].y = pts[2].y = y + font_height - 1;
2917 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2918 Polyline(hdc, pts, 5);
2919 oldpen = SelectObject(hdc, oldpen);
2920 DeleteObject(oldpen);
2921 } else if ((attr & (TATTR_ACTCURS | TATTR_PASCURS)) && ctype != 0) {
2922 int startx, starty, dx, dy, length, i;
2923 if (ctype == 1) {
2924 startx = x;
2925 starty = y + descent;
2926 dx = 1;
2927 dy = 0;
2928 length = char_width;
2929 } else {
2930 int xadjust = 0;
2931 if (attr & TATTR_RIGHTCURS)
2932 xadjust = char_width - 1;
2933 startx = x + xadjust;
2934 starty = y;
2935 dx = 0;
2936 dy = 1;
2937 length = font_height;
2938 }
2939 if (attr & TATTR_ACTCURS) {
2940 HPEN oldpen;
2941 oldpen =
2942 SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[23]));
2943 MoveToEx(hdc, startx, starty, NULL);
2944 LineTo(hdc, startx + dx * length, starty + dy * length);
2945 oldpen = SelectObject(hdc, oldpen);
2946 DeleteObject(oldpen);
2947 } else {
2948 for (i = 0; i < length; i++) {
2949 if (i % 2 == 0) {
2950 SetPixel(hdc, startx, starty, colours[23]);
2951 }
2952 startx += dx;
2953 starty += dy;
2954 }
2955 }
2956 }
2957 }
2958
2959 /* This function gets the actual width of a character in the normal font.
2960 */
2961 int CharWidth(Context ctx, int uc) {
2962 HDC hdc = ctx;
2963 int ibuf = 0;
2964
2965 /* If the font max is the same as the font ave width then this
2966 * function is a no-op.
2967 */
2968 if (!font_dualwidth) return 1;
2969
2970 switch (uc & CSET_MASK) {
2971 case ATTR_ASCII:
2972 uc = unitab_line[uc & 0xFF];
2973 break;
2974 case ATTR_LINEDRW:
2975 uc = unitab_xterm[uc & 0xFF];
2976 break;
2977 case ATTR_SCOACS:
2978 uc = unitab_scoacs[uc & 0xFF];
2979 break;
2980 }
2981 if (DIRECT_FONT(uc)) {
2982 if (dbcs_screenfont) return 1;
2983
2984 /* Speedup, I know of no font where ascii is the wrong width */
2985 if ((uc&CHAR_MASK) >= ' ' && (uc&CHAR_MASK)<= '~')
2986 return 1;
2987
2988 if ( (uc & CSET_MASK) == ATTR_ACP ) {
2989 SelectObject(hdc, fonts[FONT_NORMAL]);
2990 } else if ( (uc & CSET_MASK) == ATTR_OEMCP ) {
2991 another_font(FONT_OEM);
2992 if (!fonts[FONT_OEM]) return 0;
2993
2994 SelectObject(hdc, fonts[FONT_OEM]);
2995 } else
2996 return 0;
2997
2998 if ( GetCharWidth32(hdc, uc&CHAR_MASK, uc&CHAR_MASK, &ibuf) != 1 &&
2999 GetCharWidth(hdc, uc&CHAR_MASK, uc&CHAR_MASK, &ibuf) != 1)
3000 return 0;
3001 } else {
3002 /* Speedup, I know of no font where ascii is the wrong width */
3003 if (uc >= ' ' && uc <= '~') return 1;
3004
3005 SelectObject(hdc, fonts[FONT_NORMAL]);
3006 if ( GetCharWidth32W(hdc, uc, uc, &ibuf) == 1 )
3007 /* Okay that one worked */ ;
3008 else if ( GetCharWidthW(hdc, uc, uc, &ibuf) == 1 )
3009 /* This should work on 9x too, but it's "less accurate" */ ;
3010 else
3011 return 0;
3012 }
3013
3014 ibuf += font_width / 2 -1;
3015 ibuf /= font_width;
3016
3017 return ibuf;
3018 }
3019
3020 /*
3021 * Translate a WM_(SYS)?KEY(UP|DOWN) message into a string of ASCII
3022 * codes. Returns number of bytes used or zero to drop the message
3023 * or -1 to forward the message to windows.
3024 */
3025 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
3026 unsigned char *output)
3027 {
3028 BYTE keystate[256];
3029 int scan, left_alt = 0, key_down, shift_state;
3030 int r, i, code;
3031 unsigned char *p = output;
3032 static int alt_sum = 0;
3033
3034 HKL kbd_layout = GetKeyboardLayout(0);
3035
3036 static WORD keys[3];
3037 static int compose_char = 0;
3038 static WPARAM compose_key = 0;
3039
3040 r = GetKeyboardState(keystate);
3041 if (!r)
3042 memset(keystate, 0, sizeof(keystate));
3043 else {
3044 #if 0
3045 #define SHOW_TOASCII_RESULT
3046 { /* Tell us all about key events */
3047 static BYTE oldstate[256];
3048 static int first = 1;
3049 static int scan;
3050 int ch;
3051 if (first)
3052 memcpy(oldstate, keystate, sizeof(oldstate));
3053 first = 0;
3054
3055 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT) {
3056 debug(("+"));
3057 } else if ((HIWORD(lParam) & KF_UP)
3058 && scan == (HIWORD(lParam) & 0xFF)) {
3059 debug((". U"));
3060 } else {
3061 debug((".\n"));
3062 if (wParam >= VK_F1 && wParam <= VK_F20)
3063 debug(("K_F%d", wParam + 1 - VK_F1));
3064 else
3065 switch (wParam) {
3066 case VK_SHIFT:
3067 debug(("SHIFT"));
3068 break;
3069 case VK_CONTROL:
3070 debug(("CTRL"));
3071 break;
3072 case VK_MENU:
3073 debug(("ALT"));
3074 break;
3075 default:
3076 debug(("VK_%02x", wParam));
3077 }
3078 if (message == WM_SYSKEYDOWN || message == WM_SYSKEYUP)
3079 debug(("*"));
3080 debug((", S%02x", scan = (HIWORD(lParam) & 0xFF)));
3081
3082 ch = MapVirtualKeyEx(wParam, 2, kbd_layout);
3083 if (ch >= ' ' && ch <= '~')
3084 debug((", '%c'", ch));
3085 else if (ch)
3086 debug((", $%02x", ch));
3087
3088 if (keys[0])
3089 debug((", KB0=%02x", keys[0]));
3090 if (keys[1])
3091 debug((", KB1=%02x", keys[1]));
3092 if (keys[2])
3093 debug((", KB2=%02x", keys[2]));
3094
3095 if ((keystate[VK_SHIFT] & 0x80) != 0)
3096 debug((", S"));
3097 if ((keystate[VK_CONTROL] & 0x80) != 0)
3098 debug((", C"));
3099 if ((HIWORD(lParam) & KF_EXTENDED))
3100 debug((", E"));
3101 if ((HIWORD(lParam) & KF_UP))
3102 debug((", U"));
3103 }
3104
3105 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT);
3106 else if ((HIWORD(lParam) & KF_UP))
3107 oldstate[wParam & 0xFF] ^= 0x80;
3108 else
3109 oldstate[wParam & 0xFF] ^= 0x81;
3110
3111 for (ch = 0; ch < 256; ch++)
3112 if (oldstate[ch] != keystate[ch])
3113 debug((", M%02x=%02x", ch, keystate[ch]));
3114
3115 memcpy(oldstate, keystate, sizeof(oldstate));
3116 }
3117 #endif
3118
3119 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED)) {
3120 keystate[VK_RMENU] = keystate[VK_MENU];
3121 }
3122
3123
3124 /* Nastyness with NUMLock - Shift-NUMLock is left alone though */
3125 if ((cfg.funky_type == 3 ||
3126 (cfg.funky_type <= 1 && app_keypad_keys && !cfg.no_applic_k))
3127 && wParam == VK_NUMLOCK && !(keystate[VK_SHIFT] & 0x80)) {
3128
3129 wParam = VK_EXECUTE;
3130
3131 /* UnToggle NUMLock */
3132 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0)
3133 keystate[VK_NUMLOCK] ^= 1;
3134 }
3135
3136 /* And write back the 'adjusted' state */
3137 SetKeyboardState(keystate);
3138 }
3139
3140 /* Disable Auto repeat if required */
3141 if (repeat_off && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT)
3142 return 0;
3143
3144 if ((HIWORD(lParam) & KF_ALTDOWN) && (keystate[VK_RMENU] & 0x80) == 0)
3145 left_alt = 1;
3146
3147 key_down = ((HIWORD(lParam) & KF_UP) == 0);
3148
3149 /* Make sure Ctrl-ALT is not the same as AltGr for ToAscii unless told. */
3150 if (left_alt && (keystate[VK_CONTROL] & 0x80)) {
3151 if (cfg.ctrlaltkeys)
3152 keystate[VK_MENU] = 0;
3153 else {
3154 keystate[VK_RMENU] = 0x80;
3155 left_alt = 0;
3156 }
3157 }
3158
3159 alt_pressed = (left_alt && key_down);
3160
3161 scan = (HIWORD(lParam) & (KF_UP | KF_EXTENDED | 0xFF));
3162 shift_state = ((keystate[VK_SHIFT] & 0x80) != 0)
3163 + ((keystate[VK_CONTROL] & 0x80) != 0) * 2;
3164
3165 /* Note if AltGr was pressed and if it was used as a compose key */
3166 if (!compose_state) {
3167 compose_key = 0x100;
3168 if (cfg.compose_key) {
3169 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED))
3170 compose_key = wParam;
3171 }
3172 if (wParam == VK_APPS)
3173 compose_key = wParam;
3174 }
3175
3176 if (wParam == compose_key) {
3177 if (compose_state == 0
3178 && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0) compose_state =
3179 1;
3180 else if (compose_state == 1 && (HIWORD(lParam) & KF_UP))
3181 compose_state = 2;
3182 else
3183 compose_state = 0;
3184 } else if (compose_state == 1 && wParam != VK_CONTROL)
3185 compose_state = 0;
3186
3187 /*
3188 * Record that we pressed key so the scroll window can be reset, but
3189 * be careful to avoid Shift-UP/Down
3190 */
3191 if (wParam != VK_SHIFT && wParam != VK_PRIOR && wParam != VK_NEXT &&
3192 wParam != VK_MENU && wParam != VK_CONTROL) {
3193 seen_key_event = 1;
3194 }
3195
3196 if (compose_state > 1 && left_alt)
3197 compose_state = 0;
3198
3199 /* Sanitize the number pad if not using a PC NumPad */
3200 if (left_alt || (app_keypad_keys && !cfg.no_applic_k
3201 && cfg.funky_type != 2)
3202 || cfg.funky_type == 3 || cfg.nethack_keypad || compose_state) {
3203 if ((HIWORD(lParam) & KF_EXTENDED) == 0) {
3204 int nParam = 0;
3205 switch (wParam) {
3206 case VK_INSERT:
3207 nParam = VK_NUMPAD0;
3208 break;
3209 case VK_END:
3210 nParam = VK_NUMPAD1;
3211 break;
3212 case VK_DOWN:
3213 nParam = VK_NUMPAD2;
3214 break;
3215 case VK_NEXT:
3216 nParam = VK_NUMPAD3;
3217 break;
3218 case VK_LEFT:
3219 nParam = VK_NUMPAD4;
3220 break;
3221 case VK_CLEAR:
3222 nParam = VK_NUMPAD5;
3223 break;
3224 case VK_RIGHT:
3225 nParam = VK_NUMPAD6;
3226 break;
3227 case VK_HOME:
3228 nParam = VK_NUMPAD7;
3229 break;
3230 case VK_UP:
3231 nParam = VK_NUMPAD8;
3232 break;
3233 case VK_PRIOR:
3234 nParam = VK_NUMPAD9;
3235 break;
3236 case VK_DELETE:
3237 nParam = VK_DECIMAL;
3238 break;
3239 }
3240 if (nParam) {
3241 if (keystate[VK_NUMLOCK] & 1)
3242 shift_state |= 1;
3243 wParam = nParam;
3244 }
3245 }
3246 }
3247
3248 /* If a key is pressed and AltGr is not active */
3249 if (key_down && (keystate[VK_RMENU] & 0x80) == 0 && !compose_state) {
3250 /* Okay, prepare for most alts then ... */
3251 if (left_alt)
3252 *p++ = '\033';
3253
3254 /* Lets see if it's a pattern we know all about ... */
3255 if (wParam == VK_PRIOR && shift_state == 1) {
3256 SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0);
3257 return 0;
3258 }
3259 if (wParam == VK_NEXT && shift_state == 1) {
3260 SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
3261 return 0;
3262 }
3263 if (wParam == VK_INSERT && shift_state == 1) {
3264 term_do_paste();
3265 return 0;
3266 }
3267 if (left_alt && wParam == VK_F4 && cfg.alt_f4) {
3268 return -1;
3269 }
3270 if (left_alt && wParam == VK_SPACE && cfg.alt_space) {
3271 SendMessage(hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0);
3272 return -1;
3273 }
3274 if (left_alt && wParam == VK_RETURN && cfg.fullscreenonaltenter &&
3275 (cfg.resize_action != RESIZE_DISABLED)) {
3276 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) != KF_REPEAT)
3277 flip_full_screen();
3278 return -1;
3279 }
3280 /* Control-Numlock for app-keypad mode switch */
3281 if (wParam == VK_PAUSE && shift_state == 2) {
3282 app_keypad_keys ^= 1;
3283 return 0;
3284 }
3285
3286 /* Nethack keypad */
3287 if (cfg.nethack_keypad && !left_alt) {
3288 switch (wParam) {
3289 case VK_NUMPAD1:
3290 *p++ = shift_state ? 'B' : 'b';
3291 return p - output;
3292 case VK_NUMPAD2:
3293 *p++ = shift_state ? 'J' : 'j';
3294 return p - output;
3295 case VK_NUMPAD3:
3296 *p++ = shift_state ? 'N' : 'n';
3297 return p - output;
3298 case VK_NUMPAD4:
3299 *p++ = shift_state ? 'H' : 'h';
3300 return p - output;
3301 case VK_NUMPAD5:
3302 *p++ = shift_state ? '.' : '.';
3303 return p - output;
3304 case VK_NUMPAD6:
3305 *p++ = shift_state ? 'L' : 'l';
3306 return p - output;
3307 case VK_NUMPAD7:
3308 *p++ = shift_state ? 'Y' : 'y';
3309 return p - output;
3310 case VK_NUMPAD8:
3311 *p++ = shift_state ? 'K' : 'k';
3312 return p - output;
3313 case VK_NUMPAD9:
3314 *p++ = shift_state ? 'U' : 'u';
3315 return p - output;
3316 }
3317 }
3318
3319 /* Application Keypad */
3320 if (!left_alt) {
3321 int xkey = 0;
3322
3323 if (cfg.funky_type == 3 ||
3324 (cfg.funky_type <= 1 &&
3325 app_keypad_keys && !cfg.no_applic_k)) switch (wParam) {
3326 case VK_EXECUTE:
3327 xkey = 'P';
3328 break;
3329 case VK_DIVIDE:
3330 xkey = 'Q';
3331 break;
3332 case VK_MULTIPLY:
3333 xkey = 'R';
3334 break;
3335 case VK_SUBTRACT:
3336 xkey = 'S';
3337 break;
3338 }
3339 if (app_keypad_keys && !cfg.no_applic_k)
3340 switch (wParam) {
3341 case VK_NUMPAD0:
3342 xkey = 'p';
3343 break;
3344 case VK_NUMPAD1:
3345 xkey = 'q';
3346 break;
3347 case VK_NUMPAD2:
3348 xkey = 'r';
3349 break;
3350 case VK_NUMPAD3:
3351 xkey = 's';
3352 break;
3353 case VK_NUMPAD4:
3354 xkey = 't';
3355 break;
3356 case VK_NUMPAD5:
3357 xkey = 'u';
3358 break;
3359 case VK_NUMPAD6:
3360 xkey = 'v';
3361 break;
3362 case VK_NUMPAD7:
3363 xkey = 'w';
3364 break;
3365 case VK_NUMPAD8:
3366 xkey = 'x';
3367 break;
3368 case VK_NUMPAD9:
3369 xkey = 'y';
3370 break;
3371
3372 case VK_DECIMAL:
3373 xkey = 'n';
3374 break;
3375 case VK_ADD:
3376 if (cfg.funky_type == 2) {
3377 if (shift_state)
3378 xkey = 'l';
3379 else
3380 xkey = 'k';
3381 } else if (shift_state)
3382 xkey = 'm';
3383 else
3384 xkey = 'l';
3385 break;
3386
3387 case VK_DIVIDE:
3388 if (cfg.funky_type == 2)
3389 xkey = 'o';
3390 break;
3391 case VK_MULTIPLY:
3392 if (cfg.funky_type == 2)
3393 xkey = 'j';
3394 break;
3395 case VK_SUBTRACT:
3396 if (cfg.funky_type == 2)
3397 xkey = 'm';
3398 break;
3399
3400 case VK_RETURN:
3401 if (HIWORD(lParam) & KF_EXTENDED)
3402 xkey = 'M';
3403 break;
3404 }
3405 if (xkey) {
3406 if (vt52_mode) {
3407 if (xkey >= 'P' && xkey <= 'S')
3408 p += sprintf((char *) p, "\x1B%c", xkey);
3409 else
3410 p += sprintf((char *) p, "\x1B?%c", xkey);
3411 } else
3412 p += sprintf((char *) p, "\x1BO%c", xkey);
3413 return p - output;
3414 }
3415 }
3416
3417 if (wParam == VK_BACK && shift_state == 0) { /* Backspace */
3418 *p++ = (cfg.bksp_is_delete ? 0x7F : 0x08);
3419 *p++ = 0;
3420 return -2;
3421 }
3422 if (wParam == VK_BACK && shift_state == 1) { /* Shift Backspace */
3423 /* We do the opposite of what is configured */
3424 *p++ = (cfg.bksp_is_delete ? 0x08 : 0x7F);
3425 *p++ = 0;
3426 return -2;
3427 }
3428 if (wParam == VK_TAB && shift_state == 1) { /* Shift tab */
3429 *p++ = 0x1B;
3430 *p++ = '[';
3431 *p++ = 'Z';
3432 return p - output;
3433 }
3434 if (wParam == VK_SPACE && shift_state == 2) { /* Ctrl-Space */
3435 *p++ = 0;
3436 return p - output;
3437 }
3438 if (wParam == VK_SPACE && shift_state == 3) { /* Ctrl-Shift-Space */
3439 *p++ = 160;
3440 return p - output;
3441 }
3442 if (wParam == VK_CANCEL && shift_state == 2) { /* Ctrl-Break */
3443 *p++ = 3;
3444 *p++ = 0;
3445 return -2;
3446 }
3447 if (wParam == VK_PAUSE) { /* Break/Pause */
3448 *p++ = 26;
3449 *p++ = 0;
3450 return -2;
3451 }
3452 /* Control-2 to Control-8 are special */
3453 if (shift_state == 2 && wParam >= '2' && wParam <= '8') {
3454 *p++ = "\000\033\034\035\036\037\177"[wParam - '2'];
3455 return p - output;
3456 }
3457 if (shift_state == 2 && wParam == 0xBD) {
3458 *p++ = 0x1F;
3459 return p - output;
3460 }
3461 if (shift_state == 2 && wParam == 0xDF) {
3462 *p++ = 0x1C;
3463 return p - output;
3464 }
3465 if (shift_state == 0 && wParam == VK_RETURN && cr_lf_return) {
3466 *p++ = '\r';
3467 *p++ = '\n';
3468 return p - output;
3469 }
3470
3471 /*
3472 * Next, all the keys that do tilde codes. (ESC '[' nn '~',
3473 * for integer decimal nn.)
3474 *
3475 * We also deal with the weird ones here. Linux VCs replace F1
3476 * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
3477 * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
3478 * respectively.
3479 */
3480 code = 0;
3481 switch (wParam) {
3482 case VK_F1:
3483 code = (keystate[VK_SHIFT] & 0x80 ? 23 : 11);
3484 break;
3485 case VK_F2:
3486 code = (keystate[VK_SHIFT] & 0x80 ? 24 : 12);
3487 break;
3488 case VK_F3:
3489 code = (keystate[VK_SHIFT] & 0x80 ? 25 : 13);
3490 break;
3491 case VK_F4:
3492 code = (keystate[VK_SHIFT] & 0x80 ? 26 : 14);
3493 break;
3494 case VK_F5:
3495 code = (keystate[VK_SHIFT] & 0x80 ? 28 : 15);
3496 break;
3497 case VK_F6:
3498 code = (keystate[VK_SHIFT] & 0x80 ? 29 : 17);
3499 break;
3500 case VK_F7:
3501 code = (keystate[VK_SHIFT] & 0x80 ? 31 : 18);
3502 break;
3503 case VK_F8:
3504 code = (keystate[VK_SHIFT] & 0x80 ? 32 : 19);
3505 break;
3506 case VK_F9:
3507 code = (keystate[VK_SHIFT] & 0x80 ? 33 : 20);
3508 break;
3509 case VK_F10:
3510 code = (keystate[VK_SHIFT] & 0x80 ? 34 : 21);
3511 break;
3512 case VK_F11:
3513 code = 23;
3514 break;
3515 case VK_F12:
3516 code = 24;
3517 break;
3518 case VK_F13:
3519 code = 25;
3520 break;
3521 case VK_F14:
3522 code = 26;
3523 break;
3524 case VK_F15:
3525 code = 28;
3526 break;
3527 case VK_F16:
3528 code = 29;
3529 break;
3530 case VK_F17:
3531 code = 31;
3532 break;
3533 case VK_F18:
3534 code = 32;
3535 break;
3536 case VK_F19:
3537 code = 33;
3538 break;
3539 case VK_F20:
3540 code = 34;
3541 break;
3542 }
3543 if ((shift_state&2) == 0) switch (wParam) {
3544 case VK_HOME:
3545 code = 1;
3546 break;
3547 case VK_INSERT:
3548 code = 2;
3549 break;
3550 case VK_DELETE:
3551 code = 3;
3552 break;
3553 case VK_END:
3554 code = 4;
3555 break;
3556 case VK_PRIOR:
3557 code = 5;
3558 break;
3559 case VK_NEXT:
3560 code = 6;
3561 break;
3562 }
3563 /* Reorder edit keys to physical order */
3564 if (cfg.funky_type == 3 && code <= 6)
3565 code = "\0\2\1\4\5\3\6"[code];
3566
3567 if (vt52_mode && code > 0 && code <= 6) {
3568 p += sprintf((char *) p, "\x1B%c", " HLMEIG"[code]);
3569 return p - output;
3570 }
3571
3572 if (cfg.funky_type == 5 && /* SCO function keys */
3573 code >= 11 && code <= 34) {
3574 char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
3575 int index = 0;
3576 switch (wParam) {
3577 case VK_F1: index = 0; break;
3578 case VK_F2: index = 1; break;
3579 case VK_F3: index = 2; break;
3580 case VK_F4: index = 3; break;
3581 case VK_F5: index = 4; break;
3582 case VK_F6: index = 5; break;
3583 case VK_F7: index = 6; break;
3584 case VK_F8: index = 7; break;
3585 case VK_F9: index = 8; break;
3586 case VK_F10: index = 9; break;
3587 case VK_F11: index = 10; break;
3588 case VK_F12: index = 11; break;
3589 }
3590 if (keystate[VK_SHIFT] & 0x80) index += 12;
3591 if (keystate[VK_CONTROL] & 0x80) index += 24;
3592 p += sprintf((char *) p, "\x1B[%c", codes[index]);
3593 return p - output;
3594 }
3595 if (cfg.funky_type == 5 && /* SCO small keypad */
3596 code >= 1 && code <= 6) {
3597 char codes[] = "HL.FIG";
3598 if (code == 3) {
3599 *p++ = '\x7F';
3600 } else {
3601 p += sprintf((char *) p, "\x1B[%c", codes[code-1]);
3602 }
3603 return p - output;
3604 }
3605 if ((vt52_mode || cfg.funky_type == 4) && code >= 11 && code <= 24) {
3606 int offt = 0;
3607 if (code > 15)
3608 offt++;
3609 if (code > 21)
3610 offt++;
3611 if (vt52_mode)
3612 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11 - offt);
3613 else
3614 p +=
3615 sprintf((char *) p, "\x1BO%c", code + 'P' - 11 - offt);
3616 return p - output;
3617 }
3618 if (cfg.funky_type == 1 && code >= 11 && code <= 15) {
3619 p += sprintf((char *) p, "\x1B[[%c", code + 'A' - 11);
3620 return p - output;
3621 }
3622 if (cfg.funky_type == 2 && code >= 11 && code <= 14) {
3623 if (vt52_mode)
3624 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11);
3625 else
3626 p += sprintf((char *) p, "\x1BO%c", code + 'P' - 11);
3627 return p - output;
3628 }
3629 if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
3630 p += sprintf((char *) p, code == 1 ? "\x1B[H" : "\x1BOw");
3631 return p - output;
3632 }
3633 if (code) {
3634 p += sprintf((char *) p, "\x1B[%d~", code);
3635 return p - output;
3636 }
3637
3638 /*
3639 * Now the remaining keys (arrows and Keypad 5. Keypad 5 for
3640 * some reason seems to send VK_CLEAR to Windows...).
3641 */
3642 {
3643 char xkey = 0;
3644 switch (wParam) {
3645 case VK_UP:
3646 xkey = 'A';
3647 break;
3648 case VK_DOWN:
3649 xkey = 'B';
3650 break;
3651 case VK_RIGHT:
3652 xkey = 'C';
3653 break;
3654 case VK_LEFT:
3655 xkey = 'D';
3656 break;
3657 case VK_CLEAR:
3658 xkey = 'G';
3659 break;
3660 }
3661 if (xkey) {
3662 if (vt52_mode)
3663 p += sprintf((char *) p, "\x1B%c", xkey);
3664 else {
3665 int app_flg = (app_cursor_keys && !cfg.no_applic_c);
3666 #if 0
3667 /*
3668 * RDB: VT100 & VT102 manuals both state the
3669 * app cursor keys only work if the app keypad
3670 * is on.
3671 *
3672 * SGT: That may well be true, but xterm
3673 * disagrees and so does at least one
3674 * application, so I've #if'ed this out and the
3675 * behaviour is back to PuTTY's original: app
3676 * cursor and app keypad are independently
3677 * switchable modes. If anyone complains about
3678 * _this_ I'll have to put in a configurable
3679 * option.
3680 */
3681 if (!app_keypad_keys)
3682 app_flg = 0;
3683 #endif
3684 /* Useful mapping of Ctrl-arrows */
3685 if (shift_state == 2)
3686 app_flg = !app_flg;
3687
3688 if (app_flg)
3689 p += sprintf((char *) p, "\x1BO%c", xkey);
3690 else
3691 p += sprintf((char *) p, "\x1B[%c", xkey);
3692 }
3693 return p - output;
3694 }
3695 }
3696
3697 /*
3698 * Finally, deal with Return ourselves. (Win95 seems to
3699 * foul it up when Alt is pressed, for some reason.)
3700 */
3701 if (wParam == VK_RETURN) { /* Return */
3702 *p++ = 0x0D;
3703 *p++ = 0;
3704 return -2;
3705 }
3706
3707 if (left_alt && wParam >= VK_NUMPAD0 && wParam <= VK_NUMPAD9)
3708 alt_sum = alt_sum * 10 + wParam - VK_NUMPAD0;
3709 else
3710 alt_sum = 0;
3711 }
3712
3713 /* Okay we've done everything interesting; let windows deal with
3714 * the boring stuff */
3715 {
3716 BOOL capsOn=0;
3717
3718 /* helg: clear CAPS LOCK state if caps lock switches to cyrillic */
3719 if(cfg.xlat_capslockcyr && keystate[VK_CAPITAL] != 0) {
3720 capsOn= !left_alt;
3721 keystate[VK_CAPITAL] = 0;
3722 }
3723
3724 r = ToAsciiEx(wParam, scan, keystate, keys, 0, kbd_layout);
3725 #ifdef SHOW_TOASCII_RESULT
3726 if (r == 1 && !key_down) {
3727 if (alt_sum) {
3728 if (in_utf || dbcs_screenfont)
3729 debug((", (U+%04x)", alt_sum));
3730 else
3731 debug((", LCH(%d)", alt_sum));
3732 } else {
3733 debug((", ACH(%d)", keys[0]));
3734 }
3735 } else if (r > 0) {
3736 int r1;
3737 debug((", ASC("));
3738 for (r1 = 0; r1 < r; r1++) {
3739 debug(("%s%d", r1 ? "," : "", keys[r1]));
3740 }
3741 debug((")"));
3742 }
3743 #endif
3744 if (r > 0) {
3745 WCHAR keybuf;
3746
3747 /*
3748 * Interrupt an ongoing paste. I'm not sure this is
3749 * sensible, but for the moment it's preferable to
3750 * having to faff about buffering things.
3751 */
3752 term_nopaste();
3753
3754 p = output;
3755 for (i = 0; i < r; i++) {
3756 unsigned char ch = (unsigned char) keys[i];
3757
3758 if (compose_state == 2 && (ch & 0x80) == 0 && ch > ' ') {
3759 compose_char = ch;
3760 compose_state++;
3761 continue;
3762 }
3763 if (compose_state == 3 && (ch & 0x80) == 0 && ch > ' ') {
3764 int nc;
3765 compose_state = 0;
3766
3767 if ((nc = check_compose(compose_char, ch)) == -1) {
3768 MessageBeep(MB_ICONHAND);
3769 return 0;
3770 }
3771 keybuf = nc;
3772 luni_send(&keybuf, 1, 1);
3773 continue;
3774 }
3775
3776 compose_state = 0;
3777
3778 if (!key_down) {
3779 if (alt_sum) {
3780 if (in_utf || dbcs_screenfont) {
3781 keybuf = alt_sum;
3782 luni_send(&keybuf, 1, 1);
3783 } else {
3784 ch = (char) alt_sum;
3785 /*
3786 * We need not bother about stdin
3787 * backlogs here, because in GUI PuTTY
3788 * we can't do anything about it
3789 * anyway; there's no means of asking
3790 * Windows to hold off on KEYDOWN
3791 * messages. We _have_ to buffer
3792 * everything we're sent.
3793 */
3794 ldisc_send(&ch, 1, 1);
3795 }
3796 alt_sum = 0;
3797 } else
3798 lpage_send(kbd_codepage, &ch, 1, 1);
3799 } else {
3800 if(capsOn && ch < 0x80) {
3801 WCHAR cbuf[2];
3802 cbuf[0] = 27;
3803 cbuf[1] = xlat_uskbd2cyrllic(ch);
3804 luni_send(cbuf+!left_alt, 1+!!left_alt, 1);
3805 } else {
3806 char cbuf[2];
3807 cbuf[0] = '\033';
3808 cbuf[1] = ch;
3809 lpage_send(kbd_codepage, cbuf+!left_alt, 1+!!left_alt, 1);
3810 }
3811 }
3812 show_mouseptr(0);
3813 }
3814
3815 /* This is so the ALT-Numpad and dead keys work correctly. */
3816 keys[0] = 0;
3817
3818 return p - output;
3819 }
3820 /* If we're definitly not building up an ALT-54321 then clear it */
3821 if (!left_alt)
3822 keys[0] = 0;
3823 /* If we will be using alt_sum fix the 256s */
3824 else if (keys[0] && (in_utf || dbcs_screenfont))
3825 keys[0] = 10;
3826 }
3827
3828 /*
3829 * ALT alone may or may not want to bring up the System menu.
3830 * If it's not meant to, we return 0 on presses or releases of
3831 * ALT, to show that we've swallowed the keystroke. Otherwise
3832 * we return -1, which means Windows will give the keystroke
3833 * its default handling (i.e. bring up the System menu).
3834 */
3835 if (wParam == VK_MENU && !cfg.alt_only)
3836 return 0;
3837
3838 return -1;
3839 }
3840
3841 void request_paste(void)
3842 {
3843 /*
3844 * In Windows, pasting is synchronous: we can read the
3845 * clipboard with no difficulty, so request_paste() can just go
3846 * ahead and paste.
3847 */
3848 term_do_paste();
3849 }
3850
3851 void set_title(char *title)
3852 {
3853 sfree(window_name);
3854 window_name = smalloc(1 + strlen(title));
3855 strcpy(window_name, title);
3856 if (cfg.win_name_always || !IsIconic(hwnd))
3857 SetWindowText(hwnd, title);
3858 }
3859
3860 void set_icon(char *title)
3861 {
3862 sfree(icon_name);
3863 icon_name = smalloc(1 + strlen(title));
3864 strcpy(icon_name, title);
3865 if (!cfg.win_name_always && IsIconic(hwnd))
3866 SetWindowText(hwnd, title);
3867 }
3868
3869 void set_sbar(int total, int start, int page)
3870 {
3871 SCROLLINFO si;
3872
3873 if (is_full_screen() ? !cfg.scrollbar_in_fullscreen : !cfg.scrollbar)
3874 return;
3875
3876 si.cbSize = sizeof(si);
3877 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
3878 si.nMin = 0;
3879 si.nMax = total - 1;
3880 si.nPage = page;
3881 si.nPos = start;
3882 if (hwnd)
3883 SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
3884 }
3885
3886 Context get_ctx(void)
3887 {
3888 HDC hdc;
3889 if (hwnd) {
3890 hdc = GetDC(hwnd);
3891 if (hdc && pal)
3892 SelectPalette(hdc, pal, FALSE);
3893 return hdc;
3894 } else
3895 return NULL;
3896 }
3897
3898 void free_ctx(Context ctx)
3899 {
3900 SelectPalette(ctx, GetStockObject(DEFAULT_PALETTE), FALSE);
3901 ReleaseDC(hwnd, ctx);
3902 }
3903
3904 static void real_palette_set(int n, int r, int g, int b)
3905 {
3906 if (pal) {
3907 logpal->palPalEntry[n].peRed = r;
3908 logpal->palPalEntry[n].peGreen = g;
3909 logpal->palPalEntry[n].peBlue = b;
3910 logpal->palPalEntry[n].peFlags = PC_NOCOLLAPSE;
3911 colours[n] = PALETTERGB(r, g, b);
3912 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3913 } else
3914 colours[n] = RGB(r, g, b);
3915 }
3916
3917 void palette_set(int n, int r, int g, int b)
3918 {
3919 static const int first[21] = {
3920 0, 2, 4, 6, 8, 10, 12, 14,
3921 1, 3, 5, 7, 9, 11, 13, 15,
3922 16, 17, 18, 20, 22
3923 };
3924 real_palette_set(first[n], r, g, b);
3925 if (first[n] >= 18)
3926 real_palette_set(first[n] + 1, r, g, b);
3927 if (pal) {
3928 HDC hdc = get_ctx();
3929 UnrealizeObject(pal);
3930 RealizePalette(hdc);
3931 free_ctx(hdc);
3932 }
3933 }
3934
3935 void palette_reset(void)
3936 {
3937 int i;
3938
3939 for (i = 0; i < NCOLOURS; i++) {
3940 if (pal) {
3941 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
3942 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
3943 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
3944 logpal->palPalEntry[i].peFlags = 0;
3945 colours[i] = PALETTERGB(defpal[i].rgbtRed,
3946 defpal[i].rgbtGreen,
3947 defpal[i].rgbtBlue);
3948 } else
3949 colours[i] = RGB(defpal[i].rgbtRed,
3950 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
3951 }
3952
3953 if (pal) {
3954 HDC hdc;
3955 SetPaletteEntries(pal, 0, NCOLOURS, logpal->palPalEntry);
3956 hdc = get_ctx();
3957 RealizePalette(hdc);
3958 free_ctx(hdc);
3959 }
3960 }
3961
3962 void write_aclip(char *data, int len, int must_deselect)
3963 {
3964 HGLOBAL clipdata;
3965 void *lock;
3966
3967 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len + 1);
3968 if (!clipdata)
3969 return;
3970 lock = GlobalLock(clipdata);
3971 if (!lock)
3972 return;
3973 memcpy(lock, data, len);
3974 ((unsigned char *) lock)[len] = 0;
3975 GlobalUnlock(clipdata);
3976
3977 if (!must_deselect)
3978 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
3979
3980 if (OpenClipboard(hwnd)) {
3981 EmptyClipboard();
3982 SetClipboardData(CF_TEXT, clipdata);
3983 CloseClipboard();
3984 } else
3985 GlobalFree(clipdata);
3986
3987 if (!must_deselect)
3988 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
3989 }
3990
3991 /*
3992 * Note: unlike write_aclip() this will not append a nul.
3993 */
3994 void write_clip(wchar_t * data, int len, int must_deselect)
3995 {
3996 HGLOBAL clipdata, clipdata2, clipdata3;
3997 int len2;
3998 void *lock, *lock2, *lock3;
3999
4000 len2 = WideCharToMultiByte(CP_ACP, 0, data, len, 0, 0, NULL, NULL);
4001
4002 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE,
4003 len * sizeof(wchar_t));
4004 clipdata2 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len2);
4005
4006 if (!clipdata || !clipdata2) {
4007 if (clipdata)
4008 GlobalFree(clipdata);
4009 if (clipdata2)
4010 GlobalFree(clipdata2);
4011 return;
4012 }
4013 if (!(lock = GlobalLock(clipdata)))
4014 return;
4015 if (!(lock2 = GlobalLock(clipdata2)))
4016 return;
4017
4018 memcpy(lock, data, len * sizeof(wchar_t));
4019 WideCharToMultiByte(CP_ACP, 0, data, len, lock2, len2, NULL, NULL);
4020
4021 if (cfg.rtf_paste) {
4022 wchar_t unitab[256];
4023 char *rtf = NULL;
4024 unsigned char *tdata = (unsigned char *)lock2;
4025 wchar_t *udata = (wchar_t *)lock;
4026 int rtflen = 0, uindex = 0, tindex = 0;
4027 int rtfsize = 0;
4028 int multilen, blen, alen, totallen, i;
4029 char before[16], after[4];
4030
4031 get_unitab(CP_ACP, unitab, 0);
4032
4033 rtfsize = 100 + strlen(cfg.font);
4034 rtf = smalloc(rtfsize);
4035 sprintf(rtf, "{\\rtf1\\ansi%d{\\fonttbl\\f0\\fmodern %s;}\\f0",
4036 GetACP(), cfg.font);
4037 rtflen = strlen(rtf);
4038
4039 /*
4040 * We want to construct a piece of RTF that specifies the
4041 * same Unicode text. To do this we will read back in
4042 * parallel from the Unicode data in `udata' and the
4043 * non-Unicode data in `tdata'. For each character in
4044 * `tdata' which becomes the right thing in `udata' when
4045 * looked up in `unitab', we just copy straight over from
4046 * tdata. For each one that doesn't, we must WCToMB it
4047 * individually and produce a \u escape sequence.
4048 *
4049 * It would probably be more robust to just bite the bullet
4050 * and WCToMB each individual Unicode character one by one,
4051 * then MBToWC each one back to see if it was an accurate
4052 * translation; but that strikes me as a horrifying number
4053 * of Windows API calls so I want to see if this faster way
4054 * will work. If it screws up badly we can always revert to
4055 * the simple and slow way.
4056 */
4057 while (tindex < len2 && uindex < len &&
4058 tdata[tindex] && udata[uindex]) {
4059 if (tindex + 1 < len2 &&
4060 tdata[tindex] == '\r' &&
4061 tdata[tindex+1] == '\n') {
4062 tindex++;
4063 uindex++;
4064 }
4065 if (unitab[tdata[tindex]] == udata[uindex]) {
4066 multilen = 1;
4067 before[0] = '\0';
4068 after[0] = '\0';
4069 blen = alen = 0;
4070 } else {
4071 multilen = WideCharToMultiByte(CP_ACP, 0, unitab+uindex, 1,
4072 NULL, 0, NULL, NULL);
4073 if (multilen != 1) {
4074 blen = sprintf(before, "{\\uc%d\\u%d", multilen,
4075 udata[uindex]);
4076 alen = 1; strcpy(after, "}");
4077 } else {
4078 blen = sprintf(before, "\\u%d", udata[uindex]);
4079 alen = 0; after[0] = '\0';
4080 }
4081 }
4082 assert(tindex + multilen <= len2);
4083 totallen = blen + alen;
4084 for (i = 0; i < multilen; i++) {
4085 if (tdata[tindex+i] == '\\' ||
4086 tdata[tindex+i] == '{' ||
4087 tdata[tindex+i] == '}')
4088 totallen += 2;
4089 else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A)
4090 totallen += 6; /* \par\r\n */
4091 else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20)
4092 totallen += 4;
4093 else
4094 totallen++;
4095 }
4096
4097 if (rtfsize < rtflen + totallen + 3) {
4098 rtfsize = rtflen + totallen + 512;
4099 rtf = srealloc(rtf, rtfsize);
4100 }
4101
4102 strcpy(rtf + rtflen, before); rtflen += blen;
4103 for (i = 0; i < multilen; i++) {
4104 if (tdata[tindex+i] == '\\' ||
4105 tdata[tindex+i] == '{' ||
4106 tdata[tindex+i] == '}') {
4107 rtf[rtflen++] = '\\';
4108 rtf[rtflen++] = tdata[tindex+i];
4109 } else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A) {
4110 rtflen += sprintf(rtf+rtflen, "\\par\r\n");
4111 } else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20) {
4112 rtflen += sprintf(rtf+rtflen, "\\'%02x", tdata[tindex+i]);
4113 } else {
4114 rtf[rtflen++] = tdata[tindex+i];
4115 }
4116 }
4117 strcpy(rtf + rtflen, after); rtflen += alen;
4118
4119 tindex += multilen;
4120 uindex++;
4121 }
4122
4123 strcpy(rtf + rtflen, "}");
4124 rtflen += 2;
4125
4126 clipdata3 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, rtflen);
4127 if (clipdata3 && (lock3 = GlobalLock(clipdata3)) != NULL) {
4128 strcpy(lock3, rtf);
4129 GlobalUnlock(clipdata3);
4130 }
4131 sfree(rtf);
4132 } else
4133 clipdata3 = NULL;
4134
4135 GlobalUnlock(clipdata);
4136 GlobalUnlock(clipdata2);
4137
4138 if (!must_deselect)
4139 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
4140
4141 if (OpenClipboard(hwnd)) {
4142 EmptyClipboard();
4143 SetClipboardData(CF_UNICODETEXT, clipdata);
4144 SetClipboardData(CF_TEXT, clipdata2);
4145 if (clipdata3)
4146 SetClipboardData(RegisterClipboardFormat(CF_RTF), clipdata3);
4147 CloseClipboard();
4148 } else {
4149 GlobalFree(clipdata);
4150 GlobalFree(clipdata2);
4151 }
4152
4153 if (!must_deselect)
4154 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
4155 }
4156
4157 void get_clip(wchar_t ** p, int *len)
4158 {
4159 static HGLOBAL clipdata = NULL;
4160 static wchar_t *converted = 0;
4161 wchar_t *p2;
4162
4163 if (converted) {
4164 sfree(converted);
4165 converted = 0;
4166 }
4167 if (!p) {
4168 if (clipdata)
4169 GlobalUnlock(clipdata);
4170 clipdata = NULL;
4171 return;
4172 } else if (OpenClipboard(NULL)) {
4173 if ((clipdata = GetClipboardData(CF_UNICODETEXT))) {
4174 CloseClipboard();
4175 *p = GlobalLock(clipdata);
4176 if (*p) {
4177 for (p2 = *p; *p2; p2++);
4178 *len = p2 - *p;
4179 return;
4180 }
4181 } else if ( (clipdata = GetClipboardData(CF_TEXT)) ) {
4182 char *s;
4183 int i;
4184 CloseClipboard();
4185 s = GlobalLock(clipdata);
4186 i = MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, 0, 0);
4187 *p = converted = smalloc(i * sizeof(wchar_t));
4188 MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, converted, i);
4189 *len = i - 1;
4190 return;
4191 } else
4192 CloseClipboard();
4193 }
4194
4195 *p = NULL;
4196 *len = 0;
4197 }
4198
4199 #if 0
4200 /*
4201 * Move `lines' lines from position `from' to position `to' in the
4202 * window.
4203 */
4204 void optimised_move(int to, int from, int lines)
4205 {
4206 RECT r;
4207 int min, max;
4208
4209 min = (to < from ? to : from);
4210 max = to + from - min;
4211
4212 r.left = offset_width;
4213 r.right = offset_width + cols * font_width;
4214 r.top = offset_height + min * font_height;
4215 r.bottom = offset_height + (max + lines) * font_height;
4216 ScrollWindow(hwnd, 0, (to - from) * font_height, &r, &r);
4217 }
4218 #endif
4219
4220 /*
4221 * Print a message box and perform a fatal exit.
4222 */
4223 void fatalbox(char *fmt, ...)
4224 {
4225 va_list ap;
4226 char stuff[200];
4227
4228 va_start(ap, fmt);
4229 vsprintf(stuff, fmt, ap);
4230 va_end(ap);
4231 MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK);
4232 cleanup_exit(1);
4233 }
4234
4235 /*
4236 * Print a modal (Really Bad) message box and perform a fatal exit.
4237 */
4238 void modalfatalbox(char *fmt, ...)
4239 {
4240 va_list ap;
4241 char stuff[200];
4242
4243 va_start(ap, fmt);
4244 vsprintf(stuff, fmt, ap);
4245 va_end(ap);
4246 MessageBox(hwnd, stuff, "PuTTY Fatal Error",
4247 MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
4248 cleanup_exit(1);
4249 }
4250
4251 /*
4252 * Manage window caption / taskbar flashing, if enabled.
4253 * 0 = stop, 1 = maintain, 2 = start
4254 */
4255 static void flash_window(int mode)
4256 {
4257 static long last_flash = 0;
4258 static int flashing = 0;
4259 if ((mode == 0) || (cfg.beep_ind == B_IND_DISABLED)) {
4260 /* stop */
4261 if (flashing) {
4262 FlashWindow(hwnd, FALSE);
4263 flashing = 0;
4264 }
4265
4266 } else if (mode == 2) {
4267 /* start */
4268 if (!flashing) {
4269 last_flash = GetTickCount();
4270 flashing = 1;
4271 FlashWindow(hwnd, TRUE);
4272 }
4273
4274 } else if ((mode == 1) && (cfg.beep_ind == B_IND_FLASH)) {
4275 /* maintain */
4276 if (flashing) {
4277 long now = GetTickCount();
4278 long fdiff = now - last_flash;
4279 if (fdiff < 0 || fdiff > 450) {
4280 last_flash = now;
4281 FlashWindow(hwnd, TRUE); /* toggle */
4282 }
4283 }
4284 }
4285 }
4286
4287 /*
4288 * Beep.
4289 */
4290 void beep(int mode)
4291 {
4292 if (mode == BELL_DEFAULT) {
4293 /*
4294 * For MessageBeep style bells, we want to be careful of
4295 * timing, because they don't have the nice property of
4296 * PlaySound bells that each one cancels the previous
4297 * active one. So we limit the rate to one per 50ms or so.
4298 */
4299 static long lastbeep = 0;
4300 long beepdiff;
4301
4302 beepdiff = GetTickCount() - lastbeep;
4303 if (beepdiff >= 0 && beepdiff < 50)
4304 return;
4305 MessageBeep(MB_OK);
4306 /*
4307 * The above MessageBeep call takes time, so we record the
4308 * time _after_ it finishes rather than before it starts.
4309 */
4310 lastbeep = GetTickCount();
4311 } else if (mode == BELL_WAVEFILE) {
4312 if (!PlaySound(cfg.bell_wavefile, NULL, SND_ASYNC | SND_FILENAME)) {
4313 char buf[sizeof(cfg.bell_wavefile) + 80];
4314 sprintf(buf, "Unable to play sound file\n%s\n"
4315 "Using default sound instead", cfg.bell_wavefile);
4316 MessageBox(hwnd, buf, "PuTTY Sound Error",
4317 MB_OK | MB_ICONEXCLAMATION);
4318 cfg.beep = BELL_DEFAULT;
4319 }
4320 }
4321 /* Otherwise, either visual bell or disabled; do nothing here */
4322 if (!has_focus) {
4323 flash_window(2); /* start */
4324 }
4325 }
4326
4327 /*
4328 * Minimise or restore the window in response to a server-side
4329 * request.
4330 */
4331 void set_iconic(int iconic)
4332 {
4333 if (IsIconic(hwnd)) {
4334 if (!iconic)
4335 ShowWindow(hwnd, SW_RESTORE);
4336 } else {
4337 if (iconic)
4338 ShowWindow(hwnd, SW_MINIMIZE);
4339 }
4340 }
4341
4342 /*
4343 * Move the window in response to a server-side request.
4344 */
4345 void move_window(int x, int y)
4346 {
4347 if (cfg.resize_action == RESIZE_DISABLED ||
4348 cfg.resize_action == RESIZE_FONT ||
4349 IsZoomed(hwnd))
4350 return;
4351
4352 SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
4353 }
4354
4355 /*
4356 * Move the window to the top or bottom of the z-order in response
4357 * to a server-side request.
4358 */
4359 void set_zorder(int top)
4360 {
4361 if (cfg.alwaysontop)
4362 return; /* ignore */
4363 SetWindowPos(hwnd, top ? HWND_TOP : HWND_BOTTOM, 0, 0, 0, 0,
4364 SWP_NOMOVE | SWP_NOSIZE);
4365 }
4366
4367 /*
4368 * Refresh the window in response to a server-side request.
4369 */
4370 void refresh_window(void)
4371 {
4372 InvalidateRect(hwnd, NULL, TRUE);
4373 }
4374
4375 /*
4376 * Maximise or restore the window in response to a server-side
4377 * request.
4378 */
4379 void set_zoomed(int zoomed)
4380 {
4381 if (IsZoomed(hwnd)) {
4382 if (!zoomed)
4383 ShowWindow(hwnd, SW_RESTORE);
4384 } else {
4385 if (zoomed)
4386 ShowWindow(hwnd, SW_MAXIMIZE);
4387 }
4388 }
4389
4390 /*
4391 * Report whether the window is iconic, for terminal reports.
4392 */
4393 int is_iconic(void)
4394 {
4395 return IsIconic(hwnd);
4396 }
4397
4398 /*
4399 * Report the window's position, for terminal reports.
4400 */
4401 void get_window_pos(int *x, int *y)
4402 {
4403 RECT r;
4404 GetWindowRect(hwnd, &r);
4405 *x = r.left;
4406 *y = r.top;
4407 }
4408
4409 /*
4410 * Report the window's pixel size, for terminal reports.
4411 */
4412 void get_window_pixels(int *x, int *y)
4413 {
4414 RECT r;
4415 GetWindowRect(hwnd, &r);
4416 *x = r.right - r.left;
4417 *y = r.bottom - r.top;
4418 }
4419
4420 /*
4421 * Return the window or icon title.
4422 */
4423 char *get_window_title(int icon)
4424 {
4425 return icon ? icon_name : window_name;
4426 }
4427
4428 /*
4429 * See if we're in full-screen mode.
4430 */
4431 int is_full_screen()
4432 {
4433 if (!IsZoomed(hwnd))
4434 return FALSE;
4435 if (GetWindowLong(hwnd, GWL_STYLE) & WS_CAPTION)
4436 return FALSE;
4437 return TRUE;
4438 }
4439
4440 /* Get the rect/size of a full screen window using the nearest available
4441 * monitor in multimon systems; default to something sensible if only
4442 * one monitor is present. */
4443 static int get_fullscreen_rect(RECT * ss)
4444 {
4445 #ifdef MONITOR_DEFAULTTONEAREST
4446 HMONITOR mon;
4447 MONITORINFO mi;
4448 mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
4449 mi.cbSize = sizeof(mi);
4450 GetMonitorInfo(mon, &mi);
4451
4452 /* structure copy */
4453 *ss = mi.rcMonitor;
4454 return TRUE;
4455 #else
4456 /* could also use code like this:
4457 ss->left = ss->top = 0;
4458 ss->right = GetSystemMetrics(SM_CXSCREEN);
4459 ss->bottom = GetSystemMetrics(SM_CYSCREEN);
4460 */
4461 return GetClientRect(GetDesktopWindow(), ss);
4462 #endif
4463 }
4464
4465
4466 /*
4467 * Go full-screen. This should only be called when we are already
4468 * maximised.
4469 */
4470 void make_full_screen()
4471 {
4472 DWORD style;
4473 RECT ss;
4474
4475 assert(IsZoomed(hwnd));
4476
4477 if (is_full_screen())
4478 return;
4479
4480 /* Remove the window furniture. */
4481 style = GetWindowLong(hwnd, GWL_STYLE);
4482 style &= ~(WS_CAPTION | WS_BORDER | WS_THICKFRAME);
4483 if (cfg.scrollbar_in_fullscreen)
4484 style |= WS_VSCROLL;
4485 else
4486 style &= ~WS_VSCROLL;
4487 SetWindowLong(hwnd, GWL_STYLE, style);
4488
4489 /* Resize ourselves to exactly cover the nearest monitor. */
4490 get_fullscreen_rect(&ss);
4491 SetWindowPos(hwnd, HWND_TOP, ss.left, ss.top,
4492 ss.right - ss.left,
4493 ss.bottom - ss.top,
4494 SWP_FRAMECHANGED);
4495
4496 /* Tick the menu item in the System menu. */
4497 CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN,
4498 MF_CHECKED);
4499 }
4500
4501 /*
4502 * Clear the full-screen attributes.
4503 */
4504 void clear_full_screen()
4505 {
4506 DWORD oldstyle, style;
4507
4508 /* Reinstate the window furniture. */
4509 style = oldstyle = GetWindowLong(hwnd, GWL_STYLE);
4510 style |= WS_CAPTION | WS_BORDER;
4511 if (cfg.resize_action == RESIZE_DISABLED)
4512 style &= ~WS_THICKFRAME;
4513 else
4514 style |= WS_THICKFRAME;
4515 if (cfg.scrollbar)
4516 style |= WS_VSCROLL;
4517 else
4518 style &= ~WS_VSCROLL;
4519 if (style != oldstyle) {
4520 SetWindowLong(hwnd, GWL_STYLE, style);
4521 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
4522 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
4523 SWP_FRAMECHANGED);
4524 }
4525
4526 /* Untick the menu item in the System menu. */
4527 CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN,
4528 MF_UNCHECKED);
4529 }
4530
4531 /*
4532 * Toggle full-screen mode.
4533 */
4534 void flip_full_screen()
4535 {
4536 if (is_full_screen()) {
4537 ShowWindow(hwnd, SW_RESTORE);
4538 } else if (IsZoomed(hwnd)) {
4539 make_full_screen();
4540 } else {
4541 SendMessage(hwnd, WM_FULLSCR_ON_MAX, 0, 0);
4542 ShowWindow(hwnd, SW_MAXIMIZE);
4543 }
4544 }