File indexing completed on 2024-04-28 05:30:21

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2013, 2016 Martin Gräßlin <mgraesslin@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "keyboard_input.h"
0010 
0011 #include <config-kwin.h>
0012 
0013 #include "input_event.h"
0014 #include "input_event_spy.h"
0015 #include "inputmethod.h"
0016 #include "keyboard_layout.h"
0017 #include "keyboard_repeat.h"
0018 #include "modifier_only_shortcuts.h"
0019 #include "wayland/datadevice.h"
0020 #include "wayland/keyboard.h"
0021 #include "wayland/seat.h"
0022 #include "wayland_server.h"
0023 #include "window.h"
0024 #include "workspace.h"
0025 #include "xkb.h"
0026 // screenlocker
0027 #if KWIN_BUILD_SCREENLOCKER
0028 #include <KScreenLocker/KsldApp>
0029 #endif
0030 // Frameworks
0031 #include <KGlobalAccel>
0032 // Qt
0033 #include <QKeyEvent>
0034 
0035 #include <cmath>
0036 
0037 namespace KWin
0038 {
0039 
0040 KeyboardInputRedirection::KeyboardInputRedirection(InputRedirection *parent)
0041     : QObject(parent)
0042     , m_input(parent)
0043     , m_xkb(new Xkb(kwinApp()->followLocale1()))
0044 {
0045     connect(m_xkb.get(), &Xkb::ledsChanged, this, &KeyboardInputRedirection::ledsChanged);
0046     if (waylandServer()) {
0047         m_xkb->setSeat(waylandServer()->seat());
0048     }
0049 }
0050 
0051 KeyboardInputRedirection::~KeyboardInputRedirection() = default;
0052 
0053 Xkb *KeyboardInputRedirection::xkb() const
0054 {
0055     return m_xkb.get();
0056 }
0057 
0058 Qt::KeyboardModifiers KeyboardInputRedirection::modifiers() const
0059 {
0060     return m_xkb->modifiers();
0061 }
0062 
0063 Qt::KeyboardModifiers KeyboardInputRedirection::modifiersRelevantForGlobalShortcuts() const
0064 {
0065     return m_xkb->modifiersRelevantForGlobalShortcuts();
0066 }
0067 
0068 class KeyStateChangedSpy : public InputEventSpy
0069 {
0070 public:
0071     KeyStateChangedSpy(InputRedirection *input)
0072         : m_input(input)
0073     {
0074     }
0075 
0076     void keyEvent(KeyEvent *event) override
0077     {
0078         if (event->isAutoRepeat()) {
0079             return;
0080         }
0081         Q_EMIT m_input->keyStateChanged(event->nativeScanCode(), event->type() == QEvent::KeyPress ? InputRedirection::KeyboardKeyPressed : InputRedirection::KeyboardKeyReleased);
0082     }
0083 
0084 private:
0085     InputRedirection *m_input;
0086 };
0087 
0088 class ModifiersChangedSpy : public InputEventSpy
0089 {
0090 public:
0091     ModifiersChangedSpy(InputRedirection *input)
0092         : m_input(input)
0093         , m_modifiers()
0094     {
0095     }
0096 
0097     void keyEvent(KeyEvent *event) override
0098     {
0099         if (event->isAutoRepeat()) {
0100             return;
0101         }
0102         const Qt::KeyboardModifiers mods = event->modifiers();
0103         if (mods == m_modifiers) {
0104             return;
0105         }
0106         Q_EMIT m_input->keyboardModifiersChanged(mods, m_modifiers);
0107         m_modifiers = mods;
0108     }
0109 
0110 private:
0111     InputRedirection *m_input;
0112     Qt::KeyboardModifiers m_modifiers;
0113 };
0114 
0115 void KeyboardInputRedirection::init()
0116 {
0117     Q_ASSERT(!m_inited);
0118     m_inited = true;
0119     const auto config = kwinApp()->kxkbConfig();
0120     m_xkb->setNumLockConfig(kwinApp()->inputConfig());
0121     m_xkb->setConfig(config);
0122 
0123     // Workaround for QTBUG-54371: if there is no real keyboard Qt doesn't request virtual keyboard
0124     waylandServer()->seat()->setHasKeyboard(true);
0125     // connect(m_input, &InputRedirection::hasAlphaNumericKeyboardChanged,
0126     //         waylandServer()->seat(), &KWin::SeatInterface::setHasKeyboard);
0127 
0128     m_input->installInputEventSpy(new KeyStateChangedSpy(m_input));
0129     m_modifiersChangedSpy = new ModifiersChangedSpy(m_input);
0130     m_input->installInputEventSpy(m_modifiersChangedSpy);
0131     m_keyboardLayout = new KeyboardLayout(m_xkb.get(), config);
0132     m_keyboardLayout->init();
0133     m_input->installInputEventSpy(m_keyboardLayout);
0134 
0135     if (waylandServer()->hasGlobalShortcutSupport()) {
0136         m_input->installInputEventSpy(new ModifierOnlyShortcuts);
0137     }
0138 
0139     KeyboardRepeat *keyRepeatSpy = new KeyboardRepeat(m_xkb.get());
0140     connect(keyRepeatSpy, &KeyboardRepeat::keyRepeat, this,
0141             std::bind(&KeyboardInputRedirection::processKey, this, std::placeholders::_1, InputRedirection::KeyboardKeyAutoRepeat, std::placeholders::_2, nullptr));
0142     m_input->installInputEventSpy(keyRepeatSpy);
0143 
0144     connect(workspace(), &QObject::destroyed, this, [this] {
0145         m_inited = false;
0146     });
0147     connect(waylandServer(), &QObject::destroyed, this, [this] {
0148         m_inited = false;
0149     });
0150     connect(workspace(), &Workspace::windowActivated, this, [this] {
0151         disconnect(m_activeWindowSurfaceChangedConnection);
0152         if (auto window = workspace()->activeWindow()) {
0153             m_activeWindowSurfaceChangedConnection = connect(window, &Window::surfaceChanged, this, &KeyboardInputRedirection::update);
0154         } else {
0155             m_activeWindowSurfaceChangedConnection = QMetaObject::Connection();
0156         }
0157         update();
0158     });
0159 #if KWIN_BUILD_SCREENLOCKER
0160     if (waylandServer()->hasScreenLockerIntegration()) {
0161         connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, &KeyboardInputRedirection::update);
0162     }
0163 #endif
0164 
0165     reconfigure();
0166 }
0167 
0168 void KeyboardInputRedirection::reconfigure()
0169 {
0170     if (!m_inited) {
0171         return;
0172     }
0173     if (waylandServer()->seat()->keyboard()) {
0174         const auto config = kwinApp()->inputConfig()->group(QStringLiteral("Keyboard"));
0175         const int delay = config.readEntry("RepeatDelay", 660);
0176         const int rate = std::ceil(config.readEntry("RepeatRate", 25.0));
0177         const QString repeatMode = config.readEntry("KeyRepeat", "repeat");
0178         // when the clients will repeat the character or turn repeat key events into an accent character selection, we want
0179         // to tell the clients that we are indeed repeating keys.
0180         const bool enabled = repeatMode == QLatin1String("accent") || repeatMode == QLatin1String("repeat");
0181 
0182         waylandServer()->seat()->keyboard()->setRepeatInfo(enabled ? rate : 0, delay);
0183     }
0184 }
0185 
0186 void KeyboardInputRedirection::update()
0187 {
0188     if (!m_inited) {
0189         return;
0190     }
0191     auto seat = waylandServer()->seat();
0192     // TODO: this needs better integration
0193     Window *found = nullptr;
0194     if (waylandServer()->isScreenLocked()) {
0195         const QList<Window *> &stacking = Workspace::self()->stackingOrder();
0196         if (!stacking.isEmpty()) {
0197             auto it = stacking.end();
0198             do {
0199                 --it;
0200                 Window *t = (*it);
0201                 if (t->isDeleted()) {
0202                     // a deleted window doesn't get mouse events
0203                     continue;
0204                 }
0205                 if (!t->isLockScreen()) {
0206                     continue;
0207                 }
0208                 if (!t->readyForPainting()) {
0209                     continue;
0210                 }
0211                 found = t;
0212                 break;
0213             } while (it != stacking.begin());
0214         }
0215     } else if (!input()->isSelectingWindow()) {
0216         found = workspace()->activeWindow();
0217     }
0218     if (found && found->surface()) {
0219         if (found->surface() != seat->focusedKeyboardSurface()) {
0220             seat->setFocusedKeyboardSurface(found->surface());
0221         }
0222     } else {
0223         seat->setFocusedKeyboardSurface(nullptr);
0224     }
0225 }
0226 
0227 void KeyboardInputRedirection::processKey(uint32_t key, InputRedirection::KeyboardKeyState state, std::chrono::microseconds time, InputDevice *device)
0228 {
0229     QEvent::Type type;
0230     bool autoRepeat = false;
0231     switch (state) {
0232     case InputRedirection::KeyboardKeyAutoRepeat:
0233         autoRepeat = true;
0234         // fall through
0235     case InputRedirection::KeyboardKeyPressed:
0236         type = QEvent::KeyPress;
0237         break;
0238     case InputRedirection::KeyboardKeyReleased:
0239         type = QEvent::KeyRelease;
0240         break;
0241     default:
0242         Q_UNREACHABLE();
0243     }
0244 
0245     const quint32 previousLayout = m_xkb->currentLayout();
0246     if (!autoRepeat) {
0247         m_xkb->updateKey(key, state);
0248     }
0249 
0250     const xkb_keysym_t keySym = m_xkb->currentKeysym();
0251     const Qt::KeyboardModifiers globalShortcutsModifiers = m_xkb->modifiersRelevantForGlobalShortcuts(key);
0252     KeyEvent event(type,
0253                    m_xkb->toQtKey(keySym, key, globalShortcutsModifiers ? Qt::ControlModifier : Qt::KeyboardModifiers()),
0254                    m_xkb->modifiers(),
0255                    key,
0256                    keySym,
0257                    m_xkb->toString(keySym),
0258                    autoRepeat,
0259                    time,
0260                    device);
0261     event.setModifiersRelevantForGlobalShortcuts(globalShortcutsModifiers);
0262     event.setModifiersRelevantForTabBox(m_xkb->modifiersRelevantForTabBox());
0263 
0264     m_input->processSpies(std::bind(&InputEventSpy::keyEvent, std::placeholders::_1, &event));
0265     if (!m_inited) {
0266         return;
0267     }
0268     input()->setLastInputHandler(this);
0269     m_input->processFilters(std::bind(&InputEventFilter::keyEvent, std::placeholders::_1, &event));
0270 
0271     m_xkb->forwardModifiers();
0272     if (auto *inputmethod = kwinApp()->inputMethod()) {
0273         inputmethod->forwardModifiers(InputMethod::NoForce);
0274     }
0275 
0276     if (event.modifiersRelevantForGlobalShortcuts() == Qt::KeyboardModifier::NoModifier && type != QEvent::KeyRelease) {
0277         m_keyboardLayout->checkLayoutChange(previousLayout);
0278     }
0279 }
0280 
0281 }
0282 
0283 #include "moc_keyboard_input.cpp"