1 /*
2 SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5 */
6 // Qt
7 #include <QSignalSpy>
8 #include <QTest>
9 #include <QThread>
10 // client
11 #include "../../src/client/compositor.h"
12 #include "../../src/client/connection_thread.h"
13 #include "../../src/client/datadevice.h"
14 #include "../../src/client/datadevicemanager.h"
15 #include "../../src/client/datasource.h"
16 #include "../../src/client/event_queue.h"
17 #include "../../src/client/keyboard.h"
18 #include "../../src/client/registry.h"
19 #include "../../src/client/seat.h"
20 #include "../../src/client/surface.h"
21 // server
22 #include "../../src/server/compositor_interface.h"
23 #include "../../src/server/datadevicemanager_interface.h"
24 #include "../../src/server/display.h"
25 #include "../../src/server/seat_interface.h"
26
27 using namespace KWayland::Client;
28 using namespace KWayland::Server;
29
30 class SelectionTest : public QObject
31 {
32 Q_OBJECT
33 private Q_SLOTS:
34 void init();
35 void cleanup();
36 void testClearOnEnter();
37
38 private:
39 Display *m_display = nullptr;
40 CompositorInterface *m_compositorInterface = nullptr;
41 SeatInterface *m_seatInterface = nullptr;
42 DataDeviceManagerInterface *m_ddmInterface = nullptr;
43
44 struct Connection {
45 ConnectionThread *connection = nullptr;
46 QThread *thread = nullptr;
47 EventQueue *queue = nullptr;
48 Compositor *compositor = nullptr;
49 Seat *seat = nullptr;
50 DataDeviceManager *ddm = nullptr;
51 Keyboard *keyboard = nullptr;
52 DataDevice *dataDevice = nullptr;
53 };
54 bool setupConnection(Connection *c);
55 void cleanupConnection(Connection *c);
56
57 Connection m_client1;
58 Connection m_client2;
59 };
60
61 static const QString s_socketName = QStringLiteral("kwayland-test-selection-0");
62
init()63 void SelectionTest::init()
64 {
65 delete m_display;
66 m_display = new Display(this);
67 m_display->setSocketName(s_socketName);
68 m_display->start();
69 QVERIFY(m_display->isRunning());
70 m_display->createShm();
71 m_compositorInterface = m_display->createCompositor(m_display);
72 m_compositorInterface->create();
73 m_seatInterface = m_display->createSeat(m_display);
74 m_seatInterface->setHasKeyboard(true);
75 m_seatInterface->create();
76 m_ddmInterface = m_display->createDataDeviceManager(m_display);
77 m_ddmInterface->create();
78
79 // setup connection
80 setupConnection(&m_client1);
81 setupConnection(&m_client2);
82 }
83
setupConnection(Connection * c)84 bool SelectionTest::setupConnection(Connection *c)
85 {
86 c->connection = new ConnectionThread;
87 QSignalSpy connectedSpy(c->connection, &ConnectionThread::connected);
88 if (!connectedSpy.isValid()) {
89 return false;
90 }
91 c->connection->setSocketName(s_socketName);
92
93 c->thread = new QThread(this);
94 c->connection->moveToThread(c->thread);
95 c->thread->start();
96
97 c->connection->initConnection();
98 if (!connectedSpy.wait(500)) {
99 return false;
100 }
101
102 c->queue = new EventQueue(this);
103 c->queue->setup(c->connection);
104
105 Registry registry;
106 QSignalSpy interfacesAnnouncedSpy(®istry, &Registry::interfacesAnnounced);
107 if (!interfacesAnnouncedSpy.isValid()) {
108 return false;
109 }
110 registry.setEventQueue(c->queue);
111 registry.create(c->connection);
112 if (!registry.isValid()) {
113 return false;
114 }
115 registry.setup();
116 if (!interfacesAnnouncedSpy.wait(500)) {
117 return false;
118 }
119
120 auto compositorInterface = registry.interface(Registry::Interface::Compositor);
121 c->compositor = registry.createCompositor(compositorInterface.name, compositorInterface.version, this);
122 if (!c->compositor->isValid()) {
123 return false;
124 }
125 c->ddm = registry.createDataDeviceManager(registry.interface(Registry::Interface::DataDeviceManager).name,
126 registry.interface(Registry::Interface::DataDeviceManager).version,
127 this);
128 if (!c->ddm->isValid()) {
129 return false;
130 }
131 c->seat = registry.createSeat(registry.interface(Registry::Interface::Seat).name, registry.interface(Registry::Interface::Seat).version, this);
132 if (!c->seat->isValid()) {
133 return false;
134 }
135 QSignalSpy keyboardSpy(c->seat, &Seat::hasKeyboardChanged);
136 if (!keyboardSpy.isValid()) {
137 return false;
138 }
139 if (!keyboardSpy.wait(500)) {
140 return false;
141 }
142 if (!c->seat->hasKeyboard()) {
143 return false;
144 }
145 c->keyboard = c->seat->createKeyboard(c->seat);
146 if (!c->keyboard->isValid()) {
147 return false;
148 }
149 c->dataDevice = c->ddm->getDataDevice(c->seat, this);
150 if (!c->dataDevice->isValid()) {
151 return false;
152 }
153
154 return true;
155 }
156
cleanup()157 void SelectionTest::cleanup()
158 {
159 cleanupConnection(&m_client1);
160 cleanupConnection(&m_client2);
161 #define CLEANUP(variable) \
162 delete variable; \
163 variable = nullptr;
164
165 CLEANUP(m_ddmInterface)
166 CLEANUP(m_seatInterface)
167 CLEANUP(m_compositorInterface)
168 CLEANUP(m_display)
169 #undef CLEANUP
170 }
171
cleanupConnection(Connection * c)172 void SelectionTest::cleanupConnection(Connection *c)
173 {
174 delete c->dataDevice;
175 c->dataDevice = nullptr;
176 delete c->keyboard;
177 c->keyboard = nullptr;
178 delete c->ddm;
179 c->ddm = nullptr;
180 delete c->seat;
181 c->seat = nullptr;
182 delete c->compositor;
183 c->compositor = nullptr;
184 delete c->queue;
185 c->queue = nullptr;
186 if (c->connection) {
187 c->connection->deleteLater();
188 c->connection = nullptr;
189 }
190 if (c->thread) {
191 c->thread->quit();
192 c->thread->wait();
193 delete c->thread;
194 c->thread = nullptr;
195 }
196 }
197
testClearOnEnter()198 void SelectionTest::testClearOnEnter()
199 {
200 // this test verifies that the selection is cleared prior to keyboard enter if there is no current selection
201 QSignalSpy selectionClearedClient1Spy(m_client1.dataDevice, &DataDevice::selectionCleared);
202 QVERIFY(selectionClearedClient1Spy.isValid());
203 QSignalSpy keyboardEnteredClient1Spy(m_client1.keyboard, &Keyboard::entered);
204 QVERIFY(keyboardEnteredClient1Spy.isValid());
205
206 // now create a Surface
207 QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated);
208 QVERIFY(surfaceCreatedSpy.isValid());
209 QScopedPointer<Surface> s1(m_client1.compositor->createSurface());
210 QVERIFY(surfaceCreatedSpy.wait());
211 auto serverSurface1 = surfaceCreatedSpy.first().first().value<SurfaceInterface *>();
212 QVERIFY(serverSurface1);
213
214 // pass this surface keyboard focus
215 m_seatInterface->setFocusedKeyboardSurface(serverSurface1);
216 // should get a clear
217 QVERIFY(selectionClearedClient1Spy.wait());
218
219 // let's set a selection
220 QScopedPointer<DataSource> dataSource(m_client1.ddm->createDataSource());
221 dataSource->offer(QStringLiteral("text/plain"));
222 m_client1.dataDevice->setSelection(keyboardEnteredClient1Spy.first().first().value<quint32>(), dataSource.data());
223
224 // now let's bring in client 2
225 QSignalSpy selectionOfferedClient2Spy(m_client2.dataDevice, &DataDevice::selectionOffered);
226 QVERIFY(selectionOfferedClient2Spy.isValid());
227 QSignalSpy selectionClearedClient2Spy(m_client2.dataDevice, &DataDevice::selectionCleared);
228 QVERIFY(selectionClearedClient2Spy.isValid());
229 QSignalSpy keyboardEnteredClient2Spy(m_client2.keyboard, &Keyboard::entered);
230 QVERIFY(keyboardEnteredClient2Spy.isValid());
231 QScopedPointer<Surface> s2(m_client2.compositor->createSurface());
232 QVERIFY(surfaceCreatedSpy.wait());
233 auto serverSurface2 = surfaceCreatedSpy.last().first().value<SurfaceInterface *>();
234 QVERIFY(serverSurface2);
235
236 // entering that surface should give a selection offer
237 m_seatInterface->setFocusedKeyboardSurface(serverSurface2);
238 QVERIFY(selectionOfferedClient2Spy.wait());
239 QVERIFY(selectionClearedClient2Spy.isEmpty());
240
241 // set a data source but without offers
242 QScopedPointer<DataSource> dataSource2(m_client2.ddm->createDataSource());
243 m_client2.dataDevice->setSelection(keyboardEnteredClient2Spy.first().first().value<quint32>(), dataSource2.data());
244 QVERIFY(selectionOfferedClient2Spy.wait());
245 // and clear
246 m_client2.dataDevice->clearSelection(keyboardEnteredClient2Spy.first().first().value<quint32>());
247 QVERIFY(selectionClearedClient2Spy.wait());
248
249 // now pass focus to first surface
250 m_seatInterface->setFocusedKeyboardSurface(serverSurface1);
251 // we should get a clear
252 QVERIFY(selectionClearedClient1Spy.wait());
253 }
254
255 QTEST_GUILESS_MAIN(SelectionTest)
256 #include "test_selection.moc"
257