#include "win32_window.h" #include #include #include #include #include #include #include #include #include #include #include "resource.h" // ============================================================ // WindowCompositionAttribute 相关定义(与 flutter_acrylic 一致) // 用于调用 user32.dll 的未公开 API SetWindowCompositionAttribute // 这是 flutter_acrylic 禁用 Mica/Acrylic 的关键调用 // ============================================================ typedef enum _WCA_WINDOWCOMPOSITIONATTRIB { WCA_ATTRIB_ACCENT_POLICY = 19, } WCA_WINDOWCOMPOSITIONATTRIB; typedef enum _WCA_ACCENT_STATE { WCA_ACCENT_DISABLED = 0, WCA_ACCENT_ENABLE_ACRYLICBLURBEHIND = 4, WCA_ACCENT_ENABLE_HOSTBACKDROP = 5, } WCA_ACCENT_STATE; typedef struct _WCA_ACCENT_POLICY { WCA_ACCENT_STATE AccentState; DWORD AccentFlags; DWORD GradientColor; DWORD AnimationId; } WCA_ACCENT_POLICY; typedef struct _WCA_WINDOWCOMPOSITIONATTRIBDATA { WCA_WINDOWCOMPOSITIONATTRIB Attrib; PVOID pvData; SIZE_T cbData; } WCA_WINDOWCOMPOSITIONATTRIBDATA; typedef BOOL(WINAPI* WCA_SetWindowCompositionAttribute)( HWND, WCA_WINDOWCOMPOSITIONATTRIBDATA*); // ============================================================ // debug instrumentation for window-drag-lag // ============================================================ namespace { std::mutex g_debug_log_mutex; std::wstring g_debug_log_path; void DebugLog(const std::string& tag, const std::string& detail) { // 已禁用文件日志以排除 I/O 对消息循环的干扰。 (void)tag; (void)detail; #if 0 auto now = std::chrono::system_clock::now(); auto us = std::chrono::duration_cast( now.time_since_epoch()) .count(); std::lock_guard lock(g_debug_log_mutex); if (g_debug_log_path.empty()) { g_debug_log_path = L"E:\\project\\flutter\\f\\xianyan\\xianyan_drag_debug.log"; } std::wofstream ofs(g_debug_log_path, std::ios::app); if (!ofs.is_open()) return; ofs << us << L" [" << std::wstring(tag.begin(), tag.end()) << L"] " << std::wstring(detail.begin(), detail.end()) << L"\n"; #endif } // 仅记录拖拽状态关键事件,验证 EnterSizeMove/ExitSizeMove 是否被调用。 void DragDebugLog(const std::string& line) { static std::mutex m; static std::wstring path = L"E:\\project\\flutter\\f\\xianyan\\xianyan_drag_state.log"; std::lock_guard lock(m); std::ofstream ofs(path, std::ios::app); if (!ofs.is_open()) return; auto now = std::chrono::system_clock::now(); auto ms = std::chrono::duration_cast( now.time_since_epoch()) .count(); ofs << ms << " " << line << "\n"; } const char* HitTestName(LRESULT ht) { switch (ht) { case HTNOWHERE: return "HTNOWHERE"; case HTCLIENT: return "HTCLIENT"; case HTCAPTION: return "HTCAPTION"; case HTLEFT: return "HTLEFT"; case HTRIGHT: return "HTRIGHT"; case HTTOP: return "HTTOP"; case HTBOTTOM: return "HTBOTTOM"; case HTTOPLEFT: return "HTTOPLEFT"; case HTTOPRIGHT: return "HTTOPRIGHT"; case HTBOTTOMLEFT: return "HTBOTTOMLEFT"; case HTBOTTOMRIGHT: return "HTBOTTOMRIGHT"; case HTTRANSPARENT: return "HTTRANSPARENT"; default: return "OTHER"; } } std::string MsgName(UINT msg) { switch (msg) { case WM_NCHITTEST: return "WM_NCHITTEST"; case WM_NCLBUTTONDOWN: return "WM_NCLBUTTONDOWN"; case WM_NCLBUTTONUP: return "WM_NCLBUTTONUP"; case WM_NCMOUSEMOVE: return "WM_NCMOUSEMOVE"; case WM_LBUTTONDOWN: return "WM_LBUTTONDOWN"; case WM_LBUTTONUP: return "WM_LBUTTONUP"; case WM_MOUSEMOVE: return "WM_MOUSEMOVE"; case WM_MOVE: return "WM_MOVE"; case WM_MOVING: return "WM_MOVING"; case WM_SIZE: return "WM_SIZE"; case WM_WINDOWPOSCHANGING: return "WM_WINDOWPOSCHANGING"; case WM_WINDOWPOSCHANGED: return "WM_WINDOWPOSCHANGED"; case WM_SYSCOMMAND: return "WM_SYSCOMMAND"; case WM_ENTERSIZEMOVE: return "WM_ENTERSIZEMOVE"; case WM_EXITSIZEMOVE: return "WM_EXITSIZEMOVE"; case WM_PAINT: return "WM_PAINT"; case WM_ERASEBKGND: return "WM_ERASEBKGND"; default: return "MSG_" + std::to_string(msg); } } } // namespace namespace { /// Window attribute that enables dark mode window decorations. /// /// Redefined in case the developer's machine has a Windows SDK older than /// version 10.0.22000.0. /// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute #ifndef DWMWA_USE_IMMERSIVE_DARK_MODE #define DWMWA_USE_IMMERSIVE_DARK_MODE 20 #endif // DWM 过渡动画与背景材质类型(兼容旧版 SDK) #ifndef DWMWA_TRANSITIONS_FORCEDISABLED #define DWMWA_TRANSITIONS_FORCEDISABLED 3 #endif #ifndef DWMWA_SYSTEMBACKDROP_TYPE #define DWMWA_SYSTEMBACKDROP_TYPE 38 #endif #ifndef DWMWA_MICA_EFFECT #define DWMWA_MICA_EFFECT 1029 #endif // RtlGetVersion 用于获取真实系统版本(GetVersionExW 在 Win8.1+ 会返回兼容性版本) typedef LONG NTSTATUS, *PNTSTATUS; #define STATUS_SUCCESS (0x00000000) typedef NTSTATUS(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW); #ifndef DWMSBT_AUTO #define DWMSBT_AUTO 0 #endif #ifndef DWMSBT_NONE #define DWMSBT_NONE 1 #endif #ifndef DWMSBT_MAINWINDOW #define DWMSBT_MAINWINDOW 3 #endif #ifndef DWMSBT_TRANSIENTWINDOW #define DWMSBT_TRANSIENTWINDOW 4 #endif #ifndef DWMSBT_TABBEDWINDOW #define DWMSBT_TABBEDWINDOW 2 #endif constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; /// Registry key for app theme preference. /// /// A value of 0 indicates apps should use dark mode. A non-zero or missing /// value indicates apps should use light mode. constexpr const wchar_t kGetPreferredBrightnessRegKey[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; // The number of Win32Window objects that currently exist. static int g_active_window_count = 0; using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); // Scale helper to convert logical scaler values to physical using passed in // scale factor int Scale(int source, double scale_factor) { return static_cast(source * scale_factor); } // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. // This API is only needed for PerMonitor V1 awareness mode. void EnableFullDpiSupportIfAvailable(HWND hwnd) { HMODULE user32_module = LoadLibraryA("User32.dll"); if (!user32_module) { return; } auto enable_non_client_dpi_scaling = reinterpret_cast( GetProcAddress(user32_module, "EnableNonClientDpiScaling")); if (enable_non_client_dpi_scaling != nullptr) { enable_non_client_dpi_scaling(hwnd); } FreeLibrary(user32_module); } } // namespace // Manages the Win32Window's window class registration. class WindowClassRegistrar { public: ~WindowClassRegistrar() = default; // Returns the singleton registrar instance. static WindowClassRegistrar* GetInstance() { if (!instance_) { instance_ = new WindowClassRegistrar(); } return instance_; } // Returns the name of the window class, registering the class if it hasn't // previously been registered. const wchar_t* GetWindowClass(); // Unregisters the window class. Should only be called if there are no // instances of the window. void UnregisterWindowClass(); private: WindowClassRegistrar() = default; static WindowClassRegistrar* instance_; bool class_registered_ = false; }; WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; const wchar_t* WindowClassRegistrar::GetWindowClass() { if (!class_registered_) { WNDCLASS window_class{}; window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); window_class.lpszClassName = kWindowClassName; window_class.style = CS_DBLCLKS; window_class.cbClsExtra = 0; window_class.cbWndExtra = 0; window_class.hInstance = GetModuleHandle(nullptr); window_class.hIcon = LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); window_class.hbrBackground = 0; window_class.lpszMenuName = nullptr; window_class.lpfnWndProc = Win32Window::WndProc; RegisterClass(&window_class); class_registered_ = true; } return kWindowClassName; } void WindowClassRegistrar::UnregisterWindowClass() { UnregisterClass(kWindowClassName, nullptr); class_registered_ = false; } Win32Window::Win32Window() { ++g_active_window_count; } Win32Window::~Win32Window() { --g_active_window_count; Destroy(); } bool Win32Window::Create(const std::wstring& title, const Point& origin, const Size& size) { Destroy(); const wchar_t* window_class = WindowClassRegistrar::GetInstance()->GetWindowClass(); const POINT target_point = {static_cast(origin.x), static_cast(origin.y)}; HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); double scale_factor = dpi / 96.0; HWND window = CreateWindow( window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), Scale(size.width, scale_factor), Scale(size.height, scale_factor), nullptr, nullptr, GetModuleHandle(nullptr), this); if (!window) { return false; } DetectBackdropCapabilities(&system_backdrop_supported_, &mica_effect_supported_); UpdateTheme(window); return OnCreate(); } bool Win32Window::Show() { return ShowWindow(window_handle_, SW_SHOWNORMAL); } // static LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { if (message == WM_NCCREATE) { auto window_struct = reinterpret_cast(lparam); SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(window_struct->lpCreateParams)); auto that = static_cast(window_struct->lpCreateParams); EnableFullDpiSupportIfAvailable(window); that->window_handle_ = window; } else if (Win32Window* that = GetThisFromHandle(window)) { return that->MessageHandler(window, message, wparam, lparam); } return DefWindowProc(window, message, wparam, lparam); } LRESULT Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { // region debug-point parent-msg if (message == WM_NCHITTEST || message == WM_NCLBUTTONDOWN || message == WM_NCLBUTTONUP || message == WM_NCMOUSEMOVE || message == WM_LBUTTONDOWN || message == WM_LBUTTONUP || message == WM_MOUSEMOVE || message == WM_MOVE || message == WM_MOVING || message == WM_SIZE || message == WM_WINDOWPOSCHANGING || message == WM_WINDOWPOSCHANGED || message == WM_SYSCOMMAND || message == WM_ENTERSIZEMOVE || message == WM_EXITSIZEMOVE || message == WM_ERASEBKGND) { std::ostringstream oss; oss << "hwnd=" << hwnd << " msg=" << MsgName(message) << " wp=" << wparam << " lp=" << lparam; DebugLog("parent", oss.str()); } // endregion debug-point parent-msg switch (message) { case WM_DESTROY: window_handle_ = nullptr; Destroy(); if (quit_on_close_) { PostQuitMessage(0); } return 0; case WM_DPICHANGED: { auto newRectSize = reinterpret_cast(lparam); LONG newWidth = newRectSize->right - newRectSize->left; LONG newHeight = newRectSize->bottom - newRectSize->top; SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, newHeight, SWP_NOZORDER | SWP_NOACTIVATE); return 0; } case WM_SIZE: { RECT rect = GetClientArea(); if (child_content_ != nullptr) { // DwmExtendFrameIntoClientArea(margins={-1}) 会改变窗口帧布局, // 但 GetClientRect 始终返回正确的客户区尺寸(左上角为 0,0), // 所以 MoveWindow 从 (0,0) 开始填充整个客户区是正确的。 // 关键:确保 bRepaint=TRUE 以立即重绘。 MoveWindow(child_content_, 0, 0, rect.right, rect.bottom, TRUE); } return 0; } case WM_ACTIVATE: if (child_content_ != nullptr) { SetFocus(child_content_); } return 0; case WM_DWMCOLORIZATIONCOLORCHANGED: UpdateTheme(hwnd); return 0; case WM_ERASEBKGND: { UINT64 enter = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()) .count(); LRESULT r = DefWindowProc(hwnd, message, wparam, lparam); UINT64 now = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()) .count(); std::ostringstream oss; oss << "WM_ERASEBKGND duration_us=" << (now - enter); DebugLog("parent", oss.str()); return r; } case WM_ENTERSIZEMOVE: { EnterSizeMove(hwnd); break; } case WM_EXITSIZEMOVE: { ExitSizeMove(hwnd); break; } case WM_GETMINMAXINFO: { // 处理窗口最小尺寸限制 MINMAXINFO* mmi = reinterpret_cast(lparam); if (min_width_ > 0 && min_height_ > 0) { // 获取当前 DPI 缩放因子 HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); double scale_factor = dpi / 96.0; // 设置最小跟踪尺寸(物理像素) mmi->ptMinTrackSize.x = static_cast(min_width_ * scale_factor); mmi->ptMinTrackSize.y = static_cast(min_height_ * scale_factor); } return 0; } case WM_NCHITTEST: { // 标题栏可拖拽区域返回 HTCAPTION,让 Windows 进入原生模态拖拽循环。 // 原生拖拽由 DWM 直接处理,不会经过 Flutter 插件链,避免 MethodChannel // 和 Dart setState 导致的延迟。 POINT pt = {GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam)}; ScreenToClient(hwnd, &pt); UINT dpi = GetDpiForWindow(hwnd); double scale = static_cast(dpi) / 96.0; int title_h = static_cast(title_bar_height_ * scale); int btn_w = static_cast(title_bar_button_width_ * scale); RECT cr; GetClientRect(hwnd, &cr); { std::ostringstream oss; oss << "WM_NCHITTEST raw_screen=(" << GET_X_LPARAM(lparam) << "," << GET_Y_LPARAM(lparam) << ") client=(" << pt.x << "," << pt.y << ") dpi=" << dpi << " scale=" << scale << " title_h=" << title_h << " btn_w=" << btn_w << " cr=(" << cr.right << "," << cr.bottom << ")"; DebugLog("parent", oss.str()); } if (cr.right <= 0 || cr.bottom <= 0) { DebugLog("parent", "WM_NCHITTEST fallback: empty client rect"); break; } if (pt.y >= 0 && pt.y < title_h && pt.x >= 0 && pt.x < cr.right - btn_w) { std::ostringstream oss; oss << "WM_NCHITTEST -> HTCAPTION pt=(" << pt.x << "," << pt.y << ") title_h=" << title_h << " btn_w=" << btn_w << " cr=(" << cr.right << "," << cr.bottom << ")"; DebugLog("parent", oss.str()); return HTCAPTION; } std::ostringstream oss; oss << "WM_NCHITTEST -> Def pt=(" << pt.x << "," << pt.y << ") title_h=" << title_h << " btn_w=" << btn_w; DebugLog("parent", oss.str()); break; } case WM_NCLBUTTONDOWN: { // 标题栏 HTCAPTION 的拖拽已在 FlutterWindow::MessageHandler 中 // 前置处理(先禁用 DWM 效果再进入原生拖拽循环)。 // 此处仅处理非标题栏区域点击,由 DefWindowProc 处理窗口边框调整大小等。 std::ostringstream oss; oss << "WM_NCLBUTTONDOWN ht=" << HitTestName(static_cast(wparam)); DebugLog("parent", oss.str()); break; } } return DefWindowProc(window_handle_, message, wparam, lparam); } void Win32Window::Destroy() { OnDestroy(); if (window_handle_) { DestroyWindow(window_handle_); window_handle_ = nullptr; } if (g_active_window_count == 0) { WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); } } Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { return reinterpret_cast( GetWindowLongPtr(window, GWLP_USERDATA)); } void Win32Window::SetChildContent(HWND content) { child_content_ = content; SetParent(content, window_handle_); RECT frame = GetClientArea(); MoveWindow(content, 0, 0, frame.right, frame.bottom, true); SetFocus(child_content_); } RECT Win32Window::GetClientArea() { RECT frame; GetClientRect(window_handle_, &frame); return frame; } HWND Win32Window::GetHandle() { return window_handle_; } void Win32Window::SetQuitOnClose(bool quit_on_close) { quit_on_close_ = quit_on_close; } bool Win32Window::OnCreate() { // No-op; provided for subclasses. return true; } void Win32Window::OnDestroy() { // No-op; provided for subclasses. } void Win32Window::UpdateTheme(HWND const window) { DWORD light_mode; DWORD light_mode_size = sizeof(light_mode); LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, kGetPreferredBrightnessRegValue, RRF_RT_REG_DWORD, nullptr, &light_mode, &light_mode_size); if (result == ERROR_SUCCESS) { BOOL enable_dark_mode = light_mode == 0; DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, &enable_dark_mode, sizeof(enable_dark_mode)); } } void Win32Window::SetDarkMode(HWND const window, bool dark_mode) { BOOL enable_dark_mode = dark_mode ? TRUE : FALSE; DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, &enable_dark_mode, sizeof(enable_dark_mode)); } // ============================================================ // 静态成员变量初始化 // ============================================================ bool Win32Window::fullscreen_ = false; RECT Win32Window::saved_window_rect_ = {0}; LONG Win32Window::saved_window_style_ = 0; LONG Win32Window::saved_window_ex_style_ = 0; WINDOWPLACEMENT Win32Window::saved_placement_ = {sizeof(WINDOWPLACEMENT)}; unsigned int Win32Window::min_width_ = 400; unsigned int Win32Window::min_height_ = 600; double Win32Window::title_bar_height_ = 36.0; double Win32Window::title_bar_button_width_ = 138.0; // ============================================================ // 窗口管理扩展方法实现 // ============================================================ void Win32Window::SetWindowTitle(HWND const window, const std::wstring& title) { SetWindowTextW(window, title.c_str()); } void Win32Window::SetFullscreen(HWND const window, bool fullscreen) { if (fullscreen == fullscreen_) { return; // 状态未变化 } if (fullscreen) { // 进入全屏:保存当前窗口状态 saved_window_style_ = GetWindowLong(window, GWL_STYLE); saved_window_ex_style_ = GetWindowLong(window, GWL_EXSTYLE); GetWindowPlacement(window, &saved_placement_); GetWindowRect(window, &saved_window_rect_); // 移除标题栏和边框,设置为全屏样式 LONG new_style = saved_window_style_ & ~WS_OVERLAPPEDWINDOW; SetWindowLong(window, GWL_STYLE, new_style); // 获取屏幕尺寸并最大化 HMONITOR monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONEAREST); MONITORINFO monitor_info; monitor_info.cbSize = sizeof(monitor_info); GetMonitorInfo(monitor, &monitor_info); SetWindowPos(window, HWND_TOP, monitor_info.rcMonitor.left, monitor_info.rcMonitor.top, monitor_info.rcMonitor.right - monitor_info.rcMonitor.left, monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top, SWP_NOZORDER | SWP_FRAMECHANGED | SWP_SHOWWINDOW); fullscreen_ = true; } else { // 退出全屏:恢复保存的窗口状态 SetWindowLong(window, GWL_STYLE, saved_window_style_); SetWindowLong(window, GWL_EXSTYLE, saved_window_ex_style_); SetWindowPlacement(window, &saved_placement_); SetWindowPos(window, nullptr, saved_window_rect_.left, saved_window_rect_.top, saved_window_rect_.right - saved_window_rect_.left, saved_window_rect_.bottom - saved_window_rect_.top, SWP_NOZORDER | SWP_FRAMECHANGED | SWP_SHOWWINDOW); fullscreen_ = false; } } bool Win32Window::IsFullscreen(HWND const window) { return fullscreen_; } void Win32Window::SetMinSize(HWND const window, unsigned int width, unsigned int height) { min_width_ = width; min_height_ = height; } void Win32Window::PerformHapticFeedback(HWND const window, int feedback_type) { // Windows 无原生触觉反馈,使用 MessageBeep 模拟 // feedback_type: 0=light, 1=medium, 2=heavy, 3=selection UINT beep_type = MB_OK; // 默认 switch (feedback_type) { case 0: // light beep_type = 0xFFFFFFFF; // 简单蜂鸣 break; case 1: // medium beep_type = MB_ICONINFORMATION; break; case 2: // heavy beep_type = MB_ICONWARNING; break; case 3: // selection beep_type = 0xFFFFFFFF; break; default: break; } MessageBeep(beep_type); } std::string Win32Window::GetSystemAppearance() { DWORD light_mode; DWORD light_mode_size = sizeof(light_mode); LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, kGetPreferredBrightnessRegValue, RRF_RT_REG_DWORD, nullptr, &light_mode, &light_mode_size); if (result == ERROR_SUCCESS && light_mode == 0) { return "dark"; } return "light"; } // ============================================================ // 拖拽时临时禁用 DWM Mica/Acrylic 合成,避免拖拽卡顿 // ============================================================ void Win32Window::DetectBackdropCapabilities(bool* system_backdrop, bool* mica_effect) { *system_backdrop = false; *mica_effect = false; // 使用 RtlGetVersion 获取真实系统版本,避免 GetVersionExW 的兼容性谎言。 RTL_OSVERSIONINFOW osvi = {0}; osvi.dwOSVersionInfoSize = sizeof(osvi); HMODULE ntdll = GetModuleHandleW(L"ntdll.dll"); if (ntdll) { auto rtl_get_version = reinterpret_cast( GetProcAddress(ntdll, "RtlGetVersion")); if (rtl_get_version && STATUS_SUCCESS != rtl_get_version(&osvi)) { return; } } if (osvi.dwMajorVersion < 10) { return; } HWND hwnd = GetDesktopWindow(); if (osvi.dwBuildNumber >= 22523) { INT probe = DWMSBT_AUTO; *system_backdrop = SUCCEEDED(DwmSetWindowAttribute( hwnd, DWMWA_SYSTEMBACKDROP_TYPE, &probe, sizeof(probe))); } if (osvi.dwBuildNumber >= 22000) { BOOL probe = FALSE; *mica_effect = SUCCEEDED(DwmSetWindowAttribute( hwnd, DWMWA_MICA_EFFECT, &probe, sizeof(probe))); } } int Win32Window::GetCurrentBackdropType(HWND hwnd) { INT value = -1; HRESULT hr = DwmGetWindowAttribute(hwnd, DWMWA_SYSTEMBACKDROP_TYPE, &value, sizeof(value)); if (SUCCEEDED(hr)) { return static_cast(value); } return -1; } void Win32Window::SetBackdropType(HWND hwnd, int type) { INT value = static_cast(type); DwmSetWindowAttribute(hwnd, DWMWA_SYSTEMBACKDROP_TYPE, &value, sizeof(value)); } void Win32Window::SetTransitionsEnabled(HWND hwnd, BOOL enabled) { DwmSetWindowAttribute(hwnd, DWMWA_TRANSITIONS_FORCEDISABLED, &enabled, sizeof(enabled)); } void Win32Window::EnterSizeMove(HWND hwnd) { if (is_in_size_move_loop_) return; is_in_size_move_loop_ = true; DragDebugLog("EnterSizeMove begin"); // ============================================================ // 关键:调用 SetWindowCompositionAttribute(ACCENT_DISABLED) // // 这是 flutter_acrylic 的 Window.setEffect(disabled) 能立即禁用 Mica // 的关键调用,之前的 v4~v7 修复都遗漏了这一步。 // // SetWindowCompositionAttribute 是 user32.dll 的未公开 API, // 通过它设置窗口的 ACCENT_POLICY 为 ACCENT_DISABLED,可以立即禁用 // 所有窗口合成效果(包括 Mica/Acrylic)。 // // 仅靠 DwmSetWindowAttribute 是不够的——它是异步的,DWM 不会立即应用 // 变更。SetWindowCompositionAttribute 能立即生效。 // ============================================================ { HMODULE user32 = GetModuleHandleW(L"user32.dll"); if (user32) { auto set_window_composition_attribute = reinterpret_cast( GetProcAddress(user32, "SetWindowCompositionAttribute")); if (set_window_composition_attribute) { WCA_ACCENT_POLICY accent = {WCA_ACCENT_DISABLED, 2, 0, 0}; WCA_WINDOWCOMPOSITIONATTRIBDATA data; data.Attrib = WCA_ATTRIB_ACCENT_POLICY; data.pvData = &accent; data.cbData = sizeof(accent); set_window_composition_attribute(hwnd, &data); DragDebugLog("SetWindowCompositionAttribute(ACCENT_DISABLED) called"); } } } // 使用 {0, 0, 1, 0} 而非 {0, 0, 0, 0}: // 与 flutter_acrylic 的 setEffect(disabled) 一致。 // 注释:At least one margin should be non-negative in order to show // the DWM window shadow created by handling WM_NCCALCSIZE. saved_margins_ = {-1, -1, -1, -1}; has_saved_margins_ = true; MARGINS drag_margins = {0, 0, 1, 0}; DwmExtendFrameIntoClientArea(hwnd, &drag_margins); // 禁用 System Backdrop(Win11 22523+) saved_backdrop_type_ = GetCurrentBackdropType(hwnd); DragDebugLog(std::string("saved_backdrop_type=") + std::to_string(saved_backdrop_type_)); if (saved_backdrop_type_ > DWMSBT_NONE || saved_backdrop_type_ == -1) { SetBackdropType(hwnd, DWMSBT_NONE); } // 禁用 Mica Effect(Win11 22000+) if (mica_effect_supported_) { DWORD value = FALSE; DwmGetWindowAttribute(hwnd, DWMWA_MICA_EFFECT, &saved_mica_enabled_, sizeof(saved_mica_enabled_)); DragDebugLog(std::string("saved_mica_enabled=") + std::to_string(saved_mica_enabled_)); if (saved_mica_enabled_) { DwmSetWindowAttribute(hwnd, DWMWA_MICA_EFFECT, &value, sizeof(value)); } } // 禁用 DWM 过渡动画,进一步降低合成延迟 DwmGetWindowAttribute(hwnd, DWMWA_TRANSITIONS_FORCEDISABLED, &saved_transitions_enabled_, sizeof(saved_transitions_enabled_)); SetTransitionsEnabled(hwnd, TRUE); DragDebugLog("EnterSizeMove done"); } void Win32Window::ExitSizeMove(HWND hwnd) { if (!is_in_size_move_loop_) return; is_in_size_move_loop_ = false; DragDebugLog("ExitSizeMove begin"); // 恢复 backdrop if (system_backdrop_supported_ && saved_backdrop_type_ > DWMSBT_NONE) { SetBackdropType(hwnd, saved_backdrop_type_); saved_backdrop_type_ = -1; } // 恢复 Mica effect if (mica_effect_supported_ && saved_mica_enabled_) { DwmSetWindowAttribute(hwnd, DWMWA_MICA_EFFECT, &saved_mica_enabled_, sizeof(saved_mica_enabled_)); saved_mica_enabled_ = FALSE; } // 恢复 DWM 扩展帧边距 if (has_saved_margins_) { DwmExtendFrameIntoClientArea(hwnd, &saved_margins_); has_saved_margins_ = false; } // 恢复 DWM 过渡动画 SetTransitionsEnabled(hwnd, saved_transitions_enabled_); DragDebugLog("ExitSizeMove done"); }