X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/1620020b0980213cde7254a2f53aaa3206e9413c..e414a330d66b19abceb4c367dac70408b815b302:/windows/window.c diff --git a/windows/window.c b/windows/window.c index 65bca731..86cece85 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 @@ -80,6 +87,7 @@ 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); @@ -94,7 +102,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); @@ -109,7 +120,7 @@ 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; @@ -208,12 +219,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", @@ -229,7 +235,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); @@ -266,6 +272,7 @@ static void start_backend(void) DeleteMenu(popup_menus[i].menu, IDM_RESTART, MF_BYCOMMAND); } + must_close_session = FALSE; session_closed = FALSE; } @@ -287,6 +294,7 @@ static void close_session(void) back->free(backhandle); backhandle = NULL; back = NULL; + term_provide_resize_fn(term, NULL, NULL); update_specials_menu(NULL); } @@ -338,31 +346,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_HELP_FILE); - if ( (fp = fopen(b, "r")) != NULL) { - help_path = dupstr(b); - fclose(fp); - } else - help_path = NULL; - strcpy(r, PUTTY_HELP_CONTENTS); - 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. @@ -370,17 +356,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; @@ -403,9 +390,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 @@ -424,6 +412,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 @@ -548,7 +537,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); } @@ -605,15 +597,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; @@ -780,7 +763,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); @@ -811,8 +794,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 */ @@ -821,23 +823,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 */ } @@ -862,6 +861,7 @@ void cleanup_exit(int code) crypto_wrapup(); #endif } + shutdown_help(); exit(code); } @@ -965,7 +965,7 @@ void update_specials_menu(void *frontend) for (j = 0; j < lenof(popup_menus); j++) { if (specials_menu) { /* XXX does this free up all submenus? */ - DeleteMenu(popup_menus[j].menu, specials_menu, MF_BYCOMMAND); + DeleteMenu(popup_menus[j].menu, (UINT)specials_menu, MF_BYCOMMAND); DeleteMenu(popup_menus[j].menu, IDM_SPECIALSEP, MF_BYCOMMAND); } if (new_menu) { @@ -1051,7 +1051,7 @@ void connection_fatal(void *frontend, char *fmt, ...) if (cfg.close_on_exit == FORCE_ON) PostQuitMessage(1); else { - close_session(); + must_close_session = TRUE; } } @@ -1075,31 +1075,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); - } - } } /* @@ -1241,7 +1229,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, @@ -1253,6 +1241,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. @@ -1844,7 +1915,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) { @@ -1935,11 +2028,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)); @@ -1954,7 +2047,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, } 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); @@ -2163,8 +2256,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: /* @@ -2476,7 +2568,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: @@ -3265,12 +3369,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)) { @@ -3455,8 +3555,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) @@ -3876,9 +3977,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; @@ -3894,7 +3995,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; } @@ -4893,10 +4994,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) { @@ -4913,21 +5045,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); }