diff --git a/autotests/integration/CMakeLists.txt b/autotests/integration/CMakeLists.txt index 146ff21ab2..3dc8880325 100644 --- a/autotests/integration/CMakeLists.txt +++ b/autotests/integration/CMakeLists.txt @@ -13,6 +13,10 @@ ecm_add_qtwayland_client_protocol(KWinIntegrationTestFramework_SOURCES PROTOCOL ${WaylandProtocols_DATADIR}/unstable/input-method/input-method-unstable-v1.xml BASENAME input-method-unstable-v1 ) +ecm_add_qtwayland_client_protocol(KWinIntegrationTestFramework_SOURCES + PROTOCOL ${WaylandProtocols_DATADIR}/unstable/text-input/text-input-unstable-v3.xml + BASENAME text-input-unstable-v3 +) ecm_add_qtwayland_client_protocol(KWinIntegrationTestFramework_SOURCES PROTOCOL protocols/wlr-layer-shell-unstable-v1.xml BASENAME wlr-layer-shell-unstable-v1 diff --git a/autotests/integration/kwin_wayland_test.h b/autotests/integration/kwin_wayland_test.h index 31e212eee0..cd0fc00b67 100644 --- a/autotests/integration/kwin_wayland_test.h +++ b/autotests/integration/kwin_wayland_test.h @@ -18,6 +18,7 @@ #include #include "qwayland-wlr-layer-shell-unstable-v1.h" +#include "qwayland-text-input-unstable-v3.h" #include "qwayland-xdg-shell.h" namespace KWayland @@ -47,6 +48,8 @@ class TextInputManager; namespace QtWayland { class zwp_input_panel_surface_v1; +class zwp_text_input_v3; +class zwp_text_input_manager_v3; } namespace KWin @@ -86,6 +89,17 @@ namespace Test class MockInputMethod; +class TextInputManagerV3 : public QtWayland::zwp_text_input_manager_v3 +{ +public: + ~TextInputManagerV3() override { destroy(); } +}; + +class TextInputV3 : public QtWayland::zwp_text_input_v3 +{ + ~TextInputV3() override { destroy(); } +}; + class LayerShellV1 : public QtWayland::zwlr_layer_shell_v1 { public: @@ -222,6 +236,7 @@ enum class AdditionalWaylandInterface { TextInputManagerV2 = 1 << 10, InputMethodV1 = 1 << 11, LayerShellV1 = 1 << 12, + TextInputManagerV3 = 1 << 13 }; Q_DECLARE_FLAGS(AdditionalWaylandInterfaces, AdditionalWaylandInterface) /** @@ -272,6 +287,8 @@ LayerSurfaceV1 *createLayerSurfaceV1(KWayland::Client::Surface *surface, KWayland::Client::Output *output = nullptr, LayerShellV1::layer layer = LayerShellV1::layer_top); +TextInputManagerV3 *waylandTextInputManagerV3(); + enum class CreationSetup { CreateOnly, CreateAndConfigure, /// commit and wait for the configure event, making this surface ready to commit buffers diff --git a/autotests/integration/test_helpers.cpp b/autotests/integration/test_helpers.cpp index e8acdbb123..63e43c702d 100644 --- a/autotests/integration/test_helpers.cpp +++ b/autotests/integration/test_helpers.cpp @@ -208,6 +208,7 @@ static struct { MockInputMethod *inputMethodV1 = nullptr; QtWayland::zwp_input_method_context_v1 *inputMethodContextV1 = nullptr; LayerShellV1 *layerShellV1 = nullptr; + TextInputManagerV3 *textInputManagerV3 = nullptr; } s_waylandConnection; class MockInputMethod : public QtWayland::zwp_input_method_v1 @@ -323,6 +324,13 @@ bool setupWaylandConnection(AdditionalWaylandInterfaces flags) s_waylandConnection.layerShellV1->init(*registry, name, version); } } + if (flags & AdditionalWaylandInterface::TextInputManagerV3) { + // do something + if (interface == QByteArrayLiteral("zwp_text_input_manager_v3")) { + s_waylandConnection.textInputManagerV3 = new TextInputManagerV3(); + s_waylandConnection.textInputManagerV3->init(*registry, name, version); + } + } if (interface == QByteArrayLiteral("xdg_wm_base")) { s_waylandConnection.xdgShell = new XdgShell(); s_waylandConnection.xdgShell->init(*registry, name, version); @@ -566,6 +574,11 @@ TextInputManager *waylandTextInputManager() return s_waylandConnection.textInputManager; } +TextInputManagerV3 *waylandTextInputManagerV3() +{ + return s_waylandConnection.textInputManagerV3; +} + QVector waylandOutputs() { return s_waylandConnection.outputs; diff --git a/autotests/integration/virtualkeyboard_test.cpp b/autotests/integration/virtualkeyboard_test.cpp index 713b806fc1..28b451cca6 100644 --- a/autotests/integration/virtualkeyboard_test.cpp +++ b/autotests/integration/virtualkeyboard_test.cpp @@ -18,6 +18,7 @@ #include "virtualkeyboard.h" #include "virtualkeyboard_dbus.h" #include "qwayland-input-method-unstable-v1.h" +#include "qwayland-text-input-unstable-v3.h" #include #include @@ -33,6 +34,7 @@ #include #include #include +#include using namespace KWin; using namespace KWayland::Client; @@ -49,6 +51,7 @@ private Q_SLOTS: void cleanup(); void testOpenClose(); + void testEnableDisableV3(); }; @@ -77,7 +80,9 @@ void VirtualKeyboardTest::initTestCase() void VirtualKeyboardTest::init() { QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat | - Test::AdditionalWaylandInterface::TextInputManagerV2 | Test::AdditionalWaylandInterface::InputMethodV1)); + Test::AdditionalWaylandInterface::TextInputManagerV2 | + Test::AdditionalWaylandInterface::InputMethodV1 | + Test::AdditionalWaylandInterface::TextInputManagerV3)); screens()->setCurrent(0); @@ -149,6 +154,40 @@ void VirtualKeyboardTest::testOpenClose() QVERIFY(Test::waitForWindowDestroyed(client)); } +void VirtualKeyboardTest::testEnableDisableV3() +{ + QSignalSpy clientAddedSpy(workspace(), &Workspace::clientAdded); + QSignalSpy clientRemovedSpy(workspace(), &Workspace::clientRemoved); + QVERIFY(clientAddedSpy.isValid()); + + // Create an xdg_toplevel surface and wait for the compositor to catch up. + QScopedPointer surface(Test::createSurface()); + QScopedPointer shellSurface(Test::createXdgShellStableSurface(surface.data())); + AbstractClient *client = Test::renderAndWaitForShown(surface.data(), QSize(1280, 1024), Qt::red); + QVERIFY(client); + QVERIFY(client->isActive()); + QCOMPARE(client->frameGeometry().size(), QSize(1280, 1024)); + + Test::TextInputV3 *textInputV3 = new Test::TextInputV3(); + textInputV3->init(Test::waylandTextInputManagerV3()->get_text_input(*(Test::waylandSeat()))); + textInputV3->enable(); + // just enabling the text-input should not show it but rather on commit + QVERIFY(!clientAddedSpy.wait(100)); + + textInputV3->commit(); + + QVERIFY(clientAddedSpy.wait()); + AbstractClient *keyboardClient = clientAddedSpy.last().first().value(); + QVERIFY(keyboardClient); + QVERIFY(keyboardClient->isInputMethod()); + + // disable text input and ensure that it is not hiding input panel without commit + textInputV3->disable(); + QVERIFY(!clientRemovedSpy.wait(100)); + textInputV3->commit(); + QVERIFY(clientRemovedSpy.wait()); +} + WAYLANDTEST_MAIN(VirtualKeyboardTest) #include "virtualkeyboard_test.moc" diff --git a/virtualkeyboard.cpp b/virtualkeyboard.cpp index cf90c205cf..44832d3d3b 100644 --- a/virtualkeyboard.cpp +++ b/virtualkeyboard.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -29,6 +30,9 @@ #include #include +#include +#include + using namespace KWaylandServer; namespace KWin @@ -85,6 +89,8 @@ void VirtualKeyboard::init() if (waylandServer()) { waylandServer()->display()->createTextInputManagerV2(); + waylandServer()->display()->createTextInputManagerV3(); + connect(workspace(), &Workspace::clientAdded, this, &VirtualKeyboard::clientAdded); connect(waylandServer()->seat(), &SeatInterface::focusedTextInputSurfaceChanged, this, &VirtualKeyboard::handleFocusedSurfaceChanged); @@ -94,8 +100,14 @@ void VirtualKeyboard::init() connect(textInputV2, &TextInputV2Interface::surroundingTextChanged, this, &VirtualKeyboard::surroundingTextChanged); connect(textInputV2, &TextInputV2Interface::contentTypeChanged, this, &VirtualKeyboard::contentTypeChanged); connect(textInputV2, &TextInputV2Interface::requestReset, this, &VirtualKeyboard::requestReset); - connect(textInputV2, &TextInputV2Interface::enabledChanged, this, &VirtualKeyboard::textInputInterfaceEnabledChanged); + connect(textInputV2, &TextInputV2Interface::enabledChanged, this, &VirtualKeyboard::textInputInterfaceV2EnabledChanged); connect(textInputV2, &TextInputV2Interface::stateCommitted, this, &VirtualKeyboard::stateCommitted); + + TextInputV3Interface *textInputV3 = waylandServer()->seat()->textInputV3(); + connect(textInputV3, &TextInputV3Interface::enabledChanged, this, &VirtualKeyboard::textInputInterfaceV3EnabledChanged); + connect(textInputV3, &TextInputV3Interface::surroundingTextChanged, this, &VirtualKeyboard::surroundingTextChanged); + connect(textInputV3, &TextInputV3Interface::contentTypeChanged, this, &VirtualKeyboard::contentTypeChanged); + connect(textInputV3, &TextInputV3Interface::stateCommitted, this, &VirtualKeyboard::stateCommitted); } } @@ -159,38 +171,54 @@ void VirtualKeyboard::handleFocusedSurfaceChanged() void VirtualKeyboard::surroundingTextChanged() { - auto t = waylandServer()->seat()->textInputV2(); + auto t2 = waylandServer()->seat()->textInputV2(); + auto t3 = waylandServer()->seat()->textInputV3(); auto inputContext = waylandServer()->inputMethod()->context(); if (!inputContext) { return; } - inputContext->sendSurroundingText(t->surroundingText(), t->surroundingTextCursorPosition(), t->surroundingTextSelectionAnchor()); + if (t2 && t2->isEnabled()) { + inputContext->sendSurroundingText(t2->surroundingText(), t2->surroundingTextCursorPosition(), t2->surroundingTextSelectionAnchor()); + return; + } + if (t3 && t3->isEnabled()) { + inputContext->sendSurroundingText(t3->surroundingText(), t3->surroundingTextCursorPosition(), t3->surroundingTextSelectionAnchor()); + return; + } } void VirtualKeyboard::contentTypeChanged() { - auto t = waylandServer()->seat()->textInputV2(); + auto t2 = waylandServer()->seat()->textInputV2(); + auto t3 = waylandServer()->seat()->textInputV3(); auto inputContext = waylandServer()->inputMethod()->context(); if (!inputContext) { return; } - inputContext->sendContentType(t->contentHints(), t->contentPurpose()); + if (t2 && t2->isEnabled()) { + inputContext->sendContentType(t2->contentHints(), t2->contentPurpose()); + } + if (t3 && t3->isEnabled()) { + inputContext->sendContentType(t3->contentHints(), t3->contentPurpose()); + } } void VirtualKeyboard::requestReset() { - auto t = waylandServer()->seat()->textInputV2(); + auto t2 = waylandServer()->seat()->textInputV2(); auto inputContext = waylandServer()->inputMethod()->context(); if (!inputContext) { return; } inputContext->sendReset(); - inputContext->sendSurroundingText(t->surroundingText(), t->surroundingTextCursorPosition(), t->surroundingTextSelectionAnchor()); - inputContext->sendPreferredLanguage(t->preferredLanguage()); - inputContext->sendContentType(t->contentHints(), t->contentPurpose()); + if (t2 && t2->isEnabled()) { + inputContext->sendSurroundingText(t2->surroundingText(), t2->surroundingTextCursorPosition(), t2->surroundingTextSelectionAnchor()); + inputContext->sendPreferredLanguage(t2->preferredLanguage()); + inputContext->sendContentType(t2->contentHints(), t2->contentPurpose()); + } } -void VirtualKeyboard::textInputInterfaceEnabledChanged() +void VirtualKeyboard::textInputInterfaceV2EnabledChanged() { auto t = waylandServer()->seat()->textInputV2(); if (t->isEnabled()) { @@ -204,6 +232,21 @@ void VirtualKeyboard::textInputInterfaceEnabledChanged() } } +void VirtualKeyboard::textInputInterfaceV3EnabledChanged() +{ + auto t3 = waylandServer()->seat()->textInputV3(); + if (t3->isEnabled()) { + waylandServer()->inputMethod()->sendActivate(); + adoptInputMethodContext(); + } else { + waylandServer()->inputMethod()->sendDeactivate(); + // reset value of preedit when textinput is disabled + preedit.text = QString(); + preedit.begin = 0; + preedit.end = 0; + } +} + void VirtualKeyboard::stateCommitted(uint32_t serial) { auto inputContext = waylandServer()->inputMethod()->context(); @@ -232,98 +275,160 @@ void VirtualKeyboard::setEnabled(bool enabled) QDBusConnection::sessionBus().asyncCall(msg); } +static quint32 keysymToKeycode(quint32 sym) +{ + switch(sym) { + case XKB_KEY_BackSpace: + return KEY_BACKSPACE; + case XKB_KEY_Return: + return KEY_ENTER; + case XKB_KEY_Left: + return KEY_LEFT; + case XKB_KEY_Right: + return KEY_RIGHT; + case XKB_KEY_Up: + return KEY_UP; + case XKB_KEY_Down: + return KEY_DOWN; + default: + return KEY_UNKNOWN; + } +} + static void keysymReceived(quint32 serial, quint32 time, quint32 sym, bool pressed, Qt::KeyboardModifiers modifiers) { Q_UNUSED(serial) Q_UNUSED(time) - auto t = waylandServer()->seat()->textInputV2(); - if (t && t->isEnabled()) { + auto t2 = waylandServer()->seat()->textInputV2(); + if (t2 && t2->isEnabled()) { if (pressed) { - t->keysymPressed(sym, modifiers); + t2->keysymPressed(sym, modifiers); } else { - t->keysymReleased(sym, modifiers); + t2->keysymReleased(sym, modifiers); } + return; + } + auto t3 = waylandServer()->seat()->textInputV3(); + if (t3 && t3->isEnabled()) { + if (pressed) { + waylandServer()->seat()->keyPressed(keysymToKeycode(sym)); + } else { + waylandServer()->seat()->keyReleased(keysymToKeycode(sym)); + } + return; } } static void commitString(qint32 serial, const QString &text) { Q_UNUSED(serial) - auto t = waylandServer()->seat()->textInputV2(); - if (t && t->isEnabled()) { - t->commit(text.toUtf8()); - t->preEdit({}, {}); + auto t2 = waylandServer()->seat()->textInputV2(); + if (t2 && t2->isEnabled()) { + t2->commitString(text.toUtf8()); + t2->preEdit({}, {}); + return; } -} - -static void setPreeditCursor(qint32 index) -{ - auto t = waylandServer()->seat()->textInputV2(); - if (t && t->isEnabled()) { - t->setPreEditCursor(index); - } -} - -static void setPreeditString(uint32_t serial, const QString &text, const QString &commit) -{ - Q_UNUSED(serial) - auto t = waylandServer()->seat()->textInputV2(); - if (t && t->isEnabled()) { - t->preEdit(text.toUtf8(), commit.toUtf8()); + auto t3 = waylandServer()->seat()->textInputV3(); + if (t3 && t3->isEnabled()) { + t3->commitString(text.toUtf8()); + t3->done(); + return; } } static void deleteSurroundingText(int32_t index, uint32_t length) { - auto t = waylandServer()->seat()->textInputV2(); - if (t && t->isEnabled()) { - t->deleteSurroundingText(index, length); + auto t2 = waylandServer()->seat()->textInputV2(); + if (t2 && t2->isEnabled()) { + t2->deleteSurroundingText(index, length); + } + auto t3 = waylandServer()->seat()->textInputV3(); + if (t3 && t3->isEnabled()) { + t3->deleteSurroundingText(index, length); } } static void setCursorPosition(qint32 index, qint32 anchor) { - auto t = waylandServer()->seat()->textInputV2(); - if (t && t->isEnabled()) { - t->setCursorPosition(index, anchor); + auto t2 = waylandServer()->seat()->textInputV2(); + if (t2 && t2->isEnabled()) { + t2->setCursorPosition(index, anchor); } } static void setLanguage(uint32_t serial, const QString &language) { Q_UNUSED(serial) - auto t = waylandServer()->seat()->textInputV2(); - if (t && t->isEnabled()) { - t->setLanguage(language.toUtf8()); + auto t2 = waylandServer()->seat()->textInputV2(); + if (t2 && t2->isEnabled()) { + t2->setLanguage(language.toUtf8()); } } static void setTextDirection(uint32_t serial, Qt::LayoutDirection direction) { Q_UNUSED(serial) - auto t = waylandServer()->seat()->textInputV2(); - if (t && t->isEnabled()) { - t->setTextDirection(direction); + auto t2 = waylandServer()->seat()->textInputV2(); + if (t2 && t2->isEnabled()) { + t2->setTextDirection(direction); + } +} + +void VirtualKeyboard::setPreeditCursor(qint32 index) +{ + auto t2 = waylandServer()->seat()->textInputV2(); + if (t2 && t2->isEnabled()) { + t2->setPreEditCursor(index); + } + auto t3 = waylandServer()->seat()->textInputV3(); + if (t3 && t3->isEnabled()) { + preedit.begin = index; + preedit.end = index; + t3->sendPreEditString(preedit.text, preedit.begin, preedit.end); + } +} + + +void VirtualKeyboard::setPreeditString(uint32_t serial, const QString &text, const QString &commit) +{ + Q_UNUSED(serial) + auto t2 = waylandServer()->seat()->textInputV2(); + if (t2 && t2->isEnabled()) { + t2->preEdit(text.toUtf8(), commit.toUtf8()); + } + auto t3 = waylandServer()->seat()->textInputV3(); + if (t3 && t3->isEnabled()) { + preedit.text = text; + t3->sendPreEditString(preedit.text, preedit.begin, preedit.end); } } void VirtualKeyboard::adoptInputMethodContext() { auto inputContext = waylandServer()->inputMethod()->context(); - TextInputV2Interface *ti = waylandServer()->seat()->textInputV2(); - inputContext->sendSurroundingText(ti->surroundingText(), ti->surroundingTextCursorPosition(), ti->surroundingTextSelectionAnchor()); - inputContext->sendPreferredLanguage(ti->preferredLanguage()); - inputContext->sendContentType(ti->contentHints(), ti->contentPurpose()); + TextInputV2Interface *t2 = waylandServer()->seat()->textInputV2(); + TextInputV3Interface *t3 = waylandServer()->seat()->textInputV3(); + + if (t2 && t2->isEnabled()) { + inputContext->sendSurroundingText(t2->surroundingText(), t2->surroundingTextCursorPosition(), t2->surroundingTextSelectionAnchor()); + inputContext->sendPreferredLanguage(t2->preferredLanguage()); + inputContext->sendContentType(t2->contentHints(), t2->contentPurpose()); + connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::language, waylandServer(), &setLanguage); + connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::textDirection, waylandServer(), &setTextDirection); + } + + if (t3 && t3->isEnabled()) { + inputContext->sendSurroundingText(t3->surroundingText(), t3->surroundingTextCursorPosition(), t3->surroundingTextSelectionAnchor()); + inputContext->sendContentType(t3->contentHints(), t3->contentPurpose()); + } connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::keysym, waylandServer(), &keysymReceived); connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::commitString, waylandServer(), &commitString); - connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::preeditCursor, waylandServer(), &setPreeditCursor); - connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::preeditString, waylandServer(), &setPreeditString); connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::deleteSurroundingText, waylandServer(), &deleteSurroundingText); connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::cursorPosition, waylandServer(), &setCursorPosition); - connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::language, waylandServer(), &setLanguage); - connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::textDirection, waylandServer(), &setTextDirection); + connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::preeditString, this, &VirtualKeyboard::setPreeditString); + connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::preeditCursor, this, &VirtualKeyboard::setPreeditCursor); } void VirtualKeyboard::updateSni() diff --git a/virtualkeyboard.h b/virtualkeyboard.h index ab4a26609f..83702edfa7 100644 --- a/virtualkeyboard.h +++ b/virtualkeyboard.h @@ -42,15 +42,26 @@ private Q_SLOTS: void surroundingTextChanged(); void contentTypeChanged(); void requestReset(); - void textInputInterfaceEnabledChanged(); + void textInputInterfaceV2EnabledChanged(); + void textInputInterfaceV3EnabledChanged(); void stateCommitted(uint32_t serial); + // inputcontext slots + void setPreeditString(uint32_t serial, const QString &text, const QString &commit); + void setPreeditCursor(qint32 index); + private: void setEnabled(bool enable); void updateSni(); void updateInputPanelState(); void adoptInputMethodContext(); + struct { + QString text = QString(); + quint32 begin = 0; + quint32 end = 0; + } preedit; + bool m_enabled = false; KStatusNotifierItem *m_sni = nullptr; QPointer m_inputClient;