summaryrefslogtreecommitdiffstats
path: root/tests/auto/compositor/compositor/tst_compositor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/compositor/compositor/tst_compositor.cpp')
-rw-r--r--tests/auto/compositor/compositor/tst_compositor.cpp576
1 files changed, 576 insertions, 0 deletions
diff --git a/tests/auto/compositor/compositor/tst_compositor.cpp b/tests/auto/compositor/compositor/tst_compositor.cpp
new file mode 100644
index 000000000..05e876dd0
--- /dev/null
+++ b/tests/auto/compositor/compositor/tst_compositor.cpp
@@ -0,0 +1,576 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#include "mockclient.h"
+#include "testcompositor.h"
+#include "testkeyboardgrabber.h"
+#include "testseat.h"
+
+#include "qwaylandview.h"
+#include "qwaylandbufferref.h"
+#include "qwaylandseat.h"
+
+#include <QtWaylandCompositor/QWaylandXdgShell>
+#include <QtWaylandCompositor/QWaylandSurface>
+#include <QtWaylandCompositor/QWaylandResource>
+#include <qwayland-xdg-shell.h>
+
+#include <QtTest/QtTest>
+
+class tst_WaylandCompositor : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void init();
+ void seatCapabilities();
+ void keyboardGrab();
+ void seatCreation();
+ void seatKeyboardFocus();
+ void singleClient();
+ void multipleClients();
+ void geometry();
+ void mapSurface();
+ void frameCallback();
+ void advertisesXdgShellSupport();
+ void createsXdgSurfaces();
+ void reportsXdgSurfaceWindowGeometry();
+ void setsXdgAppId();
+ void sendsXdgConfigure();
+};
+
+void tst_WaylandCompositor::init() {
+ qputenv("XDG_RUNTIME_DIR", ".");
+}
+
+void tst_WaylandCompositor::singleClient()
+{
+ TestCompositor compositor;
+ compositor.create();
+
+ MockClient client;
+
+ wl_surface *sa = client.createSurface();
+ QTRY_COMPARE(compositor.surfaces.size(), 1);
+
+ wl_surface *sb = client.createSurface();
+ QTRY_COMPARE(compositor.surfaces.size(), 2);
+
+ QWaylandClient *ca = compositor.surfaces.at(0)->client();
+ QWaylandClient *cb = compositor.surfaces.at(1)->client();
+
+ QCOMPARE(ca, cb);
+ QVERIFY(ca != 0);
+
+ QList<QWaylandSurface *> surfaces = compositor.surfacesForClient(ca);
+ QCOMPARE(surfaces.size(), 2);
+ QVERIFY((surfaces.at(0) == compositor.surfaces.at(0) && surfaces.at(1) == compositor.surfaces.at(1))
+ || (surfaces.at(0) == compositor.surfaces.at(1) && surfaces.at(1) == compositor.surfaces.at(0)));
+
+ wl_surface_destroy(sa);
+ QTRY_COMPARE(compositor.surfaces.size(), 1);
+
+ wl_surface_destroy(sb);
+ QTRY_COMPARE(compositor.surfaces.size(), 0);
+}
+
+void tst_WaylandCompositor::multipleClients()
+{
+ TestCompositor compositor;
+ compositor.create();
+
+ MockClient a;
+ MockClient b;
+ MockClient c;
+
+ wl_surface *sa = a.createSurface();
+ wl_surface *sb = b.createSurface();
+ wl_surface *sc = c.createSurface();
+
+ QTRY_COMPARE(compositor.surfaces.size(), 3);
+
+ QWaylandClient *ca = compositor.surfaces.at(0)->client();
+ QWaylandClient *cb = compositor.surfaces.at(1)->client();
+ QWaylandClient *cc = compositor.surfaces.at(2)->client();
+
+ QVERIFY(ca != cb);
+ QVERIFY(ca != cc);
+ QVERIFY(cb != cc);
+ QVERIFY(ca != 0);
+
+ QCOMPARE(compositor.surfacesForClient(ca).size(), 1);
+ QCOMPARE(compositor.surfacesForClient(ca).at(0), compositor.surfaces.at(0));
+
+ QCOMPARE(compositor.surfacesForClient(cb).size(), 1);
+ QCOMPARE(compositor.surfacesForClient(cb).at(0), compositor.surfaces.at(1));
+
+ QCOMPARE(compositor.surfacesForClient(cc).size(), 1);
+ QCOMPARE(compositor.surfacesForClient(cc).at(0), compositor.surfaces.at(2));
+
+ wl_surface_destroy(sa);
+ wl_surface_destroy(sb);
+ wl_surface_destroy(sc);
+
+ QTRY_COMPARE(compositor.surfaces.size(), 0);
+}
+
+void tst_WaylandCompositor::keyboardGrab()
+{
+ TestCompositor compositor;
+ compositor.create();
+ MockClient mc;
+
+ mc.createSurface();
+ // This is needed for timing purposes, otherwise the query for the
+ // compositor surfaces will return null
+ QTRY_COMPARE(compositor.surfaces.size(), 1);
+
+ // Set the focused surface so that key event will flow through
+ QWaylandSurface *waylandSurface = compositor.surfaces.at(0);
+ QWaylandSeat* seat = compositor.defaultSeat();
+
+ TestKeyboardGrabber* grab = static_cast<TestKeyboardGrabber *>(seat->keyboard());
+ QTRY_COMPARE(grab, seat->keyboard());
+ QSignalSpy grabFocusSpy(grab, SIGNAL(focusedCalled()));
+ QSignalSpy grabKeyPressSpy(grab, SIGNAL(keyPressCalled()));
+ QSignalSpy grabKeyReleaseSpy(grab, SIGNAL(keyReleaseCalled()));
+ //QSignalSpy grabModifierSpy(grab, SIGNAL(modifiersCalled()));
+
+ seat->setKeyboardFocus(waylandSurface);
+ QTRY_COMPARE(grabFocusSpy.count(), 1);
+
+ QKeyEvent ke(QEvent::KeyPress, Qt::Key_A, Qt::NoModifier, 30, 0, 0);
+ QKeyEvent ke1(QEvent::KeyRelease, Qt::Key_A, Qt::NoModifier, 30, 0, 0);
+ seat->sendFullKeyEvent(&ke);
+ seat->sendFullKeyEvent(&ke1);
+ QTRY_COMPARE(grabKeyPressSpy.count(), 1);
+ QTRY_COMPARE(grabKeyReleaseSpy.count(), 1);
+
+ QKeyEvent ke2(QEvent::KeyPress, Qt::Key_Shift, Qt::NoModifier, 50, 0, 0);
+ QKeyEvent ke3(QEvent::KeyRelease, Qt::Key_Shift, Qt::NoModifier, 50, 0, 0);
+ seat->sendFullKeyEvent(&ke2);
+ seat->sendFullKeyEvent(&ke3);
+ //QTRY_COMPARE(grabModifierSpy.count(), 2);
+ // Modifiers are also keys
+ QTRY_COMPARE(grabKeyPressSpy.count(), 2);
+ QTRY_COMPARE(grabKeyReleaseSpy.count(), 2);
+
+ // Stop grabbing
+ seat->setKeyboardFocus(Q_NULLPTR);
+ seat->sendFullKeyEvent(&ke);
+ seat->sendFullKeyEvent(&ke1);
+ QTRY_COMPARE(grabKeyPressSpy.count(), 2);
+}
+
+void tst_WaylandCompositor::geometry()
+{
+ TestCompositor compositor;
+ compositor.create();
+
+ QRect geometry(0, 0, 4096, 3072);
+ compositor.defaultOutput()->setGeometry(geometry);
+
+ MockClient client;
+
+ QTRY_COMPARE(client.geometry, geometry);
+}
+
+void tst_WaylandCompositor::mapSurface()
+{
+ TestCompositor compositor;
+ compositor.create();
+
+ MockClient client;
+
+ wl_surface *surface = client.createSurface();
+ QTRY_COMPARE(compositor.surfaces.size(), 1);
+
+ QWaylandSurface *waylandSurface = compositor.surfaces.at(0);
+
+ QSignalSpy hasContentSpy(waylandSurface, SIGNAL(hasContentChanged()));
+
+ QCOMPARE(waylandSurface->size(), QSize());
+ QCOMPARE(waylandSurface->hasContent(), false);
+
+ QSize size(256, 256);
+ ShmBuffer buffer(size, client.shm);
+
+ // we need to create a shell surface here or the surface won't be mapped
+ client.createShellSurface(surface);
+ wl_surface_attach(surface, buffer.handle, 0, 0);
+ wl_surface_damage(surface, 0, 0, size.width(), size.height());
+ wl_surface_commit(surface);
+
+ QTRY_COMPARE(waylandSurface->size(), size);
+ QTRY_COMPARE(waylandSurface->hasContent(), true);
+ QTRY_COMPARE(hasContentSpy.count(), 1);
+
+ wl_surface_destroy(surface);
+}
+
+static void frameCallbackFunc(void *data, wl_callback *callback, uint32_t)
+{
+ ++*static_cast<int *>(data);
+ wl_callback_destroy(callback);
+}
+
+static void registerFrameCallback(wl_surface *surface, int *counter)
+{
+ static const wl_callback_listener frameCallbackListener = {
+ frameCallbackFunc
+ };
+
+ wl_callback_add_listener(wl_surface_frame(surface), &frameCallbackListener, counter);
+}
+
+void tst_WaylandCompositor::frameCallback()
+{
+ class BufferView : public QWaylandView
+ {
+ public:
+ void attach(const QWaylandBufferRef &ref, const QRegion &damage) Q_DECL_OVERRIDE
+ {
+ Q_UNUSED(damage);
+ bufferRef = ref;
+ }
+
+ QImage image() const
+ {
+ if (bufferRef.isNull() || !bufferRef.isSharedMemory())
+ return QImage();
+ return bufferRef.image();
+ }
+
+ QWaylandBufferRef bufferRef;
+ };
+
+ TestCompositor compositor;
+ compositor.create();
+
+ MockClient client;
+
+ wl_surface *surface = client.createSurface();
+
+ int frameCounter = 0;
+
+ QTRY_COMPARE(compositor.surfaces.size(), 1);
+ QWaylandSurface *waylandSurface = compositor.surfaces.at(0);
+ BufferView* view = new BufferView;
+ view->setSurface(waylandSurface);
+ view->setOutput(compositor.defaultOutput());
+
+ QSignalSpy damagedSpy(waylandSurface, SIGNAL(damaged(const QRegion &)));
+
+ for (int i = 0; i < 10; ++i) {
+ QSize size(i * 8 + 2, i * 8 + 2);
+ ShmBuffer buffer(size, client.shm);
+
+ // attach a new buffer every frame, else the damage signal won't be fired
+ wl_surface_attach(surface, buffer.handle, 0, 0);
+ registerFrameCallback(surface, &frameCounter);
+ wl_surface_damage(surface, 0, 0, size.width(), size.height());
+ wl_surface_commit(surface);
+
+ QTRY_COMPARE(waylandSurface->hasContent(), true);
+ QTRY_COMPARE(damagedSpy.count(), i + 1);
+
+ QCOMPARE(static_cast<BufferView*>(waylandSurface->views().first())->image(), buffer.image);
+ compositor.defaultOutput()->frameStarted();
+ compositor.defaultOutput()->sendFrameCallbacks();
+
+ QTRY_COMPARE(frameCounter, i + 1);
+ }
+
+ wl_surface_destroy(surface);
+}
+
+void tst_WaylandCompositor::seatCapabilities()
+{
+ TestCompositor compositor;
+ compositor.create();
+
+ MockClient client;
+ Q_UNUSED(client);
+
+ QWaylandSeat dev(&compositor, QWaylandSeat::Pointer);
+
+ QTRY_VERIFY(dev.pointer());
+ QTRY_VERIFY(!dev.keyboard());
+ QTRY_VERIFY(!dev.touch());
+
+ QWaylandSeat dev2(&compositor, QWaylandSeat::Keyboard | QWaylandSeat::Touch);
+ QTRY_VERIFY(!dev2.pointer());
+ QTRY_VERIFY(dev2.keyboard());
+ QTRY_VERIFY(dev2.touch());
+}
+
+void tst_WaylandCompositor::seatCreation()
+{
+ TestCompositor compositor(true);
+ compositor.create();
+
+ MockClient client;
+ Q_UNUSED(client);
+
+ TestSeat* dev = static_cast<TestSeat*>(compositor.defaultSeat());
+
+ // The compositor will create the default input device
+ QTRY_COMPARE(compositor.defaultSeat(), dev);
+
+ QList<QMouseEvent *> allEvents;
+ allEvents += dev->createMouseEvents(5);
+ foreach (QMouseEvent *me, allEvents) {
+ compositor.seatFor(me);
+ }
+
+ // The default input device will get called exatly the number of times it has created
+ // the events
+ QTRY_COMPARE(dev->queryCount(), 5);
+}
+
+void tst_WaylandCompositor::seatKeyboardFocus()
+{
+ TestCompositor compositor(true);
+ compositor.create();
+
+ // Create client after all the input devices have been set up as the mock client
+ // does not dynamically listen to new seats
+ MockClient client;
+ wl_surface *surface = client.createSurface();
+ QTRY_COMPARE(compositor.surfaces.size(), 1);
+
+ QWaylandSurface *waylandSurface = compositor.surfaces.at(0);
+ QWaylandSeat* dev = compositor.defaultSeat();
+ dev->setKeyboardFocus(waylandSurface);
+ QTRY_COMPARE(compositor.defaultSeat()->keyboardFocus(), waylandSurface);
+
+ wl_surface_destroy(surface);
+ QTRY_VERIFY(compositor.surfaces.size() == 0);
+
+ QTRY_VERIFY(!compositor.defaultSeat()->keyboardFocus());
+}
+
+class XdgTestCompositor: public TestCompositor {
+ Q_OBJECT
+public:
+ XdgTestCompositor() : xdgShell(this) {}
+ QWaylandXdgShell xdgShell;
+};
+
+void tst_WaylandCompositor::advertisesXdgShellSupport()
+{
+ XdgTestCompositor compositor;
+ compositor.create();
+
+ MockClient client;
+ QTRY_VERIFY(&client.xdgShell);
+}
+
+void tst_WaylandCompositor::createsXdgSurfaces()
+{
+ XdgTestCompositor compositor;
+ compositor.create();
+
+ MockClient client;
+ QTRY_VERIFY(&client.xdgShell);
+
+ QSignalSpy xdgSurfaceCreatedSpy(&compositor.xdgShell, &QWaylandXdgShell::xdgSurfaceCreated);
+ QWaylandXdgSurface *xdgSurface = nullptr;
+ QObject::connect(&compositor.xdgShell, &QWaylandXdgShell::xdgSurfaceCreated, [&](QWaylandXdgSurface *s) {
+ xdgSurface = s;
+ });
+
+ wl_surface *surface = client.createSurface();
+ client.createXdgSurface(surface);
+ QTRY_COMPARE(xdgSurfaceCreatedSpy.count(), 1);
+ QTRY_VERIFY(xdgSurface);
+ QTRY_VERIFY(xdgSurface->surface());
+}
+
+void tst_WaylandCompositor::reportsXdgSurfaceWindowGeometry()
+{
+ XdgTestCompositor compositor;
+ compositor.create();
+
+ QWaylandXdgSurface *xdgSurface = nullptr;
+ QObject::connect(&compositor.xdgShell, &QWaylandXdgShell::xdgSurfaceCreated, [&](QWaylandXdgSurface *s) {
+ xdgSurface = s;
+ });
+
+ MockClient client;
+ wl_surface *surface = client.createSurface();
+ xdg_surface *clientXdgSurface = client.createXdgSurface(surface);
+ QSize size(256, 256);
+ ShmBuffer buffer(size, client.shm);
+ wl_surface_attach(surface, buffer.handle, 0, 0);
+ wl_surface_damage(surface, 0, 0, size.width(), size.height());
+ wl_surface_commit(surface);
+
+ QTRY_VERIFY(xdgSurface);
+ QTRY_COMPARE(xdgSurface->windowGeometry(), QRect(QPoint(0, 0), QSize(256, 256)));
+
+ xdg_surface_set_window_geometry(clientXdgSurface, 10, 20, 100, 50);
+ wl_surface_commit(surface);
+
+ QTRY_COMPARE(xdgSurface->windowGeometry(), QRect(QPoint(10, 20), QSize(100, 50)));
+}
+
+void tst_WaylandCompositor::setsXdgAppId()
+{
+ XdgTestCompositor compositor;
+ compositor.create();
+
+ QWaylandXdgSurface *xdgSurface = nullptr;
+ QObject::connect(&compositor.xdgShell, &QWaylandXdgShell::xdgSurfaceCreated, [&](QWaylandXdgSurface *s) {
+ xdgSurface = s;
+ });
+
+ MockClient client;
+ wl_surface *surface = client.createSurface();
+ xdg_surface *clientXdgSurface = client.createXdgSurface(surface);
+
+ xdg_surface_set_app_id(clientXdgSurface, "org.foo.bar");
+
+ QTRY_VERIFY(xdgSurface);
+ QTRY_COMPARE(xdgSurface->appId(), QString("org.foo.bar"));
+}
+
+void tst_WaylandCompositor::sendsXdgConfigure()
+{
+ class MockXdgSurface : public QtWayland::xdg_surface
+ {
+ public:
+ MockXdgSurface(::xdg_surface *xdgSurface) : QtWayland::xdg_surface(xdgSurface) {}
+ void xdg_surface_configure(int32_t width, int32_t height, wl_array *rawStates, uint32_t serial) Q_DECL_OVERRIDE
+ {
+ configureSize = QSize(width, height);
+ configureSerial = serial;
+
+ uint *states = reinterpret_cast<uint*>(rawStates->data);
+ configureStates.clear();
+ size_t numStates = rawStates->size / sizeof(uint);
+ for (size_t i = 0; i < numStates; ++i) {
+ configureStates.push_back(states[i]);
+ }
+ }
+
+ QList<uint> configureStates;
+ QSize configureSize;
+ uint configureSerial;
+ };
+
+ XdgTestCompositor compositor;
+ compositor.create();
+
+ QWaylandXdgSurface *xdgSurface = nullptr;
+ QObject::connect(&compositor.xdgShell, &QWaylandXdgShell::xdgSurfaceCreated, [&](QWaylandXdgSurface *s) {
+ xdgSurface = s;
+ });
+
+ MockClient client;
+ wl_surface *surface = client.createSurface();
+ xdg_surface *clientXdgSurface = client.createXdgSurface(surface);
+ MockXdgSurface mockXdgSurface(clientXdgSurface);
+
+ QTRY_VERIFY(xdgSurface);
+ QTRY_VERIFY(!xdgSurface->activated());
+ QTRY_VERIFY(!xdgSurface->maximized());
+ QTRY_VERIFY(!xdgSurface->fullscreen());
+ QTRY_VERIFY(!xdgSurface->resizing());
+
+ xdgSurface->sendConfigure(QSize(10, 20), QVector<QWaylandXdgSurface::State>{QWaylandXdgSurface::State::ActivatedState});
+ compositor.flushClients();
+ QTRY_COMPARE(mockXdgSurface.configureStates, QList<uint>{QWaylandXdgSurface::State::ActivatedState});
+ QTRY_COMPARE(mockXdgSurface.configureSize, QSize(10, 20));
+
+ xdgSurface->sendMaximized(QSize(800, 600));
+ compositor.flushClients();
+ QTRY_VERIFY(mockXdgSurface.configureStates.contains(QWaylandXdgSurface::State::MaximizedState));
+ QTRY_VERIFY(mockXdgSurface.configureStates.contains(QWaylandXdgSurface::State::ActivatedState));
+ QTRY_COMPARE(mockXdgSurface.configureSize, QSize(800, 600));
+
+ // There hasn't been any ack_configures, so state should still be unchanged
+ QTRY_VERIFY(!xdgSurface->activated());
+ QTRY_VERIFY(!xdgSurface->maximized());
+ xdg_surface_ack_configure(clientXdgSurface, mockXdgSurface.configureSerial);
+ wl_display_dispatch_pending(client.display);
+ wl_display_flush(client.display);
+ QTRY_VERIFY(xdgSurface->activated());
+ QTRY_VERIFY(xdgSurface->maximized());
+
+ xdgSurface->sendUnmaximized();
+ compositor.flushClients();
+ QTRY_VERIFY(!mockXdgSurface.configureStates.contains(QWaylandXdgSurface::State::MaximizedState));
+ QTRY_VERIFY(mockXdgSurface.configureStates.contains(QWaylandXdgSurface::State::ActivatedState));
+ QTRY_COMPARE(mockXdgSurface.configureSize, QSize(0, 0));
+
+ // The unmaximized configure hasn't been acked, so maximized should still be true
+ QTRY_VERIFY(xdgSurface->maximized());
+ QTRY_VERIFY(xdgSurface->activated());
+
+ xdgSurface->sendResizing(QSize(800, 600));
+ compositor.flushClients();
+ QTRY_VERIFY(mockXdgSurface.configureStates.contains(QWaylandXdgSurface::State::ResizingState));
+ QTRY_COMPARE(mockXdgSurface.configureSize, QSize(800, 600));
+
+ xdgSurface->sendFullscreen(QSize(1024, 768));
+ compositor.flushClients();
+ QTRY_VERIFY(mockXdgSurface.configureStates.contains(QWaylandXdgSurface::State::ActivatedState));
+ QTRY_VERIFY(mockXdgSurface.configureStates.contains(QWaylandXdgSurface::State::FullscreenState));
+ QTRY_COMPARE(mockXdgSurface.configureSize, QSize(1024, 768));
+ uint fullscreenSerial = mockXdgSurface.configureSerial;
+
+ xdgSurface->sendUnmaximized();
+ compositor.flushClients();
+ QTRY_VERIFY(mockXdgSurface.configureStates.contains(QWaylandXdgSurface::State::ActivatedState));
+ QTRY_VERIFY(!mockXdgSurface.configureStates.contains(QWaylandXdgSurface::State::FullscreenState));
+
+ xdgSurface->sendConfigure(QSize(0, 0), QVector<QWaylandXdgSurface::State>{});
+ compositor.flushClients();
+ QTRY_VERIFY(!mockXdgSurface.configureStates.contains(QWaylandXdgSurface::State::ActivatedState));
+
+ xdgSurface->sendMaximized(QSize(800, 600));
+ compositor.flushClients();
+ QTRY_VERIFY(!mockXdgSurface.configureStates.contains(QWaylandXdgSurface::State::ActivatedState));
+
+ xdgSurface->sendFullscreen(QSize(800, 600));
+ compositor.flushClients();
+ QTRY_VERIFY(!mockXdgSurface.configureStates.contains(QWaylandXdgSurface::State::MaximizedState));
+
+ // Verify that acking a configure that's not the most recently sent works
+ xdg_surface_ack_configure(clientXdgSurface, fullscreenSerial);
+ wl_display_dispatch_pending(client.display);
+ wl_display_flush(client.display);
+ QTRY_VERIFY(xdgSurface->fullscreen());
+ QTRY_VERIFY(xdgSurface->activated());
+ QTRY_VERIFY(!xdgSurface->maximized());
+ QTRY_VERIFY(!xdgSurface->resizing());
+}
+
+#include <tst_compositor.moc>
+QTEST_MAIN(tst_WaylandCompositor);