File indexing completed on 2024-06-16 05:05:04

0001 /*
0002     SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
0003     SPDX-FileCopyrightText: 2020 Bhushan Shah <bshah@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0006 */
0007 // Qt
0008 #include <QHash>
0009 #include <QSignalSpy>
0010 #include <QTest>
0011 #include <QThread>
0012 
0013 #include "../../../tests/fakeoutput.h"
0014 
0015 // WaylandServer
0016 #include "wayland/compositor.h"
0017 #include "wayland/display.h"
0018 #include "wayland/inputmethod_v1.h"
0019 #include "wayland/output.h"
0020 #include "wayland/seat.h"
0021 
0022 #include "KWayland/Client/compositor.h"
0023 #include "KWayland/Client/connection_thread.h"
0024 #include "KWayland/Client/event_queue.h"
0025 #include "KWayland/Client/keyboard.h"
0026 #include "KWayland/Client/output.h"
0027 #include "KWayland/Client/registry.h"
0028 #include "KWayland/Client/seat.h"
0029 #include "KWayland/Client/surface.h"
0030 
0031 #include "qwayland-input-method-unstable-v1.h"
0032 #include "qwayland-server-text-input-unstable-v1.h"
0033 
0034 #include <linux/input-event-codes.h>
0035 
0036 using namespace KWin;
0037 
0038 class InputPanelSurface : public QObject, public QtWayland::zwp_input_panel_surface_v1
0039 {
0040     Q_OBJECT
0041 public:
0042     InputPanelSurface(::zwp_input_panel_surface_v1 *t)
0043         : QtWayland::zwp_input_panel_surface_v1(t)
0044     {
0045     }
0046 };
0047 
0048 class InputPanel : public QtWayland::zwp_input_panel_v1
0049 {
0050 public:
0051     InputPanel(struct ::wl_registry *registry, int id, int version)
0052         : QtWayland::zwp_input_panel_v1(registry, id, version)
0053     {
0054     }
0055 
0056     InputPanelSurface *panelForSurface(KWayland::Client::Surface *surface)
0057     {
0058         auto panelSurface = new InputPanelSurface(get_input_panel_surface(*surface));
0059         QObject::connect(surface, &QObject::destroyed, panelSurface, &QObject::deleteLater);
0060         return panelSurface;
0061     }
0062 };
0063 
0064 class InputMethodV1Context : public QObject, public QtWayland::zwp_input_method_context_v1
0065 {
0066     Q_OBJECT
0067 public:
0068     quint32 contentPurpose()
0069     {
0070         return imPurpose;
0071     }
0072     quint32 contentHints()
0073     {
0074         return imHint;
0075     }
0076 Q_SIGNALS:
0077     void content_type_changed();
0078     void invoke_action(quint32 button, quint32 index);
0079     void preferred_language(QString lang);
0080     void surrounding_text(QString lang, quint32 cursor, quint32 anchor);
0081     void reset();
0082 
0083 protected:
0084     void zwp_input_method_context_v1_content_type(uint32_t hint, uint32_t purpose) override
0085     {
0086         imHint = hint;
0087         imPurpose = purpose;
0088         Q_EMIT content_type_changed();
0089     }
0090     void zwp_input_method_context_v1_invoke_action(uint32_t button, uint32_t index) override
0091     {
0092         Q_EMIT invoke_action(button, index);
0093     }
0094     void zwp_input_method_context_v1_preferred_language(const QString &language) override
0095     {
0096         Q_EMIT preferred_language(language);
0097     }
0098     void zwp_input_method_context_v1_surrounding_text(const QString &text, uint32_t cursor, uint32_t anchor) override
0099     {
0100         Q_EMIT surrounding_text(text, cursor, anchor);
0101     }
0102     void zwp_input_method_context_v1_reset() override
0103     {
0104         Q_EMIT reset();
0105     }
0106 
0107 private:
0108     quint32 imHint = 0;
0109     quint32 imPurpose = 0;
0110 };
0111 
0112 class InputMethodV1 : public QObject, public QtWayland::zwp_input_method_v1
0113 {
0114     Q_OBJECT
0115 public:
0116     InputMethodV1(struct ::wl_registry *registry, int id, int version)
0117         : QtWayland::zwp_input_method_v1(registry, id, version)
0118     {
0119     }
0120     InputMethodV1Context *context()
0121     {
0122         return m_context;
0123     }
0124 
0125 Q_SIGNALS:
0126     void activated();
0127     void deactivated();
0128 
0129 protected:
0130     void zwp_input_method_v1_activate(struct ::zwp_input_method_context_v1 *context) override
0131     {
0132         m_context = new InputMethodV1Context();
0133         m_context->init(context);
0134         Q_EMIT activated();
0135     };
0136     void zwp_input_method_v1_deactivate(struct ::zwp_input_method_context_v1 *context) override
0137     {
0138         delete m_context;
0139         m_context = nullptr;
0140         Q_EMIT deactivated();
0141     };
0142 
0143 private:
0144     InputMethodV1Context *m_context;
0145 };
0146 
0147 class TestInputMethodInterface : public QObject
0148 {
0149     Q_OBJECT
0150 public:
0151     TestInputMethodInterface()
0152     {
0153     }
0154     ~TestInputMethodInterface() override;
0155 
0156 private Q_SLOTS:
0157     void initTestCase();
0158     void testAdd();
0159     void testActivate();
0160     void testContext();
0161     void testGrabkeyboard();
0162     void testContentHints_data();
0163     void testContentHints();
0164     void testContentPurpose_data();
0165     void testContentPurpose();
0166     void testKeyboardGrab();
0167 
0168 private:
0169     KWayland::Client::ConnectionThread *m_connection;
0170     KWayland::Client::EventQueue *m_queue;
0171     KWayland::Client::Compositor *m_clientCompositor;
0172     KWayland::Client::Seat *m_clientSeat = nullptr;
0173     KWayland::Client::Output *m_output = nullptr;
0174 
0175     InputMethodV1 *m_inputMethod;
0176     InputPanel *m_inputPanel;
0177     QThread *m_thread;
0178     KWin::Display m_display;
0179     SeatInterface *m_seat;
0180     CompositorInterface *m_serverCompositor;
0181     std::unique_ptr<FakeOutput> m_outputHandle;
0182     std::unique_ptr<OutputInterface> m_outputInterface;
0183 
0184     KWin::InputMethodV1Interface *m_inputMethodIface;
0185     KWin::InputPanelV1Interface *m_inputPanelIface;
0186 
0187     QList<SurfaceInterface *> m_surfaces;
0188 };
0189 
0190 static const QString s_socketName = QStringLiteral("kwin-wayland-server-inputmethod-test-0");
0191 
0192 void TestInputMethodInterface::initTestCase()
0193 {
0194     m_display.addSocketName(s_socketName);
0195     m_display.start();
0196     QVERIFY(m_display.isRunning());
0197 
0198     m_seat = new SeatInterface(&m_display, this);
0199     m_serverCompositor = new CompositorInterface(&m_display, this);
0200     m_inputMethodIface = new InputMethodV1Interface(&m_display, this);
0201     m_inputPanelIface = new InputPanelV1Interface(&m_display, this);
0202 
0203     m_outputHandle = std::make_unique<FakeOutput>();
0204     m_outputInterface = std::make_unique<OutputInterface>(&m_display, m_outputHandle.get());
0205 
0206     connect(m_serverCompositor, &CompositorInterface::surfaceCreated, this, [this](SurfaceInterface *surface) {
0207         m_surfaces += surface;
0208     });
0209 
0210     // setup connection
0211     m_connection = new KWayland::Client::ConnectionThread;
0212     QSignalSpy connectedSpy(m_connection, &KWayland::Client::ConnectionThread::connected);
0213     m_connection->setSocketName(s_socketName);
0214 
0215     m_thread = new QThread(this);
0216     m_connection->moveToThread(m_thread);
0217     m_thread->start();
0218 
0219     m_connection->initConnection();
0220     QVERIFY(connectedSpy.wait());
0221     QVERIFY(!m_connection->connections().isEmpty());
0222 
0223     m_queue = new KWayland::Client::EventQueue(this);
0224     QVERIFY(!m_queue->isValid());
0225     m_queue->setup(m_connection);
0226     QVERIFY(m_queue->isValid());
0227 
0228     auto registry = new KWayland::Client::Registry(this);
0229     QSignalSpy interfacesSpy(registry, &KWayland::Client::Registry::interfacesAnnounced);
0230     connect(registry, &KWayland::Client::Registry::outputAnnounced, this, [this, registry](quint32 name, quint32 version) {
0231         m_output = new KWayland::Client::Output(this);
0232         m_output->setup(registry->bindOutput(name, version));
0233     });
0234     connect(registry, &KWayland::Client::Registry::interfaceAnnounced, this, [this, registry](const QByteArray &interface, quint32 name, quint32 version) {
0235         if (interface == "zwp_input_panel_v1") {
0236             m_inputPanel = new InputPanel(registry->registry(), name, version);
0237         } else if (interface == "zwp_input_method_v1") {
0238             m_inputMethod = new InputMethodV1(registry->registry(), name, version);
0239         }
0240     });
0241     connect(registry, &KWayland::Client::Registry::seatAnnounced, this, [this, registry](quint32 name, quint32 version) {
0242         m_clientSeat = registry->createSeat(name, version);
0243     });
0244     registry->setEventQueue(m_queue);
0245     QSignalSpy compositorSpy(registry, &KWayland::Client::Registry::compositorAnnounced);
0246     registry->create(m_connection->display());
0247     QVERIFY(registry->isValid());
0248     registry->setup();
0249     wl_display_flush(m_connection->display());
0250 
0251     QVERIFY(compositorSpy.wait());
0252     m_clientCompositor = registry->createCompositor(compositorSpy.first().first().value<quint32>(), compositorSpy.first().last().value<quint32>(), this);
0253     QVERIFY(m_clientCompositor->isValid());
0254 
0255     QVERIFY(interfacesSpy.count() || interfacesSpy.wait());
0256 
0257     QSignalSpy surfaceSpy(m_serverCompositor, &CompositorInterface::surfaceCreated);
0258     for (int i = 0; i < 3; ++i) {
0259         m_clientCompositor->createSurface(this);
0260     }
0261     QVERIFY(surfaceSpy.count() < 3 && surfaceSpy.wait(200));
0262     QVERIFY(m_surfaces.count() == 3);
0263     QVERIFY(m_inputPanel);
0264     QVERIFY(m_output);
0265 }
0266 
0267 TestInputMethodInterface::~TestInputMethodInterface()
0268 {
0269     if (m_queue) {
0270         delete m_queue;
0271         m_queue = nullptr;
0272     }
0273     if (m_thread) {
0274         m_thread->quit();
0275         m_thread->wait();
0276         delete m_thread;
0277         m_thread = nullptr;
0278     }
0279     delete m_inputPanel;
0280     delete m_inputMethod;
0281     delete m_inputMethodIface;
0282     delete m_inputPanelIface;
0283     m_connection->deleteLater();
0284     m_connection = nullptr;
0285 }
0286 
0287 void TestInputMethodInterface::testAdd()
0288 {
0289     QSignalSpy panelSpy(m_inputPanelIface, &InputPanelV1Interface::inputPanelSurfaceAdded);
0290     QPointer<InputPanelSurfaceV1Interface> panelSurfaceIface;
0291     connect(m_inputPanelIface, &InputPanelV1Interface::inputPanelSurfaceAdded, this, [&panelSurfaceIface](InputPanelSurfaceV1Interface *surface) {
0292         panelSurfaceIface = surface;
0293     });
0294 
0295     auto surface = m_clientCompositor->createSurface(this);
0296     auto panelSurface = m_inputPanel->panelForSurface(surface);
0297 
0298     QVERIFY(panelSpy.wait() || panelSurfaceIface);
0299     Q_ASSERT(panelSurfaceIface);
0300     Q_ASSERT(panelSurfaceIface->surface() == m_surfaces.constLast());
0301 
0302     QSignalSpy panelTopLevelSpy(panelSurfaceIface, &InputPanelSurfaceV1Interface::topLevel);
0303     panelSurface->set_toplevel(*m_output, InputPanelSurface::position_center_bottom);
0304     QVERIFY(panelTopLevelSpy.wait());
0305 }
0306 
0307 void TestInputMethodInterface::testActivate()
0308 {
0309     QVERIFY(m_inputMethodIface);
0310     QSignalSpy inputMethodActivateSpy(m_inputMethod, &InputMethodV1::activated);
0311     QSignalSpy inputMethodDeactivateSpy(m_inputMethod, &InputMethodV1::deactivated);
0312 
0313     // before sending activate the context should be null
0314     QVERIFY(!m_inputMethodIface->context());
0315 
0316     // send activate now
0317     m_inputMethodIface->sendActivate();
0318     QVERIFY(inputMethodActivateSpy.wait());
0319     QCOMPARE(inputMethodActivateSpy.count(), 1);
0320     QVERIFY(m_inputMethodIface->context());
0321 
0322     // send deactivate and verify server interface resets context
0323     m_inputMethodIface->sendDeactivate();
0324     QVERIFY(inputMethodDeactivateSpy.wait());
0325     QCOMPARE(inputMethodActivateSpy.count(), 1);
0326     QVERIFY(!m_inputMethodIface->context());
0327 }
0328 
0329 void TestInputMethodInterface::testContext()
0330 {
0331     QVERIFY(m_inputMethodIface);
0332     QSignalSpy inputMethodActivateSpy(m_inputMethod, &InputMethodV1::activated);
0333     QSignalSpy inputMethodDeactivateSpy(m_inputMethod, &InputMethodV1::deactivated);
0334 
0335     // before sending activate the context should be null
0336     QVERIFY(!m_inputMethodIface->context());
0337 
0338     // send activate now
0339     m_inputMethodIface->sendActivate();
0340     QVERIFY(inputMethodActivateSpy.wait());
0341     QCOMPARE(inputMethodActivateSpy.count(), 1);
0342 
0343     KWin::InputMethodContextV1Interface *serverContext = m_inputMethodIface->context();
0344     QVERIFY(serverContext);
0345 
0346     InputMethodV1Context *imContext = m_inputMethod->context();
0347     QVERIFY(imContext);
0348 
0349     quint32 serial = 1;
0350 
0351     // commit some text
0352     QSignalSpy commitStringSpy(serverContext, &KWin::InputMethodContextV1Interface::commitString);
0353     imContext->commit_string(serial, "hello");
0354     QVERIFY(commitStringSpy.wait());
0355     QCOMPARE(commitStringSpy.count(), serial);
0356     QCOMPARE(commitStringSpy.last().at(0).value<quint32>(), serial);
0357     QCOMPARE(commitStringSpy.last().at(1).value<QString>(), "hello");
0358     serial++;
0359 
0360     // preedit styling event
0361     QSignalSpy preeditStylingSpy(serverContext, &KWin::InputMethodContextV1Interface::preeditStyling);
0362     // protocol does not document 3rd argument mean in much details (styling)
0363     imContext->preedit_styling(0, 5, 1);
0364     QVERIFY(preeditStylingSpy.wait());
0365     QCOMPARE(preeditStylingSpy.count(), 1);
0366     QCOMPARE(preeditStylingSpy.last().at(0).value<quint32>(), 0);
0367     QCOMPARE(preeditStylingSpy.last().at(1).value<quint32>(), 5);
0368     QCOMPARE(preeditStylingSpy.last().at(2).value<quint32>(), 1);
0369 
0370     // preedit cursor event
0371     QSignalSpy preeditCursorSpy(serverContext, &KWin::InputMethodContextV1Interface::preeditCursor);
0372     imContext->preedit_cursor(3);
0373     QVERIFY(preeditCursorSpy.wait());
0374     QCOMPARE(preeditCursorSpy.count(), 1);
0375     QCOMPARE(preeditCursorSpy.last().at(0).value<quint32>(), 3);
0376 
0377     // commit preedit_string
0378     QSignalSpy preeditStringSpy(serverContext, &KWin::InputMethodContextV1Interface::preeditString);
0379     imContext->preedit_string(serial, "hello", "kde");
0380     QVERIFY(preeditStringSpy.wait());
0381     QCOMPARE(preeditStringSpy.count(), 1);
0382     QCOMPARE(preeditStringSpy.last().at(0).value<quint32>(), serial);
0383     QCOMPARE(preeditStringSpy.last().at(1).value<QString>(), "hello");
0384     QCOMPARE(preeditStringSpy.last().at(2).value<QString>(), "kde");
0385     serial++;
0386 
0387     // delete surrounding text
0388     QSignalSpy deleteSurroundingSpy(serverContext, &KWin::InputMethodContextV1Interface::deleteSurroundingText);
0389     imContext->delete_surrounding_text(0, 5);
0390     QVERIFY(deleteSurroundingSpy.wait());
0391     QCOMPARE(deleteSurroundingSpy.count(), 1);
0392     QCOMPARE(deleteSurroundingSpy.last().at(0).value<quint32>(), 0);
0393     QCOMPARE(deleteSurroundingSpy.last().at(1).value<quint32>(), 5);
0394 
0395     // set cursor position
0396     QSignalSpy cursorPositionSpy(serverContext, &KWin::InputMethodContextV1Interface::cursorPosition);
0397     imContext->cursor_position(2, 4);
0398     QVERIFY(cursorPositionSpy.wait());
0399     QCOMPARE(cursorPositionSpy.count(), 1);
0400     QCOMPARE(cursorPositionSpy.last().at(0).value<quint32>(), 2);
0401     QCOMPARE(cursorPositionSpy.last().at(1).value<quint32>(), 4);
0402 
0403     // invoke action
0404     QSignalSpy invokeActionSpy(imContext, &InputMethodV1Context::invoke_action);
0405     serverContext->sendInvokeAction(3, 5);
0406     QVERIFY(invokeActionSpy.wait());
0407     QCOMPARE(invokeActionSpy.count(), 1);
0408     QCOMPARE(invokeActionSpy.last().at(0).value<quint32>(), 3);
0409     QCOMPARE(invokeActionSpy.last().at(1).value<quint32>(), 5);
0410 
0411     // preferred language
0412     QSignalSpy preferredLanguageSpy(imContext, &InputMethodV1Context::preferred_language);
0413     serverContext->sendPreferredLanguage("gu_IN");
0414     QVERIFY(preferredLanguageSpy.wait());
0415     QCOMPARE(preferredLanguageSpy.count(), 1);
0416     QCOMPARE(preferredLanguageSpy.last().at(0).value<QString>(), "gu_IN");
0417 
0418     // surrounding text
0419     QSignalSpy surroundingTextSpy(imContext, &InputMethodV1Context::surrounding_text);
0420     serverContext->sendSurroundingText("Hello Plasma!", 2, 4);
0421     QVERIFY(surroundingTextSpy.wait());
0422     QCOMPARE(surroundingTextSpy.count(), 1);
0423     QCOMPARE(surroundingTextSpy.last().at(0).value<QString>(), "Hello Plasma!");
0424     QCOMPARE(surroundingTextSpy.last().at(1).value<quint32>(), 2);
0425     QCOMPARE(surroundingTextSpy.last().at(2).value<quint32>(), 4);
0426 
0427     // reset
0428     QSignalSpy resetSpy(imContext, &InputMethodV1Context::reset);
0429     serverContext->sendReset();
0430     QVERIFY(resetSpy.wait());
0431     QCOMPARE(resetSpy.count(), 1);
0432 
0433     // send deactivate and verify server interface resets context
0434     m_inputMethodIface->sendDeactivate();
0435     QVERIFY(inputMethodDeactivateSpy.wait());
0436     QCOMPARE(inputMethodActivateSpy.count(), 1);
0437     QVERIFY(!m_inputMethodIface->context());
0438     QVERIFY(!m_inputMethod->context());
0439 }
0440 
0441 void TestInputMethodInterface::testGrabkeyboard()
0442 {
0443     QVERIFY(m_inputMethodIface);
0444     QSignalSpy inputMethodActivateSpy(m_inputMethod, &InputMethodV1::activated);
0445     QSignalSpy inputMethodDeactivateSpy(m_inputMethod, &InputMethodV1::deactivated);
0446 
0447     // before sending activate the context should be null
0448     QVERIFY(!m_inputMethodIface->context());
0449 
0450     // send activate now
0451     m_inputMethodIface->sendActivate();
0452     QVERIFY(inputMethodActivateSpy.wait());
0453     QCOMPARE(inputMethodActivateSpy.count(), 1);
0454 
0455     KWin::InputMethodContextV1Interface *serverContext = m_inputMethodIface->context();
0456     QVERIFY(serverContext);
0457 
0458     InputMethodV1Context *imContext = m_inputMethod->context();
0459     QVERIFY(imContext);
0460 
0461     QSignalSpy keyEventSpy(serverContext, &KWin::InputMethodContextV1Interface::key);
0462     imContext->key(0, 123, 56, 1);
0463     QEXPECT_FAIL("", "We should be not get key event if keyboard is not grabbed", Continue);
0464     QVERIFY(!keyEventSpy.wait(200));
0465 
0466     QSignalSpy modifierEventSpy(serverContext, &KWin::InputMethodContextV1Interface::modifiers);
0467     imContext->modifiers(1234, 0, 0, 0, 0);
0468     QEXPECT_FAIL("", "We should be not get modifiers event if keyboard is not grabbed", Continue);
0469     QVERIFY(!modifierEventSpy.wait(200));
0470 
0471     // grab the keyboard
0472     wl_keyboard *keyboard = imContext->grab_keyboard();
0473     QVERIFY(keyboard);
0474 
0475     // TODO: add more tests about keyboard grab here
0476 
0477     // send deactivate and verify server interface resets context
0478     m_inputMethodIface->sendDeactivate();
0479     QVERIFY(inputMethodDeactivateSpy.wait());
0480     QCOMPARE(inputMethodActivateSpy.count(), 1);
0481     QVERIFY(!m_inputMethodIface->context());
0482     QVERIFY(!m_inputMethod->context());
0483 }
0484 
0485 void TestInputMethodInterface::testContentHints_data()
0486 {
0487     QTest::addColumn<KWin::TextInputContentHints>("serverHints");
0488     QTest::addColumn<quint32>("imHint");
0489     QTest::addRow("Spellcheck") << TextInputContentHints(TextInputContentHint::AutoCorrection)
0490                                 << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_auto_correction);
0491     QTest::addRow("AutoCapital") << TextInputContentHints(TextInputContentHint::AutoCapitalization)
0492                                  << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_auto_capitalization);
0493     QTest::addRow("Lowercase") << TextInputContentHints(TextInputContentHint::LowerCase) << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_lowercase);
0494     QTest::addRow("Uppercase") << TextInputContentHints(TextInputContentHint::UpperCase) << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_uppercase);
0495     QTest::addRow("Titlecase") << TextInputContentHints(TextInputContentHint::TitleCase) << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_titlecase);
0496     QTest::addRow("HiddenText") << TextInputContentHints(TextInputContentHint::HiddenText)
0497                                 << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_hidden_text);
0498     QTest::addRow("SensitiveData") << TextInputContentHints(TextInputContentHint::SensitiveData)
0499                                    << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_sensitive_data);
0500     QTest::addRow("Latin") << TextInputContentHints(TextInputContentHint::Latin) << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_latin);
0501     QTest::addRow("Multiline") << TextInputContentHints(TextInputContentHint::MultiLine) << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_multiline);
0502     QTest::addRow("Auto") << TextInputContentHints(TextInputContentHint::AutoCorrection | TextInputContentHint::AutoCapitalization)
0503                           << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_auto_correction
0504                                      | QtWaylandServer::zwp_text_input_v1::content_hint_auto_capitalization);
0505 }
0506 
0507 void TestInputMethodInterface::testContentHints()
0508 {
0509     QVERIFY(m_inputMethodIface);
0510     QSignalSpy inputMethodActivateSpy(m_inputMethod, &InputMethodV1::activated);
0511     QSignalSpy inputMethodDeactivateSpy(m_inputMethod, &InputMethodV1::deactivated);
0512 
0513     // before sending activate the context should be null
0514     QVERIFY(!m_inputMethodIface->context());
0515 
0516     // send activate now
0517     m_inputMethodIface->sendActivate();
0518     QVERIFY(inputMethodActivateSpy.wait());
0519     QCOMPARE(inputMethodActivateSpy.count(), 1);
0520 
0521     KWin::InputMethodContextV1Interface *serverContext = m_inputMethodIface->context();
0522     QVERIFY(serverContext);
0523 
0524     InputMethodV1Context *imContext = m_inputMethod->context();
0525     QVERIFY(imContext);
0526 
0527     QSignalSpy contentTypeChangedSpy(imContext, &InputMethodV1Context::content_type_changed);
0528 
0529     QFETCH(KWin::TextInputContentHints, serverHints);
0530     serverContext->sendContentType(serverHints, KWin::TextInputContentPurpose::Normal);
0531     QVERIFY(contentTypeChangedSpy.wait());
0532     QCOMPARE(contentTypeChangedSpy.count(), 1);
0533     QEXPECT_FAIL("SensitiveData", "SensitiveData content hint need fixing", Continue);
0534     QTEST(imContext->contentHints(), "imHint");
0535 
0536     // send deactivate and verify server interface resets context
0537     m_inputMethodIface->sendDeactivate();
0538     QVERIFY(inputMethodDeactivateSpy.wait());
0539     QCOMPARE(inputMethodActivateSpy.count(), 1);
0540     QVERIFY(!m_inputMethodIface->context());
0541     QVERIFY(!m_inputMethod->context());
0542 }
0543 
0544 void TestInputMethodInterface::testContentPurpose_data()
0545 {
0546     QTest::addColumn<KWin::TextInputContentPurpose>("serverPurpose");
0547     QTest::addColumn<quint32>("imPurpose");
0548 
0549     QTest::newRow("Alpha") << TextInputContentPurpose::Alpha << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_alpha);
0550     QTest::newRow("Digits") << TextInputContentPurpose::Digits << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_digits);
0551     QTest::newRow("Number") << TextInputContentPurpose::Number << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_number);
0552     QTest::newRow("Phone") << TextInputContentPurpose::Phone << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_phone);
0553     QTest::newRow("Url") << TextInputContentPurpose::Url << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_url);
0554     QTest::newRow("Email") << TextInputContentPurpose::Email << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_email);
0555     QTest::newRow("Name") << TextInputContentPurpose::Name << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_name);
0556     QTest::newRow("Password") << TextInputContentPurpose::Password << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_password);
0557     QTest::newRow("Date") << TextInputContentPurpose::Date << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_date);
0558     QTest::newRow("Time") << TextInputContentPurpose::Time << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_time);
0559     QTest::newRow("DateTime") << TextInputContentPurpose::DateTime << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_datetime);
0560     QTest::newRow("Terminal") << TextInputContentPurpose::Terminal << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_terminal);
0561     QTest::newRow("Normal") << TextInputContentPurpose::Normal << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_normal);
0562     QTest::newRow("Pin") << TextInputContentPurpose::Pin << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_password);
0563 }
0564 
0565 void TestInputMethodInterface::testContentPurpose()
0566 {
0567     QVERIFY(m_inputMethodIface);
0568     QSignalSpy inputMethodActivateSpy(m_inputMethod, &InputMethodV1::activated);
0569     QSignalSpy inputMethodDeactivateSpy(m_inputMethod, &InputMethodV1::deactivated);
0570 
0571     // before sending activate the context should be null
0572     QVERIFY(!m_inputMethodIface->context());
0573 
0574     // send activate now
0575     m_inputMethodIface->sendActivate();
0576     QVERIFY(inputMethodActivateSpy.wait());
0577     QCOMPARE(inputMethodActivateSpy.count(), 1);
0578 
0579     KWin::InputMethodContextV1Interface *serverContext = m_inputMethodIface->context();
0580     QVERIFY(serverContext);
0581 
0582     InputMethodV1Context *imContext = m_inputMethod->context();
0583     QVERIFY(imContext);
0584 
0585     QSignalSpy contentTypeChangedSpy(imContext, &InputMethodV1Context::content_type_changed);
0586 
0587     QFETCH(KWin::TextInputContentPurpose, serverPurpose);
0588     serverContext->sendContentType(KWin::TextInputContentHints(KWin::TextInputContentHint::None), serverPurpose);
0589     QVERIFY(contentTypeChangedSpy.wait());
0590     QCOMPARE(contentTypeChangedSpy.count(), 1);
0591     QEXPECT_FAIL("Pin", "Pin should return content_purpose_password", Continue);
0592     QTEST(imContext->contentPurpose(), "imPurpose");
0593 
0594     // send deactivate and verify server interface resets context
0595     m_inputMethodIface->sendDeactivate();
0596     QVERIFY(inputMethodDeactivateSpy.wait());
0597     QCOMPARE(inputMethodActivateSpy.count(), 1);
0598     QVERIFY(!m_inputMethodIface->context());
0599     QVERIFY(!m_inputMethod->context());
0600 }
0601 
0602 void TestInputMethodInterface::testKeyboardGrab()
0603 {
0604     QVERIFY(m_inputMethodIface);
0605     QSignalSpy inputMethodActivateSpy(m_inputMethod, &InputMethodV1::activated);
0606 
0607     m_inputMethodIface->sendActivate();
0608     QVERIFY(inputMethodActivateSpy.wait());
0609 
0610     QSignalSpy keyboardGrabSpy(m_inputMethodIface->context(), &InputMethodContextV1Interface::keyboardGrabRequested);
0611     InputMethodV1Context *imContext = m_inputMethod->context();
0612     QVERIFY(imContext);
0613     KWayland::Client::Keyboard *keyboard = new KWayland::Client::Keyboard(this);
0614     keyboard->setup(imContext->grab_keyboard());
0615     QVERIFY(keyboard->isValid());
0616     QVERIFY(keyboardGrabSpy.count() || keyboardGrabSpy.wait());
0617 
0618     QSignalSpy keyboardSpy(keyboard, &KWayland::Client::Keyboard::keyChanged);
0619     m_inputMethodIface->context()->keyboardGrab()->sendKey(0, 0, KEY_F1, KeyboardKeyState::Pressed);
0620     m_inputMethodIface->context()->keyboardGrab()->sendKey(0, 0, KEY_F1, KeyboardKeyState::Released);
0621     keyboardSpy.wait();
0622     QCOMPARE(keyboardSpy.count(), 2);
0623 
0624     m_inputMethodIface->sendDeactivate();
0625 }
0626 
0627 QTEST_GUILESS_MAIN(TestInputMethodInterface)
0628 #include "test_inputmethod_interface.moc"