From 5c471560256776f17f1e3d5c377c7d3e9fdcc0f1 Mon Sep 17 00:00:00 2001 From: Yuhang Zhao <zhaoyuhang@rankyee.com> Date: 周一, 11 12月 2023 18:09:55 +0800 Subject: [PATCH] WIP: prepare border painter --- src/core/contexts/win32windowcontext.cpp | 169 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 files changed, 137 insertions(+), 32 deletions(-) diff --git a/src/core/contexts/win32windowcontext.cpp b/src/core/contexts/win32windowcontext.cpp index fcbc8f9..2a2dffb 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,22 @@ #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 { // 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 *>; @@ -60,7 +70,8 @@ // } // }; // -// #define DYNAMIC_API_DECLARE(NAME) decltype(&::NAME) p##NAME = DefaultFunc<decltype(&::NAME)>::func +// #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); @@ -76,7 +87,8 @@ #undef DYNAMIC_API_DECLARE DynamicApis() { -#define DYNAMIC_API_RESOLVE(DLL, NAME) p##NAME = reinterpret_cast<decltype(p##NAME)>(DLL##.resolve(#NAME)) +#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); @@ -190,22 +202,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; } @@ -219,6 +231,56 @@ } 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 QVariant value = registry.value(L"ColorPrevalence"); + if (!value.isValid()) { + return false; + } + return qvariant_cast<DWORD>(value); + } + + 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 QVariant value = registry.value(L"AppsUseLightTheme"); + if (!value.isValid()) { + return false; + } + return !qvariant_cast<DWORD>(value); +#endif + } + + 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 QVariant value = registry.value(L"AccentColor"); + if (!value.isValid()) { + 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(qvariant_cast<DWORD>(value)); + if (!abgr.isValid()) { + return {}; + } + return QColor::fromRgb(abgr.blue(), abgr.green(), abgr.red(), abgr.alpha()); +#endif } static inline void triggerFrameChange(HWND hwnd) { @@ -381,8 +443,8 @@ apis.ptimeEndPeriod(ms_granularity); } - static inline void showSystemMenu2(HWND hWnd, const POINT &pos, const bool selectFirstEntry, const bool fixedSize) - { + 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 @@ -393,8 +455,10 @@ const bool maxOrFull = IsMaximized(hWnd) || isFullScreen(hWnd); ::EnableMenuItem(hMenu, SC_CLOSE, (MF_BYCOMMAND | MFS_ENABLED)); - ::EnableMenuItem(hMenu, SC_MAXIMIZE, (MF_BYCOMMAND | ((maxOrFull || fixedSize) ? MFS_DISABLED : MFS_ENABLED))); - ::EnableMenuItem(hMenu, SC_RESTORE, (MF_BYCOMMAND | ((maxOrFull && !fixedSize) ? MFS_ENABLED : MFS_DISABLED))); + ::EnableMenuItem(hMenu, SC_MAXIMIZE, + (MF_BYCOMMAND | ((maxOrFull || fixedSize) ? 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 @@ -404,9 +468,11 @@ // 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))); + ::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_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 @@ -426,7 +492,10 @@ ::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); // Unhighlight the first menu item after the popup menu is closed, otherwise it will keep // highlighting until we unhighlight it manually. @@ -509,7 +578,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 { @@ -544,14 +613,12 @@ return; } instance = new WindowsNativeEventFilter(); - installNativeEventFilter(instance); } static inline void uninstall() { if (!instance) { return; } - removeNativeEventFilter(instance); delete instance; instance = nullptr; } @@ -659,6 +726,39 @@ } } + QString Win32WindowContext::key() const { + return "win32"; + } + + void Win32WindowContext::virtual_hook(int id, void *data) { + switch (id) { + case ShowSystemMenuHook: { + const auto &pos = *reinterpret_cast<const QPoint *>(data); + auto winId = m_windowHandle->winId(); + auto hWnd = reinterpret_cast<HWND>(winId); + showSystemMenu2(hWnd, qpoint2point(pos), false, + m_delegate->isHostSizeFixed(m_host)); + return; + } + case NeedsDrawBordersHook: { + auto &result = *reinterpret_cast<bool *>(data); + result = isWin10OrGreater() && !isWin11OrGreater(); + return; + } + case DrawBordersHook: { + auto args = reinterpret_cast<void **>(data); + auto &painter = *reinterpret_cast<QPainter *>(args[0]); + auto &rect = *reinterpret_cast<const QRect *>(args[1]); + auto ®ion = *reinterpret_cast<const QRegion *>(args[2]); + // ### TODO + return; + } + default: + break; + } + AbstractWindowContext::virtual_hook(id, data); + } + bool Win32WindowContext::setupHost() { // Install window hook auto winId = m_windowHandle->winId(); @@ -726,7 +826,7 @@ return false; // Not handled } - static constexpr const auto kMessageTag = WPARAM(0x97CCEA99); + static constexpr const auto kMessageTag = WPARAM(0xF1C9ADD4); static inline constexpr bool isTaggedMessage(WPARAM wParam) { return (wParam == kMessageTag); @@ -882,8 +982,9 @@ DWORD dwScreenPos = ::GetMessagePos(); POINT screenPoint{GET_X_LPARAM(dwScreenPos), GET_Y_LPARAM(dwScreenPos)}; ::ScreenToClient(hWnd, &screenPoint); - QPoint qtScenePos = QHighDpi::fromNativeLocalPosition(point2qpoint(screenPoint), 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() @@ -1114,13 +1215,14 @@ auto clientWidth = RECT_WIDTH(clientRect); auto clientHeight = RECT_HEIGHT(clientRect); - QPoint qtScenePos = QHighDpi::fromNativeLocalPosition(point2qpoint(nativeLocalPos), 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. @@ -1164,19 +1266,19 @@ // 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: @@ -1626,7 +1728,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; @@ -1634,7 +1737,8 @@ switch (message) { case WM_RBUTTONUP: { const POINT nativeLocalPos = getNativePosFromMouse(); - const QPoint qtScenePos = QHighDpi::fromNativeLocalPosition(point2qpoint(nativeLocalPos), m_windowHandle); + const QPoint qtScenePos = + QHighDpi::fromNativeLocalPosition(point2qpoint(nativeLocalPos), m_windowHandle); if (isInTitleBarDraggableArea(qtScenePos)) { shouldShowSystemMenu = true; nativeGlobalPos = nativeLocalPos; @@ -1673,7 +1777,8 @@ break; } if (shouldShowSystemMenu) { - showSystemMenu2(hWnd, nativeGlobalPos, broughtByKeyboard, m_delegate->isHostSizeFixed(m_host)); + 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