File indexing completed on 2024-11-10 04:57:48
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"