diff --git a/autotests/client/test_wayland_surface.cpp b/autotests/client/test_wayland_surface.cpp index d669ec7..957323b 100644 --- a/autotests/client/test_wayland_surface.cpp +++ b/autotests/client/test_wayland_surface.cpp @@ -1,1210 +1,1216 @@ /******************************************************************** Copyright 2014 Martin Gräßlin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ // Qt #include #include #include // KWin #include "../../src/client/compositor.h" #include "../../src/client/connection_thread.h" #include "../../src/client/event_queue.h" #include "../../src/client/idleinhibit.h" #include "../../src/client/output.h" #include "../../src/client/surface.h" #include "../../src/client/region.h" #include "../../src/client/registry.h" #include "../../src/client/shm_pool.h" #include "../../src/server/buffer_interface.h" #include "../../src/server/compositor_interface.h" #include "../../src/server/display.h" #include "../../src/server/idleinhibit_interface.h" #include "../../src/server/surface_interface.h" // Wayland #include using KWayland::Client::Registry; class TestWaylandSurface : public QObject { Q_OBJECT public: explicit TestWaylandSurface(QObject *parent = nullptr); private Q_SLOTS: void init(); void cleanup(); void testStaticAccessor(); void testDamage(); void testFrameCallback(); void testAttachBuffer(); void testMultipleSurfaces(); void testOpaque(); void testInput(); void testScale(); void testDestroy(); void testUnmapOfNotMappedSurface(); void testDamageTracking(); void testSurfaceAt(); void testDestroyAttachedBuffer(); void testDestroyWithPendingCallback(); void testOutput(); void testDisconnect(); void testInhibit(); private: KWayland::Server::Display *m_display; KWayland::Server::CompositorInterface *m_compositorInterface; KWayland::Server::IdleInhibitManagerInterface *m_idleInhibitInterface; KWayland::Client::ConnectionThread *m_connection; KWayland::Client::Compositor *m_compositor; KWayland::Client::ShmPool *m_shm; KWayland::Client::EventQueue *m_queue; KWayland::Client::IdleInhibitManager *m_idleInhibitManager; QThread *m_thread; }; static const QString s_socketName = QStringLiteral("kwin-test-wayland-surface-0"); TestWaylandSurface::TestWaylandSurface(QObject *parent) : QObject(parent) , m_display(nullptr) , m_compositorInterface(nullptr) , m_connection(nullptr) , m_compositor(nullptr) , m_thread(nullptr) { } void TestWaylandSurface::init() { using namespace KWayland::Server; delete m_display; m_display = new Display(this); m_display->setSocketName(s_socketName); m_display->start(); QVERIFY(m_display->isRunning()); m_display->createShm(); m_compositorInterface = m_display->createCompositor(m_display); QVERIFY(m_compositorInterface); m_compositorInterface->create(); QVERIFY(m_compositorInterface->isValid()); m_idleInhibitInterface = m_display->createIdleInhibitManager(IdleInhibitManagerInterfaceVersion::UnstableV1, m_display); QVERIFY(m_idleInhibitInterface); m_idleInhibitInterface->create(); QVERIFY(m_idleInhibitInterface->isValid()); // setup connection m_connection = new KWayland::Client::ConnectionThread; QSignalSpy connectedSpy(m_connection, SIGNAL(connected())); m_connection->setSocketName(s_socketName); m_thread = new QThread(this); m_connection->moveToThread(m_thread); m_thread->start(); /*connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, m_connection, [this]() { if (m_connection->display()) { wl_display_flush(m_connection->display()); } } );*/ m_connection->initConnection(); QVERIFY(connectedSpy.wait()); m_queue = new KWayland::Client::EventQueue(this); QVERIFY(!m_queue->isValid()); m_queue->setup(m_connection); QVERIFY(m_queue->isValid()); KWayland::Client::Registry registry; registry.setEventQueue(m_queue); QSignalSpy compositorSpy(®istry, SIGNAL(compositorAnnounced(quint32,quint32))); QSignalSpy shmSpy(®istry, SIGNAL(shmAnnounced(quint32,quint32))); QSignalSpy allAnnounced(®istry, SIGNAL(interfacesAnnounced())); QVERIFY(allAnnounced.isValid()); QVERIFY(shmSpy.isValid()); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); QVERIFY(allAnnounced.wait()); QVERIFY(!compositorSpy.isEmpty()); QVERIFY(!shmSpy.isEmpty()); m_compositor = registry.createCompositor(compositorSpy.first().first().value(), compositorSpy.first().last().value(), this); QVERIFY(m_compositor->isValid()); m_shm = registry.createShmPool(shmSpy.first().first().value(), shmSpy.first().last().value(), this); QVERIFY(m_shm->isValid()); m_idleInhibitManager = registry.createIdleInhibitManager(registry.interface(Registry::Interface::IdleInhibitManagerUnstableV1).name, registry.interface(Registry::Interface::IdleInhibitManagerUnstableV1).version, this); QVERIFY(m_idleInhibitManager->isValid()); } void TestWaylandSurface::cleanup() { if (m_compositor) { delete m_compositor; m_compositor = nullptr; } if (m_idleInhibitManager) { delete m_idleInhibitManager; m_idleInhibitManager = nullptr; } if (m_shm) { delete m_shm; m_shm = nullptr; } if (m_queue) { delete m_queue; m_queue = nullptr; } if (m_thread) { m_thread->quit(); m_thread->wait(); delete m_thread; m_thread = nullptr; } delete m_connection; m_connection = nullptr; delete m_compositorInterface; m_compositorInterface = nullptr; delete m_idleInhibitInterface; m_idleInhibitInterface = nullptr; delete m_display; m_display = nullptr; } void TestWaylandSurface::testStaticAccessor() { QSignalSpy serverSurfaceCreated(m_compositorInterface, SIGNAL(surfaceCreated(KWayland::Server::SurfaceInterface*))); QVERIFY(serverSurfaceCreated.isValid()); QVERIFY(!KWayland::Server::SurfaceInterface::get(nullptr)); QVERIFY(!KWayland::Server::SurfaceInterface::get(1, nullptr)); QVERIFY(KWayland::Client::Surface::all().isEmpty()); KWayland::Client::Surface *s1 = m_compositor->createSurface(); QVERIFY(s1->isValid()); QCOMPARE(KWayland::Client::Surface::all().count(), 1); QCOMPARE(KWayland::Client::Surface::all().first(), s1); QCOMPARE(KWayland::Client::Surface::get(*s1), s1); QVERIFY(serverSurfaceCreated.wait()); auto serverSurface1 = serverSurfaceCreated.first().first().value(); QVERIFY(serverSurface1); QCOMPARE(KWayland::Server::SurfaceInterface::get(serverSurface1->resource()), serverSurface1); QCOMPARE(KWayland::Server::SurfaceInterface::get(serverSurface1->id(), serverSurface1->client()), serverSurface1); QVERIFY(!s1->size().isValid()); QSignalSpy sizeChangedSpy(s1, SIGNAL(sizeChanged(QSize))); QVERIFY(sizeChangedSpy.isValid()); const QSize testSize(200, 300); s1->setSize(testSize); QCOMPARE(s1->size(), testSize); QCOMPARE(sizeChangedSpy.count(), 1); QCOMPARE(sizeChangedSpy.first().first().toSize(), testSize); // add another surface KWayland::Client::Surface *s2 = m_compositor->createSurface(); QVERIFY(s2->isValid()); QCOMPARE(KWayland::Client::Surface::all().count(), 2); QCOMPARE(KWayland::Client::Surface::all().first(), s1); QCOMPARE(KWayland::Client::Surface::all().last(), s2); QCOMPARE(KWayland::Client::Surface::get(*s1), s1); QCOMPARE(KWayland::Client::Surface::get(*s2), s2); serverSurfaceCreated.clear(); QVERIFY(serverSurfaceCreated.wait()); auto serverSurface2 = serverSurfaceCreated.first().first().value(); QVERIFY(serverSurface2); QCOMPARE(KWayland::Server::SurfaceInterface::get(serverSurface1->resource()), serverSurface1); QCOMPARE(KWayland::Server::SurfaceInterface::get(serverSurface1->id(), serverSurface1->client()), serverSurface1); QCOMPARE(KWayland::Server::SurfaceInterface::get(serverSurface2->resource()), serverSurface2); QCOMPARE(KWayland::Server::SurfaceInterface::get(serverSurface2->id(), serverSurface2->client()), serverSurface2); // delete s2 again delete s2; QCOMPARE(KWayland::Client::Surface::all().count(), 1); QCOMPARE(KWayland::Client::Surface::all().first(), s1); QCOMPARE(KWayland::Client::Surface::get(*s1), s1); // and finally delete the last one delete s1; QVERIFY(KWayland::Client::Surface::all().isEmpty()); QVERIFY(!KWayland::Client::Surface::get(nullptr)); QSignalSpy unboundSpy(serverSurface1, &KWayland::Server::Resource::unbound); QVERIFY(unboundSpy.isValid()); QVERIFY(unboundSpy.wait()); QVERIFY(!KWayland::Server::SurfaceInterface::get(nullptr)); QVERIFY(!KWayland::Server::SurfaceInterface::get(1, nullptr)); } void TestWaylandSurface::testDamage() { QSignalSpy serverSurfaceCreated(m_compositorInterface, SIGNAL(surfaceCreated(KWayland::Server::SurfaceInterface*))); QVERIFY(serverSurfaceCreated.isValid()); KWayland::Client::Surface *s = m_compositor->createSurface(); s->setScale(2); QVERIFY(serverSurfaceCreated.wait()); KWayland::Server::SurfaceInterface *serverSurface = serverSurfaceCreated.first().first().value(); QVERIFY(serverSurface); QCOMPARE(serverSurface->damage(), QRegion()); QVERIFY(serverSurface->parentResource()); QVERIFY(!serverSurface->isMapped()); QSignalSpy committedSpy(serverSurface, SIGNAL(committed())); QSignalSpy damageSpy(serverSurface, SIGNAL(damaged(QRegion))); QVERIFY(damageSpy.isValid()); // send damage without a buffer s->damage(QRect(0, 0, 100, 100)); s->commit(KWayland::Client::Surface::CommitFlag::None); wl_display_flush(m_connection->display()); QCoreApplication::processEvents(); QCoreApplication::processEvents(); QVERIFY(damageSpy.isEmpty()); QVERIFY(!serverSurface->isMapped()); QCOMPARE(committedSpy.count(), 1); QImage img(QSize(10, 10), QImage::Format_ARGB32_Premultiplied); img.fill(Qt::black); auto b = m_shm->createBuffer(img); s->attachBuffer(b); s->damage(QRect(0, 0, 10, 10)); s->commit(KWayland::Client::Surface::CommitFlag::None); QVERIFY(damageSpy.wait()); QCOMPARE(serverSurface->damage(), QRegion(0, 0, 5, 5)); // scale is 2 QCOMPARE(damageSpy.first().first().value(), QRegion(0, 0, 5, 5)); QVERIFY(serverSurface->isMapped()); QCOMPARE(committedSpy.count(), 2); // damage multiple times QRegion testRegion(5, 8, 3, 6); testRegion = testRegion.united(QRect(10, 11, 6, 1)); img = QImage(QSize(40, 35), QImage::Format_ARGB32_Premultiplied); img.fill(Qt::black); b = m_shm->createBuffer(img); s->attachBuffer(b); s->damage(testRegion); damageSpy.clear(); s->commit(KWayland::Client::Surface::CommitFlag::None); QVERIFY(damageSpy.wait()); QCOMPARE(serverSurface->damage(), testRegion); QCOMPARE(damageSpy.first().first().value(), testRegion); QVERIFY(serverSurface->isMapped()); QCOMPARE(committedSpy.count(), 3); // damage buffer const QRegion testRegion2(30, 40, 22, 4); const QRegion cmpRegion2(15, 20, 11, 2); // divided by scale factor img = QImage(QSize(80, 70), QImage::Format_ARGB32_Premultiplied); img.fill(Qt::black); b = m_shm->createBuffer(img); s->attachBuffer(b); s->damageBuffer(testRegion2); damageSpy.clear(); s->commit(KWayland::Client::Surface::CommitFlag::None); QVERIFY(damageSpy.wait()); QCOMPARE(serverSurface->damage(), cmpRegion2); QCOMPARE(damageSpy.first().first().value(), cmpRegion2); QVERIFY(serverSurface->isMapped()); // combined regular damage and damaged buffer const QRegion testRegion3 = testRegion.united(cmpRegion2); img = QImage(QSize(80, 70), QImage::Format_ARGB32_Premultiplied); img.fill(Qt::black); b = m_shm->createBuffer(img); s->attachBuffer(b); s->damage(testRegion); s->damageBuffer(testRegion2); damageSpy.clear(); s->commit(KWayland::Client::Surface::CommitFlag::None); QVERIFY(damageSpy.wait()); QVERIFY(serverSurface->damage() != testRegion); QVERIFY(serverSurface->damage() != testRegion2); QVERIFY(serverSurface->damage() != cmpRegion2); QCOMPARE(serverSurface->damage(), testRegion3); QCOMPARE(damageSpy.first().first().value(), testRegion3); QVERIFY(serverSurface->isMapped()); } void TestWaylandSurface::testFrameCallback() { QSignalSpy serverSurfaceCreated(m_compositorInterface, SIGNAL(surfaceCreated(KWayland::Server::SurfaceInterface*))); QVERIFY(serverSurfaceCreated.isValid()); KWayland::Client::Surface *s = m_compositor->createSurface(); QVERIFY(serverSurfaceCreated.wait()); KWayland::Server::SurfaceInterface *serverSurface = serverSurfaceCreated.first().first().value(); QVERIFY(serverSurface); QSignalSpy damageSpy(serverSurface, SIGNAL(damaged(QRegion))); QVERIFY(damageSpy.isValid()); QSignalSpy frameRenderedSpy(s, SIGNAL(frameRendered())); QVERIFY(frameRenderedSpy.isValid()); QImage img(QSize(10, 10), QImage::Format_ARGB32_Premultiplied); img.fill(Qt::black); auto b = m_shm->createBuffer(img); s->attachBuffer(b); s->damage(QRect(0, 0, 10, 10)); s->commit(); QVERIFY(damageSpy.wait()); serverSurface->frameRendered(10); QVERIFY(frameRenderedSpy.isEmpty()); QVERIFY(frameRenderedSpy.wait()); QVERIFY(!frameRenderedSpy.isEmpty()); } void TestWaylandSurface::testAttachBuffer() { // create the surface QSignalSpy serverSurfaceCreated(m_compositorInterface, SIGNAL(surfaceCreated(KWayland::Server::SurfaceInterface*))); QVERIFY(serverSurfaceCreated.isValid()); KWayland::Client::Surface *s = m_compositor->createSurface(); QVERIFY(serverSurfaceCreated.wait()); KWayland::Server::SurfaceInterface *serverSurface = serverSurfaceCreated.first().first().value(); QVERIFY(serverSurface); // create three images QImage black(24, 24, QImage::Format_RGB32); black.fill(Qt::black); QImage red(24, 24, QImage::Format_ARGB32); //Note - deliberately not premultiplied red.fill(QColor(255, 0, 0, 128)); QImage blue(24, 24, QImage::Format_ARGB32_Premultiplied); blue.fill(QColor(0, 0, 255, 128)); - wl_buffer *blackBuffer = *(m_shm->createBuffer(black).data()); - auto redBuffer = m_shm->createBuffer(red); - auto blueBuffer = m_shm->createBuffer(blue).toStrongRef(); + QSharedPointer blackBufferPtr = m_shm->createBuffer(black).toStrongRef(); + QVERIFY(blackBufferPtr); + wl_buffer *blackBuffer = *(blackBufferPtr.data()); + QSharedPointer redBuffer = m_shm->createBuffer(red).toStrongRef(); + QVERIFY(redBuffer); + QSharedPointer blueBuffer = m_shm->createBuffer(blue).toStrongRef(); + QVERIFY(blueBuffer); QCOMPARE(blueBuffer->format(), KWayland::Client::Buffer::Format::ARGB32); QCOMPARE(blueBuffer->size(), blue.size()); QVERIFY(!blueBuffer->isReleased()); QVERIFY(!blueBuffer->isUsed()); QCOMPARE(blueBuffer->stride(), blue.bytesPerLine()); s->attachBuffer(redBuffer.data()); s->attachBuffer(blackBuffer); s->damage(QRect(0, 0, 24, 24)); s->commit(KWayland::Client::Surface::CommitFlag::None); QSignalSpy damageSpy(serverSurface, SIGNAL(damaged(QRegion))); QVERIFY(damageSpy.isValid()); QSignalSpy unmappedSpy(serverSurface, SIGNAL(unmapped())); QVERIFY(unmappedSpy.isValid()); QVERIFY(damageSpy.wait()); QVERIFY(unmappedSpy.isEmpty()); // now the ServerSurface should have the black image attached as a buffer KWayland::Server::BufferInterface *buffer = serverSurface->buffer(); buffer->ref(); QVERIFY(buffer->shmBuffer()); QCOMPARE(buffer->data(), black); QCOMPARE(buffer->data().format(), QImage::Format_RGB32); // render another frame s->attachBuffer(redBuffer); s->damage(QRect(0, 0, 24, 24)); s->commit(KWayland::Client::Surface::CommitFlag::None); damageSpy.clear(); QVERIFY(damageSpy.wait()); QVERIFY(unmappedSpy.isEmpty()); KWayland::Server::BufferInterface *buffer2 = serverSurface->buffer(); buffer2->ref(); QVERIFY(buffer2->shmBuffer()); QCOMPARE(buffer2->data().format(), QImage::Format_ARGB32_Premultiplied); QCOMPARE(buffer2->data().width(), 24); QCOMPARE(buffer2->data().height(), 24); for (int i = 0; i < 24; ++i) { for (int j = 0; j < 24; ++j) { // it's premultiplied in the format QCOMPARE(buffer2->data().pixel(i, j), qRgba(128, 0, 0, 128)); } } buffer2->unref(); QVERIFY(buffer2->isReferenced()); QVERIFY(!redBuffer.data()->isReleased()); // render another frame blueBuffer->setUsed(true); QVERIFY(blueBuffer->isUsed()); s->attachBuffer(blueBuffer.data()); s->damage(QRect(0, 0, 24, 24)); QSignalSpy frameRenderedSpy(s, SIGNAL(frameRendered())); QVERIFY(frameRenderedSpy.isValid()); s->commit(); damageSpy.clear(); QVERIFY(damageSpy.wait()); QVERIFY(unmappedSpy.isEmpty()); QVERIFY(!buffer2->isReferenced()); delete buffer2; // TODO: we should have a signal on when the Buffer gets released QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents); if (!redBuffer.data()->isReleased()) { QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents); } QVERIFY(redBuffer.data()->isReleased()); KWayland::Server::BufferInterface *buffer3 = serverSurface->buffer(); buffer3->ref(); QVERIFY(buffer3->shmBuffer()); QCOMPARE(buffer3->data().format(), QImage::Format_ARGB32_Premultiplied); QCOMPARE(buffer3->data().width(), 24); QCOMPARE(buffer3->data().height(), 24); for (int i = 0; i < 24; ++i) { for (int j = 0; j < 24; ++j) { // it's premultiplied in the format QCOMPARE(buffer3->data().pixel(i, j), qRgba(0, 0, 128, 128)); } } buffer3->unref(); QVERIFY(buffer3->isReferenced()); serverSurface->frameRendered(1); QVERIFY(frameRenderedSpy.wait()); // commit a different value shouldn't change our buffer QCOMPARE(serverSurface->buffer(), buffer3); QVERIFY(serverSurface->input().isNull()); damageSpy.clear(); s->setInputRegion(m_compositor->createRegion(QRegion(0, 0, 24, 24)).get()); s->commit(KWayland::Client::Surface::CommitFlag::None); wl_display_flush(m_connection->display()); QCoreApplication::processEvents(); QCoreApplication::processEvents(); QCOMPARE(serverSurface->input(), QRegion(0, 0, 24, 24)); QCOMPARE(serverSurface->buffer(), buffer3); QVERIFY(damageSpy.isEmpty()); QVERIFY(unmappedSpy.isEmpty()); QVERIFY(serverSurface->isMapped()); // clear the surface s->attachBuffer(blackBuffer); s->damage(QRect(0, 0, 1, 1)); // TODO: better method s->attachBuffer((wl_buffer*)nullptr); s->damage(QRect(0, 0, 10, 10)); s->commit(KWayland::Client::Surface::CommitFlag::None); QVERIFY(unmappedSpy.wait()); QVERIFY(!unmappedSpy.isEmpty()); QCOMPARE(unmappedSpy.count(), 1); QVERIFY(damageSpy.isEmpty()); QVERIFY(!serverSurface->isMapped()); // TODO: add signal test on release buffer->unref(); } void TestWaylandSurface::testMultipleSurfaces() { using namespace KWayland::Client; using namespace KWayland::Server; Registry registry; QSignalSpy shmSpy(®istry, SIGNAL(shmAnnounced(quint32,quint32))); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); QVERIFY(shmSpy.wait()); ShmPool pool1; ShmPool pool2; pool1.setup(registry.bindShm(shmSpy.first().first().value(), shmSpy.first().last().value())); pool2.setup(registry.bindShm(shmSpy.first().first().value(), shmSpy.first().last().value())); QVERIFY(pool1.isValid()); QVERIFY(pool2.isValid()); // create the surfaces QSignalSpy serverSurfaceCreated(m_compositorInterface, SIGNAL(surfaceCreated(KWayland::Server::SurfaceInterface*))); QVERIFY(serverSurfaceCreated.isValid()); QScopedPointer s1(m_compositor->createSurface()); QVERIFY(serverSurfaceCreated.wait()); SurfaceInterface *serverSurface1 = serverSurfaceCreated.first().first().value(); QVERIFY(serverSurface1); //second surface QScopedPointer s2(m_compositor->createSurface()); QVERIFY(serverSurfaceCreated.wait()); SurfaceInterface *serverSurface2 = serverSurfaceCreated.last().first().value(); QVERIFY(serverSurface2); QVERIFY(serverSurface1->resource() != serverSurface2->resource()); // create two images QImage black(24, 24, QImage::Format_RGB32); black.fill(Qt::black); QImage red(24, 24, QImage::Format_ARGB32_Premultiplied); red.fill(QColor(255, 0, 0, 128)); auto blackBuffer = pool1.createBuffer(black); auto redBuffer = pool2.createBuffer(red); s1->attachBuffer(blackBuffer); s1->damage(QRect(0, 0, 24, 24)); s1->commit(Surface::CommitFlag::None); QSignalSpy damageSpy1(serverSurface1, SIGNAL(damaged(QRegion))); QVERIFY(damageSpy1.isValid()); QVERIFY(damageSpy1.wait()); // now the ServerSurface should have the black image attached as a buffer BufferInterface *buffer1 = serverSurface1->buffer(); QVERIFY(buffer1); QImage buffer1Data = buffer1->data(); QCOMPARE(buffer1Data, black); // accessing the same buffer is OK QImage buffer1Data2 = buffer1->data(); QCOMPARE(buffer1Data2, buffer1Data); buffer1Data = QImage(); QVERIFY(buffer1Data.isNull()); buffer1Data2 = QImage(); QVERIFY(buffer1Data2.isNull()); // attach a buffer for the other surface s2->attachBuffer(redBuffer); s2->damage(QRect(0, 0, 24, 24)); s2->commit(Surface::CommitFlag::None); QSignalSpy damageSpy2(serverSurface2, SIGNAL(damaged(QRegion))); QVERIFY(damageSpy2.isValid()); QVERIFY(damageSpy2.wait()); BufferInterface *buffer2 = serverSurface2->buffer(); QVERIFY(buffer2); QImage buffer2Data = buffer2->data(); QCOMPARE(buffer2Data, red); // while buffer2 is accessed we cannot access buffer1 buffer1Data = buffer1->data(); QVERIFY(buffer1Data.isNull()); // a deep copy can be kept around QImage deepCopy = buffer2Data.copy(); QCOMPARE(deepCopy, red); buffer2Data = QImage(); QVERIFY(buffer2Data.isNull()); QCOMPARE(deepCopy, red); // now that buffer2Data is destroyed we can access buffer1 again buffer1Data = buffer1->data(); QVERIFY(!buffer1Data.isNull()); QCOMPARE(buffer1Data, black); } void TestWaylandSurface::testOpaque() { using namespace KWayland::Client; using namespace KWayland::Server; QSignalSpy serverSurfaceCreated(m_compositorInterface, SIGNAL(surfaceCreated(KWayland::Server::SurfaceInterface*))); QVERIFY(serverSurfaceCreated.isValid()); Surface *s = m_compositor->createSurface(); QVERIFY(serverSurfaceCreated.wait()); SurfaceInterface *serverSurface = serverSurfaceCreated.first().first().value(); QVERIFY(serverSurface); QSignalSpy opaqueRegionChangedSpy(serverSurface, SIGNAL(opaqueChanged(QRegion))); QVERIFY(opaqueRegionChangedSpy.isValid()); // by default there should be an empty opaque region QCOMPARE(serverSurface->opaque(), QRegion()); // let's install an opaque region s->setOpaqueRegion(m_compositor->createRegion(QRegion(0, 10, 20, 30)).get()); // the region should only be applied after the surface got committed wl_display_flush(m_connection->display()); QCoreApplication::processEvents(); QCOMPARE(serverSurface->opaque(), QRegion()); QCOMPARE(opaqueRegionChangedSpy.count(), 0); // so let's commit to get the new region s->commit(Surface::CommitFlag::None); QVERIFY(opaqueRegionChangedSpy.wait()); QCOMPARE(opaqueRegionChangedSpy.count(), 1); QCOMPARE(opaqueRegionChangedSpy.last().first().value(), QRegion(0, 10, 20, 30)); QCOMPARE(serverSurface->opaque(), QRegion(0, 10, 20, 30)); // committing without setting a new region shouldn't change s->commit(Surface::CommitFlag::None); wl_display_flush(m_connection->display()); QCoreApplication::processEvents(); QCOMPARE(opaqueRegionChangedSpy.count(), 1); QCOMPARE(serverSurface->opaque(), QRegion(0, 10, 20, 30)); // let's change the opaque region s->setOpaqueRegion(m_compositor->createRegion(QRegion(10, 20, 30, 40)).get()); s->commit(Surface::CommitFlag::None); QVERIFY(opaqueRegionChangedSpy.wait()); QCOMPARE(opaqueRegionChangedSpy.count(), 2); QCOMPARE(opaqueRegionChangedSpy.last().first().value(), QRegion(10, 20, 30, 40)); QCOMPARE(serverSurface->opaque(), QRegion(10, 20, 30, 40)); // and let's go back to an empty region s->setOpaqueRegion(); s->commit(Surface::CommitFlag::None); QVERIFY(opaqueRegionChangedSpy.wait()); QCOMPARE(opaqueRegionChangedSpy.count(), 3); QCOMPARE(opaqueRegionChangedSpy.last().first().value(), QRegion()); QCOMPARE(serverSurface->opaque(), QRegion()); } void TestWaylandSurface::testInput() { using namespace KWayland::Client; using namespace KWayland::Server; QSignalSpy serverSurfaceCreated(m_compositorInterface, SIGNAL(surfaceCreated(KWayland::Server::SurfaceInterface*))); QVERIFY(serverSurfaceCreated.isValid()); Surface *s = m_compositor->createSurface(); QVERIFY(serverSurfaceCreated.wait()); SurfaceInterface *serverSurface = serverSurfaceCreated.first().first().value(); QVERIFY(serverSurface); QSignalSpy inputRegionChangedSpy(serverSurface, SIGNAL(inputChanged(QRegion))); QVERIFY(inputRegionChangedSpy.isValid()); // by default there should be an empty == infinite input region QCOMPARE(serverSurface->input(), QRegion()); QCOMPARE(serverSurface->inputIsInfinite(), true); // let's install an input region s->setInputRegion(m_compositor->createRegion(QRegion(0, 10, 20, 30)).get()); // the region should only be applied after the surface got committed wl_display_flush(m_connection->display()); QCoreApplication::processEvents(); QCOMPARE(serverSurface->input(), QRegion()); QCOMPARE(serverSurface->inputIsInfinite(), true); QCOMPARE(inputRegionChangedSpy.count(), 0); // so let's commit to get the new region s->commit(Surface::CommitFlag::None); QVERIFY(inputRegionChangedSpy.wait()); QCOMPARE(inputRegionChangedSpy.count(), 1); QCOMPARE(inputRegionChangedSpy.last().first().value(), QRegion(0, 10, 20, 30)); QCOMPARE(serverSurface->input(), QRegion(0, 10, 20, 30)); QCOMPARE(serverSurface->inputIsInfinite(), false); // committing without setting a new region shouldn't change s->commit(Surface::CommitFlag::None); wl_display_flush(m_connection->display()); QCoreApplication::processEvents(); QCOMPARE(inputRegionChangedSpy.count(), 1); QCOMPARE(serverSurface->input(), QRegion(0, 10, 20, 30)); QCOMPARE(serverSurface->inputIsInfinite(), false); // let's change the input region s->setInputRegion(m_compositor->createRegion(QRegion(10, 20, 30, 40)).get()); s->commit(Surface::CommitFlag::None); QVERIFY(inputRegionChangedSpy.wait()); QCOMPARE(inputRegionChangedSpy.count(), 2); QCOMPARE(inputRegionChangedSpy.last().first().value(), QRegion(10, 20, 30, 40)); QCOMPARE(serverSurface->input(), QRegion(10, 20, 30, 40)); QCOMPARE(serverSurface->inputIsInfinite(), false); // and let's go back to an empty region s->setInputRegion(); s->commit(Surface::CommitFlag::None); QVERIFY(inputRegionChangedSpy.wait()); QCOMPARE(inputRegionChangedSpy.count(), 3); QCOMPARE(inputRegionChangedSpy.last().first().value(), QRegion()); QCOMPARE(serverSurface->input(), QRegion()); QCOMPARE(serverSurface->inputIsInfinite(), true); } void TestWaylandSurface::testScale() { // this test verifies that updating the scale factor is correctly passed to the Wayland server using namespace KWayland::Client; using namespace KWayland::Server; // create surface QSignalSpy serverSurfaceCreated(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(serverSurfaceCreated.isValid()); QScopedPointer s(m_compositor->createSurface()); QCOMPARE(s->scale(), 1); QVERIFY(serverSurfaceCreated.wait()); SurfaceInterface *serverSurface = serverSurfaceCreated.first().first().value(); QVERIFY(serverSurface); QCOMPARE(serverSurface->scale(), 1); // let's change the scale factor QSignalSpy scaleChangedSpy(serverSurface, &SurfaceInterface::scaleChanged); //changing the scale implicitly changes the size QSignalSpy sizeChangedSpy(serverSurface, &SurfaceInterface::sizeChanged); QVERIFY(scaleChangedSpy.isValid()); s->setScale(2); QCOMPARE(s->scale(), 2); // needs a commit QVERIFY(!scaleChangedSpy.wait(100)); s->commit(Surface::CommitFlag::None); QVERIFY(scaleChangedSpy.wait()); QCOMPARE(scaleChangedSpy.count(), 1); QCOMPARE(scaleChangedSpy.first().first().toInt(), 2); QCOMPARE(serverSurface->scale(), 2); //even though we've changed the scale, if we don't have a buffer we //don't have a size. If we don't have a size it can't have changed QCOMPARE(sizeChangedSpy.count(), 0); QVERIFY(!serverSurface->size().isValid()); // let's try changing to same factor, should not emit changed on server s->setScale(2); s->commit(Surface::CommitFlag::None); QVERIFY(!scaleChangedSpy.wait(100)); // but changing to a different value should still work s->setScale(4); s->commit(Surface::CommitFlag::None); QVERIFY(scaleChangedSpy.wait()); QCOMPARE(scaleChangedSpy.count(), 2); QCOMPARE(scaleChangedSpy.first().first().toInt(), 2); QCOMPARE(scaleChangedSpy.last().first().toInt(), 4); QCOMPARE(serverSurface->scale(), 4); scaleChangedSpy.clear(); //attach a buffer of 100x100, our scale is 4, so this should be a size of 25x25 QImage red(100, 100, QImage::Format_ARGB32_Premultiplied); red.fill(QColor(255, 0, 0, 128)); - auto redBuffer = m_shm->createBuffer(red); + QSharedPointer redBuffer = m_shm->createBuffer(red).toStrongRef(); + QVERIFY(redBuffer); s->attachBuffer(redBuffer.data()); s->damage(QRect(0,0, 25,25)); s->commit(Surface::CommitFlag::None); QVERIFY(sizeChangedSpy.wait()); QCOMPARE(sizeChangedSpy.count(), 1); QCOMPARE(serverSurface->size(), QSize(25,25)); sizeChangedSpy.clear(); scaleChangedSpy.clear(); //set the scale to 1, buffer is still 100x100 so size should change to 100x100 s->setScale(1); s->commit(Surface::CommitFlag::None); QVERIFY(sizeChangedSpy.wait()); QCOMPARE(sizeChangedSpy.count(), 1); QCOMPARE(scaleChangedSpy.count(), 1); QCOMPARE(serverSurface->scale(), 1); QCOMPARE(serverSurface->size(), QSize(100,100)); sizeChangedSpy.clear(); scaleChangedSpy.clear(); //set scale and size in one commit, buffer is 50x50 at scale 2 so size should be 25x25 QImage blue(50, 50, QImage::Format_ARGB32_Premultiplied); red.fill(QColor(255, 0, 0, 128)); - auto blueBuffer = m_shm->createBuffer(blue); + QSharedPointer blueBuffer = m_shm->createBuffer(blue).toStrongRef(); + QVERIFY(blueBuffer); s->attachBuffer(blueBuffer.data()); s->setScale(2); s->commit(Surface::CommitFlag::None); QVERIFY(sizeChangedSpy.wait()); QCOMPARE(sizeChangedSpy.count(), 1); QCOMPARE(scaleChangedSpy.count(), 1); QCOMPARE(serverSurface->scale(), 2); QCOMPARE(serverSurface->size(), QSize(25,25)); } void TestWaylandSurface::testDestroy() { using namespace KWayland::Client; Surface *s = m_compositor->createSurface(); connect(m_connection, &ConnectionThread::connectionDied, s, &Surface::destroy); connect(m_connection, &ConnectionThread::connectionDied, m_compositor, &Compositor::destroy); connect(m_connection, &ConnectionThread::connectionDied, m_shm, &ShmPool::destroy); connect(m_connection, &ConnectionThread::connectionDied, m_queue, &EventQueue::destroy); connect(m_connection, &ConnectionThread::connectionDied, m_idleInhibitManager, &IdleInhibitManager::destroy); QVERIFY(s->isValid()); QSignalSpy connectionDiedSpy(m_connection, SIGNAL(connectionDied())); QVERIFY(connectionDiedSpy.isValid()); delete m_compositorInterface; m_compositorInterface = nullptr; delete m_idleInhibitInterface; m_idleInhibitInterface = nullptr; delete m_display; m_display = nullptr; QVERIFY(connectionDiedSpy.wait()); // now the Surface should be destroyed; QVERIFY(!s->isValid()); // calling destroy again should not fail s->destroy(); } void TestWaylandSurface::testUnmapOfNotMappedSurface() { // this test verifies that a surface which doesn't have a buffer attached doesn't trigger the unmapped signal using namespace KWayland::Client; using namespace KWayland::Server; // create surface QSignalSpy serverSurfaceCreated(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(serverSurfaceCreated.isValid()); QScopedPointer s(m_compositor->createSurface()); QVERIFY(serverSurfaceCreated.wait()); SurfaceInterface *serverSurface = serverSurfaceCreated.first().first().value(); QSignalSpy unmappedSpy(serverSurface, &SurfaceInterface::unmapped); QVERIFY(unmappedSpy.isValid()); QSignalSpy scaleChanged(serverSurface, &SurfaceInterface::scaleChanged); // let's map a null buffer and change scale to trigger a signal we can wait for s->attachBuffer(Buffer::Ptr()); s->setScale(2); s->commit(Surface::CommitFlag::None); QVERIFY(scaleChanged.wait()); QVERIFY(unmappedSpy.isEmpty()); } void TestWaylandSurface::testDamageTracking() { // this tests the damage tracking feature using namespace KWayland::Client; using namespace KWayland::Server; // create surface QSignalSpy serverSurfaceCreated(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(serverSurfaceCreated.isValid()); QScopedPointer s(m_compositor->createSurface()); QVERIFY(serverSurfaceCreated.wait()); SurfaceInterface *serverSurface = serverSurfaceCreated.first().first().value(); // before first commit, the tracked damage should be empty QVERIFY(serverSurface->trackedDamage().isEmpty()); // Now let's damage the surface QSignalSpy damagedSpy(serverSurface, &SurfaceInterface::damaged); QImage image(QSize(100, 100), QImage::Format_ARGB32_Premultiplied); image.fill(Qt::red); s->attachBuffer(m_shm->createBuffer(image)); s->damage(QRect(0, 0, 100, 100)); s->commit(Surface::CommitFlag::None); QVERIFY(damagedSpy.wait()); QCOMPARE(serverSurface->trackedDamage(), QRegion(0, 0, 100, 100)); QCOMPARE(serverSurface->damage(), QRegion(0, 0, 100, 100)); // resetting the tracked damage should empty it serverSurface->resetTrackedDamage(); QVERIFY(serverSurface->trackedDamage().isEmpty()); // but not affect the actual damage QCOMPARE(serverSurface->damage(), QRegion(0, 0, 100, 100)); // let's damage some parts of the surface QPainter p; p.begin(&image); p.fillRect(QRect(0, 0, 10, 10), Qt::blue); p.end(); s->attachBuffer(m_shm->createBuffer(image)); s->damage(QRect(0, 0, 10, 10)); s->commit(Surface::CommitFlag::None); QVERIFY(damagedSpy.wait()); QCOMPARE(serverSurface->trackedDamage(), QRegion(0, 0, 10, 10)); QCOMPARE(serverSurface->damage(), QRegion(0, 0, 10, 10)); // and damage some part completely not bounding to the current damage region p.begin(&image); p.fillRect(QRect(50, 40, 20, 30), Qt::blue); p.end(); s->attachBuffer(m_shm->createBuffer(image)); s->damage(QRect(50, 40, 20, 30)); s->commit(Surface::CommitFlag::None); QVERIFY(damagedSpy.wait()); QCOMPARE(serverSurface->trackedDamage(), QRegion(0, 0, 10, 10).united(QRegion(50, 40, 20, 30))); QCOMPARE(serverSurface->trackedDamage().rectCount(), 2); QCOMPARE(serverSurface->damage(), QRegion(50, 40, 20, 30)); // now let's reset the tracked damage again serverSurface->resetTrackedDamage(); QVERIFY(serverSurface->trackedDamage().isEmpty()); // but not affect the actual damage QCOMPARE(serverSurface->damage(), QRegion(50, 40, 20, 30)); } void TestWaylandSurface::testSurfaceAt() { // this test verifies that surfaceAt(const QPointF&) works as expected for the case of no children using namespace KWayland::Client; using namespace KWayland::Server; // create surface QSignalSpy serverSurfaceCreated(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(serverSurfaceCreated.isValid()); QScopedPointer s(m_compositor->createSurface()); QVERIFY(serverSurfaceCreated.wait()); SurfaceInterface *serverSurface = serverSurfaceCreated.first().first().value(); // a newly created surface should not be mapped and not provide a surface at a position QVERIFY(!serverSurface->isMapped()); QVERIFY(!serverSurface->surfaceAt(QPointF(0, 0))); // let's damage this surface QSignalSpy sizeChangedSpy(serverSurface, &SurfaceInterface::sizeChanged); QVERIFY(sizeChangedSpy.isValid()); QImage image(QSize(100, 100), QImage::Format_ARGB32_Premultiplied); image.fill(Qt::red); s->attachBuffer(m_shm->createBuffer(image)); s->damage(QRect(0, 0, 100, 100)); s->commit(Surface::CommitFlag::None); QVERIFY(sizeChangedSpy.wait()); // now the surface is mapped and surfaceAt should give the surface QVERIFY(serverSurface->isMapped()); QCOMPARE(serverSurface->surfaceAt(QPointF(0, 0)), serverSurface); QCOMPARE(serverSurface->surfaceAt(QPointF(100, 100)), serverSurface); // outside the geometry it should not give a surface QVERIFY(!serverSurface->surfaceAt(QPointF(101, 101))); QVERIFY(!serverSurface->surfaceAt(QPointF(-1, -1))); } void TestWaylandSurface::testDestroyAttachedBuffer() { // this test verifies that destroying of a buffer attached to a surface works using namespace KWayland::Client; using namespace KWayland::Server; // create surface QSignalSpy serverSurfaceCreated(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(serverSurfaceCreated.isValid()); QScopedPointer s(m_compositor->createSurface()); QVERIFY(serverSurfaceCreated.wait()); SurfaceInterface *serverSurface = serverSurfaceCreated.first().first().value(); // let's damage this surface QSignalSpy damagedSpy(serverSurface, &SurfaceInterface::damaged); QVERIFY(damagedSpy.isValid()); QImage image(QSize(100, 100), QImage::Format_ARGB32_Premultiplied); image.fill(Qt::red); s->attachBuffer(m_shm->createBuffer(image)); s->damage(QRect(0, 0, 100, 100)); s->commit(Surface::CommitFlag::None); QVERIFY(damagedSpy.wait()); QVERIFY(serverSurface->buffer()); // attach another buffer image.fill(Qt::blue); s->attachBuffer(m_shm->createBuffer(image)); m_connection->flush(); // Let's try to destroy it QSignalSpy destroySpy(serverSurface->buffer(), &BufferInterface::aboutToBeDestroyed); QVERIFY(destroySpy.isValid()); delete m_shm; m_shm = nullptr; QVERIFY(destroySpy.wait()); // TODO: should this emit unmapped? QVERIFY(!serverSurface->buffer()); } void TestWaylandSurface::testDestroyWithPendingCallback() { // this test tries to verify that destroying a surface with a pending callback works correctly // first create surface using namespace KWayland::Client; using namespace KWayland::Server; QScopedPointer s(m_compositor->createSurface()); QVERIFY(!s.isNull()); QVERIFY(s->isValid()); QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(surfaceCreatedSpy.isValid()); QVERIFY(surfaceCreatedSpy.wait()); auto serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(serverSurface); // now render to it QImage img(QSize(10, 10), QImage::Format_ARGB32_Premultiplied); img.fill(Qt::black); auto b = m_shm->createBuffer(img); s->attachBuffer(b); s->damage(QRect(0, 0, 10, 10)); // add some frame callbacks for (int i = 0; i < 1000; i++) { wl_surface_frame(*s); } s->commit(KWayland::Client::Surface::CommitFlag::FrameCallback); QSignalSpy damagedSpy(serverSurface, &SurfaceInterface::damaged); QVERIFY(damagedSpy.isValid()); QVERIFY(damagedSpy.wait()); // now try to destroy the Surface again QSignalSpy destroyedSpy(serverSurface, &QObject::destroyed); QVERIFY(destroyedSpy.isValid()); s.reset(); QVERIFY(destroyedSpy.wait()); } void TestWaylandSurface::testDisconnect() { // this test verifies that the server side correctly tears down the resources when the client disconnects using namespace KWayland::Client; using namespace KWayland::Server; QScopedPointer s(m_compositor->createSurface()); QVERIFY(!s.isNull()); QVERIFY(s->isValid()); QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(surfaceCreatedSpy.isValid()); QVERIFY(surfaceCreatedSpy.wait()); auto serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(serverSurface); // destroy client QSignalSpy clientDisconnectedSpy(serverSurface->client(), &ClientConnection::disconnected); QVERIFY(clientDisconnectedSpy.isValid()); QSignalSpy surfaceDestroyedSpy(serverSurface, &QObject::destroyed); QVERIFY(surfaceDestroyedSpy.isValid()); if (m_connection) { m_connection->deleteLater(); m_connection = nullptr; } QVERIFY(clientDisconnectedSpy.wait()); QCOMPARE(clientDisconnectedSpy.count(), 1); if (surfaceDestroyedSpy.isEmpty()) { QVERIFY(surfaceDestroyedSpy.wait()); } QTRY_COMPARE(surfaceDestroyedSpy.count(), 1); s->destroy(); m_shm->destroy(); m_compositor->destroy(); m_queue->destroy(); m_idleInhibitManager->destroy(); } void TestWaylandSurface::testOutput() { // This test verifies that the enter/leave are sent correctly to the Client using namespace KWayland::Client; using namespace KWayland::Server; qRegisterMetaType(); QScopedPointer s(m_compositor->createSurface()); QVERIFY(!s.isNull()); QVERIFY(s->isValid()); QVERIFY(s->outputs().isEmpty()); QSignalSpy enteredSpy(s.data(), &Surface::outputEntered); QVERIFY(enteredSpy.isValid()); QSignalSpy leftSpy(s.data(), &Surface::outputLeft); QVERIFY(leftSpy.isValid()); // wait for the surface on the Server side QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(surfaceCreatedSpy.isValid()); QVERIFY(surfaceCreatedSpy.wait()); auto serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(serverSurface); QCOMPARE(serverSurface->outputs(), QVector()); // create another registry to get notified about added outputs Registry registry; registry.setEventQueue(m_queue); QSignalSpy allAnnounced(®istry, &Registry::interfacesAnnounced); QVERIFY(allAnnounced.isValid()); registry.create(m_connection); QVERIFY(registry.isValid()); registry.setup(); QVERIFY(allAnnounced.wait()); QSignalSpy outputAnnouncedSpy(®istry, &Registry::outputAnnounced); QVERIFY(outputAnnouncedSpy.isValid()); auto serverOutput = m_display->createOutput(m_display); serverOutput->create(); QVERIFY(outputAnnouncedSpy.wait()); QScopedPointer clientOutput(registry.createOutput(outputAnnouncedSpy.first().first().value(), outputAnnouncedSpy.first().last().value())); QVERIFY(clientOutput->isValid()); m_connection->flush(); m_display->dispatchEvents(); // now enter it serverSurface->setOutputs(QVector{serverOutput}); QCOMPARE(serverSurface->outputs(), QVector{serverOutput}); QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.count(), 1); QCOMPARE(enteredSpy.first().first().value(), clientOutput.data()); QCOMPARE(s->outputs(), QVector{clientOutput.data()}); // adding to same should not trigger serverSurface->setOutputs(QVector{serverOutput}); // leave again serverSurface->setOutputs(QVector()); QCOMPARE(serverSurface->outputs(), QVector()); QVERIFY(leftSpy.wait()); QCOMPARE(enteredSpy.count(), 1); QCOMPARE(leftSpy.count(), 1); QCOMPARE(leftSpy.first().first().value(), clientOutput.data()); QCOMPARE(s->outputs(), QVector()); // leave again should not trigger serverSurface->setOutputs(QVector()); // and enter again, just to verify serverSurface->setOutputs(QVector{serverOutput}); QCOMPARE(serverSurface->outputs(), QVector{serverOutput}); QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.count(), 2); QCOMPARE(leftSpy.count(), 1); //delete output client is on. //client should get an exit and be left on no outputs (which is allowed) serverOutput->deleteLater(); QVERIFY(leftSpy.wait()); QCOMPARE(serverSurface->outputs(), QVector()); } void TestWaylandSurface::testInhibit() { using namespace KWayland::Client; using namespace KWayland::Server; QScopedPointer s(m_compositor->createSurface()); // wait for the surface on the Server side QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(surfaceCreatedSpy.isValid()); QVERIFY(surfaceCreatedSpy.wait()); auto serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(serverSurface); QCOMPARE(serverSurface->inhibitsIdle(), false); QSignalSpy inhibitsChangedSpy(serverSurface, &SurfaceInterface::inhibitsIdleChanged); QVERIFY(inhibitsChangedSpy.isValid()); // now create an idle inhibition QScopedPointer inhibitor1(m_idleInhibitManager->createInhibitor(s.data())); QVERIFY(inhibitsChangedSpy.wait()); QCOMPARE(serverSurface->inhibitsIdle(), true); // creating a second idle inhibition should not trigger the signal QScopedPointer inhibitor2(m_idleInhibitManager->createInhibitor(s.data())); QVERIFY(!inhibitsChangedSpy.wait(500)); QCOMPARE(serverSurface->inhibitsIdle(), true); // and also deleting the first inhibitor should not yet change the inhibition inhibitor1.reset(); QVERIFY(!inhibitsChangedSpy.wait(500)); QCOMPARE(serverSurface->inhibitsIdle(), true); // but deleting also the second inhibitor should trigger inhibitor2.reset(); QVERIFY(inhibitsChangedSpy.wait()); QCOMPARE(serverSurface->inhibitsIdle(), false); QCOMPARE(inhibitsChangedSpy.count(), 2); // recreate inhibitor1 should inhibit again inhibitor1.reset(m_idleInhibitManager->createInhibitor(s.data())); QVERIFY(inhibitsChangedSpy.wait()); QCOMPARE(serverSurface->inhibitsIdle(), true); // and destroying should uninhibit inhibitor1.reset(); QVERIFY(inhibitsChangedSpy.wait()); QCOMPARE(serverSurface->inhibitsIdle(), false); QCOMPARE(inhibitsChangedSpy.count(), 4); } QTEST_GUILESS_MAIN(TestWaylandSurface) #include "test_wayland_surface.moc" diff --git a/src/client/region.cpp b/src/client/region.cpp index 21972e6..bda5cb2 100644 --- a/src/client/region.cpp +++ b/src/client/region.cpp @@ -1,154 +1,154 @@ /******************************************************************** Copyright 2014 Martin Gräßlin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ #include "region.h" #include "wayland_pointer_p.h" // Qt #include #include // Wayland #include namespace KWayland { namespace Client { class Q_DECL_HIDDEN Region::Private { public: Private(const QRegion ®ion); void installRegion(const QRect &rect); void installRegion(const QRegion ®ion); void uninstallRegion(const QRect &rect); void uninstallRegion(const QRegion ®ion); WaylandPointer region; QRegion qtRegion; }; Region::Private::Private(const QRegion ®ion) : qtRegion(region) { } void Region::Private::installRegion(const QRect &rect) { if (!region.isValid()) { return; } wl_region_add(region, rect.x(), rect.y(), rect.width(), rect.height()); } void Region::Private::installRegion(const QRegion ®ion) { - for (const auto &rect : region.rects()) { + for (const QRect &rect : region) { installRegion(rect); } } void Region::Private::uninstallRegion(const QRect &rect) { if (!region.isValid()) { return; } wl_region_subtract(region, rect.x(), rect.y(), rect.width(), rect.height()); } void Region::Private::uninstallRegion(const QRegion ®ion) { - for (const auto &rect : region.rects()) { + for (const QRect &rect : region) { uninstallRegion(rect); } } Region::Region(const QRegion ®ion, QObject *parent) : QObject(parent) , d(new Private(region)) { } Region::~Region() { release(); } void Region::release() { d->region.release(); } void Region::destroy() { d->region.destroy(); } void Region::setup(wl_region *region) { Q_ASSERT(region); d->region.setup(region); d->installRegion(d->qtRegion); } bool Region::isValid() const { return d->region.isValid(); } void Region::add(const QRect &rect) { d->qtRegion = d->qtRegion.united(rect); d->installRegion(rect); } void Region::add(const QRegion ®ion) { d->qtRegion = d->qtRegion.united(region); d->installRegion(region); } void Region::subtract(const QRect &rect) { d->qtRegion = d->qtRegion.subtracted(rect); d->uninstallRegion(rect); } void Region::subtract(const QRegion ®ion) { d->qtRegion = d->qtRegion.subtracted(region); d->uninstallRegion(region); } QRegion Region::region() const { return d->qtRegion; } Region::operator wl_region*() const { return d->region; } Region::operator wl_region*() { return d->region; } } } diff --git a/src/client/surface.cpp b/src/client/surface.cpp index 549ce5e..46e659f 100644 --- a/src/client/surface.cpp +++ b/src/client/surface.cpp @@ -1,361 +1,361 @@ /******************************************************************** Copyright 2014 Martin Gräßlin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ #include "surface.h" #include "buffer.h" #include "region.h" #include "output.h" #include "wayland_pointer_p.h" #include #include #include #include #include // Wayland #include namespace KWayland { namespace Client { class Q_DECL_HIDDEN Surface::Private { public: Private(Surface *q); void setupFrameCallback(); WaylandPointer surface; bool frameCallbackInstalled = false; QSize size; bool foreign = false; qint32 scale = 1; QVector outputs; void setup(wl_surface *s); static QList s_surfaces; private: void handleFrameCallback(); static void frameCallback(void *data, wl_callback *callback, uint32_t time); static void enterCallback(void *data, wl_surface *wl_surface, wl_output *output); static void leaveCallback(void *data, wl_surface *wl_surface, wl_output *output); Surface *q; static const wl_callback_listener s_listener; static const wl_surface_listener s_surfaceListener; }; QList Surface::Private::s_surfaces = QList(); Surface::Private::Private(Surface *q) : q(q) { } Surface::Surface(QObject *parent) : QObject(parent) , d(new Private(this)) { Private::s_surfaces << this; } Surface::~Surface() { Private::s_surfaces.removeAll(this); release(); } Surface *Surface::fromWindow(QWindow *window) { if (!window) { return nullptr; } QPlatformNativeInterface *native = qApp->platformNativeInterface(); if (!native) { return nullptr; } window->create(); wl_surface *s = reinterpret_cast(native->nativeResourceForWindow(QByteArrayLiteral("surface"), window)); if (!s) { return nullptr; } if (auto surface = get(s)) { return surface; } Surface *surface = new Surface(window); surface->d->surface.setup(s, true); return surface; } Surface *Surface::fromQtWinId(WId wid) { QWindow *window = nullptr; for (auto win : qApp->allWindows()) { if (win->winId() == wid) { window = win; break; } } if (!window) { return nullptr; } return fromWindow(window); } void Surface::release() { d->surface.release(); } void Surface::destroy() { d->surface.destroy(); } void Surface::setup(wl_surface *surface) { d->setup(surface); } void Surface::Private::setup(wl_surface *s) { Q_ASSERT(s); Q_ASSERT(!surface); surface.setup(s); wl_surface_add_listener(s, &s_surfaceListener, this); } void Surface::Private::frameCallback(void *data, wl_callback *callback, uint32_t time) { Q_UNUSED(time) auto s = reinterpret_cast(data); if (callback) { wl_callback_destroy(callback); } s->handleFrameCallback(); } void Surface::Private::handleFrameCallback() { frameCallbackInstalled = false; emit q->frameRendered(); } #ifndef DOXYGEN_SHOULD_SKIP_THIS const struct wl_callback_listener Surface::Private::s_listener = { frameCallback }; const struct wl_surface_listener Surface::Private::s_surfaceListener = { enterCallback, leaveCallback }; #endif void Surface::Private::enterCallback(void *data, wl_surface *surface, wl_output *output) { Q_UNUSED(surface); auto s = reinterpret_cast(data); Output *o = Output::get(output); if (!o) { return; } s->outputs << o; QObject::connect(o, &Output::removed, s->q, [s, o]() { if (!s->outputs.contains(o)) { return; } s->outputs.removeOne(o); s->q->outputLeft(o); }); emit s->q->outputEntered(o); } void Surface::Private::leaveCallback(void *data, wl_surface *surface, wl_output *output) { Q_UNUSED(surface); auto s = reinterpret_cast(data); Output *o = Output::get(output); if (!o) { return; } s->outputs.removeOne(o); emit s->q->outputLeft(o); } void Surface::Private::setupFrameCallback() { Q_ASSERT(!frameCallbackInstalled); wl_callback *callback = wl_surface_frame(surface); wl_callback_add_listener(callback, &s_listener, this); frameCallbackInstalled = true; } void Surface::setupFrameCallback() { Q_ASSERT(isValid()); d->setupFrameCallback(); } void Surface::commit(Surface::CommitFlag flag) { Q_ASSERT(isValid()); if (flag == CommitFlag::FrameCallback) { setupFrameCallback(); } wl_surface_commit(d->surface); } void Surface::damage(const QRegion ®ion) { - for (const QRect &r : region.rects()) { - damage(r); + for (const QRect &rect : region) { + damage(rect); } } void Surface::damage(const QRect &rect) { Q_ASSERT(isValid()); wl_surface_damage(d->surface, rect.x(), rect.y(), rect.width(), rect.height()); } void Surface::damageBuffer(const QRegion ®ion) { for (const QRect &r : region) { damageBuffer(r); } } void Surface::damageBuffer(const QRect &rect) { Q_ASSERT(isValid()); wl_surface_damage_buffer(d->surface, rect.x(), rect.y(), rect.width(), rect.height()); } void Surface::attachBuffer(wl_buffer *buffer, const QPoint &offset) { Q_ASSERT(isValid()); wl_surface_attach(d->surface, buffer, offset.x(), offset.y()); } void Surface::attachBuffer(Buffer *buffer, const QPoint &offset) { attachBuffer(buffer ? buffer->buffer() : nullptr, offset); } void Surface::attachBuffer(Buffer::Ptr buffer, const QPoint &offset) { attachBuffer(buffer.toStrongRef().data(), offset); } void Surface::setInputRegion(const Region *region) { Q_ASSERT(isValid()); if (region) { wl_surface_set_input_region(d->surface, *region); } else { wl_surface_set_input_region(d->surface, nullptr); } } void Surface::setOpaqueRegion(const Region *region) { Q_ASSERT(isValid()); if (region) { wl_surface_set_opaque_region(d->surface, *region); } else { wl_surface_set_opaque_region(d->surface, nullptr); } } void Surface::setSize(const QSize &size) { if (d->size == size) { return; } d->size = size; emit sizeChanged(d->size); } Surface *Surface::get(wl_surface *native) { auto it = std::find_if(Private::s_surfaces.constBegin(), Private::s_surfaces.constEnd(), [native](Surface *s) { return s->d->surface == native; } ); if (it != Private::s_surfaces.constEnd()) { return *(it); } return nullptr; } const QList< Surface* > &Surface::all() { return Private::s_surfaces; } bool Surface::isValid() const { return d->surface.isValid(); } QSize Surface::size() const { return d->size; } Surface::operator wl_surface*() { return d->surface; } Surface::operator wl_surface*() const { return d->surface; } quint32 Surface::id() const { wl_surface *s = *this; return wl_proxy_get_id(reinterpret_cast(s)); } qint32 Surface::scale() const { return d->scale; } void Surface::setScale(qint32 scale) { d->scale = scale; wl_surface_set_buffer_scale(d->surface, scale); } QVector Surface::outputs() const { return d->outputs; } } } diff --git a/src/server/surface_interface_p.h b/src/server/surface_interface_p.h index c6103ba..d361aad 100644 --- a/src/server/surface_interface_p.h +++ b/src/server/surface_interface_p.h @@ -1,147 +1,148 @@ /******************************************************************** Copyright 2014 Martin Gräßlin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ #ifndef WAYLAND_SERVER_SURFACE_INTERFACE_P_H #define WAYLAND_SERVER_SURFACE_INTERFACE_P_H #include "surface_interface.h" #include "resource_p.h" // Qt +#include #include // Wayland #include namespace KWayland { namespace Server { class IdleInhibitorInterface; class SurfaceInterface::Private : public Resource::Private { public: struct State { QRegion damage = QRegion(); QRegion bufferDamage = QRegion(); QRegion opaque = QRegion(); QRegion input = QRegion(); bool inputIsSet = false; bool opaqueIsSet = false; bool bufferIsSet = false; bool shadowIsSet = false; bool blurIsSet = false; bool contrastIsSet = false; bool slideIsSet = false; bool inputIsInfinite = true; bool childrenChanged = false; bool scaleIsSet = false; bool transformIsSet = false; qint32 scale = 1; OutputInterface::Transform transform = OutputInterface::Transform::Normal; QList callbacks = QList(); QPoint offset = QPoint(); BufferInterface *buffer = nullptr; // stacking order: bottom (first) -> top (last) QList> children; QPointer shadow; QPointer blur; QPointer contrast; QPointer slide; }; Private(SurfaceInterface *q, CompositorInterface *c, wl_resource *parentResource); ~Private(); void destroy(); void addChild(QPointer subsurface); void removeChild(QPointer subsurface); bool raiseChild(QPointer subsurface, SurfaceInterface *sibling); bool lowerChild(QPointer subsurface, SurfaceInterface *sibling); void setShadow(const QPointer &shadow); void setBlur(const QPointer &blur); void setContrast(const QPointer &contrast); void setSlide(const QPointer &slide); void installPointerConstraint(LockedPointerInterface *lock); void installPointerConstraint(ConfinedPointerInterface *confinement); void installIdleInhibitor(IdleInhibitorInterface *inhibitor); void commitSubSurface(); void commit(); State current; State pending; State subSurfacePending; QPointer subSurface; QRegion trackedDamage; // workaround for https://bugreports.qt.io/browse/QTBUG-52192 // A subsurface needs to be considered mapped even if it doesn't have a buffer attached // Otherwise Qt's sub-surfaces will never be visible and the client will freeze due to // waiting on the frame callback of the never visible surface bool subSurfaceIsMapped = true; QVector outputs; QPointer lockedPointer; QPointer confinedPointer; QHash outputDestroyedConnections; QVector idleInhibitors; SurfaceInterface *dataProxy = nullptr; private: QMetaObject::Connection constrainsOneShotConnection; QMetaObject::Connection constrainsUnboundConnection; SurfaceInterface *q_func() { return reinterpret_cast(q); } void swapStates(State *source, State *target, bool emitChanged); void damage(const QRect &rect); void damageBuffer(const QRect &rect); void setScale(qint32 scale); void setTransform(OutputInterface::Transform transform); void addFrameCallback(uint32_t callback); void attachBuffer(wl_resource *buffer, const QPoint &offset); void setOpaque(const QRegion ®ion); void setInput(const QRegion ®ion, bool isInfinite); static void destroyFrameCallback(wl_resource *r); static void attachCallback(wl_client *client, wl_resource *resource, wl_resource *buffer, int32_t sx, int32_t sy); static void damageCallback(wl_client *client, wl_resource *resource, int32_t x, int32_t y, int32_t width, int32_t height); static void frameCallback(wl_client *client, wl_resource *resource, uint32_t callback); static void opaqueRegionCallback(wl_client *client, wl_resource *resource, wl_resource *region); static void inputRegionCallback(wl_client *client, wl_resource *resource, wl_resource *region); static void commitCallback(wl_client *client, wl_resource *resource); // since version 2 static void bufferTransformCallback(wl_client *client, wl_resource *resource, int32_t transform); // since version 3 static void bufferScaleCallback(wl_client *client, wl_resource *resource, int32_t scale); // since version 4 static void damageBufferCallback(wl_client *client, wl_resource *resource, int32_t x, int32_t y, int32_t width, int32_t height); static const struct wl_surface_interface s_interface; }; } } #endif diff --git a/src/tools/generator.cpp b/src/tools/generator.cpp index 84630d3..e596419 100644 --- a/src/tools/generator.cpp +++ b/src/tools/generator.cpp @@ -1,1587 +1,1587 @@ /******************************************************************** Copyright 2015 Martin Gräßlin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ #include "generator.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace KWayland { namespace Tools { static QMap s_clientClassNameMapping; static QString toQtInterfaceName(const QString &wlInterface) { auto it = s_clientClassNameMapping.constFind(wlInterface); if (it != s_clientClassNameMapping.constEnd()) { return it.value(); } else { qWarning() << "Cannot find mapping for " << wlInterface; } return wlInterface; } static QString toCamelCase(const QString &underscoreName) { const QStringList parts = underscoreName.split(QStringLiteral("_")); if (parts.count() < 2) { return underscoreName; } auto it = parts.constBegin(); QString camelCase = (*it); it++; for (; it != parts.constEnd(); ++it) { camelCase.append((*it).left(1).toUpper()); camelCase.append((*it).mid(1)); } return camelCase; } Argument::Argument() { } Argument::Argument(const QXmlStreamAttributes &attributes) : m_name(attributes.value(QStringLiteral("name")).toString()) , m_type(parseType(attributes.value(QStringLiteral("type")))) , m_allowNull(attributes.hasAttribute(QStringLiteral("allow-null"))) , m_inteface(attributes.value(QStringLiteral("interface")).toString()) { } Argument::~Argument() = default; Argument::Type Argument::parseType(const QStringRef &type) { if (type.compare(QLatin1String("new_id")) == 0) { return Type::NewId; } if (type.compare(QLatin1String("destructor")) == 0) { return Type::Destructor; } if (type.compare(QLatin1String("object")) == 0) { return Type::Object; } if (type.compare(QLatin1String("fd")) == 0) { return Type::FileDescriptor; } if (type.compare(QLatin1String("fixed")) == 0) { return Type::Fixed; } if (type.compare(QLatin1String("uint")) == 0) { return Type::Uint; } if (type.compare(QLatin1String("int")) == 0) { return Type::Int; } if (type.compare(QLatin1String("string")) == 0) { return Type::String; } return Type::Unknown; } QString Argument::typeAsQt() const { switch (m_type) { case Type::Destructor: return QString(); case Type::FileDescriptor: return QStringLiteral("int"); case Type::Fixed: return QStringLiteral("qreal"); case Type::Int: return QStringLiteral("qint32"); case Type::NewId: case Type::Object: return toQtInterfaceName(m_inteface); case Type::String: return QStringLiteral("const QString &"); case Type::Uint: return QStringLiteral("quint32"); case Type::Unknown: return QString(); default: Q_UNREACHABLE(); } } QString Argument::typeAsServerWl() const { switch (m_type) { case Type::Destructor: return QString(); case Type::FileDescriptor: return QStringLiteral("int32_t"); case Type::Fixed: return QStringLiteral("wl_fixed"); case Type::Int: return QStringLiteral("int32_t"); case Type::Object: return QStringLiteral("wl_resource *"); case Type::String: return QStringLiteral("const char *"); case Type::Uint: case Type::NewId: return QStringLiteral("uint32_t"); case Type::Unknown: return QString(); default: Q_UNREACHABLE(); } } Request::Request() { } Request::Request(const QString &name) : m_name(name) { } Request::~Request() = default; bool Request::isFactory() const { for (const auto a: m_arguments) { if (a.type() == Argument::Type::NewId) { return true; } } return false; } Event::Event() { } Event::Event(const QString &name) : m_name(name) { } Event::~Event() = default; Interface::Interface() = default; Interface::Interface(const QXmlStreamAttributes &attributes) : m_name(attributes.value(QStringLiteral("name")).toString()) , m_version(attributes.value(QStringLiteral("version")).toUInt()) , m_factory(nullptr) { auto it = s_clientClassNameMapping.constFind(m_name); if (it != s_clientClassNameMapping.constEnd()) { m_clientName = it.value(); } else { qWarning() << "Failed to map " << m_name << " to a KWayland name"; } } Interface::~Interface() = default; Generator::Generator(QObject *parent) : QObject(parent) { } Generator::~Generator() = default; void Generator::start() { startAuthorNameProcess(); startAuthorEmailProcess(); startParseXml(); startGenerateHeaderFile(); startGenerateCppFile(); startGenerateServerHeaderFile(); startGenerateServerCppFile(); } void Generator::startParseXml() { if (m_xmlFileName.isEmpty()) { return; } QFile xmlFile(m_xmlFileName); xmlFile.open(QIODevice::ReadOnly); m_xmlReader.setDevice(&xmlFile); while (!m_xmlReader.atEnd()) { if (!m_xmlReader.readNextStartElement()) { continue; } if (m_xmlReader.qualifiedName().compare(QLatin1String("protocol")) == 0) { parseProtocol(); } } auto findFactory = [this] (const QString interfaceName) -> Interface* { for (auto it = m_interfaces.begin(); it != m_interfaces.end(); ++it) { if ((*it).name().compare(interfaceName) == 0) { continue; } for (auto r: (*it).requests()) { for (auto a: r.arguments()) { if (a.type() == Argument::Type::NewId && a.interface().compare(interfaceName) == 0) { return &(*it); } } } } return nullptr; }; for (auto it = m_interfaces.begin(); it != m_interfaces.end(); ++it) { Interface *factory = findFactory((*it).name()); if (factory) { qDebug() << (*it).name() << "gets factored by" << factory->kwaylandClientName(); (*it).setFactory(factory); } else { qDebug() << (*it).name() << "considered as a global"; (*it).markAsGlobal(); } } } void Generator::parseProtocol() { const auto attributes = m_xmlReader.attributes(); const QString protocolName = attributes.value(QStringLiteral("name")).toString(); if (m_baseFileName.isEmpty()) { m_baseFileName = protocolName.toLower(); } while (!m_xmlReader.atEnd()) { if (!m_xmlReader.readNextStartElement()) { if (m_xmlReader.qualifiedName().compare(QLatin1String("protocol")) == 0) { return; } continue; } if (m_xmlReader.qualifiedName().compare(QLatin1String("interface")) == 0) { m_interfaces << parseInterface(); } } } Interface Generator::parseInterface() { Interface interface(m_xmlReader.attributes()); while (!m_xmlReader.atEnd()) { if (!m_xmlReader.readNextStartElement()) { if (m_xmlReader.qualifiedName().compare(QLatin1String("interface")) == 0) { break; } continue; } if (m_xmlReader.qualifiedName().compare(QLatin1String("request")) == 0) { interface.addRequest(parseRequest()); } if (m_xmlReader.qualifiedName().compare(QLatin1String("event")) == 0) { interface.addEvent(parseEvent()); } } return interface; } Request Generator::parseRequest() { const auto attributes = m_xmlReader.attributes(); Request request(attributes.value(QStringLiteral("name")).toString()); if (attributes.value(QStringLiteral("type")).toString().compare(QLatin1String("destructor")) == 0) { request.markAsDestructor(); } while (!m_xmlReader.atEnd()) { if (!m_xmlReader.readNextStartElement()) { if (m_xmlReader.qualifiedName().compare(QLatin1String("request")) == 0) { break; } continue; } if (m_xmlReader.qualifiedName().compare(QLatin1String("arg")) == 0) { request.addArgument(Argument(m_xmlReader.attributes())); } } return request; } Event Generator::parseEvent() { const auto attributes = m_xmlReader.attributes(); Event event(attributes.value(QStringLiteral("name")).toString()); while (!m_xmlReader.atEnd()) { if (!m_xmlReader.readNextStartElement()) { if (m_xmlReader.qualifiedName().compare(QLatin1String("event")) == 0) { break; } continue; } if (m_xmlReader.qualifiedName().compare(QLatin1String("arg")) == 0) { event.addArgument(Argument(m_xmlReader.attributes())); } } return event; } void Generator::startGenerateHeaderFile() { QFutureWatcher *watcher = new QFutureWatcher(this); connect(watcher, &QFutureWatcher::finished, this, &Generator::checkEnd); m_finishedCounter++; watcher->setFuture(QtConcurrent::run([this] { QFile file(QStringLiteral("%1.h").arg(m_baseFileName)); file.open(QIODevice::WriteOnly); m_stream.setLocalData(new QTextStream(&file)); m_project.setLocalData(Project::Client); generateCopyrightHeader(); generateStartIncludeGuard(); generateHeaderIncludes(); generateWaylandForwardDeclarations(); generateStartNamespace(); generateNamespaceForwardDeclarations(); for (auto it = m_interfaces.constBegin(); it != m_interfaces.constEnd(); ++it) { generateClass(*it); } generateEndNamespace(); generateEndIncludeGuard(); m_stream.setLocalData(nullptr); file.close(); })); } void Generator::startGenerateCppFile() { QFutureWatcher *watcher = new QFutureWatcher(this); connect(watcher, &QFutureWatcher::finished, this, &Generator::checkEnd); m_finishedCounter++; watcher->setFuture(QtConcurrent::run([this] { QFile file(QStringLiteral("%1.cpp").arg(m_baseFileName)); file.open(QIODevice::WriteOnly); m_stream.setLocalData(new QTextStream(&file)); m_project.setLocalData(Project::Client); generateCopyrightHeader(); generateCppIncludes(); generateStartNamespace(); for (auto it = m_interfaces.constBegin(); it != m_interfaces.constEnd(); ++it) { generatePrivateClass(*it); generateClientCpp(*it); generateClientCppRequests(*it); } generateEndNamespace(); m_stream.setLocalData(nullptr); file.close(); })); } void Generator::startGenerateServerHeaderFile() { QFutureWatcher *watcher = new QFutureWatcher(this); connect(watcher, &QFutureWatcher::finished, this, &Generator::checkEnd); m_finishedCounter++; watcher->setFuture(QtConcurrent::run([this] { QFile file(QStringLiteral("%1_interface.h").arg(m_baseFileName)); file.open(QIODevice::WriteOnly); m_stream.setLocalData(new QTextStream(&file)); m_project.setLocalData(Project::Server); generateCopyrightHeader(); generateStartIncludeGuard(); generateHeaderIncludes(); generateStartNamespace(); generateNamespaceForwardDeclarations(); if (std::any_of(m_interfaces.constBegin(), m_interfaces.constEnd(), [] (const Interface &i) { return i.isUnstableInterface(); })) { // generate the unstable semantic version auto it = std::find_if(m_interfaces.constBegin(), m_interfaces.constEnd(), [] (const Interface &i) { return i.isGlobal(); }); if (it != m_interfaces.constEnd()) { const QString templateString = QStringLiteral( "/**\n" " * Enum describing the interface versions the %1 can support.\n" " *\n" " * @since 5.XX\n" " **/\n" "enum class %1Version {\n" " /**\n" " * %2\n" " **/\n" " UnstableV%3\n" "};\n\n"); *m_stream.localData() << templateString.arg((*it).kwaylandServerName()) .arg((*it).name()) .arg((*it).name().mid((*it).name().lastIndexOf(QStringLiteral("_v")) + 2)); } } for (auto it = m_interfaces.constBegin(); it != m_interfaces.constEnd(); ++it) { generateClass(*it); } generateEndNamespace(); generateEndIncludeGuard(); m_stream.setLocalData(nullptr); file.close(); })); } void Generator::startGenerateServerCppFile() { QFutureWatcher *watcher = new QFutureWatcher(this); connect(watcher, &QFutureWatcher::finished, this, &Generator::checkEnd); m_finishedCounter++; watcher->setFuture(QtConcurrent::run([this] { QFile file(QStringLiteral("%1_interface.cpp").arg(m_baseFileName)); file.open(QIODevice::WriteOnly); m_stream.setLocalData(new QTextStream(&file)); m_project.setLocalData(Project::Server); generateCopyrightHeader(); generateCppIncludes(); generateStartNamespace(); for (auto it = m_interfaces.constBegin(); it != m_interfaces.constEnd(); ++it) { generatePrivateClass(*it); // generateClientCpp(*it); // generateClientCppRequests(*it); } generateEndNamespace(); m_stream.setLocalData(nullptr); file.close(); })); } void Generator::checkEnd() { m_finishedCounter--; if (m_finishedCounter == 0) { QCoreApplication::quit(); } } void Generator::startAuthorNameProcess() { QProcess *git = new QProcess(this); git->setArguments(QStringList{ QStringLiteral("config"), QStringLiteral("--get"), QStringLiteral("user.name") }); git->setProgram(QStringLiteral("git")); - connect(git, static_cast(&QProcess::finished), this, + connect(git, static_cast(&QProcess::finished), this, [this, git] { QMutexLocker locker(&m_mutex); m_authorName = QString::fromLocal8Bit(git->readAllStandardOutput()).trimmed(); git->deleteLater(); m_waitCondition.wakeAll(); } ); git->start(); } void Generator::startAuthorEmailProcess() { QProcess *git = new QProcess(this); git->setArguments(QStringList{ QStringLiteral("config"), QStringLiteral("--get"), QStringLiteral("user.email") }); git->setProgram(QStringLiteral("git")); - connect(git, static_cast(&QProcess::finished), this, + connect(git, static_cast(&QProcess::finished), this, [this, git] { QMutexLocker locker(&m_mutex); m_authorEmail = QString::fromLocal8Bit(git->readAllStandardOutput()).trimmed(); git->deleteLater(); m_waitCondition.wakeAll(); } ); git->start(); } void Generator::generateCopyrightHeader() { m_mutex.lock(); while (m_authorEmail.isEmpty() || m_authorName.isEmpty()) { m_waitCondition.wait(&m_mutex); } m_mutex.unlock(); const QString templateString = QStringLiteral( "/****************************************************************************\n" "Copyright %1 %2 <%3>\n" "\n" "This library is free software; you can redistribute it and/or\n" "modify it under the terms of the GNU Lesser General Public\n" "License as published by the Free Software Foundation; either\n" "version 2.1 of the License, or (at your option) version 3, or any\n" "later version accepted by the membership of KDE e.V. (or its\n" "successor approved by the membership of KDE e.V.), which shall\n" "act as a proxy defined in Section 6 of version 3 of the license.\n" "\n" "This library is distributed in the hope that it will be useful,\n" "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n" "Lesser General Public License for more details.\n" "\n" "You should have received a copy of the GNU Lesser General Public\n" "License along with this library. If not, see .\n" "****************************************************************************/\n" ); QDate date = QDate::currentDate(); *m_stream.localData() << templateString.arg(date.year()).arg(m_authorName).arg(m_authorEmail); } void Generator::generateEndIncludeGuard() { *m_stream.localData() << QStringLiteral("#endif\n"); } void Generator::generateStartIncludeGuard() { const QString templateString = QStringLiteral( "#ifndef KWAYLAND_%1_%2_H\n" "#define KWAYLAND_%1_%2_H\n\n"); *m_stream.localData() << templateString.arg(projectToName().toUpper()).arg(m_baseFileName.toUpper()); } void Generator::generateStartNamespace() { const QString templateString = QStringLiteral( "namespace KWayland\n" "{\n" "namespace %1\n" "{\n\n"); *m_stream.localData() << templateString.arg(projectToName()); } void Generator::generateEndNamespace() { *m_stream.localData() << QStringLiteral("\n}\n}\n\n"); } void Generator::generateHeaderIncludes() { switch (m_project.localData()) { case Project::Client: *m_stream.localData() << QStringLiteral("#include \n\n"); break; case Project::Server: *m_stream.localData() << QStringLiteral( "#include \"global.h\"\n" "#include \"resource.h\"\n\n"); break; default: Q_UNREACHABLE(); } *m_stream.localData() << QStringLiteral("#include \n\n").arg(projectToName()).arg(projectToName().toLower()); } void Generator::generateCppIncludes() { switch (m_project.localData()) { case Project::Client: *m_stream.localData() << QStringLiteral("#include \"%1.h\"\n").arg(m_baseFileName.toLower()); *m_stream.localData() << QStringLiteral("#include \"event_queue.h\"\n"); *m_stream.localData() << QStringLiteral("#include \"wayland_pointer_p.h\"\n\n"); break; case Project::Server: *m_stream.localData() << QStringLiteral("#include \"%1_interface.h\"\n").arg(m_baseFileName.toLower()); *m_stream.localData() << QStringLiteral( "#include \"display.h\"\n" "#include \"global_p.h\"\n" "#include \"resource_p.h\"\n\n"); break; default: Q_UNREACHABLE(); } } void Generator::generateClass(const Interface &interface) { switch (m_project.localData()) { case Project::Client: if (interface.isGlobal()) { generateClientGlobalClass(interface); } else { generateClientResourceClass(interface); } break; case Project::Server: if (interface.isGlobal()) { generateServerGlobalClass(interface); } else { generateServerResourceClass(interface); } break; default: Q_UNREACHABLE(); } } void Generator::generateClientGlobalClass(const Interface &interface) { generateClientGlobalClassDoxy(interface); generateClientClassQObjectDerived(interface); generateClientGlobalClassCtor(interface); generateClientClassDtor(interface); generateClientGlobalClassSetup(interface); generateClientClassReleaseDestroy(interface); generateClientClassStart(interface); generateClientClassRequests(interface); generateClientClassCasts(interface); generateClientClassSignals(interface); generateClientGlobalClassEnd(interface); } void Generator::generateClientResourceClass(const Interface &interface) { generateClientClassQObjectDerived(interface); generateClientClassDtor(interface); generateClientResourceClassSetup(interface); generateClientClassReleaseDestroy(interface); generateClientClassRequests(interface); generateClientClassCasts(interface); generateClientResourceClassEnd(interface); } void Generator::generateServerGlobalClass(const Interface &interface) { if (interface.isUnstableInterface()) { generateServerGlobalClassUnstable(interface); return; } const QString templateString = QStringLiteral( "class KWAYLANDSERVER_EXPORT %1 : public Global\n" "{\n" " Q_OBJECT\n" "public:\n" " virtual ~%1();\n" "\n" "private:\n" " explicit %1(Display *display, QObject *parent = nullptr);\n" " friend class Display;\n" " class Private;\n" "};\n" "\n"); *m_stream.localData() << templateString.arg(interface.kwaylandServerName()); } void Generator::generateServerGlobalClassUnstable(const Interface &interface) { const QString templateString = QStringLiteral( "class KWAYLANDSERVER_EXPORT %1 : public Global\n" "{\n" " Q_OBJECT\n" "public:\n" " virtual ~%1();\n" "\n" " /**\n" " * @returns The interface version used by this %1\n" " **/\n" " %1Version interfaceVersion() const;\n" "\n" "protected:\n" " class Private;\n" " explicit %1(Private *d, QObject *parent = nullptr);\n" "\n" "private:\n" " Private *d_func() const;\n" "};\n" "\n"); *m_stream.localData() << templateString.arg(interface.kwaylandServerName()); } void Generator::generateServerResourceClass(const Interface &interface) { if (interface.factory()->isUnstableInterface()) { generateServerResourceClassUnstable(interface); return; } const QString templateString = QStringLiteral( "class KWAYLANDSERVER_EXPORT %1 : public Resource\n" "{\n" " Q_OBJECT\n" "public:\n" " virtual ~%1();\n" "\n" "private:\n" " explicit %1(%2 *parent, wl_resource *parentResource);\n" " friend class %2;\n" "\n" " class Private;\n" " Private *d_func() const;\n" "};\n" "\n"); *m_stream.localData() << templateString.arg(interface.kwaylandServerName()).arg(interface.factory()->kwaylandServerName()); } void Generator::generateServerResourceClassUnstable(const Interface &interface) { const QString templateString = QStringLiteral( "class KWAYLANDSERVER_EXPORT %1 : public Resource\n" "{\n" " Q_OBJECT\n" "public:\n" "\n" " virtual ~%1();\n" "\n" " /**\n" " * @returns The interface version used by this %1\n" " **/\n" " %2Version interfaceVersion() const;\n" "\n" "protected:\n" " class Private;\n" " explicit %1(Private *p, QObject *parent = nullptr);\n" "\n" "private:\n" " Private *d_func() const;\n" "};\n" "\n"); *m_stream.localData() << templateString.arg(interface.kwaylandServerName()).arg(interface.factory()->kwaylandServerName()); } void Generator::generatePrivateClass(const Interface &interface) { switch (m_project.localData()) { case Project::Client: generateClientPrivateClass(interface); break; case Project::Server: if (interface.isGlobal()) { generateServerPrivateGlobalClass(interface); } else { generateServerPrivateResourceClass(interface); } break; default: Q_UNREACHABLE(); } } void Generator::generateServerPrivateGlobalClass(const Interface &interface) { QString templateString = QStringLiteral( "class %1::Private : public Global::Private\n" "{\n" "public:\n" " Private(%1 *q, Display *d);\n" "\n" "private:\n" " void bind(wl_client *client, uint32_t version, uint32_t id) override;\n" "\n" " static void unbind(wl_resource *resource);\n" " static Private *cast(wl_resource *r) {\n" " return reinterpret_cast(wl_resource_get_user_data(r));\n" " }\n" "\n"); *m_stream.localData() << templateString.arg(interface.kwaylandServerName()); generateServerPrivateCallbackDefinitions(interface); templateString = QStringLiteral( " %1 *q;\n" " static const struct %2_interface s_interface;\n" " static const quint32 s_version;\n" "};\n" "\n" "const quint32 %1::Private::s_version = %3;\n" "\n"); *m_stream.localData() << templateString.arg(interface.kwaylandServerName()).arg(interface.name()).arg(interface.version()); generateServerPrivateInterfaceClass(interface); generateServerPrivateCallbackImpl(interface); generateServerPrivateGlobalCtorBindClass(interface); } void Generator::generateServerPrivateCallbackDefinitions(const Interface &interface) { for (const auto &r: interface.requests()) { if (r.isDestructor() && !interface.isGlobal()) { continue; } *m_stream.localData() << QStringLiteral(" static void %1Callback(wl_client *client, wl_resource *resource").arg(toCamelCase(r.name())); for (const auto &a: r.arguments()) { *m_stream.localData() << QStringLiteral(", %1 %2").arg(a.typeAsServerWl()).arg(a.name()); } *m_stream.localData() << QStringLiteral(");\n"); } *m_stream.localData() << QStringLiteral("\n"); } void Generator::generateServerPrivateCallbackImpl(const Interface &interface) { for (const auto &r: interface.requests()) { if (r.isDestructor() && !interface.isGlobal()) { continue; } *m_stream.localData() << QStringLiteral("void %2::Private::%1Callback(wl_client *client, wl_resource *resource").arg(toCamelCase(r.name())).arg(interface.kwaylandServerName()); for (const auto &a: r.arguments()) { *m_stream.localData() << QStringLiteral(", %1 %2").arg(a.typeAsServerWl()).arg(a.name()); } *m_stream.localData() << QStringLiteral( ")\n" "{\n"); if (r.isDestructor()) { *m_stream.localData() << QStringLiteral( " Q_UNUSED(client)\n" " wl_resource_destroy(resource);\n"); } else { *m_stream.localData() << QStringLiteral(" // TODO: implement\n"); } *m_stream.localData() << QStringLiteral( "}\n" "\n"); } } void Generator::generateServerPrivateGlobalCtorBindClass(const Interface &interface) { QString templateString = QStringLiteral( "%1::Private::Private(%1 *q, Display *d)\n" " : Global::Private(d, &%2_interface, s_version)\n" " , q(q)\n" "{\n" "}\n" "\n" "void %1::Private::bind(wl_client *client, uint32_t version, uint32_t id)\n" "{\n" " auto c = display->getConnection(client);\n" " wl_resource *resource = c->createResource(&%2_interface, qMin(version, s_version), id);\n" " if (!resource) {\n" " wl_client_post_no_memory(client);\n" " return;\n" " }\n" " wl_resource_set_implementation(resource, &s_interface, this, unbind);\n" " // TODO: should we track?\n" "}\n" "\n" "void %1::Private::unbind(wl_resource *resource)\n" "{\n" " Q_UNUSED(resource)\n" " // TODO: implement?\n" "}\n" "\n"); *m_stream.localData() << templateString.arg(interface.kwaylandServerName()).arg(interface.name()); } void Generator::generateServerPrivateResourceClass(const Interface &interface) { QString templateString = QStringLiteral( "class %1::Private : public Resource::Private\n" "{\n" "public:\n" " Private(%1 *q, %2 *c, wl_resource *parentResource);\n" " ~Private();\n" "\n" "private:\n"); *m_stream.localData() << templateString.arg(interface.kwaylandServerName()).arg(interface.factory()->kwaylandServerName()); generateServerPrivateCallbackDefinitions(interface); templateString = QStringLiteral( " %1 *q_func() {\n" " return reinterpret_cast<%1 *>(q);\n" " }\n" "\n" " static const struct %2_interface s_interface;\n" "};\n" "\n"); *m_stream.localData() << templateString.arg(interface.kwaylandServerName()).arg(interface.name()); generateServerPrivateInterfaceClass(interface); generateServerPrivateCallbackImpl(interface); generateServerPrivateResourceCtorDtorClass(interface); } void Generator::generateServerPrivateInterfaceClass(const Interface &interface) { *m_stream.localData() << QStringLiteral("#ifndef DOXYGEN_SHOULD_SKIP_THIS\n"); *m_stream.localData() << QStringLiteral("const struct %2_interface %1::Private::s_interface = {\n").arg(interface.kwaylandServerName()).arg(interface.name()); bool first = true; for (auto r: interface.requests()) { if (!first) { *m_stream.localData() << QStringLiteral(",\n"); } else { first = false; } if (r.isDestructor() && !interface.isGlobal()) { *m_stream.localData() << QStringLiteral(" resourceDestroyedCallback"); } else { *m_stream.localData() << QStringLiteral(" %1Callback").arg(toCamelCase(r.name())); } } *m_stream.localData() << QStringLiteral("\n};\n#endif\n\n"); } void Generator::generateServerPrivateResourceCtorDtorClass(const Interface &interface) { QString templateString = QStringLiteral( "%1::Private::Private(%1 *q, %2 *c, wl_resource *parentResource)\n" " : Resource::Private(q, c, parentResource, &%3_interface, &s_interface)\n" "{\n" "}\n" "\n" "%1::Private::~Private()\n" "{\n" " if (resource) {\n" " wl_resource_destroy(resource);\n" " resource = nullptr;\n" " }\n" "}\n"); *m_stream.localData() << templateString.arg(interface.kwaylandServerName()).arg(interface.factory()->kwaylandServerName()).arg(interface.name()); } void Generator::generateClientPrivateClass(const Interface &interface) { if (interface.isGlobal()) { generateClientPrivateGlobalClass(interface); } else { generateClientPrivateResourceClass(interface); } const auto events = interface.events(); if (!events.isEmpty()) { *m_stream.localData() << QStringLiteral("\nprivate:\n"); // generate the callbacks for (auto event : events) { const QString templateString = QStringLiteral(" static void %1Callback(void *data, %2 *%2"); *m_stream.localData() << templateString.arg(event.name()).arg(interface.name()); const auto arguments = event.arguments(); for (auto argument : arguments) { if (argument.interface().isNull()) { *m_stream.localData() << QStringLiteral(", %1 %2").arg(argument.typeAsServerWl()).arg(argument.name()); } else { *m_stream.localData() << QStringLiteral(", %1 *%2").arg(argument.interface()).arg(argument.name()); } } *m_stream.localData() << ");\n"; } *m_stream.localData() << QStringLiteral("\n static const %1_listener s_listener;\n").arg(interface.name()); } *m_stream.localData() << QStringLiteral("};\n\n"); } void Generator::generateClientPrivateResourceClass(const Interface &interface) { const QString templateString = QStringLiteral( "class %1::Private\n" "{\n" "public:\n" " Private(%1 *q);\n" "\n" " void setup(%2 *arg);\n" "\n" " WaylandPointer<%2, %2_destroy> %3;\n" "\n" "private:\n" " %1 *q;\n"); *m_stream.localData() << templateString.arg(interface.kwaylandClientName()).arg(interface.name()).arg(interface.kwaylandClientName().toLower()); } void Generator::generateClientPrivateGlobalClass(const Interface &interface) { const QString templateString = QStringLiteral( "class %1::Private\n" "{\n" "public:\n" " Private() = default;\n" "\n" " void setup(%2 *arg);\n" "\n" " WaylandPointer<%2, %2_destroy> %3;\n" " EventQueue *queue = nullptr;\n"); *m_stream.localData() << templateString.arg(interface.kwaylandClientName()).arg(interface.name()).arg(interface.kwaylandClientName().toLower()); } void Generator::generateClientCpp(const Interface &interface) { // TODO: generate listener and callbacks const auto events = interface.events(); if (!events.isEmpty()) { // listener *m_stream.localData() << QStringLiteral("const %1_listener %2::Private::s_listener = {\n").arg(interface.name()).arg(interface.kwaylandClientName()); bool first = true; for (auto event : events) { if (!first) { *m_stream.localData() << QStringLiteral(",\n"); } *m_stream.localData() << QStringLiteral(" %1Callback").arg(event.name()); first = false; } *m_stream.localData() << QStringLiteral("\n};\n\n"); // callbacks for (auto event : events) { *m_stream.localData() << QStringLiteral("void %1::Private::%2Callback(void *data, %3 *%3").arg(interface.kwaylandClientName()).arg(event.name()).arg(interface.name()); const auto arguments = event.arguments(); for (auto argument : arguments) { if (argument.interface().isNull()) { *m_stream.localData() << QStringLiteral(", %1 %2").arg(argument.typeAsServerWl()).arg(argument.name()); } else { *m_stream.localData() << QStringLiteral(", %1 *%2").arg(argument.interface()).arg(argument.name()); } } *m_stream.localData() << QStringLiteral( ")\n" "{\n" " auto p = reinterpret_cast<%1::Private*>(data);\n" " Q_ASSERT(p->%2 == %3);\n").arg(interface.kwaylandClientName()) .arg(interface.kwaylandClientName().toLower()) .arg(interface.name()); for (auto argument : arguments) { *m_stream.localData() << QStringLiteral(" Q_UNUSED(%1)\n").arg(argument.name()); } *m_stream.localData() << QStringLiteral(" // TODO: implement\n}\n\n"); } } if (interface.isGlobal()) { // generate ctor without this pointer to Private const QString templateString = QStringLiteral( "%1::%1(QObject *parent)\n" " : QObject(parent)\n" " , d(new Private)\n" "{\n" "}\n"); *m_stream.localData() << templateString.arg(interface.kwaylandClientName()); } else { // Private ctor const QString templateString = QStringLiteral( "%1::Private::Private(%1 *q)\n" " : q(q)\n" "{\n" "}\n" "\n" "%1::%1(QObject *parent)\n" " : QObject(parent)\n" " , d(new Private(this))\n" "{\n" "}\n"); *m_stream.localData() << templateString.arg(interface.kwaylandClientName()); } // setup call with optional add_listener const QString setupTemplate = QStringLiteral( "\n" "void %1::Private::setup(%3 *arg)\n" "{\n" " Q_ASSERT(arg);\n" " Q_ASSERT(!%2);\n" " %2.setup(arg);\n"); *m_stream.localData() << setupTemplate.arg(interface.kwaylandClientName()) .arg(interface.kwaylandClientName().toLower()) .arg(interface.name()); if (!interface.events().isEmpty()) { *m_stream.localData() << QStringLiteral(" %1_add_listener(%2, &s_listener, this);\n").arg(interface.name()).arg(interface.kwaylandClientName().toLower()); } *m_stream.localData() << QStringLiteral("}\n"); const QString templateString = QStringLiteral( "\n" "%1::~%1()\n" "{\n" " release();\n" "}\n" "\n" "void %1::setup(%3 *%2)\n" "{\n" " d->setup(%2);\n" "}\n" "\n" "void %1::release()\n" "{\n" " d->%2.release();\n" "}\n" "\n" "void %1::destroy()\n" "{\n" " d->%2.destroy();\n" "}\n" "\n" "%1::operator %3*() {\n" " return d->%2;\n" "}\n" "\n" "%1::operator %3*() const {\n" " return d->%2;\n" "}\n" "\n" "bool %1::isValid() const\n" "{\n" " return d->%2.isValid();\n" "}\n\n"); *m_stream.localData() << templateString.arg(interface.kwaylandClientName()) .arg(interface.kwaylandClientName().toLower()) .arg(interface.name()); if (interface.isGlobal()) { const QString templateStringGlobal = QStringLiteral( "void %1::setEventQueue(EventQueue *queue)\n" "{\n" " d->queue = queue;\n" "}\n" "\n" "EventQueue *%1::eventQueue()\n" "{\n" " return d->queue;\n" "}\n\n" ); *m_stream.localData() << templateStringGlobal.arg(interface.kwaylandClientName()); } } void Generator::generateClientGlobalClassDoxy(const Interface &interface) { const QString templateString = QStringLiteral( "/**\n" " * @short Wrapper for the %2 interface.\n" " *\n" " * This class provides a convenient wrapper for the %2 interface.\n" " *\n" " * To use this class one needs to interact with the Registry. There are two\n" " * possible ways to create the %1 interface:\n" " * @code\n" " * %1 *c = registry->create%1(name, version);\n" " * @endcode\n" " *\n" " * This creates the %1 and sets it up directly. As an alternative this\n" " * can also be done in a more low level way:\n" " * @code\n" " * %1 *c = new %1;\n" " * c->setup(registry->bind%1(name, version));\n" " * @endcode\n" " *\n" " * The %1 can be used as a drop-in replacement for any %2\n" " * pointer as it provides matching cast operators.\n" " *\n" " * @see Registry\n" " **/\n"); *m_stream.localData() << templateString.arg(interface.kwaylandClientName()).arg(interface.name()); } void Generator::generateClientClassQObjectDerived(const Interface &interface) { const QString templateString = QStringLiteral( "class KWAYLANDCLIENT_EXPORT %1 : public QObject\n" "{\n" " Q_OBJECT\n" "public:\n"); *m_stream.localData() << templateString.arg(interface.kwaylandClientName()); } void Generator::generateClientGlobalClassCtor(const Interface &interface) { const QString templateString = QStringLiteral( " /**\n" " * Creates a new %1.\n" " * Note: after constructing the %1 it is not yet valid and one needs\n" " * to call setup. In order to get a ready to use %1 prefer using\n" " * Registry::create%1.\n" " **/\n" " explicit %1(QObject *parent = nullptr);\n"); *m_stream.localData() << templateString.arg(interface.kwaylandClientName()); } void Generator::generateClientClassDtor(const Interface &interface) { *m_stream.localData() << QStringLiteral(" virtual ~%1();\n\n").arg(interface.kwaylandClientName()); } void Generator::generateClientClassReleaseDestroy(const Interface &interface) { const QString templateString = QStringLiteral( " /**\n" " * @returns @c true if managing a %2.\n" " **/\n" " bool isValid() const;\n" " /**\n" " * Releases the %2 interface.\n" " * After the interface has been released the %1 instance is no\n" " * longer valid and can be setup with another %2 interface.\n" " **/\n" " void release();\n" " /**\n" " * Destroys the data held by this %1.\n" " * This method is supposed to be used when the connection to the Wayland\n" " * server goes away. If the connection is not valid anymore, it's not\n" " * possible to call release anymore as that calls into the Wayland\n" " * connection and the call would fail. This method cleans up the data, so\n" " * that the instance can be deleted or set up to a new %2 interface\n" " * once there is a new connection available.\n" " *\n" " * It is suggested to connect this method to ConnectionThread::connectionDied:\n" " * @code\n" " * connect(connection, &ConnectionThread::connectionDied, %3, &%1::destroy);\n" " * @endcode\n" " *\n" " * @see release\n" " **/\n" " void destroy();\n" "\n"); *m_stream.localData() << templateString.arg(interface.kwaylandClientName()).arg(interface.name()).arg(interface.kwaylandClientName().toLower()); } void Generator::generateClientGlobalClassSetup(const Interface &interface) { const QString templateString = QStringLiteral( " /**\n" " * Setup this %1 to manage the @p %3.\n" " * When using Registry::create%1 there is no need to call this\n" " * method.\n" " **/\n" " void setup(%2 *%3);\n"); *m_stream.localData() << templateString.arg(interface.kwaylandClientName()).arg(interface.name()).arg(interface.kwaylandClientName().toLower()); } void Generator::generateClientResourceClassSetup(const Interface &interface) { const QString templateString = QStringLiteral( " /**\n" " * Setup this %1 to manage the @p %3.\n" " * When using %4::create%1 there is no need to call this\n" " * method.\n" " **/\n" " void setup(%2 *%3);\n"); *m_stream.localData() << templateString.arg(interface.kwaylandClientName()) .arg(interface.name()) .arg(interface.kwaylandClientName().toLower()) .arg(interface.factory()->kwaylandClientName()); } void Generator::generateClientClassStart(const Interface &interface) { const QString templateString = QStringLiteral( " /**\n" " * Sets the @p queue to use for creating objects with this %1.\n" " **/\n" " void setEventQueue(EventQueue *queue);\n" " /**\n" " * @returns The event queue to use for creating objects with this %1.\n" " **/\n" " EventQueue *eventQueue();\n\n"); *m_stream.localData() << templateString.arg(interface.kwaylandClientName()); } void Generator::generateClientClassRequests(const Interface &interface) { const auto requests = interface.requests(); const QString templateString = QStringLiteral(" void %1(%2);\n\n"); const QString factoryTemplateString = QStringLiteral(" %1 *%2(%3);\n\n"); for (const auto &r: requests) { if (r.isDestructor()) { continue; } QString arguments; bool first = true; QString factored; for (const auto &a: r.arguments()) { if (a.type() == Argument::Type::NewId) { factored = a.interface(); continue; } if (!first) { arguments.append(QStringLiteral(", ")); } else { first = false; } if (a.type() == Argument::Type::Object) { arguments.append(QStringLiteral("%1 *%2").arg(a.typeAsQt()).arg(toCamelCase(a.name()))); } else { arguments.append(QStringLiteral("%1 %2").arg(a.typeAsQt()).arg(toCamelCase(a.name()))); } } if (factored.isEmpty()) { *m_stream.localData() << templateString.arg(toCamelCase((r.name()))).arg(arguments); } else { if (!first) { arguments.append(QStringLiteral(", ")); } arguments.append(QStringLiteral("QObject *parent = nullptr")); *m_stream.localData() << factoryTemplateString.arg(toQtInterfaceName(factored)).arg(toCamelCase(r.name())).arg(arguments); } } } void Generator::generateClientCppRequests(const Interface &interface) { const auto requests = interface.requests(); const QString templateString = QStringLiteral( "void %1::%2(%3)\n" "{\n" " Q_ASSERT(isValid());\n" " %4_%5(d->%6%7);\n" "}\n\n"); const QString factoryTemplateString = QStringLiteral( "%2 *%1::%3(%4)\n" "{\n" " Q_ASSERT(isValid());\n" " auto p = new %2(parent);\n" " auto w = %5_%6(d->%7%8);\n" " if (d->queue) {\n" " d->queue->addProxy(w);\n" " }\n" " p->setup(w);\n" " return p;\n" "}\n\n" ); for (const auto &r: requests) { if (r.isDestructor()) { continue; } QString arguments; QString requestArguments; bool first = true; QString factored; for (const auto &a: r.arguments()) { if (a.type() == Argument::Type::NewId) { factored = a.interface(); continue; } if (!first) { arguments.append(QStringLiteral(", ")); } else { first = false; } if (a.type() == Argument::Type::Object) { arguments.append(QStringLiteral("%1 *%2").arg(a.typeAsQt()).arg(toCamelCase(a.name()))); requestArguments.append(QStringLiteral(", *%1").arg(toCamelCase(a.name()))); } else { arguments.append(QStringLiteral("%1 %2").arg(a.typeAsQt()).arg(toCamelCase(a.name()))); QString arg = toCamelCase(a.name()); if (a.type() == Argument::Type::Fixed) { arg = QStringLiteral("wl_fixed_from_double(%1)").arg(arg); } requestArguments.append(QStringLiteral(", %1").arg(arg)); } } if (factored.isEmpty()) { *m_stream.localData() << templateString.arg(interface.kwaylandClientName()) .arg(toCamelCase(r.name())) .arg(arguments) .arg(interface.name()) .arg(r.name()) .arg(interface.kwaylandClientName().toLower()) .arg(requestArguments); } else { if (!first) { arguments.append(QStringLiteral(", ")); } arguments.append(QStringLiteral("QObject *parent")); *m_stream.localData() << factoryTemplateString.arg(interface.kwaylandClientName()) .arg(toQtInterfaceName(factored)) .arg(toCamelCase(r.name())) .arg(arguments) .arg(interface.name()) .arg(r.name()) .arg(interface.kwaylandClientName().toLower()) .arg(requestArguments); } } } void Generator::generateClientClassCasts(const Interface &interface) { const QString templateString = QStringLiteral( " operator %1*();\n" " operator %1*() const;\n\n"); *m_stream.localData() << templateString.arg(interface.name()); } void Generator::generateClientGlobalClassEnd(const Interface &interface) { Q_UNUSED(interface) *m_stream.localData() << QStringLiteral("private:\n"); generateClientClassDptr(interface); *m_stream.localData() << QStringLiteral("};\n\n"); } void Generator::generateClientClassDptr(const Interface &interface) { Q_UNUSED(interface) *m_stream.localData() << QStringLiteral( " class Private;\n" " QScopedPointer d;\n"); } void Generator::generateClientResourceClassEnd(const Interface &interface) { *m_stream.localData() << QStringLiteral( "private:\n" " friend class %2;\n" " explicit %1(QObject *parent = nullptr);\n" ).arg(interface.kwaylandClientName()).arg(interface.factory()->kwaylandClientName()); generateClientClassDptr(interface); *m_stream.localData() << QStringLiteral("};\n\n"); } void Generator::generateWaylandForwardDeclarations() { for (auto it = m_interfaces.constBegin(); it != m_interfaces.constEnd(); ++it) { *m_stream.localData() << QStringLiteral("struct %1;\n").arg((*it).name()); } *m_stream.localData() << "\n"; } void Generator::generateNamespaceForwardDeclarations() { QSet referencedObjects; for (auto it = m_interfaces.constBegin(); it != m_interfaces.constEnd(); ++it) { const auto events = (*it).events(); const auto requests = (*it).requests(); for (const auto &e: events) { const auto args = e.arguments(); for (const auto &a: args) { if (a.type() != Argument::Type::Object && a.type() != Argument::Type::NewId) { continue; } referencedObjects << a.interface(); } } for (const auto &r: requests) { const auto args = r.arguments(); for (const auto &a: args) { if (a.type() != Argument::Type::Object && a.type() != Argument::Type::NewId) { continue; } referencedObjects << a.interface(); } } } switch (m_project.localData()) { case Project::Client: *m_stream.localData() << QStringLiteral("class EventQueue;\n"); for (const auto &o : referencedObjects) { auto it = s_clientClassNameMapping.constFind(o); if (it != s_clientClassNameMapping.constEnd()) { *m_stream.localData() << QStringLiteral("class %1;\n").arg(it.value()); } else { qWarning() << "Cannot forward declare KWayland class for interface " << o; } } *m_stream.localData() << QStringLiteral("\n"); break; case Project::Server: *m_stream.localData() << QStringLiteral("class Display;\n\n"); break; default: Q_UNREACHABLE(); } } void Generator::generateClientClassSignals(const Interface &interface) { const QString templateString = QStringLiteral( "Q_SIGNALS:\n" " /**\n" " * The corresponding global for this interface on the Registry got removed.\n" " *\n" " * This signal gets only emitted if the %1 got created by\n" " * Registry::create%1\n" " **/\n" " void removed();\n\n"); *m_stream.localData() << templateString.arg(interface.kwaylandClientName()); } QString Generator::projectToName() const { switch (m_project.localData()) { case Project::Client: return QStringLiteral("Client"); case Project::Server: return QStringLiteral("Server"); default: Q_UNREACHABLE(); } } static void parseMapping() { QFile mappingFile(QStringLiteral(MAPPING_FILE)); mappingFile.open(QIODevice::ReadOnly); QTextStream stream(&mappingFile); while (!stream.atEnd()) { QString line = stream.readLine(); if (line.startsWith(QLatin1String("#")) || line.isEmpty()) { continue; } const QStringList parts = line.split(QStringLiteral(";")); if (parts.count() < 2) { continue; } s_clientClassNameMapping.insert(parts.first(), parts.at(1)); } } } } int main(int argc, char **argv) { using namespace KWayland::Tools; parseMapping(); QCoreApplication app(argc, argv); QCommandLineParser parser; QCommandLineOption xmlFile(QStringList{QStringLiteral("x"), QStringLiteral("xml")}, QStringLiteral("The wayland protocol to parse."), QStringLiteral("FileName")); QCommandLineOption fileName(QStringList{QStringLiteral("f"), QStringLiteral("file")}, QStringLiteral("The base name of files to be generated. E.g. for \"foo\" the files \"foo.h\" and \"foo.cpp\" are generated." "If not provided the base name gets derived from the xml protocol name"), QStringLiteral("FileName")); parser.addHelpOption(); parser.addOption(xmlFile); parser.addOption(fileName); parser.process(app); Generator generator(&app); generator.setXmlFileName(parser.value(xmlFile)); generator.setBaseFileName(parser.value(fileName)); generator.start(); return app.exec(); } diff --git a/src/tools/testserver/testserver.cpp b/src/tools/testserver/testserver.cpp index 7931bbe..c80bed3 100644 --- a/src/tools/testserver/testserver.cpp +++ b/src/tools/testserver/testserver.cpp @@ -1,200 +1,199 @@ /******************************************************************** Copyright 2016 Martin Gräßlin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ #include "testserver.h" #include "../../server/display.h" #include "../../server/compositor_interface.h" #include "../../server/datadevicemanager_interface.h" #include "../../server/idle_interface.h" #include "../../server/fakeinput_interface.h" #include "../../server/seat_interface.h" #include "../../server/shell_interface.h" #include "../../server/surface_interface.h" #include "../../server/subcompositor_interface.h" #include #include #include #include // system #include #include #include using namespace KWayland::Server; TestServer::TestServer(QObject *parent) : QObject(parent) , m_repaintTimer(new QTimer(this)) , m_timeSinceStart(new QElapsedTimer) , m_cursorPos(QPointF(0, 0)) { } TestServer::~TestServer() = default; void TestServer::init() { Q_ASSERT(!m_display); m_display = new Display(this); m_display->start(Display::StartMode::ConnectClientsOnly); m_display->createShm(); m_display->createCompositor()->create(); m_shell = m_display->createShell(m_display); connect(m_shell, &ShellInterface::surfaceCreated, this, [this] (ShellSurfaceInterface *surface) { m_shellSurfaces << surface; // TODO: pass keyboard/pointer/touch focus on mapped connect(surface, &QObject::destroyed, this, [this, surface] { m_shellSurfaces.removeOne(surface); } ); } ); m_shell->create(); m_seat = m_display->createSeat(m_display); m_seat->setHasKeyboard(true); m_seat->setHasPointer(true); m_seat->setHasTouch(true); m_seat->create(); m_display->createDataDeviceManager(m_display)->create(); m_display->createIdle(m_display)->create(); m_display->createSubCompositor(m_display)->create(); // output auto output = m_display->createOutput(m_display); const QSize size(1280, 1024); output->setGlobalPosition(QPoint(0, 0)); output->setPhysicalSize(size / 3.8); output->addMode(size); output->create(); auto fakeInput = m_display->createFakeInput(m_display); fakeInput->create(); connect(fakeInput, &FakeInputInterface::deviceCreated, this, [this] (FakeInputDevice *device) { device->setAuthentication(true); connect(device, &FakeInputDevice::pointerMotionRequested, this, [this] (const QSizeF &delta) { m_seat->setTimestamp(m_timeSinceStart->elapsed()); m_cursorPos = m_cursorPos + QPointF(delta.width(), delta.height()); m_seat->setPointerPos(m_cursorPos); } ); connect(device, &FakeInputDevice::pointerButtonPressRequested, this, [this] (quint32 button) { m_seat->setTimestamp(m_timeSinceStart->elapsed()); m_seat->pointerButtonPressed(button); } ); connect(device, &FakeInputDevice::pointerButtonReleaseRequested, this, [this] (quint32 button) { m_seat->setTimestamp(m_timeSinceStart->elapsed()); m_seat->pointerButtonReleased(button); } ); connect(device, &FakeInputDevice::pointerAxisRequested, this, [this] (Qt::Orientation orientation, qreal delta) { m_seat->setTimestamp(m_timeSinceStart->elapsed()); m_seat->pointerAxis(orientation, delta); } ); connect(device, &FakeInputDevice::touchDownRequested, this, [this] (quint32 id, const QPointF &pos) { m_seat->setTimestamp(m_timeSinceStart->elapsed()); m_touchIdMapper.insert(id, m_seat->touchDown(pos)); } ); connect(device, &FakeInputDevice::touchMotionRequested, this, [this] (quint32 id, const QPointF &pos) { m_seat->setTimestamp(m_timeSinceStart->elapsed()); const auto it = m_touchIdMapper.constFind(id); if (it != m_touchIdMapper.constEnd()) { m_seat->touchMove(it.value(), pos); } } ); connect(device, &FakeInputDevice::touchUpRequested, this, [this] (quint32 id) { m_seat->setTimestamp(m_timeSinceStart->elapsed()); const auto it = m_touchIdMapper.find(id); if (it != m_touchIdMapper.end()) { m_seat->touchUp(it.value()); m_touchIdMapper.erase(it); } } ); connect(device, &FakeInputDevice::touchCancelRequested, this, [this] { m_seat->setTimestamp(m_timeSinceStart->elapsed()); m_seat->cancelTouchSequence(); } ); connect(device, &FakeInputDevice::touchFrameRequested, this, [this] { m_seat->setTimestamp(m_timeSinceStart->elapsed()); m_seat->touchFrame(); } ); } ); m_repaintTimer->setInterval(1000 / 60); connect(m_repaintTimer, &QTimer::timeout, this, &TestServer::repaint); m_repaintTimer->start(); m_timeSinceStart->start(); } void TestServer::startTestApp(const QString &app, const QStringList &arguments) { int sx[2]; if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) { QCoreApplication::instance()->exit(1); return; } m_display->createClient(sx[0]); int socket = dup(sx[1]); if (socket == -1) { QCoreApplication::instance()->exit(1); return; } QProcess *p = new QProcess(this); p->setProcessChannelMode(QProcess::ForwardedChannels); QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); environment.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("wayland")); environment.insert(QStringLiteral("WAYLAND_SOCKET"), QString::fromUtf8(QByteArray::number(socket))); p->setProcessEnvironment(environment); auto finishedSignal = static_cast(&QProcess::finished); connect(p, finishedSignal, QCoreApplication::instance(), &QCoreApplication::exit); - auto errorSignal = static_cast(&QProcess::error); - connect(p, errorSignal, this, + connect(p, &QProcess::errorOccurred, this, [] { QCoreApplication::instance()->exit(1); } ); p->start(app, arguments); } void TestServer::repaint() { for (auto it = m_shellSurfaces.constBegin(), end = m_shellSurfaces.constEnd(); it != end; ++it) { (*it)->surface()->frameRendered(m_timeSinceStart->elapsed()); } }