inputmethod.cpp 21.4 KB
Newer Older
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
1
2
3
/*
    KWin - the KDE window manager
    This file is part of the KDE project.
4

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
5
    SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
6

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
7
8
    SPDX-License-Identifier: GPL-2.0-or-later
*/
9
#include "inputmethod.h"
10
#include "abstract_client.h"
11
#include "virtualkeyboard_dbus.h"
12
#include "input.h"
13
#include "keyboard_input.h"
14
15
16
17
#include "utils.h"
#include "screens.h"
#include "wayland_server.h"
#include "workspace.h"
18
#include "screenlockerwatcher.h"
19
#include "deleted.h"
20

21
#include <KWaylandServer/display.h>
22
#include <KWaylandServer/keyboard_interface.h>
23
#include <KWaylandServer/seat_interface.h>
24
#include <KWaylandServer/textinput_v3_interface.h>
25
#include <KWaylandServer/surface_interface.h>
26
#include <KWaylandServer/inputmethod_v1_interface.h>
27

28
#include <KShell>
29
30
31
32
33
34
#include <KStatusNotifierItem>
#include <KLocalizedString>

#include <QDBusConnection>
#include <QDBusPendingCall>
#include <QDBusMessage>
35
#include <QMenu>
36

37
38
#include <linux/input-event-codes.h>
#include <xkbcommon/xkbcommon-keysyms.h>
39
#include <unistd.h>
40

41
using namespace KWaylandServer;
42
43
44
45

namespace KWin
{

46
KWIN_SINGLETON_FACTORY(InputMethod)
47

48
InputMethod::InputMethod(QObject *parent)
49
50
51
52
53
    : QObject(parent)
{
    // this is actually too late. Other processes are started before init,
    // so might miss the availability of text input
    // but without Workspace we don't have the window listed at all
54
55
56
57
58
    if (workspace()) {
        init();
    } else {
        connect(kwinApp(), &Application::workspaceCreated, this, &InputMethod::init);
    }
59
60
}

61
InputMethod::~InputMethod() = default;
62

63
void InputMethod::init()
64
{
65
66
67
68
69
70
    // Stop restarting the input method if it starts crashing very frequently
    m_inputMethodCrashTimer.setInterval(20000);
    m_inputMethodCrashTimer.setSingleShot(true);
    connect(&m_inputMethodCrashTimer, &QTimer::timeout, this, [this] {
        m_inputMethodCrashes = 0;
    });
71
    connect(ScreenLockerWatcher::self(), &ScreenLockerWatcher::aboutToLock, this, &InputMethod::hide);
72

73
    if (waylandServer()) {
74
        m_enabled = !input()->hasAlphaNumericKeyboard();
75
        qCDebug(KWIN_VIRTUALKEYBOARD) << "enabled by default: " << m_enabled;
76
77
        connect(input(), &InputRedirection::hasAlphaNumericKeyboardChanged, this,
            [this] (bool set) {
78
                qCDebug(KWIN_VIRTUALKEYBOARD) << "AlphaNumeric Keyboard changed:" << set << "toggling virtual keyboard.";
79
                setEnabled(!set);
80
81
82
83
            }
        );
    }

84
    qCDebug(KWIN_VIRTUALKEYBOARD) << "Registering the SNI";
85
86
87
88
89
90

    QMenu *sniMenu = new QMenu;
    sniMenu->addAction(i18n("Configure virtual keyboards..."), this, [] {
        QProcess::startDetached("systemsettings5", {"kcm_virtualkeyboard"});
    });

91
    m_sni = new KStatusNotifierItem(QStringLiteral("kwin-virtual-keyboard"), this);
92
    m_sni->setStandardActionsEnabled(false);
93
94
95
    m_sni->setCategory(KStatusNotifierItem::Hardware);
    m_sni->setStatus(KStatusNotifierItem::Passive);
    m_sni->setTitle(i18n("Virtual Keyboard"));
96
97
98
    m_sni->setToolTipTitle(i18n("Whether to show the virtual keyboard on demand."));
    m_sni->setStandardActionsEnabled(false);
    m_sni->setContextMenu(sniMenu);
99
100
101
102
103
104
    updateSni();
    connect(m_sni, &KStatusNotifierItem::activateRequested, this,
        [this] {
            setEnabled(!m_enabled);
        }
    );
105
    connect(this, &InputMethod::enabledChanged, this, &InputMethod::updateSni);
106

107
    new VirtualKeyboardDBus(this);
108
    qCDebug(KWIN_VIRTUALKEYBOARD) << "Registering the DBus interface";
109
110

    if (waylandServer()) {
111
112
        new TextInputManagerV2Interface(waylandServer()->display());
        new TextInputManagerV3Interface(waylandServer()->display());
113

114
115
        connect(workspace(), &Workspace::clientAdded, this, &InputMethod::clientAdded);
        connect(waylandServer()->seat(), &SeatInterface::focusedTextInputSurfaceChanged, this, &InputMethod::handleFocusedSurfaceChanged);
116
117

        TextInputV2Interface *textInputV2 = waylandServer()->seat()->textInputV2();
118
119
120
121
        connect(textInputV2, &TextInputV2Interface::requestShowInputPanel, this, &InputMethod::show);
        connect(textInputV2, &TextInputV2Interface::requestHideInputPanel, this, &InputMethod::hide);
        connect(textInputV2, &TextInputV2Interface::surroundingTextChanged, this, &InputMethod::surroundingTextChanged);
        connect(textInputV2, &TextInputV2Interface::contentTypeChanged, this, &InputMethod::contentTypeChanged);
122
        connect(textInputV2, &TextInputV2Interface::stateUpdated, this, &InputMethod::textInputInterfaceV2StateUpdated);
123
124

        TextInputV3Interface *textInputV3 = waylandServer()->seat()->textInputV3();
125
126
127
        connect(textInputV3, &TextInputV3Interface::surroundingTextChanged, this, &InputMethod::surroundingTextChanged);
        connect(textInputV3, &TextInputV3Interface::contentTypeChanged, this, &InputMethod::contentTypeChanged);
        connect(textInputV3, &TextInputV3Interface::stateCommitted, this, &InputMethod::stateCommitted);
128
129
130
131
132

        if (m_enabled) {
            connect(textInputV2, &TextInputV2Interface::enabledChanged, this, &InputMethod::textInputInterfaceV2EnabledChanged);
            connect(textInputV3, &TextInputV3Interface::enabledChanged, this, &InputMethod::textInputInterfaceV3EnabledChanged);
        }
133
    }
134
}
135

136
void InputMethod::show()
137
{
138
    setActive(true);
139
140
141
142
}

void InputMethod::hide()
{
143
    setActive(false);
144
145
}

146
void InputMethod::setActive(bool active)
147
{
148
    if (m_active) {
149
150
        waylandServer()->inputMethod()->sendDeactivate();
    }
151

152
    if (active) {
153
154
155
156
157
        if (!m_enabled) {
            return;
        }

        waylandServer()->inputMethod()->sendActivate();
158
        adoptInputMethodContext();
159
160
    } else {
        updateInputPanelState();
161
    }
162

163
164
165
    if (m_active != active) {
        m_active = active;
        Q_EMIT activeChanged(active);
166
    }
167
168
}

169
void InputMethod::clientAdded(AbstractClient* client)
170
171
172
173
174
{
    if (!client->isInputMethod()) {
        return;
    }
    m_inputClient = client;
175
    connect(client->surface(), &SurfaceInterface::inputChanged, this, &InputMethod::updateInputPanelState);
176
177
178
179
180
    connect(client, &QObject::destroyed, this, [this] {
        if (m_trackedClient) {
            m_trackedClient->setVirtualKeyboardGeometry({});
        }
    });
181
    connect(m_inputClient, &AbstractClient::frameGeometryChanged, this, &InputMethod::updateInputPanelState);
182
183
184
185
    // Current code have a assumption that InputMethod started by the kwin is virtual keyboard,
    // InputMethod::hide sends out a deactivate signal to input-method client, this is not desired
    // when we support input methods like ibus which can show and hide surfaces/windows as they please
    // and are not exactly Virtual keyboards.
186
187
    connect(m_inputClient, &AbstractClient::windowHidden, this, &InputMethod::hide);
    connect(m_inputClient, &AbstractClient::windowClosed, this, &InputMethod::hide);
188
189
}

190
void InputMethod::handleFocusedSurfaceChanged()
191
{
192
193
194
    SurfaceInterface *focusedSurface = waylandServer()->seat()->focusedTextInputSurface();
    if (focusedSurface) {
        AbstractClient *focusedClient = waylandServer()->findClient(focusedSurface);
195
196
        // Reset the old client virtual keybaord geom if necessary
        // Old and new clients could be the same if focus moves between subsurfaces
197
        if (m_trackedClient != focusedClient) {
198
199
            if (m_trackedClient) {
                m_trackedClient->setVirtualKeyboardGeometry(QRect());
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
200
                disconnect(m_trackedClient, &AbstractClient::frameGeometryChanged, this, &InputMethod::updateInputPanelState);
201
            }
202
            m_trackedClient = focusedClient;
203
204
205
            if (m_trackedClient) {
                connect(m_trackedClient, &AbstractClient::frameGeometryChanged, this, &InputMethod::updateInputPanelState, Qt::QueuedConnection);
            }
206
        }
207
        updateInputPanelState();
208
    } else {
209
        setActive(false);
210
211
212
    }
}

213
void InputMethod::surroundingTextChanged()
214
{
215
216
    auto t2 = waylandServer()->seat()->textInputV2();
    auto t3 = waylandServer()->seat()->textInputV3();
217
218
219
220
    auto inputContext = waylandServer()->inputMethod()->context();
    if (!inputContext) {
        return;
    }
221
222
223
224
225
226
227
228
    if (t2 && t2->isEnabled()) {
        inputContext->sendSurroundingText(t2->surroundingText(), t2->surroundingTextCursorPosition(), t2->surroundingTextSelectionAnchor());
        return;
    }
    if (t3 && t3->isEnabled()) {
        inputContext->sendSurroundingText(t3->surroundingText(), t3->surroundingTextCursorPosition(), t3->surroundingTextSelectionAnchor());
        return;
    }
229
230
}

231
void InputMethod::contentTypeChanged()
232
{
233
234
    auto t2 = waylandServer()->seat()->textInputV2();
    auto t3 = waylandServer()->seat()->textInputV3();
235
236
237
238
    auto inputContext = waylandServer()->inputMethod()->context();
    if (!inputContext) {
        return;
    }
239
240
241
242
243
244
    if (t2 && t2->isEnabled()) {
        inputContext->sendContentType(t2->contentHints(), t2->contentPurpose());
    }
    if (t3 && t3->isEnabled()) {
        inputContext->sendContentType(t3->contentHints(), t3->contentPurpose());
    }
245
246
}

247
void InputMethod::textInputInterfaceV2StateUpdated(quint32 serial, KWaylandServer::TextInputV2Interface::UpdateReason reason)
248
{
249
250
251
252
    if (!m_enabled) {
        return;
    }

253
    auto t2 = waylandServer()->seat()->textInputV2();
254
255
256
257
    auto inputContext = waylandServer()->inputMethod()->context();
    if (!inputContext) {
        return;
    }
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
    if (!t2 || !t2->isEnabled()) {
        return;
    }
    switch (reason) {
    case KWaylandServer::TextInputV2Interface::UpdateReason::StateChange:
        inputContext->sendCommitState(serial);
        break;
    case KWaylandServer::TextInputV2Interface::UpdateReason::StateEnter:
        waylandServer()->inputMethod()->sendActivate();
        inputContext->sendCommitState(serial);
        break;
    case KWaylandServer::TextInputV2Interface::UpdateReason::StateFull:
        adoptInputMethodContext();
        inputContext->sendCommitState(serial);
        break;
    case KWaylandServer::TextInputV2Interface::UpdateReason::StateReset:
        inputContext->sendReset();
        inputContext->sendCommitState(serial);
        break;
277
    }
278
279
}

280
void InputMethod::textInputInterfaceV2EnabledChanged()
281
{
282
283
284
285
    if (!m_enabled) {
        return;
    }

286
    auto t = waylandServer()->seat()->textInputV2();
287
288
289
290
291
292
293
294
    if (t->isEnabled()) {
        waylandServer()->inputMethod()->sendActivate();
        adoptInputMethodContext();
    } else {
        hide();
    }
}

295
void InputMethod::textInputInterfaceV3EnabledChanged()
296
{
297
298
299
300
    if (!m_enabled) {
        return;
    }

301
302
303
304
305
306
307
308
309
310
    auto t3 = waylandServer()->seat()->textInputV3();
    if (t3->isEnabled()) {
        waylandServer()->inputMethod()->sendActivate();
    } else {
        waylandServer()->inputMethod()->sendDeactivate();
        // reset value of preedit when textinput is disabled
        preedit.text = QString();
        preedit.begin = 0;
        preedit.end = 0;
    }
311
312
313
314
315
    auto context = waylandServer()->inputMethod()->context();
    if (context) {
        context->sendReset();
        adoptInputMethodContext();
    }
316
317
}

318
void InputMethod::stateCommitted(uint32_t serial)
319
320
321
322
323
324
325
326
{
    auto inputContext = waylandServer()->inputMethod()->context();
    if (!inputContext) {
        return;
    }
    inputContext->sendCommitState(serial);
}

327
void InputMethod::setEnabled(bool enabled)
328
329
330
331
332
{
    if (m_enabled == enabled) {
        return;
    }
    m_enabled = enabled;
333
    emit enabledChanged(m_enabled);
334
335
336
337
338
339
340
341
342
343

    // send OSD message
    QDBusMessage msg = QDBusMessage::createMethodCall(
        QStringLiteral("org.kde.plasmashell"),
        QStringLiteral("/org/kde/osdService"),
        QStringLiteral("org.kde.osdService"),
        QStringLiteral("virtualKeyboardEnabledChanged")
    );
    msg.setArguments({enabled});
    QDBusConnection::sessionBus().asyncCall(msg);
344
    if (!m_enabled) {
345
        hide();
346
347
348
        stopInputMethod();
    } else {
        startInputMethod();
349
    }
350
351
}

352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
static quint32 keysymToKeycode(quint32 sym)
{
    switch(sym) {
    case XKB_KEY_BackSpace:
        return KEY_BACKSPACE;
    case XKB_KEY_Return:
        return KEY_ENTER;
    case XKB_KEY_Left:
        return KEY_LEFT;
    case XKB_KEY_Right:
        return KEY_RIGHT;
    case XKB_KEY_Up:
        return KEY_UP;
    case XKB_KEY_Down:
        return KEY_DOWN;
    default:
        return KEY_UNKNOWN;
    }
}

372
void InputMethod::keysymReceived(quint32 serial, quint32 time, quint32 sym, bool pressed, Qt::KeyboardModifiers modifiers)
373
374
375
{
    Q_UNUSED(serial)
    Q_UNUSED(time)
376
377
378
379
380
381
382
383
384
385
386
    auto t2 = waylandServer()->seat()->textInputV2();
    if (t2 && t2->isEnabled()) {
        if (pressed) {
            t2->keysymPressed(sym, modifiers);
        } else {
            t2->keysymReleased(sym, modifiers);
        }
        return;
    }
    auto t3 = waylandServer()->seat()->textInputV3();
    if (t3 && t3->isEnabled()) {
387
        KWaylandServer::KeyboardKeyState state;
388
        if (pressed) {
389
            state = KWaylandServer::KeyboardKeyState::Pressed;
390
        } else {
391
            state = KWaylandServer::KeyboardKeyState::Released;
392
        }
393
        waylandServer()->seat()->notifyKeyboardKey(keysymToKeycode(sym), state);
394
        return;
395
396
397
    }
}

398
void InputMethod::commitString(qint32 serial, const QString &text)
399
400
{
    Q_UNUSED(serial)
401
402
403
404
405
406
407
408
409
410
411
    auto t2 = waylandServer()->seat()->textInputV2();
    if (t2 && t2->isEnabled()) {
        t2->commitString(text.toUtf8());
        t2->preEdit({}, {});
        return;
    }
    auto t3 = waylandServer()->seat()->textInputV3();
    if (t3 && t3->isEnabled()) {
        t3->commitString(text.toUtf8());
        t3->done();
        return;
412
413
414
    }
}

415
void InputMethod::deleteSurroundingText(int32_t index, uint32_t length)
416
{
417
418
419
420
421
422
423
    auto t2 = waylandServer()->seat()->textInputV2();
    if (t2 && t2->isEnabled()) {
        t2->deleteSurroundingText(index, length);
    }
    auto t3 = waylandServer()->seat()->textInputV3();
    if (t3 && t3->isEnabled()) {
        t3->deleteSurroundingText(index, length);
424
425
426
    }
}

427
void InputMethod::setCursorPosition(qint32 index, qint32 anchor)
428
{
429
430
431
    auto t2 = waylandServer()->seat()->textInputV2();
    if (t2 && t2->isEnabled()) {
        t2->setCursorPosition(index, anchor);
432
433
434
    }
}

435
void InputMethod::setLanguage(uint32_t serial, const QString &language)
436
{
437
438
439
440
    Q_UNUSED(serial)
    auto t2 = waylandServer()->seat()->textInputV2();
    if (t2 && t2->isEnabled()) {
        t2->setLanguage(language.toUtf8());
441
442
443
    }
}

444
void InputMethod::setTextDirection(uint32_t serial, Qt::LayoutDirection direction)
445
{
446
447
448
449
    Q_UNUSED(serial)
    auto t2 = waylandServer()->seat()->textInputV2();
    if (t2 && t2->isEnabled()) {
        t2->setTextDirection(direction);
450
451
452
    }
}

453
void InputMethod::setPreeditCursor(qint32 index)
454
{
455
456
457
458
459
460
461
462
463
    auto t2 = waylandServer()->seat()->textInputV2();
    if (t2 && t2->isEnabled()) {
        t2->setPreEditCursor(index);
    }
    auto t3 = waylandServer()->seat()->textInputV3();
    if (t3 && t3->isEnabled()) {
        preedit.begin = index;
        preedit.end = index;
        t3->sendPreEditString(preedit.text, preedit.begin, preedit.end);
464
465
466
    }
}

467

468
void InputMethod::setPreeditString(uint32_t serial, const QString &text, const QString &commit)
469
470
{
    Q_UNUSED(serial)
471
472
473
474
475
476
477
478
    auto t2 = waylandServer()->seat()->textInputV2();
    if (t2 && t2->isEnabled()) {
        t2->preEdit(text.toUtf8(), commit.toUtf8());
    }
    auto t3 = waylandServer()->seat()->textInputV3();
    if (t3 && t3->isEnabled()) {
        preedit.text = text;
        t3->sendPreEditString(preedit.text, preedit.begin, preedit.end);
479
480
481
    }
}

482
void InputMethod::adoptInputMethodContext()
483
484
485
{
    auto inputContext = waylandServer()->inputMethod()->context();

486
487
488
489
490
491
492
    TextInputV2Interface *t2 = waylandServer()->seat()->textInputV2();
    TextInputV3Interface *t3 = waylandServer()->seat()->textInputV3();

    if (t2 && t2->isEnabled()) {
        inputContext->sendSurroundingText(t2->surroundingText(), t2->surroundingTextCursorPosition(), t2->surroundingTextSelectionAnchor());
        inputContext->sendPreferredLanguage(t2->preferredLanguage());
        inputContext->sendContentType(t2->contentHints(), t2->contentPurpose());
493
494
        connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::language, this, &InputMethod::setLanguage);
        connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::textDirection, this, &InputMethod::setTextDirection);
495
496
497
498
499
500
    }

    if (t3 && t3->isEnabled()) {
        inputContext->sendSurroundingText(t3->surroundingText(), t3->surroundingTextCursorPosition(), t3->surroundingTextSelectionAnchor());
        inputContext->sendContentType(t3->contentHints(), t3->contentPurpose());
    }
501

502
503
504
505
    connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::keysym, this, &InputMethod::keysymReceived, Qt::UniqueConnection);
    connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::commitString, this, &InputMethod::commitString, Qt::UniqueConnection);
    connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::deleteSurroundingText, this, &InputMethod::deleteSurroundingText, Qt::UniqueConnection);
    connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::cursorPosition, this, &InputMethod::setCursorPosition, Qt::UniqueConnection);
506
507
    connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::preeditString, this, &InputMethod::setPreeditString, Qt::UniqueConnection);
    connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::preeditCursor, this, &InputMethod::setPreeditCursor, Qt::UniqueConnection);
508
509
}

510
void InputMethod::updateSni()
511
512
513
514
{
    if (!m_sni) {
        return;
    }
515
516
517
518
519
    if (m_inputMethodCommand.isEmpty()) {
        m_sni->setIconByName(QStringLiteral("input-keyboard-virtual-on"));
        m_sni->setOverlayIconByName(QStringLiteral("emblem-unavailable"));
        m_sni->setTitle(i18n("No Virtual Keyboard configured"));
    } else if (m_enabled) {
520
        m_sni->setIconByName(QStringLiteral("input-keyboard-virtual-on"));
521
        m_sni->setTitle(i18n("Virtual Keyboard: enabled"));
522
        m_sni->setOverlayIconByName({});
523
524
    } else {
        m_sni->setIconByName(QStringLiteral("input-keyboard-virtual-off"));
525
        m_sni->setTitle(i18n("Virtual Keyboard: disabled"));
526
        m_sni->setOverlayIconByName({});
527
528
529
    }
}

530
void InputMethod::updateInputPanelState()
531
532
533
534
535
{
    if (!waylandServer()) {
        return;
    }

536
    auto t = waylandServer()->seat()->textInputV2();
537

538
    if (!t) {
539
540
541
        return;
    }

542
    QRect overlap = QRect(0, 0, 0, 0);
543
544
    if (m_trackedClient) {
        m_trackedClient->setVirtualKeyboardGeometry(m_inputClient ? m_inputClient->inputGeometry() : QRect());
545
546
547
548
549

        if (m_inputClient) {
            overlap = m_trackedClient->frameGeometry() & m_inputClient->inputGeometry();
            overlap.moveTo(m_trackedClient->mapToLocal(overlap.topLeft()));
        }
550
    }
551
    t->setInputPanelState(m_inputClient && m_inputClient->isShown(false), overlap);
552
553
}

554
555
556
557
558
559
560
561
562
563
564
void InputMethod::setInputMethodCommand(const QString &command)
{
    if (m_inputMethodCommand == command) {
        return;
    }

    m_inputMethodCommand = command;

    if (m_enabled) {
        startInputMethod();
    }
565
    updateSni();
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
}

void InputMethod::stopInputMethod()
{
    if (!m_inputMethodProcess) {
        return;
    }
    disconnect(m_inputMethodProcess, nullptr, this, nullptr);

    m_inputMethodProcess->terminate();
    if (!m_inputMethodProcess->waitForFinished()) {
        m_inputMethodProcess->kill();
        m_inputMethodProcess->waitForFinished();
    }
    if (waylandServer()) {
        waylandServer()->destroyInputMethodConnection();
    }
    m_inputMethodProcess->deleteLater();
    m_inputMethodProcess = nullptr;
}

void InputMethod::startInputMethod()
{
    stopInputMethod();
    if (m_inputMethodCommand.isEmpty() || kwinApp()->isTerminating()) {
        return;
    }

    connect(waylandServer(), &WaylandServer::terminatingInternalClientConnection, this, &InputMethod::stopInputMethod, Qt::UniqueConnection);

    QStringList arguments = KShell::splitArgs(m_inputMethodCommand);
    if (arguments.isEmpty()) {
        qWarning("Failed to launch the input method server: %s is an invalid command", qPrintable(m_inputMethodCommand));
        return;
    }

    const QString program = arguments.takeFirst();
    int socket = waylandServer()->createInputMethodConnection();
    if (socket < 0) {
        qWarning("Failed to create the input method connection");
        return;
    }
    socket = dup(socket);

    QProcessEnvironment environment = kwinApp()->processStartupEnvironment();
    environment.insert(QStringLiteral("WAYLAND_SOCKET"), QByteArray::number(socket));
    environment.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("wayland"));
    environment.remove("DISPLAY");
    environment.remove("WAYLAND_DISPLAY");
    environment.remove("XAUTHORITY");

    m_inputMethodProcess = new Process(this);
    m_inputMethodProcess->setProcessChannelMode(QProcess::ForwardedErrorChannel);
    m_inputMethodProcess->setProcessEnvironment(environment);
    m_inputMethodProcess->setProgram(program);
    m_inputMethodProcess->setArguments(arguments);
    m_inputMethodProcess->start();
    close(socket);
    connect(m_inputMethodProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus) {
        if (exitStatus == QProcess::CrashExit) {
            m_inputMethodCrashes++;
            m_inputMethodCrashTimer.start();
            qWarning() << "Input Method crashed" << m_inputMethodProcess->program() << m_inputMethodProcess->arguments() << exitCode << exitStatus;
            if (m_inputMethodCrashes < 5) {
                startInputMethod();
            } else {
                qWarning() << "Input Method keeps crashing, please fix" << m_inputMethodProcess->program() << m_inputMethodProcess->arguments();
                stopInputMethod();
            }
        }
    });
}
638
}