| | |
| | | #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 { |
| | |
| | | static void setInternalWindowFrameMargins(QWindow *window, const QMargins &margins) { |
| | | const QVariant marginsVar = QVariant::fromValue(margins); |
| | | |
| | | // TODO: Add comments |
| | | // 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()) { |
| | |
| | | |
| | | [[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 |
| | |
| | | ::SetActiveWindow(hwnd); |
| | | // Throw us on the active monitor. |
| | | moveWindowToMonitor(hwnd, activeMonitor); |
| | | } |
| | | |
| | | 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 |
| | |
| | | return true; |
| | | } |
| | | |
| | | 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 |
| | |
| | | // 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))); |
| | |
| | | case HTBORDER: |
| | | return Win32WindowContext::FixedBorder; |
| | | default: |
| | | Q_UNREACHABLE(); |
| | | break; |
| | | } |
| | | return Win32WindowContext::Outside; |
| | |
| | | |
| | | 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; |
| | | |
| | |
| | | if (!isNonClientMessage(message)) { |
| | | ::ScreenToClient(hWnd, &msg.pt); |
| | | } |
| | | |
| | | msg.time = ::GetMessageTime(); |
| | | return msg; |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | // 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)) { |
| | |
| | | } |
| | | |
| | | // 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)) { |
| | |
| | | |
| | | // 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) { |
| | |
| | | } |
| | | |
| | | #if QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDERS) |
| | | case DrawWindows10BorderHook: { |
| | | case DrawWindows10BorderHook_Emulated: { |
| | | if (!m_windowId) |
| | | return; |
| | | |
| | |
| | | return; |
| | | } |
| | | |
| | | case DrawWindows10BorderHook2: { |
| | | case DrawWindows10BorderHook_Native: { |
| | | if (!m_windowId) |
| | | return; |
| | | |
| | |
| | | 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))) |
| | |
| | | } |
| | | |
| | | if (key == QStringLiteral("title-bar-height")) { |
| | | return m_windowId |
| | | ? int(getTitleBarHeight(reinterpret_cast<HWND>(m_windowId))) |
| | | : 0; |
| | | return m_windowId ? int(getTitleBarHeight(reinterpret_cast<HWND>(m_windowId))) : 0; |
| | | } |
| | | return AbstractWindowContext::windowAttribute(key); |
| | | } |
| | |
| | | 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; |
| | | } |
| | |
| | | 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")) { |
| | |
| | | } 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; |
| | |
| | | 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). |
| | |
| | | } |
| | | restoreMargins(); |
| | | } |
| | | effectBugWorkaround(); |
| | | return true; |
| | | } |
| | | |
| | |
| | | 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; |
| | |
| | | sizeof(backdropType)); |
| | | restoreMargins(); |
| | | } |
| | | effectBugWorkaround(); |
| | | return true; |
| | | } |
| | | |
| | |
| | | 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, |
| | |
| | | |
| | | 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; |
| | |
| | | apis.pDwmEnableBlurBehindWindow(hwnd, &bb); |
| | | } |
| | | } |
| | | effectBugWorkaround(); |
| | | return true; |
| | | } |
| | | return false; |
| | |
| | | QPoint qtScenePos = QHighDpi::fromNativeLocalPosition(point2qpoint(nativeLocalPos), |
| | | m_windowHandle.data()); |
| | | |
| | | bool isFixedSize = isHostSizeFixed(); |
| | | 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; |
| | |
| | | // 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; |
| | | } |
| | | } |
| | | } |
| | |
| | | |
| | | 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 |
| | |
| | | // 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) { |
| | |
| | | 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, |
| | |
| | | // 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; |
| | | } |
| | |
| | | *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; |
| | | } |
| | |
| | | // 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. |
| | |
| | | } |
| | | } |
| | | } |
| | | // 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. |
| | |
| | | // 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; |
| | | } |
| | | |
| | |
| | | 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: |