mirror of
https://github.com/Blinue/Magpie.git
synced 2026-06-24 02:04:10 +00:00
241 lines
7.1 KiB
C++
241 lines
7.1 KiB
C++
#include "pch.h"
|
|
#include "CursorDrawer2.h"
|
|
#include "ScalingWindow.h"
|
|
#include "Win32Helper.h"
|
|
#include "Logger.h"
|
|
#include <ShellScalingApi.h>
|
|
#include <wil/registry.h>
|
|
|
|
namespace Magpie {
|
|
|
|
// 系统 DPI 在程序的生命周期内不会改变,而且使用 GetIconInfo 获得的位图尺寸
|
|
// 和此值有关。
|
|
static UINT SYSTEM_DPI;
|
|
|
|
static DWORD GetCursorBaseSize() noexcept {
|
|
DWORD cursorBaseSize = 32;
|
|
|
|
HRESULT hr = wil::reg::get_value_nothrow(
|
|
HKEY_CURRENT_USER, L"Control Panel\\Cursors", L"CursorBaseSize", &cursorBaseSize);
|
|
// 键不存在不视为错误
|
|
if (FAILED(hr) && hr != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) {
|
|
Logger::Get().ComError("wil::reg::get_value_dword_nothrow 失败", hr);
|
|
}
|
|
|
|
return cursorBaseSize;
|
|
}
|
|
|
|
bool CursorDrawer2::Initialize(GraphicsContext& graphicsContext, const RECT& destRect) noexcept {
|
|
_graphicsContext = &graphicsContext;
|
|
_destRect = destRect;
|
|
|
|
[[maybe_unused]] static Ignore _ = []() {
|
|
SYSTEM_DPI = GetDpiForSystem();
|
|
return Ignore();
|
|
}();
|
|
|
|
wil::unique_hkey key;
|
|
wil::reg::open_unique_key_nothrow(HKEY_CURRENT_USER, L"Control Panel\\Cursors", key);
|
|
_regWatcher = wil::make_registry_watcher_nothrow(std::move(key), false, [this](wil::RegistryChangeKind) {
|
|
ScalingWindow::Dispatcher().TryEnqueue(
|
|
[this, cursorBaseSize(GetCursorBaseSize()), runId(ScalingWindow::RunId())]() {
|
|
if (ScalingWindow::RunId() != runId || _cursorBaseSize == cursorBaseSize) {
|
|
return;
|
|
}
|
|
|
|
_cursorBaseSize = cursorBaseSize;
|
|
});
|
|
});
|
|
|
|
_cursorBaseSize = GetCursorBaseSize();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CursorDrawer2::CheckForRedraw(HCURSOR& hCursor, POINT cursorPos) noexcept {
|
|
const ScalingWindow& scalingWindow = ScalingWindow::Get();
|
|
const ScalingOptions& options = scalingWindow.Options();
|
|
|
|
// 检查自动隐藏光标
|
|
if (options.autoHideCursorDelay.has_value()) {
|
|
using namespace std::chrono;
|
|
|
|
// 光标在叠加层上或拖动窗口时禁用自动隐藏。光标处于隐藏状态视为形状不变,考虑形状
|
|
// 变化:箭头->隐藏->箭头,只要位置不变,自动隐藏功能应让光标始终隐藏;反之如果光
|
|
// 标隐藏时移动了或显示时形状变化了应正常显示。
|
|
if (_isCursorVirtualized &&
|
|
!_isMoving &&
|
|
!_isSrcMoving &&
|
|
_curCursorPos == cursorPos &&
|
|
(_lastRawCursorHandle == hCursor || !hCursor)
|
|
) {
|
|
const duration<float> hideDelay(*options.autoHideCursorDelay);
|
|
if (steady_clock::now() - _lastCursorActiveTime > hideDelay) {
|
|
hCursor = NULL;
|
|
}
|
|
} else {
|
|
// 启用自动隐藏时光标形状或位置变化后应记录新的形状、位置和变化时间。位置由
|
|
// _curCursorPos 记录。
|
|
_lastRawCursorHandle = hCursor;
|
|
_lastCursorActiveTime = steady_clock::now();
|
|
}
|
|
}
|
|
|
|
if (hCursor) {
|
|
if (_isCursorVisible) {
|
|
// 检查光标是否在视口内
|
|
const _CursorInfo* cursorInfo = _ResolveCursor(hCursor, cursorPos);
|
|
if (cursorInfo) {
|
|
const RECT cursorRect = {
|
|
cursorPos.x,
|
|
cursorPos.y,
|
|
cursorPos.x + (LONG)cursorInfo->size.width,
|
|
cursorPos.y + (LONG)cursorInfo->size.height
|
|
};
|
|
if (!Win32Helper::IsRectOverlap(cursorRect, _destRect)) {
|
|
hCursor = NULL;
|
|
}
|
|
} else {
|
|
Logger::Get().Error("_ResolveCursor 失败");
|
|
hCursor = NULL;
|
|
}
|
|
} else {
|
|
// 截屏时暂时不渲染光标
|
|
hCursor = NULL;
|
|
}
|
|
}
|
|
|
|
// 光标形状或位置变化时需要重新绘制
|
|
if (hCursor != _hCurCursor || (hCursor && cursorPos != _curCursorPos)) {
|
|
_hCurCursor = hCursor;
|
|
_curCursorPos = cursorPos;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void CursorDrawer2::Draw() noexcept {
|
|
|
|
}
|
|
|
|
static Size CalcCursorSize(
|
|
Size cursorBmpSize,
|
|
uint32_t cursorDpi,
|
|
uint32_t monitorDpi,
|
|
uint32_t cursorBaseSize
|
|
) noexcept {
|
|
const double scale = (GetSystemMetricsForDpi(SM_CXCURSOR, monitorDpi) * cursorBaseSize) /
|
|
double(GetSystemMetricsForDpi(SM_CXCURSOR, cursorDpi) * 32);
|
|
return { (uint32_t)std::lround(cursorBmpSize.width * scale),
|
|
(uint32_t)std::lround(cursorBmpSize.height * scale) };
|
|
}
|
|
|
|
const CursorDrawer2::_CursorInfo* CursorDrawer2::_ResolveCursor(
|
|
HCURSOR hCursor,
|
|
POINT cursorPos
|
|
) noexcept {
|
|
// 检索光标所在屏幕的 DPI
|
|
const HMONITOR hCurMon = MonitorFromPoint(
|
|
{ cursorPos.x + _destRect.left, cursorPos.y + _destRect.top }, MONITOR_DEFAULTTOPRIMARY);
|
|
UINT monitorDpi = USER_DEFAULT_SCREEN_DPI;
|
|
GetDpiForMonitor(hCurMon, MDT_EFFECTIVE_DPI, &monitorDpi, &monitorDpi);
|
|
|
|
auto it = _cursorInfos.find(std::make_pair(hCursor, monitorDpi));
|
|
if (it != _cursorInfos.end()) {
|
|
return &it->second;
|
|
}
|
|
|
|
// 检查此光标是否不随 DPI 缩放
|
|
it = _cursorInfos.find(std::make_pair(hCursor, 0));
|
|
if (it != _cursorInfos.end()) {
|
|
return &it->second;
|
|
}
|
|
|
|
ICONINFOEX iconInfoEx = { .cbSize = sizeof(iconInfoEx) };
|
|
if (!GetIconInfoEx(hCursor, &iconInfoEx)) {
|
|
Logger::Get().Win32Error("GetIconInfoEx 失败");
|
|
return nullptr;
|
|
}
|
|
|
|
wil::unique_hbitmap hColorBmp(iconInfoEx.hbmColor);
|
|
wil::unique_hbitmap hMaskBmp(iconInfoEx.hbmMask);
|
|
|
|
Size bmpSize;
|
|
{
|
|
BITMAP bmp{};
|
|
if (!GetObject(hColorBmp ? hColorBmp.get() : hMaskBmp.get(), sizeof(bmp), &bmp)) {
|
|
Logger::Get().Win32Error("GetObject 失败");
|
|
return nullptr;
|
|
}
|
|
|
|
// 单色光标的掩码位图高度是光标实际高度的两倍
|
|
bmpSize = {
|
|
uint32_t(bmp.bmWidth),
|
|
uint32_t(hColorBmp ? bmp.bmHeight : bmp.bmHeight / 2)
|
|
};
|
|
}
|
|
|
|
_CursorInfo cursorInfo{};
|
|
// 如果不能确定光标是否随 DPI 缩放则假设为真,绝大多数情况下是对的
|
|
bool isCursorDpiAware = true;
|
|
|
|
// 将线程 DPI 感知设为 unaware 后 GetIconInfo 可以获得 100% DPI 缩放下的光标位图。
|
|
// 我们借助这个特性检查光标是否随 DPI 缩放,不过只在程序启动时系统 DPI 缩放不是
|
|
// 100% 时有效。
|
|
if (SYSTEM_DPI == 96) {
|
|
cursorInfo.size = CalcCursorSize(bmpSize, SYSTEM_DPI, monitorDpi, _cursorBaseSize);
|
|
} else {
|
|
ICONINFO iconInfoDpi96{};
|
|
{
|
|
DPI_AWARENESS_CONTEXT oldDpiContext =
|
|
SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE);
|
|
auto se = wil::scope_exit([&] {
|
|
SetThreadDpiAwarenessContext(oldDpiContext);
|
|
});
|
|
|
|
if (!GetIconInfo(hCursor, &iconInfoDpi96)) {
|
|
Logger::Get().Win32Error("GetIconInfo 失败");
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
wil::unique_hbitmap hColorBmpDpi96(iconInfoDpi96.hbmColor);
|
|
wil::unique_hbitmap hMaskBmpDpi96(iconInfoDpi96.hbmMask);
|
|
|
|
Size bmpSizeDpi96;
|
|
{
|
|
BITMAP bmp{};
|
|
if (!GetObject(hColorBmpDpi96 ? hColorBmpDpi96.get() : hMaskBmpDpi96.get(), sizeof(bmp), &bmp)) {
|
|
Logger::Get().Win32Error("GetObject 失败");
|
|
return nullptr;
|
|
}
|
|
|
|
bmpSizeDpi96 = {
|
|
uint32_t(bmp.bmWidth),
|
|
uint32_t(hColorBmpDpi96 ? bmp.bmHeight : bmp.bmHeight / 2)
|
|
};
|
|
}
|
|
|
|
// 不同 DPI 下光标位图尺寸不变说明光标不跟随 DPI 缩放
|
|
if (bmpSize == bmpSizeDpi96) {
|
|
isCursorDpiAware = false;
|
|
cursorInfo.size = bmpSizeDpi96;
|
|
} else {
|
|
cursorInfo.size = CalcCursorSize(bmpSizeDpi96, 96, monitorDpi, _cursorBaseSize);
|
|
}
|
|
|
|
if (cursorInfo.size == bmpSizeDpi96) {
|
|
hColorBmp = std::move(hColorBmpDpi96);
|
|
hMaskBmp = std::move(hMaskBmpDpi96);
|
|
bmpSize = bmpSizeDpi96;
|
|
}
|
|
}
|
|
|
|
cursorInfo.resourceSize = bmpSize;
|
|
|
|
return &_cursorInfos.emplace(std::make_pair(hCursor, isCursorDpiAware ? monitorDpi : 0),
|
|
std::move(cursorInfo)).first->second;
|
|
}
|
|
|
|
}
|