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