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