mirror of
https://github.com/Blinue/Magpie.git
synced 2026-06-24 02:04:10 +00:00
优化光标区域裁剪 (#947)
* feat: 减少 ClipCursor 调用,以及支持源窗口限制鼠标 * fix: 正确还原原始光标裁剪区域,以及添加注释 * fix: 避免直接调用 ClipCursor * chore: 更新注释
This commit is contained in:
parent
d3c2e521db
commit
e6c1907eff
2 changed files with 95 additions and 44 deletions
|
|
@ -81,51 +81,63 @@ static POINT ScalingToSrc(POINT pt) noexcept {
|
|||
// 避免了并发问题。如果设置不成功则多次尝试。这里旨在尽最大努力,我怀疑是否有完美
|
||||
// 的解决方案。
|
||||
static void ReliableSetCursorPos(POINT pos) noexcept {
|
||||
const int screenWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);
|
||||
const int screenHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
|
||||
// 检查光标的限制区域,如果要设置的位置不在限制区域内则回落到 SetCursorPos
|
||||
RECT clipRect;
|
||||
GetClipCursor(&clipRect);
|
||||
|
||||
INPUT input{
|
||||
.type = INPUT_MOUSE,
|
||||
.mi{
|
||||
.dx = (pos.x * 65535) / (screenWidth - 1),
|
||||
.dy = (pos.y * 65535) / (screenHeight - 1),
|
||||
.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK
|
||||
}
|
||||
};
|
||||
|
||||
// 如果设置不成功则多次尝试
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
if (!SendInput(1, &input, sizeof(input))) {
|
||||
Logger::Get().Win32Error("SendInput 失败");
|
||||
break;
|
||||
}
|
||||
|
||||
// 等待系统处理
|
||||
Sleep(0);
|
||||
|
||||
POINT curCursorPos;
|
||||
if (!GetCursorPos(&curCursorPos)) {
|
||||
Logger::Get().Win32Error("GetCursorPos 失败");
|
||||
break;
|
||||
}
|
||||
|
||||
if (curCursorPos == pos) {
|
||||
// 已成功,但保险起见再设置一次
|
||||
SendInput(1, &input, sizeof(input));
|
||||
return;
|
||||
if (PtInRect(&clipRect, pos)) {
|
||||
const int screenWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);
|
||||
const int screenHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
|
||||
|
||||
INPUT input{
|
||||
.type = INPUT_MOUSE,
|
||||
.mi{
|
||||
.dx = (pos.x * 65535) / (screenWidth - 1),
|
||||
.dy = (pos.y * 65535) / (screenHeight - 1),
|
||||
.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK
|
||||
}
|
||||
};
|
||||
|
||||
// 如果设置不成功则多次尝试
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
if (!SendInput(1, &input, sizeof(input))) {
|
||||
Logger::Get().Win32Error("SendInput 失败");
|
||||
break;
|
||||
}
|
||||
|
||||
// 等待系统处理
|
||||
Sleep(0);
|
||||
|
||||
POINT curCursorPos;
|
||||
if (!GetCursorPos(&curCursorPos)) {
|
||||
Logger::Get().Win32Error("GetCursorPos 失败");
|
||||
break;
|
||||
}
|
||||
|
||||
if (curCursorPos == pos) {
|
||||
// 已成功,但保险起见再设置一次
|
||||
SendInput(1, &input, sizeof(input));
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 多次不成功回落到 SetCursorPos
|
||||
}
|
||||
|
||||
// 回落到 SetCursorPos
|
||||
SetCursorPos(pos.x, pos.y);
|
||||
}
|
||||
|
||||
CursorManager::~CursorManager() noexcept {
|
||||
if (ScalingWindow::Get().Options().IsDebugMode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_ShowSystemCursor(true, true);
|
||||
|
||||
ClipCursor(nullptr);
|
||||
if (_lastClip.left != std::numeric_limits<LONG>::max()) {
|
||||
_RestoreClipCursor();
|
||||
}
|
||||
|
||||
if (_isUnderCapture && !ScalingWindow::Get().Options().IsDebugMode()) {
|
||||
if (_isUnderCapture) {
|
||||
POINT cursorPos;
|
||||
if (!GetCursorPos(&cursorPos)) {
|
||||
Logger::Get().Win32Error("GetCursorPos 失败");
|
||||
|
|
@ -413,25 +425,18 @@ void CursorManager::_UpdateCursorClip() noexcept {
|
|||
// 3. 常规: 根据多屏幕限制光标,捕获/取消捕获,支持 UI 和多屏幕
|
||||
|
||||
if (options.IsDebugMode()) {
|
||||
if (_isCapturedOnOverlay) {
|
||||
// 光标被叠加层捕获时将光标限制在输出区域内
|
||||
ClipCursor(&destRect);
|
||||
} else {
|
||||
ClipCursor(nullptr);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.Is3DGameMode()) {
|
||||
// 开启“在 3D 游戏中限制光标”则每帧都限制一次光标
|
||||
ClipCursor(&srcRect);
|
||||
_SetClipCursor(srcRect, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_isCapturedOnOverlay) {
|
||||
// 光标被叠加层捕获时将光标限制在输出区域内
|
||||
ClipCursor(&destRect);
|
||||
_SetClipCursor(destRect);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -445,7 +450,7 @@ void CursorManager::_UpdateCursorClip() noexcept {
|
|||
|
||||
// 如果光标不在缩放窗口内不应限制光标
|
||||
if (_isUnderCapture) {
|
||||
ClipCursor(&srcRect);
|
||||
_SetClipCursor(srcRect);
|
||||
}
|
||||
|
||||
// 当光标被前台窗口捕获时我们除了限制光标外什么也不做,即光标
|
||||
|
|
@ -650,9 +655,10 @@ void CursorManager::_UpdateCursorClip() noexcept {
|
|||
clips.bottom = _isUnderCapture ? srcRect.bottom : destRect.bottom;
|
||||
}
|
||||
|
||||
ClipCursor(&clips);
|
||||
} else {
|
||||
ClipCursor(nullptr);
|
||||
_SetClipCursor(clips);
|
||||
} else if (_lastClip.left != std::numeric_limits<LONG>::max()) {
|
||||
_RestoreClipCursor();
|
||||
_lastClip = { std::numeric_limits<LONG>::max() };
|
||||
}
|
||||
|
||||
// SetCursorPos 应在 ClipCursor 之后,否则会受到上一次 ClipCursor 的影响
|
||||
|
|
@ -736,4 +742,42 @@ bool CursorManager::_StopCapture(POINT& cursorPos, bool onDestroy) noexcept {
|
|||
}
|
||||
}
|
||||
|
||||
void CursorManager::_SetClipCursor(const RECT& clipRect, bool is3DGameMode) noexcept {
|
||||
RECT curClip;
|
||||
GetClipCursor(&curClip);
|
||||
|
||||
// 如果当前光标裁剪区域与我们之前设置的不同,肯定是其他程序更改了,记录此原始裁剪区域
|
||||
// 用于后续计算和还原。
|
||||
// 如果我们没有裁剪光标区域,则 _lastClip.left == std::numeric_limits<LONG>::max(),
|
||||
// curClip != _lastClip 始终为真。
|
||||
// 但如果其他程序恰好设置了和我们相同的裁剪区域怎么办?
|
||||
if (curClip != _lastClip) {
|
||||
_originClip = curClip;
|
||||
}
|
||||
|
||||
// 为了尊重原始裁剪区域,计算和我们的裁剪区域的交集。除非两个区域不相交或光标在叠加层上
|
||||
RECT targetClip;
|
||||
if (_isOnOverlay || !IntersectRect(&targetClip, &_originClip, &clipRect)) {
|
||||
// 不相交则不再尊重原始裁剪区域,这不太可能发生
|
||||
targetClip = clipRect;
|
||||
}
|
||||
|
||||
// 裁剪区域变化了才调用 ClipCursor。每次调用 ClipCursor 都会向前台窗口发送 WM_MOUSEMOVE
|
||||
// 消息,一些程序无法正确处理,如 GH#920 和 GH#927
|
||||
if (targetClip != _lastClip || is3DGameMode) {
|
||||
ClipCursor(&targetClip);
|
||||
_lastClip = targetClip;
|
||||
}
|
||||
}
|
||||
|
||||
void CursorManager::_RestoreClipCursor() const noexcept {
|
||||
RECT curClip;
|
||||
GetClipCursor(&curClip);
|
||||
|
||||
// 如果 curClip != _lastClip,则其他程序已经更改光标裁剪区域,我们放弃更改
|
||||
if (curClip == _lastClip && curClip != _originClip) {
|
||||
ClipCursor(&_originClip);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,9 +48,16 @@ private:
|
|||
|
||||
bool _StopCapture(POINT& cursorPos, bool onDestroy = false) noexcept;
|
||||
|
||||
void _SetClipCursor(const RECT& clipRect, bool is3DGameMode = false) noexcept;
|
||||
|
||||
void _RestoreClipCursor() const noexcept;
|
||||
|
||||
HCURSOR _hCursor = NULL;
|
||||
POINT _cursorPos { std::numeric_limits<LONG>::max(),std::numeric_limits<LONG>::max() };
|
||||
|
||||
RECT _originClip{ std::numeric_limits<LONG>::max() };
|
||||
RECT _lastClip{ std::numeric_limits<LONG>::max() };
|
||||
|
||||
int _originCursorSpeed = 0;
|
||||
|
||||
bool _isUnderCapture = false;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue