Sine Striker
2023-12-11 acece00ae291d143c3b712a98814a64b9dd43f14
src/core/contexts/win32windowcontext.cpp
@@ -1,12 +1,11 @@
#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 <QtCore/private/qsystemlibrary_p.h>
#include <QtGui/private/qhighdpiscaling_p.h>
@@ -23,9 +22,12 @@
#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 {
@@ -60,46 +62,44 @@
//            }
//        };
//
// #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
#define DYNAMIC_API_DECLARE(NAME) decltype(&::NAME) p##NAME = nullptr
        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);
        DYNAMIC_API_DECLARE(DwmFlush);
        DYNAMIC_API_DECLARE(DwmIsCompositionEnabled);
        DYNAMIC_API_DECLARE(DwmGetCompositionTimingInfo);
        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 DWM_API_DECLARE
#undef DYNAMIC_API_DECLARE
        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);
            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;
@@ -194,22 +194,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;
    }
@@ -385,8 +385,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 +395,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.
@@ -564,7 +520,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 +555,12 @@
                return;
            }
            instance = new WindowsNativeEventFilter();
            installNativeEventFilter(instance);
        }
        static inline void uninstall() {
            if (!instance) {
                return;
            }
            removeNativeEventFilter(instance);
            delete instance;
            instance = nullptr;
        }
@@ -714,6 +668,44 @@
        }
    }
    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, {pos.x(), pos.y()}, false,
                                m_delegate->isHostSizeFixed(m_host));
                return;
            }
            case NeedsDrawBordersHook: {
                auto &result = *reinterpret_cast<bool *>(data);
                result = isWin10OrGreater() && !isWin11OrGreater();
                return;
            }
            case DrawBordersHook: {
                auto a = reinterpret_cast<void **>(data);
                auto &painter = *reinterpret_cast<QPainter *>(a[0]);
                auto &rect = *reinterpret_cast<const QRect *>(a[1]);
                auto &region = *reinterpret_cast<const QRegion *>(a[2]);
                qDebug() << "paint" << &painter << rect << region;
                // TODO: Draw border
                // ...
                break;
            }
            default:
                break;
        }
        AbstractWindowContext::virtual_hook(id, data);
    }
    bool Win32WindowContext::setupHost() {
        // Install window hook
        auto winId = m_windowHandle->winId();
@@ -781,7 +773,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);
@@ -937,9 +929,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 +1162,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.
@@ -1221,19 +1213,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:
@@ -1683,7 +1675,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;
@@ -1691,7 +1684,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;
@@ -1730,7 +1724,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