XCB: fix that modal dialogs can go behind other process windows

Task-number: QTBUG-35302
Change-Id: I1ad7a66e530710d5338a15057254360dae676451
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@digia.com>
Reviewed-by: Laszlo Agocs <laszlo.agocs@digia.com>
This commit is contained in:
Jorgen Lind 2014-04-03 13:11:59 +02:00 committed by The Qt Project
parent 3536823c7f
commit 50b8506eac
5 changed files with 206 additions and 45 deletions

View File

@ -49,11 +49,11 @@
#include "qwindowsmime.h"
#include "qwindowsinputcontext.h"
#include "qwindowstabletsupport.h"
#include <private/qguiapplication_p.h>
#ifndef QT_NO_ACCESSIBILITY
# include "accessible/qwindowsaccessibility.h"
#endif
#if !defined(Q_OS_WINCE) && !defined(QT_NO_SESSIONMANAGER)
# include <private/qguiapplication_p.h>
# include <private/qsessionmanager_p.h>
# include "qwindowssessionmanager.h"
#endif
@ -1025,6 +1025,12 @@ void QWindowsContext::handleFocusEvent(QtWindows::WindowsEventType et,
{
QWindow *nextActiveWindow = 0;
if (et == QtWindows::FocusInEvent) {
QWindow *topWindow = QWindowsWindow::topLevelOf(platformWindow->window());
QWindow *modalWindow = 0;
if (QGuiApplicationPrivate::instance()->isWindowBlocked(topWindow, &modalWindow) && topWindow != modalWindow) {
modalWindow->requestActivate();
return;
}
nextActiveWindow = platformWindow->window();
} else {
// Focus out: Is the next window known and different

View File

@ -1016,17 +1016,17 @@ QWindow *QWindowsWindow::topLevelOf(QWindow *w)
while (QWindow *parent = w->parent())
w = parent;
const QWindowsWindow *ww = static_cast<const QWindowsWindow *>(w->handle());
// In case the topmost parent is embedded, find next ancestor using native methods
if (ww->isEmbedded(0)) {
HWND parentHWND = GetAncestor(ww->handle(), GA_PARENT);
const HWND desktopHwnd = GetDesktopWindow();
const QWindowsContext *ctx = QWindowsContext::instance();
while (parentHWND && parentHWND != desktopHwnd) {
if (QWindowsWindow *ancestor = ctx->findPlatformWindow(parentHWND))
return topLevelOf(ancestor->window());
parentHWND = GetAncestor(parentHWND, GA_PARENT);
if (const QPlatformWindow *handle = w->handle()) {
const QWindowsWindow *ww = static_cast<const QWindowsWindow *>(handle);
if (ww->isEmbedded(0)) {
HWND parentHWND = GetAncestor(ww->handle(), GA_PARENT);
const HWND desktopHwnd = GetDesktopWindow();
const QWindowsContext *ctx = QWindowsContext::instance();
while (parentHWND && parentHWND != desktopHwnd) {
if (QWindowsWindow *ancestor = ctx->findPlatformWindow(parentHWND))
return topLevelOf(ancestor->window());
parentHWND = GetAncestor(parentHWND, GA_PARENT);
}
}
}
return w;

View File

@ -485,7 +485,7 @@ QXcbWindow::~QXcbWindow()
void QXcbWindow::destroy()
{
if (connection()->focusWindow() == this)
connection()->setFocusWindow(0);
doFocusOut();
if (m_syncCounter && m_usingSyncProtocol)
Q_XCB_CALL(xcb_sync_destroy_counter(xcb_connection(), m_syncCounter));
@ -671,6 +671,9 @@ void QXcbWindow::show()
Q_XCB_CALL(xcb_map_window(xcb_connection(), m_window));
if (QGuiApplication::modalWindow() == window())
requestActivateWindow();
m_screen->windowShown(this);
connection()->sync();
@ -694,6 +697,68 @@ void QXcbWindow::hide()
m_mapped = false;
}
static QWindow *tlWindow(QWindow *window)
{
if (window && window->parent())
return tlWindow(window->parent());
return window;
}
bool QXcbWindow::relayFocusToModalWindow() const
{
QWindow *w = tlWindow(static_cast<QWindowPrivate *>(QObjectPrivate::get(window()))->eventReceiver());
QWindow *modal_window = 0;
if (QGuiApplicationPrivate::instance()->isWindowBlocked(w,&modal_window) && modal_window != w) {
modal_window->requestActivate();
connection()->flush();
return true;
}
return false;
}
void QXcbWindow::doFocusIn()
{
if (relayFocusToModalWindow())
return;
QWindow *w = static_cast<QWindowPrivate *>(QObjectPrivate::get(window()))->eventReceiver();
connection()->setFocusWindow(static_cast<QXcbWindow *>(w->handle()));
QWindowSystemInterface::handleWindowActivated(w, Qt::ActiveWindowFocusReason);
}
static bool focusInPeeker(QXcbConnection *connection, xcb_generic_event_t *event)
{
if (!event) {
// FocusIn event is not in the queue, proceed with FocusOut normally.
QWindowSystemInterface::handleWindowActivated(0, Qt::ActiveWindowFocusReason);
return true;
}
uint response_type = event->response_type & ~0x80;
if (response_type == XCB_FOCUS_IN)
return true;
/* We are also interested in XEMBED_FOCUS_IN events */
if (response_type == XCB_CLIENT_MESSAGE) {
xcb_client_message_event_t *cme = (xcb_client_message_event_t *)event;
if (cme->type == connection->atom(QXcbAtom::_XEMBED)
&& cme->data.data32[1] == XEMBED_FOCUS_IN)
return true;
}
return false;
}
void QXcbWindow::doFocusOut()
{
if (relayFocusToModalWindow())
return;
connection()->setFocusWindow(0);
// Do not set the active window to 0 if there is a FocusIn coming.
// There is however no equivalent for XPutBackEvent so register a
// callback for QXcbConnection instead.
connection()->addPeekFunc(focusInPeeker);
}
struct QtMotifWmHints {
quint32 flags, functions, decorations;
qint32 input_mode;
@ -1514,6 +1579,8 @@ void QXcbWindow::handleClientMessageEvent(const xcb_client_message_event_t *even
QWindowSystemInterface::handleCloseEvent(window());
} else if (event->data.data32[0] == atom(QXcbAtom::WM_TAKE_FOCUS)) {
connection()->setTime(event->data.data32[1]);
relayFocusToModalWindow();
return;
} else if (event->data.data32[0] == atom(QXcbAtom::_NET_WM_PING)) {
if (event->window == m_screen->root())
return;
@ -1549,8 +1616,7 @@ void QXcbWindow::handleClientMessageEvent(const xcb_client_message_event_t *even
} else if (event->type == atom(QXcbAtom::_XEMBED)) {
handleXEmbedMessage(event);
} else if (event->type == atom(QXcbAtom::_NET_ACTIVE_WINDOW)) {
connection()->setFocusWindow(this);
QWindowSystemInterface::handleWindowActivated(window(), Qt::ActiveWindowFocusReason);
doFocusIn();
} else if (event->type == atom(QXcbAtom::MANAGER)
|| event->type == atom(QXcbAtom::_NET_WM_STATE)
|| event->type == atom(QXcbAtom::WM_CHANGE_STATE)) {
@ -1865,41 +1931,13 @@ void QXcbWindow::handlePropertyNotifyEvent(const xcb_property_notify_event_t *ev
void QXcbWindow::handleFocusInEvent(const xcb_focus_in_event_t *)
{
QWindow *w = window();
w = static_cast<QWindowPrivate *>(QObjectPrivate::get(w))->eventReceiver();
connection()->setFocusWindow(static_cast<QXcbWindow *>(w->handle()));
QWindowSystemInterface::handleWindowActivated(w, Qt::ActiveWindowFocusReason);
doFocusIn();
}
static bool focusInPeeker(QXcbConnection *connection, xcb_generic_event_t *event)
{
if (!event) {
// FocusIn event is not in the queue, proceed with FocusOut normally.
QWindowSystemInterface::handleWindowActivated(0, Qt::ActiveWindowFocusReason);
return true;
}
uint response_type = event->response_type & ~0x80;
if (response_type == XCB_FOCUS_IN)
return true;
/* We are also interested in XEMBED_FOCUS_IN events */
if (response_type == XCB_CLIENT_MESSAGE) {
xcb_client_message_event_t *cme = (xcb_client_message_event_t *)event;
if (cme->type == connection->atom(QXcbAtom::_XEMBED)
&& cme->data.data32[1] == XEMBED_FOCUS_IN)
return true;
}
return false;
}
void QXcbWindow::handleFocusOutEvent(const xcb_focus_out_event_t *)
{
connection()->setFocusWindow(0);
// Do not set the active window to 0 if there is a FocusIn coming.
// There is however no equivalent for XPutBackEvent so register a
// callback for QXcbConnection instead.
connection()->addPeekFunc(focusInPeeker);
doFocusOut();
}
void QXcbWindow::updateSyncRequestCounter()

View File

@ -176,6 +176,10 @@ private:
void show();
void hide();
bool relayFocusToModalWindow() const;
void doFocusIn();
void doFocusOut();
QXcbScreen *m_screen;
xcb_window_t m_window;

View File

@ -88,6 +88,10 @@ private slots:
void visibility();
void mask();
void initialSize();
void modalDialog();
void modalDialogClosingOneOfTwoModal();
void modalWithChildWindow();
void modalWindowModallity();
void initTestCase()
{
@ -1316,6 +1320,115 @@ void tst_QWindow::initialSize()
}
}
void tst_QWindow::modalDialog()
{
QWindow normalWindow;
normalWindow.resize(400, 400);
normalWindow.show();
QVERIFY(QTest::qWaitForWindowExposed(&normalWindow));
QWindow dialog;
dialog.resize(200,200);
dialog.setModality(Qt::ApplicationModal);
dialog.setFlags(Qt::Dialog);
dialog.show();
QVERIFY(QTest::qWaitForWindowExposed(&dialog));
normalWindow.requestActivate();
QGuiApplication::sync();
QGuiApplication::processEvents();
QTRY_COMPARE(QGuiApplication::focusWindow(), &dialog);
}
void tst_QWindow::modalDialogClosingOneOfTwoModal()
{
QWindow normalWindow;
normalWindow.resize(400, 400);
normalWindow.show();
QVERIFY(QTest::qWaitForWindowExposed(&normalWindow));
QWindow first_dialog;
first_dialog.resize(200,200);
first_dialog.setModality(Qt::ApplicationModal);
first_dialog.setFlags(Qt::Dialog);
first_dialog.show();
QVERIFY(QTest::qWaitForWindowExposed(&first_dialog));
{
QWindow second_dialog;
second_dialog.resize(200,200);
second_dialog.setModality(Qt::ApplicationModal);
second_dialog.setFlags(Qt::Dialog);
second_dialog.show();
QVERIFY(QTest::qWaitForWindowExposed(&second_dialog));
QTRY_COMPARE(QGuiApplication::focusWindow(), &second_dialog);
second_dialog.close();
}
QGuiApplication::sync();
QGuiApplication::processEvents();
QTRY_COMPARE(QGuiApplication::focusWindow(), &first_dialog);
}
void tst_QWindow::modalWithChildWindow()
{
QWindow normalWindow;
normalWindow.resize(400, 400);
normalWindow.show();
QVERIFY(QTest::qWaitForWindowExposed(&normalWindow));
QWindow tlw_dialog;
tlw_dialog.resize(400,200);
tlw_dialog.setModality(Qt::ApplicationModal);
tlw_dialog.setFlags(Qt::Dialog);
tlw_dialog.create();
QWindow sub_window(&tlw_dialog);
sub_window.resize(200,300);
sub_window.show();
tlw_dialog.show();
QVERIFY(QTest::qWaitForWindowExposed(&tlw_dialog));
QVERIFY(QTest::qWaitForWindowExposed(&sub_window));
QTRY_COMPARE(QGuiApplication::focusWindow(), &tlw_dialog);
sub_window.requestActivate();
QGuiApplication::sync();
QGuiApplication::processEvents();
QTRY_COMPARE(QGuiApplication::focusWindow(), &sub_window);
}
void tst_QWindow::modalWindowModallity()
{
QWindow normal_window;
normal_window.resize(400, 400);
normal_window.show();
QVERIFY(QTest::qWaitForWindowExposed(&normal_window));
QWindow parent_to_modal;
parent_to_modal.resize(400, 400);
parent_to_modal.show();
QVERIFY(QTest::qWaitForWindowExposed(&parent_to_modal));
QTRY_COMPARE(QGuiApplication::focusWindow(), &parent_to_modal);
QWindow modal_dialog;
modal_dialog.resize(400,200);
modal_dialog.setModality(Qt::WindowModal);
modal_dialog.setFlags(Qt::Dialog);
modal_dialog.setTransientParent(&parent_to_modal);
modal_dialog.show();
QVERIFY(QTest::qWaitForWindowExposed(&modal_dialog));
QTRY_COMPARE(QGuiApplication::focusWindow(), &modal_dialog);
normal_window.requestActivate();
QTRY_COMPARE(QGuiApplication::focusWindow(), &normal_window);
}
#include <tst_qwindow.moc>
QTEST_MAIN(tst_QWindow)