From 153e1f4f2fb711f5ec7072e0e49346fc76901cc3 Mon Sep 17 00:00:00 2001 From: SineStriker <55847490+SineStriker@users.noreply.github.com> Date: 周六, 26 4月 2025 13:41:20 +0800 Subject: [PATCH] Update --- src/core/contexts/win32windowcontext.cpp | 492 +++++++++++++++++++++++++++++++++++------------------ 1 files changed, 322 insertions(+), 170 deletions(-) diff --git a/src/core/contexts/win32windowcontext.cpp b/src/core/contexts/win32windowcontext.cpp index a75efe7..7ffd7da 100644 --- a/src/core/contexts/win32windowcontext.cpp +++ b/src/core/contexts/win32windowcontext.cpp @@ -33,6 +33,18 @@ #include "qwkglobal_p.h" #include "qwkwindowsextra_p.h" +#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)) && (QT_VERSION <= QT_VERSION_CHECK(6, 6, 1)) +# error Current Qt version has a critical bug which will break QWK functionality. Please upgrade to > 6.6.1 or downgrade to < 6.6.0 +#endif + +#ifndef DWM_BB_ENABLE +# define DWM_BB_ENABLE 0x00000001 +#endif + +#ifndef ABM_GETAUTOHIDEBAREX +# define ABM_GETAUTOHIDEBAREX 0x0000000b +#endif + namespace QWK { enum IconButtonClickLevelFlag { @@ -81,6 +93,11 @@ static void setInternalWindowFrameMargins(QWindow *window, const QMargins &margins) { const QVariant marginsVar = QVariant::fromValue(margins); + + // We need to tell Qt we have set a custom margin, because we are hiding + // the title bar by pretending the whole window is filled by client area, + // this however confuses Qt's internal logic. We need to do the following + // hack to let Qt consider the extra margin when changing window geometry. window->setProperty("_q_windowsCustomMargins", marginsVar); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) if (QPlatformWindow *platformWindow = window->handle()) { @@ -124,6 +141,36 @@ SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER); } + static inline bool isFullScreen(HWND hwnd) { + RECT windowRect{}; + ::GetWindowRect(hwnd, &windowRect); + // Compare to the full area of the screen, not the work area. + return (windowRect == getMonitorForWindow(hwnd).rcMonitor); + } + + static inline bool isMaximized(HWND hwnd) { + return ::IsZoomed(hwnd); + } + + static inline bool isMinimized(HWND hwnd) { + return ::IsIconic(hwnd); + } + + static inline bool isWindowNoState(HWND hwnd) { +#if 0 + WINDOWPLACEMENT wp{}; + wp.length = sizeof(wp); + ::GetWindowPlacement(hwnd, &wp); + return ((wp.showCmd == SW_NORMAL) || (wp.showCmd == SW_RESTORE)); +#else + if (isFullScreen(hwnd)) { + return false; + } + const auto style = static_cast<DWORD>(::GetWindowLongPtrW(hwnd, GWL_STYLE)); + return (!(style & (WS_MINIMIZE | WS_MAXIMIZE))); +#endif + } + static inline void bringWindowToFront(HWND hwnd) { HWND oldForegroundWindow = ::GetForegroundWindow(); if (!oldForegroundWindow) { @@ -135,7 +182,7 @@ if (!::IsWindowVisible(hwnd)) { ::ShowWindow(hwnd, SW_SHOW); } - if (IsMinimized(hwnd)) { + if (isMinimized(hwnd)) { // Restore the window if it is minimized. ::ShowWindow(hwnd, SW_RESTORE); // Once we've been restored, throw us on the active monitor. @@ -161,8 +208,8 @@ [[maybe_unused]] const auto &cleaner = qScopeGuard([windowThreadProcessId, currentThreadId]() { - ::AttachThreadInput(windowThreadProcessId, currentThreadId, FALSE); // - }); // TODO: Remove it + ::AttachThreadInput(windowThreadProcessId, currentThreadId, FALSE); + }); ::BringWindowToTop(hwnd); // Activate the window too. This will force us to the virtual desktop this @@ -170,73 +217,6 @@ ::SetActiveWindow(hwnd); // Throw us on the active monitor. moveWindowToMonitor(hwnd, activeMonitor); - } - - static inline bool isFullScreen(HWND hwnd) { - RECT windowRect{}; - ::GetWindowRect(hwnd, &windowRect); - // Compare to the full area of the screen, not the work area. - return (windowRect == getMonitorForWindow(hwnd).rcMonitor); - } - - static inline bool isWindowNoState(HWND hwnd) { -#if 0 - WINDOWPLACEMENT wp{}; - wp.length = sizeof(wp); - ::GetWindowPlacement(hwnd, &wp); - return ((wp.showCmd == SW_NORMAL) || (wp.showCmd == SW_RESTORE)); -#else - if (isFullScreen(hwnd)) { - return false; - } - const auto style = static_cast<DWORD>(::GetWindowLongPtrW(hwnd, GWL_STYLE)); - return (!(style & (WS_MINIMIZE | WS_MAXIMIZE))); -#endif - } - - static void syncPaintEventWithDwm() { - // No need to sync with DWM if DWM composition is disabled. - if (!isDwmCompositionEnabled()) { - return; - } - const DynamicApis &apis = DynamicApis::instance(); - // Dirty hack to workaround the resize flicker caused by DWM. - LARGE_INTEGER freq{}; - ::QueryPerformanceFrequency(&freq); - TIMECAPS tc{}; - apis.ptimeGetDevCaps(&tc, sizeof(tc)); - const UINT ms_granularity = tc.wPeriodMin; - apis.ptimeBeginPeriod(ms_granularity); - LARGE_INTEGER now0{}; - ::QueryPerformanceCounter(&now0); - // ask DWM where the vertical blank falls - DWM_TIMING_INFO dti{}; - dti.cbSize = sizeof(dti); - apis.pDwmGetCompositionTimingInfo(nullptr, &dti); - LARGE_INTEGER now1{}; - ::QueryPerformanceCounter(&now1); - // - DWM told us about SOME vertical blank - // - past or future, possibly many frames away - // - convert that into the NEXT vertical blank - const auto period = qreal(dti.qpcRefreshPeriod); - const auto dt = qreal(dti.qpcVBlank - now1.QuadPart); - const qreal ratio = (dt / period); - auto w = qreal(0); - auto m = qreal(0); - if ((dt > qreal(0)) || qFuzzyIsNull(dt)) { - w = ratio; - } else { - // reach back to previous period - // - so m represents consistent position within phase - w = (ratio - qreal(1)); - } - m = (dt - (period * w)); - if ((m < qreal(0)) || qFuzzyCompare(m, period) || (m > period)) { - return; - } - const qreal m_ms = (qreal(1000) * m / qreal(freq.QuadPart)); - ::Sleep(static_cast<DWORD>(std::round(m_ms))); - apis.ptimeEndPeriod(ms_granularity); } // Returns false if the menu is canceled @@ -250,12 +230,20 @@ return true; } - const bool maxOrFull = IsMaximized(hWnd) || isFullScreen(hWnd); + const auto windowStyles = ::GetWindowLongPtrW(hWnd, GWL_STYLE); + const bool allowMaximize = windowStyles & WS_MAXIMIZEBOX; + const bool allowMinimize = windowStyles & WS_MINIMIZEBOX; + + const bool maxOrFull = isMaximized(hWnd) || isFullScreen(hWnd); ::EnableMenuItem(hMenu, SC_CLOSE, (MF_BYCOMMAND | MFS_ENABLED)); - ::EnableMenuItem(hMenu, SC_MAXIMIZE, - (MF_BYCOMMAND | ((maxOrFull || fixedSize) ? MFS_DISABLED : MFS_ENABLED))); - ::EnableMenuItem(hMenu, SC_RESTORE, - (MF_BYCOMMAND | ((maxOrFull && !fixedSize) ? MFS_ENABLED : MFS_DISABLED))); + ::EnableMenuItem( + hMenu, SC_MAXIMIZE, + (MF_BYCOMMAND | + ((maxOrFull || fixedSize || !allowMaximize) ? MFS_DISABLED : MFS_ENABLED))); + ::EnableMenuItem( + hMenu, SC_RESTORE, + (MF_BYCOMMAND | + ((maxOrFull && !fixedSize && allowMaximize) ? MFS_ENABLED : MFS_DISABLED))); // The first menu item should be selected by default if the menu is brought // up by keyboard. I don't know how to pre-select a menu item but it seems // highlight can do the job. However, there's an annoying issue if we do @@ -267,7 +255,8 @@ // the menu look kind of weird. Currently I don't know how to fix this issue. ::HiliteMenuItem(hWnd, hMenu, SC_RESTORE, (MF_BYCOMMAND | (selectFirstEntry ? MFS_HILITE : MFS_UNHILITE))); - ::EnableMenuItem(hMenu, SC_MINIMIZE, (MF_BYCOMMAND | MFS_ENABLED)); + ::EnableMenuItem(hMenu, SC_MINIMIZE, + (MF_BYCOMMAND | (allowMinimize ? MFS_ENABLED : MFS_DISABLED))); ::EnableMenuItem(hMenu, SC_SIZE, (MF_BYCOMMAND | ((maxOrFull || fixedSize) ? MFS_DISABLED : MFS_ENABLED))); ::EnableMenuItem(hMenu, SC_MOVE, (MF_BYCOMMAND | (maxOrFull ? MFS_DISABLED : MFS_ENABLED))); @@ -333,7 +322,6 @@ case HTBORDER: return Win32WindowContext::FixedBorder; default: - Q_UNREACHABLE(); break; } return Win32WindowContext::Outside; @@ -387,8 +375,8 @@ static MSG createMessageBlock(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { MSG msg; - msg.hwnd = hWnd; // re-create MSG structure - msg.message = message; // time and pt fields ignored + msg.hwnd = hWnd; + msg.message = message; msg.wParam = wParam; msg.lParam = lParam; @@ -398,6 +386,8 @@ if (!isNonClientMessage(message)) { ::ScreenToClient(hWnd, &msg.pt); } + + msg.time = ::GetMessageTime(); return msg; } @@ -432,7 +422,7 @@ } // Send to QAbstractEventDispatcher - bool filterNativeEvent(MSG *msg, LRESULT *result) { + static bool filterNativeEvent(MSG *msg, LRESULT *result) { auto dispatcher = QAbstractEventDispatcher::instance(); QT_NATIVE_EVENT_RESULT_TYPE filterResult = *result; if (dispatcher && dispatcher->filterNativeEvent(nativeEventType(), msg, &filterResult)) { @@ -443,7 +433,7 @@ } // Send to QWindowSystemInterface - bool filterNativeEvent(QWindow *window, MSG *msg, LRESULT *result) { + static bool filterNativeEvent(QWindow *window, MSG *msg, LRESULT *result) { QT_NATIVE_EVENT_RESULT_TYPE filterResult = *result; if (QWindowSystemInterface::handleNativeEvent(window, nativeEventType(), msg, &filterResult)) { @@ -625,6 +615,7 @@ // Try hooked procedure and save result LRESULT result; if (ctx->windowProc(hWnd, message, wParam, lParam, &result)) { + // https://github.com/stdware/qwindowkit/issues/45 // Forward the event to user-defined native event filters, there may be some messages // that need to be processed by the user. std::ignore = @@ -655,6 +646,11 @@ // Save window handle mapping g_wndProcHash->insert(hWnd, ctx); + + // Force a WM_NCCALCSIZE message manually to avoid the title bar become visible + // while Qt is re-creating the window (such as setWindowFlag(s) calls). It has + // been observed by our users. + triggerFrameChange(hWnd); } static inline void removeManagedWindow(HWND hWnd) { @@ -704,7 +700,7 @@ const QPoint nativeGlobalPos = QHighDpi::toNativePixels(pos, m_windowHandle.data()); #endif std::ignore = showSystemMenu_sys(hWnd, qpoint2point(nativeGlobalPos), false, - m_delegate->isHostSizeFixed(m_host)); + isHostSizeFixed()); return; } @@ -719,7 +715,7 @@ } #if QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDERS) - case DrawWindows10BorderHook: { + case DrawWindows10BorderHook_Emulated: { if (!m_windowId) return; @@ -762,7 +758,7 @@ return; } - case DrawWindows10BorderHook2: { + case DrawWindows10BorderHook_Native: { if (!m_windowId) return; @@ -817,10 +813,18 @@ return isSystemBorderEnabled() && !isWin11OrGreater(); } + if (key == QStringLiteral("windows-system-border-enabled")) { + return isSystemBorderEnabled(); + } + if (key == QStringLiteral("border-thickness")) { return m_windowId ? int(getWindowFrameBorderThickness(reinterpret_cast<HWND>(m_windowId))) : 0; + } + + if (key == QStringLiteral("title-bar-height")) { + return m_windowId ? int(getTitleBarHeight(reinterpret_cast<HWND>(m_windowId))) : 0; } return AbstractWindowContext::windowAttribute(key); } @@ -831,16 +835,10 @@ lastHitTestResult = WindowPart::Outside; lastHitTestResultRaw = HTNOWHERE; - if (!isSystemBorderEnabled()) { - m_delegate->setWindowFlags(m_host, m_delegate->getWindowFlags(m_host) | - Qt::FramelessWindowHint); - } - // If the original window id is valid, remove all resources related if (oldWinId) { removeManagedWindow(reinterpret_cast<HWND>(oldWinId)); } - if (!winId) { return; } @@ -926,17 +924,60 @@ Q_UNUSED(oldAttribute) const auto hwnd = reinterpret_cast<HWND>(m_windowId); + Q_ASSERT(hwnd); + const DynamicApis &apis = DynamicApis::instance(); - static constexpr const MARGINS extendedMargins = {-1, -1, -1, -1}; + const auto &extendMargins = [this, &apis, hwnd]() { + // For some unknown reason, the window background is totally black and extending + // the window frame into the client area seems to fix it magically. + // After many times of trying, we found that the Acrylic/Mica/Mica Alt background + // only appears on the native Win32 window's background, so naturally we want to + // extend the window frame into the whole client area to be able to let the special + // material fill the whole window. Previously we are using negative margins because + // it's widely known that using negative margins will let the window frame fill + // the whole window and that's indeed what we wanted to do, however, later we found + // that doing so is causing issues. When the user enabled the "show accent color on + // window title bar and borders" option on system personalize settings, a 30px bar + // would appear on window top. It has the same color with the system accent color. + // Actually it's the original title bar we've already hidden, and it magically + // appears again when we use negative margins to extend the window frame. And again + // after some experiments, I found that the title bar won't appear if we don't extend + // from the top side. In the end I found that we only need to extend from the left + // side if we extend long enough. In this way we can see the special material even + // when the host object is a QWidget and the title bar still remain hidden. But even + // though this solution seems perfect, I really don't know why it works. The following + // hack is totally based on experiments. + static constexpr const MARGINS margins = {65536, 0, 0, 0}; + apis.pDwmExtendFrameIntoClientArea(hwnd, &margins); + }; const auto &restoreMargins = [this, &apis, hwnd]() { auto margins = qmargins2margins( - m_windowAttributes.value(QStringLiteral("extra-margins")).value<QMargins>()); + windowAttribute(QStringLiteral("extra-margins")).value<QMargins>()); apis.pDwmExtendFrameIntoClientArea(hwnd, &margins); + }; + + const auto &effectBugWorkaround = [this, hwnd]() { + // We don't need the following *HACK* for QWidget windows. + // Completely based on actual experiments, root reason is totally unknown. + if (m_host->isWidgetType()) { + return; + } + static QSet<WId> bugWindowSet{}; + if (bugWindowSet.contains(m_windowId)) { + return; + } + bugWindowSet.insert(m_windowId); + RECT rect{}; + ::GetWindowRect(hwnd, &rect); + ::MoveWindow(hwnd, rect.left, rect.top, 1, 1, FALSE); + ::MoveWindow(hwnd, rect.right - 1, rect.bottom - 1, 1, 1, FALSE); + ::MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, + FALSE); }; if (key == QStringLiteral("extra-margins")) { auto margins = qmargins2margins(attribute.value<QMargins>()); - return apis.pDwmExtendFrameIntoClientArea(hwnd, &margins) == S_OK; + return SUCCEEDED(apis.pDwmExtendFrameIntoClientArea(hwnd, &margins)); } if (key == QStringLiteral("dark-mode")) { @@ -950,12 +991,9 @@ } else { apis.pAllowDarkModeForApp(enable); } - for (const auto attr : { - _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1, - _DWMWA_USE_IMMERSIVE_DARK_MODE, - }) { - apis.pDwmSetWindowAttribute(hwnd, attr, &enable, sizeof(enable)); - } + const auto attr = isWin1020H1OrGreater() ? _DWMWA_USE_IMMERSIVE_DARK_MODE + : _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1; + apis.pDwmSetWindowAttribute(hwnd, attr, &enable, sizeof(enable)); apis.pFlushMenuThemes(); return true; @@ -967,9 +1005,7 @@ return false; } if (attribute.toBool()) { - // We need to extend the window frame into the whole client area to be able - // to see the blurred window background. - apis.pDwmExtendFrameIntoClientArea(hwnd, &extendedMargins); + extendMargins(); if (isWin1122H2OrGreater()) { // Use official DWM API to enable Mica, available since Windows 11 22H2 // (10.0.22621). @@ -993,6 +1029,7 @@ } restoreMargins(); } + effectBugWorkaround(); return true; } @@ -1001,9 +1038,7 @@ return false; } if (attribute.toBool()) { - // We need to extend the window frame into the whole client area to be able - // to see the blurred window background. - apis.pDwmExtendFrameIntoClientArea(hwnd, &extendedMargins); + extendMargins(); // Use official DWM API to enable Mica Alt, available since Windows 11 22H2 // (10.0.22621). const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_TABBEDWINDOW; @@ -1015,6 +1050,7 @@ sizeof(backdropType)); restoreMargins(); } + effectBugWorkaround(); return true; } @@ -1023,9 +1059,7 @@ return false; } if (attribute.toBool()) { - // We need to extend the window frame into the whole client area to be able - // to see the blurred window background. - apis.pDwmExtendFrameIntoClientArea(hwnd, &extendedMargins); + extendMargins(); const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_TRANSIENTWINDOW; apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, @@ -1062,13 +1096,14 @@ restoreMargins(); } + effectBugWorkaround(); return true; } if (key == QStringLiteral("dwm-blur")) { + // Extending window frame would break this effect for some unknown reason. + restoreMargins(); if (attribute.toBool()) { - // We can't extend the window frame for this effect. - restoreMargins(); if (isWin8OrGreater()) { ACCENT_POLICY policy{}; policy.dwAccentState = ACCENT_ENABLE_BLURBEHIND; @@ -1101,6 +1136,7 @@ apis.pDwmEnableBlurBehindWindow(hwnd, &bb); } } + effectBugWorkaround(); return true; } return false; @@ -1566,12 +1602,21 @@ QPoint qtScenePos = QHighDpi::fromNativeLocalPosition(point2qpoint(nativeLocalPos), m_windowHandle.data()); - bool isFixedSize = m_delegate->isHostSizeFixed(m_host); - bool isTitleBar = isInTitleBarDraggableArea(qtScenePos); - bool dontOverrideCursor = false; // ### TODO + int frameSize = getResizeBorderThickness(hWnd); + bool isFixedWidth = isHostWidthFixed(); + bool isFixedHeight = isHostHeightFixed(); + bool isFixedSize = isHostSizeFixed(); + bool isInLeftBorder = nativeLocalPos.x <= frameSize; + bool isInTopBorder = nativeLocalPos.y <= frameSize; + bool isInRightBorder = nativeLocalPos.x > clientWidth - frameSize; + bool isInBottomBorder = nativeLocalPos.y > clientHeight - frameSize; + bool isInTitleBar = isInTitleBarDraggableArea(qtScenePos); WindowAgentBase::SystemButton sysButtonType = WindowAgentBase::Unknown; - if (!isFixedSize && isInSystemButtons(qtScenePos, &sysButtonType)) { + bool isInCaptionButtons = isInSystemButtons(qtScenePos, &sysButtonType); + static constexpr bool dontOverrideCursor = false; // ### TODO + + if (isInCaptionButtons) { // Firstly, we set the hit test result to a default value to be able to detect // whether we have changed it or not afterwards. *result = HTNOWHERE; @@ -1581,29 +1626,41 @@ // window is not maximized/fullscreen/minimized, of course). if (isWindowNoState(hWnd)) { static constexpr const quint8 kBorderSize = 2; - bool isTop = (nativeLocalPos.y <= kBorderSize); + bool isTop = nativeLocalPos.y <= kBorderSize; bool isLeft = nativeLocalPos.x <= kBorderSize; - bool isRight = (nativeLocalPos.x >= (clientWidth - kBorderSize)); + bool isRight = nativeLocalPos.x > clientWidth - kBorderSize; if (isTop || isLeft || isRight) { - if (dontOverrideCursor) { + if (isFixedSize || dontOverrideCursor) { // The user doesn't want the window to be resized, so we tell // Windows we are in the client area so that the controls beneath // the mouse cursor can still be hovered or clicked. - *result = (isTitleBar ? HTCAPTION : HTCLIENT); + *result = isInTitleBar ? HTCAPTION : HTCLIENT; } else { if (isTop) { if (isLeft) { - *result = HTTOPLEFT; + if (isFixedWidth) { + *result = HTTOP; + } else if (isFixedHeight) { + *result = HTLEFT; + } else { + *result = HTTOPLEFT; + } } else if (isRight) { - *result = HTTOPRIGHT; + if (isFixedWidth) { + *result = HTTOP; + } else if (isFixedHeight) { + *result = HTRIGHT; + } else { + *result = HTTOPRIGHT; + } } else { - *result = HTTOP; + *result = isFixedHeight ? HTBORDER : HTTOP; } } else { - if (isLeft) { - *result = HTLEFT; + if (isFixedWidth) { + *result = HTBORDER; } else { - *result = HTRIGHT; + *result = isLeft ? HTLEFT : HTRIGHT; } } } @@ -1645,10 +1702,8 @@ // OK, we are not inside any chrome buttons, try to find out which part of the // window are we hitting. - bool max = IsMaximized(hWnd); + bool max = isMaximized(hWnd); bool full = isFullScreen(hWnd); - int frameSize = getResizeBorderThickness(hWnd); - bool isTop = (nativeLocalPos.y < frameSize); if (isSystemBorderEnabled()) { // This will handle the left, right and bottom parts of the frame @@ -1660,8 +1715,51 @@ // outside the window, that is, the three transparent window resize area. // Returning HTCLIENT will confuse Windows, we can't put our controls there // anyway. - *result = ((isFixedSize || dontOverrideCursor) ? HTBORDER - : originalHitTestResult); + *result = HTNOWHERE; // Make sure we can know we don't set any value + // explicitly later. + if (originalHitTestResult == HTCAPTION) { + } else if (isFixedSize || dontOverrideCursor) { + *result = HTBORDER; + } else if (isFixedWidth || isFixedHeight) { + if (originalHitTestResult == HTTOPLEFT) { + if (isFixedWidth) { + *result = HTTOP; + } else { + *result = HTLEFT; + } + } else if (originalHitTestResult == HTTOPRIGHT) { + if (isFixedWidth) { + *result = HTTOP; + } else { + *result = HTRIGHT; + } + } else if (originalHitTestResult == HTBOTTOMRIGHT) { + if (isFixedWidth) { + *result = HTBOTTOM; + } else { + *result = HTRIGHT; + } + } else if (originalHitTestResult == HTBOTTOMLEFT) { + if (isFixedWidth) { + *result = HTBOTTOM; + } else { + *result = HTLEFT; + } + } else if (originalHitTestResult == HTLEFT || + originalHitTestResult == HTRIGHT) { + if (isFixedWidth) { + *result = HTBORDER; + } + } else if (originalHitTestResult == HTTOP || + originalHitTestResult == HTBOTTOM) { + if (isFixedHeight) { + *result = HTBORDER; + } + } + } + if (*result == HTNOWHERE) { + *result = originalHitTestResult; + } return true; } if (full) { @@ -1669,7 +1767,7 @@ return true; } if (max) { - *result = (isTitleBar ? HTCAPTION : HTCLIENT); + *result = isInTitleBar ? HTCAPTION : HTCLIENT; return true; } // At this point, we know that the cursor is inside the client area, @@ -1677,16 +1775,25 @@ // title bar or the drag bar. Apparently, it must be the drag bar or // the little border at the top which the user can use to move or // resize the window. - if (isTop) { + if (isInTopBorder) { // Return HTCLIENT instead of HTBORDER here, because the mouse is // inside our homemade title bar now, return HTCLIENT to let our // title bar can still capture mouse events. - *result = ((isFixedSize || dontOverrideCursor) - ? (isTitleBar ? HTCAPTION : HTCLIENT) - : HTTOP); + *result = [&]() { + if (isFixedSize || isFixedHeight || dontOverrideCursor || + (isFixedWidth && (isInLeftBorder || isInRightBorder))) { + if (isInTitleBar) { + return HTCAPTION; + } else { + return HTCLIENT; + } + } else { + return HTTOP; + } + }(); return true; } - if (isTitleBar) { + if (isInTitleBar) { *result = HTCAPTION; return true; } @@ -1697,58 +1804,86 @@ *result = HTCLIENT; return true; } - if (max) { - *result = (isTitleBar ? HTCAPTION : HTCLIENT); + if (max || isFixedSize || dontOverrideCursor) { + *result = isInTitleBar ? HTCAPTION : HTCLIENT; return true; } - if (!isFixedSize) { - const bool isBottom = (nativeLocalPos.y >= (clientHeight - frameSize)); - // Make the border a little wider to let the user easy to resize on corners. - const auto scaleFactor = ((isTop || isBottom) ? qreal(2) : qreal(1)); - const int scaledFrameSize = std::round(qreal(frameSize) * scaleFactor); - const bool isLeft = (nativeLocalPos.x < scaledFrameSize); - const bool isRight = (nativeLocalPos.x >= (clientWidth - scaledFrameSize)); - if (dontOverrideCursor && (isTop || isBottom || isLeft || isRight)) { - // Return HTCLIENT instead of HTBORDER here, because the mouse is - // inside the window now, return HTCLIENT to let the controls - // inside our window can still capture mouse events. - *result = (isTitleBar ? HTCAPTION : HTCLIENT); - return true; + if (isFixedWidth || isFixedHeight) { + if (isInLeftBorder && isInTopBorder) { + if (isFixedWidth) { + *result = HTTOP; + } else { + *result = HTLEFT; + } + } else if (isInRightBorder && isInTopBorder) { + if (isFixedWidth) { + *result = HTTOP; + } else { + *result = HTRIGHT; + } + } else if (isInRightBorder && isInBottomBorder) { + if (isFixedWidth) { + *result = HTBOTTOM; + } else { + *result = HTRIGHT; + } + } else if (isInLeftBorder && isInBottomBorder) { + if (isFixedWidth) { + *result = HTBOTTOM; + } else { + *result = HTLEFT; + } + } else if (isInLeftBorder || isInRightBorder) { + if (isFixedWidth) { + *result = HTCLIENT; + } else { + *result = isInLeftBorder ? HTLEFT : HTRIGHT; + } + } else if (isInTopBorder || isInBottomBorder) { + if (isFixedHeight) { + *result = HTCLIENT; + } else { + *result = isInTopBorder ? HTTOP : HTBOTTOM; + } + } else { + *result = HTCLIENT; } - if (isTop) { - if (isLeft) { + return true; + } else { + if (isInTopBorder) { + if (isInLeftBorder) { *result = HTTOPLEFT; return true; } - if (isRight) { + if (isInRightBorder) { *result = HTTOPRIGHT; return true; } *result = HTTOP; return true; } - if (isBottom) { - if (isLeft) { + if (isInBottomBorder) { + if (isInLeftBorder) { *result = HTBOTTOMLEFT; return true; } - if (isRight) { + if (isInRightBorder) { *result = HTBOTTOMRIGHT; return true; } *result = HTBOTTOM; return true; } - if (isLeft) { + if (isInLeftBorder) { *result = HTLEFT; return true; } - if (isRight) { + if (isInRightBorder) { *result = HTRIGHT; return true; } } - if (isTitleBar) { + if (isInTitleBar) { *result = HTCAPTION; return true; } @@ -1927,8 +2062,24 @@ // implement an elaborate client-area preservation technique, and // simply return 0, which means "preserve the entire old client area // and align it with the upper-left corner of our new client area". + const auto clientRect = wParam ? &(reinterpret_cast<LPNCCALCSIZE_PARAMS>(lParam))->rgrc[0] : reinterpret_cast<LPRECT>(lParam); + [[maybe_unused]] const auto &flickerReducer = qScopeGuard([this]() { + // When we receive this message, it means the window size has changed + // already, and it seems this message always come before any client + // area size notifications (eg. WM_WINDOWPOSCHANGED and WM_SIZE). Let + // D3D/VK paint immediately to let user see the latest result as soon + // as possible. + const auto &isTargetSurface = [](const QSurface::SurfaceType st) { + return st != QSurface::RasterSurface && st != QSurface::OpenGLSurface && + st != QSurface::RasterGLSurface && st != QSurface::OpenVGSurface; + }; + if (m_windowHandle && isTargetSurface(m_windowHandle->surfaceType()) && + isDwmCompositionEnabled() && DynamicApis::instance().pDwmFlush) { + DynamicApis::instance().pDwmFlush(); + } + }); if (isSystemBorderEnabled()) { // Store the original top margin before the default window procedure applies the // default frame. @@ -1941,9 +2092,9 @@ // that's also how most applications customize their title bars on Windows. It's // totally OK but since we want to preserve as much original frame as possible, // we can't use that solution. - const LRESULT hitTestResult = ::DefWindowProcW(hWnd, WM_NCCALCSIZE, wParam, lParam); - if ((hitTestResult != HTERROR) && (hitTestResult != HTNOWHERE)) { - *result = hitTestResult; + const LRESULT originalResult = ::DefWindowProcW(hWnd, WM_NCCALCSIZE, wParam, lParam); + if (originalResult != 0) { + *result = originalResult; return true; } // Re-apply the original top from before the size of the default frame was @@ -1957,7 +2108,7 @@ clientRect->top = originalTop; } - const bool max = IsMaximized(hWnd); + const bool max = isMaximized(hWnd); const bool full = isFullScreen(hWnd); // We don't need this correction when we're fullscreen. We will // have the WS_POPUP size, so we don't have to worry about @@ -2050,8 +2201,6 @@ } } } - // We should call this function only before the function returns. - syncPaintEventWithDwm(); // By returning WVR_REDRAW we can make the window resizing look // less broken. But we must return 0 if wParam is FALSE, according to Microsoft // Docs. @@ -2062,7 +2211,10 @@ // of the upper-left non-client area. It's confirmed that this issue exists // from Windows 7 to Windows 10. Not tested on Windows 11 yet. Don't know // whether it exists on Windows XP to Windows Vista or not. - *result = wParam ? WVR_REDRAW : FALSE; + + // https://github.com/chromium/chromium/blob/5d297da3cf2a642e9ace2b23fed097370bc70814/ui/views/win/hwnd_message_handler.cc#L2330 + // Do not return WVR_REDRAW otherwise child HWNDs will be mispositioned. + *result = FALSE; return true; } @@ -2072,7 +2224,7 @@ return {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; }; const auto getNativeGlobalPosFromKeyboard = [hWnd]() -> POINT { - const bool maxOrFull = IsMaximized(hWnd) || isFullScreen(hWnd); + const bool maxOrFull = isMaximized(hWnd) || isFullScreen(hWnd); const quint32 frameSize = getResizeBorderThickness(hWnd); const quint32 horizontalOffset = ((maxOrFull || !isSystemBorderEnabled()) ? 0 : frameSize); @@ -2177,7 +2329,7 @@ if (!mouseHook) { mouseHook = ::SetWindowsHookExW( WH_MOUSE, - [](int nCode, WPARAM wParam, LPARAM lParam) { + [](int nCode, WPARAM wParam, LPARAM lParam) -> LRESULT { if (nCode >= 0) { switch (wParam) { case WM_LBUTTONDBLCLK: @@ -2205,8 +2357,8 @@ } } - bool res = showSystemMenu_sys(hWnd, nativeGlobalPos, broughtByKeyboard, - m_delegate->isHostSizeFixed(m_host)); + bool res = + showSystemMenu_sys(hWnd, nativeGlobalPos, broughtByKeyboard, isHostSizeFixed()); // Uninstall mouse hook and check if it's a double-click if (mouseHookedLocal) { -- Gitblit v1.9.1