1 /*
2     KWin - the KDE window manager
3     This file is part of the KDE project.
4 
5     SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
6 
7     SPDX-License-Identifier: GPL-2.0-or-later
8 */
9 #include "inputmethod.h"
10 #include "abstract_client.h"
11 #include "virtualkeyboard_dbus.h"
12 #include "input.h"
13 #include "inputpanelv1client.h"
14 #include "keyboard_input.h"
15 #include "utils.h"
16 #include "screens.h"
17 #include "wayland_server.h"
18 #include "workspace.h"
19 #include "screenlockerwatcher.h"
20 #include "deleted.h"
21 
22 #include <KWaylandServer/display.h>
23 #include <KWaylandServer/keyboard_interface.h>
24 #include <KWaylandServer/seat_interface.h>
25 #include <KWaylandServer/textinput_v3_interface.h>
26 #include <KWaylandServer/surface_interface.h>
27 #include <KWaylandServer/inputmethod_v1_interface.h>
28 
29 #include <KShell>
30 #include <KStatusNotifierItem>
31 #include <KLocalizedString>
32 
33 #include <QDBusConnection>
34 #include <QDBusPendingCall>
35 #include <QDBusMessage>
36 #include <QMenu>
37 #include <QKeyEvent>
38 
39 #include <linux/input-event-codes.h>
40 #include <xkbcommon/xkbcommon-keysyms.h>
41 #include <unistd.h>
42 
43 using namespace KWaylandServer;
44 
45 namespace KWin
46 {
47 
KWIN_SINGLETON_FACTORY(InputMethod)48 KWIN_SINGLETON_FACTORY(InputMethod)
49 
50 InputMethod::InputMethod(QObject *parent)
51     : QObject(parent)
52 {
53     m_enabled = kwinApp()->config()->group("Wayland").readEntry("VirtualKeyboardEnabled", true);
54     // this is actually too late. Other processes are started before init,
55     // so might miss the availability of text input
56     // but without Workspace we don't have the window listed at all
57     if (workspace()) {
58         init();
59     } else {
60         connect(kwinApp(), &Application::workspaceCreated, this, &InputMethod::init);
61     }
62 }
63 
64 InputMethod::~InputMethod() = default;
65 
init()66 void InputMethod::init()
67 {
68     // Stop restarting the input method if it starts crashing very frequently
69     m_inputMethodCrashTimer.setInterval(20000);
70     m_inputMethodCrashTimer.setSingleShot(true);
71     connect(&m_inputMethodCrashTimer, &QTimer::timeout, this, [this] {
72         m_inputMethodCrashes = 0;
73     });
74     connect(ScreenLockerWatcher::self(), &ScreenLockerWatcher::aboutToLock, this, &InputMethod::hide);
75 
76     new VirtualKeyboardDBus(this);
77     qCDebug(KWIN_VIRTUALKEYBOARD) << "Registering the DBus interface";
78 
79     if (waylandServer()) {
80         new TextInputManagerV2Interface(waylandServer()->display());
81         new TextInputManagerV3Interface(waylandServer()->display());
82 
83         connect(workspace(), &Workspace::clientAdded, this, &InputMethod::clientAdded);
84         connect(waylandServer()->seat(), &SeatInterface::focusedTextInputSurfaceChanged, this, &InputMethod::handleFocusedSurfaceChanged);
85 
86         TextInputV2Interface *textInputV2 = waylandServer()->seat()->textInputV2();
87         connect(textInputV2, &TextInputV2Interface::requestShowInputPanel, this, &InputMethod::show);
88         connect(textInputV2, &TextInputV2Interface::requestHideInputPanel, this, &InputMethod::hide);
89         connect(textInputV2, &TextInputV2Interface::surroundingTextChanged, this, &InputMethod::surroundingTextChanged);
90         connect(textInputV2, &TextInputV2Interface::contentTypeChanged, this, &InputMethod::contentTypeChanged);
91         connect(textInputV2, &TextInputV2Interface::stateUpdated, this, &InputMethod::textInputInterfaceV2StateUpdated);
92 
93         TextInputV3Interface *textInputV3 = waylandServer()->seat()->textInputV3();
94         connect(textInputV3, &TextInputV3Interface::surroundingTextChanged, this, &InputMethod::surroundingTextChanged);
95         connect(textInputV3, &TextInputV3Interface::contentTypeChanged, this, &InputMethod::contentTypeChanged);
96         connect(textInputV3, &TextInputV3Interface::stateCommitted, this, &InputMethod::stateCommitted);
97 
98         if (m_enabled) {
99             connect(textInputV2, &TextInputV2Interface::enabledChanged, this, &InputMethod::textInputInterfaceV2EnabledChanged);
100             connect(textInputV3, &TextInputV3Interface::enabledChanged, this, &InputMethod::textInputInterfaceV3EnabledChanged);
101         }
102     }
103 }
104 
show()105 void InputMethod::show()
106 {
107     setActive(true);
108 }
109 
hide()110 void InputMethod::hide()
111 {
112     setActive(false);
113 }
114 
setActive(bool active)115 void InputMethod::setActive(bool active)
116 {
117     const bool wasActive = waylandServer()->inputMethod()->context();
118     if (wasActive && !active) {
119         waylandServer()->inputMethod()->sendDeactivate();
120     }
121 
122     if (active) {
123         if (!m_enabled) {
124             return;
125         }
126 
127         if (!wasActive) {
128             waylandServer()->inputMethod()->sendActivate();
129         } else {
130             waylandServer()->inputMethod()->context()->sendReset();
131         }
132         adoptInputMethodContext();
133     } else {
134         updateInputPanelState();
135     }
136 
137     if (wasActive != isActive()) {
138         Q_EMIT activeChanged(active);
139     }
140 }
141 
clientAdded(AbstractClient * _client)142 void InputMethod::clientAdded(AbstractClient *_client)
143 {
144     if (!_client->isInputMethod()) {
145         return;
146     }
147 
148     if (m_inputClient) {
149         qCWarning(KWIN_VIRTUALKEYBOARD) << "Replacing input client" << m_inputClient << "with" << _client;
150         disconnect(m_inputClient, nullptr, this, nullptr);
151     }
152 
153     const auto client = dynamic_cast<InputPanelV1Client *>(_client);
154     m_inputClient = client;
155     connect(client->surface(), &SurfaceInterface::inputChanged, this, &InputMethod::updateInputPanelState);
156     connect(client, &QObject::destroyed, this, [this] {
157         if (m_trackedClient) {
158             m_trackedClient->setVirtualKeyboardGeometry({});
159         }
160     });
161     connect(m_inputClient, &AbstractClient::frameGeometryChanged, this, &InputMethod::updateInputPanelState);
162     connect(m_inputClient, &AbstractClient::windowHidden, this, &InputMethod::updateInputPanelState);
163     connect(m_inputClient, &AbstractClient::windowClosed, this, &InputMethod::updateInputPanelState);
164     connect(m_inputClient, &AbstractClient::windowShown, this, &InputMethod::visibleChanged);
165     connect(m_inputClient, &AbstractClient::windowHidden, this, &InputMethod::visibleChanged);
166     connect(m_inputClient, &AbstractClient::windowClosed, this, &InputMethod::visibleChanged);
167     Q_EMIT visibleChanged();
168     updateInputPanelState();
169 }
170 
setTrackedClient(AbstractClient * trackedClient)171 void InputMethod::setTrackedClient(AbstractClient* trackedClient)
172 {
173     // Reset the old client virtual keybaord geom if necessary
174     // Old and new clients could be the same if focus moves between subsurfaces
175     if (m_trackedClient == trackedClient) {
176         return;
177     }
178     if (m_trackedClient) {
179         m_trackedClient->setVirtualKeyboardGeometry(QRect());
180         disconnect(m_trackedClient, &AbstractClient::frameGeometryChanged, this, &InputMethod::updateInputPanelState);
181     }
182     m_trackedClient = trackedClient;
183     if (m_trackedClient) {
184         connect(m_trackedClient, &AbstractClient::frameGeometryChanged, this, &InputMethod::updateInputPanelState, Qt::QueuedConnection);
185     }
186     updateInputPanelState();
187 }
188 
handleFocusedSurfaceChanged()189 void InputMethod::handleFocusedSurfaceChanged()
190 {
191     SurfaceInterface *focusedSurface = waylandServer()->seat()->focusedTextInputSurface();
192     setTrackedClient(waylandServer()->findClient(focusedSurface));
193     if (!focusedSurface) {
194         setActive(false);
195     }
196 }
197 
surroundingTextChanged()198 void InputMethod::surroundingTextChanged()
199 {
200     auto t2 = waylandServer()->seat()->textInputV2();
201     auto t3 = waylandServer()->seat()->textInputV3();
202     auto inputContext = waylandServer()->inputMethod()->context();
203     if (!inputContext) {
204         return;
205     }
206     if (t2 && t2->isEnabled()) {
207         inputContext->sendSurroundingText(t2->surroundingText(), t2->surroundingTextCursorPosition(), t2->surroundingTextSelectionAnchor());
208         return;
209     }
210     if (t3 && t3->isEnabled()) {
211         inputContext->sendSurroundingText(t3->surroundingText(), t3->surroundingTextCursorPosition(), t3->surroundingTextSelectionAnchor());
212         return;
213     }
214 }
215 
contentTypeChanged()216 void InputMethod::contentTypeChanged()
217 {
218     auto t2 = waylandServer()->seat()->textInputV2();
219     auto t3 = waylandServer()->seat()->textInputV3();
220     auto inputContext = waylandServer()->inputMethod()->context();
221     if (!inputContext) {
222         return;
223     }
224     if (t2 && t2->isEnabled()) {
225         inputContext->sendContentType(t2->contentHints(), t2->contentPurpose());
226     }
227     if (t3 && t3->isEnabled()) {
228         inputContext->sendContentType(t3->contentHints(), t3->contentPurpose());
229     }
230 }
231 
textInputInterfaceV2StateUpdated(quint32 serial,KWaylandServer::TextInputV2Interface::UpdateReason reason)232 void InputMethod::textInputInterfaceV2StateUpdated(quint32 serial, KWaylandServer::TextInputV2Interface::UpdateReason reason)
233 {
234     if (!m_enabled) {
235         return;
236     }
237     Q_UNUSED(serial);
238 
239     auto t2 = waylandServer()->seat()->textInputV2();
240     auto inputContext = waylandServer()->inputMethod()->context();
241     if (!inputContext) {
242         return;
243     }
244     if (!t2 || !t2->isEnabled()) {
245         return;
246     }
247     switch (reason) {
248     case KWaylandServer::TextInputV2Interface::UpdateReason::StateChange:
249         break;
250     case KWaylandServer::TextInputV2Interface::UpdateReason::StateEnter:
251     case KWaylandServer::TextInputV2Interface::UpdateReason::StateFull:
252         adoptInputMethodContext();
253         break;
254     case KWaylandServer::TextInputV2Interface::UpdateReason::StateReset:
255         inputContext->sendReset();
256         break;
257     }
258 }
259 
textInputInterfaceV2EnabledChanged()260 void InputMethod::textInputInterfaceV2EnabledChanged()
261 {
262     if (!m_enabled) {
263         return;
264     }
265 
266     auto t = waylandServer()->seat()->textInputV2();
267     setActive(t->isEnabled());
268 }
269 
textInputInterfaceV3EnabledChanged()270 void InputMethod::textInputInterfaceV3EnabledChanged()
271 {
272     if (!m_enabled) {
273         return;
274     }
275 
276     auto t3 = waylandServer()->seat()->textInputV3();
277     setActive(t3->isEnabled());
278     if (!t3->isEnabled()) {
279         // reset value of preedit when textinput is disabled
280         preedit.text = QString();
281         preedit.begin = 0;
282         preedit.end = 0;
283     }
284     auto context = waylandServer()->inputMethod()->context();
285     if (context) {
286         context->sendReset();
287         adoptInputMethodContext();
288     }
289 }
290 
stateCommitted(uint32_t serial)291 void InputMethod::stateCommitted(uint32_t serial)
292 {
293     if (!isEnabled()) {
294         return;
295     }
296     TextInputV3Interface *textInputV3 = waylandServer()->seat()->textInputV3();
297     if (!textInputV3) {
298         return;
299     }
300 
301     if (auto inputContext = waylandServer()->inputMethod()->context()) {
302         inputContext->sendCommitState(serial);
303     }
304     setActive(textInputV3->isEnabled());
305 }
306 
setEnabled(bool enabled)307 void InputMethod::setEnabled(bool enabled)
308 {
309     if (m_enabled == enabled) {
310         return;
311     }
312     m_enabled = enabled;
313     Q_EMIT enabledChanged(m_enabled);
314 
315     // send OSD message
316     QDBusMessage msg = QDBusMessage::createMethodCall(
317         QStringLiteral("org.kde.plasmashell"),
318         QStringLiteral("/org/kde/osdService"),
319         QStringLiteral("org.kde.osdService"),
320         QStringLiteral("virtualKeyboardEnabledChanged")
321     );
322     msg.setArguments({enabled});
323     QDBusConnection::sessionBus().asyncCall(msg);
324     if (!m_enabled) {
325         hide();
326         stopInputMethod();
327     } else {
328         startInputMethod();
329     }
330     // save value into config
331     kwinApp()->config()->group("Wayland").writeEntry("VirtualKeyboardEnabled", m_enabled);
332     kwinApp()->config()->sync();
333 }
334 
keysymToKeycode(quint32 sym)335 static quint32 keysymToKeycode(quint32 sym)
336 {
337     switch(sym) {
338     case XKB_KEY_BackSpace:
339         return KEY_BACKSPACE;
340     case XKB_KEY_Return:
341         return KEY_ENTER;
342     case XKB_KEY_Left:
343         return KEY_LEFT;
344     case XKB_KEY_Right:
345         return KEY_RIGHT;
346     case XKB_KEY_Up:
347         return KEY_UP;
348     case XKB_KEY_Down:
349         return KEY_DOWN;
350     default:
351         return KEY_UNKNOWN;
352     }
353 }
354 
keysymReceived(quint32 serial,quint32 time,quint32 sym,bool pressed,Qt::KeyboardModifiers modifiers)355 void InputMethod::keysymReceived(quint32 serial, quint32 time, quint32 sym, bool pressed, Qt::KeyboardModifiers modifiers)
356 {
357     Q_UNUSED(serial)
358     Q_UNUSED(time)
359     auto t2 = waylandServer()->seat()->textInputV2();
360     if (t2 && t2->isEnabled()) {
361         if (pressed) {
362             t2->keysymPressed(sym, modifiers);
363         } else {
364             t2->keysymReleased(sym, modifiers);
365         }
366         return;
367     }
368     auto t3 = waylandServer()->seat()->textInputV3();
369     if (t3 && t3->isEnabled()) {
370         KWaylandServer::KeyboardKeyState state;
371         if (pressed) {
372             state = KWaylandServer::KeyboardKeyState::Pressed;
373         } else {
374             state = KWaylandServer::KeyboardKeyState::Released;
375         }
376         waylandServer()->seat()->notifyKeyboardKey(keysymToKeycode(sym), state);
377         return;
378     }
379 }
380 
commitString(qint32 serial,const QString & text)381 void InputMethod::commitString(qint32 serial, const QString &text)
382 {
383     Q_UNUSED(serial)
384     if (auto t2 = waylandServer()->seat()->textInputV2(); t2 && t2->isEnabled()) {
385         t2->commitString(text.toUtf8());
386         t2->preEdit({}, {});
387         return;
388     } else if (auto t3 = waylandServer()->seat()->textInputV3(); t3 && t3->isEnabled()) {
389         t3->commitString(text.toUtf8());
390         t3->done();
391         return;
392     } else {
393         qCWarning(KWIN_VIRTUALKEYBOARD) << "We have nobody to commit to!!!";
394     }
395 }
396 
deleteSurroundingText(int32_t index,uint32_t length)397 void InputMethod::deleteSurroundingText(int32_t index, uint32_t length)
398 {
399     auto t2 = waylandServer()->seat()->textInputV2();
400     if (t2 && t2->isEnabled()) {
401         t2->deleteSurroundingText(index, length);
402     }
403     auto t3 = waylandServer()->seat()->textInputV3();
404     if (t3 && t3->isEnabled()) {
405         t3->deleteSurroundingText(index, length);
406     }
407 }
408 
setCursorPosition(qint32 index,qint32 anchor)409 void InputMethod::setCursorPosition(qint32 index, qint32 anchor)
410 {
411     auto t2 = waylandServer()->seat()->textInputV2();
412     if (t2 && t2->isEnabled()) {
413         t2->setCursorPosition(index, anchor);
414     }
415 }
416 
setLanguage(uint32_t serial,const QString & language)417 void InputMethod::setLanguage(uint32_t serial, const QString &language)
418 {
419     Q_UNUSED(serial)
420     auto t2 = waylandServer()->seat()->textInputV2();
421     if (t2 && t2->isEnabled()) {
422         t2->setLanguage(language.toUtf8());
423     }
424 }
425 
setTextDirection(uint32_t serial,Qt::LayoutDirection direction)426 void InputMethod::setTextDirection(uint32_t serial, Qt::LayoutDirection direction)
427 {
428     Q_UNUSED(serial)
429     auto t2 = waylandServer()->seat()->textInputV2();
430     if (t2 && t2->isEnabled()) {
431         t2->setTextDirection(direction);
432     }
433 }
434 
setPreeditCursor(qint32 index)435 void InputMethod::setPreeditCursor(qint32 index)
436 {
437     auto t2 = waylandServer()->seat()->textInputV2();
438     if (t2 && t2->isEnabled()) {
439         t2->setPreEditCursor(index);
440     }
441     auto t3 = waylandServer()->seat()->textInputV3();
442     if (t3 && t3->isEnabled()) {
443         preedit.begin = index;
444         preedit.end = index;
445         t3->sendPreEditString(preedit.text, preedit.begin, preedit.end);
446     }
447 }
448 
449 
setPreeditString(uint32_t serial,const QString & text,const QString & commit)450 void InputMethod::setPreeditString(uint32_t serial, const QString &text, const QString &commit)
451 {
452     Q_UNUSED(serial)
453     auto t2 = waylandServer()->seat()->textInputV2();
454     if (t2 && t2->isEnabled()) {
455         t2->preEdit(text.toUtf8(), commit.toUtf8());
456     }
457     auto t3 = waylandServer()->seat()->textInputV3();
458     if (t3 && t3->isEnabled()) {
459         preedit.text = text;
460         t3->sendPreEditString(preedit.text, preedit.begin, preedit.end);
461     }
462 }
463 
key(quint32,quint32,quint32 keyCode,bool pressed)464 void InputMethod::key(quint32 /*serial*/, quint32 /*time*/, quint32 keyCode, bool pressed)
465 {
466     waylandServer()->seat()->notifyKeyboardKey(keyCode, pressed ? KWaylandServer::KeyboardKeyState::Pressed : KWaylandServer::KeyboardKeyState::Released);
467 }
468 
modifiers(quint32 serial,quint32 mods_depressed,quint32 mods_latched,quint32 mods_locked,quint32 group)469 void InputMethod::modifiers(quint32 serial, quint32 mods_depressed, quint32 mods_latched, quint32 mods_locked, quint32 group)
470 {
471     Q_UNUSED(serial)
472     auto xkb = input()->keyboard()->xkb();
473     xkb->updateModifiers(mods_depressed, mods_latched, mods_locked, group);
474 }
475 
adoptInputMethodContext()476 void InputMethod::adoptInputMethodContext()
477 {
478     auto inputContext = waylandServer()->inputMethod()->context();
479 
480     TextInputV2Interface *t2 = waylandServer()->seat()->textInputV2();
481     TextInputV3Interface *t3 = waylandServer()->seat()->textInputV3();
482 
483     if (t2 && t2->isEnabled()) {
484         inputContext->sendSurroundingText(t2->surroundingText(), t2->surroundingTextCursorPosition(), t2->surroundingTextSelectionAnchor());
485         inputContext->sendPreferredLanguage(t2->preferredLanguage());
486         inputContext->sendContentType(t2->contentHints(), t2->contentPurpose());
487         connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::language, this, &InputMethod::setLanguage);
488         connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::textDirection, this, &InputMethod::setTextDirection);
489     }
490 
491     if (t3 && t3->isEnabled()) {
492         inputContext->sendSurroundingText(t3->surroundingText(), t3->surroundingTextCursorPosition(), t3->surroundingTextSelectionAnchor());
493         inputContext->sendContentType(t3->contentHints(), t3->contentPurpose());
494     }
495     inputContext->sendCommitState(m_serial++);
496 
497     connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::keysym, this, &InputMethod::keysymReceived, Qt::UniqueConnection);
498     connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::key, this, &InputMethod::key, Qt::UniqueConnection);
499     connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::modifiers, this, &InputMethod::modifiers, Qt::UniqueConnection);
500     connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::commitString, this, &InputMethod::commitString, Qt::UniqueConnection);
501     connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::deleteSurroundingText, this, &InputMethod::deleteSurroundingText, Qt::UniqueConnection);
502     connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::cursorPosition, this, &InputMethod::setCursorPosition, Qt::UniqueConnection);
503     connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::preeditString, this, &InputMethod::setPreeditString, Qt::UniqueConnection);
504     connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::preeditCursor, this, &InputMethod::setPreeditCursor, Qt::UniqueConnection);
505     connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::keyboardGrabRequested, this, &InputMethod::installKeyboardGrab, Qt::UniqueConnection);
506 }
507 
updateInputPanelState()508 void InputMethod::updateInputPanelState()
509 {
510     if (!waylandServer()) {
511         return;
512     }
513 
514     auto t = waylandServer()->seat()->textInputV2();
515 
516     if (!t) {
517         return;
518     }
519 
520     QRect overlap = QRect(0, 0, 0, 0);
521     if (m_trackedClient) {
522         const bool bottomKeyboard = m_inputClient && m_inputClient->mode() != InputPanelV1Client::Overlay && m_inputClient->isShown(false);
523         m_trackedClient->setVirtualKeyboardGeometry(bottomKeyboard ? m_inputClient->inputGeometry() : QRect());
524 
525         if (m_inputClient) {
526             overlap = m_trackedClient->frameGeometry() & m_inputClient->inputGeometry();
527             overlap.moveTo(m_trackedClient->mapToLocal(overlap.topLeft()));
528         }
529     }
530     t->setInputPanelState(m_inputClient && m_inputClient->isShown(false), overlap);
531 }
532 
setInputMethodCommand(const QString & command)533 void InputMethod::setInputMethodCommand(const QString &command)
534 {
535     if (m_inputMethodCommand == command) {
536         return;
537     }
538 
539     m_inputMethodCommand = command;
540 
541     if (m_enabled) {
542         startInputMethod();
543     }
544     Q_EMIT availableChanged();
545 }
546 
stopInputMethod()547 void InputMethod::stopInputMethod()
548 {
549     if (!m_inputMethodProcess) {
550         return;
551     }
552     disconnect(m_inputMethodProcess, nullptr, this, nullptr);
553 
554     m_inputMethodProcess->terminate();
555     if (!m_inputMethodProcess->waitForFinished()) {
556         m_inputMethodProcess->kill();
557         m_inputMethodProcess->waitForFinished();
558     }
559     if (waylandServer()) {
560         waylandServer()->destroyInputMethodConnection();
561     }
562     m_inputMethodProcess->deleteLater();
563     m_inputMethodProcess = nullptr;
564 }
565 
startInputMethod()566 void InputMethod::startInputMethod()
567 {
568     stopInputMethod();
569     if (m_inputMethodCommand.isEmpty() || kwinApp()->isTerminating()) {
570         return;
571     }
572 
573     connect(waylandServer(), &WaylandServer::terminatingInternalClientConnection, this, &InputMethod::stopInputMethod, Qt::UniqueConnection);
574 
575     QStringList arguments = KShell::splitArgs(m_inputMethodCommand);
576     if (arguments.isEmpty()) {
577         qWarning("Failed to launch the input method server: %s is an invalid command", qPrintable(m_inputMethodCommand));
578         return;
579     }
580 
581     const QString program = arguments.takeFirst();
582     int socket = waylandServer()->createInputMethodConnection();
583     if (socket < 0) {
584         qWarning("Failed to create the input method connection");
585         return;
586     }
587     socket = dup(socket);
588 
589     QProcessEnvironment environment = kwinApp()->processStartupEnvironment();
590     environment.insert(QStringLiteral("WAYLAND_SOCKET"), QByteArray::number(socket));
591     environment.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("wayland"));
592     environment.remove("DISPLAY");
593     environment.remove("WAYLAND_DISPLAY");
594     environment.remove("XAUTHORITY");
595 
596     m_inputMethodProcess = new Process(this);
597     m_inputMethodProcess->setProcessChannelMode(QProcess::ForwardedErrorChannel);
598     m_inputMethodProcess->setProcessEnvironment(environment);
599     m_inputMethodProcess->setProgram(program);
600     m_inputMethodProcess->setArguments(arguments);
601     m_inputMethodProcess->start();
602     close(socket);
603     connect(m_inputMethodProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus) {
604         if (exitStatus == QProcess::CrashExit) {
605             m_inputMethodCrashes++;
606             m_inputMethodCrashTimer.start();
607             qWarning() << "Input Method crashed" << m_inputMethodProcess->program() << m_inputMethodProcess->arguments() << exitCode << exitStatus;
608             if (m_inputMethodCrashes < 5) {
609                 startInputMethod();
610             } else {
611                 qWarning() << "Input Method keeps crashing, please fix" << m_inputMethodProcess->program() << m_inputMethodProcess->arguments();
612                 stopInputMethod();
613             }
614         }
615     });
616 }
isActive() const617 bool InputMethod::isActive() const
618 {
619     return waylandServer()->inputMethod()->context();
620 }
621 
622 class InputKeyboardFilter : public InputEventFilter {
623 public:
InputKeyboardFilter(KWaylandServer::InputMethodGrabV1 * grab)624     InputKeyboardFilter(KWaylandServer::InputMethodGrabV1 *grab)
625         : m_keyboardGrab(grab)
626     {
627     }
628 
keyEvent(QKeyEvent * event)629     bool keyEvent(QKeyEvent *event) override {
630         if (event->isAutoRepeat()) {
631             return true;
632         }
633         auto newState = event->type() == QEvent::KeyPress ? KWaylandServer::KeyboardKeyState::Pressed : KWaylandServer::KeyboardKeyState::Released;
634         m_keyboardGrab->sendKey(waylandServer()->display()->nextSerial(), event->timestamp(), event->nativeScanCode(), newState);
635         return true;
636     }
637     InputMethodGrabV1 *const m_keyboardGrab;
638 };
639 
installKeyboardGrab(KWaylandServer::InputMethodGrabV1 * keyboardGrab)640 void InputMethod::installKeyboardGrab(KWaylandServer::InputMethodGrabV1 *keyboardGrab)
641 {
642     auto xkb = input()->keyboard()->xkb();
643     auto filter = new InputKeyboardFilter(keyboardGrab);
644     keyboardGrab->sendKeymap(xkb->keymapContents());
645     input()->prependInputEventFilter(filter);
646     connect(keyboardGrab, &QObject::destroyed, input(), [filter] {
647         input()->uninstallInputEventFilter(filter);
648     });
649 }
650 
isVisible() const651 bool InputMethod::isVisible() const
652 {
653     return m_inputClient && m_inputClient->isShown(false);
654 }
655 
isAvailable() const656 bool InputMethod::isAvailable() const
657 {
658     return !m_inputMethodCommand.isEmpty();
659 }
660 
661 }
662