2016-05-02 12:28:26 +00:00
|
|
|
/********************************************************************
|
|
|
|
Copyright 2016 Martin Gräßlin <mgraesslin@kde.org>
|
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
|
|
modify it under the terms of the GNU Lesser General Public
|
|
|
|
License as published by the Free Software Foundation; either
|
|
|
|
version 2.1 of the License, or (at your option) version 3, or any
|
|
|
|
later version accepted by the membership of KDE e.V. (or its
|
|
|
|
successor approved by the membership of KDE e.V.), which shall
|
|
|
|
act as a proxy defined in Section 6 of version 3 of the license.
|
|
|
|
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
Lesser General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
|
|
License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*********************************************************************/
|
|
|
|
// Qt
|
|
|
|
#include <QtTest/QtTest>
|
|
|
|
// client
|
|
|
|
#include "../../src/client/compositor.h"
|
|
|
|
#include "../../src/client/connection_thread.h"
|
|
|
|
#include "../../src/client/event_queue.h"
|
|
|
|
#include "../../src/client/keyboard.h"
|
|
|
|
#include "../../src/client/registry.h"
|
|
|
|
#include "../../src/client/seat.h"
|
|
|
|
#include "../../src/client/surface.h"
|
|
|
|
#include "../../src/client/textinput.h"
|
|
|
|
// server
|
|
|
|
#include "../../src/server/compositor_interface.h"
|
|
|
|
#include "../../src/server/display.h"
|
|
|
|
#include "../../src/server/seat_interface.h"
|
|
|
|
#include "../../src/server/textinput_interface.h"
|
|
|
|
|
|
|
|
using namespace KWayland::Client;
|
|
|
|
using namespace KWayland::Server;
|
|
|
|
|
|
|
|
class TextInputTest : public QObject
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
private Q_SLOTS:
|
|
|
|
void init();
|
|
|
|
void cleanup();
|
|
|
|
|
|
|
|
void testEnterLeave_data();
|
|
|
|
void testEnterLeave();
|
|
|
|
void testShowHidePanel_data();
|
|
|
|
void testShowHidePanel();
|
|
|
|
void testCursorRectangle_data();
|
|
|
|
void testCursorRectangle();
|
|
|
|
void testPreferredLanguage_data();
|
|
|
|
void testPreferredLanguage();
|
|
|
|
void testReset_data();
|
|
|
|
void testReset();
|
|
|
|
void testSurroundingText_data();
|
|
|
|
void testSurroundingText();
|
|
|
|
void testContentHints_data();
|
|
|
|
void testContentHints();
|
|
|
|
void testContentPurpose_data();
|
|
|
|
void testContentPurpose();
|
|
|
|
void testTextDirection_data();
|
|
|
|
void testTextDirection();
|
|
|
|
void testLanguage_data();
|
|
|
|
void testLanguage();
|
|
|
|
void testKeyEvent_data();
|
|
|
|
void testKeyEvent();
|
|
|
|
void testPreEdit_data();
|
|
|
|
void testPreEdit();
|
|
|
|
void testCommit_data();
|
|
|
|
void testCommit();
|
|
|
|
|
|
|
|
private:
|
|
|
|
SurfaceInterface *waitForSurface();
|
|
|
|
TextInput *createTextInput(TextInputInterfaceVersion version);
|
|
|
|
Display *m_display = nullptr;
|
|
|
|
SeatInterface *m_seatInterface = nullptr;
|
|
|
|
CompositorInterface *m_compositorInterface = nullptr;
|
|
|
|
TextInputManagerInterface *m_textInputManagerV0Interface = nullptr;
|
|
|
|
TextInputManagerInterface *m_textInputManagerV2Interface = nullptr;
|
|
|
|
ConnectionThread *m_connection = nullptr;
|
|
|
|
QThread *m_thread = nullptr;
|
|
|
|
EventQueue *m_queue = nullptr;
|
|
|
|
Seat *m_seat = nullptr;
|
|
|
|
Keyboard *m_keyboard = nullptr;
|
|
|
|
Compositor *m_compositor = nullptr;
|
|
|
|
TextInputManager *m_textInputManagerV0 = nullptr;
|
|
|
|
TextInputManager *m_textInputManagerV2 = nullptr;
|
|
|
|
};
|
|
|
|
|
|
|
|
static const QString s_socketName = QStringLiteral("kwayland-test-text-input-0");
|
|
|
|
|
|
|
|
void TextInputTest::init()
|
|
|
|
{
|
|
|
|
delete m_display;
|
|
|
|
m_display = new Display(this);
|
|
|
|
m_display->setSocketName(s_socketName);
|
|
|
|
m_display->start();
|
|
|
|
QVERIFY(m_display->isRunning());
|
|
|
|
m_display->createShm();
|
|
|
|
m_seatInterface = m_display->createSeat();
|
|
|
|
m_seatInterface->setHasKeyboard(true);
|
|
|
|
m_seatInterface->setHasTouch(true);
|
|
|
|
m_seatInterface->create();
|
|
|
|
m_compositorInterface = m_display->createCompositor();
|
|
|
|
m_compositorInterface->create();
|
|
|
|
m_textInputManagerV0Interface = m_display->createTextInputManager(TextInputInterfaceVersion::UnstableV0);
|
|
|
|
m_textInputManagerV0Interface->create();
|
|
|
|
m_textInputManagerV2Interface = m_display->createTextInputManager(TextInputInterfaceVersion::UnstableV2);
|
|
|
|
m_textInputManagerV2Interface->create();
|
|
|
|
|
|
|
|
// setup connection
|
|
|
|
m_connection = new KWayland::Client::ConnectionThread;
|
|
|
|
QSignalSpy connectedSpy(m_connection, &ConnectionThread::connected);
|
|
|
|
QVERIFY(connectedSpy.isValid());
|
|
|
|
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 EventQueue(this);
|
|
|
|
m_queue->setup(m_connection);
|
|
|
|
|
|
|
|
Registry registry;
|
|
|
|
QSignalSpy interfacesAnnouncedSpy(®istry, &Registry::interfacesAnnounced);
|
|
|
|
QVERIFY(interfacesAnnouncedSpy.isValid());
|
|
|
|
registry.setEventQueue(m_queue);
|
|
|
|
registry.create(m_connection);
|
|
|
|
QVERIFY(registry.isValid());
|
|
|
|
registry.setup();
|
|
|
|
QVERIFY(interfacesAnnouncedSpy.wait());
|
|
|
|
|
|
|
|
m_seat = registry.createSeat(registry.interface(Registry::Interface::Seat).name,
|
|
|
|
registry.interface(Registry::Interface::Seat).version,
|
|
|
|
this);
|
|
|
|
QVERIFY(m_seat->isValid());
|
|
|
|
QSignalSpy hasKeyboardSpy(m_seat, &Seat::hasKeyboardChanged);
|
|
|
|
QVERIFY(hasKeyboardSpy.isValid());
|
|
|
|
QVERIFY(hasKeyboardSpy.wait());
|
|
|
|
m_keyboard = m_seat->createKeyboard(this);
|
|
|
|
QVERIFY(m_keyboard->isValid());
|
|
|
|
|
|
|
|
m_compositor = registry.createCompositor(registry.interface(Registry::Interface::Compositor).name,
|
|
|
|
registry.interface(Registry::Interface::Compositor).version,
|
|
|
|
this);
|
|
|
|
QVERIFY(m_compositor->isValid());
|
|
|
|
|
|
|
|
m_textInputManagerV0 = registry.createTextInputManager(registry.interface(Registry::Interface::TextInputManagerUnstableV0).name,
|
|
|
|
registry.interface(Registry::Interface::TextInputManagerUnstableV0).version,
|
|
|
|
this);
|
|
|
|
QVERIFY(m_textInputManagerV0->isValid());
|
|
|
|
|
|
|
|
m_textInputManagerV2 = registry.createTextInputManager(registry.interface(Registry::Interface::TextInputManagerUnstableV2).name,
|
|
|
|
registry.interface(Registry::Interface::TextInputManagerUnstableV2).version,
|
|
|
|
this);
|
|
|
|
QVERIFY(m_textInputManagerV2->isValid());
|
|
|
|
}
|
|
|
|
|
|
|
|
void TextInputTest::cleanup()
|
|
|
|
{
|
|
|
|
#define CLEANUP(variable) \
|
|
|
|
if (variable) { \
|
|
|
|
delete variable; \
|
|
|
|
variable = nullptr; \
|
|
|
|
}
|
|
|
|
CLEANUP(m_textInputManagerV0)
|
|
|
|
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_textInputManagerV0Interface)
|
|
|
|
CLEANUP(m_textInputManagerV2Interface)
|
|
|
|
CLEANUP(m_compositorInterface)
|
|
|
|
CLEANUP(m_seatInterface)
|
|
|
|
CLEANUP(m_display)
|
|
|
|
#undef CLEANUP
|
|
|
|
}
|
|
|
|
|
|
|
|
SurfaceInterface *TextInputTest::waitForSurface()
|
|
|
|
{
|
|
|
|
QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated);
|
|
|
|
if (!surfaceCreatedSpy.isValid()) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
if (!surfaceCreatedSpy.wait()) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
if (surfaceCreatedSpy.count() != 1) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
return surfaceCreatedSpy.first().first().value<SurfaceInterface*>();
|
|
|
|
}
|
|
|
|
|
|
|
|
TextInput *TextInputTest::createTextInput(TextInputInterfaceVersion version)
|
|
|
|
{
|
|
|
|
switch (version) {
|
|
|
|
case TextInputInterfaceVersion::UnstableV0:
|
|
|
|
return m_textInputManagerV0->createTextInput(m_seat);
|
|
|
|
case TextInputInterfaceVersion::UnstableV2:
|
|
|
|
return m_textInputManagerV2->createTextInput(m_seat);
|
|
|
|
default:
|
|
|
|
Q_UNREACHABLE();
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TextInputTest::testEnterLeave_data()
|
|
|
|
{
|
|
|
|
QTest::addColumn<TextInputInterfaceVersion>("version");
|
|
|
|
QTest::addColumn<bool>("updatesDirectly");
|
|
|
|
|
|
|
|
QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0 << false;
|
|
|
|
QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2 << true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TextInputTest::testEnterLeave()
|
|
|
|
{
|
|
|
|
// this test verifies that enter leave are sent correctly
|
|
|
|
QScopedPointer<Surface> surface(m_compositor->createSurface());
|
|
|
|
QFETCH(TextInputInterfaceVersion, version);
|
|
|
|
QScopedPointer<TextInput> textInput(createTextInput(version));
|
|
|
|
auto serverSurface = waitForSurface();
|
|
|
|
QVERIFY(serverSurface);
|
|
|
|
QVERIFY(!textInput.isNull());
|
|
|
|
QSignalSpy enteredSpy(textInput.data(), &TextInput::entered);
|
|
|
|
QVERIFY(enteredSpy.isValid());
|
|
|
|
QSignalSpy leftSpy(textInput.data(), &TextInput::left);
|
|
|
|
QVERIFY(leftSpy.isValid());
|
|
|
|
QSignalSpy textInputChangedSpy(m_seatInterface, &SeatInterface::focusedTextInputChanged);
|
|
|
|
QVERIFY(textInputChangedSpy.isValid());
|
|
|
|
|
|
|
|
// now let's try to enter it
|
|
|
|
QVERIFY(!m_seatInterface->focusedTextInput());
|
2016-06-28 12:53:45 +00:00
|
|
|
QVERIFY(!m_seatInterface->focusedTextInputSurface());
|
2016-05-02 12:28:26 +00:00
|
|
|
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
|
2016-06-28 12:53:45 +00:00
|
|
|
QCOMPARE(m_seatInterface->focusedTextInputSurface(), serverSurface);
|
2016-05-02 12:28:26 +00:00
|
|
|
// text input not yet set for the surface
|
|
|
|
QFETCH(bool, updatesDirectly);
|
|
|
|
QCOMPARE(bool(m_seatInterface->focusedTextInput()), updatesDirectly);
|
|
|
|
QCOMPARE(textInputChangedSpy.isEmpty(), !updatesDirectly);
|
|
|
|
textInput->enable(surface.data());
|
|
|
|
// this should trigger on server side
|
|
|
|
if (!updatesDirectly) {
|
|
|
|
QVERIFY(textInputChangedSpy.wait());
|
|
|
|
}
|
|
|
|
QCOMPARE(textInputChangedSpy.count(), 1);
|
|
|
|
auto serverTextInput = m_seatInterface->focusedTextInput();
|
|
|
|
QVERIFY(serverTextInput);
|
|
|
|
QCOMPARE(serverTextInput->interfaceVersion(), version);
|
|
|
|
QSignalSpy enabledChangedSpy(serverTextInput, &TextInputInterface::enabledChanged);
|
|
|
|
QVERIFY(enabledChangedSpy.isValid());
|
|
|
|
if (updatesDirectly) {
|
|
|
|
QVERIFY(enabledChangedSpy.wait());
|
|
|
|
enabledChangedSpy.clear();
|
|
|
|
}
|
|
|
|
QCOMPARE(serverTextInput->surface().data(), serverSurface);
|
|
|
|
QVERIFY(serverTextInput->isEnabled());
|
|
|
|
|
|
|
|
// and trigger an enter
|
2016-05-30 06:54:46 +00:00
|
|
|
if (enteredSpy.isEmpty()) {
|
2016-05-02 12:28:26 +00:00
|
|
|
QVERIFY(enteredSpy.wait());
|
|
|
|
}
|
|
|
|
QCOMPARE(enteredSpy.count(), 1);
|
|
|
|
QCOMPARE(textInput->enteredSurface(), surface.data());
|
|
|
|
|
|
|
|
// now trigger a leave
|
|
|
|
m_seatInterface->setFocusedKeyboardSurface(nullptr);
|
|
|
|
QCOMPARE(textInputChangedSpy.count(), 2);
|
|
|
|
QVERIFY(!m_seatInterface->focusedTextInput());
|
|
|
|
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->focusedTextInput());
|
|
|
|
QVERIFY(enteredSpy.wait());
|
|
|
|
QCOMPARE(enteredSpy.count(), 2);
|
|
|
|
QCOMPARE(textInput->enteredSurface(), surface.data());
|
|
|
|
QVERIFY(serverTextInput->isEnabled());
|
|
|
|
|
|
|
|
// let's deactivate on client side
|
|
|
|
textInput->disable(surface.data());
|
|
|
|
QVERIFY(enabledChangedSpy.wait());
|
|
|
|
QCOMPARE(enabledChangedSpy.count(), 1);
|
|
|
|
QVERIFY(!serverTextInput->isEnabled());
|
|
|
|
// does not trigger a leave
|
|
|
|
QCOMPARE(textInputChangedSpy.count(), 3);
|
|
|
|
// should still be the same text input
|
|
|
|
QCOMPARE(m_seatInterface->focusedTextInput(), serverTextInput);
|
Validate surface is valid when sending TextInput leave event
Summary:
It's possible for the surface to be unbound when we send the leave
event; we've called Resource::unbind() of Surface, so the Surface has,
deleteLater called, but it's still a valid object, and the first check
passes.
We get in this situation because when a surface is destroyed, we're
handling text input from the same source event.
Sending a nullpointer is a protocol error, and wayland kindly closes the
connection.
This fixes my constant:
"Did the Wayland server die" error messages when running clients.
Test Plan:
Got errors after setting up qt virtual keyboard.
Had reproducible case.
Restarted kwin after this patch, now doesn't crash.
Reviewers: #plasma, graesslin
Subscribers: apol, graesslin, plasma-devel, #frameworks
Tags: #plasma_on_wayland, #frameworks
Differential Revision: https://phabricator.kde.org/D5712
2017-05-05 14:43:35 +00:00
|
|
|
//reset
|
|
|
|
textInput->enable(surface.data());
|
|
|
|
QVERIFY(enabledChangedSpy.wait());
|
|
|
|
|
|
|
|
//trigger an enter again and leave, but this
|
|
|
|
//time we try sending an event after the surface is unbound
|
|
|
|
//but not yet destroyed. It should work without errors
|
|
|
|
QCOMPARE(textInput->enteredSurface(), surface.data());
|
|
|
|
connect(serverSurface, &Resource::unbound, [=]() {
|
|
|
|
m_seatInterface->setFocusedKeyboardSurface(nullptr);
|
|
|
|
});
|
|
|
|
//delete the client and wait for the server to catch up
|
|
|
|
QSignalSpy unboundSpy(serverSurface, &QObject::destroyed);
|
|
|
|
surface.reset();
|
|
|
|
QVERIFY(unboundSpy.wait());
|
2016-05-02 12:28:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void TextInputTest::testShowHidePanel_data()
|
|
|
|
{
|
|
|
|
QTest::addColumn<TextInputInterfaceVersion>("version");
|
|
|
|
|
|
|
|
QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0;
|
|
|
|
QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TextInputTest::testShowHidePanel()
|
|
|
|
{
|
|
|
|
// this test verifies that the requests for show/hide panel work
|
|
|
|
// and that status is properly sent to the client
|
|
|
|
QScopedPointer<Surface> surface(m_compositor->createSurface());
|
|
|
|
auto serverSurface = waitForSurface();
|
|
|
|
QVERIFY(serverSurface);
|
|
|
|
QFETCH(TextInputInterfaceVersion, version);
|
|
|
|
QScopedPointer<TextInput> textInput(createTextInput(version));
|
|
|
|
QVERIFY(!textInput.isNull());
|
|
|
|
textInput->enable(surface.data());
|
|
|
|
m_connection->flush();
|
|
|
|
m_display->dispatchEvents();
|
|
|
|
|
|
|
|
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
|
|
|
|
auto ti = m_seatInterface->focusedTextInput();
|
|
|
|
QVERIFY(ti);
|
|
|
|
|
|
|
|
QSignalSpy showPanelRequestedSpy(ti, &TextInputInterface::requestShowInputPanel);
|
|
|
|
QVERIFY(showPanelRequestedSpy.isValid());
|
|
|
|
QSignalSpy hidePanelRequestedSpy(ti, &TextInputInterface::requestHideInputPanel);
|
|
|
|
QVERIFY(hidePanelRequestedSpy.isValid());
|
|
|
|
QSignalSpy inputPanelStateChangedSpy(textInput.data(), &TextInput::inputPanelStateChanged);
|
|
|
|
QVERIFY(inputPanelStateChangedSpy.isValid());
|
|
|
|
|
|
|
|
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_data()
|
|
|
|
{
|
|
|
|
QTest::addColumn<TextInputInterfaceVersion>("version");
|
|
|
|
|
|
|
|
QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0;
|
|
|
|
QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2;
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
QScopedPointer<Surface> surface(m_compositor->createSurface());
|
|
|
|
auto serverSurface = waitForSurface();
|
|
|
|
QVERIFY(serverSurface);
|
|
|
|
QFETCH(TextInputInterfaceVersion, version);
|
|
|
|
QScopedPointer<TextInput> textInput(createTextInput(version));
|
|
|
|
QVERIFY(!textInput.isNull());
|
|
|
|
textInput->enable(surface.data());
|
|
|
|
m_connection->flush();
|
|
|
|
m_display->dispatchEvents();
|
|
|
|
|
|
|
|
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
|
|
|
|
auto ti = m_seatInterface->focusedTextInput();
|
|
|
|
QVERIFY(ti);
|
|
|
|
QCOMPARE(ti->cursorRectangle(), QRect());
|
|
|
|
QSignalSpy cursorRectangleChangedSpy(ti, &TextInputInterface::cursorRectangleChanged);
|
|
|
|
QVERIFY(cursorRectangleChangedSpy.isValid());
|
|
|
|
|
|
|
|
textInput->setCursorRectangle(QRect(10, 20, 30, 40));
|
|
|
|
QVERIFY(cursorRectangleChangedSpy.wait());
|
|
|
|
QCOMPARE(ti->cursorRectangle(), QRect(10, 20, 30, 40));
|
|
|
|
}
|
|
|
|
|
|
|
|
void TextInputTest::testPreferredLanguage_data()
|
|
|
|
{
|
|
|
|
QTest::addColumn<TextInputInterfaceVersion>("version");
|
|
|
|
|
|
|
|
QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0;
|
|
|
|
QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TextInputTest::testPreferredLanguage()
|
|
|
|
{
|
|
|
|
// this test verifies that passing the preferred language from client to server works
|
|
|
|
QScopedPointer<Surface> surface(m_compositor->createSurface());
|
|
|
|
auto serverSurface = waitForSurface();
|
|
|
|
QVERIFY(serverSurface);
|
|
|
|
QFETCH(TextInputInterfaceVersion, version);
|
|
|
|
QScopedPointer<TextInput> textInput(createTextInput(version));
|
|
|
|
QVERIFY(!textInput.isNull());
|
|
|
|
textInput->enable(surface.data());
|
|
|
|
m_connection->flush();
|
|
|
|
m_display->dispatchEvents();
|
|
|
|
|
|
|
|
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
|
|
|
|
auto ti = m_seatInterface->focusedTextInput();
|
|
|
|
QVERIFY(ti);
|
|
|
|
QVERIFY(ti->preferredLanguage().isEmpty());
|
|
|
|
|
|
|
|
QSignalSpy preferredLanguageChangedSpy(ti, &TextInputInterface::preferredLanguageChanged);
|
|
|
|
QVERIFY(preferredLanguageChangedSpy.isValid());
|
|
|
|
textInput->setPreferredLanguage(QStringLiteral("foo"));
|
|
|
|
QVERIFY(preferredLanguageChangedSpy.wait());
|
|
|
|
QCOMPARE(ti->preferredLanguage(), QStringLiteral("foo").toUtf8());
|
|
|
|
}
|
|
|
|
|
|
|
|
void TextInputTest::testReset_data()
|
|
|
|
{
|
|
|
|
QTest::addColumn<TextInputInterfaceVersion>("version");
|
|
|
|
|
|
|
|
QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0;
|
|
|
|
QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TextInputTest::testReset()
|
|
|
|
{
|
|
|
|
// this test verifies that the reset request is properly passed from client to server
|
|
|
|
QScopedPointer<Surface> surface(m_compositor->createSurface());
|
|
|
|
auto serverSurface = waitForSurface();
|
|
|
|
QVERIFY(serverSurface);
|
|
|
|
QFETCH(TextInputInterfaceVersion, version);
|
|
|
|
QScopedPointer<TextInput> textInput(createTextInput(version));
|
|
|
|
QVERIFY(!textInput.isNull());
|
|
|
|
textInput->enable(surface.data());
|
|
|
|
m_connection->flush();
|
|
|
|
m_display->dispatchEvents();
|
|
|
|
|
|
|
|
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
|
|
|
|
auto ti = m_seatInterface->focusedTextInput();
|
|
|
|
QVERIFY(ti);
|
|
|
|
|
|
|
|
QSignalSpy resetRequestedSpy(ti, &TextInputInterface::requestReset);
|
|
|
|
QVERIFY(resetRequestedSpy.isValid());
|
|
|
|
|
|
|
|
textInput->reset();
|
|
|
|
QVERIFY(resetRequestedSpy.wait());
|
|
|
|
}
|
|
|
|
|
|
|
|
void TextInputTest::testSurroundingText_data()
|
|
|
|
{
|
|
|
|
QTest::addColumn<TextInputInterfaceVersion>("version");
|
|
|
|
|
|
|
|
QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0;
|
|
|
|
QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TextInputTest::testSurroundingText()
|
|
|
|
{
|
|
|
|
// this test verifies that surrounding text is properly passed around
|
|
|
|
QScopedPointer<Surface> surface(m_compositor->createSurface());
|
|
|
|
auto serverSurface = waitForSurface();
|
|
|
|
QVERIFY(serverSurface);
|
|
|
|
QFETCH(TextInputInterfaceVersion, version);
|
|
|
|
QScopedPointer<TextInput> textInput(createTextInput(version));
|
|
|
|
QVERIFY(!textInput.isNull());
|
|
|
|
textInput->enable(surface.data());
|
|
|
|
m_connection->flush();
|
|
|
|
m_display->dispatchEvents();
|
|
|
|
|
|
|
|
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
|
|
|
|
auto ti = m_seatInterface->focusedTextInput();
|
|
|
|
QVERIFY(ti);
|
|
|
|
QVERIFY(ti->surroundingText().isEmpty());
|
|
|
|
QCOMPARE(ti->surroundingTextCursorPosition(), 0);
|
|
|
|
QCOMPARE(ti->surroundingTextSelectionAnchor(), 0);
|
|
|
|
|
|
|
|
QSignalSpy surroundingTextChangedSpy(ti, &TextInputInterface::surroundingTextChanged);
|
|
|
|
QVERIFY(surroundingTextChangedSpy.isValid());
|
|
|
|
|
|
|
|
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<TextInputInterfaceVersion>("version");
|
|
|
|
QTest::addColumn<TextInput::ContentHints>("clientHints");
|
|
|
|
QTest::addColumn<TextInputInterface::ContentHints>("serverHints");
|
|
|
|
|
|
|
|
QTest::newRow("completion/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentHints(TextInput::ContentHint::AutoCompletion) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::AutoCompletion);
|
|
|
|
QTest::newRow("Correction/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentHints(TextInput::ContentHint::AutoCorrection) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::AutoCorrection);
|
|
|
|
QTest::newRow("Capitalization/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentHints(TextInput::ContentHint::AutoCapitalization) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::AutoCapitalization);
|
|
|
|
QTest::newRow("Lowercase/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentHints(TextInput::ContentHint::LowerCase) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::LowerCase);
|
|
|
|
QTest::newRow("Uppercase/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentHints(TextInput::ContentHint::UpperCase) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::UpperCase);
|
|
|
|
QTest::newRow("Titlecase/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentHints(TextInput::ContentHint::TitleCase) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::TitleCase);
|
|
|
|
QTest::newRow("HiddenText/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentHints(TextInput::ContentHint::HiddenText) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::HiddenText);
|
|
|
|
QTest::newRow("SensitiveData/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentHints(TextInput::ContentHint::SensitiveData) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::SensitiveData);
|
|
|
|
QTest::newRow("Latin/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentHints(TextInput::ContentHint::Latin) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::Latin);
|
|
|
|
QTest::newRow("Multiline/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentHints(TextInput::ContentHint::MultiLine) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::MultiLine);
|
|
|
|
|
|
|
|
QTest::newRow("autos/v0") << TextInputInterfaceVersion::UnstableV0
|
|
|
|
<< (TextInput::ContentHint::AutoCompletion | TextInput::ContentHint::AutoCorrection | TextInput::ContentHint::AutoCapitalization)
|
|
|
|
<< (TextInputInterface::ContentHint::AutoCompletion | TextInputInterface::ContentHint::AutoCorrection | TextInputInterface::ContentHint::AutoCapitalization);
|
|
|
|
|
|
|
|
// all has combinations which don't make sense - what's both lowercase and uppercase?
|
|
|
|
QTest::newRow("all/v0") << TextInputInterfaceVersion::UnstableV0
|
|
|
|
<< (TextInput::ContentHint::AutoCompletion |
|
|
|
|
TextInput::ContentHint::AutoCorrection |
|
|
|
|
TextInput::ContentHint::AutoCapitalization |
|
|
|
|
TextInput::ContentHint::LowerCase |
|
|
|
|
TextInput::ContentHint::UpperCase |
|
|
|
|
TextInput::ContentHint::TitleCase |
|
|
|
|
TextInput::ContentHint::HiddenText |
|
|
|
|
TextInput::ContentHint::SensitiveData |
|
|
|
|
TextInput::ContentHint::Latin |
|
|
|
|
TextInput::ContentHint::MultiLine)
|
|
|
|
<< (TextInputInterface::ContentHint::AutoCompletion |
|
|
|
|
TextInputInterface::ContentHint::AutoCorrection |
|
|
|
|
TextInputInterface::ContentHint::AutoCapitalization |
|
|
|
|
TextInputInterface::ContentHint::LowerCase |
|
|
|
|
TextInputInterface::ContentHint::UpperCase |
|
|
|
|
TextInputInterface::ContentHint::TitleCase |
|
|
|
|
TextInputInterface::ContentHint::HiddenText |
|
|
|
|
TextInputInterface::ContentHint::SensitiveData |
|
|
|
|
TextInputInterface::ContentHint::Latin |
|
|
|
|
TextInputInterface::ContentHint::MultiLine);
|
|
|
|
|
|
|
|
// same for version 2
|
|
|
|
|
|
|
|
QTest::newRow("completion/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentHints(TextInput::ContentHint::AutoCompletion) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::AutoCompletion);
|
|
|
|
QTest::newRow("Correction/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentHints(TextInput::ContentHint::AutoCorrection) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::AutoCorrection);
|
|
|
|
QTest::newRow("Capitalization/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentHints(TextInput::ContentHint::AutoCapitalization) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::AutoCapitalization);
|
|
|
|
QTest::newRow("Lowercase/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentHints(TextInput::ContentHint::LowerCase) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::LowerCase);
|
|
|
|
QTest::newRow("Uppercase/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentHints(TextInput::ContentHint::UpperCase) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::UpperCase);
|
|
|
|
QTest::newRow("Titlecase/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentHints(TextInput::ContentHint::TitleCase) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::TitleCase);
|
|
|
|
QTest::newRow("HiddenText/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentHints(TextInput::ContentHint::HiddenText) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::HiddenText);
|
|
|
|
QTest::newRow("SensitiveData/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentHints(TextInput::ContentHint::SensitiveData) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::SensitiveData);
|
|
|
|
QTest::newRow("Latin/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentHints(TextInput::ContentHint::Latin) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::Latin);
|
|
|
|
QTest::newRow("Multiline/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentHints(TextInput::ContentHint::MultiLine) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::MultiLine);
|
|
|
|
|
|
|
|
QTest::newRow("autos/v2") << TextInputInterfaceVersion::UnstableV2
|
|
|
|
<< (TextInput::ContentHint::AutoCompletion | TextInput::ContentHint::AutoCorrection | TextInput::ContentHint::AutoCapitalization)
|
|
|
|
<< (TextInputInterface::ContentHint::AutoCompletion | TextInputInterface::ContentHint::AutoCorrection | TextInputInterface::ContentHint::AutoCapitalization);
|
|
|
|
|
|
|
|
// all has combinations which don't make sense - what's both lowercase and uppercase?
|
|
|
|
QTest::newRow("all/v2") << TextInputInterfaceVersion::UnstableV2
|
|
|
|
<< (TextInput::ContentHint::AutoCompletion |
|
|
|
|
TextInput::ContentHint::AutoCorrection |
|
|
|
|
TextInput::ContentHint::AutoCapitalization |
|
|
|
|
TextInput::ContentHint::LowerCase |
|
|
|
|
TextInput::ContentHint::UpperCase |
|
|
|
|
TextInput::ContentHint::TitleCase |
|
|
|
|
TextInput::ContentHint::HiddenText |
|
|
|
|
TextInput::ContentHint::SensitiveData |
|
|
|
|
TextInput::ContentHint::Latin |
|
|
|
|
TextInput::ContentHint::MultiLine)
|
|
|
|
<< (TextInputInterface::ContentHint::AutoCompletion |
|
|
|
|
TextInputInterface::ContentHint::AutoCorrection |
|
|
|
|
TextInputInterface::ContentHint::AutoCapitalization |
|
|
|
|
TextInputInterface::ContentHint::LowerCase |
|
|
|
|
TextInputInterface::ContentHint::UpperCase |
|
|
|
|
TextInputInterface::ContentHint::TitleCase |
|
|
|
|
TextInputInterface::ContentHint::HiddenText |
|
|
|
|
TextInputInterface::ContentHint::SensitiveData |
|
|
|
|
TextInputInterface::ContentHint::Latin |
|
|
|
|
TextInputInterface::ContentHint::MultiLine);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TextInputTest::testContentHints()
|
|
|
|
{
|
|
|
|
// this test verifies that content hints are properly passed from client to server
|
|
|
|
QScopedPointer<Surface> surface(m_compositor->createSurface());
|
|
|
|
auto serverSurface = waitForSurface();
|
|
|
|
QVERIFY(serverSurface);
|
|
|
|
QFETCH(TextInputInterfaceVersion, version);
|
|
|
|
QScopedPointer<TextInput> textInput(createTextInput(version));
|
|
|
|
QVERIFY(!textInput.isNull());
|
|
|
|
textInput->enable(surface.data());
|
|
|
|
m_connection->flush();
|
|
|
|
m_display->dispatchEvents();
|
|
|
|
|
|
|
|
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
|
|
|
|
auto ti = m_seatInterface->focusedTextInput();
|
|
|
|
QVERIFY(ti);
|
|
|
|
QCOMPARE(ti->contentHints(), TextInputInterface::ContentHints());
|
|
|
|
|
|
|
|
QSignalSpy contentTypeChangedSpy(ti, &TextInputInterface::contentTypeChanged);
|
|
|
|
QVERIFY(contentTypeChangedSpy.isValid());
|
|
|
|
QFETCH(TextInput::ContentHints, clientHints);
|
|
|
|
textInput->setContentType(clientHints, TextInput::ContentPurpose::Normal);
|
|
|
|
QVERIFY(contentTypeChangedSpy.wait());
|
|
|
|
QTEST(ti->contentHints(), "serverHints");
|
|
|
|
|
|
|
|
// setting to same should not trigger an update
|
|
|
|
textInput->setContentType(clientHints, TextInput::ContentPurpose::Normal);
|
|
|
|
QVERIFY(!contentTypeChangedSpy.wait(100));
|
|
|
|
|
|
|
|
// unsetting should work
|
|
|
|
textInput->setContentType(TextInput::ContentHints(), TextInput::ContentPurpose::Normal);
|
|
|
|
QVERIFY(contentTypeChangedSpy.wait());
|
|
|
|
QCOMPARE(ti->contentHints(), TextInputInterface::ContentHints());
|
|
|
|
}
|
|
|
|
|
|
|
|
void TextInputTest::testContentPurpose_data()
|
|
|
|
{
|
|
|
|
QTest::addColumn<TextInputInterfaceVersion>("version");
|
|
|
|
QTest::addColumn<TextInput::ContentPurpose>("clientPurpose");
|
|
|
|
QTest::addColumn<TextInputInterface::ContentPurpose>("serverPurpose");
|
|
|
|
|
|
|
|
QTest::newRow("Alpha/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentPurpose::Alpha << TextInputInterface::ContentPurpose::Alpha;
|
|
|
|
QTest::newRow("Digits/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentPurpose::Digits << TextInputInterface::ContentPurpose::Digits;
|
|
|
|
QTest::newRow("Number/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentPurpose::Number << TextInputInterface::ContentPurpose::Number;
|
|
|
|
QTest::newRow("Phone/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentPurpose::Phone << TextInputInterface::ContentPurpose::Phone;
|
|
|
|
QTest::newRow("Url/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentPurpose::Url << TextInputInterface::ContentPurpose::Url;
|
|
|
|
QTest::newRow("Email/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentPurpose::Email << TextInputInterface::ContentPurpose::Email;
|
|
|
|
QTest::newRow("Name/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentPurpose::Name << TextInputInterface::ContentPurpose::Name;
|
|
|
|
QTest::newRow("Password/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentPurpose::Password << TextInputInterface::ContentPurpose::Password;
|
|
|
|
QTest::newRow("Date/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentPurpose::Date << TextInputInterface::ContentPurpose::Date;
|
|
|
|
QTest::newRow("Time/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentPurpose::Time << TextInputInterface::ContentPurpose::Time;
|
|
|
|
QTest::newRow("Datetime/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentPurpose::DateTime << TextInputInterface::ContentPurpose::DateTime;
|
|
|
|
QTest::newRow("Terminal/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentPurpose::Terminal << TextInputInterface::ContentPurpose::Terminal;
|
|
|
|
|
|
|
|
QTest::newRow("Alpha/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentPurpose::Alpha << TextInputInterface::ContentPurpose::Alpha;
|
|
|
|
QTest::newRow("Digits/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentPurpose::Digits << TextInputInterface::ContentPurpose::Digits;
|
|
|
|
QTest::newRow("Number/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentPurpose::Number << TextInputInterface::ContentPurpose::Number;
|
|
|
|
QTest::newRow("Phone/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentPurpose::Phone << TextInputInterface::ContentPurpose::Phone;
|
|
|
|
QTest::newRow("Url/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentPurpose::Url << TextInputInterface::ContentPurpose::Url;
|
|
|
|
QTest::newRow("Email/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentPurpose::Email << TextInputInterface::ContentPurpose::Email;
|
|
|
|
QTest::newRow("Name/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentPurpose::Name << TextInputInterface::ContentPurpose::Name;
|
|
|
|
QTest::newRow("Password/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentPurpose::Password << TextInputInterface::ContentPurpose::Password;
|
|
|
|
QTest::newRow("Date/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentPurpose::Date << TextInputInterface::ContentPurpose::Date;
|
|
|
|
QTest::newRow("Time/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentPurpose::Time << TextInputInterface::ContentPurpose::Time;
|
|
|
|
QTest::newRow("Datetime/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentPurpose::DateTime << TextInputInterface::ContentPurpose::DateTime;
|
|
|
|
QTest::newRow("Terminal/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentPurpose::Terminal << TextInputInterface::ContentPurpose::Terminal;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TextInputTest::testContentPurpose()
|
|
|
|
{
|
|
|
|
// this test verifies that content purpose are properly passed from client to server
|
|
|
|
QScopedPointer<Surface> surface(m_compositor->createSurface());
|
|
|
|
auto serverSurface = waitForSurface();
|
|
|
|
QVERIFY(serverSurface);
|
|
|
|
QFETCH(TextInputInterfaceVersion, version);
|
|
|
|
QScopedPointer<TextInput> textInput(createTextInput(version));
|
|
|
|
QVERIFY(!textInput.isNull());
|
|
|
|
textInput->enable(surface.data());
|
|
|
|
m_connection->flush();
|
|
|
|
m_display->dispatchEvents();
|
|
|
|
|
|
|
|
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
|
|
|
|
auto ti = m_seatInterface->focusedTextInput();
|
|
|
|
QVERIFY(ti);
|
|
|
|
QCOMPARE(ti->contentPurpose(), TextInputInterface::ContentPurpose::Normal);
|
|
|
|
|
|
|
|
QSignalSpy contentTypeChangedSpy(ti, &TextInputInterface::contentTypeChanged);
|
|
|
|
QVERIFY(contentTypeChangedSpy.isValid());
|
|
|
|
QFETCH(TextInput::ContentPurpose, clientPurpose);
|
|
|
|
textInput->setContentType(TextInput::ContentHints(), clientPurpose);
|
|
|
|
QVERIFY(contentTypeChangedSpy.wait());
|
|
|
|
QTEST(ti->contentPurpose(), "serverPurpose");
|
|
|
|
|
|
|
|
// setting to same should not trigger an update
|
|
|
|
textInput->setContentType(TextInput::ContentHints(), clientPurpose);
|
|
|
|
QVERIFY(!contentTypeChangedSpy.wait(100));
|
|
|
|
|
|
|
|
// unsetting should work
|
|
|
|
textInput->setContentType(TextInput::ContentHints(), TextInput::ContentPurpose::Normal);
|
|
|
|
QVERIFY(contentTypeChangedSpy.wait());
|
|
|
|
QCOMPARE(ti->contentPurpose(), TextInputInterface::ContentPurpose::Normal);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TextInputTest::testTextDirection_data()
|
|
|
|
{
|
|
|
|
QTest::addColumn<TextInputInterfaceVersion>("version");
|
|
|
|
QTest::addColumn<Qt::LayoutDirection>("textDirection");
|
|
|
|
|
|
|
|
QTest::newRow("ltr/v0") << TextInputInterfaceVersion::UnstableV0 << Qt::LeftToRight;
|
|
|
|
QTest::newRow("rtl/v0") << TextInputInterfaceVersion::UnstableV0 << Qt::RightToLeft;
|
|
|
|
|
|
|
|
QTest::newRow("ltr/v2") << TextInputInterfaceVersion::UnstableV2 << Qt::LeftToRight;
|
|
|
|
QTest::newRow("rtl/v2") << TextInputInterfaceVersion::UnstableV2 << Qt::RightToLeft;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TextInputTest::testTextDirection()
|
|
|
|
{
|
|
|
|
// this test verifies that the text direction is sent from server to client
|
|
|
|
QScopedPointer<Surface> surface(m_compositor->createSurface());
|
|
|
|
auto serverSurface = waitForSurface();
|
|
|
|
QVERIFY(serverSurface);
|
|
|
|
QFETCH(TextInputInterfaceVersion, version);
|
|
|
|
QScopedPointer<TextInput> textInput(createTextInput(version));
|
|
|
|
QVERIFY(!textInput.isNull());
|
|
|
|
// default should be auto
|
|
|
|
QCOMPARE(textInput->textDirection(), Qt::LayoutDirectionAuto);
|
|
|
|
textInput->enable(surface.data());
|
|
|
|
m_connection->flush();
|
|
|
|
m_display->dispatchEvents();
|
|
|
|
|
|
|
|
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
|
|
|
|
auto ti = m_seatInterface->focusedTextInput();
|
|
|
|
QVERIFY(ti);
|
|
|
|
|
|
|
|
// let's send the new text direction
|
|
|
|
QSignalSpy textDirectionChangedSpy(textInput.data(), &TextInput::textDirectionChanged);
|
|
|
|
QVERIFY(textDirectionChangedSpy.isValid());
|
|
|
|
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_data()
|
|
|
|
{
|
|
|
|
QTest::addColumn<TextInputInterfaceVersion>("version");
|
|
|
|
|
|
|
|
QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0;
|
|
|
|
QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TextInputTest::testLanguage()
|
|
|
|
{
|
|
|
|
// this test verifies that language is sent from server to client
|
|
|
|
QScopedPointer<Surface> surface(m_compositor->createSurface());
|
|
|
|
auto serverSurface = waitForSurface();
|
|
|
|
QVERIFY(serverSurface);
|
|
|
|
QFETCH(TextInputInterfaceVersion, version);
|
|
|
|
QScopedPointer<TextInput> textInput(createTextInput(version));
|
|
|
|
QVERIFY(!textInput.isNull());
|
|
|
|
// default should be empty
|
|
|
|
QVERIFY(textInput->language().isEmpty());
|
|
|
|
textInput->enable(surface.data());
|
|
|
|
m_connection->flush();
|
|
|
|
m_display->dispatchEvents();
|
|
|
|
|
|
|
|
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
|
|
|
|
auto ti = m_seatInterface->focusedTextInput();
|
|
|
|
QVERIFY(ti);
|
|
|
|
|
|
|
|
// let's send the new language
|
|
|
|
QSignalSpy langugageChangedSpy(textInput.data(), &TextInput::languageChanged);
|
|
|
|
QVERIFY(langugageChangedSpy.isValid());
|
|
|
|
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_data()
|
|
|
|
{
|
|
|
|
QTest::addColumn<TextInputInterfaceVersion>("version");
|
|
|
|
|
|
|
|
QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0;
|
|
|
|
QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TextInputTest::testKeyEvent()
|
|
|
|
{
|
|
|
|
qRegisterMetaType<Qt::KeyboardModifiers>();
|
|
|
|
qRegisterMetaType<TextInput::KeyState>();
|
|
|
|
// this test verifies that key events are properly sent to the client
|
|
|
|
QScopedPointer<Surface> surface(m_compositor->createSurface());
|
|
|
|
auto serverSurface = waitForSurface();
|
|
|
|
QVERIFY(serverSurface);
|
|
|
|
QFETCH(TextInputInterfaceVersion, version);
|
|
|
|
QScopedPointer<TextInput> textInput(createTextInput(version));
|
|
|
|
QVERIFY(!textInput.isNull());
|
|
|
|
textInput->enable(surface.data());
|
|
|
|
m_connection->flush();
|
|
|
|
m_display->dispatchEvents();
|
|
|
|
|
|
|
|
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
|
|
|
|
auto ti = m_seatInterface->focusedTextInput();
|
|
|
|
QVERIFY(ti);
|
|
|
|
|
|
|
|
// TODO: test modifiers
|
|
|
|
QSignalSpy keyEventSpy(textInput.data(), &TextInput::keyEvent);
|
|
|
|
QVERIFY(keyEventSpy.isValid());
|
|
|
|
m_seatInterface->setTimestamp(100);
|
|
|
|
ti->keysymPressed(2);
|
|
|
|
QVERIFY(keyEventSpy.wait());
|
|
|
|
QCOMPARE(keyEventSpy.count(), 1);
|
|
|
|
QCOMPARE(keyEventSpy.last().at(0).value<quint32>(), 2u);
|
|
|
|
QCOMPARE(keyEventSpy.last().at(1).value<TextInput::KeyState>(), TextInput::KeyState::Pressed);
|
|
|
|
QCOMPARE(keyEventSpy.last().at(2).value<Qt::KeyboardModifiers>(), Qt::KeyboardModifiers());
|
|
|
|
QCOMPARE(keyEventSpy.last().at(3).value<quint32>(), 100u);
|
|
|
|
m_seatInterface->setTimestamp(101);
|
|
|
|
ti->keysymReleased(2);
|
|
|
|
QVERIFY(keyEventSpy.wait());
|
|
|
|
QCOMPARE(keyEventSpy.count(), 2);
|
|
|
|
QCOMPARE(keyEventSpy.last().at(0).value<quint32>(), 2u);
|
|
|
|
QCOMPARE(keyEventSpy.last().at(1).value<TextInput::KeyState>(), TextInput::KeyState::Released);
|
|
|
|
QCOMPARE(keyEventSpy.last().at(2).value<Qt::KeyboardModifiers>(), Qt::KeyboardModifiers());
|
|
|
|
QCOMPARE(keyEventSpy.last().at(3).value<quint32>(), 101u);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TextInputTest::testPreEdit_data()
|
|
|
|
{
|
|
|
|
QTest::addColumn<TextInputInterfaceVersion>("version");
|
|
|
|
|
|
|
|
QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0;
|
|
|
|
QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TextInputTest::testPreEdit()
|
|
|
|
{
|
|
|
|
// this test verifies that pre-edit is correctly passed to the client
|
|
|
|
QScopedPointer<Surface> surface(m_compositor->createSurface());
|
|
|
|
auto serverSurface = waitForSurface();
|
|
|
|
QVERIFY(serverSurface);
|
|
|
|
QFETCH(TextInputInterfaceVersion, version);
|
|
|
|
QScopedPointer<TextInput> textInput(createTextInput(version));
|
|
|
|
QVERIFY(!textInput.isNull());
|
|
|
|
// verify default values
|
|
|
|
QVERIFY(textInput->composingText().isEmpty());
|
|
|
|
QVERIFY(textInput->composingFallbackText().isEmpty());
|
|
|
|
QCOMPARE(textInput->composingTextCursorPosition(), 0);
|
|
|
|
|
|
|
|
textInput->enable(surface.data());
|
|
|
|
m_connection->flush();
|
|
|
|
m_display->dispatchEvents();
|
|
|
|
|
|
|
|
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
|
|
|
|
auto ti = m_seatInterface->focusedTextInput();
|
|
|
|
QVERIFY(ti);
|
|
|
|
|
|
|
|
// now let's pass through some pre-edit events
|
|
|
|
QSignalSpy composingTextChangedSpy(textInput.data(), &TextInput::composingTextChanged);
|
|
|
|
QVERIFY(composingTextChangedSpy.isValid());
|
|
|
|
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_data()
|
|
|
|
{
|
|
|
|
QTest::addColumn<TextInputInterfaceVersion>("version");
|
|
|
|
|
|
|
|
QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0;
|
|
|
|
QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TextInputTest::testCommit()
|
|
|
|
{
|
|
|
|
// this test verifies that the commit is handled correctly by the client
|
|
|
|
QScopedPointer<Surface> surface(m_compositor->createSurface());
|
|
|
|
auto serverSurface = waitForSurface();
|
|
|
|
QVERIFY(serverSurface);
|
|
|
|
QFETCH(TextInputInterfaceVersion, version);
|
|
|
|
QScopedPointer<TextInput> textInput(createTextInput(version));
|
|
|
|
QVERIFY(!textInput.isNull());
|
|
|
|
// 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.data());
|
|
|
|
m_connection->flush();
|
|
|
|
m_display->dispatchEvents();
|
|
|
|
|
|
|
|
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
|
|
|
|
auto ti = m_seatInterface->focusedTextInput();
|
|
|
|
QVERIFY(ti);
|
|
|
|
|
|
|
|
// now let's commit
|
|
|
|
QSignalSpy committedSpy(textInput.data(), &TextInput::committed);
|
|
|
|
QVERIFY(committedSpy.isValid());
|
|
|
|
ti->setCursorPosition(3, 4);
|
|
|
|
ti->deleteSurroundingText(2, 1);
|
|
|
|
ti->commit(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.moc"
|