From 2f6c83c095724bbba0f43b2f2893ba73c17949a6 Mon Sep 17 00:00:00 2001 From: Zhao Yuhang <2546789017@qq.com> Date: 周一, 11 12月 2023 21:57:40 +0800 Subject: [PATCH] add quick border --- src/core/contexts/win32windowcontext.cpp | 475 ++++++++++++++++++++++++++++++++++++++-------------------- 1 files changed, 311 insertions(+), 164 deletions(-) diff --git a/src/core/contexts/win32windowcontext.cpp b/src/core/contexts/win32windowcontext.cpp index 9581b94..78a54c5 100644 --- a/src/core/contexts/win32windowcontext.cpp +++ b/src/core/contexts/win32windowcontext.cpp @@ -1,13 +1,15 @@ #include "win32windowcontext_p.h" -#include "qwkcoreglobal_p.h" #include <optional> #include <QtCore/QHash> -#include <QtCore/QAbstractNativeEventFilter> #include <QtCore/QScopeGuard> #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) @@ -23,14 +25,36 @@ #include <shellscalingapi.h> #include <dwmapi.h> #include <timeapi.h> -#include <versionhelpers.h> +#include "nativeeventfilter.h" + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) Q_DECLARE_METATYPE(QMargins) +#endif namespace QWK { + using _DWMWINDOWATTRIBUTE = enum _DWMWINDOWATTRIBUTE + { + _DWMWA_USE_HOSTBACKDROPBRUSH = 17, // [set] BOOL, Allows the use of host backdrop brushes for the window. + _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19, // Undocumented, the same with DWMWA_USE_IMMERSIVE_DARK_MODE, but available on systems before Win10 20H1. + _DWMWA_USE_IMMERSIVE_DARK_MODE = 20, // [set] BOOL, Allows a window to either use the accent color, or dark, according to the user Color Mode preferences. + _DWMWA_WINDOW_CORNER_PREFERENCE = 33, // [set] WINDOW_CORNER_PREFERENCE, Controls the policy that rounds top-level window corners + _DWMWA_VISIBLE_FRAME_BORDER_THICKNESS = 37, // [get] UINT, width of the visible border around a thick frame window + _DWMWA_SYSTEMBACKDROP_TYPE = 38, // [get, set] SYSTEMBACKDROP_TYPE, Controls the system-drawn backdrop material of a window, including behind the non-client area. + _DWMWA_MICA_EFFECT = 1029 // Undocumented, use this value to enable Mica material on Win11 21H2. You should use DWMWA_SYSTEMBACKDROP_TYPE instead on Win11 22H2 and newer. + }; + // The thickness of an auto-hide taskbar in pixels. static constexpr const auto kAutoHideTaskBarThickness = quint8{2}; + + static inline constexpr const auto kFrameBorderActiveColorLight = + QColor{110, 110, 110}; // #6E6E6E + static inline constexpr const auto kFrameBorderActiveColorDark = QColor{51, 51, 51}; // #333333 + static inline constexpr const auto kFrameBorderInactiveColorLight = + QColor{167, 167, 167}; // #A7A7A7 + static inline constexpr const auto kFrameBorderInactiveColorDark = + QColor{61, 61, 62}; // #3D3D3E // hWnd -> context using WndProcHash = QHash<HWND, Win32WindowContext *>; @@ -50,6 +74,11 @@ } g_hook{}; struct DynamicApis { + static const DynamicApis &instance() { + static const DynamicApis inst{}; + return inst; + } + // template <typename T> // struct DefaultFunc; // @@ -60,56 +89,52 @@ // } // }; // -// #define DWM_API_DECLARE(NAME) decltype(&::NAME) p##NAME = DefaultFunc<decltype(&::NAME)>::func -#define DWM_API_DECLARE(NAME) decltype(&::NAME) p##NAME = nullptr +// #define DYNAMIC_API_DECLARE(NAME) decltype(&::NAME) p##NAME = +// DefaultFunc<decltype(&::NAME)>::func - DWM_API_DECLARE(DwmFlush); - DWM_API_DECLARE(DwmIsCompositionEnabled); - DWM_API_DECLARE(DwmGetCompositionTimingInfo); - DWM_API_DECLARE(GetDpiForWindow); - DWM_API_DECLARE(GetSystemMetricsForDpi); - DWM_API_DECLARE(GetDpiForMonitor); - DWM_API_DECLARE(timeGetDevCaps); - DWM_API_DECLARE(timeBeginPeriod); - DWM_API_DECLARE(timeEndPeriod); +#define DYNAMIC_API_DECLARE(NAME) decltype(&::NAME) p##NAME = nullptr -#undef DWM_API_DECLARE + DYNAMIC_API_DECLARE(DwmFlush); + DYNAMIC_API_DECLARE(DwmIsCompositionEnabled); + DYNAMIC_API_DECLARE(DwmGetCompositionTimingInfo); + DYNAMIC_API_DECLARE(DwmGetWindowAttribute); + 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")); - pGetDpiForWindow = - reinterpret_cast<decltype(pGetDpiForWindow)>(user32.resolve("GetDpiForWindow")); - pGetSystemMetricsForDpi = reinterpret_cast<decltype(pGetSystemMetricsForDpi)>( - user32.resolve("GetSystemMetricsForDpi")); + DYNAMIC_API_RESOLVE(user32, GetDpiForWindow); + DYNAMIC_API_RESOLVE(user32, GetSystemMetricsForDpi); QSystemLibrary shcore(QStringLiteral("shcore")); - pGetDpiForMonitor = - reinterpret_cast<decltype(pGetDpiForMonitor)>(shcore.resolve("GetDpiForMonitor")); + DYNAMIC_API_RESOLVE(shcore, GetDpiForMonitor); QSystemLibrary dwmapi(QStringLiteral("dwmapi")); - pDwmFlush = reinterpret_cast<decltype(pDwmFlush)>(dwmapi.resolve("DwmFlush")); - pDwmIsCompositionEnabled = reinterpret_cast<decltype(pDwmIsCompositionEnabled)>( - dwmapi.resolve("DwmIsCompositionEnabled")); - pDwmGetCompositionTimingInfo = reinterpret_cast<decltype(pDwmGetCompositionTimingInfo)>( - dwmapi.resolve("DwmGetCompositionTimingInfo")); + DYNAMIC_API_RESOLVE(dwmapi, DwmFlush); + DYNAMIC_API_RESOLVE(dwmapi, DwmIsCompositionEnabled); + DYNAMIC_API_RESOLVE(dwmapi, DwmGetCompositionTimingInfo); + DYNAMIC_API_RESOLVE(dwmapi, DwmGetWindowAttribute); QSystemLibrary winmm(QStringLiteral("winmm")); - ptimeGetDevCaps = - reinterpret_cast<decltype(ptimeGetDevCaps)>(winmm.resolve("timeGetDevCaps")); - ptimeBeginPeriod = - reinterpret_cast<decltype(ptimeBeginPeriod)>(winmm.resolve("timeBeginPeriod")); - ptimeEndPeriod = - reinterpret_cast<decltype(ptimeEndPeriod)>(winmm.resolve("timeEndPeriod")); + DYNAMIC_API_RESOLVE(winmm, timeGetDevCaps); + DYNAMIC_API_RESOLVE(winmm, timeBeginPeriod); + DYNAMIC_API_RESOLVE(winmm, timeEndPeriod); + +#undef DYNAMIC_API_RESOLVE } ~DynamicApis() = default; - static const DynamicApis &instance() { - static const DynamicApis inst{}; - return inst; - } - - private: Q_DISABLE_COPY_MOVE(DynamicApis) }; @@ -194,22 +219,22 @@ } static inline bool isWin8OrGreater() { - static const bool result = ::IsWindows8OrGreater(); + static const bool result = IsWindows8OrGreater_Real(); return result; } static inline bool isWin8Point1OrGreater() { - static const bool result = ::IsWindows8Point1OrGreater(); + static const bool result = IsWindows8Point1OrGreater_Real(); return result; } static inline bool isWin10OrGreater() { - static const bool result = ::IsWindows10OrGreater(); + static const bool result = IsWindows10OrGreater_Real(); return result; } static inline bool isWin11OrGreater() { - static const bool result = ::IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN10), LOBYTE(_WIN32_WINNT_WIN10), 22000); + static const bool result = IsWindows11OrGreater_Real(); return result; } @@ -223,6 +248,69 @@ } 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; +#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); +#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()); +#endif } static inline void triggerFrameChange(HWND hwnd) { @@ -244,9 +332,21 @@ } else { // Win2K HDC hdc = ::GetDC(nullptr); const int dpiX = ::GetDeviceCaps(hdc, LOGPIXELSX); - const int dpiY = ::GetDeviceCaps(hdc, LOGPIXELSY); + //const int dpiY = ::GetDeviceCaps(hdc, LOGPIXELSY); ::ReleaseDC(nullptr, hdc); return quint32(dpiX); + } + } + + static inline quint32 getWindowFrameBorderThickness(HWND hwnd) { + UINT result{ 0 }; + const DynamicApis &apis = DynamicApis::instance(); + if (SUCCEEDED(apis.pDwmGetWindowAttribute(hwnd, _DWMWA_VISIBLE_FRAME_BORDER_THICKNESS, &result, sizeof(result)))) { + return result; + } else { + const quint32 dpi = getDpiForWindow(hwnd); + result = quint32(std::round(qreal(1) * qreal(dpi) / qreal(USER_DEFAULT_SCREEN_DPI))); + return result; } } @@ -276,11 +376,11 @@ static inline void updateInternalWindowFrameMargins(HWND hwnd, QWindow *window) { const auto margins = [hwnd]() -> QMargins { - const int titleBarHeight = getTitleBarHeight(hwnd); + const auto titleBarHeight = int(getTitleBarHeight(hwnd)); if (isWin10OrGreater()) { return {0, -titleBarHeight, 0, 0}; } else { - const int frameSize = getResizeBorderThickness(hwnd); + const auto frameSize = int(getResizeBorderThickness(hwnd)); return {-frameSize, -titleBarHeight, -frameSize, -frameSize}; } }(); @@ -289,7 +389,7 @@ #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) if (QPlatformWindow *platformWindow = window->handle()) { if (const auto ni = QGuiApplication::platformNativeInterface()) { - ni->setWindowProperty(platformWindow, QStringLiteral("_q_windowsCustomMargins"), + ni->setWindowProperty(platformWindow, QStringLiteral("WindowsCustomMargins"), marginsVar); } } @@ -385,8 +485,8 @@ apis.ptimeEndPeriod(ms_granularity); } - static inline void showSystemMenu2(HWND hWnd, const POINT &pos, const bool selectFirstEntry) - { + static inline void showSystemMenu2(HWND hWnd, const POINT &pos, const bool selectFirstEntry, + const bool fixedSize) { const HMENU hMenu = ::GetSystemMenu(hWnd, FALSE); if (!hMenu) { // The corresponding window doesn't have a system menu, most likely due to the @@ -395,97 +495,53 @@ return; } - // Tweak the menu items according to the current window status and user settings. - const bool disableClose = /*data->callbacks->getProperty(kSysMenuDisableCloseVar, false).toBool()*/false; - const bool disableRestore = /*data->callbacks->getProperty(kSysMenuDisableRestoreVar, false).toBool()*/false; - const bool disableMinimize = /*data->callbacks->getProperty(kSysMenuDisableMinimizeVar, false).toBool()*/false; - const bool disableMaximize = /*data->callbacks->getProperty(kSysMenuDisableMaximizeVar, false).toBool()*/false; - const bool disableSize = /*data->callbacks->getProperty(kSysMenuDisableSizeVar, false).toBool()*/false; - const bool disableMove = /*data->callbacks->getProperty(kSysMenuDisableMoveVar, false).toBool()*/false; - const bool removeClose = /*data->callbacks->getProperty(kSysMenuRemoveCloseVar, false).toBool()*/false; - const bool removeSeparator = /*data->callbacks->getProperty(kSysMenuRemoveSeparatorVar, false).toBool()*/false; - const bool removeRestore = /*data->callbacks->getProperty(kSysMenuRemoveRestoreVar, false).toBool()*/false; - const bool removeMinimize = /*data->callbacks->getProperty(kSysMenuRemoveMinimizeVar, false).toBool()*/false; - const bool removeMaximize = /*data->callbacks->getProperty(kSysMenuRemoveMaximizeVar, false).toBool()*/false; - const bool removeSize = /*data->callbacks->getProperty(kSysMenuRemoveSizeVar, false).toBool()*/false; - const bool removeMove = /*data->callbacks->getProperty(kSysMenuRemoveMoveVar, false).toBool()*/false; const bool maxOrFull = IsMaximized(hWnd) || isFullScreen(hWnd); - const bool fixedSize = /*data->callbacks->isWindowFixedSize()*/false; - if (removeClose) { - ::DeleteMenu(hMenu, SC_CLOSE, MF_BYCOMMAND); - } else { - ::EnableMenuItem(hMenu, SC_CLOSE, (MF_BYCOMMAND | (disableClose ? MFS_DISABLED : MFS_ENABLED))); - } - if (removeSeparator) { - // Looks like we must use 0 for the second parameter here, otherwise we can't remove the separator. - ::DeleteMenu(hMenu, 0, MFT_SEPARATOR); - } - if (removeMaximize) { - ::DeleteMenu(hMenu, SC_MAXIMIZE, MF_BYCOMMAND); - } else { - ::EnableMenuItem(hMenu, SC_MAXIMIZE, (MF_BYCOMMAND | ((maxOrFull || fixedSize || disableMaximize) ? MFS_DISABLED : MFS_ENABLED))); - } - if (removeRestore) { - ::DeleteMenu(hMenu, SC_RESTORE, MF_BYCOMMAND); - } else { - ::EnableMenuItem(hMenu, SC_RESTORE, (MF_BYCOMMAND | ((maxOrFull && !fixedSize && !disableRestore) ? 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 - // this manually: the highlighted menu item is really only highlighted, - // not selected, so even if the mouse cursor hovers on other menu items - // or the user navigates to other menu items through keyboard, the original - // highlight bar will not move accordingly, the OS will generate another - // highlight bar to indicate the current selected menu item, which will make - // 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))); - } - if (removeMinimize) { - ::DeleteMenu(hMenu, SC_MINIMIZE, MF_BYCOMMAND); - } else { - ::EnableMenuItem(hMenu, SC_MINIMIZE, (MF_BYCOMMAND | (disableMinimize ? MFS_DISABLED : MFS_ENABLED))); - } - if (removeSize) { - ::DeleteMenu(hMenu, SC_SIZE, MF_BYCOMMAND); - } else { - ::EnableMenuItem(hMenu, SC_SIZE, (MF_BYCOMMAND | ((maxOrFull || fixedSize || disableSize || disableMinimize || disableMaximize) ? MFS_DISABLED : MFS_ENABLED))); - } - if (removeMove) { - ::DeleteMenu(hMenu, SC_MOVE, MF_BYCOMMAND); - } else { - ::EnableMenuItem(hMenu, SC_MOVE, (MF_BYCOMMAND | ((maxOrFull || disableMove) ? MFS_DISABLED : MFS_ENABLED))); - } + ::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))); + // 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 + // this manually: the highlighted menu item is really only highlighted, + // not selected, so even if the mouse cursor hovers on other menu items + // or the user navigates to other menu items through keyboard, the original + // highlight bar will not move accordingly, the OS will generate another + // highlight bar to indicate the current selected menu item, which will make + // 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_SIZE, + (MF_BYCOMMAND | ((maxOrFull || fixedSize) ? MFS_DISABLED : MFS_ENABLED))); + ::EnableMenuItem(hMenu, SC_MOVE, (MF_BYCOMMAND | (maxOrFull ? MFS_DISABLED : MFS_ENABLED))); // The default menu item will appear in bold font. There can only be one default // menu item per menu at most. Set the item ID to "UINT_MAX" (or simply "-1") // can clear the default item for the given menu. - std::optional<UINT> defaultItemId = std::nullopt; + UINT defaultItemId = UINT_MAX; if (isWin11OrGreater()) { if (maxOrFull) { - if (!removeRestore) { - defaultItemId = SC_RESTORE; - } + defaultItemId = SC_RESTORE; } else { - if (!removeMaximize) { - defaultItemId = SC_MAXIMIZE; - } + defaultItemId = SC_MAXIMIZE; } } - if (!(defaultItemId.has_value() || removeClose)) { + if (defaultItemId == UINT_MAX) { defaultItemId = SC_CLOSE; } - ::SetMenuDefaultItem(hMenu, defaultItemId.value_or(UINT_MAX), FALSE); - - ::DrawMenuBar(hWnd); + ::SetMenuDefaultItem(hMenu, defaultItemId, FALSE); // Popup the system menu at the required position. - const auto result = ::TrackPopupMenu(hMenu, (TPM_RETURNCMD | (QGuiApplication::isRightToLeft() ? TPM_RIGHTALIGN : TPM_LEFTALIGN)), pos.x, pos.y, 0, hWnd, nullptr); + const auto result = ::TrackPopupMenu( + hMenu, + (TPM_RETURNCMD | (QGuiApplication::isRightToLeft() ? TPM_RIGHTALIGN : TPM_LEFTALIGN)), + pos.x, pos.y, 0, hWnd, nullptr); - if (!removeRestore) { - // Unhighlight the first menu item after the popup menu is closed, otherwise it will keep - // highlighting until we unhighlight it manually. - ::HiliteMenuItem(hWnd, hMenu, SC_RESTORE, (MF_BYCOMMAND | MFS_UNHILITE)); - } + // Unhighlight the first menu item after the popup menu is closed, otherwise it will keep + // highlighting until we unhighlight it manually. + ::HiliteMenuItem(hWnd, hMenu, SC_RESTORE, (MF_BYCOMMAND | MFS_UNHILITE)); if (!result) { // The user canceled the menu, no need to continue. @@ -520,7 +576,8 @@ case HTBORDER: return Win32WindowContext::FixedBorder; default: - break; // unreachable + // unreachable + break; } return Win32WindowContext::Outside; } @@ -564,7 +621,7 @@ // DefWindowProc(). Consequently, we have to add a global native filter that forwards the result // of the hook function, telling Qt whether we have filtered the events before. Since Qt only // handles Windows window messages in the main thread, it is safe to do so. - class WindowsNativeEventFilter : public QAbstractNativeEventFilter { + class WindowsNativeEventFilter : public NativeEventFilter { public: bool nativeEventFilter(const QByteArray &eventType, void *message, QT_NATIVE_EVENT_RESULT_TYPE *result) override { @@ -599,14 +656,12 @@ return; } instance = new WindowsNativeEventFilter(); - installNativeEventFilter(instance); } static inline void uninstall() { if (!instance) { return; } - removeNativeEventFilter(instance); delete instance; instance = nullptr; } @@ -714,6 +769,85 @@ } } + QString Win32WindowContext::key() const { + return QStringLiteral("win32"); + } + + void Win32WindowContext::virtual_hook(int id, void *data) { + switch (id) { + case CentralizeHook: { + const auto hwnd = reinterpret_cast<HWND>(m_windowHandle->winId()); + moveToDesktopCenter(hwnd); + return; + } + case ShowSystemMenuHook: { + const auto &pos = *static_cast<const QPoint *>(data); + auto hWnd = reinterpret_cast<HWND>(m_windowHandle->winId()); + showSystemMenu2(hWnd, qpoint2point(pos), false, + m_delegate->isHostSizeFixed(m_host)); + return; + } + case NeedsDrawBordersHook: { + auto &result = *static_cast<bool *>(data); + result = isWin10OrGreater() && !isWin11OrGreater(); + return; + } + case DrawBordersHook: { + auto args = static_cast<void **>(data); + auto &painter = *static_cast<QPainter *>(args[0]); + const auto &rect = *static_cast<const QRect *>(args[1]); + const auto ®ion = *static_cast<const QRegion *>(args[2]); + const auto hwnd = reinterpret_cast<HWND>(m_windowHandle->winId()); + QPen pen{}; + const auto borderThickness = int(QHighDpi::fromNativePixels(getWindowFrameBorderThickness(hwnd), m_windowHandle)); + pen.setWidth(borderThickness * 2); + const bool active = m_host->isWidgetType() ? m_host->property("isActiveWindow").toBool() : m_host->property("active").toBool(); + const bool dark = isDarkThemeActive() && isDarkWindowFrameEnabled(hwnd); + if (active) { + if (isWindowFrameBorderColorized()) { + pen.setColor(getAccentColor()); + } else { + if (dark) { + pen.setColor(kFrameBorderActiveColorDark); + } else { + pen.setColor(kFrameBorderActiveColorLight); + } + } + } else { + if (dark) { + pen.setColor(kFrameBorderInactiveColorDark); + } else { + pen.setColor(kFrameBorderInactiveColorLight); + } + } + painter.save(); + painter.setRenderHint(QPainter::Antialiasing); // ### TODO: do we need to enable or disable it? + painter.setPen(pen); + painter.drawLine(QLine{ QPoint{ 0, 0 }, QPoint{ rect.width(), 0 } }); + painter.restore(); + return; + } + case QueryBorderThicknessHook: { + auto args = static_cast<void **>(data); + const bool requireNative = *static_cast<const bool *>(args[0]); + quint32 &thickness = *static_cast<quint32 *>(args[1]); + const auto hwnd = reinterpret_cast<HWND>(m_windowHandle->winId()); + const auto nativeThickness = getWindowFrameBorderThickness(hwnd); + if (requireNative) { + thickness = nativeThickness; + } else { + thickness = QHighDpi::fromNativePixels(nativeThickness, m_windowHandle); + } + return; + } + default: { + // unreachable + break; + } + } + AbstractWindowContext::virtual_hook(id, data); + } + bool Win32WindowContext::setupHost() { // Install window hook auto winId = m_windowHandle->winId(); @@ -764,10 +898,6 @@ return false; } - if (systemMenuHandler(hWnd, message, wParam, lParam, result)) { - return true; - } - // Test snap layout if (snapLayoutHandler(hWnd, message, wParam, lParam, result)) { return true; @@ -778,14 +908,17 @@ return true; } + if (systemMenuHandler(hWnd, message, wParam, lParam, result)) { + return true; + } + return false; // Not handled } - static constexpr const auto kMessageTag = WPARAM(0x97CCEA99); - - static inline constexpr bool isTaggedMessage(WPARAM wParam) { - return (wParam == kMessageTag); - } + static constexpr const struct { + const WPARAM wParam = 0xF1C9ADD4; + const LPARAM lParam = 0xAFB6F4C6; + } kMessageTag; static inline quint64 getKeyState() { quint64 result = 0; @@ -825,7 +958,7 @@ // wParam is always ignored in mouse leave messages, but here we // give them a special tag to be able to distinguish which messages // are sent by ourselves. - return kMessageTag; + return kMessageTag.wParam; } const quint64 keyState = getKeyState(); if ((myMsg >= WM_NCXBUTTONDOWN) && (myMsg <= WM_NCXBUTTONDBLCLK)) { @@ -903,6 +1036,7 @@ SEND_MESSAGE(hWnd, WM_MOUSELEAVE, wParamNew, lParamNew); break; default: + // unreachable break; } @@ -925,7 +1059,7 @@ LPARAM lParam, LRESULT *result) { switch (message) { case WM_MOUSELEAVE: { - if (!isTaggedMessage(wParam)) { + if (wParam == kMessageTag.wParam) { // Qt will call TrackMouseEvent() to get the WM_MOUSELEAVE message when it // receives WM_MOUSEMOVE messages, and since we are converting every // WM_NCMOUSEMOVE message to WM_MOUSEMOVE message and send it back to the window @@ -937,9 +1071,9 @@ DWORD dwScreenPos = ::GetMessagePos(); POINT screenPoint{GET_X_LPARAM(dwScreenPos), GET_Y_LPARAM(dwScreenPos)}; ::ScreenToClient(hWnd, &screenPoint); - QPoint qtScenePos = QHighDpi::fromNativeLocalPosition( - QPoint{screenPoint.x, screenPoint.y}, m_windowHandle); - auto dummy = CoreWindowAgent::Unknown; + QPoint qtScenePos = QHighDpi::fromNativeLocalPosition(point2qpoint(screenPoint), + m_windowHandle); + auto dummy = WindowAgentBase::Unknown; if (isInSystemButtons(qtScenePos, &dummy)) { // We must record whether the last WM_MOUSELEAVE was filtered, because if // Qt does not receive this message it will not call TrackMouseEvent() @@ -1170,14 +1304,14 @@ auto clientWidth = RECT_WIDTH(clientRect); auto clientHeight = RECT_HEIGHT(clientRect); - QPoint qtScenePos = QHighDpi::fromNativeLocalPosition( - QPoint(nativeLocalPos.x, nativeLocalPos.y), m_windowHandle); + QPoint qtScenePos = + QHighDpi::fromNativeLocalPosition(point2qpoint(nativeLocalPos), m_windowHandle); bool isFixedSize = m_delegate->isHostSizeFixed(m_host); bool isTitleBar = isInTitleBarDraggableArea(qtScenePos); bool dontOverrideCursor = false; // ### TODO - CoreWindowAgent::SystemButton sysButtonType = CoreWindowAgent::Unknown; + WindowAgentBase::SystemButton sysButtonType = WindowAgentBase::Unknown; if (!isFixedSize && isInSystemButtons(qtScenePos, &sysButtonType)) { // Firstly, we set the hit test result to a default value to be able to detect // whether we have changed it or not afterwards. @@ -1187,22 +1321,31 @@ // this is also the normal behavior of a native Win32 window (but only when the // window is not maximized/fullscreen/minimized, of course). if (isWindowNoState(hWnd)) { - static constexpr const int kBorderSize = 2; + static constexpr const auto kBorderSize = quint8{2}; bool isTop = (nativeLocalPos.y <= kBorderSize); + bool isLeft = nativeLocalPos.x <= kBorderSize; bool isRight = (nativeLocalPos.x >= (clientWidth - kBorderSize)); - if (isTop || isRight) { + if (isTop || isLeft || isRight) { if (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); } else { - if (isTop && isRight) { - *result = HTTOPRIGHT; - } else if (isTop) { - *result = HTTOP; + if (isTop) { + if (isLeft) { + *result = HTTOPLEFT; + } else if (isRight) { + *result = HTTOPRIGHT; + } else { + *result = HTTOP; + } } else { - *result = HTRIGHT; + if (isLeft) { + *result = HTLEFT; + } else { + *result = HTRIGHT; + } } } } @@ -1212,23 +1355,24 @@ // exact role of our button. The Snap Layout feature introduced in Windows // 11 won't work without this. switch (sysButtonType) { - case CoreWindowAgent::WindowIcon: + case WindowAgentBase::WindowIcon: *result = HTSYSMENU; break; - case CoreWindowAgent::Help: + case WindowAgentBase::Help: *result = HTHELP; break; - case CoreWindowAgent::Minimize: + case WindowAgentBase::Minimize: *result = HTREDUCE; break; - case CoreWindowAgent::Maximize: + case WindowAgentBase::Maximize: *result = HTZOOM; break; - case CoreWindowAgent::Close: + case WindowAgentBase::Close: *result = HTCLOSE; break; default: - break; // unreachable + // unreachable + break; } } if (*result == HTNOWHERE) { @@ -1302,9 +1446,9 @@ 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 scaledFrameSizeX = std::round(qreal(frameSize) * scaleFactor); - const bool isLeft = (nativeLocalPos.x < scaledFrameSizeX); - const bool isRight = (nativeLocalPos.x >= (clientWidth - scaledFrameSizeX)); + 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 @@ -1674,7 +1818,8 @@ }(); RECT windowPos{}; ::GetWindowRect(hWnd, &windowPos); - return {static_cast<LONG>(windowPos.left + horizontalOffset), static_cast<LONG>(windowPos.top + verticalOffset)}; + return {static_cast<LONG>(windowPos.left + horizontalOffset), + static_cast<LONG>(windowPos.top + verticalOffset)}; }; bool shouldShowSystemMenu = false; bool broughtByKeyboard = false; @@ -1682,7 +1827,8 @@ switch (message) { case WM_RBUTTONUP: { const POINT nativeLocalPos = getNativePosFromMouse(); - const QPoint qtScenePos = QHighDpi::fromNativeLocalPosition(QPoint(nativeLocalPos.x, nativeLocalPos.y), m_windowHandle); + const QPoint qtScenePos = + QHighDpi::fromNativeLocalPosition(point2qpoint(nativeLocalPos), m_windowHandle); if (isInTitleBarDraggableArea(qtScenePos)) { shouldShowSystemMenu = true; nativeGlobalPos = nativeLocalPos; @@ -1721,7 +1867,8 @@ break; } if (shouldShowSystemMenu) { - showSystemMenu2(hWnd, nativeGlobalPos, broughtByKeyboard); + showSystemMenu2(hWnd, nativeGlobalPos, broughtByKeyboard, + m_delegate->isHostSizeFixed(m_host)); // QPA's internal code will handle system menu events separately, and its // behavior is not what we would want to see because it doesn't know our // window doesn't have any window frame now, so return early here to avoid -- Gitblit v1.9.1