You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

771 lines
33 KiB
C++

/*
SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
// Qt
#include <QSignalSpy>
#include <QTest>
// client
#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/registry.h"
#include "KWayland/Client/seat.h"
#include "KWayland/Client/surface.h"
#include "KWayland/Client/textinput.h"
// server
#include "wayland/compositor.h"
#include "wayland/display.h"
#include "wayland/seat.h"
#include "wayland/textinput.h"
#include "wayland/textinput_v2.h"
using namespace KWin;
using namespace std::literals;
class TextInputTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void init();
void cleanup();
void testEnterLeave_data();
void testEnterLeave();
void testFocusedBeforeCreateTextInput();
void testShowHidePanel();
void testCursorRectangle();
void testPreferredLanguage();
void testReset();
void testSurroundingText();
void testContentHints_data();
void testContentHints();
void testContentPurpose_data();
void testContentPurpose();
void testTextDirection_data();
void testTextDirection();
void testLanguage();
void testKeyEvent();
void testPreEdit();
void testCommit();
private:
SurfaceInterface *waitForSurface();
KWayland::Client::TextInput *createTextInput();
KWin::Display *m_display = nullptr;
SeatInterface *m_seatInterface = nullptr;
CompositorInterface *m_compositorInterface = nullptr;
TextInputManagerV2Interface *m_textInputManagerV2Interface = nullptr;
KWayland::Client::ConnectionThread *m_connection = nullptr;
QThread *m_thread = nullptr;
KWayland::Client::EventQueue *m_queue = nullptr;
KWayland::Client::Seat *m_seat = nullptr;
KWayland::Client::Keyboard *m_keyboard = nullptr;
KWayland::Client::Compositor *m_compositor = nullptr;
KWayland::Client::TextInputManager *m_textInputManagerV2 = nullptr;
};
static const QString s_socketName = QStringLiteral("kwayland-test-text-input-0");
void TextInputTest::init()
{
delete m_display;
m_display = new KWin::Display(this);
m_display->addSocketName(s_socketName);
m_display->start();
QVERIFY(m_display->isRunning());
m_display->createShm();
m_seatInterface = new SeatInterface(m_display, m_display);
m_seatInterface->setHasKeyboard(true);
m_seatInterface->setHasTouch(true);
m_compositorInterface = new CompositorInterface(m_display, m_display);
m_textInputManagerV2Interface = new TextInputManagerV2Interface(m_display, m_display);
// 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());
m_queue = new KWayland::Client::EventQueue(this);
m_queue->setup(m_connection);
KWayland::Client::Registry registry;
QSignalSpy interfacesAnnouncedSpy(&registry, &KWayland::Client::Registry::interfacesAnnounced);
registry.setEventQueue(m_queue);
registry.create(m_connection);
QVERIFY(registry.isValid());
registry.setup();
QVERIFY(interfacesAnnouncedSpy.wait());
m_seat = registry.createSeat(registry.interface(KWayland::Client::Registry::Interface::Seat).name, registry.interface(KWayland::Client::Registry::Interface::Seat).version, this);
QVERIFY(m_seat->isValid());
QSignalSpy hasKeyboardSpy(m_seat, &KWayland::Client::Seat::hasKeyboardChanged);
QVERIFY(hasKeyboardSpy.wait());
m_keyboard = m_seat->createKeyboard(this);
QVERIFY(m_keyboard->isValid());
m_compositor =
registry.createCompositor(registry.interface(KWayland::Client::Registry::Interface::Compositor).name, registry.interface(KWayland::Client::Registry::Interface::Compositor).version, this);
QVERIFY(m_compositor->isValid());
m_textInputManagerV2 = registry.createTextInputManager(registry.interface(KWayland::Client::Registry::Interface::TextInputManagerUnstableV2).name,
registry.interface(KWayland::Client::Registry::Interface::TextInputManagerUnstableV2).version,
this);
QVERIFY(m_textInputManagerV2->isValid());
}
void TextInputTest::cleanup()
{
#define CLEANUP(variable) \
if (variable) { \
delete variable; \
variable = nullptr; \
}
CLEANUP(m_textInputManagerV2)
CLEANUP(m_keyboard)
CLEANUP(m_seat)
CLEANUP(m_compositor)
CLEANUP(m_queue)
if (m_connection) {
m_connection->deleteLater();
m_connection = nullptr;
}
if (m_thread) {
m_thread->quit();
m_thread->wait();
delete m_thread;
m_thread = nullptr;
}
CLEANUP(m_display)
#undef CLEANUP
// these are the children of the display
m_textInputManagerV2Interface = nullptr;
m_compositorInterface = nullptr;
m_seatInterface = nullptr;
}
SurfaceInterface *TextInputTest::waitForSurface()
{
QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated);
if (!surfaceCreatedSpy.isValid()) {
return nullptr;
}
if (!surfaceCreatedSpy.wait(500)) {
return nullptr;
}
if (surfaceCreatedSpy.count() != 1) {
return nullptr;
}
return surfaceCreatedSpy.first().first().value<SurfaceInterface *>();
}
KWayland::Client::TextInput *TextInputTest::createTextInput()
{
return m_textInputManagerV2->createTextInput(m_seat);
}
void TextInputTest::testEnterLeave_data()
{
QTest::addColumn<bool>("updatesDirectly");
QTest::newRow("UnstableV2") << true;
}
void TextInputTest::testEnterLeave()
{
// this test verifies that enter leave are sent correctly
std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput());
auto serverSurface = waitForSurface();
QVERIFY(serverSurface);
QVERIFY(textInput != nullptr);
QSignalSpy enteredSpy(textInput.get(), &KWayland::Client::TextInput::entered);
QSignalSpy leftSpy(textInput.get(), &KWayland::Client::TextInput::left);
QSignalSpy textInputChangedSpy(m_seatInterface, &SeatInterface::focusedTextInputSurfaceChanged);
// now let's try to enter it
QVERIFY(!m_seatInterface->focusedTextInputSurface());
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
QCOMPARE(m_seatInterface->focusedTextInputSurface(), serverSurface);
// text input not yet set for the surface
QFETCH(bool, updatesDirectly);
QCOMPARE(bool(m_seatInterface->textInputV2()), updatesDirectly);
QCOMPARE(textInputChangedSpy.isEmpty(), !updatesDirectly);
textInput->enable(surface.get());
// this should trigger on server side
if (!updatesDirectly) {
QVERIFY(textInputChangedSpy.wait());
}
QCOMPARE(textInputChangedSpy.count(), 1);
auto serverTextInput = m_seatInterface->textInputV2();
QVERIFY(serverTextInput);
QSignalSpy enabledChangedSpy(serverTextInput, &TextInputV2Interface::enabledChanged);
if (updatesDirectly) {
QVERIFY(enabledChangedSpy.wait());
enabledChangedSpy.clear();
}
QCOMPARE(serverTextInput->surface().data(), serverSurface);
QVERIFY(serverTextInput->isEnabled());
// and trigger an enter
if (enteredSpy.isEmpty()) {
QVERIFY(enteredSpy.wait());
}
QCOMPARE(enteredSpy.count(), 1);
QCOMPARE(textInput->enteredSurface(), surface.get());
// now trigger a leave
m_seatInterface->setFocusedKeyboardSurface(nullptr);
QCOMPARE(textInputChangedSpy.count(), 2);
QVERIFY(leftSpy.wait());
QVERIFY(!textInput->enteredSurface());
QVERIFY(!serverTextInput->isEnabled());
// if we enter again we should directly get the text input as it's still activated
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
QCOMPARE(textInputChangedSpy.count(), 3);
QVERIFY(m_seatInterface->textInputV2());
QVERIFY(enteredSpy.wait());
QCOMPARE(enteredSpy.count(), 2);
QCOMPARE(textInput->enteredSurface(), surface.get());
QVERIFY(serverTextInput->isEnabled());
// let's deactivate on client side
textInput->disable(surface.get());
QVERIFY(enabledChangedSpy.wait());
QCOMPARE(enabledChangedSpy.count(), 3);
QVERIFY(!serverTextInput->isEnabled());
// does not trigger a leave
QCOMPARE(textInputChangedSpy.count(), 3);
// should still be the same text input
QCOMPARE(m_seatInterface->textInputV2(), serverTextInput);
// reset
textInput->enable(surface.get());
QVERIFY(enabledChangedSpy.wait());
// delete the client and wait for the server to catch up
QSignalSpy unboundSpy(serverSurface, &QObject::destroyed);
surface.reset();
QVERIFY(unboundSpy.wait());
QVERIFY(leftSpy.wait());
QVERIFY(!textInput->enteredSurface());
}
void TextInputTest::testFocusedBeforeCreateTextInput()
{
// this test verifies that enter leave are sent correctly
std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
auto serverSurface = waitForSurface();
// now let's try to enter it
QSignalSpy textInputChangedSpy(m_seatInterface, &SeatInterface::focusedTextInputSurfaceChanged);
QVERIFY(!m_seatInterface->focusedTextInputSurface());
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
QCOMPARE(m_seatInterface->focusedTextInputSurface(), serverSurface);
QCOMPARE(textInputChangedSpy.count(), 1);
// This is null because there is no text input object for this client.
QCOMPARE(m_seatInterface->textInputV2()->surface(), nullptr);
QVERIFY(serverSurface);
std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput());
QVERIFY(textInput != nullptr);
QSignalSpy enteredSpy(textInput.get(), &KWayland::Client::TextInput::entered);
QSignalSpy leftSpy(textInput.get(), &KWayland::Client::TextInput::left);
// and trigger an enter
if (enteredSpy.isEmpty()) {
QVERIFY(enteredSpy.wait());
}
QCOMPARE(enteredSpy.count(), 1);
QCOMPARE(textInput->enteredSurface(), surface.get());
// This is not null anymore because there is a text input object associated with it.
QCOMPARE(m_seatInterface->textInputV2()->surface(), serverSurface);
// now trigger a leave
m_seatInterface->setFocusedKeyboardSurface(nullptr);
QCOMPARE(textInputChangedSpy.count(), 2);
QVERIFY(leftSpy.wait());
QVERIFY(!textInput->enteredSurface());
QCOMPARE(m_seatInterface->textInputV2()->surface(), nullptr);
QCOMPARE(m_seatInterface->focusedTextInputSurface(), nullptr);
}
void TextInputTest::testShowHidePanel()
{
// this test verifies that the requests for show/hide panel work
// and that status is properly sent to the client
std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
auto serverSurface = waitForSurface();
QVERIFY(serverSurface);
std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput());
QVERIFY(textInput != nullptr);
textInput->enable(surface.get());
m_connection->flush();
m_display->dispatchEvents();
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
auto ti = m_seatInterface->textInputV2();
QVERIFY(ti);
QSignalSpy showPanelRequestedSpy(ti, &TextInputV2Interface::requestShowInputPanel);
QSignalSpy hidePanelRequestedSpy(ti, &TextInputV2Interface::requestHideInputPanel);
QSignalSpy inputPanelStateChangedSpy(textInput.get(), &KWayland::Client::TextInput::inputPanelStateChanged);
QCOMPARE(textInput->isInputPanelVisible(), false);
textInput->showInputPanel();
QVERIFY(showPanelRequestedSpy.wait());
ti->setInputPanelState(true, QRect(0, 0, 0, 0));
QVERIFY(inputPanelStateChangedSpy.wait());
QCOMPARE(textInput->isInputPanelVisible(), true);
textInput->hideInputPanel();
QVERIFY(hidePanelRequestedSpy.wait());
ti->setInputPanelState(false, QRect(0, 0, 0, 0));
QVERIFY(inputPanelStateChangedSpy.wait());
QCOMPARE(textInput->isInputPanelVisible(), false);
}
void TextInputTest::testCursorRectangle()
{
// this test verifies that passing the cursor rectangle from client to server works
// and that setting visibility state from server to client works
std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
auto serverSurface = waitForSurface();
QVERIFY(serverSurface);
std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput());
QVERIFY(textInput != nullptr);
textInput->enable(surface.get());
m_connection->flush();
m_display->dispatchEvents();
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
auto ti = m_seatInterface->textInputV2();
QVERIFY(ti);
QCOMPARE(ti->cursorRectangle(), QRect());
QSignalSpy cursorRectangleChangedSpy(ti, &TextInputV2Interface::cursorRectangleChanged);
textInput->setCursorRectangle(QRect(10, 20, 30, 40));
QVERIFY(cursorRectangleChangedSpy.wait());
QCOMPARE(ti->cursorRectangle(), QRect(10, 20, 30, 40));
}
void TextInputTest::testPreferredLanguage()
{
// this test verifies that passing the preferred language from client to server works
std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
auto serverSurface = waitForSurface();
QVERIFY(serverSurface);
std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput());
QVERIFY(textInput != nullptr);
textInput->enable(surface.get());
m_connection->flush();
m_display->dispatchEvents();
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
auto ti = m_seatInterface->textInputV2();
QVERIFY(ti);
QVERIFY(ti->preferredLanguage().isEmpty());
QSignalSpy preferredLanguageChangedSpy(ti, &TextInputV2Interface::preferredLanguageChanged);
textInput->setPreferredLanguage(QStringLiteral("foo"));
QVERIFY(preferredLanguageChangedSpy.wait());
QCOMPARE(ti->preferredLanguage(), QStringLiteral("foo").toUtf8());
}
void TextInputTest::testReset()
{
// this test verifies that the reset request is properly passed from client to server
std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
auto serverSurface = waitForSurface();
QVERIFY(serverSurface);
std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput());
QVERIFY(textInput != nullptr);
textInput->enable(surface.get());
m_connection->flush();
m_display->dispatchEvents();
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
auto ti = m_seatInterface->textInputV2();
QVERIFY(ti);
QSignalSpy stateUpdatedSpy(ti, &TextInputV2Interface::stateUpdated);
textInput->reset();
QVERIFY(stateUpdatedSpy.wait());
}
void TextInputTest::testSurroundingText()
{
// this test verifies that surrounding text is properly passed around
std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
auto serverSurface = waitForSurface();
QVERIFY(serverSurface);
std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput());
QVERIFY(textInput != nullptr);
textInput->enable(surface.get());
m_connection->flush();
m_display->dispatchEvents();
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
auto ti = m_seatInterface->textInputV2();
QVERIFY(ti);
QVERIFY(ti->surroundingText().isEmpty());
QCOMPARE(ti->surroundingTextCursorPosition(), 0);
QCOMPARE(ti->surroundingTextSelectionAnchor(), 0);
QSignalSpy surroundingTextChangedSpy(ti, &TextInputV2Interface::surroundingTextChanged);
textInput->setSurroundingText(QStringLiteral("100 €, 100 $"), 5, 6);
QVERIFY(surroundingTextChangedSpy.wait());
QCOMPARE(ti->surroundingText(), QStringLiteral("100 €, 100 $").toUtf8());
QCOMPARE(ti->surroundingTextCursorPosition(), QStringLiteral("100 €, 100 $").toUtf8().indexOf(','));
QCOMPARE(ti->surroundingTextSelectionAnchor(), QStringLiteral("100 €, 100 $").toUtf8().indexOf(' ', ti->surroundingTextCursorPosition()));
}
void TextInputTest::testContentHints_data()
{
QTest::addColumn<KWayland::Client::TextInput::ContentHints>("clientHints");
QTest::addColumn<KWin::TextInputContentHints>("serverHints");
QTest::newRow("completion/v2") << KWayland::Client::TextInput::ContentHints(KWayland::Client::TextInput::ContentHint::AutoCompletion)
<< KWin::TextInputContentHints(KWin::TextInputContentHint::AutoCompletion);
QTest::newRow("Correction/v2") << KWayland::Client::TextInput::ContentHints(KWayland::Client::TextInput::ContentHint::AutoCorrection)
<< KWin::TextInputContentHints(KWin::TextInputContentHint::AutoCorrection);
QTest::newRow("Capitalization/v2") << KWayland::Client::TextInput::ContentHints(KWayland::Client::TextInput::ContentHint::AutoCapitalization)
<< KWin::TextInputContentHints(KWin::TextInputContentHint::AutoCapitalization);
QTest::newRow("Lowercase/v2") << KWayland::Client::TextInput::ContentHints(KWayland::Client::TextInput::ContentHint::LowerCase)
<< KWin::TextInputContentHints(KWin::TextInputContentHint::LowerCase);
QTest::newRow("Uppercase/v2") << KWayland::Client::TextInput::ContentHints(KWayland::Client::TextInput::ContentHint::UpperCase)
<< KWin::TextInputContentHints(KWin::TextInputContentHint::UpperCase);
QTest::newRow("Titlecase/v2") << KWayland::Client::TextInput::ContentHints(KWayland::Client::TextInput::ContentHint::TitleCase)
<< KWin::TextInputContentHints(KWin::TextInputContentHint::TitleCase);
QTest::newRow("HiddenText/v2") << KWayland::Client::TextInput::ContentHints(KWayland::Client::TextInput::ContentHint::HiddenText)
<< KWin::TextInputContentHints(KWin::TextInputContentHint::HiddenText);
QTest::newRow("SensitiveData/v2") << KWayland::Client::TextInput::ContentHints(KWayland::Client::TextInput::ContentHint::SensitiveData)
<< KWin::TextInputContentHints(KWin::TextInputContentHint::SensitiveData);
QTest::newRow("Latin/v2") << KWayland::Client::TextInput::ContentHints(KWayland::Client::TextInput::ContentHint::Latin)
<< KWin::TextInputContentHints(KWin::TextInputContentHint::Latin);
QTest::newRow("Multiline/v2") << KWayland::Client::TextInput::ContentHints(KWayland::Client::TextInput::ContentHint::MultiLine)
<< KWin::TextInputContentHints(KWin::TextInputContentHint::MultiLine);
QTest::newRow("autos/v2") << (KWayland::Client::TextInput::ContentHint::AutoCompletion | KWayland::Client::TextInput::ContentHint::AutoCorrection | KWayland::Client::TextInput::ContentHint::AutoCapitalization)
<< (KWin::TextInputContentHint::AutoCompletion | KWin::TextInputContentHint::AutoCorrection
| KWin::TextInputContentHint::AutoCapitalization);
// all has combinations which don't make sense - what's both lowercase and uppercase?
QTest::newRow("all/v2") << (KWayland::Client::TextInput::ContentHint::AutoCompletion | KWayland::Client::TextInput::ContentHint::AutoCorrection | KWayland::Client::TextInput::ContentHint::AutoCapitalization
| KWayland::Client::TextInput::ContentHint::LowerCase | KWayland::Client::TextInput::ContentHint::UpperCase | KWayland::Client::TextInput::ContentHint::TitleCase
| KWayland::Client::TextInput::ContentHint::HiddenText | KWayland::Client::TextInput::ContentHint::SensitiveData | KWayland::Client::TextInput::ContentHint::Latin
| KWayland::Client::TextInput::ContentHint::MultiLine)
<< (KWin::TextInputContentHint::AutoCompletion | KWin::TextInputContentHint::AutoCorrection
| KWin::TextInputContentHint::AutoCapitalization | KWin::TextInputContentHint::LowerCase
| KWin::TextInputContentHint::UpperCase | KWin::TextInputContentHint::TitleCase
| KWin::TextInputContentHint::HiddenText | KWin::TextInputContentHint::SensitiveData
| KWin::TextInputContentHint::Latin | KWin::TextInputContentHint::MultiLine);
}
void TextInputTest::testContentHints()
{
// this test verifies that content hints are properly passed from client to server
std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
auto serverSurface = waitForSurface();
QVERIFY(serverSurface);
std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput());
QVERIFY(textInput != nullptr);
textInput->enable(surface.get());
m_connection->flush();
m_display->dispatchEvents();
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
auto ti = m_seatInterface->textInputV2();
QVERIFY(ti);
QCOMPARE(ti->contentHints(), KWin::TextInputContentHints());
QSignalSpy contentTypeChangedSpy(ti, &TextInputV2Interface::contentTypeChanged);
QFETCH(KWayland::Client::TextInput::ContentHints, clientHints);
textInput->setContentType(clientHints, KWayland::Client::TextInput::ContentPurpose::Normal);
QVERIFY(contentTypeChangedSpy.wait());
QTEST(ti->contentHints(), "serverHints");
// setting to same should not trigger an update
textInput->setContentType(clientHints, KWayland::Client::TextInput::ContentPurpose::Normal);
QVERIFY(!contentTypeChangedSpy.wait(100));
// unsetting should work
textInput->setContentType(KWayland::Client::TextInput::ContentHints(), KWayland::Client::TextInput::ContentPurpose::Normal);
QVERIFY(contentTypeChangedSpy.wait());
QCOMPARE(ti->contentHints(), KWin::TextInputContentHints());
}
void TextInputTest::testContentPurpose_data()
{
QTest::addColumn<KWayland::Client::TextInput::ContentPurpose>("clientPurpose");
QTest::addColumn<KWin::TextInputContentPurpose>("serverPurpose");
QTest::newRow("Alpha/v2") << KWayland::Client::TextInput::ContentPurpose::Alpha << KWin::TextInputContentPurpose::Alpha;
QTest::newRow("Digits/v2") << KWayland::Client::TextInput::ContentPurpose::Digits << KWin::TextInputContentPurpose::Digits;
QTest::newRow("Number/v2") << KWayland::Client::TextInput::ContentPurpose::Number << KWin::TextInputContentPurpose::Number;
QTest::newRow("Phone/v2") << KWayland::Client::TextInput::ContentPurpose::Phone << KWin::TextInputContentPurpose::Phone;
QTest::newRow("Url/v2") << KWayland::Client::TextInput::ContentPurpose::Url << KWin::TextInputContentPurpose::Url;
QTest::newRow("Email/v2") << KWayland::Client::TextInput::ContentPurpose::Email << KWin::TextInputContentPurpose::Email;
QTest::newRow("Name/v2") << KWayland::Client::TextInput::ContentPurpose::Name << KWin::TextInputContentPurpose::Name;
QTest::newRow("Password/v2") << KWayland::Client::TextInput::ContentPurpose::Password << KWin::TextInputContentPurpose::Password;
QTest::newRow("Date/v2") << KWayland::Client::TextInput::ContentPurpose::Date << KWin::TextInputContentPurpose::Date;
QTest::newRow("Time/v2") << KWayland::Client::TextInput::ContentPurpose::Time << KWin::TextInputContentPurpose::Time;
QTest::newRow("Datetime/v2") << KWayland::Client::TextInput::ContentPurpose::DateTime << KWin::TextInputContentPurpose::DateTime;
QTest::newRow("Terminal/v2") << KWayland::Client::TextInput::ContentPurpose::Terminal << KWin::TextInputContentPurpose::Terminal;
}
void TextInputTest::testContentPurpose()
{
// this test verifies that content purpose are properly passed from client to server
std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
auto serverSurface = waitForSurface();
QVERIFY(serverSurface);
std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput());
QVERIFY(textInput != nullptr);
textInput->enable(surface.get());
m_connection->flush();
m_display->dispatchEvents();
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
auto ti = m_seatInterface->textInputV2();
QVERIFY(ti);
QCOMPARE(ti->contentPurpose(), KWin::TextInputContentPurpose::Normal);
QSignalSpy contentTypeChangedSpy(ti, &TextInputV2Interface::contentTypeChanged);
QFETCH(KWayland::Client::TextInput::ContentPurpose, clientPurpose);
textInput->setContentType(KWayland::Client::TextInput::ContentHints(), clientPurpose);
QVERIFY(contentTypeChangedSpy.wait());
QTEST(ti->contentPurpose(), "serverPurpose");
// setting to same should not trigger an update
textInput->setContentType(KWayland::Client::TextInput::ContentHints(), clientPurpose);
QVERIFY(!contentTypeChangedSpy.wait(100));
// unsetting should work
textInput->setContentType(KWayland::Client::TextInput::ContentHints(), KWayland::Client::TextInput::ContentPurpose::Normal);
QVERIFY(contentTypeChangedSpy.wait());
QCOMPARE(ti->contentPurpose(), KWin::TextInputContentPurpose::Normal);
}
void TextInputTest::testTextDirection_data()
{
QTest::addColumn<Qt::LayoutDirection>("textDirection");
QTest::newRow("ltr/v0") << Qt::LeftToRight;
QTest::newRow("rtl/v0") << Qt::RightToLeft;
QTest::newRow("ltr/v2") << Qt::LeftToRight;
QTest::newRow("rtl/v2") << Qt::RightToLeft;
}
void TextInputTest::testTextDirection()
{
// this test verifies that the text direction is sent from server to client
std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
auto serverSurface = waitForSurface();
QVERIFY(serverSurface);
std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput());
QVERIFY(textInput != nullptr);
// default should be auto
QCOMPARE(textInput->textDirection(), Qt::LayoutDirectionAuto);
textInput->enable(surface.get());
m_connection->flush();
m_display->dispatchEvents();
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
auto ti = m_seatInterface->textInputV2();
QVERIFY(ti);
// let's send the new text direction
QSignalSpy textDirectionChangedSpy(textInput.get(), &KWayland::Client::TextInput::textDirectionChanged);
QFETCH(Qt::LayoutDirection, textDirection);
ti->setTextDirection(textDirection);
QVERIFY(textDirectionChangedSpy.wait());
QCOMPARE(textInput->textDirection(), textDirection);
// setting again should not change
ti->setTextDirection(textDirection);
QVERIFY(!textDirectionChangedSpy.wait(100));
// setting back to auto
ti->setTextDirection(Qt::LayoutDirectionAuto);
QVERIFY(textDirectionChangedSpy.wait());
QCOMPARE(textInput->textDirection(), Qt::LayoutDirectionAuto);
}
void TextInputTest::testLanguage()
{
// this test verifies that language is sent from server to client
std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
auto serverSurface = waitForSurface();
QVERIFY(serverSurface);
std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput());
QVERIFY(textInput != nullptr);
// default should be empty
QVERIFY(textInput->language().isEmpty());
textInput->enable(surface.get());
m_connection->flush();
m_display->dispatchEvents();
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
auto ti = m_seatInterface->textInputV2();
QVERIFY(ti);
// let's send the new language
QSignalSpy langugageChangedSpy(textInput.get(), &KWayland::Client::TextInput::languageChanged);
ti->setLanguage(QByteArrayLiteral("foo"));
QVERIFY(langugageChangedSpy.wait());
QCOMPARE(textInput->language(), QByteArrayLiteral("foo"));
// setting to same should not trigger
ti->setLanguage(QByteArrayLiteral("foo"));
QVERIFY(!langugageChangedSpy.wait(100));
// but to something else should trigger again
ti->setLanguage(QByteArrayLiteral("bar"));
QVERIFY(langugageChangedSpy.wait());
QCOMPARE(textInput->language(), QByteArrayLiteral("bar"));
}
void TextInputTest::testKeyEvent()
{
qRegisterMetaType<Qt::KeyboardModifiers>();
qRegisterMetaType<KWayland::Client::TextInput::KeyState>();
// this test verifies that key events are properly sent to the client
std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
auto serverSurface = waitForSurface();
QVERIFY(serverSurface);
std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput());
QVERIFY(textInput != nullptr);
textInput->enable(surface.get());
m_connection->flush();
m_display->dispatchEvents();
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
auto ti = m_seatInterface->textInputV2();
QVERIFY(ti);
// TODO: test modifiers
QSignalSpy keyEventSpy(textInput.get(), &KWayland::Client::TextInput::keyEvent);
m_seatInterface->setTimestamp(100ms);
ti->keysymPressed(2);
QVERIFY(keyEventSpy.wait());
QCOMPARE(keyEventSpy.count(), 1);
QCOMPARE(keyEventSpy.last().at(0).value<quint32>(), 2u);
QCOMPARE(keyEventSpy.last().at(1).value<KWayland::Client::TextInput::KeyState>(), KWayland::Client::TextInput::KeyState::Pressed);
QCOMPARE(keyEventSpy.last().at(2).value<Qt::KeyboardModifiers>(), Qt::KeyboardModifiers());
QCOMPARE(keyEventSpy.last().at(3).value<quint32>(), 100u);
m_seatInterface->setTimestamp(101ms);
ti->keysymReleased(2);
QVERIFY(keyEventSpy.wait());
QCOMPARE(keyEventSpy.count(), 2);
QCOMPARE(keyEventSpy.last().at(0).value<quint32>(), 2u);
QCOMPARE(keyEventSpy.last().at(1).value<KWayland::Client::TextInput::KeyState>(), KWayland::Client::TextInput::KeyState::Released);
QCOMPARE(keyEventSpy.last().at(2).value<Qt::KeyboardModifiers>(), Qt::KeyboardModifiers());
QCOMPARE(keyEventSpy.last().at(3).value<quint32>(), 101u);
}
void TextInputTest::testPreEdit()
{
// this test verifies that pre-edit is correctly passed to the client
std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
auto serverSurface = waitForSurface();
QVERIFY(serverSurface);
std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput());
QVERIFY(textInput != nullptr);
// verify default values
QVERIFY(textInput->composingText().isEmpty());
QVERIFY(textInput->composingFallbackText().isEmpty());
QCOMPARE(textInput->composingTextCursorPosition(), 0);
textInput->enable(surface.get());
m_connection->flush();
m_display->dispatchEvents();
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
auto ti = m_seatInterface->textInputV2();
QVERIFY(ti);
// now let's pass through some pre-edit events
QSignalSpy composingTextChangedSpy(textInput.get(), &KWayland::Client::TextInput::composingTextChanged);
ti->setPreEditCursor(1);
ti->preEdit(QByteArrayLiteral("foo"), QByteArrayLiteral("bar"));
QVERIFY(composingTextChangedSpy.wait());
QCOMPARE(composingTextChangedSpy.count(), 1);
QCOMPARE(textInput->composingText(), QByteArrayLiteral("foo"));
QCOMPARE(textInput->composingFallbackText(), QByteArrayLiteral("bar"));
QCOMPARE(textInput->composingTextCursorPosition(), 1);
// when no pre edit cursor is sent, it's at end of text
ti->preEdit(QByteArrayLiteral("foobar"), QByteArray());
QVERIFY(composingTextChangedSpy.wait());
QCOMPARE(composingTextChangedSpy.count(), 2);
QCOMPARE(textInput->composingText(), QByteArrayLiteral("foobar"));
QCOMPARE(textInput->composingFallbackText(), QByteArray());
QCOMPARE(textInput->composingTextCursorPosition(), 6);
}
void TextInputTest::testCommit()
{
// this test verifies that the commit is handled correctly by the client
std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
auto serverSurface = waitForSurface();
QVERIFY(serverSurface);
std::unique_ptr<KWayland::Client::TextInput> textInput(createTextInput());
QVERIFY(textInput != nullptr);
// verify default values
QCOMPARE(textInput->commitText(), QByteArray());
QCOMPARE(textInput->cursorPosition(), 0);
QCOMPARE(textInput->anchorPosition(), 0);
QCOMPARE(textInput->deleteSurroundingText().beforeLength, 0u);
QCOMPARE(textInput->deleteSurroundingText().afterLength, 0u);
textInput->enable(surface.get());
m_connection->flush();
m_display->dispatchEvents();
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
auto ti = m_seatInterface->textInputV2();
QVERIFY(ti);
// now let's commit
QSignalSpy committedSpy(textInput.get(), &KWayland::Client::TextInput::committed);
ti->setCursorPosition(3, 4);
ti->deleteSurroundingText(2, 1);
ti->commitString(QByteArrayLiteral("foo"));
QVERIFY(committedSpy.wait());
QCOMPARE(textInput->commitText(), QByteArrayLiteral("foo"));
QCOMPARE(textInput->cursorPosition(), 3);
QCOMPARE(textInput->anchorPosition(), 4);
QCOMPARE(textInput->deleteSurroundingText().beforeLength, 2u);
QCOMPARE(textInput->deleteSurroundingText().afterLength, 1u);
}
QTEST_GUILESS_MAIN(TextInputTest)
#include "test_text_input_v2.moc"