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