KWin
Loading...
Searching...
No Matches
test_datacontrol_interface.cpp
Go to the documentation of this file.
1/*
2 SPDX-FileCopyrightText: 2020 David Edmundson <davidedmundson@kde.org>
3 SPDX-FileCopyrightText: 2021 David Redondo <kde@david-redondo.de>
4
5 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6*/
7
8// Qt
9#include <QHash>
10#include <QSignalSpy>
11#include <QTest>
12#include <QThread>
13
14// WaylandServer
15#include "wayland/compositor.h"
19#include "wayland/display.h"
20#include "wayland/seat.h"
21
22#include <KWayland/Client/compositor.h>
23#include <KWayland/Client/connection_thread.h>
24#include <KWayland/Client/event_queue.h>
25#include <KWayland/Client/registry.h>
26#include <KWayland/Client/seat.h>
27
28#include "qwayland-wlr-data-control-unstable-v1.h"
29
30using namespace KWin;
31
32// Faux-client API for tests
33
34Q_DECLARE_OPAQUE_POINTER(::zwlr_data_control_offer_v1 *)
35Q_DECLARE_METATYPE(::zwlr_data_control_offer_v1 *)
36
37class DataControlDeviceManager : public QObject, public QtWayland::zwlr_data_control_manager_v1
38{
39 Q_OBJECT
40};
41
42class DataControlOffer : public QObject, public QtWayland::zwlr_data_control_offer_v1
43{
44 Q_OBJECT
45public:
47 {
48 destroy();
49 }
50 QStringList receivedOffers()
51 {
52 return m_receivedOffers;
53 }
54
55protected:
56 virtual void zwlr_data_control_offer_v1_offer(const QString &mime_type) override
57 {
58 m_receivedOffers << mime_type;
59 }
60
61private:
62 QStringList m_receivedOffers;
63};
64
65class DataControlDevice : public QObject, public QtWayland::zwlr_data_control_device_v1
66{
67 Q_OBJECT
68public:
70 {
71 destroy();
72 }
73Q_SIGNALS:
74 void dataControlOffer(DataControlOffer *offer); // our event receives a new ID, so we make a new object
75 void selection(struct ::zwlr_data_control_offer_v1 *id);
76 void primary_selection(struct ::zwlr_data_control_offer_v1 *id);
77
78protected:
79 void zwlr_data_control_device_v1_data_offer(struct ::zwlr_data_control_offer_v1 *id) override
80 {
81 auto offer = new DataControlOffer;
82 offer->init(id);
83 Q_EMIT dataControlOffer(offer);
84 }
85
86 void zwlr_data_control_device_v1_selection(struct ::zwlr_data_control_offer_v1 *id) override
87 {
88 Q_EMIT selection(id);
89 }
90
91 void zwlr_data_control_device_v1_primary_selection(struct ::zwlr_data_control_offer_v1 *id) override
92 {
93 Q_EMIT primary_selection(id);
94 }
95};
96
97class DataControlSource : public QObject, public QtWayland::zwlr_data_control_source_v1
98{
99 Q_OBJECT
100public:
102 {
103 destroy();
104 }
105
106public:
107};
108
110{
111 Q_OBJECT
112public:
114 : AbstractDataSource(nullptr)
115 {
116 }
118 {
119 Q_EMIT aboutToBeDestroyed();
120 }
121 void requestData(const QString &mimeType, qint32 fd) override
122 {
123 };
124 void cancel() override{};
125 QStringList mimeTypes() const override
126 {
127 return {"text/test1", "text/test2"};
128 }
129};
130
131// The test itself
132
133class DataControlInterfaceTest : public QObject
134{
135 Q_OBJECT
136
137private Q_SLOTS:
138 void init();
139 void cleanup();
140 void testCopyToControl();
141 void testCopyToControlPrimarySelection();
142 void testCopyFromControl();
143 void testCopyFromControlPrimarySelection();
144 void testKlipperCase();
145
146private:
147 KWayland::Client::ConnectionThread *m_connection;
148 KWayland::Client::EventQueue *m_queue;
149 KWayland::Client::Compositor *m_clientCompositor;
150 KWayland::Client::Seat *m_clientSeat = nullptr;
151
152 QThread *m_thread;
153 KWin::Display *m_display;
154 SeatInterface *m_seat;
155 CompositorInterface *m_serverCompositor;
156
157 DataControlDeviceManagerV1Interface *m_dataControlDeviceManagerInterface;
158
159 DataControlDeviceManager *m_dataControlDeviceManager;
160
161 QList<SurfaceInterface *> m_surfaces;
162};
163
164static const QString s_socketName = QStringLiteral("kwin-wayland-datacontrol-test-0");
165
166void DataControlInterfaceTest::init()
167{
168 m_display = new KWin::Display();
169 m_display->addSocketName(s_socketName);
170 m_display->start();
171 QVERIFY(m_display->isRunning());
172
173 m_seat = new SeatInterface(m_display, this);
174 m_serverCompositor = new CompositorInterface(m_display, this);
175 m_dataControlDeviceManagerInterface = new DataControlDeviceManagerV1Interface(m_display, this);
176
177 // setup connection
178 m_connection = new KWayland::Client::ConnectionThread;
179 QSignalSpy connectedSpy(m_connection, &KWayland::Client::ConnectionThread::connected);
180 m_connection->setSocketName(s_socketName);
181
182 m_thread = new QThread(this);
183 m_connection->moveToThread(m_thread);
184 m_thread->start();
185
186 m_connection->initConnection();
187 QVERIFY(connectedSpy.wait());
188 QVERIFY(!m_connection->connections().isEmpty());
189
190 m_queue = new KWayland::Client::EventQueue(this);
191 QVERIFY(!m_queue->isValid());
192 m_queue->setup(m_connection);
193 QVERIFY(m_queue->isValid());
194
195 KWayland::Client::Registry registry;
196 connect(&registry, &KWayland::Client::Registry::interfaceAnnounced, this, [this, &registry](const QByteArray &interface, quint32 name, quint32 version) {
197 if (interface == "zwlr_data_control_manager_v1") {
198 m_dataControlDeviceManager = new DataControlDeviceManager;
199 m_dataControlDeviceManager->init(registry.registry(), name, version);
200 }
201 });
202 connect(&registry, &KWayland::Client::Registry::seatAnnounced, this, [this, &registry](quint32 name, quint32 version) {
203 m_clientSeat = registry.createSeat(name, version);
204 });
205 registry.setEventQueue(m_queue);
206 QSignalSpy compositorSpy(&registry, &KWayland::Client::Registry::compositorAnnounced);
207 registry.create(m_connection->display());
208 QVERIFY(registry.isValid());
209 registry.setup();
210 wl_display_flush(m_connection->display());
211
212 QVERIFY(compositorSpy.wait());
213 m_clientCompositor = registry.createCompositor(compositorSpy.first().first().value<quint32>(), compositorSpy.first().last().value<quint32>(), this);
214 QVERIFY(m_clientCompositor->isValid());
215
216 QVERIFY(m_dataControlDeviceManager);
217}
218
219void DataControlInterfaceTest::cleanup()
220{
221#define CLEANUP(variable) \
222 if (variable) { \
223 delete variable; \
224 variable = nullptr; \
225 }
226 CLEANUP(m_dataControlDeviceManager)
227 CLEANUP(m_clientSeat)
228 CLEANUP(m_clientCompositor)
229 CLEANUP(m_queue)
230 if (m_connection) {
231 m_connection->deleteLater();
232 m_connection = nullptr;
233 }
234 if (m_thread) {
235 m_thread->quit();
236 m_thread->wait();
237 delete m_thread;
238 m_thread = nullptr;
239 }
240 CLEANUP(m_display)
241#undef CLEANUP
242
243 // these are the children of the display
244 m_seat = nullptr;
245 m_serverCompositor = nullptr;
246}
247
248void DataControlInterfaceTest::testCopyToControl()
249{
250 // we set a dummy data source on the seat using abstract client directly
251 // then confirm we receive the offer despite not having a surface
252
253 std::unique_ptr<DataControlDevice> dataControlDevice(new DataControlDevice);
254 dataControlDevice->init(m_dataControlDeviceManager->get_data_device(*m_clientSeat));
255
256 QSignalSpy newOfferSpy(dataControlDevice.get(), &DataControlDevice::dataControlOffer);
257 QSignalSpy selectionSpy(dataControlDevice.get(), &DataControlDevice::selection);
258
259 std::unique_ptr<TestDataSource> testSelection(new TestDataSource);
260 m_seat->setSelection(testSelection.get());
261
262 // selection will be sent after we've been sent a new offer object and the mimes have been sent to that object
263 selectionSpy.wait();
264
265 QCOMPARE(newOfferSpy.count(), 1);
266 std::unique_ptr<DataControlOffer> offer(newOfferSpy.first().first().value<DataControlOffer *>());
267 QCOMPARE(selectionSpy.first().first().value<struct ::zwlr_data_control_offer_v1 *>(), offer->object());
268
269 QCOMPARE(offer->receivedOffers().count(), 2);
270 QCOMPARE(offer->receivedOffers()[0], "text/test1");
271 QCOMPARE(offer->receivedOffers()[1], "text/test2");
272}
273
274void DataControlInterfaceTest::testCopyToControlPrimarySelection()
275{
276 // we set a dummy data source on the seat using abstract client directly
277 // then confirm we receive the offer despite not having a surface
278
279 std::unique_ptr<DataControlDevice> dataControlDevice(new DataControlDevice);
280 dataControlDevice->init(m_dataControlDeviceManager->get_data_device(*m_clientSeat));
281
282 QSignalSpy newOfferSpy(dataControlDevice.get(), &DataControlDevice::dataControlOffer);
283 QSignalSpy selectionSpy(dataControlDevice.get(), &DataControlDevice::primary_selection);
284
285 std::unique_ptr<TestDataSource> testSelection(new TestDataSource);
286 m_seat->setPrimarySelection(testSelection.get());
287
288 // selection will be sent after we've been sent a new offer object and the mimes have been sent to that object
289 selectionSpy.wait();
290
291 QCOMPARE(newOfferSpy.count(), 1);
292 std::unique_ptr<DataControlOffer> offer(newOfferSpy.first().first().value<DataControlOffer *>());
293 QCOMPARE(selectionSpy.first().first().value<struct ::zwlr_data_control_offer_v1 *>(), offer->object());
294
295 QCOMPARE(offer->receivedOffers().count(), 2);
296 QCOMPARE(offer->receivedOffers()[0], "text/test1");
297 QCOMPARE(offer->receivedOffers()[1], "text/test2");
298}
299
300void DataControlInterfaceTest::testCopyFromControl()
301{
302 // we create a data device and set a selection
303 // then confirm the server sees the new selection
304 QSignalSpy serverSelectionChangedSpy(m_seat, &SeatInterface::selectionChanged);
305
306 std::unique_ptr<DataControlDevice> dataControlDevice(new DataControlDevice);
307 dataControlDevice->init(m_dataControlDeviceManager->get_data_device(*m_clientSeat));
308
309 std::unique_ptr<DataControlSource> source(new DataControlSource);
310 source->init(m_dataControlDeviceManager->create_data_source());
311 source->offer("cheese/test1");
312 source->offer("cheese/test2");
313
314 dataControlDevice->set_selection(source->object());
315
316 serverSelectionChangedSpy.wait();
317 QVERIFY(m_seat->selection());
318 QCOMPARE(m_seat->selection()->mimeTypes(), QStringList({"cheese/test1", "cheese/test2"}));
319}
320
321void DataControlInterfaceTest::testCopyFromControlPrimarySelection()
322{
323 // we create a data device and set a selection
324 // then confirm the server sees the new selection
325 QSignalSpy serverSelectionChangedSpy(m_seat, &SeatInterface::primarySelectionChanged);
326
327 std::unique_ptr<DataControlDevice> dataControlDevice(new DataControlDevice);
328 dataControlDevice->init(m_dataControlDeviceManager->get_data_device(*m_clientSeat));
329
330 std::unique_ptr<DataControlSource> source(new DataControlSource);
331 source->init(m_dataControlDeviceManager->create_data_source());
332 source->offer("cheese/test1");
333 source->offer("cheese/test2");
334
335 dataControlDevice->set_primary_selection(source->object());
336
337 serverSelectionChangedSpy.wait();
338 QVERIFY(m_seat->primarySelection());
339 QCOMPARE(m_seat->primarySelection()->mimeTypes(), QStringList({"cheese/test1", "cheese/test2"}));
340}
341
342void DataControlInterfaceTest::testKlipperCase()
343{
344 // This tests the setup of klipper's real world operation and a race with a common pattern seen between clients and klipper
345 // The client's behaviour is faked with direct access to the seat
346
347 std::unique_ptr<DataControlDevice> dataControlDevice(new DataControlDevice);
348 dataControlDevice->init(m_dataControlDeviceManager->get_data_device(*m_clientSeat));
349
350 QSignalSpy newOfferSpy(dataControlDevice.get(), &DataControlDevice::dataControlOffer);
351 QSignalSpy selectionSpy(dataControlDevice.get(), &DataControlDevice::selection);
352 QSignalSpy serverSelectionChangedSpy(m_seat, &SeatInterface::selectionChanged);
353
354 // Client A has a data source
355 std::unique_ptr<TestDataSource> testSelection(new TestDataSource);
356 m_seat->setSelection(testSelection.get());
357
358 // klipper gets it
359 selectionSpy.wait();
360
361 // Client A deletes it
362 testSelection.reset();
363
364 // klipper gets told
365 selectionSpy.wait();
366
367 // Client A sets something else
368 std::unique_ptr<TestDataSource> testSelection2(new TestDataSource);
369 m_seat->setSelection(testSelection2.get());
370
371 // Meanwhile klipper updates with the old content
372 std::unique_ptr<DataControlSource> source(new DataControlSource);
373 source->init(m_dataControlDeviceManager->create_data_source());
374 source->offer("fromKlipper/test1");
375 source->offer("application/x-kde-onlyReplaceEmpty");
376
377 dataControlDevice->set_selection(source->object());
378
379 QVERIFY(!serverSelectionChangedSpy.wait(10));
380 QCOMPARE(m_seat->selection(), testSelection2.get());
381}
382
383QTEST_GUILESS_MAIN(DataControlInterfaceTest)
384
385#include "test_datacontrol_interface.moc"
void primary_selection(struct ::zwlr_data_control_offer_v1 *id)
void dataControlOffer(DataControlOffer *offer)
void selection(struct ::zwlr_data_control_offer_v1 *id)
void zwlr_data_control_device_v1_data_offer(struct ::zwlr_data_control_offer_v1 *id) override
void zwlr_data_control_device_v1_primary_selection(struct ::zwlr_data_control_offer_v1 *id) override
void zwlr_data_control_device_v1_selection(struct ::zwlr_data_control_offer_v1 *id) override
virtual void zwlr_data_control_offer_v1_offer(const QString &mime_type) override
The AbstractDataSource class abstracts the data that can be transferred to another client.
virtual QStringList mimeTypes() const =0
Class holding the Wayland server display loop.
Definition display.h:34
bool addSocketName(const QString &name=QString())
Definition display.cpp:68
bool isRunning() const
Definition display.cpp:144
bool start()
Definition display.cpp:92
Represents a Seat on the Wayland Display.
Definition seat.h:134
void setSelection(AbstractDataSource *selection)
Definition seat.cpp:1283
void setPrimarySelection(AbstractDataSource *selection)
Definition seat.cpp:1319
void selectionChanged(KWin::AbstractDataSource *)
void primarySelectionChanged(KWin::AbstractDataSource *)
AbstractDataSource * selection() const
Definition seat.cpp:1278
AbstractDataSource * primarySelection() const
Definition seat.cpp:1314
void requestData(const QString &mimeType, qint32 fd) override
QStringList mimeTypes() const override
Q_DECLARE_METATYPE(KWin::SwitchEvent::State)
KWayland::Client::Registry * registry
constexpr int version
#define CLEANUP(variable)