Implement wl_text_input and zwp_text_input_v2 interfaces

Summary:
This change introduces support for text input. Text input allows to
compose text on the server (e.g. through a virtual keyboard) and sent
the composed text to the client.

There are multiple interfaces for text input. QtWayland 5.6 uses
wl_text_input, QtWayland 5.7 uses zwp_text_input_v2.

wl_text_input is from pre Wayland-Protocols times and considered as
UnstableV0 in this implementation. The other interface is UnstableV2.
Unfortunately the V2 variant is not yet part of Wayland-Protocols, but
used in Qt.

The implementation hides the different interfaces as good as possible.
The general idea is the same, the differences are rather minor.

This means changes to how interfaces are wrapped normally. On client
side in the Registry a manager is factored which represent either of
the two interfaces. Similar on the server side Display's factory method
takes an argument to decide which interface should be factored. This
way a user of the library can expose both interfaces and thus be
compatible with Qt 5.6 and Qt 5.7 onwards.

Reviewers: #plasma

Subscribers: plasma-devel

Tags: #plasma

Differential Revision: https://phabricator.kde.org/D1631
This commit is contained in:
Martin Gräßlin 2016-05-02 14:28:26 +02:00
parent 1834dde31a
commit 990e88ac1a
15 changed files with 2815 additions and 3 deletions

View file

@ -34,6 +34,9 @@ set(SERVER_LIB_SRCS
surface_interface.cpp surface_interface.cpp
subcompositor_interface.cpp subcompositor_interface.cpp
touch_interface.cpp touch_interface.cpp
textinput_interface.cpp
textinput_interface_v0.cpp
textinput_interface_v2.cpp
) )
ecm_add_wayland_server_protocol(SERVER_LIB_SRCS ecm_add_wayland_server_protocol(SERVER_LIB_SRCS
@ -100,6 +103,16 @@ ecm_add_wayland_server_protocol(SERVER_LIB_SRCS
BASENAME server_decoration BASENAME server_decoration
) )
ecm_add_wayland_server_protocol(SERVER_LIB_SRCS
PROTOCOL ${KWAYLAND_SOURCE_DIR}/src/client/protocols/text-input.xml
BASENAME text
)
ecm_add_wayland_server_protocol(SERVER_LIB_SRCS
PROTOCOL ${KWAYLAND_SOURCE_DIR}/src/client/protocols/text-input-unstable-v2.xml
BASENAME text-input-unstable-v2
)
add_library(KF5WaylandServer ${SERVER_LIB_SRCS}) add_library(KF5WaylandServer ${SERVER_LIB_SRCS})
generate_export_header(KF5WaylandServer generate_export_header(KF5WaylandServer
BASE_NAME BASE_NAME
@ -168,6 +181,7 @@ install(FILES
slide_interface.h slide_interface.h
subcompositor_interface.h subcompositor_interface.h
surface_interface.h surface_interface.h
textinput_interface.h
touch_interface.h touch_interface.h
DESTINATION ${KF5_INCLUDE_INSTALL_DIR}/KWayland/Server COMPONENT Devel DESTINATION ${KF5_INCLUDE_INSTALL_DIR}/KWayland/Server COMPONENT Devel
) )

View file

@ -297,3 +297,14 @@ add_executable(testPlasmaWindowModel ${testPlasmaWindowModel_SRCS})
target_link_libraries( testPlasmaWindowModel Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer) target_link_libraries( testPlasmaWindowModel Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer)
add_test(kwayland-testPlasmaWindowModel testPlasmaWindowModel) add_test(kwayland-testPlasmaWindowModel testPlasmaWindowModel)
ecm_mark_as_test(testPlasmaWindowModel) ecm_mark_as_test(testPlasmaWindowModel)
########################################################
# Test TextInput
########################################################
set( testTextInput_SRCS
test_text_input.cpp
)
add_executable(testTextInput ${testTextInput_SRCS})
target_link_libraries( testTextInput Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer)
add_test(kwayland-testTextInput testTextInput)
ecm_mark_as_test(testTextInput)

View file

@ -0,0 +1,917 @@
/********************************************************************
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(&registry, &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());
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
// 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
if (!updatesDirectly) {
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);
}
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"

View file

@ -44,10 +44,13 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
#include "../../src/server/subcompositor_interface.h" #include "../../src/server/subcompositor_interface.h"
#include "../../src/server/outputmanagement_interface.h" #include "../../src/server/outputmanagement_interface.h"
#include "../../src/server/outputdevice_interface.h" #include "../../src/server/outputdevice_interface.h"
#include "../../src/server/textinput_interface.h"
// Wayland // Wayland
#include <wayland-client-protocol.h> #include <wayland-client-protocol.h>
#include <wayland-dpms-client-protocol.h> #include <wayland-dpms-client-protocol.h>
#include <wayland-server-decoration-client-protocol.h> #include <wayland-server-decoration-client-protocol.h>
#include <wayland-text-input-v0-client-protocol.h>
#include <wayland-text-input-v2-client-protocol.h>
class TestWaylandRegistry : public QObject class TestWaylandRegistry : public QObject
{ {
@ -71,6 +74,8 @@ private Q_SLOTS:
void testBindSlideManager(); void testBindSlideManager();
void testBindDpmsManager(); void testBindDpmsManager();
void testBindServerSideDecorationManager(); void testBindServerSideDecorationManager();
void testBindTextInputManagerUnstableV0();
void testBindTextInputManagerUnstableV2();
void testGlobalSync(); void testGlobalSync();
void testGlobalSyncThreaded(); void testGlobalSyncThreaded();
void testRemoval(); void testRemoval();
@ -89,6 +94,8 @@ private:
KWayland::Server::DataDeviceManagerInterface *m_dataDeviceManager; KWayland::Server::DataDeviceManagerInterface *m_dataDeviceManager;
KWayland::Server::OutputManagementInterface *m_outputManagement; KWayland::Server::OutputManagementInterface *m_outputManagement;
KWayland::Server::ServerSideDecorationManagerInterface *m_serverSideDecorationManager; KWayland::Server::ServerSideDecorationManagerInterface *m_serverSideDecorationManager;
KWayland::Server::TextInputManagerInterface *m_textInputManagerV0;
KWayland::Server::TextInputManagerInterface *m_textInputManagerV2;
}; };
static const QString s_socketName = QStringLiteral("kwin-test-wayland-registry-0"); static const QString s_socketName = QStringLiteral("kwin-test-wayland-registry-0");
@ -105,6 +112,8 @@ TestWaylandRegistry::TestWaylandRegistry(QObject *parent)
, m_dataDeviceManager(nullptr) , m_dataDeviceManager(nullptr)
, m_outputManagement(nullptr) , m_outputManagement(nullptr)
, m_serverSideDecorationManager(nullptr) , m_serverSideDecorationManager(nullptr)
, m_textInputManagerV0(nullptr)
, m_textInputManagerV2(nullptr)
{ {
} }
@ -137,6 +146,12 @@ void TestWaylandRegistry::init()
m_display->createDpmsManager()->create(); m_display->createDpmsManager()->create();
m_serverSideDecorationManager = m_display->createServerSideDecorationManager(); m_serverSideDecorationManager = m_display->createServerSideDecorationManager();
m_serverSideDecorationManager->create(); m_serverSideDecorationManager->create();
m_textInputManagerV0 = m_display->createTextInputManager(KWayland::Server::TextInputInterfaceVersion::UnstableV0);
QCOMPARE(m_textInputManagerV0->interfaceVersion(), KWayland::Server::TextInputInterfaceVersion::UnstableV0);
m_textInputManagerV0->create();
m_textInputManagerV2 = m_display->createTextInputManager(KWayland::Server::TextInputInterfaceVersion::UnstableV2);
QCOMPARE(m_textInputManagerV2->interfaceVersion(), KWayland::Server::TextInputInterfaceVersion::UnstableV2);
m_textInputManagerV2->create();
} }
void TestWaylandRegistry::cleanup() void TestWaylandRegistry::cleanup()
@ -264,6 +279,16 @@ void TestWaylandRegistry::testBindServerSideDecorationManager()
TEST_BIND(KWayland::Client::Registry::Interface::ServerSideDecorationManager, SIGNAL(serverSideDecorationManagerAnnounced(quint32,quint32)), bindServerSideDecorationManager, org_kde_kwin_server_decoration_manager_destroy) TEST_BIND(KWayland::Client::Registry::Interface::ServerSideDecorationManager, SIGNAL(serverSideDecorationManagerAnnounced(quint32,quint32)), bindServerSideDecorationManager, org_kde_kwin_server_decoration_manager_destroy)
} }
void TestWaylandRegistry::testBindTextInputManagerUnstableV0()
{
TEST_BIND(KWayland::Client::Registry::Interface::TextInputManagerUnstableV0, SIGNAL(textInputManagerUnstableV0Announced(quint32,quint32)), bindTextInputManagerUnstableV0, wl_text_input_manager_destroy)
}
void TestWaylandRegistry::testBindTextInputManagerUnstableV2()
{
TEST_BIND(KWayland::Client::Registry::Interface::TextInputManagerUnstableV2, SIGNAL(textInputManagerUnstableV2Announced(quint32,quint32)), bindTextInputManagerUnstableV2, zwp_text_input_manager_v2_destroy)
}
#undef TEST_BIND #undef TEST_BIND
void TestWaylandRegistry::testRemoval() void TestWaylandRegistry::testRemoval()

View file

@ -39,6 +39,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
#include "slide_interface.h" #include "slide_interface.h"
#include "shell_interface.h" #include "shell_interface.h"
#include "subcompositor_interface.h" #include "subcompositor_interface.h"
#include "textinput_interface_p.h"
#include <QCoreApplication> #include <QCoreApplication>
#include <QDebug> #include <QDebug>
@ -334,6 +335,23 @@ ServerSideDecorationManagerInterface *Display::createServerSideDecorationManager
return d; return d;
} }
TextInputManagerInterface *Display::createTextInputManager(const TextInputInterfaceVersion &version, QObject *parent)
{
TextInputManagerInterface *t = nullptr;
switch (version) {
case TextInputInterfaceVersion::UnstableV0:
t = new TextInputManagerUnstableV0Interface(this, parent);
break;
case TextInputInterfaceVersion::UnstableV1:
// unsupported
return nullptr;
case TextInputInterfaceVersion::UnstableV2:
t = new TextInputManagerUnstableV2Interface(this, parent);
}
connect(this, &Display::aboutToTerminate, t, [t] { delete t; });
return t;
}
void Display::createShm() void Display::createShm()
{ {
Q_ASSERT(d->display); Q_ASSERT(d->display);

View file

@ -69,6 +69,8 @@ class ServerSideDecorationManagerInterface;
class SlideManagerInterface; class SlideManagerInterface;
class ShellInterface; class ShellInterface;
class SubCompositorInterface; class SubCompositorInterface;
enum class TextInputInterfaceVersion;
class TextInputManagerInterface;
/** /**
* @brief Class holding the Wayland server display loop. * @brief Class holding the Wayland server display loop.
@ -170,6 +172,12 @@ public:
* @since 5.6 * @since 5.6
**/ **/
ServerSideDecorationManagerInterface *createServerSideDecorationManager(QObject *parent = nullptr); ServerSideDecorationManagerInterface *createServerSideDecorationManager(QObject *parent = nullptr);
/**
* Create the text input manager in interface @p version.
* @returns The created manager object
* @since 5.23
**/
TextInputManagerInterface *createTextInputManager(const TextInputInterfaceVersion &version, QObject *parent = nullptr);
/** /**
* Gets the ClientConnection for the given @p client. * Gets the ClientConnection for the given @p client.

View file

@ -27,6 +27,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
#include "pointer_interface.h" #include "pointer_interface.h"
#include "pointer_interface_p.h" #include "pointer_interface_p.h"
#include "surface_interface.h" #include "surface_interface.h"
#include "textinput_interface_p.h"
// Wayland // Wayland
#ifndef WL_SEAT_NAME_SINCE_VERSION #ifndef WL_SEAT_NAME_SINCE_VERSION
#define WL_SEAT_NAME_SINCE_VERSION 2 #define WL_SEAT_NAME_SINCE_VERSION 2
@ -204,6 +205,11 @@ DataDeviceInterface *SeatInterface::Private::dataDeviceForSurface(SurfaceInterfa
return interfaceForSurface(surface, dataDevices); return interfaceForSurface(surface, dataDevices);
} }
TextInputInterface *SeatInterface::Private::textInputForSurface(SurfaceInterface *surface) const
{
return interfaceForSurface(surface, textInputs);
}
void SeatInterface::Private::registerDataDevice(DataDeviceInterface *dataDevice) void SeatInterface::Private::registerDataDevice(DataDeviceInterface *dataDevice)
{ {
Q_ASSERT(dataDevice->seat() == q); Q_ASSERT(dataDevice->seat() == q);
@ -269,6 +275,33 @@ void SeatInterface::Private::registerDataDevice(DataDeviceInterface *dataDevice)
} }
} }
void SeatInterface::Private::registerTextInput(TextInputInterface *ti)
{
// text input version 0 might call this multiple times
if (textInputs.contains(ti)) {
return;
}
textInputs << ti;
if (textInput.focus.surface && textInput.focus.surface->client() == ti->client()) {
// this is a text input for the currently focused text input surface
if (!textInput.focus.textInput) {
textInput.focus.textInput = ti;
ti->d_func()->sendEnter(textInput.focus.surface, textInput.focus.serial);
emit q->focusedTextInputChanged();
}
}
QObject::connect(ti, &QObject::destroyed, q,
[this, ti] {
textInputs.removeAt(textInputs.indexOf(ti));
if (textInput.focus.textInput == ti) {
textInput.focus.textInput = nullptr;
emit q->focusedTextInputChanged();
}
}
);
}
void SeatInterface::Private::endDrag(quint32 serial) void SeatInterface::Private::endDrag(quint32 serial)
{ {
auto target = drag.target; auto target = drag.target;
@ -829,10 +862,13 @@ void SeatInterface::setFocusedKeyboardSurface(SurfaceInterface *surface)
} }
} }
} }
if (!k) { if (k) {
return;
}
k->setFocusedSurface(surface, serial); k->setFocusedSurface(surface, serial);
}
// focused text input surface follows keyboard
if (hasKeyboard()) {
setFocusedTextInputSurface(surface);
}
} }
void SeatInterface::setKeymap(int fd, quint32 size) void SeatInterface::setKeymap(int fd, quint32 size)
@ -1155,5 +1191,53 @@ DataDeviceInterface *SeatInterface::dragSource() const
return d->drag.source; return d->drag.source;
} }
void SeatInterface::setFocusedTextInputSurface(SurfaceInterface *surface)
{
Q_D();
const quint32 serial = d->display->nextSerial();
const auto old = d->textInput.focus.textInput;
if (d->textInput.focus.textInput) {
// TODO: setFocusedSurface like in other interfaces
d->textInput.focus.textInput->d_func()->sendLeave(serial, d->textInput.focus.surface);
}
if (d->textInput.focus.surface) {
disconnect(d->textInput.focus.destroyConnection);
}
d->textInput.focus = Private::TextInput::Focus();
d->textInput.focus.surface = surface;
TextInputInterface *t = d->textInputForSurface(surface);
if (t && !t->resource()) {
t = nullptr;
}
d->textInput.focus.textInput = t;
if (d->textInput.focus.surface) {
d->textInput.focus.destroyConnection = connect(surface, &QObject::destroyed, this,
[this] {
setFocusedTextInputSurface(nullptr);
}
);
d->textInput.focus.serial = serial;
}
if (t) {
// TODO: setFocusedSurface like in other interfaces
t->d_func()->sendEnter(surface, serial);
}
if (old != t) {
emit focusedTextInputChanged();
}
}
SurfaceInterface *SeatInterface::focusedTextInputSurface() const
{
Q_D();
return d->textInput.focus.surface;
}
TextInputInterface *SeatInterface::focusedTextInput() const
{
Q_D();
return d->textInput.focus.textInput;
}
} }
} }

View file

@ -41,6 +41,7 @@ namespace Server
class DataDeviceInterface; class DataDeviceInterface;
class Display; class Display;
class SurfaceInterface; class SurfaceInterface;
class TextInputInterface;
/** /**
* @brief Represents a Seat on the Wayland Display. * @brief Represents a Seat on the Wayland Display.
@ -413,6 +414,16 @@ public:
**/ **/
qint32 keyRepeatDelay() const; qint32 keyRepeatDelay() const;
/**
* Passes keyboard focus to @p surface.
*
* If the SeatInterface has the keyboard capability, also the focused
* text input surface will be set to @p surface.
*
* @see focusedKeyboardSurface
* @see hasKeyboard
* @see setFocusedTextInputSurface
**/
void setFocusedKeyboardSurface(SurfaceInterface *surface); void setFocusedKeyboardSurface(SurfaceInterface *surface);
SurfaceInterface *focusedKeyboardSurface() const; SurfaceInterface *focusedKeyboardSurface() const;
KeyboardInterface *focusedKeyboard() const; KeyboardInterface *focusedKeyboard() const;
@ -435,6 +446,50 @@ public:
bool isTouchSequence() const; bool isTouchSequence() const;
///@} ///@}
/**
* @name Text input related methods.
**/
///@{
/**
* Passes text input focus to @p surface.
*
* If the SeatInterface has the keyboard capability this method will
* be invoked automatically when setting the focused keyboard surface.
*
* In case there is a TextInputInterface for the @p surface, the enter
* event will be triggered on the TextInputInterface for @p surface.
* The focusedTextInput will be set to that TextInputInterface. If there
* is no TextInputInterface for that @p surface, it might get updated later on.
* In both cases the signal focusedTextInputChanged will be emitted.
*
* @see focusedTextInputSurface
* @see focusedTextInput
* @see focusedTextInputChanged
* @see setFocusedKeyboardSurface
* @since 5.23
**/
void setFocusedTextInputSurface(SurfaceInterface *surface);
/**
* @returns The SurfaceInterface which is currently focused for text input.
* @see setFocusedTextInputSurface
* @since 5.23
**/
SurfaceInterface *focusedTextInputSurface() const;
/**
* The currently focused text input, may be @c null even if there is a
* focused text input surface set.
*
* The focused text input might not be enabled for the @link{focusedTextInputSurface}.
* It is recommended to check the enabled state before interacting with the
* TextInputInterface.
*
* @see focusedTextInputChanged
* @see focusedTextInputSurface
* @since 5.23
**/
TextInputInterface *focusedTextInput() const;
///@}
static SeatInterface *get(wl_resource *native); static SeatInterface *get(wl_resource *native);
Q_SIGNALS: Q_SIGNALS:
@ -473,10 +528,18 @@ Q_SIGNALS:
* @see dragSurface * @see dragSurface
**/ **/
void dragSurfaceChanged(); void dragSurfaceChanged();
/**
* Emitted whenever the focused text input changed.
* @see focusedTextInput
* @since 5.23
**/
void focusedTextInputChanged();
private: private:
friend class Display; friend class Display;
friend class DataDeviceManagerInterface; friend class DataDeviceManagerInterface;
friend class TextInputManagerUnstableV0Interface;
friend class TextInputManagerUnstableV2Interface;
explicit SeatInterface(Display *display, QObject *parent); explicit SeatInterface(Display *display, QObject *parent);
class Private; class Private;

View file

@ -34,6 +34,7 @@ namespace Server
{ {
class DataDeviceInterface; class DataDeviceInterface;
class TextInputInterface;
class SeatInterface::Private : public Global::Private class SeatInterface::Private : public Global::Private
{ {
@ -46,7 +47,9 @@ public:
KeyboardInterface *keyboardForSurface(SurfaceInterface *surface) const; KeyboardInterface *keyboardForSurface(SurfaceInterface *surface) const;
TouchInterface *touchForSurface(SurfaceInterface *surface) const; TouchInterface *touchForSurface(SurfaceInterface *surface) const;
DataDeviceInterface *dataDeviceForSurface(SurfaceInterface *surface) const; DataDeviceInterface *dataDeviceForSurface(SurfaceInterface *surface) const;
TextInputInterface *textInputForSurface(SurfaceInterface *surface) const;
void registerDataDevice(DataDeviceInterface *dataDevice); void registerDataDevice(DataDeviceInterface *dataDevice);
void registerTextInput(TextInputInterface *textInput);
void endDrag(quint32 serial); void endDrag(quint32 serial);
QString name; QString name;
@ -59,6 +62,7 @@ public:
QVector<KeyboardInterface*> keyboards; QVector<KeyboardInterface*> keyboards;
QVector<TouchInterface*> touchs; QVector<TouchInterface*> touchs;
QVector<DataDeviceInterface*> dataDevices; QVector<DataDeviceInterface*> dataDevices;
QVector<TextInputInterface*> textInputs;
DataDeviceInterface *currentSelection = nullptr; DataDeviceInterface *currentSelection = nullptr;
// Pointer related members // Pointer related members
@ -122,6 +126,17 @@ public:
Keyboard keys; Keyboard keys;
void updateKey(quint32 key, Keyboard::State state); void updateKey(quint32 key, Keyboard::State state);
struct TextInput {
struct Focus {
SurfaceInterface *surface = nullptr;
QMetaObject::Connection destroyConnection;
quint32 serial = 0;
TextInputInterface *textInput = nullptr;
};
Focus focus;
};
TextInput textInput;
struct Touch { struct Touch {
struct Focus { struct Focus {
SurfaceInterface *surface = nullptr; SurfaceInterface *surface = nullptr;

View file

@ -0,0 +1,411 @@
/****************************************************************************
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/>.
****************************************************************************/
#include "textinput_interface_p.h"
#include "display.h"
#include "global_p.h"
#include "resource_p.h"
#include "seat_interface_p.h"
#include "surface_interface.h"
#include <QVector>
#include <wayland-text-server-protocol.h>
#include <wayland-text-input-unstable-v2-server-protocol.h>
namespace KWayland
{
namespace Server
{
void TextInputInterface::Private::destroyCallback(wl_client *client, wl_resource *resource)
{
Q_UNUSED(client)
wl_resource_destroy(resource);
}
void TextInputInterface::Private::activateCallback(wl_client *client, wl_resource *resource, wl_resource *seat, wl_resource *surface)
{
auto p = cast<Private>(resource);
Q_ASSERT(*p->client == client);
p->requestActivate(SeatInterface::get(seat), SurfaceInterface::get(surface));
}
void TextInputInterface::Private::deactivateCallback(wl_client *client, wl_resource *resource, wl_resource *seat)
{
auto p = cast<Private>(resource);
Q_ASSERT(*p->client == client);
p->requestDeactivate(SeatInterface::get(seat));
}
void TextInputInterface::Private::enableCallback(wl_client *client, wl_resource *resource, wl_resource *surface)
{
auto p = cast<Private>(resource);
Q_ASSERT(*p->client == client);
p->requestActivate(nullptr, SurfaceInterface::get(surface));
}
void TextInputInterface::Private::disableCallback(wl_client *client, wl_resource *resource, wl_resource *surface)
{
Q_UNUSED(surface)
auto p = cast<Private>(resource);
Q_ASSERT(*p->client == client);
p->requestDeactivate(nullptr);
}
void TextInputInterface::Private::showInputPanelCallback(wl_client *client, wl_resource *resource)
{
auto p = cast<Private>(resource);
Q_ASSERT(*p->client == client);
emit p->q_func()->requestShowInputPanel();
}
void TextInputInterface::Private::hideInputPanelCallback(wl_client *client, wl_resource *resource)
{
auto p = cast<Private>(resource);
Q_ASSERT(*p->client == client);
emit p->q_func()->requestHideInputPanel();
}
void TextInputInterface::Private::resetCallback(wl_client *client, wl_resource *resource)
{
auto p = cast<Private>(resource);
Q_ASSERT(*p->client == client);
emit p->q_func()->requestReset();
}
void TextInputInterface::Private::setSurroundingTextCallback(wl_client *client, wl_resource *resource, const char * text, uint32_t cursor, uint32_t anchor)
{
setSurroundingText2Callback(client, resource, text, cursor, anchor);
}
void TextInputInterface::Private::setSurroundingText2Callback(wl_client *client, wl_resource *resource, const char * text, int32_t cursor, int32_t anchor)
{
auto p = cast<Private>(resource);
Q_ASSERT(*p->client == client);
p->surroundingText = QByteArray(text);
// TODO: make qint32
p->surroundingTextCursorPosition = cursor;
p->surroundingTextSelectionAnchor = anchor;
emit p->q_func()->surroundingTextChanged();
}
namespace {
static TextInputInterface::ContentHints waylandHintsToKWayland(wl_text_input_content_hint wlHints)
{
TextInputInterface::ContentHints hints = TextInputInterface::ContentHint::None;
if (wlHints & WL_TEXT_INPUT_CONTENT_HINT_AUTO_COMPLETION) {
hints |= TextInputInterface::ContentHint::AutoCompletion;
}
if (wlHints & WL_TEXT_INPUT_CONTENT_HINT_AUTO_CORRECTION) {
hints |= TextInputInterface::ContentHint::AutoCorrection;
}
if (wlHints & WL_TEXT_INPUT_CONTENT_HINT_AUTO_CAPITALIZATION) {
hints |= TextInputInterface::ContentHint::AutoCapitalization;
}
if (wlHints & WL_TEXT_INPUT_CONTENT_HINT_LOWERCASE) {
hints |= TextInputInterface::ContentHint::LowerCase;
}
if (wlHints & WL_TEXT_INPUT_CONTENT_HINT_UPPERCASE) {
hints |= TextInputInterface::ContentHint::UpperCase;
}
if (wlHints & WL_TEXT_INPUT_CONTENT_HINT_TITLECASE) {
hints |= TextInputInterface::ContentHint::TitleCase;
}
if (wlHints & WL_TEXT_INPUT_CONTENT_HINT_HIDDEN_TEXT) {
hints |= TextInputInterface::ContentHint::HiddenText;
}
if (wlHints & WL_TEXT_INPUT_CONTENT_HINT_SENSITIVE_DATA) {
hints |= TextInputInterface::ContentHint::SensitiveData;
}
if (wlHints & WL_TEXT_INPUT_CONTENT_HINT_LATIN) {
hints |= TextInputInterface::ContentHint::Latin;
}
if (wlHints & WL_TEXT_INPUT_CONTENT_HINT_MULTILINE) {
hints |= TextInputInterface::ContentHint::MultiLine;
}
return hints;
}
static TextInputInterface::ContentPurpose waylandPurposeToKWayland(wl_text_input_content_purpose purpose)
{
switch (purpose) {
case WL_TEXT_INPUT_CONTENT_PURPOSE_ALPHA:
return TextInputInterface::ContentPurpose::Alpha;
case WL_TEXT_INPUT_CONTENT_PURPOSE_DIGITS:
return TextInputInterface::ContentPurpose::Digits;
case WL_TEXT_INPUT_CONTENT_PURPOSE_NUMBER:
return TextInputInterface::ContentPurpose::Number;
case WL_TEXT_INPUT_CONTENT_PURPOSE_PHONE:
return TextInputInterface::ContentPurpose::Phone;
case WL_TEXT_INPUT_CONTENT_PURPOSE_URL:
return TextInputInterface::ContentPurpose::Url;
case WL_TEXT_INPUT_CONTENT_PURPOSE_EMAIL:
return TextInputInterface::ContentPurpose::Email;
case WL_TEXT_INPUT_CONTENT_PURPOSE_NAME:
return TextInputInterface::ContentPurpose::Name;
case WL_TEXT_INPUT_CONTENT_PURPOSE_PASSWORD:
return TextInputInterface::ContentPurpose::Password;
case WL_TEXT_INPUT_CONTENT_PURPOSE_DATE:
return TextInputInterface::ContentPurpose::Date;
case WL_TEXT_INPUT_CONTENT_PURPOSE_TIME:
return TextInputInterface::ContentPurpose::Time;
case WL_TEXT_INPUT_CONTENT_PURPOSE_DATETIME:
return TextInputInterface::ContentPurpose::DateTime;
case WL_TEXT_INPUT_CONTENT_PURPOSE_TERMINAL:
return TextInputInterface::ContentPurpose::Terminal;
case WL_TEXT_INPUT_CONTENT_PURPOSE_NORMAL:
default:
return TextInputInterface::ContentPurpose::Normal;
}
}
}
void TextInputInterface::Private::setContentTypeCallback(wl_client *client, wl_resource *resource, uint32_t hint, uint32_t wlPurpose)
{
auto p = cast<Private>(resource);
Q_ASSERT(*p->client == client);
// TODO: pass through Private impl
const auto hints = waylandHintsToKWayland(wl_text_input_content_hint(hint));
const auto purpose = waylandPurposeToKWayland(wl_text_input_content_purpose(wlPurpose));
if (hints != p->contentHints || purpose != p->contentPurpose) {
p->contentHints = hints;
p->contentPurpose = purpose;
emit p->q_func()->contentTypeChanged();
}
}
void TextInputInterface::Private::setCursorRectangleCallback(wl_client *client, wl_resource *resource, int32_t x, int32_t y, int32_t width, int32_t height)
{
auto p = cast<Private>(resource);
Q_ASSERT(*p->client == client);
const QRect rect = QRect(x, y, width, height);
if (p->cursorRectangle != rect) {
p->cursorRectangle = rect;
emit p->q_func()->cursorRectangleChanged(p->cursorRectangle);
}
}
void TextInputInterface::Private::setPreferredLanguageCallback(wl_client *client, wl_resource *resource, const char * language)
{
auto p = cast<Private>(resource);
Q_ASSERT(*p->client == client);
const QByteArray preferredLanguage = QByteArray(language);
if (p->preferredLanguage != preferredLanguage) {
p->preferredLanguage = preferredLanguage;
emit p->q_func()->preferredLanguageChanged(p->preferredLanguage);
}
}
void TextInputInterface::Private::commitStateCallback(wl_client *client, wl_resource *resource, uint32_t serial)
{
auto p = cast<Private>(resource);
Q_ASSERT(*p->client == client);
p->latestState = serial;
}
void TextInputInterface::Private::invokeActionCallback(wl_client *client, wl_resource *resource, uint32_t button, uint32_t index)
{
Q_UNUSED(button)
Q_UNUSED(index)
// TODO: implement
auto p = cast<Private>(resource);
Q_ASSERT(*p->client == client);
}
TextInputInterface::Private::Private(TextInputInterface *q, Global *c, wl_resource *parentResource, const wl_interface *interface, const void *implementation)
: Resource::Private(q, c, parentResource, interface, implementation)
{
}
TextInputInterface::Private::~Private()
{
if (resource) {
wl_resource_destroy(resource);
resource = nullptr;
}
}
QByteArray TextInputInterface::preferredLanguage() const
{
Q_D();
return d->preferredLanguage;
}
TextInputInterface::ContentHints TextInputInterface::contentHints() const
{
Q_D();
return d->contentHints;
}
TextInputInterface::ContentPurpose TextInputInterface::contentPurpose() const
{
Q_D();
return d->contentPurpose;
}
QByteArray TextInputInterface::surroundingText() const
{
Q_D();
return d->surroundingText;
}
qint32 TextInputInterface::surroundingTextCursorPosition() const
{
Q_D();
return d->surroundingTextCursorPosition;
}
qint32 TextInputInterface::surroundingTextSelectionAnchor() const
{
Q_D();
return d->surroundingTextSelectionAnchor;
}
void TextInputInterface::preEdit(const QByteArray &text, const QByteArray &commit)
{
Q_D();
d->preEdit(text, commit);
}
void TextInputInterface::commit(const QByteArray &text)
{
Q_D();
d->commit(text);
}
void TextInputInterface::keysymPressed(quint32 keysym, Qt::KeyboardModifiers modifiers)
{
Q_UNUSED(modifiers)
Q_D();
d->keysymPressed(keysym, modifiers);
}
void TextInputInterface::keysymReleased(quint32 keysym, Qt::KeyboardModifiers modifiers)
{
Q_D();
d->keysymReleased(keysym, modifiers);
}
void TextInputInterface::deleteSurroundingText(quint32 beforeLength, quint32 afterLength)
{
Q_D();
d->deleteSurroundingText(beforeLength, afterLength);
}
void TextInputInterface::setCursorPosition(qint32 index, qint32 anchor)
{
Q_D();
d->setCursorPosition(index, anchor);
}
void TextInputInterface::setTextDirection(Qt::LayoutDirection direction)
{
Q_D();
d->setTextDirection(direction);
}
void TextInputInterface::setPreEditCursor(qint32 index)
{
Q_D();
d->setPreEditCursor(index);
}
void TextInputInterface::setInputPanelState(bool visible, const QRect &overlappedSurfaceArea)
{
Q_D();
if (d->inputPanelVisible == visible && d->overlappedSurfaceArea == overlappedSurfaceArea) {
// not changed
return;
}
d->inputPanelVisible = visible;
d->overlappedSurfaceArea = overlappedSurfaceArea;
d->sendInputPanelState();
}
void TextInputInterface::setLanguage(const QByteArray &languageTag)
{
Q_D();
if (d->language == languageTag) {
// not changed
return;
}
d->language = languageTag;
d->sendLanguage();
}
TextInputInterfaceVersion TextInputInterface::interfaceVersion() const
{
Q_D();
return d->interfaceVersion();
}
QPointer<SurfaceInterface> TextInputInterface::surface() const
{
Q_D();
return d->surface;
}
QRect TextInputInterface::cursorRectangle() const
{
Q_D();
return d->cursorRectangle;
}
bool TextInputInterface::isEnabled() const
{
Q_D();
return d->enabled;
}
TextInputInterface::Private *TextInputInterface::d_func() const
{
return reinterpret_cast<Private*>(d.data());
}
TextInputInterface::TextInputInterface(Private *p, QObject *parent)
: Resource(p, parent)
{
}
TextInputInterface::~TextInputInterface() = default;
TextInputManagerInterface::TextInputManagerInterface(Private *d, QObject *parent)
: Global(d, parent)
{
}
TextInputManagerInterface::~TextInputManagerInterface() = default;
TextInputInterfaceVersion TextInputManagerInterface::interfaceVersion() const
{
Q_D();
return d->interfaceVersion;
}
TextInputManagerInterface::Private *TextInputManagerInterface::d_func() const
{
return reinterpret_cast<Private*>(d.data());
}
}
}

View file

@ -0,0 +1,439 @@
/****************************************************************************
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/>.
****************************************************************************/
#ifndef KWAYLAND_SERVER_TEXTINPUT_INTERFACE_H
#define KWAYLAND_SERVER_TEXTINPUT_INTERFACE_H
#include "global.h"
#include "resource.h"
#include <KWayland/Server/kwaylandserver_export.h>
namespace KWayland
{
namespace Server
{
class Display;
class SeatInterface;
class SurfaceInterface;
class TextInputInterface;
/**
* Enum describing the different InterfaceVersion encapsulated in this implementation
*
* @since 5.23
**/
enum class TextInputInterfaceVersion {
/**
* wl_text_input as the non-standardized version
**/
UnstableV0,
/**
* not supported version
**/
UnstableV1,
/**
* zwp_text_input_v2 as used by Qt 5.7
**/
UnstableV2
};
/**
* @brief Represent the Global for the interface.
*
* The class can represent different interfaces. Which concrete interface is represented
* can be determined through @link{interfaceVersion}.
*
* To create a TextInputManagerInterface use @link{Display::createTextInputManager}
*
* @since 5.23
**/
class KWAYLANDSERVER_EXPORT TextInputManagerInterface : public Global
{
Q_OBJECT
public:
virtual ~TextInputManagerInterface();
/**
* @returns The interface version used by this TextInputManagerInterface
**/
TextInputInterfaceVersion interfaceVersion() const;
protected:
class Private;
explicit TextInputManagerInterface(Private *d, QObject *parent = nullptr);
private:
Private *d_func() const;
};
/**
* @brief Represents a generic Resource for a text input object.
*
* This class does not directly correspond to a Wayland resource, but is a generic contract
* for any interface which implements a text input, e.g. the unstable wl_text_input interface.
*
* It does not expose the actual interface to cover up the fact that the interface is unstable
* and might change. If one needs to know the actual used protocol, use the method @link{interfaceVersion}.
*
* A TextInputInterface gets created by the @link{TextInputManagerInterface}. The individual
* instances are not exposed directly. The SeatInterface provides access to the currently active
* TextInputInterface. This is evaluated automatically based on which SurfaceInterface has
* keyboard focus.
*
* @see TextInputManagerInterface
* @see SeatInterface
* @since 5.23
**/
class KWAYLANDSERVER_EXPORT TextInputInterface : public Resource
{
Q_OBJECT
public:
virtual ~TextInputInterface();
/**
* ContentHint allows to modify the behavior of the text input.
**/
enum class ContentHint : uint32_t {
/**
* no special behaviour
*/
None = 0,
/**
* suggest word completions
*/
AutoCompletion = 1 << 0,
/**
* suggest word corrections
*/
AutoCorrection = 1 << 1,
/**
* switch to uppercase letters at the start of a sentence
*/
AutoCapitalization = 1 << 2,
/**
* prefer lowercase letters
*/
LowerCase = 1 << 3,
/**
* prefer uppercase letters
*/
UpperCase = 1 << 4,
/**
* prefer casing for titles and headings (can be language dependent)
*/
TitleCase = 1 << 5,
/**
* characters should be hidden
*/
HiddenText = 1 << 6,
/**
* typed text should not be stored
*/
SensitiveData = 1 << 7,
/**
* just latin characters should be entered
*/
Latin = 1 << 8,
/**
* the text input is multi line
*/
MultiLine = 1 << 9
};
Q_DECLARE_FLAGS(ContentHints, ContentHint)
/**
* The ContentPurpose allows to specify the primary purpose of a text input.
*
* This allows an input method to show special purpose input panels with
* extra characters or to disallow some characters.
*/
enum class ContentPurpose : uint32_t {
/**
* default input, allowing all characters
*/
Normal,
/**
* allow only alphabetic characters
**/
Alpha,
/**
* allow only digits
*/
Digits,
/**
* input a number (including decimal separator and sign)
*/
Number,
/**
* input a phone number
*/
Phone,
/**
* input an URL
*/
Url,
/**
* input an email address
**/
Email,
/**
* input a name of a person
*/
Name,
/**
* input a password
*/
Password,
/**
* input a date
*/
Date,
/**
* input a time
*/
Time,
/**
* input a date and time
*/
DateTime,
/**
* input for a terminal
*/
Terminal
};
/**
* @returns The interface version used by this TextInputInterface
**/
TextInputInterfaceVersion interfaceVersion() const;
/**
* The preferred language as a RFC-3066 format language tag.
*
* This can be used by the server to show a language specific virtual keyboard layout.
* @see preferredLanguageChanged
**/
QByteArray preferredLanguage() const;
/**
* @see cursorRectangleChanged
**/
QRect cursorRectangle() const;
/**
* @see contentTypeChanged
**/
ContentPurpose contentPurpose() const;
/**
*@see contentTypeChanged
**/
ContentHints contentHints() const;
/**
* @returns The plain surrounding text around the input position.
* @see surroundingTextChanged
* @see surroundingTextCursorPosition
* @see surroundingTextSelectionAnchor
**/
QByteArray surroundingText() const;
/**
* @returns The byte offset of current cursor position within the @link {surroundingText}
* @see surroundingText
* @see surroundingTextChanged
**/
qint32 surroundingTextCursorPosition() const;
/**
* The byte offset of the selection anchor within the @link{surroundingText}.
*
* If there is no selected text this is the same as cursor.
* @return The byte offset of the selection anchor
* @see surroundingText
* @see surroundingTextChanged
**/
qint32 surroundingTextSelectionAnchor() const;
/**
* @return The surface the TextInputInterface is enabled on
* @see isEnabled
* @see enabledChanged
**/
QPointer<SurfaceInterface> surface() const;
/**
* @return Whether the TextInputInterface is currently enabled for a SurfaceInterface.
* @see surface
* @see enabledChanged
**/
bool isEnabled() const;
/**
* Notify when a new composing @p text (pre-edit) should be set around the
* current cursor position. Any previously set composing text should
* be removed.
*
* The @p commitText can be used to replace the preedit text on reset
* (for example on unfocus).
*
* @param text The new utf8-encoded pre-edit text
* @param commitText Utf8-encoded text to replace preedit text on reset
* @see commit
* @see preEditCursor
**/
void preEdit(const QByteArray &text, const QByteArray &commitText);
/**
* Notify when @p text should be inserted into the editor widget.
* The text to commit could be either just a single character after a key press or the
* result of some composing (@link{preEdit}). It could be also an empty text
* when some text should be removed (see @link{deleteSurroundingText}) or when
* the input cursor should be moved (see @link{cursorPosition}).
*
* Any previously set composing text should be removed.
* @param text The utf8-encoded text to be inserted into the editor widget
* @see preEdit
* @see deleteSurroundingText
**/
void commit(const QByteArray &text);
/**
* Sets the cursor position inside the composing text (as byte offset) relative to the
* start of the composing text. When @p index is a negative number no cursor is shown.
*
* The Client applies the @p index together with @link{preEdit}.
* @param index The cursor position relative to the start of the composing text
* @see preEdit
**/
void setPreEditCursor(qint32 index);
/**
* Notify when the text around the current cursor position should be deleted.
*
* The Client processes this event together with the commit string
*
* @param beforeLength length of text before current cursor positon.
* @param afterLength length of text after current cursor positon.
* @see commit
**/
void deleteSurroundingText(quint32 beforeLength, quint32 afterLength);
/**
* Notify when the cursor @p index or @p anchor position should be modified.
*
* The Client applies this together with the commit string.
**/
void setCursorPosition(qint32 index, qint32 anchor);
/**
* Sets the text @p direction of input text.
**/
void setTextDirection(Qt::LayoutDirection direction);
void keysymPressed(quint32 keysym, Qt::KeyboardModifiers modifiers = Qt::NoModifier);
void keysymReleased(quint32 keysym, Qt::KeyboardModifiers modifiers = Qt::NoModifier);
/**
* Informs the client about changes in the visibility of the input panel (virtual keyboard).
*
* The @p overlappedSurfaceArea defines the area overlapped by the input panel (virtual keyboard)
* on the SurfaceInterface having the text focus in surface local coordinates.
*
* @param visible Whether the input panel is currently visible
* @param overlappedSurfaceArea The overlapping area in surface local coordinates
**/
void setInputPanelState(bool visible, const QRect &overlappedSurfaceArea);
/**
* Sets the language of the input text. The @p languageTag is a RFC-3066 format language tag.
**/
void setLanguage(const QByteArray &languageTag);
Q_SIGNALS:
/**
* Requests input panels (virtual keyboard) to show.
* @see requestHideInputPanel
**/
void requestShowInputPanel();
/**
* Requests input panels (virtual keyboard) to hide.
* @see requestShowInputPanel
**/
void requestHideInputPanel();
/**
* Invoked by the client when the input state should be
* reset, for example after the text was changed outside of the normal
* input method flow.
**/
void requestReset();
/**
* Emitted whenever the preffered @p language changes.
* @see preferredLanguage
**/
void preferredLanguageChanged(const QByteArray &language);
/**
* @see cursorRectangle
**/
void cursorRectangleChanged(const QRect &rect);
/**
* Emitted when the @link{contentPurpose} and/or @link{contentHints} changes.
* @see contentPurpose
* @see contentHints
**/
void contentTypeChanged();
/**
* Emitted when the @link{surroundingText}, @link{surroundingTextCursorPosition}
* and/or @link{surroundingTextSelectionAnchor} changed.
* @see surroundingText
* @see surroundingTextCursorPosition
* @see surroundingTextSelectionAnchor
**/
void surroundingTextChanged();
/**
* Emitted whenever this TextInputInterface gets enabled or disabled for a SurfaceInterface.
* @see isEnabled
* @see surface
**/
void enabledChanged();
protected:
class Private;
explicit TextInputInterface(Private *p, QObject *parent = nullptr);
private:
friend class TextInputManagerUnstableV0Interface;
friend class TextInputManagerUnstableV2Interface;
friend class SeatInterface;
Private *d_func() const;
};
}
}
Q_DECLARE_METATYPE(KWayland::Server::TextInputInterfaceVersion)
Q_DECLARE_METATYPE(KWayland::Server::TextInputInterface *)
Q_DECLARE_METATYPE(KWayland::Server::TextInputInterface::ContentHint)
Q_DECLARE_METATYPE(KWayland::Server::TextInputInterface::ContentHints)
Q_DECLARE_OPERATORS_FOR_FLAGS(KWayland::Server::TextInputInterface::ContentHints)
Q_DECLARE_METATYPE(KWayland::Server::TextInputInterface::ContentPurpose)
#endif

View file

@ -0,0 +1,166 @@
/****************************************************************************
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/>.
****************************************************************************/
#ifndef KWAYLAND_SERVER_TEXTINPUT_INTERFACE_P_H
#define KWAYLAND_SERVER_TEXTINPUT_INTERFACE_P_H
#include "textinput_interface.h"
#include "resource_p.h"
#include "global_p.h"
#include <QPointer>
#include <QRect>
#include <QVector>
namespace KWayland
{
namespace Server
{
class TextInputManagerUnstableV0Interface;
class TextInputManagerUnstableV2Interface;
class TextInputManagerInterface::Private : public Global::Private
{
public:
QVector<TextInputInterface*> inputs;
TextInputInterfaceVersion interfaceVersion;
protected:
Private(TextInputInterfaceVersion interfaceVersion, TextInputManagerInterface *q, Display *d, const wl_interface *interface, quint32 version);
TextInputManagerInterface *q;
};
class TextInputInterface::Private : public Resource::Private
{
public:
~Private();
virtual void sendEnter(SurfaceInterface *surface, quint32 serial) = 0;
virtual void sendLeave(quint32 serial, SurfaceInterface *surface) = 0;
virtual void requestActivate(SeatInterface *seat, SurfaceInterface *surface) = 0;
virtual void requestDeactivate(SeatInterface *seat) = 0;
virtual void preEdit(const QByteArray &text, const QByteArray &commit) = 0;
virtual void commit(const QByteArray &text) = 0;
virtual void deleteSurroundingText(quint32 beforeLength, quint32 afterLength) = 0;
virtual void setTextDirection(Qt::LayoutDirection direction) = 0;
virtual void setPreEditCursor(qint32 index) = 0;
virtual void setCursorPosition(qint32 index, qint32 anchor) = 0;
virtual void keysymPressed(quint32 keysym, Qt::KeyboardModifiers modifiers) = 0;
virtual void keysymReleased(quint32 keysym, Qt::KeyboardModifiers modifiers) = 0;
virtual TextInputInterfaceVersion interfaceVersion() const = 0;
virtual void sendInputPanelState() = 0;
virtual void sendLanguage() = 0;
QByteArray preferredLanguage;
QRect cursorRectangle;
TextInputInterface::ContentHints contentHints = TextInputInterface::ContentHint::None;
TextInputInterface::ContentPurpose contentPurpose = TextInputInterface::ContentPurpose::Normal;
quint32 latestState = 0;
SeatInterface *seat = nullptr;
QPointer<SurfaceInterface> surface;
bool enabled = false;
QByteArray surroundingText;
qint32 surroundingTextCursorPosition = 0;
qint32 surroundingTextSelectionAnchor = 0;
bool inputPanelVisible = false;
QRect overlappedSurfaceArea;
QByteArray language;
protected:
Private(TextInputInterface *q, Global *c, wl_resource *parentResource, const wl_interface *interface, const void *implementation);
static void destroyCallback(wl_client *client, wl_resource *resource);
static void activateCallback(wl_client *client, wl_resource *resource, wl_resource * seat, wl_resource * surface);
static void deactivateCallback(wl_client *client, wl_resource *resource, wl_resource * seat);
static void enableCallback(wl_client *client, wl_resource *resource, wl_resource * surface);
static void disableCallback(wl_client *client, wl_resource *resource, wl_resource * surface);
static void showInputPanelCallback(wl_client *client, wl_resource *resource);
static void hideInputPanelCallback(wl_client *client, wl_resource *resource);
static void resetCallback(wl_client *client, wl_resource *resource);
static void setSurroundingTextCallback(wl_client *client, wl_resource *resource, const char * text, uint32_t cursor, uint32_t anchor);
static void setSurroundingText2Callback(wl_client *client, wl_resource *resource, const char * text, int32_t cursor, int32_t anchor);
static void setContentTypeCallback(wl_client *client, wl_resource *resource, uint32_t hint, uint32_t purpose);
static void setCursorRectangleCallback(wl_client *client, wl_resource *resource, int32_t x, int32_t y, int32_t width, int32_t height);
static void setPreferredLanguageCallback(wl_client *client, wl_resource *resource, const char * language);
static void commitStateCallback(wl_client *client, wl_resource *resource, uint32_t serial);
static void invokeActionCallback(wl_client *client, wl_resource *resource, uint32_t button, uint32_t index);
private:
TextInputInterface *q_func() {
return reinterpret_cast<TextInputInterface *>(q);
}
};
class TextInputUnstableV0Interface : public TextInputInterface
{
Q_OBJECT
public:
virtual ~TextInputUnstableV0Interface();
Q_SIGNALS:
/**
* @internal
**/
void requestActivate(KWayland::Server::SeatInterface *seat, KWayland::Server::SurfaceInterface *surface);
private:
explicit TextInputUnstableV0Interface(TextInputManagerUnstableV0Interface *parent, wl_resource *parentResource);
friend class TextInputManagerUnstableV0Interface;
class Private;
};
class TextInputUnstableV2Interface : public TextInputInterface
{
Q_OBJECT
public:
virtual ~TextInputUnstableV2Interface();
private:
explicit TextInputUnstableV2Interface(TextInputManagerUnstableV2Interface *parent, wl_resource *parentResource);
friend class TextInputManagerUnstableV2Interface;
class Private;
};
class TextInputManagerUnstableV0Interface : public TextInputManagerInterface
{
Q_OBJECT
public:
explicit TextInputManagerUnstableV0Interface(Display *display, QObject *parent = nullptr);
virtual ~TextInputManagerUnstableV0Interface();
private:
class Private;
};
class TextInputManagerUnstableV2Interface : public TextInputManagerInterface
{
Q_OBJECT
public:
explicit TextInputManagerUnstableV2Interface(Display *display, QObject *parent = nullptr);
virtual ~TextInputManagerUnstableV2Interface();
private:
class Private;
};
}
}
#endif

View file

@ -0,0 +1,313 @@
/****************************************************************************
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/>.
****************************************************************************/
#include "textinput_interface_p.h"
#include "display.h"
#include "resource_p.h"
#include "seat_interface_p.h"
#include "surface_interface.h"
#include <wayland-text-server-protocol.h>
namespace KWayland
{
namespace Server
{
class TextInputUnstableV0Interface::Private : public TextInputInterface::Private
{
public:
Private(TextInputInterface *q, TextInputManagerUnstableV0Interface *c, wl_resource *parentResource);
~Private();
void sendEnter(SurfaceInterface *surface, quint32 serial) override;
void sendLeave(quint32 serial, SurfaceInterface *surface) override;
void requestActivate(SeatInterface *seat, SurfaceInterface *surface) override;
void requestDeactivate(SeatInterface *seat) override;
void preEdit(const QByteArray &text, const QByteArray &commit) override;
void commit(const QByteArray &text) override;
void deleteSurroundingText(quint32 beforeLength, quint32 afterLength) override;
void setTextDirection(Qt::LayoutDirection direction) override;
void setPreEditCursor(qint32 index) override;
void setCursorPosition(qint32 index, qint32 anchor) override;
void keysymPressed(quint32 keysym, Qt::KeyboardModifiers modifiers) override;
void keysymReleased(quint32 keysym, Qt::KeyboardModifiers modifiers) override;
TextInputInterfaceVersion interfaceVersion() const override {
return TextInputInterfaceVersion::UnstableV0;
}
void sendInputPanelState() override;
void sendLanguage() override;
private:
static const struct wl_text_input_interface s_interface;
TextInputUnstableV0Interface *q_func() {
return reinterpret_cast<TextInputUnstableV0Interface *>(q);
}
};
#ifndef DOXYGEN_SHOULD_SKIP_THIS
const struct wl_text_input_interface TextInputUnstableV0Interface::Private::s_interface = {
activateCallback,
deactivateCallback,
showInputPanelCallback,
hideInputPanelCallback,
resetCallback,
setSurroundingTextCallback,
setContentTypeCallback,
setCursorRectangleCallback,
setPreferredLanguageCallback,
commitStateCallback,
invokeActionCallback
};
#endif
void TextInputUnstableV0Interface::Private::requestActivate(SeatInterface *seat, SurfaceInterface *s)
{
surface = QPointer<SurfaceInterface>(s);
enabled = true;
emit q_func()->enabledChanged();
emit q_func()->requestActivate(seat, surface);
}
void TextInputUnstableV0Interface::Private::requestDeactivate(SeatInterface *seat)
{
Q_UNUSED(seat)
surface.clear();
enabled = false;
emit q_func()->enabledChanged();
}
void TextInputUnstableV0Interface::Private::sendEnter(SurfaceInterface *surface, quint32 serial)
{
Q_UNUSED(serial)
if (!resource) {
return;
}
wl_text_input_send_enter(resource, surface->resource());
}
void TextInputUnstableV0Interface::Private::sendLeave(quint32 serial, SurfaceInterface *surface)
{
Q_UNUSED(serial)
Q_UNUSED(surface)
if (!resource) {
return;
}
wl_text_input_send_leave(resource);
}
void TextInputUnstableV0Interface::Private::preEdit(const QByteArray &text, const QByteArray &commit)
{
if (!resource) {
return;
}
wl_text_input_send_preedit_string(resource, latestState, text.constData(), commit.constData());
}
void TextInputUnstableV0Interface::Private::commit(const QByteArray &text)
{
if (!resource) {
return;
}
wl_text_input_send_commit_string(resource, latestState, text.constData());
}
void TextInputUnstableV0Interface::Private::keysymPressed(quint32 keysym, Qt::KeyboardModifiers modifiers)
{
Q_UNUSED(modifiers)
if (!resource) {
return;
}
wl_text_input_send_keysym(resource, latestState, seat ? seat->timestamp() : 0, keysym, WL_KEYBOARD_KEY_STATE_PRESSED, 0);
}
void TextInputUnstableV0Interface::Private::keysymReleased(quint32 keysym, Qt::KeyboardModifiers modifiers)
{
Q_UNUSED(modifiers)
if (!resource) {
return;
}
wl_text_input_send_keysym(resource, latestState, seat ? seat->timestamp() : 0, keysym, WL_KEYBOARD_KEY_STATE_RELEASED, 0);
}
void TextInputUnstableV0Interface::Private::deleteSurroundingText(quint32 beforeLength, quint32 afterLength)
{
if (!resource) {
return;
}
wl_text_input_send_delete_surrounding_text(resource, -1 * beforeLength, beforeLength + afterLength);
}
void TextInputUnstableV0Interface::Private::setCursorPosition(qint32 index, qint32 anchor)
{
if (!resource) {
return;
}
wl_text_input_send_cursor_position(resource, index, anchor);
}
void TextInputUnstableV0Interface::Private::setTextDirection(Qt::LayoutDirection direction)
{
if (!resource) {
return;
}
wl_text_input_text_direction wlDirection;
switch (direction) {
case Qt::LeftToRight:
wlDirection = WL_TEXT_INPUT_TEXT_DIRECTION_LTR;
break;
case Qt::RightToLeft:
wlDirection = WL_TEXT_INPUT_TEXT_DIRECTION_RTL;
break;
case Qt::LayoutDirectionAuto:
wlDirection = WL_TEXT_INPUT_TEXT_DIRECTION_AUTO;
break;
default:
Q_UNREACHABLE();
break;
}
wl_text_input_send_text_direction(resource, latestState, wlDirection);
}
void TextInputUnstableV0Interface::Private::setPreEditCursor(qint32 index)
{
if (!resource) {
return;
}
wl_text_input_send_preedit_cursor(resource, index);
}
void TextInputUnstableV0Interface::Private::sendInputPanelState()
{
if (!resource) {
return;
}
wl_text_input_send_input_panel_state(resource, inputPanelVisible);
}
void TextInputUnstableV0Interface::Private::sendLanguage()
{
if (!resource) {
return;
}
wl_text_input_send_language(resource, latestState, language.constData());
}
TextInputUnstableV0Interface::Private::Private(TextInputInterface *q, TextInputManagerUnstableV0Interface *c, wl_resource *parentResource)
: TextInputInterface::Private(q, c, parentResource, &wl_text_input_interface, &s_interface)
{
}
TextInputUnstableV0Interface::Private::~Private() = default;
TextInputUnstableV0Interface::TextInputUnstableV0Interface(TextInputManagerUnstableV0Interface *parent, wl_resource *parentResource)
: TextInputInterface(new Private(this, parent, parentResource))
{
}
TextInputUnstableV0Interface::~TextInputUnstableV0Interface() = default;
class TextInputManagerUnstableV0Interface::Private : public TextInputManagerInterface::Private
{
public:
Private(TextInputManagerUnstableV0Interface *q, Display *d);
private:
void bind(wl_client *client, uint32_t version, uint32_t id) override;
static void unbind(wl_resource *resource);
static Private *cast(wl_resource *r) {
return reinterpret_cast<Private*>(wl_resource_get_user_data(r));
}
static void createTextInputCallback(wl_client *client, wl_resource *resource, uint32_t id);
TextInputManagerUnstableV0Interface *q;
static const struct wl_text_input_manager_interface s_interface;
static const quint32 s_version;
};
const quint32 TextInputManagerUnstableV0Interface::Private::s_version = 1;
#ifndef DOXYGEN_SHOULD_SKIP_THIS
const struct wl_text_input_manager_interface TextInputManagerUnstableV0Interface::Private::s_interface = {
createTextInputCallback
};
#endif
void TextInputManagerUnstableV0Interface::Private::createTextInputCallback(wl_client *client, wl_resource *resource, uint32_t id)
{
auto m = cast(resource);
auto *t = new TextInputUnstableV0Interface(m->q, resource);
m->inputs << t;
QObject::connect(t, &QObject::destroyed, m->q,
[t, m] {
m->inputs.removeAll(t);
}
);
QObject::connect(t, &TextInputUnstableV0Interface::requestActivate, m->q,
[t, m] (SeatInterface *seat) {
// TODO: disallow for other seat
seat->d_func()->registerTextInput(t);
t->d_func()->seat = seat;
}
);
t->d->create(m->display->getConnection(client), version, id);
}
TextInputManagerInterface::Private::Private(TextInputInterfaceVersion interfaceVersion, TextInputManagerInterface *q, Display *d, const wl_interface *interface, quint32 version)
: Global::Private(d, interface, version)
, interfaceVersion(interfaceVersion)
, q(q)
{
}
TextInputManagerUnstableV0Interface::Private::Private(TextInputManagerUnstableV0Interface *q, Display *d)
: TextInputManagerInterface::Private(TextInputInterfaceVersion::UnstableV0, q, d, &wl_text_input_manager_interface, s_version)
, q(q)
{
}
void TextInputManagerUnstableV0Interface::Private::bind(wl_client *client, uint32_t version, uint32_t id)
{
auto c = display->getConnection(client);
wl_resource *resource = c->createResource(&wl_text_input_manager_interface, qMin(version, s_version), id);
if (!resource) {
wl_client_post_no_memory(client);
return;
}
wl_resource_set_implementation(resource, &s_interface, this, unbind);
// TODO: should we track?
}
void TextInputManagerUnstableV0Interface::Private::unbind(wl_resource *resource)
{
Q_UNUSED(resource)
// TODO: implement?
}
TextInputManagerUnstableV0Interface::TextInputManagerUnstableV0Interface(Display *display, QObject *parent)
: TextInputManagerInterface(new Private(this, display), parent)
{
}
TextInputManagerUnstableV0Interface::~TextInputManagerUnstableV0Interface() = default;
}
}

View file

@ -0,0 +1,324 @@
/****************************************************************************
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/>.
****************************************************************************/
#include "textinput_interface_p.h"
#include "display.h"
#include "resource_p.h"
#include "seat_interface_p.h"
#include "surface_interface.h"
#include <wayland-text-input-unstable-v2-server-protocol.h>
namespace KWayland
{
namespace Server
{
class TextInputUnstableV2Interface::Private : public TextInputInterface::Private
{
public:
Private(TextInputInterface *q, TextInputManagerUnstableV2Interface *c, wl_resource *parentResource);
~Private();
void sendEnter(SurfaceInterface *surface, quint32 serial) override;
void sendLeave(quint32 serial, SurfaceInterface *surface) override;
void requestActivate(SeatInterface *seat, SurfaceInterface *surface) override;
void requestDeactivate(SeatInterface *seat) override;
void preEdit(const QByteArray &text, const QByteArray &commit) override;
void commit(const QByteArray &text) override;
void deleteSurroundingText(quint32 beforeLength, quint32 afterLength) override;
void setTextDirection(Qt::LayoutDirection direction) override;
void setPreEditCursor(qint32 index) override;
void setCursorPosition(qint32 index, qint32 anchor) override;
void keysymPressed(quint32 keysym, Qt::KeyboardModifiers modifiers) override;
void keysymReleased(quint32 keysym, Qt::KeyboardModifiers modifiers) override;
TextInputInterfaceVersion interfaceVersion() const override {
return TextInputInterfaceVersion::UnstableV2;
}
void sendInputPanelState() override;
void sendLanguage() override;
private:
static void updateStateCallback(wl_client *client, wl_resource *resource, uint32_t serial, uint32_t reason);
static const struct zwp_text_input_v2_interface s_interface;
TextInputUnstableV2Interface *q_func() {
return reinterpret_cast<TextInputUnstableV2Interface *>(q);
}
};
#ifndef DOXYGEN_SHOULD_SKIP_THIS
const struct zwp_text_input_v2_interface TextInputUnstableV2Interface::Private::s_interface = {
destroyCallback,
enableCallback,
disableCallback,
showInputPanelCallback,
hideInputPanelCallback,
setSurroundingText2Callback,
setContentTypeCallback,
setCursorRectangleCallback,
setPreferredLanguageCallback,
updateStateCallback
};
#endif
void TextInputUnstableV2Interface::Private::requestActivate(SeatInterface *seat, SurfaceInterface *s)
{
Q_UNUSED(seat)
surface = QPointer<SurfaceInterface>(s);
enabled = true;
emit q_func()->enabledChanged();
}
void TextInputUnstableV2Interface::Private::requestDeactivate(SeatInterface *seat)
{
Q_UNUSED(seat)
surface.clear();
enabled = false;
emit q_func()->enabledChanged();
}
void TextInputUnstableV2Interface::Private::sendEnter(SurfaceInterface *surface, quint32 serial)
{
if (!resource) {
return;
}
zwp_text_input_v2_send_enter(resource, serial, surface->resource());
}
void TextInputUnstableV2Interface::Private::sendLeave(quint32 serial, SurfaceInterface *surface)
{
if (!resource || !surface) {
return;
}
zwp_text_input_v2_send_leave(resource, serial, surface->resource());
}
void TextInputUnstableV2Interface::Private::preEdit(const QByteArray &text, const QByteArray &commit)
{
if (!resource) {
return;
}
zwp_text_input_v2_send_preedit_string(resource, text.constData(), commit.constData());
}
void TextInputUnstableV2Interface::Private::commit(const QByteArray &text)
{
if (!resource) {
return;
}
zwp_text_input_v2_send_commit_string(resource, text.constData());
}
void TextInputUnstableV2Interface::Private::keysymPressed(quint32 keysym, Qt::KeyboardModifiers modifiers)
{
Q_UNUSED(modifiers)
if (!resource) {
return;
}
zwp_text_input_v2_send_keysym(resource, seat ? seat->timestamp() : 0, keysym, WL_KEYBOARD_KEY_STATE_PRESSED, 0);
}
void TextInputUnstableV2Interface::Private::keysymReleased(quint32 keysym, Qt::KeyboardModifiers modifiers)
{
Q_UNUSED(modifiers)
if (!resource) {
return;
}
zwp_text_input_v2_send_keysym(resource, seat ? seat->timestamp() : 0, keysym, WL_KEYBOARD_KEY_STATE_RELEASED, 0);
}
void TextInputUnstableV2Interface::Private::deleteSurroundingText(quint32 beforeLength, quint32 afterLength)
{
if (!resource) {
return;
}
zwp_text_input_v2_send_delete_surrounding_text(resource, beforeLength, afterLength);
}
void TextInputUnstableV2Interface::Private::setCursorPosition(qint32 index, qint32 anchor)
{
if (!resource) {
return;
}
zwp_text_input_v2_send_cursor_position(resource, index, anchor);
}
void TextInputUnstableV2Interface::Private::setTextDirection(Qt::LayoutDirection direction)
{
if (!resource) {
return;
}
zwp_text_input_v2_text_direction wlDirection;
switch (direction) {
case Qt::LeftToRight:
wlDirection = ZWP_TEXT_INPUT_V2_TEXT_DIRECTION_LTR;
break;
case Qt::RightToLeft:
wlDirection = ZWP_TEXT_INPUT_V2_TEXT_DIRECTION_RTL;
break;
case Qt::LayoutDirectionAuto:
wlDirection = ZWP_TEXT_INPUT_V2_TEXT_DIRECTION_AUTO;
break;
default:
Q_UNREACHABLE();
break;
}
zwp_text_input_v2_send_text_direction(resource, wlDirection);
}
void TextInputUnstableV2Interface::Private::setPreEditCursor(qint32 index)
{
if (!resource) {
return;
}
zwp_text_input_v2_send_preedit_cursor(resource, index);
}
void TextInputUnstableV2Interface::Private::sendInputPanelState()
{
if (!resource) {
return;
}
zwp_text_input_v2_send_input_panel_state(resource,
inputPanelVisible ? ZWP_TEXT_INPUT_V2_INPUT_PANEL_VISIBILITY_VISIBLE : ZWP_TEXT_INPUT_V2_INPUT_PANEL_VISIBILITY_HIDDEN,
overlappedSurfaceArea.x(), overlappedSurfaceArea.y(), overlappedSurfaceArea.width(), overlappedSurfaceArea.height());
}
void TextInputUnstableV2Interface::Private::sendLanguage()
{
if (!resource) {
return;
}
zwp_text_input_v2_send_language(resource, language.constData());
}
TextInputUnstableV2Interface::Private::Private(TextInputInterface *q, TextInputManagerUnstableV2Interface *c, wl_resource *parentResource)
: TextInputInterface::Private(q, c, parentResource, &zwp_text_input_v2_interface, &s_interface)
{
}
TextInputUnstableV2Interface::Private::~Private() = default;
void TextInputUnstableV2Interface::Private::updateStateCallback(wl_client *client, wl_resource *resource, uint32_t serial, uint32_t reason)
{
auto p = cast<Private>(resource);
Q_ASSERT(*p->client == client);
Q_UNUSED(serial)
// TODO: use other reason values reason
if (reason == ZWP_TEXT_INPUT_V2_UPDATE_STATE_RESET) {
emit p->q_func()->requestReset();
}
}
TextInputUnstableV2Interface::TextInputUnstableV2Interface(TextInputManagerUnstableV2Interface *parent, wl_resource *parentResource)
: TextInputInterface(new Private(this, parent, parentResource))
{
}
TextInputUnstableV2Interface::~TextInputUnstableV2Interface() = default;
class TextInputManagerUnstableV2Interface::Private : public TextInputManagerInterface::Private
{
public:
Private(TextInputManagerUnstableV2Interface *q, Display *d);
private:
void bind(wl_client *client, uint32_t version, uint32_t id) override;
static void unbind(wl_resource *resource);
static Private *cast(wl_resource *r) {
return reinterpret_cast<Private*>(wl_resource_get_user_data(r));
}
static void destroyCallback(wl_client *client, wl_resource *resource);
static void getTextInputCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource * seat);
TextInputManagerUnstableV2Interface *q;
static const struct zwp_text_input_manager_v2_interface s_interface;
static const quint32 s_version;
};
const quint32 TextInputManagerUnstableV2Interface::Private::s_version = 1;
#ifndef DOXYGEN_SHOULD_SKIP_THIS
const struct zwp_text_input_manager_v2_interface TextInputManagerUnstableV2Interface::Private::s_interface = {
destroyCallback,
getTextInputCallback
};
#endif
void TextInputManagerUnstableV2Interface::Private::destroyCallback(wl_client *client, wl_resource *resource)
{
Q_UNUSED(client)
wl_resource_destroy(resource);
}
void TextInputManagerUnstableV2Interface::Private::getTextInputCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource * seat)
{
SeatInterface *s = SeatInterface::get(seat);
if (!s) {
// TODO: send error
return;
}
auto m = cast(resource);
auto *t = new TextInputUnstableV2Interface(m->q, resource);
t->d_func()->seat = s;
m->inputs << t;
QObject::connect(t, &QObject::destroyed, m->q,
[t, m] {
m->inputs.removeAll(t);
}
);
t->d->create(m->display->getConnection(client), version, id);
s->d_func()->registerTextInput(t);
}
TextInputManagerUnstableV2Interface::Private::Private(TextInputManagerUnstableV2Interface *q, Display *d)
: TextInputManagerInterface::Private(TextInputInterfaceVersion::UnstableV2, q, d, &zwp_text_input_manager_v2_interface, s_version)
, q(q)
{
}
void TextInputManagerUnstableV2Interface::Private::bind(wl_client *client, uint32_t version, uint32_t id)
{
auto c = display->getConnection(client);
wl_resource *resource = c->createResource(&zwp_text_input_manager_v2_interface, qMin(version, s_version), id);
if (!resource) {
wl_client_post_no_memory(client);
return;
}
wl_resource_set_implementation(resource, &s_interface, this, unbind);
// TODO: should we track?
}
void TextInputManagerUnstableV2Interface::Private::unbind(wl_resource *resource)
{
Q_UNUSED(resource)
// TODO: implement?
}
TextInputManagerUnstableV2Interface::TextInputManagerUnstableV2Interface(Display *display, QObject *parent)
: TextInputManagerInterface(new Private(this, display), parent)
{
}
TextInputManagerUnstableV2Interface::~TextInputManagerUnstableV2Interface() = default;
}
}

View file

@ -40,3 +40,7 @@ org_kde_plasma_window_management;PlasmaWindowManagement
org_kde_plasma_window;PlasmaWindow org_kde_plasma_window;PlasmaWindow
org_kde_kwin_server_decoration_manager;ServerSideDecorationManager org_kde_kwin_server_decoration_manager;ServerSideDecorationManager
org_kde_kwin_server_decoration;ServerSideDecoration org_kde_kwin_server_decoration;ServerSideDecoration
wl_text_input;TextInputUnstableV0
wl_text_input_manager;TextInputManagerUnstableV0
zwp_text_input_v2;TextInputUnstableV2
zwp_text_input_manager_v2;TextInputManagerUnstableV2