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(&registry, &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