| | |
| | | |
| | | 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; |
| | | |
| | |
| | | // 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; |
| | | emulateClientAreaMessage(hWnd, message, wParam, lParam); |
| | | return true; |
| | | } |
| | | |
| | | if (lastHitTestResultRaw == HTSYSMENU) { |
| | | switch (message) { |
| | | case WM_NCLBUTTONDOWN: |
| | | 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, message, wParam, lParam); |
| | | 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 { |
| | | iconButtonClickLevel = 0; |
| | | } |
| | | break; |
| | | case WM_NCLBUTTONDBLCLK: |
| | | // A message of WM_SYSCOMMAND with SC_CLOSE will be sent by Windows |
| | | *result = ::DefWindowProcW(hWnd, message, wParam, lParam); |
| | | break; |
| | | default: |
| | | *result = FALSE; |
| | | emulateClientAreaMessage(hWnd, message, wParam, lParam); |
| | | break; |
| | | } |
| | | } else { |
| | | // According to MSDN, we should return non-zero for X button messages to |
| | |
| | | (((message >= WM_NCXBUTTONDOWN) && (message <= WM_NCXBUTTONDBLCLK)) |
| | | ? TRUE |
| | | : FALSE); |
| | | emulateClientAreaMessage(hWnd, message, wParam, lParam); |
| | | } |
| | | emulateClientAreaMessage(hWnd, message, wParam, lParam); |
| | | return true; |
| | | } |
| | | break; |
| | |
| | | break; |
| | | } |
| | | if (shouldShowSystemMenu) { |
| | | bool triggeredByIconButton = iconButtonClickTime > 0; |
| | | if (triggeredByIconButton) { |
| | | POINT menuPos{ 0, static_cast<LONG>(getTitleBarHeight(hWnd)) }; |
| | | 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()) { |
| | | auto titleBarHeight = qreal(m_delegate->mapGeometryToScene(tb).height()); |
| | | titleBarHeight *= m_windowHandle->devicePixelRatio(); |
| | |
| | | } |
| | | ::ClientToScreen(hWnd, &menuPos); |
| | | nativeGlobalPos = menuPos; |
| | | |
| | | // Install mouse hook |
| | | if (!mouseHook) { |
| | | mouseHook = ::SetWindowsHookExW( |
| | | WH_MOUSE, |
| | | [](int nCode, WPARAM wParam, LPARAM lParam) { |
| | | if (nCode >= 0) { |
| | | 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); |
| | | }, |
| | | nullptr, ::GetCurrentThreadId()); |
| | | mouseHookedLocal = true; |
| | | } |
| | | } |
| | | |
| | | 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); |
| | | // 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()) { |
| | | POINT nativeLocalPos = mouseClickPos.value(); |
| | | ::ScreenToClient(hWnd, &nativeLocalPos); |
| | | QPoint qtScenePos = QHighDpi::fromNativeLocalPosition( |
| | | point2qpoint(nativeLocalPos), m_windowHandle); |
| | | WindowAgentBase::SystemButton sysButtonType = WindowAgentBase::Unknown; |
| | | if (isInSystemButtons(qtScenePos, &sysButtonType) && |
| | | sysButtonType == WindowAgentBase::WindowIcon) { |
| | | iconButtonClickLevel |= IconButtonClicked; |
| | | if (::GetTickCount64() - iconButtonClickTime <= doubleClickTime) { |
| | | iconButtonClickLevel |= IconButtonTriggersClose; |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (mouseDoubleClicked) { |
| | | iconButtonClickLevel |= IconButtonDoubleClicked; |
| | | } |
| | | |
| | | mouseHook = nullptr; |
| | | mouseClickPos.reset(); |
| | | mouseDoubleClicked = false; |
| | | } |
| | | |
| | | // QPA's internal code will handle system menu events separately, and its |