diff --git a/src/wayland/CMakeLists.txt b/src/wayland/CMakeLists.txt index 8a28e642b7..092f725aaa 100644 --- a/src/wayland/CMakeLists.txt +++ b/src/wayland/CMakeLists.txt @@ -34,6 +34,9 @@ set(SERVER_LIB_SRCS surface_interface.cpp subcompositor_interface.cpp touch_interface.cpp + textinput_interface.cpp + textinput_interface_v0.cpp + textinput_interface_v2.cpp ) ecm_add_wayland_server_protocol(SERVER_LIB_SRCS @@ -100,6 +103,16 @@ ecm_add_wayland_server_protocol(SERVER_LIB_SRCS 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}) generate_export_header(KF5WaylandServer BASE_NAME @@ -168,6 +181,7 @@ install(FILES slide_interface.h subcompositor_interface.h surface_interface.h + textinput_interface.h touch_interface.h DESTINATION ${KF5_INCLUDE_INSTALL_DIR}/KWayland/Server COMPONENT Devel ) diff --git a/src/wayland/autotests/client/CMakeLists.txt b/src/wayland/autotests/client/CMakeLists.txt index e19fdedbf2..48fc09badf 100644 --- a/src/wayland/autotests/client/CMakeLists.txt +++ b/src/wayland/autotests/client/CMakeLists.txt @@ -297,3 +297,14 @@ add_executable(testPlasmaWindowModel ${testPlasmaWindowModel_SRCS}) target_link_libraries( testPlasmaWindowModel Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer) add_test(kwayland-testPlasmaWindowModel 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) diff --git a/src/wayland/autotests/client/test_text_input.cpp b/src/wayland/autotests/client/test_text_input.cpp new file mode 100644 index 0000000000..5045b032ca --- /dev/null +++ b/src/wayland/autotests/client/test_text_input.cpp @@ -0,0 +1,917 @@ +/******************************************************************** +Copyright 2016 Martin Gräßlin + +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 . +*********************************************************************/ +// Qt +#include +// client +#include "../../src/client/compositor.h" +#include "../../src/client/connection_thread.h" +#include "../../src/client/event_queue.h" +#include "../../src/client/keyboard.h" +#include "../../src/client/registry.h" +#include "../../src/client/seat.h" +#include "../../src/client/surface.h" +#include "../../src/client/textinput.h" +// server +#include "../../src/server/compositor_interface.h" +#include "../../src/server/display.h" +#include "../../src/server/seat_interface.h" +#include "../../src/server/textinput_interface.h" + +using namespace KWayland::Client; +using namespace KWayland::Server; + +class TextInputTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void init(); + void cleanup(); + + void testEnterLeave_data(); + void testEnterLeave(); + void testShowHidePanel_data(); + void testShowHidePanel(); + void testCursorRectangle_data(); + void testCursorRectangle(); + void testPreferredLanguage_data(); + void testPreferredLanguage(); + void testReset_data(); + void testReset(); + void testSurroundingText_data(); + void testSurroundingText(); + void testContentHints_data(); + void testContentHints(); + void testContentPurpose_data(); + void testContentPurpose(); + void testTextDirection_data(); + void testTextDirection(); + void testLanguage_data(); + void testLanguage(); + void testKeyEvent_data(); + void testKeyEvent(); + void testPreEdit_data(); + void testPreEdit(); + void testCommit_data(); + void testCommit(); + +private: + SurfaceInterface *waitForSurface(); + TextInput *createTextInput(TextInputInterfaceVersion version); + Display *m_display = nullptr; + SeatInterface *m_seatInterface = nullptr; + CompositorInterface *m_compositorInterface = nullptr; + TextInputManagerInterface *m_textInputManagerV0Interface = nullptr; + TextInputManagerInterface *m_textInputManagerV2Interface = nullptr; + ConnectionThread *m_connection = nullptr; + QThread *m_thread = nullptr; + EventQueue *m_queue = nullptr; + Seat *m_seat = nullptr; + Keyboard *m_keyboard = nullptr; + Compositor *m_compositor = nullptr; + TextInputManager *m_textInputManagerV0 = nullptr; + TextInputManager *m_textInputManagerV2 = nullptr; +}; + +static const QString s_socketName = QStringLiteral("kwayland-test-text-input-0"); + +void TextInputTest::init() +{ + delete m_display; + m_display = new Display(this); + m_display->setSocketName(s_socketName); + m_display->start(); + QVERIFY(m_display->isRunning()); + m_display->createShm(); + m_seatInterface = m_display->createSeat(); + m_seatInterface->setHasKeyboard(true); + m_seatInterface->setHasTouch(true); + m_seatInterface->create(); + m_compositorInterface = m_display->createCompositor(); + m_compositorInterface->create(); + m_textInputManagerV0Interface = m_display->createTextInputManager(TextInputInterfaceVersion::UnstableV0); + m_textInputManagerV0Interface->create(); + m_textInputManagerV2Interface = m_display->createTextInputManager(TextInputInterfaceVersion::UnstableV2); + m_textInputManagerV2Interface->create(); + + // setup connection + m_connection = new KWayland::Client::ConnectionThread; + QSignalSpy connectedSpy(m_connection, &ConnectionThread::connected); + QVERIFY(connectedSpy.isValid()); + m_connection->setSocketName(s_socketName); + + m_thread = new QThread(this); + m_connection->moveToThread(m_thread); + m_thread->start(); + + m_connection->initConnection(); + QVERIFY(connectedSpy.wait()); + + m_queue = new EventQueue(this); + m_queue->setup(m_connection); + + Registry registry; + QSignalSpy interfacesAnnouncedSpy(®istry, &Registry::interfacesAnnounced); + QVERIFY(interfacesAnnouncedSpy.isValid()); + registry.setEventQueue(m_queue); + registry.create(m_connection); + QVERIFY(registry.isValid()); + registry.setup(); + QVERIFY(interfacesAnnouncedSpy.wait()); + + m_seat = registry.createSeat(registry.interface(Registry::Interface::Seat).name, + registry.interface(Registry::Interface::Seat).version, + this); + QVERIFY(m_seat->isValid()); + QSignalSpy hasKeyboardSpy(m_seat, &Seat::hasKeyboardChanged); + QVERIFY(hasKeyboardSpy.isValid()); + QVERIFY(hasKeyboardSpy.wait()); + m_keyboard = m_seat->createKeyboard(this); + QVERIFY(m_keyboard->isValid()); + + m_compositor = registry.createCompositor(registry.interface(Registry::Interface::Compositor).name, + registry.interface(Registry::Interface::Compositor).version, + this); + QVERIFY(m_compositor->isValid()); + + m_textInputManagerV0 = registry.createTextInputManager(registry.interface(Registry::Interface::TextInputManagerUnstableV0).name, + registry.interface(Registry::Interface::TextInputManagerUnstableV0).version, + this); + QVERIFY(m_textInputManagerV0->isValid()); + + m_textInputManagerV2 = registry.createTextInputManager(registry.interface(Registry::Interface::TextInputManagerUnstableV2).name, + registry.interface(Registry::Interface::TextInputManagerUnstableV2).version, + this); + QVERIFY(m_textInputManagerV2->isValid()); +} + +void TextInputTest::cleanup() +{ +#define CLEANUP(variable) \ + if (variable) { \ + delete variable; \ + variable = nullptr; \ + } + CLEANUP(m_textInputManagerV0) + CLEANUP(m_textInputManagerV2) + CLEANUP(m_keyboard) + CLEANUP(m_seat) + CLEANUP(m_compositor) + CLEANUP(m_queue) + if (m_connection) { + m_connection->deleteLater(); + m_connection = nullptr; + } + if (m_thread) { + m_thread->quit(); + m_thread->wait(); + delete m_thread; + m_thread = nullptr; + } + + CLEANUP(m_textInputManagerV0Interface) + CLEANUP(m_textInputManagerV2Interface) + CLEANUP(m_compositorInterface) + CLEANUP(m_seatInterface) + CLEANUP(m_display) +#undef CLEANUP +} + +SurfaceInterface *TextInputTest::waitForSurface() +{ + QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); + if (!surfaceCreatedSpy.isValid()) { + return nullptr; + } + if (!surfaceCreatedSpy.wait()) { + return nullptr; + } + if (surfaceCreatedSpy.count() != 1) { + return nullptr; + } + return surfaceCreatedSpy.first().first().value(); +} + +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("version"); + QTest::addColumn("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(m_compositor->createSurface()); + QFETCH(TextInputInterfaceVersion, version); + QScopedPointer 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("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(m_compositor->createSurface()); + auto serverSurface = waitForSurface(); + QVERIFY(serverSurface); + QFETCH(TextInputInterfaceVersion, version); + QScopedPointer 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("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(m_compositor->createSurface()); + auto serverSurface = waitForSurface(); + QVERIFY(serverSurface); + QFETCH(TextInputInterfaceVersion, version); + QScopedPointer 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("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(m_compositor->createSurface()); + auto serverSurface = waitForSurface(); + QVERIFY(serverSurface); + QFETCH(TextInputInterfaceVersion, version); + QScopedPointer 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("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(m_compositor->createSurface()); + auto serverSurface = waitForSurface(); + QVERIFY(serverSurface); + QFETCH(TextInputInterfaceVersion, version); + QScopedPointer 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("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(m_compositor->createSurface()); + auto serverSurface = waitForSurface(); + QVERIFY(serverSurface); + QFETCH(TextInputInterfaceVersion, version); + QScopedPointer 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("version"); + QTest::addColumn("clientHints"); + QTest::addColumn("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(m_compositor->createSurface()); + auto serverSurface = waitForSurface(); + QVERIFY(serverSurface); + QFETCH(TextInputInterfaceVersion, version); + QScopedPointer 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("version"); + QTest::addColumn("clientPurpose"); + QTest::addColumn("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(m_compositor->createSurface()); + auto serverSurface = waitForSurface(); + QVERIFY(serverSurface); + QFETCH(TextInputInterfaceVersion, version); + QScopedPointer 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("version"); + QTest::addColumn("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(m_compositor->createSurface()); + auto serverSurface = waitForSurface(); + QVERIFY(serverSurface); + QFETCH(TextInputInterfaceVersion, version); + QScopedPointer 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("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(m_compositor->createSurface()); + auto serverSurface = waitForSurface(); + QVERIFY(serverSurface); + QFETCH(TextInputInterfaceVersion, version); + QScopedPointer 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("version"); + + QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0; + QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2; +} + +void TextInputTest::testKeyEvent() +{ + qRegisterMetaType(); + qRegisterMetaType(); + // this test verifies that key events are properly sent to the client + QScopedPointer surface(m_compositor->createSurface()); + auto serverSurface = waitForSurface(); + QVERIFY(serverSurface); + QFETCH(TextInputInterfaceVersion, version); + QScopedPointer 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(), 2u); + QCOMPARE(keyEventSpy.last().at(1).value(), TextInput::KeyState::Pressed); + QCOMPARE(keyEventSpy.last().at(2).value(), Qt::KeyboardModifiers()); + QCOMPARE(keyEventSpy.last().at(3).value(), 100u); + m_seatInterface->setTimestamp(101); + ti->keysymReleased(2); + QVERIFY(keyEventSpy.wait()); + QCOMPARE(keyEventSpy.count(), 2); + QCOMPARE(keyEventSpy.last().at(0).value(), 2u); + QCOMPARE(keyEventSpy.last().at(1).value(), TextInput::KeyState::Released); + QCOMPARE(keyEventSpy.last().at(2).value(), Qt::KeyboardModifiers()); + QCOMPARE(keyEventSpy.last().at(3).value(), 101u); +} + +void TextInputTest::testPreEdit_data() +{ + QTest::addColumn("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(m_compositor->createSurface()); + auto serverSurface = waitForSurface(); + QVERIFY(serverSurface); + QFETCH(TextInputInterfaceVersion, version); + QScopedPointer 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("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(m_compositor->createSurface()); + auto serverSurface = waitForSurface(); + QVERIFY(serverSurface); + QFETCH(TextInputInterfaceVersion, version); + QScopedPointer 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" diff --git a/src/wayland/autotests/client/test_wayland_registry.cpp b/src/wayland/autotests/client/test_wayland_registry.cpp index 5e9e6eb72a..13c335041a 100644 --- a/src/wayland/autotests/client/test_wayland_registry.cpp +++ b/src/wayland/autotests/client/test_wayland_registry.cpp @@ -44,10 +44,13 @@ License along with this library. If not, see . #include "../../src/server/subcompositor_interface.h" #include "../../src/server/outputmanagement_interface.h" #include "../../src/server/outputdevice_interface.h" +#include "../../src/server/textinput_interface.h" // Wayland #include #include #include +#include +#include class TestWaylandRegistry : public QObject { @@ -71,6 +74,8 @@ private Q_SLOTS: void testBindSlideManager(); void testBindDpmsManager(); void testBindServerSideDecorationManager(); + void testBindTextInputManagerUnstableV0(); + void testBindTextInputManagerUnstableV2(); void testGlobalSync(); void testGlobalSyncThreaded(); void testRemoval(); @@ -89,6 +94,8 @@ private: KWayland::Server::DataDeviceManagerInterface *m_dataDeviceManager; KWayland::Server::OutputManagementInterface *m_outputManagement; 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"); @@ -105,6 +112,8 @@ TestWaylandRegistry::TestWaylandRegistry(QObject *parent) , m_dataDeviceManager(nullptr) , m_outputManagement(nullptr) , m_serverSideDecorationManager(nullptr) + , m_textInputManagerV0(nullptr) + , m_textInputManagerV2(nullptr) { } @@ -137,6 +146,12 @@ void TestWaylandRegistry::init() m_display->createDpmsManager()->create(); m_serverSideDecorationManager = m_display->createServerSideDecorationManager(); 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() @@ -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) } +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 void TestWaylandRegistry::testRemoval() diff --git a/src/wayland/display.cpp b/src/wayland/display.cpp index 2672b0ab3e..4b9761f608 100644 --- a/src/wayland/display.cpp +++ b/src/wayland/display.cpp @@ -39,6 +39,7 @@ License along with this library. If not, see . #include "slide_interface.h" #include "shell_interface.h" #include "subcompositor_interface.h" +#include "textinput_interface_p.h" #include #include @@ -334,6 +335,23 @@ ServerSideDecorationManagerInterface *Display::createServerSideDecorationManager 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() { Q_ASSERT(d->display); diff --git a/src/wayland/display.h b/src/wayland/display.h index 7229e584cf..c4b6741638 100644 --- a/src/wayland/display.h +++ b/src/wayland/display.h @@ -69,6 +69,8 @@ class ServerSideDecorationManagerInterface; class SlideManagerInterface; class ShellInterface; class SubCompositorInterface; +enum class TextInputInterfaceVersion; +class TextInputManagerInterface; /** * @brief Class holding the Wayland server display loop. @@ -170,6 +172,12 @@ public: * @since 5.6 **/ 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. diff --git a/src/wayland/seat_interface.cpp b/src/wayland/seat_interface.cpp index b12ac7d788..9bfcdcf61f 100644 --- a/src/wayland/seat_interface.cpp +++ b/src/wayland/seat_interface.cpp @@ -27,6 +27,7 @@ License along with this library. If not, see . #include "pointer_interface.h" #include "pointer_interface_p.h" #include "surface_interface.h" +#include "textinput_interface_p.h" // Wayland #ifndef WL_SEAT_NAME_SINCE_VERSION #define WL_SEAT_NAME_SINCE_VERSION 2 @@ -204,6 +205,11 @@ DataDeviceInterface *SeatInterface::Private::dataDeviceForSurface(SurfaceInterfa return interfaceForSurface(surface, dataDevices); } +TextInputInterface *SeatInterface::Private::textInputForSurface(SurfaceInterface *surface) const +{ + return interfaceForSurface(surface, textInputs); +} + void SeatInterface::Private::registerDataDevice(DataDeviceInterface *dataDevice) { 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) { auto target = drag.target; @@ -829,10 +862,13 @@ void SeatInterface::setFocusedKeyboardSurface(SurfaceInterface *surface) } } } - if (!k) { - return; + if (k) { + k->setFocusedSurface(surface, serial); + } + // focused text input surface follows keyboard + if (hasKeyboard()) { + setFocusedTextInputSurface(surface); } - k->setFocusedSurface(surface, serial); } void SeatInterface::setKeymap(int fd, quint32 size) @@ -1155,5 +1191,53 @@ DataDeviceInterface *SeatInterface::dragSource() const 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; +} + } } diff --git a/src/wayland/seat_interface.h b/src/wayland/seat_interface.h index f2574a06d3..09d4e2b2b8 100644 --- a/src/wayland/seat_interface.h +++ b/src/wayland/seat_interface.h @@ -41,6 +41,7 @@ namespace Server class DataDeviceInterface; class Display; class SurfaceInterface; +class TextInputInterface; /** * @brief Represents a Seat on the Wayland Display. @@ -413,6 +414,16 @@ public: **/ 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); SurfaceInterface *focusedKeyboardSurface() const; KeyboardInterface *focusedKeyboard() const; @@ -435,6 +446,50 @@ public: 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); Q_SIGNALS: @@ -473,10 +528,18 @@ Q_SIGNALS: * @see dragSurface **/ void dragSurfaceChanged(); + /** + * Emitted whenever the focused text input changed. + * @see focusedTextInput + * @since 5.23 + **/ + void focusedTextInputChanged(); private: friend class Display; friend class DataDeviceManagerInterface; + friend class TextInputManagerUnstableV0Interface; + friend class TextInputManagerUnstableV2Interface; explicit SeatInterface(Display *display, QObject *parent); class Private; diff --git a/src/wayland/seat_interface_p.h b/src/wayland/seat_interface_p.h index 4a4ddd346b..fdf9cd7356 100644 --- a/src/wayland/seat_interface_p.h +++ b/src/wayland/seat_interface_p.h @@ -34,6 +34,7 @@ namespace Server { class DataDeviceInterface; +class TextInputInterface; class SeatInterface::Private : public Global::Private { @@ -46,7 +47,9 @@ public: KeyboardInterface *keyboardForSurface(SurfaceInterface *surface) const; TouchInterface *touchForSurface(SurfaceInterface *surface) const; DataDeviceInterface *dataDeviceForSurface(SurfaceInterface *surface) const; + TextInputInterface *textInputForSurface(SurfaceInterface *surface) const; void registerDataDevice(DataDeviceInterface *dataDevice); + void registerTextInput(TextInputInterface *textInput); void endDrag(quint32 serial); QString name; @@ -59,6 +62,7 @@ public: QVector keyboards; QVector touchs; QVector dataDevices; + QVector textInputs; DataDeviceInterface *currentSelection = nullptr; // Pointer related members @@ -122,6 +126,17 @@ public: Keyboard keys; 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 Focus { SurfaceInterface *surface = nullptr; diff --git a/src/wayland/server/textinput_interface.cpp b/src/wayland/server/textinput_interface.cpp new file mode 100644 index 0000000000..53df4ed4d8 --- /dev/null +++ b/src/wayland/server/textinput_interface.cpp @@ -0,0 +1,411 @@ +/**************************************************************************** +Copyright 2016 Martin Gräßlin + +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 . +****************************************************************************/ +#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 + +#include +#include + +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(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(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(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(resource); + Q_ASSERT(*p->client == client); + p->requestDeactivate(nullptr); +} + +void TextInputInterface::Private::showInputPanelCallback(wl_client *client, wl_resource *resource) +{ + auto p = cast(resource); + Q_ASSERT(*p->client == client); + emit p->q_func()->requestShowInputPanel(); +} + +void TextInputInterface::Private::hideInputPanelCallback(wl_client *client, wl_resource *resource) +{ + auto p = cast(resource); + Q_ASSERT(*p->client == client); + emit p->q_func()->requestHideInputPanel(); +} + +void TextInputInterface::Private::resetCallback(wl_client *client, wl_resource *resource) +{ + auto p = cast(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(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(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(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(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(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(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 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(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(d.data()); +} + +} +} diff --git a/src/wayland/server/textinput_interface.h b/src/wayland/server/textinput_interface.h new file mode 100644 index 0000000000..a0f8c02735 --- /dev/null +++ b/src/wayland/server/textinput_interface.h @@ -0,0 +1,439 @@ +/**************************************************************************** +Copyright 2016 Martin Gräßlin + +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 . +****************************************************************************/ +#ifndef KWAYLAND_SERVER_TEXTINPUT_INTERFACE_H +#define KWAYLAND_SERVER_TEXTINPUT_INTERFACE_H + +#include "global.h" +#include "resource.h" + +#include + +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 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 diff --git a/src/wayland/server/textinput_interface_p.h b/src/wayland/server/textinput_interface_p.h new file mode 100644 index 0000000000..f4fbe94088 --- /dev/null +++ b/src/wayland/server/textinput_interface_p.h @@ -0,0 +1,166 @@ +/**************************************************************************** +Copyright 2016 Martin Gräßlin + +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 . +****************************************************************************/ +#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 +#include +#include + +namespace KWayland +{ +namespace Server +{ + +class TextInputManagerUnstableV0Interface; +class TextInputManagerUnstableV2Interface; + +class TextInputManagerInterface::Private : public Global::Private +{ +public: + QVector 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 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(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 diff --git a/src/wayland/server/textinput_interface_v0.cpp b/src/wayland/server/textinput_interface_v0.cpp new file mode 100644 index 0000000000..5146538432 --- /dev/null +++ b/src/wayland/server/textinput_interface_v0.cpp @@ -0,0 +1,313 @@ +/**************************************************************************** +Copyright 2016 Martin Gräßlin + +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 . +****************************************************************************/ +#include "textinput_interface_p.h" +#include "display.h" +#include "resource_p.h" +#include "seat_interface_p.h" +#include "surface_interface.h" + +#include + +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(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(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(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; + +} +} diff --git a/src/wayland/server/textinput_interface_v2.cpp b/src/wayland/server/textinput_interface_v2.cpp new file mode 100644 index 0000000000..7ef613ce0d --- /dev/null +++ b/src/wayland/server/textinput_interface_v2.cpp @@ -0,0 +1,324 @@ +/**************************************************************************** +Copyright 2016 Martin Gräßlin + +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 . +****************************************************************************/ +#include "textinput_interface_p.h" +#include "display.h" +#include "resource_p.h" +#include "seat_interface_p.h" +#include "surface_interface.h" + +#include + +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(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(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(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(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; + +} +} diff --git a/src/wayland/tools/mapping.txt b/src/wayland/tools/mapping.txt index 85ee0cf097..bd2630bc5b 100644 --- a/src/wayland/tools/mapping.txt +++ b/src/wayland/tools/mapping.txt @@ -40,3 +40,7 @@ org_kde_plasma_window_management;PlasmaWindowManagement org_kde_plasma_window;PlasmaWindow org_kde_kwin_server_decoration_manager;ServerSideDecorationManager 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