File indexing completed on 2024-11-10 04:56:23

0001 /*
0002     SPDX-FileCopyrightText: 2020 Bhushan Shah <bshah@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #include <QSignalSpy>
0008 #include <QTest>
0009 #include <QThread>
0010 
0011 #include "wayland/compositor.h"
0012 #include "wayland/display.h"
0013 #include "wayland/seat.h"
0014 #include "wayland/surface.h"
0015 #include "wayland/textinput_v3.h"
0016 
0017 #include "KWayland/Client/compositor.h"
0018 #include "KWayland/Client/connection_thread.h"
0019 #include "KWayland/Client/event_queue.h"
0020 #include "KWayland/Client/registry.h"
0021 #include "KWayland/Client/seat.h"
0022 #include "KWayland/Client/surface.h"
0023 
0024 #include "qwayland-text-input-unstable-v3.h"
0025 
0026 using namespace KWin;
0027 
0028 Q_DECLARE_METATYPE(QtWayland::zwp_text_input_v3::content_purpose)
0029 Q_DECLARE_METATYPE(QtWayland::zwp_text_input_v3::content_hint)
0030 
0031 class TextInputV3 : public QObject, public QtWayland::zwp_text_input_v3
0032 {
0033     Q_OBJECT
0034 Q_SIGNALS:
0035     void surface_enter(wl_surface *surface);
0036     void surface_leave(wl_surface *surface);
0037     void commit_string(const QString &text);
0038     void delete_surrounding_text(quint32 before_length, quint32 after_length);
0039     void preedit_string(const QString &text, quint32 cursor_begin, quint32 cursor_end);
0040     void done(quint32 serial);
0041 
0042 public:
0043     ~TextInputV3() override
0044     {
0045         destroy();
0046     }
0047     void zwp_text_input_v3_enter(struct ::wl_surface *surface) override
0048     {
0049         Q_EMIT surface_enter(surface);
0050     }
0051     void zwp_text_input_v3_leave(struct ::wl_surface *surface) override
0052     {
0053         Q_EMIT surface_leave(surface);
0054     }
0055     void zwp_text_input_v3_commit_string(const QString &text) override
0056     {
0057         commitText = text;
0058     }
0059     void zwp_text_input_v3_delete_surrounding_text(uint32_t before_length, uint32_t after_length) override
0060     {
0061         before = before_length;
0062         after = after_length;
0063     }
0064     void zwp_text_input_v3_done(uint32_t serial) override
0065     {
0066         Q_EMIT commit_string(commitText);
0067         Q_EMIT preedit_string(preeditText, cursorBegin, cursorEnd);
0068         Q_EMIT delete_surrounding_text(before, after);
0069         Q_EMIT done(serial);
0070     }
0071     void zwp_text_input_v3_preedit_string(const QString &text, int32_t cursor_begin, int32_t cursor_end) override
0072     {
0073         preeditText = text;
0074         cursorBegin = cursor_begin;
0075         cursorEnd = cursor_end;
0076     }
0077 
0078 private:
0079     QString preeditText;
0080     QString commitText;
0081     uint32_t cursorBegin, cursorEnd;
0082     uint32_t before, after;
0083 };
0084 
0085 class TextInputManagerV3 : public QtWayland::zwp_text_input_manager_v3
0086 {
0087 public:
0088     ~TextInputManagerV3() override
0089     {
0090         destroy();
0091     }
0092 };
0093 
0094 class TestTextInputV3Interface : public QObject
0095 {
0096     Q_OBJECT
0097 
0098 public:
0099     ~TestTextInputV3Interface() override;
0100 
0101 private Q_SLOTS:
0102     void initTestCase();
0103     void testEnableDisable();
0104     void testEvents();
0105     void testContentPurpose_data();
0106     void testContentPurpose();
0107     void testContentHints_data();
0108     void testContentHints();
0109     void testMultipleTextinputs();
0110 
0111 private:
0112     KWayland::Client::ConnectionThread *m_connection;
0113     KWayland::Client::EventQueue *m_queue;
0114     KWayland::Client::Compositor *m_clientCompositor;
0115     KWayland::Client::Seat *m_clientSeat = nullptr;
0116 
0117     SeatInterface *m_seat;
0118     QThread *m_thread;
0119     KWin::Display m_display;
0120     TextInputV3 *m_clientTextInputV3;
0121     CompositorInterface *m_serverCompositor;
0122     TextInputV3Interface *m_serverTextInputV3;
0123     TextInputManagerV3 *m_clientTextInputManagerV3;
0124 
0125     quint32 m_totalCommits = 0;
0126 };
0127 
0128 static const QString s_socketName = QStringLiteral("kwin-wayland-server-text-input-v3-test-0");
0129 
0130 void TestTextInputV3Interface::initTestCase()
0131 {
0132     m_display.addSocketName(s_socketName);
0133     m_display.start();
0134     QVERIFY(m_display.isRunning());
0135 
0136     m_seat = new SeatInterface(&m_display, this);
0137     m_seat->setHasKeyboard(true);
0138 
0139     m_serverCompositor = new CompositorInterface(&m_display, this);
0140     new TextInputManagerV3Interface(&m_display);
0141 
0142     m_connection = new KWayland::Client::ConnectionThread;
0143     QSignalSpy connectedSpy(m_connection, &KWayland::Client::ConnectionThread::connected);
0144     m_connection->setSocketName(s_socketName);
0145 
0146     m_thread = new QThread(this);
0147     m_connection->moveToThread(m_thread);
0148     m_thread->start();
0149 
0150     m_connection->initConnection();
0151     QVERIFY(connectedSpy.wait());
0152     QVERIFY(!m_connection->connections().isEmpty());
0153 
0154     m_queue = new KWayland::Client::EventQueue(this);
0155     QVERIFY(!m_queue->isValid());
0156     m_queue->setup(m_connection);
0157     QVERIFY(m_queue->isValid());
0158 
0159     auto registry = new KWayland::Client::Registry(this);
0160     connect(registry, &KWayland::Client::Registry::interfaceAnnounced, this, [this, registry](const QByteArray &interface, quint32 id, quint32 version) {
0161         if (interface == QByteArrayLiteral("zwp_text_input_manager_v3")) {
0162             m_clientTextInputManagerV3 = new TextInputManagerV3();
0163             m_clientTextInputManagerV3->init(*registry, id, version);
0164         }
0165     });
0166 
0167     connect(registry, &KWayland::Client::Registry::seatAnnounced, this, [this, registry](quint32 name, quint32 version) {
0168         m_clientSeat = registry->createSeat(name, version);
0169     });
0170 
0171     QSignalSpy allAnnouncedSpy(registry, &KWayland::Client::Registry::interfaceAnnounced);
0172     QSignalSpy compositorSpy(registry, &KWayland::Client::Registry::compositorAnnounced);
0173     QSignalSpy shmSpy(registry, &KWayland::Client::Registry::shmAnnounced);
0174     registry->setEventQueue(m_queue);
0175     registry->create(m_connection->display());
0176     QVERIFY(registry->isValid());
0177     registry->setup();
0178     QVERIFY(allAnnouncedSpy.wait());
0179 
0180     m_clientCompositor = registry->createCompositor(compositorSpy.first().first().value<quint32>(), compositorSpy.first().last().value<quint32>(), this);
0181     QVERIFY(m_clientCompositor->isValid());
0182     // create a text input v3
0183     m_clientTextInputV3 = new TextInputV3();
0184     m_clientTextInputV3->init(m_clientTextInputManagerV3->get_text_input(*m_clientSeat));
0185     QVERIFY(m_clientTextInputV3);
0186 }
0187 
0188 TestTextInputV3Interface::~TestTextInputV3Interface()
0189 {
0190     if (m_clientTextInputV3) {
0191         delete m_clientTextInputV3;
0192         m_clientTextInputV3 = nullptr;
0193     }
0194     if (m_clientTextInputManagerV3) {
0195         delete m_clientTextInputManagerV3;
0196         m_clientTextInputManagerV3 = nullptr;
0197     }
0198     if (m_queue) {
0199         delete m_queue;
0200         m_queue = nullptr;
0201     }
0202     if (m_thread) {
0203         m_thread->quit();
0204         m_thread->wait();
0205         delete m_thread;
0206         m_thread = nullptr;
0207     }
0208     m_connection->deleteLater();
0209     m_connection = nullptr;
0210 }
0211 
0212 // Ensures that enable disable events don't fire without commit
0213 void TestTextInputV3Interface::testEnableDisable()
0214 {
0215     // create a surface
0216     QSignalSpy serverSurfaceCreatedSpy(m_serverCompositor, &CompositorInterface::surfaceCreated);
0217     std::unique_ptr<KWayland::Client::Surface> clientSurface(m_clientCompositor->createSurface(this));
0218     QVERIFY(serverSurfaceCreatedSpy.wait());
0219     SurfaceInterface *serverSurface = serverSurfaceCreatedSpy.first().first().value<SurfaceInterface *>();
0220     QVERIFY(serverSurface);
0221 
0222     m_serverTextInputV3 = m_seat->textInputV3();
0223     QVERIFY(m_serverTextInputV3);
0224 
0225     QSignalSpy focusedSurfaceChangedSpy(m_seat, &SeatInterface::focusedTextInputSurfaceChanged);
0226     QSignalSpy textInputEnabledSpy(m_serverTextInputV3, &TextInputV3Interface::enabledChanged);
0227     QSignalSpy cursorRectangleChangedSpy(m_serverTextInputV3, &TextInputV3Interface::cursorRectangleChanged);
0228     QSignalSpy enableRequestedSpy(m_serverTextInputV3, &TextInputV3Interface::enableRequested);
0229 
0230     QSignalSpy surfaceEnterSpy(m_clientTextInputV3, &TextInputV3::surface_enter);
0231     QSignalSpy surfaceLeaveSpy(m_clientTextInputV3, &TextInputV3::surface_leave);
0232 
0233     // Enter the textinput
0234 
0235     QCOMPARE(focusedSurfaceChangedSpy.count(), 0);
0236 
0237     // Make sure that entering surface does not trigger the text input
0238     m_seat->setFocusedTextInputSurface(serverSurface);
0239     QVERIFY(surfaceEnterSpy.wait());
0240     QCOMPARE(surfaceEnterSpy.count(), 1);
0241     QCOMPARE(focusedSurfaceChangedSpy.count(), 1);
0242     QCOMPARE(textInputEnabledSpy.count(), 0);
0243 
0244     // Now enable the textInput, we should not get event just yet
0245     m_clientTextInputV3->enable();
0246     m_clientTextInputV3->set_cursor_rectangle(0, 0, 20, 20);
0247     m_clientTextInputV3->set_surrounding_text("KDE Plasma Desktop", 0, 3);
0248     QCOMPARE(textInputEnabledSpy.count(), 0);
0249     QCOMPARE(cursorRectangleChangedSpy.count(), 0);
0250 
0251     // after we do commit we should get event
0252     m_clientTextInputV3->commit();
0253     QVERIFY(textInputEnabledSpy.wait());
0254     m_totalCommits++;
0255 
0256     QCOMPARE(enableRequestedSpy.count(), 0);
0257     QCOMPARE(textInputEnabledSpy.count(), 1);
0258     QCOMPARE(cursorRectangleChangedSpy.count(), 1);
0259     QCOMPARE(m_serverTextInputV3->cursorRectangle(), QRect(0, 0, 20, 20));
0260     QCOMPARE(m_serverTextInputV3->surroundingText(), QString("KDE Plasma Desktop"));
0261     QCOMPARE(m_serverTextInputV3->surroundingTextCursorPosition(), 0);
0262     QCOMPARE(m_serverTextInputV3->surroundingTextSelectionAnchor(), 3);
0263 
0264     // Do another enable when it's already enabled.
0265     m_clientTextInputV3->enable();
0266     m_clientTextInputV3->set_cursor_rectangle(0, 0, 20, 20);
0267     m_clientTextInputV3->set_surrounding_text("KDE Plasma Desktop", 0, 3);
0268     m_clientTextInputV3->commit();
0269     QVERIFY(enableRequestedSpy.wait());
0270     QCOMPARE(textInputEnabledSpy.count(), 1);
0271     QCOMPARE(cursorRectangleChangedSpy.count(), 1);
0272     QCOMPARE(m_serverTextInputV3->cursorRectangle(), QRect(0, 0, 20, 20));
0273     QCOMPARE(m_serverTextInputV3->surroundingText(), QString("KDE Plasma Desktop"));
0274     QCOMPARE(m_serverTextInputV3->surroundingTextCursorPosition(), 0);
0275     QCOMPARE(m_serverTextInputV3->surroundingTextSelectionAnchor(), 3);
0276     m_totalCommits++;
0277 
0278     // disabling we should not get the event
0279     m_clientTextInputV3->disable();
0280     QCOMPARE(textInputEnabledSpy.count(), 1);
0281 
0282     // after we do commit we should get event
0283     m_clientTextInputV3->commit();
0284     QVERIFY(textInputEnabledSpy.wait());
0285     QCOMPARE(textInputEnabledSpy.count(), 2);
0286     m_totalCommits++;
0287 
0288     // Lets try leaving the surface and make sure event propogage
0289     m_seat->setFocusedTextInputSurface(nullptr);
0290     QVERIFY(surfaceLeaveSpy.wait());
0291     QCOMPARE(surfaceLeaveSpy.count(), 1);
0292 }
0293 
0294 void TestTextInputV3Interface::testEvents()
0295 {
0296     // create a surface
0297     QSignalSpy serverSurfaceCreatedSpy(m_serverCompositor, &CompositorInterface::surfaceCreated);
0298     std::unique_ptr<KWayland::Client::Surface> clientSurface(m_clientCompositor->createSurface(this));
0299     QVERIFY(serverSurfaceCreatedSpy.wait());
0300     SurfaceInterface *serverSurface = serverSurfaceCreatedSpy.first().first().value<SurfaceInterface *>();
0301     QVERIFY(serverSurface);
0302 
0303     m_serverTextInputV3 = m_seat->textInputV3();
0304     QVERIFY(m_serverTextInputV3);
0305 
0306     QSignalSpy focusedSurfaceChangedSpy(m_seat, &SeatInterface::focusedTextInputSurfaceChanged);
0307     QSignalSpy textInputEnabledSpy(m_serverTextInputV3, &TextInputV3Interface::enabledChanged);
0308     QSignalSpy doneSpy(m_clientTextInputV3, &TextInputV3::done);
0309 
0310     // Enter the textinput
0311     QCOMPARE(focusedSurfaceChangedSpy.count(), 0);
0312 
0313     // Make sure that entering surface does not trigger the text input
0314     m_seat->setFocusedTextInputSurface(serverSurface);
0315     // FIXME: somehow this triggers BEFORE setFocusedTextInputSurface returns :(
0316     //  QVERIFY(focusedSurfaceChangedSpy.wait());
0317     QCOMPARE(focusedSurfaceChangedSpy.count(), 1);
0318 
0319     // Now enable the textInput
0320     m_clientTextInputV3->enable();
0321     m_clientTextInputV3->commit();
0322     m_totalCommits++;
0323     QVERIFY(textInputEnabledSpy.wait());
0324     QVERIFY(doneSpy.wait());
0325     QCOMPARE(doneSpy.count(), 1);
0326 
0327     QSignalSpy preEditSpy(m_clientTextInputV3, &TextInputV3::preedit_string);
0328     QSignalSpy commitStringSpy(m_clientTextInputV3, &TextInputV3::commit_string);
0329     QSignalSpy deleteSurroundingSpy(m_clientTextInputV3, &TextInputV3::delete_surrounding_text);
0330 
0331     m_serverTextInputV3->sendPreEditString("Hello KDE community!", 1, 2);
0332     m_serverTextInputV3->deleteSurroundingText(6, 10);
0333     m_serverTextInputV3->commitString("Plasma");
0334     m_serverTextInputV3->done();
0335 
0336     QVERIFY(doneSpy.wait());
0337     QCOMPARE(doneSpy.count(), 2);
0338     QCOMPARE(preEditSpy.count(), 1);
0339     QCOMPARE(commitStringSpy.count(), 1);
0340     QCOMPARE(deleteSurroundingSpy.count(), 1);
0341 
0342     QCOMPARE(preEditSpy.last().at(0).value<QString>(), "Hello KDE community!");
0343     QCOMPARE(preEditSpy.last().at(1).value<quint32>(), 1);
0344     QCOMPARE(preEditSpy.last().at(2).value<quint32>(), 2);
0345     QCOMPARE(commitStringSpy.last().at(0).value<QString>(), "Plasma");
0346     QCOMPARE(deleteSurroundingSpy.last().at(0).value<quint32>(), 6);
0347     QCOMPARE(deleteSurroundingSpy.last().at(1).value<quint32>(), 10);
0348 
0349     // zwp_text_input_v3.done event have serial of total commits
0350     QCOMPARE(doneSpy.last().at(0).value<quint32>(), m_totalCommits);
0351 
0352     // Now disable the textInput
0353     m_clientTextInputV3->disable();
0354     m_clientTextInputV3->commit();
0355     m_totalCommits++;
0356     QVERIFY(textInputEnabledSpy.wait());
0357 }
0358 
0359 void TestTextInputV3Interface::testContentPurpose_data()
0360 {
0361     QTest::addColumn<QtWayland::zwp_text_input_v3::content_purpose>("clientPurpose");
0362     QTest::addColumn<KWin::TextInputContentPurpose>("serverPurpose");
0363 
0364     QTest::newRow("Alpha") << QtWayland::zwp_text_input_v3::content_purpose_alpha << TextInputContentPurpose::Alpha;
0365     QTest::newRow("Digits") << QtWayland::zwp_text_input_v3::content_purpose_digits << TextInputContentPurpose::Digits;
0366     QTest::newRow("Number") << QtWayland::zwp_text_input_v3::content_purpose_number << TextInputContentPurpose::Number;
0367     QTest::newRow("Phone") << QtWayland::zwp_text_input_v3::content_purpose_phone << TextInputContentPurpose::Phone;
0368     QTest::newRow("Url") << QtWayland::zwp_text_input_v3::content_purpose_url << TextInputContentPurpose::Url;
0369     QTest::newRow("Email") << QtWayland::zwp_text_input_v3::content_purpose_email << TextInputContentPurpose::Email;
0370     QTest::newRow("Name") << QtWayland::zwp_text_input_v3::content_purpose_name << TextInputContentPurpose::Name;
0371     QTest::newRow("Password") << QtWayland::zwp_text_input_v3::content_purpose_password << TextInputContentPurpose::Password;
0372     QTest::newRow("Pin") << QtWayland::zwp_text_input_v3::content_purpose_pin << TextInputContentPurpose::Pin;
0373     QTest::newRow("Date") << QtWayland::zwp_text_input_v3::content_purpose_date << TextInputContentPurpose::Date;
0374     QTest::newRow("Time") << QtWayland::zwp_text_input_v3::content_purpose_time << TextInputContentPurpose::Time;
0375     QTest::newRow("DateTime") << QtWayland::zwp_text_input_v3::content_purpose_datetime << TextInputContentPurpose::DateTime;
0376     QTest::newRow("Terminal") << QtWayland::zwp_text_input_v3::content_purpose_terminal << TextInputContentPurpose::Terminal;
0377 }
0378 
0379 void TestTextInputV3Interface::testContentPurpose()
0380 {
0381     // create a surface
0382     QSignalSpy serverSurfaceCreatedSpy(m_serverCompositor, &CompositorInterface::surfaceCreated);
0383     std::unique_ptr<KWayland::Client::Surface> clientSurface(m_clientCompositor->createSurface(this));
0384     QVERIFY(serverSurfaceCreatedSpy.wait());
0385     SurfaceInterface *serverSurface = serverSurfaceCreatedSpy.first().first().value<SurfaceInterface *>();
0386     QVERIFY(serverSurface);
0387 
0388     m_serverTextInputV3 = m_seat->textInputV3();
0389     QVERIFY(m_serverTextInputV3);
0390 
0391     QSignalSpy focusedSurfaceChangedSpy(m_seat, &SeatInterface::focusedTextInputSurfaceChanged);
0392     QSignalSpy textInputEnabledSpy(m_serverTextInputV3, &TextInputV3Interface::enabledChanged);
0393 
0394     // Enter the textinput
0395     QCOMPARE(focusedSurfaceChangedSpy.count(), 0);
0396 
0397     // Make sure that entering surface does not trigger the text input
0398     m_seat->setFocusedTextInputSurface(serverSurface);
0399     // FIXME: somehow this triggers BEFORE setFocusedTextInputSurface returns :(
0400     //  QVERIFY(focusedSurfaceChangedSpy.wait());
0401     QCOMPARE(focusedSurfaceChangedSpy.count(), 1);
0402 
0403     // Now enable the textInput
0404     m_clientTextInputV3->enable();
0405     m_clientTextInputV3->commit();
0406     QVERIFY(textInputEnabledSpy.wait());
0407     m_totalCommits++;
0408 
0409     // Default should be normal content purpose
0410     QCOMPARE(m_serverTextInputV3->contentPurpose(), TextInputContentPurpose::Normal);
0411 
0412     QSignalSpy contentTypeChangedSpy(m_serverTextInputV3, &TextInputV3Interface::contentTypeChanged);
0413 
0414     QFETCH(QtWayland::zwp_text_input_v3::content_purpose, clientPurpose);
0415     m_clientTextInputV3->enable();
0416     m_clientTextInputV3->set_content_type(QtWayland::zwp_text_input_v3::content_hint_none, clientPurpose);
0417     m_clientTextInputV3->commit();
0418     QVERIFY(contentTypeChangedSpy.wait());
0419     QTEST(m_serverTextInputV3->contentPurpose(), "serverPurpose");
0420     m_totalCommits++;
0421 
0422     // Setting same thing should not trigger update
0423     m_clientTextInputV3->enable();
0424     m_clientTextInputV3->set_content_type(QtWayland::zwp_text_input_v3::content_hint_none, clientPurpose);
0425     m_clientTextInputV3->commit();
0426     QVERIFY(!contentTypeChangedSpy.wait(100));
0427     m_totalCommits++;
0428 
0429     // unset to normal
0430     m_clientTextInputV3->enable();
0431     m_clientTextInputV3->set_content_type(QtWayland::zwp_text_input_v3::content_hint_none, QtWayland::zwp_text_input_v3::content_purpose_normal);
0432     m_clientTextInputV3->commit();
0433     QVERIFY(contentTypeChangedSpy.wait());
0434     m_totalCommits++;
0435     QCOMPARE(m_serverTextInputV3->contentPurpose(), TextInputContentPurpose::Normal);
0436 
0437     // Now disable the textInput
0438     m_clientTextInputV3->disable();
0439     m_clientTextInputV3->commit();
0440     m_totalCommits++;
0441     QVERIFY(textInputEnabledSpy.wait());
0442 }
0443 
0444 void TestTextInputV3Interface::testContentHints_data()
0445 {
0446     QTest::addColumn<quint32>("clientHint");
0447     QTest::addColumn<KWin::TextInputContentHints>("serverHints");
0448 
0449     QTest::addRow("Spellcheck") << quint32(QtWayland::zwp_text_input_v3::content_hint_spellcheck)
0450                                 << TextInputContentHints(TextInputContentHint::AutoCorrection);
0451     QTest::addRow("Completion") << quint32(QtWayland::zwp_text_input_v3::content_hint_completion)
0452                                 << TextInputContentHints(TextInputContentHint::AutoCompletion);
0453     QTest::addRow("AutoCapital") << quint32(QtWayland::zwp_text_input_v3::content_hint_auto_capitalization)
0454                                  << TextInputContentHints(TextInputContentHint::AutoCapitalization);
0455     QTest::addRow("Lowercase") << quint32(QtWayland::zwp_text_input_v3::content_hint_lowercase) << TextInputContentHints(TextInputContentHint::LowerCase);
0456     QTest::addRow("Uppercase") << quint32(QtWayland::zwp_text_input_v3::content_hint_uppercase) << TextInputContentHints(TextInputContentHint::UpperCase);
0457     QTest::addRow("Titlecase") << quint32(QtWayland::zwp_text_input_v3::content_hint_titlecase) << TextInputContentHints(TextInputContentHint::TitleCase);
0458     QTest::addRow("HiddenText") << quint32(QtWayland::zwp_text_input_v3::content_hint_hidden_text) << TextInputContentHints(TextInputContentHint::HiddenText);
0459     QTest::addRow("SensitiveData") << quint32(QtWayland::zwp_text_input_v3::content_hint_sensitive_data)
0460                                    << TextInputContentHints(TextInputContentHint::SensitiveData);
0461     QTest::addRow("Latin") << quint32(QtWayland::zwp_text_input_v3::content_hint_latin) << TextInputContentHints(TextInputContentHint::Latin);
0462     QTest::addRow("Multiline") << quint32(QtWayland::zwp_text_input_v3::content_hint_multiline) << TextInputContentHints(TextInputContentHint::MultiLine);
0463     QTest::addRow("Auto") << quint32(QtWayland::zwp_text_input_v3::content_hint_completion | QtWayland::zwp_text_input_v3::content_hint_spellcheck
0464                                      | QtWayland::zwp_text_input_v3::content_hint_auto_capitalization)
0465                           << TextInputContentHints(TextInputContentHint::AutoCompletion | TextInputContentHint::AutoCorrection
0466                                                    | TextInputContentHint::AutoCapitalization);
0467 }
0468 
0469 void TestTextInputV3Interface::testContentHints()
0470 {
0471     // create a surface
0472     QSignalSpy serverSurfaceCreatedSpy(m_serverCompositor, &CompositorInterface::surfaceCreated);
0473     std::unique_ptr<KWayland::Client::Surface> clientSurface(m_clientCompositor->createSurface(this));
0474     QVERIFY(serverSurfaceCreatedSpy.wait());
0475     SurfaceInterface *serverSurface = serverSurfaceCreatedSpy.first().first().value<SurfaceInterface *>();
0476     QVERIFY(serverSurface);
0477 
0478     m_serverTextInputV3 = m_seat->textInputV3();
0479     QVERIFY(m_serverTextInputV3);
0480 
0481     QSignalSpy focusedSurfaceChangedSpy(m_seat, &SeatInterface::focusedTextInputSurfaceChanged);
0482     QSignalSpy textInputEnabledSpy(m_serverTextInputV3, &TextInputV3Interface::enabledChanged);
0483 
0484     // Enter the textinput
0485     QCOMPARE(focusedSurfaceChangedSpy.count(), 0);
0486 
0487     // Make sure that entering surface does not trigger the text input
0488     m_seat->setFocusedTextInputSurface(serverSurface);
0489     // FIXME: somehow this triggers BEFORE setFocusedTextInputSurface returns :(
0490     //  QVERIFY(focusedSurfaceChangedSpy.wait());
0491     QCOMPARE(focusedSurfaceChangedSpy.count(), 1);
0492 
0493     // Now enable the textInput
0494     m_clientTextInputV3->enable();
0495     m_clientTextInputV3->commit();
0496     QVERIFY(textInputEnabledSpy.wait());
0497     m_totalCommits++;
0498 
0499     QCOMPARE(m_serverTextInputV3->contentHints(), TextInputContentHint::None);
0500 
0501     // Now disable the textInput
0502     m_clientTextInputV3->disable();
0503     m_clientTextInputV3->commit();
0504     QVERIFY(textInputEnabledSpy.wait());
0505     m_totalCommits++;
0506 
0507     QSignalSpy contentTypeChangedSpy(m_serverTextInputV3, &TextInputV3Interface::contentTypeChanged);
0508 
0509     QFETCH(quint32, clientHint);
0510     m_clientTextInputV3->enable();
0511     m_clientTextInputV3->set_content_type(clientHint, QtWayland::zwp_text_input_v3::content_purpose_normal);
0512     m_clientTextInputV3->commit();
0513     QVERIFY(contentTypeChangedSpy.wait());
0514     QTEST(m_serverTextInputV3->contentHints(), "serverHints");
0515     m_totalCommits++;
0516 
0517     // Setting same thing should not trigger update
0518     m_clientTextInputV3->enable();
0519     m_clientTextInputV3->set_content_type(clientHint, QtWayland::zwp_text_input_v3::content_purpose_normal);
0520     m_clientTextInputV3->commit();
0521     QVERIFY(!contentTypeChangedSpy.wait(100));
0522     m_totalCommits++;
0523 
0524     // unset to normal
0525     m_clientTextInputV3->enable();
0526     m_clientTextInputV3->set_content_type(QtWayland::zwp_text_input_v3::content_hint_none, QtWayland::zwp_text_input_v3::content_purpose_normal);
0527     m_clientTextInputV3->commit();
0528     QVERIFY(contentTypeChangedSpy.wait());
0529     m_totalCommits++;
0530 
0531     // Now disable the textInput
0532     m_clientTextInputV3->disable();
0533     m_clientTextInputV3->commit();
0534     QVERIFY(textInputEnabledSpy.wait());
0535     m_totalCommits++;
0536 }
0537 
0538 void TestTextInputV3Interface::testMultipleTextinputs()
0539 {
0540     // create two more text inputs
0541     TextInputV3 *ti1 = new TextInputV3();
0542     ti1->init(m_clientTextInputManagerV3->get_text_input(*m_clientSeat));
0543     QVERIFY(ti1);
0544 
0545     TextInputV3 *ti2 = new TextInputV3();
0546     ti2->init(m_clientTextInputManagerV3->get_text_input(*m_clientSeat));
0547     QVERIFY(ti2);
0548 
0549     // create a surface
0550     QSignalSpy serverSurfaceCreatedSpy(m_serverCompositor, &CompositorInterface::surfaceCreated);
0551     std::unique_ptr<KWayland::Client::Surface> clientSurface(m_clientCompositor->createSurface(this));
0552     QVERIFY(serverSurfaceCreatedSpy.wait());
0553     SurfaceInterface *serverSurface = serverSurfaceCreatedSpy.first().first().value<SurfaceInterface *>();
0554     QVERIFY(serverSurface);
0555 
0556     QSignalSpy focusedSurfaceChangedSpy(m_seat, &SeatInterface::focusedTextInputSurfaceChanged);
0557     // Make sure that entering surface does not trigger the text input
0558     m_seat->setFocusedTextInputSurface(serverSurface);
0559     QCOMPARE(focusedSurfaceChangedSpy.count(), 1);
0560 
0561     m_serverTextInputV3 = m_seat->textInputV3();
0562     QVERIFY(m_serverTextInputV3);
0563     QVERIFY(!m_serverTextInputV3->isEnabled());
0564 
0565     QSignalSpy doneSpy1(ti1, &TextInputV3::done);
0566     QSignalSpy committedSpy(m_serverTextInputV3, &TextInputV3Interface::stateCommitted);
0567     // Enable ti1
0568     ti1->enable();
0569     ti1->commit();
0570     QVERIFY(committedSpy.wait());
0571     QCOMPARE(committedSpy.last().at(0).value<quint32>(), 1);
0572     QVERIFY(m_serverTextInputV3->isEnabled());
0573     QVERIFY(doneSpy1.wait());
0574 
0575     // Send another three commits on ti1
0576     ti1->enable();
0577     ti1->set_surrounding_text("hello", 0, 1);
0578     ti1->commit();
0579     QVERIFY(committedSpy.wait());
0580     QCOMPARE(committedSpy.last().at(0).value<quint32>(), 2);
0581     QVERIFY(m_serverTextInputV3->isEnabled());
0582     QVERIFY(doneSpy1.wait());
0583 
0584     ti1->enable();
0585     ti1->set_content_type(QtWayland::zwp_text_input_v3::content_hint_none, QtWayland::zwp_text_input_v3::content_purpose_normal);
0586     ti1->commit();
0587     QVERIFY(committedSpy.wait());
0588     QCOMPARE(committedSpy.last().at(0).value<quint32>(), 3);
0589     QVERIFY(m_serverTextInputV3->isEnabled());
0590     QVERIFY(doneSpy1.wait());
0591 
0592     // at this point total commit count to ti1 is 3
0593     QSignalSpy doneSpy2(ti2, &TextInputV3::done);
0594 
0595     m_serverTextInputV3->commitString("Hello");
0596     m_serverTextInputV3->done();
0597     QVERIFY(doneSpy1.wait());
0598 
0599     // zwp_text_input_v3.done event have serial of total commits
0600     QCOMPARE(doneSpy1.last().at(0).value<quint32>(), 3);
0601 
0602     // now ti1 is at 4 commit, while ti2 is still 0
0603     ti1->disable();
0604     ti1->commit();
0605     QVERIFY(committedSpy.wait());
0606     QCOMPARE(committedSpy.last().at(0).value<quint32>(), 4);
0607     QVERIFY(!m_serverTextInputV3->isEnabled());
0608 
0609     // first commit to ti2
0610     ti2->enable();
0611     ti2->commit();
0612     QVERIFY(committedSpy.wait());
0613     QCOMPARE(committedSpy.last().at(0).value<quint32>(), 1);
0614     QVERIFY(m_serverTextInputV3->isEnabled());
0615 
0616     // send commit string
0617     m_serverTextInputV3->commitString("Hello world");
0618     m_serverTextInputV3->done();
0619     QVERIFY(doneSpy2.wait());
0620 
0621     // ti2 is at one commit
0622     QCOMPARE(doneSpy2.last().at(0).value<quint32>(), 1);
0623     ti2->disable();
0624     ti2->commit();
0625     QVERIFY(committedSpy.wait());
0626     QCOMPARE(committedSpy.last().at(0).value<quint32>(), 2);
0627     QVERIFY(!m_serverTextInputV3->isEnabled());
0628 
0629     // now re-enable the ti1 and verify sending commits to t2 hasn't affected it's serial
0630     // Enable ti1 : 5 commits now
0631     ti1->enable();
0632     ti1->commit();
0633     QVERIFY(committedSpy.wait());
0634     QCOMPARE(committedSpy.last().at(0).value<quint32>(), 5);
0635     QVERIFY(m_serverTextInputV3->isEnabled());
0636 
0637     // send done signal
0638     m_serverTextInputV3->commitString("Hello");
0639     m_serverTextInputV3->done();
0640     QVERIFY(doneSpy1.wait());
0641     QCOMPARE(doneSpy1.last().at(0).value<quint32>(), 5);
0642 
0643     // cleanup
0644     if (ti1) {
0645         delete ti1;
0646         ti1 = nullptr;
0647     }
0648     if (ti2) {
0649         delete ti2;
0650         ti2 = nullptr;
0651     }
0652 }
0653 
0654 QTEST_GUILESS_MAIN(TestTextInputV3Interface)
0655 
0656 #include "test_textinputv3_interface.moc"