feat: 源窗口隐藏会等待显示然后恢复缩放

This commit is contained in:
Xu 2025-08-03 17:14:21 +08:00
commit 96a1c7287a
5 changed files with 87 additions and 69 deletions

View file

@ -89,38 +89,39 @@ void ScalingRuntime::Stop() {
});
}
// 返回值:
// -1: 应取消缩放
// 0: 仍在调整中
// 1: 调整完毕
static int GetSrcRepositionState(HWND hwndSrc) noexcept {
static std::optional<bool> IsSrcRepositioning(HWND hwndSrc) noexcept {
if (!IsWindow(hwndSrc)) {
return -1;
Logger::Get().Info("源窗口已销毁");
return std::nullopt;
}
// 窗口不可见或最小化则继续等待。注意 showCmd 不能准确判断窗口可见性,
// 应使用 IsWindowVisible。
if (!IsWindowVisible(hwndSrc)) {
return true;
}
if (Win32Helper::IsWindowHung(hwndSrc)) {
return -1;
Logger::Get().Info("源窗口已挂起");
return std::nullopt;
}
const UINT showCmd = Win32Helper::GetWindowShowCmd(hwndSrc);
if (showCmd == SW_HIDE) {
return -1;
} else if (showCmd == SW_SHOWMAXIMIZED) {
if (showCmd == SW_SHOWMAXIMIZED) {
// 窗口最大化则尝试缩放,失败会显示错误消息
return 1;
return false;
} else if (showCmd == SW_SHOWMINIMIZED) {
// 窗口最小化则继续等待
return 0;
return true;
}
// 检查源窗口是否正在调整大小或移动
GUITHREADINFO guiThreadInfo{ .cbSize = sizeof(GUITHREADINFO) };
if (!GetGUIThreadInfo(GetWindowThreadProcessId(hwndSrc, nullptr), &guiThreadInfo)) {
Logger::Get().Win32Error("GetGUIThreadInfo 失败");
return -1;
return std::nullopt;
}
return (guiThreadInfo.flags & GUI_INMOVESIZE) ? 0 : 1;
return bool(guiThreadInfo.flags & GUI_INMOVESIZE);
}
void ScalingRuntime::_ScalingThreadProc() noexcept {
@ -162,7 +163,8 @@ void ScalingRuntime::_ScalingThreadProc() noexcept {
scalingWindow.Stop();
_IsScaling(false);
return;
} else if (msg.message == CommonSharedConstants::WM_FRONTEND_RENDER && msg.hwnd == scalingWindow.Handle()) {
} else if (msg.message == CommonSharedConstants::WM_FRONTEND_RENDER &&
msg.hwnd == scalingWindow.Handle()) {
// 缩放窗口收到 WM_FRONTEND_RENDER 将执行渲染
lastRenderTime = steady_clock::now();
}
@ -189,13 +191,16 @@ void ScalingRuntime::_ScalingThreadProc() noexcept {
const DWORD restMs = DWORD((rest.count() + ratio - 1) / ratio);
MsgWaitForMultipleObjectsEx(0, nullptr, restMs, QS_ALLINPUT, MWMO_INPUTAVAILABLE);
} else if (scalingWindow.IsSrcRepositioning()) {
const int state = GetSrcRepositionState(scalingWindow.SrcTracker().Handle());
if (state == 0) {
// 等待调整完成
MsgWaitForMultipleObjectsEx(0, nullptr, 10, QS_ALLINPUT, MWMO_INPUTAVAILABLE);
} else if (state == 1) {
// 重新缩放
ScalingWindow::Get().RestartAfterSrcRepositioned();
std::optional<bool> repositioning =
IsSrcRepositioning(scalingWindow.SrcTracker().Handle());
if (repositioning.has_value()) {
if (*repositioning) {
// 等待调整完成
MsgWaitForMultipleObjectsEx(0, nullptr, 10, QS_ALLINPUT, MWMO_INPUTAVAILABLE);
} else {
// 重新缩放
ScalingWindow::Get().RestartAfterSrcRepositioned();
}
} else {
// 取消缩放
ScalingWindow::Get().CleanAfterSrcRepositioned();

View file

@ -86,11 +86,20 @@ ScalingError ScalingWindow::_StartImpl(HWND hwndSrc) noexcept {
InitMessage();
if (ScalingError error = _srcTracker.Set(hwndSrc, _options); error != ScalingError::NoError) {
bool isSrcInvisibleOrMinimized = false;
if (ScalingError error = _srcTracker.Set(hwndSrc, _options, isSrcInvisibleOrMinimized);
error != ScalingError::NoError
) {
Logger::Get().Error("初始化 SrcTracker 失败");
return error;
}
if (isSrcInvisibleOrMinimized || (_srcTracker.IsMoving() && !_options.IsWindowedMode())) {
// 等待源窗口状态改变
_isSrcRepositioning = true;
return ScalingError::NoError;
}
if (_srcTracker.IsZoomed()) {
if (_options.IsWindowedMode()) {
Logger::Get().Info("已最大化的窗口不支持窗口模式缩放");
@ -101,11 +110,6 @@ ScalingError ScalingWindow::_StartImpl(HWND hwndSrc) noexcept {
}
}
if (_srcTracker.IsMoving() && !_options.IsWindowedMode()) {
_isSrcRepositioning = true;
return ScalingError::NoError;
}
[[maybe_unused]] static Ignore _ = []() {
WNDCLASSEXW wcex{
.cbSize = sizeof(wcex),
@ -1269,24 +1273,24 @@ bool ScalingWindow::_UpdateSrcState(
return false;
}
bool srcMinimized = false;
bool isSrcInvisibleOrMinimized = false;
bool srcRectChanged = false;
bool srcSizeChanged = false;
bool srcMovingChanged = false;
if (!_srcTracker.UpdateState(hwndFore, _options.IsWindowedMode(), _isResizingOrMoving,
srcFocusedChanged, srcOwnedWindowFocusedChanged,
srcMinimized, srcRectChanged, srcSizeChanged, srcMovingChanged)) {
isSrcInvisibleOrMinimized, srcFocusedChanged, srcOwnedWindowFocusedChanged,
srcRectChanged, srcSizeChanged, srcMovingChanged)) {
return false;
}
if (srcMinimized || srcSizeChanged || (!_options.IsWindowedMode() && srcRectChanged)) {
if (isSrcInvisibleOrMinimized || srcSizeChanged || (!_options.IsWindowedMode() && srcRectChanged)) {
// 不要立刻设置 _isSrcSizing销毁窗口是异步的
isSrcRepositioning = true;
if (srcSizeChanged) {
// 源窗口大小改变则清除记忆
_lastWindowedRendererWidth = 0;
} else if (srcMinimized) {
} else if (isSrcInvisibleOrMinimized) {
if (_options.IsWindowedMode()) {
_lastWindowedRendererWidth = _rendererRect.right - _rendererRect.left;
}

View file

@ -47,7 +47,9 @@ static bool IsWindowMoving(HWND hWnd) noexcept {
}
}
ScalingError SrcTracker::Set(HWND hWnd, const ScalingOptions& options) noexcept {
ScalingError SrcTracker::Set(HWND hWnd, const ScalingOptions& options, bool& isInvisibleOrMinimized) noexcept {
assert(!isInvisibleOrMinimized);
_hWnd = hWnd;
// 这里不检查源窗口是否挂起,将在创建缩放窗口前检查
@ -57,11 +59,21 @@ ScalingError SrcTracker::Set(HWND hWnd, const ScalingOptions& options) noexcept
return ScalingError::InvalidSourceWindow;
}
// 不可见和最小化的窗口将等待源窗口状态改变,这里提前返回。注意 showCmd 不能准确
// 判断窗口可见性,应使用 IsWindowVisible。
if (!IsWindowVisible(_hWnd)) {
Logger::Get().Error("不支持缩放隐藏的窗口");
return ScalingError::InvalidSourceWindow;
isInvisibleOrMinimized = true;
return ScalingError::NoError;
}
const UINT showCmd = Win32Helper::GetWindowShowCmd(hWnd);
if (showCmd == SW_SHOWMINIMIZED) {
isInvisibleOrMinimized = true;
return ScalingError::NoError;
}
_isMaximized = showCmd == SW_SHOWMAXIMIZED;
if (Win32Helper::GetWindowClassName(hWnd) == L"Ghost") {
Logger::Get().Error("不支持缩放幽灵窗口");
return ScalingError::InvalidSourceWindow;
@ -110,14 +122,6 @@ ScalingError SrcTracker::Set(HWND hWnd, const ScalingOptions& options) noexcept
return ScalingError::ScalingFailedGeneral;
}
const UINT showCmd = Win32Helper::GetWindowShowCmd(hWnd);
if (showCmd == SW_SHOWMINIMIZED) {
Logger::Get().Error("不支持缩放最小化的窗口");
return ScalingError::InvalidSourceWindow;
}
_isMaximized = showCmd == SW_SHOWMAXIMIZED;
// 计算窗口样式
BOOL hasBorder = TRUE;
hr = DwmGetWindowAttribute(hWnd, DWMWA_NCRENDERING_ENABLED, &hasBorder, sizeof(hasBorder));
@ -182,20 +186,23 @@ bool SrcTracker::UpdateState(
HWND hwndFore,
bool isWindowedMode,
bool isResizingOrMoving,
bool& isInvisibleOrMinimized,
bool& focusedChanged,
bool& ownedWindowFocusedChanged,
bool& minimized,
bool& rectChanged,
bool& sizeChanged,
bool& movingChanged
) noexcept {
assert(!focusedChanged && !ownedWindowFocusedChanged && !rectChanged && !sizeChanged && !movingChanged);
assert(!isInvisibleOrMinimized && !focusedChanged && !ownedWindowFocusedChanged &&
!rectChanged && !sizeChanged && !movingChanged);
if (!IsWindow(_hWnd)) {
Logger::Get().Info("源窗口已销毁");
return false;
}
isInvisibleOrMinimized = !IsWindowVisible(_hWnd);
// Win32Helper::IsWindowHung 更准确,但它会向源窗口发送消息,比较耗时。
// IsHungAppWindow 的另一个好处是它不如 Win32Helper::IsWindowHung 严
// 格,因此即使源窗口挂起一段时间,只要用户不做额外的操作就不会结束缩放,
@ -213,26 +220,30 @@ bool SrcTracker::UpdateState(
ownedWindowFocusedChanged = _UpdateIsOwnedWindowFocused(hwndFore);
const bool oldMaximized = _isMaximized;
const UINT showCmd = Win32Helper::GetWindowShowCmd(_hWnd);
if (showCmd == SW_HIDE) {
Logger::Get().Info("源窗口已隐藏");
WINDOWPLACEMENT wp{ sizeof(wp) };
if (!GetWindowPlacement(_hWnd, &wp)) {
Logger::Get().Win32Error("GetWindowPlacement 失败");
return false;
} else if (showCmd == SW_SHOWMINIMIZED) {
Logger::Get().Info("源窗口已最小化");
_isMaximized = false;
minimized = true;
} else {
_isMaximized = showCmd == SW_SHOWMAXIMIZED;
}
_isMaximized = wp.showCmd == SW_SHOWMAXIMIZED;
RECT curWindowRect;
if (minimized) {
WINDOWPLACEMENT wp{ sizeof(wp) };
if (!GetWindowPlacement(_hWnd, &wp)) {
Logger::Get().Win32Error("GetWindowPlacement 失败");
if (wp.showCmd == SW_SHOWMINIMIZED) {
isInvisibleOrMinimized = true;
// rcNormalPosition 使用工作区坐标,应转换为屏幕坐标
HMONITOR hMon = MonitorFromWindow(_hWnd, MONITOR_DEFAULTTOPRIMARY);
MONITORINFO mi{ sizeof(mi) };
if (!GetMonitorInfo(hMon, &mi)) {
Logger::Get().Win32Error("GetMonitorInfo 失败");
return false;
}
curWindowRect = wp.rcNormalPosition;
Win32Helper::OffsetRect(
curWindowRect, mi.rcWork.left - mi.rcMonitor.left, mi.rcWork.top - mi.rcMonitor.top);
} else {
if (!GetWindowRect(_hWnd, &curWindowRect)) {
Logger::Get().Win32Error("GetWindowRect 失败");
@ -266,7 +277,7 @@ bool SrcTracker::UpdateState(
// 处理自己实现拖拽逻辑的窗口:将鼠标左键按下视为开始拖拽,释放视为拖拽结束。
// 可能会有误判,但幸好后果不太严重。
const bool isMoving = !minimized &&
const bool isMoving = !isInvisibleOrMinimized &&
(IsWindowMoving(_hWnd) || (rectChanged && IsPrimaryMouseButtonDown()));
if (_isMoving != isMoving) {
movingChanged = true;

View file

@ -27,15 +27,15 @@ public:
SrcTracker(const SrcTracker&) = delete;
SrcTracker(SrcTracker&&) = delete;
ScalingError Set(HWND hWnd, const ScalingOptions& options) noexcept;
ScalingError Set(HWND hWnd, const ScalingOptions& options, bool& isInvisibleOrMinimized) noexcept;
bool UpdateState(
HWND hwndFore,
bool isWindowedMode,
bool isResizingOrMoving,
bool& isInvisibleOrMinimized,
bool& focusedChanged,
bool& ownedWindowFocusedChanged,
bool& minimized,
bool& rectChanged,
bool& sizeChanged,
bool& movingChanged

View file

@ -517,13 +517,11 @@ void AppSettings::_UpdateWindowPlacement() noexcept {
return;
}
const POINT workingAreaOffset = {
mi.rcWork.left - mi.rcMonitor.left,
mi.rcWork.top - mi.rcMonitor.top
};
const LONG workingAreaOffsetX = mi.rcWork.left - mi.rcMonitor.left;
const LONG workingAreaOffsetY = mi.rcWork.top - mi.rcMonitor.top;
_mainWindowCenter = {
(wp.rcNormalPosition.left + wp.rcNormalPosition.right) / 2.0f + workingAreaOffset.x,
(wp.rcNormalPosition.top + wp.rcNormalPosition.bottom) / 2.0f + workingAreaOffset.y,
(wp.rcNormalPosition.left + wp.rcNormalPosition.right) / 2.0f + workingAreaOffsetX,
(wp.rcNormalPosition.top + wp.rcNormalPosition.bottom) / 2.0f + workingAreaOffsetY,
};
const float dpiFactor = GetDpiForWindow(hwndMain) / float(USER_DEFAULT_SCREEN_DPI);