进一步优化对弹窗的兼容性 (#1212)

* feat: 进一步优化对弹窗的兼容性

* fix

* fix: 调试模式只跳过置顶
This commit is contained in:
Xu 2025-07-24 18:52:51 +08:00 committed by GitHub
commit 06ffbf76f7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 103 additions and 53 deletions

View file

@ -347,18 +347,18 @@ void ScalingWindow::SwitchToolbarState() noexcept {
}
void ScalingWindow::Render() noexcept {
const bool originIsSrcFocused = _srcTracker.IsFocused();
bool isSrcRepositioning = false;
if (!_UpdateSrcState(isSrcRepositioning)) {
bool srcFocusedChanged = false;
bool srcOwnedWindowFocusedChanged = false;
if (!_UpdateSrcState(isSrcRepositioning, srcFocusedChanged, srcOwnedWindowFocusedChanged)) {
Logger::Get().Info("源窗口状态改变");
_DelayedStop(false, isSrcRepositioning);
return;
}
if (_srcTracker.IsFocused() != originIsSrcFocused) {
_UpdateFocusStateAsync();
}
if (srcFocusedChanged || srcOwnedWindowFocusedChanged) {
_UpdateFocusStateAsync(!srcFocusedChanged && srcOwnedWindowFocusedChanged, false);
}
// 虽然可以在第一帧渲染完成后再隐藏系统光标,但某些设备上显示窗口时光标状态会变成忙,
// 提前隐藏光标可以提高观感。缩放窗口显示后再隐藏光标还可能造成光标闪烁两次,第一次是
@ -1174,7 +1174,7 @@ void ScalingWindow::_Show() noexcept {
// 如果源窗口位于前台则将缩放窗口置顶
if (_srcTracker.IsFocused()) {
_UpdateFocusStateAsync(true);
_UpdateFocusStateAsync(false, true);
}
if (_options.IsTouchSupportEnabled()) {
@ -1228,7 +1228,11 @@ void ScalingWindow::_MoveRenderer() noexcept {
}
}
bool ScalingWindow::_UpdateSrcState(bool& isSrcRepositioning) noexcept {
bool ScalingWindow::_UpdateSrcState(
bool& isSrcRepositioning,
bool& srcFocusedChanged,
bool& srcOwnedWindowFocusedChanged
) noexcept {
HWND hwndFore = GetForegroundWindow();
if (hwndFore == Handle()) {
@ -1247,8 +1251,9 @@ bool ScalingWindow::_UpdateSrcState(bool& isSrcRepositioning) noexcept {
bool srcRectChanged = false;
bool srcSizeChanged = false;
bool srcMovingChanged = false;
if (!_srcTracker.UpdateState(hwndFore, _options.IsWindowedMode(),
_isResizingOrMoving, srcRectChanged, srcSizeChanged, srcMovingChanged)) {
if (!_srcTracker.UpdateState(hwndFore, _options.IsWindowedMode(), _isResizingOrMoving,
srcFocusedChanged, srcOwnedWindowFocusedChanged,
srcRectChanged, srcSizeChanged, srcMovingChanged)) {
return false;
}
@ -1803,14 +1808,19 @@ void ScalingWindow::_UpdateFrameMargins() const noexcept {
DwmExtendFrameIntoClientArea(Handle(), &margins);
}
winrt::fire_and_forget ScalingWindow::_UpdateFocusStateAsync(bool onShow) const noexcept {
if (_options.IsWindowedMode()) {
winrt::fire_and_forget ScalingWindow::_UpdateFocusStateAsync(
bool onSrcOwnedWindowFocusedChanged,
bool onShow
) const noexcept {
if (!onSrcOwnedWindowFocusedChanged && _options.IsWindowedMode()) {
// 根据源窗口状态绘制非客户区,我们必须自己控制非客户区是绘制成焦点状态还是非焦点
// 状态,因为缩放窗口实际上永远不会得到焦点。
DefWindowProc(Handle(), WM_NCACTIVATE, _srcTracker.IsFocused(), 0);
}
if (!_options.IsDebugMode() && (_srcTracker.IsOwnedWindowFocused() || !_options.IsWindowedMode())) {
if (_srcTracker.IsOwnedWindowFocused() ||
(!onSrcOwnedWindowFocusedChanged && !_options.IsWindowedMode()))
{
if (!onShow) {
const uint32_t runId = RunId();
@ -1834,12 +1844,14 @@ winrt::fire_and_forget ScalingWindow::_UpdateFocusStateAsync(bool onShow) const
const HWND hwndPrev = GetWindow(_srcTracker.Handle(), GW_HWNDPREV);
SetWindowPos(Handle(), hwndPrev,
0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
} else if (!_options.IsWindowedMode()) {
} else if (!onSrcOwnedWindowFocusedChanged && !_options.IsWindowedMode()) {
// 源窗口位于前台时将缩放窗口置顶,这使不支持 MPO 的显卡更容易激活 DirectFlip
if (_srcTracker.IsFocused()) {
SetWindowPos(Handle(), HWND_TOPMOST,
0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
// 再次调用 SetWindowPos 确保缩放窗口在所有置顶窗口之上
if (!_options.IsDebugMode()) {
SetWindowPos(Handle(), HWND_TOPMOST,
0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
}
// 非调试模式时再次调用 SetWindowPos 确保缩放窗口在所有置顶窗口之上
SetWindowPos(Handle(), HWND_TOP,
0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
} else {
@ -1849,11 +1861,13 @@ winrt::fire_and_forget ScalingWindow::_UpdateFocusStateAsync(bool onShow) const
}
}
if (_srcTracker.IsFocused()) {
PostMessage(HWND_BROADCAST, WM_MAGPIE_SCALINGCHANGED, 1, (LPARAM)Handle());
} else {
// lParam 传 1 表示转到后台而非结束缩放
PostMessage(HWND_BROADCAST, WM_MAGPIE_SCALINGCHANGED, 0, 1);
if (!onSrcOwnedWindowFocusedChanged) {
if (_srcTracker.IsFocused()) {
PostMessage(HWND_BROADCAST, WM_MAGPIE_SCALINGCHANGED, 1, (LPARAM)Handle());
} else {
// lParam 传 1 表示转到后台而非结束缩放
PostMessage(HWND_BROADCAST, WM_MAGPIE_SCALINGCHANGED, 0, 1);
}
}
}

View file

@ -106,7 +106,11 @@ private:
void _Show() noexcept;
bool _UpdateSrcState(bool& isSrcRepositioning) noexcept;
bool _UpdateSrcState(
bool& isSrcRepositioning,
bool& srcFocusedChanged,
bool& srcOwnedWindowFocusedChanged
) noexcept;
bool _CheckForegroundFor3DGameMode(HWND hwndFore) const noexcept;
@ -136,7 +140,10 @@ private:
void _UpdateFrameMargins() const noexcept;
winrt::fire_and_forget _UpdateFocusStateAsync(bool onShow = false) const noexcept;
winrt::fire_and_forget _UpdateFocusStateAsync(
bool onSrcOwnedWindowFocusedChanged,
bool onShow
) const noexcept;
bool _IsBorderless() const noexcept;

View file

@ -77,7 +77,9 @@ ScalingError SrcTracker::Set(HWND hWnd, const ScalingOptions& options) noexcept
return ScalingError::InvalidSourceWindow;
}
_isFocused = GetForegroundWindow() == hWnd;
const HWND hwndFore = GetForegroundWindow();
_isFocused = hwndFore == hWnd;
_UpdateIsOwnedWindowFocused(hwndFore);
if (!GetWindowRect(hWnd, &_windowRect)) {
Logger::Get().Win32Error("GetWindowRect 失败");
@ -159,16 +161,6 @@ ScalingError SrcTracker::Set(HWND hWnd, const ScalingOptions& options) noexcept
return _CalcSrcRect(options, borderThicknessInFrame);
}
static bool IsOwnedWindow(HWND hwndOwner, HWND hwndTest) noexcept {
HWND hwndCur = hwndTest;
while (bool(hwndCur = GetWindowOwner(hwndCur))) {
if (hwndCur == hwndOwner) {
return true;
}
}
return false;
}
static bool IsPrimaryMouseButtonDown() noexcept {
const bool isSwapped = GetSystemMetrics(SM_SWAPBUTTON);
const int vkPrimary = isSwapped ? VK_RBUTTON : VK_LBUTTON;
@ -179,11 +171,13 @@ bool SrcTracker::UpdateState(
HWND hwndFore,
bool isWindowedMode,
bool isResizingOrMoving,
bool& srcRectChanged,
bool& srcSizeChanged,
bool& srcMovingChanged
bool& focusedChanged,
bool& ownedWindowFocusedChanged,
bool& rectChanged,
bool& sizeChanged,
bool& movingChanged
) noexcept {
assert(!srcRectChanged && !srcSizeChanged && !srcMovingChanged);
assert(!focusedChanged && !ownedWindowFocusedChanged && !rectChanged && !sizeChanged && !movingChanged);
if (!IsWindow(_hWnd)) {
Logger::Get().Error("源窗口已销毁");
@ -204,8 +198,12 @@ bool SrcTracker::UpdateState(
return false;
}
_isFocused = hwndFore == _hWnd;
_isOwnedWindowFocused = !_isFocused && IsOwnedWindow(_hWnd, hwndFore);
if (_isFocused != (hwndFore == _hWnd)) {
_isFocused = !_isFocused;
focusedChanged = true;
}
ownedWindowFocusedChanged = _UpdateIsOwnedWindowFocused(hwndFore);
const bool oldMaximized = _isMaximized;
UINT showCmd = Win32Helper::GetWindowShowCmd(_hWnd);
@ -221,19 +219,19 @@ bool SrcTracker::UpdateState(
return false;
}
srcSizeChanged = oldMaximized != _isMaximized ||
sizeChanged = oldMaximized != _isMaximized ||
Win32Helper::GetSizeOfRect(curWindowRect) != Win32Helper::GetSizeOfRect(_windowRect);
// 缩放窗口正在调整大小或被拖动时源窗口的移动是异步的,暂时不检查源窗口是否移动
if (isResizingOrMoving) {
srcRectChanged = oldMaximized != _isMaximized;
rectChanged = oldMaximized != _isMaximized;
return true;
}
// 最大化状态改变视为尺寸发生变化
srcRectChanged = oldMaximized != _isMaximized || curWindowRect != _windowRect;
rectChanged = oldMaximized != _isMaximized || curWindowRect != _windowRect;
if (isWindowedMode && !srcSizeChanged) {
if (isWindowedMode && !sizeChanged) {
bool isMoving = false;
GUITHREADINFO guiThreadInfo{ .cbSize = sizeof(GUITHREADINFO) };
if (GetGUIThreadInfo(GetWindowThreadProcessId(_hWnd, nullptr), &guiThreadInfo)) {
@ -244,11 +242,11 @@ bool SrcTracker::UpdateState(
// 处理自己实现拖拽逻辑的窗口:将鼠标左键按下视为开始拖拽,释放视为拖拽结束。
// 可能会有误判,但幸好后果不太严重。
if (_isMoving || (!_isMoving && srcRectChanged)) {
if (_isMoving || (!_isMoving && rectChanged)) {
isMoving = isMoving || IsPrimaryMouseButtonDown();
}
if (srcRectChanged) {
if (rectChanged) {
const LONG offsetX = curWindowRect.left - _windowRect.left;
const LONG offsetY = curWindowRect.top - _windowRect.top;
Win32Helper::OffsetRect(_windowFrameRect, offsetX, offsetY);
@ -256,12 +254,12 @@ bool SrcTracker::UpdateState(
}
if (_isMoving != isMoving) {
srcMovingChanged = true;
movingChanged = true;
_isMoving = isMoving;
}
}
if (srcRectChanged) {
if (rectChanged) {
_windowRect = curWindowRect;
}
@ -422,6 +420,10 @@ static bool GetClientRectOfUWP(HWND hWnd, RECT& rect) noexcept {
}
bool SrcTracker::SetFocus() const noexcept {
if (_isOwnedWindowFocused) {
return true;
}
// 如果源窗口存在弹窗(即被源窗口所有的窗口),应把弹窗设为前台窗口
const HWND hwndPopup = GetWindow(_hWnd, GW_ENABLEDPOPUP);
return SetForegroundWindow(hwndPopup ? hwndPopup : _hWnd);
@ -488,4 +490,27 @@ ScalingError SrcTracker::_CalcSrcRect(const ScalingOptions& options, LONG border
return ScalingError::NoError;
}
static bool IsOwnedWindow(HWND hwndOwner, HWND hwndTest) noexcept {
HWND hwndCur = hwndTest;
while (bool(hwndCur = GetWindowOwner(hwndCur))) {
if (hwndCur == hwndOwner) {
return true;
}
}
return false;
}
bool SrcTracker::_UpdateIsOwnedWindowFocused(HWND hwndFore) noexcept {
// 支持两种形式的弹窗
// 1. 弹窗被源窗口所有
// 2. 弹窗没有被源窗口所有,但弹出时源窗口被禁用
bool newValue = !_isFocused && (IsOwnedWindow(_hWnd, hwndFore) || !IsWindowEnabled(_hWnd));
if (_isOwnedWindowFocused == newValue) {
return false;
} else {
_isOwnedWindowFocused = newValue;
return true;
}
}
}

View file

@ -33,9 +33,11 @@ public:
HWND hwndFore,
bool isWindowedMode,
bool isResizingOrMoving,
bool& srcRectChanged,
bool& srcSizeChanged,
bool& srcMovingChanged
bool& focusedChanged,
bool& ownedWindowFocusedChanged,
bool& rectChanged,
bool& sizeChanged,
bool& movingChanged
) noexcept;
bool Move(int offsetX, int offsetY, bool async) noexcept;
@ -85,6 +87,8 @@ public:
private:
ScalingError _CalcSrcRect(const ScalingOptions& options, LONG borderThicknessInFrame) noexcept;
bool _UpdateIsOwnedWindowFocused(HWND hwndFore) noexcept;
HWND _hWnd = NULL;
RECT _windowRect{};
RECT _windowFrameRect{};

View file

@ -4,6 +4,7 @@
#include "CommonSharedConstants.h"
#include "JsonHelper.h"
#include "Logger.h"
#include "MainWindow.h"
#include "StrHelper.h"
#include "UpdateService.h"
#include "Version.h"
@ -11,7 +12,6 @@
#include <bcrypt.h>
#include <wil/resource.h> // 再次包含以激活 CNG 相关包装器
#include <rapidjson/document.h>
#include <shellapi.h>
#include <winrt/Windows.Storage.Streams.h>
#include <winrt/Windows.Web.Http.h>
#include <zip/zip.h>