You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
558 lines
23 KiB
558 lines
23 KiB
/* |
|
SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org> |
|
|
|
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL |
|
*/ |
|
// Qt |
|
#include <QMimeType> |
|
#include <QSignalSpy> |
|
#include <QTest> |
|
|
|
#include "wayland/compositor.h" |
|
#include "wayland/datadevicemanager.h" |
|
#include "wayland/datasource.h" |
|
#include "wayland/display.h" |
|
#include "wayland/pointer.h" |
|
#include "wayland/seat.h" |
|
#include "wayland/surface.h" |
|
|
|
// KWayland |
|
#include "KWayland/Client/compositor.h" |
|
#include "KWayland/Client/connection_thread.h" |
|
#include "KWayland/Client/datadevice.h" |
|
#include "KWayland/Client/datadevicemanager.h" |
|
#include "KWayland/Client/datasource.h" |
|
#include "KWayland/Client/event_queue.h" |
|
#include "KWayland/Client/keyboard.h" |
|
#include "KWayland/Client/pointer.h" |
|
#include "KWayland/Client/registry.h" |
|
#include "KWayland/Client/seat.h" |
|
#include "KWayland/Client/surface.h" |
|
|
|
// Wayland |
|
#include <wayland-client.h> |
|
|
|
#include <unistd.h> |
|
|
|
class TestDataDevice : public QObject |
|
{ |
|
Q_OBJECT |
|
private Q_SLOTS: |
|
void init(); |
|
void cleanup(); |
|
|
|
void testCreate(); |
|
void testDrag_data(); |
|
void testDrag(); |
|
void testDragInternally_data(); |
|
void testDragInternally(); |
|
void testSetSelection(); |
|
void testSendSelectionOnSeat(); |
|
void testReplaceSource(); |
|
|
|
private: |
|
KWin::Display *m_display = nullptr; |
|
KWin::DataDeviceManagerInterface *m_dataDeviceManagerInterface = nullptr; |
|
KWin::CompositorInterface *m_compositorInterface = nullptr; |
|
KWin::SeatInterface *m_seatInterface = nullptr; |
|
KWayland::Client::ConnectionThread *m_connection = nullptr; |
|
KWayland::Client::DataDeviceManager *m_dataDeviceManager = nullptr; |
|
KWayland::Client::Compositor *m_compositor = nullptr; |
|
KWayland::Client::Seat *m_seat = nullptr; |
|
KWayland::Client::EventQueue *m_queue = nullptr; |
|
QThread *m_thread = nullptr; |
|
}; |
|
|
|
static const QString s_socketName = QStringLiteral("kwayland-test-wayland-datadevice-0"); |
|
|
|
void TestDataDevice::init() |
|
{ |
|
qRegisterMetaType<KWin::DataSourceInterface *>(); |
|
using namespace KWin; |
|
delete m_display; |
|
m_display = new KWin::Display(this); |
|
m_display->addSocketName(s_socketName); |
|
m_display->start(); |
|
QVERIFY(m_display->isRunning()); |
|
|
|
// setup connection |
|
m_connection = new KWayland::Client::ConnectionThread; |
|
QSignalSpy connectedSpy(m_connection, &KWayland::Client::ConnectionThread::connected); |
|
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()); |
|
|
|
KWayland::Client::Registry registry; |
|
QSignalSpy dataDeviceManagerSpy(®istry, &KWayland::Client::Registry::dataDeviceManagerAnnounced); |
|
QSignalSpy seatSpy(®istry, &KWayland::Client::Registry::seatAnnounced); |
|
QSignalSpy compositorSpy(®istry, &KWayland::Client::Registry::compositorAnnounced); |
|
QVERIFY(!registry.eventQueue()); |
|
registry.setEventQueue(m_queue); |
|
QCOMPARE(registry.eventQueue(), m_queue); |
|
registry.create(m_connection->display()); |
|
QVERIFY(registry.isValid()); |
|
registry.setup(); |
|
|
|
m_dataDeviceManagerInterface = new DataDeviceManagerInterface(m_display, m_display); |
|
|
|
QVERIFY(dataDeviceManagerSpy.wait()); |
|
m_dataDeviceManager = |
|
registry.createDataDeviceManager(dataDeviceManagerSpy.first().first().value<quint32>(), dataDeviceManagerSpy.first().last().value<quint32>(), this); |
|
|
|
m_seatInterface = new SeatInterface(m_display, m_display); |
|
m_seatInterface->setHasPointer(true); |
|
|
|
QVERIFY(seatSpy.wait()); |
|
m_seat = registry.createSeat(seatSpy.first().first().value<quint32>(), seatSpy.first().last().value<quint32>(), this); |
|
QVERIFY(m_seat->isValid()); |
|
QSignalSpy pointerChangedSpy(m_seat, &KWayland::Client::Seat::hasPointerChanged); |
|
QVERIFY(pointerChangedSpy.wait()); |
|
|
|
m_compositorInterface = new CompositorInterface(m_display, m_display); |
|
QVERIFY(compositorSpy.wait()); |
|
m_compositor = registry.createCompositor(compositorSpy.first().first().value<quint32>(), compositorSpy.first().last().value<quint32>(), this); |
|
QVERIFY(m_compositor->isValid()); |
|
} |
|
|
|
void TestDataDevice::cleanup() |
|
{ |
|
if (m_dataDeviceManager) { |
|
delete m_dataDeviceManager; |
|
m_dataDeviceManager = nullptr; |
|
} |
|
if (m_seat) { |
|
delete m_seat; |
|
m_seat = nullptr; |
|
} |
|
if (m_compositor) { |
|
delete m_compositor; |
|
m_compositor = 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_display; |
|
m_display = nullptr; |
|
} |
|
|
|
void TestDataDevice::testCreate() |
|
{ |
|
using namespace KWin; |
|
|
|
QSignalSpy dataDeviceCreatedSpy(m_dataDeviceManagerInterface, &KWin::DataDeviceManagerInterface::dataDeviceCreated); |
|
|
|
std::unique_ptr<KWayland::Client::DataDevice> dataDevice(m_dataDeviceManager->getDataDevice(m_seat)); |
|
QVERIFY(dataDevice->isValid()); |
|
|
|
QVERIFY(dataDeviceCreatedSpy.wait()); |
|
QCOMPARE(dataDeviceCreatedSpy.count(), 1); |
|
auto deviceInterface = dataDeviceCreatedSpy.first().first().value<DataDeviceInterface *>(); |
|
QVERIFY(deviceInterface); |
|
QCOMPARE(deviceInterface->seat(), m_seatInterface); |
|
QVERIFY(!deviceInterface->selection()); |
|
|
|
// this will probably fail, we need to make a selection client side |
|
QVERIFY(!m_seatInterface->selection()); |
|
m_seatInterface->setSelection(deviceInterface->selection()); |
|
QCOMPARE(m_seatInterface->selection(), deviceInterface->selection()); |
|
|
|
// and destroy |
|
QSignalSpy destroyedSpy(deviceInterface, &QObject::destroyed); |
|
dataDevice.reset(); |
|
QVERIFY(destroyedSpy.wait()); |
|
QVERIFY(!m_seatInterface->selection()); |
|
} |
|
|
|
void TestDataDevice::testDrag_data() |
|
{ |
|
QTest::addColumn<bool>("hasGrab"); |
|
QTest::addColumn<bool>("hasPointerFocus"); |
|
QTest::addColumn<bool>("success"); |
|
|
|
QTest::newRow("grab and focus") << true << true << true; |
|
QTest::newRow("no grab") << false << true << false; |
|
QTest::newRow("no focus") << true << false << false; |
|
QTest::newRow("no grab, no focus") << false << false << false; |
|
} |
|
|
|
void TestDataDevice::testDrag() |
|
{ |
|
using namespace KWin; |
|
std::unique_ptr<KWayland::Client::Pointer> pointer(m_seat->createPointer()); |
|
|
|
QSignalSpy dataDeviceCreatedSpy(m_dataDeviceManagerInterface, &KWin::DataDeviceManagerInterface::dataDeviceCreated); |
|
|
|
std::unique_ptr<KWayland::Client::DataDevice> dataDevice(m_dataDeviceManager->getDataDevice(m_seat)); |
|
QVERIFY(dataDevice->isValid()); |
|
|
|
QVERIFY(dataDeviceCreatedSpy.wait()); |
|
QCOMPARE(dataDeviceCreatedSpy.count(), 1); |
|
auto deviceInterface = dataDeviceCreatedSpy.first().first().value<DataDeviceInterface *>(); |
|
QVERIFY(deviceInterface); |
|
|
|
QSignalSpy dataSourceCreatedSpy(m_dataDeviceManagerInterface, &KWin::DataDeviceManagerInterface::dataSourceCreated); |
|
|
|
std::unique_ptr<KWayland::Client::DataSource> dataSource(m_dataDeviceManager->createDataSource()); |
|
QVERIFY(dataSource->isValid()); |
|
|
|
QVERIFY(dataSourceCreatedSpy.wait()); |
|
QCOMPARE(dataSourceCreatedSpy.count(), 1); |
|
auto sourceInterface = dataSourceCreatedSpy.first().first().value<DataSourceInterface *>(); |
|
QVERIFY(sourceInterface); |
|
|
|
QSignalSpy surfaceCreatedSpy(m_compositorInterface, &KWin::CompositorInterface::surfaceCreated); |
|
|
|
std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface()); |
|
QVERIFY(surface->isValid()); |
|
|
|
QVERIFY(surfaceCreatedSpy.wait()); |
|
QCOMPARE(surfaceCreatedSpy.count(), 1); |
|
auto surfaceInterface = surfaceCreatedSpy.first().first().value<SurfaceInterface *>(); |
|
|
|
// now we have all we need to start a drag operation |
|
QSignalSpy dragStartedSpy(deviceInterface, &KWin::DataDeviceInterface::dragStarted); |
|
|
|
// first we need to fake the pointer enter |
|
QFETCH(bool, hasGrab); |
|
QFETCH(bool, hasPointerFocus); |
|
QFETCH(bool, success); |
|
if (!hasGrab) { |
|
// in case we don't have grab, still generate a pointer serial to make it more interesting |
|
m_seatInterface->notifyPointerButton(Qt::LeftButton, PointerButtonState::Pressed); |
|
m_seatInterface->notifyPointerFrame(); |
|
} |
|
if (hasPointerFocus) { |
|
m_seatInterface->notifyPointerEnter(surfaceInterface, QPointF(0, 0)); |
|
} |
|
if (hasGrab) { |
|
m_seatInterface->notifyPointerButton(Qt::LeftButton, PointerButtonState::Pressed); |
|
m_seatInterface->notifyPointerFrame(); |
|
} |
|
|
|
// TODO: This test would be better, if it could also test that a client trying to guess |
|
// the last serial of a different client can't start a drag. |
|
const quint32 pointerButtonSerial = success ? m_seatInterface->pointerButtonSerial(Qt::LeftButton) : 0; |
|
|
|
QCoreApplication::processEvents(); |
|
// finally start the drag |
|
dataDevice->startDrag(pointerButtonSerial, dataSource.get(), surface.get()); |
|
QCOMPARE(dragStartedSpy.wait(500), success); |
|
QCOMPARE(!dragStartedSpy.isEmpty(), success); |
|
QCOMPARE(m_seatInterface->dragSource(), success ? sourceInterface : nullptr); |
|
QCOMPARE(m_seatInterface->dragSurface(), success ? surfaceInterface : nullptr); |
|
QVERIFY(!m_seatInterface->dragIcon()); |
|
} |
|
|
|
void TestDataDevice::testDragInternally_data() |
|
{ |
|
QTest::addColumn<bool>("hasGrab"); |
|
QTest::addColumn<bool>("hasPointerFocus"); |
|
QTest::addColumn<bool>("success"); |
|
|
|
QTest::newRow("grab and focus") << true << true << true; |
|
QTest::newRow("no grab") << false << true << false; |
|
QTest::newRow("no focus") << true << false << false; |
|
QTest::newRow("no grab, no focus") << false << false << false; |
|
} |
|
|
|
void TestDataDevice::testDragInternally() |
|
{ |
|
using namespace KWin; |
|
std::unique_ptr<KWayland::Client::Pointer> pointer(m_seat->createPointer()); |
|
|
|
QSignalSpy dataDeviceCreatedSpy(m_dataDeviceManagerInterface, &KWin::DataDeviceManagerInterface::dataDeviceCreated); |
|
|
|
std::unique_ptr<KWayland::Client::DataDevice> dataDevice(m_dataDeviceManager->getDataDevice(m_seat)); |
|
QVERIFY(dataDevice->isValid()); |
|
|
|
QVERIFY(dataDeviceCreatedSpy.wait()); |
|
QCOMPARE(dataDeviceCreatedSpy.count(), 1); |
|
auto deviceInterface = dataDeviceCreatedSpy.first().first().value<DataDeviceInterface *>(); |
|
QVERIFY(deviceInterface); |
|
|
|
QSignalSpy surfaceCreatedSpy(m_compositorInterface, &KWin::CompositorInterface::surfaceCreated); |
|
|
|
std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface()); |
|
QVERIFY(surface->isValid()); |
|
|
|
QVERIFY(surfaceCreatedSpy.wait()); |
|
QCOMPARE(surfaceCreatedSpy.count(), 1); |
|
auto surfaceInterface = surfaceCreatedSpy.first().first().value<SurfaceInterface *>(); |
|
|
|
std::unique_ptr<KWayland::Client::Surface> iconSurface(m_compositor->createSurface()); |
|
QVERIFY(iconSurface->isValid()); |
|
|
|
QVERIFY(surfaceCreatedSpy.wait()); |
|
QCOMPARE(surfaceCreatedSpy.count(), 2); |
|
auto iconSurfaceInterface = surfaceCreatedSpy.last().first().value<SurfaceInterface *>(); |
|
|
|
// now we have all we need to start a drag operation |
|
QSignalSpy dragStartedSpy(deviceInterface, &KWin::DataDeviceInterface::dragStarted); |
|
|
|
// first we need to fake the pointer enter |
|
QFETCH(bool, hasGrab); |
|
QFETCH(bool, hasPointerFocus); |
|
QFETCH(bool, success); |
|
if (!hasGrab) { |
|
// in case we don't have grab, still generate a pointer serial to make it more interesting |
|
m_seatInterface->notifyPointerButton(Qt::LeftButton, PointerButtonState::Pressed); |
|
m_seatInterface->notifyPointerFrame(); |
|
} |
|
if (hasPointerFocus) { |
|
m_seatInterface->notifyPointerEnter(surfaceInterface, QPointF(0, 0)); |
|
} |
|
if (hasGrab) { |
|
m_seatInterface->notifyPointerButton(Qt::LeftButton, PointerButtonState::Pressed); |
|
m_seatInterface->notifyPointerFrame(); |
|
} |
|
|
|
// TODO: This test would be better, if it could also test that a client trying to guess |
|
// the last serial of a different client can't start a drag. |
|
const quint32 pointerButtonSerial = success ? m_seatInterface->pointerButtonSerial(Qt::LeftButton) : 0; |
|
|
|
QCoreApplication::processEvents(); |
|
// finally start the internal drag |
|
dataDevice->startDragInternally(pointerButtonSerial, surface.get(), iconSurface.get()); |
|
QCOMPARE(dragStartedSpy.wait(500), success); |
|
QCOMPARE(!dragStartedSpy.isEmpty(), success); |
|
QVERIFY(!m_seatInterface->dragSource()); |
|
QCOMPARE(m_seatInterface->dragSurface(), success ? surfaceInterface : nullptr); |
|
|
|
if (success) { |
|
QCOMPARE(m_seatInterface->dragIcon()->surface(), iconSurfaceInterface); |
|
} else { |
|
QCOMPARE(m_seatInterface->dragIcon(), nullptr); |
|
} |
|
} |
|
|
|
void TestDataDevice::testSetSelection() |
|
{ |
|
using namespace KWin; |
|
std::unique_ptr<KWayland::Client::Pointer> pointer(m_seat->createPointer()); |
|
|
|
QSignalSpy dataDeviceCreatedSpy(m_dataDeviceManagerInterface, &KWin::DataDeviceManagerInterface::dataDeviceCreated); |
|
|
|
std::unique_ptr<KWayland::Client::DataDevice> dataDevice(m_dataDeviceManager->getDataDevice(m_seat)); |
|
QVERIFY(dataDevice->isValid()); |
|
|
|
QVERIFY(dataDeviceCreatedSpy.wait()); |
|
QCOMPARE(dataDeviceCreatedSpy.count(), 1); |
|
auto deviceInterface = dataDeviceCreatedSpy.first().first().value<DataDeviceInterface *>(); |
|
QVERIFY(deviceInterface); |
|
|
|
QSignalSpy dataSourceCreatedSpy(m_dataDeviceManagerInterface, &KWin::DataDeviceManagerInterface::dataSourceCreated); |
|
|
|
std::unique_ptr<KWayland::Client::DataSource> dataSource(m_dataDeviceManager->createDataSource()); |
|
QVERIFY(dataSource->isValid()); |
|
dataSource->offer(QStringLiteral("text/plain")); |
|
|
|
QVERIFY(dataSourceCreatedSpy.wait()); |
|
QCOMPARE(dataSourceCreatedSpy.count(), 1); |
|
auto sourceInterface = dataSourceCreatedSpy.first().first().value<DataSourceInterface *>(); |
|
QVERIFY(sourceInterface); |
|
|
|
// everything setup, now we can test setting the selection |
|
QSignalSpy selectionChangedSpy(deviceInterface, &KWin::DataDeviceInterface::selectionChanged); |
|
|
|
QVERIFY(!deviceInterface->selection()); |
|
dataDevice->setSelection(1, dataSource.get()); |
|
QVERIFY(selectionChangedSpy.wait()); |
|
QCOMPARE(selectionChangedSpy.count(), 1); |
|
QCOMPARE(selectionChangedSpy.first().first().value<DataSourceInterface *>(), sourceInterface); |
|
QCOMPARE(deviceInterface->selection(), sourceInterface); |
|
|
|
// send selection to datadevice |
|
QSignalSpy selectionOfferedSpy(dataDevice.get(), &KWayland::Client::DataDevice::selectionOffered); |
|
deviceInterface->sendSelection(deviceInterface->selection()); |
|
QVERIFY(selectionOfferedSpy.wait()); |
|
QCOMPARE(selectionOfferedSpy.count(), 1); |
|
auto dataOffer = selectionOfferedSpy.first().first().value<KWayland::Client::DataOffer *>(); |
|
QVERIFY(dataOffer); |
|
QCOMPARE(dataOffer->offeredMimeTypes().count(), 1); |
|
QCOMPARE(dataOffer->offeredMimeTypes().first().name(), QStringLiteral("text/plain")); |
|
|
|
// sending a new mimetype to the selection, should be announced in the offer |
|
QSignalSpy mimeTypeAddedSpy(dataOffer, &KWayland::Client::DataOffer::mimeTypeOffered); |
|
dataSource->offer(QStringLiteral("text/html")); |
|
QVERIFY(mimeTypeAddedSpy.wait()); |
|
QCOMPARE(mimeTypeAddedSpy.count(), 1); |
|
QCOMPARE(mimeTypeAddedSpy.first().first().toString(), QStringLiteral("text/html")); |
|
QCOMPARE(dataOffer->offeredMimeTypes().count(), 2); |
|
QCOMPARE(dataOffer->offeredMimeTypes().first().name(), QStringLiteral("text/plain")); |
|
QCOMPARE(dataOffer->offeredMimeTypes().last().name(), QStringLiteral("text/html")); |
|
|
|
// now clear the selection |
|
dataDevice->clearSelection(1); |
|
QVERIFY(selectionChangedSpy.wait()); |
|
QCOMPARE(selectionChangedSpy.count(), 2); |
|
QVERIFY(!deviceInterface->selection()); |
|
|
|
// set another selection |
|
dataDevice->setSelection(2, dataSource.get()); |
|
QVERIFY(selectionChangedSpy.wait()); |
|
// now unbind the dataDevice |
|
QSignalSpy unboundSpy(deviceInterface, &QObject::destroyed); |
|
dataDevice.reset(); |
|
QVERIFY(unboundSpy.wait()); |
|
} |
|
|
|
void TestDataDevice::testSendSelectionOnSeat() |
|
{ |
|
// this test verifies that the selection is sent when setting a focused keyboard |
|
using namespace KWin; |
|
// first add keyboard support to Seat |
|
QSignalSpy keyboardChangedSpy(m_seat, &KWayland::Client::Seat::hasKeyboardChanged); |
|
m_seatInterface->setHasKeyboard(true); |
|
QVERIFY(keyboardChangedSpy.wait()); |
|
// now create DataDevice, Keyboard and a Surface |
|
QSignalSpy dataDeviceCreatedSpy(m_dataDeviceManagerInterface, &DataDeviceManagerInterface::dataDeviceCreated); |
|
std::unique_ptr<KWayland::Client::DataDevice> dataDevice(m_dataDeviceManager->getDataDevice(m_seat)); |
|
QVERIFY(dataDevice->isValid()); |
|
QVERIFY(dataDeviceCreatedSpy.wait()); |
|
auto serverDataDevice = dataDeviceCreatedSpy.first().first().value<DataDeviceInterface *>(); |
|
QVERIFY(serverDataDevice); |
|
std::unique_ptr<KWayland::Client::Keyboard> keyboard(m_seat->createKeyboard()); |
|
QVERIFY(keyboard->isValid()); |
|
QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); |
|
std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface()); |
|
QVERIFY(surface->isValid()); |
|
QVERIFY(surfaceCreatedSpy.wait()); |
|
|
|
auto serverSurface = surfaceCreatedSpy.first().first().value<SurfaceInterface *>(); |
|
QVERIFY(serverSurface); |
|
m_seatInterface->setFocusedKeyboardSurface(serverSurface); |
|
|
|
// now set the selection |
|
std::unique_ptr<KWayland::Client::DataSource> dataSource(m_dataDeviceManager->createDataSource()); |
|
QVERIFY(dataSource->isValid()); |
|
dataSource->offer(QStringLiteral("text/plain")); |
|
dataDevice->setSelection(1, dataSource.get()); |
|
// we should get a selection offered for that on the data device |
|
QSignalSpy selectionOfferedSpy(dataDevice.get(), &KWayland::Client::DataDevice::selectionOffered); |
|
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, &QObject::destroyed); |
|
dataDevice.reset(); |
|
QVERIFY(unboundSpy.wait()); |
|
m_seatInterface->setFocusedKeyboardSurface(serverSurface); |
|
} |
|
|
|
void TestDataDevice::testReplaceSource() |
|
{ |
|
// this test verifies that replacing a data source cancels the previous source |
|
using namespace KWin; |
|
// first add keyboard support to Seat |
|
QSignalSpy keyboardChangedSpy(m_seat, &KWayland::Client::Seat::hasKeyboardChanged); |
|
m_seatInterface->setHasKeyboard(true); |
|
QVERIFY(keyboardChangedSpy.wait()); |
|
// now create DataDevice, Keyboard and a Surface |
|
QSignalSpy dataDeviceCreatedSpy(m_dataDeviceManagerInterface, &DataDeviceManagerInterface::dataDeviceCreated); |
|
std::unique_ptr<KWayland::Client::DataDevice> dataDevice(m_dataDeviceManager->getDataDevice(m_seat)); |
|
QVERIFY(dataDevice->isValid()); |
|
QVERIFY(dataDeviceCreatedSpy.wait()); |
|
auto serverDataDevice = dataDeviceCreatedSpy.first().first().value<DataDeviceInterface *>(); |
|
QVERIFY(serverDataDevice); |
|
std::unique_ptr<KWayland::Client::Keyboard> keyboard(m_seat->createKeyboard()); |
|
QVERIFY(keyboard->isValid()); |
|
QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); |
|
std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface()); |
|
QVERIFY(surface->isValid()); |
|
QVERIFY(surfaceCreatedSpy.wait()); |
|
|
|
auto serverSurface = surfaceCreatedSpy.first().first().value<SurfaceInterface *>(); |
|
QVERIFY(serverSurface); |
|
m_seatInterface->setFocusedKeyboardSurface(serverSurface); |
|
|
|
// now set the selection |
|
std::unique_ptr<KWayland::Client::DataSource> dataSource(m_dataDeviceManager->createDataSource()); |
|
QVERIFY(dataSource->isValid()); |
|
dataSource->offer(QStringLiteral("text/plain")); |
|
dataDevice->setSelection(1, dataSource.get()); |
|
QSignalSpy sourceCancelledSpy(dataSource.get(), &KWayland::Client::DataSource::cancelled); |
|
// we should get a selection offered for that on the data device |
|
QSignalSpy selectionOfferedSpy(dataDevice.get(), &KWayland::Client::DataDevice::selectionOffered); |
|
QVERIFY(selectionOfferedSpy.wait()); |
|
QCOMPARE(selectionOfferedSpy.count(), 1); |
|
|
|
// create a second data source and replace previous one |
|
std::unique_ptr<KWayland::Client::DataSource> dataSource2(m_dataDeviceManager->createDataSource()); |
|
QVERIFY(dataSource2->isValid()); |
|
dataSource2->offer(QStringLiteral("text/plain")); |
|
QSignalSpy sourceCancelled2Spy(dataSource2.get(), &KWayland::Client::DataSource::cancelled); |
|
dataDevice->setSelection(1, dataSource2.get()); |
|
QCOMPARE(selectionOfferedSpy.count(), 1); |
|
QVERIFY(sourceCancelledSpy.wait()); |
|
QCOMPARE(selectionOfferedSpy.count(), 2); |
|
QVERIFY(sourceCancelled2Spy.isEmpty()); |
|
|
|
// replace the data source with itself, ensure that it did not get cancelled |
|
dataDevice->setSelection(1, dataSource2.get()); |
|
QVERIFY(!sourceCancelled2Spy.wait(500)); |
|
QCOMPARE(selectionOfferedSpy.count(), 2); |
|
QVERIFY(sourceCancelled2Spy.isEmpty()); |
|
|
|
// create a new DataDevice and replace previous one |
|
std::unique_ptr<KWayland::Client::DataDevice> dataDevice2(m_dataDeviceManager->getDataDevice(m_seat)); |
|
QVERIFY(dataDevice2->isValid()); |
|
std::unique_ptr<KWayland::Client::DataSource> dataSource3(m_dataDeviceManager->createDataSource()); |
|
QVERIFY(dataSource3->isValid()); |
|
dataSource3->offer(QStringLiteral("text/plain")); |
|
dataDevice2->setSelection(1, dataSource3.get()); |
|
QVERIFY(sourceCancelled2Spy.wait()); |
|
|
|
// try to crash by first destroying dataSource3 and setting a new DataSource |
|
std::unique_ptr<KWayland::Client::DataSource> dataSource4(m_dataDeviceManager->createDataSource()); |
|
QVERIFY(dataSource4->isValid()); |
|
dataSource4->offer(QStringLiteral("text/plain")); |
|
dataSource3.reset(); |
|
dataDevice2->setSelection(1, dataSource4.get()); |
|
QVERIFY(selectionOfferedSpy.wait()); |
|
|
|
auto dataOffer = selectionOfferedSpy.last()[0].value<KWayland::Client::DataOffer *>(); |
|
|
|
// try to crash by destroying the data source, then requesting data |
|
dataSource4.reset(); |
|
int pipeFds[2] = {0, 0}; |
|
QVERIFY(pipe(pipeFds) == 0); |
|
|
|
dataOffer->receive(QStringLiteral("text/plain"), pipeFds[1]); |
|
close(pipeFds[1]); |
|
|
|
// spin the event loop, nothing should explode |
|
QTest::qWait(10); |
|
|
|
close(pipeFds[0]); |
|
} |
|
|
|
QTEST_GUILESS_MAIN(TestDataDevice) |
|
#include "test_datadevice.moc"
|
|
|