X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/putty/blobdiff_plain/5321c0c69fbb8966982687d208ffc5f0c17ba191..6acea9a62669bfaa56dbea9a0b15c921eaff4d01:/windows/window.c diff --git a/windows/window.c b/windows/window.c index 98b35105..670a1ace 100644 --- a/windows/window.c +++ b/windows/window.c @@ -1,9 +1,19 @@ +/* + * window.c - the PuTTY(tel) main program, which runs a PuTTY terminal + * emulator and backend in a window. + */ + #include #include #include #include +#include #include +#ifndef NO_MULTIMON +#define COMPILE_MULTIMON_STUBS +#endif + #define PUTTY_DO_GLOBALS /* actually _define_ globals */ #include "putty.h" #include "terminal.h" @@ -11,11 +21,8 @@ #include "win_res.h" #ifndef NO_MULTIMON -#if WINVER < 0x0500 -#define COMPILE_MULTIMON_STUBS #include #endif -#endif #include #include @@ -40,6 +47,7 @@ #define IDM_COPYALL 0x0170 #define IDM_FULLSCREEN 0x0180 #define IDM_PASTE 0x0190 +#define IDM_SPECIALSEP 0x0200 #define IDM_SPECIAL_MIN 0x0400 #define IDM_SPECIAL_MAX 0x0800 @@ -50,9 +58,10 @@ /* Maximum number of sessions on saved-session submenu */ #define MENU_SAVED_MAX ((IDM_SAVED_MAX-IDM_SAVED_MIN) / MENU_SAVED_STEP) -#define WM_IGNORE_CLIP (WM_XUSER + 2) -#define WM_FULLSCR_ON_MAX (WM_XUSER + 3) -#define WM_AGENT_CALLBACK (WM_XUSER + 4) +#define WM_IGNORE_CLIP (WM_APP + 2) +#define WM_FULLSCR_ON_MAX (WM_APP + 3) +#define WM_AGENT_CALLBACK (WM_APP + 4) +#define WM_GOT_CLIPDATA (WM_APP + 6) /* Needed for Chinese support and apparently not always defined. */ #ifndef VK_PROCESSKEY @@ -78,11 +87,14 @@ static void init_fonts(int, int); static void another_font(int); static void deinit_fonts(void); static void set_input_locale(HKL); +static void update_savedsess_menu(void); +static void init_flashwindow(void); static int is_full_screen(void); static void make_full_screen(void); static void clear_full_screen(void); static void flip_full_screen(void); +static int process_clipdata(HGLOBAL clipdata, int unicode); /* Window layout information */ static void reset_window(int); @@ -92,7 +104,10 @@ static int offset_width, offset_height; static int was_zoomed = 0; static int prev_rows, prev_cols; -static void enact_netevent(WPARAM, LPARAM); +static int pending_netevent = 0; +static WPARAM pend_netevent_wParam = 0; +static LPARAM pend_netevent_lParam = 0; +static void enact_pending_netevent(void); static void flash_window(int mode); static void sys_cursor_update(void); static int is_shift_pressed(void); @@ -107,23 +122,28 @@ static Backend *back; static void *backhandle; static struct unicode_data ucsdata; -static int session_closed; +static int must_close_session, session_closed; +static int reconfiguring = FALSE; + +static const struct telnet_special *specials = NULL; +static HMENU specials_menu = NULL; +static int n_specials = 0; -static const struct telnet_special *specials; -static int n_specials; +static wchar_t *clipboard_contents; +static size_t clipboard_length; #define TIMING_TIMER_ID 1234 static long timing_next_time; static struct { HMENU menu; - int specials_submenu_pos; } popup_menus[2]; enum { SYSMENU, CTXMENU }; +static HMENU savedsess_menu; Config cfg; /* exported to windlg.c */ -extern struct sesslist sesslist; /* imported from windlg.c */ +static struct sesslist sesslist; /* for saved-session menu */ struct agent_callback { void (*callback)(void *, void *, int); @@ -188,6 +208,11 @@ void ldisc_update(void *frontend, int echo, int edit) { } +char *get_ttymode(void *frontend, const char *mode) +{ + return term_get_ttymode(term, mode); +} + static void start_backend(void) { const char *error; @@ -199,12 +224,7 @@ static void start_backend(void) * Select protocol. This is farmed out into a table in a * separate file to enable an ssh-free variant. */ - back = NULL; - for (i = 0; backends[i].backend != NULL; i++) - if (backends[i].protocol == cfg.protocol) { - back = backends[i].backend; - break; - } + back = backend_from_proto(cfg.protocol); if (back == NULL) { char *str = dupprintf("%s Internal Error", appname); MessageBox(NULL, "Unsupported protocol number found", @@ -220,7 +240,7 @@ static void start_backend(void) if (error) { char *str = dupprintf("%s Error", appname); sprintf(msg, "Unable to open connection to\n" - "%.800s\n" "%s", cfg.host, error); + "%.800s\n" "%s", cfg_dest(&cfg), error); MessageBox(NULL, msg, str, MB_ICONERROR | MB_OK); sfree(str); exit(0); @@ -257,6 +277,7 @@ static void start_backend(void) DeleteMenu(popup_menus[i].menu, IDM_RESTART, MF_BYCOMMAND); } + must_close_session = FALSE; session_closed = FALSE; } @@ -278,6 +299,7 @@ static void close_session(void) back->free(backhandle); backhandle = NULL; back = NULL; + term_provide_resize_fn(term, NULL, NULL); update_specials_menu(NULL); } @@ -299,6 +321,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) int guess_width, guess_height; hinst = inst; + hwnd = NULL; flags = FLAG_VERBOSE | FLAG_INTERACTIVE; sk_init(); @@ -328,31 +351,9 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) osVersion.dwPlatformId != VER_PLATFORM_WIN32_NT)) wm_mousewheel = RegisterWindowMessage("MSWHEEL_ROLLMSG"); - /* - * See if we can find our Help file. - */ - { - char b[2048], *p, *q, *r; - FILE *fp; - GetModuleFileName(NULL, b, sizeof(b) - 1); - r = b; - p = strrchr(b, '\\'); - if (p && p >= r) r = p+1; - q = strrchr(b, ':'); - if (q && q >= r) r = q+1; - strcpy(r, "putty.hlp"); - if ( (fp = fopen(b, "r")) != NULL) { - help_path = dupstr(b); - fclose(fp); - } else - help_path = NULL; - strcpy(r, "putty.cnt"); - if ( (fp = fopen(b, "r")) != NULL) { - help_has_contents = TRUE; - fclose(fp); - } else - help_has_contents = FALSE; - } + init_help(); + + init_flashwindow(); /* * Process the command line. @@ -360,17 +361,18 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) { char *p; int got_host = 0; + /* By default, we bring up the config dialog, rather than launching + * a session. This gets set to TRUE if something happens to change + * that (e.g., a hostname is specified on the command-line). */ + int allow_launch = FALSE; default_protocol = be_default_protocol; /* Find the appropriate default port. */ { - int i; + Backend *b = backend_from_proto(default_protocol); default_port = 0; /* illegal */ - for (i = 0; backends[i].backend != NULL; i++) - if (backends[i].protocol == default_protocol) { - default_port = backends[i].backend->default_port; - break; - } + if (b) + default_port = b->default_port; } cfg.logtype = LGTYP_NONE; @@ -393,9 +395,10 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) i--; p[i] = '\0'; do_defaults(p + 1, &cfg); - if (!*cfg.host && !do_config()) { + if (!cfg_launchable(&cfg) && !do_config()) { cleanup_exit(0); } + allow_launch = TRUE; /* allow it to be launched directly */ } else if (*p == '&') { /* * An initial & means we've been given a command line @@ -414,6 +417,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) } else if (!do_config()) { cleanup_exit(0); } + allow_launch = TRUE; } else { /* * Otherwise, break up the command line and deal with @@ -436,28 +440,50 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) i++; /* skip next argument */ } else if (ret == 1) { continue; /* nothing further needs doing */ - } else if (!strcmp(p, "-cleanup")) { + } else if (!strcmp(p, "-cleanup") || + !strcmp(p, "-cleanup-during-uninstall")) { /* * `putty -cleanup'. Remove all registry * entries associated with PuTTY, and also find * and delete the random seed file. */ char *s1, *s2; - s1 = dupprintf("This procedure will remove ALL Registry\n" - "entries associated with %s, and will\n" - "also remove the random seed file.\n" - "\n" - "THIS PROCESS WILL DESTROY YOUR SAVED\n" - "SESSIONS. Are you really sure you want\n" - "to continue?", appname); - s2 = dupprintf("%s Warning", appname); - if (MessageBox(NULL, s1, s2, - MB_YESNO | MB_ICONWARNING) == IDYES) { + /* Are we being invoked from an uninstaller? */ + if (!strcmp(p, "-cleanup-during-uninstall")) { + s1 = dupprintf("Remove saved sessions and random seed file?\n" + "\n" + "If you hit Yes, ALL Registry entries associated\n" + "with %s will be removed, as well as the\n" + "random seed file. THIS PROCESS WILL\n" + "DESTROY YOUR SAVED SESSIONS.\n" + "(This only affects the currently logged-in user.)\n" + "\n" + "If you hit No, uninstallation will proceed, but\n" + "saved sessions etc will be left on the machine.", + appname); + s2 = dupprintf("%s Uninstallation", appname); + } else { + s1 = dupprintf("This procedure will remove ALL Registry entries\n" + "associated with %s, and will also remove\n" + "the random seed file. (This only affects the\n" + "currently logged-in user.)\n" + "\n" + "THIS PROCESS WILL DESTROY YOUR SAVED SESSIONS.\n" + "Are you really sure you want to continue?", + appname); + s2 = dupprintf("%s Warning", appname); + } + if (message_box(s1, s2, + MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2, + HELPCTXID(option_cleanup)) == IDYES) { cleanup_all(); } sfree(s1); sfree(s2); exit(0); + } else if (!strcmp(p, "-pgpfp")) { + pgp_fingerprints(); + exit(1); } else if (*p != '-') { char *q = p; if (got_host) { @@ -516,7 +542,10 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) cmdline_run_saved(&cfg); - if (!*cfg.host && !do_config()) { + if (loaded_session || got_host) + allow_launch = TRUE; + + if ((!allow_launch || !cfg_launchable(&cfg)) && !do_config()) { cleanup_exit(0); } @@ -573,15 +602,6 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) } } - /* Check for invalid Port number (i.e. zero) */ - if (cfg.port == 0) { - char *str = dupprintf("%s Internal Error", appname); - MessageBox(NULL, "Invalid Port Number", - str, MB_OK | MB_ICONEXCLAMATION); - sfree(str); - cleanup_exit(1); - } - if (!prev) { wndclass.style = 0; wndclass.lpfnWndProc = WndProc; @@ -597,8 +617,6 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) RegisterClass(&wndclass); } - hwnd = NULL; - memset(&ucsdata, 0, sizeof(ucsdata)); cfgtopalette(); @@ -719,34 +737,28 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) * Set up the session-control options on the system menu. */ { - HMENU s, m; - int i, j; + HMENU m; + int j; char *str; popup_menus[SYSMENU].menu = GetSystemMenu(hwnd, FALSE); popup_menus[CTXMENU].menu = CreatePopupMenu(); AppendMenu(popup_menus[CTXMENU].menu, MF_ENABLED, IDM_PASTE, "&Paste"); - s = CreateMenu(); + savedsess_menu = CreateMenu(); get_sesslist(&sesslist, TRUE); - /* skip sesslist.sessions[0] == Default Settings */ - for (i = 1; - i < ((sesslist.nsessions <= MENU_SAVED_MAX+1) ? sesslist.nsessions - : MENU_SAVED_MAX+1); - i++) - AppendMenu(s, MF_ENABLED, IDM_SAVED_MIN + (i-1)*MENU_SAVED_STEP, - sesslist.sessions[i]); + update_savedsess_menu(); for (j = 0; j < lenof(popup_menus); j++) { m = popup_menus[j].menu; AppendMenu(m, MF_SEPARATOR, 0, 0); - popup_menus[j].specials_submenu_pos = GetMenuItemCount(m); AppendMenu(m, MF_ENABLED, IDM_SHOWLOG, "&Event Log"); AppendMenu(m, MF_SEPARATOR, 0, 0); AppendMenu(m, MF_ENABLED, IDM_NEWSESS, "Ne&w Session..."); AppendMenu(m, MF_ENABLED, IDM_DUPSESS, "&Duplicate Session"); - AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) s, "Sa&ved Sessions"); + AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) savedsess_menu, + "Sa&ved Sessions"); AppendMenu(m, MF_ENABLED, IDM_RECONF, "Chan&ge Settings..."); AppendMenu(m, MF_SEPARATOR, 0, 0); AppendMenu(m, MF_ENABLED, IDM_COPYALL, "C&opy All to Clipboard"); @@ -756,7 +768,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) AppendMenu(m, (cfg.resize_action == RESIZE_DISABLED) ? MF_GRAYED : MF_ENABLED, IDM_FULLSCREEN, "&Full Screen"); AppendMenu(m, MF_SEPARATOR, 0, 0); - if (help_path) + if (has_help()) AppendMenu(m, MF_ENABLED, IDM_HELP, "&Help"); str = dupprintf("&About %s", appname); AppendMenu(m, MF_ENABLED, IDM_ABOUT, str); @@ -772,11 +784,6 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) set_input_locale(GetKeyboardLayout(0)); /* - * Open the initial log file if there is one. - */ - logfopen(logctx); - - /* * Finally show the window! */ ShowWindow(hwnd, show); @@ -792,8 +799,27 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) term_set_focus(term, GetForegroundWindow() == hwnd); UpdateWindow(hwnd); - if (GetMessage(&msg, NULL, 0, 0) == 1) { - while (msg.message != WM_QUIT) { + while (1) { + HANDLE *handles; + int nhandles, n; + + handles = handle_get_events(&nhandles); + + n = MsgWaitForMultipleObjects(nhandles, handles, FALSE, INFINITE, + QS_ALLINPUT); + + if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) { + handle_got_event(handles[n - WAIT_OBJECT_0]); + sfree(handles); + if (must_close_session) + close_session(); + } else + sfree(handles); + + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + if (msg.message == WM_QUIT) + goto finished; /* two-level break */ + if (!(IsWindow(logbox) && IsDialogMessage(logbox, &msg))) DispatchMessage(&msg); /* Send the paste buffer if there's anything to send */ @@ -802,23 +828,20 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) * we've delayed, reading the socket, writing, and repainting * the window. */ - if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) - continue; + if (must_close_session) + close_session(); + } - /* The messages seem unreliable; especially if we're being tricky */ - term_set_focus(term, GetForegroundWindow() == hwnd); + /* The messages seem unreliable; especially if we're being tricky */ + term_set_focus(term, GetForegroundWindow() == hwnd); - net_pending_errors(); + if (pending_netevent) + enact_pending_netevent(); - /* There's no point rescanning everything in the message queue - * so we do an apparently unnecessary wait here - */ - WaitMessage(); - if (GetMessage(&msg, NULL, 0, 0) != 1) - break; - } + net_pending_errors(); } + finished: cleanup_exit(msg.wParam); /* this doesn't return... */ return msg.wParam; /* ... but optimiser doesn't know */ } @@ -843,6 +866,7 @@ void cleanup_exit(int code) crypto_wrapup(); #endif } + shutdown_help(); exit(code); } @@ -874,12 +898,28 @@ char *do_select(SOCKET skt, int startup) } /* + * Refresh the saved-session submenu from `sesslist'. + */ +static void update_savedsess_menu(void) +{ + int i; + while (DeleteMenu(savedsess_menu, 0, MF_BYPOSITION)) ; + /* skip sesslist.sessions[0] == Default Settings */ + for (i = 1; + i < ((sesslist.nsessions <= MENU_SAVED_MAX+1) ? sesslist.nsessions + : MENU_SAVED_MAX+1); + i++) + AppendMenu(savedsess_menu, MF_ENABLED, + IDM_SAVED_MIN + (i-1)*MENU_SAVED_STEP, + sesslist.sessions[i]); +} + +/* * Update the Special Commands submenu. */ void update_specials_menu(void *frontend) { - HMENU p; - int menu_already_exists = (specials != NULL); + HMENU new_menu; int i, j; if (back) @@ -892,30 +932,30 @@ void update_specials_menu(void *frontend) * here's a lame "stack" that will do for now. */ HMENU saved_menu = NULL; int nesting = 1; - p = CreatePopupMenu(); + new_menu = CreatePopupMenu(); for (i = 0; nesting > 0; i++) { assert(IDM_SPECIAL_MIN + 0x10 * i < IDM_SPECIAL_MAX); switch (specials[i].code) { case TS_SEP: - AppendMenu(p, MF_SEPARATOR, 0, 0); + AppendMenu(new_menu, MF_SEPARATOR, 0, 0); break; case TS_SUBMENU: assert(nesting < 2); nesting++; - saved_menu = p; /* XXX lame stacking */ - p = CreatePopupMenu(); + saved_menu = new_menu; /* XXX lame stacking */ + new_menu = CreatePopupMenu(); AppendMenu(saved_menu, MF_POPUP | MF_ENABLED, - (UINT) p, specials[i].name); + (UINT) new_menu, specials[i].name); break; case TS_EXITMENU: nesting--; if (nesting) { - p = saved_menu; /* XXX lame stacking */ + new_menu = saved_menu; /* XXX lame stacking */ saved_menu = NULL; } break; default: - AppendMenu(p, MF_ENABLED, IDM_SPECIAL_MIN + 0x10 * i, + AppendMenu(new_menu, MF_ENABLED, IDM_SPECIAL_MIN + 0x10 * i, specials[i].name); break; } @@ -923,30 +963,25 @@ void update_specials_menu(void *frontend) /* Squirrel the highest special. */ n_specials = i - 1; } else { - p = NULL; + new_menu = NULL; n_specials = 0; } for (j = 0; j < lenof(popup_menus); j++) { - if (menu_already_exists) { + if (specials_menu) { /* XXX does this free up all submenus? */ - DeleteMenu(popup_menus[j].menu, - popup_menus[j].specials_submenu_pos, - MF_BYPOSITION); - DeleteMenu(popup_menus[j].menu, - popup_menus[j].specials_submenu_pos, - MF_BYPOSITION); + DeleteMenu(popup_menus[j].menu, (UINT)specials_menu, MF_BYCOMMAND); + DeleteMenu(popup_menus[j].menu, IDM_SPECIALSEP, MF_BYCOMMAND); } - if (specials) { - InsertMenu(popup_menus[j].menu, - popup_menus[j].specials_submenu_pos, - MF_BYPOSITION | MF_SEPARATOR, 0, 0); - InsertMenu(popup_menus[j].menu, - popup_menus[j].specials_submenu_pos, - MF_BYPOSITION | MF_POPUP | MF_ENABLED, - (UINT) p, "S&pecial Command"); + if (new_menu) { + InsertMenu(popup_menus[j].menu, IDM_SHOWLOG, + MF_BYCOMMAND | MF_POPUP | MF_ENABLED, + (UINT) new_menu, "S&pecial Command"); + InsertMenu(popup_menus[j].menu, IDM_SHOWLOG, + MF_BYCOMMAND | MF_SEPARATOR, IDM_SPECIALSEP, 0); } } + specials_menu = new_menu; } static void update_mouse_pointer(void) @@ -974,7 +1009,7 @@ static void update_mouse_pointer(void) } { HCURSOR cursor = LoadCursor(NULL, curstype); - SetClassLong(hwnd, GCL_HCURSOR, (LONG)cursor); + SetClassLongPtr(hwnd, GCLP_HCURSOR, (LONG_PTR)cursor); SetCursor(cursor); /* force redraw of cursor at current posn */ } if (force_visible != forced_visible) { @@ -1021,7 +1056,7 @@ void connection_fatal(void *frontend, char *fmt, ...) if (cfg.close_on_exit == FORCE_ON) PostQuitMessage(1); else { - close_session(); + must_close_session = TRUE; } } @@ -1045,31 +1080,19 @@ void cmdline_error(char *fmt, ...) /* * Actually do the job requested by a WM_NETEVENT */ -static void enact_netevent(WPARAM wParam, LPARAM lParam) +static void enact_pending_netevent(void) { static int reentering = 0; extern int select_result(WPARAM, LPARAM); - int ret; if (reentering) return; /* don't unpend the pending */ + pending_netevent = FALSE; + reentering = 1; - ret = select_result(wParam, lParam); + select_result(pend_netevent_wParam, pend_netevent_lParam); reentering = 0; - - if (ret == 0 && !session_closed) { - /* Abnormal exits will already have set session_closed and taken - * appropriate action. */ - if (cfg.close_on_exit == FORCE_ON || - cfg.close_on_exit == AUTO) PostQuitMessage(0); - else { - close_session(); - session_closed = TRUE; - MessageBox(hwnd, "Connection closed by remote host", - appname, MB_OK | MB_ICONINFORMATION); - } - } } /* @@ -1094,12 +1117,12 @@ static void cfgtopalette(void) for (i = 0; i < NEXTCOLOURS; i++) { if (i < 216) { int r = i / 36, g = (i / 6) % 6, b = i % 6; - defpal[i+16].rgbtRed = r * 0x33; - defpal[i+16].rgbtGreen = g * 0x33; - defpal[i+16].rgbtBlue = b * 0x33; + defpal[i+16].rgbtRed = r ? r * 40 + 55 : 0; + defpal[i+16].rgbtGreen = g ? g * 40 + 55 : 0; + defpal[i+16].rgbtBlue = b ? b * 40 + 55 : 0; } else { int shade = i - 216; - shade = (shade + 1) * 0xFF / (NEXTCOLOURS - 216 + 1); + shade = shade * 10 + 8; defpal[i+16].rgbtRed = defpal[i+16].rgbtGreen = defpal[i+16].rgbtBlue = shade; } @@ -1192,8 +1215,17 @@ static void exact_textout(HDC hdc, int x, int y, CONST RECT *lprc, unsigned short *lpString, UINT cbCount, CONST INT *lpDx, int opaque) { - +#ifdef __LCC__ + /* + * The LCC include files apparently don't supply the + * GCP_RESULTSW type, but we can make do with GCP_RESULTS + * proper: the differences aren't important to us (the only + * variable-width string parameter is one we don't use anyway). + */ + GCP_RESULTS gcpr; +#else GCP_RESULTSW gcpr; +#endif char *buffer = snewn(cbCount*2+2, char); char *classbuffer = snewn(cbCount, char); memset(&gcpr, 0, sizeof(gcpr)); @@ -1202,7 +1234,7 @@ static void exact_textout(HDC hdc, int x, int y, CONST RECT *lprc, gcpr.lStructSize = sizeof(gcpr); gcpr.lpGlyphs = (void *)buffer; - gcpr.lpClass = classbuffer; + gcpr.lpClass = (void *)classbuffer; gcpr.nGlyphs = cbCount; GetCharacterPlacementW(hdc, lpString, cbCount, 0, &gcpr, @@ -1214,6 +1246,89 @@ static void exact_textout(HDC hdc, int x, int y, CONST RECT *lprc, } /* + * The exact_textout() wrapper, unfortunately, destroys the useful + * Windows `font linking' behaviour: automatic handling of Unicode + * code points not supported in this font by falling back to a font + * which does contain them. Therefore, we adopt a multi-layered + * approach: for any potentially-bidi text, we use exact_textout(), + * and for everything else we use a simple ExtTextOut as we did + * before exact_textout() was introduced. + */ +static void general_textout(HDC hdc, int x, int y, CONST RECT *lprc, + unsigned short *lpString, UINT cbCount, + CONST INT *lpDx, int opaque) +{ + int i, j, xp, xn; + RECT newrc; + +#ifdef FIXME_REMOVE_BEFORE_CHECKIN +int k; +debug(("general_textout: %d,%d", x, y)); +for(k=0;kleft, lprc->top, lprc->right, lprc->bottom)); +debug(("\n")); +#endif + + xp = xn = x; + + for (i = 0; i < (int)cbCount ;) { + int rtl = is_rtl(lpString[i]); + + xn += lpDx[i]; + + for (j = i+1; j < (int)cbCount; j++) { + if (rtl != is_rtl(lpString[j])) + break; + xn += lpDx[j]; + } + + /* + * Now [i,j) indicates a maximal substring of lpString + * which should be displayed using the same textout + * function. + */ + if (rtl) { + newrc.left = lprc->left + xp - x; + newrc.right = lprc->left + xn - x; + newrc.top = lprc->top; + newrc.bottom = lprc->bottom; +#ifdef FIXME_REMOVE_BEFORE_CHECKIN +{ +int k; +debug((" exact_textout: %d,%d", xp, y)); +for(k=0;kleft + xp - x; + newrc.right = lprc->left + xn - x; + newrc.top = lprc->top; + newrc.bottom = lprc->bottom; + ExtTextOutW(hdc, xp, y, ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0), + &newrc, lpString+i, j-i, lpDx+i); + } + + i = j; + xp = xn; + } + +#ifdef FIXME_REMOVE_BEFORE_CHECKIN +debug(("general_textout: done, xn=%d\n", xn)); +#endif + assert(xn - x >= lprc->right - lprc->left); +} + +/* * Initialise all the fonts we will need initially. There may be as many as * three or as few as one. The other (poentially) twentyone fonts are done * if/when they are needed. @@ -1269,29 +1384,16 @@ static void init_fonts(int pick_width, int pick_height) #define f(i,c,w,u) \ fonts[i] = CreateFont (font_height, font_width, 0, 0, w, FALSE, u, FALSE, \ c, OUT_DEFAULT_PRECIS, \ - CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, \ + CLIP_DEFAULT_PRECIS, FONT_QUALITY(cfg.font_quality), \ FIXED_PITCH | FF_DONTCARE, cfg.font.name) f(FONT_NORMAL, cfg.font.charset, fw_dontcare, FALSE); - lfont.lfHeight = font_height; - lfont.lfWidth = font_width; - lfont.lfEscapement = 0; - lfont.lfOrientation = 0; - lfont.lfWeight = fw_dontcare; - lfont.lfItalic = FALSE; - lfont.lfUnderline = FALSE; - lfont.lfStrikeOut = FALSE; - lfont.lfCharSet = cfg.font.charset; - lfont.lfOutPrecision = OUT_DEFAULT_PRECIS; - lfont.lfClipPrecision = CLIP_DEFAULT_PRECIS; - lfont.lfQuality = DEFAULT_QUALITY; - lfont.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE; - strncpy(lfont.lfFaceName, cfg.font.name, LF_FACESIZE); - SelectObject(hdc, fonts[FONT_NORMAL]); GetTextMetrics(hdc, &tm); + GetObject(fonts[FONT_NORMAL], sizeof(LOGFONT), &lfont); + if (pick_width == 0 || pick_height == 0) { font_height = tm.tmHeight; font_width = tm.tmAveCharWidth; @@ -1451,7 +1553,7 @@ static void another_font(int fontno) fonts[fontno] = CreateFont(font_height * (1 + !!(fontno & FONT_HIGH)), x, 0, 0, w, FALSE, u, FALSE, c, OUT_DEFAULT_PRECIS, - CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, + CLIP_DEFAULT_PRECIS, FONT_QUALITY(cfg.font_quality), FIXED_PITCH | FF_DONTCARE, s); fontflag[fontno] = 1; @@ -1595,8 +1697,8 @@ static void reset_window(int reinit) { #endif } } else { - if ( font_width != win_width/term->cols || - font_height != win_height/term->rows) { + if ( font_width * term->cols != win_width || + font_height * term->rows != win_height) { /* Our only choice at this point is to change the * size of the terminal; Oh well. */ @@ -1818,7 +1920,29 @@ static int is_shift_pressed(void) static int resizing; -void notify_remote_exit(void *fe) { /* stub not needed in this frontend */ } +void notify_remote_exit(void *fe) +{ + int exitcode; + + if (!session_closed && + (exitcode = back->exitcode(backhandle)) >= 0) { + /* Abnormal exits will already have set session_closed and taken + * appropriate action. */ + if (cfg.close_on_exit == FORCE_ON || + (cfg.close_on_exit == AUTO && exitcode != INT_MAX)) { + PostQuitMessage(0); + } else { + must_close_session = TRUE; + session_closed = TRUE; + /* exitcode == INT_MAX indicates that the connection was closed + * by a fatal error, so an error box will be coming our way and + * we should not generate this informational one. */ + if (exitcode != INT_MAX) + MessageBox(hwnd, "Connection closed by remote host", + appname, MB_OK | MB_ICONINFORMATION); + } + } +} void timer_change_notify(long next) { @@ -1860,7 +1984,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, if (!cfg.warn_on_close || session_closed || MessageBox(hwnd, "Are you sure you want to close this session?", - str, MB_ICONWARNING | MB_OKCANCEL | MB_DEFBUTTON2) + str, MB_ICONWARNING | MB_OKCANCEL | MB_DEFBUTTON1) == IDOK) DestroyWindow(hwnd); sfree(str); @@ -1870,6 +1994,16 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, show_mouseptr(1); PostQuitMessage(0); return 0; + case WM_INITMENUPOPUP: + if ((HMENU)wParam == savedsess_menu) { + /* About to pop up Saved Sessions sub-menu. + * Refresh the session list. */ + get_sesslist(&sesslist, FALSE); /* free */ + get_sesslist(&sesslist, TRUE); + update_savedsess_menu(); + return 0; + } + break; case WM_COMMAND: case WM_SYSCOMMAND: switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */ @@ -1883,6 +2017,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, char b[2048]; char c[30], *cl; int freecl = FALSE; + BOOL inherit_handles; STARTUPINFO si; PROCESS_INFORMATION pi; HANDLE filemap = NULL; @@ -1898,11 +2033,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = NULL; sa.bInheritHandle = TRUE; - filemap = CreateFileMapping((HANDLE) 0xFFFFFFFF, + filemap = CreateFileMapping(INVALID_HANDLE_VALUE, &sa, PAGE_READWRITE, 0, sizeof(Config), NULL); - if (filemap) { + if (filemap && filemap != INVALID_HANDLE_VALUE) { p = (Config *) MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, sizeof(Config)); @@ -1911,20 +2046,24 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, UnmapViewOfFile(p); } } + inherit_handles = TRUE; sprintf(c, "putty &%p", filemap); cl = c; } else if (wParam == IDM_SAVEDSESS) { unsigned int sessno = ((lParam - IDM_SAVED_MIN) / MENU_SAVED_STEP) + 1; - if (sessno < sesslist.nsessions) { + if (sessno < (unsigned)sesslist.nsessions) { char *session = sesslist.sessions[sessno]; /* XXX spaces? quotes? "-load"? */ cl = dupprintf("putty @%s", session); + inherit_handles = FALSE; freecl = TRUE; } else break; - } else + } else /* IDM_NEWSESS */ { cl = NULL; + inherit_handles = FALSE; + } GetModuleFileName(NULL, b, sizeof(b) - 1); si.cb = sizeof(si); @@ -1934,7 +2073,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, si.dwFlags = 0; si.cbReserved2 = 0; si.lpReserved2 = NULL; - CreateProcess(b, cl, NULL, NULL, TRUE, + CreateProcess(b, cl, NULL, NULL, inherit_handles, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi); if (filemap) @@ -1946,6 +2085,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, case IDM_RESTART: if (!back) { logevent(NULL, "----- Session restarted -----"); + term_pwron(term, FALSE); start_backend(); } @@ -1954,19 +2094,30 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, { Config prev_cfg; int init_lvl = 1; + int reconfig_result; + + if (reconfiguring) + break; + else + reconfiguring = TRUE; GetWindowText(hwnd, cfg.wintitle, sizeof(cfg.wintitle)); prev_cfg = cfg; - if (!do_reconfig(hwnd, back ? back->cfg_info(backhandle) : 0)) + reconfig_result = + do_reconfig(hwnd, back ? back->cfg_info(backhandle) : 0); + reconfiguring = FALSE; + if (!reconfig_result) break; { /* Disable full-screen if resizing forbidden */ - HMENU m = GetSystemMenu (hwnd, FALSE); - EnableMenuItem(m, IDM_FULLSCREEN, MF_BYCOMMAND | - (cfg.resize_action == RESIZE_DISABLED) - ? MF_GRAYED : MF_ENABLED); + int i; + for (i = 0; i < lenof(popup_menus); i++) + EnableMenuItem(popup_menus[i].menu, IDM_FULLSCREEN, + MF_BYCOMMAND | + (cfg.resize_action == RESIZE_DISABLED) + ? MF_GRAYED : MF_ENABLED); /* Gracefully unzoom if necessary */ if (IsZoomed(hwnd) && (cfg.resize_action == RESIZE_DISABLED)) { @@ -2009,9 +2160,9 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, /* Enable or disable the scroll bar, etc */ { - LONG nflg, flag = GetWindowLong(hwnd, GWL_STYLE); + LONG nflg, flag = GetWindowLongPtr(hwnd, GWL_STYLE); LONG nexflag, exflag = - GetWindowLong(hwnd, GWL_EXSTYLE); + GetWindowLongPtr(hwnd, GWL_EXSTYLE); nexflag = exflag; if (cfg.alwaysontop != prev_cfg.alwaysontop) { @@ -2050,9 +2201,9 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, if (nflg != flag || nexflag != exflag) { if (nflg != flag) - SetWindowLong(hwnd, GWL_STYLE, nflg); + SetWindowLongPtr(hwnd, GWL_STYLE, nflg); if (nexflag != exflag) - SetWindowLong(hwnd, GWL_EXSTYLE, nexflag); + SetWindowLongPtr(hwnd, GWL_EXSTYLE, nexflag); SetWindowPos(hwnd, NULL, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOCOPYBITS | @@ -2081,6 +2232,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, cfg.font.isbold != prev_cfg.font.isbold || cfg.font.height != prev_cfg.font.height || cfg.font.charset != prev_cfg.font.charset || + cfg.font_quality != prev_cfg.font_quality || cfg.vtmode != prev_cfg.vtmode || cfg.bold_colour != prev_cfg.bold_colour || cfg.resize_action == RESIZE_DISABLED || @@ -2097,13 +2249,13 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, term_copyall(term); break; case IDM_PASTE: - term_do_paste(term); + request_paste(NULL); break; case IDM_CLRSB: term_clrsb(term); break; case IDM_RESET: - term_pwron(term); + term_pwron(term, TRUE); if (ldisc) ldisc_send(ldisc, NULL, 0, 0); break; @@ -2111,8 +2263,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, showabout(hwnd); break; case IDM_HELP: - WinHelp(hwnd, help_path, - help_has_contents ? HELP_FINDER : HELP_CONTENTS, 0); + launch_help(hwnd, NULL); break; case SC_MOUSEMENU: /* @@ -2186,26 +2337,32 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, switch (message) { case WM_LBUTTONDOWN: button = MBT_LEFT; + wParam |= MK_LBUTTON; press = 1; break; case WM_MBUTTONDOWN: button = MBT_MIDDLE; + wParam |= MK_MBUTTON; press = 1; break; case WM_RBUTTONDOWN: button = MBT_RIGHT; + wParam |= MK_RBUTTON; press = 1; break; case WM_LBUTTONUP: button = MBT_LEFT; + wParam &= ~MK_LBUTTON; press = 0; break; case WM_MBUTTONUP: button = MBT_MIDDLE; + wParam &= ~MK_MBUTTON; press = 0; break; case WM_RBUTTONUP: button = MBT_RIGHT; + wParam &= ~MK_RBUTTON; press = 0; break; default: @@ -2264,7 +2421,8 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT, wParam & MK_CONTROL, is_alt_pressed()); - ReleaseCapture(); + if (!(wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON))) + ReleaseCapture(); } } return 0; @@ -2337,12 +2495,44 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, RealizePalette(hdc); } + /* + * We have to be careful about term_paint(). It will + * set a bunch of character cells to INVALID and then + * call do_paint(), which will redraw those cells and + * _then mark them as done_. This may not be accurate: + * when painting in WM_PAINT context we are restricted + * to the rectangle which has just been exposed - so if + * that only covers _part_ of a character cell and the + * rest of it was already visible, that remainder will + * not be redrawn at all. Accordingly, we must not + * paint any character cell in a WM_PAINT context which + * already has a pending update due to terminal output. + * The simplest solution to this - and many, many + * thanks to Hung-Te Lin for working all this out - is + * not to do any actual painting at _all_ if there's a + * pending terminal update: just mark the relevant + * character cells as INVALID and wait for the + * scheduled full update to sort it out. + * + * I have a suspicion this isn't the _right_ solution. + * An alternative approach would be to have terminal.c + * separately track what _should_ be on the terminal + * screen and what _is_ on the terminal screen, and + * have two completely different types of redraw (one + * for full updates, which syncs the former with the + * terminal itself, and one for WM_PAINT which syncs + * the latter with the former); yet another possibility + * would be to have the Windows front end do what the + * GTK one already does, and maintain a bitmap of the + * current terminal appearance so that WM_PAINT becomes + * completely trivial. However, this should do for now. + */ term_paint(term, hdc, (p.rcPaint.left-offset_width)/font_width, (p.rcPaint.top-offset_height)/font_height, (p.rcPaint.right-offset_width-1)/font_width, (p.rcPaint.bottom-offset_height-1)/font_height, - TRUE); + !term->window_update_pending); if (p.fErase || p.rcPaint.left < offset_width || @@ -2392,7 +2582,19 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, } return 0; case WM_NETEVENT: - enact_netevent(wParam, lParam); + /* Notice we can get multiple netevents, FD_READ, FD_WRITE etc + * but the only one that's likely to try to overload us is FD_READ. + * This means buffering just one is fine. + */ + if (pending_netevent) + enact_pending_netevent(); + + pending_netevent = TRUE; + pend_netevent_wParam = wParam; + pend_netevent_lParam = lParam; + if (WSAGETSELECTEVENT(lParam) != FD_READ) + enact_pending_netevent(); + net_pending_errors(); return 0; case WM_SETFOCUS: @@ -2679,13 +2881,15 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, unsigned char buf[20]; int len; - if (wParam == VK_PROCESSKEY) { - MSG m; - m.hwnd = hwnd; - m.message = WM_KEYDOWN; - m.wParam = wParam; - m.lParam = lParam & 0xdfff; - TranslateMessage(&m); + if (wParam == VK_PROCESSKEY) { /* IME PROCESS key */ + if (message == WM_KEYDOWN) { + MSG m; + m.hwnd = hwnd; + m.message = WM_KEYDOWN; + m.wParam = wParam; + m.lParam = lParam & 0xdfff; + TranslateMessage(&m); + } else break; /* pass to Windows for default processing */ } else { len = TranslateKey(message, wParam, lParam, buf); if (len == -1) @@ -2723,12 +2927,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, set_input_locale((HKL)lParam); sys_cursor_update(); break; - case WM_IME_NOTIFY: - if(wParam == IMN_SETOPENSTATUS) { + case WM_IME_STARTCOMPOSITION: + { HIMC hImc = ImmGetContext(hwnd); ImmSetCompositionFont(hImc, &lfont); ImmReleaseContext(hwnd, hImc); - return 0; } break; case WM_IME_COMPOSITION: @@ -2815,6 +3018,10 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, sfree(c); } return 0; + case WM_GOT_CLIPDATA: + if (process_clipdata((HGLOBAL)lParam, wParam)) + term_do_paste(term); + return 0; default: if (message == wm_mousewheel || message == WM_MOUSEWHEEL) { int shift_pressed=0, control_pressed=0; @@ -2848,16 +3055,22 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, if (send_raw_mouse && !(cfg.mouse_override && shift_pressed)) { - /* send a mouse-down followed by a mouse up */ - term_mouse(term, b, translate_button(b), - MA_CLICK, - TO_CHR_X(X_POS(lParam)), - TO_CHR_Y(Y_POS(lParam)), shift_pressed, - control_pressed, is_alt_pressed()); - term_mouse(term, b, translate_button(b), - MA_RELEASE, TO_CHR_X(X_POS(lParam)), - TO_CHR_Y(Y_POS(lParam)), shift_pressed, - control_pressed, is_alt_pressed()); + /* Mouse wheel position is in screen coordinates for + * some reason */ + POINT p; + p.x = X_POS(lParam); p.y = Y_POS(lParam); + if (ScreenToClient(hwnd, &p)) { + /* send a mouse-down followed by a mouse up */ + term_mouse(term, b, translate_button(b), + MA_CLICK, + TO_CHR_X(p.x), + TO_CHR_Y(p.y), shift_pressed, + control_pressed, is_alt_pressed()); + term_mouse(term, b, translate_button(b), + MA_RELEASE, TO_CHR_X(p.x), + TO_CHR_Y(p.y), shift_pressed, + control_pressed, is_alt_pressed()); + } /* else: not sure when this can fail */ } else { /* trigger a scroll */ term_scroll(term, 0, @@ -2869,6 +3082,10 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, } } + /* + * Any messages we don't process completely above are passed through to + * DefWindowProc() for default processing. + */ return DefWindowProc(hwnd, message, wParam, lParam); } @@ -3176,12 +3393,8 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, wbuf[i] = text[i]; /* print Glyphs as they are, without Windows' Shaping*/ - exact_textout(hdc, x, y - font_height * (lattr == LATTR_BOT) + text_adjust, - &line_box, wbuf, len, IpDx, !(attr & TATTR_COMBINING)); -/* ExtTextOutW(hdc, x, - y - font_height * (lattr == LATTR_BOT) + text_adjust, - ETO_CLIPPED | ETO_OPAQUE, &line_box, wbuf, len, IpDx); - */ + general_textout(hdc, x, y - font_height * (lattr == LATTR_BOT) + text_adjust, + &line_box, wbuf, len, IpDx, !(attr & TATTR_COMBINING)); /* And the shadow bold hack. */ if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) { @@ -3366,8 +3579,9 @@ int char_width(Context ctx, int uc) { /* * Translate a WM_(SYS)?KEY(UP|DOWN) message into a string of ASCII - * codes. Returns number of bytes used or zero to drop the message - * or -1 to forward the message to windows. + * codes. Returns number of bytes used, zero to drop the message, + * -1 to forward the message to Windows, or another negative number + * to indicate a NUL-terminated "special" string. */ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, unsigned char *output) @@ -3608,7 +3822,7 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, return 0; } if (wParam == VK_INSERT && shift_state == 1) { - term_do_paste(term); + request_paste(NULL); return 0; } if (left_alt && wParam == VK_F4 && cfg.alt_f4) { @@ -3634,31 +3848,31 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, if (cfg.nethack_keypad && !left_alt) { switch (wParam) { case VK_NUMPAD1: - *p++ = shift_state ? 'B' : 'b'; + *p++ = "bB\002\002"[shift_state & 3]; return p - output; case VK_NUMPAD2: - *p++ = shift_state ? 'J' : 'j'; + *p++ = "jJ\012\012"[shift_state & 3]; return p - output; case VK_NUMPAD3: - *p++ = shift_state ? 'N' : 'n'; + *p++ = "nN\016\016"[shift_state & 3]; return p - output; case VK_NUMPAD4: - *p++ = shift_state ? 'H' : 'h'; + *p++ = "hH\010\010"[shift_state & 3]; return p - output; case VK_NUMPAD5: *p++ = shift_state ? '.' : '.'; return p - output; case VK_NUMPAD6: - *p++ = shift_state ? 'L' : 'l'; + *p++ = "lL\014\014"[shift_state & 3]; return p - output; case VK_NUMPAD7: - *p++ = shift_state ? 'Y' : 'y'; + *p++ = "yY\031\031"[shift_state & 3]; return p - output; case VK_NUMPAD8: - *p++ = shift_state ? 'K' : 'k'; + *p++ = "kK\013\013"[shift_state & 3]; return p - output; case VK_NUMPAD9: - *p++ = shift_state ? 'U' : 'u'; + *p++ = "uU\025\025"[shift_state & 3]; return p - output; } } @@ -3787,9 +4001,9 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, return p - output; } if (wParam == VK_CANCEL && shift_state == 2) { /* Ctrl-Break */ - *p++ = 3; - *p++ = 0; - return -2; + if (back) + back->special(backhandle, TS_BRK); + return 0; } if (wParam == VK_PAUSE) { /* Break/Pause */ *p++ = 26; @@ -3805,7 +4019,7 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, *p++ = 0x1F; return p - output; } - if (shift_state == 2 && wParam == 0xDF) { + if (shift_state == 2 && (wParam == 0xDF || wParam == 0xDC)) { *p++ = 0x1C; return p - output; } @@ -4223,16 +4437,6 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, return -1; } -void request_paste(void *frontend) -{ - /* - * In Windows, pasting is synchronous: we can read the - * clipboard with no difficulty, so request_paste() can just go - * ahead and paste. - */ - term_do_paste(term); -} - void set_title(void *frontend, char *title) { sfree(window_name); @@ -4311,6 +4515,12 @@ void palette_set(void *frontend, int n, int r, int g, int b) UnrealizeObject(pal); RealizePalette(hdc); free_ctx(hdc); + } else { + if (n == (ATTR_DEFBG>>ATTR_BGSHIFT)) + /* If Default Background changes, we need to ensure any + * space between the text area and the window border is + * redrawn. */ + InvalidateRect(hwnd, NULL, TRUE); } } @@ -4339,6 +4549,10 @@ void palette_reset(void *frontend) hdc = get_ctx(frontend); RealizePalette(hdc); free_ctx(hdc); + } else { + /* Default Background may have changed. Ensure any space between + * text area and window border is redrawn. */ + InvalidateRect(hwnd, NULL, TRUE); } } @@ -4374,7 +4588,7 @@ void write_aclip(void *frontend, char *data, int len, int must_deselect) /* * Note: unlike write_aclip() this will not append a nul. */ -void write_clip(void *frontend, wchar_t * data, int len, int must_deselect) +void write_clip(void *frontend, wchar_t * data, int *attr, int len, int must_deselect) { HGLOBAL clipdata, clipdata2, clipdata3; int len2; @@ -4410,14 +4624,83 @@ void write_clip(void *frontend, wchar_t * data, int len, int must_deselect) int rtfsize = 0; int multilen, blen, alen, totallen, i; char before[16], after[4]; + int fgcolour, lastfgcolour = 0; + int bgcolour, lastbgcolour = 0; + int attrBold, lastAttrBold = 0; + int attrUnder, lastAttrUnder = 0; + int palette[NALLCOLOURS]; + int numcolours; get_unitab(CP_ACP, unitab, 0); rtfsize = 100 + strlen(cfg.font.name); rtf = snewn(rtfsize, char); - sprintf(rtf, "{\\rtf1\\ansi%d{\\fonttbl\\f0\\fmodern %s;}\\f0", - GetACP(), cfg.font.name); - rtflen = strlen(rtf); + rtflen = sprintf(rtf, "{\\rtf1\\ansi\\deff0{\\fonttbl\\f0\\fmodern %s;}\\f0\\fs%d", + cfg.font.name, cfg.font.height*2); + + /* + * Add colour palette + * {\colortbl ;\red255\green0\blue0;\red0\green0\blue128;} + */ + + /* + * First - Determine all colours in use + * o Foregound and background colours share the same palette + */ + if (attr) { + memset(palette, 0, sizeof(palette)); + for (i = 0; i < (len-1); i++) { + fgcolour = ((attr[i] & ATTR_FGMASK) >> ATTR_FGSHIFT); + bgcolour = ((attr[i] & ATTR_BGMASK) >> ATTR_BGSHIFT); + + if (attr[i] & ATTR_REVERSE) { + int tmpcolour = fgcolour; /* Swap foreground and background */ + fgcolour = bgcolour; + bgcolour = tmpcolour; + } + + if (bold_mode == BOLD_COLOURS && (attr[i] & ATTR_BOLD)) { + if (fgcolour < 8) /* ANSI colours */ + fgcolour += 8; + else if (fgcolour >= 256) /* Default colours */ + fgcolour ++; + } + + if (attr[i] & ATTR_BLINK) { + if (bgcolour < 8) /* ANSI colours */ + bgcolour += 8; + else if (bgcolour >= 256) /* Default colours */ + bgcolour ++; + } + + palette[fgcolour]++; + palette[bgcolour]++; + } + + /* + * Next - Create a reduced palette + */ + numcolours = 0; + for (i = 0; i < NALLCOLOURS; i++) { + if (palette[i] != 0) + palette[i] = ++numcolours; + } + + /* + * Finally - Write the colour table + */ + rtf = sresize(rtf, rtfsize + (numcolours * 25), char); + strcat(rtf, "{\\colortbl ;"); + rtflen = strlen(rtf); + + for (i = 0; i < NALLCOLOURS; i++) { + if (palette[i] != 0) { + rtflen += sprintf(&rtf[rtflen], "\\red%d\\green%d\\blue%d;", defpal[i].rgbtRed, defpal[i].rgbtGreen, defpal[i].rgbtBlue); + } + } + strcpy(&rtf[rtflen], "}"); + rtflen ++; + } /* * We want to construct a piece of RTF that specifies the @@ -4444,7 +4727,96 @@ void write_clip(void *frontend, wchar_t * data, int len, int must_deselect) tdata[tindex+1] == '\n') { tindex++; uindex++; + } + + /* + * Set text attributes + */ + if (attr) { + if (rtfsize < rtflen + 64) { + rtfsize = rtflen + 512; + rtf = sresize(rtf, rtfsize, char); + } + + /* + * Determine foreground and background colours + */ + fgcolour = ((attr[tindex] & ATTR_FGMASK) >> ATTR_FGSHIFT); + bgcolour = ((attr[tindex] & ATTR_BGMASK) >> ATTR_BGSHIFT); + + if (attr[tindex] & ATTR_REVERSE) { + int tmpcolour = fgcolour; /* Swap foreground and background */ + fgcolour = bgcolour; + bgcolour = tmpcolour; + } + + if (bold_mode == BOLD_COLOURS && (attr[tindex] & ATTR_BOLD)) { + if (fgcolour < 8) /* ANSI colours */ + fgcolour += 8; + else if (fgcolour >= 256) /* Default colours */ + fgcolour ++; + } + + if (attr[tindex] & ATTR_BLINK) { + if (bgcolour < 8) /* ANSI colours */ + bgcolour += 8; + else if (bgcolour >= 256) /* Default colours */ + bgcolour ++; + } + + /* + * Collect other attributes + */ + if (bold_mode != BOLD_COLOURS) + attrBold = attr[tindex] & ATTR_BOLD; + else + attrBold = 0; + + attrUnder = attr[tindex] & ATTR_UNDER; + + /* + * Reverse video + * o If video isn't reversed, ignore colour attributes for default foregound + * or background. + * o Special case where bolded text is displayed using the default foregound + * and background colours - force to bolded RTF. + */ + if (!(attr[tindex] & ATTR_REVERSE)) { + if (bgcolour >= 256) /* Default color */ + bgcolour = -1; /* No coloring */ + + if (fgcolour >= 256) { /* Default colour */ + if (bold_mode == BOLD_COLOURS && (fgcolour & 1) && bgcolour == -1) + attrBold = ATTR_BOLD; /* Emphasize text with bold attribute */ + + fgcolour = -1; /* No coloring */ + } + } + + /* + * Write RTF text attributes + */ + if (lastfgcolour != fgcolour) { + lastfgcolour = fgcolour; + rtflen += sprintf(&rtf[rtflen], "\\cf%d ", (fgcolour >= 0) ? palette[fgcolour] : 0); + } + + if (lastbgcolour != bgcolour) { + lastbgcolour = bgcolour; + rtflen += sprintf(&rtf[rtflen], "\\highlight%d ", (bgcolour >= 0) ? palette[bgcolour] : 0); + } + + if (lastAttrBold != attrBold) { + lastAttrBold = attrBold; + rtflen += sprintf(&rtf[rtflen], "%s", attrBold ? "\\b " : "\\b0 "); + } + + if (lastAttrUnder != attrUnder) { + lastAttrUnder = attrUnder; + rtflen += sprintf(&rtf[rtflen], "%s", attrUnder ? "\\ul " : "\\ulnone "); + } } + if (unitab[tdata[tindex]] == udata[uindex]) { multilen = 1; before[0] = '\0'; @@ -4503,12 +4875,13 @@ void write_clip(void *frontend, wchar_t * data, int len, int must_deselect) uindex++; } - strcpy(rtf + rtflen, "}"); - rtflen += 2; + rtf[rtflen++] = '}'; /* Terminate RTF stream */ + rtf[rtflen++] = '\0'; + rtf[rtflen++] = '\0'; clipdata3 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, rtflen); if (clipdata3 && (lock3 = GlobalLock(clipdata3)) != NULL) { - strcpy(lock3, rtf); + memcpy(lock3, rtf, rtflen); GlobalUnlock(clipdata3); } sfree(rtf); @@ -4537,46 +4910,89 @@ void write_clip(void *frontend, wchar_t * data, int len, int must_deselect) SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0); } -void get_clip(void *frontend, wchar_t ** p, int *len) +static DWORD WINAPI clipboard_read_threadfunc(void *param) { - static HGLOBAL clipdata = NULL; - static wchar_t *converted = 0; - wchar_t *p2; + HWND hwnd = (HWND)param; + HGLOBAL clipdata; - if (converted) { - sfree(converted); - converted = 0; - } - if (!p) { - if (clipdata) - GlobalUnlock(clipdata); - clipdata = NULL; - return; - } else if (OpenClipboard(NULL)) { + if (OpenClipboard(NULL)) { if ((clipdata = GetClipboardData(CF_UNICODETEXT))) { - CloseClipboard(); - *p = GlobalLock(clipdata); - if (*p) { - for (p2 = *p; *p2; p2++); - *len = p2 - *p; - return; - } - } else if ( (clipdata = GetClipboardData(CF_TEXT)) ) { - char *s; - int i; - CloseClipboard(); - s = GlobalLock(clipdata); + SendMessage(hwnd, WM_GOT_CLIPDATA, (WPARAM)1, (LPARAM)clipdata); + } else if ((clipdata = GetClipboardData(CF_TEXT))) { + SendMessage(hwnd, WM_GOT_CLIPDATA, (WPARAM)0, (LPARAM)clipdata); + } + CloseClipboard(); + } + + return 0; +} + +static int process_clipdata(HGLOBAL clipdata, int unicode) +{ + sfree(clipboard_contents); + clipboard_contents = NULL; + clipboard_length = 0; + + if (unicode) { + wchar_t *p = GlobalLock(clipdata); + wchar_t *p2; + + if (p) { + /* Unwilling to rely on Windows having wcslen() */ + for (p2 = p; *p2; p2++); + clipboard_length = p2 - p; + clipboard_contents = snewn(clipboard_length + 1, wchar_t); + memcpy(clipboard_contents, p, clipboard_length * sizeof(wchar_t)); + clipboard_contents[clipboard_length] = L'\0'; + return TRUE; + } + } else { + char *s = GlobalLock(clipdata); + int i; + + if (s) { i = MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, 0, 0); - *p = converted = snewn(i, wchar_t); - MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, converted, i); - *len = i - 1; - return; - } else - CloseClipboard(); + clipboard_contents = snewn(i, wchar_t); + MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, + clipboard_contents, i); + clipboard_length = i - 1; + clipboard_contents[clipboard_length] = L'\0'; + return TRUE; + } } - *p = NULL; - *len = 0; + return FALSE; +} + +void request_paste(void *frontend) +{ + /* + * I always thought pasting was synchronous in Windows; the + * clipboard access functions certainly _look_ synchronous, + * unlike the X ones. But in fact it seems that in some + * situations the contents of the clipboard might not be + * immediately available, and the clipboard-reading functions + * may block. This leads to trouble if the application + * delivering the clipboard data has to get hold of it by - + * for example - talking over a network connection which is + * forwarded through this very PuTTY. + * + * Hence, we spawn a subthread to read the clipboard, and do + * our paste when it's finished. The thread will send a + * message back to our main window when it terminates, and + * that tells us it's OK to paste. + */ + DWORD in_threadid; /* required for Win9x */ + CreateThread(NULL, 0, clipboard_read_threadfunc, + hwnd, 0, &in_threadid); +} + +void get_clip(void *frontend, wchar_t **p, int *len) +{ + if (p) { + *p = clipboard_contents; + *len = clipboard_length; + } } #if 0 @@ -4635,10 +5051,41 @@ void modalfatalbox(char *fmt, ...) cleanup_exit(1); } +typedef BOOL (WINAPI *p_FlashWindowEx_t)(PFLASHWINFO); +static p_FlashWindowEx_t p_FlashWindowEx = NULL; + +static void init_flashwindow(void) +{ + HMODULE user32_module = LoadLibrary("USER32.DLL"); + if (user32_module) { + p_FlashWindowEx = (p_FlashWindowEx_t) + GetProcAddress(user32_module, "FlashWindowEx"); + } +} + +static BOOL flash_window_ex(DWORD dwFlags, UINT uCount, DWORD dwTimeout) +{ + if (p_FlashWindowEx) { + FLASHWINFO fi; + fi.cbSize = sizeof(fi); + fi.hwnd = hwnd; + fi.dwFlags = dwFlags; + fi.uCount = uCount; + fi.dwTimeout = dwTimeout; + return (*p_FlashWindowEx)(&fi); + } + else + return FALSE; /* shrug */ +} + static void flash_window(int mode); static long next_flash; static int flashing = 0; +/* + * Timer for platforms where we must maintain window flashing manually + * (e.g., Win95). + */ static void flash_window_timer(void *ctx, long now) { if (flashing && now - next_flash >= 0) { @@ -4655,21 +5102,37 @@ static void flash_window(int mode) if ((mode == 0) || (cfg.beep_ind == B_IND_DISABLED)) { /* stop */ if (flashing) { - FlashWindow(hwnd, FALSE); flashing = 0; + if (p_FlashWindowEx) + flash_window_ex(FLASHW_STOP, 0, 0); + else + FlashWindow(hwnd, FALSE); } } else if (mode == 2) { /* start */ if (!flashing) { flashing = 1; - FlashWindow(hwnd, TRUE); - next_flash = schedule_timer(450, flash_window_timer, hwnd); + if (p_FlashWindowEx) { + /* For so-called "steady" mode, we use uCount=2, which + * seems to be the traditional number of flashes used + * by user notifications (e.g., by Explorer). + * uCount=0 appears to enable continuous flashing, per + * "flashing" mode, although I haven't seen this + * documented. */ + flash_window_ex(FLASHW_ALL | FLASHW_TIMER, + (cfg.beep_ind == B_IND_FLASH ? 0 : 2), + 0 /* system cursor blink rate */); + /* No need to schedule timer */ + } else { + FlashWindow(hwnd, TRUE); + next_flash = schedule_timer(450, flash_window_timer, hwnd); + } } } else if ((mode == 1) && (cfg.beep_ind == B_IND_FLASH)) { /* maintain */ - if (flashing) { + if (flashing && !p_FlashWindowEx) { FlashWindow(hwnd, TRUE); /* toggle */ next_flash = schedule_timer(450, flash_window_timer, hwnd); } @@ -4679,7 +5142,7 @@ static void flash_window(int mode) /* * Beep. */ -void beep(void *frontend, int mode) +void do_beep(void *frontend, int mode) { if (mode == BELL_DEFAULT) { /* @@ -4844,7 +5307,7 @@ static int is_full_screen() { if (!IsZoomed(hwnd)) return FALSE; - if (GetWindowLong(hwnd, GWL_STYLE) & WS_CAPTION) + if (GetWindowLongPtr(hwnd, GWL_STYLE) & WS_CAPTION) return FALSE; return TRUE; } @@ -4890,13 +5353,13 @@ static void make_full_screen() return; /* Remove the window furniture. */ - style = GetWindowLong(hwnd, GWL_STYLE); + style = GetWindowLongPtr(hwnd, GWL_STYLE); style &= ~(WS_CAPTION | WS_BORDER | WS_THICKFRAME); if (cfg.scrollbar_in_fullscreen) style |= WS_VSCROLL; else style &= ~WS_VSCROLL; - SetWindowLong(hwnd, GWL_STYLE, style); + SetWindowLongPtr(hwnd, GWL_STYLE, style); /* Resize ourselves to exactly cover the nearest monitor. */ get_fullscreen_rect(&ss); @@ -4905,9 +5368,16 @@ static void make_full_screen() ss.bottom - ss.top, SWP_FRAMECHANGED); - /* Tick the menu item in the System menu. */ - CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN, - MF_CHECKED); + /* We may have changed size as a result */ + + reset_window(0); + + /* Tick the menu item in the System and context menus. */ + { + int i; + for (i = 0; i < lenof(popup_menus); i++) + CheckMenuItem(popup_menus[i].menu, IDM_FULLSCREEN, MF_CHECKED); + } } /* @@ -4918,7 +5388,7 @@ static void clear_full_screen() DWORD oldstyle, style; /* Reinstate the window furniture. */ - style = oldstyle = GetWindowLong(hwnd, GWL_STYLE); + style = oldstyle = GetWindowLongPtr(hwnd, GWL_STYLE); style |= WS_CAPTION | WS_BORDER; if (cfg.resize_action == RESIZE_DISABLED) style &= ~WS_THICKFRAME; @@ -4929,15 +5399,18 @@ static void clear_full_screen() else style &= ~WS_VSCROLL; if (style != oldstyle) { - SetWindowLong(hwnd, GWL_STYLE, style); + SetWindowLongPtr(hwnd, GWL_STYLE, style); SetWindowPos(hwnd, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); } - /* Untick the menu item in the System menu. */ - CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN, - MF_UNCHECKED); + /* Untick the menu item in the System and context menus. */ + { + int i; + for (i = 0; i < lenof(popup_menus); i++) + CheckMenuItem(popup_menus[i].menu, IDM_FULLSCREEN, MF_UNCHECKED); + } } /* @@ -4971,6 +5444,20 @@ int from_backend(void *frontend, int is_stderr, const char *data, int len) return term_data(term, is_stderr, data, len); } +int from_backend_untrusted(void *frontend, const char *data, int len) +{ + return term_data_untrusted(term, data, len); +} + +int get_userpass_input(prompts_t *p, unsigned char *in, int inlen) +{ + int ret; + ret = cmdline_get_passwd_input(p, in, inlen); + if (ret == -1) + ret = term_get_userpass_input(term, p, in, inlen); + return ret; +} + void agent_schedule_callback(void (*callback)(void *, void *, int), void *callback_ctx, void *data, int len) {