keyboard_input.cpp 7.67 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: 2013, 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 "keyboard_input.h"
10
#include "input_event.h"
11
#include "input_event_spy.h"
12
#include "keyboard_layout.h"
13
#include "keyboard_repeat.h"
14
#include "abstract_client.h"
15
#include "modifier_only_shortcuts.h"
16
#include "utils.h"
17
#include "screenlockerwatcher.h"
18 19 20 21
#include "toplevel.h"
#include "wayland_server.h"
#include "workspace.h"
// KWayland
22 23
#include <KWaylandServer/datadevice_interface.h>
#include <KWaylandServer/seat_interface.h>
24 25 26
//screenlocker
#include <KScreenLocker/KsldApp>
// Frameworks
27
#include <KGlobalAccel>
28 29
// Qt
#include <QKeyEvent>
30

31 32
namespace KWin
{
33

34 35 36 37 38
KeyboardInputRedirection::KeyboardInputRedirection(InputRedirection *parent)
    : QObject(parent)
    , m_input(parent)
    , m_xkb(new Xkb(parent))
{
Martin Flöser's avatar
Martin Flöser committed
39
    connect(m_xkb.data(), &Xkb::ledsChanged, this, &KeyboardInputRedirection::ledsChanged);
40 41 42
    if (waylandServer()) {
        m_xkb->setSeat(waylandServer()->seat());
    }
43 44
}

Martin Flöser's avatar
Martin Flöser committed
45
KeyboardInputRedirection::~KeyboardInputRedirection() = default;
46

47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
class KeyStateChangedSpy : public InputEventSpy
{
public:
    KeyStateChangedSpy(InputRedirection *input)
        : m_input(input)
    {
    }

    void keyEvent(KeyEvent *event) override
    {
        if (event->isAutoRepeat()) {
            return;
        }
        emit m_input->keyStateChanged(event->nativeScanCode(), event->type() == QEvent::KeyPress ? InputRedirection::KeyboardKeyPressed : InputRedirection::KeyboardKeyReleased);
    }

private:
    InputRedirection *m_input;
};

class ModifiersChangedSpy : public InputEventSpy
{
public:
    ModifiersChangedSpy(InputRedirection *input)
        : m_input(input)
        , m_modifiers()
    {
    }

    void keyEvent(KeyEvent *event) override
    {
        if (event->isAutoRepeat()) {
            return;
        }
        updateModifiers(event->modifiers());
    }

    void updateModifiers(Qt::KeyboardModifiers mods)
    {
        if (mods == m_modifiers) {
            return;
        }
        emit m_input->keyboardModifiersChanged(mods, m_modifiers);
        m_modifiers = mods;
    }

private:
    InputRedirection *m_input;
    Qt::KeyboardModifiers m_modifiers;
};

98 99 100 101
void KeyboardInputRedirection::init()
{
    Q_ASSERT(!m_inited);
    m_inited = true;
102
    const auto config = kwinApp()->kxkbConfig();
103
    m_xkb->setNumLockConfig(InputConfig::self()->inputConfig());
104 105
    m_xkb->setConfig(config);

106 107 108
    m_input->installInputEventSpy(new KeyStateChangedSpy(m_input));
    m_modifiersChangedSpy = new ModifiersChangedSpy(m_input);
    m_input->installInputEventSpy(m_modifiersChangedSpy);
109
    m_keyboardLayout = new KeyboardLayout(m_xkb.data());
110
    m_keyboardLayout->setConfig(config);
111 112
    m_keyboardLayout->init();
    m_input->installInputEventSpy(m_keyboardLayout);
113

114 115 116
    if (waylandServer()->hasGlobalShortcutSupport()) {
        m_input->installInputEventSpy(new ModifierOnlyShortcuts);
    }
117

118 119 120 121
    KeyboardRepeat *keyRepeatSpy = new KeyboardRepeat(m_xkb.data());
    connect(keyRepeatSpy, &KeyboardRepeat::keyRepeat, this,
        std::bind(&KeyboardInputRedirection::processKey, this, std::placeholders::_1, InputRedirection::KeyboardKeyAutoRepeat, std::placeholders::_2, nullptr));
    m_input->installInputEventSpy(keyRepeatSpy);
Martin Flöser's avatar
Martin Flöser committed
122

123 124
    connect(workspace(), &QObject::destroyed, this, [this] { m_inited = false; });
    connect(waylandServer(), &QObject::destroyed, this, [this] { m_inited = false; });
125 126 127 128 129 130 131 132 133 134 135
    connect(workspace(), &Workspace::clientActivated, this,
        [this] {
            disconnect(m_activeClientSurfaceChangedConnection);
            if (auto c = workspace()->activeClient()) {
                m_activeClientSurfaceChangedConnection = connect(c, &Toplevel::surfaceChanged, this, &KeyboardInputRedirection::update);
            } else {
                m_activeClientSurfaceChangedConnection = QMetaObject::Connection();
            }
            update();
        }
    );
136 137 138
    if (waylandServer()->hasScreenLockerIntegration()) {
        connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, &KeyboardInputRedirection::update);
    }
139 140 141 142 143 144 145 146 147 148 149
}

void KeyboardInputRedirection::update()
{
    if (!m_inited) {
        return;
    }
    auto seat = waylandServer()->seat();
    // TODO: this needs better integration
    Toplevel *found = nullptr;
    if (waylandServer()->isScreenLocked()) {
150
        const QList<Toplevel *> &stacking = Workspace::self()->stackingOrder();
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
        if (!stacking.isEmpty()) {
            auto it = stacking.end();
            do {
                --it;
                Toplevel *t = (*it);
                if (t->isDeleted()) {
                    // a deleted window doesn't get mouse events
                    continue;
                }
                if (!t->isLockScreen()) {
                    continue;
                }
                if (!t->readyForPainting()) {
                    continue;
                }
                found = t;
                break;
            } while (it != stacking.begin());
        }
170
    } else if (!input()->isSelectingWindow()) {
171 172 173 174 175 176 177 178 179 180 181
        found = workspace()->activeClient();
    }
    if (found && found->surface()) {
        if (found->surface() != seat->focusedKeyboardSurface()) {
            seat->setFocusedKeyboardSurface(found->surface());
        }
    } else {
        seat->setFocusedKeyboardSurface(nullptr);
    }
}

182
void KeyboardInputRedirection::processKey(uint32_t key, InputRedirection::KeyboardKeyState state, uint32_t time, LibInput::Device *device)
183
{
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
    QEvent::Type type;
    bool autoRepeat = false;
    switch (state) {
    case InputRedirection::KeyboardKeyAutoRepeat:
        autoRepeat = true;
        // fall through
    case InputRedirection::KeyboardKeyPressed:
        type = QEvent::KeyPress;
        break;
    case InputRedirection::KeyboardKeyReleased:
        type = QEvent::KeyRelease;
        break;
    default:
        Q_UNREACHABLE();
    }

200
    const quint32 previousLayout = m_xkb->currentLayout();
201 202
    if (!autoRepeat) {
        m_xkb->updateKey(key, state);
203
    }
204

205
    const xkb_keysym_t keySym = m_xkb->currentKeysym();
206 207 208 209 210
    KeyEvent event(type,
                   m_xkb->toQtKey(keySym),
                   m_xkb->modifiers(),
                   key,
                   keySym,
211
                   m_xkb->toString(keySym),
212 213 214
                   autoRepeat,
                   time,
                   device);
215
    event.setModifiersRelevantForGlobalShortcuts(m_xkb->modifiersRelevantForGlobalShortcuts());
216

217
    m_input->processSpies(std::bind(&InputEventSpy::keyEvent, std::placeholders::_1, &event));
218 219 220
    if (!m_inited) {
        return;
    }
221
    m_input->processFilters(std::bind(&InputEventFilter::keyEvent, std::placeholders::_1, &event));
222 223

    m_xkb->forwardModifiers();
224

225
    if (event.modifiersRelevantForGlobalShortcuts() == Qt::KeyboardModifier::NoModifier) {
226
        m_keyboardLayout->checkLayoutChange(previousLayout);
227
    }
228 229 230 231 232 233 234
}

void KeyboardInputRedirection::processModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group)
{
    if (!m_inited) {
        return;
    }
235
    const quint32 previousLayout = m_xkb->currentLayout();
236 237
    // TODO: send to proper Client and also send when active Client changes
    m_xkb->updateModifiers(modsDepressed, modsLatched, modsLocked, group);
238
    m_modifiersChangedSpy->updateModifiers(modifiers());
239
    m_keyboardLayout->checkLayoutChange(previousLayout);
240 241 242 243 244 245 246 247 248
}

void KeyboardInputRedirection::processKeymapChange(int fd, uint32_t size)
{
    if (!m_inited) {
        return;
    }
    // TODO: should we pass the keymap to our Clients? Or only to the currently active one and update
    m_xkb->installKeymap(fd, size);
249
    m_keyboardLayout->resetLayout();
250 251 252
}

}