| | |
| | | _DWMWA_MICA_EFFECT = 1029 |
| | | }; |
| | | |
| | | // Types used with DWMWA_SYSTEMBACKDROP_TYPE |
| | | enum _DWM_SYSTEMBACKDROP_TYPE { |
| | | _DWMSBT_AUTO, // [Default] Let DWM automatically decide the system-drawn backdrop for this window. |
| | | _DWMSBT_NONE, // [Disable] Do not draw any system backdrop. |
| | | _DWMSBT_MAINWINDOW, // [Mica] Draw the backdrop material effect corresponding to a long-lived window. |
| | | _DWMSBT_TRANSIENTWINDOW, // [Acrylic] Draw the backdrop material effect corresponding to a transient window. |
| | | _DWMSBT_TABBEDWINDOW, // [Mica Alt] Draw the backdrop material effect corresponding to a window with a tabbed title bar. |
| | | }; |
| | | |
| | | enum WINDOWCOMPOSITIONATTRIB { |
| | | WCA_UNDEFINED = 0, |
| | | WCA_NCRENDERING_ENABLED = 1, |
| | | WCA_NCRENDERING_POLICY = 2, |
| | | WCA_TRANSITIONS_FORCEDISABLED = 3, |
| | | WCA_ALLOW_NCPAINT = 4, |
| | | WCA_CAPTION_BUTTON_BOUNDS = 5, |
| | | WCA_NONCLIENT_RTL_LAYOUT = 6, |
| | | WCA_FORCE_ICONIC_REPRESENTATION = 7, |
| | | WCA_EXTENDED_FRAME_BOUNDS = 8, |
| | | WCA_HAS_ICONIC_BITMAP = 9, |
| | | WCA_THEME_ATTRIBUTES = 10, |
| | | WCA_NCRENDERING_EXILED = 11, |
| | | WCA_NCADORNMENTINFO = 12, |
| | | WCA_EXCLUDED_FROM_LIVEPREVIEW = 13, |
| | | WCA_VIDEO_OVERLAY_ACTIVE = 14, |
| | | WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15, |
| | | WCA_DISALLOW_PEEK = 16, |
| | | WCA_CLOAK = 17, |
| | | WCA_CLOAKED = 18, |
| | | WCA_ACCENT_POLICY = 19, |
| | | WCA_FREEZE_REPRESENTATION = 20, |
| | | WCA_EVER_UNCLOAKED = 21, |
| | | WCA_VISUAL_OWNER = 22, |
| | | WCA_HOLOGRAPHIC = 23, |
| | | WCA_EXCLUDED_FROM_DDA = 24, |
| | | WCA_PASSIVEUPDATEMODE = 25, |
| | | WCA_USEDARKMODECOLORS = 26, |
| | | WCA_CORNER_STYLE = 27, |
| | | WCA_PART_COLOR = 28, |
| | | WCA_DISABLE_MOVESIZE_FEEDBACK = 29, |
| | | WCA_LAST = 30 |
| | | }; |
| | | |
| | | enum ACCENT_STATE { |
| | | ACCENT_DISABLED = 0, |
| | | ACCENT_ENABLE_GRADIENT = 1, |
| | | ACCENT_ENABLE_TRANSPARENTGRADIENT = 2, |
| | | ACCENT_ENABLE_BLURBEHIND = 3, // Traditional DWM blur |
| | | ACCENT_ENABLE_ACRYLICBLURBEHIND = 4, // RS4 1803 |
| | | ACCENT_ENABLE_HOST_BACKDROP = 5, // RS5 1809 |
| | | ACCENT_INVALID_STATE = 6 // Using this value will remove the window background |
| | | }; |
| | | |
| | | enum ACCENT_FLAG { |
| | | ACCENT_NONE = 0, |
| | | ACCENT_ENABLE_ACRYLIC = 1, |
| | | ACCENT_ENABLE_ACRYLIC_WITH_LUMINOSITY = 482 |
| | | }; |
| | | |
| | | struct ACCENT_POLICY { |
| | | DWORD dwAccentState; |
| | | DWORD dwAccentFlags; |
| | | DWORD dwGradientColor; // #AABBGGRR |
| | | DWORD dwAnimationId; |
| | | }; |
| | | using PACCENT_POLICY = ACCENT_POLICY *; |
| | | |
| | | struct WINDOWCOMPOSITIONATTRIBDATA { |
| | | WINDOWCOMPOSITIONATTRIB Attrib; |
| | | PVOID pvData; |
| | | SIZE_T cbData; |
| | | }; |
| | | using PWINDOWCOMPOSITIONATTRIBDATA = WINDOWCOMPOSITIONATTRIBDATA *; |
| | | |
| | | using SetWindowCompositionAttributePtr = BOOL(WINAPI *)(HWND, PWINDOWCOMPOSITIONATTRIBDATA); |
| | | |
| | | // The thickness of an auto-hide taskbar in pixels. |
| | | static constexpr const quint8 kAutoHideTaskBarThickness = 2; |
| | | |
| | |
| | | DYNAMIC_API_DECLARE(DwmGetCompositionTimingInfo); |
| | | DYNAMIC_API_DECLARE(DwmGetWindowAttribute); |
| | | DYNAMIC_API_DECLARE(DwmSetWindowAttribute); |
| | | DYNAMIC_API_DECLARE(DwmExtendFrameIntoClientArea); |
| | | DYNAMIC_API_DECLARE(GetDpiForWindow); |
| | | DYNAMIC_API_DECLARE(GetSystemMetricsForDpi); |
| | | DYNAMIC_API_DECLARE(GetDpiForMonitor); |
| | |
| | | |
| | | #undef DYNAMIC_API_DECLARE |
| | | |
| | | SetWindowCompositionAttributePtr pSetWindowCompositionAttribute = nullptr; |
| | | |
| | | private: |
| | | DynamicApis() { |
| | | #define DYNAMIC_API_RESOLVE(DLL, NAME) \ |
| | |
| | | QSystemLibrary user32(QStringLiteral("user32")); |
| | | DYNAMIC_API_RESOLVE(user32, GetDpiForWindow); |
| | | DYNAMIC_API_RESOLVE(user32, GetSystemMetricsForDpi); |
| | | DYNAMIC_API_RESOLVE(user32, SetWindowCompositionAttribute); |
| | | |
| | | QSystemLibrary shcore(QStringLiteral("shcore")); |
| | | DYNAMIC_API_RESOLVE(shcore, GetDpiForMonitor); |
| | |
| | | DYNAMIC_API_RESOLVE(dwmapi, DwmGetCompositionTimingInfo); |
| | | DYNAMIC_API_RESOLVE(dwmapi, DwmGetWindowAttribute); |
| | | DYNAMIC_API_RESOLVE(dwmapi, DwmSetWindowAttribute); |
| | | DYNAMIC_API_RESOLVE(dwmapi, DwmExtendFrameIntoClientArea); |
| | | |
| | | QSystemLibrary winmm(QStringLiteral("winmm")); |
| | | DYNAMIC_API_RESOLVE(winmm, timeGetDevCaps); |
| | |
| | | |
| | | static inline bool isWin11OrGreater() { |
| | | static const bool result = IsWindows11OrGreater_Real(); |
| | | return result; |
| | | } |
| | | |
| | | static inline bool isWin1122H2OrGreater() { |
| | | static const bool result = IsWindows1122H2OrGreater_Real(); |
| | | return result; |
| | | } |
| | | |
| | |
| | | getSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi); |
| | | } |
| | | |
| | | static void updateInternalWindowFrameMargins(HWND hwnd, QWindow *window) { |
| | | const auto margins = [hwnd]() -> QMargins { |
| | | const auto titleBarHeight = int(getTitleBarHeight(hwnd)); |
| | | if (isWin10OrGreater()) { |
| | | return {0, -titleBarHeight, 0, 0}; |
| | | } else { |
| | | const auto frameSize = int(getResizeBorderThickness(hwnd)); |
| | | return {-frameSize, -titleBarHeight, -frameSize, -frameSize}; |
| | | } |
| | | }(); |
| | | static void setInternalWindowFrameMargins(QWindow *window, const QMargins &margins) { |
| | | const QVariant marginsVar = QVariant::fromValue(margins); |
| | | window->setProperty("_q_windowsCustomMargins", marginsVar); |
| | | #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) |
| | |
| | | monitorInfo.cbSize = sizeof(monitorInfo); |
| | | ::GetMonitorInfoW(monitor, &monitorInfo); |
| | | return monitorInfo; |
| | | }; |
| | | } |
| | | |
| | | static inline void moveToDesktopCenter(HWND hwnd) { |
| | | const auto monitorInfo = getMonitorForWindow(hwnd); |
| | | static inline void moveWindowToDesktopCenter(HWND hwnd) { |
| | | MONITORINFOEXW monitorInfo = getMonitorForWindow(hwnd); |
| | | RECT windowRect{}; |
| | | ::GetWindowRect(hwnd, &windowRect); |
| | | const auto newX = monitorInfo.rcMonitor.left + (RECT_WIDTH(monitorInfo.rcMonitor) - RECT_WIDTH(windowRect)) / 2; |
| | | const auto newY = monitorInfo.rcMonitor.top + (RECT_HEIGHT(monitorInfo.rcMonitor) - RECT_HEIGHT(windowRect)) / 2; |
| | | const auto newX = monitorInfo.rcMonitor.left + |
| | | (RECT_WIDTH(monitorInfo.rcMonitor) - RECT_WIDTH(windowRect)) / 2; |
| | | const auto newY = monitorInfo.rcMonitor.top + |
| | | (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 void moveWindowToMonitor(HWND hwnd, const MONITORINFOEXW &activeMonitor) { |
| | | RECT currentMonitorRect = getMonitorForWindow(hwnd).rcMonitor; |
| | | RECT activeMonitorRect = activeMonitor.rcMonitor; |
| | | // We are in the same monitor, nothing to adjust here. |
| | | if (currentMonitorRect == activeMonitorRect) { |
| | | return; |
| | | } |
| | | RECT currentWindowRect{}; |
| | | ::GetWindowRect(hwnd, ¤tWindowRect); |
| | | auto newWindowX = |
| | | activeMonitorRect.left + (currentWindowRect.left - currentMonitorRect.left); |
| | | auto newWindowY = activeMonitorRect.top + (currentWindowRect.top - currentMonitorRect.top); |
| | | ::SetWindowPos(hwnd, nullptr, newWindowX, newWindowY, RECT_WIDTH(currentWindowRect), |
| | | RECT_HEIGHT(currentWindowRect), |
| | | SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER); |
| | | } |
| | | |
| | | static inline void bringWindowToFront(HWND hwnd) { |
| | | HWND oldForegroundWindow = ::GetForegroundWindow(); |
| | | if (!oldForegroundWindow) { |
| | | // The foreground window can be NULL, it's not an API error. |
| | | return; |
| | | } |
| | | MONITORINFOEXW activeMonitor = getMonitorForWindow(oldForegroundWindow); |
| | | // We need to show the window first, otherwise we won't be able to bring it to front. |
| | | if (!::IsWindowVisible(hwnd)) { |
| | | ::ShowWindow(hwnd, SW_SHOW); |
| | | } |
| | | 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. |
| | | moveWindowToMonitor(hwnd, activeMonitor); |
| | | // When the window is restored, it will always become the foreground window. |
| | | // So return early here, we don't need the following code to bring it to front. |
| | | return; |
| | | } |
| | | // OK, our window is not minimized, so now we will try to bring it to front manually. |
| | | // First try to send a message to the current foreground window to check whether |
| | | // it is currently hanging or not. |
| | | if (!::SendMessageTimeoutW(oldForegroundWindow, WM_NULL, 0, 0, |
| | | SMTO_BLOCK | SMTO_ABORTIFHUNG | SMTO_NOTIMEOUTIFNOTHUNG, 1000, |
| | | nullptr)) { |
| | | // The foreground window hangs, can't activate current window. |
| | | return; |
| | | } |
| | | DWORD windowThreadProcessId = ::GetWindowThreadProcessId(oldForegroundWindow, nullptr); |
| | | DWORD currentThreadId = ::GetCurrentThreadId(); |
| | | // We won't be able to change a window's Z order if it's not our own window, |
| | | // so we use this small technique to pretend the foreground window is ours. |
| | | ::AttachThreadInput(windowThreadProcessId, currentThreadId, TRUE); |
| | | |
| | | [[maybe_unused]] const auto &cleaner = |
| | | qScopeGuard([windowThreadProcessId, currentThreadId]() { |
| | | ::AttachThreadInput(windowThreadProcessId, currentThreadId, FALSE); // |
| | | }); |
| | | |
| | | ::BringWindowToTop(hwnd); |
| | | // Activate the window too. This will force us to the virtual desktop this |
| | | // window is on, if it's on another virtual desktop. |
| | | ::SetActiveWindow(hwnd); |
| | | // Throw us on the active monitor. |
| | | moveWindowToMonitor(hwnd, activeMonitor); |
| | | } |
| | | |
| | | static inline bool isFullScreen(HWND hwnd) { |
| | |
| | | return ::CallWindowProcW(g_qtWindowProc, hWnd, message, wParam, lParam); |
| | | } |
| | | |
| | | static inline void addManagedWindow(QWindow *window, HWND hWnd, Win32WindowContext *ctx) { |
| | | const auto margins = [hWnd]() -> QMargins { |
| | | const auto titleBarHeight = int(getTitleBarHeight(hWnd)); |
| | | if (isWin10OrGreater()) { |
| | | return {0, -titleBarHeight, 0, 0}; |
| | | } else { |
| | | const auto frameSize = int(getResizeBorderThickness(hWnd)); |
| | | return {-frameSize, -titleBarHeight, -frameSize, -frameSize}; |
| | | } |
| | | }(); |
| | | |
| | | // Inform Qt we want and have set custom margins |
| | | setInternalWindowFrameMargins(window, margins); |
| | | |
| | | // Store original window proc |
| | | if (!g_qtWindowProc) { |
| | | g_qtWindowProc = reinterpret_cast<WNDPROC>(::GetWindowLongPtrW(hWnd, GWLP_WNDPROC)); |
| | | } |
| | | |
| | | // Hook window proc |
| | | ::SetWindowLongPtrW(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(QWKHookedWndProc)); |
| | | |
| | | // Install global native event filter |
| | | WindowsNativeEventFilter::install(); |
| | | |
| | | // Save window handle mapping |
| | | g_wndProcHash->insert(hWnd, ctx); |
| | | } |
| | | |
| | | static inline void removeManagedWindow(HWND hWnd) { |
| | | // Remove window handle mapping |
| | | if (!g_wndProcHash->remove(hWnd)) |
| | | return; |
| | | |
| | | // Remove event filter if the all windows has been destroyed |
| | | if (g_wndProcHash->empty()) { |
| | | WindowsNativeEventFilter::uninstall(); |
| | | } |
| | | } |
| | | |
| | | Win32WindowContext::Win32WindowContext() : AbstractWindowContext() { |
| | | } |
| | | |
| | | Win32WindowContext::~Win32WindowContext() { |
| | | // Remove window handle mapping |
| | | if (auto hWnd = reinterpret_cast<HWND>(windowId); hWnd) { |
| | | g_wndProcHash->remove(hWnd); |
| | | |
| | | // Remove event filter if the all windows has been destroyed |
| | | if (g_wndProcHash->empty()) { |
| | | WindowsNativeEventFilter::uninstall(); |
| | | } |
| | | if (windowId) { |
| | | removeManagedWindow(reinterpret_cast<HWND>(windowId)); |
| | | } |
| | | } |
| | | |
| | |
| | | void Win32WindowContext::virtual_hook(int id, void *data) { |
| | | switch (id) { |
| | | case CentralizeHook: { |
| | | const auto hwnd = reinterpret_cast<HWND>(m_windowHandle->winId()); |
| | | moveToDesktopCenter(hwnd); |
| | | if (!windowId) |
| | | return; |
| | | const auto hwnd = reinterpret_cast<HWND>(windowId); |
| | | moveWindowToDesktopCenter(hwnd); |
| | | return; |
| | | } |
| | | |
| | | case RaiseWindowHook: { |
| | | if (!windowId) |
| | | return; |
| | | const auto hwnd = reinterpret_cast<HWND>(windowId); |
| | | bringWindowToFront(hwnd); |
| | | return; |
| | | } |
| | | |
| | | case ShowSystemMenuHook: { |
| | | if (!windowId) |
| | | return; |
| | | const auto &pos = *static_cast<const QPoint *>(data); |
| | | auto hWnd = reinterpret_cast<HWND>(m_windowHandle->winId()); |
| | | auto hWnd = reinterpret_cast<HWND>(windowId); |
| | | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) |
| | | const QPoint nativeGlobalPos = QHighDpi::toNativeGlobalPosition(pos, m_windowHandle); |
| | | const QPoint nativeGlobalPos = |
| | | QHighDpi::toNativeGlobalPosition(pos, m_windowHandle); |
| | | #else |
| | | const QPoint nativeGlobalPos = QHighDpi::toNativePixels(pos, m_windowHandle); |
| | | #endif |
| | | showSystemMenu2(hWnd, qpoint2point(nativeGlobalPos), false, |
| | | m_delegate->isHostSizeFixed(m_host)); |
| | | return; |
| | | } |
| | | |
| | | case WindowAttributeChangedHook: { |
| | | auto args = static_cast<void **>(data); |
| | | const auto &key = *static_cast<const QString *>(args[0]); |
| | | const auto &newVar = *static_cast<const QVariant *>(args[1]); |
| | | const auto &oldVar = *static_cast<const QVariant *>(args[2]); |
| | | |
| | | if (!windowId) |
| | | return; |
| | | const auto hwnd = reinterpret_cast<HWND>(windowId); |
| | | |
| | | const DynamicApis &apis = DynamicApis::instance(); |
| | | |
| | | if (key == QStringLiteral("frame-shadow")) { |
| | | if (newVar.toBool()) { |
| | | // TODO: set off |
| | | } else { |
| | | // TODO: set on |
| | | } |
| | | } else if (key == QStringLiteral("mica")) { |
| | | if (!isWin11OrGreater()) { |
| | | return; |
| | | } |
| | | if (newVar.toBool()) { |
| | | // We need to extend the window frame into the whole client area to be able |
| | | // to see the blurred window background. |
| | | static constexpr const MARGINS margins = {-1, -1, -1, -1}; |
| | | apis.pDwmExtendFrameIntoClientArea(hwnd, &margins); |
| | | if (isWin1122H2OrGreater()) { |
| | | // Use official DWM API to enable Mica, available since Windows 11 22H2 (10.0.22621). |
| | | const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_MAINWINDOW; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, sizeof(backdropType)); |
| | | } else { |
| | | // Use undocumented DWM API to enable Mica, available since Windows 11 (10.0.22000). |
| | | const BOOL enable = TRUE; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_MICA_EFFECT, &enable, sizeof(enable)); |
| | | } |
| | | } else { |
| | | if (isWin1122H2OrGreater()) { |
| | | const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_AUTO; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, sizeof(backdropType)); |
| | | } else { |
| | | const BOOL enable = FALSE; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_MICA_EFFECT, &enable, sizeof(enable)); |
| | | } |
| | | static constexpr const MARGINS margins = {0, 0, 0, 0}; |
| | | apis.pDwmExtendFrameIntoClientArea(hwnd, &margins); |
| | | } |
| | | } else if (key == QStringLiteral("mica-alt")) { |
| | | if (!isWin1122H2OrGreater()) { |
| | | return; |
| | | } |
| | | if (newVar.toBool()) { |
| | | // We need to extend the window frame into the whole client area to be able |
| | | // to see the blurred window background. |
| | | static constexpr const MARGINS margins = {-1, -1, -1, -1}; |
| | | apis.pDwmExtendFrameIntoClientArea(hwnd, &margins); |
| | | // Use official DWM API to enable Mica Alt, available since Windows 11 22H2 (10.0.22621). |
| | | const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_TABBEDWINDOW; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, sizeof(backdropType)); |
| | | } else { |
| | | const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_AUTO; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, sizeof(backdropType)); |
| | | static constexpr const MARGINS margins = {0, 0, 0, 0}; |
| | | apis.pDwmExtendFrameIntoClientArea(hwnd, &margins); |
| | | } |
| | | } else if (key == QStringLiteral("acrylic-material")) { |
| | | if (newVar.toBool()) { |
| | | // We need to extend the window frame into the whole client area to be able |
| | | // to see the blurred window background. |
| | | static constexpr const MARGINS margins = {-1, -1, -1, -1}; |
| | | apis.pDwmExtendFrameIntoClientArea(hwnd, &margins); |
| | | if (isWin11OrGreater()) { |
| | | const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_TRANSIENTWINDOW; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, sizeof(backdropType)); |
| | | } else { |
| | | ACCENT_POLICY policy{}; |
| | | policy.dwAccentState = ACCENT_ENABLE_ACRYLICBLURBEHIND; |
| | | policy.dwAccentFlags = ACCENT_ENABLE_ACRYLIC_WITH_LUMINOSITY; |
| | | // This API expects the #AABBGGRR format. |
| | | //policy.dwGradientColor = DWORD(qRgba(gradientColor.blue(), gradientColor.green(), gradientColor.red(), gradientColor.alpha())); |
| | | WINDOWCOMPOSITIONATTRIBDATA wcad{}; |
| | | wcad.Attrib = WCA_ACCENT_POLICY; |
| | | wcad.pvData = &policy; |
| | | wcad.cbData = sizeof(policy); |
| | | apis.pSetWindowCompositionAttribute(hwnd, &wcad); |
| | | } |
| | | } else { |
| | | if (isWin11OrGreater()) { |
| | | const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_AUTO; |
| | | apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, sizeof(backdropType)); |
| | | } else { |
| | | ACCENT_POLICY policy{}; |
| | | policy.dwAccentState = ACCENT_DISABLED; |
| | | policy.dwAccentFlags = ACCENT_NONE; |
| | | WINDOWCOMPOSITIONATTRIBDATA wcad{}; |
| | | wcad.Attrib = WCA_ACCENT_POLICY; |
| | | wcad.pvData = &policy; |
| | | wcad.cbData = sizeof(policy); |
| | | apis.pSetWindowCompositionAttribute(hwnd, &wcad); |
| | | } |
| | | static constexpr const MARGINS margins = {0, 0, 0, 0}; |
| | | apis.pDwmExtendFrameIntoClientArea(hwnd, &margins); |
| | | } |
| | | } |
| | | return; |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | case DrawWindows10BorderHook: { |
| | | if (!windowId) |
| | | return; |
| | | |
| | | auto args = static_cast<void **>(data); |
| | | auto &painter = *static_cast<QPainter *>(args[0]); |
| | | const auto &rect = *static_cast<const QRect *>(args[1]); |
| | | const auto ®ion = *static_cast<const QRegion *>(args[2]); |
| | | const auto hwnd = reinterpret_cast<HWND>(m_windowHandle->winId()); |
| | | const auto hwnd = reinterpret_cast<HWND>(windowId); |
| | | |
| | | QPen pen; |
| | | pen.setWidth(getWindowFrameBorderThickness(hwnd) * 2); |
| | |
| | | return; |
| | | } |
| | | |
| | | default: { |
| | | // unreachable |
| | | default: |
| | | break; |
| | | } |
| | | } |
| | | AbstractWindowContext::virtual_hook(id, data); |
| | | } |
| | |
| | | return getWindowFrameBorderThickness(reinterpret_cast<HWND>(windowId)); |
| | | } |
| | | |
| | | bool Win32WindowContext::setupHost() { |
| | | void Win32WindowContext::winIdChanged() { |
| | | // If the original window id is valid, remove all resources related |
| | | if (windowId) { |
| | | removeManagedWindow(reinterpret_cast<HWND>(windowId)); |
| | | windowId = 0; |
| | | } |
| | | |
| | | if (!m_windowHandle) { |
| | | return; |
| | | } |
| | | |
| | | // Install window hook |
| | | auto winId = m_windowHandle->winId(); |
| | | auto hWnd = reinterpret_cast<HWND>(winId); |
| | | |
| | | #if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) |
| | | for (const auto attr : { _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1, _DWMWA_USE_IMMERSIVE_DARK_MODE }) { |
| | | for (const auto attr : { |
| | | _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1, |
| | | _DWMWA_USE_IMMERSIVE_DARK_MODE, |
| | | }) { |
| | | const BOOL enable = TRUE; |
| | | DynamicApis::instance().pDwmSetWindowAttribute(hWnd, attr, &enable, sizeof(enable)); |
| | | } |
| | | #endif |
| | | |
| | | // Inform Qt we want and have set custom margins |
| | | updateInternalWindowFrameMargins(hWnd, m_windowHandle); |
| | | // Add managed window |
| | | addManagedWindow(m_windowHandle, hWnd, this); |
| | | |
| | | // Store original window proc |
| | | if (!g_qtWindowProc) { |
| | | g_qtWindowProc = reinterpret_cast<WNDPROC>(::GetWindowLongPtrW(hWnd, GWLP_WNDPROC)); |
| | | } |
| | | |
| | | // Hook window proc |
| | | ::SetWindowLongPtrW(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(QWKHookedWndProc)); |
| | | |
| | | // Install global native event filter |
| | | WindowsNativeEventFilter::install(); |
| | | |
| | | // Cache window ID |
| | | // Cache win id |
| | | windowId = winId; |
| | | |
| | | // Save window handle mapping |
| | | g_wndProcHash->insert(hWnd, this); |
| | | |
| | | return true; |
| | | } |
| | | |
| | | bool Win32WindowContext::windowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, |
| | |
| | | const WindowPart currentWindowPart = lastHitTestResult; |
| | | if (message == WM_NCMOUSEMOVE) { |
| | | if (currentWindowPart != 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 |
| | | // widget. At this time, no other widgets will accept the mouse event until |
| | | // QWidgetWindow receives the mouse release event, then set `qt_button_down` |
| | | // to null. |
| | | |
| | | // Imagine the following situation, now the main window has a pop-up menu, |
| | | // the focus is not on the main window, if we click outside the pop-up menu, |
| | | // the menu will close, which seems to be completely fine. But if we close |
| | | // the menu by clicking on the title bar draggable area, then other widgets |
| | | // won't accept the mouse message afterwards. |
| | | |
| | | // Here's the reason. |
| | | // When the mouse is placed in the draggable area of the title bar, there |
| | | // are two situations. |
| | | |
| | | // 1. If the focus is on the main window, and the last result of |
| | | // WM_NCHITTEST is HTCAPTION, the mouse click event in the title bar is |
| | | // taken over by Windows and Qt does not receive the mouse click event. |
| | | |
| | | // 2. If the main window has a pop-up menu, it is completely different. When |
| | | // the mouse is pressed on the title bar, Windows sends the WM_LBUTTONDOWN |
| | | // message to the window plane of the pop-up menu, the menu is closed, but |
| | | // Qt will continue to forward the event to the QWidget under the mouse, and |
| | | // the event will be processed by QWidgetWindow, causing the title bar |
| | | // widget to be implicitly grabbed. After the menu is closed, Windows |
| | | // immediately sends WM_NCHITTEST, because the mouse is in the title bar |
| | | // draggable area, the result is HTCAPTION, so when the mouse is released, |
| | | // Windows sends WM_NCLBUTTONUP, which is a non-client message, and it |
| | | // will be ignored by Qt. As a consequence, QWidgetWindow can't receive a |
| | | // mouse release message in the client area, so the grab remains, and other |
| | | // widgets cannot receive mouse events. |
| | | |
| | | // Since we didn't watch the menu window, we cannot capture any mouse |
| | | // press events sent by Windows, so we cannot solve this problem by |
| | | // recording mouse events. Fortunately, we found that the main window will |
| | | // receive a WM_NCMOUSEMOVE message immediately after the menu is closed, so |
| | | // we just manually send a mouse release event when this message arrives and |
| | | // set qt_button_down to null. Don't worry, when receiving WM_NCMOUSEMOVE, |
| | | // there shouldn't be any control in the state of being grabbed. |
| | | |
| | | // In the native window, although QWidgetWindow handles the forwarded mouse |
| | | // press event when the menu is closed, since the native title bar is not a |
| | | // QWidget, no widget will be grabbed, and `qt_button_down` remains empty, |
| | | // the above problems would not arise. |
| | | |
| | | m_delegate->resetQtGrabbedControl(m_host); |
| | | if (mouseLeaveBlocked) { |
| | | emulateClientAreaMessage(hWnd, message, wParam, lParam, |
| | |
| | | // window from client area, which means we will get previous window part as |
| | | // HTCLIENT if the mouse leaves window from client area and enters window |
| | | // from non-client area, but it has no bad effect. |
| | | |
| | | // Why do we need to call this function here? |
| | | m_delegate->resetQtGrabbedControl(m_host); |
| | | } |
| | | } |
| | |
| | | // function. |
| | | if (wParam && !lParam) { |
| | | centered = true; |
| | | moveToDesktopCenter(hWnd); |
| | | moveWindowToDesktopCenter(hWnd); |
| | | } |
| | | } |
| | | break; |
| | |
| | | // Terminal does, however, later I found that if we choose a proper |
| | | // 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)); // |
| | | }); |
| | |
| | | *result = (isTitleBar ? HTCAPTION : HTCLIENT); |
| | | return true; |
| | | } |
| | | // At this point, we know that the cursor is inside the client area |
| | | // At this point, we know that the cursor is inside the client area, |
| | | // so it has to be either the little border at the top of our custom |
| | | // 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 |
| | |
| | | // the client area as a whole will shift to the left, which looks very abnormal if |
| | | // we don't repaint it. This exception disappears if we add SWP_NOCOPYBITS flag. |
| | | // But I don't know what caused the problem, or why this would solve it. |
| | | static constexpr const auto kBadWindowPosFlag = |
| | | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED; |
| | | const auto windowPos = reinterpret_cast<LPWINDOWPOS>(lParam); |
| | | if (windowPos->flags == 0x37) { |
| | | if (windowPos->flags == kBadWindowPosFlag) { |
| | | windowPos->flags |= SWP_NOCOPYBITS; |
| | | } |
| | | break; |