diff --git a/autotests/client/test_text_input.cpp b/autotests/client/test_text_input.cpp --- a/autotests/client/test_text_input.cpp +++ b/autotests/client/test_text_input.cpp @@ -1,5 +1,6 @@ /******************************************************************** Copyright 2016 Martin Gräßlin +Copyright 2018 Roman Gilg This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -68,6 +69,8 @@ void testKeyEvent(); void testPreEdit_data(); void testPreEdit(); + void testPreEditv3_data(); + void testPreEditv3(); void testCommit_data(); void testCommit(); @@ -79,14 +82,16 @@ CompositorInterface *m_compositorInterface = nullptr; TextInputManagerInterface *m_textInputManagerV0Interface = nullptr; TextInputManagerInterface *m_textInputManagerV2Interface = nullptr; + TextInputManagerInterface *m_textInputManagerV3Interface = 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; + TextInputManager *m_textInputManagerV3 = nullptr; }; static const QString s_socketName = QStringLiteral("kwayland-test-text-input-0"); @@ -109,6 +114,8 @@ m_textInputManagerV0Interface->create(); m_textInputManagerV2Interface = m_display->createTextInputManager(TextInputInterfaceVersion::UnstableV2); m_textInputManagerV2Interface->create(); + m_textInputManagerV3Interface = m_display->createTextInputManager(TextInputInterfaceVersion::UnstableV3); + m_textInputManagerV3Interface->create(); // setup connection m_connection = new KWayland::Client::ConnectionThread; @@ -159,6 +166,11 @@ registry.interface(Registry::Interface::TextInputManagerUnstableV2).version, this); QVERIFY(m_textInputManagerV2->isValid()); + + m_textInputManagerV3 = registry.createTextInputManager(registry.interface(Registry::Interface::TextInputManagerUnstableV3).name, + registry.interface(Registry::Interface::TextInputManagerUnstableV3).version, + this); + QVERIFY(m_textInputManagerV3->isValid()); } void TextInputTest::cleanup() @@ -170,6 +182,7 @@ } CLEANUP(m_textInputManagerV0) CLEANUP(m_textInputManagerV2) + CLEANUP(m_textInputManagerV3) CLEANUP(m_keyboard) CLEANUP(m_seat) CLEANUP(m_compositor) @@ -187,6 +200,7 @@ CLEANUP(m_textInputManagerV0Interface) CLEANUP(m_textInputManagerV2Interface) + CLEANUP(m_textInputManagerV3Interface) CLEANUP(m_compositorInterface) CLEANUP(m_seatInterface) CLEANUP(m_display) @@ -215,6 +229,8 @@ return m_textInputManagerV0->createTextInput(m_seat); case TextInputInterfaceVersion::UnstableV2: return m_textInputManagerV2->createTextInput(m_seat); + case TextInputInterfaceVersion::UnstableV3: + return m_textInputManagerV3->createTextInput(m_seat); default: Q_UNREACHABLE(); return nullptr; @@ -225,9 +241,11 @@ { QTest::addColumn("version"); QTest::addColumn("updatesDirectly"); + QTest::addColumn("clientCommits"); - QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0 << false; - QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2 << true; + QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0 << false << false; + QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2 << true << false; + QTest::newRow("UnstableV3") << TextInputInterfaceVersion::UnstableV3 << true << true; } void TextInputTest::testEnterLeave() @@ -253,6 +271,7 @@ QCOMPARE(m_seatInterface->focusedTextInputSurface(), serverSurface); // text input not yet set for the surface QFETCH(bool, updatesDirectly); + QFETCH(bool, clientCommits); QCOMPARE(bool(m_seatInterface->focusedTextInput()), updatesDirectly); QCOMPARE(textInputChangedSpy.isEmpty(), !updatesDirectly); textInput->enable(surface.data()); @@ -266,6 +285,9 @@ QCOMPARE(serverTextInput->interfaceVersion(), version); QSignalSpy enabledChangedSpy(serverTextInput, &TextInputInterface::enabledChanged); QVERIFY(enabledChangedSpy.isValid()); + if (clientCommits) { + textInput->commit(); + } if (updatesDirectly) { QVERIFY(enabledChangedSpy.wait()); enabledChangedSpy.clear(); @@ -299,6 +321,9 @@ // let's deactivate on client side textInput->disable(surface.data()); + if (clientCommits) { + textInput->commit(); + } QVERIFY(enabledChangedSpy.wait()); QCOMPARE(enabledChangedSpy.count(), 1); QVERIFY(!serverTextInput->isEnabled()); @@ -308,6 +333,9 @@ QCOMPARE(m_seatInterface->focusedTextInput(), serverTextInput); //reset textInput->enable(surface.data()); + if (clientCommits) { + textInput->commit(); + } QVERIFY(enabledChangedSpy.wait()); //trigger an enter again and leave, but this @@ -331,6 +359,7 @@ QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0; QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2; + // v3 does not support this anymore } void TextInputTest::testShowHidePanel() @@ -375,9 +404,11 @@ void TextInputTest::testCursorRectangle_data() { QTest::addColumn("version"); + QTest::addColumn("clientCommits"); - QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0; - QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2; + QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0 << false; + QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2 << false; + QTest::newRow("UnstableV3") << TextInputInterfaceVersion::UnstableV3 << true; } void TextInputTest::testCursorRectangle() @@ -402,6 +433,10 @@ QVERIFY(cursorRectangleChangedSpy.isValid()); textInput->setCursorRectangle(QRect(10, 20, 30, 40)); + QFETCH(bool, clientCommits); + if (clientCommits) { + textInput->commit(); + } QVERIFY(cursorRectangleChangedSpy.wait()); QCOMPARE(ti->cursorRectangle(), QRect(10, 20, 30, 40)); } @@ -412,6 +447,7 @@ QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0; QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2; + // v3 does not support this anymore } void TextInputTest::testPreferredLanguage() @@ -445,6 +481,7 @@ QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0; QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2; + // v3 does not support this anymore } void TextInputTest::testReset() @@ -474,9 +511,11 @@ void TextInputTest::testSurroundingText_data() { QTest::addColumn("version"); + QTest::addColumn("clientCommits"); - QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0; - QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2; + QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0 << false; + QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2 << false; + QTest::newRow("UnstableV3") << TextInputInterfaceVersion::UnstableV3 << true; } void TextInputTest::testSurroundingText() @@ -503,6 +542,10 @@ QVERIFY(surroundingTextChangedSpy.isValid()); textInput->setSurroundingText(QStringLiteral("100 €, 100 $"), 5, 6); + QFETCH(bool, clientCommits); + if (clientCommits) { + textInput->commit(); + } QVERIFY(surroundingTextChangedSpy.wait()); QCOMPARE(ti->surroundingText(), QStringLiteral("100 €, 100 $").toUtf8()); QCOMPARE(ti->surroundingTextCursorPosition(), QStringLiteral("100 €, 100 $").toUtf8().indexOf(',')); @@ -592,6 +635,46 @@ TextInputInterface::ContentHint::SensitiveData | TextInputInterface::ContentHint::Latin | TextInputInterface::ContentHint::MultiLine); + + // same for version 3 + + QTest::newRow("completion/v3") << TextInputInterfaceVersion::UnstableV3 << TextInput::ContentHints(TextInput::ContentHint::AutoCompletion) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::AutoCompletion); + QTest::newRow("Correction/v3") << TextInputInterfaceVersion::UnstableV3 << TextInput::ContentHints(TextInput::ContentHint::AutoCorrection) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::AutoCorrection); + QTest::newRow("Capitalization/v3") << TextInputInterfaceVersion::UnstableV3 << TextInput::ContentHints(TextInput::ContentHint::AutoCapitalization) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::AutoCapitalization); + QTest::newRow("Lowercase/v3") << TextInputInterfaceVersion::UnstableV3 << TextInput::ContentHints(TextInput::ContentHint::LowerCase) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::LowerCase); + QTest::newRow("Uppercase/v3") << TextInputInterfaceVersion::UnstableV3 << TextInput::ContentHints(TextInput::ContentHint::UpperCase) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::UpperCase); + QTest::newRow("Titlecase/v3") << TextInputInterfaceVersion::UnstableV3 << TextInput::ContentHints(TextInput::ContentHint::TitleCase) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::TitleCase); + QTest::newRow("HiddenText/v3") << TextInputInterfaceVersion::UnstableV3 << TextInput::ContentHints(TextInput::ContentHint::HiddenText) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::HiddenText); + QTest::newRow("SensitiveData/v3") << TextInputInterfaceVersion::UnstableV3 << TextInput::ContentHints(TextInput::ContentHint::SensitiveData) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::SensitiveData); + QTest::newRow("Latin/v3") << TextInputInterfaceVersion::UnstableV3 << TextInput::ContentHints(TextInput::ContentHint::Latin) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::Latin); + QTest::newRow("Multiline/v3") << TextInputInterfaceVersion::UnstableV3 << TextInput::ContentHints(TextInput::ContentHint::MultiLine) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::MultiLine); + + QTest::newRow("autos/v3") << TextInputInterfaceVersion::UnstableV3 + << (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/v3") << TextInputInterfaceVersion::UnstableV3 + << (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() @@ -616,15 +699,18 @@ QVERIFY(contentTypeChangedSpy.isValid()); QFETCH(TextInput::ContentHints, clientHints); textInput->setContentType(clientHints, TextInput::ContentPurpose::Normal); + textInput->commit(); // only needed for v3 QVERIFY(contentTypeChangedSpy.wait()); QTEST(ti->contentHints(), "serverHints"); // setting to same should not trigger an update textInput->setContentType(clientHints, TextInput::ContentPurpose::Normal); + textInput->commit(); QVERIFY(!contentTypeChangedSpy.wait(100)); // unsetting should work textInput->setContentType(TextInput::ContentHints(), TextInput::ContentPurpose::Normal); + textInput->commit(); QVERIFY(contentTypeChangedSpy.wait()); QCOMPARE(ti->contentHints(), TextInputInterface::ContentHints()); } @@ -643,6 +729,7 @@ 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("Pin/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentPurpose::Pin << 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; @@ -656,10 +743,25 @@ 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("Pin/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentPurpose::Pin << 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; + + QTest::newRow("Alpha/v3") << TextInputInterfaceVersion::UnstableV3 << TextInput::ContentPurpose::Alpha << TextInputInterface::ContentPurpose::Alpha; + QTest::newRow("Digits/v3") << TextInputInterfaceVersion::UnstableV3 << TextInput::ContentPurpose::Digits << TextInputInterface::ContentPurpose::Digits; + QTest::newRow("Number/v3") << TextInputInterfaceVersion::UnstableV3 << TextInput::ContentPurpose::Number << TextInputInterface::ContentPurpose::Number; + QTest::newRow("Phone/v3") << TextInputInterfaceVersion::UnstableV3 << TextInput::ContentPurpose::Phone << TextInputInterface::ContentPurpose::Phone; + QTest::newRow("Url/v3") << TextInputInterfaceVersion::UnstableV3 << TextInput::ContentPurpose::Url << TextInputInterface::ContentPurpose::Url; + QTest::newRow("Email/v3") << TextInputInterfaceVersion::UnstableV3 << TextInput::ContentPurpose::Email << TextInputInterface::ContentPurpose::Email; + QTest::newRow("Name/v3") << TextInputInterfaceVersion::UnstableV3 << TextInput::ContentPurpose::Name << TextInputInterface::ContentPurpose::Name; + QTest::newRow("Password/v3") << TextInputInterfaceVersion::UnstableV3 << TextInput::ContentPurpose::Password << TextInputInterface::ContentPurpose::Password; + QTest::newRow("Pin/v3") << TextInputInterfaceVersion::UnstableV3 << TextInput::ContentPurpose::Pin << TextInputInterface::ContentPurpose::Pin; + QTest::newRow("Date/v3") << TextInputInterfaceVersion::UnstableV3 << TextInput::ContentPurpose::Date << TextInputInterface::ContentPurpose::Date; + QTest::newRow("Time/v3") << TextInputInterfaceVersion::UnstableV3 << TextInput::ContentPurpose::Time << TextInputInterface::ContentPurpose::Time; + QTest::newRow("Datetime/v3") << TextInputInterfaceVersion::UnstableV3 << TextInput::ContentPurpose::DateTime << TextInputInterface::ContentPurpose::DateTime; + QTest::newRow("Terminal/v3") << TextInputInterfaceVersion::UnstableV3 << TextInput::ContentPurpose::Terminal << TextInputInterface::ContentPurpose::Terminal; } void TextInputTest::testContentPurpose() @@ -684,15 +786,18 @@ QVERIFY(contentTypeChangedSpy.isValid()); QFETCH(TextInput::ContentPurpose, clientPurpose); textInput->setContentType(TextInput::ContentHints(), clientPurpose); + textInput->commit(); QVERIFY(contentTypeChangedSpy.wait()); QTEST(ti->contentPurpose(), "serverPurpose"); // setting to same should not trigger an update textInput->setContentType(TextInput::ContentHints(), clientPurpose); + textInput->commit(); QVERIFY(!contentTypeChangedSpy.wait(100)); // unsetting should work textInput->setContentType(TextInput::ContentHints(), TextInput::ContentPurpose::Normal); + textInput->commit(); QVERIFY(contentTypeChangedSpy.wait()); QCOMPARE(ti->contentPurpose(), TextInputInterface::ContentPurpose::Normal); } @@ -707,6 +812,8 @@ QTest::newRow("ltr/v2") << TextInputInterfaceVersion::UnstableV2 << Qt::LeftToRight; QTest::newRow("rtl/v2") << TextInputInterfaceVersion::UnstableV2 << Qt::RightToLeft; + + // v3 does not support this anymore } void TextInputTest::testTextDirection() @@ -751,6 +858,7 @@ QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0; QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2; + // v3 does not support this anymore } void TextInputTest::testLanguage() @@ -793,6 +901,7 @@ QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0; QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2; + // v3 does not support this anymore } void TextInputTest::testKeyEvent() @@ -885,12 +994,81 @@ QCOMPARE(textInput->composingTextCursorPosition(), 6); } +void TextInputTest::testPreEditv3_data() +{ + QTest::addColumn("version"); + + QTest::newRow("UnstableV3") << TextInputInterfaceVersion::UnstableV3; +} + +void TextInputTest::testPreEditv3() +{ + // 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()); + textInput->commit(); + 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->preEdit(QByteArrayLiteral("foo"), QByteArrayLiteral("bar")); + ti->done(); + + QVERIFY(composingTextChangedSpy.wait()); + QCOMPARE(composingTextChangedSpy.count(), 1); + QCOMPARE(textInput->composingText(), QByteArrayLiteral("foo")); + QCOMPARE(textInput->composingFallbackText(), QByteArray()); + QCOMPARE(textInput->composingTextCursorPosition(), 0); + + ti->preEdit(QByteArrayLiteral("foobar"), QByteArray()); + ti->done(); + QVERIFY(composingTextChangedSpy.wait()); + QCOMPARE(composingTextChangedSpy.count(), 2); + QCOMPARE(textInput->composingText(), QByteArrayLiteral("foobar")); + QCOMPARE(textInput->composingFallbackText(), QByteArray()); + QCOMPARE(textInput->composingTextCursorPosition(), 0); + + ti->setPreEditCursor(1); + // no change yet + QVERIFY(!composingTextChangedSpy.wait(500)); + QCOMPARE(composingTextChangedSpy.count(), 2); + QCOMPARE(textInput->composingText(), QByteArrayLiteral("foobar")); + QCOMPARE(textInput->composingFallbackText(), QByteArray()); + QCOMPARE(textInput->composingTextCursorPosition(), 0); + ti->done(); + QVERIFY(composingTextChangedSpy.wait()); + QCOMPARE(composingTextChangedSpy.count(), 3); + QCOMPARE(textInput->composingText(), QByteArrayLiteral("foobar")); + QCOMPARE(textInput->composingFallbackText(), QByteArray()); + QCOMPARE(textInput->composingTextCursorPosition(), 1); +} + void TextInputTest::testCommit_data() { QTest::addColumn("version"); + QTest::addColumn("clientCommits"); + QTest::addColumn("serverDone"); + QTest::addColumn("setsCursor"); - QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0; - QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2; + QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0 << false << false << true; + QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2 << false << false << true; + QTest::newRow("UnstableV3") << TextInputInterfaceVersion::UnstableV3 << true << true << false; } void TextInputTest::testCommit() @@ -909,7 +1087,14 @@ QCOMPARE(textInput->deleteSurroundingText().beforeLength, 0u); QCOMPARE(textInput->deleteSurroundingText().afterLength, 0u); + QFETCH(bool, clientCommits); + QFETCH(bool, serverDone); + QFETCH(bool, setsCursor); + textInput->enable(surface.data()); + if (clientCommits) { + textInput->commit(); + } m_connection->flush(); m_display->dispatchEvents(); @@ -923,11 +1108,14 @@ ti->setCursorPosition(3, 4); ti->deleteSurroundingText(2, 1); ti->commit(QByteArrayLiteral("foo")); + if (serverDone) { + ti->done(); + } QVERIFY(committedSpy.wait()); QCOMPARE(textInput->commitText(), QByteArrayLiteral("foo")); - QCOMPARE(textInput->cursorPosition(), 3); - QCOMPARE(textInput->anchorPosition(), 4); + QCOMPARE(textInput->cursorPosition(), setsCursor ? 3 : 0); + QCOMPARE(textInput->anchorPosition(), setsCursor ? 4 : 0); QCOMPARE(textInput->deleteSurroundingText().beforeLength, 2u); QCOMPARE(textInput->deleteSurroundingText().afterLength, 1u); } diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -53,6 +53,7 @@ textinput.cpp textinput_v0.cpp textinput_v2.cpp + textinput_v3.cpp xdgshell.cpp xdgforeign_v2.cpp xdgforeign.cpp @@ -139,6 +140,10 @@ PROTOCOL ${KWayland_SOURCE_DIR}/src/client/protocols/text-input-unstable-v2.xml BASENAME text-input-v2 ) +ecm_add_wayland_client_protocol(CLIENT_LIB_SRCS + PROTOCOL ${KWayland_SOURCE_DIR}/src/client/protocols/text-input-unstable-v3.xml + BASENAME text-input-v3 +) ecm_add_wayland_client_protocol(CLIENT_LIB_SRCS PROTOCOL ${KWayland_SOURCE_DIR}/src/client/protocols/xdg-shell-unstable-v6.xml BASENAME xdg-shell-v6 @@ -201,6 +206,7 @@ ${CMAKE_CURRENT_BINARY_DIR}/wayland-server-decoration-palette-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-text-input-v0-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-text-input-v2-client-protocol.h + ${CMAKE_CURRENT_BINARY_DIR}/wayland-text-input-v3-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-xdg-shell-v6-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-relativepointer-unstable-v1-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-pointer-gestures-unstable-v1-client-protocol.h diff --git a/src/client/protocols/text-input-unstable-v3.xml b/src/client/protocols/text-input-unstable-v3.xml new file mode 100644 --- /dev/null +++ b/src/client/protocols/text-input-unstable-v3.xml @@ -0,0 +1,441 @@ + + + + + Copyright © 2012, 2013 Intel Corporation + Copyright © 2015, 2016 Jan Arne Petersen + Copyright © 2017, 2018 Red Hat, Inc. + Copyright © 2018 Purism SPC + + 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. + + + + This protocol allows compositors to act as input methods and to send text + to applications. A text input object is used to manage state of what are + typically text entry fields in the application. + + This document adheres to the RFC 2119 when using words like "must", + "should", "may", etc. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + The zwp_text_input_v3 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 preedit_string and commit_string events. + + Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices + must not point to middle bytes inside a code point: they must either + point to the first byte of a code point or to the end of the buffer. + Lengths must be measured between two valid indices. + + Focus moving throughout surfaces will result in the emission of + zwp_text_input_v3.enter and zwp_text_input_v3.leave events. The focused + surface must commit zwp_text_input_v3.enable and + zwp_text_input_v3.disable requests as the keyboard focus moves across + editable and non-editable elements of the UI. Those two requests are not + expected to be paired with each other, the compositor must be able to + handle consecutive series of the same request. + + State is sent by the state requests (set_surrounding_text, + set_content_type and set_cursor_rectangle) and a commit request. After an + enter event or disable request all state information is invalidated and + needs to be resent by the client. + + + + + Destroy the wp_text_input object. Also disables all surfaces enabled + through this wp_text_input object. + + + + + + Requests text input on the surface previously obtained from the enter + event. + + This request must be issued every time the active text input changes + to a new one, including within the current surface. Use + zwp_text_input_v3.disable when there is no longer any input focus on + the current surface. + + This request resets all state associated with previous enable, disable, + set_surrounding_text, set_text_change_cause, set_content_type, and + set_cursor_rectangle requests, as well as the state associated with + preedit_string, commit_string, and delete_surrounding_text events. + + The set_surrounding_text, set_content_type and set_cursor_rectangle + requests must follow if the text input supports the necessary + functionality. + + State set with this request is double-buffered. It will get applied on + the next zwp_text_input_v3.commit request, and stay valid until the + next committed enable or disable request. + + The changes must be applied by the compositor after issuing a + zwp_text_input_v3.commit request. + + + + + + Explicitly disable text input on the current surface (typically when + there is no focus on any text entry inside the surface). + + State set with this request is double-buffered. It will get applied on + the next zwp_text_input_v3.commit request. + + + + + + Sets the surrounding plain text around the input, excluding the preedit + text. + + The client should notify the compositor of any changes in any of the + values carried with this request, including changes caused by handling + incoming text-input events as well as changes caused by other + mechanisms like keyboard typing. + + If the client is unaware of the text around the cursor, it should not + issue this request, to signify lack of support to the compositor. + + Text is UTF-8 encoded, and should include the cursor position, the + complete selection and additional characters before and after them. + There is a maximum length of wayland messages, so text can not be + longer than 4000 bytes. + + Cursor is the byte offset of the cursor within text buffer. + + Anchor is the byte offset of the selection anchor within text buffer. + If there is no selected text, anchor is the same as cursor. + + If any preedit text is present, it is replaced with a cursor for the + purpose of this event. + + Values set with this request are double-buffered. They will get applied + on the next zwp_text_input_v3.commit request, and stay valid until the + next committed enable or disable request. + + The initial state for affected fields is empty, meaning that the text + input does not support sending surrounding text. If the empty values + get applied, subsequent attempts to change them may have no effect. + + + + + + + + + Reason for the change of surrounding text or cursor posision. + + + + + + + + Tells the compositor why the text surrounding the cursor changed. + + Whenever the client detects an external change in text, cursor, or + anchor posision, it must issue this request to the compositor. This + request is intended to give the input method a chance to update the + preedit text in an appropriate way, e.g. by removing it when the user + starts typing with a keyboard. + + cause describes the source of the change. + + The value set with this request is double-buffered. It must be applied + and reset to initial at the next zwp_text_input_v3.commit request. + + The initial value of cause is input_method. + + + + + + + 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. + + Values set with this request are double-buffered. They will get applied + on the next zwp_text_input_v3.commit request. + Subsequent attempts to update them may have no effect. The values + remain valid until the next committed enable or disable request. + + The initial value for hint is none, and the initial value for purpose + is normal. + + + + + + + + Marks an area around the cursor as a x, y, width, height rectangle in + surface local coordinates. + + Allows the compositor to put a window with word suggestions near the + cursor, without obstructing the text being input. + + If the client is unaware of the position of edited text, it should not + issue this request, to signify lack of support to the compositor. + + Values set with this request are double-buffered. They will get applied + on the next zwp_text_input_v3.commit request, and stay valid until the + next committed enable or disable request. + + The initial values describing a cursor rectangle are empty. That means + the text input does not support describing the cursor area. If the + empty values get applied, subsequent attempts to change them may have + no effect. + + + + + + + + + + Atomically applies state changes recently sent to the compositor. + + The commit request establishes and updates the state of the client, and + must be issued after any changes to apply them. + + Text input state (enabled status, content purpose, content hint, + surrounding text and change cause, cursor rectangle) is conceptually + double-buffered within the context of a text input, i.e. between a + committed enable request and the following committed enable or disable + request. + + Protocol requests modify the pending state, as opposed to the current + state in use by the input method. A commit request atomically applies + all pending state, replacing the current state. After commit, the new + pending state is as documented for each related request. + + Requests are applied in the order of arrival. + + Neither current nor pending state are modified unless noted otherwise. + + The compositor must count the number of commit requests coming from + each zwp_text_input_v3 object and use the count as the serial in done + events. + + + + + + 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. This event sets the current surface for the + text-input object. + + + + + + + Notification that this seat's text-input focus is no longer on a + certain surface. The client should reset any preedit string previously + set. + + The leave notification clears the current surface. It is sent before + the enter notification for the new focus. + + When the seat has the keyboard capability the text-input focus follows + the keyboard focus. + + + + + + + Notify when a new composing text (pre-edit) should be set at the + current cursor position. Any previously set composing text must be + removed. Any previously existing selected text must be removed. + + The argument text contains the pre-edit string buffer. + + The parameters cursor_begin and cursor_end are counted in bytes + relative to the beginning of the submitted text buffer. Cursor should + be hidden when both are equal to -1. + + They could be represented by the client as a line if both values are + the same, or as a text highlight otherwise. + + Values set with this event are double-buffered. They must be applied + and reset to initial on the next zwp_text_input_v3.done event. + + The initial value of text is an empty string, and cursor_begin, + cursor_end and cursor_hidden are all 0. + + + + + + + + + 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). + + Values set with this event are double-buffered. They must be applied + and reset to initial on the next zwp_text_input_v3.done event. + + The initial value of text is an empty string. + + + + + + + Notify when the text around the current cursor position should be + deleted. + + Before_length and after_length are the number of bytes before and after + the current cursor index (excluding the selection) to delete. + + If a preedit text is present, in effect before_length is counted from + the beginning of it, and after_length from its end (see done event + sequence). + + Values set with this event are double-buffered. They must be applied + and reset to initial on the next zwp_text_input_v3.done event. + + The initial values of both before_length and after_length are 0. + + + + + + + + Instruct the application to apply changes to state requested by the + preedit_string, commit_string and delete_surrounding_text events. The + state relating to these events is double-buffered, and each one + modifies the pending state. This event replaces the current state with + the pending state. + + The application must proceed by evaluating the changes in the following + order: + + 1. Replace existing preedit string with the cursor. + 2. Delete requested surrounding text. + 3. Insert commit string with the cursor at its end. + 4. Calculate surrounding text to send. + 5. Insert new preedit text in cursor position. + 6. Place cursor inside preedit text. + + The serial number reflects the last state of the zwp_text_input_v3 + object known to the compositor. The value of the serial argument must + be equal to the number of commit requests already issued on that object. + When the client receives a done event with a serial different than the + number of past commit requests, it must proceed as normal, except it + should not change the current state of the zwp_text_input_v3 object. + + + + + + + + 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/registry.h b/src/client/registry.h --- a/src/client/registry.h +++ b/src/client/registry.h @@ -36,6 +36,7 @@ struct wl_subcompositor; struct wl_text_input_manager; struct zwp_text_input_manager_v2; +struct zwp_text_input_manager_v3; struct _wl_fullscreen_shell; struct org_kde_kwin_appmenu_manager; struct org_kde_kwin_outputmanagement; @@ -101,6 +102,7 @@ class TextInputManager; class TextInputManagerUnstableV0; class TextInputManagerUnstableV2; +class TextInputManagerUnstableV3; class XdgShell; class RelativePointerManager; class XdgExporterUnstableV2; @@ -167,6 +169,7 @@ 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 + TextInputManagerUnstableV3, ///< Refers to zwp_text_input_manager_v3, @since 5.XX XdgShellUnstableV5, ///< Refers to xdg_shell (unstable version 5), @since 5.25 RelativePointerManagerUnstableV1, ///< Refers to zwp_relative_pointer_manager_v1, @since 5.28 PointerGesturesUnstableV1, ///< Refers to zwp_pointer_gestures_v1, @since 5.29 @@ -525,6 +528,16 @@ * @since 5.23 **/ zwp_text_input_manager_v2 *bindTextInputManagerUnstableV2(uint32_t name, uint32_t version) const; + /** + * Binds the zwp_text_input_manager_v3 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.XX + **/ + zwp_text_input_manager_v3 *bindTextInputManagerUnstableV3(uint32_t name, uint32_t version) const; /** * Binds the xdg_shell (unstable version 5) with @p name and @p version. * If the @p name does not exist or is not for the xdg shell interface in unstable version 5, @@ -1009,6 +1022,7 @@ * This factory method supports the following interfaces: * @li wl_text_input_manager * @li zwp_text_input_manager_v2 + * @li zwp_text_input_manager_v3 * * If @p name is for one of the supported interfaces the corresponding manager will be created, * otherwise @c null will be returned. @@ -1366,6 +1380,13 @@ * @since 5.23 **/ void textInputManagerUnstableV2Announced(quint32 name, quint32 version); + /** + * Emitted whenever a zwp_text_input_manager_v3 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 textInputManagerUnstableV3Announced(quint32 name, quint32 version); /** * Emitted whenever a xdg_shell (unstable version 5) interface gets announced. * @param name The name for the announced interface @@ -1602,6 +1623,12 @@ * @since 5.23 **/ void textInputManagerUnstableV2Removed(quint32 name); + /** + * Emitted whenever a zwp_text_input_manager_v3 interface gets removed. + * @param name The name for the removed interface + * @since 5.23 + **/ + void textInputManagerUnstableV3Removed(quint32 name); /** * Emitted whenever an xdg_shell (unstable version 5) interface gets removed. * @param name The name for the removed interface diff --git a/src/client/registry.cpp b/src/client/registry.cpp --- a/src/client/registry.cpp +++ b/src/client/registry.cpp @@ -78,6 +78,7 @@ #include #include #include +#include #include "../compat/wayland-xdg-shell-v5-client-protocol.h" #include #include @@ -284,6 +285,13 @@ &Registry::textInputManagerUnstableV2Announced, &Registry::textInputManagerUnstableV2Removed }}, + {Registry::Interface::TextInputManagerUnstableV3, { + 1, + QByteArrayLiteral("zwp_text_input_manager_v3"), + &zwp_text_input_manager_v3_interface, + &Registry::textInputManagerUnstableV3Announced, + &Registry::textInputManagerUnstableV3Removed + }}, {Registry::Interface::XdgShellUnstableV5, { 1, QByteArrayLiteral("xdg_shell"), @@ -665,6 +673,7 @@ BIND(ServerSideDecorationManager, org_kde_kwin_server_decoration_manager) BIND(TextInputManagerUnstableV0, wl_text_input_manager) BIND(TextInputManagerUnstableV2, zwp_text_input_manager_v2) +BIND(TextInputManagerUnstableV3, zwp_text_input_manager_v3) BIND(XdgShellUnstableV5, xdg_shell) BIND(XdgShellUnstableV6, zxdg_shell_v6) BIND(XdgShellStable, xdg_wm_base) @@ -758,6 +767,8 @@ return d->create(name, version, parent, &Registry::bindTextInputManagerUnstableV0); case Interface::TextInputManagerUnstableV2: return d->create(name, version, parent, &Registry::bindTextInputManagerUnstableV2); + case Interface::TextInputManagerUnstableV3: + return d->create(name, version, parent, &Registry::bindTextInputManagerUnstableV3); default: return nullptr; } diff --git a/src/client/textinput.h b/src/client/textinput.h --- a/src/client/textinput.h +++ b/src/client/textinput.h @@ -1,5 +1,6 @@ /**************************************************************************** Copyright 2016 Martin Gräßlin +Copyright 2018 Roman Gilg This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -27,6 +28,7 @@ struct wl_text_input; struct wl_text_input_manager; struct zwp_text_input_manager_v2; +struct zwp_text_input_manager_v3; namespace KWayland { @@ -48,6 +50,7 @@ * encapsulates one of the following interfaces: * @li wl_text_input * @li zwp_text_input_v2 + * @li zwp_text_input_v3 * * @since 5.23 **/ @@ -113,6 +116,27 @@ **/ void reset(); + /** + * ChangeCause allows to specify the reason for a change + * of the surrounding text or cursor position. + */ + enum class ChangeCause : uint32_t { + /** + * input method caused change + */ + InputMethod, + /** + * something else than input method caused change + */ + Other + }; + /** + * Sets the change cause of the currently pending surrounding text change. + * + * @param cause of the change + **/ + void setSurroundingTextChangeCause(ChangeCause cause); + /** * Sets the plain surrounding text around the input position. * @@ -225,6 +249,10 @@ * input a password */ Password, + /** + * input a pin + */ + Pin, /** * input a date */ @@ -264,6 +292,11 @@ * The @p language argument is a RFC-3066 format language tag. **/ void setPreferredLanguage(const QString &language); + /** + * Commits pending property changes. Only necessary for v3 of the + * unstable protocol. + **/ + void commit(); /** * The text direction of input text. @@ -441,11 +474,17 @@ **/ void setup(wl_text_input_manager *textinputmanagerunstablev0); /** - * Setup this TextInputManager to manage the @p textinputmanagerunstablev0. + * Setup this TextInputManager to manage the @p textinputmanagerunstablev2. * When using Registry::createTextInputManager there is no need to call this * method. **/ void setup(zwp_text_input_manager_v2 *textinputmanagerunstablev2); + /** + * Setup this TextInputManager to manage the @p textinputmanagerunstablev3. + * When using Registry::createTextInputManager there is no need to call this + * method. + **/ + void setup(zwp_text_input_manager_v3 *textinputmanagerunstablev3); /** * @returns @c true if managing a resource. **/ @@ -505,6 +544,14 @@ * @returns @c null if not for a zwp_text_input_manager_v2 **/ operator zwp_text_input_manager_v2*() const; + /** + * @returns @c null if not for a zwp_text_input_manager_v3 + **/ + operator zwp_text_input_manager_v3*(); + /** + * @returns @c null if not for a zwp_text_input_manager_v3 + **/ + operator zwp_text_input_manager_v3*() const; Q_SIGNALS: /** @@ -525,6 +572,7 @@ } } +Q_DECLARE_METATYPE(KWayland::Client::TextInput::ChangeCause) Q_DECLARE_METATYPE(KWayland::Client::TextInput::KeyState) Q_DECLARE_METATYPE(KWayland::Client::TextInput::ContentHint) Q_DECLARE_METATYPE(KWayland::Client::TextInput::ContentPurpose) diff --git a/src/client/textinput.cpp b/src/client/textinput.cpp --- a/src/client/textinput.cpp +++ b/src/client/textinput.cpp @@ -1,5 +1,6 @@ /**************************************************************************** Copyright 2016 Martin Gräßlin +Copyright 2018 Roman Gilg This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -33,6 +34,17 @@ pendingCommit.deleteSurrounding.beforeLength = 0; } +void TextInput::Private::setSurroundingTextChangeCause(TextInput::ChangeCause cause) +{ + Q_UNUSED(cause) + // implement by override in child classes +} + +void TextInput::Private::commit() +{ + // implement by override in child classes +} + TextInput::TextInput(Private *p, QObject *parent) : QObject(parent) , d(p) @@ -91,6 +103,11 @@ d->reset(); } +void TextInput::setSurroundingTextChangeCause(TextInput::ChangeCause cause) +{ + d->setSurroundingTextChangeCause(cause); +} + void TextInput::setSurroundingText(const QString &text, quint32 cursor, quint32 anchor) { d->setSurroundingText(text, cursor, anchor); @@ -111,6 +128,11 @@ d->setPreferredLanguage(language); } +void TextInput::commit() +{ + d->commit(); +} + Qt::LayoutDirection TextInput::textDirection() const { return d->textDirection; @@ -174,6 +196,11 @@ d->setupV2(textinputmanagerunstablev2); } +void TextInputManager::setup(zwp_text_input_manager_v3 *textinputmanagerunstablev3) +{ + d->setupV3(textinputmanagerunstablev3); +} + void TextInputManager::release() { d->release(); @@ -214,6 +241,16 @@ return *(d.data()); } +TextInputManager::operator zwp_text_input_manager_v3*() +{ + return *(d.data()); +} + +TextInputManager::operator zwp_text_input_manager_v3*() const +{ + return *(d.data()); +} + bool TextInputManager::isValid() const { return d->isValid(); diff --git a/src/client/textinput_p.h b/src/client/textinput_p.h --- a/src/client/textinput_p.h +++ b/src/client/textinput_p.h @@ -1,5 +1,6 @@ /**************************************************************************** Copyright 2016 Martin Gräßlin +Copyright 2018 Roman Gilg This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -26,6 +27,7 @@ struct wl_text_input; struct wl_text_input_manager; struct zwp_text_input_v2; +struct zwp_text_input_v3; namespace KWayland { @@ -73,6 +75,24 @@ QScopedPointer d; }; +class TextInputManagerUnstableV3 : 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 TextInputManagerUnstableV3(QObject *parent = nullptr); + virtual ~TextInputManagerUnstableV3(); + +private: + class Private; + QScopedPointer d; +}; + class Q_DECL_HIDDEN TextInputManager::Private { public: @@ -88,6 +108,9 @@ virtual void setupV2(zwp_text_input_manager_v2 *textinputmanagerunstablev2) { Q_UNUSED(textinputmanagerunstablev2) } + virtual void setupV3(zwp_text_input_manager_v3 *textinputmanagerunstablev3) { + Q_UNUSED(textinputmanagerunstablev3) + } virtual TextInput *createTextInput(Seat *seat, QObject *parent = nullptr) = 0; virtual operator wl_text_input_manager*() { return nullptr; @@ -101,6 +124,12 @@ virtual operator zwp_text_input_manager_v2*() const { return nullptr; } + virtual operator zwp_text_input_manager_v3*() { + return nullptr; + } + virtual operator zwp_text_input_manager_v3*() const { + return nullptr; + } EventQueue *queue = nullptr; }; @@ -118,9 +147,11 @@ virtual void hideInputPanel() = 0; virtual void setCursorRectangle(const QRect &rect) = 0; virtual void setPreferredLanguage(const QString &lang) = 0; + virtual void setSurroundingTextChangeCause(ChangeCause cause); virtual void setSurroundingText(const QString &text, quint32 cursor, quint32 anchor) = 0; virtual void reset() = 0; virtual void setContentType(ContentHints hint, ContentPurpose purpose) = 0; + virtual void commit(); EventQueue *queue = nullptr; Seat *seat; @@ -134,13 +165,16 @@ QByteArray text; QByteArray commitText; qint32 cursor = 0; + qint32 cursorEnd = 0; bool cursorSet = false; }; PreEdit currentPreEdit; PreEdit pendingPreEdit; struct Commit { QByteArray text; + bool textSet = false; + QByteArray surroundingText; qint32 cursor = 0; qint32 anchor = 0; DeleteSurroundingText deleteSurrounding; @@ -239,6 +273,51 @@ Private *d_func() const; }; +class TextInputUnstableV3 : public TextInput +{ + Q_OBJECT +public: + explicit TextInputUnstableV3(Seat *seat, QObject *parent = nullptr); + virtual ~TextInputUnstableV3(); + + /** + * Setup this TextInputUnstableV3 to manage the @p textinputunstablev3. + * When using TextInputManagerUnstableV3::createTextInputUnstableV3 there is no need to call this + * method. + **/ + void setup(zwp_text_input_v3 *textinputunstablev3); + /** + * Releases the zwp_text_input_v3 interface. + * After the interface has been released the TextInputUnstableV3 instance is no + * longer valid and can be setup with another zwp_text_input_v3 interface. + **/ + void release(); + /** + * Destroys the data held by this TextInputUnstableV3. + * 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_v3 interface + * once there is a new connection available. + * + * It is suggested to connect this method to ConnectionThread::connectionDied: + * @code + * connect(connection, &ConnectionThread::connectionDied, textinputunstablev3, &TextInputUnstableV3::destroy); + * @endcode + * + * @see release + **/ + void destroy(); + + operator zwp_text_input_v3*(); + operator zwp_text_input_v3*() const; + +private: + class Private; + Private *d_func() const; +}; + } } diff --git a/src/client/textinput_v0.cpp b/src/client/textinput_v0.cpp --- a/src/client/textinput_v0.cpp +++ b/src/client/textinput_v0.cpp @@ -364,6 +364,7 @@ wlPurpose = WL_TEXT_INPUT_CONTENT_PURPOSE_NAME; break; case ContentPurpose::Password: + case ContentPurpose::Pin: wlPurpose = WL_TEXT_INPUT_CONTENT_PURPOSE_PASSWORD; break; case ContentPurpose::Date: diff --git a/src/client/textinput_v2.cpp b/src/client/textinput_v2.cpp --- a/src/client/textinput_v2.cpp +++ b/src/client/textinput_v2.cpp @@ -388,6 +388,7 @@ wlPurpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_NAME; break; case ContentPurpose::Password: + case ContentPurpose::Pin: wlPurpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_PASSWORD; break; case ContentPurpose::Date: diff --git a/src/client/textinput_v3.cpp b/src/client/textinput_v3.cpp new file mode 100644 --- /dev/null +++ b/src/client/textinput_v3.cpp @@ -0,0 +1,434 @@ +/**************************************************************************** +Copyright 2018 Roman Gilg + +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 TextInputUnstableV3::Private : public TextInput::Private +{ +public: + Private(TextInputUnstableV3 *q, Seat *seat); + + void setup(zwp_text_input_v3 *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 setSurroundingTextChangeCause(TextInput::ChangeCause cause) override; + void setSurroundingText(const QString &text, quint32 cursor, quint32 anchor) override; + void reset() override; + void setContentType(ContentHints hint, ContentPurpose purpose) override; + void commit() override; + + WaylandPointer textinputunstablev3; + +private: + static void enterCallback(void *data, zwp_text_input_v3 *zwp_text_input_v3, wl_surface *surface); + static void leaveCallback(void *data, zwp_text_input_v3 *zwp_text_input_v3, wl_surface *surface); + static void preeditStringCallback(void *data, zwp_text_input_v3 *zwp_text_input_v3, const char *text, int32_t begin, int32_t end); + static void commitStringCallback(void *data, zwp_text_input_v3 *zwp_text_input_v3, const char *text); + static void deleteSurroundingTextCallback(void *data, zwp_text_input_v3 *zwp_text_input_v3, uint32_t before_length, uint32_t after_length); + static void doneCallback(void *data, zwp_text_input_v3 *zwp_text_input_v3, uint32_t serial); + void done (uint32_t serial); + + struct { + QByteArray text; + qint32 cursor = 0; + qint32 anchor = 0; + } pendingSurrounding; + + TextInputUnstableV3 *q; + + static const zwp_text_input_v3_listener s_listener; +}; + +const zwp_text_input_v3_listener TextInputUnstableV3::Private::s_listener = { + enterCallback, + leaveCallback, + preeditStringCallback, + commitStringCallback, + deleteSurroundingTextCallback, + doneCallback +}; + +void TextInputUnstableV3::Private::enterCallback(void *data, zwp_text_input_v3 *zwp_text_input_v3, wl_surface *surface) +{ + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev3 == zwp_text_input_v3); + t->enteredSurface = Surface::get(surface); + emit t->q->entered(); +} + +void TextInputUnstableV3::Private::leaveCallback(void *data, zwp_text_input_v3 *zwp_text_input_v3, wl_surface *surface) +{ + Q_UNUSED(surface) + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev3 == zwp_text_input_v3); + t->enteredSurface = nullptr; + emit t->q->left(); +} + +void TextInputUnstableV3::Private::preeditStringCallback(void *data, zwp_text_input_v3 *zwp_text_input_v3, const char *text, int32_t begin, int32_t end) +{ + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev3 == zwp_text_input_v3); + + t->pendingPreEdit.text = QByteArray(text); + t->pendingPreEdit.cursor = begin; + t->pendingPreEdit.cursorEnd = end; +} + +void TextInputUnstableV3::Private::commitStringCallback(void *data, zwp_text_input_v3 *zwp_text_input_v3, const char *text) +{ + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev3 == zwp_text_input_v3); + + t->pendingCommit.textSet = true; + t->pendingCommit.text = QByteArray(text); +} + +void TextInputUnstableV3::Private::deleteSurroundingTextCallback(void *data, zwp_text_input_v3 *zwp_text_input_v3, uint32_t before_length, uint32_t after_length) +{ + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev3 == zwp_text_input_v3); + + t->pendingCommit.deleteSurrounding.beforeLength = before_length; + t->pendingCommit.deleteSurrounding.afterLength = after_length; +} + +void TextInputUnstableV3::Private::doneCallback(void *data, zwp_text_input_v3 *zwp_text_input_v3, uint32_t serial) +{ + auto t = reinterpret_cast(data); + Q_ASSERT(t->textinputunstablev3 == zwp_text_input_v3); + t->done(serial); +} + +void TextInputUnstableV3::Private::done(uint32_t serial) +{ + // it is a compositor error if a greater serial is sent + Q_ASSERT(serial <= latestSerial); + if (serial != latestSerial) { + // event has been superseded by more recent commits + return; + } + + if (currentPreEdit.text != pendingPreEdit.text || + currentPreEdit.cursor != pendingPreEdit.cursor || + currentPreEdit.cursorEnd != pendingPreEdit.cursorEnd) { + currentPreEdit.text = pendingPreEdit.text; + currentPreEdit.cursor = pendingPreEdit.cursor; + currentPreEdit.cursorEnd = pendingPreEdit.cursorEnd; + emit q->composingTextChanged(); + } + currentCommit.deleteSurrounding = pendingCommit.deleteSurrounding; + + if (pendingCommit.textSet) { + currentCommit.text = pendingCommit.text; + pendingCommit.text = QByteArray(); + pendingCommit.textSet = false; + emit q->committed(); + } +} + +TextInputUnstableV3::Private::Private(TextInputUnstableV3 *q, Seat *seat) + : TextInput::Private(seat) + , q(q) +{ + pendingCommit.deleteSurrounding = {0,0}; + currentCommit.deleteSurrounding = {0,0}; +} + +void TextInputUnstableV3::Private::setup(zwp_text_input_v3 *ti) +{ + Q_ASSERT(ti); + Q_ASSERT(!textinputunstablev3); + textinputunstablev3.setup(ti); + zwp_text_input_v3_add_listener(ti, &s_listener, this); +} + +bool TextInputUnstableV3::Private::isValid() const +{ + return textinputunstablev3.isValid(); +} + +void TextInputUnstableV3::Private::enable(Surface *surface) +{ + Q_UNUSED(surface) + zwp_text_input_v3_enable(textinputunstablev3); +} + +void TextInputUnstableV3::Private::disable(Surface * surface) +{ + Q_UNUSED(surface) + zwp_text_input_v3_disable(textinputunstablev3); +} + +void TextInputUnstableV3::Private::showInputPanel() +{ + // not supported in v3 +} + +void TextInputUnstableV3::Private::hideInputPanel() +{ + // not supported in v3 +} + +void TextInputUnstableV3::Private::setCursorRectangle(const QRect &rect) +{ + zwp_text_input_v3_set_cursor_rectangle(textinputunstablev3, rect.x(), rect.y(), rect.width(), rect.height()); +} + +void TextInputUnstableV3::Private::setPreferredLanguage(const QString &lang) +{ + Q_UNUSED(lang) + // not supported in v3 +} + +void TextInputUnstableV3::Private::setSurroundingTextChangeCause(TextInput::ChangeCause cause) +{ + const zwp_text_input_v3_change_cause wlCause = (cause == TextInput::ChangeCause::Other) ? + ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_OTHER : ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_INPUT_METHOD; + zwp_text_input_v3_set_text_change_cause(textinputunstablev3, wlCause); +} + +void TextInputUnstableV3::Private::setSurroundingText(const QString &text, quint32 cursor, quint32 anchor) +{ + pendingSurrounding.text = QByteArray(text.toUtf8().constData()); + pendingSurrounding.cursor = cursor; + pendingSurrounding.anchor = anchor; + zwp_text_input_v3_set_surrounding_text(textinputunstablev3, text.toUtf8().constData(), + text.leftRef(cursor).toUtf8().length(), + text.leftRef(anchor).toUtf8().length()); +} + +void TextInputUnstableV3::Private::reset() +{ + // not supported in v3 +} + +void TextInputUnstableV3::Private::setContentType(ContentHints hints, ContentPurpose purpose) +{ + uint32_t wlHints = 0; + uint32_t wlPurpose = 0; + if (hints.testFlag(ContentHint::AutoCompletion)) { + wlHints |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_COMPLETION; + } + if (hints.testFlag(ContentHint::AutoCorrection)) { + wlHints |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_SPELLCHECK; + } + if (hints.testFlag(ContentHint::AutoCapitalization)) { + wlHints |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_AUTO_CAPITALIZATION; + } + if (hints.testFlag(ContentHint::LowerCase)) { + wlHints |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_LOWERCASE; + } + if (hints.testFlag(ContentHint::UpperCase)) { + wlHints |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_UPPERCASE; + } + if (hints.testFlag(ContentHint::TitleCase)) { + wlHints |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_TITLECASE; + } + if (hints.testFlag(ContentHint::HiddenText)) { + wlHints |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_HIDDEN_TEXT; + } + if (hints.testFlag(ContentHint::SensitiveData)) { + wlHints |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA; + } + if (hints.testFlag(ContentHint::Latin)) { + wlHints |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_LATIN; + } + if (hints.testFlag(ContentHint::MultiLine)) { + wlHints |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_MULTILINE; + } + switch (purpose) { + case ContentPurpose::Normal: + wlPurpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL; + break; + case ContentPurpose::Alpha: + wlPurpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ALPHA; + break; + case ContentPurpose::Digits: + wlPurpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DIGITS; + break; + case ContentPurpose::Number: + wlPurpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NUMBER; + break; + case ContentPurpose::Phone: + wlPurpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PHONE; + break; + case ContentPurpose::Url: + wlPurpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_URL; + break; + case ContentPurpose::Email: + wlPurpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_EMAIL; + break; + case ContentPurpose::Name: + wlPurpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NAME; + break; + case ContentPurpose::Password: + wlPurpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PASSWORD; + break; + case ContentPurpose::Pin: + wlPurpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PIN; + break; + case ContentPurpose::Date: + wlPurpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DATE; + break; + case ContentPurpose::Time: + wlPurpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TIME; + break; + case ContentPurpose::DateTime: + wlPurpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DATETIME; + break; + case ContentPurpose::Terminal: + wlPurpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TERMINAL; + break; + } + zwp_text_input_v3_set_content_type(textinputunstablev3, wlHints, wlPurpose); +} + +void TextInputUnstableV3::Private::commit() +{ + latestSerial++; + zwp_text_input_v3_commit(textinputunstablev3); +} + +TextInputUnstableV3::TextInputUnstableV3(Seat *seat, QObject *parent) + : TextInput(new Private(this, seat), parent) +{ +} + +TextInputUnstableV3::~TextInputUnstableV3() +{ + release(); +} + +TextInputUnstableV3::Private *TextInputUnstableV3::d_func() const +{ + return reinterpret_cast(d.data()); +} + +void TextInputUnstableV3::setup(zwp_text_input_v3 *textinputunstablev3) +{ + Q_D(); + d->setup(textinputunstablev3); +} + +void TextInputUnstableV3::release() +{ + Q_D(); + d->textinputunstablev3.release(); +} + +void TextInputUnstableV3::destroy() +{ + Q_D(); + d->textinputunstablev3.destroy(); +} + +TextInputUnstableV3::operator zwp_text_input_v3*() +{ + Q_D(); + return d->textinputunstablev3; +} + +TextInputUnstableV3::operator zwp_text_input_v3*() const +{ + Q_D(); + return d->textinputunstablev3; +} + +class TextInputManagerUnstableV3::Private : public TextInputManager::Private +{ +public: + Private() = default; + + void release() override; + void destroy() override; + bool isValid() override; + void setupV3(zwp_text_input_manager_v3 *ti) override; + TextInput *createTextInput(Seat *seat, QObject *parent = nullptr) override; + using TextInputManager::Private::operator wl_text_input_manager*; + operator zwp_text_input_manager_v3*() override { + return textinputmanagerunstablev3; + } + operator zwp_text_input_manager_v3*() const override { + return textinputmanagerunstablev3; + } + + WaylandPointer textinputmanagerunstablev3; +}; + +void TextInputManagerUnstableV3::Private::release() +{ + textinputmanagerunstablev3.release(); +} + +void TextInputManagerUnstableV3::Private::destroy() +{ + textinputmanagerunstablev3.destroy(); +} + +bool TextInputManagerUnstableV3::Private::isValid() +{ + return textinputmanagerunstablev3.isValid(); +} + +TextInputManagerUnstableV3::TextInputManagerUnstableV3(QObject *parent) + : TextInputManager(new Private, parent) +{ +} + +TextInputManagerUnstableV3::~TextInputManagerUnstableV3() = default; + +void TextInputManagerUnstableV3::Private::setupV3(zwp_text_input_manager_v3 *ti) +{ + Q_ASSERT(ti); + Q_ASSERT(!textinputmanagerunstablev3); + textinputmanagerunstablev3.setup(ti); +} + +TextInput *TextInputManagerUnstableV3::Private::createTextInput(Seat *seat, QObject *parent) +{ + Q_ASSERT(isValid()); + TextInputUnstableV3 *t = new TextInputUnstableV3(seat, parent); + auto w = zwp_text_input_manager_v3_get_text_input(textinputmanagerunstablev3, *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 @@ -50,6 +50,7 @@ textinput_interface.cpp textinput_interface_v0.cpp textinput_interface_v2.cpp + textinput_interface_v3.cpp xdgshell_interface.cpp xdgshell_v5_interface.cpp xdgforeign_v2_interface.cpp @@ -145,6 +146,11 @@ BASENAME text-input-unstable-v2 ) +ecm_add_wayland_server_protocol(SERVER_LIB_SRCS + PROTOCOL ${KWayland_SOURCE_DIR}/src/client/protocols/text-input-unstable-v3.xml + BASENAME text-input-unstable-v3 +) + ecm_add_wayland_server_protocol(SERVER_LIB_SRCS PROTOCOL ${KWayland_SOURCE_DIR}/src/client/protocols/xdg-shell-unstable-v6.xml BASENAME xdg-shell-v6 @@ -233,6 +239,8 @@ ${CMAKE_CURRENT_BINARY_DIR}/wayland-text-server-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-text-input-unstable-v2-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-text-input-unstable-v2-server-protocol.h + ${CMAKE_CURRENT_BINARY_DIR}/wayland-text-input-unstable-v3-client-protocol.h + ${CMAKE_CURRENT_BINARY_DIR}/wayland-text-input-unstable-v3-server-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-xdg-shell-v6-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-xdg-shell-v6-server-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-xdg-shell-client-protocol.h diff --git a/src/server/display.cpp b/src/server/display.cpp --- a/src/server/display.cpp +++ b/src/server/display.cpp @@ -371,6 +371,10 @@ return nullptr; case TextInputInterfaceVersion::UnstableV2: t = new TextInputManagerUnstableV2Interface(this, parent); + break; + case TextInputInterfaceVersion::UnstableV3: + t = new TextInputManagerUnstableV3Interface(this, parent); + break; } connect(this, &Display::aboutToTerminate, t, [t] { delete t; }); return t; 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 @@ -705,6 +705,7 @@ friend class DataDeviceManagerInterface; friend class TextInputManagerUnstableV0Interface; friend class TextInputManagerUnstableV2Interface; + friend class TextInputManagerUnstableV3Interface; explicit SeatInterface(Display *display, QObject *parent); class Private; diff --git a/src/server/textinput_interface.h b/src/server/textinput_interface.h --- a/src/server/textinput_interface.h +++ b/src/server/textinput_interface.h @@ -52,7 +52,11 @@ /** * zwp_text_input_v2 as used by Qt 5.7 **/ - UnstableV2 + UnstableV2, + /** + * zwp_text_input_v3, not yet used by Qt + **/ + UnstableV3 }; /** @@ -108,6 +112,21 @@ public: virtual ~TextInputInterface(); + /** + * ChangeCause allows to specify the reason for a change + * of the surrounding text or cursor position. + */ + enum class ChangeCause : uint32_t { + /** + * input method caused change + */ + InputMethod, + /** + * something else than input method caused change + */ + Other + }; + /** * ContentHint allows to modify the behavior of the text input. **/ @@ -202,6 +221,11 @@ * input a password */ Password, + /** + * input a numeric password + * @since 5.XX + */ + Pin, /** * input a date */ @@ -271,6 +295,14 @@ **/ qint32 surroundingTextSelectionAnchor() const; + /** + * The cause of the latest surrounding text change. + * + * @see surroundingText + * @see surroundingTextChanged + **/ + ChangeCause surroundingTextChangeCause() const; + /** * @return The surface the TextInputInterface is enabled on * @see isEnabled @@ -300,6 +332,18 @@ **/ void preEdit(const QByteArray &text, const QByteArray &commitText); + /** + * 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. + * + * @param text The new utf8-encoded pre-edit text + * @see commit + * @see preEditCursor + * @since 5.XX + **/ + void preEdit(const QByteArray &text); + /** * 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 @@ -324,6 +368,22 @@ **/ void setPreEditCursor(qint32 index); + /** + * Sets the cursor position inside the composing text (as byte offset) relative to the + * start of the composing text. When @p begin and end are negative numbers no cursor is + * shown. + * + * If begin and end define a range, the client may underline or otherwise mark the text + * in that range. + * + * The Client applies @p begin and @p end together with {@link preEdit}. + * @param begin The cursor start relative to the start of the composing text + * @param end The cursor end relative to the start of the composing text + * @see preEdit + * @since 5.XX + **/ + void setPreEditCursor(qint32 begin, qint32 end); + /** * Notify when the text around the current cursor position should be deleted. * @@ -365,6 +425,11 @@ * Sets the language of the input text. The @p languageTag is a RFC-3066 format language tag. **/ void setLanguage(const QByteArray &languageTag); + /** + * Instructs the client to apply previous changes + * @since 5.XX + **/ + void done(); Q_SIGNALS: /** @@ -412,14 +477,36 @@ * @see surface **/ void enabledChanged(); + /** + * Emitted whenever the client committed the pending state. + * + * This is an atomic alternative to listening for the indvidual changed + * signals. + * + * All affected properties have already been set and can be queried via the + * respective getters in this interface. + * + * Note that this signal differs from listening for individual changed signals + * only starting with v3 of the text-input protocol. + * + * @see isEnabled + * @see contentPurpose + * @see contentHints + * @see cursorRectangle + * @see surroundingText + * @see surroundingTextChangeCause + * @since 5.XX + **/ + void committed(); protected: class Private; explicit TextInputInterface(Private *p, QObject *parent = nullptr); private: friend class TextInputManagerUnstableV0Interface; friend class TextInputManagerUnstableV2Interface; + friend class TextInputManagerUnstableV3Interface; friend class SeatInterface; Private *d_func() const; @@ -431,6 +518,7 @@ Q_DECLARE_METATYPE(KWayland::Server::TextInputInterfaceVersion) Q_DECLARE_METATYPE(KWayland::Server::TextInputInterface *) +Q_DECLARE_METATYPE(KWayland::Server::TextInputInterface::ChangeCause) Q_DECLARE_METATYPE(KWayland::Server::TextInputInterface::ContentHint) Q_DECLARE_METATYPE(KWayland::Server::TextInputInterface::ContentHints) Q_DECLARE_OPERATORS_FOR_FLAGS(KWayland::Server::TextInputInterface::ContentHints) diff --git a/src/server/textinput_interface.cpp b/src/server/textinput_interface.cpp --- a/src/server/textinput_interface.cpp +++ b/src/server/textinput_interface.cpp @@ -1,5 +1,6 @@ /**************************************************************************** Copyright 2016 Martin Gräßlin +Copyright 2018 Roman Gilg This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -28,6 +29,7 @@ #include #include +#include namespace KWayland { @@ -48,39 +50,27 @@ emit p->q_func()->requestHideInputPanel(); } -void TextInputInterface::Private::setSurroundingTextCallback(wl_client *client, wl_resource *resource, const char * text, int32_t cursor, int32_t anchor) +void TextInputInterface::Private::setSurroundingTextCallback(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(); + p->setSurroundingText(text, cursor, anchor); } void TextInputInterface::Private::setContentTypeCallback(wl_client *client, wl_resource *resource, uint32_t hint, uint32_t purpose) { auto p = cast(resource); Q_ASSERT(*p->client == client); const auto contentHints = p->convertContentHint(hint); const auto contentPurpose = p->convertContentPurpose(purpose); - if (contentHints != p->contentHints || contentPurpose != p->contentPurpose) { - p->contentHints = contentHints; - p->contentPurpose = contentPurpose; - emit p->q_func()->contentTypeChanged(); - } + p->setContentType(contentHints, contentPurpose); } 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); - } + p->setCursorRectangle(QRect(x, y, width, height)); } void TextInputInterface::Private::setPreferredLanguageCallback(wl_client *client, wl_resource *resource, const char * language) @@ -107,6 +97,39 @@ } } +void TextInputInterface::Private::setSurroundingText(const char *text, int32_t cursor, int32_t anchor) +{ + surroundingText = QByteArray(text); + // TODO: make qint32 + surroundingTextCursorPosition = cursor; + surroundingTextSelectionAnchor = anchor; + emit q_func()->surroundingTextChanged(); +} + +void TextInputInterface::Private::setContentType(TextInputInterface::ContentHints hints, TextInputInterface::ContentPurpose purpose) +{ + if (hints == contentHints && purpose == contentPurpose) { + return; + } + contentHints = hints; + contentPurpose = purpose; + emit q_func()->contentTypeChanged(); +} + +void TextInputInterface::Private::setCursorRectangle(const QRect &rect) +{ + if (cursorRectangle == rect) { + return; + } + cursorRectangle = rect; + emit q_func()->cursorRectangleChanged(cursorRectangle); +} + +void TextInputInterface::Private::done() +{ + // child classes with implementation need to override +} + QByteArray TextInputInterface::preferredLanguage() const { Q_D(); @@ -143,12 +166,23 @@ return d->surroundingTextSelectionAnchor; } +TextInputInterface::ChangeCause TextInputInterface::surroundingTextChangeCause() const +{ + Q_D(); + return d->surroundingTextChangeCause; +} + void TextInputInterface::preEdit(const QByteArray &text, const QByteArray &commit) { Q_D(); d->preEdit(text, commit); } +void TextInputInterface::preEdit(const QByteArray &text) +{ + preEdit(text, QByteArray()); +} + void TextInputInterface::commit(const QByteArray &text) { Q_D(); @@ -187,9 +221,14 @@ } void TextInputInterface::setPreEditCursor(qint32 index) +{ + setPreEditCursor(index, index); +} + +void TextInputInterface::setPreEditCursor(qint32 begin, qint32 end) { Q_D(); - d->setPreEditCursor(index); + d->setPreEditCursor(begin, end); } void TextInputInterface::setInputPanelState(bool visible, const QRect &overlappedSurfaceArea) @@ -215,6 +254,12 @@ d->sendLanguage(); } +void TextInputInterface::done() +{ + Q_D(); + d->done(); +} + TextInputInterfaceVersion TextInputInterface::interfaceVersion() const { Q_D(); diff --git a/src/server/textinput_interface_p.h b/src/server/textinput_interface_p.h --- a/src/server/textinput_interface_p.h +++ b/src/server/textinput_interface_p.h @@ -34,6 +34,7 @@ class TextInputManagerUnstableV0Interface; class TextInputManagerUnstableV2Interface; +class TextInputManagerUnstableV3Interface; class TextInputManagerInterface::Private : public Global::Private { @@ -58,13 +59,14 @@ 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 setPreEditCursor(qint32 begin, qint32 end) = 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; + virtual void done(); virtual TextInputInterface::ContentHints convertContentHint(uint32_t hint) const = 0; virtual TextInputInterface::ContentPurpose convertContentPurpose(uint32_t purpose) const = 0; @@ -82,6 +84,7 @@ bool inputPanelVisible = false; QRect overlappedSurfaceArea; QByteArray language; + TextInputInterface::ChangeCause surroundingTextChangeCause = TextInputInterface::ChangeCause::InputMethod; protected: Private(TextInputInterface *q, Global *c, wl_resource *parentResource, const wl_interface *interface, const void *implementation); @@ -93,6 +96,10 @@ 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); + virtual void setSurroundingText(const char *text, int32_t cursor, int32_t anchor); + virtual void setContentType(TextInputInterface::ContentHints hints, TextInputInterface::ContentPurpose purpose); + virtual void setCursorRectangle(const QRect &rect); + private: TextInputInterface *q_func() { return reinterpret_cast(q); @@ -129,6 +136,18 @@ class Private; }; +class TextInputUnstableV3Interface : public TextInputInterface +{ + Q_OBJECT +public: + virtual ~TextInputUnstableV3Interface(); + +private: + explicit TextInputUnstableV3Interface(TextInputManagerUnstableV3Interface *parent, wl_resource *parentResource); + friend class TextInputManagerUnstableV3Interface; + class Private; +}; + class TextInputManagerUnstableV0Interface : public TextInputManagerInterface { Q_OBJECT @@ -151,6 +170,17 @@ class Private; }; +class TextInputManagerUnstableV3Interface : public TextInputManagerInterface +{ + Q_OBJECT +public: + explicit TextInputManagerUnstableV3Interface(Display *display, QObject *parent = nullptr); + virtual ~TextInputManagerUnstableV3Interface(); + +private: + class Private; +}; + } } diff --git a/src/server/textinput_interface_v0.cpp b/src/server/textinput_interface_v0.cpp --- a/src/server/textinput_interface_v0.cpp +++ b/src/server/textinput_interface_v0.cpp @@ -45,7 +45,7 @@ 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 setPreEditCursor(qint32 begin, qint32 end) override; void setCursorPosition(qint32 index, qint32 anchor) override; void keysymPressed(quint32 keysym, Qt::KeyboardModifiers modifiers) override; void keysymReleased(quint32 keysym, Qt::KeyboardModifiers modifiers) override; @@ -198,12 +198,13 @@ wl_text_input_send_text_direction(resource, latestState, wlDirection); } -void TextInputUnstableV0Interface::Private::setPreEditCursor(qint32 index) +void TextInputUnstableV0Interface::Private::setPreEditCursor(qint32 begin, qint32 end) { + Q_UNUSED(end) if (!resource) { return; } - wl_text_input_send_preedit_cursor(resource, index); + wl_text_input_send_preedit_cursor(resource, begin); } void TextInputUnstableV0Interface::Private::sendInputPanelState() diff --git a/src/server/textinput_interface_v2.cpp b/src/server/textinput_interface_v2.cpp --- a/src/server/textinput_interface_v2.cpp +++ b/src/server/textinput_interface_v2.cpp @@ -42,7 +42,7 @@ 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 setPreEditCursor(qint32 begin, qint32 end) override; void setCursorPosition(qint32 index, qint32 anchor) override; void keysymPressed(quint32 keysym, Qt::KeyboardModifiers modifiers) override; void keysymReleased(quint32 keysym, Qt::KeyboardModifiers modifiers) override; @@ -188,12 +188,13 @@ zwp_text_input_v2_send_text_direction(resource, wlDirection); } -void TextInputUnstableV2Interface::Private::setPreEditCursor(qint32 index) +void TextInputUnstableV2Interface::Private::setPreEditCursor(qint32 begin, qint32 end) { + Q_UNUSED(end) if (!resource) { return; } - zwp_text_input_v2_send_preedit_cursor(resource, index); + zwp_text_input_v2_send_preedit_cursor(resource, begin); } void TextInputUnstableV2Interface::Private::sendInputPanelState() diff --git a/src/server/textinput_interface_v3.cpp b/src/server/textinput_interface_v3.cpp new file mode 100644 --- /dev/null +++ b/src/server/textinput_interface_v3.cpp @@ -0,0 +1,546 @@ +/**************************************************************************** +Copyright 2018 Roman Gilg + +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 TextInputUnstableV3Interface::Private : public TextInputInterface::Private +{ +public: + Private(TextInputInterface *q, TextInputManagerUnstableV3Interface *c, wl_resource *parentResource); + ~Private(); + + // events + void sendEnter(SurfaceInterface *s, quint32 serial) override; + void sendLeave(quint32 serial, SurfaceInterface *s) 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 begin, qint32 end) 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::UnstableV3; + } + void sendInputPanelState() override; + void sendLanguage() override; + void done() override; + +private: + static const struct zwp_text_input_v3_interface s_interface; + + TextInputUnstableV3Interface *q_func() { + return reinterpret_cast(q); + } + + // requests + static void enableCallback(wl_client *client, wl_resource *resource); + static void disableCallback(wl_client *client, wl_resource *resource); + static void setTextChangeCauseCallback(wl_client *client, wl_resource *resource, uint32_t wlCause); + static void commitCallback(wl_client *client, wl_resource *resource); + + void enable(); + void disable(); + void setSurroundingText(const char * text, int32_t cursor, int32_t anchor) override; + void setContentType(TextInputInterface::ContentHints hints, TextInputInterface::ContentPurpose purpose) override; + void setCursorRectangle(const QRect &rect) override; + void commitState(); + + // helpers + TextInputInterface::ContentHints convertContentHint(uint32_t hint) const override; + TextInputInterface::ContentPurpose convertContentPurpose(uint32_t purpose) const override; + void sendPreeditString(); + // set pending values to default ones + void defaultPending(); + + struct { + QRect cursorRectangle; + TextInputInterface::ChangeCause surroundingTextChangeCause = TextInputInterface::ChangeCause::InputMethod; + TextInputInterface::ContentHints contentHints = TextInputInterface::ContentHint::None; + TextInputInterface::ContentPurpose contentPurpose = TextInputInterface::ContentPurpose::Normal; + bool enabled = false; + QByteArray surroundingText; + qint32 surroundingTextCursorPosition = 0; + qint32 surroundingTextSelectionAnchor = 0; + } pending; + + quint32 serial = 0; + + struct { + QByteArray string; + struct { + qint32 begin = 0; + qint32 end = 0; + } cursor; + } preedit; +}; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +const struct zwp_text_input_v3_interface TextInputUnstableV3Interface::Private::s_interface = { + resourceDestroyedCallback, + enableCallback, + disableCallback, + setSurroundingTextCallback, + setTextChangeCauseCallback, + setContentTypeCallback, + setCursorRectangleCallback, + commitCallback +}; +#endif + +void TextInputUnstableV3Interface::Private::enable() +{ + // All properties will be reset to default state + // and following requests amend the state. + defaultPending(); + pending.enabled = true; +} + +void TextInputUnstableV3Interface::Private::disable() +{ + // All properties will be reset to default state. + defaultPending(); +} + + +void TextInputUnstableV3Interface::Private::setSurroundingText(const char *text, int32_t cursor, int32_t anchor) +{ + if (!pending.enabled) { + /* + * Pending data can only be amended when the interface + * is (about to be) enabled. + */ + return; + } + pending.surroundingText = QByteArray(text); + pending.surroundingTextCursorPosition = cursor; + pending.surroundingTextSelectionAnchor = anchor; +} + +void TextInputUnstableV3Interface::Private::setContentType(TextInputInterface::ContentHints hints, TextInputInterface::ContentPurpose purpose) +{ + if (!pending.enabled) { + return; + } + pending.contentHints = hints; + pending.contentPurpose = purpose; +} + +void TextInputUnstableV3Interface::Private::setCursorRectangle(const QRect &rect) +{ + if (!pending.enabled) { + return; + } + pending.cursorRectangle = rect; +} + +void TextInputUnstableV3Interface::Private::commitState() +{ + serial++; + + if (enabled != pending.enabled) { + enabled = pending.enabled; + emit q_func()->enabledChanged(); + } + + if (surroundingTextChangeCause != pending.surroundingTextChangeCause) { + surroundingTextChangeCause = pending.surroundingTextChangeCause; + // reset to initial value for future commits + pending.surroundingTextChangeCause = TextInputInterface::ChangeCause::InputMethod; + } + if (contentHints != pending.contentHints || + contentPurpose != pending.contentPurpose) { + contentHints = pending.contentHints; + contentPurpose = pending.contentPurpose; + if (enabled) { + emit q_func()->contentTypeChanged(); + } + } + if (cursorRectangle != pending.cursorRectangle) { + cursorRectangle = pending.cursorRectangle; + if (enabled) { + emit q_func()->cursorRectangleChanged(cursorRectangle); + } + } + if (surroundingText != pending.surroundingText || + surroundingTextCursorPosition != pending.surroundingTextCursorPosition || + surroundingTextSelectionAnchor != pending.surroundingTextSelectionAnchor) { + surroundingText = pending.surroundingText; + surroundingTextCursorPosition = pending.surroundingTextCursorPosition; + surroundingTextSelectionAnchor = pending.surroundingTextSelectionAnchor; + if (enabled) { + emit q_func()->surroundingTextChanged(); + } + } + emit q_func()->committed(); +} + +TextInputInterface::ContentHints TextInputUnstableV3Interface::Private::convertContentHint(uint32_t hint) const +{ + const auto hints = zwp_text_input_v3_content_hint(hint); + TextInputInterface::ContentHints ret = TextInputInterface::ContentHint::None; + + if (hints & ZWP_TEXT_INPUT_V3_CONTENT_HINT_COMPLETION) { + ret |= TextInputInterface::ContentHint::AutoCompletion; + } + if (hints & ZWP_TEXT_INPUT_V3_CONTENT_HINT_SPELLCHECK) { + ret |= TextInputInterface::ContentHint::AutoCorrection; + } + if (hints & ZWP_TEXT_INPUT_V3_CONTENT_HINT_AUTO_CAPITALIZATION) { + ret |= TextInputInterface::ContentHint::AutoCapitalization; + } + if (hints & ZWP_TEXT_INPUT_V3_CONTENT_HINT_LOWERCASE) { + ret |= TextInputInterface::ContentHint::LowerCase; + } + if (hints & ZWP_TEXT_INPUT_V3_CONTENT_HINT_UPPERCASE) { + ret |= TextInputInterface::ContentHint::UpperCase; + } + if (hints & ZWP_TEXT_INPUT_V3_CONTENT_HINT_TITLECASE) { + ret |= TextInputInterface::ContentHint::TitleCase; + } + if (hints & ZWP_TEXT_INPUT_V3_CONTENT_HINT_HIDDEN_TEXT) { + ret |= TextInputInterface::ContentHint::HiddenText; + } + if (hints & ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA) { + ret |= TextInputInterface::ContentHint::SensitiveData; + } + if (hints & ZWP_TEXT_INPUT_V3_CONTENT_HINT_LATIN) { + ret |= TextInputInterface::ContentHint::Latin; + } + if (hints & ZWP_TEXT_INPUT_V3_CONTENT_HINT_MULTILINE) { + ret |= TextInputInterface::ContentHint::MultiLine; + } + return ret; +} + +TextInputInterface::ContentPurpose TextInputUnstableV3Interface::Private::convertContentPurpose(uint32_t purpose) const +{ + const auto wlPurpose = zwp_text_input_v3_content_purpose(purpose); + + switch (wlPurpose) { + case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ALPHA: + return TextInputInterface::ContentPurpose::Alpha; + case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DIGITS: + return TextInputInterface::ContentPurpose::Digits; + case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NUMBER: + return TextInputInterface::ContentPurpose::Number; + case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PHONE: + return TextInputInterface::ContentPurpose::Phone; + case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_URL: + return TextInputInterface::ContentPurpose::Url; + case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_EMAIL: + return TextInputInterface::ContentPurpose::Email; + case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NAME: + return TextInputInterface::ContentPurpose::Name; + case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PASSWORD: + return TextInputInterface::ContentPurpose::Password; + case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PIN: + return TextInputInterface::ContentPurpose::Pin; + case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DATE: + return TextInputInterface::ContentPurpose::Date; + case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TIME: + return TextInputInterface::ContentPurpose::Time; + case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DATETIME: + return TextInputInterface::ContentPurpose::DateTime; + case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TERMINAL: + return TextInputInterface::ContentPurpose::Terminal; + case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL: + default: + return TextInputInterface::ContentPurpose::Normal; + } +} + +void TextInputUnstableV3Interface::Private::sendPreeditString() +{ + if (!resource) { + return; + } + zwp_text_input_v3_send_preedit_string(resource, + preedit.string.constData(), + preedit.cursor.begin, + preedit.cursor.end); +} + +void TextInputUnstableV3Interface::Private::defaultPending() +{ + pending.cursorRectangle = QRect(); + pending.surroundingTextChangeCause = TextInputInterface::ChangeCause::InputMethod; + pending.contentHints = TextInputInterface::ContentHint::None; + pending.contentPurpose = TextInputInterface::ContentPurpose::Normal; + pending.enabled = false; + pending.surroundingText = QByteArray(); + pending.surroundingTextCursorPosition = 0; + pending.surroundingTextSelectionAnchor = 0; +} + + +void TextInputUnstableV3Interface::Private::sendEnter(SurfaceInterface *s, quint32 serial) +{ + Q_UNUSED(serial); + if (!resource || !s || !s->resource()) { + return; + } + surface = s; + zwp_text_input_v3_send_enter(resource, s->resource()); +} + +void TextInputUnstableV3Interface::Private::sendLeave(quint32 serial, SurfaceInterface *s) +{ + Q_UNUSED(serial); + if (!resource || !s || !s->resource()) { + return; + } + surface.clear(); + zwp_text_input_v3_send_leave(resource, s->resource()); +} + +void TextInputUnstableV3Interface::Private::preEdit(const QByteArray &text, const QByteArray &commit) +{ + Q_UNUSED(commit) + preedit.string = text; + sendPreeditString(); +} + +void TextInputUnstableV3Interface::Private::commit(const QByteArray &text) +{ + if (!resource) { + return; + } + zwp_text_input_v3_send_commit_string(resource, text.constData()); +} + +void TextInputUnstableV3Interface::Private::keysymPressed(quint32 keysym, Qt::KeyboardModifiers modifiers) +{ + Q_UNUSED(keysym) + Q_UNUSED(modifiers) + // not available in v3 +} + +void TextInputUnstableV3Interface::Private::keysymReleased(quint32 keysym, Qt::KeyboardModifiers modifiers) +{ + Q_UNUSED(keysym) + Q_UNUSED(modifiers) + // not available in v3 +} + +void TextInputUnstableV3Interface::Private::deleteSurroundingText(quint32 beforeLength, quint32 afterLength) +{ + if (!resource) { + return; + } + zwp_text_input_v3_send_delete_surrounding_text(resource, beforeLength, afterLength); +} + +void TextInputUnstableV3Interface::Private::setCursorPosition(qint32 index, qint32 anchor) +{ + Q_UNUSED(index) + Q_UNUSED(anchor) + // not available in v3 +} + +void TextInputUnstableV3Interface::Private::setTextDirection(Qt::LayoutDirection direction) +{ + Q_UNUSED(direction) + // not available in v3 +} + +void TextInputUnstableV3Interface::Private::setPreEditCursor(qint32 begin, qint32 end) +{ + preedit.cursor.begin = begin; + preedit.cursor.end = end; + sendPreeditString(); +} + +void TextInputUnstableV3Interface::Private::sendInputPanelState() +{ + // not available in v3 +} + +void TextInputUnstableV3Interface::Private::sendLanguage() +{ + // not available in v3 +} + +void TextInputUnstableV3Interface::Private::done() +{ + if (!resource) { + return; + } + zwp_text_input_v3_send_done(resource, serial); +} + +TextInputUnstableV3Interface::Private::Private(TextInputInterface *q, TextInputManagerUnstableV3Interface *c, wl_resource *parentResource) + : TextInputInterface::Private(q, c, parentResource, &zwp_text_input_v3_interface, &s_interface) +{ +} + +TextInputUnstableV3Interface::Private::~Private() = default; + +TextInputUnstableV3Interface::TextInputUnstableV3Interface(TextInputManagerUnstableV3Interface *parent, wl_resource *parentResource) + : TextInputInterface(new Private(this, parent, parentResource)) +{ +} + +TextInputUnstableV3Interface::~TextInputUnstableV3Interface() = default; + +void TextInputUnstableV3Interface::Private::enableCallback(wl_client *client, wl_resource *resource) +{ + auto p = cast(resource); + Q_ASSERT(*p->client == client); + p->enable(); +} + +void TextInputUnstableV3Interface::Private::disableCallback(wl_client *client, wl_resource *resource) +{ + auto p = cast(resource); + Q_ASSERT(*p->client == client); + p->disable(); +} + +namespace { +static TextInputInterface::ChangeCause waylandCauseToKWayland(zwp_text_input_v3_change_cause wlCause) +{ + switch (wlCause) { + case ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_INPUT_METHOD: + return TextInputInterface::ChangeCause::InputMethod; + case ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_OTHER: + default: + return TextInputInterface::ChangeCause::Other; + } +} + +} + +void TextInputUnstableV3Interface::Private::setTextChangeCauseCallback(wl_client *client, wl_resource *resource, uint32_t wlCause) +{ + auto p = cast(resource); + Q_ASSERT(*p->client == client); + if (!p->pending.enabled) { + return; + } + p->pending.surroundingTextChangeCause = waylandCauseToKWayland(zwp_text_input_v3_change_cause(wlCause)); +} + +void TextInputUnstableV3Interface::Private::commitCallback(wl_client *client, wl_resource *resource) +{ + auto p = cast(resource); + Q_ASSERT(*p->client == client); + p->commitState(); +} + +class TextInputManagerUnstableV3Interface::Private : public TextInputManagerInterface::Private +{ +public: + Private(TextInputManagerUnstableV3Interface *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); + + TextInputManagerUnstableV3Interface *q; + static const struct zwp_text_input_manager_v3_interface s_interface; + static const quint32 s_version; +}; +const quint32 TextInputManagerUnstableV3Interface::Private::s_version = 1; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +const struct zwp_text_input_manager_v3_interface TextInputManagerUnstableV3Interface::Private::s_interface = { + destroyCallback, + getTextInputCallback +}; +#endif + +void TextInputManagerUnstableV3Interface::Private::destroyCallback(wl_client *client, wl_resource *resource) +{ + Q_UNUSED(client) + wl_resource_destroy(resource); +} + +void TextInputManagerUnstableV3Interface::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 TextInputUnstableV3Interface(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); +} + +TextInputManagerUnstableV3Interface::Private::Private(TextInputManagerUnstableV3Interface *q, Display *d) + : TextInputManagerInterface::Private(TextInputInterfaceVersion::UnstableV3, q, d, &zwp_text_input_manager_v3_interface, s_version) + , q(q) +{ +} + +void TextInputManagerUnstableV3Interface::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_v3_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 TextInputManagerUnstableV3Interface::Private::unbind(wl_resource *resource) +{ + Q_UNUSED(resource) + // TODO: implement? +} + +TextInputManagerUnstableV3Interface::TextInputManagerUnstableV3Interface(Display *display, QObject *parent) + : TextInputManagerInterface(new Private(this, display), parent) +{ +} + +TextInputManagerUnstableV3Interface::~TextInputManagerUnstableV3Interface() = default; + +} +}