Windows QPA: Rework how we activate the window

The new implementation has the following advantages compared to
the old one:

0. Qt's default activation behavior is not changed, this patch only
   improves how we forcely activate the window.
1. Account for the special case that the foreground window maybe hangs.
2. Truly bring the window to front if the window is hidden or minimized.
3. Use early return to save many indentation.

Change-Id: If51ad943fbc9771ebdddfa0e49732b12566ca2b6
Reviewed-by: Oliver Wolff <oliver.wolff@qt.io>
This commit is contained in:
Yuhang Zhao 2022-10-21 14:06:22 +08:00
parent 13a8414696
commit c3455ded49

View File

@ -2785,40 +2785,75 @@ void QWindowsWindow::setMask(const QRegion &region)
void QWindowsWindow::requestActivateWindow()
{
qCDebug(lcQpaWindow) << __FUNCTION__ << this << window();
// 'Active' state handling is based in focus since it needs to work for
// child windows as well.
if (m_data.hwnd) {
const DWORD currentThread = GetCurrentThreadId();
bool attached = false;
DWORD foregroundThread = 0;
// QTBUG-14062, QTBUG-37435: Windows normally only flashes the taskbar entry
// when activating windows of inactive applications. Attach to the input of the
// currently active window while setting the foreground window to always activate
// the window when desired.
if (!m_data.hwnd)
return;
const auto activationBehavior = QWindowsIntegration::instance()->windowActivationBehavior();
if (QGuiApplication::applicationState() != Qt::ApplicationActive
&& activationBehavior == QWindowsApplication::AlwaysActivateWindow) {
if (const HWND foregroundWindow = GetForegroundWindow()) {
foregroundThread = GetWindowThreadProcessId(foregroundWindow, nullptr);
if (foregroundThread && foregroundThread != currentThread)
attached = AttachThreadInput(foregroundThread, currentThread, TRUE) == TRUE;
if (attached) {
if (!window()->flags().testFlag(Qt::WindowStaysOnBottomHint)
&& !window()->flags().testFlag(Qt::WindowStaysOnTopHint)
&& window()->type() != Qt::ToolTip) {
const UINT swpFlags = SWP_NOMOVE | SWP_NOSIZE | SWP_NOOWNERZORDER;
SetWindowPos(m_data.hwnd, HWND_TOPMOST, 0, 0, 0, 0, swpFlags);
SetWindowPos(m_data.hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, swpFlags);
}
}
}
}
if (QGuiApplication::applicationState() == Qt::ApplicationActive
|| activationBehavior != QWindowsApplication::AlwaysActivateWindow) {
SetForegroundWindow(m_data.hwnd);
SetFocus(m_data.hwnd);
if (attached)
AttachThreadInput(foregroundThread, currentThread, FALSE);
return;
}
// Force activate this window. The following code will bring the window to the
// foreground and activate it. If the window is hidden, it will show up. If
// the window is minimized, it will restore to the previous position.
// But first we need some sanity checks.
if (m_data.flags & Qt::WindowStaysOnBottomHint) {
qCWarning(lcQpaWindow) <<
"Windows with Qt::WindowStaysOnBottomHint can't be brought to the foreground.";
return;
}
if (m_data.flags & Qt::WindowStaysOnTopHint) {
qCWarning(lcQpaWindow) <<
"Windows with Qt::WindowStaysOnTopHint will always be on the foreground.";
return;
}
if (window()->type() == Qt::ToolTip) {
qCWarning(lcQpaWindow) << "ToolTip windows should not be activated.";
return;
}
// We need to show the window first, otherwise we won't be able to bring it to front.
if (!IsWindowVisible(m_data.hwnd))
ShowWindow(m_data.hwnd, SW_SHOW);
if (IsIconic(m_data.hwnd)) {
ShowWindow(m_data.hwnd, SW_RESTORE);
// 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.
const HWND oldForegroundWindow = GetForegroundWindow();
if (!oldForegroundWindow) // It may be NULL, according to MS docs.
return;
// 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) == 0) {
qCWarning(lcQpaWindow) << "The foreground window hangs, can't activate current window.";
return;
}
const DWORD windowThreadProcessId = GetWindowThreadProcessId(oldForegroundWindow, nullptr);
const DWORD currentThreadId = GetCurrentThreadId();
AttachThreadInput(windowThreadProcessId, currentThreadId, TRUE);
const auto cleanup = qScopeGuard([windowThreadProcessId, currentThreadId](){
AttachThreadInput(windowThreadProcessId, currentThreadId, FALSE);
});
BringWindowToTop(m_data.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(m_data.hwnd);
}
bool QWindowsWindow::setKeyboardGrabEnabled(bool grab)