From c3ef42dad254f6d04cab521c9ae619a46bb84f7a Mon Sep 17 00:00:00 2001 From: Sine Striker <trueful@163.com> Date: ćšć, 21 11æ 2024 21:17:32 +0800 Subject: [PATCH] Fix #151 and a crash bug related to sub frameless window --- src/core/contexts/win32windowcontext.cpp | 679 +++++++++++++++++++++---------------------------------- 1 files changed, 263 insertions(+), 416 deletions(-) diff --git a/src/core/contexts/win32windowcontext.cpp b/src/core/contexts/win32windowcontext.cpp index 9ad30da..6d54ad6 100644 --- a/src/core/contexts/win32windowcontext.cpp +++ b/src/core/contexts/win32windowcontext.cpp @@ -33,305 +33,13 @@ #include "qwkglobal_p.h" #include "qwkwindowsextra_p.h" -static QHash<int, const wchar_t *> xmsgHash = // - { - // - {0, L"WM_NULL" }, - {1, L"WM_CREATE" }, - {2, L"WM_DESTROY" }, - {3, L"WM_MOVE" }, - {5, L"WM_SIZE" }, - {6, L"WM_ACTIVATE" }, - {7, L"WM_SETFOCUS" }, - {8, L"WM_KILLFOCUS" }, - {10, L"WM_ENABLE" }, - {11, L"WM_SETREDRAW" }, - {12, L"WM_SETTEXT" }, - {13, L"WM_GETTEXT" }, - {14, L"WM_GETTEXTLENGTH" }, - {15, L"WM_PAINT" }, - {16, L"WM_CLOSE" }, - {17, L"WM_QUERYENDSESSION" }, - {18, L"WM_QUIT" }, - {19, L"WM_QUERYOPEN" }, - {20, L"WM_ERASEBKGND" }, - {21, L"WM_SYSCOLORCHANGE" }, - {22, L"WM_ENDSESSION" }, - {24, L"WM_SHOWWINDOW" }, - {25, L"WM_CTLCOLOR" }, - {26, L"WM_WININICHANGE" }, - {27, L"WM_DEVMODECHANGE" }, - {28, L"WM_ACTIVATEAPP" }, - {29, L"WM_FONTCHANGE" }, - {30, L"WM_TIMECHANGE" }, - {31, L"WM_CANCELMODE" }, - {32, L"WM_SETCURSOR" }, - {33, L"WM_MOUSEACTIVATE" }, - {34, L"WM_CHILDACTIVATE" }, - {35, L"WM_QUEUESYNC" }, - {36, L"WM_GETMINMAXINFO" }, - {38, L"WM_PAINTICON" }, - {39, L"WM_ICONERASEBKGND" }, - {40, L"WM_NEXTDLGCTL" }, - {42, L"WM_SPOOLERSTATUS" }, - {43, L"WM_DRAWITEM" }, - {44, L"WM_MEASUREITEM" }, - {45, L"WM_DELETEITEM" }, - {46, L"WM_VKEYTOITEM" }, - {47, L"WM_CHARTOITEM" }, - {48, L"WM_SETFONT" }, - {49, L"WM_GETFONT" }, - {50, L"WM_SETHOTKEY" }, - {51, L"WM_GETHOTKEY" }, - {55, L"WM_QUERYDRAGICON" }, - {57, L"WM_COMPAREITEM" }, - {61, L"WM_GETOBJECT" }, - {65, L"WM_COMPACTING" }, - {68, L"WM_COMMNOTIFY" }, - {70, L"WM_WINDOWPOSCHANGING" }, - {71, L"WM_WINDOWPOSCHANGED" }, - {72, L"WM_POWER" }, - {73, L"WM_COPYGLOBALDATA" }, - {74, L"WM_COPYDATA" }, - {75, L"WM_CANCELJOURNAL" }, - {78, L"WM_NOTIFY" }, - {80, L"WM_INPUTLANGCHANGEREQUEST"}, - {81, L"WM_INPUTLANGCHANGE" }, - {82, L"WM_TCARD" }, - {83, L"WM_HELP" }, - {84, L"WM_USERCHANGED" }, - {85, L"WM_NOTIFYFORMAT" }, - {123, L"WM_CONTEXTMENU" }, - {124, L"WM_STYLECHANGING" }, - {125, L"WM_STYLECHANGED" }, - {126, L"WM_DISPLAYCHANGE" }, - {127, L"WM_GETICON" }, - {128, L"WM_SETICON" }, - {129, L"WM_NCCREATE" }, - {130, L"WM_NCDESTROY" }, - {131, L"WM_NCCALCSIZE" }, - {132, L"WM_NCHITTEST" }, - {133, L"WM_NCPAINT" }, - {134, L"WM_NCACTIVATE" }, - {135, L"WM_GETDLGCODE" }, - {136, L"WM_SYNCPAINT" }, - {160, L"WM_NCMOUSEMOVE" }, - {161, L"WM_NCLBUTTONDOWN" }, - {162, L"WM_NCLBUTTONUP" }, - {163, L"WM_NCLBUTTONDBLCLK" }, - {164, L"WM_NCRBUTTONDOWN" }, - {165, L"WM_NCRBUTTONUP" }, - {166, L"WM_NCRBUTTONDBLCLK" }, - {167, L"WM_NCMBUTTONDOWN" }, - {168, L"WM_NCMBUTTONUP" }, - {169, L"WM_NCMBUTTONDBLCLK" }, - {171, L"WM_NCXBUTTONDOWN" }, - {172, L"WM_NCXBUTTONUP" }, - {173, L"WM_NCXBUTTONDBLCLK" }, - {176, L"EM_GETSEL" }, - {177, L"EM_SETSEL" }, - {178, L"EM_GETRECT" }, - {179, L"EM_SETRECT" }, - {180, L"EM_SETRECTNP" }, - {181, L"EM_SCROLL" }, - {182, L"EM_LINESCROLL" }, - {183, L"EM_SCROLLCARET" }, - {185, L"EM_GETMODIFY" }, - {187, L"EM_SETMODIFY" }, - {188, L"EM_GETLINECOUNT" }, - {189, L"EM_LINEINDEX" }, - {190, L"EM_SETHANDLE" }, - {191, L"EM_GETHANDLE" }, - {192, L"EM_GETTHUMB" }, - {193, L"EM_LINELENGTH" }, - {194, L"EM_REPLACESEL" }, - {195, L"EM_SETFONT" }, - {196, L"EM_GETLINE" }, - {197, L"EM_LIMITTEXT" }, - {197, L"EM_SETLIMITTEXT" }, - {198, L"EM_CANUNDO" }, - {199, L"EM_UNDO" }, - {200, L"EM_FMTLINES" }, - {201, L"EM_LINEFROMCHAR" }, - {202, L"EM_SETWORDBREAK" }, - {203, L"EM_SETTABSTOPS" }, - {204, L"EM_SETPASSWORDCHAR" }, - {205, L"EM_EMPTYUNDOBUFFER" }, - {206, L"EM_GETFIRSTVISIBLELINE" }, - {207, L"EM_SETREADONLY" }, - {209, L"EM_SETWORDBREAKPROC" }, - {209, L"EM_GETWORDBREAKPROC" }, - {210, L"EM_GETPASSWORDCHAR" }, - {211, L"EM_SETMARGINS" }, - {212, L"EM_GETMARGINS" }, - {213, L"EM_GETLIMITTEXT" }, - {214, L"EM_POSFROMCHAR" }, - {215, L"EM_CHARFROMPOS" }, - {216, L"EM_SETIMESTATUS" }, - {217, L"EM_GETIMESTATUS" }, - {224, L"SBM_SETPOS" }, - {225, L"SBM_GETPOS" }, - {226, L"SBM_SETRANGE" }, - {227, L"SBM_GETRANGE" }, - {228, L"SBM_ENABLE_ARROWS" }, - {230, L"SBM_SETRANGEREDRAW" }, - {233, L"SBM_SETSCROLLINFO" }, - {234, L"SBM_GETSCROLLINFO" }, - {235, L"SBM_GETSCROLLBARINFO" }, - {240, L"BM_GETCHECK" }, - {241, L"BM_SETCHECK" }, - {242, L"BM_GETSTATE" }, - {243, L"BM_SETSTATE" }, - {244, L"BM_SETSTYLE" }, - {245, L"BM_CLICK" }, - {246, L"BM_GETIMAGE" }, - {247, L"BM_SETIMAGE" }, - {248, L"BM_SETDONTCLICK" }, - {255, L"WM_INPUT" }, - {256, L"WM_KEYDOWN" }, - {256, L"WM_KEYFIRST" }, - {257, L"WM_KEYUP" }, - {258, L"WM_CHAR" }, - {259, L"WM_DEADCHAR" }, - {260, L"WM_SYSKEYDOWN" }, - {261, L"WM_SYSKEYUP" }, - {262, L"WM_SYSCHAR" }, - {263, L"WM_SYSDEADCHAR" }, - {264, L"WM_KEYLAST" }, - {265, L"WM_UNICHAR" }, - {265, L"WM_WNT_CONVERTREQUESTEX" }, - {266, L"WM_CONVERTREQUEST" }, - {267, L"WM_CONVERTRESULT" }, - {268, L"WM_INTERIM" }, - {269, L"WM_IME_STARTCOMPOSITION" }, - {270, L"WM_IME_ENDCOMPOSITION" }, - {271, L"WM_IME_COMPOSITION" }, - {271, L"WM_IME_KEYLAST" }, - {272, L"WM_INITDIALOG" }, - {273, L"WM_COMMAND" }, - {274, L"WM_SYSCOMMAND" }, - {275, L"WM_TIMER" }, - {276, L"WM_HSCROLL" }, - {277, L"WM_VSCROLL" }, - {278, L"WM_INITMENU" }, - {279, L"WM_INITMENUPOPUP" }, - {280, L"WM_SYSTIMER" }, - {287, L"WM_MENUSELECT" }, - {288, L"WM_MENUCHAR" }, - {289, L"WM_ENTERIDLE" }, - {290, L"WM_MENURBUTTONUP" }, - {291, L"WM_MENUDRAG" }, - {292, L"WM_MENUGETOBJECT" }, - {293, L"WM_UNINITMENUPOPUP" }, - {294, L"WM_MENUCOMMAND" }, - {295, L"WM_CHANGEUISTATE" }, - {296, L"WM_UPDATEUISTATE" }, - {297, L"WM_QUERYUISTATE" }, - {306, L"WM_CTLCOLORMSGBOX" }, - {307, L"WM_CTLCOLOREDIT" }, - {308, L"WM_CTLCOLORLISTBOX" }, - {309, L"WM_CTLCOLORBTN" }, - {310, L"WM_CTLCOLORDLG" }, - {311, L"WM_CTLCOLORSCROLLBAR" }, - {312, L"WM_CTLCOLORSTATIC" }, - {512, L"WM_MOUSEFIRST" }, - {512, L"WM_MOUSEMOVE" }, - {513, L"WM_LBUTTONDOWN" }, - {514, L"WM_LBUTTONUP" }, - {515, L"WM_LBUTTONDBLCLK" }, - {516, L"WM_RBUTTONDOWN" }, - {517, L"WM_RBUTTONUP" }, - {518, L"WM_RBUTTONDBLCLK" }, - {519, L"WM_MBUTTONDOWN" }, - {520, L"WM_MBUTTONUP" }, - {521, L"WM_MBUTTONDBLCLK" }, - {521, L"WM_MOUSELAST" }, - {522, L"WM_MOUSEWHEEL" }, - {523, L"WM_XBUTTONDOWN" }, - {524, L"WM_XBUTTONUP" }, - {525, L"WM_XBUTTONDBLCLK" }, - {528, L"WM_PARENTNOTIFY" }, - {529, L"WM_ENTERMENULOOP" }, - {530, L"WM_EXITMENULOOP" }, - {531, L"WM_NEXTMENU" }, - {532, L"WM_SIZING" }, - {533, L"WM_CAPTURECHANGED" }, - {534, L"WM_MOVING" }, - {536, L"WM_POWERBROADCAST" }, - {537, L"WM_DEVICECHANGE" }, - {544, L"WM_MDICREATE" }, - {545, L"WM_MDIDESTROY" }, - {546, L"WM_MDIACTIVATE" }, - {547, L"WM_MDIRESTORE" }, - {548, L"WM_MDINEXT" }, - {549, L"WM_MDIMAXIMIZE" }, - {550, L"WM_MDITILE" }, - {551, L"WM_MDICASCADE" }, - {552, L"WM_MDIICONARRANGE" }, - {553, L"WM_MDIGETACTIVE" }, - {560, L"WM_MDISETMENU" }, - {561, L"WM_ENTERSIZEMOVE" }, - {562, L"WM_EXITSIZEMOVE" }, - {563, L"WM_DROPFILES" }, - {564, L"WM_MDIREFRESHMENU" }, - {640, L"WM_IME_REPORT" }, - {641, L"WM_IME_SETCONTEXT" }, - {642, L"WM_IME_NOTIFY" }, - {643, L"WM_IME_CONTROL" }, - {644, L"WM_IME_COMPOSITIONFULL" }, - {645, L"WM_IME_SELECT" }, - {646, L"WM_IME_CHAR" }, - {648, L"WM_IME_REQUEST" }, - {656, L"WM_IMEKEYDOWN" }, - {656, L"WM_IME_KEYDOWN" }, - {657, L"WM_IMEKEYUP" }, - {657, L"WM_IME_KEYUP" }, - {672, L"WM_NCMOUSEHOVER" }, - {673, L"WM_MOUSEHOVER" }, - {674, L"WM_NCMOUSELEAVE" }, - {675, L"WM_MOUSELEAVE" }, - {768, L"WM_CUT" }, - {769, L"WM_COPY" }, - {770, L"WM_PASTE" }, - {771, L"WM_CLEAR" }, - {772, L"WM_UNDO" }, - {773, L"WM_RENDERFORMAT" }, - {774, L"WM_RENDERALLFORMATS" }, - {775, L"WM_DESTROYCLIPBOARD" }, - {776, L"WM_DRAWCLIPBOARD" }, - {777, L"WM_PAINTCLIPBOARD" }, - {778, L"WM_VSCROLLCLIPBOARD" }, - {779, L"WM_SIZECLIPBOARD" }, - {780, L"WM_ASKCBFORMATNAME" }, - {781, L"WM_CHANGECBCHAIN" }, - {782, L"WM_HSCROLLCLIPBOARD" }, - {783, L"WM_QUERYNEWPALETTE" }, - {784, L"WM_PALETTEISCHANGING" }, - {785, L"WM_PALETTECHANGED" }, - {786, L"WM_HOTKEY" }, - {791, L"WM_PRINT" }, - {792, L"WM_PRINTCLIENT" }, - {793, L"WM_APPCOMMAND" }, - {856, L"WM_HANDHELDFIRST" }, - {863, L"WM_HANDHELDLAST" }, - {864, L"WM_AFXFIRST" }, - {895, L"WM_AFXLAST" }, - {896, L"WM_PENWINFIRST" }, - {897, L"WM_RCRESULT" }, - {898, L"WM_HOOKRCRESULT" }, - {899, L"WM_GLOBALRCCHANGE" }, - {899, L"WM_PENMISCINFO" }, - {900, L"WM_SKB" }, - {901, L"WM_HEDITCTL" }, - {901, L"WM_PENCTL" }, - {902, L"WM_PENMISC" }, - {903, L"WM_CTLINIT" }, - {904, L"WM_PENEVENT" }, - {911, L"WM_PENWINLAST" }, - {1024, L"WM_USER" }, -}; - namespace QWK { + + enum IconButtonClickLevelFlag { + IconButtonClicked = 1, + IconButtonDoubleClicked = 2, + IconButtonTriggersClose = 4, + }; // The thickness of an auto-hide taskbar in pixels. static constexpr const quint8 kAutoHideTaskBarThickness = 2; @@ -373,6 +81,8 @@ static void setInternalWindowFrameMargins(QWindow *window, const QMargins &margins) { const QVariant marginsVar = QVariant::fromValue(margins); + + // TODO: Add comments window->setProperty("_q_windowsCustomMargins", marginsVar); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) if (QPlatformWindow *platformWindow = window->handle()) { @@ -416,6 +126,36 @@ SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER); } + static inline bool isFullScreen(HWND hwnd) { + RECT windowRect{}; + ::GetWindowRect(hwnd, &windowRect); + // Compare to the full area of the screen, not the work area. + return (windowRect == getMonitorForWindow(hwnd).rcMonitor); + } + + static inline bool isMaximized(HWND hwnd) { + return ::IsZoomed(hwnd); + } + + static inline bool isMinimized(HWND hwnd) { + return ::IsIconic(hwnd); + } + + static inline bool isWindowNoState(HWND hwnd) { +#if 0 + WINDOWPLACEMENT wp{}; + wp.length = sizeof(wp); + ::GetWindowPlacement(hwnd, &wp); + return ((wp.showCmd == SW_NORMAL) || (wp.showCmd == SW_RESTORE)); +#else + if (isFullScreen(hwnd)) { + return false; + } + const auto style = static_cast<DWORD>(::GetWindowLongPtrW(hwnd, GWL_STYLE)); + return (!(style & (WS_MINIMIZE | WS_MAXIMIZE))); +#endif + } + static inline void bringWindowToFront(HWND hwnd) { HWND oldForegroundWindow = ::GetForegroundWindow(); if (!oldForegroundWindow) { @@ -427,7 +167,7 @@ if (!::IsWindowVisible(hwnd)) { ::ShowWindow(hwnd, SW_SHOW); } - if (IsMinimized(hwnd)) { + if (isMinimized(hwnd)) { // Restore the window if it is minimized. ::ShowWindow(hwnd, SW_RESTORE); // Once we've been restored, throw us on the active monitor. @@ -462,28 +202,6 @@ ::SetActiveWindow(hwnd); // Throw us on the active monitor. moveWindowToMonitor(hwnd, activeMonitor); - } - - static inline bool isFullScreen(HWND hwnd) { - RECT windowRect{}; - ::GetWindowRect(hwnd, &windowRect); - // Compare to the full area of the screen, not the work area. - return (windowRect == getMonitorForWindow(hwnd).rcMonitor); - } - - static inline bool isWindowNoState(HWND hwnd) { -#if 0 - WINDOWPLACEMENT wp{}; - wp.length = sizeof(wp); - ::GetWindowPlacement(hwnd, &wp); - return ((wp.showCmd == SW_NORMAL) || (wp.showCmd == SW_RESTORE)); -#else - if (isFullScreen(hwnd)) { - return false; - } - const auto style = static_cast<DWORD>(::GetWindowLongPtrW(hwnd, GWL_STYLE)); - return (!(style & (WS_MINIMIZE | WS_MAXIMIZE))); -#endif } static void syncPaintEventWithDwm() { @@ -542,7 +260,7 @@ return true; } - const bool maxOrFull = IsMaximized(hWnd) || isFullScreen(hWnd); + const bool maxOrFull = isMaximized(hWnd) || isFullScreen(hWnd); ::EnableMenuItem(hMenu, SC_CLOSE, (MF_BYCOMMAND | MFS_ENABLED)); ::EnableMenuItem(hMenu, SC_MAXIMIZE, (MF_BYCOMMAND | ((maxOrFull || fixedSize) ? MFS_DISABLED : MFS_ENABLED))); @@ -625,7 +343,6 @@ case HTBORDER: return Win32WindowContext::FixedBorder; default: - Q_UNREACHABLE(); break; } return Win32WindowContext::Outside; @@ -917,6 +634,7 @@ // Try hooked procedure and save result LRESULT result; if (ctx->windowProc(hWnd, message, wParam, lParam, &result)) { + // https://github.com/stdware/qwindowkit/issues/45 // Forward the event to user-defined native event filters, there may be some messages // that need to be processed by the user. std::ignore = @@ -991,12 +709,12 @@ auto hWnd = reinterpret_cast<HWND>(m_windowId); #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) const QPoint nativeGlobalPos = - QHighDpi::toNativeGlobalPosition(pos, m_windowHandle); + QHighDpi::toNativeGlobalPosition(pos, m_windowHandle.data()); #else - const QPoint nativeGlobalPos = QHighDpi::toNativePixels(pos, m_windowHandle); + const QPoint nativeGlobalPos = QHighDpi::toNativePixels(pos, m_windowHandle.data()); #endif std::ignore = showSystemMenu_sys(hWnd, qpoint2point(nativeGlobalPos), false, - m_delegate->isHostSizeFixed(m_host)); + isHostSizeFixed()); return; } @@ -1109,10 +827,18 @@ return isSystemBorderEnabled() && !isWin11OrGreater(); } + if (key == QStringLiteral("windows-system-border-enabled")) { + return isSystemBorderEnabled(); + } + if (key == QStringLiteral("border-thickness")) { return m_windowId ? int(getWindowFrameBorderThickness(reinterpret_cast<HWND>(m_windowId))) : 0; + } + + if (key == QStringLiteral("title-bar-height")) { + return m_windowId ? int(getTitleBarHeight(reinterpret_cast<HWND>(m_windowId))) : 0; } return AbstractWindowContext::windowAttribute(key); } @@ -1123,16 +849,10 @@ lastHitTestResult = WindowPart::Outside; lastHitTestResultRaw = HTNOWHERE; - 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) { removeManagedWindow(reinterpret_cast<HWND>(oldWinId)); } - if (!winId) { return; } @@ -1185,10 +905,6 @@ if (!isValidWindow(hWnd, false, true)) { return false; } - - // qDebug().noquote() << QDateTime::currentDateTime() - // << QString::fromStdWString(xmsgHash.value(message, L"")) << Qt::hex - // << message; // Test snap layout if (snapLayoutHandler(hWnd, message, wParam, lParam, result)) { @@ -1559,7 +1275,7 @@ POINT screenPoint{GET_X_LPARAM(dwScreenPos), GET_Y_LPARAM(dwScreenPos)}; ::ScreenToClient(hWnd, &screenPoint); QPoint qtScenePos = QHighDpi::fromNativeLocalPosition(point2qpoint(screenPoint), - m_windowHandle); + m_windowHandle.data()); auto dummy = WindowAgentBase::Unknown; if (isInSystemButtons(qtScenePos, &dummy)) { // We must record whether the last WM_MOUSELEAVE was filtered, because if @@ -1678,18 +1394,25 @@ if (lastHitTestResultRaw == HTSYSMENU) { switch (message) { case WM_NCLBUTTONDOWN: - if (iconButtonClickTime == 0) { + if (iconButtonClickLevel == 0) { // 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, WM_NCLBUTTONDOWN, wParam, lParam); + *result = ::DefWindowProcW(hWnd, message, wParam, lParam); iconButtonClickTime = 0; + if (iconButtonClickLevel & IconButtonTriggersClose) { + ::PostMessageW(hWnd, WM_SYSCOMMAND, SC_CLOSE, 0); + } + if (iconButtonClickLevel & IconButtonDoubleClicked) { + iconButtonClickLevel = 0; + } + // Otherwise, no need to reset `iconButtonClickLevel` if not to + // close, if it has value, there must be another incoming + // WM_NCLBUTTONDOWN } else { - *result = FALSE; - emulateClientAreaMessage(hWnd, message, wParam, lParam); + iconButtonClickLevel = 0; } break; case WM_NCLBUTTONDBLCLK: @@ -1852,15 +1575,24 @@ auto clientWidth = RECT_WIDTH(clientRect); auto clientHeight = RECT_HEIGHT(clientRect); - QPoint qtScenePos = - QHighDpi::fromNativeLocalPosition(point2qpoint(nativeLocalPos), m_windowHandle); + QPoint qtScenePos = QHighDpi::fromNativeLocalPosition(point2qpoint(nativeLocalPos), + m_windowHandle.data()); - bool isFixedSize = m_delegate->isHostSizeFixed(m_host); - bool isTitleBar = isInTitleBarDraggableArea(qtScenePos); + int frameSize = getResizeBorderThickness(hWnd); + + bool isFixedWidth = isHostWidthFixed(); + bool isFixedHeight = isHostHeightFixed(); + bool isFixedSize = isHostSizeFixed(); + bool isInLeftBorder = nativeLocalPos.x <= frameSize; + bool isInTopBorder = nativeLocalPos.y <= frameSize; + bool isInRightBorder = nativeLocalPos.x > clientWidth - frameSize; + bool isInBottomBorder = nativeLocalPos.y > clientHeight - frameSize; + bool isInTitleBar = isInTitleBarDraggableArea(qtScenePos); + WindowAgentBase::SystemButton sysButtonType = WindowAgentBase::Unknown; + bool isInCaptionButtons = isInSystemButtons(qtScenePos, &sysButtonType); bool dontOverrideCursor = false; // ### TODO - WindowAgentBase::SystemButton sysButtonType = WindowAgentBase::Unknown; - if (!isFixedSize && isInSystemButtons(qtScenePos, &sysButtonType)) { + if (isInCaptionButtons) { // Firstly, we set the hit test result to a default value to be able to detect // whether we have changed it or not afterwards. *result = HTNOWHERE; @@ -1870,29 +1602,41 @@ // window is not maximized/fullscreen/minimized, of course). if (isWindowNoState(hWnd)) { static constexpr const quint8 kBorderSize = 2; - bool isTop = (nativeLocalPos.y <= kBorderSize); + bool isTop = nativeLocalPos.y <= kBorderSize; bool isLeft = nativeLocalPos.x <= kBorderSize; - bool isRight = (nativeLocalPos.x >= (clientWidth - kBorderSize)); + bool isRight = nativeLocalPos.x > clientWidth - kBorderSize; if (isTop || isLeft || isRight) { - if (dontOverrideCursor) { + if (isFixedSize || dontOverrideCursor) { // The user doesn't want the window to be resized, so we tell // Windows we are in the client area so that the controls beneath // the mouse cursor can still be hovered or clicked. - *result = (isTitleBar ? HTCAPTION : HTCLIENT); + *result = isInTitleBar ? HTCAPTION : HTCLIENT; } else { if (isTop) { if (isLeft) { - *result = HTTOPLEFT; + if (isFixedWidth) { + *result = HTTOP; + } else if (isFixedHeight) { + *result = HTLEFT; + } else { + *result = HTTOPLEFT; + } } else if (isRight) { - *result = HTTOPRIGHT; + if (isFixedWidth) { + *result = HTTOP; + } else if (isFixedHeight) { + *result = HTRIGHT; + } else { + *result = HTTOPRIGHT; + } } else { - *result = HTTOP; + *result = isFixedHeight ? HTBORDER : HTTOP; } } else { - if (isLeft) { - *result = HTLEFT; + if (isFixedWidth) { + *result = HTBORDER; } else { - *result = HTRIGHT; + *result = isLeft ? HTLEFT : HTRIGHT; } } } @@ -1934,10 +1678,8 @@ // OK, we are not inside any chrome buttons, try to find out which part of the // window are we hitting. - bool max = IsMaximized(hWnd); + bool max = isMaximized(hWnd); bool full = isFullScreen(hWnd); - int frameSize = getResizeBorderThickness(hWnd); - bool isTop = (nativeLocalPos.y < frameSize); if (isSystemBorderEnabled()) { // This will handle the left, right and bottom parts of the frame @@ -1949,8 +1691,51 @@ // outside the window, that is, the three transparent window resize area. // Returning HTCLIENT will confuse Windows, we can't put our controls there // anyway. - *result = ((isFixedSize || dontOverrideCursor) ? HTBORDER - : originalHitTestResult); + *result = HTNOWHERE; // Make sure we can know we don't set any value + // explicitly later. + if (originalHitTestResult == HTCAPTION) { + } else if (isFixedSize || dontOverrideCursor) { + *result = HTBORDER; + } else if (isFixedWidth || isFixedHeight) { + if (originalHitTestResult == HTTOPLEFT) { + if (isFixedWidth) { + *result = HTTOP; + } else { + *result = HTLEFT; + } + } else if (originalHitTestResult == HTTOPRIGHT) { + if (isFixedWidth) { + *result = HTTOP; + } else { + *result = HTRIGHT; + } + } else if (originalHitTestResult == HTBOTTOMRIGHT) { + if (isFixedWidth) { + *result = HTBOTTOM; + } else { + *result = HTRIGHT; + } + } else if (originalHitTestResult == HTBOTTOMLEFT) { + if (isFixedWidth) { + *result = HTBOTTOM; + } else { + *result = HTLEFT; + } + } else if (originalHitTestResult == HTLEFT || + originalHitTestResult == HTRIGHT) { + if (isFixedWidth) { + *result = HTBORDER; + } + } else if (originalHitTestResult == HTTOP || + originalHitTestResult == HTBOTTOM) { + if (isFixedHeight) { + *result = HTBORDER; + } + } + } + if (*result == HTNOWHERE) { + *result = originalHitTestResult; + } return true; } if (full) { @@ -1958,7 +1743,7 @@ return true; } if (max) { - *result = (isTitleBar ? HTCAPTION : HTCLIENT); + *result = isInTitleBar ? HTCAPTION : HTCLIENT; return true; } // At this point, we know that the cursor is inside the client area, @@ -1966,16 +1751,25 @@ // title bar or the drag bar. Apparently, it must be the drag bar or // the little border at the top which the user can use to move or // resize the window. - if (isTop) { + if (isInTopBorder) { // Return HTCLIENT instead of HTBORDER here, because the mouse is // inside our homemade title bar now, return HTCLIENT to let our // title bar can still capture mouse events. - *result = ((isFixedSize || dontOverrideCursor) - ? (isTitleBar ? HTCAPTION : HTCLIENT) - : HTTOP); + *result = [&]() { + if (isFixedSize || isFixedHeight || dontOverrideCursor || + (isFixedWidth && (isInLeftBorder || isInRightBorder))) { + if (isInTitleBar) { + return HTCAPTION; + } else { + return HTCLIENT; + } + } else { + return HTTOP; + } + }(); return true; } - if (isTitleBar) { + if (isInTitleBar) { *result = HTCAPTION; return true; } @@ -1986,58 +1780,86 @@ *result = HTCLIENT; return true; } - if (max) { - *result = (isTitleBar ? HTCAPTION : HTCLIENT); + if (max || isFixedSize || dontOverrideCursor) { + *result = isInTitleBar ? HTCAPTION : HTCLIENT; return true; } - if (!isFixedSize) { - const bool isBottom = (nativeLocalPos.y >= (clientHeight - frameSize)); - // Make the border a little wider to let the user easy to resize on corners. - const auto scaleFactor = ((isTop || isBottom) ? qreal(2) : qreal(1)); - const int scaledFrameSize = std::round(qreal(frameSize) * scaleFactor); - const bool isLeft = (nativeLocalPos.x < scaledFrameSize); - const bool isRight = (nativeLocalPos.x >= (clientWidth - scaledFrameSize)); - if (dontOverrideCursor && (isTop || isBottom || isLeft || isRight)) { - // Return HTCLIENT instead of HTBORDER here, because the mouse is - // inside the window now, return HTCLIENT to let the controls - // inside our window can still capture mouse events. - *result = (isTitleBar ? HTCAPTION : HTCLIENT); - return true; + if (isFixedWidth || isFixedHeight) { + if (isInLeftBorder && isInTopBorder) { + if (isFixedWidth) { + *result = HTTOP; + } else { + *result = HTLEFT; + } + } else if (isInRightBorder && isInTopBorder) { + if (isFixedWidth) { + *result = HTTOP; + } else { + *result = HTRIGHT; + } + } else if (isInRightBorder && isInBottomBorder) { + if (isFixedWidth) { + *result = HTBOTTOM; + } else { + *result = HTRIGHT; + } + } else if (isInLeftBorder && isInBottomBorder) { + if (isFixedWidth) { + *result = HTBOTTOM; + } else { + *result = HTLEFT; + } + } else if (isInLeftBorder || isInRightBorder) { + if (isFixedWidth) { + *result = HTCLIENT; + } else { + *result = isInLeftBorder ? HTLEFT : HTRIGHT; + } + } else if (isInTopBorder || isInBottomBorder) { + if (isFixedHeight) { + *result = HTCLIENT; + } else { + *result = isInTopBorder ? HTTOP : HTBOTTOM; + } + } else { + *result = HTCLIENT; } - if (isTop) { - if (isLeft) { + return true; + } else { + if (isInTopBorder) { + if (isInLeftBorder) { *result = HTTOPLEFT; return true; } - if (isRight) { + if (isInRightBorder) { *result = HTTOPRIGHT; return true; } *result = HTTOP; return true; } - if (isBottom) { - if (isLeft) { + if (isInBottomBorder) { + if (isInLeftBorder) { *result = HTBOTTOMLEFT; return true; } - if (isRight) { + if (isInRightBorder) { *result = HTBOTTOMRIGHT; return true; } *result = HTBOTTOM; return true; } - if (isLeft) { + if (isInLeftBorder) { *result = HTLEFT; return true; } - if (isRight) { + if (isInRightBorder) { *result = HTRIGHT; return true; } } - if (isTitleBar) { + if (isInTitleBar) { *result = HTCAPTION; return true; } @@ -2230,9 +2052,9 @@ // that's also how most applications customize their title bars on Windows. It's // totally OK but since we want to preserve as much original frame as possible, // we can't use that solution. - const LRESULT hitTestResult = ::DefWindowProcW(hWnd, WM_NCCALCSIZE, wParam, lParam); - if ((hitTestResult != HTERROR) && (hitTestResult != HTNOWHERE)) { - *result = hitTestResult; + const LRESULT originalResult = ::DefWindowProcW(hWnd, WM_NCCALCSIZE, wParam, lParam); + if (originalResult != 0) { + *result = originalResult; return true; } // Re-apply the original top from before the size of the default frame was @@ -2246,7 +2068,7 @@ clientRect->top = originalTop; } - const bool max = IsMaximized(hWnd); + const bool max = isMaximized(hWnd); const bool full = isFullScreen(hWnd); // We don't need this correction when we're fullscreen. We will // have the WS_POPUP size, so we don't have to worry about @@ -2351,7 +2173,10 @@ // of the upper-left non-client area. It's confirmed that this issue exists // from Windows 7 to Windows 10. Not tested on Windows 11 yet. Don't know // whether it exists on Windows XP to Windows Vista or not. - *result = wParam ? WVR_REDRAW : FALSE; + + // https://github.com/chromium/chromium/blob/5d297da3cf2a642e9ace2b23fed097370bc70814/ui/views/win/hwnd_message_handler.cc#L2330 + // Do not return WVR_REDRAW otherwise child HWNDs will be mispositioned. + *result = FALSE; return true; } @@ -2361,7 +2186,7 @@ return {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; }; const auto getNativeGlobalPosFromKeyboard = [hWnd]() -> POINT { - const bool maxOrFull = IsMaximized(hWnd) || isFullScreen(hWnd); + const bool maxOrFull = isMaximized(hWnd) || isFullScreen(hWnd); const quint32 frameSize = getResizeBorderThickness(hWnd); const quint32 horizontalOffset = ((maxOrFull || !isSystemBorderEnabled()) ? 0 : frameSize); @@ -2393,9 +2218,12 @@ switch (message) { case WM_RBUTTONUP: { const POINT nativeLocalPos = getNativePosFromMouse(); - const QPoint qtScenePos = - QHighDpi::fromNativeLocalPosition(point2qpoint(nativeLocalPos), m_windowHandle); - if (isInTitleBarDraggableArea(qtScenePos)) { + const QPoint qtScenePos = QHighDpi::fromNativeLocalPosition( + point2qpoint(nativeLocalPos), m_windowHandle.data()); + WindowAgentBase::SystemButton sysButtonType = WindowAgentBase::Unknown; + if (isInTitleBarDraggableArea(qtScenePos) || + (isInSystemButtons(qtScenePos, &sysButtonType) && + sysButtonType == WindowAgentBase::WindowIcon)) { shouldShowSystemMenu = true; nativeGlobalPos = nativeLocalPos; ::ClientToScreen(hWnd, &nativeGlobalPos); @@ -2445,7 +2273,10 @@ if (shouldShowSystemMenu) { static HHOOK mouseHook = nullptr; static std::optional<POINT> mouseClickPos; + static bool mouseDoubleClicked = false; bool mouseHookedLocal = false; + + // The menu is triggered by a click on icon button if (iconButtonClickTime > 0) { POINT menuPos{0, static_cast<LONG>(getTitleBarHeight(hWnd))}; if (const auto tb = titleBar()) { @@ -2462,11 +2293,23 @@ WH_MOUSE, [](int nCode, WPARAM wParam, LPARAM lParam) { if (nCode >= 0) { - if (wParam == WM_LBUTTONDOWN || wParam == WM_LBUTTONDBLCLK) { - auto pMouseStruct = reinterpret_cast<MOUSEHOOKSTRUCT *>(lParam); - if (pMouseStruct) { - mouseClickPos = pMouseStruct->pt; + switch (wParam) { + case WM_LBUTTONDBLCLK: + mouseDoubleClicked = true; + Q_FALLTHROUGH(); + + // case WM_POINTERDOWN: + + case WM_LBUTTONDOWN: { + auto pMouseStruct = + reinterpret_cast<MOUSEHOOKSTRUCT *>(lParam); + if (pMouseStruct) { + mouseClickPos = pMouseStruct->pt; + } + break; } + default: + break; } } return ::CallNextHookEx(nullptr, nCode, wParam, lParam); @@ -2475,33 +2318,37 @@ mouseHookedLocal = true; } } - bool res = showSystemMenu_sys(hWnd, nativeGlobalPos, broughtByKeyboard, - m_delegate->isHostSizeFixed(m_host)); + + bool res = + showSystemMenu_sys(hWnd, nativeGlobalPos, broughtByKeyboard, isHostSizeFixed()); // Uninstall mouse hook and check if it's a double-click if (mouseHookedLocal) { ::UnhookWindowsHookEx(mouseHook); // Emulate the Windows icon button's behavior - static uint32_t doubleClickTime = ::GetDoubleClickTime(); - if (!res && mouseClickPos.has_value() && - ::GetTickCount64() - iconButtonClickTime <= doubleClickTime) { - POINT nativeGlobalPos; - ::GetCursorPos(&nativeGlobalPos); + if (!res && mouseClickPos.has_value()) { POINT nativeLocalPos = mouseClickPos.value(); - // qDebug() << point2qpoint(nativeLocalPos) << point2qpoint(nativeGlobalPos); ::ScreenToClient(hWnd, &nativeLocalPos); QPoint qtScenePos = QHighDpi::fromNativeLocalPosition( - point2qpoint(nativeLocalPos), m_windowHandle); + point2qpoint(nativeLocalPos), m_windowHandle.data()); WindowAgentBase::SystemButton sysButtonType = WindowAgentBase::Unknown; if (isInSystemButtons(qtScenePos, &sysButtonType) && sysButtonType == WindowAgentBase::WindowIcon) { - ::PostMessageW(hWnd, WM_SYSCOMMAND, SC_CLOSE, 0); + iconButtonClickLevel |= IconButtonClicked; + if (::GetTickCount64() - iconButtonClickTime <= ::GetDoubleClickTime()) { + iconButtonClickLevel |= IconButtonTriggersClose; + } } + } + + if (mouseDoubleClicked) { + iconButtonClickLevel |= IconButtonDoubleClicked; } mouseHook = nullptr; mouseClickPos.reset(); + mouseDoubleClicked = false; } // QPA's internal code will handle system menu events separately, and its -- Gitblit v1.9.1