| | |
| | | // 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 <QtCore/QHash> |
| | | #include <QtCore/QScopeGuard> |
| | | #include <QtCore/QTimer> |
| | | #include <QtCore/QDateTime> |
| | | #include <QtGui/QGuiApplication> |
| | | #include <QtGui/QPainter> |
| | | #include <QtGui/QPalette> |
| | | #include <QtGui/QStyleHints> |
| | | |
| | | #include <QtCore/private/qwinregistry_p.h> |
| | | #include <QtCore/private/qsystemlibrary_p.h> |
| | | #include <QtGui/private/qhighdpiscaling_p.h> |
| | | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) |
| | | # include <QtGui/private/qguiapplication_p.h> |
| | |
| | | # include <QtGui/qpa/qplatformwindow_p.h> |
| | | #endif |
| | | |
| | | #include <shellscalingapi.h> |
| | | #include <dwmapi.h> |
| | | #include <timeapi.h> |
| | | #include <QWKCore/qwkconfig.h> |
| | | |
| | | #include "qwkglobal_p.h" |
| | | |
| | | #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) |
| | | Q_DECLARE_METATYPE(QMargins) |
| | | #endif |
| | | #include "qwkwindowsextra_p.h" |
| | | |
| | | namespace QWK { |
| | | |
| | | enum _DWMWINDOWATTRIBUTE { |
| | | // [set] BOOL, Allows the use of host backdrop brushes for the window. |
| | | _DWMWA_USE_HOSTBACKDROPBRUSH = 17, |
| | | |
| | | // Undocumented, the same with DWMWA_USE_IMMERSIVE_DARK_MODE, but available on systems |
| | | // before Win10 20H1. |
| | | _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19, |
| | | |
| | | // [set] BOOL, Allows a window to either use the accent color, or dark, according to the |
| | | // user Color Mode preferences. |
| | | _DWMWA_USE_IMMERSIVE_DARK_MODE = 20, |
| | | |
| | | // [set] WINDOW_CORNER_PREFERENCE, Controls the policy that rounds top-level window corners |
| | | _DWMWA_WINDOW_CORNER_PREFERENCE = 33, |
| | | |
| | | // [get] UINT, width of the visible border around a thick frame window |
| | | _DWMWA_VISIBLE_FRAME_BORDER_THICKNESS = 37, |
| | | |
| | | // [get, set] SYSTEMBACKDROP_TYPE, Controls the system-drawn backdrop material of a window, |
| | | // including behind the non-client area. |
| | | _DWMWA_SYSTEMBACKDROP_TYPE = 38, |
| | | |
| | | // Undocumented, use this value to enable Mica material on Win11 21H2. You should use |
| | | // DWMWA_SYSTEMBACKDROP_TYPE instead on Win11 22H2 and newer. |
| | | _DWMWA_MICA_EFFECT = 1029 |
| | | }; |
| | | |
| | | // The thickness of an auto-hide taskbar in pixels. |
| | | static constexpr const quint8 kAutoHideTaskBarThickness = 2; |
| | |
| | | // Original Qt window proc function |
| | | static WNDPROC g_qtWindowProc = nullptr; |
| | | |
| | | struct DynamicApis { |
| | | static const DynamicApis &instance() { |
| | | static const DynamicApis inst{}; |
| | | return inst; |
| | | } |
| | | |
| | | // template <typename T> |
| | | // struct DefaultFunc; |
| | | // |
| | | // template <typename Return, typename... Args> |
| | | // struct DefaultFunc<Return(QT_WIN_CALLBACK *)(Args...)> { |
| | | // static Return STDAPICALLTYPE func(Args...) { |
| | | // return Return{}; |
| | | // } |
| | | // }; |
| | | // |
| | | // #define DYNAMIC_API_DECLARE(NAME) decltype(&::NAME) p##NAME = |
| | | // DefaultFunc<decltype(&::NAME)>::func |
| | | |
| | | #define DYNAMIC_API_DECLARE(NAME) decltype(&::NAME) p##NAME = nullptr |
| | | |
| | | DYNAMIC_API_DECLARE(DwmFlush); |
| | | DYNAMIC_API_DECLARE(DwmIsCompositionEnabled); |
| | | DYNAMIC_API_DECLARE(DwmGetCompositionTimingInfo); |
| | | DYNAMIC_API_DECLARE(DwmGetWindowAttribute); |
| | | DYNAMIC_API_DECLARE(DwmSetWindowAttribute); |
| | | DYNAMIC_API_DECLARE(GetDpiForWindow); |
| | | DYNAMIC_API_DECLARE(GetSystemMetricsForDpi); |
| | | DYNAMIC_API_DECLARE(GetDpiForMonitor); |
| | | DYNAMIC_API_DECLARE(timeGetDevCaps); |
| | | DYNAMIC_API_DECLARE(timeBeginPeriod); |
| | | DYNAMIC_API_DECLARE(timeEndPeriod); |
| | | |
| | | #undef DYNAMIC_API_DECLARE |
| | | |
| | | private: |
| | | DynamicApis() { |
| | | #define DYNAMIC_API_RESOLVE(DLL, NAME) \ |
| | | p##NAME = reinterpret_cast<decltype(p##NAME)>(DLL.resolve(#NAME)) |
| | | |
| | | QSystemLibrary user32(QStringLiteral("user32")); |
| | | DYNAMIC_API_RESOLVE(user32, GetDpiForWindow); |
| | | DYNAMIC_API_RESOLVE(user32, GetSystemMetricsForDpi); |
| | | |
| | | QSystemLibrary shcore(QStringLiteral("shcore")); |
| | | DYNAMIC_API_RESOLVE(shcore, GetDpiForMonitor); |
| | | |
| | | QSystemLibrary dwmapi(QStringLiteral("dwmapi")); |
| | | DYNAMIC_API_RESOLVE(dwmapi, DwmFlush); |
| | | DYNAMIC_API_RESOLVE(dwmapi, DwmIsCompositionEnabled); |
| | | DYNAMIC_API_RESOLVE(dwmapi, DwmGetCompositionTimingInfo); |
| | | DYNAMIC_API_RESOLVE(dwmapi, DwmGetWindowAttribute); |
| | | DYNAMIC_API_RESOLVE(dwmapi, DwmSetWindowAttribute); |
| | | |
| | | QSystemLibrary winmm(QStringLiteral("winmm")); |
| | | DYNAMIC_API_RESOLVE(winmm, timeGetDevCaps); |
| | | DYNAMIC_API_RESOLVE(winmm, timeBeginPeriod); |
| | | DYNAMIC_API_RESOLVE(winmm, timeEndPeriod); |
| | | |
| | | #undef DYNAMIC_API_RESOLVE |
| | | } |
| | | |
| | | ~DynamicApis() = default; |
| | | |
| | | Q_DISABLE_COPY_MOVE(DynamicApis) |
| | | }; |
| | | |
| | | static inline constexpr bool operator==(const POINT &lhs, const POINT &rhs) noexcept { |
| | | return ((lhs.x == rhs.x) && (lhs.y == rhs.y)); |
| | | } |
| | | |
| | | static inline constexpr bool operator!=(const POINT &lhs, const POINT &rhs) noexcept { |
| | | return !operator==(lhs, rhs); |
| | | } |
| | | |
| | | static inline constexpr bool operator==(const SIZE &lhs, const SIZE &rhs) noexcept { |
| | | return ((lhs.cx == rhs.cx) && (lhs.cy == rhs.cy)); |
| | | } |
| | | |
| | | static inline constexpr bool operator!=(const SIZE &lhs, const SIZE &rhs) noexcept { |
| | | return !operator==(lhs, rhs); |
| | | } |
| | | |
| | | static inline constexpr bool operator>(const SIZE &lhs, const SIZE &rhs) noexcept { |
| | | return ((lhs.cx * lhs.cy) > (rhs.cx * rhs.cy)); |
| | | } |
| | | |
| | | static inline constexpr bool operator>=(const SIZE &lhs, const SIZE &rhs) noexcept { |
| | | return (operator>(lhs, rhs) || operator==(lhs, rhs)); |
| | | } |
| | | |
| | | static inline constexpr bool operator<(const SIZE &lhs, const SIZE &rhs) noexcept { |
| | | return (operator!=(lhs, rhs) && !operator>(lhs, rhs)); |
| | | } |
| | | |
| | | static inline constexpr bool operator<=(const SIZE &lhs, const SIZE &rhs) noexcept { |
| | | return (operator<(lhs, rhs) || operator==(lhs, rhs)); |
| | | } |
| | | |
| | | static inline constexpr bool operator==(const RECT &lhs, const RECT &rhs) noexcept { |
| | | return ((lhs.left == rhs.left) && (lhs.top == rhs.top) && (lhs.right == rhs.right) && |
| | | (lhs.bottom == rhs.bottom)); |
| | | } |
| | | |
| | | static inline constexpr bool operator!=(const RECT &lhs, const RECT &rhs) noexcept { |
| | | return !operator==(lhs, rhs); |
| | | } |
| | | |
| | | static inline constexpr QPoint point2qpoint(const POINT &point) { |
| | | return QPoint{int(point.x), int(point.y)}; |
| | | } |
| | | |
| | | static inline constexpr POINT qpoint2point(const QPoint &point) { |
| | | return POINT{LONG(point.x()), LONG(point.y())}; |
| | | } |
| | | |
| | | static inline constexpr QSize size2qsize(const SIZE &size) { |
| | | return QSize{int(size.cx), int(size.cy)}; |
| | | } |
| | | |
| | | static inline constexpr SIZE qsize2size(const QSize &size) { |
| | | return SIZE{LONG(size.width()), LONG(size.height())}; |
| | | } |
| | | |
| | | static inline constexpr QRect rect2qrect(const RECT &rect) { |
| | | return QRect{ |
| | | QPoint{int(rect.left), int(rect.top) }, |
| | | QSize{int(RECT_WIDTH(rect)), int(RECT_HEIGHT(rect))} |
| | | }; |
| | | } |
| | | |
| | | static inline constexpr RECT qrect2rect(const QRect &qrect) { |
| | | return RECT{LONG(qrect.left()), LONG(qrect.top()), LONG(qrect.right()), |
| | | LONG(qrect.bottom())}; |
| | | } |
| | | |
| | | static inline /*constexpr*/ QString hwnd2str(const WId windowId) { |
| | | // NULL handle is allowed here. |
| | | return QLatin1String("0x") + |
| | | QString::number(windowId, 16).toUpper().rightJustified(8, u'0'); |
| | | } |
| | | |
| | | static inline /*constexpr*/ QString hwnd2str(HWND hwnd) { |
| | | // NULL handle is allowed here. |
| | | return hwnd2str(reinterpret_cast<WId>(hwnd)); |
| | | } |
| | | |
| | | static inline bool isWin8OrGreater() { |
| | | static const bool result = IsWindows8OrGreater_Real(); |
| | | return result; |
| | | } |
| | | |
| | | static inline bool isWin8Point1OrGreater() { |
| | | static const bool result = IsWindows8Point1OrGreater_Real(); |
| | | return result; |
| | | } |
| | | |
| | | static inline bool isWin10OrGreater() { |
| | | static const bool result = IsWindows10OrGreater_Real(); |
| | | return result; |
| | | } |
| | | |
| | | static inline bool isWin11OrGreater() { |
| | | static const bool result = IsWindows11OrGreater_Real(); |
| | | return result; |
| | | } |
| | | |
| | | static inline bool isDwmCompositionEnabled() { |
| | | if (isWin8OrGreater()) { |
| | | return true; |
| | | } |
| | | const DynamicApis &apis = DynamicApis::instance(); |
| | | if (!apis.pDwmIsCompositionEnabled) { |
| | | return false; |
| | | } |
| | | BOOL enabled = FALSE; |
| | | return SUCCEEDED(apis.pDwmIsCompositionEnabled(&enabled)) && enabled; |
| | | } |
| | | |
| | | static inline bool isWindowFrameBorderColorized() { |
| | | const QWinRegistryKey registry(HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\DWM)"); |
| | | if (!registry.isValid()) { |
| | | return false; |
| | | } |
| | | const auto value = registry.dwordValue(L"ColorPrevalence"); |
| | | if (!value.second) { |
| | | return false; |
| | | } |
| | | return value.first; |
| | | } |
| | | |
| | | static inline bool isDarkThemeActive() { |
| | | #if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) |
| | | return QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark; |
| | | #else |
| | | const QWinRegistryKey registry( |
| | | HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\CurrentVersion\Themes\Personalize)"); |
| | | if (!registry.isValid()) { |
| | | return false; |
| | | } |
| | | const auto value = registry.dwordValue(L"AppsUseLightTheme"); |
| | | if (!value.second) { |
| | | return false; |
| | | } |
| | | return !value.first; |
| | | static inline bool |
| | | #if !QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDERS) |
| | | constexpr |
| | | #endif |
| | | } |
| | | |
| | | static inline bool isDarkWindowFrameEnabled(HWND hwnd) { |
| | | BOOL enabled = FALSE; |
| | | const DynamicApis &apis = DynamicApis::instance(); |
| | | if (SUCCEEDED(apis.pDwmGetWindowAttribute(hwnd, _DWMWA_USE_IMMERSIVE_DARK_MODE, &enabled, |
| | | sizeof(enabled)))) { |
| | | return enabled; |
| | | } else if (SUCCEEDED(apis.pDwmGetWindowAttribute(hwnd, |
| | | _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1, |
| | | &enabled, sizeof(enabled)))) { |
| | | return enabled; |
| | | } else { |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | static inline QColor getAccentColor() { |
| | | #if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) |
| | | return QGuiApplication::palette().color(QPalette::Accent); |
| | | isSystemBorderEnabled() { |
| | | return |
| | | #if QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDERS) |
| | | isWin10OrGreater() |
| | | #else |
| | | const QWinRegistryKey registry(HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\DWM)"); |
| | | if (!registry.isValid()) { |
| | | return {}; |
| | | } |
| | | const auto value = registry.dwordValue(L"AccentColor"); |
| | | if (!value.second) { |
| | | return {}; |
| | | } |
| | | // The retrieved value is in the #AABBGGRR format, we need to |
| | | // convert it to the #AARRGGBB format which Qt expects. |
| | | const QColor abgr = QColor::fromRgba(value.first); |
| | | if (!abgr.isValid()) { |
| | | return {}; |
| | | } |
| | | return QColor::fromRgb(abgr.blue(), abgr.green(), abgr.red(), abgr.alpha()); |
| | | false |
| | | #endif |
| | | ; |
| | | } |
| | | |
| | | static inline void triggerFrameChange(HWND hwnd) { |
| | |
| | | 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 updateInternalWindowFrameMargins(HWND hwnd, QWindow *window) { |
| | | const auto margins = [hwnd]() -> QMargins { |
| | | const auto titleBarHeight = int(getTitleBarHeight(hwnd)); |
| | | if (isWin10OrGreater()) { |
| | | return {0, -titleBarHeight, 0, 0}; |
| | | } else { |
| | | const auto frameSize = int(getResizeBorderThickness(hwnd)); |
| | | return {-frameSize, -titleBarHeight, -frameSize, -frameSize}; |
| | | } |
| | | }(); |
| | | static void setInternalWindowFrameMargins(QWindow *window, const QMargins &margins) { |
| | | const QVariant marginsVar = QVariant::fromValue(margins); |
| | | window->setProperty("_q_windowsCustomMargins", marginsVar); |
| | | #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) |
| | |
| | | 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 |
| | |
| | | return ::CallWindowProcW(g_qtWindowProc, hWnd, message, wParam, lParam); |
| | | } |
| | | |
| | | static inline void addManagedWindow(HWND hWnd, Win32WindowContext *ctx) { |
| | | static inline void addManagedWindow(QWindow *window, HWND hWnd, Win32WindowContext *ctx) { |
| | | const auto margins = [hWnd]() -> QMargins { |
| | | const auto titleBarHeight = int(getTitleBarHeight(hWnd)); |
| | | if (isSystemBorderEnabled()) { |
| | | return {0, -titleBarHeight, 0, 0}; |
| | | } else { |
| | | const auto frameSize = int(getResizeBorderThickness(hWnd)); |
| | | return {-frameSize, -titleBarHeight, -frameSize, -frameSize}; |
| | | } |
| | | }(); |
| | | |
| | | // Inform Qt we want and have set custom margins |
| | | setInternalWindowFrameMargins(window, margins); |
| | | |
| | | // Store original window proc |
| | | if (!g_qtWindowProc) { |
| | | g_qtWindowProc = reinterpret_cast<WNDPROC>(::GetWindowLongPtrW(hWnd, GWLP_WNDPROC)); |
| | |
| | | void Win32WindowContext::virtual_hook(int id, void *data) { |
| | | switch (id) { |
| | | case CentralizeHook: { |
| | | if (!windowId) |
| | | return; |
| | | const auto hwnd = reinterpret_cast<HWND>(windowId); |
| | | moveWindowToDesktopCenter(hwnd); |
| | | return; |
| | | } |
| | | |
| | | case RaiseWindowHook: { |
| | | if (!windowId) |
| | | return; |
| | | m_delegate->setWindowVisible(m_host, true); |
| | | const auto hwnd = reinterpret_cast<HWND>(windowId); |
| | | bringWindowToFront(hwnd); |
| | | return; |
| | | } |
| | | |
| | | case ShowSystemMenuHook: { |
| | | if (!windowId) |
| | | return; |
| | | const auto &pos = *static_cast<const QPoint *>(data); |
| | | auto hWnd = reinterpret_cast<HWND>(windowId); |
| | | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) |
| | |
| | | #endif |
| | | showSystemMenu2(hWnd, qpoint2point(nativeGlobalPos), false, |
| | | m_delegate->isHostSizeFixed(m_host)); |
| | | return; |
| | | } |
| | | |
| | | case WindowAttributeChangedHook: { |
| | | auto args = static_cast<void **>(data); |
| | | const auto &key = *static_cast<const QString *>(args[0]); |
| | | const auto &newVar = *static_cast<const QVariant *>(args[1]); |
| | | const auto &oldVar = *static_cast<const QVariant *>(args[2]); |
| | | |
| | | if (key == QStringLiteral("no-frame-shadow")) { |
| | | if (newVar.toBool()) { |
| | | // TODO: set off |
| | | } else { |
| | | // TODO: set on |
| | | } |
| | | } |
| | | |
| | | return; |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | case DrawWindows10BorderHook: { |
| | | #if QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDERS) |
| | | if (!windowId) |
| | | return; |
| | | |
| | | auto args = static_cast<void **>(data); |
| | | auto &painter = *static_cast<QPainter *>(args[0]); |
| | | const auto &rect = *static_cast<const QRect *>(args[1]); |
| | |
| | | const auto hwnd = reinterpret_cast<HWND>(windowId); |
| | | |
| | | QPen pen; |
| | | pen.setWidth(getWindowFrameBorderThickness(hwnd) * 2); |
| | | pen.setWidth(int(getWindowFrameBorderThickness(hwnd)) * 2); |
| | | |
| | | const bool dark = isDarkThemeActive() && isDarkWindowFrameEnabled(hwnd); |
| | | if (m_delegate->isWindowActive(m_host)) { |
| | |
| | | } |
| | | painter.save(); |
| | | |
| | | // We needs anti-aliasing to give us better result. |
| | | // We need antialiasing to give us better result. |
| | | painter.setRenderHint(QPainter::Antialiasing); |
| | | |
| | | painter.setPen(pen); |
| | |
| | | QPoint{m_windowHandle->width(), 0} |
| | | }); |
| | | painter.restore(); |
| | | #endif |
| | | return; |
| | | } |
| | | |
| | | default: { |
| | | break; |
| | | case DrawWindows10BorderHook2: { |
| | | #if QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDERS) |
| | | if (!m_windowHandle) |
| | | return; |
| | | |
| | | // https://github.com/microsoft/terminal/blob/71a6f26e6ece656084e87de1a528c4a8072eeabd/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp#L1025 |
| | | // https://docs.microsoft.com/en-us/windows/win32/dwm/customframe#extending-the-client-frame |
| | | // Draw a black rectangle to make Windows native top border show |
| | | |
| | | auto hWnd = reinterpret_cast<HWND>(windowId); |
| | | HDC hdc = ::GetDC(hWnd); |
| | | RECT windowRect{}; |
| | | ::GetClientRect(hWnd, &windowRect); |
| | | RECT rcTopBorder = { |
| | | 0, |
| | | 0, |
| | | RECT_WIDTH(windowRect), |
| | | int(getWindowFrameBorderThickness(hWnd)), |
| | | }; |
| | | ::FillRect(hdc, &rcTopBorder, |
| | | reinterpret_cast<HBRUSH>(::GetStockObject(BLACK_BRUSH))); |
| | | ::ReleaseDC(hWnd, hdc); |
| | | #endif |
| | | return; |
| | | } |
| | | |
| | | default: |
| | | break; |
| | | } |
| | | AbstractWindowContext::virtual_hook(id, data); |
| | | } |
| | | |
| | | bool Win32WindowContext::needBorderPainter() const { |
| | | return isWin10OrGreater() && !isWin11OrGreater(); |
| | | 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); |
| | | } |
| | | |
| | | int Win32WindowContext::borderThickness() const { |
| | | return getWindowFrameBorderThickness(reinterpret_cast<HWND>(windowId)); |
| | | } |
| | | void Win32WindowContext::winIdChanged() { |
| | | // If the original window id is valid, remove all resources related |
| | | if (windowId) { |
| | | removeManagedWindow(reinterpret_cast<HWND>(windowId)); |
| | | windowId = 0; |
| | | } |
| | | |
| | | void Win32WindowContext::winIdChanged(QWindow *oldWindow) { |
| | | removeManagedWindow(reinterpret_cast<HWND>(windowId)); |
| | | if (!m_windowHandle) { |
| | | return; |
| | | } |
| | |
| | | auto winId = m_windowHandle->winId(); |
| | | auto hWnd = reinterpret_cast<HWND>(winId); |
| | | |
| | | #if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) |
| | | for (const auto attr : { |
| | | _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1, |
| | | _DWMWA_USE_IMMERSIVE_DARK_MODE, |
| | | }) { |
| | | const BOOL enable = TRUE; |
| | | DynamicApis::instance().pDwmSetWindowAttribute(hWnd, attr, &enable, sizeof(enable)); |
| | | } |
| | | #endif |
| | | if (!isSystemBorderEnabled()) { |
| | | static auto margins = QVariant::fromValue(QMargins(1, 1, 1, 1)); |
| | | |
| | | // Inform Qt we want and have set custom margins |
| | | updateInternalWindowFrameMargins(hWnd, m_windowHandle); |
| | | // 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 (isSystemBorderEnabled()) { |
| | | ::SetWindowLongPtrW(hWnd, GWL_STYLE, style & (~WS_SYSMENU)); |
| | | } else { |
| | | ::SetWindowLongPtrW(hWnd, GWL_STYLE, |
| | | (style | WS_THICKFRAME | WS_CAPTION) & (~WS_SYSMENU)); |
| | | } |
| | | } |
| | | |
| | | // Add managed window |
| | | addManagedWindow(hWnd, this); |
| | | addManagedWindow(m_windowHandle, hWnd, this); |
| | | |
| | | // Cache win id |
| | | windowId = winId; |
| | |
| | | msg.wParam = wParam; |
| | | msg.lParam = lParam; |
| | | QT_NATIVE_EVENT_RESULT_TYPE res = 0; |
| | | if (dispatch(QByteArrayLiteral("windows_generic_MSG"), &msg, &res)) { |
| | | if (nativeDispatch(QByteArrayLiteral("windows_generic_MSG"), &msg, &res)) { |
| | | *result = LRESULT(res); |
| | | return true; |
| | | } |
| | | } |
| | | return false; // Not handled |
| | | } |
| | | |
| | | 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 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")) { |
| | | auto margins = qmargins2margins(attribute.value<QMargins>()); |
| | | return apis.pDwmExtendFrameIntoClientArea(hwnd, &margins) == S_OK; |
| | | } |
| | | |
| | | if (key == QStringLiteral("dark-mode")) { |
| | | if (!isWin101809OrGreater()) { |
| | | return false; |
| | | } |
| | | |
| | | BOOL enable = attribute.toBool(); |
| | | if (isWin101903OrGreater()) { |
| | | apis.pSetPreferredAppMode(enable ? PAM_AUTO : PAM_DEFAULT); |
| | | } 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)); |
| | | } |
| | | |
| | | apis.pFlushMenuThemes(); |
| | | return true; |
| | | } |
| | | |
| | | // For Win11 or later |
| | | if (key == QStringLiteral("mica")) { |
| | | if (!isWin11OrGreater()) { |
| | | 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); |
| | | if (isWin1122H2OrGreater()) { |
| | | // Use official DWM API to enable Mica, available since Windows 11 22H2 |
| | | // (10.0.22621). |
| | | const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_MAINWINDOW; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, |
| | | sizeof(backdropType)); |
| | | } else { |
| | | // Use undocumented DWM API to enable Mica, available since Windows 11 |
| | | // (10.0.22000). |
| | | const BOOL enable = TRUE; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_MICA_EFFECT, &enable, sizeof(enable)); |
| | | } |
| | | } else { |
| | | if (isWin1122H2OrGreater()) { |
| | | const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_AUTO; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, |
| | | sizeof(backdropType)); |
| | | } else { |
| | | const BOOL enable = FALSE; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_MICA_EFFECT, &enable, sizeof(enable)); |
| | | } |
| | | restoreMargins(); |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | if (key == QStringLiteral("mica-alt")) { |
| | | if (!isWin1122H2OrGreater()) { |
| | | 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); |
| | | // Use official DWM API to enable Mica Alt, available since Windows 11 22H2 |
| | | // (10.0.22621). |
| | | const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_TABBEDWINDOW; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, |
| | | sizeof(backdropType)); |
| | | } else { |
| | | const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_AUTO; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, |
| | | sizeof(backdropType)); |
| | | restoreMargins(); |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | if (key == QStringLiteral("acrylic-material")) { |
| | | if (!isWin11OrGreater()) { |
| | | 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); |
| | | |
| | | const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_TRANSIENTWINDOW; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, |
| | | sizeof(backdropType)); |
| | | |
| | | // PRIVATE API REFERENCE: |
| | | // QColor gradientColor = {}; |
| | | // ACCENT_POLICY policy{}; |
| | | // policy.dwAccentState = ACCENT_ENABLE_ACRYLICBLURBEHIND; |
| | | // policy.dwAccentFlags = ACCENT_ENABLE_ACRYLIC_WITH_LUMINOSITY; |
| | | // // This API expects the #AABBGGRR format. |
| | | // policy.dwGradientColor = |
| | | // DWORD(qRgba(gradientColor.blue(), gradientColor.green(), |
| | | // gradientColor.red(), gradientColor.alpha())); |
| | | // WINDOWCOMPOSITIONATTRIBDATA wcad{}; |
| | | // wcad.Attrib = WCA_ACCENT_POLICY; |
| | | // wcad.pvData = &policy; |
| | | // wcad.cbData = sizeof(policy); |
| | | // apis.pSetWindowCompositionAttribute(hwnd, &wcad); |
| | | } else { |
| | | const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_AUTO; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, |
| | | sizeof(backdropType)); |
| | | |
| | | // PRIVATE API REFERENCE: |
| | | // ACCENT_POLICY policy{}; |
| | | // policy.dwAccentState = ACCENT_DISABLED; |
| | | // policy.dwAccentFlags = ACCENT_NONE; |
| | | // WINDOWCOMPOSITIONATTRIBDATA wcad{}; |
| | | // wcad.Attrib = WCA_ACCENT_POLICY; |
| | | // wcad.pvData = &policy; |
| | | // wcad.cbData = sizeof(policy); |
| | | // apis.pSetWindowCompositionAttribute(hwnd, &wcad); |
| | | |
| | | restoreMargins(); |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | if (key == QStringLiteral("dwm-blur")) { |
| | | if (attribute.toBool()) { |
| | | // We can't extend the window frame for this effect. |
| | | restoreMargins(); |
| | | if (isWin8OrGreater()) { |
| | | ACCENT_POLICY policy{}; |
| | | policy.dwAccentState = ACCENT_ENABLE_BLURBEHIND; |
| | | policy.dwAccentFlags = ACCENT_NONE; |
| | | WINDOWCOMPOSITIONATTRIBDATA wcad{}; |
| | | wcad.Attrib = WCA_ACCENT_POLICY; |
| | | wcad.pvData = &policy; |
| | | wcad.cbData = sizeof(policy); |
| | | apis.pSetWindowCompositionAttribute(hwnd, &wcad); |
| | | } else { |
| | | DWM_BLURBEHIND bb{}; |
| | | bb.fEnable = TRUE; |
| | | bb.dwFlags = DWM_BB_ENABLE; |
| | | apis.pDwmEnableBlurBehindWindow(hwnd, &bb); |
| | | } |
| | | } else { |
| | | if (isWin8OrGreater()) { |
| | | ACCENT_POLICY policy{}; |
| | | policy.dwAccentState = ACCENT_DISABLED; |
| | | policy.dwAccentFlags = ACCENT_NONE; |
| | | WINDOWCOMPOSITIONATTRIBDATA wcad{}; |
| | | wcad.Attrib = WCA_ACCENT_POLICY; |
| | | wcad.pvData = &policy; |
| | | wcad.cbData = sizeof(policy); |
| | | apis.pSetWindowCompositionAttribute(hwnd, &wcad); |
| | | } else { |
| | | DWM_BLURBEHIND bb{}; |
| | | bb.fEnable = FALSE; |
| | | bb.dwFlags = DWM_BB_ENABLE; |
| | | apis.pDwmEnableBlurBehindWindow(hwnd, &bb); |
| | | } |
| | | } |
| | | return true; |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | QWK_USED static constexpr const struct { |
| | |
| | | // widget to be implicitly grabbed. After the menu is closed, Windows |
| | | // immediately sends WM_NCHITTEST, because the mouse is in the title bar |
| | | // draggable area, the result is HTCAPTION, so when the mouse is released, |
| | | // Windows sends WM_NCLBUTTONUP, which is a non-client area message, and it |
| | | // Windows sends WM_NCLBUTTONUP, which is a non-client message, and it |
| | | // will be ignored by Qt. As a consequence, QWidgetWindow can't receive a |
| | | // mouse release messages in the client area, so the grab remains, and other |
| | | // mouse release message in the client area, so the grab remains, and other |
| | | // widgets cannot receive mouse events. |
| | | |
| | | // Since we didn't watch the menu window, we cannot capture any mouse |
| | |
| | | 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); |
| | | } |
| | | } |
| | |
| | | int frameSize = getResizeBorderThickness(hWnd); |
| | | bool isTop = (nativeLocalPos.y < frameSize); |
| | | |
| | | if (isWin10OrGreater()) { |
| | | if (isSystemBorderEnabled()) { |
| | | // This will handle the left, right and bottom parts of the frame |
| | | // because we didn't change them. |
| | | LRESULT originalHitTestResult = ::DefWindowProcW(hWnd, WM_NCHITTEST, 0, lParam); |
| | |
| | | *result = (isTitleBar ? HTCAPTION : HTCLIENT); |
| | | return true; |
| | | } |
| | | // At this point, we know that the cursor is inside the client area |
| | | // At this point, we know that the cursor is inside the client area, |
| | | // so it has to be either the little border at the top of our custom |
| | | // 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 |
| | |
| | | break; |
| | | } |
| | | |
| | | if (!isWin10OrGreater()) { |
| | | if (!isSystemBorderEnabled()) { |
| | | switch (message) { |
| | | case WM_NCUAHDRAWCAPTION: |
| | | case WM_NCUAHDRAWFRAME: { |
| | |
| | | // window activation state change. |
| | | *result = ::DefWindowProcW(hWnd, WM_NCACTIVATE, wParam, -1); |
| | | } else { |
| | | if (wParam) { |
| | | *result = FALSE; |
| | | } else { |
| | | *result = TRUE; |
| | | } |
| | | *result = TRUE; |
| | | } |
| | | return true; |
| | | } |
| | |
| | | // 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); |
| | | if (isWin10OrGreater()) { |
| | | if (isSystemBorderEnabled()) { |
| | | // Store the original top margin before the default window procedure applies the |
| | | // default frame. |
| | | const LONG originalTop = clientRect->top; |
| | |
| | | // technique to bring the top border back. |
| | | clientRect->top = originalTop; |
| | | } |
| | | |
| | | const bool max = IsMaximized(hWnd); |
| | | const bool full = isFullScreen(hWnd); |
| | | // We don't need this correction when we're fullscreen. We will |
| | |
| | | // a window when it's maximized unless you restore it). |
| | | const quint32 frameSize = getResizeBorderThickness(hWnd); |
| | | clientRect->top += frameSize; |
| | | if (!isWin10OrGreater()) { |
| | | if (!isSystemBorderEnabled()) { |
| | | clientRect->bottom -= frameSize; |
| | | clientRect->left += frameSize; |
| | | clientRect->right -= frameSize; |
| | |
| | | const auto getNativeGlobalPosFromKeyboard = [hWnd]() -> POINT { |
| | | const bool maxOrFull = IsMaximized(hWnd) || isFullScreen(hWnd); |
| | | const quint32 frameSize = getResizeBorderThickness(hWnd); |
| | | const quint32 horizontalOffset = ((maxOrFull || !isWin10OrGreater()) ? 0 : frameSize); |
| | | const quint32 horizontalOffset = |
| | | ((maxOrFull || !isSystemBorderEnabled()) ? 0 : frameSize); |
| | | const auto verticalOffset = [hWnd, maxOrFull, frameSize]() -> quint32 { |
| | | const quint32 titleBarHeight = getTitleBarHeight(hWnd); |
| | | if (!isWin10OrGreater()) { |
| | | if (!isSystemBorderEnabled()) { |
| | | return titleBarHeight; |
| | | } |
| | | if (isWin11OrGreater()) { |