diff --git a/autotests/client/test_datadevice.cpp b/autotests/client/test_datadevice.cpp --- a/autotests/client/test_datadevice.cpp +++ b/autotests/client/test_datadevice.cpp @@ -26,6 +26,7 @@ #include "../../src/client/datadevicemanager.h" #include "../../src/client/datasource.h" #include "../../src/client/compositor.h" +#include "../../src/client/keyboard.h" #include "../../src/client/pointer.h" #include "../../src/client/registry.h" #include "../../src/client/seat.h" @@ -51,6 +52,7 @@ void testDrag(); void testDragInternally(); void testSetSelection(); + void testSendSelectionOnSeat(); void testDestroy(); private: @@ -380,6 +382,63 @@ QVERIFY(!deviceInterface->selection()); } +void TestDataDevice::testSendSelectionOnSeat() +{ + // this test verifies that the selection is sent when setting a focused keyboard + using namespace KWayland::Client; + using namespace KWayland::Server; + // first add keyboard support to Seat + QSignalSpy keyboardChangedSpy(m_seat, &Seat::hasKeyboardChanged); + QVERIFY(keyboardChangedSpy.isValid()); + m_seatInterface->setHasKeyboard(true); + QVERIFY(keyboardChangedSpy.wait()); + // now create DataDevice, Keyboard and a Surface + QSignalSpy dataDeviceCreatedSpy(m_dataDeviceManagerInterface, &DataDeviceManagerInterface::dataDeviceCreated); + QVERIFY(dataDeviceCreatedSpy.isValid()); + QScopedPointer dataDevice(m_dataDeviceManager->getDataDevice(m_seat)); + QVERIFY(dataDevice->isValid()); + QVERIFY(dataDeviceCreatedSpy.wait()); + auto serverDataDevice = dataDeviceCreatedSpy.first().first().value(); + QVERIFY(serverDataDevice); + QScopedPointer keyboard(m_seat->createKeyboard()); + QVERIFY(keyboard->isValid()); + QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); + QVERIFY(surfaceCreatedSpy.isValid()); + QScopedPointer surface(m_compositor->createSurface()); + QVERIFY(surface->isValid()); + QVERIFY(surfaceCreatedSpy.wait()); + + auto serverSurface = surfaceCreatedSpy.first().first().value(); + QVERIFY(serverSurface); + m_seatInterface->setFocusedKeyboardSurface(serverSurface); + + // now set the selection + QScopedPointer dataSource(m_dataDeviceManager->createDataSource()); + QVERIFY(dataSource->isValid()); + dataSource->offer(QStringLiteral("text/plain")); + dataDevice->setSelection(1, dataSource.data()); + // we should get a selection offered for that on the data device + QSignalSpy selectionOfferedSpy(dataDevice.data(), &DataDevice::selectionOffered); + QVERIFY(selectionOfferedSpy.isValid()); + QVERIFY(selectionOfferedSpy.wait()); + QCOMPARE(selectionOfferedSpy.count(), 1); + + // now unfocus the keyboard + m_seatInterface->setFocusedKeyboardSurface(nullptr); + // if setting the same surface again, we should get another offer + m_seatInterface->setFocusedKeyboardSurface(serverSurface); + QVERIFY(selectionOfferedSpy.wait()); + QCOMPARE(selectionOfferedSpy.count(), 2); + + // now let's try to destroy the data device and set a focused keyboard just while the data device is being destroyedd + m_seatInterface->setFocusedKeyboardSurface(nullptr); + QSignalSpy unboundSpy(serverDataDevice, &Resource::unbound); + QVERIFY(unboundSpy.isValid()); + dataDevice.reset(); + QVERIFY(unboundSpy.wait()); + m_seatInterface->setFocusedKeyboardSurface(serverSurface); +} + void TestDataDevice::testDestroy() { using namespace KWayland::Client; diff --git a/src/server/datadevice_interface.cpp b/src/server/datadevice_interface.cpp --- a/src/server/datadevice_interface.cpp +++ b/src/server/datadevice_interface.cpp @@ -130,6 +130,9 @@ DataOfferInterface *DataDeviceInterface::Private::createDataOffer(DataSourceInterface *source) { + if (!resource) { + return nullptr; + } Q_Q(DataDeviceInterface); DataOfferInterface *offer = new DataOfferInterface(source, q, resource); auto c = q->global()->display()->getConnection(wl_resource_get_client(resource)); diff --git a/src/server/resource.h b/src/server/resource.h --- a/src/server/resource.h +++ b/src/server/resource.h @@ -74,6 +74,14 @@ **/ quint32 id() const; +Q_SIGNALS: + /** + * This signal is emitted when the client unbound this Resource. + * The Resource will be deleted in the next event cycle after this event. + * @since 5.24 + **/ + void unbound(); + protected: class Private; explicit Resource(Private *d, QObject *parent = nullptr); diff --git a/src/server/resource.cpp b/src/server/resource.cpp --- a/src/server/resource.cpp +++ b/src/server/resource.cpp @@ -64,6 +64,7 @@ { Private *p = cast(r); p->resource = nullptr; + emit p->q->unbound(); p->q->deleteLater(); } diff --git a/src/server/seat_interface.cpp b/src/server/seat_interface.cpp --- a/src/server/seat_interface.cpp +++ b/src/server/seat_interface.cpp @@ -214,21 +214,21 @@ { Q_ASSERT(dataDevice->seat() == q); dataDevices << dataDevice; - QObject::connect(dataDevice, &QObject::destroyed, q, - [this, dataDevice] { - dataDevices.removeAt(dataDevices.indexOf(dataDevice)); - if (keys.focus.selection == dataDevice) { - keys.focus.selection = nullptr; - } - if (currentSelection == dataDevice) { - // current selection is cleared - currentSelection = nullptr; - if (keys.focus.selection) { - keys.focus.selection->sendClearSelection(); - } + auto dataDeviceCleanup = [this, dataDevice] { + dataDevices.removeOne(dataDevice); + if (keys.focus.selection == dataDevice) { + keys.focus.selection = nullptr; + } + if (currentSelection == dataDevice) { + // current selection is cleared + currentSelection = nullptr; + if (keys.focus.selection) { + keys.focus.selection->sendClearSelection(); } } - ); + }; + QObject::connect(dataDevice, &QObject::destroyed, q, dataDeviceCleanup); + QObject::connect(dataDevice, &Resource::unbound, q, dataDeviceCleanup); QObject::connect(dataDevice, &DataDeviceInterface::selectionChanged, q, [this, dataDevice] { updateSelection(dataDevice, true);