From eb266da10552eeefcb0f7dfe514d4385aec563f8 Mon Sep 17 00:00:00 2001
From: Sine Striker <trueful@163.com>
Date: 周日, 03 12月 2023 05:05:23 +0800
Subject: [PATCH] Add windows context implementation

---
 src/core/corewindowagent.cpp                |   10 +-
 src/core/corewindowagent_p.h                |    2 
 src/core/qwkcoreglobal.h                    |   12 --
 src/core/windowitemdelegate.h               |    2 
 src/core/contexts/abstractwindowcontext.cpp |    4 
 src/core/contexts/abstractwindowcontext_p.h |    3 
 src/core/CMakeLists.txt                     |    1 
 src/core/qwindowkit_windows.h               |   18 ++++
 src/core/qwkcoreglobal_p.h                  |   36 +++++++++
 src/core/contexts/win32windowcontext.cpp    |  125 +++++++++++++++++++++++++++----
 10 files changed, 173 insertions(+), 40 deletions(-)

diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 8444ad6..2ad4a0c 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -5,6 +5,7 @@
 
 set(_src
     qwkcoreglobal.h
+    qwkcoreglobal_p.h
     corewindowagent.h
     corewindowagent_p.h
     corewindowagent.cpp
diff --git a/src/core/contexts/abstractwindowcontext.cpp b/src/core/contexts/abstractwindowcontext.cpp
index a6d4aeb..920e210 100644
--- a/src/core/contexts/abstractwindowcontext.cpp
+++ b/src/core/contexts/abstractwindowcontext.cpp
@@ -2,9 +2,7 @@
 
 namespace QWK {
 
-    AbstractWindowContext::~AbstractWindowContext() {
-        delete m_delegate;
-    }
+    AbstractWindowContext::~AbstractWindowContext() = default;
 
     void AbstractWindowContext::setupWindow(QWindow *window) {
         Q_ASSERT(window);
diff --git a/src/core/contexts/abstractwindowcontext_p.h b/src/core/contexts/abstractwindowcontext_p.h
index 4c04734..c9b1374 100644
--- a/src/core/contexts/abstractwindowcontext_p.h
+++ b/src/core/contexts/abstractwindowcontext_p.h
@@ -2,6 +2,7 @@
 #define ABSTRACTWINDOWCONTEXT_P_H
 
 #include <array>
+#include <memory>
 
 #include <QtCore/QSet>
 #include <QtGui/QWindow>
@@ -42,7 +43,7 @@
 
     protected:
         QWindow *m_windowHandle;
-        WindowItemDelegate *m_delegate;
+        std::unique_ptr<WindowItemDelegate> m_delegate;
 
         QSet<QObject *> m_hitTestVisibleItems;
         QList<QRect> m_hitTestVisibleRects;
diff --git a/src/core/contexts/win32windowcontext.cpp b/src/core/contexts/win32windowcontext.cpp
index a0d5779..73a8eb2 100644
--- a/src/core/contexts/win32windowcontext.cpp
+++ b/src/core/contexts/win32windowcontext.cpp
@@ -1,6 +1,10 @@
 #include "win32windowcontext_p.h"
 
 #include <QtCore/QHash>
+#include <QtCore/QAbstractNativeEventFilter>
+#include <QtCore/QCoreApplication>
+
+#include "qwkcoreglobal_p.h"
 
 namespace QWK {
 
@@ -9,8 +13,78 @@
 
     static WNDPROC g_qtWindowProc = nullptr; // Original Qt window proc function
 
-    extern "C" LRESULT QT_WIN_CALLBACK QWK_WindowsWndProc(HWND hWnd, UINT message, WPARAM wParam,
-                                                          LPARAM lParam) {
+    static bool g_lastMessageHandled = false;
+
+    static LRESULT g_lastMessageResult = false;
+
+    class WindowsNativeEventFilter : public QAbstractNativeEventFilter {
+    public:
+        bool nativeEventFilter(const QByteArray &eventType, void *message,
+                               QT_NATIVE_EVENT_RESULT_TYPE *result) override {
+            if (g_lastMessageHandled) {
+                *result = static_cast<QT_NATIVE_EVENT_RESULT_TYPE>(g_lastMessageResult);
+                return true;
+            }
+            return false;
+        }
+    };
+
+    static WindowsNativeEventFilter *g_nativeFilter = 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
+    // unfiltered messages to the event dispatcher. To activate the Snap Layout feature on Windows
+    // 11, we must process some non-client area messages ourselves, but unfortunately these messages
+    // have been filtered out already in that line, and thus we'll never have the chance to process
+    // them ourselves. This is Qt's low level platform specific code, so we don't have any official
+    // ways to change this behavior. But luckily we can replace the window procedure function of
+    // Qt's windows, and in this hooked window procedure function, we finally have the chance to
+    // process window messages before Qt touches them. So we reconstruct the MSG structure and send
+    // it to our own custom native event filter to do all the magic works. But since the system menu
+    // feature doesn't necessarily belong to the native implementation, we seperate the handling
+    // code and always process the system menu part in this function for both implementations.
+    //
+    // Original event flow:
+    //      [Entry]             Windows Message Queue
+    //                          |
+    //      [Qt Window Proc]    qwindowscontext.cpp#L1547: qWindowsWndProc()
+    //                              ```
+    //                              const bool handled = QWindowsContext::instance()->windowsProc
+    //                                  (hwnd, message, et, wParam, lParam, &result,
+    //                                  &platformWindow);
+    //                              ```
+    //                          |
+    //      [Non-Input Filter]  qwindowscontext.cpp#L1025: QWindowsContext::windowsProc()
+    //                              ```
+    //                              if (!isInputMessage(msg.message) &&
+    //                                  filterNativeEvent(&msg, result))
+    //                                  return true;
+    //                              ```
+    //                          |
+    //      [User Filter]       qwindowscontext.cpp#L1588: QWindowsContext::windowsProc()
+    //                              ```
+    //                              QAbstractEventDispatcher *dispatcher =
+    //                              QAbstractEventDispatcher::instance();
+    //                              qintptr filterResult = 0;
+    //                              if (dispatcher &&
+    //                              dispatcher->filterNativeEvent(nativeEventType(), msg,
+    //                              &filterResult)) {
+    //                                  *result = LRESULT(filterResult);
+    //                                  return true;
+    //                              }
+    //                              ```
+    //                          |
+    //      [Extra work]        The rest of QWindowsContext::windowsProc() and qWindowsWndProc()
+    //
+    // Notice: Only non-input messages will be processed by the user-defined global native event
+    // filter!!! These events are then passed to the widget class's own overridden
+    // QWidget::nativeEvent() as a local filter, where all native events can be handled, but we must
+    // create a new class derived from QWidget which we don't intend to. Therefore, we don't expect
+    // to process events from the global native event filter, but instead hook Qt's window
+    // procedure.
+
+    extern "C" LRESULT QT_WIN_CALLBACK QWKHookedWndProc(HWND hWnd, UINT message, WPARAM wParam,
+                                                        LPARAM lParam) {
         Q_ASSERT(hWnd);
         if (!hWnd) {
             return FALSE;
@@ -22,13 +96,14 @@
             return ::DefWindowProcW(hWnd, message, wParam, lParam);
         }
 
-        // Try hooked procedure
-        LRESULT result;
-        if (ctx->windowProc(hWnd, message, wParam, lParam, &result)) {
-            return result;
-        }
+        // Try hooked procedure and save result
+        g_lastMessageHandled = ctx->windowProc(hWnd, message, wParam, lParam, &g_lastMessageResult);
 
-        // Fallback to Qt's procedure
+        // TODO: Determine whether to show system menu
+        // ...
+
+        // Since Qt does the necessary processing of the message afterward, we still need to
+        // continue dispatching it.
         return ::CallWindowProcW(g_qtWindowProc, hWnd, message, wParam, lParam);
     }
 
@@ -38,8 +113,16 @@
 
     Win32WindowContext::~Win32WindowContext() {
         // Remove window handle mapping
-        auto hWnd = reinterpret_cast<HWND>(windowId);
-        g_wndProcHash->remove(hWnd);
+        if (auto hWnd = reinterpret_cast<HWND>(windowId); hWnd) {
+            g_wndProcHash->remove(hWnd);
+
+            // Remove event filter if the last window is destroyed
+            if (g_wndProcHash->empty()) {
+                qApp->removeNativeEventFilter(g_nativeFilter);
+                delete g_nativeFilter;
+                g_nativeFilter = nullptr;
+            }
+        }
     }
 
     bool Win32WindowContext::setup() {
@@ -47,15 +130,23 @@
 
         // Install window hook
         auto hWnd = reinterpret_cast<HWND>(winId);
-        auto qtWindowProc = reinterpret_cast<WNDPROC>(::GetWindowLongPtrW(hWnd, GWLP_WNDPROC));
-        ::SetWindowLongPtrW(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(QWK_WindowsWndProc));
-
-        windowId = winId;
 
         // Store original window proc
         if (!g_qtWindowProc) {
-            g_qtWindowProc = qtWindowProc;
+            g_qtWindowProc = reinterpret_cast<WNDPROC>(::GetWindowLongPtrW(hWnd, GWLP_WNDPROC));
         }
+
+        // Hook window proc
+        ::SetWindowLongPtrW(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(QWKHookedWndProc));
+
+        // Install global native event filter
+        if (!g_nativeFilter) {
+            g_nativeFilter = new WindowsNativeEventFilter();
+            qApp->installNativeEventFilter(g_nativeFilter);
+        }
+
+        // Cache window ID
+        windowId = winId;
 
         // Save window handle mapping
         g_wndProcHash->insert(hWnd, this);
@@ -67,10 +158,10 @@
                                         LRESULT *result) {
         *result = FALSE;
 
+        Q_UNUSED(windowId)
+
         // TODO: Implement
         // ...
-
-        Q_UNUSED(windowId)
 
         return false; // Not handled
     }
diff --git a/src/core/corewindowagent.cpp b/src/core/corewindowagent.cpp
index c843273..abcd6fb 100644
--- a/src/core/corewindowagent.cpp
+++ b/src/core/corewindowagent.cpp
@@ -1,6 +1,8 @@
 #include "corewindowagent.h"
 #include "corewindowagent_p.h"
 
+#include "qwkcoreglobal_p.h"
+
 #ifdef Q_OS_WINDOWS
 #  include "win32windowcontext_p.h"
 #else
@@ -11,12 +13,10 @@
 
 namespace QWK {
 
-    CoreWindowAgentPrivate::CoreWindowAgentPrivate() : eventHandler(nullptr) {
+    CoreWindowAgentPrivate::CoreWindowAgentPrivate() : q_ptr(nullptr), eventHandler(nullptr) {
     }
 
-    CoreWindowAgentPrivate::~CoreWindowAgentPrivate() {
-        delete eventHandler;
-    }
+    CoreWindowAgentPrivate::~CoreWindowAgentPrivate() = default;
 
     void CoreWindowAgentPrivate::init() {
     }
@@ -38,7 +38,7 @@
             delete handler;
             return false;
         }
-        eventHandler = handler;
+        eventHandler.reset(handler);
         return true;
     }
 
diff --git a/src/core/corewindowagent_p.h b/src/core/corewindowagent_p.h
index 0c9be23..013ccbe 100644
--- a/src/core/corewindowagent_p.h
+++ b/src/core/corewindowagent_p.h
@@ -18,7 +18,7 @@
 
         bool setup(QWindow *window, WindowItemDelegate *delegate);
 
-        AbstractWindowContext *eventHandler;
+        std::unique_ptr<AbstractWindowContext> eventHandler;
 
         Q_DISABLE_COPY_MOVE(CoreWindowAgentPrivate)
     };
diff --git a/src/core/qwindowkit_windows.h b/src/core/qwindowkit_windows.h
index c7726b9..ff7ff2b 100644
--- a/src/core/qwindowkit_windows.h
+++ b/src/core/qwindowkit_windows.h
@@ -2,5 +2,23 @@
 #define QWINDOWKIT_WINDOWS_H
 
 #include <QtCore/qt_windows.h>
+#include <QtCore/qglobal.h>
+
+// MOC can't handle C++ attributes before 5.15.
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
+#  define Q_NODISCARD    [[nodiscard]]
+#  define Q_MAYBE_UNUSED [[maybe_unused]]
+#else
+#  define Q_NODISCARD
+#  define Q_MAYBE_UNUSED
+#endif
+
+#ifndef GET_X_LPARAM
+#  define GET_X_LPARAM(lp) (static_cast<int>(static_cast<short>(LOWORD(lp))))
+#endif
+
+#ifndef GET_Y_LPARAM
+#  define GET_Y_LPARAM(lp) (static_cast<int>(static_cast<short>(HIWORD(lp))))
+#endif
 
 #endif // QWINDOWKIT_WINDOWS_H
diff --git a/src/core/qwkcoreglobal.h b/src/core/qwkcoreglobal.h
index 517d91c..94982e6 100644
--- a/src/core/qwkcoreglobal.h
+++ b/src/core/qwkcoreglobal.h
@@ -1,7 +1,7 @@
 #ifndef QWKCOREGLOBAL_H
 #define QWKCOREGLOBAL_H
 
-#include <QtCore/QLoggingCategory>
+#include <QtCore/QtGlobal>
 
 #ifndef QWK_CORE_EXPORT
 #  ifdef QWK_CORE_STATIC
@@ -13,16 +13,6 @@
 #      define QWK_CORE_EXPORT Q_DECL_IMPORT
 #    endif
 #  endif
-#endif
-
-QWK_CORE_EXPORT Q_DECLARE_LOGGING_CATEGORY(qWindowKitLog)
-
-#define QWK_INFO     qCInfo(qWindowKitLog)
-#define QWK_DEBUG    qCDebug(qWindowKitLog)
-#define QWK_WARNING  qCWarning(qWindowKitLog)
-#define QWK_CRITICAL qCCritical(qWindowKitLog)
-#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
-#  define QWK_FATAL qCFatal(qWindowKitLog)
 #endif
 
 #endif // QWKCOREGLOBAL_H
diff --git a/src/core/qwkcoreglobal_p.h b/src/core/qwkcoreglobal_p.h
new file mode 100644
index 0000000..5f76437
--- /dev/null
+++ b/src/core/qwkcoreglobal_p.h
@@ -0,0 +1,36 @@
+#ifndef QWKCOREGLOBAL_P_H
+#define QWKCOREGLOBAL_P_H
+
+#include <QtCore/QEvent>
+#include <QtCore/QLoggingCategory>
+
+#include <QWKCore/qwkcoreglobal.h>
+
+#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
+using QT_NATIVE_EVENT_RESULT_TYPE = qintptr;
+using QT_ENTER_EVENT_TYPE = QEnterEvent;
+#else
+using QT_NATIVE_EVENT_RESULT_TYPE = long;
+using QT_ENTER_EVENT_TYPE = QEvent;
+#endif
+
+QWK_CORE_EXPORT Q_DECLARE_LOGGING_CATEGORY(qWindowKitLog)
+
+#define QWK_INFO     qCInfo(qWindowKitLog)
+#define QWK_DEBUG    qCDebug(qWindowKitLog)
+#define QWK_WARNING  qCWarning(qWindowKitLog)
+#define QWK_CRITICAL qCCritical(qWindowKitLog)
+#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
+#  define QWK_FATAL qCFatal(qWindowKitLog)
+#endif
+
+// MOC can't handle C++ attributes before 5.15.
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
+#  define Q_NODISCARD    [[nodiscard]]
+#  define Q_MAYBE_UNUSED [[maybe_unused]]
+#else
+#  define Q_NODISCARD
+#  define Q_MAYBE_UNUSED
+#endif
+
+#endif // QWKCOREGLOBAL_P_H
diff --git a/src/core/windowitemdelegate.h b/src/core/windowitemdelegate.h
index 985dedc..fe9a252 100644
--- a/src/core/windowitemdelegate.h
+++ b/src/core/windowitemdelegate.h
@@ -1,8 +1,6 @@
 #ifndef WINDOWITEMDELEGATE_H
 #define WINDOWITEMDELEGATE_H
 
-#include <memory>
-
 #include <QtCore/QObject>
 #include <QtGui/QWindow>
 

--
Gitblit v1.9.1