kwin/autotests/wayland/client/test_selection.cpp
David Redondo 31018c000b wayland: Check serial instead of focus for changing selections
Clients can have valid reasons to change the selection when the
same user action that also caused the selection request
to lose keyboard focus. This is notbaly the case for menus
created from a Plasma panel which itself will not take focus
but when clicking on action it only triggers after the menu
is closed.
This also matches what weston and sway do.
BUG:490803
2024-08-06 12:54:00 +00:00

247 lines
8.4 KiB
C++

/*
SPDX-FileCopyrightText: 2016 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 <QSignalSpy>
#include <QTest>
// client
#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/registry.h"
#include "KWayland/Client/seat.h"
#include "KWayland/Client/surface.h"
// server
#include "wayland/compositor.h"
#include "wayland/datadevicemanager.h"
#include "wayland/display.h"
#include "wayland/seat.h"
using namespace KWin;
class SelectionTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void init();
void cleanup();
void testClearOnEnter();
private:
KWin::Display *m_display = nullptr;
CompositorInterface *m_compositorInterface = nullptr;
SeatInterface *m_seatInterface = nullptr;
DataDeviceManagerInterface *m_ddmInterface = nullptr;
struct Connection
{
KWayland::Client::ConnectionThread *connection = nullptr;
QThread *thread = nullptr;
KWayland::Client::EventQueue *queue = nullptr;
KWayland::Client::Compositor *compositor = nullptr;
KWayland::Client::Seat *seat = nullptr;
KWayland::Client::DataDeviceManager *ddm = nullptr;
KWayland::Client::Keyboard *keyboard = nullptr;
KWayland::Client::DataDevice *dataDevice = nullptr;
};
bool setupConnection(Connection *c);
void cleanupConnection(Connection *c);
Connection m_client1;
Connection m_client2;
};
static const QString s_socketName = QStringLiteral("kwayland-test-selection-0");
void SelectionTest::init()
{
delete m_display;
m_display = new KWin::Display(this);
m_display->addSocketName(s_socketName);
m_display->start();
QVERIFY(m_display->isRunning());
m_display->createShm();
m_compositorInterface = new CompositorInterface(m_display, m_display);
m_seatInterface = new SeatInterface(m_display, m_display);
m_seatInterface->setHasKeyboard(true);
m_ddmInterface = new DataDeviceManagerInterface(m_display, m_display);
// setup connection
setupConnection(&m_client1);
setupConnection(&m_client2);
}
bool SelectionTest::setupConnection(Connection *c)
{
c->connection = new KWayland::Client::ConnectionThread;
QSignalSpy connectedSpy(c->connection, &KWayland::Client::ConnectionThread::connected);
if (!connectedSpy.isValid()) {
return false;
}
c->connection->setSocketName(s_socketName);
c->thread = new QThread(this);
c->connection->moveToThread(c->thread);
c->thread->start();
c->connection->initConnection();
if (!connectedSpy.wait(500)) {
return false;
}
c->queue = new KWayland::Client::EventQueue(this);
c->queue->setup(c->connection);
KWayland::Client::Registry registry;
QSignalSpy interfacesAnnouncedSpy(&registry, &KWayland::Client::Registry::interfacesAnnounced);
if (!interfacesAnnouncedSpy.isValid()) {
return false;
}
registry.setEventQueue(c->queue);
registry.create(c->connection);
if (!registry.isValid()) {
return false;
}
registry.setup();
if (!interfacesAnnouncedSpy.wait(500)) {
return false;
}
c->compositor =
registry.createCompositor(registry.interface(KWayland::Client::Registry::Interface::Compositor).name, registry.interface(KWayland::Client::Registry::Interface::Compositor).version, this);
if (!c->compositor->isValid()) {
return false;
}
c->ddm = registry.createDataDeviceManager(registry.interface(KWayland::Client::Registry::Interface::DataDeviceManager).name,
registry.interface(KWayland::Client::Registry::Interface::DataDeviceManager).version,
this);
if (!c->ddm->isValid()) {
return false;
}
c->seat = registry.createSeat(registry.interface(KWayland::Client::Registry::Interface::Seat).name, registry.interface(KWayland::Client::Registry::Interface::Seat).version, this);
if (!c->seat->isValid()) {
return false;
}
QSignalSpy keyboardSpy(c->seat, &KWayland::Client::Seat::hasKeyboardChanged);
if (!keyboardSpy.isValid()) {
return false;
}
if (!keyboardSpy.wait(500)) {
return false;
}
if (!c->seat->hasKeyboard()) {
return false;
}
c->keyboard = c->seat->createKeyboard(c->seat);
if (!c->keyboard->isValid()) {
return false;
}
c->dataDevice = c->ddm->getDataDevice(c->seat, this);
if (!c->dataDevice->isValid()) {
return false;
}
return true;
}
void SelectionTest::cleanup()
{
cleanupConnection(&m_client1);
cleanupConnection(&m_client2);
#define CLEANUP(variable) \
delete variable; \
variable = nullptr;
CLEANUP(m_display)
#undef CLEANUP
// these are the children of the display
m_ddmInterface = nullptr;
m_seatInterface = nullptr;
m_compositorInterface = nullptr;
}
void SelectionTest::cleanupConnection(Connection *c)
{
delete c->dataDevice;
c->dataDevice = nullptr;
delete c->keyboard;
c->keyboard = nullptr;
delete c->ddm;
c->ddm = nullptr;
delete c->seat;
c->seat = nullptr;
delete c->compositor;
c->compositor = nullptr;
delete c->queue;
c->queue = nullptr;
if (c->connection) {
c->connection->deleteLater();
c->connection = nullptr;
}
if (c->thread) {
c->thread->quit();
c->thread->wait();
delete c->thread;
c->thread = nullptr;
}
}
void SelectionTest::testClearOnEnter()
{
// this test verifies that the selection is cleared prior to keyboard enter if there is no current selection
QSignalSpy selectionClearedClient1Spy(m_client1.dataDevice, &KWayland::Client::DataDevice::selectionCleared);
QSignalSpy keyboardEnteredClient1Spy(m_client1.keyboard, &KWayland::Client::Keyboard::entered);
// now create a Surface
QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated);
std::unique_ptr<KWayland::Client::Surface> s1(m_client1.compositor->createSurface());
QVERIFY(surfaceCreatedSpy.wait());
auto serverSurface1 = surfaceCreatedSpy.first().first().value<SurfaceInterface *>();
QVERIFY(serverSurface1);
// pass this surface keyboard focus
m_seatInterface->setFocusedKeyboardSurface(serverSurface1);
// should get a clear
QVERIFY(selectionClearedClient1Spy.wait());
// let's set a selection
std::unique_ptr<KWayland::Client::DataSource> dataSource(m_client1.ddm->createDataSource());
dataSource->offer(QStringLiteral("text/plain"));
m_client1.dataDevice->setSelection(keyboardEnteredClient1Spy.first().first().value<quint32>(), dataSource.get());
// now let's bring in client 2
QSignalSpy selectionOfferedClient2Spy(m_client2.dataDevice, &KWayland::Client::DataDevice::selectionOffered);
QSignalSpy selectionClearedClient2Spy(m_client2.dataDevice, &KWayland::Client::DataDevice::selectionCleared);
QSignalSpy keyboardEnteredClient2Spy(m_client2.keyboard, &KWayland::Client::Keyboard::entered);
std::unique_ptr<KWayland::Client::Surface> s2(m_client2.compositor->createSurface());
QVERIFY(surfaceCreatedSpy.wait());
auto serverSurface2 = surfaceCreatedSpy.last().first().value<SurfaceInterface *>();
QVERIFY(serverSurface2);
// entering that surface should give a selection offer
m_seatInterface->setFocusedKeyboardSurface(serverSurface2);
QVERIFY(selectionOfferedClient2Spy.wait());
QVERIFY(selectionClearedClient2Spy.isEmpty());
// set a data source but without offers
std::unique_ptr<KWayland::Client::DataSource> dataSource2(m_client2.ddm->createDataSource());
m_client2.dataDevice->setSelection(keyboardEnteredClient2Spy.first().first().value<quint32>(), dataSource2.get());
QVERIFY(selectionOfferedClient2Spy.wait());
// and clear
m_client2.dataDevice->clearSelection(keyboardEnteredClient2Spy.first().first().value<quint32>() + 1);
QVERIFY(selectionClearedClient2Spy.wait());
// now pass focus to first surface
m_seatInterface->setFocusedKeyboardSurface(serverSurface1);
// we should get a clear
QVERIFY(selectionClearedClient1Spy.wait());
}
QTEST_GUILESS_MAIN(SelectionTest)
#include "test_selection.moc"