inputmethod.cpp 17.7 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
29
30
31
32
33
34

#include <KStatusNotifierItem>
#include <KLocalizedString>

#include <QDBusConnection>
#include <QDBusPendingCall>
#include <QDBusMessage>

35
36
37
#include <linux/input-event-codes.h>
#include <xkbcommon/xkbcommon-keysyms.h>

38
using namespace KWaylandServer;
39
40
41
42

namespace KWin
{

43
KWIN_SINGLETON_FACTORY(InputMethod)
44

45
InputMethod::InputMethod(QObject *parent)
46
47
48
49
50
    : 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
51
    connect(kwinApp(), &Application::workspaceCreated, this, &InputMethod::init);
52
53
}

54
InputMethod::~InputMethod() = default;
55

56
void InputMethod::init()
57
{
58
    connect(ScreenLockerWatcher::self(), &ScreenLockerWatcher::aboutToLock, this, &InputMethod::hide);
59

60
    if (waylandServer()) {
61
        m_enabled = !input()->hasAlphaNumericKeyboard();
62
        qCDebug(KWIN_VIRTUALKEYBOARD) << "enabled by default: " << m_enabled;
63
64
        connect(input(), &InputRedirection::hasAlphaNumericKeyboardChanged, this,
            [this] (bool set) {
65
                qCDebug(KWIN_VIRTUALKEYBOARD) << "AlphaNumeric Keyboard changed:" << set << "toggling virtual keyboard.";
66
                setEnabled(!set);
67
68
69
70
            }
        );
    }

71
    qCDebug(KWIN_VIRTUALKEYBOARD) << "Registering the SNI";
72
    m_sni = new KStatusNotifierItem(QStringLiteral("kwin-virtual-keyboard"), this);
73
    m_sni->setStandardActionsEnabled(false);
74
75
76
77
78
79
80
81
82
    m_sni->setCategory(KStatusNotifierItem::Hardware);
    m_sni->setStatus(KStatusNotifierItem::Passive);
    m_sni->setTitle(i18n("Virtual Keyboard"));
    updateSni();
    connect(m_sni, &KStatusNotifierItem::activateRequested, this,
        [this] {
            setEnabled(!m_enabled);
        }
    );
83
    connect(this, &InputMethod::enabledChanged, this, &InputMethod::updateSni);
84

85
    new VirtualKeyboardDBus(this);
86
    qCDebug(KWIN_VIRTUALKEYBOARD) << "Registering the DBus interface";
87
    connect(input(), &InputRedirection::keyStateChanged, this, &InputMethod::hide);
88
89

    if (waylandServer()) {
90
91
        new TextInputManagerV2Interface(waylandServer()->display());
        new TextInputManagerV3Interface(waylandServer()->display());
92

93
94
        connect(workspace(), &Workspace::clientAdded, this, &InputMethod::clientAdded);
        connect(waylandServer()->seat(), &SeatInterface::focusedTextInputSurfaceChanged, this, &InputMethod::handleFocusedSurfaceChanged);
95
96

        TextInputV2Interface *textInputV2 = waylandServer()->seat()->textInputV2();
97
98
99
100
        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);
101
        connect(textInputV2, &TextInputV2Interface::stateUpdated, this, &InputMethod::textInputInterfaceV2StateUpdated);
102
103

        TextInputV3Interface *textInputV3 = waylandServer()->seat()->textInputV3();
104
105
106
        connect(textInputV3, &TextInputV3Interface::surroundingTextChanged, this, &InputMethod::surroundingTextChanged);
        connect(textInputV3, &TextInputV3Interface::contentTypeChanged, this, &InputMethod::contentTypeChanged);
        connect(textInputV3, &TextInputV3Interface::stateCommitted, this, &InputMethod::stateCommitted);
107
108
109
110
111

        if (m_enabled) {
            connect(textInputV2, &TextInputV2Interface::enabledChanged, this, &InputMethod::textInputInterfaceV2EnabledChanged);
            connect(textInputV3, &TextInputV3Interface::enabledChanged, this, &InputMethod::textInputInterfaceV3EnabledChanged);
        }
112
    }
113
}
114

115
void InputMethod::show()
116
{
117
    setActive(true);
118
119
120
121
}

void InputMethod::hide()
{
122
    setActive(false);
123
124
}

125
void InputMethod::setActive(bool active)
126
{
127
    if (m_active) {
128
129
        waylandServer()->inputMethod()->sendDeactivate();
    }
130

131
    if (active) {
132
133
134
135
136
        if (!m_enabled) {
            return;
        }

        waylandServer()->inputMethod()->sendActivate();
137
        adoptInputMethodContext();
138
139
    } else {
        updateInputPanelState();
140
    }
141

142
143
144
    if (m_active != active) {
        m_active = active;
        Q_EMIT activeChanged(active);
145
    }
146
147
}

148
void InputMethod::clientAdded(AbstractClient* client)
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
{
    if (!client->isInputMethod()) {
        return;
    }
    m_inputClient = client;
    auto refreshFrame = [this] {
        if (!m_trackedClient) {
            return;
        }

        if (m_inputClient && !m_inputClient->inputGeometry().isEmpty()) {
            m_trackedClient->setVirtualKeyboardGeometry(m_inputClient->inputGeometry());
        }
    };
    connect(client->surface(), &SurfaceInterface::inputChanged, this, refreshFrame);
    connect(client, &QObject::destroyed, this, [this] {
        if (m_trackedClient) {
            m_trackedClient->setVirtualKeyboardGeometry({});
        }
    });
    connect(m_inputClient, &AbstractClient::frameGeometryChanged, this, refreshFrame);
170
171
172
173
    // 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.
174
175
    connect(m_inputClient, &AbstractClient::windowHidden, this, &InputMethod::hide);
    connect(m_inputClient, &AbstractClient::windowClosed, this, &InputMethod::hide);
176
177
}

178
void InputMethod::handleFocusedSurfaceChanged()
179
{
180
181
182
    SurfaceInterface *focusedSurface = waylandServer()->seat()->focusedTextInputSurface();
    if (focusedSurface) {
        AbstractClient *focusedClient = waylandServer()->findClient(focusedSurface);
183
184
        // Reset the old client virtual keybaord geom if necessary
        // Old and new clients could be the same if focus moves between subsurfaces
185
        if (m_trackedClient != focusedClient) {
186
187
188
            if (m_trackedClient) {
                m_trackedClient->setVirtualKeyboardGeometry(QRect());
            }
189
            m_trackedClient = focusedClient;
190
        }
191
        updateInputPanelState();
192
    } else {
193
        setActive(false);
194
195
196
    }
}

197
void InputMethod::surroundingTextChanged()
198
{
199
200
    auto t2 = waylandServer()->seat()->textInputV2();
    auto t3 = waylandServer()->seat()->textInputV3();
201
202
203
204
    auto inputContext = waylandServer()->inputMethod()->context();
    if (!inputContext) {
        return;
    }
205
206
207
208
209
210
211
212
    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;
    }
213
214
}

215
void InputMethod::contentTypeChanged()
216
{
217
218
    auto t2 = waylandServer()->seat()->textInputV2();
    auto t3 = waylandServer()->seat()->textInputV3();
219
220
221
222
    auto inputContext = waylandServer()->inputMethod()->context();
    if (!inputContext) {
        return;
    }
223
224
225
226
227
228
    if (t2 && t2->isEnabled()) {
        inputContext->sendContentType(t2->contentHints(), t2->contentPurpose());
    }
    if (t3 && t3->isEnabled()) {
        inputContext->sendContentType(t3->contentHints(), t3->contentPurpose());
    }
229
230
}

231
void InputMethod::textInputInterfaceV2StateUpdated(quint32 serial, KWaylandServer::TextInputV2Interface::UpdateReason reason)
232
{
233
    auto t2 = waylandServer()->seat()->textInputV2();
234
235
236
237
    auto inputContext = waylandServer()->inputMethod()->context();
    if (!inputContext) {
        return;
    }
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
    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;
257
    }
258
259
}

260
void InputMethod::textInputInterfaceV2EnabledChanged()
261
{
262
    auto t = waylandServer()->seat()->textInputV2();
263
264
265
266
267
268
269
270
    if (t->isEnabled()) {
        waylandServer()->inputMethod()->sendActivate();
        adoptInputMethodContext();
    } else {
        hide();
    }
}

271
void InputMethod::textInputInterfaceV3EnabledChanged()
272
273
274
275
276
277
278
279
280
281
282
{
    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;
    }
283
284
285
286
287
    auto context = waylandServer()->inputMethod()->context();
    if (context) {
        context->sendReset();
        adoptInputMethodContext();
    }
288
289
}

290
void InputMethod::stateCommitted(uint32_t serial)
291
292
293
294
295
296
297
298
{
    auto inputContext = waylandServer()->inputMethod()->context();
    if (!inputContext) {
        return;
    }
    inputContext->sendCommitState(serial);
}

299
void InputMethod::setEnabled(bool enabled)
300
301
302
303
304
{
    if (m_enabled == enabled) {
        return;
    }
    m_enabled = enabled;
305
    emit enabledChanged(m_enabled);
306
307
308
309
310
311
312
313
314
315

    // 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);
316
317
318
319
320
321
322
323
324
325
326
327

    auto textInputV2 = waylandServer()->seat()->textInputV2();
    auto textInputV3 = waylandServer()->seat()->textInputV3();
    if (m_enabled) {
        connect(textInputV2, &TextInputV2Interface::enabledChanged, this, &InputMethod::textInputInterfaceV2EnabledChanged, Qt::UniqueConnection);
        connect(textInputV3, &TextInputV3Interface::enabledChanged, this, &InputMethod::textInputInterfaceV3EnabledChanged, Qt::UniqueConnection);
    } else {
        hide();

        disconnect(textInputV2, &TextInputV2Interface::enabledChanged, this, &InputMethod::textInputInterfaceV2EnabledChanged);
        disconnect(textInputV3, &TextInputV3Interface::enabledChanged, this, &InputMethod::textInputInterfaceV3EnabledChanged);
    }
328
329
}

330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
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;
    }
}

350
void InputMethod::keysymReceived(quint32 serial, quint32 time, quint32 sym, bool pressed, Qt::KeyboardModifiers modifiers)
351
352
353
{
    Q_UNUSED(serial)
    Q_UNUSED(time)
354
355
356
357
358
359
360
361
362
363
364
    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()) {
365
        KWaylandServer::KeyboardKeyState state;
366
        if (pressed) {
367
            state = KWaylandServer::KeyboardKeyState::Pressed;
368
        } else {
369
            state = KWaylandServer::KeyboardKeyState::Released;
370
        }
371
        waylandServer()->seat()->notifyKeyboardKey(keysymToKeycode(sym), state);
372
        return;
373
374
375
    }
}

376
void InputMethod::commitString(qint32 serial, const QString &text)
377
378
{
    Q_UNUSED(serial)
379
380
381
382
383
384
385
386
387
388
389
    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;
390
391
392
    }
}

393
void InputMethod::deleteSurroundingText(int32_t index, uint32_t length)
394
{
395
396
397
398
399
400
401
    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);
402
403
404
    }
}

405
void InputMethod::setCursorPosition(qint32 index, qint32 anchor)
406
{
407
408
409
    auto t2 = waylandServer()->seat()->textInputV2();
    if (t2 && t2->isEnabled()) {
        t2->setCursorPosition(index, anchor);
410
411
412
    }
}

413
void InputMethod::setLanguage(uint32_t serial, const QString &language)
414
{
415
416
417
418
    Q_UNUSED(serial)
    auto t2 = waylandServer()->seat()->textInputV2();
    if (t2 && t2->isEnabled()) {
        t2->setLanguage(language.toUtf8());
419
420
421
    }
}

422
void InputMethod::setTextDirection(uint32_t serial, Qt::LayoutDirection direction)
423
{
424
425
426
427
    Q_UNUSED(serial)
    auto t2 = waylandServer()->seat()->textInputV2();
    if (t2 && t2->isEnabled()) {
        t2->setTextDirection(direction);
428
429
430
    }
}

431
void InputMethod::setPreeditCursor(qint32 index)
432
{
433
434
435
436
437
438
439
440
441
    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);
442
443
444
    }
}

445

446
void InputMethod::setPreeditString(uint32_t serial, const QString &text, const QString &commit)
447
448
{
    Q_UNUSED(serial)
449
450
451
452
453
454
455
456
    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);
457
458
459
    }
}

460
void InputMethod::adoptInputMethodContext()
461
462
463
{
    auto inputContext = waylandServer()->inputMethod()->context();

464
465
466
467
468
469
470
    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());
471
472
        connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::language, this, &InputMethod::setLanguage);
        connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::textDirection, this, &InputMethod::setTextDirection);
473
474
475
476
477
478
    }

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

480
481
482
483
    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);
484
485
    connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::preeditString, this, &InputMethod::setPreeditString, Qt::UniqueConnection);
    connect(inputContext, &KWaylandServer::InputMethodContextV1Interface::preeditCursor, this, &InputMethod::setPreeditCursor, Qt::UniqueConnection);
486
487
}

488
void InputMethod::updateSni()
489
490
491
492
493
494
{
    if (!m_sni) {
        return;
    }
    if (m_enabled) {
        m_sni->setIconByName(QStringLiteral("input-keyboard-virtual-on"));
495
        m_sni->setTitle(i18n("Virtual Keyboard: enabled"));
496
497
    } else {
        m_sni->setIconByName(QStringLiteral("input-keyboard-virtual-off"));
498
        m_sni->setTitle(i18n("Virtual Keyboard: disabled"));
499
    }
500
    m_sni->setToolTipTitle(i18n("Whether to show the virtual keyboard on demand."));
501
502
}

503
void InputMethod::updateInputPanelState()
504
505
506
507
508
{
    if (!waylandServer()) {
        return;
    }

509
    auto t = waylandServer()->seat()->textInputV2();
510

511
    if (!t) {
512
513
514
        return;
    }

515
516
517
    if (m_trackedClient) {
        m_trackedClient->setVirtualKeyboardGeometry(m_inputClient ? m_inputClient->inputGeometry() : QRect());
    }
518
    t->setInputPanelState(m_inputClient && m_inputClient->isShown(false), QRect(0, 0, 0, 0));
519
520
521
}

}