mirror of
https://github.com/Blinue/Magpie.git
synced 2026-06-24 02:04:10 +00:00
* feat: 去除标题栏 * chore: 添加注释 * feat: 保存最大化状态 * fix: 优化最大化状态 * feat: Win11 无需绘制上边框 * feat: 添加 TitlebarControl * UI: 更改主界面样式 * fix: 修复一个隐蔽的bug * feat: 添加 CaptionButtonsControl * feat: 优化上边框颜色 * feat: 实现标题栏的 UI * fix: 修复标题栏按钮不跟随主题的问题 * fix: 优化主窗口最小尺寸 * fix: 修复上边框绘制错误 * UI: 优化样式 * UI: 稍微优化标题按钮样式 * UI: 优化标题按钮样式 * UI: 优化标题按钮样式 * UI: 优化标题栏样式 * feat: 实现拖拽功能 * fix: 修复调整窗口大小时闪烁的问题 * fix: 更改上边框的实现方式 * feat: 实现上边框调整尺寸和支持 Win11 的贴靠布局 * feat: 实现标题栏按钮的 hover * feat: 实现标题栏按钮的功能 * perf: 优化性能和添加注释 * fix: 修复一个小错误 * fix: 小修复 * fix: 优化最大化状态 * fix: 修复标题栏上右键菜单 * chore: 添加注释 * fix: 修复 Win10 中以最大化启动时一瞬间显示主题色背景的问题 * UI: 更新 ToggleSwitch 样式 * fix: 修复以最大化显示时的窗口动画 * fix: 修复 Win11 21H1/21H2 的背景 * chore: 优化注释 * feat: 在标题栏显示图标 * UI: 为标题栏添加动画 * fix: 修复导航栏菜单覆盖标题栏的问题 * fix: 修复标题按钮下方的可拖动区域 * feat: 导航栏不再支持 Minimal 状态 * chore: 删除不再需要的代码 * UI: 修正配置文件页面图标位置 * docs: 更新主窗口截图
578 lines
14 KiB
C++
578 lines
14 KiB
C++
#include "pch.h"
|
||
#include "MagApp.h"
|
||
#include "Logger.h"
|
||
#include "Win32Utils.h"
|
||
#include "ExclModeHack.h"
|
||
#include "DeviceResources.h"
|
||
#include "GraphicsCaptureFrameSource.h"
|
||
#include "DesktopDuplicationFrameSource.h"
|
||
#include "GDIFrameSource.h"
|
||
#include "DwmSharedSurfaceFrameSource.h"
|
||
#include "StrUtils.h"
|
||
#include "CursorManager.h"
|
||
#include "Renderer.h"
|
||
#include "GPUTimer.h"
|
||
#include "WindowHelper.h"
|
||
|
||
namespace Magpie::Core {
|
||
|
||
static constexpr const wchar_t* HOST_WINDOW_CLASS_NAME = L"Window_Magpie_967EB565-6F73-4E94-AE53-00CC42592A22";
|
||
static constexpr const wchar_t* DDF_WINDOW_CLASS_NAME = L"Window_Magpie_C322D752-C866-4630-91F5-32CB242A8930";
|
||
|
||
|
||
static LRESULT DDFWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
|
||
if (msg == WM_DESTROY) {
|
||
return 0;
|
||
}
|
||
|
||
return DefWindowProc(hWnd, msg, wParam, lParam);
|
||
}
|
||
|
||
static LRESULT CALLBACK LowLevelKeyboardProc(
|
||
_In_ int nCode,
|
||
_In_ WPARAM wParam,
|
||
_In_ LPARAM lParam
|
||
) {
|
||
if (nCode != HC_ACTION || wParam != WM_KEYDOWN) {
|
||
return CallNextHookEx(NULL, nCode, wParam, lParam);
|
||
}
|
||
|
||
KBDLLHOOKSTRUCT* info = (KBDLLHOOKSTRUCT*)lParam;
|
||
if (info->vkCode == VK_SNAPSHOT) {
|
||
([]()->winrt::fire_and_forget {
|
||
MagApp& app = MagApp::Get();
|
||
|
||
if (!app.GetOptions().IsDrawCursor()) {
|
||
co_return;
|
||
}
|
||
|
||
// 暂时隐藏光标
|
||
app.GetCursorManager().Hide();
|
||
app.GetRenderer().Render(true);
|
||
|
||
winrt::DispatcherQueue dispatcher = app.Dispatcher();
|
||
|
||
co_await 400ms;
|
||
co_await dispatcher;
|
||
|
||
if (app.GetHwndHost()) {
|
||
app.GetCursorManager().Show();
|
||
}
|
||
})();
|
||
}
|
||
|
||
return CallNextHookEx(NULL, nCode, wParam, lParam);
|
||
}
|
||
|
||
MagApp::MagApp() :
|
||
_hInst(GetModuleHandle(nullptr)),
|
||
_dispatcher(winrt::DispatcherQueue::GetForCurrentThread())
|
||
{
|
||
}
|
||
|
||
MagApp::~MagApp() {}
|
||
|
||
static bool CheckSrcWindow(HWND hwndSrc) {
|
||
if (!WindowHelper::IsValidSrcWindow(hwndSrc)) {
|
||
Logger::Get().Info("禁止缩放系统窗口");
|
||
return false;
|
||
}
|
||
|
||
// 不缩放最大化和最小化的窗口
|
||
if (Win32Utils::GetWindowShowCmd(hwndSrc) != SW_NORMAL) {
|
||
Logger::Get().Info("源窗口已最大化或最小化");
|
||
return false;
|
||
}
|
||
|
||
// 不缩放过小的窗口
|
||
RECT clientRect{};
|
||
GetClientRect(hwndSrc, &clientRect);
|
||
SIZE clientSize = Win32Utils::GetSizeOfRect(clientRect);
|
||
if (clientSize.cx < 5 || clientSize.cy < 5) {
|
||
Logger::Get().Info("源窗口尺寸过小");
|
||
return false;
|
||
}
|
||
|
||
#if _DEBUG
|
||
OutputDebugString(fmt::format(L"可执行文件路径:{}\n窗口类:{}\n",
|
||
Win32Utils::GetPathOfWnd(hwndSrc), Win32Utils::GetWndClassName(hwndSrc)).c_str());
|
||
#endif // _DEBUG
|
||
|
||
return true;
|
||
}
|
||
|
||
bool MagApp::Start(HWND hwndSrc, MagOptions&& options) {
|
||
if (_hwndHost) {
|
||
return false;
|
||
}
|
||
|
||
if (!CheckSrcWindow(hwndSrc)) {
|
||
return false;
|
||
}
|
||
|
||
_hwndSrc = hwndSrc;
|
||
_options = options;
|
||
|
||
_RegisterWndClasses();
|
||
|
||
if (!_CreateHostWnd()) {
|
||
_hwndSrc = NULL;
|
||
return false;
|
||
}
|
||
|
||
_deviceResources = std::make_unique<DeviceResources>();
|
||
if (!_deviceResources->Initialize()) {
|
||
Logger::Get().Error("初始化 DeviceResources 失败");
|
||
Stop();
|
||
return false;
|
||
}
|
||
|
||
if (!_InitFrameSource()) {
|
||
Logger::Get().Error("_InitFrameSource 失败");
|
||
Stop();
|
||
return false;
|
||
}
|
||
|
||
_renderer = std::make_unique<Renderer>();
|
||
if (!_renderer->Initialize()) {
|
||
Logger::Get().Error("初始化 Renderer 失败");
|
||
Stop();
|
||
return false;
|
||
}
|
||
|
||
_cursorManager = std::make_unique<CursorManager>();
|
||
if (!_cursorManager->Initialize()) {
|
||
Logger::Get().Error("初始化 CursorManager 失败");
|
||
Stop();
|
||
return false;
|
||
}
|
||
|
||
if (_options.IsDisableDirectFlip() && !_options.IsDebugMode()) {
|
||
// 在此处创建的 DDF 窗口不会立刻显示
|
||
if (!_DisableDirectFlip()) {
|
||
Logger::Get().Error("_DisableDirectFlip 失败");
|
||
}
|
||
}
|
||
|
||
_hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, NULL, 0);
|
||
|
||
assert(_hwndHost);
|
||
// SW_SHOWMAXIMIZED 使 Wallpaper Engine 可以在缩放时暂停动态壁纸
|
||
// GH#502
|
||
ShowWindow(_hwndHost, SW_SHOWMAXIMIZED);
|
||
|
||
// 模拟独占全屏
|
||
if (MagApp::Get().GetOptions().IsSimulateExclusiveFullscreen()) {
|
||
// 延迟 1s 以避免干扰游戏的初始化,见 #495
|
||
([](HWND hwndHost)->winrt::fire_and_forget {
|
||
co_await 1s;
|
||
MagApp::Get()._dispatcher.TryEnqueue([hwndHost]() {
|
||
MagApp& app = MagApp::Get();
|
||
// 缩放窗口句柄相同就认为中途没有退出缩放。
|
||
// 实践中很难创建出两个句柄相同的窗口,见 https://stackoverflow.com/a/65617844
|
||
if (app._hwndHost == hwndHost && app._options.IsSimulateExclusiveFullscreen() && !app._exclModeHack) {
|
||
app._exclModeHack = std::make_unique<ExclModeHack>();
|
||
}
|
||
});
|
||
})(_hwndHost);
|
||
};
|
||
|
||
return true;
|
||
}
|
||
|
||
winrt::fire_and_forget MagApp::_WaitForSrcMovingOrSizing() {
|
||
HWND hwndSrc = _hwndSrc;
|
||
while (true) {
|
||
if (!IsWindow(hwndSrc)
|
||
|| GetForegroundWindow() != hwndSrc
|
||
|| Win32Utils::GetWindowShowCmd(hwndSrc) != SW_NORMAL
|
||
) {
|
||
break;
|
||
}
|
||
|
||
// 检查源窗口是否正在调整大小或移动
|
||
GUITHREADINFO guiThreadInfo{};
|
||
guiThreadInfo.cbSize = sizeof(GUITHREADINFO);
|
||
if (!GetGUIThreadInfo(GetWindowThreadProcessId(hwndSrc, nullptr), &guiThreadInfo)) {
|
||
Logger::Get().Win32Error("GetGUIThreadInfo 失败");
|
||
break;
|
||
}
|
||
|
||
if (guiThreadInfo.flags & GUI_INMOVESIZE) {
|
||
co_await 10ms;
|
||
} else {
|
||
_dispatcher.TryEnqueue([this]() {
|
||
_isWaitingForSrcMovingOrSizing = false;
|
||
Start(_hwndSrc, std::move(_options));
|
||
});
|
||
co_return;
|
||
}
|
||
}
|
||
|
||
_dispatcher.TryEnqueue([this]() {
|
||
_isWaitingForSrcMovingOrSizing = false;
|
||
});
|
||
}
|
||
|
||
void MagApp::Stop(bool isSrcMovingOrSizing) {
|
||
if (_hwndHost) {
|
||
_dispatcher.TryEnqueue([this, isSrcMovingOrSizing]() {
|
||
_isWaitingForSrcMovingOrSizing = isSrcMovingOrSizing;
|
||
|
||
DestroyWindow(_hwndHost);
|
||
|
||
if(isSrcMovingOrSizing) {
|
||
// 源窗口的大小或位置不再改变时重新缩放
|
||
_WaitForSrcMovingOrSizing();
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
void MagApp::ToggleOverlay() {
|
||
_renderer->SetUIVisibility(!_renderer->IsUIVisiable());
|
||
}
|
||
|
||
uint32_t MagApp::RegisterWndProcHandler(std::function<std::optional<LRESULT>(HWND, UINT, WPARAM, LPARAM)> handler) noexcept {
|
||
uint32_t id = _nextWndProcHandlerID++;
|
||
_wndProcHandlers.emplace_back(std::move(handler), id);
|
||
return id;
|
||
}
|
||
|
||
bool MagApp::UnregisterWndProcHandler(uint32_t id) noexcept {
|
||
if (id == 0) {
|
||
return false;
|
||
}
|
||
|
||
// 从后向前查找,因为后注册的回调更可能先取消注册
|
||
for (int i = (int)_wndProcHandlers.size() - 1; i >= 0; --i) {
|
||
if (_wndProcHandlers[i].second == id) {
|
||
_wndProcHandlers.erase(_wndProcHandlers.begin() + i);
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
bool MagApp::MessageLoop() {
|
||
if (!_hwndHost) {
|
||
return true;
|
||
}
|
||
|
||
while (true) {
|
||
MSG msg;
|
||
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
|
||
if (msg.message == WM_QUIT) {
|
||
Stop();
|
||
return false;
|
||
}
|
||
|
||
TranslateMessage(&msg);
|
||
DispatchMessage(&msg);
|
||
}
|
||
|
||
if (!_hwndHost) {
|
||
if (_isWaitingForSrcMovingOrSizing) {
|
||
// 防止 CPU 占用过高
|
||
WaitMessage();
|
||
continue;
|
||
} else {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
_renderer->Render();
|
||
|
||
// 第二帧(等待时或完成后)显示 DDF 窗口
|
||
// 如果在 Run 中创建会有短暂的灰屏
|
||
// 选择第二帧的原因:当 GetFrameCount() 返回 1 时第一帧可能处于等待状态而没有渲染,见 Renderer::Render()
|
||
if (_renderer->GetGPUTimer().GetFrameCount() == 2 && _hwndDDF) {
|
||
ShowWindow(_hwndDDF, SW_NORMAL);
|
||
|
||
if (!SetWindowPos(_hwndDDF, _hwndHost, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOREDRAW)) {
|
||
Logger::Get().Win32Error("SetWindowPos 失败");
|
||
}
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
void MagApp::_RegisterWndClasses() const {
|
||
static bool registered = false;
|
||
if (!registered) {
|
||
registered = true;
|
||
|
||
WNDCLASSEX wcex = {};
|
||
wcex.cbSize = sizeof(WNDCLASSEX);
|
||
wcex.lpfnWndProc = _HostWndProcStatic;
|
||
wcex.hInstance = _hInst;
|
||
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
|
||
wcex.lpszClassName = HOST_WINDOW_CLASS_NAME;
|
||
|
||
if (!RegisterClassEx(&wcex)) {
|
||
// 忽略此错误,因为可能是重复注册产生的错误
|
||
Logger::Get().Win32Error("注册缩放窗口类失败");
|
||
}
|
||
|
||
wcex.lpfnWndProc = DDFWndProc;
|
||
wcex.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH);
|
||
wcex.lpszClassName = DDF_WINDOW_CLASS_NAME;
|
||
|
||
if (!RegisterClassEx(&wcex)) {
|
||
Logger::Get().Win32Error("注册 DDF 窗口类失败");
|
||
}
|
||
}
|
||
}
|
||
|
||
static BOOL CALLBACK MonitorEnumProc(HMONITOR, HDC, LPRECT monitorRect, LPARAM data) {
|
||
RECT* params = (RECT*)data;
|
||
|
||
if (Win32Utils::CheckOverlap(params[0], *monitorRect)) {
|
||
UnionRect(¶ms[1], monitorRect, ¶ms[1]);
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static bool CalcHostWndRect(HWND hWnd, MultiMonitorUsage multiMonitorUsage, RECT& result) {
|
||
switch (multiMonitorUsage) {
|
||
case MultiMonitorUsage::Closest:
|
||
{
|
||
// 使用距离源窗口最近的显示器
|
||
HMONITOR hMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
|
||
if (!hMonitor) {
|
||
Logger::Get().Win32Error("MonitorFromWindow 失败");
|
||
return false;
|
||
}
|
||
|
||
MONITORINFO mi{};
|
||
mi.cbSize = sizeof(mi);
|
||
if (!GetMonitorInfo(hMonitor, &mi)) {
|
||
Logger::Get().Win32Error("GetMonitorInfo 失败");
|
||
return false;
|
||
}
|
||
result = mi.rcMonitor;
|
||
|
||
break;
|
||
}
|
||
case MultiMonitorUsage::Intersected:
|
||
{
|
||
// 使用源窗口跨越的所有显示器
|
||
|
||
// [0] 存储源窗口坐标,[1] 存储计算结果
|
||
RECT params[2]{};
|
||
|
||
if (!Win32Utils::GetWindowFrameRect(hWnd, params[0])) {
|
||
Logger::Get().Error("GetWindowFrameRect 失败");
|
||
return false;
|
||
}
|
||
|
||
if (!EnumDisplayMonitors(NULL, NULL, MonitorEnumProc, (LPARAM)¶ms)) {
|
||
Logger::Get().Win32Error("EnumDisplayMonitors 失败");
|
||
return false;
|
||
}
|
||
|
||
result = params[1];
|
||
if (result.right - result.left <= 0 || result.bottom - result.top <= 0) {
|
||
Logger::Get().Error("计算缩放窗口坐标失败");
|
||
return false;
|
||
}
|
||
|
||
break;
|
||
}
|
||
case MultiMonitorUsage::All:
|
||
{
|
||
// 使用所有显示器(Virtual Screen)
|
||
int vsWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);
|
||
int vsHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
|
||
int vsX = GetSystemMetrics(SM_XVIRTUALSCREEN);
|
||
int vsY = GetSystemMetrics(SM_YVIRTUALSCREEN);
|
||
result = { vsX, vsY, vsX + vsWidth, vsY + vsHeight };
|
||
|
||
break;
|
||
}
|
||
default:
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
// 创建缩放窗口
|
||
bool MagApp::_CreateHostWnd() {
|
||
if (FindWindow(HOST_WINDOW_CLASS_NAME, nullptr)) {
|
||
Logger::Get().Error("已存在缩放窗口");
|
||
return false;
|
||
}
|
||
|
||
if (!CalcHostWndRect(_hwndSrc, _options.multiMonitorUsage, _hostWndRect)) {
|
||
Logger::Get().Error("CalcHostWndRect 失败");
|
||
return false;
|
||
}
|
||
|
||
{
|
||
// 源窗口和缩放窗口重合则不缩放,此时源窗口可能是无边框全屏窗口
|
||
RECT srcRect;
|
||
if (!Win32Utils::GetWindowFrameRect(_hwndSrc, srcRect)) {
|
||
Win32Utils::GetClientScreenRect(_hwndSrc, srcRect);
|
||
}
|
||
|
||
if (srcRect == _hostWndRect) {
|
||
Logger::Get().Info("源窗口已全屏");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// WS_EX_NOREDIRECTIONBITMAP 可以避免 WS_EX_LAYERED 导致的额外内存开销
|
||
_hwndHost = CreateWindowEx(
|
||
(_options.IsDebugMode() ? 0 : WS_EX_TOPMOST) | WS_EX_NOACTIVATE
|
||
| WS_EX_LAYERED | WS_EX_NOREDIRECTIONBITMAP | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW,
|
||
HOST_WINDOW_CLASS_NAME,
|
||
NULL, // 标题为空,否则会被添加新配置页面列为候选窗口
|
||
WS_POPUP,
|
||
_hostWndRect.left,
|
||
_hostWndRect.top,
|
||
_hostWndRect.right - _hostWndRect.left,
|
||
_hostWndRect.bottom - _hostWndRect.top,
|
||
NULL,
|
||
NULL,
|
||
_hInst,
|
||
NULL
|
||
);
|
||
if (!_hwndHost) {
|
||
Logger::Get().Win32Error("创建缩放窗口失败");
|
||
return false;
|
||
}
|
||
|
||
Logger::Get().Info(fmt::format("缩放窗口尺寸:{}x{}",
|
||
_hostWndRect.right - _hostWndRect.left, _hostWndRect.bottom - _hostWndRect.top));
|
||
|
||
// 设置窗口不透明
|
||
// 不完全透明时可关闭 DirectFlip
|
||
if (!SetLayeredWindowAttributes(_hwndHost, 0, _options.IsDisableDirectFlip() ? 254 : 255, LWA_ALPHA)) {
|
||
Logger::Get().Win32Error("SetLayeredWindowAttributes 失败");
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
bool MagApp::_InitFrameSource() {
|
||
switch (_options.captureMethod) {
|
||
case CaptureMethod::GraphicsCapture:
|
||
_frameSource = std::make_unique<GraphicsCaptureFrameSource>();
|
||
break;
|
||
case CaptureMethod::DesktopDuplication:
|
||
_frameSource = std::make_unique<DesktopDuplicationFrameSource>();
|
||
break;
|
||
case CaptureMethod::GDI:
|
||
_frameSource = std::make_unique<GDIFrameSource>();
|
||
break;
|
||
case CaptureMethod::DwmSharedSurface:
|
||
_frameSource = std::make_unique<DwmSharedSurfaceFrameSource>();
|
||
break;
|
||
default:
|
||
Logger::Get().Critical("未知的捕获模式");
|
||
return false;
|
||
}
|
||
|
||
Logger::Get().Info(StrUtils::Concat("当前捕获模式:", _frameSource->GetName()));
|
||
|
||
if (!_frameSource->Initialize()) {
|
||
Logger::Get().Critical("初始化 FrameSource 失败");
|
||
return false;
|
||
}
|
||
|
||
const RECT& frameRect = _frameSource->GetSrcFrameRect();
|
||
Logger::Get().Info(fmt::format("源窗口尺寸:{}x{}",
|
||
frameRect.right - frameRect.left, frameRect.bottom - frameRect.top));
|
||
|
||
return true;
|
||
}
|
||
|
||
bool MagApp::_DisableDirectFlip() {
|
||
// 没有显式关闭 DirectFlip 的方法
|
||
// 将全屏窗口设为稍微透明,以灰色全屏窗口为背景
|
||
_hwndDDF = CreateWindowEx(
|
||
WS_EX_NOACTIVATE | WS_EX_LAYERED | WS_EX_TRANSPARENT,
|
||
DDF_WINDOW_CLASS_NAME,
|
||
NULL,
|
||
WS_POPUP,
|
||
_hostWndRect.left,
|
||
_hostWndRect.top,
|
||
_hostWndRect.right - _hostWndRect.left,
|
||
_hostWndRect.bottom - _hostWndRect.top,
|
||
NULL,
|
||
NULL,
|
||
_hInst,
|
||
NULL
|
||
);
|
||
|
||
if (!_hwndDDF) {
|
||
Logger::Get().Win32Error("创建 DDF 窗口失败");
|
||
return false;
|
||
}
|
||
|
||
// 设置窗口不透明
|
||
if (!SetLayeredWindowAttributes(_hwndDDF, 0, 255, LWA_ALPHA)) {
|
||
Logger::Get().Win32Error("SetLayeredWindowAttributes 失败");
|
||
}
|
||
|
||
if (_frameSource->IsScreenCapture()) {
|
||
if (Win32Utils::GetOSVersion().Is20H1OrNewer()) {
|
||
// 使 DDF 窗口无法被捕获到
|
||
if (!SetWindowDisplayAffinity(_hwndDDF, WDA_EXCLUDEFROMCAPTURE)) {
|
||
Logger::Get().Win32Error("SetWindowDisplayAffinity 失败");
|
||
}
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
LRESULT MagApp::_HostWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
|
||
// 以反向调用回调
|
||
for (auto it = _wndProcHandlers.rbegin(); it != _wndProcHandlers.rend(); ++it) {
|
||
const auto& result = it->first(hWnd, msg, wParam, lParam);
|
||
if (result.has_value()) {
|
||
return *result;
|
||
}
|
||
}
|
||
|
||
switch (msg) {
|
||
case WM_DESTROY:
|
||
{
|
||
_OnQuit();
|
||
|
||
if (_hwndDDF) {
|
||
DestroyWindow(_hwndDDF);
|
||
_hwndDDF = NULL;
|
||
}
|
||
|
||
_hwndHost = NULL;
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
return DefWindowProc(hWnd, msg, wParam, lParam);
|
||
}
|
||
|
||
void MagApp::_OnQuit() {
|
||
if (_hKeyboardHook) {
|
||
UnhookWindowsHookEx(_hKeyboardHook);
|
||
_hKeyboardHook = NULL;
|
||
}
|
||
|
||
// 释放资源
|
||
_exclModeHack.reset();
|
||
_cursorManager.reset();
|
||
_renderer.reset();
|
||
_frameSource.reset();
|
||
_deviceResources.reset();
|
||
|
||
_nextWndProcHandlerID = 1;
|
||
_wndProcHandlers.clear();
|
||
}
|
||
|
||
}
|