Zhao Yuhang
2023-12-11 2f6c83c095724bbba0f43b2f2893ba73c17949a6
src/core/contexts/win32windowcontext.cpp
@@ -34,13 +34,27 @@
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 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
    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,6 +74,11 @@
    } g_hook{};
    struct DynamicApis {
        static const DynamicApis &instance() {
            static const DynamicApis inst{};
            return inst;
        }
//        template <typename T>
//        struct DefaultFunc;
//
@@ -72,11 +91,13 @@
//
// #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(GetDpiForWindow);
        DYNAMIC_API_DECLARE(GetSystemMetricsForDpi);
        DYNAMIC_API_DECLARE(GetDpiForMonitor);
@@ -86,6 +107,7 @@
#undef DYNAMIC_API_DECLARE
    private:
        DynamicApis() {
#define DYNAMIC_API_RESOLVE(DLL, NAME)                                                             \
    p##NAME = reinterpret_cast<decltype(p##NAME)>(DLL.resolve(#NAME))
@@ -101,6 +123,7 @@
            DYNAMIC_API_RESOLVE(dwmapi, DwmFlush);
            DYNAMIC_API_RESOLVE(dwmapi, DwmIsCompositionEnabled);
            DYNAMIC_API_RESOLVE(dwmapi, DwmGetCompositionTimingInfo);
            DYNAMIC_API_RESOLVE(dwmapi, DwmGetWindowAttribute);
            QSystemLibrary winmm(QStringLiteral("winmm"));
            DYNAMIC_API_RESOLVE(winmm, timeGetDevCaps);
@@ -112,12 +135,6 @@
        ~DynamicApis() = default;
        static const DynamicApis &instance() {
            static const DynamicApis inst{};
            return inst;
        }
    private:
        Q_DISABLE_COPY_MOVE(DynamicApis)
    };
@@ -238,27 +255,40 @@
        if (!registry.isValid()) {
            return false;
        }
        const QVariant value = registry.value(L"ColorPrevalence");
        if (!value.isValid()) {
        const auto value = registry.dwordValue(L"ColorPrevalence");
        if (!value.second) {
            return false;
        }
        return qvariant_cast<DWORD>(value);
        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)");
        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()) {
        const auto value = registry.dwordValue(L"AppsUseLightTheme");
        if (!value.second) {
            return false;
        }
        return !qvariant_cast<DWORD>(value);
        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() {
@@ -269,13 +299,13 @@
        if (!registry.isValid()) {
            return {};
        }
        const QVariant value = registry.value(L"AccentColor");
        if (!value.isValid()) {
        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(qvariant_cast<DWORD>(value));
        const QColor abgr = QColor::fromRgba(value.first);
        if (!abgr.isValid()) {
            return {};
        }
@@ -302,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;
        }
    }
@@ -334,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};
            }
        }();
@@ -534,7 +576,8 @@
            case HTBORDER:
                return Win32WindowContext::FixedBorder;
            default:
                break; // unreachable
                // unreachable
                break;
        }
        return Win32WindowContext::Outside;
    }
@@ -727,34 +770,80 @@
    }
    QString Win32WindowContext::key() const {
        return "win32";
        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 = *reinterpret_cast<const QPoint *>(data);
                auto winId = m_windowHandle->winId();
                auto hWnd = reinterpret_cast<HWND>(winId);
                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 = *reinterpret_cast<bool *>(data);
                auto &result = *static_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 &region = *reinterpret_cast<const QRegion *>(args[2]);
                // ### TODO
                auto args = static_cast<void **>(data);
                auto &painter = *static_cast<QPainter *>(args[0]);
                const auto &rect = *static_cast<const QRect *>(args[1]);
                const auto &region = *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;
            }
            default:
            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);
    }
@@ -826,11 +915,10 @@
        return false; // Not handled
    }
    static constexpr const auto kMessageTag = WPARAM(0xF1C9ADD4);
    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;
@@ -870,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)) {
@@ -948,6 +1036,7 @@
                SEND_MESSAGE(hWnd, WM_MOUSELEAVE, wParamNew, lParamNew);
                break;
            default:
                // unreachable
                break;
        }
@@ -970,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
@@ -1282,7 +1371,8 @@
                                *result = HTCLOSE;
                                break;
                            default:
                                break; // unreachable
                                // unreachable
                                break;
                        }
                    }
                    if (*result == HTNOWHERE) {