Files
RedBear-OS/local/recipes/kde/kwin/source-pristine/autotests/integration/selection_test.cpp
T

992 lines
52 KiB
C++

/*
SPDX-FileCopyrightText: 2025 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "kwin_wayland_test.h"
#include "core/output.h"
#include "pointer_input.h"
#include "utils/pipe.h"
#include "wayland_server.h"
#include "window.h"
#include "workspace.h"
#include <KWayland/Client/datadevice.h>
#include <KWayland/Client/dataoffer.h>
#include <KWayland/Client/datasource.h>
#include <KWayland/Client/pointer.h>
#include <KWayland/Client/seat.h>
#include <KWayland/Client/surface.h>
#include <QFile>
#include <QFuture>
#include <QFutureWatcher>
#include <QMimeDatabase>
#include <QMimeType>
#include <QtConcurrent>
#include <fcntl.h>
#include <linux/input-event-codes.h>
namespace KWin
{
static const QString s_socketName = QStringLiteral("wayland_test_selection-0");
template<typename Offer>
static QFuture<QByteArray> readMimeTypeData(const Offer &offer, const QMimeType &mimeType)
{
auto pipe = Pipe::create(O_CLOEXEC);
if (!pipe) {
return QFuture<QByteArray>();
}
offer->receive(mimeType.name(), pipe->writeEndpoint.get());
return QtConcurrent::run([fd = std::move(pipe->readEndpoint)] {
QFile file;
if (!file.open(fd.get(), QFile::ReadOnly | QFile::Text)) {
return QByteArray();
}
return file.readAll();
});
}
template<typename T>
static bool waitFuture(const QFuture<T> &future)
{
QFutureWatcher<T> watcher;
QSignalSpy finishedSpy(&watcher, &QFutureWatcher<T>::finished);
watcher.setFuture(future);
return finishedSpy.wait();
}
static QMimeType plainText()
{
static QMimeType mimeType = QMimeDatabase().mimeTypeForName(QStringLiteral("text/plain"));
return mimeType;
}
static QMimeType htmlText()
{
static QMimeType mimeType = QMimeDatabase().mimeTypeForName(QStringLiteral("text/html"));
return mimeType;
}
class SelectionTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void init();
void selection();
void internalSelection();
void destroySelection();
void invalidSerialForSelection();
void unsetSupersededSelection();
void receiveFromWithdrawnSelectionOffer();
void singleSelectionPerClient();
void primarySelection();
void internalPrimarySelection();
void destroyPrimarySelection();
void invalidSerialForPrimarySelection();
void unsetSupersededPrimarySelection();
void receiveFromWithdrawnPrimarySelectionOffer();
void singlePrimarySelectionPerClient();
};
void SelectionTest::initTestCase()
{
qRegisterMetaType<Window *>();
QVERIFY(waylandServer()->init(s_socketName));
kwinApp()->start();
Test::setOutputConfig({
Rect(0, 0, 1280, 1024),
Rect(1280, 0, 1280, 1024),
});
const auto outputs = workspace()->outputs();
QCOMPARE(outputs.count(), 2);
QCOMPARE(outputs[0]->geometry(), Rect(0, 0, 1280, 1024));
QCOMPARE(outputs[1]->geometry(), Rect(1280, 0, 1280, 1024));
}
void SelectionTest::init()
{
workspace()->setActiveOutput(QPoint(640, 512));
input()->pointer()->warp(QPoint(640, 512));
}
void SelectionTest::selection()
{
// This test verifies that the clipboard works as expected between two clients.
auto sourceConnection = Test::Connection::setup(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::DataDeviceManager);
auto targetConnection = Test::Connection::setup(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::DataDeviceManager);
// Setup the source side.
std::unique_ptr<KWayland::Client::DataDevice> sourceDataDevice(sourceConnection->dataDeviceManager->getDataDevice(sourceConnection->seat));
std::unique_ptr<KWayland::Client::DataSource> dataSource(sourceConnection->dataDeviceManager->createDataSource());
dataSource->offer(plainText());
connect(dataSource.get(), &KWayland::Client::DataSource::sendDataRequested, this, [](const QString &mimeType, int fd) {
const auto data = QByteArrayLiteral("foo");
write(fd, data.data(), data.size());
close(fd);
});
auto sourceSurface = Test::createSurface(sourceConnection->compositor);
auto sourceShellSurface = Test::createXdgToplevelSurface(sourceConnection->xdgShell, sourceSurface.get());
auto sourceWindow = Test::renderAndWaitForShown(sourceConnection->shm, sourceSurface.get(), QSize(100, 100), Qt::red);
// Setup the target side.
std::unique_ptr<KWayland::Client::DataDevice> targetDataDevice(targetConnection->dataDeviceManager->getDataDevice(targetConnection->seat));
auto targetSurface = Test::createSurface(targetConnection->compositor);
auto targetShellSurface = Test::createXdgToplevelSurface(targetConnection->xdgShell, targetSurface.get());
auto targetWindow = Test::renderAndWaitForShown(targetConnection->shm, targetSurface.get(), QSize(100, 100), Qt::blue);
// Activate the source window.
std::unique_ptr<KWayland::Client::Keyboard> sourceKeyboard(sourceConnection->seat->createKeyboard());
QSignalSpy sourceKeyboardEnteredSpy(sourceKeyboard.get(), &KWayland::Client::Keyboard::entered);
workspace()->activateWindow(sourceWindow);
QVERIFY(sourceKeyboardEnteredSpy.wait());
const quint32 sourceEnteredSerial = sourceKeyboardEnteredSpy.last().at(0).value<quint32>();
// Set the selection.
sourceDataDevice->setSelection(sourceEnteredSerial, dataSource.get());
// The target device shouldn't receive an offer while it's not focused.
QSignalSpy targetDataDeviceSelectionOfferedSpy(targetDataDevice.get(), &KWayland::Client::DataDevice::selectionOffered);
QVERIFY(!targetDataDeviceSelectionOfferedSpy.wait(100));
workspace()->activateWindow(targetWindow);
QVERIFY(targetDataDeviceSelectionOfferedSpy.wait());
KWayland::Client::DataOffer *offer = targetDataDevice->offeredSelection();
QCOMPARE(offer->offeredMimeTypes(), QList<QMimeType>{plainText()});
// Ask for data.
const QFuture<QByteArray> plainData = readMimeTypeData(offer, plainText());
QVERIFY(waitFuture(plainData));
QCOMPARE(plainData.result(), QByteArrayLiteral("foo"));
// text/html hasn't been offered, so this should fail.
const QFuture<QByteArray> htmlData = readMimeTypeData(offer, htmlText());
QVERIFY(waitFuture(htmlData));
QCOMPARE(htmlData.result(), QByteArray());
// Clear selection.
QSignalSpy targetDataDeviceSelectionClearedSpy(targetDataDevice.get(), &KWayland::Client::DataDevice::selectionCleared);
sourceDataDevice->setSelection(sourceEnteredSerial, nullptr);
QVERIFY(targetDataDeviceSelectionClearedSpy.wait());
}
void SelectionTest::internalSelection()
{
// This test verifies that the source client also receives a wl_data_offer for its own selection.
auto connection = Test::Connection::setup(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::DataDeviceManager);
std::unique_ptr<KWayland::Client::DataDevice> dataDevice(connection->dataDeviceManager->getDataDevice(connection->seat));
std::unique_ptr<KWayland::Client::DataSource> dataSource(connection->dataDeviceManager->createDataSource());
dataSource->offer(plainText());
connect(dataSource.get(), &KWayland::Client::DataSource::sendDataRequested, this, [](const QString &mimeType, int fd) {
const auto data = QByteArrayLiteral("foo");
write(fd, data.data(), data.size());
close(fd);
});
auto surface = Test::createSurface(connection->compositor);
auto shellSurface = Test::createXdgToplevelSurface(connection->xdgShell, surface.get());
auto window = Test::renderAndWaitForShown(connection->shm, surface.get(), QSize(100, 100), Qt::red);
std::unique_ptr<KWayland::Client::Keyboard> keyboard(connection->seat->createKeyboard());
QSignalSpy keyboardEnteredSpy(keyboard.get(), &KWayland::Client::Keyboard::entered);
workspace()->activateWindow(window);
QVERIFY(keyboardEnteredSpy.wait());
const quint32 enteredSerial = keyboardEnteredSpy.last().at(0).value<quint32>();
// Set the selection.
dataDevice->setSelection(enteredSerial, dataSource.get());
QSignalSpy dataDeviceSelectionOfferedSpy(dataDevice.get(), &KWayland::Client::DataDevice::selectionOffered);
QVERIFY(dataDeviceSelectionOfferedSpy.wait());
KWayland::Client::DataOffer *offer = dataDevice->offeredSelection();
QCOMPARE(offer->offeredMimeTypes(), QList<QMimeType>{plainText()});
// Ask for data.
const QFuture<QByteArray> plainData = readMimeTypeData(offer, plainText());
QVERIFY(waitFuture(plainData));
QCOMPARE(plainData.result(), QByteArrayLiteral("foo"));
// text/html hasn't been offered, so this should fail.
const QFuture<QByteArray> htmlData = readMimeTypeData(offer, htmlText());
QVERIFY(waitFuture(htmlData));
QCOMPARE(htmlData.result(), QByteArray());
// Clear selection.
QSignalSpy selectionClearedSpy(dataDevice.get(), &KWayland::Client::DataDevice::selectionCleared);
dataDevice->setSelection(enteredSerial, nullptr);
QVERIFY(selectionClearedSpy.wait());
}
void SelectionTest::destroySelection()
{
// This test verifies that the wl_data_offer will be withdrawn if the associated data source is destroyed.
auto sourceConnection = Test::Connection::setup(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::DataDeviceManager);
auto targetConnection = Test::Connection::setup(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::DataDeviceManager);
// Setup the source side.
std::unique_ptr<KWayland::Client::DataDevice> sourceDataDevice(sourceConnection->dataDeviceManager->getDataDevice(sourceConnection->seat));
std::unique_ptr<KWayland::Client::DataSource> dataSource(sourceConnection->dataDeviceManager->createDataSource());
dataSource->offer(plainText());
connect(dataSource.get(), &KWayland::Client::DataSource::sendDataRequested, this, [](const QString &mimeType, int fd) {
const auto data = QByteArrayLiteral("foo");
write(fd, data.data(), data.size());
close(fd);
});
auto sourceSurface = Test::createSurface(sourceConnection->compositor);
auto sourceShellSurface = Test::createXdgToplevelSurface(sourceConnection->xdgShell, sourceSurface.get());
auto sourceWindow = Test::renderAndWaitForShown(sourceConnection->shm, sourceSurface.get(), QSize(100, 100), Qt::red);
// Setup the target side.
std::unique_ptr<KWayland::Client::DataDevice> targetDataDevice(targetConnection->dataDeviceManager->getDataDevice(targetConnection->seat));
auto targetSurface = Test::createSurface(targetConnection->compositor);
auto targetShellSurface = Test::createXdgToplevelSurface(targetConnection->xdgShell, targetSurface.get());
auto targetWindow = Test::renderAndWaitForShown(targetConnection->shm, targetSurface.get(), QSize(100, 100), Qt::blue);
// Activate the source window.
std::unique_ptr<KWayland::Client::Keyboard> sourceKeyboard(sourceConnection->seat->createKeyboard());
QSignalSpy sourceKeyboardEnteredSpy(sourceKeyboard.get(), &KWayland::Client::Keyboard::entered);
workspace()->activateWindow(sourceWindow);
QVERIFY(sourceKeyboardEnteredSpy.wait());
const quint32 sourceEnteredSerial = sourceKeyboardEnteredSpy.last().at(0).value<quint32>();
// Set the selection.
sourceDataDevice->setSelection(sourceEnteredSerial, dataSource.get());
// Activate the target window and wait for the data offer.
QSignalSpy targetDataDeviceSelectionOfferedSpy(targetDataDevice.get(), &KWayland::Client::DataDevice::selectionOffered);
workspace()->activateWindow(targetWindow);
QVERIFY(targetDataDeviceSelectionOfferedSpy.wait());
KWayland::Client::DataOffer *offer = targetDataDevice->offeredSelection();
QCOMPARE(offer->offeredMimeTypes(), QList<QMimeType>{plainText()});
// Destroy the data source.
QSignalSpy targetDataDeviceSelectionClearedSpy(targetDataDevice.get(), &KWayland::Client::DataDevice::selectionCleared);
dataSource.reset();
QVERIFY(targetDataDeviceSelectionClearedSpy.wait());
}
void SelectionTest::invalidSerialForSelection()
{
// This test verifies that a set_selection request will be ignored if the specified serial is invalid.
auto connection = Test::Connection::setup(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::DataDeviceManager);
std::unique_ptr<KWayland::Client::DataDevice> dataDevice(connection->dataDeviceManager->getDataDevice(connection->seat));
std::unique_ptr<KWayland::Client::DataSource> dataSource(connection->dataDeviceManager->createDataSource());
dataSource->offer(QStringLiteral("text/plain"));
connect(dataSource.get(), &KWayland::Client::DataSource::sendDataRequested, this, [](const QString &mimeType, int fd) {
const auto data = QByteArrayLiteral("foo");
write(fd, data.data(), data.size());
close(fd);
});
auto surface = Test::createSurface(connection->compositor);
auto shellSurface = Test::createXdgToplevelSurface(connection->xdgShell, surface.get());
Test::renderAndWaitForShown(connection->shm, surface.get(), QSize(100, 100), Qt::red);
std::unique_ptr<KWayland::Client::Keyboard> keyboard(connection->seat->createKeyboard());
QSignalSpy keyboardEnteredSpy(keyboard.get(), &KWayland::Client::Keyboard::entered);
QVERIFY(keyboardEnteredSpy.wait());
const quint32 enteredSerial = keyboardEnteredSpy.last().at(0).value<quint32>();
// Set the selection.
QSignalSpy dataSourceCancelledSpy(dataSource.get(), &KWayland::Client::DataSource::cancelled);
dataDevice->setSelection(enteredSerial - 100, dataSource.get());
QVERIFY(dataSourceCancelledSpy.wait(100));
}
void SelectionTest::unsetSupersededSelection()
{
// This test verifies that the current selection won't be unset if the previous selection is unset with set_selection(null).
// Setup the first client.
auto firstConnection = Test::Connection::setup(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::DataDeviceManager);
QVERIFY(Test::waitForWaylandKeyboard(firstConnection->seat));
std::unique_ptr<KWayland::Client::Keyboard> firstKeyboard(firstConnection->seat->createKeyboard());
std::unique_ptr<KWayland::Client::DataDevice> firstDataDevice(firstConnection->dataDeviceManager->getDataDevice(firstConnection->seat));
std::unique_ptr<KWayland::Client::DataSource> firstDataSource(firstConnection->dataDeviceManager->createDataSource());
firstDataSource->offer(plainText());
connect(firstDataSource.get(), &KWayland::Client::DataSource::sendDataRequested, this, [](const QString &mimeType, int fd) {
const auto data = QByteArrayLiteral("foo");
write(fd, data.data(), data.size());
close(fd);
});
auto firstSurface = Test::createSurface(firstConnection->compositor);
auto firstShellSurface = Test::createXdgToplevelSurface(firstConnection->xdgShell, firstSurface.get());
auto firstWindow = Test::renderAndWaitForShown(firstConnection->shm, firstSurface.get(), QSize(100, 100), Qt::red);
// Setup the second client.
auto secondConnection = Test::Connection::setup(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::DataDeviceManager);
QVERIFY(Test::waitForWaylandKeyboard(secondConnection->seat));
std::unique_ptr<KWayland::Client::Keyboard> secondKeyboard(secondConnection->seat->createKeyboard());
std::unique_ptr<KWayland::Client::DataDevice> secondDataDevice(secondConnection->dataDeviceManager->getDataDevice(secondConnection->seat));
std::unique_ptr<KWayland::Client::DataSource> secondDataSource(secondConnection->dataDeviceManager->createDataSource());
secondDataSource->offer(htmlText());
connect(secondDataSource.get(), &KWayland::Client::DataSource::sendDataRequested, this, [](const QString &mimeType, int fd) {
const auto data = QByteArrayLiteral("bar");
write(fd, data.data(), data.size());
close(fd);
});
auto secondSurface = Test::createSurface(secondConnection->compositor);
auto secondShellSurface = Test::createXdgToplevelSurface(secondConnection->xdgShell, secondSurface.get());
auto secondWindow = Test::renderAndWaitForShown(secondConnection->shm, secondSurface.get(), QSize(100, 100), Qt::blue);
// Activate the first window and set the selection.
QSignalSpy firstKeyboardEnteredSpy(firstKeyboard.get(), &KWayland::Client::Keyboard::entered);
QSignalSpy firstDataDeviceSelectionOfferedSpy(firstDataDevice.get(), &KWayland::Client::DataDevice::selectionOffered);
workspace()->activateWindow(firstWindow);
QVERIFY(firstKeyboardEnteredSpy.wait());
const quint32 firstEnteredSerial = firstKeyboardEnteredSpy.last().at(0).value<quint32>();
firstDataDevice->setSelection(firstEnteredSerial, firstDataSource.get());
QVERIFY(firstDataDeviceSelectionOfferedSpy.wait());
// Activate the second window and set the selection.
QSignalSpy secondKeyboardEnteredSpy(secondKeyboard.get(), &KWayland::Client::Keyboard::entered);
QSignalSpy secondDataDeviceSelectionOfferedSpy(secondDataDevice.get(), &KWayland::Client::DataDevice::selectionOffered);
workspace()->activateWindow(secondWindow);
QVERIFY(secondKeyboardEnteredSpy.wait());
const quint32 secondEnteredSerial = secondKeyboardEnteredSpy.last().at(0).value<quint32>();
secondDataDevice->setSelection(secondEnteredSerial, secondDataSource.get());
QVERIFY(secondDataDeviceSelectionOfferedSpy.wait());
// Attempt to unset the first selection.
QSignalSpy secondDataDeviceSelectionClearedSpy(secondDataDevice.get(), &KWayland::Client::DataDevice::selectionCleared);
firstDataDevice->setSelection(firstEnteredSerial, nullptr);
QVERIFY(!secondDataDeviceSelectionClearedSpy.wait(100));
// Attempt to unset the second selection.
secondDataDevice->setSelection(secondEnteredSerial, nullptr);
QVERIFY(secondDataDeviceSelectionClearedSpy.wait());
}
void SelectionTest::receiveFromWithdrawnSelectionOffer()
{
// This test verifies that a client cannot receive data from a withdrawn selection offer.
auto sourceConnection = Test::Connection::setup(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::DataDeviceManager);
auto targetConnection = Test::Connection::setup(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::DataDeviceManager);
// Setup the source side.
std::unique_ptr<KWayland::Client::DataDevice> sourceDataDevice(sourceConnection->dataDeviceManager->getDataDevice(sourceConnection->seat));
std::unique_ptr<KWayland::Client::DataSource> dataSource(sourceConnection->dataDeviceManager->createDataSource());
dataSource->offer(plainText());
connect(dataSource.get(), &KWayland::Client::DataSource::sendDataRequested, this, [](const QString &mimeType, int fd) {
const auto data = QByteArrayLiteral("foo");
write(fd, data.data(), data.size());
close(fd);
});
auto sourceSurface = Test::createSurface(sourceConnection->compositor);
auto sourceShellSurface = Test::createXdgToplevelSurface(sourceConnection->xdgShell, sourceSurface.get());
auto sourceWindow = Test::renderAndWaitForShown(sourceConnection->shm, sourceSurface.get(), QSize(100, 100), Qt::red);
// Setup the target side.
std::unique_ptr<KWayland::Client::DataDevice> targetDataDevice(targetConnection->dataDeviceManager->getDataDevice(targetConnection->seat));
auto targetSurface = Test::createSurface(targetConnection->compositor);
auto targetShellSurface = Test::createXdgToplevelSurface(targetConnection->xdgShell, targetSurface.get());
auto targetWindow = Test::renderAndWaitForShown(targetConnection->shm, targetSurface.get(), QSize(100, 100), Qt::blue);
// Focus the source window, and set the selection.
std::unique_ptr<KWayland::Client::Keyboard> sourceKeyboard(sourceConnection->seat->createKeyboard());
QSignalSpy sourceKeyboardEnteredSpy(sourceKeyboard.get(), &KWayland::Client::Keyboard::entered);
workspace()->activateWindow(sourceWindow);
QVERIFY(sourceKeyboardEnteredSpy.wait());
const quint32 sourceEnteredSerial = sourceKeyboardEnteredSpy.last().at(0).value<quint32>();
sourceDataDevice->setSelection(sourceEnteredSerial, dataSource.get());
// The target client should be offered the selection when its window gets focused.
QSignalSpy targetDataDeviceSelectionOfferedSpy(targetDataDevice.get(), &KWayland::Client::DataDevice::selectionOffered);
workspace()->activateWindow(targetWindow);
QVERIFY(targetDataDeviceSelectionOfferedSpy.wait());
std::unique_ptr<KWayland::Client::DataOffer> offer = targetDataDevice->takeOfferedSelection();
QCOMPARE(offer->offeredMimeTypes(), QList<QMimeType>{plainText()});
// Ask for data.
const QFuture<QByteArray> plainData = readMimeTypeData(offer, plainText());
QVERIFY(waitFuture(plainData));
QCOMPARE(plainData.result(), QByteArrayLiteral("foo"));
// Remove focus from the target window, it should not be able to receive data anymore.
workspace()->activateWindow(nullptr);
const QFuture<QByteArray> emptyData = readMimeTypeData(offer, plainText());
QVERIFY(waitFuture(emptyData));
QCOMPARE(emptyData.result(), QByteArray());
}
void SelectionTest::singleSelectionPerClient()
{
// This test verifies that only one selection event will be sent when switching between two
// surfaces that belong to the same client.
// Setup the first client.
auto firstConnection = Test::Connection::setup(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::DataDeviceManager);
QVERIFY(Test::waitForWaylandKeyboard(firstConnection->seat));
std::unique_ptr<KWayland::Client::Keyboard> firstKeyboard(firstConnection->seat->createKeyboard());
std::unique_ptr<KWayland::Client::DataDevice> firstDataDevice(firstConnection->dataDeviceManager->getDataDevice(firstConnection->seat));
std::unique_ptr<KWayland::Client::DataSource> firstDataSource(firstConnection->dataDeviceManager->createDataSource());
firstDataSource->offer(plainText());
connect(firstDataSource.get(), &KWayland::Client::DataSource::sendDataRequested, this, [](const QString &mimeType, int fd) {
const auto data = QByteArrayLiteral("foo");
write(fd, data.data(), data.size());
close(fd);
});
auto firstSurface = Test::createSurface(firstConnection->compositor);
auto firstShellSurface = Test::createXdgToplevelSurface(firstConnection->xdgShell, firstSurface.get());
auto firstWindow = Test::renderAndWaitForShown(firstConnection->shm, firstSurface.get(), QSize(100, 100), Qt::red);
// Setup the second client.
auto secondConnection = Test::Connection::setup(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::DataDeviceManager);
QVERIFY(Test::waitForWaylandKeyboard(secondConnection->seat));
std::unique_ptr<KWayland::Client::Keyboard> secondKeyboard(secondConnection->seat->createKeyboard());
std::unique_ptr<KWayland::Client::DataDevice> secondDataDevice(secondConnection->dataDeviceManager->getDataDevice(secondConnection->seat));
auto secondSurface = Test::createSurface(secondConnection->compositor);
auto secondShellSurface = Test::createXdgToplevelSurface(secondConnection->xdgShell, secondSurface.get());
auto secondWindow = Test::renderAndWaitForShown(secondConnection->shm, secondSurface.get(), QSize(100, 100), Qt::blue);
auto thirdSurface = Test::createSurface(secondConnection->compositor);
auto thirdShellSurface = Test::createXdgToplevelSurface(secondConnection->xdgShell, thirdSurface.get());
auto thirdWindow = Test::renderAndWaitForShown(secondConnection->shm, thirdSurface.get(), QSize(100, 100), Qt::green);
// Activate the first window and set the selection.
QSignalSpy firstKeyboardEnteredSpy(firstKeyboard.get(), &KWayland::Client::Keyboard::entered);
workspace()->activateWindow(firstWindow);
QVERIFY(firstKeyboardEnteredSpy.wait());
const quint32 firstEnteredSerial = firstKeyboardEnteredSpy.last().at(0).value<quint32>();
firstDataDevice->setSelection(firstEnteredSerial, firstDataSource.get());
QSignalSpy firstDataDeviceSelectionOfferedSpy(firstDataDevice.get(), &KWayland::Client::DataDevice::selectionOffered);
QSignalSpy firstDataDeviceSelectionClearedSpy(firstDataDevice.get(), &KWayland::Client::DataDevice::selectionCleared);
QSignalSpy secondDataDeviceSelectionOfferedSpy(secondDataDevice.get(), &KWayland::Client::DataDevice::selectionOffered);
QSignalSpy secondDataDeviceSelectionClearedSpy(secondDataDevice.get(), &KWayland::Client::DataDevice::selectionCleared);
QVERIFY(firstDataDeviceSelectionOfferedSpy.wait());
QCOMPARE(firstDataDeviceSelectionOfferedSpy.count(), 1);
QCOMPARE(firstDataDeviceSelectionClearedSpy.count(), 0);
QCOMPARE(secondDataDeviceSelectionOfferedSpy.count(), 0);
QCOMPARE(secondDataDeviceSelectionClearedSpy.count(), 0);
// Activate the 2nd window.
workspace()->activateWindow(secondWindow);
QVERIFY(secondDataDeviceSelectionOfferedSpy.wait());
QCOMPARE(firstDataDeviceSelectionOfferedSpy.count(), 1);
QCOMPARE(firstDataDeviceSelectionClearedSpy.count(), 0);
QCOMPARE(secondDataDeviceSelectionOfferedSpy.count(), 1);
QCOMPARE(secondDataDeviceSelectionClearedSpy.count(), 0);
// Activate the 3rd window (the 2nd and the 3rd window belong to the same client).
workspace()->activateWindow(thirdWindow);
QVERIFY(!secondDataDeviceSelectionOfferedSpy.wait(100));
QCOMPARE(firstDataDeviceSelectionOfferedSpy.count(), 1);
QCOMPARE(firstDataDeviceSelectionClearedSpy.count(), 0);
QCOMPARE(secondDataDeviceSelectionOfferedSpy.count(), 1);
QCOMPARE(secondDataDeviceSelectionClearedSpy.count(), 0);
// Activate the 2nd window (the 2nd and the 3rd window belong to the same client).
workspace()->activateWindow(secondWindow);
QVERIFY(!secondDataDeviceSelectionOfferedSpy.wait(100));
QCOMPARE(firstDataDeviceSelectionOfferedSpy.count(), 1);
QCOMPARE(firstDataDeviceSelectionClearedSpy.count(), 0);
QCOMPARE(secondDataDeviceSelectionOfferedSpy.count(), 1);
QCOMPARE(secondDataDeviceSelectionClearedSpy.count(), 0);
// Activate the 1st window (different connection from the 2nd and 3rd window).
workspace()->activateWindow(firstWindow);
QVERIFY(firstDataDeviceSelectionOfferedSpy.wait());
QCOMPARE(firstDataDeviceSelectionOfferedSpy.count(), 2);
QCOMPARE(firstDataDeviceSelectionClearedSpy.count(), 0);
QCOMPARE(secondDataDeviceSelectionOfferedSpy.count(), 1);
QCOMPARE(secondDataDeviceSelectionClearedSpy.count(), 0);
// Clear the selection.
firstDataDevice->setSelection(firstEnteredSerial, nullptr);
QVERIFY(firstDataDeviceSelectionClearedSpy.wait());
QCOMPARE(firstDataDeviceSelectionOfferedSpy.count(), 2);
QCOMPARE(firstDataDeviceSelectionClearedSpy.count(), 1);
QCOMPARE(secondDataDeviceSelectionOfferedSpy.count(), 1);
QCOMPARE(secondDataDeviceSelectionClearedSpy.count(), 0);
// Activate the 2nd window (different connection from the 1st window).
workspace()->activateWindow(secondWindow);
QVERIFY(!secondDataDeviceSelectionOfferedSpy.wait(100));
QCOMPARE(firstDataDeviceSelectionOfferedSpy.count(), 2);
QCOMPARE(firstDataDeviceSelectionClearedSpy.count(), 1);
QCOMPARE(secondDataDeviceSelectionOfferedSpy.count(), 1);
QCOMPARE(secondDataDeviceSelectionClearedSpy.count(), 1); // 1 because a null selection is sent
}
void SelectionTest::primarySelection()
{
// This test verifies that the primary selection works as expected between two clients.
auto sourceConnection = Test::Connection::setup(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::WpPrimarySelectionV1);
auto targetConnection = Test::Connection::setup(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::WpPrimarySelectionV1);
// Setup the source side.
std::unique_ptr<Test::WpPrimarySelectionDeviceV1> sourceDataDevice(sourceConnection->primarySelectionManager->getDevice(sourceConnection->seat));
std::unique_ptr<Test::WpPrimarySelectionSourceV1> dataSource(sourceConnection->primarySelectionManager->createSource());
dataSource->offer(plainText().name());
connect(dataSource.get(), &Test::WpPrimarySelectionSourceV1::sendDataRequested, this, [](const QString &mimeType, int fd) {
const auto data = QByteArrayLiteral("foo");
write(fd, data.data(), data.size());
close(fd);
});
auto sourceSurface = Test::createSurface(sourceConnection->compositor);
auto sourceShellSurface = Test::createXdgToplevelSurface(sourceConnection->xdgShell, sourceSurface.get());
auto sourceWindow = Test::renderAndWaitForShown(sourceConnection->shm, sourceSurface.get(), QSize(100, 100), Qt::red);
// Setup the target side.
std::unique_ptr<Test::WpPrimarySelectionDeviceV1> targetDataDevice(targetConnection->primarySelectionManager->getDevice(targetConnection->seat));
auto targetSurface = Test::createSurface(targetConnection->compositor);
auto targetShellSurface = Test::createXdgToplevelSurface(targetConnection->xdgShell, targetSurface.get());
auto targetWindow = Test::renderAndWaitForShown(targetConnection->shm, targetSurface.get(), QSize(100, 100), Qt::blue);
// Activate the source window.
std::unique_ptr<KWayland::Client::Keyboard> sourceKeyboard(sourceConnection->seat->createKeyboard());
QSignalSpy sourceKeyboardEnteredSpy(sourceKeyboard.get(), &KWayland::Client::Keyboard::entered);
workspace()->activateWindow(sourceWindow);
QVERIFY(sourceKeyboardEnteredSpy.wait());
const quint32 sourceEnteredSerial = sourceKeyboardEnteredSpy.last().at(0).value<quint32>();
// Set the selection.
sourceDataDevice->set_selection(dataSource->object(), sourceEnteredSerial);
// The target device shouldn't receive an offer while it's not focused.
QSignalSpy targetDataDeviceSelectionOfferedSpy(targetDataDevice.get(), &Test::WpPrimarySelectionDeviceV1::selectionOffered);
QVERIFY(!targetDataDeviceSelectionOfferedSpy.wait(100));
workspace()->activateWindow(targetWindow);
QVERIFY(targetDataDeviceSelectionOfferedSpy.wait());
Test::WpPrimarySelectionOfferV1 *offer = targetDataDevice->offer();
QCOMPARE(offer->mimeTypes(), QList<QMimeType>{plainText()});
// Ask for data.
const QFuture<QByteArray> plainData = readMimeTypeData(offer, plainText());
QVERIFY(waitFuture(plainData));
QCOMPARE(plainData.result(), QByteArrayLiteral("foo"));
// text/html hasn't been offered, so this should fail.
const QFuture<QByteArray> htmlData = readMimeTypeData(offer, htmlText());
QVERIFY(waitFuture(htmlData));
QCOMPARE(htmlData.result(), QByteArray());
// Clear selection.
QSignalSpy targetDataDeviceSelectionClearedSpy(targetDataDevice.get(), &Test::WpPrimarySelectionDeviceV1::selectionCleared);
sourceDataDevice->set_selection(nullptr, sourceEnteredSerial);
QVERIFY(targetDataDeviceSelectionClearedSpy.wait());
}
void SelectionTest::internalPrimarySelection()
{
// This test verifies that the source client also receives a wp_primary_selection_offer_v1 for its own selection.
auto connection = Test::Connection::setup(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::WpPrimarySelectionV1);
std::unique_ptr<Test::WpPrimarySelectionDeviceV1> dataDevice(connection->primarySelectionManager->getDevice(connection->seat));
std::unique_ptr<Test::WpPrimarySelectionSourceV1> dataSource(connection->primarySelectionManager->createSource());
dataSource->offer(plainText().name());
connect(dataSource.get(), &Test::WpPrimarySelectionSourceV1::sendDataRequested, this, [](const QString &mimeType, int fd) {
const auto data = QByteArrayLiteral("foo");
write(fd, data.data(), data.size());
close(fd);
});
auto surface = Test::createSurface(connection->compositor);
auto shellSurface = Test::createXdgToplevelSurface(connection->xdgShell, surface.get());
auto window = Test::renderAndWaitForShown(connection->shm, surface.get(), QSize(100, 100), Qt::red);
std::unique_ptr<KWayland::Client::Keyboard> keyboard(connection->seat->createKeyboard());
QSignalSpy keyboardEnteredSpy(keyboard.get(), &KWayland::Client::Keyboard::entered);
workspace()->activateWindow(window);
QVERIFY(keyboardEnteredSpy.wait());
const quint32 enteredSerial = keyboardEnteredSpy.last().at(0).value<quint32>();
// Set the selection.
dataDevice->set_selection(dataSource->object(), enteredSerial);
QSignalSpy dataDeviceSelectionOfferedSpy(dataDevice.get(), &Test::WpPrimarySelectionDeviceV1::selectionOffered);
QVERIFY(dataDeviceSelectionOfferedSpy.wait());
Test::WpPrimarySelectionOfferV1 *offer = dataDevice->offer();
QCOMPARE(offer->mimeTypes(), QList<QMimeType>{plainText()});
// Ask for data.
const QFuture<QByteArray> plainData = readMimeTypeData(offer, plainText());
QVERIFY(waitFuture(plainData));
QCOMPARE(plainData.result(), QByteArrayLiteral("foo"));
// text/html hasn't been offered, so this should fail.
const QFuture<QByteArray> htmlData = readMimeTypeData(offer, htmlText());
QVERIFY(waitFuture(htmlData));
QCOMPARE(htmlData.result(), QByteArray());
// Clear selection.
QSignalSpy selectionClearedSpy(dataDevice.get(), &Test::WpPrimarySelectionDeviceV1::selectionCleared);
dataDevice->set_selection(nullptr, enteredSerial);
QVERIFY(selectionClearedSpy.wait());
}
void SelectionTest::destroyPrimarySelection()
{
// This test verifies that the wp_primary_selection_offer_v1 will be withdrawn if the associated data source is destroyed.
auto sourceConnection = Test::Connection::setup(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::WpPrimarySelectionV1);
auto targetConnection = Test::Connection::setup(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::WpPrimarySelectionV1);
// Setup the source side.
std::unique_ptr<Test::WpPrimarySelectionDeviceV1> sourceDataDevice(sourceConnection->primarySelectionManager->getDevice(sourceConnection->seat));
std::unique_ptr<Test::WpPrimarySelectionSourceV1> dataSource(sourceConnection->primarySelectionManager->createSource());
dataSource->offer(plainText().name());
connect(dataSource.get(), &Test::WpPrimarySelectionSourceV1::sendDataRequested, this, [](const QString &mimeType, int fd) {
const auto data = QByteArrayLiteral("foo");
write(fd, data.data(), data.size());
close(fd);
});
auto sourceSurface = Test::createSurface(sourceConnection->compositor);
auto sourceShellSurface = Test::createXdgToplevelSurface(sourceConnection->xdgShell, sourceSurface.get());
auto sourceWindow = Test::renderAndWaitForShown(sourceConnection->shm, sourceSurface.get(), QSize(100, 100), Qt::red);
// Setup the target side.
std::unique_ptr<Test::WpPrimarySelectionDeviceV1> targetDataDevice(targetConnection->primarySelectionManager->getDevice(targetConnection->seat));
auto targetSurface = Test::createSurface(targetConnection->compositor);
auto targetShellSurface = Test::createXdgToplevelSurface(targetConnection->xdgShell, targetSurface.get());
auto targetWindow = Test::renderAndWaitForShown(targetConnection->shm, targetSurface.get(), QSize(100, 100), Qt::blue);
// Activate the source window.
std::unique_ptr<KWayland::Client::Keyboard> sourceKeyboard(sourceConnection->seat->createKeyboard());
QSignalSpy sourceKeyboardEnteredSpy(sourceKeyboard.get(), &KWayland::Client::Keyboard::entered);
workspace()->activateWindow(sourceWindow);
QVERIFY(sourceKeyboardEnteredSpy.wait());
const quint32 sourceEnteredSerial = sourceKeyboardEnteredSpy.last().at(0).value<quint32>();
// Set the selection.
sourceDataDevice->set_selection(dataSource->object(), sourceEnteredSerial);
// Activate the target window and wait for the data offer.
QSignalSpy targetDataDeviceSelectionOfferedSpy(targetDataDevice.get(), &Test::WpPrimarySelectionDeviceV1::selectionOffered);
workspace()->activateWindow(targetWindow);
QVERIFY(targetDataDeviceSelectionOfferedSpy.wait());
Test::WpPrimarySelectionOfferV1 *offer = targetDataDevice->offer();
QCOMPARE(offer->mimeTypes(), QList<QMimeType>{plainText()});
// Destroy the data source.
QSignalSpy targetDataDeviceSelectionClearedSpy(targetDataDevice.get(), &Test::WpPrimarySelectionDeviceV1::selectionCleared);
dataSource.reset();
QVERIFY(targetDataDeviceSelectionClearedSpy.wait());
}
void SelectionTest::invalidSerialForPrimarySelection()
{
// This test verifies that a set_selection request will be ignored if the specified serial is invalid.
auto connection = Test::Connection::setup(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::WpPrimarySelectionV1);
std::unique_ptr<Test::WpPrimarySelectionDeviceV1> dataDevice(connection->primarySelectionManager->getDevice(connection->seat));
std::unique_ptr<Test::WpPrimarySelectionSourceV1> dataSource(connection->primarySelectionManager->createSource());
dataSource->offer(QStringLiteral("text/plain"));
connect(dataSource.get(), &Test::WpPrimarySelectionSourceV1::sendDataRequested, this, [](const QString &mimeType, int fd) {
const auto data = QByteArrayLiteral("foo");
write(fd, data.data(), data.size());
close(fd);
});
auto surface = Test::createSurface(connection->compositor);
auto shellSurface = Test::createXdgToplevelSurface(connection->xdgShell, surface.get());
Test::renderAndWaitForShown(connection->shm, surface.get(), QSize(100, 100), Qt::red);
std::unique_ptr<KWayland::Client::Keyboard> keyboard(connection->seat->createKeyboard());
QSignalSpy keyboardEnteredSpy(keyboard.get(), &KWayland::Client::Keyboard::entered);
QVERIFY(keyboardEnteredSpy.wait());
const quint32 enteredSerial = keyboardEnteredSpy.last().at(0).value<quint32>();
// Set the selection.
QSignalSpy dataSourceCancelledSpy(dataSource.get(), &Test::WpPrimarySelectionSourceV1::cancelled);
dataDevice->set_selection(dataSource->object(), enteredSerial - 100);
QVERIFY(dataSourceCancelledSpy.wait());
}
void SelectionTest::unsetSupersededPrimarySelection()
{
// This test verifies that the current selection won't be unset if the previous selection is unset with set_selection(null).
// Setup the first client.
auto firstConnection = Test::Connection::setup(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::WpPrimarySelectionV1);
QVERIFY(Test::waitForWaylandKeyboard(firstConnection->seat));
std::unique_ptr<KWayland::Client::Keyboard> firstKeyboard(firstConnection->seat->createKeyboard());
std::unique_ptr<Test::WpPrimarySelectionDeviceV1> firstDataDevice(firstConnection->primarySelectionManager->getDevice(firstConnection->seat));
std::unique_ptr<Test::WpPrimarySelectionSourceV1> firstDataSource(firstConnection->primarySelectionManager->createSource());
firstDataSource->offer(plainText().name());
connect(firstDataSource.get(), &Test::WpPrimarySelectionSourceV1::sendDataRequested, this, [](const QString &mimeType, int fd) {
const auto data = QByteArrayLiteral("foo");
write(fd, data.data(), data.size());
close(fd);
});
auto firstSurface = Test::createSurface(firstConnection->compositor);
auto firstShellSurface = Test::createXdgToplevelSurface(firstConnection->xdgShell, firstSurface.get());
auto firstWindow = Test::renderAndWaitForShown(firstConnection->shm, firstSurface.get(), QSize(100, 100), Qt::red);
// Setup the second client.
auto secondConnection = Test::Connection::setup(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::WpPrimarySelectionV1);
QVERIFY(Test::waitForWaylandKeyboard(secondConnection->seat));
std::unique_ptr<KWayland::Client::Keyboard> secondKeyboard(secondConnection->seat->createKeyboard());
std::unique_ptr<Test::WpPrimarySelectionDeviceV1> secondDataDevice(secondConnection->primarySelectionManager->getDevice(secondConnection->seat));
std::unique_ptr<Test::WpPrimarySelectionSourceV1> secondDataSource(secondConnection->primarySelectionManager->createSource());
secondDataSource->offer(htmlText().name());
connect(secondDataSource.get(), &Test::WpPrimarySelectionSourceV1::sendDataRequested, this, [](const QString &mimeType, int fd) {
const auto data = QByteArrayLiteral("bar");
write(fd, data.data(), data.size());
close(fd);
});
auto secondSurface = Test::createSurface(secondConnection->compositor);
auto secondShellSurface = Test::createXdgToplevelSurface(secondConnection->xdgShell, secondSurface.get());
auto secondWindow = Test::renderAndWaitForShown(secondConnection->shm, secondSurface.get(), QSize(100, 100), Qt::blue);
// Activate the first window and set the selection.
QSignalSpy firstKeyboardEnteredSpy(firstKeyboard.get(), &KWayland::Client::Keyboard::entered);
QSignalSpy firstDataDeviceSelectionOfferedSpy(firstDataDevice.get(), &Test::WpPrimarySelectionDeviceV1::selectionOffered);
workspace()->activateWindow(firstWindow);
QVERIFY(firstKeyboardEnteredSpy.wait());
const quint32 firstEnteredSerial = firstKeyboardEnteredSpy.last().at(0).value<quint32>();
firstDataDevice->set_selection(firstDataSource->object(), firstEnteredSerial);
QVERIFY(firstDataDeviceSelectionOfferedSpy.wait());
// Activate the second window and set the selection.
QSignalSpy secondKeyboardEnteredSpy(secondKeyboard.get(), &KWayland::Client::Keyboard::entered);
QSignalSpy secondDataDeviceSelectionOfferedSpy(secondDataDevice.get(), &Test::WpPrimarySelectionDeviceV1::selectionOffered);
workspace()->activateWindow(secondWindow);
QVERIFY(secondKeyboardEnteredSpy.wait());
const quint32 secondEnteredSerial = secondKeyboardEnteredSpy.last().at(0).value<quint32>();
secondDataDevice->set_selection(secondDataSource->object(), secondEnteredSerial);
QVERIFY(secondDataDeviceSelectionOfferedSpy.wait());
// Attempt to unset the first selection.
QSignalSpy secondDataDeviceSelectionClearedSpy(secondDataDevice.get(), &Test::WpPrimarySelectionDeviceV1::selectionCleared);
firstDataDevice->set_selection(nullptr, firstEnteredSerial);
QVERIFY(!secondDataDeviceSelectionClearedSpy.wait(100));
// Attempt to unset the second selection.
secondDataDevice->set_selection(nullptr, secondEnteredSerial);
QVERIFY(secondDataDeviceSelectionClearedSpy.wait());
}
void SelectionTest::receiveFromWithdrawnPrimarySelectionOffer()
{
// This test verifies that a client cannot receive data from a withdrawn primary selection offer.
auto sourceConnection = Test::Connection::setup(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::WpPrimarySelectionV1);
auto targetConnection = Test::Connection::setup(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::WpPrimarySelectionV1);
// Setup the source side.
std::unique_ptr<Test::WpPrimarySelectionDeviceV1> sourceDataDevice(sourceConnection->primarySelectionManager->getDevice(sourceConnection->seat));
std::unique_ptr<Test::WpPrimarySelectionSourceV1> dataSource(sourceConnection->primarySelectionManager->createSource());
dataSource->offer(plainText().name());
connect(dataSource.get(), &Test::WpPrimarySelectionSourceV1::sendDataRequested, this, [](const QString &mimeType, int fd) {
const auto data = QByteArrayLiteral("foo");
write(fd, data.data(), data.size());
close(fd);
});
auto sourceSurface = Test::createSurface(sourceConnection->compositor);
auto sourceShellSurface = Test::createXdgToplevelSurface(sourceConnection->xdgShell, sourceSurface.get());
auto sourceWindow = Test::renderAndWaitForShown(sourceConnection->shm, sourceSurface.get(), QSize(100, 100), Qt::red);
// Setup the target side.
std::unique_ptr<Test::WpPrimarySelectionDeviceV1> targetDataDevice(targetConnection->primarySelectionManager->getDevice(targetConnection->seat));
auto targetSurface = Test::createSurface(targetConnection->compositor);
auto targetShellSurface = Test::createXdgToplevelSurface(targetConnection->xdgShell, targetSurface.get());
auto targetWindow = Test::renderAndWaitForShown(targetConnection->shm, targetSurface.get(), QSize(100, 100), Qt::blue);
// Focus the source window, and set the primary selection.
std::unique_ptr<KWayland::Client::Keyboard> sourceKeyboard(sourceConnection->seat->createKeyboard());
QSignalSpy sourceKeyboardEnteredSpy(sourceKeyboard.get(), &KWayland::Client::Keyboard::entered);
workspace()->activateWindow(sourceWindow);
QVERIFY(sourceKeyboardEnteredSpy.wait());
const quint32 sourceEnteredSerial = sourceKeyboardEnteredSpy.last().at(0).value<quint32>();
sourceDataDevice->set_selection(dataSource->object(), sourceEnteredSerial);
// The target client should be offered the selection when its window gets focused.
QSignalSpy targetDataDeviceSelectionOfferedSpy(targetDataDevice.get(), &Test::WpPrimarySelectionDeviceV1::selectionOffered);
workspace()->activateWindow(targetWindow);
QVERIFY(targetDataDeviceSelectionOfferedSpy.wait());
std::unique_ptr<Test::WpPrimarySelectionOfferV1> offer = targetDataDevice->takeOffer();
QCOMPARE(offer->mimeTypes(), QList<QMimeType>{plainText()});
// Ask for data.
const QFuture<QByteArray> plainData = readMimeTypeData(offer, plainText());
QVERIFY(waitFuture(plainData));
QCOMPARE(plainData.result(), QByteArrayLiteral("foo"));
// Remove focus from the target window, it should not be able to receive data anymore.
workspace()->activateWindow(nullptr);
const QFuture<QByteArray> emptyData = readMimeTypeData(offer, plainText());
QVERIFY(waitFuture(emptyData));
QCOMPARE(emptyData.result(), QByteArray());
}
void SelectionTest::singlePrimarySelectionPerClient()
{
// This test verifies that only one selection event will be sent when switching between two
// surfaces that belong to the same client.
// Setup the first client.
auto firstConnection = Test::Connection::setup(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::WpPrimarySelectionV1);
QVERIFY(Test::waitForWaylandKeyboard(firstConnection->seat));
std::unique_ptr<KWayland::Client::Keyboard> firstKeyboard(firstConnection->seat->createKeyboard());
std::unique_ptr<Test::WpPrimarySelectionDeviceV1> firstDataDevice(firstConnection->primarySelectionManager->getDevice(firstConnection->seat));
std::unique_ptr<Test::WpPrimarySelectionSourceV1> firstDataSource(firstConnection->primarySelectionManager->createSource());
firstDataSource->offer(plainText().name());
connect(firstDataSource.get(), &Test::WpPrimarySelectionSourceV1::sendDataRequested, this, [](const QString &mimeType, int fd) {
const auto data = QByteArrayLiteral("foo");
write(fd, data.data(), data.size());
close(fd);
});
auto firstSurface = Test::createSurface(firstConnection->compositor);
auto firstShellSurface = Test::createXdgToplevelSurface(firstConnection->xdgShell, firstSurface.get());
auto firstWindow = Test::renderAndWaitForShown(firstConnection->shm, firstSurface.get(), QSize(100, 100), Qt::red);
// Setup the second client.
auto secondConnection = Test::Connection::setup(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::WpPrimarySelectionV1);
QVERIFY(Test::waitForWaylandKeyboard(secondConnection->seat));
std::unique_ptr<KWayland::Client::Keyboard> secondKeyboard(secondConnection->seat->createKeyboard());
std::unique_ptr<Test::WpPrimarySelectionDeviceV1> secondDataDevice(secondConnection->primarySelectionManager->getDevice(secondConnection->seat));
auto secondSurface = Test::createSurface(secondConnection->compositor);
auto secondShellSurface = Test::createXdgToplevelSurface(secondConnection->xdgShell, secondSurface.get());
auto secondWindow = Test::renderAndWaitForShown(secondConnection->shm, secondSurface.get(), QSize(100, 100), Qt::blue);
auto thirdSurface = Test::createSurface(secondConnection->compositor);
auto thirdShellSurface = Test::createXdgToplevelSurface(secondConnection->xdgShell, thirdSurface.get());
auto thirdWindow = Test::renderAndWaitForShown(secondConnection->shm, thirdSurface.get(), QSize(100, 100), Qt::green);
// Activate the first window and set the selection.
QSignalSpy firstKeyboardEnteredSpy(firstKeyboard.get(), &KWayland::Client::Keyboard::entered);
workspace()->activateWindow(firstWindow);
QVERIFY(firstKeyboardEnteredSpy.wait());
const quint32 firstEnteredSerial = firstKeyboardEnteredSpy.last().at(0).value<quint32>();
firstDataDevice->set_selection(firstDataSource->object(), firstEnteredSerial);
QSignalSpy firstDataDeviceSelectionOfferedSpy(firstDataDevice.get(), &Test::WpPrimarySelectionDeviceV1::selectionOffered);
QSignalSpy firstDataDeviceSelectionClearedSpy(firstDataDevice.get(), &Test::WpPrimarySelectionDeviceV1::selectionCleared);
QSignalSpy secondDataDeviceSelectionOfferedSpy(secondDataDevice.get(), &Test::WpPrimarySelectionDeviceV1::selectionOffered);
QSignalSpy secondDataDeviceSelectionClearedSpy(secondDataDevice.get(), &Test::WpPrimarySelectionDeviceV1::selectionCleared);
QVERIFY(firstDataDeviceSelectionOfferedSpy.wait());
QCOMPARE(firstDataDeviceSelectionOfferedSpy.count(), 1);
QCOMPARE(firstDataDeviceSelectionClearedSpy.count(), 0);
QCOMPARE(secondDataDeviceSelectionOfferedSpy.count(), 0);
QCOMPARE(secondDataDeviceSelectionClearedSpy.count(), 0);
// Activate the 2nd window.
workspace()->activateWindow(secondWindow);
QVERIFY(secondDataDeviceSelectionOfferedSpy.wait());
QCOMPARE(firstDataDeviceSelectionOfferedSpy.count(), 1);
QCOMPARE(firstDataDeviceSelectionClearedSpy.count(), 0);
QCOMPARE(secondDataDeviceSelectionOfferedSpy.count(), 1);
QCOMPARE(secondDataDeviceSelectionClearedSpy.count(), 0);
// Activate the 3rd window (the 2nd and the 3rd window belong to the same client).
workspace()->activateWindow(thirdWindow);
QVERIFY(!secondDataDeviceSelectionOfferedSpy.wait(100));
QCOMPARE(firstDataDeviceSelectionOfferedSpy.count(), 1);
QCOMPARE(firstDataDeviceSelectionClearedSpy.count(), 0);
QCOMPARE(secondDataDeviceSelectionOfferedSpy.count(), 1);
QCOMPARE(secondDataDeviceSelectionClearedSpy.count(), 0);
// Activate the 2nd window (the 2nd and the 3rd window belong to the same client).
workspace()->activateWindow(secondWindow);
QVERIFY(!secondDataDeviceSelectionOfferedSpy.wait(100));
QCOMPARE(firstDataDeviceSelectionOfferedSpy.count(), 1);
QCOMPARE(firstDataDeviceSelectionClearedSpy.count(), 0);
QCOMPARE(secondDataDeviceSelectionOfferedSpy.count(), 1);
QCOMPARE(secondDataDeviceSelectionClearedSpy.count(), 0);
// Activate the 1st window (different connection from the 2nd and 3rd window).
workspace()->activateWindow(firstWindow);
QVERIFY(firstDataDeviceSelectionOfferedSpy.wait());
QCOMPARE(firstDataDeviceSelectionOfferedSpy.count(), 2);
QCOMPARE(firstDataDeviceSelectionClearedSpy.count(), 0);
QCOMPARE(secondDataDeviceSelectionOfferedSpy.count(), 1);
QCOMPARE(secondDataDeviceSelectionClearedSpy.count(), 0);
// Clear the selection.
firstDataDevice->set_selection(nullptr, firstEnteredSerial);
QVERIFY(firstDataDeviceSelectionClearedSpy.wait());
QCOMPARE(firstDataDeviceSelectionOfferedSpy.count(), 2);
QCOMPARE(firstDataDeviceSelectionClearedSpy.count(), 1);
QCOMPARE(secondDataDeviceSelectionOfferedSpy.count(), 1);
QCOMPARE(secondDataDeviceSelectionClearedSpy.count(), 0);
// Activate the 2nd window (different connection from the 1st window).
workspace()->activateWindow(secondWindow);
QVERIFY(!secondDataDeviceSelectionOfferedSpy.wait(100));
QCOMPARE(firstDataDeviceSelectionOfferedSpy.count(), 2);
QCOMPARE(firstDataDeviceSelectionClearedSpy.count(), 1);
QCOMPARE(secondDataDeviceSelectionOfferedSpy.count(), 1);
QCOMPARE(secondDataDeviceSelectionClearedSpy.count(), 1); // 1 because a null selection is sent
}
} // namespace KWin
WAYLANDTEST_MAIN(KWin::SelectionTest)
#include "selection_test.moc"