Skip to content

Commit fb4c3b1

Browse files
committed
feat: WindowCase 支持模拟不同类型的光标
1 parent 17497bb commit fb4c3b1

23 files changed

+334
-51
lines changed

src/Magpie.Core/CompSwapchainPresenter.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ static winrt::com_ptr<IPresentationFactory> CreatePresentationFactory(ID3D11Devi
1111
winrt::com_ptr<IPresentationFactory> result;
1212

1313
static const auto createPresentationFactory =
14-
Win32Helper::LoadSystemFunction<decltype(::CreatePresentationFactory)>(
14+
Win32Helper::LoadFunction<decltype(::CreatePresentationFactory)>(
1515
L"dcomp.dll", "CreatePresentationFactory");
1616
if (!createPresentationFactory) {
1717
return result;

src/Magpie.Core/CursorManager.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ void CursorManager::_ShowSystemCursor(bool show, bool onDestory) {
225225
}
226226

227227
static const auto showSystemCursor =
228-
Win32Helper::LoadSystemFunction<void WINAPI(BOOL)>(L"user32.dll", "ShowSystemCursor");
228+
Win32Helper::LoadFunction<void WINAPI(BOOL)>(L"user32.dll", "ShowSystemCursor");
229229

230230
if (showSystemCursor) {
231231
showSystemCursor((BOOL)show);

src/Magpie.Core/DwmSharedSurfaceFrameSource.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
namespace Magpie {
1010

11-
using DwmGetDxSharedSurfaceFunc = BOOL(
11+
using FnDwmGetDxSharedSurface = BOOL WINAPI(
1212
HWND hWnd,
1313
HANDLE* phSurface,
1414
LUID* pAdapterLuid,
@@ -17,11 +17,11 @@ using DwmGetDxSharedSurfaceFunc = BOOL(
1717
ULONGLONG* pWin32KUpdateId
1818
);
1919

20-
static DwmGetDxSharedSurfaceFunc* DwmGetDxSharedSurface = nullptr;
20+
static FnDwmGetDxSharedSurface* DwmGetDxSharedSurface = nullptr;
2121

2222
bool DwmSharedSurfaceFrameSource::_Initialize() noexcept {
2323
[[maybe_unused]] static Ignore _ = [] {
24-
DwmGetDxSharedSurface = Win32Helper::LoadSystemFunction<DwmGetDxSharedSurfaceFunc>(
24+
DwmGetDxSharedSurface = Win32Helper::LoadFunction<FnDwmGetDxSharedSurface>(
2525
L"user32.dll", "DwmGetDxSharedSurface");
2626
return Ignore();
2727
}();

src/Magpie.Core/Renderer2.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ ScalingError Renderer2::Initialize(
9898

9999
_presenter = std::make_unique<SwapChainPresenter>();
100100
if (!_presenter->Initialize(_graphicsContext, hwndAttach, size, _colorInfo)) {
101-
Logger::Get().Error("初始化 SwapChainPresenter 失败");
101+
Logger::Get().Error("SwapChainPresenter::Initialize 失败");
102102
return ScalingError::ScalingFailedGeneral;
103103
}
104104

src/Magpie.Core/SrcTracker.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ bool SrcTracker::UpdateState(
187187
// 不要使用 IsHungAppWindow,它有误报的情况,见 GH#1244。这里用了未记录函数
188188
// GhostWindowFromHungWindow,它可以准确检查源窗口是否已被替换为幽灵窗口。
189189
static const auto ghostWindowFromHungWindow =
190-
Win32Helper::LoadSystemFunction<HWND WINAPI(HWND)>(
190+
Win32Helper::LoadFunction<HWND WINAPI(HWND)>(
191191
L"user32.dll", "GhostWindowFromHungWindow");
192192
if (ghostWindowFromHungWindow && ghostWindowFromHungWindow(_hWnd)) {
193193
// 检查源窗口是否真的处于无响应状态

src/Magpie.Core/SwapChainPresenter.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ static void WaitForDwmComposition() noexcept {
135135
// Win11 可以使用准确的 DCompositionWaitForCompositorClock
136136
if (Win32Helper::GetOSVersion().IsWin11()) {
137137
static const auto dCompositionWaitForCompositorClock =
138-
Win32Helper::LoadSystemFunction<decltype(DCompositionWaitForCompositorClock)>(
138+
Win32Helper::LoadFunction<decltype(DCompositionWaitForCompositorClock)>(
139139
L"dcomp.dll", "DCompositionWaitForCompositorClock");
140140
if (dCompositionWaitForCompositorClock) {
141141
dCompositionWaitForCompositorClock(0, nullptr, INFINITE);

src/Magpie.Core/Win32Helper.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ wil::unique_process_handle Win32Helper::GetWindowProcessHandle(HWND hWnd) noexce
5454

5555
// 在某些窗口上 OpenProcess 会失败(如暗黑 2),尝试使用 GetProcessHandleFromHwnd
5656
static const auto getProcessHandleFromHwnd =
57-
Win32Helper::LoadSystemFunction<HANDLE WINAPI(HWND)>(L"Oleacc.dll", "GetProcessHandleFromHwnd");
57+
Win32Helper::LoadFunction<HANDLE WINAPI(HWND)>(L"Oleacc.dll", "GetProcessHandleFromHwnd");
5858
if (!getProcessHandleFromHwnd) {
5959
return result;
6060
}
@@ -426,7 +426,7 @@ bool Win32Helper::CreateDir(const std::wstring& path, bool recursive) noexcept {
426426
const Win32Helper::OSVersion& Win32Helper::GetOSVersion() noexcept {
427427
static OSVersion version = [] {
428428
const auto rtlGetVersion =
429-
LoadSystemFunction<LONG WINAPI(PRTL_OSVERSIONINFOW)>(L"ntdll.dll", "RtlGetVersion");
429+
LoadFunction<LONG WINAPI(PRTL_OSVERSIONINFOW)>(L"ntdll.dll", "RtlGetVersion");
430430
if (!rtlGetVersion) {
431431
return OSVersion();
432432
}
@@ -885,7 +885,7 @@ void Win32Helper::WaitForDwmComposition() noexcept {
885885
// Win11 可以使用准确的 DCompositionWaitForCompositorClock
886886
if (Win32Helper::GetOSVersion().IsWin11()) {
887887
static const auto dCompositionWaitForCompositorClock =
888-
Win32Helper::LoadSystemFunction<decltype(DCompositionWaitForCompositorClock)>(
888+
Win32Helper::LoadFunction<decltype(DCompositionWaitForCompositorClock)>(
889889
L"dcomp.dll", "DCompositionWaitForCompositorClock");
890890
if (dCompositionWaitForCompositorClock) {
891891
dCompositionWaitForCompositorClock(0, nullptr, INFINITE);

src/Magpie.Core/include/Win32Helper.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ struct Win32Helper {
187187
static const std::filesystem::path& GetExePath() noexcept;
188188

189189
template<typename T, std::enable_if_t<std::is_function_v<T>, int> = 0>
190-
static T* LoadSystemFunction(const wchar_t* dllName, const char* funcName) noexcept {
190+
static T* LoadFunction(const wchar_t* dllName, const char* funcName) noexcept {
191191
assert(dllName && funcName);
192192

193193
HMODULE hMod = GetModuleHandle(dllName);

src/Magpie/SmoothResizeHelper.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ bool SmoothResizeHelper::EnableResizeSync(HWND hWnd, const winrt::Application& a
1919
// UWP 使用这个未记录的接口实现平滑调整尺寸
2020
// https://gist.github.com/apkipa/20cae438aef2a8633f99e10e0b90b11e
2121
static auto enableResizeLayoutSynchronization =
22-
Win32Helper::LoadSystemFunction<void WINAPI(HWND hwnd, BOOL enable)>(
22+
Win32Helper::LoadFunction<void WINAPI(HWND hwnd, BOOL enable)>(
2323
L"user32.dll", MAKEINTRESOURCEA(2615));
2424

2525
// 检查是否支持 IFrameworkApplicationPrivate 接口

tools/WindowCase/CursorWindow.cpp

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
#include "pch.h"
2+
#include "CursorWindow.h"
3+
4+
static HCURSOR CreateCursorFromBitmaps(HBITMAP hColorBitmap, HBITMAP hMaskBitmap) noexcept {
5+
ICONINFO iconInfo = {
6+
.fIcon = FALSE,
7+
.xHotspot = 0,
8+
.yHotspot = 0,
9+
.hbmMask = hMaskBitmap,
10+
.hbmColor = hColorBitmap
11+
};
12+
return CreateIconIndirect(&iconInfo);
13+
}
14+
15+
static HCURSOR CreateColorCursor() noexcept {
16+
const uint32_t width = (uint32_t)GetSystemMetrics(SM_CXCURSOR);
17+
const uint32_t height = (uint32_t)GetSystemMetrics(SM_CYCURSOR);
18+
19+
// 颜色位图初始化为半透明红色
20+
std::vector<uint32_t> colorBits(width * height, 0x80FF0000);
21+
22+
wil::unique_hbitmap hColorBitmap(CreateBitmap(width, height, 1, 32, colorBits.data()));
23+
// 仍需要遮罩位图,不过既然颜色位图有透明通道,遮罩位图不会被使用,因此无需初始化
24+
wil::unique_hbitmap hMaskBitmap(CreateBitmap(width, height, 1, 1, nullptr));
25+
return CreateCursorFromBitmaps(hColorBitmap.get(), hMaskBitmap.get());
26+
}
27+
28+
static HCURSOR CreateMonochromeCursor() noexcept {
29+
const uint32_t width = (uint32_t)GetSystemMetrics(SM_CXCURSOR);
30+
const uint32_t height = (uint32_t)GetSystemMetrics(SM_CYCURSOR);
31+
32+
// width 必定是 16 的倍数,因此遮罩位图每行都已按 WORD 对齐
33+
assert(width % 16 == 0);
34+
const uint32_t widthByteCount = width / 8;
35+
// 单色光标无颜色位图,遮罩位图高度是光标高度的两倍
36+
const uint32_t bitmapHeight = height * 2;
37+
38+
// 分为四个部分:
39+
// 左上:白色 (AND=0, XOR=1)
40+
// 右上:黑色 (AND=0, XOR=0)
41+
// 左下:反色 (AND=1, XOR=1)
42+
// 右下:透明 (AND=1, XOR=0)
43+
assert(width % 16 == 0);
44+
std::vector<uint8_t> maskBits(widthByteCount * bitmapHeight);
45+
46+
// AND 遮罩下半置 1
47+
for (uint32_t y = height / 2; y < height; ++y) {
48+
for (uint32_t x = 0; x < width; ++x) {
49+
uint32_t byteIdx = y * widthByteCount + x / 8;
50+
uint32_t bitIdx = 7 - (x % 8);
51+
maskBits[byteIdx] |= 1 << bitIdx;
52+
}
53+
}
54+
55+
// XOR 遮罩左半置 1
56+
for (uint32_t y = height; y < bitmapHeight; ++y) {
57+
for (uint32_t x = 0; x < width / 2; ++x) {
58+
uint32_t byteIdx = y * widthByteCount + x / 8;
59+
uint32_t bitIdx = 7 - (x % 8);
60+
maskBits[byteIdx] |= 1 << bitIdx;
61+
}
62+
}
63+
64+
wil::unique_hbitmap hMaskBitmap(CreateBitmap(width, bitmapHeight, 1, 1, maskBits.data()));
65+
return CreateCursorFromBitmaps(NULL, hMaskBitmap.get());
66+
}
67+
68+
static HCURSOR CreateMaskedColorCursor() noexcept {
69+
const uint32_t width = (uint32_t)GetSystemMetrics(SM_CXCURSOR);
70+
const uint32_t height = (uint32_t)GetSystemMetrics(SM_CYCURSOR);
71+
72+
// width 必定是 16 的倍数,因此遮罩位图每行都已按 WORD 对齐
73+
assert(width % 16 == 0);
74+
const uint32_t widthByteCount = width / 8;
75+
76+
// 分为两个部分:
77+
// 左半:红色 (COLOR=0x00FF0000, MASK=0)
78+
// 右半:和红色 XOR (COLOR=0x00FF0000, MASK=1)
79+
80+
// 颜色位图初始化为红色。注意透明通道为 0,否则会被识别为彩色光标,遮罩位图被忽略
81+
std::vector<uint32_t> colorBits(width * height, 0x00FF0000);
82+
std::vector<uint8_t> maskBits(widthByteCount * height, 0);
83+
84+
// XOR 遮罩右半置 1
85+
for (uint32_t y = 0; y < height; ++y) {
86+
for (uint32_t x = width / 2; x < width; ++x) {
87+
uint32_t byteIdx = y * widthByteCount + x / 8;
88+
uint32_t bitIdx = 7 - (x % 8);
89+
maskBits[byteIdx] |= 1 << bitIdx;
90+
}
91+
}
92+
93+
wil::unique_hbitmap hColorBitmap(CreateBitmap(width, height, 1, 32, colorBits.data()));
94+
wil::unique_hbitmap hMaskBitmap(CreateBitmap(width, height, 1, 1, maskBits.data()));
95+
return CreateCursorFromBitmaps(hColorBitmap.get(), hMaskBitmap.get());
96+
}
97+
98+
bool CursorWindow::Create() noexcept {
99+
static const wchar_t* WINDOW_NAME = L"CursorWindow";
100+
101+
_hCursor = CreateColorCursor();
102+
103+
WNDCLASSEXW wcex = {
104+
.cbSize = sizeof(WNDCLASSEX),
105+
.style = CS_HREDRAW | CS_VREDRAW,
106+
.lpfnWndProc = _WndProc,
107+
.hInstance = wil::GetModuleInstanceHandle(),
108+
.lpszClassName = WINDOW_NAME
109+
};
110+
if (!RegisterClassEx(&wcex)) {
111+
return false;
112+
}
113+
114+
CreateWindow(
115+
WINDOW_NAME,
116+
WINDOW_NAME,
117+
WS_OVERLAPPEDWINDOW,
118+
CW_USEDEFAULT,
119+
CW_USEDEFAULT,
120+
CW_USEDEFAULT,
121+
CW_USEDEFAULT,
122+
NULL,
123+
NULL,
124+
wil::GetModuleInstanceHandle(),
125+
this
126+
);
127+
if (!Handle()) {
128+
return false;
129+
}
130+
131+
const double dpiScale = _DpiScale();
132+
SetWindowPos(Handle(), NULL, 0, 0,
133+
std::lround(500 * dpiScale), std::lround(400 * dpiScale),
134+
SWP_NOMOVE | SWP_SHOWWINDOW);
135+
return true;
136+
}
137+
138+
LRESULT CursorWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) noexcept {
139+
switch (msg) {
140+
case WM_CREATE:
141+
{
142+
const LRESULT ret = base_type::_MessageHandler(msg, wParam, lParam);
143+
144+
const HMODULE hInst = wil::GetModuleInstanceHandle();
145+
_hwndBtn1 = CreateWindow(L"BUTTON", L"彩色光标",
146+
WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, Handle(), (HMENU)1, hInst, 0);
147+
_hwndBtn2 = CreateWindow(L"BUTTON", L"单色光标",
148+
WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, Handle(), (HMENU)2, hInst, 0);
149+
_hwndBtn3 = CreateWindow(L"BUTTON", L"彩色掩码光标",
150+
WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, Handle(), (HMENU)3, hInst, 0);
151+
_UpdateButtonPos();
152+
153+
SendMessage(_hwndBtn1, WM_SETFONT, (WPARAM)_UIFont(), TRUE);
154+
SendMessage(_hwndBtn2, WM_SETFONT, (WPARAM)_UIFont(), TRUE);
155+
SendMessage(_hwndBtn3, WM_SETFONT, (WPARAM)_UIFont(), TRUE);
156+
157+
return ret;
158+
}
159+
case WM_COMMAND:
160+
{
161+
if (HIWORD(wParam) == BN_CLICKED) {
162+
const WORD btnId = LOWORD(wParam);
163+
if (btnId == 1) {
164+
_hCursor = CreateColorCursor();
165+
} else if (btnId == 2) {
166+
_hCursor = CreateMonochromeCursor();
167+
} else {
168+
_hCursor = CreateMaskedColorCursor();
169+
}
170+
171+
return 0;
172+
}
173+
break;
174+
}
175+
case WM_SIZE:
176+
{
177+
_UpdateButtonPos();
178+
break;
179+
}
180+
case WM_SETCURSOR:
181+
{
182+
if (LOWORD(lParam) == HTCLIENT) {
183+
SetCursor(_hCursor);
184+
return TRUE;
185+
}
186+
break;
187+
}
188+
case WM_ERASEBKGND:
189+
{
190+
// 绘制渐变背景
191+
PAINTSTRUCT ps;
192+
HDC hdc = BeginPaint(Handle(), &ps);
193+
194+
RECT rc;
195+
GetClientRect(Handle(), &rc);
196+
197+
TRIVERTEX vertices[] = {
198+
{
199+
.x = rc.left,
200+
.y = rc.top,
201+
.Red = 0xE000,
202+
.Green = 0x6000,
203+
.Blue = 0xE000
204+
},
205+
{
206+
.x = rc.right,
207+
.y = rc.bottom,
208+
.Red = 0x6000,
209+
.Green = 0xE000,
210+
.Blue = 0x6000
211+
}
212+
};
213+
GRADIENT_RECT rect = { 0, 1 };
214+
GradientFill(hdc, vertices, 2, &rect, 1, GRADIENT_FILL_RECT_V);
215+
216+
EndPaint(Handle(), &ps);
217+
return TRUE;
218+
}
219+
case WM_DESTROY:
220+
PostQuitMessage(0);
221+
break;
222+
}
223+
224+
return base_type::_MessageHandler(msg, wParam, lParam);
225+
}
226+
227+
void CursorWindow::_UpdateButtonPos() noexcept {
228+
RECT clientRect;
229+
GetClientRect(Handle(), &clientRect);
230+
231+
const double dpiScale = _DpiScale();
232+
const SIZE btnSize = { std::lround(160 * dpiScale),std::lround(40 * dpiScale) };
233+
const LONG spacing = std::lround(8 * dpiScale);
234+
235+
const LONG btnLeft = ((clientRect.right - clientRect.left) - btnSize.cx) / 2;
236+
const LONG windowCenterY = (clientRect.bottom - clientRect.top) / 2;
237+
238+
SetWindowPos(_hwndBtn1, NULL, btnLeft, windowCenterY - 3 * btnSize.cy / 2 - spacing,
239+
btnSize.cx, btnSize.cy, SWP_NOACTIVATE);
240+
SetWindowPos(_hwndBtn2, NULL, btnLeft, windowCenterY - btnSize.cy / 2,
241+
btnSize.cx, btnSize.cy, SWP_NOACTIVATE);
242+
SetWindowPos(_hwndBtn3, NULL, btnLeft, windowCenterY + btnSize.cy / 2 + spacing,
243+
btnSize.cx, btnSize.cy, SWP_NOACTIVATE);
244+
}

0 commit comments

Comments
 (0)