File indexing completed on 2024-12-01 13:37:30

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "inputmethod.h"
0010 
0011 #include <config-kwin.h>
0012 
0013 #include "input.h"
0014 #include "inputpanelv1window.h"
0015 #include "keyboard_input.h"
0016 #include "utils/common.h"
0017 #include "virtualkeyboard_dbus.h"
0018 #include "wayland_server.h"
0019 #include "window.h"
0020 #include "workspace.h"
0021 #if KWIN_BUILD_SCREENLOCKER
0022 #include "screenlockerwatcher.h"
0023 #endif
0024 #include "deleted.h"
0025 #include "tablet_input.h"
0026 #include "touch_input.h"
0027 #include "wayland/display.h"
0028 #include "wayland/inputmethod_v1_interface.h"
0029 #include "wayland/keyboard_interface.h"
0030 #include "wayland/seat_interface.h"
0031 #include "wayland/surface_interface.h"
0032 #include "wayland/textinput_v1_interface.h"
0033 #include "wayland/textinput_v3_interface.h"
0034 #include "xkb.h"
0035 
0036 #include <KLocalizedString>
0037 #include <KShell>
0038 #include <KKeyServer>
0039 
0040 #include <QDBusConnection>
0041 #include <QDBusMessage>
0042 #include <QDBusPendingCall>
0043 #include <QKeyEvent>
0044 #include <QMenu>
0045 
0046 #include <linux/input-event-codes.h>
0047 #include <unistd.h>
0048 #include <xkbcommon/xkbcommon-keysyms.h>
0049 
0050 using namespace KWaylandServer;
0051 
0052 namespace KWin
0053 {
0054 
0055 static std::vector<quint32> textToKey(const QString &text)
0056 {
0057     if (text.isEmpty()) {
0058         return {};
0059     }
0060 
0061     auto sequence = QKeySequence::fromString(text);
0062     if (sequence.isEmpty()) {
0063         return {};
0064     }
0065 
0066     int sym;
0067     if (!KKeyServer::keyQtToSymX(sequence[0], &sym)) {
0068         return {};
0069     }
0070 
0071     auto keyCode = KWin::input()->keyboard()->xkb()->keycodeFromKeysym(sym);
0072     if (!keyCode) {
0073         return {};
0074     }
0075 
0076     if (text.isUpper()) {
0077         return {KEY_LEFTSHIFT, quint32(keyCode.value())};
0078     }
0079 
0080     return {quint32(keyCode.value())};
0081 }
0082 
0083 InputMethod::InputMethod()
0084 {
0085     m_enabled = kwinApp()->config()->group("Wayland").readEntry("VirtualKeyboardEnabled", true);
0086     // this is actually too late. Other processes are started before init,
0087     // so might miss the availability of text input
0088     // but without Workspace we don't have the window listed at all
0089     if (workspace()) {
0090         init();
0091     } else {
0092         connect(kwinApp(), &Application::workspaceCreated, this, &InputMethod::init);
0093     }
0094 }
0095 
0096 InputMethod::~InputMethod()
0097 {
0098     stopInputMethod();
0099 }
0100 
0101 void InputMethod::init()
0102 {
0103     // Stop restarting the input method if it starts crashing very frequently
0104     m_inputMethodCrashTimer.setInterval(20000);
0105     m_inputMethodCrashTimer.setSingleShot(true);
0106     connect(&m_inputMethodCrashTimer, &QTimer::timeout, this, [this] {
0107         m_inputMethodCrashes = 0;
0108     });
0109 #if KWIN_BUILD_SCREENLOCKER
0110     connect(kwinApp()->screenLockerWatcher(), &ScreenLockerWatcher::aboutToLock, this, &InputMethod::hide);
0111 #endif
0112 
0113     new VirtualKeyboardDBus(this);
0114     qCDebug(KWIN_VIRTUALKEYBOARD) << "Registering the DBus interface";
0115 
0116     if (waylandServer()) {
0117         new TextInputManagerV1Interface(waylandServer()->display());
0118         new TextInputManagerV2Interface(waylandServer()->display());
0119         new TextInputManagerV3Interface(waylandServer()->display());
0120 
0121         connect(waylandServer()->seat(), &SeatInterface::focusedTextInputSurfaceChanged, this, &InputMethod::handleFocusedSurfaceChanged);
0122 
0123         TextInputV1Interface *textInputV1 = waylandServer()->seat()->textInputV1();
0124         connect(textInputV1, &TextInputV1Interface::requestShowInputPanel, this, &InputMethod::show);
0125         connect(textInputV1, &TextInputV1Interface::requestHideInputPanel, this, &InputMethod::hide);
0126         connect(textInputV1, &TextInputV1Interface::surroundingTextChanged, this, &InputMethod::surroundingTextChanged);
0127         connect(textInputV1, &TextInputV1Interface::contentTypeChanged, this, &InputMethod::contentTypeChanged);
0128         connect(textInputV1, &TextInputV1Interface::stateUpdated, this, &InputMethod::textInputInterfaceV1StateUpdated);
0129         connect(textInputV1, &TextInputV1Interface::reset, this, &InputMethod::textInputInterfaceV1Reset);
0130         connect(textInputV1, &TextInputV1Interface::invokeAction, this, &InputMethod::invokeAction);
0131         connect(textInputV1, &TextInputV1Interface::enabledChanged, this, &InputMethod::textInputInterfaceV1EnabledChanged);
0132 
0133         TextInputV2Interface *textInputV2 = waylandServer()->seat()->textInputV2();
0134         connect(textInputV2, &TextInputV2Interface::requestShowInputPanel, this, &InputMethod::show);
0135         connect(textInputV2, &TextInputV2Interface::requestHideInputPanel, this, &InputMethod::hide);
0136         connect(textInputV2, &TextInputV2Interface::surroundingTextChanged, this, &InputMethod::surroundingTextChanged);
0137         connect(textInputV2, &TextInputV2Interface::contentTypeChanged, this, &InputMethod::contentTypeChanged);
0138         connect(textInputV2, &TextInputV2Interface::stateUpdated, this, &InputMethod::textInputInterfaceV2StateUpdated);
0139         connect(textInputV2, &TextInputV2Interface::enabledChanged, this, &InputMethod::textInputInterfaceV2EnabledChanged);
0140 
0141         TextInputV3Interface *textInputV3 = waylandServer()->seat()->textInputV3();
0142         connect(textInputV3, &TextInputV3Interface::surroundingTextChanged, this, &InputMethod::surroundingTextChanged);
0143         connect(textInputV3, &TextInputV3Interface::contentTypeChanged, this, &InputMethod::contentTypeChanged);
0144         connect(textInputV3, &TextInputV3Interface::stateCommitted, this, &InputMethod::stateCommitted);
0145         connect(textInputV3, &TextInputV3Interface::enabledChanged, this, &InputMethod::textInputInterfaceV3EnabledChanged);
0146         connect(textInputV3, &TextInputV3Interface::enableRequested, this, &InputMethod::textInputInterfaceV3EnableRequested);
0147 
0148         connect(input()->keyboard()->xkb(), &Xkb::modifierStateChanged, this, [this]() {
0149             m_hasPendingModifiers = true;
0150         });
0151     }
0152 }
0153 
0154 void InputMethod::show()
0155 {
0156     m_shouldShowPanel = true;
0157     if (m_panel) {
0158         m_panel->show();
0159         updateInputPanelState();
0160     } else if (isActive()) {
0161         adoptInputMethodContext();
0162     }
0163 }
0164 
0165 void InputMethod::hide()
0166 {
0167     m_shouldShowPanel = false;
0168     if (m_panel) {
0169         m_panel->hide();
0170         updateInputPanelState();
0171     }
0172 }
0173 
0174 bool InputMethod::shouldShowOnActive() const
0175 {
0176     static bool alwaysShowIm = qEnvironmentVariableIntValue("KWIN_IM_SHOW_ALWAYS") != 0;
0177     return alwaysShowIm || input()->touch() == input()->lastInputHandler()
0178         || input()->tablet() == input()->lastInputHandler();
0179 }
0180 
0181 void InputMethod::refreshActive()
0182 {
0183     auto seat = waylandServer()->seat();
0184     auto t1 = seat->textInputV1();
0185     auto t2 = seat->textInputV2();
0186     auto t3 = seat->textInputV3();
0187 
0188     bool active = false;
0189     if (auto focusedSurface = seat->focusedTextInputSurface()) {
0190         auto client = focusedSurface->client();
0191         if ((t1->clientSupportsTextInput(client) && t1->isEnabled()) || (t2->clientSupportsTextInput(client) && t2->isEnabled()) || (t3->clientSupportsTextInput(client) && t3->isEnabled())) {
0192             active = true;
0193         }
0194     }
0195 
0196     setActive(active);
0197 }
0198 
0199 void InputMethod::setActive(bool active)
0200 {
0201     const bool wasActive = waylandServer()->inputMethod()->context();
0202     if (wasActive && !active) {
0203         waylandServer()->inputMethod()->sendDeactivate();
0204     }
0205 
0206     if (active) {
0207         if (!m_enabled) {
0208             return;
0209         }
0210 
0211         if (!wasActive) {
0212             waylandServer()->inputMethod()->sendActivate();
0213         }
0214         adoptInputMethodContext();
0215     } else {
0216         updateInputPanelState();
0217     }
0218 
0219     if (wasActive != isActive()) {
0220         Q_EMIT activeChanged(active);
0221     }
0222 }
0223 
0224 InputPanelV1Window *InputMethod::panel() const
0225 {
0226     return m_panel;
0227 }
0228 
0229 void InputMethod::setPanel(InputPanelV1Window *panel)
0230 {
0231     Q_ASSERT(panel->isInputMethod());
0232     if (m_panel) {
0233         qCWarning(KWIN_VIRTUALKEYBOARD) << "Replacing input panel" << m_panel << "with" << panel;
0234         disconnect(m_panel, nullptr, this, nullptr);
0235     }
0236 
0237     m_panel = panel;
0238     connect(panel->surface(), &SurfaceInterface::inputChanged, this, &InputMethod::updateInputPanelState);
0239     connect(panel, &QObject::destroyed, this, [this] {
0240         if (m_trackedWindow) {
0241             m_trackedWindow->setVirtualKeyboardGeometry({});
0242         }
0243     });
0244     connect(m_panel, &Window::frameGeometryChanged, this, &InputMethod::updateInputPanelState);
0245     connect(m_panel, &Window::windowHidden, this, &InputMethod::updateInputPanelState);
0246     connect(m_panel, &Window::windowClosed, this, &InputMethod::updateInputPanelState);
0247     connect(m_panel, &Window::windowShown, this, &InputMethod::visibleChanged);
0248     connect(m_panel, &Window::windowHidden, this, &InputMethod::visibleChanged);
0249     connect(m_panel, &Window::windowClosed, this, &InputMethod::visibleChanged);
0250     Q_EMIT visibleChanged();
0251     updateInputPanelState();
0252     Q_EMIT panelChanged();
0253 
0254     if (m_shouldShowPanel) {
0255         show();
0256     }
0257 }
0258 
0259 void InputMethod::setTrackedWindow(Window *trackedWindow)
0260 {
0261     // Reset the old window virtual keybaord geom if necessary
0262     // Old and new windows could be the same if focus moves between subsurfaces
0263     if (m_trackedWindow == trackedWindow) {
0264         return;
0265     }
0266     if (m_trackedWindow) {
0267         m_trackedWindow->setVirtualKeyboardGeometry(QRect());
0268         disconnect(m_trackedWindow, &Window::frameGeometryChanged, this, &InputMethod::updateInputPanelState);
0269     }
0270     m_trackedWindow = trackedWindow;
0271     m_shouldShowPanel = false;
0272     if (m_trackedWindow) {
0273         connect(m_trackedWindow, &Window::frameGeometryChanged, this, &InputMethod::updateInputPanelState, Qt::QueuedConnection);
0274     }
0275     updateInputPanelState();
0276 }
0277 
0278 void InputMethod::handleFocusedSurfaceChanged()
0279 {
0280     auto seat = waylandServer()->seat();
0281     SurfaceInterface *focusedSurface = seat->focusedTextInputSurface();
0282 
0283     setTrackedWindow(waylandServer()->findWindow(focusedSurface));
0284 
0285     const auto client = focusedSurface ? focusedSurface->client() : nullptr;
0286     bool ret = seat->textInputV2()->clientSupportsTextInput(client)
0287             || seat->textInputV3()->clientSupportsTextInput(client);
0288     if (ret != m_activeClientSupportsTextInput) {
0289         m_activeClientSupportsTextInput = ret;
0290         Q_EMIT activeClientSupportsTextInputChanged();
0291     }
0292 }
0293 
0294 void InputMethod::surroundingTextChanged()
0295 {
0296     auto t2 = waylandServer()->seat()->textInputV2();
0297     auto t3 = waylandServer()->seat()->textInputV3();
0298     auto inputContext = waylandServer()->inputMethod()->context();
0299     if (!inputContext) {
0300         return;
0301     }
0302     if (t2 && t2->isEnabled()) {
0303         inputContext->sendSurroundingText(t2->surroundingText(), t2->surroundingTextCursorPosition(), t2->surroundingTextSelectionAnchor());
0304         return;
0305     }
0306     if (t3 && t3->isEnabled()) {
0307         inputContext->sendSurroundingText(t3->surroundingText(), t3->surroundingTextCursorPosition(), t3->surroundingTextSelectionAnchor());
0308         return;
0309     }
0310 }
0311 
0312 void InputMethod::contentTypeChanged()
0313 {
0314     auto t1 = waylandServer()->seat()->textInputV1();
0315     auto t2 = waylandServer()->seat()->textInputV2();
0316     auto t3 = waylandServer()->seat()->textInputV3();
0317     auto inputContext = waylandServer()->inputMethod()->context();
0318     if (!inputContext) {
0319         return;
0320     }
0321     if (t1 && t1->isEnabled()) {
0322         inputContext->sendContentType(t1->contentHints(), t1->contentPurpose());
0323     }
0324     if (t2 && t2->isEnabled()) {
0325         inputContext->sendContentType(t2->contentHints(), t2->contentPurpose());
0326     }
0327     if (t3 && t3->isEnabled()) {
0328         inputContext->sendContentType(t3->contentHints(), t3->contentPurpose());
0329     }
0330 }
0331 
0332 void InputMethod::textInputInterfaceV1Reset()
0333 {
0334     if (!m_enabled) {
0335         return;
0336     }
0337     auto t1 = waylandServer()->seat()->textInputV1();
0338     auto inputContext = waylandServer()->inputMethod()->context();
0339     if (!inputContext) {
0340         return;
0341     }
0342     if (!t1 || !t1->isEnabled()) {
0343         return;
0344     }
0345     inputContext->sendReset();
0346 }
0347 
0348 void InputMethod::invokeAction(quint32 button, quint32 index)
0349 {
0350     if (!m_enabled) {
0351         return;
0352     }
0353     auto t1 = waylandServer()->seat()->textInputV1();
0354     auto inputContext = waylandServer()->inputMethod()->context();
0355     if (!inputContext) {
0356         return;
0357     }
0358     if (!t1 || !t1->isEnabled()) {
0359         return;
0360     }
0361     inputContext->sendInvokeAction(button, index);
0362 }
0363 
0364 void InputMethod::textInputInterfaceV1StateUpdated(quint32 serial)
0365 {
0366     if (!m_enabled) {
0367         return;
0368     }
0369     auto t1 = waylandServer()->seat()->textInputV1();
0370     auto inputContext = waylandServer()->inputMethod()->context();
0371     if (!inputContext) {
0372         return;
0373     }
0374     if (!t1 || !t1->isEnabled()) {
0375         return;
0376     }
0377     inputContext->sendCommitState(serial);
0378 }
0379 
0380 void InputMethod::textInputInterfaceV2StateUpdated(quint32 serial, KWaylandServer::TextInputV2Interface::UpdateReason reason)
0381 {
0382     if (!m_enabled) {
0383         return;
0384     }
0385 
0386     auto t2 = waylandServer()->seat()->textInputV2();
0387     auto inputContext = waylandServer()->inputMethod()->context();
0388     if (!inputContext) {
0389         return;
0390     }
0391     if (!t2 || !t2->isEnabled()) {
0392         return;
0393     }
0394     if (m_panel && shouldShowOnActive()) {
0395         m_panel->allow();
0396     }
0397     switch (reason) {
0398     case KWaylandServer::TextInputV2Interface::UpdateReason::StateChange:
0399         break;
0400     case KWaylandServer::TextInputV2Interface::UpdateReason::StateEnter:
0401     case KWaylandServer::TextInputV2Interface::UpdateReason::StateFull:
0402         adoptInputMethodContext();
0403         break;
0404     case KWaylandServer::TextInputV2Interface::UpdateReason::StateReset:
0405         inputContext->sendReset();
0406         break;
0407     }
0408 }
0409 
0410 void InputMethod::textInputInterfaceV1EnabledChanged()
0411 {
0412     if (!m_enabled) {
0413         return;
0414     }
0415 
0416     refreshActive();
0417 }
0418 
0419 void InputMethod::textInputInterfaceV2EnabledChanged()
0420 {
0421     if (!m_enabled) {
0422         return;
0423     }
0424 
0425     refreshActive();
0426 }
0427 
0428 void InputMethod::textInputInterfaceV3EnabledChanged()
0429 {
0430     if (!m_enabled) {
0431         return;
0432     }
0433 
0434     auto t3 = waylandServer()->seat()->textInputV3();
0435     refreshActive();
0436     if (t3->isEnabled()) {
0437         show();
0438     } else {
0439         // reset value of preedit when textinput is disabled
0440         resetPendingPreedit();
0441     }
0442     auto context = waylandServer()->inputMethod()->context();
0443     if (context) {
0444         context->sendReset();
0445         adoptInputMethodContext();
0446     }
0447 }
0448 
0449 void InputMethod::stateCommitted(uint32_t serial)
0450 {
0451     if (!isEnabled()) {
0452         return;
0453     }
0454     TextInputV3Interface *textInputV3 = waylandServer()->seat()->textInputV3();
0455     if (!textInputV3) {
0456         return;
0457     }
0458 
0459     if (auto inputContext = waylandServer()->inputMethod()->context()) {
0460         inputContext->sendCommitState(serial);
0461     }
0462 }
0463 
0464 void InputMethod::setEnabled(bool enabled)
0465 {
0466     if (m_enabled == enabled) {
0467         return;
0468     }
0469     m_enabled = enabled;
0470     Q_EMIT enabledChanged(m_enabled);
0471 
0472     // send OSD message
0473     QDBusMessage msg = QDBusMessage::createMethodCall(
0474         QStringLiteral("org.kde.plasmashell"),
0475         QStringLiteral("/org/kde/osdService"),
0476         QStringLiteral("org.kde.osdService"),
0477         QStringLiteral("virtualKeyboardEnabledChanged"));
0478     msg.setArguments({enabled});
0479     QDBusConnection::sessionBus().asyncCall(msg);
0480     if (!m_enabled) {
0481         hide();
0482         stopInputMethod();
0483     } else {
0484         startInputMethod();
0485     }
0486     // save value into config
0487     kwinApp()->config()->group("Wayland").writeEntry("VirtualKeyboardEnabled", m_enabled);
0488     kwinApp()->config()->sync();
0489 }
0490 
0491 static quint32 keysymToKeycode(quint32 sym)
0492 {
0493     switch (sym) {
0494     case XKB_KEY_BackSpace:
0495         return KEY_BACKSPACE;
0496     case XKB_KEY_Return:
0497         return KEY_ENTER;
0498     case XKB_KEY_Left:
0499         return KEY_LEFT;
0500     case XKB_KEY_Right:
0501         return KEY_RIGHT;
0502     case XKB_KEY_Up:
0503         return KEY_UP;
0504     case XKB_KEY_Down:
0505         return KEY_DOWN;
0506     default:
0507         return KEY_UNKNOWN;
0508     }
0509 }
0510 
0511 void InputMethod::keysymReceived(quint32 serial, quint32 time, quint32 sym, bool pressed, quint32 modifiers)
0512 {
0513     if (auto t1 = waylandServer()->seat()->textInputV1(); t1 && t1->isEnabled()) {
0514         if (pressed) {
0515             t1->keysymPressed(time, sym, modifiers);
0516         } else {
0517             t1->keysymReleased(time, sym, modifiers);
0518         }
0519         return;
0520     }
0521 
0522     auto t2 = waylandServer()->seat()->textInputV2();
0523     if (t2 && t2->isEnabled()) {
0524         if (pressed) {
0525             t2->keysymPressed(sym, modifiers);
0526         } else {
0527             t2->keysymReleased(sym, modifiers);
0528         }
0529         return;
0530     }
0531 
0532     KWaylandServer::KeyboardKeyState state;
0533     if (pressed) {
0534         state = KWaylandServer::KeyboardKeyState::Pressed;
0535     } else {
0536         state = KWaylandServer::KeyboardKeyState::Released;
0537     }
0538     waylandServer()->seat()->notifyKeyboardKey(keysymToKeycode(sym), state);
0539 }
0540 
0541 void InputMethod::commitString(qint32 serial, const QString &text)
0542 {
0543     if (auto t1 = waylandServer()->seat()->textInputV1(); t1 && t1->isEnabled()) {
0544         t1->commitString(text.toUtf8());
0545         t1->setPreEditCursor(0);
0546         t1->preEdit({}, {});
0547         return;
0548     }
0549     if (auto t2 = waylandServer()->seat()->textInputV2(); t2 && t2->isEnabled()) {
0550         t2->commitString(text.toUtf8());
0551         t2->setPreEditCursor(0);
0552         t2->preEdit({}, {});
0553         return;
0554     } else if (auto t3 = waylandServer()->seat()->textInputV3(); t3 && t3->isEnabled()) {
0555         t3->commitString(text.toUtf8());
0556         t3->done();
0557         return;
0558     } else {
0559         // The application has no way of communicating with the input method.
0560         // So instead, try to convert what we get from the input method into
0561         // keycodes and send those as fake input to the client.
0562         auto keys = textToKey(text);
0563         if (keys.empty()) {
0564             return;
0565         }
0566 
0567         // First, send all the extracted keys as pressed keys to the client.
0568         for (const auto &key : keys) {
0569             waylandServer()->seat()->notifyKeyboardKey(key, KWaylandServer::KeyboardKeyState::Pressed);
0570         }
0571 
0572         // Then, send key release for those keys in reverse.
0573         for (auto itr = keys.rbegin(); itr != keys.rend(); ++itr) {
0574             // Since we are faking key events, we do not have distinct press/release
0575             // events. So instead, just queue the button release so it gets sent
0576             // a few moments after the press.
0577             auto key = *itr;
0578             QMetaObject::invokeMethod(
0579                 this, [key]() {
0580                     waylandServer()->seat()->notifyKeyboardKey(key, KWaylandServer::KeyboardKeyState::Released);
0581                 },
0582                 Qt::QueuedConnection);
0583         }
0584     }
0585 }
0586 
0587 void InputMethod::deleteSurroundingText(int32_t index, uint32_t length)
0588 {
0589     // zwp_input_method_v1 Delete surrounding text interface is designed for text-input-v1.
0590     // The parameter has different meaning in text-input-v{2,3}.
0591     // Current cursor is at index 0.
0592     // The actualy deleted text range is [index, index + length].
0593     // In v{2,3}'s before/after style, text to be deleted with v{2,3} interface is [-before, after].
0594     // And before/after are all unsigned, which make it impossible to do certain things.
0595     // Those request will be ignored.
0596 
0597     // Verify we can handle such request.
0598     if (index > 0 || index + static_cast<ssize_t>(length) < 0) {
0599         return;
0600     }
0601     const quint32 before = -index;
0602     const quint32 after = index + length;
0603 
0604     auto t1 = waylandServer()->seat()->textInputV1();
0605     if (t1 && t1->isEnabled()) {
0606         t1->deleteSurroundingText(before, after);
0607     }
0608     auto t2 = waylandServer()->seat()->textInputV2();
0609     if (t2 && t2->isEnabled()) {
0610         t2->deleteSurroundingText(before, after);
0611     }
0612     auto t3 = waylandServer()->seat()->textInputV3();
0613     if (t3 && t3->isEnabled()) {
0614         t3->deleteSurroundingText(before, after);
0615         t3->done();
0616     }
0617 }
0618 
0619 void InputMethod::setCursorPosition(qint32 index, qint32 anchor)
0620 {
0621     auto t1 = waylandServer()->seat()->textInputV1();
0622     if (t1 && t1->isEnabled()) {
0623         t1->setCursorPosition(index, anchor);
0624     }
0625     auto t2 = waylandServer()->seat()->textInputV2();
0626     if (t2 && t2->isEnabled()) {
0627         t2->setCursorPosition(index, anchor);
0628     }
0629 }
0630 
0631 void InputMethod::setLanguage(uint32_t serial, const QString &language)
0632 {
0633     auto t1 = waylandServer()->seat()->textInputV1();
0634     if (t1 && t1->isEnabled()) {
0635         t1->setLanguage(language.toUtf8());
0636     }
0637     auto t2 = waylandServer()->seat()->textInputV2();
0638     if (t2 && t2->isEnabled()) {
0639         t2->setLanguage(language.toUtf8());
0640     }
0641 }
0642 
0643 void InputMethod::setTextDirection(uint32_t serial, Qt::LayoutDirection direction)
0644 {
0645     auto t1 = waylandServer()->seat()->textInputV1();
0646     if (t1 && t1->isEnabled()) {
0647         t1->setTextDirection(direction);
0648     }
0649     auto t2 = waylandServer()->seat()->textInputV2();
0650     if (t2 && t2->isEnabled()) {
0651         t2->setTextDirection(direction);
0652     }
0653 }
0654 
0655 void InputMethod::setPreeditCursor(qint32 index)
0656 {
0657     auto t1 = waylandServer()->seat()->textInputV1();
0658     if (t1 && t1->isEnabled()) {
0659         t1->setPreEditCursor(index);
0660     }
0661     auto t2 = waylandServer()->seat()->textInputV2();
0662     if (t2 && t2->isEnabled()) {
0663         t2->setPreEditCursor(index);
0664     }
0665     auto t3 = waylandServer()->seat()->textInputV3();
0666     if (t3 && t3->isEnabled()) {
0667         preedit.cursor = index;
0668     }
0669 }
0670 
0671 void InputMethod::setPreeditStyling(quint32 index, quint32 length, quint32 style)
0672 {
0673     auto t1 = waylandServer()->seat()->textInputV1();
0674     if (t1 && t1->isEnabled()) {
0675         t1->preEditStyling(index, length, style);
0676     }
0677     auto t2 = waylandServer()->seat()->textInputV2();
0678     if (t2 && t2->isEnabled()) {
0679         t2->preEditStyling(index, length, style);
0680     }
0681     auto t3 = waylandServer()->seat()->textInputV3();
0682     if (t3 && t3->isEnabled()) {
0683         // preedit style: highlight(4) or selection(6)
0684         if (style == 4 || style == 6) {
0685             preedit.highlightRanges.emplace_back(index, index + length);
0686         }
0687     }
0688 }
0689 
0690 void InputMethod::setPreeditString(uint32_t serial, const QString &text, const QString &commit)
0691 {
0692     auto t1 = waylandServer()->seat()->textInputV1();
0693     if (t1 && t1->isEnabled()) {
0694         t1->preEdit(text.toUtf8(), commit.toUtf8());
0695     }
0696     auto t2 = waylandServer()->seat()->textInputV2();
0697     if (t2 && t2->isEnabled()) {
0698         t2->preEdit(text.toUtf8(), commit.toUtf8());
0699     }
0700     auto t3 = waylandServer()->seat()->textInputV3();
0701     if (t3 && t3->isEnabled()) {
0702         preedit.text = text;
0703         if (!preedit.text.isEmpty()) {
0704             quint32 cursor = 0, cursorEnd = 0;
0705             if (preedit.cursor > 0) {
0706                 cursor = cursorEnd = preedit.cursor;
0707             }
0708             // Check if we can convert highlight style to a range of selection.
0709             if (!preedit.highlightRanges.empty()) {
0710                 std::sort(preedit.highlightRanges.begin(), preedit.highlightRanges.end());
0711                 // Check if starting point matches.
0712                 if (preedit.highlightRanges.front().first == cursor) {
0713                     quint32 end = preedit.highlightRanges.front().second;
0714                     bool nonContinousHighlight = false;
0715                     for (size_t i = 1; i < preedit.highlightRanges.size(); i++) {
0716                         if (end >= preedit.highlightRanges[i].first) {
0717                             end = std::max(end, preedit.highlightRanges[i].second);
0718                         } else {
0719                             nonContinousHighlight = true;
0720                             break;
0721                         }
0722                     }
0723                     if (!nonContinousHighlight) {
0724                         cursorEnd = end;
0725                     }
0726                 }
0727             }
0728 
0729             t3->sendPreEditString(preedit.text, cursor, cursorEnd);
0730         }
0731         t3->done();
0732     }
0733     resetPendingPreedit();
0734 }
0735 
0736 void InputMethod::key(quint32 /*serial*/, quint32 /*time*/, quint32 keyCode, bool pressed)
0737 {
0738     waylandServer()->seat()->notifyKeyboardKey(keyCode,
0739                                                pressed ? KWaylandServer::KeyboardKeyState::Pressed : KWaylandServer::KeyboardKeyState::Released);
0740 }
0741 
0742 void InputMethod::modifiers(quint32 serial, quint32 mods_depressed, quint32 mods_latched, quint32 mods_locked, quint32 group)
0743 {
0744     auto xkb = input()->keyboard()->xkb();
0745     xkb->updateModifiers(mods_depressed, mods_latched, mods_locked, group);
0746 }
0747 
0748 void InputMethod::forwardModifiers(ForwardModifiersForce force)
0749 {
0750     const bool sendModifiers = m_hasPendingModifiers || force == Force;
0751     m_hasPendingModifiers = false;
0752     if (!sendModifiers) {
0753         return;
0754     }
0755     auto xkb = input()->keyboard()->xkb();
0756     if (m_keyboardGrab) {
0757         m_keyboardGrab->sendModifiers(waylandServer()->display()->nextSerial(),
0758                                       xkb->modifierState().depressed,
0759                                       xkb->modifierState().latched,
0760                                       xkb->modifierState().locked,
0761                                       xkb->currentLayout());
0762     }
0763 }
0764 
0765 void InputMethod::adoptInputMethodContext()
0766 {
0767     auto inputContext = waylandServer()->inputMethod()->context();
0768 
0769     TextInputV1Interface *t1 = waylandServer()->seat()->textInputV1();
0770     TextInputV2Interface *t2 = waylandServer()->seat()->textInputV2();
0771     TextInputV3Interface *t3 = waylandServer()->seat()->textInputV3();
0772 
0773     if (t1 && t1->isEnabled()) {
0774         inputContext->sendSurroundingText(t1->surroundingText(), t1->surroundingTextCursorPosition(), t1->surroundingTextSelectionAnchor());
0775         inputContext->sendPreferredLanguage(t1->preferredLanguage());
0776         inputContext->sendContentType(t1->contentHints(), t2->contentPurpose());
0777         connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::language, this, &InputMethod::setLanguage);
0778         connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::textDirection, this, &InputMethod::setTextDirection);
0779     } else if (t2 && t2->isEnabled()) {
0780         inputContext->sendSurroundingText(t2->surroundingText(), t2->surroundingTextCursorPosition(), t2->surroundingTextSelectionAnchor());
0781         inputContext->sendPreferredLanguage(t2->preferredLanguage());
0782         inputContext->sendContentType(t2->contentHints(), t2->contentPurpose());
0783         connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::language, this, &InputMethod::setLanguage);
0784         connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::textDirection, this, &InputMethod::setTextDirection);
0785     } else if (t3 && t3->isEnabled()) {
0786         inputContext->sendSurroundingText(t3->surroundingText(), t3->surroundingTextCursorPosition(), t3->surroundingTextSelectionAnchor());
0787         inputContext->sendContentType(t3->contentHints(), t3->contentPurpose());
0788     } else {
0789         // When we have neither text-input-v2 nor text-input-v3 we can only send
0790         // fake key events, not more complex text. So ask the input method to
0791         // only send basic characters without any pre-editing.
0792         inputContext->sendContentType(KWaylandServer::TextInputContentHint::Latin, KWaylandServer::TextInputContentPurpose::Normal);
0793     }
0794 
0795     inputContext->sendCommitState(m_serial++);
0796 
0797     connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::keysym, this, &InputMethod::keysymReceived, Qt::UniqueConnection);
0798     connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::key, this, &InputMethod::key, Qt::UniqueConnection);
0799     connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::modifiers, this, &InputMethod::modifiers, Qt::UniqueConnection);
0800     connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::commitString, this, &InputMethod::commitString, Qt::UniqueConnection);
0801     connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::deleteSurroundingText, this, &InputMethod::deleteSurroundingText, Qt::UniqueConnection);
0802     connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::cursorPosition, this, &InputMethod::setCursorPosition, Qt::UniqueConnection);
0803     connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::preeditStyling, this, &InputMethod::setPreeditStyling, Qt::UniqueConnection);
0804     connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::preeditString, this, &InputMethod::setPreeditString, Qt::UniqueConnection);
0805     connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::preeditCursor, this, &InputMethod::setPreeditCursor, Qt::UniqueConnection);
0806     connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::keyboardGrabRequested, this, &InputMethod::installKeyboardGrab, Qt::UniqueConnection);
0807     connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::modifiersMap, this, &InputMethod::updateModifiersMap, Qt::UniqueConnection);
0808 }
0809 
0810 void InputMethod::updateInputPanelState()
0811 {
0812     if (!waylandServer()) {
0813         return;
0814     }
0815 
0816     auto t = waylandServer()->seat()->textInputV2();
0817 
0818     if (!t) {
0819         return;
0820     }
0821 
0822     if (m_panel && shouldShowOnActive()) {
0823         m_panel->allow();
0824     }
0825 
0826     QRectF overlap = QRectF(0, 0, 0, 0);
0827     if (m_trackedWindow) {
0828         const bool bottomKeyboard = m_panel && m_panel->mode() != InputPanelV1Window::Mode::Overlay && m_panel->isShown();
0829         m_trackedWindow->setVirtualKeyboardGeometry(bottomKeyboard ? m_panel->inputGeometry() : QRectF());
0830 
0831         if (m_panel && m_panel->mode() != InputPanelV1Window::Mode::Overlay) {
0832             overlap = m_trackedWindow->frameGeometry() & m_panel->inputGeometry();
0833             overlap.moveTo(m_trackedWindow->mapToLocal(overlap.topLeft()));
0834         }
0835     }
0836     t->setInputPanelState(m_panel && m_panel->isShown(), overlap.toRect());
0837 }
0838 
0839 void InputMethod::setInputMethodCommand(const QString &command)
0840 {
0841     if (m_inputMethodCommand == command) {
0842         return;
0843     }
0844 
0845     m_inputMethodCommand = command;
0846 
0847     if (m_enabled) {
0848         startInputMethod();
0849     }
0850     Q_EMIT availableChanged();
0851 }
0852 
0853 void InputMethod::stopInputMethod()
0854 {
0855     if (!m_inputMethodProcess) {
0856         return;
0857     }
0858     disconnect(m_inputMethodProcess, nullptr, this, nullptr);
0859 
0860     m_inputMethodProcess->terminate();
0861     if (!m_inputMethodProcess->waitForFinished()) {
0862         m_inputMethodProcess->kill();
0863         m_inputMethodProcess->waitForFinished();
0864     }
0865     m_inputMethodProcess->deleteLater();
0866     m_inputMethodProcess = nullptr;
0867 
0868     waylandServer()->destroyInputMethodConnection();
0869 }
0870 
0871 void InputMethod::startInputMethod()
0872 {
0873     stopInputMethod();
0874     if (m_inputMethodCommand.isEmpty() || kwinApp()->isTerminating()) {
0875         return;
0876     }
0877 
0878     QStringList arguments = KShell::splitArgs(m_inputMethodCommand);
0879     if (arguments.isEmpty()) {
0880         qWarning("Failed to launch the input method server: %s is an invalid command", qPrintable(m_inputMethodCommand));
0881         return;
0882     }
0883 
0884     const QString program = arguments.takeFirst();
0885     int socket = waylandServer()->createInputMethodConnection();
0886     if (socket < 0) {
0887         qWarning("Failed to create the input method connection");
0888         return;
0889     }
0890     socket = dup(socket);
0891 
0892     QProcessEnvironment environment = kwinApp()->processStartupEnvironment();
0893     environment.insert(QStringLiteral("WAYLAND_SOCKET"), QByteArray::number(socket));
0894     environment.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("wayland"));
0895     // When we use Maliit as virtual keyboard, we want KWin to handle the animation
0896     // since that works a lot better. So we need to tell Maliit to not do client side
0897     // animation.
0898     environment.insert(QStringLiteral("MALIIT_ENABLE_ANIMATIONS"), "0");
0899 
0900     m_inputMethodProcess = new QProcess(this);
0901     m_inputMethodProcess->setProcessChannelMode(QProcess::ForwardedErrorChannel);
0902     m_inputMethodProcess->setProcessEnvironment(environment);
0903     m_inputMethodProcess->setProgram(program);
0904     m_inputMethodProcess->setArguments(arguments);
0905     m_inputMethodProcess->start();
0906     close(socket);
0907     connect(m_inputMethodProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus) {
0908         if (exitStatus == QProcess::CrashExit) {
0909             m_inputMethodCrashes++;
0910             m_inputMethodCrashTimer.start();
0911             qWarning() << "Input Method crashed" << m_inputMethodProcess->program() << m_inputMethodProcess->arguments() << exitCode << exitStatus;
0912             if (m_inputMethodCrashes < 5) {
0913                 startInputMethod();
0914             } else {
0915                 qWarning() << "Input Method keeps crashing, please fix" << m_inputMethodProcess->program() << m_inputMethodProcess->arguments();
0916                 stopInputMethod();
0917             }
0918         }
0919     });
0920 }
0921 bool InputMethod::isActive() const
0922 {
0923     return waylandServer()->inputMethod()->context();
0924 }
0925 
0926 KWaylandServer::InputMethodGrabV1 *InputMethod::keyboardGrab()
0927 {
0928     return isActive() ? m_keyboardGrab : nullptr;
0929 }
0930 
0931 void InputMethod::installKeyboardGrab(KWaylandServer::InputMethodGrabV1 *keyboardGrab)
0932 {
0933     auto xkb = input()->keyboard()->xkb();
0934     m_keyboardGrab = keyboardGrab;
0935     keyboardGrab->sendKeymap(xkb->keymapContents());
0936     forwardModifiers(Force);
0937 }
0938 
0939 void InputMethod::updateModifiersMap(const QByteArray &modifiers)
0940 {
0941     TextInputV2Interface *t2 = waylandServer()->seat()->textInputV2();
0942 
0943     if (t2 && t2->isEnabled()) {
0944         t2->setModifiersMap(modifiers);
0945     }
0946 }
0947 
0948 bool InputMethod::isVisible() const
0949 {
0950     return m_panel && m_panel->isShown() && m_panel->readyForPainting();
0951 }
0952 
0953 bool InputMethod::isAvailable() const
0954 {
0955     return !m_inputMethodCommand.isEmpty();
0956 }
0957 
0958 void InputMethod::resetPendingPreedit()
0959 {
0960     preedit.text = QString();
0961     preedit.cursor = 0;
0962     preedit.highlightRanges.clear();
0963 }
0964 
0965 bool InputMethod::activeClientSupportsTextInput() const
0966 {
0967     return m_activeClientSupportsTextInput;
0968 }
0969 
0970 void InputMethod::forceActivate()
0971 {
0972     setActive(true);
0973     show();
0974 }
0975 
0976 void InputMethod::textInputInterfaceV3EnableRequested()
0977 {
0978     refreshActive();
0979     show();
0980 }
0981 }