File indexing completed on 2024-11-10 04:56:17
0001 /* 0002 SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0005 */ 0006 // Qt 0007 #include <QSignalSpy> 0008 #include <QTest> 0009 // client 0010 #include "KWayland/Client/compositor.h" 0011 #include "KWayland/Client/connection_thread.h" 0012 #include "KWayland/Client/event_queue.h" 0013 #include "KWayland/Client/keyboard.h" 0014 #include "KWayland/Client/registry.h" 0015 #include "KWayland/Client/seat.h" 0016 #include "KWayland/Client/surface.h" 0017 #include "KWayland/Client/textinput.h" 0018 // server 0019 #include "wayland/compositor.h" 0020 #include "wayland/display.h" 0021 #include "wayland/seat.h" 0022 #include "wayland/textinput.h" 0023 #include "wayland/textinput_v2.h" 0024 0025 using namespace KWin; 0026 using namespace std::literals; 0027 0028 class TextInputTest : public QObject 0029 { 0030 Q_OBJECT 0031 private Q_SLOTS: 0032 void init(); 0033 void cleanup(); 0034 0035 void testEnterLeave_data(); 0036 void testEnterLeave(); 0037 void testFocusedBeforeCreateTextInput(); 0038 void testShowHidePanel(); 0039 void testCursorRectangle(); 0040 void testPreferredLanguage(); 0041 void testReset(); 0042 void testSurroundingText(); 0043 void testContentHints_data(); 0044 void testContentHints(); 0045 void testContentPurpose_data(); 0046 void testContentPurpose(); 0047 void testTextDirection_data(); 0048 void testTextDirection(); 0049 void testLanguage(); 0050 void testKeyEvent(); 0051 void testPreEdit(); 0052 void testCommit(); 0053 0054 private: 0055 SurfaceInterface *waitForSurface(); 0056 KWayland::Client::TextInput *createTextInput(); 0057 KWin::Display *m_display = nullptr; 0058 SeatInterface *m_seatInterface = nullptr; 0059 CompositorInterface *m_compositorInterface = nullptr; 0060 TextInputManagerV2Interface *m_textInputManagerV2Interface = nullptr; 0061 KWayland::Client::ConnectionThread *m_connection = nullptr; 0062 QThread *m_thread = nullptr; 0063 KWayland::Client::EventQueue *m_queue = nullptr; 0064 KWayland::Client::Seat *m_seat = nullptr; 0065 KWayland::Client::Keyboard *m_keyboard = nullptr; 0066 KWayland::Client::Compositor *m_compositor = nullptr; 0067 KWayland::Client::TextInputManager *m_textInputManagerV2 = nullptr; 0068 }; 0069 0070 static const QString s_socketName = QStringLiteral("kwayland-test-text-input-0"); 0071 0072 void TextInputTest::init() 0073 { 0074 delete m_display; 0075 m_display = new KWin::Display(this); 0076 m_display->addSocketName(s_socketName); 0077 m_display->start(); 0078 QVERIFY(m_display->isRunning()); 0079 m_display->createShm(); 0080 m_seatInterface = new SeatInterface(m_display, m_display); 0081 m_seatInterface->setHasKeyboard(true); 0082 m_seatInterface->setHasTouch(true); 0083 m_compositorInterface = new CompositorInterface(m_display, m_display); 0084 m_textInputManagerV2Interface = new TextInputManagerV2Interface(m_display, m_display); 0085 0086 // setup connection 0087 m_connection = new KWayland::Client::ConnectionThread; 0088 QSignalSpy connectedSpy(m_connection, &KWayland::Client::ConnectionThread::connected); 0089 m_connection->setSocketName(s_socketName); 0090 0091 m_thread = new QThread(this); 0092 m_connection->moveToThread(m_thread); 0093 m_thread->start(); 0094 0095 m_connection->initConnection(); 0096 QVERIFY(connectedSpy.wait()); 0097 0098 m_queue = new KWayland::Client::EventQueue(this); 0099 m_queue->setup(m_connection); 0100 0101 KWayland::Client::Registry registry; 0102 QSignalSpy interfacesAnnouncedSpy(®istry, &KWayland::Client::Registry::interfacesAnnounced); 0103 registry.setEventQueue(m_queue); 0104 registry.create(m_connection); 0105 QVERIFY(registry.isValid()); 0106 registry.setup(); 0107 QVERIFY(interfacesAnnouncedSpy.wait()); 0108 0109 m_seat = registry.createSeat(registry.interface(KWayland::Client::Registry::Interface::Seat).name, registry.interface(KWayland::Client::Registry::Interface::Seat).version, this); 0110 QVERIFY(m_seat->isValid()); 0111 QSignalSpy hasKeyboardSpy(m_seat, &KWayland::Client::Seat::hasKeyboardChanged); 0112 QVERIFY(hasKeyboardSpy.wait()); 0113 m_keyboard = m_seat->createKeyboard(this); 0114 QVERIFY(m_keyboard->isValid()); 0115 0116 m_compositor = 0117 registry.createCompositor(registry.interface(KWayland::Client::Registry::Interface::Compositor).name, registry.interface(KWayland::Client::Registry::Interface::Compositor).version, this); 0118 QVERIFY(m_compositor->isValid()); 0119 0120 m_textInputManagerV2 = registry.createTextInputManager(registry.interface(KWayland::Client::Registry::Interface::TextInputManagerUnstableV2).name, 0121 registry.interface(KWayland::Client::Registry::Interface::TextInputManagerUnstableV2).version, 0122 this); 0123 QVERIFY(m_textInputManagerV2->isValid()); 0124 } 0125 0126 void TextInputTest::cleanup() 0127 { 0128 #define CLEANUP(variable) \ 0129 if (variable) { \ 0130 delete variable; \ 0131 variable = nullptr; \ 0132 } 0133 CLEANUP(m_textInputManagerV2) 0134 CLEANUP(m_keyboard) 0135 CLEANUP(m_seat) 0136 CLEANUP(m_compositor) 0137 CLEANUP(m_queue) 0138 if (m_connection) { 0139 m_connection->deleteLater(); 0140 m_connection = nullptr; 0141 } 0142 if (m_thread) { 0143 m_thread->quit(); 0144 m_thread->wait(); 0145 delete m_thread; 0146 m_thread = nullptr; 0147 } 0148 0149 CLEANUP(m_display) 0150 #undef CLEANUP 0151 0152 // these are the children of the display 0153 m_textInputManagerV2Interface = nullptr; 0154 m_compositorInterface = nullptr; 0155 m_seatInterface = nullptr; 0156 } 0157 0158 SurfaceInterface *TextInputTest::waitForSurface() 0159 { 0160 QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); 0161 if (!surfaceCreatedSpy.isValid()) { 0162 return nullptr; 0163 } 0164 if (!surfaceCreatedSpy.wait(500)) { 0165 return nullptr; 0166 } 0167 if (surfaceCreatedSpy.count() != 1) { 0168 return nullptr; 0169 } 0170 return surfaceCreatedSpy.first().first().value<SurfaceInterface *>(); 0171 } 0172 0173 KWayland::Client::TextInput *TextInputTest::createTextInput() 0174 { 0175 return m_textInputManagerV2->createTextInput(m_seat); 0176 } 0177 0178 void TextInputTest::testEnterLeave_data() 0179 { 0180 QTest::addColumn<bool>("updatesDirectly"); 0181 QTest::newRow("UnstableV2") << true; 0182 } 0183 0184 void TextInputTest::testEnterLeave() 0185 { 0186 // this test verifies that enter leave are sent correctly 0187 std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface()); 0188 0189 std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput()); 0190 auto serverSurface = waitForSurface(); 0191 QVERIFY(serverSurface); 0192 QVERIFY(textInput != nullptr); 0193 QSignalSpy enteredSpy(textInput.get(), &KWayland::Client::TextInput::entered); 0194 QSignalSpy leftSpy(textInput.get(), &KWayland::Client::TextInput::left); 0195 QSignalSpy textInputChangedSpy(m_seatInterface, &SeatInterface::focusedTextInputSurfaceChanged); 0196 0197 // now let's try to enter it 0198 QVERIFY(!m_seatInterface->focusedTextInputSurface()); 0199 m_seatInterface->setFocusedKeyboardSurface(serverSurface); 0200 QCOMPARE(m_seatInterface->focusedTextInputSurface(), serverSurface); 0201 // text input not yet set for the surface 0202 QFETCH(bool, updatesDirectly); 0203 QCOMPARE(bool(m_seatInterface->textInputV2()), updatesDirectly); 0204 QCOMPARE(textInputChangedSpy.isEmpty(), !updatesDirectly); 0205 textInput->enable(surface.get()); 0206 // this should trigger on server side 0207 if (!updatesDirectly) { 0208 QVERIFY(textInputChangedSpy.wait()); 0209 } 0210 QCOMPARE(textInputChangedSpy.count(), 1); 0211 auto serverTextInput = m_seatInterface->textInputV2(); 0212 QVERIFY(serverTextInput); 0213 QSignalSpy enabledChangedSpy(serverTextInput, &TextInputV2Interface::enabledChanged); 0214 if (updatesDirectly) { 0215 QVERIFY(enabledChangedSpy.wait()); 0216 enabledChangedSpy.clear(); 0217 } 0218 QCOMPARE(serverTextInput->surface().data(), serverSurface); 0219 QVERIFY(serverTextInput->isEnabled()); 0220 0221 // and trigger an enter 0222 if (enteredSpy.isEmpty()) { 0223 QVERIFY(enteredSpy.wait()); 0224 } 0225 QCOMPARE(enteredSpy.count(), 1); 0226 QCOMPARE(textInput->enteredSurface(), surface.get()); 0227 0228 // now trigger a leave 0229 m_seatInterface->setFocusedKeyboardSurface(nullptr); 0230 QCOMPARE(textInputChangedSpy.count(), 2); 0231 QVERIFY(leftSpy.wait()); 0232 QVERIFY(!textInput->enteredSurface()); 0233 QVERIFY(!serverTextInput->isEnabled()); 0234 0235 // if we enter again we should directly get the text input as it's still activated 0236 m_seatInterface->setFocusedKeyboardSurface(serverSurface); 0237 QCOMPARE(textInputChangedSpy.count(), 3); 0238 QVERIFY(m_seatInterface->textInputV2()); 0239 QVERIFY(enteredSpy.wait()); 0240 QCOMPARE(enteredSpy.count(), 2); 0241 QCOMPARE(textInput->enteredSurface(), surface.get()); 0242 QVERIFY(serverTextInput->isEnabled()); 0243 0244 // let's deactivate on client side 0245 textInput->disable(surface.get()); 0246 QVERIFY(enabledChangedSpy.wait()); 0247 QCOMPARE(enabledChangedSpy.count(), 3); 0248 QVERIFY(!serverTextInput->isEnabled()); 0249 // does not trigger a leave 0250 QCOMPARE(textInputChangedSpy.count(), 3); 0251 // should still be the same text input 0252 QCOMPARE(m_seatInterface->textInputV2(), serverTextInput); 0253 // reset 0254 textInput->enable(surface.get()); 0255 QVERIFY(enabledChangedSpy.wait()); 0256 0257 // delete the client and wait for the server to catch up 0258 QSignalSpy unboundSpy(serverSurface, &QObject::destroyed); 0259 surface.reset(); 0260 QVERIFY(unboundSpy.wait()); 0261 QVERIFY(leftSpy.wait()); 0262 QVERIFY(!textInput->enteredSurface()); 0263 } 0264 0265 void TextInputTest::testFocusedBeforeCreateTextInput() 0266 { 0267 // this test verifies that enter leave are sent correctly 0268 std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface()); 0269 auto serverSurface = waitForSurface(); 0270 // now let's try to enter it 0271 QSignalSpy textInputChangedSpy(m_seatInterface, &SeatInterface::focusedTextInputSurfaceChanged); 0272 QVERIFY(!m_seatInterface->focusedTextInputSurface()); 0273 m_seatInterface->setFocusedKeyboardSurface(serverSurface); 0274 QCOMPARE(m_seatInterface->focusedTextInputSurface(), serverSurface); 0275 QCOMPARE(textInputChangedSpy.count(), 1); 0276 0277 // This is null because there is no text input object for this client. 0278 QCOMPARE(m_seatInterface->textInputV2()->surface(), nullptr); 0279 0280 QVERIFY(serverSurface); 0281 std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput()); 0282 QVERIFY(textInput != nullptr); 0283 QSignalSpy enteredSpy(textInput.get(), &KWayland::Client::TextInput::entered); 0284 QSignalSpy leftSpy(textInput.get(), &KWayland::Client::TextInput::left); 0285 0286 // and trigger an enter 0287 if (enteredSpy.isEmpty()) { 0288 QVERIFY(enteredSpy.wait()); 0289 } 0290 QCOMPARE(enteredSpy.count(), 1); 0291 QCOMPARE(textInput->enteredSurface(), surface.get()); 0292 0293 // This is not null anymore because there is a text input object associated with it. 0294 QCOMPARE(m_seatInterface->textInputV2()->surface(), serverSurface); 0295 0296 // now trigger a leave 0297 m_seatInterface->setFocusedKeyboardSurface(nullptr); 0298 QCOMPARE(textInputChangedSpy.count(), 2); 0299 QVERIFY(leftSpy.wait()); 0300 QVERIFY(!textInput->enteredSurface()); 0301 0302 QCOMPARE(m_seatInterface->textInputV2()->surface(), nullptr); 0303 QCOMPARE(m_seatInterface->focusedTextInputSurface(), nullptr); 0304 } 0305 0306 void TextInputTest::testShowHidePanel() 0307 { 0308 // this test verifies that the requests for show/hide panel work 0309 // and that status is properly sent to the client 0310 std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface()); 0311 auto serverSurface = waitForSurface(); 0312 QVERIFY(serverSurface); 0313 0314 std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput()); 0315 QVERIFY(textInput != nullptr); 0316 textInput->enable(surface.get()); 0317 m_connection->flush(); 0318 m_display->dispatchEvents(); 0319 0320 m_seatInterface->setFocusedKeyboardSurface(serverSurface); 0321 auto ti = m_seatInterface->textInputV2(); 0322 QVERIFY(ti); 0323 0324 QSignalSpy showPanelRequestedSpy(ti, &TextInputV2Interface::requestShowInputPanel); 0325 QSignalSpy hidePanelRequestedSpy(ti, &TextInputV2Interface::requestHideInputPanel); 0326 QSignalSpy inputPanelStateChangedSpy(textInput.get(), &KWayland::Client::TextInput::inputPanelStateChanged); 0327 0328 QCOMPARE(textInput->isInputPanelVisible(), false); 0329 textInput->showInputPanel(); 0330 QVERIFY(showPanelRequestedSpy.wait()); 0331 ti->setInputPanelState(true, QRect(0, 0, 0, 0)); 0332 QVERIFY(inputPanelStateChangedSpy.wait()); 0333 QCOMPARE(textInput->isInputPanelVisible(), true); 0334 0335 textInput->hideInputPanel(); 0336 QVERIFY(hidePanelRequestedSpy.wait()); 0337 ti->setInputPanelState(false, QRect(0, 0, 0, 0)); 0338 QVERIFY(inputPanelStateChangedSpy.wait()); 0339 QCOMPARE(textInput->isInputPanelVisible(), false); 0340 } 0341 0342 void TextInputTest::testCursorRectangle() 0343 { 0344 // this test verifies that passing the cursor rectangle from client to server works 0345 // and that setting visibility state from server to client works 0346 std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface()); 0347 auto serverSurface = waitForSurface(); 0348 QVERIFY(serverSurface); 0349 0350 std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput()); 0351 QVERIFY(textInput != nullptr); 0352 textInput->enable(surface.get()); 0353 m_connection->flush(); 0354 m_display->dispatchEvents(); 0355 0356 m_seatInterface->setFocusedKeyboardSurface(serverSurface); 0357 auto ti = m_seatInterface->textInputV2(); 0358 QVERIFY(ti); 0359 QCOMPARE(ti->cursorRectangle(), QRect()); 0360 QSignalSpy cursorRectangleChangedSpy(ti, &TextInputV2Interface::cursorRectangleChanged); 0361 0362 textInput->setCursorRectangle(QRect(10, 20, 30, 40)); 0363 QVERIFY(cursorRectangleChangedSpy.wait()); 0364 QCOMPARE(ti->cursorRectangle(), QRect(10, 20, 30, 40)); 0365 } 0366 0367 void TextInputTest::testPreferredLanguage() 0368 { 0369 // this test verifies that passing the preferred language from client to server works 0370 std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface()); 0371 auto serverSurface = waitForSurface(); 0372 QVERIFY(serverSurface); 0373 0374 std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput()); 0375 QVERIFY(textInput != nullptr); 0376 textInput->enable(surface.get()); 0377 m_connection->flush(); 0378 m_display->dispatchEvents(); 0379 0380 m_seatInterface->setFocusedKeyboardSurface(serverSurface); 0381 auto ti = m_seatInterface->textInputV2(); 0382 QVERIFY(ti); 0383 QVERIFY(ti->preferredLanguage().isEmpty()); 0384 0385 QSignalSpy preferredLanguageChangedSpy(ti, &TextInputV2Interface::preferredLanguageChanged); 0386 textInput->setPreferredLanguage(QStringLiteral("foo")); 0387 QVERIFY(preferredLanguageChangedSpy.wait()); 0388 QCOMPARE(ti->preferredLanguage(), QStringLiteral("foo").toUtf8()); 0389 } 0390 0391 void TextInputTest::testReset() 0392 { 0393 // this test verifies that the reset request is properly passed from client to server 0394 std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface()); 0395 auto serverSurface = waitForSurface(); 0396 QVERIFY(serverSurface); 0397 0398 std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput()); 0399 QVERIFY(textInput != nullptr); 0400 textInput->enable(surface.get()); 0401 m_connection->flush(); 0402 m_display->dispatchEvents(); 0403 0404 m_seatInterface->setFocusedKeyboardSurface(serverSurface); 0405 auto ti = m_seatInterface->textInputV2(); 0406 QVERIFY(ti); 0407 0408 QSignalSpy stateUpdatedSpy(ti, &TextInputV2Interface::stateUpdated); 0409 0410 textInput->reset(); 0411 QVERIFY(stateUpdatedSpy.wait()); 0412 } 0413 0414 void TextInputTest::testSurroundingText() 0415 { 0416 // this test verifies that surrounding text is properly passed around 0417 std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface()); 0418 auto serverSurface = waitForSurface(); 0419 QVERIFY(serverSurface); 0420 0421 std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput()); 0422 QVERIFY(textInput != nullptr); 0423 textInput->enable(surface.get()); 0424 m_connection->flush(); 0425 m_display->dispatchEvents(); 0426 0427 m_seatInterface->setFocusedKeyboardSurface(serverSurface); 0428 auto ti = m_seatInterface->textInputV2(); 0429 QVERIFY(ti); 0430 QVERIFY(ti->surroundingText().isEmpty()); 0431 QCOMPARE(ti->surroundingTextCursorPosition(), 0); 0432 QCOMPARE(ti->surroundingTextSelectionAnchor(), 0); 0433 0434 QSignalSpy surroundingTextChangedSpy(ti, &TextInputV2Interface::surroundingTextChanged); 0435 0436 textInput->setSurroundingText(QStringLiteral("100 €, 100 $"), 5, 6); 0437 QVERIFY(surroundingTextChangedSpy.wait()); 0438 QCOMPARE(ti->surroundingText(), QStringLiteral("100 €, 100 $").toUtf8()); 0439 QCOMPARE(ti->surroundingTextCursorPosition(), QStringLiteral("100 €, 100 $").toUtf8().indexOf(',')); 0440 QCOMPARE(ti->surroundingTextSelectionAnchor(), QStringLiteral("100 €, 100 $").toUtf8().indexOf(' ', ti->surroundingTextCursorPosition())); 0441 } 0442 0443 void TextInputTest::testContentHints_data() 0444 { 0445 QTest::addColumn<KWayland::Client::TextInput::ContentHints>("clientHints"); 0446 QTest::addColumn<KWin::TextInputContentHints>("serverHints"); 0447 0448 QTest::newRow("completion/v2") << KWayland::Client::TextInput::ContentHints(KWayland::Client::TextInput::ContentHint::AutoCompletion) 0449 << KWin::TextInputContentHints(KWin::TextInputContentHint::AutoCompletion); 0450 QTest::newRow("Correction/v2") << KWayland::Client::TextInput::ContentHints(KWayland::Client::TextInput::ContentHint::AutoCorrection) 0451 << KWin::TextInputContentHints(KWin::TextInputContentHint::AutoCorrection); 0452 QTest::newRow("Capitalization/v2") << KWayland::Client::TextInput::ContentHints(KWayland::Client::TextInput::ContentHint::AutoCapitalization) 0453 << KWin::TextInputContentHints(KWin::TextInputContentHint::AutoCapitalization); 0454 QTest::newRow("Lowercase/v2") << KWayland::Client::TextInput::ContentHints(KWayland::Client::TextInput::ContentHint::LowerCase) 0455 << KWin::TextInputContentHints(KWin::TextInputContentHint::LowerCase); 0456 QTest::newRow("Uppercase/v2") << KWayland::Client::TextInput::ContentHints(KWayland::Client::TextInput::ContentHint::UpperCase) 0457 << KWin::TextInputContentHints(KWin::TextInputContentHint::UpperCase); 0458 QTest::newRow("Titlecase/v2") << KWayland::Client::TextInput::ContentHints(KWayland::Client::TextInput::ContentHint::TitleCase) 0459 << KWin::TextInputContentHints(KWin::TextInputContentHint::TitleCase); 0460 QTest::newRow("HiddenText/v2") << KWayland::Client::TextInput::ContentHints(KWayland::Client::TextInput::ContentHint::HiddenText) 0461 << KWin::TextInputContentHints(KWin::TextInputContentHint::HiddenText); 0462 QTest::newRow("SensitiveData/v2") << KWayland::Client::TextInput::ContentHints(KWayland::Client::TextInput::ContentHint::SensitiveData) 0463 << KWin::TextInputContentHints(KWin::TextInputContentHint::SensitiveData); 0464 QTest::newRow("Latin/v2") << KWayland::Client::TextInput::ContentHints(KWayland::Client::TextInput::ContentHint::Latin) 0465 << KWin::TextInputContentHints(KWin::TextInputContentHint::Latin); 0466 QTest::newRow("Multiline/v2") << KWayland::Client::TextInput::ContentHints(KWayland::Client::TextInput::ContentHint::MultiLine) 0467 << KWin::TextInputContentHints(KWin::TextInputContentHint::MultiLine); 0468 0469 QTest::newRow("autos/v2") << (KWayland::Client::TextInput::ContentHint::AutoCompletion | KWayland::Client::TextInput::ContentHint::AutoCorrection | KWayland::Client::TextInput::ContentHint::AutoCapitalization) 0470 << (KWin::TextInputContentHint::AutoCompletion | KWin::TextInputContentHint::AutoCorrection 0471 | KWin::TextInputContentHint::AutoCapitalization); 0472 0473 // all has combinations which don't make sense - what's both lowercase and uppercase? 0474 QTest::newRow("all/v2") << (KWayland::Client::TextInput::ContentHint::AutoCompletion | KWayland::Client::TextInput::ContentHint::AutoCorrection | KWayland::Client::TextInput::ContentHint::AutoCapitalization 0475 | KWayland::Client::TextInput::ContentHint::LowerCase | KWayland::Client::TextInput::ContentHint::UpperCase | KWayland::Client::TextInput::ContentHint::TitleCase 0476 | KWayland::Client::TextInput::ContentHint::HiddenText | KWayland::Client::TextInput::ContentHint::SensitiveData | KWayland::Client::TextInput::ContentHint::Latin 0477 | KWayland::Client::TextInput::ContentHint::MultiLine) 0478 << (KWin::TextInputContentHint::AutoCompletion | KWin::TextInputContentHint::AutoCorrection 0479 | KWin::TextInputContentHint::AutoCapitalization | KWin::TextInputContentHint::LowerCase 0480 | KWin::TextInputContentHint::UpperCase | KWin::TextInputContentHint::TitleCase 0481 | KWin::TextInputContentHint::HiddenText | KWin::TextInputContentHint::SensitiveData 0482 | KWin::TextInputContentHint::Latin | KWin::TextInputContentHint::MultiLine); 0483 } 0484 0485 void TextInputTest::testContentHints() 0486 { 0487 // this test verifies that content hints are properly passed from client to server 0488 std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface()); 0489 auto serverSurface = waitForSurface(); 0490 QVERIFY(serverSurface); 0491 0492 std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput()); 0493 QVERIFY(textInput != nullptr); 0494 textInput->enable(surface.get()); 0495 m_connection->flush(); 0496 m_display->dispatchEvents(); 0497 0498 m_seatInterface->setFocusedKeyboardSurface(serverSurface); 0499 auto ti = m_seatInterface->textInputV2(); 0500 QVERIFY(ti); 0501 QCOMPARE(ti->contentHints(), KWin::TextInputContentHints()); 0502 0503 QSignalSpy contentTypeChangedSpy(ti, &TextInputV2Interface::contentTypeChanged); 0504 QFETCH(KWayland::Client::TextInput::ContentHints, clientHints); 0505 textInput->setContentType(clientHints, KWayland::Client::TextInput::ContentPurpose::Normal); 0506 QVERIFY(contentTypeChangedSpy.wait()); 0507 QTEST(ti->contentHints(), "serverHints"); 0508 0509 // setting to same should not trigger an update 0510 textInput->setContentType(clientHints, KWayland::Client::TextInput::ContentPurpose::Normal); 0511 QVERIFY(!contentTypeChangedSpy.wait(100)); 0512 0513 // unsetting should work 0514 textInput->setContentType(KWayland::Client::TextInput::ContentHints(), KWayland::Client::TextInput::ContentPurpose::Normal); 0515 QVERIFY(contentTypeChangedSpy.wait()); 0516 QCOMPARE(ti->contentHints(), KWin::TextInputContentHints()); 0517 } 0518 0519 void TextInputTest::testContentPurpose_data() 0520 { 0521 QTest::addColumn<KWayland::Client::TextInput::ContentPurpose>("clientPurpose"); 0522 QTest::addColumn<KWin::TextInputContentPurpose>("serverPurpose"); 0523 0524 QTest::newRow("Alpha/v2") << KWayland::Client::TextInput::ContentPurpose::Alpha << KWin::TextInputContentPurpose::Alpha; 0525 QTest::newRow("Digits/v2") << KWayland::Client::TextInput::ContentPurpose::Digits << KWin::TextInputContentPurpose::Digits; 0526 QTest::newRow("Number/v2") << KWayland::Client::TextInput::ContentPurpose::Number << KWin::TextInputContentPurpose::Number; 0527 QTest::newRow("Phone/v2") << KWayland::Client::TextInput::ContentPurpose::Phone << KWin::TextInputContentPurpose::Phone; 0528 QTest::newRow("Url/v2") << KWayland::Client::TextInput::ContentPurpose::Url << KWin::TextInputContentPurpose::Url; 0529 QTest::newRow("Email/v2") << KWayland::Client::TextInput::ContentPurpose::Email << KWin::TextInputContentPurpose::Email; 0530 QTest::newRow("Name/v2") << KWayland::Client::TextInput::ContentPurpose::Name << KWin::TextInputContentPurpose::Name; 0531 QTest::newRow("Password/v2") << KWayland::Client::TextInput::ContentPurpose::Password << KWin::TextInputContentPurpose::Password; 0532 QTest::newRow("Date/v2") << KWayland::Client::TextInput::ContentPurpose::Date << KWin::TextInputContentPurpose::Date; 0533 QTest::newRow("Time/v2") << KWayland::Client::TextInput::ContentPurpose::Time << KWin::TextInputContentPurpose::Time; 0534 QTest::newRow("Datetime/v2") << KWayland::Client::TextInput::ContentPurpose::DateTime << KWin::TextInputContentPurpose::DateTime; 0535 QTest::newRow("Terminal/v2") << KWayland::Client::TextInput::ContentPurpose::Terminal << KWin::TextInputContentPurpose::Terminal; 0536 } 0537 0538 void TextInputTest::testContentPurpose() 0539 { 0540 // this test verifies that content purpose are properly passed from client to server 0541 std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface()); 0542 auto serverSurface = waitForSurface(); 0543 QVERIFY(serverSurface); 0544 0545 std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput()); 0546 QVERIFY(textInput != nullptr); 0547 textInput->enable(surface.get()); 0548 m_connection->flush(); 0549 m_display->dispatchEvents(); 0550 0551 m_seatInterface->setFocusedKeyboardSurface(serverSurface); 0552 auto ti = m_seatInterface->textInputV2(); 0553 QVERIFY(ti); 0554 QCOMPARE(ti->contentPurpose(), KWin::TextInputContentPurpose::Normal); 0555 0556 QSignalSpy contentTypeChangedSpy(ti, &TextInputV2Interface::contentTypeChanged); 0557 QFETCH(KWayland::Client::TextInput::ContentPurpose, clientPurpose); 0558 textInput->setContentType(KWayland::Client::TextInput::ContentHints(), clientPurpose); 0559 QVERIFY(contentTypeChangedSpy.wait()); 0560 QTEST(ti->contentPurpose(), "serverPurpose"); 0561 0562 // setting to same should not trigger an update 0563 textInput->setContentType(KWayland::Client::TextInput::ContentHints(), clientPurpose); 0564 QVERIFY(!contentTypeChangedSpy.wait(100)); 0565 0566 // unsetting should work 0567 textInput->setContentType(KWayland::Client::TextInput::ContentHints(), KWayland::Client::TextInput::ContentPurpose::Normal); 0568 QVERIFY(contentTypeChangedSpy.wait()); 0569 QCOMPARE(ti->contentPurpose(), KWin::TextInputContentPurpose::Normal); 0570 } 0571 0572 void TextInputTest::testTextDirection_data() 0573 { 0574 QTest::addColumn<Qt::LayoutDirection>("textDirection"); 0575 0576 QTest::newRow("ltr/v0") << Qt::LeftToRight; 0577 QTest::newRow("rtl/v0") << Qt::RightToLeft; 0578 0579 QTest::newRow("ltr/v2") << Qt::LeftToRight; 0580 QTest::newRow("rtl/v2") << Qt::RightToLeft; 0581 } 0582 0583 void TextInputTest::testTextDirection() 0584 { 0585 // this test verifies that the text direction is sent from server to client 0586 std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface()); 0587 auto serverSurface = waitForSurface(); 0588 QVERIFY(serverSurface); 0589 0590 std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput()); 0591 QVERIFY(textInput != nullptr); 0592 // default should be auto 0593 QCOMPARE(textInput->textDirection(), Qt::LayoutDirectionAuto); 0594 textInput->enable(surface.get()); 0595 m_connection->flush(); 0596 m_display->dispatchEvents(); 0597 0598 m_seatInterface->setFocusedKeyboardSurface(serverSurface); 0599 auto ti = m_seatInterface->textInputV2(); 0600 QVERIFY(ti); 0601 0602 // let's send the new text direction 0603 QSignalSpy textDirectionChangedSpy(textInput.get(), &KWayland::Client::TextInput::textDirectionChanged); 0604 QFETCH(Qt::LayoutDirection, textDirection); 0605 ti->setTextDirection(textDirection); 0606 QVERIFY(textDirectionChangedSpy.wait()); 0607 QCOMPARE(textInput->textDirection(), textDirection); 0608 // setting again should not change 0609 ti->setTextDirection(textDirection); 0610 QVERIFY(!textDirectionChangedSpy.wait(100)); 0611 0612 // setting back to auto 0613 ti->setTextDirection(Qt::LayoutDirectionAuto); 0614 QVERIFY(textDirectionChangedSpy.wait()); 0615 QCOMPARE(textInput->textDirection(), Qt::LayoutDirectionAuto); 0616 } 0617 0618 void TextInputTest::testLanguage() 0619 { 0620 // this test verifies that language is sent from server to client 0621 std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface()); 0622 auto serverSurface = waitForSurface(); 0623 QVERIFY(serverSurface); 0624 0625 std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput()); 0626 QVERIFY(textInput != nullptr); 0627 // default should be empty 0628 QVERIFY(textInput->language().isEmpty()); 0629 textInput->enable(surface.get()); 0630 m_connection->flush(); 0631 m_display->dispatchEvents(); 0632 0633 m_seatInterface->setFocusedKeyboardSurface(serverSurface); 0634 auto ti = m_seatInterface->textInputV2(); 0635 QVERIFY(ti); 0636 0637 // let's send the new language 0638 QSignalSpy langugageChangedSpy(textInput.get(), &KWayland::Client::TextInput::languageChanged); 0639 ti->setLanguage(QByteArrayLiteral("foo")); 0640 QVERIFY(langugageChangedSpy.wait()); 0641 QCOMPARE(textInput->language(), QByteArrayLiteral("foo")); 0642 // setting to same should not trigger 0643 ti->setLanguage(QByteArrayLiteral("foo")); 0644 QVERIFY(!langugageChangedSpy.wait(100)); 0645 // but to something else should trigger again 0646 ti->setLanguage(QByteArrayLiteral("bar")); 0647 QVERIFY(langugageChangedSpy.wait()); 0648 QCOMPARE(textInput->language(), QByteArrayLiteral("bar")); 0649 } 0650 0651 void TextInputTest::testKeyEvent() 0652 { 0653 qRegisterMetaType<Qt::KeyboardModifiers>(); 0654 qRegisterMetaType<KWayland::Client::TextInput::KeyState>(); 0655 // this test verifies that key events are properly sent to the client 0656 std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface()); 0657 auto serverSurface = waitForSurface(); 0658 QVERIFY(serverSurface); 0659 0660 std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput()); 0661 QVERIFY(textInput != nullptr); 0662 textInput->enable(surface.get()); 0663 m_connection->flush(); 0664 m_display->dispatchEvents(); 0665 0666 m_seatInterface->setFocusedKeyboardSurface(serverSurface); 0667 auto ti = m_seatInterface->textInputV2(); 0668 QVERIFY(ti); 0669 0670 // TODO: test modifiers 0671 QSignalSpy keyEventSpy(textInput.get(), &KWayland::Client::TextInput::keyEvent); 0672 m_seatInterface->setTimestamp(100ms); 0673 ti->keysymPressed(2); 0674 QVERIFY(keyEventSpy.wait()); 0675 QCOMPARE(keyEventSpy.count(), 1); 0676 QCOMPARE(keyEventSpy.last().at(0).value<quint32>(), 2u); 0677 QCOMPARE(keyEventSpy.last().at(1).value<KWayland::Client::TextInput::KeyState>(), KWayland::Client::TextInput::KeyState::Pressed); 0678 QCOMPARE(keyEventSpy.last().at(2).value<Qt::KeyboardModifiers>(), Qt::KeyboardModifiers()); 0679 QCOMPARE(keyEventSpy.last().at(3).value<quint32>(), 100u); 0680 m_seatInterface->setTimestamp(101ms); 0681 ti->keysymReleased(2); 0682 QVERIFY(keyEventSpy.wait()); 0683 QCOMPARE(keyEventSpy.count(), 2); 0684 QCOMPARE(keyEventSpy.last().at(0).value<quint32>(), 2u); 0685 QCOMPARE(keyEventSpy.last().at(1).value<KWayland::Client::TextInput::KeyState>(), KWayland::Client::TextInput::KeyState::Released); 0686 QCOMPARE(keyEventSpy.last().at(2).value<Qt::KeyboardModifiers>(), Qt::KeyboardModifiers()); 0687 QCOMPARE(keyEventSpy.last().at(3).value<quint32>(), 101u); 0688 } 0689 0690 void TextInputTest::testPreEdit() 0691 { 0692 // this test verifies that pre-edit is correctly passed to the client 0693 std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface()); 0694 auto serverSurface = waitForSurface(); 0695 QVERIFY(serverSurface); 0696 0697 std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput()); 0698 QVERIFY(textInput != nullptr); 0699 // verify default values 0700 QVERIFY(textInput->composingText().isEmpty()); 0701 QVERIFY(textInput->composingFallbackText().isEmpty()); 0702 QCOMPARE(textInput->composingTextCursorPosition(), 0); 0703 0704 textInput->enable(surface.get()); 0705 m_connection->flush(); 0706 m_display->dispatchEvents(); 0707 0708 m_seatInterface->setFocusedKeyboardSurface(serverSurface); 0709 auto ti = m_seatInterface->textInputV2(); 0710 QVERIFY(ti); 0711 0712 // now let's pass through some pre-edit events 0713 QSignalSpy composingTextChangedSpy(textInput.get(), &KWayland::Client::TextInput::composingTextChanged); 0714 ti->setPreEditCursor(1); 0715 ti->preEdit(QByteArrayLiteral("foo"), QByteArrayLiteral("bar")); 0716 QVERIFY(composingTextChangedSpy.wait()); 0717 QCOMPARE(composingTextChangedSpy.count(), 1); 0718 QCOMPARE(textInput->composingText(), QByteArrayLiteral("foo")); 0719 QCOMPARE(textInput->composingFallbackText(), QByteArrayLiteral("bar")); 0720 QCOMPARE(textInput->composingTextCursorPosition(), 1); 0721 0722 // when no pre edit cursor is sent, it's at end of text 0723 ti->preEdit(QByteArrayLiteral("foobar"), QByteArray()); 0724 QVERIFY(composingTextChangedSpy.wait()); 0725 QCOMPARE(composingTextChangedSpy.count(), 2); 0726 QCOMPARE(textInput->composingText(), QByteArrayLiteral("foobar")); 0727 QCOMPARE(textInput->composingFallbackText(), QByteArray()); 0728 QCOMPARE(textInput->composingTextCursorPosition(), 6); 0729 } 0730 0731 void TextInputTest::testCommit() 0732 { 0733 // this test verifies that the commit is handled correctly by the client 0734 std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface()); 0735 auto serverSurface = waitForSurface(); 0736 QVERIFY(serverSurface); 0737 0738 std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput()); 0739 QVERIFY(textInput != nullptr); 0740 // verify default values 0741 QCOMPARE(textInput->commitText(), QByteArray()); 0742 QCOMPARE(textInput->cursorPosition(), 0); 0743 QCOMPARE(textInput->anchorPosition(), 0); 0744 QCOMPARE(textInput->deleteSurroundingText().beforeLength, 0u); 0745 QCOMPARE(textInput->deleteSurroundingText().afterLength, 0u); 0746 0747 textInput->enable(surface.get()); 0748 m_connection->flush(); 0749 m_display->dispatchEvents(); 0750 0751 m_seatInterface->setFocusedKeyboardSurface(serverSurface); 0752 auto ti = m_seatInterface->textInputV2(); 0753 QVERIFY(ti); 0754 0755 // now let's commit 0756 QSignalSpy committedSpy(textInput.get(), &KWayland::Client::TextInput::committed); 0757 ti->setCursorPosition(3, 4); 0758 ti->deleteSurroundingText(2, 1); 0759 ti->commitString(QByteArrayLiteral("foo")); 0760 0761 QVERIFY(committedSpy.wait()); 0762 QCOMPARE(textInput->commitText(), QByteArrayLiteral("foo")); 0763 QCOMPARE(textInput->cursorPosition(), 3); 0764 QCOMPARE(textInput->anchorPosition(), 4); 0765 QCOMPARE(textInput->deleteSurroundingText().beforeLength, 2u); 0766 QCOMPARE(textInput->deleteSurroundingText().afterLength, 1u); 0767 } 0768 0769 QTEST_GUILESS_MAIN(TextInputTest) 0770 #include "test_text_input_v2.moc"