kwin/autotests/wayland/server/test_inputmethod_interface.cpp
Vlad Zahorodnii d7b1661e08 Merge libkwineffects into libkwin
libkwineffects was introduced when kwin used to be an executable. It
provided an api to implement effects and shielded from the technical
quirks in kwin.

Over the time, kwin internals had been split and abstractions were
refactored so they can be consumed in scripts or plugins. Besides that,
new ways to implement extensions have been introduced, which use
kwin's internal abstractions.

On the other hand, effects continue using libkwineffects specific apis.
This has a few issues: qtquick effects use both apis and it bites us,
duplicating same apis.

The best solution so far is to merge libkwineffects with libkwin, and
replace libkwineffects abstractions with libkwin abstractions, e.g.
EffectScreen -> Output, etc. This change takes care of adjusting libs.

Obviously, the main disadvantage of doing so is that binary effects
have to be recompiled every time new libkwin is released. But it's
already the case with libkwineffects too.
2023-10-25 12:46:12 +00:00

628 lines
25 KiB
C++

/*
SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
SPDX-FileCopyrightText: 2020 Bhushan Shah <bshah@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
// Qt
#include <QHash>
#include <QSignalSpy>
#include <QTest>
#include <QThread>
#include "../../../tests/fakeoutput.h"
// WaylandServer
#include "wayland/compositor.h"
#include "wayland/display.h"
#include "wayland/inputmethod_v1.h"
#include "wayland/output.h"
#include "wayland/seat.h"
#include "KWayland/Client/compositor.h"
#include "KWayland/Client/connection_thread.h"
#include "KWayland/Client/event_queue.h"
#include "KWayland/Client/keyboard.h"
#include "KWayland/Client/output.h"
#include "KWayland/Client/registry.h"
#include "KWayland/Client/seat.h"
#include "KWayland/Client/surface.h"
#include "qwayland-input-method-unstable-v1.h"
#include "qwayland-server-text-input-unstable-v1.h"
#include <linux/input-event-codes.h>
using namespace KWin;
class InputPanelSurface : public QObject, public QtWayland::zwp_input_panel_surface_v1
{
Q_OBJECT
public:
InputPanelSurface(::zwp_input_panel_surface_v1 *t)
: QtWayland::zwp_input_panel_surface_v1(t)
{
}
};
class InputPanel : public QtWayland::zwp_input_panel_v1
{
public:
InputPanel(struct ::wl_registry *registry, int id, int version)
: QtWayland::zwp_input_panel_v1(registry, id, version)
{
}
InputPanelSurface *panelForSurface(KWayland::Client::Surface *surface)
{
auto panelSurface = new InputPanelSurface(get_input_panel_surface(*surface));
QObject::connect(surface, &QObject::destroyed, panelSurface, &QObject::deleteLater);
return panelSurface;
}
};
class InputMethodV1Context : public QObject, public QtWayland::zwp_input_method_context_v1
{
Q_OBJECT
public:
quint32 contentPurpose()
{
return imPurpose;
}
quint32 contentHints()
{
return imHint;
}
Q_SIGNALS:
void content_type_changed();
void invoke_action(quint32 button, quint32 index);
void preferred_language(QString lang);
void surrounding_text(QString lang, quint32 cursor, quint32 anchor);
void reset();
protected:
void zwp_input_method_context_v1_content_type(uint32_t hint, uint32_t purpose) override
{
imHint = hint;
imPurpose = purpose;
Q_EMIT content_type_changed();
}
void zwp_input_method_context_v1_invoke_action(uint32_t button, uint32_t index) override
{
Q_EMIT invoke_action(button, index);
}
void zwp_input_method_context_v1_preferred_language(const QString &language) override
{
Q_EMIT preferred_language(language);
}
void zwp_input_method_context_v1_surrounding_text(const QString &text, uint32_t cursor, uint32_t anchor) override
{
Q_EMIT surrounding_text(text, cursor, anchor);
}
void zwp_input_method_context_v1_reset() override
{
Q_EMIT reset();
}
private:
quint32 imHint = 0;
quint32 imPurpose = 0;
};
class InputMethodV1 : public QObject, public QtWayland::zwp_input_method_v1
{
Q_OBJECT
public:
InputMethodV1(struct ::wl_registry *registry, int id, int version)
: QtWayland::zwp_input_method_v1(registry, id, version)
{
}
InputMethodV1Context *context()
{
return m_context;
}
Q_SIGNALS:
void activated();
void deactivated();
protected:
void zwp_input_method_v1_activate(struct ::zwp_input_method_context_v1 *context) override
{
m_context = new InputMethodV1Context();
m_context->init(context);
Q_EMIT activated();
};
void zwp_input_method_v1_deactivate(struct ::zwp_input_method_context_v1 *context) override
{
delete m_context;
m_context = nullptr;
Q_EMIT deactivated();
};
private:
InputMethodV1Context *m_context;
};
class TestInputMethodInterface : public QObject
{
Q_OBJECT
public:
TestInputMethodInterface()
{
}
~TestInputMethodInterface() override;
private Q_SLOTS:
void initTestCase();
void testAdd();
void testActivate();
void testContext();
void testGrabkeyboard();
void testContentHints_data();
void testContentHints();
void testContentPurpose_data();
void testContentPurpose();
void testKeyboardGrab();
private:
KWayland::Client::ConnectionThread *m_connection;
KWayland::Client::EventQueue *m_queue;
KWayland::Client::Compositor *m_clientCompositor;
KWayland::Client::Seat *m_clientSeat = nullptr;
KWayland::Client::Output *m_output = nullptr;
InputMethodV1 *m_inputMethod;
InputPanel *m_inputPanel;
QThread *m_thread;
KWin::Display m_display;
SeatInterface *m_seat;
CompositorInterface *m_serverCompositor;
std::unique_ptr<FakeOutput> m_outputHandle;
std::unique_ptr<OutputInterface> m_outputInterface;
KWin::InputMethodV1Interface *m_inputMethodIface;
KWin::InputPanelV1Interface *m_inputPanelIface;
QList<SurfaceInterface *> m_surfaces;
};
static const QString s_socketName = QStringLiteral("kwin-wayland-server-inputmethod-test-0");
void TestInputMethodInterface::initTestCase()
{
m_display.addSocketName(s_socketName);
m_display.start();
QVERIFY(m_display.isRunning());
m_seat = new SeatInterface(&m_display, this);
m_serverCompositor = new CompositorInterface(&m_display, this);
m_inputMethodIface = new InputMethodV1Interface(&m_display, this);
m_inputPanelIface = new InputPanelV1Interface(&m_display, this);
m_outputHandle = std::make_unique<FakeOutput>();
m_outputInterface = std::make_unique<OutputInterface>(&m_display, m_outputHandle.get());
connect(m_serverCompositor, &CompositorInterface::surfaceCreated, this, [this](SurfaceInterface *surface) {
m_surfaces += surface;
});
// setup connection
m_connection = new KWayland::Client::ConnectionThread;
QSignalSpy connectedSpy(m_connection, &KWayland::Client::ConnectionThread::connected);
m_connection->setSocketName(s_socketName);
m_thread = new QThread(this);
m_connection->moveToThread(m_thread);
m_thread->start();
m_connection->initConnection();
QVERIFY(connectedSpy.wait());
QVERIFY(!m_connection->connections().isEmpty());
m_queue = new KWayland::Client::EventQueue(this);
QVERIFY(!m_queue->isValid());
m_queue->setup(m_connection);
QVERIFY(m_queue->isValid());
auto registry = new KWayland::Client::Registry(this);
QSignalSpy interfacesSpy(registry, &KWayland::Client::Registry::interfacesAnnounced);
connect(registry, &KWayland::Client::Registry::outputAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_output = new KWayland::Client::Output(this);
m_output->setup(registry->bindOutput(name, version));
});
connect(registry, &KWayland::Client::Registry::interfaceAnnounced, this, [this, registry](const QByteArray &interface, quint32 name, quint32 version) {
if (interface == "zwp_input_panel_v1") {
m_inputPanel = new InputPanel(registry->registry(), name, version);
} else if (interface == "zwp_input_method_v1") {
m_inputMethod = new InputMethodV1(registry->registry(), name, version);
}
});
connect(registry, &KWayland::Client::Registry::seatAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_clientSeat = registry->createSeat(name, version);
});
registry->setEventQueue(m_queue);
QSignalSpy compositorSpy(registry, &KWayland::Client::Registry::compositorAnnounced);
registry->create(m_connection->display());
QVERIFY(registry->isValid());
registry->setup();
wl_display_flush(m_connection->display());
QVERIFY(compositorSpy.wait());
m_clientCompositor = registry->createCompositor(compositorSpy.first().first().value<quint32>(), compositorSpy.first().last().value<quint32>(), this);
QVERIFY(m_clientCompositor->isValid());
QVERIFY(interfacesSpy.count() || interfacesSpy.wait());
QSignalSpy surfaceSpy(m_serverCompositor, &CompositorInterface::surfaceCreated);
for (int i = 0; i < 3; ++i) {
m_clientCompositor->createSurface(this);
}
QVERIFY(surfaceSpy.count() < 3 && surfaceSpy.wait(200));
QVERIFY(m_surfaces.count() == 3);
QVERIFY(m_inputPanel);
QVERIFY(m_output);
}
TestInputMethodInterface::~TestInputMethodInterface()
{
if (m_queue) {
delete m_queue;
m_queue = nullptr;
}
if (m_thread) {
m_thread->quit();
m_thread->wait();
delete m_thread;
m_thread = nullptr;
}
delete m_inputPanel;
delete m_inputMethod;
delete m_inputMethodIface;
delete m_inputPanelIface;
m_connection->deleteLater();
m_connection = nullptr;
}
void TestInputMethodInterface::testAdd()
{
QSignalSpy panelSpy(m_inputPanelIface, &InputPanelV1Interface::inputPanelSurfaceAdded);
QPointer<InputPanelSurfaceV1Interface> panelSurfaceIface;
connect(m_inputPanelIface, &InputPanelV1Interface::inputPanelSurfaceAdded, this, [&panelSurfaceIface](InputPanelSurfaceV1Interface *surface) {
panelSurfaceIface = surface;
});
auto surface = m_clientCompositor->createSurface(this);
auto panelSurface = m_inputPanel->panelForSurface(surface);
QVERIFY(panelSpy.wait() || panelSurfaceIface);
Q_ASSERT(panelSurfaceIface);
Q_ASSERT(panelSurfaceIface->surface() == m_surfaces.constLast());
QSignalSpy panelTopLevelSpy(panelSurfaceIface, &InputPanelSurfaceV1Interface::topLevel);
panelSurface->set_toplevel(*m_output, InputPanelSurface::position_center_bottom);
QVERIFY(panelTopLevelSpy.wait());
}
void TestInputMethodInterface::testActivate()
{
QVERIFY(m_inputMethodIface);
QSignalSpy inputMethodActivateSpy(m_inputMethod, &InputMethodV1::activated);
QSignalSpy inputMethodDeactivateSpy(m_inputMethod, &InputMethodV1::deactivated);
// before sending activate the context should be null
QVERIFY(!m_inputMethodIface->context());
// send activate now
m_inputMethodIface->sendActivate();
QVERIFY(inputMethodActivateSpy.wait());
QCOMPARE(inputMethodActivateSpy.count(), 1);
QVERIFY(m_inputMethodIface->context());
// send deactivate and verify server interface resets context
m_inputMethodIface->sendDeactivate();
QVERIFY(inputMethodDeactivateSpy.wait());
QCOMPARE(inputMethodActivateSpy.count(), 1);
QVERIFY(!m_inputMethodIface->context());
}
void TestInputMethodInterface::testContext()
{
QVERIFY(m_inputMethodIface);
QSignalSpy inputMethodActivateSpy(m_inputMethod, &InputMethodV1::activated);
QSignalSpy inputMethodDeactivateSpy(m_inputMethod, &InputMethodV1::deactivated);
// before sending activate the context should be null
QVERIFY(!m_inputMethodIface->context());
// send activate now
m_inputMethodIface->sendActivate();
QVERIFY(inputMethodActivateSpy.wait());
QCOMPARE(inputMethodActivateSpy.count(), 1);
KWin::InputMethodContextV1Interface *serverContext = m_inputMethodIface->context();
QVERIFY(serverContext);
InputMethodV1Context *imContext = m_inputMethod->context();
QVERIFY(imContext);
quint32 serial = 1;
// commit some text
QSignalSpy commitStringSpy(serverContext, &KWin::InputMethodContextV1Interface::commitString);
imContext->commit_string(serial, "hello");
QVERIFY(commitStringSpy.wait());
QCOMPARE(commitStringSpy.count(), serial);
QCOMPARE(commitStringSpy.last().at(0).value<quint32>(), serial);
QCOMPARE(commitStringSpy.last().at(1).value<QString>(), "hello");
serial++;
// preedit styling event
QSignalSpy preeditStylingSpy(serverContext, &KWin::InputMethodContextV1Interface::preeditStyling);
// protocol does not document 3rd argument mean in much details (styling)
imContext->preedit_styling(0, 5, 1);
QVERIFY(preeditStylingSpy.wait());
QCOMPARE(preeditStylingSpy.count(), 1);
QCOMPARE(preeditStylingSpy.last().at(0).value<quint32>(), 0);
QCOMPARE(preeditStylingSpy.last().at(1).value<quint32>(), 5);
QCOMPARE(preeditStylingSpy.last().at(2).value<quint32>(), 1);
// preedit cursor event
QSignalSpy preeditCursorSpy(serverContext, &KWin::InputMethodContextV1Interface::preeditCursor);
imContext->preedit_cursor(3);
QVERIFY(preeditCursorSpy.wait());
QCOMPARE(preeditCursorSpy.count(), 1);
QCOMPARE(preeditCursorSpy.last().at(0).value<quint32>(), 3);
// commit preedit_string
QSignalSpy preeditStringSpy(serverContext, &KWin::InputMethodContextV1Interface::preeditString);
imContext->preedit_string(serial, "hello", "kde");
QVERIFY(preeditStringSpy.wait());
QCOMPARE(preeditStringSpy.count(), 1);
QCOMPARE(preeditStringSpy.last().at(0).value<quint32>(), serial);
QCOMPARE(preeditStringSpy.last().at(1).value<QString>(), "hello");
QCOMPARE(preeditStringSpy.last().at(2).value<QString>(), "kde");
serial++;
// delete surrounding text
QSignalSpy deleteSurroundingSpy(serverContext, &KWin::InputMethodContextV1Interface::deleteSurroundingText);
imContext->delete_surrounding_text(0, 5);
QVERIFY(deleteSurroundingSpy.wait());
QCOMPARE(deleteSurroundingSpy.count(), 1);
QCOMPARE(deleteSurroundingSpy.last().at(0).value<quint32>(), 0);
QCOMPARE(deleteSurroundingSpy.last().at(1).value<quint32>(), 5);
// set cursor position
QSignalSpy cursorPositionSpy(serverContext, &KWin::InputMethodContextV1Interface::cursorPosition);
imContext->cursor_position(2, 4);
QVERIFY(cursorPositionSpy.wait());
QCOMPARE(cursorPositionSpy.count(), 1);
QCOMPARE(cursorPositionSpy.last().at(0).value<quint32>(), 2);
QCOMPARE(cursorPositionSpy.last().at(1).value<quint32>(), 4);
// invoke action
QSignalSpy invokeActionSpy(imContext, &InputMethodV1Context::invoke_action);
serverContext->sendInvokeAction(3, 5);
QVERIFY(invokeActionSpy.wait());
QCOMPARE(invokeActionSpy.count(), 1);
QCOMPARE(invokeActionSpy.last().at(0).value<quint32>(), 3);
QCOMPARE(invokeActionSpy.last().at(1).value<quint32>(), 5);
// preferred language
QSignalSpy preferredLanguageSpy(imContext, &InputMethodV1Context::preferred_language);
serverContext->sendPreferredLanguage("gu_IN");
QVERIFY(preferredLanguageSpy.wait());
QCOMPARE(preferredLanguageSpy.count(), 1);
QCOMPARE(preferredLanguageSpy.last().at(0).value<QString>(), "gu_IN");
// surrounding text
QSignalSpy surroundingTextSpy(imContext, &InputMethodV1Context::surrounding_text);
serverContext->sendSurroundingText("Hello Plasma!", 2, 4);
QVERIFY(surroundingTextSpy.wait());
QCOMPARE(surroundingTextSpy.count(), 1);
QCOMPARE(surroundingTextSpy.last().at(0).value<QString>(), "Hello Plasma!");
QCOMPARE(surroundingTextSpy.last().at(1).value<quint32>(), 2);
QCOMPARE(surroundingTextSpy.last().at(2).value<quint32>(), 4);
// reset
QSignalSpy resetSpy(imContext, &InputMethodV1Context::reset);
serverContext->sendReset();
QVERIFY(resetSpy.wait());
QCOMPARE(resetSpy.count(), 1);
// send deactivate and verify server interface resets context
m_inputMethodIface->sendDeactivate();
QVERIFY(inputMethodDeactivateSpy.wait());
QCOMPARE(inputMethodActivateSpy.count(), 1);
QVERIFY(!m_inputMethodIface->context());
QVERIFY(!m_inputMethod->context());
}
void TestInputMethodInterface::testGrabkeyboard()
{
QVERIFY(m_inputMethodIface);
QSignalSpy inputMethodActivateSpy(m_inputMethod, &InputMethodV1::activated);
QSignalSpy inputMethodDeactivateSpy(m_inputMethod, &InputMethodV1::deactivated);
// before sending activate the context should be null
QVERIFY(!m_inputMethodIface->context());
// send activate now
m_inputMethodIface->sendActivate();
QVERIFY(inputMethodActivateSpy.wait());
QCOMPARE(inputMethodActivateSpy.count(), 1);
KWin::InputMethodContextV1Interface *serverContext = m_inputMethodIface->context();
QVERIFY(serverContext);
InputMethodV1Context *imContext = m_inputMethod->context();
QVERIFY(imContext);
QSignalSpy keyEventSpy(serverContext, &KWin::InputMethodContextV1Interface::key);
imContext->key(0, 123, 56, 1);
QEXPECT_FAIL("", "We should be not get key event if keyboard is not grabbed", Continue);
QVERIFY(!keyEventSpy.wait(200));
QSignalSpy modifierEventSpy(serverContext, &KWin::InputMethodContextV1Interface::modifiers);
imContext->modifiers(1234, 0, 0, 0, 0);
QEXPECT_FAIL("", "We should be not get modifiers event if keyboard is not grabbed", Continue);
QVERIFY(!modifierEventSpy.wait(200));
// grab the keyboard
wl_keyboard *keyboard = imContext->grab_keyboard();
QVERIFY(keyboard);
// TODO: add more tests about keyboard grab here
// send deactivate and verify server interface resets context
m_inputMethodIface->sendDeactivate();
QVERIFY(inputMethodDeactivateSpy.wait());
QCOMPARE(inputMethodActivateSpy.count(), 1);
QVERIFY(!m_inputMethodIface->context());
QVERIFY(!m_inputMethod->context());
}
void TestInputMethodInterface::testContentHints_data()
{
QTest::addColumn<KWin::TextInputContentHints>("serverHints");
QTest::addColumn<quint32>("imHint");
QTest::addRow("Spellcheck") << TextInputContentHints(TextInputContentHint::AutoCorrection)
<< quint32(QtWaylandServer::zwp_text_input_v1::content_hint_auto_correction);
QTest::addRow("AutoCapital") << TextInputContentHints(TextInputContentHint::AutoCapitalization)
<< quint32(QtWaylandServer::zwp_text_input_v1::content_hint_auto_capitalization);
QTest::addRow("Lowercase") << TextInputContentHints(TextInputContentHint::LowerCase) << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_lowercase);
QTest::addRow("Uppercase") << TextInputContentHints(TextInputContentHint::UpperCase) << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_uppercase);
QTest::addRow("Titlecase") << TextInputContentHints(TextInputContentHint::TitleCase) << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_titlecase);
QTest::addRow("HiddenText") << TextInputContentHints(TextInputContentHint::HiddenText)
<< quint32(QtWaylandServer::zwp_text_input_v1::content_hint_hidden_text);
QTest::addRow("SensitiveData") << TextInputContentHints(TextInputContentHint::SensitiveData)
<< quint32(QtWaylandServer::zwp_text_input_v1::content_hint_sensitive_data);
QTest::addRow("Latin") << TextInputContentHints(TextInputContentHint::Latin) << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_latin);
QTest::addRow("Multiline") << TextInputContentHints(TextInputContentHint::MultiLine) << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_multiline);
QTest::addRow("Auto") << TextInputContentHints(TextInputContentHint::AutoCorrection | TextInputContentHint::AutoCapitalization)
<< quint32(QtWaylandServer::zwp_text_input_v1::content_hint_auto_correction
| QtWaylandServer::zwp_text_input_v1::content_hint_auto_capitalization);
}
void TestInputMethodInterface::testContentHints()
{
QVERIFY(m_inputMethodIface);
QSignalSpy inputMethodActivateSpy(m_inputMethod, &InputMethodV1::activated);
QSignalSpy inputMethodDeactivateSpy(m_inputMethod, &InputMethodV1::deactivated);
// before sending activate the context should be null
QVERIFY(!m_inputMethodIface->context());
// send activate now
m_inputMethodIface->sendActivate();
QVERIFY(inputMethodActivateSpy.wait());
QCOMPARE(inputMethodActivateSpy.count(), 1);
KWin::InputMethodContextV1Interface *serverContext = m_inputMethodIface->context();
QVERIFY(serverContext);
InputMethodV1Context *imContext = m_inputMethod->context();
QVERIFY(imContext);
QSignalSpy contentTypeChangedSpy(imContext, &InputMethodV1Context::content_type_changed);
QFETCH(KWin::TextInputContentHints, serverHints);
serverContext->sendContentType(serverHints, KWin::TextInputContentPurpose::Normal);
QVERIFY(contentTypeChangedSpy.wait());
QCOMPARE(contentTypeChangedSpy.count(), 1);
QEXPECT_FAIL("SensitiveData", "SensitiveData content hint need fixing", Continue);
QTEST(imContext->contentHints(), "imHint");
// send deactivate and verify server interface resets context
m_inputMethodIface->sendDeactivate();
QVERIFY(inputMethodDeactivateSpy.wait());
QCOMPARE(inputMethodActivateSpy.count(), 1);
QVERIFY(!m_inputMethodIface->context());
QVERIFY(!m_inputMethod->context());
}
void TestInputMethodInterface::testContentPurpose_data()
{
QTest::addColumn<KWin::TextInputContentPurpose>("serverPurpose");
QTest::addColumn<quint32>("imPurpose");
QTest::newRow("Alpha") << TextInputContentPurpose::Alpha << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_alpha);
QTest::newRow("Digits") << TextInputContentPurpose::Digits << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_digits);
QTest::newRow("Number") << TextInputContentPurpose::Number << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_number);
QTest::newRow("Phone") << TextInputContentPurpose::Phone << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_phone);
QTest::newRow("Url") << TextInputContentPurpose::Url << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_url);
QTest::newRow("Email") << TextInputContentPurpose::Email << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_email);
QTest::newRow("Name") << TextInputContentPurpose::Name << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_name);
QTest::newRow("Password") << TextInputContentPurpose::Password << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_password);
QTest::newRow("Date") << TextInputContentPurpose::Date << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_date);
QTest::newRow("Time") << TextInputContentPurpose::Time << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_time);
QTest::newRow("DateTime") << TextInputContentPurpose::DateTime << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_datetime);
QTest::newRow("Terminal") << TextInputContentPurpose::Terminal << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_terminal);
QTest::newRow("Normal") << TextInputContentPurpose::Normal << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_normal);
QTest::newRow("Pin") << TextInputContentPurpose::Pin << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_password);
}
void TestInputMethodInterface::testContentPurpose()
{
QVERIFY(m_inputMethodIface);
QSignalSpy inputMethodActivateSpy(m_inputMethod, &InputMethodV1::activated);
QSignalSpy inputMethodDeactivateSpy(m_inputMethod, &InputMethodV1::deactivated);
// before sending activate the context should be null
QVERIFY(!m_inputMethodIface->context());
// send activate now
m_inputMethodIface->sendActivate();
QVERIFY(inputMethodActivateSpy.wait());
QCOMPARE(inputMethodActivateSpy.count(), 1);
KWin::InputMethodContextV1Interface *serverContext = m_inputMethodIface->context();
QVERIFY(serverContext);
InputMethodV1Context *imContext = m_inputMethod->context();
QVERIFY(imContext);
QSignalSpy contentTypeChangedSpy(imContext, &InputMethodV1Context::content_type_changed);
QFETCH(KWin::TextInputContentPurpose, serverPurpose);
serverContext->sendContentType(KWin::TextInputContentHints(KWin::TextInputContentHint::None), serverPurpose);
QVERIFY(contentTypeChangedSpy.wait());
QCOMPARE(contentTypeChangedSpy.count(), 1);
QEXPECT_FAIL("Pin", "Pin should return content_purpose_password", Continue);
QTEST(imContext->contentPurpose(), "imPurpose");
// send deactivate and verify server interface resets context
m_inputMethodIface->sendDeactivate();
QVERIFY(inputMethodDeactivateSpy.wait());
QCOMPARE(inputMethodActivateSpy.count(), 1);
QVERIFY(!m_inputMethodIface->context());
QVERIFY(!m_inputMethod->context());
}
void TestInputMethodInterface::testKeyboardGrab()
{
QVERIFY(m_inputMethodIface);
QSignalSpy inputMethodActivateSpy(m_inputMethod, &InputMethodV1::activated);
m_inputMethodIface->sendActivate();
QVERIFY(inputMethodActivateSpy.wait());
QSignalSpy keyboardGrabSpy(m_inputMethodIface->context(), &InputMethodContextV1Interface::keyboardGrabRequested);
InputMethodV1Context *imContext = m_inputMethod->context();
QVERIFY(imContext);
KWayland::Client::Keyboard *keyboard = new KWayland::Client::Keyboard(this);
keyboard->setup(imContext->grab_keyboard());
QVERIFY(keyboard->isValid());
QVERIFY(keyboardGrabSpy.count() || keyboardGrabSpy.wait());
QSignalSpy keyboardSpy(keyboard, &KWayland::Client::Keyboard::keyChanged);
m_inputMethodIface->context()->keyboardGrab()->sendKey(0, 0, KEY_F1, KeyboardKeyState::Pressed);
m_inputMethodIface->context()->keyboardGrab()->sendKey(0, 0, KEY_F1, KeyboardKeyState::Released);
keyboardSpy.wait();
QCOMPARE(keyboardSpy.count(), 2);
m_inputMethodIface->sendDeactivate();
}
QTEST_GUILESS_MAIN(TestInputMethodInterface)
#include "test_inputmethod_interface.moc"