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 |  283 +++++++++++++++++++++++++++++++++++++++-----------------
 1 files changed, 198 insertions(+), 85 deletions(-)

diff --git a/src/core/contexts/win32windowcontext.cpp b/src/core/contexts/win32windowcontext.cpp
index 84c80ef..6d54ad6 100644
--- a/src/core/contexts/win32windowcontext.cpp
+++ b/src/core/contexts/win32windowcontext.cpp
@@ -81,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()) {
@@ -124,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) {
@@ -135,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.
@@ -170,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() {
@@ -250,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)));
@@ -333,7 +343,6 @@
             case HTBORDER:
                 return Win32WindowContext::FixedBorder;
             default:
-                Q_UNREACHABLE();
                 break;
         }
         return Win32WindowContext::Outside;
@@ -818,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);
     }
@@ -832,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;
         }
@@ -1567,12 +1578,21 @@
                 QPoint qtScenePos = QHighDpi::fromNativeLocalPosition(point2qpoint(nativeLocalPos),
                                                                       m_windowHandle.data());
 
+                int frameSize = getResizeBorderThickness(hWnd);
+
+                bool isFixedWidth = isHostWidthFixed();
+                bool isFixedHeight = isHostHeightFixed();
                 bool isFixedSize = isHostSizeFixed();
-                bool isTitleBar = isInTitleBarDraggableArea(qtScenePos);
+                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;
@@ -1582,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;
                                     }
                                 }
                             }
@@ -1646,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
@@ -1661,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) {
@@ -1670,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,
@@ -1678,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;
                     }
@@ -1698,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;
                     }
@@ -1942,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
@@ -1958,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
@@ -2063,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;
     }
 
@@ -2073,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);

--
Gitblit v1.9.1