From 6fff8d79befe42adfd584d360e59403337da4d55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= Date: Tue, 17 Mar 2026 20:47:47 +0100 Subject: [PATCH] w32_common: fix handling of WM_SIZING Fixes some corner cases when resizing window with keep aspect ratio enabled. Limit correctly window to working area, also after adjusting aspect ratio to avoid unexpected resizes and window not staying in position. Use integer math to have consistent rounding, to avoid window "drifting". Fixes: #8728 --- video/out/w32_common.c | 107 +++++++++++++++++++++++++---------------- 1 file changed, 66 insertions(+), 41 deletions(-) diff --git a/video/out/w32_common.c b/video/out/w32_common.c index 14ebc6980122c..0dabe98d1e445 100644 --- a/video/out/w32_common.c +++ b/video/out/w32_common.c @@ -320,26 +320,6 @@ static LRESULT borderless_nchittest(struct vo_w32_state *w32, int x, int y) return HTCLIENT; } -// turn a WMSZ_* input value in v into the border that should be resized -// take into consideration which borders are snapped to avoid detaching -// returns: 0=left, 1=top, 2=right, 3=bottom, -1=undefined -static int get_resize_border(struct vo_w32_state *w32, int v) -{ - switch (v) { - case WMSZ_LEFT: - case WMSZ_RIGHT: - return w32->snapped_bottom ? 1 : 3; - case WMSZ_TOP: - case WMSZ_BOTTOM: - return w32->snapped_right ? 0 : 2; - case WMSZ_TOPLEFT: return 1; - case WMSZ_TOPRIGHT: return 1; - case WMSZ_BOTTOMLEFT: return 3; - case WMSZ_BOTTOMRIGHT: return 3; - default: return -1; - } -} - static bool key_state(int vk) { return GetKeyState(vk) & 0x8000; @@ -1264,6 +1244,68 @@ static void update_ime_enabled(struct vo_w32_state *w32, bool enable) } } +static void handle_sizing(struct vo_w32_state *w32, int edge, RECT *rc) +{ + RECT wa = get_working_area(w32); + RECT wr; + if (GetWindowRect(w32->window, &wr)) + adjust_working_area_for_extended_frame(&wa, &wr, w32->window); + + bool drag_l = edge == WMSZ_LEFT || edge == WMSZ_TOPLEFT || edge == WMSZ_BOTTOMLEFT; + bool drag_r = edge == WMSZ_RIGHT || edge == WMSZ_TOPRIGHT || edge == WMSZ_BOTTOMRIGHT; + bool drag_t = edge == WMSZ_TOP || edge == WMSZ_TOPLEFT || edge == WMSZ_TOPRIGHT; + bool drag_b = edge == WMSZ_BOTTOM || edge == WMSZ_BOTTOMLEFT || edge == WMSZ_BOTTOMRIGHT; + + // Clamp to working area + if (drag_l) + rc->left = MPMAX(rc->right - rect_w(wa), rc->left); + if (drag_r) + rc->right = MPMIN(rc->left + rect_w(wa), rc->right); + if (drag_t) + rc->top = MPMAX(rc->bottom - rect_h(wa), rc->top); + if (drag_b) + rc->bottom = MPMIN(rc->top + rect_h(wa), rc->bottom); + + // Windows added title-bar docking (drag top edge to the top of working area), + // and it seems to send WM_SIZING with edge (wParam) set to 9 when undocking + // the window by dragging. This is not documented, and not even an WMSZ_* + // value in 10.0.26100.7705 SDK. However, it does restore window size before + // docking, so we don't have to do anything here. + if (!drag_l && !drag_r && !drag_t && !drag_b) + return; + + bool keep_aspect = w32->opts->keepaspect && w32->opts->keepaspect_window; + if (!keep_aspect || !w32->o_dwidth || !w32->o_dheight) + return; + + RECT wb = {0}; + add_window_borders(w32, w32->window, &wb); + LONG max_w = rect_w(wa) - rect_w(wb), max_h = rect_h(wa) - rect_h(wb); + LONG c_w = rect_w(*rc) - rect_w(wb), c_h = rect_h(*rc) - rect_h(wb); + if ((drag_t || drag_b) && !drag_l && !drag_r) { + c_w = c_h * w32->o_dwidth / w32->o_dheight; + if (c_w > max_w) { + c_w = max_w; + c_h = c_w * w32->o_dheight / w32->o_dwidth; + } + } else { + c_h = c_w * w32->o_dheight / w32->o_dwidth; + if (c_h > max_h) { + c_h = max_h; + c_w = c_h * w32->o_dwidth / w32->o_dheight; + } + } + LONG w_w = c_w + rect_w(wb), w_h = c_h + rect_h(wb); + if (drag_l || (!drag_r && w32->snapped_right)) + rc->left = rc->right - w_w; + else + rc->right = rc->left + w_w; + if (drag_t || (!drag_b && w32->snapped_bottom)) + rc->top = rc->bottom - w_h; + else + rc->bottom = rc->top + w_h; +} + static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { @@ -1408,27 +1450,10 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, break; } case WM_SIZING: - if (w32->opts->keepaspect && w32->opts->keepaspect_window && - !w32->current_fs && !w32->parent && w32->o_dwidth && w32->o_dheight) - { - RECT *rc = (RECT*)lParam; - // get client area of the windows if it had the rect rc - // (subtracting the window borders) - RECT r = *rc; - subtract_window_borders(w32, w32->window, &r); - int c_w = rect_w(r), c_h = rect_h(r); - double aspect = w32->o_dwidth / (double)w32->o_dheight; - int d_w = roundl(c_h * aspect - c_w); - int d_h = roundl(c_w / aspect - c_h); - int d_corners[4] = { d_w, d_h, -d_w, -d_h }; - int corners[4] = { rc->left, rc->top, rc->right, rc->bottom }; - int corner = get_resize_border(w32, wParam); - if (corner >= 0) - corners[corner] -= d_corners[corner]; - *rc = (RECT) { corners[0], corners[1], corners[2], corners[3] }; - return TRUE; - } - break; + if (w32->parent || w32->current_fs) + break; + handle_sizing(w32, wParam, (RECT *)lParam); + return TRUE; case WM_DPICHANGED: update_display_info(w32);