diff options
Diffstat (limited to 'tests/auto/gui/kernel/qwindow')
-rw-r--r-- | tests/auto/gui/kernel/qwindow/BLACKLIST | 14 | ||||
-rw-r--r-- | tests/auto/gui/kernel/qwindow/CMakeLists.txt | 35 | ||||
-rw-r--r-- | tests/auto/gui/kernel/qwindow/tst_foreignwindow.cpp | 196 | ||||
-rw-r--r-- | tests/auto/gui/kernel/qwindow/tst_qwindow.cpp | 659 |
4 files changed, 794 insertions, 110 deletions
diff --git a/tests/auto/gui/kernel/qwindow/BLACKLIST b/tests/auto/gui/kernel/qwindow/BLACKLIST index ee1d6563cb..69df8883c8 100644 --- a/tests/auto/gui/kernel/qwindow/BLACKLIST +++ b/tests/auto/gui/kernel/qwindow/BLACKLIST @@ -1,9 +1,4 @@ [positioning] -opensuse-leap -[positioning:default] -linux -macos ci -[positioning:fake] macos ci [modalWithChildWindow] # QTBUG-69160 @@ -11,13 +6,11 @@ android [modalWindowEnterEventOnHide_QTBUG35109] osx android -windows-10 +windows [spuriousMouseMove] # QTBUG-69162 windows-10 android -[testInputEvents] -rhel-7.4 [modalWindowPosition] # QTBUG-69161 android @@ -28,3 +21,8 @@ android android [modalWindowModallity] android +[enterLeaveOnWindowShowHide] +windows-10 +windows-11 +android +rhel diff --git a/tests/auto/gui/kernel/qwindow/CMakeLists.txt b/tests/auto/gui/kernel/qwindow/CMakeLists.txt index 23671cc385..5824989ac3 100644 --- a/tests/auto/gui/kernel/qwindow/CMakeLists.txt +++ b/tests/auto/gui/kernel/qwindow/CMakeLists.txt @@ -1,22 +1,51 @@ -# Generated from qwindow.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tst_qwindow Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qwindow LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qwindow SOURCES tst_qwindow.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::CorePrivate Qt::Gui Qt::GuiPrivate ) +if(APPLE OR WIN32 OR QT_FEATURE_xcb) + qt_internal_add_test(tst_foreignwindow + LOWDPI + SOURCES + tst_foreignwindow.cpp + LIBRARIES + Qt::CorePrivate + Qt::Gui + Qt::GuiPrivate + ) + + if(APPLE) + enable_language(OBJCXX) + set_source_files_properties(tst_foreignwindow.cpp PROPERTIES LANGUAGE OBJCXX) + set_property(TARGET tst_foreignwindow PROPERTY PROPERTY MACOSX_BUNDLE TRUE) + endif() + + if(QT_FEATURE_xcb) + target_link_libraries(tst_foreignwindow PRIVATE XCB::XCB) + endif() +endif() + ## Scopes: ##################################################################### qt_internal_extend_target(tst_qwindow CONDITION QT_FEATURE_dynamicgl AND WIN32 - PUBLIC_LIBRARIES + LIBRARIES user32 ) diff --git a/tests/auto/gui/kernel/qwindow/tst_foreignwindow.cpp b/tests/auto/gui/kernel/qwindow/tst_foreignwindow.cpp new file mode 100644 index 0000000000..fdb1b333ef --- /dev/null +++ b/tests/auto/gui/kernel/qwindow/tst_foreignwindow.cpp @@ -0,0 +1,196 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QTest> + +#include <QtCore/qloggingcategory.h> +#include <private/qguiapplication_p.h> +#include <qpa/qplatformintegration.h> + +#include "../../../../shared/nativewindow.h" + +class tst_ForeignWindow: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase() + { + auto *platformIntegration = QGuiApplicationPrivate::platformIntegration(); + if (!platformIntegration->hasCapability(QPlatformIntegration::ForeignWindows)) + QSKIP("This platform does not support foreign windows"); + } + + void fromWinId(); + void initialState(); + + void embedForeignWindow(); + void embedInForeignWindow(); + + void destroyExplicitly(); + void destroyWhenParentIsDestroyed(); +}; + +void tst_ForeignWindow::fromWinId() +{ + NativeWindow nativeWindow; + QVERIFY(nativeWindow); + + std::unique_ptr<QWindow> foreignWindow(QWindow::fromWinId(nativeWindow)); + QVERIFY(foreignWindow); + QVERIFY(foreignWindow->flags().testFlag(Qt::ForeignWindow)); + QVERIFY(foreignWindow->handle()); + + // fromWinId does not take (exclusive) ownership of the native window, + // so deleting the foreign window should not be a problem/cause crashes. + foreignWindow.reset(); +} + +void tst_ForeignWindow::initialState() +{ + NativeWindow nativeWindow; + QVERIFY(nativeWindow); + + // A foreign window can be used to embed a Qt UI in a foreign window hierarchy, + // in which case the foreign window merely acts as a parent and should not be + // modified, or to embed a foreign window in a Qt UI, in which case the foreign + // window must to be able to re-parent, move, resize, show, etc, so that the + // containing Qt UI can treat it as any other window. + + // At the point of creation though, we don't know what the foreign window + // will be used for, so the platform should not assume it can modify the + // window. Any properties set on the native window should persist past + // creation of the foreign window. + + const QRect initialGeometry(123, 456, 321, 654); + nativeWindow.setGeometry(initialGeometry); + QTRY_COMPARE(nativeWindow.geometry(), initialGeometry); + + std::unique_ptr<QWindow> foreignWindow(QWindow::fromWinId(nativeWindow)); + QCOMPARE(nativeWindow.geometry(), initialGeometry); + + // For extra bonus points, the foreign window should actually + // reflect the state of the native window. + QCOMPARE(foreignWindow->geometry(), initialGeometry); +} + +void tst_ForeignWindow::embedForeignWindow() +{ + // A foreign window embedded into a Qt UI requires that the rest of Qt + // is to be able to treat the foreign child window as any other window + // that it can show, hide, stack, and move around. + + QWindow parentWindow; + + NativeWindow nativeWindow; + QVERIFY(nativeWindow); + + // As a prerequisite to that, we must be able to reparent the foreign window + std::unique_ptr<QWindow> foreignWindow(QWindow::fromWinId(nativeWindow)); + foreignWindow->setParent(&parentWindow); + QTRY_COMPARE(nativeWindow.parentWinId(), parentWindow.winId()); + + // FIXME: This test is flakey on Linux. Figure out why +#if !defined(Q_OS_LINUX) + foreignWindow->setParent(nullptr); + QTRY_VERIFY(nativeWindow.parentWinId() != parentWindow.winId()); +#endif +} + +void tst_ForeignWindow::embedInForeignWindow() +{ + // When a foreign window is used as a container to embed a Qt UI + // in a foreign window hierarchy, the foreign window merely acts + // as a parent, and should not be modified. + + { + // At a minimum, we must be able to reparent into the window + NativeWindow nativeWindow; + QVERIFY(nativeWindow); + + std::unique_ptr<QWindow> foreignWindow(QWindow::fromWinId(nativeWindow)); + + QWindow embeddedWindow; + embeddedWindow.setParent(foreignWindow.get()); + QTRY_VERIFY(nativeWindow.isParentOf(embeddedWindow.winId())); + } + + { + // The foreign window's native window should not be reparent as a + // result of creating the foreign window, adding and removing children, + // or destroying the foreign window. + + NativeWindow topLevelNativeWindow; + NativeWindow childNativeWindow; + childNativeWindow.setParent(topLevelNativeWindow); + QVERIFY(topLevelNativeWindow.isParentOf(childNativeWindow)); + + std::unique_ptr<QWindow> foreignWindow(QWindow::fromWinId(childNativeWindow)); + QVERIFY(topLevelNativeWindow.isParentOf(childNativeWindow)); + + QWindow embeddedWindow; + embeddedWindow.setParent(foreignWindow.get()); + QTRY_VERIFY(childNativeWindow.isParentOf(embeddedWindow.winId())); + QVERIFY(topLevelNativeWindow.isParentOf(childNativeWindow)); + + embeddedWindow.setParent(nullptr); + QVERIFY(topLevelNativeWindow.isParentOf(childNativeWindow)); + + foreignWindow.reset(); + QVERIFY(topLevelNativeWindow.isParentOf(childNativeWindow)); + } +} + +void tst_ForeignWindow::destroyExplicitly() +{ + NativeWindow nativeWindow; + QVERIFY(nativeWindow); + + std::unique_ptr<QWindow> foreignWindow(QWindow::fromWinId(nativeWindow)); + QVERIFY(foreignWindow->handle()); + + // Explicitly destroying a foreign window is a no-op, as + // the documentation claims that it "releases the native + // platform resources associated with this window.", which + // is not technically true for foreign windows. + auto *windowHandleBeforeDestroy = foreignWindow->handle(); + foreignWindow->destroy(); + QCOMPARE(foreignWindow->handle(), windowHandleBeforeDestroy); +} + +void tst_ForeignWindow::destroyWhenParentIsDestroyed() +{ + QWindow parentWindow; + + NativeWindow nativeWindow; + QVERIFY(nativeWindow); + + std::unique_ptr<QWindow> foreignWindow(QWindow::fromWinId(nativeWindow)); + foreignWindow->setParent(&parentWindow); + QTRY_COMPARE(nativeWindow.parentWinId(), parentWindow.winId()); + + // Reparenting into a window will result in creating it + QVERIFY(parentWindow.handle()); + + parentWindow.show(); + QVERIFY(QTest::qWaitForWindowExposed(&parentWindow)); + + // Destroying the parent window of the foreign window results + // in destroying the foreign window as well, as the foreign + // window no longer has a parent it can be embedded in. + QVERIFY(foreignWindow->handle()); + parentWindow.destroy(); + QVERIFY(!foreignWindow->handle()); + + // But the foreign window can be recreated again, and will + // continue to be a native child of the parent window. + foreignWindow->create(); + QVERIFY(foreignWindow->handle()); + QTRY_COMPARE(nativeWindow.parentWinId(), parentWindow.winId()); + + parentWindow.show(); + QVERIFY(QTest::qWaitForWindowExposed(&parentWindow)); +} + +#include <tst_foreignwindow.moc> +QTEST_MAIN(tst_ForeignWindow) diff --git a/tests/auto/gui/kernel/qwindow/tst_qwindow.cpp b/tests/auto/gui/kernel/qwindow/tst_qwindow.cpp index 46d6f5bf44..227fd77e1a 100644 --- a/tests/auto/gui/kernel/qwindow/tst_qwindow.cpp +++ b/tests/auto/gui/kernel/qwindow/tst_qwindow.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <qrasterwindow.h> #include <qpa/qwindowsysteminterface.h> @@ -32,6 +7,7 @@ #include <qpa/qplatformwindow.h> #include <private/qguiapplication_p.h> #include <private/qhighdpiscaling_p.h> +#include <private/qwindow_p.h> #include <QtGui/QPainter> #include <QTest> @@ -47,6 +23,12 @@ Q_LOGGING_CATEGORY(lcTests, "qt.gui.tests") +static bool isPlatformEglFS() +{ + static const bool isEglFS = !QGuiApplication::platformName().compare(QLatin1String("eglfs"), Qt::CaseInsensitive); + return isEglFS; +} + class tst_QWindow: public QObject { Q_OBJECT @@ -55,6 +37,7 @@ private slots: void create(); void setParent(); void setVisible(); + void setVisibleThenCreate(); void setVisibleFalseDoesNotCreateWindow(); void eventOrderOnShow(); void paintEvent(); @@ -95,6 +78,7 @@ private slots: void modalWithChildWindow(); void modalWindowModallity(); void modalWindowPosition(); + void modalCloseWhileBlocked(); #ifndef QT_NO_CURSOR void modalWindowEnterEventOnHide_QTBUG35109(); void spuriousMouseMove(); @@ -111,6 +95,15 @@ private slots: void keepPendingUpdateRequests(); void activateDeactivateEvent(); void qobject_castOnDestruction(); + void touchToMouseTranslationByPopup(); + void stateChangeSignal(); +#ifndef QT_NO_CURSOR + void enterLeaveOnWindowShowHide_data(); + void enterLeaveOnWindowShowHide(); +#endif + void windowExposedAfterReparent(); + void childEvents(); + void parentEvents(); private: QPoint m_availableTopLeft; @@ -121,8 +114,17 @@ private: QInputDevice::Capability::Position | QInputDevice::Capability::MouseEmulation); }; +static bool isPlatformWayland() +{ + return QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive); +} + void tst_QWindow::initTestCase() { +#ifdef Q_OS_ANDROID + if (QNativeInterface::QAndroidApplication::sdkVersion() == 33) + QSKIP("Is flaky on Android 13 / RHEL 8.6 and 8.8 (QTQAINFRA-5606)"); +#endif // Size of reference window, 200 for < 2000, scale up for larger screens // to avoid Windows warnings about minimum size for decorated windows. int width = 200; @@ -132,6 +134,10 @@ void tst_QWindow::initTestCase() if (screenWidth > 2000) width = 100 * ((screenWidth + 500) / 1000); m_testWindowSize = QSize(width, width); + + // Make sure test runs consistently on all compositors by force-disabling window decorations + if (isPlatformWayland()) + qputenv("QT_WAYLAND_DISABLE_WINDOWDECORATION", "1"); } void tst_QWindow::cleanup() @@ -244,6 +250,40 @@ void tst_QWindow::setVisible() QVERIFY(QTest::qWaitForWindowExposed(&i)); } +class SurfaceCreatedWindow : public QWindow +{ + Q_OBJECT +public: + using QWindow::QWindow; + + bool eventFilter(QObject *o, QEvent *e) override + { + if (e->type() == QEvent::PlatformSurface) { + auto type = static_cast<QPlatformSurfaceEvent*>(e)->surfaceEventType(); + if (type == QPlatformSurfaceEvent::SurfaceCreated) + ++surfaceCreatedEvents; + } + return QWindow::eventFilter(o, e); + } + + int surfaceCreatedEvents = 0; +}; + +void tst_QWindow::setVisibleThenCreate() +{ + QWindow parent; + parent.setObjectName("Parent"); + SurfaceCreatedWindow child(&parent); + child.installEventFilter(&child); + child.setObjectName("Child"); + child.setVisible(true); + child.create(); + QCOMPARE(child.surfaceCreatedEvents, 1); + parent.setVisible(true); + QCOMPARE(child.surfaceCreatedEvents, 1); + QVERIFY(QTest::qWaitForWindowExposed(&child)); +} + void tst_QWindow::setVisibleFalseDoesNotCreateWindow() { QWindow w; @@ -311,13 +351,6 @@ public: m_received[event->type()]++; m_order << event->type(); switch (event->type()) { - case QEvent::Expose: -QT_WARNING_PUSH -QT_WARNING_DISABLE_DEPRECATED - m_exposeRegion = static_cast<QExposeEvent *>(event)->region(); -QT_WARNING_POP - break; - case QEvent::PlatformSurface: m_surfaceventType = static_cast<QPlatformSurfaceEvent *>(event)->surfaceEventType(); break; @@ -347,11 +380,6 @@ QT_WARNING_POP return m_order.indexOf(type); } - QRegion exposeRegion() const - { - return m_exposeRegion; - } - QPlatformSurfaceEvent::SurfaceEventType surfaceEventType() const { return m_surfaceventType; @@ -363,7 +391,6 @@ QT_WARNING_POP private: QHash<QEvent::Type, int> m_received; QList<QEvent::Type> m_order; - QRegion m_exposeRegion; QPlatformSurfaceEvent::SurfaceEventType m_surfaceventType; }; @@ -455,11 +482,16 @@ void tst_QWindow::resizeEventAfterResize() // Make sure we get a resizeEvent after calling resize window.resize(m_testWindowSize); + if (isPlatformEglFS()) + QEXPECT_FAIL("", "eglfs windows are fullscreen by default.", Continue); + QTRY_COMPARE(window.received(QEvent::Resize), 2); } void tst_QWindow::exposeEventOnShrink_QTBUG54040() { + if (isPlatformEglFS()) + QSKIP("", "eglfs windows are fullscreen by default.", Continue); Window window; window.setGeometry(QRect(m_availableTopLeft + QPoint(80, 80), m_testWindowSize)); window.setTitle(QTest::currentTestFunction()); @@ -523,13 +555,11 @@ static QString msgRectMismatch(const QRect &r1, const QRect &r2) return result; } -static bool isPlatformWayland() -{ - return !QGuiApplication::platformName().compare(QLatin1String("wayland"), Qt::CaseInsensitive); -} - void tst_QWindow::positioning() { +#ifdef Q_OS_ANDROID + QSKIP("Fails on Android. QTBUG-105201"); +#endif if (!QGuiApplicationPrivate::platformIntegration()->hasCapability( QPlatformIntegration::NonFullScreenWindows)) { QSKIP("This platform does not support non-fullscreen windows"); @@ -551,9 +581,8 @@ void tst_QWindow::positioning() QCOMPARE(window.geometry(), geometry); // explicitly use non-fullscreen show. show() can be fullscreen on some platforms window.showNormal(); - QCoreApplication::processEvents(); - QVERIFY(QTest::qWaitForWindowExposed(&window)); + QVERIFY(QTest::qWaitForWindowActive(&window)); QMargins originalMargins = window.frameMargins(); @@ -645,9 +674,8 @@ void tst_QWindow::childWindowPositioning_data() void tst_QWindow::childWindowPositioning() { - if (isPlatformWayland()) - QSKIP("Wayland: This is flaky (protocol errors for xdg-shell v6). See QTBUG-67648."); - + if (isPlatformEglFS()) + QSKIP("eglfs does not support child windows."); const QPoint topLeftOrigin(0, 0); ColoredWindow topLevelWindowFirst(Qt::green); @@ -750,7 +778,7 @@ void tst_QWindow::stateChange() // explicitly use non-fullscreen show. show() can be fullscreen on some platforms window.showNormal(); QVERIFY(QTest::qWaitForWindowExposed(&window)); - for (Qt::WindowState state : qAsConst(stateSequence)) { + for (Qt::WindowState state : std::as_const(stateSequence)) { window.setWindowState(state); QCoreApplication::processEvents(); } @@ -824,16 +852,6 @@ void tst_QWindow::isExposed() QTRY_VERIFY(window.received(QEvent::Expose) > 0); QTRY_VERIFY(window.isExposed()); -#ifndef Q_OS_WIN - // This is a top-level window so assuming it is completely exposed, the - // expose region must be (0, 0), (width, height). If this is not the case, - // the platform plugin is sending expose events with a region in an - // incorrect coordinate system. - QRect r = window.exposeRegion().boundingRect(); - r = QRect(window.mapToGlobal(r.topLeft()), r.size()); - QCOMPARE(r, window.geometry()); -#endif - window.hide(); QCoreApplication::processEvents(); @@ -978,6 +996,9 @@ public: if (spinLoopWhenPressed) QCoreApplication::processEvents(); } + if (closeOnTap) + this->close(); + } void mouseReleaseEvent(QMouseEvent *event) override { @@ -1034,7 +1055,7 @@ public: } touchEventType = event->type(); QList<QTouchEvent::TouchPoint> points = event->points(); - for (int i = 0; i < points.count(); ++i) { + for (int i = 0; i < points.size(); ++i) { const auto &point = points.at(i); switch (point.state()) { case QEventPoint::State::Pressed: @@ -1045,6 +1066,8 @@ public: touchPressLocalPos = point.position(); touchPressGlobalPos = point.globalPosition(); } + if (closeOnTap) + this->close(); break; case QEventPoint::State::Released: ++touchReleasedCount; @@ -1101,6 +1124,8 @@ public: const QPointingDevice *mouseDevice = nullptr; const QPointingDevice *touchDevice = nullptr; + + bool closeOnTap = false; }; static void simulateMouseClick(QWindow *target, const QPointF &local, const QPointF &global) @@ -1324,6 +1349,8 @@ void tst_QWindow::mouseToTouchTranslation() QTRY_COMPARE(window.touchPressedCount, 1); QTRY_COMPARE(window.touchReleasedCount, 1); QCOMPARE(window.mouseDevice, window.touchDevice); + if (isPlatformWayland()) + QEXPECT_FAIL("", "Wayland: This fails. See QTBUG-100887.", Abort); QCOMPARE(window.touchDevice->type(), QInputDevice::DeviceType::Mouse); QCOMPARE(window.touchPressLocalPos.toPoint(), localPos); QCOMPARE(window.touchPressGlobalPos.toPoint(), window.mapToGlobal(localPos)); @@ -1481,10 +1508,8 @@ void tst_QWindow::touchCancelWithTouchToMouse() void tst_QWindow::touchInterruptedByPopup() { - if (isPlatformWayland()) - QSKIP("Wayland: This test crashes with xdg-shell unstable v6"); - InputTestWindow window; + window.setObjectName("main"); window.setTitle(QLatin1String(QTest::currentTestFunction())); window.setGeometry(QRect(m_availableTopLeft + QPoint(80, 80), m_testWindowSize)); window.show(); @@ -1505,6 +1530,7 @@ void tst_QWindow::touchInterruptedByPopup() // Launch a popup window InputTestWindow popup; + window.setObjectName("popup"); popup.setFlags(Qt::Popup); popup.setModality(Qt::WindowModal); popup.resize(m_testWindowSize / 2); @@ -1527,9 +1553,6 @@ void tst_QWindow::touchInterruptedByPopup() QWindowSystemInterface::handleTouchEvent(&window, touchDevice, points); QCoreApplication::processEvents(); QTRY_COMPARE(window.touchReleasedCount, 0); - - // Due to temporary fix for QTBUG-37371: the original window should receive a TouchCancel - QTRY_COMPARE(window.touchEventType, QEvent::TouchCancel); } void tst_QWindow::orientation() @@ -1548,7 +1571,7 @@ void tst_QWindow::orientation() QSignalSpy spy(&window, SIGNAL(contentOrientationChanged(Qt::ScreenOrientation))); window.reportContentOrientationChange(Qt::LandscapeOrientation); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); } void tst_QWindow::sizes() @@ -1567,40 +1590,58 @@ void tst_QWindow::sizes() QCOMPARE(window.minimumHeight(), 0); QCOMPARE(window.minimumSize(), QSize(10, 0)); QCOMPARE(window.maximumSize(), oldMaximum); - QCOMPARE(minimumWidthSpy.count(), 1); - QCOMPARE(minimumHeightSpy.count(), 0); - QCOMPARE(maximumWidthSpy.count(), 0); - QCOMPARE(maximumHeightSpy.count(), 0); + QCOMPARE(minimumWidthSpy.size(), 1); + QCOMPARE(minimumHeightSpy.size(), 0); + QCOMPARE(maximumWidthSpy.size(), 0); + QCOMPARE(maximumHeightSpy.size(), 0); window.setMinimumHeight(10); QCOMPARE(window.minimumWidth(), 10); QCOMPARE(window.minimumHeight(), 10); QCOMPARE(window.minimumSize(), QSize(10, 10)); QCOMPARE(window.maximumSize(), oldMaximum); - QCOMPARE(minimumWidthSpy.count(), 1); - QCOMPARE(minimumHeightSpy.count(), 1); - QCOMPARE(maximumWidthSpy.count(), 0); - QCOMPARE(maximumHeightSpy.count(), 0); + QCOMPARE(minimumWidthSpy.size(), 1); + QCOMPARE(minimumHeightSpy.size(), 1); + QCOMPARE(maximumWidthSpy.size(), 0); + QCOMPARE(maximumHeightSpy.size(), 0); window.setMaximumWidth(100); QCOMPARE(window.maximumWidth(), 100); QCOMPARE(window.maximumHeight(), oldMaximum.height()); QCOMPARE(window.minimumSize(), QSize(10, 10)); QCOMPARE(window.maximumSize(), QSize(100, oldMaximum.height())); - QCOMPARE(minimumWidthSpy.count(), 1); - QCOMPARE(minimumHeightSpy.count(), 1); - QCOMPARE(maximumWidthSpy.count(), 1); - QCOMPARE(maximumHeightSpy.count(), 0); + QCOMPARE(minimumWidthSpy.size(), 1); + QCOMPARE(minimumHeightSpy.size(), 1); + QCOMPARE(maximumWidthSpy.size(), 1); + QCOMPARE(maximumHeightSpy.size(), 0); window.setMaximumHeight(100); QCOMPARE(window.maximumWidth(), 100); QCOMPARE(window.maximumHeight(), 100); QCOMPARE(window.minimumSize(), QSize(10, 10)); QCOMPARE(window.maximumSize(), QSize(100, 100)); - QCOMPARE(minimumWidthSpy.count(), 1); - QCOMPARE(minimumHeightSpy.count(), 1); - QCOMPARE(maximumWidthSpy.count(), 1); - QCOMPARE(maximumHeightSpy.count(), 1); + QCOMPARE(minimumWidthSpy.size(), 1); + QCOMPARE(minimumHeightSpy.size(), 1); + QCOMPARE(maximumWidthSpy.size(), 1); + QCOMPARE(maximumHeightSpy.size(), 1); + + // test if min and max limits will change the size + QVERIFY(window.minimumWidth() < 50 && window.maximumWidth() > 80); + QVERIFY(window.minimumHeight() < 50 && window.maximumHeight() > 80); + window.resize(50, 50); + QCOMPARE(window.size(), QSize(50, 50)); + window.setMinimumSize(QSize(60, 60)); + QCOMPARE(window.size(), QSize(60, 60)); + window.resize(80, 80); + window.setMaximumSize(QSize(70, 70)); + QCOMPARE(window.size(), QSize(70, 70)); + + // QTBUG-113233 + // test for an invalid min/max pair + window.setMinimumSize(QSize(80, 80)); // current maximumSize = QSize(70, 70) + QCOMPARE(window.size(), QSize(70, 70)); + window.setMaximumSize(QSize(90, 90)); + QCOMPARE(window.size(), QSize(80, 80)); } class CloseOnCloseEventWindow : public QWindow @@ -1832,25 +1873,25 @@ void tst_QWindow::windowModality() QCOMPARE(window.modality(), Qt::NonModal); window.setModality(Qt::NonModal); QCOMPARE(window.modality(), Qt::NonModal); - QCOMPARE(spy.count(), 0); + QCOMPARE(spy.size(), 0); window.setModality(Qt::WindowModal); QCOMPARE(window.modality(), Qt::WindowModal); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); window.setModality(Qt::WindowModal); QCOMPARE(window.modality(), Qt::WindowModal); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); window.setModality(Qt::ApplicationModal); QCOMPARE(window.modality(), Qt::ApplicationModal); - QCOMPARE(spy.count(), 2); + QCOMPARE(spy.size(), 2); window.setModality(Qt::ApplicationModal); QCOMPARE(window.modality(), Qt::ApplicationModal); - QCOMPARE(spy.count(), 2); + QCOMPARE(spy.size(), 2); window.setModality(Qt::NonModal); QCOMPARE(window.modality(), Qt::NonModal); - QCOMPARE(spy.count(), 3); + QCOMPARE(spy.size(), 3); } void tst_QWindow::inputReentrancy() @@ -2033,32 +2074,32 @@ void tst_QWindow::visibility() QVERIFY(window.isVisible()); QVERIFY(window.visibility() != QWindow::Hidden); QVERIFY(window.visibility() != QWindow::AutomaticVisibility); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); spy.clear(); window.setVisibility(QWindow::Hidden); QVERIFY(!window.isVisible()); QCOMPARE(window.visibility(), QWindow::Hidden); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); spy.clear(); window.setVisibility(QWindow::FullScreen); QVERIFY(window.isVisible()); QCOMPARE(window.windowState(), Qt::WindowFullScreen); QCOMPARE(window.visibility(), QWindow::FullScreen); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); QTRY_COMPARE(window.lastReceivedWindowState, Qt::WindowFullScreen); spy.clear(); window.setWindowState(Qt::WindowNoState); QCOMPARE(window.visibility(), QWindow::Windowed); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); QTRY_COMPARE(window.lastReceivedWindowState, Qt::WindowNoState); spy.clear(); window.setVisible(false); QCOMPARE(window.visibility(), QWindow::Hidden); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); spy.clear(); } @@ -2091,9 +2132,6 @@ void tst_QWindow::mask() void tst_QWindow::initialSize() { - if (isPlatformWayland()) - QSKIP("Wayland: This fails. See QTBUG-66818."); - QSize defaultSize(0,0); { Window w; @@ -2108,6 +2146,10 @@ void tst_QWindow::initialSize() w.setTitle(QLatin1String(QTest::currentTestFunction())); w.setWidth(m_testWindowSize.width()); w.showNormal(); + + if (isPlatformEglFS()) + QEXPECT_FAIL("", "eglfs windows are fullscreen by default.", Continue); + QTRY_COMPARE(w.width(), m_testWindowSize.width()); QTRY_VERIFY(w.height() > 0); } @@ -2119,6 +2161,8 @@ void tst_QWindow::initialSize() w.showNormal(); const QSize expectedSize = testSize; + if (isPlatformEglFS()) + QEXPECT_FAIL("", "eglfs windows are fullscreen by default.", Continue); QTRY_COMPARE(w.size(), expectedSize); } } @@ -2283,6 +2327,9 @@ void tst_QWindow::modalWindowModallity() void tst_QWindow::modalWindowPosition() { + if (isPlatformWayland()) + QSKIP("Window position not queryable on Wayland"); + QWindow window; window.setTitle(QLatin1String(QTest::currentTestFunction())); window.setGeometry(QRect(m_availableTopLeft + QPoint(100, 100), m_testWindowSize)); @@ -2291,9 +2338,29 @@ void tst_QWindow::modalWindowPosition() window.setModality(Qt::WindowModal); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); + + if (isPlatformEglFS()) + QEXPECT_FAIL("", "eglfs windows are fullscreen by default.", Continue); + QCOMPARE(window.geometry(), origGeo); } +void tst_QWindow::modalCloseWhileBlocked() +{ + QWindow first; + first.setModality(Qt::ApplicationModal); + first.show(); + QVERIFY(QTest::qWaitForWindowExposed(&first)); + + QWindow second; + second.setModality(Qt::ApplicationModal); + second.show(); + QVERIFY(QTest::qWaitForWindowExposed(&first)); + + first.close(); + QTRY_VERIFY(!first.isVisible()); +} + #ifndef QT_NO_CURSOR void tst_QWindow::modalWindowEnterEventOnHide_QTBUG35109() { @@ -2303,6 +2370,9 @@ void tst_QWindow::modalWindowEnterEventOnHide_QTBUG35109() if (isPlatformOffscreenOrMinimal()) QSKIP("Can't test window focusing on offscreen/minimal"); + if (isPlatformEglFS()) + QSKIP("QCursor::setPos() is not supported on this platform"); + const QPoint center = QGuiApplication::primaryScreen()->availableGeometry().center(); const int childOffset = 16; @@ -2483,6 +2553,8 @@ void tst_QWindow::spuriousMouseMove() QSKIP("No enter events sent"); if (platformName == QLatin1String("wayland")) QSKIP("Setting mouse cursor position is not possible on Wayland"); + if (isPlatformEglFS()) + QSKIP("QCursor::setPos() is not supported on this platform"); const QRect screenGeometry = QGuiApplication::primaryScreen()->geometry(); const QPoint center = screenGeometry.center(); QCursor::setPos(center); @@ -2758,6 +2830,395 @@ void tst_QWindow::qobject_castOnDestruction() }); } +void tst_QWindow::touchToMouseTranslationByPopup() +{ + InputTestWindow window; + window.setTitle(QLatin1String(QTest::currentTestFunction())); + window.ignoreTouch = true; + window.setGeometry(QRect(m_availableTopLeft, m_testWindowSize)); + window.show(); + QVERIFY(QTest::qWaitForWindowExposed(&window)); + + InputTestWindow popupWindow; + popupWindow.setGeometry(QRect(m_availableTopLeft + QPoint(20, 20), + QSize(m_testWindowSize.width(), m_testWindowSize.height() / 2))); + popupWindow.setFlag(Qt::Popup); + popupWindow.setTransientParent(&window); + popupWindow.ignoreTouch = true; + popupWindow.closeOnTap = true; + popupWindow.show(); + QVERIFY(QTest::qWaitForWindowExposed(&popupWindow)); + + QTest::touchEvent(&popupWindow, touchDevice).press(0, {1, 1}, &window); + QVERIFY(!popupWindow.isVisible()); + + // Omit touchpoint 0: because the popup was closed, touchpoint0.release is not sent. + const QPoint tp1(50, 1); + QTest::touchEvent(&window, touchDevice).press(1, tp1, &window); + QTRY_COMPARE(window.mousePressButton, int(Qt::LeftButton)); + QTest::touchEvent(&window, touchDevice).release(1, tp1, &window); + QTRY_COMPARE(window.mouseReleaseButton, int(Qt::LeftButton)); +} + +// Test that windowStateChanged is not emitted on noop change (QTBUG-102478) +void tst_QWindow::stateChangeSignal() +{ + // Test only for Windows, Linux and macOS +#if !defined(Q_OS_LINUX) && !defined(Q_OS_WINDOWS) && !defined(Q_OS_DARWIN) + QSKIP("Singular windowStateChanged signal emission is guaranteed for Linux, Windows and macOS only.\n" + "On other operating systems, the signal may be emitted twice."); +#endif + QWindow w; + Q_ASSERT(connect (&w, &QWindow::windowStateChanged, [](Qt::WindowState s){qCDebug(lcTests) << "State change to" << s;})); + QSignalSpy spy(&w, SIGNAL(windowStateChanged(Qt::WindowState))); + unsigned short signalCount = 0; + QList<Qt::WindowState> effectiveStates; + Q_ASSERT(connect(&w, &QWindow::windowStateChanged, [&effectiveStates](Qt::WindowState state) + { effectiveStates.append(state); })); + // Part 1: + // => test signal emission on programmatic state changes + QCOMPARE(w.windowState(), Qt::WindowNoState); + // - wait for target state to be set + // - wait for signal spy to have reached target count + // - extract state from signal and compare to target +#define CHECK_STATE(State)\ + QTRY_VERIFY(QTest::qWaitFor([&w](){return (w.windowState() == State); }));\ + CHECK_SIGNAL(State) +#define CHECK_SIGNAL(State)\ + QTRY_COMPARE(spy.count(), signalCount);\ + if (signalCount > 0) {\ + QVariantList list = spy.at(signalCount - 1).toList();\ + QCOMPARE(list.count(), 1);\ + bool ok;\ + const int stateInt = list.at(0).toInt(&ok);\ + QVERIFY(ok);\ + const Qt::WindowState newState = static_cast<Qt::WindowState>(stateInt);\ + QCOMPARE(newState, State);\ + } + // Check initialization + CHECK_STATE(Qt::WindowNoState); + // showMaximized after init + // expected behavior: signal emitted once with state == WindowMaximized + ++signalCount; + w.showMaximized(); + CHECK_STATE(Qt::WindowMaximized); + // setWindowState to normal + // expected behavior: signal emitted once with state == WindowNoState + ++signalCount; + w.setWindowState(Qt::WindowNoState); + CHECK_STATE(Qt::WindowNoState); + // redundant setWindowState to normal - except windows, where the no-op is counted + // expected behavior: No emits. + // On Windows, a no-op state change causes a no-op resize and repaint, leading to a + // no-op state change and singal emission. +#ifdef Q_OS_WINDOWS + ++signalCount; + ++signalCount; +#endif + w.setWindowState(Qt::WindowNoState); + CHECK_STATE(Qt::WindowNoState); + // setWindowState to minimized + // expected behavior: signal emitted once with state == WindowMinimized + ++signalCount; + w.showMinimized(); + CHECK_STATE(Qt::WindowMinimized); + // setWindowState to Normal + // expected behavior: signal emitted once with state == WindowNoState + ++signalCount; + w.showNormal(); + CHECK_STATE(Qt::WindowNoState); + /* + - Testcase showFullScreen is omitted: Depending on window manager, + WindowFullScreen can be mapped to WindowMaximized + - Transition from WindowMinimized to WindowMaximized is omitted: + WindowNoState to WindowMaximized + */ + // Part 2: + // => test signal emission on simulated user interaction + // To test the code path, inject state change events into the QPA event queue. + // Test the signal emission only, not the window's actual visible state. + + // Flush pending events and clear + QCoreApplication::processEvents(); + spy.clear(); + effectiveStates.clear(); + signalCount = 0; + // Maximize window + QWindowSystemInterface::handleWindowStateChanged(&w, Qt::WindowMaximized, w.windowState()); + ++signalCount; + CHECK_SIGNAL(Qt::WindowMaximized); + // Normalize window + QWindowSystemInterface::handleWindowStateChanged(&w, Qt::WindowNoState, w.windowState()); + ++signalCount; + CHECK_SIGNAL(Qt::WindowNoState); + // Minimize window + QWindowSystemInterface::handleWindowStateChanged(&w, Qt::WindowMinimized, w.windowState()); + ++signalCount; + CHECK_SIGNAL(Qt::WindowMinimized); +} + +#ifndef QT_NO_CURSOR +void tst_QWindow::enterLeaveOnWindowShowHide_data() +{ + QTest::addColumn<Qt::WindowType>("windowType"); + QTest::addRow("dialog") << Qt::Dialog; + QTest::addRow("popup") << Qt::Popup; +} + +/*! + Verify that we get enter and leave events if the window under the mouse + opens and closes a modal dialog or popup. QWindow might get multiple + events in a row, as the various QPA plugins need to use different techniques + to synthesize events if the native platform doesn't provide them for us. +*/ +void tst_QWindow::enterLeaveOnWindowShowHide() +{ + if (isPlatformWayland()) + QSKIP("Can't set cursor position and qWaitForWindowActive on Wayland"); + + if (isPlatformEglFS()) + QSKIP("QCursor::setPos() is not supported on this platform"); + + QFETCH(Qt::WindowType, windowType); + + class Window : public QWindow + { + public: + int numEnterEvents = 0; + int numLeaveEvents = 0; + QPoint enterPosition; + protected: + bool event(QEvent *e) override + { + switch (e->type()) { + case QEvent::Enter: + ++numEnterEvents; + enterPosition = static_cast<QEnterEvent*>(e)->position().toPoint(); + break; + case QEvent::Leave: + ++numLeaveEvents; + break; + default: + break; + } + return QWindow::event(e); + } + }; + + int expectedEnter = 0; + int expectedLeave = 0; + + Window window; + const QRect screenGeometry = window.screen()->availableGeometry(); + const QPoint cursorPos = screenGeometry.topLeft() + QPoint(50, 50); + window.setGeometry(QRect(cursorPos - QPoint(50, 50), screenGeometry.size() / 4)); + QCursor::setPos(cursorPos); + + if (!QTest::qWaitFor([&]{ return window.geometry().contains(QCursor::pos()); })) + QSKIP("We can't move the cursor"); + + window.show(); + window.requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(&window)); + + ++expectedEnter; + QTRY_COMPARE_WITH_TIMEOUT(window.numEnterEvents, expectedEnter, 250); + QCOMPARE(window.enterPosition, window.mapFromGlobal(QCursor::pos())); + + QWindow secondary; + secondary.setFlag(windowType); + secondary.setModality(Qt::WindowModal); + secondary.setTransientParent(&window); + secondary.setPosition(cursorPos + QPoint(50, 50)); + secondary.show(); + QVERIFY(QTest::qWaitForWindowExposed(&secondary)); + ++expectedLeave; + QTRY_VERIFY(window.numLeaveEvents >= expectedLeave); + secondary.close(); + ++expectedEnter; + QTRY_VERIFY(window.numEnterEvents >= expectedEnter); + QCOMPARE(window.enterPosition, window.mapFromGlobal(QCursor::pos())); +} +#endif + +void tst_QWindow::windowExposedAfterReparent() +{ + QWindow parent; + QWindow child(&parent); + child.show(); + parent.show(); + + QVERIFY(QTest::qWaitForWindowExposed(&parent)); + QVERIFY(QTest::qWaitForWindowExposed(&child)); + + child.setParent(nullptr); + QCoreApplication::processEvents(); + QVERIFY(QTest::qWaitForWindowExposed(&child)); + + child.setParent(&parent); + QCoreApplication::processEvents(); + QVERIFY(QTest::qWaitForWindowExposed(&child)); +} + +struct ParentWindow : public QWindow +{ + bool event(QEvent *event) override + { + [&]() -> void { + if (event->type() == QEvent::ChildWindowAdded + || event->type() == QEvent::ChildWindowRemoved) { + // We should not receive child events after the window has been destructed + QVERIFY(this->isWindowType()); + + auto *parentWindow = this; + auto *childEvent = static_cast<QChildWindowEvent*>(event); + auto *childWindow = childEvent->child(); + + if (event->type() == QEvent::ChildWindowAdded) { + QVERIFY(childWindow->parent()); + QVERIFY(parentWindow->isAncestorOf(childWindow)); + if (childWindow->handle()) + QVERIFY(childWindow->handle()->parent() == parentWindow->handle()); + + } else { + QVERIFY(!childWindow->parent()); + QVERIFY(!parentWindow->isAncestorOf(childWindow)); + if (childWindow->handle()) + QVERIFY(childWindow->handle()->parent() != parentWindow->handle()); + } + } + }(); + + return QWindow::event(event); + } +}; + +void tst_QWindow::childEvents() +{ + ParentWindow parent; + + { + // ChildAdded via constructor + QWindow constructorChild(&parent); + if (QTest::currentTestFailed()) return; + // ChildRemoved via destructor + } + + if (QTest::currentTestFailed()) return; + + // ChildAdded and ChildRemoved via setParent + QWindow child; + child.setParent(&parent); + if (QTest::currentTestFailed()) return; + child.setParent(nullptr); + if (QTest::currentTestFailed()) return; + + parent.create(); + child.create(); + + // ChildAdded and ChildRemoved after creation + child.setParent(&parent); + if (QTest::currentTestFailed()) return; + child.setParent(nullptr); + if (QTest::currentTestFailed()) return; +} + +struct ChildWindowPrivate; +struct ChildWindow : public QWindow +{ + ChildWindow(QWindow *parent = nullptr); +}; + +struct ChildWindowPrivate : public QWindowPrivate +{ + ChildWindowPrivate() : QWindowPrivate() + { + receiveParentEvents = true; + } +}; + +ChildWindow::ChildWindow(QWindow *parent) + : QWindow(*new ChildWindowPrivate, parent) +{} + +struct ParentEventTester : public QObject +{ + bool eventFilter(QObject *object, QEvent *event) override + { + [&]() -> void { + if (event->type() == QEvent::ParentWindowAboutToChange + || event->type() == QEvent::ParentWindowChange) { + // We should not receive parent events after the window has been destructed + QVERIFY(object->isWindowType()); + auto *window = static_cast<QWindow*>(object); + + if (event->type() == QEvent::ParentWindowAboutToChange) { + QVERIFY(window->parent() != nextExpectedParent); + if (window->handle()) { + QVERIFY(window->handle()->parent() != + (nextExpectedParent ? nextExpectedParent->handle() : nullptr)); + } + } else { + QVERIFY(window->parent() == nextExpectedParent); + if (window->handle()) { + QVERIFY(window->handle()->parent() == + (nextExpectedParent ? nextExpectedParent->handle() : nullptr)); + } + } + } + }(); + + return QObject::eventFilter(object, event); + } + + QWindow *nextExpectedParent = nullptr; +}; + + + +void tst_QWindow::parentEvents() +{ + QWindow parent; + + { + ParentEventTester tester; + + { + // We can't hook in early enough to get the parent change during + // QObject construction. + ChildWindow child(&parent); + + // But we can observe the one during destruction + child.installEventFilter(&tester); + tester.nextExpectedParent = nullptr; + } + } + if (QTest::currentTestFailed()) return; + + ParentEventTester tester; + ChildWindow child; + child.installEventFilter(&tester); + + tester.nextExpectedParent = &parent; + child.setParent(&parent); + if (QTest::currentTestFailed()) return; + + tester.nextExpectedParent = nullptr; + child.setParent(nullptr); + if (QTest::currentTestFailed()) return; + + parent.create(); + child.create(); + + tester.nextExpectedParent = &parent; + child.setParent(&parent); + if (QTest::currentTestFailed()) return; + + tester.nextExpectedParent = nullptr; + child.setParent(nullptr); + if (QTest::currentTestFailed()) return; +} + #include <tst_qwindow.moc> QTEST_MAIN(tst_QWindow) |