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 | 178 +++++++++++++++++++++++++++++++++++++++++++++++------------ 1 files changed, 141 insertions(+), 37 deletions(-) diff --git a/src/core/contexts/win32windowcontext.cpp b/src/core/contexts/win32windowcontext.cpp index a5dd7f7..73a8eb2 100644 --- a/src/core/contexts/win32windowcontext.cpp +++ b/src/core/contexts/win32windowcontext.cpp @@ -1,65 +1,169 @@ #include "win32windowcontext_p.h" #include <QtCore/QHash> +#include <QtCore/QAbstractNativeEventFilter> +#include <QtCore/QCoreApplication> + +#include "qwkcoreglobal_p.h" namespace QWK { - using WndProcHash = QHash<HWND, Win32WindowContext *>; + using WndProcHash = QHash<HWND, Win32WindowContext *>; // hWnd -> context Q_GLOBAL_STATIC(WndProcHash, g_wndProcHash); - Win32WindowContext::Win32WindowContext(QWindow *window, WindowItemDelegate *delegate) - : AbstractWindowContext(window, delegate), windowId(0), qtWindowProc(nullptr) { - } + static WNDPROC g_qtWindowProc = nullptr; // Original Qt window proc function - Win32WindowContext::~Win32WindowContext() { - auto hWnd = reinterpret_cast<HWND>(windowId); - g_wndProcHash->remove(hWnd); - } + static bool g_lastMessageHandled = false; - bool Win32WindowContext::setup() { - auto winId = m_windowHandle->winId(); - Q_ASSERT(winId); - if (!winId) { + 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; } + }; - // Install window hook - auto hWnd = reinterpret_cast<HWND>(winId); - auto orgWndProc = reinterpret_cast<WNDPROC>(::GetWindowLongPtrW(hWnd, GWLP_WNDPROC)); - Q_ASSERT(orgWndProc); - if (!orgWndProc) { - QWK_WARNING << winLastErrorMessage(); - return false; - } + static WindowsNativeEventFilter *g_nativeFilter = nullptr; - if (::SetWindowLongPtrW(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(windowProc)) == 0) { - QWK_WARNING << winLastErrorMessage(); - return false; - } + // 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. - windowId = winId; - qtWindowProc = orgWndProc; // Store original window proc - g_wndProcHash->insert(hWnd, this); // Save window handle mapping - return true; - } - - LRESULT Win32WindowContext::windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + extern "C" LRESULT QT_WIN_CALLBACK QWKHookedWndProc(HWND hWnd, UINT message, WPARAM wParam, + LPARAM lParam) { Q_ASSERT(hWnd); if (!hWnd) { return FALSE; } - const auto *ctx = g_wndProcHash->value(hWnd); + // Search window context + auto ctx = g_wndProcHash->value(hWnd); if (!ctx) { - return ::DefWindowProcW(hWnd, uMsg, wParam, lParam); + return ::DefWindowProcW(hWnd, message, wParam, lParam); } - auto winId = reinterpret_cast<WId>(hWnd); + // Try hooked procedure and save result + g_lastMessageHandled = ctx->windowProc(hWnd, message, wParam, lParam, &g_lastMessageResult); - // Further procedure - Q_UNUSED(winId) + // TODO: Determine whether to show system menu + // ... - return ::CallWindowProcW(ctx->qtWindowProc, hWnd, uMsg, wParam, lParam); + // 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); + } + + Win32WindowContext::Win32WindowContext(QWindow *window, WindowItemDelegate *delegate) + : AbstractWindowContext(window, delegate), windowId(0) { + } + + Win32WindowContext::~Win32WindowContext() { + // Remove window handle mapping + 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() { + auto winId = m_windowHandle->winId(); + + // Install window hook + auto hWnd = reinterpret_cast<HWND>(winId); + + // Store original window proc + if (!g_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); + + return true; + } + + bool Win32WindowContext::windowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, + LRESULT *result) { + *result = FALSE; + + Q_UNUSED(windowId) + + // TODO: Implement + // ... + + return false; // Not handled } } \ No newline at end of file -- Gitblit v1.9.1