KWin
Loading...
Searching...
No Matches
test_text_input_v2.cpp
Go to the documentation of this file.
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// client
10#include "KWayland/Client/compositor.h"
11#include "KWayland/Client/connection_thread.h"
12#include "KWayland/Client/event_queue.h"
13#include "KWayland/Client/keyboard.h"
14#include "KWayland/Client/registry.h"
15#include "KWayland/Client/seat.h"
16#include "KWayland/Client/surface.h"
17#include "KWayland/Client/textinput.h"
18// server
19#include "wayland/compositor.h"
20#include "wayland/display.h"
21#include "wayland/seat.h"
22#include "wayland/textinput.h"
24
25using namespace KWin;
26using namespace std::literals;
27
28class TextInputTest : public QObject
29{
30 Q_OBJECT
31private Q_SLOTS:
32 void init();
33 void cleanup();
34
35 void testEnterLeave_data();
36 void testEnterLeave();
37 void testFocusedBeforeCreateTextInput();
38 void testShowHidePanel();
39 void testCursorRectangle();
40 void testPreferredLanguage();
41 void testReset();
42 void testSurroundingText();
43 void testContentHints_data();
44 void testContentHints();
45 void testContentPurpose_data();
46 void testContentPurpose();
47 void testTextDirection_data();
48 void testTextDirection();
49 void testLanguage();
50 void testKeyEvent();
51 void testPreEdit();
52 void testCommit();
53
54private:
55 SurfaceInterface *waitForSurface();
56 KWayland::Client::TextInput *createTextInput();
57 KWin::Display *m_display = nullptr;
58 SeatInterface *m_seatInterface = nullptr;
59 CompositorInterface *m_compositorInterface = nullptr;
60 TextInputManagerV2Interface *m_textInputManagerV2Interface = nullptr;
61 KWayland::Client::ConnectionThread *m_connection = nullptr;
62 QThread *m_thread = nullptr;
63 KWayland::Client::EventQueue *m_queue = nullptr;
64 KWayland::Client::Seat *m_seat = nullptr;
65 KWayland::Client::Keyboard *m_keyboard = nullptr;
66 KWayland::Client::Compositor *m_compositor = nullptr;
67 KWayland::Client::TextInputManager *m_textInputManagerV2 = nullptr;
68};
69
70static const QString s_socketName = QStringLiteral("kwayland-test-text-input-0");
71
72void TextInputTest::init()
73{
74 delete m_display;
75 m_display = new KWin::Display(this);
76 m_display->addSocketName(s_socketName);
77 m_display->start();
78 QVERIFY(m_display->isRunning());
79 m_display->createShm();
80 m_seatInterface = new SeatInterface(m_display, m_display);
81 m_seatInterface->setHasKeyboard(true);
82 m_seatInterface->setHasTouch(true);
83 m_compositorInterface = new CompositorInterface(m_display, m_display);
84 m_textInputManagerV2Interface = new TextInputManagerV2Interface(m_display, m_display);
85
86 // setup connection
87 m_connection = new KWayland::Client::ConnectionThread;
88 QSignalSpy connectedSpy(m_connection, &KWayland::Client::ConnectionThread::connected);
89 m_connection->setSocketName(s_socketName);
90
91 m_thread = new QThread(this);
92 m_connection->moveToThread(m_thread);
93 m_thread->start();
94
95 m_connection->initConnection();
96 QVERIFY(connectedSpy.wait());
97
98 m_queue = new KWayland::Client::EventQueue(this);
99 m_queue->setup(m_connection);
100
101 KWayland::Client::Registry registry;
102 QSignalSpy interfacesAnnouncedSpy(&registry, &KWayland::Client::Registry::interfacesAnnounced);
103 registry.setEventQueue(m_queue);
104 registry.create(m_connection);
105 QVERIFY(registry.isValid());
106 registry.setup();
107 QVERIFY(interfacesAnnouncedSpy.wait());
108
109 m_seat = registry.createSeat(registry.interface(KWayland::Client::Registry::Interface::Seat).name, registry.interface(KWayland::Client::Registry::Interface::Seat).version, this);
110 QVERIFY(m_seat->isValid());
111 QSignalSpy hasKeyboardSpy(m_seat, &KWayland::Client::Seat::hasKeyboardChanged);
112 QVERIFY(hasKeyboardSpy.wait());
113 m_keyboard = m_seat->createKeyboard(this);
114 QVERIFY(m_keyboard->isValid());
115
116 m_compositor =
117 registry.createCompositor(registry.interface(KWayland::Client::Registry::Interface::Compositor).name, registry.interface(KWayland::Client::Registry::Interface::Compositor).version, this);
118 QVERIFY(m_compositor->isValid());
119
120 m_textInputManagerV2 = registry.createTextInputManager(registry.interface(KWayland::Client::Registry::Interface::TextInputManagerUnstableV2).name,
121 registry.interface(KWayland::Client::Registry::Interface::TextInputManagerUnstableV2).version,
122 this);
123 QVERIFY(m_textInputManagerV2->isValid());
124}
125
126void TextInputTest::cleanup()
127{
128#define CLEANUP(variable) \
129 if (variable) { \
130 delete variable; \
131 variable = nullptr; \
132 }
133 CLEANUP(m_textInputManagerV2)
134 CLEANUP(m_keyboard)
135 CLEANUP(m_seat)
136 CLEANUP(m_compositor)
137 CLEANUP(m_queue)
138 if (m_connection) {
139 m_connection->deleteLater();
140 m_connection = nullptr;
141 }
142 if (m_thread) {
143 m_thread->quit();
144 m_thread->wait();
145 delete m_thread;
146 m_thread = nullptr;
147 }
148
149 CLEANUP(m_display)
150#undef CLEANUP
151
152 // these are the children of the display
153 m_textInputManagerV2Interface = nullptr;
154 m_compositorInterface = nullptr;
155 m_seatInterface = nullptr;
156}
157
158SurfaceInterface *TextInputTest::waitForSurface()
159{
160 QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated);
161 if (!surfaceCreatedSpy.isValid()) {
162 return nullptr;
163 }
164 if (!surfaceCreatedSpy.wait(500)) {
165 return nullptr;
166 }
167 if (surfaceCreatedSpy.count() != 1) {
168 return nullptr;
169 }
170 return surfaceCreatedSpy.first().first().value<SurfaceInterface *>();
171}
172
173KWayland::Client::TextInput *TextInputTest::createTextInput()
174{
175 return m_textInputManagerV2->createTextInput(m_seat);
176}
177
178void TextInputTest::testEnterLeave_data()
179{
180 QTest::addColumn<bool>("updatesDirectly");
181 QTest::newRow("UnstableV2") << true;
182}
183
184void TextInputTest::testEnterLeave()
185{
186 // this test verifies that enter leave are sent correctly
187 std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
188
189 std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput());
190 auto serverSurface = waitForSurface();
191 QVERIFY(serverSurface);
192 QVERIFY(textInput != nullptr);
193 QSignalSpy enteredSpy(textInput.get(), &KWayland::Client::TextInput::entered);
194 QSignalSpy leftSpy(textInput.get(), &KWayland::Client::TextInput::left);
195 QSignalSpy textInputChangedSpy(m_seatInterface, &SeatInterface::focusedTextInputSurfaceChanged);
196
197 // now let's try to enter it
198 QVERIFY(!m_seatInterface->focusedTextInputSurface());
199 m_seatInterface->setFocusedKeyboardSurface(serverSurface);
200 QCOMPARE(m_seatInterface->focusedTextInputSurface(), serverSurface);
201 // text input not yet set for the surface
202 QFETCH(bool, updatesDirectly);
203 QCOMPARE(bool(m_seatInterface->textInputV2()), updatesDirectly);
204 QCOMPARE(textInputChangedSpy.isEmpty(), !updatesDirectly);
205 textInput->enable(surface.get());
206 // this should trigger on server side
207 if (!updatesDirectly) {
208 QVERIFY(textInputChangedSpy.wait());
209 }
210 QCOMPARE(textInputChangedSpy.count(), 1);
211 auto serverTextInput = m_seatInterface->textInputV2();
212 QVERIFY(serverTextInput);
213 QSignalSpy enabledChangedSpy(serverTextInput, &TextInputV2Interface::enabledChanged);
214 if (updatesDirectly) {
215 QVERIFY(enabledChangedSpy.wait());
216 enabledChangedSpy.clear();
217 }
218 QCOMPARE(serverTextInput->surface().data(), serverSurface);
219 QVERIFY(serverTextInput->isEnabled());
220
221 // and trigger an enter
222 if (enteredSpy.isEmpty()) {
223 QVERIFY(enteredSpy.wait());
224 }
225 QCOMPARE(enteredSpy.count(), 1);
226 QCOMPARE(textInput->enteredSurface(), surface.get());
227
228 // now trigger a leave
229 m_seatInterface->setFocusedKeyboardSurface(nullptr);
230 QCOMPARE(textInputChangedSpy.count(), 2);
231 QVERIFY(leftSpy.wait());
232 QVERIFY(!textInput->enteredSurface());
233 QVERIFY(!serverTextInput->isEnabled());
234
235 // if we enter again we should directly get the text input as it's still activated
236 m_seatInterface->setFocusedKeyboardSurface(serverSurface);
237 QCOMPARE(textInputChangedSpy.count(), 3);
238 QVERIFY(m_seatInterface->textInputV2());
239 QVERIFY(enteredSpy.wait());
240 QCOMPARE(enteredSpy.count(), 2);
241 QCOMPARE(textInput->enteredSurface(), surface.get());
242 QVERIFY(serverTextInput->isEnabled());
243
244 // let's deactivate on client side
245 textInput->disable(surface.get());
246 QVERIFY(enabledChangedSpy.wait());
247 QCOMPARE(enabledChangedSpy.count(), 3);
248 QVERIFY(!serverTextInput->isEnabled());
249 // does not trigger a leave
250 QCOMPARE(textInputChangedSpy.count(), 3);
251 // should still be the same text input
252 QCOMPARE(m_seatInterface->textInputV2(), serverTextInput);
253 // reset
254 textInput->enable(surface.get());
255 QVERIFY(enabledChangedSpy.wait());
256
257 // delete the client and wait for the server to catch up
258 QSignalSpy unboundSpy(serverSurface, &QObject::destroyed);
259 surface.reset();
260 QVERIFY(unboundSpy.wait());
261 QVERIFY(leftSpy.wait());
262 QVERIFY(!textInput->enteredSurface());
263}
264
265void TextInputTest::testFocusedBeforeCreateTextInput()
266{
267 // this test verifies that enter leave are sent correctly
268 std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
269 auto serverSurface = waitForSurface();
270 // now let's try to enter it
271 QSignalSpy textInputChangedSpy(m_seatInterface, &SeatInterface::focusedTextInputSurfaceChanged);
272 QVERIFY(!m_seatInterface->focusedTextInputSurface());
273 m_seatInterface->setFocusedKeyboardSurface(serverSurface);
274 QCOMPARE(m_seatInterface->focusedTextInputSurface(), serverSurface);
275 QCOMPARE(textInputChangedSpy.count(), 1);
276
277 // This is null because there is no text input object for this client.
278 QCOMPARE(m_seatInterface->textInputV2()->surface(), nullptr);
279
280 QVERIFY(serverSurface);
281 std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput());
282 QVERIFY(textInput != nullptr);
283 QSignalSpy enteredSpy(textInput.get(), &KWayland::Client::TextInput::entered);
284 QSignalSpy leftSpy(textInput.get(), &KWayland::Client::TextInput::left);
285
286 // and trigger an enter
287 if (enteredSpy.isEmpty()) {
288 QVERIFY(enteredSpy.wait());
289 }
290 QCOMPARE(enteredSpy.count(), 1);
291 QCOMPARE(textInput->enteredSurface(), surface.get());
292
293 // This is not null anymore because there is a text input object associated with it.
294 QCOMPARE(m_seatInterface->textInputV2()->surface(), serverSurface);
295
296 // now trigger a leave
297 m_seatInterface->setFocusedKeyboardSurface(nullptr);
298 QCOMPARE(textInputChangedSpy.count(), 2);
299 QVERIFY(leftSpy.wait());
300 QVERIFY(!textInput->enteredSurface());
301
302 QCOMPARE(m_seatInterface->textInputV2()->surface(), nullptr);
303 QCOMPARE(m_seatInterface->focusedTextInputSurface(), nullptr);
304}
305
306void TextInputTest::testShowHidePanel()
307{
308 // this test verifies that the requests for show/hide panel work
309 // and that status is properly sent to the client
310 std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
311 auto serverSurface = waitForSurface();
312 QVERIFY(serverSurface);
313
314 std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput());
315 QVERIFY(textInput != nullptr);
316 textInput->enable(surface.get());
317 m_connection->flush();
318 m_display->dispatchEvents();
319
320 m_seatInterface->setFocusedKeyboardSurface(serverSurface);
321 auto ti = m_seatInterface->textInputV2();
322 QVERIFY(ti);
323
324 QSignalSpy showPanelRequestedSpy(ti, &TextInputV2Interface::requestShowInputPanel);
325 QSignalSpy hidePanelRequestedSpy(ti, &TextInputV2Interface::requestHideInputPanel);
326 QSignalSpy inputPanelStateChangedSpy(textInput.get(), &KWayland::Client::TextInput::inputPanelStateChanged);
327
328 QCOMPARE(textInput->isInputPanelVisible(), false);
329 textInput->showInputPanel();
330 QVERIFY(showPanelRequestedSpy.wait());
331 ti->setInputPanelState(true, QRect(0, 0, 0, 0));
332 QVERIFY(inputPanelStateChangedSpy.wait());
333 QCOMPARE(textInput->isInputPanelVisible(), true);
334
335 textInput->hideInputPanel();
336 QVERIFY(hidePanelRequestedSpy.wait());
337 ti->setInputPanelState(false, QRect(0, 0, 0, 0));
338 QVERIFY(inputPanelStateChangedSpy.wait());
339 QCOMPARE(textInput->isInputPanelVisible(), false);
340}
341
342void TextInputTest::testCursorRectangle()
343{
344 // this test verifies that passing the cursor rectangle from client to server works
345 // and that setting visibility state from server to client works
346 std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
347 auto serverSurface = waitForSurface();
348 QVERIFY(serverSurface);
349
350 std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput());
351 QVERIFY(textInput != nullptr);
352 textInput->enable(surface.get());
353 m_connection->flush();
354 m_display->dispatchEvents();
355
356 m_seatInterface->setFocusedKeyboardSurface(serverSurface);
357 auto ti = m_seatInterface->textInputV2();
358 QVERIFY(ti);
359 QCOMPARE(ti->cursorRectangle(), QRect());
360 QSignalSpy cursorRectangleChangedSpy(ti, &TextInputV2Interface::cursorRectangleChanged);
361
362 textInput->setCursorRectangle(QRect(10, 20, 30, 40));
363 QVERIFY(cursorRectangleChangedSpy.wait());
364 QCOMPARE(ti->cursorRectangle(), QRect(10, 20, 30, 40));
365}
366
367void TextInputTest::testPreferredLanguage()
368{
369 // this test verifies that passing the preferred language from client to server works
370 std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
371 auto serverSurface = waitForSurface();
372 QVERIFY(serverSurface);
373
374 std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput());
375 QVERIFY(textInput != nullptr);
376 textInput->enable(surface.get());
377 m_connection->flush();
378 m_display->dispatchEvents();
379
380 m_seatInterface->setFocusedKeyboardSurface(serverSurface);
381 auto ti = m_seatInterface->textInputV2();
382 QVERIFY(ti);
383 QVERIFY(ti->preferredLanguage().isEmpty());
384
385 QSignalSpy preferredLanguageChangedSpy(ti, &TextInputV2Interface::preferredLanguageChanged);
386 textInput->setPreferredLanguage(QStringLiteral("foo"));
387 QVERIFY(preferredLanguageChangedSpy.wait());
388 QCOMPARE(ti->preferredLanguage(), QStringLiteral("foo").toUtf8());
389}
390
391void TextInputTest::testReset()
392{
393 // this test verifies that the reset request is properly passed from client to server
394 std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
395 auto serverSurface = waitForSurface();
396 QVERIFY(serverSurface);
397
398 std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput());
399 QVERIFY(textInput != nullptr);
400 textInput->enable(surface.get());
401 m_connection->flush();
402 m_display->dispatchEvents();
403
404 m_seatInterface->setFocusedKeyboardSurface(serverSurface);
405 auto ti = m_seatInterface->textInputV2();
406 QVERIFY(ti);
407
408 QSignalSpy stateUpdatedSpy(ti, &TextInputV2Interface::stateUpdated);
409
410 textInput->reset();
411 QVERIFY(stateUpdatedSpy.wait());
412}
413
414void TextInputTest::testSurroundingText()
415{
416 // this test verifies that surrounding text is properly passed around
417 std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
418 auto serverSurface = waitForSurface();
419 QVERIFY(serverSurface);
420
421 std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput());
422 QVERIFY(textInput != nullptr);
423 textInput->enable(surface.get());
424 m_connection->flush();
425 m_display->dispatchEvents();
426
427 m_seatInterface->setFocusedKeyboardSurface(serverSurface);
428 auto ti = m_seatInterface->textInputV2();
429 QVERIFY(ti);
430 QVERIFY(ti->surroundingText().isEmpty());
431 QCOMPARE(ti->surroundingTextCursorPosition(), 0);
432 QCOMPARE(ti->surroundingTextSelectionAnchor(), 0);
433
434 QSignalSpy surroundingTextChangedSpy(ti, &TextInputV2Interface::surroundingTextChanged);
435
436 textInput->setSurroundingText(QStringLiteral("100 €, 100 $"), 5, 6);
437 QVERIFY(surroundingTextChangedSpy.wait());
438 QCOMPARE(ti->surroundingText(), QStringLiteral("100 €, 100 $").toUtf8());
439 QCOMPARE(ti->surroundingTextCursorPosition(), QStringLiteral("100 €, 100 $").toUtf8().indexOf(','));
440 QCOMPARE(ti->surroundingTextSelectionAnchor(), QStringLiteral("100 €, 100 $").toUtf8().indexOf(' ', ti->surroundingTextCursorPosition()));
441}
442
443void TextInputTest::testContentHints_data()
444{
445 QTest::addColumn<KWayland::Client::TextInput::ContentHints>("clientHints");
446 QTest::addColumn<KWin::TextInputContentHints>("serverHints");
447
448 QTest::newRow("completion/v2") << KWayland::Client::TextInput::ContentHints(KWayland::Client::TextInput::ContentHint::AutoCompletion)
449 << KWin::TextInputContentHints(KWin::TextInputContentHint::AutoCompletion);
450 QTest::newRow("Correction/v2") << KWayland::Client::TextInput::ContentHints(KWayland::Client::TextInput::ContentHint::AutoCorrection)
451 << KWin::TextInputContentHints(KWin::TextInputContentHint::AutoCorrection);
452 QTest::newRow("Capitalization/v2") << KWayland::Client::TextInput::ContentHints(KWayland::Client::TextInput::ContentHint::AutoCapitalization)
453 << KWin::TextInputContentHints(KWin::TextInputContentHint::AutoCapitalization);
454 QTest::newRow("Lowercase/v2") << KWayland::Client::TextInput::ContentHints(KWayland::Client::TextInput::ContentHint::LowerCase)
455 << KWin::TextInputContentHints(KWin::TextInputContentHint::LowerCase);
456 QTest::newRow("Uppercase/v2") << KWayland::Client::TextInput::ContentHints(KWayland::Client::TextInput::ContentHint::UpperCase)
457 << KWin::TextInputContentHints(KWin::TextInputContentHint::UpperCase);
458 QTest::newRow("Titlecase/v2") << KWayland::Client::TextInput::ContentHints(KWayland::Client::TextInput::ContentHint::TitleCase)
459 << KWin::TextInputContentHints(KWin::TextInputContentHint::TitleCase);
460 QTest::newRow("HiddenText/v2") << KWayland::Client::TextInput::ContentHints(KWayland::Client::TextInput::ContentHint::HiddenText)
461 << KWin::TextInputContentHints(KWin::TextInputContentHint::HiddenText);
462 QTest::newRow("SensitiveData/v2") << KWayland::Client::TextInput::ContentHints(KWayland::Client::TextInput::ContentHint::SensitiveData)
463 << KWin::TextInputContentHints(KWin::TextInputContentHint::SensitiveData);
464 QTest::newRow("Latin/v2") << KWayland::Client::TextInput::ContentHints(KWayland::Client::TextInput::ContentHint::Latin)
465 << KWin::TextInputContentHints(KWin::TextInputContentHint::Latin);
466 QTest::newRow("Multiline/v2") << KWayland::Client::TextInput::ContentHints(KWayland::Client::TextInput::ContentHint::MultiLine)
467 << KWin::TextInputContentHints(KWin::TextInputContentHint::MultiLine);
468
469 QTest::newRow("autos/v2") << (KWayland::Client::TextInput::ContentHint::AutoCompletion | KWayland::Client::TextInput::ContentHint::AutoCorrection | KWayland::Client::TextInput::ContentHint::AutoCapitalization)
472
473 // all has combinations which don't make sense - what's both lowercase and uppercase?
474 QTest::newRow("all/v2") << (KWayland::Client::TextInput::ContentHint::AutoCompletion | KWayland::Client::TextInput::ContentHint::AutoCorrection | KWayland::Client::TextInput::ContentHint::AutoCapitalization
475 | KWayland::Client::TextInput::ContentHint::LowerCase | KWayland::Client::TextInput::ContentHint::UpperCase | KWayland::Client::TextInput::ContentHint::TitleCase
476 | KWayland::Client::TextInput::ContentHint::HiddenText | KWayland::Client::TextInput::ContentHint::SensitiveData | KWayland::Client::TextInput::ContentHint::Latin
477 | KWayland::Client::TextInput::ContentHint::MultiLine)
483}
484
485void TextInputTest::testContentHints()
486{
487 // this test verifies that content hints are properly passed from client to server
488 std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
489 auto serverSurface = waitForSurface();
490 QVERIFY(serverSurface);
491
492 std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput());
493 QVERIFY(textInput != nullptr);
494 textInput->enable(surface.get());
495 m_connection->flush();
496 m_display->dispatchEvents();
497
498 m_seatInterface->setFocusedKeyboardSurface(serverSurface);
499 auto ti = m_seatInterface->textInputV2();
500 QVERIFY(ti);
501 QCOMPARE(ti->contentHints(), KWin::TextInputContentHints());
502
503 QSignalSpy contentTypeChangedSpy(ti, &TextInputV2Interface::contentTypeChanged);
504 QFETCH(KWayland::Client::TextInput::ContentHints, clientHints);
505 textInput->setContentType(clientHints, KWayland::Client::TextInput::ContentPurpose::Normal);
506 QVERIFY(contentTypeChangedSpy.wait());
507 QTEST(ti->contentHints(), "serverHints");
508
509 // setting to same should not trigger an update
510 textInput->setContentType(clientHints, KWayland::Client::TextInput::ContentPurpose::Normal);
511 QVERIFY(!contentTypeChangedSpy.wait(100));
512
513 // unsetting should work
514 textInput->setContentType(KWayland::Client::TextInput::ContentHints(), KWayland::Client::TextInput::ContentPurpose::Normal);
515 QVERIFY(contentTypeChangedSpy.wait());
516 QCOMPARE(ti->contentHints(), KWin::TextInputContentHints());
517}
518
519void TextInputTest::testContentPurpose_data()
520{
521 QTest::addColumn<KWayland::Client::TextInput::ContentPurpose>("clientPurpose");
522 QTest::addColumn<KWin::TextInputContentPurpose>("serverPurpose");
523
524 QTest::newRow("Alpha/v2") << KWayland::Client::TextInput::ContentPurpose::Alpha << KWin::TextInputContentPurpose::Alpha;
525 QTest::newRow("Digits/v2") << KWayland::Client::TextInput::ContentPurpose::Digits << KWin::TextInputContentPurpose::Digits;
526 QTest::newRow("Number/v2") << KWayland::Client::TextInput::ContentPurpose::Number << KWin::TextInputContentPurpose::Number;
527 QTest::newRow("Phone/v2") << KWayland::Client::TextInput::ContentPurpose::Phone << KWin::TextInputContentPurpose::Phone;
528 QTest::newRow("Url/v2") << KWayland::Client::TextInput::ContentPurpose::Url << KWin::TextInputContentPurpose::Url;
529 QTest::newRow("Email/v2") << KWayland::Client::TextInput::ContentPurpose::Email << KWin::TextInputContentPurpose::Email;
530 QTest::newRow("Name/v2") << KWayland::Client::TextInput::ContentPurpose::Name << KWin::TextInputContentPurpose::Name;
531 QTest::newRow("Password/v2") << KWayland::Client::TextInput::ContentPurpose::Password << KWin::TextInputContentPurpose::Password;
532 QTest::newRow("Date/v2") << KWayland::Client::TextInput::ContentPurpose::Date << KWin::TextInputContentPurpose::Date;
533 QTest::newRow("Time/v2") << KWayland::Client::TextInput::ContentPurpose::Time << KWin::TextInputContentPurpose::Time;
534 QTest::newRow("Datetime/v2") << KWayland::Client::TextInput::ContentPurpose::DateTime << KWin::TextInputContentPurpose::DateTime;
535 QTest::newRow("Terminal/v2") << KWayland::Client::TextInput::ContentPurpose::Terminal << KWin::TextInputContentPurpose::Terminal;
536}
537
538void TextInputTest::testContentPurpose()
539{
540 // this test verifies that content purpose are properly passed from client to server
541 std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
542 auto serverSurface = waitForSurface();
543 QVERIFY(serverSurface);
544
545 std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput());
546 QVERIFY(textInput != nullptr);
547 textInput->enable(surface.get());
548 m_connection->flush();
549 m_display->dispatchEvents();
550
551 m_seatInterface->setFocusedKeyboardSurface(serverSurface);
552 auto ti = m_seatInterface->textInputV2();
553 QVERIFY(ti);
554 QCOMPARE(ti->contentPurpose(), KWin::TextInputContentPurpose::Normal);
555
556 QSignalSpy contentTypeChangedSpy(ti, &TextInputV2Interface::contentTypeChanged);
557 QFETCH(KWayland::Client::TextInput::ContentPurpose, clientPurpose);
558 textInput->setContentType(KWayland::Client::TextInput::ContentHints(), clientPurpose);
559 QVERIFY(contentTypeChangedSpy.wait());
560 QTEST(ti->contentPurpose(), "serverPurpose");
561
562 // setting to same should not trigger an update
563 textInput->setContentType(KWayland::Client::TextInput::ContentHints(), clientPurpose);
564 QVERIFY(!contentTypeChangedSpy.wait(100));
565
566 // unsetting should work
567 textInput->setContentType(KWayland::Client::TextInput::ContentHints(), KWayland::Client::TextInput::ContentPurpose::Normal);
568 QVERIFY(contentTypeChangedSpy.wait());
569 QCOMPARE(ti->contentPurpose(), KWin::TextInputContentPurpose::Normal);
570}
571
572void TextInputTest::testTextDirection_data()
573{
574 QTest::addColumn<Qt::LayoutDirection>("textDirection");
575
576 QTest::newRow("ltr/v0") << Qt::LeftToRight;
577 QTest::newRow("rtl/v0") << Qt::RightToLeft;
578
579 QTest::newRow("ltr/v2") << Qt::LeftToRight;
580 QTest::newRow("rtl/v2") << Qt::RightToLeft;
581}
582
583void TextInputTest::testTextDirection()
584{
585 // this test verifies that the text direction is sent from server to client
586 std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
587 auto serverSurface = waitForSurface();
588 QVERIFY(serverSurface);
589
590 std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput());
591 QVERIFY(textInput != nullptr);
592 // default should be auto
593 QCOMPARE(textInput->textDirection(), Qt::LayoutDirectionAuto);
594 textInput->enable(surface.get());
595 m_connection->flush();
596 m_display->dispatchEvents();
597
598 m_seatInterface->setFocusedKeyboardSurface(serverSurface);
599 auto ti = m_seatInterface->textInputV2();
600 QVERIFY(ti);
601
602 // let's send the new text direction
603 QSignalSpy textDirectionChangedSpy(textInput.get(), &KWayland::Client::TextInput::textDirectionChanged);
604 QFETCH(Qt::LayoutDirection, textDirection);
605 ti->setTextDirection(textDirection);
606 QVERIFY(textDirectionChangedSpy.wait());
607 QCOMPARE(textInput->textDirection(), textDirection);
608 // setting again should not change
609 ti->setTextDirection(textDirection);
610 QVERIFY(!textDirectionChangedSpy.wait(100));
611
612 // setting back to auto
613 ti->setTextDirection(Qt::LayoutDirectionAuto);
614 QVERIFY(textDirectionChangedSpy.wait());
615 QCOMPARE(textInput->textDirection(), Qt::LayoutDirectionAuto);
616}
617
618void TextInputTest::testLanguage()
619{
620 // this test verifies that language is sent from server to client
621 std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
622 auto serverSurface = waitForSurface();
623 QVERIFY(serverSurface);
624
625 std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput());
626 QVERIFY(textInput != nullptr);
627 // default should be empty
628 QVERIFY(textInput->language().isEmpty());
629 textInput->enable(surface.get());
630 m_connection->flush();
631 m_display->dispatchEvents();
632
633 m_seatInterface->setFocusedKeyboardSurface(serverSurface);
634 auto ti = m_seatInterface->textInputV2();
635 QVERIFY(ti);
636
637 // let's send the new language
638 QSignalSpy langugageChangedSpy(textInput.get(), &KWayland::Client::TextInput::languageChanged);
639 ti->setLanguage(QByteArrayLiteral("foo"));
640 QVERIFY(langugageChangedSpy.wait());
641 QCOMPARE(textInput->language(), QByteArrayLiteral("foo"));
642 // setting to same should not trigger
643 ti->setLanguage(QByteArrayLiteral("foo"));
644 QVERIFY(!langugageChangedSpy.wait(100));
645 // but to something else should trigger again
646 ti->setLanguage(QByteArrayLiteral("bar"));
647 QVERIFY(langugageChangedSpy.wait());
648 QCOMPARE(textInput->language(), QByteArrayLiteral("bar"));
649}
650
651void TextInputTest::testKeyEvent()
652{
653 qRegisterMetaType<Qt::KeyboardModifiers>();
654 qRegisterMetaType<KWayland::Client::TextInput::KeyState>();
655 // this test verifies that key events are properly sent to the client
656 std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
657 auto serverSurface = waitForSurface();
658 QVERIFY(serverSurface);
659
660 std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput());
661 QVERIFY(textInput != nullptr);
662 textInput->enable(surface.get());
663 m_connection->flush();
664 m_display->dispatchEvents();
665
666 m_seatInterface->setFocusedKeyboardSurface(serverSurface);
667 auto ti = m_seatInterface->textInputV2();
668 QVERIFY(ti);
669
670 // TODO: test modifiers
671 QSignalSpy keyEventSpy(textInput.get(), &KWayland::Client::TextInput::keyEvent);
672 m_seatInterface->setTimestamp(100ms);
673 ti->keysymPressed(2);
674 QVERIFY(keyEventSpy.wait());
675 QCOMPARE(keyEventSpy.count(), 1);
676 QCOMPARE(keyEventSpy.last().at(0).value<quint32>(), 2u);
677 QCOMPARE(keyEventSpy.last().at(1).value<KWayland::Client::TextInput::KeyState>(), KWayland::Client::TextInput::KeyState::Pressed);
678 QCOMPARE(keyEventSpy.last().at(2).value<Qt::KeyboardModifiers>(), Qt::KeyboardModifiers());
679 QCOMPARE(keyEventSpy.last().at(3).value<quint32>(), 100u);
680 m_seatInterface->setTimestamp(101ms);
681 ti->keysymReleased(2);
682 QVERIFY(keyEventSpy.wait());
683 QCOMPARE(keyEventSpy.count(), 2);
684 QCOMPARE(keyEventSpy.last().at(0).value<quint32>(), 2u);
685 QCOMPARE(keyEventSpy.last().at(1).value<KWayland::Client::TextInput::KeyState>(), KWayland::Client::TextInput::KeyState::Released);
686 QCOMPARE(keyEventSpy.last().at(2).value<Qt::KeyboardModifiers>(), Qt::KeyboardModifiers());
687 QCOMPARE(keyEventSpy.last().at(3).value<quint32>(), 101u);
688}
689
690void TextInputTest::testPreEdit()
691{
692 // this test verifies that pre-edit is correctly passed to the client
693 std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
694 auto serverSurface = waitForSurface();
695 QVERIFY(serverSurface);
696
697 std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput());
698 QVERIFY(textInput != nullptr);
699 // verify default values
700 QVERIFY(textInput->composingText().isEmpty());
701 QVERIFY(textInput->composingFallbackText().isEmpty());
702 QCOMPARE(textInput->composingTextCursorPosition(), 0);
703
704 textInput->enable(surface.get());
705 m_connection->flush();
706 m_display->dispatchEvents();
707
708 m_seatInterface->setFocusedKeyboardSurface(serverSurface);
709 auto ti = m_seatInterface->textInputV2();
710 QVERIFY(ti);
711
712 // now let's pass through some pre-edit events
713 QSignalSpy composingTextChangedSpy(textInput.get(), &KWayland::Client::TextInput::composingTextChanged);
714 ti->setPreEditCursor(1);
715 ti->preEdit(QByteArrayLiteral("foo"), QByteArrayLiteral("bar"));
716 QVERIFY(composingTextChangedSpy.wait());
717 QCOMPARE(composingTextChangedSpy.count(), 1);
718 QCOMPARE(textInput->composingText(), QByteArrayLiteral("foo"));
719 QCOMPARE(textInput->composingFallbackText(), QByteArrayLiteral("bar"));
720 QCOMPARE(textInput->composingTextCursorPosition(), 1);
721
722 // when no pre edit cursor is sent, it's at end of text
723 ti->preEdit(QByteArrayLiteral("foobar"), QByteArray());
724 QVERIFY(composingTextChangedSpy.wait());
725 QCOMPARE(composingTextChangedSpy.count(), 2);
726 QCOMPARE(textInput->composingText(), QByteArrayLiteral("foobar"));
727 QCOMPARE(textInput->composingFallbackText(), QByteArray());
728 QCOMPARE(textInput->composingTextCursorPosition(), 6);
729}
730
731void TextInputTest::testCommit()
732{
733 // this test verifies that the commit is handled correctly by the client
734 std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
735 auto serverSurface = waitForSurface();
736 QVERIFY(serverSurface);
737
738 std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput());
739 QVERIFY(textInput != nullptr);
740 // verify default values
741 QCOMPARE(textInput->commitText(), QByteArray());
742 QCOMPARE(textInput->cursorPosition(), 0);
743 QCOMPARE(textInput->anchorPosition(), 0);
744 QCOMPARE(textInput->deleteSurroundingText().beforeLength, 0u);
745 QCOMPARE(textInput->deleteSurroundingText().afterLength, 0u);
746
747 textInput->enable(surface.get());
748 m_connection->flush();
749 m_display->dispatchEvents();
750
751 m_seatInterface->setFocusedKeyboardSurface(serverSurface);
752 auto ti = m_seatInterface->textInputV2();
753 QVERIFY(ti);
754
755 // now let's commit
756 QSignalSpy committedSpy(textInput.get(), &KWayland::Client::TextInput::committed);
757 ti->setCursorPosition(3, 4);
758 ti->deleteSurroundingText(2, 1);
759 ti->commitString(QByteArrayLiteral("foo"));
760
761 QVERIFY(committedSpy.wait());
762 QCOMPARE(textInput->commitText(), QByteArrayLiteral("foo"));
763 QCOMPARE(textInput->cursorPosition(), 3);
764 QCOMPARE(textInput->anchorPosition(), 4);
765 QCOMPARE(textInput->deleteSurroundingText().beforeLength, 2u);
766 QCOMPARE(textInput->deleteSurroundingText().afterLength, 1u);
767}
768
769QTEST_GUILESS_MAIN(TextInputTest)
770#include "test_text_input_v2.moc"
void surfaceCreated(KWin::SurfaceInterface *surface)
Class holding the Wayland server display loop.
Definition display.h:34
void createShm()
Definition display.cpp:128
void dispatchEvents()
Definition display.cpp:116
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
SurfaceInterface * focusedTextInputSurface() const
Definition seat.cpp:1259
void setHasTouch(bool has)
Definition seat.cpp:372
void setHasKeyboard(bool has)
Definition seat.cpp:342
void setTimestamp(std::chrono::microseconds time)
Definition seat.cpp:488
void focusedTextInputSurfaceChanged()
TextInputV2Interface * textInputV2() const
Definition seat.cpp:1269
void setFocusedKeyboardSurface(SurfaceInterface *surface)
Definition seat.cpp:915
Resource representing a wl_surface.
Definition surface.h:80
Represent the Global for the interface.
void stateUpdated(uint32_t serial, UpdateReason reason)
void preferredLanguageChanged(const QString &language)
QPointer< SurfaceInterface > surface() const
void cursorRectangleChanged(const QRect &rect)
#define CLEANUP(variable)