From 16087cf6409d58606137b3c963040cd583e762e9 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Mon, 22 Dec 2025 09:04:15 +0100 Subject: [PATCH] fix: windows stretch issue --- windows/runner/mpv/mpv_container.cpp | 62 +++--------- windows/runner/mpv/mpv_container.h | 3 + windows/runner/mpv/mpv_core.cpp | 138 +++++++++++---------------- windows/runner/mpv/mpv_core.h | 5 +- 4 files changed, 78 insertions(+), 130 deletions(-) diff --git a/windows/runner/mpv/mpv_container.cpp b/windows/runner/mpv/mpv_container.cpp index ca7dcd18..2b332a08 100644 --- a/windows/runner/mpv/mpv_container.cpp +++ b/windows/runner/mpv/mpv_container.cpp @@ -1,33 +1,25 @@ #include "mpv_container.h" -#include #include -#include #include "mpv_core.h" #include "utils.h" -static void LogToFile(const char* message) { - std::ofstream log("C:\\Users\\admin\\mpv_debug.log", std::ios::app); - if (log.is_open()) { - log << message << std::endl; - log.close(); - } - OutputDebugStringA(message); - OutputDebugStringA("\n"); -} - namespace mpv { MpvContainer* MpvContainer::GetInstance() { return instance_.get(); } -HWND MpvContainer::Create() { - LogToFile("MpvContainer::Create called"); +MpvContainer::~MpvContainer() { + if (taskbar_) { + taskbar_->Release(); + taskbar_ = nullptr; + } +} +HWND MpvContainer::Create() { auto window_class = WNDCLASSEX{}; ::SecureZeroMemory(&window_class, sizeof(window_class)); window_class.cbSize = sizeof(window_class); - // Don't use CS_DROPSHADOW, and avoid redraw styles that might cause issues window_class.style = 0; window_class.lpfnWndProc = WindowProc; window_class.hInstance = GetModuleHandle(nullptr); @@ -35,10 +27,7 @@ HWND MpvContainer::Create() { window_class.hCursor = ::LoadCursorW(nullptr, IDC_ARROW); window_class.hbrBackground = ::CreateSolidBrush(RGB(0, 0, 0)); - ATOM atom = ::RegisterClassExW(&window_class); - char msg[256]; - snprintf(msg, sizeof(msg), "MpvContainer::Create - RegisterClassExW returned: %d", atom); - LogToFile(msg); + ::RegisterClassExW(&window_class); // Use WS_POPUP for a borderless window without title bar. // Use WS_EX_TOOLWINDOW | WS_EX_NOREDIRECTIONBITMAP to prevent shadow and DWM effects. @@ -48,15 +37,6 @@ HWND MpvContainer::Create() { 0, 0, 100, 100, nullptr, nullptr, GetModuleHandle(nullptr), nullptr); - if (!handle_) { - DWORD error = GetLastError(); - snprintf(msg, sizeof(msg), "MpvContainer::Create - CreateWindow failed with error %lu", error); - LogToFile(msg); - } else { - snprintf(msg, sizeof(msg), "MpvContainer::Create - handle_: %p", handle_); - LogToFile(msg); - } - // Disable DWM animations on the container. auto disable_window_transitions = TRUE; DwmSetWindowAttribute(handle_, DWMWA_TRANSITIONS_FORCEDISABLED, @@ -67,42 +47,30 @@ HWND MpvContainer::Create() { } HWND MpvContainer::Get(HWND flutter_window) { - LogToFile("MpvContainer::Get called"); - char msg[256]; - snprintf(msg, sizeof(msg), "MpvContainer::Get - flutter_window: %p, handle_: %p", flutter_window, handle_); - LogToFile(msg); - if (!handle_) { - LogToFile("MpvContainer::Get - handle_ is null, calling Create()"); Create(); } RECT window_rect; ::GetWindowRect(flutter_window, &window_rect); - snprintf(msg, sizeof(msg), "MpvContainer::Get - window_rect: %ld,%ld,%ld,%ld", - window_rect.left, window_rect.top, window_rect.right, window_rect.bottom); - LogToFile(msg); - ::SetWindowPos(handle_, flutter_window, window_rect.left, window_rect.top, window_rect.right - window_rect.left, window_rect.bottom - window_rect.top, SWP_NOACTIVATE); ::SetWindowLongPtr(handle_, GWLP_USERDATA, reinterpret_cast(flutter_window)); - // Remove taskbar entry using ITaskbarList3. - ITaskbarList3* taskbar = nullptr; - HRESULT hr = ::CoCreateInstance(CLSID_TaskbarList, 0, CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(&taskbar)); - if (SUCCEEDED(hr) && taskbar) { - taskbar->DeleteTab(handle_); - taskbar->Release(); + // Remove taskbar entry using cached ITaskbarList3. + if (!taskbar_) { + ::CoCreateInstance(CLSID_TaskbarList, 0, CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&taskbar_)); + } + if (taskbar_) { + taskbar_->DeleteTab(handle_); } ::ShowWindow(handle_, SW_SHOWNOACTIVATE); ::SetFocus(flutter_window); - snprintf(msg, sizeof(msg), "MpvContainer::Get - returning handle_: %p", handle_); - LogToFile(msg); return handle_; } diff --git a/windows/runner/mpv/mpv_container.h b/windows/runner/mpv/mpv_container.h index 1def5696..e071315b 100644 --- a/windows/runner/mpv/mpv_container.h +++ b/windows/runner/mpv/mpv_container.h @@ -1,6 +1,7 @@ #ifndef MPV_CONTAINER_H_ #define MPV_CONTAINER_H_ +#include #include #include @@ -17,6 +18,7 @@ class MpvContainer { static MpvContainer* GetInstance(); MpvContainer() = default; + ~MpvContainer(); // Creates the container window. HWND Create(); @@ -32,6 +34,7 @@ class MpvContainer { LPARAM lparam) noexcept; HWND handle_ = nullptr; + ITaskbarList3* taskbar_ = nullptr; static constexpr wchar_t kClassName[] = L"MPV_CONTAINER"; static constexpr wchar_t kWindowName[] = L""; diff --git a/windows/runner/mpv/mpv_core.cpp b/windows/runner/mpv/mpv_core.cpp index 3f57a065..c427a9d2 100644 --- a/windows/runner/mpv/mpv_core.cpp +++ b/windows/runner/mpv/mpv_core.cpp @@ -1,20 +1,10 @@ #include "mpv_core.h" -#include +#include #include "mpv_container.h" #include "utils.h" -static void LogToFile(const char* message) { - std::ofstream log("C:\\Users\\admin\\mpv_debug.log", std::ios::app); - if (log.is_open()) { - log << message << std::endl; - log.close(); - } - OutputDebugStringA(message); - OutputDebugStringA("\n"); -} - namespace mpv { MpvCore* MpvCore::GetInstance() { return instance_.get(); } @@ -40,20 +30,8 @@ MpvCore::~MpvCore() { } void MpvCore::EnsureInitialized() { - LogToFile("MpvCore::EnsureInitialized called"); - char msg[256]; - snprintf(msg, sizeof(msg), "MpvCore::EnsureInitialized - flutter_window_: %p", flutter_window_); - LogToFile(msg); - - // Enable per-pixel transparency on Flutter window. - LogToFile("MpvCore::EnsureInitialized - calling SetWindowComposition"); - SetWindowComposition(flutter_window_, 6, 0); - - LogToFile("MpvCore::EnsureInitialized - getting container"); + // Get container - composition will be enabled in SetVisible() to batch DwmFlush calls container_ = MpvContainer::GetInstance()->Get(flutter_window_); - - snprintf(msg, sizeof(msg), "MpvCore::EnsureInitialized - container: %p", container_); - LogToFile(msg); } void MpvCore::CreateMpvView(HWND mpv_hwnd, RECT rect, @@ -101,23 +79,34 @@ void MpvCore::SetHitTestBehavior(int32_t hittest_behavior) { ex_style &= ~(WS_EX_TRANSPARENT | WS_EX_LAYERED); } ::SetWindowLong(flutter_window_, GWL_EXSTYLE, ex_style); + + // Force Windows to recalculate window frame after extended style changes. + // This ensures the render surface dimensions match the new window state. + ::SetWindowPos(flutter_window_, nullptr, 0, 0, 0, 0, + SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); + + // Sync with DWM to ensure render target is updated + ::DwmFlush(); } void MpvCore::SetVisible(bool visible) { - char msg[256]; - snprintf(msg, sizeof(msg), "MpvCore::SetVisible - visible: %d, container_: %p", visible, container_); - LogToFile(msg); - visible_ = visible; if (container_) { if (visible) { - SetWindowComposition(flutter_window_, 6, 0); + // Batch all window operations, single DwmFlush at end + ::SetWindowPos(flutter_window_, nullptr, 0, 0, 0, 0, + SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); + if (!composition_enabled_) { + SetWindowComposition(flutter_window_, 6, 0); + composition_enabled_ = true; + } ::ShowWindow(container_, SW_SHOWNOACTIVATE); - LogToFile("MpvCore::SetVisible - showed container, set composition to 6"); + ::DwmFlush(); // Single sync with DWM after all operations } else { SetWindowComposition(flutter_window_, 0, 0); + composition_enabled_ = false; ::ShowWindow(container_, SW_HIDE); - LogToFile("MpvCore::SetVisible - hid container, set composition to 0"); + ::DwmFlush(); // Sync with DWM after hiding } } } @@ -135,68 +124,55 @@ std::optional MpvCore::WindowProc(HWND hwnd, UINT message, break; } case WM_SIZE: { - char msg[256]; - snprintf(msg, sizeof(msg), "WM_SIZE - wparam: %llu, last: %llu, visible_: %d, was_hidden: %d", - (unsigned long long)wparam, (unsigned long long)last_wm_size_wparam_, - visible_, was_window_hidden_due_to_minimize_); - LogToFile(msg); - // Handle Windows's minimize & maximize animations properly. // During these transitions, we hide the container and make Flutter opaque, - // then restore after the animation completes. + // then restore after the animation completes using a Windows timer. if (wparam != SIZE_RESTORED || last_wm_size_wparam_ == SIZE_MINIMIZED || last_wm_size_wparam_ == SIZE_MAXIMIZED || was_window_hidden_due_to_minimize_) { was_window_hidden_due_to_minimize_ = false; SetWindowComposition(flutter_window_, 0, 0); + composition_enabled_ = false; ::ShowWindow(container_, SW_HIDE); - LogToFile("WM_SIZE - hiding container, starting delay thread"); - last_thread_time_ = - std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); - std::thread( - [this](uint64_t time) { - std::this_thread::sleep_for( - std::chrono::milliseconds(kPositionAndShowDelay)); + ::DwmFlush(); // Single sync after hiding - // Check if this thread is still the latest (another WM_SIZE may have come in) - if (time != last_thread_time_) { - LogToFile("WM_SIZE thread - superseded by newer thread, skipping"); - return; - } - - char msg2[256]; - snprintf(msg2, sizeof(msg2), "WM_SIZE thread - after delay, visible_: %d", - visible_); - LogToFile(msg2); - - // Update container position to match current Flutter window bounds - RECT window_rect; - ::GetWindowRect(flutter_window_, &window_rect); - snprintf(msg2, sizeof(msg2), "WM_SIZE thread - flutter rect: %ld,%ld,%ld,%ld", - window_rect.left, window_rect.top, window_rect.right, window_rect.bottom); - LogToFile(msg2); - - ::SetWindowPos(container_, flutter_window_, window_rect.left, - window_rect.top, window_rect.right - window_rect.left, - window_rect.bottom - window_rect.top, SWP_NOACTIVATE); - LogToFile("WM_SIZE thread - updated container position"); - - // Always restore transparency if video is visible - if (visible_) { - SetWindowComposition(flutter_window_, 6, 0); - LogToFile("WM_SIZE thread - restored composition to 6"); - ::ShowWindow(container_, SW_SHOWNOACTIVATE); - LogToFile("WM_SIZE thread - showed container"); - } - }, - last_thread_time_) - .detach(); + // Cancel any pending timer and set a new one. + ::KillTimer(flutter_window_, kCompositionRestoreTimerId); + ::SetTimer(flutter_window_, kCompositionRestoreTimerId, kPositionAndShowDelay, nullptr); } last_wm_size_wparam_ = wparam; break; } + case WM_TIMER: { + if (wparam == kCompositionRestoreTimerId) { + ::KillTimer(flutter_window_, kCompositionRestoreTimerId); + + // Update container position to match current Flutter window bounds + RECT window_rect; + ::GetWindowRect(flutter_window_, &window_rect); + ::SetWindowPos(container_, flutter_window_, window_rect.left, + window_rect.top, window_rect.right - window_rect.left, + window_rect.bottom - window_rect.top, SWP_NOACTIVATE); + + // Restore transparency if video is visible + if (visible_) { + // Batch all operations, single DwmFlush at end + ::SetWindowPos(flutter_window_, nullptr, 0, 0, 0, 0, + SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); + if (!composition_enabled_) { + SetWindowComposition(flutter_window_, 6, 0); + composition_enabled_ = true; + } + ::ShowWindow(container_, SW_SHOWNOACTIVATE); + ::DwmFlush(); // Single sync after all operations + + // Force a redraw to ensure Flutter's render surface is correctly sized + ::RedrawWindow(flutter_window_, nullptr, nullptr, + RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN); + } + } + break; + } case WM_MOVE: case WM_MOVING: case WM_WINDOWPOSCHANGED: { @@ -211,7 +187,9 @@ std::optional MpvCore::WindowProc(HWND hwnd, UINT message, if (window_rect.left < 0 && window_rect.top < 0 && window_rect.right < 0 && window_rect.bottom < 0) { SetWindowComposition(flutter_window_, 0, 0); + composition_enabled_ = false; ::ShowWindow(container_, SW_HIDE); + ::DwmFlush(); // Single sync after hiding was_window_hidden_due_to_minimize_ = true; } } diff --git a/windows/runner/mpv/mpv_core.h b/windows/runner/mpv/mpv_core.h index 2adca0be..945d153c 100644 --- a/windows/runner/mpv/mpv_core.h +++ b/windows/runner/mpv/mpv_core.h @@ -3,12 +3,10 @@ #include -#include #include #include #include #include -#include namespace mpv { @@ -17,6 +15,7 @@ namespace mpv { class MpvCore { public: static constexpr auto kPositionAndShowDelay = 300; + static constexpr UINT_PTR kCompositionRestoreTimerId = 1001; static MpvCore* GetInstance(); static void SetInstance(std::unique_ptr instance); @@ -57,10 +56,10 @@ class MpvCore { HWND container_ = nullptr; double device_pixel_ratio_ = 1.0; std::map mpv_views_; - uint64_t last_thread_time_ = 0; WPARAM last_wm_size_wparam_ = SIZE_RESTORED; bool was_window_hidden_due_to_minimize_ = false; bool visible_ = true; + bool composition_enabled_ = false; static std::unique_ptr instance_; static std::optional proc_id_;