Files
xianyan/windows/runner/win32_window.cpp
Developer f7520b17b2 win提交
2026-06-22 03:50:59 +08:00

843 lines
28 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "win32_window.h"
#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.
///
/// 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<int>(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<EnableNonClientDpiScaling*>(
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<LONG>(origin.x),
static_cast<LONG>(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<CREATESTRUCT*>(lparam);
SetWindowLongPtr(window, GWLP_USERDATA,
reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
auto that = static_cast<Win32Window*>(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<RECT*>(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::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);
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<LONG>(min_width_ * scale_factor);
mmi->ptMinTrackSize.y = static_cast<LONG>(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<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);
}
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<Win32Window*>(
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<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");
}