inputmethod.cpp 25.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 "inputpanelv1client.h"
14
#include "keyboard_input.h"
15
#include "utils/common.h"
16
17
18
#include "screens.h"
#include "wayland_server.h"
#include "workspace.h"
19
#include "screenlockerwatcher.h"
20
#include "deleted.h"
21
#include "touch_input.h"
22
#include "tablet_input.h"
23

24
#include <KWaylandServer/display.h>
25
#include <KWaylandServer/keyboard_interface.h>
26
#include <KWaylandServer/seat_interface.h>
27
#include <KWaylandServer/textinput_v3_interface.h>
28
#include <KWaylandServer/surface_interface.h>
29
#include <KWaylandServer/inputmethod_v1_interface.h>
30

31
#include <KShell>
32
33
34
35
36
37
#include <KStatusNotifierItem>
#include <KLocalizedString>

#include <QDBusConnection>
#include <QDBusPendingCall>
#include <QDBusMessage>
38
#include <QMenu>
39
#include <QKeyEvent>
40

41
42
#include <linux/input-event-codes.h>
#include <xkbcommon/xkbcommon-keysyms.h>
43
#include <unistd.h>
44

45
using namespace KWaylandServer;
46
47
48
49

namespace KWin
{

50
KWIN_SINGLETON_FACTORY(InputMethod)
51

52
InputMethod::InputMethod(QObject *parent)
53
54
    : QObject(parent)
{
Xaver Hugl's avatar
Xaver Hugl committed
55
    m_enabled = kwinApp()->config()->group("Wayland").readEntry("VirtualKeyboardEnabled", true);
56
57
58
    // 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
59
60
61
62
63
    if (workspace()) {
        init();
    } else {
        connect(kwinApp(), &Application::workspaceCreated, this, &InputMethod::init);
    }
64
65
}

66
67
68
69
70
InputMethod::~InputMethod()
{
    stopInputMethod();
    s_self = nullptr;
}
71

72
void InputMethod::init()
73
{
74
75
76
77
78
79
    // 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;
    });
80
    connect(ScreenLockerWatcher::self(), &ScreenLockerWatcher::aboutToLock, this, &InputMethod::hide);
81

82
    new VirtualKeyboardDBus(this);
83
    qCDebug(KWIN_VIRTUALKEYBOARD) << "Registering the DBus interface";
84
85

    if (waylandServer()) {
86
87
        new TextInputManagerV2Interface(waylandServer()->display());
        new TextInputManagerV3Interface(waylandServer()->display());
88

89
        connect(waylandServer()->seat(), &SeatInterface::focusedTextInputSurfaceChanged, this, &InputMethod::handleFocusedSurfaceChanged);
90
91

        TextInputV2Interface *textInputV2 = waylandServer()->seat()->textInputV2();
92
93
94
95
        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);
96
        connect(textInputV2, &TextInputV2Interface::stateUpdated, this, &InputMethod::textInputInterfaceV2StateUpdated);
97
        connect(textInputV2, &TextInputV2Interface::enabledChanged, this, &InputMethod::textInputInterfaceV2EnabledChanged);
98
99

        TextInputV3Interface *textInputV3 = waylandServer()->seat()->textInputV3();
100
101
102
        connect(textInputV3, &TextInputV3Interface::surroundingTextChanged, this, &InputMethod::surroundingTextChanged);
        connect(textInputV3, &TextInputV3Interface::contentTypeChanged, this, &InputMethod::contentTypeChanged);
        connect(textInputV3, &TextInputV3Interface::stateCommitted, this, &InputMethod::stateCommitted);
103
        connect(textInputV3, &TextInputV3Interface::enabledChanged, this, &InputMethod::textInputInterfaceV3EnabledChanged);
104

105
106
107
        connect(input()->keyboard()->xkb(), &Xkb::modifierStateChanged, this, [this]() {
            m_hasPendingModifiers = true;
        });
108
    }
109
}
110

111
void InputMethod::show()
112
{
113
114
115
116
    if (m_inputClient) {
        m_inputClient->showClient();
        updateInputPanelState();
    }
117
118
119
120
}

void InputMethod::hide()
{
121
122
123
124
    if (m_inputClient) {
        m_inputClient->hideClient();
        updateInputPanelState();
    }
125
126
}

127
bool InputMethod::shouldShowOnActive() const
128
{
129
130
    return input()->touch() == input()->lastInputHandler()
        || input()->tablet() == input()->lastInputHandler();
131
132
133
134
}

void InputMethod::setActive(bool active)
{
135
136
137
138
    const bool wasActive = waylandServer()->inputMethod()->context();
    if (wasActive && !active) {
        waylandServer()->inputMethod()->sendDeactivate();
    }
139

140
    if (active) {
141
142
143
144
        if (!m_enabled) {
            return;
        }

145
146
147
        if (!wasActive) {
            waylandServer()->inputMethod()->sendActivate();
        }
148
        adoptInputMethodContext();
149
150
    } else {
        updateInputPanelState();
151
    }
152

153
    if (wasActive != isActive()) {
154
        Q_EMIT activeChanged(active);
155
    }
156
157
}

158
void InputMethod::setPanel(InputPanelV1Client *client)
159
{
160
    Q_ASSERT(client->isInputMethod());
161
    if (m_inputClient) {
162
        qCWarning(KWIN_VIRTUALKEYBOARD) << "Replacing input client" << m_inputClient << "with" << client;
163
164
165
        disconnect(m_inputClient, nullptr, this, nullptr);
    }

166
    m_inputClient = client;
167
    connect(client->surface(), &SurfaceInterface::inputChanged, this, &InputMethod::updateInputPanelState);
168
169
170
171
172
    connect(client, &QObject::destroyed, this, [this] {
        if (m_trackedClient) {
            m_trackedClient->setVirtualKeyboardGeometry({});
        }
    });
173
    connect(m_inputClient, &AbstractClient::frameGeometryChanged, this, &InputMethod::updateInputPanelState);
174
175
    connect(m_inputClient, &AbstractClient::windowHidden, this, &InputMethod::updateInputPanelState);
    connect(m_inputClient, &AbstractClient::windowClosed, this, &InputMethod::updateInputPanelState);
176
177
178
179
    connect(m_inputClient, &AbstractClient::windowShown, this, &InputMethod::visibleChanged);
    connect(m_inputClient, &AbstractClient::windowHidden, this, &InputMethod::visibleChanged);
    connect(m_inputClient, &AbstractClient::windowClosed, this, &InputMethod::visibleChanged);
    Q_EMIT visibleChanged();
180
    updateInputPanelState();
181
182
}

183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
void InputMethod::setTrackedClient(AbstractClient* trackedClient)
{
    // Reset the old client virtual keybaord geom if necessary
    // Old and new clients could be the same if focus moves between subsurfaces
    if (m_trackedClient == trackedClient) {
        return;
    }
    if (m_trackedClient) {
        m_trackedClient->setVirtualKeyboardGeometry(QRect());
        disconnect(m_trackedClient, &AbstractClient::frameGeometryChanged, this, &InputMethod::updateInputPanelState);
    }
    m_trackedClient = trackedClient;
    if (m_trackedClient) {
        connect(m_trackedClient, &AbstractClient::frameGeometryChanged, this, &InputMethod::updateInputPanelState, Qt::QueuedConnection);
    }
    updateInputPanelState();
}

201
void InputMethod::handleFocusedSurfaceChanged()
202
{
203
    SurfaceInterface *focusedSurface = waylandServer()->seat()->focusedTextInputSurface();
204
205
    setTrackedClient(waylandServer()->findClient(focusedSurface));
    if (!focusedSurface) {
206
        setActive(false);
207
208
209
    }
}

210
void InputMethod::surroundingTextChanged()
211
{
212
213
    auto t2 = waylandServer()->seat()->textInputV2();
    auto t3 = waylandServer()->seat()->textInputV3();
214
215
216
217
    auto inputContext = waylandServer()->inputMethod()->context();
    if (!inputContext) {
        return;
    }
218
219
220
221
222
223
224
225
    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;
    }
226
227
}

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

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

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

275
void InputMethod::textInputInterfaceV2EnabledChanged()
276
{
277
278
279
280
    if (!m_enabled) {
        return;
    }

281
    auto t = waylandServer()->seat()->textInputV2();
282
    setActive(t->isEnabled());
283
284
}

285
void InputMethod::textInputInterfaceV3EnabledChanged()
286
{
287
288
289
290
    if (!m_enabled) {
        return;
    }

291
    auto t3 = waylandServer()->seat()->textInputV3();
292
293
    setActive(t3->isEnabled());
    if (!t3->isEnabled()) {
294
        // reset value of preedit when textinput is disabled
295
        resetPendingPreedit();
296
    }
297
298
299
300
301
    auto context = waylandServer()->inputMethod()->context();
    if (context) {
        context->sendReset();
        adoptInputMethodContext();
    }
302
303
}

304
void InputMethod::stateCommitted(uint32_t serial)
305
{
306
    if (!isEnabled()) {
Bhushan Shah's avatar
Bhushan Shah committed
307
        return;
308
309
310
    }
    TextInputV3Interface *textInputV3 = waylandServer()->seat()->textInputV3();
    if (!textInputV3) {
311
312
        return;
    }
313

314
315
    if (auto inputContext = waylandServer()->inputMethod()->context()) {
        inputContext->sendCommitState(serial);
316
    }
317
    setActive(textInputV3->isEnabled());
318
319
}

320
void InputMethod::setEnabled(bool enabled)
321
322
323
324
325
{
    if (m_enabled == enabled) {
        return;
    }
    m_enabled = enabled;
326
    Q_EMIT enabledChanged(m_enabled);
327
328
329
330
331
332
333
334
335
336

    // 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);
337
    if (!m_enabled) {
338
        hide();
339
340
341
        stopInputMethod();
    } else {
        startInputMethod();
342
    }
Xaver Hugl's avatar
Xaver Hugl committed
343
344
345
    // save value into config
    kwinApp()->config()->group("Wayland").writeEntry("VirtualKeyboardEnabled", m_enabled);
    kwinApp()->config()->sync();
346
347
}

348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
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;
    }
}

368
void InputMethod::keysymReceived(quint32 serial, quint32 time, quint32 sym, bool pressed, quint32 modifiers)
369
370
371
{
    Q_UNUSED(serial)
    Q_UNUSED(time)
372
373
374
375
376
377
378
379
380
381
382
    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()) {
383
        KWaylandServer::KeyboardKeyState state;
384
        if (pressed) {
385
            state = KWaylandServer::KeyboardKeyState::Pressed;
386
        } else {
387
            state = KWaylandServer::KeyboardKeyState::Released;
388
        }
389
        waylandServer()->seat()->notifyKeyboardKey(keysymToKeycode(sym), state);
390
        return;
391
392
393
    }
}

394
void InputMethod::commitString(qint32 serial, const QString &text)
395
396
{
    Q_UNUSED(serial)
397
    if (auto t2 = waylandServer()->seat()->textInputV2(); t2 && t2->isEnabled()) {
398
399
400
        t2->commitString(text.toUtf8());
        t2->preEdit({}, {});
        return;
401
    } else if (auto t3 = waylandServer()->seat()->textInputV3(); t3 && t3->isEnabled()) {
402
403
404
        t3->commitString(text.toUtf8());
        t3->done();
        return;
405
406
    } else {
        qCWarning(KWIN_VIRTUALKEYBOARD) << "We have nobody to commit to!!!";
407
408
409
    }
}

410
void InputMethod::deleteSurroundingText(int32_t index, uint32_t length)
411
{
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
    // zwp_input_method_v1 Delete surrounding text interface is designed for text-input-v1.
    // The parameter has different meaning in text-input-v{2,3}.
    // Current cursor is at index 0.
    // The actualy deleted text range is [index, index + length].
    // In v{2,3}'s before/after style, text to be deleted with v{2,3} interface is [-before, after].
    // And before/after are all unsigned, which make it impossible to do certain things.
    // Those request will be ignored.

    // Verify we can handle such request.
    if (index > 0 || index + static_cast<ssize_t>(length) < 0) {
        return;
    }
    const quint32 before = -index;
    const quint32 after = index + length;

427
428
    auto t2 = waylandServer()->seat()->textInputV2();
    if (t2 && t2->isEnabled()) {
429
        t2->deleteSurroundingText(before, after);
430
431
432
    }
    auto t3 = waylandServer()->seat()->textInputV3();
    if (t3 && t3->isEnabled()) {
433
        t3->deleteSurroundingText(before, after);
434
        t3->done();
435
436
437
    }
}

438
void InputMethod::setCursorPosition(qint32 index, qint32 anchor)
439
{
440
441
442
    auto t2 = waylandServer()->seat()->textInputV2();
    if (t2 && t2->isEnabled()) {
        t2->setCursorPosition(index, anchor);
443
444
445
    }
}

446
void InputMethod::setLanguage(uint32_t serial, const QString &language)
447
{
448
449
450
451
    Q_UNUSED(serial)
    auto t2 = waylandServer()->seat()->textInputV2();
    if (t2 && t2->isEnabled()) {
        t2->setLanguage(language.toUtf8());
452
453
454
    }
}

455
void InputMethod::setTextDirection(uint32_t serial, Qt::LayoutDirection direction)
456
{
457
458
459
460
    Q_UNUSED(serial)
    auto t2 = waylandServer()->seat()->textInputV2();
    if (t2 && t2->isEnabled()) {
        t2->setTextDirection(direction);
461
462
463
    }
}

464
void InputMethod::setPreeditCursor(qint32 index)
465
{
466
467
468
469
470
471
    auto t2 = waylandServer()->seat()->textInputV2();
    if (t2 && t2->isEnabled()) {
        t2->setPreEditCursor(index);
    }
    auto t3 = waylandServer()->seat()->textInputV3();
    if (t3 && t3->isEnabled()) {
472
        preedit.cursor = index;
473
474
475
    }
}

476
477
478
479
480
481
void InputMethod::setPreeditStyling(quint32 index, quint32 length, quint32 style)
{
    auto t2 = waylandServer()->seat()->textInputV2();
    if (t2 && t2->isEnabled()) {
        t2->preEditStyling(index, length, style);
    }
482
483
484
485
486
487
488
    auto t3 = waylandServer()->seat()->textInputV3();
    if (t3 && t3->isEnabled()) {
        // preedit style: highlight(4) or selection(6)
        if (style == 4 || style == 6) {
            preedit.highlightRanges.emplace_back(index, index + length);
        }
    }
489
}
490

491
void InputMethod::setPreeditString(uint32_t serial, const QString &text, const QString &commit)
492
493
{
    Q_UNUSED(serial)
494
495
496
497
498
499
500
    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;
501
        if (!preedit.text.isEmpty()) {
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
            quint32 cursor = 0, cursorEnd = 0;
            if (preedit.cursor > 0) {
                cursor = cursorEnd = preedit.cursor;
            }
            // Check if we can convert highlight style to a range of selection.
            if (!preedit.highlightRanges.empty()) {
                std::sort(preedit.highlightRanges.begin(), preedit.highlightRanges.end());
                // Check if starting point matches.
                if (preedit.highlightRanges.front().first == cursor) {
                    quint32 end = preedit.highlightRanges.front().second;
                    bool nonContinousHighlight = false;
                    for (size_t i = 1 ; i < preedit.highlightRanges.size(); i ++) {
                        if (end >= preedit.highlightRanges[i].first) {
                            end = std::max(end, preedit.highlightRanges[i].second);
                        } else {
                            nonContinousHighlight = true;
                            break;
                        }
                    }
                    if (!nonContinousHighlight) {
                        cursorEnd = end;
                    }
                }
            }

            t3->sendPreEditString(preedit.text, cursor, cursorEnd);
528
        }
529
        t3->done();
530
    }
531
    resetPendingPreedit();
532
533
}

534
535
536
537
538
539
540
541
542
543
544
545
void InputMethod::key(quint32 /*serial*/, quint32 /*time*/, quint32 keyCode, bool pressed)
{
    waylandServer()->seat()->notifyKeyboardKey(keyCode, pressed ? KWaylandServer::KeyboardKeyState::Pressed : KWaylandServer::KeyboardKeyState::Released);
}

void InputMethod::modifiers(quint32 serial, quint32 mods_depressed, quint32 mods_latched, quint32 mods_locked, quint32 group)
{
    Q_UNUSED(serial)
    auto xkb = input()->keyboard()->xkb();
    xkb->updateModifiers(mods_depressed, mods_latched, mods_locked, group);
}

546
void InputMethod::forwardModifiers(ForwardModifiersForce force)
547
{
548
549
550
551
552
    const bool sendModifiers = m_hasPendingModifiers || force == Force;
    m_hasPendingModifiers = false;
    if (!sendModifiers) {
        return;
    }
553
554
555
556
557
558
559
560
561
562
    auto xkb = input()->keyboard()->xkb();
    if (m_keyboardGrab) {
        m_keyboardGrab->sendModifiers(waylandServer()->display()->nextSerial(),
                                      xkb->modifierState().depressed,
                                      xkb->modifierState().latched,
                                      xkb->modifierState().locked,
                                      xkb->currentLayout());
    }
}

563
void InputMethod::adoptInputMethodContext()
564
565
566
{
    auto inputContext = waylandServer()->inputMethod()->context();

567
568
569
570
571
572
573
    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());
574
575
        connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::language, this, &InputMethod::setLanguage);
        connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::textDirection, this, &InputMethod::setTextDirection);
576
577
578
579
580
581
    }

    if (t3 && t3->isEnabled()) {
        inputContext->sendSurroundingText(t3->surroundingText(), t3->surroundingTextCursorPosition(), t3->surroundingTextSelectionAnchor());
        inputContext->sendContentType(t3->contentHints(), t3->contentPurpose());
    }
582
    inputContext->sendCommitState(m_serial++);
583

584
    connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::keysym, this, &InputMethod::keysymReceived, Qt::UniqueConnection);
585
586
    connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::key, this, &InputMethod::key, Qt::UniqueConnection);
    connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::modifiers, this, &InputMethod::modifiers, Qt::UniqueConnection);
587
588
589
    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);
590
    connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::preeditStyling, this, &InputMethod::setPreeditStyling, Qt::UniqueConnection);
591
592
    connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::preeditString, this, &InputMethod::setPreeditString, Qt::UniqueConnection);
    connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::preeditCursor, this, &InputMethod::setPreeditCursor, Qt::UniqueConnection);
593
    connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::keyboardGrabRequested, this, &InputMethod::installKeyboardGrab, Qt::UniqueConnection);
594
    connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::modifiersMap, this, &InputMethod::updateModifiersMap, Qt::UniqueConnection);
595
596
}

597
void InputMethod::updateInputPanelState()
598
599
600
601
602
{
    if (!waylandServer()) {
        return;
    }

603
    auto t = waylandServer()->seat()->textInputV2();
604

605
    if (!t) {
606
607
608
        return;
    }

609
    if (m_inputClient && shouldShowOnActive()) {
610
611
612
        m_inputClient->allow();
    }

613
    QRect overlap = QRect(0, 0, 0, 0);
614
    if (m_trackedClient) {
615
        const bool bottomKeyboard = m_inputClient && m_inputClient->mode() != InputPanelV1Client::Overlay && m_inputClient->isShown();
616
        m_trackedClient->setVirtualKeyboardGeometry(bottomKeyboard ? m_inputClient->inputGeometry() : QRect());
617

618
        if (m_inputClient && m_inputClient->mode() != InputPanelV1Client::Overlay) {
619
620
621
            overlap = m_trackedClient->frameGeometry() & m_inputClient->inputGeometry();
            overlap.moveTo(m_trackedClient->mapToLocal(overlap.topLeft()));
        }
622
    }
623
    t->setInputPanelState(m_inputClient && m_inputClient->isShown(), overlap);
624
625
}

626
627
628
629
630
631
632
633
634
635
636
void InputMethod::setInputMethodCommand(const QString &command)
{
    if (m_inputMethodCommand == command) {
        return;
    }

    m_inputMethodCommand = command;

    if (m_enabled) {
        startInputMethod();
    }
637
    Q_EMIT availableChanged();
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
}

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();
    }
    m_inputMethodProcess->deleteLater();
    m_inputMethodProcess = nullptr;
654
655

    waylandServer()->destroyInputMethodConnection();
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
}

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

    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"));

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
683
    m_inputMethodProcess = new QProcess(this);
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
    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();
            }
        }
    });
}
704
705
706
707
708
bool InputMethod::isActive() const
{
    return waylandServer()->inputMethod()->context();
}

709
710
711
712
KWaylandServer::InputMethodGrabV1 *InputMethod::keyboardGrab()
{
    return isActive() ? m_keyboardGrab : nullptr;
}
713
714
715
716

void InputMethod::installKeyboardGrab(KWaylandServer::InputMethodGrabV1 *keyboardGrab)
{
    auto xkb = input()->keyboard()->xkb();
717
    m_keyboardGrab = keyboardGrab;
718
    keyboardGrab->sendKeymap(xkb->keymapContents());
719
    forwardModifiers(Force);
720
721
}

722
723
724
725
726
727
728
729
730
void InputMethod::updateModifiersMap(const QByteArray &modifiers)
{
    TextInputV2Interface *t2 = waylandServer()->seat()->textInputV2();

    if (t2 && t2->isEnabled()) {
        t2->setModifiersMap(modifiers);
    }
}

731
732
bool InputMethod::isVisible() const
{
733
    return m_inputClient && m_inputClient->isShown();
734
}
735

736
737
bool InputMethod::isAvailable() const
{
738
    return !m_inputMethodCommand.isEmpty();
739
740
}

741
742
743
744
745
746
void InputMethod::resetPendingPreedit() {
    preedit.text = QString();
    preedit.cursor = 0;
    preedit.highlightRanges.clear();
}

747
}