X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/3a9531212a6d0077e566912d4790020315d15eb1..102b17eb3753c35dcd743c3813f26468fd4e5b6c:/window.c diff --git a/window.c b/window.c index 1b61b6c6..59757d91 100644 --- a/window.c +++ b/window.c @@ -11,10 +11,12 @@ #endif #endif +#ifndef NO_MULTIMON #if WINVER < 0x0500 #define COMPILE_MULTIMON_STUBS #include #endif +#endif #include #include @@ -67,9 +69,12 @@ #define VK_PROCESSKEY 0xE5 #endif -/* Needed for mouse wheel support and not defined in earlier SDKs. */ +/* Mouse wheel support. */ #ifndef WM_MOUSEWHEEL -#define WM_MOUSEWHEEL 0x020A +#define WM_MOUSEWHEEL 0x020A /* not defined in earlier SDKs */ +#endif +#ifndef WHEEL_DELTA +#define WHEEL_DELTA 120 #endif static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); @@ -81,6 +86,7 @@ static void init_fonts(int, int); static void another_font(int); static void deinit_fonts(void); static void set_input_locale(HKL); +static int do_mouse_wheel_msg(UINT message, WPARAM wParam, LPARAM lParam); static int is_full_screen(void); static void make_full_screen(void); @@ -100,9 +106,13 @@ 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 get_fullscreen_rect(RECT * ss); static time_t last_movement = 0; +static int caret_x = -1, caret_y = -1; + #define FONT_NORMAL 0 #define FONT_BOLD 1 #define FONT_UNDERLINE 2 @@ -150,8 +160,12 @@ static char *window_name, *icon_name; static int compose_state = 0; +static int wsa_started = 0; + static OSVERSIONINFO osVersion; +static UINT wm_mousewheel = WM_MOUSEWHEEL; + /* Dummy routine, only required in plink. */ void ldisc_update(int echo, int edit) { @@ -181,6 +195,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) WSACleanup(); return 1; } + wsa_started = 1; /* WISHLIST: maybe allow config tweaking even if winsock not present? */ sk_init(); @@ -201,6 +216,16 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) } /* + * If we're running a version of Windows that doesn't support + * WM_MOUSEWHEEL, find out what message number we should be + * using instead. + */ + if (osVersion.dwMajorVersion < 4 || + (osVersion.dwMajorVersion == 4 && + osVersion.dwPlatformId != VER_PLATFORM_WIN32_NT)) + wm_mousewheel = RegisterWindowMessage("MSWHEEL_ROLLMSG"); + + /* * See if we can find our Help file. */ { @@ -231,6 +256,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) */ { char *p; + int got_host = 0; default_protocol = DEFAULT_PROTOCOL; default_port = DEFAULT_PORT; @@ -239,54 +265,16 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) do_defaults(NULL, &cfg); p = cmdline; - while (*p && isspace(*p)) - p++; /* - * Process command line options first. Yes, this can be - * done better, and it will be as soon as I have the - * energy... + * Process a couple of command-line options which are more + * easily dealt with before the line is broken up into + * words. These are the soon-to-be-defunct @sessionname and + * the internal-use-only &sharedmemoryhandle, neither of + * which are combined with anything else. */ - while (*p == '-') { - char *q = p + strcspn(p, " \t"); + while (*p && isspace(*p)) p++; - if (q == p + 3 && - tolower(p[0]) == 's' && - tolower(p[1]) == 's' && tolower(p[2]) == 'h') { - default_protocol = cfg.protocol = PROT_SSH; - default_port = cfg.port = 22; - } else if (q == p + 7 && - tolower(p[0]) == 'c' && - tolower(p[1]) == 'l' && - tolower(p[2]) == 'e' && - tolower(p[3]) == 'a' && - tolower(p[4]) == 'n' && - tolower(p[5]) == 'u' && tolower(p[6]) == 'p') { - /* - * `putty -cleanup'. Remove all registry entries - * associated with PuTTY, and also find and delete - * the random seed file. - */ - if (MessageBox(NULL, - "This procedure will remove ALL Registry\n" - "entries associated with PuTTY, and will\n" - "also remove the PuTTY random seed file.\n" - "\n" - "THIS PROCESS WILL DESTROY YOUR SAVED\n" - "SESSIONS. Are you really sure you want\n" - "to continue?", - "PuTTY Warning", - MB_YESNO | MB_ICONWARNING) == IDYES) { - cleanup_all(); - } - exit(0); - } - p = q + strspn(q, " \t"); - } - - /* - * An initial @ means to activate a saved session. - */ if (*p == '@') { int i = strlen(p); while (i > 1 && isspace(p[i - 1])) @@ -316,53 +304,107 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) WSACleanup(); return 0; } - } else if (*p) { - char *q = p; + } else { /* - * If the hostname starts with "telnet:", set the - * protocol to Telnet and process the string as a - * Telnet URL. + * Otherwise, break up the command line and deal with + * it sensibly. */ - if (!strncmp(q, "telnet:", 7)) { - char c; - - q += 7; - if (q[0] == '/' && q[1] == '/') - q += 2; - cfg.protocol = PROT_TELNET; - p = q; - while (*p && *p != ':' && *p != '/') - p++; - c = *p; - if (*p) - *p++ = '\0'; - if (c == ':') - cfg.port = atoi(p); - else - cfg.port = -1; - strncpy(cfg.host, q, sizeof(cfg.host) - 1); - cfg.host[sizeof(cfg.host) - 1] = '\0'; - } else { - while (*p && !isspace(*p)) - p++; - if (*p) - *p++ = '\0'; - strncpy(cfg.host, q, sizeof(cfg.host) - 1); - cfg.host[sizeof(cfg.host) - 1] = '\0'; - while (*p && isspace(*p)) - p++; - if (*p) - cfg.port = atoi(p); - else - cfg.port = -1; - } - } else { - if (!do_config()) { - WSACleanup(); - return 0; + int argc, i; + char **argv; + + split_into_argv(cmdline, &argc, &argv, NULL); + + for (i = 0; i < argc; i++) { + char *p = argv[i]; + int ret; + + ret = cmdline_process_param(p, i+1 r.right - r.left) guess_width = r.right - r.left; if (guess_height > r.bottom - r.top) @@ -704,7 +745,9 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) timer_id = 0; } HideCaret(hwnd); - term_out(); + if (GetCapture() != hwnd || + (send_raw_mouse && !(cfg.mouse_override && is_shift_pressed()))) + term_out(); term_update(); ShowCaret(hwnd); @@ -731,6 +774,15 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) } } + cleanup_exit(msg.wParam); /* this doesn't return... */ + return msg.wParam; /* ... but optimiser doesn't know */ +} + +/* + * Clean up and exit. + */ +void cleanup_exit(int code) +{ /* * Clean up. */ @@ -738,7 +790,9 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) sfree(logpal); if (pal) DeleteObject(pal); - WSACleanup(); + sk_cleanup(); + if (wsa_started) + WSACleanup(); if (cfg.protocol == PROT_SSH) { random_save_seed(); @@ -747,7 +801,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) #endif } - return msg.wParam; + exit(code); } /* @@ -781,6 +835,7 @@ char *do_select(SOCKET skt, int startup) */ void set_raw_mouse_mode(int activate) { + activate = activate && !cfg.no_mouse_rep; send_raw_mouse = activate; SetCursor(LoadCursor(NULL, activate ? IDC_ARROW : IDC_IBEAM)); } @@ -806,6 +861,21 @@ void connection_fatal(char *fmt, ...) } /* + * Report an error at the command-line parsing stage. + */ +void cmdline_error(char *fmt, ...) +{ + va_list ap; + char stuff[200]; + + va_start(ap, fmt); + vsprintf(stuff, fmt, ap); + va_end(ap); + MessageBox(hwnd, stuff, "PuTTY Command Line Error", MB_ICONERROR | MB_OK); + exit(1); +} + +/* * Actually do the job requested by a WM_NETEVENT */ static void enact_pending_netevent(void) @@ -1175,7 +1245,7 @@ void request_resize(int w, int h) switch (first_time) { case 1: /* Get the size of the screen */ - if (GetClientRect(GetDesktopWindow(), &ss)) + if (get_fullscreen_rect(&ss)) /* first_time = 0 */ ; else { first_time = 2; @@ -1344,8 +1414,9 @@ static void reset_window(int reinit) { static RECT ss; int width, height; + + get_fullscreen_rect(&ss); - GetClientRect(GetDesktopWindow(), &ss); width = (ss.right - ss.left - extra_width) / font_width; height = (ss.bottom - ss.top - extra_height) / font_height; @@ -1484,6 +1555,17 @@ static int is_alt_pressed(void) return FALSE; } +static int is_shift_pressed(void) +{ + BYTE keystate[256]; + int r = GetKeyboardState(keystate); + if (!r) + return FALSE; + if (keystate[VK_SHIFT] & 0x80) + return TRUE; + return FALSE; +} + static int resizing; static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, @@ -1498,7 +1580,9 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, case WM_TIMER: if (pending_netevent) enact_pending_netevent(); - term_out(); + if (GetCapture() != hwnd || + (send_raw_mouse && !(cfg.mouse_override && is_shift_pressed()))) + term_out(); noise_regular(); HideCaret(hwnd); term_update(); @@ -1648,6 +1732,9 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, cfgtopalette(); init_palette(); + /* Give terminal a heads-up on miscellaneous stuff */ + term_reconfig(); + /* Screen size changed ? */ if (cfg.height != prev_cfg.height || cfg.width != prev_cfg.width || @@ -1846,45 +1933,6 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, #define TO_CHR_X(x) ((((x)<0 ? (x)-font_width+1 : (x))-offset_width) / font_width) #define TO_CHR_Y(y) ((((y)<0 ? (y)-font_height+1: (y))-offset_height) / font_height) -#define WHEEL_DELTA 120 - case WM_MOUSEWHEEL: - { - wheel_accumulator += (short) HIWORD(wParam); - wParam = LOWORD(wParam); - - /* process events when the threshold is reached */ - while (abs(wheel_accumulator) >= WHEEL_DELTA) { - int b; - - /* reduce amount for next time */ - if (wheel_accumulator > 0) { - b = MBT_WHEEL_UP; - wheel_accumulator -= WHEEL_DELTA; - } else if (wheel_accumulator < 0) { - b = MBT_WHEEL_DOWN; - wheel_accumulator += WHEEL_DELTA; - } else - break; - - if (send_raw_mouse) { - /* send a mouse-down followed by a mouse up */ - - term_mouse(b, - MA_CLICK, - TO_CHR_X(X_POS(lParam)), - TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT, - wParam & MK_CONTROL, is_alt_pressed()); - term_mouse(b, MA_RELEASE, TO_CHR_X(X_POS(lParam)), - TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT, - wParam & MK_CONTROL, is_alt_pressed()); - } else { - /* trigger a scroll */ - term_scroll(0, - b == MBT_WHEEL_UP ? -rows / 2 : rows / 2); - } - } - return 0; - } case WM_LBUTTONDOWN: case WM_MBUTTONDOWN: case WM_RBUTTONDOWN: @@ -1957,7 +2005,8 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, */ noise_ultralight(lParam); - if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)) { + if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON) && + GetCapture() == hwnd) { Mouse_Button b; if (wParam & MK_LBUTTON) b = MBT_LEFT; @@ -2012,6 +2061,17 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, colours[(ATTR_DEFBG>>ATTR_BGSHIFT)*2]); oldpen = SelectObject(hdc, edge); + /* + * Jordan Russell reports that this apparently + * ineffectual IntersectClipRect() call masks a + * Windows NT/2K bug causing strange display + * problems when the PuTTY window is taller than + * the primary monitor. It seems harmless enough... + */ + IntersectClipRect(hdc, + p.rcPaint.left, p.rcPaint.top, + p.rcPaint.right, p.rcPaint.bottom); + ExcludeClipRect(hdc, offset_width, offset_height, offset_width+font_width*cols, @@ -2062,6 +2122,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, show_mouseptr(1); has_focus = FALSE; DestroyCaret(); + caret_x = caret_y = -1; /* ensure caret is replaced next time */ term_out(); term_update(); break; @@ -2178,6 +2239,9 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, case WM_FULLSCR_ON_MAX: fullscr_on_max = TRUE; break; + case WM_MOVE: + sys_cursor_update(); + break; case WM_SIZE: #ifdef RDB_DEBUG_PATCH debug((27, "WM_SIZE %s (%d,%d)", @@ -2196,8 +2260,8 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, if (wParam == SIZE_RESTORED) clear_full_screen(); if (wParam == SIZE_MAXIMIZED && fullscr_on_max) { - make_full_screen(); fullscr_on_max = FALSE; + make_full_screen(); } if (cfg.resize_action == RESIZE_DISABLED) { @@ -2261,6 +2325,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, reset_window(0); } } + sys_cursor_update(); return 0; case WM_VSCROLL: switch (LOWORD(wParam)) { @@ -2370,6 +2435,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, /* wParam == Font number */ /* lParam == Locale */ set_input_locale((HKL)lParam); + sys_cursor_update(); break; case WM_IME_NOTIFY: if(wParam == IMN_SETOPENSTATUS) { @@ -2442,6 +2508,56 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, SetCursor(LoadCursor(NULL, IDC_ARROW)); return TRUE; } + default: + if (message == wm_mousewheel || message == WM_MOUSEWHEEL) { + int shift_pressed=0, control_pressed=0, alt_pressed=0; + + if (message == WM_MOUSEWHEEL) { + wheel_accumulator += (short)HIWORD(wParam); + shift_pressed=LOWORD(wParam) & MK_SHIFT; + control_pressed=LOWORD(wParam) & MK_CONTROL; + } else { + BYTE keys[256]; + wheel_accumulator += (int)wParam; + if (GetKeyboardState(keys)!=0) { + shift_pressed=keys[VK_SHIFT]&0x80; + control_pressed=keys[VK_CONTROL]&0x80; + } + } + + /* process events when the threshold is reached */ + while (abs(wheel_accumulator) >= WHEEL_DELTA) { + int b; + + /* reduce amount for next time */ + if (wheel_accumulator > 0) { + b = MBT_WHEEL_UP; + wheel_accumulator -= WHEEL_DELTA; + } else if (wheel_accumulator < 0) { + b = MBT_WHEEL_DOWN; + wheel_accumulator += WHEEL_DELTA; + } else + break; + + if (send_raw_mouse && + !(cfg.mouse_override && shift_pressed)) { + /* send a mouse-down followed by a mouse up */ + term_mouse(b, + MA_CLICK, + TO_CHR_X(X_POS(lParam)), + TO_CHR_Y(Y_POS(lParam)), shift_pressed, + control_pressed, is_alt_pressed()); + term_mouse(b, MA_RELEASE, TO_CHR_X(X_POS(lParam)), + TO_CHR_Y(Y_POS(lParam)), shift_pressed, + control_pressed, is_alt_pressed()); + } else { + /* trigger a scroll */ + term_scroll(0, + b == MBT_WHEEL_UP ? -rows / 2 : rows / 2); + } + } + return 0; + } } return DefWindowProc(hwnd, message, wParam, lParam); @@ -2455,13 +2571,35 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, */ void sys_cursor(int x, int y) { + int cx, cy; + + if (!has_focus) return; + + /* + * Avoid gratuitously re-updating the cursor position and IMM + * window if there's no actual change required. + */ + cx = x * font_width + offset_width; + cy = y * font_height + offset_height; + if (cx == caret_x && cy == caret_y) + return; + caret_x = cx; + caret_y = cy; + + sys_cursor_update(); +} + +static void sys_cursor_update(void) +{ COMPOSITIONFORM cf; HIMC hIMC; if (!has_focus) return; - - SetCaretPos(x * font_width + offset_width, - y * font_height + offset_height); + + if (caret_x < 0 || caret_y < 0) + return; + + SetCaretPos(caret_x, caret_y); /* IMM calls on Win98 and beyond only */ if(osVersion.dwPlatformId == VER_PLATFORM_WIN32s) return; /* 3.11 */ @@ -2472,8 +2610,8 @@ void sys_cursor(int x, int y) /* we should have the IMM functions */ hIMC = ImmGetContext(hwnd); cf.dwStyle = CFS_POINT; - cf.ptCurrentPos.x = x * font_width + offset_width; - cf.ptCurrentPos.y = y * font_height + offset_height; + cf.ptCurrentPos.x = caret_x; + cf.ptCurrentPos.y = caret_y; ImmSetCompositionWindow(hIMC, &cf); ImmReleaseContext(hwnd, hIMC); @@ -4060,7 +4198,23 @@ void fatalbox(char *fmt, ...) vsprintf(stuff, fmt, ap); va_end(ap); MessageBox(hwnd, stuff, "PuTTY Fatal Error", MB_ICONERROR | MB_OK); - exit(1); + cleanup_exit(1); +} + +/* + * Print a modal (Really Bad) message box and perform a fatal exit. + */ +void modalfatalbox(char *fmt, ...) +{ + va_list ap; + char stuff[200]; + + va_start(ap, fmt); + vsprintf(stuff, fmt, ap); + va_end(ap); + MessageBox(hwnd, stuff, "PuTTY Fatal Error", + MB_SYSTEMMODAL | MB_ICONERROR | MB_OK); + cleanup_exit(1); } /* @@ -4159,6 +4313,11 @@ void set_iconic(int iconic) */ void move_window(int x, int y) { + if (cfg.resize_action == RESIZE_DISABLED || + cfg.resize_action == RESIZE_FONT || + IsZoomed(hwnd)) + return; + SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER); } @@ -4247,6 +4406,32 @@ int is_full_screen() return TRUE; } +/* Get the rect/size of a full screen window using the nearest available + * monitor in multimon systems; default to something sensible if only + * one monitor is present. */ +static int get_fullscreen_rect(RECT * ss) +{ +#ifdef MONITOR_DEFAULTTONEAREST + HMONITOR mon; + MONITORINFO mi; + mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + mi.cbSize = sizeof(mi); + GetMonitorInfo(mon, &mi); + + /* structure copy */ + *ss = mi.rcMonitor; + return TRUE; +#else +/* could also use code like this: + ss->left = ss->top = 0; + ss->right = GetSystemMetrics(SM_CXSCREEN); + ss->bottom = GetSystemMetrics(SM_CYSCREEN); +*/ + return GetClientRect(GetDesktopWindow(), ss); +#endif +} + + /* * Go full-screen. This should only be called when we are already * maximised. @@ -4254,10 +4439,13 @@ int is_full_screen() void make_full_screen() { DWORD style; - int x, y, w, h; + RECT ss; assert(IsZoomed(hwnd)); + if (is_full_screen()) + return; + /* Remove the window furniture. */ style = GetWindowLong(hwnd, GWL_STYLE); style &= ~(WS_CAPTION | WS_BORDER | WS_THICKFRAME); @@ -4268,24 +4456,11 @@ void make_full_screen() SetWindowLong(hwnd, GWL_STYLE, style); /* Resize ourselves to exactly cover the nearest monitor. */ -#ifdef MONITOR_DEFAULTTONEAREST - { - HMONITOR mon; - MONITORINFO mi; - mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); - mi.cbSize = sizeof(mi); - GetMonitorInfo(mon, &mi); - x = mi.rcMonitor.left; - y = mi.rcMonitor.top; - w = mi.rcMonitor.right; - h = mi.rcMonitor.bottom; - } -#else - x = y = 0; - w = GetSystemMetrics(SM_CXSCREEN); - h = GetSystemMetrics(SM_CYSCREEN); -#endif - SetWindowPos(hwnd, HWND_TOP, x, y, w, h, SWP_FRAMECHANGED); + get_fullscreen_rect(&ss); + SetWindowPos(hwnd, HWND_TOP, ss.left, ss.top, + ss.right - ss.left, + ss.bottom - ss.top, + SWP_FRAMECHANGED); /* Tick the menu item in the System menu. */ CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN,