| | |
| | | // Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware) |
| | | // Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao) |
| | | // SPDX-License-Identifier: Apache-2.0 |
| | | |
| | | #include "win32windowcontext_p.h" |
| | | |
| | | #include <optional> |
| | |
| | | #include <QtGui/QGuiApplication> |
| | | #include <QtGui/QPainter> |
| | | #include <QtGui/QPalette> |
| | | #include <QtGui/QStyleHints> |
| | | |
| | | #include <QtGui/private/qhighdpiscaling_p.h> |
| | | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) |
| | |
| | | |
| | | #include "qwkglobal_p.h" |
| | | #include "qwkwindowsextra_p.h" |
| | | |
| | | #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) |
| | | Q_DECLARE_METATYPE(QMargins) |
| | | #endif |
| | | |
| | | namespace QWK { |
| | | |
| | |
| | | static WNDPROC g_qtWindowProc = nullptr; |
| | | |
| | | static inline bool |
| | | #if !QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDER) |
| | | #if !QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDERS) |
| | | constexpr |
| | | #endif |
| | | |
| | | isSystemBorderEnabled() { |
| | | return |
| | | #if QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDER) |
| | | #if QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDERS) |
| | | isWin10OrGreater() |
| | | #else |
| | | false |
| | |
| | | ::SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, |
| | | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER | |
| | | SWP_FRAMECHANGED); |
| | | } |
| | | |
| | | static inline quint32 getDpiForWindow(HWND hwnd) { |
| | | const DynamicApis &apis = DynamicApis::instance(); |
| | | if (apis.pGetDpiForWindow) { // Win10 |
| | | return apis.pGetDpiForWindow(hwnd); |
| | | } else if (apis.pGetDpiForMonitor) { // Win8.1 |
| | | HMONITOR monitor = ::MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); |
| | | UINT dpiX{0}; |
| | | UINT dpiY{0}; |
| | | apis.pGetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY); |
| | | return dpiX; |
| | | } else { // Win2K |
| | | HDC hdc = ::GetDC(nullptr); |
| | | const int dpiX = ::GetDeviceCaps(hdc, LOGPIXELSX); |
| | | // const int dpiY = ::GetDeviceCaps(hdc, LOGPIXELSY); |
| | | ::ReleaseDC(nullptr, hdc); |
| | | return quint32(dpiX); |
| | | } |
| | | } |
| | | |
| | | static inline quint32 getSystemMetricsForDpi(int index, quint32 dpi) { |
| | | const DynamicApis &apis = DynamicApis::instance(); |
| | | if (apis.pGetSystemMetricsForDpi) { |
| | | return ::GetSystemMetricsForDpi(index, dpi); |
| | | } |
| | | return ::GetSystemMetrics(index); |
| | | } |
| | | |
| | | static inline quint32 getWindowFrameBorderThickness(HWND hwnd) { |
| | | const DynamicApis &apis = DynamicApis::instance(); |
| | | if (UINT result = 0; SUCCEEDED(apis.pDwmGetWindowAttribute( |
| | | hwnd, _DWMWA_VISIBLE_FRAME_BORDER_THICKNESS, &result, sizeof(result)))) { |
| | | return result; |
| | | } |
| | | return getSystemMetricsForDpi(SM_CXBORDER, getDpiForWindow(hwnd)); |
| | | } |
| | | |
| | | static inline quint32 getResizeBorderThickness(HWND hwnd) { |
| | | const quint32 dpi = getDpiForWindow(hwnd); |
| | | return getSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) + |
| | | getSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi); |
| | | } |
| | | |
| | | static inline quint32 getTitleBarHeight(HWND hwnd) { |
| | | const quint32 dpi = getDpiForWindow(hwnd); |
| | | return getSystemMetricsForDpi(SM_CYCAPTION, dpi) + |
| | | getSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) + |
| | | getSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi); |
| | | } |
| | | |
| | | static void setInternalWindowFrameMargins(QWindow *window, const QMargins &margins) { |
| | |
| | | return false; |
| | | } |
| | | |
| | | static WindowsNativeEventFilter *instance; |
| | | static Win32WindowContext *lastMessageContext; |
| | | static inline WindowsNativeEventFilter *instance = nullptr; |
| | | static inline Win32WindowContext *lastMessageContext = nullptr; |
| | | |
| | | static inline void install() { |
| | | if (instance) { |
| | |
| | | instance = nullptr; |
| | | } |
| | | }; |
| | | |
| | | WindowsNativeEventFilter *WindowsNativeEventFilter::instance = nullptr; |
| | | Win32WindowContext *WindowsNativeEventFilter::lastMessageContext = nullptr; |
| | | |
| | | // https://github.com/qt/qtbase/blob/e26a87f1ecc40bc8c6aa5b889fce67410a57a702/src/plugins/platforms/windows/qwindowscontext.cpp#L1025 |
| | | // We can see from the source code that Qt will filter out some messages first and then send the |
| | |
| | | case RaiseWindowHook: { |
| | | if (!windowId) |
| | | return; |
| | | m_delegate->setWindowVisible(m_host, true); |
| | | const auto hwnd = reinterpret_cast<HWND>(windowId); |
| | | bringWindowToFront(hwnd); |
| | | return; |
| | |
| | | } |
| | | |
| | | case DrawWindows10BorderHook: { |
| | | #if QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDER) |
| | | #if QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDERS) |
| | | if (!windowId) |
| | | return; |
| | | |
| | |
| | | QPoint{m_windowHandle->width(), 0} |
| | | }); |
| | | painter.restore(); |
| | | return; |
| | | #endif |
| | | return; |
| | | } |
| | | |
| | | case DrawWindows10BorderHook2: { |
| | | #if QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDER) |
| | | #if QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDERS) |
| | | if (!m_windowHandle) |
| | | return; |
| | | |
| | |
| | | return; |
| | | } |
| | | |
| | | // case AbstractWindowContext::DrawWindows10BackgroundHook: { |
| | | // #if QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDER) |
| | | // if (!m_windowHandle) |
| | | // return; |
| | | // |
| | | // auto hWnd = reinterpret_cast<HWND>(windowId); |
| | | // HDC hdc = ::GetDC(hWnd); |
| | | // RECT windowRect{}; |
| | | // ::GetClientRect(hWnd, &windowRect); |
| | | // RECT rcRest = { |
| | | // 0, |
| | | // int(getWindowFrameBorderThickness(hWnd)), |
| | | // RECT_WIDTH(windowRect), |
| | | // RECT_HEIGHT(windowRect), |
| | | // }; |
| | | // HBRUSH blueBrush = ::CreateSolidBrush(RGB(0, 0, 255)); |
| | | // |
| | | // // To hide the original title bar, we have to paint on top of it |
| | | // with |
| | | // // the alpha component set to 255. This is a hack to do it with |
| | | // GDI. |
| | | // // See NonClientIslandWindow::_UpdateFrameMargins for more |
| | | // information. HDC opaqueDc; BP_PAINTPARAMS params = |
| | | // {sizeof(params), BPPF_NOCLIP | BPPF_ERASE}; auto buf = |
| | | // BeginBufferedPaint(hdc, &rcRest, BPBF_TOPDOWNDIB, ¶ms, |
| | | // &opaqueDc); if (!buf || !opaqueDc) { |
| | | // return; |
| | | // } |
| | | // |
| | | // ::FillRect(opaqueDc, &rcRest, blueBrush); |
| | | // ::BufferedPaintSetAlpha(buf, nullptr, 255); |
| | | // ::EndBufferedPaint(buf, TRUE); |
| | | // |
| | | // ::DeleteObject(blueBrush); |
| | | // ::ReleaseDC(hWnd, hdc); |
| | | // #endif |
| | | // return; |
| | | // } |
| | | |
| | | default: |
| | | break; |
| | | } |
| | | AbstractWindowContext::virtual_hook(id, data); |
| | | } |
| | | |
| | | QVariant Win32WindowContext::windowAttribute(const QString &key) const { |
| | | if (key == QStringLiteral("window-rect")) { |
| | | if (!m_windowHandle) |
| | | return {}; |
| | | |
| | | RECT frame{}; |
| | | auto hwnd = reinterpret_cast<HWND>(windowId); |
| | | // According to MSDN, WS_OVERLAPPED is not allowed for AdjustWindowRect. |
| | | auto style = static_cast<DWORD>(::GetWindowLongPtrW(hwnd, GWL_STYLE) & ~WS_OVERLAPPED); |
| | | auto exStyle = static_cast<DWORD>(::GetWindowLongPtrW(hwnd, GWL_EXSTYLE)); |
| | | const DynamicApis &apis = DynamicApis::instance(); |
| | | if (apis.pAdjustWindowRectExForDpi) { |
| | | apis.pAdjustWindowRectExForDpi(&frame, style, FALSE, exStyle, |
| | | getDpiForWindow(hwnd)); |
| | | } else { |
| | | ::AdjustWindowRectEx(&frame, style, FALSE, exStyle); |
| | | } |
| | | return QVariant::fromValue(rect2qrect(frame)); |
| | | } |
| | | |
| | | if (key == QStringLiteral("win10-border-needed")) { |
| | | return isSystemBorderEnabled() && !isWin11OrGreater(); |
| | | } |
| | | |
| | | if (key == QStringLiteral("border-thickness")) { |
| | | return m_windowHandle |
| | | ? int(getWindowFrameBorderThickness(reinterpret_cast<HWND>(windowId))) |
| | | : 0; |
| | | } |
| | | |
| | | return AbstractWindowContext::windowAttribute(key); |
| | | } |
| | | |
| | | void Win32WindowContext::winIdChanged() { |
| | |
| | | auto hWnd = reinterpret_cast<HWND>(winId); |
| | | |
| | | if (!isSystemBorderEnabled()) { |
| | | setWindowAttribute("extra-margins", true); |
| | | static auto margins = QVariant::fromValue(QMargins(1, 1, 1, 1)); |
| | | |
| | | // If we remove the system border, the window will lose its shadow. If dwm is enabled, |
| | | // then we need to set at least 1px margins, otherwise the following operation will |
| | | // fail with no effect. |
| | | setWindowAttribute(QStringLiteral("extra-margins"), margins); |
| | | } |
| | | |
| | | // We should disable WS_SYSMENU, otherwise the system button icons will be visible if mica |
| | | // is enabled and the title bar is transparent. |
| | | { |
| | | auto style = ::GetWindowLongPtrW(hWnd, GWL_STYLE); |
| | | #if QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDER) |
| | | ::SetWindowLongPtrW(hWnd, GWL_STYLE, style & (~WS_SYSMENU)); |
| | | #else |
| | | ::SetWindowLongPtrW(hWnd, GWL_STYLE, |
| | | (style | WS_THICKFRAME | WS_CAPTION) & (~WS_SYSMENU)); |
| | | #endif |
| | | if (isSystemBorderEnabled()) { |
| | | ::SetWindowLongPtrW(hWnd, GWL_STYLE, style & (~WS_SYSMENU)); |
| | | } else { |
| | | ::SetWindowLongPtrW(hWnd, GWL_STYLE, |
| | | (style | WS_THICKFRAME | WS_CAPTION) & (~WS_SYSMENU)); |
| | | } |
| | | } |
| | | |
| | | // Add managed window |
| | |
| | | |
| | | bool Win32WindowContext::windowAttributeChanged(const QString &key, const QVariant &attribute, |
| | | const QVariant &oldAttribute) { |
| | | Q_UNUSED(oldAttribute) |
| | | |
| | | const auto hwnd = reinterpret_cast<HWND>(m_windowHandle->winId()); |
| | | const DynamicApis &apis = DynamicApis::instance(); |
| | | static constexpr const MARGINS defaultEmptyMargins = {0, 0, 0, 0}; |
| | | static constexpr const MARGINS defaultExtraMargins = {1, 1, 1, 1}; |
| | | static constexpr const MARGINS extendedMargins = {-1, -1, -1, -1}; |
| | | const auto &restoreMargins = [this, &apis, hwnd]() { |
| | | auto margins = qmargins2margins( |
| | | m_windowAttributes.value(QStringLiteral("extra-margins")).value<QMargins>()); |
| | | apis.pDwmExtendFrameIntoClientArea(hwnd, &margins); |
| | | }; |
| | | |
| | | if (key == QStringLiteral("extra-margins")) { |
| | | if (isWin11OrGreater()) |
| | | return false; |
| | | hasExtraMargins = attribute.toBool(); |
| | | DynamicApis::instance().pDwmExtendFrameIntoClientArea( |
| | | hwnd, hasExtraMargins ? &defaultExtraMargins : &defaultEmptyMargins); |
| | | return true; |
| | | auto margins = qmargins2margins(attribute.value<QMargins>()); |
| | | return apis.pDwmExtendFrameIntoClientArea(hwnd, &margins) == S_OK; |
| | | } |
| | | |
| | | if (key == QStringLiteral("dark-mode")) { |
| | |
| | | } |
| | | |
| | | // For Win11 or later |
| | | static const auto &defaultMargins = |
| | | isSystemBorderEnabled() ? defaultExtraMargins : defaultEmptyMargins; |
| | | if (key == QStringLiteral("mica")) { |
| | | if (!isWin11OrGreater()) { |
| | | return false; |
| | |
| | | const BOOL enable = FALSE; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_MICA_EFFECT, &enable, sizeof(enable)); |
| | | } |
| | | apis.pDwmExtendFrameIntoClientArea(hwnd, &defaultMargins); |
| | | restoreMargins(); |
| | | } |
| | | return true; |
| | | } |
| | |
| | | const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_AUTO; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, |
| | | sizeof(backdropType)); |
| | | apis.pDwmExtendFrameIntoClientArea(hwnd, &defaultMargins); |
| | | restoreMargins(); |
| | | } |
| | | return true; |
| | | } |
| | |
| | | // wcad.cbData = sizeof(policy); |
| | | // apis.pSetWindowCompositionAttribute(hwnd, &wcad); |
| | | |
| | | apis.pDwmExtendFrameIntoClientArea(hwnd, &defaultMargins); |
| | | restoreMargins(); |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | if (key == QStringLiteral("dwm-blur")) { |
| | | // TODO: Optimize |
| | | // Currently not available!!! |
| | | 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); |
| | | // We can't extend the window frame for this effect. |
| | | restoreMargins(); |
| | | if (isWin8OrGreater()) { |
| | | ACCENT_POLICY policy{}; |
| | | policy.dwAccentState = ACCENT_ENABLE_BLURBEHIND; |
| | |
| | | bb.dwFlags = DWM_BB_ENABLE; |
| | | apis.pDwmEnableBlurBehindWindow(hwnd, &bb); |
| | | } |
| | | apis.pDwmExtendFrameIntoClientArea(hwnd, &defaultMargins); |
| | | } |
| | | return true; |
| | | } |
| | |
| | | LPARAM lParam, LRESULT *result) { |
| | | switch (message) { |
| | | case WM_SHOWWINDOW: { |
| | | if (!centered) { |
| | | if (!initialCentered) { |
| | | // If wParam is TRUE, the window is being shown. |
| | | // If lParam is zero, the message was sent because of a call to the ShowWindow |
| | | // function. |
| | | if (wParam && !lParam) { |
| | | centered = true; |
| | | initialCentered = true; |
| | | moveWindowToDesktopCenter(hWnd); |
| | | } |
| | | } |
| | |
| | | return true; |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | bool Win32WindowContext::needBorderPainter() const { |
| | | Q_UNUSED(this) |
| | | return isSystemBorderEnabled() && !isWin11OrGreater(); |
| | | } |
| | | |
| | | int Win32WindowContext::borderThickness() const { |
| | | return int(getWindowFrameBorderThickness(reinterpret_cast<HWND>(windowId))); |
| | | } |
| | | |
| | | } |