11#include <config-kwin.h>
21#if KWIN_BUILD_SCREENLOCKER
35#include <KLocalizedString>
39#include <QDBusConnection>
40#include <QDBusMessage>
41#include <QDBusPendingCall>
45#include <linux/input-event-codes.h>
47#include <xkbcommon/xkbcommon-keysyms.h>
52static std::vector<quint32> textToKey(
const QString &text)
58 auto sequence = QKeySequence::fromString(text);
59 if (sequence.isEmpty()) {
63 const QList<int> syms(KKeyServer::keyQtToSymXs(sequence[0]));
68 std::optional<int> keyCode;
69 for (
int sym : syms) {
81 return {KEY_LEFTSHIFT, quint32(keyCode.value())};
84 return {quint32(keyCode.value())};
89 m_enabled = kwinApp()->config()->group(QStringLiteral(
"Wayland")).readEntry(
"VirtualKeyboardEnabled",
true);
108 m_inputMethodCrashTimer.setInterval(20000);
109 m_inputMethodCrashTimer.setSingleShot(
true);
110 connect(&m_inputMethodCrashTimer, &QTimer::timeout,
this, [
this] {
111 m_inputMethodCrashes = 0;
113#if KWIN_BUILD_SCREENLOCKER
118 qCDebug(KWIN_VIRTUALKEYBOARD) <<
"Registering the DBus interface";
153 m_hasPendingModifiers =
true;
160 m_shouldShowPanel =
true;
163 updateInputPanelState();
171 adoptInputMethodContext();
178 m_shouldShowPanel =
false;
181 updateInputPanelState();
187 static bool alwaysShowIm = qEnvironmentVariableIntValue(
"KWIN_IM_SHOW_ALWAYS") != 0;
192void InputMethod::refreshActive()
195 auto t1 = seat->textInputV1();
196 auto t2 = seat->textInputV2();
197 auto t3 = seat->textInputV3();
200 if (
auto focusedSurface = seat->focusedTextInputSurface()) {
201 auto client = focusedSurface->client();
202 if ((t1->clientSupportsTextInput(client) && t1->isEnabled()) || (t2->clientSupportsTextInput(client) && t2->isEnabled()) || (t3->clientSupportsTextInput(client) && t3->isEnabled())) {
213 if (wasActive && !active) {
225 adoptInputMethodContext();
227 updateInputPanelState();
244 qCWarning(KWIN_VIRTUALKEYBOARD) <<
"Replacing input panel" << m_panel <<
"with" <<
panel;
245 disconnect(m_panel,
nullptr,
this,
nullptr);
250 if (m_trackedWindow) {
251 m_trackedWindow->setVirtualKeyboardGeometry({});
256 connect(m_panel, &
Window::closed,
this, &InputMethod::updateInputPanelState);
261 updateInputPanelState();
264 if (m_shouldShowPanel) {
269void InputMethod::setTrackedWindow(
Window *trackedWindow)
273 if (m_trackedWindow == trackedWindow) {
276 if (m_trackedWindow) {
277 m_trackedWindow->setVirtualKeyboardGeometry(QRect());
280 m_trackedWindow = trackedWindow;
281 m_shouldShowPanel =
false;
282 if (m_trackedWindow) {
285 updateInputPanelState();
288void InputMethod::handleFocusedSurfaceChanged()
291 SurfaceInterface *focusedSurface =
seat->focusedTextInputSurface();
293 setTrackedWindow(
waylandServer()->findWindow(focusedSurface));
295 const auto client = focusedSurface ? focusedSurface->client() :
nullptr;
296 bool ret =
seat->textInputV2()->clientSupportsTextInput(client)
297 ||
seat->textInputV3()->clientSupportsTextInput(client);
298 if (ret != m_activeClientSupportsTextInput) {
299 m_activeClientSupportsTextInput = ret;
304void InputMethod::surroundingTextChanged()
312 if (t2 && t2->isEnabled()) {
313 inputContext->
sendSurroundingText(t2->surroundingText(), t2->surroundingTextCursorPosition(), t2->surroundingTextSelectionAnchor());
316 if (t3 && t3->isEnabled()) {
317 inputContext->sendSurroundingText(t3->surroundingText(), t3->surroundingTextCursorPosition(), t3->surroundingTextSelectionAnchor());
322void InputMethod::contentTypeChanged()
331 if (t1 && t1->isEnabled()) {
332 inputContext->
sendContentType(t1->contentHints(), t1->contentPurpose());
334 if (t2 && t2->isEnabled()) {
335 inputContext->sendContentType(t2->contentHints(), t2->contentPurpose());
337 if (t3 && t3->isEnabled()) {
338 inputContext->sendContentType(t3->contentHints(), t3->contentPurpose());
342void InputMethod::textInputInterfaceV1Reset()
352 if (!t1 || !t1->isEnabled()) {
358void InputMethod::invokeAction(quint32 button, quint32 index)
368 if (!t1 || !t1->isEnabled()) {
374void InputMethod::textInputInterfaceV1StateUpdated(quint32 serial)
384 if (!t1 || !t1->isEnabled()) {
401 if (!t2 || !t2->isEnabled()) {
412 adoptInputMethodContext();
415 inputContext->sendReset();
420void InputMethod::textInputInterfaceV1EnabledChanged()
429void InputMethod::textInputInterfaceV2EnabledChanged()
438void InputMethod::textInputInterfaceV3EnabledChanged()
446 if (t3->isEnabled()) {
450 resetPendingPreedit();
455 adoptInputMethodContext();
459void InputMethod::stateCommitted(uint32_t serial)
470 inputContext->sendCommitState(serial);
476 if (m_enabled == enabled) {
483 QDBusMessage msg = QDBusMessage::createMethodCall(
484 QStringLiteral(
"org.kde.plasmashell"),
485 QStringLiteral(
"/org/kde/osdService"),
486 QStringLiteral(
"org.kde.osdService"),
487 QStringLiteral(
"virtualKeyboardEnabledChanged"));
488 msg.setArguments({enabled});
489 QDBusConnection::sessionBus().asyncCall(msg);
497 kwinApp()->config()->group(QStringLiteral(
"Wayland")).writeEntry(
"VirtualKeyboardEnabled", m_enabled);
498 kwinApp()->config()->sync();
501static quint32 keysymToKeycode(quint32 sym)
504 case XKB_KEY_BackSpace:
505 return KEY_BACKSPACE;
521void InputMethod::keysymReceived(quint32 serial, quint32 time, quint32 sym,
bool pressed, quint32 modifiers)
525 t1->keysymPressed(time, sym, modifiers);
527 t1->keysymReleased(time, sym, modifiers);
533 if (t2 && t2->isEnabled()) {
537 t2->keysymReleased(sym, modifiers);
551void InputMethod::commitString(qint32 serial,
const QString &text)
554 t1->commitString(
text.toUtf8());
555 t1->setPreEditCursor(0);
560 t2->commitString(
text.toUtf8());
561 t2->setPreEditCursor(0);
564 }
else if (
auto t3 =
waylandServer()->
seat()->textInputV3(); t3 && t3->isEnabled()) {
565 t3->commitString(
text.toUtf8());
572 auto keys = textToKey(
text);
578 for (
const auto &key : keys) {
583 for (
auto itr = keys.rbegin(); itr != keys.rend(); ++itr) {
588 QMetaObject::invokeMethod(
592 Qt::QueuedConnection);
597void InputMethod::deleteSurroundingText(int32_t index, uint32_t length)
608 if (index > 0 || index +
static_cast<ssize_t
>(length) < 0) {
611 const quint32 before = -index;
612 const quint32 after = index + length;
615 if (t1 && t1->isEnabled()) {
619 if (t2 && t2->isEnabled()) {
623 if (t3 && t3->isEnabled()) {
629void InputMethod::setCursorPosition(qint32 index, qint32 anchor)
632 if (t1 && t1->isEnabled()) {
636 if (t2 && t2->isEnabled()) {
641void InputMethod::setLanguage(uint32_t serial,
const QString &language)
644 if (t1 && t1->isEnabled()) {
648 if (t2 && t2->isEnabled()) {
653void InputMethod::setTextDirection(uint32_t serial, Qt::LayoutDirection direction)
656 if (t1 && t1->isEnabled()) {
660 if (t2 && t2->isEnabled()) {
665void InputMethod::setPreeditCursor(qint32 index)
668 if (t1 && t1->isEnabled()) {
672 if (t2 && t2->isEnabled()) {
676 if (t3 && t3->isEnabled()) {
677 preedit.cursor = index;
681void InputMethod::setPreeditStyling(quint32 index, quint32 length, quint32 style)
684 if (t1 && t1->isEnabled()) {
688 if (t2 && t2->isEnabled()) {
692 if (t3 && t3->isEnabled()) {
694 if (style == 4 || style == 6) {
695 preedit.highlightRanges.emplace_back(index, index + length);
700void InputMethod::setPreeditString(uint32_t serial,
const QString &text,
const QString &commit)
703 if (t1 && t1->isEnabled()) {
707 if (t2 && t2->isEnabled()) {
711 if (t3 && t3->isEnabled()) {
713 if (!preedit.text.isEmpty()) {
714 quint32
cursor = 0, cursorEnd = 0;
715 if (preedit.cursor > 0) {
716 cursor = cursorEnd = preedit.cursor;
719 if (!preedit.highlightRanges.empty()) {
720 std::sort(preedit.highlightRanges.begin(), preedit.highlightRanges.end());
722 if (preedit.highlightRanges.front().first ==
cursor) {
723 quint32 end = preedit.highlightRanges.front().second;
724 bool nonContinousHighlight =
false;
725 for (
size_t i = 1; i < preedit.highlightRanges.size(); i++) {
726 if (end >= preedit.highlightRanges[i].first) {
727 end = std::max(end, preedit.highlightRanges[i].second);
729 nonContinousHighlight =
true;
733 if (!nonContinousHighlight) {
739 t3->sendPreEditString(preedit.text,
cursor, cursorEnd);
743 resetPendingPreedit();
746void InputMethod::key(quint32 , quint32 , quint32 keyCode,
bool pressed)
752void InputMethod::modifiers(quint32 serial, quint32 mods_depressed, quint32 mods_latched, quint32 mods_locked, quint32 group)
755 xkb->
updateModifiers(mods_depressed, mods_latched, mods_locked, group);
760 const bool sendModifiers = m_hasPendingModifiers || force ==
Force;
761 m_hasPendingModifiers =
false;
762 if (!sendModifiers) {
766 if (m_keyboardGrab) {
767 m_keyboardGrab->sendModifiers(
waylandServer()->display()->nextSerial(),
768 xkb->modifierState().depressed,
769 xkb->modifierState().latched,
770 xkb->modifierState().locked,
771 xkb->currentLayout());
775void InputMethod::adoptInputMethodContext()
805 inputContext->sendCommitState(m_serial++);
820void InputMethod::updateInputPanelState()
836 QRectF overlap = QRectF(0, 0, 0, 0);
837 if (m_trackedWindow) {
839 m_trackedWindow->setVirtualKeyboardGeometry(bottomKeyboard ? m_panel->frameGeometry() : QRectF());
842 overlap = m_trackedWindow->frameGeometry() & m_panel->frameGeometry();
843 overlap.moveTo(m_trackedWindow->mapToLocal(overlap.topLeft()));
846 t->setInputPanelState(m_panel && m_panel->isShown(), overlap.toRect());
851 if (m_inputMethodCommand == command) {
855 m_inputMethodCommand = command;
863void InputMethod::stopInputMethod()
865 if (!m_inputMethodProcess) {
868 disconnect(m_inputMethodProcess,
nullptr,
this,
nullptr);
870 m_inputMethodProcess->terminate();
871 if (!m_inputMethodProcess->waitForFinished()) {
872 m_inputMethodProcess->kill();
873 m_inputMethodProcess->waitForFinished();
875 m_inputMethodProcess->deleteLater();
876 m_inputMethodProcess =
nullptr;
881void InputMethod::startInputMethod()
884 if (m_inputMethodCommand.isEmpty() || kwinApp()->isTerminating()) {
888 QStringList arguments = KShell::splitArgs(m_inputMethodCommand);
889 if (arguments.isEmpty()) {
890 qWarning(
"Failed to launch the input method server: %s is an invalid command", qPrintable(m_inputMethodCommand));
894 const QString program = arguments.takeFirst();
897 qWarning(
"Failed to create the input method connection");
900 socket = dup(socket);
902 QProcessEnvironment environment = kwinApp()->processStartupEnvironment();
903 environment.insert(QStringLiteral(
"WAYLAND_SOCKET"), QByteArray::number(socket));
904 environment.insert(QStringLiteral(
"QT_QPA_PLATFORM"), QStringLiteral(
"wayland"));
908 environment.insert(QStringLiteral(
"MALIIT_ENABLE_ANIMATIONS"),
"0");
910 m_inputMethodProcess =
new QProcess(
this);
911 m_inputMethodProcess->setProcessChannelMode(QProcess::ForwardedErrorChannel);
912 m_inputMethodProcess->setProcessEnvironment(environment);
913 m_inputMethodProcess->setProgram(program);
914 m_inputMethodProcess->setArguments(arguments);
915 m_inputMethodProcess->start();
917 connect(m_inputMethodProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
this, [
this](
int exitCode, QProcess::ExitStatus exitStatus) {
918 if (exitStatus == QProcess::CrashExit) {
919 m_inputMethodCrashes++;
920 m_inputMethodCrashTimer.start();
921 qWarning() <<
"Input Method crashed" << m_inputMethodProcess->program() << m_inputMethodProcess->arguments() << exitCode << exitStatus;
922 if (m_inputMethodCrashes < 5) {
925 qWarning() <<
"Input Method keeps crashing, please fix" << m_inputMethodProcess->program() << m_inputMethodProcess->arguments();
938 return isActive() ? m_keyboardGrab :
nullptr;
949void InputMethod::updateModifiersMap(
const QByteArray &modifiers)
953 if (t2 && t2->isEnabled()) {
960 return m_panel && m_panel->isShown() && m_panel->readyForPainting();
965 return !m_inputMethodCommand.isEmpty();
968void InputMethod::resetPendingPreedit()
970 preedit.text = QString();
972 preedit.highlightRanges.clear();
977 return m_activeClientSupportsTextInput;
986void InputMethod::textInputInterfaceV3EnableRequested()
993#include "moc_inputmethod.cpp"
void language(quint32 serial, const QString &language)
void deleteSurroundingText(qint32 index, quint32 length)
void sendSurroundingText(const QString &text, quint32 cursor, quint32 anchor)
void sendCommitState(quint32 serial)
void modifiersMap(const QByteArray &map)
void preeditCursor(qint32 index)
void sendInvokeAction(quint32 button, quint32 index)
void preeditString(quint32 serial, const QString &text, const QString &commit)
void key(quint32 serial, quint32 time, quint32 key, bool pressed)
void modifiers(quint32 serial, quint32 mods_depressed, quint32 mods_latched, quint32 mods_locked, quint32 group)
void cursorPosition(qint32 index, qint32 anchor)
void sendContentType(KWin::TextInputContentHints hint, KWin::TextInputContentPurpose purpose)
void keysym(quint32 serial, quint32 time, quint32 sym, bool pressed, quint32 modifiers)
void keyboardGrabRequested(InputMethodGrabV1 *keyboardGrab)
void textDirection(quint32 serial, Qt::LayoutDirection direction)
void preeditStyling(quint32 index, quint32 length, quint32 style)
void commitString(quint32 serial, const QString &text)
TextInputV1Interface * textInputV1() const
void notifyKeyboardKey(quint32 keyCode, KeyboardKeyState state)
void focusedTextInputSurfaceChanged()
TextInputV2Interface * textInputV2() const
TextInputV3Interface * textInputV3() const
Represent the Global for the interface.
Represent the Global for the interface.
Represent the Global for the interface.
Represents a generic Resource for a text input object.
qint32 surroundingTextSelectionAnchor() const
void requestShowInputPanel()
QString preferredLanguage() const
void stateUpdated(quint32 serial)
void surroundingTextChanged()
void preEditStyling(uint32_t index, uint32_t length, uint32_t style)
void setLanguage(const QString &languageTag)
qint32 surroundingTextCursorPosition() const
void setPreEditCursor(qint32 index)
void preEdit(const QString &text, const QString &commitText)
void contentTypeChanged()
void setTextDirection(Qt::LayoutDirection direction)
void setCursorPosition(qint32 index, qint32 anchor)
void invokeAction(quint32 button, quint32 index)
void deleteSurroundingText(quint32 beforeLength, quint32 afterLength)
void requestHideInputPanel()
TextInputContentHints contentHints() const
QString surroundingText() const
Represents a generic Resource for a text input object.
void preEdit(const QString &text, const QString &commitText)
void setPreEditCursor(qint32 index)
void deleteSurroundingText(quint32 beforeLength, quint32 afterLength)
TextInputContentHints contentHints() const
qint32 surroundingTextCursorPosition() const
void keysymPressed(quint32 keysym, quint32 modifiers=0)
QString surroundingText() const
void setTextDirection(Qt::LayoutDirection direction)
void requestShowInputPanel()
void preEditStyling(uint32_t index, uint32_t length, uint32_t style)
void setCursorPosition(qint32 index, qint32 anchor)
QString preferredLanguage() const
void stateUpdated(uint32_t serial, UpdateReason reason)
TextInputContentPurpose contentPurpose() const
void requestHideInputPanel()
void setModifiersMap(const QByteArray &modifiersMap)
void surroundingTextChanged()
void contentTypeChanged()
qint32 surroundingTextSelectionAnchor() const
void setLanguage(const QString &languageTag)
Represents a generic Resource for a text input object.A TextInputV3Interface gets created by the Text...
void stateCommitted(quint32 serial)
void deleteSurroundingText(quint32 beforeLength, quint32 afterLength)
TextInputContentPurpose contentPurpose() const
void surroundingTextChanged()
qint32 surroundingTextSelectionAnchor() const
QString surroundingText() const
qint32 surroundingTextCursorPosition() const
TextInputContentHints contentHints() const
void contentTypeChanged()
int createInputMethodConnection()
void destroyInputMethodConnection()
SeatInterface * seat() const
InputMethodV1Interface * inputMethod() const
void windowShown(KWin::Window *window)
void frameGeometryChanged(const QRectF &oldGeometry)
void windowHidden(KWin::Window *window)
void modifierStateChanged()
std::optional< int > keycodeFromKeysym(xkb_keysym_t keysym)
void updateModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group)
KWayland::Client::Seat * seat
MockInputMethod * inputMethod()
WaylandServer * waylandServer()
InputRedirection * input()