win提交

This commit is contained in:
Developer
2026-06-22 03:50:59 +08:00
parent 8786abd59e
commit f7520b17b2
32 changed files with 2179 additions and 1431 deletions

View File

@@ -3,9 +3,132 @@
#include <dwmapi.h>
#include <flutter_windows.h>
#include <shellscalingapi.h>
#include <windowsx.h>
#include <chrono>
#include <fstream>
#include <iomanip>
#include <mutex>
#include <sstream>
#include <string>
#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<std::chrono::microseconds>(
now.time_since_epoch())
.count();
std::lock_guard<std::mutex> 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<std::mutex> 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<std::chrono::milliseconds>(
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.
@@ -17,6 +140,37 @@ namespace {
#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.
@@ -92,7 +246,7 @@ const wchar_t* WindowClassRegistrar::GetWindowClass() {
WNDCLASS window_class{};
window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
window_class.lpszClassName = kWindowClassName;
window_class.style = CS_HREDRAW | CS_VREDRAW;
window_class.style = CS_DBLCLKS;
window_class.cbClsExtra = 0;
window_class.cbWndExtra = 0;
window_class.hInstance = GetModuleHandle(nullptr);
@@ -136,7 +290,8 @@ bool Win32Window::Create(const std::wstring& title,
double scale_factor = dpi / 96.0;
HWND window = CreateWindow(
window_class, title.c_str(), WS_OVERLAPPEDWINDOW,
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);
@@ -145,6 +300,8 @@ bool Win32Window::Create(const std::wstring& title,
return false;
}
DetectBackdropCapabilities(&system_backdrop_supported_,
&mica_effect_supported_);
UpdateTheme(window);
return OnCreate();
@@ -179,6 +336,22 @@ 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;
@@ -201,9 +374,11 @@ Win32Window::MessageHandler(HWND hwnd,
case WM_SIZE: {
RECT rect = GetClientArea();
if (child_content_ != nullptr) {
// Size and position the child window.
MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
rect.bottom - rect.top, TRUE);
// DwmExtendFrameIntoClientArea(margins={-1}) 会改变窗口帧布局,
// 但 GetClientRect 始终返回正确的客户区尺寸(左上角为 0,0
// 所以 MoveWindow 从 (0,0) 开始填充整个客户区是正确的。
// 关键:确保 bRepaint=TRUE 以立即重绘。
MoveWindow(child_content_, 0, 0, rect.right, rect.bottom, TRUE);
}
return 0;
}
@@ -218,6 +393,30 @@ Win32Window::MessageHandler(HWND hwnd,
UpdateTheme(hwnd);
return 0;
case WM_ERASEBKGND: {
UINT64 enter = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
LRESULT r = DefWindowProc(hwnd, message, wparam, lparam);
UINT64 now = std::chrono::duration_cast<std::chrono::microseconds>(
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<MINMAXINFO*>(lparam);
@@ -233,6 +432,61 @@ Win32Window::MessageHandler(HWND hwnd,
}
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<double>(dpi) / 96.0;
int title_h = static_cast<int>(title_bar_height_ * scale);
int btn_w = static_cast<int>(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<LRESULT>(wparam));
DebugLog("parent", oss.str());
break;
}
}
return DefWindowProc(window_handle_, message, wparam, lparam);
@@ -260,8 +514,7 @@ void Win32Window::SetChildContent(HWND content) {
SetParent(content, window_handle_);
RECT frame = GetClientArea();
MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
frame.bottom - frame.top, true);
MoveWindow(content, 0, 0, frame.right, frame.bottom, true);
SetFocus(child_content_);
}
@@ -321,6 +574,8 @@ 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;
// ============================================================
// 窗口管理扩展方法实现
@@ -422,3 +677,166 @@ std::string Win32Window::GetSystemAppearance() {
}
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<RtlGetVersionPtr>(
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<int>(value);
}
return -1;
}
void Win32Window::SetBackdropType(HWND hwnd, int type) {
INT value = static_cast<INT>(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<WCA_SetWindowCompositionAttribute>(
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 BackdropWin11 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 EffectWin11 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");
}