File indexing completed on 2024-04-28 05:30:20

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