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/contexts/win32windowcontext.cpp | 125 ++++++++++++++++++++++++++++++++++++----- 1 files changed, 108 insertions(+), 17 deletions(-) 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 } -- Gitblit v1.9.1