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