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