diff --git a/autotests/client/CMakeLists.txt b/autotests/client/CMakeLists.txt --- a/autotests/client/CMakeLists.txt +++ b/autotests/client/CMakeLists.txt @@ -297,3 +297,14 @@ 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/autotests/client/test_text_input.cpp b/autotests/client/test_text_input.cpp new file mode 100644 --- /dev/null +++ b/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/autotests/client/test_wayland_registry.cpp b/autotests/client/test_wayland_registry.cpp --- a/autotests/client/test_wayland_registry.cpp +++ b/autotests/client/test_wayland_registry.cpp @@ -44,10 +44,13 @@ #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 @@ void testBindSlideManager(); void testBindDpmsManager(); void testBindServerSideDecorationManager(); + void testBindTextInputManagerUnstableV0(); + void testBindTextInputManagerUnstableV2(); void testGlobalSync(); void testGlobalSyncThreaded(); void testRemoval(); @@ -89,6 +94,8 @@ 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 @@ , m_dataDeviceManager(nullptr) , m_outputManagement(nullptr) , m_serverSideDecorationManager(nullptr) + , m_textInputManagerV0(nullptr) + , m_textInputManagerV2(nullptr) { } @@ -137,6 +146,12 @@ 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 @@ 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/client/CMakeLists.txt b/src/client/CMakeLists.txt --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -42,6 +42,9 @@ subsurface.cpp surface.cpp touch.cpp + textinput.cpp + textinput_v0.cpp + textinput_v2.cpp ) ecm_add_wayland_client_protocol(CLIENT_LIB_SRCS @@ -106,6 +109,14 @@ PROTOCOL ${KWAYLAND_SOURCE_DIR}/src/client/protocols/server-decoration.xml BASENAME server-decoration ) +ecm_add_wayland_client_protocol(CLIENT_LIB_SRCS + PROTOCOL ${KWAYLAND_SOURCE_DIR}/src/client/protocols/text-input.xml + BASENAME text-input-v0 +) +ecm_add_wayland_client_protocol(CLIENT_LIB_SRCS + PROTOCOL ${KWAYLAND_SOURCE_DIR}/src/client/protocols/text-input-unstable-v2.xml + BASENAME text-input-v2 +) add_library(KF5WaylandClient ${CLIENT_LIB_SRCS}) generate_export_header(KF5WaylandClient @@ -173,6 +184,7 @@ subsurface.h surface.h touch.h + textinput.h DESTINATION ${KF5_INCLUDE_INSTALL_DIR}/KWayland/Client COMPONENT Devel ) diff --git a/src/client/protocols/text-input-unstable-v2.xml b/src/client/protocols/text-input-unstable-v2.xml new file mode 100644 --- /dev/null +++ b/src/client/protocols/text-input-unstable-v2.xml @@ -0,0 +1,478 @@ + + + + + Copyright © 2012, 2013 Intel Corporation + Copyright © 2015, 2016 Jan Arne Petersen + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + The zwp_text_input_v2 interface represents text input and input methods + associated with a seat. It provides enter/leave events to follow the + text input focus for a seat. + + Requests are used to enable/disable the text-input object and set + state information like surrounding and selected text or the content type. + The information about the entered text is sent to the text-input object + via the pre-edit and commit events. Using this interface removes the need + for applications to directly process hardware key events and compose text + out of them. + + Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices + have to always point to the first byte of an UTF-8 encoded code point. + Lengths are not allowed to contain just a part of an UTF-8 encoded code + point. + + State is sent by the state requests (set_surrounding_text, + set_content_type, set_cursor_rectangle and set_preferred_language) and + an update_state request. After an enter or an input_method_change event + all state information is invalidated and needs to be resent from the + client. A reset or entering a new widget on client side also + invalidates all current state information. + + + + + Destroy the wp_text_input object. Also disables all surfaces enabled + through this wp_text_input object + + + + + + Enable text input in a surface (usually when a text entry inside of it + has focus). + + This can be called before or after a surface gets text (or keyboard) + focus via the enter event. Text input to a surface is only active + when it has the current text (or keyboard) focus and is enabled. + + + + + + + Disable text input in a surface (typically when there is no focus on any + text entry inside the surface). + + + + + + + Requests input panels (virtual keyboard) to show. + + This should be used for example to show a virtual keyboard again + (with a tap) after it was closed by pressing on a close button on the + keyboard. + + + + + + Requests input panels (virtual keyboard) to hide. + + + + + + Sets the plain surrounding text around the input position. Text is + UTF-8 encoded. Cursor is the byte offset within the surrounding text. + Anchor is the byte offset of the selection anchor within the + surrounding text. If there is no selected text, anchor is the same as + cursor. + + Make sure to always send some text before and after the cursor + except when the cursor is at the beginning or end of text. + + When there was a configure_surrounding_text event take the + before_cursor and after_cursor arguments into account for picking how + much surrounding text to send. + + There is a maximum length of wayland messages so text can not be + longer than 4000 bytes. + + + + + + + + + Content hint is a bitmask to allow to modify the behavior of the text + input. + + + + + + + + + + + + + + + + + The content purpose 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. + + + + + + + + + + + + + + + + + + + Sets the content purpose and content hint. While the purpose is the + basic purpose of an input field, the hint flags allow to modify some + of the behavior. + + When no content type is explicitly set, a normal content purpose with + none hint should be assumed. + + + + + + + + Sets the cursor outline as a x, y, width, height rectangle in surface + local coordinates. + + Allows the compositor to put a window with word suggestions near the + cursor. + + + + + + + + + + Sets a specific language. This allows for example a virtual keyboard to + show a language specific layout. The "language" argument is a RFC-3066 + format language tag. + + It could be used for example in a word processor to indicate language of + currently edited document or in an instant message application which + tracks languages of contacts. + + + + + + + Defines the reason for sending an updated state. + + + + + + + + + + Allows to atomically send state updates from client. + + This request should follow after a batch of state updating requests + like set_surrounding_text, set_content_type, set_cursor_rectangle and + set_preferred_language. + + The flags field indicates why an updated state is sent to the input + method. + + Reset should be used by an editor widget after the text was changed + outside of the normal input method flow. + + For "change" it is enough to send the changed state, else the full + state should be send. + + Serial should be set to the serial from the last enter or + input_method_changed event. + + To make sure to not receive outdated input method events after a + reset or switching to a new widget wl_display_sync() should be used + after update_state in these cases. + + + + + + + + Notification that this seat's text-input focus is on a certain surface. + + When the seat has the keyboard capability the text-input focus follows + the keyboard focus. + + + + + + + + Notification that this seat's text-input focus is no longer on + a certain surface. + + The leave notification is sent before the enter notification + for the new focus. + + When the seat has the keyboard capabillity the text-input focus follows + the keyboard focus. + + + + + + + + + + + + + Notification that the visibility of the input panel (virtual keyboard) + changed. + + The rectangle x, y, width, height defines the area overlapped by the + input panel (virtual keyboard) on the surface having the text + focus in surface local coordinates. + + That can be used to make sure widgets are visible and not covered by + a virtual keyboard. + + + + + + + + + + + Notify when a new composing text (pre-edit) should be set around the + current cursor position. Any previously set composing text should + be removed. + + The commit text can be used to replace the composing text in some cases + (for example when losing focus). + + The text input should also handle all preedit_style and preedit_cursor + events occurring directly before preedit_string. + + + + + + + + + + + + + + + + + + + Sets styling information on composing text. The style is applied for + length bytes from index relative to the beginning of the composing + text (as byte offset). Multiple styles can be applied to a composing + text by sending multiple preedit_styling events. + + This event is handled as part of a following preedit_string event. + + + + + + + + + Sets the cursor position inside the composing text (as byte + offset) relative to the start of the composing text. When index is a + negative number no cursor is shown. + + When no preedit_cursor event is sent the cursor will be at the end of + the composing text by default. + + This event is handled as part of a following preedit_string event. + + + + + + + Notify when 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 (pre-edit). It could be also an empty text + when some text should be removed (see delete_surrounding_text) or when + the input cursor should be moved (see cursor_position). + + Any previously set composing text should be removed. + + + + + + + Notify when the cursor or anchor position should be modified. + + This event should be handled as part of a following commit_string + event. + + The text between anchor and index should be selected. + + + + + + + + Notify when the text around the current cursor position should be + deleted. BeforeLength and afterLength is the length (in bytes) of text + before and after the current cursor position (excluding the selection) + to delete. + + This event should be handled as part of a following commit_string + or preedit_string event. + + + + + + + + Transfer an array of 0-terminated modifiers names. The position in + the array is the index of the modifier as used in the modifiers + bitmask in the keysym event. + + + + + + + Notify when a key event was sent. Key events should not be used + for normal text input operations, which should be done with + commit_string, delete_surrounding_text, etc. The key event follows + the wl_keyboard key event convention. Sym is a XKB keysym, state a + wl_keyboard key_state. Modifiers are a mask for effective modifiers + (where the modifier indices are set by the modifiers_map event) + + + + + + + + + + Sets the language of the input text. The "language" argument is a RFC-3066 + format language tag. + + + + + + + + + + + + + Sets the text direction of input text. + + It is mainly needed for showing input cursor on correct side of the + editor when there is no input yet done and making sure neutral + direction text is laid out properly. + + + + + + + Configure what amount of surrounding text is expected by the + input method. The surrounding text will be sent in the + set_surrounding_text request on the following state information updates. + + + + + + + + The input method changed on compositor side, which invalidates all + current state information. New state information should be sent from + the client via state requests (set_surrounding_text, + set_content_hint, ...) and update_state. + + + + + + + + + A factory for text-input objects. This object is a global singleton. + + + + + Destroy the wp_text_input_manager object. + + + + + + Creates a new text-input object for a given seat. + + + + + + diff --git a/src/client/protocols/text-input.xml b/src/client/protocols/text-input.xml new file mode 100644 --- /dev/null +++ b/src/client/protocols/text-input.xml @@ -0,0 +1,346 @@ + + + + + Copyright © 2012, 2013 Intel Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + An object used for text input. Adds support for text input and input + methods to applications. A text-input object is created from a + wl_text_input_manager and corresponds typically to a text entry in an + application. + Requests are used to activate/deactivate the text-input object and set + state information like surrounding and selected text or the content type. + The information about entered text is sent to the text-input object via + the pre-edit and commit events. Using this interface removes the need + for applications to directly process hardware key events and compose text + out of them. + + Text is generally UTF-8 encoded, indices and lengths are in bytes. + + Serials are used to synchronize the state between the text input and + an input method. New serials are sent by the text input in the + commit_state request and are used by the input method to indicate + the known text input state in events like preedit_string, commit_string, + and keysym. The text input can then ignore events from the input method + which are based on an outdated state (for example after a reset). + + + + Requests the text-input object to be activated (typically when the + text entry gets focus). + The seat argument is a wl_seat which maintains the focus for this + activation. The surface argument is a wl_surface assigned to the + text-input object and tracked for focus lost. The enter event + is emitted on successful activation. + + + + + + + Requests the text-input object to be deactivated (typically when the + text entry lost focus). The seat argument is a wl_seat which was used + for activation. + + + + + + Requests input panels (virtual keyboard) to show. + + + + + Requests input panels (virtual keyboard) to hide. + + + + + Should be called by an editor widget when the input state should be + reset, for example after the text was changed outside of the normal + input method flow. + + + + + Sets the plain surrounding text around the input position. Text is + UTF-8 encoded. Cursor is the byte offset within the + surrounding text. Anchor is the byte offset of the + selection anchor within the surrounding text. If there is no selected + text anchor is the same as cursor. + + + + + + + + Content hint is a bitmask to allow to modify the behavior of the text + input. + + + + + + + + + + + + + + + + + + The content purpose 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. + + + + + + + + + + + + + + + + + + Sets the content purpose and content hint. While the purpose is the + basic purpose of an input field, the hint flags allow to modify some + of the behavior. + + When no content type is explicitly set, a normal content purpose with + default hints (auto completion, auto correction, auto capitalization) + should be assumed. + + + + + + + + + + + + + Sets a specific language. This allows for example a virtual keyboard to + show a language specific layout. The "language" argument is a RFC-3066 + format language tag. + + It could be used for example in a word processor to indicate language of + currently edited document or in an instant message application which tracks + languages of contacts. + + + + + + + + + + + + + Notify the text-input object when it received focus. Typically in + response to an activate request. + + + + + + Notify the text-input object when it lost focus. Either in response + to a deactivate request or when the assigned surface lost focus or was + destroyed. + + + + + Transfer an array of 0-terminated modifiers names. The position in + the array is the index of the modifier as used in the modifiers + bitmask in the keysym event. + + + + + + Notify when the visibility state of the input panel changed. + + + + + + Notify when a new composing text (pre-edit) should be set around the + current cursor position. Any previously set composing text should + be removed. + + The commit text can be used to replace the preedit text on reset + (for example on unfocus). + + The text input should also handle all preedit_style and preedit_cursor + events occuring directly before preedit_string. + + + + + + + + + + + + + + + + + + Sets styling information on composing text. The style is applied for + length bytes from index relative to the beginning of the composing + text (as byte offset). Multiple styles can + be applied to a composing text by sending multiple preedit_styling + events. + + This event is handled as part of a following preedit_string event. + + + + + + + + Sets the cursor position inside the composing text (as byte + offset) relative to the start of the composing text. When index is a + negative number no cursor is shown. + + This event is handled as part of a following preedit_string event. + + + + + + Notify when 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 (pre-edit). It could be also an empty text + when some text should be removed (see delete_surrounding_text) or when + the input cursor should be moved (see cursor_position). + + Any previously set composing text should be removed. + + + + + + + Notify when the cursor or anchor position should be modified. + + This event should be handled as part of a following commit_string + event. + + + + + + + Notify when the text around the current cursor position should be + deleted. + + Index is relative to the current cursor (in bytes). + Length is the length of deleted text (in bytes). + + This event should be handled as part of a following commit_string + event. + + + + + + + Notify when a key event was sent. Key events should not be used + for normal text input operations, which should be done with + commit_string, delete_surrounding_text, etc. The key event follows + the wl_keyboard key event convention. Sym is a XKB keysym, state a + wl_keyboard key_state. Modifiers are a mask for effective modifiers + (where the modifier indices are set by the modifiers_map event) + + + + + + + + + + Sets the language of the input text. The "language" argument is a RFC-3066 + format language tag. + + + + + + + + + + + + Sets the text direction of input text. + + It is mainly needed for showing input cursor on correct side of the + editor when there is no input yet done and making sure neutral + direction text is laid out properly. + + + + + + + + + A factory for text-input objects. This object is a global singleton. + + + + Creates a new text-input object. + + + + + diff --git a/src/client/registry.h b/src/client/registry.h --- a/src/client/registry.h +++ b/src/client/registry.h @@ -34,6 +34,8 @@ struct wl_shell; struct wl_shm; struct wl_subcompositor; +struct wl_text_input_manager; +struct zwp_text_input_manager_v2; struct _wl_fullscreen_shell; struct org_kde_kwin_outputmanagement; struct org_kde_kwin_outputdevice; @@ -75,6 +77,9 @@ class ShmPool; class ServerSideDecorationManager; class SubCompositor; +class TextInputManager; +class TextInputManagerUnstableV0; +class TextInputManagerUnstableV2; /** * @short Wrapper for the wl_registry interface. @@ -132,6 +137,8 @@ OutputManagement, ///< Refers to the wl_data_device_manager interface OutputDevice, ///< Refers to the org_kde_kwin_outputdevice interface ServerSideDecorationManager, ///< Refers to org_kde_kwin_server_decoration_manager + TextInputManagerUnstableV0, ///< Refers to wl_text_input_manager, @since 5.23 + TextInputManagerUnstableV2 ///< Refers to zwp_text_input_manager_v2, @since 5.23 }; explicit Registry(QObject *parent = nullptr); virtual ~Registry(); @@ -436,6 +443,26 @@ * @since 5.6 **/ org_kde_kwin_server_decoration_manager *bindServerSideDecorationManager(uint32_t name, uint32_t version) const; + /* + * Binds the wl_text_input_manager with @p name and @p version. + * If the @p name does not exist or is not for the text input interface in unstable version 0, + * @c null will be returned. + * + * Prefer using createTextInputManager instead. + * @see createTextInputManager + * @since 5.23 + **/ + wl_text_input_manager *bindTextInputManagerUnstableV0(uint32_t name, uint32_t version) const; + /* + * Binds the zwp_text_input_manager_v2 with @p name and @p version. + * If the @p name does not exist or is not for the text input interface in unstable version 2, + * @c null will be returned. + * + * Prefer using createTextInputManager instead. + * @see createTextInputManager + * @since 5.23 + **/ + zwp_text_input_manager_v2 *bindTextInputManagerUnstableV2(uint32_t name, uint32_t version) const; ///@} /** @@ -755,6 +782,25 @@ * @since 5.6 **/ ServerSideDecorationManager *createServerSideDecorationManager(quint32 name, quint32 version, QObject *parent = nullptr); + /** + * Creates a TextInputManager and sets it up to manage the interface identified by + * @p name and @p version. + * + * This factory method supports the following interfaces: + * @li wl_text_input_manager + * @li zwp_text_input_manager_v2 + * + * If @p name is for one of the supported interfaces the corresponding manager will be created, + * otherwise @c null will be returned. + * + * @param name The name of the interface to bind + * @param version The version of the interface to use + * @param parent The parent for the TextInputManager + * + * @returns The created TextInputManager + * @since 5.23 + **/ + TextInputManager *createTextInputManager(quint32 name, quint32 version, QObject *parent = nullptr); ///@} /** @@ -902,6 +948,20 @@ * @since 5.6 **/ void serverSideDecorationManagerAnnounced(quint32 name, quint32 version); + /** + * Emitted whenever a wl_text_input_manager interface gets announced. + * @param name The name for the announced interface + * @param version The maximum supported version of the announced interface + * @since 5.23 + **/ + void textInputManagerUnstableV0Announced(quint32 name, quint32 version); + /** + * Emitted whenever a zwp_text_input_manager_v2 interface gets announced. + * @param name The name for the announced interface + * @param version The maximum supported version of the announced interface + * @since 5.23 + **/ + void textInputManagerUnstableV2Announced(quint32 name, quint32 version); ///@} /** * @name Interface removed signals. @@ -1019,6 +1079,18 @@ * @since 5.6 **/ void serverSideDecorationManagerRemoved(quint32 name); + /** + * Emitted whenever a wl_text_input_manager interface gets removed. + * @param name The name for the removed interface + * @since 5.23 + **/ + void textInputManagerUnstableV0Removed(quint32 name); + /** + * Emitted whenever a zwp_text_input_manager_v2 interface gets removed. + * @param name The name for the removed interface + * @since 5.23 + **/ + void textInputManagerUnstableV2Removed(quint32 name); ///@} /** * Generic announced signal which gets emitted whenever an interface gets diff --git a/src/client/registry.cpp b/src/client/registry.cpp --- a/src/client/registry.cpp +++ b/src/client/registry.cpp @@ -42,6 +42,7 @@ #include "shell.h" #include "shm_pool.h" #include "subcompositor.h" +#include "textinput_p.h" #include "wayland_pointer_p.h" // Qt #include @@ -60,6 +61,8 @@ #include #include #include +#include +#include /***** * How to add another interface: @@ -227,6 +230,20 @@ &org_kde_kwin_server_decoration_manager_interface, &Registry::serverSideDecorationManagerAnnounced, &Registry::serverSideDecorationManagerRemoved + }}, + {Registry::Interface::TextInputManagerUnstableV0, { + 1, + QByteArrayLiteral("wl_text_input_manager"), + &wl_text_input_manager_interface, + &Registry::textInputManagerUnstableV0Announced, + &Registry::textInputManagerUnstableV0Removed + }}, + {Registry::Interface::TextInputManagerUnstableV2, { + 1, + QByteArrayLiteral("zwp_text_input_manager_v2"), + &zwp_text_input_manager_v2_interface, + &Registry::textInputManagerUnstableV2Announced, + &Registry::textInputManagerUnstableV2Removed }} }; @@ -248,6 +265,7 @@ bool hasInterface(Interface interface) const; AnnouncedInterface interface(Interface interface) const; QVector interfaces(Interface interface) const; + Interface interfaceForName(quint32 name) const; template T *bind(Interface interface, uint32_t name, uint32_t version) const; template @@ -468,6 +486,18 @@ return AnnouncedInterface{0, 0}; } +Registry::Interface Registry::Private::interfaceForName(quint32 name) const +{ + auto it = std::find_if(m_interfaces.constBegin(), m_interfaces.constEnd(), + [name] (const InterfaceData &data) { + return data.name == name; + }); + if (it == m_interfaces.constEnd()) { + return Interface::Unknown; + } + return (*it).interface; +} + bool Registry::hasInterface(Registry::Interface interface) const { return d->hasInterface(interface); @@ -506,6 +536,8 @@ BIND(OutputManagement, org_kde_kwin_outputmanagement) BIND(OutputDevice, org_kde_kwin_outputdevice) BIND(ServerSideDecorationManager, org_kde_kwin_server_decoration_manager) +BIND(TextInputManagerUnstableV0, wl_text_input_manager) +BIND(TextInputManagerUnstableV2, zwp_text_input_manager_v2) BIND2(ShadowManager, Shadow, org_kde_kwin_shadow_manager) BIND2(BlurManager, Blur, org_kde_kwin_blur_manager) BIND2(ContrastManager, Contrast, org_kde_kwin_contrast_manager) @@ -563,6 +595,18 @@ #undef CREATE #undef CREATE2 +TextInputManager *Registry::createTextInputManager(quint32 name, quint32 version, QObject *parent) +{ + switch (d->interfaceForName(name)) { + case Interface::TextInputManagerUnstableV0: + return d->create(name, version, parent, &Registry::bindTextInputManagerUnstableV0); + case Interface::TextInputManagerUnstableV2: + return d->create(name, version, parent, &Registry::bindTextInputManagerUnstableV2); + default: + return nullptr; + } +} + namespace { static const wl_interface *wlInterface(Registry::Interface interface) { diff --git a/src/client/textinput.h b/src/client/textinput.h new file mode 100644 --- /dev/null +++ b/src/client/textinput.h @@ -0,0 +1,536 @@ +/**************************************************************************** +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_CLIENT_TEXTINPUT_H +#define KWAYLAND_CLIENT_TEXTINPUT_H + +#include + +#include + +struct wl_text_input; +struct wl_text_input_manager; +struct zwp_text_input_manager_v2; + +namespace KWayland +{ +namespace Client +{ + +class EventQueue; +class TextInputUnstableV0; +class Surface; +class Seat; + +/** + * @brief TextInput represents a Wayland interface for text input. + * + * The TextInput allows to have text composed by the Compositor and be sent to + * the client. + * + * Depending on the interface the TextInputManager got created for this class + * encapsulates one of the following interfaces: + * @li wl_text_input + * @li zwp_text_input_v2 + * + * @since 5.23 + **/ +class KWAYLANDCLIENT_EXPORT TextInput : public QObject +{ + Q_OBJECT +public: + virtual ~TextInput(); + /** + * @returns @c true if managing a resource. + **/ + bool isValid() const; + + /** + * @returns The Surface which has the text input focus on this TextInput. + * @see entered + * @see left + **/ + Surface *enteredSurface() const; + + void setEventQueue(EventQueue *queue); + EventQueue *eventQueue() const; + + /** + * @returns whether the input panel (virtual keyboard) is currently visible on the screen + * @see inputPanelStateChanged + **/ + bool isInputPanelVisible() const; + + /** + * Enable text input in a @p surface (usually when a text entry inside of it has focus). + * + * This can be called before or after a surface gets text (or keyboard) focus via the + * enter event. Text input to a surface is only active when it has the current + * text (or keyboard) focus and is enabled. + * @see deactivate + **/ + void enable(Surface *surface); + + /** + * Disable text input in a @p surface (typically when there is no focus on any + * text entry inside the surface). + * @see enable + **/ + void disable(Surface *surface); + + /** + * Requests input panels (virtual keyboard) to show. + * @see hideInputPanel + **/ + void showInputPanel(); + + /** + * Requests input panels (virtual keyboard) to hide. + * @see showInputPanel + **/ + void hideInputPanel(); + + /** + * Should be called by an editor widget when the input state should be + * reset, for example after the text was changed outside of the normal + * input method flow. + **/ + void reset(); + + /** + * Sets the plain surrounding text around the input position. + * + * @param text The text surrounding the cursor position + * @param cursor Index in the text describing the cursor position + * @param anchor Index of the selection anchor, if no selection same as cursor + **/ + void setSurroundingText(const QString &text, quint32 cursor, quint32 anchor); + + /** + * The possible states for a keyEvent. + * @see keyEvent + **/ + enum class KeyState { + Pressed, + Released + }; + + /** + * 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 + }; + /** + * Sets the content @p purpose and content @p hints. + * While the @p purpose is the basic purpose of an input field, the @p hints flags allow + * to modify some of the behavior. + **/ + void setContentType(ContentHints hints, ContentPurpose purpose); + + /** + * Sets the cursor outline @p rect in surface local coordinates. + * + * Allows the compositor to e.g. put a window with word suggestions + * near the cursor. + **/ + void setCursorRectangle(const QRect &rect); + + /** + * Sets a specific @p language. + * + * This allows for example a virtual keyboard to show a language specific layout. + * The @p language argument is a RFC-3066 format language tag. + **/ + void setPreferredLanguage(const QString &language); + + /** + * The text direction of input text. + * + * It is mainly needed for showing input cursor on correct side of the + * editor when there is no input yet done and making sure neutral + * direction text is laid out properly. + * @see textDirectionChnaged + **/ + Qt::LayoutDirection textDirection() const; + + /** + * The language of the input text. + * + * As long as the server has not emitted the language, the code will be empty. + * + * @returns a RFC-3066 format language tag in utf-8. + * @see languageChanged + **/ + QByteArray language() const; + + /** + * The cursor position inside the @link{composingText} (as byte offset) relative + * to the start of the @link{composingText}. + * If index is a negative number no cursor is shown. + * @see composingText + * @see composingTextChanged + **/ + qint32 composingTextCursorPosition() const; + + /** + * The currently being composed text around the @link{composingTextCursorPosition}. + * @see composingTextCursorPosition + * @see composingTextChanged + **/ + QByteArray composingText() const; + + /** + * The fallback text can be used to replace the @link{composingText} in some cases + * (for example when losing focus). + * + * @see composingText + * @see composingTextChanged + **/ + QByteArray composingFallbackText() const; + + /** + * The commit text to be inserted. + * + * The commit text might be empty if only text should be deleted or the cursor be moved. + * @see cursorPosition + * @see anchorPosition + * @see deleteSurroundingText + * @see committed + **/ + QByteArray commitText() const; + + /** + * The cursor position in bytes at which the @link{commitText} should be inserted. + * @see committed + **/ + qint32 cursorPosition() const; + + /** + * The text between anchorPosition and @link(cursorPosition} should be selected. + * @see cursorPosition + * @see committed + **/ + qint32 anchorPosition() const; + + /** + * Holds the length before and after the cursor position to be deleted. + **/ + struct DeleteSurroundingText { + quint32 beforeLength; + quint32 afterLength; + }; + /** + * @returns The lenght in bytes which should be deleted around the cursor position + * @see committed + **/ + DeleteSurroundingText deleteSurroundingText() const; + +Q_SIGNALS: + /** + * Emitted whenever a Surface is focused on this TextInput. + * @see enteredSurface + * @see left + **/ + void entered(); + /** + * Emitted whenever a Surface loses the focus on this TextInput. + * @see enteredSurface + * @see entered + **/ + void left(); + /** + * Emitted whenever the state of the input panel (virtual keyboard changes). + * @see isInputPanelVisible + **/ + void inputPanelStateChanged(); + /** + * Emitted whenver the text direction changes. + * @see textDirection + **/ + void textDirectionChanged(); + /** + * Emitted whenever the language changes. + * @see language + **/ + void languageChanged(); + + /** + * Emitted when a key event was sent. + * Key events are not used for normal text input operations, but for specific key symbols + * which are not composable through text. + * + * @param xkbKeySym The XKB key symbol, not a key code + * @param state Whether the event represents a press or release event + * @param modifiers The hold modifiers on this event + * @param time Timestamp of this event + **/ + void keyEvent(quint32 xkbKeySym, KWayland::Client::TextInput::KeyState state, Qt::KeyboardModifiers modifiers, quint32 time); + + /** + * Emitted whenever the composing text and related states changed. + * @see composingText + * @see composingTextCursorPosition + * @see composingFallbackText + **/ + void composingTextChanged(); + + /** + * Emitted when the currently composing text got committed. + * The @link{commitText} should get inserted at the @link{cursorPosition} and + * the text around @link{deleteSurroundingText} should be deleted. + * + * @see commitText + * @see cursorPosition + * @see anchorPosition + * @see deleteSurroundingText + **/ + void committed(); + +protected: + class Private; + QScopedPointer d; + explicit TextInput(Private *p, QObject *parent = nullptr); +}; + +/** + * @brief Manager class for the TextInputManager interfaces. + * + * The TextInputManager supports multiple interfaces: + * @li wl_text_input_manager + * @li zwp_text_input_manager_v2 + * + * Due to that it is different to other manager classes. It can only be created through + * the corresponding factory method in Registry. A manual setup is not directly possible. + * + * The only task of a TextInputManager is to create TextInput for a given Seat. + * + * @since 5.23 + **/ +class KWAYLANDCLIENT_EXPORT TextInputManager : public QObject +{ + Q_OBJECT +public: + virtual ~TextInputManager(); + + /** + * Setup this TextInputManager to manage the @p textinputmanagerunstablev0. + * When using Registry::createTextInputManager there is no need to call this + * method. + **/ + void setup(wl_text_input_manager *textinputmanagerunstablev0); + /** + * Setup this TextInputManager to manage the @p textinputmanagerunstablev0. + * When using Registry::createTextInputManager there is no need to call this + * method. + **/ + void setup(zwp_text_input_manager_v2 *textinputmanagerunstablev2); + /** + * @returns @c true if managing a resource. + **/ + bool isValid() const; + /** + * Releases the interface. + * After the interface has been released the TextInputManager instance is no + * longer valid and can be setup with another interface. + **/ + void release(); + /** + * Destroys the data held by this TextInputManager. + * This method is supposed to be used when the connection to the Wayland + * server goes away. If the connection is not valid anymore, it's not + * possible to call release anymore as that calls into the Wayland + * connection and the call would fail. This method cleans up the data, so + * that the instance can be deleted or set up to a new interface + * once there is a new connection available. + * + * It is suggested to connect this method to ConnectionThread::connectionDied: + * @code + * connect(connection, &ConnectionThread::connectionDied, textinputmanager, &TextInputManager::destroy); + * @endcode + * + * @see release + **/ + void destroy(); + + /** + * Sets the @p queue to use for creating objects with this TextInputManager. + **/ + void setEventQueue(EventQueue *queue); + /** + * @returns The event queue to use for creating objects with this TextInputManager. + **/ + EventQueue *eventQueue(); + + /** + * Creates a TextInput for the @p seat. + * + * @param seat The Seat to create the TextInput for + * @param parent The parent to use for the TextInput + **/ + TextInput *createTextInput(Seat *seat, QObject *parent = nullptr); + + /** + * @returns @c null if not for a wl_text_input_manager + **/ + operator wl_text_input_manager*(); + /** + * @returns @c null if not for a wl_text_input_manager + **/ + operator wl_text_input_manager*() const; + /** + * @returns @c null if not for a zwp_text_input_manager_v2 + **/ + operator zwp_text_input_manager_v2*(); + /** + * @returns @c null if not for a zwp_text_input_manager_v2 + **/ + operator zwp_text_input_manager_v2*() const; + +Q_SIGNALS: + /** + * The corresponding global for this interface on the Registry got removed. + * + * This signal gets only emitted if the TextInputManager got created by + * Registry::createTextInputManager + **/ + void removed(); + +protected: + class Private; + explicit TextInputManager(Private *p, QObject *parent = nullptr); + + QScopedPointer d; +}; + +} +} + +Q_DECLARE_METATYPE(KWayland::Client::TextInput::KeyState) +Q_DECLARE_METATYPE(KWayland::Client::TextInput::ContentHint) +Q_DECLARE_METATYPE(KWayland::Client::TextInput::ContentPurpose) +Q_DECLARE_METATYPE(KWayland::Client::TextInput::ContentHints) +Q_DECLARE_OPERATORS_FOR_FLAGS(KWayland::Client::TextInput::ContentHints) + +#endif diff --git a/src/client/textinput.cpp b/src/client/textinput.cpp new file mode 100644 --- /dev/null +++ b/src/client/textinput.cpp @@ -0,0 +1,228 @@ +/**************************************************************************** +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_p.h" + +namespace KWayland +{ +namespace Client +{ + +TextInput::Private::Private(Seat *seat) + : seat(seat) +{ + currentCommit.deleteSurrounding.afterLength = 0; + currentCommit.deleteSurrounding.beforeLength = 0; + pendingCommit.deleteSurrounding.afterLength = 0; + pendingCommit.deleteSurrounding.beforeLength = 0; +} + +TextInput::TextInput(Private *p, QObject *parent) + : QObject(parent) + , d(p) +{ +} + +TextInput::~TextInput() = default; + +void TextInput::setEventQueue(EventQueue *queue) +{ + d->queue = queue; +} + +EventQueue *TextInput::eventQueue() const +{ + return d->queue; +} + +bool TextInput::isValid() const +{ + return d->isValid(); +} + +Surface *TextInput::enteredSurface() const +{ + return d->enteredSurface; +} + +bool TextInput::isInputPanelVisible() const +{ + return d->inputPanelVisible; +} + +void TextInput::enable(Surface *surface) +{ + d->enable(surface); +} + +void TextInput::disable(Surface *surface) +{ + d->disable(surface); +} + +void TextInput::showInputPanel() +{ + d->showInputPanel(); +} + +void TextInput::hideInputPanel() +{ + d->hideInputPanel(); +} + +void TextInput::reset() +{ + d->reset(); +} + +void TextInput::setSurroundingText(const QString &text, quint32 cursor, quint32 anchor) +{ + d->setSurroundingText(text, cursor, anchor); +} + +void TextInput::setContentType(ContentHints hint, ContentPurpose purpose) +{ + d->setContentType(hint, purpose); +} + +void TextInput::setCursorRectangle(const QRect &rect) +{ + d->setCursorRectangle(rect); +} + +void TextInput::setPreferredLanguage(const QString &language) +{ + d->setPreferredLanguage(language); +} + +Qt::LayoutDirection TextInput::textDirection() const +{ + return d->textDirection; +} + +QByteArray TextInput::language() const +{ + return d->language; +} + +qint32 TextInput::composingTextCursorPosition() const +{ + return d->currentPreEdit.cursor; +} + +QByteArray TextInput::composingText() const +{ + return d->currentPreEdit.text; +} + +QByteArray TextInput::composingFallbackText() const +{ + return d->currentPreEdit.commitText; +} + +qint32 TextInput::anchorPosition() const +{ + return d->currentCommit.anchor; +} + +qint32 TextInput::cursorPosition() const +{ + return d->currentCommit.cursor; +} + +TextInput::DeleteSurroundingText TextInput::deleteSurroundingText() const +{ + return d->currentCommit.deleteSurrounding; +} + +QByteArray TextInput::commitText() const +{ + return d->currentCommit.text; +} + +TextInputManager::TextInputManager(Private *p, QObject *parent) + : QObject(parent) + , d(p) +{ +} + +TextInputManager::~TextInputManager() = default; + +void TextInputManager::setup(wl_text_input_manager *textinputmanagerunstablev0) +{ + d->setupV0(textinputmanagerunstablev0); +} + +void TextInputManager::setup(zwp_text_input_manager_v2 *textinputmanagerunstablev2) +{ + d->setupV2(textinputmanagerunstablev2); +} + +void TextInputManager::release() +{ + d->release(); +} + +void TextInputManager::destroy() +{ + d->destroy(); +} + +void TextInputManager::setEventQueue(EventQueue *queue) +{ + d->queue = queue; +} + +EventQueue *TextInputManager::eventQueue() +{ + return d->queue; +} + +TextInputManager::operator wl_text_input_manager*() +{ + return *(d.data()); +} + +TextInputManager::operator wl_text_input_manager*() const +{ + return *(d.data()); +} + +TextInputManager::operator zwp_text_input_manager_v2*() +{ + return *(d.data()); +} + +TextInputManager::operator zwp_text_input_manager_v2*() const +{ + return *(d.data()); +} + +bool TextInputManager::isValid() const +{ + return d->isValid(); +} + +TextInput *TextInputManager::createTextInput(Seat *seat, QObject *parent) +{ + return d->createTextInput(seat, parent); +} + +} +} diff --git a/src/client/textinput_p.h b/src/client/textinput_p.h new file mode 100644 --- /dev/null +++ b/src/client/textinput_p.h @@ -0,0 +1,245 @@ +/**************************************************************************** +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_CLIENT_TEXTINPUT_P_H +#define KWAYLAND_CLIENT_TEXTINPUT_P_H +#include "textinput.h" + +#include + +struct wl_text_input; +struct wl_text_input_manager; +struct zwp_text_input_v2; + +namespace KWayland +{ +namespace Client +{ + +class EventQueue; +class TextInputUnstableV0; +class Surface; +class Seat; + +class TextInputManagerUnstableV0 : public TextInputManager +{ + Q_OBJECT +public: + /** + * Creates a new TextInputManagerUnstableV0. + * Note: after constructing the TextInputManagerUnstableV0 it is not yet valid and one needs + * to call setup. In order to get a ready to use TextInputManagerUnstableV0 prefer using + * Registry::createTextInputManagerUnstableV0. + **/ + explicit TextInputManagerUnstableV0(QObject *parent = nullptr); + virtual ~TextInputManagerUnstableV0(); + +private: + class Private; + Private *d_func() const; +}; + +class TextInputManagerUnstableV2 : public TextInputManager +{ + Q_OBJECT +public: + /** + * Creates a new TextInputManagerUnstableV0. + * Note: after constructing the TextInputManagerUnstableV0 it is not yet valid and one needs + * to call setup. In order to get a ready to use TextInputManagerUnstableV0 prefer using + * Registry::createTextInputManagerUnstableV0. + **/ + explicit TextInputManagerUnstableV2(QObject *parent = nullptr); + virtual ~TextInputManagerUnstableV2(); + +private: + class Private; + QScopedPointer d; +}; + +class TextInputManager::Private +{ +public: + Private() = default; + virtual ~Private() = default; + + virtual void release() = 0; + virtual void destroy() = 0; + virtual bool isValid() = 0; + virtual void setupV0(wl_text_input_manager *textinputmanagerunstablev0) { + Q_UNUSED(textinputmanagerunstablev0) + } + virtual void setupV2(zwp_text_input_manager_v2 *textinputmanagerunstablev2) { + Q_UNUSED(textinputmanagerunstablev2) + } + virtual TextInput *createTextInput(Seat *seat, QObject *parent = nullptr) = 0; + virtual operator wl_text_input_manager*() { + return nullptr; + } + virtual operator wl_text_input_manager*() const { + return nullptr; + } + virtual operator zwp_text_input_manager_v2*() { + return nullptr; + } + virtual operator zwp_text_input_manager_v2*() const { + return nullptr; + } + + EventQueue *queue = nullptr; +}; + +class TextInput::Private +{ +public: + Private(Seat *seat); + virtual ~Private() = default; + + virtual bool isValid() const = 0; + virtual void enable(Surface *surface) = 0; + virtual void disable(Surface *surface) = 0; + virtual void showInputPanel() = 0; + virtual void hideInputPanel() = 0; + virtual void setCursorRectangle(const QRect &rect) = 0; + virtual void setPreferredLanguage(const QString &lang) = 0; + virtual void setSurroundingText(const QString &text, quint32 cursor, quint32 anchor) = 0; + virtual void reset() = 0; + virtual void setContentType(ContentHints hint, ContentPurpose purpose) = 0; + + EventQueue *queue = nullptr; + Seat *seat; + Surface *enteredSurface = nullptr; + quint32 latestSerial = 0; + bool inputPanelVisible = false; + Qt::LayoutDirection textDirection = Qt::LayoutDirectionAuto; + QByteArray language; + + struct PreEdit { + QByteArray text; + QByteArray commitText; + qint32 cursor = 0; + bool cursorSet = false; + }; + PreEdit currentPreEdit; + PreEdit pendingPreEdit; + + struct Commit { + QByteArray text; + qint32 cursor = 0; + qint32 anchor = 0; + DeleteSurroundingText deleteSurrounding; + }; + Commit currentCommit; + Commit pendingCommit; +}; + +class TextInputUnstableV0 : public TextInput +{ + Q_OBJECT +public: + explicit TextInputUnstableV0(Seat *seat, QObject *parent = nullptr); + virtual ~TextInputUnstableV0(); + + /** + * Setup this TextInputUnstableV0 to manage the @p textinputunstablev0. + * When using TextInputManagerUnstableV0::createTextInputUnstableV0 there is no need to call this + * method. + **/ + void setup(wl_text_input *textinputunstablev0); + /** + * Releases the wl_text_input interface. + * After the interface has been released the TextInputUnstableV0 instance is no + * longer valid and can be setup with another wl_text_input interface. + **/ + void release(); + /** + * Destroys the data held by this TextInputUnstableV0. + * This method is supposed to be used when the connection to the Wayland + * server goes away. If the connection is not valid anymore, it's not + * possible to call release anymore as that calls into the Wayland + * connection and the call would fail. This method cleans up the data, so + * that the instance can be deleted or set up to a new wl_text_input interface + * once there is a new connection available. + * + * It is suggested to connect this method to ConnectionThread::connectionDied: + * @code + * connect(connection, &ConnectionThread::connectionDied, textinputunstablev0, &TextInputUnstableV0::destroy); + * @endcode + * + * @see release + **/ + void destroy(); + + operator wl_text_input*(); + operator wl_text_input*() const; + +private: + class Private; + Private *d_func() const; +}; + +class TextInputUnstableV2 : public TextInput +{ + Q_OBJECT +public: + explicit TextInputUnstableV2(Seat *seat, QObject *parent = nullptr); + virtual ~TextInputUnstableV2(); + + /** + * Setup this TextInputUnstableV2 to manage the @p textinputunstablev2. + * When using TextInputManagerUnstableV2::createTextInputUnstableV2 there is no need to call this + * method. + **/ + void setup(zwp_text_input_v2 *textinputunstablev2); + /** + * Releases the zwp_text_input_v2 interface. + * After the interface has been released the TextInputUnstableV2 instance is no + * longer valid and can be setup with another zwp_text_input_v2 interface. + **/ + void release(); + /** + * Destroys the data held by this TextInputUnstableV2. + * This method is supposed to be used when the connection to the Wayland + * server goes away. If the connection is not valid anymore, it's not + * possible to call release anymore as that calls into the Wayland + * connection and the call would fail. This method cleans up the data, so + * that the instance can be deleted or set up to a new zwp_text_input_v2 interface + * once there is a new connection available. + * + * It is suggested to connect this method to ConnectionThread::connectionDied: + * @code + * connect(connection, &ConnectionThread::connectionDied, textinputunstablev2, &TextInputUnstableV2::destroy); + * @endcode + * + * @see release + **/ + void destroy(); + + operator zwp_text_input_v2*(); + operator zwp_text_input_v2*() const; + +private: + class Private; + Private *d_func() const; +}; + +} +} + +#endif diff --git a/src/client/textinput_v0.cpp b/src/client/textinput_v0.cpp new file mode 100644 --- /dev/null +++ b/src/client/textinput_v0.cpp @@ -0,0 +1,500 @@ +/**************************************************************************** +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_p.h" +#include "event_queue.h" +#include "seat.h" +#include "surface.h" +#include "wayland_pointer_p.h" + +#include + +namespace KWayland +{ +namespace Client +{ + +class TextInputUnstableV0::Private : public TextInput::Private +{ +public: + Private(TextInputUnstableV0 *q, Seat *seat); + + void setup(wl_text_input *textinputmanagerunstablev0); + + bool isValid() const override; + void enable(Surface *surface) override; + void disable(Surface * surface) override; + void showInputPanel() override; + void hideInputPanel() override; + void setCursorRectangle(const QRect &rect) override; + void setPreferredLanguage(const QString &lang) override; + void setSurroundingText(const QString &text, quint32 cursor, quint32 anchor) override; + void reset() override; + void setContentType(ContentHints hint, ContentPurpose purpose) override; + + WaylandPointer textinputunstablev0; + +private: + static void enterCallaback(void *data, wl_text_input *wl_text_input, wl_surface *surface); + static void leaveCallback(void *data, wl_text_input *wl_text_input); + static void modifiersMapCallback(void *data, wl_text_input *wl_text_input, wl_array *map); + static void inputPanelStateCallback(void *data, wl_text_input *wl_text_input, uint32_t state); + static void preeditStringCallback(void *data, wl_text_input *wl_text_input, uint32_t serial, const char *text, const char *commit); + static void preeditStylingCallback(void *data, wl_text_input *wl_text_input, uint32_t index, uint32_t length, uint32_t style); + static void preeditCursorCallback(void *data, wl_text_input *wl_text_input, int32_t index); + static void commitStringCallback(void *data, wl_text_input *wl_text_input, uint32_t serial, const char *text); + static void cursorPositionCallback(void *data, wl_text_input *wl_text_input, int32_t index, int32_t anchor); + static void deleteSurroundingTextCallback(void *data, wl_text_input *wl_text_input, int32_t index, uint32_t length); + static void keysymCallback(void *data, wl_text_input *wl_text_input, uint32_t serial, uint32_t time, uint32_t sym, uint32_t state, uint32_t modifiers); + static void languageCallback(void *data, wl_text_input *wl_text_input, uint32_t serial, const char *language); + static void textDirectionCallback(void *data, wl_text_input *wl_text_input, uint32_t serial, uint32_t direction); + + TextInputUnstableV0 *q; + + static const wl_text_input_listener s_listener; +}; + +const wl_text_input_listener TextInputUnstableV0::Private::s_listener = { + enterCallaback, + leaveCallback, + modifiersMapCallback, + inputPanelStateCallback, + preeditStringCallback, + preeditStylingCallback, + preeditCursorCallback, + commitStringCallback, + cursorPositionCallback, + deleteSurroundingTextCallback, + keysymCallback, + languageCallback, + textDirectionCallback +}; + +void TextInputUnstableV0::Private::enterCallaback(void *data, wl_text_input *wl_text_input, wl_surface *surface) +{ + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev0 == wl_text_input); + t->enteredSurface = Surface::get(surface); + emit t->q->entered(); +} + +void TextInputUnstableV0::Private::leaveCallback(void *data, wl_text_input *wl_text_input) +{ + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev0 == wl_text_input); + t->enteredSurface = nullptr; + emit t->q->left(); +} + +void TextInputUnstableV0::Private::modifiersMapCallback(void *data, wl_text_input *wl_text_input, wl_array *map) +{ + Q_UNUSED(map) + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev0 == wl_text_input); + // TODO: implement +} + +void TextInputUnstableV0::Private::inputPanelStateCallback(void *data, wl_text_input *wl_text_input, uint32_t state) +{ + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev0 == wl_text_input); + if (t->inputPanelVisible != state) { + t->inputPanelVisible = state; + emit t->q->inputPanelStateChanged(); + } +} + +void TextInputUnstableV0::Private::preeditStringCallback(void *data, wl_text_input *wl_text_input, uint32_t serial, const char *text, const char *commit) +{ + Q_UNUSED(serial) + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev0 == wl_text_input); + t->pendingPreEdit.commitText = QByteArray(commit); + t->pendingPreEdit.text = QByteArray(text); + if (!t->pendingPreEdit.cursorSet) { + t->pendingPreEdit.cursor = t->pendingPreEdit.text.length(); + } + t->currentPreEdit = t->pendingPreEdit; + t->pendingPreEdit = TextInput::Private::PreEdit(); + emit t->q->composingTextChanged(); +} + +void TextInputUnstableV0::Private::preeditStylingCallback(void *data, wl_text_input *wl_text_input, uint32_t index, uint32_t length, uint32_t style) +{ + Q_UNUSED(index) + Q_UNUSED(length) + Q_UNUSED(style) + // TODO: implement + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev0 == wl_text_input); +} + +void TextInputUnstableV0::Private::preeditCursorCallback(void *data, wl_text_input *wl_text_input, int32_t index) +{ + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev0 == wl_text_input); + t->pendingPreEdit.cursor = index; + t->pendingPreEdit.cursorSet = true; +} + +void TextInputUnstableV0::Private::commitStringCallback(void *data, wl_text_input *wl_text_input, uint32_t serial, const char *text) +{ + Q_UNUSED(serial) + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev0 == wl_text_input); + t->pendingCommit.text = QByteArray(text); + t->currentCommit = t->pendingCommit; + // TODO: what are the proper values it should be set to? + t->pendingCommit = TextInput::Private::Commit(); + t->pendingCommit.deleteSurrounding.beforeLength = 0; + t->pendingCommit.deleteSurrounding.afterLength = 0; + emit t->q->committed(); +} + +void TextInputUnstableV0::Private::cursorPositionCallback(void *data, wl_text_input *wl_text_input, int32_t index, int32_t anchor) +{ + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev0 == wl_text_input); + t->pendingCommit.cursor = index; + t->pendingCommit.anchor = anchor; +} + +void TextInputUnstableV0::Private::deleteSurroundingTextCallback(void *data, wl_text_input *wl_text_input, int32_t index, uint32_t length) +{ + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev0 == wl_text_input); + t->pendingCommit.deleteSurrounding.beforeLength = qAbs(index); + t->pendingCommit.deleteSurrounding.afterLength = length - t->pendingCommit.deleteSurrounding.beforeLength; +} + +void TextInputUnstableV0::Private::keysymCallback(void *data, wl_text_input *wl_text_input, uint32_t serial, uint32_t time, uint32_t sym, uint32_t wlState, uint32_t modifiers) +{ + Q_UNUSED(serial) + // TODO: add support for modifiers + Q_UNUSED(modifiers) + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev0 == wl_text_input); + TextInput::KeyState state; + switch (wlState) { + case WL_KEYBOARD_KEY_STATE_RELEASED: + state = TextInput::KeyState::Released; + break; + case WL_KEYBOARD_KEY_STATE_PRESSED: + state = TextInput::KeyState::Pressed; + break; + default: + // invalid + return; + } + emit t->q->keyEvent(sym, state, Qt::KeyboardModifiers(), time); +} + +void TextInputUnstableV0::Private::languageCallback(void *data, wl_text_input *wl_text_input, uint32_t serial, const char *language) +{ + Q_UNUSED(serial) + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev0 == wl_text_input); + if (qstrcmp(t->language, language) != 0) { + t->language = QByteArray(language); + emit t->q->languageChanged(); + } +} + +void TextInputUnstableV0::Private::textDirectionCallback(void *data, wl_text_input *wl_text_input, uint32_t serial, uint32_t wlDirection) +{ + Q_UNUSED(serial) + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev0 == wl_text_input); + Qt::LayoutDirection direction; + switch (wlDirection) { + case WL_TEXT_INPUT_TEXT_DIRECTION_LTR: + direction = Qt::LeftToRight; + break; + case WL_TEXT_INPUT_TEXT_DIRECTION_RTL: + direction = Qt::RightToLeft; + break; + case WL_TEXT_INPUT_TEXT_DIRECTION_AUTO: + direction = Qt::LayoutDirectionAuto; + break; + default: + // invalid + return; + } + if (direction != t->textDirection) { + t->textDirection = direction; + emit t->q->textDirectionChanged(); + } +} + +TextInputUnstableV0::Private::Private(TextInputUnstableV0 *q, Seat *seat) + : TextInput::Private(seat) + , q(q) +{ +} + +void TextInputUnstableV0::Private::setup(wl_text_input *ti) +{ + Q_ASSERT(ti); + Q_ASSERT(!textinputunstablev0); + textinputunstablev0.setup(ti); + wl_text_input_add_listener(ti, &s_listener, this); +} + +bool TextInputUnstableV0::Private::isValid() const +{ + return textinputunstablev0.isValid(); +} + +void TextInputUnstableV0::Private::enable(Surface *surface) +{ + wl_text_input_activate(textinputunstablev0, *seat, *surface); +} + +void TextInputUnstableV0::Private::disable(Surface *surface) +{ + Q_UNUSED(surface) + wl_text_input_deactivate(textinputunstablev0, *seat); +} + +void TextInputUnstableV0::Private::showInputPanel() +{ + wl_text_input_show_input_panel(textinputunstablev0); +} + +void TextInputUnstableV0::Private::hideInputPanel() +{ + wl_text_input_hide_input_panel(textinputunstablev0); +} + +void TextInputUnstableV0::Private::setCursorRectangle(const QRect &rect) +{ + wl_text_input_set_cursor_rectangle(textinputunstablev0, rect.x(), rect.y(), rect.width(), rect.height()); +} + +void TextInputUnstableV0::Private::setPreferredLanguage(const QString &lang) +{ + wl_text_input_set_preferred_language(textinputunstablev0, lang.toUtf8().constData()); +} + +void TextInputUnstableV0::Private::setSurroundingText(const QString &text, quint32 cursor, quint32 anchor) +{ + wl_text_input_set_surrounding_text(textinputunstablev0, text.toUtf8().constData(), + text.leftRef(cursor).toUtf8().length(), + text.leftRef(anchor).toUtf8().length()); +} + +void TextInputUnstableV0::Private::reset() +{ + wl_text_input_reset(textinputunstablev0); +} + +void TextInputUnstableV0::Private::setContentType(ContentHints hints, ContentPurpose purpose) +{ + uint32_t wlHints = 0; + uint32_t wlPurpose = 0; + if (hints.testFlag(ContentHint::AutoCompletion)) { + wlHints |= WL_TEXT_INPUT_CONTENT_HINT_AUTO_COMPLETION; + } + if (hints.testFlag(ContentHint::AutoCorrection)) { + wlHints |= WL_TEXT_INPUT_CONTENT_HINT_AUTO_CORRECTION; + } + if (hints.testFlag(ContentHint::AutoCapitalization)) { + wlHints |= WL_TEXT_INPUT_CONTENT_HINT_AUTO_CAPITALIZATION; + } + if (hints.testFlag(ContentHint::LowerCase)) { + wlHints |= WL_TEXT_INPUT_CONTENT_HINT_LOWERCASE; + } + if (hints.testFlag(ContentHint::UpperCase)) { + wlHints |= WL_TEXT_INPUT_CONTENT_HINT_UPPERCASE; + } + if (hints.testFlag(ContentHint::TitleCase)) { + wlHints |= WL_TEXT_INPUT_CONTENT_HINT_TITLECASE; + } + if (hints.testFlag(ContentHint::HiddenText)) { + wlHints |= WL_TEXT_INPUT_CONTENT_HINT_HIDDEN_TEXT; + } + if (hints.testFlag(ContentHint::SensitiveData)) { + wlHints |= WL_TEXT_INPUT_CONTENT_HINT_SENSITIVE_DATA; + } + if (hints.testFlag(ContentHint::Latin)) { + wlHints |= WL_TEXT_INPUT_CONTENT_HINT_LATIN; + } + if (hints.testFlag(ContentHint::MultiLine)) { + wlHints |= WL_TEXT_INPUT_CONTENT_HINT_MULTILINE; + } + switch (purpose) { + case ContentPurpose::Normal: + wlPurpose = WL_TEXT_INPUT_CONTENT_PURPOSE_NORMAL; + break; + case ContentPurpose::Alpha: + wlPurpose = WL_TEXT_INPUT_CONTENT_PURPOSE_ALPHA; + break; + case ContentPurpose::Digits: + wlPurpose = WL_TEXT_INPUT_CONTENT_PURPOSE_DIGITS; + break; + case ContentPurpose::Number: + wlPurpose = WL_TEXT_INPUT_CONTENT_PURPOSE_NUMBER; + break; + case ContentPurpose::Phone: + wlPurpose = WL_TEXT_INPUT_CONTENT_PURPOSE_PHONE; + break; + case ContentPurpose::Url: + wlPurpose = WL_TEXT_INPUT_CONTENT_PURPOSE_URL; + break; + case ContentPurpose::Email: + wlPurpose = WL_TEXT_INPUT_CONTENT_PURPOSE_EMAIL; + break; + case ContentPurpose::Name: + wlPurpose = WL_TEXT_INPUT_CONTENT_PURPOSE_NAME; + break; + case ContentPurpose::Password: + wlPurpose = WL_TEXT_INPUT_CONTENT_PURPOSE_PASSWORD; + break; + case ContentPurpose::Date: + wlPurpose = WL_TEXT_INPUT_CONTENT_PURPOSE_DATE; + break; + case ContentPurpose::Time: + wlPurpose = WL_TEXT_INPUT_CONTENT_PURPOSE_TIME; + break; + case ContentPurpose::DateTime: + wlPurpose = WL_TEXT_INPUT_CONTENT_PURPOSE_DATETIME; + break; + case ContentPurpose::Terminal: + wlPurpose = WL_TEXT_INPUT_CONTENT_PURPOSE_TERMINAL; + break; + } + wl_text_input_set_content_type(textinputunstablev0, wlHints, wlPurpose); +} + +TextInputUnstableV0::TextInputUnstableV0(Seat *seat, QObject *parent) + : TextInput(new Private(this, seat), parent) +{ +} + +TextInputUnstableV0::~TextInputUnstableV0() +{ + release(); +} + +TextInputUnstableV0::Private *TextInputUnstableV0::d_func() const +{ + return reinterpret_cast(d.data()); +} + +void TextInputUnstableV0::setup(wl_text_input *textinputunstablev0) +{ + Q_D(); + d->setup(textinputunstablev0); +} + +void TextInputUnstableV0::release() +{ + Q_D(); + d->textinputunstablev0.release(); +} + +void TextInputUnstableV0::destroy() +{ + Q_D(); + d->textinputunstablev0.destroy(); +} + +TextInputUnstableV0::operator wl_text_input*() +{ + Q_D(); + return d->textinputunstablev0; +} + +TextInputUnstableV0::operator wl_text_input*() const +{ + Q_D(); + return d->textinputunstablev0; +} + +class TextInputManagerUnstableV0::Private : public TextInputManager::Private +{ +public: + Private() = default; + + void release() override; + void destroy() override; + bool isValid() override; + void setupV0(wl_text_input_manager *ti) override; + TextInput *createTextInput(Seat *seat, QObject *parent = nullptr) override; + operator wl_text_input_manager*() override { + return textinputmanagerunstablev0; + } + operator wl_text_input_manager*() const override { + return textinputmanagerunstablev0; + } + + WaylandPointer textinputmanagerunstablev0; +}; + +void TextInputManagerUnstableV0::Private::release() +{ + textinputmanagerunstablev0.release(); +} + +void TextInputManagerUnstableV0::Private::destroy() +{ + textinputmanagerunstablev0.destroy(); +} + +bool TextInputManagerUnstableV0::Private::isValid() +{ + return textinputmanagerunstablev0.isValid(); +} + +TextInputManagerUnstableV0::TextInputManagerUnstableV0(QObject *parent) + : TextInputManager(new Private, parent) +{ +} + +TextInputManagerUnstableV0::Private *TextInputManagerUnstableV0::d_func() const +{ + return reinterpret_cast(d.data()); +} + +TextInputManagerUnstableV0::~TextInputManagerUnstableV0() +{ + release(); +} + +void TextInputManagerUnstableV0::Private::setupV0(wl_text_input_manager *ti) +{ + Q_ASSERT(ti); + Q_ASSERT(!textinputmanagerunstablev0); + textinputmanagerunstablev0.setup(ti); +} + +TextInput *TextInputManagerUnstableV0::Private::createTextInput(Seat *seat, QObject *parent) +{ + Q_ASSERT(isValid()); + TextInputUnstableV0 *t = new TextInputUnstableV0(seat, parent); + auto w = wl_text_input_manager_create_text_input(textinputmanagerunstablev0); + if (queue) { + queue->addProxy(w); + } + t->setup(w); + return t; +} + +} +} diff --git a/src/client/textinput_v2.cpp b/src/client/textinput_v2.cpp new file mode 100644 --- /dev/null +++ b/src/client/textinput_v2.cpp @@ -0,0 +1,516 @@ +/**************************************************************************** +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_p.h" +#include "event_queue.h" +#include "seat.h" +#include "surface.h" +#include "wayland_pointer_p.h" + +#include + +namespace KWayland +{ +namespace Client +{ + +class TextInputUnstableV2::Private : public TextInput::Private +{ +public: + Private(TextInputUnstableV2 *q, Seat *seat); + + void setup(zwp_text_input_v2 *textinputmanagerunstablev0); + + bool isValid() const override; + void enable(Surface *surface) override; + void disable(Surface * surface) override; + void showInputPanel() override; + void hideInputPanel() override; + void setCursorRectangle(const QRect &rect) override; + void setPreferredLanguage(const QString &lang) override; + void setSurroundingText(const QString &text, quint32 cursor, quint32 anchor) override; + void reset() override; + void setContentType(ContentHints hint, ContentPurpose purpose) override; + + WaylandPointer textinputunstablev2; + +private: + static void enterCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t serial, wl_surface *surface); + static void leaveCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t serial, wl_surface *surface); + static void inputPanelStateCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t state, int32_t x, int32_t y, int32_t width, int32_t height); + static void preeditStringCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, const char *text, const char *commit); + static void preeditStylingCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t index, uint32_t length, uint32_t style); + static void preeditCursorCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, int32_t index); + static void commitStringCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, const char *text); + static void cursorPositionCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, int32_t index, int32_t anchor); + static void deleteSurroundingTextCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t before_length, uint32_t after_length); + static void modifiersMapCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, wl_array *map); + static void keysymCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t time, uint32_t sym, uint32_t state, uint32_t modifiers); + static void languageCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, const char *language); + static void textDirectionCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t direction); + static void configureSurroundingTextCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, int32_t before_cursor, int32_t after_cursor); + static void inputMethodChangedCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t serial, uint32_t flags); + + TextInputUnstableV2 *q; + + static const zwp_text_input_v2_listener s_listener; +}; + +const zwp_text_input_v2_listener TextInputUnstableV2::Private::s_listener = { + enterCallback, + leaveCallback, + inputPanelStateCallback, + preeditStringCallback, + preeditStylingCallback, + preeditCursorCallback, + commitStringCallback, + cursorPositionCallback, + deleteSurroundingTextCallback, + modifiersMapCallback, + keysymCallback, + languageCallback, + textDirectionCallback, + configureSurroundingTextCallback, + inputMethodChangedCallback +}; + +void TextInputUnstableV2::Private::enterCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t serial, wl_surface *surface) +{ + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev2 == zwp_text_input_v2); + t->latestSerial = serial; + t->enteredSurface = Surface::get(surface); + emit t->q->entered(); +} + +void TextInputUnstableV2::Private::leaveCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t serial, wl_surface *surface) +{ + Q_UNUSED(surface) + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev2 == zwp_text_input_v2); + t->enteredSurface = nullptr; + t->latestSerial = serial; + emit t->q->left(); +} + +void TextInputUnstableV2::Private::inputPanelStateCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t state, int32_t x, int32_t y, int32_t width, int32_t height) +{ + Q_UNUSED(x) + Q_UNUSED(y) + Q_UNUSED(width) + Q_UNUSED(height) + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev2 == zwp_text_input_v2); + // TODO: add rect + if (t->inputPanelVisible != state) { + t->inputPanelVisible = state; + emit t->q->inputPanelStateChanged(); + } +} + +void TextInputUnstableV2::Private::preeditStringCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, const char *text, const char *commit) +{ + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev2 == zwp_text_input_v2); + t->pendingPreEdit.commitText = QByteArray(commit); + t->pendingPreEdit.text = QByteArray(text); + if (!t->pendingPreEdit.cursorSet) { + t->pendingPreEdit.cursor = t->pendingPreEdit.text.length(); + } + t->currentPreEdit = t->pendingPreEdit; + t->pendingPreEdit = TextInput::Private::PreEdit(); + emit t->q->composingTextChanged(); +} + +void TextInputUnstableV2::Private::preeditStylingCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t index, uint32_t length, uint32_t style) +{ + Q_UNUSED(index) + Q_UNUSED(length) + Q_UNUSED(style) + // TODO: implement + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev2 == zwp_text_input_v2); +} + +void TextInputUnstableV2::Private::preeditCursorCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, int32_t index) +{ + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev2 == zwp_text_input_v2); + t->pendingPreEdit.cursor = index; + t->pendingPreEdit.cursorSet = true; +} + +void TextInputUnstableV2::Private::commitStringCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, const char *text) +{ + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev2 == zwp_text_input_v2); + t->pendingCommit.text = QByteArray(text); + t->currentCommit = t->pendingCommit; + // TODO: what are the proper values it should be set to? + t->pendingCommit = TextInput::Private::Commit(); + t->pendingCommit.deleteSurrounding.beforeLength = 0; + t->pendingCommit.deleteSurrounding.afterLength = 0; + emit t->q->committed(); +} + +void TextInputUnstableV2::Private::cursorPositionCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, int32_t index, int32_t anchor) +{ + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev2 == zwp_text_input_v2); + t->pendingCommit.cursor = index; + t->pendingCommit.anchor = anchor; +} + +void TextInputUnstableV2::Private::deleteSurroundingTextCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t before_length, uint32_t after_length) +{ + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev2 == zwp_text_input_v2); + t->pendingCommit.deleteSurrounding.beforeLength = before_length; + t->pendingCommit.deleteSurrounding.afterLength = after_length; +} + +void TextInputUnstableV2::Private::modifiersMapCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, wl_array *map) +{ + // TODO: implement + Q_UNUSED(map) + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev2 == zwp_text_input_v2); +} + +void TextInputUnstableV2::Private::keysymCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t time, uint32_t sym, uint32_t wlState, uint32_t modifiers) +{ + // TODO: add support for modifiers + Q_UNUSED(modifiers) + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev2 == zwp_text_input_v2); + TextInput::KeyState state; + switch (wlState) { + case WL_KEYBOARD_KEY_STATE_RELEASED: + state = TextInput::KeyState::Released; + break; + case WL_KEYBOARD_KEY_STATE_PRESSED: + state = TextInput::KeyState::Pressed; + break; + default: + // invalid + return; + } + emit t->q->keyEvent(sym, state, Qt::KeyboardModifiers(), time); +} + +void TextInputUnstableV2::Private::languageCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, const char *language) +{ + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev2 == zwp_text_input_v2); + if (qstrcmp(t->language, language) != 0) { + t->language = QByteArray(language); + emit t->q->languageChanged(); + } +} + +void TextInputUnstableV2::Private::textDirectionCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t wlDirection) +{ + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev2 == zwp_text_input_v2); + Qt::LayoutDirection direction; + switch (wlDirection) { + case ZWP_TEXT_INPUT_V2_TEXT_DIRECTION_LTR: + direction = Qt::LeftToRight; + break; + case ZWP_TEXT_INPUT_V2_TEXT_DIRECTION_RTL: + direction = Qt::RightToLeft; + break; + case ZWP_TEXT_INPUT_V2_TEXT_DIRECTION_AUTO: + direction = Qt::LayoutDirectionAuto; + break; + default: + // invalid + return; + } + if (direction != t->textDirection) { + t->textDirection = direction; + emit t->q->textDirectionChanged(); + } +} + +void TextInputUnstableV2::Private::configureSurroundingTextCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, int32_t before_cursor, int32_t after_cursor) +{ + // TODO: implement + Q_UNUSED(before_cursor) + Q_UNUSED(after_cursor) + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev2 == zwp_text_input_v2); +} + +void TextInputUnstableV2::Private::inputMethodChangedCallback(void *data, zwp_text_input_v2 *zwp_text_input_v2, uint32_t serial, uint32_t flags) +{ + Q_UNUSED(serial) + Q_UNUSED(flags) + // TODO: implement + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev2 == zwp_text_input_v2); +} + +TextInputUnstableV2::Private::Private(TextInputUnstableV2 *q, Seat *seat) + : TextInput::Private(seat) + , q(q) +{ +} + +void TextInputUnstableV2::Private::setup(zwp_text_input_v2 *ti) +{ + Q_ASSERT(ti); + Q_ASSERT(!textinputunstablev2); + textinputunstablev2.setup(ti); + zwp_text_input_v2_add_listener(ti, &s_listener, this); +} + +bool TextInputUnstableV2::Private::isValid() const +{ + return textinputunstablev2.isValid(); +} + +void TextInputUnstableV2::Private::enable(Surface *surface) +{ + zwp_text_input_v2_enable(textinputunstablev2, *surface); +} + +void TextInputUnstableV2::Private::disable(Surface * surface) +{ + zwp_text_input_v2_disable(textinputunstablev2, *surface); +} + +void TextInputUnstableV2::Private::showInputPanel() +{ + zwp_text_input_v2_show_input_panel(textinputunstablev2); +} + +void TextInputUnstableV2::Private::hideInputPanel() +{ + zwp_text_input_v2_hide_input_panel(textinputunstablev2); +} + +void TextInputUnstableV2::Private::setCursorRectangle(const QRect &rect) +{ + zwp_text_input_v2_set_cursor_rectangle(textinputunstablev2, rect.x(), rect.y(), rect.width(), rect.height()); +} + +void TextInputUnstableV2::Private::setPreferredLanguage(const QString &lang) +{ + zwp_text_input_v2_set_preferred_language(textinputunstablev2, lang.toUtf8().constData()); +} + +void TextInputUnstableV2::Private::setSurroundingText(const QString &text, quint32 cursor, quint32 anchor) +{ + zwp_text_input_v2_set_surrounding_text(textinputunstablev2, text.toUtf8().constData(), + text.leftRef(cursor).toUtf8().length(), + text.leftRef(anchor).toUtf8().length()); +} + +void TextInputUnstableV2::Private::reset() +{ + zwp_text_input_v2_update_state(textinputunstablev2, latestSerial, ZWP_TEXT_INPUT_V2_UPDATE_STATE_RESET); +} + +void TextInputUnstableV2::Private::setContentType(ContentHints hints, ContentPurpose purpose) +{ + uint32_t wlHints = 0; + uint32_t wlPurpose = 0; + if (hints.testFlag(ContentHint::AutoCompletion)) { + wlHints |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_AUTO_COMPLETION; + } + if (hints.testFlag(ContentHint::AutoCorrection)) { + wlHints |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_AUTO_CORRECTION; + } + if (hints.testFlag(ContentHint::AutoCapitalization)) { + wlHints |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_AUTO_CAPITALIZATION; + } + if (hints.testFlag(ContentHint::LowerCase)) { + wlHints |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_LOWERCASE; + } + if (hints.testFlag(ContentHint::UpperCase)) { + wlHints |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_UPPERCASE; + } + if (hints.testFlag(ContentHint::TitleCase)) { + wlHints |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_TITLECASE; + } + if (hints.testFlag(ContentHint::HiddenText)) { + wlHints |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_HIDDEN_TEXT; + } + if (hints.testFlag(ContentHint::SensitiveData)) { + wlHints |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_SENSITIVE_DATA; + } + if (hints.testFlag(ContentHint::Latin)) { + wlHints |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_LATIN; + } + if (hints.testFlag(ContentHint::MultiLine)) { + wlHints |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_MULTILINE; + } + switch (purpose) { + case ContentPurpose::Normal: + wlPurpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_NORMAL; + break; + case ContentPurpose::Alpha: + wlPurpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_ALPHA; + break; + case ContentPurpose::Digits: + wlPurpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_DIGITS; + break; + case ContentPurpose::Number: + wlPurpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_NUMBER; + break; + case ContentPurpose::Phone: + wlPurpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_PHONE; + break; + case ContentPurpose::Url: + wlPurpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_URL; + break; + case ContentPurpose::Email: + wlPurpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_EMAIL; + break; + case ContentPurpose::Name: + wlPurpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_NAME; + break; + case ContentPurpose::Password: + wlPurpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_PASSWORD; + break; + case ContentPurpose::Date: + wlPurpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_DATE; + break; + case ContentPurpose::Time: + wlPurpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_TIME; + break; + case ContentPurpose::DateTime: + wlPurpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_DATETIME; + break; + case ContentPurpose::Terminal: + wlPurpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_TERMINAL; + break; + } + zwp_text_input_v2_set_content_type(textinputunstablev2, wlHints, wlPurpose); +} + +TextInputUnstableV2::TextInputUnstableV2(Seat *seat, QObject *parent) + : TextInput(new Private(this, seat), parent) +{ +} + +TextInputUnstableV2::~TextInputUnstableV2() +{ + release(); +} + +TextInputUnstableV2::Private *TextInputUnstableV2::d_func() const +{ + return reinterpret_cast(d.data()); +} + +void TextInputUnstableV2::setup(zwp_text_input_v2 *textinputunstablev2) +{ + Q_D(); + d->setup(textinputunstablev2); +} + +void TextInputUnstableV2::release() +{ + Q_D(); + d->textinputunstablev2.release(); +} + +void TextInputUnstableV2::destroy() +{ + Q_D(); + d->textinputunstablev2.destroy(); +} + +TextInputUnstableV2::operator zwp_text_input_v2*() +{ + Q_D(); + return d->textinputunstablev2; +} + +TextInputUnstableV2::operator zwp_text_input_v2*() const +{ + Q_D(); + return d->textinputunstablev2; +} + +class TextInputManagerUnstableV2::Private : public TextInputManager::Private +{ +public: + Private() = default; + + void release() override; + void destroy() override; + bool isValid() override; + void setupV2(zwp_text_input_manager_v2 *ti) override; + TextInput *createTextInput(Seat *seat, QObject *parent = nullptr) override; + operator zwp_text_input_manager_v2*() override { + return textinputmanagerunstablev2; + } + operator zwp_text_input_manager_v2*() const override { + return textinputmanagerunstablev2; + } + + WaylandPointer textinputmanagerunstablev2; +}; + +void TextInputManagerUnstableV2::Private::release() +{ + textinputmanagerunstablev2.release(); +} + +void TextInputManagerUnstableV2::Private::destroy() +{ + textinputmanagerunstablev2.destroy(); +} + +bool TextInputManagerUnstableV2::Private::isValid() +{ + return textinputmanagerunstablev2.isValid(); +} + +TextInputManagerUnstableV2::TextInputManagerUnstableV2(QObject *parent) + : TextInputManager(new Private, parent) +{ +} + +TextInputManagerUnstableV2::~TextInputManagerUnstableV2() = default; + +void TextInputManagerUnstableV2::Private::setupV2(zwp_text_input_manager_v2 *ti) +{ + Q_ASSERT(ti); + Q_ASSERT(!textinputmanagerunstablev2); + textinputmanagerunstablev2.setup(ti); +} + +TextInput *TextInputManagerUnstableV2::Private::createTextInput(Seat *seat, QObject *parent) +{ + Q_ASSERT(isValid()); + TextInputUnstableV2 *t = new TextInputUnstableV2(seat, parent); + auto w = zwp_text_input_manager_v2_get_text_input(textinputmanagerunstablev2, *seat); + if (queue) { + queue->addProxy(w); + } + t->setup(w); + return t; +} + +} +} diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -34,6 +34,9 @@ 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 @@ 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 @@ 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/server/display.h b/src/server/display.h --- a/src/server/display.h +++ b/src/server/display.h @@ -69,6 +69,8 @@ class SlideManagerInterface; class ShellInterface; class SubCompositorInterface; +enum class TextInputInterfaceVersion; +class TextInputManagerInterface; /** * @brief Class holding the Wayland server display loop. @@ -170,6 +172,12 @@ * @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/server/display.cpp b/src/server/display.cpp --- a/src/server/display.cpp +++ b/src/server/display.cpp @@ -39,6 +39,7 @@ #include "slide_interface.h" #include "shell_interface.h" #include "subcompositor_interface.h" +#include "textinput_interface_p.h" #include #include @@ -334,6 +335,23 @@ 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/server/seat_interface.h b/src/server/seat_interface.h --- a/src/server/seat_interface.h +++ b/src/server/seat_interface.h @@ -41,6 +41,7 @@ class DataDeviceInterface; class Display; class SurfaceInterface; +class TextInputInterface; /** * @brief Represents a Seat on the Wayland Display. @@ -413,6 +414,16 @@ **/ 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 @@ 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 @@ * @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/server/seat_interface.cpp b/src/server/seat_interface.cpp --- a/src/server/seat_interface.cpp +++ b/src/server/seat_interface.cpp @@ -27,6 +27,7 @@ #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 @@ 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::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 @@ } } } - 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 @@ 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/server/seat_interface_p.h b/src/server/seat_interface_p.h --- a/src/server/seat_interface_p.h +++ b/src/server/seat_interface_p.h @@ -34,6 +34,7 @@ { class DataDeviceInterface; +class TextInputInterface; class SeatInterface::Private : public Global::Private { @@ -46,7 +47,9 @@ 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 @@ QVector keyboards; QVector touchs; QVector dataDevices; + QVector textInputs; DataDeviceInterface *currentSelection = nullptr; // Pointer related members @@ -122,6 +126,17 @@ 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/server/textinput_interface.h b/src/server/textinput_interface.h new file mode 100644 --- /dev/null +++ b/src/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/server/textinput_interface.cpp b/src/server/textinput_interface.cpp new file mode 100644 --- /dev/null +++ b/src/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/server/textinput_interface_p.h b/src/server/textinput_interface_p.h new file mode 100644 --- /dev/null +++ b/src/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/server/textinput_interface_v0.cpp b/src/server/textinput_interface_v0.cpp new file mode 100644 --- /dev/null +++ b/src/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/server/textinput_interface_v2.cpp b/src/server/textinput_interface_v2.cpp new file mode 100644 --- /dev/null +++ b/src/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/tools/mapping.txt b/src/tools/mapping.txt --- a/src/tools/mapping.txt +++ b/src/tools/mapping.txt @@ -40,3 +40,7 @@ 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