| | |
| | | #include "win32windowcontext_p.h" |
| | | #include "qwkcoreglobal_p.h" |
| | | |
| | | #include <optional> |
| | | |
| | | #include <QtCore/QHash> |
| | | #include <QtCore/QAbstractNativeEventFilter> |
| | | #include <QtCore/QCoreApplication> |
| | | #include <QtCore/QOperatingSystemVersion> |
| | | #include <QtCore/QScopeGuard> |
| | | #include <QtGui/QGuiApplication> |
| | | |
| | | #include <QtCore/private/qsystemlibrary_p.h> |
| | | #include <QtGui/private/qhighdpiscaling_p.h> |
| | | |
| | | #include "qwkcoreglobal_p.h" |
| | | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) |
| | | # include <QtGui/private/qguiapplication_p.h> |
| | | #endif |
| | | #include <QtGui/qpa/qplatformwindow.h> |
| | | #if QT_VERSION < QT_VERSION_CHECK(6, 2, 0) |
| | | # include <QtGui/qpa/qplatformnativeinterface.h> |
| | | #else |
| | | # include <QtGui/qpa/qplatformwindow_p.h> |
| | | #endif |
| | | |
| | | #include <shellscalingapi.h> |
| | | #include <dwmapi.h> |
| | | |
| | | Q_DECLARE_METATYPE(QMargins) |
| | | |
| | | namespace QWK { |
| | | |
| | |
| | | |
| | | static WNDPROC g_qtWindowProc = nullptr; // Original Qt window proc function |
| | | |
| | | static struct QWK_Hook { |
| | | QWK_Hook() { |
| | | qApp->setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); |
| | | } |
| | | } g_hook{}; |
| | | |
| | | struct DynamicApis { |
| | | decltype(&::DwmFlush) pDwmFlush = nullptr; |
| | | decltype(&::DwmIsCompositionEnabled) pDwmIsCompositionEnabled = nullptr; |
| | | decltype(&::GetDpiForWindow) pGetDpiForWindow = nullptr; |
| | | decltype(&::GetSystemMetricsForDpi) pGetSystemMetricsForDpi = nullptr; |
| | | decltype(&::GetDpiForMonitor) pGetDpiForMonitor = nullptr; |
| | |
| | | |
| | | QSystemLibrary dwmapi(QStringLiteral("dwmapi.dll")); |
| | | pDwmFlush = reinterpret_cast<decltype(pDwmFlush)>(dwmapi.resolve("DwmFlush")); |
| | | pDwmIsCompositionEnabled = reinterpret_cast<decltype(pDwmIsCompositionEnabled)>( |
| | | dwmapi.resolve("DwmIsCompositionEnabled")); |
| | | } |
| | | |
| | | ~DynamicApis() = default; |
| | |
| | | return hwnd2str(reinterpret_cast<WId>(hwnd)); |
| | | } |
| | | |
| | | static inline bool isWin8OrGreater() { |
| | | static const bool result = |
| | | QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8; |
| | | return result; |
| | | } |
| | | |
| | | static inline bool isWin8Point1OrGreater() { |
| | | static const bool result = |
| | | QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8_1; |
| | |
| | | return result; |
| | | } |
| | | |
| | | static inline bool isDwmCompositionEnabled() { |
| | | if (isWin8OrGreater()) { |
| | | return true; |
| | | } |
| | | const DynamicApis &apis = DynamicApis::instance(); |
| | | if (!apis.pDwmIsCompositionEnabled) { |
| | | return false; |
| | | } |
| | | BOOL enabled = FALSE; |
| | | return SUCCEEDED(apis.pDwmIsCompositionEnabled(&enabled)) && enabled; |
| | | } |
| | | |
| | | static inline void triggerFrameChange(HWND hwnd) { |
| | | Q_ASSERT(hwnd); |
| | | if (!hwnd) { |
| | | return; |
| | | } |
| | | ::SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, |
| | | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER | |
| | | SWP_FRAMECHANGED); |
| | | } |
| | | |
| | | static inline quint32 getDpiForWindow(HWND hwnd) { |
| | | Q_ASSERT(hwnd); |
| | | if (!hwnd) { |
| | | return USER_DEFAULT_SCREEN_DPI; |
| | | return 0; |
| | | } |
| | | const DynamicApis &apis = DynamicApis::instance(); |
| | | if (apis.pGetDpiForWindow) { // Win10 |
| | | if (apis.pGetDpiForWindow) { // Win10 |
| | | return apis.pGetDpiForWindow(hwnd); |
| | | } else if (apis.pGetDpiForMonitor) { // Win8.1 |
| | | HMONITOR monitor = ::MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); |
| | |
| | | } |
| | | } |
| | | |
| | | static inline std::optional<MONITORINFOEXW> getMonitorForWindow(HWND hwnd) { |
| | | static inline quint32 getTitleBarHeight(HWND hwnd) { |
| | | Q_ASSERT(hwnd); |
| | | if (!hwnd) { |
| | | return std::nullopt; |
| | | return 0; |
| | | } |
| | | const auto captionHeight = [hwnd]() -> int { |
| | | const DynamicApis &apis = DynamicApis::instance(); |
| | | if (apis.pGetSystemMetricsForDpi) { |
| | | const quint32 dpi = getDpiForWindow(hwnd); |
| | | return apis.pGetSystemMetricsForDpi(SM_CYCAPTION, dpi); |
| | | } else { |
| | | return ::GetSystemMetrics(SM_CYCAPTION); |
| | | } |
| | | }(); |
| | | return captionHeight + getResizeBorderThickness(hwnd); |
| | | } |
| | | |
| | | static inline void updateInternalWindowFrameMargins(HWND hwnd, QWindow *window) { |
| | | Q_ASSERT(hwnd); |
| | | Q_ASSERT(window); |
| | | if (!hwnd || !window) { |
| | | return; |
| | | } |
| | | const auto margins = [hwnd]() -> QMargins { |
| | | const int titleBarHeight = getTitleBarHeight(hwnd); |
| | | if (isWin10OrGreater()) { |
| | | return {0, -titleBarHeight, 0, 0}; |
| | | } else { |
| | | const int frameSize = getResizeBorderThickness(hwnd); |
| | | return {-frameSize, -titleBarHeight, -frameSize, -frameSize}; |
| | | } |
| | | }(); |
| | | const QVariant marginsVar = QVariant::fromValue(margins); |
| | | window->setProperty("_q_windowsCustomMargins", marginsVar); |
| | | #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) |
| | | if (QPlatformWindow *platformWindow = window->handle()) { |
| | | if (const auto ni = QGuiApplication::platformNativeInterface()) { |
| | | ni->setWindowProperty(platformWindow, QStringLiteral("_q_windowsCustomMargins"), |
| | | marginsVar); |
| | | } |
| | | } |
| | | #else |
| | | if (const auto platformWindow = |
| | | dynamic_cast<QNativeInterface::Private::QWindowsWindow *>(window->handle())) { |
| | | platformWindow->setCustomMargins(margins); |
| | | } |
| | | #endif |
| | | } |
| | | |
| | | static inline MONITORINFOEXW getMonitorForWindow(HWND hwnd) { |
| | | Q_ASSERT(hwnd); |
| | | if (!hwnd) { |
| | | return {}; |
| | | } |
| | | // Use "MONITOR_DEFAULTTONEAREST" here so that we can still get the correct |
| | | // monitor even if the window is minimized. |
| | |
| | | return monitorInfo; |
| | | }; |
| | | |
| | | static inline void moveToDesktopCenter(HWND hwnd) { |
| | | Q_ASSERT(hwnd); |
| | | if (!hwnd) { |
| | | return; |
| | | } |
| | | const auto monitorInfo = getMonitorForWindow(hwnd); |
| | | RECT windowRect{}; |
| | | ::GetWindowRect(hwnd, &windowRect); |
| | | const auto newX = (RECT_WIDTH(monitorInfo.rcMonitor) - RECT_WIDTH(windowRect)) / 2; |
| | | const auto newY = (RECT_HEIGHT(monitorInfo.rcMonitor) - RECT_HEIGHT(windowRect)) / 2; |
| | | ::SetWindowPos(hwnd, nullptr, newX, newY, 0, 0, |
| | | SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER); |
| | | } |
| | | |
| | | static inline bool isFullScreen(HWND hwnd) { |
| | | Q_ASSERT(hwnd); |
| | | if (!hwnd) { |
| | |
| | | } |
| | | RECT windowRect{}; |
| | | ::GetWindowRect(hwnd, &windowRect); |
| | | const std::optional<MONITORINFOEXW> mi = getMonitorForWindow(hwnd); |
| | | // Compare to the full area of the screen, not the work area. |
| | | return (windowRect == mi.value_or(MONITORINFOEXW{}).rcMonitor); |
| | | return (windowRect == getMonitorForWindow(hwnd).rcMonitor); |
| | | } |
| | | |
| | | static inline bool isWindowNoState(HWND hwnd) { |
| | |
| | | } |
| | | |
| | | static bool isValidWindow(HWND hWnd, bool checkVisible, bool checkTopLevel) { |
| | | if (::IsWindow(hWnd) == FALSE) { |
| | | if (!::IsWindow(hWnd)) { |
| | | return false; |
| | | } |
| | | const LONG_PTR styles = ::GetWindowLongPtrW(hWnd, GWL_STYLE); |
| | |
| | | if (exStyles & WS_EX_TOOLWINDOW) { |
| | | return false; |
| | | } |
| | | RECT rect = {0, 0, 0, 0}; |
| | | if (::GetWindowRect(hWnd, &rect) == FALSE) { |
| | | RECT rect{}; |
| | | if (!::GetWindowRect(hWnd, &rect)) { |
| | | return false; |
| | | } |
| | | if ((rect.left >= rect.right) || (rect.top >= rect.bottom)) { |
| | | return false; |
| | | } |
| | | if (checkVisible) { |
| | | if (::IsWindowVisible(hWnd) == FALSE) { |
| | | if (!::IsWindowVisible(hWnd)) { |
| | | return false; |
| | | } |
| | | } |
| | |
| | | public: |
| | | bool nativeEventFilter(const QByteArray &eventType, void *message, |
| | | QT_NATIVE_EVENT_RESULT_TYPE *result) override { |
| | | Q_UNUSED(eventType); |
| | | |
| | | // It has been observed that the pointer that Qt gives us is sometimes null on some |
| | | // machines. We need to guard against it in such scenarios. |
| | | if (!result) { |
| | | return false; |
| | | } |
| | | |
| | | if (lastMessageHandled) { |
| | | *result = static_cast<QT_NATIVE_EVENT_RESULT_TYPE>(lastMessageResult); |
| | | return true; |
| | |
| | | // TODO: Determine whether to show system menu |
| | | // ... |
| | | |
| | | // Since Qt does the necessary processing of the message afterward, we still need to |
| | | // Since Qt does the necessary processing of the WM_NCCALCSIZE afterward, we still need to |
| | | // continue dispatching it. |
| | | if (handled && message != WM_NCCALCSIZE) { |
| | | return result; |
| | | } |
| | | return ::CallWindowProcW(g_qtWindowProc, hWnd, message, wParam, lParam); |
| | | } |
| | | |
| | | Win32WindowContext::Win32WindowContext(QWindow *window, WindowItemDelegate *delegate) |
| | | : AbstractWindowContext(window, delegate) { |
| | | Win32WindowContext::Win32WindowContext(QObject *host, WindowItemDelegate *delegate) |
| | | : AbstractWindowContext(host, delegate) { |
| | | } |
| | | |
| | | Win32WindowContext::~Win32WindowContext() { |
| | |
| | | } |
| | | |
| | | bool Win32WindowContext::setup() { |
| | | auto winId = m_windowHandle->winId(); |
| | | if (!m_windowHandle) { |
| | | return false; |
| | | } |
| | | |
| | | // Install window hook |
| | | auto winId = m_windowHandle->winId(); |
| | | auto hWnd = reinterpret_cast<HWND>(winId); |
| | | |
| | | // Inform Qt we want and have set custom margins |
| | | updateInternalWindowFrameMargins(hWnd, m_windowHandle); |
| | | |
| | | // Store original window proc |
| | | if (!g_qtWindowProc) { |
| | |
| | | const auto &get = [](const int virtualKey) -> bool { |
| | | return (::GetAsyncKeyState(virtualKey) < 0); |
| | | }; |
| | | const bool buttonSwapped = (::GetSystemMetrics(SM_SWAPBUTTON) != FALSE); |
| | | const bool buttonSwapped = ::GetSystemMetrics(SM_SWAPBUTTON); |
| | | if (get(VK_LBUTTON)) { |
| | | result |= (buttonSwapped ? MK_RBUTTON : MK_LBUTTON); |
| | | } |
| | |
| | | // actually lost the hover state. So we filter out these superfluous mouse leave |
| | | // events here to avoid this issue. |
| | | DWORD dwScreenPos = ::GetMessagePos(); |
| | | POINT screenPoint{GET_X_LPARAM(dwScreenPos), GET_Y_LPARAM(dwScreenPos)}; |
| | | ::ScreenToClient(hWnd, &screenPoint); |
| | | QPoint qtScenePos = |
| | | fromNativeLocalPosition(m_windowHandle, QPoint(GET_X_LPARAM(dwScreenPos), |
| | | GET_Y_LPARAM(dwScreenPos))); |
| | | fromNativeLocalPosition(m_windowHandle, {screenPoint.x, screenPoint.y}); |
| | | auto dummy = CoreWindowAgent::Unknown; |
| | | if (isInSystemButtons(qtScenePos, &dummy)) { |
| | | // We must record whether the last WM_MOUSELEAVE was filtered, because if |
| | |
| | | bool Win32WindowContext::customWindowHandler(HWND hWnd, UINT message, WPARAM wParam, |
| | | LPARAM lParam, LRESULT *result) { |
| | | switch (message) { |
| | | case WM_SHOWWINDOW: { |
| | | if (!centered) { |
| | | // If wParam is TRUE, the window is being shown. |
| | | // If lParam is zero, the message was sent because of a call to the ShowWindow |
| | | // function. |
| | | if (wParam && lParam == 0) { |
| | | centered = true; |
| | | moveToDesktopCenter(hWnd); |
| | | } |
| | | } |
| | | break; |
| | | } |
| | | case WM_NCCALCSIZE: { |
| | | // Windows是根据这个消息的返回值来设置窗口的客户区(窗口中真正显示的内容) |
| | | // 和非客户区(标题栏、窗口边框、菜单栏和状态栏等Windows系统自行提供的部分 |
| | |
| | | // simply return 0, which means "preserve the entire old client area |
| | | // and align it with the upper-left corner of our new client area". |
| | | const auto clientRect = |
| | | ((wParam == FALSE) ? reinterpret_cast<LPRECT>(lParam) |
| | | : &(reinterpret_cast<LPNCCALCSIZE_PARAMS>(lParam))->rgrc[0]); |
| | | wParam ? &(reinterpret_cast<LPNCCALCSIZE_PARAMS>(lParam))->rgrc[0] |
| | | : reinterpret_cast<LPRECT>(lParam); |
| | | if (isWin10OrGreater()) { |
| | | // Store the original top margin before the default window procedure applies the |
| | | // default frame. |
| | |
| | | // we have to use another way to judge this if we are running |
| | | // on Windows 7 or Windows 8. |
| | | if (isWin8Point1OrGreater()) { |
| | | const std::optional<MONITORINFOEXW> monitorInfo = |
| | | getMonitorForWindow(hWnd); |
| | | const RECT monitorRect = monitorInfo.value().rcMonitor; |
| | | const RECT monitorRect = getMonitorForWindow(hWnd).rcMonitor; |
| | | // This helper can be used to determine if there's an |
| | | // auto-hide taskbar on the given edge of the monitor |
| | | // we're currently on. |
| | |
| | | // 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 == FALSE ? FALSE : WVR_REDRAW; |
| | | *result = wParam ? WVR_REDRAW : FALSE; |
| | | return true; |
| | | } |
| | | case WM_NCHITTEST: { |
| | |
| | | // window can behave just like a normal Win32 window even if we now |
| | | // doesn't have a title bar at all. Most importantly, the flicker and |
| | | // jitter during window resizing is totally gone now. The disadvantage |
| | | // is we have to draw a top frame border ourself. Previously I thought |
| | | // is we have to draw a top frame border ourselves. Previously I thought |
| | | // we have to do the black magic in WM_PAINT just like what Windows |
| | | // Terminal does, however, later I found that if we choose a proper |
| | | // color, our homemade top border can almost have exactly the same |
| | |
| | | QPoint qtScenePos = fromNativeLocalPosition( |
| | | m_windowHandle, QPoint(nativeLocalPos.x, nativeLocalPos.y)); |
| | | |
| | | bool isFixedSize = /*isWindowFixedSize()*/ false; // ### FIXME |
| | | bool isFixedSize = m_delegate->isHostSizeFixed(m_host); |
| | | bool isTitleBar = isInTitleBarDraggableArea(qtScenePos); |
| | | bool dontOverrideCursor = false; // ### TODO |
| | | bool dontOverrideCursor = false; // ### TODO |
| | | |
| | | CoreWindowAgent::SystemButton sysButtonType = CoreWindowAgent::Unknown; |
| | | if (!isFixedSize && isInSystemButtons(qtScenePos, &sysButtonType)) { |
| | |
| | | return true; |
| | | } |
| | | *result = HTCLIENT; |
| | | return true; |
| | | } else { |
| | | if (full) { |
| | | *result = HTCLIENT; |
| | |
| | | return true; |
| | | } |
| | | *result = HTCLIENT; |
| | | return true; |
| | | } |
| | | return true; |
| | | } |
| | | default: |
| | | break; |
| | | } |
| | | if (!isWin10OrGreater()) { |
| | | switch (message) { |
| | | case WM_NCUAHDRAWCAPTION: |
| | | case WM_NCUAHDRAWFRAME: { |
| | | // These undocumented messages are sent to draw themed window |
| | | // borders. Block them to prevent drawing borders over the client |
| | | // area. |
| | | *result = FALSE; |
| | | return true; |
| | | } |
| | | case WM_NCPAINT: { |
| | | // 边框阴影处于非客户区的范围,因此如果直接阻止非客户区的绘制,会导致边框阴影丢失 |
| | | |
| | | if (!isDwmCompositionEnabled()) { |
| | | // Only block WM_NCPAINT when DWM composition is disabled. If |
| | | // it's blocked when DWM composition is enabled, the frame |
| | | // shadow won't be drawn. |
| | | *result = FALSE; |
| | | return true; |
| | | } else { |
| | | break; |
| | | } |
| | | } |
| | | case WM_NCACTIVATE: { |
| | | if (isDwmCompositionEnabled()) { |
| | | // DefWindowProc won't repaint the window border if lParam (normally a HRGN) |
| | | // is -1. See the following link's "lParam" section: |
| | | // https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-ncactivate |
| | | // Don't use "*result = 0" here, otherwise the window won't respond to the |
| | | // window activation state change. |
| | | *result = ::DefWindowProcW(hWnd, WM_NCACTIVATE, wParam, -1); |
| | | } else { |
| | | if (wParam) { |
| | | *result = FALSE; |
| | | } else { |
| | | *result = TRUE; |
| | | } |
| | | } |
| | | return true; |
| | | } |
| | | case WM_SETICON: |
| | | case WM_SETTEXT: { |
| | | // Disable painting while these messages are handled to prevent them |
| | | // from drawing a window caption over the client area. |
| | | const auto oldStyle = static_cast<DWORD>(::GetWindowLongPtrW(hWnd, GWL_STYLE)); |
| | | // Prevent Windows from drawing the default title bar by temporarily |
| | | // toggling the WS_VISIBLE style. |
| | | const DWORD newStyle = (oldStyle & ~WS_VISIBLE); |
| | | ::SetWindowLongPtrW(hWnd, GWL_STYLE, static_cast<LONG_PTR>(newStyle)); |
| | | triggerFrameChange(hWnd); |
| | | const LRESULT originalResult = ::DefWindowProcW(hWnd, message, wParam, lParam); |
| | | ::SetWindowLongPtrW(hWnd, GWL_STYLE, static_cast<LONG_PTR>(oldStyle)); |
| | | triggerFrameChange(hWnd); |
| | | *result = originalResult; |
| | | return true; |
| | | } |
| | | default: |
| | | break; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | } |
| | | } |