KWin
Loading...
Searching...
No Matches
test_textinputv3_interface.cpp
Go to the documentation of this file.
1/*
2 SPDX-FileCopyrightText: 2020 Bhushan Shah <bshah@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5*/
6
7#include <QSignalSpy>
8#include <QTest>
9#include <QThread>
10
11#include "wayland/compositor.h"
12#include "wayland/display.h"
13#include "wayland/seat.h"
14#include "wayland/surface.h"
16
17#include "KWayland/Client/compositor.h"
18#include "KWayland/Client/connection_thread.h"
19#include "KWayland/Client/event_queue.h"
20#include "KWayland/Client/registry.h"
21#include "KWayland/Client/seat.h"
22#include "KWayland/Client/surface.h"
23
24#include "qwayland-text-input-unstable-v3.h"
25
26using namespace KWin;
27
28Q_DECLARE_METATYPE(QtWayland::zwp_text_input_v3::content_purpose)
29Q_DECLARE_METATYPE(QtWayland::zwp_text_input_v3::content_hint)
30
31class TextInputV3 : public QObject, public QtWayland::zwp_text_input_v3
32{
33 Q_OBJECT
34Q_SIGNALS:
35 void surface_enter(wl_surface *surface);
36 void surface_leave(wl_surface *surface);
37 void commit_string(const QString &text);
38 void delete_surrounding_text(quint32 before_length, quint32 after_length);
39 void preedit_string(const QString &text, quint32 cursor_begin, quint32 cursor_end);
40 void done(quint32 serial);
41
42public:
43 ~TextInputV3() override
44 {
45 destroy();
46 }
47 void zwp_text_input_v3_enter(struct ::wl_surface *surface) override
48 {
49 Q_EMIT surface_enter(surface);
50 }
51 void zwp_text_input_v3_leave(struct ::wl_surface *surface) override
52 {
53 Q_EMIT surface_leave(surface);
54 }
55 void zwp_text_input_v3_commit_string(const QString &text) override
56 {
57 commitText = text;
58 }
59 void zwp_text_input_v3_delete_surrounding_text(uint32_t before_length, uint32_t after_length) override
60 {
61 before = before_length;
62 after = after_length;
63 }
64 void zwp_text_input_v3_done(uint32_t serial) override
65 {
66 Q_EMIT commit_string(commitText);
67 Q_EMIT preedit_string(preeditText, cursorBegin, cursorEnd);
68 Q_EMIT delete_surrounding_text(before, after);
69 Q_EMIT done(serial);
70 }
71 void zwp_text_input_v3_preedit_string(const QString &text, int32_t cursor_begin, int32_t cursor_end) override
72 {
73 preeditText = text;
74 cursorBegin = cursor_begin;
75 cursorEnd = cursor_end;
76 }
77
78private:
79 QString preeditText;
80 QString commitText;
81 uint32_t cursorBegin, cursorEnd;
82 uint32_t before, after;
83};
84
85class TextInputManagerV3 : public QtWayland::zwp_text_input_manager_v3
86{
87public:
89 {
90 destroy();
91 }
92};
93
94class TestTextInputV3Interface : public QObject
95{
96 Q_OBJECT
97
98public:
100
101private Q_SLOTS:
102 void initTestCase();
103 void testEnableDisable();
104 void testEvents();
105 void testContentPurpose_data();
106 void testContentPurpose();
107 void testContentHints_data();
108 void testContentHints();
109 void testMultipleTextinputs();
110
111private:
112 KWayland::Client::ConnectionThread *m_connection;
113 KWayland::Client::EventQueue *m_queue;
114 KWayland::Client::Compositor *m_clientCompositor;
115 KWayland::Client::Seat *m_clientSeat = nullptr;
116
117 SeatInterface *m_seat;
118 QThread *m_thread;
119 KWin::Display m_display;
120 TextInputV3 *m_clientTextInputV3;
121 CompositorInterface *m_serverCompositor;
122 TextInputV3Interface *m_serverTextInputV3;
123 TextInputManagerV3 *m_clientTextInputManagerV3;
124
125 quint32 m_totalCommits = 0;
126};
127
128static const QString s_socketName = QStringLiteral("kwin-wayland-server-text-input-v3-test-0");
129
130void TestTextInputV3Interface::initTestCase()
131{
132 m_display.addSocketName(s_socketName);
133 m_display.start();
134 QVERIFY(m_display.isRunning());
135
136 m_seat = new SeatInterface(&m_display, this);
137 m_seat->setHasKeyboard(true);
138
139 m_serverCompositor = new CompositorInterface(&m_display, this);
140 new TextInputManagerV3Interface(&m_display);
141
142 m_connection = new KWayland::Client::ConnectionThread;
143 QSignalSpy connectedSpy(m_connection, &KWayland::Client::ConnectionThread::connected);
144 m_connection->setSocketName(s_socketName);
145
146 m_thread = new QThread(this);
147 m_connection->moveToThread(m_thread);
148 m_thread->start();
149
150 m_connection->initConnection();
151 QVERIFY(connectedSpy.wait());
152 QVERIFY(!m_connection->connections().isEmpty());
153
154 m_queue = new KWayland::Client::EventQueue(this);
155 QVERIFY(!m_queue->isValid());
156 m_queue->setup(m_connection);
157 QVERIFY(m_queue->isValid());
158
159 auto registry = new KWayland::Client::Registry(this);
160 connect(registry, &KWayland::Client::Registry::interfaceAnnounced, this, [this, registry](const QByteArray &interface, quint32 id, quint32 version) {
161 if (interface == QByteArrayLiteral("zwp_text_input_manager_v3")) {
162 m_clientTextInputManagerV3 = new TextInputManagerV3();
163 m_clientTextInputManagerV3->init(*registry, id, version);
164 }
165 });
166
167 connect(registry, &KWayland::Client::Registry::seatAnnounced, this, [this, registry](quint32 name, quint32 version) {
168 m_clientSeat = registry->createSeat(name, version);
169 });
170
171 QSignalSpy allAnnouncedSpy(registry, &KWayland::Client::Registry::interfaceAnnounced);
172 QSignalSpy compositorSpy(registry, &KWayland::Client::Registry::compositorAnnounced);
173 QSignalSpy shmSpy(registry, &KWayland::Client::Registry::shmAnnounced);
174 registry->setEventQueue(m_queue);
175 registry->create(m_connection->display());
176 QVERIFY(registry->isValid());
177 registry->setup();
178 QVERIFY(allAnnouncedSpy.wait());
179
180 m_clientCompositor = registry->createCompositor(compositorSpy.first().first().value<quint32>(), compositorSpy.first().last().value<quint32>(), this);
181 QVERIFY(m_clientCompositor->isValid());
182 // create a text input v3
183 m_clientTextInputV3 = new TextInputV3();
184 m_clientTextInputV3->init(m_clientTextInputManagerV3->get_text_input(*m_clientSeat));
185 QVERIFY(m_clientTextInputV3);
186}
187
189{
190 if (m_clientTextInputV3) {
191 delete m_clientTextInputV3;
192 m_clientTextInputV3 = nullptr;
193 }
194 if (m_clientTextInputManagerV3) {
195 delete m_clientTextInputManagerV3;
196 m_clientTextInputManagerV3 = nullptr;
197 }
198 if (m_queue) {
199 delete m_queue;
200 m_queue = nullptr;
201 }
202 if (m_thread) {
203 m_thread->quit();
204 m_thread->wait();
205 delete m_thread;
206 m_thread = nullptr;
207 }
208 m_connection->deleteLater();
209 m_connection = nullptr;
210}
211
212// Ensures that enable disable events don't fire without commit
213void TestTextInputV3Interface::testEnableDisable()
214{
215 // create a surface
216 QSignalSpy serverSurfaceCreatedSpy(m_serverCompositor, &CompositorInterface::surfaceCreated);
217 std::unique_ptr<KWayland::Client::Surface> clientSurface(m_clientCompositor->createSurface(this));
218 QVERIFY(serverSurfaceCreatedSpy.wait());
219 SurfaceInterface *serverSurface = serverSurfaceCreatedSpy.first().first().value<SurfaceInterface *>();
220 QVERIFY(serverSurface);
221
222 m_serverTextInputV3 = m_seat->textInputV3();
223 QVERIFY(m_serverTextInputV3);
224
225 QSignalSpy focusedSurfaceChangedSpy(m_seat, &SeatInterface::focusedTextInputSurfaceChanged);
226 QSignalSpy textInputEnabledSpy(m_serverTextInputV3, &TextInputV3Interface::enabledChanged);
227 QSignalSpy cursorRectangleChangedSpy(m_serverTextInputV3, &TextInputV3Interface::cursorRectangleChanged);
228 QSignalSpy enableRequestedSpy(m_serverTextInputV3, &TextInputV3Interface::enableRequested);
229
230 QSignalSpy surfaceEnterSpy(m_clientTextInputV3, &TextInputV3::surface_enter);
231 QSignalSpy surfaceLeaveSpy(m_clientTextInputV3, &TextInputV3::surface_leave);
232
233 // Enter the textinput
234
235 QCOMPARE(focusedSurfaceChangedSpy.count(), 0);
236
237 // Make sure that entering surface does not trigger the text input
238 m_seat->setFocusedTextInputSurface(serverSurface);
239 QVERIFY(surfaceEnterSpy.wait());
240 QCOMPARE(surfaceEnterSpy.count(), 1);
241 QCOMPARE(focusedSurfaceChangedSpy.count(), 1);
242 QCOMPARE(textInputEnabledSpy.count(), 0);
243
244 // Now enable the textInput, we should not get event just yet
245 m_clientTextInputV3->enable();
246 m_clientTextInputV3->set_cursor_rectangle(0, 0, 20, 20);
247 m_clientTextInputV3->set_surrounding_text("KDE Plasma Desktop", 0, 3);
248 QCOMPARE(textInputEnabledSpy.count(), 0);
249 QCOMPARE(cursorRectangleChangedSpy.count(), 0);
250
251 // after we do commit we should get event
252 m_clientTextInputV3->commit();
253 QVERIFY(textInputEnabledSpy.wait());
254 m_totalCommits++;
255
256 QCOMPARE(enableRequestedSpy.count(), 0);
257 QCOMPARE(textInputEnabledSpy.count(), 1);
258 QCOMPARE(cursorRectangleChangedSpy.count(), 1);
259 QCOMPARE(m_serverTextInputV3->cursorRectangle(), QRect(0, 0, 20, 20));
260 QCOMPARE(m_serverTextInputV3->surroundingText(), QString("KDE Plasma Desktop"));
261 QCOMPARE(m_serverTextInputV3->surroundingTextCursorPosition(), 0);
262 QCOMPARE(m_serverTextInputV3->surroundingTextSelectionAnchor(), 3);
263
264 // Do another enable when it's already enabled.
265 m_clientTextInputV3->enable();
266 m_clientTextInputV3->set_cursor_rectangle(0, 0, 20, 20);
267 m_clientTextInputV3->set_surrounding_text("KDE Plasma Desktop", 0, 3);
268 m_clientTextInputV3->commit();
269 QVERIFY(enableRequestedSpy.wait());
270 QCOMPARE(textInputEnabledSpy.count(), 1);
271 QCOMPARE(cursorRectangleChangedSpy.count(), 1);
272 QCOMPARE(m_serverTextInputV3->cursorRectangle(), QRect(0, 0, 20, 20));
273 QCOMPARE(m_serverTextInputV3->surroundingText(), QString("KDE Plasma Desktop"));
274 QCOMPARE(m_serverTextInputV3->surroundingTextCursorPosition(), 0);
275 QCOMPARE(m_serverTextInputV3->surroundingTextSelectionAnchor(), 3);
276 m_totalCommits++;
277
278 // disabling we should not get the event
279 m_clientTextInputV3->disable();
280 QCOMPARE(textInputEnabledSpy.count(), 1);
281
282 // after we do commit we should get event
283 m_clientTextInputV3->commit();
284 QVERIFY(textInputEnabledSpy.wait());
285 QCOMPARE(textInputEnabledSpy.count(), 2);
286 m_totalCommits++;
287
288 // Lets try leaving the surface and make sure event propogage
289 m_seat->setFocusedTextInputSurface(nullptr);
290 QVERIFY(surfaceLeaveSpy.wait());
291 QCOMPARE(surfaceLeaveSpy.count(), 1);
292}
293
294void TestTextInputV3Interface::testEvents()
295{
296 // create a surface
297 QSignalSpy serverSurfaceCreatedSpy(m_serverCompositor, &CompositorInterface::surfaceCreated);
298 std::unique_ptr<KWayland::Client::Surface> clientSurface(m_clientCompositor->createSurface(this));
299 QVERIFY(serverSurfaceCreatedSpy.wait());
300 SurfaceInterface *serverSurface = serverSurfaceCreatedSpy.first().first().value<SurfaceInterface *>();
301 QVERIFY(serverSurface);
302
303 m_serverTextInputV3 = m_seat->textInputV3();
304 QVERIFY(m_serverTextInputV3);
305
306 QSignalSpy focusedSurfaceChangedSpy(m_seat, &SeatInterface::focusedTextInputSurfaceChanged);
307 QSignalSpy textInputEnabledSpy(m_serverTextInputV3, &TextInputV3Interface::enabledChanged);
308 QSignalSpy doneSpy(m_clientTextInputV3, &TextInputV3::done);
309
310 // Enter the textinput
311 QCOMPARE(focusedSurfaceChangedSpy.count(), 0);
312
313 // Make sure that entering surface does not trigger the text input
314 m_seat->setFocusedTextInputSurface(serverSurface);
315 // FIXME: somehow this triggers BEFORE setFocusedTextInputSurface returns :(
316 // QVERIFY(focusedSurfaceChangedSpy.wait());
317 QCOMPARE(focusedSurfaceChangedSpy.count(), 1);
318
319 // Now enable the textInput
320 m_clientTextInputV3->enable();
321 m_clientTextInputV3->commit();
322 m_totalCommits++;
323 QVERIFY(textInputEnabledSpy.wait());
324 QVERIFY(doneSpy.wait());
325 QCOMPARE(doneSpy.count(), 1);
326
327 QSignalSpy preEditSpy(m_clientTextInputV3, &TextInputV3::preedit_string);
328 QSignalSpy commitStringSpy(m_clientTextInputV3, &TextInputV3::commit_string);
329 QSignalSpy deleteSurroundingSpy(m_clientTextInputV3, &TextInputV3::delete_surrounding_text);
330
331 m_serverTextInputV3->sendPreEditString("Hello KDE community!", 1, 2);
332 m_serverTextInputV3->deleteSurroundingText(6, 10);
333 m_serverTextInputV3->commitString("Plasma");
334 m_serverTextInputV3->done();
335
336 QVERIFY(doneSpy.wait());
337 QCOMPARE(doneSpy.count(), 2);
338 QCOMPARE(preEditSpy.count(), 1);
339 QCOMPARE(commitStringSpy.count(), 1);
340 QCOMPARE(deleteSurroundingSpy.count(), 1);
341
342 QCOMPARE(preEditSpy.last().at(0).value<QString>(), "Hello KDE community!");
343 QCOMPARE(preEditSpy.last().at(1).value<quint32>(), 1);
344 QCOMPARE(preEditSpy.last().at(2).value<quint32>(), 2);
345 QCOMPARE(commitStringSpy.last().at(0).value<QString>(), "Plasma");
346 QCOMPARE(deleteSurroundingSpy.last().at(0).value<quint32>(), 6);
347 QCOMPARE(deleteSurroundingSpy.last().at(1).value<quint32>(), 10);
348
349 // zwp_text_input_v3.done event have serial of total commits
350 QCOMPARE(doneSpy.last().at(0).value<quint32>(), m_totalCommits);
351
352 // Now disable the textInput
353 m_clientTextInputV3->disable();
354 m_clientTextInputV3->commit();
355 m_totalCommits++;
356 QVERIFY(textInputEnabledSpy.wait());
357}
358
359void TestTextInputV3Interface::testContentPurpose_data()
360{
361 QTest::addColumn<QtWayland::zwp_text_input_v3::content_purpose>("clientPurpose");
362 QTest::addColumn<KWin::TextInputContentPurpose>("serverPurpose");
363
364 QTest::newRow("Alpha") << QtWayland::zwp_text_input_v3::content_purpose_alpha << TextInputContentPurpose::Alpha;
365 QTest::newRow("Digits") << QtWayland::zwp_text_input_v3::content_purpose_digits << TextInputContentPurpose::Digits;
366 QTest::newRow("Number") << QtWayland::zwp_text_input_v3::content_purpose_number << TextInputContentPurpose::Number;
367 QTest::newRow("Phone") << QtWayland::zwp_text_input_v3::content_purpose_phone << TextInputContentPurpose::Phone;
368 QTest::newRow("Url") << QtWayland::zwp_text_input_v3::content_purpose_url << TextInputContentPurpose::Url;
369 QTest::newRow("Email") << QtWayland::zwp_text_input_v3::content_purpose_email << TextInputContentPurpose::Email;
370 QTest::newRow("Name") << QtWayland::zwp_text_input_v3::content_purpose_name << TextInputContentPurpose::Name;
371 QTest::newRow("Password") << QtWayland::zwp_text_input_v3::content_purpose_password << TextInputContentPurpose::Password;
372 QTest::newRow("Pin") << QtWayland::zwp_text_input_v3::content_purpose_pin << TextInputContentPurpose::Pin;
373 QTest::newRow("Date") << QtWayland::zwp_text_input_v3::content_purpose_date << TextInputContentPurpose::Date;
374 QTest::newRow("Time") << QtWayland::zwp_text_input_v3::content_purpose_time << TextInputContentPurpose::Time;
375 QTest::newRow("DateTime") << QtWayland::zwp_text_input_v3::content_purpose_datetime << TextInputContentPurpose::DateTime;
376 QTest::newRow("Terminal") << QtWayland::zwp_text_input_v3::content_purpose_terminal << TextInputContentPurpose::Terminal;
377}
378
379void TestTextInputV3Interface::testContentPurpose()
380{
381 // create a surface
382 QSignalSpy serverSurfaceCreatedSpy(m_serverCompositor, &CompositorInterface::surfaceCreated);
383 std::unique_ptr<KWayland::Client::Surface> clientSurface(m_clientCompositor->createSurface(this));
384 QVERIFY(serverSurfaceCreatedSpy.wait());
385 SurfaceInterface *serverSurface = serverSurfaceCreatedSpy.first().first().value<SurfaceInterface *>();
386 QVERIFY(serverSurface);
387
388 m_serverTextInputV3 = m_seat->textInputV3();
389 QVERIFY(m_serverTextInputV3);
390
391 QSignalSpy focusedSurfaceChangedSpy(m_seat, &SeatInterface::focusedTextInputSurfaceChanged);
392 QSignalSpy textInputEnabledSpy(m_serverTextInputV3, &TextInputV3Interface::enabledChanged);
393
394 // Enter the textinput
395 QCOMPARE(focusedSurfaceChangedSpy.count(), 0);
396
397 // Make sure that entering surface does not trigger the text input
398 m_seat->setFocusedTextInputSurface(serverSurface);
399 // FIXME: somehow this triggers BEFORE setFocusedTextInputSurface returns :(
400 // QVERIFY(focusedSurfaceChangedSpy.wait());
401 QCOMPARE(focusedSurfaceChangedSpy.count(), 1);
402
403 // Now enable the textInput
404 m_clientTextInputV3->enable();
405 m_clientTextInputV3->commit();
406 QVERIFY(textInputEnabledSpy.wait());
407 m_totalCommits++;
408
409 // Default should be normal content purpose
410 QCOMPARE(m_serverTextInputV3->contentPurpose(), TextInputContentPurpose::Normal);
411
412 QSignalSpy contentTypeChangedSpy(m_serverTextInputV3, &TextInputV3Interface::contentTypeChanged);
413
414 QFETCH(QtWayland::zwp_text_input_v3::content_purpose, clientPurpose);
415 m_clientTextInputV3->enable();
416 m_clientTextInputV3->set_content_type(QtWayland::zwp_text_input_v3::content_hint_none, clientPurpose);
417 m_clientTextInputV3->commit();
418 QVERIFY(contentTypeChangedSpy.wait());
419 QTEST(m_serverTextInputV3->contentPurpose(), "serverPurpose");
420 m_totalCommits++;
421
422 // Setting same thing should not trigger update
423 m_clientTextInputV3->enable();
424 m_clientTextInputV3->set_content_type(QtWayland::zwp_text_input_v3::content_hint_none, clientPurpose);
425 m_clientTextInputV3->commit();
426 QVERIFY(!contentTypeChangedSpy.wait(100));
427 m_totalCommits++;
428
429 // unset to normal
430 m_clientTextInputV3->enable();
431 m_clientTextInputV3->set_content_type(QtWayland::zwp_text_input_v3::content_hint_none, QtWayland::zwp_text_input_v3::content_purpose_normal);
432 m_clientTextInputV3->commit();
433 QVERIFY(contentTypeChangedSpy.wait());
434 m_totalCommits++;
435 QCOMPARE(m_serverTextInputV3->contentPurpose(), TextInputContentPurpose::Normal);
436
437 // Now disable the textInput
438 m_clientTextInputV3->disable();
439 m_clientTextInputV3->commit();
440 m_totalCommits++;
441 QVERIFY(textInputEnabledSpy.wait());
442}
443
444void TestTextInputV3Interface::testContentHints_data()
445{
446 QTest::addColumn<quint32>("clientHint");
447 QTest::addColumn<KWin::TextInputContentHints>("serverHints");
448
449 QTest::addRow("Spellcheck") << quint32(QtWayland::zwp_text_input_v3::content_hint_spellcheck)
450 << TextInputContentHints(TextInputContentHint::AutoCorrection);
451 QTest::addRow("Completion") << quint32(QtWayland::zwp_text_input_v3::content_hint_completion)
452 << TextInputContentHints(TextInputContentHint::AutoCompletion);
453 QTest::addRow("AutoCapital") << quint32(QtWayland::zwp_text_input_v3::content_hint_auto_capitalization)
454 << TextInputContentHints(TextInputContentHint::AutoCapitalization);
455 QTest::addRow("Lowercase") << quint32(QtWayland::zwp_text_input_v3::content_hint_lowercase) << TextInputContentHints(TextInputContentHint::LowerCase);
456 QTest::addRow("Uppercase") << quint32(QtWayland::zwp_text_input_v3::content_hint_uppercase) << TextInputContentHints(TextInputContentHint::UpperCase);
457 QTest::addRow("Titlecase") << quint32(QtWayland::zwp_text_input_v3::content_hint_titlecase) << TextInputContentHints(TextInputContentHint::TitleCase);
458 QTest::addRow("HiddenText") << quint32(QtWayland::zwp_text_input_v3::content_hint_hidden_text) << TextInputContentHints(TextInputContentHint::HiddenText);
459 QTest::addRow("SensitiveData") << quint32(QtWayland::zwp_text_input_v3::content_hint_sensitive_data)
460 << TextInputContentHints(TextInputContentHint::SensitiveData);
461 QTest::addRow("Latin") << quint32(QtWayland::zwp_text_input_v3::content_hint_latin) << TextInputContentHints(TextInputContentHint::Latin);
462 QTest::addRow("Multiline") << quint32(QtWayland::zwp_text_input_v3::content_hint_multiline) << TextInputContentHints(TextInputContentHint::MultiLine);
463 QTest::addRow("Auto") << quint32(QtWayland::zwp_text_input_v3::content_hint_completion | QtWayland::zwp_text_input_v3::content_hint_spellcheck
464 | QtWayland::zwp_text_input_v3::content_hint_auto_capitalization)
465 << TextInputContentHints(TextInputContentHint::AutoCompletion | TextInputContentHint::AutoCorrection
466 | TextInputContentHint::AutoCapitalization);
467}
468
469void TestTextInputV3Interface::testContentHints()
470{
471 // create a surface
472 QSignalSpy serverSurfaceCreatedSpy(m_serverCompositor, &CompositorInterface::surfaceCreated);
473 std::unique_ptr<KWayland::Client::Surface> clientSurface(m_clientCompositor->createSurface(this));
474 QVERIFY(serverSurfaceCreatedSpy.wait());
475 SurfaceInterface *serverSurface = serverSurfaceCreatedSpy.first().first().value<SurfaceInterface *>();
476 QVERIFY(serverSurface);
477
478 m_serverTextInputV3 = m_seat->textInputV3();
479 QVERIFY(m_serverTextInputV3);
480
481 QSignalSpy focusedSurfaceChangedSpy(m_seat, &SeatInterface::focusedTextInputSurfaceChanged);
482 QSignalSpy textInputEnabledSpy(m_serverTextInputV3, &TextInputV3Interface::enabledChanged);
483
484 // Enter the textinput
485 QCOMPARE(focusedSurfaceChangedSpy.count(), 0);
486
487 // Make sure that entering surface does not trigger the text input
488 m_seat->setFocusedTextInputSurface(serverSurface);
489 // FIXME: somehow this triggers BEFORE setFocusedTextInputSurface returns :(
490 // QVERIFY(focusedSurfaceChangedSpy.wait());
491 QCOMPARE(focusedSurfaceChangedSpy.count(), 1);
492
493 // Now enable the textInput
494 m_clientTextInputV3->enable();
495 m_clientTextInputV3->commit();
496 QVERIFY(textInputEnabledSpy.wait());
497 m_totalCommits++;
498
499 QCOMPARE(m_serverTextInputV3->contentHints(), TextInputContentHint::None);
500
501 // Now disable the textInput
502 m_clientTextInputV3->disable();
503 m_clientTextInputV3->commit();
504 QVERIFY(textInputEnabledSpy.wait());
505 m_totalCommits++;
506
507 QSignalSpy contentTypeChangedSpy(m_serverTextInputV3, &TextInputV3Interface::contentTypeChanged);
508
509 QFETCH(quint32, clientHint);
510 m_clientTextInputV3->enable();
511 m_clientTextInputV3->set_content_type(clientHint, QtWayland::zwp_text_input_v3::content_purpose_normal);
512 m_clientTextInputV3->commit();
513 QVERIFY(contentTypeChangedSpy.wait());
514 QTEST(m_serverTextInputV3->contentHints(), "serverHints");
515 m_totalCommits++;
516
517 // Setting same thing should not trigger update
518 m_clientTextInputV3->enable();
519 m_clientTextInputV3->set_content_type(clientHint, QtWayland::zwp_text_input_v3::content_purpose_normal);
520 m_clientTextInputV3->commit();
521 QVERIFY(!contentTypeChangedSpy.wait(100));
522 m_totalCommits++;
523
524 // unset to normal
525 m_clientTextInputV3->enable();
526 m_clientTextInputV3->set_content_type(QtWayland::zwp_text_input_v3::content_hint_none, QtWayland::zwp_text_input_v3::content_purpose_normal);
527 m_clientTextInputV3->commit();
528 QVERIFY(contentTypeChangedSpy.wait());
529 m_totalCommits++;
530
531 // Now disable the textInput
532 m_clientTextInputV3->disable();
533 m_clientTextInputV3->commit();
534 QVERIFY(textInputEnabledSpy.wait());
535 m_totalCommits++;
536}
537
538void TestTextInputV3Interface::testMultipleTextinputs()
539{
540 // create two more text inputs
541 TextInputV3 *ti1 = new TextInputV3();
542 ti1->init(m_clientTextInputManagerV3->get_text_input(*m_clientSeat));
543 QVERIFY(ti1);
544
545 TextInputV3 *ti2 = new TextInputV3();
546 ti2->init(m_clientTextInputManagerV3->get_text_input(*m_clientSeat));
547 QVERIFY(ti2);
548
549 // create a surface
550 QSignalSpy serverSurfaceCreatedSpy(m_serverCompositor, &CompositorInterface::surfaceCreated);
551 std::unique_ptr<KWayland::Client::Surface> clientSurface(m_clientCompositor->createSurface(this));
552 QVERIFY(serverSurfaceCreatedSpy.wait());
553 SurfaceInterface *serverSurface = serverSurfaceCreatedSpy.first().first().value<SurfaceInterface *>();
554 QVERIFY(serverSurface);
555
556 QSignalSpy focusedSurfaceChangedSpy(m_seat, &SeatInterface::focusedTextInputSurfaceChanged);
557 // Make sure that entering surface does not trigger the text input
558 m_seat->setFocusedTextInputSurface(serverSurface);
559 QCOMPARE(focusedSurfaceChangedSpy.count(), 1);
560
561 m_serverTextInputV3 = m_seat->textInputV3();
562 QVERIFY(m_serverTextInputV3);
563 QVERIFY(!m_serverTextInputV3->isEnabled());
564
565 QSignalSpy doneSpy1(ti1, &TextInputV3::done);
566 QSignalSpy committedSpy(m_serverTextInputV3, &TextInputV3Interface::stateCommitted);
567 // Enable ti1
568 ti1->enable();
569 ti1->commit();
570 QVERIFY(committedSpy.wait());
571 QCOMPARE(committedSpy.last().at(0).value<quint32>(), 1);
572 QVERIFY(m_serverTextInputV3->isEnabled());
573 QVERIFY(doneSpy1.wait());
574
575 // Send another three commits on ti1
576 ti1->enable();
577 ti1->set_surrounding_text("hello", 0, 1);
578 ti1->commit();
579 QVERIFY(committedSpy.wait());
580 QCOMPARE(committedSpy.last().at(0).value<quint32>(), 2);
581 QVERIFY(m_serverTextInputV3->isEnabled());
582 QVERIFY(doneSpy1.wait());
583
584 ti1->enable();
585 ti1->set_content_type(QtWayland::zwp_text_input_v3::content_hint_none, QtWayland::zwp_text_input_v3::content_purpose_normal);
586 ti1->commit();
587 QVERIFY(committedSpy.wait());
588 QCOMPARE(committedSpy.last().at(0).value<quint32>(), 3);
589 QVERIFY(m_serverTextInputV3->isEnabled());
590 QVERIFY(doneSpy1.wait());
591
592 // at this point total commit count to ti1 is 3
593 QSignalSpy doneSpy2(ti2, &TextInputV3::done);
594
595 m_serverTextInputV3->commitString("Hello");
596 m_serverTextInputV3->done();
597 QVERIFY(doneSpy1.wait());
598
599 // zwp_text_input_v3.done event have serial of total commits
600 QCOMPARE(doneSpy1.last().at(0).value<quint32>(), 3);
601
602 // now ti1 is at 4 commit, while ti2 is still 0
603 ti1->disable();
604 ti1->commit();
605 QVERIFY(committedSpy.wait());
606 QCOMPARE(committedSpy.last().at(0).value<quint32>(), 4);
607 QVERIFY(!m_serverTextInputV3->isEnabled());
608
609 // first commit to ti2
610 ti2->enable();
611 ti2->commit();
612 QVERIFY(committedSpy.wait());
613 QCOMPARE(committedSpy.last().at(0).value<quint32>(), 1);
614 QVERIFY(m_serverTextInputV3->isEnabled());
615
616 // send commit string
617 m_serverTextInputV3->commitString("Hello world");
618 m_serverTextInputV3->done();
619 QVERIFY(doneSpy2.wait());
620
621 // ti2 is at one commit
622 QCOMPARE(doneSpy2.last().at(0).value<quint32>(), 1);
623 ti2->disable();
624 ti2->commit();
625 QVERIFY(committedSpy.wait());
626 QCOMPARE(committedSpy.last().at(0).value<quint32>(), 2);
627 QVERIFY(!m_serverTextInputV3->isEnabled());
628
629 // now re-enable the ti1 and verify sending commits to t2 hasn't affected it's serial
630 // Enable ti1 : 5 commits now
631 ti1->enable();
632 ti1->commit();
633 QVERIFY(committedSpy.wait());
634 QCOMPARE(committedSpy.last().at(0).value<quint32>(), 5);
635 QVERIFY(m_serverTextInputV3->isEnabled());
636
637 // send done signal
638 m_serverTextInputV3->commitString("Hello");
639 m_serverTextInputV3->done();
640 QVERIFY(doneSpy1.wait());
641 QCOMPARE(doneSpy1.last().at(0).value<quint32>(), 5);
642
643 // cleanup
644 if (ti1) {
645 delete ti1;
646 ti1 = nullptr;
647 }
648 if (ti2) {
649 delete ti2;
650 ti2 = nullptr;
651 }
652}
653
654QTEST_GUILESS_MAIN(TestTextInputV3Interface)
655
656#include "test_textinputv3_interface.moc"
void surfaceCreated(KWin::SurfaceInterface *surface)
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 setHasKeyboard(bool has)
Definition seat.cpp:342
void focusedTextInputSurfaceChanged()
void setFocusedTextInputSurface(SurfaceInterface *surface)
Definition seat.cpp:1231
TextInputV3Interface * textInputV3() const
Definition seat.cpp:1274
Resource representing a wl_surface.
Definition surface.h:80
Represent the Global for the interface.
Represents a generic Resource for a text input object.A TextInputV3Interface gets created by the Text...
void stateCommitted(quint32 serial)
void deleteSurroundingText(quint32 beforeLength, quint32 afterLength)
void cursorRectangleChanged(const QRect &rect)
TextInputContentPurpose contentPurpose() const
qint32 surroundingTextSelectionAnchor() const
void commitString(const QString &text)
QString surroundingText() const
qint32 surroundingTextCursorPosition() const
TextInputContentHints contentHints() const
void sendPreEditString(const QString &text, quint32 cursorBegin, quint32 cursorEnd)
void zwp_text_input_v3_leave(struct ::wl_surface *surface) override
void zwp_text_input_v3_enter(struct ::wl_surface *surface) override
void surface_leave(wl_surface *surface)
void zwp_text_input_v3_delete_surrounding_text(uint32_t before_length, uint32_t after_length) override
void zwp_text_input_v3_commit_string(const QString &text) override
void preedit_string(const QString &text, quint32 cursor_begin, quint32 cursor_end)
void zwp_text_input_v3_done(uint32_t serial) override
void zwp_text_input_v3_preedit_string(const QString &text, int32_t cursor_begin, int32_t cursor_end) override
void delete_surrounding_text(quint32 before_length, quint32 after_length)
void surface_enter(wl_surface *surface)
void done(quint32 serial)
void commit_string(const QString &text)
Q_DECLARE_METATYPE(KWin::SwitchEvent::State)
KWayland::Client::Registry * registry
constexpr int version