diff --git a/virtualkeyboard.h b/virtualkeyboard.h --- a/virtualkeyboard.h +++ b/virtualkeyboard.h @@ -57,10 +57,12 @@ void setEnabled(bool enable); void updateSni(); void updateInputPanelState(); + void adoptInputMethodContext(); bool m_enabled = false; KStatusNotifierItem *m_sni = nullptr; QScopedPointer m_inputWindow; + QPointer m_inputSurface; QPointer m_trackedClient; // If a surface loses focus immediately after being resized by the keyboard, don't react to it to avoid resize loops QTimer *m_floodTimer; diff --git a/virtualkeyboard.cpp b/virtualkeyboard.cpp --- a/virtualkeyboard.cpp +++ b/virtualkeyboard.cpp @@ -29,7 +29,9 @@ #include "xdgshellclient.h" #include "screenlockerwatcher.h" +#include #include +#include #include #include #include @@ -127,6 +129,16 @@ t->create(); auto t2 = waylandServer()->display()->createTextInputManager(TextInputInterfaceVersion::UnstableV2, waylandServer()->display()); t2->create(); + + connect(waylandServer()->inputPanel(), &InputPanelInterface::inputPanelSurfaceAdded, this, [this](quint32, InputPanelSurfaceInterface *surface) { + m_inputSurface = waylandServer()->createInputPanelSurface(surface); + connect(m_inputSurface, &KWin::AbstractClient::geometryShapeChanged, this, [this] { + if (!m_trackedClient) + return; + m_trackedClient->setVirtualKeyboardGeometry(m_inputSurface->rect()); + }); + }); + connect(waylandServer()->seat(), &SeatInterface::focusedTextInputChanged, this, [this] { disconnect(m_waylandShowConnection); @@ -150,11 +162,15 @@ } ); m_waylandResetConnection = connect(t, &TextInputInterface::requestReset, qApp->inputMethod(), &QInputMethod::reset); - m_waylandEnabledConnection = connect(t, &TextInputInterface::enabledChanged, this, - [] { - qApp->inputMethod()->update(Qt::ImQueryAll); - } - ); + m_waylandEnabledConnection = connect(t, &TextInputInterface::enabledChanged, this, [t, this] { + if (t->isEnabled()) { + waylandServer()->inputMethod()->sendDeactivate(); + waylandServer()->inputMethod()->sendActivate(); + adoptInputMethodContext(); + } else + waylandServer()->inputMethod()->sendDeactivate(); + qApp->inputMethod()->update(Qt::ImQueryAll); + }); auto newClient = waylandServer()->findAbstractClient(waylandServer()->seat()->focusedTextInputSurface()); // Reset the old client virtual keybaord geom if necessary @@ -223,6 +239,69 @@ QDBusConnection::sessionBus().asyncCall(msg); } +void VirtualKeyboard::adoptInputMethodContext() +{ + auto inputContext = waylandServer()->inputMethod()->context(); + TextInputInterface *ti = waylandServer()->seat()->focusedTextInput(); + + inputContext->sendSurroundingText(QString::fromUtf8(ti->surroundingText()), ti->surroundingTextCursorPosition(), ti->surroundingTextSelectionAnchor()); + inputContext->sendPreferredLanguage(QString::fromUtf8(ti->preferredLanguage())); + + connect(inputContext, &KWayland::Server::InputMethodContextInterface::keysym, waylandServer(), [](quint32 serial, quint32 time, quint32 sym, bool pressed, Qt::KeyboardModifiers modifiers) { + auto t = waylandServer()->seat()->focusedTextInput(); + if (t && t->isEnabled()) { + if (pressed) { + t->keysymPressed(sym, modifiers); + } else { + t->keysymReleased(sym, modifiers); + } + } + }); + + connect(inputContext, &KWayland::Server::InputMethodContextInterface::commitString, waylandServer(), [](qint32 serial, const QString &text) { + auto t = waylandServer()->seat()->focusedTextInput(); + if (t && t->isEnabled()) { + t->commit(text.toUtf8()); + } + }); + connect(inputContext, &KWayland::Server::InputMethodContextInterface::preeditCursor, waylandServer(), [](qint32 index) { + auto t = waylandServer()->seat()->focusedTextInput(); + if (t && t->isEnabled()) { + t->setPreEditCursor(index); + } + }); + connect(inputContext, &KWayland::Server::InputMethodContextInterface::preeditString, waylandServer(), [](uint32_t serial, const QString &text, const QString &commit) { + auto t = waylandServer()->seat()->focusedTextInput(); + if (t && t->isEnabled()) { + t->preEdit(text.toUtf8(), commit.toUtf8()); + } + }); + connect(inputContext, &KWayland::Server::InputMethodContextInterface::deleteSurroundingText, waylandServer(), [](int32_t index, uint32_t length) { + auto t = waylandServer()->seat()->focusedTextInput(); + if (t && t->isEnabled()) { + t->deleteSurroundingText(index, length); + } + }); + connect(inputContext, &KWayland::Server::InputMethodContextInterface::cursorPosition, waylandServer(), [](qint32 index, qint32 anchor) { + auto t = waylandServer()->seat()->focusedTextInput(); + if (t && t->isEnabled()) { + t->setCursorPosition(index, anchor); + } + }); + connect(inputContext, &KWayland::Server::InputMethodContextInterface::language, waylandServer(), [](uint32_t serial, const QString &language) { + auto t = waylandServer()->seat()->focusedTextInput(); + if (t && t->isEnabled()) { + t->setLanguage(language.toUtf8()); + } + }); + connect(inputContext, &KWayland::Server::InputMethodContextInterface::textDirection, waylandServer(), [](uint32_t serial, Qt::LayoutDirection direction) { + auto t = waylandServer()->seat()->focusedTextInput(); + if (t && t->isEnabled()) { + t->setTextDirection(direction); + } + }); +} + void VirtualKeyboard::updateSni() { if (!m_sni) { @@ -246,35 +325,41 @@ auto t = waylandServer()->seat()->focusedTextInput(); - if (!t || !m_inputWindow) { + if (!t) { return; } - const bool inputPanelHasBeenClosed = m_inputWindow->isVisible() && !qApp->inputMethod()->isVisible(); - if (inputPanelHasBeenClosed && m_floodTimer->isActive()) { - return; - } - m_floodTimer->start(); + if (m_inputWindow) { + const bool inputPanelHasBeenClosed = m_inputWindow->isVisible() && !qApp->inputMethod()->isVisible(); + if (inputPanelHasBeenClosed && m_floodTimer->isActive()) { + return; + } + m_floodTimer->start(); - m_inputWindow->setVisible(qApp->inputMethod()->isVisible()); + m_inputWindow->setVisible(qApp->inputMethod()->isVisible()); - if (qApp->inputMethod()->isVisible()) { - m_inputWindow->setMask(m_inputWindow->rootObject()->childrenRect().toRect()); - } + if (qApp->inputMethod()->isVisible()) { + m_inputWindow->setMask(m_inputWindow->rootObject()->childrenRect().toRect()); + } - if (m_inputWindow->isVisible() && m_trackedClient && m_inputWindow->rootObject()) { - const QRect inputPanelGeom = m_inputWindow->rootObject()->childrenRect().toRect().translated(m_inputWindow->geometry().topLeft()); + if (m_inputWindow->isVisible() && m_trackedClient && m_inputWindow->rootObject()) { + const QRect inputPanelGeom = m_inputWindow->rootObject()->childrenRect().toRect().translated(m_inputWindow->geometry().topLeft()); - m_trackedClient->setVirtualKeyboardGeometry(inputPanelGeom); + m_trackedClient->setVirtualKeyboardGeometry(inputPanelGeom); - t->setInputPanelState(true, QRect(0, 0, 0, 0)); + t->setInputPanelState(true, QRect(0, 0, 0, 0)); - } else { - if (inputPanelHasBeenClosed && m_trackedClient) { - m_trackedClient->setVirtualKeyboardGeometry(QRect()); - } + } else { + if (inputPanelHasBeenClosed && m_trackedClient) { + m_trackedClient->setVirtualKeyboardGeometry(QRect()); + } - t->setInputPanelState(false, QRect(0, 0, 0, 0)); + t->setInputPanelState(false, QRect(0, 0, 0, 0)); + } + } + if (m_inputSurface) { + m_trackedClient->setVirtualKeyboardGeometry(m_inputSurface->frameGeometry()); + t->setInputPanelState(true, QRect(0, 0, 0, 0)); } } diff --git a/wayland_server.h b/wayland_server.h --- a/wayland_server.h +++ b/wayland_server.h @@ -49,6 +49,9 @@ class Display; class DataDeviceInterface; class IdleInterface; +class InputMethodInterface; +class InputPanelInterface; +class InputPanelSurfaceInterface; class SeatInterface; class DataDeviceManagerInterface; class ServerSideDecorationManagerInterface; @@ -121,6 +124,12 @@ return m_xdgOutputManager; } KWayland::Server::LinuxDmabufUnstableV1Interface *linuxDmabuf(); + KWayland::Server::InputMethodInterface *inputMethod() const { + return m_inputMethod; + } + KWayland::Server::InputPanelInterface *inputPanel() const { + return m_inputPanel; + } QList clients() const { return m_clients; @@ -228,6 +237,7 @@ void removeLinuxDmabufBuffer(KWayland::Server::LinuxDmabufUnstableV1Buffer *buffer) { m_linuxDmabufBuffers.remove(buffer); } + AbstractClient *createInputPanelSurface(KWayland::Server::InputPanelSurfaceInterface *surface); Q_SIGNALS: void shellClientAdded(KWin::XdgShellClient *); @@ -260,6 +270,8 @@ KWayland::Server::XdgOutputManagerInterface *m_xdgOutputManager = nullptr; KWayland::Server::XdgDecorationManagerInterface *m_xdgDecorationManager = nullptr; KWayland::Server::LinuxDmabufUnstableV1Interface *m_linuxDmabuf = nullptr; + KWayland::Server::InputMethodInterface* m_inputMethod = nullptr; + KWayland::Server::InputPanelInterface* m_inputPanel = nullptr; QSet m_linuxDmabufBuffers; struct { KWayland::Server::ClientConnection *client = nullptr; diff --git a/wayland_server.cpp b/wayland_server.cpp --- a/wayland_server.cpp +++ b/wayland_server.cpp @@ -66,6 +66,7 @@ #include #include #include +#include // KF #include @@ -190,6 +191,23 @@ }); } +AbstractClient *WaylandServer::createInputPanelSurface(KWayland::Server::InputPanelSurfaceInterface *surface) +{ + XdgShellClient *client = new XdgShellClient(surface); + m_clients << client; + if (client->readyForPainting()) { + emit shellClientAdded(client); + } else { + connect(client, &XdgShellClient::windowShown, this, &WaylandServer::shellClientShown); + } + + //not directly connected as the connection is tied to client instead of this + connect(m_XdgForeign, &KWayland::Server::XdgForeignInterface::transientChanged, client, [this](KWayland::Server::SurfaceInterface *child) { + emit foreignTransientChanged(child); + }); + return client; +} + class KWinDisplay : public KWayland::Server::FilteredDisplay { public: @@ -451,6 +469,9 @@ m_keyState = m_display->createKeyStateInterface(m_display); m_keyState->create(); + m_inputMethod = m_display->createInputMethodInterface(m_display); + m_inputPanel = m_display->createInputPanelInterface(m_display); + return true; } diff --git a/xdgshellclient.h b/xdgshellclient.h --- a/xdgshellclient.h +++ b/xdgshellclient.h @@ -25,6 +25,7 @@ #include "abstract_client.h" #include +#include namespace KWayland { @@ -54,6 +55,7 @@ Q_OBJECT public: + XdgShellClient(KWayland::Server::InputPanelSurfaceInterface *surface); XdgShellClient(KWayland::Server::XdgShellSurfaceInterface *surface); XdgShellClient(KWayland::Server::XdgShellPopupInterface *surface); ~XdgShellClient() override; @@ -196,6 +198,7 @@ KWayland::Server::XdgShellSurfaceInterface *m_xdgShellToplevel; KWayland::Server::XdgShellPopupInterface *m_xdgShellPopup; + KWayland::Server::InputPanelSurfaceInterface *m_inputPanelSurface = nullptr; QRect m_bufferGeometry; QRect m_windowGeometry; diff --git a/xdgshellclient.cpp b/xdgshellclient.cpp --- a/xdgshellclient.cpp +++ b/xdgshellclient.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -83,6 +84,42 @@ init(); } +XdgShellClient::XdgShellClient(InputPanelSurfaceInterface *panelSurface) + : AbstractClient() + , m_xdgShellToplevel(nullptr) + , m_xdgShellPopup(nullptr) + , m_inputPanelSurface(panelSurface) +{ + setSurface(panelSurface->surface()); + init(); + setSkipPager(true); + setSkipTaskbar(true); + setKeepAbove(true); + + setObjectName(QStringLiteral("Input Panel")); + + connect(panelSurface, &KWayland::Server::InputPanelSurfaceInterface::topLevel, this, [this, panelSurface] (OutputInterface *output, KWayland::Server::InputPanelSurfaceInterface::Position position) { + const QSize panelSize = panelSurface->surface()->size(); + QRect geo = {output->globalPosition(), panelSize}; + if (position == KWayland::Server::InputPanelSurfaceInterface::CenterBottom) { + geo.translate((output->pixelSize().width() - panelSize.width())/2, output->pixelSize().height() - panelSize.height()); + } + finishInit(); + markAsMapped(); + doSetGeometry(geo); + }); + connect(panelSurface, &KWayland::Server::InputPanelSurfaceInterface::overlayPanel, this, [this, panelSurface] () { + finishInit(); + markAsMapped(); + + auto focusedField = waylandServer()->findAbstractClient(waylandServer()->seat()->focusedTextInputSurface()); + if (focusedField) { + const QSize panelSize = panelSurface->surface()->size(); + doSetGeometry({focusedField->pos(), panelSize}); + } + }); +} + XdgShellClient::~XdgShellClient() = default; void XdgShellClient::init()