From e95e2bfd29b64d3144515c30fb9cf76adb75c6c3 Mon Sep 17 00:00:00 2001 From: Sine Striker <trueful@163.com> Date: 周二, 07 5月 2024 21:44:21 +0800 Subject: [PATCH] minor tweaks --- src/core/contexts/win32windowcontext.cpp | 173 ++++++++++++++++++++++++++++++++++++++------------------- 1 files changed, 115 insertions(+), 58 deletions(-) diff --git a/src/core/contexts/win32windowcontext.cpp b/src/core/contexts/win32windowcontext.cpp index 4fda1eb..171a348 100644 --- a/src/core/contexts/win32windowcontext.cpp +++ b/src/core/contexts/win32windowcontext.cpp @@ -33,14 +33,6 @@ #include "qwkglobal_p.h" #include "qwkwindowsextra_p.h" -// https://github.com/qt/qtbase/blob/6.6.1/src/plugins/platforms/windows/qwindowswindow.cpp#L2791 -// https://github.com/qt/qtbase/blob/6.6.1/src/plugins/platforms/windows/qwindowswindow.cpp#L3321 -// This issue exists only in Qt 6.6.1, and was introduced by QTBUG-113736 patch and fixed by -// QTBUG-117704 patch. -#if !QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDERS) && QT_VERSION == QT_VERSION_CHECK(6, 6, 1) -# define QTBUG_113736_WORKAROUND -#endif - namespace QWK { // The thickness of an auto-hide taskbar in pixels. @@ -241,14 +233,15 @@ apis.ptimeEndPeriod(ms_granularity); } - static void showSystemMenu2(HWND hWnd, const POINT &pos, const bool selectFirstEntry, - const bool fixedSize) { + // Returns false if the menu is canceled + static bool showSystemMenu_sys(HWND hWnd, const POINT &pos, const bool selectFirstEntry, + const bool fixedSize) { HMENU hMenu = ::GetSystemMenu(hWnd, FALSE); if (!hMenu) { // The corresponding window doesn't have a system menu, most likely due to the // lack of the "WS_SYSMENU" window style. This situation should not be treated // as an error so just ignore it and return early. - return; + return true; } const bool maxOrFull = IsMaximized(hWnd) || isFullScreen(hWnd); @@ -292,7 +285,8 @@ // Popup the system menu at the required position. const auto result = ::TrackPopupMenu( hMenu, - (TPM_RETURNCMD | (QGuiApplication::isRightToLeft() ? TPM_RIGHTALIGN : TPM_LEFTALIGN)), + (TPM_RETURNCMD | (QGuiApplication::isRightToLeft() ? TPM_RIGHTALIGN : TPM_LEFTALIGN) | + TPM_RIGHTBUTTON), pos.x, pos.y, 0, hWnd, nullptr); // Unhighlight the first menu item after the popup menu is closed, otherwise it will keep @@ -301,11 +295,12 @@ if (!result) { // The user canceled the menu, no need to continue. - return; + return false; } // Send the command that the user chooses to the corresponding window. ::PostMessageW(hWnd, WM_SYSCOMMAND, result, 0); + return true; } static inline Win32WindowContext::WindowPart getHitWindowPart(int hitTestResult) { @@ -490,17 +485,38 @@ return false; } - // https://github.com/qt/qtbase/blob/e26a87f1ecc40bc8c6aa5b889fce67410a57a702/src/plugins/platforms/windows/qwindowscontext.cpp#L1546 - // Qt needs to refer to the WM_NCCALCSIZE message data that hasn't been processed, so we - // have to process it after Qt acquires the initial data. auto msg = static_cast<const MSG *>(message); - if (msg->message == WM_NCCALCSIZE && lastMessageContext) { - LRESULT res; - if (lastMessageContext->nonClientCalcSizeHandler(msg->hwnd, msg->message, - msg->wParam, msg->lParam, &res)) { - *result = decltype(*result)(res); - return true; + switch (msg->message) { + case WM_NCCALCSIZE: { + // https://github.com/qt/qtbase/blob/e26a87f1ecc40bc8c6aa5b889fce67410a57a702/src/plugins/platforms/windows/qwindowscontext.cpp#L1546 + // Qt needs to refer to the WM_NCCALCSIZE message data that hasn't been + // processed, so we have to process it after Qt acquires the initial data. + if (lastMessageContext) { + LRESULT res; + if (lastMessageContext->nonClientCalcSizeHandler( + msg->hwnd, msg->message, msg->wParam, msg->lParam, &res)) { + *result = decltype(*result)(res); + return true; + } + } + break; } + + // case WM_NCHITTEST: { + // // The child window must return HTTRANSPARENT when processing + // WM_NCHITTEST for + // // the parent window to receive WM_NCHITTEST. + // if (!lastMessageContext) { + // auto rootHWnd = ::GetAncestor(msg->hwnd, GA_ROOT); + // if (rootHWnd != msg->hwnd) { + // if (auto ctx = g_wndProcHash->value(rootHWnd)) { + // *result = HTTRANSPARENT; + // return true; + // } + // } + // } + // break; + // } } return false; } @@ -589,13 +605,15 @@ return ::DefWindowProcW(hWnd, message, wParam, lParam); } + WindowsNativeEventFilter::lastMessageContext = ctx; + const auto &contextCleaner = qScopeGuard([]() { + WindowsNativeEventFilter::lastMessageContext = nullptr; // + }); + // Since Qt does the necessary processing of the WM_NCCALCSIZE message, we need to // forward it right away and process it in our native event filter. if (message == WM_NCCALCSIZE) { - WindowsNativeEventFilter::lastMessageContext = ctx; - LRESULT result = ::CallWindowProcW(g_qtWindowProc, hWnd, message, wParam, lParam); - WindowsNativeEventFilter::lastMessageContext = nullptr; - return result; + return ::CallWindowProcW(g_qtWindowProc, hWnd, message, wParam, lParam); } // Try hooked procedure and save result @@ -613,20 +631,10 @@ } static inline void addManagedWindow(QWindow *window, HWND hWnd, Win32WindowContext *ctx) { -#ifndef QTBUG_113736_WORKAROUND - const auto margins = [](HWND hWnd) -> QMargins { - const auto titleBarHeight = int(getTitleBarHeight(hWnd)); - if (isSystemBorderEnabled()) { - return {0, -titleBarHeight, 0, 0}; - } else { - const auto frameSize = int(getResizeBorderThickness(hWnd)); - return {-frameSize, -titleBarHeight, -frameSize, -frameSize}; - } - }(hWnd); - - // Inform Qt we want and have set custom margins - setInternalWindowFrameMargins(window, margins); -#endif + if (isSystemBorderEnabled()) { + // Inform Qt we want and have set custom margins + setInternalWindowFrameMargins(window, QMargins(0, -int(getTitleBarHeight(hWnd)), 0, 0)); + } // Store original window proc if (!g_qtWindowProc) { @@ -689,8 +697,8 @@ #else const QPoint nativeGlobalPos = QHighDpi::toNativePixels(pos, m_windowHandle); #endif - showSystemMenu2(hWnd, qpoint2point(nativeGlobalPos), false, - m_delegate->isHostSizeFixed(m_host)); + std::ignore = showSystemMenu_sys(hWnd, qpoint2point(nativeGlobalPos), false, + m_delegate->isHostSizeFixed(m_host)); return; } @@ -808,7 +816,6 @@ ? int(getWindowFrameBorderThickness(reinterpret_cast<HWND>(m_windowId))) : 0; } - return AbstractWindowContext::windowAttribute(key); } @@ -816,11 +823,12 @@ // Reset the context data mouseLeaveBlocked = false; lastHitTestResult = WindowPart::Outside; + lastHitTestResultRaw = HTNOWHERE; -#ifdef QTBUG_113736_WORKAROUND - m_delegate->setWindowFlags(m_host, - m_delegate->getWindowFlags(m_host) | Qt::FramelessWindowHint); -#endif + if (!isSystemBorderEnabled()) { + m_delegate->setWindowFlags(m_host, m_delegate->getWindowFlags(m_host) | + Qt::FramelessWindowHint); + } // If the original window id is valid, remove all resources related if (oldWinId) { @@ -1294,9 +1302,8 @@ case WM_NCPOINTERUP: #endif case WM_NCMOUSEHOVER: { - const WindowPart currentWindowPart = lastHitTestResult; if (message == WM_NCMOUSEMOVE) { - if (currentWindowPart != WindowPart::ChromeButton) { + if (lastHitTestResult != WindowPart::ChromeButton) { // https://github.com/qt/qtbase/blob/e26a87f1ecc40bc8c6aa5b889fce67410a57a702/src/widgets/kernel/qwidgetwindow.cpp#L472 // When the mouse press event arrives, QWidgetWindow will implicitly grab // the top widget right under the mouse, and set `qt_button_down` to this @@ -1355,14 +1362,28 @@ } } - if (currentWindowPart == WindowPart::ChromeButton) { - emulateClientAreaMessage(hWnd, message, wParam, lParam); + if (lastHitTestResult == WindowPart::ChromeButton) { if (message == WM_NCMOUSEMOVE) { // ### FIXME FIXME FIXME // ### FIXME: Calling DefWindowProc() here is really dangerous, investigate // how to avoid doing this. // ### FIXME FIXME FIXME *result = ::DefWindowProcW(hWnd, WM_NCMOUSEMOVE, wParam, lParam); + } else if (lastHitTestResultRaw == HTSYSMENU) { + if (message == WM_NCLBUTTONDOWN) { + // A message of WM_SYSCOMMAND with SC_MOUSEMENU will be sent by Windows, + // and the current control flow will be blocked by the menu while + // Windows will create and execute a new event loop until the menu + // returns + iconButtonClickTime = ::GetTickCount64(); + *result = ::DefWindowProcW(hWnd, message, wParam, lParam); + iconButtonClickTime = 0; + } else if (message == WM_NCLBUTTONDBLCLK) { + // A message of WM_SYSCOMMAND with SC_CLOSE will be sent by Windows + *result = ::DefWindowProcW(hWnd, message, wParam, lParam); + } else { + *result = FALSE; + } } else { // According to MSDN, we should return non-zero for X button messages to // indicate we have handled these messages (due to historical reasons), for @@ -1372,6 +1393,7 @@ ? TRUE : FALSE); } + emulateClientAreaMessage(hWnd, message, wParam, lParam); return true; } break; @@ -1500,7 +1522,8 @@ // color, our homemade top border can almost have exactly the same // appearance with the system's one. [[maybe_unused]] const auto &hitTestRecorder = qScopeGuard([this, result]() { - lastHitTestResult = getHitWindowPart(int(*result)); // + lastHitTestResultRaw = int(*result); + lastHitTestResult = getHitWindowPart(lastHitTestResultRaw); }); POINT nativeGlobalPos{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; @@ -2049,6 +2072,7 @@ bool shouldShowSystemMenu = false; bool broughtByKeyboard = false; POINT nativeGlobalPos{}; + switch (message) { case WM_RBUTTONUP: { const POINT nativeLocalPos = getNativePosFromMouse(); @@ -2070,10 +2094,20 @@ } case WM_SYSCOMMAND: { const WPARAM filteredWParam = (wParam & 0xFFF0); - if ((filteredWParam == SC_KEYMENU) && (lParam == VK_SPACE)) { - shouldShowSystemMenu = true; - broughtByKeyboard = true; - nativeGlobalPos = getNativeGlobalPosFromKeyboard(); + switch (filteredWParam) { + case SC_MOUSEMENU: + shouldShowSystemMenu = true; + nativeGlobalPos = getNativeGlobalPosFromKeyboard(); + break; + case SC_KEYMENU: + if (lParam == VK_SPACE) { + shouldShowSystemMenu = true; + broughtByKeyboard = true; + nativeGlobalPos = getNativeGlobalPosFromKeyboard(); + } + break; + default: + break; } break; } @@ -2092,8 +2126,31 @@ break; } if (shouldShowSystemMenu) { - showSystemMenu2(hWnd, nativeGlobalPos, broughtByKeyboard, - m_delegate->isHostSizeFixed(m_host)); + bool triggeredByIconButton = iconButtonClickTime > 0; + if (triggeredByIconButton) { + // TODO: Adjust `nativeGlobalPos` to (0, realTitleBarHeight) + // hint: use m_delgegate->mapGeometryToScene + } + bool res = showSystemMenu_sys(hWnd, nativeGlobalPos, broughtByKeyboard, + m_delegate->isHostSizeFixed(m_host)); + + // Emulate the Windows SYSMENU button's behavior + static uint32_t doubleClickTime = ::GetDoubleClickTime(); + if (triggeredByIconButton && !res && + ::GetTickCount64() - iconButtonClickTime <= doubleClickTime) { + POINT nativeGlobalPos; + ::GetCursorPos(&nativeGlobalPos); + POINT nativeLocalPos = nativeGlobalPos; + ::ScreenToClient(hWnd, &nativeLocalPos); + QPoint qtScenePos = + QHighDpi::fromNativeLocalPosition(point2qpoint(nativeLocalPos), m_windowHandle); + WindowAgentBase::SystemButton sysButtonType = WindowAgentBase::Unknown; + if (isInSystemButtons(qtScenePos, &sysButtonType) && + sysButtonType == WindowAgentBase::WindowIcon) { + ::PostMessageW(hWnd, WM_SYSCOMMAND, SC_CLOSE, 0); + } + } + // 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 -- Gitblit v1.9.1