kwin/src/keyboard_input.cpp
Andrey Butirsky 43aec9ca18 fixup! [wayland] fix ignored keyboard RepeatRate
Ceiling so that it repeats on minimal 0.x rates accessible via UI

BUG: 443721
2021-11-08 10:37:51 +02:00

278 lines
9 KiB
C++

/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2013, 2016 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "keyboard_input.h"
#include "input_event.h"
#include "input_event_spy.h"
#include "keyboard_layout.h"
#include "keyboard_repeat.h"
#include "abstract_client.h"
#include "modifier_only_shortcuts.h"
#include "utils.h"
#include "screenlockerwatcher.h"
#include "toplevel.h"
#include "wayland_server.h"
#include "workspace.h"
// KWayland
#include <KWaylandServer/datadevice_interface.h>
#include <KWaylandServer/keyboard_interface.h>
#include <KWaylandServer/seat_interface.h>
//screenlocker
#include <KScreenLocker/KsldApp>
// Frameworks
#include <KGlobalAccel>
// Qt
#include <QKeyEvent>
#include <cmath>
namespace KWin
{
KeyboardInputRedirection::KeyboardInputRedirection(InputRedirection *parent)
: QObject(parent)
, m_input(parent)
, m_xkb(new Xkb(parent))
{
connect(m_xkb.data(), &Xkb::ledsChanged, this, &KeyboardInputRedirection::ledsChanged);
if (waylandServer()) {
m_xkb->setSeat(waylandServer()->seat());
}
}
KeyboardInputRedirection::~KeyboardInputRedirection() = default;
class KeyStateChangedSpy : public InputEventSpy
{
public:
KeyStateChangedSpy(InputRedirection *input)
: m_input(input)
{
}
void keyEvent(KeyEvent *event) override
{
if (event->isAutoRepeat()) {
return;
}
Q_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;
}
Q_EMIT m_input->keyboardModifiersChanged(mods, m_modifiers);
m_modifiers = mods;
}
private:
InputRedirection *m_input;
Qt::KeyboardModifiers m_modifiers;
};
void KeyboardInputRedirection::init()
{
Q_ASSERT(!m_inited);
m_inited = true;
const auto config = kwinApp()->kxkbConfig();
m_xkb->setNumLockConfig(InputConfig::self()->inputConfig());
m_xkb->setConfig(config);
// Workaround for QTBUG-54371: if there is no real keyboard Qt doesn't request virtual keyboard
waylandServer()->seat()->setHasKeyboard(true);
// connect(m_input, &InputRedirection::hasAlphaNumericKeyboardChanged,
// waylandServer()->seat(), &KWaylandServer::SeatInterface::setHasKeyboard);
m_input->installInputEventSpy(new KeyStateChangedSpy(m_input));
m_modifiersChangedSpy = new ModifiersChangedSpy(m_input);
m_input->installInputEventSpy(m_modifiersChangedSpy);
m_keyboardLayout = new KeyboardLayout(m_xkb.data(), config);
m_keyboardLayout->init();
m_input->installInputEventSpy(m_keyboardLayout);
if (waylandServer()->hasGlobalShortcutSupport()) {
m_input->installInputEventSpy(new ModifierOnlyShortcuts);
}
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);
connect(workspace(), &QObject::destroyed, this, [this] { m_inited = false; });
connect(waylandServer(), &QObject::destroyed, this, [this] { m_inited = false; });
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();
}
);
if (waylandServer()->hasScreenLockerIntegration()) {
connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, &KeyboardInputRedirection::update);
}
reconfigure();
}
void KeyboardInputRedirection::reconfigure()
{
if (waylandServer()->seat()->keyboard()) {
const auto config = InputConfig::self()->inputConfig()->group(QStringLiteral("Keyboard"));
const int delay = config.readEntry("RepeatDelay", 660);
const int rate = std::ceil(config.readEntry("RepeatRate", 25.0));
const QString repeatMode = config.readEntry("KeyRepeat", "repeat");
// when the clients will repeat the character or turn repeat key events into an accent character selection, we want
// to tell the clients that we are indeed repeating keys.
const bool enabled = repeatMode == QLatin1String("accent") || repeatMode == QLatin1String("repeat");
waylandServer()->seat()->keyboard()->setRepeatInfo(enabled ? rate : 0, delay);
}
}
void KeyboardInputRedirection::update()
{
if (!m_inited) {
return;
}
auto seat = waylandServer()->seat();
// TODO: this needs better integration
Toplevel *found = nullptr;
if (waylandServer()->isScreenLocked()) {
const QList<Toplevel *> &stacking = Workspace::self()->stackingOrder();
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());
}
} else if (!input()->isSelectingWindow()) {
found = workspace()->activeClient();
}
if (found && found->surface()) {
if (found->surface() != seat->focusedKeyboardSurface()) {
seat->setFocusedKeyboardSurface(found->surface());
}
} else {
seat->setFocusedKeyboardSurface(nullptr);
}
}
void KeyboardInputRedirection::processKey(uint32_t key, InputRedirection::KeyboardKeyState state, uint32_t time, InputDevice *device)
{
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();
}
const quint32 previousLayout = m_xkb->currentLayout();
if (!autoRepeat) {
m_xkb->updateKey(key, state);
}
const xkb_keysym_t keySym = m_xkb->currentKeysym();
const Qt::KeyboardModifiers globalShortcutsModifiers = m_xkb->modifiersRelevantForGlobalShortcuts(key);
KeyEvent event(type,
m_xkb->toQtKey(keySym, key, globalShortcutsModifiers ? Qt::ControlModifier : Qt::KeyboardModifiers()),
m_xkb->modifiers(),
key,
keySym,
m_xkb->toString(keySym),
autoRepeat,
time,
device);
event.setModifiersRelevantForGlobalShortcuts(globalShortcutsModifiers);
m_input->processSpies(std::bind(&InputEventSpy::keyEvent, std::placeholders::_1, &event));
if (!m_inited) {
return;
}
m_lastEventTime = time;
m_input->processFilters(std::bind(&InputEventFilter::keyEvent, std::placeholders::_1, &event));
m_xkb->forwardModifiers();
if (event.modifiersRelevantForGlobalShortcuts() == Qt::KeyboardModifier::NoModifier && type != QEvent::KeyRelease) {
m_keyboardLayout->checkLayoutChange(previousLayout);
}
}
void KeyboardInputRedirection::processModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group)
{
if (!m_inited) {
return;
}
const quint32 previousLayout = m_xkb->currentLayout();
// TODO: send to proper Client and also send when active Client changes
m_xkb->updateModifiers(modsDepressed, modsLatched, modsLocked, group);
m_modifiersChangedSpy->updateModifiers(modifiers());
m_keyboardLayout->checkLayoutChange(previousLayout);
}
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);
m_keyboardLayout->resetLayout();
}
}