File indexing completed on 2024-06-02 05:36:42

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