/**************************************************************************** ** ** 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 #include #include #include #include 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 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(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(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(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(compositor.defaultSeat()); // The compositor will create the default input device QTRY_COMPARE(compositor.defaultSeat(), dev); QList 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) {} QWaylandXdgShellV5 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, &QWaylandXdgShellV5::xdgSurfaceCreated); QWaylandXdgSurfaceV5 *xdgSurface = nullptr; QObject::connect(&compositor.xdgShell, &QWaylandXdgShellV5::xdgSurfaceCreated, [&](QWaylandXdgSurfaceV5 *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(); QWaylandXdgSurfaceV5 *xdgSurface = nullptr; QObject::connect(&compositor.xdgShell, &QWaylandXdgShellV5::xdgSurfaceCreated, [&](QWaylandXdgSurfaceV5 *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(); QWaylandXdgSurfaceV5 *xdgSurface = nullptr; QObject::connect(&compositor.xdgShell, &QWaylandXdgShellV5::xdgSurfaceCreated, [&](QWaylandXdgSurfaceV5 *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(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 configureStates; QSize configureSize; uint configureSerial; }; XdgTestCompositor compositor; compositor.create(); QWaylandXdgSurfaceV5 *xdgSurface = nullptr; QObject::connect(&compositor.xdgShell, &QWaylandXdgShellV5::xdgSurfaceCreated, [&](QWaylandXdgSurfaceV5 *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{QWaylandXdgSurfaceV5::State::ActivatedState}); compositor.flushClients(); QTRY_COMPARE(mockXdgSurface.configureStates, QList{QWaylandXdgSurfaceV5::State::ActivatedState}); QTRY_COMPARE(mockXdgSurface.configureSize, QSize(10, 20)); xdgSurface->sendMaximized(QSize(800, 600)); compositor.flushClients(); QTRY_VERIFY(mockXdgSurface.configureStates.contains(QWaylandXdgSurfaceV5::State::MaximizedState)); QTRY_VERIFY(mockXdgSurface.configureStates.contains(QWaylandXdgSurfaceV5::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(QWaylandXdgSurfaceV5::State::MaximizedState)); QTRY_VERIFY(mockXdgSurface.configureStates.contains(QWaylandXdgSurfaceV5::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(QWaylandXdgSurfaceV5::State::ResizingState)); QTRY_COMPARE(mockXdgSurface.configureSize, QSize(800, 600)); xdgSurface->sendFullscreen(QSize(1024, 768)); compositor.flushClients(); QTRY_VERIFY(mockXdgSurface.configureStates.contains(QWaylandXdgSurfaceV5::State::ActivatedState)); QTRY_VERIFY(mockXdgSurface.configureStates.contains(QWaylandXdgSurfaceV5::State::FullscreenState)); QTRY_COMPARE(mockXdgSurface.configureSize, QSize(1024, 768)); uint fullscreenSerial = mockXdgSurface.configureSerial; xdgSurface->sendUnmaximized(); compositor.flushClients(); QTRY_VERIFY(mockXdgSurface.configureStates.contains(QWaylandXdgSurfaceV5::State::ActivatedState)); QTRY_VERIFY(!mockXdgSurface.configureStates.contains(QWaylandXdgSurfaceV5::State::FullscreenState)); xdgSurface->sendConfigure(QSize(0, 0), QVector{}); compositor.flushClients(); QTRY_VERIFY(!mockXdgSurface.configureStates.contains(QWaylandXdgSurfaceV5::State::ActivatedState)); xdgSurface->sendMaximized(QSize(800, 600)); compositor.flushClients(); QTRY_VERIFY(!mockXdgSurface.configureStates.contains(QWaylandXdgSurfaceV5::State::ActivatedState)); xdgSurface->sendFullscreen(QSize(800, 600)); compositor.flushClients(); QTRY_VERIFY(!mockXdgSurface.configureStates.contains(QWaylandXdgSurfaceV5::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 QTEST_MAIN(tst_WaylandCompositor);