diff --git a/include/FramelessHelper/Core/utils.h b/include/FramelessHelper/Core/utils.h index 0fa15d27..a0eed285 100644 --- a/include/FramelessHelper/Core/utils.h +++ b/include/FramelessHelper/Core/utils.h @@ -121,6 +121,7 @@ FRAMELESSHELPER_CORE_API void enableNonClientAreaDpiScalingForWindow(const WId w FRAMELESSHELPER_CORE_API void fixupChildWindowsDpiMessage(const WId windowId); FRAMELESSHELPER_CORE_API void fixupDialogsDpiScaling(); FRAMELESSHELPER_CORE_API void setDarkModeAllowedForApp(const bool allow = true); +FRAMELESSHELPER_CORE_API void bringWindowToFront(const WId windowId); #endif // Q_OS_WINDOWS #ifdef Q_OS_LINUX diff --git a/src/core/framelesshelper_win.cpp b/src/core/framelesshelper_win.cpp index 63e318e5..82dc9924 100644 --- a/src/core/framelesshelper_win.cpp +++ b/src/core/framelesshelper_win.cpp @@ -805,7 +805,7 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me // we have to use another way to judge this if we are running // on Windows 7 or Windows 8. if (WindowsVersionHelper::isWin8Point1OrGreater()) { - MONITORINFO monitorInfo; + MONITORINFOEXW monitorInfo; SecureZeroMemory(&monitorInfo, sizeof(monitorInfo)); monitorInfo.cbSize = sizeof(monitorInfo); const HMONITOR monitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST); diff --git a/src/core/framelesshelpercore_global.cpp b/src/core/framelesshelpercore_global.cpp index c7da434d..2aed3bdb 100644 --- a/src/core/framelesshelpercore_global.cpp +++ b/src/core/framelesshelpercore_global.cpp @@ -260,16 +260,16 @@ void uninitialize() VersionInfo version() { - static const VersionInfo result = []() -> VersionInfo { - const char *_compiler = []() -> const char * { return COMPILER_STRING; }(); - const bool _debug = []() -> bool { + static const auto result = []() -> VersionInfo { + const auto _compiler = []() -> const char * { return COMPILER_STRING; }(); + const auto _debug = []() -> bool { #ifdef _DEBUG return true; #else return false; #endif }(); - const bool _static = []() -> bool { + const auto _static = []() -> bool { #ifdef FRAMELESSHELPER_CORE_STATIC return true; #else diff --git a/src/core/framelessmanager.cpp b/src/core/framelessmanager.cpp index 0e7612be..03f695a1 100644 --- a/src/core/framelessmanager.cpp +++ b/src/core/framelessmanager.cpp @@ -95,7 +95,7 @@ FRAMELESSHELPER_STRING_CONSTANT2(IconFontFamilyName_common, "micon_nb") [[nodiscard]] static inline QString iconFontFamilyName() { - static const QString result = []() -> QString { + static const auto result = []() -> QString { #ifdef Q_OS_WINDOWS if (WindowsVersionHelper::isWin11OrGreater()) { return kIconFontFamilyName_win11; @@ -158,7 +158,7 @@ void FramelessManagerPrivate::initializeIconFont() QFont FramelessManagerPrivate::getIconFont() { - static const QFont font = []() -> QFont { + static const auto font = []() -> QFont { QFont f = {}; f.setFamily(iconFontFamilyName()); f.setPointSize(kIconFontPointSize); @@ -338,7 +338,7 @@ void FramelessManagerPrivate::notifyWallpaperHasChangedOrNot() bool FramelessManagerPrivate::usePureQtImplementation() { - static const bool result = []() -> bool { + static const auto result = []() -> bool { #ifdef Q_OS_WINDOWS return FramelessConfig::instance()->isSet(Option::UseCrossPlatformQtImplementation); #else diff --git a/src/core/utils_linux.cpp b/src/core/utils_linux.cpp index 8884c2d4..f82b1c49 100644 --- a/src/core/utils_linux.cpp +++ b/src/core/utils_linux.cpp @@ -525,7 +525,7 @@ WallpaperAspectStyle Utils::getWallpaperAspectStyle() bool Utils::isBlurBehindWindowSupported() { - static const bool result = []() -> bool { + static const auto result = []() -> bool { if (FramelessConfig::instance()->isSet(Option::ForceNonNativeBackgroundBlur)) { return false; } diff --git a/src/core/utils_mac.mm b/src/core/utils_mac.mm index 691b3bb8..3717ac67 100644 --- a/src/core/utils_mac.mm +++ b/src/core/utils_mac.mm @@ -584,7 +584,7 @@ static void sendEvent(id obj, SEL sel, NSEvent *event) const auto proxy = new NSWindowProxy(qwindow, nswindow); g_macUtilsData()->hash.insert(windowId, proxy); } - static const int hook = []() -> int { + static const auto hook = []() -> int { FramelessHelper::Core::registerUninitializeHook([](){ const QMutexLocker locker(&g_macUtilsData()->mutex); if (g_macUtilsData()->hash.isEmpty()) { @@ -745,7 +745,7 @@ static void sendEvent(id obj, SEL sel, NSEvent *event) bool Utils::isBlurBehindWindowSupported() { - static const bool result = []() -> bool { + static const auto result = []() -> bool { if (FramelessConfig::instance()->isSet(Option::ForceNonNativeBackgroundBlur)) { return false; } diff --git a/src/core/utils_win.cpp b/src/core/utils_win.cpp index 2df146ec..e2314b06 100644 --- a/src/core/utils_win.cpp +++ b/src/core/utils_win.cpp @@ -799,6 +799,11 @@ FRAMELESSHELPER_STRING_CONSTANT(AdjustWindowRectExForDpi) FRAMELESSHELPER_STRING_CONSTANT(GetDpiMetrics) FRAMELESSHELPER_STRING_CONSTANT(EnablePerMonitorDialogScaling) FRAMELESSHELPER_STRING_CONSTANT(EnableChildWindowDpiMessage) +FRAMELESSHELPER_STRING_CONSTANT(GetForegroundWindow) +FRAMELESSHELPER_STRING_CONSTANT(SendMessageTimeoutW) +FRAMELESSHELPER_STRING_CONSTANT(AttachThreadInput) +FRAMELESSHELPER_STRING_CONSTANT(BringWindowToTop) +FRAMELESSHELPER_STRING_CONSTANT(SetActiveWindow) struct Win32UtilsHelperData { @@ -871,7 +876,7 @@ struct SYSTEM_METRIC [[nodiscard]] static inline bool doCompareWindowsVersion(const VersionNumber &targetOsVer) { - static const std::optional currentOsVer = []() -> std::optional { + static const auto currentOsVer = []() -> std::optional { if (API_NT_AVAILABLE(RtlGetVersion)) { using RtlGetVersionPtr = _NTSTATUS(WINAPI *)(PRTL_OSVERSIONINFOW); const auto pRtlGetVersion = @@ -950,6 +955,69 @@ struct SYSTEM_METRIC return __getSystemErrorMessage(function, dwError); } +[[nodiscard]] static inline bool operator==(const RECT &lhs, const RECT &rhs) noexcept +{ + return ((lhs.left == rhs.left) && (lhs.top == rhs.top) + && (lhs.right == rhs.right) && (lhs.bottom == rhs.bottom)); +} + +[[nodiscard]] static inline std::optional getMonitorForWindow(const HWND hwnd) +{ + Q_ASSERT(hwnd); + if (!hwnd) { + return std::nullopt; + } + const HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + if (!monitor) { + WARNING << Utils::getSystemErrorMessage(kMonitorFromWindow); + return std::nullopt; + } + MONITORINFOEXW monitorInfo; + SecureZeroMemory(&monitorInfo, sizeof(monitorInfo)); + monitorInfo.cbSize = sizeof(monitorInfo); + if (GetMonitorInfoW(monitor, &monitorInfo) == FALSE) { + WARNING << Utils::getSystemErrorMessage(kGetMonitorInfoW); + return std::nullopt; + } + return monitorInfo; +}; + +static inline void moveWindowToMonitor(const HWND hwnd, const MONITORINFOEXW &activeMonitor) +{ + Q_ASSERT(hwnd); + if (!hwnd) { + return; + } + const std::optional currentMonitor = getMonitorForWindow(hwnd); + if (!currentMonitor.has_value()) { + WARNING << "Failed to retrieve the window's monitor."; + return; + } + const RECT currentMonitorRect = currentMonitor.value().rcMonitor; + const RECT activeMonitorRect = activeMonitor.rcMonitor; + // We are in the same monitor, nothing to adjust here. + if (currentMonitorRect == activeMonitorRect) { + return; + } + RECT currentWindowRect = {}; + if (GetWindowRect(hwnd, ¤tWindowRect) == FALSE) { + WARNING << Utils::getSystemErrorMessage(kGetWindowRect); + return; + } + const int currentWindowWidth = qAbs(currentWindowRect.right - currentWindowRect.left); + const int currentWindowHeight = qAbs(currentWindowRect.bottom - currentWindowRect.top); + const int currentWindowOffsetX = (currentWindowRect.left - currentMonitorRect.left); + const int currentWindowOffsetY = (currentWindowRect.top - currentMonitorRect.top); + const int newWindowX = activeMonitorRect.left + currentWindowOffsetX; + const int newWindowY = activeMonitorRect.top + currentWindowOffsetY; + static constexpr const UINT flags = + (SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOOWNERZORDER); + if (SetWindowPos(hwnd, nullptr, newWindowX, newWindowY, + currentWindowWidth, currentWindowHeight, flags) == FALSE) { + WARNING << Utils::getSystemErrorMessage(kSetWindowPos); + } +} + [[nodiscard]] static inline int getSystemMetrics2(const WId windowId, const int index, const bool horizontal, const bool scaled) { @@ -1365,29 +1433,18 @@ bool Utils::isFullScreen(const WId windowId) return false; } const auto hwnd = reinterpret_cast(windowId); - RECT wndRect = {}; - if (GetWindowRect(hwnd, &wndRect) == FALSE) { + RECT windowRect = {}; + if (GetWindowRect(hwnd, &windowRect) == FALSE) { WARNING << getSystemErrorMessage(kGetWindowRect); return false; } - // According to Microsoft Docs, we should compare to the primary screen's geometry - // (if we can't determine the correct screen of our window). - const HMONITOR mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY); - if (!mon) { - WARNING << getSystemErrorMessage(kMonitorFromWindow); - return false; - } - MONITORINFO mi; - SecureZeroMemory(&mi, sizeof(mi)); - mi.cbSize = sizeof(mi); - if (GetMonitorInfoW(mon, &mi) == FALSE) { - WARNING << getSystemErrorMessage(kGetMonitorInfoW); + const std::optional mi = getMonitorForWindow(hwnd); + if (!mi.has_value()) { + WARNING << "Failed to retrieve the window's monitor."; return false; } // Compare to the full area of the screen, not the work area. - const RECT scrRect = mi.rcMonitor; - return ((wndRect.left == scrRect.left) && (wndRect.top == scrRect.top) - && (wndRect.right == scrRect.right) && (wndRect.bottom == scrRect.bottom)); + return (windowRect == mi.value().rcMonitor); } bool Utils::isWindowNoState(const WId windowId) @@ -1850,7 +1907,7 @@ void Utils::startSystemResize(QWindow *window, const Qt::Edges edges, const QPoi bool Utils::isWindowFrameBorderVisible() { - static const bool result = []() -> bool { + static const auto result = []() -> bool { const FramelessConfig * const config = FramelessConfig::instance(); if (config->isSet(Option::UseCrossPlatformQtImplementation)) { return false; @@ -2444,7 +2501,7 @@ WallpaperAspectStyle Utils::getWallpaperAspectStyle() bool Utils::isBlurBehindWindowSupported() { - static const bool result = []() -> bool { + static const auto result = []() -> bool { if (FramelessConfig::instance()->isSet(Option::ForceNonNativeBackgroundBlur)) { return false; } @@ -2787,4 +2844,85 @@ void Utils::setDarkModeAllowedForApp(const bool allow) } } +void Utils::bringWindowToFront(const WId windowId) +{ + Q_ASSERT(windowId); + if (!windowId) { + return; + } + const auto hwnd = reinterpret_cast(windowId); + const HWND oldForegroundWindow = GetForegroundWindow(); + if (!oldForegroundWindow) { + WARNING << getSystemErrorMessage(kGetForegroundWindow); + return; + } + const std::optional activeMonitor = getMonitorForWindow(oldForegroundWindow); + if (!activeMonitor.has_value()) { + WARNING << "Failed to retrieve the window's monitor."; + return; + } + // We need to show the window first, otherwise we won't be able to bring it to front. + if (IsWindowVisible(hwnd) == FALSE) { + 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.value()); + // 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. + static constexpr const UINT kTimeout = 1000; + if (SendMessageTimeoutW(oldForegroundWindow, WM_NULL, 0, 0, + SMTO_BLOCK | SMTO_ABORTIFHUNG | SMTO_NOTIMEOUTIFNOTHUNG, kTimeout, nullptr) == 0) { + if (GetLastError() == ERROR_TIMEOUT) { + WARNING << "The foreground window hangs, can't activate current window."; + } else { + WARNING << getSystemErrorMessage(kSendMessageTimeoutW); + } + return; + } + const DWORD windowThreadProcessId = GetWindowThreadProcessId(oldForegroundWindow, nullptr); + const 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. + if (AttachThreadInput(windowThreadProcessId, currentThreadId, TRUE) == FALSE) { + WARNING << getSystemErrorMessage(kAttachThreadInput); + return; + } + // And also don't forget to disconnect from it. + // Ideally we would want to use qScopeGuard to do this, but sadly it was introduced + // in Qt 5.12 and we still want to support some old Qt versions. + const struct __Cleanup { + const DWORD idAttach = 0; + const DWORD idAttachTo = 0; + __Cleanup(const DWORD attach, const DWORD attachTo) + : idAttach(attach), idAttachTo(attachTo) {} + ~__Cleanup() { + if (AttachThreadInput(idAttach, idAttachTo, FALSE) == FALSE) { + WARNING << getSystemErrorMessage(kAttachThreadInput); + } + } + } __cleanup(windowThreadProcessId, currentThreadId); + Q_UNUSED(__cleanup); + // Make our window be the first one in the Z order. + if (BringWindowToTop(hwnd) == FALSE) { + WARNING << getSystemErrorMessage(kBringWindowToTop); + return; + } + // Activate the window too. This will force us to the virtual desktop this + // window is on, if it's on another virtual desktop. + if (SetActiveWindow(hwnd) == nullptr) { + WARNING << getSystemErrorMessage(kSetActiveWindow); + return; + } + // Throw us on the active monitor. + moveWindowToMonitor(hwnd, activeMonitor.value()); +} + FRAMELESSHELPER_END_NAMESPACE diff --git a/src/quick/framelessquickhelper.cpp b/src/quick/framelessquickhelper.cpp index aff4390f..13d98012 100644 --- a/src/quick/framelessquickhelper.cpp +++ b/src/quick/framelessquickhelper.cpp @@ -391,18 +391,26 @@ void FramelessQuickHelperPrivate::bringWindowToFront() if (!window) { return; } - if (window->visibility() == QQuickWindow::Hidden) { - window->show(); - } - if (window->visibility() == QQuickWindow::Minimized) { + const auto bringWindowToFront_impl = [window]() -> void { + if (window->visibility() == QQuickWindow::Hidden) { + window->show(); + } + if (window->visibility() == QQuickWindow::Minimized) { #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) - window->setWindowStates(window->windowStates() & ~Qt::WindowMinimized); + window->setWindowStates(window->windowStates() & ~Qt::WindowMinimized); #else - window->showNormal(); + window->showNormal(); +#endif + } + window->raise(); + window->requestActivate(); + }; +#ifdef Q_OS_WINDOWS + Q_UNUSED(bringWindowToFront_impl); + Utils::bringWindowToFront(window->winId()); +#else + bringWindowToFront_impl(); #endif - } - window->raise(); - window->requestActivate(); } bool FramelessQuickHelperPrivate::isWindowFixedSize() const diff --git a/src/widgets/framelesswidgetshelper.cpp b/src/widgets/framelesswidgetshelper.cpp index 2107dad6..1568f177 100644 --- a/src/widgets/framelesswidgetshelper.cpp +++ b/src/widgets/framelesswidgetshelper.cpp @@ -780,14 +780,22 @@ void FramelessWidgetsHelperPrivate::bringWindowToFront() if (!m_window) { return; } - if (m_window->isHidden()) { - m_window->show(); - } - if (m_window->isMinimized()) { - m_window->setWindowState(m_window->windowState() & ~Qt::WindowMinimized); - } - m_window->raise(); - m_window->activateWindow(); + const auto bringWindowToFront_impl = [this]() -> void { + if (m_window->isHidden()) { + m_window->show(); + } + if (m_window->isMinimized()) { + m_window->setWindowState(m_window->windowState() & ~Qt::WindowMinimized); + } + m_window->raise(); + m_window->activateWindow(); + }; +#ifdef Q_OS_WINDOWS + Q_UNUSED(bringWindowToFront_impl); + Utils::bringWindowToFront(m_window->winId()); +#else + bringWindowToFront_impl(); +#endif } void FramelessWidgetsHelperPrivate::showSystemMenu(const QPoint &pos)