X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/44e975e0ca158977991317b312238ab136820f4a..53e66fad5465e72c7231ff4af272ab584dd33f1a:/windows/window.c diff --git a/windows/window.c b/windows/window.c index c86b14b5..670a1ace 100644 --- a/windows/window.c +++ b/windows/window.c @@ -10,6 +10,10 @@ #include #include +#ifndef NO_MULTIMON +#define COMPILE_MULTIMON_STUBS +#endif + #define PUTTY_DO_GLOBALS /* actually _define_ globals */ #include "putty.h" #include "terminal.h" @@ -17,11 +21,8 @@ #include "win_res.h" #ifndef NO_MULTIMON -#if WINVER < 0x0500 -#define COMPILE_MULTIMON_STUBS #include #endif -#endif #include #include @@ -60,6 +61,7 @@ #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 @@ -86,11 +88,13 @@ 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); @@ -125,6 +129,9 @@ static const struct telnet_special *specials = NULL; static HMENU specials_menu = NULL; static int n_specials = 0; +static wchar_t *clipboard_contents; +static size_t clipboard_length; + #define TIMING_TIMER_ID 1234 static long timing_next_time; @@ -217,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", @@ -297,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); } @@ -350,23 +353,26 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) init_help(); + init_flashwindow(); + /* * Process the command line. */ { 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; @@ -392,6 +398,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) 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 @@ -410,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 @@ -534,7 +542,10 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) cmdline_run_saved(&cfg); - if (!cfg_launchable(&cfg) && !do_config()) { + if (loaded_session || got_host) + allow_launch = TRUE; + + if ((!allow_launch || !cfg_launchable(&cfg)) && !do_config()) { cleanup_exit(0); } @@ -591,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; @@ -811,9 +813,8 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) sfree(handles); if (must_close_session) close_session(); - } - - sfree(handles); + } else + sfree(handles); while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) @@ -1324,7 +1325,7 @@ debug(("\n rect: [%d,%d %d,%d]\n", newrc.left, newrc.top, newrc.right, #ifdef FIXME_REMOVE_BEFORE_CHECKIN debug(("general_textout: done, xn=%d\n", xn)); #endif - assert(xn - x == lprc->right - lprc->left); + assert(xn - x >= lprc->right - lprc->left); } /* @@ -2111,10 +2112,12 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, { /* 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)) { @@ -2246,7 +2249,7 @@ 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); @@ -2334,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: @@ -2412,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; @@ -3008,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; @@ -3041,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, @@ -3559,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) @@ -3801,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) { @@ -3980,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; @@ -3998,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; } @@ -4416,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); @@ -4899,46 +4910,89 @@ void write_clip(void *frontend, wchar_t * data, int *attr, int len, int must_des 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 @@ -4997,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) { @@ -5017,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); } @@ -5271,9 +5372,12 @@ static void make_full_screen() reset_window(0); - /* Tick the menu item in the System menu. */ - CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN, - MF_CHECKED); + /* 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); + } } /* @@ -5301,9 +5405,12 @@ static void clear_full_screen() 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); + } } /*