blob: e44475de71d7c36acb97e7d2f136ea7a17865d9d [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2017 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$
**
****************************************************************************/
#include "mockcompositor.h"
#include <QBackingStore>
#include <QPainter>
#include <QScreen>
#include <QWindow>
#include <QMimeData>
#include <QPixmap>
#include <QDrag>
#include <QtTest/QtTest>
static const QSize screenSize(1600, 1200);
class TestWindow : public QWindow
{
Q_OBJECT
public:
TestWindow()
{
setSurfaceType(QSurface::RasterSurface);
setGeometry(0, 0, 32, 32);
create();
}
bool event(QEvent *event) override
{
if (event->type() == QEvent::WindowStateChange)
emit windowStateChangeEventReceived(static_cast<QWindowStateChangeEvent *>(event)->oldState());
return QWindow::event(event);
}
void exposeEvent(QExposeEvent *event) override
{
++exposeEventCount;
QWindow::exposeEvent(event);
}
int exposeEventCount = 0;
signals:
void windowStateChangeEventReceived(uint oldState);
};
class tst_WaylandClientXdgShellV6 : public QObject
{
Q_OBJECT
public:
tst_WaylandClientXdgShellV6(MockCompositor *c)
: m_compositor(c)
{
qRegisterMetaType<Qt::WindowState>();
QSocketNotifier *notifier = new QSocketNotifier(m_compositor->waylandFileDescriptor(), QSocketNotifier::Read, this);
connect(notifier, &QSocketNotifier::activated, this, &tst_WaylandClientXdgShellV6::processWaylandEvents);
// connect to the event dispatcher to make sure to flush out the outgoing message queue
connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::awake, this, &tst_WaylandClientXdgShellV6::processWaylandEvents);
connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, this, &tst_WaylandClientXdgShellV6::processWaylandEvents);
}
public slots:
void processWaylandEvents()
{
m_compositor->processWaylandEvents();
}
void cleanup()
{
// make sure the surfaces from the last test are properly cleaned up
// and don't show up as false positives in the next test
QTRY_VERIFY(!m_compositor->surface());
QTRY_VERIFY(!m_compositor->xdgToplevelV6());
}
private slots:
void createDestroyWindow();
void configure();
void showMinimized();
void setMinimized();
void unsetMaximized();
void focusWindowFollowsConfigure();
void windowStateChangedEvents();
void windowGeometrySimple();
void windowGeometryFixed();
void flushUnconfiguredXdgSurface();
void dontSpamExposeEvents();
private:
MockCompositor *m_compositor = nullptr;
};
void tst_WaylandClientXdgShellV6::createDestroyWindow()
{
TestWindow window;
window.show();
QTRY_VERIFY(m_compositor->surface());
window.destroy();
QTRY_VERIFY(!m_compositor->surface());
}
void tst_WaylandClientXdgShellV6::configure()
{
QSharedPointer<MockOutput> output;
QTRY_VERIFY(output = m_compositor->output());
TestWindow window;
window.show();
QSharedPointer<MockSurface> surface;
QTRY_VERIFY(surface = m_compositor->surface());
m_compositor->processWaylandEvents();
QTRY_VERIFY(window.isVisible());
QTRY_VERIFY(!window.isExposed()); //Window should not be exposed before the first configure event
//TODO: according to xdg-shell protocol, a buffer should not be attached to a the surface
//until it's configured. Ensure this in the test!
QSharedPointer<MockXdgToplevelV6> toplevel;
QTRY_VERIFY(toplevel = m_compositor->xdgToplevelV6());
const QSize newSize(123, 456);
m_compositor->sendXdgToplevelV6Configure(toplevel, newSize);
QTRY_VERIFY(window.isExposed());
QTRY_COMPARE(window.visibility(), QWindow::Windowed);
QTRY_COMPARE(window.windowStates(), Qt::WindowNoState);
QTRY_COMPARE(window.frameGeometry(), QRect(QPoint(), newSize));
m_compositor->sendXdgToplevelV6Configure(toplevel, screenSize, { ZXDG_TOPLEVEL_V6_STATE_ACTIVATED, ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED });
QTRY_COMPARE(window.visibility(), QWindow::Maximized);
QTRY_COMPARE(window.windowStates(), Qt::WindowMaximized);
QTRY_COMPARE(window.frameGeometry(), QRect(QPoint(), screenSize));
m_compositor->sendXdgToplevelV6Configure(toplevel, screenSize, { ZXDG_TOPLEVEL_V6_STATE_ACTIVATED, ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN });
QTRY_COMPARE(window.visibility(), QWindow::FullScreen);
QTRY_COMPARE(window.windowStates(), Qt::WindowFullScreen);
QTRY_COMPARE(window.frameGeometry(), QRect(QPoint(), screenSize));
//The window should remember it's original size
m_compositor->sendXdgToplevelV6Configure(toplevel, QSize(0, 0), { ZXDG_TOPLEVEL_V6_STATE_ACTIVATED });
QTRY_COMPARE(window.visibility(), QWindow::Windowed);
QTRY_COMPARE(window.windowStates(), Qt::WindowNoState);
QTRY_COMPARE(window.frameGeometry(), QRect(QPoint(), newSize));
}
void tst_WaylandClientXdgShellV6::showMinimized()
{
// On xdg-shell v6 there's really no way for the compositor to tell the window if it's minimized
// There are wl_surface.enter events and so on, but there's really no way to differentiate
// between a window preview and an unminimized window.
TestWindow window;
window.showMinimized();
QCOMPARE(window.windowStates(), Qt::WindowMinimized); // should return minimized until
QTRY_COMPARE(window.windowStates(), Qt::WindowNoState); // rejected by handleWindowStateChanged
// Make sure the window on the compositor side is/was created here, and not after the test
// finishes, as that may mess up for later tests.
QTRY_VERIFY(m_compositor->xdgToplevelV6());
}
void tst_WaylandClientXdgShellV6::setMinimized()
{
TestWindow window;
window.show();
QSharedPointer<MockXdgToplevelV6> toplevel;
QTRY_VERIFY(toplevel = m_compositor->xdgToplevelV6());
m_compositor->sendXdgToplevelV6Configure(toplevel);
QTRY_COMPARE(window.visibility(), QWindow::Windowed);
QTRY_COMPARE(window.windowStates(), Qt::WindowNoState);
QSignalSpy setMinimizedSpy(toplevel.data(), SIGNAL(setMinimizedRequested()));
QSignalSpy windowStateChangeSpy(&window, SIGNAL(windowStateChangeEventReceived(uint)));
window.setVisibility(QWindow::Minimized);
QCOMPARE(window.visibility(), QWindow::Minimized);
QCOMPARE(window.windowStates(), Qt::WindowMinimized);
QTRY_COMPARE(setMinimizedSpy.count(), 1);
{
QTRY_VERIFY(windowStateChangeSpy.count() > 0);
Qt::WindowStates oldStates(windowStateChangeSpy.takeFirst().at(0).toUInt());
QCOMPARE(oldStates, Qt::WindowNoState);
}
// In the meantime the compositor may minimize, do nothing or reshow the window without
// telling us.
QTRY_COMPARE(window.visibility(), QWindow::Windowed); // verify that we don't know anything
QTRY_COMPARE(window.windowStates(), Qt::WindowNoState);
{
QTRY_COMPARE(windowStateChangeSpy.count(), 1);
Qt::WindowStates oldStates(windowStateChangeSpy.takeFirst().at(0).toUInt());
QCOMPARE(oldStates, Qt::WindowNoState); // because the window never was minimized
}
// Setting visibility again should send another set_minimized request
window.setVisibility(QWindow::Minimized);
QTRY_COMPARE(setMinimizedSpy.count(), 2);
}
void tst_WaylandClientXdgShellV6::unsetMaximized()
{
TestWindow window;
window.show();
QSharedPointer<MockXdgToplevelV6> toplevel;
QTRY_VERIFY(toplevel = m_compositor->xdgToplevelV6());
QSignalSpy unsetMaximizedSpy(toplevel.data(), SIGNAL(unsetMaximizedRequested()));
QSignalSpy windowStateChangedSpy(&window, SIGNAL(windowStateChanged(Qt::WindowState)));
m_compositor->sendXdgToplevelV6Configure(toplevel, screenSize, { ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED });
QTRY_COMPARE(windowStateChangedSpy.count(), 1);
QCOMPARE(windowStateChangedSpy.takeFirst().at(0).toUInt(), Qt::WindowMaximized);
window.setWindowStates(Qt::WindowNoState);
QTRY_COMPARE(unsetMaximizedSpy.count(), 1);
QTRY_COMPARE(windowStateChangedSpy.count(), 1);
QCOMPARE(windowStateChangedSpy.takeFirst().at(0).toUInt(), Qt::WindowNoState);
m_compositor->sendXdgToplevelV6Configure(toplevel, QSize(0, 0), {});
QTRY_COMPARE(windowStateChangedSpy.count(), 1);
QCOMPARE(windowStateChangedSpy.takeFirst().at(0).toUInt(), Qt::WindowNoState);
}
void tst_WaylandClientXdgShellV6::focusWindowFollowsConfigure()
{
TestWindow window;
window.show();
QSharedPointer<MockXdgToplevelV6> toplevel;
QTRY_VERIFY(toplevel = m_compositor->xdgToplevelV6());
QTRY_VERIFY(!window.isActive());
QSignalSpy windowStateChangeSpy(&window, SIGNAL(windowStateChangeEventReceived(uint)));
m_compositor->sendXdgToplevelV6Configure(toplevel, QSize(0, 0), { ZXDG_TOPLEVEL_V6_STATE_ACTIVATED });
QTRY_VERIFY(window.isActive());
m_compositor->sendXdgToplevelV6Configure(toplevel, QSize(0, 0), {});
QTRY_VERIFY(!window.isActive());
}
void tst_WaylandClientXdgShellV6::windowStateChangedEvents()
{
TestWindow window;
window.show();
QSharedPointer<MockXdgToplevelV6> toplevel;
QTRY_VERIFY(toplevel = m_compositor->xdgToplevelV6());
QSignalSpy eventSpy(&window, SIGNAL(windowStateChangeEventReceived(uint)));
QSignalSpy signalSpy(&window, SIGNAL(windowStateChanged(Qt::WindowState)));
m_compositor->sendXdgToplevelV6Configure(toplevel, screenSize, { ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED });
QTRY_COMPARE(window.windowStates(), Qt::WindowMaximized);
QTRY_COMPARE(window.windowState(), Qt::WindowMaximized);
{
QTRY_COMPARE(eventSpy.count(), 1);
Qt::WindowStates oldStates(eventSpy.takeFirst().at(0).toUInt());
QCOMPARE(oldStates, Qt::WindowNoState);
QTRY_COMPARE(signalSpy.count(), 1);
uint newState = signalSpy.takeFirst().at(0).toUInt();
QCOMPARE(newState, Qt::WindowMaximized);
}
m_compositor->sendXdgToplevelV6Configure(toplevel, screenSize, { ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN });
QTRY_COMPARE(window.windowStates(), Qt::WindowFullScreen);
QTRY_COMPARE(window.windowState(), Qt::WindowFullScreen);
{
QTRY_COMPARE(eventSpy.count(), 1);
Qt::WindowStates oldStates(eventSpy.takeFirst().at(0).toUInt());
QCOMPARE(oldStates, Qt::WindowMaximized);
QTRY_COMPARE(signalSpy.count(), 1);
uint newState = signalSpy.takeFirst().at(0).toUInt();
QCOMPARE(newState, Qt::WindowFullScreen);
}
m_compositor->sendXdgToplevelV6Configure(toplevel, QSize(0, 0), {});
QTRY_COMPARE(window.windowStates(), Qt::WindowNoState);
QTRY_COMPARE(window.windowState(), Qt::WindowNoState);
{
QTRY_COMPARE(eventSpy.count(), 1);
Qt::WindowStates oldStates(eventSpy.takeFirst().at(0).toUInt());
QCOMPARE(oldStates, Qt::WindowFullScreen);
QTRY_COMPARE(signalSpy.count(), 1);
uint newState = signalSpy.takeFirst().at(0).toUInt();
QCOMPARE(newState, Qt::WindowNoState);
}
}
void tst_WaylandClientXdgShellV6::windowGeometrySimple()
{
QSKIP("TODO: This test is flaky, figure out why.");
QWindow window;
window.show();
QSharedPointer<MockXdgToplevelV6> toplevel;
QTRY_VERIFY(toplevel = m_compositor->xdgToplevelV6());
QSignalSpy geometrySpy(toplevel.data(), SIGNAL(windowGeometryRequested(QRect)));
m_compositor->sendXdgToplevelV6Configure(toplevel);
QTRY_COMPARE(geometrySpy.count(), 1);
QCOMPARE(geometrySpy.takeFirst().at(0).toRect().size(), window.frameGeometry().size());
m_compositor->sendXdgToplevelV6Configure(toplevel, QSize(123, 456));
QTRY_COMPARE(geometrySpy.count(), 1);
QCOMPARE(geometrySpy.takeFirst().at(0).toRect().size(), QSize(123, 456));
}
void tst_WaylandClientXdgShellV6::windowGeometryFixed()
{
QSKIP("TODO: This test is flaky, figure out why.");
QWindow window;
window.resize(QSize(1337, 137));
window.setMaximumSize(window.size());
window.setMinimumSize(window.size());
window.show();
QSharedPointer<MockXdgToplevelV6> toplevel;
QTRY_VERIFY(toplevel = m_compositor->xdgToplevelV6());
QSignalSpy geometrySpy(toplevel.data(), SIGNAL(windowGeometryRequested(QRect)));
m_compositor->sendXdgToplevelV6Configure(toplevel);
QTRY_COMPARE(geometrySpy.count(), 1);
QRect initialWindowGeometry = geometrySpy.takeFirst().at(0).toRect();
QCOMPARE(initialWindowGeometry.size(), window.frameGeometry().size());
m_compositor->sendXdgToplevelV6Configure(toplevel, QSize(123, 456));
QTRY_COMPARE(geometrySpy.count(), 1);
// Configuring the window should not change the window geometry
QCOMPARE(geometrySpy.takeFirst().at(0).toRect().size(), initialWindowGeometry.size());
}
void tst_WaylandClientXdgShellV6::flushUnconfiguredXdgSurface()
{
TestWindow window;
window.show();
QSharedPointer<MockSurface> surface;
QTRY_VERIFY(surface = m_compositor->surface());
// Paint and flush some magenta
QBackingStore backingStore(&window);
QRect rect(QPoint(), window.size());
backingStore.resize(rect.size());
backingStore.beginPaint(rect);
QColor color = Qt::magenta;
QPainter p(backingStore.paintDevice());
p.fillRect(rect, color);
p.end();
backingStore.endPaint();
backingStore.flush(rect);
// We're not allowed to send buffer on this surface since it isn't yet configured.
// So, from the compositor's point of view there should be no buffer data yet.
m_compositor->processWaylandEvents();
QVERIFY(surface->image.isNull());
QVERIFY(!window.isExposed());
// Finally sending the configure should trigger an attach and commit with the
// right buffer.
m_compositor->sendShellSurfaceConfigure(surface);
QTRY_COMPARE(surface->image.size(), window.frameGeometry().size());
QTRY_COMPARE(surface->image.pixel(window.frameMargins().left(), window.frameMargins().top()), color.rgba());
QVERIFY(window.isExposed());
}
void tst_WaylandClientXdgShellV6::dontSpamExposeEvents()
{
TestWindow window;
window.show();
QSharedPointer<MockSurface> surface;
QTRY_VERIFY(surface = m_compositor->surface());
QTRY_COMPARE(window.exposeEventCount, 0);
m_compositor->sendShellSurfaceConfigure(surface);
QTRY_VERIFY(window.isExposed());
QTRY_COMPARE(window.exposeEventCount, 1);
}
int main(int argc, char **argv)
{
QTemporaryDir tmpRuntimeDir;
setenv("XDG_RUNTIME_DIR", tmpRuntimeDir.path().toLocal8Bit(), 1);
setenv("QT_QPA_PLATFORM", "wayland", 1); // force QGuiApplication to use wayland plugin
setenv("QT_WAYLAND_SHELL_INTEGRATION", "xdg-shell-v6", 1);
// wayland-egl hangs in the test setup when we try to initialize. Until it gets
// figured out, avoid clientBufferIntegration() from being called in
// QWaylandWindow::createDecorations().
setenv("QT_WAYLAND_DISABLE_WINDOWDECORATION", "1", 1);
MockCompositor compositor;
compositor.setOutputMode(screenSize);
QGuiApplication app(argc, argv);
compositor.applicationInitialized();
tst_WaylandClientXdgShellV6 tc(&compositor);
return QTest::qExec(&tc, argc, argv);
}
#include <tst_xdgshellv6.moc>