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 }