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