| | |
| | | |
| | | static void setInternalWindowFrameMargins(QWindow *window, const QMargins &margins) { |
| | | const QVariant marginsVar = QVariant::fromValue(margins); |
| | | |
| | | // TODO: Add comments |
| | | window->setProperty("_q_windowsCustomMargins", marginsVar); |
| | | #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) |
| | | if (QPlatformWindow *platformWindow = window->handle()) { |
| | |
| | | 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) { |
| | |
| | | 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. |
| | |
| | | ::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() { |
| | |
| | | return true; |
| | | } |
| | | |
| | | const bool maxOrFull = IsMaximized(hWnd) || isFullScreen(hWnd); |
| | | 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))); |
| | |
| | | case HTBORDER: |
| | | return Win32WindowContext::FixedBorder; |
| | | default: |
| | | Q_UNREACHABLE(); |
| | | break; |
| | | } |
| | | return Win32WindowContext::Outside; |
| | |
| | | // 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 = |
| | |
| | | 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; |
| | | } |
| | | |
| | |
| | | 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); |
| | | } |
| | |
| | | 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; |
| | | } |
| | |
| | | |
| | | 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; |
| | |
| | | QPoint qtScenePos = QHighDpi::fromNativeLocalPosition(point2qpoint(nativeLocalPos), |
| | | m_windowHandle.data()); |
| | | |
| | | bool isFixedSize = m_delegate->isHostSizeFixed(m_host); |
| | | bool isTitleBar = isInTitleBarDraggableArea(qtScenePos); |
| | | 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; |
| | | bool isInCaptionButtons = isInSystemButtons(qtScenePos, &sysButtonType); |
| | | bool dontOverrideCursor = false; // ### TODO |
| | | |
| | | WindowAgentBase::SystemButton sysButtonType = WindowAgentBase::Unknown; |
| | | if (!isFixedSize && isInSystemButtons(qtScenePos, &sysButtonType)) { |
| | | 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; |
| | | } |
| | | } |
| | | } |
| | |
| | | // 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 |
| | |
| | | // 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; |
| | | } |
| | |
| | | // 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 |
| | |
| | | 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 |
| | |
| | | // 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; |
| | | } |
| | | |
| | |
| | | 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); |
| | |
| | | } |
| | | } |
| | | |
| | | 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) { |