2020-08-02 22:22:19 +00:00
|
|
|
/*
|
|
|
|
KWin - the KDE window manager
|
|
|
|
This file is part of the KDE project.
|
2016-02-15 12:42:48 +00:00
|
|
|
|
2020-08-02 22:22:19 +00:00
|
|
|
SPDX-FileCopyrightText: 2013, 2016 Martin Gräßlin <mgraesslin@kde.org>
|
2016-02-15 12:42:48 +00:00
|
|
|
|
2020-08-02 22:22:19 +00:00
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
*/
|
2022-02-23 13:27:05 +00:00
|
|
|
#include <config-kwin.h>
|
2016-02-15 12:42:48 +00:00
|
|
|
#include "keyboard_input.h"
|
2021-12-29 17:19:43 +00:00
|
|
|
#include "abstract_client.h"
|
2016-05-24 08:57:57 +00:00
|
|
|
#include "input_event.h"
|
2016-12-27 19:16:50 +00:00
|
|
|
#include "input_event_spy.h"
|
2021-12-29 17:19:43 +00:00
|
|
|
#include "inputmethod.h"
|
2017-01-14 17:06:41 +00:00
|
|
|
#include "keyboard_layout.h"
|
2017-01-27 05:50:50 +00:00
|
|
|
#include "keyboard_repeat.h"
|
2017-02-03 16:26:51 +00:00
|
|
|
#include "modifier_only_shortcuts.h"
|
2016-02-15 12:42:48 +00:00
|
|
|
#include "toplevel.h"
|
2022-01-18 08:35:52 +00:00
|
|
|
#include "utils/common.h"
|
2016-02-15 12:42:48 +00:00
|
|
|
#include "wayland_server.h"
|
|
|
|
#include "workspace.h"
|
|
|
|
// KWayland
|
2020-04-29 15:18:41 +00:00
|
|
|
#include <KWaylandServer/datadevice_interface.h>
|
2021-10-02 08:26:51 +00:00
|
|
|
#include <KWaylandServer/keyboard_interface.h>
|
2020-04-29 15:18:41 +00:00
|
|
|
#include <KWaylandServer/seat_interface.h>
|
2016-02-15 12:42:48 +00:00
|
|
|
//screenlocker
|
2022-02-28 18:58:35 +00:00
|
|
|
#if KWIN_BUILD_SCREENLOCKER
|
2016-02-15 12:42:48 +00:00
|
|
|
#include <KScreenLocker/KsldApp>
|
2022-02-23 13:27:05 +00:00
|
|
|
#endif
|
2016-02-15 12:42:48 +00:00
|
|
|
// Frameworks
|
2016-02-19 12:50:39 +00:00
|
|
|
#include <KGlobalAccel>
|
2016-02-15 12:42:48 +00:00
|
|
|
// Qt
|
|
|
|
#include <QKeyEvent>
|
2016-02-19 08:08:32 +00:00
|
|
|
|
2021-10-29 22:41:16 +00:00
|
|
|
#include <cmath>
|
|
|
|
|
2016-02-15 12:42:48 +00:00
|
|
|
namespace KWin
|
|
|
|
{
|
2016-02-19 08:08:56 +00:00
|
|
|
|
2016-02-15 12:42:48 +00:00
|
|
|
KeyboardInputRedirection::KeyboardInputRedirection(InputRedirection *parent)
|
|
|
|
: QObject(parent)
|
|
|
|
, m_input(parent)
|
|
|
|
, m_xkb(new Xkb(parent))
|
|
|
|
{
|
2017-08-13 18:43:28 +00:00
|
|
|
connect(m_xkb.data(), &Xkb::ledsChanged, this, &KeyboardInputRedirection::ledsChanged);
|
2017-08-13 18:53:36 +00:00
|
|
|
if (waylandServer()) {
|
|
|
|
m_xkb->setSeat(waylandServer()->seat());
|
|
|
|
}
|
2016-02-15 12:42:48 +00:00
|
|
|
}
|
|
|
|
|
2016-10-05 06:26:29 +00:00
|
|
|
KeyboardInputRedirection::~KeyboardInputRedirection() = default;
|
2016-02-15 12:42:48 +00:00
|
|
|
|
2017-01-13 19:41:21 +00:00
|
|
|
class KeyStateChangedSpy : public InputEventSpy
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
KeyStateChangedSpy(InputRedirection *input)
|
|
|
|
: m_input(input)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void keyEvent(KeyEvent *event) override
|
|
|
|
{
|
|
|
|
if (event->isAutoRepeat()) {
|
|
|
|
return;
|
|
|
|
}
|
2021-06-08 07:02:14 +00:00
|
|
|
Q_EMIT m_input->keyStateChanged(event->nativeScanCode(), event->type() == QEvent::KeyPress ? InputRedirection::KeyboardKeyPressed : InputRedirection::KeyboardKeyReleased);
|
2017-01-13 19:41:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2021-06-08 07:02:14 +00:00
|
|
|
Q_EMIT m_input->keyboardModifiersChanged(mods, m_modifiers);
|
2017-01-13 19:41:21 +00:00
|
|
|
m_modifiers = mods;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
InputRedirection *m_input;
|
|
|
|
Qt::KeyboardModifiers m_modifiers;
|
|
|
|
};
|
|
|
|
|
2016-02-15 12:42:48 +00:00
|
|
|
void KeyboardInputRedirection::init()
|
|
|
|
{
|
|
|
|
Q_ASSERT(!m_inited);
|
|
|
|
m_inited = true;
|
2017-01-27 16:15:32 +00:00
|
|
|
const auto config = kwinApp()->kxkbConfig();
|
2020-07-13 01:56:45 +00:00
|
|
|
m_xkb->setNumLockConfig(InputConfig::self()->inputConfig());
|
2017-01-27 16:15:32 +00:00
|
|
|
m_xkb->setConfig(config);
|
|
|
|
|
2021-10-02 08:26:51 +00:00
|
|
|
// 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);
|
|
|
|
|
2017-01-13 19:41:21 +00:00
|
|
|
m_input->installInputEventSpy(new KeyStateChangedSpy(m_input));
|
|
|
|
m_modifiersChangedSpy = new ModifiersChangedSpy(m_input);
|
|
|
|
m_input->installInputEventSpy(m_modifiersChangedSpy);
|
2021-01-06 16:25:03 +00:00
|
|
|
m_keyboardLayout = new KeyboardLayout(m_xkb.data(), config);
|
2017-01-14 17:06:41 +00:00
|
|
|
m_keyboardLayout->init();
|
|
|
|
m_input->installInputEventSpy(m_keyboardLayout);
|
2016-02-15 12:42:48 +00:00
|
|
|
|
2018-12-02 12:09:37 +00:00
|
|
|
if (waylandServer()->hasGlobalShortcutSupport()) {
|
|
|
|
m_input->installInputEventSpy(new ModifierOnlyShortcuts);
|
|
|
|
}
|
2017-02-03 16:26:51 +00:00
|
|
|
|
2017-01-27 05:50:50 +00:00
|
|
|
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);
|
2016-10-05 06:26:29 +00:00
|
|
|
|
2016-02-15 12:42:48 +00:00
|
|
|
connect(workspace(), &QObject::destroyed, this, [this] { m_inited = false; });
|
|
|
|
connect(waylandServer(), &QObject::destroyed, this, [this] { m_inited = false; });
|
2016-06-26 13:57:38 +00:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
);
|
2022-02-28 18:58:35 +00:00
|
|
|
#if KWIN_BUILD_SCREENLOCKER
|
2016-04-25 06:51:33 +00:00
|
|
|
if (waylandServer()->hasScreenLockerIntegration()) {
|
|
|
|
connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, &KeyboardInputRedirection::update);
|
|
|
|
}
|
2022-02-23 13:27:05 +00:00
|
|
|
#endif
|
2021-10-02 08:26:51 +00:00
|
|
|
|
|
|
|
reconfigure();
|
|
|
|
}
|
|
|
|
|
|
|
|
void KeyboardInputRedirection::reconfigure()
|
|
|
|
{
|
|
|
|
if (waylandServer()->seat()->keyboard()) {
|
|
|
|
const auto config = InputConfig::self()->inputConfig()->group(QStringLiteral("Keyboard"));
|
|
|
|
const int delay = config.readEntry("RepeatDelay", 660);
|
2021-10-29 22:41:16 +00:00
|
|
|
const int rate = std::ceil(config.readEntry("RepeatRate", 25.0));
|
2021-10-02 08:26:51 +00:00
|
|
|
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);
|
|
|
|
}
|
2016-02-15 12:42:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void KeyboardInputRedirection::update()
|
|
|
|
{
|
|
|
|
if (!m_inited) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
auto seat = waylandServer()->seat();
|
|
|
|
// TODO: this needs better integration
|
|
|
|
Toplevel *found = nullptr;
|
|
|
|
if (waylandServer()->isScreenLocked()) {
|
Drop some custom list typedefs
Summary:
Qt has its own thing where a type might also have corresponding list
alias, e.g. QObject and QObjectList, QWidget and QWidgetList. I don't
know why Qt does that, maybe for some historical reasons, but what
matters is that we copy this pattern here in KWin. While this pattern
might be useful with some long list types, for example
QList<QWeakPointer<TabBoxClient>> TabBoxClientList
in general, it causes more harm than good. For example, we've got two
new client types, do we need corresponding list typedefs for them? If
no, why do we have ClientList and so on?
Another problem with these typedefs is that you need to include utils.h
header in order to use them. A better way to handle such things is to
just forward declare a client class (if that's possible) and use it
directly with QList or QVector. This way translation units don't get
"bloated" with utils.h stuff for no apparent reason.
So, in order to make code more consistent and easier to follow, this
change drops some of our custom typedefs. Namely ConstClientList,
ClientList, DeletedList, UnmanagedList, ToplevelList, and GroupList.
Test Plan: Compiles.
Reviewers: #kwin
Subscribers: kwin
Tags: #kwin
Differential Revision: https://phabricator.kde.org/D24950
2019-10-16 09:11:04 +00:00
|
|
|
const QList<Toplevel *> &stacking = Workspace::self()->stackingOrder();
|
2016-02-15 12:42:48 +00:00
|
|
|
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());
|
|
|
|
}
|
2017-04-18 05:10:46 +00:00
|
|
|
} else if (!input()->isSelectingWindow()) {
|
2016-02-15 12:42:48 +00:00
|
|
|
found = workspace()->activeClient();
|
|
|
|
}
|
|
|
|
if (found && found->surface()) {
|
|
|
|
if (found->surface() != seat->focusedKeyboardSurface()) {
|
|
|
|
seat->setFocusedKeyboardSurface(found->surface());
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
seat->setFocusedKeyboardSurface(nullptr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-02 08:26:51 +00:00
|
|
|
void KeyboardInputRedirection::processKey(uint32_t key, InputRedirection::KeyboardKeyState state, uint32_t time, InputDevice *device)
|
2016-02-15 12:42:48 +00:00
|
|
|
{
|
2016-02-19 06:53:20 +00:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2020-12-13 03:06:16 +00:00
|
|
|
const quint32 previousLayout = m_xkb->currentLayout();
|
2016-02-19 06:53:20 +00:00
|
|
|
if (!autoRepeat) {
|
|
|
|
m_xkb->updateKey(key, state);
|
2016-02-15 12:42:48 +00:00
|
|
|
}
|
2016-02-19 06:53:20 +00:00
|
|
|
|
2016-08-29 15:04:13 +00:00
|
|
|
const xkb_keysym_t keySym = m_xkb->currentKeysym();
|
2021-03-15 17:34:41 +00:00
|
|
|
const Qt::KeyboardModifiers globalShortcutsModifiers = m_xkb->modifiersRelevantForGlobalShortcuts(key);
|
2016-05-24 08:57:57 +00:00
|
|
|
KeyEvent event(type,
|
2021-03-15 17:34:41 +00:00
|
|
|
m_xkb->toQtKey(keySym, key, globalShortcutsModifiers ? Qt::ControlModifier : Qt::KeyboardModifiers()),
|
2016-05-24 08:57:57 +00:00
|
|
|
m_xkb->modifiers(),
|
|
|
|
key,
|
|
|
|
keySym,
|
2016-08-29 15:04:13 +00:00
|
|
|
m_xkb->toString(keySym),
|
2016-05-24 08:57:57 +00:00
|
|
|
autoRepeat,
|
|
|
|
time,
|
|
|
|
device);
|
2021-03-15 17:34:41 +00:00
|
|
|
event.setModifiersRelevantForGlobalShortcuts(globalShortcutsModifiers);
|
2016-02-15 12:42:48 +00:00
|
|
|
|
2016-12-27 19:16:50 +00:00
|
|
|
m_input->processSpies(std::bind(&InputEventSpy::keyEvent, std::placeholders::_1, &event));
|
2017-02-03 16:26:51 +00:00
|
|
|
if (!m_inited) {
|
|
|
|
return;
|
|
|
|
}
|
2022-02-11 18:12:48 +00:00
|
|
|
input()->setLastInputHandler(this);
|
2016-12-24 10:00:08 +00:00
|
|
|
m_input->processFilters(std::bind(&InputEventFilter::keyEvent, std::placeholders::_1, &event));
|
2017-04-14 19:01:20 +00:00
|
|
|
|
|
|
|
m_xkb->forwardModifiers();
|
2022-01-03 04:55:05 +00:00
|
|
|
if (auto *inputmethod = InputMethod::self()) {
|
|
|
|
inputmethod->forwardModifiers(InputMethod::NoForce);
|
|
|
|
}
|
2020-12-13 03:06:16 +00:00
|
|
|
|
|
|
|
if (event.modifiersRelevantForGlobalShortcuts() == Qt::KeyboardModifier::NoModifier && type != QEvent::KeyRelease) {
|
|
|
|
m_keyboardLayout->checkLayoutChange(previousLayout);
|
|
|
|
}
|
2016-02-15 12:42:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void KeyboardInputRedirection::processModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group)
|
|
|
|
{
|
|
|
|
if (!m_inited) {
|
|
|
|
return;
|
|
|
|
}
|
2020-08-24 13:51:43 +00:00
|
|
|
const quint32 previousLayout = m_xkb->currentLayout();
|
2016-02-15 12:42:48 +00:00
|
|
|
// TODO: send to proper Client and also send when active Client changes
|
|
|
|
m_xkb->updateModifiers(modsDepressed, modsLatched, modsLocked, group);
|
2017-01-13 19:41:21 +00:00
|
|
|
m_modifiersChangedSpy->updateModifiers(modifiers());
|
2020-08-24 13:51:43 +00:00
|
|
|
m_keyboardLayout->checkLayoutChange(previousLayout);
|
2016-02-15 12:42:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2017-01-14 17:06:41 +00:00
|
|
|
m_keyboardLayout->resetLayout();
|
2016-02-15 12:42:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|