diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst index c4e9e18f3e01c..5f3f13f024c93 100644 --- a/DOCS/man/options.rst +++ b/DOCS/man/options.rst @@ -3677,7 +3677,7 @@ Window ``--force-window-position`` Forcefully move mpv's video output window to default location whenever there is a change in video parameters, video stream or file. This used to - be the default behavior. Currently only affects X11, macvk and SDL VOs. + be the default behavior. Currently only affects Windows, X11, macvk and SDL VOs. ``--auto-window-resize=`` By default, mpv will automatically resize itself if the video's size changes diff --git a/video/out/mac/common.swift b/video/out/mac/common.swift index 77557bb5a53b2..07b9f83149ed4 100644 --- a/video/out/mac/common.swift +++ b/video/out/mac/common.swift @@ -428,7 +428,7 @@ class Common: NSObject { y1: Int32(originY + rv.size.height)) var geo: vo_win_geometry = vo_win_geometry() - vo_calc_window_geometry(vo, &screenRC, &screenRC, Double(screen.backingScaleFactor), false, &geo) + vo_calc_window_geometry(vo, vo.pointee.opts, &screenRC, &screenRC, Double(screen.backingScaleFactor), false, &geo, nil) vo_apply_window_geometry(vo, &geo) let height = CGFloat(geo.win.y1 - geo.win.y0) diff --git a/video/out/vo_sdl.c b/video/out/vo_sdl.c index 4b3794fcaeeb2..5ba12b20cbfc7 100644 --- a/video/out/vo_sdl.c +++ b/video/out/vo_sdl.c @@ -453,7 +453,7 @@ static int reconfig(struct vo *vo, struct mp_image_params *params) struct mp_rect screenrc; update_screeninfo(vo, &screenrc); - vo_calc_window_geometry(vo, &screenrc, &screenrc, 1.0, false, &geo); + vo_calc_window_geometry(vo, vc->opts_cache->opts, &screenrc, &screenrc, 1.0, false, &geo, NULL); vo_apply_window_geometry(vo, &geo); int win_w = vo->dwidth; diff --git a/video/out/w32_common.c b/video/out/w32_common.c index 247151850e75a..924fe5f81f561 100644 --- a/video/out/w32_common.c +++ b/video/out/w32_common.c @@ -15,10 +15,9 @@ * License along with mpv. If not, see . */ -#include #include +#include #include -#include #define _DECL_DLLMAIN #include @@ -118,11 +117,12 @@ struct vo_w32_state { bool window_bounds_initialized; bool current_fs; - bool toggle_fs; // whether the current fullscreen state needs to be switched + bool pending_resize; + bool pending_reset_size; + bool pending_maximize; - // Note: maximized state doesn't involve nor modify windowrc - RECT windowrc; // currently known normal/fullscreen window client rect - RECT prev_windowrc; // saved normal window client rect while in fullscreen + RECT current_rect; // client rect of the window + RECT windowed_rect; // client rect of the window, when windowed. // video size uint32_t o_dwidth; @@ -147,15 +147,6 @@ struct vo_w32_state { // UTF-16 decoding state for WM_CHAR and VK_PACKET int high_surrogate; - // Fit the window to one monitor working area next time it's not fullscreen - // and not maximized. Used once after every new "untrusted" size comes from - // mpv, else we assume that the last known size is valid and don't fit. - // FIXME: on a multi-monitor setup one bit is not enough, because the first - // fit (autofit etc) should be to one monitor, but later size changes from - // mpv like window-scale (VOCTRL_SET_UNFS_WINDOW_SIZE) should allow the - // entire virtual desktop area - but we still limit to one monitor size. - bool fit_on_screen; - bool win_force_pos; ITaskbarList2 *taskbar_list; @@ -190,7 +181,6 @@ struct vo_w32_state { BOOL win_arranging; bool conversion_mode_init; - bool unmaximize; HIMC imc; @@ -241,11 +231,7 @@ static int get_title_bar_height(struct vo_w32_state *w32) DwmGetWindowAttribute(w32->window, DWMWA_VISIBLE_FRAME_BORDER_THICKNESS, &visible_border, sizeof(visible_border)); } - int top_bar = IsMaximized(w32->window) - ? get_system_metrics(w32, SM_CYFRAME) + - get_system_metrics(w32, SM_CXPADDEDBORDER) - : visible_border; - return top_bar; + return visible_border; } static void add_window_borders(struct vo_w32_state *w32, HWND hwnd, RECT *rc) @@ -803,7 +789,7 @@ static HMONITOR get_default_monitor(struct vo_w32_state *w32) if (id < 0) { if (w32->win_force_pos && !w32->current_fs) { // Get window from forced position - return MonitorFromRect(&w32->windowrc, MONITOR_DEFAULTTOPRIMARY); + return MonitorFromRect(&w32->current_rect, MONITOR_DEFAULTTOPRIMARY); } else { // Let compositor decide return MonitorFromWindow(w32->window, MONITOR_DEFAULTTOPRIMARY); @@ -942,11 +928,11 @@ static bool is_high_contrast(void) return hc.dwFlags & HCF_HIGHCONTRASTON; } -static DWORD update_style(struct vo_w32_state *w32, DWORD style) +static LONG_PTR update_style(struct vo_w32_state *w32, LONG_PTR style) { - const DWORD NO_FRAME = WS_OVERLAPPED | WS_MINIMIZEBOX | WS_THICKFRAME; - const DWORD FRAME = WS_OVERLAPPEDWINDOW; - const DWORD FULLSCREEN = NO_FRAME & ~WS_THICKFRAME; + const LONG_PTR NO_FRAME = WS_OVERLAPPED | WS_MINIMIZEBOX | WS_THICKFRAME; + const LONG_PTR FRAME = WS_OVERLAPPEDWINDOW; + const LONG_PTR FULLSCREEN = NO_FRAME & ~WS_THICKFRAME; style &= ~(NO_FRAME | FRAME | FULLSCREEN); style |= WS_SYSMENU; if (w32->current_fs) { @@ -959,7 +945,7 @@ static DWORD update_style(struct vo_w32_state *w32, DWORD style) return style; } -static DWORD update_exstyle(struct vo_w32_state *w32, DWORD exstyle) +static LONG_PTR update_exstyle(struct vo_w32_state *w32, LONG_PTR exstyle) { exstyle &= ~(WS_EX_TOOLWINDOW); if (!w32->opts->show_in_taskbar) @@ -974,12 +960,12 @@ static void update_window_style(struct vo_w32_state *w32) // SetWindowLongPtr can trigger a WM_SIZE event, so window rect // has to be saved now and restored after setting the new style. - const RECT wr = w32->windowrc; - const DWORD style = GetWindowLongPtrW(w32->window, GWL_STYLE); - const DWORD exstyle = GetWindowLongPtrW(w32->window, GWL_EXSTYLE); - SetWindowLongPtrW(w32->window, GWL_STYLE, update_style(w32, style)); - SetWindowLongPtrW(w32->window, GWL_EXSTYLE, update_exstyle(w32, exstyle)); - w32->windowrc = wr; + const RECT wr = w32->current_rect; + const LONG_PTR style = GetWindowLongPtr(w32->window, GWL_STYLE); + const LONG_PTR exstyle = GetWindowLongPtr(w32->window, GWL_EXSTYLE); + SetWindowLongPtr(w32->window, GWL_STYLE, update_style(w32, style)); + SetWindowLongPtr(w32->window, GWL_EXSTYLE, update_exstyle(w32, exstyle)); + w32->current_rect = wr; } // Resize window rect to width = w and height = h. If window is snapped, @@ -1015,8 +1001,8 @@ static bool fit_rect_size(struct vo_w32_state *w32, RECT *rc, long n_w, long n_h return false; // Apply letterboxing - const float o_asp = o_w / (float)MPMAX(o_h, 1); - const float n_asp = n_w / (float)MPMAX(n_h, 1); + const double o_asp = o_w / (double)MPMAX(o_h, 1); + const double n_asp = n_w / (double)MPMAX(n_h, 1); if (o_asp > n_asp) { n_h = n_w / o_asp; } else { @@ -1030,7 +1016,7 @@ static bool fit_rect_size(struct vo_w32_state *w32, RECT *rc, long n_w, long n_h // If the window is bigger than the desktop, shrink to fit with same center. // Also, if the top edge is above the working area, move down to align. -static void fit_window_on_screen(struct vo_w32_state *w32) +static void fit_rect_on_screen(struct vo_w32_state *w32, RECT *rc) { RECT screen = get_working_area(w32); if (w32->opts->border) @@ -1040,125 +1026,120 @@ static void fit_window_on_screen(struct vo_w32_state *w32) if (GetWindowRect(w32->window, &window_rect)) adjust_working_area_for_extended_frame(&screen, &window_rect, w32->window); - bool adjusted = fit_rect_size(w32, &w32->windowrc, rect_w(screen), rect_h(screen)); + bool adjusted = fit_rect_size(w32, rc, rect_w(screen), rect_h(screen)); - if (w32->windowrc.top < screen.top) { + if (rc->top < screen.top) { // if the top-edge of client area is above the target area (mainly // because the client-area is centered but the title bar is taller // than the bottom border), then move it down to align the edges. // Windows itself applies the same constraint during manual move. - w32->windowrc.bottom += screen.top - w32->windowrc.top; - w32->windowrc.top = screen.top; + rc->bottom += screen.top - rc->top; + rc->top = screen.top; adjusted = true; } if (adjusted) { MP_VERBOSE(w32, "adjusted window bounds: %d:%d:%d:%d\n", - (int)w32->windowrc.left, (int)w32->windowrc.top, - (int)rect_w(w32->windowrc), (int)rect_h(w32->windowrc)); + (int)rc->left, (int)rc->top, + (int)rect_w(*rc), (int)rect_h(*rc)); } } -// Calculate new fullscreen state and change window size and position. -static void update_fullscreen_state(struct vo_w32_state *w32) +static void update_minimized_state(struct vo_w32_state *w32) { if (w32->parent) return; - bool new_fs = w32->opts->fullscreen; - if (w32->toggle_fs) { - new_fs = !w32->current_fs; - w32->toggle_fs = false; - } - - bool toggle_fs = w32->current_fs != new_fs; - w32->opts->fullscreen = w32->current_fs = new_fs; - m_config_cache_write_opt(w32->opts_cache, - &w32->opts->fullscreen); - - if (toggle_fs && (!w32->opts->window_maximized || w32->unmaximize)) { - if (w32->current_fs) { - // Save window rect when switching to fullscreen. - w32->prev_windowrc = w32->windowrc; - MP_VERBOSE(w32, "save window bounds: %d:%d:%d:%d\n", - (int)w32->windowrc.left, (int)w32->windowrc.top, - (int)rect_w(w32->windowrc), (int)rect_h(w32->windowrc)); - } else { - // Restore window rect when switching from fullscreen. - w32->windowrc = w32->prev_windowrc; - } - } - - if (w32->current_fs) - w32->windowrc = get_screen_area(w32); - - MP_VERBOSE(w32, "reset window bounds: %d:%d:%d:%d\n", - (int)w32->windowrc.left, (int)w32->windowrc.top, - (int)rect_w(w32->windowrc), (int)rect_h(w32->windowrc)); + ShowWindow(w32->window, w32->opts->window_minimized ? SW_SHOWMINNOACTIVE : SW_RESTORE); } -static void update_minimized_state(struct vo_w32_state *w32) +static void update_maximized_state(struct vo_w32_state *w32) { if (w32->parent) return; - if (!!IsMinimized(w32->window) != w32->opts->window_minimized) { - if (w32->opts->window_minimized) { - ShowWindow(w32->window, SW_SHOWMINNOACTIVE); + if (IsMinimized(w32->window)) { + WINDOWPLACEMENT wp = { .length = sizeof(wp) }; + GetWindowPlacement(w32->window, &wp); + if (w32->opts->window_maximized && !w32->current_fs) { + wp.flags |= WPF_RESTORETOMAXIMIZED; } else { - ShowWindow(w32->window, SW_RESTORE); + wp.flags &= ~WPF_RESTORETOMAXIMIZED; } + SetWindowPlacement(w32->window, &wp); + w32->pending_maximize = w32->opts->window_maximized && w32->current_fs; + return; + } + + if (w32->current_fs) { + w32->pending_maximize = w32->opts->window_maximized; + return; } + + ShowWindow(w32->window, w32->opts->window_maximized ? SW_MAXIMIZE : SW_RESTORE); } -static void update_window_state(struct vo_w32_state *w32); +static void window_resize(struct vo_w32_state *w32); +static void window_set_pos(struct vo_w32_state *w32, RECT client_rect); -static void update_maximized_state(struct vo_w32_state *w32, bool leaving_fullscreen) +static void update_fullscreen_state(struct vo_w32_state *w32) { if (w32->parent) return; - // Apply the maximized state on leaving fullscreen. - if (w32->current_fs && !leaving_fullscreen) + if (IsMinimized(w32->window)) { + w32->pending_resize = true; return; + } - bool toggle = w32->opts->window_maximized ^ IsMaximized(w32->window); + if (w32->current_fs == w32->opts->fullscreen) + return; - if (toggle && !w32->current_fs && w32->opts->window_maximized) - w32->prev_windowrc = w32->windowrc; + w32->current_fs = w32->opts->fullscreen; + update_window_style(w32); - WINDOWPLACEMENT wp = { .length = sizeof wp }; + WINDOWPLACEMENT wp = { .length = sizeof(wp) }; GetWindowPlacement(w32->window, &wp); + if (!w32->current_fs) { + wp.showCmd = w32->pending_maximize ? SW_SHOWMAXIMIZED : SW_SHOWNORMAL; + + if (w32->pending_maximize || IsMaximized(w32->window)) { + w32->pending_maximize = false; + + // We have to do this in two steps because Windows adds transition + // animations when maximizing, which start from rcNormalPosition + // to the maximized rectangle. To get a valid transition from fullscreen + // to maximized, we first enter the maximized state and then set the + // restore size. + SetWindowPlacement(w32->window, &wp); + wp.rcNormalPosition = w32->windowed_rect; + add_window_borders(w32, w32->window, &wp.rcNormalPosition); + SetWindowPlacement(w32->window, &wp); + + // This is required after updating the window style. And also for us to + // signal new window size to the VO. + SetWindowPos(w32->window, 0, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); + goto done; - if (wp.showCmd == SW_SHOWMINIMIZED) { - // When the window is minimized, setting this property just changes - // whether it will be maximized when it's restored - if (w32->opts->window_maximized) { - wp.flags |= WPF_RESTORETOMAXIMIZED; - } else { - wp.flags &= ~WPF_RESTORETOMAXIMIZED; } + } else if (IsMaximized(w32->window)) { + wp.showCmd = SW_SHOWNORMAL; SetWindowPlacement(w32->window, &wp); - } else if ((wp.showCmd == SW_SHOWMAXIMIZED) != w32->opts->window_maximized) { - if (w32->opts->window_maximized) { - ShowWindow(w32->window, SW_SHOWMAXIMIZED); - } else { - ShowWindow(w32->window, SW_SHOWNOACTIVATE); - } + w32->pending_maximize = true; } - update_window_style(w32); + RECT r = w32->current_fs ? get_screen_area(w32) : w32->windowed_rect; + window_set_pos(w32, r); - if (toggle && !w32->current_fs && !w32->opts->window_maximized) { - w32->windowrc = w32->prev_windowrc; - update_window_state(w32); - } +done: + signal_events(w32, VO_EVENT_RESIZE); } static bool is_visible(HWND window) { // Unlike IsWindowVisible, this doesn't check the window's parents - return GetWindowLongPtrW(window, GWL_STYLE) & WS_VISIBLE; + return GetWindowLongPtr(window, GWL_STYLE) & WS_VISIBLE; } //Set the mpv window's affinity. @@ -1171,116 +1152,16 @@ static void update_affinity(struct vo_w32_state *w32) SetWindowDisplayAffinity(w32->window, w32->opts->window_affinity); } -static void update_window_state(struct vo_w32_state *w32) +static void update_corners_pref(const struct vo_w32_state *w32) { if (w32->parent) return; - RECT wr = w32->windowrc; - add_window_borders(w32, w32->window, &wr); - - SetWindowPos(w32->window, w32->opts->ontop ? HWND_TOPMOST : HWND_NOTOPMOST, - wr.left, wr.top, rect_w(wr), rect_h(wr), - SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOOWNERZORDER); - - // Unmaximize the window if a size change is requested because SetWindowPos - // doesn't change the window maximized state. - // ShowWindow(SW_SHOWNOACTIVATE) can't be used here because it tries to - // "restore" the window to its size before it's maximized. - if (w32->unmaximize && !w32->current_fs) { - WINDOWPLACEMENT wp = { .length = sizeof wp }; - GetWindowPlacement(w32->window, &wp); - wp.showCmd = SW_SHOWNOACTIVATE; - wp.rcNormalPosition = wr; - SetWindowPlacement(w32->window, &wp); - w32->unmaximize = false; - } - - // Show the window if it's not yet visible - if (!is_visible(w32->window)) { - if (w32->opts->window_minimized) { - ShowWindow(w32->window, SW_SHOWMINNOACTIVE); - update_maximized_state(w32, false); // Set the WPF_RESTORETOMAXIMIZED flag - } else if (w32->opts->window_maximized) { - ShowWindow(w32->window, SW_SHOWMAXIMIZED); - } else { - ShowWindow(w32->window, SW_SHOW); - } - } - - // Notify the taskbar about the fullscreen state only after the window - // is visible, to make sure the taskbar item has already been created - if (w32->taskbar_list) { - ITaskbarList2_MarkFullscreenWindow(w32->taskbar_list, - w32->window, w32->current_fs); - } - - // Update snapping status if needed - if (w32->opts->snap_window && !w32->parent && - !w32->current_fs && !IsMaximized(w32->window)) { - RECT wa = get_working_area(w32); - - adjust_working_area_for_extended_frame(&wa, &wr, w32->window); - - // snapped_left & snapped_right are mutually exclusive - if (wa.left == wr.left && wa.right == wr.right) { - // Leave as is. - } else if (wa.left == wr.left) { - w32->snapped_left = 1; - w32->snapped_right = 0; - } else if (wa.right == wr.right) { - w32->snapped_right = 1; - w32->snapped_left = 0; - } else { - w32->snapped_left = w32->snapped_right = 0; - } - - // snapped_top & snapped_bottom are mutually exclusive - if (wa.top == wr.top && wa.bottom == wr.bottom) { - // Leave as is. - } else if (wa.top == wr.top) { - w32->snapped_top = 1; - w32->snapped_bottom = 0; - } else if (wa.bottom == wr.bottom) { - w32->snapped_bottom = 1; - w32->snapped_top = 0; - } else { - w32->snapped_top = w32->snapped_bottom = 0; - } - } - - signal_events(w32, VO_EVENT_RESIZE); -} - -static void update_corners_pref(const struct vo_w32_state *w32) { - if (w32->parent) - return; - int pref = w32->current_fs ? 0 : w32->opts->window_corners; DwmSetWindowAttribute(w32->window, DWMWA_WINDOW_CORNER_PREFERENCE, &pref, sizeof(pref)); } -static void reinit_window_state(struct vo_w32_state *w32) -{ - if (w32->parent) - return; - - // The order matters: fs state should be updated prior to changing styles - update_fullscreen_state(w32); - update_corners_pref(w32); - update_window_style(w32); - - // fit_on_screen is applied at most once when/if applicable (normal win). - if (w32->fit_on_screen && !w32->current_fs && !IsMaximized(w32->window)) { - fit_window_on_screen(w32); - w32->fit_on_screen = false; - } - - // Show and activate the window after all window state parameters were set - update_window_state(w32); -} - // Follow Windows settings and update dark mode state // Microsoft documented how to enable dark mode for title bar: // https://learn.microsoft.com/windows/apps/desktop/modernize/apply-windows-themes @@ -1419,8 +1300,11 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, case WM_MOVE: { w32->moving = false; const int x = GET_X_LPARAM(lParam), y = GET_Y_LPARAM(lParam); - OffsetRect(&w32->windowrc, x - w32->windowrc.left, - y - w32->windowrc.top); + OffsetRect(&w32->current_rect, x - w32->current_rect.left, + y - w32->current_rect.top); + + if (!w32->current_fs && !IsMaximized(w32->window) && !IsMinimized(w32->window)) + w32->windowed_rect = w32->current_rect; // Window may intersect with new monitors (see VOCTRL_GET_DISPLAY_NAMES) signal_events(w32, VO_EVENT_WIN_STATE); @@ -1440,7 +1324,7 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, if (w32->win_arranging) { SystemParametersInfoW(SPI_SETWINARRANGING, FALSE, 0, 0); } - *rc = w32->windowrc; + *rc = w32->current_rect; return TRUE; } if (snap_to_screen_edges(w32, rc)) @@ -1465,44 +1349,58 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, break; case WM_SIZE: { const int w = LOWORD(lParam), h = HIWORD(lParam); + int events = 0; if (w > 0 && h > 0) { - w32->windowrc.right = w32->windowrc.left + w; - w32->windowrc.bottom = w32->windowrc.top + h; - signal_events(w32, VO_EVENT_RESIZE); - MP_VERBOSE(w32, "resize window: %d:%d\n", w, h); + w32->current_rect.right = w32->current_rect.left + w; + w32->current_rect.bottom = w32->current_rect.top + h; + if (!w32->current_fs && !IsMaximized(w32->window) && !IsMinimized(w32->window)) + w32->windowed_rect = w32->current_rect; + events |= VO_EVENT_RESIZE; + MP_TRACE(w32, "resize window: %d:%d\n", w, h); } - // Window may have been minimized, maximized or restored if (is_visible(w32->window)) { - WINDOWPLACEMENT wp = { .length = sizeof wp }; - GetWindowPlacement(w32->window, &wp); - - bool is_minimized = wp.showCmd == SW_SHOWMINIMIZED; - if (w32->opts->window_minimized != is_minimized) { - w32->opts->window_minimized = is_minimized; - m_config_cache_write_opt(w32->opts_cache, - &w32->opts->window_minimized); + switch (wParam) { + case SIZE_MAXIMIZED: + w32->opts->window_maximized = true; + break; + case SIZE_MINIMIZED: + w32->opts->window_minimized = true; + WINDOWPLACEMENT wp = { .length = sizeof(wp) }; + GetWindowPlacement(w32->window, &wp); + if (w32->opts->window_maximized && !w32->current_fs) { + wp.flags |= WPF_RESTORETOMAXIMIZED; + } else { + wp.flags &= ~WPF_RESTORETOMAXIMIZED; + } + SetWindowPlacement(w32->window, &wp); + break; + case SIZE_RESTORED: + w32->opts->window_maximized = w32->pending_maximize && w32->current_fs; + w32->opts->window_minimized = false; + break; } - bool is_maximized = wp.showCmd == SW_SHOWMAXIMIZED || - (wp.showCmd == SW_SHOWMINIMIZED && - (wp.flags & WPF_RESTORETOMAXIMIZED)); - if (w32->opts->window_maximized != is_maximized) { - w32->opts->window_maximized = is_maximized; - update_window_style(w32); - m_config_cache_write_opt(w32->opts_cache, - &w32->opts->window_maximized); + if (w32->taskbar_list) { + ITaskbarList2_MarkFullscreenWindow(w32->taskbar_list, w32->window, + w32->current_fs && !IsMinimized(w32->window)); } } - signal_events(w32, VO_EVENT_WIN_STATE); + bool updated = (m_config_cache_write_opt(w32->opts_cache, &w32->opts->window_maximized) || + m_config_cache_write_opt(w32->opts_cache, &w32->opts->window_minimized)); + if (updated || w32->pending_resize) { + window_resize(w32); + events |= VO_EVENT_WIN_STATE; + } + signal_events(w32, events); update_display_info(w32); break; } case WM_SIZING: if (w32->opts->keepaspect && w32->opts->keepaspect_window && - !w32->current_fs && !w32->parent) + !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 @@ -1510,9 +1408,9 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, RECT r = *rc; subtract_window_borders(w32, w32->window, &r); int c_w = rect_w(r), c_h = rect_h(r); - float aspect = w32->o_dwidth / (float) MPMAX(w32->o_dheight, 1); - int d_w = c_h * aspect - c_w; - int d_h = c_w / aspect - c_h; + 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); @@ -1526,9 +1424,9 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, update_display_info(w32); RECT *rc = (RECT*)lParam; - w32->windowrc = *rc; - subtract_window_borders(w32, w32->window, &w32->windowrc); - update_window_state(w32); + w32->current_rect = *rc; + subtract_window_borders(w32, w32->window, &w32->current_rect); + window_resize(w32); break; case WM_CLOSE: // Don't destroy the window yet to not lose wakeup events. @@ -1564,14 +1462,6 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, return 0; } break; - case SC_RESTORE: - if (IsMaximized(w32->window) && w32->current_fs) { - w32->toggle_fs = true; - reinit_window_state(w32); - - return 0; - } - break; } // All custom items must use ids of less than 0xF000. The context menu items are // also larger than WM_USER, which excludes SCF_ISSECURE. @@ -1724,7 +1614,7 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, case WM_SETTINGCHANGE: update_dark_mode(w32); update_window_style(w32); - update_window_state(w32); + window_resize(w32); break; case WM_NCCALCSIZE: if (!w32->opts->border && !IsMaximized(w32->window)) @@ -1941,18 +1831,31 @@ static void run_message_loop(struct vo_w32_state *w32) } } -static void window_reconfig(struct vo_w32_state *w32, bool force) +static void window_set_pos(struct vo_w32_state *w32, RECT client_rect) +{ + if (w32->parent) + return; + + add_window_borders(w32, w32->window, &client_rect); + SetWindowPos(w32->window, w32->opts->ontop ? HWND_TOPMOST : HWND_NOTOPMOST, + client_rect.left, client_rect.top, rect_w(client_rect), rect_h(client_rect), + SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOOWNERZORDER); + MP_VERBOSE(w32, "Window size changed to: %ld:%ld:%ld:%ld\n", + client_rect.left, client_rect.top, rect_w(client_rect), rect_h(client_rect)); +} + +static void window_resize(struct vo_w32_state *w32) { struct vo *vo = w32->vo; RECT r = get_working_area(w32); - // for normal window which is auto-positioned (centered), center the window - // rather than the content (by subtracting the borders from the work area) - if (!w32->current_fs && !IsMaximized(w32->window) && w32->opts->border && - !w32->opts->geometry.xy_valid /* specific position not requested */) - { + if (!w32->current_fs && !IsMaximized(w32->window) && w32->opts->border) subtract_window_borders(w32, w32->window, &r); - } + + RECT window_rect; + if (GetWindowRect(w32->window, &window_rect)) + adjust_working_area_for_extended_frame(&r, &window_rect, w32->window); + struct mp_rect screen = { r.left, r.top, r.right, r.bottom }; struct vo_win_geometry geo; @@ -1962,56 +1865,76 @@ static void window_reconfig(struct vo_w32_state *w32, bool force) if (w32->dpi_scale == 0) force_update_display_info(w32); - vo_calc_window_geometry(vo, &screen, &mon, w32->dpi_scale, - !w32->window_bounds_initialized, &geo); + struct m_geometry size_constraint = { + .w = 100, + .h = 100, + .w_per = true, + .h_per = true, + .wh_valid = true, + }; + + vo_calc_window_geometry(vo, w32->opts, &screen, &mon, w32->dpi_scale, + !w32->window_bounds_initialized, &geo, &size_constraint); vo_apply_window_geometry(vo, &geo); - bool reset_size = ((w32->o_dwidth != vo->dwidth || - w32->o_dheight != vo->dheight) && - w32->opts->auto_window_resize) || force; + w32->pending_reset_size |= w32->opts->auto_window_resize && + (w32->o_dwidth != vo->dwidth || + w32->o_dheight != vo->dheight); + + if (w32->parent) { + GetClientRect(w32->window, &r); + vo->dwidth = r.right; + vo->dheight = r.bottom; + goto finish; + } w32->o_dwidth = vo->dwidth; w32->o_dheight = vo->dheight; - if (!w32->parent && (!w32->window_bounds_initialized || force)) { - int x0 = geo.win.x0; - int y0 = geo.win.y0; - if (!w32->opts->geometry.xy_valid && w32->window_bounds_initialized) { - x0 = w32->windowrc.left; - y0 = w32->windowrc.top; - } - SetRect(&w32->windowrc, x0, y0, x0 + vo->dwidth, y0 + vo->dheight); - w32->prev_windowrc = w32->windowrc; - w32->window_bounds_initialized = true; - w32->win_force_pos = geo.flags & VO_WIN_FORCE_POS; - w32->fit_on_screen = true; - goto finish; + RECT client_rect; + SetRect(&client_rect, geo.win.x0, geo.win.y0, geo.win.x1, geo.win.y1); + + w32->win_force_pos = geo.flags & VO_WIN_FORCE_POS; + if (!w32->win_force_pos) { + int new_w = rect_w(client_rect), new_h = rect_h(client_rect); + client_rect = w32->windowed_rect; + resize_and_move_rect(w32, &client_rect, new_w, new_h); + fit_rect_on_screen(w32, &client_rect); } - // The rect which size is going to be modified. - RECT *rc = &w32->windowrc; + w32->pending_resize = w32->opts->window_minimized || + w32->opts->window_maximized || w32->current_fs; + if (w32->pending_resize && w32->window_bounds_initialized) + goto set_pos_done; - // The desired size always matches the window size in wid mode. - if (!reset_size || w32->parent) { - GetClientRect(w32->window, &r); - // Restore vo_dwidth and vo_dheight, which were reset in vo_config() - vo->dwidth = r.right; - vo->dheight = r.bottom; - } else { - if (w32->current_fs || w32->opts->window_maximized) - rc = &w32->prev_windowrc; - w32->fit_on_screen = true; - } + if (w32->window_bounds_initialized && !w32->pending_reset_size) + client_rect = w32->current_rect; + + window_set_pos(w32, client_rect); + w32->pending_reset_size = false; + +set_pos_done: + w32->window_bounds_initialized = true; + + update_fullscreen_state(w32); - resize_and_move_rect(w32, rc, vo->dwidth, vo->dheight); + if (!is_visible(w32->window)) { + if (w32->opts->window_minimized) { + ShowWindow(w32->window, SW_SHOWMINNOACTIVE); + } else if (w32->opts->window_maximized) { + ShowWindow(w32->window, SW_SHOWMAXIMIZED); + } else { + ShowWindow(w32->window, SW_SHOW); + } + } finish: - reinit_window_state(w32); + signal_events(w32, VO_EVENT_RESIZE); } static void gui_thread_reconfig(void *ptr) { - window_reconfig(ptr, false); + window_resize(ptr); } // Resize the window. On the first call, it's also made visible. @@ -2325,13 +2248,14 @@ static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg) struct mp_vo_opts *vo_opts = w32->opts_cache->opts; if (changed_option == &vo_opts->fullscreen) { - if (!vo_opts->fullscreen) - update_maximized_state(w32, true); - reinit_window_state(w32); + update_fullscreen_state(w32); } else if (changed_option == &vo_opts->window_affinity) { update_affinity(w32); } else if (changed_option == &vo_opts->ontop) { - update_window_state(w32); + if (!w32->parent) { + SetWindowPos(w32->window, w32->opts->ontop ? HWND_TOPMOST : HWND_NOTOPMOST, + 0, 0, 0, 0, SWP_NOMOVE | SWP_NOREDRAW | SWP_NOSIZE); + } } else if (changed_option == &vo_opts->backdrop_type) { update_backdrop(w32); } else if (changed_option == &vo_opts->cursor_passthrough) { @@ -2340,18 +2264,19 @@ static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg) changed_option == &vo_opts->title_bar) { update_window_style(w32); - update_window_state(w32); + window_resize(w32); } else if (changed_option == &vo_opts->show_in_taskbar) { // This hide and show is apparently required according to the documentation: // https://learn.microsoft.com/en-us/windows/win32/shell/taskbar#managing-taskbar-buttons ShowWindow(w32->window, SW_HIDE); update_window_style(w32); ShowWindow(w32->window, SW_SHOW); - update_window_state(w32); + window_resize(w32); } else if (changed_option == &vo_opts->window_minimized) { update_minimized_state(w32); } else if (changed_option == &vo_opts->window_maximized) { - update_maximized_state(w32, false); + mp_require(w32->window_bounds_initialized); + update_maximized_state(w32); } else if (changed_option == &vo_opts->window_corners) { update_corners_pref(w32); } else if (changed_option == &vo_opts->native_touch) { @@ -2361,10 +2286,22 @@ static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg) } else if (changed_option == &vo_opts->geometry || changed_option == &vo_opts->autofit || changed_option == &vo_opts->autofit_smaller || changed_option == &vo_opts->autofit_larger) { - if (w32->opts->window_maximized) { - w32->unmaximize = true; + if (w32->parent) + return VO_FALSE; + + if (w32->current_fs) { + w32->pending_resize = true; + w32->pending_maximize = false; + // Immediately notify that we will not restore maximized state. + w32->opts->window_maximized = false; + m_config_cache_write_opt(w32->opts_cache, &w32->opts->window_maximized); + signal_events(w32, VO_EVENT_WIN_STATE); + } else if (IsMaximized(w32->window)) { + ShowWindow(w32->window, SW_RESTORE); + } else { + w32->pending_reset_size = true; + window_resize(w32); } - window_reconfig(w32, true); } } @@ -2386,26 +2323,33 @@ static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg) if (!w32->window_bounds_initialized) return VO_FALSE; - RECT *rc = (w32->current_fs || w32->opts->window_maximized) - ? &w32->prev_windowrc : &w32->windowrc; - s[0] = rect_w(*rc); - s[1] = rect_h(*rc); + s[0] = rect_w(w32->windowed_rect); + s[1] = rect_h(w32->windowed_rect); return VO_TRUE; } case VOCTRL_SET_UNFS_WINDOW_SIZE: { int *s = arg; - if (!w32->window_bounds_initialized) - return VO_FALSE; + resize_and_move_rect(w32, &w32->windowed_rect, s[0], s[1]); + fit_rect_on_screen(w32, &w32->windowed_rect); - RECT *rc = w32->current_fs ? &w32->prev_windowrc : &w32->windowrc; - resize_and_move_rect(w32, rc, s[0], s[1]); + if (w32->current_fs) + return VO_TRUE; - if (w32->opts->window_maximized && !w32->current_fs) { - w32->unmaximize = true; + if (IsMaximized(w32->window) || IsMinimized(w32->window)) { + WINDOWPLACEMENT wp = { .length = sizeof(wp) }; + GetWindowPlacement(w32->window, &wp); + wp.showCmd = SW_RESTORE; + // We need to restore the window first, to get the correct border size. + SetWindowPlacement(w32->window, &wp); + wp.rcNormalPosition = w32->windowed_rect; + add_window_borders(w32, w32->window, &wp.rcNormalPosition); + SetWindowPlacement(w32->window, &wp); + return VO_TRUE; } - w32->fit_on_screen = true; - reinit_window_state(w32); + + window_set_pos(w32, w32->windowed_rect); + return VO_TRUE; } case VOCTRL_SET_CURSOR_VISIBILITY: @@ -2493,8 +2437,8 @@ static void do_control(void *ptr) *events |= atomic_fetch_and(&w32->event_flags, 0); // Safe access, since caller (owner of vo) is blocked. if (*events & VO_EVENT_RESIZE) { - w32->vo->dwidth = rect_w(w32->windowrc); - w32->vo->dheight = rect_h(w32->windowrc); + w32->vo->dwidth = rect_w(w32->current_rect); + w32->vo->dheight = rect_h(w32->current_rect); } } @@ -2505,8 +2449,8 @@ int vo_w32_control(struct vo *vo, int *events, int request, void *arg) *events |= atomic_fetch_and(&w32->event_flags, 0); if (*events & VO_EVENT_RESIZE) { mp_dispatch_lock(w32->dispatch); - vo->dwidth = rect_w(w32->windowrc); - vo->dheight = rect_h(w32->windowrc); + vo->dwidth = rect_w(w32->current_rect); + vo->dheight = rect_h(w32->current_rect); mp_dispatch_unlock(w32->dispatch); } return VO_TRUE; diff --git a/video/out/wayland_common.c b/video/out/wayland_common.c index 21011edb852d6..719dc5279d3bd 100644 --- a/video/out/wayland_common.c +++ b/video/out/wayland_common.c @@ -3704,7 +3704,7 @@ static void set_geometry(struct vo_wayland_state *wl, bool resize) struct vo_win_geometry geo; struct mp_rect screenrc = wl->current_output->geometry; - vo_calc_window_geometry(vo, &screenrc, &screenrc, wl->scaling_factor, false, &geo); + vo_calc_window_geometry(vo, wl->opts, &screenrc, &screenrc, wl->scaling_factor, false, &geo, NULL); vo_apply_window_geometry(vo, &geo); int gcd = greatest_common_divisor(vo->dwidth, vo->dheight); diff --git a/video/out/win_state.c b/video/out/win_state.c index 05e5116e7e176..3ca22fecf39a2 100644 --- a/video/out/win_state.c +++ b/video/out/win_state.c @@ -77,17 +77,20 @@ static void apply_autofit(int *w, int *h, int scr_w, int scr_h, // force_center: force centering x/y in the middle of the given screen even if // opts->force_window_position is not set. ignored if the user // supplies valid x/y coordinates on their own. +// size_constraint: if non-NULL, the resulting size will be limited by the size +// specified in this geometry. This applies to both autofit +// and geometry options. // Use vo_apply_window_geometry() to copy the result into the vo. // NOTE: currently, all windowing backends do their own handling of window // geometry additional to this code. This is to deal with initial window // placement, fullscreen handling, avoiding resize on reconfig() with no // size change, multi-monitor stuff, and possibly more. -void vo_calc_window_geometry(struct vo *vo, const struct mp_rect *screen, +void vo_calc_window_geometry(struct vo *vo, struct mp_vo_opts *opts, + const struct mp_rect *screen, const struct mp_rect *monitor, double dpi_scale, - bool force_center, struct vo_win_geometry *out_geo) + bool force_center, struct vo_win_geometry *out_geo, + struct m_geometry *size_constraint) { - struct mp_vo_opts *opts = vo->opts; - *out_geo = (struct vo_win_geometry){0}; // The case of calling this function even though no video was configured @@ -121,6 +124,8 @@ void vo_calc_window_geometry(struct vo *vo, const struct mp_rect *screen, apply_autofit(&d_w, &d_h, scr_w, scr_h, &opts->autofit, true, true); apply_autofit(&d_w, &d_h, scr_w, scr_h, &opts->autofit_smaller, true, false); apply_autofit(&d_w, &d_h, scr_w, scr_h, &opts->autofit_larger, false, true); + if (size_constraint) + apply_autofit(&d_w, &d_h, scr_w, scr_h, size_constraint, false, true); out_geo->win.x0 = (int)(scr_w - d_w) / 2; out_geo->win.y0 = (int)(scr_h - d_h) / 2; @@ -129,6 +134,15 @@ void vo_calc_window_geometry(struct vo *vo, const struct mp_rect *screen, m_geometry_apply(&out_geo->win.x0, &out_geo->win.y0, &d_w, &d_h, scr_w, scr_h, center, &opts->geometry); + if (size_constraint) { + int old_w = d_w, old_h = d_h; + apply_autofit(&d_w, &d_h, scr_w, scr_h, size_constraint, false, true); + if (!opts->geometry.xy_valid) { + out_geo->win.x0 += (old_w - d_w) / 2; + out_geo->win.y0 += (old_h - d_h) / 2; + } + } + out_geo->win.x0 += screen->x0; out_geo->win.y0 += screen->y0; out_geo->win.x1 = out_geo->win.x0 + d_w; diff --git a/video/out/win_state.h b/video/out/win_state.h index f19a15618bae7..59dc98e08c480 100644 --- a/video/out/win_state.h +++ b/video/out/win_state.h @@ -23,9 +23,14 @@ struct vo_win_geometry { double monitor_par; }; -void vo_calc_window_geometry(struct vo *vo, const struct mp_rect *screen, +struct mp_vo_opts; +struct m_geometry; + +void vo_calc_window_geometry(struct vo *vo, struct mp_vo_opts *opts, + const struct mp_rect *screen, const struct mp_rect *monitor, double dpi_scale, - bool force_center, struct vo_win_geometry *out_geo); + bool force_center, struct vo_win_geometry *out_geo, + struct m_geometry *size_constraint); void vo_apply_window_geometry(struct vo *vo, const struct vo_win_geometry *geo); #endif diff --git a/video/out/x11_common.c b/video/out/x11_common.c index 7099199e3a742..d03cc20b1db80 100644 --- a/video/out/x11_common.c +++ b/video/out/x11_common.c @@ -1805,8 +1805,8 @@ void vo_x11_config_vo_window(struct vo *vo) vo_x11_update_screeninfo(vo); struct vo_win_geometry geo; - vo_calc_window_geometry(vo, &x11->screenrc, &x11->screenrc, x11->dpi_scale, - !x11->pseudo_mapped, &geo); + vo_calc_window_geometry(vo, opts, &x11->screenrc, &x11->screenrc, x11->dpi_scale, + !x11->pseudo_mapped, &geo, NULL); vo_apply_window_geometry(vo, &geo); struct mp_rect rc = !x11->pseudo_mapped || opts->auto_window_resize || opts->geometry.wh_valid ||