2 * window.c - the PuTTY(tel) main program, which runs a PuTTY terminal
\r
3 * emulator and backend in a window.
\r
14 #define COMPILE_MULTIMON_STUBS
\r
17 #define PUTTY_DO_GLOBALS /* actually _define_ globals */
\r
19 #include "terminal.h"
\r
20 #include "storage.h"
\r
21 #include "win_res.h"
\r
24 #include <multimon.h>
\r
28 #include <commctrl.h>
\r
29 #include <richedit.h>
\r
30 #include <mmsystem.h>
\r
32 /* From MSDN: In the WM_SYSCOMMAND message, the four low-order bits of
\r
33 * wParam are used by Windows, and should be masked off, so we shouldn't
\r
34 * attempt to store information in them. Hence all these identifiers have
\r
35 * the low 4 bits clear. Also, identifiers should < 0xF000. */
\r
37 #define IDM_SHOWLOG 0x0010
\r
38 #define IDM_NEWSESS 0x0020
\r
39 #define IDM_DUPSESS 0x0030
\r
40 #define IDM_RESTART 0x0040
\r
41 #define IDM_RECONF 0x0050
\r
42 #define IDM_CLRSB 0x0060
\r
43 #define IDM_RESET 0x0070
\r
44 #define IDM_HELP 0x0140
\r
45 #define IDM_ABOUT 0x0150
\r
46 #define IDM_SAVEDSESS 0x0160
\r
47 #define IDM_COPYALL 0x0170
\r
48 #define IDM_FULLSCREEN 0x0180
\r
49 #define IDM_PASTE 0x0190
\r
50 #define IDM_SPECIALSEP 0x0200
\r
52 #define IDM_SPECIAL_MIN 0x0400
\r
53 #define IDM_SPECIAL_MAX 0x0800
\r
55 #define IDM_SAVED_MIN 0x1000
\r
56 #define IDM_SAVED_MAX 0x5000
\r
57 #define MENU_SAVED_STEP 16
\r
58 /* Maximum number of sessions on saved-session submenu */
\r
59 #define MENU_SAVED_MAX ((IDM_SAVED_MAX-IDM_SAVED_MIN) / MENU_SAVED_STEP)
\r
61 #define WM_IGNORE_CLIP (WM_APP + 2)
\r
62 #define WM_FULLSCR_ON_MAX (WM_APP + 3)
\r
63 #define WM_AGENT_CALLBACK (WM_APP + 4)
\r
64 #define WM_GOT_CLIPDATA (WM_APP + 6)
\r
66 /* Needed for Chinese support and apparently not always defined. */
\r
67 #ifndef VK_PROCESSKEY
\r
68 #define VK_PROCESSKEY 0xE5
\r
71 /* Mouse wheel support. */
\r
72 #ifndef WM_MOUSEWHEEL
\r
73 #define WM_MOUSEWHEEL 0x020A /* not defined in earlier SDKs */
\r
76 #define WHEEL_DELTA 120
\r
79 static Mouse_Button translate_button(Mouse_Button button);
\r
80 static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
\r
81 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
\r
82 unsigned char *output);
\r
83 static void cfgtopalette(void);
\r
84 static void systopalette(void);
\r
85 static void init_palette(void);
\r
86 static void init_fonts(int, int);
\r
87 static void another_font(int);
\r
88 static void deinit_fonts(void);
\r
89 static void set_input_locale(HKL);
\r
90 static void update_savedsess_menu(void);
\r
91 static void init_flashwindow(void);
\r
93 static int is_full_screen(void);
\r
94 static void make_full_screen(void);
\r
95 static void clear_full_screen(void);
\r
96 static void flip_full_screen(void);
\r
97 static int process_clipdata(HGLOBAL clipdata, int unicode);
\r
99 /* Window layout information */
\r
100 static void reset_window(int);
\r
101 static int extra_width, extra_height;
\r
102 static int font_width, font_height, font_dualwidth, font_varpitch;
\r
103 static int offset_width, offset_height;
\r
104 static int was_zoomed = 0;
\r
105 static int prev_rows, prev_cols;
\r
107 static int pending_netevent = 0;
\r
108 static WPARAM pend_netevent_wParam = 0;
\r
109 static LPARAM pend_netevent_lParam = 0;
\r
110 static void enact_pending_netevent(void);
\r
111 static void flash_window(int mode);
\r
112 static void sys_cursor_update(void);
\r
113 static int get_fullscreen_rect(RECT * ss);
\r
115 static int caret_x = -1, caret_y = -1;
\r
117 static int kbd_codepage;
\r
119 static void *ldisc;
\r
120 static Backend *back;
\r
121 static void *backhandle;
\r
123 static struct unicode_data ucsdata;
\r
124 static int must_close_session, session_closed;
\r
125 static int reconfiguring = FALSE;
\r
127 static const struct telnet_special *specials = NULL;
\r
128 static HMENU specials_menu = NULL;
\r
129 static int n_specials = 0;
\r
131 static wchar_t *clipboard_contents;
\r
132 static size_t clipboard_length;
\r
134 #define TIMING_TIMER_ID 1234
\r
135 static long timing_next_time;
\r
140 enum { SYSMENU, CTXMENU };
\r
141 static HMENU savedsess_menu;
\r
143 Config cfg; /* exported to windlg.c */
\r
145 static struct sesslist sesslist; /* for saved-session menu */
\r
147 struct agent_callback {
\r
148 void (*callback)(void *, void *, int);
\r
149 void *callback_ctx;
\r
154 #define FONT_NORMAL 0
\r
155 #define FONT_BOLD 1
\r
156 #define FONT_UNDERLINE 2
\r
157 #define FONT_BOLDUND 3
\r
158 #define FONT_WIDE 0x04
\r
159 #define FONT_HIGH 0x08
\r
160 #define FONT_NARROW 0x10
\r
162 #define FONT_OEM 0x20
\r
163 #define FONT_OEMBOLD 0x21
\r
164 #define FONT_OEMUND 0x22
\r
165 #define FONT_OEMBOLDUND 0x23
\r
167 #define FONT_MAXNO 0x2F
\r
168 #define FONT_SHIFT 5
\r
169 static HFONT fonts[FONT_MAXNO];
\r
170 static LOGFONT lfont;
\r
171 static int fontflag[FONT_MAXNO];
\r
173 BOLD_COLOURS, BOLD_SHADOW, BOLD_FONT
\r
178 static int descent;
\r
180 #define NCFGCOLOURS 22
\r
181 #define NEXTCOLOURS 240
\r
182 #define NALLCOLOURS (NCFGCOLOURS + NEXTCOLOURS)
\r
183 static COLORREF colours[NALLCOLOURS];
\r
184 static HPALETTE pal;
\r
185 static LPLOGPALETTE logpal;
\r
186 static RGBTRIPLE defpal[NALLCOLOURS];
\r
188 static HBITMAP caretbm;
\r
190 static int dbltime, lasttime, lastact;
\r
191 static Mouse_Button lastbtn;
\r
193 /* this allows xterm-style mouse handling. */
\r
194 static int send_raw_mouse = 0;
\r
195 static int wheel_accumulator = 0;
\r
197 static int busy_status = BUSY_NOT;
\r
199 static char *window_name, *icon_name;
\r
201 static int compose_state = 0;
\r
203 static UINT wm_mousewheel = WM_MOUSEWHEEL;
\r
205 /* Dummy routine, only required in plink. */
\r
206 void ldisc_update(void *frontend, int echo, int edit)
\r
210 char *get_ttymode(void *frontend, const char *mode)
\r
212 return term_get_ttymode(term, mode);
\r
215 static void start_backend(void)
\r
218 char msg[1024], *title;
\r
223 * Select protocol. This is farmed out into a table in a
\r
224 * separate file to enable an ssh-free variant.
\r
226 back = backend_from_proto(cfg.protocol);
\r
227 if (back == NULL) {
\r
228 char *str = dupprintf("%s Internal Error", appname);
\r
229 MessageBox(NULL, "Unsupported protocol number found",
\r
230 str, MB_OK | MB_ICONEXCLAMATION);
\r
235 error = back->init(NULL, &backhandle, &cfg,
\r
236 cfg.host, cfg.port, &realhost, cfg.tcp_nodelay,
\r
237 cfg.tcp_keepalives);
\r
238 back->provide_logctx(backhandle, logctx);
\r
240 char *str = dupprintf("%s Error", appname);
\r
241 sprintf(msg, "Unable to open connection to\n"
\r
242 "%.800s\n" "%s", cfg_dest(&cfg), error);
\r
243 MessageBox(NULL, msg, str, MB_ICONERROR | MB_OK);
\r
247 window_name = icon_name = NULL;
\r
248 if (*cfg.wintitle) {
\r
249 title = cfg.wintitle;
\r
251 sprintf(msg, "%s - %s", realhost, appname);
\r
255 set_title(NULL, title);
\r
256 set_icon(NULL, title);
\r
259 * Connect the terminal to the backend for resize purposes.
\r
261 term_provide_resize_fn(term, back->size, backhandle);
\r
264 * Set up a line discipline.
\r
266 ldisc = ldisc_create(&cfg, term, back, backhandle, NULL);
\r
269 * Destroy the Restart Session menu item. (This will return
\r
270 * failure if it's already absent, as it will be the very first
\r
271 * time we call this function. We ignore that, because as long
\r
272 * as the menu item ends up not being there, we don't care
\r
273 * whether it was us who removed it or not!)
\r
275 for (i = 0; i < lenof(popup_menus); i++) {
\r
276 DeleteMenu(popup_menus[i].menu, IDM_RESTART, MF_BYCOMMAND);
\r
279 must_close_session = FALSE;
\r
280 session_closed = FALSE;
\r
283 static void close_session(void)
\r
285 char morestuff[100];
\r
288 session_closed = TRUE;
\r
289 sprintf(morestuff, "%.70s (inactive)", appname);
\r
290 set_icon(NULL, morestuff);
\r
291 set_title(NULL, morestuff);
\r
298 back->free(backhandle);
\r
301 term_provide_resize_fn(term, NULL, NULL);
\r
302 update_specials_menu(NULL);
\r
306 * Show the Restart Session menu item. Do a precautionary
\r
307 * delete first to ensure we never end up with more than one.
\r
309 for (i = 0; i < lenof(popup_menus); i++) {
\r
310 DeleteMenu(popup_menus[i].menu, IDM_RESTART, MF_BYCOMMAND);
\r
311 InsertMenu(popup_menus[i].menu, IDM_DUPSESS, MF_BYCOMMAND | MF_ENABLED,
\r
312 IDM_RESTART, "&Restart Session");
\r
316 * Unset the 'must_close_session' flag, or else we'll come
\r
317 * straight back here the next time we go round the main message
\r
318 * loop - which, worse still, will be immediately (without
\r
319 * blocking) because we've just triggered a WM_SETTEXT by the
\r
320 * window title change above.
\r
322 must_close_session = FALSE;
\r
325 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
\r
330 int guess_width, guess_height;
\r
334 flags = FLAG_VERBOSE | FLAG_INTERACTIVE;
\r
338 InitCommonControls();
\r
340 /* Ensure a Maximize setting in Explorer doesn't maximise the
\r
342 defuse_showwindow();
\r
344 if (!init_winver())
\r
346 char *str = dupprintf("%s Fatal Error", appname);
\r
347 MessageBox(NULL, "Windows refuses to report a version",
\r
348 str, MB_OK | MB_ICONEXCLAMATION);
\r
354 * If we're running a version of Windows that doesn't support
\r
355 * WM_MOUSEWHEEL, find out what message number we should be
\r
358 if (osVersion.dwMajorVersion < 4 ||
\r
359 (osVersion.dwMajorVersion == 4 &&
\r
360 osVersion.dwPlatformId != VER_PLATFORM_WIN32_NT))
\r
361 wm_mousewheel = RegisterWindowMessage("MSWHEEL_ROLLMSG");
\r
365 init_flashwindow();
\r
370 hr = CoInitialize(NULL);
\r
371 if (hr != S_OK && hr != S_FALSE) {
\r
372 char *str = dupprintf("%s Fatal Error", appname);
\r
373 MessageBox(NULL, "Failed to initialize COM subsystem",
\r
374 str, MB_OK | MB_ICONEXCLAMATION);
\r
380 * Process the command line.
\r
385 /* By default, we bring up the config dialog, rather than launching
\r
386 * a session. This gets set to TRUE if something happens to change
\r
387 * that (e.g., a hostname is specified on the command-line). */
\r
388 int allow_launch = FALSE;
\r
390 default_protocol = be_default_protocol;
\r
391 /* Find the appropriate default port. */
\r
393 Backend *b = backend_from_proto(default_protocol);
\r
394 default_port = 0; /* illegal */
\r
396 default_port = b->default_port;
\r
398 cfg.logtype = LGTYP_NONE;
\r
400 do_defaults(NULL, &cfg);
\r
405 * Process a couple of command-line options which are more
\r
406 * easily dealt with before the line is broken up into words.
\r
407 * These are the old-fashioned but convenient @sessionname and
\r
408 * the internal-use-only &sharedmemoryhandle, neither of which
\r
409 * are combined with anything else.
\r
411 while (*p && isspace(*p))
\r
415 * An initial @ means that the whole of the rest of the
\r
416 * command line should be treated as the name of a saved
\r
417 * session, with _no quoting or escaping_. This makes it a
\r
418 * very convenient means of automated saved-session
\r
419 * launching, via IDM_SAVEDSESS or Windows 7 jump lists.
\r
422 while (i > 1 && isspace(p[i - 1]))
\r
425 do_defaults(p + 1, &cfg);
\r
426 if (!cfg_launchable(&cfg) && !do_config()) {
\r
429 allow_launch = TRUE; /* allow it to be launched directly */
\r
430 } else if (*p == '&') {
\r
432 * An initial & means we've been given a command line
\r
433 * containing the hex value of a HANDLE for a file
\r
434 * mapping object, which we must then extract as a
\r
439 if (sscanf(p + 1, "%p", &filemap) == 1 &&
\r
440 (cp = MapViewOfFile(filemap, FILE_MAP_READ,
\r
441 0, 0, sizeof(Config))) != NULL) {
\r
443 UnmapViewOfFile(cp);
\r
444 CloseHandle(filemap);
\r
445 } else if (!do_config()) {
\r
448 allow_launch = TRUE;
\r
451 * Otherwise, break up the command line and deal with
\r
457 split_into_argv(cmdline, &argc, &argv, NULL);
\r
459 for (i = 0; i < argc; i++) {
\r
463 ret = cmdline_process_param(p, i+1<argc?argv[i+1]:NULL,
\r
466 cmdline_error("option \"%s\" requires an argument", p);
\r
467 } else if (ret == 2) {
\r
468 i++; /* skip next argument */
\r
469 } else if (ret == 1) {
\r
470 continue; /* nothing further needs doing */
\r
471 } else if (!strcmp(p, "-cleanup") ||
\r
472 !strcmp(p, "-cleanup-during-uninstall")) {
\r
474 * `putty -cleanup'. Remove all registry
\r
475 * entries associated with PuTTY, and also find
\r
476 * and delete the random seed file.
\r
479 /* Are we being invoked from an uninstaller? */
\r
480 if (!strcmp(p, "-cleanup-during-uninstall")) {
\r
481 s1 = dupprintf("Remove saved sessions and random seed file?\n"
\r
483 "If you hit Yes, ALL Registry entries associated\n"
\r
484 "with %s will be removed, as well as the\n"
\r
485 "random seed file. THIS PROCESS WILL\n"
\r
486 "DESTROY YOUR SAVED SESSIONS.\n"
\r
487 "(This only affects the currently logged-in user.)\n"
\r
489 "If you hit No, uninstallation will proceed, but\n"
\r
490 "saved sessions etc will be left on the machine.",
\r
492 s2 = dupprintf("%s Uninstallation", appname);
\r
494 s1 = dupprintf("This procedure will remove ALL Registry entries\n"
\r
495 "associated with %s, and will also remove\n"
\r
496 "the random seed file. (This only affects the\n"
\r
497 "currently logged-in user.)\n"
\r
499 "THIS PROCESS WILL DESTROY YOUR SAVED SESSIONS.\n"
\r
500 "Are you really sure you want to continue?",
\r
502 s2 = dupprintf("%s Warning", appname);
\r
504 if (message_box(s1, s2,
\r
505 MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2,
\r
506 HELPCTXID(option_cleanup)) == IDYES) {
\r
512 } else if (!strcmp(p, "-pgpfp")) {
\r
513 pgp_fingerprints();
\r
515 } else if (*p != '-') {
\r
519 * If we already have a host name, treat
\r
520 * this argument as a port number. NB we
\r
521 * have to treat this as a saved -P
\r
522 * argument, so that it will be deferred
\r
523 * until it's a good moment to run it.
\r
525 int ret = cmdline_process_param("-P", p, 1, &cfg);
\r
527 } else if (!strncmp(q, "telnet:", 7)) {
\r
529 * If the hostname starts with "telnet:",
\r
530 * set the protocol to Telnet and process
\r
531 * the string as a Telnet URL.
\r
536 if (q[0] == '/' && q[1] == '/')
\r
538 cfg.protocol = PROT_TELNET;
\r
540 while (*p && *p != ':' && *p != '/')
\r
546 cfg.port = atoi(p);
\r
549 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
\r
550 cfg.host[sizeof(cfg.host) - 1] = '\0';
\r
554 * Otherwise, treat this argument as a host
\r
557 while (*p && !isspace(*p))
\r
561 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
\r
562 cfg.host[sizeof(cfg.host) - 1] = '\0';
\r
566 cmdline_error("unknown option \"%s\"", p);
\r
571 cmdline_run_saved(&cfg);
\r
573 if (loaded_session || got_host)
\r
574 allow_launch = TRUE;
\r
576 if ((!allow_launch || !cfg_launchable(&cfg)) && !do_config()) {
\r
581 * Trim leading whitespace off the hostname if it's there.
\r
584 int space = strspn(cfg.host, " \t");
\r
585 memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
\r
588 /* See if host is of the form user@host */
\r
589 if (cfg.host[0] != '\0') {
\r
590 char *atsign = strrchr(cfg.host, '@');
\r
591 /* Make sure we're not overflowing the user field */
\r
593 if (atsign - cfg.host < sizeof cfg.username) {
\r
594 strncpy(cfg.username, cfg.host, atsign - cfg.host);
\r
595 cfg.username[atsign - cfg.host] = '\0';
\r
597 memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
\r
602 * Trim a colon suffix off the hostname if it's there. In
\r
603 * order to protect IPv6 address literals against this
\r
604 * treatment, we do not do this if there's _more_ than one
\r
608 char *c = strchr(cfg.host, ':');
\r
611 char *d = strchr(c+1, ':');
\r
618 * Remove any remaining whitespace from the hostname.
\r
621 int p1 = 0, p2 = 0;
\r
622 while (cfg.host[p2] != '\0') {
\r
623 if (cfg.host[p2] != ' ' && cfg.host[p2] != '\t') {
\r
624 cfg.host[p1] = cfg.host[p2];
\r
629 cfg.host[p1] = '\0';
\r
634 wndclass.style = 0;
\r
635 wndclass.lpfnWndProc = WndProc;
\r
636 wndclass.cbClsExtra = 0;
\r
637 wndclass.cbWndExtra = 0;
\r
638 wndclass.hInstance = inst;
\r
639 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
\r
640 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
\r
641 wndclass.hbrBackground = NULL;
\r
642 wndclass.lpszMenuName = NULL;
\r
643 wndclass.lpszClassName = appname;
\r
645 RegisterClass(&wndclass);
\r
648 memset(&ucsdata, 0, sizeof(ucsdata));
\r
653 * Guess some defaults for the window size. This all gets
\r
654 * updated later, so we don't really care too much. However, we
\r
655 * do want the font width/height guesses to correspond to a
\r
656 * large font rather than a small one...
\r
663 guess_width = extra_width + font_width * cfg.width;
\r
664 guess_height = extra_height + font_height * cfg.height;
\r
667 get_fullscreen_rect(&r);
\r
668 if (guess_width > r.right - r.left)
\r
669 guess_width = r.right - r.left;
\r
670 if (guess_height > r.bottom - r.top)
\r
671 guess_height = r.bottom - r.top;
\r
675 int winmode = WS_OVERLAPPEDWINDOW | WS_VSCROLL;
\r
677 if (!cfg.scrollbar)
\r
678 winmode &= ~(WS_VSCROLL);
\r
679 if (cfg.resize_action == RESIZE_DISABLED)
\r
680 winmode &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
\r
681 if (cfg.alwaysontop)
\r
682 exwinmode |= WS_EX_TOPMOST;
\r
683 if (cfg.sunken_edge)
\r
684 exwinmode |= WS_EX_CLIENTEDGE;
\r
685 hwnd = CreateWindowEx(exwinmode, appname, appname,
\r
686 winmode, CW_USEDEFAULT, CW_USEDEFAULT,
\r
687 guess_width, guess_height,
\r
688 NULL, NULL, inst, NULL);
\r
692 * Initialise the terminal. (We have to do this _after_
\r
693 * creating the window, since the terminal is the first thing
\r
694 * which will call schedule_timer(), which will in turn call
\r
695 * timer_change_notify() which will expect hwnd to exist.)
\r
697 term = term_init(&cfg, &ucsdata, NULL);
\r
698 logctx = log_init(NULL, &cfg);
\r
699 term_provide_logctx(term, logctx);
\r
700 term_size(term, cfg.height, cfg.width, cfg.savelines);
\r
703 * Initialise the fonts, simultaneously correcting the guesses
\r
704 * for font_{width,height}.
\r
709 * Correct the guesses for extra_{width,height}.
\r
713 GetWindowRect(hwnd, &wr);
\r
714 GetClientRect(hwnd, &cr);
\r
715 offset_width = offset_height = cfg.window_border;
\r
716 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
\r
717 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
\r
721 * Resize the window, now we know what size we _really_ want it
\r
724 guess_width = extra_width + font_width * term->cols;
\r
725 guess_height = extra_height + font_height * term->rows;
\r
726 SetWindowPos(hwnd, NULL, 0, 0, guess_width, guess_height,
\r
727 SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
\r
730 * Set up a caret bitmap, with no content.
\r
734 int size = (font_width + 15) / 16 * 2 * font_height;
\r
735 bits = snewn(size, char);
\r
736 memset(bits, 0, size);
\r
737 caretbm = CreateBitmap(font_width, font_height, 1, 1, bits);
\r
740 CreateCaret(hwnd, caretbm, font_width, font_height);
\r
743 * Initialise the scroll bar.
\r
748 si.cbSize = sizeof(si);
\r
749 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
\r
751 si.nMax = term->rows - 1;
\r
752 si.nPage = term->rows;
\r
754 SetScrollInfo(hwnd, SB_VERT, &si, FALSE);
\r
758 * Prepare the mouse handler.
\r
760 lastact = MA_NOTHING;
\r
761 lastbtn = MBT_NOTHING;
\r
762 dbltime = GetDoubleClickTime();
\r
765 * Set up the session-control options on the system menu.
\r
772 popup_menus[SYSMENU].menu = GetSystemMenu(hwnd, FALSE);
\r
773 popup_menus[CTXMENU].menu = CreatePopupMenu();
\r
774 AppendMenu(popup_menus[CTXMENU].menu, MF_ENABLED, IDM_PASTE, "&Paste");
\r
776 savedsess_menu = CreateMenu();
\r
777 get_sesslist(&sesslist, TRUE);
\r
778 update_savedsess_menu();
\r
780 for (j = 0; j < lenof(popup_menus); j++) {
\r
781 m = popup_menus[j].menu;
\r
783 AppendMenu(m, MF_SEPARATOR, 0, 0);
\r
784 AppendMenu(m, MF_ENABLED, IDM_SHOWLOG, "&Event Log");
\r
785 AppendMenu(m, MF_SEPARATOR, 0, 0);
\r
786 AppendMenu(m, MF_ENABLED, IDM_NEWSESS, "Ne&w Session...");
\r
787 AppendMenu(m, MF_ENABLED, IDM_DUPSESS, "&Duplicate Session");
\r
788 AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) savedsess_menu,
\r
789 "Sa&ved Sessions");
\r
790 AppendMenu(m, MF_ENABLED, IDM_RECONF, "Chan&ge Settings...");
\r
791 AppendMenu(m, MF_SEPARATOR, 0, 0);
\r
792 AppendMenu(m, MF_ENABLED, IDM_COPYALL, "C&opy All to Clipboard");
\r
793 AppendMenu(m, MF_ENABLED, IDM_CLRSB, "C&lear Scrollback");
\r
794 AppendMenu(m, MF_ENABLED, IDM_RESET, "Rese&t Terminal");
\r
795 AppendMenu(m, MF_SEPARATOR, 0, 0);
\r
796 AppendMenu(m, (cfg.resize_action == RESIZE_DISABLED) ?
\r
797 MF_GRAYED : MF_ENABLED, IDM_FULLSCREEN, "&Full Screen");
\r
798 AppendMenu(m, MF_SEPARATOR, 0, 0);
\r
800 AppendMenu(m, MF_ENABLED, IDM_HELP, "&Help");
\r
801 str = dupprintf("&About %s", appname);
\r
802 AppendMenu(m, MF_ENABLED, IDM_ABOUT, str);
\r
810 * Set up the initial input locale.
\r
812 set_input_locale(GetKeyboardLayout(0));
\r
815 * Finally show the window!
\r
817 ShowWindow(hwnd, show);
\r
818 SetForegroundWindow(hwnd);
\r
821 * Set the palette up.
\r
827 term_set_focus(term, GetForegroundWindow() == hwnd);
\r
828 UpdateWindow(hwnd);
\r
834 handles = handle_get_events(&nhandles);
\r
836 n = MsgWaitForMultipleObjects(nhandles, handles, FALSE, INFINITE,
\r
839 if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) {
\r
840 handle_got_event(handles[n - WAIT_OBJECT_0]);
\r
842 if (must_close_session)
\r
847 while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
\r
848 if (msg.message == WM_QUIT)
\r
849 goto finished; /* two-level break */
\r
851 if (!(IsWindow(logbox) && IsDialogMessage(logbox, &msg)))
\r
852 DispatchMessage(&msg);
\r
853 /* Send the paste buffer if there's anything to send */
\r
855 /* If there's nothing new in the queue then we can do everything
\r
856 * we've delayed, reading the socket, writing, and repainting
\r
859 if (must_close_session)
\r
863 /* The messages seem unreliable; especially if we're being tricky */
\r
864 term_set_focus(term, GetForegroundWindow() == hwnd);
\r
866 if (pending_netevent)
\r
867 enact_pending_netevent();
\r
869 net_pending_errors();
\r
873 cleanup_exit(msg.wParam); /* this doesn't return... */
\r
874 return msg.wParam; /* ... but optimiser doesn't know */
\r
878 * Clean up and exit.
\r
880 void cleanup_exit(int code)
\r
891 if (cfg.protocol == PROT_SSH) {
\r
892 random_save_seed();
\r
899 /* Clean up COM. */
\r
906 * Set up, or shut down, an AsyncSelect. Called from winnet.c.
\r
908 char *do_select(SOCKET skt, int startup)
\r
913 events = (FD_CONNECT | FD_READ | FD_WRITE |
\r
914 FD_OOB | FD_CLOSE | FD_ACCEPT);
\r
919 return "do_select(): internal error (hwnd==NULL)";
\r
920 if (p_WSAAsyncSelect(skt, hwnd, msg, events) == SOCKET_ERROR) {
\r
921 switch (p_WSAGetLastError()) {
\r
923 return "Network is down";
\r
925 return "WSAAsyncSelect(): unknown error";
\r
932 * Refresh the saved-session submenu from `sesslist'.
\r
934 static void update_savedsess_menu(void)
\r
937 while (DeleteMenu(savedsess_menu, 0, MF_BYPOSITION)) ;
\r
938 /* skip sesslist.sessions[0] == Default Settings */
\r
940 i < ((sesslist.nsessions <= MENU_SAVED_MAX+1) ? sesslist.nsessions
\r
941 : MENU_SAVED_MAX+1);
\r
943 AppendMenu(savedsess_menu, MF_ENABLED,
\r
944 IDM_SAVED_MIN + (i-1)*MENU_SAVED_STEP,
\r
945 sesslist.sessions[i]);
\r
946 if (sesslist.nsessions <= 1)
\r
947 AppendMenu(savedsess_menu, MF_GRAYED, IDM_SAVED_MIN, "(No sessions)");
\r
951 * Update the Special Commands submenu.
\r
953 void update_specials_menu(void *frontend)
\r
959 specials = back->get_specials(backhandle);
\r
964 /* We can't use Windows to provide a stack for submenus, so
\r
965 * here's a lame "stack" that will do for now. */
\r
966 HMENU saved_menu = NULL;
\r
968 new_menu = CreatePopupMenu();
\r
969 for (i = 0; nesting > 0; i++) {
\r
970 assert(IDM_SPECIAL_MIN + 0x10 * i < IDM_SPECIAL_MAX);
\r
971 switch (specials[i].code) {
\r
973 AppendMenu(new_menu, MF_SEPARATOR, 0, 0);
\r
976 assert(nesting < 2);
\r
978 saved_menu = new_menu; /* XXX lame stacking */
\r
979 new_menu = CreatePopupMenu();
\r
980 AppendMenu(saved_menu, MF_POPUP | MF_ENABLED,
\r
981 (UINT) new_menu, specials[i].name);
\r
986 new_menu = saved_menu; /* XXX lame stacking */
\r
991 AppendMenu(new_menu, MF_ENABLED, IDM_SPECIAL_MIN + 0x10 * i,
\r
996 /* Squirrel the highest special. */
\r
997 n_specials = i - 1;
\r
1003 for (j = 0; j < lenof(popup_menus); j++) {
\r
1004 if (specials_menu) {
\r
1005 /* XXX does this free up all submenus? */
\r
1006 DeleteMenu(popup_menus[j].menu, (UINT)specials_menu, MF_BYCOMMAND);
\r
1007 DeleteMenu(popup_menus[j].menu, IDM_SPECIALSEP, MF_BYCOMMAND);
\r
1010 InsertMenu(popup_menus[j].menu, IDM_SHOWLOG,
\r
1011 MF_BYCOMMAND | MF_POPUP | MF_ENABLED,
\r
1012 (UINT) new_menu, "S&pecial Command");
\r
1013 InsertMenu(popup_menus[j].menu, IDM_SHOWLOG,
\r
1014 MF_BYCOMMAND | MF_SEPARATOR, IDM_SPECIALSEP, 0);
\r
1017 specials_menu = new_menu;
\r
1020 static void update_mouse_pointer(void)
\r
1023 int force_visible = FALSE;
\r
1024 static int forced_visible = FALSE;
\r
1025 switch (busy_status) {
\r
1027 if (send_raw_mouse)
\r
1028 curstype = IDC_ARROW;
\r
1030 curstype = IDC_IBEAM;
\r
1032 case BUSY_WAITING:
\r
1033 curstype = IDC_APPSTARTING; /* this may be an abuse */
\r
1034 force_visible = TRUE;
\r
1037 curstype = IDC_WAIT;
\r
1038 force_visible = TRUE;
\r
1044 HCURSOR cursor = LoadCursor(NULL, curstype);
\r
1045 SetClassLongPtr(hwnd, GCLP_HCURSOR, (LONG_PTR)cursor);
\r
1046 SetCursor(cursor); /* force redraw of cursor at current posn */
\r
1048 if (force_visible != forced_visible) {
\r
1049 /* We want some cursor shapes to be visible always.
\r
1050 * Along with show_mouseptr(), this manages the ShowCursor()
\r
1051 * counter such that if we switch back to a non-force_visible
\r
1052 * cursor, the previous visibility state is restored. */
\r
1053 ShowCursor(force_visible);
\r
1054 forced_visible = force_visible;
\r
1058 void set_busy_status(void *frontend, int status)
\r
1060 busy_status = status;
\r
1061 update_mouse_pointer();
\r
1065 * set or clear the "raw mouse message" mode
\r
1067 void set_raw_mouse_mode(void *frontend, int activate)
\r
1069 activate = activate && !cfg.no_mouse_rep;
\r
1070 send_raw_mouse = activate;
\r
1071 update_mouse_pointer();
\r
1075 * Print a message box and close the connection.
\r
1077 void connection_fatal(void *frontend, char *fmt, ...)
\r
1080 char *stuff, morestuff[100];
\r
1082 va_start(ap, fmt);
\r
1083 stuff = dupvprintf(fmt, ap);
\r
1085 sprintf(morestuff, "%.70s Fatal Error", appname);
\r
1086 MessageBox(hwnd, stuff, morestuff, MB_ICONERROR | MB_OK);
\r
1089 if (cfg.close_on_exit == FORCE_ON)
\r
1090 PostQuitMessage(1);
\r
1092 must_close_session = TRUE;
\r
1097 * Report an error at the command-line parsing stage.
\r
1099 void cmdline_error(char *fmt, ...)
\r
1102 char *stuff, morestuff[100];
\r
1104 va_start(ap, fmt);
\r
1105 stuff = dupvprintf(fmt, ap);
\r
1107 sprintf(morestuff, "%.70s Command Line Error", appname);
\r
1108 MessageBox(hwnd, stuff, morestuff, MB_ICONERROR | MB_OK);
\r
1114 * Actually do the job requested by a WM_NETEVENT
\r
1116 static void enact_pending_netevent(void)
\r
1118 static int reentering = 0;
\r
1119 extern int select_result(WPARAM, LPARAM);
\r
1122 return; /* don't unpend the pending */
\r
1124 pending_netevent = FALSE;
\r
1127 select_result(pend_netevent_wParam, pend_netevent_lParam);
\r
1132 * Copy the colour palette from the configuration data into defpal.
\r
1133 * This is non-trivial because the colour indices are different.
\r
1135 static void cfgtopalette(void)
\r
1138 static const int ww[] = {
\r
1139 256, 257, 258, 259, 260, 261,
\r
1140 0, 8, 1, 9, 2, 10, 3, 11,
\r
1141 4, 12, 5, 13, 6, 14, 7, 15
\r
1144 for (i = 0; i < 22; i++) {
\r
1146 defpal[w].rgbtRed = cfg.colours[i][0];
\r
1147 defpal[w].rgbtGreen = cfg.colours[i][1];
\r
1148 defpal[w].rgbtBlue = cfg.colours[i][2];
\r
1150 for (i = 0; i < NEXTCOLOURS; i++) {
\r
1152 int r = i / 36, g = (i / 6) % 6, b = i % 6;
\r
1153 defpal[i+16].rgbtRed = r ? r * 40 + 55 : 0;
\r
1154 defpal[i+16].rgbtGreen = g ? g * 40 + 55 : 0;
\r
1155 defpal[i+16].rgbtBlue = b ? b * 40 + 55 : 0;
\r
1157 int shade = i - 216;
\r
1158 shade = shade * 10 + 8;
\r
1159 defpal[i+16].rgbtRed = defpal[i+16].rgbtGreen =
\r
1160 defpal[i+16].rgbtBlue = shade;
\r
1164 /* Override with system colours if appropriate */
\r
1165 if (cfg.system_colour)
\r
1170 * Override bit of defpal with colours from the system.
\r
1171 * (NB that this takes a copy the system colours at the time this is called,
\r
1172 * so subsequent colour scheme changes don't take effect. To fix that we'd
\r
1173 * probably want to be using GetSysColorBrush() and the like.)
\r
1175 static void systopalette(void)
\r
1178 static const struct { int nIndex; int norm; int bold; } or[] =
\r
1180 { COLOR_WINDOWTEXT, 256, 257 }, /* Default Foreground */
\r
1181 { COLOR_WINDOW, 258, 259 }, /* Default Background */
\r
1182 { COLOR_HIGHLIGHTTEXT, 260, 260 }, /* Cursor Text */
\r
1183 { COLOR_HIGHLIGHT, 261, 261 }, /* Cursor Colour */
\r
1186 for (i = 0; i < (sizeof(or)/sizeof(or[0])); i++) {
\r
1187 COLORREF colour = GetSysColor(or[i].nIndex);
\r
1188 defpal[or[i].norm].rgbtRed =
\r
1189 defpal[or[i].bold].rgbtRed = GetRValue(colour);
\r
1190 defpal[or[i].norm].rgbtGreen =
\r
1191 defpal[or[i].bold].rgbtGreen = GetGValue(colour);
\r
1192 defpal[or[i].norm].rgbtBlue =
\r
1193 defpal[or[i].bold].rgbtBlue = GetBValue(colour);
\r
1198 * Set up the colour palette.
\r
1200 static void init_palette(void)
\r
1203 HDC hdc = GetDC(hwnd);
\r
1205 if (cfg.try_palette && GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) {
\r
1207 * This is a genuine case where we must use smalloc
\r
1208 * because the snew macros can't cope.
\r
1210 logpal = smalloc(sizeof(*logpal)
\r
1211 - sizeof(logpal->palPalEntry)
\r
1212 + NALLCOLOURS * sizeof(PALETTEENTRY));
\r
1213 logpal->palVersion = 0x300;
\r
1214 logpal->palNumEntries = NALLCOLOURS;
\r
1215 for (i = 0; i < NALLCOLOURS; i++) {
\r
1216 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
\r
1217 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
\r
1218 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
\r
1219 logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
\r
1221 pal = CreatePalette(logpal);
\r
1223 SelectPalette(hdc, pal, FALSE);
\r
1224 RealizePalette(hdc);
\r
1225 SelectPalette(hdc, GetStockObject(DEFAULT_PALETTE), FALSE);
\r
1228 ReleaseDC(hwnd, hdc);
\r
1231 for (i = 0; i < NALLCOLOURS; i++)
\r
1232 colours[i] = PALETTERGB(defpal[i].rgbtRed,
\r
1233 defpal[i].rgbtGreen,
\r
1234 defpal[i].rgbtBlue);
\r
1236 for (i = 0; i < NALLCOLOURS; i++)
\r
1237 colours[i] = RGB(defpal[i].rgbtRed,
\r
1238 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
\r
1242 * This is a wrapper to ExtTextOut() to force Windows to display
\r
1243 * the precise glyphs we give it. Otherwise it would do its own
\r
1244 * bidi and Arabic shaping, and we would end up uncertain which
\r
1245 * characters it had put where.
\r
1247 static void exact_textout(HDC hdc, int x, int y, CONST RECT *lprc,
\r
1248 unsigned short *lpString, UINT cbCount,
\r
1249 CONST INT *lpDx, int opaque)
\r
1253 * The LCC include files apparently don't supply the
\r
1254 * GCP_RESULTSW type, but we can make do with GCP_RESULTS
\r
1255 * proper: the differences aren't important to us (the only
\r
1256 * variable-width string parameter is one we don't use anyway).
\r
1260 GCP_RESULTSW gcpr;
\r
1262 char *buffer = snewn(cbCount*2+2, char);
\r
1263 char *classbuffer = snewn(cbCount, char);
\r
1264 memset(&gcpr, 0, sizeof(gcpr));
\r
1265 memset(buffer, 0, cbCount*2+2);
\r
1266 memset(classbuffer, GCPCLASS_NEUTRAL, cbCount);
\r
1268 gcpr.lStructSize = sizeof(gcpr);
\r
1269 gcpr.lpGlyphs = (void *)buffer;
\r
1270 gcpr.lpClass = (void *)classbuffer;
\r
1271 gcpr.nGlyphs = cbCount;
\r
1272 GetCharacterPlacementW(hdc, lpString, cbCount, 0, &gcpr,
\r
1273 FLI_MASK | GCP_CLASSIN | GCP_DIACRITIC);
\r
1275 ExtTextOut(hdc, x, y,
\r
1276 ETO_GLYPH_INDEX | ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0),
\r
1277 lprc, buffer, cbCount, lpDx);
\r
1281 * The exact_textout() wrapper, unfortunately, destroys the useful
\r
1282 * Windows `font linking' behaviour: automatic handling of Unicode
\r
1283 * code points not supported in this font by falling back to a font
\r
1284 * which does contain them. Therefore, we adopt a multi-layered
\r
1285 * approach: for any potentially-bidi text, we use exact_textout(),
\r
1286 * and for everything else we use a simple ExtTextOut as we did
\r
1287 * before exact_textout() was introduced.
\r
1289 static void general_textout(HDC hdc, int x, int y, CONST RECT *lprc,
\r
1290 unsigned short *lpString, UINT cbCount,
\r
1291 CONST INT *lpDx, int opaque)
\r
1294 int bkmode = 0, got_bkmode = FALSE;
\r
1298 for (i = 0; i < (int)cbCount ;) {
\r
1299 int rtl = is_rtl(lpString[i]);
\r
1303 for (j = i+1; j < (int)cbCount; j++) {
\r
1304 if (rtl != is_rtl(lpString[j]))
\r
1310 * Now [i,j) indicates a maximal substring of lpString
\r
1311 * which should be displayed using the same textout
\r
1315 exact_textout(hdc, xp, y, lprc, lpString+i, j-i,
\r
1316 font_varpitch ? NULL : lpDx+i, opaque);
\r
1318 ExtTextOutW(hdc, xp, y, ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0),
\r
1319 lprc, lpString+i, j-i,
\r
1320 font_varpitch ? NULL : lpDx+i);
\r
1326 bkmode = GetBkMode(hdc);
\r
1327 got_bkmode = TRUE;
\r
1328 SetBkMode(hdc, TRANSPARENT);
\r
1333 SetBkMode(hdc, bkmode);
\r
1336 static int get_font_width(HDC hdc, const TEXTMETRIC *tm)
\r
1339 /* Note that the TMPF_FIXED_PITCH bit is defined upside down :-( */
\r
1340 if (!(tm->tmPitchAndFamily & TMPF_FIXED_PITCH)) {
\r
1341 ret = tm->tmAveCharWidth;
\r
1345 ABCFLOAT widths[LAST-FIRST + 1];
\r
1348 font_varpitch = TRUE;
\r
1349 font_dualwidth = TRUE;
\r
1350 if (GetCharABCWidthsFloat(hdc, FIRST, LAST, widths)) {
\r
1352 for (j = 0; j < lenof(widths); j++) {
\r
1353 int width = (int)(0.5 + widths[j].abcfA +
\r
1354 widths[j].abcfB + widths[j].abcfC);
\r
1359 ret = tm->tmMaxCharWidth;
\r
1368 * Initialise all the fonts we will need initially. There may be as many as
\r
1369 * three or as few as one. The other (potentially) twenty-one fonts are done
\r
1370 * if/when they are needed.
\r
1374 * - check the font width and height, correcting our guesses if
\r
1377 * - verify that the bold font is the same width as the ordinary
\r
1378 * one, and engage shadow bolding if not.
\r
1380 * - verify that the underlined font is the same width as the
\r
1381 * ordinary one (manual underlining by means of line drawing can
\r
1382 * be done in a pinch).
\r
1384 static void init_fonts(int pick_width, int pick_height)
\r
1391 int fw_dontcare, fw_bold;
\r
1393 for (i = 0; i < FONT_MAXNO; i++)
\r
1396 bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT;
\r
1397 und_mode = UND_FONT;
\r
1399 if (cfg.font.isbold) {
\r
1400 fw_dontcare = FW_BOLD;
\r
1401 fw_bold = FW_HEAVY;
\r
1403 fw_dontcare = FW_DONTCARE;
\r
1404 fw_bold = FW_BOLD;
\r
1407 hdc = GetDC(hwnd);
\r
1410 font_height = pick_height;
\r
1412 font_height = cfg.font.height;
\r
1413 if (font_height > 0) {
\r
1415 -MulDiv(font_height, GetDeviceCaps(hdc, LOGPIXELSY), 72);
\r
1418 font_width = pick_width;
\r
1420 #define f(i,c,w,u) \
\r
1421 fonts[i] = CreateFont (font_height, font_width, 0, 0, w, FALSE, u, FALSE, \
\r
1422 c, OUT_DEFAULT_PRECIS, \
\r
1423 CLIP_DEFAULT_PRECIS, FONT_QUALITY(cfg.font_quality), \
\r
1424 FIXED_PITCH | FF_DONTCARE, cfg.font.name)
\r
1426 f(FONT_NORMAL, cfg.font.charset, fw_dontcare, FALSE);
\r
1428 SelectObject(hdc, fonts[FONT_NORMAL]);
\r
1429 GetTextMetrics(hdc, &tm);
\r
1431 GetObject(fonts[FONT_NORMAL], sizeof(LOGFONT), &lfont);
\r
1433 /* Note that the TMPF_FIXED_PITCH bit is defined upside down :-( */
\r
1434 if (!(tm.tmPitchAndFamily & TMPF_FIXED_PITCH)) {
\r
1435 font_varpitch = FALSE;
\r
1436 font_dualwidth = (tm.tmAveCharWidth != tm.tmMaxCharWidth);
\r
1438 font_varpitch = TRUE;
\r
1439 font_dualwidth = TRUE;
\r
1441 if (pick_width == 0 || pick_height == 0) {
\r
1442 font_height = tm.tmHeight;
\r
1443 font_width = get_font_width(hdc, &tm);
\r
1446 #ifdef RDB_DEBUG_PATCH
\r
1447 debug(23, "Primary font H=%d, AW=%d, MW=%d",
\r
1448 tm.tmHeight, tm.tmAveCharWidth, tm.tmMaxCharWidth);
\r
1453 DWORD cset = tm.tmCharSet;
\r
1454 memset(&info, 0xFF, sizeof(info));
\r
1456 /* !!! Yes the next line is right */
\r
1457 if (cset == OEM_CHARSET)
\r
1458 ucsdata.font_codepage = GetOEMCP();
\r
1460 if (TranslateCharsetInfo ((DWORD *) cset, &info, TCI_SRCCHARSET))
\r
1461 ucsdata.font_codepage = info.ciACP;
\r
1463 ucsdata.font_codepage = -1;
\r
1465 GetCPInfo(ucsdata.font_codepage, &cpinfo);
\r
1466 ucsdata.dbcs_screenfont = (cpinfo.MaxCharSize > 1);
\r
1469 f(FONT_UNDERLINE, cfg.font.charset, fw_dontcare, TRUE);
\r
1472 * Some fonts, e.g. 9-pt Courier, draw their underlines
\r
1473 * outside their character cell. We successfully prevent
\r
1474 * screen corruption by clipping the text output, but then
\r
1475 * we lose the underline completely. Here we try to work
\r
1476 * out whether this is such a font, and if it is, we set a
\r
1477 * flag that causes underlines to be drawn by hand.
\r
1479 * Having tried other more sophisticated approaches (such
\r
1480 * as examining the TEXTMETRIC structure or requesting the
\r
1481 * height of a string), I think we'll do this the brute
\r
1482 * force way: we create a small bitmap, draw an underlined
\r
1483 * space on it, and test to see whether any pixels are
\r
1484 * foreground-coloured. (Since we expect the underline to
\r
1485 * go all the way across the character cell, we only search
\r
1486 * down a single column of the bitmap, half way across.)
\r
1490 HBITMAP und_bm, und_oldbm;
\r
1494 und_dc = CreateCompatibleDC(hdc);
\r
1495 und_bm = CreateCompatibleBitmap(hdc, font_width, font_height);
\r
1496 und_oldbm = SelectObject(und_dc, und_bm);
\r
1497 SelectObject(und_dc, fonts[FONT_UNDERLINE]);
\r
1498 SetTextAlign(und_dc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
\r
1499 SetTextColor(und_dc, RGB(255, 255, 255));
\r
1500 SetBkColor(und_dc, RGB(0, 0, 0));
\r
1501 SetBkMode(und_dc, OPAQUE);
\r
1502 ExtTextOut(und_dc, 0, 0, ETO_OPAQUE, NULL, " ", 1, NULL);
\r
1504 for (i = 0; i < font_height; i++) {
\r
1505 c = GetPixel(und_dc, font_width / 2, i);
\r
1506 if (c != RGB(0, 0, 0))
\r
1509 SelectObject(und_dc, und_oldbm);
\r
1510 DeleteObject(und_bm);
\r
1513 und_mode = UND_LINE;
\r
1514 DeleteObject(fonts[FONT_UNDERLINE]);
\r
1515 fonts[FONT_UNDERLINE] = 0;
\r
1519 if (bold_mode == BOLD_FONT) {
\r
1520 f(FONT_BOLD, cfg.font.charset, fw_bold, FALSE);
\r
1524 descent = tm.tmAscent + 1;
\r
1525 if (descent >= font_height)
\r
1526 descent = font_height - 1;
\r
1528 for (i = 0; i < 3; i++) {
\r
1530 if (SelectObject(hdc, fonts[i]) && GetTextMetrics(hdc, &tm))
\r
1531 fontsize[i] = get_font_width(hdc, &tm) + 256 * tm.tmHeight;
\r
1538 ReleaseDC(hwnd, hdc);
\r
1540 if (fontsize[FONT_UNDERLINE] != fontsize[FONT_NORMAL]) {
\r
1541 und_mode = UND_LINE;
\r
1542 DeleteObject(fonts[FONT_UNDERLINE]);
\r
1543 fonts[FONT_UNDERLINE] = 0;
\r
1546 if (bold_mode == BOLD_FONT &&
\r
1547 fontsize[FONT_BOLD] != fontsize[FONT_NORMAL]) {
\r
1548 bold_mode = BOLD_SHADOW;
\r
1549 DeleteObject(fonts[FONT_BOLD]);
\r
1550 fonts[FONT_BOLD] = 0;
\r
1552 fontflag[0] = fontflag[1] = fontflag[2] = 1;
\r
1554 init_ucs(&cfg, &ucsdata);
\r
1557 static void another_font(int fontno)
\r
1560 int fw_dontcare, fw_bold;
\r
1564 if (fontno < 0 || fontno >= FONT_MAXNO || fontflag[fontno])
\r
1567 basefont = (fontno & ~(FONT_BOLDUND));
\r
1568 if (basefont != fontno && !fontflag[basefont])
\r
1569 another_font(basefont);
\r
1571 if (cfg.font.isbold) {
\r
1572 fw_dontcare = FW_BOLD;
\r
1573 fw_bold = FW_HEAVY;
\r
1575 fw_dontcare = FW_DONTCARE;
\r
1576 fw_bold = FW_BOLD;
\r
1579 c = cfg.font.charset;
\r
1582 s = cfg.font.name;
\r
1585 if (fontno & FONT_WIDE)
\r
1587 if (fontno & FONT_NARROW)
\r
1589 if (fontno & FONT_OEM)
\r
1591 if (fontno & FONT_BOLD)
\r
1593 if (fontno & FONT_UNDERLINE)
\r
1597 CreateFont(font_height * (1 + !!(fontno & FONT_HIGH)), x, 0, 0, w,
\r
1598 FALSE, u, FALSE, c, OUT_DEFAULT_PRECIS,
\r
1599 CLIP_DEFAULT_PRECIS, FONT_QUALITY(cfg.font_quality),
\r
1600 DEFAULT_PITCH | FF_DONTCARE, s);
\r
1602 fontflag[fontno] = 1;
\r
1605 static void deinit_fonts(void)
\r
1608 for (i = 0; i < FONT_MAXNO; i++) {
\r
1610 DeleteObject(fonts[i]);
\r
1616 void request_resize(void *frontend, int w, int h)
\r
1618 int width, height;
\r
1620 /* If the window is maximized supress resizing attempts */
\r
1621 if (IsZoomed(hwnd)) {
\r
1622 if (cfg.resize_action == RESIZE_TERM)
\r
1626 if (cfg.resize_action == RESIZE_DISABLED) return;
\r
1627 if (h == term->rows && w == term->cols) return;
\r
1629 /* Sanity checks ... */
\r
1631 static int first_time = 1;
\r
1634 switch (first_time) {
\r
1636 /* Get the size of the screen */
\r
1637 if (get_fullscreen_rect(&ss))
\r
1638 /* first_time = 0 */ ;
\r
1644 /* Make sure the values are sane */
\r
1645 width = (ss.right - ss.left - extra_width) / 4;
\r
1646 height = (ss.bottom - ss.top - extra_height) / 6;
\r
1648 if (w > width || h > height)
\r
1657 term_size(term, h, w, cfg.savelines);
\r
1659 if (cfg.resize_action != RESIZE_FONT && !IsZoomed(hwnd)) {
\r
1660 width = extra_width + font_width * w;
\r
1661 height = extra_height + font_height * h;
\r
1663 SetWindowPos(hwnd, NULL, 0, 0, width, height,
\r
1664 SWP_NOACTIVATE | SWP_NOCOPYBITS |
\r
1665 SWP_NOMOVE | SWP_NOZORDER);
\r
1669 InvalidateRect(hwnd, NULL, TRUE);
\r
1672 static void reset_window(int reinit) {
\r
1674 * This function decides how to resize or redraw when the
\r
1675 * user changes something.
\r
1677 * This function doesn't like to change the terminal size but if the
\r
1678 * font size is locked that may be it's only soluion.
\r
1680 int win_width, win_height;
\r
1683 #ifdef RDB_DEBUG_PATCH
\r
1684 debug((27, "reset_window()"));
\r
1687 /* Current window sizes ... */
\r
1688 GetWindowRect(hwnd, &wr);
\r
1689 GetClientRect(hwnd, &cr);
\r
1691 win_width = cr.right - cr.left;
\r
1692 win_height = cr.bottom - cr.top;
\r
1694 if (cfg.resize_action == RESIZE_DISABLED) reinit = 2;
\r
1696 /* Are we being forced to reload the fonts ? */
\r
1698 #ifdef RDB_DEBUG_PATCH
\r
1699 debug((27, "reset_window() -- Forced deinit"));
\r
1705 /* Oh, looks like we're minimised */
\r
1706 if (win_width == 0 || win_height == 0)
\r
1709 /* Is the window out of position ? */
\r
1711 (offset_width != (win_width-font_width*term->cols)/2 ||
\r
1712 offset_height != (win_height-font_height*term->rows)/2) ){
\r
1713 offset_width = (win_width-font_width*term->cols)/2;
\r
1714 offset_height = (win_height-font_height*term->rows)/2;
\r
1715 InvalidateRect(hwnd, NULL, TRUE);
\r
1716 #ifdef RDB_DEBUG_PATCH
\r
1717 debug((27, "reset_window() -> Reposition terminal"));
\r
1721 if (IsZoomed(hwnd)) {
\r
1722 /* We're fullscreen, this means we must not change the size of
\r
1723 * the window so it's the font size or the terminal itself.
\r
1726 extra_width = wr.right - wr.left - cr.right + cr.left;
\r
1727 extra_height = wr.bottom - wr.top - cr.bottom + cr.top;
\r
1729 if (cfg.resize_action != RESIZE_TERM) {
\r
1730 if ( font_width != win_width/term->cols ||
\r
1731 font_height != win_height/term->rows) {
\r
1733 init_fonts(win_width/term->cols, win_height/term->rows);
\r
1734 offset_width = (win_width-font_width*term->cols)/2;
\r
1735 offset_height = (win_height-font_height*term->rows)/2;
\r
1736 InvalidateRect(hwnd, NULL, TRUE);
\r
1737 #ifdef RDB_DEBUG_PATCH
\r
1738 debug((25, "reset_window() -> Z font resize to (%d, %d)",
\r
1739 font_width, font_height));
\r
1743 if ( font_width * term->cols != win_width ||
\r
1744 font_height * term->rows != win_height) {
\r
1745 /* Our only choice at this point is to change the
\r
1746 * size of the terminal; Oh well.
\r
1748 term_size(term, win_height/font_height, win_width/font_width,
\r
1750 offset_width = (win_width-font_width*term->cols)/2;
\r
1751 offset_height = (win_height-font_height*term->rows)/2;
\r
1752 InvalidateRect(hwnd, NULL, TRUE);
\r
1753 #ifdef RDB_DEBUG_PATCH
\r
1754 debug((27, "reset_window() -> Zoomed term_size"));
\r
1761 /* Hmm, a force re-init means we should ignore the current window
\r
1762 * so we resize to the default font size.
\r
1765 #ifdef RDB_DEBUG_PATCH
\r
1766 debug((27, "reset_window() -> Forced re-init"));
\r
1769 offset_width = offset_height = cfg.window_border;
\r
1770 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
\r
1771 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
\r
1773 if (win_width != font_width*term->cols + offset_width*2 ||
\r
1774 win_height != font_height*term->rows + offset_height*2) {
\r
1776 /* If this is too large windows will resize it to the maximum
\r
1777 * allowed window size, we will then be back in here and resize
\r
1778 * the font or terminal to fit.
\r
1780 SetWindowPos(hwnd, NULL, 0, 0,
\r
1781 font_width*term->cols + extra_width,
\r
1782 font_height*term->rows + extra_height,
\r
1783 SWP_NOMOVE | SWP_NOZORDER);
\r
1786 InvalidateRect(hwnd, NULL, TRUE);
\r
1790 /* Okay the user doesn't want us to change the font so we try the
\r
1791 * window. But that may be too big for the screen which forces us
\r
1792 * to change the terminal.
\r
1794 if ((cfg.resize_action == RESIZE_TERM && reinit<=0) ||
\r
1795 (cfg.resize_action == RESIZE_EITHER && reinit<0) ||
\r
1797 offset_width = offset_height = cfg.window_border;
\r
1798 extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
\r
1799 extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
\r
1801 if (win_width != font_width*term->cols + offset_width*2 ||
\r
1802 win_height != font_height*term->rows + offset_height*2) {
\r
1805 int width, height;
\r
1807 get_fullscreen_rect(&ss);
\r
1809 width = (ss.right - ss.left - extra_width) / font_width;
\r
1810 height = (ss.bottom - ss.top - extra_height) / font_height;
\r
1812 /* Grrr too big */
\r
1813 if ( term->rows > height || term->cols > width ) {
\r
1814 if (cfg.resize_action == RESIZE_EITHER) {
\r
1815 /* Make the font the biggest we can */
\r
1816 if (term->cols > width)
\r
1817 font_width = (ss.right - ss.left - extra_width)
\r
1819 if (term->rows > height)
\r
1820 font_height = (ss.bottom - ss.top - extra_height)
\r
1824 init_fonts(font_width, font_height);
\r
1826 width = (ss.right - ss.left - extra_width) / font_width;
\r
1827 height = (ss.bottom - ss.top - extra_height) / font_height;
\r
1829 if ( height > term->rows ) height = term->rows;
\r
1830 if ( width > term->cols ) width = term->cols;
\r
1831 term_size(term, height, width, cfg.savelines);
\r
1832 #ifdef RDB_DEBUG_PATCH
\r
1833 debug((27, "reset_window() -> term resize to (%d,%d)",
\r
1839 SetWindowPos(hwnd, NULL, 0, 0,
\r
1840 font_width*term->cols + extra_width,
\r
1841 font_height*term->rows + extra_height,
\r
1842 SWP_NOMOVE | SWP_NOZORDER);
\r
1844 InvalidateRect(hwnd, NULL, TRUE);
\r
1845 #ifdef RDB_DEBUG_PATCH
\r
1846 debug((27, "reset_window() -> window resize to (%d,%d)",
\r
1847 font_width*term->cols + extra_width,
\r
1848 font_height*term->rows + extra_height));
\r
1854 /* We're allowed to or must change the font but do we want to ? */
\r
1856 if (font_width != (win_width-cfg.window_border*2)/term->cols ||
\r
1857 font_height != (win_height-cfg.window_border*2)/term->rows) {
\r
1860 init_fonts((win_width-cfg.window_border*2)/term->cols,
\r
1861 (win_height-cfg.window_border*2)/term->rows);
\r
1862 offset_width = (win_width-font_width*term->cols)/2;
\r
1863 offset_height = (win_height-font_height*term->rows)/2;
\r
1865 extra_width = wr.right - wr.left - cr.right + cr.left +offset_width*2;
\r
1866 extra_height = wr.bottom - wr.top - cr.bottom + cr.top+offset_height*2;
\r
1868 InvalidateRect(hwnd, NULL, TRUE);
\r
1869 #ifdef RDB_DEBUG_PATCH
\r
1870 debug((25, "reset_window() -> font resize to (%d,%d)",
\r
1871 font_width, font_height));
\r
1876 static void set_input_locale(HKL kl)
\r
1880 GetLocaleInfo(LOWORD(kl), LOCALE_IDEFAULTANSICODEPAGE,
\r
1881 lbuf, sizeof(lbuf));
\r
1883 kbd_codepage = atoi(lbuf);
\r
1886 static void click(Mouse_Button b, int x, int y, int shift, int ctrl, int alt)
\r
1888 int thistime = GetMessageTime();
\r
1890 if (send_raw_mouse && !(cfg.mouse_override && shift)) {
\r
1891 lastbtn = MBT_NOTHING;
\r
1892 term_mouse(term, b, translate_button(b), MA_CLICK,
\r
1893 x, y, shift, ctrl, alt);
\r
1897 if (lastbtn == b && thistime - lasttime < dbltime) {
\r
1898 lastact = (lastact == MA_CLICK ? MA_2CLK :
\r
1899 lastact == MA_2CLK ? MA_3CLK :
\r
1900 lastact == MA_3CLK ? MA_CLICK : MA_NOTHING);
\r
1903 lastact = MA_CLICK;
\r
1905 if (lastact != MA_NOTHING)
\r
1906 term_mouse(term, b, translate_button(b), lastact,
\r
1907 x, y, shift, ctrl, alt);
\r
1908 lasttime = thistime;
\r
1912 * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT)
\r
1913 * into a cooked one (SELECT, EXTEND, PASTE).
\r
1915 static Mouse_Button translate_button(Mouse_Button button)
\r
1917 if (button == MBT_LEFT)
\r
1918 return MBT_SELECT;
\r
1919 if (button == MBT_MIDDLE)
\r
1920 return cfg.mouse_is_xterm == 1 ? MBT_PASTE : MBT_EXTEND;
\r
1921 if (button == MBT_RIGHT)
\r
1922 return cfg.mouse_is_xterm == 1 ? MBT_EXTEND : MBT_PASTE;
\r
1923 return 0; /* shouldn't happen */
\r
1926 static void show_mouseptr(int show)
\r
1928 /* NB that the counter in ShowCursor() is also frobbed by
\r
1929 * update_mouse_pointer() */
\r
1930 static int cursor_visible = 1;
\r
1931 if (!cfg.hide_mouseptr) /* override if this feature disabled */
\r
1933 if (cursor_visible && !show)
\r
1934 ShowCursor(FALSE);
\r
1935 else if (!cursor_visible && show)
\r
1937 cursor_visible = show;
\r
1940 static int is_alt_pressed(void)
\r
1942 BYTE keystate[256];
\r
1943 int r = GetKeyboardState(keystate);
\r
1946 if (keystate[VK_MENU] & 0x80)
\r
1948 if (keystate[VK_RMENU] & 0x80)
\r
1953 static int resizing;
\r
1955 void notify_remote_exit(void *fe)
\r
1959 if (!session_closed &&
\r
1960 (exitcode = back->exitcode(backhandle)) >= 0) {
\r
1961 /* Abnormal exits will already have set session_closed and taken
\r
1962 * appropriate action. */
\r
1963 if (cfg.close_on_exit == FORCE_ON ||
\r
1964 (cfg.close_on_exit == AUTO && exitcode != INT_MAX)) {
\r
1965 PostQuitMessage(0);
\r
1967 must_close_session = TRUE;
\r
1968 session_closed = TRUE;
\r
1969 /* exitcode == INT_MAX indicates that the connection was closed
\r
1970 * by a fatal error, so an error box will be coming our way and
\r
1971 * we should not generate this informational one. */
\r
1972 if (exitcode != INT_MAX)
\r
1973 MessageBox(hwnd, "Connection closed by remote host",
\r
1974 appname, MB_OK | MB_ICONINFORMATION);
\r
1979 void timer_change_notify(long next)
\r
1981 long ticks = next - GETTICKCOUNT();
\r
1982 if (ticks <= 0) ticks = 1; /* just in case */
\r
1983 KillTimer(hwnd, TIMING_TIMER_ID);
\r
1984 SetTimer(hwnd, TIMING_TIMER_ID, ticks, NULL);
\r
1985 timing_next_time = next;
\r
1988 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
\r
1989 WPARAM wParam, LPARAM lParam)
\r
1992 static int ignore_clip = FALSE;
\r
1993 static int need_backend_resize = FALSE;
\r
1994 static int fullscr_on_max = FALSE;
\r
1995 static int processed_resize = FALSE;
\r
1996 static UINT last_mousemove = 0;
\r
1998 switch (message) {
\r
2000 if ((UINT_PTR)wParam == TIMING_TIMER_ID) {
\r
2003 KillTimer(hwnd, TIMING_TIMER_ID);
\r
2004 if (run_timers(timing_next_time, &next)) {
\r
2005 timer_change_notify(next);
\r
2016 str = dupprintf("%s Exit Confirmation", appname);
\r
2017 if (!cfg.warn_on_close || session_closed ||
\r
2019 "Are you sure you want to close this session?",
\r
2020 str, MB_ICONWARNING | MB_OKCANCEL | MB_DEFBUTTON1)
\r
2022 DestroyWindow(hwnd);
\r
2028 PostQuitMessage(0);
\r
2030 case WM_INITMENUPOPUP:
\r
2031 if ((HMENU)wParam == savedsess_menu) {
\r
2032 /* About to pop up Saved Sessions sub-menu.
\r
2033 * Refresh the session list. */
\r
2034 get_sesslist(&sesslist, FALSE); /* free */
\r
2035 get_sesslist(&sesslist, TRUE);
\r
2036 update_savedsess_menu();
\r
2041 case WM_SYSCOMMAND:
\r
2042 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
\r
2044 showeventlog(hwnd);
\r
2048 case IDM_SAVEDSESS:
\r
2052 int freecl = FALSE;
\r
2053 BOOL inherit_handles;
\r
2055 PROCESS_INFORMATION pi;
\r
2056 HANDLE filemap = NULL;
\r
2058 if (wParam == IDM_DUPSESS) {
\r
2060 * Allocate a file-mapping memory chunk for the
\r
2061 * config structure.
\r
2063 SECURITY_ATTRIBUTES sa;
\r
2066 sa.nLength = sizeof(sa);
\r
2067 sa.lpSecurityDescriptor = NULL;
\r
2068 sa.bInheritHandle = TRUE;
\r
2069 filemap = CreateFileMapping(INVALID_HANDLE_VALUE,
\r
2072 0, sizeof(Config), NULL);
\r
2073 if (filemap && filemap != INVALID_HANDLE_VALUE) {
\r
2074 p = (Config *) MapViewOfFile(filemap,
\r
2076 0, 0, sizeof(Config));
\r
2078 *p = cfg; /* structure copy */
\r
2079 UnmapViewOfFile(p);
\r
2082 inherit_handles = TRUE;
\r
2083 sprintf(c, "putty &%p", filemap);
\r
2085 } else if (wParam == IDM_SAVEDSESS) {
\r
2086 unsigned int sessno = ((lParam - IDM_SAVED_MIN)
\r
2087 / MENU_SAVED_STEP) + 1;
\r
2088 if (sessno < (unsigned)sesslist.nsessions) {
\r
2089 char *session = sesslist.sessions[sessno];
\r
2090 cl = dupprintf("putty @%s", session);
\r
2091 inherit_handles = FALSE;
\r
2095 } else /* IDM_NEWSESS */ {
\r
2097 inherit_handles = FALSE;
\r
2100 GetModuleFileName(NULL, b, sizeof(b) - 1);
\r
2101 si.cb = sizeof(si);
\r
2102 si.lpReserved = NULL;
\r
2103 si.lpDesktop = NULL;
\r
2104 si.lpTitle = NULL;
\r
2106 si.cbReserved2 = 0;
\r
2107 si.lpReserved2 = NULL;
\r
2108 CreateProcess(b, cl, NULL, NULL, inherit_handles,
\r
2109 NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
\r
2112 CloseHandle(filemap);
\r
2119 logevent(NULL, "----- Session restarted -----");
\r
2120 term_pwron(term, FALSE);
\r
2129 int reconfig_result;
\r
2131 if (reconfiguring)
\r
2134 reconfiguring = TRUE;
\r
2136 GetWindowText(hwnd, cfg.wintitle, sizeof(cfg.wintitle));
\r
2140 do_reconfig(hwnd, back ? back->cfg_info(backhandle) : 0);
\r
2141 reconfiguring = FALSE;
\r
2142 if (!reconfig_result)
\r
2146 /* Disable full-screen if resizing forbidden */
\r
2148 for (i = 0; i < lenof(popup_menus); i++)
\r
2149 EnableMenuItem(popup_menus[i].menu, IDM_FULLSCREEN,
\r
2151 (cfg.resize_action == RESIZE_DISABLED)
\r
2152 ? MF_GRAYED : MF_ENABLED);
\r
2153 /* Gracefully unzoom if necessary */
\r
2154 if (IsZoomed(hwnd) &&
\r
2155 (cfg.resize_action == RESIZE_DISABLED)) {
\r
2156 ShowWindow(hwnd, SW_RESTORE);
\r
2160 /* Pass new config data to the logging module */
\r
2161 log_reconfig(logctx, &cfg);
\r
2165 * Flush the line discipline's edit buffer in the
\r
2166 * case where local editing has just been disabled.
\r
2169 ldisc_send(ldisc, NULL, 0, 0);
\r
2171 DeleteObject(pal);
\r
2177 /* Pass new config data to the terminal */
\r
2178 term_reconfig(term, &cfg);
\r
2180 /* Pass new config data to the back end */
\r
2182 back->reconfig(backhandle, &cfg);
\r
2184 /* Screen size changed ? */
\r
2185 if (cfg.height != prev_cfg.height ||
\r
2186 cfg.width != prev_cfg.width ||
\r
2187 cfg.savelines != prev_cfg.savelines ||
\r
2188 cfg.resize_action == RESIZE_FONT ||
\r
2189 (cfg.resize_action == RESIZE_EITHER && IsZoomed(hwnd)) ||
\r
2190 cfg.resize_action == RESIZE_DISABLED)
\r
2191 term_size(term, cfg.height, cfg.width, cfg.savelines);
\r
2193 /* Enable or disable the scroll bar, etc */
\r
2195 LONG nflg, flag = GetWindowLongPtr(hwnd, GWL_STYLE);
\r
2196 LONG nexflag, exflag =
\r
2197 GetWindowLongPtr(hwnd, GWL_EXSTYLE);
\r
2200 if (cfg.alwaysontop != prev_cfg.alwaysontop) {
\r
2201 if (cfg.alwaysontop) {
\r
2202 nexflag |= WS_EX_TOPMOST;
\r
2203 SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
\r
2204 SWP_NOMOVE | SWP_NOSIZE);
\r
2206 nexflag &= ~(WS_EX_TOPMOST);
\r
2207 SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0,
\r
2208 SWP_NOMOVE | SWP_NOSIZE);
\r
2211 if (cfg.sunken_edge)
\r
2212 nexflag |= WS_EX_CLIENTEDGE;
\r
2214 nexflag &= ~(WS_EX_CLIENTEDGE);
\r
2217 if (is_full_screen() ?
\r
2218 cfg.scrollbar_in_fullscreen : cfg.scrollbar)
\r
2219 nflg |= WS_VSCROLL;
\r
2221 nflg &= ~WS_VSCROLL;
\r
2223 if (cfg.resize_action == RESIZE_DISABLED ||
\r
2225 nflg &= ~WS_THICKFRAME;
\r
2227 nflg |= WS_THICKFRAME;
\r
2229 if (cfg.resize_action == RESIZE_DISABLED)
\r
2230 nflg &= ~WS_MAXIMIZEBOX;
\r
2232 nflg |= WS_MAXIMIZEBOX;
\r
2234 if (nflg != flag || nexflag != exflag) {
\r
2236 SetWindowLongPtr(hwnd, GWL_STYLE, nflg);
\r
2237 if (nexflag != exflag)
\r
2238 SetWindowLongPtr(hwnd, GWL_EXSTYLE, nexflag);
\r
2240 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
\r
2241 SWP_NOACTIVATE | SWP_NOCOPYBITS |
\r
2242 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
\r
2243 SWP_FRAMECHANGED);
\r
2250 if (cfg.resize_action == RESIZE_DISABLED && IsZoomed(hwnd)) {
\r
2251 force_normal(hwnd);
\r
2255 set_title(NULL, cfg.wintitle);
\r
2256 if (IsIconic(hwnd)) {
\r
2257 SetWindowText(hwnd,
\r
2258 cfg.win_name_always ? window_name :
\r
2262 if (strcmp(cfg.font.name, prev_cfg.font.name) != 0 ||
\r
2263 strcmp(cfg.line_codepage, prev_cfg.line_codepage) != 0 ||
\r
2264 cfg.font.isbold != prev_cfg.font.isbold ||
\r
2265 cfg.font.height != prev_cfg.font.height ||
\r
2266 cfg.font.charset != prev_cfg.font.charset ||
\r
2267 cfg.font_quality != prev_cfg.font_quality ||
\r
2268 cfg.vtmode != prev_cfg.vtmode ||
\r
2269 cfg.bold_colour != prev_cfg.bold_colour ||
\r
2270 cfg.resize_action == RESIZE_DISABLED ||
\r
2271 cfg.resize_action == RESIZE_EITHER ||
\r
2272 (cfg.resize_action != prev_cfg.resize_action))
\r
2275 InvalidateRect(hwnd, NULL, TRUE);
\r
2276 reset_window(init_lvl);
\r
2277 net_pending_errors();
\r
2281 term_copyall(term);
\r
2284 request_paste(NULL);
\r
2290 term_pwron(term, TRUE);
\r
2292 ldisc_send(ldisc, NULL, 0, 0);
\r
2298 launch_help(hwnd, NULL);
\r
2300 case SC_MOUSEMENU:
\r
2302 * We get this if the System menu has been activated
\r
2303 * using the mouse.
\r
2309 * We get this if the System menu has been activated
\r
2310 * using the keyboard. This might happen from within
\r
2311 * TranslateKey, in which case it really wants to be
\r
2312 * followed by a `space' character to actually _bring
\r
2313 * the menu up_ rather than just sitting there in
\r
2314 * `ready to appear' state.
\r
2316 show_mouseptr(1); /* make sure pointer is visible */
\r
2318 PostMessage(hwnd, WM_CHAR, ' ', 0);
\r
2320 case IDM_FULLSCREEN:
\r
2321 flip_full_screen();
\r
2324 if (wParam >= IDM_SAVED_MIN && wParam < IDM_SAVED_MAX) {
\r
2325 SendMessage(hwnd, WM_SYSCOMMAND, IDM_SAVEDSESS, wParam);
\r
2327 if (wParam >= IDM_SPECIAL_MIN && wParam <= IDM_SPECIAL_MAX) {
\r
2328 int i = (wParam - IDM_SPECIAL_MIN) / 0x10;
\r
2330 * Ensure we haven't been sent a bogus SYSCOMMAND
\r
2331 * which would cause us to reference invalid memory
\r
2332 * and crash. Perhaps I'm just too paranoid here.
\r
2334 if (i >= n_specials)
\r
2337 back->special(backhandle, specials[i].code);
\r
2338 net_pending_errors();
\r
2343 #define X_POS(l) ((int)(short)LOWORD(l))
\r
2344 #define Y_POS(l) ((int)(short)HIWORD(l))
\r
2346 #define TO_CHR_X(x) ((((x)<0 ? (x)-font_width+1 : (x))-offset_width) / font_width)
\r
2347 #define TO_CHR_Y(y) ((((y)<0 ? (y)-font_height+1: (y))-offset_height) / font_height)
\r
2348 case WM_LBUTTONDOWN:
\r
2349 case WM_MBUTTONDOWN:
\r
2350 case WM_RBUTTONDOWN:
\r
2351 case WM_LBUTTONUP:
\r
2352 case WM_MBUTTONUP:
\r
2353 case WM_RBUTTONUP:
\r
2354 if (message == WM_RBUTTONDOWN &&
\r
2355 ((wParam & MK_CONTROL) || (cfg.mouse_is_xterm == 2))) {
\r
2358 show_mouseptr(1); /* make sure pointer is visible */
\r
2359 GetCursorPos(&cursorpos);
\r
2360 TrackPopupMenu(popup_menus[CTXMENU].menu,
\r
2361 TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RIGHTBUTTON,
\r
2362 cursorpos.x, cursorpos.y,
\r
2367 int button, press;
\r
2369 switch (message) {
\r
2370 case WM_LBUTTONDOWN:
\r
2371 button = MBT_LEFT;
\r
2372 wParam |= MK_LBUTTON;
\r
2375 case WM_MBUTTONDOWN:
\r
2376 button = MBT_MIDDLE;
\r
2377 wParam |= MK_MBUTTON;
\r
2380 case WM_RBUTTONDOWN:
\r
2381 button = MBT_RIGHT;
\r
2382 wParam |= MK_RBUTTON;
\r
2385 case WM_LBUTTONUP:
\r
2386 button = MBT_LEFT;
\r
2387 wParam &= ~MK_LBUTTON;
\r
2390 case WM_MBUTTONUP:
\r
2391 button = MBT_MIDDLE;
\r
2392 wParam &= ~MK_MBUTTON;
\r
2395 case WM_RBUTTONUP:
\r
2396 button = MBT_RIGHT;
\r
2397 wParam &= ~MK_RBUTTON;
\r
2401 button = press = 0; /* shouldn't happen */
\r
2405 * Special case: in full-screen mode, if the left
\r
2406 * button is clicked in the very top left corner of the
\r
2407 * window, we put up the System menu instead of doing
\r
2411 char mouse_on_hotspot = 0;
\r
2414 GetCursorPos(&pt);
\r
2415 #ifndef NO_MULTIMON
\r
2420 mon = MonitorFromPoint(pt, MONITOR_DEFAULTTONULL);
\r
2422 if (mon != NULL) {
\r
2423 mi.cbSize = sizeof(MONITORINFO);
\r
2424 GetMonitorInfo(mon, &mi);
\r
2426 if (mi.rcMonitor.left == pt.x &&
\r
2427 mi.rcMonitor.top == pt.y) {
\r
2428 mouse_on_hotspot = 1;
\r
2433 if (pt.x == 0 && pt.y == 0) {
\r
2434 mouse_on_hotspot = 1;
\r
2437 if (is_full_screen() && press &&
\r
2438 button == MBT_LEFT && mouse_on_hotspot) {
\r
2439 SendMessage(hwnd, WM_SYSCOMMAND, SC_MOUSEMENU,
\r
2440 MAKELPARAM(pt.x, pt.y));
\r
2447 TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam)),
\r
2448 wParam & MK_SHIFT, wParam & MK_CONTROL,
\r
2449 is_alt_pressed());
\r
2452 term_mouse(term, button, translate_button(button), MA_RELEASE,
\r
2453 TO_CHR_X(X_POS(lParam)),
\r
2454 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
\r
2455 wParam & MK_CONTROL, is_alt_pressed());
\r
2456 if (!(wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)))
\r
2461 case WM_MOUSEMOVE:
\r
2464 * Windows seems to like to occasionally send MOUSEMOVE
\r
2465 * events even if the mouse hasn't moved. Don't unhide
\r
2466 * the mouse pointer in this case.
\r
2468 static WPARAM wp = 0;
\r
2469 static LPARAM lp = 0;
\r
2470 if (wParam != wp || lParam != lp ||
\r
2471 last_mousemove != WM_MOUSEMOVE) {
\r
2473 wp = wParam; lp = lParam;
\r
2474 last_mousemove = WM_MOUSEMOVE;
\r
2478 * Add the mouse position and message time to the random
\r
2481 noise_ultralight(lParam);
\r
2483 if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON) &&
\r
2484 GetCapture() == hwnd) {
\r
2486 if (wParam & MK_LBUTTON)
\r
2488 else if (wParam & MK_MBUTTON)
\r
2492 term_mouse(term, b, translate_button(b), MA_DRAG,
\r
2493 TO_CHR_X(X_POS(lParam)),
\r
2494 TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
\r
2495 wParam & MK_CONTROL, is_alt_pressed());
\r
2498 case WM_NCMOUSEMOVE:
\r
2500 static WPARAM wp = 0;
\r
2501 static LPARAM lp = 0;
\r
2502 if (wParam != wp || lParam != lp ||
\r
2503 last_mousemove != WM_NCMOUSEMOVE) {
\r
2505 wp = wParam; lp = lParam;
\r
2506 last_mousemove = WM_NCMOUSEMOVE;
\r
2509 noise_ultralight(lParam);
\r
2511 case WM_IGNORE_CLIP:
\r
2512 ignore_clip = wParam; /* don't panic on DESTROYCLIPBOARD */
\r
2514 case WM_DESTROYCLIPBOARD:
\r
2516 term_deselect(term);
\r
2517 ignore_clip = FALSE;
\r
2524 hdc = BeginPaint(hwnd, &p);
\r
2526 SelectPalette(hdc, pal, TRUE);
\r
2527 RealizePalette(hdc);
\r
2531 * We have to be careful about term_paint(). It will
\r
2532 * set a bunch of character cells to INVALID and then
\r
2533 * call do_paint(), which will redraw those cells and
\r
2534 * _then mark them as done_. This may not be accurate:
\r
2535 * when painting in WM_PAINT context we are restricted
\r
2536 * to the rectangle which has just been exposed - so if
\r
2537 * that only covers _part_ of a character cell and the
\r
2538 * rest of it was already visible, that remainder will
\r
2539 * not be redrawn at all. Accordingly, we must not
\r
2540 * paint any character cell in a WM_PAINT context which
\r
2541 * already has a pending update due to terminal output.
\r
2542 * The simplest solution to this - and many, many
\r
2543 * thanks to Hung-Te Lin for working all this out - is
\r
2544 * not to do any actual painting at _all_ if there's a
\r
2545 * pending terminal update: just mark the relevant
\r
2546 * character cells as INVALID and wait for the
\r
2547 * scheduled full update to sort it out.
\r
2549 * I have a suspicion this isn't the _right_ solution.
\r
2550 * An alternative approach would be to have terminal.c
\r
2551 * separately track what _should_ be on the terminal
\r
2552 * screen and what _is_ on the terminal screen, and
\r
2553 * have two completely different types of redraw (one
\r
2554 * for full updates, which syncs the former with the
\r
2555 * terminal itself, and one for WM_PAINT which syncs
\r
2556 * the latter with the former); yet another possibility
\r
2557 * would be to have the Windows front end do what the
\r
2558 * GTK one already does, and maintain a bitmap of the
\r
2559 * current terminal appearance so that WM_PAINT becomes
\r
2560 * completely trivial. However, this should do for now.
\r
2562 term_paint(term, hdc,
\r
2563 (p.rcPaint.left-offset_width)/font_width,
\r
2564 (p.rcPaint.top-offset_height)/font_height,
\r
2565 (p.rcPaint.right-offset_width-1)/font_width,
\r
2566 (p.rcPaint.bottom-offset_height-1)/font_height,
\r
2567 !term->window_update_pending);
\r
2570 p.rcPaint.left < offset_width ||
\r
2571 p.rcPaint.top < offset_height ||
\r
2572 p.rcPaint.right >= offset_width + font_width*term->cols ||
\r
2573 p.rcPaint.bottom>= offset_height + font_height*term->rows)
\r
2575 HBRUSH fillcolour, oldbrush;
\r
2576 HPEN edge, oldpen;
\r
2577 fillcolour = CreateSolidBrush (
\r
2578 colours[ATTR_DEFBG>>ATTR_BGSHIFT]);
\r
2579 oldbrush = SelectObject(hdc, fillcolour);
\r
2580 edge = CreatePen(PS_SOLID, 0,
\r
2581 colours[ATTR_DEFBG>>ATTR_BGSHIFT]);
\r
2582 oldpen = SelectObject(hdc, edge);
\r
2585 * Jordan Russell reports that this apparently
\r
2586 * ineffectual IntersectClipRect() call masks a
\r
2587 * Windows NT/2K bug causing strange display
\r
2588 * problems when the PuTTY window is taller than
\r
2589 * the primary monitor. It seems harmless enough...
\r
2591 IntersectClipRect(hdc,
\r
2592 p.rcPaint.left, p.rcPaint.top,
\r
2593 p.rcPaint.right, p.rcPaint.bottom);
\r
2595 ExcludeClipRect(hdc,
\r
2596 offset_width, offset_height,
\r
2597 offset_width+font_width*term->cols,
\r
2598 offset_height+font_height*term->rows);
\r
2600 Rectangle(hdc, p.rcPaint.left, p.rcPaint.top,
\r
2601 p.rcPaint.right, p.rcPaint.bottom);
\r
2603 /* SelectClipRgn(hdc, NULL); */
\r
2605 SelectObject(hdc, oldbrush);
\r
2606 DeleteObject(fillcolour);
\r
2607 SelectObject(hdc, oldpen);
\r
2608 DeleteObject(edge);
\r
2610 SelectObject(hdc, GetStockObject(SYSTEM_FONT));
\r
2611 SelectObject(hdc, GetStockObject(WHITE_PEN));
\r
2612 EndPaint(hwnd, &p);
\r
2617 /* Notice we can get multiple netevents, FD_READ, FD_WRITE etc
\r
2618 * but the only one that's likely to try to overload us is FD_READ.
\r
2619 * This means buffering just one is fine.
\r
2621 if (pending_netevent)
\r
2622 enact_pending_netevent();
\r
2624 pending_netevent = TRUE;
\r
2625 pend_netevent_wParam = wParam;
\r
2626 pend_netevent_lParam = lParam;
\r
2627 if (WSAGETSELECTEVENT(lParam) != FD_READ)
\r
2628 enact_pending_netevent();
\r
2630 net_pending_errors();
\r
2633 term_set_focus(term, TRUE);
\r
2634 CreateCaret(hwnd, caretbm, font_width, font_height);
\r
2636 flash_window(0); /* stop */
\r
2637 compose_state = 0;
\r
2638 term_update(term);
\r
2640 case WM_KILLFOCUS:
\r
2642 term_set_focus(term, FALSE);
\r
2644 caret_x = caret_y = -1; /* ensure caret is replaced next time */
\r
2645 term_update(term);
\r
2647 case WM_ENTERSIZEMOVE:
\r
2648 #ifdef RDB_DEBUG_PATCH
\r
2649 debug((27, "WM_ENTERSIZEMOVE"));
\r
2653 need_backend_resize = FALSE;
\r
2655 case WM_EXITSIZEMOVE:
\r
2658 #ifdef RDB_DEBUG_PATCH
\r
2659 debug((27, "WM_EXITSIZEMOVE"));
\r
2661 if (need_backend_resize) {
\r
2662 term_size(term, cfg.height, cfg.width, cfg.savelines);
\r
2663 InvalidateRect(hwnd, NULL, TRUE);
\r
2668 * This does two jobs:
\r
2669 * 1) Keep the sizetip uptodate
\r
2670 * 2) Make sure the window size is _stepped_ in units of the font size.
\r
2672 if (cfg.resize_action == RESIZE_TERM ||
\r
2673 (cfg.resize_action == RESIZE_EITHER && !is_alt_pressed())) {
\r
2674 int width, height, w, h, ew, eh;
\r
2675 LPRECT r = (LPRECT) lParam;
\r
2677 if ( !need_backend_resize && cfg.resize_action == RESIZE_EITHER &&
\r
2678 (cfg.height != term->rows || cfg.width != term->cols )) {
\r
2680 * Great! It seems that both the terminal size and the
\r
2681 * font size have been changed and the user is now dragging.
\r
2683 * It will now be difficult to get back to the configured
\r
2686 * This would be easier but it seems to be too confusing.
\r
2688 term_size(term, cfg.height, cfg.width, cfg.savelines);
\r
2691 cfg.height=term->rows; cfg.width=term->cols;
\r
2693 InvalidateRect(hwnd, NULL, TRUE);
\r
2694 need_backend_resize = TRUE;
\r
2697 width = r->right - r->left - extra_width;
\r
2698 height = r->bottom - r->top - extra_height;
\r
2699 w = (width + font_width / 2) / font_width;
\r
2702 h = (height + font_height / 2) / font_height;
\r
2705 UpdateSizeTip(hwnd, w, h);
\r
2706 ew = width - w * font_width;
\r
2707 eh = height - h * font_height;
\r
2709 if (wParam == WMSZ_LEFT ||
\r
2710 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
\r
2716 if (wParam == WMSZ_TOP ||
\r
2717 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
\r
2727 int width, height, w, h, rv = 0;
\r
2728 int ex_width = extra_width + (cfg.window_border - offset_width) * 2;
\r
2729 int ex_height = extra_height + (cfg.window_border - offset_height) * 2;
\r
2730 LPRECT r = (LPRECT) lParam;
\r
2732 width = r->right - r->left - ex_width;
\r
2733 height = r->bottom - r->top - ex_height;
\r
2735 w = (width + term->cols/2)/term->cols;
\r
2736 h = (height + term->rows/2)/term->rows;
\r
2737 if ( r->right != r->left + w*term->cols + ex_width)
\r
2740 if (wParam == WMSZ_LEFT ||
\r
2741 wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
\r
2742 r->left = r->right - w*term->cols - ex_width;
\r
2744 r->right = r->left + w*term->cols + ex_width;
\r
2746 if (r->bottom != r->top + h*term->rows + ex_height)
\r
2749 if (wParam == WMSZ_TOP ||
\r
2750 wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
\r
2751 r->top = r->bottom - h*term->rows - ex_height;
\r
2753 r->bottom = r->top + h*term->rows + ex_height;
\r
2757 /* break; (never reached) */
\r
2758 case WM_FULLSCR_ON_MAX:
\r
2759 fullscr_on_max = TRUE;
\r
2762 sys_cursor_update();
\r
2765 #ifdef RDB_DEBUG_PATCH
\r
2766 debug((27, "WM_SIZE %s (%d,%d)",
\r
2767 (wParam == SIZE_MINIMIZED) ? "SIZE_MINIMIZED":
\r
2768 (wParam == SIZE_MAXIMIZED) ? "SIZE_MAXIMIZED":
\r
2769 (wParam == SIZE_RESTORED && resizing) ? "to":
\r
2770 (wParam == SIZE_RESTORED) ? "SIZE_RESTORED":
\r
2772 LOWORD(lParam), HIWORD(lParam)));
\r
2774 if (wParam == SIZE_MINIMIZED)
\r
2775 SetWindowText(hwnd,
\r
2776 cfg.win_name_always ? window_name : icon_name);
\r
2777 if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
\r
2778 SetWindowText(hwnd, window_name);
\r
2779 if (wParam == SIZE_RESTORED) {
\r
2780 processed_resize = FALSE;
\r
2781 clear_full_screen();
\r
2782 if (processed_resize) {
\r
2784 * Inhibit normal processing of this WM_SIZE; a
\r
2785 * secondary one was triggered just now by
\r
2786 * clear_full_screen which contained the correct
\r
2787 * client area size.
\r
2792 if (wParam == SIZE_MAXIMIZED && fullscr_on_max) {
\r
2793 fullscr_on_max = FALSE;
\r
2794 processed_resize = FALSE;
\r
2795 make_full_screen();
\r
2796 if (processed_resize) {
\r
2798 * Inhibit normal processing of this WM_SIZE; a
\r
2799 * secondary one was triggered just now by
\r
2800 * make_full_screen which contained the correct client
\r
2807 processed_resize = TRUE;
\r
2809 if (cfg.resize_action == RESIZE_DISABLED) {
\r
2810 /* A resize, well it better be a minimize. */
\r
2814 int width, height, w, h;
\r
2816 width = LOWORD(lParam);
\r
2817 height = HIWORD(lParam);
\r
2819 if (wParam == SIZE_MAXIMIZED && !was_zoomed) {
\r
2821 prev_rows = term->rows;
\r
2822 prev_cols = term->cols;
\r
2823 if (cfg.resize_action == RESIZE_TERM) {
\r
2824 w = width / font_width;
\r
2826 h = height / font_height;
\r
2829 term_size(term, h, w, cfg.savelines);
\r
2832 } else if (wParam == SIZE_RESTORED && was_zoomed) {
\r
2834 if (cfg.resize_action == RESIZE_TERM) {
\r
2835 w = (width-cfg.window_border*2) / font_width;
\r
2837 h = (height-cfg.window_border*2) / font_height;
\r
2839 term_size(term, h, w, cfg.savelines);
\r
2841 } else if (cfg.resize_action != RESIZE_FONT)
\r
2845 } else if (wParam == SIZE_MINIMIZED) {
\r
2847 } else if (cfg.resize_action == RESIZE_TERM ||
\r
2848 (cfg.resize_action == RESIZE_EITHER &&
\r
2849 !is_alt_pressed())) {
\r
2850 w = (width-cfg.window_border*2) / font_width;
\r
2852 h = (height-cfg.window_border*2) / font_height;
\r
2857 * Don't call back->size in mid-resize. (To
\r
2858 * prevent massive numbers of resize events
\r
2859 * getting sent down the connection during an NT
\r
2862 need_backend_resize = TRUE;
\r
2866 term_size(term, h, w, cfg.savelines);
\r
2872 sys_cursor_update();
\r
2875 switch (LOWORD(wParam)) {
\r
2877 term_scroll(term, -1, 0);
\r
2880 term_scroll(term, +1, 0);
\r
2883 term_scroll(term, 0, +1);
\r
2886 term_scroll(term, 0, -1);
\r
2889 term_scroll(term, 0, +term->rows / 2);
\r
2892 term_scroll(term, 0, -term->rows / 2);
\r
2894 case SB_THUMBPOSITION:
\r
2895 case SB_THUMBTRACK:
\r
2896 term_scroll(term, 1, HIWORD(wParam));
\r
2900 case WM_PALETTECHANGED:
\r
2901 if ((HWND) wParam != hwnd && pal != NULL) {
\r
2902 HDC hdc = get_ctx(NULL);
\r
2904 if (RealizePalette(hdc) > 0)
\r
2905 UpdateColors(hdc);
\r
2910 case WM_QUERYNEWPALETTE:
\r
2911 if (pal != NULL) {
\r
2912 HDC hdc = get_ctx(NULL);
\r
2914 if (RealizePalette(hdc) > 0)
\r
2915 UpdateColors(hdc);
\r
2922 case WM_SYSKEYDOWN:
\r
2926 * Add the scan code and keypress timing to the random
\r
2929 noise_ultralight(lParam);
\r
2932 * We don't do TranslateMessage since it disassociates the
\r
2933 * resulting CHAR message from the KEYDOWN that sparked it,
\r
2934 * which we occasionally don't want. Instead, we process
\r
2935 * KEYDOWN, and call the Win32 translator functions so that
\r
2936 * we get the translations under _our_ control.
\r
2939 unsigned char buf[20];
\r
2942 if (wParam == VK_PROCESSKEY) { /* IME PROCESS key */
\r
2943 if (message == WM_KEYDOWN) {
\r
2946 m.message = WM_KEYDOWN;
\r
2947 m.wParam = wParam;
\r
2948 m.lParam = lParam & 0xdfff;
\r
2949 TranslateMessage(&m);
\r
2950 } else break; /* pass to Windows for default processing */
\r
2952 len = TranslateKey(message, wParam, lParam, buf);
\r
2954 return DefWindowProc(hwnd, message, wParam, lParam);
\r
2958 * Interrupt an ongoing paste. I'm not sure
\r
2959 * this is sensible, but for the moment it's
\r
2960 * preferable to having to faff about buffering
\r
2963 term_nopaste(term);
\r
2966 * We need not bother about stdin backlogs
\r
2967 * here, because in GUI PuTTY we can't do
\r
2968 * anything about it anyway; there's no means
\r
2969 * of asking Windows to hold off on KEYDOWN
\r
2970 * messages. We _have_ to buffer everything
\r
2973 term_seen_key_event(term);
\r
2975 ldisc_send(ldisc, buf, len, 1);
\r
2980 net_pending_errors();
\r
2982 case WM_INPUTLANGCHANGE:
\r
2983 /* wParam == Font number */
\r
2984 /* lParam == Locale */
\r
2985 set_input_locale((HKL)lParam);
\r
2986 sys_cursor_update();
\r
2988 case WM_IME_STARTCOMPOSITION:
\r
2990 HIMC hImc = ImmGetContext(hwnd);
\r
2991 ImmSetCompositionFont(hImc, &lfont);
\r
2992 ImmReleaseContext(hwnd, hImc);
\r
2995 case WM_IME_COMPOSITION:
\r
3001 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ||
\r
3002 osVersion.dwPlatformId == VER_PLATFORM_WIN32s) break; /* no Unicode */
\r
3004 if ((lParam & GCS_RESULTSTR) == 0) /* Composition unfinished. */
\r
3005 break; /* fall back to DefWindowProc */
\r
3007 hIMC = ImmGetContext(hwnd);
\r
3008 n = ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, NULL, 0);
\r
3012 buff = snewn(n, char);
\r
3013 ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, buff, n);
\r
3015 * Jaeyoun Chung reports that Korean character
\r
3016 * input doesn't work correctly if we do a single
\r
3017 * luni_send() covering the whole of buff. So
\r
3018 * instead we luni_send the characters one by one.
\r
3020 term_seen_key_event(term);
\r
3021 for (i = 0; i < n; i += 2) {
\r
3023 luni_send(ldisc, (unsigned short *)(buff+i), 1, 1);
\r
3027 ImmReleaseContext(hwnd, hIMC);
\r
3032 if (wParam & 0xFF00) {
\r
3033 unsigned char buf[2];
\r
3036 buf[0] = wParam >> 8;
\r
3037 term_seen_key_event(term);
\r
3039 lpage_send(ldisc, kbd_codepage, buf, 2, 1);
\r
3041 char c = (unsigned char) wParam;
\r
3042 term_seen_key_event(term);
\r
3044 lpage_send(ldisc, kbd_codepage, &c, 1, 1);
\r
3050 * Nevertheless, we are prepared to deal with WM_CHAR
\r
3051 * messages, should they crop up. So if someone wants to
\r
3052 * post the things to us as part of a macro manoeuvre,
\r
3053 * we're ready to cope.
\r
3056 char c = (unsigned char)wParam;
\r
3057 term_seen_key_event(term);
\r
3059 lpage_send(ldisc, CP_ACP, &c, 1, 1);
\r
3062 case WM_SYSCOLORCHANGE:
\r
3063 if (cfg.system_colour) {
\r
3064 /* Refresh palette from system colours. */
\r
3065 /* XXX actually this zaps the entire palette. */
\r
3068 /* Force a repaint of the terminal window. */
\r
3069 term_invalidate(term);
\r
3072 case WM_AGENT_CALLBACK:
\r
3074 struct agent_callback *c = (struct agent_callback *)lParam;
\r
3075 c->callback(c->callback_ctx, c->data, c->len);
\r
3079 case WM_GOT_CLIPDATA:
\r
3080 if (process_clipdata((HGLOBAL)lParam, wParam))
\r
3081 term_do_paste(term);
\r
3084 if (message == wm_mousewheel || message == WM_MOUSEWHEEL) {
\r
3085 int shift_pressed=0, control_pressed=0;
\r
3087 if (message == WM_MOUSEWHEEL) {
\r
3088 wheel_accumulator += (short)HIWORD(wParam);
\r
3089 shift_pressed=LOWORD(wParam) & MK_SHIFT;
\r
3090 control_pressed=LOWORD(wParam) & MK_CONTROL;
\r
3093 wheel_accumulator += (int)wParam;
\r
3094 if (GetKeyboardState(keys)!=0) {
\r
3095 shift_pressed=keys[VK_SHIFT]&0x80;
\r
3096 control_pressed=keys[VK_CONTROL]&0x80;
\r
3100 /* process events when the threshold is reached */
\r
3101 while (abs(wheel_accumulator) >= WHEEL_DELTA) {
\r
3104 /* reduce amount for next time */
\r
3105 if (wheel_accumulator > 0) {
\r
3107 wheel_accumulator -= WHEEL_DELTA;
\r
3108 } else if (wheel_accumulator < 0) {
\r
3109 b = MBT_WHEEL_DOWN;
\r
3110 wheel_accumulator += WHEEL_DELTA;
\r
3114 if (send_raw_mouse &&
\r
3115 !(cfg.mouse_override && shift_pressed)) {
\r
3116 /* Mouse wheel position is in screen coordinates for
\r
3119 p.x = X_POS(lParam); p.y = Y_POS(lParam);
\r
3120 if (ScreenToClient(hwnd, &p)) {
\r
3121 /* send a mouse-down followed by a mouse up */
\r
3122 term_mouse(term, b, translate_button(b),
\r
3125 TO_CHR_Y(p.y), shift_pressed,
\r
3126 control_pressed, is_alt_pressed());
\r
3127 term_mouse(term, b, translate_button(b),
\r
3128 MA_RELEASE, TO_CHR_X(p.x),
\r
3129 TO_CHR_Y(p.y), shift_pressed,
\r
3130 control_pressed, is_alt_pressed());
\r
3131 } /* else: not sure when this can fail */
\r
3133 /* trigger a scroll */
\r
3134 term_scroll(term, 0,
\r
3135 b == MBT_WHEEL_UP ?
\r
3136 -term->rows / 2 : term->rows / 2);
\r
3144 * Any messages we don't process completely above are passed through to
\r
3145 * DefWindowProc() for default processing.
\r
3147 return DefWindowProc(hwnd, message, wParam, lParam);
\r
3151 * Move the system caret. (We maintain one, even though it's
\r
3152 * invisible, for the benefit of blind people: apparently some
\r
3153 * helper software tracks the system caret, so we should arrange to
\r
3156 void sys_cursor(void *frontend, int x, int y)
\r
3160 if (!term->has_focus) return;
\r
3163 * Avoid gratuitously re-updating the cursor position and IMM
\r
3164 * window if there's no actual change required.
\r
3166 cx = x * font_width + offset_width;
\r
3167 cy = y * font_height + offset_height;
\r
3168 if (cx == caret_x && cy == caret_y)
\r
3173 sys_cursor_update();
\r
3176 static void sys_cursor_update(void)
\r
3178 COMPOSITIONFORM cf;
\r
3181 if (!term->has_focus) return;
\r
3183 if (caret_x < 0 || caret_y < 0)
\r
3186 SetCaretPos(caret_x, caret_y);
\r
3188 /* IMM calls on Win98 and beyond only */
\r
3189 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32s) return; /* 3.11 */
\r
3191 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS &&
\r
3192 osVersion.dwMinorVersion == 0) return; /* 95 */
\r
3194 /* we should have the IMM functions */
\r
3195 hIMC = ImmGetContext(hwnd);
\r
3196 cf.dwStyle = CFS_POINT;
\r
3197 cf.ptCurrentPos.x = caret_x;
\r
3198 cf.ptCurrentPos.y = caret_y;
\r
3199 ImmSetCompositionWindow(hIMC, &cf);
\r
3201 ImmReleaseContext(hwnd, hIMC);
\r
3205 * Draw a line of text in the window, at given character
\r
3206 * coordinates, in given attributes.
\r
3208 * We are allowed to fiddle with the contents of `text'.
\r
3210 void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len,
\r
3211 unsigned long attr, int lattr)
\r
3213 COLORREF fg, bg, t;
\r
3214 int nfg, nbg, nfont;
\r
3217 int force_manual_underline = 0;
\r
3218 int fnt_width, char_width;
\r
3219 int text_adjust = 0;
\r
3221 int maxlen, remaining, opaque;
\r
3222 static int *lpDx = NULL;
\r
3223 static int lpDx_len = 0;
\r
3226 lattr &= LATTR_MODE;
\r
3228 char_width = fnt_width = font_width * (1 + (lattr != LATTR_NORM));
\r
3230 if (attr & ATTR_WIDE)
\r
3233 /* Only want the left half of double width lines */
\r
3234 if (lattr != LATTR_NORM && x*2 >= term->cols)
\r
3239 x += offset_width;
\r
3240 y += offset_height;
\r
3242 if ((attr & TATTR_ACTCURS) && (cfg.cursor_type == 0 || term->big_cursor)) {
\r
3243 attr &= ~(ATTR_REVERSE|ATTR_BLINK|ATTR_COLOURS);
\r
3244 if (bold_mode == BOLD_COLOURS)
\r
3245 attr &= ~ATTR_BOLD;
\r
3247 /* cursor fg and bg */
\r
3248 attr |= (260 << ATTR_FGSHIFT) | (261 << ATTR_BGSHIFT);
\r
3252 if (cfg.vtmode == VT_POORMAN && lattr != LATTR_NORM) {
\r
3253 /* Assume a poorman font is borken in other ways too. */
\r
3254 lattr = LATTR_WIDE;
\r
3260 nfont |= FONT_WIDE;
\r
3263 nfont |= FONT_WIDE + FONT_HIGH;
\r
3266 if (attr & ATTR_NARROW)
\r
3267 nfont |= FONT_NARROW;
\r
3269 /* Special hack for the VT100 linedraw glyphs. */
\r
3270 if (text[0] >= 0x23BA && text[0] <= 0x23BD) {
\r
3271 switch ((unsigned char) (text[0])) {
\r
3273 text_adjust = -2 * font_height / 5;
\r
3276 text_adjust = -1 * font_height / 5;
\r
3279 text_adjust = font_height / 5;
\r
3282 text_adjust = 2 * font_height / 5;
\r
3285 if (lattr == LATTR_TOP || lattr == LATTR_BOT)
\r
3287 text[0] = ucsdata.unitab_xterm['q'];
\r
3288 if (attr & ATTR_UNDER) {
\r
3289 attr &= ~ATTR_UNDER;
\r
3290 force_manual_underline = 1;
\r
3294 /* Anything left as an original character set is unprintable. */
\r
3295 if (DIRECT_CHAR(text[0])) {
\r
3297 for (i = 0; i < len; i++)
\r
3302 if ((text[0] & CSET_MASK) == CSET_OEMCP)
\r
3303 nfont |= FONT_OEM;
\r
3305 nfg = ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
\r
3306 nbg = ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
\r
3307 if (bold_mode == BOLD_FONT && (attr & ATTR_BOLD))
\r
3308 nfont |= FONT_BOLD;
\r
3309 if (und_mode == UND_FONT && (attr & ATTR_UNDER))
\r
3310 nfont |= FONT_UNDERLINE;
\r
3311 another_font(nfont);
\r
3312 if (!fonts[nfont]) {
\r
3313 if (nfont & FONT_UNDERLINE)
\r
3314 force_manual_underline = 1;
\r
3315 /* Don't do the same for manual bold, it could be bad news. */
\r
3317 nfont &= ~(FONT_BOLD | FONT_UNDERLINE);
\r
3319 another_font(nfont);
\r
3320 if (!fonts[nfont])
\r
3321 nfont = FONT_NORMAL;
\r
3322 if (attr & ATTR_REVERSE) {
\r
3327 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BOLD)) {
\r
3328 if (nfg < 16) nfg |= 8;
\r
3329 else if (nfg >= 256) nfg |= 1;
\r
3331 if (bold_mode == BOLD_COLOURS && (attr & ATTR_BLINK)) {
\r
3332 if (nbg < 16) nbg |= 8;
\r
3333 else if (nbg >= 256) nbg |= 1;
\r
3335 fg = colours[nfg];
\r
3336 bg = colours[nbg];
\r
3337 SelectObject(hdc, fonts[nfont]);
\r
3338 SetTextColor(hdc, fg);
\r
3339 SetBkColor(hdc, bg);
\r
3340 if (attr & TATTR_COMBINING)
\r
3341 SetBkMode(hdc, TRANSPARENT);
\r
3343 SetBkMode(hdc, OPAQUE);
\r
3344 line_box.left = x;
\r
3346 line_box.right = x + char_width * len;
\r
3347 line_box.bottom = y + font_height;
\r
3349 /* Only want the left half of double width lines */
\r
3350 if (line_box.right > font_width*term->cols+offset_width)
\r
3351 line_box.right = font_width*term->cols+offset_width;
\r
3353 if (font_varpitch) {
\r
3355 * If we're using a variable-pitch font, we unconditionally
\r
3356 * draw the glyphs one at a time and centre them in their
\r
3357 * character cells (which means in particular that we must
\r
3358 * disable the lpDx mechanism). This gives slightly odd but
\r
3359 * generally reasonable results.
\r
3361 xoffset = char_width / 2;
\r
3362 SetTextAlign(hdc, TA_TOP | TA_CENTER | TA_NOUPDATECP);
\r
3363 lpDx_maybe = NULL;
\r
3367 * In a fixed-pitch font, we draw the whole string in one go
\r
3368 * in the normal way.
\r
3371 SetTextAlign(hdc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
\r
3372 lpDx_maybe = lpDx;
\r
3376 opaque = TRUE; /* start by erasing the rectangle */
\r
3377 for (remaining = len; remaining > 0;
\r
3378 text += len, remaining -= len, x += char_width * len) {
\r
3379 len = (maxlen < remaining ? maxlen : remaining);
\r
3381 if (len > lpDx_len) {
\r
3382 if (len > lpDx_len) {
\r
3383 lpDx_len = len * 9 / 8 + 16;
\r
3384 lpDx = sresize(lpDx, lpDx_len, int);
\r
3389 for (i = 0; i < len; i++)
\r
3390 lpDx[i] = char_width;
\r
3393 /* We're using a private area for direct to font. (512 chars.) */
\r
3394 if (ucsdata.dbcs_screenfont && (text[0] & CSET_MASK) == CSET_ACP) {
\r
3395 /* Ho Hum, dbcs fonts are a PITA! */
\r
3396 /* To display on W9x I have to convert to UCS */
\r
3397 static wchar_t *uni_buf = 0;
\r
3398 static int uni_len = 0;
\r
3400 if (len > uni_len) {
\r
3403 uni_buf = snewn(uni_len, wchar_t);
\r
3406 for(nlen = mptr = 0; mptr<len; mptr++) {
\r
3407 uni_buf[nlen] = 0xFFFD;
\r
3408 if (IsDBCSLeadByteEx(ucsdata.font_codepage,
\r
3409 (BYTE) text[mptr])) {
\r
3411 dbcstext[0] = text[mptr] & 0xFF;
\r
3412 dbcstext[1] = text[mptr+1] & 0xFF;
\r
3413 lpDx[nlen] += char_width;
\r
3414 MultiByteToWideChar(ucsdata.font_codepage, MB_USEGLYPHCHARS,
\r
3415 dbcstext, 2, uni_buf+nlen, 1);
\r
3421 dbcstext[0] = text[mptr] & 0xFF;
\r
3422 MultiByteToWideChar(ucsdata.font_codepage, MB_USEGLYPHCHARS,
\r
3423 dbcstext, 1, uni_buf+nlen, 1);
\r
3428 return; /* Eeek! */
\r
3430 ExtTextOutW(hdc, x + xoffset,
\r
3431 y - font_height * (lattr == LATTR_BOT) + text_adjust,
\r
3432 ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0),
\r
3433 &line_box, uni_buf, nlen,
\r
3435 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
\r
3436 SetBkMode(hdc, TRANSPARENT);
\r
3437 ExtTextOutW(hdc, x + xoffset - 1,
\r
3438 y - font_height * (lattr ==
\r
3439 LATTR_BOT) + text_adjust,
\r
3440 ETO_CLIPPED, &line_box, uni_buf, nlen, lpDx_maybe);
\r
3444 } else if (DIRECT_FONT(text[0])) {
\r
3445 static char *directbuf = NULL;
\r
3446 static int directlen = 0;
\r
3448 if (len > directlen) {
\r
3450 directbuf = sresize(directbuf, directlen, char);
\r
3453 for (i = 0; i < len; i++)
\r
3454 directbuf[i] = text[i] & 0xFF;
\r
3456 ExtTextOut(hdc, x + xoffset,
\r
3457 y - font_height * (lattr == LATTR_BOT) + text_adjust,
\r
3458 ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0),
\r
3459 &line_box, directbuf, len, lpDx_maybe);
\r
3460 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
\r
3461 SetBkMode(hdc, TRANSPARENT);
\r
3463 /* GRR: This draws the character outside its box and
\r
3464 * can leave 'droppings' even with the clip box! I
\r
3465 * suppose I could loop it one character at a time ...
\r
3468 * Or ... I could do a test print with "W", and use +1
\r
3469 * or -1 for this shift depending on if the leftmost
\r
3470 * column is blank...
\r
3472 ExtTextOut(hdc, x + xoffset - 1,
\r
3473 y - font_height * (lattr ==
\r
3474 LATTR_BOT) + text_adjust,
\r
3475 ETO_CLIPPED, &line_box, directbuf, len, lpDx_maybe);
\r
3478 /* And 'normal' unicode characters */
\r
3479 static WCHAR *wbuf = NULL;
\r
3480 static int wlen = 0;
\r
3486 wbuf = snewn(wlen, WCHAR);
\r
3489 for (i = 0; i < len; i++)
\r
3490 wbuf[i] = text[i];
\r
3492 /* print Glyphs as they are, without Windows' Shaping*/
\r
3493 general_textout(hdc, x + xoffset,
\r
3494 y - font_height * (lattr==LATTR_BOT) + text_adjust,
\r
3495 &line_box, wbuf, len, lpDx,
\r
3496 opaque && !(attr & TATTR_COMBINING));
\r
3498 /* And the shadow bold hack. */
\r
3499 if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
\r
3500 SetBkMode(hdc, TRANSPARENT);
\r
3501 ExtTextOutW(hdc, x + xoffset - 1,
\r
3502 y - font_height * (lattr ==
\r
3503 LATTR_BOT) + text_adjust,
\r
3504 ETO_CLIPPED, &line_box, wbuf, len, lpDx_maybe);
\r
3509 * If we're looping round again, stop erasing the background
\r
3512 SetBkMode(hdc, TRANSPARENT);
\r
3515 if (lattr != LATTR_TOP && (force_manual_underline ||
\r
3516 (und_mode == UND_LINE
\r
3517 && (attr & ATTR_UNDER)))) {
\r
3519 int dec = descent;
\r
3520 if (lattr == LATTR_BOT)
\r
3521 dec = dec * 2 - font_height;
\r
3523 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, fg));
\r
3524 MoveToEx(hdc, line_box.left, line_box.top + dec, NULL);
\r
3525 LineTo(hdc, line_box.right, line_box.top + dec);
\r
3526 oldpen = SelectObject(hdc, oldpen);
\r
3527 DeleteObject(oldpen);
\r
3532 * Wrapper that handles combining characters.
\r
3534 void do_text(Context ctx, int x, int y, wchar_t *text, int len,
\r
3535 unsigned long attr, int lattr)
\r
3537 if (attr & TATTR_COMBINING) {
\r
3538 unsigned long a = 0;
\r
3539 attr &= ~TATTR_COMBINING;
\r
3541 do_text_internal(ctx, x, y, text, 1, attr | a, lattr);
\r
3543 a = TATTR_COMBINING;
\r
3546 do_text_internal(ctx, x, y, text, len, attr, lattr);
\r
3549 void do_cursor(Context ctx, int x, int y, wchar_t *text, int len,
\r
3550 unsigned long attr, int lattr)
\r
3556 int ctype = cfg.cursor_type;
\r
3558 lattr &= LATTR_MODE;
\r
3560 if ((attr & TATTR_ACTCURS) && (ctype == 0 || term->big_cursor)) {
\r
3561 if (*text != UCSWIDE) {
\r
3562 do_text(ctx, x, y, text, len, attr, lattr);
\r
3566 attr |= TATTR_RIGHTCURS;
\r
3569 fnt_width = char_width = font_width * (1 + (lattr != LATTR_NORM));
\r
3570 if (attr & ATTR_WIDE)
\r
3574 x += offset_width;
\r
3575 y += offset_height;
\r
3577 if ((attr & TATTR_PASCURS) && (ctype == 0 || term->big_cursor)) {
\r
3580 pts[0].x = pts[1].x = pts[4].x = x;
\r
3581 pts[2].x = pts[3].x = x + char_width - 1;
\r
3582 pts[0].y = pts[3].y = pts[4].y = y;
\r
3583 pts[1].y = pts[2].y = y + font_height - 1;
\r
3584 oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[261]));
\r
3585 Polyline(hdc, pts, 5);
\r
3586 oldpen = SelectObject(hdc, oldpen);
\r
3587 DeleteObject(oldpen);
\r
3588 } else if ((attr & (TATTR_ACTCURS | TATTR_PASCURS)) && ctype != 0) {
\r
3589 int startx, starty, dx, dy, length, i;
\r
3592 starty = y + descent;
\r
3595 length = char_width;
\r
3598 if (attr & TATTR_RIGHTCURS)
\r
3599 xadjust = char_width - 1;
\r
3600 startx = x + xadjust;
\r
3604 length = font_height;
\r
3606 if (attr & TATTR_ACTCURS) {
\r
3609 SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[261]));
\r
3610 MoveToEx(hdc, startx, starty, NULL);
\r
3611 LineTo(hdc, startx + dx * length, starty + dy * length);
\r
3612 oldpen = SelectObject(hdc, oldpen);
\r
3613 DeleteObject(oldpen);
\r
3615 for (i = 0; i < length; i++) {
\r
3617 SetPixel(hdc, startx, starty, colours[261]);
\r
3626 /* This function gets the actual width of a character in the normal font.
\r
3628 int char_width(Context ctx, int uc) {
\r
3632 /* If the font max is the same as the font ave width then this
\r
3633 * function is a no-op.
\r
3635 if (!font_dualwidth) return 1;
\r
3637 switch (uc & CSET_MASK) {
\r
3639 uc = ucsdata.unitab_line[uc & 0xFF];
\r
3641 case CSET_LINEDRW:
\r
3642 uc = ucsdata.unitab_xterm[uc & 0xFF];
\r
3645 uc = ucsdata.unitab_scoacs[uc & 0xFF];
\r
3648 if (DIRECT_FONT(uc)) {
\r
3649 if (ucsdata.dbcs_screenfont) return 1;
\r
3651 /* Speedup, I know of no font where ascii is the wrong width */
\r
3652 if ((uc&~CSET_MASK) >= ' ' && (uc&~CSET_MASK)<= '~')
\r
3655 if ( (uc & CSET_MASK) == CSET_ACP ) {
\r
3656 SelectObject(hdc, fonts[FONT_NORMAL]);
\r
3657 } else if ( (uc & CSET_MASK) == CSET_OEMCP ) {
\r
3658 another_font(FONT_OEM);
\r
3659 if (!fonts[FONT_OEM]) return 0;
\r
3661 SelectObject(hdc, fonts[FONT_OEM]);
\r
3665 if ( GetCharWidth32(hdc, uc&~CSET_MASK, uc&~CSET_MASK, &ibuf) != 1 &&
\r
3666 GetCharWidth(hdc, uc&~CSET_MASK, uc&~CSET_MASK, &ibuf) != 1)
\r
3669 /* Speedup, I know of no font where ascii is the wrong width */
\r
3670 if (uc >= ' ' && uc <= '~') return 1;
\r
3672 SelectObject(hdc, fonts[FONT_NORMAL]);
\r
3673 if ( GetCharWidth32W(hdc, uc, uc, &ibuf) == 1 )
\r
3674 /* Okay that one worked */ ;
\r
3675 else if ( GetCharWidthW(hdc, uc, uc, &ibuf) == 1 )
\r
3676 /* This should work on 9x too, but it's "less accurate" */ ;
\r
3681 ibuf += font_width / 2 -1;
\r
3682 ibuf /= font_width;
\r
3688 * Translate a WM_(SYS)?KEY(UP|DOWN) message into a string of ASCII
\r
3689 * codes. Returns number of bytes used, zero to drop the message,
\r
3690 * -1 to forward the message to Windows, or another negative number
\r
3691 * to indicate a NUL-terminated "special" string.
\r
3693 static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
\r
3694 unsigned char *output)
\r
3696 BYTE keystate[256];
\r
3697 int scan, left_alt = 0, key_down, shift_state;
\r
3699 unsigned char *p = output;
\r
3700 static int alt_sum = 0;
\r
3702 HKL kbd_layout = GetKeyboardLayout(0);
\r
3704 /* keys is for ToAsciiEx. There's some ick here, see below. */
\r
3705 static WORD keys[3];
\r
3706 static int compose_char = 0;
\r
3707 static WPARAM compose_key = 0;
\r
3709 r = GetKeyboardState(keystate);
\r
3711 memset(keystate, 0, sizeof(keystate));
\r
3714 #define SHOW_TOASCII_RESULT
\r
3715 { /* Tell us all about key events */
\r
3716 static BYTE oldstate[256];
\r
3717 static int first = 1;
\r
3721 memcpy(oldstate, keystate, sizeof(oldstate));
\r
3724 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT) {
\r
3726 } else if ((HIWORD(lParam) & KF_UP)
\r
3727 && scan == (HIWORD(lParam) & 0xFF)) {
\r
3731 if (wParam >= VK_F1 && wParam <= VK_F20)
\r
3732 debug(("K_F%d", wParam + 1 - VK_F1));
\r
3745 debug(("VK_%02x", wParam));
\r
3747 if (message == WM_SYSKEYDOWN || message == WM_SYSKEYUP)
\r
3749 debug((", S%02x", scan = (HIWORD(lParam) & 0xFF)));
\r
3751 ch = MapVirtualKeyEx(wParam, 2, kbd_layout);
\r
3752 if (ch >= ' ' && ch <= '~')
\r
3753 debug((", '%c'", ch));
\r
3755 debug((", $%02x", ch));
\r
3758 debug((", KB0=%02x", keys[0]));
\r
3760 debug((", KB1=%02x", keys[1]));
\r
3762 debug((", KB2=%02x", keys[2]));
\r
3764 if ((keystate[VK_SHIFT] & 0x80) != 0)
\r
3766 if ((keystate[VK_CONTROL] & 0x80) != 0)
\r
3768 if ((HIWORD(lParam) & KF_EXTENDED))
\r
3770 if ((HIWORD(lParam) & KF_UP))
\r
3774 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT);
\r
3775 else if ((HIWORD(lParam) & KF_UP))
\r
3776 oldstate[wParam & 0xFF] ^= 0x80;
\r
3778 oldstate[wParam & 0xFF] ^= 0x81;
\r
3780 for (ch = 0; ch < 256; ch++)
\r
3781 if (oldstate[ch] != keystate[ch])
\r
3782 debug((", M%02x=%02x", ch, keystate[ch]));
\r
3784 memcpy(oldstate, keystate, sizeof(oldstate));
\r
3788 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED)) {
\r
3789 keystate[VK_RMENU] = keystate[VK_MENU];
\r
3793 /* Nastyness with NUMLock - Shift-NUMLock is left alone though */
\r
3794 if ((cfg.funky_type == FUNKY_VT400 ||
\r
3795 (cfg.funky_type <= FUNKY_LINUX && term->app_keypad_keys &&
\r
3796 !cfg.no_applic_k))
\r
3797 && wParam == VK_NUMLOCK && !(keystate[VK_SHIFT] & 0x80)) {
\r
3799 wParam = VK_EXECUTE;
\r
3801 /* UnToggle NUMLock */
\r
3802 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0)
\r
3803 keystate[VK_NUMLOCK] ^= 1;
\r
3806 /* And write back the 'adjusted' state */
\r
3807 SetKeyboardState(keystate);
\r
3810 /* Disable Auto repeat if required */
\r
3811 if (term->repeat_off &&
\r
3812 (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT)
\r
3815 if ((HIWORD(lParam) & KF_ALTDOWN) && (keystate[VK_RMENU] & 0x80) == 0)
\r
3818 key_down = ((HIWORD(lParam) & KF_UP) == 0);
\r
3820 /* Make sure Ctrl-ALT is not the same as AltGr for ToAscii unless told. */
\r
3821 if (left_alt && (keystate[VK_CONTROL] & 0x80)) {
\r
3822 if (cfg.ctrlaltkeys)
\r
3823 keystate[VK_MENU] = 0;
\r
3825 keystate[VK_RMENU] = 0x80;
\r
3830 scan = (HIWORD(lParam) & (KF_UP | KF_EXTENDED | 0xFF));
\r
3831 shift_state = ((keystate[VK_SHIFT] & 0x80) != 0)
\r
3832 + ((keystate[VK_CONTROL] & 0x80) != 0) * 2;
\r
3834 /* Note if AltGr was pressed and if it was used as a compose key */
\r
3835 if (!compose_state) {
\r
3836 compose_key = 0x100;
\r
3837 if (cfg.compose_key) {
\r
3838 if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED))
\r
3839 compose_key = wParam;
\r
3841 if (wParam == VK_APPS)
\r
3842 compose_key = wParam;
\r
3845 if (wParam == compose_key) {
\r
3846 if (compose_state == 0
\r
3847 && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0) compose_state =
\r
3849 else if (compose_state == 1 && (HIWORD(lParam) & KF_UP))
\r
3850 compose_state = 2;
\r
3852 compose_state = 0;
\r
3853 } else if (compose_state == 1 && wParam != VK_CONTROL)
\r
3854 compose_state = 0;
\r
3856 if (compose_state > 1 && left_alt)
\r
3857 compose_state = 0;
\r
3859 /* Sanitize the number pad if not using a PC NumPad */
\r
3860 if (left_alt || (term->app_keypad_keys && !cfg.no_applic_k
\r
3861 && cfg.funky_type != FUNKY_XTERM)
\r
3862 || cfg.funky_type == FUNKY_VT400 || cfg.nethack_keypad || compose_state) {
\r
3863 if ((HIWORD(lParam) & KF_EXTENDED) == 0) {
\r
3867 nParam = VK_NUMPAD0;
\r
3870 nParam = VK_NUMPAD1;
\r
3873 nParam = VK_NUMPAD2;
\r
3876 nParam = VK_NUMPAD3;
\r
3879 nParam = VK_NUMPAD4;
\r
3882 nParam = VK_NUMPAD5;
\r
3885 nParam = VK_NUMPAD6;
\r
3888 nParam = VK_NUMPAD7;
\r
3891 nParam = VK_NUMPAD8;
\r
3894 nParam = VK_NUMPAD9;
\r
3897 nParam = VK_DECIMAL;
\r
3901 if (keystate[VK_NUMLOCK] & 1)
\r
3908 /* If a key is pressed and AltGr is not active */
\r
3909 if (key_down && (keystate[VK_RMENU] & 0x80) == 0 && !compose_state) {
\r
3910 /* Okay, prepare for most alts then ... */
\r
3914 /* Lets see if it's a pattern we know all about ... */
\r
3915 if (wParam == VK_PRIOR && shift_state == 1) {
\r
3916 SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0);
\r
3919 if (wParam == VK_PRIOR && shift_state == 2) {
\r
3920 SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0);
\r
3923 if (wParam == VK_NEXT && shift_state == 1) {
\r
3924 SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
\r
3927 if (wParam == VK_NEXT && shift_state == 2) {
\r
3928 SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0);
\r
3931 if ((wParam == VK_PRIOR || wParam == VK_NEXT) && shift_state == 3) {
\r
3932 term_scroll_to_selection(term, (wParam == VK_PRIOR ? 0 : 1));
\r
3935 if (wParam == VK_INSERT && shift_state == 1) {
\r
3936 request_paste(NULL);
\r
3939 if (left_alt && wParam == VK_F4 && cfg.alt_f4) {
\r
3942 if (left_alt && wParam == VK_SPACE && cfg.alt_space) {
\r
3943 SendMessage(hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0);
\r
3946 if (left_alt && wParam == VK_RETURN && cfg.fullscreenonaltenter &&
\r
3947 (cfg.resize_action != RESIZE_DISABLED)) {
\r
3948 if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) != KF_REPEAT)
\r
3949 flip_full_screen();
\r
3952 /* Control-Numlock for app-keypad mode switch */
\r
3953 if (wParam == VK_PAUSE && shift_state == 2) {
\r
3954 term->app_keypad_keys ^= 1;
\r
3958 /* Nethack keypad */
\r
3959 if (cfg.nethack_keypad && !left_alt) {
\r
3962 *p++ = "bB\002\002"[shift_state & 3];
\r
3963 return p - output;
\r
3965 *p++ = "jJ\012\012"[shift_state & 3];
\r
3966 return p - output;
\r
3968 *p++ = "nN\016\016"[shift_state & 3];
\r
3969 return p - output;
\r
3971 *p++ = "hH\010\010"[shift_state & 3];
\r
3972 return p - output;
\r
3974 *p++ = shift_state ? '.' : '.';
\r
3975 return p - output;
\r
3977 *p++ = "lL\014\014"[shift_state & 3];
\r
3978 return p - output;
\r
3980 *p++ = "yY\031\031"[shift_state & 3];
\r
3981 return p - output;
\r
3983 *p++ = "kK\013\013"[shift_state & 3];
\r
3984 return p - output;
\r
3986 *p++ = "uU\025\025"[shift_state & 3];
\r
3987 return p - output;
\r
3991 /* Application Keypad */
\r
3995 if (cfg.funky_type == FUNKY_VT400 ||
\r
3996 (cfg.funky_type <= FUNKY_LINUX &&
\r
3997 term->app_keypad_keys && !cfg.no_applic_k)) switch (wParam) {
\r
4011 if (term->app_keypad_keys && !cfg.no_applic_k)
\r
4048 if (cfg.funky_type == FUNKY_XTERM) {
\r
4053 } else if (shift_state)
\r
4060 if (cfg.funky_type == FUNKY_XTERM)
\r
4064 if (cfg.funky_type == FUNKY_XTERM)
\r
4068 if (cfg.funky_type == FUNKY_XTERM)
\r
4073 if (HIWORD(lParam) & KF_EXTENDED)
\r
4078 if (term->vt52_mode) {
\r
4079 if (xkey >= 'P' && xkey <= 'S')
\r
4080 p += sprintf((char *) p, "\x1B%c", xkey);
\r
4082 p += sprintf((char *) p, "\x1B?%c", xkey);
\r
4084 p += sprintf((char *) p, "\x1BO%c", xkey);
\r
4085 return p - output;
\r
4089 if (wParam == VK_BACK && shift_state == 0) { /* Backspace */
\r
4090 *p++ = (cfg.bksp_is_delete ? 0x7F : 0x08);
\r
4094 if (wParam == VK_BACK && shift_state == 1) { /* Shift Backspace */
\r
4095 /* We do the opposite of what is configured */
\r
4096 *p++ = (cfg.bksp_is_delete ? 0x08 : 0x7F);
\r
4100 if (wParam == VK_TAB && shift_state == 1) { /* Shift tab */
\r
4104 return p - output;
\r
4106 if (wParam == VK_SPACE && shift_state == 2) { /* Ctrl-Space */
\r
4108 return p - output;
\r
4110 if (wParam == VK_SPACE && shift_state == 3) { /* Ctrl-Shift-Space */
\r
4112 return p - output;
\r
4114 if (wParam == VK_CANCEL && shift_state == 2) { /* Ctrl-Break */
\r
4116 back->special(backhandle, TS_BRK);
\r
4119 if (wParam == VK_PAUSE) { /* Break/Pause */
\r
4124 /* Control-2 to Control-8 are special */
\r
4125 if (shift_state == 2 && wParam >= '2' && wParam <= '8') {
\r
4126 *p++ = "\000\033\034\035\036\037\177"[wParam - '2'];
\r
4127 return p - output;
\r
4129 if (shift_state == 2 && (wParam == 0xBD || wParam == 0xBF)) {
\r
4131 return p - output;
\r
4133 if (shift_state == 2 && (wParam == 0xDF || wParam == 0xDC)) {
\r
4135 return p - output;
\r
4137 if (shift_state == 3 && wParam == 0xDE) {
\r
4138 *p++ = 0x1E; /* Ctrl-~ == Ctrl-^ in xterm at least */
\r
4139 return p - output;
\r
4141 if (shift_state == 0 && wParam == VK_RETURN && term->cr_lf_return) {
\r
4144 return p - output;
\r
4148 * Next, all the keys that do tilde codes. (ESC '[' nn '~',
\r
4149 * for integer decimal nn.)
\r
4151 * We also deal with the weird ones here. Linux VCs replace F1
\r
4152 * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
\r
4153 * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
\r
4159 code = (keystate[VK_SHIFT] & 0x80 ? 23 : 11);
\r
4162 code = (keystate[VK_SHIFT] & 0x80 ? 24 : 12);
\r
4165 code = (keystate[VK_SHIFT] & 0x80 ? 25 : 13);
\r
4168 code = (keystate[VK_SHIFT] & 0x80 ? 26 : 14);
\r
4171 code = (keystate[VK_SHIFT] & 0x80 ? 28 : 15);
\r
4174 code = (keystate[VK_SHIFT] & 0x80 ? 29 : 17);
\r
4177 code = (keystate[VK_SHIFT] & 0x80 ? 31 : 18);
\r
4180 code = (keystate[VK_SHIFT] & 0x80 ? 32 : 19);
\r
4183 code = (keystate[VK_SHIFT] & 0x80 ? 33 : 20);
\r
4186 code = (keystate[VK_SHIFT] & 0x80 ? 34 : 21);
\r
4219 if ((shift_state&2) == 0) switch (wParam) {
\r
4239 /* Reorder edit keys to physical order */
\r
4240 if (cfg.funky_type == FUNKY_VT400 && code <= 6)
\r
4241 code = "\0\2\1\4\5\3\6"[code];
\r
4243 if (term->vt52_mode && code > 0 && code <= 6) {
\r
4244 p += sprintf((char *) p, "\x1B%c", " HLMEIG"[code]);
\r
4245 return p - output;
\r
4248 if (cfg.funky_type == FUNKY_SCO && /* SCO function keys */
\r
4249 code >= 11 && code <= 34) {
\r
4250 char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
\r
4253 case VK_F1: index = 0; break;
\r
4254 case VK_F2: index = 1; break;
\r
4255 case VK_F3: index = 2; break;
\r
4256 case VK_F4: index = 3; break;
\r
4257 case VK_F5: index = 4; break;
\r
4258 case VK_F6: index = 5; break;
\r
4259 case VK_F7: index = 6; break;
\r
4260 case VK_F8: index = 7; break;
\r
4261 case VK_F9: index = 8; break;
\r
4262 case VK_F10: index = 9; break;
\r
4263 case VK_F11: index = 10; break;
\r
4264 case VK_F12: index = 11; break;
\r
4266 if (keystate[VK_SHIFT] & 0x80) index += 12;
\r
4267 if (keystate[VK_CONTROL] & 0x80) index += 24;
\r
4268 p += sprintf((char *) p, "\x1B[%c", codes[index]);
\r
4269 return p - output;
\r
4271 if (cfg.funky_type == FUNKY_SCO && /* SCO small keypad */
\r
4272 code >= 1 && code <= 6) {
\r
4273 char codes[] = "HL.FIG";
\r
4277 p += sprintf((char *) p, "\x1B[%c", codes[code-1]);
\r
4279 return p - output;
\r
4281 if ((term->vt52_mode || cfg.funky_type == FUNKY_VT100P) && code >= 11 && code <= 24) {
\r
4287 if (term->vt52_mode)
\r
4288 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11 - offt);
\r
4291 sprintf((char *) p, "\x1BO%c", code + 'P' - 11 - offt);
\r
4292 return p - output;
\r
4294 if (cfg.funky_type == FUNKY_LINUX && code >= 11 && code <= 15) {
\r
4295 p += sprintf((char *) p, "\x1B[[%c", code + 'A' - 11);
\r
4296 return p - output;
\r
4298 if (cfg.funky_type == FUNKY_XTERM && code >= 11 && code <= 14) {
\r
4299 if (term->vt52_mode)
\r
4300 p += sprintf((char *) p, "\x1B%c", code + 'P' - 11);
\r
4302 p += sprintf((char *) p, "\x1BO%c", code + 'P' - 11);
\r
4303 return p - output;
\r
4305 if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
\r
4306 p += sprintf((char *) p, code == 1 ? "\x1B[H" : "\x1BOw");
\r
4307 return p - output;
\r
4310 p += sprintf((char *) p, "\x1B[%d~", code);
\r
4311 return p - output;
\r
4315 * Now the remaining keys (arrows and Keypad 5. Keypad 5 for
\r
4316 * some reason seems to send VK_CLEAR to Windows...).
\r
4338 p += format_arrow_key(p, term, xkey, shift_state);
\r
4339 return p - output;
\r
4344 * Finally, deal with Return ourselves. (Win95 seems to
\r
4345 * foul it up when Alt is pressed, for some reason.)
\r
4347 if (wParam == VK_RETURN) { /* Return */
\r
4353 if (left_alt && wParam >= VK_NUMPAD0 && wParam <= VK_NUMPAD9)
\r
4354 alt_sum = alt_sum * 10 + wParam - VK_NUMPAD0;
\r
4359 /* Okay we've done everything interesting; let windows deal with
\r
4360 * the boring stuff */
\r
4364 /* helg: clear CAPS LOCK state if caps lock switches to cyrillic */
\r
4365 if(cfg.xlat_capslockcyr && keystate[VK_CAPITAL] != 0) {
\r
4366 capsOn= !left_alt;
\r
4367 keystate[VK_CAPITAL] = 0;
\r
4370 /* XXX how do we know what the max size of the keys array should
\r
4371 * be is? There's indication on MS' website of an Inquire/InquireEx
\r
4372 * functioning returning a KBINFO structure which tells us. */
\r
4373 if (osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT) {
\r
4374 /* XXX 'keys' parameter is declared in MSDN documentation as
\r
4375 * 'LPWORD lpChar'.
\r
4376 * The experience of a French user indicates that on
\r
4377 * Win98, WORD[] should be passed in, but on Win2K, it should
\r
4378 * be BYTE[]. German WinXP and my Win2K with "US International"
\r
4379 * driver corroborate this.
\r
4380 * Experimentally I've conditionalised the behaviour on the
\r
4381 * Win9x/NT split, but I suspect it's worse than that.
\r
4382 * See wishlist item `win-dead-keys' for more horrible detail
\r
4383 * and speculations. */
\r
4386 r = ToAsciiEx(wParam, scan, keystate, (LPWORD)keybs, 0, kbd_layout);
\r
4387 for (i=0; i<3; i++) keys[i] = keybs[i];
\r
4389 r = ToAsciiEx(wParam, scan, keystate, keys, 0, kbd_layout);
\r
4391 #ifdef SHOW_TOASCII_RESULT
\r
4392 if (r == 1 && !key_down) {
\r
4394 if (in_utf(term) || ucsdata.dbcs_screenfont)
\r
4395 debug((", (U+%04x)", alt_sum));
\r
4397 debug((", LCH(%d)", alt_sum));
\r
4399 debug((", ACH(%d)", keys[0]));
\r
4401 } else if (r > 0) {
\r
4403 debug((", ASC("));
\r
4404 for (r1 = 0; r1 < r; r1++) {
\r
4405 debug(("%s%d", r1 ? "," : "", keys[r1]));
\r
4414 * Interrupt an ongoing paste. I'm not sure this is
\r
4415 * sensible, but for the moment it's preferable to
\r
4416 * having to faff about buffering things.
\r
4418 term_nopaste(term);
\r
4421 for (i = 0; i < r; i++) {
\r
4422 unsigned char ch = (unsigned char) keys[i];
\r
4424 if (compose_state == 2 && (ch & 0x80) == 0 && ch > ' ') {
\r
4425 compose_char = ch;
\r
4429 if (compose_state == 3 && (ch & 0x80) == 0 && ch > ' ') {
\r
4431 compose_state = 0;
\r
4433 if ((nc = check_compose(compose_char, ch)) == -1) {
\r
4434 MessageBeep(MB_ICONHAND);
\r
4438 term_seen_key_event(term);
\r
4440 luni_send(ldisc, &keybuf, 1, 1);
\r
4444 compose_state = 0;
\r
4448 if (in_utf(term) || ucsdata.dbcs_screenfont) {
\r
4450 term_seen_key_event(term);
\r
4452 luni_send(ldisc, &keybuf, 1, 1);
\r
4454 ch = (char) alt_sum;
\r
4456 * We need not bother about stdin
\r
4457 * backlogs here, because in GUI PuTTY
\r
4458 * we can't do anything about it
\r
4459 * anyway; there's no means of asking
\r
4460 * Windows to hold off on KEYDOWN
\r
4461 * messages. We _have_ to buffer
\r
4462 * everything we're sent.
\r
4464 term_seen_key_event(term);
\r
4466 ldisc_send(ldisc, &ch, 1, 1);
\r
4470 term_seen_key_event(term);
\r
4472 lpage_send(ldisc, kbd_codepage, &ch, 1, 1);
\r
4475 if(capsOn && ch < 0x80) {
\r
4478 cbuf[1] = xlat_uskbd2cyrllic(ch);
\r
4479 term_seen_key_event(term);
\r
4481 luni_send(ldisc, cbuf+!left_alt, 1+!!left_alt, 1);
\r
4486 term_seen_key_event(term);
\r
4488 lpage_send(ldisc, kbd_codepage,
\r
4489 cbuf+!left_alt, 1+!!left_alt, 1);
\r
4495 /* This is so the ALT-Numpad and dead keys work correctly. */
\r
4498 return p - output;
\r
4500 /* If we're definitly not building up an ALT-54321 then clear it */
\r
4503 /* If we will be using alt_sum fix the 256s */
\r
4504 else if (keys[0] && (in_utf(term) || ucsdata.dbcs_screenfont))
\r
4509 * ALT alone may or may not want to bring up the System menu.
\r
4510 * If it's not meant to, we return 0 on presses or releases of
\r
4511 * ALT, to show that we've swallowed the keystroke. Otherwise
\r
4512 * we return -1, which means Windows will give the keystroke
\r
4513 * its default handling (i.e. bring up the System menu).
\r
4515 if (wParam == VK_MENU && !cfg.alt_only)
\r
4521 void set_title(void *frontend, char *title)
\r
4523 sfree(window_name);
\r
4524 window_name = snewn(1 + strlen(title), char);
\r
4525 strcpy(window_name, title);
\r
4526 if (cfg.win_name_always || !IsIconic(hwnd))
\r
4527 SetWindowText(hwnd, title);
\r
4530 void set_icon(void *frontend, char *title)
\r
4533 icon_name = snewn(1 + strlen(title), char);
\r
4534 strcpy(icon_name, title);
\r
4535 if (!cfg.win_name_always && IsIconic(hwnd))
\r
4536 SetWindowText(hwnd, title);
\r
4539 void set_sbar(void *frontend, int total, int start, int page)
\r
4543 if (is_full_screen() ? !cfg.scrollbar_in_fullscreen : !cfg.scrollbar)
\r
4546 si.cbSize = sizeof(si);
\r
4547 si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
\r
4549 si.nMax = total - 1;
\r
4553 SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
\r
4556 Context get_ctx(void *frontend)
\r
4560 hdc = GetDC(hwnd);
\r
4562 SelectPalette(hdc, pal, FALSE);
\r
4568 void free_ctx(Context ctx)
\r
4570 SelectPalette(ctx, GetStockObject(DEFAULT_PALETTE), FALSE);
\r
4571 ReleaseDC(hwnd, ctx);
\r
4574 static void real_palette_set(int n, int r, int g, int b)
\r
4577 logpal->palPalEntry[n].peRed = r;
\r
4578 logpal->palPalEntry[n].peGreen = g;
\r
4579 logpal->palPalEntry[n].peBlue = b;
\r
4580 logpal->palPalEntry[n].peFlags = PC_NOCOLLAPSE;
\r
4581 colours[n] = PALETTERGB(r, g, b);
\r
4582 SetPaletteEntries(pal, 0, NALLCOLOURS, logpal->palPalEntry);
\r
4584 colours[n] = RGB(r, g, b);
\r
4587 void palette_set(void *frontend, int n, int r, int g, int b)
\r
4591 if (n > NALLCOLOURS)
\r
4593 real_palette_set(n, r, g, b);
\r
4595 HDC hdc = get_ctx(frontend);
\r
4596 UnrealizeObject(pal);
\r
4597 RealizePalette(hdc);
\r
4600 if (n == (ATTR_DEFBG>>ATTR_BGSHIFT))
\r
4601 /* If Default Background changes, we need to ensure any
\r
4602 * space between the text area and the window border is
\r
4604 InvalidateRect(hwnd, NULL, TRUE);
\r
4608 void palette_reset(void *frontend)
\r
4613 for (i = 0; i < NALLCOLOURS; i++) {
\r
4615 logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
\r
4616 logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
\r
4617 logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
\r
4618 logpal->palPalEntry[i].peFlags = 0;
\r
4619 colours[i] = PALETTERGB(defpal[i].rgbtRed,
\r
4620 defpal[i].rgbtGreen,
\r
4621 defpal[i].rgbtBlue);
\r
4623 colours[i] = RGB(defpal[i].rgbtRed,
\r
4624 defpal[i].rgbtGreen, defpal[i].rgbtBlue);
\r
4629 SetPaletteEntries(pal, 0, NALLCOLOURS, logpal->palPalEntry);
\r
4630 hdc = get_ctx(frontend);
\r
4631 RealizePalette(hdc);
\r
4634 /* Default Background may have changed. Ensure any space between
\r
4635 * text area and window border is redrawn. */
\r
4636 InvalidateRect(hwnd, NULL, TRUE);
\r
4640 void write_aclip(void *frontend, char *data, int len, int must_deselect)
\r
4645 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len + 1);
\r
4648 lock = GlobalLock(clipdata);
\r
4651 memcpy(lock, data, len);
\r
4652 ((unsigned char *) lock)[len] = 0;
\r
4653 GlobalUnlock(clipdata);
\r
4655 if (!must_deselect)
\r
4656 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
\r
4658 if (OpenClipboard(hwnd)) {
\r
4660 SetClipboardData(CF_TEXT, clipdata);
\r
4663 GlobalFree(clipdata);
\r
4665 if (!must_deselect)
\r
4666 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
\r
4670 * Note: unlike write_aclip() this will not append a nul.
\r
4672 void write_clip(void *frontend, wchar_t * data, int *attr, int len, int must_deselect)
\r
4674 HGLOBAL clipdata, clipdata2, clipdata3;
\r
4676 void *lock, *lock2, *lock3;
\r
4678 len2 = WideCharToMultiByte(CP_ACP, 0, data, len, 0, 0, NULL, NULL);
\r
4680 clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE,
\r
4681 len * sizeof(wchar_t));
\r
4682 clipdata2 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len2);
\r
4684 if (!clipdata || !clipdata2) {
\r
4686 GlobalFree(clipdata);
\r
4688 GlobalFree(clipdata2);
\r
4691 if (!(lock = GlobalLock(clipdata)))
\r
4693 if (!(lock2 = GlobalLock(clipdata2)))
\r
4696 memcpy(lock, data, len * sizeof(wchar_t));
\r
4697 WideCharToMultiByte(CP_ACP, 0, data, len, lock2, len2, NULL, NULL);
\r
4699 if (cfg.rtf_paste) {
\r
4700 wchar_t unitab[256];
\r
4702 unsigned char *tdata = (unsigned char *)lock2;
\r
4703 wchar_t *udata = (wchar_t *)lock;
\r
4704 int rtflen = 0, uindex = 0, tindex = 0;
\r
4706 int multilen, blen, alen, totallen, i;
\r
4707 char before[16], after[4];
\r
4708 int fgcolour, lastfgcolour = 0;
\r
4709 int bgcolour, lastbgcolour = 0;
\r
4710 int attrBold, lastAttrBold = 0;
\r
4711 int attrUnder, lastAttrUnder = 0;
\r
4712 int palette[NALLCOLOURS];
\r
4715 get_unitab(CP_ACP, unitab, 0);
\r
4717 rtfsize = 100 + strlen(cfg.font.name);
\r
4718 rtf = snewn(rtfsize, char);
\r
4719 rtflen = sprintf(rtf, "{\\rtf1\\ansi\\deff0{\\fonttbl\\f0\\fmodern %s;}\\f0\\fs%d",
\r
4720 cfg.font.name, cfg.font.height*2);
\r
4723 * Add colour palette
\r
4724 * {\colortbl ;\red255\green0\blue0;\red0\green0\blue128;}
\r
4728 * First - Determine all colours in use
\r
4729 * o Foregound and background colours share the same palette
\r
4732 memset(palette, 0, sizeof(palette));
\r
4733 for (i = 0; i < (len-1); i++) {
\r
4734 fgcolour = ((attr[i] & ATTR_FGMASK) >> ATTR_FGSHIFT);
\r
4735 bgcolour = ((attr[i] & ATTR_BGMASK) >> ATTR_BGSHIFT);
\r
4737 if (attr[i] & ATTR_REVERSE) {
\r
4738 int tmpcolour = fgcolour; /* Swap foreground and background */
\r
4739 fgcolour = bgcolour;
\r
4740 bgcolour = tmpcolour;
\r
4743 if (bold_mode == BOLD_COLOURS && (attr[i] & ATTR_BOLD)) {
\r
4744 if (fgcolour < 8) /* ANSI colours */
\r
4746 else if (fgcolour >= 256) /* Default colours */
\r
4750 if (attr[i] & ATTR_BLINK) {
\r
4751 if (bgcolour < 8) /* ANSI colours */
\r
4753 else if (bgcolour >= 256) /* Default colours */
\r
4757 palette[fgcolour]++;
\r
4758 palette[bgcolour]++;
\r
4762 * Next - Create a reduced palette
\r
4765 for (i = 0; i < NALLCOLOURS; i++) {
\r
4766 if (palette[i] != 0)
\r
4767 palette[i] = ++numcolours;
\r
4771 * Finally - Write the colour table
\r
4773 rtf = sresize(rtf, rtfsize + (numcolours * 25), char);
\r
4774 strcat(rtf, "{\\colortbl ;");
\r
4775 rtflen = strlen(rtf);
\r
4777 for (i = 0; i < NALLCOLOURS; i++) {
\r
4778 if (palette[i] != 0) {
\r
4779 rtflen += sprintf(&rtf[rtflen], "\\red%d\\green%d\\blue%d;", defpal[i].rgbtRed, defpal[i].rgbtGreen, defpal[i].rgbtBlue);
\r
4782 strcpy(&rtf[rtflen], "}");
\r
4787 * We want to construct a piece of RTF that specifies the
\r
4788 * same Unicode text. To do this we will read back in
\r
4789 * parallel from the Unicode data in `udata' and the
\r
4790 * non-Unicode data in `tdata'. For each character in
\r
4791 * `tdata' which becomes the right thing in `udata' when
\r
4792 * looked up in `unitab', we just copy straight over from
\r
4793 * tdata. For each one that doesn't, we must WCToMB it
\r
4794 * individually and produce a \u escape sequence.
\r
4796 * It would probably be more robust to just bite the bullet
\r
4797 * and WCToMB each individual Unicode character one by one,
\r
4798 * then MBToWC each one back to see if it was an accurate
\r
4799 * translation; but that strikes me as a horrifying number
\r
4800 * of Windows API calls so I want to see if this faster way
\r
4801 * will work. If it screws up badly we can always revert to
\r
4802 * the simple and slow way.
\r
4804 while (tindex < len2 && uindex < len &&
\r
4805 tdata[tindex] && udata[uindex]) {
\r
4806 if (tindex + 1 < len2 &&
\r
4807 tdata[tindex] == '\r' &&
\r
4808 tdata[tindex+1] == '\n') {
\r
4814 * Set text attributes
\r
4817 if (rtfsize < rtflen + 64) {
\r
4818 rtfsize = rtflen + 512;
\r
4819 rtf = sresize(rtf, rtfsize, char);
\r
4823 * Determine foreground and background colours
\r
4825 fgcolour = ((attr[tindex] & ATTR_FGMASK) >> ATTR_FGSHIFT);
\r
4826 bgcolour = ((attr[tindex] & ATTR_BGMASK) >> ATTR_BGSHIFT);
\r
4828 if (attr[tindex] & ATTR_REVERSE) {
\r
4829 int tmpcolour = fgcolour; /* Swap foreground and background */
\r
4830 fgcolour = bgcolour;
\r
4831 bgcolour = tmpcolour;
\r
4834 if (bold_mode == BOLD_COLOURS && (attr[tindex] & ATTR_BOLD)) {
\r
4835 if (fgcolour < 8) /* ANSI colours */
\r
4837 else if (fgcolour >= 256) /* Default colours */
\r
4841 if (attr[tindex] & ATTR_BLINK) {
\r
4842 if (bgcolour < 8) /* ANSI colours */
\r
4844 else if (bgcolour >= 256) /* Default colours */
\r
4849 * Collect other attributes
\r
4851 if (bold_mode != BOLD_COLOURS)
\r
4852 attrBold = attr[tindex] & ATTR_BOLD;
\r
4856 attrUnder = attr[tindex] & ATTR_UNDER;
\r
4860 * o If video isn't reversed, ignore colour attributes for default foregound
\r
4862 * o Special case where bolded text is displayed using the default foregound
\r
4863 * and background colours - force to bolded RTF.
\r
4865 if (!(attr[tindex] & ATTR_REVERSE)) {
\r
4866 if (bgcolour >= 256) /* Default color */
\r
4867 bgcolour = -1; /* No coloring */
\r
4869 if (fgcolour >= 256) { /* Default colour */
\r
4870 if (bold_mode == BOLD_COLOURS && (fgcolour & 1) && bgcolour == -1)
\r
4871 attrBold = ATTR_BOLD; /* Emphasize text with bold attribute */
\r
4873 fgcolour = -1; /* No coloring */
\r
4878 * Write RTF text attributes
\r
4880 if (lastfgcolour != fgcolour) {
\r
4881 lastfgcolour = fgcolour;
\r
4882 rtflen += sprintf(&rtf[rtflen], "\\cf%d ", (fgcolour >= 0) ? palette[fgcolour] : 0);
\r
4885 if (lastbgcolour != bgcolour) {
\r
4886 lastbgcolour = bgcolour;
\r
4887 rtflen += sprintf(&rtf[rtflen], "\\highlight%d ", (bgcolour >= 0) ? palette[bgcolour] : 0);
\r
4890 if (lastAttrBold != attrBold) {
\r
4891 lastAttrBold = attrBold;
\r
4892 rtflen += sprintf(&rtf[rtflen], "%s", attrBold ? "\\b " : "\\b0 ");
\r
4895 if (lastAttrUnder != attrUnder) {
\r
4896 lastAttrUnder = attrUnder;
\r
4897 rtflen += sprintf(&rtf[rtflen], "%s", attrUnder ? "\\ul " : "\\ulnone ");
\r
4901 if (unitab[tdata[tindex]] == udata[uindex]) {
\r
4907 multilen = WideCharToMultiByte(CP_ACP, 0, unitab+uindex, 1,
\r
4908 NULL, 0, NULL, NULL);
\r
4909 if (multilen != 1) {
\r
4910 blen = sprintf(before, "{\\uc%d\\u%d", multilen,
\r
4912 alen = 1; strcpy(after, "}");
\r
4914 blen = sprintf(before, "\\u%d", udata[uindex]);
\r
4915 alen = 0; after[0] = '\0';
\r
4918 assert(tindex + multilen <= len2);
\r
4919 totallen = blen + alen;
\r
4920 for (i = 0; i < multilen; i++) {
\r
4921 if (tdata[tindex+i] == '\\' ||
\r
4922 tdata[tindex+i] == '{' ||
\r
4923 tdata[tindex+i] == '}')
\r
4925 else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A)
\r
4926 totallen += 6; /* \par\r\n */
\r
4927 else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20)
\r
4933 if (rtfsize < rtflen + totallen + 3) {
\r
4934 rtfsize = rtflen + totallen + 512;
\r
4935 rtf = sresize(rtf, rtfsize, char);
\r
4938 strcpy(rtf + rtflen, before); rtflen += blen;
\r
4939 for (i = 0; i < multilen; i++) {
\r
4940 if (tdata[tindex+i] == '\\' ||
\r
4941 tdata[tindex+i] == '{' ||
\r
4942 tdata[tindex+i] == '}') {
\r
4943 rtf[rtflen++] = '\\';
\r
4944 rtf[rtflen++] = tdata[tindex+i];
\r
4945 } else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A) {
\r
4946 rtflen += sprintf(rtf+rtflen, "\\par\r\n");
\r
4947 } else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20) {
\r
4948 rtflen += sprintf(rtf+rtflen, "\\'%02x", tdata[tindex+i]);
\r
4950 rtf[rtflen++] = tdata[tindex+i];
\r
4953 strcpy(rtf + rtflen, after); rtflen += alen;
\r
4955 tindex += multilen;
\r
4959 rtf[rtflen++] = '}'; /* Terminate RTF stream */
\r
4960 rtf[rtflen++] = '\0';
\r
4961 rtf[rtflen++] = '\0';
\r
4963 clipdata3 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, rtflen);
\r
4964 if (clipdata3 && (lock3 = GlobalLock(clipdata3)) != NULL) {
\r
4965 memcpy(lock3, rtf, rtflen);
\r
4966 GlobalUnlock(clipdata3);
\r
4972 GlobalUnlock(clipdata);
\r
4973 GlobalUnlock(clipdata2);
\r
4975 if (!must_deselect)
\r
4976 SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0);
\r
4978 if (OpenClipboard(hwnd)) {
\r
4980 SetClipboardData(CF_UNICODETEXT, clipdata);
\r
4981 SetClipboardData(CF_TEXT, clipdata2);
\r
4983 SetClipboardData(RegisterClipboardFormat(CF_RTF), clipdata3);
\r
4986 GlobalFree(clipdata);
\r
4987 GlobalFree(clipdata2);
\r
4990 if (!must_deselect)
\r
4991 SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0);
\r
4994 static DWORD WINAPI clipboard_read_threadfunc(void *param)
\r
4996 HWND hwnd = (HWND)param;
\r
4999 if (OpenClipboard(NULL)) {
\r
5000 if ((clipdata = GetClipboardData(CF_UNICODETEXT))) {
\r
5001 SendMessage(hwnd, WM_GOT_CLIPDATA, (WPARAM)1, (LPARAM)clipdata);
\r
5002 } else if ((clipdata = GetClipboardData(CF_TEXT))) {
\r
5003 SendMessage(hwnd, WM_GOT_CLIPDATA, (WPARAM)0, (LPARAM)clipdata);
\r
5011 static int process_clipdata(HGLOBAL clipdata, int unicode)
\r
5013 sfree(clipboard_contents);
\r
5014 clipboard_contents = NULL;
\r
5015 clipboard_length = 0;
\r
5018 wchar_t *p = GlobalLock(clipdata);
\r
5022 /* Unwilling to rely on Windows having wcslen() */
\r
5023 for (p2 = p; *p2; p2++);
\r
5024 clipboard_length = p2 - p;
\r
5025 clipboard_contents = snewn(clipboard_length + 1, wchar_t);
\r
5026 memcpy(clipboard_contents, p, clipboard_length * sizeof(wchar_t));
\r
5027 clipboard_contents[clipboard_length] = L'\0';
\r
5031 char *s = GlobalLock(clipdata);
\r
5035 i = MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, 0, 0);
\r
5036 clipboard_contents = snewn(i, wchar_t);
\r
5037 MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1,
\r
5038 clipboard_contents, i);
\r
5039 clipboard_length = i - 1;
\r
5040 clipboard_contents[clipboard_length] = L'\0';
\r
5048 void request_paste(void *frontend)
\r
5051 * I always thought pasting was synchronous in Windows; the
\r
5052 * clipboard access functions certainly _look_ synchronous,
\r
5053 * unlike the X ones. But in fact it seems that in some
\r
5054 * situations the contents of the clipboard might not be
\r
5055 * immediately available, and the clipboard-reading functions
\r
5056 * may block. This leads to trouble if the application
\r
5057 * delivering the clipboard data has to get hold of it by -
\r
5058 * for example - talking over a network connection which is
\r
5059 * forwarded through this very PuTTY.
\r
5061 * Hence, we spawn a subthread to read the clipboard, and do
\r
5062 * our paste when it's finished. The thread will send a
\r
5063 * message back to our main window when it terminates, and
\r
5064 * that tells us it's OK to paste.
\r
5066 DWORD in_threadid; /* required for Win9x */
\r
5067 CreateThread(NULL, 0, clipboard_read_threadfunc,
\r
5068 hwnd, 0, &in_threadid);
\r
5071 void get_clip(void *frontend, wchar_t **p, int *len)
\r
5074 *p = clipboard_contents;
\r
5075 *len = clipboard_length;
\r
5081 * Move `lines' lines from position `from' to position `to' in the
\r
5084 void optimised_move(void *frontend, int to, int from, int lines)
\r
5089 min = (to < from ? to : from);
\r
5090 max = to + from - min;
\r
5092 r.left = offset_width;
\r
5093 r.right = offset_width + term->cols * font_width;
\r
5094 r.top = offset_height + min * font_height;
\r
5095 r.bottom = offset_height + (max + lines) * font_height;
\r
5096 ScrollWindow(hwnd, 0, (to - from) * font_height, &r, &r);
\r
5101 * Print a message box and perform a fatal exit.
\r
5103 void fatalbox(char *fmt, ...)
\r
5106 char *stuff, morestuff[100];
\r
5108 va_start(ap, fmt);
\r
5109 stuff = dupvprintf(fmt, ap);
\r
5111 sprintf(morestuff, "%.70s Fatal Error", appname);
\r
5112 MessageBox(hwnd, stuff, morestuff, MB_ICONERROR | MB_OK);
\r
5118 * Print a modal (Really Bad) message box and perform a fatal exit.
\r
5120 void modalfatalbox(char *fmt, ...)
\r
5123 char *stuff, morestuff[100];
\r
5125 va_start(ap, fmt);
\r
5126 stuff = dupvprintf(fmt, ap);
\r
5128 sprintf(morestuff, "%.70s Fatal Error", appname);
\r
5129 MessageBox(hwnd, stuff, morestuff,
\r
5130 MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
\r
5135 DECL_WINDOWS_FUNCTION(static, BOOL, FlashWindowEx, (PFLASHWINFO));
\r
5137 static void init_flashwindow(void)
\r
5139 HMODULE user32_module = load_system32_dll("user32.dll");
\r
5140 GET_WINDOWS_FUNCTION(user32_module, FlashWindowEx);
\r
5143 static BOOL flash_window_ex(DWORD dwFlags, UINT uCount, DWORD dwTimeout)
\r
5145 if (p_FlashWindowEx) {
\r
5147 fi.cbSize = sizeof(fi);
\r
5149 fi.dwFlags = dwFlags;
\r
5150 fi.uCount = uCount;
\r
5151 fi.dwTimeout = dwTimeout;
\r
5152 return (*p_FlashWindowEx)(&fi);
\r
5155 return FALSE; /* shrug */
\r
5158 static void flash_window(int mode);
\r
5159 static long next_flash;
\r
5160 static int flashing = 0;
\r
5163 * Timer for platforms where we must maintain window flashing manually
\r
5166 static void flash_window_timer(void *ctx, long now)
\r
5168 if (flashing && now - next_flash >= 0) {
\r
5174 * Manage window caption / taskbar flashing, if enabled.
\r
5175 * 0 = stop, 1 = maintain, 2 = start
\r
5177 static void flash_window(int mode)
\r
5179 if ((mode == 0) || (cfg.beep_ind == B_IND_DISABLED)) {
\r
5183 if (p_FlashWindowEx)
\r
5184 flash_window_ex(FLASHW_STOP, 0, 0);
\r
5186 FlashWindow(hwnd, FALSE);
\r
5189 } else if (mode == 2) {
\r
5193 if (p_FlashWindowEx) {
\r
5194 /* For so-called "steady" mode, we use uCount=2, which
\r
5195 * seems to be the traditional number of flashes used
\r
5196 * by user notifications (e.g., by Explorer).
\r
5197 * uCount=0 appears to enable continuous flashing, per
\r
5198 * "flashing" mode, although I haven't seen this
\r
5200 flash_window_ex(FLASHW_ALL | FLASHW_TIMER,
\r
5201 (cfg.beep_ind == B_IND_FLASH ? 0 : 2),
\r
5202 0 /* system cursor blink rate */);
\r
5203 /* No need to schedule timer */
\r
5205 FlashWindow(hwnd, TRUE);
\r
5206 next_flash = schedule_timer(450, flash_window_timer, hwnd);
\r
5210 } else if ((mode == 1) && (cfg.beep_ind == B_IND_FLASH)) {
\r
5212 if (flashing && !p_FlashWindowEx) {
\r
5213 FlashWindow(hwnd, TRUE); /* toggle */
\r
5214 next_flash = schedule_timer(450, flash_window_timer, hwnd);
\r
5222 void do_beep(void *frontend, int mode)
\r
5224 if (mode == BELL_DEFAULT) {
\r
5226 * For MessageBeep style bells, we want to be careful of
\r
5227 * timing, because they don't have the nice property of
\r
5228 * PlaySound bells that each one cancels the previous
\r
5229 * active one. So we limit the rate to one per 50ms or so.
\r
5231 static long lastbeep = 0;
\r
5234 beepdiff = GetTickCount() - lastbeep;
\r
5235 if (beepdiff >= 0 && beepdiff < 50)
\r
5237 MessageBeep(MB_OK);
\r
5239 * The above MessageBeep call takes time, so we record the
\r
5240 * time _after_ it finishes rather than before it starts.
\r
5242 lastbeep = GetTickCount();
\r
5243 } else if (mode == BELL_WAVEFILE) {
\r
5244 if (!PlaySound(cfg.bell_wavefile.path, NULL,
\r
5245 SND_ASYNC | SND_FILENAME)) {
\r
5246 char buf[sizeof(cfg.bell_wavefile.path) + 80];
\r
5247 char otherbuf[100];
\r
5248 sprintf(buf, "Unable to play sound file\n%s\n"
\r
5249 "Using default sound instead", cfg.bell_wavefile.path);
\r
5250 sprintf(otherbuf, "%.70s Sound Error", appname);
\r
5251 MessageBox(hwnd, buf, otherbuf,
\r
5252 MB_OK | MB_ICONEXCLAMATION);
\r
5253 cfg.beep = BELL_DEFAULT;
\r
5255 } else if (mode == BELL_PCSPEAKER) {
\r
5256 static long lastbeep = 0;
\r
5259 beepdiff = GetTickCount() - lastbeep;
\r
5260 if (beepdiff >= 0 && beepdiff < 50)
\r
5264 * We must beep in different ways depending on whether this
\r
5265 * is a 95-series or NT-series OS.
\r
5267 if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT)
\r
5271 lastbeep = GetTickCount();
\r
5273 /* Otherwise, either visual bell or disabled; do nothing here */
\r
5274 if (!term->has_focus) {
\r
5275 flash_window(2); /* start */
\r
5280 * Minimise or restore the window in response to a server-side
\r
5283 void set_iconic(void *frontend, int iconic)
\r
5285 if (IsIconic(hwnd)) {
\r
5287 ShowWindow(hwnd, SW_RESTORE);
\r
5290 ShowWindow(hwnd, SW_MINIMIZE);
\r
5295 * Move the window in response to a server-side request.
\r
5297 void move_window(void *frontend, int x, int y)
\r
5299 if (cfg.resize_action == RESIZE_DISABLED ||
\r
5300 cfg.resize_action == RESIZE_FONT ||
\r
5304 SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
\r
5308 * Move the window to the top or bottom of the z-order in response
\r
5309 * to a server-side request.
\r
5311 void set_zorder(void *frontend, int top)
\r
5313 if (cfg.alwaysontop)
\r
5314 return; /* ignore */
\r
5315 SetWindowPos(hwnd, top ? HWND_TOP : HWND_BOTTOM, 0, 0, 0, 0,
\r
5316 SWP_NOMOVE | SWP_NOSIZE);
\r
5320 * Refresh the window in response to a server-side request.
\r
5322 void refresh_window(void *frontend)
\r
5324 InvalidateRect(hwnd, NULL, TRUE);
\r
5328 * Maximise or restore the window in response to a server-side
\r
5331 void set_zoomed(void *frontend, int zoomed)
\r
5333 if (IsZoomed(hwnd)) {
\r
5335 ShowWindow(hwnd, SW_RESTORE);
\r
5338 ShowWindow(hwnd, SW_MAXIMIZE);
\r
5343 * Report whether the window is iconic, for terminal reports.
\r
5345 int is_iconic(void *frontend)
\r
5347 return IsIconic(hwnd);
\r
5351 * Report the window's position, for terminal reports.
\r
5353 void get_window_pos(void *frontend, int *x, int *y)
\r
5356 GetWindowRect(hwnd, &r);
\r
5362 * Report the window's pixel size, for terminal reports.
\r
5364 void get_window_pixels(void *frontend, int *x, int *y)
\r
5367 GetWindowRect(hwnd, &r);
\r
5368 *x = r.right - r.left;
\r
5369 *y = r.bottom - r.top;
\r
5373 * Return the window or icon title.
\r
5375 char *get_window_title(void *frontend, int icon)
\r
5377 return icon ? icon_name : window_name;
\r
5381 * See if we're in full-screen mode.
\r
5383 static int is_full_screen()
\r
5385 if (!IsZoomed(hwnd))
\r
5387 if (GetWindowLongPtr(hwnd, GWL_STYLE) & WS_CAPTION)
\r
5392 /* Get the rect/size of a full screen window using the nearest available
\r
5393 * monitor in multimon systems; default to something sensible if only
\r
5394 * one monitor is present. */
\r
5395 static int get_fullscreen_rect(RECT * ss)
\r
5397 #if defined(MONITOR_DEFAULTTONEAREST) && !defined(NO_MULTIMON)
\r
5400 mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
\r
5401 mi.cbSize = sizeof(mi);
\r
5402 GetMonitorInfo(mon, &mi);
\r
5404 /* structure copy */
\r
5405 *ss = mi.rcMonitor;
\r
5408 /* could also use code like this:
\r
5409 ss->left = ss->top = 0;
\r
5410 ss->right = GetSystemMetrics(SM_CXSCREEN);
\r
5411 ss->bottom = GetSystemMetrics(SM_CYSCREEN);
\r
5413 return GetClientRect(GetDesktopWindow(), ss);
\r
5419 * Go full-screen. This should only be called when we are already
\r
5422 static void make_full_screen()
\r
5427 assert(IsZoomed(hwnd));
\r
5429 if (is_full_screen())
\r
5432 /* Remove the window furniture. */
\r
5433 style = GetWindowLongPtr(hwnd, GWL_STYLE);
\r
5434 style &= ~(WS_CAPTION | WS_BORDER | WS_THICKFRAME);
\r
5435 if (cfg.scrollbar_in_fullscreen)
\r
5436 style |= WS_VSCROLL;
\r
5438 style &= ~WS_VSCROLL;
\r
5439 SetWindowLongPtr(hwnd, GWL_STYLE, style);
\r
5441 /* Resize ourselves to exactly cover the nearest monitor. */
\r
5442 get_fullscreen_rect(&ss);
\r
5443 SetWindowPos(hwnd, HWND_TOP, ss.left, ss.top,
\r
5444 ss.right - ss.left,
\r
5445 ss.bottom - ss.top,
\r
5446 SWP_FRAMECHANGED);
\r
5448 /* We may have changed size as a result */
\r
5452 /* Tick the menu item in the System and context menus. */
\r
5455 for (i = 0; i < lenof(popup_menus); i++)
\r
5456 CheckMenuItem(popup_menus[i].menu, IDM_FULLSCREEN, MF_CHECKED);
\r
5461 * Clear the full-screen attributes.
\r
5463 static void clear_full_screen()
\r
5465 DWORD oldstyle, style;
\r
5467 /* Reinstate the window furniture. */
\r
5468 style = oldstyle = GetWindowLongPtr(hwnd, GWL_STYLE);
\r
5469 style |= WS_CAPTION | WS_BORDER;
\r
5470 if (cfg.resize_action == RESIZE_DISABLED)
\r
5471 style &= ~WS_THICKFRAME;
\r
5473 style |= WS_THICKFRAME;
\r
5474 if (cfg.scrollbar)
\r
5475 style |= WS_VSCROLL;
\r
5477 style &= ~WS_VSCROLL;
\r
5478 if (style != oldstyle) {
\r
5479 SetWindowLongPtr(hwnd, GWL_STYLE, style);
\r
5480 SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
\r
5481 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
\r
5482 SWP_FRAMECHANGED);
\r
5485 /* Untick the menu item in the System and context menus. */
\r
5488 for (i = 0; i < lenof(popup_menus); i++)
\r
5489 CheckMenuItem(popup_menus[i].menu, IDM_FULLSCREEN, MF_UNCHECKED);
\r
5494 * Toggle full-screen mode.
\r
5496 static void flip_full_screen()
\r
5498 if (is_full_screen()) {
\r
5499 ShowWindow(hwnd, SW_RESTORE);
\r
5500 } else if (IsZoomed(hwnd)) {
\r
5501 make_full_screen();
\r
5503 SendMessage(hwnd, WM_FULLSCR_ON_MAX, 0, 0);
\r
5504 ShowWindow(hwnd, SW_MAXIMIZE);
\r
5508 void frontend_keypress(void *handle)
\r
5511 * Keypress termination in non-Close-On-Exit mode is not
\r
5512 * currently supported in PuTTY proper, because the window
\r
5513 * always has a perfectly good Close button anyway. So we do
\r
5519 int from_backend(void *frontend, int is_stderr, const char *data, int len)
\r
5521 return term_data(term, is_stderr, data, len);
\r
5524 int from_backend_untrusted(void *frontend, const char *data, int len)
\r
5526 return term_data_untrusted(term, data, len);
\r
5529 int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
\r
5532 ret = cmdline_get_passwd_input(p, in, inlen);
\r
5534 ret = term_get_userpass_input(term, p, in, inlen);
\r
5538 void agent_schedule_callback(void (*callback)(void *, void *, int),
\r
5539 void *callback_ctx, void *data, int len)
\r
5541 struct agent_callback *c = snew(struct agent_callback);
\r
5542 c->callback = callback;
\r
5543 c->callback_ctx = callback_ctx;
\r
5546 PostMessage(hwnd, WM_AGENT_CALLBACK, 0, (LPARAM)c);
\r