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