diff --git a/autotests/client/test_xdg_foreign.cpp b/autotests/client/test_xdg_foreign.cpp index cab84b8..8f234b9 100644 --- a/autotests/client/test_xdg_foreign.cpp +++ b/autotests/client/test_xdg_foreign.cpp @@ -1,401 +1,401 @@ /******************************************************************** Copyright 2014 Martin Gräßlin Copyright 2017 Marco Martin 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 // KWin #include "../../src/client/compositor.h" #include "../../src/client/connection_thread.h" #include "../../src/client/event_queue.h" #include "../../src/client/region.h" #include "../../src/client/registry.h" #include "../../src/client/surface.h" #include "../../src/client/xdgforeign.h" #include "../../src/server/display.h" #include "../../src/server/compositor_interface.h" #include "../../src/server/surface_interface.h" #include "../../src/server/xdgforeign_interface.h" using namespace KWayland::Client; class TestForeign : public QObject { Q_OBJECT public: explicit TestForeign(QObject *parent = nullptr); private Q_SLOTS: void init(); void cleanup(); void testExport(); void testDeleteImported(); void testDeleteChildSurface(); void testDeleteParentSurface(); void testDeleteExported(); void testExportTwoTimes(); void testImportTwoTimes(); private: void doExport(); KWayland::Server::Display *m_display; KWayland::Server::CompositorInterface *m_compositorInterface; KWayland::Server::XdgForeignInterface *m_foreignInterface; KWayland::Client::ConnectionThread *m_connection; KWayland::Client::Compositor *m_compositor; KWayland::Client::EventQueue *m_queue; KWayland::Client::XdgExporter *m_exporter; KWayland::Client::XdgImporter *m_importer; QPointer m_exportedSurface; QPointer m_exportedSurfaceInterface; QPointer m_exported; QPointer m_imported; QPointer m_childSurface; QPointer m_childSurfaceInterface; QThread *m_thread; }; static const QString s_socketName = QStringLiteral("kwayland-test-xdg-foreign-0"); TestForeign::TestForeign(QObject *parent) : QObject(parent) , m_display(nullptr) , m_compositorInterface(nullptr) , m_connection(nullptr) , m_compositor(nullptr) , m_queue(nullptr) , m_exporter(nullptr) , m_importer(nullptr) , m_thread(nullptr) { } void TestForeign::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()); qRegisterMetaType("KWayland::Server::SurfaceInterface"); // setup connection m_connection = new KWayland::Client::ConnectionThread; QSignalSpy connectedSpy(m_connection, &ConnectionThread::connected); QVERIFY(connectedSpy.isValid()); m_connection->setSocketName(s_socketName); m_thread = new QThread(this); m_connection->moveToThread(m_thread); m_thread->start(); 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()); Registry registry; QSignalSpy compositorSpy(®istry, &Registry::compositorAnnounced); QVERIFY(compositorSpy.isValid()); QSignalSpy exporterSpy(®istry, &Registry::exporterUnstableV2Announced); QVERIFY(exporterSpy.isValid()); QSignalSpy importerSpy(®istry, &Registry::importerUnstableV2Announced); QVERIFY(importerSpy.isValid()); QVERIFY(!registry.eventQueue()); registry.setEventQueue(m_queue); QCOMPARE(registry.eventQueue(), m_queue); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); m_compositorInterface = m_display->createCompositor(m_display); m_compositorInterface->create(); QVERIFY(m_compositorInterface->isValid()); QVERIFY(compositorSpy.wait()); m_compositor = registry.createCompositor(compositorSpy.first().first().value(), compositorSpy.first().last().value(), this); m_foreignInterface = m_display->createXdgForeignInterface(m_display); m_foreignInterface->create(); QVERIFY(m_foreignInterface->isValid()); QVERIFY(exporterSpy.wait()); //Both importer and exporter should have been triggered by now QCOMPARE(exporterSpy.count(), 1); QCOMPARE(importerSpy.count(), 1); m_exporter = registry.createXdgExporter(exporterSpy.first().first().value(), exporterSpy.first().last().value(), this); m_importer = registry.createXdgImporter(importerSpy.first().first().value(), importerSpy.first().last().value(), this); } void TestForeign::cleanup() { #define CLEANUP(variable) \ if (variable) { \ delete variable; \ variable = nullptr; \ } //some tests delete it beforehand if (m_exportedSurfaceInterface) { QSignalSpy exportedSurfaceDestroyedSpy(m_exportedSurfaceInterface.data(), &QObject::destroyed); QVERIFY(exportedSurfaceDestroyedSpy.isValid()); CLEANUP(m_exportedSurface) exportedSurfaceDestroyedSpy.wait(); } if (m_childSurfaceInterface) { QSignalSpy childSurfaceDestroyedSpy(m_childSurfaceInterface.data(), &QObject::destroyed); QVERIFY(childSurfaceDestroyedSpy.isValid()); CLEANUP(m_childSurface) childSurfaceDestroyedSpy.wait(); } CLEANUP(m_compositor) CLEANUP(m_exporter) CLEANUP(m_importer) CLEANUP(m_queue) if (m_connection) { m_connection->deleteLater(); m_connection = nullptr; } if (m_thread) { m_thread->quit(); m_thread->wait(); delete m_thread; m_thread = nullptr; } CLEANUP(m_compositorInterface) CLEANUP(m_foreignInterface) CLEANUP(m_display) #undef CLEANUP } void TestForeign::doExport() { QSignalSpy serverSurfaceCreated(m_compositorInterface, SIGNAL(surfaceCreated(KWayland::Server::SurfaceInterface*))); QVERIFY(serverSurfaceCreated.isValid()); m_exportedSurface = m_compositor->createSurface(); QVERIFY(serverSurfaceCreated.wait()); m_exportedSurfaceInterface = serverSurfaceCreated.first().first().value(); //Export a window - m_exported = m_exporter->exportSurface(m_exportedSurface, this); + m_exported = m_exporter->exportTopLevel(m_exportedSurface, this); QVERIFY(m_exported->handle().isEmpty()); QSignalSpy doneSpy(m_exported.data(), &XdgExported::done); QVERIFY(doneSpy.wait()); QVERIFY(!m_exported->handle().isEmpty()); QSignalSpy transientSpy(m_foreignInterface, &KWayland::Server::XdgForeignInterface::transientChanged); QVERIFY(transientSpy.isValid()); //Import the just exported window - m_imported = m_importer->import(m_exported->handle(), this); + m_imported = m_importer->importTopLevel(m_exported->handle(), this); QVERIFY(m_imported->isValid()); QSignalSpy childSurfaceInterfaceCreated(m_compositorInterface, SIGNAL(surfaceCreated(KWayland::Server::SurfaceInterface*))); QVERIFY(serverSurfaceCreated.isValid()); m_childSurface = m_compositor->createSurface(); QVERIFY(childSurfaceInterfaceCreated.wait()); m_childSurfaceInterface = childSurfaceInterfaceCreated.first().first().value(); m_childSurface->commit(Surface::CommitFlag::None); m_imported->setParentOf(m_childSurface); QVERIFY(transientSpy.wait()); QCOMPARE(transientSpy.first().first().value(), m_childSurfaceInterface.data()); QCOMPARE(transientSpy.first().at(1).value(), m_exportedSurfaceInterface.data()); //transientFor api QCOMPARE(m_foreignInterface->transientFor(m_childSurfaceInterface), m_exportedSurfaceInterface.data()); } void TestForeign::testExport() { doExport(); } void TestForeign::testDeleteImported() { doExport(); QSignalSpy transientSpy(m_foreignInterface, &KWayland::Server::XdgForeignInterface::transientChanged); QVERIFY(transientSpy.isValid()); m_imported->deleteLater(); m_imported = nullptr; QVERIFY(transientSpy.wait()); QCOMPARE(transientSpy.first().first().value(), m_childSurfaceInterface.data()); QCOMPARE(transientSpy.first().at(1).value(), nullptr); QCOMPARE(m_foreignInterface->transientFor(m_childSurfaceInterface), nullptr); } void TestForeign::testDeleteChildSurface() { doExport(); QSignalSpy transientSpy(m_foreignInterface, &KWayland::Server::XdgForeignInterface::transientChanged); QVERIFY(transientSpy.isValid()); m_childSurface->deleteLater(); QVERIFY(transientSpy.wait()); //when the client surface dies, the server one will eventually die too QSignalSpy surfaceDestroyedSpy(m_childSurfaceInterface, SIGNAL(destroyed())); QVERIFY(surfaceDestroyedSpy.wait()); QCOMPARE(transientSpy.first().at(0).value(), nullptr); QCOMPARE(transientSpy.first().at(1).value(), m_exportedSurfaceInterface.data()); } void TestForeign::testDeleteParentSurface() { doExport(); QSignalSpy transientSpy(m_foreignInterface, &KWayland::Server::XdgForeignInterface::transientChanged); QVERIFY(transientSpy.isValid()); m_exportedSurface->deleteLater(); QSignalSpy exportedSurfaceDestroyedSpy(m_exportedSurfaceInterface.data(), &QObject::destroyed); QVERIFY(exportedSurfaceDestroyedSpy.isValid()); exportedSurfaceDestroyedSpy.wait(); QVERIFY(transientSpy.wait()); QCOMPARE(transientSpy.first().first().value(), m_childSurfaceInterface.data()); QCOMPARE(transientSpy.first().at(1).value(), nullptr); QCOMPARE(m_foreignInterface->transientFor(m_childSurfaceInterface), nullptr); } void TestForeign::testDeleteExported() { doExport(); QSignalSpy transientSpy(m_foreignInterface, &KWayland::Server::XdgForeignInterface::transientChanged); QSignalSpy destroyedSpy(m_imported.data(), &KWayland::Client::XdgImported::importedDestroyed); QVERIFY(transientSpy.isValid()); m_exported->deleteLater(); m_exported = nullptr; QVERIFY(transientSpy.wait()); QVERIFY(destroyedSpy.wait()); QCOMPARE(transientSpy.first().first().value(), m_childSurfaceInterface.data()); QCOMPARE(transientSpy.first().at(1).value(), nullptr); QCOMPARE(m_foreignInterface->transientFor(m_childSurfaceInterface), nullptr); QVERIFY(!m_imported->isValid()); } void TestForeign::testExportTwoTimes() { doExport(); //Export second window - KWayland::Client::XdgExported *exported2 = m_exporter->exportSurface(m_exportedSurface, this); + KWayland::Client::XdgExported *exported2 = m_exporter->exportTopLevel(m_exportedSurface, this); QVERIFY(exported2->handle().isEmpty()); QSignalSpy doneSpy(exported2, &XdgExported::done); QVERIFY(doneSpy.wait()); QVERIFY(!exported2->handle().isEmpty()); QSignalSpy transientSpy(m_foreignInterface, &KWayland::Server::XdgForeignInterface::transientChanged); QVERIFY(transientSpy.isValid()); //Import the just exported window - KWayland::Client::XdgImported *imported2 = m_importer->import(exported2->handle(), this); + KWayland::Client::XdgImported *imported2 = m_importer->importTopLevel(exported2->handle(), this); QVERIFY(imported2->isValid()); //create a second child surface QSignalSpy serverSurfaceCreated(m_compositorInterface, SIGNAL(surfaceCreated(KWayland::Server::SurfaceInterface*))); QVERIFY(serverSurfaceCreated.isValid()); KWayland::Client::Surface *childSurface2 = m_compositor->createSurface(); QVERIFY(serverSurfaceCreated.wait()); KWayland::Server::SurfaceInterface *childSurface2Interface = serverSurfaceCreated.first().first().value(); imported2->setParentOf(childSurface2); QVERIFY(transientSpy.wait()); QCOMPARE(transientSpy.first().first().value(), childSurface2Interface); QCOMPARE(transientSpy.first().at(1).value(), m_exportedSurfaceInterface.data()); //transientFor api //check the old relationship is still here QCOMPARE(m_foreignInterface->transientFor(m_childSurfaceInterface), m_exportedSurfaceInterface.data()); //check the new relationship QCOMPARE(m_foreignInterface->transientFor(childSurface2Interface), m_exportedSurfaceInterface.data()); } void TestForeign::testImportTwoTimes() { doExport(); QSignalSpy transientSpy(m_foreignInterface, &KWayland::Server::XdgForeignInterface::transientChanged); QVERIFY(transientSpy.isValid()); //Import another time the exported window - KWayland::Client::XdgImported *imported2 = m_importer->import(m_exported->handle(), this); + KWayland::Client::XdgImported *imported2 = m_importer->importTopLevel(m_exported->handle(), this); QVERIFY(imported2->isValid()); //create a second child surface QSignalSpy serverSurfaceCreated(m_compositorInterface, SIGNAL(surfaceCreated(KWayland::Server::SurfaceInterface*))); QVERIFY(serverSurfaceCreated.isValid()); KWayland::Client::Surface *childSurface2 = m_compositor->createSurface(); QVERIFY(serverSurfaceCreated.wait()); KWayland::Server::SurfaceInterface *childSurface2Interface = serverSurfaceCreated.first().first().value(); imported2->setParentOf(childSurface2); QVERIFY(transientSpy.wait()); QCOMPARE(transientSpy.first().first().value(), childSurface2Interface); QCOMPARE(transientSpy.first().at(1).value(), m_exportedSurfaceInterface.data()); //transientFor api //check the old relationship is still here QCOMPARE(m_foreignInterface->transientFor(m_childSurfaceInterface), m_exportedSurfaceInterface.data()); //check the new relationship QCOMPARE(m_foreignInterface->transientFor(childSurface2Interface), m_exportedSurfaceInterface.data()); } QTEST_GUILESS_MAIN(TestForeign) #include "test_xdg_foreign.moc" diff --git a/src/client/xdgforeign.cpp b/src/client/xdgforeign.cpp index 67344e4..16fe265 100644 --- a/src/client/xdgforeign.cpp +++ b/src/client/xdgforeign.cpp @@ -1,271 +1,271 @@ /**************************************************************************** Copyright 2017 Marco Martin 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 "xdgforeign.h" #include "xdgforeign_p.h" #include "event_queue.h" #include "wayland_pointer_p.h" #include #include namespace KWayland { namespace Client { XdgExporter::Private::Private() { } XdgExporter::Private::~Private() { } XdgExporter::XdgExporter(Private *p, QObject *parent) : QObject(parent) , d(p) { } XdgExporter::~XdgExporter() { release(); } void XdgExporter::setup(zxdg_exporter_v2 *exporter) { d->setupV2(exporter); } void XdgExporter::release() { d->release(); } void XdgExporter::destroy() { d->destroy(); } XdgExporter::operator zxdg_exporter_v2*() { return d->exporterV2(); } XdgExporter::operator zxdg_exporter_v2*() const { return d->exporterV2(); } bool XdgExporter::isValid() const { return d->isValid(); } void XdgExporter::setEventQueue(EventQueue *queue) { d->queue = queue; } EventQueue *XdgExporter::eventQueue() { return d->queue; } -XdgExported *XdgExporter::exportSurface(Surface *surface, QObject *parent) +XdgExported *XdgExporter::exportTopLevel(Surface *surface, QObject *parent) { return d->exportTopLevelV2(surface, parent); } XdgImporter::Private::Private() { } XdgImporter::Private::~Private() { } XdgImporter::XdgImporter(Private *p, QObject *parent) : QObject(parent) , d(p) { } XdgImporter::~XdgImporter() { release(); } void XdgImporter::setup(zxdg_importer_v2 *importer) { d->setupV2(importer); } void XdgImporter::release() { d->release(); } void XdgImporter::destroy() { d->destroy(); } XdgImporter::operator zxdg_importer_v2*() { return d->importerV2(); } XdgImporter::operator zxdg_importer_v2*() const { return d->importerV2(); } bool XdgImporter::isValid() const { return d->isValid(); } void XdgImporter::setEventQueue(EventQueue *queue) { d->queue = queue; } EventQueue *XdgImporter::eventQueue() { return d->queue; } -XdgImported *XdgImporter::import(const QString & handle, QObject *parent) +XdgImported *XdgImporter::importTopLevel(const QString & handle, QObject *parent) { Q_ASSERT(isValid()); return d->importTopLevelV2(handle, parent); } XdgExported::XdgExported(Private *p, QObject *parent) : QObject(parent) , d(p) { } XdgExported::Private::Private(XdgExported *q) : q(q) { } XdgExported::Private::~Private() { } XdgExported::~XdgExported() { release(); } void XdgExported::setup(zxdg_exported_v2 *exported) { d->setupV2(exported); } void XdgExported::release() { d->release(); } void XdgExported::destroy() { d->destroy(); } QString XdgExported::handle() const { return d->handle; } XdgExported::operator zxdg_exported_v2*() { return d->exportedV2(); } XdgExported::operator zxdg_exported_v2*() const { return d->exportedV2(); } bool XdgExported::isValid() const { return d->isValid(); } XdgImported::Private::Private(XdgImported *q) : q(q) { } XdgImported::Private::~Private() { } XdgImported::XdgImported(Private *p, QObject *parent) : QObject(parent) , d(p) { } XdgImported::~XdgImported() { release(); } void XdgImported::setup(zxdg_imported_v2 *imported) { d->setupV2(imported); } void XdgImported::release() { d->release(); } void XdgImported::destroy() { d->destroy(); } XdgImported::operator zxdg_imported_v2*() { return d->importedV2(); } XdgImported::operator zxdg_imported_v2*() const { return d->importedV2(); } bool XdgImported::isValid() const { return d->isValid(); } void XdgImported::setParentOf(Surface *surface) { Q_ASSERT(isValid()); d->setParentOf(surface); } } } diff --git a/src/client/xdgforeign.h b/src/client/xdgforeign.h index 7507f90..2b4392c 100644 --- a/src/client/xdgforeign.h +++ b/src/client/xdgforeign.h @@ -1,350 +1,384 @@ /**************************************************************************** Copyright 2017 Marco Martin 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 KWAYLAND_CLIENT_XDGFOREIGN_H #define KWAYLAND_CLIENT_XDGFOREIGN_H #include "surface.h" #include #include struct zxdg_exporter_v2; struct zxdg_importer_v2; struct zxdg_exported_v2; struct zxdg_imported_v2; namespace KWayland { namespace Client { class EventQueue; class Surface; class XdgExported; class XdgImported; /** * @short Wrapper for the zxdg_exporter_v2 interface. * * This class provides a convenient wrapper for the zxdg_exporter_v2 interface. * * To use this class one needs to interact with the Registry. There are two * possible ways to create the interface: * @code * *c = registry->create(name, version); * @endcode * * This creates the and sets it up directly. As an alternative this * can also be done in a more low level way: * @code * *c = new ; * c->setup(registry->bind(name, version)); * @endcode * * The can be used as a drop-in replacement for any zxdg_exporter_v2 * pointer as it provides matching cast operators. * * @see Registry **/ class KWAYLANDCLIENT_EXPORT XdgExporter : public QObject { Q_OBJECT public: virtual ~XdgExporter(); /** * Setup this to manage the @p . * When using Registry::create there is no need to call this * method. **/ void setup(zxdg_exporter_v2 *); /** * @returns @c true if managing a zxdg_exporter_v2. **/ bool isValid() const; /** * Releases the zxdg_exporter_v2 interface. * After the interface has been released the instance is no * longer valid and can be setup with another zxdg_exporter_v2 interface. **/ void release(); /** * Destroys the data held by this . * This method is supposed to be used when the connection to the Wayland * server goes away. If the connection is not valid anymore, it's not * possible to call release anymore as that calls into the Wayland * connection and the call would fail. This method cleans up the data, so * that the instance can be deleted or set up to a new zxdg_exporter_v2 interface * once there is a new connection available. * * It is suggested to connect this method to ConnectionThread::connectionDied: * @code * connect(connection, &ConnectionThread::connectionDied, , &::destroy); * @endcode * * @see release **/ void destroy(); /** * Sets the @p queue to use for creating objects with this . **/ void setEventQueue(EventQueue *queue); /** * @returns The event queue to use for creating objects with this . **/ EventQueue *eventQueue(); - XdgExported *exportSurface(Surface *surface, QObject *parent = nullptr); + /** + * The export request exports the passed surface so that it can later be + * imported via XdgImporter::importTopLevel. + * A surface may be exported multiple times, and each exported handle may + * be used to create an XdgImported multiple times. + * @param surface the surface which we want to export an handle. + * @param parent the parent in the QObject's hierarchy of the new XdgExported + */ + XdgExported *exportTopLevel(Surface *surface, QObject *parent = nullptr); operator zxdg_exporter_v2*(); operator zxdg_exporter_v2*() const; Q_SIGNALS: /** * The corresponding global for this interface on the Registry got removed. * * This signal gets only emitted if the got created by * Registry::create **/ void removed(); protected: class Private; explicit XdgExporter(Private *p, QObject *parent = nullptr); QScopedPointer d; }; /** * @short Wrapper for the zxdg_importer_v2 interface. * * This class provides a convenient wrapper for the zxdg_importer_v2 interface. * * To use this class one needs to interact with the Registry. There are two * possible ways to create the interface: * @code * *c = registry->create(name, version); * @endcode * * This creates the and sets it up directly. As an alternative this * can also be done in a more low level way: * @code * *c = new ; * c->setup(registry->bind(name, version)); * @endcode * * The can be used as a drop-in replacement for any zxdg_importer_v2 * pointer as it provides matching cast operators. * * @see Registry **/ class KWAYLANDCLIENT_EXPORT XdgImporter : public QObject { Q_OBJECT public: virtual ~XdgImporter(); /** * Setup this to manage the @p . * When using Registry::create there is no need to call this * method. **/ void setup(zxdg_importer_v2 *); /** * @returns @c true if managing a zxdg_importer_v2. **/ bool isValid() const; /** * Releases the zxdg_importer_v2 interface. * After the interface has been released the instance is no * longer valid and can be setup with another zxdg_importer_v2 interface. **/ void release(); /** * Destroys the data held by this . * This method is supposed to be used when the connection to the Wayland * server goes away. If the connection is not valid anymore, it's not * possible to call release anymore as that calls into the Wayland * connection and the call would fail. This method cleans up the data, so * that the instance can be deleted or set up to a new zxdg_importer_v2 interface * once there is a new connection available. * * It is suggested to connect this method to ConnectionThread::connectionDied: * @code * connect(connection, &ConnectionThread::connectionDied, , &::destroy); * @endcode * * @see release **/ void destroy(); /** * Sets the @p queue to use for creating objects with this . **/ void setEventQueue(EventQueue *queue); /** * @returns The event queue to use for creating objects with this . **/ EventQueue *eventQueue(); - XdgImported *import(const QString & handle, QObject *parent = nullptr); + /** + * Imports a surface from any client given a handle + * retrieved by exporting said surface using XdgExporter::exportTopLevel. + * When called, a new XdgImported object will be created. + * This new object represents the imported surface, and the importing + * client can manipulate its relationship using it. + * + * @param handle the unique handle that represent an exported toplevel surface. + * it has to have been generated by the XdgExporter by either this + * or some other process (which would have communicated the handle + * in some way, such as command line or a DBus call) + * @param parent the parent in the QObject's hierarchy of the new XdgImported + */ + XdgImported *importTopLevel(const QString & handle, QObject *parent = nullptr); operator zxdg_importer_v2*(); operator zxdg_importer_v2*() const; Q_SIGNALS: /** * The corresponding global for this interface on the Registry got removed. * * This signal gets only emitted if the got created by * Registry::create **/ void removed(); protected: class Private; explicit XdgImporter(Private *p, QObject *parent = nullptr); QScopedPointer d; }; class KWAYLANDCLIENT_EXPORT XdgExported : public QObject { Q_OBJECT public: virtual ~XdgExported(); /** * Setup this to manage the @p . * When using ::create there is no need to call this * method. **/ void setup(zxdg_exported_v2 *); /** * @returns @c true if managing a zxdg_exported_v2. **/ bool isValid() const; /** * Releases the zxdg_exported_v2 interface. * After the interface has been released the instance is no * longer valid and can be setup with another zxdg_exported_v2 interface. **/ void release(); /** * Destroys the data held by this . * This method is supposed to be used when the connection to the Wayland * server goes away. If the connection is not valid anymore, it's not * possible to call release anymore as that calls into the Wayland * connection and the call would fail. This method cleans up the data, so * that the instance can be deleted or set up to a new zxdg_exported_v2 interface * once there is a new connection available. * * It is suggested to connect this method to ConnectionThread::connectionDied: * @code * connect(connection, &ConnectionThread::connectionDied, , &::destroy); * @endcode * * @see release **/ void destroy(); + /** + * @returns The unique handle corresponding tho this exported surface. + * Any process can import this toplevel surface provided they know this + * unique string. + */ QString handle() const; operator zxdg_exported_v2*(); operator zxdg_exported_v2*() const; Q_SIGNALS: /** * Emitted when the exported window is fully initialized. * the handle will be valid at this point **/ void done(); protected: friend class XdgExporter; class Private; explicit XdgExported(Private *p, QObject *parent = nullptr); QScopedPointer d; }; class KWAYLANDCLIENT_EXPORT XdgImported : public QObject { Q_OBJECT public: virtual ~XdgImported(); /** * Setup this to manage the @p . * When using ::create there is no need to call this * method. **/ void setup(zxdg_imported_v2 *); /** * @returns @c true if managing a zxdg_imported_v2. **/ bool isValid() const; /** * Releases the zxdg_imported_v2 interface. * After the interface has been released the instance is no * longer valid and can be setup with another zxdg_imported_v2 interface. **/ void release(); /** * Destroys the data held by this . * This method is supposed to be used when the connection to the Wayland * server goes away. If the connection is not valid anymore, it's not * possible to call release anymore as that calls into the Wayland * connection and the call would fail. This method cleans up the data, so * that the instance can be deleted or set up to a new zxdg_imported_v2 interface * once there is a new connection available. * * It is suggested to connect this method to ConnectionThread::connectionDied: * @code * connect(connection, &ConnectionThread::connectionDied, , &::destroy); * @endcode * * @see release **/ void destroy(); + /** + * Set the imported surface as the parent of some surface of the client. + * The passed surface must be a toplevel xdg_surface. + * Calling this function sets up a surface to surface relation with the same + * stacking and positioning semantics as XdgShellSurface::setTransientFor + * + * @param surface the child surface, which must belong to this process. + */ void setParentOf(Surface *surface); operator zxdg_imported_v2*(); operator zxdg_imported_v2*() const; Q_SIGNALS: /** * Emitted when the imported surface is not valid anymore, * for instance because it's no longer exported on the other end */ void importedDestroyed(); protected: friend class XdgImporter; class Private; explicit XdgImported(Private *p, QObject *parent = nullptr); QScopedPointer d; }; } } #endif diff --git a/src/server/xdgforeign_interface.h b/src/server/xdgforeign_interface.h index 1be4129..07ec6dd 100644 --- a/src/server/xdgforeign_interface.h +++ b/src/server/xdgforeign_interface.h @@ -1,76 +1,89 @@ /**************************************************************************** Copyright 2017 Marco Martin 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 KWAYLAND_SERVER_XDGFOREIGN_H #define KWAYLAND_SERVER_XDGFOREIGN_H #include "global.h" #include "resource.h" #include namespace KWayland { namespace Server { class Display; class SurfaceInterface; class XdgExporterUnstableV2Interface; class XdgImporterUnstableV2Interface; +/** + * This class encapsulates the server side logic of the XdgForeign protocol. + * a process can export a surface to be identifiable by a server-wide unique + * string handle, and another process can in turn import that surface, and set it + * as transient parent for one of its own surfaces. + * This parent relationship is traced by the transientChanged signal and the + * transientFor method. + * + * @since 5.40 + */ class KWAYLANDSERVER_EXPORT XdgForeignInterface : public QObject { Q_OBJECT public: XdgForeignInterface(Display *display, QObject *parent = nullptr); ~XdgForeignInterface(); void create(); bool isValid(); /** - * if a client did set + * If a client did import a surface and set one of its own as child of the + * imported one, this returns the mapping. + * @param surface the child surface we want to search an imported transientParent for. + * @returns the transient parent of the surface, if found, nullptr otherwise. */ SurfaceInterface *transientFor(SurfaceInterface *surface); Q_SIGNALS: /** * A surface got a new imported transient parent * @param parent is the surface exported by one client and imported into another, which will act as parent. * @param child is the surface that the importer client did set as child of the surface * that it imported. * If one of the two paramenters is nullptr, it means that a previously relation is not * valid anymore and either one of the surfaces has been unmapped, or the parent surface * is not exported anymore. */ void transientChanged(KWayland::Server::SurfaceInterface *child, KWayland::Server::SurfaceInterface *parent); private: friend class Display; friend class XdgExporterUnstableV2Interface; friend class XdgImporterUnstableV2Interface; class Private; Private *d; }; } } #endif diff --git a/tests/xdgforeigntest.cpp b/tests/xdgforeigntest.cpp index 922ed63..6101408 100644 --- a/tests/xdgforeigntest.cpp +++ b/tests/xdgforeigntest.cpp @@ -1,208 +1,208 @@ /******************************************************************** Copyright 2016 Martin Gräßlin Copyright 2017 Marco Martin 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 "../src/client/compositor.h" #include "../src/client/connection_thread.h" #include "../src/client/event_queue.h" #include "../src/client/registry.h" #include "../src/client/shell.h" #include "../src/client/shm_pool.h" #include "../src/client/server_decoration.h" #include "../src/client/xdgshell.h" #include "../src/client/xdgforeign.h" // Qt #include #include #include #include #include using namespace KWayland::Client; class XdgForeignTest : public QObject { Q_OBJECT public: explicit XdgForeignTest(QObject *parent = nullptr); virtual ~XdgForeignTest(); void init(); private: void setupRegistry(Registry *registry); void render(); QThread *m_connectionThread; ConnectionThread *m_connectionThreadObject; EventQueue *m_eventQueue = nullptr; Compositor *m_compositor = nullptr; XdgShell *m_shell = nullptr; XdgShellSurface *m_shellSurface = nullptr; ShmPool *m_shm = nullptr; Surface *m_surface = nullptr; XdgShellSurface *m_childShellSurface = nullptr; Surface *m_childSurface = nullptr; KWayland::Client::XdgExporter *m_exporter = nullptr; KWayland::Client::XdgImporter *m_importer = nullptr; KWayland::Client::XdgExported *m_exported = nullptr; KWayland::Client::XdgImported *m_imported = nullptr; KWayland::Client::ServerSideDecorationManager *m_decoration = nullptr; }; XdgForeignTest::XdgForeignTest(QObject *parent) : QObject(parent) , m_connectionThread(new QThread(this)) , m_connectionThreadObject(new ConnectionThread()) { } XdgForeignTest::~XdgForeignTest() { m_connectionThread->quit(); m_connectionThread->wait(); m_connectionThreadObject->deleteLater(); } void XdgForeignTest::init() { connect(m_connectionThreadObject, &ConnectionThread::connected, this, [this] { m_eventQueue = new EventQueue(this); m_eventQueue->setup(m_connectionThreadObject); Registry *registry = new Registry(this); setupRegistry(registry); }, Qt::QueuedConnection ); m_connectionThreadObject->moveToThread(m_connectionThread); m_connectionThread->start(); m_connectionThreadObject->initConnection(); } void XdgForeignTest::setupRegistry(Registry *registry) { connect(registry, &Registry::compositorAnnounced, this, [this, registry](quint32 name, quint32 version) { m_compositor = registry->createCompositor(name, version, this); } ); connect(registry, &Registry::xdgShellUnstableV5Announced, this, [this, registry](quint32 name, quint32 version) { m_shell = registry->createXdgShell(name, version, this); } ); connect(registry, &Registry::shmAnnounced, this, [this, registry](quint32 name, quint32 version) { m_shm = registry->createShmPool(name, version, this); } ); connect(registry, &Registry::exporterUnstableV2Announced, this, [this, registry](quint32 name, quint32 version) { m_exporter = registry->createXdgExporter(name, version, this); m_exporter->setEventQueue(m_eventQueue); } ); connect(registry, &Registry::importerUnstableV2Announced, this, [this, registry](quint32 name, quint32 version) { m_importer = registry->createXdgImporter(name, version, this); m_importer->setEventQueue(m_eventQueue); } ); connect(registry, &Registry::serverSideDecorationManagerAnnounced, this, [this, registry](quint32 name, quint32 version) { m_decoration = registry->createServerSideDecorationManager(name, version, this); m_decoration->setEventQueue(m_eventQueue); } ); connect(registry, &Registry::interfacesAnnounced, this, [this] { Q_ASSERT(m_compositor); Q_ASSERT(m_shell); Q_ASSERT(m_shm); Q_ASSERT(m_exporter); Q_ASSERT(m_importer); m_surface = m_compositor->createSurface(this); Q_ASSERT(m_surface); auto parentDeco = m_decoration->create(m_surface, this); m_shellSurface = m_shell->createSurface(m_surface, this); Q_ASSERT(m_shellSurface); connect(m_shellSurface, &XdgShellSurface::sizeChanged, this, &XdgForeignTest::render); m_childSurface = m_compositor->createSurface(this); Q_ASSERT(m_childSurface); auto childDeco = m_decoration->create(m_childSurface, this); m_childShellSurface = m_shell->createSurface(m_childSurface, this); Q_ASSERT(m_childShellSurface); connect(m_childShellSurface, &XdgShellSurface::sizeChanged, this, &XdgForeignTest::render); - m_exported = m_exporter->exportSurface(m_surface, this); + m_exported = m_exporter->exportTopLevel(m_surface, this); Q_ASSERT(m_decoration); connect(m_exported, &XdgExported::done, this, [this]() { - m_imported = m_importer->import(m_exported->handle(), this); + m_imported = m_importer->importTopLevel(m_exported->handle(), this); m_imported->setParentOf(m_childSurface); }); render(); } ); registry->setEventQueue(m_eventQueue); registry->create(m_connectionThreadObject); registry->setup(); } void XdgForeignTest::render() { QSize size = m_shellSurface->size().isValid() ? m_shellSurface->size() : QSize(500, 500); auto buffer = m_shm->getBuffer(size, size.width() * 4).toStrongRef(); buffer->setUsed(true); QImage image(buffer->address(), size.width(), size.height(), QImage::Format_ARGB32_Premultiplied); image.fill(QColor(255, 255, 255, 255)); m_surface->attachBuffer(*buffer); m_surface->damage(QRect(QPoint(0, 0), size)); m_surface->commit(Surface::CommitFlag::None); buffer->setUsed(false); size = m_childShellSurface->size().isValid() ? m_childShellSurface->size() : QSize(200, 200); buffer = m_shm->getBuffer(size, size.width() * 4).toStrongRef(); buffer->setUsed(true); image = QImage(buffer->address(), size.width(), size.height(), QImage::Format_ARGB32_Premultiplied); image.fill(QColor(255, 0, 0, 255)); m_childSurface->attachBuffer(*buffer); m_childSurface->damage(QRect(QPoint(0, 0), size)); m_childSurface->commit(Surface::CommitFlag::None); buffer->setUsed(false); } int main(int argc, char **argv) { QCoreApplication app(argc, argv); XdgForeignTest client; client.init(); return app.exec(); } #include "xdgforeigntest.moc"